@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,274 @@
1
+ use crate::event;
2
+ use crate::ffi;
3
+ use std::rc::Rc;
4
+
5
+ fn with_utf8(value: &str, callback: impl FnOnce(usize, u32)) {
6
+ let bytes = value.as_bytes();
7
+ callback(
8
+ if bytes.is_empty() {
9
+ 0
10
+ } else {
11
+ bytes.as_ptr() as usize
12
+ },
13
+ bytes.len() as u32,
14
+ );
15
+ }
16
+
17
+ #[derive(Clone, Copy, Debug, PartialEq)]
18
+ pub struct PersistedScrollOffset {
19
+ pub x: f32,
20
+ pub y: f32,
21
+ }
22
+
23
+ #[derive(Clone, Debug, PartialEq, Eq)]
24
+ pub struct PersistedTextState {
25
+ pub version: u32,
26
+ pub payload: String,
27
+ }
28
+
29
+ pub trait PersistedStateAdapter {
30
+ fn kind(&self) -> &str;
31
+ fn version(&self) -> u32;
32
+ fn capture(&self) -> Option<String>;
33
+ fn restore(&self, payload: &str, version: u32);
34
+ }
35
+
36
+ pub trait PersistedStateCodec<T>: 'static {
37
+ fn encode(&self, value: T) -> String;
38
+ fn decode(&self, payload: &str, version: u32) -> T;
39
+ }
40
+
41
+ pub struct PersistedStringCodec;
42
+ pub struct PersistedBoolCodec;
43
+ pub struct PersistedInt32Codec;
44
+ pub struct PersistedFloat32Codec;
45
+
46
+ impl PersistedStateCodec<String> for PersistedStringCodec {
47
+ fn encode(&self, value: String) -> String {
48
+ value
49
+ }
50
+
51
+ fn decode(&self, payload: &str, _version: u32) -> String {
52
+ payload.to_string()
53
+ }
54
+ }
55
+
56
+ impl PersistedStateCodec<bool> for PersistedBoolCodec {
57
+ fn encode(&self, value: bool) -> String {
58
+ value.to_string()
59
+ }
60
+
61
+ fn decode(&self, payload: &str, _version: u32) -> bool {
62
+ payload.parse::<bool>().unwrap_or(false)
63
+ }
64
+ }
65
+
66
+ impl PersistedStateCodec<i32> for PersistedInt32Codec {
67
+ fn encode(&self, value: i32) -> String {
68
+ value.to_string()
69
+ }
70
+
71
+ fn decode(&self, payload: &str, _version: u32) -> i32 {
72
+ payload.parse::<i32>().unwrap_or(0)
73
+ }
74
+ }
75
+
76
+ impl PersistedStateCodec<f32> for PersistedFloat32Codec {
77
+ fn encode(&self, value: f32) -> String {
78
+ value.to_string()
79
+ }
80
+
81
+ fn decode(&self, payload: &str, _version: u32) -> f32 {
82
+ payload.parse::<f32>().unwrap_or(0.0)
83
+ }
84
+ }
85
+
86
+ struct PersistedValueAdapter<T, TCodec>
87
+ where
88
+ T: 'static,
89
+ TCodec: PersistedStateCodec<T>,
90
+ {
91
+ kind: String,
92
+ version: u32,
93
+ codec: TCodec,
94
+ capture_value: Rc<dyn Fn() -> Option<T>>,
95
+ restore_value: Rc<dyn Fn(T)>,
96
+ }
97
+
98
+ impl<T, TCodec> PersistedStateAdapter for PersistedValueAdapter<T, TCodec>
99
+ where
100
+ T: 'static,
101
+ TCodec: PersistedStateCodec<T>,
102
+ {
103
+ fn kind(&self) -> &str {
104
+ &self.kind
105
+ }
106
+
107
+ fn version(&self) -> u32 {
108
+ self.version
109
+ }
110
+
111
+ fn capture(&self) -> Option<String> {
112
+ (self.capture_value)().map(|value| self.codec.encode(value))
113
+ }
114
+
115
+ fn restore(&self, payload: &str, version: u32) {
116
+ (self.restore_value)(self.codec.decode(payload, version));
117
+ }
118
+ }
119
+
120
+ pub fn persisted_value_adapter<T, TCodec>(
121
+ kind: impl Into<String>,
122
+ codec: TCodec,
123
+ version: u32,
124
+ capture_value: impl Fn() -> Option<T> + 'static,
125
+ restore_value: impl Fn(T) + 'static,
126
+ ) -> Rc<dyn PersistedStateAdapter>
127
+ where
128
+ T: 'static,
129
+ TCodec: PersistedStateCodec<T>,
130
+ {
131
+ let kind = kind.into();
132
+ assert!(
133
+ !kind.is_empty(),
134
+ "PersistedStateAdapter requires a non-empty kind."
135
+ );
136
+ Rc::new(PersistedValueAdapter {
137
+ kind,
138
+ version,
139
+ codec,
140
+ capture_value: Rc::new(capture_value),
141
+ restore_value: Rc::new(restore_value),
142
+ })
143
+ }
144
+
145
+ pub fn store_scroll_offset(node_id: &str, x: f32, y: f32) {
146
+ with_utf8(node_id, |node_id_ptr, node_id_len| unsafe {
147
+ ffi::fui_set_persisted_scroll_offset(node_id_ptr, node_id_len, x, y);
148
+ });
149
+ }
150
+
151
+ pub fn store_text_state(node_id: &str, kind: &str, version: u32, payload: &str) {
152
+ with_utf8(node_id, |node_id_ptr, node_id_len| {
153
+ with_utf8(kind, |kind_ptr, kind_len| {
154
+ with_utf8(payload, |payload_ptr, payload_len| unsafe {
155
+ ffi::fui_set_persisted_state(
156
+ node_id_ptr,
157
+ node_id_len,
158
+ kind_ptr,
159
+ kind_len,
160
+ version,
161
+ payload_ptr,
162
+ payload_len,
163
+ );
164
+ })
165
+ })
166
+ });
167
+ }
168
+
169
+ pub fn try_load_scroll_offset(node_id: &str) -> Option<PersistedScrollOffset> {
170
+ let mut x = 0.0f32;
171
+ let mut y = 0.0f32;
172
+ let found = with_utf8_result(node_id, |node_id_ptr, node_id_len| unsafe {
173
+ ffi::fui_try_get_persisted_scroll_offset(
174
+ node_id_ptr,
175
+ node_id_len,
176
+ (&mut x as *mut f32) as usize,
177
+ (&mut y as *mut f32) as usize,
178
+ )
179
+ });
180
+ if found {
181
+ Some(PersistedScrollOffset { x, y })
182
+ } else {
183
+ None
184
+ }
185
+ }
186
+
187
+ pub fn try_load_text_state(node_id: &str, kind: &str) -> Option<PersistedTextState> {
188
+ let mut version = 0u32;
189
+ let payload_ptr = event::__fui_text_buffer() as usize;
190
+ let payload_capacity = event::__fui_text_buffer_size();
191
+ let copied = with_utf8_result(node_id, |node_id_ptr, node_id_len| {
192
+ with_utf8_result(kind, |kind_ptr, kind_len| unsafe {
193
+ ffi::fui_copy_persisted_state(
194
+ node_id_ptr,
195
+ node_id_len,
196
+ kind_ptr,
197
+ kind_len,
198
+ (&mut version as *mut u32) as usize,
199
+ payload_ptr,
200
+ payload_capacity,
201
+ )
202
+ })
203
+ });
204
+ if copied < 0 {
205
+ return None;
206
+ }
207
+ let copied_len = copied as usize;
208
+ if copied_len > payload_capacity as usize {
209
+ panic!("Persisted state payload exceeded shared Rust text buffer capacity.");
210
+ }
211
+ let payload = if copied_len == 0 {
212
+ String::new()
213
+ } else {
214
+ let bytes = unsafe { std::slice::from_raw_parts(payload_ptr as *const u8, copied_len) };
215
+ String::from_utf8_lossy(bytes).into_owned()
216
+ };
217
+ Some(PersistedTextState { version, payload })
218
+ }
219
+
220
+ fn with_utf8_result<T>(value: &str, callback: impl FnOnce(usize, u32) -> T) -> T {
221
+ let bytes = value.as_bytes();
222
+ callback(
223
+ if bytes.is_empty() {
224
+ 0
225
+ } else {
226
+ bytes.as_ptr() as usize
227
+ },
228
+ bytes.len() as u32,
229
+ )
230
+ }
231
+
232
+ #[cfg(test)]
233
+ mod tests {
234
+ use super::{
235
+ persisted_value_adapter, store_scroll_offset, store_text_state, try_load_scroll_offset,
236
+ try_load_text_state, PersistedBoolCodec, PersistedScrollOffset, PersistedTextState,
237
+ };
238
+ use crate::ffi::{self, Call};
239
+
240
+ #[test]
241
+ fn persisted_scroll_round_trips_through_host() {
242
+ ffi::test::reset();
243
+ store_scroll_offset("list", 12.0, 34.0);
244
+ assert_eq!(
245
+ try_load_scroll_offset("list"),
246
+ Some(PersistedScrollOffset { x: 12.0, y: 34.0 }),
247
+ );
248
+ let calls = ffi::test::take_calls();
249
+ assert!(calls.iter().any(|call| matches!(call, Call::SetPersistedScrollOffset { node_id, x, y } if node_id == "list" && *x == 12.0 && *y == 34.0)));
250
+ assert!(calls.iter().any(|call| matches!(call, Call::TryGetPersistedScrollOffset { node_id } if node_id == "list")));
251
+ }
252
+
253
+ #[test]
254
+ fn persisted_text_round_trips_through_host() {
255
+ ffi::test::reset();
256
+ store_text_state("input", "text", 2, "hello");
257
+ assert_eq!(
258
+ try_load_text_state("input", "text"),
259
+ Some(PersistedTextState {
260
+ version: 2,
261
+ payload: "hello".to_string(),
262
+ }),
263
+ );
264
+ let calls = ffi::test::take_calls();
265
+ assert!(calls.iter().any(|call| matches!(call, Call::SetPersistedState { node_id, kind, version, payload } if node_id == "input" && kind == "text" && *version == 2 && payload == "hello")));
266
+ assert!(calls.iter().any(|call| matches!(call, Call::CopyPersistedState { node_id, kind } if node_id == "input" && kind == "text")));
267
+ }
268
+
269
+ #[test]
270
+ #[should_panic(expected = "PersistedStateAdapter requires a non-empty kind.")]
271
+ fn persisted_value_adapter_rejects_empty_kind() {
272
+ let _ = persisted_value_adapter("", PersistedBoolCodec, 1, || Some(true), |_| {});
273
+ }
274
+ }