@effindomv2/fui-rs 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/COMMERCIAL.md +7 -0
  2. package/Cargo.toml +34 -0
  3. package/LICENSE.md +9 -0
  4. package/README.md +88 -0
  5. package/package.json +54 -0
  6. package/scripts/build.sh +227 -0
  7. package/scripts/check-runtime-dependency.sh +41 -0
  8. package/scripts/framework-host-services.ts +40 -0
  9. package/scripts/generate-host-events.ts +17 -0
  10. package/scripts/generate-host-services.ts +28 -0
  11. package/scripts/hostgen/common.ts +73 -0
  12. package/scripts/hostgen/registry.ts +159 -0
  13. package/scripts/hostgen/rust-host-events.ts +171 -0
  14. package/scripts/hostgen/rust-host-services.ts +257 -0
  15. package/src/animation.rs +625 -0
  16. package/src/app.rs +324 -0
  17. package/src/assets.rs +572 -0
  18. package/src/bindings/mod.rs +1 -0
  19. package/src/bindings/ui.rs +705 -0
  20. package/src/bitmap.rs +317 -0
  21. package/src/bridge_callbacks.rs +242 -0
  22. package/src/context_menu_manager.rs +332 -0
  23. package/src/controls/anti_selection_area.rs +54 -0
  24. package/src/controls/button.rs +542 -0
  25. package/src/controls/checkbox.rs +372 -0
  26. package/src/controls/combobox.rs +1574 -0
  27. package/src/controls/context_menu.rs +1367 -0
  28. package/src/controls/control_template_set.rs +46 -0
  29. package/src/controls/control_tokens.rs +596 -0
  30. package/src/controls/dialog.rs +590 -0
  31. package/src/controls/dropdown.rs +1010 -0
  32. package/src/controls/form.rs +229 -0
  33. package/src/controls/internal/button_presenter.rs +142 -0
  34. package/src/controls/internal/checkbox_indicator_presenter.rs +210 -0
  35. package/src/controls/internal/dropdown_chevron_presenter.rs +134 -0
  36. package/src/controls/internal/dropdown_field_presenter.rs +269 -0
  37. package/src/controls/internal/dropdown_option_row_presenter.rs +184 -0
  38. package/src/controls/internal/mod.rs +13 -0
  39. package/src/controls/internal/pressable_indicator_presenter.rs +26 -0
  40. package/src/controls/internal/pressable_labeled_control.rs +492 -0
  41. package/src/controls/internal/radio_indicator_presenter.rs +208 -0
  42. package/src/controls/internal/selectable_popup_list.rs +597 -0
  43. package/src/controls/internal/slider_presenter.rs +282 -0
  44. package/src/controls/internal/switch_indicator_presenter.rs +235 -0
  45. package/src/controls/internal/text_input_core.rs +1074 -0
  46. package/src/controls/internal/text_input_presenter.rs +108 -0
  47. package/src/controls/mod.rs +235 -0
  48. package/src/controls/nav_link.rs +395 -0
  49. package/src/controls/popup.rs +191 -0
  50. package/src/controls/progress_bar.rs +287 -0
  51. package/src/controls/radio_button.rs +348 -0
  52. package/src/controls/radio_group.rs +283 -0
  53. package/src/controls/selection_area.rs +82 -0
  54. package/src/controls/shared.rs +68 -0
  55. package/src/controls/slider.rs +701 -0
  56. package/src/controls/switch.rs +293 -0
  57. package/src/controls/templating.rs +49 -0
  58. package/src/controls/tests.rs +3905 -0
  59. package/src/controls/text_area.rs +207 -0
  60. package/src/controls/text_input.rs +192 -0
  61. package/src/debug.rs +146 -0
  62. package/src/drag_drop.rs +424 -0
  63. package/src/drag_gesture.rs +258 -0
  64. package/src/drawing.rs +385 -0
  65. package/src/event.rs +1603 -0
  66. package/src/external_drop.rs +467 -0
  67. package/src/fetch.rs +500 -0
  68. package/src/ffi.rs +3542 -0
  69. package/src/file.rs +1677 -0
  70. package/src/focus_adorner.rs +307 -0
  71. package/src/focus_visibility.rs +121 -0
  72. package/src/frame_scheduler.rs +159 -0
  73. package/src/frame_signal.rs +64 -0
  74. package/src/generated/ffi.rs +798 -0
  75. package/src/generated/framework_host_services.rs +74 -0
  76. package/src/generated/mod.rs +2 -0
  77. package/src/host_services.rs +162 -0
  78. package/src/image_sampling.rs +78 -0
  79. package/src/keyboard_scroll.rs +137 -0
  80. package/src/keyboard_scroll_tracker.rs +385 -0
  81. package/src/lib.rs +547 -0
  82. package/src/logger.rs +56 -0
  83. package/src/mobile_text_selection_toolbar.rs +1034 -0
  84. package/src/navigation.rs +48 -0
  85. package/src/node/core.rs +2210 -0
  86. package/src/node/custom_drawable.rs +149 -0
  87. package/src/node/flex_box.rs +874 -0
  88. package/src/node/grid.rs +304 -0
  89. package/src/node/helpers.rs +442 -0
  90. package/src/node/image.rs +506 -0
  91. package/src/node/mod.rs +315 -0
  92. package/src/node/scroll_bar.rs +845 -0
  93. package/src/node/scroll_box.rs +514 -0
  94. package/src/node/scroll_state.rs +121 -0
  95. package/src/node/scroll_view.rs +557 -0
  96. package/src/node/svg_node.rs +474 -0
  97. package/src/node/text_node.rs +633 -0
  98. package/src/node/virtual_list.rs +678 -0
  99. package/src/panic_hook.rs +36 -0
  100. package/src/persisted.rs +274 -0
  101. package/src/platform.rs +556 -0
  102. package/src/popup_presenter.rs +344 -0
  103. package/src/selection_handle_adorner.rs +838 -0
  104. package/src/signal.rs +134 -0
  105. package/src/text.rs +1065 -0
  106. package/src/theme.rs +571 -0
  107. package/src/timers.rs +106 -0
  108. package/src/tool_tip.rs +167 -0
  109. package/src/tool_tip_manager.rs +691 -0
  110. package/src/transitions.rs +41 -0
  111. package/src/typography.rs +520 -0
  112. package/src/viewport.rs +108 -0
  113. package/src/worker.rs +308 -0
  114. package/src/worker_job.rs +82 -0
  115. package/src/worker_runtime.rs +172 -0
@@ -0,0 +1,287 @@
1
+ use super::*;
2
+ use crate::logger;
3
+ use crate::node::WeakFlexBox;
4
+ use crate::signal::SubscriptionGuard;
5
+
6
+ #[derive(Clone)]
7
+ pub struct ProgressBar {
8
+ root: FlexBox,
9
+ fill: FlexBox,
10
+ min: Rc<Cell<f32>>,
11
+ max: Rc<Cell<f32>>,
12
+ value: Rc<Cell<f32>>,
13
+ length: Rc<Cell<f32>>,
14
+ thickness: Rc<Cell<f32>>,
15
+ track_color_value: Rc<Cell<u32>>,
16
+ fill_color_value: Rc<Cell<u32>>,
17
+ track_color_overridden: Rc<Cell<bool>>,
18
+ fill_color_overridden: Rc<Cell<bool>>,
19
+ corner_radius_overridden: Rc<Cell<bool>>,
20
+ weak_root: WeakFlexBox,
21
+ weak_fill: WeakFlexBox,
22
+ theme_guard: Rc<RefCell<Option<SubscriptionGuard>>>,
23
+ }
24
+
25
+ impl Default for ProgressBar {
26
+ fn default() -> Self {
27
+ Self::new()
28
+ }
29
+ }
30
+
31
+ impl ProgressBar {
32
+ pub fn new() -> Self {
33
+ let root = flex_box();
34
+ let fill = flex_box();
35
+ root.semantic_value_range(0.0, 0.0, 100.0).child(&fill);
36
+ let control = Self {
37
+ weak_root: root.downgrade(),
38
+ weak_fill: fill.downgrade(),
39
+ root,
40
+ fill,
41
+ min: Rc::new(Cell::new(0.0)),
42
+ max: Rc::new(Cell::new(100.0)),
43
+ value: Rc::new(Cell::new(0.0)),
44
+ length: Rc::new(Cell::new(PROGRESS_LENGTH)),
45
+ thickness: Rc::new(Cell::new(PROGRESS_THICKNESS)),
46
+ track_color_value: Rc::new(Cell::new(0)),
47
+ fill_color_value: Rc::new(Cell::new(0)),
48
+ track_color_overridden: Rc::new(Cell::new(false)),
49
+ fill_color_overridden: Rc::new(Cell::new(false)),
50
+ corner_radius_overridden: Rc::new(Cell::new(false)),
51
+ theme_guard: Rc::new(RefCell::new(None)),
52
+ };
53
+ control.install_visual_subscriptions();
54
+ control.set_value(0.0);
55
+ control.handle_theme_changed();
56
+ control
57
+ }
58
+
59
+ pub fn min(&self, value: f32) -> &Self {
60
+ self.min.set(value);
61
+ if self.max.get() < value {
62
+ self.max.set(value);
63
+ }
64
+ self.set_value(self.value.get());
65
+ self
66
+ }
67
+
68
+ pub fn max(&self, value: f32) -> &Self {
69
+ self.max.set(value);
70
+ if self.min.get() > value {
71
+ self.min.set(value);
72
+ }
73
+ self.set_value(self.value.get());
74
+ self
75
+ }
76
+
77
+ pub fn value(&self, value: f32) -> &Self {
78
+ self.set_value(value);
79
+ self
80
+ }
81
+
82
+ pub fn length(&self, value: f32) -> &Self {
83
+ if value <= 0.0 {
84
+ logger::warn(
85
+ "Layout",
86
+ &format!("ProgressBar.length() received {value}; clamping to 1.0."),
87
+ );
88
+ }
89
+ self.length.set(if value > 0.0 { value } else { 1.0 });
90
+ self.sync_geometry();
91
+ self
92
+ }
93
+
94
+ pub fn thickness(&self, value: f32) -> &Self {
95
+ if value <= 0.0 {
96
+ logger::warn(
97
+ "Layout",
98
+ &format!("ProgressBar.thickness() received {value}; clamping to 1.0."),
99
+ );
100
+ }
101
+ self.thickness.set(if value > 0.0 { value } else { 1.0 });
102
+ if !self.corner_radius_overridden.get() {
103
+ let radius = self.thickness.get() * 0.5;
104
+ self.root.corner_radius(radius);
105
+ }
106
+ self.sync_geometry();
107
+ self.sync_visual_state();
108
+ self
109
+ }
110
+
111
+ pub fn track_color(&self, color: u32) -> &Self {
112
+ self.track_color_overridden.set(true);
113
+ self.track_color_value.set(color);
114
+ self.sync_visual_state();
115
+ self
116
+ }
117
+
118
+ pub fn fill_color(&self, color: u32) -> &Self {
119
+ self.fill_color_overridden.set(true);
120
+ self.fill_color_value.set(color);
121
+ self.sync_visual_state();
122
+ self
123
+ }
124
+
125
+ pub fn corner_radius(&self, radius: f32) -> &Self {
126
+ self.corner_radius_overridden.set(true);
127
+ self.root.corner_radius(radius);
128
+ self.fill.corner_radius(radius);
129
+ self
130
+ }
131
+
132
+ pub fn current_value(&self) -> f32 {
133
+ self.value.get()
134
+ }
135
+
136
+ fn install_visual_subscriptions(&self) {
137
+ let weak_root = self.weak_root.clone();
138
+ let weak_fill = self.weak_fill.clone();
139
+ let min = self.min.clone();
140
+ let max = self.max.clone();
141
+ let value = self.value.clone();
142
+ let length = self.length.clone();
143
+ let thickness = self.thickness.clone();
144
+ let track_color_value = self.track_color_value.clone();
145
+ let fill_color_value = self.fill_color_value.clone();
146
+ let track_color_overridden = self.track_color_overridden.clone();
147
+ let fill_color_overridden = self.fill_color_overridden.clone();
148
+ let corner_radius_overridden = self.corner_radius_overridden.clone();
149
+ *self.theme_guard.borrow_mut() = Some(subscribe(move |_theme| {
150
+ let Some(root) = weak_root.upgrade() else {
151
+ return;
152
+ };
153
+ let Some(fill) = weak_fill.upgrade() else {
154
+ return;
155
+ };
156
+ sync_progress_visual_state(
157
+ &root,
158
+ &fill,
159
+ thickness.get(),
160
+ track_color_value.get(),
161
+ fill_color_value.get(),
162
+ track_color_overridden.get(),
163
+ fill_color_overridden.get(),
164
+ corner_radius_overridden.get(),
165
+ );
166
+ sync_progress_geometry(
167
+ &root,
168
+ &fill,
169
+ min.get(),
170
+ max.get(),
171
+ value.get(),
172
+ length.get(),
173
+ thickness.get(),
174
+ );
175
+ }));
176
+ }
177
+
178
+ fn handle_theme_changed(&self) {
179
+ self.sync_visual_state();
180
+ self.sync_geometry();
181
+ }
182
+
183
+ fn set_value(&self, value: f32) {
184
+ self.value.set(value.clamp(self.min.get(), self.max.get()));
185
+ self.sync_geometry();
186
+ }
187
+
188
+ fn sync_geometry(&self) {
189
+ sync_progress_geometry(
190
+ &self.root,
191
+ &self.fill,
192
+ self.min.get(),
193
+ self.max.get(),
194
+ self.value.get(),
195
+ self.length.get(),
196
+ self.thickness.get(),
197
+ );
198
+ }
199
+
200
+ fn sync_visual_state(&self) {
201
+ sync_progress_visual_state(
202
+ &self.root,
203
+ &self.fill,
204
+ self.thickness.get(),
205
+ self.track_color_value.get(),
206
+ self.fill_color_value.get(),
207
+ self.track_color_overridden.get(),
208
+ self.fill_color_overridden.get(),
209
+ self.corner_radius_overridden.get(),
210
+ );
211
+ }
212
+ }
213
+
214
+ impl Node for ProgressBar {
215
+ fn retained_node_ref(&self) -> NodeRef {
216
+ self.root.retained_node_ref()
217
+ }
218
+
219
+ fn build_self(&self) {
220
+ self.sync_visual_state();
221
+ self.sync_geometry();
222
+ self.root.build_self();
223
+ }
224
+ }
225
+
226
+ impl HasFlexBoxRoot for ProgressBar {
227
+ fn flex_box_root(&self) -> &FlexBox {
228
+ &self.root
229
+ }
230
+ }
231
+
232
+ fn sync_progress_geometry(
233
+ root: &FlexBox,
234
+ fill: &FlexBox,
235
+ min: f32,
236
+ max: f32,
237
+ current_value: f32,
238
+ length: f32,
239
+ thickness: f32,
240
+ ) {
241
+ let range = max - min;
242
+ let fraction = if range > 0.0 {
243
+ ((current_value - min) / range).clamp(0.0, 1.0)
244
+ } else {
245
+ 0.0
246
+ };
247
+ let fill_length = length * fraction;
248
+ root.width(length, Unit::Pixel)
249
+ .height(thickness, Unit::Pixel)
250
+ .semantic_value_range(current_value, min, max)
251
+ .default_semantic_label(format!(
252
+ "Progress bar, value {}, range {} to {}",
253
+ current_value, min, max
254
+ ));
255
+ fill.width(fill_length, Unit::Pixel)
256
+ .height(thickness, Unit::Pixel);
257
+ }
258
+
259
+ fn sync_progress_visual_state(
260
+ root: &FlexBox,
261
+ fill: &FlexBox,
262
+ thickness: f32,
263
+ track_color_value: u32,
264
+ fill_color_value: u32,
265
+ track_color_overridden: bool,
266
+ fill_color_overridden: bool,
267
+ corner_radius_overridden: bool,
268
+ ) {
269
+ let theme = current_theme();
270
+ let track_color = if track_color_overridden {
271
+ track_color_value
272
+ } else {
273
+ theme.colors.scrollbar_track
274
+ };
275
+ let fill_color = if fill_color_overridden {
276
+ fill_color_value
277
+ } else {
278
+ theme.colors.accent
279
+ };
280
+ if !corner_radius_overridden {
281
+ let radius = thickness * 0.5;
282
+ root.corner_radius(radius);
283
+ fill.corner_radius(radius);
284
+ }
285
+ root.bg_color(track_color).border(1.0, theme.colors.border);
286
+ fill.bg_color(fill_color);
287
+ }
@@ -0,0 +1,348 @@
1
+ use super::internal::pressable_labeled_control::PressableLabeledControlState;
2
+ use super::internal::radio_indicator_presenter::{
3
+ create_default_radio_indicator_presenter, RadioIndicatorPresenter, RadioIndicatorTemplate,
4
+ RadioIndicatorVisualState,
5
+ };
6
+ use super::*;
7
+ use crate::bindings::ui;
8
+ use crate::controls::radio_group::RadioGroupEventTarget;
9
+ use crate::ffi::KeyEventType;
10
+ use std::cell::{Cell, RefCell};
11
+ use std::rc::Rc;
12
+
13
+ fn create_indicator_presenter(
14
+ template: Option<Rc<dyn RadioIndicatorTemplate>>,
15
+ sizing: Option<LabeledControlSizing>,
16
+ ) -> Rc<dyn RadioIndicatorPresenter> {
17
+ if let Some(template) = template {
18
+ return template.create(sizing);
19
+ }
20
+ if let Some(template_set) = get_control_templates() {
21
+ if let Some(template) = template_set.radio_indicator {
22
+ return template.create(sizing);
23
+ }
24
+ }
25
+ create_default_radio_indicator_presenter(sizing)
26
+ }
27
+
28
+ #[derive(Clone)]
29
+ pub struct RadioButton {
30
+ base: PressableLabeledControl,
31
+ root: FlexBox,
32
+ indicator_presenter: Rc<RefCell<Rc<dyn RadioIndicatorPresenter>>>,
33
+ template_override: Rc<RefCell<Option<Rc<dyn RadioIndicatorTemplate>>>>,
34
+ sizing_value: Rc<Cell<Option<LabeledControlSizing>>>,
35
+ colors_value: Rc<Cell<Option<LabeledControlColors>>>,
36
+ value_text: String,
37
+ checked: Rc<Cell<bool>>,
38
+ changed: Rc<RefCell<Option<RadioButtonChangedCallback>>>,
39
+ owner_group: Rc<RefCell<Option<WeakRadioGroupEventTarget>>>,
40
+ weak_root: Rc<WeakNodeRef>,
41
+ }
42
+
43
+ impl RadioButton {
44
+ pub fn new(value: impl Into<String>) -> Self {
45
+ let value = value.into();
46
+ Self::with_label(value.clone(), value)
47
+ }
48
+
49
+ pub fn with_label(value: impl Into<String>, label: impl Into<String>) -> Self {
50
+ let value = value.into();
51
+ let label = label.into();
52
+ let indicator_presenter = create_indicator_presenter(None, None);
53
+ let base =
54
+ PressableLabeledControl::new(SemanticRole::Radio, label, indicator_presenter.root());
55
+ let root = base.root();
56
+ let weak_root = Rc::new(root.node_ref().downgrade());
57
+ let control = Self {
58
+ base,
59
+ root,
60
+ indicator_presenter: Rc::new(RefCell::new(indicator_presenter)),
61
+ template_override: Rc::new(RefCell::new(None)),
62
+ sizing_value: Rc::new(Cell::new(None)),
63
+ colors_value: Rc::new(Cell::new(None)),
64
+ value_text: value,
65
+ checked: Rc::new(Cell::new(false)),
66
+ changed: Rc::new(RefCell::new(None)),
67
+ owner_group: Rc::new(RefCell::new(None)),
68
+ weak_root,
69
+ };
70
+ control.root.semantic_checked(SemanticCheckedState::False);
71
+ let activation = RadioEventTarget::from_radio(&control);
72
+ control.base.set_activation(move |state| {
73
+ activation.activate(state);
74
+ });
75
+ let state_sync = RadioEventTarget::from_radio(&control);
76
+ control.base.set_state_changed(move |state| {
77
+ state_sync.sync_visual(state, false, false);
78
+ });
79
+ let key_target = RadioEventTarget::from_radio(&control);
80
+ control
81
+ .base
82
+ .set_key_handler(move |event| key_target.handle_key_event(event));
83
+ control.sync_visual(control.base.snapshot_state(), false, false);
84
+ control
85
+ }
86
+
87
+ pub fn value(&self) -> &str {
88
+ &self.value_text
89
+ }
90
+
91
+ pub fn checked(&self, checked: bool) -> &Self {
92
+ self.check(checked);
93
+ self
94
+ }
95
+
96
+ pub fn check(&self, checked: bool) -> &Self {
97
+ self.update_checked(checked, true, false);
98
+ self
99
+ }
100
+
101
+ pub fn is_checked(&self) -> bool {
102
+ self.checked.get()
103
+ }
104
+
105
+ pub fn sizing(&self, sizing: Option<LabeledControlSizing>) -> &Self {
106
+ self.sizing_value.set(sizing);
107
+ self.base.set_label_font_size_override(
108
+ sizing
109
+ .filter(|sizing| sizing.has_label_font_size())
110
+ .map(|sizing| sizing.label_font_size_px())
111
+ .unwrap_or(0.0),
112
+ );
113
+ self.replace_indicator_presenter(create_indicator_presenter(
114
+ self.template_override.borrow().clone(),
115
+ self.sizing_value.get(),
116
+ ));
117
+ self.sync_visual(self.base.snapshot_state(), false, false);
118
+ self
119
+ }
120
+
121
+ pub fn template(&self, template: Option<Rc<dyn RadioIndicatorTemplate>>) -> &Self {
122
+ self.template_override.replace(template.clone());
123
+ self.replace_indicator_presenter(create_indicator_presenter(
124
+ template,
125
+ self.sizing_value.get(),
126
+ ));
127
+ self.sync_visual(self.base.snapshot_state(), false, false);
128
+ self
129
+ }
130
+
131
+ pub fn colors(&self, colors: Option<LabeledControlColors>) -> &Self {
132
+ self.colors_value.set(colors);
133
+ self.base.colors(colors);
134
+ self.sync_visual(self.base.snapshot_state(), false, false);
135
+ self
136
+ }
137
+
138
+ pub fn enabled(&self, enabled: bool) -> &Self {
139
+ self.base.enabled(enabled);
140
+ self
141
+ }
142
+
143
+ pub fn on_changed(&self, handler: impl Fn(RadioButtonChangedEventArgs) + 'static) -> &Self {
144
+ *self.changed.borrow_mut() = Some(Rc::new(handler));
145
+ self
146
+ }
147
+
148
+ pub fn focus_now(&self) {
149
+ ui::request_focus(self.root.retained_node_ref().handle().raw());
150
+ }
151
+
152
+ pub(crate) fn bind_group(&self, group: WeakRadioGroupEventTarget) {
153
+ *self.owner_group.borrow_mut() = Some(group);
154
+ }
155
+
156
+ pub(crate) fn is_enabled(&self) -> bool {
157
+ self.root.retained_node_ref().is_enabled_for_routing()
158
+ }
159
+
160
+ pub(crate) fn update_checked(&self, checked: bool, emit: bool, announce: bool) {
161
+ apply_radio_checked(
162
+ &self.indicator_presenter,
163
+ &self.weak_root,
164
+ &self.checked,
165
+ checked,
166
+ emit,
167
+ announce,
168
+ &self.changed,
169
+ self.colors_value.get(),
170
+ self.base.snapshot_state(),
171
+ );
172
+ }
173
+
174
+ fn replace_indicator_presenter(&self, next_presenter: Rc<dyn RadioIndicatorPresenter>) {
175
+ let previous = self.indicator_presenter.borrow().clone();
176
+ if Rc::ptr_eq(&previous, &next_presenter) {
177
+ return;
178
+ }
179
+ self.base.replace_indicator_root(next_presenter.root());
180
+ self.indicator_presenter.replace(next_presenter);
181
+ }
182
+
183
+ fn sync_visual(&self, base_state: PressableLabeledControlState, emit: bool, announce: bool) {
184
+ apply_radio_checked(
185
+ &self.indicator_presenter,
186
+ &self.weak_root,
187
+ &self.checked,
188
+ self.checked.get(),
189
+ emit,
190
+ announce,
191
+ &self.changed,
192
+ self.colors_value.get(),
193
+ base_state,
194
+ );
195
+ }
196
+ }
197
+
198
+ impl Node for RadioButton {
199
+ fn retained_node_ref(&self) -> NodeRef {
200
+ self.root.retained_node_ref()
201
+ }
202
+
203
+ fn build_self(&self) {
204
+ self.sync_visual(self.base.snapshot_state(), false, false);
205
+ self.root.build_self();
206
+ }
207
+ }
208
+
209
+ impl HasFlexBoxRoot for RadioButton {
210
+ fn flex_box_root(&self) -> &FlexBox {
211
+ &self.root
212
+ }
213
+ }
214
+
215
+ #[derive(Clone)]
216
+ struct RadioEventTarget {
217
+ presenter: Rc<RefCell<Rc<dyn RadioIndicatorPresenter>>>,
218
+ checked: Rc<Cell<bool>>,
219
+ colors_value: Rc<Cell<Option<LabeledControlColors>>>,
220
+ changed: Rc<RefCell<Option<RadioButtonChangedCallback>>>,
221
+ owner_group: Rc<RefCell<Option<WeakRadioGroupEventTarget>>>,
222
+ weak_root: Rc<WeakNodeRef>,
223
+ }
224
+
225
+ impl RadioEventTarget {
226
+ fn from_radio(radio: &RadioButton) -> Self {
227
+ Self {
228
+ presenter: radio.indicator_presenter.clone(),
229
+ checked: radio.checked.clone(),
230
+ colors_value: radio.colors_value.clone(),
231
+ changed: radio.changed.clone(),
232
+ owner_group: radio.owner_group.clone(),
233
+ weak_root: radio.weak_root.clone(),
234
+ }
235
+ }
236
+
237
+ fn activate(&self, base_state: PressableLabeledControlState) {
238
+ if let Some(group) = self.owner_group() {
239
+ if let Some(handle) = upgraded_handle(&self.weak_root) {
240
+ group.select_radio_handle(handle.raw(), true);
241
+ return;
242
+ }
243
+ }
244
+ apply_radio_checked(
245
+ &self.presenter,
246
+ &self.weak_root,
247
+ &self.checked,
248
+ true,
249
+ true,
250
+ true,
251
+ &self.changed,
252
+ self.colors_value.get(),
253
+ base_state,
254
+ );
255
+ }
256
+
257
+ fn handle_key_event(&self, event: &mut KeyEventArgs) -> bool {
258
+ if event.event_type != KeyEventType::Down || event.modifiers != 0 {
259
+ return false;
260
+ }
261
+ let Some(group) = self.owner_group() else {
262
+ return false;
263
+ };
264
+ let Some(handle) = upgraded_handle(&self.weak_root) else {
265
+ return false;
266
+ };
267
+ match event.key.as_str() {
268
+ "ArrowLeft" | "ArrowUp" => {
269
+ group.move_selection_from_handle(handle.raw(), -1);
270
+ true
271
+ }
272
+ "ArrowRight" | "ArrowDown" => {
273
+ group.move_selection_from_handle(handle.raw(), 1);
274
+ true
275
+ }
276
+ "Home" => {
277
+ group.select_first_enabled(true);
278
+ true
279
+ }
280
+ "End" => {
281
+ group.select_last_enabled(true);
282
+ true
283
+ }
284
+ _ => false,
285
+ }
286
+ }
287
+
288
+ fn sync_visual(&self, base_state: PressableLabeledControlState, emit: bool, announce: bool) {
289
+ apply_radio_checked(
290
+ &self.presenter,
291
+ &self.weak_root,
292
+ &self.checked,
293
+ self.checked.get(),
294
+ emit,
295
+ announce,
296
+ &self.changed,
297
+ self.colors_value.get(),
298
+ base_state,
299
+ );
300
+ }
301
+
302
+ fn owner_group(&self) -> Option<Rc<RadioGroupEventTarget>> {
303
+ self.owner_group
304
+ .borrow()
305
+ .as_ref()
306
+ .and_then(WeakRadioGroupEventTarget::upgrade)
307
+ }
308
+ }
309
+
310
+ fn apply_radio_checked(
311
+ presenter: &Rc<RefCell<Rc<dyn RadioIndicatorPresenter>>>,
312
+ weak_root: &Rc<WeakNodeRef>,
313
+ checked_cell: &Rc<Cell<bool>>,
314
+ checked: bool,
315
+ emit: bool,
316
+ announce: bool,
317
+ changed: &Rc<RefCell<Option<RadioButtonChangedCallback>>>,
318
+ colors: Option<LabeledControlColors>,
319
+ base_state: PressableLabeledControlState,
320
+ ) {
321
+ let previous = checked_cell.get();
322
+ checked_cell.set(checked);
323
+ update_semantic_checked(
324
+ weak_root,
325
+ if checked {
326
+ SemanticCheckedState::True
327
+ } else {
328
+ SemanticCheckedState::False
329
+ },
330
+ announce && previous != checked,
331
+ );
332
+ presenter.borrow().apply(
333
+ current_theme(),
334
+ RadioIndicatorVisualState::new(
335
+ checked,
336
+ base_state.hovered,
337
+ base_state.pressed,
338
+ base_state.focused,
339
+ base_state.enabled,
340
+ ),
341
+ colors,
342
+ );
343
+ if emit && previous != checked {
344
+ if let Some(callback) = changed.borrow().clone() {
345
+ callback(RadioButtonChangedEventArgs { checked });
346
+ }
347
+ }
348
+ }