From 716c9f96c16adb6313217bab64fff32e8af4c8f8 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Wed, 26 Nov 2025 06:46:28 +0200 Subject: [PATCH 01/10] Refine desktop window handling --- .../impl/CodenameOneImplementation.java | 49 +++++++ CodenameOne/src/com/codename1/ui/CN.java | 53 +++++++ CodenameOne/src/com/codename1/ui/Display.java | 138 ++++++++++++++++++ .../com/codename1/ui/events/WindowEvent.java | 98 +++++++++++++ .../com/codename1/impl/javase/JavaSEPort.java | 91 +++++++++++- .../TestCodenameOneImplementation.java | 46 ++++++ .../java/com/codename1/ui/WindowApiTest.java | 73 +++++++++ 7 files changed, 544 insertions(+), 4 deletions(-) create mode 100644 CodenameOne/src/com/codename1/ui/events/WindowEvent.java create mode 100644 maven/core-unittests/src/test/java/com/codename1/ui/WindowApiTest.java diff --git a/CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java b/CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java index e24c30f36e..4caf92e5b8 100644 --- a/CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java +++ b/CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java @@ -174,6 +174,7 @@ public abstract class CodenameOneImplementation { */ private final Rectangle paintDirtyTmpRect = new Rectangle(); private BrowserComponent sharedJavascriptContext; + private Dimension initialWindowSizeHintPercent; static void setOnCurrentFormChange(Runnable on) { onCurrentFormChange = on; @@ -467,6 +468,54 @@ public int getActualDisplayHeight() { return getDisplayHeight(); } + /** + * Returns the size of the desktop area hosting the application window when running on a desktop + * platform. Implementations that do not support windows may return {@code null}. + * + * @return the desktop size or {@code null} + */ + public Dimension getDesktopSize() { + return null; + } + + /** + * Returns the bounds of the application window when running on a desktop platform. + * + * @return the window bounds, defaults to the current display size + */ + public Rectangle getWindowBounds() { + return new Rectangle(0, 0, getDisplayWidth(), getDisplayHeight()); + } + + /** + * Requests a resize of the application window when supported by the platform. + * + * @param width the desired window width in pixels + * @param height the desired window height in pixels + */ + public void setWindowSize(int width, int height) { + } + + /** + * Stores an optional window size hint (in percent values) for desktop environments. Implementations + * that do not support windows may ignore this value. + * + * @param hint a {@link Dimension} whose width/height represent percentages of the desktop to use for + * the initial window size, or {@code null} to clear a previously stored hint + */ + public void setInitialWindowSizeHintPercent(Dimension hint) { + initialWindowSizeHintPercent = hint; + } + + /** + * Returns the optional desktop window size hint provided by the first form. + * + * @return the stored hint or {@code null} + */ + public Dimension getInitialWindowSizeHintPercent() { + return initialWindowSizeHintPercent; + } + /** * Invoked when an exception occurs on the EDT, allows the implementation to * take control of the device to produce testing information. diff --git a/CodenameOne/src/com/codename1/ui/CN.java b/CodenameOne/src/com/codename1/ui/CN.java index fafbdc2fad..c9041549da 100644 --- a/CodenameOne/src/com/codename1/ui/CN.java +++ b/CodenameOne/src/com/codename1/ui/CN.java @@ -33,6 +33,8 @@ import com.codename1.plugin.PluginSupport; import com.codename1.ui.events.ActionListener; import com.codename1.ui.events.MessageEvent; +import com.codename1.ui.events.WindowEvent; +import com.codename1.ui.geom.Dimension; import com.codename1.ui.geom.Rectangle; import com.codename1.ui.plaf.Style; import com.codename1.util.RunnableWithResultSync; @@ -813,6 +815,57 @@ public static boolean isDesktop() { return Display.impl.isDesktop(); } + /** + * Returns the size of the desktop hosting the application window when running on a desktop platform. + * + * @return the desktop size + */ + public static Dimension getDesktopSize() { + return Display.getInstance().getDesktopSize(); + } + + /** + * Returns the number of monitors attached to the desktop environment when available. + * + * @return the number of monitors + */ + /** + * Returns the current bounds of the application window when supported by the platform. + * + * @return the window bounds + */ + public static Rectangle getWindowBounds() { + return Display.getInstance().getWindowBounds(); + } + + /** + * Requests a resize of the application window when supported by the platform. + * + * @param width the desired window width + * @param height the desired window height + */ + public static void setWindowSize(int width, int height) { + Display.getInstance().setWindowSize(width, height); + } + + /** + * Adds a listener for window events such as resize or move. + * + * @param l the listener to add + */ + public static void addWindowListener(ActionListener l) { + Display.getInstance().addWindowListener(l); + } + + /** + * Removes a previously registered window listener. + * + * @param l the listener to remove + */ + public static void removeWindowListener(ActionListener l) { + Display.getInstance().removeWindowListener(l); + } + /** * Returns true if the device has dialing capabilities * diff --git a/CodenameOne/src/com/codename1/ui/Display.java b/CodenameOne/src/com/codename1/ui/Display.java index b0025a08ec..fb570aad80 100644 --- a/CodenameOne/src/com/codename1/ui/Display.java +++ b/CodenameOne/src/com/codename1/ui/Display.java @@ -55,6 +55,7 @@ import com.codename1.ui.events.ActionEvent; import com.codename1.ui.events.ActionListener; import com.codename1.ui.events.MessageEvent; +import com.codename1.ui.events.WindowEvent; import com.codename1.ui.geom.Dimension; import com.codename1.ui.geom.Rectangle; import com.codename1.ui.plaf.Style; @@ -261,6 +262,12 @@ public final class Display extends CN1Constants { * Indicates that commands should try to add themselves to the native menus */ public static final int COMMAND_BEHAVIOR_NATIVE = 10; + /** + * Client property key used on the first shown {@link Form} to indicate the desired initial + * window size as a percentage of the available desktop. The value should be a {@link com.codename1.ui.geom.Dimension} + * whose width and height represent percentages. + */ + public static final String WINDOW_SIZE_HINT_PERCENT = "cn1.windowSizePercent"; static final Display INSTANCE = new Display(); static final Object lock = new Object(); private static final int POINTER_PRESSED = 1; @@ -293,6 +300,7 @@ public final class Display extends CN1Constants { private boolean inNativeUI; private Runnable bookmark; private EventDispatcher messageListeners; + private EventDispatcher windowListeners; private boolean disableInvokeAndBlock; /** * Enable Async stack traces. This is disabled by default, but will cause @@ -1648,6 +1656,9 @@ void setCurrentForm(Form newForm) { } else { forceShow = true; } + if (forceShow) { + applyInitialWindowSize(newForm); + } keyRepeatCharged = false; longPressCharged = false; longPointerCharged = false; @@ -1669,6 +1680,50 @@ void setCurrentForm(Form newForm) { newForm.onShowCompletedImpl(); } + private void applyInitialWindowSize(Form form) { + if (form == null) { + return; + } + Dimension percent = parseWindowSizePercent(form.getClientProperty(WINDOW_SIZE_HINT_PERCENT)); + if (percent == null) { + return; + } + if (impl.getInitialWindowSizeHintPercent() == null) { + impl.setInitialWindowSizeHintPercent(percent); + } + } + + private Dimension parseWindowSizePercent(Object hint) { + if (hint instanceof Dimension) { + return (Dimension) hint; + } + if (hint instanceof int[]) { + int[] arr = (int[]) hint; + if (arr.length >= 2) { + return new Dimension(arr[0], arr[1]); + } + } + if (hint instanceof float[]) { + float[] arr = (float[]) hint; + if (arr.length >= 2) { + return new Dimension(Math.round(arr[0]), Math.round(arr[1])); + } + } + if (hint instanceof double[]) { + double[] arr = (double[]) hint; + if (arr.length >= 2) { + return new Dimension((int) Math.round(arr[0]), (int) Math.round(arr[1])); + } + } + if (hint instanceof Number[]) { + Number[] arr = (Number[]) hint; + if (arr.length >= 2) { + return new Dimension(arr[0].intValue(), arr[1].intValue()); + } + } + return null; + } + /** * Indicates whether a delay should exist between calls to flush graphics during * transition. In some devices flushGraphics is asynchronious causing it to be @@ -2552,6 +2607,42 @@ public int getDisplayHeight() { return impl.getDisplayHeight(); } + /** + * Returns the size of the desktop hosting the application window when running on a desktop platform. + * + * @return the desktop size or the current display size if not supported + */ + public Dimension getDesktopSize() { + Dimension desktopSize = impl.getDesktopSize(); + if (desktopSize != null) { + return desktopSize; + } + return new Dimension(getDisplayWidth(), getDisplayHeight()); + } + + /** + * Returns the current window bounds when running on a desktop platform. + * + * @return the bounds of the application window + */ + public Rectangle getWindowBounds() { + Rectangle bounds = impl.getWindowBounds(); + if (bounds == null) { + return new Rectangle(0, 0, getDisplayWidth(), getDisplayHeight()); + } + return bounds; + } + + /** + * Requests a resize of the application window when supported by the platform. + * + * @param width the desired window width + * @param height the desired window height + */ + public void setWindowSize(int width, int height) { + impl.setWindowSize(width, height); + } + /** * Causes the given component to repaint, used internally by Form * @@ -3320,6 +3411,53 @@ public void dispatchMessage(MessageEvent evt) { } } + /** + * Adds a listener to receive notifications about native window changes such as resize or movement. + * + * @param l the listener to add + */ + public synchronized void addWindowListener(ActionListener l) { + if (windowListeners == null) { + windowListeners = new EventDispatcher(); + } + windowListeners.addListener(l); + } + + /** + * Removes a previously registered window listener. + * + * @param l the listener to remove + */ + public synchronized void removeWindowListener(ActionListener l) { + if (windowListeners != null) { + windowListeners.removeListener(l); + } + } + + /** + * Dispatches a window change event to registered listeners. This method is intended to be invoked by + * platform implementations. + * + * @param evt the window event to dispatch + */ + public void fireWindowEvent(WindowEvent evt) { + if (evt == null || windowListeners == null || !windowListeners.hasListeners()) { + return; + } + if (isEdt()) { + windowListeners.fireActionEvent(evt); + } else { + callSerially(new Runnable() { + @Override + public void run() { + if (windowListeners != null && windowListeners.hasListeners()) { + windowListeners.fireActionEvent(evt); + } + } + }); + } + } + /** * Returns the property from the underlying platform deployment or the default * value if no deployment values are supported. This is equivalent to the diff --git a/CodenameOne/src/com/codename1/ui/events/WindowEvent.java b/CodenameOne/src/com/codename1/ui/events/WindowEvent.java new file mode 100644 index 0000000000..73b516e678 --- /dev/null +++ b/CodenameOne/src/com/codename1/ui/events/WindowEvent.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores + * CA 94065 USA or visit www.oracle.com if you need additional information or + * have any questions. + */ +package com.codename1.ui.events; + +import com.codename1.ui.Display; +import com.codename1.ui.geom.Rectangle; + +/** + * Describes a change in the native window hosting the Codename One display on + * desktop platforms. + */ +public class WindowEvent extends ActionEvent { + + /** + * The type of window change that occurred. + */ + public enum Type { + /** + * The window became visible. + */ + Shown, + /** + * The window is no longer visible. + */ + Hidden, + /** + * The window was minimized/iconified. + */ + Minimized, + /** + * The window was restored from a minimized/iconified state. + */ + Restored, + /** + * The window was resized. + */ + Resized, + /** + * The window was moved. + */ + Moved + } + + private final Type type; + private final Rectangle bounds; + + /** + * Creates a new window event instance. + * + * @param source the display that generated the event + * @param type the type of the window event + * @param bounds the bounds of the window, if known + */ + public WindowEvent(Display source, Type type, Rectangle bounds) { + super(source, ActionEvent.Type.Other); + this.type = type; + this.bounds = bounds; + } + + /** + * The type of window event. + * + * @return the event type + */ + public Type getType() { + return type; + } + + /** + * Returns the window bounds associated with this event. + * + * @return the window bounds or {@code null} if not provided + */ + public Rectangle getBounds() { + return bounds; + } +} diff --git a/Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java b/Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java index 68a86ccf07..c49af0acc4 100644 --- a/Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java +++ b/Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java @@ -114,6 +114,7 @@ import com.codename1.ui.TextArea; import com.codename1.ui.TextSelection; import com.codename1.ui.Transform; +import com.codename1.ui.events.WindowEvent; import com.codename1.ui.plaf.Style; import com.codename1.ui.util.UITimer; import com.codename1.util.AsyncResource; @@ -227,6 +228,14 @@ private JFrame findTopFrame() { */ } + private void fireDesktopWindowEvent(WindowEvent.Type type) { + if (!isDesktop() || !Display.isInitialized()) { + return; + } + Display display = Display.getInstance(); + display.fireWindowEvent(new WindowEvent(display, type, getWindowBounds())); + } + @Override public boolean isFullScreenSupported() { Preferences pref = Preferences.userNodeForPackage(JavaSEPort.class); @@ -5532,6 +5541,7 @@ public void componentResized(ComponentEvent e) { window.addWindowListener(new WindowListener() { public void windowOpened(WindowEvent e) { + fireDesktopWindowEvent(com.codename1.ui.events.WindowEvent.Type.Shown); } public void windowClosing(WindowEvent e) { @@ -5539,12 +5549,15 @@ public void windowClosing(WindowEvent e) { } public void windowClosed(WindowEvent e) { + fireDesktopWindowEvent(com.codename1.ui.events.WindowEvent.Type.Hidden); } public void windowIconified(WindowEvent e) { + fireDesktopWindowEvent(com.codename1.ui.events.WindowEvent.Type.Minimized); } public void windowDeiconified(WindowEvent e) { + fireDesktopWindowEvent(com.codename1.ui.events.WindowEvent.Type.Restored); } public void windowActivated(WindowEvent e) { @@ -5569,15 +5582,27 @@ private void saveBounds(ComponentEvent e) { @Override public void componentResized(ComponentEvent e) { saveBounds(e); + fireDesktopWindowEvent(com.codename1.ui.events.WindowEvent.Type.Resized); } @Override public void componentMoved(ComponentEvent e) { saveBounds(e); + fireDesktopWindowEvent(com.codename1.ui.events.WindowEvent.Type.Moved); } - - - + + @Override + public void componentShown(ComponentEvent e) { + fireDesktopWindowEvent(com.codename1.ui.events.WindowEvent.Type.Shown); + } + + @Override + public void componentHidden(ComponentEvent e) { + fireDesktopWindowEvent(com.codename1.ui.events.WindowEvent.Type.Hidden); + } + + + }); window.setLocationByPlatform(true); @@ -5616,7 +5641,13 @@ public void componentMoved(ComponentEvent e) { window.setUndecorated(true); window.setExtendedState(JFrame.MAXIMIZED_BOTH); } - window.pack(); + java.awt.Dimension initialSize = resolveInitialWindowSizeFromHint(); + if (initialSize != null) { + window.setSize(initialSize); + window.validate(); + } else { + window.pack(); + } if (getSkin() != null && !scrollableSkin) { zoomLevel = zoomLevel(); } @@ -5745,6 +5776,58 @@ private int getDisplayHeightImpl() { return Math.max(h, 100); } + @Override + public com.codename1.ui.geom.Dimension getDesktopSize() { + if (!isDesktop()) { + return super.getDesktopSize(); + } + java.awt.Dimension size = Toolkit.getDefaultToolkit().getScreenSize(); + return new com.codename1.ui.geom.Dimension(size.width, size.height); + } + + private java.awt.Dimension resolveInitialWindowSizeFromHint() { + com.codename1.ui.geom.Dimension percent = getInitialWindowSizeHintPercent(); + com.codename1.ui.geom.Dimension desktopSize = getDesktopSize(); + if (percent == null || desktopSize == null) { + return null; + } + int width = Math.min(desktopSize.getWidth(), Math.max(1, Math.round(desktopSize.getWidth() * (percent.getWidth() / 100f)))); + int height = Math.min(desktopSize.getHeight(), Math.max(1, Math.round(desktopSize.getHeight() * (percent.getHeight() / 100f)))); + setInitialWindowSizeHintPercent(null); + return new java.awt.Dimension(width, height); + } + + @Override + public com.codename1.ui.geom.Rectangle getWindowBounds() { + if (!isDesktop()) { + return super.getWindowBounds(); + } + JFrame frame = findTopFrame(); + if (frame == null) { + return super.getWindowBounds(); + } + java.awt.Rectangle bounds = frame.getBounds(); + return new com.codename1.ui.geom.Rectangle(bounds.x, bounds.y, bounds.width, bounds.height); + } + + @Override + public void setWindowSize(int width, int height) { + if (!isDesktop()) { + return; + } + JFrame frame = findTopFrame(); + if (frame != null) { + java.awt.Dimension size = new java.awt.Dimension(width, height); + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + frame.setSize(size); + frame.validate(); + } + }); + } + } + /** * Creates a soft/weak reference to an object that allows it to be collected * yet caches it. This method is in the porting layer since CLDC only 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 e0b33dbf69..168266572f 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 @@ -87,6 +87,9 @@ public class TestCodenameOneImplementation extends CodenameOneImplementation { private final TestFont defaultFont = new TestFont(8, 16); private int displayWidth = 1080; private int displayHeight = 1920; + private Dimension desktopSize = new Dimension(displayWidth, displayHeight); + private Dimension lastWindowSize; + private Rectangle windowBounds = new Rectangle(0, 0, displayWidth, displayHeight); private int deviceDensity = Display.DENSITY_MEDIUM; private boolean portrait = true; private boolean touchDevice = true; @@ -1048,6 +1051,49 @@ public int getDisplayHeight() { return displayHeight; } + @Override + public Dimension getDesktopSize() { + return desktopSize; + } + + public void setDesktopSize(Dimension desktopSize) { + this.desktopSize = desktopSize; + } + + @Override + public Rectangle getWindowBounds() { + if (windowBounds == null) { + return new Rectangle(0, 0, displayWidth, displayHeight); + } + return new Rectangle(windowBounds); + } + + public void setWindowBounds(Rectangle windowBounds) { + this.windowBounds = windowBounds; + } + + @Override + public void setInitialWindowSizeHintPercent(Dimension hint) { + super.setInitialWindowSizeHintPercent(hint); + if (hint != null && desktopSize != null && lastWindowSize == null) { + int width = Math.min(desktopSize.getWidth(), Math.max(1, Math.round(desktopSize.getWidth() * (hint.getWidth() / 100f)))); + int height = Math.min(desktopSize.getHeight(), Math.max(1, Math.round(desktopSize.getHeight() * (hint.getHeight() / 100f)))); + setWindowSize(width, height); + } + } + + @Override + public void setWindowSize(int width, int height) { + lastWindowSize = new Dimension(width, height); + displayWidth = width; + displayHeight = height; + windowBounds = new Rectangle(windowBounds == null ? 0 : windowBounds.getX(), windowBounds == null ? 0 : windowBounds.getY(), width, height); + } + + public Dimension getLastWindowSize() { + return lastWindowSize == null ? null : new Dimension(lastWindowSize); + } + @Override public void editString(com.codename1.ui.Component cmp, int maxSize, int constraint, String text, int initiatingKeycode) { if (cmp instanceof TextField) { diff --git a/maven/core-unittests/src/test/java/com/codename1/ui/WindowApiTest.java b/maven/core-unittests/src/test/java/com/codename1/ui/WindowApiTest.java new file mode 100644 index 0000000000..8cd945494d --- /dev/null +++ b/maven/core-unittests/src/test/java/com/codename1/ui/WindowApiTest.java @@ -0,0 +1,73 @@ +package com.codename1.ui; + +import com.codename1.junit.FormTest; +import com.codename1.junit.UITestBase; +import com.codename1.ui.events.ActionListener; +import com.codename1.ui.events.WindowEvent; +import com.codename1.ui.geom.Dimension; +import com.codename1.ui.geom.Rectangle; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class WindowApiTest extends UITestBase { + + @FormTest + void desktopInformationExposedThroughDisplay() { + Dimension desktopSize = new Dimension(1600, 900); + implementation.setDesktopSize(desktopSize); + + Dimension fromDisplay = Display.getInstance().getDesktopSize(); + + assertEquals(desktopSize.getWidth(), fromDisplay.getWidth()); + assertEquals(desktopSize.getHeight(), fromDisplay.getHeight()); + } + + @FormTest + void windowListenersReceiveDispatchedEvents() { + Display display = Display.getInstance(); + List events = new ArrayList(); + WindowEvent event = new WindowEvent(display, WindowEvent.Type.Resized, new Rectangle(10, 20, 300, 200)); + + ActionListener listener = evt -> events.add(evt); + display.addWindowListener(listener); + display.fireWindowEvent(event); + flushSerialCalls(); + + assertEquals(1, events.size()); + WindowEvent captured = events.get(0); + assertEquals(WindowEvent.Type.Resized, captured.getType()); + Rectangle bounds = captured.getBounds(); + assertNotNull(bounds); + assertEquals(10, bounds.getX()); + assertEquals(20, bounds.getY()); + assertEquals(300, bounds.getSize().getWidth()); + assertEquals(200, bounds.getSize().getHeight()); + display.removeWindowListener(listener); + } + + @FormTest + void firstFormWindowSizeHintUsesPercentages() { + implementation.setDesktopSize(new Dimension(2000, 1000)); + Form first = new Form(); + first.putClientProperty(Display.WINDOW_SIZE_HINT_PERCENT, new Dimension(50, 25)); + + first.show(); + flushSerialCalls(); + + Dimension lastWindowSize = implementation.getLastWindowSize(); + assertNotNull(lastWindowSize); + assertEquals(1000, lastWindowSize.getWidth()); + assertEquals(250, lastWindowSize.getHeight()); + + Form second = new Form(); + second.putClientProperty(Display.WINDOW_SIZE_HINT_PERCENT, new Dimension(10, 10)); + second.show(); + flushSerialCalls(); + + assertEquals(1000, implementation.getLastWindowSize().getWidth()); + assertEquals(250, implementation.getLastWindowSize().getHeight()); + } +} From 7ca85543ff0aa6563c2b3fdb3043b97759555e66 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Wed, 26 Nov 2025 07:01:40 +0200 Subject: [PATCH 02/10] Make window event dispatch thread-safe --- CodenameOne/src/com/codename1/ui/Display.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CodenameOne/src/com/codename1/ui/Display.java b/CodenameOne/src/com/codename1/ui/Display.java index fb570aad80..d3a6f7ff83 100644 --- a/CodenameOne/src/com/codename1/ui/Display.java +++ b/CodenameOne/src/com/codename1/ui/Display.java @@ -3447,11 +3447,12 @@ public void fireWindowEvent(WindowEvent evt) { if (isEdt()) { windowListeners.fireActionEvent(evt); } else { + final WindowEvent windowEvent = evt; callSerially(new Runnable() { @Override public void run() { if (windowListeners != null && windowListeners.hasListeners()) { - windowListeners.fireActionEvent(evt); + windowListeners.fireActionEvent(windowEvent); } } }); From 84e9ad9f777168883491e5b2c9322df16f9b72f8 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Wed, 26 Nov 2025 07:20:59 +0200 Subject: [PATCH 03/10] Ensure initial window hint is applied and fix window listener import --- CodenameOne/src/com/codename1/ui/Display.java | 4 +--- Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/CodenameOne/src/com/codename1/ui/Display.java b/CodenameOne/src/com/codename1/ui/Display.java index d3a6f7ff83..2749f93391 100644 --- a/CodenameOne/src/com/codename1/ui/Display.java +++ b/CodenameOne/src/com/codename1/ui/Display.java @@ -1688,9 +1688,7 @@ private void applyInitialWindowSize(Form form) { if (percent == null) { return; } - if (impl.getInitialWindowSizeHintPercent() == null) { - impl.setInitialWindowSizeHintPercent(percent); - } + impl.setInitialWindowSizeHintPercent(percent); } private Dimension parseWindowSizePercent(Object hint) { diff --git a/Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java b/Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java index c49af0acc4..43c51db01d 100644 --- a/Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java +++ b/Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java @@ -114,7 +114,6 @@ import com.codename1.ui.TextArea; import com.codename1.ui.TextSelection; import com.codename1.ui.Transform; -import com.codename1.ui.events.WindowEvent; import com.codename1.ui.plaf.Style; import com.codename1.ui.util.UITimer; import com.codename1.util.AsyncResource; From 410fd53d024065bf53a4908c5edbe5f2f945ea82 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Wed, 26 Nov 2025 07:53:22 +0200 Subject: [PATCH 04/10] Ensure initial window hint applied once and fix desktop event type --- CodenameOne/src/com/codename1/ui/Display.java | 8 +++++++- .../JavaSE/src/com/codename1/impl/javase/JavaSEPort.java | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CodenameOne/src/com/codename1/ui/Display.java b/CodenameOne/src/com/codename1/ui/Display.java index 2749f93391..139f483594 100644 --- a/CodenameOne/src/com/codename1/ui/Display.java +++ b/CodenameOne/src/com/codename1/ui/Display.java @@ -301,6 +301,10 @@ public final class Display extends CN1Constants { private Runnable bookmark; private EventDispatcher messageListeners; private EventDispatcher windowListeners; + /** + * Tracks whether the initial window size hint has already been consumed for the first shown form. + */ + private boolean initialWindowSizeApplied; private boolean disableInvokeAndBlock; /** * Enable Async stack traces. This is disabled by default, but will cause @@ -438,6 +442,7 @@ private Display() { public static void init(Object m) { if (!INSTANCE.codenameOneRunning) { INSTANCE.codenameOneRunning = true; + INSTANCE.initialWindowSizeApplied = false; INSTANCE.pluginSupport = new PluginSupport(); INSTANCE.displayInitTime = System.currentTimeMillis(); @@ -1656,8 +1661,9 @@ void setCurrentForm(Form newForm) { } else { forceShow = true; } - if (forceShow) { + if (!initialWindowSizeApplied) { applyInitialWindowSize(newForm); + initialWindowSizeApplied = true; } keyRepeatCharged = false; longPressCharged = false; diff --git a/Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java b/Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java index 43c51db01d..d65c4a80dc 100644 --- a/Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java +++ b/Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java @@ -227,12 +227,12 @@ private JFrame findTopFrame() { */ } - private void fireDesktopWindowEvent(WindowEvent.Type type) { + private void fireDesktopWindowEvent(com.codename1.ui.events.WindowEvent.Type type) { if (!isDesktop() || !Display.isInitialized()) { return; } Display display = Display.getInstance(); - display.fireWindowEvent(new WindowEvent(display, type, getWindowBounds())); + display.fireWindowEvent(new com.codename1.ui.events.WindowEvent(display, type, getWindowBounds())); } @Override From 3f2aa853930395213b32c80f51ed9b2d4c132b3e Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Wed, 26 Nov 2025 17:39:51 +0200 Subject: [PATCH 05/10] Reinitialize display for each UI unit test --- .../src/test/java/com/codename1/junit/UITestBase.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/maven/core-unittests/src/test/java/com/codename1/junit/UITestBase.java b/maven/core-unittests/src/test/java/com/codename1/junit/UITestBase.java index 8bd54ad99c..c1fbf95b5b 100644 --- a/maven/core-unittests/src/test/java/com/codename1/junit/UITestBase.java +++ b/maven/core-unittests/src/test/java/com/codename1/junit/UITestBase.java @@ -25,12 +25,16 @@ public abstract class UITestBase { @BeforeEach protected void setUpDisplay() throws Exception { - if(!Display.isInitialized() && TestCodenameOneImplementation.getInstance() == null) { - implementation = new TestCodenameOneImplementation(); + if (!Display.isInitialized()) { + implementation = TestCodenameOneImplementation.getInstance(); + if (implementation == null) { + implementation = new TestCodenameOneImplementation(); + } + final TestCodenameOneImplementation implRef = implementation; ImplementationFactory.setInstance(new ImplementationFactory() { @Override public Object createImplementation() { - return implementation; + return implRef; } }); Display.init(null); From 048c489c669ac61a86a3f20a1b0770a69d2c9127 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Wed, 26 Nov 2025 19:56:33 +0200 Subject: [PATCH 06/10] Apply initial window size hint only when provided --- CodenameOne/src/com/codename1/ui/Display.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CodenameOne/src/com/codename1/ui/Display.java b/CodenameOne/src/com/codename1/ui/Display.java index 139f483594..4d919fed71 100644 --- a/CodenameOne/src/com/codename1/ui/Display.java +++ b/CodenameOne/src/com/codename1/ui/Display.java @@ -1662,8 +1662,7 @@ void setCurrentForm(Form newForm) { forceShow = true; } if (!initialWindowSizeApplied) { - applyInitialWindowSize(newForm); - initialWindowSizeApplied = true; + initialWindowSizeApplied = applyInitialWindowSize(newForm); } keyRepeatCharged = false; longPressCharged = false; @@ -1686,15 +1685,16 @@ void setCurrentForm(Form newForm) { newForm.onShowCompletedImpl(); } - private void applyInitialWindowSize(Form form) { + private boolean applyInitialWindowSize(Form form) { if (form == null) { - return; + return false; } Dimension percent = parseWindowSizePercent(form.getClientProperty(WINDOW_SIZE_HINT_PERCENT)); if (percent == null) { - return; + return false; } impl.setInitialWindowSizeHintPercent(percent); + return true; } private Dimension parseWindowSizePercent(Object hint) { From 383accebc598812fd202b17e1852d8c43fe9e6c4 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Wed, 26 Nov 2025 20:20:40 +0200 Subject: [PATCH 07/10] Handle window size hints without Number dependency --- CodenameOne/src/com/codename1/ui/Display.java | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/CodenameOne/src/com/codename1/ui/Display.java b/CodenameOne/src/com/codename1/ui/Display.java index 4d919fed71..0f97e4c65d 100644 --- a/CodenameOne/src/com/codename1/ui/Display.java +++ b/CodenameOne/src/com/codename1/ui/Display.java @@ -1719,15 +1719,41 @@ private Dimension parseWindowSizePercent(Object hint) { return new Dimension((int) Math.round(arr[0]), (int) Math.round(arr[1])); } } - if (hint instanceof Number[]) { - Number[] arr = (Number[]) hint; + if (hint instanceof Object[]) { + Object[] arr = (Object[]) hint; if (arr.length >= 2) { - return new Dimension(arr[0].intValue(), arr[1].intValue()); + Integer w = parseNumericObject(arr[0]); + Integer h = parseNumericObject(arr[1]); + if (w != null && h != null) { + return new Dimension(w.intValue(), h.intValue()); + } } } return null; } + private Integer parseNumericObject(Object value) { + if (value instanceof Integer) { + return (Integer) value; + } + if (value instanceof Float) { + return Integer.valueOf(Math.round(((Float) value).floatValue())); + } + if (value instanceof Double) { + return Integer.valueOf((int) Math.round(((Double) value).doubleValue())); + } + if (value instanceof Long) { + return Integer.valueOf((int) ((Long) value).longValue()); + } + if (value instanceof Short) { + return Integer.valueOf(((Short) value).shortValue()); + } + if (value instanceof Byte) { + return Integer.valueOf(((Byte) value).byteValue()); + } + return null; + } + /** * Indicates whether a delay should exist between calls to flush graphics during * transition. In some devices flushGraphics is asynchronious causing it to be From 727007592f3fbb7d25b5b659ae736823607d7df4 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Wed, 26 Nov 2025 20:59:03 +0200 Subject: [PATCH 08/10] Fix window sizing hint application --- Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java | 4 ++-- .../com/codename1/testing/TestCodenameOneImplementation.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java b/Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java index d65c4a80dc..2a1bda2f9b 100644 --- a/Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java +++ b/Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java @@ -5814,9 +5814,9 @@ public void setWindowSize(int width, int height) { if (!isDesktop()) { return; } - JFrame frame = findTopFrame(); + final JFrame frame = findTopFrame(); if (frame != null) { - java.awt.Dimension size = new java.awt.Dimension(width, height); + final java.awt.Dimension size = new java.awt.Dimension(width, height); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { 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 168266572f..b6310ebfc0 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 @@ -1075,7 +1075,7 @@ public void setWindowBounds(Rectangle windowBounds) { @Override public void setInitialWindowSizeHintPercent(Dimension hint) { super.setInitialWindowSizeHintPercent(hint); - if (hint != null && desktopSize != null && lastWindowSize == null) { + if (hint != null && desktopSize != null) { int width = Math.min(desktopSize.getWidth(), Math.max(1, Math.round(desktopSize.getWidth() * (hint.getWidth() / 100f)))); int height = Math.min(desktopSize.getHeight(), Math.max(1, Math.round(desktopSize.getHeight() * (hint.getHeight() / 100f)))); setWindowSize(width, height); From 9c6147fd32aeda2ebbcc50aa06e2fde4da5dfb73 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Thu, 27 Nov 2025 04:16:30 +0200 Subject: [PATCH 09/10] Expose initial window size hint API --- CodenameOne/src/com/codename1/ui/CN.java | 9 +++ CodenameOne/src/com/codename1/ui/Display.java | 72 ++++--------------- .../java/com/codename1/ui/WindowApiTest.java | 5 ++ 3 files changed, 26 insertions(+), 60 deletions(-) diff --git a/CodenameOne/src/com/codename1/ui/CN.java b/CodenameOne/src/com/codename1/ui/CN.java index c9041549da..5d4a7f7408 100644 --- a/CodenameOne/src/com/codename1/ui/CN.java +++ b/CodenameOne/src/com/codename1/ui/CN.java @@ -848,6 +848,15 @@ public static void setWindowSize(int width, int height) { Display.getInstance().setWindowSize(width, height); } + /** + * Returns the initial desktop window size hint provided by the first shown form, when available. + * + * @return the stored hint or {@code null} + */ + public static Dimension getInitialWindowSizeHintPercent() { + return Display.getInstance().getInitialWindowSizeHintPercent(); + } + /** * Adds a listener for window events such as resize or move. * diff --git a/CodenameOne/src/com/codename1/ui/Display.java b/CodenameOne/src/com/codename1/ui/Display.java index 0f97e4c65d..01ff660c99 100644 --- a/CodenameOne/src/com/codename1/ui/Display.java +++ b/CodenameOne/src/com/codename1/ui/Display.java @@ -1689,71 +1689,14 @@ private boolean applyInitialWindowSize(Form form) { if (form == null) { return false; } - Dimension percent = parseWindowSizePercent(form.getClientProperty(WINDOW_SIZE_HINT_PERCENT)); - if (percent == null) { + Object hint = form.getClientProperty(WINDOW_SIZE_HINT_PERCENT); + if (!(hint instanceof Dimension)) { return false; } - impl.setInitialWindowSizeHintPercent(percent); + impl.setInitialWindowSizeHintPercent((Dimension) hint); return true; } - private Dimension parseWindowSizePercent(Object hint) { - if (hint instanceof Dimension) { - return (Dimension) hint; - } - if (hint instanceof int[]) { - int[] arr = (int[]) hint; - if (arr.length >= 2) { - return new Dimension(arr[0], arr[1]); - } - } - if (hint instanceof float[]) { - float[] arr = (float[]) hint; - if (arr.length >= 2) { - return new Dimension(Math.round(arr[0]), Math.round(arr[1])); - } - } - if (hint instanceof double[]) { - double[] arr = (double[]) hint; - if (arr.length >= 2) { - return new Dimension((int) Math.round(arr[0]), (int) Math.round(arr[1])); - } - } - if (hint instanceof Object[]) { - Object[] arr = (Object[]) hint; - if (arr.length >= 2) { - Integer w = parseNumericObject(arr[0]); - Integer h = parseNumericObject(arr[1]); - if (w != null && h != null) { - return new Dimension(w.intValue(), h.intValue()); - } - } - } - return null; - } - - private Integer parseNumericObject(Object value) { - if (value instanceof Integer) { - return (Integer) value; - } - if (value instanceof Float) { - return Integer.valueOf(Math.round(((Float) value).floatValue())); - } - if (value instanceof Double) { - return Integer.valueOf((int) Math.round(((Double) value).doubleValue())); - } - if (value instanceof Long) { - return Integer.valueOf((int) ((Long) value).longValue()); - } - if (value instanceof Short) { - return Integer.valueOf(((Short) value).shortValue()); - } - if (value instanceof Byte) { - return Integer.valueOf(((Byte) value).byteValue()); - } - return null; - } - /** * Indicates whether a delay should exist between calls to flush graphics during * transition. In some devices flushGraphics is asynchronious causing it to be @@ -2673,6 +2616,15 @@ public void setWindowSize(int width, int height) { impl.setWindowSize(width, height); } + /** + * Returns the initial desktop window size hint provided by the first shown form, when available. + * + * @return the stored hint or {@code null} + */ + public Dimension getInitialWindowSizeHintPercent() { + return impl.getInitialWindowSizeHintPercent(); + } + /** * Causes the given component to repaint, used internally by Form * diff --git a/maven/core-unittests/src/test/java/com/codename1/ui/WindowApiTest.java b/maven/core-unittests/src/test/java/com/codename1/ui/WindowApiTest.java index 8cd945494d..b5d711730f 100644 --- a/maven/core-unittests/src/test/java/com/codename1/ui/WindowApiTest.java +++ b/maven/core-unittests/src/test/java/com/codename1/ui/WindowApiTest.java @@ -57,6 +57,11 @@ void firstFormWindowSizeHintUsesPercentages() { first.show(); flushSerialCalls(); + Dimension initialHint = Display.getInstance().getInitialWindowSizeHintPercent(); + assertNotNull(initialHint); + assertEquals(50, initialHint.getWidth()); + assertEquals(25, initialHint.getHeight()); + Dimension lastWindowSize = implementation.getLastWindowSize(); assertNotNull(lastWindowSize); assertEquals(1000, lastWindowSize.getWidth()); From 081372ee94f7ae60b2a0ca9d64e2e178ba12dcca Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Thu, 27 Nov 2025 04:16:35 +0200 Subject: [PATCH 10/10] Expose initial window size hint setter --- CodenameOne/src/com/codename1/ui/CN.java | 12 ++++++++++++ CodenameOne/src/com/codename1/ui/Display.java | 12 ++++++++++++ .../test/java/com/codename1/ui/WindowApiTest.java | 5 +++++ 3 files changed, 29 insertions(+) diff --git a/CodenameOne/src/com/codename1/ui/CN.java b/CodenameOne/src/com/codename1/ui/CN.java index 5d4a7f7408..41d50661db 100644 --- a/CodenameOne/src/com/codename1/ui/CN.java +++ b/CodenameOne/src/com/codename1/ui/CN.java @@ -857,6 +857,18 @@ public static Dimension getInitialWindowSizeHintPercent() { return Display.getInstance().getInitialWindowSizeHintPercent(); } + /** + * Sets the initial desktop window size hint (percent of the desktop) that should be used when the + * first form is shown. This is primarily useful for desktop environments where the Codename One + * application is hosted in a window rather than full-screen. + * + * @param hint a {@link Dimension} whose width/height represent percentages of the desktop to use for + * the initial window size, or {@code null} to clear a previously stored hint + */ + public static void setInitialWindowSizeHintPercent(Dimension hint) { + Display.getInstance().setInitialWindowSizeHintPercent(hint); + } + /** * Adds a listener for window events such as resize or move. * diff --git a/CodenameOne/src/com/codename1/ui/Display.java b/CodenameOne/src/com/codename1/ui/Display.java index 01ff660c99..e290455e27 100644 --- a/CodenameOne/src/com/codename1/ui/Display.java +++ b/CodenameOne/src/com/codename1/ui/Display.java @@ -2625,6 +2625,18 @@ public Dimension getInitialWindowSizeHintPercent() { return impl.getInitialWindowSizeHintPercent(); } + /** + * Sets the initial desktop window size hint (percent of the desktop) that should be used when the + * first form is shown. This is primarily useful for desktop environments where the Codename One + * application is hosted in a window rather than full-screen. + * + * @param hint a {@link Dimension} whose width/height represent percentages of the desktop to use for + * the initial window size, or {@code null} to clear a previously stored hint + */ + public void setInitialWindowSizeHintPercent(Dimension hint) { + impl.setInitialWindowSizeHintPercent(hint); + } + /** * Causes the given component to repaint, used internally by Form * diff --git a/maven/core-unittests/src/test/java/com/codename1/ui/WindowApiTest.java b/maven/core-unittests/src/test/java/com/codename1/ui/WindowApiTest.java index b5d711730f..1ee3acda57 100644 --- a/maven/core-unittests/src/test/java/com/codename1/ui/WindowApiTest.java +++ b/maven/core-unittests/src/test/java/com/codename1/ui/WindowApiTest.java @@ -62,6 +62,11 @@ void firstFormWindowSizeHintUsesPercentages() { assertEquals(50, initialHint.getWidth()); assertEquals(25, initialHint.getHeight()); + Dimension initialHintFromCN = CN.getInitialWindowSizeHintPercent(); + assertNotNull(initialHintFromCN); + assertEquals(50, initialHintFromCN.getWidth()); + assertEquals(25, initialHintFromCN.getHeight()); + Dimension lastWindowSize = implementation.getLastWindowSize(); assertNotNull(lastWindowSize); assertEquals(1000, lastWindowSize.getWidth());