Commit a82b8963 authored by Ian Hickson's avatar Ian Hickson

Merge pull request #1741 from Hixie/drags

Draggable: childWhenDragging, maxSimultaneousDrags
parents 02d23e0a c56d3788
...@@ -2,6 +2,9 @@ ...@@ -2,6 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:math' as math;
import 'dart:ui' as ui;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/painting.dart'; import 'package:flutter/painting.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
...@@ -22,7 +25,7 @@ class ExampleDragTargetState extends State<ExampleDragTarget> { ...@@ -22,7 +25,7 @@ class ExampleDragTargetState extends State<ExampleDragTarget> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new DragTarget<Color>( return new DragTarget<Color>(
onAccept: _handleAccept, onAccept: _handleAccept,
builder: (BuildContext context, List<Color> data, _) { builder: (BuildContext context, List<Color> data, List<Color> rejectedData) {
return new Container( return new Container(
height: 100.0, height: 100.0,
margin: new EdgeDims.all(10.0), margin: new EdgeDims.all(10.0),
...@@ -129,7 +132,83 @@ class ExampleDragSource extends StatelessComponent { ...@@ -129,7 +132,83 @@ class ExampleDragSource extends StatelessComponent {
} }
} }
class DragAndDropApp extends StatelessComponent { class DashOutlineCirclePainter extends CustomPainter {
const DashOutlineCirclePainter();
static const int segments = 17;
static const double deltaTheta = math.PI * 2 / segments; // radians
static const double segmentArc = deltaTheta / 2.0; // radians
static const double startOffset = 1.0; // radians
void paint(Canvas canvas, Size size) {
final double radius = size.shortestSide / 2.0;
final Paint paint = new Paint()
..color = const Color(0xFF000000)
..style = ui.PaintingStyle.stroke
..strokeWidth = radius / 10.0;
final Path path = new Path();
final Rect box = Point.origin & size;
for (double theta = 0.0; theta < math.PI * 2.0; theta += deltaTheta)
path.addArc(box, theta + startOffset, segmentArc);
canvas.drawPath(path, paint);
}
bool shouldRepaint(DashOutlineCirclePainter oldPainter) => false;
}
class MovableBall extends StatelessComponent {
MovableBall(this.position, this.ballPosition, this.callback);
final int position;
final int ballPosition;
final ValueChanged<int> callback;
static const double kBallSize = 50.0;
Widget build(BuildContext context) {
Widget ball = new DefaultTextStyle(
style: Theme.of(context).text.body1.copyWith(
textAlign: TextAlign.center,
color: Colors.white
),
child: new Dot(
color: Colors.blue[700],
size: kBallSize,
child: new Center(child: new Text('BALL'))
)
);
Widget dashedBall = new Container(
width: kBallSize,
height: kBallSize,
child: new CustomPaint(
painter: const DashOutlineCirclePainter()
)
);
if (position == ballPosition) {
return new Draggable<bool>(
data: true,
child: ball,
childWhenDragging: dashedBall,
feedback: ball,
maxSimultaneousDrags: 1
);
} else {
return new DragTarget<bool>(
onAccept: (bool data) { callback(position); },
builder: (BuildContext context, List<bool> accepted, List<bool> rejected) {
return dashedBall;
}
);
}
}
}
class DragAndDropApp extends StatefulComponent {
DragAndDropAppState createState() => new DragAndDropAppState();
}
class DragAndDropAppState extends State<DragAndDropApp> {
int position = 1;
void moveBall(int newPosition) {
setState(() { position = newPosition; });
}
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new Scaffold( return new Scaffold(
toolBar: new ToolBar( toolBar: new ToolBar(
...@@ -137,30 +216,32 @@ class DragAndDropApp extends StatelessComponent { ...@@ -137,30 +216,32 @@ class DragAndDropApp extends StatelessComponent {
), ),
body: new Column( body: new Column(
children: <Widget>[ children: <Widget>[
new Flexible(child: new Row( new Flexible(
children: <Widget>[ child: new Row(
new ExampleDragSource( children: <Widget>[
color: const Color(0xFFFFF000), new ExampleDragSource(
under: true, color: Colors.yellow[300],
heavy: false, under: true,
child: new Text('under') heavy: false,
), child: new Text('under')
new ExampleDragSource( ),
color: const Color(0xFF0FFF00), new ExampleDragSource(
under: false, color: Colors.green[300],
heavy: true, under: false,
child: new Text('long-press above') heavy: true,
), child: new Text('long-press above')
new ExampleDragSource( ),
color: const Color(0xFF00FFF0), new ExampleDragSource(
under: false, color: Colors.indigo[300],
heavy: false, under: false,
child: new Text('above') heavy: false,
), child: new Text('above')
], ),
alignItems: FlexAlignItems.center, ],
justifyContent: FlexJustifyContent.spaceAround alignItems: FlexAlignItems.center,
)), justifyContent: FlexJustifyContent.spaceAround
)
),
new Flexible( new Flexible(
child: new Row( child: new Row(
children: <Widget>[ children: <Widget>[
...@@ -171,6 +252,16 @@ class DragAndDropApp extends StatelessComponent { ...@@ -171,6 +252,16 @@ class DragAndDropApp extends StatelessComponent {
] ]
) )
), ),
new Flexible(
child: new Row(
children: <Widget>[
new MovableBall(1, position, moveBall),
new MovableBall(2, position, moveBall),
new MovableBall(3, position, moveBall),
],
justifyContent: FlexJustifyContent.spaceAround
)
),
] ]
) )
); );
......
...@@ -44,18 +44,28 @@ abstract class DraggableBase<T> extends StatefulComponent { ...@@ -44,18 +44,28 @@ abstract class DraggableBase<T> extends StatefulComponent {
Key key, Key key,
this.data, this.data,
this.child, this.child,
this.childWhenDragging,
this.feedback, this.feedback,
this.feedbackOffset: Offset.zero, this.feedbackOffset: Offset.zero,
this.dragAnchor: DragAnchor.child this.dragAnchor: DragAnchor.child,
this.maxSimultaneousDrags
}) : super(key: key) { }) : super(key: key) {
assert(child != null); assert(child != null);
assert(feedback != null); assert(feedback != null);
assert(maxSimultaneousDrags == null || maxSimultaneousDrags > 0);
} }
final T data; final T data;
final Widget child; final Widget child;
/// The widget to show when a drag is under way. /// The widget to show instead of [child] when a drag is under way.
///
/// If this is null, then [child] will be used instead (and so the
/// drag source representation will change while a drag is under
/// way).
final Widget childWhenDragging;
/// The widget to show under the pointer when a drag is under way.
final Widget feedback; final Widget feedback;
/// The feedbackOffset can be used to set the hit test target point for the /// The feedbackOffset can be used to set the hit test target point for the
...@@ -66,6 +76,11 @@ abstract class DraggableBase<T> extends StatefulComponent { ...@@ -66,6 +76,11 @@ abstract class DraggableBase<T> extends StatefulComponent {
/// Where this widget should be anchored during a drag. /// Where this widget should be anchored during a drag.
final DragAnchor dragAnchor; final DragAnchor dragAnchor;
/// How many simultaneous drags to support. When null, no limit is applied.
/// Set this to 1 if you want to only allow the drag source to have one item
/// dragged at a time.
final int maxSimultaneousDrags;
/// Should return a GestureRecognizer instance that is configured to call the starter /// 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 /// 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 /// resolved at the time that the callback is invoked, because the draggable itself
...@@ -81,16 +96,20 @@ class Draggable<T> extends DraggableBase<T> { ...@@ -81,16 +96,20 @@ class Draggable<T> extends DraggableBase<T> {
Key key, Key key,
T data, T data,
Widget child, Widget child,
Widget childWhenDragging,
Widget feedback, Widget feedback,
Offset feedbackOffset: Offset.zero, Offset feedbackOffset: Offset.zero,
DragAnchor dragAnchor: DragAnchor.child DragAnchor dragAnchor: DragAnchor.child,
int maxSimultaneousDrags
}) : super( }) : super(
key: key, key: key,
data: data, data: data,
child: child, child: child,
childWhenDragging: childWhenDragging,
feedback: feedback, feedback: feedback,
feedbackOffset: feedbackOffset, feedbackOffset: feedbackOffset,
dragAnchor: dragAnchor dragAnchor: dragAnchor,
maxSimultaneousDrags: maxSimultaneousDrags
); );
GestureRecognizer createRecognizer(PointerRouter router, DragStartCallback starter) { GestureRecognizer createRecognizer(PointerRouter router, DragStartCallback starter) {
...@@ -108,16 +127,20 @@ class LongPressDraggable<T> extends DraggableBase<T> { ...@@ -108,16 +127,20 @@ class LongPressDraggable<T> extends DraggableBase<T> {
Key key, Key key,
T data, T data,
Widget child, Widget child,
Widget childWhenDragging,
Widget feedback, Widget feedback,
Offset feedbackOffset: Offset.zero, Offset feedbackOffset: Offset.zero,
DragAnchor dragAnchor: DragAnchor.child DragAnchor dragAnchor: DragAnchor.child,
int maxSimultaneousDrags
}) : super( }) : super(
key: key, key: key,
data: data, data: data,
child: child, child: child,
childWhenDragging: childWhenDragging,
feedback: feedback, feedback: feedback,
feedbackOffset: feedbackOffset, feedbackOffset: feedbackOffset,
dragAnchor: dragAnchor dragAnchor: dragAnchor,
maxSimultaneousDrags: maxSimultaneousDrags
); );
GestureRecognizer createRecognizer(PointerRouter router, DragStartCallback starter) { GestureRecognizer createRecognizer(PointerRouter router, DragStartCallback starter) {
...@@ -144,6 +167,7 @@ class _DraggableState<T> extends State<DraggableBase<T>> implements GestureArena ...@@ -144,6 +167,7 @@ class _DraggableState<T> extends State<DraggableBase<T>> implements GestureArena
GestureRecognizer _recognizer; GestureRecognizer _recognizer;
Map<int, GestureArenaEntry> _activePointers = <int, GestureArenaEntry>{}; Map<int, GestureArenaEntry> _activePointers = <int, GestureArenaEntry>{};
int _activeCount = 0;
void _routePointer(PointerEvent event) { void _routePointer(PointerEvent event) {
_activePointers[event.pointer] = Gesturer.instance.gestureArena.add(event.pointer, this); _activePointers[event.pointer] = Gesturer.instance.gestureArena.add(event.pointer, this);
...@@ -159,6 +183,8 @@ class _DraggableState<T> extends State<DraggableBase<T>> implements GestureArena ...@@ -159,6 +183,8 @@ class _DraggableState<T> extends State<DraggableBase<T>> implements GestureArena
} }
void _startDrag(Point position, int pointer) { void _startDrag(Point position, int pointer) {
if (config.maxSimultaneousDrags != null && _activeCount >= config.maxSimultaneousDrags)
return;
assert(_activePointers.containsKey(pointer)); assert(_activePointers.containsKey(pointer));
_activePointers[pointer].resolve(GestureDisposition.accepted); _activePointers[pointer].resolve(GestureDisposition.accepted);
Point dragStartPoint; Point dragStartPoint;
...@@ -171,6 +197,9 @@ class _DraggableState<T> extends State<DraggableBase<T>> implements GestureArena ...@@ -171,6 +197,9 @@ class _DraggableState<T> extends State<DraggableBase<T>> implements GestureArena
dragStartPoint = Point.origin; dragStartPoint = Point.origin;
break; break;
} }
setState(() {
_activeCount += 1;
});
new _DragAvatar<T>( new _DragAvatar<T>(
pointer: pointer, pointer: pointer,
router: router, router: router,
...@@ -179,14 +208,20 @@ class _DraggableState<T> extends State<DraggableBase<T>> implements GestureArena ...@@ -179,14 +208,20 @@ class _DraggableState<T> extends State<DraggableBase<T>> implements GestureArena
initialPosition: position, initialPosition: position,
dragStartPoint: dragStartPoint, dragStartPoint: dragStartPoint,
feedback: config.feedback, feedback: config.feedback,
feedbackOffset: config.feedbackOffset feedbackOffset: config.feedbackOffset,
onDragEnd: () {
_activeCount -= 1;
}
); );
} }
Widget build(BuildContext context) { Widget build(BuildContext context) {
final bool canDrag = config.maxSimultaneousDrags == null ||
_activeCount < config.maxSimultaneousDrags;
final bool showChild = _activeCount == 0 || config.childWhenDragging == null;
return new Listener( return new Listener(
onPointerDown: _routePointer, onPointerDown: canDrag ? _routePointer : null,
child: config.child child: showChild ? config.child : config.childWhenDragging
); );
} }
} }
...@@ -278,7 +313,8 @@ class _DragAvatar<T> { ...@@ -278,7 +313,8 @@ class _DragAvatar<T> {
Point initialPosition, Point initialPosition,
this.dragStartPoint: Point.origin, this.dragStartPoint: Point.origin,
this.feedback, this.feedback,
this.feedbackOffset: Offset.zero this.feedbackOffset: Offset.zero,
this.onDragEnd
}) { }) {
assert(pointer != null); assert(pointer != null);
assert(router != null); assert(router != null);
...@@ -297,6 +333,7 @@ class _DragAvatar<T> { ...@@ -297,6 +333,7 @@ class _DragAvatar<T> {
final Point dragStartPoint; final Point dragStartPoint;
final Widget feedback; final Widget feedback;
final Offset feedbackOffset; final Offset feedbackOffset;
final VoidCallback onDragEnd;
_DragTargetState _activeTarget; _DragTargetState _activeTarget;
bool _activeTargetWillAcceptDrop = false; bool _activeTargetWillAcceptDrop = false;
...@@ -353,6 +390,8 @@ class _DragAvatar<T> { ...@@ -353,6 +390,8 @@ class _DragAvatar<T> {
_entry.remove(); _entry.remove();
_entry = null; _entry = null;
router.removeRoute(pointer, handleEvent); router.removeRoute(pointer, handleEvent);
if (onDragEnd != null)
onDragEnd();
} }
Widget _build(BuildContext context) { Widget _build(BuildContext context) {
......
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