Skip to content

Fix #5028 / #5029: InteractionDialog popup overlaps target when rect straddles midline#5030

Merged
shai-almog merged 2 commits into
masterfrom
fix-5028-5029-popup-overlaps-target
May 24, 2026
Merged

Fix #5028 / #5029: InteractionDialog popup overlaps target when rect straddles midline#5030
shai-almog merged 2 commits into
masterfrom
fix-5028-5029-popup-overlaps-target

Conversation

@shai-almog
Copy link
Copy Markdown
Collaborator

Summary

Root cause

showPopupDialogImpl picked popup placement purely from which half of the screen the anchor rect sat in:

if (rect.getY() + rect.getHeight() < availableHeight / 2) {
    // popup downwards
} else if (rect.getY() > availableHeight / 2) {
    // popup upwards
} else if (rect.getY() < availableHeight / 2) {
    // popup over aligned with TOP of rect, but inset a few mm
    y = rect.getY() + CN.convertToPixels(3);   // <-- draws ON TOP of target
    ...
} else {
    // popup over aligned with bottom of rect
    ...
}

When the rect straddled the midline, neither of the first two branches matched, and control fell into the "popup over" branches that drew the popup on top of the target. Two user-visible symptoms reported against the same code:

Fix

Choose placement by available space instead of midline:

  1. If spaceBelow >= prefHeight → place below.
  2. Else if spaceAbove >= prefHeight → place above.
  3. Else pick the side with more room.
  4. Only fall back to the historical "over the rect" branches when neither side has any room at all (degenerate: rect fills the viewport top-to-bottom).

The historical fallback branches are kept verbatim so the truly degenerate "no room either side" case still renders the popup somewhere visible rather than off-screen.

Test plan

  • mvn -pl core-unittests test -DunitTests -Dtest=InteractionDialogTest,PickerTest — 11 tests pass.
  • Confirmed the two new unit tests (showPopupDialogStraddlingMidlineDoesNotOverlapTarget, showPopupDialogArrowDirectionConsistentWithPlacement) fail without the fix with the exact overlap coordinates, e.g. #5028: popup [933..967) overlaps anchor [930..990), and pass with the fix.
  • Full core-unittests suite: 2550 tests pass, 0 failures.
  • Reviewer: drop jsfan3's / ThomasH99's reproducer from InteractionDialog not positioned correctly when pointing to an element positioned roughly in the middle of the screen #5028 into the simulator, scroll the target rect to the vertical middle, click — the popup should land above or below the target, never covering it, and the arrow should point at it on the first paint.

Fixes #5028.
Fixes #5029.

🤖 Generated with Claude Code

…straddles vertical midline

`showPopupDialogImpl` picked popup placement purely from which half of
the screen the anchor rect sat in. When the rect straddled the midline,
neither the "popup downwards" branch (`rect.bottom < availableHeight/2`)
nor the "popup upwards" branch (`rect.y > availableHeight/2`) matched,
and control fell into a "popup over aligned with top of rect" branch
that set `y = rect.getY() + 3mm` -- drawing the popup ON TOP of the
target.

This produced two user-visible symptoms reported against the same code:

- #5028: the popup covers the target Button (hiding the Close button in
  the reporter's screenshot) when the form is scrolled so the target
  lands near the vertical middle of the screen.
- #5029: on the initial show the popup's arrow points UPWARDS instead
  of at the Close button, and only flips to the correct side once the
  form is scrolled. With the popup overlapping the target,
  `CSSBorder.Arrow` cannot pick a consistent direction (`cabsY`
  straddles `trackY..trackY+h`, so neither the
  `cabsY >= trackY + trackHeight` nor `cabsY + height <= trackY` check
  fires), and the arrow tip renders on the wrong edge.

Fix: choose placement by available space instead of midline. Prefer
whichever side fits the popup's preferred height; fall back to the side
with more room when neither fits. The historical "over the rect"
branches are kept as a last resort for the truly degenerate case where
the rect fills the viewport top-to-bottom and there is literally no
room either above or below it.

Tests:

- `maven/core-unittests/.../InteractionDialogTest`: two new unit tests
  pin the invariants for #5028 (popup must not overlap target) and
  #5029 (popup must land entirely above or below target so
  `CSSBorder.Arrow` can pick a direction). Both were verified to FAIL
  on the unmodified code with the exact overlap coordinates, and pass
  with the fix.
- `tests/core/test/com/codename1/ui/InteractionDialogPopupTests5028.java`,
  `InteractionDialogPopupTests5029.java`: ant-side regression tests
  that drive the same scenarios through `showPopupDialog(Rectangle)`
  and `showPopupDialog(Component)` end-to-end.
- `tests/core/test/com/codename1/ui/InteractionDialogPopupTests4991.java`:
  ant-side mirror of the existing `core-unittests` regression for
  PR #5011 / issue #4991 (full-width landscape anchor). The unit-test
  copy was painful to find when grepping the popup-regression tests
  directory; mirroring it keeps both copies discoverable.

Full `core-unittests` suite: 2550 tests pass, 0 failures.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 24, 2026

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

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 24, 2026

Compared 110 screenshots: 110 matched.

Native Android coverage

  • 📊 Line coverage: 11.83% (6794/57442 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 9.62% (34130/354705), branch 4.15% (1400/33724), complexity 5.19% (1678/32362), method 8.99% (1361/15142), class 14.44% (303/2098)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)
      • kotlinx.coroutines.kotlinx.coroutines.JobSupport – 0.00% (0/423 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 11.83% (6794/57442 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 9.62% (34130/354705), branch 4.15% (1400/33724), complexity 5.19% (1678/32362), method 8.99% (1361/15142), class 14.44% (303/2098)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)
      • kotlinx.coroutines.kotlinx.coroutines.JobSupport – 0.00% (0/423 lines covered)

Benchmark Results

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 926.000 ms
Base64 CN1 encode 137.000 ms
Base64 encode ratio (CN1/native) 0.148x (85.2% faster)
Base64 native decode 1135.000 ms
Base64 CN1 decode 176.000 ms
Base64 decode ratio (CN1/native) 0.155x (84.5% faster)
Image encode benchmark status skipped (SIMD unsupported)

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 24, 2026

Compared 110 screenshots: 110 matched.
✅ Native iOS Metal screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 183 seconds

Build and Run Timing

Metric Duration
Simulator Boot 65000 ms
Simulator Boot (Run) 1000 ms
App Install 17000 ms
App Launch 10000 ms
Test Execution 294000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 760.000 ms
Base64 CN1 encode 1566.000 ms
Base64 encode ratio (CN1/native) 2.061x (106.1% slower)
Base64 native decode 455.000 ms
Base64 CN1 decode 1100.000 ms
Base64 decode ratio (CN1/native) 2.418x (141.8% slower)
Base64 SIMD encode 385.000 ms
Base64 encode ratio (SIMD/native) 0.507x (49.3% faster)
Base64 encode ratio (SIMD/CN1) 0.246x (75.4% faster)
Base64 SIMD decode 481.000 ms
Base64 decode ratio (SIMD/native) 1.057x (5.7% slower)
Base64 decode ratio (SIMD/CN1) 0.437x (56.3% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 58.000 ms
Image createMask (SIMD on) 10.000 ms
Image createMask ratio (SIMD on/off) 0.172x (82.8% faster)
Image applyMask (SIMD off) 164.000 ms
Image applyMask (SIMD on) 75.000 ms
Image applyMask ratio (SIMD on/off) 0.457x (54.3% faster)
Image modifyAlpha (SIMD off) 129.000 ms
Image modifyAlpha (SIMD on) 102.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.791x (20.9% faster)
Image modifyAlpha removeColor (SIMD off) 301.000 ms
Image modifyAlpha removeColor (SIMD on) 73.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.243x (75.7% faster)
Image PNG encode (SIMD off) 1447.000 ms
Image PNG encode (SIMD on) 883.000 ms
Image PNG encode ratio (SIMD on/off) 0.610x (39.0% faster)
Image JPEG encode 534.000 ms

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 24, 2026

Compared 110 screenshots: 110 matched.
✅ Native iOS screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 172 seconds

Build and Run Timing

Metric Duration
Simulator Boot 63000 ms
Simulator Boot (Run) 0 ms
App Install 11000 ms
App Launch 3000 ms
Test Execution 296000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 483.000 ms
Base64 CN1 encode 1287.000 ms
Base64 encode ratio (CN1/native) 2.665x (166.5% slower)
Base64 native decode 269.000 ms
Base64 CN1 decode 920.000 ms
Base64 decode ratio (CN1/native) 3.420x (242.0% slower)
Base64 SIMD encode 379.000 ms
Base64 encode ratio (SIMD/native) 0.785x (21.5% faster)
Base64 encode ratio (SIMD/CN1) 0.294x (70.6% faster)
Base64 SIMD decode 374.000 ms
Base64 decode ratio (SIMD/native) 1.390x (39.0% slower)
Base64 decode ratio (SIMD/CN1) 0.407x (59.3% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 57.000 ms
Image createMask (SIMD on) 9.000 ms
Image createMask ratio (SIMD on/off) 0.158x (84.2% faster)
Image applyMask (SIMD off) 117.000 ms
Image applyMask (SIMD on) 52.000 ms
Image applyMask ratio (SIMD on/off) 0.444x (55.6% faster)
Image modifyAlpha (SIMD off) 123.000 ms
Image modifyAlpha (SIMD on) 57.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.463x (53.7% faster)
Image modifyAlpha removeColor (SIMD off) 149.000 ms
Image modifyAlpha removeColor (SIMD on) 88.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.591x (40.9% faster)
Image PNG encode (SIMD off) 1075.000 ms
Image PNG encode (SIMD on) 1929.000 ms
Image PNG encode ratio (SIMD on/off) 1.794x (79.4% slower)
Image JPEG encode 678.000 ms

The three `tests/core/test/com/codename1/ui/InteractionDialogPopupTests*.java`
files I added in the previous commit don't actually run in CI:

- `pr.yml` calls `ant test-javase`, but the body of that target in
  `build.xml:87-97` is wrapped in `<!-- ... -->` -- effectively a no-op.
- `ant.yml` runs `tests/all.sh` -> `tests/core.sh` which builds a cn1app
  archetype project from `tests/core/` and runs `mvn package`. Surefire
  enumerates the .class files but reports `Tests run: 0` for each --
  these classes extend `AbstractTest` rather than JUnit so surefire
  finds no test methods, and the CN1 `TestRunner` that would invoke
  `runTest()` is not wired into the lifecycle.

Verified the same on the latest master CI run: `RoundRectBorderTest`,
`TestComponent2`, and other long-standing ant tests all report
`Tests run: 0`, and `SideMenuHamburgerTests4895` doesn't appear at all.

The two unit tests added to
`maven/core-unittests/.../InteractionDialogTest` cover the same
assertions and DO execute in CI (via the "Run Unit Tests" step in
pr.yml: `mvn clean verify -DunitTests=true -pl core-unittests -am`).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@shai-almog shai-almog merged commit 83e9d82 into master May 24, 2026
21 of 22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant