Skip to main content

launcher/
theme.rs

1//! The web pages' design system, mapped onto egui.
2//!
3//! The static pages (leaderboard, account page, control panel) share one
4//! stylesheet, `php/assets/ssbz.css`. Build.rs parses its custom
5//! properties into the two `Sheet` constants below, so the launcher and
6//! the web pages cannot drift apart: change the stylesheet and both
7//! surfaces follow. This module installs those tokens into egui, for both
8//! the dark and the light theme, and hands them to the few hand-painted
9//! widgets that need raw colors.
10
11use eframe::egui;
12
13include!(concat!(env!("OUT_DIR"), "/ssbz_tokens.rs"));
14
15/// The sheet for the context's active theme.
16#[must_use]
17pub fn palette(ctx: &egui::Context) -> &'static Sheet {
18    match ctx.theme() {
19        egui::Theme::Dark => &DARK,
20        _ => &LIGHT,
21    }
22}
23
24/// One step from the card tone toward the line tone: the hover fill the
25/// stylesheet's brightness filters approximate on the web.
26#[must_use]
27pub fn hover_fill(p: &Sheet) -> egui::Color32 {
28    let mix = |a: u8, b: u8| u8::try_from((u32::from(a) + u32::from(b)) / 2).unwrap_or(255);
29    egui::Color32::from_rgb(
30        mix(p.card.r(), p.line.r()),
31        mix(p.card.g(), p.line.g()),
32        mix(p.card.b(), p.line.b()),
33    )
34}
35
36fn corner_radius() -> egui::CornerRadius {
37    egui::CornerRadius::same(4)
38}
39
40fn apply(p: &Sheet, style: &mut egui::Style) {
41    let hover = hover_fill(p);
42    let v = &mut style.visuals;
43
44    v.panel_fill = p.bg;
45    v.window_fill = p.card;
46    v.extreme_bg_color = p.code;
47    v.faint_bg_color = p.zebra;
48    v.hyperlink_color = p.chip_ink;
49    v.error_fg_color = p.err;
50    v.warn_fg_color = p.err;
51    v.window_stroke = egui::Stroke::new(1.0, p.line);
52    v.selection.bg_fill = p.accent;
53    v.selection.stroke = egui::Stroke::new(1.0, p.accent_ink);
54
55    v.widgets.noninteractive.bg_fill = p.bg;
56    v.widgets.noninteractive.weak_bg_fill = p.bg;
57    v.widgets.noninteractive.fg_stroke = egui::Stroke::new(1.0, p.muted);
58    v.widgets.noninteractive.bg_stroke = egui::Stroke::new(1.0, p.line);
59
60    v.widgets.inactive.bg_fill = p.card;
61    v.widgets.inactive.weak_bg_fill = p.card;
62    v.widgets.inactive.fg_stroke = egui::Stroke::new(1.0, p.text);
63    v.widgets.inactive.bg_stroke = egui::Stroke::new(1.0, p.line);
64
65    v.widgets.hovered.bg_fill = hover;
66    v.widgets.hovered.weak_bg_fill = hover;
67    v.widgets.hovered.fg_stroke = egui::Stroke::new(1.0, p.text);
68    v.widgets.hovered.bg_stroke = egui::Stroke::new(1.0, p.line);
69
70    v.widgets.active.bg_fill = p.accent;
71    v.widgets.active.weak_bg_fill = p.accent;
72    v.widgets.active.fg_stroke = egui::Stroke::new(1.0, p.accent_ink);
73    v.widgets.active.bg_stroke = egui::Stroke::new(1.0, p.accent);
74
75    for widget in [
76        &mut v.widgets.noninteractive,
77        &mut v.widgets.inactive,
78        &mut v.widgets.hovered,
79        &mut v.widgets.active,
80    ] {
81        widget.corner_radius = corner_radius();
82    }
83    v.window_corner_radius = corner_radius();
84    v.menu_corner_radius = corner_radius();
85
86    // The stylesheet's type scale, close to egui's default proportions:
87    // a 15 px body, headings that stay section-sized instead of shouty.
88    let text_styles = &mut style.text_styles;
89    text_styles.insert(egui::TextStyle::Body, egui::FontId::proportional(14.0));
90    text_styles.insert(egui::TextStyle::Button, egui::FontId::proportional(14.0));
91    text_styles.insert(egui::TextStyle::Heading, egui::FontId::proportional(15.0));
92    text_styles.insert(egui::TextStyle::Small, egui::FontId::proportional(11.0));
93    text_styles.insert(egui::TextStyle::Monospace, egui::FontId::monospace(13.0));
94}
95
96/// Install the web design system into both of egui's themes, so the
97/// launcher follows the system preference just like the pages follow
98/// `prefers-color-scheme`.
99pub fn install(ctx: &egui::Context) {
100    for theme in [egui::Theme::Dark, egui::Theme::Light] {
101        let sheet = match theme {
102            egui::Theme::Dark => &DARK,
103            _ => &LIGHT,
104        };
105        let mut style = (*ctx.style_of(theme)).clone();
106        apply(sheet, &mut style);
107        ctx.set_style_of(theme, style);
108    }
109}