(flutter) add experimental GestureHandler widget and decouple from ThermionGestureDetectorDesktop
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:thermion_dart/thermion_dart/entities/abstract_gizmo.dart';
|
||||
import 'package:thermion_dart/thermion_dart/thermion_viewer.dart';
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user