diff --git a/.github/workflows/_build-ios-port.yml b/.github/workflows/_build-ios-port.yml index 577e7aa8d2..e7d2d585b0 100644 --- a/.github/workflows/_build-ios-port.yml +++ b/.github/workflows/_build-ios-port.yml @@ -62,7 +62,7 @@ jobs: id: src_hash run: | set -euo pipefail - SRC_HASH=$(find CodenameOne/src Ports/iOSPort vm/JavaAPI vm/ByteCodeTranslator Themes \ + SRC_HASH=$(find CodenameOne/src Ports/iOSPort vm/JavaAPI vm/ByteCodeTranslator Themes native-themes \ -type f \( -name '*.java' -o -name '*.m' -o -name '*.h' -o -name '*.xml' -o -name '*.properties' -o -name '*.css' \) 2>/dev/null \ | sort | xargs shasum -a 256 | shasum -a 256 | awk '{print $1}') POM_HASH=$(find . -name 'pom.xml' -not -path './scripts/*' 2>/dev/null \ diff --git a/.github/workflows/ios-packaging.yml b/.github/workflows/ios-packaging.yml index 4b35730fc3..1db554a0e3 100644 --- a/.github/workflows/ios-packaging.yml +++ b/.github/workflows/ios-packaging.yml @@ -85,7 +85,7 @@ jobs: id: src_hash run: | set -euo pipefail - SRC_HASH=$(find CodenameOne/src Ports/iOSPort vm/JavaAPI vm/ByteCodeTranslator Themes \ + SRC_HASH=$(find CodenameOne/src Ports/iOSPort vm/JavaAPI vm/ByteCodeTranslator Themes native-themes \ -type f \( -name '*.java' -o -name '*.m' -o -name '*.h' -o -name '*.xml' -o -name '*.properties' -o -name '*.css' \) 2>/dev/null \ | sort | xargs shasum -a 256 | shasum -a 256 | awk '{print $1}') POM_HASH=$(find . -name 'pom.xml' -not -path './scripts/*' 2>/dev/null \ diff --git a/.github/workflows/scripts-android.yml b/.github/workflows/scripts-android.yml index 4a42658c9b..2bbcf41f30 100644 --- a/.github/workflows/scripts-android.yml +++ b/.github/workflows/scripts-android.yml @@ -22,6 +22,8 @@ name: Test Android build scripts - '!CodenameOne/src/**/*.md' - 'Ports/Android/**' - '!Ports/Android/**/*.md' + - 'native-themes/android-material/**' + - '!native-themes/android-material/**/*.md' - 'maven/**' - '!maven/core-unittests/**' - 'tests/**' @@ -49,6 +51,8 @@ name: Test Android build scripts - '!CodenameOne/src/**/*.md' - 'Ports/Android/**' - '!Ports/Android/**/*.md' + - 'native-themes/android-material/**' + - '!native-themes/android-material/**/*.md' - 'maven/**' - '!maven/core-unittests/**' - 'tests/**' diff --git a/.github/workflows/scripts-ios-native.yml b/.github/workflows/scripts-ios-native.yml index 5fc0b56aac..0c1ec5ddb5 100644 --- a/.github/workflows/scripts-ios-native.yml +++ b/.github/workflows/scripts-ios-native.yml @@ -20,6 +20,8 @@ on: - '!CodenameOne/src/**/*.md' - 'Ports/iOSPort/**' - '!Ports/iOSPort/**/*.md' + - 'native-themes/ios-modern/**' + - '!native-themes/ios-modern/**/*.md' - 'vm/**' - '!vm/**/*.md' - 'tests/**' @@ -47,6 +49,8 @@ on: - '!CodenameOne/src/**/*.md' - 'Ports/iOSPort/**' - '!Ports/iOSPort/**/*.md' + - 'native-themes/ios-modern/**' + - '!native-themes/ios-modern/**/*.md' - 'vm/**' - '!vm/**/*.md' - 'tests/**' @@ -108,7 +112,7 @@ jobs: id: src_hash run: | set -euo pipefail - SRC_HASH=$(find CodenameOne/src Ports/iOSPort vm/JavaAPI vm/ByteCodeTranslator Themes \ + SRC_HASH=$(find CodenameOne/src Ports/iOSPort vm/JavaAPI vm/ByteCodeTranslator Themes native-themes \ -type f \( -name '*.java' -o -name '*.m' -o -name '*.h' -o -name '*.xml' -o -name '*.properties' -o -name '*.css' \) 2>/dev/null \ | sort | xargs shasum -a 256 | shasum -a 256 | awk '{print $1}') POM_HASH=$(find . -name 'pom.xml' -not -path './scripts/*' 2>/dev/null \ diff --git a/.github/workflows/scripts-ios.yml b/.github/workflows/scripts-ios.yml index 718f24b681..76859359c6 100644 --- a/.github/workflows/scripts-ios.yml +++ b/.github/workflows/scripts-ios.yml @@ -22,6 +22,8 @@ on: - '!CodenameOne/src/**/*.md' - 'Ports/iOSPort/**' - '!Ports/iOSPort/**/*.md' + - 'native-themes/ios-modern/**' + - '!native-themes/ios-modern/**/*.md' - 'vm/**' - '!vm/**/*.md' - 'tests/**' @@ -51,6 +53,8 @@ on: - '!CodenameOne/src/**/*.md' - 'Ports/iOSPort/**' - '!Ports/iOSPort/**/*.md' + - 'native-themes/ios-modern/**' + - '!native-themes/ios-modern/**/*.md' - 'vm/**' - '!vm/**/*.md' - 'tests/**' @@ -118,7 +122,7 @@ jobs: id: src_hash run: | set -euo pipefail - SRC_HASH=$(find CodenameOne/src Ports/iOSPort vm/JavaAPI vm/ByteCodeTranslator Themes \ + SRC_HASH=$(find CodenameOne/src Ports/iOSPort vm/JavaAPI vm/ByteCodeTranslator Themes native-themes \ -type f \( -name '*.java' -o -name '*.m' -o -name '*.h' -o -name '*.xml' -o -name '*.properties' -o -name '*.css' \) 2>/dev/null \ | sort | xargs shasum -a 256 | shasum -a 256 | awk '{print $1}') POM_HASH=$(find . -name 'pom.xml' -not -path './scripts/*' 2>/dev/null \ diff --git a/docs/developer-guide/Native-Themes.asciidoc b/docs/developer-guide/Native-Themes.asciidoc index 67953807d4..2401407fe8 100644 --- a/docs/developer-guide/Native-Themes.asciidoc +++ b/docs/developer-guide/Native-Themes.asciidoc @@ -147,8 +147,11 @@ inherits from it. |=== To rebrand the app, override the colour at the role level rather -than touching every UIID. For example, to flip the primary -container palette to teal: +than touching every UIID. The example below layers a teal palette +on top from your app's own `theme.css`; if you'd rather edit the +shipped theme directly and ship a fully recoloured `.res`, see +`Recolouring the accent palette via CSS variables` further down - +that path lets a single variable edit drive every accent UIID. [source,css] ---- @@ -240,6 +243,134 @@ accent-driven UIIDs the same way as Android above. The colour names match Apple's `UIColor.systemBlue` etc. so you can mirror the SF Symbols semantics if you want. +=== Recolouring the accent palette via CSS variables + +Both shipped themes expose their accent palette as CSS variables +inside the `#Constants` block at the top of the theme file, so a +single edit recolours every accent-bearing component without +re-declaring rules per UIID. This is the simplest way to ship a +rebranded native theme with your app. + +.iOS modern (`native-themes/ios-modern/theme.css`) +[cols="2,1,1", options="header"] +|=== +|Variable |Light |Dark + +|`--accent-color` +|`#007aff` +|`#0a84ff` + +|`--accent-pressed-color` +|`#0064d1` +|`#64b1ff` + +|`--accent-disabled-color` +|`#b3d4ff` +|`#004a99` + +|`--accent-on-color` +|`#ffffff` +|(same) +|=== + +Drives `Button`, `RaisedButton`, `CheckBox.selected`, +`RadioButton.selected`, `OnOffSwitch`, `BackCommand`, +`TitleCommand`, `FloatingActionButton`. + +.Android Material 3 (`native-themes/android-material/theme.css`) +[cols="2,1,1,2", options="header"] +|=== +|Variable |Light |Dark |Material 3 token + +|`--accent-color` +|`#6750a4` +|`#d0bcff` +|`primary` + +|`--accent-on-color` +|`#ffffff` +|`#381e72` +|`on-primary` + +|`--accent-container-color` +|`#eaddff` +|`#4f378b` +|`primary-container` + +|`--accent-on-container-color` +|`#21005d` +|`#eaddff` +|`on-primary-container` + +|`--accent-pressed-color` +|`#d0bcff` +|`#4f378b` +|`state-pressed` +|=== + +Drives `Button`, `RaisedButton`, `FlatButton`, `CheckBox.selected`, +`RadioButton.selected`, `Switch.selected`, `OnOffSwitch`, +`BackCommand`, `TitleCommand`, `Tab.selected`, `SelectedTab`, +`SideCommand.pressed`, `MultiButton.pressed`, +`FloatingActionButton`. + +==== Forking a theme to rebrand + +Copy the native theme into your project, edit the variable values, +and rebuild: + +[source,bash] +---- +cp -R native-themes/ios-modern native-themes/ios-myapp +$EDITOR native-themes/ios-myapp/theme.css +./scripts/build-native-themes.sh +---- + +For example, to flip the iOS accent from system blue to brand +magenta in both light and dark mode, change just the variable +declarations at the top of the file: + +[source,css] +---- +#Constants { + --accent-color: #d81b60; + --accent-color-dark: #ff80ab; + --accent-pressed-color: #b71c5c; + --accent-pressed-color-dark: #f06292; + --accent-disabled-color: #ffd6e2; + --accent-disabled-color-dark: #4a0026; + --accent-on-color: #ffffff; + /* ... existing constants below ... */ +} +---- + +Every UIID listed above picks up the new colour. No per-UIID rule +edits needed. + +==== When variable substitution applies + +The CSS compiler resolves `var()` references at *theme-compile +time* - the next run of `scripts/build-native-themes.sh` bakes +the values into the `.res` file. This is what you want when +shipping a customised theme with your app: edit the source, build +once, ship the `.res`. + +It is *not* a runtime mechanism. Two cases where the variable +indirection does **not** propagate: + +* **Layered app `theme.css` redefining a variable.** Your app's + `theme.css` is compiled into a separate `.res` and merged at + startup; redeclaring `--accent-color` there has no effect on + the already-baked native theme styles. Override the specific + UIIDs instead (see `Customising in your own theme` below), or + fork the native theme. +* **Switching the accent palette while the app is running.** + Live theming, in-app accent toggles, A/B tests - use the + `UIManager.addThemeProps` path documented in the next section. + +In practice neither is a problem: most apps fork once at build +time and ship a single recoloured native theme. + === Runtime palette override Push a Hashtable of theme props through `UIManager.addThemeProps` @@ -382,6 +513,12 @@ RaisedButton.disabled { background-color: #ffd6e2; color: #ffffff; } The user's CSS is layered on top of the native theme at app launch, so refresh / restart picks the override up. +For a wholesale accent rebrand, forking the shipped theme.css and +editing its CSS variables (see `Recolouring the accent palette via +CSS variables` above) is usually less code than redeclaring every +accent-bearing UIID here. Layering remains the right choice when +you want to keep the framework's accent and only tweak a few UIIDs. + === Inheriting from a native UIID `cn1-derive` lets a custom UIID start from one of the native diff --git a/native-themes/android-material/theme.css b/native-themes/android-material/theme.css index 8e7553dc84..410a3d868e 100644 --- a/native-themes/android-material/theme.css +++ b/native-themes/android-material/theme.css @@ -5,8 +5,7 @@ * rounded/pill borders, state selectors via .pressed / .selected / * .disabled. Light and dark palettes live at the bottom of this file * in a prefers-color-scheme block the compiler rewrites into $DarkUIID - * entries. Colors are inlined (not CSS variables) because the rewriter - * operates at string level and doesn't re-scope :root declarations. + * entries. * * Material 3 baseline palette reference: * primary #6750a4 dark #d0bcff @@ -24,12 +23,39 @@ * state-disabled #e0dce4 dark #2b2930 * on-disabled #a5a0ab dark #5c5967 * - * Overridable palette: user apps layer their own theme.css or runtime - * UIManager.addThemeProps on top of this theme - see - * PaletteOverrideThemeScreenshotTest for a working example. + * Accent palette is declared as CSS variables inside #Constants so a + * fork of this file can recolor every accent-bearing component + * (Button, RaisedButton, FlatButton, CheckBox.selected, + * RadioButton.selected, Switch.selected, OnOffSwitch, BackCommand, + * TitleCommand, Tab.selected, SideCommand.pressed, MultiButton.pressed, + * FloatingActionButton) by changing one declaration. The compiler + * resolves var() at parse time, so layering an unrelated theme.css + * that only redefines the variable does NOT retroactively recolor + * the already-baked styles - runtime accent override still goes + * through UIManager.addThemeProps on the specific UIIDs (see + * PaletteOverrideThemeScreenshotTest). */ #Constants { + /* Material 3 primary accent palette - see header for the override + semantics. Variable names mirror the M3 token names: --accent + maps to "primary", --accent-on to "on-primary", --accent-container + to "primary-container", --accent-on-container to + "on-primary-container", --accent-pressed to "state-pressed". + Each pair has a -dark counterpart used by the corresponding + $DarkUIID entries the dark @media block compiles into. Keep + both halves in sync if a token shifts. */ + --accent-color: #6750a4; + --accent-color-dark: #d0bcff; + --accent-on-color: #ffffff; + --accent-on-color-dark: #381e72; + --accent-container-color: #eaddff; + --accent-container-color-dark: #4f378b; + --accent-on-container-color: #21005d; + --accent-on-container-color-dark: #eaddff; + --accent-pressed-color: #d0bcff; + --accent-pressed-color-dark: #4f378b; + includeNativeBool: false; darkModeBool: true; commandBehavior: Native; @@ -118,8 +144,8 @@ SpanLabelText { cn1-derive: Label; background-color: transparent; } only honours base-level cn1-derive), so explicit values are the reliable fix. */ Button { - color: #ffffff; - background-color: #6750a4; + color: var(--accent-on-color, #ffffff); + background-color: var(--accent-color, #6750a4); padding: 1.5mm 4mm 1.5mm 4mm; margin: 0.8mm; font-family: "native:MainRegular"; @@ -128,8 +154,8 @@ Button { text-align: center; } Button.pressed { - color: #21005d; - background-color: #d0bcff; + color: var(--accent-on-container-color, #21005d); + background-color: var(--accent-pressed-color, #d0bcff); padding: 1.5mm 4mm 1.5mm 4mm; margin: 0.8mm; font-family: "native:MainRegular"; @@ -153,8 +179,8 @@ Button.disabled { dark mode where Button's own container color otherwise matches. */ RaisedButton { cn1-derive: Button; - color: #21005d; - background-color: #eaddff; + color: var(--accent-on-container-color, #21005d); + background-color: var(--accent-container-color, #eaddff); padding: 1.5mm 4mm 1.5mm 4mm; margin: 0.8mm; font-family: "native:MainRegular"; @@ -163,8 +189,8 @@ RaisedButton { text-align: center; } RaisedButton.pressed { - color: #21005d; - background-color: #d0bcff; + color: var(--accent-on-container-color, #21005d); + background-color: var(--accent-pressed-color, #d0bcff); padding: 1.5mm 4mm 1.5mm 4mm; margin: 0.8mm; font-family: "native:MainRegular"; @@ -186,11 +212,11 @@ RaisedButton.disabled { FlatButton { cn1-derive: Button; background-color: transparent; - color: #6750a4; + color: var(--accent-color, #6750a4); cn1-background-type: cn1-pill-border; text-align: center; } -FlatButton.pressed { background-color: #eaddff; color: #21005d; cn1-background-type: cn1-pill-border; } +FlatButton.pressed { background-color: var(--accent-container-color, #eaddff); color: var(--accent-on-container-color, #21005d); cn1-background-type: cn1-pill-border; } TextField { color: #1d1b20; @@ -231,7 +257,7 @@ CheckBox { font-size: 3.5mm; icon-gap: 2mm; } -CheckBox.selected { color: #6750a4; } +CheckBox.selected { color: var(--accent-color, #6750a4); } CheckBox.disabled { color: #a5a0ab; background-color: #e0dce4; } RadioButton { @@ -243,7 +269,7 @@ RadioButton { font-size: 3.5mm; icon-gap: 2mm; } -RadioButton.selected { color: #6750a4; } +RadioButton.selected { color: var(--accent-color, #6750a4); } RadioButton.disabled { color: #a5a0ab; background-color: #e0dce4; } /* Switch's track left-edge aligns with Label's text left-edge @@ -255,7 +281,7 @@ Switch { margin: 1mm 1.5mm 1mm 1.5mm; text-align: left; } -Switch.selected { color: #ffffff; background-color: #6750a4; } +Switch.selected { color: var(--accent-on-color, #ffffff); background-color: var(--accent-color, #6750a4); } Switch.disabled { color: #a5a0ab; background-color: #e0dce4; } OnOffSwitch { @@ -265,7 +291,7 @@ OnOffSwitch { padding: 1mm 2mm 1mm 2mm; cn1-background-type: cn1-pill-border; } -OnOffSwitch.selected { background-color: #6750a4; color: #ffffff; } +OnOffSwitch.selected { background-color: var(--accent-color, #6750a4); color: var(--accent-on-color, #ffffff); } Toolbar { background-color: #fef7ff; @@ -292,8 +318,8 @@ Title { } MainTitle { cn1-derive: Title; } -BackCommand { cn1-derive: Button; background-color: transparent; color: #6750a4; padding: 1mm 2mm 1mm 2mm; } -TitleCommand { cn1-derive: Button; background-color: transparent; color: #6750a4; padding: 1mm 2mm 1mm 2mm; } +BackCommand { cn1-derive: Button; background-color: transparent; color: var(--accent-color, #6750a4); padding: 1mm 2mm 1mm 2mm; } +TitleCommand { cn1-derive: Button; background-color: transparent; color: var(--accent-color, #6750a4); padding: 1mm 2mm 1mm 2mm; } /* Material 3 top tabs: flat surface with selected tab underlined by color rather than a pill fill. No border-radius here - @@ -311,10 +337,10 @@ Tab { font-size: 3.5mm; text-align: center; } -Tab.selected { color: #6750a4; background-color: #fef7ff; } -Tab.pressed { color: #21005d; background-color: #eaddff; } +Tab.selected { color: var(--accent-color, #6750a4); background-color: #fef7ff; } +Tab.pressed { color: var(--accent-on-container-color, #21005d); background-color: var(--accent-container-color, #eaddff); } -SelectedTab { cn1-derive: Tab; color: #6750a4; } +SelectedTab { cn1-derive: Tab; color: var(--accent-color, #6750a4); } UnselectedTab { cn1-derive: Tab; color: #49454f; } SideNavigationPanel { background-color: #fef7ff; padding: 0; margin: 0; } @@ -326,7 +352,7 @@ SideCommand { padding: 2mm 3mm 2mm 3mm; margin: 0; } -SideCommand.pressed { background-color: #eaddff; } +SideCommand.pressed { background-color: var(--accent-container-color, #eaddff); } List { background-color: #fef7ff; padding: 0; margin: 0; } @@ -346,7 +372,7 @@ MultiButton { font-family: "native:MainRegular"; font-size: 3.5mm; } -MultiButton.pressed { background-color: #eaddff; } +MultiButton.pressed { background-color: var(--accent-container-color, #eaddff); } MultiButton.disabled { color: #a5a0ab; background-color: #e0dce4; } MultiLine1 { cn1-derive: Label; color: #1d1b20; font-family: "native:MainBold"; } @@ -387,14 +413,14 @@ DialogContentPane { background-color: transparent; padding: 2mm; margin: 0; } DialogCommandArea { background-color: transparent; padding: 1mm; } FloatingActionButton { - color: #21005d; - background-color: #eaddff; + color: var(--accent-on-container-color, #21005d); + background-color: var(--accent-container-color, #eaddff); padding: 3mm; margin: 3mm; font-family: "native:MainBold"; cn1-background-type: cn1-pill-border; } -FloatingActionButton.pressed { background-color: #d0bcff; } +FloatingActionButton.pressed { background-color: var(--accent-pressed-color, #d0bcff); } Separator { background-color: #cac4d0; padding: 0; margin: 0; } PopupContent { @@ -419,16 +445,21 @@ PopupContent { /* Re-declare cn1-background-type on each dark Button override - the compiler doesn't carry the light declaration's pill border forward into the $Dark entries. */ - Button { color: #381e72; background-color: #d0bcff; cn1-background-type: cn1-pill-border; } - Button.pressed { background-color: #4f378b; color: #eaddff; cn1-background-type: cn1-pill-border; } + Button { color: var(--accent-on-color-dark, #381e72); background-color: var(--accent-color-dark, #d0bcff); cn1-background-type: cn1-pill-border; } + Button.pressed { background-color: var(--accent-pressed-color-dark, #4f378b); color: var(--accent-on-container-color-dark, #eaddff); cn1-background-type: cn1-pill-border; } Button.disabled { color: #5c5967; background-color: #2b2930; cn1-background-type: cn1-pill-border; } - RaisedButton { color: #eaddff; background-color: #4f378b; cn1-background-type: cn1-pill-border; } - RaisedButton.pressed { color: #eaddff; background-color: #6750a4; cn1-background-type: cn1-pill-border; } + RaisedButton { color: var(--accent-on-container-color-dark, #eaddff); background-color: var(--accent-container-color-dark, #4f378b); cn1-background-type: cn1-pill-border; } + /* Dark RaisedButton.pressed bg is the LIGHT --accent-color value + (#6750a4) on purpose - the comment on the light RaisedButton + above explains the "bumped surface" choice that keeps it + visually distinct from regular Button.pressed in dark mode. + Left inline so it doesn't masquerade as an accent-token role. */ + RaisedButton.pressed { color: var(--accent-on-container-color-dark, #eaddff); background-color: #6750a4; cn1-background-type: cn1-pill-border; } RaisedButton.disabled { background-color: #2b2930; color: #5c5967; cn1-background-type: cn1-pill-border; } - FlatButton { color: #d0bcff; background-color: transparent; cn1-background-type: cn1-pill-border; } - FlatButton.pressed { background-color: #4f378b; color: #eaddff; cn1-background-type: cn1-pill-border; } + FlatButton { color: var(--accent-color-dark, #d0bcff); background-color: transparent; cn1-background-type: cn1-pill-border; } + FlatButton.pressed { background-color: var(--accent-pressed-color-dark, #4f378b); color: var(--accent-on-container-color-dark, #eaddff); cn1-background-type: cn1-pill-border; } TextField { color: #e6e0e9; background-color: #211f26; } TextField.pressed { background-color: #49454f; } @@ -439,42 +470,42 @@ PopupContent { TextHint { color: #938f99; } CheckBox { color: #e6e0e9; background-color: transparent; } - CheckBox.selected { color: #d0bcff; } + CheckBox.selected { color: var(--accent-color-dark, #d0bcff); } CheckBox.disabled { color: #5c5967; background-color: #2b2930; } RadioButton { color: #e6e0e9; background-color: transparent; } - RadioButton.selected { color: #d0bcff; } + RadioButton.selected { color: var(--accent-color-dark, #d0bcff); } RadioButton.disabled { color: #5c5967; background-color: #2b2930; } Switch { color: #938f99; background-color: #49454f; } - Switch.selected { color: #381e72; background-color: #d0bcff; } + Switch.selected { color: var(--accent-on-color-dark, #381e72); background-color: var(--accent-color-dark, #d0bcff); } Switch.disabled { color: #5c5967; background-color: #2b2930; } - OnOffSwitch { color: #381e72; background-color: #49454f; } - OnOffSwitch.selected { background-color: #d0bcff; color: #381e72; } + OnOffSwitch { color: var(--accent-on-color-dark, #381e72); background-color: #49454f; } + OnOffSwitch.selected { background-color: var(--accent-color-dark, #d0bcff); color: var(--accent-on-color-dark, #381e72); } Toolbar { background-color: #141218; color: #e6e0e9; } TitleArea { background-color: #141218; color: #e6e0e9; } Title { color: #e6e0e9; background-color: #141218; } MainTitle { color: #e6e0e9; background-color: #141218; } - BackCommand { color: #d0bcff; background-color: transparent; } - TitleCommand { color: #d0bcff; background-color: transparent; } + BackCommand { color: var(--accent-color-dark, #d0bcff); background-color: transparent; } + TitleCommand { color: var(--accent-color-dark, #d0bcff); background-color: transparent; } Tabs { background-color: #141218; } TabsContainer { background-color: #141218; color: #e6e0e9; } Tab { color: #cac4d0; background-color: #141218; } - Tab.selected { color: #d0bcff; background-color: #141218; } - Tab.pressed { color: #eaddff; background-color: #4f378b; } - SelectedTab { color: #d0bcff; } + Tab.selected { color: var(--accent-color-dark, #d0bcff); background-color: #141218; } + Tab.pressed { color: var(--accent-on-container-color-dark, #eaddff); background-color: var(--accent-pressed-color-dark, #4f378b); } + SelectedTab { color: var(--accent-color-dark, #d0bcff); } UnselectedTab { color: #cac4d0; } SideNavigationPanel { background-color: #141218; } SideCommand { color: #e6e0e9; background-color: transparent; } - SideCommand.pressed { background-color: #4f378b; } + SideCommand.pressed { background-color: var(--accent-pressed-color-dark, #4f378b); } List { background-color: #141218; } ListRenderer { color: #e6e0e9; background-color: transparent; } MultiButton { background-color: #141218; color: #e6e0e9; } - MultiButton.pressed { background-color: #4f378b; } + MultiButton.pressed { background-color: var(--accent-pressed-color-dark, #4f378b); } MultiLine1 { color: #e6e0e9; } MultiLine2 { color: #cac4d0; } MultiLine3 { color: #938f99; } @@ -487,8 +518,8 @@ PopupContent { DialogCommandArea { background-color: #211f26; } PopupContent { background-color: #211f26; color: #e6e0e9; } - FloatingActionButton { color: #eaddff; background-color: #4f378b; } - FloatingActionButton.pressed { background-color: #d0bcff; } + FloatingActionButton { color: var(--accent-on-container-color-dark, #eaddff); background-color: var(--accent-container-color-dark, #4f378b); } + FloatingActionButton.pressed { background-color: var(--accent-color-dark, #d0bcff); } Separator { background-color: #49454f; } } diff --git a/native-themes/ios-modern/theme.css b/native-themes/ios-modern/theme.css index 8149446f39..db47f23eb5 100644 --- a/native-themes/ios-modern/theme.css +++ b/native-themes/ios-modern/theme.css @@ -5,8 +5,7 @@ * rounded/pill borders, state selectors via .pressed / .selected / * .disabled. Light and dark palettes live at the bottom of this file * in a prefers-color-scheme block the compiler rewrites into $DarkUIID - * entries. Colors are inlined (not CSS variables) because the rewriter - * operates at string level and doesn't re-scope :root declarations. + * entries. * * Apple system palette reference (light / dark): * accent 007aff / 0a84ff @@ -22,13 +21,31 @@ * separator c6c6c8 / 38383a * success 34c759 / 30d158 * - * Overridable palette: user apps layer their own theme.css or runtime - * UIManager.addThemeProps on top of this theme - see - * PaletteOverrideThemeScreenshotTest for a working example that flips - * the accent to magenta at runtime. + * Accent palette is declared as CSS variables inside #Constants so a + * fork of this file can recolor every accent-bearing component + * (Button, RaisedButton, CheckBox.selected, RadioButton.selected, + * OnOffSwitch, BackCommand, TitleCommand, FloatingActionButton) by + * changing one declaration. The compiler resolves var() at parse + * time, so layering an unrelated theme.css that only redefines the + * variable does NOT retroactively recolor the already-baked styles - + * runtime accent override still goes through UIManager.addThemeProps + * on the specific UIIDs (see PaletteOverrideThemeScreenshotTest). */ #Constants { + /* Accent palette - see header for the override semantics. The + light values are used by every default rule below; the -dark + values feed the corresponding $DarkUIID entries that the dark + @media block compiles into. Keep both halves in sync if a + single accent shifts. */ + --accent-color: #007aff; + --accent-color-dark: #0a84ff; + --accent-pressed-color: #0064d1; + --accent-pressed-color-dark: #64b1ff; + --accent-disabled-color: #b3d4ff; + --accent-disabled-color-dark: #004a99; + --accent-on-color: #ffffff; + includeNativeBool: false; darkModeBool: true; commandBehavior: Native; @@ -129,7 +146,7 @@ SpanLabelText { cn1-derive: Label; background-color: transparent; } degenerate to a circle when width<=height - RoundBorder paints a circle in that case, border-radius always keeps the corner radius. */ Button { - color: #007aff; + color: var(--accent-color, #007aff); padding: 2mm 4mm 2mm 4mm; margin: 1mm; font-family: "native:MainRegular"; @@ -137,18 +154,18 @@ Button { border-radius: 3mm; text-align: center; } -Button.pressed { color: #0064d1; background-color: #e5e5ea; } -Button.disabled { color: #b3d4ff; } +Button.pressed { color: var(--accent-pressed-color, #0064d1); background-color: #e5e5ea; } +Button.disabled { color: var(--accent-disabled-color, #b3d4ff); } RaisedButton { cn1-derive: Button; - color: #ffffff; - background-color: #007aff; + color: var(--accent-on-color, #ffffff); + background-color: var(--accent-color, #007aff); border-radius: 3mm; text-align: center; } -RaisedButton.pressed { color: #ffffff; background-color: #0064d1; } -RaisedButton.disabled { background-color: #b3d4ff; color: #ffffff; } +RaisedButton.pressed { color: var(--accent-on-color, #ffffff); background-color: var(--accent-pressed-color, #0064d1); } +RaisedButton.disabled { background-color: var(--accent-disabled-color, #b3d4ff); color: var(--accent-on-color, #ffffff); } FlatButton { cn1-derive: Button; } @@ -188,7 +205,7 @@ CheckBox { font-family: "native:MainRegular"; icon-gap: 2mm; } -CheckBox.selected { color: #007aff; } +CheckBox.selected { color: var(--accent-color, #007aff); } CheckBox.disabled { color: #c7c7cc; background-color: #e5e5ea; } RadioButton { @@ -199,7 +216,7 @@ RadioButton { font-family: "native:MainRegular"; icon-gap: 2mm; } -RadioButton.selected { color: #007aff; } +RadioButton.selected { color: var(--accent-color, #007aff); } RadioButton.disabled { color: #c7c7cc; background-color: #e5e5ea; } /* Switch's track-left needs to land at the same x as the Label @@ -219,7 +236,7 @@ Switch.disabled { color: #c7c7cc; background-color: #e5e5ea; } OnOffSwitch { cn1-derive: Label; - color: #007aff; + color: var(--accent-color, #007aff); background-color: #e5e5ea; padding: 1mm 2mm 1mm 2mm; cn1-background-type: cn1-pill-border; @@ -249,8 +266,8 @@ Title { } MainTitle { cn1-derive: Title; } -BackCommand { cn1-derive: Button; color: #007aff; padding: 1mm 2mm 1mm 2mm; } -TitleCommand { cn1-derive: Button; color: #007aff; padding: 1mm 2mm 1mm 2mm; } +BackCommand { cn1-derive: Button; color: var(--accent-color, #007aff); padding: 1mm 2mm 1mm 2mm; } +TitleCommand { cn1-derive: Button; color: var(--accent-color, #007aff); padding: 1mm 2mm 1mm 2mm; } /* iOS 26 tab-bar look: the TabsContainer is the visible pill group hugging the bottom edge, with individual tabs rendered as @@ -401,14 +418,14 @@ DialogContentPane { background-color: transparent; padding: 2mm; margin: 0; } DialogCommandArea { background-color: transparent; padding: 1mm; } FloatingActionButton { - color: #ffffff; - background-color: #007aff; + color: var(--accent-on-color, #ffffff); + background-color: var(--accent-color, #007aff); padding: 3mm; margin: 3mm; font-family: "native:MainBold"; cn1-background-type: cn1-pill-border; } -FloatingActionButton.pressed { background-color: #0064d1; } +FloatingActionButton.pressed { background-color: var(--accent-pressed-color, #0064d1); } Separator { background-color: #c6c6c8; padding: 0; margin: 0; } PopupContent { @@ -430,13 +447,13 @@ PopupContent { SpanLabel { color: #ffffff; background-color: transparent; } SpanLabelText { color: #ffffff; background-color: transparent; } - Button { color: #0a84ff; } - Button.pressed { color: #64b1ff; background-color: #3a3a3c; } - Button.disabled { color: #004a99; } + Button { color: var(--accent-color-dark, #0a84ff); } + Button.pressed { color: var(--accent-pressed-color-dark, #64b1ff); background-color: #3a3a3c; } + Button.disabled { color: var(--accent-disabled-color-dark, #004a99); } - RaisedButton { color: #ffffff; background-color: #0a84ff; } - RaisedButton.pressed { background-color: #64b1ff; } - RaisedButton.disabled { background-color: #004a99; } + RaisedButton { color: var(--accent-on-color, #ffffff); background-color: var(--accent-color-dark, #0a84ff); } + RaisedButton.pressed { background-color: var(--accent-pressed-color-dark, #64b1ff); } + RaisedButton.disabled { background-color: var(--accent-disabled-color-dark, #004a99); } TextField { color: #ffffff; background-color: #1c1c1e; } TextField.pressed { background-color: #2c2c2e; } @@ -447,25 +464,25 @@ PopupContent { TextHint { color: #8e8e93; } CheckBox { color: #ffffff; background-color: transparent; } - CheckBox.selected { color: #0a84ff; } + CheckBox.selected { color: var(--accent-color-dark, #0a84ff); } CheckBox.disabled { color: #48484a; background-color: #2c2c2e; } RadioButton { color: #ffffff; background-color: transparent; } - RadioButton.selected { color: #0a84ff; } + RadioButton.selected { color: var(--accent-color-dark, #0a84ff); } RadioButton.disabled { color: #48484a; background-color: #2c2c2e; } Switch { color: #ffffff; background-color: #2c2c2e; } Switch.selected { color: #ffffff; background-color: #30d158; } Switch.disabled { color: #48484a; background-color: #2c2c2e; } - OnOffSwitch { color: #0a84ff; background-color: #2c2c2e; } + OnOffSwitch { color: var(--accent-color-dark, #0a84ff); background-color: #2c2c2e; } OnOffSwitch.selected { background-color: #30d158; color: #ffffff; } Toolbar { background-color: #000000; color: #ffffff; } TitleArea { background-color: #000000; color: #ffffff; } Title { color: #ffffff; background-color: #000000; } MainTitle { color: #ffffff; background-color: #000000; } - BackCommand { color: #0a84ff; } - TitleCommand { color: #0a84ff; } + BackCommand { color: var(--accent-color-dark, #0a84ff); } + TitleCommand { color: var(--accent-color-dark, #0a84ff); } Tabs { background-color: transparent; } TabbedPane { background-color: #1c1c1e; } @@ -512,8 +529,8 @@ PopupContent { DialogCommandArea { background-color: transparent; } PopupContent { background-color: rgba(44,44,46,0.95); color: #ffffff; } - FloatingActionButton { color: #ffffff; background-color: #0a84ff; } - FloatingActionButton.pressed { background-color: #64b1ff; } + FloatingActionButton { color: var(--accent-on-color, #ffffff); background-color: var(--accent-color-dark, #0a84ff); } + FloatingActionButton.pressed { background-color: var(--accent-pressed-color-dark, #64b1ff); } Separator { background-color: #38383a; } }