freya_components/
button.rs

1use freya_core::prelude::*;
2
3use crate::{
4    get_theme,
5    theming::component_themes::{
6        ButtonColorsThemePartial,
7        ButtonLayoutThemePartial,
8        ButtonLayoutThemePartialExt,
9    },
10};
11
12#[derive(Clone, PartialEq)]
13pub enum ButtonStyleVariant {
14    Normal,
15    Filled,
16    Outline,
17    Flat,
18}
19
20#[derive(Clone, PartialEq)]
21pub enum ButtonLayoutVariant {
22    Normal,
23    Compact,
24    Expanded,
25}
26
27/// Simply a button.
28///
29/// ## **Normal**
30///
31/// ```rust
32/// # use freya::prelude::*;
33/// fn app() -> impl IntoElement {
34///     Button::new()
35///         .on_press(|_| println!("Pressed!"))
36///         .child("Press me")
37/// }
38/// # use freya_testing::prelude::*;
39/// # launch_doc(|| {
40/// #   rect().center().expanded().child(app())
41/// # }, "./images/gallery_button.png").render();
42/// ```
43/// ## **Filled**
44///
45/// ```rust
46/// # use freya::prelude::*;
47/// fn app() -> impl IntoElement {
48///     Button::new()
49///         .on_press(|_| println!("Pressed!"))
50///         .filled()
51///         .child("Press me")
52/// }
53/// # use freya_testing::prelude::*;
54/// # launch_doc(|| {
55/// #   rect().center().expanded().child(app())
56/// # }, "./images/gallery_filled_button.png").render();
57/// ```
58/// ## **Outline**
59///
60/// ```rust
61/// # use freya::prelude::*;
62/// fn app() -> impl IntoElement {
63///     Button::new()
64///         .on_press(|_| println!("Pressed!"))
65///         .outline()
66///         .child("Press me")
67/// }
68/// # use freya_testing::prelude::*;
69/// # launch_doc(|| {
70/// #   rect().center().expanded().child(app())
71/// # }, "./images/gallery_outline_button.png").render();
72/// ```
73/// ## **Flat**
74///
75/// ```rust
76/// # use freya::prelude::*;
77/// fn app() -> impl IntoElement {
78///     Button::new()
79///         .on_press(|_| println!("Pressed!"))
80///         .flat()
81///         .child("Press me")
82/// }
83/// # use freya_testing::prelude::*;
84/// # launch_doc(|| {
85/// #   rect().center().expanded().child(app())
86/// # }, "./images/gallery_flat_button.png").render();
87/// ```
88///
89/// # Preview
90/// ![Button Preview][button]
91/// ![Outline Button Preview][outline_button]
92/// ![Filled Button Preview][filled_button]
93/// ![Flat Button Preview][flat_button]
94#[cfg_attr(feature = "docs",
95    doc = embed_doc_image::embed_image!("button", "images/gallery_button.png"),
96    doc = embed_doc_image::embed_image!("filled_button", "images/gallery_filled_button.png"),
97    doc = embed_doc_image::embed_image!("outline_button", "images/gallery_outline_button.png"),
98    doc = embed_doc_image::embed_image!("flat_button", "images/gallery_flat_button.png"),
99)]
100#[derive(Clone, PartialEq)]
101pub struct Button {
102    pub(crate) theme_colors: Option<ButtonColorsThemePartial>,
103    pub(crate) theme_layout: Option<ButtonLayoutThemePartial>,
104    elements: Vec<Element>,
105    on_press: Option<EventHandler<Event<PressEventData>>>,
106    on_secondary_press: Option<EventHandler<Event<PressEventData>>>,
107    key: DiffKey,
108    style_variant: ButtonStyleVariant,
109    layout_variant: ButtonLayoutVariant,
110    enabled: bool,
111}
112
113impl Default for Button {
114    fn default() -> Self {
115        Self::new()
116    }
117}
118
119impl ChildrenExt for Button {
120    fn get_children(&mut self) -> &mut Vec<Element> {
121        &mut self.elements
122    }
123}
124
125impl KeyExt for Button {
126    fn write_key(&mut self) -> &mut DiffKey {
127        &mut self.key
128    }
129}
130
131impl Button {
132    pub fn new() -> Self {
133        Self {
134            theme_colors: None,
135            theme_layout: None,
136            style_variant: ButtonStyleVariant::Normal,
137            layout_variant: ButtonLayoutVariant::Normal,
138            on_press: None,
139            on_secondary_press: None,
140            elements: Vec::default(),
141            enabled: true,
142            key: DiffKey::None,
143        }
144    }
145
146    pub fn get_layout_variant(&self) -> &ButtonLayoutVariant {
147        &self.layout_variant
148    }
149
150    pub fn get_theme_layout(&self) -> Option<&ButtonLayoutThemePartial> {
151        self.theme_layout.as_ref()
152    }
153
154    pub fn enabled(mut self, enabled: impl Into<bool>) -> Self {
155        self.enabled = enabled.into();
156        self
157    }
158
159    pub fn style_variant(mut self, style_variant: impl Into<ButtonStyleVariant>) -> Self {
160        self.style_variant = style_variant.into();
161        self
162    }
163
164    pub fn layout_variant(mut self, layout_variant: impl Into<ButtonLayoutVariant>) -> Self {
165        self.layout_variant = layout_variant.into();
166        self
167    }
168
169    pub fn on_press(mut self, on_press: impl Into<EventHandler<Event<PressEventData>>>) -> Self {
170        self.on_press = Some(on_press.into());
171        self
172    }
173
174    pub fn on_secondary_press(
175        mut self,
176        on_secondary_press: impl Into<EventHandler<Event<PressEventData>>>,
177    ) -> Self {
178        self.on_secondary_press = Some(on_secondary_press.into());
179        self
180    }
181
182    pub fn theme_colors(mut self, theme: ButtonColorsThemePartial) -> Self {
183        self.theme_colors = Some(theme);
184        self
185    }
186
187    pub fn theme_layout(mut self, theme: ButtonLayoutThemePartial) -> Self {
188        self.theme_layout = Some(theme);
189        self
190    }
191
192    /// Shortcut for [Self::theme_layout] and [ButtonLayoutVariant::Compact].
193    pub fn compact(self) -> Self {
194        self.layout_variant(ButtonLayoutVariant::Compact)
195    }
196
197    /// Shortcut for [Self::theme_layout] and [ButtonLayoutVariant::Expanded].
198    pub fn expanded(self) -> Self {
199        self.layout_variant(ButtonLayoutVariant::Expanded)
200    }
201
202    /// Shortcut for [Self::style_variant] and [ButtonStyleVariant::Filled].
203    pub fn filled(self) -> Self {
204        self.style_variant(ButtonStyleVariant::Filled)
205    }
206
207    /// Shortcut for [Self::style_variant] and [ButtonStyleVariant::Outline].
208    pub fn outline(self) -> Self {
209        self.style_variant(ButtonStyleVariant::Outline)
210    }
211
212    /// Shortcut for [Self::style_variant] and [ButtonStyleVariant::Flat].
213    pub fn flat(self) -> Self {
214        self.style_variant(ButtonStyleVariant::Flat)
215    }
216
217    /// Shortcut for [Self::corner_radius] with `99`.
218    pub fn rounded(self) -> Self {
219        self.corner_radius(99.)
220    }
221}
222
223impl Component for Button {
224    fn render(&self) -> impl IntoElement {
225        let mut hovering = use_state(|| false);
226        let focus = use_focus();
227        let focus_status = use_focus_status(focus);
228
229        let enabled = use_reactive(&self.enabled);
230        use_drop(move || {
231            if hovering() && enabled() {
232                Cursor::set(CursorIcon::default());
233            }
234        });
235
236        let theme_colors = match self.style_variant {
237            ButtonStyleVariant::Normal => get_theme!(&self.theme_colors, button),
238            ButtonStyleVariant::Outline => get_theme!(&self.theme_colors, outline_button),
239            ButtonStyleVariant::Filled => get_theme!(&self.theme_colors, filled_button),
240            ButtonStyleVariant::Flat => get_theme!(&self.theme_colors, flat_button),
241        };
242        let theme_layout = match self.layout_variant {
243            ButtonLayoutVariant::Normal => get_theme!(&self.theme_layout, button_layout),
244            ButtonLayoutVariant::Compact => get_theme!(&self.theme_layout, compact_button_layout),
245            ButtonLayoutVariant::Expanded => get_theme!(&self.theme_layout, expanded_button_layout),
246        };
247
248        let border = if focus_status() == FocusStatus::Keyboard {
249            Border::new()
250                .fill(theme_colors.focus_border_fill)
251                .width(2.)
252                .alignment(BorderAlignment::Inner)
253        } else {
254            Border::new()
255                .fill(theme_colors.border_fill.mul_if(!self.enabled, 0.9))
256                .width(1.)
257                .alignment(BorderAlignment::Inner)
258        };
259        let background = if enabled() && hovering() {
260            theme_colors.hover_background
261        } else {
262            theme_colors.background
263        };
264
265        rect()
266            .overflow(Overflow::Clip)
267            .a11y_id(focus.a11y_id())
268            .a11y_focusable(self.enabled)
269            .a11y_role(AccessibilityRole::Button)
270            .background(background.mul_if(!self.enabled, 0.9))
271            .border(border)
272            .padding(theme_layout.padding)
273            .corner_radius(theme_layout.corner_radius)
274            .width(theme_layout.width)
275            .height(theme_layout.height)
276            .color(theme_colors.color.mul_if(!self.enabled, 0.9))
277            .center()
278            .maybe(self.enabled, |rect| {
279                rect.on_all_press({
280                    let on_press = self.on_press.clone();
281                    let on_secondary_press = self.on_secondary_press.clone();
282                    move |e: Event<PressEventData>| {
283                        focus.request_focus();
284                        match e.data() {
285                            PressEventData::Mouse(data) => match data.button {
286                                Some(MouseButton::Left) => {
287                                    if let Some(handler) = &on_press {
288                                        handler.call(e);
289                                    }
290                                }
291                                Some(MouseButton::Right) => {
292                                    if let Some(handler) = &on_secondary_press {
293                                        handler.call(e);
294                                    }
295                                }
296                                _ => {}
297                            },
298                            PressEventData::Touch(_) | PressEventData::Keyboard(_) => {
299                                if let Some(handler) = &on_press {
300                                    handler.call(e);
301                                }
302                            }
303                        }
304                    }
305                })
306            })
307            .on_pointer_enter(move |_| {
308                hovering.set(true);
309                if enabled() {
310                    Cursor::set(CursorIcon::Pointer);
311                } else {
312                    Cursor::set(CursorIcon::NotAllowed);
313                }
314            })
315            .on_pointer_leave(move |_| {
316                if hovering() {
317                    Cursor::set(CursorIcon::default());
318                    hovering.set(false);
319                }
320            })
321            .children(self.elements.clone())
322    }
323
324    fn render_key(&self) -> DiffKey {
325        self.key.clone().or(self.default_key())
326    }
327}