Skip to main content

ferroui_controls/
radio_button.rs

1use 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/// Represents a button that allows a user to select a single option from a
17/// group of options.
18#[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
83/// The radio button viewed as a member of a radio group.
84struct 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        /// Defines the `GroupName` property.
111        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    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
123    pub fn construct() -> Self {
124        Self { base: ToggleButton::construct(), group_manager: RefCell::new(None) }
125    }
126
127    /// Creates a radio button.
128    pub fn new() -> Ref<Self> {
129        instantiate(Self::construct())
130    }
131
132    /// The name that specifies which radio button controls are mutually
133    /// exclusive.
134    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}