git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

commite253fe2c4fd28e5e2de1d5d18f35d12cb4361e36
parente42e757f69
authorLucas Galante <[email protected]>
date2026-07-31 13:00
feat: Application::handle_pinch — first-class trackpad pinch hook

The pinch gesture was only ever delivered as a synthesized ctrl+wheel
PixelDelta sized for the graph's zoom mapping, which underfeeds any
surface with different zoom constants. Apps can now consume the raw
per-update scale factor (1:1 direct manipulation); returning false
keeps the legacy synthesis, so existing ctrl-scroll zoom surfaces are
unchanged.

Co-Authored-By: Claude Fable 5 <[email protected]>

 src/backend/window_runner.rs | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/src/backend/window_runner.rs b/src/backend/window_runner.rs
index 882c242..b9fb9f4 100644
--- a/src/backend/window_runner.rs
+++ b/src/backend/window_runner.rs
@@ -2406,6 +2406,16 @@ pub trait Application: Sized + 'static {
     fn handle_pointer_move(&mut self, pos: LogicalPosition, needs_rebuild: &mut bool);
     fn handle_mouse_input(&mut self, button: MouseButton, state: ElementState, pos: LogicalPosition, needs_rebuild: &mut bool) -> Option<Self::Message>;
     fn handle_mouse_wheel(&mut self, delta: &MouseScrollDelta, pos: LogicalPosition, needs_rebuild: &mut bool);
+    /// Trackpad pinch (zwp_pointer_gestures pinch). `factor` is the scale
+    /// change SINCE THE LAST update (1.0 = no change, >1 = fingers spreading),
+    /// so direct-manipulation zoom is `content_scale *= factor`. Return true
+    /// to consume; returning false falls back to the engine's legacy
+    /// synthesis — a ctrl+wheel PixelDelta sized for the graph's zoom mapping
+    /// (`y = (factor-1)/0.015`) — so ctrl-scroll-zoom surfaces keep working
+    /// without implementing this.
+    fn handle_pinch(&mut self, _factor: f32, _pos: LogicalPosition, _needs_rebuild: &mut bool) -> bool {
+        false
+    }
     fn handle_key_input(&mut self, event: &KeyEvent, needs_rebuild: &mut bool) -> Option<Self::Message>;
     /// Keyboard focus entered/left the window (the compositor keyboard-focuses
     /// the focused window, so this is the "am I the focused window" signal —
@@ -3779,6 +3789,16 @@ impl<A: Application> wayland_client::Dispatch<ZwpPointerGesturePinchV1, ()> for
                 let (px, py) = state.cursor_pos;
                 let mut rebuild = false;
 
+                // First offer the gesture as-is: apps with true pinch
+                // surfaces (the designer's 3D viewport) consume it here at
+                // 1:1 scale instead of through the wheel synthesis below.
+                if state.inner.as_mut().unwrap().handle_pinch(factor, LogicalPosition::new(px, py), &mut rebuild) {
+                    if rebuild {
+                        state.redraw = true;
+                    }
+                    return;
+                }
+
                 // Calculate the y_delta for PixelDelta mapping.
                 // Since cce-graph interprets factor = 1.0 + y_delta * 0.015, we reverse it:
                 let y_delta = (factor - 1.0) / 0.015;