diff --git a/examples/widgets/drag_and_drop.dart b/examples/widgets/drag_and_drop.dart index ffbd9f534d3..14e60391f15 100644 --- a/examples/widgets/drag_and_drop.dart +++ b/examples/widgets/drag_and_drop.dart @@ -6,29 +6,23 @@ import 'package:flutter/material.dart'; import 'package:flutter/painting.dart'; import 'package:flutter/rendering.dart'; -class DragData { - DragData(this.text); - - final String text; -} - class ExampleDragTarget extends StatefulComponent { ExampleDragTargetState createState() => new ExampleDragTargetState(); } class ExampleDragTargetState extends State { - String _text = 'Drag Target'; + Color _color = Colors.grey[500]; - void _handleAccept(DragData data) { + void _handleAccept(Color data) { setState(() { - _text = 'dropped: ${data.text}'; + _color = data; }); } Widget build(BuildContext context) { - return new DragTarget( + return new DragTarget( onAccept: _handleAccept, - builder: (BuildContext context, List data, _) { + builder: (BuildContext context, List data, _) { return new Container( height: 100.0, margin: new EdgeDims.all(10.0), @@ -37,10 +31,7 @@ class ExampleDragTargetState extends State { width: 3.0, color: data.isEmpty ? Colors.white : Colors.blue[500] ), - backgroundColor: data.isEmpty ? Colors.grey[500] : Colors.green[500] - ), - child: new Center( - child: new Text(_text) + backgroundColor: data.isEmpty ? _color : Colors.grey[200] ) ); } @@ -49,42 +40,84 @@ class ExampleDragTargetState extends State { } class Dot extends StatelessComponent { - Dot({ Key key, this.color, this.size }) : super(key: key); + Dot({ Key key, this.color, this.size, this.child }) : super(key: key); final Color color; final double size; + final Widget child; Widget build(BuildContext context) { return new Container( width: size, height: size, decoration: new BoxDecoration( - borderRadius: 10.0, - backgroundColor: color - ) + backgroundColor: color, + shape: Shape.circle + ), + child: child ); } } class ExampleDragSource extends StatelessComponent { - ExampleDragSource({ Key key, this.name, this.color }) : super(key: key); - final String name; - final Color color; + ExampleDragSource({ + Key key, + this.color, + this.heavy: false, + this.under: true, + this.child + }) : super(key: key); - static const kDotSize = 50.0; - static const kFingerSize = 50.0; + final Color color; + final bool heavy; + final bool under; + final Widget child; + + static const double kDotSize = 50.0; + static const double kHeavyMultiplier = 1.5; + static const double kFingerSize = 50.0; Widget build(BuildContext context) { - return new Draggable( - data: new DragData(name), - child: new Dot(color: color, size: kDotSize), - feedback: new Transform( - transform: new Matrix4.identity()..translate(-kDotSize / 2.0, -(kDotSize / 2.0 + kFingerSize)), - child: new Opacity( - opacity: 0.75, - child: new Dot(color: color, size: kDotSize) - ) - ), - feedbackOffset: const Offset(0.0, -kFingerSize), - dragAnchor: DragAnchor.pointer + double size = kDotSize; + DraggableConstructor constructor = new Draggable#; + if (heavy) { + size *= kHeavyMultiplier; + constructor = new LongPressDraggable#; + } + + Widget contents = new DefaultTextStyle( + style: Theme.of(context).text.body1.copyWith(textAlign: TextAlign.center), + child: new Dot( + color: color, + size: size, + child: new Center(child: child) + ) + ); + + Widget feedback = new Opacity( + opacity: 0.75, + child: contents + ); + + Offset feedbackOffset; + DragAnchor anchor; + if (!under) { + feedback = new Transform( + transform: new Matrix4.identity() + ..translate(-size / 2.0, -(size / 2.0 + kFingerSize)), + child: feedback + ); + feedbackOffset = const Offset(0.0, -kFingerSize); + anchor = DragAnchor.pointer; + } else { + feedbackOffset = Offset.zero; + anchor = DragAnchor.child; + } + + return constructor( + data: color, + child: contents, + feedback: feedback, + feedbackOffset: feedbackOffset, + dragAnchor: anchor ); } } @@ -95,25 +128,37 @@ class DragAndDropApp extends StatelessComponent { toolBar: new ToolBar( center: new Text('Drag and Drop Flutter Demo') ), - body: new DefaultTextStyle( - style: Theme.of(context).text.body1.copyWith(textAlign: TextAlign.center), - child: new Column([ - new Flexible(child: new Row([ - new ExampleDragSource(name: 'Orange', color: const Color(0xFFFF9000)), - new ExampleDragSource(name: 'Teal', color: const Color(0xFF00FFFF)), - new ExampleDragSource(name: 'Yellow', color: const Color(0xFFFFF000)), - ], - alignItems: FlexAlignItems.center, - justifyContent: FlexJustifyContent.spaceAround - )), - new Flexible(child: new Row([ - new Flexible(child: new ExampleDragTarget()), - new Flexible(child: new ExampleDragTarget()), - new Flexible(child: new ExampleDragTarget()), - new Flexible(child: new ExampleDragTarget()), - ])), - ]) - ) + body: new Column([ + new Flexible(child: new Row([ + new ExampleDragSource( + color: const Color(0xFFFFF000), + under: true, + heavy: false, + child: new Text('under') + ), + new ExampleDragSource( + color: const Color(0xFF0FFF00), + under: false, + heavy: true, + child: new Text('long-press above') + ), + new ExampleDragSource( + color: const Color(0xFF00FFF0), + under: false, + heavy: false, + child: new Text('above') + ), + ], + alignItems: FlexAlignItems.center, + justifyContent: FlexJustifyContent.spaceAround + )), + new Flexible(child: new Row([ + new Flexible(child: new ExampleDragTarget()), + new Flexible(child: new ExampleDragTarget()), + new Flexible(child: new ExampleDragTarget()), + new Flexible(child: new ExampleDragTarget()), + ])), + ]) ); } } diff --git a/packages/flutter/lib/src/gestures/arena.dart b/packages/flutter/lib/src/gestures/arena.dart index 184a314ac2a..50c7a5b00cd 100644 --- a/packages/flutter/lib/src/gestures/arena.dart +++ b/packages/flutter/lib/src/gestures/arena.dart @@ -48,6 +48,12 @@ class _GestureArenaState { bool isHeld = false; bool hasPendingSweep = false; + /// If a gesture attempts to win while the arena is still open, it becomes the + /// "eager winnner". We look for an eager winner when closing the arena to new + /// participants, and if there is one, we resolve the arena it its favour at + /// that time. + GestureArenaMember eagerWinner; + void add(GestureArenaMember member) { assert(isOpen); members.add(member); @@ -122,6 +128,8 @@ class GestureArena { state.members.first.acceptGesture(key); } else if (state.members.isEmpty) { _arenas.remove(key); + } else if (state.eagerWinner != null) { + _resolveInFavorOf(key, state, state.eagerWinner); } } @@ -129,20 +137,33 @@ class GestureArena { _GestureArenaState state = _arenas[key]; if (state == null) return; // This arena has already resolved. - assert(!state.isOpen); assert(state.members.contains(member)); if (disposition == GestureDisposition.rejected) { state.members.remove(member); member.rejectGesture(key); - _tryToResolveArena(key, state); + if (!state.isOpen) + _tryToResolveArena(key, state); } else { assert(disposition == GestureDisposition.accepted); - _arenas.remove(key); - for (GestureArenaMember rejectedMember in state.members) { - if (rejectedMember != member) - rejectedMember.rejectGesture(key); + if (state.isOpen) { + if (state.eagerWinner == null) + state.eagerWinner = member; + } else { + _resolveInFavorOf(key, state, member); } - member.acceptGesture(key); } } -} + + void _resolveInFavorOf(Object key, _GestureArenaState state, GestureArenaMember member) { + assert(state == _arenas[key]); + assert(state != null); + assert(state.eagerWinner == null || state.eagerWinner == member); + assert(!state.isOpen); + _arenas.remove(key); + for (GestureArenaMember rejectedMember in state.members) { + if (rejectedMember != member) + rejectedMember.rejectGesture(key); + } + member.acceptGesture(key); + } +} \ No newline at end of file diff --git a/packages/flutter/lib/src/gestures/multitap.dart b/packages/flutter/lib/src/gestures/multitap.dart index 841b968d96f..ed9fdd13745 100644 --- a/packages/flutter/lib/src/gestures/multitap.dart +++ b/packages/flutter/lib/src/gestures/multitap.dart @@ -10,7 +10,13 @@ import 'constants.dart'; import 'events.dart'; import 'pointer_router.dart'; import 'recognizer.dart'; -import 'tap.dart' show GestureTapDownCallback, GestureTapDownCallback, GestureTapCallback, GestureTapCancelCallback; + +typedef void GestureDoubleTapCallback(); + +typedef void GestureMultiTapDownCallback(Point globalPosition, int pointer); +typedef void GestureMultiTapUpCallback(Point globalPosition, int pointer); +typedef void GestureMultiTapCallback(int pointer); +typedef void GestureMultiTapCancelCallback(int pointer); /// TapTracker helps track individual tap sequences as part of a /// larger gesture. @@ -52,7 +58,12 @@ class _TapTracker { class DoubleTapGestureRecognizer extends GestureRecognizer { - DoubleTapGestureRecognizer({ this.router, this.onDoubleTap }); + DoubleTapGestureRecognizer({ + PointerRouter router, + this.onDoubleTap + }) : _router = router { + assert(router != null); + } // Implementation notes: // The double tap recognizer can be in one of four states. There's no @@ -74,8 +85,8 @@ class DoubleTapGestureRecognizer extends GestureRecognizer { // - The long timer between taps expires // - The gesture arena decides we have been rejected wholesale - PointerRouter router; - GestureTapCallback onDoubleTap; + PointerRouter _router; + GestureDoubleTapCallback onDoubleTap; Timer _doubleTapTimer; _TapTracker _firstTap; @@ -92,22 +103,26 @@ class DoubleTapGestureRecognizer extends GestureRecognizer { entry: GestureArena.instance.add(event.pointer, this) ); _trackers[event.pointer] = tracker; - tracker.startTrackingPointer(router, handleEvent); + tracker.startTrackingPointer(_router, handleEvent); } void handleEvent(PointerInputEvent event) { _TapTracker tracker = _trackers[event.pointer]; assert(tracker != null); - if (event.type == 'pointerup') { - if (_firstTap == null) - _registerFirstTap(tracker); - else - _registerSecondTap(tracker); - } else if (event.type == 'pointermove' && - !tracker.isWithinTolerance(event, kDoubleTapTouchSlop)) { - _reject(tracker); - } else if (event.type == 'pointercancel') { - _reject(tracker); + switch (event.type) { + case 'pointerup': + if (_firstTap == null) + _registerFirstTap(tracker); + else + _registerSecondTap(tracker); + break; + case 'pointermove': + if (!tracker.isWithinTolerance(event, kDoubleTapTouchSlop)) + _reject(tracker); + break; + case 'pointercancel': + _reject(tracker); + break; } } @@ -139,7 +154,7 @@ class DoubleTapGestureRecognizer extends GestureRecognizer { void dispose() { _reset(); - router = null; + _router = null; } void _reset() { @@ -184,7 +199,7 @@ class DoubleTapGestureRecognizer extends GestureRecognizer { } void _freezeTracker(_TapTracker tracker) { - tracker.stopTrackingPointer(router, handleEvent); + tracker.stopTrackingPointer(_router, handleEvent); } void _startDoubleTapTimer() { @@ -213,21 +228,35 @@ class _TapGesture extends _TapTracker { _TapGesture({ MultiTapGestureRecognizer gestureRecognizer, - PointerInputEvent event + PointerInputEvent event, + Duration longTapDelay }) : gestureRecognizer = gestureRecognizer, + _lastPosition = event.position, super(event: event, entry: GestureArena.instance.add(event.pointer, gestureRecognizer)) { startTrackingPointer(gestureRecognizer.router, handleEvent); + if (longTapDelay > Duration.ZERO) { + _timer = new Timer(longTapDelay, () { + _timer = null; + gestureRecognizer._handleLongTap(event.pointer, _lastPosition); + }); + } } final MultiTapGestureRecognizer gestureRecognizer; bool _wonArena = false; + Timer _timer; + + Point _lastPosition; Point _finalPosition; void handleEvent(PointerInputEvent event) { assert(event.pointer == pointer); - if (event.type == 'pointermove' && !isWithinTolerance(event, kTouchSlop)) { - cancel(); + if (event.type == 'pointermove') { + if (!isWithinTolerance(event, kTouchSlop)) + cancel(); + else + _lastPosition = event.position; } else if (event.type == 'pointercancel') { cancel(); } else if (event.type == 'pointerup') { @@ -237,6 +266,12 @@ class _TapGesture extends _TapTracker { } } + void stopTrackingPointer(PointerRouter router, PointerRoute route) { + _timer?.cancel(); + _timer = null; + super.stopTrackingPointer(router, route); + } + void accept() { _wonArena = true; _check(); @@ -269,61 +304,77 @@ class _TapGesture extends _TapTracker { /// taps, on up-1 and up-2. class MultiTapGestureRecognizer extends GestureRecognizer { MultiTapGestureRecognizer({ - this.router, + PointerRouter router, this.onTapDown, this.onTapUp, this.onTap, - this.onTapCancel - }); + this.onTapCancel, + this.longTapDelay: Duration.ZERO, + this.onLongTapDown + }) : _router = router { + assert(router != null); + } - PointerRouter router; - GestureTapDownCallback onTapDown; - GestureTapDownCallback onTapUp; - GestureTapCallback onTap; - GestureTapCancelCallback onTapCancel; + PointerRouter get router => _router; + PointerRouter _router; + GestureMultiTapDownCallback onTapDown; + GestureMultiTapUpCallback onTapUp; + GestureMultiTapCallback onTap; + GestureMultiTapCancelCallback onTapCancel; + Duration longTapDelay; + GestureMultiTapDownCallback onLongTapDown; - Map _gestureMap = new Map(); + final Map _gestureMap = new Map(); void addPointer(PointerInputEvent event) { assert(!_gestureMap.containsKey(event.pointer)); _gestureMap[event.pointer] = new _TapGesture( gestureRecognizer: this, - event: event + event: event, + longTapDelay: longTapDelay ); if (onTapDown != null) - onTapDown(event.position); + onTapDown(event.position, event.pointer); } void acceptGesture(int pointer) { assert(_gestureMap.containsKey(pointer)); _gestureMap[pointer]?.accept(); + assert(!_gestureMap.containsKey(pointer)); } void rejectGesture(int pointer) { assert(_gestureMap.containsKey(pointer)); _gestureMap[pointer]?.reject(); + assert(!_gestureMap.containsKey(pointer)); } void _resolveTap(int pointer, _TapResolution resolution, Point globalPosition) { _gestureMap.remove(pointer); if (resolution == _TapResolution.tap) { if (onTapUp != null) - onTapUp(globalPosition); + onTapUp(globalPosition, pointer); if (onTap != null) - onTap(); + onTap(pointer); } else { if (onTapCancel != null) - onTapCancel(); + onTapCancel(pointer); } } + void _handleLongTap(int pointer, Point lastPosition) { + assert(_gestureMap.containsKey(pointer)); + if (onLongTapDown != null) + onLongTapDown(lastPosition, pointer); + } + void dispose() { List<_TapGesture> localGestures = new List<_TapGesture>.from(_gestureMap.values); for (_TapGesture gesture in localGestures) gesture.cancel(); // Rejection of each gesture should cause it to be removed from our map assert(_gestureMap.isEmpty); - router = null; + _router = null; } } diff --git a/packages/flutter/lib/src/gestures/recognizer.dart b/packages/flutter/lib/src/gestures/recognizer.dart index 984e0658655..5c5c5a8abaf 100644 --- a/packages/flutter/lib/src/gestures/recognizer.dart +++ b/packages/flutter/lib/src/gestures/recognizer.dart @@ -3,7 +3,7 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:ui' as ui; +import 'dart:ui' show Point, Offset; import 'arena.dart'; import 'constants.dart'; @@ -84,10 +84,6 @@ enum GestureRecognizerState { defunct } -ui.Point _getPoint(PointerInputEvent event) { - return new ui.Point(event.x, event.y); -} - abstract class PrimaryPointerGestureRecognizer extends OneSequenceGestureRecognizer { PrimaryPointerGestureRecognizer({ PointerRouter router, this.deadline }) : super(router: router); @@ -96,7 +92,7 @@ abstract class PrimaryPointerGestureRecognizer extends OneSequenceGestureRecogni GestureRecognizerState state = GestureRecognizerState.ready; int primaryPointer; - ui.Point initialPosition; + Point initialPosition; Timer _timer; void addPointer(PointerInputEvent event) { @@ -104,7 +100,7 @@ abstract class PrimaryPointerGestureRecognizer extends OneSequenceGestureRecogni if (state == GestureRecognizerState.ready) { state = GestureRecognizerState.possible; primaryPointer = event.pointer; - initialPosition = _getPoint(event); + initialPosition = event.position; if (deadline != null) _timer = new Timer(deadline, didExceedDeadline); } @@ -159,7 +155,7 @@ abstract class PrimaryPointerGestureRecognizer extends OneSequenceGestureRecogni } double _getDistance(PointerInputEvent event) { - ui.Offset offset = _getPoint(event) - initialPosition; + Offset offset = event.position - initialPosition; return offset.distance; } diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index 5b3f84e4aa8..1a21b2d30a8 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -587,6 +587,7 @@ class Container extends StatelessComponent { }) : super(key: key) { assert(margin == null || margin.isNonNegative); assert(padding == null || padding.isNonNegative); + assert(decoration == null || decoration.shape != Shape.circle || decoration.borderRadius == null); // can't have a border radius if you're a circle } final Widget child; diff --git a/packages/flutter/lib/src/widgets/drag_target.dart b/packages/flutter/lib/src/widgets/drag_target.dart index 99abd503827..92a6be0fb25 100644 --- a/packages/flutter/lib/src/widgets/drag_target.dart +++ b/packages/flutter/lib/src/widgets/drag_target.dart @@ -4,7 +4,9 @@ import 'dart:collection'; +import 'package:flutter/gestures.dart'; import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; import 'basic.dart'; import 'binding.dart'; @@ -15,6 +17,16 @@ import 'overlay.dart'; typedef bool DragTargetWillAccept(T data); typedef void DragTargetAccept(T data); typedef Widget DragTargetBuilder(BuildContext context, List candidateData, List rejectedData); +typedef void DragStartCallback(Point position, int pointer); + +typedef DraggableBase DraggableConstructor({ + Key key, + T data, + Widget child, + Widget feedback, + Offset feedbackOffset, + DragAnchor dragAnchor +}); enum DragAnchor { /// Display the feedback anchored at the position of the original child. If @@ -35,8 +47,8 @@ enum DragAnchor { pointer, } -class Draggable extends StatefulComponent { - Draggable({ +abstract class DraggableBase extends StatefulComponent { + DraggableBase({ Key key, this.data, this.child, @@ -48,7 +60,7 @@ class Draggable extends StatefulComponent { assert(feedback != null); } - final dynamic data; + final T data; final Widget child; final Widget feedback; @@ -58,69 +70,122 @@ class Draggable extends StatefulComponent { final Offset feedbackOffset; final DragAnchor dragAnchor; - _DraggableState createState() => new _DraggableState(); + /// Should return a GestureRecognizer instance that is configured to call the starter + /// argument when the drag is to begin. The arena for the pointer must not yet have + /// resolved at the time that the callback is invoked, because the draggable itself + /// is going to attempt to win the pointer's arena in that case. + GestureRecognizer createRecognizer(PointerRouter router, DragStartCallback starter); + + _DraggableState createState() => new _DraggableState(); } -class _DraggableState extends State { - _DragAvatar _avatar; +class Draggable extends DraggableBase { + Draggable({ + Key key, + T data, + Widget child, + Widget feedback, + Offset feedbackOffset: Offset.zero, + DragAnchor dragAnchor: DragAnchor.child + }) : super( + key: key, + data: data, + child: child, + feedback: feedback, + feedbackOffset: feedbackOffset, + dragAnchor: dragAnchor + ); - void _startDrag(PointerInputEvent event) { - if (_avatar != null) - return; // TODO(ianh): once we switch to using gestures, just hand the gesture to the avatar so it can do everything itself. then we can have multiple drags at the same time. - final Point point = new Point(event.x, event.y); + GestureRecognizer createRecognizer(PointerRouter router, DragStartCallback starter) { + return new MultiTapGestureRecognizer( + router: router, + onTapDown: starter + ); + } +} + +class LongPressDraggable extends DraggableBase { + LongPressDraggable({ + Key key, + T data, + Widget child, + Widget feedback, + Offset feedbackOffset: Offset.zero, + DragAnchor dragAnchor: DragAnchor.child + }) : super( + key: key, + data: data, + child: child, + feedback: feedback, + feedbackOffset: feedbackOffset, + dragAnchor: dragAnchor + ); + + GestureRecognizer createRecognizer(PointerRouter router, DragStartCallback starter) { + return new MultiTapGestureRecognizer( + router: router, + longTapDelay: kLongPressTimeout, + onLongTapDown: (Point position, int pointer) { + userFeedback.performHapticFeedback(HapticFeedbackType.VIRTUAL_KEY); + starter(position, pointer); + } + ); + } +} + +class _DraggableState extends State> implements GestureArenaMember { + + PointerRouter get router => FlutterBinding.instance.pointerRouter; + + void initState() { + super.initState(); + _recognizer = config.createRecognizer(router, _startDrag); + } + + GestureRecognizer _recognizer; + Map _activePointers = {}; + + void _routePointer(PointerInputEvent event) { + _activePointers[event.pointer] = GestureArena.instance.add(event.pointer, this); + _recognizer.addPointer(event); + } + + void acceptGesture(int pointer) { + _activePointers.remove(pointer); + } + + void rejectGesture(int pointer) { + _activePointers.remove(pointer); + } + + void _startDrag(Point position, int pointer) { + assert(_activePointers.containsKey(pointer)); + _activePointers[pointer].resolve(GestureDisposition.accepted); Point dragStartPoint; switch (config.dragAnchor) { case DragAnchor.child: final RenderBox renderObject = context.findRenderObject(); - dragStartPoint = renderObject.globalToLocal(point); + dragStartPoint = renderObject.globalToLocal(position); break; case DragAnchor.pointer: dragStartPoint = Point.origin; - break; + break; } - assert(dragStartPoint != null); - _avatar = new _DragAvatar( + new _DragAvatar( + pointer: pointer, + router: router, + overlay: Navigator.of(context).overlay, data: config.data, + initialPosition: position, dragStartPoint: dragStartPoint, feedback: config.feedback, - feedbackOffset: config.feedbackOffset, - onDragFinished: () { - _avatar = null; - } + feedbackOffset: config.feedbackOffset ); - _avatar.update(point); - _avatar.markNeedsBuild(context); - } - - void _updateDrag(PointerInputEvent event) { - if (_avatar != null) { - _avatar.update(new Point(event.x, event.y)); - _avatar.markNeedsBuild(context); - } - } - - void _cancelDrag(PointerInputEvent event) { - if (_avatar != null) { - _avatar.finish(_DragEndKind.canceled); - assert(_avatar == null); - } - } - - void _drop(PointerInputEvent event) { - if (_avatar != null) { - _avatar.update(new Point(event.x, event.y)); - _avatar.finish(_DragEndKind.dropped); - assert(_avatar == null); - } } Widget build(BuildContext context) { - // TODO(abarth): We should be using a GestureDetector return new Listener( - onPointerDown: _startDrag, - onPointerMove: _updateDrag, - onPointerCancel: _cancelDrag, - onPointerUp: _drop, + onPointerDown: _routePointer, child: config.child ); } @@ -181,7 +246,8 @@ class DragTargetState extends State> { metaData: this, child: config.builder(context, new UnmodifiableListView(_candidateData), - new UnmodifiableListView(_rejectedData)) + new UnmodifiableListView(_rejectedData) + ) ); } } @@ -189,30 +255,63 @@ class DragTargetState extends State> { enum _DragEndKind { dropped, canceled } -class _DragAvatar { +// The lifetime of this object is a little dubious right now. Specifically, it +// lives as long as the pointer is down. Arguably it should self-immolate if the +// overlay goes away, or maybe even if the Draggable that created goes away. +// This will probably need to be changed once we have more experience with using +// this widget. +class _DragAvatar { _DragAvatar({ + this.pointer, + this.router, + OverlayState overlay, this.data, + Point initialPosition, this.dragStartPoint: Point.origin, this.feedback, - this.feedbackOffset: Offset.zero, - this.onDragFinished + this.feedbackOffset: Offset.zero }) { + assert(pointer != null); + assert(router != null); + assert(overlay != null); + assert(dragStartPoint != null); assert(feedbackOffset != null); + router.addRoute(pointer, handleEvent); + _entry = new OverlayEntry(builder: _build); + overlay.insert(_entry); + update(initialPosition); } - final dynamic data; + final int pointer; + final PointerRouter router; + final T data; final Point dragStartPoint; final Widget feedback; final Offset feedbackOffset; - final VoidCallback onDragFinished; DragTargetState _activeTarget; bool _activeTargetWillAcceptDrop = false; Offset _lastOffset; OverlayEntry _entry; + void handleEvent(PointerInputEvent event) { + switch(event.type) { + case 'pointerup': + update(event.position); + finish(_DragEndKind.dropped); + break; + case 'pointercancel': + finish(_DragEndKind.canceled); + break; + case 'pointermove': + update(event.position); + break; + } + } + void update(Point globalPosition) { _lastOffset = globalPosition - dragStartPoint; + _entry.markNeedsBuild(); HitTestResult result = WidgetFlutterBinding.instance.hitTest(globalPosition + feedbackOffset); DragTargetState target = _getDragTarget(result.path); if (target == _activeTarget) @@ -223,18 +322,10 @@ class _DragAvatar { _activeTargetWillAcceptDrop = _activeTarget != null && _activeTarget.didEnter(data); } - void markNeedsBuild(BuildContext context) { - if (_entry == null) { - _entry = new OverlayEntry(builder: _build); - Navigator.of(context).overlay.insert(_entry); - } else { - _entry.markNeedsBuild(); - } - } - DragTargetState _getDragTarget(List path) { - // TODO(abarth): Why do we reverse the path here? - for (HitTestEntry entry in path.reversed) { + // Look for the RenderBox that corresponds to the hit target (the hit target + // widget builds a RenderMetadata box for us for this purpose). + for (HitTestEntry entry in path) { if (entry.target is RenderMetaData) { RenderMetaData renderMetaData = entry.target; if (renderMetaData.metaData is DragTargetState) @@ -255,8 +346,7 @@ class _DragAvatar { _activeTargetWillAcceptDrop = false; _entry.remove(); _entry = null; - if (onDragFinished != null) - onDragFinished(); + router.removeRoute(pointer, handleEvent); } Widget _build(BuildContext context) { diff --git a/packages/flutter/lib/src/widgets/gesture_detector.dart b/packages/flutter/lib/src/widgets/gesture_detector.dart index 5c9bd1d0992..e62764e3a64 100644 --- a/packages/flutter/lib/src/widgets/gesture_detector.dart +++ b/packages/flutter/lib/src/widgets/gesture_detector.dart @@ -84,7 +84,7 @@ class GestureDetector extends StatefulComponent { } class _GestureDetectorState extends State { - final PointerRouter _router = FlutterBinding.instance.pointerRouter; + PointerRouter get _router => FlutterBinding.instance.pointerRouter; TapGestureRecognizer _tap; DoubleTapGestureRecognizer _doubleTap;