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 must be bound to some boolean data.
13///
14/// ```
15/// # use vizia_core::prelude::*;
16/// #
17/// # #[derive(Lens)]
18/// # struct AppData {
19/// #     value: bool,
20/// # }
21/// #
22/// # impl Model for AppData {}
23/// #
24/// # let cx = &mut Context::default();
25/// #
26/// # AppData { value: false }.build(cx);
27/// #
28/// Checkbox::new(cx, AppData::value);
29/// ```
30///
31/// ## Checkbox with an action
32///
33/// A checkbox can be used to trigger a callback when toggled. Usually this is emitting an
34/// event responsible for changing the data the checkbox is bound to.
35///
36/// ```
37/// # use vizia_core::prelude::*;
38/// #
39/// # #[derive(Lens)]
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/// # AppData { value: false }.build(cx);
53/// #
54/// Checkbox::new(cx, AppData::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 data the checkbox
61/// is bound to 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/// # #[derive(Lens)]
71/// # struct AppData {
72/// #     value: bool,
73/// # }
74/// #
75/// # impl Model for AppData {}
76/// #
77/// # let cx = &mut Context::default();
78/// #
79/// # AppData { value: false }.build(cx);
80/// #
81/// HStack::new(cx, |cx| {
82///     Checkbox::new(cx, AppData::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/// # #[derive(Lens)]
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/// # AppData { value: false }.build(cx);
110/// # use vizia_core::icons::ICON_X;
111///
112/// Checkbox::with_icons(cx, AppData::value, None, 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    /// # #[derive(Lens)]
128    /// # struct AppData {
129    /// #     value: bool,
130    /// # }
131    /// #
132    /// # impl Model for AppData {}
133    /// #
134    /// # let cx = &mut Context::default();
135    /// #
136    /// # AppData { value: false }.build(cx);
137    /// #
138    /// Checkbox::new(cx, AppData::value);
139    /// ```
140    pub fn new(cx: &mut Context, checked: impl Lens<Target = bool>) -> Handle<Self> {
141        Self { on_toggle: None }
142            .build(cx, |cx| {
143                Binding::new(cx, checked, |cx, checked| {
144                    if checked.get(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    /// # #[derive(Lens)]
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    /// # AppData { value: false }.build(cx);
175    /// # use vizia_core::icons::ICON_X;
176    ///
177    /// Checkbox::with_icons(cx, AppData::value, None, 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 Lens<Target = bool>,
183        icon_default: Option<impl Res<T> + 'static + Clone>,
184        icon_checked: Option<impl Res<T> + 'static + Clone>,
185    ) -> Handle<Self>
186    where
187        T: AsRef<[u8]> + 'static,
188    {
189        Self { on_toggle: None }
190            .build(cx, |cx| {
191                Binding::new(cx, checked, move |cx, checked| {
192                    let icon_default = icon_default.clone();
193                    let icon_checked = icon_checked.clone();
194                    if checked.get(cx) {
195                        if let Some(icon) = icon_checked {
196                            Svg::new(cx, icon);
197                        }
198                    } else if let Some(icon) = icon_default {
199                        Svg::new(cx, icon);
200                    }
201                })
202            })
203            .checked(checked)
204            .role(Role::CheckBox)
205            .navigable(true)
206    }
207
208    /// Creates a new checkbox in an intermediate state.
209    pub fn intermediate(
210        cx: &mut Context,
211        checked: impl Lens<Target = bool>,
212        intermediate: impl Lens<Target = bool>,
213    ) -> Handle<Self> {
214        Self { on_toggle: None }
215            .build(cx, |_| {})
216            .bind(checked, move |handle, c| {
217                handle.bind(intermediate, move |handle, i| {
218                    if c.get(&handle) {
219                        handle.text(ICON_CHECK).toggle_class("intermediate", false);
220                    } else if i.get(&handle) {
221                        handle.text("-").toggle_class("intermediate", true);
222                    } else {
223                        handle.text("").toggle_class("intermediate", false);
224                    }
225                });
226            })
227            .checked(checked)
228            .navigable(true)
229    }
230}
231
232impl Handle<'_, Checkbox> {
233    /// Set the callback triggered when the checkbox is pressed.
234    ///
235    /// # Examples
236    ///
237    /// ```
238    /// # use vizia_core::prelude::*;
239    /// #
240    /// # #[derive(Lens)]
241    /// # struct AppData {
242    /// #     value: bool,
243    /// # }
244    /// #
245    /// # impl Model for AppData {}
246    /// #
247    /// # enum AppEvent {
248    /// #     ToggleValue,
249    /// # }
250    /// #
251    /// # let cx = &mut Context::default();
252    /// #
253    /// # AppData { value: false }.build(cx);
254    /// #
255    /// Checkbox::new(cx, AppData::value)
256    ///     .on_toggle(|cx| cx.emit(AppEvent::ToggleValue));
257    /// ```
258    pub fn on_toggle<F>(self, callback: F) -> Self
259    where
260        F: 'static + Fn(&mut EventContext),
261    {
262        self.modify(|checkbox| checkbox.on_toggle = Some(Box::new(callback)))
263    }
264}
265
266impl View for Checkbox {
267    fn element(&self) -> Option<&'static str> {
268        Some("checkbox")
269    }
270
271    fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
272        event.map(|window_event, meta| match window_event {
273            WindowEvent::PressDown { mouse: _ } => {
274                if meta.target == cx.current {
275                    cx.focus();
276                }
277            }
278
279            WindowEvent::Press { mouse: _ } => {
280                if meta.target == cx.current {
281                    if let Some(callback) = &self.on_toggle {
282                        (callback)(cx);
283                    }
284                }
285            }
286
287            WindowEvent::ActionRequest(action) => match action.action {
288                Action::Click => {
289                    if let Some(callback) = &self.on_toggle {
290                        (callback)(cx);
291                    }
292                }
293
294                _ => {}
295            },
296
297            _ => {}
298        });
299    }
300}