Skip to main content

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}