The pinch is read from AppKit rather than through iced, which is the kind of decision that reads as arbitrary without the reason next to it. The file also records what the pixel/notch split costs: a smooth-scrolling mouse pans with a trackpad.
3.6 KiB
Trackpad
The macOS desktop app reads trackpad gestures directly: two fingers pan, a pinch zooms. The web build and the Linux desktop build keep zooming on every scroll delta and have no pinch; the reasons are in Implementation below.
A mouse is unaffected. The wheel zooms, the middle button pans, and Shift together with the middle button orbits, exactly as before.
Navigation
| Gesture | Behavior |
|---|---|
| Two-finger scroll | Pan the active view, both axes, the drawing following the fingers. |
| Pinch | Zoom about the cursor, at the scale of the fingers' own spread. |
| Wheel (mouse) | Zoom about the cursor. ZOOMFACTOR scales a notch; ZOOMWHEEL reverses it. |
| Middle-button drag | Pan. Shift together with the middle button orbits the model view. |
PAN / P |
Pan with the left button until Escape. |
Panning covers the same ground whichever way it starts: the active model tile, a paper sheet, or the model view inside a floating viewport in MSPACE. A pan dragged out of a two-finger gesture and one dragged with the middle button move through the same code, so they agree on speed, on the tile they pan by, and on keeping an open selection box attached to the drawing underneath.
A pinch is not affected by ZOOMWHEEL. The direction of a pinch is the
direction of the fingers, not a convention about a device.
On macOS a mouse that scrolls smoothly, such as a Magic Mouse, reports precise pixel deltas like a trackpad does, so it pans instead of zooming. That follows from the split described below; it is not a separate setting.
Implementation
src/input/trackpad.rs: the pinch. iced's winit layer converts only the window events that have an iced equivalent, so winit'sPinchGestureis dropped before the app sees it. The gesture is read from AppKit with a localNSEventmonitor instead, installed on the main thread where iced builds subscriptions. The monitor's block runs on the main thread during AppKit's own dispatch and only publishes the magnification into a sink; the app receives it through an iced subscription shaped like the SpaceMouse bridge (src/input/spacemouse/). The monitor token is never released, which does not uninstall the monitor —removeMonitordoes, and the app has one window for its whole life.src/app/update/viewport.rs:scroll_intentdecides what a scroll delta means. A wheel reports notches and zooms; a precise-scrolling device reports pixels and pans. There is no modifier and no setting in that decision.pan_active_viewandzoom_view_at_cursorare the bodies shared by the mouse and the gestures, and they apply a pinch as1 - step / 10 = 1 / (1 + m)so a pinch ofmleaves the view exactly1 + mtimes closer.src/app/mod.rscarries oneMessage::TrackpadPinch(f32)per gesture event.
Pixel deltas pan only where they can only mean a trackpad. The web build
receives every wheel notch as a pixel delta from the browser, so there — and on
Linux, where a smooth-scrolling mouse reports them the same way — they keep
zooming. A pinch exists nowhere else either: winit reports PinchGesture on
macOS and iOS only, its PanGesture is iOS-only, and its Wayland backend does
not bind the pointer-gesture protocol at all.
Validation
cargo test --locked --lib scroll_intent
The tests cover the decision between a notch and a pixel, and the 1:1 mapping of a pinch onto the camera. The gestures themselves are checked by hand: macOS offers no way to synthesize a magnify event, so a pinch cannot be driven by a test. Two-finger pan and pinch are exercised in model space, in a tiled view, on a paper sheet, and inside a floating viewport in MSPACE.