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}