|
|
|
|
|
|
using System; |
|
|
|
using System.Collections.Generic; |
|
|
|
using Unity.UIWidgets.async; |
|
|
|
using Unity.UIWidgets.foundation; |
|
|
|
|
|
|
public delegate void GestureDoubleTapCallback(DoubleTapDetails details); |
|
|
|
|
|
|
|
public delegate void GestureMultiTapDownCallback(int pointer, TapDownDetails details); |
|
|
|
|
|
|
|
public delegate void GestureMultiTapUpCallback(int pointer, TapUpDetails details); |
|
|
|
|
|
|
|
public delegate void GestureMultiTapCallback(int pointer); |
|
|
|
|
|
|
|
public delegate void GestureMultiTapCancelCallback(int pointer); |
|
|
|
|
|
|
|
public class _CountdownZoned { |
|
|
|
_CountdownZoned(TimeSpan duration) { |
|
|
|
D.assert(duration != null); |
|
|
|
this._timer = Window.instance.run(duration, this._onTimeout); |
|
|
|
} |
|
|
|
|
|
|
|
public bool _timeout = false; |
|
|
|
public Timer _timer; |
|
|
|
|
|
|
|
public bool timeout { |
|
|
|
get { return this._timeout; } |
|
|
|
} |
|
|
|
|
|
|
|
void _onTimeout() { |
|
|
|
this._timeout = true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public class DoubleTapDetails { |
|
|
|
public DoubleTapDetails(Offset firstGlobalPosition = null) { |
|
|
|
this.firstGlobalPosition = firstGlobalPosition ?? Offset.zero; |
|
|
|
|
|
|
|
|
|
|
class _TapTracker { |
|
|
|
internal _TapTracker( |
|
|
|
PointerDownEvent evt = null, |
|
|
|
GestureArenaEntry entry = null) { |
|
|
|
PointerDownEvent evt, |
|
|
|
TimeSpan doubleTapMinTime, |
|
|
|
GestureArenaEntry entry = null |
|
|
|
) { |
|
|
|
this.pointer = evt.pointer; |
|
|
|
this._initialPosition = evt.position; |
|
|
|
this.entry = entry; |
|
|
|
|
|
|
public readonly GestureArenaEntry entry; |
|
|
|
internal readonly Offset _initialPosition; |
|
|
|
internal readonly _CountdownZoned _doubleTapMinTimeCountdown; |
|
|
|
|
|
|
|
bool _isTrackingPointer = false; |
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public void stopTrackingPointer(PointerRoute route) { |
|
|
|
public virtual void stopTrackingPointer(PointerRoute route) { |
|
|
|
if (this._isTrackingPointer) { |
|
|
|
this._isTrackingPointer = false; |
|
|
|
GestureBinding.instance.pointerRouter.removeRoute(this.pointer, route); |
|
|
|
|
|
|
Offset offset = evt.position - this._initialPosition; |
|
|
|
return offset.distance <= tolerance; |
|
|
|
} |
|
|
|
|
|
|
|
public bool hasElapsedMinTime() { |
|
|
|
return this._doubleTapMinTimeCountdown.timeout; |
|
|
|
} |
|
|
|
: base(debugOwner: debugOwner, kind: kind) { |
|
|
|
} |
|
|
|
: base(debugOwner: debugOwner, kind: kind) { } |
|
|
|
|
|
|
|
public GestureDoubleTapCallback onDoubleTap; |
|
|
|
|
|
|
|
|
|
|
this._stopDoubleTapTimer(); |
|
|
|
_TapTracker tracker = new _TapTracker( |
|
|
|
evt: evt, |
|
|
|
entry: GestureBinding.instance.gestureArena.add(evt.pointer, this) |
|
|
|
entry: GestureBinding.instance.gestureArena.add(evt.pointer, this), |
|
|
|
doubleTapMinTime: Constants.kDoubleTapMinTime |
|
|
|
); |
|
|
|
this._trackers[evt.pointer] = tracker; |
|
|
|
tracker.startTrackingPointer(this._handleEvent); |
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public override void acceptGesture(int pointer) { |
|
|
|
} |
|
|
|
public override void acceptGesture(int pointer) { } |
|
|
|
|
|
|
|
public override void rejectGesture(int pointer) { |
|
|
|
_TapTracker tracker; |
|
|
|
|
|
|
foreach (var tracker in this._trackers.Values) { |
|
|
|
this._reject(tracker); |
|
|
|
} |
|
|
|
|
|
|
|
D.assert(this._trackers.isEmpty()); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public override string debugDescription { |
|
|
|
get { return "double tap"; } |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
class _TapGesture : _TapTracker { |
|
|
|
public _TapGesture( |
|
|
|
MultiTapGestureRecognizer gestureRecognizer, |
|
|
|
PointerEvent evt, |
|
|
|
TimeSpan longTapDelay |
|
|
|
) : base( |
|
|
|
evt: (PointerDownEvent) evt, |
|
|
|
entry: GestureBinding.instance.gestureArena.add(evt.pointer, gestureRecognizer), |
|
|
|
doubleTapMinTime: Constants.kDoubleTapMinTime |
|
|
|
) { |
|
|
|
this.gestureRecognizer = gestureRecognizer; |
|
|
|
this._lastPosition = evt.position; |
|
|
|
this.startTrackingPointer(this.handleEvent); |
|
|
|
if (longTapDelay > TimeSpan.Zero) { |
|
|
|
this._timer = Window.instance.run(longTapDelay, () => { |
|
|
|
this._timer = null; |
|
|
|
this.gestureRecognizer._dispatchLongTap(evt.pointer, this._lastPosition); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public readonly MultiTapGestureRecognizer gestureRecognizer; |
|
|
|
|
|
|
|
bool _wonArena = false; |
|
|
|
Timer _timer; |
|
|
|
|
|
|
|
Offset _lastPosition; |
|
|
|
Offset _finalPosition; |
|
|
|
|
|
|
|
void handleEvent(PointerEvent evt) { |
|
|
|
D.assert(evt.pointer == this.pointer); |
|
|
|
if (evt is PointerMoveEvent) { |
|
|
|
if (!this.isWithinTolerance(evt, Constants.kTouchSlop)) { |
|
|
|
this.cancel(); |
|
|
|
} |
|
|
|
else { |
|
|
|
this._lastPosition = evt.position; |
|
|
|
} |
|
|
|
} |
|
|
|
else if (evt is PointerCancelEvent) { |
|
|
|
this.cancel(); |
|
|
|
} |
|
|
|
else if (evt is PointerUpEvent) { |
|
|
|
this.stopTrackingPointer(this.handleEvent); |
|
|
|
this._finalPosition = evt.position; |
|
|
|
this._check(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public override void stopTrackingPointer(PointerRoute route) { |
|
|
|
this._timer?.cancel(); |
|
|
|
this._timer = null; |
|
|
|
base.stopTrackingPointer(route); |
|
|
|
} |
|
|
|
|
|
|
|
public void accept() { |
|
|
|
this._wonArena = true; |
|
|
|
this._check(); |
|
|
|
} |
|
|
|
|
|
|
|
public void reject() { |
|
|
|
this.stopTrackingPointer(this.handleEvent); |
|
|
|
this.gestureRecognizer._dispatchCancel(this.pointer); |
|
|
|
} |
|
|
|
|
|
|
|
public void cancel() { |
|
|
|
if (this._wonArena) { |
|
|
|
this.reject(); |
|
|
|
} |
|
|
|
else { |
|
|
|
this.entry.resolve(GestureDisposition.rejected); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
void _check() { |
|
|
|
if (this._wonArena && this._finalPosition != null) { |
|
|
|
this.gestureRecognizer._dispatchTap(this.pointer, this._finalPosition); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public class MultiTapGestureRecognizer : GestureRecognizer { |
|
|
|
public MultiTapGestureRecognizer( |
|
|
|
TimeSpan? longTapDelay = null, |
|
|
|
object debugOwner = null, |
|
|
|
PointerDeviceKind? kind = null |
|
|
|
) : base(debugOwner: debugOwner, kind: kind) { |
|
|
|
this.longTapDelay = longTapDelay ?? TimeSpan.Zero; |
|
|
|
} |
|
|
|
|
|
|
|
GestureMultiTapDownCallback onTapDown; |
|
|
|
|
|
|
|
GestureMultiTapUpCallback onTapUp; |
|
|
|
|
|
|
|
GestureMultiTapCallback onTap; |
|
|
|
|
|
|
|
GestureMultiTapCancelCallback onTapCancel; |
|
|
|
|
|
|
|
TimeSpan longTapDelay; |
|
|
|
|
|
|
|
GestureMultiTapDownCallback onLongTapDown; |
|
|
|
|
|
|
|
readonly Dictionary<int, _TapGesture> _gestureMap = new Dictionary<int, _TapGesture>(); |
|
|
|
|
|
|
|
public override void addAllowedPointer(PointerDownEvent evt) { |
|
|
|
D.assert(!this._gestureMap.ContainsKey(evt.pointer)); |
|
|
|
this._gestureMap[evt.pointer] = new _TapGesture( |
|
|
|
gestureRecognizer: this, |
|
|
|
evt: evt, |
|
|
|
longTapDelay: this.longTapDelay |
|
|
|
); |
|
|
|
if (this.onTapDown != null) { |
|
|
|
this.invokeCallback<object>("onTapDown", () => { |
|
|
|
this.onTapDown(evt.pointer, new TapDownDetails(globalPosition: evt.position)); |
|
|
|
return null; |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public override void acceptGesture(int pointer) { |
|
|
|
D.assert(this._gestureMap.ContainsKey(pointer)); |
|
|
|
this._gestureMap[pointer].accept(); |
|
|
|
} |
|
|
|
|
|
|
|
public override void rejectGesture(int pointer) { |
|
|
|
D.assert(this._gestureMap.ContainsKey(pointer)); |
|
|
|
this._gestureMap[pointer].reject(); |
|
|
|
D.assert(!this._gestureMap.ContainsKey(pointer)); |
|
|
|
} |
|
|
|
|
|
|
|
public void _dispatchCancel(int pointer) { |
|
|
|
D.assert(this._gestureMap.ContainsKey(pointer)); |
|
|
|
this._gestureMap.Remove(pointer); |
|
|
|
if (this.onTapCancel != null) { |
|
|
|
this.invokeCallback<object>("onTapCancel", () => { |
|
|
|
this.onTapCancel(pointer); |
|
|
|
return null; |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public void _dispatchTap(int pointer, Offset globalPosition) { |
|
|
|
D.assert(this._gestureMap.ContainsKey(pointer)); |
|
|
|
this._gestureMap.Remove(pointer); |
|
|
|
if (this.onTapUp != null) { |
|
|
|
this.invokeCallback<object>("onTapUp", |
|
|
|
() => { |
|
|
|
this.onTapUp(pointer, new TapUpDetails(globalPosition: globalPosition)); |
|
|
|
return null; |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
if (this.onTap != null) { |
|
|
|
this.invokeCallback<object>("onTap", () => { |
|
|
|
this.onTap(pointer); |
|
|
|
return null; |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public void _dispatchLongTap(int pointer, Offset lastPosition) { |
|
|
|
D.assert(this._gestureMap.ContainsKey(pointer)); |
|
|
|
if (this.onLongTapDown != null) { |
|
|
|
this.invokeCallback<object>("onLongTapDown", |
|
|
|
() => { |
|
|
|
this.onLongTapDown(pointer, new TapDownDetails(globalPosition: lastPosition)); |
|
|
|
return null; |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public override void dispose() { |
|
|
|
List<_TapGesture> localGestures = new List<_TapGesture>(); |
|
|
|
foreach (var item in this._gestureMap) { |
|
|
|
localGestures.Add(item.Value); |
|
|
|
} |
|
|
|
|
|
|
|
foreach (_TapGesture gesture in localGestures) { |
|
|
|
gesture.cancel(); |
|
|
|
} |
|
|
|
|
|
|
|
D.assert(this._gestureMap.isEmpty); |
|
|
|
base.dispose(); |
|
|
|
} |
|
|
|
|
|
|
|
public override string debugDescription { |
|
|
|
get { return "multitap"; } |
|
|
|
} |
|
|
|
} |
|
|
|
} |