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