add delegate-based implementations for gesture handlers
This commit is contained in:
@@ -1,155 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'package:flutter/gestures.dart';
|
|
||||||
import 'package:logging/logging.dart';
|
|
||||||
import 'package:thermion_dart/thermion_dart/thermion_viewer.dart';
|
|
||||||
import 'package:thermion_flutter/thermion/widgets/camera/gestures/thermion_gesture_handler.dart';
|
|
||||||
|
|
||||||
class ConfigurablePanRotateGestureHandler implements ThermionGestureHandler {
|
|
||||||
|
|
||||||
final ThermionViewer viewer;
|
|
||||||
final Logger _logger = Logger("ConfigurablePanRotateGestureHandler");
|
|
||||||
|
|
||||||
ConfigurablePanRotateGestureHandler({
|
|
||||||
required this.viewer,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> onPointerHover(Offset localPosition) async {
|
|
||||||
// noop
|
|
||||||
}
|
|
||||||
|
|
||||||
// @override
|
|
||||||
// Future<void> onPointerScroll(Offset localPosition, double scrollDelta) async {
|
|
||||||
// await _zoom(localPosition, scrollDelta);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @override
|
|
||||||
// Future<void> onPointerDown(Offset localPosition, int buttons) async {
|
|
||||||
// if (buttons == kMiddleMouseButton) {
|
|
||||||
// await viewer.rotateStart(localPosition.dx, localPosition.dy);
|
|
||||||
// } else if (buttons == kPrimaryMouseButton) {
|
|
||||||
// await viewer.panStart(localPosition.dx, localPosition.dy);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @override
|
|
||||||
// Future<void> onPointerMove(
|
|
||||||
// Offset localPosition, Offset delta, int buttons) async {
|
|
||||||
// switch (_currentState) {
|
|
||||||
// case ThermionGestureState.NULL:
|
|
||||||
// break;
|
|
||||||
// case ThermionGestureState.ENTITY_HIGHLIGHTED:
|
|
||||||
// await _handleEntityHighlightedMove(localPosition);
|
|
||||||
// break;
|
|
||||||
// case ThermionGestureState.GIZMO_ATTACHED:
|
|
||||||
// break;
|
|
||||||
// case ThermionGestureState.ROTATING:
|
|
||||||
// if (enableCamera) {
|
|
||||||
// await viewer.rotateUpdate(localPosition.dx, localPosition.dy);
|
|
||||||
// }
|
|
||||||
// break;
|
|
||||||
// case ThermionGestureState.PANNING:
|
|
||||||
// if (enableCamera) {
|
|
||||||
// await viewer.panUpdate(localPosition.dx, localPosition.dy);
|
|
||||||
// }
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// @override
|
|
||||||
// Future<void> onPointerUp(int buttons) async {
|
|
||||||
// switch (_currentState) {
|
|
||||||
// case ThermionGestureState.ROTATING:
|
|
||||||
// await viewer.rotateEnd();
|
|
||||||
// _currentState = ThermionGestureState.NULL;
|
|
||||||
// break;
|
|
||||||
// case ThermionGestureState.PANNING:
|
|
||||||
// await viewer.panEnd();
|
|
||||||
// _currentState = ThermionGestureState.NULL;
|
|
||||||
// break;
|
|
||||||
// default:
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Future<void> _handleEntityHighlightedMove(Offset localPosition) async {
|
|
||||||
// if (_highlightedEntity != null) {
|
|
||||||
// await viewer.queuePositionUpdateFromViewportCoords(
|
|
||||||
// _highlightedEntity!,
|
|
||||||
// localPosition.dx,
|
|
||||||
// localPosition.dy,
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Future<void> _zoom(Offset localPosition, double scrollDelta) async {
|
|
||||||
// _scrollTimer?.cancel();
|
|
||||||
// await viewer.zoomBegin();
|
|
||||||
// await viewer.zoomUpdate(
|
|
||||||
// localPosition.dx, localPosition.dy, scrollDelta > 0 ? 1 : -1);
|
|
||||||
|
|
||||||
// _scrollTimer = Timer(const Duration(milliseconds: 100), () async {
|
|
||||||
// await viewer.zoomEnd();
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<bool> get initialized => viewer.initialized;
|
|
||||||
|
|
||||||
@override
|
|
||||||
GestureAction getActionForType(GestureType type) {
|
|
||||||
// TODO: implement getActionForType
|
|
||||||
throw UnimplementedError();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> onScaleEnd() {
|
|
||||||
// TODO: implement onScaleEnd
|
|
||||||
throw UnimplementedError();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> onScaleStart() {
|
|
||||||
// TODO: implement onScaleStart
|
|
||||||
throw UnimplementedError();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> onScaleUpdate() {
|
|
||||||
// TODO: implement onScaleUpdate
|
|
||||||
throw UnimplementedError();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void setActionForType(GestureType type, GestureAction action) {
|
|
||||||
// TODO: implement setActionForType
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> onPointerDown(Offset localPosition, int buttons) {
|
|
||||||
// TODO: implement onPointerDown
|
|
||||||
throw UnimplementedError();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> onPointerMove(Offset localPosition, Offset delta, int buttons) {
|
|
||||||
// TODO: implement onPointerMove
|
|
||||||
throw UnimplementedError();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> onPointerScroll(Offset localPosition, double scrollDelta) {
|
|
||||||
// TODO: implement onPointerScroll
|
|
||||||
throw UnimplementedError();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> onPointerUp(int buttons) {
|
|
||||||
// TODO: implement onPointerUp
|
|
||||||
throw UnimplementedError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,6 @@ import 'package:thermion_flutter/thermion/widgets/camera/gestures/thermion_gestu
|
|||||||
|
|
||||||
// Renamed implementation
|
// Renamed implementation
|
||||||
class PickingCameraGestureHandler implements ThermionGestureHandler {
|
class PickingCameraGestureHandler implements ThermionGestureHandler {
|
||||||
|
|
||||||
final ThermionViewer viewer;
|
final ThermionViewer viewer;
|
||||||
final bool enableCamera;
|
final bool enableCamera;
|
||||||
final bool enablePicking;
|
final bool enablePicking;
|
||||||
@@ -71,6 +70,7 @@ class PickingCameraGestureHandler implements ThermionGestureHandler {
|
|||||||
_highlightedEntity = targetEntity;
|
_highlightedEntity = targetEntity;
|
||||||
if (_highlightedEntity != null) {
|
if (_highlightedEntity != null) {
|
||||||
await viewer.setStencilHighlight(_highlightedEntity!);
|
await viewer.setStencilHighlight(_highlightedEntity!);
|
||||||
|
_gizmo?.attach(_highlightedEntity!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,10 +92,11 @@ class PickingCameraGestureHandler implements ThermionGestureHandler {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> onPointerScroll(Offset localPosition, double scrollDelta) async {
|
Future<void> onPointerScroll(Offset localPosition, double scrollDelta) async {
|
||||||
if(!enableCamera) {
|
if (!enableCamera) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_currentState == ThermionGestureState.NULL || _currentState == ThermionGestureState.ZOOMING) {
|
if (_currentState == ThermionGestureState.NULL ||
|
||||||
|
_currentState == ThermionGestureState.ZOOMING) {
|
||||||
await _zoom(localPosition, scrollDelta);
|
await _zoom(localPosition, scrollDelta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,7 +128,7 @@ class PickingCameraGestureHandler implements ThermionGestureHandler {
|
|||||||
await _handleEntityHighlightedMove(localPosition);
|
await _handleEntityHighlightedMove(localPosition);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (_currentState) {
|
switch (_currentState) {
|
||||||
case ThermionGestureState.NULL:
|
case ThermionGestureState.NULL:
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -2,8 +2,21 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:thermion_flutter/thermion/widgets/camera/gestures/v2/delegates.dart';
|
||||||
|
|
||||||
enum GestureType { POINTER_HOVER, POINTER_DOWN, SCALE }
|
enum GestureType {
|
||||||
|
POINTER1_DOWN,
|
||||||
|
POINTER1_MOVE,
|
||||||
|
POINTER1_UP,
|
||||||
|
POINTER1_HOVER,
|
||||||
|
POINTER2_DOWN,
|
||||||
|
POINTER2_MOVE,
|
||||||
|
POINTER2_UP,
|
||||||
|
POINTER2_HOVER,
|
||||||
|
SCALE1,
|
||||||
|
SCALE2,
|
||||||
|
POINTER_ZOOM,
|
||||||
|
}
|
||||||
|
|
||||||
enum GestureAction {
|
enum GestureAction {
|
||||||
PAN_CAMERA,
|
PAN_CAMERA,
|
||||||
@@ -21,8 +34,6 @@ enum ThermionGestureState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
abstract class ThermionGestureHandler {
|
abstract class ThermionGestureHandler {
|
||||||
GestureAction getActionForType(GestureType type);
|
|
||||||
void setActionForType(GestureType type, GestureAction action);
|
|
||||||
Future<void> onPointerHover(Offset localPosition);
|
Future<void> onPointerHover(Offset localPosition);
|
||||||
Future<void> onPointerScroll(Offset localPosition, double scrollDelta);
|
Future<void> onPointerScroll(Offset localPosition, double scrollDelta);
|
||||||
Future<void> onPointerDown(Offset localPosition, int buttons);
|
Future<void> onPointerDown(Offset localPosition, int buttons);
|
||||||
@@ -33,190 +44,7 @@ abstract class ThermionGestureHandler {
|
|||||||
Future<void> onScaleEnd();
|
Future<void> onScaleEnd();
|
||||||
Future<bool> get initialized;
|
Future<bool> get initialized;
|
||||||
void dispose();
|
void dispose();
|
||||||
|
|
||||||
|
void setActionForType(GestureType gestureType, GestureAction gestureAction);
|
||||||
|
GestureAction? getActionForType(GestureType gestureType);
|
||||||
}
|
}
|
||||||
|
|
||||||
// enum ThermionGestureState {
|
|
||||||
// NULL,
|
|
||||||
// ENTITY_HIGHLIGHTED,
|
|
||||||
// GIZMO_ATTACHED,
|
|
||||||
// ROTATING,
|
|
||||||
// PANNING,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// class ThermionGestureHandler {
|
|
||||||
// final ThermionViewer viewer;
|
|
||||||
// final bool enableCamera;
|
|
||||||
// final bool enablePicking;
|
|
||||||
// final Logger _logger = Logger("ThermionGestureHandler");
|
|
||||||
|
|
||||||
// ThermionGestureState _currentState = ThermionGestureState.NULL;
|
|
||||||
// AbstractGizmo? _gizmo;
|
|
||||||
// Timer? _scrollTimer;
|
|
||||||
// ThermionEntity? _highlightedEntity;
|
|
||||||
// StreamSubscription<FilamentPickResult>? _pickResultSubscription;
|
|
||||||
|
|
||||||
// ThermionGestureHandler({
|
|
||||||
// required this.viewer,
|
|
||||||
// this.enableCamera = true,
|
|
||||||
// this.enablePicking = true,
|
|
||||||
// }) {
|
|
||||||
// try {
|
|
||||||
// _gizmo = viewer.gizmo;
|
|
||||||
// } catch (err) {
|
|
||||||
// _logger.warning(
|
|
||||||
// "Failed to get gizmo. If you are running on WASM, this is expected");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// _pickResultSubscription = viewer.pickResult.listen(_onPickResult);
|
|
||||||
|
|
||||||
// // Add keyboard listener
|
|
||||||
// RawKeyboard.instance.addListener(_handleKeyEvent);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// void _handleKeyEvent(RawKeyEvent event) {
|
|
||||||
// if (event is RawKeyDownEvent &&
|
|
||||||
// event.logicalKey == LogicalKeyboardKey.escape) {
|
|
||||||
// _resetToNullState();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// void _resetToNullState() async {
|
|
||||||
// // set current state to NULL first, so that any subsequent pointer movements
|
|
||||||
// // won't attempt to translate a deleted entity
|
|
||||||
// _currentState = ThermionGestureState.NULL;
|
|
||||||
// if (_highlightedEntity != null) {
|
|
||||||
// await viewer.removeStencilHighlight(_highlightedEntity!);
|
|
||||||
// _highlightedEntity = null;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// void _onPickResult(FilamentPickResult result) async {
|
|
||||||
// var targetEntity = await viewer.getAncestor(result.entity) ?? result.entity;
|
|
||||||
|
|
||||||
// if (_highlightedEntity != targetEntity) {
|
|
||||||
// if (_highlightedEntity != null) {
|
|
||||||
// await viewer.removeStencilHighlight(_highlightedEntity!);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// _highlightedEntity = targetEntity;
|
|
||||||
// if (_highlightedEntity != null) {
|
|
||||||
// await viewer.setStencilHighlight(_highlightedEntity!);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// _currentState = _highlightedEntity != null
|
|
||||||
// ? ThermionGestureState.ENTITY_HIGHLIGHTED
|
|
||||||
// : ThermionGestureState.NULL;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Future<void> onPointerHover(Offset localPosition) async {
|
|
||||||
// if (_currentState == ThermionGestureState.GIZMO_ATTACHED) {
|
|
||||||
// _gizmo?.checkHover(localPosition.dx, localPosition.dy);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Update highlighted entity position
|
|
||||||
// if (_highlightedEntity != null) {
|
|
||||||
// await viewer.queuePositionUpdateFromViewportCoords(
|
|
||||||
// _highlightedEntity!,
|
|
||||||
// localPosition.dx,
|
|
||||||
// localPosition.dy,
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Future<void> onPointerScroll(Offset localPosition, double scrollDelta) async {
|
|
||||||
// if (_currentState == ThermionGestureState.NULL && enableCamera) {
|
|
||||||
// await _zoom(localPosition, scrollDelta);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Future<void> onPointerDown(Offset localPosition, int buttons) async {
|
|
||||||
// if (_currentState == ThermionGestureState.ENTITY_HIGHLIGHTED) {
|
|
||||||
// _resetToNullState();
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (enablePicking && buttons != kMiddleMouseButton) {
|
|
||||||
// viewer.pick(localPosition.dx.toInt(), localPosition.dy.toInt());
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (buttons == kMiddleMouseButton && enableCamera) {
|
|
||||||
// await viewer.rotateStart(localPosition.dx, localPosition.dy);
|
|
||||||
// _currentState = ThermionGestureState.ROTATING;
|
|
||||||
// } else if (buttons == kPrimaryMouseButton && enableCamera) {
|
|
||||||
// await viewer.panStart(localPosition.dx, localPosition.dy);
|
|
||||||
// _currentState = ThermionGestureState.PANNING;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Future<void> onPointerMove(
|
|
||||||
// Offset localPosition, Offset delta, int buttons) async {
|
|
||||||
// switch (_currentState) {
|
|
||||||
// case ThermionGestureState.NULL:
|
|
||||||
// // This case should not occur now, as we set the state on pointer down
|
|
||||||
// break;
|
|
||||||
// case ThermionGestureState.ENTITY_HIGHLIGHTED:
|
|
||||||
// await _handleEntityHighlightedMove(localPosition);
|
|
||||||
// break;
|
|
||||||
// case ThermionGestureState.GIZMO_ATTACHED:
|
|
||||||
// // Do nothing
|
|
||||||
// break;
|
|
||||||
// case ThermionGestureState.ROTATING:
|
|
||||||
// if (enableCamera) {
|
|
||||||
// await viewer.rotateUpdate(localPosition.dx, localPosition.dy);
|
|
||||||
// }
|
|
||||||
// break;
|
|
||||||
// case ThermionGestureState.PANNING:
|
|
||||||
// if (enableCamera) {
|
|
||||||
// await viewer.panUpdate(localPosition.dx, localPosition.dy);
|
|
||||||
// }
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Future<void> onPointerUp(int buttons) async {
|
|
||||||
// switch (_currentState) {
|
|
||||||
// case ThermionGestureState.ROTATING:
|
|
||||||
// await viewer.rotateEnd();
|
|
||||||
// _currentState = ThermionGestureState.NULL;
|
|
||||||
// break;
|
|
||||||
// case ThermionGestureState.PANNING:
|
|
||||||
// await viewer.panEnd();
|
|
||||||
// _currentState = ThermionGestureState.NULL;
|
|
||||||
// break;
|
|
||||||
// default:
|
|
||||||
// // For other states, no action needed
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Future<void> _handleEntityHighlightedMove(Offset localPosition) async {
|
|
||||||
// if (_highlightedEntity != null) {
|
|
||||||
// await viewer.queuePositionUpdateFromViewportCoords(
|
|
||||||
// _highlightedEntity!,
|
|
||||||
// localPosition.dx,
|
|
||||||
// localPosition.dy,
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Future<void> _zoom(Offset localPosition, double scrollDelta) async {
|
|
||||||
// _scrollTimer?.cancel();
|
|
||||||
// await viewer.zoomBegin();
|
|
||||||
// await viewer.zoomUpdate(
|
|
||||||
// localPosition.dx, localPosition.dy, scrollDelta > 0 ? 1 : -1);
|
|
||||||
|
|
||||||
// _scrollTimer = Timer(const Duration(milliseconds: 100), () async {
|
|
||||||
// await viewer.zoomEnd();
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// void dispose() {
|
|
||||||
// _pickResultSubscription?.cancel();
|
|
||||||
// if (_highlightedEntity != null) {
|
|
||||||
// viewer.removeStencilHighlight(_highlightedEntity!);
|
|
||||||
// }
|
|
||||||
// // Remove keyboard listener
|
|
||||||
// RawKeyboard.instance.removeListener(_handleKeyEvent);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:thermion_flutter/thermion/widgets/camera/gestures/thermion_gesture_handler.dart';
|
import 'package:thermion_flutter/thermion/widgets/camera/gestures/thermion_gesture_handler.dart';
|
||||||
import 'package:thermion_flutter/thermion/widgets/camera/gestures/v2/thermion_gesture_detector_desktop_widget.dart';
|
|
||||||
import 'package:thermion_flutter/thermion/widgets/camera/gestures/v2/thermion_gesture_detector_mobile_widget.dart';
|
|
||||||
|
|
||||||
///
|
///
|
||||||
/// A widget that captures swipe/pointer events.
|
/// A widget that captures swipe/pointer events.
|
||||||
/// This is a dumb listener that simply forwards events to the provided [ThermionGestureHandler].
|
/// This is a dumb listener; events are forwarded to a [ThermionGestureHandler].
|
||||||
///
|
///
|
||||||
class ThermionListenerWidget extends StatelessWidget {
|
class ThermionListenerWidget extends StatelessWidget {
|
||||||
///
|
///
|
||||||
@@ -23,16 +22,41 @@ class ThermionListenerWidget extends StatelessWidget {
|
|||||||
///
|
///
|
||||||
final ThermionGestureHandler gestureHandler;
|
final ThermionGestureHandler gestureHandler;
|
||||||
|
|
||||||
ThermionListenerWidget({
|
const ThermionListenerWidget({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.gestureHandler,
|
required this.gestureHandler,
|
||||||
this.child,
|
this.child,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
bool get isDesktop => kIsWeb ||
|
bool get isDesktop =>
|
||||||
Platform.isLinux ||
|
kIsWeb || Platform.isLinux || Platform.isWindows || Platform.isMacOS;
|
||||||
Platform.isWindows ||
|
|
||||||
Platform.isMacOS;
|
Widget _desktop() {
|
||||||
|
return Listener(
|
||||||
|
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: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _mobile() {
|
||||||
|
return _MobileListenerWidget(
|
||||||
|
gestureHandler: gestureHandler);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -43,15 +67,66 @@ class ThermionListenerWidget extends StatelessWidget {
|
|||||||
return child ?? Container();
|
return child ?? Container();
|
||||||
}
|
}
|
||||||
return Stack(children: [
|
return Stack(children: [
|
||||||
if(child != null)
|
if (child != null) Positioned.fill(child: child!),
|
||||||
Positioned.fill(child:child!),
|
|
||||||
if (isDesktop)
|
if (isDesktop)
|
||||||
Positioned.fill(child:ThermionGestureDetectorDesktop(
|
Positioned.fill(
|
||||||
gestureHandler: gestureHandler)),
|
child: _desktop()),
|
||||||
if(!isDesktop)
|
if (!isDesktop)
|
||||||
Positioned.fill(child:ThermionGestureDetectorMobile(
|
Positioned.fill(
|
||||||
gestureHandler: gestureHandler))
|
child: _mobile())
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class _MobileListenerWidget extends StatefulWidget {
|
||||||
|
final ThermionGestureHandler gestureHandler;
|
||||||
|
|
||||||
|
const _MobileListenerWidget(
|
||||||
|
{Key? key, required this.gestureHandler})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() => _MobileListenerWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MobileListenerWidgetState
|
||||||
|
extends State<_MobileListenerWidget> {
|
||||||
|
GestureAction current = GestureAction.PAN_CAMERA;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
behavior: HitTestBehavior.translucent,
|
||||||
|
onTapDown: (details) =>
|
||||||
|
widget.gestureHandler.onPointerDown(details.localPosition, 0),
|
||||||
|
onDoubleTap: () {
|
||||||
|
if (current == GestureAction.PAN_CAMERA) {
|
||||||
|
widget.gestureHandler.setActionForType(
|
||||||
|
GestureType.SCALE1, GestureAction.ROTATE_CAMERA);
|
||||||
|
current = GestureAction.ROTATE_CAMERA;
|
||||||
|
} else {
|
||||||
|
widget.gestureHandler.setActionForType(
|
||||||
|
GestureType.SCALE1, GestureAction.PAN_CAMERA);
|
||||||
|
current = GestureAction.PAN_CAMERA;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onScaleStart: (details) async {
|
||||||
|
await widget.gestureHandler.onScaleStart();
|
||||||
|
},
|
||||||
|
onScaleUpdate: (details) async {
|
||||||
|
await widget.gestureHandler.onScaleUpdate();
|
||||||
|
},
|
||||||
|
onScaleEnd: (details) async {
|
||||||
|
await widget.gestureHandler.onScaleUpdate();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:thermion_dart/thermion_dart/thermion_viewer.dart';
|
||||||
|
import 'package:thermion_flutter/thermion/widgets/camera/gestures/v2/delegates.dart';
|
||||||
|
import 'package:vector_math/vector_math_64.dart';
|
||||||
|
|
||||||
|
class DefaultPanCameraDelegate implements PanCameraDelegate {
|
||||||
|
final ThermionViewer viewer;
|
||||||
|
|
||||||
|
DefaultPanCameraDelegate(this.viewer);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> panCamera(Offset delta, Vector2? velocity) async {
|
||||||
|
// Implement panning logic here
|
||||||
|
// This is a placeholder implementation
|
||||||
|
print("Panning camera by $delta");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:thermion_flutter/thermion/widgets/camera/gestures/v2/delegates.dart';
|
||||||
|
import 'package:vector_math/vector_math_64.dart';
|
||||||
|
|
||||||
|
class DefaultVelocityDelegate extends VelocityDelegate {
|
||||||
|
Vector2? _velocity;
|
||||||
|
Timer? _decelerationTimer;
|
||||||
|
final double _decelerationFactor = 0.95;
|
||||||
|
final double _minVelocity = 0.01;
|
||||||
|
|
||||||
|
Vector2? get velocity => _velocity;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void updateVelocity(Offset delta) {
|
||||||
|
_velocity = Vector2(delta.dx, delta.dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void startDeceleration() {
|
||||||
|
if (_velocity != null && _velocity!.length > _minVelocity) {
|
||||||
|
_decelerationTimer = Timer.periodic(Duration(milliseconds: 16), (timer) {
|
||||||
|
if (_velocity == null || _velocity!.length <= _minVelocity) {
|
||||||
|
stopDeceleration();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_velocity = _velocity! * _decelerationFactor;
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void stopDeceleration() {
|
||||||
|
_decelerationTimer?.cancel();
|
||||||
|
_decelerationTimer = null;
|
||||||
|
_velocity = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
stopDeceleration();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:thermion_dart/thermion_dart/thermion_viewer.dart';
|
||||||
|
import 'package:thermion_flutter/thermion/widgets/camera/gestures/v2/delegates.dart';
|
||||||
|
import 'package:vector_math/vector_math_64.dart';
|
||||||
|
|
||||||
|
class DefaultZoomCameraDelegate implements ZoomCameraDelegate {
|
||||||
|
final ThermionViewer viewer;
|
||||||
|
final double _zoomSensitivity = 0.0005;
|
||||||
|
|
||||||
|
final double? Function(Vector3 cameraPosition)? getDistanceToTarget;
|
||||||
|
|
||||||
|
DefaultZoomCameraDelegate(this.viewer, {this.getDistanceToTarget});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> zoomCamera(double scrollDelta, Vector2? velocity) async {
|
||||||
|
Matrix4 currentModelMatrix = await viewer.getCameraModelMatrix();
|
||||||
|
final cameraRotation = currentModelMatrix.getRotation();
|
||||||
|
final cameraPosition = currentModelMatrix.getTranslation();
|
||||||
|
|
||||||
|
Vector3 forwardVector = cameraRotation.getColumn(2);
|
||||||
|
forwardVector.normalize();
|
||||||
|
|
||||||
|
double? distanceToTarget = getDistanceToTarget?.call(cameraPosition);
|
||||||
|
double zoomDistance = scrollDelta * _zoomSensitivity;
|
||||||
|
if (distanceToTarget != null) {
|
||||||
|
zoomDistance *= distanceToTarget;
|
||||||
|
if (zoomDistance.abs() < 0.0001) {
|
||||||
|
zoomDistance = scrollDelta * _zoomSensitivity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
zoomDistance = max(zoomDistance, scrollDelta * _zoomSensitivity);
|
||||||
|
|
||||||
|
Vector3 newPosition = cameraPosition + (forwardVector * zoomDistance);
|
||||||
|
await viewer.setCameraPosition(newPosition.x, newPosition.y, newPosition.z);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:thermion_flutter/thermion/widgets/camera/gestures/v2/delegates.dart';
|
||||||
|
import 'package:thermion_flutter/thermion_flutter.dart';
|
||||||
|
|
||||||
|
class DelegateGestureHandler implements ThermionGestureHandler {
|
||||||
|
final ThermionViewer viewer;
|
||||||
|
final Logger _logger = Logger("CustomGestureHandler");
|
||||||
|
|
||||||
|
ThermionGestureState _currentState = ThermionGestureState.NULL;
|
||||||
|
|
||||||
|
// Class-based delegates
|
||||||
|
RotateCameraDelegate? rotateCameraDelegate;
|
||||||
|
PanCameraDelegate? panCameraDelegate;
|
||||||
|
ZoomCameraDelegate? zoomCameraDelegate;
|
||||||
|
VelocityDelegate? velocityDelegate;
|
||||||
|
|
||||||
|
DelegateGestureHandler({
|
||||||
|
required this.viewer,
|
||||||
|
required this.rotateCameraDelegate,
|
||||||
|
required this.panCameraDelegate,
|
||||||
|
required this.zoomCameraDelegate,
|
||||||
|
required this.velocityDelegate,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onPointerDown(Offset localPosition, int buttons) async {
|
||||||
|
velocityDelegate?.stopDeceleration();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onPointerMove(
|
||||||
|
Offset localPosition, Offset delta, int buttons) async {
|
||||||
|
velocityDelegate?.updateVelocity(delta);
|
||||||
|
|
||||||
|
GestureType gestureType;
|
||||||
|
if (buttons == kPrimaryMouseButton) {
|
||||||
|
gestureType = GestureType.POINTER1_MOVE;
|
||||||
|
} else if (buttons == kSecondaryMouseButton) {
|
||||||
|
gestureType = GestureType.POINTER2_MOVE;
|
||||||
|
} else {
|
||||||
|
throw Exception("Unsupported button: $buttons");
|
||||||
|
}
|
||||||
|
|
||||||
|
var action = _actions[gestureType];
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case GestureAction.PAN_CAMERA:
|
||||||
|
_currentState = ThermionGestureState.PANNING;
|
||||||
|
await panCameraDelegate?.panCamera(delta, velocityDelegate?.velocity);
|
||||||
|
case GestureAction.ROTATE_CAMERA:
|
||||||
|
_currentState = ThermionGestureState.ROTATING;
|
||||||
|
await rotateCameraDelegate?.rotateCamera(delta, velocityDelegate?.velocity);
|
||||||
|
case null:
|
||||||
|
// ignore;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw Exception("Unsupported gesture type : $gestureType ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onPointerUp(int buttons) async {
|
||||||
|
_currentState = ThermionGestureState.NULL;
|
||||||
|
velocityDelegate?.startDeceleration();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onPointerHover(Offset localPosition) async {
|
||||||
|
// TODO, currently noop
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onPointerScroll(Offset localPosition, double scrollDelta) async {
|
||||||
|
if (_currentState != ThermionGestureState.NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_actions[GestureType.POINTER_ZOOM] != GestureAction.ZOOM_CAMERA) {
|
||||||
|
throw Exception(
|
||||||
|
"Unsupported action : ${_actions[GestureType.POINTER_ZOOM]}");
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentState = ThermionGestureState.ZOOMING;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await zoomCameraDelegate?.zoomCamera(scrollDelta, velocityDelegate?.velocity);
|
||||||
|
} catch (e) {
|
||||||
|
_logger.warning("Error during camera zoom: $e");
|
||||||
|
} finally {
|
||||||
|
_currentState = ThermionGestureState.NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
// Clean up any resources if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> get initialized => viewer.initialized;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onScaleEnd() async {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onScaleStart() async {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onScaleUpdate() async {}
|
||||||
|
|
||||||
|
final _actions = {
|
||||||
|
GestureType.POINTER1_MOVE: GestureAction.PAN_CAMERA,
|
||||||
|
GestureType.POINTER2_MOVE: GestureAction.ROTATE_CAMERA,
|
||||||
|
GestureType.POINTER_ZOOM: GestureAction.ZOOM_CAMERA
|
||||||
|
};
|
||||||
|
|
||||||
|
@override
|
||||||
|
void setActionForType(GestureType gestureType, GestureAction gestureAction) {
|
||||||
|
_actions[gestureType] = gestureAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
GestureAction? getActionForType(GestureType gestureType) {
|
||||||
|
return _actions[gestureType];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:vector_math/vector_math_64.dart';
|
||||||
|
|
||||||
|
abstract class RotateCameraDelegate {
|
||||||
|
Future<void> rotateCamera(Offset delta, Vector2? velocity);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class PanCameraDelegate {
|
||||||
|
Future<void> panCamera(Offset delta, Vector2? velocity);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class ZoomCameraDelegate {
|
||||||
|
Future<void> zoomCamera(double scrollDelta, Vector2? velocity);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class VelocityDelegate {
|
||||||
|
Vector2? get velocity;
|
||||||
|
|
||||||
|
void updateVelocity(Offset delta);
|
||||||
|
|
||||||
|
void startDeceleration();
|
||||||
|
|
||||||
|
void stopDeceleration();
|
||||||
|
|
||||||
|
void dispose() {
|
||||||
|
stopDeceleration();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:thermion_dart/thermion_dart/thermion_viewer.dart';
|
||||||
|
import 'package:thermion_flutter/thermion/widgets/camera/gestures/v2/delegates.dart';
|
||||||
|
import 'package:vector_math/vector_math_64.dart';
|
||||||
|
|
||||||
|
class FixedOrbitRotateCameraDelegate implements RotateCameraDelegate {
|
||||||
|
final ThermionViewer viewer;
|
||||||
|
static final _up = Vector3(0, 1, 0);
|
||||||
|
static final _forward = Vector3(0, 0, -1);
|
||||||
|
|
||||||
|
static const double _rotationSensitivity = 0.01;
|
||||||
|
|
||||||
|
FixedOrbitRotateCameraDelegate(this.viewer);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> rotateCamera(Offset delta, Vector2? velocity) async {
|
||||||
|
double deltaX = delta.dx;
|
||||||
|
double deltaY = delta.dy;
|
||||||
|
deltaX *= _rotationSensitivity * viewer.pixelRatio;
|
||||||
|
deltaY *= _rotationSensitivity * viewer.pixelRatio;
|
||||||
|
|
||||||
|
Matrix4 currentModelMatrix = await viewer.getCameraModelMatrix();
|
||||||
|
Vector3 currentPosition = currentModelMatrix.getTranslation();
|
||||||
|
double distance = currentPosition.length;
|
||||||
|
Quaternion currentRotation =
|
||||||
|
Quaternion.fromRotation(currentModelMatrix.getRotation());
|
||||||
|
|
||||||
|
Quaternion yawRotation = Quaternion.axisAngle(_up, -deltaX);
|
||||||
|
Vector3 right = _up.cross(_forward)..normalize();
|
||||||
|
Quaternion pitchRotation = Quaternion.axisAngle(right, -deltaY);
|
||||||
|
|
||||||
|
Quaternion newRotation = currentRotation * yawRotation * pitchRotation;
|
||||||
|
newRotation.normalize();
|
||||||
|
|
||||||
|
Vector3 newPosition = _forward.clone()
|
||||||
|
..applyQuaternion(newRotation)
|
||||||
|
..scale(-distance);
|
||||||
|
|
||||||
|
Matrix4 newModelMatrix =
|
||||||
|
Matrix4.compose(newPosition, newRotation, Vector3(1, 1, 1));
|
||||||
|
await viewer.setCameraModelMatrix4(newModelMatrix);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,11 +5,9 @@ class ThermionGestureDetectorMobile extends StatefulWidget {
|
|||||||
final Widget? child;
|
final Widget? child;
|
||||||
final ThermionGestureHandler gestureHandler;
|
final ThermionGestureHandler gestureHandler;
|
||||||
|
|
||||||
const ThermionGestureDetectorMobile({
|
const ThermionGestureDetectorMobile(
|
||||||
Key? key,
|
{Key? key, required this.gestureHandler, this.child})
|
||||||
required this.gestureHandler,
|
: super(key: key);
|
||||||
this.child,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() => _ThermionGestureDetectorMobileState();
|
State<StatefulWidget> createState() => _ThermionGestureDetectorMobileState();
|
||||||
@@ -17,6 +15,13 @@ class ThermionGestureDetectorMobile extends StatefulWidget {
|
|||||||
|
|
||||||
class _ThermionGestureDetectorMobileState
|
class _ThermionGestureDetectorMobileState
|
||||||
extends State<ThermionGestureDetectorMobile> {
|
extends State<ThermionGestureDetectorMobile> {
|
||||||
|
GestureAction current = GestureAction.PAN_CAMERA;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Stack(children: [
|
return Stack(children: [
|
||||||
@@ -26,18 +31,21 @@ class _ThermionGestureDetectorMobileState
|
|||||||
onTapDown: (details) =>
|
onTapDown: (details) =>
|
||||||
widget.gestureHandler.onPointerDown(details.localPosition, 0),
|
widget.gestureHandler.onPointerDown(details.localPosition, 0),
|
||||||
onDoubleTap: () {
|
onDoubleTap: () {
|
||||||
var current = widget.gestureHandler.getActionForType(GestureType.SCALE);
|
if (current == GestureAction.PAN_CAMERA) {
|
||||||
if(current == GestureAction.PAN_CAMERA) {
|
widget.gestureHandler.setActionForType(
|
||||||
widget.gestureHandler.setActionForType(GestureType.SCALE, GestureAction.ROTATE_CAMERA);
|
GestureType.SCALE1, GestureAction.ROTATE_CAMERA);
|
||||||
|
current = GestureAction.ROTATE_CAMERA;
|
||||||
} else {
|
} else {
|
||||||
widget.gestureHandler.setActionForType(GestureType.SCALE, GestureAction.PAN_CAMERA);
|
widget.gestureHandler.setActionForType(
|
||||||
|
GestureType.SCALE1, GestureAction.PAN_CAMERA);
|
||||||
|
current = GestureAction.PAN_CAMERA;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onScaleStart: (details) async {
|
onScaleStart: (details) async {
|
||||||
await widget.gestureHandler.onScaleStart();
|
await widget.gestureHandler.onScaleStart();
|
||||||
},
|
},
|
||||||
onScaleUpdate: (details) async {
|
onScaleUpdate: (details) async {
|
||||||
await widget.gestureHandler.onScaleUpdate();
|
await widget.gestureHandler.onScaleUpdate();
|
||||||
},
|
},
|
||||||
onScaleEnd: (details) async {
|
onScaleEnd: (details) async {
|
||||||
await widget.gestureHandler.onScaleUpdate();
|
await widget.gestureHandler.onScaleUpdate();
|
||||||
@@ -45,7 +53,6 @@ class _ThermionGestureDetectorMobileState
|
|||||||
child: widget.child,
|
child: widget.child,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user