]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0853: popup: popup images do not support scaling v9.2.0853
authorFoxe Chen <chen.foxe@gmail.com>
Fri, 24 Jul 2026 21:56:37 +0000 (21:56 +0000)
committerChristian Brabandt <cb@256bit.org>
Fri, 24 Jul 2026 21:56:37 +0000 (21:56 +0000)
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 <chen.foxe@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
src/cairo.c
src/gui.c
src/gui.h
src/gui_gtk4.c
src/gui_gtk4_da.c
src/gui_gtk4_da.h
src/gui_gtk_x11.c
src/popupwin.c
src/proto/cairo.pro
src/proto/popupwin.pro
src/version.c

index 7543a33d9b4015e704a4b0ff2c6a887c29c10174..7f1df2b2d031af28137e1c7ae8c050709de27735 100644 (file)
@@ -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);
index f3ba3705a9fa5872497faabd284f088a0059c52e..0c7b39be0b3cf33c212e486969b244befaa70e61 100644 (file)
--- 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;
 
index 1fadca0f01f883af6cbf3a59869512ef6cbee2e3..16f13e477249b90a0f53ba0ad8da2b3d310f6b29 100644 (file)
--- 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)
index b29b180d6356ca0e3ddcb2505e197655aabce4bb..486ed38a0e3340a120fbc6191a7000e209055b8c 100644 (file)
@@ -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
 {
index 4a7211fd70773f613fd507d0b73855c77fd67ae6..739f2e0aed5ac459d962943c796687db31de8b02 100644 (file)
@@ -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)
     {
index 5cd1cdc5529362c9e46e13a7454e0ae1d683881b..a214729a876593bf8d9bf1a13f9503e169a3aeb2 100644 (file)
@@ -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
 
index 098892d5c12268273b069fd4d1c34340f93686cf..1291919b29f60d7c30ae49d1eddea24999b3ae05 100644 (file)
@@ -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;
 }
 
index 293305ed92f5fdd5b4c5918844941cb8a227d969..bbbfa300c68cee07a068ddb08fec405bd1fd9d05 100644 (file)
@@ -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 *)"\033[?25l");
+    cursor_off();
     term_windgoto(row, col);
     out_str(wp->w_popup_image_seq);
     screen_start();
     setcursor_mayforce(TRUE);
-    out_str((char_u *)"\033[?25h");
+    cursor_on();
     out_flush();
 
     // The sixel bytes just painted over every cell of the emitted rectangle,
index a2973710425ffdcc454b82b5201184a674c98714..1aad52c2bdc024812e5e534e4edc9152eb78748d 100644 (file)
@@ -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 : */
index 98e3a5f5922bfad9c4a99f1f98c54d19c5285a2f..758695c35fc2bea78b34375d220952067313fe2b 100644 (file)
@@ -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);
index c857374a9398116b598385bbde3661d7c3000b5d..7601e516d072d5a6d908e3b23c4db4bebd8cbe08 100644 (file)
@@ -758,6 +758,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    853,
 /**/
     852,
 /**/