Skip to main content

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