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 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math' as math;
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/painting.dart';
import 'package:flutter/rendering.dart';
......@@ -22,7 +25,7 @@ class ExampleDragTargetState extends State<ExampleDragTarget> {
Widget build(BuildContext context) {
return new DragTarget<Color>(
onAccept: _handleAccept,
builder: (BuildContext context, List<Color> data, _) {
builder: (BuildContext context, List<Color> data, List<Color> rejectedData) {
return new Container(
height: 100.0,
margin: new EdgeDims.all(10.0),
......@@ -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) {
return new Scaffold(
toolBar: new ToolBar(
......@@ -137,30 +216,32 @@ class DragAndDropApp extends StatelessComponent {
),
body: new Column(
children: <Widget>[
new Flexible(child: new Row(
children: <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(
children: <Widget>[
new ExampleDragSource(
color: Colors.yellow[300],
under: true,
heavy: false,
child: new Text('under')
),
new ExampleDragSource(
color: Colors.green[300],
under: false,
heavy: true,
child: new Text('long-press above')
),
new ExampleDragSource(
color: Colors.indigo[300],
under: false,
heavy: false,
child: new Text('above')
),
],
alignItems: FlexAlignItems.center,
justifyContent: FlexJustifyContent.spaceAround
)
),
new Flexible(
child: new Row(
children: <Widget>[
......@@ -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 {
Key key,
this.data,
this.child,
this.childWhenDragging,
this.feedback,
this.feedbackOffset: Offset.zero,
this.dragAnchor: DragAnchor.child
this.dragAnchor: DragAnchor.child,
this.maxSimultaneousDrags
}) : super(key: key) {
assert(child != null);
assert(feedback != null);
assert(maxSimultaneousDrags == null || maxSimultaneousDrags > 0);
}
final T data;
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;
/// The feedbackOffset can be used to set the hit test target point for the
......@@ -66,6 +76,11 @@ abstract class DraggableBase<T> extends StatefulComponent {
/// Where this widget should be anchored during a drag.
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
/// 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
......@@ -81,16 +96,20 @@ class Draggable<T> extends DraggableBase<T> {
Key key,
T data,
Widget child,
Widget childWhenDragging,
Widget feedback,
Offset feedbackOffset: Offset.zero,
DragAnchor dragAnchor: DragAnchor.child
DragAnchor dragAnchor: DragAnchor.child,
int maxSimultaneousDrags
}) : super(
key: key,
data: data,
child: child,
childWhenDragging: childWhenDragging,
feedback: feedback,
feedbackOffset: feedbackOffset,
dragAnchor: dragAnchor
dragAnchor: dragAnchor,
maxSimultaneousDrags: maxSimultaneousDrags
);
GestureRecognizer createRecognizer(PointerRouter router, DragStartCallback starter) {
......@@ -108,16 +127,20 @@ class LongPressDraggable<T> extends DraggableBase<T> {
Key key,
T data,
Widget child,
Widget childWhenDragging,
Widget feedback,
Offset feedbackOffset: Offset.zero,
DragAnchor dragAnchor: DragAnchor.child
DragAnchor dragAnchor: DragAnchor.child,
int maxSimultaneousDrags
}) : super(
key: key,
data: data,
child: child,
childWhenDragging: childWhenDragging,
feedback: feedback,
feedbackOffset: feedbackOffset,
dragAnchor: dragAnchor
dragAnchor: dragAnchor,
maxSimultaneousDrags: maxSimultaneousDrags
);
GestureRecognizer createRecognizer(PointerRouter router, DragStartCallback starter) {
......@@ -144,6 +167,7 @@ class _DraggableState<T> extends State<DraggableBase<T>> implements GestureArena
GestureRecognizer _recognizer;
Map<int, GestureArenaEntry> _activePointers = <int, GestureArenaEntry>{};
int _activeCount = 0;
void _routePointer(PointerEvent event) {
_activePointers[event.pointer] = Gesturer.instance.gestureArena.add(event.pointer, this);
......@@ -159,6 +183,8 @@ class _DraggableState<T> extends State<DraggableBase<T>> implements GestureArena
}
void _startDrag(Point position, int pointer) {
if (config.maxSimultaneousDrags != null && _activeCount >= config.maxSimultaneousDrags)
return;
assert(_activePointers.containsKey(pointer));
_activePointers[pointer].resolve(GestureDisposition.accepted);
Point dragStartPoint;
......@@ -171,6 +197,9 @@ class _DraggableState<T> extends State<DraggableBase<T>> implements GestureArena
dragStartPoint = Point.origin;
break;
}
setState(() {
_activeCount += 1;
});
new _DragAvatar<T>(
pointer: pointer,
router: router,
......@@ -179,14 +208,20 @@ class _DraggableState<T> extends State<DraggableBase<T>> implements GestureArena
initialPosition: position,
dragStartPoint: dragStartPoint,
feedback: config.feedback,
feedbackOffset: config.feedbackOffset
feedbackOffset: config.feedbackOffset,
onDragEnd: () {
_activeCount -= 1;
}
);
}
Widget build(BuildContext context) {
final bool canDrag = config.maxSimultaneousDrags == null ||
_activeCount < config.maxSimultaneousDrags;
final bool showChild = _activeCount == 0 || config.childWhenDragging == null;
return new Listener(
onPointerDown: _routePointer,
child: config.child
onPointerDown: canDrag ? _routePointer : null,
child: showChild ? config.child : config.childWhenDragging
);
}
}
......@@ -278,7 +313,8 @@ class _DragAvatar<T> {
Point initialPosition,
this.dragStartPoint: Point.origin,
this.feedback,
this.feedbackOffset: Offset.zero
this.feedbackOffset: Offset.zero,
this.onDragEnd
}) {
assert(pointer != null);
assert(router != null);
......@@ -297,6 +333,7 @@ class _DragAvatar<T> {
final Point dragStartPoint;
final Widget feedback;
final Offset feedbackOffset;
final VoidCallback onDragEnd;
_DragTargetState _activeTarget;
bool _activeTargetWillAcceptDrop = false;
......@@ -353,6 +390,8 @@ class _DragAvatar<T> {
_entry.remove();
_entry = null;
router.removeRoute(pointer, handleEvent);
if (onDragEnd != null)
onDragEnd();
}
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