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