Skip to content

Commit ae0153c

Browse files
amartya4256akoch-yatta
authored andcommitted
[win32] make DPI_CHANGED event handling async
With this commit, all the widgets scale themselves asynchronously independent of the order saving the wait time over their children to scale.
1 parent 2fde7cc commit ae0153c

File tree

5 files changed

+131
-47
lines changed

5 files changed

+131
-47
lines changed

bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/graphics/GCWin32Tests.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@
2828
class GCWin32Tests {
2929

3030
@Test
31-
public void gcZoomLevelMustChangeOnShellZoomChange() {
31+
public void gcZoomLevelMustChangeOnShellZoomChange() throws Exception {
3232
checkGcZoomLevelOnCanvas(DPIUtil.getNativeDeviceZoom());
3333
checkGcZoomLevelOnCanvas(DPIUtil.getNativeDeviceZoom()*2);
3434
}
3535

36-
private void checkGcZoomLevelOnCanvas(int expectedZoom) {
36+
private void checkGcZoomLevelOnCanvas(int expectedZoom) throws Exception {
3737
Display display = Display.getDefault();
3838
Shell shell = new Shell(display);
3939
CompletableFuture<Integer> gcNativeZoom = new CompletableFuture<>();
@@ -47,7 +47,8 @@ private void checkGcZoomLevelOnCanvas(int expectedZoom) {
4747

4848
DPITestUtil.changeDPIZoom(shell, expectedZoom);
4949
canvas.update();
50-
assertEquals("GCData must have a zoom level equal to the actual zoom level of the widget/shell", expectedZoom, (int) gcNativeZoom.join());
50+
int returnedZoom = (int) gcNativeZoom.get(10000, TimeUnit.SECONDS);
51+
assertEquals("GCData must have a zoom level equal to the actual zoom level of the widget/shell", expectedZoom, returnedZoom);
5152
shell.dispose();
5253
}
5354

bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/internal/DPITestUtil.java

Lines changed: 0 additions & 34 deletions
This file was deleted.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2024 Yatta Solutions
3+
*
4+
* This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Public License 2.0
6+
* which accompanies this distribution, and is available at
7+
* https://www.eclipse.org/legal/epl-2.0/
8+
*
9+
* SPDX-License-Identifier: EPL-2.0
10+
*
11+
* Contributors:
12+
* Yatta Solutions - initial API and implementation
13+
*******************************************************************************/
14+
package org.eclipse.swt.widgets;
15+
16+
import java.time.*;
17+
import java.util.concurrent.atomic.*;
18+
19+
import org.eclipse.swt.internal.*;
20+
import org.eclipse.swt.widgets.Control.*;
21+
22+
public final class DPITestUtil {
23+
24+
private DPITestUtil() {
25+
}
26+
27+
private static final int TIMEOUT_MILLIS = 10000;
28+
29+
public static void changeDPIZoom (Shell shell, int nativeZoom) {
30+
DPIUtil.setDeviceZoom(nativeZoom);
31+
Event event = shell.createZoomChangedEvent(nativeZoom);
32+
shell.sendZoomChangedEvent(event, shell);
33+
DPIChangeExecution data = (DPIChangeExecution) event.data;
34+
waitForDPIChange(shell, TIMEOUT_MILLIS, data.taskCount);
35+
}
36+
37+
private static void waitForDPIChange(Shell shell, int timeout, AtomicInteger scalingCounter) {
38+
waitForPassCondition(shell, timeout, scalingCounter);
39+
}
40+
41+
private static void waitForPassCondition(Shell shell, int timeout, AtomicInteger scalingCounter) {
42+
final Instant timeOut = Instant.now().plusMillis(timeout);
43+
final Display display = shell == null ? Display.getDefault() : shell.getDisplay();
44+
45+
while (Instant.now().isBefore(timeOut) && scalingCounter.get() != 0) {
46+
if (!display.isDisposed()) {
47+
display.readAndDispatch();
48+
}
49+
}
50+
}
51+
}

bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Composite.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1977,7 +1977,7 @@ public String toString() {
19771977
void handleDPIChange(Event event, float scalingFactor) {
19781978
super.handleDPIChange(event, scalingFactor);
19791979
for (Control child : getChildren()) {
1980-
child.notifyListeners(SWT.ZoomChanged, event);
1980+
child.sendZoomChangedEvent(event, getShell());
19811981
}
19821982
}
19831983
}

bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Control.java

Lines changed: 75 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717

1818
import java.util.*;
19+
import java.util.concurrent.atomic.*;
1920
import java.util.stream.*;
2021

2122
import org.eclipse.swt.*;
@@ -78,6 +79,9 @@ public abstract class Control extends Widget implements Drawable {
7879
Font font;
7980
int drawCount, foreground, background, backgroundAlpha = 255;
8081

82+
/** Cache for currently processed DPI change event to be able to cancel it if a new one is triggered */
83+
Event currentDpiChangeEvent;
84+
8185
/**
8286
* Prevents uninitialized instances from being created outside the package.
8387
*/
@@ -4760,7 +4764,10 @@ public boolean setParent (Composite parent) {
47604764
if (parent.nativeZoom != nativeZoom) {
47614765
int newZoom = parent.nativeZoom;
47624766
Event zoomChangedEvent = createZoomChangedEvent(newZoom);
4763-
notifyListeners(SWT.ZoomChanged, zoomChangedEvent);
4767+
if (currentDpiChangeEvent != null) {
4768+
currentDpiChangeEvent.doit = false;
4769+
}
4770+
sendZoomChangedEvent(zoomChangedEvent, getShell());
47644771
}
47654772
int flags = OS.SWP_NOSIZE | OS.SWP_NOMOVE | OS.SWP_NOACTIVATE;
47664773
OS.SetWindowPos (topHandle, OS.HWND_BOTTOM, 0, 0, 0, 0, flags);
@@ -4953,19 +4960,24 @@ LRESULT WM_DESTROY (long wParam, long lParam) {
49534960
return null;
49544961
}
49554962

4956-
void handleMonitorSpecificDpiChange(int newNativeZoom, Rectangle newBoundsInPixels) {
4963+
private void handleMonitorSpecificDpiChange(int newNativeZoom, Rectangle newBoundsInPixels) {
49574964
DPIUtil.setDeviceZoom (newNativeZoom);
49584965
Event zoomChangedEvent = createZoomChangedEvent(newNativeZoom);
4966+
if (currentDpiChangeEvent != null) {
4967+
currentDpiChangeEvent.doit = false;
4968+
}
4969+
currentDpiChangeEvent = zoomChangedEvent;
49594970
notifyListeners(SWT.ZoomChanged, zoomChangedEvent);
49604971
this.setBoundsInPixels(newBoundsInPixels.x, newBoundsInPixels.y, newBoundsInPixels.width, newBoundsInPixels.height);
49614972
}
49624973

4963-
private Event createZoomChangedEvent(int zoom) {
4974+
Event createZoomChangedEvent(int zoom) {
49644975
Event event = new Event();
49654976
event.type = SWT.ZoomChanged;
49664977
event.widget = this;
49674978
event.detail = zoom;
49684979
event.doit = true;
4980+
event.data = new DPIChangeExecution();
49694981
return event;
49704982
}
49714983

@@ -4974,12 +4986,10 @@ LRESULT WM_DPICHANGED (long wParam, long lParam) {
49744986
int newNativeZoom = DPIUtil.mapDPIToZoom (OS.HIWORD (wParam));
49754987
if (getDisplay().isRescalingAtRuntime()) {
49764988
Device.win32_destroyUnusedHandles(getDisplay());
4977-
if (newNativeZoom != nativeZoom) {
4978-
RECT rect = new RECT ();
4979-
COM.MoveMemory(rect, lParam, RECT.sizeof);
4980-
handleMonitorSpecificDpiChange(newNativeZoom, new Rectangle(rect.left, rect.top, rect.right - rect.left, rect.bottom-rect.top));
4981-
return LRESULT.ZERO;
4982-
}
4989+
RECT rect = new RECT ();
4990+
COM.MoveMemory(rect, lParam, RECT.sizeof);
4991+
handleMonitorSpecificDpiChange(newNativeZoom, new Rectangle(rect.left, rect.top, rect.right - rect.left, rect.bottom-rect.top));
4992+
return LRESULT.ZERO;
49834993
} else {
49844994
int newZoom = DPIUtil.getZoomForAutoscaleProperty (newNativeZoom);
49854995
int oldZoom = DPIUtil.getZoomForAutoscaleProperty (nativeZoom);
@@ -5879,6 +5889,62 @@ LRESULT wmScrollChild (long wParam, long lParam) {
58795889
return null;
58805890
}
58815891

5892+
static class DPIChangeExecution {
5893+
AtomicInteger taskCount = new AtomicInteger();
5894+
private boolean asyncExec = true;
5895+
5896+
private void process(Control control, Runnable operation) {
5897+
boolean currentAsyncExec = asyncExec;
5898+
if (control instanceof Composite comp) {
5899+
// do not execute the DPI change asynchronously, if there is no
5900+
// layout manager available otherwise size calculations could lead
5901+
// to wrong results, because no final layout will be triggered
5902+
asyncExec &= (comp.layout != null);
5903+
}
5904+
if (asyncExec) {
5905+
control.getDisplay().asyncExec(operation::run);
5906+
} else {
5907+
operation.run();
5908+
}
5909+
// resetting it prevents to break asynchronous execution when the synchronous
5910+
// DPI change handling is finished
5911+
asyncExec = currentAsyncExec;
5912+
}
5913+
5914+
private void increment() {
5915+
taskCount.incrementAndGet();
5916+
}
5917+
5918+
private boolean decrement() {
5919+
return taskCount.decrementAndGet() <= 0;
5920+
}
5921+
}
5922+
5923+
void sendZoomChangedEvent(Event event, Shell shell) {
5924+
this.currentDpiChangeEvent = event;
5925+
if (event.data instanceof DPIChangeExecution dpiExecData) {
5926+
dpiExecData.increment();
5927+
dpiExecData.process(this, () -> {
5928+
try {
5929+
if (!this.isDisposed() && event.doit) {
5930+
notifyListeners(SWT.ZoomChanged, event);
5931+
}
5932+
} finally {
5933+
if (shell.isDisposed()) {
5934+
return;
5935+
}
5936+
if (dpiExecData.decrement()) {
5937+
if (event == currentDpiChangeEvent) {
5938+
currentDpiChangeEvent = null;
5939+
}
5940+
if (event.doit) {
5941+
shell.WM_SIZE(0, 0);
5942+
}
5943+
}
5944+
}
5945+
});
5946+
}
5947+
}
58825948

58835949
@Override
58845950
void handleDPIChange(Event event, float scalingFactor) {

0 commit comments

Comments
 (0)