Commit 46fc8e6a authored by Adam Barth's avatar Adam Barth

Merge pull request #653 from abarth/drag_target

Add support for drag-and-drop
parents fca0c3c9 ac0ec322
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:sky' as sky;
import 'package:sky/theme/colors.dart' as colors;
import 'package:sky/widgets.dart';
final double kTop = 10.0 + sky.view.paddingTop;
final double kLeft = 10.0;
class DragData {
DragData(this.text);
final String text;
}
class ExampleDragTarget extends StatefulComponent {
String _text = 'ready';
void syncFields(ExampleDragTarget source) {
}
void _handleAccept(DragData data) {
setState(() {
_text = data.text;
});
}
Widget build() {
return new DragTarget<DragData>(
onAccept: _handleAccept,
builder: (List<DragData> data, _) {
return new Container(
width: 100.0,
height: 100.0,
margin: new EdgeDims.all(10.0),
decoration: new BoxDecoration(
border: new Border.all(
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)
)
);
}
);
}
}
class Dot extends Component {
Widget build() {
return new Container(
width: 50.0,
height: 50.0,
decoration: new BoxDecoration(
backgroundColor: colors.DeepOrange[500]
)
);
}
}
class DragAndDropApp extends App {
DragController _dragController;
Offset _displacement = Offset.zero;
EventDisposition _startDrag(sky.PointerEvent event) {
setState(() {
_dragController = new DragController(new DragData("Orange"));
_dragController.update(new Point(event.x, event.y));
_displacement = Offset.zero;
});
return EventDisposition.consumed;
}
EventDisposition _updateDrag(sky.PointerEvent event) {
setState(() {
_dragController.update(new Point(event.x, event.y));
_displacement += new Offset(event.dx, event.dy);
});
return EventDisposition.consumed;
}
EventDisposition _cancelDrag(sky.PointerEvent event) {
setState(() {
_dragController.cancel();
_dragController = null;
});
return EventDisposition.consumed;
}
EventDisposition _drop(sky.PointerEvent event) {
setState(() {
_dragController.update(new Point(event.x, event.y));
_dragController.drop();
_dragController = null;
_displacement = Offset.zero;
});
return EventDisposition.consumed;
}
Widget build() {
List<Widget> layers = <Widget>[
new Flex([
new ExampleDragTarget(),
new ExampleDragTarget(),
new ExampleDragTarget(),
new ExampleDragTarget(),
]),
new Positioned(
top: kTop,
left: kLeft,
child: new Listener(
onPointerDown: _startDrag,
onPointerMove: _updateDrag,
onPointerCancel: _cancelDrag,
onPointerUp: _drop,
child: new Dot()
)
),
];
if (_dragController != null) {
layers.add(
new Positioned(
top: kTop + _displacement.dy,
left: kLeft + _displacement.dx,
child: new IgnorePointer(
child: new Opacity(
opacity: 0.5,
child: new Dot()
)
)
)
);
}
return new Container(
decoration: new BoxDecoration(backgroundColor: colors.Pink[500]),
child: new Stack(layers)
);
}
}
void main() {
runApp(new DragAndDropApp());
}
......@@ -85,9 +85,7 @@ class SkyBinding {
if (event is sky.PointerEvent) {
_handlePointerEvent(event);
} else if (event is sky.GestureEvent) {
HitTestResult result = new HitTestResult();
_renderView.hitTest(result, position: new Point(event.x, event.y));
dispatchEvent(event, result);
dispatchEvent(event, hitTest(new Point(event.x, event.y)));
} else {
for (EventListener e in _eventListeners)
e(event);
......@@ -97,8 +95,7 @@ class SkyBinding {
Map<int, PointerState> _stateForPointer = new Map<int, PointerState>();
PointerState _createStateForPointer(sky.PointerEvent event, Point position) {
HitTestResult result = new HitTestResult();
_renderView.hitTest(result, position: position);
HitTestResult result = hitTest(position);
PointerState state = new PointerState(result: result, lastPosition: position);
_stateForPointer[event.pointer] = state;
return state;
......@@ -128,6 +125,12 @@ class SkyBinding {
return dispatchEvent(event, state.result);
}
HitTestResult hitTest(Point position) {
HitTestResult result = new HitTestResult();
_renderView.hitTest(result, position: position);
return result;
}
EventDisposition dispatchEvent(sky.Event event, HitTestResult result) {
assert(result != null);
EventDisposition disposition = EventDisposition.ignored;
......
......@@ -14,6 +14,7 @@ export 'widgets/checkbox.dart';
export 'widgets/default_text_style.dart';
export 'widgets/dialog.dart';
export 'widgets/dismissable.dart';
export 'widgets/drag_target.dart';
export 'widgets/drawer.dart';
export 'widgets/drawer_divider.dart';
export 'widgets/drawer_header.dart';
......
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:collection';
import 'package:sky/base/hit_test.dart';
import 'package:sky/rendering/sky_binding.dart';
import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/framework.dart';
typedef bool DragTargetWillAccept<T>(T data);
typedef void DragTargetAccept<T>(T data);
typedef Widget DragTargetBuilder<T>(List<T> candidateData, List<dynamic> rejectedData);
class DragTarget<T> extends StatefulComponent {
DragTarget({
Key key,
this.builder,
this.onWillAccept,
this.onAccept
}) : super(key: key);
DragTargetBuilder<T> builder;
DragTargetWillAccept<T> onWillAccept;
DragTargetAccept<T> onAccept;
final List<T> _candidateData = new List<T>();
final List<dynamic> _rejectedData = new List<dynamic>();
void syncFields(DragTarget source) {
builder = source.builder;
onWillAccept = source.onWillAccept;
onAccept = source.onAccept;
}
bool didEnter(dynamic data) {
assert(!_candidateData.contains(data));
assert(!_rejectedData.contains(data));
if (data is T && (onWillAccept == null || onWillAccept(data))) {
setState(() {
_candidateData.add(data);
});
return true;
}
_rejectedData.add(data);
return false;
}
void didLeave(dynamic data) {
assert(_candidateData.contains(data) || _rejectedData.contains(data));
setState(() {
_candidateData.remove(data);
_rejectedData.remove(data);
});
}
void didDrop(dynamic data) {
assert(_candidateData.contains(data));
setState(() {
_candidateData.remove(data);
});
if (onAccept != null)
onAccept(data);
}
Widget build() {
return builder(new UnmodifiableListView<T>(_candidateData),
new UnmodifiableListView<dynamic>(_rejectedData));
}
}
class DragController {
DragController(this.data);
final dynamic data;
DragTarget _activeTarget;
bool _activeTargetWillAcceptDrop = false;
DragTarget _getDragTarget(List<HitTestEntry> path) {
for (HitTestEntry entry in path.reversed) {
for (Widget widget in RenderObjectWrapper.getWidgetsForRenderObject(entry.target)) {
if (widget is DragTarget)
return widget;
}
}
return null;
}
void update(Point globalPosition) {
HitTestResult result = SkyBinding.instance.hitTest(globalPosition);
DragTarget target = _getDragTarget(result.path);
if (target == _activeTarget)
return;
if (_activeTarget != null)
_activeTarget.didLeave(data);
_activeTarget = target;
_activeTargetWillAcceptDrop = _activeTarget != null && _activeTarget.didEnter(data);
}
void cancel() {
if (_activeTarget != null)
_activeTarget.didLeave(data);
_activeTarget = null;
_activeTargetWillAcceptDrop = false;
}
void drop() {
if (_activeTarget == null)
return;
if (_activeTargetWillAcceptDrop)
_activeTarget.didDrop(data);
else
_activeTarget.didLeave(data);
_activeTarget = null;
_activeTargetWillAcceptDrop = false;
}
}
......@@ -866,6 +866,17 @@ abstract class RenderObjectWrapper extends Widget {
new HashMap<RenderObject, RenderObjectWrapper>();
static RenderObjectWrapper _getMounted(RenderObject node) => _nodeMap[node];
static Iterable<Widget> getWidgetsForRenderObject(RenderObject renderObject) sync* {
Widget target = RenderObjectWrapper._getMounted(renderObject);
if (target == null)
return;
RenderObject targetRoot = target.root;
while (target != null && target.root == targetRoot) {
yield target;
target = target.parent;
}
}
RenderObjectWrapper _ancestor;
void insertChildRoot(RenderObjectWrapper child, dynamic slot);
void detachChildRoot(RenderObjectWrapper child);
......@@ -1209,11 +1220,7 @@ class WidgetSkyBinding extends SkyBinding {
if (disposition == EventDisposition.consumed)
return EventDisposition.consumed;
for (HitTestEntry entry in result.path.reversed) {
Widget target = RenderObjectWrapper._getMounted(entry.target);
if (target == null)
continue;
RenderObject targetRoot = target.root;
while (target != null && target.root == targetRoot) {
for (Widget target in RenderObjectWrapper.getWidgetsForRenderObject(entry.target)) {
if (target is Listener) {
EventDisposition targetDisposition = target._handleEvent(event);
if (targetDisposition == EventDisposition.consumed) {
......
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