Skip to main content

material_colors/
theme.rs

1use crate::{
2    blend::harmonize,
3    color::Argb,
4    dynamic_color::{DynamicScheme, Variant},
5    palette::{CorePalette, Palette, TonalPalette},
6    scheme::Scheme,
7};
8#[cfg(not(feature = "std"))]
9use alloc::{string::String, vec::Vec};
10#[cfg(feature = "serde")]
11use serde::Serialize;
12#[cfg(feature = "std")]
13use std::{string::String, vec::Vec};
14
15/// Custom color used to pair with a theme
16#[derive(Debug)]
17#[cfg_attr(feature = "serde", derive(Serialize))]
18pub struct CustomColor {
19    pub value: Argb,
20    pub name: String,
21    pub blend: bool,
22}
23
24/// Color group
25#[derive(Debug)]
26#[cfg_attr(feature = "serde", derive(Serialize))]
27pub struct ColorGroup {
28    pub color: Argb,
29    pub on_color: Argb,
30    pub color_container: Argb,
31    pub on_color_container: Argb,
32}
33
34/// Custom Color Group
35#[derive(Debug)]
36#[cfg_attr(feature = "serde", derive(Serialize))]
37pub struct CustomColorGroup {
38    pub color: CustomColor,
39    pub value: Argb,
40    pub light: ColorGroup,
41    pub dark: ColorGroup,
42}
43
44impl CustomColorGroup {
45    /// Generate custom color group from source and target color
46    ///
47    /// @link <https://m3.material.io/styles/color/the-color-system/color-roles>
48    fn new(source: Argb, color: CustomColor) -> Self {
49        let mut value = color.value;
50
51        if color.blend {
52            value = harmonize(value, source);
53        }
54
55        let palette = CorePalette::of(value);
56        let tones = palette.primary;
57
58        Self {
59            color,
60            value,
61            light: ColorGroup {
62                color: tones.tone(40),
63                on_color: tones.tone(100),
64                color_container: tones.tone(90),
65                on_color_container: tones.tone(10),
66            },
67            dark: ColorGroup {
68                color: tones.tone(80),
69                on_color: tones.tone(20),
70                color_container: tones.tone(30),
71                on_color_container: tones.tone(90),
72            },
73        }
74    }
75}
76
77#[derive(Debug)]
78#[cfg_attr(feature = "serde", derive(Serialize))]
79pub struct Schemes {
80    pub light: Scheme,
81    pub dark: Scheme,
82}
83
84#[derive(Debug)]
85#[cfg_attr(feature = "serde", derive(Serialize))]
86pub struct Palettes {
87    pub primary: TonalPalette,
88    pub secondary: TonalPalette,
89    pub tertiary: TonalPalette,
90    pub neutral: TonalPalette,
91    pub neutral_variant: TonalPalette,
92    pub error: TonalPalette,
93}
94
95pub struct ThemeBuilder {
96    source: Argb,
97    variant: Variant,
98    color_match: bool,
99    primary: Option<Argb>,
100    secondary: Option<Argb>,
101    tertiary: Option<Argb>,
102    error: Option<Argb>,
103    neutral: Option<Argb>,
104    neutral_variant: Option<Argb>,
105    custom_colors: Vec<CustomColor>,
106}
107
108impl ThemeBuilder {
109    /// Creates a theme builder with a custom source color.
110    #[must_use]
111    pub const fn with_source(source: Argb) -> Self {
112        Self {
113            source,
114            variant: Variant::TonalSpot,
115            color_match: false,
116            primary: None,
117            secondary: None,
118            tertiary: None,
119            error: None,
120            neutral: None,
121            neutral_variant: None,
122            custom_colors: Vec::new(),
123        }
124    }
125
126    /// Sets the theme variant.
127    #[must_use]
128    pub const fn variant(mut self, variant: Variant) -> Self {
129        self.variant = variant;
130
131        self
132    }
133
134    /// Sets the primary color of the theme.
135    #[must_use]
136    pub const fn primary(mut self, color: Argb) -> Self {
137        self.primary = Some(color);
138
139        self
140    }
141
142    /// Sets the secondary color of the theme.
143    #[must_use]
144    pub const fn secondary(mut self, color: Argb) -> Self {
145        self.secondary = Some(color);
146
147        self
148    }
149
150    /// Sets the tertiary color of the theme.
151    #[must_use]
152    pub const fn tertiary(mut self, color: Argb) -> Self {
153        self.tertiary = Some(color);
154
155        self
156    }
157
158    /// Sets the error color of the theme.
159    #[must_use]
160    pub const fn error(mut self, color: Argb) -> Self {
161        self.error = Some(color);
162
163        self
164    }
165
166    /// Sets the neutral color, used for background and surfaces.
167    #[must_use]
168    pub const fn neutral(mut self, color: Argb) -> Self {
169        self.neutral = Some(color);
170
171        self
172    }
173
174    /// Sets the neutral variant color, used for for medium emphasis and variants.
175    #[must_use]
176    pub const fn neutral_variant(mut self, color: Argb) -> Self {
177        self.neutral_variant = Some(color);
178
179        self
180    }
181
182    /// Sets the custom colors, used as complementary tones.
183    ///
184    /// Custom colors are also known as extended colors.
185    #[must_use]
186    pub fn custom_colors(mut self, custom_colors: Vec<CustomColor>) -> Self {
187        self.custom_colors = custom_colors;
188
189        self
190    }
191
192    #[must_use]
193    pub const fn color_match(mut self, enabled: bool) -> Self {
194        self.color_match = enabled;
195
196        self
197    }
198
199    #[must_use]
200    pub fn build(mut self) -> Theme {
201        let palette = CorePalette::of(self.source);
202
203        if self.color_match {
204            self.variant = Variant::Fidelity;
205        }
206
207        let mut light = DynamicScheme::by_variant(self.source, &self.variant, false, None);
208        let mut dark = DynamicScheme::by_variant(self.source, &self.variant, true, None);
209
210        if let Some(color) = self.primary {
211            let palette = TonalPalette::by_variant(&color.into(), &self.variant, &Palette::Primary);
212
213            light.primary_palette = palette;
214            dark.primary_palette = palette;
215        }
216
217        if let Some(color) = self.secondary {
218            let palette =
219                TonalPalette::by_variant(&color.into(), &self.variant, &Palette::Secondary);
220
221            light.secondary_palette = palette;
222            dark.secondary_palette = palette;
223        }
224
225        if let Some(color) = self.tertiary {
226            let palette =
227                TonalPalette::by_variant(&color.into(), &self.variant, &Palette::Tertiary);
228
229            light.tertiary_palette = palette;
230            dark.tertiary_palette = palette;
231        }
232
233        if let Some(color) = self.error {
234            let palette = TonalPalette::by_variant(&color.into(), &self.variant, &Palette::Error);
235
236            light.error_palette = palette;
237            dark.error_palette = palette;
238        }
239
240        if let Some(color) = self.neutral {
241            let palette = TonalPalette::by_variant(&color.into(), &self.variant, &Palette::Neutral);
242
243            light.neutral_palette = palette;
244            dark.neutral_palette = palette;
245        }
246
247        if let Some(color) = self.neutral_variant {
248            let palette =
249                TonalPalette::by_variant(&color.into(), &self.variant, &Palette::NeutralVariant);
250
251            light.neutral_variant_palette = palette;
252            dark.neutral_variant_palette = palette;
253        }
254
255        Theme {
256            source: self.source,
257            schemes: Schemes {
258                light: light.into(),
259                dark: dark.into(),
260            },
261            palettes: Palettes {
262                primary: palette.primary,
263                secondary: palette.secondary,
264                tertiary: palette.tertiary,
265                neutral: palette.neutral,
266                neutral_variant: palette.neutral_variant,
267                error: palette.error,
268            },
269            custom_colors: self
270                .custom_colors
271                .into_iter()
272                .map(|color| CustomColorGroup::new(self.source, color))
273                .collect(),
274        }
275    }
276}
277
278#[derive(Debug)]
279#[cfg_attr(feature = "serde", derive(Serialize))]
280pub struct Theme {
281    pub source: Argb,
282    pub schemes: Schemes,
283    pub palettes: Palettes,
284    pub custom_colors: Vec<CustomColorGroup>,
285}