Skip to content

Commit

Permalink
Use monitor-aware coordinates in multi-zoom coordinate system
Browse files Browse the repository at this point in the history
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 eclipse-platform#62 and eclipse-platform#127
  • Loading branch information
amartya4256 authored and HeikoKlare committed Jan 20, 2025
1 parent d441a95 commit a0a0485
Show file tree
Hide file tree
Showing 6 changed files with 335 additions and 100 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package org.eclipse.swt.widgets;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.function.*;
import java.util.stream.*;
Expand All @@ -39,62 +40,104 @@ private Monitor createMonitor(CoordinateSystemMapper mapper, Rectangle boundsInP
return monitor;
}

void setupMonitors(CoordinateSystemMapper mapper) {
private void setupMonitors(CoordinateSystemMapper mapper) {
Rectangle boundsInPixelsForLeftMonitor = new Rectangle(0, 0, 2000, 2000);
Rectangle boundsInPixelsForRightMonitor = new Rectangle(2000, 0, 2000, 2000);
monitors = new Monitor[] { createMonitor(mapper, boundsInPixelsForLeftMonitor, 200),
createMonitor(mapper, boundsInPixelsForRightMonitor, 100) };
}

Stream<CoordinateSystemMapper> provideCoordinateSystemMappers() {
return Stream.of(new MultiZoomCoordinateSystemMapper(null, () -> monitors),
new SingleZoomCoordinateSystemMapper(null));
private Stream<CoordinateSystemMapper> provideCoordinateSystemMappers() {
return Stream.of(getMultiZoomCoordinateSystemMapper(), getSingleZoomCoordinateSystemMapper());
}

private MultiZoomCoordinateSystemMapper getMultiZoomCoordinateSystemMapper() {
return new MultiZoomCoordinateSystemMapper(null, () -> monitors);
}

private SingleZoomCoordinateSystemMapper getSingleZoomCoordinateSystemMapper() {
return new SingleZoomCoordinateSystemMapper(null);
}

@ParameterizedTest
@MethodSource("provideCoordinateSystemMappers")
void translatePointInNoMonitorBackAndForthShouldBeTheSame(CoordinateSystemMapper mapper) {
setupMonitors(mapper);
Point pt = new Point(5000, -400);
Point pt = createExpectedPoint(mapper, 5000, -400, monitors[0]);
Point px = mapper.translateToDisplayCoordinates(pt, monitors[0].getZoom());
assertEquals(pt, mapper.translateFromDisplayCoordinates(px, monitors[0].getZoom()));
}

@ParameterizedTest
@MethodSource("provideCoordinateSystemMappers")
@Disabled("Disabled due to current limitations of MultiZoomCoordinateSystemMapper")
void translatePointInGapBackAndForthShouldBeTheSame(CoordinateSystemMapper mapper) {
@Test
void translatePointInGapBackAndForthInSingleZoomShouldBeTheSame() {
SingleZoomCoordinateSystemMapper mapper = getSingleZoomCoordinateSystemMapper();
setupMonitors(mapper);
Point pt = new Point(1900, 400);
Point px = mapper.translateToDisplayCoordinates(pt, monitors[0].getZoom());
assertEquals(pt, mapper.translateFromDisplayCoordinates(px, monitors[0].getZoom()));
}

@Test
void translatePointInGapBackAndForthInMultiZoomShouldEndInsideTheSameMonitor() {
MultiZoomCoordinateSystemMapper mapper = getMultiZoomCoordinateSystemMapper();
setupMonitors(mapper);
Point pt = new Point(1900, 400);
Point px = mapper.translateToDisplayCoordinates(pt, monitors[0].getZoom());
Point translatedPt = mapper.translateFromDisplayCoordinates(px, monitors[0].getZoom());
Point translatedPx = mapper.translateToDisplayCoordinates(translatedPt, monitors[0].getZoom());
assertEquals(new Point(translatedPt.x, translatedPt.y), translatedPx);
assertEquals(translatedPx, px);
}

@ParameterizedTest
@MethodSource("provideCoordinateSystemMappers")
void translateRectangleInNoMonitorBackAndForthShouldBeTheSame(CoordinateSystemMapper mapper) {
setupMonitors(mapper);
Rectangle rectInPts = new Rectangle(5000, -400, 200, 200);
Rectangle rectInPts = createExpectedRectangle(mapper, 5000, -400, 200, 200, monitors[0]);
Rectangle rectInPxs = mapper.translateToDisplayCoordinates(rectInPts, monitors[0].getZoom());
assertEquals(rectInPts, mapper.translateFromDisplayCoordinates(rectInPxs, monitors[0].getZoom()));
}

@ParameterizedTest
@MethodSource("provideCoordinateSystemMappers")
@Disabled("Disabled due to current limitations of MultiZoomCoordinateSystemMapper")
void translateRectangleInGapBackAndForthShouldBeTheSame(CoordinateSystemMapper mapper) {
@Test
void translateRectangleInGapBackAndForthInSingleZoomShouldBeTheSame() {
SingleZoomCoordinateSystemMapper mapper = getSingleZoomCoordinateSystemMapper();
setupMonitors(mapper);
Rectangle rectInPts = new Rectangle(1800, 400, 100, 100);
Rectangle rectInPxs = mapper.translateToDisplayCoordinates(rectInPts, monitors[0].getZoom());
assertEquals(rectInPts, mapper.translateFromDisplayCoordinates(rectInPxs, monitors[0].getZoom()));
}

@ParameterizedTest
@MethodSource("provideCoordinateSystemMappers")
@Disabled("Disabled due to current limitations of MultiZoomCoordinateSystemMapper")
void translateRectangleInGapPartiallyInRightBackAndForthShouldBeTheSame(CoordinateSystemMapper mapper) {
@Test
void translateRectangleInGapBackAndForthInMultiZoomShouldBeInMonitorBounds() {
MultiZoomCoordinateSystemMapper mapper = getMultiZoomCoordinateSystemMapper();
setupMonitors(mapper);
Rectangle rectInPts = new Rectangle(1800, 400, 100, 100);
Rectangle rectInPxs = mapper.translateToDisplayCoordinates(rectInPts, monitors[0].getZoom());
Rectangle rectInPtsTranslated = mapper.translateFromDisplayCoordinates(rectInPxs, monitors[0].getZoom());
boolean isInsideMonitor = false;
for (Monitor monitor : monitors) {
if (monitor.getClientArea().intersects(rectInPtsTranslated)) {
isInsideMonitor = true;
break;
}
}
assertTrue(isInsideMonitor, "The translated rectangle in points is inside the monitor bounds in points");
}

@Test
void translateRectangleInGapPartiallyInRightBackAndForthInSingleZoomShouldBeTheSame() {
SingleZoomCoordinateSystemMapper mapper = getSingleZoomCoordinateSystemMapper();
setupMonitors(mapper);
Rectangle rectInPts = new Rectangle(1950, 400, 100, 100);
Rectangle rectInPts = new Rectangle(1950, 400, 150, 100);
Rectangle rectInPxs = mapper.translateToDisplayCoordinates(rectInPts, monitors[0].getZoom());
assertEquals(rectInPts, mapper.translateFromDisplayCoordinates(rectInPxs, monitors[0].getZoom()));
}

@Test
void translateRectangleInGapPartiallyInRightBackAndForthInMultiZoomShouldBeInside() {
MultiZoomCoordinateSystemMapper mapper = getMultiZoomCoordinateSystemMapper();
setupMonitors(mapper);
Rectangle rectInPts = new MonitorAwareRectangle(1950, 400, 150, 100, monitors[1]);
Rectangle rectInPxs = mapper.translateToDisplayCoordinates(rectInPts, monitors[0].getZoom());
assertEquals(rectInPts, mapper.translateFromDisplayCoordinates(rectInPxs, monitors[0].getZoom()));
}
Expand All @@ -103,24 +146,35 @@ void translateRectangleInGapPartiallyInRightBackAndForthShouldBeTheSame(Coordina
@MethodSource("provideCoordinateSystemMappers")
void translateRectangleInGapPartiallyInLeftBackAndForthShouldBeTheSame(CoordinateSystemMapper mapper) {
setupMonitors(mapper);
Rectangle rectInPts = new Rectangle(750, 400, 100, 100);
Rectangle rectInPts = createExpectedRectangle(mapper, 750, 400, 100, 100, monitors[0]);
Rectangle rectInPxs = mapper.translateToDisplayCoordinates(rectInPts, monitors[0].getZoom());
assertEquals(rectInPts, mapper.translateFromDisplayCoordinates(rectInPxs, monitors[0].getZoom()));
}

@ParameterizedTest
@MethodSource("provideCoordinateSystemMappers")
void translateRectangleInPointsInBothMonitorsPartiallyBackAndForthShouldBeTheSame(CoordinateSystemMapper mapper) {
@Test
void translateRectangleInPointsInBothMonitorsPartiallyBackAndForthInSingleZoomShouldBeTheSame() {
SingleZoomCoordinateSystemMapper mapper = getSingleZoomCoordinateSystemMapper();
setupMonitors(mapper);
Rectangle rectInPts = new Rectangle(950, 400, 1500, 100);
Rectangle rectInPxs = mapper.translateToDisplayCoordinates(rectInPts, monitors[0].getZoom());
assertEquals(rectInPts, mapper.translateFromDisplayCoordinates(rectInPxs, monitors[0].getZoom()));
}

@Test
@Disabled("Disabled due to current limitations of MultiZoomCoordinateSystemMapper")
void translateRectangleInPointsInBothMonitorsPartiallyBackAndForthInMultiZoomShouldNotEndUpInGap() {
MultiZoomCoordinateSystemMapper mapper = getMultiZoomCoordinateSystemMapper();
setupMonitors(mapper);
Rectangle rectInPts = new Rectangle(950, 400, 1500, 100);
Rectangle rectInPxs = mapper.translateToDisplayCoordinates(rectInPts, monitors[0].getZoom());
Rectangle rectInPtsTranslated = mapper.translateFromDisplayCoordinates(rectInPxs, monitors[0].getZoom());
Rectangle rectInPxsTranslated = mapper.translateToDisplayCoordinates(rectInPtsTranslated,
monitors[0].getZoom());
assertEquals(rectInPxs, rectInPxsTranslated);
}

@Test
void moveRectangleInPixelsInRightMonitorsPartiallyBackAndForthShouldBeTheSame() {
CoordinateSystemMapper mapper = provideCoordinateSystemMappers().findFirst().get();
MultiZoomCoordinateSystemMapper mapper = getMultiZoomCoordinateSystemMapper();
setupMonitors(mapper);
Rectangle rectInPxs = new Rectangle(1990, -10, 2000, 2000);
Rectangle expectedSmallRectInPxs = new Rectangle(0, 0, 0, monitors[0].getZoom());
Expand All @@ -140,10 +194,9 @@ void moveRectangleInPixelsInRightMonitorsPartiallyBackAndForthShouldBeTheSame()

@ParameterizedTest
@MethodSource("provideCoordinateSystemMappers")
@Disabled("Disabled due to current limitations of MultiZoomCoordinateSystemMapper")
void translateRectangleInPixelsOutisdeMonitorsBackAndForthShouldBeTheSame(CoordinateSystemMapper mapper) {
setupMonitors(mapper);
Rectangle rectInPxs = new Rectangle(4400, 400, 1000, 1000);
Rectangle rectInPxs = new Rectangle(400, 2400, 1000, 1000);
Rectangle rectInPts = mapper.translateFromDisplayCoordinates(rectInPxs, monitors[0].getZoom());
assertEquals(rectInPxs, mapper.translateToDisplayCoordinates(rectInPts, monitors[0].getZoom()));
}
Expand All @@ -157,4 +210,20 @@ void translateRectangleInPixelsInBothMonitorsBackAndForthShouldBeTheSame(Coordin
assertEquals(rectInPxs, mapper.translateToDisplayCoordinates(rectInPts, monitors[0].getZoom()));
}

private Point createExpectedPoint(CoordinateSystemMapper mapper, int x, int y, Monitor monitor) {
if (mapper instanceof SingleZoomCoordinateSystemMapper) {
return new Point(x, y);
} else {
return new MonitorAwarePoint(x, y, monitor);
}
}

private Rectangle createExpectedRectangle(CoordinateSystemMapper mapper, int x, int y, int width, int height, Monitor monitor) {
if (mapper instanceof SingleZoomCoordinateSystemMapper) {
return new Rectangle(x, y, width, height);
} else {
return new MonitorAwareRectangle(x, y, width, height, monitor);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*******************************************************************************
* Copyright (c) 2025 Yatta Solutions 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:
* Yatta Solutions - initial API and implementation
*******************************************************************************/
package org.eclipse.swt.graphics;

import java.util.*;

import org.eclipse.swt.widgets.*;

/**
* Instances of this class represent {@link org.eclipse.swt.graphics.Point}
* objects along with the context of the monitor in relation to which they are
* placed on the display. The monitor awareness makes it easy to scale and
* translate the points between pixels and points.
*
* @since 3.129
* @noreference This class is not intended to be referenced by clients
*/
public final class MonitorAwarePoint extends Point {

private static final long serialVersionUID = 6077427420686999194L;

private final Monitor monitor;

/**
* Constructs a new MonitorAwarePoint
*
* @param x the x coordinate of the point
* @param y the y coordinate of the point
* @param monitor the monitor with whose context the point is created
*/
public MonitorAwarePoint(int x, int y, Monitor monitor) {
super(x, y);
this.monitor = monitor;
}

/**
* {@return the monitor with whose context the instance is created}
*/
public Monitor getMonitor() {
return monitor;
}

@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (!super.equals(object)) {
return false;
}
MonitorAwarePoint other = (MonitorAwarePoint) object;
return Objects.equals(this.monitor, other.monitor);
}

@Override
public int hashCode() {
return Objects.hash(super.hashCode(), monitor);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*******************************************************************************
* Copyright (c) 2025 Yatta Solutions 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:
* Yatta Solutions - initial API and implementation
*******************************************************************************/
package org.eclipse.swt.graphics;

import java.util.*;

import org.eclipse.swt.widgets.*;

/**
* Instances of this class represent {@link org.eclipse.swt.graphics.Rectangle}
* objects along with the context of the monitor in relation to which they are
* placed on the display. The monitor awareness makes it easy to scale and
* translate the rectangles between pixels and points.
*
* @since 3.129
* @noreference This class is not intended to be referenced by clients
*/
public final class MonitorAwareRectangle extends Rectangle {

private static final long serialVersionUID = 5041911840525116925L;

private final Monitor monitor;

/**
* Constructs a new MonitorAwareRectangle
*
* @param x the x coordinate of the top left corner of the rectangle
* @param y the y coordinate of the top left corner of the rectangle
* @param width the width of the rectangle
* @param height the height of the rectangle
* @param monitor the monitor with whose context the rectangle is created
*/
public MonitorAwareRectangle(int x, int y, int width, int height, Monitor monitor) {
super(x, y, width, height);
this.monitor = monitor;
}

/**
* {@return the monitor with whose context the instance is created}
*/
public Monitor getMonitor() {
return monitor;
}

@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (!super.equals(object)) {
return false;
}
MonitorAwareRectangle other = (MonitorAwareRectangle) object;
return Objects.equals(this.monitor, other.monitor);
}

@Override
public int hashCode() {
return Objects.hash(super.hashCode(), monitor);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
* @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
*/

public final class Point implements Serializable {
public sealed class Point implements Serializable permits MonitorAwarePoint {

/**
* the x coordinate of the point
Expand Down Expand Up @@ -78,9 +78,17 @@ public Point (int x, int y) {
*/
@Override
public boolean equals (Object object) {
if (object == this) return true;
if (!(object instanceof Point p)) return false;
return (p.x == this.x) && (p.y == this.y);
if (object == null) {
return false;
}
if (object == this) {
return true;
}
if (object.getClass() != this.getClass()) {
return false;
}
Point other = (Point) object;
return (other.x == this.x) && (other.y == this.y);
}

/**
Expand Down
Loading

0 comments on commit a0a0485

Please sign in to comment.