From f089ed69d7c6ac7364b14afdbaa5eabdcd31a45b Mon Sep 17 00:00:00 2001 From: Gabriele Barbero Date: Mon, 7 Jul 2025 11:33:05 +0200 Subject: [PATCH] tools: prevent on-canvas text editor from being dragged outside the image bounds This commit prevents the on-canvas text editor from being dragged outside the image bounds by checking the overlay's position and adjusting the coordinates to keep it within the canvas. Additionally, it checks whether the overlay is entirely outside the canvas and, if so, pushes it back inside. --- app/display/gimpdisplayshell.c | 189 +++++++++++++++++++++++++++++++- app/display/gimpdisplayshell.h | 10 ++ app/text/gimptextlayer.c | 12 ++ app/text/gimptextlayer.h | 2 + app/tools/gimptexttool-editor.c | 29 +++++ 5 files changed, 238 insertions(+), 4 deletions(-) diff --git a/app/display/gimpdisplayshell.c b/app/display/gimpdisplayshell.c index ec22d8672e..5e3476c298 100644 --- a/app/display/gimpdisplayshell.c +++ b/app/display/gimpdisplayshell.c @@ -195,6 +195,11 @@ static void gimp_display_shell_transform_overlay (GimpDisplayShell *shell, static gboolean gimp_display_shell_draw (GimpDisplayShell *shell, cairo_t *cr, gpointer *data); +static void gimp_display_shell_push_overlay_inside_canvas + (GimpDisplayShell *shell, + GtkWidget *child, + gdouble *limits, + gdouble *corners); G_DEFINE_TYPE_WITH_CODE (GimpDisplayShell, gimp_display_shell, @@ -1276,12 +1281,39 @@ gimp_display_shell_overlay_allocate (GtkWidget *child, GtkAllocation *allocation, GimpDisplayShellOverlay *overlay) { - gdouble x, y; + gdouble tlx, tly, brx, bry; + gdouble llimit, rlimit, ulimit, blimit; - gimp_display_shell_transform_overlay (overlay->shell, child, &x, &y); + gimp_ruler_get_range ((GimpRuler *) overlay->shell->hrule, + &llimit, &rlimit, NULL); + gimp_ruler_get_range ((GimpRuler *) overlay->shell->vrule, + &ulimit, &blimit, NULL); - gimp_overlay_box_set_child_position (GIMP_OVERLAY_BOX (overlay->shell->canvas), - child, x, y); + gimp_display_shell_get_overlay_corners (overlay->shell, + child, + overlay->image_x, overlay->image_y, + &tlx, &tly, + &brx, &bry); + + /* If the overlay is entirely outside the canvas, we push it inside the canvas */ + if (tlx > rlimit || + tly > blimit || + brx < llimit || + bry < ulimit) + { + gimp_display_shell_push_overlay_inside_canvas (overlay->shell, + child, + (gdouble[]) {ulimit, rlimit, blimit, llimit}, + (gdouble[]) {tlx, tly, brx, bry}); + } + else + { + gdouble x, y; + + gimp_display_shell_transform_overlay (overlay->shell, child, &x, &y); + gimp_overlay_box_set_child_position (GIMP_OVERLAY_BOX (overlay->shell->canvas), + child, x, y); + } } static void @@ -1386,6 +1418,62 @@ gimp_display_shell_draw (GimpDisplayShell *shell, return FALSE; } +static void +gimp_display_shell_push_overlay_inside_canvas (GimpDisplayShell *shell, + GtkWidget *child, + gdouble *limits, + gdouble *corners) +{ + gdouble ulimit, rlimit, blimit, llimit; + gdouble tlx, tly, brx, bry; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + g_return_if_fail (GTK_IS_WIDGET (shell)); + + ulimit = limits[0]; + rlimit = limits[1]; + blimit = limits[2]; + llimit = limits[3]; + + tlx = corners[0]; + tly = corners[1]; + brx = corners[2]; + bry = corners[3]; + + if (brx < llimit) + { + gimp_display_shell_move_overlay (shell, + child, + llimit, tly, + GIMP_HANDLE_ANCHOR_NORTH_WEST, + 0, 0); + } + if (bry < ulimit) + { + gimp_display_shell_move_overlay (shell, + child, + tlx, ulimit, + GIMP_HANDLE_ANCHOR_NORTH_WEST, + 0, 0); + } + if (tlx > rlimit) + { + gimp_display_shell_move_overlay (shell, + child, + rlimit, bry, + GIMP_HANDLE_ANCHOR_SOUTH_EAST, + 0, 0); + } + if (tly > blimit) + { + gimp_display_shell_move_overlay (shell, + child, + brx, blimit, + GIMP_HANDLE_ANCHOR_SOUTH_EAST, + 0, 0); + } +} + /* public functions */ GtkWidget * @@ -2340,3 +2428,96 @@ gimp_display_shell_is_drawn (GimpDisplayShell *shell) { return shell->drawn; } + +/** + * gimp_display_shell_get_overlay_corners: + * @shell: a #GimpDisplayShell + * @child: a child widget of the shell + * @image_x: the x coordinate in image coordinates + * @image_y: the y coordinate in image coordinates + * @top_left_x: return location for the top left x coordinate in image coordinates + * @top_left_y: return location for the top left y coordinate in image coordinates + * @bottom_right_x: return location for the bottom right x coordinate in image coordinates + * @bottom_right_y: return location for the bottom right y coordinate in image coordinates + * + * This function calculates the corners of an overlay widget in image coordinates. + **/ +void +gimp_display_shell_get_overlay_corners (GimpDisplayShell *shell, + GtkWidget *child, + gdouble image_x, + gdouble image_y, + gdouble *top_left_x, + gdouble *top_left_y, + gdouble *bottom_right_x, + gdouble *bottom_right_y) +{ + GimpDisplayShellOverlay *overlay; + GtkRequisition req; + gdouble tl_disp_x, tl_disp_y; + gdouble br_disp_x, br_disp_y; + + g_return_if_fail (GIMP_IS_DISPLAY_SHELL (shell)); + + overlay = g_object_get_data (G_OBJECT (child), "image-coords-overlay"); + + gimp_display_shell_transform_xy_f (shell, + image_x, image_y, + &tl_disp_x, &tl_disp_y); + + gtk_widget_get_preferred_size (child, &req, NULL); + + switch (overlay->anchor) + { + case GIMP_HANDLE_ANCHOR_CENTER: + tl_disp_x -= req.width / 2; + tl_disp_y -= req.height / 2; + break; + case GIMP_HANDLE_ANCHOR_NORTH: + tl_disp_x -= req.width / 2; + tl_disp_y += overlay->spacing_y; + break; + case GIMP_HANDLE_ANCHOR_NORTH_WEST: + tl_disp_x += overlay->spacing_x; + tl_disp_y += overlay->spacing_y; + break; + case GIMP_HANDLE_ANCHOR_NORTH_EAST: + tl_disp_x -= req.width + overlay->spacing_x; + tl_disp_y += overlay->spacing_y; + break; + case GIMP_HANDLE_ANCHOR_SOUTH: + tl_disp_x -= req.width / 2; + tl_disp_y -= req.height + overlay->spacing_y; + break; + case GIMP_HANDLE_ANCHOR_SOUTH_WEST: + tl_disp_x += overlay->spacing_x; + tl_disp_y -= req.height + overlay->spacing_y; + break; + case GIMP_HANDLE_ANCHOR_SOUTH_EAST: + tl_disp_x -= req.width + overlay->spacing_x; + tl_disp_y -= req.height + overlay->spacing_y; + break; + case GIMP_HANDLE_ANCHOR_WEST: + tl_disp_x += overlay->spacing_x; + tl_disp_y -= req.height / 2; + break; + case GIMP_HANDLE_ANCHOR_EAST: + tl_disp_x -= req.width + overlay->spacing_x; + tl_disp_y -= req.height / 2; + break; + default: + tl_disp_x -= overlay->spacing_x; + tl_disp_y -= overlay->spacing_y; + break; + } + + br_disp_x = tl_disp_x + req.width; + br_disp_y = tl_disp_y + req.height; + + gimp_display_shell_untransform_xy_f (shell, + tl_disp_x, tl_disp_y, + top_left_x, top_left_y); + gimp_display_shell_untransform_xy_f (shell, + br_disp_x, br_disp_y, + bottom_right_x, bottom_right_y); +} diff --git a/app/display/gimpdisplayshell.h b/app/display/gimpdisplayshell.h index 4137049036..c5b345e485 100644 --- a/app/display/gimpdisplayshell.h +++ b/app/display/gimpdisplayshell.h @@ -363,3 +363,13 @@ void gimp_display_shell_set_mask (GimpDisplayShell *shell, gboolean inverted); gboolean gimp_display_shell_is_drawn (GimpDisplayShell *shell); + +void gimp_display_shell_get_overlay_corners + (GimpDisplayShell *shell, + GtkWidget *child, + gdouble image_x, + gdouble image_y, + gdouble *top_left_x, + gdouble *top_left_y, + gdouble *bottom_right_x, + gdouble *bottom_right_y); diff --git a/app/text/gimptextlayer.c b/app/text/gimptextlayer.c index 3c92994c76..9bc1caccf4 100644 --- a/app/text/gimptextlayer.c +++ b/app/text/gimptextlayer.c @@ -617,6 +617,18 @@ gimp_text_layer_get_style_overlay_position (GimpTextLayer *layer, return TRUE; } +gboolean +gimp_text_layer_is_style_overlay_positioned (GimpTextLayer *layer) +{ + GimpTextLayerPrivate *priv; + + g_return_val_if_fail (GIMP_IS_TEXT_LAYER (layer), FALSE); + + priv = layer->private; + + return priv->style_overlay_positioned; +} + void gimp_text_layer_set_style_overlay_offset (GimpTextLayer *layer, gdouble offset_x, diff --git a/app/text/gimptextlayer.h b/app/text/gimptextlayer.h index 2618f537fe..07b7bbe420 100644 --- a/app/text/gimptextlayer.h +++ b/app/text/gimptextlayer.h @@ -80,6 +80,8 @@ gboolean gimp_text_layer_get_style_overlay_position (GimpTextLayer *layer, gdouble *x, gdouble *y); +gboolean gimp_text_layer_is_style_overlay_positioned + (GimpTextLayer *layer); void gimp_text_layer_set_style_overlay_offset (GimpTextLayer *layer, gdouble offset_x, diff --git a/app/tools/gimptexttool-editor.c b/app/tools/gimptexttool-editor.c index 6a2c3595ff..a7519ab2cb 100644 --- a/app/tools/gimptexttool-editor.c +++ b/app/tools/gimptexttool-editor.c @@ -2076,6 +2076,8 @@ gimp_text_tool_style_overlay_button_motion (GtkWidget *widget, GimpTool *tool = GIMP_TOOL (text_tool); GimpDisplayShell *shell = gimp_display_get_shell (tool->display); GimpTextStyleEditor *style_editor; + gdouble llimit, rlimit, ulimit, blimit; + gdouble tlx, tly, brx, bry; gdouble x, y; gdouble x_off, y_off; @@ -2089,6 +2091,33 @@ gimp_text_tool_style_overlay_button_motion (GtkWidget *widget, x, y, &x, &y); + gimp_ruler_get_range ((GimpRuler *) shell->hrule, + &llimit, &rlimit, NULL); + gimp_ruler_get_range ((GimpRuler *) shell->vrule, + &ulimit, &blimit, NULL); + + gimp_display_shell_get_overlay_corners (shell, text_tool->style_overlay, + x, y, + &tlx, &tly, + &brx, &bry); + + /* If the overlay is being dragged, we need to check if it is still + * within the image bounds. If it is not, we adjust the coordinates. + * This is to prevent the overlay from being dragged outside the image + * bounds, which would cause it to be lost. + */ + if (gimp_text_layer_is_style_overlay_positioned (text_tool->layer)) + { + if (tlx < llimit) + x += (llimit - tlx); + if (tly < ulimit) + y += (ulimit - tly); + if (brx > rlimit) + x -= (brx - rlimit); + if (bry > blimit) + y -= (bry - blimit); + } + gimp_display_shell_move_overlay (shell, text_tool->style_overlay, x, y, -1,