Skip to content

Touchpad: allow seamless and smartphone-like pinch zoom and pan gestures#20812

Draft
da-phil wants to merge 4 commits intodarktable-org:masterfrom
da-phil:touchpad_seamless_pinch_zoom_and_pan
Draft

Touchpad: allow seamless and smartphone-like pinch zoom and pan gestures#20812
da-phil wants to merge 4 commits intodarktable-org:masterfrom
da-phil:touchpad_seamless_pinch_zoom_and_pan

Conversation

@da-phil
Copy link
Copy Markdown
Contributor

@da-phil da-phil commented Apr 14, 2026

This addresses points (d) of issue #20750.

With this change gesture based pinch zooming and panning is seamless and keeps pinch fully continuous for a smartphone-like feeling, including at high zoom levels.

Screencast.from.2026-04-14.11-47-04.mp4

CC @zisoft @MStraeten: I would appreciate feedback on how this feels on MacOS.

Disclaimer: co-created with copilot.

@jenshannoschwalm
Copy link
Copy Markdown
Collaborator

  1. compiled with this pr included and rebased on master.
  2. Opened an image in darkroom and tried to zoom in/out (two fingers and moved up&down) as has been working for very long. -d input
  3. Nothing happens and nothing shown in log. Zoomed manually by selecting 100% - after that the "two fingers move" work exactly as if "scrolled up/down", no logs shown.

@da-phil
Copy link
Copy Markdown
Contributor Author

da-phil commented Apr 15, 2026

  1. compiled with this pr included and rebased on master.

    1. Opened an image in darkroom and tried to zoom in/out (two fingers and moved up&down) as has been working for very long. -d input

    2. Nothing happens and nothing shown in log. Zoomed manually by selecting 100% - after that the "two fingers move" work exactly as if "scrolled up/down", no logs shown.

Maybe that was meant as a reply to my comment in the other PR, with the additional input touchpad logging output:
#20810 (comment)
Can you pls try with the changes of that PR?

@zisoft
Copy link
Copy Markdown
Collaborator

zisoft commented Apr 15, 2026

Works fine on macOS:

pan_zoom.mp4

But current master is already working the same, so no difference :)

-d input gives:

     0.4078 [input device] Input devices found:

    43.8195 [_midi_open_devices] PortMidi initialized
    50.8623 [_gamepad_open_devices] SDL initialized

Nothing else in the log

@da-phil
Copy link
Copy Markdown
Contributor Author

da-phil commented Apr 15, 2026

Works fine on macOS:
pan_zoom.mp4

But current master is already working the same, so no difference :)

-d input gives:

     0.4078 [input device] Input devices found:

    43.8195 [_midi_open_devices] PortMidi initialized
    50.8623 [_gamepad_open_devices] SDL initialized

Nothing else in the log

Cool! As stated above, the additional diagnosis logging output was provided in the other PR which is supposed to generally address issue with touchpad gestures.

Does seamless / combined zooming and panning work as well for you? Like a combined zooming & panning gesture, which also works on smartphones?

@zisoft
Copy link
Copy Markdown
Collaborator

zisoft commented Apr 15, 2026

I can either zoom or pan with two fingers. Starting a zoom and then pan in the same gesture does not work.

But as I already wrote above: This also works on current master without this PR.

@da-phil
Copy link
Copy Markdown
Contributor Author

da-phil commented Apr 15, 2026

I can either zoom or pan with two fingers. Starting a zoom and then pan in the same gesture does not work.

But as I already wrote above: This also works on current master without this PR.

Yes, the same for me on Ubuntu 24.04 when running master, however this PR should allow seamless / combined panning & zooming.
Then I think we need to also add additional input device logging output to root cause why seamless zooming & panning does not work on macOS. I'll push a commit soon, to add this logging output.

@da-phil
Copy link
Copy Markdown
Contributor Author

da-phil commented Apr 15, 2026

@zisoft I pushed two commits which added some logging statements to diagnose why it is not working on macOS, and even a potential fix.
Can you please check again and provide a part of the logging output when trying to simultaneously pinch zoom and pan?

This is how it looks for me when zooming in:

   85.2769 [touchpad] gesture-type event: type=42 device='Wayland Pointer' source-type=0
    85.2769 [touchpad] pinch phase=1 x=1181.5 y=518.2 dx=-4.488 dy=2.992 scale=1.570312 state=0x10
    85.2769 [darkroom pinch] pan component eff_dx=-4.488 eff_dy=2.992 (combined with scale)
    85.2769 [darkroom pinch] update x=1181.5 y=518.2 raw_dx=-4.488 raw_dy=2.992 eff_dx=-4.488 eff_dy=2.992 scale=1.570312 state=0x0 -> tscale=0.280155 (floor=0.089204 top=16.0) zoom_scale=0.280155
    85.2769 [touchpad] gesture-type event: type=42 device='Wayland Pointer' source-type=0
    85.2769 [touchpad] pinch phase=1 x=1181.5 y=518.2 dx=-1.496 dy=5.984 scale=1.578125 state=0x10
    85.2769 [darkroom pinch] pan component eff_dx=-1.496 eff_dy=5.984 (combined with scale)
    85.2769 [darkroom pinch] update x=1181.5 y=518.2 raw_dx=-1.496 raw_dy=5.984 eff_dx=-1.496 eff_dy=5.984 scale=1.578125 state=0x0 -> tscale=0.281549 (floor=0.089204 top=16.0) zoom_scale=0.281549
    85.2769 [touchpad] gesture-type event: type=42 device='Wayland Pointer' source-type=0
    85.2769 [touchpad] pinch phase=1 x=1181.5 y=518.2 dx=-2.992 dy=4.488 scale=1.597656 state=0x10
    85.2769 [darkroom pinch] pan component eff_dx=-2.992 eff_dy=4.488 (combined with scale)
    85.2769 [darkroom pinch] update x=1181.5 y=518.2 raw_dx=-2.992 raw_dy=4.488 eff_dx=-2.992 eff_dy=4.488 scale=1.597656 state=0x0 -> tscale=0.285033 (floor=0.089204 top=16.0) zoom_scale=0.285033
    85.2769 [touchpad] gesture-type event: type=42 device='Wayland Pointer' source-type=0
    85.2769 [touchpad] pinch phase=1 x=1181.5 y=518.2 dx=-1.496 dy=4.488 scale=1.617188 state=0x10
    85.2769 [darkroom pinch] pan component eff_dx=-1.496 eff_dy=4.488 (combined with scale)
    85.2769 [darkroom pinch] update x=1181.5 y=518.2 raw_dx=-1.496 raw_dy=4.488 eff_dx=-1.496 eff_dy=4.488 scale=1.617188 state=0x0 -> tscale=0.288518 (floor=0.089204 top=16.0) zoom_scale=0.288518
    85.2834 [touchpad] gesture-type event: type=42 device='Wayland Pointer' source-type=0
    85.2834 [touchpad] pinch phase=1 x=1181.5 y=518.2 dx=-2.992 dy=-1.496 scale=1.644531 state=0x10
    85.2834 [darkroom pinch] pan component eff_dx=-2.992 eff_dy=-1.496 (combined with scale)
    85.2835 [darkroom pinch] update x=1181.5 y=518.2 raw_dx=-2.992 raw_dy=-1.496 eff_dx=-2.992 eff_dy=-1.496 scale=1.644531 state=0x0 -> tscale=0.293396 (floor=0.089204 top=16.0) zoom_scale=0.293396
    85.3332 [touchpad] gesture-type event: type=42 device='Wayland Pointer' source-type=0
    85.3333 [touchpad] pinch phase=2 x=1181.5 y=518.2 dx=0.000 dy=0.000 scale=1.000000 state=0x10
    85.3333 [darkroom pinch] end x=1181.5 y=518.2 scale=1.000000 state=0x0

and for zooming out:

   190.0221 [touchpad] gesture-type event: type=42 device='Wayland Pointer' source-type=0
   190.0222 [touchpad] pinch phase=1 x=784.7 y=773.1 dx=0.000 dy=-1.234 scale=0.464844 state=0x10
   190.0223 [darkroom pinch] pan component eff_dx=0.000 eff_dy=-1.234 (combined with scale)
   190.0225 [darkroom pinch] update x=784.7 y=773.1 raw_dx=0.000 raw_dy=-1.234 eff_dx=0.000 eff_dy=-1.234 scale=0.464844 state=0x0 -> tscale=0.136383 (floor=0.089204 top=16.0) zoom_scale=0.136383
   190.0832 [touchpad] gesture-type event: type=42 device='Wayland Pointer' source-type=0
   190.0833 [touchpad] pinch phase=1 x=784.7 y=773.1 dx=0.000 dy=0.000 scale=0.460938 state=0x10
   190.0833 [darkroom pinch] update x=784.7 y=773.1 raw_dx=0.000 raw_dy=0.000 eff_dx=0.000 eff_dy=0.000 scale=0.460938 state=0x0 -> tscale=0.135237 (floor=0.089204 top=16.0) zoom_scale=0.135237
   190.0834 [touchpad] gesture-type event: type=42 device='Wayland Pointer' source-type=0
   190.0834 [touchpad] pinch phase=1 x=784.7 y=773.1 dx=-2.043 dy=-2.043 scale=0.453125 state=0x10
   190.0834 [darkroom pinch] pan component eff_dx=-2.043 eff_dy=-2.043 (combined with scale)
   190.0834 [darkroom pinch] update x=784.7 y=773.1 raw_dx=-2.043 raw_dy=-2.043 eff_dx=-2.043 eff_dy=-2.043 scale=0.453125 state=0x0 -> tscale=0.132945 (floor=0.089204 top=16.0) zoom_scale=0.132945
   190.0834 [touchpad] gesture-type event: type=42 device='Wayland Pointer' source-type=0
   190.0834 [touchpad] pinch phase=1 x=784.7 y=773.1 dx=-4.266 dy=1.422 scale=0.437500 state=0x10
   190.0834 [darkroom pinch] pan component eff_dx=-4.266 eff_dy=1.422 (combined with scale)
   190.0834 [darkroom pinch] update x=784.7 y=773.1 raw_dx=-4.266 raw_dy=1.422 eff_dx=-4.266 eff_dy=1.422 scale=0.437500 state=0x0 -> tscale=0.128361 (floor=0.089204 top=16.0) zoom_scale=0.128361
   190.0835 [touchpad] gesture-type event: type=42 device='Wayland Pointer' source-type=0
   190.0835 [touchpad] pinch phase=2 x=784.7 y=773.1 dx=0.000 dy=0.000 scale=1.000000 state=0x10
   190.0835 [darkroom pinch] end x=784.7 y=773.1 scale=1.000000 state=0x0

@zisoft
Copy link
Copy Markdown
Collaborator

zisoft commented Apr 16, 2026

zoom in:

   101.9639 [darkroom pinch] update x=426.0 y=315.0 raw_dx=0.000 raw_dy=0.000 eff_dx=0.000 eff_dy=0.000 scale=1.828346 state=0x0 -> tscale=0.667370 (floor=0.129745 top=16.0) zoom_scale=0.333685
   101.9805 [touchpad] gesture-type event: type=42 device='Core Pointer' source-type=0
   101.9806 [touchpad] pinch phase=1 x=426.0 y=315.0 dx=0.000 dy=0.000 scale=1.834762 state=0x0
   101.9806 [darkroom pinch] update x=426.0 y=315.0 raw_dx=0.000 raw_dy=0.000 eff_dx=0.000 eff_dy=0.000 scale=1.834762 state=0x0 -> tscale=0.669712 (floor=0.129745 top=16.0) zoom_scale=0.334856
   101.9889 [touchpad] gesture-type event: type=42 device='Core Pointer' source-type=0
   101.9889 [touchpad] pinch phase=1 x=426.0 y=315.0 dx=0.000 dy=0.000 scale=1.837590 state=0x0
   101.9889 [darkroom pinch] update x=426.0 y=315.0 raw_dx=0.000 raw_dy=0.000 eff_dx=0.000 eff_dy=0.000 scale=1.837590 state=0x0 -> tscale=0.670744 (floor=0.129745 top=16.0) zoom_scale=0.335372
   102.0055 [touchpad] gesture-type event: type=42 device='Core Pointer' source-type=0
   102.0056 [touchpad] pinch phase=1 x=426.0 y=315.0 dx=0.000 dy=0.000 scale=1.842665 state=0x0
   102.0056 [darkroom pinch] update x=426.0 y=315.0 raw_dx=0.000 raw_dy=0.000 eff_dx=0.000 eff_dy=0.000 scale=1.842665 state=0x0 -> tscale=0.672597 (floor=0.129745 top=16.0) zoom_scale=0.336298
   102.0222 [touchpad] gesture-type event: type=42 device='Core Pointer' source-type=0
   102.0222 [touchpad] pinch phase=1 x=426.0 y=315.0 dx=0.000 dy=0.000 scale=1.846348 state=0x0
   102.0222 [darkroom pinch] update x=426.0 y=315.0 raw_dx=0.000 raw_dy=0.000 eff_dx=0.000 eff_dy=0.000 scale=1.846348 state=0x0 -> tscale=0.673941 (floor=0.129745 top=16.0) zoom_scale=0.336971
   102.0305 [touchpad] gesture-type event: type=42 device='Core Pointer' source-type=0
   102.0305 [touchpad] pinch phase=1 x=426.0 y=315.0 dx=0.000 dy=0.000 scale=1.850039 state=0x0
   102.0305 [darkroom pinch] update x=426.0 y=315.0 raw_dx=0.000 raw_dy=0.000 eff_dx=0.000 eff_dy=0.000 scale=1.850039 state=0x0 -> tscale=0.675288 (floor=0.129745 top=16.0) zoom_scale=0.337644
   102.0472 [touchpad] gesture-type event: type=42 device='Core Pointer' source-type=0
   102.0472 [touchpad] pinch phase=1 x=426.0 y=315.0 dx=0.000 dy=0.000 scale=1.853737 state=0x0
   102.0472 [darkroom pinch] update x=426.0 y=315.0 raw_dx=0.000 raw_dy=0.000 eff_dx=0.000 eff_dy=0.000 scale=1.853737 state=0x0 -> tscale=0.676638 (floor=0.129745 top=16.0) zoom_scale=0.338319
   102.0639 [touchpad] gesture-type event: type=42 device='Core Pointer' source-type=0
   102.0639 [touchpad] pinch phase=1 x=426.0 y=315.0 dx=0.000 dy=0.000 scale=1.861148 state=0x0
   102.0639 [darkroom pinch] update x=426.0 y=315.0 raw_dx=0.000 raw_dy=0.000 eff_dx=0.000 eff_dy=0.000 scale=1.861148 state=0x0 -> tscale=0.679343 (floor=0.129745 top=16.0) zoom_scale=0.339672
   102.0805 [touchpad] gesture-type event: type=42 device='Core Pointer' source-type=0

zoom out:

   186.4138 [touchpad] gesture-type event: type=42 device='Core Pointer' source-type=0
   186.4138 [touchpad] pinch phase=1 x=503.0 y=359.0 dx=0.000 dy=0.000 scale=0.529386 state=0x0
   186.4139 [darkroom pinch] update x=503.0 y=359.0 raw_dx=0.000 raw_dy=0.000 eff_dx=0.000 eff_dy=0.000 scale=0.529386 state=0x0 -> tscale=0.364659 (floor=0.129745 top=16.0) zoom_scale=0.182329
   186.4198 [touchpad] gesture-type event: type=42 device='Core Pointer' source-type=0
   186.4198 [touchpad] pinch phase=1 x=503.0 y=359.0 dx=0.000 dy=0.000 scale=0.523804 state=0x0
   186.4198 [darkroom pinch] update x=503.0 y=359.0 raw_dx=0.000 raw_dy=0.000 eff_dx=0.000 eff_dy=0.000 scale=0.523804 state=0x0 -> tscale=0.360814 (floor=0.129745 top=16.0) zoom_scale=0.180407
   186.4388 [touchpad] gesture-type event: type=42 device='Core Pointer' source-type=0
   186.4388 [touchpad] pinch phase=1 x=503.0 y=359.0 dx=0.000 dy=0.000 scale=0.519496 state=0x0
   186.4388 [darkroom pinch] update x=503.0 y=359.0 raw_dx=0.000 raw_dy=0.000 eff_dx=0.000 eff_dy=0.000 scale=0.519496 state=0x0 -> tscale=0.357846 (floor=0.129745 top=16.0) zoom_scale=0.178923
   186.4454 [touchpad] gesture-type event: type=42 device='Core Pointer' source-type=0
   186.4454 [touchpad] pinch phase=1 x=503.0 y=359.0 dx=0.000 dy=0.000 scale=0.516310 state=0x0
   186.4454 [darkroom pinch] update x=503.0 y=359.0 raw_dx=0.000 raw_dy=0.000 eff_dx=0.000 eff_dy=0.000 scale=0.516310 state=0x0 -> tscale=0.355651 (floor=0.129745 top=16.0) zoom_scale=0.177826
   186.4638 [touchpad] gesture-type event: type=42 device='Core Pointer' source-type=0
   186.4639 [touchpad] pinch phase=1 x=503.0 y=359.0 dx=0.000 dy=0.000 scale=0.511961 state=0x0
   186.4639 [darkroom pinch] update x=503.0 y=359.0 raw_dx=0.000 raw_dy=0.000 eff_dx=0.000 eff_dy=0.000 scale=0.511961 state=0x0 -> tscale=0.352656 (floor=0.129745 top=16.0) zoom_scale=0.176328
   186.4699 [touchpad] gesture-type event: type=42 device='Core Pointer' source-type=0
   186.4699 [touchpad] pinch phase=1 x=503.0 y=359.0 dx=0.000 dy=0.000 scale=0.510938 state=0x0
   186.4699 [darkroom pinch] update x=503.0 y=359.0 raw_dx=0.000 raw_dy=0.000 eff_dx=0.000 eff_dy=0.000 scale=0.510938 state=0x0 -> tscale=0.351951 (floor=0.129745 top=16.0) zoom_scale=0.175975
   186.4888 [touchpad] gesture-type event: type=42 device='Core Pointer' source-type=0

combined zoom and pan still does not work.
Starting with zoom and then try to pan. Log output:

   258.2680 [touchpad] pinch phase=1 x=386.0 y=289.0 dx=0.000 dy=0.000 scale=1.469380 state=0x0
   258.2680 [darkroom pinch] update x=386.0 y=289.0 raw_dx=0.000 raw_dy=0.000 eff_dx=0.000 eff_dy=0.000 scale=1.469380 state=0x0 -> tscale=0.508622 (floor=0.129745 top=16.0) zoom_scale=0.254311
   258.2812 [touchpad] gesture-type event: type=42 device='Core Pointer' source-type=0
   258.2812 [touchpad] pinch phase=1 x=386.0 y=289.0 dx=0.000 dy=0.000 scale=1.469380 state=0x0
   258.2812 [darkroom pinch] update x=386.0 y=289.0 raw_dx=0.000 raw_dy=0.000 eff_dx=0.000 eff_dy=0.000 scale=1.469380 state=0x0 -> tscale=0.508622 (floor=0.129745 top=16.0) zoom_scale=0.254311
   258.3013 [touchpad] gesture-type event: type=42 device='Core Pointer' source-type=0
   258.3013 [touchpad] pinch phase=1 x=386.0 y=289.0 dx=0.000 dy=0.000 scale=1.469380 state=0x0
   258.3013 [darkroom pinch] update x=386.0 y=289.0 raw_dx=0.000 raw_dy=0.000 eff_dx=0.000 eff_dy=0.000 scale=1.469380 state=0x0 -> tscale=0.508622 (floor=0.129745 top=16.0) zoom_scale=0.254311
   258.3896 [touchpad] gesture-type event: type=42 device='Core Pointer' source-type=0
   258.3896 [touchpad] pinch phase=1 x=386.0 y=289.0 dx=0.000 dy=0.000 scale=1.469380 state=0x0
   258.3896 [darkroom pinch] update x=386.0 y=289.0 raw_dx=0.000 raw_dy=0.000 eff_dx=0.000 eff_dy=0.000 scale=1.469380 state=0x0 -> tscale=0.508622 (floor=0.129745 top=16.0) zoom_scale=0.254311
   258.4096 [touchpad] gesture-type event: type=42 device='Core Pointer' source-type=0
   258.4096 [touchpad] pinch phase=1 x=386.0 y=289.0 dx=0.000 dy=0.000 scale=1.469380 state=0x0
   258.4096 [darkroom pinch] update x=386.0 y=289.0 raw_dx=0.000 raw_dy=0.000 eff_dx=0.000 eff_dy=0.000 scale=1.469380 state=0x0 -> tscale=0.508622 (floor=0.129745 top=16.0) zoom_scale=0.254311
   258.4263 [touchpad] gesture-type event: type=42 device='Core Pointer' source-type=0
   258.4263 [touchpad] pinch phase=1 x=386.0 y=289.0 dx=0.000 dy=0.000 scale=1.469380 state=0x0
   258.4263 [darkroom pinch] update x=386.0 y=289.0 raw_dx=0.000 raw_dy=0.000 eff_dx=0.000 eff_dy=0.000 scale=1.469380 state=0x0 -> tscale=0.508622 (floor=0.129745 top=16.0) zoom_scale=0.254311
   258.4346 [touchpad] gesture-type event: type=42 device='Core Pointer' source-type=0
   258.4347 [touchpad] pinch phase=1 x=386.0 y=289.0 dx=0.000 dy=0.000 scale=1.469380 state=0x0
   258.4347 [darkroom pinch] update x=386.0 y=289.0 raw_dx=0.000 raw_dy=0.000 eff_dx=0.000 eff_dy=0.000 scale=1.469380 state=0x0 -> tscale=0.508622 (floor=0.129745 top=16.0) zoom_scale=0.254311
   258.4347 [touchpad] gesture-type event: type=42 device='Core Pointer' source-type=0
   258.4347 [touchpad] pinch phase=1 x=386.0 y=289.0 dx=0.000 dy=0.000 scale=1.466443 state=0x0
   258.4347 [darkroom pinch] update x=386.0 y=289.0 raw_dx=0.000 raw_dy=0.000 eff_dx=0.000 eff_dy=0.000 scale=1.466443 state=0x0 -> tscale=0.507605 (floor=0.129745 top=16.0) zoom_scale=0.253802
   258.4575 [touchpad] gesture-type event: type=42 device='Core Pointer' source-type=0

The macOS built-in trackpad reports as GDK_SOURCE_MOUSE (not GDK_SOURCE_TOUCHPAD).
The _scrolled routing condition required either GDK_SOURCE_TOUCHPAD or a device previously seen in a pinch event (_touchpad).
On macOS, neither condition held before a pinch had been seen, so two-finger scroll events fell through to dt_view_manager_scrolled (image navigation) instead of gesture_pan (panning). A #ifdef GDK_WINDOWING_QUARTZ branch now routes all non-ctrl smooth scrolls to gesture_pan on macOS.

Net result: On macOS, zoom via pinch works (was already working), and pan via two-finger swipe now works via gesture_pan. Combined pinch+pan simultaneously is not possible at the GDK level on macOS — NSEventTypeMagnify does not deliver the translational component; it must be done as sequential operations (zoom then pan).
@da-phil
Copy link
Copy Markdown
Contributor Author

da-phil commented Apr 16, 2026

Thanks for providing the log msgs, this was helpful.
Turns out that macOS uses the generic GDK_SOURCE_MOUSE input device for gestures instead of GDK_SOURCE_TOUCHPAD which seems to be used on all other platforms gtk supports.

Can you try again with the latest changes please and provide logs if it still doesn't work?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants