ferroui_controls/primitives/
toggle_button.rs1use super::TemplatedControlImpl;
2use crate::metadata::PseudoClassesAttribute;
3use crate::{Button, ButtonImpl, ButtonImplExt, ContentControlImpl, ControlImpl};
4use ferroui_base::data::BindingMode;
5use ferroui_base::input::InputElementImpl;
6use ferroui_base::interactivity::{
7 Interactive, InteractiveImpl, RoutedEvent, RoutedEventArgs, RoutedEventHandlerToken, RoutingStrategies,
8};
9use ferroui_base::layout::LayoutableImpl;
10use ferroui_base::{
11 ferro_class, ferro_impl_classes, ferro_property, ferro_routed_event, instantiate, FerroObjectImpl,
12 FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs, Ref, StyledElementImpl, StyledProperty,
13 StyledPropertyOptions, VisualImpl,
14};
15
16const PC_CHECKED: &str = ":checked";
17const PC_UNCHECKED: &str = ":unchecked";
18const PC_INDETERMINATE: &str = ":indeterminate";
19
20#[repr(C)]
23pub struct ToggleButton {
24 base: Button,
25}
26
27ferro_class! {
28 ToggleButton: Button, virtuals ToggleButtonImpl: ButtonImpl {
29 fn toggle(this);
31 fn on_is_checked_changed(this, e: &RoutedEventArgs);
35 }
36}
37ferroui_base::ferro_class_info!(ToggleButton { new: ToggleButton::new });
38
39ferro_impl_classes!(
40 ToggleButton: StyledElementImpl,
41 VisualImpl,
42 LayoutableImpl,
43 InteractiveImpl,
44 InputElementImpl,
45 TemplatedControlImpl,
46 ContentControlImpl
47);
48
49impl ControlImpl for ToggleButton {
50 fn on_create_automation_peer(this: &Self) -> Ref<crate::automation::peers::AutomationPeer> {
51 crate::automation::peers::ToggleButtonAutomationPeer::new(this).upcast()
52 }
53}
54
55impl FerroObjectImpl for ToggleButton {
56 fn constructed(this: &Self) {
57 Self::parent_constructed(this);
58 this.update_pseudo_classes(this.is_checked());
59 }
60
61 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
62 Self::parent_on_property_changed(this, change);
63
64 if change.property() == Self::is_checked_property().as_property() {
65 let new_value = change.get_new_value::<Option<bool>>();
66 this.update_pseudo_classes(new_value);
67 this.on_is_checked_changed(&RoutedEventArgs::with_event(Self::is_checked_changed_event()));
68 }
69 }
70}
71
72impl ButtonImpl for ToggleButton {
73 fn on_click(this: &Self) {
74 if !this.is_effectively_enabled() {
75 return;
76 }
77
78 this.toggle();
79 Self::parent_on_click(this);
80 }
81}
82
83impl ToggleButtonImpl for ToggleButton {
84 fn toggle(this: &Self) {
85 let new_value = match this.is_checked() {
86 Some(true) => {
87 if this.is_three_state() {
88 None
89 } else {
90 Some(false)
91 }
92 }
93 Some(false) => Some(true),
94 None => Some(false),
95 };
96
97 this.set_current_value(Self::is_checked_property(), new_value);
98 }
99
100 fn on_is_checked_changed(this: &Self, e: &RoutedEventArgs) {
101 this.raise_event(e);
102 }
103}
104
105impl ToggleButton {
106 pub const PSEUDO_CLASSES: PseudoClassesAttribute =
108 PseudoClassesAttribute::new(&[PC_CHECKED, PC_UNCHECKED, PC_INDETERMINATE]);
109}
110
111ferroui_base::ferro_properties! { impl ToggleButton {
112 ferro_property!(
113 pub fn is_checked_property() -> StyledProperty<Option<bool>> {
115 FerroProperty::register_with::<ToggleButton, _>(
116 "IsChecked",
117 StyledPropertyOptions::new(Some(false)).default_binding_mode(BindingMode::TwoWay),
118 )
119 }
120 );
121
122 ferro_property!(
123 pub fn is_three_state_property() -> StyledProperty<bool> {
125 FerroProperty::register::<ToggleButton, _>("IsThreeState", false)
126 }
127 );
128} }
129
130impl ToggleButton {
131 ferro_routed_event!(
132 pub fn is_checked_changed_event() -> RoutedEvent<RoutedEventArgs> {
134 RoutedEvent::register::<ToggleButton, _>("IsCheckedChanged", RoutingStrategies::BUBBLE)
135 }
136 );
137
138 pub fn construct() -> Self {
140 Self { base: Button::construct() }
141 }
142
143 pub fn new() -> Ref<Self> {
145 instantiate(Self::construct())
146 }
147
148 pub fn is_checked_changed(
150 &self,
151 handler: impl Fn(&Interactive, &RoutedEventArgs) + 'static,
152 ) -> RoutedEventHandlerToken {
153 self.add_handler(Self::is_checked_changed_event(), handler)
154 }
155
156 pub fn is_checked(&self) -> Option<bool> {
159 self.get_value(Self::is_checked_property())
160 }
161
162 pub fn set_is_checked(&self, value: Option<bool>) {
163 self.set_value(Self::is_checked_property(), value)
164 }
165
166 pub fn is_three_state(&self) -> bool {
168 self.get_value(Self::is_three_state_property())
169 }
170
171 pub fn set_is_three_state(&self, value: bool) {
172 self.set_value(Self::is_three_state_property(), value)
173 }
174
175 fn update_pseudo_classes(&self, is_checked: Option<bool>) {
176 self.pseudo_classes().set(PC_CHECKED, is_checked == Some(true));
177 self.pseudo_classes().set(PC_UNCHECKED, is_checked == Some(false));
178 self.pseudo_classes().set(PC_INDETERMINATE, is_checked.is_none());
179 }
180}