garage_auth, garage_entitlements, garage_iap and garage_ui, moved out of Garage-Services and Metro-Map-Maker into one public repo. MIT, one readme, docs under docs/. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013F4NWNvYcdeSgqbWMT1VQ7
64 lines
2.5 KiB
Dart
64 lines
2.5 KiB
Dart
import "package:flutter/foundation.dart";
|
|
import "package:flutter/services.dart";
|
|
|
|
// hides the OS cursor and keeps it visually pinned at its start point for a
|
|
// drag - see macos/Runner/CursorLock.swift for the actual warp-and-suppress
|
|
// implementation. no native handler is registered on platforms other than
|
|
// macOS, so [lock]/[unlock] just fail (and get logged) instead of doing
|
|
// anything - callers dont need to guard by platform themselves, they just
|
|
// wont get live deltas or a frozen cursor there.
|
|
//
|
|
// Deltas come back over the SAME channel as a native-to-dart call rather
|
|
// than through Flutters own PointerMoveEvent - the native side intercepts
|
|
// and swallows the underlying mouse-dragged events entirely (see the Swift
|
|
// side for why), so Flutters own pointer delta cannot be trusted at all
|
|
// while locked.
|
|
class CursorLock {
|
|
CursorLock._();
|
|
|
|
static const _channel = MethodChannel("garage/cursor_lock");
|
|
|
|
// whether theres actually a native side listening. anywhere else the
|
|
// lock/unlock calls are no-ops that throw a MissingPluginException, and -
|
|
// more importantly - no deltas ever come back, so callers need to know to
|
|
// drive themselves off Flutters own pointer stream instead.
|
|
static bool get isSupported =>
|
|
!kIsWeb && defaultTargetPlatform == TargetPlatform.macOS;
|
|
|
|
static void Function(double dx, double dy)? _onDelta;
|
|
static bool _handlerInstalled = false;
|
|
|
|
static void _ensureHandlerInstalled() {
|
|
if (_handlerInstalled) return;
|
|
_handlerInstalled = true;
|
|
_channel.setMethodCallHandler((call) async {
|
|
if (call.method != "scrubDelta") return;
|
|
final args = call.arguments as Map;
|
|
final dx = (args["dx"] as num).toDouble();
|
|
final dy = (args["dy"] as num).toDouble();
|
|
_onDelta?.call(dx, dy);
|
|
});
|
|
}
|
|
|
|
// locks the cursor and starts forwarding raw pointer deltas to [onDelta]
|
|
// until [unlock] is called. only one scrub can be active at a time (theres
|
|
// only one OS cursor to hide), so this just replaces whatever listener was
|
|
// there before.
|
|
static void lock(void Function(double dx, double dy) onDelta) {
|
|
_ensureHandlerInstalled();
|
|
_onDelta = onDelta;
|
|
if (!isSupported) return;
|
|
_channel.invokeMethod("lock").catchError((e, st) {
|
|
debugPrint("CursorLock.lock failed: $e\n$st");
|
|
});
|
|
}
|
|
|
|
static void unlock() {
|
|
_onDelta = null;
|
|
if (!isSupported) return;
|
|
_channel.invokeMethod("unlock").catchError((e, st) {
|
|
debugPrint("CursorLock.unlock failed: $e\n$st");
|
|
});
|
|
}
|
|
}
|