Skip to main content

vizia_core/views/
checkbox.rs

1use crate::icons::ICON_CHECK;
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 = checked.clone().to_signal(cx);
213        let intermediate_state = intermediate.to_signal(cx);
214
215        let text_memo = Memo::new(move |_| {
216            if checked_state.get() {
217                ICON_CHECK
218            } else if intermediate_state.get() {
219                "-"
220            } else {
221                ""
222            }
223        });
224
225        let is_intermediate_memo =
226            Memo::new(move |_| !checked_state.get() && intermediate_state.get());
227
228        Self { on_toggle: None }
229            .build(cx, |_| {})
230            .text(text_memo)
231            .toggle_class("intermediate", is_intermediate_memo)
232            .checked(checked)
233            .navigable(true)
234    }
235}
236
237impl Handle<'_, Checkbox> {
238    /// Set the callback triggered when the checkbox is pressed.
239    ///
240    /// # Examples
241    ///
242    /// ```
243    /// # use vizia_core::prelude::*;
244    /// #
245    /// #
246    /// # struct AppData {
247    /// #     value: bool,
248    /// # }
249    /// #
250    /// # impl Model for AppData {}
251    /// #
252    /// # enum AppEvent {
253    /// #     ToggleValue,
254    /// # }
255    /// #
256    /// # let cx = &mut Context::default();
257    /// #
258    /// # let value = Signal::new(false);
259    /// #
260    /// Checkbox::new(cx, value)
261    ///     .on_toggle(|cx| cx.emit(AppEvent::ToggleValue));
262    /// ```
263    pub fn on_toggle<F>(self, callback: F) -> Self
264    where
265        F: 'static + Fn(&mut EventContext),
266    {
267        self.modify(|checkbox| checkbox.on_toggle = Some(Box::new(callback)))
268    }
269}
270
271impl View for Checkbox {
272    fn element(&self) -> Option<&'static str> {
273        Some("checkbox")
274    }
275
276    fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
277        event.map(|window_event, meta| match window_event {
278            WindowEvent::PressDown { mouse: _ } => {
279                if meta.target == cx.current {
280                    cx.focus();
281                }
282            }
283
284            WindowEvent::Press { mouse: _ } => {
285                if meta.target == cx.current {
286                    if let Some(callback) = &self.on_toggle {
287                        (callback)(cx);
288                    }
289                }
290            }
291
292            WindowEvent::ActionRequest(action) => match action.action {
293                Action::Click => {
294                    if let Some(callback) = &self.on_toggle {
295                        (callback)(cx);
296                    }
297                }
298
299                _ => {}
300            },
301
302            _ => {}
303        });
304    }
305}