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}