Skip to main content

sequoia_style/
button_style.rs

1use sequoia_core::Color;
2
3#[derive(Clone, Copy, Debug, Default, PartialEq)]
4pub struct ButtonStyle {
5    pub background: Option<Color>,
6
7    pub hover: Option<Color>,
8
9    pub pressed: Option<Color>,
10
11    pub disabled: Option<Color>,
12
13    pub text: Option<Color>,
14
15    pub text_disabled: Option<Color>,
16
17    pub font_size: Option<f32>,
18
19    pub font_weight: Option<u16>,
20
21    pub radius: Option<f32>,
22
23    pub padding: Option<(f32, f32)>,
24
25    pub border_width: Option<f32>,
26
27    pub border_color: Option<Color>,
28}
29
30impl ButtonStyle {
31    pub const fn new() -> Self {
32        Self {
33            background: None,
34            hover: None,
35            pressed: None,
36            disabled: None,
37            text: None,
38            text_disabled: None,
39            font_size: None,
40            font_weight: None,
41            radius: None,
42            padding: None,
43            border_width: None,
44            border_color: None,
45        }
46    }
47
48    pub const PRIMARY: Self = Self {
49        background: Some(Color::rgb8(0x35, 0x6F, 0xD1)),
50        hover: Some(Color::rgb8(0x45, 0x82, 0xE0)),
51        pressed: Some(Color::rgb8(0x2C, 0x5A, 0xA8)),
52        disabled: None,
53        text: Some(Color::WHITE),
54        text_disabled: None,
55        font_size: None,
56        font_weight: Some(600),
57        radius: None,
58        padding: None,
59        border_width: None,
60        border_color: None,
61    };
62
63    pub const SECONDARY: Self = Self {
64        background: Some(Color::rgb8(0x33, 0x36, 0x3D)),
65        hover: Some(Color::rgb8(0x40, 0x44, 0x4E)),
66        pressed: Some(Color::rgb8(0x2A, 0x2D, 0x33)),
67        disabled: None,
68        text: Some(Color::rgb8(0xE6, 0xE6, 0xE6)),
69        text_disabled: None,
70        font_size: None,
71        font_weight: None,
72        radius: None,
73        padding: None,
74        border_width: None,
75        border_color: None,
76    };
77
78    pub const DANGER: Self = Self {
79        background: Some(Color::rgb8(0xB3, 0x26, 0x1E)),
80        hover: Some(Color::rgb8(0xD3, 0x2F, 0x2F)),
81        pressed: Some(Color::rgb8(0x8A, 0x1A, 0x14)),
82        disabled: None,
83        text: Some(Color::WHITE),
84        text_disabled: None,
85        font_size: None,
86        font_weight: Some(600),
87        radius: None,
88        padding: None,
89        border_width: None,
90        border_color: None,
91    };
92
93    pub const GHOST: Self = Self {
94        background: Some(Color::TRANSPARENT),
95        hover: Some(Color::rgb8(0x33, 0x36, 0x3D)),
96        pressed: Some(Color::rgb8(0x2A, 0x2D, 0x33)),
97        disabled: None,
98        text: Some(Color::rgb8(0x35, 0x6F, 0xD1)),
99        text_disabled: None,
100        font_size: None,
101        font_weight: None,
102        radius: None,
103        padding: None,
104        border_width: None,
105        border_color: None,
106    };
107
108    pub const DARK_FILLED: Self = Self {
109        background: Some(Color::hex(0x1F1F1F)),
110        hover: Some(Color::hex(0x2B2B2B)),
111        pressed: Some(Color::hex(0x141414)),
112        disabled: None,
113        text: Some(Color::hex(0xEDEDED)),
114        text_disabled: None,
115        font_size: Some(12.0),
116        font_weight: Some(500),
117        radius: None,
118        padding: Some((12.0, 6.0)),
119        border_width: None,
120        border_color: None,
121    };
122
123    pub const OUTLINE_ROSE: Self = Self {
124        background: Some(Color::TRANSPARENT),
125        hover: Some(Color::hexa(0xB0868626)),
126        pressed: Some(Color::hexa(0xB086864D)),
127        disabled: None,
128        text: Some(Color::hex(0xB08686)),
129        text_disabled: None,
130        font_size: Some(12.0),
131        font_weight: Some(600),
132        radius: None,
133        padding: Some((12.0, 6.0)),
134        border_width: Some(1.0),
135        border_color: Some(Color::hex(0xB08686)),
136    };
137
138    pub const fn primary() -> Self {
139        Self::PRIMARY
140    }
141
142    pub const fn secondary() -> Self {
143        Self::SECONDARY
144    }
145
146    pub const fn danger() -> Self {
147        Self::DANGER
148    }
149
150    pub const fn ghost() -> Self {
151        Self::GHOST
152    }
153
154    pub const fn dark_filled() -> Self {
155        Self::DARK_FILLED
156    }
157
158    pub const fn outline_rose() -> Self {
159        Self::OUTLINE_ROSE
160    }
161
162    pub const fn background(mut self, color: Color) -> Self {
163        self.background = Some(color);
164        self
165    }
166
167    pub const fn hover(mut self, color: Color) -> Self {
168        self.hover = Some(color);
169        self
170    }
171
172    pub const fn pressed(mut self, color: Color) -> Self {
173        self.pressed = Some(color);
174        self
175    }
176
177    pub const fn disabled(mut self, color: Color) -> Self {
178        self.disabled = Some(color);
179        self
180    }
181
182    pub const fn text(mut self, color: Color) -> Self {
183        self.text = Some(color);
184        self
185    }
186
187    pub const fn text_disabled(mut self, color: Color) -> Self {
188        self.text_disabled = Some(color);
189        self
190    }
191
192    pub const fn font_size(mut self, size: f32) -> Self {
193        self.font_size = Some(size);
194        self
195    }
196
197    pub const fn font_weight(mut self, weight: u16) -> Self {
198        self.font_weight = Some(weight);
199        self
200    }
201
202    pub const fn radius(mut self, radius: f32) -> Self {
203        self.radius = Some(radius);
204        self
205    }
206
207    pub const fn padding_xy(mut self, pad_x: f32, pad_y: f32) -> Self {
208        self.padding = Some((pad_x, pad_y));
209        self
210    }
211
212    pub const fn border_width(mut self, width: f32) -> Self {
213        self.border_width = Some(width);
214        self
215    }
216
217    pub const fn border_color(mut self, color: Color) -> Self {
218        self.border_color = Some(color);
219        self
220    }
221
222    pub const fn border(mut self, width: f32, color: Color) -> Self {
223        self.border_width = Some(width);
224        self.border_color = Some(color);
225        self
226    }
227}
228
229#[cfg(test)]
230mod tests {
231    use super::*;
232
233    #[test]
234    fn presets_differ_per_state() {
235        assert_ne!(ButtonStyle::PRIMARY.background, ButtonStyle::PRIMARY.hover);
236        assert_ne!(ButtonStyle::PRIMARY.hover, ButtonStyle::PRIMARY.pressed);
237        assert_ne!(
238            ButtonStyle::DANGER.background,
239            ButtonStyle::PRIMARY.background
240        );
241    }
242
243    #[test]
244    fn const_composition_in_const_context() {
245        const CUSTOM: ButtonStyle = ButtonStyle::PRIMARY.radius(12.0);
246        assert_eq!(CUSTOM.radius, Some(12.0));
247        assert_eq!(CUSTOM.background, ButtonStyle::PRIMARY.background);
248    }
249
250    #[test]
251    fn struct_update_syntax_keeps_base() {
252        const CUSTOM: ButtonStyle = ButtonStyle {
253            background: Some(Color::hex(0xB3261E)),
254            ..ButtonStyle::PRIMARY
255        };
256        assert_eq!(CUSTOM.background, Some(Color::hex(0xB3261E)));
257        assert_eq!(CUSTOM.hover, ButtonStyle::PRIMARY.hover);
258    }
259
260    #[test]
261    fn border_builders_set_both_fields() {
262        const S: ButtonStyle = ButtonStyle::new().border(1.0, Color::WHITE);
263        assert_eq!(S.border_width, Some(1.0));
264        assert_eq!(S.border_color, Some(Color::WHITE));
265    }
266
267    #[test]
268    fn new_variants_share_metrics_but_differ_in_look() {
269        assert_eq!(
270            ButtonStyle::DARK_FILLED.padding,
271            ButtonStyle::OUTLINE_ROSE.padding
272        );
273        assert_eq!(ButtonStyle::DARK_FILLED.padding, Some((12.0, 6.0)));
274        assert_eq!(ButtonStyle::DARK_FILLED.font_size, Some(12.0));
275        assert_eq!(ButtonStyle::OUTLINE_ROSE.font_size, Some(12.0));
276
277        assert_eq!(ButtonStyle::DARK_FILLED.font_weight, Some(500));
278        assert_eq!(ButtonStyle::OUTLINE_ROSE.font_weight, Some(600));
279        assert_eq!(
280            ButtonStyle::DARK_FILLED.background,
281            Some(Color::hex(0x1F1F1F))
282        );
283        assert_eq!(ButtonStyle::DARK_FILLED.text, Some(Color::hex(0xEDEDED)));
284        assert_eq!(ButtonStyle::DARK_FILLED.border_width, None);
285        assert_eq!(
286            ButtonStyle::OUTLINE_ROSE.background,
287            Some(Color::TRANSPARENT)
288        );
289        assert_eq!(ButtonStyle::OUTLINE_ROSE.text, Some(Color::hex(0xB08686)));
290        assert_eq!(ButtonStyle::OUTLINE_ROSE.border_width, Some(1.0));
291        assert_eq!(
292            ButtonStyle::OUTLINE_ROSE.border_color,
293            Some(Color::hex(0xB08686))
294        );
295        assert_ne!(
296            ButtonStyle::DARK_FILLED.hover,
297            ButtonStyle::DARK_FILLED.background
298        );
299        assert_ne!(
300            ButtonStyle::OUTLINE_ROSE.hover,
301            ButtonStyle::OUTLINE_ROSE.background
302        );
303    }
304}