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    focusable: bool,
112}
113
114impl Default for Button {
115    fn default() -> Self {
116        Self::new()
117    }
118}
119
120impl ChildrenExt for Button {
121    fn get_children(&mut self) -> &mut Vec<Element> {
122        &mut self.elements
123    }
124}
125
126impl KeyExt for Button {
127    fn write_key(&mut self) -> &mut DiffKey {
128        &mut self.key
129    }
130}
131
132impl Button {
133    pub fn new() -> Self {
134        Self {
135            theme_colors: None,
136            theme_layout: None,
137            style_variant: ButtonStyleVariant::Normal,
138            layout_variant: ButtonLayoutVariant::Normal,
139            on_press: None,
140            on_secondary_press: None,
141            elements: Vec::default(),
142            enabled: true,
143            focusable: true,
144            key: DiffKey::None,
145        }
146    }
147
148    pub fn get_layout_variant(&self) -> &ButtonLayoutVariant {
149        &self.layout_variant
150    }
151
152    pub fn get_theme_layout(&self) -> Option<&ButtonLayoutThemePartial> {
153        self.theme_layout.as_ref()
154    }
155
156    pub fn enabled(mut self, enabled: impl Into<bool>) -> Self {
157        self.enabled = enabled.into();
158        self
159    }
160
161    pub fn focusable(mut self, focusable: impl Into<bool>) -> Self {
162        self.focusable = focusable.into();
163        self
164    }
165
166    pub fn style_variant(mut self, style_variant: impl Into<ButtonStyleVariant>) -> Self {
167        self.style_variant = style_variant.into();
168        self
169    }
170
171    pub fn layout_variant(mut self, layout_variant: impl Into<ButtonLayoutVariant>) -> Self {
172        self.layout_variant = layout_variant.into();
173        self
174    }
175
176    pub fn on_press(mut self, on_press: impl Into<EventHandler<Event<PressEventData>>>) -> Self {
177        self.on_press = Some(on_press.into());
178        self
179    }
180
181    pub fn on_secondary_press(
182        mut self,
183        on_secondary_press: impl Into<EventHandler<Event<PressEventData>>>,
184    ) -> Self {
185        self.on_secondary_press = Some(on_secondary_press.into());
186        self
187    }
188
189    pub fn theme_colors(mut self, theme: ButtonColorsThemePartial) -> Self {
190        self.theme_colors = Some(theme);
191        self
192    }
193
194    pub fn theme_layout(mut self, theme: ButtonLayoutThemePartial) -> Self {
195        self.theme_layout = Some(theme);
196        self
197    }
198
199    /// Shortcut for [Self::theme_layout] and [ButtonLayoutVariant::Compact].
200    pub fn compact(self) -> Self {
201        self.layout_variant(ButtonLayoutVariant::Compact)
202    }
203
204    /// Shortcut for [Self::theme_layout] and [ButtonLayoutVariant::Expanded].
205    pub fn expanded(self) -> Self {
206        self.layout_variant(ButtonLayoutVariant::Expanded)
207    }
208
209    /// Shortcut for [Self::style_variant] and [ButtonStyleVariant::Filled].
210    pub fn filled(self) -> Self {
211        self.style_variant(ButtonStyleVariant::Filled)
212    }
213
214    /// Shortcut for [Self::style_variant] and [ButtonStyleVariant::Outline].
215    pub fn outline(self) -> Self {
216        self.style_variant(ButtonStyleVariant::Outline)
217    }
218
219    /// Shortcut for [Self::style_variant] and [ButtonStyleVariant::Flat].
220    pub fn flat(self) -> Self {
221        self.style_variant(ButtonStyleVariant::Flat)
222    }
223}
224
225impl CornerRadiusExt for Button {
226    fn with_corner_radius(self, corner_radius: f32) -> Self {
227        self.corner_radius(corner_radius)
228    }
229}
230
231impl Component for Button {
232    fn render(&self) -> impl IntoElement {
233        let mut hovering = use_state(|| false);
234        let focus = use_focus();
235        let focus_status = use_focus_status(focus);
236
237        let enabled = use_reactive(&self.enabled);
238        use_drop(move || {
239            if hovering() && enabled() {
240                Cursor::set(CursorIcon::default());
241            }
242        });
243
244        let theme_colors = match self.style_variant {
245            ButtonStyleVariant::Normal => get_theme!(&self.theme_colors, button),
246            ButtonStyleVariant::Outline => get_theme!(&self.theme_colors, outline_button),
247            ButtonStyleVariant::Filled => get_theme!(&self.theme_colors, filled_button),
248            ButtonStyleVariant::Flat => get_theme!(&self.theme_colors, flat_button),
249        };
250        let theme_layout = match self.layout_variant {
251            ButtonLayoutVariant::Normal => get_theme!(&self.theme_layout, button_layout),
252            ButtonLayoutVariant::Compact => get_theme!(&self.theme_layout, compact_button_layout),
253            ButtonLayoutVariant::Expanded => get_theme!(&self.theme_layout, expanded_button_layout),
254        };
255
256        let border = if focus_status() == FocusStatus::Keyboard {
257            Border::new()
258                .fill(theme_colors.focus_border_fill)
259                .width(2.)
260                .alignment(BorderAlignment::Inner)
261        } else {
262            Border::new()
263                .fill(theme_colors.border_fill.mul_if(!self.enabled, 0.9))
264                .width(1.)
265                .alignment(BorderAlignment::Inner)
266        };
267        let background = if enabled() && hovering() {
268            theme_colors.hover_background
269        } else {
270            theme_colors.background
271        };
272
273        rect()
274            .overflow(Overflow::Clip)
275            .a11y_id(focus.a11y_id())
276            .a11y_focusable(self.enabled && self.focusable)
277            .a11y_role(AccessibilityRole::Button)
278            .background(background.mul_if(!self.enabled, 0.9))
279            .border(border)
280            .padding(theme_layout.padding)
281            .corner_radius(theme_layout.corner_radius)
282            .width(theme_layout.width)
283            .height(theme_layout.height)
284            .color(theme_colors.color.mul_if(!self.enabled, 0.9))
285            .center()
286            .maybe(self.enabled, |rect| {
287                rect.on_pointer_down(|e: Event<PointerEventData>| e.stop_propagation())
288                    .on_all_press({
289                        let on_press = self.on_press.clone();
290                        let on_secondary_press = self.on_secondary_press.clone();
291                        move |e: Event<PressEventData>| {
292                            focus.request_focus();
293                            match e.data() {
294                                PressEventData::Mouse(data) => match data.button {
295                                    Some(MouseButton::Left) => {
296                                        if let Some(handler) = &on_press {
297                                            handler.call(e);
298                                        }
299                                    }
300                                    Some(MouseButton::Right) => {
301                                        if let Some(handler) = &on_secondary_press {
302                                            handler.call(e);
303                                        }
304                                    }
305                                    _ => {}
306                                },
307                                PressEventData::Touch(_) | PressEventData::Keyboard(_) => {
308                                    if let Some(handler) = &on_press {
309                                        handler.call(e);
310                                    }
311                                }
312                            }
313                        }
314                    })
315            })
316            .on_pointer_enter(move |_| {
317                hovering.set(true);
318                if enabled() {
319                    Cursor::set(CursorIcon::Pointer);
320                } else {
321                    Cursor::set(CursorIcon::NotAllowed);
322                }
323            })
324            .on_pointer_leave(move |_| {
325                if hovering() {
326                    Cursor::set(CursorIcon::default());
327                    hovering.set(false);
328                }
329            })
330            .children(self.elements.clone())
331    }
332
333    fn render_key(&self) -> DiffKey {
334        self.key.clone().or(self.default_key())
335    }
336}