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}