From ae9ab3daa63f86c5caf23f273b6e7475f98acfc3 Mon Sep 17 00:00:00 2001 From: Daniele Pighin Date: Sat, 17 Oct 2026 18:27:54 +0200 Subject: [PATCH] masks: stop the canvas shifting when module focus changes With the mask panel on the canvas, its 1px edge strips were shown and hidden with module focus. They are columns beside the canvas, so every focus change resized the canvas and moved the image by a pixel. The strips now keep their room while the darkroom uses the canvas position, whatever is focused. With no panel to show they paint the canvas margin color and ignore input. --- src/gui/gtk.c | 39 ++++++++++++++++++++++++++++++++++++--- src/gui/gtk.h | 4 ++++ src/views/darkroom.c | 5 +++++ 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/gui/gtk.c b/src/gui/gtk.c index 110621ef3b1..f1a3044b5b9 100644 --- a/src/gui/gtk.c +++ b/src/gui/gtk.c @@ -34,6 +34,7 @@ #include "gui/splash.h" #include "bauhaus/bauhaus.h" #include "develop/blend.h" +#include "develop/blend_gui_internal.h" #include "develop/develop.h" #include "develop/imageop.h" // dt_masks_get_edit_mode: the flexi panel's edge halo stands down while @@ -168,7 +169,9 @@ typedef struct dt_ui_t gboolean flexi_mask_active; // TRUE when the hosted module's mask is actually in use gchar *flexi_mask_label; // human-readable current mask type, for the hint gboolean flexi_panel_right; // current side (FALSE = left, TRUE = right) - GtkWidget *flexi_centerrow; // the panel's home when docked as a real column + gboolean flexi_has_content; // the edge strips are live (there is a panel to reach) + gboolean flexi_in_view; // the darkroom is showing, so the strips may take room + GtkWidget *flexi_centerrow; // the panel's home when docked as a real column } dt_ui_t; /* initialize the whole left panel */ @@ -4248,6 +4251,15 @@ static double _flexi_halo_arrow_size(GtkWidget *halo) static gboolean _flexi_sliver_draw(GtkWidget *w, cairo_t *cr, gpointer user_data) { + // kept only for its room (see _flexi_slivers_update): the canvas' own margin + // color, as darkroom.c paints it, so it reads as part of the canvas + if(!darktable.gui->ui->flexi_has_content) + { + dt_gui_gtk_set_source_rgb(cr, DT_GUI_COLOR_DARKROOM_BG); + cairo_paint(cr); + return FALSE; + } + GtkStyleContext *ctx = gtk_widget_get_style_context(w); const int width = gtk_widget_get_allocated_width(w); const int height = gtk_widget_get_allocated_height(w); @@ -4381,6 +4393,7 @@ static void _flexi_sliver_enter(GtkEventControllerMotion *controller, GtkWidget *w) { dt_ui_t *ui = darktable.gui->ui; + if(!ui->flexi_has_content) return; _flexi_sliver_hint(ui, _flexi_sliver_is_right(ui, w)); } @@ -4396,8 +4409,9 @@ static void _flexi_sliver_pressed(GtkGestureSingle *gesture, GtkWidget *w) { if(gtk_gesture_single_get_current_button(gesture) != GDK_BUTTON_PRIMARY) return; - dt_gui_claim(gesture); dt_ui_t *ui = darktable.gui->ui; + if(!ui->flexi_has_content) return; + dt_gui_claim(gesture); const gboolean right = _flexi_sliver_is_right(ui, w); // the strip stays put once the panel is docked, so it needs something to do @@ -4739,9 +4753,21 @@ static void _flexi_slivers_update(dt_ui_t *ui, const gboolean show) { _flexi_halo_sync_width(ui); + ui->flexi_has_content = show; + // the strips are columns of centerrow, so showing or hiding them re-lays out + // the canvas and moves the image by a pixel. Their room is kept for as long + // as the canvas position is in use, whatever is focused; with no panel to + // reach they pass for canvas (_flexi_sliver_draw) and take no input + const gboolean reserve = + show || (ui->flexi_in_view && dt_masks_gui_panel_position() == MASKS_PANEL_POS_CANVAS); + for(int i = 0; i < 2; i++) { - if(ui->flexi_sliver[i]) gtk_widget_set_visible(ui->flexi_sliver[i], show); + if(ui->flexi_sliver[i]) + { + gtk_widget_set_visible(ui->flexi_sliver[i], reserve); + gtk_widget_queue_draw(ui->flexi_sliver[i]); + } // whether a halo is up is the proximity poll's business, but with no panel // to reach for there must be none, and no poll looking for one either if(!show && ui->flexi_halo[i]) gtk_widget_set_visible(ui->flexi_halo[i], FALSE); @@ -4908,6 +4934,13 @@ void dt_ui_flexi_panel_set_icon(dt_ui_t *ui, const gboolean active, const char * if(ui->flexi_halo[i]) gtk_widget_queue_draw(ui->flexi_halo[i]); } +void dt_ui_flexi_panel_set_in_view(dt_ui_t *ui, const gboolean in_view) +{ + if(!ui) return; + ui->flexi_in_view = in_view; + _flexi_slivers_update(ui, in_view && ui->flexi_has_content); +} + gboolean dt_ui_flexi_panel_is_collapsed(dt_ui_t *ui) { if(!ui->flexi_panel_overlay) return TRUE; diff --git a/src/gui/gtk.h b/src/gui/gtk.h index 065a6836e9d..2b22f26b487 100644 --- a/src/gui/gtk.h +++ b/src/gui/gtk.h @@ -485,6 +485,10 @@ void dt_ui_flexi_panel_set_collapsed(struct dt_ui_t *ui, const gboolean collapsed, const gboolean has_content, const gboolean persist); +/** \brief whether the darkroom is the current view. While it is, and the + canvas position is chosen, the edge strips keep their room in the center + row even with nothing to show, so that focus changes do not move the image */ +void dt_ui_flexi_panel_set_in_view(struct dt_ui_t *ui, const gboolean in_view); gboolean dt_ui_flexi_panel_is_collapsed(struct dt_ui_t *ui); /** \brief which edge the panel is on now, which can differ from the stored preference: anything mirroring the panel's own chrome follows this */ diff --git a/src/views/darkroom.c b/src/views/darkroom.c index 008a1250f07..1d3d67bcfdf 100644 --- a/src/views/darkroom.c +++ b/src/views/darkroom.c @@ -4134,6 +4134,10 @@ void enter(dt_view_t *self) // clean the undo list dt_undo_clear(darktable.undo, DT_UNDO_DEVELOP); + // before any module takes focus, so the canvas is laid out with the edge + // strips' room from the start + dt_ui_flexi_panel_set_in_view(darktable.gui->ui, TRUE); + /* connect to ui pipe finished signal for redraw */ DT_CONTROL_SIGNAL_HANDLE(DT_SIGNAL_DEVELOP_IMAGE_CHANGED, _darkroom_ui_image_changed_callback); @@ -4333,6 +4337,7 @@ void leave(dt_view_t *self) // not per-view lib content -- hide it and its edge strips explicitly, since // nothing else does this when leaving the view dt_ui_flexi_panel_set_collapsed(darktable.gui->ui, TRUE, FALSE, FALSE); + dt_ui_flexi_panel_set_in_view(darktable.gui->ui, FALSE); dt_iop_color_picker_cleanup(); if(darktable.lib->proxy.colorpicker.picker_proxy)