Commit 4dac4603 authored by Michael Liao's avatar Michael Liao Committed by Commit Bot

DevTools [Common - Overview Grid]: Implement keyboard navigation for overview grids

Re-upload of https://chromium-review.googlesource.com/c/chromium/src/+/1636250

This is a shared control between Timeline, Network, Memory, JS Profiler and Layers tools

1. This PR adds the ability to use keyboard to navigate sliders in the Overview grid.
- Can tab into the sliders
- Can use left and right arrow keys to navigate the grid sliders (step by 2 pixels)
- Can use Ctrl + left and right arrow keys to navigate the grid sliders faster (step by 10 pixels)

Gif: https://imgur.com/Im7m6su

Bug: 963183
Change-Id: I64ec60864165d9cca6e7a76c6849ed1cf1beee61
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1676677Reviewed-by: default avatarMathias Bynens <mathias@chromium.org>
Reviewed-by: default avatarYang Guo <yangguo@chromium.org>
Commit-Queue: Michael Liao <michael.liao@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#704888}
parent b0d3c675
......@@ -138,6 +138,8 @@ PerfUI.OverviewGrid.WindowScrollSpeedFactor = .3;
PerfUI.OverviewGrid.ResizerOffset = 3.5; // half pixel because offset values are not rounded but ceiled
PerfUI.OverviewGrid.OffsetFromWindowEnds = 10;
/**
* @unrestricted
*/
......@@ -172,11 +174,23 @@ PerfUI.OverviewGrid.Window = class extends Common.Object {
this._rightResizeElement, this._resizerElementStartDragging.bind(this),
this._rightResizeElementDragging.bind(this), null, 'ew-resize');
this._leftResizeElement.tabIndex = 0;
this._leftResizeElement.addEventListener('keydown', event => this._handleKeyboardResizing(event, false));
this._rightResizeElement.tabIndex = 0;
this._rightResizeElement.addEventListener('keydown', event => this._handleKeyboardResizing(event, true));
this._rightResizeElement.addEventListener('focus', this._onRightResizeElementFocused.bind(this));
this._leftCurtainElement = parentElement.createChild('div', 'window-curtain-left');
this._rightCurtainElement = parentElement.createChild('div', 'window-curtain-right');
this.reset();
}
_onRightResizeElementFocused() {
// To prevent browser focus from scrolling the element into view and shifting the contents of the strip
this._parentElement.scrollLeft = 0;
}
reset() {
this.windowLeft = 0.0;
this.windowRight = 1.0;
......@@ -226,6 +240,50 @@ PerfUI.OverviewGrid.Window = class extends Common.Object {
event.preventDefault();
}
/**
* @param {!Event} event
* @param {boolean=} moveRightResizer
*/
_handleKeyboardResizing(event, moveRightResizer) {
let increment = false;
if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') {
if (event.key === 'ArrowRight') {
increment = true;
}
const newPos = this._getNewResizerPosition(event.target.offsetLeft, increment, event.ctrlKey);
if (moveRightResizer) {
this._resizeWindowRight(newPos);
} else {
this._resizeWindowLeft(newPos);
}
event.consume(true);
}
}
/**
* @param {number} offset
* @param {boolean=} increment
* @param {boolean=} ctrlPressed
* @return {number}
*/
_getNewResizerPosition(offset, increment, ctrlPressed) {
let newPos;
// We shift by 10px if the ctrlKey is pressed and 2 otherwise. 1px shifts result in noOp due to rounding in _updateCurtains
let pixelsToShift = ctrlPressed ? 10 : 2;
pixelsToShift = increment ? pixelsToShift : -Math.abs(pixelsToShift);
const offsetLeft = offset + PerfUI.OverviewGrid.ResizerOffset;
newPos = offsetLeft + pixelsToShift;
if (increment && newPos < PerfUI.OverviewGrid.OffsetFromWindowEnds) {
// When incrementing, snap to the window offset value (10px) if the new position is between 0px and 10px
newPos = PerfUI.OverviewGrid.OffsetFromWindowEnds;
} else if (!increment && newPos > this._parentElement.clientWidth - PerfUI.OverviewGrid.OffsetFromWindowEnds) {
// When decrementing, snap to the window offset value (10px) from the rightmost side if the new position is within 10px from the end.
newPos = this._parentElement.clientWidth - PerfUI.OverviewGrid.OffsetFromWindowEnds;
}
return newPos;
}
/**
* @param {!Event} event
* @return {boolean}
......@@ -306,7 +364,7 @@ PerfUI.OverviewGrid.Window = class extends Common.Object {
*/
_resizeWindowLeft(start) {
// Glue to edge.
if (start < 10) {
if (start < PerfUI.OverviewGrid.OffsetFromWindowEnds) {
start = 0;
} else if (start > this._rightResizeElement.offsetLeft - 4) {
start = this._rightResizeElement.offsetLeft - 4;
......@@ -319,7 +377,7 @@ PerfUI.OverviewGrid.Window = class extends Common.Object {
*/
_resizeWindowRight(end) {
// Glue to edge.
if (end > this._parentElement.clientWidth - 10) {
if (end > this._parentElement.clientWidth - PerfUI.OverviewGrid.OffsetFromWindowEnds) {
end = this._parentElement.clientWidth;
} else if (end < this._leftResizeElement.offsetLeft + PerfUI.OverviewGrid.MinSelectableSize) {
end = this._leftResizeElement.offsetLeft + PerfUI.OverviewGrid.MinSelectableSize;
......@@ -347,13 +405,16 @@ PerfUI.OverviewGrid.Window = class extends Common.Object {
let right = this.windowRight;
const width = right - left;
// We allow actual time window to be arbitrarily small but don't want the UI window to be too small.
const widthInPixels = width * this._parentElement.clientWidth;
const minWidthInPixels = PerfUI.OverviewGrid.MinSelectableSize / 2;
if (widthInPixels < minWidthInPixels) {
const factor = minWidthInPixels / widthInPixels;
left = ((this.windowRight + this.windowLeft) - width * factor) / 2;
right = ((this.windowRight + this.windowLeft) + width * factor) / 2;
// OverviewGrids that are instantiated before the parentElement is shown will have a parent element client width of 0 which throws off the 'factor' calculation
if (this._parentElement.clientWidth !== 0) {
// We allow actual time window to be arbitrarily small but don't want the UI window to be too small.
const widthInPixels = width * this._parentElement.clientWidth;
const minWidthInPixels = PerfUI.OverviewGrid.MinSelectableSize / 2;
if (widthInPixels < minWidthInPixels) {
const factor = minWidthInPixels / widthInPixels;
left = ((this.windowRight + this.windowLeft) - width * factor) / 2;
right = ((this.windowRight + this.windowLeft) + width * factor) / 2;
}
}
this._leftResizeElement.style.left = (100 * left).toFixed(2) + '%';
this._rightResizeElement.style.left = (100 * right).toFixed(2) + '%';
......
......@@ -24,6 +24,10 @@
z-index: 500;
}
.overview-grid-window-resizer[data-keyboard-focus="true"]:focus {
background-color: var(--active-control-bg-color);
}
.overview-grid-cursor-area {
position: absolute;
left: 0;
......
......@@ -36,6 +36,7 @@
--accent-color: #1a73e8;
--accent-fg-color: #1a73e8;
--accent-color-hover: #3b86e8;
--active-control-bg-color: #5a5a5a;
--focus-bg-color: hsl(214, 40%, 92%);
--toolbar-bg-color: #f3f3f3;
--toolbar-hover-bg-color: #eaeaea;
......@@ -57,6 +58,7 @@
--accent-color: #0e639c;
--accent-fg-color: #cccccc;
--accent-color-hover: rgb(17, 119, 187);
--active-control-bg-color: #cdcdcd;
--focus-bg-color: hsl(214, 19%, 27%);
--toolbar-bg-color: #333333;
--toolbar-hover-bg-color: #202020;
......
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