Skip to main content

sequoia_style/
lib.rs

1#![forbid(unsafe_code)]
2
3mod avatar_style;
4mod badge_style;
5mod box_style;
6mod button_style;
7mod checkbox_style;
8mod chip_style;
9mod context_menu_style;
10mod dialog_style;
11mod divider_style;
12mod drag_value_style;
13mod drawer_style;
14mod effect;
15mod filter;
16mod floating_menu_style;
17mod focus_ring_style;
18mod form_style;
19mod kbd_style;
20mod modal_style;
21mod radio_style;
22mod resize_handle_style;
23mod sheet_style;
24mod slider_style;
25mod split_panel_style;
26mod switch_style;
27mod table_tree_style;
28mod text_area_style;
29mod text_field_style;
30mod toast_style;
31mod tokens;
32mod tooltip_style;
33
34pub use avatar_style::AvatarStyle;
35pub use badge_style::BadgeStyle;
36pub use box_style::{BorderSide, BoxStyle, ShadowSpec};
37pub use button_style::ButtonStyle;
38pub use checkbox_style::CheckboxStyle;
39pub use chip_style::ChipStyle;
40pub use context_menu_style::ContextMenuStyle;
41pub use dialog_style::{DIALOG_DANGER, DialogStyle};
42pub use divider_style::DividerStyle;
43pub use drag_value_style::DragValueStyle;
44pub use drawer_style::DrawerStyle;
45pub use effect::Backdrop;
46pub use filter::{
47    Effect, Filter, GradientAlphas, Mask, VERTICAL, backdrop_blur, blur, gradient, gradient_angled,
48    gradient_mask,
49};
50pub use floating_menu_style::FloatingMenuStyle;
51pub use focus_ring_style::FocusRingStyle;
52pub use form_style::{FORM_ERROR, FormStyle};
53pub use kbd_style::KbdStyle;
54pub use modal_style::ModalStyle;
55pub use radio_style::RadioStyle;
56pub use resize_handle_style::ResizeHandleStyle;
57pub use sheet_style::SheetStyle;
58pub use slider_style::SliderStyle;
59pub use split_panel_style::SplitPanelStyle;
60pub use switch_style::SwitchStyle;
61pub use table_tree_style::{TableStyle, TreeStyle};
62pub use text_area_style::TextAreaStyle;
63pub use text_field_style::TextFieldStyle;
64pub use toast_style::ToastStyle;
65pub use tokens::TokensError;
66pub use tooltip_style::TooltipStyle;
67
68use sequoia_core::Color;
69
70#[derive(Clone, Copy, Debug, PartialEq)]
71#[non_exhaustive]
72pub struct Style {
73    pub background: Option<Color>,
74
75    pub radius: f32,
76
77    pub color: Color,
78
79    pub font_size: f32,
80
81    pub line_height_factor: f32,
82}
83
84impl Default for Style {
85    fn default() -> Self {
86        Self {
87            background: None,
88            radius: 0.0,
89            color: Color::rgb8(0xE6, 0xE6, 0xE6),
90            font_size: 14.0,
91            line_height_factor: 1.35,
92        }
93    }
94}
95
96#[derive(Clone, Copy, Debug, PartialEq)]
97#[non_exhaustive]
98pub struct Theme {
99    pub background: Color,
100
101    pub surface: Color,
102
103    pub border: Color,
104
105    pub text: Color,
106
107    pub text_secondary: Color,
108
109    pub accent: Color,
110
111    pub accent_hover: Color,
112
113    pub accent_pressed: Color,
114
115    pub disabled: Color,
116
117    pub radius: f32,
118
119    pub font_size: f32,
120}
121
122impl Theme {
123    pub const fn dark() -> Self {
124        Self {
125            background: Color::rgb8(0x19, 0x19, 0x19),
126            surface: Color::rgb8(0x26, 0x26, 0x26),
127            border: Color::rgb8(0x45, 0x45, 0x45),
128            text: Color::rgb8(0xD4, 0xD4, 0xD4),
129            text_secondary: Color::rgb8(0x9a, 0x9a, 0x9a),
130            accent: Color::rgb8(0x35, 0xC2, 0xF1),
131            accent_hover: Color::rgb8(0x5a, 0xD3, 0xF5),
132            accent_pressed: Color::rgb8(0x1e, 0x9a, 0xCD),
133            disabled: Color::rgb8(0x6B, 0x6B, 0x6B),
134            radius: 3.0,
135            font_size: 12.0,
136        }
137    }
138
139    pub const fn dcc_dark_panel() -> Self {
140        Self::dark()
141    }
142
143    pub const fn light() -> Self {
144        Self {
145            background: Color::rgb8(0xF5, 0xF5, 0xF5),
146            surface: Color::rgb8(0xFF, 0xFF, 0xFF),
147            border: Color::rgb8(0xD4, 0xD4, 0xD4),
148            text: Color::rgb8(0x1A, 0x1A, 0x1A),
149            text_secondary: Color::rgb8(0x5A, 0x5A, 0x5A),
150            accent: Color::rgb8(0x0D, 0x6E, 0xD8),
151            accent_hover: Color::rgb8(0x2B, 0x88, 0xE5),
152            accent_pressed: Color::rgb8(0x0B, 0x5C, 0xB5),
153            disabled: Color::rgb8(0xA8, 0xA8, 0xA8),
154            radius: 3.0,
155            font_size: 12.0,
156        }
157    }
158}
159
160pub const ACCENT_ACTIVE: Color = Color::rgb8(0xE8, 0x84, 0x2C);
161
162#[derive(Clone, Copy, Debug, Default, PartialEq)]
163#[non_exhaustive]
164pub struct VisualState {
165    pub hovered: bool,
166
167    pub pressed: bool,
168
169    pub focused: bool,
170
171    pub disabled: bool,
172
173    pub selected: bool,
174}
175
176pub fn resolve_accent(theme: &Theme, state: VisualState) -> Color {
177    if state.disabled {
178        theme.disabled
179    } else if state.pressed {
180        theme.accent_pressed
181    } else if state.hovered {
182        theme.accent_hover
183    } else {
184        theme.accent
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use super::*;
191
192    #[test]
193    fn theme_dark_is_sane() {
194        let t = Theme::dark();
195        assert_ne!(t.background, t.surface);
196        assert_ne!(t.accent, t.accent_hover);
197    }
198
199    #[test]
200    fn theme_light_is_sane_and_distinct() {
201        let l = Theme::light();
202        let d = Theme::dark();
203        assert_ne!(l.background, d.background);
204        assert_ne!(l.text, d.text);
205        assert_ne!(l.background, l.surface);
206        assert_ne!(l.accent, l.accent_hover);
207    }
208
209    #[test]
210    fn resolve_accent_by_state() {
211        let t = Theme::dark();
212        assert_eq!(resolve_accent(&t, VisualState::default()), t.accent);
213        assert_eq!(
214            resolve_accent(
215                &t,
216                VisualState {
217                    hovered: true,
218                    ..Default::default()
219                }
220            ),
221            t.accent_hover
222        );
223        assert_eq!(
224            resolve_accent(
225                &t,
226                VisualState {
227                    pressed: true,
228                    ..Default::default()
229                }
230            ),
231            t.accent_pressed
232        );
233        assert_eq!(
234            resolve_accent(
235                &t,
236                VisualState {
237                    disabled: true,
238                    ..Default::default()
239                }
240            ),
241            t.disabled
242        );
243    }
244}