Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
widget_settings.h
Go to the documentation of this file.
1/*
2 * This file is part of Ansel,
3 * Copyright (C) 2026 Aurélien PIERRE.
4 *
5 * Ansel is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * Ansel is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with Ansel. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19#ifndef DT_WIDGETS_WIDGET_SETTINGS_H
20#define DT_WIDGETS_WIDGET_SETTINGS_H
21
22/* Only what the declarations below need: GtkWidget/GdkRGBA/cairo_t (gtk.h), pthread_t, and
23 * va_list. This header used to include gui/screen_metrics.h purely to re-export
24 * dt_cairo_image_surface_*() to ~45 consumers that never asked it for them -- a supply line
25 * nobody declared and nobody could see, which breaks somewhere unrelated the day it is
26 * tidied. Those files include screen_metrics.h themselves now. */
27#include <gtk/gtk.h>
28#include <pthread.h>
29#include <stdarg.h>
30
32
33/* Toolkit-wide state that widgets need and the application merely configures.
34 *
35 * These three used to be fields of dt_gui_gtk_t, which meant a widget had to reach the
36 * application global to read its own event mask. They are not application data -- a scroll
37 * mask, a "who owns scroll right now" register and a cairo filter are properties of the
38 * widget toolkit. Ownership moves here so widgets/ needs nothing from gui/.
39 *
40 * The application sets them once during GUI init; everything else reads them.
41 */
42
46
47/* Which widget currently owns scroll input.
48 *
49 * Ansel routes scroll to one widget at a time rather than letting it propagate: a slider
50 * under the pointer takes the wheel, and views clear the register when they change. */
53
55cairo_filter_t dt_widget_image_filter(void);
56void dt_widget_set_image_filter(cairo_filter_t filter);
57
58
59/* Widget-update suppression.
60 *
61 * Programmatic widget updates must not be mistaken for user input. Code wraps such updates
62 * in dt_gui_freeze_begin()/end() and every widget callback opens with
63 * `if(dt_gui_widgets_suppressed()) return;`.
64 *
65 * The depth counter used to live in dt_gui_gtk_t, which meant a widget had to reach the
66 * application global to find out whether it should ignore its own callback. */
67gboolean dt_gui_widgets_suppressed(void);
68
73void dt_widget_set_gui_thread(pthread_t thread);
74void dt_gui_freeze_begin_(const char *file, int line);
75void dt_gui_freeze_end_(const char *file, int line);
76void dt_gui_freeze_reset(void); // hard-reset depth to 0 (GUI init only)
77
78/* Bracket programmatic widget updates with these so the widget's own "value-changed" handler
79 * does not mistake them for user input. The scope-guard form ends the freeze automatically on
80 * every exit path, including an early return, which the raw pair leaks. */
81#define dt_gui_freeze_begin() dt_gui_freeze_begin_(__FILE__, __LINE__)
82#define dt_gui_freeze_end() dt_gui_freeze_end_(__FILE__, __LINE__)
83
84typedef struct { const char *file; int line; } dt_gui_freeze_token_t;
86{
87 dt_gui_freeze_end_(t->file, t->line);
88}
89#define DT_FREEZE_CAT_(a, b) a##b
90#define DT_FREEZE_CAT(a, b) DT_FREEZE_CAT_(a, b)
91#define dt_gui_widget_freeze() \
92 dt_gui_freeze_token_t DT_FREEZE_CAT(_dt_freeze_guard_, __LINE__) \
93 __attribute__((cleanup(dt_gui_freeze_release_))) = { __FILE__, __LINE__ }; \
94 dt_gui_freeze_begin_(__FILE__, __LINE__)
95
96/* Scroll deltas in discrete units, accumulating smooth-scroll fractions and discarding
97 * pointer-emulated duplicates. Pure GTK event arithmetic. */
98gboolean dt_gui_get_scroll_unit_deltas(const GdkEventScroll *event, int *delta_x, int *delta_y);
99gboolean dt_gui_get_scroll_unit_delta(const GdkEventScroll *event, int *delta);
100
103void dt_widget_set_scroll_reversed(gboolean reverse_x, gboolean reverse_y);
104
105/* The host's root window, used for things that exist before any widget does: resolving theme
106 * colours at toolkit init, and parenting a popup so Wayland compositors place it correctly.
107 * Unregistered: NULL, and callers fall back to screen defaults. */
108typedef GtkWidget *(*dt_widget_root_window_handler_t)(void);
111
112/* How wide the host would like `widget` to be naturally -- it knows which panel the widget
113 * sits in and how wide that panel is. Returns -1 when the host has no opinion, which is also
114 * what an unregistered handler reports. */
118
119/* Transient user-facing message ("that widget no longer exists"). How and where it is shown
120 * is the host's decision -- a toast, a status line, or nothing at all. Unregistered, the
121 * message is dropped. */
122typedef void (*dt_widget_message_handler_t)(const char *message);
124void dt_widget_message(const char *message);
125
126/* Pointer shape during widget interaction (a panel-handle drag wants a resize cursor).
127 * The host owns the window whose cursor changes, so it supplies the setter. Unregistered,
128 * the cursor is left alone. */
129typedef void (*dt_widget_cursor_handler_t)(GdkCursorType cursor);
131void dt_widget_set_cursor(GdkCursorType cursor);
132
133/* ------------------------------------------------------------------------------------------
134 * Diagnostics.
135 *
136 * A debug build prints straight to stdout; a release build compiles the call away. widgets/
137 * deliberately does not route diagnostics through the application: a logging system means a
138 * global flags word and a global stream, and reaching for either is what this module exists to
139 * avoid. Nothing is lost by keeping it local -- these messages describe widget internals, and
140 * whoever is reading them is running a debug build anyway.
141 * ------------------------------------------------------------------------------------------ */
142#ifdef _DEBUG
143void dt_widget_log(const char *format, ...) G_GNUC_PRINTF(1, 2);
144#define dt_widget_log_enabled() TRUE
145#else
146#define dt_widget_log(...) do { } while(0)
147#define dt_widget_log_enabled() FALSE
148#endif
149
152gboolean dt_widget_debug_overlays(void);
153void dt_widget_set_debug_overlays(gboolean enabled);
154
155/* ------------------------------------------------------------------------------------------
156 * Per-widget persistence.
157 *
158 * A resizable panel remembers the height the user dragged it to; a collapsible section
159 * remembers whether they left it open. The key is supplied by whoever built the widget --
160 * widgets/ neither invents keys nor knows where they are stored, because a configuration
161 * system is application state reached through a global, which is exactly what this module
162 * exists to keep out of the toolkit.
163 *
164 * Unregistered, nothing is stored and every read reports "not set", so a widget falls back to
165 * its default size or its collapsed state. That is also the correct behaviour for any host
166 * that has no preferences to offer.
167 * ------------------------------------------------------------------------------------------ */
168typedef gboolean (*dt_widget_stored_int_getter_t)(const char *key, int *value);
169typedef void (*dt_widget_stored_int_setter_t)(const char *key, int value);
170typedef gboolean (*dt_widget_stored_bool_getter_t)(const char *key);
171typedef void (*dt_widget_stored_bool_setter_t)(const char *key, gboolean value);
172
177
179gboolean dt_widget_stored_int(const char *key, int *value);
180void dt_widget_store_int(const char *key, int value);
181
183gboolean dt_widget_stored_bool(const char *key);
184void dt_widget_store_bool(const char *key, gboolean value);
185
186/* Has the application loaded its CSS theme yet? Dialogs that can run during startup -- before
187 * any styling exists -- pad themselves by hand when it has not. */
188gboolean dt_widget_theme_loaded(void);
189void dt_widget_set_theme_loaded(gboolean loaded);
190
191/* Return keyboard focus to the application's main working area. A widget that swallows keys
192 * (a text entry) has to hand focus back when the user presses Escape, but which widget is
193 * "the main area" is the host's business -- the image in darkroom, the grid in lighttable.
194 * Unregistered, the request is dropped. */
197void dt_widget_refocus(void);
198
199/* A GtkNotebook the host registered an owner for has switched page. The host relays this on
200 * its own signal bus; widgets/ has no bus and no idea what the owner is. */
204
205/* Toolkit metrics: the UI zoom factor, the integer device scale, and the resolved root font
206 * size in pixels. Widgets scale themselves by these; the application computes them from the
207 * screen and the theme and pushes them here. They lived in dt_gui_gtk_t, which meant a widget
208 * had to reach the application global to size itself. */
210double dt_widget_dpi(void);
211void dt_widget_set_dpi(double dpi);
212
217gboolean dt_widget_metrics_probed(void);
218
219double dt_widget_dpi_factor(void);
221
222double dt_widget_ppd(void);
223void dt_widget_set_ppd(double ppd);
224
230
232double dt_widget_em_size(void);
233void dt_widget_set_em_size(double em);
234
237double dt_get_system_gui_ppd(GtkWidget *widget);
238
240GdkModifierType dt_key_modifier_state(void);
241
242/* Scale a 96-DPI-baseline value. UI: logical pixels GTK will scale further. DEVICE: raw
243 * device pixels for cairo surfaces and hit-tests, which GTK does not scale for us. */
244#define DT_UI_SCALE_UI(value) ((value) * dt_widget_dpi_factor())
245#define DT_UI_SCALE_DEVICE(value) ((value) * dt_widget_dpi_factor() * dt_widget_ppd())
246#define DT_PIXEL_APPLY_DPI(value) DT_UI_SCALE_UI(value)
247#define DT_PIXEL_APPLY_DPI_DPP(value) DT_UI_SCALE_DEVICE(value)
248
249/* Gutter between children of boxes/grids/flowboxes -- settable only from code, so it is
250 * centralised here for the whole app. Expressed as a fraction of 1em so it tracks the user's
251 * font size like the em-based margins in ansel.css. 0.625em == 10px at the 16px reference.
252 * The font's pt->px conversion already folds in DPI, so this needs no DPI scaling on top. */
253#define DT_GUI_EM_SIZE ((gint)dt_widget_em_size())
254#define DT_GUI_BOX_SPACING_EM 0.625
255#define DT_GUI_BOX_SPACING ((gint)(DT_GUI_EM_SIZE * DT_GUI_BOX_SPACING_EM + 0.5))
256
257/* Colour-label slots. These mirror the application's dt_colorlabels_enum, and gui/gtk.c
258 * carries a _Static_assert that they cannot drift apart -- that is the one place both
259 * headers are visible. Declaring them here keeps widgets/ free of application headers. */
260enum
261{
269
270/* The colour-label palette, as RGBA. Widgets paint colour labels; which colours those are is
271 * a theme decision the application supplies. Indices match dt_colorlabels_enum. */
272const GdkRGBA *dt_widget_colorlabel(int index);
273void dt_widget_set_colorlabels(const GdkRGBA *labels, int count);
274
275/* Does `state` carry exactly `desired_modifier_mask`, ignoring lock/scroll bits? */
276static inline gboolean dt_modifier_is(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
277{
278 const GdkModifierType modifiers = gtk_accelerator_get_default_mod_mask();
279//TODO: on Macs, remap the GDK_CONTROL_MASK bit in desired_modifier_mask to be the bit for the Cmd key
280 return (state & modifiers) == desired_modifier_mask;
281}
282
283/* Does `state` carry AT LEAST `desired_modifier_mask`? Same arithmetic, weaker test. */
284static inline gboolean dt_modifiers_include(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
285{
286//TODO: on Macs, remap the GDK_CONTROL_MASK bit in desired_modifier_mask to be the bit for the Cmd key
287 const GdkModifierType modifiers = gtk_accelerator_get_default_mod_mask();
288 return (state & (modifiers & desired_modifier_mask)) == desired_modifier_mask;
289}
290
291/* Scroll deltas as fractions, for consumers that want the raw smooth-scroll amount rather
292 * than the accumulated discrete units above. */
293gboolean dt_gui_get_scroll_deltas(const GdkEventScroll *event, gdouble *delta_x, gdouble *delta_y);
294gboolean dt_gui_get_scroll_delta(const GdkEventScroll *event, gdouble *delta);
295
296
297/* ------------------------------------------------------------------------------------------
298 * Theme palette.
299 *
300 * The colours widgets paint with are a theme decision the application resolves (from CSS, at
301 * dt_gui_load_theme() time) and pushes here. Widgets read them; they do not look them up.
302 * Storage lives here rather than in the application struct so that widgets/draw.h can be a
303 * leaf header -- it paints with DT_GUI_COLOR_BUTTON_FG and must not reach for gui/.
304 * ------------------------------------------------------------------------------------------ */
341
343GdkRGBA *dt_widget_colors(void);
344
348
349/* Overlay tint for shapes drawn over the image (mask outlines, guides, crop handles). A user
350 * preference the application resolves; widgets/draw.h paints with it. */
355
357void dt_widget_set_overlay_color(double red, double green, double blue, double contrast);
358
359/* Mouse hit-test radius in device pixels: the raw value, and the one clamped to stay usable
360 * for overlay selection at any zoom. The darkroom recomputes both when the zoom changes. */
361float dt_widget_mouse_radius(void);
363void dt_widget_set_mouse_radius(float radius, float clamped);
364
366#define DT_GUI_MOUSE_EFFECT_RADIUS dt_widget_mouse_radius_clamped()
367
368
369/* ------------------------------------------------------------------------------------------
370 * Call-site diagnostics for two GTK setters.
371 *
372 * GTK reports only its own assertion site when a non-widget reaches gtk_widget_queue_draw(),
373 * which says nothing about which Ansel code owned the bad pointer. In debug-capable builds
374 * both calls are rerouted through a wrapper that names the caller's file and line, so an
375 * ownership/lifetime bug points at the source line that queued the redraw. Toggle state
376 * changes are wrapped for the same reason: they usually precede a redraw, so catching the
377 * invalid object here surfaces the first error rather than the secondary redraw assertion.
378 * ------------------------------------------------------------------------------------------ */
379#ifdef _DEBUG
380void dt_gtk_widget_queue_draw_ext(GtkWidget *widget, const char *name, const char *file, const int line);
381#define dt_gtk_widget_queue_draw(widget) dt_gtk_widget_queue_draw_ext((GtkWidget *)(widget), #widget, __FILE__, __LINE__)
382#define gtk_widget_queue_draw(widget) dt_gtk_widget_queue_draw(widget)
383
384void dt_gtk_toggle_button_set_active_ext(GtkToggleButton *toggle_button, const char *name, const gboolean active,
385 const char *file, const int line);
386#define dt_gtk_toggle_button_set_active(toggle_button, active) \
387 dt_gtk_toggle_button_set_active_ext((GtkToggleButton *)(toggle_button), #toggle_button, active, __FILE__, __LINE__)
388#define gtk_toggle_button_set_active(toggle_button, active) \
389 dt_gtk_toggle_button_set_active(toggle_button, active)
390#else
391#define dt_gtk_widget_queue_draw(widget) gtk_widget_queue_draw(widget)
392#define dt_gtk_toggle_button_set_active(toggle_button, active) gtk_toggle_button_set_active(toggle_button, active)
393#endif
394
396
397#endif // DT_WIDGETS_WIDGET_SETTINGS_H
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
const int t
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
const float delta
GdkRGBA color[]
Definition geotagging.c:539
char * key
uint32_t width
Definition mipmap_cache.c:0
const float factor
Definition pdf.h:91
const char * name
Definition pdf.h:90
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
const float uint32_t state[4]
void dt_widget_set_ppd(double ppd)
gboolean dt_gui_get_scroll_deltas(const GdkEventScroll *event, gdouble *delta_x, gdouble *delta_y)
GtkWidget * dt_widget_root_window(void)
float dt_widget_mouse_radius(void)
double dt_widget_ppd(void)
void(* dt_widget_refocus_handler_t)(void)
gboolean dt_gui_get_scroll_delta(const GdkEventScroll *event, gdouble *delta)
gboolean dt_gui_get_scroll_unit_deltas(const GdkEventScroll *event, int *delta_x, int *delta_y)
void dt_widget_set_colorlabels(const GdkRGBA *labels, int count)
void dt_widget_set_cursor_handler(dt_widget_cursor_handler_t handler)
gboolean(* dt_widget_stored_int_getter_t)(const char *key, int *value)
static gboolean dt_modifiers_include(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
gboolean dt_gui_get_scroll_unit_delta(const GdkEventScroll *event, int *delta)
void dt_widget_set_debug_overlays(gboolean enabled)
void dt_widget_set_dpi_factor(double factor)
gboolean(* dt_widget_stored_bool_getter_t)(const char *key)
dt_gui_color_t
@ DT_GUI_COLOR_BG
@ DT_GUI_COLOR_DARKROOM_PREVIEW_BG
@ DT_GUI_COLOR_LIGHTTABLE_FONT
@ DT_GUI_COLOR_MAP_LOC_SHAPE_HIGH
@ DT_GUI_COLOR_LOG_FG
@ DT_GUI_COLOR_BRUSH_CURSOR
@ DT_GUI_COLOR_LAST
@ DT_GUI_COLOR_THUMBNAIL_FONT
@ DT_GUI_COLOR_THUMBNAIL_SELECTED_BG
@ DT_GUI_COLOR_MAP_COUNT_BG
@ DT_GUI_COLOR_LOG_BG
@ DT_GUI_COLOR_THUMBNAIL_SELECTED_BORDER
@ DT_GUI_COLOR_THUMBNAIL_OUTLINE
@ DT_GUI_COLOR_MAP_LOC_SHAPE_LOW
@ DT_GUI_COLOR_LIGHTTABLE_BG
@ DT_GUI_COLOR_BUTTON_FG
@ DT_GUI_COLOR_DARKROOM_BG
@ DT_GUI_COLOR_THUMBNAIL_BORDER
@ DT_GUI_COLOR_LIGHTTABLE_PREVIEW_BG
@ DT_GUI_COLOR_WARNING
@ DT_GUI_COLOR_MAP_COUNT_SAME_LOC
@ DT_GUI_COLOR_MAP_COUNT_DIFF_LOC
@ DT_GUI_COLOR_THUMBNAIL_BG
@ DT_GUI_COLOR_PREVIEW_HOVER_BORDER
@ DT_GUI_COLOR_FILMSTRIP_BG
@ DT_GUI_COLOR_BRUSH_TRACE
@ DT_GUI_COLOR_THUMBNAIL_SELECTED_FONT
@ DT_GUI_COLOR_THUMBNAIL_SELECTED_OUTLINE
@ DT_GUI_COLOR_THUMBNAIL_HOVER_BG
@ DT_GUI_COLOR_PRINT_BG
@ DT_GUI_COLOR_MAP_LOC_SHAPE_DEF
@ DT_GUI_COLOR_THUMBNAIL_HOVER_FONT
@ DT_GUI_COLOR_THUMBNAIL_HOVER_OUTLINE
void dt_widget_set_image_filter(cairo_filter_t filter)
void dt_widget_set_scroll_reversed(gboolean reverse_x, gboolean reverse_y)
void dt_widget_set_source_rgba(cairo_t *cr, dt_gui_color_t color, float opacity_coef)
gboolean dt_widget_stored_int(const char *key, int *value)
GdkRGBA * dt_widget_colors(void)
void dt_gui_freeze_reset(void)
void dt_widget_set_theme_loaded(gboolean loaded)
static void dt_gui_freeze_release_(dt_gui_freeze_token_t *t)
gint(* dt_widget_natural_width_handler_t)(GtkWidget *widget)
gboolean dt_widget_metrics_probed(void)
GtkWidget * dt_widget_scroll_focus(void)
void dt_widget_set_natural_width_handler(dt_widget_natural_width_handler_t handler)
void dt_widget_store_bool(const char *key, gboolean value)
void dt_gui_freeze_end_(const char *file, int line)
void dt_widget_set_root_window_handler(dt_widget_root_window_handler_t handler)
void dt_widget_set_em_size(double em)
void dt_widget_set_gui_thread(pthread_t thread)
GdkModifierType dt_key_modifier_state(void)
void dt_widget_set_dpi(double dpi)
void dt_widget_set_source_rgb(cairo_t *cr, dt_gui_color_t color)
gboolean dt_widget_debug_overlays(void)
void dt_widget_set_notebook_page_handler(dt_widget_notebook_page_handler_t handler)
void dt_widget_set_overlay_color(double red, double green, double blue, double contrast)
void(* dt_widget_stored_int_setter_t)(const char *key, int value)
gint dt_widget_min_panel_width(void)
void dt_widget_set_refocus_handler(dt_widget_refocus_handler_t handler)
const GdkRGBA * dt_widget_colorlabel(int index)
void dt_widget_set_scroll_focus(GtkWidget *widget)
double dt_get_system_gui_ppd(GtkWidget *widget)
float dt_widget_mouse_radius_clamped(void)
void dt_widget_set_storage_handlers(dt_widget_stored_int_getter_t get_int, dt_widget_stored_int_setter_t set_int, dt_widget_stored_bool_getter_t get_bool, dt_widget_stored_bool_setter_t set_bool)
void(* dt_widget_cursor_handler_t)(GdkCursorType cursor)
gboolean dt_widget_stored_bool(const char *key)
void dt_widget_set_min_panel_width(gint width)
void dt_widget_set_message_handler(dt_widget_message_handler_t handler)
@ DT_WIDGET_COLORLABEL_RED
@ DT_WIDGET_COLORLABEL_PURPLE
@ DT_WIDGET_COLORLABEL_COUNT
@ DT_WIDGET_COLORLABEL_YELLOW
@ DT_WIDGET_COLORLABEL_GREEN
@ DT_WIDGET_COLORLABEL_BLUE
double dt_widget_dpi_factor(void)
void dt_widget_store_int(const char *key, int value)
G_BEGIN_DECLS GdkEventMask dt_widget_scroll_mask(void)
void dt_widget_refocus(void)
void(* dt_widget_notebook_page_handler_t)(gpointer owner)
gboolean dt_gui_widgets_suppressed(void)
cairo_filter_t dt_widget_image_filter(void)
#define dt_widget_log(...)
void dt_widget_notebook_page_changed(gpointer owner)
void dt_widget_set_mouse_radius(float radius, float clamped)
static gboolean dt_modifier_is(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
GtkWidget *(* dt_widget_root_window_handler_t)(void)
const dt_widget_overlay_color_t * dt_widget_overlay_color(void)
void dt_widget_set_scroll_mask(GdkEventMask mask)
gint dt_widget_natural_width(GtkWidget *widget)
double dt_widget_dpi(void)
double dt_widget_em_size(void)
void dt_widget_message(const char *message)
void(* dt_widget_stored_bool_setter_t)(const char *key, gboolean value)
void(* dt_widget_message_handler_t)(const char *message)
void dt_widget_set_cursor(GdkCursorType cursor)
void dt_gui_freeze_begin_(const char *file, int line)
gboolean dt_widget_theme_loaded(void)