767 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			767 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
import 'dart:async';
 | 
						|
import 'package:flutter/gestures.dart';
 | 
						|
import 'package:flutter/widgets.dart';
 | 
						|
 | 
						|
enum GestureState {
 | 
						|
  none,
 | 
						|
  oneFingerPan,
 | 
						|
  twoFingerScale,
 | 
						|
  threeFingerVerticalDrag
 | 
						|
}
 | 
						|
 | 
						|
class CustomTouchGestureRecognizer extends ScaleGestureRecognizer {
 | 
						|
  CustomTouchGestureRecognizer({
 | 
						|
    Object? debugOwner,
 | 
						|
    Set<PointerDeviceKind>? supportedDevices,
 | 
						|
  }) : super(
 | 
						|
          debugOwner: debugOwner,
 | 
						|
          supportedDevices: supportedDevices,
 | 
						|
        ) {
 | 
						|
    _init();
 | 
						|
  }
 | 
						|
 | 
						|
  // oneFingerPan
 | 
						|
  GestureDragStartCallback? onOneFingerPanStart;
 | 
						|
  GestureDragUpdateCallback? onOneFingerPanUpdate;
 | 
						|
  GestureDragEndCallback? onOneFingerPanEnd;
 | 
						|
 | 
						|
  // twoFingerScale : scale + pan event
 | 
						|
  GestureScaleStartCallback? onTwoFingerScaleStart;
 | 
						|
  GestureScaleUpdateCallback? onTwoFingerScaleUpdate;
 | 
						|
  GestureScaleEndCallback? onTwoFingerScaleEnd;
 | 
						|
 | 
						|
  // threeFingerVerticalDrag
 | 
						|
  GestureDragStartCallback? onThreeFingerVerticalDragStart;
 | 
						|
  GestureDragUpdateCallback? onThreeFingerVerticalDragUpdate;
 | 
						|
  GestureDragEndCallback? onThreeFingerVerticalDragEnd;
 | 
						|
 | 
						|
  var _currentState = GestureState.none;
 | 
						|
  Timer? _debounceTimer;
 | 
						|
 | 
						|
  void _init() {
 | 
						|
    debugPrint("CustomTouchGestureRecognizer init");
 | 
						|
    // onStart = (d) {};
 | 
						|
    onUpdate = (d) {
 | 
						|
      _debounceTimer?.cancel();
 | 
						|
      if (d.pointerCount == 1 && _currentState != GestureState.oneFingerPan) {
 | 
						|
        onOneFingerStartDebounce(d);
 | 
						|
      } else if (d.pointerCount == 2 &&
 | 
						|
          _currentState != GestureState.twoFingerScale) {
 | 
						|
        onTwoFingerStartDebounce(d);
 | 
						|
      } else if (d.pointerCount == 3 &&
 | 
						|
          _currentState != GestureState.threeFingerVerticalDrag) {
 | 
						|
        _currentState = GestureState.threeFingerVerticalDrag;
 | 
						|
        if (onThreeFingerVerticalDragStart != null) {
 | 
						|
          onThreeFingerVerticalDragStart!(
 | 
						|
              DragStartDetails(globalPosition: d.localFocalPoint));
 | 
						|
        }
 | 
						|
        debugPrint("start threeFingerScale");
 | 
						|
      }
 | 
						|
      if (_currentState != GestureState.none) {
 | 
						|
        switch (_currentState) {
 | 
						|
          case GestureState.oneFingerPan:
 | 
						|
            if (onOneFingerPanUpdate != null) {
 | 
						|
              onOneFingerPanUpdate!(_getDragUpdateDetails(d));
 | 
						|
            }
 | 
						|
            break;
 | 
						|
          case GestureState.twoFingerScale:
 | 
						|
            if (onTwoFingerScaleUpdate != null) {
 | 
						|
              onTwoFingerScaleUpdate!(d);
 | 
						|
            }
 | 
						|
            break;
 | 
						|
          case GestureState.threeFingerVerticalDrag:
 | 
						|
            if (onThreeFingerVerticalDragUpdate != null) {
 | 
						|
              onThreeFingerVerticalDragUpdate!(_getDragUpdateDetails(d));
 | 
						|
            }
 | 
						|
            break;
 | 
						|
          default:
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        return;
 | 
						|
      }
 | 
						|
    };
 | 
						|
    onEnd = (d) {
 | 
						|
      debugPrint("ScaleGestureRecognizer onEnd");
 | 
						|
      _debounceTimer?.cancel();
 | 
						|
      // end
 | 
						|
      switch (_currentState) {
 | 
						|
        case GestureState.oneFingerPan:
 | 
						|
          debugPrint("TwoFingerState.pan onEnd");
 | 
						|
          if (onOneFingerPanEnd != null) {
 | 
						|
            onOneFingerPanEnd!(_getDragEndDetails(d));
 | 
						|
          }
 | 
						|
          break;
 | 
						|
        case GestureState.twoFingerScale:
 | 
						|
          debugPrint("TwoFingerState.scale onEnd");
 | 
						|
          if (onTwoFingerScaleEnd != null) {
 | 
						|
            onTwoFingerScaleEnd!(d);
 | 
						|
          }
 | 
						|
          break;
 | 
						|
        case GestureState.threeFingerVerticalDrag:
 | 
						|
          debugPrint("ThreeFingerState.vertical onEnd");
 | 
						|
          if (onThreeFingerVerticalDragEnd != null) {
 | 
						|
            onThreeFingerVerticalDragEnd!(_getDragEndDetails(d));
 | 
						|
          }
 | 
						|
          break;
 | 
						|
        default:
 | 
						|
          break;
 | 
						|
      }
 | 
						|
      _debounceTimer = Timer(Duration(milliseconds: 200), () {
 | 
						|
        _currentState = GestureState.none;
 | 
						|
      });
 | 
						|
    };
 | 
						|
  }
 | 
						|
 | 
						|
  // FIXME: This debounce logic is not working properly.
 | 
						|
  // If we move our finger very fast, we won't be able to detect the "oneFingerPan" event sometimes.
 | 
						|
  void onOneFingerStartDebounce(ScaleUpdateDetails d) {
 | 
						|
    start(ScaleUpdateDetails d) {
 | 
						|
      _currentState = GestureState.oneFingerPan;
 | 
						|
      if (onOneFingerPanStart != null) {
 | 
						|
        onOneFingerPanStart!(DragStartDetails(
 | 
						|
            localPosition: d.localFocalPoint, globalPosition: d.focalPoint));
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    if (_currentState != GestureState.none) {
 | 
						|
      _debounceTimer = Timer(Duration(milliseconds: 200), () {
 | 
						|
        start(d);
 | 
						|
        debugPrint("debounce start oneFingerPan");
 | 
						|
      });
 | 
						|
    } else {
 | 
						|
      start(d);
 | 
						|
      debugPrint("start oneFingerPan");
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  void onTwoFingerStartDebounce(ScaleUpdateDetails d) {
 | 
						|
    start(ScaleUpdateDetails d) {
 | 
						|
      _currentState = GestureState.twoFingerScale;
 | 
						|
      if (onTwoFingerScaleStart != null) {
 | 
						|
        onTwoFingerScaleStart!(ScaleStartDetails(
 | 
						|
            localFocalPoint: d.localFocalPoint, focalPoint: d.focalPoint));
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    if (_currentState == GestureState.threeFingerVerticalDrag) {
 | 
						|
      _debounceTimer = Timer(Duration(milliseconds: 200), () {
 | 
						|
        start(d);
 | 
						|
        debugPrint("debounce start twoFingerScale");
 | 
						|
      });
 | 
						|
    } else {
 | 
						|
      start(d);
 | 
						|
      debugPrint("start twoFingerScale");
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  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;
 | 
						|
  GestureDragEndCallback? onHoldDragEnd;
 | 
						|
 | 
						|
  bool _isStart = false;
 | 
						|
 | 
						|
  Timer? _firstTapUpTimer;
 | 
						|
  Timer? _secondTapDownTimer;
 | 
						|
  _TapTracker? _firstTap;
 | 
						|
  _TapTracker? _secondTap;
 | 
						|
 | 
						|
  PointerDownEvent? _lastPointerDownEvent;
 | 
						|
 | 
						|
  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 &&
 | 
						|
              onHoldDragEnd == 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);
 | 
						|
  }
 | 
						|
 | 
						|
  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;
 | 
						|
    _lastPointerDownEvent = event;
 | 
						|
    tracker.startTrackingPointer(_handleEvent, event.transform);
 | 
						|
  }
 | 
						|
 | 
						|
  void _handleEvent(PointerEvent event) {
 | 
						|
    final _TapTracker tracker = _trackers[event.pointer]!;
 | 
						|
    if (event is PointerUpEvent) {
 | 
						|
      if (_firstTap == null && _secondTap == null) {
 | 
						|
        _registerFirstTap(tracker);
 | 
						|
      } else if (_secondTap != null) {
 | 
						|
        if (event.pointer == _secondTap!.pointer) {
 | 
						|
          if (onHoldDragEnd != null) {
 | 
						|
            onHoldDragEnd!(DragEndDetails());
 | 
						|
            _secondTap = null;
 | 
						|
            _isStart = false;
 | 
						|
          }
 | 
						|
        }
 | 
						|
      } else {
 | 
						|
        _reject(tracker);
 | 
						|
      }
 | 
						|
    } else if (event is PointerDownEvent) {
 | 
						|
      if (_firstTap != null && _secondTap == null) {
 | 
						|
        _registerSecondTap(tracker);
 | 
						|
      }
 | 
						|
    } else if (event is PointerMoveEvent) {
 | 
						|
      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(
 | 
						|
        kind: _lastPointerDownEvent?.kind,
 | 
						|
      ));
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  void _reject(_TapTracker tracker) {
 | 
						|
    try {
 | 
						|
      _checkCancel();
 | 
						|
      _isStart = false;
 | 
						|
      _trackers.remove(tracker.pointer);
 | 
						|
      tracker.entry.resolve(GestureDisposition.rejected);
 | 
						|
      _freezeTracker(tracker);
 | 
						|
      _reset();
 | 
						|
    } catch (e) {
 | 
						|
      debugPrint("Failed to _reject:$e");
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  @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);
 | 
						|
      }
 | 
						|
    }
 | 
						|
    _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) {
 | 
						|
    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;
 | 
						|
  _TapTracker? _firstTap;
 | 
						|
 | 
						|
  PointerDownEvent? _lastPointerDownEvent;
 | 
						|
 | 
						|
  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;
 | 
						|
      _lastPointerDownEvent = event;
 | 
						|
      _startFirstTapDownTimer();
 | 
						|
    }
 | 
						|
    _trackTap(event);
 | 
						|
  }
 | 
						|
 | 
						|
  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);
 | 
						|
  }
 | 
						|
 | 
						|
  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;
 | 
						|
    _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(
 | 
						|
        kind: _lastPointerDownEvent?.kind,
 | 
						|
      ));
 | 
						|
    }
 | 
						|
    _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,
 | 
						|
  })  : 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}) {
 | 
						|
    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,
 | 
						|
  GestureDragEndCallback? onHoldDragEnd,
 | 
						|
  GestureTapDownCallback? onDoubleFinerTap,
 | 
						|
  GestureDragStartCallback? onOneFingerPanStart,
 | 
						|
  GestureDragUpdateCallback? onOneFingerPanUpdate,
 | 
						|
  GestureDragEndCallback? onOneFingerPanEnd,
 | 
						|
  GestureScaleUpdateCallback? onTwoFingerScaleUpdate,
 | 
						|
  GestureScaleEndCallback? onTwoFingerScaleEnd,
 | 
						|
  GestureDragUpdateCallback? onThreeFingerVerticalDragUpdate,
 | 
						|
}) {
 | 
						|
  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
 | 
						|
                  ..onHoldDragEnd = onHoldDragEnd),
 | 
						|
        DoubleFinerTapGestureRecognizer: GestureRecognizerFactoryWithHandlers<
 | 
						|
                DoubleFinerTapGestureRecognizer>(
 | 
						|
            () => DoubleFinerTapGestureRecognizer(), (instance) {
 | 
						|
          instance.onDoubleFinerTap = onDoubleFinerTap;
 | 
						|
        }),
 | 
						|
        CustomTouchGestureRecognizer:
 | 
						|
            GestureRecognizerFactoryWithHandlers<CustomTouchGestureRecognizer>(
 | 
						|
                () => CustomTouchGestureRecognizer(), (instance) {
 | 
						|
          instance
 | 
						|
            ..onOneFingerPanStart = onOneFingerPanStart
 | 
						|
            ..onOneFingerPanUpdate = onOneFingerPanUpdate
 | 
						|
            ..onOneFingerPanEnd = onOneFingerPanEnd
 | 
						|
            ..onTwoFingerScaleUpdate = onTwoFingerScaleUpdate
 | 
						|
            ..onTwoFingerScaleEnd = onTwoFingerScaleEnd
 | 
						|
            ..onThreeFingerVerticalDragUpdate = onThreeFingerVerticalDragUpdate;
 | 
						|
        }),
 | 
						|
      });
 | 
						|
}
 |