Index: client/touch/Scroller.dart |
=================================================================== |
--- client/touch/Scroller.dart (revision 4144) |
+++ client/touch/Scroller.dart (working copy) |
@@ -1,731 +0,0 @@ |
-// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file |
-// for details. All rights reserved. Use of this source code is governed by a |
-// BSD-style license that can be found in the LICENSE file. |
- |
-/** |
- * Implementation of a custom scrolling behavior. |
- * This behavior overrides native scrolling for an area. This area can be a |
- * single defined part of a page, the entire page, or several different parts |
- * of a page. |
- * |
- * To use this scrolling behavior you need to define a frame and the content. |
- * The frame defines the area that the content will scroll within. The frame and |
- * content must both be HTML Elements, with the content being a direct child of |
- * the frame. Usually the frame is smaller in size than the content. This is |
- * not necessary though, if the content is smaller then bouncing will occur to |
- * provide feedback that you are past the scrollable area. |
- * |
- * The scrolling behavior works using the webkit translate3d transformation, |
- * which means browsers that do not have hardware accelerated transformations |
- * will not perform as well using this. Simple scrolling should be fine even |
- * without hardware acceleration, but animating momentum and deceleration is |
- * unacceptably slow without it. There is also the option to use relative |
- * positioning (setting the left and top styles). |
- * |
- * For this to work properly you need to set -webkit-text-size-adjust to 'none' |
- * on an ancestor element of the frame, or on the frame itself. If you forget |
- * this you may see the text content of the scrollable area changing size as it |
- * moves. |
- * |
- * The behavior is intended to support vertical and horizontal scrolling, and |
- * scrolling with momentum when a touch gesture flicks with enough velocity. |
- */ |
-typedef void Callback(); |
- |
-// Helper method to await the completion of 2 futures. |
-void joinFutures(List<Future> futures, Callback callback) { |
- int count = 0; |
- int len = futures.length; |
- void helper(value) { |
- count++; |
- if (count == len) { |
- callback(); |
- } |
- } |
- for (Future p in futures) { |
- p.then(helper); |
- } |
-} |
- |
-class Scroller implements Draggable, MomentumDelegate { |
- |
- /** Pixels to move each time an arrow key is pressed. */ |
- static final ARROW_KEY_DELTA = 30; |
- static final SCROLL_WHEEL_VELOCITY = 0.01; |
- static final FAST_SNAP_DECELERATION_FACTOR = 0.84; |
- static final PAGE_KEY_SCROLL_FRACTION = .85; |
- |
- // TODO(jacobr): remove this static variable. |
- static bool _dragInProgress = false; |
- |
- /** The node that will actually scroll. */ |
- Element _element; |
- |
- /** |
- * Frame is the node that will serve as the container for the scrolling |
- * content. |
- */ |
- Element _frame; |
- |
- /** Touch manager to track the events on the scrollable area. */ |
- TouchHandler _touchHandler; |
- |
- Momentum _momentum; |
- |
- EventListenerList _onScrollerStart; |
- EventListenerList _onScrollerEnd; |
- EventListenerList _onScrollerDragEnd; |
- EventListenerList _onContentMoved; |
- EventListenerList _onDecelStart; |
- |
- /** Set if vertical scrolling should be enabled. */ |
- bool verticalEnabled; |
- |
- /** Set if horizontal scrolling should be enabled. */ |
- bool horizontalEnabled; |
- |
- /** |
- * Set if momentum should be enabled. |
- */ |
- bool _momentumEnabled; |
- |
- /** Set which type of scrolling translation technique should be used. */ |
- int _scrollTechnique; |
- |
- /** |
- * The maximum coordinate that the left upper corner of the content can scroll |
- * to. |
- */ |
- Coordinate _maxPoint; |
- |
- /** |
- * An offset to subtract from the maximum coordinate that the left upper |
- * corner of the content can scroll to. |
- */ |
- Coordinate _maxOffset; |
- |
- /** |
- * An offset to add to the minimum coordinate that the left upper corner of |
- * the content can scroll to. |
- */ |
- Coordinate _minOffset; |
- |
- /** Initialize the current content offset. */ |
- Coordinate _contentOffset; |
- |
- // TODO(jacobr): the function type is |
- // [:Function(Element, num, num)->void:]. |
- /** |
- * The function to use that will actually translate the scrollable node. |
- */ |
- Function _setOffsetFunction; |
- /** |
- * Function that returns the content size that can be specified instead of |
- * querying the DOM. |
- */ |
- Function _lookupContentSizeDelegate; |
- |
- Size _scrollSize; |
- Size _contentSize; |
- Coordinate _minPoint; |
- bool _isStopping = false; |
- Coordinate _contentStartOffset; |
- bool _started = false; |
- bool _activeGesture = false; |
- ScrollWatcher _scrollWatcher; |
- |
- Scroller(Element scrollableElem, [this.verticalEnabled = false, |
- this.horizontalEnabled = false, |
- momentumEnabled = true, |
- lookupContentSizeDelegate = null, |
- num defaultDecelerationFactor = 1, |
- int scrollTechnique = null, bool capture = false]) |
- : _momentumEnabled = momentumEnabled, |
- _lookupContentSizeDelegate = lookupContentSizeDelegate, |
- _element = scrollableElem, |
- _frame = scrollableElem.parent, |
- _scrollTechnique = scrollTechnique !== null |
- ? scrollTechnique : ScrollerScrollTechnique.TRANSFORM_3D, |
- _minPoint = new Coordinate(0, 0), |
- _maxPoint = new Coordinate(0, 0), |
- _maxOffset = new Coordinate(0, 0), |
- _minOffset = new Coordinate(0, 0), |
- _contentOffset = new Coordinate(0, 0) { |
- _touchHandler = new TouchHandler(this, scrollableElem.parent); |
- _momentum = new Momentum(this, defaultDecelerationFactor); |
- |
- Element parentElem = scrollableElem.parent; |
- assert(parentElem != null); |
- _setOffsetFunction = _getOffsetFunction(_scrollTechnique); |
- _touchHandler.setDraggable(this); |
- _touchHandler.enable(capture); |
- |
- _frame.on.mouseWheel.add((e) { |
- if (e.wheelDeltaY != 0 && verticalEnabled || |
- e.wheelDeltaX != 0 && horizontalEnabled) { |
- num x = horizontalEnabled ? e.wheelDeltaX : 0; |
- num y = verticalEnabled ? e.wheelDeltaY : 0; |
- throwDelta(x, y, FAST_SNAP_DECELERATION_FACTOR); |
- e.preventDefault(); |
- } |
- }); |
- |
- _frame.on.keyDown.add((KeyboardEvent e) { |
- bool handled = false; |
- // We ignore key events where further scrolling in that direction |
- // would have no impact which matches default browser behavior with |
- // nested scrollable areas. |
- |
- switch(e.keyCode) { |
- case 33: // page-up |
- throwDelta( |
- 0, |
- _scrollSize.height * PAGE_KEY_SCROLL_FRACTION); |
- handled = true; |
- break; |
- case 34: // page-down |
- throwDelta( |
- 0, -_scrollSize.height * PAGE_KEY_SCROLL_FRACTION); |
- handled = true; |
- break; |
- case 35: // End |
- throwTo(_maxPoint.x, _minPoint.y, |
- FAST_SNAP_DECELERATION_FACTOR); |
- handled = true; |
- break; |
- case 36: // Home |
- throwTo(_maxPoint.x,_maxPoint.y, |
- FAST_SNAP_DECELERATION_FACTOR); |
- handled = true; |
- break; |
-/* TODO(jacobr): enable arrow keys when the don't conflict with other |
- application keyboard shortcuts. |
- case 38: // up |
- handled = throwDelta( |
- 0, |
- ARROW_KEY_DELTA, |
- FAST_SNAP_DECELERATION_FACTOR); |
- break; |
- case 40: // down |
- handled = throwDelta( |
- 0, -ARROW_KEY_DELTA, |
- FAST_SNAP_DECELERATION_FACTOR); |
- break; |
- case 37: // left |
- handled = throwDelta( |
- ARROW_KEY_DELTA, 0, |
- FAST_SNAP_DECELERATION_FACTOR); |
- break; |
- case 39: // right |
- handled = throwDelta( |
- -ARROW_KEY_DELTA, |
- 0, |
- FAST_SNAP_DECELERATION_FACTOR); |
- break; |
- */ |
- } |
- if (handled) { |
- e.preventDefault(); |
- } |
- }); |
- // The scrollable element must be relatively positioned. |
- // TODO(jacobr): this assert fires asynchronously which could be confusing. |
- if (_scrollTechnique == ScrollerScrollTechnique.RELATIVE_POSITIONING) { |
- _element.computedStyle.then((CSSStyleDeclaration style) { |
- assert(style.position != "static"); |
- }); |
- } |
- |
- _initLayer(); |
- } |
- |
- EventListenerList get onScrollerStart() { |
- if (_onScrollerStart === null) { |
- _onScrollerStart = new SimpleEventListenerList(); |
- } |
- return _onScrollerStart; |
- } |
- |
- EventListenerList get onScrollerEnd() { |
- if (_onScrollerEnd === null) { |
- _onScrollerEnd = new SimpleEventListenerList(); |
- } |
- return _onScrollerEnd; |
- } |
- |
- EventListenerList get onScrollerDragEnd() { |
- if (_onScrollerDragEnd === null) { |
- _onScrollerDragEnd = new SimpleEventListenerList(); |
- } |
- return _onScrollerDragEnd; |
- } |
- |
- EventListenerList get onContentMoved() { |
- if (_onContentMoved === null) { |
- _onContentMoved = new SimpleEventListenerList(); |
- } |
- return _onContentMoved; |
- } |
- |
- EventListenerList get onDecelStart() { |
- if (_onDecelStart === null) { |
- _onDecelStart = new SimpleEventListenerList(); |
- } |
- return _onDecelStart; |
- } |
- |
- |
- /** |
- * Add a scroll listener. This allows other classes to subscribe to scroll |
- * notifications from this scroller. |
- */ |
- void addScrollListener(ScrollListener listener) { |
- if (_scrollWatcher === null) { |
- _scrollWatcher = new ScrollWatcher(this); |
- _scrollWatcher.initialize(); |
- } |
- _scrollWatcher.addListener(listener); |
- } |
- |
- /** |
- * Adjust the new calculated scroll position based on the minimum allowed |
- * position and returns the adjusted scroll value. |
- */ |
- num _adjustValue(num newPosition, num minPosition, |
- num maxPosition) { |
- assert(minPosition <= maxPosition); |
- |
- if (newPosition < minPosition) { |
- newPosition -= (newPosition - minPosition) / 2; |
- } else { |
- if (newPosition > maxPosition) { |
- newPosition -= (newPosition - maxPosition) / 2; |
- } |
- } |
- return newPosition; |
- } |
- |
- /** |
- * Coordinate we would end up at if we did nothing. |
- */ |
- Coordinate get currentTarget() { |
- Coordinate end = _momentum.destination; |
- if (end === null) { |
- end = _contentOffset; |
- } |
- return end; |
- } |
- |
- Coordinate get contentOffset() => _contentOffset; |
- |
- /** |
- * Animate the position of the scroller to the specified [x], [y] coordinates |
- * by applying the throw gesture with the correct velocity to end at that |
- * location. |
- */ |
- void throwTo(num x, num y, [num decelerationFactor = null]) { |
- reconfigure(() { |
- final snappedTarget = _snapToBounds(x, y); |
- // If a deceleration factor is not specified, use the existing |
- // deceleration factor specified by the momentum simulator. |
- if (decelerationFactor == null) { |
- decelerationFactor = _momentum.decelerationFactor; |
- } |
- |
- if (snappedTarget != currentTarget) { |
- _momentum.abort(); |
- |
- _startDeceleration( |
- _momentum.calculateVelocity( |
- _contentOffset, |
- snappedTarget, |
- decelerationFactor), |
- decelerationFactor); |
- onDecelStart.dispatch(new Event(ScrollerEventType.DECEL_START)); |
- } |
- }); |
- } |
- |
- void throwDelta(num deltaX, num deltaY, [num decelerationFactor = null]) { |
- Coordinate start = _contentOffset; |
- Coordinate end = currentTarget; |
- int x = end.x.toInt(); |
- int y = end.y.toInt(); |
- // If we are throwing in the opposite direction of the existing momentum, |
- // cancel the current momentum. |
- if (deltaX != 0 && deltaX.isNegative() != (end.x - start.x).isNegative()) { |
- x = start.x; |
- } |
- if (deltaY != 0 && deltaY.isNegative() != (end.y - start.y).isNegative()) { |
- y = start.y; |
- } |
- x += deltaX.toInt(); |
- y += deltaY.toInt(); |
- throwTo(x, y, decelerationFactor); |
- } |
- |
- void setPosition(num x, num y) { |
- _momentum.abort(); |
- _contentOffset.x = x; |
- _contentOffset.y = y; |
- _snapContentOffsetToBounds(); |
- _setContentOffset(_contentOffset.x, _contentOffset.y); |
- } |
- /** |
- * Adjusted content size is a size with the combined largest height and width |
- * of both the content and the frame. |
- */ |
- Size _getAdjustedContentSize() { |
- return new Size(Math.max(_scrollSize.width, _contentSize.width), |
- Math.max(_scrollSize.height, _contentSize.height)); |
- } |
- |
- // TODO(jmesserly): these should be properties instead of get* methods |
- num getDefaultVerticalOffset() => _maxPoint.y; |
- Element getElement() => _element; |
- Element getFrame() => _frame; |
- num getHorizontalOffset() => _contentOffset.x; |
- |
- /** |
- * [x] Value to use as reference for percent measurement. If |
- * none is provided then the content's current x offset will be used. |
- * Returns the percent of the page scrolled horizontally. |
- */ |
- num getHorizontalScrollPercent([num x = null]) { |
- x = x !== null ? x : _contentOffset.x; |
- return (x - _minPoint.x) / (_maxPoint.x - _minPoint.x); |
- } |
- |
- num getMaxPointY()=> _maxPoint.y; |
- num getMinPointY() => _minPoint.y; |
- Momentum get momentum() => _momentum; |
- |
- /** |
- * Provide access to the touch handler that the scroller created to manage |
- * touch events. |
- */ |
- TouchHandler getTouchHandler() => _touchHandler; |
- num getVerticalOffset() => _contentOffset.y; |
- |
- /** |
- * [y] value is used as reference for percent measurement. If |
- * none is provided then the content's current y offset will be used. |
- */ |
- num getVerticalScrollPercent([num y = null]) { |
- y = y !== null ? y : _contentOffset.y; |
- return (y - _minPoint.y) / Math.max(1, _maxPoint.y - _minPoint.y); |
- } |
- |
- /** |
- * Initialize the dom elements necessary for the scrolling to work. |
- */ |
- void _initLayer() { |
- // The scrollable node provided to Scroller must be a direct child |
- // of the scrollable frame. |
- // TODO(jacobr): Figure out why this is failing on dartium. |
- // assert(_element.parent == _frame); |
- _setContentOffset(_maxPoint.x, _maxPoint.y); |
- } |
- |
- void onDecelerate(num x, num y) { |
- _setContentOffset(x, y); |
- } |
- |
- void onDecelerationEnd() { |
- onScrollerEnd.dispatch(new Event(ScrollerEventType.SCROLLER_END)); |
- _started = false; |
- } |
- |
- void onDragEnd() { |
- _dragInProgress = false; |
- |
- bool decelerating = false; |
- if (_activeGesture) { |
- if (_momentumEnabled) { |
- decelerating = _startDeceleration(_touchHandler.getEndVelocity()); |
- } |
- } |
- |
- onScrollerDragEnd.dispatch(new Event(ScrollerEventType.DRAG_END)); |
- |
- if (!decelerating) { |
- _snapContentOffsetToBounds(); |
- onScrollerEnd.dispatch(new Event(ScrollerEventType.SCROLLER_END)); |
- _started = false; |
- } else { |
- onDecelStart.dispatch(new Event(ScrollerEventType.DECEL_START)); |
- } |
- _activeGesture = false; |
- } |
- |
- void onDragMove() { |
- if (_isStopping || (!_activeGesture && _dragInProgress)) { |
- return; |
- } |
- |
- assert(_contentStartOffset != null); // Content start not set |
- Coordinate contentStart = _contentStartOffset; |
- num newX = contentStart.x + _touchHandler.getDragDeltaX(); |
- num newY = contentStart.y + _touchHandler.getDragDeltaY(); |
- newY = _shouldScrollVertically() ? |
- _adjustValue(newY, _minPoint.y, _maxPoint.y) : 0; |
- newX = _shouldScrollHorizontally() ? |
- _adjustValue(newX, _minPoint.x, _maxPoint.x) : 0; |
- if (!_activeGesture) { |
- _activeGesture = true; |
- _dragInProgress = true; |
- } |
- if (!_started) { |
- _started = true; |
- onScrollerStart.dispatch(new Event(ScrollerEventType.SCROLLER_START)); |
- } |
- _setContentOffset(newX, newY); |
- } |
- |
- bool onDragStart(TouchEvent e) { |
- if (e.touches.length > 1) { |
- return false; |
- } |
- bool shouldHorizontal = _shouldScrollHorizontally(); |
- bool shouldVertical = _shouldScrollVertically(); |
- bool verticalish = _touchHandler.getDragDeltaY().abs() > |
- _touchHandler.getDragDeltaX().abs(); |
- return !!(shouldVertical || shouldHorizontal && !verticalish); |
- } |
- |
- void onTouchEnd() { |
- } |
- |
- /** |
- * Prepare the scrollable area for possible movement. |
- */ |
- bool onTouchStart(TouchEvent e) { |
- reconfigure(() { |
- final touch = e.touches[0]; |
- if (_momentum.decelerating) { |
- e.preventDefault(); |
- e.stopPropagation(); |
- stop(); |
- } |
- _contentStartOffset = _contentOffset.clone(); |
- _snapContentOffsetToBounds(); |
- }); |
- return true; |
- } |
- |
- /** |
- * Recalculate dimensions of the frame and the content. Adjust the minPoint |
- * and maxPoint allowed for scrolling and scroll to a valid position. Call |
- * this method if you know the frame or content has been updated. Called |
- * internally on every touchstart event the frame receives. |
- */ |
- void reconfigure(Callback callback) { |
- _resize(() { |
- _snapContentOffsetToBounds(); |
- callback(); |
- }); |
- } |
- |
- void reset() { |
- stop(); |
- _touchHandler.reset(); |
- _maxOffset.x = 0; |
- _maxOffset.y = 0; |
- _minOffset.x = 0; |
- _minOffset.y = 0; |
- reconfigure(() => _setContentOffset(_maxPoint.x, _maxPoint.y)); |
- } |
- |
- /** |
- * Recalculate dimensions of the frame and the content. Adjust the minPoint |
- * and maxPoint allowed for scrolling. |
- */ |
- void _resize(Callback callback) { |
- final frameRect = _frame.rect; |
- Future contentSizeFuture; |
- |
- if (_lookupContentSizeDelegate !== null) { |
- contentSizeFuture = _lookupContentSizeDelegate(); |
- contentSizeFuture.then((Size size) { |
- _contentSize = size; |
- }); |
- } else { |
- contentSizeFuture = _element.rect; |
- contentSizeFuture.then((ElementRect rect) { |
- _contentSize = new Size(rect.scroll.width, rect.scroll.height); |
- }); |
- } |
- |
- joinFutures(<Future>[frameRect, contentSizeFuture], () { |
- _scrollSize = new Size(frameRect.value.offset.width, |
- frameRect.value.offset.height); |
- Size adjusted = _getAdjustedContentSize(); |
- _maxPoint = new Coordinate(-_maxOffset.x, -_maxOffset.y); |
- _minPoint = new Coordinate( |
- Math.min( |
- _scrollSize.width - adjusted.width + _minOffset.x, _maxPoint.x), |
- Math.min( |
- _scrollSize.height - adjusted.height + _minOffset.y, _maxPoint.y)); |
- callback(); |
- }); |
- } |
- |
- Coordinate _snapToBounds(num x, num y) { |
- num clampX = GoogleMath.clamp(_minPoint.x, x, _maxPoint.x); |
- num clampY = GoogleMath.clamp(_minPoint.y, y, _maxPoint.y); |
- return new Coordinate(clampX, clampY); |
- } |
- |
- /** |
- * Translate the content to a new position specified in px. |
- */ |
- void _setContentOffset(num x, num y) { |
- _contentOffset.x = x; |
- _contentOffset.y = y; |
- _setOffsetFunction(_element, x, y); |
- onContentMoved.dispatch(new Event(ScrollerEventType.CONTENT_MOVED)); |
- } |
- |
- /** |
- * Enable or disable momentum. |
- */ |
- void setMomentum(bool enable) { |
- _momentumEnabled = enable; |
- } |
- |
- /** |
- * Sets the vertical scrolled offset of the element where [y] is the amount |
- * of vertical space to be scrolled, in pixels. |
- */ |
- void setVerticalOffset(num y) { |
- _setContentOffset(_contentOffset.x, y); |
- } |
- |
- /** |
- * Whether the scrollable area should scroll horizontally. Only |
- * returns true if the client has enabled horizontal scrolling, and the |
- * content is wider than the frame. |
- */ |
- bool _shouldScrollHorizontally() { |
- return horizontalEnabled && _scrollSize.width < _contentSize.width; |
- } |
- |
- /** |
- * Whether the scrollable area should scroll vertically. Only |
- * returns true if the client has enabled vertical scrolling. |
- * Vertical bouncing will occur even if frame is taller than content, because |
- * this is what iPhone web apps tend to do. If this is not the desired |
- * behavior, either disable vertical scrolling for this scroller or add a |
- * 'bouncing' parameter to this interface. |
- */ |
- bool _shouldScrollVertically() { |
- return verticalEnabled; |
- } |
- |
- /** |
- * In the event that the content is currently beyond the bounds of |
- * the frame, snap it back in to place. |
- */ |
- void _snapContentOffsetToBounds() { |
- num clampX = |
- GoogleMath.clamp(_minPoint.x, _contentOffset.x, _maxPoint.x); |
- num clampY = |
- GoogleMath.clamp(_minPoint.y, _contentOffset.y, _maxPoint.y); |
- if (_contentOffset.x != clampX || _contentOffset.y != clampY) { |
- _setContentOffset(clampX, clampY); |
- } |
- } |
- |
- /** |
- * Initiate the deceleration behavior given a flick [velocity]. |
- * Returns true if deceleration has been initiated. |
- */ |
- bool _startDeceleration(Coordinate velocity, |
- [num decelerationFactor = null]) { |
- if (!_shouldScrollHorizontally()) { |
- velocity.x = 0; |
- } |
- if (!_shouldScrollVertically()) { |
- velocity.y = 0; |
- } |
- assert(_minPoint != null); // Min point is not set |
- assert(_maxPoint != null); // Max point is not set |
- return _momentum.start(velocity, _minPoint, _maxPoint, _contentOffset, |
- decelerationFactor); |
- } |
- |
- Coordinate stop() { |
- return _momentum.stop(); |
- } |
- |
- /** |
- * Stop the deceleration of the scrollable content given a new position in px. |
- */ |
- void _stopDecelerating(num x, num y) { |
- _momentum.stop(); |
- _setContentOffset(x, y); |
- } |
- |
- static Function _getOffsetFunction(int scrollTechnique) { |
- return scrollTechnique == ScrollerScrollTechnique.TRANSFORM_3D ? |
- (el, x, y) { FxUtil.setTranslate(el, x, y, 0); } : |
- (el, x, y) { FxUtil.setLeftAndTop(el, x, y); }; |
- } |
-} |
- |
-// TODO(jacobr): cleanup this class of enum constants. |
-class ScrollerEventType { |
- static final SCROLLER_START = "scroller:scroll_start"; |
- static final SCROLLER_END = "scroller:scroll_end"; |
- static final DRAG_END = "scroller:drag_end"; |
- static final CONTENT_MOVED = "scroller:content_moved"; |
- static final DECEL_START = "scroller:decel_start"; |
-} |
- |
-// TODO(jacobr): for now this ignores capture. |
-class SimpleEventListenerList implements EventListenerList { |
- // Ignores capture for now. |
- List<EventListener> _listeners; |
- |
- SimpleEventListenerList() : _listeners = new List<EventListener>() { } |
- |
- EventListenerList add(EventListener handler, [bool useCapture = false]) { |
- _add(handler, useCapture); |
- return this; |
- } |
- |
- EventListenerList remove(EventListener handler, [bool useCapture = false]) { |
- _remove(handler, useCapture); |
- return this; |
- } |
- |
- EventListenerList addCapture(EventListener handler) { |
- _add(handler, true); |
- return this; |
- } |
- |
- EventListenerList removeCapture(EventListener handler) { |
- _remove(handler, true); |
- return this; |
- } |
- |
- void _add(EventListener handler, bool useCapture) { |
- _listeners.add(handler); |
- } |
- |
- void _remove(EventListener handler, bool useCapture) { |
- // TODO(jacobr): implemenet as needed. |
- throw 'Not implemented yet.'; |
- } |
- |
- bool dispatch(Event evt) { |
- for (EventListener listener in _listeners) { |
- listener(evt); |
- } |
- } |
-} |
- |
-class ScrollerScrollTechnique { |
- static final TRANSFORM_3D = 1; |
- static final RELATIVE_POSITIONING = 2; |
-} |