Expand description
ThemeBuilder: the defineTheme/copyWith analog for composing a
Theme from a baseline plus layered edits.
A theme starts from a baseline — Theme::neutral,
the language-free floor, or any other already-built Theme (a design
system’s own baseline; this is also how a design system assembles that
baseline in the first place) — then layers edits through
Theme::builder
in application order — whichever call runs last for a given group wins
(ThemeExtensions’ own last-write-wins insert for extensions, mirrored
here at the group level):
- baseline — the
Themepassed toTheme::builder. - whole-group swaps —
ThemeBuilder::colors_light/colors_dark/type_scale/shape/elevation/motion/glassreplace a whole group’s value outright. - per-token closure edits — the
map_*counterpart of each group setter above (map_colors_light/map_colors_dark/map_type_scale/map_shape/map_elevation/map_motion/map_glass) hands the current group value to anFnOnce(T) -> T, so a caller writes a Rust struct-update (..) edit instead of restating every field. - extensions —
ThemeBuilder::extensioninserts or replaces a typed extension (seecrate::extensions), same replace-by-TypeIdsemantics asThemeExtensions::insert.
There’s deliberately no separate “whole-value setter vs. closure editor,
same method name” overload: Rust has no method overloading, so each group
gets two distinctly-named methods (the whole-value setter and its map_*
closure counterpart) rather than one method accepting either shape.
ThemeBuilder::build does no validation — tokens are data (v1; see
module docs above), so build() simply returns the accumulated Theme.
§Examples
Baseline + a whole-group color swap + a per-token shape edit:
use frust_theme::{ColorScheme, DesignLanguage, ShapeScale, Theme};
use peniko::Color;
const BRAND: Color = Color::from_rgb8(0xFF, 0x6A, 0x00);
let theme = Theme::builder(Theme::neutral())
.colors_dark(ColorScheme {
primary: BRAND,
..ColorScheme::neutral_dark()
}) // whole-group swap
.map_shape(|s| ShapeScale { medium: 8.0, ..s }) // per-token closure edit
.design_language(DesignLanguage::Custom("acme"))
.build();
assert_eq!(theme.dark.primary, BRAND);
assert_eq!(theme.shape.medium, 8.0);
// Every other shape token is untouched (struct-update `..` above).
assert_eq!(theme.shape.large, ShapeScale::neutral().large);Attaching a typed extension (see crate::extensions):
use frust_theme::Theme;
#[derive(Debug, Clone, PartialEq)]
struct BrandTokens {
logo_glow: bool,
}
let theme = Theme::builder(Theme::neutral())
.extension(BrandTokens { logo_glow: true })
.build();
assert_eq!(theme.extension::<BrandTokens>(), Some(&BrandTokens { logo_glow: true }));Structs§
- Theme
Builder - A layered builder over a
Themebaseline — see the module docs for the full precedence order. Every method takes/returnsSelfby value so calls chain (Theme::builder(base).colors_dark(..).map_shape(..).build()).