From 9445d3beb27361d6284a2cea59f0c507efcc0b2f Mon Sep 17 00:00:00 2001 From: Lars Vogel Date: Tue, 27 Jan 2026 17:25:01 +0100 Subject: [PATCH 1/2] [Win32] Optimize Composite.resizeChildren and enable deferred layout for SashForm This commit improves the performance of Composite.resizeChildren by batching calls to the Windows DeferWindowPos API. Instead of making individual JNI calls for each child, arrays of window positions are passed to a new native method, which handles the loop in C. This optimization allows SashForm to use deferred layout on Windows, which eliminates flickering during sash dragging. Fixes https://github.com/eclipse-platform/eclipse.platform.swt/issues/1726 --- .../org/eclipse/swt/custom/SashForm.java | 3 ++ .../Eclipse SWT PI/win32/library/os_custom.c | 42 ++++++++++++++++++- .../org/eclipse/swt/internal/win32/OS.java | 2 + .../org/eclipse/swt/widgets/Composite.java | 36 ++++++++++++---- 4 files changed, 75 insertions(+), 8 deletions(-) diff --git a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/SashForm.java b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/SashForm.java index 23a9874a20d..29098bd39a6 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/SashForm.java +++ b/bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common/org/eclipse/swt/custom/SashForm.java @@ -287,9 +287,12 @@ void onDragSash(Event event) { ((SashFormData)data2).weight = (((long)b2.height << 16) + area.height - 1) / area.height; } if (correction || (event.doit && event.detail != SWT.DRAG)) { + boolean isWin32 = "win32".equals(SWT.getPlatform()); + if (isWin32) setLayoutDeferred(true); c1.setBounds(b1); sash.setBounds(event.x, event.y, event.width, event.height); c2.setBounds(b2); + if (isWin32) setLayoutDeferred(false); } } /** diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/win32/library/os_custom.c b/bundles/org.eclipse.swt/Eclipse SWT PI/win32/library/os_custom.c index 1e8ac836b8a..35eeeb0900b 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/win32/library/os_custom.c +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/win32/library/os_custom.c @@ -439,7 +439,47 @@ JNIEXPORT jlong JNICALL OS_NATIVE(DPI_1AWARENESS_1CONTEXT_1UNAWARE_1GDISCALED) #else rc = 0; #endif - OS_NATIVE_EXIT(env, that, DPI_1AWARENESS_1CONTEXT_1UNAWARE_1GDISCALED_FUNC); + +#ifndef NO_DeferWindowPos__J_3J_3I_3I_3I_3I_3I +JNIEXPORT jlong JNICALL OS_NATIVE(DeferWindowPos__J_3J_3I_3I_3I_3I_3I) + (JNIEnv *env, jclass that, jlong arg0, jlongArray arg1, jintArray arg2, jintArray arg3, jintArray arg4, jintArray arg5, jintArray arg6) +{ + jlong *lparg1=NULL; + jint *lparg2=NULL; + jint *lparg3=NULL; + jint *lparg4=NULL; + jint *lparg5=NULL; + jint *lparg6=NULL; + jsize len = 0; + jlong rc = 0; + int i; + + OS_NATIVE_ENTER(env, that, DeferWindowPos__J_3J_3I_3I_3I_3I_3I_FUNC); + + if (arg1) if ((lparg1 = (*env)->GetLongArrayElements(env, arg1, NULL)) == NULL) goto fail; + if (arg2) if ((lparg2 = (*env)->GetIntArrayElements(env, arg2, NULL)) == NULL) goto fail; + if (arg3) if ((lparg3 = (*env)->GetIntArrayElements(env, arg3, NULL)) == NULL) goto fail; + if (arg4) if ((lparg4 = (*env)->GetIntArrayElements(env, arg4, NULL)) == NULL) goto fail; + if (arg5) if ((lparg5 = (*env)->GetIntArrayElements(env, arg5, NULL)) == NULL) goto fail; + if (arg6) if ((lparg6 = (*env)->GetIntArrayElements(env, arg6, NULL)) == NULL) goto fail; + + len = (*env)->GetArrayLength(env, arg1); + rc = arg0; + + for (i = 0; i < len; i++) { + if (rc == 0) break; + rc = (jlong)DeferWindowPos((HDWP)rc, (HWND)lparg1[i], (HWND)0, lparg2[i], lparg3[i], lparg4[i], lparg5[i], lparg6[i]); + } + +fail: + if (arg6 && lparg6) (*env)->ReleaseIntArrayElements(env, arg6, lparg6, 0); + if (arg5 && lparg5) (*env)->ReleaseIntArrayElements(env, arg5, lparg5, 0); + if (arg4 && lparg4) (*env)->ReleaseIntArrayElements(env, arg4, lparg4, 0); + if (arg3 && lparg3) (*env)->ReleaseIntArrayElements(env, arg3, lparg3, 0); + if (arg2 && lparg2) (*env)->ReleaseIntArrayElements(env, arg2, lparg2, 0); + if (arg1 && lparg1) (*env)->ReleaseLongArrayElements(env, arg1, lparg1, 0); + + OS_NATIVE_EXIT(env, that, DeferWindowPos__J_3J_3I_3I_3I_3I_3I_FUNC); return rc; } #endif diff --git a/bundles/org.eclipse.swt/Eclipse SWT PI/win32/org/eclipse/swt/internal/win32/OS.java b/bundles/org.eclipse.swt/Eclipse SWT PI/win32/org/eclipse/swt/internal/win32/OS.java index 200c280610b..558e793e72c 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT PI/win32/org/eclipse/swt/internal/win32/OS.java +++ b/bundles/org.eclipse.swt/Eclipse SWT PI/win32/org/eclipse/swt/internal/win32/OS.java @@ -2532,6 +2532,8 @@ public static int HRESULT_FROM_WIN32(int x) { * @param hWndInsertAfter cast=(HWND) */ public static final native long DeferWindowPos (long hWinPosInfo, long hWnd, long hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags); +/** @method flags=no_gen */ +public static final native long DeferWindowPos (long hWinPosInfo, long[] hWnd, int[] X, int[] Y, int[] cx, int[] cy, int[] uFlags); /** * @param hWnd cast=(HWND) * @param wParam cast=(WPARAM) diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Composite.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Composite.java index 10646461f88..1e34fd978b0 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Composite.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Composite.java @@ -977,13 +977,35 @@ boolean resizeChildren (boolean defer, WINDOWPOS [] pwp) { if (defer) { hdwp = OS.BeginDeferWindowPos (pwp.length); if (hdwp == 0) return false; - } - for (WINDOWPOS wp : pwp) { - if (wp != null) { - if (defer) { - hdwp = OS.DeferWindowPos (hdwp, wp.hwnd, 0, wp.x, wp.y, wp.cx, wp.cy, wp.flags); - if (hdwp == 0) return false; - } else { + int count = 0; + for (WINDOWPOS wp : pwp) { + if (wp != null) count++; + } + if (count > 0) { + long [] hwnds = new long [count]; + int [] xs = new int [count]; + int [] ys = new int [count]; + int [] cxs = new int [count]; + int [] cys = new int [count]; + int [] flags = new int [count]; + int index = 0; + for (WINDOWPOS wp : pwp) { + if (wp != null) { + hwnds [index] = wp.hwnd; + xs [index] = wp.x; + ys [index] = wp.y; + cxs [index] = wp.cx; + cys [index] = wp.cy; + flags [index] = wp.flags; + index++; + } + } + hdwp = OS.DeferWindowPos (hdwp, hwnds, xs, ys, cxs, cys, flags); + if (hdwp == 0) return false; + } + } else { + for (WINDOWPOS wp : pwp) { + if (wp != null) { OS.SetWindowPos (wp.hwnd, 0, wp.x, wp.y, wp.cx, wp.cy, wp.flags); } } From a42e67e348196a4db5e4f7e3a10d2c22447a6a56 Mon Sep 17 00:00:00 2001 From: Lars Vogel Date: Tue, 27 Jan 2026 17:33:38 +0100 Subject: [PATCH 2/2] [Tests] Add tests for Composite deferred layout and SashForm --- .../junit/Test_CompositeDeferredLayout.java | 85 +++++++++++++++++++ .../Test_org_eclipse_swt_custom_SashForm.java | 74 ++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100644 tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_CompositeDeferredLayout.java create mode 100644 tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_SashForm.java diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_CompositeDeferredLayout.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_CompositeDeferredLayout.java new file mode 100644 index 00000000000..ab1b488c461 --- /dev/null +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_CompositeDeferredLayout.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright (c) 2024 IBM Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.tests.junit; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Shell; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class Test_CompositeDeferredLayout { + + public Shell shell; + public Composite composite; + + @BeforeEach + public void setUp() { + shell = new Shell(); + composite = new Composite(shell, SWT.NONE); + } + + @Test + public void test_deferredLayout_movesChildren() { + int numChildren = 5; + Button[] buttons = new Button[numChildren]; + for (int i = 0; i < numChildren; i++) { + buttons[i] = new Button(composite, SWT.PUSH); + buttons[i].setBounds(0, 0, 10, 10); + } + + composite.setLayoutDeferred(true); + + for (int i = 0; i < numChildren; i++) { + buttons[i].setBounds(10 * i, 10 * i, 20, 20); + } + + composite.setLayoutDeferred(false); + + for (int i = 0; i < numChildren; i++) { + Rectangle bounds = buttons[i].getBounds(); + assertEquals(new Rectangle(10 * i, 10 * i, 20, 20), bounds, "Button " + i + " bounds incorrect after deferred layout"); + } + } + + @Test + public void test_resizeChildren_batching() { + // This test indirectly targets the batching optimization in Composite.resizeChildren + // by verifying that a large number of children are moved correctly. + int numChildren = 100; + Button[] buttons = new Button[numChildren]; + for (int i = 0; i < numChildren; i++) { + buttons[i] = new Button(composite, SWT.PUSH); + buttons[i].setBounds(0, 0, 10, 10); + } + + composite.setLayoutDeferred(true); + + for (int i = 0; i < numChildren; i++) { + buttons[i].setBounds(i, i, 15, 15); + } + + // This call triggers resizeChildren with the deferred logic + composite.setLayoutDeferred(false); + + for (int i = 0; i < numChildren; i++) { + Rectangle bounds = buttons[i].getBounds(); + assertEquals(new Rectangle(i, i, 15, 15), bounds, "Button " + i + " bounds incorrect after large batch deferred layout"); + } + } +} diff --git a/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_SashForm.java b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_SashForm.java new file mode 100644 index 00000000000..9fb3b1bd66e --- /dev/null +++ b/tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_custom_SashForm.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2024 IBM Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.tests.junit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.SashForm; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Shell; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class Test_org_eclipse_swt_custom_SashForm { + + public Shell shell; + public SashForm sashForm; + + @BeforeEach + public void setUp() { + shell = new Shell(); + sashForm = new SashForm(shell, SWT.HORIZONTAL); + shell.setSize(400, 400); + } + + @Test + public void test_initialLayout() { + Button b1 = new Button(sashForm, SWT.PUSH); + Button b2 = new Button(sashForm, SWT.PUSH); + sashForm.setWeights(new int[] {50, 50}); + + shell.open(); // Trigger layout + + Rectangle bounds1 = b1.getBounds(); + Rectangle bounds2 = b2.getBounds(); + + assertTrue(bounds1.width > 0, "Button 1 width should be > 0"); + assertTrue(bounds2.width > 0, "Button 2 width should be > 0"); + assertEquals(bounds1.width, bounds2.width, 5, "Buttons should have approximately equal width"); // Allow small tolerance + } + + @Test + public void test_layoutDeferredDuringResize() { + // This test verifies that we can set layout deferred on SashForm + // (simulating what happens internally on Windows during drag) + // and that the layout updates correctly afterwards. + Button b1 = new Button(sashForm, SWT.PUSH); + Button b2 = new Button(sashForm, SWT.PUSH); + sashForm.setWeights(new int[] {50, 50}); + shell.open(); + + Rectangle initialBounds1 = b1.getBounds(); + + sashForm.setLayoutDeferred(true); + sashForm.setSize(600, 400); // Resize the sash form + sashForm.setLayoutDeferred(false); // Should trigger layout update + + Rectangle newBounds1 = b1.getBounds(); + assertTrue(newBounds1.width > initialBounds1.width, "Button 1 should have grown after SashForm resize"); + } +}