Rebuild native themes via CSS compiler (phase 2: module split)#4793
Rebuild native themes via CSS compiler (phase 2: module split)#4793liannacasper wants to merge 75 commits intomasterfrom
Conversation
Android screenshot updatesCompared 65 screenshots: 37 matched, 28 missing references.
Native Android coverage
Benchmark ResultsDetailed Performance Metrics
|
✅ Continuous Quality ReportTest & Coverage
Static Analysis
Generated automatically by the PR CI workflow. |
|
Compared 7 screenshots: 7 matched. |
|
Compared 34 screenshots: 34 matched. |
|
Developer Guide build artifacts are available for download from this workflow run:
Developer Guide quality checks: |
290b09d to
e2f4545
Compare
✅ ByteCodeTranslator Quality ReportTest & Coverage
Benchmark Results
Static Analysis
Generated automatically by the PR CI workflow. |
5b2b914 to
abc5b63
Compare
First slice of the native-themes refactor: the CSS compiler now lives in
its own Maven module with clean dependencies (core + flute + sac) so the
shipped platform themes (iOS Modern liquid-glass + Android Material) can
be generated at framework build time without pulling in JavaSE / JavaFX /
CEF / the Designer GUI.
Phase 1 (no-cef enforcement):
- CSSTheme.strictNoCef static flag + enforceNoCef() pre-scan that lists
every UIID state requiring CEF-backed image rasterization and throws
before any WebView call.
- CN1CSSCLI gained a -no-cef CLI arg.
- New NoCefCSSCLI minimal entry point (no JavaSEPort/BrowserComponent
bootstrap) with a throwing WebViewProvider as a safety net.
Phase 2 (module split):
- New maven/css-compiler Maven module; registered in the reactor between
factory and sqlite-jdbc. Produces a jar and a fat
jar-with-dependencies whose main class is NoCefCSSCLI.
- maven/designer now depends on codenameone-css-compiler.
- EditableResources physically moved into maven/css-compiler, with its
com.codename1.designer.* and com.codename1.impl.javase.* imports
stripped. GUI functionality exposed as protected throwing hooks
(persistUIContainer, onOpenFileComplete, writeUIXml,
getRuntimeNativeTheme) plus a settable loadedBaseFile field and an
inline IS_MAC constant replacing ResourceEditorApp.IS_MAC.
- New EditableResourcesEditor subclass lives in the Designer and
overrides every hook, reinstating the GUI behavior (UserInterfaceEditor,
ThemeEditor, JavaSEPortWithSVGSupport, getResourceEditor, ...).
- New com.codename1.ui.util.SVGDocument interface in core; javase-svg's
SVG class implements it. EditableResources casts to SVGDocument so the
thin module avoids the compile-time dep on impl.javase.SVG.
- EditableResourcesForCSS, CSSTheme, ResourcesMutator, Color,
MissingNativeBrowserException, PollingFileWatcher, and the
com.codename1.ui.util.xml package moved alongside EditableResources.
- Designer callers bulk-updated: new EditableResources(...) ->
new EditableResourcesEditor(...) with imports added, in
ResourceEditorView, ResourceEditorApp, AddThemeResource, AddUIResource,
CodenameOneTask, CN1CSSCLI, CN1CSSCompiler, CN1CSSInstallerCLI.
- ResourceEditorView.loadedResources retyped to EditableResourcesEditor.
Build pipeline:
- scripts/build-native-themes.sh drives the thin jar (prefers a fresh
target/ build, falls back to ~/.m2). Writes iOSModernTheme.res and
AndroidMaterialTheme.res under Themes/ (gitignored).
- Smoke CSS sources in native-themes/{ios-modern,android-material}/theme.css
with light+dark tokens and includeNativeBool:false to avoid the
self-inheriting recursion trap.
- native-themes/README.md documents the CEF-free subset.
CI:
- pr.yml gains a step that installs css-compiler and runs the
native-themes build, failing on missing outputs.
- designer.yml switched from Ant to Maven for building the Designer jar
and running the CLI CSS smoke test (the Ant Designer build is broken
until its source roots are taught about maven/css-compiler; Maven is
the preferred path per CLAUDE.md anyway). Also runs the native-themes
smoke under xvfb.
Known follow-ups (not in this commit):
- Ant-based Designer build (CodenameOneDesigner/build.xml) still expects
all CSS classes under src/; local NetBeans/Ant developers will need
source-tree awareness of maven/css-compiler or a switch to Maven.
- ResourceEditorView line ~2382 still calls EditableResources.open(...)
returning a base instance; fine for the override-resource side path
but a future EditableResourcesEditor.open(...) factory would be tidier.
- Phase 3+ (real CSS themes, port integration, simulator bundling,
build hints, screenshot tests) pending.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
maven/designer/pom.xml declared a dependency on codenameone-css-compiler without a version, expecting the root pom's dependencyManagement to fill it in. The entry was missing, so every downstream module failed to resolve the POM (observed in PR CI). Add the managed version entry next to codenameone-core. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI (Java CI + Designer CI) surfaced two classes of errors that the refactor missed: 1. Accessor/helper classes declared in core-like packages but living in the Designer source tree (EditorFont, EditorTTFFont, CodenameOneAccessor, animations.AnimationAccessor, plaf.Accessor, plaf.ProtectedUIManager) were left behind when EditableResources moved to the css-compiler module. They use package-private access into core, so they must travel with EditableResources. Moved them into the css-compiler src tree. Designer still sees them via the codenameone-css-compiler dependency. 2. EditableResources.openFile() directly instantiated CodenameOneDesigner's UIBuilderOverride to materialize an XML-stored UI container before re-serializing. UIBuilderOverride imports com.codename1.designer.* (ActionCommand, UserInterfaceEditor) so it cannot live in the thin module. Introduced a new protected hook loadUIContainerFromXml(ComponentEntry) that returns null in the base (triggering the binary-blob fallback already in the loop) and is overridden by EditableResourcesEditor to drive UIBuilderOverride. 3. SimpleWebServer and WebviewSnapshotter (used by ResourcesMutator's CEF image rasterization) had clean imports and were still referenced by the compile path, so they moved to the css-compiler module too. In strict-no-cef builds they are still never invoked. 4. SVGDocument.java switched from /** classic Javadoc to /// markdown comments per the repo's java25-markdown-docs style validator. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two leftover references prevented the css-compiler module from compiling:
- EditableResources.saveXMLFile() still instantiated UIBuilderOverride
directly in the MAGIC_UI branch to materialize a container from a
binary UI resource before writing it back as XML. Wrapped in a new
materializeUIContainer(resourceName) hook; base throws, the Designer
EditableResourcesEditor overrides with the UIBuilderOverride call.
- ResourcesMutator.createScreenshots() used Logger.getLogger(CN1CSSCompiler
.class.getName()) purely as a logger name. Rerouted to
Logger.getLogger(ResourcesMutator.class.getName()).
Also tightened NoCefCSSCLI's header comment (plain text instead of a
broken {@link CN1CSSCLI} reference that javadoc-plugin would flag).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
.claude/scheduled_tasks.lock slipped into the previous commit because it wasn't covered by .gitignore. It's a Claude Code session-local scheduled-wakeup lock, not repo content. Untrack and ignore. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ditor Retyping ResourceEditorView.loadedResources to EditableResourcesEditor broke generateStateMachineCodeEx (takes a base EditableResources and assigns it to the field). Narrower fix: field stays base-typed and the single .getResourceEditor(...) call site casts to the editor subclass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two CI fixes on top of the now-green Java CI: - build-native-themes.sh: ensure_jar() used log() (which went to stdout) AND echo "$jar" inside the same function whose output was captured via $(...) by the caller. Result: the log line "Using CSS compiler jar: <path>" got concatenated with the path and handed to java -jar, which responded with "Unable to access jarfile". Redirect log() to stderr so only the jar path lands on stdout. - designer.yml: the Maven-produced codenameone-designer-*-jar-with-dependencies.jar is actually a ZIP wrapper around the runnable designer_1.jar (see the antrun add-designer-jar-with-dependencies execution in maven/designer/pom.xml). Unzip to a temp dir and run the inner jar. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both PR CI and Designer CI are hitting 'Cannot assign field "cssFile" because "theme" is null' at NoCefCSSCLI.java - meaning CSSTheme.load returned null without any diagnostic. The Designer's original NPE catch logged nothing for non-"encoding properties" NPEs (the Logger.log line was commented out). Re-enable logging for the general case, null-guard the message check, and have NoCefCSSCLI fail with a helpful message if the parser returns null. Next CI run should show the real stack trace. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stack trace from PR CI shows: at com.codename1.io.Util.copy(Util.java:211) at com.codename1.io.Util.readInputStream(Util.java:402) at com.codename1.designer.css.CSSTheme.load(CSSTheme.java:7110) at com.codename1.designer.css.NoCefCSSCLI.main(NoCefCSSCLI.java:55) Util.copy(in, out, bufferSize) unconditionally dereferences Util.getImplementation() to route cleanup() through the platform impl. In the native-themes build the css-compiler runs headless - no Display has been initialized, no Util implementation is set, and the unwrapped null crashes before CSSTheme can even parse the CSS. Guard the cleanup path: if no implementation is set, close the streams directly (which is what every impl's cleanup(Object) ends up doing for InputStream/OutputStream anyway). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Designer CI logs (now with working stack traces) show:
CSSException: Unsupported CSS condition type 10 for ElementSelectorImpl
at com.codename1.designer.css.CSSTheme.getElementForSelector(CSSTheme.java:6561)
My smoke CSS used :pressed / :disabled pseudo-classes. The CN1 CSS
compiler actually handles state selectors as dot-class conditions
(.pressed, .disabled) - see docs/developer-guide/css.asciidoc line 38
("Button.pressed defines styles for the 'Button' UIID's 'pressed' state")
and the SAC_CLASS_CONDITION branch in CSSTheme.getElementForSelector.
The pseudo-class syntax (condition type 10) is not recognized. Switch
smoke themes to .state syntax and clarify the native-themes README.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Next NPE after the pseudo-class fix: at com.codename1.ui.Font.<init>(Font.java:176) at com.codename1.ui.Font.createSystemFont(Font.java:452) at CSSTheme$Element.getThemeFont(CSSTheme.java:4671) at CSSTheme.updateResources(CSSTheme.java:1887) Font(int, int, int) dereferences Display.getInstance().getImplementation() to create a native font - null in the headless css-compiler run. The smoke themes don't need a font to exercise the no-cef pipeline end to end, so drop font-family. Phase 3 will add a minimal headless impl (or make Font creation degrade gracefully when Display is uninitialized) so real themes can specify fonts. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 3: replace the smoke placeholder CSS with real component coverage.
Both themes now style ~25 UIIDs each with light/dark palettes, including:
- base (Component, Form, ContentPane, Container)
- typography (Label, SecondaryLabel, TertiaryLabel, SpanLabel*)
- buttons (Button, RaisedButton, FlatButton + .pressed / .disabled)
- text input (TextField, TextArea, TextHint + focused/disabled)
- selection controls (CheckBox, RadioButton, OnOffSwitch + .selected)
- toolbar (Toolbar, TitleArea, Title, MainTitle, Back/Title commands)
- tabs (Tabs, TabsContainer, Tab, Selected/UnselectedTab)
- side menu (SideNavigationPanel, SideCommand)
- list + MultiButton (List, ListRenderer, MultiButton, MultiLine1..4)
- dialog/sheet (Dialog, DialogBody, DialogTitle, Dialog{Content,Command}Area)
- FAB (FloatingActionButton + .pressed)
- misc (Separator, PopupContent)
Palettes:
- iOS Modern — Apple system colors (accent=#007aff light / #0a84ff dark,
Apple grouped-background surfaces, separator colors); liquid-glass feel
is approximated via solid fills with subtle tonal surface variants.
- Android Material — Material 3 baseline tonal palette (primary=#6750a4
light / #d0bcff dark, Material surface-container tiers). Elevation is
approximated with surface-container-high tonal values since box-shadow
would force CEF rasterization.
Font.java (core) small fix: the package-private Font(int,int,int)
constructor used to NPE when Display.impl was null. The css-compiler
native-themes build is headless (no Display.init) and needs to serialize
font descriptors without actually allocating native font handles. Guard
the createFont call; headless serialization writes face/style/size only
and the native handle is recreated when the resource is loaded in a
running app.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 3 Designer CI revealed: RuntimeException: Unsupported CSS property cn1-pill-border RuntimeException: Unsupported CSS property cn1-round-border Those are not top-level CSS properties in the CN1 compiler; they are values of the cn1-background-type property. Rewrite to cn1-background-type: cn1-pill-border; cn1-background-type: cn1-round-border; Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…iter) Phase 3 CI error was a cascade: the CSS compiler's transformDarkModeMediaQueries turns any selector inside @media (prefers-color-scheme: dark) into $DarkSelector. For component selectors this produces the wanted $DarkButton etc. But for :root the rewrite emits $DarkComponent:root which Flute rejects ("encountered ' '. Was expecting ':' <IDENT> <FUNCTION>"), and every declaration inside that dark :root block is skipped. The light :root block then survives just fine, but because Flute aborts the dark block early the parser never registers those variables. When update_resources later tries to serialize a fg color it finds a raw var() FUNCTION lexical-unit instead of a resolved color and throws "Unsupported color type 41". Simplest path that keeps the compiler as-is: drop CSS variables from the shipped themes and inline hex values per UIID. Light values go in the top-level rules, dark values go in the @media (prefers-color-scheme: dark) block which the compiler maps to $DarkUIID. Every UIID now has a matching dark entry. When the compiler grows real :root-in-@media support (separate change), we can re-introduce variables. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two more headless NPEs surfaced by the real themes: 1. EditorTTFFont.refresh() loaded /com/codename1/impl/javase/Roboto-*.ttf via getClass().getResourceAsStream. That resource ships in the javase port jar, not in our thin css-compiler jar, so the stream is null and java.awt.Font.createFont(null) throws IOException. Guard the null stream and return early; the .res serialization only needs the nativeFontName descriptor, and the native AWT font is recreated at app runtime when the platform impl is available. 2. RoundBorder.<init> calls Display.getInstance().convertToPixels(2) to set its shadowSpread - which dereferences a null impl in the headless build. Make convertToPixels return a 1:1 fallback when impl is null. Conversions are recomputed at runtime. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Next NPE in Phase 3: at com.codename1.ui.Font.getFace(Font.java:742) at com.codename1.ui.util.EditableResources.saveTheme(EditableResources.java:2095) EditableResources serializes system fonts by calling Font.getFace(), getSize(), getStyle(), each of which dereferences Display.impl. In the headless css-compiler build impl is null. Capture face/style/size in the Font(int,int,int) constructor into headlessFace/Style/Size fields and return them from the three accessors when impl is null. Non-system fonts (TTF, bitmap) never enter this path and keep the fields at zero. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Feedback: "SVGDocument should be package private to avoid polluting the UI. Looking at the code I don't see why it needs to be in the core API to begin with." Deleted com.codename1.ui.util.SVGDocument from core. Reverted javase-svg's SVG class to a plain class (no implements). The few EditableResources code paths that need SVG fields now go through a package-private static SvgBridge inside EditableResources itself, which reflectively calls SVG's methods. This is cold code from the css-compiler point of view (SVG paths only fire when the resource being serialized contains SVG images, which the native-themes build never produces) so reflection overhead is a non-issue. Bridge lives where it is used, no cross-module interface or API surface is added. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Port integration for the CSS-generated iOSModernTheme.res and
AndroidMaterialTheme.res:
- IOSImplementation.installNativeTheme() resolves theme by the
existing ios.themeMode hint. "modern" / "liquid" / "auto" loads
/iOSModernTheme.res (with a graceful fall-through to iOS7Theme if
the generator hasn't produced it yet, so apps still boot in a
partial build); "ios7" / "flat" keeps the flat theme; everything
else falls back to the pre-flat iPhone theme. "auto" now defaults
to modern, per the decided release plan.
- AndroidImplementation.installNativeTheme() reads a new
cn1.androidTheme property ("material" | "hololight" | "legacy");
and.hololight=true still maps to hololight for back-compat. Default
is material. Drops the SDK_INT<14 gate (universal Android today)
and swaps the holo-unless-hint logic for the cleaner hint-first
path. Falls back to holo light if the apk doesn't contain the
modern .res (partial build).
- Ports/iOSPort/build.xml -pre-compile copies
../../Themes/iOSModernTheme.res into nativeSources/ so it ends up
in dist/nativeios.jar alongside the legacy .res files.
failonerror=false lets the port still build if
scripts/build-native-themes.sh hasn't produced the file yet
(runtime fallback kicks in).
- Ports/Android/build.xml -pre-compile copies
../../Themes/AndroidMaterialTheme.res into src/ so it lands on the
APK classpath via the existing <fileset dir="src" includes="*.res"/>
jar packaging. Same failonerror=false guard.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The java25-markdown-docs validator rejects /** ... */ Javadoc blocks in CodenameOne/ and Ports/CLDC11/. My Phase 3 edit to Font.java added one for the headlessFace/Style/Size fields. Convert to /// markdown. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SpotBugs on core-unittests flagged DE_MIGHT_IGNORE for the inline catch (IOException ignored) blocks I added for the headless close fallback. Refactor into a closeQuietly(Closeable) helper that prints the exception to stderr instead of silently swallowing it. Semantics preserved (close failure doesn't propagate) but no more "might ignore" warning. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The last run narrows the ButtonTheme hang to the Form(title, layout) constructor - the trace emits runAppearance.enter, setDarkMode.done, refreshTheme.done, then never reaches form.created. Three candidates inside that constructor: the no-arg Form chain, setLayout, or setTitle (which resolves Title/TitleArea/Toolbar UIIDs against the freshly installed modern theme). Split the call into the three sequential steps with a log line between each so the next iOS run names the exact step that deadlocks. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous run pins the hang to new Form() itself - form.newCtor.begin
is the last log, nothing after. Form's field initializers resolve
styles for Title (via Label("", "Title")) and Container (via
titleArea = new Container(...)); the ctor body then resolves Form,
TitleArea, ContentPane, Toolbar (via initGlobalToolbar).
Probe each of those UIIDs' Style up front with a log before/after,
so the next iOS run names the UIID whose getComponentStyle call
goes into the infinite loop on the freshly-installed modern theme.
Likely suspect: Title (only UIID in the modern theme that uses
"native:MainBold", and the iOS port may round-trip to native code
to resolve that font which could deadlock).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Last iOS UI run pins the BottomTheme hang to getComponentStyle('TitleArea')
- the probe emits probeStyle.begin uiid=TitleArea but never reaches .done.
TitleArea is the only probe UIID that uses cn1-derive to reference a
sibling (Toolbar) in the freshly-installed modern theme; Title
(which uses native:MainBold but no derive) resolves fine and Android
renders the same derive-heavy structure without issue.
Inline Toolbar's fg/bg/margin into TitleArea as a workaround so
the iOS screenshots can make progress. The underlying cn1-derive
resolver bug on iOS (after setThemeProps swaps the prop table
mid-flight) still needs a real fix in the port / UIManager; that
is tracked for a follow-up once the fidelity captures confirm this
is the only affected UIID.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The per-step runAppearance trace and probeStyle helper served their one-off purpose of narrowing the iOS cn1-derive hang to TitleArea and landing the inline-Toolbar-props workaround. Keep the install path INFO/WARN/ERR lines (they are useful any time a platform fails to pick up a native theme) and collapse the Form construction back to the one-line new Form(title, layout) form. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
14 tests x (light + dark) x 2 platforms = 56 PNGs captured from run 24862307480 (iOS UI build scripts) and 24862307487 (Test Android build scripts) on 12d47fd. The new theme-fidelity tests were previously producing missing_expected status (non-fatal under CN1SS_FAIL_ON_MISMATCH=1) because no goldens existed - reviewers got no visual diff in PR comments and future theme regressions would not be caught. With these goldens in place the tests become regression-worthy and the PR-comment diff report lights them up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…mpler derives User feedback on the first round of modern-theme captures: 1. Dialog blends into the Form in Android light mode (floating text + buttons without a visible dialog surface). Bumped Material 3 Dialog bg from surface-container (#f3edf7) to surface-container-highest (#e6e0e9) so it sits visibly above the surface (#fef7ff). iOS Dialog stays pure white against the grouped-form #f2f2f7 background. Both themes now inline Dialog/DialogBody/DialogTitle/PopupContent props instead of using cross-UIID cn1-derive. 2. Android Title label renders as a white rectangle on the off-white TitleArea (Title has no explicit bg). Gave Title an explicit background-color matching TitleArea and bumped font-size to 4.5mm (the default size is too small for a title on both platforms). 3. iOS Tabs appeared top-left-aligned instead of the iOS 26 bottom navigation-bar look. Added @tabPlacementInt=2 (Component.BOTTOM) and @tabsFillRowsBool=true to both modern themes; styled TabsContainer as a rounded-rect pill with a surface-container fill and distinct selected-Tab pill inside it. Mirrors the Material 3 bottom-nav-rail look on Android and approximates the iOS 26 pill tab group. A real backdrop-filter glass is still future work and will need port-side code. 4. cn1-derive cross-dependency limit: per user, inheritance should stay simple (child refines parent). Inlined TitleArea/Toolbar, DialogTitle/Title, DialogBody/Dialog, PopupContent/Dialog, RadioButton/CheckBox, TextArea/TextField so only refinement-style derives (Label variants, Button variants, Tab variants, MainTitle from Title) remain. 5. Doc: auto stays on legacy for now. The build-hint docs previously said the default had flipped; reverted to reflect the agreed behaviour - auto (unset) keeps iOS 7 / Holo Light and we will flip in a later release. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The 7338c18 theme edits (bottom tabs, distinct dialog bg, explicit title bg, font-size bumps, derives inlined) intentionally change the rendered pixels of every theme-fidelity test on both iOS and Android. Leaving the existing goldens in place would make CN1SS report 28 "different" captures on Android and tank the build under CN1SS_FAIL_ON_MISMATCH=1. Drop the goldens so the next CI run reports missing_expected (non-fatal) and produces a clean diff report; regenerate them after visually confirming the new captures match the design intent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codify the inheritance rule ("child refines parent" only) and the
set of currently-inlined cross-context pairs so future contributors
know why TitleArea/DialogTitle/etc. don't cn1-derive. Also note
that real backdrop-filter glass needs a separate PR (port-side
UIVisualEffectView / RenderEffect mapping for a new
cn1-backdrop-filter CSS primitive).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tabs.initLaf reads the tabPlacementInt theme constant (Component.TOP / BOTTOM / LEFT / RIGHT) and assigns it to the tabPlacement field - but the constructor has already added tabsContainer to BorderLayout.NORTH from the default field value. Writing the field alone leaves the tabsContainer in NORTH regardless of what the theme asked for. Route through setTabPlacement() so the container is re-parented to the correct BorderLayout slot. The condition guards against a revalidate cascade when the constant matches the current placement. Found while verifying the modern theme puts tabs at the bottom (iOS 26 / Material 3 bottom-nav-rail look). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Component() super-ctor invokes initLaf polymorphically before the Tabs ctor body has allocated tabsContainer. The previous form of this fix called setTabPlacement() unconditionally, which NPE'd inside setTabsLayout() (tabsContainer.setLayout on null) and left the container stuck at NORTH from the original field-default assignment. Check tabsContainer==null: on the first call during super() just stash tabPlace into the field, and rely on the ctor's own setTabPlacement(tabPlacement) call at line 156 to reparent the then-allocated container. On subsequent refreshTheme-triggered initLaf calls, the container exists and we reparent if needed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The existing assignment in Tabs.initLaf does not take effect because initLaf fires during Component()'s super-ctor - at which point Tabs's tabPlacement field hasn't been set, and even if initLaf updates it, the field assignment is inside the (not-yet-started) subclass initialisation phase and the constructor's own setTabPlacement call reads the value Java initialised to default (TOP=0), never the theme's value. Read the constant directly in the new Tabs() path (tabP == -1) before the setTabPlacement call so the container actually lands in the theme-specified slot. initLaf continues to handle subsequent refreshTheme cycles for the tabsContainer != null case. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Android captures on 3a2efe0 still show tabs at TOP despite the direct-read fix in the ctor. Either getThemeConstant returns -1 at this call site (theme constants not yet populated?), or the setTabPlacement call hits an unexpected early-return path. Log the values so the next Android run tells us which is the case. Log lines are throwaway and will be removed once the root cause is fixed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
buildTheme() strips @-prefixed keys into a separate themeConstants
map and drops them from the main themeProps map. refreshTheme()
rebuilt the input to setThemePropsImpl from themeProps alone, so
every constant (tabPlacementInt, tabsFillRowsBool,
paintsTitleBarBool, switchTrackScaleY, etc.) was silently lost on
the second call. Diagnosed via a CI println in the Tabs ctor:
getThemeConstant("tabPlacementInt", -1) returned -1 even though
setThemeProps had been called with the constant moments earlier.
Re-add the @-prefixed keys from themeConstants when rebuilding the
input Hashtable so setThemePropsImpl -> buildTheme sees the shape
it expects. Also drop the diagnostic println from Tabs.java now
that the root cause is fixed.
This unblocks the Android/iOS modern-theme captures rendering
tabs at the bottom (tabPlacementInt=2) instead of stuck at TOP
because the refresh-then-construct flow in
DualAppearanceBaseTest.runAppearance was silently resetting the
placement constant back to the default.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…contrast fixes User review round 2 on 95290d2 captures: * Android fonts / padding were too tall for the small emulator screen. Bumped everything down a notch - Label 3.5mm, Title 4mm, Button padding 1.5/3mm, CheckBox/Radio 0.5/1.5mm, TextField 1.5/2.5mm. Keeps Material 3 proportions without overflowing. * Android bottom-tab change reverted. Material 3 Tabs stay at TOP (bottom-rail is a separate component, not Tabs). Removed @tabPlacementInt/tabsFillRowsBool from the Android theme and restored the flat underline-by-color top-tab styling. * iOS TabsContainer + selected/pressed Tab switched from border-radius to cn1-pill-border so the group is a true pill. * Button / RaisedButton / FlatButton now declare text-align: center explicitly (the legend overlay was complaining about left-aligned button text). * RaisedButton.pressed had no explicit text color so it was invisible against the darker pressed fill. Pinned to #ffffff on iOS and #21005d on Android. * Android Raised vs default Button now use different container tones (elevated #eaddff vs primary #6750a4) so the pair stays distinct in both light and dark mode. Showcase dark no longer collapses them. * iOS Dark Dialog moved from #1c1c1e (matched form bg) to #2c2c2e (elevated surface) so the dialog doesn't look like floating text in dark mode. * CheckBox / RadioButton gained icon-gap: 2mm for breathing room between the symbol and the label text. native-themes/README.md cn1-derive rule was not changed in this commit; the cn1-pill-border / border-radius + border CEF caveats still apply. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds docs/developer-guide/Native-Themes.asciidoc and registers it in developer-guide.asciidoc between Advanced-Theming and css. Covers: * How to enable the iOS liquid-glass / Android Material 3 themes via ios.themeMode / cn1.androidTheme / cn1.nativeTheme, plus the and.hololight back-compat hint. * Default stays on legacy; auto flip is planned for later. * Dark mode wiring (@media prefers-color-scheme: dark -> $DarkUIID, Display.setDarkMode, system appearance). * Overriding the palette statically (includeNativeBool: true in the user theme) and dynamically (UIManager.addThemeProps + refreshTheme), with a pointer at the runtime-override example test. * Complete table of @constants each theme exposes (commandBehavior, tabPlacementInt, tabsFillRowsBool, switchThumbScaleY, switchTrackScaleX, ios7StatusBarHack, etc.) with their iOS and Android defaults and what each one controls. * Catalogue of every styled UIID grouped by role: base, typography, buttons, inputs, navigation / containers, dialogs / overlays. * cn1-derive rule of thumb ("child refines parent") with safe and deliberately-avoided examples; names the cross-context chains that now inline their props instead of deriving. * CEF-free subset constraints (allowed vs forbidden CSS). * Future backdrop-filter glass primitive as out-of-scope work. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…radio icons User feedback on d412866 round: * Add an opt-in textured backdrop (diagonal-stripe palette, light and dark variants) to DualAppearanceBaseTest. Dialog and Tabs tests opt in so any translucent surface reveals its see-through tint instead of blending into a plain Form bg. Non-translucent tests keep the clean surface. * Beef up the iOS MultiButton styling: 3mm/4mm padding, 4mm primary bold line, 3.5mm secondary regular, 3mm tertiary/quaternary, left-aligned text, 3mm icon gap. Matches the iOS Settings row density the user expected. * Per-platform CheckBox/RadioButton icons: DefaultLookAndFeel now honours four new optional theme constants (@checkBoxCheckedIconInt / @checkBoxUncheckedIconInt / @radioCheckedIconInt / @radioUncheckedIconInt) containing the Material icon codepoint to use for each state. Existing themes that don't set these see no change. The iOS modern theme uses CHECK_CIRCLE / CHECK_CIRCLE_OUTLINE for checkboxes (Reminders-app circle aesthetic) and keeps the circular radio glyphs; Android Material keeps the default square check-box so Material 3 look is preserved. * Dev guide documents the four new icon constants alongside the existing @constants table. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Setting the texture via form.getUnselectedStyle().setBgPainter silently did not take effect on Android: the captures on aeff616 still showed the plain theme form bg. The Form's style pipeline installs a BGPainter at component-init time that gets re-initialised on show(), so any post-ctor setBgPainter replacement is discarded before the paint loop runs. Instead override paintBackground on the anonymous Form subclass used by runAppearance and paint the texture directly there when useTexturedBackdrop() is true. Same painter, same colours; just a location that actually survives the style pipeline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The paintBackground override does paint the diagonal-stripe texture, but the ContentPane / TitleArea above it have their own opaque bgColor from the modern theme and immediately wash over it with a solid surface fill. Setting bgTransparency=0 on those sub-containers when useTexturedBackdrop() is true lets the texture read through, so any translucent widget on top of them shows its see-through tint against the stripes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…alignment
User feedback round 4:
* Remove headless-Display fallbacks added to CodenameOne core for
the css-compiler. Replace them with a proper
HeadlessCssCompilerImplementation in maven/css-compiler that gets
installed into Display.impl + Util.implInstance via reflection at
the start of NoCefCSSCLI.main, mirroring the unit-test module's
TestCodenameOneImplementation pattern. CN1 core no longer carries
compiler-only stubs.
* Tabs.initLaf reads tabPlacementInt via the component's own
getUIManager() instead of UIManager.getInstance().
* BuildHintSchemaDefaults gets a class-level Javadoc that explains
why it exists (publishes hint schema metadata for the simulator's
Build Hints UI), what fails without it, and that it is unrelated
to live CSS recompilation.
* Native-Themes developer-guide chapter rewritten:
- Drops implementation-detail constants (commandBehavior,
ios7StatusBarHack, paintsTitleBarBool) and the CEF-free /
Platform-constraints sections - those are framework-internal,
not user-facing.
- Documents each platform's design palette (Material 3 baseline
+ Apple system) with a colour-role -> UIID mapping so brand
overrides are obvious.
- Notes that Display.setDarkMode(null) follows the device.
- Calls out platform-specific UIIDs (iOS Settings-style
MultiButton, top-tab vs bottom-pill behaviour, status-bar
treatment).
- Documents the four optional check-box / radio glyph constants
and the @switchTrackScale / @tabPlacementInt tuning constants.
* iOS Dialog now uses rgba(.., 0.78) so it reads as translucent
over the textured backdrop. DialogBody / Title / ContentPane /
CommandArea are transparent so the Dialog surface paints once
and the rounded corners no longer expose a different inner
shade. Same treatment for dark mode (rgba(44,44,46,0.78)).
* iOS Tabs container + selected tab use rgba so the pill group
reads as glass-frosted. Dark mode unselected tab text stays on
the translucent dark surface.
* Android Dialog sub-UIIDs (Body / Title / ContentPane /
CommandArea) are transparent so the Dialog surface is the only
fill - fixes the visible body-vs-dialog colour mismatch in
light mode.
* iOS MultiButton: rgba surface + 1mm/3mm margin + 3mm border-radius
so the rows read as separated cards instead of edge-to-edge
stripes.
* Switch (both themes) zero padding + margin tuned so the track's
left edge lands at the same x as the Label text-left of the row
above (fixes the visible misalignment).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Generated projects now ship with ios.themeMode=modern, cn1.androidTheme=material, and the cross-platform cn1.nativeTheme=modern hint already set in the project's codenameone_settings.properties. New apps get the iOS liquid-glass + Android Material 3 themes by default; existing projects are unaffected because the cn1 framework itself still defaults to legacy when these hints are unset. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sets ios.themeMode=modern, cn1.androidTheme=material, cn1.nativeTheme=modern in the playground's codenameone_settings.properties so users browsing the playground see the iOS liquid-glass and Material 3 looks while they explore components. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ba23584 to
4059455
Compare
Cloudflare Preview
|
The pass-4 revert that removed headless fallbacks left an unused import. PMD flagged it as UnnecessaryImport. Drop it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…emplates * Root-cause fix for the iOS cn1-derive hang: UIManager.resetThemeProps unconditionally injected a defensive Toolbar.derive=TitleArea default. When a user theme declared TitleArea.derive=Toolbar (the iOS modern theme does), themeProps ended up with both directions and createStyle's derive resolver looped forever between them. Diagnosed via instrumented Log.p tracing on the derive-investigation branch (PR #4810): Toolbar resolution derived TitleArea, TitleArea derived Toolbar, lather/rinse/SIGTERM. Skip the legacy default whenever the installed theme already wires TitleArea -> Toolbar. iOS modern theme reverts the workaround that inlined Toolbar's props into TitleArea; cn1-derive: Toolbar now works correctly. * iOS Tabs polish: TabbedPane (the inner content area) gets an explicit surface fill in both light and dark blocks so the tab pages stay opaque against the textured backdrop in both modes (was opaque-light / transparent-dark). The pill TabsContainer is the only translucent piece. Tab text + icons stay neutral (#000 light / #fff dark) regardless of selection state - the selected pill now uses surface-tertiary for visible contrast instead of an accent colour. Reduced TabsContainer / Tab padding so the selected pill centers vertically inside the group. * iOS Dark Dialog opacity bumped from 0.78 to 0.95 - lower opacity let bright textured-backdrop stripes bleed through and clash with the white text. The light Dialog stays at 0.78 because the light backdrop palette is gentle enough that text contrast is fine at that level. * Generative templates: scripts/initializr and scripts/cn1playground ship a common.zip that the host app expands into newly-generated CN1 projects. Both zips's bundled common/codenameone_settings.properties now include the three modern-theme hints by default so any project spawned by initializr or by the playground starts with the liquid-glass + Material 3 themes pre-selected. The host apps' own preview settings (which I touched earlier) keep the modern themes for their in-app preview. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DialogTheme tests use SpanLabel for the dialog body text. The default Label fill is opaque (no background-color in my CSS, so it falls to defaultStyle = surface white / dark). Inside a translucent Dialog the SpanLabel's white block was painting over the parent Dialog's translucency and reading as a visible different shade. Set SpanLabel + SpanLabelText to background-color: transparent on both themes; Label itself stays as it was. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SpanLabel (CodenameOne/src/com/codename1/components/SpanLabel.java
line 91) sets its outer UIID to "Container", not "SpanLabel". The
SpanLabel { background-color: transparent; } rule I added in the
previous pass therefore never reached the actual rendered widget -
the resolved Container style filled an opaque surface-coloured
block over whatever parent lay below. In the Dialog test that block
read as a visibly different shade inside the dialog body.
Make Container transparent by default in both modern themes.
Container is a layout-only UIID; the specific UIIDs that DO want a
surface (Form, Dialog, Tab, FloatingActionButton, etc.) all declare
their bgColor explicitly so this change does not regress them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SpanLabel.java line 97 sets its internal TextArea's UIID to "Label". Default Label had no explicit background-color and was falling back to the opaque defaultStyle, painting a visible white block behind the wrapped dialog body text - distinct from the Dialog's own translucent surface. Set Label background-color: transparent on both modern themes. UIIDs that need a label-with-fill (RaisedButton, FAB, etc.) declare their bgColor explicitly so this change doesn't regress them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Tabs.java: gate setSafeArea on `tabsSafeAreaBool` theme constant so a floating-pill tab bar can opt out of the internal safe-area padding (back-compat default true). - iOS Modern theme: tabsSafeAreaBool=false, tighten Tab vertical padding, bump TabsContainer bottom margin to clear the home indicator since the internal inset is gone. - UIManager.refreshTheme: switch to entrySet iteration to silence SpotBugs WMI_WRONG_MAP_ITERATOR. - cn1playground CN1Playground: layerIosTheme/layerAndroidTheme prefer the modern Theme.res files for the live preview, fall back to legacy. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Tabs.java: when tabsSafeAreaBool=false the pill (tabsContainer) gets a parent Container (tabsContainerHost) that absorbs the safe-area inset as bottom padding. The pill draws tightly while still clearing the home indicator, instead of either inflating its own background into the indicator zone (the previous behaviour) or losing the indicator clearance entirely (the prior fix). Legacy themes leave tabsSafeAreaBool=true and behave exactly as before. - iOS Modern theme: revert the 5mm bottom-margin hack and the squeezed Tab vertical padding now that the wrapper handles indicator clearance. - cn1playground: drop the legacy iOS7/iPhone/androidTheme fallbacks in layerIosTheme/layerAndroidTheme so the live preview always uses the modern themes. - initializr: override Lifecycle.init to layer iOSModernTheme on top of whatever the JavaScript port supplied. The deployed JS port doesn't yet pick up ios.themeMode=modern, so the live preview previously inherited the legacy iOS7 base. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

























































Summary
First slice of the native-themes refactor planned in /plan. Extracts the Codename One CSS compiler into a thin
maven/css-compilermodule with clean dependencies (core + flute + sac) so the shipped iOS Modern (liquid-glass) and Android Material themes can be generated at framework build time without pulling in JavaSE / JavaFX / CEF / the Designer GUI.strictNoCefflag +enforceNoCef()pre-scan inCSSTheme+-no-cefCLI arg onCN1CSSCLI+ newNoCefCSSCLIminimal entry point.EditableResourcesphysically moved intomaven/css-compiler; designer/javase imports stripped; four throwing hooks introduced;EditableResourcesEditorsubclass in the Designer overrides the hooks. Newcom.codename1.ui.util.SVGDocumentinterface in core replaces the compile-time dep onimpl.javase.SVG.CSSTheme,ResourcesMutator,Color, helpers, and thecom.codename1.ui.util.xmlpackage moved alongside.maven/designernow depends oncodenameone-css-compiler.scripts/build-native-themes.sh+ smoke CSS atnative-themes/{ios-modern,android-material}/theme.css+Themes/.gitignore.pr.ymlgains a step that installs css-compiler and runs the native-themes build;designer.ymlswitched to Maven for building the Designer jar + CLI CSS smoke + native-themes smoke under xvfb.Plan: /Users/shai/.claude/plans/at-the-moment-we-dapper-origami.md (approved earlier).
Known follow-ups (not in this PR)
CodenameOneDesigner/build.xml) still expects all CSS classes undersrc/; needs source-root awareness ofmaven/css-compileror NetBeans/Ant devs to switch to Maven.ResourceEditorView~line 2382 still callsEditableResources.open(...)returning a base instance — fine for the override-resource side path but a futureEditableResourcesEditor.open(...)factory would be tidier.installNativeTheme()rewrites, simulator bundling + override menu,cn1.nativeThemebuild hint, screenshot fidelity tests).Test plan
pr.ymlmatrix (Java 8/17/21) builds green including the new "Build CSS compiler and smoke native-themes" step.designer.ymlbuilds the Designer jar via Maven, the CLI CSS compile smoke produces a non-empty.res, and the native-themes smoke step writesThemes/iOSModernTheme.res+Themes/AndroidMaterialTheme.res.ant.yml(Java CI) full reactormvn install -Plocal-dev-javasepasses../scripts/build-native-themes.shlocally aftermvn -pl css-compiler -am installproduces both.resfiles and an intentional forbidden rule (e.g.box-shadow: 0 2px 4px #000in a theme CSS) fails the build with the offending-UIID list..resfile in the Designer GUI and confirm all resource types still open editors correctly (proves theEditableResourcesEditorwiring).🤖 Generated with Claude Code