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"); }); } }