您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
580 行
18 KiB
580 行
18 KiB
using System.Collections.Generic;
|
|
using Unity.UIWidgets.foundation;
|
|
using Unity.UIWidgets.gestures;
|
|
using Unity.UIWidgets.painting;
|
|
using Unity.UIWidgets.rendering;
|
|
using Unity.UIWidgets.ui;
|
|
|
|
namespace Unity.UIWidgets.widgets {
|
|
public delegate bool DragTargetWillAccept<T>(T data);
|
|
|
|
public delegate void DragTargetAccept<T>(T data);
|
|
|
|
public delegate Widget DragTargetBuilder<T>(BuildContext context, List<T> candidateData, List<T> rejectedData);
|
|
|
|
public delegate void DraggableCanceledCallback(Velocity velocity, Offset offset);
|
|
|
|
public delegate void DragEndCallback(DraggableDetails details);
|
|
|
|
public delegate void DragTargetLeave<T>(T data);
|
|
|
|
public enum DragAnchor {
|
|
child,
|
|
pointer
|
|
}
|
|
|
|
static class _DragUtils {
|
|
public static List<T> _mapAvatarsToData<T>(List<_DragAvatar<T>> avatars) {
|
|
List<T> ret = new List<T>(avatars.Count);
|
|
foreach (var avatar in avatars) {
|
|
ret.Add(avatar.data);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
public class Draggable<T> : StatefulWidget {
|
|
public Draggable(
|
|
T data,
|
|
Key key = null,
|
|
Widget child = null,
|
|
Widget feedback = null,
|
|
Axis? axis = null,
|
|
Widget childWhenDragging = null,
|
|
Offset feedbackOffset = null,
|
|
DragAnchor dragAnchor = DragAnchor.child,
|
|
Axis? affinity = null,
|
|
int? maxSimultaneousDrags = null,
|
|
VoidCallback onDragStarted = null,
|
|
DraggableCanceledCallback onDraggableCanceled = null,
|
|
DragEndCallback onDragEnd = null,
|
|
VoidCallback onDragCompleted = null) : base(key) {
|
|
D.assert(child != null);
|
|
D.assert(feedback != null);
|
|
D.assert(maxSimultaneousDrags == null || maxSimultaneousDrags >= 0);
|
|
|
|
this.child = child;
|
|
this.feedback = feedback;
|
|
this.data = data;
|
|
this.axis = axis;
|
|
this.childWhenDragging = childWhenDragging;
|
|
if (feedbackOffset == null) {
|
|
feedbackOffset = Offset.zero;
|
|
}
|
|
|
|
this.feedbackOffset = feedbackOffset;
|
|
this.dragAnchor = dragAnchor;
|
|
this.affinity = affinity;
|
|
this.maxSimultaneousDrags = maxSimultaneousDrags;
|
|
this.onDragStarted = onDragStarted;
|
|
this.onDraggableCanceled = onDraggableCanceled;
|
|
this.onDragEnd = onDragEnd;
|
|
this.onDragCompleted = onDragCompleted;
|
|
}
|
|
|
|
public readonly T data;
|
|
|
|
public readonly Axis? axis;
|
|
|
|
public readonly Widget child;
|
|
|
|
public readonly Widget childWhenDragging;
|
|
|
|
public readonly Widget feedback;
|
|
|
|
public readonly Offset feedbackOffset;
|
|
|
|
public readonly DragAnchor dragAnchor;
|
|
|
|
readonly Axis? affinity;
|
|
|
|
public readonly int? maxSimultaneousDrags;
|
|
|
|
public readonly VoidCallback onDragStarted;
|
|
|
|
public readonly DraggableCanceledCallback onDraggableCanceled;
|
|
|
|
public readonly VoidCallback onDragCompleted;
|
|
|
|
public readonly DragEndCallback onDragEnd;
|
|
|
|
|
|
public virtual GestureRecognizer createRecognizer(GestureMultiDragStartCallback onStart) {
|
|
switch (affinity) {
|
|
case Axis.horizontal: {
|
|
return new HorizontalMultiDragGestureRecognizer(this) {onStart = onStart};
|
|
}
|
|
case Axis.vertical: {
|
|
return new VerticalMultiDragGestureRecognizer(this) {onStart = onStart};
|
|
}
|
|
}
|
|
|
|
return new ImmediateMultiDragGestureRecognizer(this) {onStart = onStart};
|
|
}
|
|
|
|
public override State createState() {
|
|
return new _DraggableState<T>();
|
|
}
|
|
}
|
|
|
|
|
|
public class LongPressDraggable<T> : Draggable<T> {
|
|
public LongPressDraggable(
|
|
T data,
|
|
Key key = null,
|
|
Widget child = null,
|
|
Widget feedback = null,
|
|
Axis? axis = null,
|
|
Widget childWhenDragging = null,
|
|
Offset feedbackOffset = null,
|
|
DragAnchor dragAnchor = DragAnchor.child,
|
|
int? maxSimultaneousDrags = null,
|
|
VoidCallback onDragStarted = null,
|
|
DraggableCanceledCallback onDraggableCanceled = null,
|
|
DragEndCallback onDragEnd = null,
|
|
VoidCallback onDragCompleted = null
|
|
) : base(
|
|
key: key,
|
|
child: child,
|
|
feedback: feedback,
|
|
data: data,
|
|
axis: axis,
|
|
childWhenDragging: childWhenDragging,
|
|
feedbackOffset: feedbackOffset,
|
|
dragAnchor: dragAnchor,
|
|
maxSimultaneousDrags: maxSimultaneousDrags,
|
|
onDragStarted: onDragStarted,
|
|
onDraggableCanceled: onDraggableCanceled,
|
|
onDragEnd: onDragEnd,
|
|
onDragCompleted: onDragCompleted
|
|
) {
|
|
}
|
|
|
|
public override GestureRecognizer createRecognizer(GestureMultiDragStartCallback onStart) {
|
|
return new DelayedMultiDragGestureRecognizer(Constants.kLongPressTimeout) {
|
|
onStart = (Offset position) => {
|
|
Drag result = onStart(position);
|
|
return result;
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
public class _DraggableState<T> : State<Draggable<T>> {
|
|
public override void initState() {
|
|
base.initState();
|
|
_recognizer = widget.createRecognizer(_startDrag);
|
|
}
|
|
|
|
public override void dispose() {
|
|
_disposeRecognizerIfInactive();
|
|
base.dispose();
|
|
}
|
|
|
|
GestureRecognizer _recognizer;
|
|
int _activeCount;
|
|
|
|
void _disposeRecognizerIfInactive() {
|
|
if (_activeCount > 0) {
|
|
return;
|
|
}
|
|
|
|
_recognizer.dispose();
|
|
_recognizer = null;
|
|
}
|
|
|
|
|
|
void _routePointer(PointerEvent pEvent) {
|
|
if (widget.maxSimultaneousDrags != null &&
|
|
_activeCount >= widget.maxSimultaneousDrags) {
|
|
return;
|
|
}
|
|
|
|
if (pEvent is PointerDownEvent) {
|
|
_recognizer.addPointer((PointerDownEvent) pEvent);
|
|
}
|
|
}
|
|
|
|
_DragAvatar<T> _startDrag(Offset position) {
|
|
if (widget.maxSimultaneousDrags != null &&
|
|
_activeCount >= widget.maxSimultaneousDrags) {
|
|
return null;
|
|
}
|
|
|
|
var dragStartPoint = Offset.zero;
|
|
switch (widget.dragAnchor) {
|
|
case DragAnchor.child:
|
|
RenderBox renderObject = context.findRenderObject() as RenderBox;
|
|
dragStartPoint = renderObject.globalToLocal(position);
|
|
break;
|
|
case DragAnchor.pointer:
|
|
dragStartPoint = Offset.zero;
|
|
break;
|
|
}
|
|
|
|
setState(() => { _activeCount += 1; });
|
|
|
|
_DragAvatar<T> avatar = new _DragAvatar<T>(
|
|
overlayState: Overlay.of(context, debugRequiredFor: widget),
|
|
data: widget.data,
|
|
axis: widget.axis,
|
|
initialPosition: position,
|
|
dragStartPoint: dragStartPoint,
|
|
feedback: widget.feedback,
|
|
feedbackOffset: widget.feedbackOffset,
|
|
onDragEnd: (Velocity velocity, Offset offset, bool wasAccepted) => {
|
|
if (mounted) {
|
|
setState(() => { _activeCount -= 1; });
|
|
}
|
|
else {
|
|
_activeCount -= 1;
|
|
_disposeRecognizerIfInactive();
|
|
}
|
|
|
|
if (mounted && widget.onDragEnd != null) {
|
|
widget.onDragEnd(new DraggableDetails(
|
|
wasAccepted: wasAccepted,
|
|
velocity: velocity,
|
|
offset: offset
|
|
));
|
|
}
|
|
|
|
if (wasAccepted && widget.onDragCompleted != null) {
|
|
widget.onDragCompleted();
|
|
}
|
|
|
|
if (!wasAccepted && widget.onDraggableCanceled != null) {
|
|
widget.onDraggableCanceled(velocity, offset);
|
|
}
|
|
}
|
|
);
|
|
if (widget.onDragStarted != null) {
|
|
widget.onDragStarted();
|
|
}
|
|
|
|
return avatar;
|
|
}
|
|
|
|
public override Widget build(BuildContext context) {
|
|
D.assert(Overlay.of(context, debugRequiredFor: widget) != null);
|
|
bool canDrag = widget.maxSimultaneousDrags == null ||
|
|
_activeCount < widget.maxSimultaneousDrags;
|
|
|
|
bool showChild = _activeCount == 0 || widget.childWhenDragging == null;
|
|
if (canDrag) {
|
|
return new Listener(
|
|
onPointerDown: _routePointer,
|
|
child: showChild ? widget.child : widget.childWhenDragging
|
|
);
|
|
}
|
|
|
|
return new Listener(
|
|
child: showChild ? widget.child : widget.childWhenDragging);
|
|
}
|
|
}
|
|
|
|
|
|
public class DraggableDetails {
|
|
public DraggableDetails(
|
|
bool wasAccepted = false,
|
|
Velocity velocity = null,
|
|
Offset offset = null
|
|
) {
|
|
D.assert(velocity != null);
|
|
D.assert(offset != null);
|
|
this.wasAccepted = wasAccepted;
|
|
this.velocity = velocity;
|
|
this.offset = offset;
|
|
}
|
|
|
|
public readonly bool wasAccepted;
|
|
|
|
public readonly Velocity velocity;
|
|
|
|
public readonly Offset offset;
|
|
}
|
|
|
|
|
|
public class DragTarget<T> : StatefulWidget {
|
|
public DragTarget(
|
|
Key key = null,
|
|
DragTargetBuilder<T> builder = null,
|
|
DragTargetWillAccept<T> onWillAccept = null,
|
|
DragTargetAccept<T> onAccept = null,
|
|
DragTargetLeave<T> onLeave = null
|
|
) : base(key) {
|
|
D.assert(builder != null);
|
|
this.builder = builder;
|
|
this.onWillAccept = onWillAccept;
|
|
this.onAccept = onAccept;
|
|
this.onLeave = onLeave;
|
|
}
|
|
|
|
public readonly DragTargetBuilder<T> builder;
|
|
|
|
public readonly DragTargetWillAccept<T> onWillAccept;
|
|
|
|
public readonly DragTargetAccept<T> onAccept;
|
|
|
|
public readonly DragTargetLeave<T> onLeave;
|
|
|
|
public override State createState() {
|
|
return new _DragTargetState<T>();
|
|
}
|
|
}
|
|
|
|
public class _DragTargetState<T> : State<DragTarget<T>> {
|
|
readonly List<_DragAvatar<T>> _candidateAvatars = new List<_DragAvatar<T>>();
|
|
readonly List<_DragAvatar<T>> _rejectedAvatars = new List<_DragAvatar<T>>();
|
|
|
|
public bool didEnter(_DragAvatar<T> avatar) {
|
|
D.assert(!_candidateAvatars.Contains(avatar));
|
|
D.assert(!_rejectedAvatars.Contains(avatar));
|
|
|
|
if (avatar.data is T && (widget.onWillAccept == null || widget.onWillAccept(avatar.data))) {
|
|
setState(() => { _candidateAvatars.Add(avatar); });
|
|
return true;
|
|
}
|
|
|
|
_rejectedAvatars.Add(avatar);
|
|
return false;
|
|
}
|
|
|
|
public void didLeave(_DragAvatar<T> avatar) {
|
|
D.assert(_candidateAvatars.Contains(avatar) || _rejectedAvatars.Contains(avatar));
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
|
|
setState(() => {
|
|
_candidateAvatars.Remove(avatar);
|
|
_rejectedAvatars.Remove(avatar);
|
|
});
|
|
if (widget.onLeave != null) {
|
|
widget.onLeave(avatar.data);
|
|
}
|
|
}
|
|
|
|
public void didDrop(_DragAvatar<T> avatar) {
|
|
D.assert(_candidateAvatars.Contains(avatar));
|
|
if (!mounted) {
|
|
return;
|
|
}
|
|
|
|
setState(() => { _candidateAvatars.Remove(avatar); });
|
|
if (widget.onAccept != null) {
|
|
widget.onAccept(avatar.data);
|
|
}
|
|
}
|
|
|
|
public override Widget build(BuildContext context) {
|
|
D.assert(widget.builder != null);
|
|
return new MetaData(
|
|
metaData: this,
|
|
behavior: HitTestBehavior.translucent,
|
|
child: widget.builder(context, _DragUtils._mapAvatarsToData(_candidateAvatars),
|
|
_DragUtils._mapAvatarsToData(_rejectedAvatars)));
|
|
}
|
|
}
|
|
|
|
|
|
public enum _DragEndKind {
|
|
dropped,
|
|
canceled
|
|
}
|
|
|
|
public delegate void _OnDragEnd(Velocity velocity, Offset offset, bool wasAccepted);
|
|
|
|
|
|
public class _DragAvatar<T> : Drag {
|
|
public _DragAvatar(
|
|
T data,
|
|
OverlayState overlayState,
|
|
Axis? axis = null,
|
|
Offset initialPosition = null,
|
|
Offset dragStartPoint = null,
|
|
Widget feedback = null,
|
|
Offset feedbackOffset = null,
|
|
_OnDragEnd onDragEnd = null
|
|
) {
|
|
if (initialPosition == null) {
|
|
initialPosition = Offset.zero;
|
|
}
|
|
|
|
if (dragStartPoint == null) {
|
|
dragStartPoint = Offset.zero;
|
|
}
|
|
|
|
if (feedbackOffset == null) {
|
|
feedbackOffset = Offset.zero;
|
|
}
|
|
|
|
D.assert(overlayState != null);
|
|
this.overlayState = overlayState;
|
|
this.data = data;
|
|
this.axis = axis;
|
|
this.dragStartPoint = dragStartPoint;
|
|
this.feedback = feedback;
|
|
this.feedbackOffset = feedbackOffset;
|
|
this.onDragEnd = onDragEnd;
|
|
|
|
_entry = new OverlayEntry(_build);
|
|
|
|
this.overlayState.insert(_entry);
|
|
_position = initialPosition;
|
|
updateDrag(initialPosition);
|
|
}
|
|
|
|
public readonly T data;
|
|
|
|
readonly Axis? axis;
|
|
|
|
readonly Offset dragStartPoint;
|
|
|
|
readonly Widget feedback;
|
|
|
|
readonly Offset feedbackOffset;
|
|
|
|
readonly _OnDragEnd onDragEnd;
|
|
|
|
readonly OverlayState overlayState;
|
|
|
|
_DragTargetState<T> _activeTarget;
|
|
|
|
readonly List<_DragTargetState<T>> _enteredTargets = new List<_DragTargetState<T>>();
|
|
|
|
Offset _position;
|
|
|
|
Offset _lastOffset;
|
|
|
|
OverlayEntry _entry;
|
|
|
|
public void update(DragUpdateDetails details) {
|
|
_position += _restrictAxis(details.delta);
|
|
updateDrag(_position);
|
|
}
|
|
|
|
public void end(DragEndDetails details) {
|
|
finishDrag(_DragEndKind.dropped, _restrictVelocityAxis(details.velocity));
|
|
}
|
|
|
|
public void cancel() {
|
|
finishDrag(_DragEndKind.canceled);
|
|
}
|
|
|
|
void updateDrag(Offset globalPosition) {
|
|
_lastOffset = globalPosition - dragStartPoint;
|
|
_entry.markNeedsBuild();
|
|
|
|
HitTestResult result = new HitTestResult();
|
|
WidgetsBinding.instance.hitTest(result, globalPosition + feedbackOffset);
|
|
|
|
List<_DragTargetState<T>> targets = _getDragTargets(result.path);
|
|
|
|
bool listsMatch = false;
|
|
if (targets.Count >= _enteredTargets.Count && _enteredTargets.isNotEmpty()) {
|
|
listsMatch = true;
|
|
List<_DragTargetState<T>>.Enumerator iterator = targets.GetEnumerator();
|
|
for (int i = 0; i < _enteredTargets.Count; i++) {
|
|
iterator.MoveNext();
|
|
if (iterator.Current != _enteredTargets[i]) {
|
|
listsMatch = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (listsMatch) {
|
|
return;
|
|
}
|
|
|
|
_leaveAllEntered();
|
|
|
|
_DragTargetState<T> newTarget = null;
|
|
foreach (var target in targets) {
|
|
_enteredTargets.Add(target);
|
|
if (target.didEnter(this)) {
|
|
newTarget = target;
|
|
break;
|
|
}
|
|
}
|
|
|
|
_activeTarget = newTarget;
|
|
}
|
|
|
|
List<_DragTargetState<T>> _getDragTargets(IList<HitTestEntry> path) {
|
|
List<_DragTargetState<T>> ret = new List<_DragTargetState<T>>();
|
|
|
|
foreach (HitTestEntry entry in path) {
|
|
if (entry.target is RenderMetaData) {
|
|
RenderMetaData renderMetaData = (RenderMetaData) entry.target;
|
|
if (renderMetaData.metaData is _DragTargetState<T>) {
|
|
ret.Add((_DragTargetState<T>) renderMetaData.metaData);
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void _leaveAllEntered() {
|
|
for (int i = 0; i < _enteredTargets.Count; i++) {
|
|
_enteredTargets[i].didLeave(this);
|
|
}
|
|
|
|
_enteredTargets.Clear();
|
|
}
|
|
|
|
void finishDrag(_DragEndKind endKind, Velocity velocity = null) {
|
|
bool wasAccepted = false;
|
|
if (endKind == _DragEndKind.dropped && _activeTarget != null) {
|
|
_activeTarget.didDrop(this);
|
|
wasAccepted = true;
|
|
_enteredTargets.Remove(_activeTarget);
|
|
}
|
|
|
|
_leaveAllEntered();
|
|
_activeTarget = null;
|
|
_entry.remove();
|
|
_entry = null;
|
|
|
|
if (onDragEnd != null) {
|
|
onDragEnd(velocity == null ? Velocity.zero : velocity, _lastOffset, wasAccepted);
|
|
}
|
|
}
|
|
|
|
public Widget _build(BuildContext context) {
|
|
RenderBox box = (RenderBox) overlayState.context.findRenderObject();
|
|
Offset overlayTopLeft = box.localToGlobal(Offset.zero);
|
|
return new Positioned(
|
|
left: _lastOffset.dx - overlayTopLeft.dx,
|
|
top: _lastOffset.dy - overlayTopLeft.dy,
|
|
child: new IgnorePointer(
|
|
child: feedback
|
|
)
|
|
);
|
|
}
|
|
|
|
Velocity _restrictVelocityAxis(Velocity velocity) {
|
|
if (axis == null) {
|
|
return velocity;
|
|
}
|
|
|
|
return new Velocity(
|
|
_restrictAxis(velocity.pixelsPerSecond));
|
|
}
|
|
|
|
Offset _restrictAxis(Offset offset) {
|
|
if (axis == null) {
|
|
return offset;
|
|
}
|
|
|
|
if (axis == Axis.horizontal) {
|
|
return new Offset(offset.dx, 0.0f);
|
|
}
|
|
|
|
return new Offset(0.0f, offset.dy);
|
|
}
|
|
}
|
|
}
|