1use super::internal::pressable_labeled_control::{
2 PressableLabeledControlState, WeakPressableLabeledControl,
3};
4use super::internal::radio_indicator_presenter::{
5 create_default_radio_indicator_presenter, RadioIndicatorPresenter, RadioIndicatorTemplate,
6 RadioIndicatorVisualState,
7};
8use super::*;
9use crate::bindings::ui;
10use crate::controls::radio_group::RadioGroupEventTarget;
11use crate::ffi::KeyEventType;
12use std::cell::{Cell, RefCell};
13use std::rc::Rc;
14
15fn create_indicator_presenter(
16 template: Option<Rc<dyn RadioIndicatorTemplate>>,
17 sizing: Option<LabeledControlSizing>,
18) -> Rc<dyn RadioIndicatorPresenter> {
19 if let Some(template) = template {
20 return template.create(sizing);
21 }
22 if let Some(template_set) = get_control_templates() {
23 if let Some(template) = template_set.radio_indicator {
24 return template.create(sizing);
25 }
26 }
27 create_default_radio_indicator_presenter(sizing)
28}
29
30#[derive(Clone)]
31pub struct RadioButton {
32 base: PressableLabeledControl,
33 root: FlexBox,
34 indicator_presenter: Rc<RefCell<Rc<dyn RadioIndicatorPresenter>>>,
35 template_override: Rc<RefCell<Option<Rc<dyn RadioIndicatorTemplate>>>>,
36 sizing_value: Rc<Cell<Option<LabeledControlSizing>>>,
37 colors_value: Rc<Cell<Option<LabeledControlColors>>>,
38 value_text: String,
39 checked: Rc<Cell<bool>>,
40 changed: Rc<RefCell<Option<RadioButtonChangedCallback>>>,
41 owner_group: Rc<RefCell<Option<WeakRadioGroupEventTarget>>>,
42 weak_root: Rc<WeakNodeRef>,
43}
44
45impl RadioButton {
46 pub fn new(value: impl Into<String>) -> Self {
47 let value = value.into();
48 Self::with_label(value.clone(), value)
49 }
50
51 pub fn with_label(value: impl Into<String>, label: impl Into<String>) -> Self {
52 let value = value.into();
53 let label = label.into();
54 let indicator_presenter = create_indicator_presenter(None, None);
55 let base =
56 PressableLabeledControl::new(SemanticRole::Radio, label, indicator_presenter.root());
57 let root = base.root();
58 let weak_root = Rc::new(root.node_ref().downgrade());
59 let control = Self {
60 base,
61 root,
62 indicator_presenter: Rc::new(RefCell::new(indicator_presenter)),
63 template_override: Rc::new(RefCell::new(None)),
64 sizing_value: Rc::new(Cell::new(None)),
65 colors_value: Rc::new(Cell::new(None)),
66 value_text: value,
67 checked: Rc::new(Cell::new(false)),
68 changed: Rc::new(RefCell::new(None)),
69 owner_group: Rc::new(RefCell::new(None)),
70 weak_root,
71 };
72 control.root.semantic_checked(SemanticCheckedState::False);
73 let activation = RadioEventTarget::from_radio(&control);
74 control.base.set_activation(move |state| {
75 activation.activate(state);
76 });
77 let state_sync = RadioEventTarget::from_radio(&control);
78 control.base.set_state_changed(move |state| {
79 state_sync.sync_visual(state, false, false);
80 });
81 let key_target = RadioEventTarget::from_radio(&control);
82 control
83 .base
84 .set_key_handler(move |event| key_target.handle_key_event(event));
85 control.sync_visual(control.base.snapshot_state(), false, false);
86 control
87 }
88
89 pub fn value(&self) -> &str {
90 &self.value_text
91 }
92
93 pub fn checked(&self, checked: bool) -> &Self {
94 self.check(checked);
95 self
96 }
97
98 pub fn check(&self, checked: bool) -> &Self {
99 self.update_checked(checked, true, false);
100 self
101 }
102
103 pub fn is_checked(&self) -> bool {
104 self.checked.get()
105 }
106
107 pub fn sizing(&self, sizing: LabeledControlSizing) -> &Self {
108 self.set_sizing(Some(sizing))
109 }
110
111 pub fn clear_sizing(&self) -> &Self {
112 self.set_sizing(None)
113 }
114
115 fn set_sizing(&self, sizing: Option<LabeledControlSizing>) -> &Self {
116 self.sizing_value.set(sizing);
117 self.base.set_label_font_size_override(
118 sizing
119 .filter(|sizing| sizing.has_label_font_size())
120 .map(|sizing| sizing.label_font_size_px())
121 .unwrap_or(0.0),
122 );
123 self.replace_indicator_presenter(create_indicator_presenter(
124 self.template_override.borrow().clone(),
125 self.sizing_value.get(),
126 ));
127 self.sync_visual(self.base.snapshot_state(), false, false);
128 self
129 }
130
131 pub fn template(&self, template: Rc<dyn RadioIndicatorTemplate>) -> &Self {
132 self.set_template(Some(template))
133 }
134
135 pub fn clear_template(&self) -> &Self {
136 self.set_template(None)
137 }
138
139 fn set_template(&self, template: Option<Rc<dyn RadioIndicatorTemplate>>) -> &Self {
140 self.template_override.replace(template.clone());
141 self.replace_indicator_presenter(create_indicator_presenter(
142 template,
143 self.sizing_value.get(),
144 ));
145 self.sync_visual(self.base.snapshot_state(), false, false);
146 self
147 }
148
149 pub fn colors(&self, colors: LabeledControlColors) -> &Self {
150 self.set_colors(Some(colors))
151 }
152
153 pub fn clear_colors(&self) -> &Self {
154 self.set_colors(None)
155 }
156
157 fn set_colors(&self, colors: Option<LabeledControlColors>) -> &Self {
158 self.colors_value.set(colors);
159 self.base.colors(colors);
160 self.sync_visual(self.base.snapshot_state(), false, false);
161 self
162 }
163
164 pub fn enabled(&self, enabled: bool) -> &Self {
165 self.base.enabled(enabled);
166 self
167 }
168
169 pub fn on_changed(&self, handler: impl Fn(RadioButtonChangedEventArgs) + 'static) -> &Self {
170 *self.changed.borrow_mut() = Some(Rc::new(handler));
171 self
172 }
173
174 pub fn focus_now(&self) {
175 ui::request_focus(self.root.retained_node_ref().handle().raw());
176 }
177
178 pub(crate) fn bind_group(&self, group: WeakRadioGroupEventTarget) {
179 *self.owner_group.borrow_mut() = Some(group);
180 }
181
182 pub(crate) fn is_enabled(&self) -> bool {
183 self.root.retained_node_ref().is_enabled_for_routing()
184 }
185
186 pub(crate) fn update_checked(&self, checked: bool, emit: bool, announce: bool) {
187 apply_radio_checked(
188 &self.indicator_presenter,
189 &self.weak_root,
190 &self.checked,
191 checked,
192 emit,
193 announce,
194 &self.changed,
195 self.colors_value.get(),
196 self.base.snapshot_state(),
197 );
198 }
199
200 fn replace_indicator_presenter(&self, next_presenter: Rc<dyn RadioIndicatorPresenter>) {
201 let previous = self.indicator_presenter.borrow().clone();
202 if Rc::ptr_eq(&previous, &next_presenter) {
203 return;
204 }
205 self.base.replace_indicator_root(next_presenter.root());
206 self.indicator_presenter.replace(next_presenter);
207 }
208
209 fn sync_visual(&self, base_state: PressableLabeledControlState, emit: bool, announce: bool) {
210 apply_radio_checked(
211 &self.indicator_presenter,
212 &self.weak_root,
213 &self.checked,
214 self.checked.get(),
215 emit,
216 announce,
217 &self.changed,
218 self.colors_value.get(),
219 base_state,
220 );
221 }
222}
223
224impl Clickable for RadioButton {
225 fn on_click(&self, handler: impl Fn(ClickEventArgs) + 'static) -> &Self {
226 self.base.on_click(handler);
227 self
228 }
229}
230
231impl LabeledControlTextStyle for RadioButton {
232 fn set_label_font_family(&self, family: crate::FontFamily) {
233 self.base.font_family(family);
234 }
235
236 fn set_label_font_size(&self, size: f32) {
237 self.base.font_size(size);
238 }
239
240 fn set_label_text_color(&self, color: u32) {
241 self.base.text_color(color);
242 }
243}
244
245impl Node for RadioButton {
246 fn retained_node_ref(&self) -> NodeRef {
247 self.root.retained_node_ref()
248 }
249
250 fn build_self(&self) {
251 self.sync_visual(self.base.snapshot_state(), false, false);
252 self.root.build_self();
253 }
254}
255
256impl HasFlexBoxRoot for RadioButton {
257 fn flex_box_root(&self) -> &FlexBox {
258 &self.root
259 }
260}
261
262impl ThemeBindable for RadioButton {
263 fn theme_binding_node(&self) -> NodeRef {
264 self.root.retained_node_ref()
265 }
266
267 fn weak_theme_target(&self) -> Box<dyn Fn() -> Option<Self>> {
268 let target = RadioEventTarget::from_radio(self);
269 Box::new(move || target.upgrade())
270 }
271}
272
273#[derive(Clone)]
274struct RadioEventTarget {
275 base: WeakPressableLabeledControl,
276 presenter: Rc<RefCell<Rc<dyn RadioIndicatorPresenter>>>,
277 template_override: Rc<RefCell<Option<Rc<dyn RadioIndicatorTemplate>>>>,
278 sizing_value: Rc<Cell<Option<LabeledControlSizing>>>,
279 value_text: String,
280 checked: Rc<Cell<bool>>,
281 colors_value: Rc<Cell<Option<LabeledControlColors>>>,
282 changed: Rc<RefCell<Option<RadioButtonChangedCallback>>>,
283 owner_group: Rc<RefCell<Option<WeakRadioGroupEventTarget>>>,
284 weak_root: Rc<WeakNodeRef>,
285}
286
287impl RadioEventTarget {
288 fn from_radio(radio: &RadioButton) -> Self {
289 Self {
290 base: radio.base.downgrade(),
291 presenter: radio.indicator_presenter.clone(),
292 template_override: radio.template_override.clone(),
293 sizing_value: radio.sizing_value.clone(),
294 value_text: radio.value_text.clone(),
295 checked: radio.checked.clone(),
296 colors_value: radio.colors_value.clone(),
297 changed: radio.changed.clone(),
298 owner_group: radio.owner_group.clone(),
299 weak_root: radio.weak_root.clone(),
300 }
301 }
302
303 fn upgrade(&self) -> Option<RadioButton> {
304 let base = self.base.upgrade()?;
305 Some(RadioButton {
306 root: base.root(),
307 base,
308 indicator_presenter: self.presenter.clone(),
309 template_override: self.template_override.clone(),
310 sizing_value: self.sizing_value.clone(),
311 colors_value: self.colors_value.clone(),
312 value_text: self.value_text.clone(),
313 checked: self.checked.clone(),
314 changed: self.changed.clone(),
315 owner_group: self.owner_group.clone(),
316 weak_root: self.weak_root.clone(),
317 })
318 }
319
320 fn activate(&self, base_state: PressableLabeledControlState) {
321 if let Some(group) = self.owner_group() {
322 if let Some(handle) = upgraded_handle(&self.weak_root) {
323 group.select_radio_handle(handle.raw(), true);
324 return;
325 }
326 }
327 apply_radio_checked(
328 &self.presenter,
329 &self.weak_root,
330 &self.checked,
331 true,
332 true,
333 true,
334 &self.changed,
335 self.colors_value.get(),
336 base_state,
337 );
338 }
339
340 fn handle_key_event(&self, event: &mut KeyEventArgs) -> bool {
341 if event.event_type != KeyEventType::Down || event.modifiers != 0 {
342 return false;
343 }
344 let Some(group) = self.owner_group() else {
345 return false;
346 };
347 let Some(handle) = upgraded_handle(&self.weak_root) else {
348 return false;
349 };
350 match event.key.as_str() {
351 "ArrowLeft" | "ArrowUp" => {
352 group.move_selection_from_handle(handle.raw(), -1);
353 true
354 }
355 "ArrowRight" | "ArrowDown" => {
356 group.move_selection_from_handle(handle.raw(), 1);
357 true
358 }
359 "Home" => {
360 group.select_first_enabled(true);
361 true
362 }
363 "End" => {
364 group.select_last_enabled(true);
365 true
366 }
367 _ => false,
368 }
369 }
370
371 fn sync_visual(&self, base_state: PressableLabeledControlState, emit: bool, announce: bool) {
372 apply_radio_checked(
373 &self.presenter,
374 &self.weak_root,
375 &self.checked,
376 self.checked.get(),
377 emit,
378 announce,
379 &self.changed,
380 self.colors_value.get(),
381 base_state,
382 );
383 }
384
385 fn owner_group(&self) -> Option<Rc<RadioGroupEventTarget>> {
386 self.owner_group
387 .borrow()
388 .as_ref()
389 .and_then(WeakRadioGroupEventTarget::upgrade)
390 }
391}
392
393#[allow(clippy::too_many_arguments)]
394fn apply_radio_checked(
395 presenter: &Rc<RefCell<Rc<dyn RadioIndicatorPresenter>>>,
396 weak_root: &Rc<WeakNodeRef>,
397 checked_cell: &Rc<Cell<bool>>,
398 checked: bool,
399 emit: bool,
400 announce: bool,
401 changed: &Rc<RefCell<Option<RadioButtonChangedCallback>>>,
402 colors: Option<LabeledControlColors>,
403 base_state: PressableLabeledControlState,
404) {
405 let previous = checked_cell.get();
406 checked_cell.set(checked);
407 update_semantic_checked(
408 weak_root,
409 if checked {
410 SemanticCheckedState::True
411 } else {
412 SemanticCheckedState::False
413 },
414 announce && previous != checked,
415 );
416 presenter.borrow().apply(
417 current_theme(),
418 RadioIndicatorVisualState::new(
419 checked,
420 base_state.hovered,
421 base_state.pressed,
422 base_state.focused,
423 base_state.enabled,
424 ),
425 colors,
426 );
427 if emit && previous != checked {
428 if let Some(callback) = changed.borrow().clone() {
429 callback(RadioButtonChangedEventArgs { checked });
430 }
431 }
432}