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 part of touch; | 5 part of touch; |
6 | 6 |
7 /** | 7 /** |
8 * Implementation of a custom scrolling behavior. | 8 * Implementation of a custom scrolling behavior. |
9 * This behavior overrides native scrolling for an area. This area can be a | 9 * This behavior overrides native scrolling for an area. This area can be a |
10 * single defined part of a page, the entire page, or several different parts | 10 * single defined part of a page, the entire page, or several different parts |
(...skipping 26 matching lines...) Expand all Loading... |
37 // Helper method to await the completion of 2 futures. | 37 // Helper method to await the completion of 2 futures. |
38 void joinFutures(List<Future> futures, Callback callback) { | 38 void joinFutures(List<Future> futures, Callback callback) { |
39 int count = 0; | 39 int count = 0; |
40 int len = futures.length; | 40 int len = futures.length; |
41 void helper(value) { | 41 void helper(value) { |
42 count++; | 42 count++; |
43 if (count == len) { | 43 if (count == len) { |
44 callback(); | 44 callback(); |
45 } | 45 } |
46 } | 46 } |
| 47 |
47 for (Future p in futures) { | 48 for (Future p in futures) { |
48 p.then(helper); | 49 p.then(helper); |
49 } | 50 } |
50 } | 51 } |
51 | 52 |
52 class Scroller implements Draggable, MomentumDelegate { | 53 class Scroller implements Draggable, MomentumDelegate { |
53 | |
54 /** Pixels to move each time an arrow key is pressed. */ | 54 /** Pixels to move each time an arrow key is pressed. */ |
55 static const ARROW_KEY_DELTA = 30; | 55 static const ARROW_KEY_DELTA = 30; |
56 static const SCROLL_WHEEL_VELOCITY = 0.01; | 56 static const SCROLL_WHEEL_VELOCITY = 0.01; |
57 static const FAST_SNAP_DECELERATION_FACTOR = 0.84; | 57 static const FAST_SNAP_DECELERATION_FACTOR = 0.84; |
58 static const PAGE_KEY_SCROLL_FRACTION = .85; | 58 static const PAGE_KEY_SCROLL_FRACTION = .85; |
59 | 59 |
60 // TODO(jacobr): remove this static variable. | 60 // TODO(jacobr): remove this static variable. |
61 static bool _dragInProgress = false; | 61 static bool _dragInProgress = false; |
62 | 62 |
63 /** The node that will actually scroll. */ | 63 /** The node that will actually scroll. */ |
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
134 | 134 |
135 Size _scrollSize; | 135 Size _scrollSize; |
136 Size _contentSize; | 136 Size _contentSize; |
137 Coordinate _minPoint; | 137 Coordinate _minPoint; |
138 bool _isStopping = false; | 138 bool _isStopping = false; |
139 Coordinate _contentStartOffset; | 139 Coordinate _contentStartOffset; |
140 bool _started = false; | 140 bool _started = false; |
141 bool _activeGesture = false; | 141 bool _activeGesture = false; |
142 ScrollWatcher _scrollWatcher; | 142 ScrollWatcher _scrollWatcher; |
143 | 143 |
144 Scroller(Element scrollableElem, [this.verticalEnabled = false, | 144 Scroller(Element scrollableElem, |
145 this.horizontalEnabled = false, | 145 [this.verticalEnabled = false, |
146 momentumEnabled = true, | 146 this.horizontalEnabled = false, |
147 lookupContentSizeDelegate = null, | 147 momentumEnabled = true, |
148 num defaultDecelerationFactor = 1, | 148 lookupContentSizeDelegate = null, |
149 int scrollTechnique = null, bool capture = false]) | 149 num defaultDecelerationFactor = 1, |
| 150 int scrollTechnique = null, |
| 151 bool capture = false]) |
150 : _momentumEnabled = momentumEnabled, | 152 : _momentumEnabled = momentumEnabled, |
151 _lookupContentSizeDelegate = lookupContentSizeDelegate, | 153 _lookupContentSizeDelegate = lookupContentSizeDelegate, |
152 _element = scrollableElem, | 154 _element = scrollableElem, |
153 _frame = scrollableElem.parent, | 155 _frame = scrollableElem.parent, |
154 _scrollTechnique = scrollTechnique != null | 156 _scrollTechnique = scrollTechnique != null |
155 ? scrollTechnique : ScrollerScrollTechnique.TRANSFORM_3D, | 157 ? scrollTechnique |
| 158 : ScrollerScrollTechnique.TRANSFORM_3D, |
156 _minPoint = new Coordinate(0, 0), | 159 _minPoint = new Coordinate(0, 0), |
157 _maxPoint = new Coordinate(0, 0), | 160 _maxPoint = new Coordinate(0, 0), |
158 _maxOffset = new Coordinate(0, 0), | 161 _maxOffset = new Coordinate(0, 0), |
159 _minOffset = new Coordinate(0, 0), | 162 _minOffset = new Coordinate(0, 0), |
160 _contentOffset = new Coordinate(0, 0) { | 163 _contentOffset = new Coordinate(0, 0) { |
161 _touchHandler = new TouchHandler(this, scrollableElem.parent); | 164 _touchHandler = new TouchHandler(this, scrollableElem.parent); |
162 _momentum = new Momentum(this, defaultDecelerationFactor); | 165 _momentum = new Momentum(this, defaultDecelerationFactor); |
163 | 166 |
164 Element parentElem = scrollableElem.parent; | 167 Element parentElem = scrollableElem.parent; |
165 assert(parentElem != null); | 168 assert(parentElem != null); |
166 _setOffsetFunction = _getOffsetFunction(_scrollTechnique); | 169 _setOffsetFunction = _getOffsetFunction(_scrollTechnique); |
167 _touchHandler.setDraggable(this); | 170 _touchHandler.setDraggable(this); |
168 _touchHandler.enable(capture); | 171 _touchHandler.enable(capture); |
169 | 172 |
170 _frame.onMouseWheel.listen((e) { | 173 _frame.onMouseWheel.listen((e) { |
171 if (e.deltaY != 0 && verticalEnabled || | 174 if (e.deltaY != 0 && verticalEnabled || |
172 e.deltaX != 0 && horizontalEnabled) { | 175 e.deltaX != 0 && horizontalEnabled) { |
173 num x = horizontalEnabled ? e.deltaX : 0; | 176 num x = horizontalEnabled ? e.deltaX : 0; |
174 num y = verticalEnabled ? e.deltaY : 0; | 177 num y = verticalEnabled ? e.deltaY : 0; |
175 throwDelta(x, y, FAST_SNAP_DECELERATION_FACTOR); | 178 throwDelta(x, y, FAST_SNAP_DECELERATION_FACTOR); |
176 e.preventDefault(); | 179 e.preventDefault(); |
177 } | 180 } |
178 }); | 181 }); |
179 | 182 |
180 _frame.onKeyDown.listen((KeyboardEvent e) { | 183 _frame.onKeyDown.listen((KeyboardEvent e) { |
181 bool handled = false; | 184 bool handled = false; |
182 // We ignore key events where further scrolling in that direction | 185 // We ignore key events where further scrolling in that direction |
183 // would have no impact which matches default browser behavior with | 186 // would have no impact which matches default browser behavior with |
184 // nested scrollable areas. | 187 // nested scrollable areas. |
185 | 188 |
186 switch(e.keyCode) { | 189 switch (e.keyCode) { |
187 case 33: // page-up | 190 case 33: // page-up |
188 throwDelta( | 191 throwDelta(0, _scrollSize.height * PAGE_KEY_SCROLL_FRACTION); |
189 0, | 192 handled = true; |
190 _scrollSize.height * PAGE_KEY_SCROLL_FRACTION); | 193 break; |
191 handled = true; | 194 case 34: // page-down |
192 break; | 195 throwDelta(0, -_scrollSize.height * PAGE_KEY_SCROLL_FRACTION); |
193 case 34: // page-down | 196 handled = true; |
194 throwDelta( | 197 break; |
195 0, -_scrollSize.height * PAGE_KEY_SCROLL_FRACTION); | 198 case 35: // End |
196 handled = true; | 199 throwTo(_maxPoint.x, _minPoint.y, FAST_SNAP_DECELERATION_FACTOR); |
197 break; | 200 handled = true; |
198 case 35: // End | 201 break; |
199 throwTo(_maxPoint.x, _minPoint.y, | 202 case 36: // Home |
200 FAST_SNAP_DECELERATION_FACTOR); | 203 throwTo(_maxPoint.x, _maxPoint.y, FAST_SNAP_DECELERATION_FACTOR); |
201 handled = true; | 204 handled = true; |
202 break; | 205 break; |
203 case 36: // Home | |
204 throwTo(_maxPoint.x,_maxPoint.y, | |
205 FAST_SNAP_DECELERATION_FACTOR); | |
206 handled = true; | |
207 break; | |
208 /* TODO(jacobr): enable arrow keys when the don't conflict with other | 206 /* TODO(jacobr): enable arrow keys when the don't conflict with other |
209 application keyboard shortcuts. | 207 application keyboard shortcuts. |
210 case 38: // up | 208 case 38: // up |
211 handled = throwDelta( | 209 handled = throwDelta( |
212 0, | 210 0, |
213 ARROW_KEY_DELTA, | 211 ARROW_KEY_DELTA, |
214 FAST_SNAP_DECELERATION_FACTOR); | 212 FAST_SNAP_DECELERATION_FACTOR); |
215 break; | 213 break; |
216 case 40: // down | 214 case 40: // down |
217 handled = throwDelta( | 215 handled = throwDelta( |
218 0, -ARROW_KEY_DELTA, | 216 0, -ARROW_KEY_DELTA, |
219 FAST_SNAP_DECELERATION_FACTOR); | 217 FAST_SNAP_DECELERATION_FACTOR); |
220 break; | 218 break; |
221 case 37: // left | 219 case 37: // left |
222 handled = throwDelta( | 220 handled = throwDelta( |
223 ARROW_KEY_DELTA, 0, | 221 ARROW_KEY_DELTA, 0, |
224 FAST_SNAP_DECELERATION_FACTOR); | 222 FAST_SNAP_DECELERATION_FACTOR); |
225 break; | 223 break; |
226 case 39: // right | 224 case 39: // right |
227 handled = throwDelta( | 225 handled = throwDelta( |
228 -ARROW_KEY_DELTA, | 226 -ARROW_KEY_DELTA, |
229 0, | 227 0, |
230 FAST_SNAP_DECELERATION_FACTOR); | 228 FAST_SNAP_DECELERATION_FACTOR); |
231 break; | 229 break; |
232 */ | 230 */ |
233 } | 231 } |
234 if (handled) { | 232 if (handled) { |
235 e.preventDefault(); | 233 e.preventDefault(); |
236 } | 234 } |
237 }); | 235 }); |
238 // The scrollable element must be relatively positioned. | 236 // The scrollable element must be relatively positioned. |
239 // TODO(jacobr): this assert fires asynchronously which could be confusing. | 237 // TODO(jacobr): this assert fires asynchronously which could be confusing. |
240 if (_scrollTechnique == ScrollerScrollTechnique.RELATIVE_POSITIONING) { | 238 if (_scrollTechnique == ScrollerScrollTechnique.RELATIVE_POSITIONING) { |
241 assert(_element.getComputedStyle().position != "static"); | 239 assert(_element.getComputedStyle().position != "static"); |
242 } | 240 } |
243 | 241 |
244 _initLayer(); | 242 _initLayer(); |
245 } | 243 } |
246 | 244 |
247 Stream<Event> get onScrollerStart { | 245 Stream<Event> get onScrollerStart { |
(...skipping 29 matching lines...) Expand all Loading... |
277 } | 275 } |
278 | 276 |
279 Stream<Event> get onDecelStart { | 277 Stream<Event> get onDecelStart { |
280 if (_onDecelStart == null) { | 278 if (_onDecelStart == null) { |
281 _onDecelStart = new StreamController<Event>.broadcast(sync: true); | 279 _onDecelStart = new StreamController<Event>.broadcast(sync: true); |
282 _onDecelStartStream = _onDecelStart.stream; | 280 _onDecelStartStream = _onDecelStart.stream; |
283 } | 281 } |
284 return _onDecelStartStream; | 282 return _onDecelStartStream; |
285 } | 283 } |
286 | 284 |
287 | |
288 /** | 285 /** |
289 * Add a scroll listener. This allows other classes to subscribe to scroll | 286 * Add a scroll listener. This allows other classes to subscribe to scroll |
290 * notifications from this scroller. | 287 * notifications from this scroller. |
291 */ | 288 */ |
292 void addScrollListener(ScrollListener listener) { | 289 void addScrollListener(ScrollListener listener) { |
293 if (_scrollWatcher == null) { | 290 if (_scrollWatcher == null) { |
294 _scrollWatcher = new ScrollWatcher(this); | 291 _scrollWatcher = new ScrollWatcher(this); |
295 _scrollWatcher.initialize(); | 292 _scrollWatcher.initialize(); |
296 } | 293 } |
297 _scrollWatcher.addListener(listener); | 294 _scrollWatcher.addListener(listener); |
298 } | 295 } |
299 | 296 |
300 /** | 297 /** |
301 * Adjust the new calculated scroll position based on the minimum allowed | 298 * Adjust the new calculated scroll position based on the minimum allowed |
302 * position and returns the adjusted scroll value. | 299 * position and returns the adjusted scroll value. |
303 */ | 300 */ |
304 num _adjustValue(num newPosition, num minPosition, | 301 num _adjustValue(num newPosition, num minPosition, num maxPosition) { |
305 num maxPosition) { | |
306 assert(minPosition <= maxPosition); | 302 assert(minPosition <= maxPosition); |
307 | 303 |
308 if (newPosition < minPosition) { | 304 if (newPosition < minPosition) { |
309 newPosition -= (newPosition - minPosition) / 2; | 305 newPosition -= (newPosition - minPosition) / 2; |
310 } else { | 306 } else { |
311 if (newPosition > maxPosition) { | 307 if (newPosition > maxPosition) { |
312 newPosition -= (newPosition - maxPosition) / 2; | 308 newPosition -= (newPosition - maxPosition) / 2; |
313 } | 309 } |
314 } | 310 } |
315 return newPosition; | 311 return newPosition; |
(...skipping 24 matching lines...) Expand all Loading... |
340 // deceleration factor specified by the momentum simulator. | 336 // deceleration factor specified by the momentum simulator. |
341 if (decelerationFactor == null) { | 337 if (decelerationFactor == null) { |
342 decelerationFactor = _momentum.decelerationFactor; | 338 decelerationFactor = _momentum.decelerationFactor; |
343 } | 339 } |
344 | 340 |
345 if (snappedTarget != currentTarget) { | 341 if (snappedTarget != currentTarget) { |
346 _momentum.abort(); | 342 _momentum.abort(); |
347 | 343 |
348 _startDeceleration( | 344 _startDeceleration( |
349 _momentum.calculateVelocity( | 345 _momentum.calculateVelocity( |
350 _contentOffset, | 346 _contentOffset, snappedTarget, decelerationFactor), |
351 snappedTarget, | |
352 decelerationFactor), | |
353 decelerationFactor); | 347 decelerationFactor); |
354 if (_onDecelStart != null) { | 348 if (_onDecelStart != null) { |
355 _onDecelStart.add(new Event(ScrollerEventType.DECEL_START)); | 349 _onDecelStart.add(new Event(ScrollerEventType.DECEL_START)); |
356 } | 350 } |
357 } | 351 } |
358 }); | 352 }); |
359 } | 353 } |
360 | 354 |
361 void throwDelta(num deltaX, num deltaY, [num decelerationFactor = null]) { | 355 void throwDelta(num deltaX, num deltaY, [num decelerationFactor = null]) { |
362 Coordinate start = _contentOffset; | 356 Coordinate start = _contentOffset; |
(...skipping 13 matching lines...) Expand all Loading... |
376 throwTo(x, y, decelerationFactor); | 370 throwTo(x, y, decelerationFactor); |
377 } | 371 } |
378 | 372 |
379 void setPosition(num x, num y) { | 373 void setPosition(num x, num y) { |
380 _momentum.abort(); | 374 _momentum.abort(); |
381 _contentOffset.x = x; | 375 _contentOffset.x = x; |
382 _contentOffset.y = y; | 376 _contentOffset.y = y; |
383 _snapContentOffsetToBounds(); | 377 _snapContentOffsetToBounds(); |
384 _setContentOffset(_contentOffset.x, _contentOffset.y); | 378 _setContentOffset(_contentOffset.x, _contentOffset.y); |
385 } | 379 } |
| 380 |
386 /** | 381 /** |
387 * Adjusted content size is a size with the combined largest height and width | 382 * Adjusted content size is a size with the combined largest height and width |
388 * of both the content and the frame. | 383 * of both the content and the frame. |
389 */ | 384 */ |
390 Size _getAdjustedContentSize() { | 385 Size _getAdjustedContentSize() { |
391 return new Size(Math.max(_scrollSize.width, _contentSize.width), | 386 return new Size(Math.max(_scrollSize.width, _contentSize.width), |
392 Math.max(_scrollSize.height, _contentSize.height)); | 387 Math.max(_scrollSize.height, _contentSize.height)); |
393 } | 388 } |
394 | 389 |
395 // TODO(jmesserly): these should be properties instead of get* methods | 390 // TODO(jmesserly): these should be properties instead of get* methods |
396 num getDefaultVerticalOffset() => _maxPoint.y; | 391 num getDefaultVerticalOffset() => _maxPoint.y; |
397 Element getElement() => _element; | 392 Element getElement() => _element; |
398 Element getFrame() => _frame; | 393 Element getFrame() => _frame; |
399 num getHorizontalOffset() => _contentOffset.x; | 394 num getHorizontalOffset() => _contentOffset.x; |
400 | 395 |
401 /** | 396 /** |
402 * [x] Value to use as reference for percent measurement. If | 397 * [x] Value to use as reference for percent measurement. If |
403 * none is provided then the content's current x offset will be used. | 398 * none is provided then the content's current x offset will be used. |
404 * Returns the percent of the page scrolled horizontally. | 399 * Returns the percent of the page scrolled horizontally. |
405 */ | 400 */ |
406 num getHorizontalScrollPercent([num x = null]) { | 401 num getHorizontalScrollPercent([num x = null]) { |
407 x = x != null ? x : _contentOffset.x; | 402 x = x != null ? x : _contentOffset.x; |
408 return (x - _minPoint.x) / (_maxPoint.x - _minPoint.x); | 403 return (x - _minPoint.x) / (_maxPoint.x - _minPoint.x); |
409 } | 404 } |
410 | 405 |
411 num getMaxPointY()=> _maxPoint.y; | 406 num getMaxPointY() => _maxPoint.y; |
412 num getMinPointY() => _minPoint.y; | 407 num getMinPointY() => _minPoint.y; |
413 Momentum get momentum => _momentum; | 408 Momentum get momentum => _momentum; |
414 | 409 |
415 /** | 410 /** |
416 * Provide access to the touch handler that the scroller created to manage | 411 * Provide access to the touch handler that the scroller created to manage |
417 * touch events. | 412 * touch events. |
418 */ | 413 */ |
419 TouchHandler getTouchHandler() => _touchHandler; | 414 TouchHandler getTouchHandler() => _touchHandler; |
420 num getVerticalOffset() => _contentOffset.y; | 415 num getVerticalOffset() => _contentOffset.y; |
421 | 416 |
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
480 | 475 |
481 void onDragMove() { | 476 void onDragMove() { |
482 if (_isStopping || (!_activeGesture && _dragInProgress)) { | 477 if (_isStopping || (!_activeGesture && _dragInProgress)) { |
483 return; | 478 return; |
484 } | 479 } |
485 | 480 |
486 assert(_contentStartOffset != null); // Content start not set | 481 assert(_contentStartOffset != null); // Content start not set |
487 Coordinate contentStart = _contentStartOffset; | 482 Coordinate contentStart = _contentStartOffset; |
488 num newX = contentStart.x + _touchHandler.getDragDeltaX(); | 483 num newX = contentStart.x + _touchHandler.getDragDeltaX(); |
489 num newY = contentStart.y + _touchHandler.getDragDeltaY(); | 484 num newY = contentStart.y + _touchHandler.getDragDeltaY(); |
490 newY = _shouldScrollVertically() ? | 485 newY = _shouldScrollVertically() |
491 _adjustValue(newY, _minPoint.y, _maxPoint.y) : 0; | 486 ? _adjustValue(newY, _minPoint.y, _maxPoint.y) |
492 newX = _shouldScrollHorizontally() ? | 487 : 0; |
493 _adjustValue(newX, _minPoint.x, _maxPoint.x) : 0; | 488 newX = _shouldScrollHorizontally() |
| 489 ? _adjustValue(newX, _minPoint.x, _maxPoint.x) |
| 490 : 0; |
494 if (!_activeGesture) { | 491 if (!_activeGesture) { |
495 _activeGesture = true; | 492 _activeGesture = true; |
496 _dragInProgress = true; | 493 _dragInProgress = true; |
497 } | 494 } |
498 if (!_started) { | 495 if (!_started) { |
499 _started = true; | 496 _started = true; |
500 if (_onScrollerStart != null) { | 497 if (_onScrollerStart != null) { |
501 _onScrollerStart.add(new Event(ScrollerEventType.SCROLLER_START)); | 498 _onScrollerStart.add(new Event(ScrollerEventType.SCROLLER_START)); |
502 } | 499 } |
503 } | 500 } |
504 _setContentOffset(newX, newY); | 501 _setContentOffset(newX, newY); |
505 } | 502 } |
506 | 503 |
507 bool onDragStart(TouchEvent e) { | 504 bool onDragStart(TouchEvent e) { |
508 if (e.touches.length > 1) { | 505 if (e.touches.length > 1) { |
509 return false; | 506 return false; |
510 } | 507 } |
511 bool shouldHorizontal = _shouldScrollHorizontally(); | 508 bool shouldHorizontal = _shouldScrollHorizontally(); |
512 bool shouldVertical = _shouldScrollVertically(); | 509 bool shouldVertical = _shouldScrollVertically(); |
513 bool verticalish = _touchHandler.getDragDeltaY().abs() > | 510 bool verticalish = _touchHandler.getDragDeltaY().abs() > |
514 _touchHandler.getDragDeltaX().abs(); | 511 _touchHandler.getDragDeltaX().abs(); |
515 return !!(shouldVertical || shouldHorizontal && !verticalish); | 512 return !!(shouldVertical || shouldHorizontal && !verticalish); |
516 } | 513 } |
517 | 514 |
518 void onTouchEnd() { | 515 void onTouchEnd() {} |
519 } | |
520 | 516 |
521 /** | 517 /** |
522 * Prepare the scrollable area for possible movement. | 518 * Prepare the scrollable area for possible movement. |
523 */ | 519 */ |
524 bool onTouchStart(TouchEvent e) { | 520 bool onTouchStart(TouchEvent e) { |
525 reconfigure(() { | 521 reconfigure(() { |
526 final touch = e.touches[0]; | 522 final touch = e.touches[0]; |
527 if (_momentum.decelerating) { | 523 if (_momentum.decelerating) { |
528 e.preventDefault(); | 524 e.preventDefault(); |
529 e.stopPropagation(); | 525 e.stopPropagation(); |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
563 * and maxPoint allowed for scrolling. | 559 * and maxPoint allowed for scrolling. |
564 */ | 560 */ |
565 void _resize(Callback callback) { | 561 void _resize(Callback callback) { |
566 scheduleMicrotask(() { | 562 scheduleMicrotask(() { |
567 if (_lookupContentSizeDelegate != null) { | 563 if (_lookupContentSizeDelegate != null) { |
568 _contentSize = _lookupContentSizeDelegate(); | 564 _contentSize = _lookupContentSizeDelegate(); |
569 } else { | 565 } else { |
570 _contentSize = new Size(_element.scrollWidth, _element.scrollHeight); | 566 _contentSize = new Size(_element.scrollWidth, _element.scrollHeight); |
571 } | 567 } |
572 | 568 |
573 _scrollSize = new Size(_frame.offset.width, | 569 _scrollSize = new Size(_frame.offset.width, _frame.offset.height); |
574 _frame.offset.height); | |
575 Size adjusted = _getAdjustedContentSize(); | 570 Size adjusted = _getAdjustedContentSize(); |
576 _maxPoint = new Coordinate(-_maxOffset.x, -_maxOffset.y); | 571 _maxPoint = new Coordinate(-_maxOffset.x, -_maxOffset.y); |
577 _minPoint = new Coordinate( | 572 _minPoint = new Coordinate( |
578 Math.min( | 573 Math.min( |
579 _scrollSize.width - adjusted.width + _minOffset.x, _maxPoint.x), | 574 _scrollSize.width - adjusted.width + _minOffset.x, _maxPoint.x), |
580 Math.min( | 575 Math.min(_scrollSize.height - adjusted.height + _minOffset.y, |
581 _scrollSize.height - adjusted.height + _minOffset.y, _maxPoint.y))
; | 576 _maxPoint.y)); |
582 callback(); | 577 callback(); |
583 }); | 578 }); |
584 } | 579 } |
585 | 580 |
586 Coordinate _snapToBounds(num x, num y) { | 581 Coordinate _snapToBounds(num x, num y) { |
587 num clampX = GoogleMath.clamp(_minPoint.x, x, _maxPoint.x); | 582 num clampX = GoogleMath.clamp(_minPoint.x, x, _maxPoint.x); |
588 num clampY = GoogleMath.clamp(_minPoint.y, y, _maxPoint.y); | 583 num clampY = GoogleMath.clamp(_minPoint.y, y, _maxPoint.y); |
589 return new Coordinate(clampX, clampY); | 584 return new Coordinate(clampX, clampY); |
590 } | 585 } |
591 | 586 |
(...skipping 17 matching lines...) Expand all Loading... |
609 } | 604 } |
610 | 605 |
611 /** | 606 /** |
612 * Sets the vertical scrolled offset of the element where [y] is the amount | 607 * Sets the vertical scrolled offset of the element where [y] is the amount |
613 * of vertical space to be scrolled, in pixels. | 608 * of vertical space to be scrolled, in pixels. |
614 */ | 609 */ |
615 void setVerticalOffset(num y) { | 610 void setVerticalOffset(num y) { |
616 _setContentOffset(_contentOffset.x, y); | 611 _setContentOffset(_contentOffset.x, y); |
617 } | 612 } |
618 | 613 |
619 /** | 614 /** |
620 * Whether the scrollable area should scroll horizontally. Only | 615 * Whether the scrollable area should scroll horizontally. Only |
621 * returns true if the client has enabled horizontal scrolling, and the | 616 * returns true if the client has enabled horizontal scrolling, and the |
622 * content is wider than the frame. | 617 * content is wider than the frame. |
623 */ | 618 */ |
624 bool _shouldScrollHorizontally() { | 619 bool _shouldScrollHorizontally() { |
625 return horizontalEnabled && _scrollSize.width < _contentSize.width; | 620 return horizontalEnabled && _scrollSize.width < _contentSize.width; |
626 } | 621 } |
627 | 622 |
628 /** | 623 /** |
629 * Whether the scrollable area should scroll vertically. Only | 624 * Whether the scrollable area should scroll vertically. Only |
630 * returns true if the client has enabled vertical scrolling. | 625 * returns true if the client has enabled vertical scrolling. |
631 * Vertical bouncing will occur even if frame is taller than content, because | 626 * Vertical bouncing will occur even if frame is taller than content, because |
632 * this is what iPhone web apps tend to do. If this is not the desired | 627 * this is what iPhone web apps tend to do. If this is not the desired |
633 * behavior, either disable vertical scrolling for this scroller or add a | 628 * behavior, either disable vertical scrolling for this scroller or add a |
634 * 'bouncing' parameter to this interface. | 629 * 'bouncing' parameter to this interface. |
635 */ | 630 */ |
636 bool _shouldScrollVertically() { | 631 bool _shouldScrollVertically() { |
637 return verticalEnabled; | 632 return verticalEnabled; |
638 } | 633 } |
639 | 634 |
640 /** | 635 /** |
641 * In the event that the content is currently beyond the bounds of | 636 * In the event that the content is currently beyond the bounds of |
642 * the frame, snap it back in to place. | 637 * the frame, snap it back in to place. |
643 */ | 638 */ |
644 void _snapContentOffsetToBounds() { | 639 void _snapContentOffsetToBounds() { |
645 num clampX = | 640 num clampX = GoogleMath.clamp(_minPoint.x, _contentOffset.x, _maxPoint.x); |
646 GoogleMath.clamp(_minPoint.x, _contentOffset.x, _maxPoint.x); | 641 num clampY = GoogleMath.clamp(_minPoint.y, _contentOffset.y, _maxPoint.y); |
647 num clampY = | |
648 GoogleMath.clamp(_minPoint.y, _contentOffset.y, _maxPoint.y); | |
649 if (_contentOffset.x != clampX || _contentOffset.y != clampY) { | 642 if (_contentOffset.x != clampX || _contentOffset.y != clampY) { |
650 _setContentOffset(clampX, clampY); | 643 _setContentOffset(clampX, clampY); |
651 } | 644 } |
652 } | 645 } |
653 | 646 |
654 /** | 647 /** |
655 * Initiate the deceleration behavior given a flick [velocity]. | 648 * Initiate the deceleration behavior given a flick [velocity]. |
656 * Returns true if deceleration has been initiated. | 649 * Returns true if deceleration has been initiated. |
657 */ | 650 */ |
658 bool _startDeceleration(Coordinate velocity, | 651 bool _startDeceleration(Coordinate velocity, |
659 [num decelerationFactor = null]) { | 652 [num decelerationFactor = null]) { |
660 if (!_shouldScrollHorizontally()) { | 653 if (!_shouldScrollHorizontally()) { |
661 velocity.x = 0; | 654 velocity.x = 0; |
662 } | 655 } |
663 if (!_shouldScrollVertically()) { | 656 if (!_shouldScrollVertically()) { |
664 velocity.y = 0; | 657 velocity.y = 0; |
665 } | 658 } |
666 assert(_minPoint != null); // Min point is not set | 659 assert(_minPoint != null); // Min point is not set |
667 assert(_maxPoint != null); // Max point is not set | 660 assert(_maxPoint != null); // Max point is not set |
668 return _momentum.start(velocity, _minPoint, _maxPoint, _contentOffset, | 661 return _momentum.start( |
669 decelerationFactor); | 662 velocity, _minPoint, _maxPoint, _contentOffset, decelerationFactor); |
670 } | 663 } |
671 | 664 |
672 Coordinate stop() { | 665 Coordinate stop() { |
673 return _momentum.stop(); | 666 return _momentum.stop(); |
674 } | 667 } |
675 | 668 |
676 /** | 669 /** |
677 * Stop the deceleration of the scrollable content given a new position in px. | 670 * Stop the deceleration of the scrollable content given a new position in px. |
678 */ | 671 */ |
679 void _stopDecelerating(num x, num y) { | 672 void _stopDecelerating(num x, num y) { |
680 _momentum.stop(); | 673 _momentum.stop(); |
681 _setContentOffset(x, y); | 674 _setContentOffset(x, y); |
682 } | 675 } |
683 | 676 |
684 static Function _getOffsetFunction(int scrollTechnique) { | 677 static Function _getOffsetFunction(int scrollTechnique) { |
685 return scrollTechnique == ScrollerScrollTechnique.TRANSFORM_3D ? | 678 return scrollTechnique == ScrollerScrollTechnique.TRANSFORM_3D |
686 (el, x, y) { FxUtil.setTranslate(el, x, y, 0); } : | 679 ? (el, x, y) { |
687 (el, x, y) { FxUtil.setLeftAndTop(el, x, y); }; | 680 FxUtil.setTranslate(el, x, y, 0); |
| 681 } |
| 682 : (el, x, y) { |
| 683 FxUtil.setLeftAndTop(el, x, y); |
| 684 }; |
688 } | 685 } |
689 } | 686 } |
690 | 687 |
691 // TODO(jacobr): cleanup this class of enum constants. | 688 // TODO(jacobr): cleanup this class of enum constants. |
692 class ScrollerEventType { | 689 class ScrollerEventType { |
693 static const SCROLLER_START = "scroller:scroll_start"; | 690 static const SCROLLER_START = "scroller:scroll_start"; |
694 static const SCROLLER_END = "scroller:scroll_end"; | 691 static const SCROLLER_END = "scroller:scroll_end"; |
695 static const DRAG_END = "scroller:drag_end"; | 692 static const DRAG_END = "scroller:drag_end"; |
696 static const CONTENT_MOVED = "scroller:content_moved"; | 693 static const CONTENT_MOVED = "scroller:content_moved"; |
697 static const DECEL_START = "scroller:decel_start"; | 694 static const DECEL_START = "scroller:decel_start"; |
698 } | 695 } |
699 | 696 |
700 class ScrollerScrollTechnique { | 697 class ScrollerScrollTechnique { |
701 static const TRANSFORM_3D = 1; | 698 static const TRANSFORM_3D = 1; |
702 static const RELATIVE_POSITIONING = 2; | 699 static const RELATIVE_POSITIONING = 2; |
703 } | 700 } |
OLD | NEW |