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}