vizia_core/views/checkbox.rs
1use crate::icons::{ICON_CHECK, ICON_MINUS};
2use crate::prelude::*;
3
4/// A checkbox used to display and toggle a boolean state.
5///
6/// Pressing the checkbox triggers the [`on_toggle`](Checkbox::on_toggle) callback.
7///
8/// # Examples
9///
10/// ## Basic checkbox
11///
12/// The checkbox takes a boolean signal (for example `Signal<bool>`).
13///
14/// ```
15/// # use vizia_core::prelude::*;
16/// #
17/// #
18/// # struct AppData {
19/// # value: bool,
20/// # }
21/// #
22/// # impl Model for AppData {}
23/// #
24/// # let cx = &mut Context::default();
25/// #
26/// # let value = Signal::new(false);
27/// #
28/// Checkbox::new(cx, value);
29/// ```
30///
31/// ## Checkbox with an action
32///
33/// A checkbox can be used to trigger a callback when toggled. Usually this updates
34/// the underlying boolean state.
35///
36/// ```
37/// # use vizia_core::prelude::*;
38/// #
39/// #
40/// # struct AppData {
41/// # value: bool,
42/// # }
43/// #
44/// # impl Model for AppData {}
45/// #
46/// # enum AppEvent {
47/// # ToggleValue,
48/// # }
49/// #
50/// # let cx = &mut Context::default();
51/// #
52/// # let value = Signal::new(false);
53/// #
54/// Checkbox::new(cx, value)
55/// .on_toggle(|cx| cx.emit(AppEvent::ToggleValue));
56/// ```
57///
58/// ## Checkbox with a label
59///
60/// A checkbox is usually used with a label next to it describing what state the checkbox
61/// controls or what the checkbox does when pressed. This can be done, for example, by
62/// wrapping the checkbox in an [`HStack`](crate::prelude::HStack) and adding a [`Label`](crate::prelude::Label)
63/// to it.
64///
65/// The Label can be used to trigger the checkbox by assigning the checkbox an id name and using it with the `describing` modifier on the label.
66///
67/// ```
68/// # use vizia_core::prelude::*;
69/// #
70/// #
71/// # struct AppData {
72/// # value: bool,
73/// # }
74/// #
75/// # impl Model for AppData {}
76/// #
77/// # let cx = &mut Context::default();
78/// #
79/// # let value = Signal::new(false);
80/// #
81/// HStack::new(cx, |cx| {
82/// Checkbox::new(cx, value)
83/// .id("check1");
84/// Label::new(cx, "Press me")
85/// .describing("check1");
86/// });
87/// ```
88///
89/// ## Custom checkbox
90///
91/// The `with_icons` constructor can be used to create a checkbox with custom icons for both checked and unchecked states.
92///
93/// ```
94/// # use vizia_core::prelude::*;
95/// #
96/// #
97/// # struct AppData {
98/// # value: bool,
99/// # }
100/// #
101/// # impl Model for AppData {}
102/// #
103/// # enum AppEvent {
104/// # ToggleValue,
105/// # }
106/// #
107/// # let cx = &mut Context::default();
108/// #
109/// # let value = Signal::new(false);
110/// # use vizia_core::icons::ICON_X;
111///
112/// Checkbox::with_icons(cx, value, Some(""), Some(ICON_X))
113/// .on_toggle(|cx| cx.emit(AppEvent::ToggleValue));
114/// ```
115pub struct Checkbox {
116 on_toggle: Option<Box<dyn Fn(&mut EventContext)>>,
117}
118
119impl Checkbox {
120 /// Creates a new checkbox.
121 ///
122 /// # Examples
123 ///
124 /// ```
125 /// # use vizia_core::prelude::*;
126 /// #
127 /// #
128 /// # struct AppData {
129 /// # value: bool,
130 /// # }
131 /// #
132 /// # impl Model for AppData {}
133 /// #
134 /// # let cx = &mut Context::default();
135 /// #
136 /// # let value = Signal::new(false);
137 /// #
138 /// Checkbox::new(cx, value);
139 /// ```
140 pub fn new(cx: &mut Context, checked: impl Res<bool> + Copy + 'static) -> Handle<Self> {
141 Self { on_toggle: None }
142 .build(cx, move |cx| {
143 checked.set_or_bind(cx, |cx, checked| {
144 if checked.get_value(cx) {
145 Svg::new(cx, ICON_CHECK);
146 }
147 });
148 })
149 .checked(checked)
150 .role(Role::CheckBox)
151 .navigable(true)
152 }
153
154 /// Creates a new checkbox with custom icons for both checked and unchecked states.
155 ///
156 /// # Examples
157 ///
158 /// ```
159 /// # use vizia_core::prelude::*;
160 /// #
161 /// #
162 /// # struct AppData {
163 /// # value: bool,
164 /// # }
165 /// #
166 /// # impl Model for AppData {}
167 /// #
168 /// # enum AppEvent {
169 /// # ToggleValue,
170 /// # }
171 /// #
172 /// # let cx = &mut Context::default();
173 /// #
174 /// # let value = Signal::new(false);
175 /// # use vizia_core::icons::ICON_X;
176 ///
177 /// Checkbox::with_icons(cx, value, Some(""), Some(ICON_X))
178 /// .on_toggle(|cx| cx.emit(AppEvent::ToggleValue));
179 /// ```
180 pub fn with_icons<T>(
181 cx: &mut Context,
182 checked: impl Res<bool> + Copy + 'static,
183 icon_default: Option<impl Res<T> + Copy + 'static>,
184 icon_checked: Option<impl Res<T> + Copy + 'static>,
185 ) -> Handle<Self>
186 where
187 T: AsRef<[u8]> + 'static,
188 {
189 Self { on_toggle: None }
190 .build(cx, move |cx| {
191 checked.set_or_bind(cx, move |cx, checked| {
192 if checked.get_value(cx) {
193 if let Some(icon) = icon_checked {
194 Svg::new(cx, icon);
195 }
196 } else if let Some(icon) = icon_default {
197 Svg::new(cx, icon);
198 }
199 });
200 })
201 .checked(checked)
202 .role(Role::CheckBox)
203 .navigable(true)
204 }
205
206 /// Creates a new checkbox in an intermediate state.
207 pub fn intermediate(
208 cx: &mut Context,
209 checked: impl Res<bool> + Clone + 'static,
210 intermediate: impl Res<bool> + Clone + 'static,
211 ) -> Handle<Self> {
212 let checked_state_for_icon = checked.clone().to_signal(cx);
213 let intermediate_state_for_icon = intermediate.clone().to_signal(cx);
214 let checked_state_for_class = checked.clone().to_signal(cx);
215 let intermediate_state_for_class = intermediate.clone().to_signal(cx);
216
217 let is_intermediate_memo = Memo::new(move |_| {
218 let checked = checked_state_for_class.get();
219 let intermediate = intermediate_state_for_class.get();
220 !checked && intermediate
221 });
222
223 Self { on_toggle: None }
224 .build(cx, move |cx| {
225 let icon_memo = Memo::new(move |_| {
226 if checked_state_for_icon.get() {
227 Some(ICON_CHECK)
228 } else if intermediate_state_for_icon.get() {
229 Some(ICON_MINUS)
230 } else {
231 None
232 }
233 });
234
235 Binding::new(cx, icon_memo, move |cx| {
236 if let Some(icon) = icon_memo.get() {
237 Svg::new(cx, icon);
238 }
239 });
240 })
241 .toggle_class("intermediate", is_intermediate_memo)
242 .checked(checked)
243 .navigable(true)
244 }
245}
246
247impl Handle<'_, Checkbox> {
248 /// Set the callback triggered when the checkbox is pressed.
249 ///
250 /// # Examples
251 ///
252 /// ```
253 /// # use vizia_core::prelude::*;
254 /// #
255 /// #
256 /// # struct AppData {
257 /// # value: bool,
258 /// # }
259 /// #
260 /// # impl Model for AppData {}
261 /// #
262 /// # enum AppEvent {
263 /// # ToggleValue,
264 /// # }
265 /// #
266 /// # let cx = &mut Context::default();
267 /// #
268 /// # let value = Signal::new(false);
269 /// #
270 /// Checkbox::new(cx, value)
271 /// .on_toggle(|cx| cx.emit(AppEvent::ToggleValue));
272 /// ```
273 pub fn on_toggle<F>(self, callback: F) -> Self
274 where
275 F: 'static + Fn(&mut EventContext),
276 {
277 self.modify(|checkbox| checkbox.on_toggle = Some(Box::new(callback)))
278 }
279}
280
281impl View for Checkbox {
282 fn element(&self) -> Option<&'static str> {
283 Some("checkbox")
284 }
285
286 fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
287 event.map(|window_event, meta| match window_event {
288 WindowEvent::PressDown { mouse: _ } => {
289 if meta.target == cx.current {
290 cx.focus();
291 }
292 }
293
294 WindowEvent::Press { mouse: _ } => {
295 if meta.target == cx.current {
296 if let Some(callback) = &self.on_toggle {
297 (callback)(cx);
298 }
299 }
300 }
301
302 WindowEvent::ActionRequest(action) => match action.action {
303 Action::Click => {
304 if let Some(callback) = &self.on_toggle {
305 (callback)(cx);
306 }
307 }
308
309 _ => {}
310 },
311
312 _ => {}
313 });
314 }
315}