您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
580 行
19 KiB
580 行
19 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 (this.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();
|
|
this._recognizer = this.widget.createRecognizer(this._startDrag);
|
|
}
|
|
|
|
public override void dispose() {
|
|
this._disposeRecognizerIfInactive();
|
|
base.dispose();
|
|
}
|
|
|
|
GestureRecognizer _recognizer;
|
|
int _activeCount;
|
|
|
|
void _disposeRecognizerIfInactive() {
|
|
if (this._activeCount > 0) {
|
|
return;
|
|
}
|
|
|
|
this._recognizer.dispose();
|
|
this._recognizer = null;
|
|
}
|
|
|
|
|
|
void _routePointer(PointerEvent pEvent) {
|
|
if (this.widget.maxSimultaneousDrags != null &&
|
|
this._activeCount >= this.widget.maxSimultaneousDrags) {
|
|
return;
|
|
}
|
|
|
|
if (pEvent is PointerDownEvent) {
|
|
this._recognizer.addPointer((PointerDownEvent) pEvent);
|
|
}
|
|
}
|
|
|
|
_DragAvatar<T> _startDrag(Offset position) {
|
|
if (this.widget.maxSimultaneousDrags != null &&
|
|
this._activeCount >= this.widget.maxSimultaneousDrags) {
|
|
return null;
|
|
}
|
|
|
|
var dragStartPoint = Offset.zero;
|
|
switch (this.widget.dragAnchor) {
|
|
case DragAnchor.child:
|
|
RenderBox renderObject = this.context.findRenderObject() as RenderBox;
|
|
dragStartPoint = renderObject.globalToLocal(position);
|
|
break;
|
|
case DragAnchor.pointer:
|
|
dragStartPoint = Offset.zero;
|
|
break;
|
|
}
|
|
|
|
this.setState(() => { this._activeCount += 1; });
|
|
|
|
_DragAvatar<T> avatar = new _DragAvatar<T>(
|
|
overlayState: Overlay.of(this.context, debugRequiredFor: this.widget),
|
|
data: this.widget.data,
|
|
axis: this.widget.axis,
|
|
initialPosition: position,
|
|
dragStartPoint: dragStartPoint,
|
|
feedback: this.widget.feedback,
|
|
feedbackOffset: this.widget.feedbackOffset,
|
|
onDragEnd: (Velocity velocity, Offset offset, bool wasAccepted) => {
|
|
if (this.mounted) {
|
|
this.setState(() => { this._activeCount -= 1; });
|
|
}
|
|
else {
|
|
this._activeCount -= 1;
|
|
this._disposeRecognizerIfInactive();
|
|
}
|
|
|
|
if (this.mounted && this.widget.onDragEnd != null) {
|
|
this.widget.onDragEnd(new DraggableDetails(
|
|
wasAccepted: wasAccepted,
|
|
velocity: velocity,
|
|
offset: offset
|
|
));
|
|
}
|
|
|
|
if (wasAccepted && this.widget.onDragCompleted != null) {
|
|
this.widget.onDragCompleted();
|
|
}
|
|
|
|
if (!wasAccepted && this.widget.onDraggableCanceled != null) {
|
|
this.widget.onDraggableCanceled(velocity, offset);
|
|
}
|
|
}
|
|
);
|
|
if (this.widget.onDragStarted != null) {
|
|
this.widget.onDragStarted();
|
|
}
|
|
|
|
return avatar;
|
|
}
|
|
|
|
public override Widget build(BuildContext context) {
|
|
D.assert(Overlay.of(context, debugRequiredFor: this.widget) != null);
|
|
bool canDrag = this.widget.maxSimultaneousDrags == null ||
|
|
this._activeCount < this.widget.maxSimultaneousDrags;
|
|
|
|
bool showChild = this._activeCount == 0 || this.widget.childWhenDragging == null;
|
|
if (canDrag) {
|
|
return new Listener(
|
|
onPointerDown: this._routePointer,
|
|
child: showChild ? this.widget.child : this.widget.childWhenDragging
|
|
);
|
|
}
|
|
|
|
return new Listener(
|
|
child: showChild ? this.widget.child : this.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(!this._candidateAvatars.Contains(avatar));
|
|
D.assert(!this._rejectedAvatars.Contains(avatar));
|
|
|
|
if (avatar.data is T && (this.widget.onWillAccept == null || this.widget.onWillAccept(avatar.data))) {
|
|
this.setState(() => { this._candidateAvatars.Add(avatar); });
|
|
return true;
|
|
}
|
|
|
|
this._rejectedAvatars.Add(avatar);
|
|
return false;
|
|
}
|
|
|
|
public void didLeave(_DragAvatar<T> avatar) {
|
|
D.assert(this._candidateAvatars.Contains(avatar) || this._rejectedAvatars.Contains(avatar));
|
|
if (!this.mounted) {
|
|
return;
|
|
}
|
|
|
|
this.setState(() => {
|
|
this._candidateAvatars.Remove(avatar);
|
|
this._rejectedAvatars.Remove(avatar);
|
|
});
|
|
if (this.widget.onLeave != null) {
|
|
this.widget.onLeave(avatar.data);
|
|
}
|
|
}
|
|
|
|
public void didDrop(_DragAvatar<T> avatar) {
|
|
D.assert(this._candidateAvatars.Contains(avatar));
|
|
if (!this.mounted) {
|
|
return;
|
|
}
|
|
|
|
this.setState(() => { this._candidateAvatars.Remove(avatar); });
|
|
if (this.widget.onAccept != null) {
|
|
this.widget.onAccept(avatar.data);
|
|
}
|
|
}
|
|
|
|
public override Widget build(BuildContext context) {
|
|
D.assert(this.widget.builder != null);
|
|
return new MetaData(
|
|
metaData: this,
|
|
behavior: HitTestBehavior.translucent,
|
|
child: this.widget.builder(context, _DragUtils._mapAvatarsToData(this._candidateAvatars),
|
|
_DragUtils._mapAvatarsToData(this._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;
|
|
|
|
this._entry = new OverlayEntry(this._build);
|
|
|
|
this.overlayState.insert(this._entry);
|
|
this._position = initialPosition;
|
|
this.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) {
|
|
this._position += this._restrictAxis(details.delta);
|
|
this.updateDrag(this._position);
|
|
}
|
|
|
|
public void end(DragEndDetails details) {
|
|
this.finishDrag(_DragEndKind.dropped, this._restrictVelocityAxis(details.velocity));
|
|
}
|
|
|
|
public void cancel() {
|
|
this.finishDrag(_DragEndKind.canceled);
|
|
}
|
|
|
|
void updateDrag(Offset globalPosition) {
|
|
this._lastOffset = globalPosition - this.dragStartPoint;
|
|
this._entry.markNeedsBuild();
|
|
|
|
HitTestResult result = new HitTestResult();
|
|
WidgetsBinding.instance.hitTest(result, globalPosition + this.feedbackOffset);
|
|
|
|
List<_DragTargetState<T>> targets = this._getDragTargets(result.path);
|
|
|
|
bool listsMatch = false;
|
|
if (targets.Count >= this._enteredTargets.Count && this._enteredTargets.isNotEmpty()) {
|
|
listsMatch = true;
|
|
List<_DragTargetState<T>>.Enumerator iterator = targets.GetEnumerator();
|
|
for (int i = 0; i < this._enteredTargets.Count; i++) {
|
|
iterator.MoveNext();
|
|
if (iterator.Current != this._enteredTargets[i]) {
|
|
listsMatch = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (listsMatch) {
|
|
return;
|
|
}
|
|
|
|
this._leaveAllEntered();
|
|
|
|
_DragTargetState<T> newTarget = null;
|
|
foreach (var target in targets) {
|
|
this._enteredTargets.Add(target);
|
|
if (target.didEnter(this)) {
|
|
newTarget = target;
|
|
break;
|
|
}
|
|
}
|
|
|
|
this._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 < this._enteredTargets.Count; i++) {
|
|
this._enteredTargets[i].didLeave(this);
|
|
}
|
|
|
|
this._enteredTargets.Clear();
|
|
}
|
|
|
|
void finishDrag(_DragEndKind endKind, Velocity velocity = null) {
|
|
bool wasAccepted = false;
|
|
if (endKind == _DragEndKind.dropped && this._activeTarget != null) {
|
|
this._activeTarget.didDrop(this);
|
|
wasAccepted = true;
|
|
this._enteredTargets.Remove(this._activeTarget);
|
|
}
|
|
|
|
this._leaveAllEntered();
|
|
this._activeTarget = null;
|
|
this._entry.remove();
|
|
this._entry = null;
|
|
|
|
if (this.onDragEnd != null) {
|
|
this.onDragEnd(velocity == null ? Velocity.zero : velocity, this._lastOffset, wasAccepted);
|
|
}
|
|
}
|
|
|
|
public Widget _build(BuildContext context) {
|
|
RenderBox box = (RenderBox) this.overlayState.context.findRenderObject();
|
|
Offset overlayTopLeft = box.localToGlobal(Offset.zero);
|
|
return new Positioned(
|
|
left: this._lastOffset.dx - overlayTopLeft.dx,
|
|
top: this._lastOffset.dy - overlayTopLeft.dy,
|
|
child: new IgnorePointer(
|
|
child: this.feedback
|
|
)
|
|
);
|
|
}
|
|
|
|
Velocity _restrictVelocityAxis(Velocity velocity) {
|
|
if (this.axis == null) {
|
|
return velocity;
|
|
}
|
|
|
|
return new Velocity(
|
|
this._restrictAxis(velocity.pixelsPerSecond));
|
|
}
|
|
|
|
Offset _restrictAxis(Offset offset) {
|
|
if (this.axis == null) {
|
|
return offset;
|
|
}
|
|
|
|
if (this.axis == Axis.horizontal) {
|
|
return new Offset(offset.dx, 0.0f);
|
|
}
|
|
|
|
return new Offset(0.0f, offset.dy);
|
|
}
|
|
}
|
|
}
|