vizia_core/views/
button.rs

1use crate::prelude::*;
2
3/// A simple push button with a contained view.
4///
5/// # Examples
6///
7/// ## Button with an action
8///
9/// A button can be used to call an action when interacted with. Usually this is an
10/// event that is being emitted when the button is [pressed](crate::modifiers::ActionModifiers::on_press).
11///
12/// ```
13/// # use vizia_core::prelude::*;
14/// #
15/// # enum AppEvent {
16/// #     Action,
17/// # }
18/// #
19/// # let cx = &mut Context::default();
20/// #
21/// Button::new(cx, |cx| Label::new(cx, "Text"))
22///     .on_press(|ex| ex.emit(AppEvent::Action))
23/// ```
24///
25/// ## Button without an action
26///
27/// A button can be used without an action and therefore do nothing when pressed.
28/// This is useful for prototyping and testing out the different styling options of
29/// a button without having to add an action.
30///
31/// ```
32/// # use vizia_core::prelude::*;
33/// #
34/// # let cx = &mut Context::default();
35/// #
36/// Button::new(cx, |cx| Label::new(cx, "Text"));
37/// ```
38///
39/// ## Button containing multiple views
40///
41/// A button can contain more than just a single view or label inside of it. This can
42/// for example be done by using a [`HStack`](crate::prelude::HStack) or [`VStack`](crate::prelude::VStack).
43///
44/// ```
45/// # use vizia_core::prelude::*;
46/// #
47/// # let cx = &mut Context::default();
48/// #
49/// Button::new(
50///     cx,
51///     |cx| {
52///         HStack::new(cx, |cx| {
53///             Label::new(cx, "Hello");
54///             Label::new(cx, "World");
55///         })
56///     },
57/// );
58/// ```
59///
60/// # Button Variants
61///
62/// The style of a button can be modified using the [`variant`](ButtonModifiers::variant) modifier from the [`ButtonModifiers`] trait
63/// by specifying the [`ButtonVariant`].
64///
65/// ```
66/// # use vizia_core::prelude::*;
67/// #
68/// # let cx = &mut Context::default();
69/// #
70/// Button::new(cx, |cx| Label::new(cx, "Text"))
71///     .variant(ButtonVariant::Accent);
72/// ```
73pub struct Button {
74    pub(crate) action: Option<Box<dyn Fn(&mut EventContext)>>,
75}
76
77impl Button {
78    /// Creates a new button with specified content.
79    ///
80    /// # Example
81    /// ```
82    /// # use vizia_core::prelude::*;
83    /// #
84    /// # let cx = &mut Context::default();
85    /// #
86    /// Button::new(cx, |cx| Label::new(cx, "Press Me"));
87    /// ```
88    pub fn new<C, V>(cx: &mut Context, content: C) -> Handle<Self>
89    where
90        C: FnOnce(&mut Context) -> Handle<V>,
91        V: View,
92    {
93        Self { action: None }
94            .build(cx, move |cx| {
95                (content)(cx).hoverable(false);
96            })
97            .role(Role::Button)
98            .navigable(true)
99    }
100}
101
102impl View for Button {
103    fn element(&self) -> Option<&'static str> {
104        Some("button")
105    }
106
107    fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
108        event.map(|window_event, meta| match window_event {
109            WindowEvent::PressDown { mouse: _ } => {
110                if meta.target == cx.current {
111                    cx.focus();
112                }
113            }
114
115            WindowEvent::Press { .. } => {
116                if meta.target == cx.current {
117                    if let Some(action) = &self.action {
118                        (action)(cx);
119                    }
120                }
121            }
122
123            WindowEvent::ActionRequest(action) => match action.action {
124                Action::Click => {
125                    if let Some(action) = &self.action {
126                        (action)(cx);
127                    }
128                }
129
130                _ => {}
131            },
132
133            _ => {}
134        });
135    }
136}
137
138/// Used in conjunction with the [`variant`](ButtonModifiers::variant) modifier for selecting the style variant of a button or button group.
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140pub enum ButtonVariant {
141    /// A normal button.
142    Normal,
143    /// A button with an accent color.
144    Accent,
145    /// A button with just a border.
146    Outline,
147    /// A button with just text.
148    Text,
149}
150
151/// Modifiers for changing the appearance of buttons.
152pub trait ButtonModifiers {
153    /// Selects the style variant to be used by the button or button group.
154    ///
155    /// # Example
156    /// ```
157    /// # use vizia_core::prelude::*;
158    /// #
159    /// #
160    /// # let cx = &mut Context::default();
161    /// #
162    /// Button::new(cx, |cx| Label::new(cx, "Text"))
163    ///     .variant(ButtonVariant::Accent);
164    /// ```
165    fn variant<U: Into<ButtonVariant>>(self, variant: impl Res<U>) -> Self;
166}
167
168impl ButtonModifiers for Handle<'_, Button> {
169    fn variant<U: Into<ButtonVariant>>(self, variant: impl Res<U>) -> Self {
170        self.bind(variant, |handle, val| {
171            let var: ButtonVariant = val.get(&handle).into();
172            match var {
173                ButtonVariant::Normal => {
174                    handle
175                        .toggle_class("accent", false)
176                        .toggle_class("outline", false)
177                        .toggle_class("text", false);
178                }
179
180                ButtonVariant::Accent => {
181                    handle
182                        .toggle_class("accent", true)
183                        .toggle_class("outline", false)
184                        .toggle_class("text", false);
185                }
186
187                ButtonVariant::Outline => {
188                    handle
189                        .toggle_class("accent", false)
190                        .toggle_class("outline", true)
191                        .toggle_class("text", false);
192                }
193
194                ButtonVariant::Text => {
195                    handle
196                        .toggle_class("accent", false)
197                        .toggle_class("outline", false)
198                        .toggle_class("text", true);
199                }
200            }
201        })
202    }
203}
204
205/// A view which represents a group of buttons.
206pub struct ButtonGroup {}
207
208impl ButtonGroup {
209    /// Creates a new button group.
210    ///
211    /// # Example
212    /// ```
213    /// # use vizia_core::prelude::*;
214    ///
215    /// # let cx = &mut Context::default();
216    ///
217    /// ButtonGroup::new(cx, |cx| {
218    ///     Button::new(cx, |cx| Label::new(cx, "ONE"));
219    ///     Button::new(cx, |cx| Label::new(cx, "TWO"));
220    ///     Button::new(cx, |cx| Label::new(cx, "THREE"));
221    /// });
222    /// ```
223    pub fn new<C>(cx: &mut Context, content: C) -> Handle<Self>
224    where
225        C: FnOnce(&mut Context),
226    {
227        Self {}.build(cx, |cx| {
228            (content)(cx);
229        })
230    }
231}
232
233impl View for ButtonGroup {
234    fn element(&self) -> Option<&'static str> {
235        Some("button-group")
236    }
237}
238
239impl Handle<'_, ButtonGroup> {
240    /// Sets whether the button group is in vertical orientation.
241    pub fn vertical(self, is_vertical: impl Res<bool>) -> Self {
242        self.toggle_class("vertical", is_vertical)
243    }
244}
245
246impl ButtonModifiers for Handle<'_, ButtonGroup> {
247    fn variant<U: Into<ButtonVariant>>(self, variant: impl Res<U>) -> Self {
248        self.bind(variant, |handle, val| {
249            let var: ButtonVariant = val.get(&handle).into();
250            match var {
251                ButtonVariant::Normal => {
252                    handle
253                        .toggle_class("accent", false)
254                        .toggle_class("outline", false)
255                        .toggle_class("text", false);
256                }
257
258                ButtonVariant::Accent => {
259                    handle
260                        .toggle_class("accent", true)
261                        .toggle_class("outline", false)
262                        .toggle_class("text", false);
263                }
264
265                ButtonVariant::Outline => {
266                    handle
267                        .toggle_class("accent", false)
268                        .toggle_class("outline", true)
269                        .toggle_class("text", false);
270                }
271
272                ButtonVariant::Text => {
273                    handle
274                        .toggle_class("accent", false)
275                        .toggle_class("outline", false)
276                        .toggle_class("text", true);
277                }
278            }
279        })
280    }
281}