OLD | NEW |
| (Empty) |
1 // Copyright (c) 2013 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 'use strict'; | |
6 | |
7 /** | |
8 * Creates a new scroll bar element. | |
9 * @extends {HTMLDivElement} | |
10 * @constructor | |
11 */ | |
12 var ScrollBar = cr.ui.define('div'); | |
13 | |
14 /** | |
15 * Mode of the scrollbar. As for now, only vertical scrollbars are supported. | |
16 * @type {number} | |
17 */ | |
18 ScrollBar.Mode = { | |
19 VERTICAL: 0, | |
20 HORIZONTAL: 1 | |
21 }; | |
22 | |
23 ScrollBar.prototype = { | |
24 set mode(value) { | |
25 this.mode_ = value; | |
26 if (this.mode_ == ScrollBar.Mode.VERTICAL) { | |
27 this.classList.remove('scrollbar-horizontal'); | |
28 this.classList.add('scrollbar-vertical'); | |
29 } else { | |
30 this.classList.remove('scrollbar-vertical'); | |
31 this.classList.add('scrollbar-horizontal'); | |
32 } | |
33 this.redraw_(); | |
34 }, | |
35 get mode() { | |
36 return this.mode_; | |
37 } | |
38 }; | |
39 | |
40 /** | |
41 * Inherits after HTMLDivElement. | |
42 */ | |
43 ScrollBar.prototype.__proto__ = HTMLDivElement.prototype; | |
44 | |
45 /** | |
46 * Initializes the DOM structure of the scrollbar. | |
47 */ | |
48 ScrollBar.prototype.decorate = function() { | |
49 this.classList.add('scrollbar'); | |
50 this.button_ = util.createChild(this, 'scrollbar-button', 'div'); | |
51 this.mode = ScrollBar.Mode.VERTICAL; | |
52 this.idleTimerId_ = 0; | |
53 | |
54 this.button_.addEventListener('mousedown', | |
55 this.onButtonPressed_.bind(this)); | |
56 window.addEventListener('mouseup', this.onMouseUp_.bind(this)); | |
57 window.addEventListener('mousemove', this.onMouseMove_.bind(this)); | |
58 }; | |
59 | |
60 /** | |
61 * Initialize a scrollbar. | |
62 * | |
63 * @param {Element} parent Parent element, must have a relative or absolute | |
64 * positioning. | |
65 * @param {Element=} opt_scrollableArea Element with scrollable contents. | |
66 * If not passed, then call attachToView manually when the scrollable | |
67 * element becomes available. | |
68 */ | |
69 ScrollBar.prototype.initialize = function(parent, opt_scrollableArea) { | |
70 parent.appendChild(this); | |
71 if (opt_scrollableArea) | |
72 this.attachToView(opt_scrollableArea); | |
73 }; | |
74 | |
75 /** | |
76 * Attaches the scrollbar to a scrollable element and attaches handlers. | |
77 * @param {Element} view Scrollable element. | |
78 */ | |
79 ScrollBar.prototype.attachToView = function(view) { | |
80 this.view_ = view; | |
81 this.view_.addEventListener('scroll', this.onScroll_.bind(this)); | |
82 this.view_.addEventListener('relayout', this.onRelayout_.bind(this)); | |
83 this.domObserver_ = new MutationObserver(this.onDomChanged_.bind(this)); | |
84 this.domObserver_.observe(this.view_, {subtree: true, attributes: true}); | |
85 this.onRelayout_(); | |
86 }; | |
87 | |
88 /** | |
89 * Scroll handler. | |
90 * @private | |
91 */ | |
92 ScrollBar.prototype.onScroll_ = function() { | |
93 this.scrollTop_ = this.view_.scrollTop; | |
94 this.redraw_(); | |
95 | |
96 // Add class 'scrolling' to scrollbar to make it visible while scrolling. | |
97 this.button_.classList.add('scrolling'); | |
98 | |
99 // Set timer to remove class 'scrolling' after scrolling becomes idle. | |
100 if (this.idleTimerId_) | |
101 clearTimeout(this.idleTimerId_); | |
102 this.idleTimerId_ = setTimeout(function() { | |
103 this.idleTimerId_ = 0; | |
104 this.button_.classList.remove('scrolling'); | |
105 }.bind(this), 1000); | |
106 }; | |
107 | |
108 /** | |
109 * Relayout handler. | |
110 * @private | |
111 */ | |
112 ScrollBar.prototype.onRelayout_ = function() { | |
113 this.scrollHeight_ = this.view_.scrollHeight; | |
114 this.clientHeight_ = this.view_.clientHeight; | |
115 this.offsetTop_ = this.view_.offsetTop; | |
116 this.scrollTop_ = this.view_.scrollTop; | |
117 this.redraw_(); | |
118 }; | |
119 | |
120 /** | |
121 * Pressing on the scrollbar's button handler. | |
122 * | |
123 * @param {Event} event Pressing event. | |
124 * @private | |
125 */ | |
126 ScrollBar.prototype.onButtonPressed_ = function(event) { | |
127 this.buttonPressed_ = true; | |
128 this.buttonPressedEvent_ = event; | |
129 this.buttonPressedPosition_ = this.button_.offsetTop - this.view_.offsetTop; | |
130 this.button_.classList.add('pressed'); | |
131 | |
132 event.preventDefault(); | |
133 }; | |
134 | |
135 /** | |
136 * Releasing the button handler. Note, that it may not be called when releasing | |
137 * outside of the window. Therefore this is also called from onMouseMove_. | |
138 * | |
139 * @param {Event} event Mouse event. | |
140 * @private | |
141 */ | |
142 ScrollBar.prototype.onMouseUp_ = function(event) { | |
143 this.buttonPressed_ = false; | |
144 this.button_.classList.remove('pressed'); | |
145 }; | |
146 | |
147 /** | |
148 * Mouse move handler. Updates the scroll position. | |
149 * | |
150 * @param {Event} event Mouse event. | |
151 * @private | |
152 */ | |
153 ScrollBar.prototype.onMouseMove_ = function(event) { | |
154 if (!this.buttonPressed_) | |
155 return; | |
156 if (!event.which) { | |
157 this.onMouseUp_(event); | |
158 return; | |
159 } | |
160 var clientSize = this.getClientHeight(); | |
161 var totalSize = this.getTotalHeight(); | |
162 // TODO(hirono): Fix the geometric calculation. crbug.com/253779 | |
163 var buttonSize = Math.max(50, clientSize / totalSize * clientSize); | |
164 var buttonPosition = this.buttonPressedPosition_ + | |
165 (event.screenY - this.buttonPressedEvent_.screenY); | |
166 // Ensures the scrollbar is in the view. | |
167 buttonPosition = | |
168 Math.max(0, Math.min(buttonPosition, clientSize - buttonSize)); | |
169 var scrollPosition; | |
170 if (clientSize > buttonSize) { | |
171 scrollPosition = Math.max(totalSize - clientSize, 0) * | |
172 buttonPosition / (clientSize - buttonSize); | |
173 } else { | |
174 scrollPosition = 0; | |
175 } | |
176 | |
177 this.scrollTop_ = scrollPosition; | |
178 this.view_.scrollTop = scrollPosition; | |
179 this.redraw_(); | |
180 }; | |
181 | |
182 /** | |
183 * Handles changed in Dom by redrawing the scrollbar. Ignores consecutive calls. | |
184 * @private | |
185 */ | |
186 ScrollBar.prototype.onDomChanged_ = function() { | |
187 if (this.domChangedTimer_) { | |
188 clearTimeout(this.domChangedTimer_); | |
189 this.domChangedTimer_ = null; | |
190 } | |
191 this.domChangedTimer_ = setTimeout(function() { | |
192 this.onRelayout_(); | |
193 this.domChangedTimer_ = null; | |
194 }.bind(this), 50); | |
195 }; | |
196 | |
197 /** | |
198 * Redraws the scrollbar. | |
199 * @private | |
200 */ | |
201 ScrollBar.prototype.redraw_ = function() { | |
202 if (!this.view_) | |
203 return; | |
204 | |
205 var clientSize = this.getClientHeight(); | |
206 var clientTop = this.offsetTop_; | |
207 var scrollPosition = this.scrollTop_; | |
208 var totalSize = this.getTotalHeight(); | |
209 var hidden = totalSize <= clientSize; | |
210 | |
211 var buttonSize = Math.max(50, clientSize / totalSize * clientSize); | |
212 var buttonPosition; | |
213 if (clientSize - buttonSize > 0) { | |
214 buttonPosition = scrollPosition / (totalSize - clientSize) * | |
215 (clientSize - buttonSize); | |
216 } else { | |
217 buttonPosition = 0; | |
218 } | |
219 var buttonTop = buttonPosition + clientTop; | |
220 | |
221 var time = Date.now(); | |
222 if (this.hidden != hidden || | |
223 this.lastButtonTop_ != buttonTop || | |
224 this.lastButtonSize_ != buttonSize) { | |
225 requestAnimationFrame(function() { | |
226 this.hidden = hidden; | |
227 this.button_.style.top = buttonTop + 'px'; | |
228 this.button_.style.height = buttonSize + 'px'; | |
229 }.bind(this)); | |
230 } | |
231 | |
232 this.lastButtonTop_ = buttonTop; | |
233 this.lastButtonSize_ = buttonSize; | |
234 }; | |
235 | |
236 /** | |
237 * Returns the viewport height of the view. | |
238 * @return {number} The viewport height of the view in px. | |
239 * @protected | |
240 */ | |
241 ScrollBar.prototype.getClientHeight = function() { | |
242 return this.clientHeight_; | |
243 }; | |
244 | |
245 /** | |
246 * Returns the total height of the view. | |
247 * @return {number} The total height of the view in px. | |
248 * @protected | |
249 */ | |
250 ScrollBar.prototype.getTotalHeight = function() { | |
251 return this.scrollHeight_; | |
252 }; | |
253 | |
254 /** | |
255 * Creates a new scroll bar for elements in the main panel. | |
256 * @extends {ScrollBar} | |
257 * @constructor | |
258 */ | |
259 var MainPanelScrollBar = cr.ui.define('div'); | |
260 | |
261 /** | |
262 * Inherits after ScrollBar. | |
263 */ | |
264 MainPanelScrollBar.prototype.__proto__ = ScrollBar.prototype; | |
265 | |
266 /** @override */ | |
267 MainPanelScrollBar.prototype.decorate = function() { | |
268 ScrollBar.prototype.decorate.call(this); | |
269 | |
270 /** | |
271 * Margin for the transparent preview panel at the bottom. | |
272 * @type {number} | |
273 * @private | |
274 */ | |
275 this.bottomMarginForPanel_ = 0; | |
276 }; | |
277 | |
278 /** | |
279 * GReturns the viewport height of the view, considering the preview panel. | |
280 * | |
281 * @return {number} The viewport height of the view in px. | |
282 * @override | |
283 * @protected | |
284 */ | |
285 MainPanelScrollBar.prototype.getClientHeight = function() { | |
286 return this.clientHeight_ - this.bottomMarginForPanel_; | |
287 }; | |
288 | |
289 /** | |
290 * Returns the total height of the view, considering the preview panel. | |
291 * | |
292 * @return {number} The total height of the view in px. | |
293 * @override | |
294 * @protected | |
295 */ | |
296 MainPanelScrollBar.prototype.getTotalHeight = function() { | |
297 return this.scrollHeight_ - this.bottomMarginForPanel_; | |
298 }; | |
299 | |
300 /** | |
301 * Sets the bottom margin height of the view for the transparent preview panel. | |
302 * @param {number} margin Margin to be set in px. | |
303 */ | |
304 MainPanelScrollBar.prototype.setBottomMarginForPanel = function(margin) { | |
305 this.bottomMarginForPanel_ = margin; | |
306 }; | |
OLD | NEW |