Size CSS border-radius by the CSS box model (#5454)#5469
Conversation
A stylesheet rule such as
btnSend { border-radius: 0mm 3mm 3mm 0mm; padding: 0.6mm 4mm; }
started rendering a much taller button in 7.0.260 than in 7.0.233.
Cause: PR #5054 (JS port native themes) added a branch to
CSSTheme.getThemeBorder that prefers RoundRectBorder over CSSBorder for
the simple border-radius case, so this rule switched border classes.
RoundRectBorder.getMinimumHeight() reports twice the corner radius and
DefaultLookAndFeel maxes the preferred height against it, so a 3mm
radius forced a >=6mm tall button regardless of padding and font size.
CSSBorder has no such floor. That floor is deliberate for hand written
borders (it guarantees a pill), but it is not CSS: there border-radius
never contributes to the size of the box.
Rather than revert the border choice, which the JS port needs, this adds
an opt-in sizing mode:
- RoundRectBorder.cssBoxModel(boolean). When set, the border reserves no
space for the radius, only for a shadow it actually draws. Off by
default, so hand written borders and the designer are unaffected.
- createShape now applies the CSS corner overlap rule, scaling the radius
down when the corners do not fit. Per edge, so a shape with only its
right corners rounded may use the full height rather than half of it.
Previously the path could fold over itself once a component was smaller
than the radius, which the CSS mode makes reachable.
- The CSS compiler sets the flag on every border it generates.
- Resource format 1.16 carries the flag. A 1.15 or older .res reads as
legacy, so existing themes keep their current sizing.
- Sheet.updateBorderForPosition clones RoundRectBorder field by field and
now carries the flag across.
The regenerated native themes are exactly one byte larger per
RoundRectBorder (70 in the iOS theme, 60 in the Material theme) with no
other content change. Touching the theme CSS runs the native theme
fidelity suite on this PR so the new sizing is measured against the real
OS widgets on both platforms.
Tests: border sizing and corner scaling in BorderAndPlafTest, the
reported CSS compiling to a flagged border in CSSThemeBorderRadiusTest,
and .res round-trip plus 1.15 backward compatibility in
RoundRectBorderCssBoxModelResourceTest.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Note
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Fixes a regression where CSS border-radius unintentionally inflated component preferred size by introducing an opt-in “CSS box model” sizing mode for RoundRectBorder, ensuring radii don’t contribute to layout size while preserving legacy behavior for handwritten/designer borders.
Changes:
- Add
RoundRectBorder.cssBoxModel(boolean)and persist it through CSS compilation, resource IO (format 1.16), XML, andSheetborder cloning. - Implement CSS corner-overlap radius scaling in
RoundRectBorder.createShape()to prevent self-intersecting paths on small components. - Add/extend unit tests and update docs/native theme comments to document the new sizing behavior.
Reviewed changes
Copilot reviewed 12 out of 16 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| native-themes/ios-modern/theme.css | Documents CSS border-radius sizing expectations for the native iOS theme CSS. |
| native-themes/android-material/theme.css | Documents CSS border-radius sizing expectations for the native Material theme CSS. |
| maven/css-compiler/src/main/java/com/codename1/ui/util/xml/Border.java | Adds XML-model field + getter/setter for cssBoxModel. |
| maven/css-compiler/src/main/java/com/codename1/ui/util/EditableResources.java | Bumps resource minor version to 1.16; writes/reads cssBoxModel in XML + binary border serialization. |
| maven/css-compiler/src/main/java/com/codename1/designer/css/CSSTheme.java | Ensures CSS-compiled RoundRectBorder enables cssBoxModel(true). |
| maven/core-unittests/src/test/java/com/codename1/ui/util/RoundRectBorderCssBoxModelResourceTest.java | Adds resource round-trip tests for new flag and legacy behavior. |
| maven/core-unittests/src/test/java/com/codename1/ui/plaf/BorderAndPlafTest.java | Adds behavioral tests for legacy vs CSS sizing and radius scaling behavior. |
| maven/core-unittests/src/test/java/com/codename1/designer/css/CSSThemeBorderRadiusTest.java | Adds build-time tests ensuring CSS compiles to the correct border type/flag. |
| docs/developer-guide/css.asciidoc | Documents that CSS border-radius doesn’t affect box size and scales down to fit. |
| CodenameOne/src/com/codename1/ui/util/Resources.java | Reads cssBoxModel from resource format 1.16+ for RoundRectBorder. |
| CodenameOne/src/com/codename1/ui/plaf/RoundRectBorder.java | Implements cssBoxModel sizing + radius scaling; updates minimum size calculation accordingly. |
| CodenameOne/src/com/codename1/ui/Sheet.java | Preserves cssBoxModel when cloning borders for sheet position. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Any radius bigger than the button itself, so the legacy reservation is the value | ||
| // that wins in the preferred size calculation and the difference is visible here. | ||
| float radius = expected; | ||
| assertTrue(rightRoundedCorners(radius).getMinimumHeight() > expected, | ||
| "test setup: the radius has to exceed the natural height of the button"); | ||
|
|
||
| Button cssRounded = new Button("Send"); | ||
| cssRounded.getAllStyles().setBorder(rightRoundedCorners(radius).cssBoxModel(true)); |
| radius = scaleRadiusToFit(radius, widthF, heightF, | ||
| roundTopLeft, roundTopRight, roundBottomLeft, roundBottomRight); |
| private int minimumSize() { | ||
| if (cssBoxModel) { | ||
| if (shadowOpacity <= 0) { | ||
| return 0; | ||
| } | ||
| return Display.getInstance().convertToPixels(shadowSpread); | ||
| } |
|
Developer Guide build artifacts are available for download from this workflow run:
Developer Guide quality checks: |
Microsoft.Contractions wants "that's"; rewording without the relative clause reads better than the contraction. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
| File f = File.createTempFile("cn1-test-", ".css"); | ||
| f.deleteOnExit(); | ||
| FileWriter w = new FileWriter(f); | ||
| try { | ||
| w.write(css); | ||
| } finally { | ||
| w.close(); | ||
| } |
| // Any radius bigger than the button itself, so the legacy reservation is the value | ||
| // that wins in the preferred size calculation and the difference is visible here. | ||
| float radius = expected; | ||
| assertTrue(rightRoundedCorners(radius).getMinimumHeight() > expected, | ||
| "test setup: the radius has to exceed the natural height of the button"); | ||
|
|
||
| Button cssRounded = new Button("Send"); | ||
| cssRounded.getAllStyles().setBorder(rightRoundedCorners(radius).cssBoxModel(true)); |
Cloudflare Preview
|
Native fidelity (Android, Material 3)54 pairs compared -- median 95.6%, worst 91.3% ( Distribution --
Geometry vs native (bbox offset / size ratio / center offset / corner radius) -- gated separately from the visual score
Side-by-side comparisons (worst first)
|
✅ Continuous Quality ReportTest & Coverage
Static Analysis
Generated automatically by the PR CI workflow. |
ChatBubbleUser and ChatBubbleAssistant carry "border-radius: 4mm" with "padding: 2mm 3mm", so a single line bubble was previously floored at twice the radius rather than sized by its padding. With the CSS box model that floor is gone and single line bubbles hug their text, which the ChatView screenshot suites picked up on every platform. Multi line bubbles were always taller than the floor and are unaffected. Raising the vertical padding to 3mm keeps the roomy bubble as a stated design choice instead of a side effect of the corner radius. The ChatView goldens still move on every platform and are re-seeded from the CI artifacts in a follow-up commit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Downloading the emulator-screenshot artifact and diffing it against the committed golden shows the real impact of the CSS box model on ChatView: the single line bubble goes from 50px to 49px and the multi line bubbles are byte identical. The 8mm floor was only just binding. Raising the vertical padding to 3mm would have added about 12.5px to EVERY bubble at this density, including the multi line ones that were never floored, so it would have moved the design far more than the 1px it was meant to compensate for. Keeping "padding: 2mm 3mm" is what actually preserves the current look. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Compared 217 screenshots: 217 matched. |
iOS Metal screenshot updatesCompared 149 screenshots: 147 matched, 2 updated.
Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
iOS screenshot updatesCompared 143 screenshots: 141 matched, 2 updated.
Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
Apple TV (tvOS / Metal)Compared 144 screenshots: 142 matched, 2 updated.
|
Native fidelity (iOS Modern, Metal)68 pairs compared -- median 95.0%, worst 83.5% ( Distribution --
Geometry vs native (bbox offset / size ratio / center offset / corner radius) -- gated separately from the visual score
Side-by-side comparisons (worst first)
|
ChatBubbleUser and ChatBubbleAssistant pair "border-radius: 4mm" with
"padding: 2mm 3mm", so a single line bubble used to be floored at twice
the radius instead of sized by its padding. With the radius no longer
sizing the box those bubbles hug their text and everything below them
shifts up, which is the whole of the diff: on Android the single line
bubble goes 50px -> 49px, the two and five line bubbles are byte
identical.
Every PNG here was taken from the CI artifact of the run for this
branch, and each was diffed against the golden it replaces to confirm
the change is confined to the chat bubbles.
android 2 (identical across the jdk17/jdk21/default8 matrix,
and byte identical across two separate runs)
ios, ios-metal 4
ios-tv 2
javascript 4 (includes the ios-theme ChatView_ios_* variants)
mac-native 2
Not re-seeded because they render ChatView unchanged, verified by
diffing their artifacts rather than assuming: watchOS (suite passed),
linux x64 and windows (both byte identical to their goldens). The linux
and windows suites do not match this PR's path filters, so they were
dispatched manually to check rather than left to fail the nightly cron.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>





























































































































































































































































Fixes the sizing regression reported in discussion #5454:
border-radius: 0mm 3mm 3mm 0mmon a button renders much taller in 7.0.260 than in 7.0.233.Cause
#5054 (JS port native themes) added a branch to
CSSTheme.getThemeBorderthat prefersRoundRectBorderoverCSSBorderfor the simple border-radius case, so the reported rule changed border class between the two releases.RoundRectBorder.getMinimumHeight()returns twice the corner radius, andDefaultLookAndFeelmaxes the preferred height against it, so a 3mm radius forces a >=6mm tall button no matter what the padding and font-size say.CSSBorderhas no such floor. Measuring the two screenshots from the report confirms it: the glyphs, the horizontal padding and the corner arcs are pixel-identical, only the height changes (82px -> 121px, split evenly above and below the text).The floor is deliberate for hand written borders (it guarantees a pill shape), but it is not CSS behavior: there
border-radiusnever contributes to the size of the box.Fix
Reverting the border choice would re-break the JS port, so the sizing becomes opt-in instead:
RoundRectBorder.cssBoxModel(boolean)- when set, the border reserves no space for the radius, only for a shadow it actually draws. Off by default, so hand written borders and the designer are untouched.createShape- the radius scales down when the corners do not fit, per edge, so a shape with only its right corners rounded may use the full height rather than half of it. Previously the path could fold over itself once a component was smaller than the radius, which the CSS mode makes reachable..resreads as legacy, so existing themes keep their current sizing.Sheet.updateBorderForPositionclones aRoundRectBorderfield by field and now carries the flag across.Native themes
The regenerated
.resfiles are exactly one byte larger perRoundRectBorder(70 in the iOS theme, 60 in the Material theme) with no other content change. Touching the theme CSS runs the native theme fidelity suite on this PR, so the new sizing gets measured against the real OS widgets on both platforms. Both themes still compile instrictNoCefmode and the compiler output is byte-for-byte deterministic.Worth watching in the fidelity results:
Slider/SliderFull/ProgressBaruseborder-radiuswithpadding: 0, so they were previously floored at twice their radius and now take their natural height.Tests
BorderAndPlafTest- legacy sizing pinned, CSS sizing reserves nothing, shadow still reserved, the reported button no longer inflates, and radius scaling both per shape and per edge.CSSThemeBorderRadiusTest(new) - the exact CSS from the report compiles to a flaggedRoundRectBorder; per-corner radii that differ still compile to aCSSBorder.RoundRectBorderCssBoxModelResourceTest(new) -.resround-trip, legacy default, and a 1.15 resource reading back as legacy.Full suite: 4185 tests green. Copyright, since-tag and PMD forbidden-rule gates checked locally.
Notes for review
RoundRectBorderconstructor seedsshadowSpreadwithconvertToPixels(0.2f), a pixel count in a field every reader treats as millimeters. It is a real bug, but correcting it would resize and re-shadow every existing hand written border, so it is documented in place rather than changed. CSS-mode borders sidestep it by reserving no spread unless a shadow is drawn.roundRectborders is broken for every attribute exceptroundBorderColor:SimpleXmlParserbinds through setters andcom.codename1.ui.util.xml.Borderhas none. Pre-existing, from the JAXB removal in 3edb800. I added the setter for the new flag only; the rest is a separate fix.BorderEditorin the designer builds a fresh border from its dialog fields, so editing a CSS-compiled theme there would drop the flag. Adding a checkbox means editing the NetBeans.form; left out of this PR.🤖 Generated with Claude Code