gestures.dart 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767
  1. import 'dart:async';
  2. import 'package:flutter/gestures.dart';
  3. import 'package:flutter/widgets.dart';
  4. enum GestureState {
  5. none,
  6. oneFingerPan,
  7. twoFingerScale,
  8. threeFingerVerticalDrag
  9. }
  10. class CustomTouchGestureRecognizer extends ScaleGestureRecognizer {
  11. CustomTouchGestureRecognizer({
  12. Object? debugOwner,
  13. Set<PointerDeviceKind>? supportedDevices,
  14. }) : super(
  15. debugOwner: debugOwner,
  16. supportedDevices: supportedDevices,
  17. ) {
  18. _init();
  19. }
  20. // oneFingerPan
  21. GestureDragStartCallback? onOneFingerPanStart;
  22. GestureDragUpdateCallback? onOneFingerPanUpdate;
  23. GestureDragEndCallback? onOneFingerPanEnd;
  24. // twoFingerScale : scale + pan event
  25. GestureScaleStartCallback? onTwoFingerScaleStart;
  26. GestureScaleUpdateCallback? onTwoFingerScaleUpdate;
  27. GestureScaleEndCallback? onTwoFingerScaleEnd;
  28. // threeFingerVerticalDrag
  29. GestureDragStartCallback? onThreeFingerVerticalDragStart;
  30. GestureDragUpdateCallback? onThreeFingerVerticalDragUpdate;
  31. GestureDragEndCallback? onThreeFingerVerticalDragEnd;
  32. var _currentState = GestureState.none;
  33. Timer? _debounceTimer;
  34. void _init() {
  35. debugPrint("CustomTouchGestureRecognizer init");
  36. // onStart = (d) {};
  37. onUpdate = (d) {
  38. _debounceTimer?.cancel();
  39. if (d.pointerCount == 1 && _currentState != GestureState.oneFingerPan) {
  40. onOneFingerStartDebounce(d);
  41. } else if (d.pointerCount == 2 &&
  42. _currentState != GestureState.twoFingerScale) {
  43. onTwoFingerStartDebounce(d);
  44. } else if (d.pointerCount == 3 &&
  45. _currentState != GestureState.threeFingerVerticalDrag) {
  46. _currentState = GestureState.threeFingerVerticalDrag;
  47. if (onThreeFingerVerticalDragStart != null) {
  48. onThreeFingerVerticalDragStart!(
  49. DragStartDetails(globalPosition: d.localFocalPoint));
  50. }
  51. debugPrint("start threeFingerScale");
  52. }
  53. if (_currentState != GestureState.none) {
  54. switch (_currentState) {
  55. case GestureState.oneFingerPan:
  56. if (onOneFingerPanUpdate != null) {
  57. onOneFingerPanUpdate!(_getDragUpdateDetails(d));
  58. }
  59. break;
  60. case GestureState.twoFingerScale:
  61. if (onTwoFingerScaleUpdate != null) {
  62. onTwoFingerScaleUpdate!(d);
  63. }
  64. break;
  65. case GestureState.threeFingerVerticalDrag:
  66. if (onThreeFingerVerticalDragUpdate != null) {
  67. onThreeFingerVerticalDragUpdate!(_getDragUpdateDetails(d));
  68. }
  69. break;
  70. default:
  71. break;
  72. }
  73. return;
  74. }
  75. };
  76. onEnd = (d) {
  77. debugPrint("ScaleGestureRecognizer onEnd");
  78. _debounceTimer?.cancel();
  79. // end
  80. switch (_currentState) {
  81. case GestureState.oneFingerPan:
  82. debugPrint("OneFingerState.pan onEnd");
  83. if (onOneFingerPanEnd != null) {
  84. onOneFingerPanEnd!(_getDragEndDetails(d));
  85. }
  86. break;
  87. case GestureState.twoFingerScale:
  88. debugPrint("TwoFingerState.scale onEnd");
  89. if (onTwoFingerScaleEnd != null) {
  90. onTwoFingerScaleEnd!(d);
  91. }
  92. break;
  93. case GestureState.threeFingerVerticalDrag:
  94. debugPrint("ThreeFingerState.vertical onEnd");
  95. if (onThreeFingerVerticalDragEnd != null) {
  96. onThreeFingerVerticalDragEnd!(_getDragEndDetails(d));
  97. }
  98. break;
  99. default:
  100. break;
  101. }
  102. _debounceTimer = Timer(Duration(milliseconds: 200), () {
  103. _currentState = GestureState.none;
  104. });
  105. };
  106. }
  107. // FIXME: This debounce logic is not working properly.
  108. // If we move our finger very fast, we won't be able to detect the "oneFingerPan" event sometimes.
  109. void onOneFingerStartDebounce(ScaleUpdateDetails d) {
  110. start(ScaleUpdateDetails d) {
  111. _currentState = GestureState.oneFingerPan;
  112. if (onOneFingerPanStart != null) {
  113. onOneFingerPanStart!(DragStartDetails(
  114. localPosition: d.localFocalPoint, globalPosition: d.focalPoint));
  115. }
  116. }
  117. if (_currentState != GestureState.none) {
  118. _debounceTimer = Timer(Duration(milliseconds: 200), () {
  119. start(d);
  120. debugPrint("debounce start oneFingerPan");
  121. });
  122. } else {
  123. start(d);
  124. debugPrint("start oneFingerPan");
  125. }
  126. }
  127. void onTwoFingerStartDebounce(ScaleUpdateDetails d) {
  128. start(ScaleUpdateDetails d) {
  129. _currentState = GestureState.twoFingerScale;
  130. if (onTwoFingerScaleStart != null) {
  131. onTwoFingerScaleStart!(ScaleStartDetails(
  132. localFocalPoint: d.localFocalPoint, focalPoint: d.focalPoint));
  133. }
  134. }
  135. if (_currentState == GestureState.threeFingerVerticalDrag) {
  136. _debounceTimer = Timer(Duration(milliseconds: 200), () {
  137. start(d);
  138. debugPrint("debounce start twoFingerScale");
  139. });
  140. } else {
  141. start(d);
  142. debugPrint("start twoFingerScale");
  143. }
  144. }
  145. DragUpdateDetails _getDragUpdateDetails(ScaleUpdateDetails d) =>
  146. DragUpdateDetails(
  147. globalPosition: d.focalPoint,
  148. localPosition: d.localFocalPoint,
  149. delta: d.focalPointDelta);
  150. DragEndDetails _getDragEndDetails(ScaleEndDetails d) =>
  151. DragEndDetails(velocity: d.velocity);
  152. }
  153. class HoldTapMoveGestureRecognizer extends GestureRecognizer {
  154. HoldTapMoveGestureRecognizer({
  155. Object? debugOwner,
  156. Set<PointerDeviceKind>? supportedDevices,
  157. }) : super(
  158. debugOwner: debugOwner,
  159. supportedDevices: supportedDevices,
  160. );
  161. GestureDragStartCallback? onHoldDragStart;
  162. GestureDragUpdateCallback? onHoldDragUpdate;
  163. GestureDragDownCallback? onHoldDragDown;
  164. GestureDragCancelCallback? onHoldDragCancel;
  165. GestureDragEndCallback? onHoldDragEnd;
  166. bool _isStart = false;
  167. Timer? _firstTapUpTimer;
  168. Timer? _secondTapDownTimer;
  169. _TapTracker? _firstTap;
  170. _TapTracker? _secondTap;
  171. PointerDownEvent? _lastPointerDownEvent;
  172. final Map<int, _TapTracker> _trackers = <int, _TapTracker>{};
  173. @override
  174. bool isPointerAllowed(PointerDownEvent event) {
  175. if (_firstTap == null) {
  176. switch (event.buttons) {
  177. case kPrimaryButton:
  178. if (onHoldDragStart == null &&
  179. onHoldDragUpdate == null &&
  180. onHoldDragCancel == null &&
  181. onHoldDragEnd == null) {
  182. return false;
  183. }
  184. break;
  185. default:
  186. return false;
  187. }
  188. }
  189. return super.isPointerAllowed(event);
  190. }
  191. @override
  192. void addAllowedPointer(PointerDownEvent event) {
  193. if (_firstTap != null) {
  194. if (!_firstTap!.isWithinGlobalTolerance(event, kDoubleTapSlop)) {
  195. // Ignore out-of-bounds second taps.
  196. return;
  197. } else if (!_firstTap!.hasElapsedMinTime() ||
  198. !_firstTap!.hasSameButton(event)) {
  199. // Restart when the second tap is too close to the first (touch screens
  200. // often detect touches intermittently), or when buttons mismatch.
  201. _reset();
  202. return _trackTap(event);
  203. } else if (onHoldDragDown != null) {
  204. invokeCallback<void>(
  205. 'onHoldDragDown',
  206. () => onHoldDragDown!(DragDownDetails(
  207. globalPosition: event.position,
  208. localPosition: event.localPosition)));
  209. }
  210. }
  211. _trackTap(event);
  212. }
  213. void _trackTap(PointerDownEvent event) {
  214. _stopFirstTapUpTimer();
  215. _stopSecondTapDownTimer();
  216. final _TapTracker tracker = _TapTracker(
  217. event: event,
  218. entry: GestureBinding.instance.gestureArena.add(event.pointer, this),
  219. doubleTapMinTime: kDoubleTapMinTime,
  220. gestureSettings: gestureSettings,
  221. );
  222. _trackers[event.pointer] = tracker;
  223. _lastPointerDownEvent = event;
  224. tracker.startTrackingPointer(_handleEvent, event.transform);
  225. }
  226. void _handleEvent(PointerEvent event) {
  227. final _TapTracker tracker = _trackers[event.pointer]!;
  228. if (event is PointerUpEvent) {
  229. if (_firstTap == null && _secondTap == null) {
  230. _registerFirstTap(tracker);
  231. } else if (_secondTap != null) {
  232. if (event.pointer == _secondTap!.pointer) {
  233. if (onHoldDragEnd != null) {
  234. onHoldDragEnd!(DragEndDetails());
  235. _secondTap = null;
  236. _isStart = false;
  237. }
  238. }
  239. } else {
  240. _reject(tracker);
  241. }
  242. } else if (event is PointerDownEvent) {
  243. if (_firstTap != null && _secondTap == null) {
  244. _registerSecondTap(tracker);
  245. }
  246. } else if (event is PointerMoveEvent) {
  247. if (!tracker.isWithinGlobalTolerance(event, kDoubleTapTouchSlop)) {
  248. if (_firstTap != null && _firstTap!.pointer == event.pointer) {
  249. // first tap move
  250. _reject(tracker);
  251. } else if (_secondTap != null && _secondTap!.pointer == event.pointer) {
  252. // debugPrint("_secondTap move");
  253. // second tap move
  254. if (!_isStart) {
  255. _resolve();
  256. }
  257. if (onHoldDragUpdate != null) {
  258. onHoldDragUpdate!(DragUpdateDetails(
  259. globalPosition: event.position,
  260. localPosition: event.localPosition,
  261. delta: event.delta));
  262. }
  263. }
  264. }
  265. } else if (event is PointerCancelEvent) {
  266. _reject(tracker);
  267. }
  268. }
  269. @override
  270. void acceptGesture(int pointer) {}
  271. @override
  272. void rejectGesture(int pointer) {
  273. _TapTracker? tracker = _trackers[pointer];
  274. // If tracker isn't in the list, check if this is the first tap tracker
  275. if (tracker == null && _firstTap != null && _firstTap!.pointer == pointer) {
  276. tracker = _firstTap;
  277. }
  278. // If tracker is still null, we rejected ourselves already
  279. if (tracker != null) {
  280. _reject(tracker);
  281. }
  282. }
  283. void _resolve() {
  284. _stopSecondTapDownTimer();
  285. _firstTap?.entry.resolve(GestureDisposition.accepted);
  286. _secondTap?.entry.resolve(GestureDisposition.accepted);
  287. _isStart = true;
  288. // TODO start details
  289. if (onHoldDragStart != null) {
  290. onHoldDragStart!(DragStartDetails(
  291. kind: _lastPointerDownEvent?.kind,
  292. ));
  293. }
  294. }
  295. void _reject(_TapTracker tracker) {
  296. try {
  297. _checkCancel();
  298. _isStart = false;
  299. _trackers.remove(tracker.pointer);
  300. tracker.entry.resolve(GestureDisposition.rejected);
  301. _freezeTracker(tracker);
  302. _reset();
  303. } catch (e) {
  304. debugPrint("Failed to _reject:$e");
  305. }
  306. }
  307. @override
  308. void dispose() {
  309. _reset();
  310. super.dispose();
  311. }
  312. void _reset() {
  313. _isStart = false;
  314. // debugPrint("reset");
  315. _stopFirstTapUpTimer();
  316. _stopSecondTapDownTimer();
  317. if (_firstTap != null) {
  318. if (_trackers.isNotEmpty) {
  319. _checkCancel();
  320. }
  321. // Note, order is important below in order for the resolve -> reject logic
  322. // to work properly.
  323. final _TapTracker tracker = _firstTap!;
  324. _firstTap = null;
  325. _reject(tracker);
  326. GestureBinding.instance.gestureArena.release(tracker.pointer);
  327. if (_secondTap != null) {
  328. final _TapTracker tracker = _secondTap!;
  329. _secondTap = null;
  330. _reject(tracker);
  331. GestureBinding.instance.gestureArena.release(tracker.pointer);
  332. }
  333. }
  334. _firstTap = null;
  335. _secondTap = null;
  336. _clearTrackers();
  337. }
  338. void _registerFirstTap(_TapTracker tracker) {
  339. _startFirstTapUpTimer();
  340. GestureBinding.instance.gestureArena.hold(tracker.pointer);
  341. // Note, order is important below in order for the clear -> reject logic to
  342. // work properly.
  343. _freezeTracker(tracker);
  344. _trackers.remove(tracker.pointer);
  345. _firstTap = tracker;
  346. }
  347. void _registerSecondTap(_TapTracker tracker) {
  348. if (_firstTap != null) {
  349. _stopFirstTapUpTimer();
  350. _freezeTracker(_firstTap!);
  351. _firstTap = null;
  352. }
  353. _startSecondTapDownTimer();
  354. GestureBinding.instance.gestureArena.hold(tracker.pointer);
  355. _secondTap = tracker;
  356. // TODO
  357. }
  358. void _clearTrackers() {
  359. _trackers.values.toList().forEach(_reject);
  360. assert(_trackers.isEmpty);
  361. }
  362. void _freezeTracker(_TapTracker tracker) {
  363. tracker.stopTrackingPointer(_handleEvent);
  364. }
  365. void _startFirstTapUpTimer() {
  366. _firstTapUpTimer ??= Timer(kDoubleTapTimeout, _reset);
  367. }
  368. void _startSecondTapDownTimer() {
  369. _secondTapDownTimer ??= Timer(kDoubleTapTimeout, _resolve);
  370. }
  371. void _stopFirstTapUpTimer() {
  372. if (_firstTapUpTimer != null) {
  373. _firstTapUpTimer!.cancel();
  374. _firstTapUpTimer = null;
  375. }
  376. }
  377. void _stopSecondTapDownTimer() {
  378. if (_secondTapDownTimer != null) {
  379. _secondTapDownTimer!.cancel();
  380. _secondTapDownTimer = null;
  381. }
  382. }
  383. void _checkCancel() {
  384. if (onHoldDragCancel != null) {
  385. invokeCallback<void>('onHoldDragCancel', onHoldDragCancel!);
  386. }
  387. }
  388. @override
  389. String get debugDescription => 'double tap';
  390. }
  391. class DoubleFinerTapGestureRecognizer extends GestureRecognizer {
  392. DoubleFinerTapGestureRecognizer({
  393. Object? debugOwner,
  394. Set<PointerDeviceKind>? supportedDevices,
  395. }) : super(
  396. debugOwner: debugOwner,
  397. supportedDevices: supportedDevices,
  398. );
  399. GestureTapDownCallback? onDoubleFinerTapDown;
  400. GestureTapDownCallback? onDoubleFinerTap;
  401. GestureTapCancelCallback? onDoubleFinerTapCancel;
  402. Timer? _firstTapTimer;
  403. _TapTracker? _firstTap;
  404. PointerDownEvent? _lastPointerDownEvent;
  405. var _isStart = false;
  406. final Set<int> _upTap = {};
  407. final Map<int, _TapTracker> _trackers = <int, _TapTracker>{};
  408. @override
  409. bool isPointerAllowed(PointerDownEvent event) {
  410. if (_firstTap == null) {
  411. switch (event.buttons) {
  412. case kPrimaryButton:
  413. if (onDoubleFinerTapDown == null &&
  414. onDoubleFinerTap == null &&
  415. onDoubleFinerTapCancel == null) {
  416. return false;
  417. }
  418. break;
  419. default:
  420. return false;
  421. }
  422. }
  423. return super.isPointerAllowed(event);
  424. }
  425. @override
  426. void addAllowedPointer(PointerDownEvent event) {
  427. debugPrint("addAllowedPointer");
  428. if (_isStart) {
  429. // second
  430. if (onDoubleFinerTapDown != null) {
  431. final TapDownDetails details = TapDownDetails(
  432. globalPosition: event.position,
  433. localPosition: event.localPosition,
  434. kind: getKindForPointer(event.pointer),
  435. );
  436. invokeCallback<void>(
  437. 'onDoubleFinerTapDown', () => onDoubleFinerTapDown!(details));
  438. }
  439. } else {
  440. // first tap
  441. _isStart = true;
  442. _lastPointerDownEvent = event;
  443. _startFirstTapDownTimer();
  444. }
  445. _trackTap(event);
  446. }
  447. void _trackTap(PointerDownEvent event) {
  448. final _TapTracker tracker = _TapTracker(
  449. event: event,
  450. entry: GestureBinding.instance.gestureArena.add(event.pointer, this),
  451. doubleTapMinTime: kDoubleTapMinTime,
  452. gestureSettings: gestureSettings,
  453. );
  454. _trackers[event.pointer] = tracker;
  455. // debugPrint("_trackers:$_trackers");
  456. tracker.startTrackingPointer(_handleEvent, event.transform);
  457. _registerTap(tracker);
  458. }
  459. void _handleEvent(PointerEvent event) {
  460. final _TapTracker tracker = _trackers[event.pointer]!;
  461. if (event is PointerUpEvent) {
  462. debugPrint("PointerUpEvent");
  463. _upTap.add(tracker.pointer);
  464. } else if (event is PointerMoveEvent) {
  465. if (!tracker.isWithinGlobalTolerance(event, kDoubleTapTouchSlop)) {
  466. _reject(tracker);
  467. }
  468. } else if (event is PointerCancelEvent) {
  469. _reject(tracker);
  470. }
  471. }
  472. @override
  473. void acceptGesture(int pointer) {}
  474. @override
  475. void rejectGesture(int pointer) {
  476. _TapTracker? tracker = _trackers[pointer];
  477. // If tracker isn't in the list, check if this is the first tap tracker
  478. if (tracker == null && _firstTap != null && _firstTap!.pointer == pointer) {
  479. tracker = _firstTap;
  480. }
  481. // If tracker is still null, we rejected ourselves already
  482. if (tracker != null) {
  483. _reject(tracker);
  484. }
  485. }
  486. void _reject(_TapTracker tracker) {
  487. _trackers.remove(tracker.pointer);
  488. tracker.entry.resolve(GestureDisposition.rejected);
  489. _freezeTracker(tracker);
  490. if (_firstTap != null) {
  491. if (tracker == _firstTap) {
  492. _reset();
  493. } else {
  494. _checkCancel();
  495. if (_trackers.isEmpty) {
  496. _reset();
  497. }
  498. }
  499. }
  500. }
  501. @override
  502. void dispose() {
  503. _reset();
  504. super.dispose();
  505. }
  506. void _reset() {
  507. _stopFirstTapUpTimer();
  508. _firstTap = null;
  509. _clearTrackers();
  510. }
  511. void _registerTap(_TapTracker tracker) {
  512. GestureBinding.instance.gestureArena.hold(tracker.pointer);
  513. // Note, order is important below in order for the clear -> reject logic to
  514. // work properly.
  515. }
  516. void _clearTrackers() {
  517. _trackers.values.toList().forEach(_reject);
  518. assert(_trackers.isEmpty);
  519. }
  520. void _freezeTracker(_TapTracker tracker) {
  521. tracker.stopTrackingPointer(_handleEvent);
  522. }
  523. void _startFirstTapDownTimer() {
  524. _firstTapTimer ??= Timer(kDoubleTapTimeout, _timeoutCheck);
  525. }
  526. void _stopFirstTapUpTimer() {
  527. if (_firstTapTimer != null) {
  528. _firstTapTimer!.cancel();
  529. _firstTapTimer = null;
  530. }
  531. }
  532. void _timeoutCheck() {
  533. _isStart = false;
  534. if (_upTap.length == 2) {
  535. _resolve();
  536. } else {
  537. _reset();
  538. }
  539. _upTap.clear();
  540. }
  541. void _resolve() {
  542. // TODO tap down details
  543. if (onDoubleFinerTap != null) {
  544. onDoubleFinerTap!(TapDownDetails(
  545. kind: _lastPointerDownEvent?.kind,
  546. ));
  547. }
  548. _trackers.forEach((key, value) {
  549. value.entry.resolve(GestureDisposition.accepted);
  550. });
  551. _reset();
  552. }
  553. void _checkCancel() {
  554. if (onDoubleFinerTapCancel != null) {
  555. invokeCallback<void>('onHoldDragCancel', onDoubleFinerTapCancel!);
  556. }
  557. }
  558. @override
  559. String get debugDescription => 'double tap';
  560. }
  561. /// TapTracker helps track individual tap sequences as part of a
  562. /// larger gesture.
  563. class _TapTracker {
  564. _TapTracker({
  565. required PointerDownEvent event,
  566. required this.entry,
  567. required Duration doubleTapMinTime,
  568. required this.gestureSettings,
  569. }) : pointer = event.pointer,
  570. _initialGlobalPosition = event.position,
  571. initialButtons = event.buttons,
  572. _doubleTapMinTimeCountdown =
  573. _CountdownZoned(duration: doubleTapMinTime);
  574. final DeviceGestureSettings? gestureSettings;
  575. final int pointer;
  576. final GestureArenaEntry entry;
  577. final Offset _initialGlobalPosition;
  578. final int initialButtons;
  579. final _CountdownZoned _doubleTapMinTimeCountdown;
  580. bool _isTrackingPointer = false;
  581. void startTrackingPointer(PointerRoute route, Matrix4? transform) {
  582. if (!_isTrackingPointer) {
  583. _isTrackingPointer = true;
  584. GestureBinding.instance.pointerRouter.addRoute(pointer, route, transform);
  585. }
  586. }
  587. void stopTrackingPointer(PointerRoute route) {
  588. if (_isTrackingPointer) {
  589. _isTrackingPointer = false;
  590. GestureBinding.instance.pointerRouter.removeRoute(pointer, route);
  591. }
  592. }
  593. bool isWithinGlobalTolerance(PointerEvent event, double tolerance) {
  594. final Offset offset = event.position - _initialGlobalPosition;
  595. return offset.distance <= tolerance;
  596. }
  597. bool hasElapsedMinTime() {
  598. return _doubleTapMinTimeCountdown.timeout;
  599. }
  600. bool hasSameButton(PointerDownEvent event) {
  601. return event.buttons == initialButtons;
  602. }
  603. }
  604. /// CountdownZoned tracks whether the specified duration has elapsed since
  605. /// creation, honoring [Zone].
  606. class _CountdownZoned {
  607. _CountdownZoned({required Duration duration}) {
  608. Timer(duration, _onTimeout);
  609. }
  610. bool _timeout = false;
  611. bool get timeout => _timeout;
  612. void _onTimeout() {
  613. _timeout = true;
  614. }
  615. }
  616. RawGestureDetector getMixinGestureDetector({
  617. Widget? child,
  618. GestureTapUpCallback? onTapUp,
  619. GestureTapDownCallback? onDoubleTapDown,
  620. GestureDoubleTapCallback? onDoubleTap,
  621. GestureLongPressDownCallback? onLongPressDown,
  622. GestureLongPressCallback? onLongPress,
  623. GestureDragStartCallback? onHoldDragStart,
  624. GestureDragUpdateCallback? onHoldDragUpdate,
  625. GestureDragCancelCallback? onHoldDragCancel,
  626. GestureDragEndCallback? onHoldDragEnd,
  627. GestureTapDownCallback? onDoubleFinerTap,
  628. GestureDragStartCallback? onOneFingerPanStart,
  629. GestureDragUpdateCallback? onOneFingerPanUpdate,
  630. GestureDragEndCallback? onOneFingerPanEnd,
  631. GestureScaleUpdateCallback? onTwoFingerScaleUpdate,
  632. GestureScaleEndCallback? onTwoFingerScaleEnd,
  633. GestureDragUpdateCallback? onThreeFingerVerticalDragUpdate,
  634. }) {
  635. return RawGestureDetector(
  636. child: child,
  637. gestures: <Type, GestureRecognizerFactory>{
  638. // Official
  639. TapGestureRecognizer:
  640. GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
  641. () => TapGestureRecognizer(), (instance) {
  642. instance.onTapUp = onTapUp;
  643. }),
  644. DoubleTapGestureRecognizer:
  645. GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(
  646. () => DoubleTapGestureRecognizer(), (instance) {
  647. instance
  648. ..onDoubleTapDown = onDoubleTapDown
  649. ..onDoubleTap = onDoubleTap;
  650. }),
  651. LongPressGestureRecognizer:
  652. GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
  653. () => LongPressGestureRecognizer(), (instance) {
  654. instance
  655. ..onLongPressDown = onLongPressDown
  656. ..onLongPress = onLongPress;
  657. }),
  658. // Customized
  659. HoldTapMoveGestureRecognizer:
  660. GestureRecognizerFactoryWithHandlers<HoldTapMoveGestureRecognizer>(
  661. () => HoldTapMoveGestureRecognizer(),
  662. (instance) => instance
  663. ..onHoldDragStart = onHoldDragStart
  664. ..onHoldDragUpdate = onHoldDragUpdate
  665. ..onHoldDragCancel = onHoldDragCancel
  666. ..onHoldDragEnd = onHoldDragEnd),
  667. DoubleFinerTapGestureRecognizer: GestureRecognizerFactoryWithHandlers<
  668. DoubleFinerTapGestureRecognizer>(
  669. () => DoubleFinerTapGestureRecognizer(), (instance) {
  670. instance.onDoubleFinerTap = onDoubleFinerTap;
  671. }),
  672. CustomTouchGestureRecognizer:
  673. GestureRecognizerFactoryWithHandlers<CustomTouchGestureRecognizer>(
  674. () => CustomTouchGestureRecognizer(), (instance) {
  675. instance
  676. ..onOneFingerPanStart = onOneFingerPanStart
  677. ..onOneFingerPanUpdate = onOneFingerPanUpdate
  678. ..onOneFingerPanEnd = onOneFingerPanEnd
  679. ..onTwoFingerScaleUpdate = onTwoFingerScaleUpdate
  680. ..onTwoFingerScaleEnd = onTwoFingerScaleEnd
  681. ..onThreeFingerVerticalDragUpdate = onThreeFingerVerticalDragUpdate;
  682. }),
  683. });
  684. }