darktable-org / darktable-org/darktable

Inconsistent scroll behaviour

Open
#20,843 16 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C
Stars
13.1k
Forks
1.4k
Avg merge
22h 14m
Merged PRs (30d)
198

Description

Is there an existing issue for this?
  • I checked and did not find my issue in the already reported ones
Describe the bug

In general, scrolling up (away from the user) or to the right should increase size (of shapes: masks, effect feedback circles, feathering...) or values (e.g. on sliders), while scrolling down (towards the user) or to the left should do the opposite. Unfortunately, this is not always the case.
The following is an analysis of the current situation, performed with the aid of coding agents. I will review the UI findings later, when I have time. I can confirm several of them are correct (e.g. denoise (profiled)).

The effect of OS-level scroll reversals (e.g. "natural scroll") is not taken into account. Plus, we have a setting to reverse some adjustments (probably in response to "natural scroll"), which is mentioned below. More human verification / clarification is needed.

The purpose of this report is to start a discussion and come up with a plan.

1. Direction primitives

  • dt_gui_get_scroll_unit_deltas() / dt_gui_get_scroll_deltas() (src/gui/gtk.c): GDK_SCROLL_UPdelta_y = -1, GDK_SCROLL_DOWN+1, GDK_SCROLL_LEFTdelta_x = -1, GDK_SCROLL_RIGHT+1.
  • Scalar-collapse caveat: dt_gui_get_scroll_delta() and dt_gui_get_scroll_unit_delta() (src/gui/gtk.c:597, 609) return delta_x + delta_y. Any handler built on these treats LEFT like UP and RIGHT like DOWN, collapsing the two axes to one scalar.
  • GtkEventControllerScroll "scroll" signal (used by bauhaus, histogram): UPdy < 0, RIGHTdx > 0 (GTK convention).
  • Mask-shape handlers get a boolean up, mapped through dt_mask_scroll_increases() (src/develop/imageop_gui.c:317). The config key masks_scroll_down_increases (default false) inverts the whole family when enabled.

2. Controls that accept scroll

Class Source Behavior
Bauhaus sliders src/bauhaus/bauhaus.c::_widget_scroll, _popup_scroll vertical UP = increase; horizontal RIGHT = decrease (§4)
Bauhaus comboboxes same UP = previous entry, DOWN = next (ordered cycling)
Gradient sliders src/dtgtk/gradientslider.c::_gradient_slider_scroll_event vertical conforms; RIGHT violates via scalar helper
Notebook tabs src/gui/gtk.c::_notebook_scroll_callback UP = next, DOWN = previous (ordered cycling)
Preset cycling src/develop/imageop.c::_presets_scroll_callback ordered
Module-group buttons src/libs/modulegroups.c::_scroll_group_buttons ordered
Wrapped-panel content / resize src/gui/gtk.c::_resize_wrap_scroll, _scroll_wrap_height plain = pan; Shift+Alt = resize (§4)
Curve point editors src/iop/basecurve.c::_scrolled, src/iop/tonecurve.c::_scrolled, src/iop/rgbcurve.c::_area_scrolled_callback UP = raise point; RIGHT violates via scalar helper
Level handles src/iop/levels.c, src/iop/rgblevels.c UP = raise handle
Color-correction saturation src/iop/colorcorrection.c::dt_iop_colorcorrection_scrolled UP = increase; RIGHT violates
Zone-system bar src/iop/zonesystem.c::dt_iop_zonesystem_bar_scrolled UP = more zones; RIGHT violates
Geotagging datetime src/libs/geotagging.c::_datetime_scroll_over UP = +1; RIGHT violates
Timeline src/libs/tools/timeline.c::_lib_timeline_scroll_callback plain = pan; Ctrl = zoom (UP = zoom-in)
LUT list src/iop/lut3d.c::_mouse_scroll ordered list navigation
Darkroom image / second window src/views/darkroom.c::scrolled, _second_window_scrolled_callback UP = zoom-in; 2nd-window RIGHT violates via scalar helper
Lighttable / culling src/dtgtk/thumbtable.c::_event_scroll, src/dtgtk/culling.c::_event_scroll pan / zoom (UP = bigger thumbs)
Histogram / scope strip src/libs/histogram.c::_eventbox_scroll_callback, motion handler at src/libs/histogram.c:344-348src/iop/exposure.c::_exposure_proxy_handle_event drag UP/RIGHT brightens (conforms); scroll UP/RIGHT darkens in both the exposure and the black-point regions (§4)
Vectorscope src/libs/scopes/vectorscope.c::_vec_eventbox_scroll width is categorical cycling; rotation UP = counter-clockwise (§4)
OSM map src/views/map.c::_view_map_scroll_event base-map zoom conforms; active-location ROI violates (§4)

3. Module-specific / in-canvas widgets

Module Widget Source Status
mask: circle / ellipse / brush / path / gradient / object / group shape overlay (size / feather / opacity / rotation) src/develop/masks/{circle,ellipse,brush,path,gradient,object,group}.c::*_events_mouse_scrolled default conforms (user-invertible)
liquify warp stamp (size / strength / angle) src/iop/liquify.c::scrolled conforms (inherits mask preference)
graduated density density (Ctrl) / hardness (Shift) src/iop/graduatednd.c::scrolled conforms
tone equalizer on-image tone cursor src/iop/toneequal.c::scrolled conforms
retouch wavelet-decomposition bar src/iop/retouch.c::rt_wdbar_scrolled conforms
perspective correction near-line selection radius src/iop/ashift.c::scrolled conforms
color zones (curve mode) selected node y / Ctrl = graph zoom src/iop/colorzones.c::_area_scrolled_callback conforms
color equaliser graph forwards to bauhaus slider / notebook src/iop/colorequal.c::_area_scrolled_callback inherits slider behaviour
contrast equaliser feedback-circle diameter (mouse_radius) src/iop/atrous.c::area_scrolled violates — UP shrinks
denoise (profiled) feedback-circle diameter src/iop/denoiseprofile.c::denoiseprofile_scrolled violates
raw denoise feedback-circle diameter src/iop/rawdenoise.c::rawdenoise_scrolled violates
lowlight vision feedback-circle diameter src/iop/lowlight.c::lowlight_scrolled violates
color zones (edit-by-area) feedback-circle diameter src/iop/colorzones.c::_area_scrolled_callback violates
monochrome virtual-filter size (p->size) src/iop/monochrome.c::_monochrome_scrolled violates

4. Convention violations

4.1 Vertical (UP → decreases)

Each of these is an independent callback; the formula differs but the direction error is the same.

Widget Source Effect on UP
Contrast-equaliser feedback circle src/iop/atrous.c::area_scrolled radius shrinks
Denoise (profiled) feedback circle src/iop/denoiseprofile.c::denoiseprofile_scrolled radius shrinks
Raw-denoise feedback circle src/iop/rawdenoise.c::rawdenoise_scrolled radius shrinks
Lowlight feedback circle src/iop/lowlight.c::lowlight_scrolled radius shrinks
Color-zones edit-by-area circle src/iop/colorzones.c::_area_scrolled_callback (area branch) radius shrinks
Monochrome filter size src/iop/monochrome.c::_monochrome_scrolled filter shrinks
Map active-location ROI (delta1 / delta2) src/views/map.c::_view_map_scroll_event ROI shrinks
Histogram/scope exposure region (scroll) src/libs/histogram.c::_eventbox_scroll_callbacksrc/iop/exposure.c::_exposure_proxy_handle_event exposure decreases → image darkens
Histogram/scope black-point region (scroll) same black value increases (shadows clipped harder) → image darkens
Wrapped-panel resize (Shift+Alt+scroll) src/gui/gtk.c::_resize_wrap_scroll (line count), _scroll_wrap_height (pixel height) size / line count shrinks
Vectorscope harmony rotation src/libs/scopes/vectorscope.c::_vec_eventbox_scroll rotation decreases (counter-clockwise)

4.2 Horizontal (RIGHT → decreases) — all stem from scalar-collapse helpers

Any handler that reads scroll through dt_gui_get_scroll_delta(), dt_gui_get_scroll_unit_delta() or literally dx + dy treats RIGHT like DOWN. Known instances:

  • Bauhaus sliders — src/bauhaus/bauhaus.c::_widget_scroll, _popup_scroll
  • Gradient sliders — src/dtgtk/gradientslider.c::_gradient_slider_scroll_event
  • Base / tone / RGB curve point scroll — src/iop/{basecurve,tonecurve,rgbcurve}.c
  • Color-correction saturation — src/iop/colorcorrection.c::dt_iop_colorcorrection_scrolled
  • Zone-system bar — src/iop/zonesystem.c::dt_iop_zonesystem_bar_scrolled
  • Geotagging datetime fields — src/libs/geotagging.c::_datetime_scroll_over
  • Darkroom second-window zoom — src/views/darkroom.c::_second_window_scrolled_callback

In addition, histogram exposure decreases on RIGHT independently of the scalar helpers, because _eventbox_scroll_callback passes dy - dx directly.

Horizontal scroll only surfaces on devices that emit it (trackpads, tilt wheels).

5. Non-violations worth flagging

  • Mask preference masks_scroll_down_increases (src/develop/imageop_gui.c::dt_mask_scroll_increases): if the user toggles it on, every mask-shape and liquify scroll inverts; default is off so the default behaviour conforms.
  • Scope drag vs. scope scroll: the intuitive convention over a scope is "UP/RIGHT (in the scope's natural bright direction) brightens the image", not "UP/RIGHT increases the underlying slider value". Drag already follows this — each scope's get_exposure_delta returns a positive delta when the pointer moves towards the bright side (histogram: offset_x / width; horizontal waveform: -offset_y / height; vertical waveform: offset_x / width), and _exposure_proxy_handle_event negates the delta for black-point so that both regions brighten together. The scroll path instead passes dy - dx, giving delta = -1 for UP and for RIGHT, which darkens both regions — that is the §4 violation. This convention applies only when the widget in question is a scope; scrolling over the actual exposure/black sliders in the exposure module is governed by the regular "UP = increase slider value" rule.
  • Lighttable "images per row" (Ctrl+scroll): UP decreases the numeric count but means larger thumbnails ("zoom in"); not a violation in spirit.
  • Color-zones Ctrl+scroll graph zoom (zoom_factor *= 1 - 0.1*delta_y): UP zooms in. Conforms.
Steps to reproduce

Not specific -- use scroll in various modules

Expected behavior

No response

Logfile | Screenshot | Screencast

No response

Commit

No response

Where did you obtain darktable from?

darktable.org / GitHub release

darktable version

5.5.0+1010~gc0800819c8

What OS are you using?

Linux

What is the version of your OS?

Ubuntu 25.10

Describe your system

No response

Are you using OpenCL GPU in darktable?

None

If yes, what is the GPU card and driver?

No response

Please provide additional context if applicable. You can attach files too, but might need to rename to .txt or .zip

No response

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading the scroll direction helpers in src/gui/gtk.c and the listed callbacks across src/bauhaus, src/dtgtk, src/iop, src/libs, and src/views. Narrow the report to a decided set of violations, then verify each affected control's vertical and horizontal behavior; done means the agreed controls follow the documented convention and are manually checked.

Written by the indexing model from the issue text.

Assessment

Tech stack
c
Domain
desktop
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.