OLD | NEW |
| (Empty) |
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 | |
3 // found in the LICENSE file. | |
4 /** | |
5 * @unrestricted | |
6 */ | |
7 UI.ViewportControl = class { | |
8 /** | |
9 * @param {!UI.ViewportControl.Provider} provider | |
10 */ | |
11 constructor(provider) { | |
12 this.element = createElement('div'); | |
13 this.element.style.overflow = 'auto'; | |
14 this._innerElement = this.element.createChild('div'); | |
15 this._innerElement.style.height = '0px'; | |
16 this._innerElement.style.position = 'relative'; | |
17 this._innerElement.style.overflow = 'hidden'; | |
18 | |
19 this._provider = provider; | |
20 this.element.addEventListener('scroll', this._update.bind(this), false); | |
21 this._itemCount = 0; | |
22 this._indexSymbol = Symbol('UI.ViewportControl._indexSymbol'); | |
23 } | |
24 | |
25 refresh() { | |
26 this._itemCount = this._provider.itemCount(); | |
27 this._innerElement.removeChildren(); | |
28 | |
29 var height = 0; | |
30 this._cumulativeHeights = new Int32Array(this._itemCount); | |
31 for (var i = 0; i < this._itemCount; ++i) { | |
32 height += this._provider.fastItemHeight(i); | |
33 this._cumulativeHeights[i] = height; | |
34 } | |
35 this._innerElement.style.height = height + 'px'; | |
36 | |
37 this._update(); | |
38 } | |
39 | |
40 _update() { | |
41 if (!this._cumulativeHeights) { | |
42 this.refresh(); | |
43 return; | |
44 } | |
45 | |
46 var visibleHeight = this._visibleHeight(); | |
47 var visibleFrom = this.element.scrollTop; | |
48 var activeHeight = visibleHeight * 2; | |
49 var firstActiveIndex = Math.max( | |
50 Array.prototype.lowerBound.call(this._cumulativeHeights, visibleFrom + 1
- (activeHeight - visibleHeight) / 2), | |
51 0); | |
52 var lastActiveIndex = Math.min( | |
53 Array.prototype.lowerBound.call( | |
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; | |
75 | |
76 element.style.position = 'absolute'; | |
77 element.style.top = (this._cumulativeHeights[index - 1] || 0) + 'px'; | |
78 element.style.left = '0'; | |
79 element.style.right = '0'; | |
80 element[this._indexSymbol] = index; | |
81 this._innerElement.appendChild(element); | |
82 } | |
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; | |
109 if (makeLast) | |
110 this.forceScrollItemToBeLast(index); | |
111 else if (index <= firstVisibleIndex) | |
112 this.forceScrollItemToBeFirst(index); | |
113 else if (index >= lastVisibleIndex) | |
114 this.forceScrollItemToBeLast(index); | |
115 } | |
116 | |
117 /** | |
118 * @param {number} index | |
119 */ | |
120 forceScrollItemToBeFirst(index) { | |
121 this.element.scrollTop = index > 0 ? this._cumulativeHeights[index - 1] : 0; | |
122 this._update(); | |
123 } | |
124 | |
125 /** | |
126 * @param {number} index | |
127 */ | |
128 forceScrollItemToBeLast(index) { | |
129 this.element.scrollTop = this._cumulativeHeights[index] - this._visibleHeigh
t(); | |
130 this._update(); | |
131 } | |
132 | |
133 /** | |
134 * @return {number} | |
135 */ | |
136 _visibleHeight() { | |
137 return this.element.offsetHeight; | |
138 } | |
139 }; | |
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 }; | |
OLD | NEW |