Skip to content

Safe area: a bottom bar no longer jumps between two heights - #6129

Open
shai-almog wants to merge 1 commit into
masterfrom
fix/safe-area-bottom-bar-jump
Open

shai-almog wants to merge 1 commit into
masterfrom
fix/safe-area-bottom-bar-jump

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

Problem

A setSafeArea(true) bottom tab bar (Savor's customer tabs, and customer apps) jumped between two layouts on tab switches: sized for its content plus the bottom inset, or sized for the content alone with the inset padded inside it, clipping the labels.

Container.calcPreferredSize judged the inset from the container's current bounds and cached the result. Measured before its first layout, or mid-layout after its parent moved but before it was placed, the bar got no inset, and nothing made layout reconsider; whichever later pass happened to re-measure it put the inset back.

Fix (Container.java)

  • The inset arithmetic is split into a pure safeAreaPadding(); the snap applies it.
  • Measurement takes the larger of the inset judged at current bounds and the inset the last layout applied, so a stale position cannot drop it.
  • Layout compares the inset it applies with the measured one, only on an axis the parent sized from the final preferred size (stretched/scrollable containers are untouched); on a mismatch the size is re-measured and the root lays out again within the same revalidate (max 3 passes, per-container cap with hysteresis), so no frame paints the stale size.
  • No extra style writes or events; apps without display insets take no new path.

Tests

SafeAreaBottomBarStabilityTest (core-unittests): tab switches, a bar above it appearing/going, safe area removed, bar moved to the top, safe-area bar inside a safe-area pane (one inset), and one layout per revalidate for a settled bar, a stretched pane and a scrollable pane (whose scroll size keeps the inset). Three fail on master (bar 28px instead of 62px).

Locally: core-unittests 8249 tests green, SpotBugs 0, PMD 0.

🤖 Generated with Claude Code

A setSafeArea(true) container measured its preferred size from its current
bounds and cached it. Measured before its first layout, or while its parent
had moved and it had not, a bottom tab bar was sized for its content alone;
layout then padded the bottom inset into that height and clipped the labels.
Whichever later pass re-measured it put it back, so the bar jumped between
the two layouts as the screen changed (Savor's customer tabs show it).

Measurement now also counts the inset the last layout applied, and layout
compares the inset it applies with the one measured, on any axis the parent
sized from the preferred size; when they differ the size is measured again
and the root lays out again in the same revalidate (bounded), so no frame
paints the stale size. The inset arithmetic is a pure function, so the check
writes no style and fires no extra events. Apps without display insets take
no new path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

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)

@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@github-actions

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)
    • ✅ android-compat: 0 findings (no issues)
    • ✅ android-res-compiler: 0 findings (no issues)
    • ✅ backend: 0 findings (no issues)
    • ✅ backend-test: 0 findings (no issues)
    • ✅ build-engine: 0 findings (no issues)
    • ✅ build-hint-catalog: 0 findings (no issues)
    • ✅ build-hint-tools: 0 findings (no issues)
    • ✅ codenameone-gradle-plugin: 0 findings (no issues)
    • ✅ codenameone-maven-plugin: 0 findings (no issues)
    • ✅ core-unittests: 0 findings (no issues)
    • ✅ desktop-compat-runtime: 0 findings (no issues)
    • ✅ fxml-compiler: 0 findings (no issues)
    • ✅ ios: 0 findings (no issues)
    • ✅ javac: 0 findings (no issues)
    • ✅ javafx-compat: 0 findings (no issues)
    • ✅ project-model: 0 findings (no issues)
    • ✅ swing-compat: 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

shai-almog commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator Author

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

@shai-almog

shai-almog commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 206 screenshots: 206 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 58ms / native 5ms = 11.6x speedup
SIMD float-mul (64K x300) java 62ms / native 5ms = 12.4x 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 75.000 ms
Base64 CN1 decode 89.000 ms
Base64 SIMD encode 105.000 ms
Base64 encode ratio (SIMD/CN1) 1.400x (40.0% slower)
Base64 SIMD decode 99.000 ms
Base64 decode ratio (SIMD/CN1) 1.112x (11.2% slower)
Image encode benchmark iterations 100
Image createMask (SIMD off) 16.000 ms
Image createMask (SIMD on) 3.000 ms
Image createMask ratio (SIMD on/off) 0.188x (81.3% faster)
Image applyMask (SIMD off) 30.000 ms
Image applyMask (SIMD on) 27.000 ms
Image applyMask ratio (SIMD on/off) 0.900x (10.0% faster)
Image modifyAlpha (SIMD off) 36.000 ms
Image modifyAlpha (SIMD on) 17.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.472x (52.8% faster)
Image modifyAlpha removeColor (SIMD off) 29.000 ms
Image modifyAlpha removeColor (SIMD on) 19.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.655x (34.5% faster)

ParparVM vs HotSpot (JDK 25): Windows x64

Runner CPU: AMD64 Family 25 Model 1 Stepping 1, AuthenticAMD (baseline windows-x64@amd64-family-25-model-1-authenticamd)

Ratios are ParparVM / JDK 25: below 1.00x ParparVM is faster (time) or smaller (RAM). Median of 5 interleaved, paired rounds; every run's output was verified. A ratio more than 20% (time) / 25% (RAM) above its baseline in vm/selfhost/perf-baseline/ is a regression and fails; one that far below is an improvement, reported but not failed (a row whose calibration runs were noisier carries a wider tolerance), and for RAM the change must also exceed 0.05x in absolute terms. Both run unpinned on all of the runner's CPUs, with their own default thread counts.

Benchmark Cores Time RAM Status
hello (WinHelloMain) 4 1.54x (base 1.22x, +25.8%) 0.59x (base 0.58x, +0.2%) ok
translator (self) 4 0.80x (base 0.83x, -4.0%) 0.54x (base 0.54x, +0.0%) ok
intArithmetic 4 1.10x (base 1.10x, +0.0%) 0.06x (base 0.06x, +2.8%) ok
longArithmetic 4 1.14x (base 1.08x, +5.0%) 0.06x (base 0.05x, +3.7%) ok
mathTranscendental 4 0.79x (base 0.79x, -0.2%) 0.06x (base 0.06x, +2.9%) ok
arraySequential 4 1.34x (base 1.36x, -1.6%) 0.39x (base 0.39x, +0.2%) ok
arrayRandom 4 1.23x (base 1.06x, +16.0%) 0.23x (base 0.23x, +0.5%) ok
objectAllocation 4 1.56x (base 1.56x, +0.1%) 0.30x (base 0.39x, -21.5%) ok
valueEscape 4 0.10x (base 0.10x, +0.4%) 0.05x (base 0.04x, +4.8%) ok
hashMapChurn 4 1.89x (base 1.80x, +5.0%) 0.10x (base 0.09x, +2.5%) ok
stringBuilding 4 1.33x (base 1.22x, +9.6%) 0.29x (base 0.32x, -8.8%) ok
recursion 4 1.41x (base 1.49x, -5.8%) 0.06x (base 0.06x, +1.7%) ok
quicksort 4 1.11x (base 1.13x, -2.4%) 0.12x (base 0.12x, +0.9%) ok

Result: no regression

@shai-almog

shai-almog commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator Author

Compared 206 screenshots: 206 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 50ms / native 3ms = 16.6x speedup
SIMD float-mul (64K x300) java 48ms / native 3ms = 16.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 62.000 ms
Base64 CN1 decode 71.000 ms
Base64 SIMD encode 73.000 ms
Base64 encode ratio (SIMD/CN1) 1.177x (17.7% slower)
Base64 SIMD decode 73.000 ms
Base64 decode ratio (SIMD/CN1) 1.028x (2.8% slower)
Image encode benchmark iterations 100
Image createMask (SIMD off) 5.000 ms
Image createMask (SIMD on) 1.000 ms
Image createMask ratio (SIMD on/off) 0.200x (80.0% faster)
Image applyMask (SIMD off) 18.000 ms
Image applyMask (SIMD on) 20.000 ms
Image applyMask ratio (SIMD on/off) 1.111x (11.1% slower)
Image modifyAlpha (SIMD off) 18.000 ms
Image modifyAlpha (SIMD on) 21.000 ms
Image modifyAlpha ratio (SIMD on/off) 1.167x (16.7% slower)
Image modifyAlpha removeColor (SIMD off) 28.000 ms
Image modifyAlpha removeColor (SIMD on) 14.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.500x (50.0% faster)

ParparVM vs HotSpot (JDK 25): Windows arm64

Runner CPU: ARMv8 (64-bit) Family 8 Model D49 Revision 0, MICROSOFT CORPORATION (baseline windows-arm64@armv8-64-bit-family-8-model-d49-microsoft-corporation)

Ratios are ParparVM / JDK 25: below 1.00x ParparVM is faster (time) or smaller (RAM). Median of 5 interleaved, paired rounds; every run's output was verified. A ratio more than 20% (time) / 25% (RAM) above its baseline in vm/selfhost/perf-baseline/ is a regression and fails; one that far below is an improvement, reported but not failed (a row whose calibration runs were noisier carries a wider tolerance), and for RAM the change must also exceed 0.05x in absolute terms. Both run unpinned on all of the runner's CPUs, with their own default thread counts.

Benchmark Cores Time RAM Status
hello (WinHelloMain) 4 0.99x (base 1.11x, -10.2%) 0.62x (base 0.75x, -17.8%) ok
translator (self) 4 0.87x (base 0.84x, +4.3%) 0.49x (base 0.51x, -3.2%) ok
intArithmetic 4 1.04x (base 1.04x, +0.0%) 0.06x (base 0.06x, +3.1%) ok
longArithmetic 4 0.80x (base 0.80x, +0.1%) 0.06x (base 0.06x, +3.4%) ok
mathTranscendental 4 0.72x (base 0.72x, +0.1%) 0.07x (base 0.06x, +1.7%) ok
arraySequential 4 0.39x (base 0.46x, -13.3%) 0.39x (base 0.39x, -0.0%) ok
arrayRandom 4 0.93x (base 0.94x, -0.4%) 0.23x (base 0.23x, +0.2%) ok
objectAllocation 4 1.76x (base 1.82x, -3.4%) 0.42x (base 0.43x, -3.3%) ok
valueEscape 4 0.76x (base 0.76x, +0.1%) 0.05x (base 0.05x, +2.0%) ok
hashMapChurn 4 0.98x (base 0.98x, -0.0%) 0.12x (base 0.12x, -0.3%) ok
stringBuilding 4 1.59x (base 1.43x, +11.8%) 0.27x (base 0.27x, +0.0%) ok
recursion 4 1.55x (base 1.46x, +6.0%) 0.07x (base 0.07x, +1.0%) ok
quicksort 4 1.00x (base 1.00x, +0.0%) 0.12x (base 0.12x, +0.5%) ok

Result: no regression

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