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