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