From 2959c1e9965bc99774763e15ab5cdef004d2379e Mon Sep 17 00:00:00 2001 From: Kofa Date: Wed, 3 Jun 2026 22:10:38 +0200 Subject: [PATCH 01/12] fix scroll direction: collapse dx/dy to dominant axis, correct radius/size polarity --- src/bauhaus/bauhaus.c | 32 ++++++++++++++++++++++++-------- src/gui/gtk.c | 6 ++++-- src/gui/gtk.h | 2 +- src/iop/atrous.c | 2 +- src/iop/colorzones.c | 2 +- src/iop/denoiseprofile.c | 2 +- src/iop/lowlight.c | 2 +- src/iop/monochrome.c | 2 +- src/iop/rawdenoise.c | 2 +- 9 files changed, 35 insertions(+), 17 deletions(-) diff --git a/src/bauhaus/bauhaus.c b/src/bauhaus/bauhaus.c index adb276f874a6..5c6af9721abc 100644 --- a/src/bauhaus/bauhaus.c +++ b/src/bauhaus/bauhaus.c @@ -508,13 +508,21 @@ static gboolean _popup_scroll(GtkWidget *widget, gpointer user_data) { dt_bauhaus_widget_t *w = darktable.bauhaus->current; - int delta_y = 0; - if(dt_gui_get_scroll_unit_delta(event, &delta_y)) + if(w->type == DT_BAUHAUS_COMBOBOX) { - if(w->type == DT_BAUHAUS_COMBOBOX) - _combobox_next_sensitive(w, delta_y, 0, w->combobox.mute_scrolling); - else - _slider_zoom_range(w, delta_y); + // match keyboard: right & down -> next + int delta_x = 0, delta_y = 0; + if(dt_gui_get_scroll_unit_deltas(event, &delta_x, &delta_y)) + { + int delta = abs(delta_x) > abs(delta_y) ? delta_x : delta_y; + _combobox_next_sensitive(w, delta, 0, w->combobox.mute_scrolling); + } + } + else + { + int delta = 0; + if(dt_gui_get_scroll_unit_delta(event, &delta)) + _slider_zoom_range(w, delta); } return TRUE; } @@ -3087,14 +3095,17 @@ static void _widget_scroll(GtkEventControllerScroll *controller, { gtk_widget_grab_focus(widget); - int delta = dx + dy; - if(delta != 0) + int magnitude_x = fabs(dx); + int magnitude_y = fabs(dy); + + if(magnitude_x || magnitude_y) { dt_bauhaus_widget_t *w = (dt_bauhaus_widget_t *)widget; _request_focus(w); if(w->type == DT_BAUHAUS_SLIDER) { + int delta = magnitude_x > magnitude_y ? -dx : dy; const gboolean force = darktable.control->element == DT_ACTION_ELEMENT_FORCE && event->scroll.window == gtk_widget_get_window(widget); if(force && dt_modifier_is(event->scroll.state, GDK_SHIFT_MASK | GDK_CONTROL_MASK)) @@ -3106,7 +3117,12 @@ static void _widget_scroll(GtkEventControllerScroll *controller, _slider_add_step(widget, - delta, event->scroll.state, force); } else + { + // match keyboard: right & down -> next + int delta = magnitude_x > magnitude_y ? dx : dy; + _combobox_next_sensitive(w, delta, 0, FALSE); + } } } if(event) gdk_event_free(event); diff --git a/src/gui/gtk.c b/src/gui/gtk.c index 32fc10563c8b..008567ad82c5 100644 --- a/src/gui/gtk.c +++ b/src/gui/gtk.c @@ -603,7 +603,8 @@ gboolean dt_gui_get_scroll_delta(const GdkEventScroll *event, gdouble delta_x, delta_y; if(dt_gui_get_scroll_deltas(event, &delta_x, &delta_y)) { - *delta = delta_x + delta_y; + // treat right like up, left like down + *delta = fabs(delta_x) > fabs(delta_y) ? -delta_x : delta_y; return TRUE; } return FALSE; @@ -615,7 +616,8 @@ gboolean dt_gui_get_scroll_unit_delta(const GdkEventScroll *event, int delta_x, delta_y; if(dt_gui_get_scroll_unit_deltas(event, &delta_x, &delta_y)) { - *delta = delta_x + delta_y; + // treat right like up, left like down + *delta = abs(delta_x) > abs(delta_y) ? -delta_x : delta_y; return TRUE; } return FALSE; diff --git a/src/gui/gtk.h b/src/gui/gtk.h index 93a9999f6489..5a16f68cb26f 100644 --- a/src/gui/gtk.h +++ b/src/gui/gtk.h @@ -236,7 +236,7 @@ gboolean dt_gui_get_scroll_unit_deltas(const GdkEventScroll *event, int *delta_x * So if Shift changes scrolling effect, both scrolls should be handled the same. * For this case (or if it's otherwise useful) use the following 2 functions. */ -/* Return sum of scroll deltas from event. Return TRUE if any deltas +/* Return delta of larger magnitude from the event. Return TRUE if any deltas * can be retrieved. Handles both GDK_SCROLL_UP/DOWN/LEFT/RIGHT and * GDK_SCROLL_SMOOTH style scroll events. */ gboolean dt_gui_get_scroll_delta(const GdkEventScroll *event, gdouble *delta); diff --git a/src/iop/atrous.c b/src/iop/atrous.c index a953d74f85d5..9c913f8668df 100644 --- a/src/iop/atrous.c +++ b/src/iop/atrous.c @@ -1523,7 +1523,7 @@ static gboolean area_scrolled(GtkWidget *widget, int delta_y; if(dt_gui_get_scroll_unit_delta(event, &delta_y)) { - g->mouse_radius = CLAMP(g->mouse_radius * (1.0 + 0.1 * delta_y), 0.25 / BANDS, 1.0); + g->mouse_radius = CLAMP(g->mouse_radius * (1.0 - 0.1 * delta_y), 0.25 / BANDS, 1.0); gtk_widget_queue_draw(widget); } return TRUE; diff --git a/src/iop/colorzones.c b/src/iop/colorzones.c index 170cce237158..a01e6cdf4e49 100644 --- a/src/iop/colorzones.c +++ b/src/iop/colorzones.c @@ -1906,7 +1906,7 @@ static gboolean _area_scrolled_callback(GtkWidget *widget, if(g->edit_by_area) { const int bands = p->curve_num_nodes[g->channel]; - g->mouse_radius = CLAMP(g->mouse_radius * (1.0 + 0.1 * delta_y), 0.2 / bands, 1.0); + g->mouse_radius = CLAMP(g->mouse_radius * (1.0 - 0.1 * delta_y), 0.2 / bands, 1.0); gtk_widget_queue_draw(widget); } else diff --git a/src/iop/denoiseprofile.c b/src/iop/denoiseprofile.c index 95a3f951b36a..023c536e19d2 100644 --- a/src/iop/denoiseprofile.c +++ b/src/iop/denoiseprofile.c @@ -3548,7 +3548,7 @@ static gboolean denoiseprofile_scrolled(GtkWidget *widget, int delta_y; if(dt_gui_get_scroll_unit_delta(event, &delta_y)) { - g->mouse_radius = CLAMP(g->mouse_radius * (1.f + 0.1f * delta_y), + g->mouse_radius = CLAMP(g->mouse_radius * (1.f - 0.1f * delta_y), 0.2f / DT_IOP_DENOISE_PROFILE_BANDS, 1.f); gtk_widget_queue_draw(widget); } diff --git a/src/iop/lowlight.c b/src/iop/lowlight.c index 92fef19453fd..3466f0bf2960 100644 --- a/src/iop/lowlight.c +++ b/src/iop/lowlight.c @@ -776,7 +776,7 @@ static gboolean lowlight_scrolled(GtkWidget *widget, GdkEventScroll *event, dt_i int delta_y; if(dt_gui_get_scroll_unit_delta(event, &delta_y)) { - g->mouse_radius = CLAMP(g->mouse_radius * (1.0 + 0.1 * delta_y), 0.2 / DT_IOP_LOWLIGHT_BANDS, 1.0); + g->mouse_radius = CLAMP(g->mouse_radius * (1.0 - 0.1 * delta_y), 0.2 / DT_IOP_LOWLIGHT_BANDS, 1.0); gtk_widget_queue_draw(widget); } diff --git a/src/iop/monochrome.c b/src/iop/monochrome.c index 3fc9870736c0..77a63032e28a 100644 --- a/src/iop/monochrome.c +++ b/src/iop/monochrome.c @@ -538,7 +538,7 @@ static gboolean _monochrome_scrolled(GtkWidget *widget, GdkEventScroll *event, d if(dt_gui_get_scroll_unit_delta(event, &delta_y)) { const float old_size = p->size; - p->size = CLAMP(p->size + delta_y * 0.1, 0.5f, 3.0f); + p->size = CLAMP(p->size - delta_y * 0.1, 0.5f, 3.0f); if(old_size != p->size) dt_dev_add_history_item(darktable.develop, self, TRUE); gtk_widget_queue_draw(widget); } diff --git a/src/iop/rawdenoise.c b/src/iop/rawdenoise.c index 1e06988c8dc9..ac9ffcd2bf9c 100644 --- a/src/iop/rawdenoise.c +++ b/src/iop/rawdenoise.c @@ -857,7 +857,7 @@ static gboolean rawdenoise_scrolled(GtkWidget *widget, GdkEventScroll *event, dt int delta_y; if(dt_gui_get_scroll_unit_delta(event, &delta_y)) { - g->mouse_radius = CLAMP(g->mouse_radius * (1.0 + 0.1 * delta_y), 0.2 / DT_IOP_RAWDENOISE_BANDS, 1.0); + g->mouse_radius = CLAMP(g->mouse_radius * (1.0 - 0.1 * delta_y), 0.2 / DT_IOP_RAWDENOISE_BANDS, 1.0); gtk_widget_queue_draw(widget); } From faedee7de0804ebcc209535e16531a7b332582cd Mon Sep 17 00:00:00 2001 From: Kofa Date: Sun, 7 Jun 2026 14:28:04 +0200 Subject: [PATCH 02/12] fix some scroll direction regressions where up!=right (e.g. up==left==previous, down==right==next) --- src/develop/imageop.c | 10 +++++++--- src/dtgtk/culling.c | 17 +++++++++++------ src/libs/tools/timeline.c | 7 ++++--- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/develop/imageop.c b/src/develop/imageop.c index 6fe982d0bbb6..74e817e0dd60 100644 --- a/src/develop/imageop.c +++ b/src/develop/imageop.c @@ -2368,9 +2368,13 @@ static gboolean _presets_scroll_callback(GtkWidget *widget, { if(dt_gui_ignore_scroll(event)) return FALSE; - int delta_y = 0; - if(dt_gui_get_scroll_unit_delta(event, &delta_y)) - dt_gui_presets_apply_adjacent_preset(module, delta_y); + // preset cycling: right==down==next + int delta_x = 0, delta_y = 0; + if(dt_gui_get_scroll_unit_deltas(event, &delta_x, &delta_y)) + { + const int delta = abs(delta_x) > abs(delta_y) ? delta_x : delta_y; + dt_gui_presets_apply_adjacent_preset(module, delta); + } return TRUE; } diff --git a/src/dtgtk/culling.c b/src/dtgtk/culling.c index 3a8e08076c17..e7b7e07e1d16 100644 --- a/src/dtgtk/culling.c +++ b/src/dtgtk/culling.c @@ -694,10 +694,11 @@ static gboolean _event_scroll(GtkWidget *widget, gdouble dx = 0.0, dy = 0.0; if(dt_gui_get_scroll_deltas(e, &dx, &dy) && (dx != 0.0 || dy != 0.0)) { - // dt_gui_get_scroll_deltas gives the raw fractional platform delta. + // dt_gui_get_scroll_deltas gives the raw fractional platform delta. // Scale so that one full unit of scroll (delta_y == 1.0) matches the - // 0.5 zoom_delta of a discrete mouse-wheel click. - const float zoom_delta = (float)(-(dx + dy) * 0.5); + // 0.5 zoom_delta of a discrete mouse-wheel click. right==up==zoom-in + const gdouble delta = fabs(dx) > fabs(dy) ? -dx : dy; + const float zoom_delta = (float)(-delta * 0.5); // convert screen to culling coordinates int ox = 0, oy = 0; GdkWindow *win = gtk_widget_get_window(table->widget); @@ -750,12 +751,14 @@ static gboolean _event_scroll(GtkWidget *widget, } } - int delta; - if(dt_gui_get_scroll_unit_delta(e, &delta)) + int delta_x = 0, delta_y = 0; + if(dt_gui_get_scroll_unit_deltas(e, &delta_x, &delta_y)) { + const gboolean is_horizontal = abs(delta_x) > abs(delta_y); if(dt_modifiers_include(e->state, GDK_CONTROL_MASK)) { - // zooming + // zooming: right==up==zoom-in + const int delta = is_horizontal ? -delta_x : delta_y; const float zoom_delta = delta < 0 ? 0.5f : -0.5f; // convert screen to culling coordinates int ox = 0, oy = 0; @@ -775,6 +778,8 @@ static gboolean _event_scroll(GtkWidget *widget, } else { + // navigation: right==down==next + const int delta = is_horizontal ? delta_x : delta_y; const int move = delta < 0 ? -1 : 1; dt_print(DT_DEBUG_INPUT, "[culling scroll] navigate move=%d", move); _thumbs_move(table, move); diff --git a/src/libs/tools/timeline.c b/src/libs/tools/timeline.c index b3c4b37a5a16..aa2ec475ad2d 100644 --- a/src/libs/tools/timeline.c +++ b/src/libs/tools/timeline.c @@ -1342,10 +1342,11 @@ static gboolean _lib_timeline_scroll_callback(GtkWidget *w, GdkEventScroll *e, d } else { - int delta; - if(dt_gui_get_scroll_unit_delta(e, &delta)) + // timeline panning: right==down==forward + int delta_x = 0, delta_y = 0; + if(dt_gui_get_scroll_unit_deltas(e, &delta_x, &delta_y)) { - int move = delta; + int move = abs(delta_x) > abs(delta_y) ? delta_x : delta_y; if(dt_modifier_is(e->state, GDK_SHIFT_MASK)) move *= 2; _time_add(&(strip->time_pos), move, strip->zoom); From 2b2f385c1f7140f197940f0bb5b5867b41c58052 Mon Sep 17 00:00:00 2001 From: Kofa Date: Sun, 14 Jun 2026 15:30:43 +0200 Subject: [PATCH 03/12] fix scroll direction: in scopes, up/right (both for exposure and black level) will now mean brightening (moving exposure in the positive, black level correction in the negative direction) --- src/iop/exposure.c | 2 ++ src/libs/histogram.c | 12 +++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/iop/exposure.c b/src/iop/exposure.c index e0d96955576a..304f81cd05d1 100644 --- a/src/iop/exposure.c +++ b/src/iop/exposure.c @@ -840,6 +840,8 @@ static void _exposure_proxy_handle_event(int n_press, ? g->deflicker_target_level : g->exposure); const float val = dt_bauhaus_slider_get(widget); const float accel = dt_accel_get_speed_multiplier(widget, state); + // delta > 0 means 'brighten'; increase exposure or move the black point correction + // in the negative direction if(is_blackpoint) delta = -delta; diff --git a/src/libs/histogram.c b/src/libs/histogram.c index 2715469bfce0..e6dc3892cc0a 100644 --- a/src/libs/histogram.c +++ b/src/libs/histogram.c @@ -502,7 +502,17 @@ static void _eventbox_scroll_callback(GtkEventControllerScroll* self, // FIXME: should handle smooth scrolling rather than discrete? // FIXME: should scrolling of scope be handled in the drawable rather than // the eventbox. - dt_dev_exposure_handle_event(0, dy - dx, event->scroll.state, + + // get the dominant direction, standardize on delta < 0 => up==right + const gdouble delta = fabs(dx) > fabs(dy) ? -dx : dy; + // for exposure ('highlight') mode: we want up/right: brighten the image + // -> increase exposure, or decrease black point (exposure handles that); + // in vectorscope, rgb parade, parts of the visualization moves upwards; + // in histogram, to the right. + // dt_dev_exposure_handle_event requires 'delta' in the 'brighten' > 0 convention, that is also what + // drag events emit + + dt_dev_exposure_handle_event(0, - delta, event->scroll.state, s->highlight == DT_SCOPES_HIGHLIGHT_BLACK_POINT); } else From fa3e0abd79d58abdc5f70390ab14dac298159b52 Mon Sep 17 00:00:00 2001 From: Kofa Date: Wed, 17 Jun 2026 19:16:40 +0200 Subject: [PATCH 04/12] scroll: vectorscope to honour the horizontal axis (left/right tilt, 2-finger drag) --- src/libs/scopes/vectorscope.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/scopes/vectorscope.c b/src/libs/scopes/vectorscope.c index a777b15851cb..4f4eac77b72c 100644 --- a/src/libs/scopes/vectorscope.c +++ b/src/libs/scopes/vectorscope.c @@ -1172,7 +1172,8 @@ static void _vec_eventbox_scroll(dt_scopes_mode_t *const self, // FIXME: if have own drawable for vectorscope can set scroll handler directly // clamp as mouse wheel scrolls sometimes report a delta of 2 - const int delta = CLAMP(delta_y, -1.0, 1.0); + const gdouble dominant_axis_delta = fabs(delta_x) > fabs(delta_y) ? -delta_x : delta_y; + const int delta = CLAMP(dominant_axis_delta, -1.0, 1.0); if(dt_modifier_is(state, GDK_SHIFT_MASK)) //( SHIFT+SCROLL { d->harmony_guide.width = (d->harmony_guide.width + delta + DT_COLOR_HARMONY_WIDTH_N) From 270595b7e6725a8e1410cf81c1e671f1ce1c54a2 Mon Sep 17 00:00:00 2001 From: Kofa Date: Wed, 17 Jun 2026 19:37:45 +0200 Subject: [PATCH 05/12] scroll: vectorscope Alt+right/left to select next/previous type (like Alt+down/up) --- src/libs/scopes/vectorscope.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libs/scopes/vectorscope.c b/src/libs/scopes/vectorscope.c index 4f4eac77b72c..c8323cc9be40 100644 --- a/src/libs/scopes/vectorscope.c +++ b/src/libs/scopes/vectorscope.c @@ -1173,7 +1173,7 @@ static void _vec_eventbox_scroll(dt_scopes_mode_t *const self, // FIXME: if have own drawable for vectorscope can set scroll handler directly // clamp as mouse wheel scrolls sometimes report a delta of 2 const gdouble dominant_axis_delta = fabs(delta_x) > fabs(delta_y) ? -delta_x : delta_y; - const int delta = CLAMP(dominant_axis_delta, -1.0, 1.0); + const int delta = (int) CLAMP(dominant_axis_delta, -1.0, 1.0); if(dt_modifier_is(state, GDK_SHIFT_MASK)) //( SHIFT+SCROLL { d->harmony_guide.width = (d->harmony_guide.width + delta + DT_COLOR_HARMONY_WIDTH_N) @@ -1181,8 +1181,12 @@ static void _vec_eventbox_scroll(dt_scopes_mode_t *const self, } else if(dt_modifier_is(state, GDK_MOD1_MASK)) // ALT+SCROLL { + // we want up, left: previous type; down, right: next type + const gdouble dominant_axis_selection_delta = fabs(delta_x) > fabs(delta_y) ? delta_x : delta_y; + const int selection_delta = (int) CLAMP(dominant_axis_selection_delta, -1.0, 1.0); + const dt_color_harmony_type_t new_type = - (d->harmony_guide.type + delta + DT_COLOR_HARMONY_N) % DT_COLOR_HARMONY_N; + (d->harmony_guide.type + selection_delta + DT_COLOR_HARMONY_N) % DT_COLOR_HARMONY_N; if(new_type == DT_COLOR_HARMONY_NONE) // turn all buttons off gtk_toggle_button_set_active From f8604393e8c0eeddb7cd1409ea55ae8177418a3a Mon Sep 17 00:00:00 2001 From: Kofa Date: Wed, 17 Jun 2026 21:32:54 +0200 Subject: [PATCH 06/12] scroll: scroll up/down (tilt right/left) now increases/decreases default speed in shortcut editor --- src/gui/accelerators.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gui/accelerators.c b/src/gui/accelerators.c index 3eb4393e596b..3bd21aaf0ac4 100644 --- a/src/gui/accelerators.c +++ b/src/gui/accelerators.c @@ -4556,7 +4556,8 @@ gboolean dt_shortcut_dispatcher(GtkWidget *w, int delta; if(middle_click || dt_gui_get_scroll_unit_delta(&event->scroll, &delta)) { - s.speed = middle_click ? -1 : powf(10.0f, delta); + // delta < 0 -> 10^(-delta) increases speed + s.speed = middle_click ? -1 : powf(10.0f, -delta); if(_insert_shortcut(&s, TRUE, FALSE)) dt_control_log("%s", _action_description(&s, 2)); From e4ae1c81bcd3ba3a35b3afa5e4120e7aecc733df Mon Sep 17 00:00:00 2001 From: Kofa Date: Wed, 17 Jun 2026 21:55:12 +0200 Subject: [PATCH 07/12] scroll: fix navigation panel left/right tilt zoom (for tilt, dy was 0, dy < 0 evaluated to false -> zoom out for both) --- src/libs/navigation.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/libs/navigation.c b/src/libs/navigation.c index 975fd476ffec..3e9fff949947 100644 --- a/src/libs/navigation.c +++ b/src/libs/navigation.c @@ -544,8 +544,13 @@ static void _lib_navigation_scroll_callback(GtkEventControllerScroll *controller if(_lib_navigation_widget_to_center(GTK_EVENT_CONTROLLER(controller), event->scroll.x, event->scroll.y, &x, &y)) + { + const double delta = fabs(dx) > fabs(dy) ? -dx : dy; + const gboolean zoom_in = delta < 0; + dt_dev_zoom_move(&darktable.develop->full, DT_ZOOM_SCROLL, - 0.0f, dy < 0, x, y, constrain); + 0.0f, zoom_in, x, y, constrain); + } } gdk_event_free(event); } From 1cd3f39ef7b69d53622a45e99a5a42d1bfbf60a7 Mon Sep 17 00:00:00 2001 From: Kofa Date: Sat, 4 Jul 2026 18:49:39 +0200 Subject: [PATCH 08/12] scroll: fix thumbtable and filmstrip scoll issues --- src/dtgtk/thumbtable.c | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/dtgtk/thumbtable.c b/src/dtgtk/thumbtable.c index 4a4bee4c49f6..6b401e684eb8 100644 --- a/src/dtgtk/thumbtable.c +++ b/src/dtgtk/thumbtable.c @@ -1121,7 +1121,7 @@ static gboolean _event_scroll(GtkWidget *widget, dt_thumbtable_t *table) { GdkEventScroll *e = (GdkEventScroll *)event; - int delta_x, delta_y; + int delta; // file manager can either scroll fractionally and smoothly for precision // touch pads, or in one-thumbnail increments for clicky scroll wheels, @@ -1129,16 +1129,25 @@ static gboolean _event_scroll(GtkWidget *widget, if(table->mode == DT_THUMBTABLE_MODE_FILEMANAGER && !dt_modifier_is(e->state, GDK_CONTROL_MASK)) { - gdouble deltaf_x, deltaf_y; + gdouble deltaf = 0.f; gboolean did_scroll; if(dt_conf_get_bool("thumbtable_fractional_scrolling")) { + gdouble deltaf_x, deltaf_y; did_scroll = dt_gui_get_scroll_deltas(e, &deltaf_x, &deltaf_y); + if (did_scroll) { + // file manager scroll: tilt right (delta_x >) 0 or scroll down (delta_y > 0) -> down (towards the last image) + deltaf = fabs(deltaf_x) > fabs(deltaf_y) ? deltaf_x : deltaf_y; + } } else { + int delta_x, delta_y; did_scroll = dt_gui_get_scroll_unit_deltas(e, &delta_x, &delta_y); - deltaf_y = (float)delta_y; + if (did_scroll) + { + deltaf = abs(delta_x) > abs(delta_y) ? delta_x : delta_y; + } } if(did_scroll) { @@ -1148,14 +1157,14 @@ static gboolean _event_scroll(GtkWidget *widget, { table->scroll_timeout_id = g_timeout_add(10, _event_scroll_compressed, table); } - table->scroll_value += deltaf_y; + table->scroll_value += deltaf; } // we stop here to avoid scrolledwindow to move return TRUE; } // filmstrip and zoom mode always use clicky scroll: - if(dt_gui_get_scroll_unit_deltas(e, &delta_x, &delta_y)) + if(dt_gui_get_scroll_unit_delta(e, &delta)) { // for zoomable, scroll = zoom if(table->mode == DT_THUMBTABLE_MODE_ZOOM @@ -1163,7 +1172,7 @@ static gboolean _event_scroll(GtkWidget *widget, { if(table->mode == DT_THUMBTABLE_MODE_FILMSTRIP) { - const int sx = CLAMP(table->view_width / ((table->view_width / table->thumb_size / 2 + (delta_x+delta_y)) * 2 + 1), + const int sx = CLAMP(table->view_width / ((table->view_width / table->thumb_size / 2 + delta) * 2 + 1), dt_conf_get_int("min_panel_height"), dt_conf_get_int("max_panel_height")); dt_ui_panel_set_size(darktable.gui->ui, DT_UI_PANEL_BOTTOM, sx); @@ -1171,13 +1180,17 @@ static gboolean _event_scroll(GtkWidget *widget, else { const int old = dt_view_lighttable_get_zoom(darktable.view_manager); - const int new = CLAMP(old + delta_y, 1, DT_LIGHTTABLE_MAX_ZOOM); + const int new = CLAMP(old + delta, 1, DT_LIGHTTABLE_MAX_ZOOM); dt_thumbtable_zoom_changed(table, old, new); } } else if(table->mode == DT_THUMBTABLE_MODE_FILMSTRIP) { - _move(table, -(delta_x+delta_y) * (dt_modifier_is(e->state, GDK_SHIFT_MASK) + int delta_x, delta_y; + dt_gui_get_scroll_unit_deltas(e, &delta_x, &delta_y); + // filmstrip scroll: tilt right (delta_x >) 0 or scroll down (delta_y > 0) -> down (towards the last image) + delta = abs(delta_x) > abs(delta_y) ? delta_x : delta_y; + _move(table, -delta * (dt_modifier_is(e->state, GDK_SHIFT_MASK) ? table->view_width - table->thumb_size : table->thumb_size), 0, TRUE); @@ -1187,7 +1200,7 @@ static gboolean _event_scroll(GtkWidget *widget, dt_control_set_mouse_over_id(th->imgid); } } - // we stop here to avoid scrolledwindow to move + // we stop here to avoid scrolled window to move return TRUE; } @@ -1527,7 +1540,7 @@ static gboolean _event_motion_notify(GtkWidget *widget, table->drag_dy += dy; if(table->drag_thumb && !table->drag_thumb->moved) { - // we only considers that this is a real move if the total + // we only consider that this is a real move if the total // distance is not too low table->drag_thumb->moved = ((abs(table->drag_dx) + abs(table->drag_dy)) > DT_PIXEL_APPLY_DPI(8)); From 5781be33fb02c4222e17dd2daa0cf377b48fdfaf Mon Sep 17 00:00:00 2001 From: Kofa Date: Sun, 5 Jul 2026 09:22:24 +0200 Subject: [PATCH 09/12] scroll: colorzones node adjustment using the scroll wheel (int truncation) --- src/iop/colorzones.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/iop/colorzones.c b/src/iop/colorzones.c index a01e6cdf4e49..0953e07714e2 100644 --- a/src/iop/colorzones.c +++ b/src/iop/colorzones.c @@ -1911,8 +1911,8 @@ static gboolean _area_scrolled_callback(GtkWidget *widget, } else { - delta_y *= -DT_IOP_COLORZONES_DEFAULT_STEP; - return _move_point_internal(self, widget, g->selected, 0.f, delta_y, event->state); + const float dy = delta_y * -DT_IOP_COLORZONES_DEFAULT_STEP; + return _move_point_internal(self, widget, g->selected, 0.f, dy, event->state); } } From 8a196cd3836c07f3d3be07954fcceec6bf67a056 Mon Sep 17 00:00:00 2001 From: Kofa Date: Sun, 5 Jul 2026 09:49:27 +0200 Subject: [PATCH 10/12] scroll: map view zoom fix --- src/views/map.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/views/map.c b/src/views/map.c index 497bfb3ce24d..463ba872f4ee 100644 --- a/src/views/map.c +++ b/src/views/map.c @@ -2020,30 +2020,32 @@ static gboolean _view_map_scroll_event(GtkWidget *w, osm_gps_map_point_get_degrees(p, &lat, &lon); if(lib->loc.main.id > 0) { + const gboolean increase = event->direction == GDK_SCROLL_UP || event->direction == GDK_SCROLL_RIGHT; + const gboolean decrease = event->direction == GDK_SCROLL_DOWN || event->direction == GDK_SCROLL_LEFT; if(dt_map_location_included(lon, lat, &lib->loc.main.data)) { if(dt_modifier_is(event->state, GDK_SHIFT_MASK)) { - if(event->direction == GDK_SCROLL_DOWN) + if(increase) lib->loc.main.data.delta1 *= 1.1; - else + else if(decrease) lib->loc.main.data.delta1 /= 1.1; } else if(dt_modifier_is(event->state, GDK_CONTROL_MASK)) { - if(event->direction == GDK_SCROLL_DOWN) + if(increase) lib->loc.main.data.delta2 *= 1.1; - else + else if(decrease) lib->loc.main.data.delta2 /= 1.1; } else { - if(event->direction == GDK_SCROLL_DOWN) + if(increase) { lib->loc.main.data.delta1 *= 1.1; lib->loc.main.data.delta2 *= 1.1; } - else + else if (decrease) { lib->loc.main.data.delta1 /= 1.1; lib->loc.main.data.delta2 /= 1.1; From 881ecdacd6d953837cf1f2a8a156c26b225f7039 Mon Sep 17 00:00:00 2001 From: Kofa Date: Sun, 5 Jul 2026 10:46:44 +0200 Subject: [PATCH 11/12] scroll: fix filmstrip regression (double consumption of accumulated smooth scroll / touchpad events) --- src/dtgtk/thumbtable.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/dtgtk/thumbtable.c b/src/dtgtk/thumbtable.c index 6b401e684eb8..3d76a1fb968b 100644 --- a/src/dtgtk/thumbtable.c +++ b/src/dtgtk/thumbtable.c @@ -1121,7 +1121,6 @@ static gboolean _event_scroll(GtkWidget *widget, dt_thumbtable_t *table) { GdkEventScroll *e = (GdkEventScroll *)event; - int delta; // file manager can either scroll fractionally and smoothly for precision // touch pads, or in one-thumbnail increments for clicky scroll wheels, @@ -1164,12 +1163,16 @@ static gboolean _event_scroll(GtkWidget *widget, } // filmstrip and zoom mode always use clicky scroll: - if(dt_gui_get_scroll_unit_delta(e, &delta)) + int delta_x, delta_y; + + if(dt_gui_get_scroll_unit_deltas(e, &delta_x, &delta_y)) { // for zoomable, scroll = zoom if(table->mode == DT_THUMBTABLE_MODE_ZOOM || dt_modifier_is(e->state, GDK_CONTROL_MASK)) { + // up==right==zoom in + const int delta = abs(delta_x) > abs(delta_y) ? -delta_x : delta_y; if(table->mode == DT_THUMBTABLE_MODE_FILMSTRIP) { const int sx = CLAMP(table->view_width / ((table->view_width / table->thumb_size / 2 + delta) * 2 + 1), @@ -1186,10 +1189,8 @@ static gboolean _event_scroll(GtkWidget *widget, } else if(table->mode == DT_THUMBTABLE_MODE_FILMSTRIP) { - int delta_x, delta_y; - dt_gui_get_scroll_unit_deltas(e, &delta_x, &delta_y); // filmstrip scroll: tilt right (delta_x >) 0 or scroll down (delta_y > 0) -> down (towards the last image) - delta = abs(delta_x) > abs(delta_y) ? delta_x : delta_y; + const int delta = abs(delta_x) > abs(delta_y) ? delta_x : delta_y; _move(table, -delta * (dt_modifier_is(e->state, GDK_SHIFT_MASK) ? table->view_width - table->thumb_size : table->thumb_size), 0, TRUE); From 2ae626c406b66bcf5546c22f6e2dcb504f33ca81 Mon Sep 17 00:00:00 2001 From: Kofa Date: Sun, 5 Jul 2026 11:16:00 +0200 Subject: [PATCH 12/12] scroll: notebook tabs scroll down / tilt right: next, scroll up / tilt left: previous (same as how Chrome switches tabs) --- src/gui/gtk.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/gui/gtk.c b/src/gui/gtk.c index 008567ad82c5..128f58b2aed9 100644 --- a/src/gui/gtk.c +++ b/src/gui/gtk.c @@ -4022,16 +4022,19 @@ GtkNotebook *dt_ui_notebook_new(dt_action_def_t *def) static gboolean _notebook_scroll_callback(GtkNotebook *notebook, GdkEventScroll *event, - gpointer user_data) -{ + gpointer user_data) { if(dt_gui_ignore_scroll(event)) return FALSE; - int delta = 0; - if(dt_gui_get_scroll_unit_delta(event, &delta) && delta) + int delta_x = 0, delta_y = 0; + if(dt_gui_get_scroll_unit_deltas(event, &delta_x, &delta_y)) + { + // RIGHT: delta_x > 0, DOWN: delta_y > 0 -> next, like in filmstrip and lists + const int delta = abs(delta_x) > abs(delta_y) ? -delta_x : -delta_y; _action_process_tabs(notebook, DT_ACTION_EFFECT_DEFAULT_KEY, delta < 0 ? DT_ACTION_EFFECT_NEXT : DT_ACTION_EFFECT_PREVIOUS, delta); + } return TRUE; }