Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(656)

Unified Diff: third_party/WebKit/Source/devtools/front_end/ui/ViewportControl.js

Issue 2592433003: [DevTools] Replace ViewportControl with ListControl. (Closed)
Patch Set: partial Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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..e868af22f6501ff876f7318c4b7a420758ebe606 100644
--- a/third_party/WebKit/Source/devtools/front_end/ui/ViewportControl.js
+++ b/third_party/WebKit/Source/devtools/front_end/ui/ViewportControl.js
@@ -3,167 +3,330 @@
// found in the LICENSE file.
/**
* @unrestricted
+ * @template T
*/
UI.ViewportControl = class {
- /**
- * @param {!UI.ViewportControl.Provider} provider
- */
- constructor(provider) {
+ constructor() {
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._provider = provider;
- this.element.addEventListener('scroll', this._update.bind(this), false);
- this._itemCount = 0;
- this._indexSymbol = Symbol('UI.ViewportControl._indexSymbol');
- }
-
- 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;
- }
- this._innerElement.style.height = height + 'px';
-
- this._update();
+ this._topElement = this.element.createChild('div');
+ this._bottomElement = this.element.createChild('div');
+ this._firstIndex = 0;
+ this._lastIndex = 0;
+ this._fixedHeight = true;
+ this._measuredHeight = 0;
+ /** @type {?function(T):!Element} */
+ this._renderer = null;
+ /** @type {!Array<T>} */
+ this._items = [];
+ this._elementSymbol = Symbol('UI.ViewportControl.element');
+ this.element.addEventListener('scroll', this._onScroll.bind(this), false);
+ this._update(0);
}
- _update() {
- if (!this._cumulativeHeights) {
- this.refresh();
- return;
- }
+ setVariableHeights() {
+ // TODO(dgozman): support variable height.
+ throw 'Not supported';
+ }
- 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();
- }
+ /**
+ * @param {number} elementHeight
+ */
+ setFixedHeights(elementHeight) {
+ this._fixedHeight = true;
+ this._measuredHeight = elementHeight;
+ }
- for (var i = firstActiveIndex; i <= lastActiveIndex; ++i)
- this._insertElement(i);
+ // !!!!! Move this to clients.
+ setFixedMeasuredHeights() {
+ this._fixedHeight = true;
+ this._measuredHeight = 0;
}
/**
- * @param {number} index
+ * @param {?function(T):!Element} renderer
*/
- _insertElement(index) {
- var element = this._provider.itemElement(index);
- if (!element || element.parentElement === this._innerElement)
- return;
+ setRenderer(renderer) {
+ this._renderer = renderer;
+ for (var i = 0; i < this._items; i++)
+ this._items[i][this._elementSymbol] = null;
+ this._refresh();
+ }
- 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);
+ slowRefresh() {
+ this._refresh();
}
/**
* @return {number}
*/
- firstVisibleIndex() {
- return Math.max(Array.prototype.lowerBound.call(this._cumulativeHeights, this.element.scrollTop + 1), 0);
+ measuredElementHeight() {
+ return this._elementHeight();
}
/**
* @return {number}
*/
- lastVisibleIndex() {
- return Math.min(
- Array.prototype.lowerBound.call(this._cumulativeHeights, this.element.scrollTop + this._visibleHeight()),
- this._itemCount);
+ elementsPerViewport() {
+ if (!this._fixedHeight)
+ throw 'Only supported in fixed-height scenario';
+ var elementHeight = this._elementHeight();
+ return elementHeight ? Math.floor(this.element.offsetHeight / elementHeight) : 0;
}
/**
* @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) {
+ return this._elementAtIndex(index);
+ }
+
+ /**
+ * @return {number}
+ */
+ length() {
+ return this._items.length;
}
/**
* @param {number} index
+ * @return {T}
*/
- forceScrollItemToBeFirst(index) {
- this.element.scrollTop = index > 0 ? this._cumulativeHeights[index - 1] : 0;
- this._update();
+ itemAtIndex(index) {
+ return this._items[index];
+ }
+
+ /**
+ * @param {T} item
+ */
+ pushItem(item) {
+ var totalHeight = this._totalHeight();
+ this._items.push(item);
+ this._invalidate(totalHeight, this._items.length - 1, this._items.length - 1);
+ }
+
+ /**
+ * @return {T}
+ */
+ popItem() {
+ var totalHeight = this._totalHeight();
+ var result = this._items.pop();
+ result[this._elementSymbol] = null;
+ this._invalidate(totalHeight, this._items.length - 1, this._items.length);
+ return result;
}
/**
* @param {number} index
+ * @param {T} item
*/
- forceScrollItemToBeLast(index) {
- this.element.scrollTop = this._cumulativeHeights[index] - this._visibleHeight();
- this._update();
+ insertItemAtIndex(index, item) {
+ var totalHeight = this._totalHeight();
+ this._items.splice(index, 0, item);
+ this._invalidate(totalHeight, index, index);
}
/**
- * @return {number}
+ * @param {number} index
+ * @return {T}
*/
- _visibleHeight() {
- return this.element.offsetHeight;
+ removeItemAtIndex(index) {
+ var totalHeight = this._totalHeight();
+ var result = this._items[index];
+ this._items.splice(index, 1);
+ result[this._elementSymbol] = null;
+ this._invalidate(totalHeight, index, index + 1);
+ return result;
}
-};
-/**
- * @interface
- */
-UI.ViewportControl.Provider = function() {};
+ /**
+ * @param {number} from
+ * @param {number} to
+ * @param {!Array<T>} items
+ */
+ replaceItemsInRange(from, to, items) {
+ var totalHeight = this._totalHeight();
+ for (var i = from; i < to; i++)
+ this._items[i][this._elementSymbol] = null;
+ this._items.splice.bind(this._items, from, to - from).apply(null, items);
+ this._invalidate(totalHeight, from, to);
+ }
+
+ /**
+ * @param {!Array<T>} items
+ */
+ replaceAllItems(items) {
+ var totalHeight = this._totalHeight();
+ var length = this._items.length;
+ for (var i = 0; i < this._items; i++)
+ this._items[i][this._elementSymbol] = null;
+ this._items = items;
+ this._invalidate(totalHeight, 0, length);
+ }
-UI.ViewportControl.Provider.prototype = {
/**
* @param {number} index
+ */
+ scrollItemAtIndexIntoView(index) {
+ var top = this._offsetAtIndex(index);
+ var bottom = this._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);
+ }
+
+ /**
* @return {number}
*/
- fastItemHeight(index) {
- return 0;
- },
+ _elementHeight() {
+ if (!this._fixedHeight)
+ throw 'Only measure in fixed-height scenario';
+ if (!this._measuredHeight && this._items.length)
+ this._measuredHeight = UI.measurePreferredSize(this._elementAtIndex(0), this.element).height;
+ return this._measuredHeight;
+ }
/**
* @return {number}
*/
- itemCount() {
- return 0;
- },
+ _totalHeight() {
+ return this._items.length ? this._items.length * this._elementHeight() : 0;
+ }
+
+ /**
+ * @param {number} offset
+ * @return {number}
+ */
+ _indexAtOffset(offset) {
+ if (!this._items.length)
+ return 0;
+ var index = Math.floor(offset / this._elementHeight());
+ if (index >= this._items.length)
+ return this._items.length - 1;
+ return index;
+ }
/**
* @param {number} index
- * @return {?Element}
+ * @return {!Element}
+ */
+ _elementAtIndex(index) {
+ var item = this._items[index];
+ var element = item[this._elementSymbol];
+ if (!element) {
+ if (this._renderer) {
+ element = this._renderer.call(null, item);
+ } else {
+ element = createElement('div');
+ // TODO(dgozman): support variable height.
+ element.style.height = this._measuredHeight + 'px';
+ }
+ item[this._elementSymbol] = element;
+ }
+ return element;
+ }
+
+ /**
+ * @param {number} index
+ * @return {number}
+ */
+ _offsetAtIndex(index) {
+ return this._items.length ? index * this._elementHeight() : 0;
+ }
+
+ /**
+ * @param {number} totalHeight
+ * @param {number} from
+ * @param {number} to
+ */
+ _invalidate(totalHeight, from, to) {
+ var scrollTop = this.element.scrollTop;
+ var availableHeight = this.element.offsetHeight;
+ var heightDelta = this._totalHeight() - totalHeight;
+ if (totalHeight < availableHeight || this._totalHeight() < availableHeight) {
+ this._refresh();
+ return;
+ }
+ if (to <= this._firstIndex) {
+ var topHeight = this._topHeight + heightDelta;
+ this._topElement.style.height = topHeight + 'px';
+ this.element.scrollTop = scrollTop + heightDelta;
+ this._topHeight = topHeight;
+ return;
+ }
+ if (from >= this._lastIndex) {
+ var bottomHeight = this._bottomHeight + heightDelta;
+ this._bottomElement.style.height = bottomHeight + 'px';
+ this.element.scrollTop = scrollTop + heightDelta;
+ this._bottomHeight = bottomHeight;
+ 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);
+ }
+
+ _onScroll() {
+ this._update(this.element.scrollTop);
+ }
+
+ /**
+ * @param {number} scrollTop
*/
- itemElement(index) {
- return null;
+ _update(scrollTop) {
+ var totalHeight = this._totalHeight();
+ if (!totalHeight) {
+ this._firstIndex = 0;
+ this._lastIndex = 0;
+ this._topHeight = 0;
+ this._bottomHeight = 0;
+ this._topElement.style.height = '0px';
+ this._bottomElement.style.height = '0px';
+ return;
+ }
+
+ var height = this.element.offsetHeight;
+ var firstIndex = this._indexAtOffset(Math.max(0, scrollTop - height));
+ var lastIndex = this._indexAtOffset(Math.min(totalHeight, scrollTop + 2 * height)) + 1;
+
+ for (var index = this._firstIndex; index < firstIndex; index++) {
+ var element = this._elementAtIndex(index);
+ element.remove();
+ this._firstIndex++;
+ }
+ for (var index = this._lastIndex - 1; index >= lastIndex; index--) {
+ var element = this._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._elementAtIndex(index);
+ this.element.insertBefore(element, this._topElement.nextSibling);
+ }
+ for (var index = this._lastIndex; index < lastIndex; index++) {
+ var element = this._elementAtIndex(index);
+ this.element.insertBefore(element, this._bottomElement);
+ }
+
+ this._firstIndex = firstIndex;
+ this._lastIndex = lastIndex;
+ this._topHeight = this._offsetAtIndex(firstIndex);
+ this._topElement.style.height = this._topHeight + 'px';
+ this._bottomHeight = (totalHeight - this._offsetAtIndex(lastIndex));
+ this._bottomElement.style.height = this._bottomHeight + 'px';
+ this.element.scrollTop = scrollTop;
}
};

Powered by Google App Engine
This is Rietveld 408576698