diff --git a/framework/animation/animated_value.dart b/framework/animation/animated_value.dart index 11f1dea27197fb3892ee5d9319478a5c3ab8d247..d07dac5646191c465fd2f2539a8581d0ae1ee61f 100644 --- a/framework/animation/animated_value.dart +++ b/framework/animation/animated_value.dart @@ -67,4 +67,11 @@ class AnimatedValue { _completer = new Completer(); return _completer.future; } + + double get remainingTime { + if (_animation == null) + return 0.0; + return _animation.remainingTime; + } + } diff --git a/framework/animation/generators.dart b/framework/animation/generators.dart index 3f05eee382ca642938843d913cb117b992e7cd11..7dab878a1b1ddf46b66a645b6768f679a6ce388f 100644 --- a/framework/animation/generators.dart +++ b/framework/animation/generators.dart @@ -68,6 +68,7 @@ class AnimationGenerator extends Generator { FrameGenerator _generator; Stream _stream; bool _done = false; + double _lastTime; AnimationGenerator({ this.initialDelay: 0.0, @@ -87,13 +88,20 @@ class AnimationGenerator extends Generator { startTime = timeStamp; double t = (timeStamp - (startTime + initialDelay)) / duration; - return math.max(0.0, math.min(t, 1.0)); + _lastTime = math.max(0.0, math.min(t, 1.0)); + return _lastTime; }) .takeWhile(_checkForCompletion) .where((t) => t >= 0.0) .map(_transform); } + double get remainingTime { + if (_lastTime == null) + return duration; + return duration - _lastTime; + } + void cancel() { _generator.cancel(); } diff --git a/framework/components/ink_splash.dart b/framework/components/ink_splash.dart index 335d49d22befc43877274fa6e62a48aa14ec3317..6b897680d6ebb7d979404f105f42a3876cec7882 100644 --- a/framework/components/ink_splash.dart +++ b/framework/components/ink_splash.dart @@ -13,6 +13,8 @@ import 'dart:sky' as sky; const double _kSplashConfirmedDuration = 350.0; const double _kSplashUnconfirmedDuration = config.kDefaultLongPressTimeout; +const double _kSplashAbortDuration = 100.0; +const double _kSplashInitialDelay = 0.0; // we could delay initially in case the user scrolls double _getSplashTargetSize(sky.ClientRect rect, double x, double y) { return 2.0 * math.max(math.max(x - rect.left, rect.right - x), @@ -26,11 +28,13 @@ class SplashController { final AnimatedValue _size = new AnimatedValue(0.0); double _offsetX; double _offsetY; + double _lastSize = 0.0; + bool _growing = true; double _targetSize; Stream _styleStream; void start() { - _size.animateTo(_targetSize, _kSplashUnconfirmedDuration, curve: easeOut); + _size.animateTo(_targetSize, _kSplashUnconfirmedDuration, curve: easeOut, initialDelay: _kSplashInitialDelay); } void confirm() { @@ -41,6 +45,14 @@ class SplashController { _size.animateTo(_targetSize, duration, curve: easeOut); } + void abort() { + _growing = false; + double durationRemaining = _size.remainingTime; + if (durationRemaining <= _kSplashAbortDuration) + return; + _size.animateTo(_targetSize, _kSplashAbortDuration, curve: easeOut); + } + void cancel() { _size.stop(); } @@ -55,12 +67,19 @@ class SplashController { if (p == _targetSize) { onDone(); } + double size; + if (_growing) { + sise = p; + _lastSize = p; + } else { + size = _lastSize; + } return ''' - top: ${_offsetY - p/2}px; - left: ${_offsetX - p/2}px; - width: ${p}px; - height: ${p}px; - border-radius: ${p}px; + top: ${_offsetY - size/2}px; + left: ${_offsetX - size/2}px; + width: ${size}px; + height: ${size}px; + border-radius: ${size}px; opacity: ${1.0 - (p / _targetSize)};'''; }); diff --git a/framework/components/ink_well.dart b/framework/components/ink_well.dart index 205e9f38f771ea6d43186337859821b7ac406a2f..096fa007963b8465bbb0fdaf8b554921e9436857 100644 --- a/framework/components/ink_well.dart +++ b/framework/components/ink_well.dart @@ -6,8 +6,9 @@ import '../fn.dart'; import 'dart:collection'; import 'dart:sky' as sky; import 'ink_splash.dart'; +import 'scrollable.dart'; -class InkWell extends Component { +class InkWell extends Component implements ScrollClient { static final Style _containmentStyleHack = new Style(''' transform: translateX(0);'''); @@ -55,9 +56,30 @@ class InkWell extends Component { pointer: event.primaryPointer, onDone: () { _splashDone(splash); }); _splashes.add(splash); + UINode node = parent; + while (node != null) { + if (node is Scrollable) + node.registerScrollClient(this); + node = node.parent; + } }); } + bool ancestorScrolled(Scrollable ancestor) { + _abortSplashes(); + return false; + } + + void handleRemoved() { + UINode node = parent; + while (node != null) { + if (node is Scrollable) + node.unregisterScrollClient(this); + node = node.parent; + } + super.handleRemoved(); + } + void _confirmSplash(sky.GestureEvent event) { if (_splashes == null) return; @@ -65,6 +87,14 @@ class InkWell extends Component { .forEach((splash) { splash.confirm(); }); } + void _abortSplashes() { + if (_splashes == null) + return; + setState(() { + _splashes.forEach((s) { s.abort(); }); + }); + } + void _cancelSplashes(sky.Event event) { if (_splashes == null) return; diff --git a/framework/components/scrollable.dart b/framework/components/scrollable.dart index dadcf775a67fd2c445a22c267f8231a0acfe1839..36bbaffef4c62953cb79a407ec1fa9d6d67b343a 100644 --- a/framework/components/scrollable.dart +++ b/framework/components/scrollable.dart @@ -17,6 +17,10 @@ double _velocityForFlingGesture(sky.GestureEvent event) { -event.velocityY)) / _kMillisecondsPerSecond; } +abstract class ScrollClient { + bool ancestorScrolled(Scrollable ancestor); +} + abstract class Scrollable extends Component { ScrollBehavior scrollBehavior; double get scrollOffset => _scrollOffset; @@ -43,12 +47,43 @@ abstract class Scrollable extends Component { ); } + List _registeredScrollClients; + + void registerScrollClient(ScrollClient notifiee) { + if (_registeredScrollClients == null) + _registeredScrollClients = new List(); + setState(() { + _registeredScrollClients.add(notifiee); + }); + } + + void unregisterScrollClient(ScrollClient notifiee) { + if (_registeredScrollClients == null) + return; + setState(() { + _registeredScrollClients.remove(notifiee); + }); + } + bool scrollTo(double newScrollOffset) { if (newScrollOffset == _scrollOffset) return false; setState(() { _scrollOffset = newScrollOffset; }); + if (_registeredScrollClients != null) { + var newList = null; + _registeredScrollClients.forEach((target) { + if (target.ancestorScrolled(this)) { + if (newList == null) + newList = new List(); + newList.add(target); + } + }); + setState(() { + _registeredScrollClients = newList; + }); + } return true; } diff --git a/framework/fn.dart b/framework/fn.dart index e7ff992764452bb1a800b87056b9c618b26f41c3..850ffd2597e50dd5f55545757de57341cef03cc2 100644 --- a/framework/fn.dart +++ b/framework/fn.dart @@ -62,6 +62,7 @@ enum _SyncOperation { IDENTICAL, INSERTION, STATEFUL, STATELESS, REMOVAL } abstract class UINode { String _key; UINode _parent; + UINode get parent => _parent; sky.Node _root; bool _defunct = false; @@ -78,7 +79,9 @@ abstract class UINode { void _remove() { _defunct = true; _root = null; + handleRemoved(); } + void handleRemoved() { } int _nodeDepth; void _ensureDepth() {