@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,332 @@
1
+ use crate::bindings::ui;
2
+ use crate::controls::{ContextMenu, ContextMenuAction, MenuItem};
3
+ use crate::event;
4
+ use crate::ffi::{HandleValue, PointerEventType};
5
+ use crate::navigation;
6
+ use crate::node::NodeRef;
7
+ use crate::platform;
8
+ use std::cell::RefCell;
9
+
10
+ thread_local! {
11
+ static ACTIVE_POINTER_SELECTION_HANDLES: RefCell<Vec<u64>> = const { RefCell::new(Vec::new()) };
12
+ static DEFAULT_MENU: RefCell<Option<ContextMenu>> = const { RefCell::new(None) };
13
+ static ACTIVE_MENU_LINK: RefCell<Option<NodeRef>> = const { RefCell::new(None) };
14
+ }
15
+
16
+ fn append_menu_section(items: &mut Vec<MenuItem>, section: Vec<MenuItem>) {
17
+ if section.is_empty() {
18
+ return;
19
+ }
20
+ if !items.is_empty() {
21
+ items.push(MenuItem::separator());
22
+ }
23
+ items.extend(section);
24
+ }
25
+
26
+ fn default_menu() -> ContextMenu {
27
+ DEFAULT_MENU.with(|slot| {
28
+ let mut slot = slot.borrow_mut();
29
+ if let Some(menu) = slot.as_ref() {
30
+ return menu.clone();
31
+ }
32
+ let menu = create_default_menu();
33
+ *slot = Some(menu.clone());
34
+ menu
35
+ })
36
+ }
37
+
38
+ fn resolve_history_shortcut_label(forward: bool) -> String {
39
+ if platform::platform_family() == platform::PlatformFamily::Apple {
40
+ return platform::format_shortcut_label(
41
+ if forward { "]" } else { "[" },
42
+ crate::ffi::KeyModifier::Meta as u32,
43
+ );
44
+ }
45
+ platform::format_shortcut_label(
46
+ if forward { "ArrowRight" } else { "ArrowLeft" },
47
+ crate::ffi::KeyModifier::Alt as u32,
48
+ )
49
+ }
50
+
51
+ pub(crate) fn create_default_menu() -> ContextMenu {
52
+ let menu = ContextMenu::new();
53
+ menu.on_visibility_changed(|event| {
54
+ if !event.visible {
55
+ hide_active_menu();
56
+ }
57
+ });
58
+ DEFAULT_MENU.with(|slot| slot.replace(Some(menu.clone())));
59
+ menu
60
+ }
61
+
62
+ pub(crate) fn track_pointer_event(event_type: PointerEventType, handle: u64) {
63
+ if event_type != PointerEventType::Down {
64
+ return;
65
+ }
66
+
67
+ ACTIVE_POINTER_SELECTION_HANDLES.with(|handles| {
68
+ let mut handles = handles.borrow_mut();
69
+ handles.clear();
70
+ if handle != HandleValue::Invalid as u64 {
71
+ handles.push(handle);
72
+ }
73
+ });
74
+ }
75
+
76
+ pub(crate) fn can_show_for_handle(handle: u64) -> bool {
77
+ let Some(target) = event::registered_node(handle) else {
78
+ return handle == HandleValue::Invalid as u64;
79
+ };
80
+ !has_disabled_context_menu_ancestor(Some(target))
81
+ }
82
+
83
+ pub(crate) fn show_for_current_selection(handle: u64, x: f32, y: f32) -> bool {
84
+ let target_node = event::registered_node(handle);
85
+ if has_disabled_context_menu_ancestor(target_node.clone()) {
86
+ return false;
87
+ }
88
+ if invoke_custom_context_menu_handler(target_node.clone(), handle, x, y) {
89
+ return false;
90
+ }
91
+ let mut items = build_built_in_items(handle, x, y, true);
92
+
93
+ let mut navigation_items = Vec::new();
94
+ if navigation::can_navigate_back() {
95
+ navigation_items.push(
96
+ MenuItem::new("Back", ContextMenuAction::NavigateBack)
97
+ .shortcut_label(resolve_history_shortcut_label(false)),
98
+ );
99
+ }
100
+ if navigation::can_navigate_forward() {
101
+ navigation_items.push(
102
+ MenuItem::new("Forward", ContextMenuAction::NavigateForward)
103
+ .shortcut_label(resolve_history_shortcut_label(true)),
104
+ );
105
+ }
106
+ navigation_items.push(
107
+ MenuItem::new("Reload Page", ContextMenuAction::ReloadPage)
108
+ .shortcut_label(platform::format_primary_shortcut_label("r")),
109
+ );
110
+ append_menu_section(&mut items, navigation_items);
111
+
112
+ if items.is_empty() {
113
+ return false;
114
+ }
115
+
116
+ let menu = default_menu();
117
+ menu.items(items);
118
+ menu.show_from_context_pointer(x, y);
119
+ true
120
+ }
121
+
122
+ pub(crate) fn show_for_long_press(handle: u64, x: f32, y: f32) -> bool {
123
+ let target_node = event::registered_node(handle);
124
+ if has_disabled_context_menu_ancestor(target_node.clone()) {
125
+ return false;
126
+ }
127
+ if invoke_custom_context_menu_handler(target_node, handle, x, y) {
128
+ return true;
129
+ }
130
+ let items = build_built_in_items(handle, x, y, false);
131
+ if items.is_empty() {
132
+ return false;
133
+ }
134
+
135
+ let menu = default_menu();
136
+ menu.items(items);
137
+ menu.show_from_context_pointer(x, y);
138
+ true
139
+ }
140
+
141
+ pub(crate) fn hide_active_menu() {
142
+ release_active_menu_link_preview();
143
+ ContextMenu::hide_active_menu();
144
+ }
145
+
146
+ fn build_built_in_items(
147
+ handle: u64,
148
+ point_x: f32,
149
+ point_y: f32,
150
+ clear_selection_on_background_miss: bool,
151
+ ) -> Vec<MenuItem> {
152
+ release_active_menu_link_preview();
153
+ let target_node = event::registered_node(handle);
154
+ if has_disabled_context_menu_ancestor(target_node.clone()) {
155
+ return Vec::new();
156
+ }
157
+ let mut items = Vec::new();
158
+
159
+ if let Some((link, href)) = resolve_nav_link(target_node.clone()) {
160
+ link.pin_link_preview_for_routing();
161
+ ACTIVE_MENU_LINK.with(|slot| slot.replace(Some(link)));
162
+ append_menu_section(
163
+ &mut items,
164
+ vec![
165
+ MenuItem::new("New Tab", ContextMenuAction::OpenLinkInNewTab).payload(href.clone()),
166
+ MenuItem::new("Open", ContextMenuAction::OpenLink).payload(href),
167
+ ],
168
+ );
169
+ }
170
+
171
+ let selection_hit = ui::is_point_in_selection(point_x, point_y);
172
+ if let Some(text_target) = resolve_text_target(target_node.clone()) {
173
+ append_menu_section(&mut items, build_text_section(&text_target));
174
+ }
175
+
176
+ if let Some(image_url) = resolve_image_url(target_node.clone(), point_x, point_y) {
177
+ append_menu_section(
178
+ &mut items,
179
+ vec![
180
+ MenuItem::new("New Tab", ContextMenuAction::OpenImageInNewTab)
181
+ .payload(image_url.clone()),
182
+ MenuItem::new("Open", ContextMenuAction::OpenImage).payload(image_url),
183
+ ],
184
+ );
185
+ }
186
+
187
+ if selection_hit && resolve_text_target(target_node.clone()).is_none() {
188
+ append_menu_section(
189
+ &mut items,
190
+ vec![MenuItem::new(
191
+ "Copy",
192
+ ContextMenuAction::CopyCurrentSelection,
193
+ )],
194
+ );
195
+ } else if clear_selection_on_background_miss && !selection_hit && items.is_empty() {
196
+ ui::clear_current_selection();
197
+ }
198
+
199
+ items
200
+ }
201
+
202
+ fn has_disabled_context_menu_ancestor(node: Option<NodeRef>) -> bool {
203
+ let mut current = node;
204
+ while let Some(node) = current {
205
+ if node.is_context_menu_disabled_for_routing() {
206
+ return true;
207
+ }
208
+ current = node.parent();
209
+ }
210
+ false
211
+ }
212
+
213
+ fn invoke_custom_context_menu_handler(
214
+ node: Option<NodeRef>,
215
+ target_handle: u64,
216
+ x: f32,
217
+ y: f32,
218
+ ) -> bool {
219
+ let mut current = node;
220
+ while let Some(node) = current {
221
+ if node.is_context_menu_disabled_for_routing() {
222
+ return true;
223
+ }
224
+ if let Some(handler) = node.context_menu_handler_for_routing() {
225
+ handler(crate::node::ContextMenuEventArgs {
226
+ target: crate::node::NodeHandle::from_raw(target_handle),
227
+ x,
228
+ y,
229
+ });
230
+ return true;
231
+ }
232
+ current = node.parent();
233
+ }
234
+ false
235
+ }
236
+
237
+ fn release_active_menu_link_preview() {
238
+ ACTIVE_MENU_LINK.with(|slot| {
239
+ let link = slot.borrow_mut().take();
240
+ if let Some(link) = link {
241
+ link.release_link_preview_for_routing();
242
+ }
243
+ });
244
+ }
245
+
246
+ fn resolve_nav_link(node: Option<NodeRef>) -> Option<(NodeRef, String)> {
247
+ let mut current = node;
248
+ while let Some(node) = current {
249
+ if let Some(href) = node.link_url_for_routing() {
250
+ if !href.is_empty() {
251
+ return Some((node, href));
252
+ }
253
+ }
254
+ current = node.parent();
255
+ }
256
+ None
257
+ }
258
+
259
+ fn resolve_text_target(node: Option<NodeRef>) -> Option<NodeRef> {
260
+ let mut current = node;
261
+ while let Some(node) = current {
262
+ if node.is_selectable_text_for_routing() || node.is_editable_text_for_routing() {
263
+ return Some(node);
264
+ }
265
+ current = node.parent();
266
+ }
267
+ None
268
+ }
269
+
270
+ fn resolve_image_url(node: Option<NodeRef>, point_x: f32, point_y: f32) -> Option<String> {
271
+ let mut current = node.clone();
272
+ while let Some(node) = current {
273
+ if let Some(url) = node.image_url_for_routing() {
274
+ if !url.is_empty() {
275
+ return Some(url);
276
+ }
277
+ }
278
+ current = node.parent();
279
+ }
280
+ node.and_then(|node| resolve_descendant_image_url(&node, point_x, point_y))
281
+ }
282
+
283
+ fn resolve_descendant_image_url(node: &NodeRef, point_x: f32, point_y: f32) -> Option<String> {
284
+ for child in node.children() {
285
+ let bounds = if child.handle() == crate::node::NodeHandle::INVALID {
286
+ [0.0; 4]
287
+ } else {
288
+ ui::get_bounds(child.handle().raw()).unwrap_or([0.0; 4])
289
+ };
290
+ let contains = point_x >= bounds[0]
291
+ && point_y >= bounds[1]
292
+ && point_x <= bounds[0] + bounds[2]
293
+ && point_y <= bounds[1] + bounds[3];
294
+ if !contains {
295
+ continue;
296
+ }
297
+ if let Some(url) = child.image_url_for_routing() {
298
+ if !url.is_empty() {
299
+ return Some(url);
300
+ }
301
+ }
302
+ if let Some(url) = resolve_descendant_image_url(&child, point_x, point_y) {
303
+ return Some(url);
304
+ }
305
+ }
306
+ None
307
+ }
308
+
309
+ fn build_text_section(target: &NodeRef) -> Vec<MenuItem> {
310
+ let handle = target.handle().raw();
311
+ if handle == HandleValue::Invalid as u64 || !target.is_selectable_text_for_routing() {
312
+ return Vec::new();
313
+ }
314
+
315
+ let has_selection = unsafe { crate::ffi::ui_has_text_selection(handle) }
316
+ || unsafe { crate::ffi::fui_has_text_selection_snapshot(handle) };
317
+ let has_text = target
318
+ .text_content_for_routing()
319
+ .map(|content| !content.is_empty())
320
+ .unwrap_or(false);
321
+
322
+ vec![
323
+ MenuItem::new("Copy", ContextMenuAction::CopyCurrentSelection)
324
+ .shortcut_label(platform::format_primary_shortcut_label("c"))
325
+ .disabled(!has_selection)
326
+ .target_handle(handle),
327
+ MenuItem::new("Select All", ContextMenuAction::SelectAllText)
328
+ .shortcut_label(platform::format_primary_shortcut_label("a"))
329
+ .disabled(!has_text)
330
+ .target_handle(handle),
331
+ ]
332
+ }
@@ -0,0 +1,54 @@
1
+ use super::*;
2
+
3
+ #[derive(Clone)]
4
+ pub struct AntiSelectionArea {
5
+ root: FlexBox,
6
+ }
7
+
8
+ impl AntiSelectionArea {
9
+ pub fn new() -> Self {
10
+ let root = flex_box();
11
+ root.selection_area_barrier(true);
12
+ Self { root }
13
+ }
14
+
15
+ pub fn child<T: Node>(&self, node: &T) -> &Self {
16
+ self.root.child(node);
17
+ self
18
+ }
19
+
20
+ pub fn children<I, C>(&self, nodes: I) -> &Self
21
+ where
22
+ I: IntoIterator<Item = C>,
23
+ C: Into<Child>,
24
+ {
25
+ for node in nodes {
26
+ self.root
27
+ .retained_node_ref()
28
+ .append_child_ref(&node.into().node_ref);
29
+ }
30
+ self
31
+ }
32
+ }
33
+
34
+ impl Default for AntiSelectionArea {
35
+ fn default() -> Self {
36
+ Self::new()
37
+ }
38
+ }
39
+
40
+ impl Node for AntiSelectionArea {
41
+ fn retained_node_ref(&self) -> NodeRef {
42
+ self.root.retained_node_ref()
43
+ }
44
+
45
+ fn build_self(&self) {
46
+ self.root.build_self();
47
+ }
48
+ }
49
+
50
+ impl HasFlexBoxRoot for AntiSelectionArea {
51
+ fn flex_box_root(&self) -> &FlexBox {
52
+ &self.root
53
+ }
54
+ }