Commit ec6a6c26 authored by Esmael El-Moslimany's avatar Esmael El-Moslimany Committed by Commit Bot

Settings WebUI: settings-slider, handle keyboard repeat without feedback loop

Bug: 949479
Change-Id: Icace96388ff832fadbf69918c1aa804b07bfc2bf
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1596623
Commit-Queue: Esmael El-Moslimany <aee@chromium.org>
Reviewed-by: default avatarHector Carmona <hcarmona@chromium.org>
Cr-Commit-Position: refs/heads/master@{#657790}
parent 11cb9141
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
} }
#label-end { #label-end {
-webkit-margin-begin: 4px; margin-inline-start: 4px;
} }
</style> </style>
<template is="dom-if" if="[[pref.controlledBy]]" restamp> <template is="dom-if" if="[[pref.controlledBy]]" restamp>
...@@ -59,7 +59,8 @@ ...@@ -59,7 +59,8 @@
<div class="outer"> <div class="outer">
<cr-slider id="slider" disabled$="[[disableSlider_]]" ticks="[[ticks]]" <cr-slider id="slider" disabled$="[[disableSlider_]]" ticks="[[ticks]]"
on-cr-slider-value-changed="onSliderChanged_" max="[[max]]" on-cr-slider-value-changed="onSliderChanged_" max="[[max]]"
min="[[min]]" on-dragging-changed="onSliderChanged_"></cr-slider> min="[[min]]" on-dragging-changed="onSliderChanged_"
on-updating-from-key="onSliderChanged_"></cr-slider>
<div id="labels" disabled$="[[disableSlider_]]"> <div id="labels" disabled$="[[disableSlider_]]">
<div id="label-begin">[[labelMin]]</div> <div id="label-begin">[[labelMin]]</div>
<div id="label-end">[[labelMax]]</div> <div id="label-end">[[labelMax]]</div>
......
...@@ -126,7 +126,8 @@ Polymer({ ...@@ -126,7 +126,8 @@ Polymer({
* @private * @private
*/ */
valueChanged_: function() { valueChanged_: function() {
if (this.pref == undefined || !this.loaded_) { if (this.pref == undefined || !this.loaded_ || this.$.slider.dragging ||
this.$.slider.updatingFromKey) {
return; return;
} }
...@@ -141,18 +142,7 @@ Polymer({ ...@@ -141,18 +142,7 @@ Polymer({
// The preference and slider values are continuous when |ticks| is empty. // The preference and slider values are continuous when |ticks| is empty.
if (numTicks == 0) { if (numTicks == 0) {
// This method is handling a preference value change. If the slider is, this.$.slider.value = prefValue * this.scale;
// in a dragging state, that change is discarded and the the preference
// value is updated based on the slider value.
if (this.$.slider.dragging) {
const prefValueFromSlider = this.$.slider.value / this.scale;
if (this.updateValueInstantly && prefValue != prefValueFromSlider) {
this.set('pref.value', prefValueFromSlider);
}
} else {
// When not dragging, simply update the slider value.
this.$.slider.value = prefValue * this.scale;
}
return; return;
} }
...@@ -162,15 +152,6 @@ Polymer({ ...@@ -162,15 +152,6 @@ Polymer({
this.$.slider.markerCount = this.$.slider.markerCount =
(this.showMarkers || numTicks <= MAX_TICKS) ? numTicks : 0; (this.showMarkers || numTicks <= MAX_TICKS) ? numTicks : 0;
if (this.$.slider.dragging) {
const tickValue = this.getTickValueAtIndex_(this.$.slider.value);
if (this.updateValueInstantly && this.pref.value != tickValue) {
this.set('pref.value', tickValue);
}
return;
}
// Convert from the public |value| to the slider index (where the knob // Convert from the public |value| to the slider index (where the knob
// should be positioned on the slider). // should be positioned on the slider).
const index = const index =
......
...@@ -82,6 +82,7 @@ CrSettingsSliderTest.prototype = { ...@@ -82,6 +82,7 @@ CrSettingsSliderTest.prototype = {
/** @override */ /** @override */
extraLibraries: CrSettingsBrowserTest.prototype.extraLibraries.concat([ extraLibraries: CrSettingsBrowserTest.prototype.extraLibraries.concat([
'test_util.js',
'settings_slider_tests.js', 'settings_slider_tests.js',
]), ]),
}; };
......
...@@ -27,36 +27,41 @@ suite('SettingsSlider', function() { ...@@ -27,36 +27,41 @@ suite('SettingsSlider', function() {
return PolymerTest.flushTasks(); return PolymerTest.flushTasks();
}); });
function press(key) {
MockInteractions.keyDownOn(crSlider, null, [], key);
MockInteractions.keyUpOn(crSlider, null, [], key);
}
function pressArrowRight() { function pressArrowRight() {
MockInteractions.pressAndReleaseKeyOn(crSlider, 39, [], 'ArrowRight'); press('ArrowRight');
} }
function pressArrowLeft() { function pressArrowLeft() {
MockInteractions.pressAndReleaseKeyOn(crSlider, 37, [], 'ArrowLeft'); press('ArrowLeft');
} }
function pressPageUp() { function pressPageUp() {
MockInteractions.pressAndReleaseKeyOn(crSlider, 33, [], 'PageUp'); press('PageUp');
} }
function pressPageDown() { function pressPageDown() {
MockInteractions.pressAndReleaseKeyOn(crSlider, 34, [], 'PageDown'); press('PageDown');
} }
function pressArrowUp() { function pressArrowUp() {
MockInteractions.pressAndReleaseKeyOn(crSlider, 38, [], 'ArrowUp'); press('ArrowUp');
} }
function pressArrowDown() { function pressArrowDown() {
MockInteractions.pressAndReleaseKeyOn(crSlider, 40, [], 'ArrowDown'); press('ArrowDown');
} }
function pressHome() { function pressHome() {
MockInteractions.pressAndReleaseKeyOn(crSlider, 36, [], 'Home'); press('Home');
} }
function pressEnd() { function pressEnd() {
MockInteractions.pressAndReleaseKeyOn(crSlider, 35, [], 'End'); press('End');
} }
function pointerEvent(eventType, ratio) { function pointerEvent(eventType, ratio) {
...@@ -87,6 +92,15 @@ suite('SettingsSlider', function() { ...@@ -87,6 +92,15 @@ suite('SettingsSlider', function() {
`expected ${expected} to be close to ${actual}`); `expected ${expected} to be close to ${actual}`);
} }
async function checkSliderValueFromPref(prefValue, sliderValue) {
assertNotEquals(sliderValue, crSlider.value);
if (crSlider.updatingFromKey) {
await test_util.eventToPromise('updating-from-key-changed', crSlider);
}
slider.set('pref.value', prefValue);
assertEquals(sliderValue, crSlider.value);
}
test('enforce value', function() { test('enforce value', function() {
// Test that the indicator is not present until after the pref is // Test that the indicator is not present until after the pref is
// enforced. // enforced.
...@@ -103,124 +117,110 @@ suite('SettingsSlider', function() { ...@@ -103,124 +117,110 @@ suite('SettingsSlider', function() {
assertTrue(!!indicator); assertTrue(!!indicator);
}); });
test('set value', function() { test('set value', async () => {
slider.ticks = ticks; slider.ticks = ticks;
slider.set('pref.value', 16); await checkSliderValueFromPref(8, 2);
Polymer.dom.flush(); assertEquals(6, crSlider.max);
expectEquals(6, crSlider.max);
expectEquals(3, crSlider.value);
// settings-slider only supports snapping to a range of tick values. // settings-slider only supports snapping to a range of tick values.
// Setting to an in-between value should snap to an indexed value. // Setting to an in-between value should snap to an indexed value.
slider.set('pref.value', 70); await checkSliderValueFromPref(70, 5);
return PolymerTest.flushTasks() assertEquals(64, slider.pref.value);
.then(() => {
expectEquals(5, crSlider.value); // Setting the value out-of-range should clamp the slider.
expectEquals(64, slider.pref.value); await checkSliderValueFromPref(-100, 0);
assertEquals(2, slider.pref.value);
// Setting the value out-of-range should clamp the slider.
slider.set('pref.value', -100);
return PolymerTest.flushTasks();
})
.then(() => {
expectEquals(0, crSlider.value);
expectEquals(2, slider.pref.value);
});
}); });
test('move slider', function() { test('move slider', async () => {
slider.ticks = ticks; slider.ticks = ticks;
slider.set('pref.value', 30); await checkSliderValueFromPref(30, 4);
expectEquals(4, crSlider.value);
pressArrowRight(); pressArrowRight();
expectEquals(5, crSlider.value); assertEquals(5, crSlider.value);
expectEquals(64, slider.pref.value); assertEquals(64, slider.pref.value);
pressArrowRight(); pressArrowRight();
expectEquals(6, crSlider.value); assertEquals(6, crSlider.value);
expectEquals(128, slider.pref.value); assertEquals(128, slider.pref.value);
pressArrowRight(); pressArrowRight();
expectEquals(6, crSlider.value); assertEquals(6, crSlider.value);
expectEquals(128, slider.pref.value); assertEquals(128, slider.pref.value);
pressArrowLeft(); pressArrowLeft();
expectEquals(5, crSlider.value); assertEquals(5, crSlider.value);
expectEquals(64, slider.pref.value); assertEquals(64, slider.pref.value);
pressPageUp(); pressPageUp();
expectEquals(6, crSlider.value); assertEquals(6, crSlider.value);
expectEquals(128, slider.pref.value); assertEquals(128, slider.pref.value);
pressPageDown(); pressPageDown();
expectEquals(5, crSlider.value); assertEquals(5, crSlider.value);
expectEquals(64, slider.pref.value); assertEquals(64, slider.pref.value);
pressHome(); pressHome();
expectEquals(0, crSlider.value); assertEquals(0, crSlider.value);
expectEquals(2, slider.pref.value); assertEquals(2, slider.pref.value);
pressArrowDown(); pressArrowDown();
expectEquals(0, crSlider.value); assertEquals(0, crSlider.value);
expectEquals(2, slider.pref.value); assertEquals(2, slider.pref.value);
pressArrowUp(); pressArrowUp();
expectEquals(1, crSlider.value); assertEquals(1, crSlider.value);
expectEquals(4, slider.pref.value); assertEquals(4, slider.pref.value);
pressEnd(); pressEnd();
expectEquals(6, crSlider.value); assertEquals(6, crSlider.value);
expectEquals(128, slider.pref.value); assertEquals(128, slider.pref.value);
}); });
test('scaled slider', function() { test('scaled slider', async () => {
slider.set('pref.value', 2); await checkSliderValueFromPref(2, 2);
expectEquals(2, crSlider.value);
slider.scale = 10; slider.scale = 10;
slider.max = 4; slider.max = 4;
pressArrowRight(); pressArrowRight();
expectEquals(3, crSlider.value); assertEquals(3, crSlider.value);
expectEquals(.3, slider.pref.value); assertEquals(.3, slider.pref.value);
pressArrowRight(); pressArrowRight();
expectEquals(4, crSlider.value); assertEquals(4, crSlider.value);
expectEquals(.4, slider.pref.value); assertEquals(.4, slider.pref.value);
pressArrowRight(); pressArrowRight();
expectEquals(4, crSlider.value); assertEquals(4, crSlider.value);
expectEquals(.4, slider.pref.value); assertEquals(.4, slider.pref.value);
pressHome(); pressHome();
expectEquals(0, crSlider.value); assertEquals(0, crSlider.value);
expectEquals(0, slider.pref.value); assertEquals(0, slider.pref.value);
pressEnd(); pressEnd();
expectEquals(4, crSlider.value); assertEquals(4, crSlider.value);
expectEquals(.4, slider.pref.value); assertEquals(.4, slider.pref.value);
slider.set('pref.value', .25); await checkSliderValueFromPref(.25, 2.5);
expectEquals(2.5, crSlider.value); assertEquals(.25, slider.pref.value);
expectEquals(.25, slider.pref.value);
pressPageUp(); pressPageUp();
expectEquals(3.5, crSlider.value); assertEquals(3.5, crSlider.value);
expectEquals(.35, slider.pref.value); assertEquals(.35, slider.pref.value);
pressPageUp(); pressPageUp();
expectEquals(4, crSlider.value); assertEquals(4, crSlider.value);
expectEquals(.4, slider.pref.value); assertEquals(.4, slider.pref.value);
}); });
test('update value instantly both off and on with ticks', () => { test('update value instantly both off and on with ticks', async () => {
slider.ticks = ticks; slider.ticks = ticks;
slider.set('pref.value', 2); await checkSliderValueFromPref(4, 1);
slider.updateValueInstantly = false; slider.updateValueInstantly = false;
assertEquals(0, crSlider.value);
pointerDown(3 / crSlider.max); pointerDown(3 / crSlider.max);
assertEquals(3, crSlider.value); assertEquals(3, crSlider.value);
assertEquals(2, slider.pref.value); assertEquals(4, slider.pref.value);
pointerUp(); pointerUp();
assertEquals(3, crSlider.value); assertEquals(3, crSlider.value);
assertEquals(16, slider.pref.value); assertEquals(16, slider.pref.value);
...@@ -244,11 +244,10 @@ suite('SettingsSlider', function() { ...@@ -244,11 +244,10 @@ suite('SettingsSlider', function() {
assertEquals(8, slider.pref.value); assertEquals(8, slider.pref.value);
}); });
test('update value instantly both off and on', () => { test('update value instantly both off and on', async () => {
slider.scale = 10; slider.scale = 10;
slider.set('pref.value', 2); await checkSliderValueFromPref(2, 20);
slider.updateValueInstantly = false; slider.updateValueInstantly = false;
assertCloseTo(20, crSlider.value);
pointerDown(.3); pointerDown(.3);
assertCloseTo(30, crSlider.value); assertCloseTo(30, crSlider.value);
assertEquals(2, slider.pref.value); assertEquals(2, slider.pref.value);
......
...@@ -86,6 +86,12 @@ cr_slider.SliderTick; ...@@ -86,6 +86,12 @@ cr_slider.SliderTick;
reflectToAttribute: true, reflectToAttribute: true,
}, },
updatingFromKey: {
type: Boolean,
value: false,
notify: true,
},
markerCount: { markerCount: {
type: Number, type: Number,
value: 0, value: 0,
...@@ -181,6 +187,7 @@ cr_slider.SliderTick; ...@@ -181,6 +187,7 @@ cr_slider.SliderTick;
focus: 'onFocus_', focus: 'onFocus_',
blur: 'onBlur_', blur: 'onBlur_',
keydown: 'onKeyDown_', keydown: 'onKeyDown_',
keyup: 'onKeyUp_',
pointerdown: 'onPointerDown_', pointerdown: 'onPointerDown_',
}, },
...@@ -314,6 +321,7 @@ cr_slider.SliderTick; ...@@ -314,6 +321,7 @@ cr_slider.SliderTick;
return; return;
} }
this.updatingFromKey = true;
if (this.updateValue_(newValue)) { if (this.updateValue_(newValue)) {
this.fire('cr-slider-value-changed'); this.fire('cr-slider-value-changed');
} }
...@@ -324,6 +332,19 @@ cr_slider.SliderTick; ...@@ -324,6 +332,19 @@ cr_slider.SliderTick;
}); });
}, },
/**
* @param {!Event} event
* @private
*/
onKeyUp_: function(event) {
if (event.key == 'Home' || event.key == 'End' ||
this.deltaKeyMap_.has(event.key)) {
setTimeout(() => {
this.updatingFromKey = false;
});
}
},
/** /**
* This code is taken from cr-input. See https://crbug.com/832177#c31 for * This code is taken from cr-input. See https://crbug.com/832177#c31 for
* the CL that handles escaping focus from the shadow DOM. * the CL that handles escaping focus from the shadow DOM.
...@@ -379,7 +400,8 @@ cr_slider.SliderTick; ...@@ -379,7 +400,8 @@ cr_slider.SliderTick;
this.draggingEventTracker_.add(this, 'pointerdown', stopDragging); this.draggingEventTracker_.add(this, 'pointerdown', stopDragging);
this.draggingEventTracker_.add(this, 'pointerup', stopDragging); this.draggingEventTracker_.add(this, 'pointerup', stopDragging);
this.draggingEventTracker_.add(this, 'keydown', e => { this.draggingEventTracker_.add(this, 'keydown', e => {
if (e.key == 'Escape' || e.key == 'Tab') { if (e.key == 'Escape' || e.key == 'Tab' || e.key == 'Home' ||
e.key == 'End' || this.deltaKeyMap_.has(e.key)) {
stopDragging(); stopDragging();
} }
}); });
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment