Commit 255ed0b9 authored by Hixie's avatar Hixie

Make Draggable use gestures

Draggable is now itself a gesture arena member. This means it won't
conflict with other gesture recognisers in the same path.

This also allows variants of Draggable that are triggered by other
gestures.

Also, some cleanup of DoubleTapGestureRecognizer, GestureDetector, and
PrimaryPointerGestureRecognizer.

Also, make MultiTapGestureRecognizer support a timeout for longpress.

Also, make Draggable data be typed.

Also, hide warnings about constructor warnings for now. Analyzer doesn't
support them yet. (Have to do this on a per-line basis)

Directions for future research:
 - animating the avatar (enter/exit transitions)
 - interaction with the navigator (canceling a drag on page navigation, etc)
 - double-tap draggable
parent 5516d12f
......@@ -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<ExampleDragTarget> {
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<DragData>(
return new DragTarget<Color>(
onAccept: _handleAccept,
builder: (BuildContext context, List<DragData> data, _) {
builder: (BuildContext context, List<Color> data, _) {
return new Container(
height: 100.0,
margin: new EdgeDims.all(10.0),
......@@ -37,10 +31,7 @@ class ExampleDragTargetState extends State<ExampleDragTarget> {
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<ExampleDragTarget> {
}
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;
ExampleDragSource({
Key key,
this.color,
this.heavy: false,
this.under: true,
this.child
}) : super(key: key);
final Color color;
final bool heavy;
final bool under;
final Widget child;
static const kDotSize = 50.0;
static const kFingerSize = 50.0;
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<Color> constructor = new Draggable<Color>#;
if (heavy) {
size *= kHeavyMultiplier;
constructor = new LongPressDraggable<Color>#;
}
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(<Widget>[
new Flexible(child: new Row(<Widget>[
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(<Widget>[
new Flexible(child: new ExampleDragTarget()),
new Flexible(child: new ExampleDragTarget()),
new Flexible(child: new ExampleDragTarget()),
new Flexible(child: new ExampleDragTarget()),
])),
])
)
body: new Column(<Widget>[
new Flexible(child: new Row(<Widget>[
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(<Widget>[
new Flexible(child: new ExampleDragTarget()),
new Flexible(child: new ExampleDragTarget()),
new Flexible(child: new ExampleDragTarget()),
new Flexible(child: new ExampleDragTarget()),
])),
])
);
}
}
......
......@@ -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
......@@ -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<int, _TapGesture> _gestureMap = new Map<int, _TapGesture>();
final Map<int, _TapGesture> _gestureMap = new Map<int, _TapGesture>();
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;
}
}
......@@ -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;
}
......
......@@ -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;
......
......@@ -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>(T data);
typedef void DragTargetAccept<T>(T data);
typedef Widget DragTargetBuilder<T>(BuildContext context, List<T> candidateData, List<dynamic> rejectedData);
typedef void DragStartCallback(Point position, int pointer);
typedef DraggableBase<T> DraggableConstructor<T>({
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<T> 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<T> createState() => new _DraggableState<T>();
}
class _DraggableState extends State<Draggable> {
_DragAvatar _avatar;
class Draggable<T> extends DraggableBase<T> {
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
);
GestureRecognizer createRecognizer(PointerRouter router, DragStartCallback starter) {
return new MultiTapGestureRecognizer(
router: router,
onTapDown: starter
);
}
}
class LongPressDraggable<T> extends DraggableBase<T> {
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<T> extends State<DraggableBase<T>> implements GestureArenaMember {
PointerRouter get router => FlutterBinding.instance.pointerRouter;
void initState() {
super.initState();
_recognizer = config.createRecognizer(router, _startDrag);
}
GestureRecognizer _recognizer;
Map<int, GestureArenaEntry> _activePointers = <int, GestureArenaEntry>{};
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(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);
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<T>(
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<T> extends State<DragTarget<T>> {
metaData: this,
child: config.builder(context,
new UnmodifiableListView<T>(_candidateData),
new UnmodifiableListView<dynamic>(_rejectedData))
new UnmodifiableListView<dynamic>(_rejectedData)
)
);
}
}
......@@ -189,30 +255,63 @@ class DragTargetState<T> extends State<DragTarget<T>> {
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<T> {
_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<HitTestEntry> 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) {
......
......@@ -84,7 +84,7 @@ class GestureDetector extends StatefulComponent {
}
class _GestureDetectorState extends State<GestureDetector> {
final PointerRouter _router = FlutterBinding.instance.pointerRouter;
PointerRouter get _router => FlutterBinding.instance.pointerRouter;
TapGestureRecognizer _tap;
DoubleTapGestureRecognizer _doubleTap;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment