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 OUTLINE: Self = Self {
110 background: Some(Color::TRANSPARENT),
111 hover: Some(Color::hexa(0x64748B1F)),
112 pressed: Some(Color::hexa(0x64748B33)),
113 disabled: None,
114 text: Some(Color::hex(0x64748B)),
115 text_disabled: None,
116 font_size: None,
117 font_weight: Some(500),
118 radius: None,
119 padding: None,
120 border_width: Some(1.0),
121 border_color: Some(Color::hexa(0x64748B66)),
122 };
123
124 pub const DARK_FILLED: Self = Self {
125 background: Some(Color::hex(0x1F1F1F)),
126 hover: Some(Color::hex(0x2B2B2B)),
127 pressed: Some(Color::hex(0x141414)),
128 disabled: None,
129 text: Some(Color::hex(0xEDEDED)),
130 text_disabled: None,
131 font_size: Some(12.0),
132 font_weight: Some(500),
133 radius: None,
134 padding: Some((12.0, 6.0)),
135 border_width: None,
136 border_color: None,
137 };
138
139 pub const OUTLINE_ROSE: Self = Self {
140 background: Some(Color::TRANSPARENT),
141 hover: Some(Color::hexa(0xB0868626)),
142 pressed: Some(Color::hexa(0xB086864D)),
143 disabled: None,
144 text: Some(Color::hex(0xB08686)),
145 text_disabled: None,
146 font_size: Some(12.0),
147 font_weight: Some(600),
148 radius: None,
149 padding: Some((12.0, 6.0)),
150 border_width: Some(1.0),
151 border_color: Some(Color::hex(0xB08686)),
152 };
153
154 pub const fn primary() -> Self {
155 Self::PRIMARY
156 }
157
158 pub const fn secondary() -> Self {
159 Self::SECONDARY
160 }
161
162 pub const fn danger() -> Self {
163 Self::DANGER
164 }
165
166 pub const fn ghost() -> Self {
167 Self::GHOST
168 }
169
170 pub const fn outline() -> Self {
171 Self::OUTLINE
172 }
173
174 pub const fn dark_filled() -> Self {
175 Self::DARK_FILLED
176 }
177
178 pub const fn outline_rose() -> Self {
179 Self::OUTLINE_ROSE
180 }
181
182 pub const fn background(mut self, color: Color) -> Self {
183 self.background = Some(color);
184 self
185 }
186
187 pub const fn hover(mut self, color: Color) -> Self {
188 self.hover = Some(color);
189 self
190 }
191
192 pub const fn pressed(mut self, color: Color) -> Self {
193 self.pressed = Some(color);
194 self
195 }
196
197 pub const fn disabled(mut self, color: Color) -> Self {
198 self.disabled = Some(color);
199 self
200 }
201
202 pub const fn text(mut self, color: Color) -> Self {
203 self.text = Some(color);
204 self
205 }
206
207 pub const fn text_disabled(mut self, color: Color) -> Self {
208 self.text_disabled = Some(color);
209 self
210 }
211
212 pub const fn font_size(mut self, size: f32) -> Self {
213 self.font_size = Some(size);
214 self
215 }
216
217 pub const fn font_weight(mut self, weight: u16) -> Self {
218 self.font_weight = Some(weight);
219 self
220 }
221
222 pub const fn radius(mut self, radius: f32) -> Self {
223 self.radius = Some(radius);
224 self
225 }
226
227 pub const fn padding_xy(mut self, pad_x: f32, pad_y: f32) -> Self {
228 self.padding = Some((pad_x, pad_y));
229 self
230 }
231
232 pub const fn border_width(mut self, width: f32) -> Self {
233 self.border_width = Some(width);
234 self
235 }
236
237 pub const fn border_color(mut self, color: Color) -> Self {
238 self.border_color = Some(color);
239 self
240 }
241
242 pub const fn border(mut self, width: f32, color: Color) -> Self {
243 self.border_width = Some(width);
244 self.border_color = Some(color);
245 self
246 }
247}
248
249#[cfg(test)]
250mod tests {
251 use super::*;
252
253 #[test]
254 fn presets_differ_per_state() {
255 assert_ne!(ButtonStyle::PRIMARY.background, ButtonStyle::PRIMARY.hover);
256 assert_ne!(ButtonStyle::PRIMARY.hover, ButtonStyle::PRIMARY.pressed);
257 assert_ne!(
258 ButtonStyle::DANGER.background,
259 ButtonStyle::PRIMARY.background
260 );
261 }
262
263 #[test]
264 fn const_composition_in_const_context() {
265 const CUSTOM: ButtonStyle = ButtonStyle::PRIMARY.radius(12.0);
266 assert_eq!(CUSTOM.radius, Some(12.0));
267 assert_eq!(CUSTOM.background, ButtonStyle::PRIMARY.background);
268 }
269
270 #[test]
271 fn struct_update_syntax_keeps_base() {
272 const CUSTOM: ButtonStyle = ButtonStyle {
273 background: Some(Color::hex(0xB3261E)),
274 ..ButtonStyle::PRIMARY
275 };
276 assert_eq!(CUSTOM.background, Some(Color::hex(0xB3261E)));
277 assert_eq!(CUSTOM.hover, ButtonStyle::PRIMARY.hover);
278 }
279
280 #[test]
281 fn border_builders_set_both_fields() {
282 const S: ButtonStyle = ButtonStyle::new().border(1.0, Color::WHITE);
283 assert_eq!(S.border_width, Some(1.0));
284 assert_eq!(S.border_color, Some(Color::WHITE));
285 }
286
287 #[test]
288 fn new_variants_share_metrics_but_differ_in_look() {
289 assert_eq!(
290 ButtonStyle::DARK_FILLED.padding,
291 ButtonStyle::OUTLINE_ROSE.padding
292 );
293 assert_eq!(ButtonStyle::DARK_FILLED.padding, Some((12.0, 6.0)));
294 assert_eq!(ButtonStyle::DARK_FILLED.font_size, Some(12.0));
295 assert_eq!(ButtonStyle::OUTLINE_ROSE.font_size, Some(12.0));
296
297 assert_eq!(ButtonStyle::DARK_FILLED.font_weight, Some(500));
298 assert_eq!(ButtonStyle::OUTLINE_ROSE.font_weight, Some(600));
299 assert_eq!(
300 ButtonStyle::DARK_FILLED.background,
301 Some(Color::hex(0x1F1F1F))
302 );
303 assert_eq!(ButtonStyle::DARK_FILLED.text, Some(Color::hex(0xEDEDED)));
304 assert_eq!(ButtonStyle::DARK_FILLED.border_width, None);
305 assert_eq!(
306 ButtonStyle::OUTLINE_ROSE.background,
307 Some(Color::TRANSPARENT)
308 );
309 assert_eq!(ButtonStyle::OUTLINE_ROSE.text, Some(Color::hex(0xB08686)));
310 assert_eq!(ButtonStyle::OUTLINE_ROSE.border_width, Some(1.0));
311 assert_eq!(
312 ButtonStyle::OUTLINE_ROSE.border_color,
313 Some(Color::hex(0xB08686))
314 );
315 assert_ne!(
316 ButtonStyle::DARK_FILLED.hover,
317 ButtonStyle::DARK_FILLED.background
318 );
319 assert_ne!(
320 ButtonStyle::OUTLINE_ROSE.hover,
321 ButtonStyle::OUTLINE_ROSE.background
322 );
323 }
324}