| OLD | NEW |
| 1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 /** | 5 /** |
| 6 * Implementation of a custom scrolling behavior. | 6 * Implementation of a custom scrolling behavior. |
| 7 * This behavior overrides native scrolling for an area. This area can be a | 7 * This behavior overrides native scrolling for an area. This area can be a |
| 8 * single defined part of a page, the entire page, or several different parts | 8 * single defined part of a page, the entire page, or several different parts |
| 9 * of a page. | 9 * of a page. |
| 10 * | 10 * |
| (...skipping 12 matching lines...) Expand all Loading... |
| 23 * positioning (setting the left and top styles). | 23 * positioning (setting the left and top styles). |
| 24 * | 24 * |
| 25 * For this to work properly you need to set -webkit-text-size-adjust to 'none' | 25 * For this to work properly you need to set -webkit-text-size-adjust to 'none' |
| 26 * on an ancestor element of the frame, or on the frame itself. If you forget | 26 * on an ancestor element of the frame, or on the frame itself. If you forget |
| 27 * this you may see the text content of the scrollable area changing size as it | 27 * this you may see the text content of the scrollable area changing size as it |
| 28 * moves. | 28 * moves. |
| 29 * | 29 * |
| 30 * The behavior is intended to support vertical and horizontal scrolling, and | 30 * The behavior is intended to support vertical and horizontal scrolling, and |
| 31 * scrolling with momentum when a touch gesture flicks with enough velocity. | 31 * scrolling with momentum when a touch gesture flicks with enough velocity. |
| 32 */ | 32 */ |
| 33 typedef void Callback(); |
| 34 |
| 35 // Helper method to await the completion of 2 futures. |
| 36 void joinFutures(List<Future> futures, Callback callback) { |
| 37 int count = 0; |
| 38 int len = futures.length; |
| 39 void helper(value) { |
| 40 count++; |
| 41 if (count == len) { |
| 42 callback(); |
| 43 } |
| 44 } |
| 45 for (Future p in futures) { |
| 46 p.then(helper); |
| 47 } |
| 48 } |
| 33 | 49 |
| 34 class Scroller implements Draggable, MomentumDelegate { | 50 class Scroller implements Draggable, MomentumDelegate { |
| 35 | 51 |
| 36 /** Pixels to move each time an arrow key is pressed. */ | 52 /** Pixels to move each time an arrow key is pressed. */ |
| 37 static final ARROW_KEY_DELTA = 30; | 53 static final ARROW_KEY_DELTA = 30; |
| 38 static final SCROLL_WHEEL_VELOCITY = 0.01; | 54 static final SCROLL_WHEEL_VELOCITY = 0.01; |
| 39 static final FAST_SNAP_DECELERATION_FACTOR = 0.84; | 55 static final FAST_SNAP_DECELERATION_FACTOR = 0.84; |
| 40 static final PAGE_KEY_SCROLL_FRACTION = .85; | 56 static final PAGE_KEY_SCROLL_FRACTION = .85; |
| 41 | 57 |
| 42 // TODO(jacobr): remove this static variable. | 58 // TODO(jacobr): remove this static variable. |
| (...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 105 Function _setOffsetFunction; | 121 Function _setOffsetFunction; |
| 106 /** | 122 /** |
| 107 * Function that returns the content size that can be specified instead of | 123 * Function that returns the content size that can be specified instead of |
| 108 * querying the DOM. | 124 * querying the DOM. |
| 109 */ | 125 */ |
| 110 Function _lookupContentSizeDelegate; | 126 Function _lookupContentSizeDelegate; |
| 111 | 127 |
| 112 Size _scrollSize; | 128 Size _scrollSize; |
| 113 Size _contentSize; | 129 Size _contentSize; |
| 114 Coordinate _minPoint; | 130 Coordinate _minPoint; |
| 115 bool _activeTransition = false; | |
| 116 bool _isStopping = false; | 131 bool _isStopping = false; |
| 117 Coordinate _contentStartOffset; | 132 Coordinate _contentStartOffset; |
| 118 bool _started = false; | 133 bool _started = false; |
| 119 bool _activeGesture = false; | 134 bool _activeGesture = false; |
| 120 ScrollWatcher _scrollWatcher; | 135 ScrollWatcher _scrollWatcher; |
| 121 | 136 |
| 122 Scroller(Element scrollableElem, [this.verticalEnabled = false, | 137 Scroller(Element scrollableElem, [this.verticalEnabled = false, |
| 123 this.horizontalEnabled = false, | 138 this.horizontalEnabled = false, |
| 124 this._momentumEnabled = true, | 139 this._momentumEnabled = true, |
| 125 this._lookupContentSizeDelegate = null, | 140 this._lookupContentSizeDelegate = null, |
| 126 num defaultDecelerationFactor = 1, | 141 num defaultDecelerationFactor = 1, |
| 127 int scrollTechnique = null, bool capture = false]) | 142 int scrollTechnique = null, bool capture = false]) |
| 128 : _element = scrollableElem, | 143 : _element = scrollableElem, |
| 129 _frame = scrollableElem.parent, | 144 _frame = scrollableElem.parent, |
| 130 _scrollTechnique = scrollTechnique !== null | 145 _scrollTechnique = scrollTechnique !== null |
| 131 ? scrollTechnique : ScrollerScrollTechnique.TRANSFORM_3D, | 146 ? scrollTechnique : ScrollerScrollTechnique.TRANSFORM_3D, |
| 132 _minPoint = new Coordinate(0, 0), | 147 _minPoint = new Coordinate(0, 0), |
| 133 _maxPoint = new Coordinate(0, 0), | 148 _maxPoint = new Coordinate(0, 0), |
| 134 _maxOffset = new Coordinate(0, 0), | 149 _maxOffset = new Coordinate(0, 0), |
| 135 _minOffset = new Coordinate(0, 0), | 150 _minOffset = new Coordinate(0, 0), |
| 136 _contentOffset = new Coordinate(0, 0) { | 151 _contentOffset = new Coordinate(0, 0) { |
| 137 _touchHandler = new TouchHandler(this, scrollableElem.parent); | 152 _touchHandler = new TouchHandler(this, scrollableElem.parent); |
| 138 _momentum = new Momentum(this, defaultDecelerationFactor); | 153 _momentum = new Momentum(this, defaultDecelerationFactor); |
| 139 | 154 |
| 140 Element parentElem = scrollableElem.parent; | 155 Element parentElem = scrollableElem.parent; |
| 141 assert(parentElem != null); | 156 assert(parentElem != null); |
| 142 _setOffsetFunction = _getOffsetFunction(_scrollTechnique); | 157 _setOffsetFunction = _getOffsetFunction(_scrollTechnique); |
| 143 _element.on.transitionEnd.add((event) { onTransitionEnd(event); }); | |
| 144 _touchHandler.setDraggable(this); | 158 _touchHandler.setDraggable(this); |
| 145 _touchHandler.enable(capture); | 159 _touchHandler.enable(capture); |
| 146 | 160 |
| 147 _frame.on.mouseWheel.add((e) { | 161 _frame.on.mouseWheel.add((e) { |
| 148 if (e.wheelDeltaY != 0 && verticalEnabled || | 162 if (e.wheelDeltaY != 0 && verticalEnabled || |
| 149 e.wheelDeltaX != 0 && horizontalEnabled) { | 163 e.wheelDeltaX != 0 && horizontalEnabled) { |
| 150 num x = horizontalEnabled ? e.wheelDeltaX : 0; | 164 num x = horizontalEnabled ? e.wheelDeltaX : 0; |
| 151 num y = verticalEnabled ? e.wheelDeltaY : 0; | 165 num y = verticalEnabled ? e.wheelDeltaY : 0; |
| 152 if (throwDelta(x, y, FAST_SNAP_DECELERATION_FACTOR)) { | 166 throwDelta(x, y, FAST_SNAP_DECELERATION_FACTOR); |
| 153 e.preventDefault(); | 167 e.preventDefault(); |
| 154 } | |
| 155 } | 168 } |
| 156 }); | 169 }); |
| 157 | 170 |
| 158 _frame.on.keyDown.add((KeyboardEvent e) { | 171 _frame.on.keyDown.add((KeyboardEvent e) { |
| 159 bool handled = false; | 172 bool handled = false; |
| 160 // We ignore key events where further scrolling in that direction | 173 // We ignore key events where further scrolling in that direction |
| 161 // would have no impact which matches default browser behavior with | 174 // would have no impact which matches default browser behavior with |
| 162 // nested scrollable areas. | 175 // nested scrollable areas. |
| 163 | 176 |
| 164 switch(e.keyCode) { | 177 switch(e.keyCode) { |
| 165 case 33: // page-up | 178 case 33: // page-up |
| 166 handled = throwDelta( | 179 throwDelta( |
| 167 0, | 180 0, |
| 168 _scrollSize.height * PAGE_KEY_SCROLL_FRACTION); | 181 _scrollSize.height * PAGE_KEY_SCROLL_FRACTION); |
| 182 handled = true; |
| 169 break; | 183 break; |
| 170 case 34: // page-down | 184 case 34: // page-down |
| 171 handled = throwDelta( | 185 throwDelta( |
| 172 0, -_scrollSize.height * PAGE_KEY_SCROLL_FRACTION); | 186 0, -_scrollSize.height * PAGE_KEY_SCROLL_FRACTION); |
| 187 handled = true; |
| 173 break; | 188 break; |
| 174 case 35: // End | 189 case 35: // End |
| 175 handled = throwTo(_maxPoint.x, _minPoint.y, | 190 throwTo(_maxPoint.x, _minPoint.y, |
| 176 FAST_SNAP_DECELERATION_FACTOR); | 191 FAST_SNAP_DECELERATION_FACTOR); |
| 192 handled = true; |
| 177 break; | 193 break; |
| 178 case 36: // Home | 194 case 36: // Home |
| 179 handled = throwTo(_maxPoint.x,_maxPoint.y, | 195 throwTo(_maxPoint.x,_maxPoint.y, |
| 180 FAST_SNAP_DECELERATION_FACTOR); | 196 FAST_SNAP_DECELERATION_FACTOR); |
| 197 handled = true; |
| 181 break; | 198 break; |
| 182 /* TODO(jacobr): enable arrow keys when the don't conflict with other | 199 /* TODO(jacobr): enable arrow keys when the don't conflict with other |
| 183 application keyboard shortcuts. | 200 application keyboard shortcuts. |
| 184 case 38: // up | 201 case 38: // up |
| 185 handled = throwDelta( | 202 handled = throwDelta( |
| 186 0, | 203 0, |
| 187 ARROW_KEY_DELTA, | 204 ARROW_KEY_DELTA, |
| 188 FAST_SNAP_DECELERATION_FACTOR); | 205 FAST_SNAP_DECELERATION_FACTOR); |
| 189 break; | 206 break; |
| 190 case 40: // down | 207 case 40: // down |
| (...skipping 12 matching lines...) Expand all Loading... |
| 203 0, | 220 0, |
| 204 FAST_SNAP_DECELERATION_FACTOR); | 221 FAST_SNAP_DECELERATION_FACTOR); |
| 205 break; | 222 break; |
| 206 */ | 223 */ |
| 207 } | 224 } |
| 208 if (handled) { | 225 if (handled) { |
| 209 e.preventDefault(); | 226 e.preventDefault(); |
| 210 } | 227 } |
| 211 }); | 228 }); |
| 212 // The scrollable element must be relatively positioned. | 229 // The scrollable element must be relatively positioned. |
| 213 assert(_scrollTechnique != ScrollerScrollTechnique.RELATIVE_POSITIONING || | 230 // TODO(jacobr): this assert fires asynchronously which could be confusing. |
| 214 window.getComputedStyle(_element, null).position != "static"); | 231 if (_scrollTechnique == ScrollerScrollTechnique.RELATIVE_POSITIONING) { |
| 232 _element.computedStyle.then((CSSStyleDeclaration style) { |
| 233 assert(style.position != "static"); |
| 234 }); |
| 235 } |
| 236 |
| 215 _initLayer(); | 237 _initLayer(); |
| 216 } | 238 } |
| 217 | 239 |
| 218 EventListenerList get onScrollerStart() { | 240 EventListenerList get onScrollerStart() { |
| 219 if (_onScrollerStart === null) { | 241 if (_onScrollerStart === null) { |
| 220 _onScrollerStart = new SimpleEventListenerList(); | 242 _onScrollerStart = new SimpleEventListenerList(); |
| 221 } | 243 } |
| 222 return _onScrollerStart; | 244 return _onScrollerStart; |
| 223 } | 245 } |
| 224 | 246 |
| (...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 291 } | 313 } |
| 292 return end; | 314 return end; |
| 293 } | 315 } |
| 294 | 316 |
| 295 Coordinate get contentOffset() => _contentOffset; | 317 Coordinate get contentOffset() => _contentOffset; |
| 296 | 318 |
| 297 /** | 319 /** |
| 298 * Animate the position of the scroller to the specified [x], [y] coordinates | 320 * Animate the position of the scroller to the specified [x], [y] coordinates |
| 299 * by applying the throw gesture with the correct velocity to end at that | 321 * by applying the throw gesture with the correct velocity to end at that |
| 300 * location. | 322 * location. |
| 301 * Return false if no delta needs to be applied. | |
| 302 */ | 323 */ |
| 303 bool throwTo(num x, num y, | 324 void throwTo(num x, num y, [num decelerationFactor = null]) { |
| 304 [num decelerationFactor = null]) { | 325 reconfigure(() { |
| 305 reconfigure(); | 326 final snappedTarget = _snapToBounds(x, y); |
| 306 final snappedTarget = _snapToBounds(x, y); | 327 // If a deceleration factor is not specified, use the existing |
| 307 // If a deceleration factor is not specified, use the existing | 328 // deceleration factor specified by the momentum simulator. |
| 308 // deceleration factor specified by the momentum simulator. | 329 if (decelerationFactor == null) { |
| 309 if (decelerationFactor == null) { | 330 decelerationFactor = _momentum.decelerationFactor; |
| 310 decelerationFactor = _momentum.decelerationFactor; | 331 } |
| 311 } | |
| 312 | 332 |
| 313 if (snappedTarget != currentTarget) { | 333 if (snappedTarget != currentTarget) { |
| 314 _momentum.abort(); | 334 _momentum.abort(); |
| 315 reconfigure(); | |
| 316 | 335 |
| 317 _startDeceleration( | 336 _startDeceleration( |
| 318 _momentum.calculateVelocity( | 337 _momentum.calculateVelocity( |
| 319 _contentOffset, | 338 _contentOffset, |
| 320 snappedTarget, | 339 snappedTarget, |
| 321 decelerationFactor), | 340 decelerationFactor), |
| 322 decelerationFactor); | 341 decelerationFactor); |
| 323 onDecelStart.dispatch(new Event(ScrollerEventType.DECEL_START)); | 342 onDecelStart.dispatch(new Event(ScrollerEventType.DECEL_START)); |
| 324 return true; | 343 } |
| 325 } else { | 344 }); |
| 326 return false; | |
| 327 } | |
| 328 } | 345 } |
| 329 | 346 |
| 330 bool throwDelta(num deltaX, num deltaY, [num decelerationFactor = null]) { | 347 void throwDelta(num deltaX, num deltaY, [num decelerationFactor = null]) { |
| 331 Coordinate start = _contentOffset; | 348 Coordinate start = _contentOffset; |
| 332 Coordinate end = currentTarget; | 349 Coordinate end = currentTarget; |
| 333 int x = end.x.toInt(); | 350 int x = end.x.toInt(); |
| 334 int y = end.y.toInt(); | 351 int y = end.y.toInt(); |
| 335 // If we are throwing in the opposite direction of the existing momentum, | 352 // If we are throwing in the opposite direction of the existing momentum, |
| 336 // cancel the current momentum. | 353 // cancel the current momentum. |
| 337 if (deltaX != 0 && deltaX.isNegative() != (end.x - start.x).isNegative()) { | 354 if (deltaX != 0 && deltaX.isNegative() != (end.x - start.x).isNegative()) { |
| 338 x = start.x; | 355 x = start.x; |
| 339 } | 356 } |
| 340 if (deltaY != 0 && deltaY.isNegative() != (end.y - start.y).isNegative()) { | 357 if (deltaY != 0 && deltaY.isNegative() != (end.y - start.y).isNegative()) { |
| 341 y = start.y; | 358 y = start.y; |
| 342 } | 359 } |
| 343 x += deltaX.toInt(); | 360 x += deltaX.toInt(); |
| 344 y += deltaY.toInt(); | 361 y += deltaY.toInt(); |
| 345 return throwTo(x, y, decelerationFactor); | 362 throwTo(x, y, decelerationFactor); |
| 346 } | |
| 347 | |
| 348 void animateTo(num x, num y, [num duration = null, | |
| 349 String timingFunction = null]) { | |
| 350 if (duration !== null && duration != 0 | |
| 351 && _scrollTechnique == ScrollerScrollTechnique.TRANSFORM_3D) { | |
| 352 _setWebkitTransition(_element, duration, StyleUtil.TRANSFORM_STYLE, | |
| 353 timingFunction); | |
| 354 } | |
| 355 _setContentOffset(x, y); | |
| 356 } | 363 } |
| 357 | 364 |
| 358 void setPosition(num x, num y) { | 365 void setPosition(num x, num y) { |
| 359 _momentum.abort(); | 366 _momentum.abort(); |
| 360 _contentOffset.x = x; | 367 _contentOffset.x = x; |
| 361 _contentOffset.y = y; | 368 _contentOffset.y = y; |
| 362 _snapContentOffsetToBounds(); | 369 _snapContentOffsetToBounds(); |
| 363 _setContentOffset(_contentOffset.x, _contentOffset.y); | 370 _setContentOffset(_contentOffset.x, _contentOffset.y); |
| 364 } | 371 } |
| 365 /** | 372 /** |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 411 * Initialize the dom elements necessary for the scrolling to work. | 418 * Initialize the dom elements necessary for the scrolling to work. |
| 412 */ | 419 */ |
| 413 void _initLayer() { | 420 void _initLayer() { |
| 414 // The scrollable node provided to Scroller must be a direct child | 421 // The scrollable node provided to Scroller must be a direct child |
| 415 // of the scrollable frame. | 422 // of the scrollable frame. |
| 416 // TODO(jacobr): Figure out why this is failing on dartium. | 423 // TODO(jacobr): Figure out why this is failing on dartium. |
| 417 // assert(_element.parent == _frame); | 424 // assert(_element.parent == _frame); |
| 418 _setContentOffset(_maxPoint.x, _maxPoint.y); | 425 _setContentOffset(_maxPoint.x, _maxPoint.y); |
| 419 } | 426 } |
| 420 | 427 |
| 421 void onDecelerate(num x, num y, [num duration = 0, | 428 void onDecelerate(num x, num y) { |
| 422 String timingFunction = null]) { | 429 _setContentOffset(x, y); |
| 423 // TODO(jacobr): remove once dartc fixes default args. | |
| 424 if (duration === null) { | |
| 425 duration = 0; | |
| 426 } | |
| 427 animateTo(x, y, duration, timingFunction); | |
| 428 } | 430 } |
| 429 | 431 |
| 430 void onDecelerationEnd() { | 432 void onDecelerationEnd() { |
| 431 _setWebkitTransition(_element, 0); | |
| 432 onScrollerEnd.dispatch(new Event(ScrollerEventType.SCROLLER_END)); | 433 onScrollerEnd.dispatch(new Event(ScrollerEventType.SCROLLER_END)); |
| 433 _started = false; | 434 _started = false; |
| 434 } | 435 } |
| 435 | 436 |
| 436 void onDragEnd() { | 437 void onDragEnd() { |
| 437 _dragInProgress = false; | 438 _dragInProgress = false; |
| 438 | 439 |
| 439 bool decelerating = false; | 440 bool decelerating = false; |
| 440 if (_activeGesture) { | 441 if (_activeGesture) { |
| 441 if (_momentumEnabled && !_activeTransition) { | 442 if (_momentumEnabled) { |
| 442 decelerating = _startDeceleration(_touchHandler.getEndVelocity()); | 443 decelerating = _startDeceleration(_touchHandler.getEndVelocity()); |
| 443 } | 444 } |
| 444 } | 445 } |
| 445 | 446 |
| 446 onScrollerDragEnd.dispatch(new Event(ScrollerEventType.DRAG_END)); | 447 onScrollerDragEnd.dispatch(new Event(ScrollerEventType.DRAG_END)); |
| 447 | 448 |
| 448 if (!decelerating) { | 449 if (!decelerating) { |
| 449 _snapContentOffsetToBounds(); | 450 _snapContentOffsetToBounds(); |
| 450 onScrollerEnd.dispatch(new Event(ScrollerEventType.SCROLLER_END)); | 451 onScrollerEnd.dispatch(new Event(ScrollerEventType.SCROLLER_END)); |
| 451 _started = false; | 452 _started = false; |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 490 return !!(shouldVertical || shouldHorizontal && !verticalish); | 491 return !!(shouldVertical || shouldHorizontal && !verticalish); |
| 491 } | 492 } |
| 492 | 493 |
| 493 void onTouchEnd() { | 494 void onTouchEnd() { |
| 494 } | 495 } |
| 495 | 496 |
| 496 /** | 497 /** |
| 497 * Prepare the scrollable area for possible movement. | 498 * Prepare the scrollable area for possible movement. |
| 498 */ | 499 */ |
| 499 bool onTouchStart(TouchEvent e) { | 500 bool onTouchStart(TouchEvent e) { |
| 500 reconfigure(); | 501 reconfigure(() { |
| 501 final touch = e.touches[0]; | 502 final touch = e.touches[0]; |
| 502 if (_momentum.decelerating) { | 503 if (_momentum.decelerating) { |
| 503 e.preventDefault(); | 504 e.preventDefault(); |
| 504 e.stopPropagation(); | 505 e.stopPropagation(); |
| 505 stop(); | 506 stop(); |
| 506 } else { | 507 } |
| 507 _setWebkitTransition(_element, 0); | 508 _contentStartOffset = _contentOffset.clone(); |
| 508 } | 509 _snapContentOffsetToBounds(); |
| 509 _contentStartOffset = _contentOffset.clone(); | 510 }); |
| 510 _snapContentOffsetToBounds(); | |
| 511 return true; | 511 return true; |
| 512 } | 512 } |
| 513 | 513 |
| 514 /** | 514 /** |
| 515 * Transition end event handler. | |
| 516 */ | |
| 517 void onTransitionEnd(Event e) { | |
| 518 if (e.target == _element) { | |
| 519 _activeTransition = false; | |
| 520 _momentum.onTransitionEnd(); | |
| 521 } | |
| 522 } | |
| 523 | |
| 524 /** | |
| 525 * Recalculate dimensions of the frame and the content. Adjust the minPoint | 515 * Recalculate dimensions of the frame and the content. Adjust the minPoint |
| 526 * and maxPoint allowed for scrolling and scroll to a valid position. Call | 516 * and maxPoint allowed for scrolling and scroll to a valid position. Call |
| 527 * this method if you know the frame or content has been updated. Called | 517 * this method if you know the frame or content has been updated. Called |
| 528 * internally on every touchstart event the frame receives. | 518 * internally on every touchstart event the frame receives. |
| 529 */ | 519 */ |
| 530 void reconfigure() { | 520 void reconfigure(Callback callback) { |
| 531 _resize(); | 521 _resize(() { |
| 532 _snapContentOffsetToBounds(); | 522 _snapContentOffsetToBounds(); |
| 523 callback(); |
| 524 }); |
| 533 } | 525 } |
| 534 | 526 |
| 535 void reset() { | 527 void reset() { |
| 536 stop(); | 528 stop(); |
| 537 _touchHandler.reset(); | 529 _touchHandler.reset(); |
| 538 _setWebkitTransition(_element, 0); | 530 _maxOffset.x = 0; |
| 539 setMinOffset(0, 0); | 531 _maxOffset.y = 0; |
| 540 setMaxOffset(0, 0); | 532 _minOffset.x = 0; |
| 541 reconfigure(); | 533 _minOffset.y = 0; |
| 542 _setContentOffset(_maxPoint.x, _maxPoint.y); | 534 reconfigure(() => _setContentOffset(_maxPoint.x, _maxPoint.y)); |
| 543 } | 535 } |
| 544 | 536 |
| 545 /** | 537 /** |
| 546 * Recalculate dimensions of the frame and the content. Adjust the minPoint | 538 * Recalculate dimensions of the frame and the content. Adjust the minPoint |
| 547 * and maxPoint allowed for scrolling. | 539 * and maxPoint allowed for scrolling. |
| 548 */ | 540 */ |
| 549 void _resize() { | 541 void _resize(Callback callback) { |
| 550 _scrollSize = new Size(_frame.offsetWidth, _frame.offsetHeight); | 542 final frameRect = _frame.rect; |
| 551 _contentSize = _lookupContentSizeDelegate !== null ? | 543 Future contentSizeFuture; |
| 552 _lookupContentSizeDelegate() : | 544 |
| 553 new Size(_element.scrollWidth, _element.scrollHeight); | 545 if (_lookupContentSizeDelegate !== null) { |
| 554 Size adjusted = _getAdjustedContentSize(); | 546 contentSizeFuture = _lookupContentSizeDelegate(); |
| 555 _maxPoint = new Coordinate(-_maxOffset.x, -_maxOffset.y); | 547 contentSizeFuture.then((Size size) { |
| 556 _minPoint = new Coordinate( | 548 _contentSize = size; |
| 557 Math.min( | 549 }); |
| 558 _scrollSize.width - adjusted.width + _minOffset.x, _maxPoint.x), | 550 } else { |
| 559 Math.min( | 551 contentSizeFuture = _element.rect; |
| 560 _scrollSize.height - adjusted.height + _minOffset.y, _maxPoint.y)); | 552 contentSizeFuture.then((ElementRect rect) { |
| 553 _contentSize = new Size(rect.scroll.width, rect.scroll.height); |
| 554 }); |
| 555 } |
| 556 |
| 557 joinFutures(<Future>[frameRect, contentSizeFuture], () { |
| 558 _scrollSize = new Size(frameRect.value.offset.width, |
| 559 frameRect.value.offset.height); |
| 560 Size adjusted = _getAdjustedContentSize(); |
| 561 _maxPoint = new Coordinate(-_maxOffset.x, -_maxOffset.y); |
| 562 _minPoint = new Coordinate( |
| 563 Math.min( |
| 564 _scrollSize.width - adjusted.width + _minOffset.x, _maxPoint.x), |
| 565 Math.min( |
| 566 _scrollSize.height - adjusted.height + _minOffset.y, _maxPoint.y))
; |
| 567 callback(); |
| 568 }); |
| 561 } | 569 } |
| 562 | 570 |
| 563 Coordinate _snapToBounds(num x, num y) { | 571 Coordinate _snapToBounds(num x, num y) { |
| 564 num clampX = GoogleMath.clamp(_minPoint.x, x, _maxPoint.x); | 572 num clampX = GoogleMath.clamp(_minPoint.x, x, _maxPoint.x); |
| 565 num clampY = GoogleMath.clamp(_minPoint.y, y, _maxPoint.y); | 573 num clampY = GoogleMath.clamp(_minPoint.y, y, _maxPoint.y); |
| 566 return new Coordinate(clampX, clampY); | 574 return new Coordinate(clampX, clampY); |
| 567 } | 575 } |
| 568 | 576 |
| 569 /** | 577 /** |
| 570 * Translate the content to a new position specified in px. | 578 * Translate the content to a new position specified in px. |
| 571 */ | 579 */ |
| 572 void _setContentOffset(num x, num y) { | 580 void _setContentOffset(num x, num y) { |
| 573 _contentOffset.x = x; | 581 _contentOffset.x = x; |
| 574 _contentOffset.y = y; | 582 _contentOffset.y = y; |
| 575 _setOffsetFunction(_element, x, y); | 583 _setOffsetFunction(_element, x, y); |
| 576 onContentMoved.dispatch(new Event(ScrollerEventType.CONTENT_MOVED)); | 584 onContentMoved.dispatch(new Event(ScrollerEventType.CONTENT_MOVED)); |
| 577 } | 585 } |
| 578 | 586 |
| 579 /** | 587 /** |
| 580 * Sets the offset to subtract from the maximum coordinate that the left upper | |
| 581 * corner of the content can scroll to. | |
| 582 */ | |
| 583 void setMaxOffset(num x, num y) { | |
| 584 _maxOffset.x = x; | |
| 585 _maxOffset.y = y; | |
| 586 _resize(); | |
| 587 } | |
| 588 | |
| 589 /** | |
| 590 * Sets the offset to add to the minimum coordinate that the left upper corner | |
| 591 * of the content can scroll to. | |
| 592 */ | |
| 593 void setMinOffset(num x, num y) { | |
| 594 _minOffset.x = x; | |
| 595 _minOffset.y = y; | |
| 596 _resize(); | |
| 597 } | |
| 598 | |
| 599 /** | |
| 600 * Enable or disable momentum. | 588 * Enable or disable momentum. |
| 601 */ | 589 */ |
| 602 void setMomentum(bool enable) { | 590 void setMomentum(bool enable) { |
| 603 _momentumEnabled = enable; | 591 _momentumEnabled = enable; |
| 604 } | 592 } |
| 605 | 593 |
| 606 /** | 594 /** |
| 607 * Update the scroll technique used for animating the scrollable area. | |
| 608 */ | |
| 609 void setScrollTechnique(int technique) { | |
| 610 _scrollTechnique = technique; | |
| 611 _setOffsetFunction = _getOffsetFunction(technique); | |
| 612 | |
| 613 // The scrollable element must be relatively positioned. | |
| 614 assert(technique != ScrollerScrollTechnique.RELATIVE_POSITIONING || | |
| 615 window.getComputedStyle(_element, null).position != "static"); | |
| 616 | |
| 617 if (technique != ScrollerScrollTechnique.TRANSFORM_3D) { | |
| 618 FxUtil.clearWebkitTransition(_element); | |
| 619 FxUtil.clearWebkitTransform(_element); | |
| 620 } | |
| 621 if (technique != ScrollerScrollTechnique.RELATIVE_POSITIONING) { | |
| 622 FxUtil.setLeftAndTop(_element, 0, 0); | |
| 623 } | |
| 624 _setOffsetFunction(_element, _contentOffset.x, _contentOffset.y); | |
| 625 } | |
| 626 | |
| 627 /** | |
| 628 * Sets the vertical scrolled offset of the element where [y] is the amount | 595 * Sets the vertical scrolled offset of the element where [y] is the amount |
| 629 * of vertical space to be scrolled, in pixels. | 596 * of vertical space to be scrolled, in pixels. |
| 630 */ | 597 */ |
| 631 void setVerticalOffset(num y) { | 598 void setVerticalOffset(num y) { |
| 632 _setContentOffset(_contentOffset.x, y); | 599 _setContentOffset(_contentOffset.x, y); |
| 633 } | 600 } |
| 634 | 601 |
| 635 /** | 602 /** |
| 636 * Applies a webkit transition on the element where the [duration] of the | |
| 637 * animation is specified in ms. | |
| 638 */ | |
| 639 void _setWebkitTransition(Element el, num duration, [String property = null, | |
| 640 String timingFunction = null]) { | |
| 641 _activeTransition = duration > 0; | |
| 642 FxUtil.setWebkitTransition(el, duration, property, timingFunction); | |
| 643 } | |
| 644 | |
| 645 /** | |
| 646 * Whether the scrollable area should scroll horizontally. Only | 603 * Whether the scrollable area should scroll horizontally. Only |
| 647 * returns true if the client has enabled horizontal scrolling, and the | 604 * returns true if the client has enabled horizontal scrolling, and the |
| 648 * content is wider than the frame. | 605 * content is wider than the frame. |
| 649 */ | 606 */ |
| 650 bool _shouldScrollHorizontally() { | 607 bool _shouldScrollHorizontally() { |
| 651 return horizontalEnabled && _scrollSize.width < _contentSize.width; | 608 return horizontalEnabled && _scrollSize.width < _contentSize.width; |
| 652 } | 609 } |
| 653 | 610 |
| 654 /** | 611 /** |
| 655 * Whether the scrollable area should scroll vertically. Only | 612 * Whether the scrollable area should scroll vertically. Only |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 689 if (!_shouldScrollVertically()) { | 646 if (!_shouldScrollVertically()) { |
| 690 velocity.y = 0; | 647 velocity.y = 0; |
| 691 } | 648 } |
| 692 assert(_minPoint != null); // Min point is not set | 649 assert(_minPoint != null); // Min point is not set |
| 693 assert(_maxPoint != null); // Max point is not set | 650 assert(_maxPoint != null); // Max point is not set |
| 694 return _momentum.start(velocity, _minPoint, _maxPoint, _contentOffset, | 651 return _momentum.start(velocity, _minPoint, _maxPoint, _contentOffset, |
| 695 decelerationFactor); | 652 decelerationFactor); |
| 696 } | 653 } |
| 697 | 654 |
| 698 Coordinate stop() { | 655 Coordinate stop() { |
| 699 Coordinate velocity = _momentum.stop(); | 656 return _momentum.stop(); |
| 700 | |
| 701 if (_momentum.decelerating) { | |
| 702 CSSMatrix transform1 = StyleUtil.getCurrentTransformMatrix( | |
| 703 _element); | |
| 704 if (!_activeTransition) { | |
| 705 _stopDecelerating(transform1.m41, transform1.m42); | |
| 706 return velocity; | |
| 707 } | |
| 708 _contentOffset.x = transform1.m41; | |
| 709 _contentOffset.y = transform1.m42; | |
| 710 _isStopping = true; | |
| 711 window.setTimeout(() { | |
| 712 CSSMatrix transform2 = StyleUtil.getCurrentTransformMatrix( | |
| 713 _element); | |
| 714 _setWebkitTransition(_element, 0); | |
| 715 window.setTimeout(function() { _isStopping = false; }, 0); | |
| 716 num deltaX = transform2.m41 - transform1.m41; | |
| 717 num deltaY = transform2.m42 - transform1.m42; | |
| 718 num newX = transform2.m41 + 2 * deltaX; | |
| 719 num newY = transform2.m42 + 2 * deltaY; | |
| 720 newX = GoogleMath.clamp(newX, _minPoint.x, _maxPoint.x); | |
| 721 newY = GoogleMath.clamp(newY, _minPoint.y, _maxPoint.y); | |
| 722 _stopDecelerating(newX, newY); | |
| 723 }, 0); | |
| 724 } | |
| 725 return velocity; | |
| 726 } | 657 } |
| 727 | 658 |
| 728 /** | 659 /** |
| 729 * Stop the deceleration of the scrollable content given a new position in px. | 660 * Stop the deceleration of the scrollable content given a new position in px. |
| 730 */ | 661 */ |
| 731 void _stopDecelerating(num x, num y) { | 662 void _stopDecelerating(num x, num y) { |
| 732 _momentum.stop(); | 663 _momentum.stop(); |
| 733 _setContentOffset(x, y); | 664 _setContentOffset(x, y); |
| 734 } | 665 } |
| 735 | 666 |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 789 for (EventListener listener in _listeners) { | 720 for (EventListener listener in _listeners) { |
| 790 listener(evt); | 721 listener(evt); |
| 791 } | 722 } |
| 792 } | 723 } |
| 793 } | 724 } |
| 794 | 725 |
| 795 class ScrollerScrollTechnique { | 726 class ScrollerScrollTechnique { |
| 796 static final TRANSFORM_3D = 1; | 727 static final TRANSFORM_3D = 1; |
| 797 static final RELATIVE_POSITIONING = 2; | 728 static final RELATIVE_POSITIONING = 2; |
| 798 } | 729 } |
| OLD | NEW |