diff --git a/maven/core-unittests/src/test/java/com/codename1/charts/ChartComponentTest.java b/maven/core-unittests/src/test/java/com/codename1/charts/ChartComponentTest.java index 84c2e952d3..411ee7f40e 100644 --- a/maven/core-unittests/src/test/java/com/codename1/charts/ChartComponentTest.java +++ b/maven/core-unittests/src/test/java/com/codename1/charts/ChartComponentTest.java @@ -11,10 +11,12 @@ import com.codename1.charts.views.AbstractChart; import com.codename1.charts.views.ClickableArea; import com.codename1.charts.views.XYChart; +import com.codename1.impl.CodenameOneImplementation; import com.codename1.test.UITestBase; import com.codename1.ui.Transform; import com.codename1.ui.geom.Rectangle; import com.codename1.ui.geom.Shape; +import com.codename1.testing.TestCodenameOneImplementation; import org.junit.jupiter.api.Test; import java.lang.reflect.Field; @@ -23,6 +25,13 @@ import static org.junit.jupiter.api.Assertions.*; class ChartComponentTest extends UITestBase { + private TestCodenameOneImplementation testImplementation; + + @Override + protected CodenameOneImplementation createImplementation() { + testImplementation = new TestCodenameOneImplementation(); + return testImplementation; + } @Test void constructorCopiesPanAndZoomSettingsFromXYChart() { XYMultipleSeriesRenderer renderer = new XYMultipleSeriesRenderer(); @@ -59,18 +68,30 @@ void shapeConversionsTranslateBetweenSpaces() throws Exception { ChartComponent component = new PositionedChartComponent(chart, 10, 15); Rectangle screenRect = new Rectangle(component.getAbsoluteX(), component.getAbsoluteY(), 40, 50); - Rectangle chartBounds = component.screenToChartShape(screenRect).getBounds(); - assertEquals(0, chartBounds.getX()); - assertEquals(0, chartBounds.getY()); - assertEquals(0, chartBounds.getSize().getWidth()); - assertEquals(0, chartBounds.getSize().getHeight()); + Shape chartShape = component.screenToChartShape(screenRect); + Rectangle chartBounds = chartShape.getBounds(); + Shape roundTripScreenShape = component.chartToScreenShape(chartShape); + Rectangle roundTripScreenBounds = roundTripScreenShape.getBounds(); + assertEquals(screenRect.getX(), roundTripScreenBounds.getX()); + assertEquals(screenRect.getY(), roundTripScreenBounds.getY()); + assertEquals(screenRect.getWidth(), chartBounds.getWidth()); + assertEquals(screenRect.getHeight(), chartBounds.getHeight()); + assertEquals(screenRect.getWidth(), roundTripScreenBounds.getWidth()); + assertEquals(screenRect.getHeight(), roundTripScreenBounds.getHeight()); Rectangle chartRect = new Rectangle(0, 0, 40, 50); Rectangle screenBounds = component.chartToScreenShape(chartRect).getBounds(); - assertEquals(0, screenBounds.getX()); - assertEquals(0, screenBounds.getY()); - assertEquals(0, screenBounds.getSize().getWidth()); - assertEquals(0, screenBounds.getSize().getHeight()); + assertEquals(component.getAbsoluteX(), screenBounds.getX()); + assertEquals(component.getAbsoluteY(), screenBounds.getY()); + assertEquals(40, screenBounds.getWidth()); + assertEquals(50, screenBounds.getHeight()); + + Shape roundTripChartShape = component.screenToChartShape(component.chartToScreenShape(chartRect)); + Rectangle roundTripChartBounds = roundTripChartShape.getBounds(); + assertEquals(chartRect.getX(), roundTripChartBounds.getX()); + assertEquals(chartRect.getY(), roundTripChartBounds.getY()); + assertEquals(chartRect.getWidth(), roundTripChartBounds.getWidth()); + assertEquals(chartRect.getHeight(), roundTripChartBounds.getHeight()); } @Test diff --git a/maven/core-unittests/src/test/java/com/codename1/test/UITestBase.java b/maven/core-unittests/src/test/java/com/codename1/test/UITestBase.java index adfa6f8998..24577c832a 100644 --- a/maven/core-unittests/src/test/java/com/codename1/test/UITestBase.java +++ b/maven/core-unittests/src/test/java/com/codename1/test/UITestBase.java @@ -3,6 +3,7 @@ import com.codename1.impl.CodenameOneImplementation; import com.codename1.io.Util; import com.codename1.plugin.PluginSupport; +import com.codename1.testing.TestCodenameOneImplementation; import com.codename1.ui.Display; import com.codename1.ui.Graphics; import com.codename1.ui.plaf.UIManager; @@ -38,41 +39,8 @@ protected void setUpDisplay() throws Exception { display = Display.getInstance(); resetUIManager(); - implementation = mock(CodenameOneImplementation.class); - final Object defaultFont = new Object(); - when(implementation.getDisplayWidth()).thenReturn(1080); - when(implementation.getDisplayHeight()).thenReturn(1920); - when(implementation.getActualDisplayHeight()).thenReturn(1920); - when(implementation.getDeviceDensity()).thenReturn(Display.DENSITY_MEDIUM); - when(implementation.convertToPixels(anyInt(), anyBoolean())).thenAnswer(invocation -> invocation.getArgument(0)); - when(implementation.createFont(anyInt(), anyInt(), anyInt())).thenReturn(defaultFont); - when(implementation.getDefaultFont()).thenReturn(defaultFont); - when(implementation.isTrueTypeSupported()).thenReturn(true); - when(implementation.isLookupFontSupported()).thenReturn(true); - when(implementation.isInitialized()).thenReturn(true); - when(implementation.getCommandBehavior()).thenReturn(Display.COMMAND_BEHAVIOR_DEFAULT); - when(implementation.isNativeFontSchemeSupported()).thenReturn(true); - when(implementation.loadTrueTypeFont(anyString(), anyString())).thenReturn(defaultFont); - when(implementation.deriveTrueTypeFont(any(), anyFloat(), anyInt())).thenReturn(defaultFont); - when(implementation.loadNativeFont(anyString())).thenReturn(defaultFont); - when(implementation.getNativeGraphics()).thenReturn(new Object()); - when(implementation.paintNativePeersBehind()).thenReturn(false); - when(implementation.handleEDTException(any(Throwable.class))).thenReturn(false); - when(implementation.charWidth(any(), anyChar())).thenReturn(8); - when(implementation.stringWidth(any(), anyString())).thenAnswer(invocation -> { - String text = (String) invocation.getArgument(1); - return text == null ? 0 : text.length() * 8; - }); - when(implementation.charsWidth(any(), any(char[].class), anyInt(), anyInt())).thenAnswer(invocation -> { - Integer length = (Integer) invocation.getArgument(3); - return length == null ? 0 : Math.max(0, length) * 8; - }); - when(implementation.getHeight(any())).thenReturn(16); - when(implementation.getPlatformName()).thenReturn("and"); - when(implementation.getProperty(anyString(), anyString())).thenAnswer(invocation -> (String) invocation.getArgument(1)); - when(implementation.loadTrueTypeFont(anyString(), anyString())).thenAnswer(invocation -> new Object()); - when(implementation.deriveTrueTypeFont(any(), anyFloat(), anyInt())).thenAnswer(invocation -> new Object()); - when(implementation.loadNativeFont(anyString())).thenAnswer(invocation -> new Object()); + implementation = createImplementation(); + configureImplementation(implementation); pluginSupport = new PluginSupport(); @@ -113,15 +81,75 @@ private void resetUIManager() throws Exception { } private Graphics createGraphics() throws Exception { + Object nativeGraphics = implementation != null ? implementation.getNativeGraphics() : null; + if (nativeGraphics == null) { + nativeGraphics = new Object(); + } java.lang.reflect.Constructor constructor = Graphics.class.getDeclaredConstructor(Object.class); constructor.setAccessible(true); - Graphics graphics = constructor.newInstance(new Object()); + Graphics graphics = constructor.newInstance(nativeGraphics); Field paintPeersField = Graphics.class.getDeclaredField("paintPeersBehind"); paintPeersField.setAccessible(true); paintPeersField.setBoolean(graphics, false); return graphics; } + protected CodenameOneImplementation createImplementation() { + return mock(CodenameOneImplementation.class); + } + + protected void configureImplementation(CodenameOneImplementation implementation) { + if (implementation instanceof TestCodenameOneImplementation) { + configureTestImplementation((TestCodenameOneImplementation) implementation); + } else { + configureMockImplementation(implementation); + } + } + + private void configureMockImplementation(CodenameOneImplementation implementation) { + final Object defaultFont = new Object(); + when(implementation.getDisplayWidth()).thenReturn(1080); + when(implementation.getDisplayHeight()).thenReturn(1920); + when(implementation.getActualDisplayHeight()).thenReturn(1920); + when(implementation.getDeviceDensity()).thenReturn(Display.DENSITY_MEDIUM); + when(implementation.convertToPixels(anyInt(), anyBoolean())).thenAnswer(invocation -> invocation.getArgument(0)); + when(implementation.createFont(anyInt(), anyInt(), anyInt())).thenReturn(defaultFont); + when(implementation.getDefaultFont()).thenReturn(defaultFont); + when(implementation.isTrueTypeSupported()).thenReturn(true); + when(implementation.isLookupFontSupported()).thenReturn(true); + when(implementation.isInitialized()).thenReturn(true); + when(implementation.getCommandBehavior()).thenReturn(Display.COMMAND_BEHAVIOR_DEFAULT); + when(implementation.isNativeFontSchemeSupported()).thenReturn(true); + when(implementation.loadTrueTypeFont(anyString(), anyString())).thenReturn(defaultFont); + when(implementation.deriveTrueTypeFont(any(), anyFloat(), anyInt())).thenReturn(defaultFont); + when(implementation.loadNativeFont(anyString())).thenReturn(defaultFont); + when(implementation.getNativeGraphics()).thenReturn(new Object()); + when(implementation.paintNativePeersBehind()).thenReturn(false); + when(implementation.handleEDTException(any(Throwable.class))).thenReturn(false); + when(implementation.charWidth(any(), anyChar())).thenReturn(8); + when(implementation.stringWidth(any(), anyString())).thenAnswer(invocation -> { + String text = (String) invocation.getArgument(1); + return text == null ? 0 : text.length() * 8; + }); + when(implementation.charsWidth(any(), any(char[].class), anyInt(), anyInt())).thenAnswer(invocation -> { + Integer length = (Integer) invocation.getArgument(3); + return length == null ? 0 : Math.max(0, length) * 8; + }); + when(implementation.getHeight(any())).thenReturn(16); + when(implementation.getPlatformName()).thenReturn("and"); + when(implementation.getProperty(anyString(), anyString())).thenAnswer(invocation -> (String) invocation.getArgument(1)); + when(implementation.loadTrueTypeFont(anyString(), anyString())).thenAnswer(invocation -> new Object()); + when(implementation.deriveTrueTypeFont(any(), anyFloat(), anyInt())).thenAnswer(invocation -> new Object()); + when(implementation.loadNativeFont(anyString())).thenAnswer(invocation -> new Object()); + } + + protected void configureTestImplementation(TestCodenameOneImplementation implementation) { + implementation.setDisplaySize(1080, 1920); + implementation.setDeviceDensity(Display.DENSITY_MEDIUM); + implementation.setTouchDevice(true); + implementation.setTimeoutSupported(true); + } + /** * Processes any pending serial calls that were queued via {@link Display#callSerially(Runnable)}. */ diff --git a/maven/core-unittests/src/test/java/com/codename1/testing/TestCodenameOneImplementation.java b/maven/core-unittests/src/test/java/com/codename1/testing/TestCodenameOneImplementation.java index f4bc53e551..b0dc64d185 100644 --- a/maven/core-unittests/src/test/java/com/codename1/testing/TestCodenameOneImplementation.java +++ b/maven/core-unittests/src/test/java/com/codename1/testing/TestCodenameOneImplementation.java @@ -10,9 +10,12 @@ import com.codename1.media.MediaRecorderBuilder; import com.codename1.ui.Display; import com.codename1.ui.Form; +import com.codename1.ui.Stroke; import com.codename1.ui.TextArea; import com.codename1.ui.TextField; import com.codename1.ui.util.ImageIO; +import com.codename1.ui.geom.Rectangle; +import com.codename1.ui.geom.Shape; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -44,6 +47,15 @@ public class TestCodenameOneImplementation extends CodenameOneImplementation { private boolean timeoutSupported; private boolean timeoutInvoked; private int timeoutValue; + private boolean translationSupported; + private boolean translateInvoked; + private boolean shapeSupported; + private boolean drawShapeInvoked; + private boolean fillShapeInvoked; + private Shape lastClipShape; + private Shape lastDrawShape; + private Shape lastFillShape; + private Stroke lastDrawStroke; private String[] accessPointIds = new String[0]; private final Map accessPointTypes = new HashMap<>(); private final Map accessPointNames = new HashMap<>(); @@ -128,6 +140,150 @@ public void resetTimeoutTracking() { timeoutValue = 0; } + public void setTranslationSupported(boolean translationSupported) { + this.translationSupported = translationSupported; + } + + public boolean wasTranslateInvoked() { + return translateInvoked; + } + + public void resetTranslateTracking() { + translateInvoked = false; + } + + public void setShapeSupported(boolean shapeSupported) { + this.shapeSupported = shapeSupported; + } + + public boolean wasDrawShapeInvoked() { + return drawShapeInvoked; + } + + public boolean wasFillShapeInvoked() { + return fillShapeInvoked; + } + + public Shape getLastClipShape() { + return lastClipShape; + } + + public Shape getLastDrawShape() { + return lastDrawShape; + } + + public Shape getLastFillShape() { + return lastFillShape; + } + + public Stroke getLastDrawStroke() { + return lastDrawStroke; + } + + public void resetShapeTracking() { + drawShapeInvoked = false; + fillShapeInvoked = false; + lastDrawShape = null; + lastFillShape = null; + lastDrawStroke = null; + } + + public void resetClipTracking() { + lastClipShape = null; + } + + @Override + public boolean isTransformSupported() { + return true; + } + + @Override + public boolean isTransformSupported(Object graphics) { + return true; + } + + @Override + public Object makeTransformIdentity() { + return new TestTransform(); + } + + @Override + public void setTransformIdentity(Object transform) { + ((TestTransform) transform).setIdentity(); + } + + @Override + public Object makeTransformTranslation(float translateX, float translateY, float translateZ) { + TestTransform transform = new TestTransform(); + transform.setTranslation(translateX, translateY, translateZ); + return transform; + } + + @Override + public void setTransformTranslation(Object transform, float translateX, float translateY, float translateZ) { + ((TestTransform) transform).setTranslation(translateX, translateY, translateZ); + } + + @Override + public Object makeTransformScale(float scaleX, float scaleY, float scaleZ) { + TestTransform transform = new TestTransform(); + transform.setScale(scaleX, scaleY, scaleZ); + return transform; + } + + @Override + public void setTransformScale(Object transform, float scaleX, float scaleY, float scaleZ) { + ((TestTransform) transform).setScale(scaleX, scaleY, scaleZ); + } + + @Override + public Object makeTransformAffine(double m00, double m10, double m01, double m11, double m02, double m12) { + TestTransform transform = new TestTransform(); + transform.setAffine((float) m00, (float) m01, (float) m02, (float) m10, (float) m11, (float) m12); + return transform; + } + + @Override + public Object makeTransformInverse(Object nativeTransform) { + return ((TestTransform) nativeTransform).createInverse(); + } + + @Override + public void setTransformInverse(Object nativeTransform) { + ((TestTransform) nativeTransform).invert(); + } + + @Override + public void concatenateTransform(Object left, Object right) { + ((TestTransform) left).concatenate((TestTransform) right); + } + + @Override + public void copyTransform(Object src, Object dest) { + ((TestTransform) dest).copyFrom((TestTransform) src); + } + + @Override + public boolean transformNativeEqualsImpl(Object t1, Object t2) { + if (t1 == t2) { + return true; + } + if (t1 == null || t2 == null) { + return false; + } + return ((TestTransform) t1).equals((TestTransform) t2); + } + + @Override + public void transformPoint(Object nativeTransform, float[] in, float[] out) { + if (nativeTransform == null) { + int len = Math.min(in.length, out.length); + System.arraycopy(in, 0, out, 0, len); + return; + } + ((TestTransform) nativeTransform).transformPoint(in, out); + } + // ----------------------------------------------------------------- // CodenameOneImplementation abstract methods // ----------------------------------------------------------------- @@ -256,6 +412,29 @@ public boolean isTouchDevice() { return touchDevice; } + @Override + public boolean isTranslationSupported() { + return translationSupported; + } + + @Override + public void translate(Object graphics, int x, int y) { + translateInvoked = true; + TestGraphics g = (TestGraphics) graphics; + g.translateX += x; + g.translateY += y; + } + + @Override + public int getTranslateX(Object graphics) { + return ((TestGraphics) graphics).translateX; + } + + @Override + public int getTranslateY(Object graphics) { + return ((TestGraphics) graphics).translateY; + } + @Override public int getColor(Object graphics) { return ((TestGraphics) graphics).color; @@ -301,6 +480,17 @@ public int getClipHeight(Object graphics) { return ((TestGraphics) graphics).clipHeight; } + @Override + public void setClip(Object graphics, Shape shape) { + lastClipShape = shape; + if (shape == null) { + setClip(graphics, 0, 0, getDisplayWidth(), getDisplayHeight()); + return; + } + Rectangle bounds = shape.getBounds(); + setClip(graphics, bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight()); + } + @Override public void setClip(Object graphics, int x, int y, int width, int height) { TestGraphics g = (TestGraphics) graphics; @@ -323,6 +513,24 @@ public void clipRect(Object graphics, int x, int y, int width, int height) { g.clipHeight = newH; } + @Override + public boolean isShapeSupported(Object nativeGraphics) { + return shapeSupported; + } + + @Override + public void drawShape(Object graphics, Shape shape, Stroke stroke) { + drawShapeInvoked = true; + lastDrawShape = shape; + lastDrawStroke = stroke; + } + + @Override + public void fillShape(Object graphics, Shape shape) { + fillShapeInvoked = true; + lastFillShape = shape; + } + @Override public void drawLine(Object graphics, int x1, int y1, int x2, int y2) { } @@ -407,6 +615,31 @@ public Object createFont(int face, int style, int size) { return new TestFont(defaultFont.charWidth, defaultFont.height); } + @Override + public Object loadTrueTypeFont(String fontName, String fileName) { + return new TestFont(defaultFont.charWidth, defaultFont.height); + } + + @Override + public Object deriveTrueTypeFont(Object font, float size, int weight) { + return new TestFont(defaultFont.charWidth, defaultFont.height); + } + + @Override + public Object loadNativeFont(String lookup) { + return new TestFont(defaultFont.charWidth, defaultFont.height); + } + + @Override + public boolean isTrueTypeSupported() { + return true; + } + + @Override + public boolean isNativeFontSchemeSupported() { + return true; + } + @Override public Object connect(String url, boolean read, boolean write) throws IOException { TestConnection connection = connections.computeIfAbsent(url, TestConnection::new); @@ -743,6 +976,8 @@ public static final class TestGraphics { int clipY; int clipWidth; int clipHeight; + int translateX; + int translateY; TestFont font; TestGraphics(int width, int height) { @@ -751,6 +986,174 @@ public static final class TestGraphics { } } + private static final class TestTransform { + private float m00; + private float m01; + private float m02; + private float m10; + private float m11; + private float m12; + private float m20; + private float m21; + private float m22; + private float translateZ; + + TestTransform() { + setIdentity(); + } + + void setIdentity() { + m00 = 1f; + m01 = 0f; + m02 = 0f; + m10 = 0f; + m11 = 1f; + m12 = 0f; + m20 = 0f; + m21 = 0f; + m22 = 1f; + translateZ = 0f; + } + + void setTranslation(float tx, float ty, float tz) { + setIdentity(); + m02 = tx; + m12 = ty; + translateZ = tz; + } + + void setScale(float sx, float sy, float sz) { + setIdentity(); + m00 = sx; + m11 = sy; + m22 = sz; + } + + void setAffine(float nm00, float nm01, float nm02, float nm10, float nm11, float nm12) { + m00 = nm00; + m01 = nm01; + m02 = nm02; + m10 = nm10; + m11 = nm11; + m12 = nm12; + m20 = 0f; + m21 = 0f; + m22 = 1f; + translateZ = 0f; + } + + void copyFrom(TestTransform other) { + m00 = other.m00; + m01 = other.m01; + m02 = other.m02; + m10 = other.m10; + m11 = other.m11; + m12 = other.m12; + m20 = other.m20; + m21 = other.m21; + m22 = other.m22; + translateZ = other.translateZ; + } + + TestTransform createInverse() { + TestTransform inverse = new TestTransform(); + inverse.copyFrom(this); + inverse.invert(); + return inverse; + } + + void invert() { + float det = m00 * m11 - m01 * m10; + if (Math.abs(det) < 1.0e-6f) { + setIdentity(); + return; + } + float invDet = 1f / det; + float nm00 = m11 * invDet; + float nm01 = -m01 * invDet; + float nm02 = (m01 * m12 - m11 * m02) * invDet; + float nm10 = -m10 * invDet; + float nm11 = m00 * invDet; + float nm12 = (m10 * m02 - m00 * m12) * invDet; + m00 = nm00; + m01 = nm01; + m02 = nm02; + m10 = nm10; + m11 = nm11; + m12 = nm12; + translateZ = -translateZ; + } + + void concatenate(TestTransform right) { + float nm00 = m00 * right.m00 + m01 * right.m10; + float nm01 = m00 * right.m01 + m01 * right.m11; + float nm02 = m00 * right.m02 + m01 * right.m12 + m02; + float nm10 = m10 * right.m00 + m11 * right.m10; + float nm11 = m10 * right.m01 + m11 * right.m11; + float nm12 = m10 * right.m02 + m11 * right.m12 + m12; + float nm20 = m20 * right.m00 + m21 * right.m10 + m22 * right.m20; + float nm21 = m20 * right.m01 + m21 * right.m11 + m22 * right.m21; + float nm22 = m20 * right.m02 + m21 * right.m12 + m22 * right.m22; + m00 = nm00; + m01 = nm01; + m02 = nm02; + m10 = nm10; + m11 = nm11; + m12 = nm12; + m20 = nm20; + m21 = nm21; + m22 = nm22; + translateZ = translateZ + right.translateZ; + } + + void transformPoint(float[] in, float[] out) { + float x = in[0]; + float y = in[1]; + out[0] = m00 * x + m01 * y + m02; + out[1] = m10 * x + m11 * y + m12; + if (in.length > 2 && out.length > 2) { + float z = in[2]; + out[2] = m22 * z + translateZ; + } + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof TestTransform)) { + return false; + } + TestTransform other = (TestTransform) obj; + return Float.compare(m00, other.m00) == 0 + && Float.compare(m01, other.m01) == 0 + && Float.compare(m02, other.m02) == 0 + && Float.compare(m10, other.m10) == 0 + && Float.compare(m11, other.m11) == 0 + && Float.compare(m12, other.m12) == 0 + && Float.compare(m20, other.m20) == 0 + && Float.compare(m21, other.m21) == 0 + && Float.compare(m22, other.m22) == 0 + && Float.compare(translateZ, other.translateZ) == 0; + } + + @Override + public int hashCode() { + int result = Float.floatToIntBits(m00); + result = 31 * result + Float.floatToIntBits(m01); + result = 31 * result + Float.floatToIntBits(m02); + result = 31 * result + Float.floatToIntBits(m10); + result = 31 * result + Float.floatToIntBits(m11); + result = 31 * result + Float.floatToIntBits(m12); + result = 31 * result + Float.floatToIntBits(m20); + result = 31 * result + Float.floatToIntBits(m21); + result = 31 * result + Float.floatToIntBits(m22); + result = 31 * result + Float.floatToIntBits(translateZ); + return result; + } + } + public static final class TestFont { final int charWidth; final int height; diff --git a/maven/core-unittests/src/test/java/com/codename1/ui/GraphicsTest.java b/maven/core-unittests/src/test/java/com/codename1/ui/GraphicsTest.java new file mode 100644 index 0000000000..9e0c355c27 --- /dev/null +++ b/maven/core-unittests/src/test/java/com/codename1/ui/GraphicsTest.java @@ -0,0 +1,169 @@ +package com.codename1.ui; + +import com.codename1.impl.CodenameOneImplementation; +import com.codename1.test.UITestBase; +import com.codename1.testing.TestCodenameOneImplementation; +import com.codename1.ui.geom.Rectangle; +import com.codename1.ui.geom.Rectangle2D; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class GraphicsTest extends UITestBase { + + private Graphics graphics; + private Object nativeGraphics; + private TestCodenameOneImplementation testImplementation; + + @Override + protected CodenameOneImplementation createImplementation() { + testImplementation = new TestCodenameOneImplementation(); + return testImplementation; + } + + @BeforeEach + void setupGraphics() throws Exception { + graphics = createGraphics(); + nativeGraphics = getNativeGraphics(graphics); + testImplementation.resetTranslateTracking(); + testImplementation.resetShapeTracking(); + testImplementation.resetClipTracking(); + testImplementation.setTranslationSupported(false); + testImplementation.setShapeSupported(false); + } + + @Test + void testTranslateWhenTranslationUnsupportedTracksOffsetsLocally() { + graphics.translate(5, 7); + assertFalse(testImplementation.wasTranslateInvoked()); + assertEquals(5, graphics.getTranslateX()); + assertEquals(7, graphics.getTranslateY()); + } + + @Test + void testTranslateWhenTranslationSupportedDelegatesToImplementation() { + testImplementation.setTranslationSupported(true); + testImplementation.resetTranslateTracking(); + graphics.translate(3, 4); + assertTrue(testImplementation.wasTranslateInvoked()); + assertEquals(3, graphics.getTranslateX()); + assertEquals(4, graphics.getTranslateY()); + } + + @Test + void testSetColorClearsPaintAndMasksAlpha() { + DummyPaint paint = new DummyPaint(); + graphics.setColor(paint); + assertSame(paint, graphics.getPaint()); + graphics.setColor(0x80FF0000); + assertNull(graphics.getPaint()); + assertEquals(0xFF0000, graphics.getColor()); + assertEquals(0xFF0000, testImplementation.getColor(nativeGraphics)); + } + + @Test + void testSetAndGetColorReturnsPreviousValue() { + graphics.setColor(0x00112233); + int previous = graphics.setAndGetColor(0x00ABCDEF); + assertEquals(0x112233, previous); + assertEquals(0xABCDEF, graphics.getColor()); + } + + @Test + void testSetClipRectangleAppliesTranslation() { + graphics.translate(2, 3); + graphics.setClip(4, 5, 6, 7); + assertEquals(6, testImplementation.getClipX(nativeGraphics)); + assertEquals(8, testImplementation.getClipY(nativeGraphics)); + assertEquals(6, testImplementation.getClipWidth(nativeGraphics)); + assertEquals(7, testImplementation.getClipHeight(nativeGraphics)); + } + + @Test + void testClipRectUsesTranslation() { + graphics.translate(1, 2); + graphics.clipRect(5, 6, 7, 8); + assertEquals(6, testImplementation.getClipX(nativeGraphics)); + assertEquals(8, testImplementation.getClipY(nativeGraphics)); + assertEquals(7, testImplementation.getClipWidth(nativeGraphics)); + assertEquals(8, testImplementation.getClipHeight(nativeGraphics)); + } + + @Test + void testGetClipAccountsForTranslation() { + testImplementation.setClip(nativeGraphics, 15, 25, 30, 40); + graphics.translate(5, 5); + int[] clip = graphics.getClip(); + assertArrayEquals(new int[]{10, 20, 30, 40}, clip); + } + + @Test + void testSetClipWithTranslationWrapsShape() { + graphics.translate(3, 3); + Rectangle rectangle = new Rectangle(0, 0, 10, 10); + graphics.setClip(rectangle); + assertNotSame(rectangle, testImplementation.getLastClipShape()); + } + + @Test + void testDrawShapeDelegatesWhenSupported() { + testImplementation.setShapeSupported(true); + Rectangle rectangle = new Rectangle(0, 0, 5, 5); + Stroke stroke = new Stroke(2, Stroke.CAP_ROUND, Stroke.JOIN_ROUND, 1f); + graphics.drawShape(rectangle, stroke); + assertTrue(testImplementation.wasDrawShapeInvoked()); + assertSame(rectangle, testImplementation.getLastDrawShape()); + assertEquals(stroke, testImplementation.getLastDrawStroke()); + } + + @Test + void testFillShapeWithPaintUsesCustomPaint() { + testImplementation.setShapeSupported(true); + graphics.setColor(new DummyPaint()); + Rectangle rectangle = new Rectangle(0, 0, 10, 20); + graphics.fillShape(rectangle); + assertTrue(((DummyPaint) graphics.getPaint()).wasPainted()); + assertFalse(testImplementation.wasFillShapeInvoked()); + } + + @Test + void testLighterAndDarkerColorAdjustments() { + graphics.setColor(0x00101010); + graphics.lighterColor(0x20); + assertEquals(0x303030, graphics.getColor()); + graphics.darkerColor(0x10); + assertEquals(0x202020, graphics.getColor()); + } + + private Graphics createGraphics() throws Exception { + java.lang.reflect.Constructor constructor = Graphics.class.getDeclaredConstructor(Object.class); + constructor.setAccessible(true); + Object nativeObject = testImplementation.getNativeGraphics(); + return constructor.newInstance(nativeObject); + } + + private Object getNativeGraphics(Graphics g) throws Exception { + java.lang.reflect.Field field = Graphics.class.getDeclaredField("nativeGraphics"); + field.setAccessible(true); + return field.get(g); + } + + private static class DummyPaint implements Paint { + private boolean painted; + + @Override + public void paint(Graphics g, Rectangle2D bounds) { + painted = true; + } + + @Override + public void paint(Graphics g, double x, double y, double w, double h) { + painted = true; + } + + boolean wasPainted() { + return painted; + } + } +} diff --git a/maven/core-unittests/src/test/java/com/codename1/ui/geom/GeneralPathTest.java b/maven/core-unittests/src/test/java/com/codename1/ui/geom/GeneralPathTest.java new file mode 100644 index 0000000000..34ed4d31af --- /dev/null +++ b/maven/core-unittests/src/test/java/com/codename1/ui/geom/GeneralPathTest.java @@ -0,0 +1,165 @@ +package com.codename1.ui.geom; + +import com.codename1.impl.CodenameOneImplementation; +import com.codename1.test.UITestBase; +import com.codename1.testing.TestCodenameOneImplementation; +import com.codename1.ui.Transform; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class GeneralPathTest extends UITestBase { + private TestCodenameOneImplementation testImplementation; + + @Override + protected CodenameOneImplementation createImplementation() { + testImplementation = new TestCodenameOneImplementation(); + return testImplementation; + } + + @Test + void testLineToWithoutMoveThrowsException() { + GeneralPath path = new GeneralPath(); + assertThrows(IndexOutOfBoundsException.class, () -> path.lineTo(1f, 1f)); + } + + @Test + void testMoveToOverwritesPreviousMove() { + GeneralPath path = new GeneralPath(); + path.moveTo(5f, 7f); + assertEquals(1, path.typeSize); + assertEquals(PathIterator.SEG_MOVETO, path.types[0]); + path.moveTo(9f, 11f); + assertEquals(1, path.typeSize); + assertEquals(9f, path.points[0]); + assertEquals(11f, path.points[1]); + } + + @Test + void testAppendWithConnection() { + GeneralPath first = new GeneralPath(); + first.moveTo(0f, 0f); + first.lineTo(1f, 1f); + + GeneralPath second = new GeneralPath(); + second.moveTo(2f, 2f); + second.lineTo(3f, 3f); + + first.append(second, true); + + List segmentTypes = new ArrayList(); + List coords = new ArrayList(); + PathIterator iterator = first.getPathIterator(); + float[] buffer = new float[6]; + while (!iterator.isDone()) { + segmentTypes.add(iterator.currentSegment(buffer)); + coords.add(new float[]{buffer[0], buffer[1]}); + iterator.next(); + } + + assertEquals(4, segmentTypes.size()); + assertEquals(PathIterator.SEG_MOVETO, segmentTypes.get(0).intValue()); + assertArrayEquals(new float[]{0f, 0f}, coords.get(0), 0.0001f); + assertEquals(PathIterator.SEG_LINETO, segmentTypes.get(1).intValue()); + assertArrayEquals(new float[]{1f, 1f}, coords.get(1), 0.0001f); + assertEquals(PathIterator.SEG_LINETO, segmentTypes.get(2).intValue()); + assertArrayEquals(new float[]{2f, 2f}, coords.get(2), 0.0001f); + assertEquals(PathIterator.SEG_LINETO, segmentTypes.get(3).intValue()); + assertArrayEquals(new float[]{3f, 3f}, coords.get(3), 0.0001f); + } + + @Test + void testSetShapeWithTransform() { + Rectangle rectangle = new Rectangle(5, 7, 10, 20); + Transform transform = Transform.makeScale(2f, 3f); + + GeneralPath path = new GeneralPath(); + path.setShape(rectangle, transform); + + Rectangle bounds = path.getBounds(); + assertEquals(10, bounds.getX()); + assertEquals(21, bounds.getY()); + assertEquals(20, bounds.getWidth()); + assertEquals(60, bounds.getHeight()); + assertTrue(path.isRectangle()); + } + + @Test + void testCreateTransformedShapeReturnsScaledCopy() { + GeneralPath original = new GeneralPath(); + original.setRect(new Rectangle(0, 0, 10, 10), null); + + Transform translation = Transform.makeTranslation(5f, 10f); + Shape transformed = original.createTransformedShape(translation); + assertTrue(transformed instanceof GeneralPath); + Rectangle bounds = transformed.getBounds(); + assertEquals(5, bounds.getX()); + assertEquals(10, bounds.getY()); + assertEquals(10, bounds.getWidth()); + assertEquals(10, bounds.getHeight()); + } + + @Test + void testArcProducesExpectedBounds() { + GeneralPath path = new GeneralPath(); + path.arc(100f, 200f, 300f, 400f, 0f, (float) (Math.PI * 2)); + Rectangle bounds = path.getBounds(); + assertEquals(100, bounds.getX()); + assertEquals(200, bounds.getY()); + assertEquals(300, bounds.getWidth()); + assertEquals(400, bounds.getHeight()); + } + + @Test + void testGetCurrentPointAfterClosePath() { + GeneralPath path = new GeneralPath(); + path.setRect(new Rectangle(0, 0, 10, 10), null); + float[] current = path.getCurrentPoint(); + assertArrayEquals(new float[]{0f, 0f}, current, 0.0001f); + } + + @Test + void testResetClearsAllSegments() { + GeneralPath path = new GeneralPath(); + path.moveTo(0f, 0f); + path.lineTo(10f, 0f); + assertNotNull(path.getCurrentPoint()); + path.reset(); + assertNull(path.getCurrentPoint()); + float[] bounds = path.getBounds2D(); + assertArrayEquals(new float[]{0f, 0f, 0f, 0f}, bounds, 0.0001f); + } + + @Test + void testIsRectangleDetection() { + GeneralPath rectanglePath = new GeneralPath(); + rectanglePath.setRect(new Rectangle(0, 0, 5, 5), null); + assertTrue(rectanglePath.isRectangle()); + + GeneralPath curved = new GeneralPath(); + curved.moveTo(0f, 0f); + curved.quadTo(5f, 5f, 10f, 0f); + assertFalse(curved.isRectangle()); + } + + @Test + void testIntersectWithRectangleUpdatesPath() { + GeneralPath path = new GeneralPath(); + path.setRect(new Rectangle(0, 0, 10, 10), null); + + Rectangle intersecting = new Rectangle(5, 5, 10, 10); + assertTrue(path.intersect(intersecting)); + Rectangle bounds = path.getBounds(); + assertEquals(5, bounds.getX()); + assertEquals(5, bounds.getY()); + assertEquals(5, bounds.getWidth()); + assertEquals(5, bounds.getHeight()); + + Rectangle outside = new Rectangle(50, 50, 10, 10); + assertFalse(path.intersect(outside)); + assertNull(path.getCurrentPoint()); + } +} diff --git a/maven/core-unittests/src/test/java/com/codename1/ui/geom/GeometryTest.java b/maven/core-unittests/src/test/java/com/codename1/ui/geom/GeometryTest.java new file mode 100644 index 0000000000..e3cd73c50d --- /dev/null +++ b/maven/core-unittests/src/test/java/com/codename1/ui/geom/GeometryTest.java @@ -0,0 +1,107 @@ +package com.codename1.ui.geom; + +import com.codename1.ui.geom.Geometry.BezierCurve; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class GeometryTest { + + @Test + void testBezierCurveRequiresEvenNumberOfCoordinates() { + assertThrows(IllegalArgumentException.class, () -> new BezierCurve(0d, 0d, 1d)); + } + + @Test + void testCopyConstructorCreatesIndependentCurve() { + BezierCurve original = new BezierCurve(0d, 0d, 5d, 10d, 10d, 0d); + BezierCurve copy = new BezierCurve(original); + assertNotSame(original, copy); + assertEquals(original.getStartPoint().getX(), copy.getStartPoint().getX(), 0.0001); + assertEquals(original.getEndPoint().getY(), copy.getEndPoint().getY(), 0.0001); + } + + @Test + void testExtractBezierCurvesFromPath() { + GeneralPath path = new GeneralPath(); + path.moveTo(0f, 0f); + path.quadTo(10f, 20f, 20f, 0f); + path.curveTo(30f, 30f, 40f, -10f, 50f, 0f); + List curves = new ArrayList(); + BezierCurve.extractBezierCurvesFromPath(path, curves); + assertEquals(2, curves.size()); + BezierCurve first = curves.get(0); + assertEquals(0d, first.getStartPoint().getX(), 0.0001); + assertEquals(0d, first.getStartPoint().getY(), 0.0001); + BezierCurve second = curves.get(1); + assertEquals(20d, second.getStartPoint().getX(), 0.0001); + } + + @Test + void testParametricEvaluationForQuadraticCurve() { + BezierCurve curve = new BezierCurve(0d, 0d, 50d, 100d, 100d, 0d); + assertEquals(0d, curve.x(0d), 0.0001); + assertEquals(50d, curve.x(0.5d), 0.0001); + assertEquals(100d, curve.x(1d), 0.0001); + assertEquals(0d, curve.y(0d), 0.0001); + assertEquals(50d, curve.y(0.5d), 0.0001); + assertEquals(0d, curve.y(1d), 0.0001); + } + + @Test + void testDerivativeCoefficientsForQuadraticCurve() { + BezierCurve curve = new BezierCurve(0d, 0d, 50d, 100d, 100d, 0d); + double[] derivativeX = curve.getDerivativeCoefficientsX(); + assertEquals(100d, derivativeX[0], 0.0001); + assertEquals(0d, derivativeX[1], 0.0001); + assertEquals(0d, derivativeX[2], 0.0001); + double[] derivativeY = curve.getDerivativeCoefficientsY(); + assertEquals(200d, derivativeY[0], 0.0001); + assertEquals(-400d, derivativeY[1], 0.0001); + assertEquals(0d, derivativeY[2], 0.0001); + } + + @Test + void testReverseSwapsStartAndEndPoints() { + BezierCurve curve = new BezierCurve(0d, 0d, 10d, 0d); + BezierCurve reversed = curve.reverse(); + assertEquals(curve.getStartPoint().getX(), reversed.getEndPoint().getX(), 0.0001); + assertEquals(curve.getEndPoint().getY(), reversed.getStartPoint().getY(), 0.0001); + } + + @Test + void testSegmentSplitsCurveIntoTwoParts() { + BezierCurve curve = new BezierCurve(0d, 0d, 50d, 100d, 100d, 0d); + List segments = new ArrayList(); + curve.segment(0.5d, segments); + assertEquals(2, segments.size()); + BezierCurve firstHalf = segments.get(0); + BezierCurve secondHalf = segments.get(1); + assertEquals(curve.getStartPoint().getX(), firstHalf.getStartPoint().getX(), 0.0001); + assertEquals(curve.getEndPoint().getY(), secondHalf.getEndPoint().getY(), 0.0001); + assertEquals(firstHalf.getEndPoint().getX(), secondHalf.getStartPoint().getX(), 0.0001); + } + + @Test + void testBoundingRectIncludesCurveExtrema() { + BezierCurve curve = new BezierCurve(0d, 0d, 50d, 100d, 100d, 0d); + Rectangle2D bounds = curve.getBoundingRect(); + assertEquals(0d, bounds.getX(), 0.0001); + assertEquals(0d, bounds.getY(), 0.0001); + assertEquals(100d, bounds.getWidth(), 0.0001); + assertEquals(50d, bounds.getHeight(), 0.0001); + } + + @Test + void testSegmentWithRectangleOutsideReturnsOriginal() { + BezierCurve curve = new BezierCurve(0d, 0d, 50d, 100d, 100d, 0d); + Rectangle2D rect = new Rectangle2D(200d, 200d, 10d, 10d); + List segments = new ArrayList(); + curve.segment(rect, segments); + assertEquals(1, segments.size()); + assertTrue(curve.equals(segments.get(0), 0.0001)); + } +} diff --git a/maven/core-unittests/src/test/java/com/codename1/ui/geom/RectangleTest.java b/maven/core-unittests/src/test/java/com/codename1/ui/geom/RectangleTest.java new file mode 100644 index 0000000000..cfc77a3894 --- /dev/null +++ b/maven/core-unittests/src/test/java/com/codename1/ui/geom/RectangleTest.java @@ -0,0 +1,109 @@ +package com.codename1.ui.geom; + +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class RectangleTest { + + @Test + void testCreateFromPoolReusesInstances() { + Rectangle first = Rectangle.createFromPool(1, 2, 3, 4); + Rectangle.recycle(first); + Rectangle second = Rectangle.createFromPool(5, 6, 7, 8); + assertSame(first, second); + assertEquals(5, second.getX()); + assertEquals(6, second.getY()); + assertEquals(7, second.getWidth()); + assertEquals(8, second.getHeight()); + Rectangle.recycle(second); + } + + @Test + void testContainsPointAndRectangle() { + Rectangle rect = new Rectangle(0, 0, 10, 20); + assertTrue(rect.contains(5, 5)); + assertTrue(rect.contains(0, 0, 10, 20)); + assertFalse(rect.contains(11, 5)); + Rectangle other = new Rectangle(2, 2, 5, 5); + assertTrue(rect.contains(other)); + } + + @Test + void testStaticContains() { + assertTrue(Rectangle.contains(0, 0, 10, 10, 2, 2, 4, 4)); + assertFalse(Rectangle.contains(0, 0, 5, 5, 3, 3, 4, 4)); + } + + @Test + void testIntersects() { + assertTrue(Rectangle.intersects(0, 0, 10, 10, 5, 5, 10, 10)); + assertFalse(Rectangle.intersects(0, 0, 5, 5, 10, 10, 2, 2)); + Rectangle rect = new Rectangle(0, 0, 10, 10); + assertTrue(rect.intersects(5, 5, 4, 4)); + assertFalse(rect.intersects(20, 20, 2, 2)); + } + + @Test + void testStaticIntersectionPopulatesDestination() { + Rectangle dest = new Rectangle(); + Rectangle.intersection(0, 0, 10, 10, 5, 5, 10, 10, dest); + assertEquals(5, dest.getX()); + assertEquals(5, dest.getY()); + assertEquals(5, dest.getWidth()); + assertEquals(5, dest.getHeight()); + } + + @Test + void testInstanceIntersectionProducesExpectedRectangle() { + Rectangle rect = new Rectangle(0, 0, 10, 10); + Rectangle intersection = rect.intersection(5, 5, 10, 10); + assertEquals(new Rectangle(5, 5, 5, 5), intersection); + } + + @Test + void testIntersectionWithOutputRectangle() { + Rectangle source = new Rectangle(0, 0, 10, 10); + Rectangle input = new Rectangle(8, -5, 10, 10); + Rectangle output = new Rectangle(); + source.intersection(input, output); + assertEquals(8, output.getX()); + assertEquals(0, output.getY()); + assertEquals(2, output.getWidth()); + assertEquals(5, output.getHeight()); + } + + @Test + void testPathIteratorCreatesRectangleShape() { + Rectangle rect = new Rectangle(1, 2, 3, 4); + PathIterator iterator = rect.getPathIterator(); + List segments = new ArrayList(); + float[] coords = new float[6]; + while (!iterator.isDone()) { + segments.add(iterator.currentSegment(coords)); + iterator.next(); + } + assertEquals(5, segments.size()); + assertEquals(PathIterator.SEG_MOVETO, segments.get(0).intValue()); + assertEquals(PathIterator.SEG_CLOSE, segments.get(4).intValue()); + } + + @Test + void testEqualsAndHashCode() { + Rectangle first = new Rectangle(1, 2, 3, 4); + Rectangle second = new Rectangle(1, 2, 3, 4); + assertEquals(first, second); + assertEquals(first.hashCode(), second.hashCode()); + assertNotEquals(first, new Rectangle(2, 2, 3, 4)); + } + + @Test + void testGetBounds2D() { + Rectangle rect = new Rectangle(3, 4, 5, 6); + float[] bounds = rect.getBounds2D(); + assertArrayEquals(new float[]{3f, 4f, 5f, 6f}, bounds, 0.0001f); + } +}