Skip to content

Commit a0a0485

Browse files
amartya4256HeikoKlare
authored andcommitted
Use monitor-aware coordinates in multi-zoom coordinate system
This commit contributes to the use of monitor-aware Points and Rectangles for the translation between points and pixels coordinates in the Display Coordinate System. Since the Display Coordinate System can have different scales (zoom) in different monitors, it is designed to be not continuous in the points coordinates. Hence when we manipulate the coordinates of a Point or a Rectangle object, it might end up in a region which is between two monitors in the point coordinate system, which we consider a gap. So, we need the context of the monitor on which those points and rectangles were created in the first place to evaluate the scaling factor. If the context is not available or the coordinates were updated to an irrelevant value, a fallback method tries to evaluate the right monitor for the coordinates and evaluates the scaled value with that. Contributes to #62 and #127
1 parent d441a95 commit a0a0485

File tree

6 files changed

+335
-100
lines changed

6 files changed

+335
-100
lines changed

bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/widgets/CoordinateSystemMapperTests.java

Lines changed: 96 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
package org.eclipse.swt.widgets;
1515

1616
import static org.junit.jupiter.api.Assertions.assertEquals;
17+
import static org.junit.jupiter.api.Assertions.assertTrue;
1718

1819
import java.util.function.*;
1920
import java.util.stream.*;
@@ -39,62 +40,104 @@ private Monitor createMonitor(CoordinateSystemMapper mapper, Rectangle boundsInP
3940
return monitor;
4041
}
4142

42-
void setupMonitors(CoordinateSystemMapper mapper) {
43+
private void setupMonitors(CoordinateSystemMapper mapper) {
4344
Rectangle boundsInPixelsForLeftMonitor = new Rectangle(0, 0, 2000, 2000);
4445
Rectangle boundsInPixelsForRightMonitor = new Rectangle(2000, 0, 2000, 2000);
4546
monitors = new Monitor[] { createMonitor(mapper, boundsInPixelsForLeftMonitor, 200),
4647
createMonitor(mapper, boundsInPixelsForRightMonitor, 100) };
4748
}
4849

49-
Stream<CoordinateSystemMapper> provideCoordinateSystemMappers() {
50-
return Stream.of(new MultiZoomCoordinateSystemMapper(null, () -> monitors),
51-
new SingleZoomCoordinateSystemMapper(null));
50+
private Stream<CoordinateSystemMapper> provideCoordinateSystemMappers() {
51+
return Stream.of(getMultiZoomCoordinateSystemMapper(), getSingleZoomCoordinateSystemMapper());
52+
}
53+
54+
private MultiZoomCoordinateSystemMapper getMultiZoomCoordinateSystemMapper() {
55+
return new MultiZoomCoordinateSystemMapper(null, () -> monitors);
56+
}
57+
58+
private SingleZoomCoordinateSystemMapper getSingleZoomCoordinateSystemMapper() {
59+
return new SingleZoomCoordinateSystemMapper(null);
5260
}
5361

5462
@ParameterizedTest
5563
@MethodSource("provideCoordinateSystemMappers")
5664
void translatePointInNoMonitorBackAndForthShouldBeTheSame(CoordinateSystemMapper mapper) {
5765
setupMonitors(mapper);
58-
Point pt = new Point(5000, -400);
66+
Point pt = createExpectedPoint(mapper, 5000, -400, monitors[0]);
5967
Point px = mapper.translateToDisplayCoordinates(pt, monitors[0].getZoom());
6068
assertEquals(pt, mapper.translateFromDisplayCoordinates(px, monitors[0].getZoom()));
6169
}
6270

63-
@ParameterizedTest
64-
@MethodSource("provideCoordinateSystemMappers")
65-
@Disabled("Disabled due to current limitations of MultiZoomCoordinateSystemMapper")
66-
void translatePointInGapBackAndForthShouldBeTheSame(CoordinateSystemMapper mapper) {
71+
@Test
72+
void translatePointInGapBackAndForthInSingleZoomShouldBeTheSame() {
73+
SingleZoomCoordinateSystemMapper mapper = getSingleZoomCoordinateSystemMapper();
6774
setupMonitors(mapper);
6875
Point pt = new Point(1900, 400);
6976
Point px = mapper.translateToDisplayCoordinates(pt, monitors[0].getZoom());
7077
assertEquals(pt, mapper.translateFromDisplayCoordinates(px, monitors[0].getZoom()));
7178
}
7279

80+
@Test
81+
void translatePointInGapBackAndForthInMultiZoomShouldEndInsideTheSameMonitor() {
82+
MultiZoomCoordinateSystemMapper mapper = getMultiZoomCoordinateSystemMapper();
83+
setupMonitors(mapper);
84+
Point pt = new Point(1900, 400);
85+
Point px = mapper.translateToDisplayCoordinates(pt, monitors[0].getZoom());
86+
Point translatedPt = mapper.translateFromDisplayCoordinates(px, monitors[0].getZoom());
87+
Point translatedPx = mapper.translateToDisplayCoordinates(translatedPt, monitors[0].getZoom());
88+
assertEquals(new Point(translatedPt.x, translatedPt.y), translatedPx);
89+
assertEquals(translatedPx, px);
90+
}
91+
7392
@ParameterizedTest
7493
@MethodSource("provideCoordinateSystemMappers")
7594
void translateRectangleInNoMonitorBackAndForthShouldBeTheSame(CoordinateSystemMapper mapper) {
7695
setupMonitors(mapper);
77-
Rectangle rectInPts = new Rectangle(5000, -400, 200, 200);
96+
Rectangle rectInPts = createExpectedRectangle(mapper, 5000, -400, 200, 200, monitors[0]);
7897
Rectangle rectInPxs = mapper.translateToDisplayCoordinates(rectInPts, monitors[0].getZoom());
7998
assertEquals(rectInPts, mapper.translateFromDisplayCoordinates(rectInPxs, monitors[0].getZoom()));
8099
}
81100

82-
@ParameterizedTest
83-
@MethodSource("provideCoordinateSystemMappers")
84-
@Disabled("Disabled due to current limitations of MultiZoomCoordinateSystemMapper")
85-
void translateRectangleInGapBackAndForthShouldBeTheSame(CoordinateSystemMapper mapper) {
101+
@Test
102+
void translateRectangleInGapBackAndForthInSingleZoomShouldBeTheSame() {
103+
SingleZoomCoordinateSystemMapper mapper = getSingleZoomCoordinateSystemMapper();
86104
setupMonitors(mapper);
87105
Rectangle rectInPts = new Rectangle(1800, 400, 100, 100);
88106
Rectangle rectInPxs = mapper.translateToDisplayCoordinates(rectInPts, monitors[0].getZoom());
89107
assertEquals(rectInPts, mapper.translateFromDisplayCoordinates(rectInPxs, monitors[0].getZoom()));
90108
}
91109

92-
@ParameterizedTest
93-
@MethodSource("provideCoordinateSystemMappers")
94-
@Disabled("Disabled due to current limitations of MultiZoomCoordinateSystemMapper")
95-
void translateRectangleInGapPartiallyInRightBackAndForthShouldBeTheSame(CoordinateSystemMapper mapper) {
110+
@Test
111+
void translateRectangleInGapBackAndForthInMultiZoomShouldBeInMonitorBounds() {
112+
MultiZoomCoordinateSystemMapper mapper = getMultiZoomCoordinateSystemMapper();
113+
setupMonitors(mapper);
114+
Rectangle rectInPts = new Rectangle(1800, 400, 100, 100);
115+
Rectangle rectInPxs = mapper.translateToDisplayCoordinates(rectInPts, monitors[0].getZoom());
116+
Rectangle rectInPtsTranslated = mapper.translateFromDisplayCoordinates(rectInPxs, monitors[0].getZoom());
117+
boolean isInsideMonitor = false;
118+
for (Monitor monitor : monitors) {
119+
if (monitor.getClientArea().intersects(rectInPtsTranslated)) {
120+
isInsideMonitor = true;
121+
break;
122+
}
123+
}
124+
assertTrue(isInsideMonitor, "The translated rectangle in points is inside the monitor bounds in points");
125+
}
126+
127+
@Test
128+
void translateRectangleInGapPartiallyInRightBackAndForthInSingleZoomShouldBeTheSame() {
129+
SingleZoomCoordinateSystemMapper mapper = getSingleZoomCoordinateSystemMapper();
96130
setupMonitors(mapper);
97-
Rectangle rectInPts = new Rectangle(1950, 400, 100, 100);
131+
Rectangle rectInPts = new Rectangle(1950, 400, 150, 100);
132+
Rectangle rectInPxs = mapper.translateToDisplayCoordinates(rectInPts, monitors[0].getZoom());
133+
assertEquals(rectInPts, mapper.translateFromDisplayCoordinates(rectInPxs, monitors[0].getZoom()));
134+
}
135+
136+
@Test
137+
void translateRectangleInGapPartiallyInRightBackAndForthInMultiZoomShouldBeInside() {
138+
MultiZoomCoordinateSystemMapper mapper = getMultiZoomCoordinateSystemMapper();
139+
setupMonitors(mapper);
140+
Rectangle rectInPts = new MonitorAwareRectangle(1950, 400, 150, 100, monitors[1]);
98141
Rectangle rectInPxs = mapper.translateToDisplayCoordinates(rectInPts, monitors[0].getZoom());
99142
assertEquals(rectInPts, mapper.translateFromDisplayCoordinates(rectInPxs, monitors[0].getZoom()));
100143
}
@@ -103,24 +146,35 @@ void translateRectangleInGapPartiallyInRightBackAndForthShouldBeTheSame(Coordina
103146
@MethodSource("provideCoordinateSystemMappers")
104147
void translateRectangleInGapPartiallyInLeftBackAndForthShouldBeTheSame(CoordinateSystemMapper mapper) {
105148
setupMonitors(mapper);
106-
Rectangle rectInPts = new Rectangle(750, 400, 100, 100);
149+
Rectangle rectInPts = createExpectedRectangle(mapper, 750, 400, 100, 100, monitors[0]);
107150
Rectangle rectInPxs = mapper.translateToDisplayCoordinates(rectInPts, monitors[0].getZoom());
108151
assertEquals(rectInPts, mapper.translateFromDisplayCoordinates(rectInPxs, monitors[0].getZoom()));
109152
}
110153

111-
@ParameterizedTest
112-
@MethodSource("provideCoordinateSystemMappers")
113-
void translateRectangleInPointsInBothMonitorsPartiallyBackAndForthShouldBeTheSame(CoordinateSystemMapper mapper) {
154+
@Test
155+
void translateRectangleInPointsInBothMonitorsPartiallyBackAndForthInSingleZoomShouldBeTheSame() {
156+
SingleZoomCoordinateSystemMapper mapper = getSingleZoomCoordinateSystemMapper();
114157
setupMonitors(mapper);
115158
Rectangle rectInPts = new Rectangle(950, 400, 1500, 100);
116159
Rectangle rectInPxs = mapper.translateToDisplayCoordinates(rectInPts, monitors[0].getZoom());
117160
assertEquals(rectInPts, mapper.translateFromDisplayCoordinates(rectInPxs, monitors[0].getZoom()));
118161
}
119162

120163
@Test
121-
@Disabled("Disabled due to current limitations of MultiZoomCoordinateSystemMapper")
164+
void translateRectangleInPointsInBothMonitorsPartiallyBackAndForthInMultiZoomShouldNotEndUpInGap() {
165+
MultiZoomCoordinateSystemMapper mapper = getMultiZoomCoordinateSystemMapper();
166+
setupMonitors(mapper);
167+
Rectangle rectInPts = new Rectangle(950, 400, 1500, 100);
168+
Rectangle rectInPxs = mapper.translateToDisplayCoordinates(rectInPts, monitors[0].getZoom());
169+
Rectangle rectInPtsTranslated = mapper.translateFromDisplayCoordinates(rectInPxs, monitors[0].getZoom());
170+
Rectangle rectInPxsTranslated = mapper.translateToDisplayCoordinates(rectInPtsTranslated,
171+
monitors[0].getZoom());
172+
assertEquals(rectInPxs, rectInPxsTranslated);
173+
}
174+
175+
@Test
122176
void moveRectangleInPixelsInRightMonitorsPartiallyBackAndForthShouldBeTheSame() {
123-
CoordinateSystemMapper mapper = provideCoordinateSystemMappers().findFirst().get();
177+
MultiZoomCoordinateSystemMapper mapper = getMultiZoomCoordinateSystemMapper();
124178
setupMonitors(mapper);
125179
Rectangle rectInPxs = new Rectangle(1990, -10, 2000, 2000);
126180
Rectangle expectedSmallRectInPxs = new Rectangle(0, 0, 0, monitors[0].getZoom());
@@ -140,10 +194,9 @@ void moveRectangleInPixelsInRightMonitorsPartiallyBackAndForthShouldBeTheSame()
140194

141195
@ParameterizedTest
142196
@MethodSource("provideCoordinateSystemMappers")
143-
@Disabled("Disabled due to current limitations of MultiZoomCoordinateSystemMapper")
144197
void translateRectangleInPixelsOutisdeMonitorsBackAndForthShouldBeTheSame(CoordinateSystemMapper mapper) {
145198
setupMonitors(mapper);
146-
Rectangle rectInPxs = new Rectangle(4400, 400, 1000, 1000);
199+
Rectangle rectInPxs = new Rectangle(400, 2400, 1000, 1000);
147200
Rectangle rectInPts = mapper.translateFromDisplayCoordinates(rectInPxs, monitors[0].getZoom());
148201
assertEquals(rectInPxs, mapper.translateToDisplayCoordinates(rectInPts, monitors[0].getZoom()));
149202
}
@@ -157,4 +210,20 @@ void translateRectangleInPixelsInBothMonitorsBackAndForthShouldBeTheSame(Coordin
157210
assertEquals(rectInPxs, mapper.translateToDisplayCoordinates(rectInPts, monitors[0].getZoom()));
158211
}
159212

213+
private Point createExpectedPoint(CoordinateSystemMapper mapper, int x, int y, Monitor monitor) {
214+
if (mapper instanceof SingleZoomCoordinateSystemMapper) {
215+
return new Point(x, y);
216+
} else {
217+
return new MonitorAwarePoint(x, y, monitor);
218+
}
219+
}
220+
221+
private Rectangle createExpectedRectangle(CoordinateSystemMapper mapper, int x, int y, int width, int height, Monitor monitor) {
222+
if (mapper instanceof SingleZoomCoordinateSystemMapper) {
223+
return new Rectangle(x, y, width, height);
224+
} else {
225+
return new MonitorAwareRectangle(x, y, width, height, monitor);
226+
}
227+
}
228+
160229
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Yatta Solutions and others.
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.graphics;
15+
16+
import java.util.*;
17+
18+
import org.eclipse.swt.widgets.*;
19+
20+
/**
21+
* Instances of this class represent {@link org.eclipse.swt.graphics.Point}
22+
* objects along with the context of the monitor in relation to which they are
23+
* placed on the display. The monitor awareness makes it easy to scale and
24+
* translate the points between pixels and points.
25+
*
26+
* @since 3.129
27+
* @noreference This class is not intended to be referenced by clients
28+
*/
29+
public final class MonitorAwarePoint extends Point {
30+
31+
private static final long serialVersionUID = 6077427420686999194L;
32+
33+
private final Monitor monitor;
34+
35+
/**
36+
* Constructs a new MonitorAwarePoint
37+
*
38+
* @param x the x coordinate of the point
39+
* @param y the y coordinate of the point
40+
* @param monitor the monitor with whose context the point is created
41+
*/
42+
public MonitorAwarePoint(int x, int y, Monitor monitor) {
43+
super(x, y);
44+
this.monitor = monitor;
45+
}
46+
47+
/**
48+
* {@return the monitor with whose context the instance is created}
49+
*/
50+
public Monitor getMonitor() {
51+
return monitor;
52+
}
53+
54+
@Override
55+
public boolean equals(Object object) {
56+
if (this == object) {
57+
return true;
58+
}
59+
if (!super.equals(object)) {
60+
return false;
61+
}
62+
MonitorAwarePoint other = (MonitorAwarePoint) object;
63+
return Objects.equals(this.monitor, other.monitor);
64+
}
65+
66+
@Override
67+
public int hashCode() {
68+
return Objects.hash(super.hashCode(), monitor);
69+
}
70+
71+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Yatta Solutions and others.
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.graphics;
15+
16+
import java.util.*;
17+
18+
import org.eclipse.swt.widgets.*;
19+
20+
/**
21+
* Instances of this class represent {@link org.eclipse.swt.graphics.Rectangle}
22+
* objects along with the context of the monitor in relation to which they are
23+
* placed on the display. The monitor awareness makes it easy to scale and
24+
* translate the rectangles between pixels and points.
25+
*
26+
* @since 3.129
27+
* @noreference This class is not intended to be referenced by clients
28+
*/
29+
public final class MonitorAwareRectangle extends Rectangle {
30+
31+
private static final long serialVersionUID = 5041911840525116925L;
32+
33+
private final Monitor monitor;
34+
35+
/**
36+
* Constructs a new MonitorAwareRectangle
37+
*
38+
* @param x the x coordinate of the top left corner of the rectangle
39+
* @param y the y coordinate of the top left corner of the rectangle
40+
* @param width the width of the rectangle
41+
* @param height the height of the rectangle
42+
* @param monitor the monitor with whose context the rectangle is created
43+
*/
44+
public MonitorAwareRectangle(int x, int y, int width, int height, Monitor monitor) {
45+
super(x, y, width, height);
46+
this.monitor = monitor;
47+
}
48+
49+
/**
50+
* {@return the monitor with whose context the instance is created}
51+
*/
52+
public Monitor getMonitor() {
53+
return monitor;
54+
}
55+
56+
@Override
57+
public boolean equals(Object object) {
58+
if (this == object) {
59+
return true;
60+
}
61+
if (!super.equals(object)) {
62+
return false;
63+
}
64+
MonitorAwareRectangle other = (MonitorAwareRectangle) object;
65+
return Objects.equals(this.monitor, other.monitor);
66+
}
67+
68+
@Override
69+
public int hashCode() {
70+
return Objects.hash(super.hashCode(), monitor);
71+
}
72+
73+
}

bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/graphics/Point.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
* @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
4242
*/
4343

44-
public final class Point implements Serializable {
44+
public sealed class Point implements Serializable permits MonitorAwarePoint {
4545

4646
/**
4747
* the x coordinate of the point
@@ -78,9 +78,17 @@ public Point (int x, int y) {
7878
*/
7979
@Override
8080
public boolean equals (Object object) {
81-
if (object == this) return true;
82-
if (!(object instanceof Point p)) return false;
83-
return (p.x == this.x) && (p.y == this.y);
81+
if (object == null) {
82+
return false;
83+
}
84+
if (object == this) {
85+
return true;
86+
}
87+
if (object.getClass() != this.getClass()) {
88+
return false;
89+
}
90+
Point other = (Point) object;
91+
return (other.x == this.x) && (other.y == this.y);
8492
}
8593

8694
/**

0 commit comments

Comments
 (0)