Native desktop windows - #5556
Conversation
There was a problem hiding this comment.
💡 Codex Review
When f is a Window, this invokes the inherited Container.keyPressed(), because Window does not override the key handlers. That implementation only forwards to a container lead component, so ordinary focused controls receive no physical-key input, focus traversal never runs, and the listeners stored by Window.addKeyListener() are never fired. Window needs form-equivalent key pressed, released, repeated, and long-press dispatch.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Compared 12 screenshots: 12 matched. |
✅ Continuous Quality ReportTest & Coverage
Static Analysis
Generated automatically by the PR CI workflow. |
Cloudflare Preview
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 338ee1a6f1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Developer Guide build artifacts are available for download from this workflow run:
Developer Guide quality checks: |
|
Compared 151 screenshots: 151 matched. Native Android coverage
✅ Native Android screenshot tests passed. Native Android coverage
Benchmark ResultsDetailed Performance Metrics
|
|
Compared 163 screenshots: 163 matched. Benchmark ResultsDetailed Performance Metrics
|
|
Compared 163 screenshots: 163 matched. Benchmark ResultsDetailed Performance Metrics
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 13311a9bed
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1670ca0579
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bea352c1ee
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c7832a8ae0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Compared 144 screenshots: 144 matched. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bc45217f4f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fc3370ca1b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Compared 217 screenshots: 217 matched. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 259c862b4b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a128cc7ff2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Compared 181 screenshots: 181 matched. |
Form implements TopLevelContainer now, so 43 of its methods override an interface method and the MissingOverride rule wants them annotated. The volatile rule has no exception list, so the two volatile fields are gone rather than suppressed: disposed is published and read under Display.lock, which is the monitor showModal already parks on, and paintedOnce is written and read only on the event dispatch thread. Two index loops are deliberate -- a window can be disposed part way through a nested event loop, and an animation can deregister itself while it is being iterated -- and are marked, matching the existing NOPMD in Form.loopAnimations. The owned-window loop had no such reason and is a foreach now. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 47e0aa010f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Compared 163 screenshots: 163 matched. Benchmark ResultsDetailed Performance Metrics
|
|
Compared 163 screenshots: 163 matched. |
|
Compared 163 screenshots: 163 matched. |
Dragging a non-scrollable child of a window recursed until the stack ran out. A drag bubbles up looking for something scrollable and stopped only at a Form; a Window dispatches drags to the pressed child itself, so bubbling past one came straight back and repeated. It stops at any top level now. The title area is a sibling of the content pane, and hit testing always started at the content pane, so a Toolbar or button drawn as custom chrome in an undecorated window could never be pressed. Pull to refresh threw when the window was shown. Its setup still resolved a Form and called listener methods on it straight away, and a component in a window has no Form. It goes through the top level, which also meant giving TopLevelContainer the awaiting-release contract Form already had. JavaSE modality only elevated the modal window and left every blocked frame enabled. The framework filters canvas input, but the native title bar is outside that filter: closing a blocked window still fired a close request, and closing the main frame still exited the application. The frames a modal window blocks are disabled while it is up. A Catalyst window hidden before its scene arrived came back visible, because the queued hide found no window yet and adoption then showed it unconditionally. The requested visibility is recorded and honoured. An unowned window with MODALITY_WINDOW blocked the main form, which never owned it. And showing a child before its owner marked it owned by the main window, fixing the wrong native owner permanently, since every port establishes that relation at creation -- the owner is created first now. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Compared 143 screenshots: 143 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
The Mac producer caught one of the fourteen windows rendering at the previous window's size: a recycled scene reports its old geometry the instant it is adopted, before the new request lands, and the framework laid the window out against that. A layout report is now filtered against any geometry still outstanding. While a request is unanswered only the size that was asked for is delivered; anything else is the old geometry on its way out. Once it matches, the window is free again and a later user resize passes straight through. The harness let it past because the size rule only rejected a window larger than requested -- chrome makes one legitimately smaller on Windows. It now also rejects one implausibly smaller: chrome costs tens of pixels, never a quarter of the window. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The modality change captured a method parameter in an anonymous Runnable without declaring it final. Maven builds this port at a source level where effectively-final is enough, so it compiled here; the Ant project that the simulator integration tests use sets javac.source=1.7, where it is an error. Verified by compiling all 174 port sources with -source 1.7 rather than by fixing the one line the compiler happened to name. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 75ef76f159
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…splay Display had grown 27 arrays, all of them parallel tables keyed by window id, with three separate slot allocators handing entries out and taking them back. That was the wrong shape, and it was not a theoretical problem: two of the defects found in review last round were caused by it rather than found by it. The drag-activation filter silently switched off for the whole application once eight windows had been disposed mid-press without returning their slots, and the pressed-selection bug was "fixed" by adding three more arrays. State that belongs to a window now lives on the window, as fields: drag path, drag-occurred, pressed-selection and its coordinates, key repeat and long key press timers, long pointer press timer Display keeps exactly what it kept before this branch for the main surface -- its own dragPathX/Y/Time ring, keyRepeatCharged, longPressCharged, longPointerCharged, keyRepeatValue, nextKeyRepeatEvent, longKeyPressTime -- so window zero is the special case it always was, on the code it always ran. The event loop services those the way master does, then walks the windows that are actually open rather than a fixed table mostly full of empty entries. Removed: 17 arrays, dragHistorySlot(), keyRepeatSlot(), the inline allocator in chargeLongPress(), and releaseDragHistory() with its three call sites -- including the one guarded against a nested invokeAndBlock, which only had to exist because the ring was shared. PointerDragHistory is the one piece worth sharing: the sample ring and its wrap arithmetic. Display owns one, each Window owns one. Everything else is a field, and a field on the right object needs no sharing. Two tests were rewritten rather than repaired. dragHistorySlotsAreReclaimed- AfterEachGesture reflected into the slot table to prove slots came back; there are no slots, so it now asserts the behaviour that mattered -- a window opened after ten others have gestured can still drag. The key-repeat and long-press helpers likewise ask the surface that owns the timer instead of reading a table out of Display. 5372 core tests pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…tion Two of the things that made this branch spread. A Window had a title area and a Toolbar, which is the mobile title bar standing in for chrome the platform already draws -- so a window carried two titles and gave up content space to the one nobody asked for. TopLevelContainer no longer declares getTitleArea(), getToolbar() or setToolbar(); a Window's title goes straight to the platform, and Toolbar and its search bar are Form-only again. That alone reverts Toolbar.java to master exactly: 285 lines and 26 of the getTopLevelContainer() call sites, gone because the question does not arise. The other is the pattern the sweep kept repeating. Registering a component for animation had turned into "resolve the top level, null-check it, remember it in a private field so deregistering can use the same one" -- twelve lines and a field, replacing a single getComponentForm().registerAnimated(this). It was written out again in each class that needed it, and the field was there because of a real defect: register against one top level, get moved, deregister resolves another, and the first goes on animating a component that left it. Component.registerForAnimation() and deregisterFromAnimation() hold that. The component remembers what it registered with and deregisters from the same thing, so the defect is fixed once instead of a dozen times, and the call sites are shorter than they were before this branch. ImageViewer's three of them go from twelve lines each to one. Nine tests went with the toolbar: eight exercised toolbars and side menus inside a Window, which is no longer a thing. The ninth is about animation registration in a window, which still is, so it stays with the toolbar part removed. One test changed for a real behaviour change rather than a fix: aPressDraggedOutOfAButtonInAWindowIsCancelled released at (2,2) as "outside the button", which only worked while a title area occupied the top of the window. With the content pane filling the window a centred button covers every in-window point, so it now releases outside the window. 5364 core tests pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…pers Ten more sites that had each written out "resolve the top level, null-check it, register this" -- and in two cases the matching resolve-and-deregister -- now call registerForAnimation() and deregisterFromAnimation(). InfiniteProgress, ScaleImageButton, ScaleImageLabel, Button, Label, TextField and Component. Worth recording how the sweep went wrong, because the pattern-match nearly repeated the mistake it was cleaning up: the substitution also consumed the declaration it matched, and in TextField.deinitialize() that variable had a second use further down. The compiler caught it; nothing else in the batch had the same shape. Enumerating would have been safer than matching, again. 5364 core tests pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Modality is a question about windows, and the window registry is on Desktop. Display was holding the modal list, the push/pop, the native blocking sync and the four predicates that decide what a given modal blocks -- 153 lines about a concept the main surface has no stake in. They move to Desktop. Display now asks one question, isWindowInputBlocked(id), and does not need to know what a modal is; Window pushes and pops its own registration directly rather than through Display. Display's diff against master is down from +2249 to +1968 lines with this and the earlier state moves. 5364 core tests pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…o Desktop
Display is already a god class; the window work was making it worse. Moving
out what is genuinely about windows rather than about the display:
window lifecycle shown, hidden, focus, moved, resized, monitor changed,
close requested, closed natively, activation failed,
monitorsChanged and the WindowCallback that carries them,
plus the pending-size table and the coalescing guard
geometry windowWidth, windowHeight, windowDragRegionStatus
input entry the pointer and key events a port reports for a window
All of it resolves an id to a window, and the registry that does that is on
Desktop. Display's diff against master drops from +2249 to +1504 lines, and
the ports now report window events to Desktop instead of to Display.
Some of it deliberately stayed. windowInputCancelled and windowDisposed tear
down input state Display owns. windowMouseWheelEvent, windowMagnifyGesture
and windowRotationGesture carry the shared implementation the main surface
uses as well, so moving them would have duplicated it. windowKeyReleased and
the two hover-press entry points only pack an event and put it on the queue,
which is Display's actual job -- moving those would have meant widening the
queue's internals to the package to buy nothing.
pointerReleasedImpl now cancels the long-press timer itself rather than
relying on its callers to, since a release ends the gesture either way.
5364 core tests pass; core, javase, windows, linux and ios all compile.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
createWindowGraphics, the paint pass over the open windows and the "does any window still animate" check were all "walk the windows and do something", which is Desktop's job. Display's event loop keeps the loop and calls Desktop once per pass for the window half. repaintTopLevels splits the same way: the current Form is Display's to repaint, the windows are Desktop's. wakeEdt stays -- it notifies Display's own lock. Display's diff against master is now +1459 lines, down from +2249 when this round started, and what remains is the input queue, the main surface's own state and the shared implementations window zero uses too. 5364 core tests pass; core, javase, windows, linux and ios all compile through the reactor. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
SpotBugs is a zero-findings gate and caught five leftovers from moving the window API out of Display: a local that lost its last use when windowWidth moved to Desktop, and four private methods -- isSelectionPressed, longPressKey, repeatTarget, selectionX and selectionY -- whose only callers went with the code that moved. This is the case CLAUDE.md warns about: removing a caller can make a private method dead, and the gate fails on it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Five iOS jobs -- build-ios, build-ios-watch, build-ios-metal, native-ios and packaging -- were failing on the same six errors: call to undeclared function 'com_codename1_impl_ios_IOSWearableCallbacks_nativeMessageReceived___...'; ISO C99 and later do not support implicit function declarations Nothing about them had changed. The WatchConnectivity block in IOSNative.m calls six translated static methods on IOSWearableCallbacks, ParparVM emits their definitions, and the file includes no header that declares them -- so every call was an implicit declaration. C99 dropped those, and this only compiled while the toolchain treated it as a warning. The same commit passed this job at 17:42 and failed it at 00:07 with no relevant change in between, which is what a runner image moving to a stricter clang looks like. Declared explicitly, inside the same CN1_USE_WATCHCONNECTIVITY guard as the calls. check-native-signatures.sh reports no MISSING or SIGNATURE findings for the ios port. Not caused by this branch -- the wearable code came from master in #5487 -- but it blocks it, so it is fixed here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Moving the key-repeat and long-press timers onto the windows dropped a check the old routing helper made. repeatTarget() returned null for a surface that a modal was blocking, so a key held down before the modal appeared stopped repeating into the window behind it. serviceInputTimers() checked only that the window was visible, so those repeats started getting through again, and the main surface had lost the same guard. Both now consult Desktop.isWindowInputBlocked() before firing. Found by refusing to delete a test. SpotBugs reported repeatTarget() as an uncalled private method once its production callers moved to Desktop, and I deleted it -- but a test reached it by reflection, which SpotBugs cannot see, so the build went from a clean gate to a NoSuchMethodException that killed the event dispatch thread and left every later test timing out at five seconds a piece. Reflecting into privates is what made that possible, so the test is rewritten against behaviour: it holds a key, drives the timers with an explicit clock rather than waiting out the 800ms first-repeat delay, and asserts the repeats stop when a modal goes up. It fails without the fix. 5364 core tests pass; SpotBugs, PMD and Checkstyle report nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4b1456cef9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
… goldens build-test (8) failed on seventeen forbidden PMD violations, all of them residue from moving the window API to Desktop: nine index loops over the Window[] getWindows() returns ForLoopCanBeForeach MAIN_LONG_PRESS_ID, orphaned by the slot removal UnusedPrivateField WindowManager import, orphaned by the modality move UnnecessaryImport TopLevelContainer import in ScaleImageButton UnnecessaryImport five fully qualified Desktop.getInstance() calls UnnecessaryFullyQualifiedName Worth recording why local verification missed them: PMD is enforced by .github/scripts/generate-quality-report.py against its own forbidden list, not by the Maven build, so `mvn verify` passes with violations present. Reading maven/core-unittests/target/pmd.xml is the local equivalent, and it now reports zero -- as do SpotBugs and Checkstyle. The Linux windowed goldens are re-recorded, both arches. Taking the title area out of Window changed exactly what it should: the in-content title label is gone and the content starts at the top of the window instead of below it. The window dimensions are unchanged, and the fourteen windowed captures were the only ones that moved -- nothing else in the suite differs. Checked by looking at the images side by side rather than by trusting the dimensions. Windows and Mac Catalyst carry the same fourteen and will need the same treatment once their runs report. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The fix itself went in with a14ff33, whose subject is about PMD and goldens and says nothing about it -- my mistake, and worth stating rather than quietly leaving the history misleading. The defect: the material pull-to-refresh drag listener is built once and kept for the life of the component, capturing the top level it was created in. A component moved to another Form or Window is re-registered on the new one but the listener still targets the old, so the overlay goes up on the top level the component left and the release arriving on the new one finds nothing to finish -- the refresh task never runs. The host is now resolved when the drag happens instead of captured when the listener is built. Pre-existing: master captures `final Form p` in exactly the same place. This branch only widened where it bites, by letting a component move between a Form and a Window. The test moves a scrollable pull-to-refresh container from a Form into a Window, pulls, and asserts the overlay lands on the window and nothing is added to the form. It fails with the captured host. The container is given content that overflows on purpose -- the gesture is gated on the container actually being scrollable, and a first attempt with a single label produced no overlay either way, which would have passed for the wrong reason. 147 WindowTest tests pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0b0be1a5cb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The array and dict tags were made structural earlier in this branch; the key tags were left matching "</key>" as a literal, so a fragment writing "</key >" -- which is valid XML -- reported the key as absent. Both callers then fail, in opposite directions. plistKeyIndex tells the injection path there is no UIApplicationSceneManifest, so it appends a second one beside the application's own and the bundle ships duplicate keys. plistKeyEnd loses the key's value, so the validation path rejects a Mac Catalyst build that is correctly configured. Both now use plistCloseElementIndex, the closing-tag counterpart already written for the container tags. Two tests, and both fail against the literal matching: one on a single key with a spaced closing tag, one on a whole scene manifest where every closing tag is spaced. 912 plugin tests pass; SpotBugs zero. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6c4d17bba7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…restore AWT delivers componentShown / componentHidden for every visibility change, whoever caused it, and the port turned those into windowShowNotify / windowHideNotify. The existing comment claimed that was safe for the explicit path because Window.hide() and show() set nativeVisible before calling the manager, so the notification would find the state already correct. That holds only if the notification is delivered inline, and it is not: the AWT callback runs on the AWT thread and queues onto the Codename One event dispatch thread. A show and a hide in the same turn therefore both queue, and both run afterwards against the state the second one left. The pair reads as a minimize followed by a restore, and in the show-then-hide order the window finishes hidden while still marked iconified -- which is the state showModal() waits on, so its caller waits for good. The port now counts the visibility events it is about to cause, on the AWT thread where they are also delivered, and the listeners consume that count instead of reporting a lifecycle change. Counted only when the frame is really changing state, since AWT delivers nothing when it is not and the count would otherwise be spent on a later event the user caused. The test drives show() then hide() in one turn and asserts no Minimized or Restored is reported. Without the correlation the window reports: [Resized, Shown, Hidden, Moved, Resized, Restored, Minimized] 278 JavaSE port tests pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5a53552596
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
… sweep Two findings, both mine. edtLoopImpl takes an early return while the main form is running a transition, which is right for the main surface and wrong for the windows: a secondary window is an independent native window with no part in that transition, and it stopped painting and animating for the transition's duration. The new test shows it painting zero times either side of one. The window pass now runs before that return. Input deliberately stays queued there rather than being dispatched. That is how the main surface already behaves during a transition, and draining the shared queue from that branch would change the main path's semantics to fix a window's -- the wrong trade for a few hundred milliseconds of deferred input. The other is the third report of one bug class: a closing tag matched as a literal. plistStringValueAfter searched for "</string>", so a delegate written "</string >" looked absent and aborted a correctly configured Catalyst build. Rather than fix the reported line alone, every remaining literal closing tag in the file was enumerated -- which turned up a second one, mergeUserActivityTypes parsing "<array>" and "</array>" out of the injected fragment. Both now use the structural lookups. The one literal left reads our own generated Info.plist line by line, where we control the spelling. Enumerating should have happened when the first of these was fixed; matching the reported pattern is what let it come back twice. 5366 core tests and 92 IPhoneBuilder tests pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8a785b1283
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
minimize(), toggleMaximize() and requestWindowFocus() called the window manager straight from the calling thread, unlike show(), hide(), restore() and dispose(). setResizable() and setAlwaysOnTop() did the same with their native half. I had looked at exactly these methods when centerOn() and restore() were marshalled, and left them alone on the grounds that each was a single call with no read to go stale and that the ports marshal internally. The second half of that was wrong. WindowsWindowManager.minimize() resolves the peer to a slot index on whatever thread calls it and hands that index to the native layer -- so a background caller can read a slot an EDT disposal is tearing down, and the same shape appears in setTitle, setResizable, setAlwaysOnTop, setMinimumSize, requestFocus and toggleMaximize. The developer guide also promises callers this is marshalled for them. The three commands marshal whole. The two setters write their field on the calling thread, so a getter stays consistent, and marshal only the SPI call. TestWindowManager now records any window-manager call that arrives off the event dispatch thread, which is the property worth asserting -- not whether the call eventually happened. The test drives all three controls from a background thread and expects an empty list; without the fix it names the offender: expected <[]> but was <[minimize]>. 5367 core tests pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1c1298262d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Window.getMonitor() answers from a lazy cache, and moving the window is exactly what invalidates it -- but moved() fired the Moved event without touching it. The cache is otherwise refreshed only by monitorChanged(), which both the JavaSE componentMoved path and the Windows WM_MOVE path enqueue *after* Moved, so a Moved listener asking getMonitor(), getScale() or getDensity() was told which monitor the window had been on before it moved, and nothing later told the application to ask again. Cleared rather than recomputed, so a move nobody asks about costs nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Same 14 that shifted on Linux, for the same reason: a Window no longer paints a title area into its own content, since the title belongs to the native chrome the capture does not include. Content moves up by the height of that strip and is otherwise unchanged. The x64 and cross-compiled runs produce these byte-for-byte identically, so one golden set covers both. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Container is the nearest common supertype of Form and Window and already
declared inert hooks for exactly this, so the chains of
if (top instanceof Form) { ((Form) top).x(); }
else if (top instanceof Window) { ((Window) top).x(); }
were ceremony around a virtual call. TopLevelSupport had eight of them and
now has none. The same treatment covers the command notification behind a
Button and a List, the title-area height, the clear command a TextField
displaces, the selection a press paints, the native peer of a top level and
the safe area a container snaps to.
Two hooks were missing and are added beside the existing ones:
addComponentToTopLevel/removeComponentFromTopLevel. Form forwards them to
its existing Form-named pair so Toolbar, MenuBar and SideMenuBar keep the
call sites they have on master; Window has no such callers and is renamed
onto the hook.
Display keeps one new package-private method, mainSurfacePressIsOver, so
Form can answer showsSelectionFor from the pointer state that lives there
rather than Display asking what kind of top level it is looking at.
SearchBar goes back to Form entirely. It reached for the top level because
a Window used to have a Toolbar; a Window has neither toolbar nor title
area now, so a SearchBar can only ever be in a Form. It keeps
getComponentForm() rather than master's cast of getParent(), which
ParparVM would not have checked.
Behaviour is preserved deliberately in two places the hooks would have
changed: a component with no top level still answers selection from the
main surface, and a window that has not been shown yet still reports the
primary monitor rather than borrowing the main window's.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CodenameOneImplementation is the largest class in the framework and this branch had been adding to it: a public static inner class plus six public methods that took the surface as an Object and cast it back on every call. PaintSurface is now a class of its own in com.codename1.impl, owning its queue, its graphics and the paint routine that drains one into the other. Callers hold a typed reference and say surface.clear(), surface.repaint(a), surface.paintDirty(w, h) -- so the Object handles, the asPaintSurface cast and the six wrappers are gone. What is left on the implementation is one factory, createPaintSurface, and a package-private hook the surface calls to unregister itself. Two fields it reads (displayLock, getPaintableBounds) widen from private to package private, which keeps them invisible to ports: they live in com.codename1.impl and every port is in a subpackage of it. The scratch rectangle for paintable bounds becomes per surface rather than one shared field, so one surface's paint pass cannot read another's. Window input was split across two classes for no reason: ten entry points on Desktop and six on Display, with ports calling whichever half happened to hold theirs. All sixteen are on Desktop now, which is the class that owns the windows; Display keeps the bodies as package-private *Impl helpers, matching keyPressedImpl and the rest that were already shaped that way. getWindowPeerForComponent moves to Desktop for the same reason -- which window a component is in is not a question about the application's main surface -- and Display loses it entirely. Null surfaces stay tolerated. The handle API ignored them and a window that was never shown has none, so Window guards rather than letting hide() or dispose() start failing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The pointer drag-activation filter for windows was five parallel arrays on CodenameOneImplementation with a claim/release protocol and a fixed cap of eight, plus windowDragSlot to hand entries out and releaseWindowDragSlot to give them back. Its own comment described the failure: a window disposed with a press still down never releases, window ids are never reused, and after eight of those the allocator answers -1 and every window silently loses the filter -- so a pixel of jitter becomes a real drag. That state belongs to a window. PointerDragActivation is now a small class in com.codename1.impl, each Window owns one, and Desktop routes the implementation to the right one by id. Nothing to claim, nothing to release, nothing to leak, and no cap. The main surface is untouched: it keeps the same four fields on the implementation it has always had. everyOpenWindowFiltersItsOwnDragNoMatterHowMany drags sixteen windows at once and checks each filters its own jitter. Reintroducing the cap makes it fail at window 8, which is where the old design stopped filtering. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The developer guide isn't dated the way a blog post is, so framing the chapter as news ages badly: "Codename One applications have always had exactly one window" reads as false the moment the reader is holding the API that makes it false, and "the new TopLevelContainer" is still claiming to be new years from now. The chapter now says what is true rather than what changed -- a phone application has one window and a desktop one usually doesn't -- and describes the components as asking getTopLevelContainer() rather than as having been migrated to it. One stale claim went with it: TopLevelContainer was described as carrying the toolbar. It doesn't, and a Window has no Toolbar at all; its title and menus belong to the platform chrome. The Toolbar is now listed with the other Form-only members. Vale and LanguageTool both report zero on the file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The six windowed screenshot baselines reported SKIPPED on android, ios-gl,
ios-metal, javascript, tvos and watchos -- thirty-six rows on the public
port status table, each needing an erratum explaining that the reader should
not count it against the port. A port with one full-screen surface was never
asked to open a second window, so there is nothing there to skip.
port_status.json can now scope a test to the ports it applies to:
{"test": "WindowLayoutTest", "ports": ["linux-x64", ...]}
and every per-port check -- what a report may carry, what it must carry, and
what build_report tracks in the first place -- reads that scope instead of
the whole test list. Those ports' reports no longer carry the six at all,
and the six errata are gone with them. The tests themselves report nothing
on a port without a windowing system rather than reporting a skip.
MultiWindowApiTest stays unscoped. It asserts the contract on every port,
including that the API throws where windows are unsupported, and it passes
everywhere.
Scoping must not become a way to stop reporting quietly, so it is fenced:
a scope naming a port that does not exist, or naming none at all, fails
validation, and a report missing a test scoped *to* it is still drift.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The port status conflicts are all one change: #5590 stopped treating the checked-in snapshots as contract. Reports are CI output, a report that predates a registered test is ordinary drift rather than a build failure, and "every registered test runs on every port" moved to the coverage gate, which reads the reports the ports actually published. So this branch stops hand-editing the eleven snapshots. They are taken from master verbatim; all eleven simply predate the window tests, which is what drift means and what the page renders as "awaiting this port's next run". The scoping this branch added is reapplied on top of the new shape, and it matters more under it than it did before. The coverage gate reads a test absent from a port's report as dropped once any earlier run elsewhere carried it -- so the first desktop port to publish WindowLayoutTest would have made every phone port look like it had dropped a test it can never run. tests_for_port() now bounds the absence check, build_report, and the publishable drift split. MultiWindowApiTest stays unscoped: it asserts the contract on every port, including that the API throws where windows are unsupported. Verified on the merged tree: 5478 core tests pass, PMD, SpotBugs and Checkstyle report zero, javase, ios, windows and linux compile, and all 81 port-status tests pass. Removing the scope from the coverage gate fails test_coverage_does_not_call_a_scoped_test_dropped_where_it_cannot_run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both were seeded from CodenameOneImplementation, which carries the older Oracle header. A newly added file has to use the Codename One GPLv2 + Classpath Exception header, which is what check-copyright-headers enforces. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Codename One has no windowing API. Even on JavaSE, Mac, Win32 and Linux, where the OS has real windows, an app gets exactly one, welded to a single global "current Form":
CodenameOneImplementationholds onecurrentForm,Display.edtLoopImplpaints one surface per tick,paintDirtyuses one global paint queue clipped togetDisplayWidth()/getDisplayHeight(), andhandleEventroutes every input event to one form. Everything that looks like a second window today —Sheet,InteractionDialog,ToastBar,Dialog— is an overlay inside the current form's layered panes.This adds real native windows, each rendering its own component tree, on all four desktop targets, without changing the single-form model mobile depends on.
API
TopLevelContaineris the shared contractFormandWindowboth implement. Its members were chosen by counting actualgetComponentForm().<method>()chains inCodenameOne/src, and every one of them was already public onFormwith an identical signature, soFormneeded nothing beyond theimplementsclause andasContainer()— a Java interface cannot extend a class, so without that bridge aTopLevelContainerreference cannot go anywhere aComponentis wanted.Window extends Container implements TopLevelContainer. Inside a windowgetComponentForm()returnsnull, by design;Component.getTopLevelContainer()is the new resolution API, and core now uses it internally.DesktopandMonitorare the public parallel toDisplayfor "what screens exist and what windows are open", including per-monitor DPI and backing scale;Displaykeeps meaning "the main app surface" exactly as before.Modality is enforced in core rather than per port, so it behaves identically everywhere:
Displaykeeps a modal stack andhandleEventdrops input to blocked windows.showModal()parks the caller throughinvokeAndBlockthe wayDialogalready does, which re-enters the event loop — so every other window stays live and repainting while a modal is up.Implementation
The impl SPI is a single
WindowManagerfacade returned fromCodenameOneImplementation.getWindowManager(). Returningnullis the capability query, so there is no separateisMultiWindowSupported()that could drift from it. Only genuinely universal operations are abstract; anything a port might not offer has a no-op default, so adding a capability later never breaks a port.Per-window paint state moves into a
PaintSurfacevalue object with the main window as instance zero;getCodenameOneGraphics(),repaint(Animation),cancelRepaintandhasPendingPaints()keep their signatures, so every existing port still compiles and behaves.paintDirty()'s body is parameterized rather than globally rebound — a global "active surface" was rejected becauseDisplay.getDisplayWidth()is public and callable off the EDT, so a live binding would change its answer re-entrantly across ~210 call sites.Events pack the window id into the type word (
type | (windowId << 8)). Window 0 is numerically identical to the previous wire format, so drag coalescing and the stack-swap logic are untouched. The port is handed the id at creation and echoes it back, so there is no peer-to-window map on the off-EDT input path.Ports: JavaSE (per-canvas graphics de-singletonization —
getNativeGraphicsused to return one shared instance, andisScreenGraphicswas an identity check against one buffer, so a second window would have drawn into the first window's pixels), native Windows (Direct2D per-window render targets,GWLP_USERDATAidentity,WM_DPICHANGED), native Linux (per-window cairo back buffer, GTK closure data), and Mac Catalyst (UIWindowSceneper window). Peer components and native text editing work in every window on every one of the four. iOS, Android and JavaScript need no port changes at all: they inherit the false capability and the throw lives in core.Latent bug fixed on the way
handleEventreturnedoffsetunchanged when the form was null, while the caller loopswhile (offset < actualTmpPointer)— an infinite EDT spin. It is unreachable today only because all nine entry points guard ongetCurrentForm() != null; window disposal with events in flight makes it reachable. It is now askipEventthat drains the packet so the rest of the batch still dispatches.Testing
Core unit tests drive a scriptable fake
WindowManageronTestCodenameOneImplementation— settable, defaulting to null, so the unsupported path is the default — covering lifecycle, paint isolation, event routing, modality including a modal window nested in a modal dialog, theTopLevelContainercontract, and a fake multi-monitor table at mixed DPI. JavaSE port tests cover the per-canvas graphics resolution, which is the riskiest edit here and had no coverage before.The centrepiece is a windowed screenshot family in
scripts/hellocodenameone: representative UI re-run inside a real window at several sizes and compared against its own goldens. A picture of a window proves nothing; layout, scrolling, graphics, layered overlays, native editing and modality rendering correctly on a non-primary surface is the actual claim. The three sizes, including a deliberately non-square one, are what prove content lays out to the window rather than toDisplay.getDisplayWidth(). This needed per-window capture on every port, since the existing pipeline can only see the main framebuffer.Mac Catalyst was built and run on real hardware for this branch rather than left to CI, because it is the hardest of the four. That found four defects compiling never would have:
capture()was unimplemented; the readiness probe was a false positive; captures were taken before the first paint; and the scene was never asked for the geometry the window was created with, so several captures came out at the main display size with the window's content in the corner.Known scope limits, documented
HTMLComponent, accessibility on secondary windows,Dialog.show()from inside a window and form transitions into or out of one are out of scope for v1 and called out in the guide.Display.getDisplayWidth()/getDisplayHeight()keep reporting the main window; components inside a window use their top level's size.🤖 Generated with Claude Code