Index: third_party/WebKit/Source/devtools/front_end/ui/ViewportControl.js |
diff --git a/third_party/WebKit/Source/devtools/front_end/ui/ViewportControl.js b/third_party/WebKit/Source/devtools/front_end/ui/ViewportControl.js |
index dbd36008c8a08dd7baf1be0f7679ec221d521a0e..0706671ddcdf20dc7fb82f93a85523ae8b7e0090 100644 |
--- a/third_party/WebKit/Source/devtools/front_end/ui/ViewportControl.js |
+++ b/third_party/WebKit/Source/devtools/front_end/ui/ViewportControl.js |
@@ -6,164 +6,262 @@ |
*/ |
UI.ViewportControl = class { |
/** |
- * @param {!UI.ViewportControl.Provider} provider |
+ * @param {!UI.ViewportProvider} provider |
*/ |
constructor(provider) { |
this.element = createElement('div'); |
this.element.style.overflow = 'auto'; |
- this._innerElement = this.element.createChild('div'); |
- this._innerElement.style.height = '0px'; |
- this._innerElement.style.position = 'relative'; |
- this._innerElement.style.overflow = 'hidden'; |
- |
+ this._topElement = this.element.createChild('div'); |
+ this._bottomElement = this.element.createChild('div'); |
this._provider = provider; |
- this.element.addEventListener('scroll', this._update.bind(this), false); |
- this._itemCount = 0; |
- this._indexSymbol = Symbol('UI.ViewportControl._indexSymbol'); |
+ this._firstIndex = 0; |
+ this._lastIndex = 0; |
+ this.element.addEventListener('scroll', this._onScroll.bind(this), false); |
+ this._update(0); |
} |
- refresh() { |
- this._itemCount = this._provider.itemCount(); |
- this._innerElement.removeChildren(); |
- |
- var height = 0; |
- this._cumulativeHeights = new Int32Array(this._itemCount); |
- for (var i = 0; i < this._itemCount; ++i) { |
- height += this._provider.fastItemHeight(i); |
- this._cumulativeHeights[i] = height; |
+ /** |
+ * @param {number} from |
+ * @param {number} to |
+ */ |
+ spliced(from, to) { |
+ var scrollTop = this.element.scrollTop; |
+ var totalHeight = this._provider.totalHeight(); |
+ if (this._totalHeight < this.element.offsetHeight) { |
+ this.refresh(); |
+ return; |
+ } |
+ if (to <= this._firstIndex) { |
+ var topHeight = this._topHeight + totalHeight - this._totalHeight; |
+ this._topElement.style.height = topHeight + 'px'; |
+ this.element.scrollTop = scrollTop + totalHeight - this._totalHeight; |
+ this._topHeight = topHeight; |
+ this._totalHeight = totalHeight; |
+ return; |
} |
- this._innerElement.style.height = height + 'px'; |
+ if (from >= this._lastIndex) { |
+ var bottomHeight = this._bottomHeight + totalHeight - this._totalHeight; |
+ this._bottomElement.style.height = bottomHeight + 'px'; |
+ this.element.scrollTop = scrollTop + totalHeight - this._totalHeight; |
+ this._bottomHeight = bottomHeight; |
+ this._totalHeight = totalHeight; |
+ return; |
+ } |
+ this.refresh(); |
+ } |
+ |
+ refresh() { |
+ this._firstIndex = 0; |
+ this._lastIndex = 0; |
+ this.element.removeChildren(); |
+ this.element.appendChild(this._topElement); |
+ this.element.appendChild(this._bottomElement); |
+ this._update(0); |
+ } |
- this._update(); |
+ _onScroll() { |
+ this._update(this.element.scrollTop); |
} |
- _update() { |
- if (!this._cumulativeHeights) { |
- this.refresh(); |
+ /** |
+ * @param {number} scrollTop |
+ */ |
+ _update(scrollTop) { |
+ var totalHeight = this._provider.totalHeight(); |
+ if (!totalHeight) { |
+ this._firstIndex = 0; |
+ this._lastIndex = 0; |
+ this._topHeight = 0; |
+ this._bottomHeight = 0; |
+ this._totalHeight = 0; |
+ this._topElement.style.height = '0px'; |
+ this._bottomElement.style.height = '0px'; |
return; |
} |
- var visibleHeight = this._visibleHeight(); |
- var visibleFrom = this.element.scrollTop; |
- var activeHeight = visibleHeight * 2; |
- var firstActiveIndex = Math.max( |
- Array.prototype.lowerBound.call(this._cumulativeHeights, visibleFrom + 1 - (activeHeight - visibleHeight) / 2), |
- 0); |
- var lastActiveIndex = Math.min( |
- Array.prototype.lowerBound.call( |
- this._cumulativeHeights, visibleFrom + visibleHeight + (activeHeight - visibleHeight) / 2), |
- this._itemCount - 1); |
- |
- var children = this._innerElement.children; |
- for (var i = children.length - 1; i >= 0; --i) { |
- var element = children[i]; |
- if (element[this._indexSymbol] < firstActiveIndex || element[this._indexSymbol] > lastActiveIndex) |
- element.remove(); |
+ var height = this.element.offsetHeight; |
+ var firstIndex = this._provider.indexAtOffset(Math.max(0, scrollTop - height)); |
+ var lastIndex = this._provider.indexAtOffset(Math.min(totalHeight, scrollTop + 2 * height)) + 1; |
+ |
+ for (var index = this._firstIndex; index < firstIndex; index++) { |
+ var element = this._provider.elementAtIndex(index); |
+ element.remove(); |
+ this._firstIndex++; |
+ } |
+ for (var index = this._lastIndex - 1; index >= lastIndex; index--) { |
+ var element = this._provider.elementAtIndex(index); |
+ element.remove(); |
+ this._lastIndex--; |
+ } |
+ this._firstIndex = Math.min(this._firstIndex, lastIndex); |
+ this._lastIndex = Math.max(this._lastIndex, firstIndex); |
+ for (var index = this._firstIndex - 1; index >= firstIndex; index--) { |
+ var element = this._provider.elementAtIndex(index); |
+ this.element.insertBefore(element, this._topElement.nextSibling); |
+ } |
+ for (var index = this._lastIndex; index < lastIndex; index++) { |
+ var element = this._provider.elementAtIndex(index); |
+ this.element.insertBefore(element, this._bottomElement); |
} |
- for (var i = firstActiveIndex; i <= lastActiveIndex; ++i) |
- this._insertElement(i); |
+ this._firstIndex = firstIndex; |
+ this._lastIndex = lastIndex; |
+ this._totalHeight = totalHeight; |
+ this._topHeight = this._provider.offsetAtIndex(firstIndex); |
+ this._topElement.style.height = this._topHeight + 'px'; |
+ this._bottomHeight = (totalHeight - this._provider.offsetAtIndex(lastIndex)); |
+ this._bottomElement.style.height = this._bottomHeight + 'px'; |
+ this.element.scrollTop = scrollTop; |
} |
/** |
* @param {number} index |
*/ |
- _insertElement(index) { |
- var element = this._provider.itemElement(index); |
- if (!element || element.parentElement === this._innerElement) |
- return; |
- |
- element.style.position = 'absolute'; |
- element.style.top = (this._cumulativeHeights[index - 1] || 0) + 'px'; |
- element.style.left = '0'; |
- element.style.right = '0'; |
- element[this._indexSymbol] = index; |
- this._innerElement.appendChild(element); |
+ scrollItemIntoView(index) { |
+ var top = this._provider.offsetAtIndex(index); |
+ var bottom = this._provider.offsetAtIndex(index + 1); |
+ var scrollTop = this.element.scrollTop; |
+ var height = this.element.offsetHeight; |
+ if (top < scrollTop) |
+ this._update(top); |
+ else if (bottom > scrollTop + height) |
+ this._update(bottom - height); |
} |
+}; |
+/** |
+ * @interface |
+ */ |
+UI.ViewportProvider = function() {}; |
+ |
+UI.ViewportProvider.prototype = { |
/** |
+ * @param {number} height |
* @return {number} |
*/ |
- firstVisibleIndex() { |
- return Math.max(Array.prototype.lowerBound.call(this._cumulativeHeights, this.element.scrollTop + 1), 0); |
- } |
+ indexAtOffset(height) {}, |
pfeldman
2016/12/20 01:23:51
I understand the former API, but don't get the new
|
/** |
* @return {number} |
*/ |
- lastVisibleIndex() { |
- return Math.min( |
- Array.prototype.lowerBound.call(this._cumulativeHeights, this.element.scrollTop + this._visibleHeight()), |
- this._itemCount); |
- } |
+ totalHeight() {}, |
/** |
* @param {number} index |
- * @param {boolean=} makeLast |
+ * @return {!Element} |
*/ |
- scrollItemIntoView(index, makeLast) { |
- var firstVisibleIndex = this.firstVisibleIndex(); |
- var lastVisibleIndex = this.lastVisibleIndex(); |
- if (index > firstVisibleIndex && index < lastVisibleIndex) |
- return; |
- if (makeLast) |
- this.forceScrollItemToBeLast(index); |
- else if (index <= firstVisibleIndex) |
- this.forceScrollItemToBeFirst(index); |
- else if (index >= lastVisibleIndex) |
- this.forceScrollItemToBeLast(index); |
- } |
+ elementAtIndex(index) {}, |
/** |
* @param {number} index |
+ * @return {number} |
+ */ |
+ offsetAtIndex(index) {}, |
+}; |
+ |
+/** |
+ * @implements {UI.ViewportProvider} |
+ */ |
+UI.SimpleViewport = class { |
+ /** |
+ * @param {function(number):!Element} elementsFactory |
*/ |
- forceScrollItemToBeFirst(index) { |
- this.element.scrollTop = index > 0 ? this._cumulativeHeights[index - 1] : 0; |
- this._update(); |
+ constructor(elementsFactory) { |
+ this._elementsFactory = elementsFactory; |
+ /** @type {!Array<?Element>} */ |
+ this._elements = []; |
+ /** @type {number} */ |
+ this._height = 0; |
+ this._length = 0; |
+ this._viewport = new UI.ViewportControl(this); |
+ this.element = this._viewport.element; |
} |
/** |
- * @param {number} index |
+ * @param {number} length |
*/ |
- forceScrollItemToBeLast(index) { |
- this.element.scrollTop = this._cumulativeHeights[index] - this._visibleHeight(); |
- this._update(); |
+ refresh(length) { |
+ this._elements = []; |
+ var oldLength = this._length; |
+ this._length = length; |
+ this._viewport.spliced(0, oldLength); |
} |
/** |
* @return {number} |
*/ |
- _visibleHeight() { |
- return this.element.offsetHeight; |
+ elementHeight() { |
+ if (!this._height) |
+ this._height = UI.measurePreferredSize(this.elementAtIndex(0), this._viewport.element).height; |
+ return this._height; |
} |
-}; |
-/** |
- * @interface |
- */ |
-UI.ViewportControl.Provider = function() {}; |
+ resetElementHeight() { |
+ this._height = 0; |
+ } |
+ |
+ /** |
+ * @return {number} |
+ */ |
+ elementsPerViewport() { |
+ if (!this._length) |
+ return 0; |
+ return Math.floor(this._viewport.element.offsetHeight / this.elementHeight()); |
+ } |
-UI.ViewportControl.Provider.prototype = { |
/** |
* @param {number} index |
+ */ |
+ scrollItemIntoView(index) { |
+ this._viewport.scrollItemIntoView(index); |
+ } |
+ |
+ /** |
+ * @override |
+ * @param {number} height |
* @return {number} |
*/ |
- fastItemHeight(index) { |
- return 0; |
- }, |
+ indexAtOffset(height) { |
+ if (!this._length) |
+ return 0; |
+ var index = Math.floor(height / this.elementHeight()); |
+ if (index >= this._length) |
+ return this._length - 1; |
+ return index; |
+ } |
/** |
+ * @override |
* @return {number} |
*/ |
- itemCount() { |
- return 0; |
- }, |
+ totalHeight() { |
+ if (!this._length) |
+ return 0; |
+ return this._length * this.elementHeight(); |
+ } |
/** |
+ * @override |
* @param {number} index |
- * @return {?Element} |
+ * @return {!Element} |
+ */ |
+ elementAtIndex(index) { |
+ var element = this._elements[index]; |
+ if (!element) { |
+ element = this._elementsFactory.call(null, index); |
+ this._elements[index] = element; |
+ } |
+ return element; |
+ } |
+ |
+ /** |
+ * @override |
+ * @param {number} index |
+ * @return {number} |
*/ |
- itemElement(index) { |
- return null; |
+ offsetAtIndex(index) { |
+ if (!this._length) |
+ return 0; |
+ return index * this.elementHeight(); |
} |
}; |