chore: rearrange library dirs, gesture handler improvements
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
analyzer:
|
||||
errors:
|
||||
constant_identifier_names: ignore
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:thermion_dart/thermion_dart/thermion_viewer.dart';
|
||||
import 'package:thermion_flutter/src/gestures/delegates.dart';
|
||||
import 'package:vector_math/vector_math_64.dart';
|
||||
|
||||
class DefaultKeyboardCameraFlightDelegate
|
||||
{
|
||||
final ThermionViewer viewer;
|
||||
|
||||
static const double _panSensitivity = 0.005;
|
||||
static const double _keyMoveSensitivity = 0.1;
|
||||
|
||||
final Map<PhysicalKeyboardKey, bool> _pressedKeys = {};
|
||||
Timer? _moveTimer;
|
||||
|
||||
DefaultKeyboardCameraFlightDelegate(this.viewer) {
|
||||
_startMoveLoop();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> panCamera(Offset delta, Vector2? velocity) async {
|
||||
double deltaX = delta.dx;
|
||||
double deltaY = delta.dy;
|
||||
deltaX *= _panSensitivity * viewer.pixelRatio;
|
||||
deltaY *= _panSensitivity * viewer.pixelRatio;
|
||||
|
||||
await _moveCamera(deltaX, deltaY, 0);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onKeypress(PhysicalKeyboardKey key) async {
|
||||
_pressedKeys[key] = true;
|
||||
}
|
||||
|
||||
// New method to handle key release
|
||||
Future<void> onKeyRelease(PhysicalKeyboardKey key) async {
|
||||
_pressedKeys.remove(key);
|
||||
}
|
||||
|
||||
void _startMoveLoop() {
|
||||
_moveTimer = Timer.periodic(
|
||||
Duration(milliseconds: 16), (_) => _processKeyboardInput());
|
||||
}
|
||||
|
||||
Future<void> _processKeyboardInput() async {
|
||||
double dx = 0, dy = 0, dz = 0;
|
||||
|
||||
if (_pressedKeys[PhysicalKeyboardKey.keyW] == true)
|
||||
dz += _keyMoveSensitivity;
|
||||
if (_pressedKeys[PhysicalKeyboardKey.keyS] == true)
|
||||
dz -= _keyMoveSensitivity;
|
||||
if (_pressedKeys[PhysicalKeyboardKey.keyA] == true)
|
||||
dx -= _keyMoveSensitivity;
|
||||
if (_pressedKeys[PhysicalKeyboardKey.keyD] == true)
|
||||
dx += _keyMoveSensitivity;
|
||||
|
||||
if (dx != 0 || dy != 0 || dz != 0) {
|
||||
await _moveCamera(dx, dy, dz);
|
||||
}
|
||||
// Removed _pressedKeys.clear(); from here
|
||||
}
|
||||
|
||||
Future<void> _moveCamera(double dx, double dy, double dz) async {
|
||||
Matrix4 currentModelMatrix = await viewer.getCameraModelMatrix();
|
||||
Vector3 currentPosition = currentModelMatrix.getTranslation();
|
||||
Quaternion currentRotation =
|
||||
Quaternion.fromRotation(currentModelMatrix.getRotation());
|
||||
|
||||
Vector3 forward = Vector3(0, 0, -1)..applyQuaternion(currentRotation);
|
||||
Vector3 right = Vector3(1, 0, 0)..applyQuaternion(currentRotation);
|
||||
Vector3 up = Vector3(0, 1, 0)..applyQuaternion(currentRotation);
|
||||
|
||||
Vector3 moveOffset = right * dx + up * dy + forward * dz;
|
||||
Vector3 newPosition = currentPosition + moveOffset;
|
||||
|
||||
Matrix4 newModelMatrix =
|
||||
Matrix4.compose(newPosition, currentRotation, Vector3(1, 1, 1));
|
||||
await viewer.setCameraModelMatrix4(newModelMatrix);
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_moveTimer?.cancel();
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
// 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;
|
||||
|
||||
// static const double _panSensitivity = 0.005;
|
||||
|
||||
// DefaultPanCameraDelegate(this.viewer);
|
||||
// static const double _panSensitivity = 0.005;
|
||||
|
||||
// @override
|
||||
// Future<void> panCamera(Offset delta, Vector2? velocity) async {
|
||||
// double deltaX = delta.dx;
|
||||
// double deltaY = delta.dy;
|
||||
// deltaX *= _panSensitivity * viewer.pixelRatio;
|
||||
// deltaY *= _panSensitivity * viewer.pixelRatio;
|
||||
|
||||
// Matrix4 currentModelMatrix = await viewer.getCameraModelMatrix();
|
||||
// Vector3 currentPosition = currentModelMatrix.getTranslation();
|
||||
// Quaternion currentRotation = Quaternion.fromRotation(currentModelMatrix.getRotation());
|
||||
|
||||
// Vector3 right = Vector3(1, 0, 0)..applyQuaternion(currentRotation);
|
||||
// Vector3 up = Vector3(0, 1, 0)..applyQuaternion(currentRotation);
|
||||
|
||||
// Vector3 panOffset = right * -deltaX + up * deltaY;
|
||||
// Vector3 newPosition = currentPosition + panOffset;
|
||||
|
||||
// Matrix4 newModelMatrix = Matrix4.compose(newPosition, currentRotation, Vector3(1, 1, 1));
|
||||
// await viewer.setCameraModelMatrix4(newModelMatrix);
|
||||
// }
|
||||
// }
|
||||
@@ -1,15 +0,0 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:thermion_dart/thermion_dart/thermion_viewer.dart';
|
||||
import 'package:thermion_flutter/src/gestures/delegates.dart';
|
||||
|
||||
class DefaultPickDelegate extends PickDelegate {
|
||||
final ThermionViewer _viewer;
|
||||
|
||||
const DefaultPickDelegate(this._viewer);
|
||||
|
||||
@override
|
||||
void pick(Offset location) {
|
||||
_viewer.pick(location.dx.toInt(), location.dy.toInt());
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:thermion_flutter/src/gestures/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();
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thermion_dart/thermion_dart/thermion_viewer.dart';
|
||||
import 'package:thermion_flutter/src/gestures/delegates.dart';
|
||||
import 'package:vector_math/vector_math_64.dart';
|
||||
|
||||
class DefaultZoomCameraDelegate {
|
||||
final ThermionViewer viewer;
|
||||
final double zoomSensitivity;
|
||||
|
||||
final double? Function(Vector3 cameraPosition)? getDistanceToTarget;
|
||||
|
||||
DefaultZoomCameraDelegate(this.viewer,
|
||||
{this.zoomSensitivity = 0.005, this.getDistanceToTarget});
|
||||
|
||||
///
|
||||
/// Converts the given [scrollDelta] (usually somewhere between 1 and -1) to
|
||||
/// a percentage of the current camera distance (either to the origin,
|
||||
/// or to a custom target) along its forward vector.
|
||||
/// In other words, "shift "
|
||||
///
|
||||
double calculateZoomFactor(
|
||||
double scrollDelta, Vector2? velocity) {
|
||||
double zoomFactor = scrollDelta * zoomSensitivity;
|
||||
if (zoomFactor.abs() < 0.0001) {
|
||||
zoomFactor = scrollDelta * zoomSensitivity;
|
||||
}
|
||||
return zoomFactor;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> zoom(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();
|
||||
|
||||
var zoomDistance =
|
||||
calculateZoomFactor(scrollDelta, velocity);
|
||||
|
||||
Vector3 newPosition = cameraPosition + (forwardVector * zoomDistance);
|
||||
await viewer.setCameraPosition(newPosition.x, newPosition.y, newPosition.z);
|
||||
}
|
||||
}
|
||||
@@ -1,245 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:thermion_flutter/src/gestures/default_pick_delegate.dart';
|
||||
import 'package:thermion_flutter/src/gestures/default_velocity_delegate.dart';
|
||||
import 'package:thermion_flutter/src/gestures/delegates.dart';
|
||||
import 'package:thermion_flutter/src/gestures/fixed_orbit_camera_rotation_delegate.dart';
|
||||
import 'package:thermion_flutter/src/gestures/free_flight_camera_delegate.dart';
|
||||
import 'package:thermion_flutter/thermion_flutter.dart';
|
||||
import 'package:vector_math/vector_math_64.dart';
|
||||
|
||||
class DelegateGestureHandler implements ThermionGestureHandler {
|
||||
final ThermionViewer viewer;
|
||||
final Logger _logger = Logger("CustomGestureHandler");
|
||||
|
||||
CameraDelegate? cameraDelegate;
|
||||
VelocityDelegate? velocityDelegate;
|
||||
PickDelegate? pickDelegate;
|
||||
|
||||
Ticker? _ticker;
|
||||
|
||||
Map<GestureType, Offset> _accumulatedDeltas = {};
|
||||
double _accumulatedScrollDelta = 0.0;
|
||||
int _activePointers = 0;
|
||||
bool _isMiddleMouseButtonPressed = false;
|
||||
|
||||
VoidCallback? _keyboardListenerDisposer;
|
||||
|
||||
final Map<GestureType, GestureAction> _actions = {
|
||||
GestureType.LMB_HOLD_AND_MOVE: GestureAction.PAN_CAMERA,
|
||||
GestureType.MMB_HOLD_AND_MOVE: GestureAction.ROTATE_CAMERA,
|
||||
GestureType.SCROLLWHEEL: GestureAction.ZOOM_CAMERA,
|
||||
GestureType.POINTER_MOVE: GestureAction.NONE,
|
||||
};
|
||||
|
||||
DelegateGestureHandler({
|
||||
required this.viewer,
|
||||
required this.cameraDelegate,
|
||||
required this.velocityDelegate,
|
||||
this.pickDelegate,
|
||||
Map<GestureType, GestureAction>? actions,
|
||||
}) {
|
||||
_initializeKeyboardListener();
|
||||
if (actions != null) {
|
||||
_actions.addAll(actions);
|
||||
}
|
||||
_initializeAccumulatedDeltas();
|
||||
}
|
||||
|
||||
factory DelegateGestureHandler.fixedOrbit(ThermionViewer viewer,
|
||||
{double minimumDistance = 10.0,
|
||||
double? Function(Vector3)? getDistanceToTarget,
|
||||
PickDelegate? pickDelegate}) =>
|
||||
DelegateGestureHandler(
|
||||
viewer: viewer,
|
||||
pickDelegate: pickDelegate,
|
||||
cameraDelegate: FixedOrbitRotateCameraDelegate(viewer,
|
||||
getDistanceToTarget: getDistanceToTarget,
|
||||
minimumDistance: minimumDistance),
|
||||
velocityDelegate: DefaultVelocityDelegate(),
|
||||
actions: {GestureType.MMB_HOLD_AND_MOVE:GestureAction.ROTATE_CAMERA}
|
||||
);
|
||||
|
||||
factory DelegateGestureHandler.flight(ThermionViewer viewer,
|
||||
{PickDelegate? pickDelegate}) =>
|
||||
DelegateGestureHandler(
|
||||
viewer: viewer,
|
||||
pickDelegate: pickDelegate,
|
||||
cameraDelegate: FreeFlightCameraDelegate(viewer),
|
||||
velocityDelegate: DefaultVelocityDelegate(),
|
||||
actions: {GestureType.POINTER_MOVE: GestureAction.ROTATE_CAMERA},
|
||||
);
|
||||
|
||||
void _initializeAccumulatedDeltas() {
|
||||
for (var gestureType in GestureType.values) {
|
||||
_accumulatedDeltas[gestureType] = Offset.zero;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _applyAccumulatedUpdates() async {
|
||||
for (var gestureType in GestureType.values) {
|
||||
Offset delta = _accumulatedDeltas[gestureType] ?? Offset.zero;
|
||||
if (delta != Offset.zero) {
|
||||
velocityDelegate?.updateVelocity(delta);
|
||||
|
||||
var action = _actions[gestureType];
|
||||
switch (action) {
|
||||
case GestureAction.PAN_CAMERA:
|
||||
await cameraDelegate?.pan(delta, velocityDelegate?.velocity);
|
||||
break;
|
||||
case GestureAction.ROTATE_CAMERA:
|
||||
await cameraDelegate?.rotate(delta, velocityDelegate?.velocity);
|
||||
break;
|
||||
case GestureAction.NONE:
|
||||
// Do nothing
|
||||
break;
|
||||
default:
|
||||
_logger.warning(
|
||||
"Unsupported gesture action: $action for type: $gestureType");
|
||||
break;
|
||||
}
|
||||
|
||||
_accumulatedDeltas[gestureType] = Offset.zero;
|
||||
}
|
||||
}
|
||||
|
||||
if (_accumulatedScrollDelta != 0.0) {
|
||||
await cameraDelegate?.zoom(
|
||||
_accumulatedScrollDelta, velocityDelegate?.velocity);
|
||||
_accumulatedScrollDelta = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onPointerDown(Offset localPosition, int buttons) async {
|
||||
velocityDelegate?.stopDeceleration();
|
||||
_activePointers++;
|
||||
if (buttons & kMiddleMouseButton != 0) {
|
||||
_isMiddleMouseButtonPressed = true;
|
||||
}
|
||||
if (buttons & kPrimaryButton != 0) {
|
||||
final action = _actions[GestureType.LMB_DOWN];
|
||||
switch (action) {
|
||||
case GestureAction.PICK_ENTITY:
|
||||
pickDelegate?.pick(localPosition);
|
||||
default:
|
||||
// noop
|
||||
}
|
||||
}
|
||||
await _applyAccumulatedUpdates();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onPointerMove(
|
||||
Offset localPosition, Offset delta, int buttons) async {
|
||||
GestureType gestureType = _getGestureTypeFromButtons(buttons);
|
||||
if (gestureType == GestureType.MMB_HOLD_AND_MOVE ||
|
||||
(_actions[GestureType.POINTER_MOVE] == GestureAction.ROTATE_CAMERA &&
|
||||
gestureType == GestureType.POINTER_MOVE)) {
|
||||
_accumulatedDeltas[GestureType.MMB_HOLD_AND_MOVE] =
|
||||
(_accumulatedDeltas[GestureType.MMB_HOLD_AND_MOVE] ?? Offset.zero) +
|
||||
delta;
|
||||
} else {
|
||||
_accumulatedDeltas[gestureType] =
|
||||
(_accumulatedDeltas[gestureType] ?? Offset.zero) + delta;
|
||||
}
|
||||
await _applyAccumulatedUpdates();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onPointerUp(int buttons) async {
|
||||
_activePointers--;
|
||||
if (_activePointers == 0) {
|
||||
velocityDelegate?.startDeceleration();
|
||||
}
|
||||
if (buttons & kMiddleMouseButton != 0) {
|
||||
_isMiddleMouseButtonPressed = false;
|
||||
}
|
||||
}
|
||||
|
||||
GestureType _getGestureTypeFromButtons(int buttons) {
|
||||
if (buttons & kPrimaryMouseButton != 0) {
|
||||
return GestureType.LMB_HOLD_AND_MOVE;
|
||||
}
|
||||
if (buttons & kMiddleMouseButton != 0 || _isMiddleMouseButtonPressed) {
|
||||
return GestureType.MMB_HOLD_AND_MOVE;
|
||||
}
|
||||
return GestureType.POINTER_MOVE;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onPointerHover(Offset localPosition, Offset delta) async {
|
||||
if (_actions[GestureType.POINTER_MOVE] == GestureAction.ROTATE_CAMERA) {
|
||||
_accumulatedDeltas[GestureType.POINTER_MOVE] =
|
||||
(_accumulatedDeltas[GestureType.POINTER_MOVE] ?? Offset.zero) + delta;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onPointerScroll(Offset localPosition, double scrollDelta) async {
|
||||
if (_actions[GestureType.SCROLLWHEEL] != GestureAction.ZOOM_CAMERA) {
|
||||
throw Exception(
|
||||
"Unsupported action: ${_actions[GestureType.SCROLLWHEEL]}");
|
||||
}
|
||||
|
||||
try {
|
||||
_accumulatedScrollDelta += scrollDelta;
|
||||
} catch (e) {
|
||||
_logger.warning("Error during scroll accumulation: $e");
|
||||
}
|
||||
await _applyAccumulatedUpdates();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
velocityDelegate?.dispose();
|
||||
_keyboardListenerDisposer?.call();
|
||||
_ticker?.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> get initialized => viewer.initialized;
|
||||
|
||||
@override
|
||||
Future<void> onScaleEnd() async {}
|
||||
|
||||
@override
|
||||
Future<void> onScaleStart() async {}
|
||||
|
||||
@override
|
||||
Future<void> onScaleUpdate() async {}
|
||||
|
||||
@override
|
||||
void setActionForType(GestureType gestureType, GestureAction gestureAction) {
|
||||
_actions[gestureType] = gestureAction;
|
||||
}
|
||||
|
||||
@override
|
||||
GestureAction? getActionForType(GestureType gestureType) {
|
||||
return _actions[gestureType];
|
||||
}
|
||||
|
||||
void _initializeKeyboardListener() {
|
||||
HardwareKeyboard.instance.addHandler(_handleKeyEvent);
|
||||
_keyboardListenerDisposer = () {
|
||||
HardwareKeyboard.instance.removeHandler(_handleKeyEvent);
|
||||
};
|
||||
}
|
||||
|
||||
bool _handleKeyEvent(KeyEvent event) {
|
||||
if (_actions[GestureType.KEYDOWN] == GestureAction.NONE) {
|
||||
return false;
|
||||
}
|
||||
if (event is KeyDownEvent || event is KeyRepeatEvent) {
|
||||
cameraDelegate?.onKeypress(event.physicalKey);
|
||||
return true;
|
||||
} else if (event is KeyUpEvent) {
|
||||
cameraDelegate?.onKeyRelease(event.physicalKey);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import 'dart:ui';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:vector_math/vector_math_64.dart';
|
||||
|
||||
abstract class CameraDelegate {
|
||||
Future<void> rotate(Offset delta, Vector2? velocity);
|
||||
Future<void> pan(Offset delta, Vector2? velocity);
|
||||
Future<void> zoom(double yScrollDeltaInPixels, Vector2? velocity);
|
||||
Future<void> onKeypress(PhysicalKeyboardKey key);
|
||||
Future<void> onKeyRelease(PhysicalKeyboardKey key);
|
||||
}
|
||||
|
||||
abstract class VelocityDelegate {
|
||||
Vector2? get velocity;
|
||||
|
||||
void updateVelocity(Offset delta);
|
||||
|
||||
void startDeceleration();
|
||||
|
||||
void stopDeceleration();
|
||||
|
||||
void dispose() {
|
||||
stopDeceleration();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class PickDelegate {
|
||||
const PickDelegate();
|
||||
void pick(Offset location);
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:thermion_dart/thermion_dart/thermion_viewer.dart';
|
||||
import 'package:thermion_flutter/src/gestures/delegates.dart';
|
||||
import 'package:vector_math/vector_math_64.dart';
|
||||
|
||||
/// A camera delegate that rotates the camera around the origin.
|
||||
/// Panning is not permitted; zooming is permitted (up to a minimum distance)
|
||||
///
|
||||
/// The rotation sensitivity will be automatically adjusted so that
|
||||
/// 100 horizontal pixels equates to a geodetic distance of 1m when the camera
|
||||
/// is 1m from the surface (denoted by distanceToSurface). This scales to 10m
|
||||
/// geodetic distance when the camera is 100m from the surface, 100m when the
|
||||
/// camera is 1000m from the surface, and so on.
|
||||
///
|
||||
///
|
||||
class FixedOrbitRotateCameraDelegate implements CameraDelegate {
|
||||
final ThermionViewer viewer;
|
||||
final double minimumDistance;
|
||||
double? Function(Vector3)? getDistanceToTarget;
|
||||
|
||||
Offset _accumulatedRotationDelta = Offset.zero;
|
||||
double _accumulatedZoomDelta = 0.0;
|
||||
|
||||
static final _up = Vector3(0, 1, 0);
|
||||
Timer? _updateTimer;
|
||||
|
||||
FixedOrbitRotateCameraDelegate(
|
||||
this.viewer, {
|
||||
this.getDistanceToTarget,
|
||||
this.minimumDistance = 10.0,
|
||||
});
|
||||
|
||||
void dispose() {
|
||||
_updateTimer?.cancel();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> rotate(Offset delta, Vector2? velocity) async {
|
||||
_accumulatedRotationDelta += delta;
|
||||
await _applyAccumulatedUpdates();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> pan(Offset delta, Vector2? velocity) {
|
||||
throw UnimplementedError("Not supported in fixed orbit mode");
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> zoom(double yScrollDeltaInPixels, Vector2? velocity) async {
|
||||
_accumulatedZoomDelta += yScrollDeltaInPixels > 0 ? 1 : -1;
|
||||
await _applyAccumulatedUpdates();
|
||||
}
|
||||
|
||||
Future<void> _applyAccumulatedUpdates() async {
|
||||
if (_accumulatedRotationDelta.distanceSquared == 0.0 &&
|
||||
_accumulatedZoomDelta == 0.0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var viewMatrix = await viewer.getCameraViewMatrix();
|
||||
var modelMatrix = await viewer.getCameraModelMatrix();
|
||||
var projectionMatrix = await viewer.getCameraProjectionMatrix();
|
||||
var inverseProjectionMatrix = projectionMatrix.clone()..invert();
|
||||
Vector3 currentPosition = modelMatrix.getTranslation();
|
||||
|
||||
Vector3 forward = -currentPosition.normalized();
|
||||
Vector3 right = _up.cross(forward).normalized();
|
||||
Vector3 up = forward.cross(right);
|
||||
|
||||
// first, we find the point in the sphere that intersects with the camera
|
||||
// forward vector
|
||||
double radius = 0.0;
|
||||
double? distanceToTarget = getDistanceToTarget?.call(currentPosition);
|
||||
if (distanceToTarget != null) {
|
||||
radius = currentPosition.length - distanceToTarget;
|
||||
} else {
|
||||
radius = 1.0;
|
||||
}
|
||||
Vector3 intersection = (-forward).scaled(radius);
|
||||
|
||||
// next, calculate the depth value at that intersection point
|
||||
final intersectionInViewSpace = viewMatrix *
|
||||
Vector4(intersection.x, intersection.y, intersection.z, 1.0);
|
||||
final intersectionInClipSpace = projectionMatrix * intersectionInViewSpace;
|
||||
final intersectionInNdcSpace =
|
||||
intersectionInClipSpace / intersectionInClipSpace.w;
|
||||
|
||||
// using that depth value, find the world space position of the mouse
|
||||
// note we flip the signs of the X and Y values
|
||||
|
||||
final ndcX = 2 *
|
||||
((-_accumulatedRotationDelta.dx * viewer.pixelRatio) /
|
||||
viewer.viewportDimensions.$1);
|
||||
final ndcY = 2 *
|
||||
((_accumulatedRotationDelta.dy * viewer.pixelRatio) /
|
||||
viewer.viewportDimensions.$2);
|
||||
final ndc = Vector4(ndcX, ndcY, intersectionInNdcSpace.z, 1.0);
|
||||
|
||||
var clipSpace = Vector4(
|
||||
ndc.x * intersectionInClipSpace.w,
|
||||
ndcY * intersectionInClipSpace.w,
|
||||
ndc.z * intersectionInClipSpace.w,
|
||||
intersectionInClipSpace.w);
|
||||
Vector4 cameraSpace = inverseProjectionMatrix * clipSpace;
|
||||
Vector4 worldSpace = modelMatrix * cameraSpace;
|
||||
|
||||
// the new camera world space position will be that position,
|
||||
// scaled to the camera's current distance
|
||||
var worldSpace3 = worldSpace.xyz.normalized() * currentPosition.length;
|
||||
currentPosition = worldSpace3;
|
||||
|
||||
// Apply zoom
|
||||
if (_accumulatedZoomDelta != 0.0) {
|
||||
// double zoomFactor = 1.0 + ();
|
||||
Vector3 toSurface = currentPosition - intersection;
|
||||
currentPosition = currentPosition + toSurface.scaled(_accumulatedZoomDelta * 0.1);
|
||||
_accumulatedZoomDelta = 0.0;
|
||||
}
|
||||
|
||||
// Ensure minimum distance
|
||||
if (currentPosition.length < radius + minimumDistance) {
|
||||
currentPosition =
|
||||
(currentPosition.normalized() * (radius + minimumDistance));
|
||||
}
|
||||
|
||||
// Calculate view matrix
|
||||
forward = -currentPosition.normalized();
|
||||
right = _up.cross(forward).normalized();
|
||||
up = forward.cross(right);
|
||||
|
||||
Matrix4 newViewMatrix = makeViewMatrix(currentPosition, Vector3.zero(), up);
|
||||
newViewMatrix.invert();
|
||||
|
||||
// Set the camera model matrix
|
||||
await viewer.setCameraModelMatrix4(newViewMatrix);
|
||||
_accumulatedRotationDelta = Offset.zero;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onKeyRelease(PhysicalKeyboardKey key) async {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onKeypress(PhysicalKeyboardKey key) async {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:thermion_dart/thermion_dart/thermion_viewer.dart';
|
||||
import 'package:thermion_flutter/src/gestures/delegates.dart';
|
||||
import 'package:vector_math/vector_math_64.dart';
|
||||
|
||||
class FreeFlightCameraDelegate implements CameraDelegate {
|
||||
final ThermionViewer viewer;
|
||||
|
||||
final Vector3? minBounds;
|
||||
final Vector3? maxBounds;
|
||||
|
||||
final double rotationSensitivity;
|
||||
final double movementSensitivity;
|
||||
final double zoomSensitivity;
|
||||
final double panSensitivity;
|
||||
final double keyMoveSensitivity;
|
||||
|
||||
static final _up = Vector3(0, 1, 0);
|
||||
static final _forward = Vector3(0, 0, -1);
|
||||
static final Vector3 _right = Vector3(1, 0, 0);
|
||||
|
||||
Offset _accumulatedRotation = Offset.zero;
|
||||
Offset _accumulatedPan = Offset.zero;
|
||||
double _accumulatedZoom = 0.0;
|
||||
Vector2? _lastVelocity;
|
||||
|
||||
Ticker? _ticker;
|
||||
Timer? _moveTimer;
|
||||
final Map<PhysicalKeyboardKey, bool> _pressedKeys = {};
|
||||
|
||||
FreeFlightCameraDelegate(
|
||||
this.viewer, {
|
||||
this.minBounds,
|
||||
this.maxBounds,
|
||||
this.rotationSensitivity = 0.001,
|
||||
this.movementSensitivity = 0.1,
|
||||
this.zoomSensitivity = 0.1,
|
||||
this.panSensitivity = 0.01,
|
||||
this.keyMoveSensitivity = 0.1,
|
||||
}) {
|
||||
_initializeTicker();
|
||||
_startMoveLoop();
|
||||
}
|
||||
|
||||
void _initializeTicker() {
|
||||
_ticker = Ticker(_onTick);
|
||||
_ticker!.start();
|
||||
}
|
||||
|
||||
void _startMoveLoop() {
|
||||
_moveTimer = Timer.periodic(
|
||||
Duration(milliseconds: 16), (_) => _processKeyboardInput());
|
||||
}
|
||||
|
||||
void _onTick(Duration elapsed) {
|
||||
_applyAccumulatedUpdates();
|
||||
}
|
||||
|
||||
Future<void> _applyAccumulatedUpdates() async {
|
||||
if (_accumulatedRotation != Offset.zero ||
|
||||
_accumulatedPan != Offset.zero ||
|
||||
_accumulatedZoom != 0.0) {
|
||||
Matrix4 currentModelMatrix = await viewer.getCameraModelMatrix();
|
||||
Vector3 currentPosition = currentModelMatrix.getTranslation();
|
||||
Quaternion currentRotation =
|
||||
Quaternion.fromRotation(currentModelMatrix.getRotation());
|
||||
|
||||
// Apply rotation
|
||||
if (_accumulatedRotation != Offset.zero) {
|
||||
double deltaX = _accumulatedRotation.dx * rotationSensitivity * viewer.pixelRatio;
|
||||
double deltaY = _accumulatedRotation.dy * rotationSensitivity * viewer.pixelRatio;
|
||||
double deltaZ = (_accumulatedRotation.dx + _accumulatedRotation.dy) * rotationSensitivity * 0.5 * viewer.pixelRatio;
|
||||
|
||||
Quaternion yawRotation = Quaternion.axisAngle(_up, -deltaX);
|
||||
Quaternion pitchRotation = Quaternion.axisAngle(_right, -deltaY);
|
||||
Quaternion rollRotation = Quaternion.axisAngle(_forward, deltaZ);
|
||||
|
||||
currentRotation = currentRotation * rollRotation * pitchRotation * yawRotation ;
|
||||
currentRotation.normalize();
|
||||
|
||||
_accumulatedRotation = Offset.zero;
|
||||
}
|
||||
|
||||
// Apply pan
|
||||
if (_accumulatedPan != Offset.zero) {
|
||||
Vector3 right = _right.clone()..applyQuaternion(currentRotation);
|
||||
Vector3 up = _up.clone()..applyQuaternion(currentRotation);
|
||||
|
||||
double deltaX = _accumulatedPan.dx * panSensitivity * viewer.pixelRatio;
|
||||
double deltaY = _accumulatedPan.dy * panSensitivity * viewer.pixelRatio;
|
||||
|
||||
Vector3 newPosition = currentPosition + right * -deltaX + up * deltaY;
|
||||
newPosition = _constrainPosition(newPosition);
|
||||
|
||||
currentPosition = newPosition;
|
||||
|
||||
_accumulatedPan = Offset.zero;
|
||||
}
|
||||
|
||||
// Apply zoom
|
||||
if (_accumulatedZoom != 0.0) {
|
||||
Vector3 forward = _forward.clone()..applyQuaternion(currentRotation);
|
||||
Vector3 newPosition = currentPosition + forward * _accumulatedZoom * zoomSensitivity;
|
||||
newPosition = _constrainPosition(newPosition);
|
||||
|
||||
currentPosition = newPosition;
|
||||
_accumulatedZoom = 0.0;
|
||||
}
|
||||
|
||||
Matrix4 newModelMatrix =
|
||||
Matrix4.compose(currentPosition, currentRotation, Vector3(1, 1, 1));
|
||||
await viewer.setCameraModelMatrix4(newModelMatrix);
|
||||
}
|
||||
}
|
||||
|
||||
Vector3 _constrainPosition(Vector3 position) {
|
||||
if (minBounds != null) {
|
||||
position.x = position.x.clamp(minBounds!.x, double.infinity);
|
||||
position.y = position.y.clamp(minBounds!.y, double.infinity);
|
||||
position.z = position.z.clamp(minBounds!.z, double.infinity);
|
||||
}
|
||||
if (maxBounds != null) {
|
||||
position.x = position.x.clamp(double.negativeInfinity, maxBounds!.x);
|
||||
position.y = position.y.clamp(double.negativeInfinity, maxBounds!.y);
|
||||
position.z = position.z.clamp(double.negativeInfinity, maxBounds!.z);
|
||||
}
|
||||
return position;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> rotate(Offset delta, Vector2? velocity) async {
|
||||
_accumulatedRotation += delta;
|
||||
_lastVelocity = velocity;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> pan(Offset delta, Vector2? velocity) async {
|
||||
_accumulatedPan += delta;
|
||||
_lastVelocity = velocity;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> zoom(double scrollDelta, Vector2? velocity) async {
|
||||
_accumulatedZoom -= scrollDelta;
|
||||
_lastVelocity = velocity;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onKeypress(PhysicalKeyboardKey key) async {
|
||||
_pressedKeys[key] = true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onKeyRelease(PhysicalKeyboardKey key) async {
|
||||
_pressedKeys.remove(key);
|
||||
}
|
||||
|
||||
Future<void> _processKeyboardInput() async {
|
||||
double dx = 0, dy = 0, dz = 0;
|
||||
|
||||
if (_pressedKeys[PhysicalKeyboardKey.keyW] == true) {
|
||||
dz += keyMoveSensitivity;
|
||||
}
|
||||
if (_pressedKeys[PhysicalKeyboardKey.keyS] == true) {
|
||||
dz -= keyMoveSensitivity;
|
||||
}
|
||||
if (_pressedKeys[PhysicalKeyboardKey.keyA] == true) {
|
||||
dx -= keyMoveSensitivity;
|
||||
}
|
||||
if (_pressedKeys[PhysicalKeyboardKey.keyD] == true) {
|
||||
dx += keyMoveSensitivity;
|
||||
}
|
||||
|
||||
if (dx != 0 || dy != 0 || dz != 0) {
|
||||
await _moveCamera(dx, dy, dz);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _moveCamera(double dx, double dy, double dz) async {
|
||||
Matrix4 currentModelMatrix = await viewer.getCameraModelMatrix();
|
||||
Vector3 currentPosition = currentModelMatrix.getTranslation();
|
||||
Quaternion currentRotation =
|
||||
Quaternion.fromRotation(currentModelMatrix.getRotation());
|
||||
|
||||
Vector3 forward = Vector3(0, 0, -1)..applyQuaternion(currentRotation);
|
||||
Vector3 right = Vector3(1, 0, 0)..applyQuaternion(currentRotation);
|
||||
Vector3 up = Vector3(0, 1, 0)..applyQuaternion(currentRotation);
|
||||
|
||||
Vector3 moveOffset = right * dx + up * dy + forward * dz;
|
||||
Vector3 newPosition = currentPosition + moveOffset;
|
||||
newPosition = _constrainPosition(newPosition);
|
||||
|
||||
Matrix4 newModelMatrix =
|
||||
Matrix4.compose(newPosition, currentRotation, Vector3(1, 1, 1));
|
||||
await viewer.setCameraModelMatrix4(newModelMatrix);
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_ticker?.dispose();
|
||||
_moveTimer?.cancel();
|
||||
}
|
||||
}
|
||||
@@ -1,231 +0,0 @@
|
||||
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';
|
||||
import 'dart:ui';
|
||||
import 'package:thermion_flutter/src/widgets/camera/gestures/thermion_gesture_handler.dart';
|
||||
|
||||
// Renamed implementation
|
||||
class PickingCameraGestureHandler implements ThermionGestureHandler {
|
||||
final ThermionViewer viewer;
|
||||
final bool enableCamera;
|
||||
final bool enablePicking;
|
||||
final Logger _logger = Logger("PickingCameraGestureHandler");
|
||||
|
||||
ThermionGestureState _currentState = ThermionGestureState.NULL;
|
||||
AbstractGizmo? _gizmo;
|
||||
Timer? _scrollTimer;
|
||||
ThermionEntity? _highlightedEntity;
|
||||
StreamSubscription<FilamentPickResult>? _pickResultSubscription;
|
||||
|
||||
bool _gizmoAttached = false;
|
||||
|
||||
PickingCameraGestureHandler({
|
||||
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);
|
||||
}
|
||||
|
||||
@override
|
||||
ThermionGestureState get currentState => _currentState;
|
||||
|
||||
void _handleKeyEvent(RawKeyEvent event) {
|
||||
if (event is RawKeyDownEvent &&
|
||||
event.logicalKey == LogicalKeyboardKey.escape) {
|
||||
_resetToNullState();
|
||||
}
|
||||
}
|
||||
|
||||
void _resetToNullState() async {
|
||||
_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!);
|
||||
_gizmo?.attach(_highlightedEntity!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onPointerHover(Offset localPosition, Offset delta) async {
|
||||
if (_gizmoAttached) {
|
||||
_gizmo?.checkHover(localPosition.dx, localPosition.dy);
|
||||
}
|
||||
|
||||
if (_highlightedEntity != null) {
|
||||
await viewer.queuePositionUpdateFromViewportCoords(
|
||||
_highlightedEntity!,
|
||||
localPosition.dx,
|
||||
localPosition.dy,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onPointerScroll(Offset localPosition, double scrollDelta) async {
|
||||
if (!enableCamera) {
|
||||
return;
|
||||
}
|
||||
if (_currentState == ThermionGestureState.NULL ||
|
||||
_currentState == ThermionGestureState.ZOOMING) {
|
||||
await _zoom(localPosition, scrollDelta);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onPointerDown(Offset localPosition, int buttons) async {
|
||||
if (_highlightedEntity != null) {
|
||||
_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;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> onPointerMove(
|
||||
Offset localPosition, Offset delta, int buttons) async {
|
||||
if (_highlightedEntity != null) {
|
||||
await _handleEntityHighlightedMove(localPosition);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (_currentState) {
|
||||
case ThermionGestureState.NULL:
|
||||
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;
|
||||
case ThermionGestureState.ZOOMING:
|
||||
// ignore
|
||||
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();
|
||||
_currentState = ThermionGestureState.ZOOMING;
|
||||
await viewer.zoomBegin();
|
||||
await viewer.zoomUpdate(
|
||||
localPosition.dx, localPosition.dy, scrollDelta > 0 ? 1 : -1);
|
||||
|
||||
_scrollTimer = Timer(const Duration(milliseconds: 100), () async {
|
||||
await viewer.zoomEnd();
|
||||
_currentState = ThermionGestureState.NULL;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_pickResultSubscription?.cancel();
|
||||
if (_highlightedEntity != null) {
|
||||
viewer.removeStencilHighlight(_highlightedEntity!);
|
||||
}
|
||||
RawKeyboard.instance.removeListener(_handleKeyEvent);
|
||||
}
|
||||
|
||||
@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
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:thermion_flutter/src/widgets/camera/gestures/thermion_gesture_handler.dart';
|
||||
|
||||
class MobileGestureHandlerSelectorWidget extends StatelessWidget {
|
||||
final ThermionGestureHandler handler;
|
||||
|
||||
const MobileGestureHandlerSelectorWidget({super.key, required this.handler});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
throw Exception("TODO");
|
||||
// return GestureDetector(
|
||||
// onTap: () {
|
||||
|
||||
// var curIdx =
|
||||
// GestureType.values.indexOf(handler.gestureType);
|
||||
// var nextIdx =
|
||||
// curIdx == GestureType.values.length - 1 ? 0 : curIdx + 1;
|
||||
// handler.setGestureType(GestureType.values[nextIdx]);
|
||||
// });
|
||||
// },
|
||||
// child: Container(
|
||||
// padding: const EdgeInsets.all(50),
|
||||
// child: Icon(_icons[widget.gestureHandler.gestureType],
|
||||
// color: Colors.green),
|
||||
// ),
|
||||
// );
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:thermion_dart/thermion_dart/entities/abstract_gizmo.dart';
|
||||
import 'package:thermion_dart/thermion_dart/thermion_viewer.dart';
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:thermion_flutter/src/widgets/camera/gestures/thermion_gesture_handler.dart';
|
||||
import 'package:vector_math/vector_math_64.dart' as v64;
|
||||
|
||||
class ThermionGestureDetectorDesktop extends StatefulWidget {
|
||||
final Widget? child;
|
||||
final ThermionGestureHandler gestureHandler;
|
||||
final bool showControlOverlay;
|
||||
final bool enableCamera;
|
||||
final bool enablePicking;
|
||||
|
||||
const ThermionGestureDetectorDesktop({
|
||||
Key? key,
|
||||
required this.gestureHandler,
|
||||
this.child,
|
||||
this.showControlOverlay = false,
|
||||
this.enableCamera = true,
|
||||
this.enablePicking = true,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _ThermionGestureDetectorDesktopState();
|
||||
}
|
||||
|
||||
class _ThermionGestureDetectorDesktopState
|
||||
extends State<ThermionGestureDetectorDesktop> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Listener(
|
||||
onPointerHover: (event) =>
|
||||
widget.gestureHandler.onPointerHover(event.localPosition, event.delta),
|
||||
onPointerSignal: (PointerSignalEvent pointerSignal) {
|
||||
if (pointerSignal is PointerScrollEvent) {
|
||||
widget.gestureHandler.onPointerScroll(
|
||||
pointerSignal.localPosition, pointerSignal.scrollDelta.dy);
|
||||
}
|
||||
},
|
||||
onPointerPanZoomStart: (pzs) {
|
||||
throw Exception("TODO - is this a pinch zoom on laptop trackpad?");
|
||||
},
|
||||
onPointerDown: (d) =>
|
||||
widget.gestureHandler.onPointerDown(d.localPosition, d.buttons),
|
||||
onPointerMove: (d) =>
|
||||
widget.gestureHandler.onPointerMove(d.localPosition, d.delta, d.buttons),
|
||||
onPointerUp: (d) => widget.gestureHandler.onPointerUp(d.buttons),
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:thermion_flutter/src/gestures/delegates.dart';
|
||||
|
||||
enum GestureType {
|
||||
LMB_DOWN,
|
||||
LMB_HOLD_AND_MOVE,
|
||||
LMB_UP,
|
||||
LMB_HOVER,
|
||||
MMB_DOWN,
|
||||
MMB_HOLD_AND_MOVE,
|
||||
MMB_UP,
|
||||
MMB_HOVER,
|
||||
SCALE1,
|
||||
SCALE2,
|
||||
SCROLLWHEEL,
|
||||
POINTER_MOVE,
|
||||
KEYDOWN
|
||||
}
|
||||
|
||||
enum GestureAction {
|
||||
PAN_CAMERA,
|
||||
ROTATE_CAMERA,
|
||||
ZOOM_CAMERA,
|
||||
TRANSLATE_ENTITY,
|
||||
ROTATE_ENTITY,
|
||||
PICK_ENTITY,
|
||||
NONE
|
||||
}
|
||||
|
||||
enum ThermionGestureState {
|
||||
NULL,
|
||||
ROTATING,
|
||||
PANNING,
|
||||
ZOOMING, // aka SCROLL
|
||||
}
|
||||
|
||||
abstract class ThermionGestureHandler {
|
||||
Future<void> onPointerHover(Offset localPosition, Offset delta);
|
||||
Future<void> onPointerScroll(Offset localPosition, double scrollDelta);
|
||||
Future<void> onPointerDown(Offset localPosition, int buttons);
|
||||
Future<void> onPointerMove(Offset localPosition, Offset delta, int buttons);
|
||||
Future<void> onPointerUp(int buttons);
|
||||
Future<void> onScaleStart();
|
||||
Future<void> onScaleUpdate();
|
||||
Future<void> onScaleEnd();
|
||||
Future<bool> get initialized;
|
||||
void dispose();
|
||||
|
||||
void setActionForType(GestureType gestureType, GestureAction gestureAction);
|
||||
GestureAction? getActionForType(GestureType gestureType);
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:thermion_flutter/src/widgets/camera/gestures/thermion_gesture_handler.dart';
|
||||
|
||||
///
|
||||
/// A widget that captures swipe/pointer events.
|
||||
/// This is a dumb listener; events are forwarded to a [ThermionGestureHandler].
|
||||
///
|
||||
class ThermionListenerWidget extends StatelessWidget {
|
||||
///
|
||||
/// 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 [FilamentViewer].
|
||||
///
|
||||
final Widget? child;
|
||||
|
||||
///
|
||||
/// The handler to use for interpreting gestures/pointer movements.
|
||||
///
|
||||
final ThermionGestureHandler gestureHandler;
|
||||
|
||||
const ThermionListenerWidget({
|
||||
Key? key,
|
||||
required this.gestureHandler,
|
||||
this.child,
|
||||
}) : super(key: key);
|
||||
|
||||
bool get isDesktop =>
|
||||
kIsWeb || Platform.isLinux || Platform.isWindows || Platform.isMacOS;
|
||||
|
||||
Widget _desktop() {
|
||||
return Listener(
|
||||
onPointerHover: (event) =>
|
||||
gestureHandler.onPointerHover(event.localPosition, event.delta),
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder(
|
||||
future: gestureHandler.initialized,
|
||||
builder: (_, initialized) {
|
||||
if (initialized.data != true) {
|
||||
return child ?? Container();
|
||||
}
|
||||
return Stack(children: [
|
||||
if (child != null) Positioned.fill(child: child!),
|
||||
if (isDesktop)
|
||||
Positioned.fill(
|
||||
child: _desktop()),
|
||||
if (!isDesktop)
|
||||
Positioned.fill(
|
||||
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();
|
||||
},
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
import 'package:thermion_dart/thermion_dart/thermion_viewer.dart';import 'package:flutter/material.dart';
|
||||
import '../../utils/camera_orientation.dart';
|
||||
import '../../../utils/camera_orientation.dart';
|
||||
|
||||
import 'dart:math';
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
// import 'package:flutter/widgets.dart';
|
||||
// import 'package:thermion_dart/thermion_dart/input/input_handler.dart';
|
||||
|
||||
// class MobileGestureHandlerSelectorWidget extends StatelessWidget {
|
||||
// final InputHandler handler;
|
||||
|
||||
// const MobileGestureHandlerSelectorWidget({super.key, required this.handler});
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// throw Exception("TODO");
|
||||
// // return GestureDetector(
|
||||
// // onTap: () {
|
||||
|
||||
// // var curIdx =
|
||||
// // InputType.values.indexOf(handler.gestureType);
|
||||
// // var nextIdx =
|
||||
// // curIdx == InputType.values.length - 1 ? 0 : curIdx + 1;
|
||||
// // handler.setInputType(InputType.values[nextIdx]);
|
||||
// // });
|
||||
// // },
|
||||
// // child: Container(
|
||||
// // padding: const EdgeInsets.all(50),
|
||||
// // child: Icon(_icons[widget.gestureHandler.gestureType],
|
||||
// // color: Colors.green),
|
||||
// // ),
|
||||
// // );
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,60 @@
|
||||
// import 'package:thermion_dart/thermion_dart.dart';
|
||||
|
||||
// import 'package:flutter/gestures.dart';
|
||||
// import 'package:flutter/material.dart';
|
||||
|
||||
// import 'package:vector_math/vector_math_64.dart';
|
||||
// import 'dart:ui' show Offset;
|
||||
|
||||
// extension OffsetExtension on Offset {
|
||||
// Vector2 toVector2() {
|
||||
// return Vector2(dx, dy);
|
||||
// }
|
||||
// }
|
||||
|
||||
// class ThermionGestureDetectorDesktop extends StatefulWidget {
|
||||
// final Widget? child;
|
||||
// final InputHandler gestureHandler;
|
||||
// final bool showControlOverlay;
|
||||
// final bool enableCamera;
|
||||
// final bool enablePicking;
|
||||
|
||||
// const ThermionGestureDetectorDesktop({
|
||||
// Key? key,
|
||||
// required this.gestureHandler,
|
||||
// this.child,
|
||||
// this.showControlOverlay = false,
|
||||
// this.enableCamera = true,
|
||||
// this.enablePicking = true,
|
||||
// }) : super(key: key);
|
||||
|
||||
// @override
|
||||
// State<StatefulWidget> createState() => _ThermionGestureDetectorDesktopState();
|
||||
// }
|
||||
|
||||
// class _ThermionGestureDetectorDesktopState
|
||||
// extends State<ThermionGestureDetectorDesktop> {
|
||||
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return Listener(
|
||||
// onPointerHover: (event) =>
|
||||
// widget.gestureHandler.onPointerHover(event.localPosition.toVector2(), event.delta.toVector2()),
|
||||
// onPointerSignal: (PointerSignalEvent pointerSignal) {
|
||||
// if (pointerSignal is PointerScrollEvent) {
|
||||
// widget.gestureHandler.onPointerScroll(
|
||||
// pointerSignal.localPosition.toVector2(), pointerSignal.scrollDelta.dy);
|
||||
// }
|
||||
// },
|
||||
// onPointerPanZoomStart: (pzs) {
|
||||
// throw Exception("TODO - is this a pinch zoom on laptop trackpad?");
|
||||
// },
|
||||
// onPointerDown: (d) =>
|
||||
// widget.gestureHandler.onPointerDown(d.localPosition.toVector2(), d.buttons & kMiddleMouseButton != 0),
|
||||
// onPointerMove: (PointerMoveEvent d) =>
|
||||
// widget.gestureHandler.onPointerMove(d.localPosition.toVector2(), d.delta.toVector2(), d.buttons & kMiddleMouseButton != 0),
|
||||
// onPointerUp: (d) => widget.gestureHandler.onPointerUp(d.buttons),
|
||||
// child: widget.child,
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
@@ -2,7 +2,7 @@ import 'dart:async';
|
||||
import 'package:thermion_dart/thermion_dart/thermion_viewer.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum GestureType { rotateCamera, panCamera, panBackground }
|
||||
enum InputType { rotateCamera, panCamera, panBackground }
|
||||
|
||||
///
|
||||
/// A widget that translates finger/mouse gestures to zoom/pan/rotate actions.
|
||||
@@ -62,12 +62,12 @@ class ThermionGestureDetectorMobile extends StatefulWidget {
|
||||
|
||||
class _ThermionGestureDetectorMobileState
|
||||
extends State<ThermionGestureDetectorMobile> {
|
||||
GestureType gestureType = GestureType.panCamera;
|
||||
InputType gestureType = InputType.panCamera;
|
||||
|
||||
final _icons = {
|
||||
GestureType.panBackground: Icons.image,
|
||||
GestureType.panCamera: Icons.pan_tool,
|
||||
GestureType.rotateCamera: Icons.rotate_90_degrees_ccw
|
||||
InputType.panBackground: Icons.image,
|
||||
InputType.panCamera: Icons.pan_tool,
|
||||
InputType.rotateCamera: Icons.rotate_90_degrees_ccw
|
||||
};
|
||||
|
||||
// on mobile, we can't differentiate between pointer down events like we do on desktop with primary/secondary/tertiary buttons
|
||||
@@ -97,18 +97,18 @@ class _ThermionGestureDetectorMobileState
|
||||
|
||||
void _setFunction() {
|
||||
switch (gestureType) {
|
||||
case GestureType.rotateCamera:
|
||||
case InputType.rotateCamera:
|
||||
_functionStart = widget.viewer.rotateStart;
|
||||
_functionUpdate = widget.viewer.rotateUpdate;
|
||||
_functionEnd = widget.viewer.rotateEnd;
|
||||
break;
|
||||
case GestureType.panCamera:
|
||||
case InputType.panCamera:
|
||||
_functionStart = widget.viewer.panStart;
|
||||
_functionUpdate = widget.viewer.panUpdate;
|
||||
_functionEnd = widget.viewer.panEnd;
|
||||
break;
|
||||
// TODO
|
||||
case GestureType.panBackground:
|
||||
case InputType.panBackground:
|
||||
_functionStart = (x, y) async {};
|
||||
_functionUpdate = (x, y) async {};
|
||||
_functionEnd = () async {};
|
||||
@@ -216,11 +216,11 @@ class _ThermionGestureDetectorMobileState
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
var curIdx = GestureType.values.indexOf(gestureType);
|
||||
var nextIdx = curIdx == GestureType.values.length - 1
|
||||
var curIdx = InputType.values.indexOf(gestureType);
|
||||
var nextIdx = curIdx == InputType.values.length - 1
|
||||
? 0
|
||||
: curIdx + 1;
|
||||
gestureType = GestureType.values[nextIdx];
|
||||
gestureType = InputType.values[nextIdx];
|
||||
_setFunction();
|
||||
});
|
||||
},
|
||||
@@ -0,0 +1,165 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:thermion_dart/thermion_dart.dart';
|
||||
import 'package:vector_math/vector_math_64.dart';
|
||||
|
||||
extension OffsetExtension on Offset {
|
||||
Vector2 toVector2() {
|
||||
return Vector2(dx, dy);
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// A widget that captures swipe/pointer events.
|
||||
/// This is a dumb listener; events are forwarded to a [InputHandler].
|
||||
///
|
||||
class ThermionListenerWidget 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 [FilamentViewer].
|
||||
///
|
||||
final Widget? child;
|
||||
|
||||
///
|
||||
/// The handler to use for interpreting gestures/pointer movements.
|
||||
///
|
||||
final InputHandler gestureHandler;
|
||||
|
||||
const ThermionListenerWidget({
|
||||
Key? key,
|
||||
required this.gestureHandler,
|
||||
this.child,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<ThermionListenerWidget> createState() => _ThermionListenerWidgetState();
|
||||
}
|
||||
|
||||
class _ThermionListenerWidgetState extends State<ThermionListenerWidget> {
|
||||
bool get isDesktop =>
|
||||
kIsWeb || Platform.isLinux || Platform.isWindows || Platform.isMacOS;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
HardwareKeyboard.instance.addHandler(_handleKeyEvent);
|
||||
}
|
||||
|
||||
final _keyMap = {
|
||||
PhysicalKeyboardKey.keyW: PhysicalKey.W,
|
||||
PhysicalKeyboardKey.keyA: PhysicalKey.A,
|
||||
PhysicalKeyboardKey.keyS: PhysicalKey.S,
|
||||
PhysicalKeyboardKey.keyD: PhysicalKey.D,
|
||||
};
|
||||
|
||||
bool _handleKeyEvent(KeyEvent event) {
|
||||
PhysicalKey? key = _keyMap[event.physicalKey];
|
||||
|
||||
if (key == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event is KeyDownEvent || event is KeyRepeatEvent) {
|
||||
widget.gestureHandler.keyDown(key!);
|
||||
} else if (event is KeyUpEvent) {
|
||||
widget.gestureHandler.keyUp(key!);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
HardwareKeyboard.instance.removeHandler(_handleKeyEvent);
|
||||
}
|
||||
|
||||
Widget _desktop() {
|
||||
return Listener(
|
||||
onPointerHover: (event) => widget.gestureHandler
|
||||
.onPointerHover(event.localPosition.toVector2(), event.delta.toVector2()),
|
||||
onPointerSignal: (PointerSignalEvent pointerSignal) {
|
||||
if (pointerSignal is PointerScrollEvent) {
|
||||
widget.gestureHandler.onPointerScroll(
|
||||
pointerSignal.localPosition.toVector2(),
|
||||
pointerSignal.scrollDelta.dy);
|
||||
}
|
||||
},
|
||||
onPointerPanZoomStart: (pzs) {
|
||||
throw Exception("TODO - is this a pinch zoom on laptop trackpad?");
|
||||
},
|
||||
onPointerDown: (d) => widget.gestureHandler
|
||||
.onPointerDown(d.localPosition.toVector2(), d.buttons & kMiddleMouseButton != 0),
|
||||
onPointerMove: (d) => widget.gestureHandler
|
||||
.onPointerMove(d.localPosition.toVector2(), d.delta.toVector2(), d.buttons & kMiddleMouseButton != 0),
|
||||
onPointerUp: (d) => widget.gestureHandler.onPointerUp(d.buttons & kMiddleMouseButton != 0),
|
||||
child: widget.child,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _mobile() {
|
||||
return _MobileListenerWidget(gestureHandler: widget.gestureHandler);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder(
|
||||
future: widget.gestureHandler.initialized,
|
||||
builder: (_, initialized) {
|
||||
if (initialized.data != true) {
|
||||
return widget.child ?? Container();
|
||||
}
|
||||
return Stack(children: [
|
||||
if (widget.child != null) Positioned.fill(child: widget.child!),
|
||||
if (isDesktop) Positioned.fill(child: _desktop()),
|
||||
if (!isDesktop) Positioned.fill(child: _mobile())
|
||||
]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _MobileListenerWidget extends StatefulWidget {
|
||||
final InputHandler gestureHandler;
|
||||
|
||||
const _MobileListenerWidget({Key? key, required this.gestureHandler})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _MobileListenerWidgetState();
|
||||
}
|
||||
|
||||
class _MobileListenerWidgetState extends State<_MobileListenerWidget> {
|
||||
bool isPan = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTapDown: (details) => widget.gestureHandler
|
||||
.onPointerDown(details.localPosition.toVector2(), false),
|
||||
onDoubleTap: () {
|
||||
widget.gestureHandler.setActionForType(InputType.SCALE1,
|
||||
isPan ? InputAction.TRANSLATE : InputAction.ROTATE);
|
||||
},
|
||||
onScaleStart: (details) async {
|
||||
await widget.gestureHandler.onScaleStart();
|
||||
},
|
||||
onScaleUpdate: (details) async {
|
||||
await widget.gestureHandler.onScaleUpdate();
|
||||
},
|
||||
onScaleEnd: (details) async {
|
||||
await widget.gestureHandler.onScaleUpdate();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,8 @@ import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:thermion_flutter/src/widgets/thermion_widget_web.dart';
|
||||
import 'package:thermion_flutter/src/widgets/transparent_filament_widget.dart';
|
||||
import 'package:thermion_flutter/src/widgets/src/thermion_widget_web.dart';
|
||||
import 'package:thermion_flutter/src/widgets/src/transparent_filament_widget.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:thermion_flutter_platform_interface/thermion_flutter_texture.dart';
|
||||
@@ -12,9 +12,11 @@ import 'package:thermion_flutter_web/thermion_flutter_web_options.dart';
|
||||
import 'resize_observer.dart';
|
||||
|
||||
class ThermionWidget extends StatefulWidget {
|
||||
|
||||
final ThermionViewer viewer;
|
||||
final ThermionFlutterOptions? options;
|
||||
|
||||
|
||||
///
|
||||
/// The content to render before the texture widget is available.
|
||||
/// The default is a solid red Container, intentionally chosen to make it clear that there will be at least one frame where the Texture widget is not being rendered.
|
||||
@@ -63,6 +65,7 @@ class _ThermionWidgetState extends State<ThermionWidget> {
|
||||
|
||||
void _requestFrame() {
|
||||
WidgetsBinding.instance.scheduleFrameCallback((d) {
|
||||
|
||||
widget.viewer.requestFrame();
|
||||
_requestFrame();
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
library;
|
||||
|
||||
export 'src/thermion_widget.dart';
|
||||
export 'src/camera/gestures/thermion_listener_widget.dart';
|
||||
@@ -1,11 +1,6 @@
|
||||
library thermion_flutter;
|
||||
|
||||
export 'src/thermion_flutter_plugin.dart';
|
||||
export 'src/widgets/thermion_widget.dart';
|
||||
export 'src/widgets/camera/gestures/thermion_gesture_detector.dart';
|
||||
export 'src/widgets/camera/gestures/thermion_gesture_handler.dart';
|
||||
export 'src/gestures/delegate_gesture_handler.dart';
|
||||
|
||||
export 'src/widgets/camera/camera_orientation_widget.dart';
|
||||
export 'src/widgets/widgets.dart';
|
||||
export 'package:thermion_flutter_platform_interface/thermion_flutter_platform_interface.dart';
|
||||
export 'package:thermion_dart/thermion_dart.dart';
|
||||
|
||||
Reference in New Issue
Block a user