]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0710: GTK4 GUI resize handling can be improved v9.2.0710
authorFoxe Chen <chen.foxe@gmail.com>
Tue, 23 Jun 2026 20:03:31 +0000 (20:03 +0000)
committerChristian Brabandt <cb@256bit.org>
Tue, 23 Jun 2026 20:03:31 +0000 (20:03 +0000)
Problem:  GTK4 GUI resize handling can be improved
Solution: Remove the resize debounce, set the draw area's size request
          in gui_mch_set_text_area_pos() via vim_form_move_resize()
          (Foxe Chen).

reverts: #20327
closes:  #20486

Signed-off-by: Foxe Chen <chen.foxe@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
src/gui.c
src/gui.h
src/gui_gtk4.c
src/gui_gtk4_da.c
src/gui_gtk4_f.c
src/proto/gui_gtk4.pro
src/version.c

index 0cd586114a92777fada8060702062eb5a64acb44..0ca09d8eb1368243493c7a7cff60682e70338a3d 100644 (file)
--- a/src/gui.c
+++ b/src/gui.c
@@ -1625,6 +1625,14 @@ again:
     gui_position_components(pixel_width);
     gui_reset_scroll_region();
 
+#if defined(FEAT_GUI_GTK) && defined(USE_GTK4) && !defined(USE_GTK4_SNAPSHOT)
+    // We do not resize the draw area via the "resize" signal. This is because
+    // when the window is resized, the form widget is the one that is resized,
+    // so let that call gui_resize_shell() which will allocate the surface and
+    // allocate the drawing area size/position.
+    gui_gtk4_resize(pixel_width, pixel_height);
+#endif
+
     /*
      * At the "more" and ":confirm" prompt there is no redraw, put the cursor
      * at the last line here (why does it have to be one row too low?).
@@ -1647,6 +1655,9 @@ again:
 
     gui_update_scrollbars(TRUE);
     gui_update_cursor(FALSE, TRUE);
+#if defined(FEAT_GUI_GTK) && defined(USE_GTK4_SNAPSHOT)
+    gui_gtk_calculate_bleed(pixel_width, pixel_height);
+#endif
 #if defined(FEAT_XIM) && !defined(FEAT_GUI_GTK)
     xim_set_status_area();
 #endif
@@ -1821,6 +1832,10 @@ gui_set_shellsize(
     gui_position_components(width);
     gui_update_scrollbars(TRUE);
     gui_reset_scroll_region();
+
+#if defined(FEAT_GUI_GTK) && defined(USE_GTK4_SNAPSHOT)
+    gui_gtk_calculate_bleed(width, height);
+#endif
 }
 
 /*
index 674601e0f7c21ec3dc15693fd7d05f93d0569cd2..78ad3a09bbc3f6ca091fade66ca18d5bba7aa624 100644 (file)
--- a/src/gui.h
+++ b/src/gui.h
@@ -273,6 +273,10 @@ typedef struct Gui
 #ifdef FEAT_DIRECTX
     bool       directx_enabled;    // DirectX (DirectWrite) rendering active
 #endif
+#if defined(FEAT_GUI_GTK) && defined(USE_GTK4_SNAPSHOT)
+    int                bleed_right;        // Number of pixels to bleed bg color right
+    int                bleed_bot;          // Number of pixels to bleed bg color down
+#endif
 
 #ifdef FEAT_MENU
 # ifndef FEAT_GUI_GTK
index 5126f991989a5fef637e51796cd69f372a4fb8f4..7084516d87642c4d928952b4d121796185896cd4 100644 (file)
@@ -299,12 +299,9 @@ static void tabline_menu_press_event(GtkGestureClick *gesture, int n_press, doub
 static void mainwin_destroy_cb(GObject *object, gpointer data);
 static gboolean delete_event_cb(GtkWindow *window, gpointer data);
 static void mainwin_fullscreened_cb(GObject *obj, GParamSpec *pspec, gpointer user_data);
-#ifndef USE_GTK4_SNAPSHOT
 static void drawarea_realize_cb(GtkWidget *widget, gpointer data);
-#endif
 static void drawarea_unrealize_cb(GtkWidget *widget, gpointer data);
 #ifndef USE_GTK4_SNAPSHOT
-static void drawarea_resize_cb(GtkDrawingArea *area, int width, int height, gpointer data);
 static void drawarea_scale_factor_cb(GObject *object, GParamSpec *pspec, gpointer data);
 static cairo_surface_t *create_backing_surface(int width, int height);
 #endif
@@ -598,13 +595,11 @@ gui_mch_init(void)
     gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(gui.drawarea),
            (GtkDrawingAreaDrawFunc)draw_event, NULL, NULL);
 
-    g_signal_connect(G_OBJECT(gui.drawarea), "resize",
-                    G_CALLBACK(drawarea_resize_cb), 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);
-#endif
     g_signal_connect(G_OBJECT(gui.drawarea), "unrealize",
                     G_CALLBACK(drawarea_unrealize_cb), NULL);
 
@@ -887,10 +882,10 @@ gui_mch_newfont(void)
 {
     int w, h;
 
+    // Do not subtract width and height with menubar, toolbar, etc, because
+    // those are not part of the shell.
     w = gtk_widget_get_width(gui.formwin);
     h = gtk_widget_get_height(gui.formwin);
-    w -= get_menu_tool_width();
-    h -= get_menu_tool_height();
     gui_resize_shell(w, h);
 }
 
@@ -968,7 +963,13 @@ gui_mch_get_screen_dimensions(int *screen_w, int *screen_h)
 gui_mch_enable_menu(int showit)
 {
     if (gui.menubar != NULL)
+    {
        gtk_widget_set_visible(gui.menubar, showit);
+       // Draw area might become blank after this for some reason, queue a
+       // redraw, same for toolbar as well.
+       if (gui.drawarea != NULL)
+           gtk_widget_queue_draw(gui.drawarea);
+    }
 }
 #endif
 
@@ -982,6 +983,8 @@ gui_mch_show_toolbar(int showit)
        if (showit)
            vim_toolbar_set_style(VIM_TOOLBAR(gui.toolbar),
                    toolbar_flags, tbis_flags);
+       if (gui.drawarea != NULL)
+           gtk_widget_queue_draw(gui.drawarea);
     }
 }
 #endif
@@ -2339,10 +2342,10 @@ menubar_popover_closed_hook(GSignalInvocationHint *ihint UNUSED,
 }
 #endif
 
-#ifndef USE_GTK4_SNAPSHOT
     static void
 drawarea_realize_cb(GtkWidget *widget UNUSED, gpointer data UNUSED)
 {
+#ifndef USE_GTK4_SNAPSHOT
     int w, h;
 
     // Use formwin size since drawarea may not have its final size yet
@@ -2363,10 +2366,10 @@ drawarea_realize_cb(GtkWidget *widget UNUSED, gpointer data UNUSED)
     if (gui.surface != NULL)
        cairo_surface_destroy(gui.surface);
     gui.surface = create_backing_surface(w, h);
+#endif
 
     gui_mch_new_colors();
 }
-#endif
 
     static void
 drawarea_unrealize_cb(GtkWidget *widget UNUSED, gpointer data UNUSED)
@@ -2384,42 +2387,8 @@ drawarea_unrealize_cb(GtkWidget *widget UNUSED, gpointer data UNUSED)
 }
 
 #ifndef USE_GTK4_SNAPSHOT
-// Debounced resize: drawarea_resize_cb only resizes the backing surface
-// (preserving old content) and (re)arms a short timeout. The actual
-// gui_resize_shell() runs from drawarea_resize_apply_cb once the user has
-// stopped dragging for ~100 ms, by which time no input is pending and
-// update_screen() will not bail in screenclear()'s wake.
-static guint drawarea_resize_timeout_id = 0;
-static int drawarea_resize_pending_w = 0;
-static int drawarea_resize_pending_h = 0;
-
-    static gboolean
-drawarea_resize_apply_cb(gpointer data UNUSED)
-{
-    int width = drawarea_resize_pending_w;
-    int height = drawarea_resize_pending_h;
-
-    drawarea_resize_timeout_id = 0;
-
-    if (width <= 0 || height <= 0)
-       return G_SOURCE_REMOVE;
-    if (updating_screen)
-    {
-       drawarea_resize_timeout_id = g_timeout_add(50,
-               drawarea_resize_apply_cb, NULL);
-       return G_SOURCE_REMOVE;
-    }
-
-    gui.force_redraw = TRUE;
-    gui_resize_shell(width, height);
-    if (gui.in_use)
-       redraw_all_later(UPD_CLEAR);
-    return G_SOURCE_REMOVE;
-}
-
-    static void
-drawarea_resize_cb(GtkDrawingArea *area UNUSED, int width, int height,
-       gpointer data UNUSED)
+    void
+gui_gtk4_resize(int width, int height)
 {
     cairo_t *cr;
     cairo_surface_t *old_surface;
@@ -2428,9 +2397,6 @@ drawarea_resize_cb(GtkDrawingArea *area UNUSED, int width, int height,
     if (width <= 0 || height <= 0)
        return;
 
-    drawarea_resize_pending_w = width;
-    drawarea_resize_pending_h = height;
-
     // Keep the backing surface in sync with the drawing area so GTK keeps
     // showing the previous frame. Re-creating it preserves the old
     // contents.
@@ -2465,13 +2431,6 @@ drawarea_resize_cb(GtkDrawingArea *area UNUSED, int width, int height,
            cairo_destroy(cr);
        }
     }
-
-    // Debounce: (re)arm the apply timeout, so gui_resize_shell() only
-    // runs once the resize stream settles.
-    if (drawarea_resize_timeout_id != 0)
-       g_source_remove(drawarea_resize_timeout_id);
-    drawarea_resize_timeout_id = g_timeout_add(100,
-           drawarea_resize_apply_cb, NULL);
 }
 
     static void
@@ -4977,20 +4936,41 @@ gui_mch_update_scrollbar_size(void)
     void
 gui_mch_set_text_area_pos(int x, int y, int w, int h)
 {
+    // "h" may be negative especially when draw area size is smaller than
+    // "gui.char_height".
+    if (w <= 0 || h <= 0)
+       return;
     last_text_area_w = w;
     last_text_area_h = h;
-    // Don't use vim_form_move_resize for drawarea because its
-    // set_size_request would prevent the window from shrinking.
-    // Just update position; the actual allocation is handled by
-    // vim_form_size_allocate which gives drawarea the formwin's full size.
-    vim_form_move(VIM_FORM(gui.formwin), gui.drawarea, x, y);
-
-    // Surface sizing is owned by drawarea_resize_cb; don't recreate it
-    // here. Recreating on every text-area change wiped any preserved
-    // content whenever a sub-cell resize shifted the cell grid, and
-    // update_screen() may bail (char_avail()) during a drag and leave
-    // the fresh surface blank.
+
+    vim_form_move_resize(VIM_FORM(gui.formwin), gui.drawarea, x, y, w, h);
+}
+
+#ifdef USE_GTK4_SNAPSHOT
+/*
+ * Calculate the number of pixels to bleed background color to. Should be called
+ * after all UI elements are positioned and resized.
+ */
+    void
+gui_gtk_calculate_bleed(int width, int height)
+{
+    gui.bleed_right = width - last_text_area_w;
+    gui.bleed_bot = height - last_text_area_h;
+
+    if (gui.which_scrollbars[SBAR_LEFT])
+       gui.bleed_right -= gui.scrollbar_width;
+    if (gui.which_scrollbars[SBAR_RIGHT])
+       gui.bleed_right -= gui.scrollbar_width;
+    if (gui.which_scrollbars[SBAR_BOTTOM])
+       gui.bleed_bot -= gui.scrollbar_height;
+
+    // Not sure if this can happen, but be safe...
+    if (gui.bleed_right < 0)
+       gui.bleed_right = 0;
+    if (gui.bleed_bot < 0)
+       gui.bleed_bot = 0;
 }
+#endif
 
 /*
  * ============================================================
index 34dad93a614996fcbe2c588462206c7caee3d730..11b7463892ae024904440b4224dc16d5ce9db75d 100644 (file)
@@ -75,7 +75,7 @@ struct _VimDrawArea
     int         n_rows;
     int                n_cols;
 
-    int                resize_count;
+    int                bleed_right;
 
     // Used for hollow and part style cursors. For the block cursor, that is
     // simply rendered as a cell using vim_draw_area_add_glyphs(). May be NULL.
@@ -107,7 +107,6 @@ G_DEFINE_TYPE(VimDrawArea, vim_draw_area, GTK_TYPE_WIDGET)
 static void draw_image_free(DrawImage *dimg);
 #endif
 static void vim_draw_area_snapshot(GtkWidget *widget, GtkSnapshot *snapshot);
-static void vim_draw_area_size_allocate(GtkWidget *widget, int width, int height, int baseline);
 
     static void
 vim_draw_area_finalize(GObject *obj)
@@ -138,10 +137,10 @@ vim_draw_area_class_init(VimDrawAreaClass *class)
     GObjectClass    *obj_class = G_OBJECT_CLASS(class);
 
     widget_class->snapshot = vim_draw_area_snapshot;
-    widget_class->size_allocate = vim_draw_area_size_allocate;
 
     obj_class->finalize = vim_draw_area_finalize;
 
+    gtk_widget_class_set_layout_manager_type(widget_class, GTK_TYPE_BIN_LAYOUT);
 }
 
     static void
@@ -178,7 +177,6 @@ vim_draw_area_set_size(VimDrawArea *self, int rows, int cols)
     self->n_cols = cols;
     self->cells = g_realloc_n(self->cells, rows * cols, sizeof(DrawCell));
     memset(self->cells, 0, rows * (sizeof(DrawCell) * cols));
-    self->resize_count++;
 }
 
     static void
@@ -574,14 +572,17 @@ draw_node_render(DrawNode *dnode, int row, VimDrawArea *da)
     if (!(dnode->dnode_flags & DRAW_NODE_NOBG))
     {
        int width = dnode->n_cells * gui.char_width;
-       int bleed = gtk_widget_get_width(GTK_WIDGET(da)) - FILL_X(da->n_cols);
 
        // If this draw node touches the end of the draw area. Bleed its
        // background to the right if the space the draw area covers is slightly
        // bigger than its actual visible area (that all cells cover). This just
        // makes things like status bars look a bit nicer
-       if (END_COL(dnode) == da->n_cols - 1 && bleed > 0)
-           width += bleed;
+       //
+       // Don't do this for the bottom, because that will make the cursor in
+       // the cmdline look weird. Instead only bleed downwards when drawing the
+       // global background color (see vim_draw_area_snapshot())
+       if (END_COL(dnode) == da->n_cols - 1)
+           width += gui.bleed_right;
 
        nodes[n_nodes++] = gsk_color_node_new(&dnode->bg_color,
                &GRAPHENE_RECT_INIT(FILL_X(dnode->start_col), FILL_Y(row),
@@ -1475,8 +1476,8 @@ vim_draw_area_snapshot(GtkWidget *widget, GtkSnapshot *snapshot)
     garray_T               invert_ga;
 
     gui_mch_set_bg_color(gui.back_pixel);
-    height = gtk_widget_get_height(widget);
-    width = gtk_widget_get_width(widget);
+    height = gtk_widget_get_height(widget) + gui.bleed_bot;
+    width = gtk_widget_get_width(widget) + gui.bleed_right;
 
     if (self->cells == NULL)
     {
@@ -1485,6 +1486,23 @@ vim_draw_area_snapshot(GtkWidget *widget, GtkSnapshot *snapshot)
        return;
     }
 
+    // If number of pixels to bleed has changed, then dirty the nodes at the
+    // right edge of the draw area.
+    if (self->bleed_right != gui.bleed_right)
+    {
+       self->bleed_right = gui.bleed_right;
+       for (int r = 0; r < self->n_rows; r++)
+       {
+           DrawCell *dcell = &GET_ROW(self, r)[self->n_cols - 1];
+
+           if (dcell->dnode != NULL)
+           {
+               (void)draw_node_make_dirty(dcell->dnode);
+               draw_node_render(dcell->dnode, r, self);
+           }
+       }
+    }
+
     // For inverted cells, we first build an array of bounds that represent
     // blocks of inverted cells. Then we apply a white color to each of those
     // bounds and then finish the blend.
@@ -1565,36 +1583,4 @@ vim_draw_area_snapshot(GtkWidget *widget, GtkSnapshot *snapshot)
 #endif
 }
 
-    static void
-vim_draw_area_size_allocate(
-       GtkWidget   *widget,
-       int         width,
-       int         height,
-       int         baseline UNUSED)
-{
-    VimDrawArea *self = VIM_DRAW_AREA(widget);
-    int                old_count = self->resize_count;
-
-    gui_resize_shell(width, height);
-
-    if (old_count == self->resize_count)
-    {
-       // Number of columns or rows hasn't changed. However still re render the
-       // draw nodes at the right edge of the draw area, so that they can
-       // update their background bleed (see draw_node_render()).
-       for (int r = 0; r < self->n_rows; r++)
-       {
-           DrawCell *dcell = &GET_ROW(self, r)[self->n_cols - 1];
-
-           if (dcell->dnode != NULL)
-           {
-               (void)draw_node_make_dirty(dcell->dnode);
-               draw_node_render(dcell->dnode, r, self);
-           }
-       }
-    }
-
-    return;
-}
-
 #endif // USE_GTK4_SNAPSHOT
index 52941874fb8ab00c6767080888d84b80f6584752..29c17aa6d5b292f4e333197b788ad9c3bfaf982a 100644 (file)
@@ -209,18 +209,13 @@ vim_form_snapshot(GtkWidget *widget, GtkSnapshot *snapshot)
     static gboolean
 vim_form_resize_idle_cb(VimForm *self)
 {
-    int w, h;
-
     self->resize_idle_id = 0;
 
-    // Use drawarea's actual allocation, not formwin's
     if (gui.drawarea == NULL)
        goto exit;
-    w = gtk_widget_get_width(gui.drawarea);
-    h = gtk_widget_get_height(gui.drawarea);
 
-    if (w > 1 && h > 1)
-       gui_resize_shell(w, h);
+    if (self->last_width > 1 && self->last_height > 1)
+       gui_resize_shell(self->last_width, self->last_height);
 
 exit:
     g_object_unref(self);
index acfb13c9a519b95de0131d9a7452c03009865674..eaf8eb26861956d6167d4f163ce3040685c46370 100644 (file)
@@ -52,6 +52,7 @@ void gui_mch_draw_hollow_cursor(guicolor_T color);
 void gui_mch_draw_part_cursor(int w, int h, guicolor_T color);
 void gui_mch_flash(int msec);
 void gui_mch_invert_rectangle(int r, int c, int nr, int nc);
+void gui_gtk4_resize(int width, int height);
 void gui_mch_update(void);
 int gui_mch_wait_for_chars(long wtime);
 void gui_mch_flush(void);
@@ -103,6 +104,7 @@ void gui_mch_create_scrollbar(scrollbar_T *sb, int orient);
 void gui_mch_destroy_scrollbar(scrollbar_T *sb);
 void gui_mch_update_scrollbar_size(void);
 void gui_mch_set_text_area_pos(int x, int y, int w, int h);
+void gui_gtk_calculate_bleed(int width, int height);
 char_u *gui_mch_browse(int saving, char_u *title, char_u *dflt, char_u *ext, char_u *initdir, char_u *filter);
 char_u *gui_mch_browsedir(char_u *title, char_u *initdir);
 int gui_mch_dialog(int type, char_u *title, char_u *message, char_u *buttons, int def_but, char_u *textfield, int ex_cmd);
index 3f52cbadff20e00b1a6b0d8e699c82af4b3bc3d1..7433593c773dd250f1e44ce4f6a497bbff5d04e6 100644 (file)
@@ -759,6 +759,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    710,
 /**/
     709,
 /**/