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    on_secondary_press: Option<EventHandler<Event<PressEventData>>>,
89    key: DiffKey,
90    style_variant: ButtonStyleVariant,
91    layout_variant: ButtonLayoutVariant,
92    enabled: bool,
93}
94
95impl Default for Button {
96    fn default() -> Self {
97        Self::new()
98    }
99}
100
101impl ChildrenExt for Button {
102    fn get_children(&mut self) -> &mut Vec<Element> {
103        &mut self.elements
104    }
105}
106
107impl KeyExt for Button {
108    fn write_key(&mut self) -> &mut DiffKey {
109        &mut self.key
110    }
111}
112
113impl Button {
114    pub fn new() -> Self {
115        Self {
116            theme_colors: None,
117            theme_layout: None,
118            style_variant: ButtonStyleVariant::Normal,
119            layout_variant: ButtonLayoutVariant::Normal,
120            on_press: None,
121            on_secondary_press: None,
122            elements: Vec::default(),
123            enabled: true,
124            key: DiffKey::None,
125        }
126    }
127
128    pub fn enabled(mut self, enabled: impl Into<bool>) -> Self {
129        self.enabled = enabled.into();
130        self
131    }
132
133    pub fn style_variant(mut self, style_variant: impl Into<ButtonStyleVariant>) -> Self {
134        self.style_variant = style_variant.into();
135        self
136    }
137
138    pub fn layout_variant(mut self, layout_variant: impl Into<ButtonLayoutVariant>) -> Self {
139        self.layout_variant = layout_variant.into();
140        self
141    }
142
143    pub fn on_press(mut self, on_press: impl FnMut(Event<PressEventData>) + 'static) -> Self {
144        self.on_press = Some(EventHandler::new(on_press));
145        self
146    }
147
148    pub fn on_secondary_press(
149        mut self,
150        on_secondary_press: impl FnMut(Event<PressEventData>) + 'static,
151    ) -> Self {
152        self.on_secondary_press = Some(EventHandler::new(on_secondary_press));
153        self
154    }
155
156    pub fn theme_colors(mut self, theme: ButtonColorsThemePartial) -> Self {
157        self.theme_colors = Some(theme);
158        self
159    }
160
161    pub fn theme_layout(mut self, theme: ButtonLayoutThemePartial) -> Self {
162        self.theme_layout = Some(theme);
163        self
164    }
165
166    /// Shortcut for [Self::theme_layout] and [ButtonLayoutVariant::Compact].
167    pub fn compact(self) -> Self {
168        self.layout_variant(ButtonLayoutVariant::Compact)
169    }
170
171    /// Shortcut for [Self::theme_layout] and [ButtonLayoutVariant::Expanded].
172    pub fn expanded(self) -> Self {
173        self.layout_variant(ButtonLayoutVariant::Expanded)
174    }
175
176    /// Shortcut for [Self::style_variant] and [ButtonStyleVariant::Filled].
177    pub fn filled(self) -> Self {
178        self.style_variant(ButtonStyleVariant::Filled)
179    }
180
181    /// Shortcut for [Self::style_variant] and [ButtonStyleVariant::Outline].
182    pub fn outline(self) -> Self {
183        self.style_variant(ButtonStyleVariant::Outline)
184    }
185
186    /// Shortcut for [Self::corner_radius] with `99`.
187    pub fn rounded(self) -> Self {
188        self.corner_radius(99.)
189    }
190}
191
192impl Render for Button {
193    fn render(&self) -> impl IntoElement {
194        let mut hovering = use_state(|| false);
195        let focus = use_focus();
196        let focus_status = use_focus_status(focus);
197
198        let enabled = use_reactive(&self.enabled);
199        use_drop(move || {
200            if hovering() && enabled() {
201                Cursor::set(CursorIcon::default());
202            }
203        });
204
205        let theme_colors = match self.style_variant {
206            ButtonStyleVariant::Normal => get_theme!(&self.theme_colors, button),
207            ButtonStyleVariant::Outline => get_theme!(&self.theme_colors, outline_button),
208            ButtonStyleVariant::Filled => get_theme!(&self.theme_colors, filled_button),
209        };
210        let theme_layout = match self.layout_variant {
211            ButtonLayoutVariant::Normal => get_theme!(&self.theme_layout, button_layout),
212            ButtonLayoutVariant::Compact => get_theme!(&self.theme_layout, compact_button_layout),
213            ButtonLayoutVariant::Expanded => get_theme!(&self.theme_layout, expanded_button_layout),
214        };
215
216        let border = if focus_status() == FocusStatus::Keyboard {
217            Border::new()
218                .fill(theme_colors.focus_border_fill)
219                .width(2.)
220                .alignment(BorderAlignment::Inner)
221        } else {
222            Border::new()
223                .fill(theme_colors.border_fill.mul_if(!self.enabled, 0.9))
224                .width(1.)
225                .alignment(BorderAlignment::Inner)
226        };
227        let background = if enabled() && hovering() {
228            theme_colors.hover_background
229        } else {
230            theme_colors.background
231        };
232
233        rect()
234            .a11y_id(focus.a11y_id())
235            .a11y_focusable(self.enabled)
236            .a11y_role(AccessibilityRole::Button)
237            .background(background.mul_if(!self.enabled, 0.9))
238            .border(border)
239            .padding(theme_layout.padding)
240            .corner_radius(theme_layout.corner_radius)
241            .width(theme_layout.width)
242            .height(theme_layout.height)
243            .color(theme_colors.color.mul_if(!self.enabled, 0.9))
244            .center()
245            .maybe(self.enabled, |rect| {
246                rect.on_all_press({
247                    let on_press = self.on_press.clone();
248                    let on_secondary_press = self.on_secondary_press.clone();
249                    move |e: Event<PressEventData>| {
250                        focus.request_focus();
251                        if let PressEventData::Mouse(data) = e.data() {
252                            match (data.button, &on_press, &on_secondary_press) {
253                                (Some(MouseButton::Left), Some(on_press), _) => on_press.call(e),
254                                (Some(MouseButton::Right), _, Some(on_secondary_press)) => {
255                                    on_secondary_press.call(e)
256                                }
257                                _ => {}
258                            }
259                        }
260                    }
261                })
262            })
263            .on_pointer_enter(move |_| {
264                hovering.set(true);
265                if enabled() {
266                    Cursor::set(CursorIcon::Pointer);
267                } else {
268                    Cursor::set(CursorIcon::NotAllowed);
269                }
270            })
271            .on_pointer_leave(move |_| {
272                if hovering() {
273                    Cursor::set(CursorIcon::default());
274                    hovering.set(false);
275                }
276            })
277            .children(self.elements.clone())
278    }
279
280    fn render_key(&self) -> DiffKey {
281        self.key.clone().or(self.default_key())
282    }
283}