1use super::internal::checkbox_indicator_presenter::{
2 create_default_checkbox_indicator_presenter, CheckboxIndicatorPresenter,
3 CheckboxIndicatorTemplate, CheckboxIndicatorVisualState,
4};
5use super::internal::pressable_labeled_control::{
6 PressableLabeledControlState, WeakPressableLabeledControl,
7};
8use super::*;
9use std::cell::{Cell, RefCell};
10use std::rc::Rc;
11
12fn create_indicator_presenter(
13 template: Option<Rc<dyn CheckboxIndicatorTemplate>>,
14 sizing: Option<LabeledControlSizing>,
15) -> Rc<dyn CheckboxIndicatorPresenter> {
16 if let Some(template) = template {
17 return template.create(sizing);
18 }
19 create_default_checkbox_indicator_presenter(sizing)
20}
21
22#[derive(Clone)]
23pub struct Checkbox {
24 base: PressableLabeledControl,
25 root: FlexBox,
26 indicator_presenter: Rc<RefCell<Rc<dyn CheckboxIndicatorPresenter>>>,
27 template_override: Rc<RefCell<Option<Rc<dyn CheckboxIndicatorTemplate>>>>,
28 sizing_value: Rc<Cell<Option<LabeledControlSizing>>>,
29 state: Rc<Cell<CheckState>>,
30 tri_state: Rc<Cell<bool>>,
31 colors_value: Rc<Cell<Option<LabeledControlColors>>>,
32 changed: Rc<RefCell<Option<CheckboxChangedCallback>>>,
33 weak_root: Rc<WeakNodeRef>,
34}
35
36impl Checkbox {
37 pub fn new(label: impl Into<String>) -> Self {
38 let label = label.into();
39 let indicator_presenter = create_indicator_presenter(None, None);
40 let base =
41 PressableLabeledControl::new(SemanticRole::Checkbox, label, indicator_presenter.root());
42 let root = base.root();
43 let weak_root = Rc::new(root.node_ref().downgrade());
44 let control = Self {
45 base,
46 root,
47 indicator_presenter: Rc::new(RefCell::new(indicator_presenter)),
48 template_override: Rc::new(RefCell::new(None)),
49 sizing_value: Rc::new(Cell::new(None)),
50 state: Rc::new(Cell::new(CheckState::False)),
51 tri_state: Rc::new(Cell::new(false)),
52 colors_value: Rc::new(Cell::new(None)),
53 changed: Rc::new(RefCell::new(None)),
54 weak_root,
55 };
56 control.root.semantic_checked(SemanticCheckedState::False);
57 let activation = CheckboxEventTarget::from_checkbox(&control);
58 control.base.set_activation(move |state| {
59 activation.activate(state);
60 });
61 let state_sync = CheckboxEventTarget::from_checkbox(&control);
62 control.base.set_state_changed(move |state| {
63 state_sync.sync_visual(state, false, false);
64 });
65 let snapshot = control.base.state_snapshotter();
66 let state = control.state.clone();
67 let tri_state = control.tri_state.clone();
68 let weak_root = control.weak_root.clone();
69 let presenter = control.indicator_presenter.clone();
70 let colors_value = control.colors_value.clone();
71 let changed = control.changed.clone();
72 control.persist_state(crate::persisted::persisted_value_adapter(
73 "checkbox-checked-state",
74 crate::persisted::PersistedInt32Codec,
75 1,
76 {
77 let state = state.clone();
78 move || {
79 Some(match state.get() {
80 CheckState::False => 0,
81 CheckState::True => 1,
82 CheckState::Mixed => 2,
83 })
84 }
85 },
86 move |value| {
87 let next = match value {
88 1 => CheckState::True,
89 2 => CheckState::Mixed,
90 _ => CheckState::False,
91 };
92 let effective = if !tri_state.get() && next == CheckState::Mixed {
93 CheckState::False
94 } else {
95 next
96 };
97 apply_checkbox_state(
98 &presenter,
99 &weak_root,
100 &state,
101 effective,
102 true,
103 false,
104 &changed,
105 colors_value.get(),
106 snapshot(),
107 );
108 },
109 ));
110 control.sync_visual(control.base.snapshot_state(), false, false);
111 control
112 }
113
114 pub fn checked(&self, checked: bool) -> &Self {
115 self.check(checked)
116 }
117
118 pub fn check(&self, checked: bool) -> &Self {
119 self.set_state(
120 if checked {
121 CheckState::True
122 } else {
123 CheckState::False
124 },
125 true,
126 false,
127 );
128 self
129 }
130
131 pub fn mixed(&self, mixed: bool) -> &Self {
132 if !self.tri_state.get() {
133 return self;
134 }
135 self.set_state(
136 if mixed {
137 CheckState::Mixed
138 } else {
139 CheckState::False
140 },
141 true,
142 false,
143 );
144 self
145 }
146
147 pub fn check_state(&self, state: CheckState) -> &Self {
148 self.set_state(state, true, false);
149 self
150 }
151
152 pub fn tri_state(&self, enabled: bool) -> &Self {
153 self.tri_state.set(enabled);
154 if !enabled && self.state.get() == CheckState::Mixed {
155 self.set_state(CheckState::False, true, false);
156 }
157 self
158 }
159
160 pub fn sizing(&self, sizing: LabeledControlSizing) -> &Self {
161 self.set_sizing(Some(sizing))
162 }
163
164 pub fn clear_sizing(&self) -> &Self {
165 self.set_sizing(None)
166 }
167
168 fn set_sizing(&self, sizing: Option<LabeledControlSizing>) -> &Self {
169 self.sizing_value.set(sizing);
170 self.base.set_label_font_size_override(
171 sizing
172 .filter(|sizing| sizing.has_label_font_size())
173 .map(|sizing| sizing.label_font_size_px())
174 .unwrap_or(0.0),
175 );
176 self.replace_indicator_presenter(create_indicator_presenter(
177 self.template_override.borrow().clone(),
178 self.sizing_value.get(),
179 ));
180 self.sync_visual(self.base.snapshot_state(), false, false);
181 self
182 }
183
184 pub fn template(&self, template: Rc<dyn CheckboxIndicatorTemplate>) -> &Self {
185 self.set_template(Some(template))
186 }
187
188 pub fn clear_template(&self) -> &Self {
189 self.set_template(None)
190 }
191
192 fn set_template(&self, template: Option<Rc<dyn CheckboxIndicatorTemplate>>) -> &Self {
193 self.template_override.replace(template.clone());
194 self.replace_indicator_presenter(create_indicator_presenter(
195 template,
196 self.sizing_value.get(),
197 ));
198 self.sync_visual(self.base.snapshot_state(), false, false);
199 self
200 }
201
202 pub fn colors(&self, colors: LabeledControlColors) -> &Self {
203 self.set_colors(Some(colors))
204 }
205
206 pub fn clear_colors(&self) -> &Self {
207 self.set_colors(None)
208 }
209
210 fn set_colors(&self, colors: Option<LabeledControlColors>) -> &Self {
211 self.colors_value.set(colors);
212 self.base.colors(colors);
213 self.sync_visual(self.base.snapshot_state(), false, false);
214 self
215 }
216
217 pub fn on_changed(&self, handler: impl Fn(CheckboxChangedEventArgs) + 'static) -> &Self {
218 *self.changed.borrow_mut() = Some(Rc::new(handler));
219 self
220 }
221
222 pub fn checked_state(&self) -> CheckState {
223 self.state.get()
224 }
225
226 pub fn state(&self) -> CheckState {
227 self.checked_state()
228 }
229
230 pub fn is_checked(&self) -> bool {
231 self.state.get().is_checked()
232 }
233
234 pub fn enabled(&self, enabled: bool) -> &Self {
235 self.base.enabled(enabled);
236 self
237 }
238
239 #[cfg(test)]
240 pub(crate) fn test_parts(&self) -> (FlexBox, FlexBox, FlexBox, FlexBox) {
241 self.base.test_parts()
242 }
243
244 fn replace_indicator_presenter(&self, next_presenter: Rc<dyn CheckboxIndicatorPresenter>) {
245 let previous = self.indicator_presenter.borrow().clone();
246 if Rc::ptr_eq(&previous, &next_presenter) {
247 return;
248 }
249 self.base.replace_indicator_root(next_presenter.root());
250 self.indicator_presenter.replace(next_presenter);
251 }
252
253 fn set_state(&self, state: CheckState, emit: bool, announce: bool) {
254 let effective = if !self.tri_state.get() && state == CheckState::Mixed {
255 CheckState::False
256 } else {
257 state
258 };
259 apply_checkbox_state(
260 &self.indicator_presenter,
261 &self.weak_root,
262 &self.state,
263 effective,
264 emit,
265 announce,
266 &self.changed,
267 self.colors_value.get(),
268 self.base.snapshot_state(),
269 );
270 }
271
272 fn sync_visual(&self, base_state: PressableLabeledControlState, emit: bool, announce: bool) {
273 apply_checkbox_state(
274 &self.indicator_presenter,
275 &self.weak_root,
276 &self.state,
277 self.state.get(),
278 emit,
279 announce,
280 &self.changed,
281 self.colors_value.get(),
282 base_state,
283 );
284 }
285}
286
287impl Clickable for Checkbox {
288 fn on_click(&self, handler: impl Fn(ClickEventArgs) + 'static) -> &Self {
289 self.base.on_click(handler);
290 self
291 }
292}
293
294impl LabeledControlTextStyle for Checkbox {
295 fn set_label_font_family(&self, family: crate::FontFamily) {
296 self.base.font_family(family);
297 }
298
299 fn set_label_font_size(&self, size: f32) {
300 self.base.font_size(size);
301 }
302
303 fn set_label_text_color(&self, color: u32) {
304 self.base.text_color(color);
305 }
306}
307
308impl Node for Checkbox {
309 fn retained_node_ref(&self) -> NodeRef {
310 self.root.retained_node_ref()
311 }
312
313 fn build_self(&self) {
314 self.sync_visual(self.base.snapshot_state(), false, false);
315 self.root.build_self();
316 }
317}
318
319impl HasFlexBoxRoot for Checkbox {
320 fn flex_box_root(&self) -> &FlexBox {
321 &self.root
322 }
323}
324
325impl ThemeBindable for Checkbox {
326 fn theme_binding_node(&self) -> NodeRef {
327 self.root.retained_node_ref()
328 }
329
330 fn weak_theme_target(&self) -> Box<dyn Fn() -> Option<Self>> {
331 let target = CheckboxEventTarget::from_checkbox(self);
332 Box::new(move || target.upgrade())
333 }
334}
335
336#[derive(Clone)]
337struct CheckboxEventTarget {
338 base: WeakPressableLabeledControl,
339 presenter: Rc<RefCell<Rc<dyn CheckboxIndicatorPresenter>>>,
340 template_override: Rc<RefCell<Option<Rc<dyn CheckboxIndicatorTemplate>>>>,
341 sizing_value: Rc<Cell<Option<LabeledControlSizing>>>,
342 state: Rc<Cell<CheckState>>,
343 tri_state: Rc<Cell<bool>>,
344 colors_value: Rc<Cell<Option<LabeledControlColors>>>,
345 changed: Rc<RefCell<Option<CheckboxChangedCallback>>>,
346 weak_root: Rc<WeakNodeRef>,
347}
348
349impl CheckboxEventTarget {
350 fn from_checkbox(checkbox: &Checkbox) -> Self {
351 Self {
352 base: checkbox.base.downgrade(),
353 presenter: checkbox.indicator_presenter.clone(),
354 template_override: checkbox.template_override.clone(),
355 sizing_value: checkbox.sizing_value.clone(),
356 state: checkbox.state.clone(),
357 tri_state: checkbox.tri_state.clone(),
358 colors_value: checkbox.colors_value.clone(),
359 changed: checkbox.changed.clone(),
360 weak_root: checkbox.weak_root.clone(),
361 }
362 }
363
364 fn upgrade(&self) -> Option<Checkbox> {
365 let base = self.base.upgrade()?;
366 Some(Checkbox {
367 root: base.root(),
368 base,
369 indicator_presenter: self.presenter.clone(),
370 template_override: self.template_override.clone(),
371 sizing_value: self.sizing_value.clone(),
372 state: self.state.clone(),
373 tri_state: self.tri_state.clone(),
374 colors_value: self.colors_value.clone(),
375 changed: self.changed.clone(),
376 weak_root: self.weak_root.clone(),
377 })
378 }
379
380 fn activate(&self, base_state: PressableLabeledControlState) {
381 let next = match (self.state.get(), self.tri_state.get()) {
382 (CheckState::False, _) => CheckState::True,
383 (CheckState::True, true) => CheckState::Mixed,
384 (CheckState::Mixed, _) | (CheckState::True, false) => CheckState::False,
385 };
386 apply_checkbox_state(
387 &self.presenter,
388 &self.weak_root,
389 &self.state,
390 next,
391 true,
392 true,
393 &self.changed,
394 self.colors_value.get(),
395 base_state,
396 );
397 }
398
399 fn sync_visual(&self, base_state: PressableLabeledControlState, emit: bool, announce: bool) {
400 apply_checkbox_state(
401 &self.presenter,
402 &self.weak_root,
403 &self.state,
404 self.state.get(),
405 emit,
406 announce,
407 &self.changed,
408 self.colors_value.get(),
409 base_state,
410 );
411 }
412}
413
414#[allow(clippy::too_many_arguments)]
415fn apply_checkbox_state(
416 presenter: &Rc<RefCell<Rc<dyn CheckboxIndicatorPresenter>>>,
417 weak_root: &Rc<WeakNodeRef>,
418 state_cell: &Rc<Cell<CheckState>>,
419 state: CheckState,
420 emit: bool,
421 announce: bool,
422 changed: &Rc<RefCell<Option<CheckboxChangedCallback>>>,
423 colors: Option<LabeledControlColors>,
424 base_state: PressableLabeledControlState,
425) {
426 let previous = state_cell.get();
427 state_cell.set(state);
428 update_semantic_checked(weak_root, state.semantic(), announce && previous != state);
429 presenter.borrow().apply(
430 current_theme(),
431 CheckboxIndicatorVisualState::new(
432 state.semantic(),
433 base_state.hovered,
434 base_state.pressed,
435 base_state.focused,
436 base_state.enabled,
437 ),
438 colors,
439 );
440 if emit && previous != state {
441 if let Some(callback) = changed.borrow().clone() {
442 callback(CheckboxChangedEventArgs {
443 state,
444 checked: state.is_checked(),
445 });
446 }
447 }
448}