Skip to content

Commit 1486557

Browse files
committed
Scale down MacOS scrolling from event controllers
MacOS trackpad scroll events appear to be of much greater magnitude than those on other systems. To make them usable, we need to scale down the scroll deltas. For traditional GTK 3 code, dt_gui_get_scroll_unit_deltas() took care of this. Unfortunately, this code depends on the "scroll-event" signal. To make the code GTK 4 ready, we need to handle the "scroll" signal generated by a GtkEventControllerScroll. For discrete scrolls, we can handle this transparently via the dt_gui_connect_scroll_discrete() setup function. This new function is similar to dt_gui_connect_scroll(). There is no need to call it with the GTK_EVENT_CONTROLLER_SCROLL_DISCRETE flag, as it adds this automatically. If darktable is compiled for MacOS, a proxy function will scale down the scroll events. Note that this commit doesn't do anything to help scale down scroll events from a non-discrete GtkEventControllerScroll. Also: In bauhaus _widget_scroll, actually warn if called an a non-scroll event or no event rather than having a useless NOP conditional. Don't free the event in unlikely case we can't retrieve it. Also: Update/remove a couple comments. In GTK4 it seems preferable to target events directly to widgets (as gtk_propagate_event and gtk_event_controller_handle_event are gone) so don't apologize in comments for using gtk_widget_event(). Fixes: #20698
1 parent fbd6a7a commit 1486557

4 files changed

Lines changed: 109 additions & 18 deletions

File tree

src/bauhaus/bauhaus.c

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3077,12 +3077,11 @@ static void _widget_scroll(GtkEventControllerScroll *controller,
30773077
{
30783078
GdkEvent *event = gtk_get_current_event();
30793079
if(!event || gdk_event_get_event_type(event) != GDK_SCROLL)
3080+
dt_print(DT_DEBUG_ALWAYS, "[_widget_scroll] called on non-scroll event");
3081+
else if(dt_gui_ignore_scroll(&event->scroll))
30803082
{
3081-
// NOP, should not happen
3082-
}
3083-
if(dt_gui_ignore_scroll(&event->scroll))
3084-
{
3085-
// hack: GTK3 event controller handlers can't return GDK_EVENT_PROPAGATE
3083+
// event controller handlers can't propagate events, so synthesize
3084+
// an event directly to the destination widget
30863085
GtkWidget *sw = gtk_widget_get_ancestor(widget, GTK_TYPE_SCROLLED_WINDOW);
30873086
if(sw)
30883087
gtk_widget_event(sw, event);
@@ -3118,7 +3117,7 @@ static void _widget_scroll(GtkEventControllerScroll *controller,
31183117
_combobox_next_sensitive(w, delta, 0, FALSE);
31193118
}
31203119
}
3121-
gdk_event_free(event);
3120+
if(event) gdk_event_free(event);
31223121
}
31233122

31243123
static gboolean _widget_key_press(GtkWidget *widget, GdkEventKey *event)
@@ -3733,9 +3732,8 @@ static void dt_bh_init(DtBauhausWidget *w)
37333732
dt_gui_connect_motion(w, _widget_motion, _widget_enter, _widget_leave, widget);
37343733

37353734
GtkEventController *scroll_controller =
3736-
dt_gui_connect_scroll(w, GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES
3737-
| GTK_EVENT_CONTROLLER_SCROLL_DISCRETE,
3738-
_widget_scroll, widget);
3735+
dt_gui_connect_scroll_discrete(w, GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES,
3736+
_widget_scroll, widget);
37393737
// allows for capturing propagated events from other widgets
37403738
gtk_event_controller_set_propagation_phase(scroll_controller, GTK_PHASE_BUBBLE);
37413739

src/gui/gtk.c

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4668,6 +4668,93 @@ GtkEventController *(dt_gui_connect_scroll)(GtkWidget *widget,
46684668
return controller;
46694669
}
46704670

4671+
typedef void (*scroll_handler_t)(GtkEventControllerScroll*, gdouble, gdouble, gpointer);
4672+
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)
4701+
{
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)
4709+
{
4710+
h->cur_dx += dx / DT_UI_SCROLL_SMOOTH_DELTA_SCALE;
4711+
h->cur_dy += dy / DT_UI_SCROLL_SMOOTH_DELTA_SCALE;
4712+
dx = dy = 0.0;
4713+
if(fabs(h->cur_dx) >= 1.0)
4714+
{
4715+
int steps = trunc(h->cur_dx);
4716+
h->cur_dx -= steps;
4717+
dx = steps;
4718+
}
4719+
if(fabs(h->cur_dy) >= 1.0)
4720+
{
4721+
int steps = trunc(h->cur_dy);
4722+
h->cur_dy -= steps;
4723+
dy = steps;
4724+
}
4725+
}
4726+
if(dx != 0.0 || dy != 0.0)
4727+
h->scroll_callback_real(self, dx, dy, h->scroll_data_real);
4728+
}
4729+
#endif
4730+
4731+
GtkEventController *(dt_gui_connect_scroll_discrete)(GtkWidget *widget,
4732+
GtkEventControllerScrollFlags flags,
4733+
GCallback scroll_callback,
4734+
gpointer data)
4735+
{
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;
4741+
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);
4749+
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
4756+
}
4757+
46714758

46724759
static int busy_nest_count = 0;
46734760

src/gui/gtk.h

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -564,11 +564,6 @@ GtkGesture *(dt_gui_connect_drag)(GtkWidget *widget,
564564
ASSERT_FUNC_TYPE(drag_update, void(*)(GtkGestureDrag *, double, double, __typeof__(data))), \
565565
dt_gui_connect_drag(GTK_WIDGET(widget), G_CALLBACK(drag_begin), G_CALLBACK(drag_end), G_CALLBACK(drag_update), (data)))
566566

567-
#define dt_gui_claim(gesture) \
568-
gtk_gesture_set_state(GTK_GESTURE(gesture), GTK_EVENT_SEQUENCE_CLAIMED)
569-
#define dt_gui_deny(gesture) \
570-
gtk_gesture_set_state(GTK_GESTURE(gesture), GTK_EVENT_SEQUENCE_DENIED)
571-
572567
GtkEventController *(dt_gui_connect_motion)(GtkWidget *widget,
573568
GCallback motion,
574569
GCallback enter,
@@ -588,6 +583,19 @@ GtkEventController *(dt_gui_connect_scroll)(GtkWidget *widget,
588583
ASSERT_FUNC_TYPE(scroll, void(*)(GtkEventControllerScroll *, double, double, __typeof__(data))), \
589584
dt_gui_connect_scroll(GTK_WIDGET(widget), (flags), G_CALLBACK(scroll), (data)))
590585

586+
GtkEventController *(dt_gui_connect_scroll_discrete)(GtkWidget *widget,
587+
GtkEventControllerScrollFlags flags,
588+
GCallback scroll,
589+
gpointer data);
590+
#define dt_gui_connect_scroll_discrete(widget, flags, scroll, data) ( \
591+
ASSERT_FUNC_TYPE(scroll, void(*)(GtkEventControllerScroll *, double, double, __typeof__(data))), \
592+
dt_gui_connect_scroll_discrete(GTK_WIDGET(widget), (flags), G_CALLBACK(scroll), (data)))
593+
594+
#define dt_gui_claim(gesture) \
595+
gtk_gesture_set_state(GTK_GESTURE(gesture), GTK_EVENT_SEQUENCE_CLAIMED)
596+
#define dt_gui_deny(gesture) \
597+
gtk_gesture_set_state(GTK_GESTURE(gesture), GTK_EVENT_SEQUENCE_DENIED)
598+
591599
// GTK4 gtk_event_controller_get_current_event_state(GTK_EVENT_CONTROLLER(controller));
592600
#define dt_modifier_eq(controller, mask)\
593601
dt_modifier_is(dt_key_modifier_state(), mask)

src/libs/histogram.c

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,6 @@ static void _eventbox_scroll_callback(GtkEventControllerScroll* self,
486486
GDK_SHIFT_MASK | GDK_MOD1_MASK))
487487
{
488488
// bubble to adjusting the overall widget size
489-
// FIXME: use gtk_event_controller_handle_event()
490489
gtk_widget_event(s->scope_draw, event);
491490
}
492491
else if(s->highlight != DT_SCOPES_HIGHLIGHT_NONE)
@@ -863,9 +862,8 @@ void gui_init(dt_lib_module_t *self)
863862

864863
// FIXME: add (optional) propagation phase argument to dt_gui_connect_*()
865864
GtkEventController *scroll_controller =
866-
dt_gui_connect_scroll(eventbox, GTK_EVENT_CONTROLLER_SCROLL_VERTICAL
867-
| GTK_EVENT_CONTROLLER_SCROLL_DISCRETE,
868-
_eventbox_scroll_callback, s);
865+
dt_gui_connect_scroll_discrete(eventbox, GTK_EVENT_CONTROLLER_SCROLL_VERTICAL,
866+
_eventbox_scroll_callback, s);
869867
gtk_event_controller_set_propagation_phase(scroll_controller, GTK_PHASE_CAPTURE);
870868
// use GTK_PHASE_TARGET to capture enter/leave events, as
871869
// enter/leave events apparently not bubbled in GTK < 3.24.43.

0 commit comments

Comments
 (0)