@effindomv2/fui-rs 0.1.17 → 0.1.19

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 (43) hide show
  1. package/Cargo.toml +1 -1
  2. package/README.md +69 -0
  3. package/package.json +1 -1
  4. package/scripts/hostgen/rust-host-events.ts +43 -13
  5. package/src/app.rs +7 -0
  6. package/src/assets.rs +2 -2
  7. package/src/bindings/ui.rs +2 -0
  8. package/src/bridge_callbacks.rs +27 -17
  9. package/src/color.rs +112 -0
  10. package/src/controls/button.rs +1 -0
  11. package/src/controls/checkbox.rs +1 -0
  12. package/src/controls/combobox.rs +5 -3
  13. package/src/controls/dropdown.rs +3 -1
  14. package/src/controls/internal/pressable_labeled_control.rs +1 -0
  15. package/src/controls/internal/text_input_core.rs +7 -3
  16. package/src/controls/radio_button.rs +1 -0
  17. package/src/controls/shared.rs +2 -5
  18. package/src/controls/slider.rs +1 -0
  19. package/src/controls/switch.rs +1 -0
  20. package/src/controls/tests.rs +36 -7
  21. package/src/debug.rs +8 -5
  22. package/src/drag_drop.rs +1 -3
  23. package/src/drawing.rs +8 -0
  24. package/src/event.rs +39 -27
  25. package/src/fetch.rs +21 -7
  26. package/src/ffi.rs +3 -1
  27. package/src/file.rs +88 -59
  28. package/src/host_events.rs +41 -0
  29. package/src/lib.rs +55 -9
  30. package/src/node/core.rs +22 -21
  31. package/src/node/helpers.rs +1 -1
  32. package/src/node/mod.rs +1 -1
  33. package/src/node/scroll_bar.rs +101 -0
  34. package/src/node/scroll_box.rs +7 -0
  35. package/src/node/scroll_view.rs +20 -14
  36. package/src/node/virtual_list.rs +120 -33
  37. package/src/platform.rs +3 -0
  38. package/src/popup_presenter.rs +1 -0
  39. package/src/theme.rs +8 -48
  40. package/src/timers.rs +1 -1
  41. package/src/typography.rs +6 -14
  42. package/src/worker.rs +25 -9
  43. package/src/worker_runtime.rs +61 -17
package/src/file.rs CHANGED
@@ -1124,11 +1124,8 @@ fn finish_worker_process(
1124
1124
  Option<FileWorkerCompleteCallback>,
1125
1125
  Option<FileErrorCallback>,
1126
1126
  )> {
1127
- let Some(request) =
1128
- ACTIVE_WORKER_PROCESS_REQUESTS.with(|requests| requests.borrow_mut().remove(&request_id))
1129
- else {
1130
- return None;
1131
- };
1127
+ let request = ACTIVE_WORKER_PROCESS_REQUESTS
1128
+ .with(|requests| requests.borrow_mut().remove(&request_id))?;
1132
1129
  let mut inner = request.borrow_mut();
1133
1130
  if inner.finished {
1134
1131
  return None;
@@ -1160,8 +1157,10 @@ pub fn reset_file_runtime() {
1160
1157
  NEXT_FILE_REQUEST_ID.with(|slot| *slot.borrow_mut() = 1);
1161
1158
  }
1162
1159
 
1163
- #[no_mangle]
1164
- pub extern "C" fn __fui_on_file_pick_result(
1160
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
1161
+ /// # Safety
1162
+ /// `payload_ptr` must be null for an empty payload or point to `payload_len` readable bytes.
1163
+ pub unsafe extern "C" fn __fui_on_file_pick_result(
1165
1164
  request_id: u32,
1166
1165
  status: u32,
1167
1166
  payload_ptr: *const u8,
@@ -1191,8 +1190,10 @@ pub extern "C" fn __fui_on_file_pick_result(
1191
1190
  );
1192
1191
  }
1193
1192
 
1194
- #[no_mangle]
1195
- pub extern "C" fn __fui_on_file_read_result(
1193
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
1194
+ /// # Safety
1195
+ /// `payload_ptr` must be null for an empty payload or point to `payload_len` readable bytes.
1196
+ pub unsafe extern "C" fn __fui_on_file_read_result(
1196
1197
  request_id: u32,
1197
1198
  status: u32,
1198
1199
  offset_bytes: u64,
@@ -1227,8 +1228,10 @@ pub extern "C" fn __fui_on_file_read_result(
1227
1228
  );
1228
1229
  }
1229
1230
 
1230
- #[no_mangle]
1231
- pub extern "C" fn __fui_on_file_save_result(
1231
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
1232
+ /// # Safety
1233
+ /// `payload_ptr` must be null for an empty payload or point to `payload_len` readable bytes.
1234
+ pub unsafe extern "C" fn __fui_on_file_save_result(
1232
1235
  request_id: u32,
1233
1236
  status: u32,
1234
1237
  written_bytes: u64,
@@ -1264,8 +1267,10 @@ pub extern "C" fn __fui_on_file_save_result(
1264
1267
  );
1265
1268
  }
1266
1269
 
1267
- #[no_mangle]
1268
- pub extern "C" fn __fui_on_file_writer_created(
1270
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
1271
+ /// # Safety
1272
+ /// `payload_ptr` must be null for an empty payload or point to `payload_len` readable bytes.
1273
+ pub unsafe extern "C" fn __fui_on_file_writer_created(
1269
1274
  request_id: u32,
1270
1275
  status: u32,
1271
1276
  payload_ptr: *const u8,
@@ -1301,8 +1306,10 @@ pub extern "C" fn __fui_on_file_writer_created(
1301
1306
  );
1302
1307
  }
1303
1308
 
1304
- #[no_mangle]
1305
- pub extern "C" fn __fui_on_file_write_result(
1309
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
1310
+ /// # Safety
1311
+ /// `payload_ptr` must be null for an empty payload or point to `payload_len` readable bytes.
1312
+ pub unsafe extern "C" fn __fui_on_file_write_result(
1306
1313
  request_id: u32,
1307
1314
  status: u32,
1308
1315
  written_bytes: u64,
@@ -1332,8 +1339,10 @@ pub extern "C" fn __fui_on_file_write_result(
1332
1339
  );
1333
1340
  }
1334
1341
 
1335
- #[no_mangle]
1336
- pub extern "C" fn __fui_on_file_finish_result(
1342
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
1343
+ /// # Safety
1344
+ /// `payload_ptr` must be null for an empty payload or point to `payload_len` readable bytes.
1345
+ pub unsafe extern "C" fn __fui_on_file_finish_result(
1337
1346
  request_id: u32,
1338
1347
  status: u32,
1339
1348
  written_bytes: u64,
@@ -1370,8 +1379,10 @@ pub extern "C" fn __fui_on_file_finish_result(
1370
1379
  );
1371
1380
  }
1372
1381
 
1373
- #[no_mangle]
1374
- pub extern "C" fn __fui_on_file_worker_process_progress(
1382
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
1383
+ /// # Safety
1384
+ /// `payload_ptr` must be null for an empty payload or point to `payload_len` readable bytes.
1385
+ pub unsafe extern "C" fn __fui_on_file_worker_process_progress(
1375
1386
  request_id: u32,
1376
1387
  processed_bytes: u64,
1377
1388
  total_bytes: u64,
@@ -1398,8 +1409,10 @@ pub extern "C" fn __fui_on_file_worker_process_progress(
1398
1409
  }
1399
1410
  }
1400
1411
 
1401
- #[no_mangle]
1402
- pub extern "C" fn __fui_on_file_worker_process_chunk(
1412
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
1413
+ /// # Safety
1414
+ /// `payload_ptr` must be null for an empty payload or point to `payload_len` readable bytes.
1415
+ pub unsafe extern "C" fn __fui_on_file_worker_process_chunk(
1403
1416
  request_id: u32,
1404
1417
  offset_bytes: u64,
1405
1418
  file_size_bytes: u64,
@@ -1426,8 +1439,10 @@ pub extern "C" fn __fui_on_file_worker_process_chunk(
1426
1439
  }
1427
1440
  }
1428
1441
 
1429
- #[no_mangle]
1430
- pub extern "C" fn __fui_on_file_worker_process_complete(
1442
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
1443
+ /// # Safety
1444
+ /// `payload_ptr` must be null for an empty payload or point to `payload_len` readable bytes.
1445
+ pub unsafe extern "C" fn __fui_on_file_worker_process_complete(
1431
1446
  request_id: u32,
1432
1447
  processed_bytes: u64,
1433
1448
  payload_ptr: *const u8,
@@ -1451,8 +1466,10 @@ pub extern "C" fn __fui_on_file_worker_process_complete(
1451
1466
  }
1452
1467
  }
1453
1468
 
1454
- #[no_mangle]
1455
- pub extern "C" fn __fui_on_file_worker_process_error(
1469
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
1470
+ /// # Safety
1471
+ /// `payload_ptr` must be null for an empty payload or point to `payload_len` readable bytes.
1472
+ pub unsafe extern "C" fn __fui_on_file_worker_process_error(
1456
1473
  request_id: u32,
1457
1474
  status: u32,
1458
1475
  payload_ptr: *const u8,
@@ -1513,12 +1530,14 @@ mod tests {
1513
1530
  payload.extend_from_slice(b"note.txt");
1514
1531
  payload.extend_from_slice(&10u32.to_le_bytes());
1515
1532
  payload.extend_from_slice(b"text/plain");
1516
- super::__fui_on_file_pick_result(
1517
- 1,
1518
- FILE_STATUS_SUCCESS,
1519
- payload.as_ptr(),
1520
- payload.len() as u32,
1521
- );
1533
+ unsafe {
1534
+ super::__fui_on_file_pick_result(
1535
+ 1,
1536
+ FILE_STATUS_SUCCESS,
1537
+ payload.as_ptr(),
1538
+ payload.len() as u32,
1539
+ );
1540
+ }
1522
1541
 
1523
1542
  let picked = picked.borrow();
1524
1543
  assert_eq!(picked.len(), 1);
@@ -1542,14 +1561,16 @@ mod tests {
1542
1561
  assert!(calls.iter().any(|call| matches!(call, Call::FileReadChunk { request_id, file_id, offset_bytes, max_bytes } if *request_id == 1 && file_id == "picked-1" && *offset_bytes == 10 && *max_bytes == 8)));
1543
1562
 
1544
1563
  let bytes = b"1234".to_vec();
1545
- super::__fui_on_file_read_result(
1546
- 1,
1547
- FILE_STATUS_SUCCESS,
1548
- 10,
1549
- 100,
1550
- bytes.as_ptr(),
1551
- bytes.len() as u32,
1552
- );
1564
+ unsafe {
1565
+ super::__fui_on_file_read_result(
1566
+ 1,
1567
+ FILE_STATUS_SUCCESS,
1568
+ 10,
1569
+ 100,
1570
+ bytes.as_ptr(),
1571
+ bytes.len() as u32,
1572
+ );
1573
+ }
1553
1574
  let chunk = chunk.borrow().clone().expect("chunk");
1554
1575
  assert_eq!(chunk.offset_bytes, 10);
1555
1576
  assert_eq!(chunk.file_size_bytes, 100);
@@ -1585,12 +1606,14 @@ mod tests {
1585
1606
  "writer-1",
1586
1607
  Some("report.txt"),
1587
1608
  );
1588
- super::__fui_on_file_writer_created(
1589
- 1,
1590
- FILE_STATUS_SUCCESS,
1591
- payload.as_ptr(),
1592
- payload.len() as u32,
1593
- );
1609
+ unsafe {
1610
+ super::__fui_on_file_writer_created(
1611
+ 1,
1612
+ FILE_STATUS_SUCCESS,
1613
+ payload.as_ptr(),
1614
+ payload.len() as u32,
1615
+ );
1616
+ }
1594
1617
  let writer = writer.borrow().clone().expect("writer");
1595
1618
  assert_eq!(writer.file_name, "report.txt");
1596
1619
  assert_eq!(writer.mode, FileSaveMode::NativePicker);
@@ -1604,13 +1627,15 @@ mod tests {
1604
1627
  let calls = ffi::test::take_calls();
1605
1628
  assert!(calls.iter().any(|call| matches!(call, Call::FileWriterFinish { request_id, writer_id } if *request_id == 2 && writer_id == "writer-1")));
1606
1629
  let payload = writer_payload(FileSaveMode::NativePicker as u32, "report.txt", None);
1607
- super::__fui_on_file_finish_result(
1608
- 2,
1609
- FILE_STATUS_SUCCESS,
1610
- 44,
1611
- payload.as_ptr(),
1612
- payload.len() as u32,
1613
- );
1630
+ unsafe {
1631
+ super::__fui_on_file_finish_result(
1632
+ 2,
1633
+ FILE_STATUS_SUCCESS,
1634
+ 44,
1635
+ payload.as_ptr(),
1636
+ payload.len() as u32,
1637
+ );
1638
+ }
1614
1639
  let finished = finished.borrow().clone().expect("finish");
1615
1640
  assert_eq!(finished.file_name, "report.txt");
1616
1641
  assert_eq!(finished.written_bytes, 44);
@@ -1657,18 +1682,22 @@ mod tests {
1657
1682
  let calls = ffi::test::take_calls();
1658
1683
  assert!(calls.iter().any(|call| matches!(call, Call::FileProcessWorkerStart { request_id, worker_wasm_path, worker_entry_name, file_id, suggested_name, chunk_bytes, save_to_picked_file } if *request_id == 1 && worker_wasm_path == "./workers/file_worker.wasm" && worker_entry_name == "entry" && file_id == "picked-1" && suggested_name == "copy.bin" && *chunk_bytes == 64 * 1024 && *save_to_picked_file)));
1659
1684
 
1660
- super::__fui_on_file_worker_process_progress(1, 20, 100, b"copy.bin".as_ptr(), 8);
1685
+ unsafe {
1686
+ super::__fui_on_file_worker_process_progress(1, 20, 100, b"copy.bin".as_ptr(), 8);
1687
+ }
1661
1688
  let progress = progress.borrow().clone().expect("progress");
1662
1689
  assert_eq!(progress.processed_bytes, 20);
1663
1690
  assert_eq!(progress.output_file_name, Some("copy.bin".to_string()));
1664
1691
 
1665
1692
  let payload = b"copy.bin\0sha256".to_vec();
1666
- super::__fui_on_file_worker_process_complete(
1667
- 1,
1668
- 100,
1669
- payload.as_ptr(),
1670
- payload.len() as u32,
1671
- );
1693
+ unsafe {
1694
+ super::__fui_on_file_worker_process_complete(
1695
+ 1,
1696
+ 100,
1697
+ payload.as_ptr(),
1698
+ payload.len() as u32,
1699
+ );
1700
+ }
1672
1701
  let result = result.borrow().clone().expect("result");
1673
1702
  assert_eq!(result.processed_bytes, 100);
1674
1703
  assert_eq!(result.output_file_name, Some("copy.bin".to_string()));
@@ -0,0 +1,41 @@
1
+ #[must_use = "the host-event handler is removed when the subscription is dropped"]
2
+ pub struct HostEventSubscription {
3
+ unsubscribe: Option<Box<dyn FnOnce()>>,
4
+ }
5
+
6
+ impl HostEventSubscription {
7
+ #[doc(hidden)]
8
+ pub fn new(unsubscribe: impl FnOnce() + 'static) -> Self {
9
+ Self {
10
+ unsubscribe: Some(Box::new(unsubscribe)),
11
+ }
12
+ }
13
+ }
14
+
15
+ impl Drop for HostEventSubscription {
16
+ fn drop(&mut self) {
17
+ if let Some(unsubscribe) = self.unsubscribe.take() {
18
+ unsubscribe();
19
+ }
20
+ }
21
+ }
22
+
23
+ #[cfg(test)]
24
+ mod tests {
25
+ use super::*;
26
+ use std::cell::Cell;
27
+ use std::rc::Rc;
28
+
29
+ #[test]
30
+ fn dropping_host_event_subscription_runs_unsubscribe_once() {
31
+ let unsubscribe_count = Rc::new(Cell::new(0));
32
+ let captured_count = unsubscribe_count.clone();
33
+ let subscription = HostEventSubscription::new(move || {
34
+ captured_count.set(captured_count.get() + 1);
35
+ });
36
+
37
+ drop(subscription);
38
+
39
+ assert_eq!(unsubscribe_count.get(), 1);
40
+ }
41
+ }
package/src/lib.rs CHANGED
@@ -4,6 +4,7 @@ pub mod assets;
4
4
  #[doc(hidden)]
5
5
  pub mod bindings;
6
6
  pub mod bitmap;
7
+ pub mod color;
7
8
  #[doc(hidden)]
8
9
  pub mod bridge_callbacks;
9
10
  pub(crate) mod context_menu_manager;
@@ -24,6 +25,7 @@ pub mod frame_scheduler;
24
25
  pub mod frame_signal;
25
26
  #[doc(hidden)]
26
27
  pub mod generated;
28
+ pub mod host_events;
27
29
  pub mod host_services;
28
30
  pub mod image_sampling;
29
31
  pub(crate) mod keyboard_scroll;
@@ -77,6 +79,46 @@ macro_rules! fui_app {
77
79
  };
78
80
  }
79
81
 
82
+ #[cfg(feature = "worker-runtime")]
83
+ #[macro_export]
84
+ macro_rules! fui_worker {
85
+ ($($entry:ident => $job:ty),+ $(,)?) => {
86
+ $(
87
+ #[doc = "Worker entrypoint generated by `fui_worker!`.\n\n# Safety\n`input_ptr` must reference `input_len` readable bytes when `input_len` is non-zero."]
88
+ #[no_mangle]
89
+ pub unsafe extern "C" fn $entry(input_ptr: usize, input_len: u32) {
90
+ ::std::thread_local! {
91
+ static ACTIVE_JOB: ::std::cell::RefCell<Option<$job>> =
92
+ const { ::std::cell::RefCell::new(None) };
93
+ }
94
+ let input = unsafe {
95
+ $crate::WorkerRuntime::entry_input(input_ptr, input_len)
96
+ };
97
+ ACTIVE_JOB.with(|slot| {
98
+ let mut active = slot.borrow_mut();
99
+ if active.is_none() {
100
+ $crate::worker_runtime::reset_worker_runtime();
101
+ }
102
+ let mut job = active.take().unwrap_or_default();
103
+ if $crate::WorkerJob::resume(&mut job, input) {
104
+ *active = Some(job);
105
+ }
106
+ });
107
+ }
108
+ )+
109
+
110
+ #[no_mangle]
111
+ pub extern "C" fn __fui_worker_text_buffer() -> usize {
112
+ $crate::worker_runtime::worker_text_buffer_ptr()
113
+ }
114
+
115
+ #[no_mangle]
116
+ pub extern "C" fn __fui_worker_text_buffer_size() -> u32 {
117
+ $crate::worker_runtime::worker_text_buffer_size()
118
+ }
119
+ };
120
+ }
121
+
80
122
  #[macro_export]
81
123
  macro_rules! fui_managed_app {
82
124
  ($page_ty:ty, $build_page:expr, $get_root:expr) => {
@@ -367,6 +409,7 @@ pub mod prelude {
367
409
  pub use crate::app::{Application, ApplicationRegistration, ManagedApplication};
368
410
  pub use crate::bitmap::{Bitmap, BitmapTextReadyEventArgs};
369
411
  pub use crate::bridge_callbacks::current_route;
412
+ pub use crate::color::{hsl_to_color, mix_color, rgb, rgba, with_alpha};
370
413
  pub use crate::controls::{
371
414
  anti_selection_area, button, checkbox, clear_control_templates, combo_box, context_menu,
372
415
  create_default_button_presenter, create_default_checkbox_indicator_presenter,
@@ -435,6 +478,9 @@ pub mod prelude {
435
478
  pub use crate::focus_visibility::show_keyboard_focus_for_key_event;
436
479
  pub use crate::frame_scheduler::{mark_needs_commit, on_loaded, LoadedEventArgs};
437
480
  pub use crate::fui_component;
481
+ #[cfg(feature = "worker-runtime")]
482
+ pub use crate::fui_worker;
483
+ pub use crate::host_events::HostEventSubscription;
438
484
  pub use crate::image_sampling::{ImageSampling, ImageSamplingMode};
439
485
  pub use crate::logger;
440
486
  pub use crate::navigation;
@@ -442,9 +488,10 @@ pub mod prelude {
442
488
  auto, column, custom_drawable, fill, flex_box, grid, image, pct, portal, px, row,
443
489
  scroll_box, scroll_view, svg, text, viewport_height, viewport_width, virtual_list, Border,
444
490
  Child, ContextMenuEventArgs, Corners, CustomDrawable, DrawableInvalidator, EdgeInsets,
445
- FlexBox, FlexBoxSurface, GradientStop, Grid, GridTrack, HasFlexBoxRoot, Image, ImageNode, Length, Node,
446
- PresenterHostStyle, ScrollBar, ScrollBarVisibility, ScrollBox, ScrollState, ScrollView,
447
- Shadow, Svg, SvgNode, Text, TextCore, TextNode, ThemeBindable, VirtualList,
491
+ FlexBox, FlexBoxSurface, GradientStop, Grid, GridTrack, HasFlexBoxRoot, Image, ImageNode,
492
+ Length, Node, PresenterHostStyle, ScrollBar, ScrollBarStyle, ScrollBarVisibility,
493
+ ScrollBox, ScrollState, ScrollView, Shadow, Svg, SvgNode, Text, TextCore, TextNode,
494
+ ThemeBindable, VirtualList,
448
495
  };
449
496
  pub use crate::persisted;
450
497
  pub use crate::platform;
@@ -489,6 +536,7 @@ pub use app::{Application, ApplicationRegistration, ManagedApplication};
489
536
  pub use assets::*;
490
537
  pub use bitmap::{Bitmap, BitmapTextReadyEventArgs};
491
538
  pub use bridge_callbacks::current_route;
539
+ pub use color::{hsl_to_color, mix_color, rgb, rgba, with_alpha};
492
540
  pub use controls::{
493
541
  anti_selection_area, button, checkbox, clear_control_templates, combo_box, context_menu,
494
542
  create_default_button_presenter, create_default_checkbox_indicator_presenter,
@@ -553,6 +601,7 @@ pub use file::{
553
601
  pub use focus_visibility::show_keyboard_focus_for_key_event;
554
602
  pub use frame_scheduler::{mark_needs_commit, on_loaded, LoadedEventArgs};
555
603
  pub use frame_signal::{frame_time_signal, FrameTimeSignalHandle};
604
+ pub use host_events::HostEventSubscription;
556
605
  pub use image_sampling::{ImageSampling, ImageSamplingMode};
557
606
  pub use logger::*;
558
607
  pub use navigation::*;
@@ -561,8 +610,8 @@ pub use node::{
561
610
  scroll_view, svg, text, viewport_height, viewport_width, virtual_list, Border, Child,
562
611
  ContextMenuEventArgs, Corners, CustomDrawable, DrawableInvalidator, EdgeInsets, FlexBox,
563
612
  FlexBoxSurface, GradientStop, Grid, GridTrack, HasFlexBoxRoot, Image, ImageNode, Length, Node,
564
- PresenterHostStyle, ScrollBar, ScrollBarVisibility, ScrollBox, ScrollState, ScrollView, Shadow,
565
- Svg, SvgNode, Text, TextCore, TextNode, ThemeBindable, VirtualList,
613
+ PresenterHostStyle, ScrollBar, ScrollBarStyle, ScrollBarVisibility, ScrollBox, ScrollState,
614
+ ScrollView, Shadow, Svg, SvgNode, Text, TextCore, TextNode, ThemeBindable, VirtualList,
566
615
  };
567
616
  pub use persisted::*;
568
617
  pub use platform::*;
@@ -592,8 +641,5 @@ pub use worker::{Worker, WorkerCompletedEventArgs, WorkerErrorEventArgs, WorkerP
592
641
  pub use worker_job::{WorkerJob, WorkerJobState};
593
642
  #[cfg(feature = "worker-runtime")]
594
643
  pub use worker_runtime::{
595
- file_read_chunk, file_worker_write_chunk, handle_fetch_complete as __fui_on_fetch_complete,
596
- handle_fetch_error as __fui_on_fetch_error, reset_worker_runtime,
597
- worker_text_buffer_ptr as __fui_worker_text_buffer,
598
- worker_text_buffer_size as __fui_worker_text_buffer_size, WorkerRuntime,
644
+ file_read_chunk, file_worker_write_chunk, reset_worker_runtime, WorkerRuntime,
599
645
  };
package/src/node/core.rs CHANGED
@@ -262,7 +262,7 @@ pub(crate) struct LinearGradient {
262
262
  pub(crate) offsets: Vec<f32>,
263
263
  pub(crate) colors: Vec<u32>,
264
264
  }
265
- #[derive(Clone)]
265
+ #[derive(Clone, Default)]
266
266
  pub(crate) struct FlexBoxProps {
267
267
  pub(crate) width: Option<(f32, Unit)>,
268
268
  pub(crate) height: Option<(f32, Unit)>,
@@ -278,25 +278,6 @@ pub(crate) struct FlexBoxProps {
278
278
  pub(crate) transitions: Option<NodeTransitions>,
279
279
  }
280
280
 
281
- impl Default for FlexBoxProps {
282
- fn default() -> Self {
283
- Self {
284
- width: None,
285
- height: None,
286
- bg_color: None,
287
- padding: None,
288
- flex_direction: None,
289
- box_style: None,
290
- opacity: None,
291
- blur_sigma: None,
292
- drop_shadow: None,
293
- background_blur_sigma: None,
294
- linear_gradient: None,
295
- transitions: None,
296
- }
297
- }
298
- }
299
-
300
281
  #[derive(Clone, Default)]
301
282
  pub(crate) struct TextProps {
302
283
  pub(crate) content: String,
@@ -532,6 +513,26 @@ impl NodeRef {
532
513
  self.inner.borrow().handle
533
514
  }
534
515
 
516
+ pub(crate) fn set_semantic_checked(
517
+ &self,
518
+ state: SemanticCheckedState,
519
+ announce: bool,
520
+ ) {
521
+ let handle = {
522
+ let mut core = self.inner.borrow_mut();
523
+ core.behavior.semantic_checked = Some(state);
524
+ core.handle
525
+ };
526
+ if handle == NodeHandle::INVALID {
527
+ return;
528
+ }
529
+ ui::set_semantic_checked(handle.raw(), state as u32);
530
+ if announce {
531
+ ui::request_semantic_announcement(handle.raw());
532
+ }
533
+ crate::frame_scheduler::mark_needs_commit();
534
+ }
535
+
535
536
  pub(crate) fn ptr_eq(&self, other: &Self) -> bool {
536
537
  Rc::ptr_eq(&self.inner, &other.inner)
537
538
  }
@@ -2219,7 +2220,7 @@ pub trait Node: Clone {
2219
2220
  }
2220
2221
  }
2221
2222
 
2222
- impl<T: Node + ?Sized> Node for &T {
2223
+ impl<T: Node> Node for &T {
2223
2224
  fn retained_node_ref(&self) -> NodeRef {
2224
2225
  (*self).retained_node_ref()
2225
2226
  }
@@ -31,7 +31,7 @@ pub fn scroll_box() -> ScrollBox {
31
31
  ScrollBox::new()
32
32
  }
33
33
 
34
- pub fn virtual_list(total_items: i32, item_height: f32) -> VirtualList {
34
+ pub fn virtual_list(total_items: i32, item_height: f32) -> VirtualList<FlexBox> {
35
35
  VirtualList::new(total_items, item_height)
36
36
  }
37
37
 
package/src/node/mod.rs CHANGED
@@ -48,7 +48,7 @@ pub use helpers::{
48
48
  pub use image::ImageNode;
49
49
  pub(crate) use presenter_host_style::HostStyleLayers;
50
50
  pub use presenter_host_style::{Corners, EdgeInsets, PresenterHostStyle, Shadow};
51
- pub use scroll_bar::{ScrollBar, ScrollBarVisibility};
51
+ pub use scroll_bar::{ScrollBar, ScrollBarStyle, ScrollBarVisibility};
52
52
  pub use scroll_box::ScrollBox;
53
53
  pub use scroll_state::ScrollState;
54
54
  pub use scroll_view::ScrollView;
@@ -12,6 +12,91 @@ const DEFAULT_TRACK_THICKNESS: f32 = 8.0;
12
12
  const DEFAULT_THUMB_THICKNESS: f32 = 8.0;
13
13
  const DEFAULT_MIN_THUMB_SIZE: f32 = 18.0;
14
14
 
15
+ #[derive(Clone, Copy, Debug, PartialEq)]
16
+ pub struct ScrollBarStyle {
17
+ track_width: f32,
18
+ thumb_width: f32,
19
+ thumb_min_height: f32,
20
+ track_corner_radius: f32,
21
+ thumb_corner_radius: f32,
22
+ track_color: Option<u32>,
23
+ thumb_color: Option<u32>,
24
+ }
25
+
26
+ impl ScrollBarStyle {
27
+ pub fn new() -> Self {
28
+ Self::default()
29
+ }
30
+
31
+ pub fn track_width(mut self, value: f32) -> Self {
32
+ self.track_width = value;
33
+ self
34
+ }
35
+
36
+ pub fn thumb_width(mut self, value: f32) -> Self {
37
+ self.thumb_width = value;
38
+ self
39
+ }
40
+
41
+ pub fn thumb_min_height(mut self, value: f32) -> Self {
42
+ self.thumb_min_height = value;
43
+ self
44
+ }
45
+
46
+ pub fn track_corner_radius(mut self, value: f32) -> Self {
47
+ self.track_corner_radius = value;
48
+ self
49
+ }
50
+
51
+ pub fn thumb_corner_radius(mut self, value: f32) -> Self {
52
+ self.thumb_corner_radius = value;
53
+ self
54
+ }
55
+
56
+ pub fn track_color(mut self, value: u32) -> Self {
57
+ self.track_color = Some(value);
58
+ self
59
+ }
60
+
61
+ pub fn thumb_color(mut self, value: u32) -> Self {
62
+ self.thumb_color = Some(value);
63
+ self
64
+ }
65
+
66
+ pub(crate) fn apply_to(self, scroll_bar: &ScrollBar) {
67
+ scroll_bar
68
+ .track_width(self.track_width)
69
+ .thumb_width(self.thumb_width)
70
+ .thumb_min_height(self.thumb_min_height)
71
+ .track_corner_radius(self.track_corner_radius)
72
+ .thumb_corner_radius(self.thumb_corner_radius);
73
+ if let Some(color) = self.track_color {
74
+ scroll_bar.track_color(color);
75
+ } else {
76
+ scroll_bar.clear_track_color();
77
+ }
78
+ if let Some(color) = self.thumb_color {
79
+ scroll_bar.thumb_color(color);
80
+ } else {
81
+ scroll_bar.clear_thumb_color();
82
+ }
83
+ }
84
+ }
85
+
86
+ impl Default for ScrollBarStyle {
87
+ fn default() -> Self {
88
+ Self {
89
+ track_width: DEFAULT_TRACK_THICKNESS,
90
+ thumb_width: DEFAULT_THUMB_THICKNESS,
91
+ thumb_min_height: DEFAULT_MIN_THUMB_SIZE,
92
+ track_corner_radius: 0.0,
93
+ thumb_corner_radius: 0.0,
94
+ track_color: None,
95
+ thumb_color: None,
96
+ }
97
+ }
98
+ }
99
+
15
100
  fn clamp(value: f32, min: f32, max: f32) -> f32 {
16
101
  if value < min {
17
102
  return min;
@@ -237,6 +322,14 @@ impl ScrollBar {
237
322
  self
238
323
  }
239
324
 
325
+ fn clear_track_color(&self) {
326
+ self.inner.track_color_overridden.set(false);
327
+ self.inner
328
+ .track_color
329
+ .set(current_theme().colors.scrollbar_track);
330
+ self.apply_color_style();
331
+ }
332
+
240
333
  pub fn thumb_color(&self, color: u32) -> &Self {
241
334
  self.inner.thumb_color_overridden.set(true);
242
335
  self.inner.thumb_color.set(color);
@@ -244,6 +337,14 @@ impl ScrollBar {
244
337
  self
245
338
  }
246
339
 
340
+ fn clear_thumb_color(&self) {
341
+ self.inner.thumb_color_overridden.set(false);
342
+ self.inner
343
+ .thumb_color
344
+ .set(current_theme().colors.scrollbar_thumb);
345
+ self.apply_color_style();
346
+ }
347
+
247
348
  pub fn bind_scroll_handle(&self, handle: u64) {
248
349
  self.inner.target_handle.set(handle);
249
350
  self.inner
@@ -219,6 +219,13 @@ impl ScrollBox {
219
219
  self
220
220
  }
221
221
 
222
+ pub fn scrollbar_style(&self, style: ScrollBarStyle) -> &Self {
223
+ style.apply_to(&self.inner.vertical_scrollbar);
224
+ style.apply_to(&self.inner.horizontal_scrollbar);
225
+ self.refresh_chrome();
226
+ self
227
+ }
228
+
222
229
  fn attach_listeners(&self) {
223
230
  let weak = Rc::downgrade(&self.inner);
224
231
  self.inner