Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
filmstrip.c
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*/
25#include "common/image.h"
26#include "common/logging.h"
28#include "control/signal.h"
31#include "gui/dtgtk/thumbnail.h"
32#include "gui/application.h"
33#include "views/view.h"
34
35
36// --- Content widget ---------------------------------------------------------------------------
37
39{
40 // GtkLayout implements GtkScrollable, so the GtkScrolledWindow drives it directly with NO
41 // implicit GtkViewport. That viewport was the root of issue #877: at the default ~120px strip
42 // height it collapsed the content to the viewport width, disabling horizontal scrolling so
43 // thumbnails positioned past the first screen never drew (they only appeared on hover, when a
44 // pointer event forced a repaint). With a GtkLayout the content extent is declared explicitly
45 // via gtk_layout_set_size(), independent of any child-size negotiation, so it never collapses.
46 return gtk_layout_new(NULL, NULL);
47}
48
49
50// --- Geometry ---------------------------------------------------------------------------------
51
52static void _filmstrip_configure_dims(dt_thumbtable_t *table, int *new_width, int *new_height,
53 int *per_row, int *thumb_width, int *thumb_height)
54{
55 gint sb_spacing = 0;
56 gtk_widget_style_get(table->scroll_window, "scrollbar-spacing", &sb_spacing, NULL);
57
58 // Don't use GtkAdjustment page sizes here: in filmstrip, the scrolled window height can
59 // be influenced by its (resized) children during initial layout, which may cause a
60 // feedback loop where thumbnails keep growing.
61 int width = gtk_widget_get_allocated_width(table->parent_overlay);
62 int height = gtk_widget_get_allocated_height(table->parent_overlay);
63 GtkWidget *h_scroll = gtk_scrolled_window_get_hscrollbar(GTK_SCROLLED_WINDOW(table->scroll_window));
64 int h_scroll_h = 0;
65 if(h_scroll)
66 {
67 h_scroll_h = gtk_widget_get_allocated_height(h_scroll);
68 if(h_scroll_h > 0) h_scroll_h += sb_spacing;
69 }
70
71 // Clamp to the explicit panel size request when present to enforce "container drives child size".
72 int req_w = -1, req_h = -1;
73 gtk_widget_get_size_request(table->parent_overlay, &req_w, &req_h);
74 if(req_h > 0)
75 height = MIN(height, req_h);
76
77 height -= h_scroll_h;
78
79 const int deco = dt_thumbtable_thumb_cell_decoration();
80
81 *new_width = width;
82 *new_height = height;
83 *per_row = 1;
84 *thumb_height = height - deco;
85 *thumb_width = *thumb_height;
86}
87
88static void _filmstrip_rowid_to_position(dt_thumbtable_t *table, int rowid, int *x, int *y)
89{
90 *x = rowid * table->thumb_width;
91 *y = 0;
92}
93
94static int _filmstrip_position_to_rowid(dt_thumbtable_t *table, const double x, const double y)
95{
96 return x + (table->view_width / 2.) / table->thumb_width;
97}
98
99static void _filmstrip_get_row_ids(dt_thumbtable_t *table, int *rowid_min, int *rowid_max)
100{
101 float page_size = gtk_adjustment_get_page_size(table->h_scrollbar);
102 float position = gtk_adjustment_get_value(table->h_scrollbar);
103
104 // Preload the previous and next pages too because thumbnails are typically small
105 int row_min = (position - page_size) / table->thumb_width;
106 int row_max = (position + 2.f * page_size) / table->thumb_width;
107
108 *rowid_min = row_min * table->thumbs_per_row;
109 *rowid_max = row_max * table->thumbs_per_row;
110}
111
112static gboolean _filmstrip_is_rowid_visible(dt_thumbtable_t *table, int rowid)
113{
114 int page_size = gtk_adjustment_get_page_size(table->h_scrollbar);
115 int position = gtk_adjustment_get_value(table->h_scrollbar);
116 int page_right = page_size + position;
117
118 int img_left = rowid * table->thumb_height;
119 int img_right = img_left + table->thumb_width;
120 return img_left >= position && img_right <= page_right;
121}
122
124{
125 // GtkLayout carries its own scrollable extent (which drives the scrollbar adjustments) instead
126 // of a widget size request: width spans the whole collection, height matches the viewport so
127 // there is never a vertical scroll.
128 guint current_w = 0, current_h = 0;
129 gtk_layout_get_size(GTK_LAYOUT(table->grid), &current_w, &current_h);
130
131 const guint width = (guint)MAX(table->collection_count * table->thumb_width, 0);
132 const guint height = (guint)MAX(table->view_height, 0);
133 if(current_w != width || current_h != height)
134 {
135 gtk_layout_set_size(GTK_LAYOUT(table->grid), width, height);
136 dt_print(DT_DEBUG_LIGHTTABLE, "Configuring grid size main dimension: %u\n", width);
137 }
138}
139
140
141// --- Group borders ----------------------------------------------------------------------------
142
144{
145 const int32_t rowid = thumb->rowid;
146 const int32_t groupid = thumb->info.group_id;
147
149
150 if(table->lut[CLAMP_ROW(rowid - 1)].groupid != groupid
151 || IS_COLLECTION_EDGE(rowid - 1))
152 *borders |= DT_THUMBNAIL_BORDER_LEFT;
153
154 if(table->lut[CLAMP_ROW(rowid + 1)].groupid != groupid
155 || IS_COLLECTION_EDGE(rowid + 1))
156 *borders |= DT_THUMBNAIL_BORDER_RIGHT;
157}
158
159
160// --- Child placement --------------------------------------------------------------------------
161
163{
164 gtk_layout_put(GTK_LAYOUT(table->grid), thumb->widget, thumb->x, thumb->y);
165}
166
168{
169 gtk_layout_move(GTK_LAYOUT(table->grid), thumb->widget, thumb->x, thumb->y);
170}
171
172
173// --- Scrollbars -------------------------------------------------------------------------------
174
175static gboolean _filmstrip_wants_scroll_value(dt_thumbtable_t *table, GtkAdjustment *adjustment)
176{
177 return adjustment == table->h_scrollbar;
178}
179
180static gboolean _filmstrip_wants_page_size_notify(dt_thumbtable_t *table, GObject *object)
181{
182 // Page size is only used to size the filemanager/grid. Filmstrip uses its parent allocation.
183 return FALSE;
184}
185
186static gboolean _filmstrip_relevant_scrollbar_changed(dt_thumbtable_t *table, GtkWidget *widget, GtkAllocation *allocation)
187{
188 // Filmstrip height depends on the horizontal scrollbar height. When it gets realized/allocated,
189 // we need to recompute thumbnail sizes even if the parent size didn't change.
190 GtkWidget *h_scroll = gtk_scrolled_window_get_hscrollbar(GTK_SCROLLED_WINDOW(table->scroll_window));
191 if(widget != h_scroll) return FALSE;
192 if(allocation->height == table->last_h_scrollbar_height) return FALSE;
193 table->last_h_scrollbar_height = allocation->height;
194 return TRUE;
195}
196
197
198// --- Per-thumbnail state ----------------------------------------------------------------------
199
200// The filmstrip highlights the active/developed image(s), NOT the lighttable selection (which the
201// darkroom clears on image change) - see issue #954.
202static gboolean _filmstrip_is_thumb_highlighted(dt_thumbtable_t *table, int32_t imgid)
203{
205}
206
212
213static void _filmstrip_on_drag_begin(dt_thumbtable_t *table, int32_t imgid)
214{
215 if(imgid > UNKNOWN_IMAGE)
216 {
217 /* Views that need drags to commit the hovered image must do it before
218 * dt_act_on_get_images() snapshots the payload. */
220 }
221}
222
223
224// --- Parent -----------------------------------------------------------------------------------
225
227{
228 // The filmstrip is a static sibling of the heavily, continuously repainted darkroom center.
229 // As an overlay child its own offscreen GdkWindow goes stale/blank on Wayland until a pointer
230 // event invalidates it (thumbnails only appear on hover, issue #877). Make scroll_window the
231 // overlay's MAIN child so it draws into the overlay's own window and stays painted.
232 //
233 // The main child drives the overlay's size request, which would pin the panel height to the
234 // grid and break the resize-handle shrink + _parent_overlay_size_allocate reconfigure flow
235 // (the regression seen when the grid couldn't be downsized). Counter that with the vertical
236 // EXTERNAL policy + min_content_height(1) + propagate_natural_height(FALSE) recipe so the
237 // panel can still be freely shrunk; filmstrip thumb height is derived from the panel's
238 // allocation (see dt_thumbtable_configure), not from the scrolled window's own height request.
239 gtk_container_add(GTK_CONTAINER(table->parent_overlay), table->scroll_window);
240 gtk_widget_set_name(table->grid, "thumbtable-filmstrip");
241 dt_gui_add_help_link(table->grid, dt_get_help_url("filmstrip"));
242 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(table->scroll_window), GTK_POLICY_ALWAYS, GTK_POLICY_EXTERNAL);
243 gtk_scrolled_window_set_min_content_height(GTK_SCROLLED_WINDOW(table->scroll_window), 1);
244 gtk_scrolled_window_set_propagate_natural_height(GTK_SCROLLED_WINDOW(table->scroll_window), FALSE);
245}
246
247
248// --- Vtable -----------------------------------------------------------------------------------
249
251{
252 static const dt_thumbtable_layout_ops_t ops = {
254 .configure_dims = _filmstrip_configure_dims,
255 .rowid_to_position = _filmstrip_rowid_to_position,
256 .position_to_rowid = _filmstrip_position_to_rowid,
257 .get_row_ids = _filmstrip_get_row_ids,
258 .is_rowid_visible = _filmstrip_is_rowid_visible,
259 .update_content_size = _filmstrip_update_content_size,
260 .group_borders = _filmstrip_group_borders,
261 .place_child = _filmstrip_place_child,
262 .move_child = _filmstrip_move_child,
263 .wants_scroll_value = _filmstrip_wants_scroll_value,
264 .wants_page_size_notify = _filmstrip_wants_page_size_notify,
265 .relevant_scrollbar_changed = _filmstrip_relevant_scrollbar_changed,
266 .is_thumb_highlighted = _filmstrip_is_thumb_highlighted,
267 .on_thumbnail_added = _filmstrip_on_thumbnail_added,
268 .on_drag_begin = _filmstrip_on_drag_begin,
269 .setup_parent = _filmstrip_setup_parent,
270 .grab_focus = NULL, // filmstrip never grabs keyboard focus for its content widget
271 .handle_key = NULL, // filmstrip has no grid-only navigation/selection keys
272 .pre_activate = NULL, // filmstrip activates without pre-selecting
273 };
274 return &ops;
275}
276
277// clang-format off
278// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
279// vim: shiftwidth=2 expandtab tabstop=2 cindent
280// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
281// clang-format on
void dt_gui_add_help_link(GtkWidget *widget, char *link)
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
int position()
static const float x
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
static void _filmstrip_group_borders(dt_thumbtable_t *table, dt_thumbnail_t *thumb, dt_thumbnail_border_t *borders)
Definition filmstrip.c:143
static GtkWidget * _filmstrip_create_content_widget(void)
Definition filmstrip.c:38
static void _filmstrip_move_child(dt_thumbtable_t *table, dt_thumbnail_t *thumb)
Definition filmstrip.c:167
const dt_thumbtable_layout_ops_t * dt_thumbtable_filmstrip_ops(void)
Definition filmstrip.c:250
static void _filmstrip_setup_parent(dt_thumbtable_t *table)
Definition filmstrip.c:226
static gboolean _filmstrip_wants_scroll_value(dt_thumbtable_t *table, GtkAdjustment *adjustment)
Definition filmstrip.c:175
static void _filmstrip_configure_dims(dt_thumbtable_t *table, int *new_width, int *new_height, int *per_row, int *thumb_width, int *thumb_height)
Definition filmstrip.c:52
static int _filmstrip_position_to_rowid(dt_thumbtable_t *table, const double x, const double y)
Definition filmstrip.c:94
static void _filmstrip_update_content_size(dt_thumbtable_t *table)
Definition filmstrip.c:123
static gboolean _filmstrip_relevant_scrollbar_changed(dt_thumbtable_t *table, GtkWidget *widget, GtkAllocation *allocation)
Definition filmstrip.c:186
static gboolean _filmstrip_wants_page_size_notify(dt_thumbtable_t *table, GObject *object)
Definition filmstrip.c:180
static void _filmstrip_get_row_ids(dt_thumbtable_t *table, int *rowid_min, int *rowid_max)
Definition filmstrip.c:99
static void _filmstrip_on_thumbnail_added(dt_thumbtable_t *table, dt_thumbnail_t *thumb)
Definition filmstrip.c:207
static void _filmstrip_place_child(dt_thumbtable_t *table, dt_thumbnail_t *thumb)
Definition filmstrip.c:162
static gboolean _filmstrip_is_thumb_highlighted(dt_thumbtable_t *table, int32_t imgid)
Definition filmstrip.c:202
static void _filmstrip_on_drag_begin(dt_thumbtable_t *table, int32_t imgid)
Definition filmstrip.c:213
static void _filmstrip_rowid_to_position(dt_thumbtable_t *table, int rowid, int *x, int *y)
Definition filmstrip.c:88
static gboolean _filmstrip_is_rowid_visible(dt_thumbtable_t *table, int rowid)
Definition filmstrip.c:112
#define UNKNOWN_IMAGE
Definition image.h:78
@ DT_DEBUG_LIGHTTABLE
Definition logging.h:60
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:386
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:616
@ DT_SIGNAL_VIEWMANAGER_FILMSTRIP_DRAG_BEGIN
This signal is raised when a drag starts from the filmstrip. Views that need filmstrip drags to commi...
Definition signal.h:114
int32_t group_id
Definition image.h:401
int32_t id
Definition image.h:401
gboolean disable_actions
Definition thumbnail.h:105
dt_image_t info
Definition thumbnail.h:77
int32_t rowid
Definition thumbnail.h:69
GtkWidget * widget
Definition thumbnail.h:80
Per-mode layout strategy. One instance per frontend, shared (const) by all tables of that mode and st...
GtkWidget *(* create_content_widget)(void)
GtkWidget * grid
Definition thumbtable.h:110
int last_h_scrollbar_height
Definition thumbtable.h:202
GtkWidget * scroll_window
Definition thumbtable.h:154
dt_thumbtable_cache_t * lut
Definition thumbtable.h:152
GtkAdjustment * h_scrollbar
Definition thumbtable.h:158
GtkWidget * parent_overlay
Definition thumbtable.h:163
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
void dt_thumbnail_update_selection(dt_thumbnail_t *thumb, gboolean selected)
Definition thumbnail.c:923
dt_thumbnail_border_t
Definition thumbnail.h:50
@ DT_THUMBNAIL_BORDER_BOTTOM
Definition thumbnail.h:55
@ DT_THUMBNAIL_BORDER_TOP
Definition thumbnail.h:53
@ DT_THUMBNAIL_BORDER_RIGHT
Definition thumbnail.h:54
@ DT_THUMBNAIL_BORDER_LEFT
Definition thumbnail.h:52
int dt_thumbtable_thumb_cell_decoration(void)
Definition thumbtable.c:535
#define CLAMP_ROW(rowid)
Definition thumbtable.c:635
#define IS_COLLECTION_EDGE(rowid)
Definition thumbtable.c:636
A widget to manage and display image thumbnails in Ansel's lighttable and filmstrip views.
Private interface shared between the thumbtable engine (thumbtable.c) and its two frontends (filemana...
char * dt_get_help_url(char *name)
gboolean dt_view_active_images_has_imgid(int32_t imgid)
Definition view.c:1297