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

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

Issue 2592433003: [DevTools] Replace ViewportControl with ListControl. (Closed)
Patch Set: different api Created 3 years, 12 months 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 unified diff | Download patch
OLDNEW
1 // Copyright 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 /** 4 /**
5 * @unrestricted 5 * @unrestricted
6 * @template T
einbinder 2016/12/21 00:23:53 T needs to be some kind of object, otherwise you c
6 */ 7 */
7 UI.ViewportControl = class { 8 UI.ViewportControl = class {
8 /** 9 /**
9 * @param {!UI.ViewportControl.Provider} provider 10 * @param {?function(T):!Element} renderer
10 */ 11 */
11 constructor(provider) { 12 constructor(renderer) {
12 this.element = createElement('div'); 13 this.element = createElement('div');
13 this.element.style.overflow = 'auto'; 14 this.element.style.overflow = 'auto';
14 this._innerElement = this.element.createChild('div'); 15 this._topElement = this.element.createChild('div');
15 this._innerElement.style.height = '0px'; 16 this._bottomElement = this.element.createChild('div');
16 this._innerElement.style.position = 'relative'; 17
17 this._innerElement.style.overflow = 'hidden'; 18 this._firstIndex = 0;
18 19 this._lastIndex = 0;
19 this._provider = provider; 20 this._renderedHeight = 0;
20 this.element.addEventListener('scroll', this._update.bind(this), false); 21
21 this._itemCount = 0; 22 this._fixedHeight = 0;
22 this._indexSymbol = Symbol('UI.ViewportControl._indexSymbol'); 23 /** @type {?function(T):!Element} */
23 } 24 this._renderer = renderer;
24 25 /** @type {!Array<T>} */
25 refresh() { 26 this._items = [];
26 this._itemCount = this._provider.itemCount(); 27
27 this._innerElement.removeChildren(); 28 this._elementSymbol = Symbol('UI.ViewportControl.element');
einbinder 2016/12/21 00:23:53 It might make sense to use a WeakMap (or just a Ma
dgozman 2016/12/27 21:32:59 Done.
28 29 this.element.addEventListener('scroll', this._onScroll.bind(this), false);
29 var height = 0; 30 this._update(0, this.element.offsetHeight);
30 this._cumulativeHeights = new Int32Array(this._itemCount); 31 }
31 for (var i = 0; i < this._itemCount; ++i) { 32
32 height += this._provider.fastItemHeight(i); 33 setVariableHeight() {
33 this._cumulativeHeights[i] = height; 34 // TODO(dgozman): support variable height.
34 } 35 throw 'Not supported';
35 this._innerElement.style.height = height + 'px'; 36 }
36 37
37 this._update(); 38 /**
38 } 39 * @param {number} elementHeight
39 40 */
40 _update() { 41 setFixedHeight(elementHeight) {
41 if (!this._cumulativeHeights) { 42 this._fixedHeight = elementHeight;
42 this.refresh(); 43 }
44
45 /**
46 * @param {?function(T):!Element} renderer
47 */
48 setRenderer(renderer) {
49 this._renderer = renderer;
50 for (var i = 0; i < this._items.length; i++)
51 this._items[i][this._elementSymbol] = null;
52 this._refresh();
53 }
54
55 /**
56 * @param {number} from
57 * @param {number} to
58 */
59 refreshRange(from, to) {
60 this._invalidate(from, to);
61 }
62
63 resized() {
64 this._refresh();
65 }
66
67 /**
68 * @param {number} index
69 */
70 scrollItemAtIndexIntoView(index) {
71 var top = this._offsetAtIndex(index);
72 var bottom = this._offsetAtIndex(index + 1);
73 var scrollTop = this.element.scrollTop;
74 var height = this.element.offsetHeight;
75 if (top < scrollTop)
76 this._update(top, height);
77 else if (bottom > scrollTop + height)
78 this._update(bottom - height, height);
79 }
80
81 /**
82 * @param {number} index
83 * @return {!Element}
84 */
85 elementAtIndex(index) {
86 return this._elementAtIndex(index);
87 }
88
89 /**
90 * @return {number}
91 */
92 length() {
93 return this._items.length;
94 }
95
96 /**
97 * @param {number} index
98 * @return {T}
99 */
100 itemAtIndex(index) {
101 return this._items[index];
102 }
103
104 /**
105 * @param {T} item
106 */
107 pushItem(item) {
108 this._items.push(item);
109 this._invalidate(this._items.length - 1, this._items.length - 1);
110 }
111
112 /**
113 * @return {T}
114 */
115 popItem() {
116 var result = this._items.pop();
117 result[this._elementSymbol] = null;
118 this._invalidate(this._items.length - 1, this._items.length);
119 return result;
120 }
121
122 /**
123 * @param {number} index
124 * @param {T} item
125 */
126 insertItemAtIndex(index, item) {
127 this._items.splice(index, 0, item);
128 this._invalidate(index, index);
129 }
130
131 /**
132 * @param {number} index
133 * @return {T}
134 */
135 removeItemAtIndex(index) {
136 var result = this._items[index];
137 this._items.splice(index, 1);
138 result[this._elementSymbol] = null;
139 this._invalidate(index, index + 1);
140 return result;
141 }
142
143 /**
144 * @param {number} from
145 * @param {number} to
146 * @param {!Array<T>} items
147 */
148 replaceItemsInRange(from, to, items) {
149 for (var i = from; i < to; i++)
150 this._items[i][this._elementSymbol] = null;
151 this._items.splice.bind(this._items, from, to - from).apply(null, items);
152 this._invalidate(from, to);
153 }
154
155 /**
156 * @param {!Array<T>} items
157 */
158 replaceAllItems(items) {
159 var length = this._items.length;
160 for (var i = 0; i < this._items.length; i++)
161 this._items[i][this._elementSymbol] = null;
162 this._items = items;
163 this._invalidate(0, length);
164 }
165
166 /**
167 * @return {number}
168 */
169 _totalHeight() {
170 return this._items.length * this._fixedHeight;
171 }
172
173 /**
174 * @param {number} offset
175 * @return {number}
176 */
177 _indexAtOffset(offset) {
178 if (!this._items.length)
179 return 0;
180 var index = Math.floor(offset / this._fixedHeight);
181 if (index >= this._items.length)
182 return this._items.length - 1;
183 return index;
184 }
185
186 /**
187 * @param {number} index
188 * @return {!Element}
189 */
190 _elementAtIndex(index) {
191 var item = this._items[index];
192 var element = item[this._elementSymbol];
193 if (!element) {
194 if (this._renderer) {
195 element = this._renderer.call(null, item);
196 } else {
197 element = createElement('div');
198 // TODO(dgozman): support variable height.
199 element.style.height = this._fixedHeight + 'px';
200 }
201 item[this._elementSymbol] = element;
202 }
203 return element;
204 }
205
206 /**
207 * @param {number} index
208 * @return {number}
209 */
210 _offsetAtIndex(index) {
211 return index * this._fixedHeight;
212 }
213
214 /**
215 * @param {number} from
216 * @param {number} to
217 */
218 _invalidate(from, to) {
219 var availableHeight = this.element.offsetHeight;
220 var totalHeight = this._totalHeight();
221 if (this._renderedHeight < availableHeight || totalHeight < availableHeight) {
222 this._refresh();
43 return; 223 return;
44 } 224 }
45 225
46 var visibleHeight = this._visibleHeight(); 226 var scrollTop = this.element.scrollTop;
47 var visibleFrom = this.element.scrollTop; 227 var heightDelta = totalHeight - this._renderedHeight;
48 var activeHeight = visibleHeight * 2; 228 if (to <= this._firstIndex) {
49 var firstActiveIndex = Math.max( 229 var topHeight = this._topHeight + heightDelta;
50 Array.prototype.lowerBound.call(this._cumulativeHeights, visibleFrom + 1 - (activeHeight - visibleHeight) / 2), 230 this._topElement.style.height = topHeight + 'px';
51 0); 231 this.element.scrollTop = scrollTop + heightDelta;
52 var lastActiveIndex = Math.min( 232 this._topHeight = topHeight;
53 Array.prototype.lowerBound.call( 233 this._renderedHeight = totalHeight;
54 this._cumulativeHeights, visibleFrom + visibleHeight + (activeHeight - visibleHeight) / 2),
55 this._itemCount - 1);
56
57 var children = this._innerElement.children;
58 for (var i = children.length - 1; i >= 0; --i) {
59 var element = children[i];
60 if (element[this._indexSymbol] < firstActiveIndex || element[this._indexSy mbol] > lastActiveIndex)
61 element.remove();
62 }
63
64 for (var i = firstActiveIndex; i <= lastActiveIndex; ++i)
65 this._insertElement(i);
66 }
67
68 /**
69 * @param {number} index
70 */
71 _insertElement(index) {
72 var element = this._provider.itemElement(index);
73 if (!element || element.parentElement === this._innerElement)
74 return; 234 return;
75 235 }
76 element.style.position = 'absolute'; 236
77 element.style.top = (this._cumulativeHeights[index - 1] || 0) + 'px'; 237 if (from >= this._lastIndex) {
78 element.style.left = '0'; 238 var bottomHeight = this._bottomHeight + heightDelta;
79 element.style.right = '0'; 239 this._bottomElement.style.height = bottomHeight + 'px';
80 element[this._indexSymbol] = index; 240 this.element.scrollTop = scrollTop + heightDelta;
81 this._innerElement.appendChild(element); 241 this._bottomHeight = bottomHeight;
82 } 242 this._renderedHeight = totalHeight;
83
84 /**
85 * @return {number}
86 */
87 firstVisibleIndex() {
88 return Math.max(Array.prototype.lowerBound.call(this._cumulativeHeights, thi s.element.scrollTop + 1), 0);
89 }
90
91 /**
92 * @return {number}
93 */
94 lastVisibleIndex() {
95 return Math.min(
96 Array.prototype.lowerBound.call(this._cumulativeHeights, this.element.sc rollTop + this._visibleHeight()),
97 this._itemCount);
98 }
99
100 /**
101 * @param {number} index
102 * @param {boolean=} makeLast
103 */
104 scrollItemIntoView(index, makeLast) {
105 var firstVisibleIndex = this.firstVisibleIndex();
106 var lastVisibleIndex = this.lastVisibleIndex();
107 if (index > firstVisibleIndex && index < lastVisibleIndex)
108 return; 243 return;
109 if (makeLast) 244 }
110 this.forceScrollItemToBeLast(index); 245
111 else if (index <= firstVisibleIndex) 246 this._refresh();
112 this.forceScrollItemToBeFirst(index); 247 }
113 else if (index >= lastVisibleIndex) 248
114 this.forceScrollItemToBeLast(index); 249 _refresh() {
115 } 250 var height = this.element.offsetHeight;
116 251 var scrollTop = Math.max(0, Math.min(this.element.scrollTop, this._totalHeig ht() - height));
117 /** 252 this._firstIndex = 0;
118 * @param {number} index 253 this._lastIndex = 0;
119 */ 254 this._renderedHeight = 0;
120 forceScrollItemToBeFirst(index) { 255 this.element.removeChildren();
121 this.element.scrollTop = index > 0 ? this._cumulativeHeights[index - 1] : 0; 256 this.element.appendChild(this._topElement);
122 this._update(); 257 this.element.appendChild(this._bottomElement);
123 } 258 this._update(scrollTop, height);
124 259 }
125 /** 260
126 * @param {number} index 261 _onScroll() {
127 */ 262 this._update(this.element.scrollTop, this.element.offsetHeight);
128 forceScrollItemToBeLast(index) { 263 }
129 this.element.scrollTop = this._cumulativeHeights[index] - this._visibleHeigh t(); 264
130 this._update(); 265 /**
131 } 266 * @param {number} scrollTop
132 267 * @param {number} height
133 /** 268 */
134 * @return {number} 269 _update(scrollTop, height) {
135 */ 270 // Note: this method should not force layout. Be careful.
136 _visibleHeight() { 271
137 return this.element.offsetHeight; 272 var totalHeight = this._totalHeight();
273 if (!totalHeight) {
274 this._firstIndex = 0;
275 this._lastIndex = 0;
276 this._topHeight = 0;
277 this._bottomHeight = 0;
278 this._renderedHeight = 0;
279 this._topElement.style.height = '0px';
280 this._bottomElement.style.height = '0px';
281 return;
282 }
283
284 var firstIndex = this._indexAtOffset(Math.max(0, scrollTop - height));
285 var lastIndex = this._indexAtOffset(Math.min(totalHeight, scrollTop + 2 * he ight)) + 1;
286
287 for (var index = this._firstIndex; index < firstIndex; index++) {
288 var element = this._elementAtIndex(index);
289 element.remove();
290 this._firstIndex++;
291 }
292 for (var index = this._lastIndex - 1; index >= lastIndex; index--) {
293 var element = this._elementAtIndex(index);
294 element.remove();
295 this._lastIndex--;
296 }
297 this._firstIndex = Math.min(this._firstIndex, lastIndex);
298 this._lastIndex = Math.max(this._lastIndex, firstIndex);
299 for (var index = this._firstIndex - 1; index >= firstIndex; index--) {
300 var element = this._elementAtIndex(index);
301 this.element.insertBefore(element, this._topElement.nextSibling);
302 }
303 for (var index = this._lastIndex; index < lastIndex; index++) {
304 var element = this._elementAtIndex(index);
305 this.element.insertBefore(element, this._bottomElement);
306 }
307
308 this._firstIndex = firstIndex;
309 this._lastIndex = lastIndex;
310 this._topHeight = this._offsetAtIndex(firstIndex);
311 this._topElement.style.height = this._topHeight + 'px';
312 this._bottomHeight = (totalHeight - this._offsetAtIndex(lastIndex));
313 this._bottomElement.style.height = this._bottomHeight + 'px';
314 this._renderedHeight = totalHeight;
315 this.element.scrollTop = scrollTop;
138 } 316 }
139 }; 317 };
140
141 /**
142 * @interface
143 */
144 UI.ViewportControl.Provider = function() {};
145
146 UI.ViewportControl.Provider.prototype = {
147 /**
148 * @param {number} index
149 * @return {number}
150 */
151 fastItemHeight(index) {
152 return 0;
153 },
154
155 /**
156 * @return {number}
157 */
158 itemCount() {
159 return 0;
160 },
161
162 /**
163 * @param {number} index
164 * @return {?Element}
165 */
166 itemElement(index) {
167 return null;
168 }
169 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698