| 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 |