1use fission_ir::op::{BoxShadow, Color, Stroke};
17use serde::{Deserialize, Serialize};
18
19#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
28pub struct ColorTokens {
29 pub primary: Color,
30 pub on_primary: Color,
31 pub secondary: Color,
32 pub on_secondary: Color,
33 pub surface: Color,
34 pub on_surface: Color,
35 pub background: Color,
36 pub on_background: Color,
37 pub error: Color,
38 pub on_error: Color,
39 pub border: Color,
40 pub text_primary: Color,
41 pub text_secondary: Color,
42}
43
44impl Default for ColorTokens {
45 fn default() -> Self {
46 Self {
47 primary: Color { r: 103, g: 85, b: 143, a: 255 }, on_primary: Color::WHITE,
49 secondary: Color { r: 98, g: 91, b: 113, a: 255 },
50 on_secondary: Color::WHITE,
51 surface: Color { r: 255, g: 251, b: 254, a: 255 },
52 on_surface: Color { r: 28, g: 27, b: 31, a: 255 },
53 background: Color { r: 255, g: 251, b: 254, a: 255 },
54 on_background: Color { r: 28, g: 27, b: 31, a: 255 },
55 error: Color { r: 179, g: 38, b: 30, a: 255 },
56 on_error: Color::WHITE,
57 border: Color { r: 188, g: 188, b: 188, a: 255 },
58 text_primary: Color { r: 28, g: 27, b: 31, a: 255 },
59 text_secondary: Color { r: 86, g: 86, b: 86, a: 255 },
60 }
61 }
62}
63
64impl ColorTokens {
65 pub fn dark() -> Self {
66 Self {
67 primary: Color { r: 187, g: 134, b: 252, a: 255 },
68 on_primary: Color { r: 0, g: 0, b: 0, a: 255 },
69 secondary: Color { r: 3, g: 218, b: 197, a: 255 },
70 on_secondary: Color { r: 0, g: 0, b: 0, a: 255 },
71 surface: Color { r: 30, g: 30, b: 30, a: 255 },
72 on_surface: Color { r: 230, g: 230, b: 230, a: 255 },
73 background: Color { r: 18, g: 18, b: 18, a: 255 },
74 on_background: Color { r: 230, g: 230, b: 230, a: 255 },
75 error: Color { r: 207, g: 102, b: 121, a: 255 },
76 on_error: Color { r: 0, g: 0, b: 0, a: 255 },
77 border: Color { r: 60, g: 60, b: 60, a: 255 },
78 text_primary: Color { r: 230, g: 230, b: 230, a: 255 },
79 text_secondary: Color { r: 160, g: 160, b: 160, a: 255 },
80 }
81 }
82}
83
84#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
88pub struct SpacingTokens {
89 pub none: f32, pub xs: f32, pub s: f32, pub m: f32, pub l: f32, pub xl: f32, }
96
97impl Default for SpacingTokens {
98 fn default() -> Self {
99 Self {
100 none: 0.0,
101 xs: 4.0,
102 s: 8.0,
103 m: 16.0,
104 l: 24.0,
105 xl: 32.0,
106 }
107 }
108}
109
110#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
115pub struct TypographyTokens {
116 pub label_large_size: f32,
117 pub body_medium_size: f32,
118 pub body_large_size: f32,
119 pub heading_size: f32,
120}
121
122impl Default for TypographyTokens {
123 fn default() -> Self {
124 Self {
125 label_large_size: 15.0,
126 body_medium_size: 15.0,
127 body_large_size: 17.0,
128 heading_size: 28.0,
129 }
130 }
131}
132
133#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
137pub struct RadiusTokens {
138 pub small: f32,
139 pub medium: f32,
140 pub large: f32,
141 pub full: f32,
142}
143
144impl Default for RadiusTokens {
145 fn default() -> Self {
146 Self {
147 small: 4.0,
148 medium: 8.0,
149 large: 12.0,
150 full: 9999.0,
151 }
152 }
153}
154
155#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
160pub struct ElevationTokens {
161 pub level0: Option<BoxShadow>,
162 pub level1: Option<BoxShadow>,
163 pub level2: Option<BoxShadow>,
164 pub level3: Option<BoxShadow>,
165 pub level4: Option<BoxShadow>,
166 pub level5: Option<BoxShadow>,
167}
168
169impl Default for ElevationTokens {
170 fn default() -> Self {
171 let black_alpha = |a| Color { r: 0, g: 0, b: 0, a };
172 Self {
173 level0: None,
174 level1: Some(BoxShadow { color: black_alpha(40), offset: (0.0, 1.0), blur_radius: 2.0 }),
175 level2: Some(BoxShadow { color: black_alpha(60), offset: (0.0, 2.0), blur_radius: 4.0 }),
176 level3: Some(BoxShadow { color: black_alpha(60), offset: (0.0, 4.0), blur_radius: 8.0 }),
177 level4: None,
178 level5: None,
179 }
180 }
181}
182
183#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
189pub struct Tokens {
190 pub colors: ColorTokens,
191 pub spacing: SpacingTokens,
192 pub typography: TypographyTokens,
193 pub radii: RadiusTokens,
194 pub elevations: ElevationTokens,
195}
196
197impl Tokens {
198 pub fn dark() -> Self {
199 Self {
200 colors: ColorTokens::dark(),
201 spacing: SpacingTokens::default(),
202 typography: TypographyTokens::default(),
203 radii: RadiusTokens::default(),
204 elevations: ElevationTokens::default(),
205 }
206 }
207}
208
209#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
216pub struct ButtonTheme {
217 pub height: f32,
218 pub padding_horizontal: f32,
219 pub padding_vertical: f32,
220 pub radius: f32,
221 pub text_size: f32,
222 pub elevation_rest: Option<BoxShadow>,
223 pub elevation_hover: Option<BoxShadow>,
224 pub elevation_pressed: Option<BoxShadow>,
225 pub focus_stroke: Option<Stroke>,
226}
227
228impl ButtonTheme {
229 pub fn from_tokens(tokens: &Tokens) -> Self {
230 Self {
231 height: 42.0,
232 padding_horizontal: tokens.spacing.m,
233 padding_vertical: tokens.spacing.s,
234 radius: tokens.radii.full,
235 text_size: tokens.typography.label_large_size,
236 elevation_rest: tokens.elevations.level1,
237 elevation_hover: tokens.elevations.level2,
238 elevation_pressed: tokens.elevations.level0,
239 focus_stroke: Some(Stroke {
240 color: tokens.colors.on_background,
241 width: 2.0,
242 }),
243 }
244 }
245}
246
247#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
252pub struct TextInputTheme {
253 pub height: f32,
254 pub padding_h: f32,
255 pub radius: f32,
256 pub font_size: f32,
257 pub border_color: Color,
258 pub border_width: f32,
259 pub focus_color: Color,
260 pub text_color: Color,
261 pub placeholder_color: Color,
262}
263
264impl TextInputTheme {
265 pub fn from_tokens(tokens: &Tokens) -> Self {
266 Self {
267 height: 40.0,
268 padding_h: tokens.spacing.m,
269 radius: tokens.radii.small,
270 font_size: tokens.typography.body_large_size,
271 border_color: tokens.colors.border,
272 border_width: 1.0,
273 focus_color: tokens.colors.primary,
274 text_color: tokens.colors.text_primary,
275 placeholder_color: tokens.colors.text_secondary,
276 }
277 }
278}
279
280#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
282pub struct CalendarTheme {
283 pub bg_color: Color,
284 pub border_color: Color,
285 pub radius: f32,
286 pub selected_bg: Color,
287 pub selected_text: Color,
288 pub today_outline: Color,
289}
290
291impl CalendarTheme {
292 pub fn from_tokens(tokens: &Tokens) -> Self {
293 Self {
294 bg_color: tokens.colors.surface,
295 border_color: tokens.colors.border,
296 radius: tokens.radii.medium,
297 selected_bg: tokens.colors.primary,
298 selected_text: tokens.colors.on_primary,
299 today_outline: tokens.colors.secondary,
300 }
301 }
302}
303
304#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
306pub struct PaginationTheme {
307 pub spacing: f32,
308 pub active_bg: Color,
309 pub active_text: Color,
310}
311
312impl PaginationTheme {
313 pub fn from_tokens(tokens: &Tokens) -> Self {
314 Self {
315 spacing: tokens.spacing.s,
316 active_bg: tokens.colors.primary,
317 active_text: tokens.colors.on_primary,
318 }
319 }
320}
321
322#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
324pub struct TimelineTheme {
325 pub dot_size: f32,
326 pub line_width: f32,
327 pub dot_color: Color,
328 pub line_color: Color,
329}
330
331impl TimelineTheme {
332 pub fn from_tokens(tokens: &Tokens) -> Self {
333 Self {
334 dot_size: 12.0,
335 line_width: 2.0,
336 dot_color: tokens.colors.primary,
337 line_color: tokens.colors.border,
338 }
339 }
340}
341
342#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
344pub struct SegmentedControlTheme {
345 pub bg_color: Color,
346 pub border_color: Color,
347 pub radius: f32,
348 pub active_bg: Color,
349 pub active_text: Color,
350}
351
352impl SegmentedControlTheme {
353 pub fn from_tokens(tokens: &Tokens) -> Self {
354 Self {
355 bg_color: tokens.colors.surface,
356 border_color: tokens.colors.border,
357 radius: tokens.radii.full,
358 active_bg: tokens.colors.primary,
359 active_text: tokens.colors.on_primary,
360 }
361 }
362}
363
364#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
366pub struct AlertTheme {
367 pub info_bg: Color,
368 pub warning_bg: Color,
369 pub error_bg: Color,
370 pub success_bg: Color,
371 pub radius: f32,
372}
373
374impl AlertTheme {
375 pub fn from_tokens(tokens: &Tokens) -> Self {
376 Self {
377 info_bg: Color { r: 230, g: 242, b: 255, a: 255 },
378 warning_bg: Color { r: 255, g: 244, b: 229, a: 255 },
379 error_bg: tokens.colors.error.with_alpha(30),
380 success_bg: Color { r: 237, g: 247, b: 237, a: 255 },
381 radius: tokens.radii.medium,
382 }
383 }
384}
385
386#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
388pub struct BadgeTheme {
389 pub radius: f32,
390 pub font_size: f32,
391}
392
393impl BadgeTheme {
394 pub fn from_tokens(tokens: &Tokens) -> Self {
395 Self {
396 radius: tokens.radii.full,
397 font_size: 10.0,
398 }
399 }
400}
401
402#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
404pub struct TabsTheme {
405 pub active_color: Color,
406 pub inactive_color: Color,
407 pub indicator_height: f32,
408 pub background: Color,
409 pub divider_color: Color,
410}
411
412impl TabsTheme {
413 pub fn from_tokens(tokens: &Tokens) -> Self {
414 Self {
415 active_color: tokens.colors.primary,
416 inactive_color: tokens.colors.text_secondary,
417 indicator_height: 3.0,
418 background: tokens.colors.background,
419 divider_color: tokens.colors.border.with_alpha(120),
420 }
421 }
422}
423
424#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
428pub struct ModalTheme {
429 pub bg_color: Color,
430 pub radius: f32,
431 pub shadow: Option<BoxShadow>,
432 pub max_width: f32,
433}
434
435impl ModalTheme {
436 pub fn from_tokens(tokens: &Tokens) -> Self {
437 Self {
438 bg_color: tokens.colors.surface,
439 radius: tokens.radii.large,
440 shadow: tokens.elevations.level3,
441 max_width: 600.0,
442 }
443 }
444}
445
446#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
448pub struct TreeViewTheme {
449 pub indent: f32,
450 pub selected_bg: Color,
451 pub hover_bg: Color,
452}
453
454impl TreeViewTheme {
455 pub fn from_tokens(tokens: &Tokens) -> Self {
456 Self {
457 indent: 16.0,
458 selected_bg: tokens.colors.primary.with_alpha(52),
459 hover_bg: tokens.colors.surface,
460 }
461 }
462}
463
464#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
466pub struct ProgressTheme {
467 pub height: f32,
468 pub track_color: Color,
469 pub bar_color: Color,
470}
471
472impl ProgressTheme {
473 pub fn from_tokens(tokens: &Tokens) -> Self {
474 Self {
475 height: 8.0,
476 track_color: tokens.colors.border,
477 bar_color: tokens.colors.primary,
478 }
479 }
480}
481
482#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
484pub struct TooltipTheme {
485 pub bg_color: Color,
486 pub text_color: Color,
487 pub radius: f32,
488 pub font_size: f32,
489}
490
491impl TooltipTheme {
492 pub fn from_tokens(tokens: &Tokens) -> Self {
493 Self {
494 bg_color: Color { r: 50, g: 50, b: 50, a: 255 },
495 text_color: Color::WHITE,
496 radius: tokens.radii.small,
497 font_size: 12.0,
498 }
499 }
500}
501
502#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
507pub struct ComponentTheme {
508 pub button: ButtonTheme,
509 pub text_input: TextInputTheme,
510 pub calendar: CalendarTheme,
511 pub pagination: PaginationTheme,
512 pub timeline: TimelineTheme,
513 pub segmented_control: SegmentedControlTheme,
514 pub alert: AlertTheme,
515 pub badge: BadgeTheme,
516 pub tabs: TabsTheme,
517 pub modal: ModalTheme,
518 pub tree_view: TreeViewTheme,
519 pub progress: ProgressTheme,
520 pub tooltip: TooltipTheme,
521}
522
523impl ComponentTheme {
524 pub fn from_tokens(tokens: &Tokens) -> Self {
525 Self {
526 button: ButtonTheme::from_tokens(tokens),
527 text_input: TextInputTheme::from_tokens(tokens),
528 calendar: CalendarTheme::from_tokens(tokens),
529 pagination: PaginationTheme::from_tokens(tokens),
530 timeline: TimelineTheme::from_tokens(tokens),
531 segmented_control: SegmentedControlTheme::from_tokens(tokens),
532 alert: AlertTheme::from_tokens(tokens),
533 badge: BadgeTheme::from_tokens(tokens),
534 tabs: TabsTheme::from_tokens(tokens),
535 modal: ModalTheme::from_tokens(tokens),
536 tree_view: TreeViewTheme::from_tokens(tokens),
537 progress: ProgressTheme::from_tokens(tokens),
538 tooltip: TooltipTheme::from_tokens(tokens),
539 }
540 }
541}
542
543#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
549pub struct Theme {
550 pub tokens: Tokens,
551 pub components: ComponentTheme,
552}
553
554impl Default for Theme {
555 fn default() -> Self {
556 let tokens = Tokens::default();
557 let components = ComponentTheme::from_tokens(&tokens);
558 Self { tokens, components }
559 }
560}
561
562impl Theme {
563 pub fn dark() -> Self {
564 let tokens = Tokens::dark();
565 let components = ComponentTheme::from_tokens(&tokens);
566 Self { tokens, components }
567 }
568}
569
570pub mod fonts {
574 pub const NOTO_SANS_REGULAR_TTF: &[u8] = include_bytes!("../fonts/Noto_Sans/static/NotoSans-Regular.ttf");
575 pub const INTER_24PT_REGULAR_TTF: &[u8] = include_bytes!("../fonts/Inter/static/Inter_24pt-Regular.ttf");
576 #[inline]
577 pub fn default_font_bytes() -> &'static [u8] { NOTO_SANS_REGULAR_TTF }
578}