mirror of
https://github.com/rustdesk/rustdesk.git
synced 2024-11-30 01:02:49 +08:00
816 lines
24 KiB
Dart
816 lines
24 KiB
Dart
import 'dart:async';
|
||
import 'package:flutter/gestures.dart';
|
||
import 'package:flutter/widgets.dart';
|
||
|
||
enum CustomTouchGestureState {
|
||
none,
|
||
oneFingerPan,
|
||
twoFingerScale,
|
||
twoFingerVerticalDrag,
|
||
twoFingerHorizontalDrag,
|
||
twoFingerPan
|
||
}
|
||
|
||
const kScaleSlop = kPrecisePointerPanSlop / 10;
|
||
|
||
class CustomTouchGestureRecognizer extends ScaleGestureRecognizer {
|
||
CustomTouchGestureRecognizer({
|
||
Object? debugOwner,
|
||
Set<PointerDeviceKind>? supportedDevices,
|
||
}) : super(
|
||
debugOwner: debugOwner,
|
||
supportedDevices: supportedDevices,
|
||
) {
|
||
_init();
|
||
}
|
||
|
||
// oneFingerPan
|
||
GestureDragStartCallback? onOneFingerPanStart;
|
||
GestureDragUpdateCallback? onOneFingerPanUpdate;
|
||
GestureDragEndCallback? onOneFingerPanEnd;
|
||
|
||
// twoFingerScale
|
||
GestureScaleStartCallback? onTwoFingerScaleStart;
|
||
GestureScaleUpdateCallback? onTwoFingerScaleUpdate;
|
||
GestureScaleEndCallback? onTwoFingerScaleEnd;
|
||
|
||
// twoFingerVerticalDrag
|
||
GestureDragStartCallback? onTwoFingerVerticalDragStart;
|
||
GestureDragUpdateCallback? onTwoFingerVerticalDragUpdate;
|
||
GestureDragEndCallback? onTwoFingerVerticalDragEnd;
|
||
|
||
// twoFingerHorizontalDrag
|
||
GestureDragStartCallback? onTwoFingerHorizontalDragStart;
|
||
GestureDragUpdateCallback? onTwoFingerHorizontalDragUpdate;
|
||
GestureDragEndCallback? onTwoFingerHorizontalDragEnd;
|
||
|
||
// twoFingerPan
|
||
GestureDragStartCallback? onTwoFingerPanStart;
|
||
GestureDragUpdateCallback? onTwoFingerPanUpdate;
|
||
GestureDragEndCallback? onTwoFingerPanEnd;
|
||
|
||
void _init() {
|
||
debugPrint("CustomTouchGestureRecognizer init");
|
||
onStart = (d) {
|
||
if (d.pointerCount == 1) {
|
||
_currentState = CustomTouchGestureState.oneFingerPan;
|
||
if (onOneFingerPanStart != null) {
|
||
onOneFingerPanStart!(DragStartDetails(
|
||
localPosition: d.localFocalPoint, globalPosition: d.focalPoint));
|
||
}
|
||
debugPrint("start pan");
|
||
} else if (d.pointerCount == 2) {
|
||
_currentState = CustomTouchGestureState.none;
|
||
startWatchTimer();
|
||
} else {
|
||
_currentState = CustomTouchGestureState.none;
|
||
_reset();
|
||
}
|
||
};
|
||
onUpdate = (d) {
|
||
if (_isWatch) {
|
||
_updateCompute(d);
|
||
return;
|
||
}
|
||
if (_currentState != CustomTouchGestureState.none) {
|
||
switch (_currentState) {
|
||
case CustomTouchGestureState.oneFingerPan:
|
||
if (onOneFingerPanUpdate != null) {
|
||
onOneFingerPanUpdate!(_getDragUpdateDetails(d));
|
||
}
|
||
break;
|
||
case CustomTouchGestureState.twoFingerScale:
|
||
if (onTwoFingerScaleUpdate != null) {
|
||
onTwoFingerScaleUpdate!(d);
|
||
}
|
||
break;
|
||
case CustomTouchGestureState.twoFingerHorizontalDrag:
|
||
if (onTwoFingerHorizontalDragUpdate != null) {
|
||
onTwoFingerHorizontalDragUpdate!(_getDragUpdateDetails(d));
|
||
}
|
||
break;
|
||
case CustomTouchGestureState.twoFingerVerticalDrag:
|
||
if (onTwoFingerVerticalDragUpdate != null) {
|
||
onTwoFingerVerticalDragUpdate!(_getDragUpdateDetails(d));
|
||
}
|
||
break;
|
||
case CustomTouchGestureState.twoFingerPan:
|
||
if (onTwoFingerPanUpdate != null) {
|
||
onTwoFingerPanUpdate!(_getDragUpdateDetails(d));
|
||
}
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
return;
|
||
}
|
||
};
|
||
onEnd = (d) {
|
||
debugPrint("ScaleGestureRecognizer onEnd");
|
||
// end
|
||
switch (_currentState) {
|
||
case CustomTouchGestureState.oneFingerPan:
|
||
debugPrint("TwoFingerState.pan onEnd");
|
||
if (onOneFingerPanEnd != null) {
|
||
onOneFingerPanEnd!(_getDragEndDetails(d));
|
||
}
|
||
break;
|
||
case CustomTouchGestureState.twoFingerScale:
|
||
debugPrint("TwoFingerState.scale onEnd");
|
||
if (onTwoFingerScaleEnd != null) {
|
||
onTwoFingerScaleEnd!(d);
|
||
}
|
||
break;
|
||
case CustomTouchGestureState.twoFingerHorizontalDrag:
|
||
debugPrint("TwoFingerState.horizontal onEnd");
|
||
if (onTwoFingerHorizontalDragEnd != null) {
|
||
onTwoFingerHorizontalDragEnd!(_getDragEndDetails(d));
|
||
}
|
||
break;
|
||
case CustomTouchGestureState.twoFingerVerticalDrag:
|
||
debugPrint("TwoFingerState.vertical onEnd");
|
||
if (onTwoFingerVerticalDragEnd != null) {
|
||
onTwoFingerVerticalDragEnd!(_getDragEndDetails(d));
|
||
}
|
||
break;
|
||
case CustomTouchGestureState.twoFingerPan:
|
||
debugPrint("TwoFingerState.twoFingerPan onEnd");
|
||
if (onTwoFingerPanEnd != null) {
|
||
onTwoFingerPanEnd!(_getDragEndDetails(d));
|
||
}
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
_currentState = CustomTouchGestureState.none;
|
||
_reset();
|
||
};
|
||
}
|
||
|
||
var _currentState = CustomTouchGestureState.none;
|
||
var _isWatch = false;
|
||
|
||
Timer? _timer;
|
||
double _sumScale = 0;
|
||
double _sumVertical = 0;
|
||
double _sumHorizontal = 0;
|
||
|
||
void _clearSum() {
|
||
_sumScale = 0;
|
||
_sumVertical = 0;
|
||
_sumHorizontal = 0;
|
||
}
|
||
|
||
void _reset() {
|
||
_isWatch = false;
|
||
_clearSum();
|
||
if (_timer != null) _timer!.cancel();
|
||
}
|
||
|
||
void _updateCompute(ScaleUpdateDetails d) {
|
||
_sumScale += d.scale - 1;
|
||
_sumHorizontal += d.focalPointDelta.dx;
|
||
_sumVertical += d.focalPointDelta.dy;
|
||
// start
|
||
if (_sumScale.abs() > kScaleSlop) {
|
||
debugPrint("start Scale");
|
||
_currentState = CustomTouchGestureState.twoFingerScale;
|
||
if (onTwoFingerScaleStart != null) {
|
||
onTwoFingerScaleStart!(ScaleStartDetails(
|
||
localFocalPoint: d.localFocalPoint, focalPoint: d.focalPoint));
|
||
}
|
||
_reset();
|
||
} else if (_sumVertical.abs() > kPrecisePointerPanSlop &&
|
||
_sumHorizontal.abs() < kPrecisePointerHitSlop) {
|
||
debugPrint("start Vertical");
|
||
if (onTwoFingerVerticalDragStart != null) {
|
||
_getDragStartDetails(d);
|
||
}
|
||
_currentState = CustomTouchGestureState.twoFingerVerticalDrag;
|
||
_reset();
|
||
} else if ((_sumHorizontal.abs() + _sumVertical.abs()) >
|
||
kPrecisePointerPanSlop) {
|
||
debugPrint("start TwoFingerPan");
|
||
_currentState = CustomTouchGestureState.twoFingerPan;
|
||
if (onTwoFingerPanStart != null) {
|
||
onTwoFingerPanStart!(_getDragStartDetails(d));
|
||
}
|
||
_reset();
|
||
}
|
||
}
|
||
|
||
void startWatchTimer() {
|
||
debugPrint("startWatchTimer");
|
||
_isWatch = true;
|
||
_clearSum();
|
||
if (_timer != null) _timer!.cancel();
|
||
_timer = Timer(const Duration(milliseconds: 200), _reset);
|
||
}
|
||
|
||
DragStartDetails _getDragStartDetails(ScaleUpdateDetails d) =>
|
||
DragStartDetails(
|
||
globalPosition: d.focalPoint,
|
||
localPosition: d.localFocalPoint,
|
||
);
|
||
|
||
DragUpdateDetails _getDragUpdateDetails(ScaleUpdateDetails d) =>
|
||
DragUpdateDetails(
|
||
globalPosition: d.focalPoint,
|
||
localPosition: d.localFocalPoint,
|
||
delta: d.focalPointDelta);
|
||
|
||
DragEndDetails _getDragEndDetails(ScaleEndDetails d) =>
|
||
DragEndDetails(velocity: d.velocity);
|
||
}
|
||
|
||
class HoldTapMoveGestureRecognizer extends GestureRecognizer {
|
||
HoldTapMoveGestureRecognizer({
|
||
Object? debugOwner,
|
||
Set<PointerDeviceKind>? supportedDevices,
|
||
}) : super(
|
||
debugOwner: debugOwner,
|
||
supportedDevices: supportedDevices,
|
||
);
|
||
|
||
GestureDragStartCallback? onHoldDragStart;
|
||
GestureDragUpdateCallback? onHoldDragUpdate;
|
||
GestureDragDownCallback? onHoldDragDown;
|
||
GestureDragCancelCallback? onHoldDragCancel;
|
||
|
||
bool _isStart = false;
|
||
|
||
Timer? _firstTapUpTimer; // 第一次点击后的计时 超时未等到第二次操作则reject
|
||
Timer? _secondTapDownTimer; // 第二次点击后的计时 期间内有其他的操作则reject 超时则判定成功 drag update
|
||
_TapTracker? _firstTap;
|
||
_TapTracker? _secondTap;
|
||
|
||
final Map<int, _TapTracker> _trackers = <int, _TapTracker>{};
|
||
|
||
@override
|
||
bool isPointerAllowed(PointerDownEvent event) {
|
||
if (_firstTap == null) {
|
||
switch (event.buttons) {
|
||
case kPrimaryButton:
|
||
if (onHoldDragStart == null &&
|
||
onHoldDragUpdate == null &&
|
||
onHoldDragCancel == null) {
|
||
return false;
|
||
}
|
||
break;
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
return super.isPointerAllowed(event);
|
||
}
|
||
|
||
@override
|
||
void addAllowedPointer(PointerDownEvent event) {
|
||
// 检测按下事件
|
||
if (_firstTap != null) {
|
||
if (!_firstTap!.isWithinGlobalTolerance(event, kDoubleTapSlop)) {
|
||
// Ignore out-of-bounds second taps.
|
||
return;
|
||
} else if (!_firstTap!.hasElapsedMinTime() ||
|
||
!_firstTap!.hasSameButton(event)) {
|
||
// Restart when the second tap is too close to the first (touch screens
|
||
// often detect touches intermittently), or when buttons mismatch.
|
||
_reset();
|
||
return _trackTap(event);
|
||
} else if (onHoldDragDown != null) {
|
||
invokeCallback<void>(
|
||
'onHoldDragDown',
|
||
() => onHoldDragDown!(DragDownDetails(
|
||
globalPosition: event.position,
|
||
localPosition: event.localPosition)));
|
||
}
|
||
}
|
||
_trackTap(event); // 捕捉第一次tap
|
||
}
|
||
|
||
void _trackTap(PointerDownEvent event) {
|
||
_stopFirstTapUpTimer();
|
||
_stopSecondTapDownTimer();
|
||
final _TapTracker tracker = _TapTracker(
|
||
event: event,
|
||
entry: GestureBinding.instance!.gestureArena.add(event.pointer, this),
|
||
doubleTapMinTime: kDoubleTapMinTime,
|
||
gestureSettings: gestureSettings,
|
||
);
|
||
_trackers[event.pointer] = tracker;
|
||
tracker.startTrackingPointer(_handleEvent, event.transform);
|
||
}
|
||
|
||
// 实际的逻辑应该是第二次down后一段时间没有抬起则表示start 刚好是双击取反
|
||
void _handleEvent(PointerEvent event) {
|
||
final _TapTracker tracker = _trackers[event.pointer]!;
|
||
if (event is PointerUpEvent) {
|
||
if (_firstTap == null && _secondTap == null) {
|
||
_registerFirstTap(tracker);
|
||
} else {
|
||
// 检测到其他的抬起事件则取消
|
||
_reject(tracker);
|
||
}
|
||
} else if (event is PointerDownEvent) {
|
||
if (_firstTap != null && _secondTap == null) {
|
||
_registerSecondTap(tracker);
|
||
}
|
||
} else if (event is PointerMoveEvent) {
|
||
// 检测到first tap move 则取消,检测到second tap move且已经通过竞技场则update
|
||
if (!tracker.isWithinGlobalTolerance(event, kDoubleTapTouchSlop)) {
|
||
if (_firstTap != null && _firstTap!.pointer == event.pointer) {
|
||
// first tap move
|
||
_reject(tracker);
|
||
} else if (_secondTap != null && _secondTap!.pointer == event.pointer) {
|
||
// debugPrint("_secondTap move");
|
||
// second tap move
|
||
if (!_isStart) {
|
||
_resolve();
|
||
}
|
||
if (onHoldDragUpdate != null)
|
||
onHoldDragUpdate!(DragUpdateDetails(
|
||
globalPosition: event.position,
|
||
localPosition: event.localPosition,
|
||
delta: event.delta));
|
||
}
|
||
}
|
||
} else if (event is PointerCancelEvent) {
|
||
_reject(tracker);
|
||
}
|
||
}
|
||
|
||
@override
|
||
void acceptGesture(int pointer) {}
|
||
|
||
@override
|
||
void rejectGesture(int pointer) {
|
||
_TapTracker? tracker = _trackers[pointer];
|
||
// If tracker isn't in the list, check if this is the first tap tracker
|
||
if (tracker == null && _firstTap != null && _firstTap!.pointer == pointer) {
|
||
tracker = _firstTap;
|
||
}
|
||
// If tracker is still null, we rejected ourselves already
|
||
if (tracker != null) {
|
||
_reject(tracker);
|
||
}
|
||
}
|
||
|
||
void _resolve() {
|
||
_stopSecondTapDownTimer();
|
||
_firstTap?.entry.resolve(GestureDisposition.accepted);
|
||
_secondTap?.entry.resolve(GestureDisposition.accepted);
|
||
_isStart = true;
|
||
// TODO start details
|
||
if (onHoldDragStart != null) onHoldDragStart!(DragStartDetails());
|
||
}
|
||
|
||
void _reject(_TapTracker tracker) {
|
||
_checkCancel();
|
||
|
||
_isStart = false;
|
||
_trackers.remove(tracker.pointer);
|
||
tracker.entry.resolve(GestureDisposition.rejected);
|
||
_freezeTracker(tracker);
|
||
_reset();
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_reset();
|
||
super.dispose();
|
||
}
|
||
|
||
void _reset() {
|
||
_isStart = false;
|
||
// debugPrint("reset");
|
||
_stopFirstTapUpTimer();
|
||
_stopSecondTapDownTimer();
|
||
if (_firstTap != null) {
|
||
if (_trackers.isNotEmpty) {
|
||
_checkCancel();
|
||
}
|
||
// Note, order is important below in order for the resolve -> reject logic
|
||
// to work properly.
|
||
final _TapTracker tracker = _firstTap!;
|
||
_firstTap = null;
|
||
_reject(tracker);
|
||
GestureBinding.instance!.gestureArena.release(tracker.pointer);
|
||
|
||
if (_secondTap != null) {
|
||
final _TapTracker tracker = _secondTap!;
|
||
_secondTap = null;
|
||
_reject(tracker);
|
||
GestureBinding.instance!.gestureArena.release(tracker.pointer);
|
||
}
|
||
}
|
||
// TODO 正确的释放资源
|
||
_firstTap = null;
|
||
_secondTap = null;
|
||
_clearTrackers();
|
||
}
|
||
|
||
void _registerFirstTap(_TapTracker tracker) {
|
||
_startFirstTapUpTimer();
|
||
GestureBinding.instance!.gestureArena.hold(tracker.pointer);
|
||
// Note, order is important below in order for the clear -> reject logic to
|
||
// work properly.
|
||
_freezeTracker(tracker);
|
||
_trackers.remove(tracker.pointer);
|
||
_firstTap = tracker;
|
||
}
|
||
|
||
void _registerSecondTap(_TapTracker tracker) {
|
||
// 清除first tap的状态
|
||
if (_firstTap != null) {
|
||
_stopFirstTapUpTimer();
|
||
_freezeTracker(_firstTap!);
|
||
_firstTap = null;
|
||
}
|
||
|
||
_startSecondTapDownTimer();
|
||
GestureBinding.instance!.gestureArena.hold(tracker.pointer);
|
||
|
||
_secondTap = tracker;
|
||
|
||
// TODO
|
||
}
|
||
|
||
void _clearTrackers() {
|
||
_trackers.values.toList().forEach(_reject);
|
||
assert(_trackers.isEmpty);
|
||
}
|
||
|
||
void _freezeTracker(_TapTracker tracker) {
|
||
tracker.stopTrackingPointer(_handleEvent);
|
||
}
|
||
|
||
void _startFirstTapUpTimer() {
|
||
_firstTapUpTimer ??= Timer(kDoubleTapTimeout, _reset);
|
||
}
|
||
|
||
void _startSecondTapDownTimer() {
|
||
_secondTapDownTimer ??= Timer(kDoubleTapTimeout, _resolve);
|
||
}
|
||
|
||
void _stopFirstTapUpTimer() {
|
||
if (_firstTapUpTimer != null) {
|
||
_firstTapUpTimer!.cancel();
|
||
_firstTapUpTimer = null;
|
||
}
|
||
}
|
||
|
||
void _stopSecondTapDownTimer() {
|
||
if (_secondTapDownTimer != null) {
|
||
_secondTapDownTimer!.cancel();
|
||
_secondTapDownTimer = null;
|
||
}
|
||
}
|
||
|
||
void _checkCancel() {
|
||
if (onHoldDragCancel != null) {
|
||
invokeCallback<void>('onHoldDragCancel', onHoldDragCancel!);
|
||
}
|
||
}
|
||
|
||
@override
|
||
String get debugDescription => 'double tap';
|
||
}
|
||
|
||
class DoubleFinerTapGestureRecognizer extends GestureRecognizer {
|
||
DoubleFinerTapGestureRecognizer({
|
||
Object? debugOwner,
|
||
Set<PointerDeviceKind>? supportedDevices,
|
||
}) : super(
|
||
debugOwner: debugOwner,
|
||
supportedDevices: supportedDevices,
|
||
);
|
||
|
||
GestureTapDownCallback? onDoubleFinerTapDown;
|
||
GestureTapDownCallback? onDoubleFinerTap;
|
||
GestureTapCancelCallback? onDoubleFinerTapCancel;
|
||
|
||
Timer? _firstTapTimer; // 第一次点击后的计时 超时未等到第二次操作则reject
|
||
_TapTracker? _firstTap;
|
||
|
||
var _isStart = false;
|
||
|
||
final Set<int> _upTap = {};
|
||
|
||
final Map<int, _TapTracker> _trackers = <int, _TapTracker>{};
|
||
|
||
@override
|
||
bool isPointerAllowed(PointerDownEvent event) {
|
||
if (_firstTap == null) {
|
||
switch (event.buttons) {
|
||
case kPrimaryButton:
|
||
if (onDoubleFinerTapDown == null &&
|
||
onDoubleFinerTap == null &&
|
||
onDoubleFinerTapCancel == null) {
|
||
return false;
|
||
}
|
||
break;
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
return super.isPointerAllowed(event);
|
||
}
|
||
|
||
@override
|
||
void addAllowedPointer(PointerDownEvent event) {
|
||
// 检测按下事件
|
||
debugPrint("addAllowedPointer");
|
||
if (_isStart) {
|
||
// second
|
||
if (onDoubleFinerTapDown != null) {
|
||
final TapDownDetails details = TapDownDetails(
|
||
globalPosition: event.position,
|
||
localPosition: event.localPosition,
|
||
kind: getKindForPointer(event.pointer),
|
||
);
|
||
invokeCallback<void>(
|
||
'onDoubleFinerTapDown', () => onDoubleFinerTapDown!(details));
|
||
}
|
||
} else {
|
||
// first tap
|
||
_isStart = true;
|
||
_startFirstTapDownTimer();
|
||
}
|
||
_trackTap(event); // 捕捉tap
|
||
}
|
||
|
||
void _trackTap(PointerDownEvent event) {
|
||
final _TapTracker tracker = _TapTracker(
|
||
event: event,
|
||
entry: GestureBinding.instance!.gestureArena.add(event.pointer, this),
|
||
doubleTapMinTime: kDoubleTapMinTime,
|
||
gestureSettings: gestureSettings,
|
||
);
|
||
_trackers[event.pointer] = tracker;
|
||
// debugPrint("_trackers:$_trackers");
|
||
tracker.startTrackingPointer(_handleEvent, event.transform);
|
||
|
||
_registerTap(tracker);
|
||
}
|
||
|
||
// 实际的逻辑应该是第二次down后一段时间没有抬起则表示start 刚好是双击取反
|
||
void _handleEvent(PointerEvent event) {
|
||
final _TapTracker tracker = _trackers[event.pointer]!;
|
||
if (event is PointerUpEvent) {
|
||
debugPrint("PointerUpEvent");
|
||
_upTap.add(tracker.pointer);
|
||
} else if (event is PointerMoveEvent) {
|
||
if (!tracker.isWithinGlobalTolerance(event, kDoubleTapTouchSlop))
|
||
_reject(tracker);
|
||
} else if (event is PointerCancelEvent) {
|
||
_reject(tracker);
|
||
}
|
||
}
|
||
|
||
@override
|
||
void acceptGesture(int pointer) {}
|
||
|
||
@override
|
||
void rejectGesture(int pointer) {
|
||
_TapTracker? tracker = _trackers[pointer];
|
||
// If tracker isn't in the list, check if this is the first tap tracker
|
||
if (tracker == null && _firstTap != null && _firstTap!.pointer == pointer) {
|
||
tracker = _firstTap;
|
||
}
|
||
// If tracker is still null, we rejected ourselves already
|
||
if (tracker != null) {
|
||
_reject(tracker);
|
||
}
|
||
}
|
||
|
||
void _reject(_TapTracker tracker) {
|
||
_trackers.remove(tracker.pointer);
|
||
tracker.entry.resolve(GestureDisposition.rejected);
|
||
_freezeTracker(tracker);
|
||
if (_firstTap != null) {
|
||
if (tracker == _firstTap) {
|
||
_reset();
|
||
} else {
|
||
_checkCancel();
|
||
if (_trackers.isEmpty) {
|
||
_reset();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_reset();
|
||
super.dispose();
|
||
}
|
||
|
||
void _reset() {
|
||
_stopFirstTapUpTimer();
|
||
_firstTap = null;
|
||
// TODO 正确的释放资源
|
||
_clearTrackers();
|
||
}
|
||
|
||
void _registerTap(_TapTracker tracker) {
|
||
GestureBinding.instance!.gestureArena.hold(tracker.pointer);
|
||
// Note, order is important below in order for the clear -> reject logic to
|
||
// work properly.
|
||
}
|
||
|
||
void _clearTrackers() {
|
||
_trackers.values.toList().forEach(_reject);
|
||
assert(_trackers.isEmpty);
|
||
}
|
||
|
||
void _freezeTracker(_TapTracker tracker) {
|
||
tracker.stopTrackingPointer(_handleEvent);
|
||
}
|
||
|
||
void _startFirstTapDownTimer() {
|
||
_firstTapTimer ??= Timer(kDoubleTapTimeout, _timeoutCheck);
|
||
}
|
||
|
||
void _stopFirstTapUpTimer() {
|
||
if (_firstTapTimer != null) {
|
||
_firstTapTimer!.cancel();
|
||
_firstTapTimer = null;
|
||
}
|
||
}
|
||
|
||
void _timeoutCheck() {
|
||
_isStart = false;
|
||
if (_upTap.length == 2) {
|
||
_resolve();
|
||
} else {
|
||
_reset();
|
||
}
|
||
_upTap.clear();
|
||
}
|
||
|
||
void _resolve() {
|
||
// TODO tap down details
|
||
if (onDoubleFinerTap != null) onDoubleFinerTap!(TapDownDetails());
|
||
_trackers.forEach((key, value) {
|
||
value.entry.resolve(GestureDisposition.accepted);
|
||
});
|
||
_reset();
|
||
}
|
||
|
||
void _checkCancel() {
|
||
if (onDoubleFinerTapCancel != null) {
|
||
invokeCallback<void>('onHoldDragCancel', onDoubleFinerTapCancel!);
|
||
}
|
||
}
|
||
|
||
@override
|
||
String get debugDescription => 'double tap';
|
||
}
|
||
|
||
/// TapTracker helps track individual tap sequences as part of a
|
||
/// larger gesture.
|
||
class _TapTracker {
|
||
_TapTracker({
|
||
required PointerDownEvent event,
|
||
required this.entry,
|
||
required Duration doubleTapMinTime,
|
||
required this.gestureSettings,
|
||
}) : assert(doubleTapMinTime != null),
|
||
assert(event != null),
|
||
assert(event.buttons != null),
|
||
pointer = event.pointer,
|
||
_initialGlobalPosition = event.position,
|
||
initialButtons = event.buttons,
|
||
_doubleTapMinTimeCountdown =
|
||
_CountdownZoned(duration: doubleTapMinTime);
|
||
|
||
final DeviceGestureSettings? gestureSettings;
|
||
final int pointer;
|
||
final GestureArenaEntry entry;
|
||
final Offset _initialGlobalPosition;
|
||
final int initialButtons;
|
||
final _CountdownZoned _doubleTapMinTimeCountdown;
|
||
|
||
bool _isTrackingPointer = false;
|
||
|
||
void startTrackingPointer(PointerRoute route, Matrix4? transform) {
|
||
if (!_isTrackingPointer) {
|
||
_isTrackingPointer = true;
|
||
GestureBinding.instance!.pointerRouter
|
||
.addRoute(pointer, route, transform);
|
||
}
|
||
}
|
||
|
||
void stopTrackingPointer(PointerRoute route) {
|
||
if (_isTrackingPointer) {
|
||
_isTrackingPointer = false;
|
||
GestureBinding.instance!.pointerRouter.removeRoute(pointer, route);
|
||
}
|
||
}
|
||
|
||
bool isWithinGlobalTolerance(PointerEvent event, double tolerance) {
|
||
final Offset offset = event.position - _initialGlobalPosition;
|
||
return offset.distance <= tolerance;
|
||
}
|
||
|
||
bool hasElapsedMinTime() {
|
||
return _doubleTapMinTimeCountdown.timeout;
|
||
}
|
||
|
||
bool hasSameButton(PointerDownEvent event) {
|
||
return event.buttons == initialButtons;
|
||
}
|
||
}
|
||
|
||
/// CountdownZoned tracks whether the specified duration has elapsed since
|
||
/// creation, honoring [Zone].
|
||
class _CountdownZoned {
|
||
_CountdownZoned({required Duration duration}) : assert(duration != null) {
|
||
Timer(duration, _onTimeout);
|
||
}
|
||
|
||
bool _timeout = false;
|
||
|
||
bool get timeout => _timeout;
|
||
|
||
void _onTimeout() {
|
||
_timeout = true;
|
||
}
|
||
}
|
||
|
||
RawGestureDetector getMixinGestureDetector({
|
||
Widget? child,
|
||
GestureTapUpCallback? onTapUp,
|
||
GestureTapDownCallback? onDoubleTapDown,
|
||
GestureDoubleTapCallback? onDoubleTap,
|
||
GestureLongPressDownCallback? onLongPressDown,
|
||
GestureLongPressCallback? onLongPress,
|
||
GestureDragStartCallback? onHoldDragStart,
|
||
GestureDragUpdateCallback? onHoldDragUpdate,
|
||
GestureDragCancelCallback? onHoldDragCancel,
|
||
GestureTapDownCallback? onDoubleFinerTap,
|
||
GestureDragStartCallback? onOneFingerPanStart,
|
||
GestureDragUpdateCallback? onOneFingerPanUpdate,
|
||
GestureDragEndCallback? onOneFingerPanEnd,
|
||
GestureScaleUpdateCallback? onTwoFingerScaleUpdate,
|
||
GestureScaleEndCallback? onTwoFingerScaleEnd,
|
||
GestureDragUpdateCallback? onTwoFingerHorizontalDragUpdate,
|
||
GestureDragUpdateCallback? onTwoFingerVerticalDragUpdate,
|
||
GestureDragStartCallback? onTwoFingerPanStart,
|
||
GestureDragUpdateCallback? onTwoFingerPanUpdate,
|
||
}) {
|
||
return RawGestureDetector(
|
||
child: child,
|
||
gestures: <Type, GestureRecognizerFactory>{
|
||
// Official
|
||
TapGestureRecognizer:
|
||
GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
|
||
() => TapGestureRecognizer(), (instance) {
|
||
instance.onTapUp = onTapUp;
|
||
}),
|
||
DoubleTapGestureRecognizer:
|
||
GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(
|
||
() => DoubleTapGestureRecognizer(), (instance) {
|
||
instance
|
||
..onDoubleTapDown = onDoubleTapDown
|
||
..onDoubleTap = onDoubleTap;
|
||
}),
|
||
LongPressGestureRecognizer:
|
||
GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
|
||
() => LongPressGestureRecognizer(), (instance) {
|
||
instance
|
||
..onLongPressDown = onLongPressDown
|
||
..onLongPress = onLongPress;
|
||
}),
|
||
// Customized
|
||
HoldTapMoveGestureRecognizer:
|
||
GestureRecognizerFactoryWithHandlers<HoldTapMoveGestureRecognizer>(
|
||
() => HoldTapMoveGestureRecognizer(),
|
||
(instance) => {
|
||
instance
|
||
..onHoldDragStart = onHoldDragStart
|
||
..onHoldDragUpdate = onHoldDragUpdate
|
||
..onHoldDragCancel = onHoldDragCancel
|
||
}),
|
||
DoubleFinerTapGestureRecognizer: GestureRecognizerFactoryWithHandlers<
|
||
DoubleFinerTapGestureRecognizer>(
|
||
() => DoubleFinerTapGestureRecognizer(), (instance) {
|
||
instance.onDoubleFinerTap = onDoubleFinerTap;
|
||
}),
|
||
CustomTouchGestureRecognizer:
|
||
GestureRecognizerFactoryWithHandlers<CustomTouchGestureRecognizer>(
|
||
() => CustomTouchGestureRecognizer(), (instance) {
|
||
instance
|
||
..onOneFingerPanStart = onOneFingerPanStart
|
||
..onOneFingerPanUpdate = onOneFingerPanUpdate
|
||
..onOneFingerPanEnd = onOneFingerPanEnd
|
||
..onTwoFingerScaleUpdate = onTwoFingerScaleUpdate
|
||
..onTwoFingerScaleEnd = onTwoFingerScaleEnd
|
||
..onTwoFingerHorizontalDragUpdate = onTwoFingerHorizontalDragUpdate
|
||
..onTwoFingerVerticalDragUpdate = onTwoFingerVerticalDragUpdate
|
||
..onTwoFingerPanStart = onTwoFingerPanStart
|
||
..onTwoFingerPanUpdate = onTwoFingerPanUpdate;
|
||
})
|
||
});
|
||
}
|