Skip to main content

Module builder

Module builder 

Source
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):

  1. baseline — the Theme passed to Theme::builder.
  2. whole-group swaps — ThemeBuilder::colors_light/colors_dark/ type_scale/shape/ elevation/motion/ glass replace a whole group’s value outright.
  3. 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 an FnOnce(T) -> T, so a caller writes a Rust struct-update (..) edit instead of restating every field.
  4. extensions — ThemeBuilder::extension inserts or replaces a typed extension (see crate::extensions), same replace-by-TypeId semantics as ThemeExtensions::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§

ThemeBuilder
A layered builder over a Theme baseline — see the module docs for the full precedence order. Every method takes/returns Self by value so calls chain (Theme::builder(base).colors_dark(..).map_shape(..).build()).