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