darktable-org / darktable-org/darktable
Inconsistent scroll behaviour
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_UP→delta_y = -1,GDK_SCROLL_DOWN→+1,GDK_SCROLL_LEFT→delta_x = -1,GDK_SCROLL_RIGHT→+1.- Scalar-collapse caveat:
dt_gui_get_scroll_delta()anddt_gui_get_scroll_unit_delta()(src/gui/gtk.c:597, 609) returndelta_x + delta_y. Any handler built on these treatsLEFTlikeUPandRIGHTlikeDOWN, collapsing the two axes to one scalar. GtkEventControllerScroll"scroll" signal (used by bauhaus, histogram):UP→dy < 0,RIGHT→dx > 0(GTK convention).- Mask-shape handlers get a boolean
up, mapped throughdt_mask_scroll_increases()(src/develop/imageop_gui.c:317). The config keymasks_scroll_down_increases(defaultfalse) 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-348 → src/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_callback → src/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_deltareturns 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_eventnegates the delta for black-point so that both regions brighten together. The scroll path instead passesdy - 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+scrollgraph 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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