Skip to main content

urushi/theme/
component_theme.rs

1//! Canonical presentations and shared role styles for reusable components.
2
3use crate::{
4    BlockStyle, Border, ListPresentation, ScrollbarPresentation, TablePresentation, TextStyle,
5    TreePresentation,
6};
7
8use super::{ComponentRole, ListRole, ScrollbarRole, SemanticTokens, TableRole, TreeRole};
9
10const COMPONENT_ROLE_COUNT: usize = 15;
11
12/// Theme-derived presentations and shared role styles for common components.
13#[derive(Debug, Clone, PartialEq)]
14pub struct ComponentTheme {
15    text_styles: [TextStyle; COMPONENT_ROLE_COUNT],
16    panel: BlockStyle,
17    panel_focused: BlockStyle,
18    list: ListPresentation,
19    tree: TreePresentation,
20    table: TablePresentation,
21    scrollbar: ScrollbarPresentation,
22}
23
24impl ComponentTheme {
25    /// Builds the canonical component theme from semantic tokens.
26    pub fn from_tokens(tokens: &SemanticTokens) -> Self {
27        let panel = BlockStyle::new()
28            .foreground(tokens.text)
29            .background(tokens.surface)
30            .border(Border::ROUNDED)
31            .border_foreground(tokens.border)
32            .padding((0, 1));
33
34        let text_styles = [
35            TextStyle::new().foreground(tokens.text),
36            TextStyle::new().foreground(tokens.text_muted).dim(),
37            TextStyle::new().foreground(tokens.accent).bold(),
38            TextStyle::new().foreground(tokens.success),
39            TextStyle::new().foreground(tokens.error).bold(),
40            TextStyle::new().foreground(tokens.accent).bold(),
41            TextStyle::new().foreground(tokens.text),
42            TextStyle::new().foreground(tokens.text_muted).italic(),
43            TextStyle::new().foreground(tokens.accent).bold(),
44            TextStyle::new().foreground(tokens.text),
45            TextStyle::new()
46                .foreground(tokens.accent_text)
47                .background(tokens.accent)
48                .bold(),
49            TextStyle::new()
50                .foreground(tokens.text)
51                .background(tokens.surface),
52            TextStyle::new()
53                .foreground(tokens.accent_text)
54                .background(tokens.accent)
55                .bold(),
56            TextStyle::new().foreground(tokens.text_muted).dim(),
57            TextStyle::new().foreground(tokens.error),
58        ];
59        Self {
60            text_styles,
61            panel: panel.clone(),
62            panel_focused: panel.border_foreground(tokens.accent),
63            list: ListPresentation::new(
64                TextStyle::new().foreground(tokens.text),
65                TextStyle::new().foreground(tokens.text_muted),
66            ),
67            tree: TreePresentation::new(
68                TextStyle::new().foreground(tokens.text).bold(),
69                TextStyle::new().foreground(tokens.text),
70                TextStyle::new().foreground(tokens.text_muted),
71            ),
72            table: TablePresentation::new(
73                BlockStyle::new().foreground(tokens.text).bold(),
74                BlockStyle::new().foreground(tokens.text),
75                TextStyle::new().foreground(tokens.border),
76            ),
77            scrollbar: ScrollbarPresentation::new(
78                TextStyle::new().foreground(tokens.accent),
79                TextStyle::new().foreground(tokens.border),
80            ),
81        }
82    }
83
84    pub fn get_text_style(&self, role: ComponentRole) -> &TextStyle {
85        &self.text_styles[role.index()]
86    }
87
88    /// Returns the framed-surface block style.
89    pub const fn get_panel(&self) -> &BlockStyle {
90        &self.panel
91    }
92
93    /// Returns the focused framed-surface block style.
94    pub const fn get_panel_focused(&self) -> &BlockStyle {
95        &self.panel_focused
96    }
97
98    /// Returns the style assigned to one List-specific role.
99    pub fn get_list_style(&self, role: ListRole) -> &TextStyle {
100        self.list.get_style(role)
101    }
102
103    /// Returns the style assigned to one Tree-specific role.
104    pub fn get_tree_style(&self, role: TreeRole) -> &TextStyle {
105        self.tree.get_style(role)
106    }
107
108    /// Returns the block style assigned to one Table-specific cell role.
109    pub fn get_table_style(&self, role: TableRole) -> &BlockStyle {
110        self.table.get_style(role)
111    }
112
113    /// Returns the style assigned to one Scrollbar-specific role.
114    pub fn get_scrollbar_style(&self, role: ScrollbarRole) -> &TextStyle {
115        self.scrollbar.get_style(role)
116    }
117
118    /// Returns the default presentation policy for lists.
119    pub fn list(&self) -> &ListPresentation {
120        &self.list
121    }
122
123    /// Returns the default presentation policy for trees.
124    pub fn tree(&self) -> &TreePresentation {
125        &self.tree
126    }
127
128    /// Returns the default presentation policy for tables.
129    pub fn table(&self) -> &TablePresentation {
130        &self.table
131    }
132
133    /// Returns the default presentation policy for scrollbars.
134    pub fn scrollbar(&self) -> &ScrollbarPresentation {
135        &self.scrollbar
136    }
137
138    #[must_use]
139    pub fn text_style(mut self, role: ComponentRole, style: TextStyle) -> Self {
140        self.text_styles[role.index()] = style;
141        self
142    }
143
144    /// Replaces the framed-surface block style.
145    #[must_use]
146    pub fn panel(mut self, panel: BlockStyle) -> Self {
147        self.panel = panel;
148        self
149    }
150
151    /// Replaces the focused framed-surface block style.
152    #[must_use]
153    pub fn panel_focused(mut self, panel: BlockStyle) -> Self {
154        self.panel_focused = panel;
155        self
156    }
157
158    /// Replaces the style assigned to one List-specific role.
159    #[must_use]
160    pub fn list_style(mut self, role: ListRole, style: TextStyle) -> Self {
161        self.list = self.list.style(role, style);
162        self
163    }
164
165    /// Replaces the style assigned to one Tree-specific role.
166    #[must_use]
167    pub fn tree_style(mut self, role: TreeRole, style: TextStyle) -> Self {
168        self.tree = self.tree.style(role, style);
169        self
170    }
171
172    /// Replaces the block style assigned to one Table-specific cell role.
173    #[must_use]
174    pub fn table_style(mut self, role: TableRole, style: BlockStyle) -> Self {
175        self.table = self.table.style(role, style);
176        self
177    }
178
179    /// Replaces the style assigned to one Scrollbar-specific role.
180    #[must_use]
181    pub fn scrollbar_style(mut self, role: ScrollbarRole, style: TextStyle) -> Self {
182        self.scrollbar = self.scrollbar.style(role, style);
183        self
184    }
185}