Revert "Added theme constants for accents (#4848)"#4877
Merged
shai-almog merged 1 commit intomasterfrom May 7, 2026
Merged
Conversation
This reverts commit af0309e.
Contributor
|
Developer Guide build artifacts are available for download from this workflow run:
Developer Guide quality checks:
Unused image preview:
|
Contributor
✅ Continuous Quality ReportTest & Coverage
Static Analysis
Generated automatically by the PR CI workflow. |
Contributor
Cloudflare Preview
|
Collaborator
Author
|
Compared 90 screenshots: 90 matched. Native Android coverage
✅ Native Android screenshot tests passed. Native Android coverage
Benchmark ResultsDetailed Performance Metrics
|
Collaborator
Author
|
Compared 90 screenshots: 90 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
Collaborator
Author
|
Compared 90 screenshots: 90 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
shai-almog
added a commit
that referenced
this pull request
May 9, 2026
* Native theme accents: bake var() bindings into the .res, retune at runtime
CSS rules in the iOS Modern and Android Material 3 native themes
reference an accent palette via var(--accent-color, fallback). The
Flute compiler still inlines the fallback as the baked-in default
AND additionally emits a @cn1-bind:<UIID>.<key>=accent-color
constant alongside, so the .res file remembers which style keys
track which palette variable.
UIManager.buildTheme() gains an applyThemeBindings() pass that
overlays @<varname> overrides supplied via addThemeProps onto every
bound theme key. A user app rebrands the accent with a single
addThemeProps({"@accent-color": "ff2d95", ...}) call - no per-UIID
rule duplication, no theme recompile.
Replaces the compile-time-only var() approach reverted in #4877
(PR #4848). The same accent vocabulary works at runtime now and
the docs no longer suggest forking the shipped native theme.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* PaletteOverride: also retune @accent-disabled-color so iOS visibly diffs
The old per-UIID override and the new @accent-color override happen
to map to the same set of visible widgets on the iOS Modern capture
form (Button.fgColor, RaisedButton.bg/fg) - both produce the same
magenta there, so the iOS pipeline shows zero unmatched screenshots
which masks whether the new binding mechanism actually fires on iOS.
Add a vivid teal override on @accent-disabled-color (iOS-only - the
M3 theme hard-codes its disabled colours and has no binding for this
slot) so the disabled RaisedButton on the form switches from the
default iOS accent-disabled blue to teal. iOS captures now diverge
from the pre-binding baseline, confirming the runtime binding pass
fires on iOS too. Android's diff is already covered by the magenta
@accent-container-color retuning RaisedButton's tonal fill.
Add a sanity log at install time that surfaces any leak from a
previous test in the suite (a stale @accent-color constant). The
test runs near the tail of Cn1ssDeviceRunner and finish() reloads
/theme via initFirstTheme which clears themeConstants - so the
expected pre-state is "no leak". The log is the cheap signal we
need if a future framework regression ever drops that cleanup.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* RoundBorder cn1-pill-border: respect Style.bgColor when bound to a var()
Android Material 3's RaisedButton (and other UIIDs using
cn1-pill-border) wraps its fill in a RoundBorder whose color the CSS
compiler bakes in at compile time. By default RoundBorder paints from
that baked field via fillShape() with uiid=false, ignoring
Style.bgColor at render time. The runtime binding pass updates
themeProps[<UIID>.bgColor] correctly when the user pushes an
@accent-color override, but the visible pill stays at the
compile-time fallback because the border, not the Style, owns the
visible color.
When the source background-color came from a var() expansion (i.e.
the binding mechanism wants this fill to be runtime-tunable), flip
the RoundBorder into uiid mode so it routes through
Style.getBgPainter() at paint time. Style.bgColor then drives the
fill, and a runtime @accent-* override propagates all the way to the
visible pixels.
Legacy themes whose backgrounds are inlined hex (no var()) keep the
existing baked-color path, so this is a no-op for everything that
isn't already opted into the binding mechanism.
Update iOS PaletteOverrideTheme_light/dark goldens (both GL and Metal)
to the captures produced by the previously-pushed override-color
expansion - iOS uses border-radius (RoundRectBorder) which already
respects Style.bgColor, so its captures only changed because we
added @accent-disabled-color to the override and the disabled
RaisedButton on the form is now teal instead of accent-disabled
blue. Android goldens will need a fresh CI run with this fix to
capture the now-correct magenta RaisedButton; deferring those.
NativeThemeBindingsTest: extended to cover the AndroidMaterialTheme
.res so the binding round-trip is exercised on both shipped
native-theme palettes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* CSS compiler: export #Constants `--name` declarations as @name theme constants
Lets a user app's theme.css override a native-theme palette variable
purely from CSS:
#Constants {
includeNativeBool: true;
--accent-color: #ff2d95;
--accent-color-dark: #ff2d95;
}
Previously `--name` declarations short-circuited into the parser-
internal variables map (used only for compile-time var() resolution
within the same compilation unit) and never reached the runtime, so
a user's --accent-color redeclaration was silently dropped. The
Flute compiler now ALSO emits these as @name theme constants when
they sit inside a #Constants pseudo-element, which routes them
through UIManager.themeConstants where the binding-overlay pass
already knows what to do with them - the user theme.css is loaded
after the native theme (via includeNativeBool=true), the @-constant
overwrites the native default, applyThemeBindings retunes every
bound UIID. Same end-state as runtime addThemeProps but driven from
CSS, no Java code, no Hashtable.
Adds SAC_RGBCOLOR / SAC_FUNCTION (rgb, rgba) handling to the
constants-serialization loop so hex / rgb() colors in #Constants
make it out as plain hex strings (the format runtime themeProps and
applyThemeBindings expect for color values).
Native theme captures still emit their own @accent-color etc. from
their #Constants blocks - this is by design: the constants are
already in themeProps with the native default, so a no-op overlay
runs after each native-theme-load. When the user theme then loads
on top, the user's @accent-color overwrites the native default and
the next applyThemeBindings overlays the user's value.
NativeThemeBindingsTest now also asserts @accent-color is present
in the loaded theme so the round-trip CSS -> .res -> Hashtable is
covered for both shipped native themes.
Native-Themes docs lead with the CSS-from-theme.css path; the
runtime addThemeProps path is documented as the dynamic-theming
counterpart for cases like in-app accent toggles. Test docstring
clarifies it's exercising the runtime path because screenshot
tests can't easily mutate the app's compiled theme.css.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* CSS compiler: don't inherit a parent's binding when the child sets a literal
Element.resolveBinding walked the parent chain whenever the current
Element had no `bindings` entry for the requested property, but
`bindings` only records properties whose VALUE came from a var() -
not properties the rule set with a literal. So a state rule like
`Button.disabled { background-color: #e0dce4 }` (a literal that
deliberately breaks the inheritance from base `Button { background-
color: var(--accent-color) }`) was treated by the binding walker as
"no value of its own" and the parent's accent binding was emitted
for `Button.dis#bgColor`. At runtime the @accent-color override
then stomped the disabled tone with the primary colour, visibly
shifting `Button.disabled` away from the M3 baseline.
Fix: in resolveBinding, after the local `bindings` miss, check
whether the current Element's `style` map has an entry for the
property. If yes, this rule overrode the value with a literal;
return null so the override stops at this level. Only walk to the
parent when the Element has no value of its own (the derive-only
case Button-derived RaisedButton relies on, or the implicit
unselected state inheriting from the base UIID).
Caught by Android ButtonTheme_dark/light captures shifting in the
disabled-button band on the latest CI run; the Button.dis#bgColor
binding is now correctly absent from the rebuilt
AndroidMaterialTheme.res.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ci: bump Metal screenshot step timeout from 30 to 45 minutes
The Metal screenshot pipeline hit the 30-minute step timeout on the
latest PR run. Tracing the device-runner.log: the suite reached
TabsTheme_light (~13 min into test execution), captured 111533
PNG bytes + 84124-byte preview chunk stream at preview_quality=6
(still over the 20480-byte preview cap), then went silent for
~18 minutes before the timer killed it. No FATAL / Test-failure
markers - just dead air on the logcat replay.
The earlier passing Metal run finished ~28 minutes in, so the suite
is consistently running right at the wall. Bump the timeout to 45
minutes - matches the build-ios job's own cap and the iOS Metal
runner's natural ceiling - so a borderline-slow chunk-stream replay
doesn't get conflated with a real Metal port hang.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Revert "ci: bump Metal screenshot step timeout from 30 to 45 minutes"
This reverts commit 7de74c2.
* Metal port: cache pipeline + matrix state across consecutive draws
The texture-backdrop test painter (used by Tabs / Dialog screenshot
tests in hellocodenameone) was the immediate trigger, but the
underlying robustness issue is in the Metal port itself: drawQuad and
drawSolidPrimitive called setRenderPipelineState + 3-4 setVertexBytes
on EVERY draw, even when consecutive draws shared the same pipeline
and matrix snapshot.
For one-off fills that's fine. For burst patterns -- the painter was
issuing 2556 fillRect calls per band x ~50 bands = ~125k fillRects
per textured-backdrop frame -- the redundant per-call setVertexBytes
of a 192-byte matrix struct plus the redundant pipeline state-set
choke the CAMetalLayer command buffer. The TabsTheme dark-mode
capture stalled the iOS Metal screenshot suite for 18 minutes (until
the surrounding step's wall-clock timer fired).
Two complementary fixes:
1. Painter (hellocodenameone DualAppearanceBaseTest.TextureBackdrop
Painter): each diagonal stripe is a parallelogram. Replace the
2556-iteration scanline fillRect loop with one fillPolygon call
per band. Polygon fill is universally supported by every CN1 port
we ship (Graphics.fillPolygon is core API, not a port extension);
the previous comment claiming otherwise was wrong. ~125k draws
per frame -> ~50.
2. Metal port (CN1Metalcompat.m): track last-bound pipeline state
and last-uploaded matrix bytes per encoder. Skip the Metal API
call when they haven't changed. Cache invalidates on every
activeEncoder reassignment (BeginFrame, BeginMutableImageDraw,
EndMutableImageDraw restore, EndFrame) because Metal command
encoders don't carry state between encoders. memcmp on a 192-byte
struct per draw is much cheaper than the encoder's setVertexBytes
argument-buffer copy.
This isn't only about TextureBackdropPainter -- it makes Metal
robust to any code path that emits a long burst of same-pipeline /
same-matrix draws (gradient scanline approximations, RoundRectBorder
interior scanline fills, custom painters in user apps that happen to
choose scanline strategies for portability). A small CSS or layout
mistake should not be able to cascade into 100k+ redundant Metal
encoder calls.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Revert "Metal port: cache pipeline + matrix state across consecutive draws"
This reverts commit 2cfb34c.
* TextureBackdropPainter: fillPolygon per band instead of scanline fillRect
Each diagonal band is a parallelogram. The previous loop emitted one
fillRect per row (h rows) per band; at phone resolution that's
~2500 fillRects x 50 bands = ~125k draw calls per backdrop frame.
On iOS Metal that call volume saturated the CAMetalLayer command
buffer and stalled the dark-mode transition for TabsTheme by 18
minutes (until the surrounding step's wall-clock timer fired).
Replace with one fillPolygon per band - 50 draw calls instead of
125k. Graphics.fillPolygon is core CN1 API, supported on every port
we ship; the previous comment claiming otherwise was wrong.
This is the painter half of the earlier "Metal port: cache pipeline
+ matrix state" commit. The Metal-side state caching half is left
out of this revert chain because the original CI run with both
pieces produced a different failure mode (simctl couldn't launch
the rebuilt app -- "Application unknown to FrontBoard") that I
couldn't reproduce locally and don't want to chase blind. The
painter fix alone reduces the call volume by ~2500x, which is the
actual root-cause mitigation; the encoder state cache was a
defensive second pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Revert "TextureBackdropPainter: fillPolygon per band instead of scanline fillRect"
This reverts commit 668d39e.
* Metal port: cache pipeline + matrix state across consecutive draws
drawQuad and drawSolidPrimitive called setRenderPipelineState + 3-4
setVertexBytes on EVERY draw, even when consecutive draws shared the
same pipeline and matrix snapshot.
For one-off fills that's fine. For burst patterns -- the
hellocodenameone TextureBackdropPainter (used by Tabs / Dialog
screenshot tests) is the immediate trigger, issuing ~60k fillRects per
textured-backdrop frame, and locally instrumented Metal draw counts
confirmed ~60,000 draw calls/frame sustained over 25+ frames per
TabsTheme capture (~1.5M Metal API calls total). On the GitHub Actions
runner this stalls the CAMetalLayer command buffer for 18+ minutes
until the surrounding step's wall-clock timer fires. The local M-series
hardware tolerates the call volume but at ~40-50ms per frame.
Fix: track last-bound pipeline state and last-uploaded matrix bytes
per encoder. Skip the Metal API call when they haven't changed. Cache
invalidates on every activeEncoder reassignment (BeginFrame, BeginMutab
leImageDraw, EndMutableImageDraw restore, EndFrame) because Metal
command encoders don't carry state between encoders. memcmp on a
192-byte struct per draw is much cheaper than the encoder's
setVertexBytes argument-buffer copy.
This isn't only about TextureBackdropPainter -- it makes Metal robust
to any code path that emits a long burst of same-pipeline / same-matrix
draws (gradient scanline approximations, RoundRectBorder interior
scanline fills, custom painters in user apps that happen to choose
scanline strategies for portability). A small CSS or layout mistake
should not be able to cascade into 100k+ redundant Metal encoder
calls.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* TextureBackdropPainter: cache rendered backdrop in an Image
The painter is invoked once per Form paint cycle. The screenshot
test framework's settle window emits ~30 paints at 60Hz before the
capture fires; multiplied by ~50 bands x ~2532 rows of fillRect per
paint, that's ~3.8M fillRect calls per textured-backdrop capture.
Even after Metal port pipeline+matrix state caching skips 99.99% of
redundant API calls, the remaining setVertexBytes(positions)/
setVertexBytes(color)/drawPrimitives per fillRect is enough to keep
the GitHub Actions runner stalled past the 30-minute screenshot
step timeout on TabsThemeScreenshotTest.
Render the pattern into an Image once (size keyed on form bounds)
and drawImage(cached) on every subsequent paint. First paint:
~125k draw calls; later paints in the same form: 1 drawImage call.
Total per capture drops from ~3.8M draws to ~125k.
Cross-port safe: Image.createImage + drawImage are core CN1 API
that every port implements, unlike the per-band fillPolygon variant
that silently dropped on Android (canvas.drawPath behaviour at
extreme parallelogram coordinates). The cache is keyed on
(width, height) so a rotation/resize re-renders.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* iOS GL goldens: refresh 4 textured-backdrop captures after painter cache
The painter now renders the diagonal-stripe texture into a cached
Image and drawImage()s it on subsequent paints, instead of
re-running the per-row scanline fillRects every frame. Visually
identical to the previous goldens, but sub-pixel alpha-blend
rounding differs (Image alpha-blend vs direct framebuffer
alpha-blend produces tiny per-pixel deltas on iOS GL).
iOS Metal and Android goldens already accept the new render
(Metal: 90/90 matched, Android: 90/90 matched). Only iOS GL
strict pixel comparison flagged the four textured tests:
DialogTheme_light/dark, TabsTheme_light/dark.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This reverts commit af0309e.