Index: third_party/WebKit/Source/devtools/front_end/ui/ListControl.js |
diff --git a/third_party/WebKit/Source/devtools/front_end/ui/ListControl.js b/third_party/WebKit/Source/devtools/front_end/ui/ListControl.js |
index a76022f03598a1dae20f6b5baac5182dd216d20a..2afc2ee30016eb697569b0567eb26306e626e2a7 100644 |
--- a/third_party/WebKit/Source/devtools/front_end/ui/ListControl.js |
+++ b/third_party/WebKit/Source/devtools/front_end/ui/ListControl.js |
@@ -37,10 +37,11 @@ UI.ListDelegate.prototype = { |
}; |
/** @enum {symbol} */ |
-UI.ListHeightMode = { |
- Fixed: Symbol('UI.ListHeightMode.Fixed'), |
- Measured: Symbol('UI.ListHeightMode.Measured'), |
- Variable: Symbol('UI.ListHeightMode.Variable') |
+UI.ListMode = { |
+ Grow: Symbol('UI.ListMode.Grow'), |
+ ViewportFixedItems: Symbol('UI.ListMode.ViewportFixedItems'), |
+ ViewportFixedItemsMeasured: Symbol('UI.ListMode.ViewportFixedItemsMeasured'), |
+ ViewportVariableItems: Symbol('UI.ListMode.ViewportVariableItems') |
}; |
/** |
@@ -60,7 +61,6 @@ UI.ListControl = class { |
this._renderedHeight = 0; |
this._topHeight = 0; |
this._bottomHeight = 0; |
- this._clearViewport(); |
/** @type {!Array<T>} */ |
this._items = []; |
@@ -76,24 +76,42 @@ UI.ListControl = class { |
if (this.onClick(event)) |
event.consume(true); |
}; |
+ this._boundScroll = event => { |
+ this._updateViewport(this.element.scrollTop, this.element.offsetHeight); |
+ }; |
this._delegate = delegate; |
- this._heightMode = UI.ListHeightMode.Measured; |
+ this._mode = UI.ListMode.ViewportFixedItemsMeasured; |
this._fixedHeight = 0; |
this._variableOffsets = new Int32Array(0); |
- |
- this.element.addEventListener('scroll', this._onScroll.bind(this), false); |
+ this._clearViewport(); |
+ this.element.addEventListener('scroll', this._boundScroll, false); |
} |
/** |
- * @param {!UI.ListHeightMode} mode |
+ * @param {!UI.ListMode} mode |
*/ |
- setHeightMode(mode) { |
- this._heightMode = mode; |
- this._fixedHeight = 0; |
- if (this._items.length) { |
- this._itemToElement.clear(); |
- this._invalidate(0, this._items.length, this._items.length); |
+ setMode(mode) { |
caseq
2016/12/29 19:25:31
Should we even allow changing mode on the fly? Per
dgozman
2016/12/29 20:38:44
Done.
|
+ if (mode === UI.ListMode.Grow) { |
+ if (this._mode !== UI.ListMode.Grow) |
+ this.element.removeEventListener('scroll', this._boundScroll, false); |
caseq
2016/12/29 19:25:31
do it unconditionally just in case?
|
+ this._mode = mode; |
caseq
2016/12/29 19:25:30
this would go before if then
|
+ if (this._items.length) { |
+ this._itemToElement.clear(); |
+ this._clearContents(); |
+ this._invalidateGrowMode(0, 0, this._items.length); |
+ } |
+ } else { |
+ if (this._mode === UI.ListMode.Grow) |
+ this.element.addEventListener('scroll', this._boundScroll, false); |
+ this._mode = mode; |
+ this._fixedHeight = 0; |
+ if (this._items.length) { |
+ this._itemToElement.clear(); |
+ if (this._mode === UI.ListMode.Grow) |
caseq
2016/12/29 19:25:30
this can't be true :-)
|
+ this._clearViewport(); |
+ this._invalidate(0, this._items.length, this._items.length); |
+ } |
} |
} |
@@ -181,9 +199,9 @@ UI.ListControl = class { |
if (this._selectedIndex >= to) { |
this._selectedIndex += items.length - (to - from); |
} else if (this._selectedIndex >= from) { |
- var index = this._findClosestSelectable(from + items.length, +1, 0, false); |
+ var index = this._findFirstSelectable(from + items.length, +1, false); |
if (index === -1) |
- index = this._findClosestSelectable(from - 1, -1, 0, false); |
+ index = this._findFirstSelectable(from - 1, -1, false); |
this._select(index, oldSelectedItem, oldSelectedElement); |
} |
} |
@@ -204,8 +222,9 @@ UI.ListControl = class { |
} |
viewportResized() { |
- // TODO(dgozman): try to keep the visible scrollTop the same |
- // when invalidating after firstIndex but before first visible element. |
+ if (this._mode === UI.ListMode.Grow) |
+ return; |
+ // TODO(dgozman): try to keep visible scrollTop the same. |
var scrollTop = this.element.scrollTop; |
var viewportHeight = this.element.offsetHeight; |
this._clearViewport(); |
@@ -216,6 +235,10 @@ UI.ListControl = class { |
* @param {number} index |
*/ |
scrollItemAtIndexIntoView(index) { |
+ if (this._mode === UI.ListMode.Grow) { |
+ this._elementAtIndex(index).scrollIntoViewIfNeeded(false); |
+ return; |
+ } |
var top = this._offsetAtIndex(index); |
var bottom = this._offsetAtIndex(index + 1); |
var scrollTop = this.element.scrollTop; |
@@ -260,22 +283,24 @@ UI.ListControl = class { |
var index = -1; |
switch (event.key) { |
case 'ArrowUp': |
- index = this._selectedIndex === -1 ? this._findClosestSelectable(this._items.length - 1, -1, 0, true) : |
- this._findClosestSelectable(this._selectedIndex, -1, 1, true); |
+ index = this._selectedIndex === -1 ? this._items.length - 1 : this._selectedIndex - 1; |
+ index = this._findFirstSelectable(index, -1, true); |
break; |
case 'ArrowDown': |
- index = this._selectedIndex === -1 ? this._findClosestSelectable(0, +1, 0, true) : |
- this._findClosestSelectable(this._selectedIndex, +1, 1, true); |
+ index = this._selectedIndex === -1 ? 0 : this._selectedIndex + 1; |
+ index = this._findFirstSelectable(index, +1, true); |
break; |
case 'PageUp': |
+ if (this._mode === UI.ListMode.Grow) |
+ return false; |
index = this._selectedIndex === -1 ? this._items.length - 1 : this._selectedIndex; |
- // Compensate for zoom rounding errors with -1. |
- index = this._findClosestSelectable(index, -1, this.element.offsetHeight - 1, false); |
+ index = this._findPageSelectable(index, -1); |
break; |
case 'PageDown': |
+ if (this._mode === UI.ListMode.Grow) |
+ return false; |
index = this._selectedIndex === -1 ? 0 : this._selectedIndex; |
- // Compensate for zoom rounding errors with -1. |
- index = this._findClosestSelectable(index, +1, this.element.offsetHeight - 1, false); |
+ index = this._findPageSelectable(index, +1); |
break; |
default: |
return false; |
@@ -298,9 +323,19 @@ UI.ListControl = class { |
node = node.parentNodeOrShadowHost(); |
if (!node || node.nodeType !== Node.ELEMENT_NODE) |
return false; |
- var offset = /** @type {!Element} */ (node).getBoundingClientRect().top; |
- offset -= this.element.getBoundingClientRect().top; |
- var index = this._indexAtOffset(offset + this.element.scrollTop); |
+ var index = -1; |
+ if (this._mode === UI.ListMode.Grow) { |
+ for (var i = 0; i < this._items.length; i++) { |
caseq
2016/12/29 19:25:31
use findIndex()?
dgozman
2016/12/29 20:38:44
Done.
|
+ if (this._itemToElement.get(this._items[i]) === node) { |
+ index = i; |
+ break; |
+ } |
+ } |
+ } else { |
+ var offset = /** @type {!Element} */ (node).getBoundingClientRect().top; |
+ offset -= this.element.getBoundingClientRect().top; |
+ index = this._indexAtOffset(offset + this.element.scrollTop); |
+ } |
if (index === -1 || !this._delegate.isItemSelectable(this._items[index])) |
return false; |
this._select(index); |
@@ -319,9 +354,11 @@ UI.ListControl = class { |
* @return {number} |
*/ |
_indexAtOffset(offset) { |
+ if (this._mode === UI.ListMode.Grow) |
+ throw 'There should be no offset conversions in grow mode'; |
if (!this._items.length || offset < 0) |
return 0; |
- if (this._heightMode === UI.ListHeightMode.Variable) { |
+ if (this._mode === UI.ListMode.ViewportVariableItems) { |
return Math.min( |
this._items.length - 1, this._variableOffsets.lowerBound(offset, undefined, 0, this._items.length)); |
} |
@@ -349,9 +386,11 @@ UI.ListControl = class { |
* @return {number} |
*/ |
_offsetAtIndex(index) { |
+ if (this._mode === UI.ListMode.Grow) |
+ throw 'There should be no offset conversions in grow mode'; |
if (!this._items.length) |
return 0; |
- if (this._heightMode === UI.ListHeightMode.Variable) |
+ if (this._mode === UI.ListMode.ViewportVariableItems) |
return this._variableOffsets[index]; |
if (!this._fixedHeight) |
this._measureHeight(); |
@@ -359,7 +398,7 @@ UI.ListControl = class { |
} |
_measureHeight() { |
- if (this._heightMode === UI.ListHeightMode.Measured) |
+ if (this._mode === UI.ListMode.ViewportFixedItemsMeasured) |
this._fixedHeight = UI.measurePreferredSize(this._elementAtIndex(0), this.element).height; |
else |
this._fixedHeight = this._delegate.heightForItem(this._items[0]); |
@@ -384,41 +423,45 @@ UI.ListControl = class { |
/** |
* @param {number} index |
* @param {number} direction |
- * @param {number} minSkippedHeight |
* @param {boolean} canWrap |
* @return {number} |
*/ |
- _findClosestSelectable(index, direction, minSkippedHeight, canWrap) { |
+ _findFirstSelectable(index, direction, canWrap) { |
var length = this._items.length; |
if (!length) |
return -1; |
- |
- var lastSelectable = -1; |
- var start = -1; |
- var startOffset = this._offsetAtIndex(index); |
- while (true) { |
+ for (var step = 0; step <= length; step++) { |
if (index < 0 || index >= length) { |
if (!canWrap) |
- return lastSelectable; |
+ return -1; |
index = (index + length) % length; |
} |
+ if (this._delegate.isItemSelectable(this._items[index])) |
+ return index; |
+ index += direction; |
+ } |
+ return -1; |
+ } |
- // Handle full wrap-around. |
- if (index === start) |
- return lastSelectable; |
- if (start === -1) { |
- start = index; |
- startOffset = this._offsetAtIndex(index); |
- } |
- |
+ /** |
+ * @param {number} index |
+ * @param {number} direction |
+ * @return {number} |
+ */ |
+ _findPageSelectable(index, direction) { |
+ var lastSelectable = -1; |
+ var startOffset = this._offsetAtIndex(index); |
+ // Compensate for zoom rounding errors with -1. |
+ var viewportHeight = this.element.offsetHeight - 1; |
+ while (index >= 0 && index < this._items.length) { |
if (this._delegate.isItemSelectable(this._items[index])) { |
- if (Math.abs(this._offsetAtIndex(index) - startOffset) >= minSkippedHeight) |
+ if (Math.abs(this._offsetAtIndex(index) - startOffset) >= viewportHeight) |
return index; |
lastSelectable = index; |
} |
- |
index += direction; |
} |
+ return lastSelectable; |
} |
/** |
@@ -443,12 +486,17 @@ UI.ListControl = class { |
* @param {number} inserted |
*/ |
_invalidate(from, to, inserted) { |
- if (this._heightMode === UI.ListHeightMode.Variable) { |
+ if (this._mode === UI.ListMode.ViewportVariableItems) { |
this._reallocateVariableOffsets(this._items.length + 1, from + 1); |
for (var i = from + 1; i <= this._items.length; i++) |
this._variableOffsets[i] = this._variableOffsets[i - 1] + this._delegate.heightForItem(this._items[i - 1]); |
} |
+ if (this._mode === UI.ListMode.Grow) { |
caseq
2016/12/29 19:25:31
nit: move up
dgozman
2016/12/29 20:38:44
Done.
|
+ this._invalidateGrowMode(from, to - from, inserted); |
+ return; |
+ } |
+ |
var viewportHeight = this.element.offsetHeight; |
var totalHeight = this._totalHeight(); |
var scrollTop = this.element.scrollTop; |
@@ -480,18 +528,42 @@ UI.ListControl = class { |
return; |
} |
- // TODO(dgozman): try to keep the visible scrollTop the same |
+ // TODO(dgozman): try to keep visible scrollTop the same |
// when invalidating after firstIndex but before first visible element. |
this._clearViewport(); |
this._updateViewport(Number.constrain(scrollTop, 0, totalHeight - viewportHeight), viewportHeight); |
} |
+ /** |
+ * @param {number} start |
+ * @param {number} remove |
+ * @param {number} add |
+ */ |
+ _invalidateGrowMode(start, remove, add) { |
+ var startElement = this._topElement; |
+ for (var index = 0; index < start; index++) |
+ startElement = startElement.nextSibling; |
caseq
2016/12/29 19:25:31
nextElementSibling
dgozman
2016/12/29 20:38:44
Done.
|
+ for (var index = 0; index < remove; index++) |
caseq
2016/12/29 19:25:30
nit: while (remove--)
dgozman
2016/12/29 20:38:44
Done.
|
+ startElement.nextSibling.remove(); |
caseq
2016/12/29 19:25:31
ditto.
|
+ for (var index = add - 1; index >= 0; index--) { |
+ var element = this._elementAtIndex(start + index); |
+ this.element.insertBefore(element, startElement.nextSibling); |
+ } |
+ } |
+ |
_clearViewport() { |
+ if (this._mode === UI.ListMode.Grow) |
+ throw 'There should be no viewport updates in grow mode'; |
this._firstIndex = 0; |
this._lastIndex = 0; |
this._renderedHeight = 0; |
this._topHeight = 0; |
this._bottomHeight = 0; |
+ this._clearContents(); |
+ } |
+ |
+ _clearContents() { |
+ // Note: this method should not force layout. Be careful. |
this._topElement.style.height = '0'; |
this._bottomElement.style.height = '0'; |
this.element.removeChildren(); |
@@ -499,16 +571,14 @@ UI.ListControl = class { |
this.element.appendChild(this._bottomElement); |
} |
- _onScroll() { |
- this._updateViewport(this.element.scrollTop, this.element.offsetHeight); |
- } |
- |
/** |
* @param {number} scrollTop |
* @param {number} viewportHeight |
*/ |
_updateViewport(scrollTop, viewportHeight) { |
// Note: this method should not force layout. Be careful. |
+ if (this._mode === UI.ListMode.Grow) |
+ throw 'There should be no viewport updates in grow mode'; |
var totalHeight = this._totalHeight(); |
if (!totalHeight) { |