Skip to main content

iced_plus_components/
button.rs

1//! Type-safe button with phantom type encoding for variants and sizes.
2//!
3//! The button variant and size are encoded in the type signature, enabling
4//! compile-time style resolution and monomorphization for optimal performance.
5
6use 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// ============================================================================
16// Variant marker types
17// ============================================================================
18
19/// Primary button variant - main call-to-action.
20#[derive(Debug, Clone, Copy, Default)]
21pub struct Primary;
22impl Sealed for Primary {}
23
24/// Secondary button variant - alternative actions.
25#[derive(Debug, Clone, Copy, Default)]
26pub struct Secondary;
27impl Sealed for Secondary {}
28
29/// Ghost button variant - minimal styling.
30#[derive(Debug, Clone, Copy, Default)]
31pub struct Ghost;
32impl Sealed for Ghost {}
33
34/// Destructive button variant - dangerous/irreversible actions.
35#[derive(Debug, Clone, Copy, Default)]
36pub struct Destructive;
37impl Sealed for Destructive {}
38
39/// Outline button variant - bordered with transparent background.
40#[derive(Debug, Clone, Copy, Default)]
41pub struct Outline;
42impl Sealed for Outline {}
43
44/// Trait for button variants.
45pub trait ButtonVariant: Sealed + Copy + Default {
46    /// Get the corresponding ButtonClass for iced styling.
47    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// ============================================================================
81// Size marker types
82// ============================================================================
83
84/// Extra small button size.
85#[derive(Debug, Clone, Copy, Default)]
86pub struct ExtraSmall;
87impl Sealed for ExtraSmall {}
88
89/// Small button size.
90#[derive(Debug, Clone, Copy, Default)]
91pub struct Small;
92impl Sealed for Small {}
93
94/// Medium button size (default).
95#[derive(Debug, Clone, Copy, Default)]
96pub struct Medium;
97impl Sealed for Medium {}
98
99/// Large button size.
100#[derive(Debug, Clone, Copy, Default)]
101pub struct Large;
102impl Sealed for Large {}
103
104/// Trait for button sizes.
105pub trait ButtonSize: Sealed + Copy + Default {
106    /// Horizontal padding in pixels.
107    const PADDING_H: f32;
108    /// Vertical padding in pixels.
109    const PADDING_V: f32;
110    /// Font size in pixels.
111    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
138// ============================================================================
139// Button widget
140// ============================================================================
141
142/// A type-safe button with variant and size encoded in the type signature.
143///
144/// # Type Parameters
145///
146/// - `V`: The variant type (Primary, Secondary, Ghost, Destructive, Outline)
147/// - `S`: The size type (ExtraSmall, Small, Medium, Large)
148/// - `Message`: The message type for button press events
149///
150/// # Example
151///
152/// ```rust,ignore
153/// use iced_plus_components::button::Button;
154///
155/// // Type: Button<'_, Primary, Medium, Message>
156/// let btn = Button::primary("Click me")
157///     .on_press(Message::Clicked);
158///
159/// // Type: Button<'_, Destructive, Small, Message>
160/// let delete = Button::destructive("Delete")
161///     .small()
162///     .on_press(Message::Delete);
163/// ```
164pub 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
176// Constructors for each variant (returns Medium size by default)
177impl<'a, Message> Button<'a, Primary, Medium, Message> {
178    /// Create a primary button.
179    #[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    /// Create a secondary button.
187    #[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    /// Create a ghost button.
195    #[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    /// Create a destructive button.
203    #[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    /// Create an outline button.
211    #[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    /// Set the message to emit when pressed.
233    #[must_use]
234    pub fn on_press(mut self, message: Message) -> Self {
235        self.on_press = Some(message);
236        self
237    }
238
239    /// Set the message to emit when pressed, if Some.
240    #[must_use]
241    pub fn on_press_maybe(mut self, message: Option<Message>) -> Self {
242        self.on_press = message;
243        self
244    }
245
246    /// Set the button width.
247    #[must_use]
248    pub fn width(mut self, width: impl Into<Length>) -> Self {
249        self.width = width.into();
250        self
251    }
252
253    /// Make the button fill the available width.
254    #[must_use]
255    pub fn fill_width(mut self) -> Self {
256        self.width = Length::Fill;
257        self
258    }
259
260    // Size changers - these change the type parameter S
261
262    /// Change to extra small size.
263    #[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    /// Change to small size.
275    #[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    /// Change to large size.
287    #[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
322// Also implement for default iced::Theme for flexibility
323impl<'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}