1use repose_core::prelude::{Color, Modifier, StateColors};
19use repose_core::{AlignItems, Dp, JustifyContent, PaddingValues};
20
21pub const FONT: &str = "Fredoka";
24
25pub const MATERIAL_SYMBOLS: &str = "Material Symbols Outlined";
27
28pub const ICON_BUTTON: f32 = 46.0;
30
31pub const ICON_GLYPH: f32 = 26.0;
33
34#[derive(Clone, Copy, Debug, PartialEq)]
36pub struct Tokens {
37 pub ink: Color,
39 pub cream: Color,
41 pub gold: Color,
43 pub gold_dark: Color,
45 pub coral: Color,
47 pub sky: Color,
49 pub sky_dark: Color,
51 pub cyan: Color,
53 pub pink: Color,
55 pub green: Color,
57 pub scrim: Color,
59 pub button_shadow: Color,
61 pub touch_bg: Color,
63 pub touch_ring: Color,
65 pub touch_shadow: Color,
67 pub touch_knob: Color,
69 pub touch_knob_dark: Color,
71 pub touch_center: Color,
73}
74
75impl Tokens {
76 pub const KICKBACK: Tokens = Tokens {
78 ink: Color(0x2B, 0x1A, 0x14, 0xFF),
79 cream: Color(0xFF, 0xF3, 0xDC, 0xFF),
80 gold: Color(0xFF, 0xE0, 0x66, 0xFF),
81 gold_dark: Color(0xD9, 0xA5, 0x1E, 0xFF),
82 coral: Color(0xFF, 0x5F, 0xA2, 0xFF),
83 sky: Color(0xF2, 0xA6, 0x5A, 0xFF),
84 sky_dark: Color(0xE8, 0x89, 0x4A, 0xFF),
85 cyan: Color(0x7F, 0xF0, 0xE4, 0xFF),
86 pink: Color(0xFF, 0x8A, 0xC8, 0xFF),
87 green: Color(0x8B, 0xD9, 0x6E, 0xFF),
88 scrim: Color(0x2B, 0x1A, 0x14, 0x77),
89 button_shadow: Color(0x2B, 0x1A, 0x14, 0x40),
90 touch_bg: Color(0xFF, 0xF3, 0xDC, 0x99),
91 touch_ring: Color(0x2B, 0x1A, 0x14, 0xFF),
92 touch_shadow: Color(0x2B, 0x1A, 0x14, 0x2E),
93 touch_knob: Color(0xFF, 0xFF, 0xFF, 0xFF),
94 touch_knob_dark: Color(0xE8, 0xC4, 0x9E, 0xFF),
95 touch_center: Color(0x2B, 0x1A, 0x14, 0xFF),
96 };
97
98 pub const TEMPLATE: Tokens = Tokens {
101 ink: Color(0x1E, 0x2A, 0x4A, 0xFF),
102 cream: Color(0xFF, 0xF8, 0xEA, 0xFF),
103 gold: Color(0xFF, 0xC9, 0x3C, 0xFF),
104 gold_dark: Color(0xE0, 0x9B, 0x1F, 0xFF),
105 coral: Color(0xF4, 0x71, 0x6A, 0xFF),
106 sky: Color(0x7F, 0xC9, 0xF2, 0xFF),
107 sky_dark: Color(0x5F, 0xAC, 0xDE, 0xFF),
108 cyan: Color(0x46, 0xC8, 0xF5, 0xFF),
109 pink: Color(0xFF, 0x9E, 0xB1, 0xFF),
110 green: Color(0x7B, 0xD8, 0x8F, 0xFF),
111 scrim: Color(0x1E, 0x2A, 0x4A, 0x66),
112 button_shadow: Color(0x1E, 0x2A, 0x4A, 0x33),
113 touch_bg: Color(0xFF, 0xF8, 0xEA, 0x99),
114 touch_ring: Color(0x1E, 0x2A, 0x4A, 0xFF),
115 touch_shadow: Color(0x1E, 0x2A, 0x4A, 0x22),
116 touch_knob: Color(0xFF, 0xFF, 0xFF, 0xFF),
117 touch_knob_dark: Color(0xC9, 0xD8, 0xEE, 0xFF),
118 touch_center: Color(0x1E, 0x2A, 0x4A, 0xFF),
119 };
120}
121
122impl Default for Tokens {
123 fn default() -> Self {
124 Self::KICKBACK
125 }
126}
127
128pub fn mix(a: Color, b: Color, t: f32) -> Color {
131 let t = t.clamp(0.0, 1.0);
132 let blend = |from: u8, to: u8| -> u8 {
133 (from as f32 + (to as f32 - from as f32) * t) as u8
134 };
135 Color(
136 blend(a.0, b.0),
137 blend(a.1, b.1),
138 blend(a.2, b.2),
139 blend(a.3, b.3),
140 )
141}
142
143pub fn lighten(color: Color, t: f32) -> Color {
145 mix(color, Color(0xFF, 0xFF, 0xFF, 0xFF), t)
146}
147
148pub fn darken(color: Color, t: f32) -> Color {
150 mix(color, Color(0x00, 0x00, 0x00, 0xFF), t)
151}
152
153pub fn state_colors(face: Color, ink: Color) -> StateColors {
158 StateColors {
159 default: face,
160 hovered: lighten(face, 0.08),
161 focused: lighten(face, 0.04),
162 pressed: mix(face, ink, 0.16),
163 disabled: mix(face, ink, 0.30),
164 dragged: mix(face, ink, 0.16),
165 }
166}
167
168pub fn hand_cut(radius: f32, seed: u32) -> [Dp; 4] {
174 let wobble = |k: u32| -> Dp {
175 let bits = seed.wrapping_mul(0x9E37_79B9 ^ k.wrapping_mul(0x85EB_CA6B));
176 let frac = ((bits >> 16) & 0xFF) as f32 / 255.0 - 0.5;
177 Dp(radius * (1.0 + frac * 0.34))
178 };
179 [wobble(1), wobble(2), wobble(3), wobble(4)]
180}
181
182pub fn chunky(
189 tokens: &Tokens,
190 face: Color,
191 pad_x: f32,
192 pad_y: f32,
193 radius: f32,
194 scale: f32,
195) -> Modifier {
196 Modifier::new()
197 .state_colors(state_colors(face, tokens.ink))
198 .border(Dp(3.0 * scale), tokens.ink, Dp(radius * scale))
199 .padding_values(PaddingValues {
200 left: Dp(pad_x * scale),
201 right: Dp(pad_x * scale),
202 top: Dp(pad_y * scale),
203 bottom: Dp(pad_y * scale),
204 })
205 .shadow_with_color(Dp(0.0), Dp(5.0 * scale), tokens.button_shadow)
206 .align_items(AlignItems::CENTER)
207 .justify_content(JustifyContent::CENTER)
208 .focusable(true)
209}
210
211pub fn chip(tokens: &Tokens, face: Color, scale: f32) -> Modifier {
213 Modifier::new()
214 .background(face)
215 .border(Dp(2.5 * scale), tokens.ink, Dp(14.0 * scale))
216 .padding_values(PaddingValues {
217 left: Dp(14.0 * scale),
218 right: Dp(14.0 * scale),
219 top: Dp(6.0 * scale),
220 bottom: Dp(6.0 * scale),
221 })
222}
223
224pub fn outline_for(size: f32) -> f32 {
227 (size / 3.0).max(2.5)
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233
234 const A: Color = Color(0x10, 0x20, 0x30, 0x40);
235 const B: Color = Color(0x80, 0x90, 0xA0, 0xFF);
236
237 #[test]
238 fn mix_endpoints_are_the_inputs() {
239 assert_eq!(mix(A, B, 0.0), A);
240 assert_eq!(mix(A, B, 1.0), B);
241 assert_eq!(mix(A, B, -3.0), A, "clamped below");
242 assert_eq!(mix(A, B, 9.0), B, "clamped above");
243 }
244
245 #[test]
246 fn mix_midpoint_is_the_average() {
247 assert_eq!(mix(A, B, 0.5), Color(0x48, 0x58, 0x68, 0x9F));
248 }
249
250 #[test]
251 fn lighten_and_darken_reach_white_and_black() {
252 assert_eq!(lighten(A, 0.0), A);
253 assert_eq!(lighten(A, 1.0), Color(0xFF, 0xFF, 0xFF, 0xFF));
254 assert_eq!(darken(A, 1.0), Color(0x00, 0x00, 0x00, 0xFF));
255 }
256
257 #[test]
258 fn state_colors_keep_the_face_as_default_and_shade_the_rest() {
259 let colors = state_colors(A, Color(0x00, 0x00, 0x00, 0xFF));
260 assert_eq!(colors.default, A);
261 assert_eq!(colors.hovered, lighten(A, 0.08));
262 assert_eq!(colors.focused, lighten(A, 0.04));
263 assert_eq!(colors.pressed, mix(A, Color(0x00, 0x00, 0x00, 0xFF), 0.16));
264 assert_eq!(colors.disabled, mix(A, Color(0x00, 0x00, 0x00, 0xFF), 0.30));
265 }
266
267 #[test]
268 fn hand_cut_stays_within_the_documented_band() {
269 for seed in [0u32, 1, 7, 0xDEAD_BEEF, 42] {
270 for corner in hand_cut(16.0, seed) {
271 assert!(
272 (16.0 * 0.66..=16.0 * 1.34).contains(&corner.0),
273 "seed {seed} produced {corner:?}"
274 );
275 }
276 }
277 }
278
279 #[test]
280 fn hand_cut_is_stable_for_a_seed() {
281 assert_eq!(hand_cut(12.0, 99), hand_cut(12.0, 99));
282 assert_ne!(hand_cut(12.0, 98), hand_cut(12.0, 99));
283 }
284
285 #[test]
286 fn the_default_tokens_are_the_palette_pair() {
287 assert_eq!(Tokens::default(), Tokens::KICKBACK);
288 assert_ne!(Tokens::KICKBACK.ink, Tokens::TEMPLATE.ink);
289 }
290}