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}
18
19#[derive(Clone, PartialEq)]
20pub enum ButtonLayoutVariant {
21    Normal,
22    Compact,
23    Expanded,
24}
25
26/// Simply a button.
27///
28/// ## **Normal**
29///
30/// ```rust
31/// # use freya::prelude::*;
32/// fn app() -> impl IntoElement {
33///     Button::new()
34///         .on_press(|_| println!("Pressed!"))
35///         .child("Press me")
36/// }
37/// # use freya_testing::prelude::*;
38/// # launch_doc(|| {
39/// #   rect().center().expanded().child(app())
40/// # }, (250., 250.).into(), "./images/gallery_button.png");
41/// ```
42/// ## **Filled**
43///
44/// ```rust
45/// # use freya::prelude::*;
46/// fn app() -> impl IntoElement {
47///     Button::new()
48///         .on_press(|_| println!("Pressed!"))
49///         .filled()
50///         .child("Press me")
51/// }
52/// # use freya_testing::prelude::*;
53/// # launch_doc(|| {
54/// #   rect().center().expanded().child(app())
55/// # }, (250., 250.).into(), "./images/gallery_filled_button.png");
56/// ```
57/// ## **Outline**
58///
59/// ```rust
60/// # use freya::prelude::*;
61/// fn app() -> impl IntoElement {
62///     Button::new()
63///         .on_press(|_| println!("Pressed!"))
64///         .outline()
65///         .child("Press me")
66/// }
67/// # use freya_testing::prelude::*;
68/// # launch_doc(|| {
69/// #   rect().center().expanded().child(app())
70/// # }, (250., 250.).into(), "./images/gallery_outline_button.png");
71/// ```
72///
73/// # Preview
74/// ![Button Preview][button]
75/// ![Outline Button Preview][outline_button]
76/// ![Filled Button Preview][filled_button]
77#[cfg_attr(feature = "docs",
78    doc = embed_doc_image::embed_image!("button", "images/gallery_button.png"),
79    doc = embed_doc_image::embed_image!("filled_button", "images/gallery_filled_button.png"),
80    doc = embed_doc_image::embed_image!("outline_button", "images/gallery_outline_button.png"),
81)]
82#[derive(Clone, PartialEq)]
83pub struct Button {
84    pub(crate) theme_colors: Option<ButtonColorsThemePartial>,
85    pub(crate) theme_layout: Option<ButtonLayoutThemePartial>,
86    elements: Vec<Element>,
87    on_press: Option<EventHandler<Event<PressEventData>>>,
88    key: DiffKey,
89    style_variant: ButtonStyleVariant,
90    layout_variant: ButtonLayoutVariant,
91    enabled: bool,
92}
93
94impl Default for Button {
95    fn default() -> Self {
96        Self::new()
97    }
98}
99
100impl ChildrenExt for Button {
101    fn get_children(&mut self) -> &mut Vec<Element> {
102        &mut self.elements
103    }
104}
105
106impl KeyExt for Button {
107    fn write_key(&mut self) -> &mut DiffKey {
108        &mut self.key
109    }
110}
111
112impl Button {
113    pub fn new() -> Self {
114        Self {
115            theme_colors: None,
116            theme_layout: None,
117            style_variant: ButtonStyleVariant::Normal,
118            layout_variant: ButtonLayoutVariant::Normal,
119            on_press: None,
120            elements: Vec::default(),
121            enabled: true,
122            key: DiffKey::None,
123        }
124    }
125
126    pub fn enabled(mut self, enabled: impl Into<bool>) -> Self {
127        self.enabled = enabled.into();
128        self
129    }
130
131    pub fn style_variant(mut self, style_variant: impl Into<ButtonStyleVariant>) -> Self {
132        self.style_variant = style_variant.into();
133        self
134    }
135
136    pub fn layout_variant(mut self, layout_variant: impl Into<ButtonLayoutVariant>) -> Self {
137        self.layout_variant = layout_variant.into();
138        self
139    }
140
141    pub fn on_press(mut self, on_press: impl FnMut(Event<PressEventData>) + 'static) -> Self {
142        self.on_press = Some(EventHandler::new(on_press));
143        self
144    }
145
146    pub fn theme_colors(mut self, theme: ButtonColorsThemePartial) -> Self {
147        self.theme_colors = Some(theme);
148        self
149    }
150
151    pub fn theme_layout(mut self, theme: ButtonLayoutThemePartial) -> Self {
152        self.theme_layout = Some(theme);
153        self
154    }
155
156    /// Shortcut for [Self::theme_layout] and [ButtonLayoutVariant::Compact].
157    pub fn compact(self) -> Self {
158        self.layout_variant(ButtonLayoutVariant::Compact)
159    }
160
161    /// Shortcut for [Self::theme_layout] and [ButtonLayoutVariant::Expanded].
162    pub fn expanded(self) -> Self {
163        self.layout_variant(ButtonLayoutVariant::Expanded)
164    }
165
166    /// Shortcut for [Self::style_variant] and [ButtonStyleVariant::Filled].
167    pub fn filled(self) -> Self {
168        self.style_variant(ButtonStyleVariant::Filled)
169    }
170
171    /// Shortcut for [Self::style_variant] and [ButtonStyleVariant::Outline].
172    pub fn outline(self) -> Self {
173        self.style_variant(ButtonStyleVariant::Outline)
174    }
175
176    /// Shortcut for [Self::corner_radius] with `99`.
177    pub fn rounded(self) -> Self {
178        self.corner_radius(99.)
179    }
180}
181
182impl Render for Button {
183    fn render(&self) -> impl IntoElement {
184        let mut hovering = use_state(|| false);
185        let focus = use_focus();
186        let focus_status = use_focus_status(focus);
187
188        let enabled = use_reactive(&self.enabled);
189        use_drop(move || {
190            if hovering() && enabled() {
191                Cursor::set(CursorIcon::default());
192            }
193        });
194
195        let theme_colors = match self.style_variant {
196            ButtonStyleVariant::Normal => get_theme!(&self.theme_colors, button),
197            ButtonStyleVariant::Outline => get_theme!(&self.theme_colors, outline_button),
198            ButtonStyleVariant::Filled => get_theme!(&self.theme_colors, filled_button),
199        };
200        let theme_layout = match self.layout_variant {
201            ButtonLayoutVariant::Normal => get_theme!(&self.theme_layout, button_layout),
202            ButtonLayoutVariant::Compact => get_theme!(&self.theme_layout, compact_button_layout),
203            ButtonLayoutVariant::Expanded => get_theme!(&self.theme_layout, expanded_button_layout),
204        };
205
206        let border = if focus_status() == FocusStatus::Keyboard {
207            Border::new()
208                .fill(theme_colors.focus_border_fill)
209                .width(2.)
210                .alignment(BorderAlignment::Inner)
211        } else {
212            Border::new()
213                .fill(theme_colors.border_fill.mul_if(!self.enabled, 0.9))
214                .width(1.)
215                .alignment(BorderAlignment::Inner)
216        };
217        let background = if enabled() && hovering() {
218            theme_colors.hover_background
219        } else {
220            theme_colors.background
221        };
222
223        rect()
224            .a11y_id(focus.a11y_id())
225            .a11y_focusable(self.enabled)
226            .a11y_role(AccessibilityRole::Button)
227            .background(background.mul_if(!self.enabled, 0.9))
228            .border(border)
229            .padding(theme_layout.padding)
230            .corner_radius(theme_layout.corner_radius)
231            .width(theme_layout.width)
232            .height(theme_layout.height)
233            .color(theme_colors.color.mul_if(!self.enabled, 0.9))
234            .center()
235            .maybe(self.enabled, |rect| {
236                rect.on_press({
237                    let on_press = self.on_press.clone();
238                    move |e| {
239                        focus.request_focus();
240                        if let Some(on_press) = &on_press {
241                            on_press.call(e)
242                        }
243                    }
244                })
245            })
246            .on_pointer_enter(move |_| {
247                hovering.set(true);
248                if enabled() {
249                    Cursor::set(CursorIcon::Pointer);
250                } else {
251                    Cursor::set(CursorIcon::NotAllowed);
252                }
253            })
254            .on_pointer_leave(move |_| {
255                if hovering() {
256                    Cursor::set(CursorIcon::default());
257                    hovering.set(false);
258                }
259            })
260            .children(self.elements.clone())
261    }
262
263    fn render_key(&self) -> DiffKey {
264        self.key.clone().or(self.default_key())
265    }
266}