Skip to content

Commit e8af2e6

Browse files
committed
gtk: rework MacOS-friendly attenuated smooth scrolling
Attenuate smooth scroll delta by calculating delta ** compression, which will reduce large deltas (apparently found in momentum-based scrolling on MacOS). On MacOS, instead of scaling down smooth scroll by DT_UI_SCROLL_SMOOTH_DELTA_SCALE, scale down by 0.25. It appears that for MacOS, we do not receive "scroll-begin" and "scroll-end" events for trackpad events, so instead exam the GdkEvent, which brings this code closer to extant working dt_gui_get_scroll_unit_deltas(). Don't use GTK_EVENT_CONTROLLER_SCROLL_DISCRETE flag, regardless of OS, as _scroll_discrete_proxy() will accumulate smooth scroll events. Don't bother to handle scroll stop events as we won't receive them from GTK and it is OK to not clear accumulated scroll on stop. Simplify storage for proxy discrete scroll handler. Don't use an allocated struct. We can keep dx/dy globally, as we do not expect multiple simultaneous scrolls of multiple widgets. Store real scroll handler as GObject data. Note that the exponential attenuation math is on recommendation from ChatGPT, based on very little actual data from MacOS.
1 parent 1486557 commit e8af2e6

1 file changed

Lines changed: 47 additions & 64 deletions

File tree

src/gui/gtk.c

Lines changed: 47 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -4669,90 +4669,73 @@ GtkEventController *(dt_gui_connect_scroll)(GtkWidget *widget,
46694669
}
46704670

46714671
typedef void (*scroll_handler_t)(GtkEventControllerScroll*, gdouble, gdouble, gpointer);
4672+
static gdouble _scroll_discrete_dx = 0.0;
4673+
static gdouble _scroll_discrete_dy = 0.0;
4674+
static const char *_scroll_discrete_real_handler_key = "real-scroll-discrete-handler";
46724675

4673-
#ifdef GDK_WINDOWING_QUARTZ
4674-
typedef struct _scroll_handler_proxy_t
4675-
{
4676-
scroll_handler_t scroll_callback_real;
4677-
gpointer scroll_data_real;
4678-
gboolean smooth_scroll;
4679-
gdouble cur_dx;
4680-
gdouble cur_dy;
4681-
} _scroll_handler_proxy_t;
4682-
4683-
static void _scroll_discrete_begin(GtkEventControllerScroll* self,
4684-
_scroll_handler_proxy_t *h)
4685-
{
4686-
// scroll-begin is generated by events from trackpoint or touchpad
4687-
h->smooth_scroll = TRUE;
4688-
h->cur_dx = h->cur_dy = 0.0;
4689-
}
4690-
4691-
static void _scroll_discrete_end(GtkEventControllerScroll* self,
4692-
_scroll_handler_proxy_t *h)
4693-
{
4694-
h->smooth_scroll = FALSE;
4695-
}
4696-
4697-
static void _scroll_discrete_proxy(GtkEventControllerScroll* self,
4698-
gdouble dx,
4699-
gdouble dy,
4700-
_scroll_handler_proxy_t *h)
4676+
static void _scroll_discrete_proxy(GtkEventControllerScroll* controller,
4677+
gdouble dx,
4678+
gdouble dy,
4679+
gpointer user_data)
47014680
{
4702-
// Even though the event controller has already accumulated smooth
4703-
// scroll events to discrete dx/dy, MacOS smooth scrolling events
4704-
// are too fast so scale them down via another accumulating stage.
4705-
// For GTK3, we handled this via dt_gui_get_scroll_unit_deltas(),
4706-
// but to be GTK4 ready, use this code, which is heavily modeled on
4707-
// GTK's gtk_event_controller_scroll_handle_event()
4708-
if(h->smooth_scroll)
4681+
GdkEvent *event = gtk_get_current_event();
4682+
if(!event) return;
4683+
if(gdk_event_get_event_type(event) == GDK_SCROLL
4684+
&& event->scroll.direction == GDK_SCROLL_SMOOTH
4685+
// avoid double counting real and emulated events when receiving smooth scrolls
4686+
&& !gdk_event_get_pointer_emulated(event))
47094687
{
4710-
h->cur_dx += dx / DT_UI_SCROLL_SMOOTH_DELTA_SCALE;
4711-
h->cur_dy += dy / DT_UI_SCROLL_SMOOTH_DELTA_SCALE;
4688+
// MacOS scrolling is apparently distance-based, so can produce a
4689+
// range of large/small deltas. For GTK3, we accumulating &
4690+
// attenuating via dt_gui_get_scroll_unit_deltas(). For GTK4-ready
4691+
// "scroll" events, work as discrete scrolling implemented in
4692+
// gtk_event_controller_scroll_handle_event() but also attenuate
4693+
#ifdef GDK_WINDOWING_QUARTZ
4694+
const double scale = 0.25;
4695+
const double compression = 0.7;
4696+
#else
4697+
const double scale = 0.95;
4698+
const double compression = 0.9;
4699+
#endif
4700+
_scroll_discrete_dx += copysign(pow(fabs(dx), compression), dx * scale);
4701+
_scroll_discrete_dy += copysign(pow(fabs(dy), compression), dy * scale);
47124702
dx = dy = 0.0;
4713-
if(fabs(h->cur_dx) >= 1.0)
4703+
if(fabs(_scroll_discrete_dx) >= 1.0)
47144704
{
4715-
int steps = trunc(h->cur_dx);
4716-
h->cur_dx -= steps;
4705+
int steps = trunc(_scroll_discrete_dx);
4706+
_scroll_discrete_dx -= steps;
47174707
dx = steps;
47184708
}
4719-
if(fabs(h->cur_dy) >= 1.0)
4709+
if(fabs(_scroll_discrete_dy) >= 1.0)
47204710
{
4721-
int steps = trunc(h->cur_dy);
4722-
h->cur_dy -= steps;
4711+
int steps = trunc(_scroll_discrete_dy);
4712+
_scroll_discrete_dy -= steps;
47234713
dy = steps;
47244714
}
47254715
}
47264716
if(dx != 0.0 || dy != 0.0)
4727-
h->scroll_callback_real(self, dx, dy, h->scroll_data_real);
4717+
{
4718+
scroll_handler_t real_handler =
4719+
g_object_get_data(G_OBJECT(controller), _scroll_discrete_real_handler_key);
4720+
real_handler(controller, dx, dy, user_data);
4721+
}
4722+
gdk_event_free(event);
47284723
}
4729-
#endif
47304724

47314725
GtkEventController *(dt_gui_connect_scroll_discrete)(GtkWidget *widget,
47324726
GtkEventControllerScrollFlags flags,
47334727
GCallback scroll_callback,
4734-
gpointer data)
4728+
gpointer user_data)
47354729
{
4736-
// on macOS, set up a proxy to scale smooth scroll deltas
4737-
#ifdef GDK_WINDOWING_QUARTZ
4738-
_scroll_handler_proxy_t *const h = dt_calloc1_align_type(_scroll_handler_proxy_t);
4739-
h->scroll_callback_real = (scroll_handler_t)scroll_callback;
4740-
h->scroll_data_real = data;
4730+
// Use proxy, not GTK discrete scrolling, to attenuate. We could use
4731+
// GTK discrete scrolling for non-MacOS, but it makes it hard to
4732+
// test if a chunk of code is particular to one OS.
47414733
GtkEventController *controller =
4742-
dt_gui_connect_scroll(widget,
4743-
flags | GTK_EVENT_CONTROLLER_SCROLL_DISCRETE,
4744-
_scroll_discrete_proxy,
4745-
h);
4746-
g_object_weak_ref(G_OBJECT(widget), (GWeakNotify) dt_free_align_ptr, h);
4747-
g_signal_connect(controller, "scroll-begin", G_CALLBACK(_scroll_discrete_begin), h);
4748-
g_signal_connect(controller, "scroll-end", G_CALLBACK(_scroll_discrete_end), h);
4734+
dt_gui_connect_scroll(widget, flags & ~GTK_EVENT_CONTROLLER_SCROLL_DISCRETE,
4735+
_scroll_discrete_proxy, user_data);
4736+
g_object_set_data(G_OBJECT(controller),
4737+
_scroll_discrete_real_handler_key, scroll_callback);
47494738
return controller;
4750-
#else
4751-
return dt_gui_connect_scroll(widget,
4752-
flags | GTK_EVENT_CONTROLLER_SCROLL_DISCRETE,
4753-
(scroll_handler_t)scroll_callback,
4754-
data);
4755-
#endif
47564739
}
47574740

47584741

0 commit comments

Comments
 (0)