@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,474 @@
1
+ use super::core::*;
2
+ use super::*;
3
+ use crate::assets::{
4
+ acquire_svg_asset, get_svg_asset_error, get_svg_asset_height, get_svg_asset_state,
5
+ get_svg_asset_url, get_svg_asset_width, release_svg_asset, AssetLoadState,
6
+ };
7
+ use crate::image_sampling::ImageSampling;
8
+ use crate::signal::{Callback, SubscriptionGuard};
9
+ use std::rc::Weak;
10
+
11
+ struct SvgState {
12
+ source_url: String,
13
+ owned_svg_asset_id: u32,
14
+ requested_width_value: f32,
15
+ requested_width_unit: Unit,
16
+ has_requested_width: bool,
17
+ requested_height_value: f32,
18
+ requested_height_unit: Unit,
19
+ has_requested_height: bool,
20
+ tracked_svg_asset_id: u32,
21
+ asset_state_subscription: Option<SubscriptionGuard>,
22
+ }
23
+
24
+ impl Default for SvgState {
25
+ fn default() -> Self {
26
+ Self {
27
+ source_url: String::new(),
28
+ owned_svg_asset_id: 0,
29
+ requested_width_value: 0.0,
30
+ requested_width_unit: Unit::Auto,
31
+ has_requested_width: false,
32
+ requested_height_value: 0.0,
33
+ requested_height_unit: Unit::Auto,
34
+ has_requested_height: false,
35
+ tracked_svg_asset_id: 0,
36
+ asset_state_subscription: None,
37
+ }
38
+ }
39
+ }
40
+
41
+ #[derive(Clone)]
42
+ pub struct SvgNode {
43
+ core: Rc<RefCell<NodeCore>>,
44
+ props: Rc<RefCell<SvgProps>>,
45
+ state: Rc<RefCell<SvgState>>,
46
+ }
47
+
48
+ impl SvgNode {
49
+ pub fn new(svg_id: u32) -> Self {
50
+ let node = Self {
51
+ core: Rc::new(RefCell::new(NodeCore::new(NodeKind::Svg))),
52
+ props: Rc::new(RefCell::new(SvgProps {
53
+ width: None,
54
+ height: None,
55
+ svg_id,
56
+ source_url: None,
57
+ tint_color: 0,
58
+ sampling_kind: ImageSamplingKind::Linear,
59
+ max_aniso: 0,
60
+ opacity: None,
61
+ })),
62
+ state: Rc::new(RefCell::new(SvgState {
63
+ requested_width_value: 0.0,
64
+ requested_width_unit: Unit::Auto,
65
+ has_requested_width: true,
66
+ requested_height_value: 0.0,
67
+ requested_height_unit: Unit::Auto,
68
+ has_requested_height: true,
69
+ ..SvgState::default()
70
+ })),
71
+ };
72
+ node.attach_asset_state_listener();
73
+ node
74
+ }
75
+
76
+ pub fn width(&self, width: f32, unit: Unit) -> &Self {
77
+ {
78
+ let mut state = self.state.borrow_mut();
79
+ state.requested_width_value = width;
80
+ state.requested_width_unit = unit;
81
+ state.has_requested_width = true;
82
+ }
83
+ self.apply_resolved_sizing();
84
+ self
85
+ }
86
+
87
+ pub fn height(&self, height: f32, unit: Unit) -> &Self {
88
+ {
89
+ let mut state = self.state.borrow_mut();
90
+ state.requested_height_value = height;
91
+ state.requested_height_unit = unit;
92
+ state.has_requested_height = true;
93
+ }
94
+ self.apply_resolved_sizing();
95
+ self
96
+ }
97
+
98
+ pub fn svg(&self, svg_id: u32) -> &Self {
99
+ self.release_owned_source_asset();
100
+ self.state.borrow_mut().source_url.clear();
101
+ self.props.borrow_mut().svg_id = svg_id;
102
+ self.props.borrow_mut().source_url = None;
103
+ self.retained_node_ref().set_image_url_for_routing(None);
104
+ self.attach_asset_state_listener();
105
+ self.apply_resolved_sizing();
106
+ if self.has_built_handle() {
107
+ self.apply_svg_source();
108
+ self.notify_retained_mutation();
109
+ }
110
+ self
111
+ }
112
+
113
+ pub fn source(&self, url: impl Into<String>) -> &Self {
114
+ let url = url.into();
115
+ if url.is_empty() {
116
+ return self.clear_source();
117
+ }
118
+ if self.state.borrow().owned_svg_asset_id != 0 && self.state.borrow().source_url == url {
119
+ return self;
120
+ }
121
+ self.release_owned_source_asset();
122
+ {
123
+ let mut state = self.state.borrow_mut();
124
+ state.source_url = url.clone();
125
+ }
126
+ let svg_id = acquire_svg_asset(&url);
127
+ {
128
+ let mut props = self.props.borrow_mut();
129
+ props.svg_id = svg_id;
130
+ props.source_url = Some(url.clone());
131
+ }
132
+ self.state.borrow_mut().owned_svg_asset_id = svg_id;
133
+ self.retained_node_ref()
134
+ .set_image_url_for_routing(Some(url.clone()));
135
+ self.attach_asset_state_listener();
136
+ self.apply_resolved_sizing();
137
+ if self.has_built_handle() {
138
+ self.apply_svg_source();
139
+ self.notify_retained_mutation();
140
+ }
141
+ self
142
+ }
143
+
144
+ pub fn source_url(&self, url: impl Into<String>) -> &Self {
145
+ self.source(url)
146
+ }
147
+
148
+ pub fn clear_source(&self) -> &Self {
149
+ self.release_owned_source_asset();
150
+ {
151
+ let mut state = self.state.borrow_mut();
152
+ state.source_url.clear();
153
+ }
154
+ {
155
+ let mut props = self.props.borrow_mut();
156
+ props.svg_id = 0;
157
+ props.source_url = None;
158
+ }
159
+ self.retained_node_ref().set_image_url_for_routing(None);
160
+ self.attach_asset_state_listener();
161
+ self.apply_resolved_sizing();
162
+ if self.has_built_handle() {
163
+ self.apply_svg_source();
164
+ self.notify_retained_mutation();
165
+ }
166
+ self
167
+ }
168
+
169
+ pub fn clear_source_url(&self) -> &Self {
170
+ self.clear_source()
171
+ }
172
+
173
+ pub fn tint(&self, tint_color: u32) -> &Self {
174
+ self.props.borrow_mut().tint_color = tint_color;
175
+ if self.has_built_handle() {
176
+ self.apply_svg_source();
177
+ self.notify_retained_mutation();
178
+ }
179
+ self
180
+ }
181
+
182
+ pub fn opacity(&self, value: f32) -> &Self {
183
+ self.props.borrow_mut().opacity = Some(value.clamp(0.0, 1.0));
184
+ if self.has_built_handle() {
185
+ self.build_self();
186
+ self.notify_retained_mutation();
187
+ }
188
+ self
189
+ }
190
+
191
+ pub fn alt_text(&self, value: impl Into<String>) -> &Self {
192
+ let value = value.into();
193
+ self.semantic_role(SemanticRole::Image);
194
+ self.semantic_label(value);
195
+ self
196
+ }
197
+
198
+ pub fn sampling(&self, sampling: ImageSampling) -> &Self {
199
+ let mut props = self.props.borrow_mut();
200
+ props.sampling_kind = sampling.ffi_kind();
201
+ props.max_aniso = sampling.max_aniso();
202
+ drop(props);
203
+ if self.has_built_handle() {
204
+ self.apply_svg_source();
205
+ self.notify_retained_mutation();
206
+ }
207
+ self
208
+ }
209
+
210
+ pub fn interactive(&self, interactive: bool) -> &Self {
211
+ self.core.borrow_mut().behavior.interactive = interactive;
212
+ self
213
+ }
214
+
215
+ pub fn on_click(&self, handler: impl Fn(&mut PointerEventArgs) + 'static) -> &Self {
216
+ self.core.borrow_mut().handlers.pointer_click = Some(Rc::new(handler));
217
+ self.retained_node_ref().require_interactive();
218
+ self
219
+ }
220
+
221
+ pub fn asset_state_signal(&self) -> crate::assets::AssetStateSignal {
222
+ get_svg_asset_state(self.props.borrow().svg_id)
223
+ }
224
+
225
+ pub fn asset_state(&self) -> AssetLoadState {
226
+ self.asset_state_signal().get()
227
+ }
228
+
229
+ pub fn asset_error(&self) -> String {
230
+ get_svg_asset_error(self.props.borrow().svg_id)
231
+ }
232
+
233
+ pub fn asset_url(&self) -> String {
234
+ let source_url = self.state.borrow().source_url.clone();
235
+ if source_url.is_empty() {
236
+ get_svg_asset_url(self.props.borrow().svg_id)
237
+ } else {
238
+ source_url
239
+ }
240
+ }
241
+
242
+ pub fn asset_width(&self) -> f32 {
243
+ get_svg_asset_width(self.props.borrow().svg_id)
244
+ }
245
+
246
+ pub fn asset_height(&self) -> f32 {
247
+ get_svg_asset_height(self.props.borrow().svg_id)
248
+ }
249
+
250
+ fn apply_svg_source(&self) {
251
+ let props = self.props.borrow();
252
+ ui::set_svg(
253
+ self.handle().raw(),
254
+ props.svg_id,
255
+ props.tint_color,
256
+ props.sampling_kind,
257
+ props.max_aniso,
258
+ );
259
+ }
260
+
261
+ fn apply_resolved_sizing(&self) {
262
+ let (
263
+ has_requested_width,
264
+ requested_width_value,
265
+ requested_width_unit,
266
+ has_requested_height,
267
+ requested_height_value,
268
+ requested_height_unit,
269
+ ) = {
270
+ let state = self.state.borrow();
271
+ (
272
+ state.has_requested_width,
273
+ state.requested_width_value,
274
+ state.requested_width_unit,
275
+ state.has_requested_height,
276
+ state.requested_height_value,
277
+ state.requested_height_unit,
278
+ )
279
+ };
280
+ if !has_requested_width && !has_requested_height {
281
+ return;
282
+ }
283
+
284
+ let asset_width = self.asset_width();
285
+ let asset_height = self.asset_height();
286
+ let has_intrinsic_size = asset_width > 0.0 && asset_height > 0.0;
287
+ let mut props = self.props.borrow_mut();
288
+
289
+ if has_requested_width {
290
+ let mut resolved_width_value = requested_width_value;
291
+ let mut resolved_width_unit = requested_width_unit;
292
+ if requested_width_unit == Unit::Auto && has_intrinsic_size {
293
+ if has_requested_height && requested_height_unit == Unit::Pixel {
294
+ resolved_width_value = requested_height_value * (asset_width / asset_height);
295
+ } else {
296
+ resolved_width_value = asset_width;
297
+ }
298
+ resolved_width_unit = Unit::Pixel;
299
+ }
300
+ props.width = Some((resolved_width_value, resolved_width_unit));
301
+ if self.has_built_handle() {
302
+ ui::set_width(
303
+ self.handle().raw(),
304
+ resolved_width_value,
305
+ resolved_width_unit as u32,
306
+ );
307
+ }
308
+ }
309
+
310
+ if has_requested_height {
311
+ let mut resolved_height_value = requested_height_value;
312
+ let mut resolved_height_unit = requested_height_unit;
313
+ if requested_height_unit == Unit::Auto && has_intrinsic_size {
314
+ if has_requested_width && requested_width_unit == Unit::Pixel {
315
+ resolved_height_value = requested_width_value * (asset_height / asset_width);
316
+ } else {
317
+ resolved_height_value = asset_height;
318
+ }
319
+ resolved_height_unit = Unit::Pixel;
320
+ }
321
+ props.height = Some((resolved_height_value, resolved_height_unit));
322
+ if self.has_built_handle() {
323
+ ui::set_height(
324
+ self.handle().raw(),
325
+ resolved_height_value,
326
+ resolved_height_unit as u32,
327
+ );
328
+ }
329
+ }
330
+
331
+ drop(props);
332
+ if self.has_built_handle() {
333
+ self.notify_retained_layout_mutation();
334
+ }
335
+ }
336
+
337
+ fn attach_asset_state_listener(&self) {
338
+ let svg_id = self.props.borrow().svg_id;
339
+ {
340
+ let mut state = self.state.borrow_mut();
341
+ if state.tracked_svg_asset_id == svg_id {
342
+ return;
343
+ }
344
+ state.asset_state_subscription = None;
345
+ state.tracked_svg_asset_id = svg_id;
346
+ if svg_id == 0 {
347
+ return;
348
+ }
349
+ }
350
+
351
+ let core_weak: Weak<RefCell<NodeCore>> = Rc::downgrade(&self.core);
352
+ let props_weak: Weak<RefCell<SvgProps>> = Rc::downgrade(&self.props);
353
+ let state_weak: Weak<RefCell<SvgState>> = Rc::downgrade(&self.state);
354
+ let callback: Callback = Rc::new(move || {
355
+ if let (Some(core), Some(props), Some(state)) = (
356
+ core_weak.upgrade(),
357
+ props_weak.upgrade(),
358
+ state_weak.upgrade(),
359
+ ) {
360
+ let node = SvgNode { core, props, state };
361
+ node.apply_resolved_sizing();
362
+ }
363
+ });
364
+
365
+ let guard = get_svg_asset_state(svg_id).subscribe(callback);
366
+ self.state.borrow_mut().asset_state_subscription = Some(guard);
367
+ }
368
+
369
+ fn release_owned_source_asset(&self) {
370
+ let owned_svg_asset_id = self.state.borrow().owned_svg_asset_id;
371
+ if owned_svg_asset_id == 0 {
372
+ return;
373
+ }
374
+ release_svg_asset(owned_svg_asset_id);
375
+ self.state.borrow_mut().owned_svg_asset_id = 0;
376
+ }
377
+
378
+ #[cfg(test)]
379
+ pub(crate) fn test_svg_id(&self) -> u32 {
380
+ self.props.borrow().svg_id
381
+ }
382
+ }
383
+
384
+ impl Node for SvgNode {
385
+ fn retained_node_ref(&self) -> NodeRef {
386
+ NodeRef::from_node(self.core.clone(), self.clone())
387
+ }
388
+
389
+ fn build_self(&self) {
390
+ self.apply_resolved_sizing();
391
+ apply_svg_props(
392
+ self.handle(),
393
+ &self.props.borrow(),
394
+ self.core.borrow().behavior.clone(),
395
+ );
396
+ }
397
+ }
398
+
399
+ impl Drop for SvgNode {
400
+ fn drop(&mut self) {
401
+ if Rc::strong_count(&self.state) == 1 {
402
+ self.state.borrow_mut().asset_state_subscription = None;
403
+ let owned_svg_asset_id = self.state.borrow().owned_svg_asset_id;
404
+ if owned_svg_asset_id != 0 {
405
+ release_svg_asset(owned_svg_asset_id);
406
+ self.state.borrow_mut().owned_svg_asset_id = 0;
407
+ }
408
+ }
409
+ }
410
+ }
411
+
412
+ #[cfg(test)]
413
+ mod tests {
414
+ use super::*;
415
+ use crate::assets::{self, AssetLoadState};
416
+ use crate::bridge_callbacks;
417
+ use crate::ffi::{self, Call};
418
+ use crate::Application;
419
+
420
+ #[test]
421
+ fn source_url_reuses_registry_asset_and_resizes_after_ready() {
422
+ assets::test_reset();
423
+ ffi::test::reset();
424
+
425
+ let svg = SvgNode::new(0);
426
+ svg.source("/img/icon.svg");
427
+ let svg_id = svg.test_svg_id();
428
+ assert!(svg_id >= 0x1000_0000);
429
+ assert_eq!(svg.asset_state(), AssetLoadState::Loading);
430
+
431
+ Application::mount(svg.clone());
432
+ ffi::test::take_calls();
433
+
434
+ bridge_callbacks::__fui_on_svg_loaded(svg_id, 30.0, 15.0);
435
+ let calls = ffi::test::take_calls();
436
+ assert!(calls.iter().any(|call| matches!(
437
+ call,
438
+ Call::SetWidth { value, unit_enum, .. } if *value == 30.0 && *unit_enum == Unit::Pixel as u32
439
+ )));
440
+ assert!(calls.iter().any(|call| matches!(
441
+ call,
442
+ Call::SetHeight { value, unit_enum, .. } if *value == 15.0 && *unit_enum == Unit::Pixel as u32
443
+ )));
444
+ assert_eq!(svg.asset_width(), 30.0);
445
+ assert_eq!(svg.asset_height(), 15.0);
446
+ assert_eq!(svg.asset_state(), AssetLoadState::Ready);
447
+ }
448
+
449
+ #[test]
450
+ fn explicit_svg_id_attaches_listener_and_resizes_after_ready() {
451
+ assets::test_reset();
452
+ ffi::test::reset();
453
+
454
+ let svg = SvgNode::new(88);
455
+ assert_eq!(svg.asset_state(), AssetLoadState::Idle);
456
+
457
+ Application::mount(svg.clone());
458
+ ffi::test::take_calls();
459
+
460
+ bridge_callbacks::__fui_on_svg_loaded(88, 40.0, 20.0);
461
+ let calls = ffi::test::take_calls();
462
+ assert!(calls.iter().any(|call| matches!(
463
+ call,
464
+ Call::SetWidth { value, unit_enum, .. } if *value == 40.0 && *unit_enum == Unit::Pixel as u32
465
+ )));
466
+ assert!(calls.iter().any(|call| matches!(
467
+ call,
468
+ Call::SetHeight { value, unit_enum, .. } if *value == 20.0 && *unit_enum == Unit::Pixel as u32
469
+ )));
470
+ assert_eq!(svg.asset_width(), 40.0);
471
+ assert_eq!(svg.asset_height(), 20.0);
472
+ assert_eq!(svg.asset_state(), AssetLoadState::Ready);
473
+ }
474
+ }