pointer_lock 0.4.2 copy "pointer_lock: ^0.4.2" to clipboard
pointer_lock: ^0.4.2 copied to clipboard

PlatformmacOS

Locks the mouse pointer in place and reports its motion as relative deltas, which Flutter offers on no desktop platform and does not surface in a browser.

pointer_lock #

Locks the mouse pointer in place and reports its motion as relative deltas.

Flutter exposes no pointer lock on any desktop platform and does not surface the browser's either, so a first-person camera cannot work out of the box: the cursor reaches the edge of the window and the view stops turning. This plugin adds pointer lock on both.

final capture = MouseCapture.instance;

if (capture.isSupported) {
  await capture.capture();
}

// Once per simulation step:
final delta = capture.takeDelta();
yaw   += delta.dx * sensitivity;
pitch += delta.dy * sensitivity;

Pull, not push #

The caller drains the delta with takeDelta instead of receiving it from a stream, because that is the shape a game wants. A simulation on a fixed timestep asks "how far did the mouse move since the last step" once per step. A stream of individual mouse events would move the accumulation into every caller, and each of them would have to get it right.

takeDelta is synchronous for the same reason. It is called from inside the step, and awaiting anything there would mean the step no longer sees a consistent snapshot of its inputs.

Releasing #

Losing window focus drops the capture and announces it on onStateChanged. Otherwise a hidden cursor would be left over another application, and the user could not get it back. Treat an unrequested CaptureState.released as a reason to pause.

The plugin does not watch for Escape. Key handling belongs to the application, which calls release() itself.

Platforms #

Platform Status How
macOS supported CGAssociateMouseAndMouseCursorPosition(0), NSCursor.hide(), a local NSEvent monitor
Web, desktop browser supported document.requestPointerLock through package:web. Pure Dart, so nothing is registered and flutter test --platform chrome reaches it
Web, phone or tablet not applicable reported by (pointer: coarse); isSupported is false so the game shows its touch controls
Windows not yet Raw Input plus ClipCursor
Linux not yet gdk_seat_grab, or XI2 raw events
iOS, Android not applicable no pointer to capture; isSupported is false

The backend answers isSupported; the caller does not have to guess. An application can then offer another control scheme instead of discovering the gap at the first call. The engine above this plugin reads exactly that flag: in a build where it is false, the camera turns by dragging.

What a browser does that a desktop does not #

A capture must come from a user gesture. The browser refuses requestPointerLock when it is called from a timer, a future or a frame callback, so ask inside the handler of the press that prompted it.

A refusal arrives as an event, not an exception. It comes as pointerlockerror, and on browsers that return a promise, also as a rejected promise. The plugin handles both, and a refused capture leaves the state released instead of pretending it succeeded.

The player can leave without asking. Escape releases the lock, and so does switching tabs. Both arrive as an unrequested CaptureState.released, which is the signal to pause.

In an iframe the parent page decides. A page embedding the game needs allow="pointer-lock" on the iframe, or every capture is refused and nothing in the console says why.

Hot restart #

The native side outlives the Dart isolate. Without a reset, a hot restart while the pointer is captured would leave the cursor hidden, with nothing left that remembers to ask for it back. So construction always issues a reset first.

Prior art #

helgoboss/pointer_lock (MIT) solves the same problem across more platforms, and the macOS technique here came from it. It is not published on pub.dev. This plugin exists because of three things that package does not do, each of which a game needs:

  • no observer for focus loss, so Cmd+Tab strands the cursor system-wide;
  • one channel message per mouse event, at a 1000 Hz polling rate;
  • no isSupported, which a build targeting mobile needs.

Cost #

One platform-channel message per mouse event, carrying a two-element Float64List. Nobody has yet measured whether that shows up in a frame profile at a 1000 Hz polling rate. If it does, the fix is to accumulate natively and flush once per frame.


Part of flutter3d, an independent implementation of a 3D engine for Flutter. It is not a fork or a binding of another engine, and it is not affiliated with the Flutter team. It has four switchable rendering backends: Impeller via Flutter GPU, WebGL2, WebGPU and a software rasteriser. It loads glTF, OBJ and .f3d, and has six lighting models, shadows, bloom, skinning, animation, BVH culling and picking, plus a deterministic fixed-step game layer with collision, navigation, positional audio, and gamepad and touch input. Four example games (shooter, platformer, racing, strategy) are each built on a genre package: flutter3d_game_shooter, flutter3d_game_platformer, flutter3d_game_racing, flutter3d_game_strategy. A new game starts from the editor's scaffold, which writes one from a template: https://flutter3d.pleion.dev/first-project/. Documentation: https://flutter3d.pleion.dev.

0
likes
160
points
263
downloads

Documentation

API reference

Publisher

verified publisherpleion.dev

Weekly Downloads

Locks the mouse pointer in place and reports its motion as relative deltas, which Flutter offers on no desktop platform and does not surface in a browser.

Homepage
Repository (GitHub)
View/report issues
Contributing

Topics

#input #mouse #pointer-lock #game-development

License

MIT (license)

Dependencies

flutter, plugin_platform_interface, web

More

Packages that depend on pointer_lock

Packages that implement pointer_lock