1use crate::presenters::ContentPresenter;
2use crate::primitives::{TemplateAppliedEventArgs, TemplatedControlImpl, TemplatedControlImplExt, ToggleButton, ToggleButtonImpl};
3use crate::templates::IDataTemplate;
4use crate::{ButtonImpl, Canvas, ContentControlImpl, ContentControlImplExt, Control, ControlImpl, ControlImplExt, Panel};
5use ferroui_base::animation::Transitions;
6use ferroui_base::input::{
7 InputElement, InputElementImpl, PointerEventArgs, PointerPressedEventArgs, PointerReleasedEventArgs,
8};
9use ferroui_base::interactivity::{InteractiveImpl, RoutedEventArgs};
10use ferroui_base::layout::LayoutableImpl;
11use ferroui_base::utilities::math_utilities::{max, min};
12use ferroui_base::{
13 ferro_class, ferro_class_info, ferro_impl_classes, ferro_properties, instantiate, BoxedValue, FerroObjectImpl,
14 FerroProperty, FerroPropertyChangedEventArgs, Point, Ref, StyledElement, StyledElementImpl, StyledProperty, Visual,
15 VisualImpl,
16};
17use std::cell::{Cell, RefCell};
18use std::rc::Rc;
19
20const PC_DRAGGING: &str = ":dragging";
21
22#[repr(C)]
24pub struct ToggleSwitch {
25 base: ToggleButton,
26 knobs_panel: RefCell<Option<Ref<Panel>>>,
27 switch_knob: RefCell<Option<Ref<Panel>>>,
28 knobs_panel_pressed: Cell<bool>,
29 switch_start_point: Cell<Point>,
30 init_left: Cell<f64>,
31 is_dragging: Cell<bool>,
32 off_content_presenter: RefCell<Option<Ref<ContentPresenter>>>,
33 on_content_presenter: RefCell<Option<Ref<ContentPresenter>>>,
34}
35
36ferro_class!(ToggleSwitch: ToggleButton);
37
38ferro_class_info!(ToggleSwitch {
39 new: ToggleSwitch::new,
40 markup: {
41 attributes: [
42 TemplatePart("PART_MovingKnobs", type(Ref<Panel>), IsRequired = true),
43 TemplatePart("PART_OffContentPresenter", type(Ref<ContentPresenter>)),
44 TemplatePart("PART_OnContentPresenter", type(Ref<ContentPresenter>)),
45 TemplatePart("PART_SwitchKnob", type(Ref<Panel>)),
46 PseudoClasses(":dragging"),
47 ],
48 },
49});
50
51ferro_impl_classes!(
52 ToggleSwitch: FerroObjectImpl,
53 StyledElementImpl,
54 VisualImpl,
55 LayoutableImpl,
56 InteractiveImpl,
57 InputElementImpl,
58 ButtonImpl,
59 ToggleButtonImpl
60);
61
62impl ControlImpl for ToggleSwitch {
63 fn on_loaded(this: &Self, e: &RoutedEventArgs) {
64 Self::parent_on_loaded(this, e);
65 this.update_knob_transitions();
66 }
67}
68
69impl TemplatedControlImpl for ToggleSwitch {
70 fn on_apply_template(this: &Self, e: &TemplateAppliedEventArgs) {
71 Self::parent_on_apply_template(this, e);
72
73 *this.switch_knob.borrow_mut() = e.name_scope().find_as::<Panel>("PART_SwitchKnob");
74 let knobs_panel = e.name_scope().get_as::<Panel>("PART_MovingKnobs");
75 *this.knobs_panel.borrow_mut() = Some(knobs_panel.clone());
76
77 let weak = this.to_ref().downgrade();
80 knobs_panel.add_handler(InputElement::pointer_pressed_event(), {
81 let weak = weak.clone();
82 move |_, e: &PointerPressedEventArgs| {
83 if let Some(this) = weak.upgrade() {
84 this.knobs_panel_pointer_pressed(e);
85 }
86 }
87 });
88 knobs_panel.add_handler(InputElement::pointer_released_event(), {
89 let weak = weak.clone();
90 move |_, e: &PointerReleasedEventArgs| {
91 if let Some(this) = weak.upgrade() {
92 this.knobs_panel_pointer_released(e);
93 }
94 }
95 });
96 knobs_panel.add_handler(InputElement::pointer_moved_event(), move |_, e: &PointerEventArgs| {
97 if let Some(this) = weak.upgrade() {
98 this.knobs_panel_pointer_moved(e);
99 }
100 });
101
102 if let Some(is_checked) = this.is_checked() {
103 this.update_knob_pos(is_checked);
104 }
105 }
106}
107
108impl ContentControlImpl for ToggleSwitch {
109 fn register_content_presenter(this: &Self, presenter: &ContentPresenter) -> bool {
110 let mut result = Self::parent_register_content_presenter(this, presenter);
111
112 let name = presenter.name();
113 if name.as_deref() == Some("PART_OnContentPresenter") {
114 *this.on_content_presenter.borrow_mut() = Some(presenter.to_ref());
115 result = true;
116 } else if name.as_deref() == Some("PART_OffContentPresenter") {
117 *this.off_content_presenter.borrow_mut() = Some(presenter.to_ref());
118 result = true;
119 }
120
121 result
122 }
123}
124
125ferro_properties! {
126 impl ToggleSwitch {
127 pub fn off_content_property() -> StyledProperty<Option<BoxedValue>> {
129 let default: BoxedValue = Rc::new("Off".to_string());
130 FerroProperty::register::<ToggleSwitch, _>("OffContent", Some(default))
131 }
132
133 pub fn off_content_template_property() -> StyledProperty<Option<Rc<dyn IDataTemplate>>> {
135 FerroProperty::register::<ToggleSwitch, _>("OffContentTemplate", None)
136 }
137
138 pub fn on_content_property() -> StyledProperty<Option<BoxedValue>> {
140 let default: BoxedValue = Rc::new("On".to_string());
141 FerroProperty::register::<ToggleSwitch, _>("OnContent", Some(default))
142 }
143
144 pub fn on_content_template_property() -> StyledProperty<Option<Rc<dyn IDataTemplate>>> {
146 FerroProperty::register::<ToggleSwitch, _>("OnContentTemplate", None)
147 }
148
149 pub fn knob_transitions_property() -> StyledProperty<Option<Transitions>> {
151 FerroProperty::register::<ToggleSwitch, _>("KnobTransitions", None)
152 }
153 }
154}
155
156impl ToggleSwitch {
157 fn static_constructor() {
158 Self::off_content_property().changed().add_class_handler::<ToggleSwitch>(|x, e| x.off_content_changed(e));
159 Self::on_content_property().changed().add_class_handler::<ToggleSwitch>(|x, e| x.on_content_changed(e));
160 ToggleButton::is_checked_property().changed().add_class_handler::<ToggleSwitch>(|x, e| {
161 if let Some(value) = e.get_new_value::<Option<bool>>() {
162 x.update_knob_pos(value);
163 }
164 });
165
166 Visual::bounds_property().changed().add_class_handler::<ToggleSwitch>(|x, _| {
167 if let Some(is_checked) = x.is_checked() {
168 x.update_knob_pos(is_checked);
169 }
170 });
171 Self::knob_transitions_property().changed().add_class_handler::<ToggleSwitch>(|x, _| {
172 x.update_knob_transitions();
173 });
174 }
175
176 pub fn construct() -> Self {
178 Self {
179 base: ToggleButton::construct(),
180 knobs_panel: RefCell::new(None),
181 switch_knob: RefCell::new(None),
182 knobs_panel_pressed: Cell::new(false),
183 switch_start_point: Cell::new(Point::default()),
184 init_left: Cell::new(-1.0),
185 is_dragging: Cell::new(false),
186 off_content_presenter: RefCell::new(None),
187 on_content_presenter: RefCell::new(None),
188 }
189 }
190
191 pub fn new() -> Ref<Self> {
192 instantiate(Self::construct())
193 }
194
195 pub fn on_content(&self) -> Option<BoxedValue> {
197 self.get_value(Self::on_content_property())
198 }
199
200 pub fn set_on_content(&self, value: Option<BoxedValue>) {
201 self.set_value(Self::on_content_property(), value)
202 }
203
204 pub fn off_content(&self) -> Option<BoxedValue> {
206 self.get_value(Self::off_content_property())
207 }
208
209 pub fn set_off_content(&self, value: Option<BoxedValue>) {
210 self.set_value(Self::off_content_property(), value)
211 }
212
213 pub fn off_content_presenter(&self) -> Option<Ref<ContentPresenter>> {
215 self.off_content_presenter.borrow().clone()
216 }
217
218 pub fn on_content_presenter(&self) -> Option<Ref<ContentPresenter>> {
220 self.on_content_presenter.borrow().clone()
221 }
222
223 pub fn off_content_template(&self) -> Option<Rc<dyn IDataTemplate>> {
225 self.get_value(Self::off_content_template_property())
226 }
227
228 pub fn set_off_content_template(&self, value: Option<Rc<dyn IDataTemplate>>) {
229 self.set_value(Self::off_content_template_property(), value)
230 }
231
232 pub fn on_content_template(&self) -> Option<Rc<dyn IDataTemplate>> {
234 self.get_value(Self::on_content_template_property())
235 }
236
237 pub fn set_on_content_template(&self, value: Option<Rc<dyn IDataTemplate>>) {
238 self.set_value(Self::on_content_template_property(), value)
239 }
240
241 pub fn knob_transitions(&self) -> Option<Transitions> {
243 self.get_value(Self::knob_transitions_property())
244 }
245
246 pub fn set_knob_transitions(&self, value: Option<Transitions>) {
247 self.set_value(Self::knob_transitions_property(), value)
248 }
249
250 fn off_content_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
251 self.logical_content_changed(e);
252 }
253
254 fn on_content_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
255 self.logical_content_changed(e);
256 }
257
258 fn logical_content_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
260 let (old_value, new_value) = e.get_old_and_new_value::<Option<BoxedValue>>();
261
262 if let Some(old_child) = old_value.as_ref().and_then(Control::logical_from_boxed) {
263 StyledElement::logical_children(self).remove(&old_child);
264 }
265
266 if let Some(new_child) = new_value.as_ref().and_then(Control::logical_from_boxed) {
267 StyledElement::logical_children(self).add(new_child);
268 }
269 }
270
271 fn update_knob_transitions(&self) {
272 let knobs_panel = self.knobs_panel.borrow().clone();
273 if let Some(knobs_panel) = knobs_panel {
274 knobs_panel.set_transitions(self.knob_transitions());
275 }
276 }
277
278 fn switch_knob_visual(&self) -> Option<Ref<Visual>> {
279 self.switch_knob.borrow().clone().map(Ref::upcast)
280 }
281
282 fn knobs_panel_pointer_pressed(&self, e: &PointerPressedEventArgs) {
283 let knobs_panel = self.knobs_panel.borrow().clone().expect("the knobs panel raised the event");
284 self.switch_start_point.set(e.get_position(self.switch_knob_visual().as_deref()));
285 self.init_left.set(Canvas::get_left(&knobs_panel));
286 self.is_dragging.set(false);
287 self.knobs_panel_pressed.set(true);
288 }
289
290 fn knobs_panel_pointer_released(&self, e: &PointerReleasedEventArgs) {
291 if self.is_dragging.get() {
292 e.set_handled(true);
293
294 let knobs_panel = self.knobs_panel.borrow().clone().expect("the knobs panel raised the event");
295 let switch_knob = self.switch_knob.borrow().clone().expect("dragging requires the switch knob");
296 let should_become_checked = Canvas::get_left(&knobs_panel) >= (switch_knob.bounds().width / 2.0);
297 knobs_panel.clear_value(Canvas::left_property());
298
299 self.pseudo_classes().set(PC_DRAGGING, false);
300
301 if Some(should_become_checked) == self.is_checked() {
302 self.update_knob_pos(should_become_checked);
303 } else {
304 self.set_current_value(ToggleButton::is_checked_property(), Some(should_become_checked));
305 }
306 self.update_knob_transitions();
307 }
308
309 self.is_dragging.set(false);
310
311 self.knobs_panel_pressed.set(false);
312 }
313
314 fn knobs_panel_pointer_moved(&self, e: &PointerEventArgs) {
315 if self.knobs_panel_pressed.get() {
316 let difference = e.get_position(self.switch_knob_visual().as_deref()) - self.switch_start_point.get();
317
318 if !self.is_dragging.get() && difference.x.abs() > 3.0 {
319 let knobs_panel = self.knobs_panel.borrow().clone();
320 if let Some(knobs_panel) = knobs_panel {
321 knobs_panel.set_transitions(None);
322 }
323 self.is_dragging.set(true);
324 self.pseudo_classes().set(PC_DRAGGING, true);
325 }
326
327 if self.is_dragging.get() {
328 let knobs_panel = self.knobs_panel.borrow().clone().expect("the knobs panel raised the event");
329 let switch_knob = self.switch_knob.borrow().clone().expect("dragging requires the switch knob");
330 Canvas::set_left(
333 &knobs_panel,
334 min(switch_knob.bounds().width, max(0.0, self.init_left.get() + difference.x)),
335 );
336 }
337 }
338 }
339
340 fn update_knob_pos(&self, value: bool) {
341 let switch_knob = self.switch_knob.borrow().clone();
342 let knobs_panel = self.knobs_panel.borrow().clone();
343 if let (Some(switch_knob), Some(knobs_panel)) = (switch_knob, knobs_panel) {
344 if value {
345 Canvas::set_left(&knobs_panel, switch_knob.bounds().width);
346 } else {
347 Canvas::set_left(&knobs_panel, 0.0);
348 }
349 }
350 }
351}