ferroui_controls/
radio_button.rs1use crate::primitives::{TemplatedControlImpl, ToggleButton, ToggleButtonImpl};
2use crate::radio_button_group_manager::{register_radio_button, IRadioButton, RadioButtonGroupManager};
3use crate::{ButtonImpl, ContentControlImpl, ControlImpl, MenuItemToggleType};
4use ferroui_base::input::InputElementImpl;
5use ferroui_base::interactivity::InteractiveImpl;
6use ferroui_base::layout::LayoutableImpl;
7use ferroui_base::rendering::IPresentationSource;
8use ferroui_base::{
9 ferro_class, ferro_impl_classes, ferro_property, instantiate, FerroObjectImpl, FerroObjectImplExt,
10 FerroProperty, FerroPropertyChangedEventArgs, Ref, StyledElement, StyledElementImpl, StyledProperty,
11 VisualImpl, VisualImplExt, VisualTreeAttachmentEventArgs,
12};
13use std::cell::RefCell;
14use std::rc::Rc;
15
16#[repr(C)]
19pub struct RadioButton {
20 base: ToggleButton,
21 group_manager: RefCell<Option<Rc<RadioButtonGroupManager>>>,
22}
23
24ferro_class!(RadioButton: ToggleButton);
25ferroui_base::ferro_class_info!(RadioButton { new: RadioButton::new });
26ferro_impl_classes!(
27 RadioButton: StyledElementImpl,
28 LayoutableImpl,
29 InteractiveImpl,
30 InputElementImpl,
31 TemplatedControlImpl,
32 ContentControlImpl,
33 ButtonImpl
34);
35
36impl ControlImpl for RadioButton {
37 fn on_create_automation_peer(this: &Self) -> Ref<crate::automation::peers::AutomationPeer> {
38 crate::automation::peers::RadioButtonAutomationPeer::new(this).upcast()
39 }
40}
41
42impl FerroObjectImpl for RadioButton {
43 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
44 Self::parent_on_property_changed(this, change);
45
46 if change.property() == ToggleButton::is_checked_property().as_property() {
47 this.is_checked_value_changed(change.get_new_value::<Option<bool>>());
48 } else if change.property() == Self::group_name_property().as_property() {
49 let (old_value, new_value) = change.get_old_and_new_value::<Option<String>>();
50 this.on_group_name_changed(old_value.as_deref(), new_value.as_deref());
51 }
52 }
53}
54
55impl VisualImpl for RadioButton {
56 fn on_attached_to_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
57 let group_manager = this.group_manager.borrow().clone();
58 if let Some(group_manager) = group_manager {
59 group_manager.remove(&this.handle(), this.group_name().as_deref());
60 }
61 this.ensure_radio_group_manager(Some(e.presentation_source().clone()));
62 Self::parent_on_attached_to_visual_tree(this, e);
63 }
64
65 fn on_detached_from_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
66 Self::parent_on_detached_from_visual_tree(this, e);
67
68 let group_manager = this.group_manager.borrow_mut().take();
69 if let Some(group_manager) = group_manager {
70 group_manager.remove(&this.handle(), this.group_name().as_deref());
71 }
72 }
73}
74
75impl ToggleButtonImpl for RadioButton {
76 fn toggle(this: &Self) {
77 if !this.is_checked().unwrap_or_default() {
78 this.set_current_value(ToggleButton::is_checked_property(), Some(true));
79 }
80 }
81}
82
83struct RadioButtonHandle(Ref<RadioButton>);
85
86impl IRadioButton for RadioButtonHandle {
87 fn logical(&self) -> Ref<StyledElement> {
88 self.0.clone().upcast()
89 }
90
91 fn group_name(&self) -> Option<String> {
92 self.0.group_name()
93 }
94
95 fn toggle_type(&self) -> MenuItemToggleType {
96 MenuItemToggleType::Radio
97 }
98
99 fn is_checked(&self) -> bool {
100 self.0.is_checked().unwrap_or_default()
101 }
102
103 fn set_is_checked(&self, value: bool) {
104 self.0.set_current_value(ToggleButton::is_checked_property(), Some(value));
105 }
106}
107
108ferroui_base::ferro_properties! { impl RadioButton {
109 ferro_property!(
110 pub fn group_name_property() -> StyledProperty<Option<String>> {
112 FerroProperty::register::<RadioButton, _>("GroupName", None)
113 }
114 );
115} }
116
117impl RadioButton {
118 fn static_constructor() {
119 register_radio_button::<RadioButton>(|radio_button| Rc::new(RadioButtonHandle(radio_button)));
120 }
121
122 pub fn construct() -> Self {
124 Self { base: ToggleButton::construct(), group_manager: RefCell::new(None) }
125 }
126
127 pub fn new() -> Ref<Self> {
129 instantiate(Self::construct())
130 }
131
132 pub fn group_name(&self) -> Option<String> {
135 self.get_value(Self::group_name_property())
136 }
137
138 pub fn set_group_name(&self, value: Option<String>) {
139 self.set_value(Self::group_name_property(), value)
140 }
141
142 fn handle(&self) -> RadioButtonHandle {
143 RadioButtonHandle(self.to_ref())
144 }
145
146 fn on_group_name_changed(&self, old_group_name: Option<&str>, new_group_name: Option<&str>) {
147 if old_group_name.is_some_and(|name| !name.is_empty()) {
148 let group_manager = self.group_manager.borrow().clone();
149 if let Some(group_manager) = group_manager {
150 group_manager.remove(&self.handle(), old_group_name);
151 }
152 }
153 if new_group_name.is_some_and(|name| !name.is_empty()) {
154 self.ensure_radio_group_manager(None);
155 }
156 }
157
158 fn is_checked_value_changed(&self, value: Option<bool>) {
159 if value.unwrap_or_default() {
160 let group_manager = self.ensure_radio_group_manager(None);
161 group_manager.on_checked_changed(&self.handle());
162 }
163 }
164
165 fn ensure_radio_group_manager(&self, source: Option<Rc<dyn IPresentationSource>>) -> Rc<RadioButtonGroupManager> {
166 let source = source.or_else(|| self.presentation_source());
167 let group_manager = RadioButtonGroupManager::get_or_create_for_root(source.as_ref());
168 *self.group_manager.borrow_mut() = Some(group_manager.clone());
169 group_manager.add(&self.handle());
170 group_manager
171 }
172}