From 425c7b6c600689428ef9734822a54d4c920b4c61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=8A?= =?UTF-8?q?=D1=80=20=D0=9A=D1=83=D1=80=D1=82=D0=B0=D0=BA=D0=BE=D0=B2?= Date: Fri, 15 May 2026 14:55:40 +0300 Subject: [PATCH] [Gtk4] Fix PaintListener paint ordering for all widget types On Gtk 4, PaintListener on leaf controls (Button, Label, etc.) drew custom content under the native widget appearance instead of over it (issue #2819). Fixing this by simply moving snapshotToDraw after the children loop regressed JFace Forms rendering (PR #3299). New paint-ordering scheme that mirrors Gtk 3's signal split (EXPOSE_EVENT_INVERSE for containers, DRAW/EXPOSE_EVENT for leaf controls). --- .../org/eclipse/swt/widgets/Composite.java | 12 ++++++++++ .../gtk/org/eclipse/swt/widgets/Control.java | 19 ++++++++++++++++ .../gtk/org/eclipse/swt/widgets/Display.java | 5 ++++- .../gtk/org/eclipse/swt/widgets/Table.java | 12 ++++++++++ .../gtk/org/eclipse/swt/widgets/Tree.java | 12 ++++++++++ .../gtk/org/eclipse/swt/widgets/Widget.java | 22 ++++++++++++++----- 6 files changed, 76 insertions(+), 6 deletions(-) diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Composite.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Composite.java index 0c1cebdd5a6..5e090cebb26 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Composite.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Composite.java @@ -558,6 +558,18 @@ void snapshotBackground (long handle, long snapshot) { Graphene.graphene_rect_free(rect); } +@Override +void snapshotToDraw (long handle, long snapshot) { + // Containers paint before children so SWT.Paint backgrounds appear behind child controls, + // matching GTK3 EXPOSE_EVENT_INVERSE (after=false) behavior. + snapshotPaint(handle, snapshot); +} + +@Override +void snapshotToDrawAfterChildren (long handle, long snapshot) { + // Suppress Control's after-children paint: Composite already painted before children above. +} + /** * Fills the interior of the rectangle specified by the arguments, diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Control.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Control.java index ca50225611f..88f7a051ded 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Control.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Control.java @@ -524,6 +524,25 @@ boolean hooksPaint () { return hooks (SWT.Paint) || filters (SWT.Paint); } +@Override +void snapshotPaint (long handle, long snapshot) { + /* + * Guard against creating an empty Cairo render node when there are no paint + * listeners. gtk_snapshot_append_cairo() appends a node immediately; leaving it + * empty/unfinished causes it to obscure render nodes already in the snapshot + * (e.g. GtkTreeView content snapshotted before this call). + */ + if (!hooksPaint()) return; + super.snapshotPaint(handle, snapshot); +} + +@Override +void snapshotToDrawAfterChildren (long handle, long snapshot) { + // Leaf controls (Button, Label, etc.) paint after children so SWT.Paint listeners + // draw on top of the native widget appearance, matching GTK3 DRAW (after=true) behavior. + snapshotPaint(handle, snapshot); +} + @Override long hoverProc (long widget) { int[] x = new int[1], y = new int[1], mask = new int[1]; diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Display.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Display.java index 77c78620728..149e873b9a1 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Display.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Display.java @@ -1720,7 +1720,7 @@ void snapshotDrawProc(long handle, long snapshot) { // Draw background before children so it appears behind them if (widget != null) widget.snapshotBackground(handle, snapshot); - // Draw SWT custom paint before children so child widgets remain visible. + // Paint before children (used by Composite subclasses for backgrounds) if (widget != null) widget.snapshotToDraw(handle, snapshot); long child = GTK4.gtk_widget_get_first_child(handle); @@ -1729,6 +1729,9 @@ void snapshotDrawProc(long handle, long snapshot) { GTK4.gtk_widget_snapshot_child(handle, child, snapshot); child = GTK4.gtk_widget_get_next_sibling(child); } + + // Paint after children (used by leaf controls for overlay/on-top drawing) + if (widget != null) widget.snapshotToDrawAfterChildren(handle, snapshot); } static long rendererGetPreferredWidthProc (long cell, long handle, long minimun_size, long natural_size) { diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Table.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Table.java index a72fb2f3a97..c416841785f 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Table.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Table.java @@ -4233,6 +4233,18 @@ private void gtk3_paintEvent(long cairo) { event.gc = null; } +@Override +void snapshotToDraw(long handle, long snapshot) { + // Table renders via native GTK children (GtkScrolledWindow > GtkTreeView). + // Like GTK3 where Table explicitly fires paint in EXPOSE_EVENT (after=true), + // GTK4 must paint after children so SWT.Paint overlays appear on top. +} + +@Override +void snapshotToDrawAfterChildren(long handle, long snapshot) { + snapshotPaint(handle, snapshot); +} + @Override public void dispose() { super.dispose(); diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Tree.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Tree.java index aa25c3203c7..86089db0022 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Tree.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Tree.java @@ -4306,6 +4306,18 @@ private void gtk3_paintEvent(long cairo) { event.gc = null; } +@Override +void snapshotToDraw(long handle, long snapshot) { + // Tree renders via native GTK children (GtkScrolledWindow > GtkTreeView). + // Like GTK3 where Tree explicitly fires paint in EXPOSE_EVENT (after=true), + // GTK4 must paint after children so SWT.Paint overlays appear on top. +} + +@Override +void snapshotToDrawAfterChildren(long handle, long snapshot) { + snapshotPaint(handle, snapshot); +} + private void throwCannotRemoveItem(int i) { String message = "Cannot remove item with index " + i + "."; throw new SWTException(message); diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Widget.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Widget.java index 898bd7117e9..9e1889f6d87 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Widget.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Widget.java @@ -2292,12 +2292,10 @@ void snapshotBackground (long handle, long snapshot) { /** * Converts an incoming snapshot into a gtk_draw() call, complete with - * a Cairo context. - * - * @param handle the widget receiving the snapshot - * @param snapshot the actual GtkSnapshot + * a Cairo context. Used by subclasses to + * trigger painting at the appropriate point in the snapshot order. */ -void snapshotToDraw (long handle, long snapshot) { +void snapshotPaint (long handle, long snapshot) { GtkAllocation allocation = new GtkAllocation(); GTK.gtk_widget_get_allocation(handle, allocation); long rect = Graphene.graphene_rect_alloc(); @@ -2312,6 +2310,20 @@ void snapshotToDraw (long handle, long snapshot) { Graphene.graphene_rect_free(rect); } +/** + * Called before child widgets are snapshotted. Containers (Composite) override + * this to paint backgrounds behind their children. + */ +void snapshotToDraw (long handle, long snapshot) { +} + +/** + * Called after child widgets are snapshotted. Leaf controls (Button, Label, + * etc.) override this to paint on top of their native appearance. + */ +void snapshotToDrawAfterChildren (long handle, long snapshot) { +} + long gtk_widget_get_window (long widget){ GTK.gtk_widget_realize(widget); return GTK3.gtk_widget_get_window (widget);