Commit: patch 9.2.0853: popup: popup images do not support scaling

Christian Brabandt <[email protected]>
Newsgroups gmane.editors.vim.devel
Message-ID <[email protected]>
patch 9.2.0853: popup: popup images do not support scaling

Commit: https://github.com/vim/vim/commit/2e2747ad5fa3b151884112be51264c20fa19abd7
Author: Foxe Chen <[email protected]>
Date:   Fri Jul 24 21:56:37 2026 +0000

    patch 9.2.0853: popup: popup images do not support scaling
    
    Problem:  popup: popup images do not support scaling
    Solution: Add popup scaling support for GTK4 UI
              (Foxe Chen).
    
    closes: #20611
    
    Signed-off-by: Foxe Chen <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

diff --git a/src/cairo.c b/src/cairo.c
index 7543a33d9..7f1df2b2d 100644
--- a/src/cairo.c
+++ b/src/cairo.c
@@ -122,6 +122,7 @@ cairo_popup_image_ensure(win_T *wp)
 	cairo_surface_destroy(surf);
 	return false;
     }
+    cairo_surface_set_device_scale(surf, gui.scale, gui.scale);
     cairo_popup_image_fill_surface(wp, surf);
     wp->w_popup_image_surface = surf;
     return true;
@@ -163,7 +164,8 @@ cairo_popup_image_free(win_T *wp)
  * non-zero offsets to crop the portion that falls outside the host window.
  * Builds the cache on first call.  Caller is responsible for scheduling a
  * redraw of the target's owning widget if needed (e.g.
- * gtk_widget_queue_draw_area on GTK).
+ * gtk_widget_queue_draw_area on GTK). "x" and "y" are assumed to be in logical
+ * pixels, the rest are in physical pixels.
  */
     void
 cairo_popup_image_paint(
@@ -171,10 +173,10 @@ cairo_popup_image_paint(
 	void	*target,
 	int	 x,
 	int	 y,
-	int	 src_x,
-	int	 src_y,
-	int	 draw_w,
-	int	 draw_h)
+	double	 src_x,
+	double	 src_y,
+	double	 draw_w,
+	double	 draw_h)
 {
     cairo_t *cr;
 
@@ -184,6 +186,10 @@ cairo_popup_image_paint(
 	return;
     cairo_surface_t *surface = (cairo_surface_t *)wp->w_popup_image_surface;
 
+    src_x = PHY2LOG(src_x);
+    src_y = PHY2LOG(src_y);
+    draw_w = PHY2LOG(draw_w);
+    draw_h = PHY2LOG(draw_h);
 
 # if !GTK_CHECK_VERSION(3,0,0)
     cr = gdk_cairo_create((GdkDrawable *)target);
diff --git a/src/gui.c b/src/gui.c
index f3ba3705a..0c7b39be0 100644
--- a/src/gui.c
+++ b/src/gui.c
@@ -680,6 +680,9 @@ gui_init(void)
      * Create the GUI shell.
      */
     gui.in_use = true;		// Must be set after menus have been set up
+#if defined(FEAT_GUI_GTK) && defined(FEAT_IMAGE)
+    gui.scale = 1.0; // Default value
+#endif
     if (gui_mch_init() == FAIL)
 	goto error;
 
diff --git a/src/gui.h b/src/gui.h
index 1fadca0f0..16f13e477 100644
--- a/src/gui.h
+++ b/src/gui.h
@@ -103,6 +103,15 @@ typedef GdkEvent GdkEventKey;	// GTK4: GdkEventKey merged into GdkEvent
 # define FILL_Y(row)	((row) * gui.char_height + gui.border_offset)
 # define Y_2_ROW(y)	(((y) - gui.border_offset) / gui.char_height)
 #endif
+#if defined(FEAT_GUI_GTK) && defined(FEAT_IMAGE)
+// Logical pixels to physical pixels
+# define LOG2PHY(l) (gui.in_use ? (double)(l) * gui.scale : (l))
+ // Physical pixels to logical pixels
+# define PHY2LOG(p) (gui.in_use ? (double)(p) / gui.scale : (p))
+#else
+# define LOG2PHY(l) (l)
+# define PHY2LOG(p) (p)
+#endif
 
 // Indices for arrays of scrollbars
 #define SBAR_NONE	    (-1)
@@ -423,6 +432,9 @@ typedef struct Gui
 # ifdef GDK_WINDOWING_WAYLAND
     bool	is_wayland;	    // active gdk backend in gtk is wayland
 # endif
+# ifdef FEAT_IMAGE
+    double	scale;		    // Current scaling (may be fractional)
+# endif
 #endif	// FEAT_GUI_GTK
 
 #if defined(FEAT_GUI_GTK) || defined(FEAT_GUI_MSWIN)
diff --git a/src/gui_gtk4.c b/src/gui_gtk4.c
index b29b180d6..486ed38a0 100644
--- a/src/gui_gtk4.c
+++ b/src/gui_gtk4.c
@@ -300,9 +300,9 @@ static void mainwin_fullscreened_cb(GObject *obj, GParamSpec *pspec, gpointer us
 static void drawarea_realize_cb(GtkWidget *widget, gpointer data);
 static void drawarea_unrealize_cb(GtkWidget *widget, gpointer data);
 #ifndef USE_GTK4_SNAPSHOT
-static void drawarea_scale_factor_cb(GObject *object, GParamSpec *pspec, gpointer data);
 static cairo_surface_t *create_backing_surface(int width, int height);
 #endif
+static void scale_factor_cb(GdkSurface *surface, GParamSpec *pspec, void *udata);
 static void clipboard_changed_cb(GdkClipboard *clipboard, gpointer user_data);
 #ifdef FEAT_MENU
 static void show_menubar_popover(void);
@@ -569,9 +569,6 @@ gui_mch_init(void)
     // Set up drawing.
     gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(gui.drawarea),
 	    (GtkDrawingAreaDrawFunc)draw_event, NULL, NULL);
-
-    g_signal_connect(G_OBJECT(gui.drawarea), "notify::scale-factor",
-		     G_CALLBACK(drawarea_scale_factor_cb), NULL);
 #endif
     g_signal_connect(G_OBJECT(gui.drawarea), "realize",
 		     G_CALLBACK(drawarea_realize_cb), NULL);
@@ -2343,6 +2340,17 @@ drawarea_realize_cb(GtkWidget *widget UNUSED, gpointer data UNUSED)
 	cairo_surface_destroy(gui.surface);
     gui.surface = create_backing_surface(w, h);
 #endif
+    {
+	// Use GdkSurface, as that handles fractional scale values.
+	GdkSurface *surface = gtk_native_get_surface(
+		gtk_widget_get_native(gui.drawarea));
+	double old = gui.scale;
+
+	gui.scale = gdk_surface_get_scale(surface);
+	popup_update_scale(old);
+	g_signal_connect(G_OBJECT(surface), "notify::scale",
+		G_CALLBACK(scale_factor_cb), NULL);
+    }
 
     gui_mch_new_colors();
 }
@@ -2408,16 +2416,16 @@ gui_gtk4_resize(int width, int height)
 	}
     }
 }
+#endif
 
     static void
-drawarea_scale_factor_cb(GObject *object UNUSED,
-	GParamSpec *pspec UNUSED, gpointer data UNUSED)
+scale_factor_cb(GdkSurface  *surface,
+	GParamSpec	    *pspec UNUSED,
+	void		    *udata UNUSED)
 {
+#ifndef USE_GTK4_SNAPSHOT
     int	w, h;
 
-    if (gui.drawarea == NULL)
-	return;
-
     w = gtk_widget_get_width(gui.drawarea);
     h = gtk_widget_get_height(gui.drawarea);
     if (w <= 0 || h <= 0)
@@ -2437,8 +2445,16 @@ drawarea_scale_factor_cb(GObject *object UNUSED,
     gtk_widget_queue_draw(gui.drawarea);
     if (gui.in_use)
 	redraw_all_later(UPD_CLEAR);
-}
 #endif
+#if defined(FEAT_IMAGE)
+    {
+	double old = gui.scale;
+
+	gui.scale = gdk_surface_get_scale(surface);
+	popup_update_scale(old);
+    }
+#endif
+}
 
 typedef enum
 {
diff --git a/src/gui_gtk4_da.c b/src/gui_gtk4_da.c
index 4a7211fd7..739f2e0ae 100644
--- a/src/gui_gtk4_da.c
+++ b/src/gui_gtk4_da.c
@@ -1752,7 +1752,8 @@ vim_draw_area_queue_image(VimDrawArea *self, GList *link)
  * (src_x, src_y, draw_w, draw_h) describe which pixel sub-rect of the source
  * texture should be drawn. If there is an image that has the same id, then it
  * is re-rendered with the new texture. If zindex of an image changed, then the
- * queue will be updated accordingly.
+ * queue will be updated accordingly. Note that the dimensions/positions are to
+ * be in physical pixels!!!
  */
     void
 vim_draw_area_add_image(
@@ -1760,15 +1761,15 @@ vim_draw_area_add_image(
 	GdkTexture  *image,
 	int	    row,
 	int	    col,
-	int	    src_x,
-	int	    src_y,
-	int	    draw_w,
-	int	    draw_h,
+	double	    src_x,
+	double	    src_y,
+	double	    draw_w,
+	double	    draw_h,
 	int	    zindex,
 	int	    id)
 {
     GskRenderNode   *node, *old;
-    int		    w, h;
+    double	    w, h;
     graphene_rect_t clip;
     GList	    *link;
     DrawImage	    *dimg;
@@ -1778,12 +1779,16 @@ vim_draw_area_add_image(
 		|| col >= self->n_cols))
 	return;
 
-    w = gdk_texture_get_width(image);
-    h = gdk_texture_get_height(image);
+    w = PHY2LOG(gdk_texture_get_width(image));
+    h = PHY2LOG(gdk_texture_get_height(image));
+    src_x = PHY2LOG(src_x);
+    src_y = PHY2LOG(src_y);
+    draw_w = PHY2LOG(draw_w);
+    draw_h = PHY2LOG(draw_h);
 
-    node = gsk_texture_node_new(image,
+    node = gsk_texture_scale_node_new(image,
 	    &GRAPHENE_RECT_INIT(FILL_X(col) - src_x, FILL_Y(row) - src_y,
-		w, h));
+		w, h), GSK_SCALING_FILTER_TRILINEAR);
 
     if (node != NULL)
     {
diff --git a/src/gui_gtk4_da.h b/src/gui_gtk4_da.h
index 5cd1cdc55..a214729a8 100644
--- a/src/gui_gtk4_da.h
+++ b/src/gui_gtk4_da.h
@@ -34,7 +34,7 @@ void vim_draw_area_add_sign(VimDrawArea *self, GdkTexture *sign, int row, int co
 void vim_draw_area_add_multisign(VimDrawArea *self, cairo_surface_t *surf, int row, int col, int width, int height);
 # endif
 # ifdef FEAT_IMAGE_GDK
-void vim_draw_area_add_image(VimDrawArea *self, GdkTexture *image, int row, int col, int src_x, int src_y, int draw_w, int draw_h, int zindex, int id);
+void vim_draw_area_add_image(VimDrawArea *self, GdkTexture *image, int row, int col, double src_x, double src_y, double draw_w, double draw_h, int zindex, int id);
 void vim_draw_area_remove_image(VimDrawArea *self, int id);
 # endif
 
diff --git a/src/gui_gtk_x11.c b/src/gui_gtk_x11.c
index 098892d5c..1291919b2 100644
--- a/src/gui_gtk_x11.c
+++ b/src/gui_gtk_x11.c
@@ -836,6 +836,14 @@ scale_factor_event(GtkWidget *widget,
     gui.force_redraw = 1;
     gui_resize_shell(w, usable_height);
     gui_gtk_form_thaw(GTK_FORM(gui.formwin));
+#  ifdef FEAT_IMAGE
+    {
+	double old = gui.scale;
+
+	gui.scale = gtk_widget_get_scale_factor(widget);
+	popup_update_scale(old);
+    }
+#  endif
 
     return TRUE;
 }
@@ -4291,6 +4299,10 @@ gui_mch_init(void)
 			   G_CALLBACK(gtk_settings_xft_dpi_changed_cb), NULL);
     }
 
+#ifdef FEAT_IMAGE
+    gui.scale = gtk_widget_get_scale_factor(gui.formwin);
+#endif
+
     return OK;
 }
 
diff --git a/src/popupwin.c b/src/popupwin.c
index 293305ed9..bbbfa300c 100644
--- a/src/popupwin.c
+++ b/src/popupwin.c
@@ -1110,9 +1110,9 @@ apply_general_options(win_T *wp, dict_T *dict)
 			    if (gui.in_use)
 			    {
 				if (gui.char_width > 0)
-				    cx = gui.char_width;
+				    cx = LOG2PHY(gui.char_width);
 				if (gui.char_height > 0)
-				    cy = gui.char_height;
+				    cy = LOG2PHY(gui.char_height);
 			    }
 			    else
 #  endif
@@ -3013,6 +3013,35 @@ popup_adjust_position(win_T *wp)
     }
 }
 
+#if defined(FEAT_GUI_GTK) && defined(FEAT_IMAGE)
+/*
+ * Should be called when scale factor changes.
+ */
+    void
+popup_update_scale(double old_scale)
+{
+    win_T *wp;
+    double ratio = old_scale / gui.scale;
+
+    FOR_ALL_POPUPWINS_IN_TAB(curtab, wp)
+    {
+	// If the popup has an image, recalculate its bounding box in cells
+	if (wp->w_popup_image_data != NULL)
+	{
+	    // Add +0.5 so that it rounds to nearest whole value
+	    wp->w_minwidth  = wp->w_minwidth * ratio + 0.5;
+	    wp->w_maxwidth  = wp->w_maxwidth * ratio + 0.5;
+	    wp->w_minheight = wp->w_minheight * ratio + 0.5;
+	    wp->w_maxheight = wp->w_maxheight * ratio + 0.5;
+	}
+
+	// Reflow the popup layout with the newly calculated limits
+	popup_adjust_position(wp);
+    }
+    redraw_later(UPD_CLEAR);
+}
+#endif
+
 typedef enum
 {
     TYPE_NORMAL,
@@ -6825,9 +6854,13 @@ popup_image_gui_clip(
 	int	*draw_h)
 {
     popup_clip_T    cl;
-    int		    cell_x = gui.char_width > 0 ? gui.char_width : 8;
-    int		    cell_y = gui.char_height > 0 ? gui.char_height : 16;
+    // "gui.char_*" are in logical pixels, must convert to physical first,
+    // because all the other dimensions are in physical pixels.
+    int		    cell_x = LOG2PHY(gui.char_width > 0 ? gui.char_width : 8);
+    int		    cell_y = LOG2PHY(gui.char_height > 0 ? gui.char_height : 16);
     win_T	    *cw;
+    int		    visible_w;
+    int		    visible_h;
 
     popup_compute_clip(wp, &cl);
     *row += cl.clip_top_content;
@@ -6857,6 +6890,18 @@ popup_image_gui_clip(
 	*draw_w = 0;
     if (*draw_h < 0)
 	*draw_h = 0;
+
+    // If image is larger than the actual screen size, then clip it. Maybe best
+    // solution would be to somehow make popup win bigger than the screen size
+    // (as if 'nowrap' is on). However that would require a lot of changes so
+    // just clip the image for now.
+    visible_w = wp->w_width - cl.clip_left_content - cl.clip_right_content;
+    visible_h = wp->w_height - cl.clip_top_content - cl.clip_bot_content;
+
+    if (visible_w > 0)
+	*draw_w = MIN(*draw_w, visible_w * cell_x);
+    if (visible_h > 0)
+	*draw_h = MIN(*draw_h, visible_h * cell_y);
 }
 # endif
 
@@ -7085,12 +7130,12 @@ popup_emit_image(win_T *wp)
     // vim still thinks it is at the image origin, so the relative-move
     // optimisation would otherwise place the cursor on the line just
     // after the image.
-    out_str((char_u *)" [?25l");
+    cursor_off();
     term_windgoto(row, col);
     out_str(wp->w_popup_image_seq);
     screen_start();
     setcursor_mayforce(TRUE);
-    out_str((char_u *)" [?25h");
+    cursor_on();
     out_flush();
 
     // The sixel bytes just painted over every cell of the emitted rectangle,
diff --git a/src/proto/cairo.pro b/src/proto/cairo.pro
index a29737104..1aad52c2b 100644
--- a/src/proto/cairo.pro
+++ b/src/proto/cairo.pro
@@ -2,5 +2,5 @@
 bool cairo_popup_image_ensure(win_T *wp);
 bool cairo_popup_image_update(win_T *wp);
 void cairo_popup_image_free(win_T *wp);
-void cairo_popup_image_paint(win_T *wp, void *target, int x, int y, int src_x, int src_y, int draw_w, int draw_h);
+void cairo_popup_image_paint(win_T *wp, void *target, int x, int y, double src_x, double src_y, double draw_w, double draw_h);
 /* vim: set ft=c : */
diff --git a/src/proto/popupwin.pro b/src/proto/popupwin.pro
index 98e3a5f59..758695c35 100644
--- a/src/proto/popupwin.pro
+++ b/src/proto/popupwin.pro
@@ -11,6 +11,7 @@ int popup_left_extra(win_T *wp);
 int popup_height(win_T *wp);
 int popup_width(win_T *wp);
 int popup_extra_width(win_T *wp);
+void popup_update_scale(double old_scale);
 int parse_previewpopup(win_T *wp);
 int parse_completepopup(win_T *wp);
 void popup_set_wantpos_cursor(win_T *wp, int width, dict_T *d);
diff --git a/src/version.c b/src/version.c
index c857374a9..7601e516d 100644
--- a/src/version.c
+++ b/src/version.c
@@ -758,6 +758,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    853,
 /**/
     852,
 /**/

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups "vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To view this discussion visit https://groups.google.com/d/msgid/vim_dev/E1wnOAa-00CEwV-30%40256bit.org.
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.