Skip to main content

lattice_dashboard/
theme.rs

1//! The `dashboard.*` theme element vocabulary (DB.3).
2//!
3//! `dashboard-mode` OWNS these elements — it registers them (and their
4//! defaults) against the [`ThemeRegistry`] in `on_activate`
5//! ([[feedback_mode_owns_its_surface]]). Registration is idempotent by name,
6//! so re-activation is safe and always resolves to the same interned ids.
7//! Themes override any of them through the normal element-override scopes; the
8//! brand colours ship as `Literal` defaults so the mark reads correctly out of
9//! the box under any theme, and the text roles reference palette keys so they
10//! reskin naturally.
11//!
12//! The ids are consumed by the branding virtual-row provider (DB.4), which
13//! resolves them into cell colours.
14
15use lattice_theme::{
16    Color, ColorRef, ElementId, ElementName, ElementOwner, StyleSpec, ThemeRegistry,
17};
18
19pub const ELEM_LOGO: &str = "dashboard.logo";
20pub const ELEM_CURSOR: &str = "dashboard.cursor";
21pub const ELEM_TITLE: &str = "dashboard.title";
22pub const ELEM_TAGLINE: &str = "dashboard.tagline";
23pub const ELEM_SECTION: &str = "dashboard.section";
24pub const ELEM_KEY: &str = "dashboard.key";
25pub const ELEM_LINK: &str = "dashboard.link";
26pub const ELEM_BODY: &str = "dashboard.body";
27pub const ELEM_VERSION: &str = "dashboard.version";
28
29/// Brand blue (`assets/lattice-mark.svg`).
30pub const BRAND_BLUE: Color = Color::Rgb(0x1f, 0x6f, 0xeb);
31/// Brand amber — the cursor bar inside the mark.
32pub const BRAND_AMBER: Color = Color::Rgb(0xf5, 0x9e, 0x0b);
33
34/// Interned ids for the `dashboard.*` elements, held in a `Copy` struct so
35/// the branding provider (DB.4) reads `resolved.get(id)` by field.
36#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37pub struct DashboardElementIds {
38    pub logo: ElementId,
39    pub cursor: ElementId,
40    pub title: ElementId,
41    pub tagline: ElementId,
42    pub section: ElementId,
43    pub key: ElementId,
44    pub link: ElementId,
45    pub body: ElementId,
46    pub version: ElementId,
47}
48
49/// Register the `dashboard.*` elements + defaults under `owner`. Idempotent by
50/// name; returns the interned ids.
51pub fn register_dashboard_theme_elements(
52    reg: &dyn ThemeRegistry,
53    owner: ElementOwner,
54) -> DashboardElementIds {
55    let elem = |name: &str, default: StyleSpec, doc: &'static str| {
56        reg.register(
57            ElementName::from(name.to_string()),
58            owner.clone(),
59            default,
60            doc,
61        )
62    };
63
64    DashboardElementIds {
65        // Brand colours ship as literals so the mark is correct under any
66        // theme; still overridable.
67        logo: elem(
68            ELEM_LOGO,
69            StyleSpec::new().fg(BRAND_BLUE),
70            "Dashboard brand mark (the interlocking L bracket).",
71        ),
72        cursor: elem(
73            ELEM_CURSOR,
74            StyleSpec::new().fg(BRAND_AMBER),
75            "Dashboard mark cursor bar.",
76        ),
77        title: elem(
78            ELEM_TITLE,
79            StyleSpec::new().fg(BRAND_BLUE),
80            "Dashboard \"Lattice\" wordmark.",
81        ),
82        // Text roles reference palette keys so they reskin with the theme.
83        tagline: elem(
84            ELEM_TAGLINE,
85            StyleSpec::new().fg(ColorRef::Palette("subtext".into())),
86            "Dashboard tagline / muted hint text.",
87        ),
88        section: elem(
89            ELEM_SECTION,
90            StyleSpec::new().fg(ColorRef::Palette("blue".into())),
91            "Dashboard section heading.",
92        ),
93        key: elem(
94            ELEM_KEY,
95            StyleSpec::new().fg(ColorRef::Palette("green".into())),
96            "Dashboard key cap (e.g. `:`, `<leader>`).",
97        ),
98        link: elem(
99            ELEM_LINK,
100            StyleSpec::new().fg(ColorRef::Palette("blue".into())),
101            "Dashboard followable link.",
102        ),
103        // Body inherits the default foreground.
104        body: elem(ELEM_BODY, StyleSpec::new(), "Dashboard body text."),
105        // Version inherits the default foreground (regular text color)
106        // but can be overridden through the theme.
107        version: elem(ELEM_VERSION, StyleSpec::new(), "Dashboard version string."),
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114    use lattice_theme::{InMemoryThemeRegistry, default_palette};
115
116    fn mode_owner() -> ElementOwner {
117        ElementOwner::Mode("dashboard-mode".into())
118    }
119
120    #[test]
121    fn registers_all_nine_under_mode_owner() {
122        let reg = InMemoryThemeRegistry::new(default_palette());
123        register_dashboard_theme_elements(&reg, mode_owner());
124        for name in [
125            ELEM_LOGO,
126            ELEM_CURSOR,
127            ELEM_TITLE,
128            ELEM_TAGLINE,
129            ELEM_SECTION,
130            ELEM_KEY,
131            ELEM_LINK,
132            ELEM_BODY,
133            ELEM_VERSION,
134        ] {
135            let info = reg
136                .describe(&ElementName::from(name.to_string()))
137                .unwrap_or_else(|| panic!("{name} should be registered"));
138            assert_eq!(info.owner, mode_owner(), "{name} owner");
139        }
140    }
141
142    #[test]
143    fn brand_colours_are_literal_defaults() {
144        let reg = InMemoryThemeRegistry::new(default_palette());
145        register_dashboard_theme_elements(&reg, mode_owner());
146        for name in [ELEM_LOGO, ELEM_TITLE] {
147            let info = reg.describe(&ElementName::from(name.to_string())).unwrap();
148            assert_eq!(info.default.fg, Some(ColorRef::Literal(BRAND_BLUE)));
149        }
150        let cursor = reg
151            .describe(&ElementName::from(ELEM_CURSOR.to_string()))
152            .unwrap();
153        assert_eq!(cursor.default.fg, Some(ColorRef::Literal(BRAND_AMBER)));
154    }
155
156    #[test]
157    fn text_roles_reference_palette_keys() {
158        let reg = InMemoryThemeRegistry::new(default_palette());
159        register_dashboard_theme_elements(&reg, mode_owner());
160        let tagline = reg
161            .describe(&ElementName::from(ELEM_TAGLINE.to_string()))
162            .unwrap();
163        assert_eq!(
164            tagline.default.fg,
165            Some(ColorRef::Palette("subtext".into()))
166        );
167    }
168
169    #[test]
170    fn registration_is_idempotent() {
171        let reg = InMemoryThemeRegistry::new(default_palette());
172        let a = register_dashboard_theme_elements(&reg, mode_owner());
173        let b = register_dashboard_theme_elements(&reg, mode_owner());
174        assert_eq!(a, b, "re-registering must return the same interned ids");
175    }
176}