From eefa3cbe98c8649ca9cb7ea6b2e0911adf672f1b Mon Sep 17 00:00:00 2001 From: Nick Fisher Date: Wed, 11 Sep 2024 18:05:24 +0800 Subject: [PATCH] (flutter) add experimental GestureHandler widget and decouple from ThermionGestureDetectorDesktop --- .../thermion_gesture_detector_desktop.dart | 182 ++++-------------- 1 file changed, 39 insertions(+), 143 deletions(-) diff --git a/thermion_flutter/thermion_flutter/lib/thermion/widgets/camera/gestures/thermion_gesture_detector_desktop.dart b/thermion_flutter/thermion_flutter/lib/thermion/widgets/camera/gestures/thermion_gesture_detector_desktop.dart index 4ab1e711..45dbc6b5 100644 --- a/thermion_flutter/thermion_flutter/lib/thermion/widgets/camera/gestures/thermion_gesture_detector_desktop.dart +++ b/thermion_flutter/thermion_flutter/lib/thermion/widgets/camera/gestures/thermion_gesture_detector_desktop.dart @@ -5,49 +5,24 @@ import 'package:thermion_dart/thermion_dart/thermion_viewer.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:thermion_flutter/thermion/widgets/camera/gestures/thermion_gesture_handler.dart'; import 'package:vector_math/vector_math_64.dart' as v64; -/// -/// A widget that translates finger/mouse gestures to zoom/pan/rotate actions. -/// class ThermionGestureDetectorDesktop extends StatefulWidget { - /// - /// The content to display below the gesture detector/listener widget. - /// This will usually be a ThermionWidget (so you can navigate by directly interacting with the viewport), but this is not necessary. - /// It is equally possible to render the viewport/gesture controls elsewhere in the widget hierarchy. The only requirement is that they share the same [FilamentController]. - /// final Widget? child; - - /// - /// The [controller] attached to the [ThermionWidget] you wish to control. - /// final ThermionViewer controller; - - /// - /// If true, an overlay will be shown with buttons to toggle whether pointer movements are interpreted as: - /// 1) rotate or a pan (mobile only), - /// 2) moving the camera or the background image (TODO). - /// final bool showControlOverlay; - - /// - /// If false, gestures will not manipulate the active camera. - /// final bool enableCamera; - - /// - /// If false, pointer down events will not trigger hit-testing (picking). - /// final bool enablePicking; - const ThermionGestureDetectorDesktop( - {Key? key, - required this.controller, - this.child, - this.showControlOverlay = false, - this.enableCamera = true, - this.enablePicking = true}) - : super(key: key); + const ThermionGestureDetectorDesktop({ + Key? key, + required this.controller, + this.child, + this.showControlOverlay = false, + this.enableCamera = true, + this.enablePicking = true, + }) : super(key: key); @override State createState() => _ThermionGestureDetectorDesktopState(); @@ -55,129 +30,50 @@ class ThermionGestureDetectorDesktop extends StatefulWidget { class _ThermionGestureDetectorDesktopState extends State { - final _logger = Logger("_ThermionGestureDetectorDesktopState"); - - /// - /// - // ignore: unused_field - final bool _scaling = false; - - bool _pointerMoving = false; - - AbstractGizmo? _gizmo; + late ThermionGestureHandler _gestureHandler; @override void initState() { super.initState(); - try { - _gizmo = widget.controller.gizmo; - } catch (err) { - _logger.warning( - "Failed to get gizmo. If you are running on WASM, this is expected"); - } + _gestureHandler = ThermionGestureHandler( + enableCamera: widget.enableCamera, + enablePicking: widget.enablePicking, viewer: widget.controller, + ); } @override void didUpdateWidget(ThermionGestureDetectorDesktop oldWidget) { - if (widget.showControlOverlay != oldWidget.showControlOverlay || - widget.enableCamera != oldWidget.enableCamera || + if (widget.enableCamera != oldWidget.enableCamera || widget.enablePicking != oldWidget.enablePicking) { - setState(() {}); + _gestureHandler = ThermionGestureHandler( + viewer: widget.controller, + enableCamera: widget.enableCamera, + enablePicking: widget.enablePicking, + ); } - super.didUpdateWidget(oldWidget); } - Timer? _scrollTimer; - - /// - /// Scroll-wheel on desktop, interpreted as zoom - /// - void _zoom(PointerScrollEvent pointerSignal) async { - _scrollTimer?.cancel(); - await widget.controller.zoomBegin(); - await widget.controller.zoomUpdate( - pointerSignal.localPosition.dx, - pointerSignal.localPosition.dy, - pointerSignal.scrollDelta.dy > 0 ? 1 : -1); - - // we don't want to end the zoom in the same frame, because this will destroy the camera manipulator (and cancel the zoom update). - // here, we just defer calling [zoomEnd] for 100ms to ensure the update is propagated through. - _scrollTimer = Timer(const Duration(milliseconds: 100), () async { - await widget.controller.zoomEnd(); - }); - } - - Timer? _pickTimer; - @override Widget build(BuildContext context) { return Listener( - onPointerHover: (event) async { - _gizmo?.checkHover(event.localPosition.dx, event.localPosition.dy); - }, - onPointerSignal: (PointerSignalEvent pointerSignal) async { - if (pointerSignal is PointerScrollEvent) { - if (widget.enableCamera) { - _zoom(pointerSignal); - } - } else { - throw Exception("TODO"); - } - }, - onPointerPanZoomStart: (pzs) { - throw Exception("TODO - is this a pinch zoom on laptop trackpad?"); - }, - onPointerDown: (d) async { - if (d.buttons != kTertiaryButton && widget.enablePicking) { - widget.controller - .pick(d.localPosition.dx.toInt(), d.localPosition.dy.toInt()); - } - _pointerMoving = false; - }, - // holding/moving the left mouse button is interpreted as a pan, middle mouse button as a rotate - onPointerMove: (PointerMoveEvent d) async { - if (_gizmo?.isHovered == true) { - _gizmo!.translate(d.delta.dx, d.delta.dy); - return; - } - // if this is the first move event, we need to call rotateStart/panStart to set the first coordinates - if (!_pointerMoving) { - if (d.buttons == kTertiaryButton && widget.enableCamera) { - widget.controller - .rotateStart(d.localPosition.dx, d.localPosition.dy); - } else if (widget.enableCamera) { - widget.controller - .panStart(d.localPosition.dx, d.localPosition.dy); - } - } - // set the _pointerMoving flag so we don't call rotateStart/panStart on future move events - _pointerMoving = true; - if (d.buttons == kTertiaryButton && widget.enableCamera) { - widget.controller - .rotateUpdate(d.localPosition.dx, d.localPosition.dy); - } else if (widget.enableCamera) { - widget.controller.panUpdate(d.localPosition.dx, d.localPosition.dy); - } - }, - // when the left mouse button is released: - // 1) if _pointerMoving is true, this completes the pan - // 2) if _pointerMoving is false, this is interpreted as a pick - // same applies to middle mouse button, but this is ignored as a pick - onPointerUp: (PointerUpEvent d) async { - if (_gizmo?.isHovered == true) { - return; - } - - if (d.buttons == kTertiaryButton && widget.enableCamera) { - widget.controller.rotateEnd(); - } else { - if (_pointerMoving && widget.enableCamera) { - widget.controller.panEnd(); - } - } - _pointerMoving = false; - }, - child: widget.child); + onPointerHover: (event) => + _gestureHandler.onPointerHover(event.localPosition), + onPointerSignal: (PointerSignalEvent pointerSignal) { + if (pointerSignal is PointerScrollEvent) { + _gestureHandler.onPointerScroll( + pointerSignal.localPosition, pointerSignal.scrollDelta.dy); + } + }, + onPointerPanZoomStart: (pzs) { + throw Exception("TODO - is this a pinch zoom on laptop trackpad?"); + }, + onPointerDown: (d) => + _gestureHandler.onPointerDown(d.localPosition, d.buttons), + onPointerMove: (d) => _gestureHandler.onPointerMove( + d.localPosition, d.delta, d.buttons), + onPointerUp: (d) => _gestureHandler.onPointerUp(d.buttons), + child: widget.child, + ); } -} +} \ No newline at end of file