您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
490 行
16 KiB
490 行
16 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using Unity.UIWidgets.async2;
|
|
using Unity.UIWidgets.foundation;
|
|
using Unity.UIWidgets.ui;
|
|
|
|
namespace Unity.UIWidgets.gestures {
|
|
public delegate void GestureDoubleTapCallback();
|
|
|
|
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);
|
|
|
|
class _CountdownZoned {
|
|
public _CountdownZoned(TimeSpan duration) {
|
|
D.assert(duration != null);
|
|
_timer = Timer.create(duration, _onTimeout);
|
|
}
|
|
|
|
public bool _timeout = false;
|
|
public Timer _timer;
|
|
|
|
public bool timeout {
|
|
get { return _timeout; }
|
|
}
|
|
|
|
void _onTimeout() {
|
|
_timeout = true;
|
|
}
|
|
}
|
|
|
|
public class DoubleTapDetails {
|
|
public DoubleTapDetails(Offset firstGlobalPosition = null) {
|
|
this.firstGlobalPosition = firstGlobalPosition ?? Offset.zero;
|
|
}
|
|
|
|
public readonly Offset firstGlobalPosition;
|
|
}
|
|
|
|
class _TapTracker {
|
|
internal _TapTracker(
|
|
PointerDownEvent evt,
|
|
TimeSpan doubleTapMinTime,
|
|
GestureArenaEntry entry = null
|
|
) {
|
|
D.assert(evt != null);
|
|
pointer = evt.pointer;
|
|
_initialGlobalPosition = evt.position;
|
|
initialButtons = evt.buttons;
|
|
_doubleTapMinTimeCountdown = new _CountdownZoned(duration: doubleTapMinTime);
|
|
this.entry = entry;
|
|
}
|
|
|
|
public readonly int pointer;
|
|
public readonly GestureArenaEntry entry;
|
|
internal readonly Offset _initialGlobalPosition;
|
|
internal readonly int initialButtons;
|
|
internal readonly _CountdownZoned _doubleTapMinTimeCountdown;
|
|
|
|
bool _isTrackingPointer = false;
|
|
|
|
public void startTrackingPointer(PointerRoute route, Matrix4 transform) {
|
|
if (!_isTrackingPointer) {
|
|
_isTrackingPointer = true;
|
|
GestureBinding.instance.pointerRouter.addRoute(pointer, route, transform);
|
|
}
|
|
}
|
|
|
|
public virtual void stopTrackingPointer(PointerRoute route) {
|
|
if (_isTrackingPointer) {
|
|
_isTrackingPointer = false;
|
|
GestureBinding.instance.pointerRouter.removeRoute(pointer, route);
|
|
}
|
|
}
|
|
|
|
public bool isWithinGlobalTolerance(PointerEvent evt, float tolerance) {
|
|
Offset offset = evt.position - _initialGlobalPosition;
|
|
return offset.distance <= tolerance;
|
|
}
|
|
|
|
public bool hasElapsedMinTime() {
|
|
return _doubleTapMinTimeCountdown.timeout;
|
|
}
|
|
|
|
public bool hasSameButton(PointerDownEvent evt) {
|
|
return evt.buttons == initialButtons;
|
|
}
|
|
}
|
|
|
|
|
|
public class DoubleTapGestureRecognizer : GestureRecognizer {
|
|
public DoubleTapGestureRecognizer(object debugOwner = null, PointerDeviceKind? kind = null)
|
|
: base(debugOwner: debugOwner, kind: kind) {
|
|
}
|
|
|
|
public GestureDoubleTapCallback onDoubleTap;
|
|
|
|
protected override bool isPointerAllowed(PointerDownEvent evt) {
|
|
if (_firstTap == null) {
|
|
switch (evt.buttons) {
|
|
case gesture_.kPrimaryButton:
|
|
if (onDoubleTap == null)
|
|
return false;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return base.isPointerAllowed(evt as PointerDownEvent);
|
|
}
|
|
|
|
Timer _doubleTapTimer;
|
|
_TapTracker _firstTap;
|
|
readonly Dictionary<int, _TapTracker> _trackers = new Dictionary<int, _TapTracker>();
|
|
|
|
public override void addAllowedPointer(PointerDownEvent evt) {
|
|
if (_firstTap != null) {
|
|
if (!_firstTap.isWithinGlobalTolerance(evt, Constants.kDoubleTapSlop)) {
|
|
// Ignore out-of-bounds second taps.
|
|
return;
|
|
}
|
|
else if (!_firstTap.hasElapsedMinTime() ||
|
|
!_firstTap.hasSameButton(evt as PointerDownEvent)) {
|
|
// Restart when the second tap is too close to the first, or when buttons
|
|
// mismatch.
|
|
_reset();
|
|
_trackFirstTap(evt);
|
|
return;
|
|
}
|
|
}
|
|
|
|
_trackFirstTap(evt);
|
|
}
|
|
|
|
void _trackFirstTap(PointerEvent evt) {
|
|
_stopDoubleTapTimer();
|
|
_TapTracker tracker = new _TapTracker(
|
|
evt: evt as PointerDownEvent,
|
|
entry: GestureBinding.instance.gestureArena.add(evt.pointer, this),
|
|
doubleTapMinTime: Constants.kDoubleTapMinTime
|
|
);
|
|
_trackers[evt.pointer] = tracker;
|
|
tracker.startTrackingPointer(_handleEvent, evt.transform);
|
|
}
|
|
|
|
void _handleEvent(PointerEvent evt) {
|
|
_TapTracker tracker = _trackers[evt.pointer];
|
|
D.assert(tracker != null);
|
|
if (evt is PointerUpEvent) {
|
|
if (_firstTap == null) {
|
|
_registerFirstTap(tracker);
|
|
}
|
|
else {
|
|
_registerSecondTap(tracker);
|
|
}
|
|
}
|
|
else if (evt is PointerMoveEvent) {
|
|
if (!tracker.isWithinGlobalTolerance(evt, Constants.kDoubleTapTouchSlop)) {
|
|
_reject(tracker);
|
|
}
|
|
}
|
|
else if (evt is PointerCancelEvent) {
|
|
_reject(tracker);
|
|
}
|
|
}
|
|
|
|
public override void acceptGesture(int pointer) {
|
|
}
|
|
|
|
public override void rejectGesture(int pointer) {
|
|
_TapTracker tracker;
|
|
_trackers.TryGetValue(pointer, out tracker);
|
|
|
|
if (tracker == null &&
|
|
_firstTap != null &&
|
|
_firstTap.pointer == pointer) {
|
|
tracker = _firstTap;
|
|
}
|
|
|
|
if (tracker != null) {
|
|
_reject(tracker);
|
|
}
|
|
}
|
|
|
|
void _reject(_TapTracker tracker) {
|
|
_trackers.Remove(tracker.pointer);
|
|
tracker.entry.resolve(GestureDisposition.rejected);
|
|
_freezeTracker(tracker);
|
|
if (_firstTap != null &&
|
|
(_trackers.isEmpty() || tracker == _firstTap)) {
|
|
_reset();
|
|
}
|
|
}
|
|
|
|
public override void dispose() {
|
|
_reset();
|
|
base.dispose();
|
|
}
|
|
|
|
void _reset() {
|
|
_stopDoubleTapTimer();
|
|
if (_firstTap != null) {
|
|
_TapTracker tracker = _firstTap;
|
|
_firstTap = null;
|
|
_reject(tracker);
|
|
GestureBinding.instance.gestureArena.release(tracker.pointer);
|
|
}
|
|
|
|
_clearTrackers();
|
|
}
|
|
|
|
void _registerFirstTap(_TapTracker tracker) {
|
|
_startDoubleTapTimer();
|
|
GestureBinding.instance.gestureArena.hold(tracker.pointer);
|
|
_freezeTracker(tracker);
|
|
_trackers.Remove(tracker.pointer);
|
|
_clearTrackers();
|
|
_firstTap = tracker;
|
|
}
|
|
|
|
void _registerSecondTap(_TapTracker tracker) {
|
|
_firstTap.entry.resolve(GestureDisposition.accepted);
|
|
tracker.entry.resolve(GestureDisposition.accepted);
|
|
_freezeTracker(tracker);
|
|
_trackers.Remove(tracker.pointer);
|
|
_checkUp(tracker.initialButtons);
|
|
|
|
_reset();
|
|
}
|
|
|
|
void _clearTrackers() {
|
|
foreach (var tracker in _trackers.Values) {
|
|
_reject(tracker);
|
|
}
|
|
|
|
D.assert(_trackers.isEmpty());
|
|
}
|
|
|
|
void _freezeTracker(_TapTracker tracker) {
|
|
tracker.stopTrackingPointer(_handleEvent);
|
|
}
|
|
|
|
void _startDoubleTapTimer() {
|
|
_doubleTapTimer =
|
|
_doubleTapTimer
|
|
?? Timer.create(Constants.kDoubleTapTimeout, _reset);
|
|
}
|
|
|
|
void _stopDoubleTapTimer() {
|
|
if (_doubleTapTimer != null) {
|
|
_doubleTapTimer.cancel();
|
|
_doubleTapTimer = null;
|
|
}
|
|
}
|
|
|
|
void _checkUp(int buttons) {
|
|
D.assert(buttons == gesture_.kPrimaryButton);
|
|
if (onDoubleTap != null) {
|
|
invokeCallback("onDoubleTap",() => onDoubleTap);
|
|
}
|
|
}
|
|
|
|
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;
|
|
_lastPosition = OffsetPair.fromEventPosition(evt);
|
|
startTrackingPointer(handleEvent, evt.transform);
|
|
if (longTapDelay > TimeSpan.Zero) {
|
|
_timer = Timer.create(longTapDelay, () => {
|
|
_timer = null;
|
|
this.gestureRecognizer._dispatchLongTap(evt.pointer, _lastPosition);
|
|
});
|
|
}
|
|
}
|
|
|
|
public readonly MultiTapGestureRecognizer gestureRecognizer;
|
|
|
|
bool _wonArena = false;
|
|
Timer _timer;
|
|
|
|
OffsetPair _lastPosition;
|
|
OffsetPair _finalPosition;
|
|
|
|
void handleEvent(PointerEvent evt) {
|
|
D.assert(evt.pointer == pointer);
|
|
if (evt is PointerMoveEvent) {
|
|
if (!isWithinGlobalTolerance(evt, Constants.kTouchSlop)) {
|
|
cancel();
|
|
}
|
|
else {
|
|
_lastPosition = OffsetPair.fromEventPosition(evt);
|
|
}
|
|
}
|
|
else if (evt is PointerCancelEvent) {
|
|
cancel();
|
|
}
|
|
else if (evt is PointerUpEvent) {
|
|
stopTrackingPointer(handleEvent);
|
|
_finalPosition = OffsetPair.fromEventPosition(evt);
|
|
_check();
|
|
}
|
|
}
|
|
|
|
public override void stopTrackingPointer(PointerRoute route) {
|
|
_timer?.cancel();
|
|
_timer = null;
|
|
base.stopTrackingPointer(route);
|
|
}
|
|
|
|
public void accept() {
|
|
_wonArena = true;
|
|
_check();
|
|
}
|
|
|
|
public void reject() {
|
|
stopTrackingPointer(handleEvent);
|
|
gestureRecognizer._dispatchCancel(pointer);
|
|
}
|
|
|
|
public void cancel() {
|
|
if (_wonArena) {
|
|
reject();
|
|
}
|
|
else {
|
|
entry.resolve(GestureDisposition.rejected);
|
|
}
|
|
}
|
|
|
|
void _check() {
|
|
if (_wonArena && _finalPosition != null) {
|
|
gestureRecognizer._dispatchTap(pointer, _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;
|
|
}
|
|
|
|
public GestureMultiTapDownCallback onTapDown;
|
|
|
|
public GestureMultiTapUpCallback onTapUp;
|
|
|
|
public GestureMultiTapCallback onTap;
|
|
|
|
public GestureMultiTapCancelCallback onTapCancel;
|
|
|
|
public TimeSpan longTapDelay;
|
|
|
|
public GestureMultiTapDownCallback onLongTapDown;
|
|
|
|
readonly Dictionary<int, _TapGesture> _gestureMap = new Dictionary<int, _TapGesture>();
|
|
|
|
public override void addAllowedPointer(PointerDownEvent evt) {
|
|
D.assert(!_gestureMap.ContainsKey(evt.pointer));
|
|
_gestureMap[evt.pointer] = new _TapGesture(
|
|
gestureRecognizer: this,
|
|
evt: evt,
|
|
longTapDelay: longTapDelay
|
|
);
|
|
if (onTapDown != null) {
|
|
invokeCallback<object>("onTapDown", () => {
|
|
onTapDown(evt.pointer, new TapDownDetails(globalPosition: evt.position,
|
|
localPosition: evt.localPosition,
|
|
kind: evt.kind
|
|
));
|
|
return null;
|
|
});
|
|
}
|
|
}
|
|
|
|
public override void acceptGesture(int pointer) {
|
|
D.assert(_gestureMap.ContainsKey(pointer));
|
|
_gestureMap[pointer].accept();
|
|
}
|
|
|
|
public override void rejectGesture(int pointer) {
|
|
D.assert(_gestureMap.ContainsKey(pointer));
|
|
_gestureMap[pointer].reject();
|
|
D.assert(!_gestureMap.ContainsKey(pointer));
|
|
}
|
|
|
|
public void _dispatchCancel(int pointer) {
|
|
D.assert(_gestureMap.ContainsKey(pointer));
|
|
_gestureMap.Remove(pointer);
|
|
if (onTapCancel != null) {
|
|
invokeCallback<object>("onTapCancel", () => {
|
|
onTapCancel(pointer);
|
|
return null;
|
|
});
|
|
}
|
|
}
|
|
|
|
public void _dispatchTap(int pointer, OffsetPair position) {
|
|
D.assert(_gestureMap.ContainsKey(pointer));
|
|
_gestureMap.Remove(pointer);
|
|
if (onTapUp != null) {
|
|
invokeCallback<object>("onTapUp",
|
|
() => {
|
|
onTapUp(pointer, new TapUpDetails(
|
|
localPosition: position.local,
|
|
globalPosition: position.global
|
|
));
|
|
return null;
|
|
});
|
|
}
|
|
|
|
if (onTap != null) {
|
|
invokeCallback<object>("onTap", () => {
|
|
onTap(pointer);
|
|
return null;
|
|
});
|
|
}
|
|
}
|
|
|
|
public void _dispatchLongTap(int pointer, OffsetPair lastPosition) {
|
|
D.assert(_gestureMap.ContainsKey(pointer));
|
|
if (onLongTapDown != null) {
|
|
invokeCallback<object>("onLongTapDown",
|
|
() => {
|
|
onLongTapDown(pointer, new TapDownDetails(
|
|
globalPosition: lastPosition.global,
|
|
localPosition: lastPosition.local
|
|
));
|
|
return null;
|
|
});
|
|
}
|
|
}
|
|
|
|
public override void dispose() {
|
|
List<_TapGesture> localGestures = new List<_TapGesture>();
|
|
foreach (var item in _gestureMap) {
|
|
localGestures.Add(item.Value);
|
|
}
|
|
|
|
foreach (_TapGesture gesture in localGestures) {
|
|
gesture.cancel();
|
|
}
|
|
|
|
D.assert(_gestureMap.isEmpty);
|
|
base.dispose();
|
|
}
|
|
|
|
public override string debugDescription {
|
|
get { return "multitap"; }
|
|
}
|
|
}
|
|
|
|
delegate bool _RouteEntryPredicate(_RouteEntry entry);
|
|
|
|
class _RouteEntry {
|
|
public _RouteEntry(
|
|
PointerRoute route,
|
|
Matrix4 transform
|
|
) {
|
|
this.route = route;
|
|
this.transform = transform;
|
|
}
|
|
|
|
public readonly PointerRoute route;
|
|
public readonly Matrix4 transform;
|
|
|
|
static _RouteEntryPredicate isRoutePredicate(PointerRoute route) {
|
|
return (_RouteEntry entry) => entry.route == route;
|
|
}
|
|
}
|
|
}
|