Skip to content

Native desktop windows - #5556

Open
shai-almog wants to merge 284 commits into
masterfrom
feat-desktop-windows
Open

Native desktop windows#5556
shai-almog wants to merge 284 commits into
masterfrom
feat-desktop-windows

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

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": CodenameOneImplementation holds one currentForm, Display.edtLoopImpl paints one surface per tick, paintDirty uses one global paint queue clipped to getDisplayWidth()/getDisplayHeight(), and handleEvent routes 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

TopLevelContainer is the shared contract Form and Window both implement. Its members were chosen by counting actual getComponentForm().<method>() chains in CodenameOne/src, and every one of them was already public on Form with an identical signature, so Form needed nothing beyond the implements clause and asContainer() — a Java interface cannot extend a class, so without that bridge a TopLevelContainer reference cannot go anywhere a Component is wanted.

Window extends Container implements TopLevelContainer. Inside a window getComponentForm() returns null, by design; Component.getTopLevelContainer() is the new resolution API, and core now uses it internally. Desktop and Monitor are the public parallel to Display for "what screens exist and what windows are open", including per-monitor DPI and backing scale; Display keeps meaning "the main app surface" exactly as before.

Modality is enforced in core rather than per port, so it behaves identically everywhere: Display keeps a modal stack and handleEvent drops input to blocked windows. showModal() parks the caller through invokeAndBlock the way Dialog already 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 WindowManager facade returned from CodenameOneImplementation.getWindowManager(). Returning null is the capability query, so there is no separate isMultiWindowSupported() 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 PaintSurface value object with the main window as instance zero; getCodenameOneGraphics(), repaint(Animation), cancelRepaint and hasPendingPaints() 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 because Display.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 — getNativeGraphics used to return one shared instance, and isScreenGraphics was 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_USERDATA identity, WM_DPICHANGED), native Linux (per-window cairo back buffer, GTK closure data), and Mac Catalyst (UIWindowScene per 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

handleEvent returned offset unchanged when the form was null, while the caller loops while (offset < actualTmpPointer) — an infinite EDT spin. It is unreachable today only because all nine entry points guard on getCurrentForm() != null; window disposal with events in flight makes it reachable. It is now a skipEvent that drains the packet so the rest of the batch still dispatches.

Testing

Core unit tests drive a scriptable fake WindowManager on TestCodenameOneImplementation — 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, the TopLevelContainer contract, 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 to Display.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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

f.keyPressed(inputEventStackTmp[offset]);

P1 Badge Dispatch key events to the window's focused component

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".

Comment thread CodenameOne/src/com/codename1/ui/Window.java Outdated
Comment thread CodenameOne/src/com/codename1/ui/Window.java Outdated
Comment thread Ports/WindowsPort/src/com/codename1/impl/windows/WindowsWindowManager.java Outdated
Comment thread CodenameOne/src/com/codename1/ui/Window.java Outdated
Comment thread CodenameOne/src/com/codename1/ui/Window.java Outdated
Comment thread CodenameOne/src/com/codename1/ui/Window.java
Comment thread CodenameOne/src/com/codename1/ui/Window.java
@shai-almog

shai-almog commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 12 screenshots: 12 matched.
✅ JavaSE simulator integration screenshots matched stored baselines.

@github-actions

github-actions Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs [Report archive]
    • ByteCodeTranslator: 0 findings (no issues)
    • android: 0 findings (no issues)
    • codenameone-maven-plugin: 0 findings (no issues)
    • core-unittests: 0 findings (no issues)
    • ios: 0 findings (no issues)
  • PMD: 0 findings (no issues) [Report archive]
  • Checkstyle: 0 findings (no issues) [Report archive]

Generated automatically by the PR CI workflow.

@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread Ports/LinuxPort/nativeSources/cn1_linux_desktopwindow.c Outdated
Comment thread Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java Outdated
Comment thread CodenameOne/src/com/codename1/ui/Window.java Outdated
Comment thread CodenameOne/src/com/codename1/ui/Window.java Outdated
Comment thread CodenameOne/src/com/codename1/ui/Window.java
@github-actions

github-actions Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Developer Guide build artifacts are available for download from this workflow run:

Developer Guide quality checks:

  • AsciiDoc linter: No issues found (report)
  • Vale: No alerts found (report)
  • Paragraph capitalization: No paragraph capitalization issues (report)
  • LanguageTool: No grammar matches (report)
  • Image references: No unused images detected (report)

@shai-almog

shai-almog commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 151 screenshots: 151 matched.

Native Android coverage

  • 📊 Line coverage: 9.03% (8944/99009 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.83% (46223/523333), branch 3.45% (1709/49575), complexity 3.45% (1826/52883), method 5.30% (1476/27827), class 10.67% (397/3720)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 9.03% (8944/99009 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 8.83% (46223/523333), branch 3.45% (1709/49575), complexity 3.45% (1826/52883), method 5.30% (1476/27827), class 10.67% (397/3720)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6367 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.ClassReader – 0.00% (0/1524 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1187 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.MethodWriter – 0.00% (0/922 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/736 lines covered)
      • com.google.common.cache.com.google.common.cache.LocalCache$Segment – 0.00% (0/726 lines covered)
      • okio.okio.Buffer – 0.00% (0/687 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/625 lines covered)
      • org.jacoco.agent.rt.internal_0e20598.asm.org.jacoco.agent.rt.internal_0e20598.asm.Frame – 0.00% (0/570 lines covered)

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend scalar fallback (no native SIMD)
SIMD int-add (64K x300) java 150ms / native 134ms = 1.1x speedup
SIMD float-mul (64K x300) java 107ms / native 65ms = 1.6x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 94.000 ms
Base64 CN1 decode 84.000 ms
Base64 native encode 430.000 ms
Base64 encode ratio (CN1/native) 0.219x (78.1% faster)
Base64 native decode 270.000 ms
Base64 decode ratio (CN1/native) 0.311x (68.9% faster)
Image encode benchmark status skipped (SIMD unsupported)

@shai-almog

shai-almog commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 163 screenshots: 163 matched.
Native Windows port (x64 / Intel-AMD): full hellocodenameone screenshot suite rendered offscreen with Direct2D/DirectWrite, plus the real benchmarks (base64 native/CN1/SIMD, image createMask/applyMask/modifyAlpha/PNG/JPEG, SSE2 SIMD kernels). Compared against the in-repo baseline in scripts/windows/screenshots.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 59ms / native 4ms = 14.7x speedup
SIMD float-mul (64K x300) java 61ms / native 4ms = 15.2x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 175.000 ms
Base64 CN1 decode 120.000 ms
Base64 SIMD encode 93.000 ms
Base64 encode ratio (SIMD/CN1) 0.531x (46.9% faster)
Base64 SIMD decode 91.000 ms
Base64 decode ratio (SIMD/CN1) 0.758x (24.2% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 37.000 ms
Image createMask (SIMD on) 24.000 ms
Image createMask ratio (SIMD on/off) 0.649x (35.1% faster)
Image applyMask (SIMD off) 69.000 ms
Image applyMask (SIMD on) 47.000 ms
Image applyMask ratio (SIMD on/off) 0.681x (31.9% faster)
Image modifyAlpha (SIMD off) 49.000 ms
Image modifyAlpha (SIMD on) 377.000 ms
Image modifyAlpha ratio (SIMD on/off) 7.694x (669.4% slower)
Image modifyAlpha removeColor (SIMD off) 88.000 ms
Image modifyAlpha removeColor (SIMD on) 60.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.682x (31.8% faster)

@shai-almog

shai-almog commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 163 screenshots: 163 matched.
Native Windows port (arm64 / Apple Silicon - Arm): full hellocodenameone screenshot suite rendered offscreen with Direct2D/DirectWrite, plus the real benchmarks (base64 native/CN1/SIMD, image createMask/applyMask/modifyAlpha/PNG/JPEG, NEON SIMD kernels). Compared against the in-repo baseline in scripts/windows/screenshots.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 57ms / native 3ms = 19.0x speedup
SIMD float-mul (64K x300) java 56ms / native 4ms = 14.0x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 266.000 ms
Base64 CN1 decode 155.000 ms
Base64 SIMD encode 64.000 ms
Base64 encode ratio (SIMD/CN1) 0.241x (75.9% faster)
Base64 SIMD decode 63.000 ms
Base64 decode ratio (SIMD/CN1) 0.406x (59.4% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 12.000 ms
Image createMask (SIMD on) 7.000 ms
Image createMask ratio (SIMD on/off) 0.583x (41.7% faster)
Image applyMask (SIMD off) 23.000 ms
Image applyMask (SIMD on) 18.000 ms
Image applyMask ratio (SIMD on/off) 0.783x (21.7% faster)
Image modifyAlpha (SIMD off) 16.000 ms
Image modifyAlpha (SIMD on) 12.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.750x (25.0% faster)
Image modifyAlpha removeColor (SIMD off) 169.000 ms
Image modifyAlpha removeColor (SIMD on) 11.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.065x (93.5% faster)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread CodenameOne/src/com/codename1/ui/Display.java
Comment thread CodenameOne/src/com/codename1/ui/Window.java
Comment thread Ports/WindowsPort/src/com/codename1/impl/windows/WindowsWindowManager.java Outdated
Comment thread Ports/WindowsPort/src/com/codename1/impl/windows/WindowsWindowManager.java Outdated
Comment thread CodenameOne/src/com/codename1/ui/Window.java
Comment thread Ports/JavaSE/src/com/codename1/impl/javase/JavaSEWindowManager.java

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread CodenameOne/src/com/codename1/ui/Display.java Outdated
Comment thread CodenameOne/src/com/codename1/ui/Window.java
Comment thread docs/developer-guide/Desktop-Windows.asciidoc Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java
Comment thread Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java
Comment thread CodenameOne/src/com/codename1/ui/Window.java

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread CodenameOne/src/com/codename1/ui/Window.java Outdated
Comment thread CodenameOne/src/com/codename1/ui/Window.java
Comment thread Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java Outdated
Comment thread CodenameOne/src/com/codename1/ui/Window.java Outdated
@shai-almog

shai-almog commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 144 screenshots: 144 matched.
✅ Native Apple TV (tvOS, Metal) screenshot tests passed.

showModal() from a background thread returned before the window appeared.
show() only queues its work off the event dispatch thread, so the wait saw
a window that was not visible yet, concluded the modal was already over
and returned; the queued show() then acquired modality afterwards. The
show is awaited when the caller is not on the event dispatch thread.

Changing the owner of a window that already exists silently did nothing
useful and stranded its modal blocker: native ownership is fixed at
creation on every platform -- the owner HWND, the transient parent, the
JDialog's owner -- and release derives the blocked window from the field,
so it would have enabled the new owner and left the old one disabled. It
throws now rather than pretending.

The simulated pinch drag on JavaSE went to the main form even though the
press that started the gesture was tagged with the window, so a right
button or shift drag over a window dragged unrelated main form content.

Tooltips cannot work inside a window: TooltipManager schedules only when
getComponentForm() is non-null and displays through an InteractionDialog
on the current form. The half-wired call is gone and they are listed with
the other form-coupled overlays in the guide, rather than appearing to
work and silently doing nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread CodenameOne/src/com/codename1/ui/Window.java Outdated
Comment thread Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java
Comment thread CodenameOne/src/com/codename1/ui/Window.java Outdated
Minimizing a modal window ended its modality. hideNotify clears
nativeVisible, which showModal read as "the modal is over": the wait
returned and the blocker was dropped, so restoring the still-open window
left a modal on screen with input flowing to the windows behind it.
Iconification is tracked separately from an explicit hide now.

A native editor opened in a desktop window was removed from the primary
canvas, which is not its parent, so the removal did nothing: the editor
stayed on screen swallowing input and every further edit stacked another
one on top. Both teardown paths remove from the parent the editor
actually has, and the legacy AWT editor path now attaches to the owning
window's canvas too rather than always to the primary one.

hide() ran on the calling thread, unlike show() and dispose(), so hiding
from a background thread raced the event dispatch thread while it painted
or dispatched input. It marshals now, which is what the guide's threading
section already promised.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread Ports/iOSPort/nativeSources/CN1MacWindows.m Outdated
Comment thread Ports/iOSPort/nativeSources/CN1MacWindows.m
Comment thread Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java
Comment thread Ports/LinuxPort/nativeSources/cn1_linux_desktopwindow.c Outdated
@shai-almog

shai-almog commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 217 screenshots: 217 matched.
✅ Native Apple Watch (watchOS, Core Graphics) screenshot tests passed.

Mac Catalyst pointer coordinates arrived unscaled. UIKit reports a touch
location in points while the window is laid out in device pixels, so on a
Retina display every press, drag and release landed at half its rendered
position and only the top left of a window could be clicked where it
looked like it was.

A Catalyst secondary window received no hardware keyboard input at all.
Its scene is rooted at the window's own controller, and only the main
view controller implemented the UIKit press handlers, so nothing reached
the focused component. The window controller implements them now and
shares the main controller's UIKey mapping rather than duplicating a
hundred-case switch that would drift.

A CEF or browser peer inside a JavaSE window was inert: attaching and
positioning were fixed earlier, but CN1JPanel still hit tested, converted
coordinates and forwarded events against the primary canvas, which the
peer is no longer inside. It resolves its own window's canvas from its
Swing ancestry now, cached and dropped on re-parenting since these run for
every mouse move.

Linux secondary windows sent the raw GDK keyval as the key code. GDK
encodes many Unicode keyvals differently from their code point, so any
non-ASCII key produced the wrong character; the main window's
gdk_keyval_to_unicode conversion is mirrored.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread CodenameOne/src/com/codename1/ui/Window.java
Comment thread Ports/JavaSE/src/com/codename1/impl/javase/JavaSEWindowManager.java
Comment thread Ports/JavaSE/src/com/codename1/impl/javase/JavaSEWindowManager.java Outdated
A multi-pointer drag reached Component's version rather than the window's,
so a pinch was tested against the window itself and then collapsed to a
single coordinate: the pressed child got an ordinary one-finger drag and
never its pinch callbacks.

Moving a window within one monitor reported nothing. Every port only
raised the monitor-changed event, so the documented Moved event never
fired for an ordinary move and nothing could persist a window position.
All three desktop ports raise it now.

Releasing modality on JavaSE cleared an always-on-top the application had
asked for, because both went through the same frame property. The window
kept claiming it was always on top and never reapplied it, since the
native window already existed. The application's setting and the temporary
elevation a modal gets are tracked separately.

The fake window manager handed back a marker object for a window's
graphics, so flushing the event dispatch thread with a window open threw
rather than painting -- no existing test had pumped the loop with one
open. It returns real graphics sized to the window now.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread Ports/JavaSE/src/com/codename1/impl/javase/JavaSEWindowManager.java Outdated
Comment thread Ports/WindowsPort/nativeSources/cn1_windows_desktopwindow.cpp
Comment thread CodenameOne/src/com/codename1/ui/Window.java Outdated
Comment thread CodenameOne/src/com/codename1/ui/Window.java
shai-almog and others added 3 commits August 17, 2026 07:41
A negative coordinate meant "no position given", but a monitor left of or
above the primary display has a negative origin, so a window restored onto
one was centred on the primary display instead. All three ports take an
explicit flag now and honour the coordinate whatever its sign.

Minimizing on native Windows arrived only as a resize to zero, and Linux
had no iconification handler at all, so the framework went on treating a
minimized window as displayed: still painted, and an animation in it still
keeping the event dispatch thread awake. Both report hidden and shown now,
which is what the JavaSE port already did.

hide() skipped everything when the window was iconified, because that also
clears nativeVisible. The native window stayed alive, the modal blocker
stayed registered and showModal stayed parked even though the application
had explicitly asked to hide it.

A null parent peer meant two different things -- no owner, or an owner
that is the main form, which has no peer -- and ports could not tell them
apart: JavaSE ignored a form owner while Windows and Linux made even
unowned windows children of the main one. The flag is explicit now.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Produced by the cross-compiled Windows producer job and inspected rather
than taken on trust. Every one comes out at exactly the requested size
minus the platform's chrome -- 400x300 renders at 384x261, 900x700 at
884x661 -- which is the contract the harness checks, and the content is
real: the layout case shows every widget, the scroll case its list and
scrollbar, the overlay case its layer over the base content, and the modal
case a background window that is still painting while a modal is up.

The mappings needed globs. They are matched with fnmatch, so a bare
"Window-Layout" pattern matched nothing once the size suffix was appended
and every golden reported as unmapped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Produced by the Linux producer job and inspected, not taken on trust.
Unlike Windows these come out at exactly the requested size, because the
GTK drawing area fills the window rather than sitting inside native
chrome, and the content is real: every widget in the layout case, the
list and its scrollbar in the scroll case, and a background window that
is still painting while a modal is up.

x64 and arm64 are byte identical, which is worth noting on its own -- the
port renders the same on both architectures -- but they are stored in the
two directories the workflow compares against separately, as every other
Linux baseline is. The musl variant is not compared and needs none.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shai-almog

shai-almog commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 181 screenshots: 181 matched.
✅ JavaScript-port screenshot tests passed.

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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread CodenameOne/src/com/codename1/ui/Window.java
Comment thread CodenameOne/src/com/codename1/ui/Window.java
Comment thread CodenameOne/src/com/codename1/ui/Window.java
Comment thread Ports/JavaSE/src/com/codename1/impl/javase/JavaSEWindowManager.java
Comment thread Ports/iOSPort/nativeSources/CN1MacWindows.m Outdated
Comment thread CodenameOne/src/com/codename1/ui/Display.java Outdated
Comment thread CodenameOne/src/com/codename1/ui/Window.java
@shai-almog

shai-almog commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 163 screenshots: 163 matched.
Native Windows port, REAL shipping pipeline: the hellocodenameone screenshot suite rendered by a binary CROSS-COMPILED on Linux (clang-cl + xwin, WebView2 linked) and RUN on a Windows x64 runner. Compared against the in-repo baseline in scripts/windows/screenshots.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 59ms / native 4ms = 14.7x speedup
SIMD float-mul (64K x300) java 69ms / native 7ms = 9.8x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 170.000 ms
Base64 CN1 decode 123.000 ms
Base64 SIMD encode 91.000 ms
Base64 encode ratio (SIMD/CN1) 0.535x (46.5% faster)
Base64 SIMD decode 95.000 ms
Base64 decode ratio (SIMD/CN1) 0.772x (22.8% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 27.000 ms
Image createMask (SIMD on) 21.000 ms
Image createMask ratio (SIMD on/off) 0.778x (22.2% faster)
Image applyMask (SIMD off) 52.000 ms
Image applyMask (SIMD on) 49.000 ms
Image applyMask ratio (SIMD on/off) 0.942x (5.8% faster)
Image modifyAlpha (SIMD off) 281.000 ms
Image modifyAlpha (SIMD on) 49.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.174x (82.6% faster)
Image modifyAlpha removeColor (SIMD off) 57.000 ms
Image modifyAlpha removeColor (SIMD on) 48.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.842x (15.8% faster)

shai-almog and others added 2 commits August 23, 2026 18:25
…roll over

Two gaps against the Form path, both in Window.pointerPressed.

The press handle was created after the window's pointer-pressed listeners
ran. A listener can enter a nested event loop -- showModal() does -- and the
matching physical release is then processed inside it. With the handle
created afterwards that nested release found no gesture to clear, and the
method went on to install a fresh press whose release had already happened,
leaving the component latched until some later gesture freed it. Form
creates its handle before firing listeners; so does the framework's own
press record, for exactly this reason. Moved.

A press landing on a still-gliding container stopped the motion, cleared
pressedCmp and returned. Stopping the glide is right, but it was only half
of what Form does: Form re-enters the drag path so the same physical gesture
takes the scroll over, while here every following drag packet had no target
and the user had to lift and press again. The press now cancels the glide,
primes drag and drop, and re-enters through this window's drag path -- not
Display.pointerDragged(), which is the main surface's and would deliver to
the current Form instead.

It hands pressedCmp to the component the scroll was taken over from. Form
reaches the same place differently, by re-resolving the component under the
pointer whenever it has no pressed one; giving this window's drag path that
same fallback was tried and rejected -- it changed routing for every gesture
and broke five unrelated tests.

While here, drag events now carry setPointerPressedDuringDrag as Form's do,
read and cleared in the scalar path and reported without clearing in the
multi-pointer one, matching Form on both counts.

Both tests fail without their own fix. Each disposes its window in a finally,
because an assertion that throws before dispose leaves a window showing and
times out the next test's setup.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Window.capture() falls back to re-rendering the component hierarchy when the
window manager returns no pixels. That fallback produces a plausible image of
the right size, so a capture path that never reads the real surface looks
exactly like one that does -- which is how the missing Windows override went
unnoticed in the first place.

The Windows manager now says so, once per process, when the native capture
comes back empty. Without it the only way to tell a live readback from a
silent fallback is to find a pixel that differs, and the windowed goldens are
byte-identical either way: the harness stops editing before capturing, so no
native editor is in frame to give it away.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 150a2f2890

ℹ️ 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".

Comment thread CodenameOne/src/com/codename1/ui/Window.java
Comment thread CodenameOne/src/com/codename1/ui/Window.java
shai-almog and others added 2 commits August 23, 2026 19:06
setWindowLocation already carries a comment about this trap: the read has to
happen on the event dispatch thread with the write, because setWindowBounds
marshals itself and reading beforehand queues a move carrying the old size.
centerOnDesktop(), centerOn() and restore() had the same shape and were
missed.

Both centring methods read the window's bounds, compute a position and only
then call something that marshals. From a background thread that computes
against geometry a queued resize is about to replace, so a caller that
resized and then centred got a window centred for the size it no longer has.

restore() is an ordering problem rather than an arithmetic one.
showOwnerChain() may queue the owner's show(), while the native restore ran
immediately -- so the child could reach the platform ahead of its owner, and
a WindowManager call happened off the event dispatch thread, which is the
only context that SPI is defined in.

All three now marshal the whole method.

Checked the rest of the class by enumerating every public method that touches
the window manager or reads bounds rather than grepping for the pattern:
setTitle, setResizable, setDecorated, setAlwaysOnTop, setUtilityWindow,
setWindowIcon, setMinimumWindowSize, minimize, toggleMaximize and
requestWindowFocus are each a single call with no read to go stale, and every
port marshals internally, so they are left alone deliberately.

Both tests fail without their own fix: the centring one is out by exactly
half the size difference, and the restore one sees the port called before the
queue drains.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reverts the Catalyst halves of c1e1a76 (outer-bounds contract) and
1c5ee65 (raster release). The Linux half of the bounds work is untouched.

The mac-native suite went from 162/162 to 148/162 on this branch, and not by
the golden churn I predicted. The app died of an uncaught NSException during
WindowScrollTest, so the eleven windowed tests after it never ran at all:

  -[MTLRenderPassColorAttachmentDescriptorArrayInternal mtlMutableTexture]:
      unrecognized selector sent to instance 0x600001d421c0
  at CN1MetalBeginMutableImageDraw + 40
  from -[CodenameOne_GLViewController drawFrame:allowInactive:]

That is a use-after-free wearing a wrong-selector costume: the GLUIImage had
been freed and its memory recycled into a Metal descriptor array.

The freed image is the window raster released in 1c5ee65. The reasoning
behind that release was wrong in a specific way worth recording: I checked
that capture() copies rather than retaining the live raster, and that the
paint loop re-fetches the graphics each frame, and concluded nothing else
held it. Both are true and neither is sufficient -- Catalyst defers drawing
through queued ExecutableOps, and CodenameOne_GLViewController passes their
opTarget straight to CN1MetalBeginMutableImageDraw. An op queued before the
release still names the raster, so freeing it early is exactly the
unretained-op-target crash this port has hit before. Doing it safely means
draining or retaining through that queue, which is why the finalizer was
left to do the freeing.

The bounds half goes back with it for a different reason: it had no effect.
Window-Layout still matched its stored golden pixel for pixel, so the
captured content was the full requested size and the outer-frame request
never reached the window. Keeping an inert change that ships alongside a
crash helps nobody.

So Catalyst keeps the contract it had: setWindowBounds sizes the content
rather than the frame, which still disagrees with Windows. The Linux fix
stands, and the review thread stays open, because two of the three ports
still do not agree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ce37e11785

ℹ️ 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".

Comment thread Ports/LinuxPort/src/com/codename1/impl/linux/LinuxWindowManager.java Outdated
shai-almog and others added 10 commits August 24, 2026 03:07
Hiding an owner cascades to the windows it owns, and every port reports those
children through windowHideNotify(). That is the minimize path, so the child
keeps its modal registration deliberately -- hideNotify() cannot tell an
owner cascade from a minimize, and a minimized modal is still open and still
modal. But isBlockedByModal() consulted every registered modal without ever
asking whether it was reachable, so an application modal went on blocking the
main surface and every unrelated window while being on nobody's screen, with
nothing available to dismiss it, until the owner was shown again.

Two halves, and the test needs both.

isBlockedByModal() now skips a modal with an owner that is not showing. The
modal's own visibility is deliberately not consulted: a minimized modal must
keep blocking, which is what stops a modal quietly releasing the application.
An owner that is not showing is different -- its children went with it and
cannot be restored on their own.

And syncNativeModalBlocking() now runs on the SHOWN and HIDDEN callbacks. It
was called only from push, pop and dispose, so the ports were never told the
block had changed: the framework would have stopped blocking while the
platform kept the main surface disabled.

The test asserts through an unrelated window's peer rather than a private
predicate, because what an application modal costs when it will not let go is
every other surface. It fails with either half removed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread CodenameOne/src/com/codename1/ui/Component.java
shai-almog and others added 2 commits August 24, 2026 13:00
… 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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread Ports/JavaSE/src/com/codename1/impl/javase/JavaSEWindowManager.java
…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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread CodenameOne/src/com/codename1/ui/Display.java
… 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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread CodenameOne/src/com/codename1/ui/Window.java
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>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread CodenameOne/src/com/codename1/ui/Window.java
shai-almog and others added 2 commits August 24, 2026 14:53
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant