@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,283 @@
1
+ use super::*;
2
+ use crate::logger;
3
+ use std::cell::{Cell, RefCell};
4
+ use std::rc::{Rc, Weak};
5
+
6
+ #[derive(Clone)]
7
+ pub(crate) struct RadioGroupEventTarget {
8
+ radios: Rc<RefCell<Vec<RadioButton>>>,
9
+ selected_index: Rc<Cell<i32>>,
10
+ changed: Rc<RefCell<Option<RadioGroupChangedCallback>>>,
11
+ }
12
+
13
+ impl RadioGroupEventTarget {
14
+ pub(crate) fn select_radio_handle(&self, radio_handle: u64, focus: bool) {
15
+ let index = self.index_of_radio_handle(radio_handle);
16
+ if index >= 0 {
17
+ self.select_index_internal(index, focus, true, true);
18
+ }
19
+ }
20
+
21
+ pub(crate) fn move_selection_from_handle(&self, radio_handle: u64, delta: i32) {
22
+ let start_index = self.index_of_radio_handle(radio_handle);
23
+ if start_index < 0 || self.radios.borrow().is_empty() {
24
+ return;
25
+ }
26
+ let next_index = self.find_enabled_index(start_index, delta);
27
+ if next_index >= 0 {
28
+ self.select_index_internal(next_index, true, true, true);
29
+ }
30
+ }
31
+
32
+ pub(crate) fn select_first_enabled(&self, focus: bool) {
33
+ let next_index = self.find_boundary_index(true);
34
+ if next_index >= 0 {
35
+ self.select_index_internal(next_index, focus, true, true);
36
+ }
37
+ }
38
+
39
+ pub(crate) fn select_last_enabled(&self, focus: bool) {
40
+ let next_index = self.find_boundary_index(false);
41
+ if next_index >= 0 {
42
+ self.select_index_internal(next_index, focus, true, true);
43
+ }
44
+ }
45
+
46
+ pub(crate) fn select_index(&self, index: i32) {
47
+ if index == -1 {
48
+ let previous_index = self.selected_index.get();
49
+ let changed = previous_index != -1;
50
+ if let Some(previous) = self.radio_at(previous_index) {
51
+ previous.update_checked(false, true, false);
52
+ }
53
+ self.selected_index.set(-1);
54
+ if changed {
55
+ self.emit_changed(String::new());
56
+ }
57
+ return;
58
+ }
59
+ let radio_count = self.radios.borrow().len() as i32;
60
+ if radio_count == 0 {
61
+ if index != -1 {
62
+ logger::warn(
63
+ "Layout",
64
+ &format!(
65
+ "RadioGroup.select_index() received {index} before any radios were added."
66
+ ),
67
+ );
68
+ }
69
+ return;
70
+ }
71
+ let clamped_index = if index < 0 {
72
+ 0
73
+ } else if index >= radio_count {
74
+ radio_count - 1
75
+ } else {
76
+ index
77
+ };
78
+ if clamped_index != index {
79
+ logger::warn(
80
+ "Layout",
81
+ &format!(
82
+ "RadioGroup.select_index() received {index}; clamping to {clamped_index}."
83
+ ),
84
+ );
85
+ }
86
+ self.select_index_internal(clamped_index, false, true, false);
87
+ }
88
+
89
+ fn select_index_internal(&self, index: i32, focus: bool, emit: bool, announce: bool) {
90
+ let Some(radio) = self.radio_at(index) else {
91
+ return;
92
+ };
93
+ if !radio.is_enabled() {
94
+ return;
95
+ }
96
+ let previous_index = self.selected_index.get();
97
+ if previous_index == index {
98
+ if focus {
99
+ radio.focus_now();
100
+ }
101
+ return;
102
+ }
103
+ if let Some(previous) = self.radio_at(previous_index) {
104
+ previous.update_checked(false, emit, false);
105
+ }
106
+ self.selected_index.set(index);
107
+ radio.update_checked(true, emit, announce);
108
+ if focus {
109
+ radio.focus_now();
110
+ }
111
+ if emit {
112
+ self.emit_changed(radio.value().to_string());
113
+ }
114
+ }
115
+
116
+ fn emit_changed(&self, value: String) {
117
+ if let Some(callback) = self.changed.borrow().clone() {
118
+ callback(RadioGroupChangedEventArgs { value });
119
+ }
120
+ }
121
+
122
+ fn index_of_radio_handle(&self, target_handle: u64) -> i32 {
123
+ self.radios
124
+ .borrow()
125
+ .iter()
126
+ .position(|radio| radio.retained_node_ref().handle().raw() == target_handle)
127
+ .map(|index| index as i32)
128
+ .unwrap_or(-1)
129
+ }
130
+
131
+ fn find_enabled_index(&self, start_index: i32, delta: i32) -> i32 {
132
+ let radios = self.radios.borrow();
133
+ if radios.is_empty() {
134
+ return -1;
135
+ }
136
+ let len = radios.len() as i32;
137
+ let mut cursor = start_index;
138
+ for _ in 0..len {
139
+ cursor += delta;
140
+ if cursor < 0 {
141
+ cursor = len - 1;
142
+ } else if cursor >= len {
143
+ cursor = 0;
144
+ }
145
+ if radios[cursor as usize].is_enabled() {
146
+ return cursor;
147
+ }
148
+ }
149
+ -1
150
+ }
151
+
152
+ fn find_boundary_index(&self, first: bool) -> i32 {
153
+ let radios = self.radios.borrow();
154
+ if first {
155
+ radios
156
+ .iter()
157
+ .position(|radio| radio.is_enabled())
158
+ .map(|index| index as i32)
159
+ .unwrap_or(-1)
160
+ } else {
161
+ radios
162
+ .iter()
163
+ .rposition(|radio| radio.is_enabled())
164
+ .map(|index| index as i32)
165
+ .unwrap_or(-1)
166
+ }
167
+ }
168
+
169
+ fn radio_at(&self, index: i32) -> Option<RadioButton> {
170
+ if index < 0 {
171
+ return None;
172
+ }
173
+ self.radios.borrow().get(index as usize).cloned()
174
+ }
175
+ }
176
+
177
+ #[derive(Clone)]
178
+ pub struct RadioGroup {
179
+ root: FlexBox,
180
+ event_target: Rc<RadioGroupEventTarget>,
181
+ }
182
+
183
+ impl RadioGroup {
184
+ pub fn new() -> Self {
185
+ let root = flex_box();
186
+ root.semantic_role(SemanticRole::RadioGroup)
187
+ .flex_direction(FlexDirection::Column);
188
+ let event_target = Rc::new(RadioGroupEventTarget {
189
+ radios: Rc::new(RefCell::new(Vec::new())),
190
+ selected_index: Rc::new(Cell::new(-1)),
191
+ changed: Rc::new(RefCell::new(None)),
192
+ });
193
+ root.retained_node_ref()
194
+ .retain_attachment(event_target.clone());
195
+ let control = Self { root, event_target };
196
+ let selected_index = control.event_target.selected_index.clone();
197
+ let restore_target = control.event_target.clone();
198
+ control.persist_state(crate::persisted::persisted_value_adapter(
199
+ "radio-group-selected-index",
200
+ crate::persisted::PersistedInt32Codec,
201
+ 1,
202
+ move || {
203
+ let index = selected_index.get();
204
+ if index >= 0 {
205
+ Some(index)
206
+ } else {
207
+ None
208
+ }
209
+ },
210
+ move |index| {
211
+ restore_target.select_index(index);
212
+ },
213
+ ));
214
+ control
215
+ }
216
+
217
+ pub fn add_radio(&self, radio: RadioButton) -> &Self {
218
+ radio.bind_group(Rc::downgrade(&self.event_target));
219
+ if self.selected_index() < 0 && radio.is_checked() {
220
+ self.event_target
221
+ .selected_index
222
+ .set(self.event_target.radios.borrow().len() as i32);
223
+ }
224
+ self.root.child(&radio);
225
+ self.event_target.radios.borrow_mut().push(radio);
226
+ self
227
+ }
228
+
229
+ pub fn add_option(&self, value: impl Into<String>, label: impl Into<String>) -> RadioButton {
230
+ let radio = RadioButton::with_label(value, label);
231
+ self.add_radio(radio.clone());
232
+ radio
233
+ }
234
+
235
+ pub fn add_options<I>(&self, radios: I) -> &Self
236
+ where
237
+ I: IntoIterator<Item = RadioButton>,
238
+ {
239
+ for radio in radios {
240
+ self.add_radio(radio);
241
+ }
242
+ self
243
+ }
244
+
245
+ pub fn on_changed(&self, handler: impl Fn(RadioGroupChangedEventArgs) + 'static) -> &Self {
246
+ *self.event_target.changed.borrow_mut() = Some(Rc::new(handler));
247
+ self
248
+ }
249
+
250
+ pub fn selected_index(&self) -> i32 {
251
+ self.event_target.selected_index.get()
252
+ }
253
+
254
+ pub fn selected_value(&self) -> String {
255
+ self.event_target
256
+ .radio_at(self.selected_index())
257
+ .map(|radio| radio.value().to_string())
258
+ .unwrap_or_default()
259
+ }
260
+
261
+ pub fn select_index(&self, index: i32) -> &Self {
262
+ self.event_target.select_index(index);
263
+ self
264
+ }
265
+ }
266
+
267
+ impl Default for RadioGroup {
268
+ fn default() -> Self {
269
+ Self::new()
270
+ }
271
+ }
272
+
273
+ impl Node for RadioGroup {
274
+ fn retained_node_ref(&self) -> NodeRef {
275
+ self.root.retained_node_ref()
276
+ }
277
+
278
+ fn build_self(&self) {
279
+ self.root.build_self();
280
+ }
281
+ }
282
+
283
+ pub(crate) type WeakRadioGroupEventTarget = Weak<RadioGroupEventTarget>;
@@ -0,0 +1,82 @@
1
+ use super::*;
2
+ use crate::signal::{Callback, Signal, Subscription};
3
+
4
+ #[derive(Clone)]
5
+ pub struct SelectionArea {
6
+ root: FlexBox,
7
+ selected_text_signal: Rc<RefCell<Signal<String>>>,
8
+ }
9
+
10
+ impl SelectionArea {
11
+ pub fn new() -> Self {
12
+ let root = flex_box();
13
+ root.selection_area(true);
14
+ let selected_text_signal = Rc::new(RefCell::new(Signal::new(String::new())));
15
+ let signal = selected_text_signal.clone();
16
+ root.core.borrow_mut().handlers.cross_selection_changed = Some(Rc::new(move |text| {
17
+ let callbacks = signal.borrow_mut().set(text);
18
+ if let Some(callbacks) = callbacks {
19
+ for callback in callbacks {
20
+ callback();
21
+ }
22
+ }
23
+ }));
24
+ Self {
25
+ root,
26
+ selected_text_signal,
27
+ }
28
+ }
29
+
30
+ pub fn child<T: Node>(&self, node: &T) -> &Self {
31
+ self.root.child(node);
32
+ self
33
+ }
34
+
35
+ pub fn children<I, C>(&self, nodes: I) -> &Self
36
+ where
37
+ I: IntoIterator<Item = C>,
38
+ C: Into<Child>,
39
+ {
40
+ for node in nodes {
41
+ self.root
42
+ .retained_node_ref()
43
+ .append_child_ref(&node.into().node_ref);
44
+ }
45
+ self
46
+ }
47
+
48
+ pub fn selected_text(&self) -> String {
49
+ self.selected_text_signal.borrow().get()
50
+ }
51
+
52
+ pub fn subscribe_selected_text(&self, handler: impl Fn(String) + 'static) -> Subscription {
53
+ let signal = self.selected_text_signal.clone();
54
+ let callback: Callback = Rc::new(move || handler(signal.borrow().get()));
55
+ self.selected_text_signal.borrow_mut().subscribe(callback)
56
+ }
57
+ }
58
+
59
+ impl Default for SelectionArea {
60
+ fn default() -> Self {
61
+ Self::new()
62
+ }
63
+ }
64
+
65
+ impl Node for SelectionArea {
66
+ fn retained_node_ref(&self) -> NodeRef {
67
+ let selection_area = self.clone();
68
+ self.root
69
+ .retained_node_ref()
70
+ .with_build_callback(move || selection_area.build())
71
+ }
72
+
73
+ fn build_self(&self) {
74
+ self.root.build_self();
75
+ }
76
+ }
77
+
78
+ impl HasFlexBoxRoot for SelectionArea {
79
+ fn flex_box_root(&self) -> &FlexBox {
80
+ &self.root
81
+ }
82
+ }
@@ -0,0 +1,68 @@
1
+ use super::*;
2
+
3
+ pub(crate) const PROGRESS_LENGTH: f32 = 220.0;
4
+ pub(crate) const PROGRESS_THICKNESS: f32 = 14.0;
5
+ pub(crate) const SLIDER_FOCUS_BORDER_WIDTH: f32 = 2.0;
6
+ pub(crate) const SLIDER_PADDING: f32 = 2.0;
7
+ pub(crate) const SLIDER_CONTENT_INSET: f32 = 1.0;
8
+ pub(crate) const SLIDER_OUTER_INSET: f32 =
9
+ SLIDER_FOCUS_BORDER_WIDTH + SLIDER_PADDING + SLIDER_CONTENT_INSET;
10
+ pub(crate) const SLIDER_CHILD_INSET: f32 = SLIDER_PADDING + SLIDER_CONTENT_INSET;
11
+
12
+ pub(crate) type ClickCallback = Rc<dyn Fn(ClickEventArgs)>;
13
+ pub(crate) type CheckboxChangedCallback = Rc<dyn Fn(CheckboxChangedEventArgs)>;
14
+ pub(crate) type RadioButtonChangedCallback = Rc<dyn Fn(RadioButtonChangedEventArgs)>;
15
+ pub(crate) type RadioGroupChangedCallback = Rc<dyn Fn(RadioGroupChangedEventArgs)>;
16
+ pub(crate) type SwitchChangedCallback = Rc<dyn Fn(SwitchChangedEventArgs)>;
17
+ pub(crate) type SliderChangedCallback = Rc<dyn Fn(SliderChangedEventArgs)>;
18
+
19
+ pub(crate) fn is_activation_key(event: &KeyEventArgs) -> bool {
20
+ event.key == "Enter" || event.key == " " || event.key == "Space" || event.key == "Spacebar"
21
+ }
22
+
23
+ pub(crate) fn fire_click_callbacks(
24
+ click: &Rc<RefCell<Option<ClickCallback>>>,
25
+ double_click: &Rc<RefCell<Option<ClickCallback>>>,
26
+ triple_click: &Rc<RefCell<Option<ClickCallback>>>,
27
+ click_count: i32,
28
+ ) {
29
+ let args = ClickEventArgs { click_count };
30
+ if let Some(callback) = click.borrow().clone() {
31
+ callback(args);
32
+ }
33
+ if click_count == 3 {
34
+ if let Some(callback) = triple_click.borrow().clone() {
35
+ callback(args);
36
+ }
37
+ } else if click_count == 2 {
38
+ if let Some(callback) = double_click.borrow().clone() {
39
+ callback(args);
40
+ }
41
+ }
42
+ }
43
+
44
+ pub(crate) fn normalize_slider_value(value: f32, min: f32, max: f32, step: f32) -> f32 {
45
+ let clamped = value.clamp(min.min(max), min.max(max));
46
+ if step <= 0.0 {
47
+ return clamped;
48
+ }
49
+ let snapped = ((clamped - min) / step).round() * step;
50
+ (min + snapped).clamp(min.min(max), min.max(max))
51
+ }
52
+
53
+ pub(crate) fn upgraded_handle(weak_root: &Rc<WeakNodeRef>) -> Option<crate::node::NodeHandle> {
54
+ weak_root.upgrade().map(|node| node.handle())
55
+ }
56
+
57
+ pub(crate) fn update_semantic_checked(
58
+ weak_root: &Rc<WeakNodeRef>,
59
+ state: SemanticCheckedState,
60
+ announce: bool,
61
+ ) {
62
+ if let Some(handle) = upgraded_handle(weak_root) {
63
+ ui::set_semantic_checked(handle.raw(), state as u32);
64
+ if announce {
65
+ ui::request_semantic_announcement(handle.raw());
66
+ }
67
+ }
68
+ }