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}