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