1use std::borrow::Cow;
7use std::marker::PhantomData;
8
9use iced::widget::button;
10use iced::{Element, Length};
11use iced_plus_theme::{AppTheme, ButtonClass};
12
13use crate::private::Sealed;
14
15#[derive(Debug, Clone, Copy, Default)]
21pub struct Primary;
22impl Sealed for Primary {}
23
24#[derive(Debug, Clone, Copy, Default)]
26pub struct Secondary;
27impl Sealed for Secondary {}
28
29#[derive(Debug, Clone, Copy, Default)]
31pub struct Ghost;
32impl Sealed for Ghost {}
33
34#[derive(Debug, Clone, Copy, Default)]
36pub struct Destructive;
37impl Sealed for Destructive {}
38
39#[derive(Debug, Clone, Copy, Default)]
41pub struct Outline;
42impl Sealed for Outline {}
43
44pub trait ButtonVariant: Sealed + Copy + Default {
46 fn button_class() -> ButtonClass;
48}
49
50impl ButtonVariant for Primary {
51 fn button_class() -> ButtonClass {
52 ButtonClass::Primary
53 }
54}
55
56impl ButtonVariant for Secondary {
57 fn button_class() -> ButtonClass {
58 ButtonClass::Secondary
59 }
60}
61
62impl ButtonVariant for Ghost {
63 fn button_class() -> ButtonClass {
64 ButtonClass::Ghost
65 }
66}
67
68impl ButtonVariant for Destructive {
69 fn button_class() -> ButtonClass {
70 ButtonClass::Destructive
71 }
72}
73
74impl ButtonVariant for Outline {
75 fn button_class() -> ButtonClass {
76 ButtonClass::Outline
77 }
78}
79
80#[derive(Debug, Clone, Copy, Default)]
86pub struct ExtraSmall;
87impl Sealed for ExtraSmall {}
88
89#[derive(Debug, Clone, Copy, Default)]
91pub struct Small;
92impl Sealed for Small {}
93
94#[derive(Debug, Clone, Copy, Default)]
96pub struct Medium;
97impl Sealed for Medium {}
98
99#[derive(Debug, Clone, Copy, Default)]
101pub struct Large;
102impl Sealed for Large {}
103
104pub trait ButtonSize: Sealed + Copy + Default {
106 const PADDING_H: f32;
108 const PADDING_V: f32;
110 const FONT_SIZE: f32;
112}
113
114impl ButtonSize for ExtraSmall {
115 const PADDING_H: f32 = 8.0;
116 const PADDING_V: f32 = 4.0;
117 const FONT_SIZE: f32 = 12.0;
118}
119
120impl ButtonSize for Small {
121 const PADDING_H: f32 = 12.0;
122 const PADDING_V: f32 = 6.0;
123 const FONT_SIZE: f32 = 14.0;
124}
125
126impl ButtonSize for Medium {
127 const PADDING_H: f32 = 16.0;
128 const PADDING_V: f32 = 8.0;
129 const FONT_SIZE: f32 = 14.0;
130}
131
132impl ButtonSize for Large {
133 const PADDING_H: f32 = 20.0;
134 const PADDING_V: f32 = 10.0;
135 const FONT_SIZE: f32 = 16.0;
136}
137
138pub struct Button<'a, V, S, Message>
165where
166 V: ButtonVariant,
167 S: ButtonSize,
168{
169 label: Cow<'a, str>,
170 on_press: Option<Message>,
171 width: Length,
172 _variant: PhantomData<V>,
173 _size: PhantomData<S>,
174}
175
176impl<'a, Message> Button<'a, Primary, Medium, Message> {
178 #[must_use]
180 pub fn primary(label: impl Into<Cow<'a, str>>) -> Self {
181 Self::new(label)
182 }
183}
184
185impl<'a, Message> Button<'a, Secondary, Medium, Message> {
186 #[must_use]
188 pub fn secondary(label: impl Into<Cow<'a, str>>) -> Self {
189 Self::new(label)
190 }
191}
192
193impl<'a, Message> Button<'a, Ghost, Medium, Message> {
194 #[must_use]
196 pub fn ghost(label: impl Into<Cow<'a, str>>) -> Self {
197 Self::new(label)
198 }
199}
200
201impl<'a, Message> Button<'a, Destructive, Medium, Message> {
202 #[must_use]
204 pub fn destructive(label: impl Into<Cow<'a, str>>) -> Self {
205 Self::new(label)
206 }
207}
208
209impl<'a, Message> Button<'a, Outline, Medium, Message> {
210 #[must_use]
212 pub fn outline(label: impl Into<Cow<'a, str>>) -> Self {
213 Self::new(label)
214 }
215}
216
217impl<'a, V, S, Message> Button<'a, V, S, Message>
218where
219 V: ButtonVariant,
220 S: ButtonSize,
221{
222 fn new(label: impl Into<Cow<'a, str>>) -> Self {
223 Self {
224 label: label.into(),
225 on_press: None,
226 width: Length::Shrink,
227 _variant: PhantomData,
228 _size: PhantomData,
229 }
230 }
231
232 #[must_use]
234 pub fn on_press(mut self, message: Message) -> Self {
235 self.on_press = Some(message);
236 self
237 }
238
239 #[must_use]
241 pub fn on_press_maybe(mut self, message: Option<Message>) -> Self {
242 self.on_press = message;
243 self
244 }
245
246 #[must_use]
248 pub fn width(mut self, width: impl Into<Length>) -> Self {
249 self.width = width.into();
250 self
251 }
252
253 #[must_use]
255 pub fn fill_width(mut self) -> Self {
256 self.width = Length::Fill;
257 self
258 }
259
260 #[must_use]
264 pub fn extra_small(self) -> Button<'a, V, ExtraSmall, Message> {
265 Button {
266 label: self.label,
267 on_press: self.on_press,
268 width: self.width,
269 _variant: PhantomData,
270 _size: PhantomData,
271 }
272 }
273
274 #[must_use]
276 pub fn small(self) -> Button<'a, V, Small, Message> {
277 Button {
278 label: self.label,
279 on_press: self.on_press,
280 width: self.width,
281 _variant: PhantomData,
282 _size: PhantomData,
283 }
284 }
285
286 #[must_use]
288 pub fn large(self) -> Button<'a, V, Large, Message> {
289 Button {
290 label: self.label,
291 on_press: self.on_press,
292 width: self.width,
293 _variant: PhantomData,
294 _size: PhantomData,
295 }
296 }
297}
298
299impl<'a, V, S, Message> From<Button<'a, V, S, Message>> for Element<'a, Message, AppTheme<'a>>
300where
301 V: ButtonVariant + 'a,
302 S: ButtonSize + 'a,
303 Message: Clone + 'a,
304{
305 fn from(btn: Button<'a, V, S, Message>) -> Self {
306 let label: String = btn.label.into_owned();
307 let content = iced::widget::text(label).size(S::FONT_SIZE);
308
309 let mut button = button(content)
310 .padding([S::PADDING_V, S::PADDING_H])
311 .width(btn.width)
312 .class(V::button_class());
313
314 if let Some(msg) = btn.on_press {
315 button = button.on_press(msg);
316 }
317
318 button.into()
319 }
320}
321
322impl<'a, V, S, Message> From<Button<'a, V, S, Message>> for Element<'a, Message, iced::Theme>
324where
325 V: ButtonVariant + 'a,
326 S: ButtonSize + 'a,
327 Message: Clone + 'a,
328{
329 fn from(btn: Button<'a, V, S, Message>) -> Self {
330 let label: String = btn.label.into_owned();
331 let content = iced::widget::text(label).size(S::FONT_SIZE);
332
333 let mut button = button(content)
334 .padding([S::PADDING_V, S::PADDING_H])
335 .width(btn.width);
336
337 if let Some(msg) = btn.on_press {
338 button = button.on_press(msg);
339 }
340
341 button.into()
342 }
343}