From bc508c899590d1e071c9884deee7eca82f90b0b0 Mon Sep 17 00:00:00 2001 From: Arecsu Date: Tue, 11 Aug 2026 08:59:35 -0300 Subject: [PATCH 1/3] fix: mouse wheel responds on the first tick after a direction change The discrete scroll accumulators (both the event controller proxy used by bauhaus sliders/dropdowns and dt_gui_get_scroll_unit_deltas) kept the signed fractional remainder from the previous scroll direction. After reversing direction, the first tick was silently spent cancelling that stale remainder, so it did nothing and a second tick was needed before the value changed. Drop the accumulated remainder when the incoming scroll delta changes sign, so the new direction takes effect on its very first tick. --- src/gui/gtk.c | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/src/gui/gtk.c b/src/gui/gtk.c index cbb13e1334b..572b038b0f2 100644 --- a/src/gui/gtk.c +++ b/src/gui/gtk.c @@ -600,16 +600,27 @@ gboolean dt_gui_get_scroll_unit_deltas(const GdkEventScroll *event, acc_x = acc_y = 0.0; break; } - // accumulate trackpad/touch scrolls until they make a unit - // scroll, and only then tell caller that there is a scroll to - // handle + { + // same direction-change handling as the discrete scroll proxy: + // drop the remainder accumulated in the previous direction so the + // first tick of the new direction is not spent cancelling it + const gdouble scroll_delta_x = dt_gdk_event_get_scroll_delta_x(event); + const gdouble scroll_delta_y = dt_gdk_event_get_scroll_delta_y(event); + if((scroll_delta_x < 0.0 && acc_x > 0.0) || (scroll_delta_x > 0.0 && acc_x < 0.0)) + acc_x = 0.0; + if((scroll_delta_y < 0.0 && acc_y > 0.0) || (scroll_delta_y > 0.0 && acc_y < 0.0)) + acc_y = 0.0; + // accumulate trackpad/touch scrolls until they make a unit + // scroll, and only then tell caller that there is a scroll to + // handle #ifdef GDK_WINDOWING_QUARTZ // on macOS deltas need to be scaled - acc_x += dt_gdk_event_get_scroll_delta_x(event) / DT_UI_SCROLL_SMOOTH_DELTA_SCALE; - acc_y += dt_gdk_event_get_scroll_delta_y(event) / DT_UI_SCROLL_SMOOTH_DELTA_SCALE; + acc_x += scroll_delta_x / DT_UI_SCROLL_SMOOTH_DELTA_SCALE; + acc_y += scroll_delta_y / DT_UI_SCROLL_SMOOTH_DELTA_SCALE; #else - acc_x += dt_gdk_event_get_scroll_delta_x(event); - acc_y += dt_gdk_event_get_scroll_delta_y(event); + acc_x += scroll_delta_x; + acc_y += scroll_delta_y; #endif + } const gdouble amt_x = trunc(acc_x); const gdouble amt_y = trunc(acc_y); if(amt_x != 0 || amt_y != 0) @@ -5002,6 +5013,15 @@ static void _scroll_proxy_real(GtkEventControllerScroll* controller, dy = _scroll_attenuate(dy); if(discrete) { + // a change in scroll direction must not be spent cancelling the + // remainder accumulated in the previous direction: if it is kept, + // the first tick of the new direction does nothing and a second + // one is needed before the value changes. Drop the stale + // remainder so the new direction responds on its very first tick. + if((dx < 0.0 && _scroll_discrete_dx > 0.0) || (dx > 0.0 && _scroll_discrete_dx < 0.0)) + _scroll_discrete_dx = 0.0; + if((dy < 0.0 && _scroll_discrete_dy > 0.0) || (dy > 0.0 && _scroll_discrete_dy < 0.0)) + _scroll_discrete_dy = 0.0; _scroll_discrete_dx += dx; _scroll_discrete_dy += dy; dx = dy = 0.0; From 68830399f0f18982697c690ef894ce785ae4bb4e Mon Sep 17 00:00:00 2001 From: Arecsu Date: Tue, 11 Aug 2026 12:10:05 -0300 Subject: [PATCH 2/3] fix: don't attenuate wheel notch deltas in the discrete scroll proxy Wheel notches arrive as GDK_SCROLL_SMOOTH events with |delta| == 1.0. The discrete proxy (bauhaus sliders, dropdowns and their popups) attenuated them by the 0.95 Linux/Windows scale, leaving a single notch at 0.95 -- below the 1.0 emit threshold of the accumulator. As a result the first notch of a scroll direction silently did nothing, and the fractional remainder it built up made the first tick after a direction change do nothing either. Dropping the remainder on a direction change (the previous commit) cannot help here: after the reset a single 0.95 notch still cannot cross the threshold. Keep the delta unattenuated in the discrete proxy so a notch is exactly one step; the smooth/touchpad proxy keeps the attenuation, and macOS keeps its compression on both paths since its scroll deltas are distance-based (several units per event). --- src/gui/gtk.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/gui/gtk.c b/src/gui/gtk.c index 572b038b0f2..fb9e4716d6a 100644 --- a/src/gui/gtk.c +++ b/src/gui/gtk.c @@ -5007,10 +5007,29 @@ static void _scroll_proxy_real(GtkEventControllerScroll* controller, && !gdk_event_get_pointer_emulated(event) && !_scroll_sidebar(controller, dy, event)) { - if(dt_gdk_event_get_scroll_direction(event) == GDK_SCROLL_SMOOTH) + const GdkScrollDirection direction = dt_gdk_event_get_scroll_direction(event); + if(direction == GDK_SCROLL_SMOOTH) { + // Wheel notches arrive here as GDK_SCROLL_SMOOTH events with + // |delta| == 1.0. For the discrete proxy a notch must be exactly + // one step, so keep those deltas unattenuated: attenuating a notch + // to 0.95 (the Linux/Windows scale) left it below the 1.0 emit + // threshold, so the first notch of a direction silently did + // nothing, and the fractional remainder it built up made the first + // tick after a direction change do nothing either. The non- + // discrete (smooth/touchpad) proxy keeps the attenuation. macOS + // scroll deltas are distance-based (several units per event), so it + // keeps the compression on both paths. +#ifdef GDK_WINDOWING_QUARTZ dx = _scroll_attenuate(dx); dy = _scroll_attenuate(dy); +#else + if(!discrete) + { + dx = _scroll_attenuate(dx); + dy = _scroll_attenuate(dy); + } +#endif if(discrete) { // a change in scroll direction must not be spent cancelling the From 00ad5438e6633769937d19072f29d0f64fefac95 Mon Sep 17 00:00:00 2001 From: Arecsu Date: Thu, 13 Aug 2026 13:50:16 -0300 Subject: [PATCH 3/3] gtk: simplify scroll attenuation conditional per review Restructure the #ifdef so only the conditional differs between platforms, making the shared attenuation code explicit. --- src/gui/gtk.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/gui/gtk.c b/src/gui/gtk.c index fb9e4716d6a..d842d7047ef 100644 --- a/src/gui/gtk.c +++ b/src/gui/gtk.c @@ -5020,16 +5020,13 @@ static void _scroll_proxy_real(GtkEventControllerScroll* controller, // discrete (smooth/touchpad) proxy keeps the attenuation. macOS // scroll deltas are distance-based (several units per event), so it // keeps the compression on both paths. -#ifdef GDK_WINDOWING_QUARTZ - dx = _scroll_attenuate(dx); - dy = _scroll_attenuate(dy); -#else +#ifndef GDK_WINDOWING_QUARTZ if(!discrete) +#endif { dx = _scroll_attenuate(dx); dy = _scroll_attenuate(dy); } -#endif if(discrete) { // a change in scroll direction must not be spent cancelling the