@effindomv2/fui-rs 0.1.17 → 0.1.18

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.
package/src/event.rs CHANGED
@@ -1,3 +1,5 @@
1
+ #![allow(clippy::too_many_arguments)]
2
+
1
3
  use crate::context_menu_manager;
2
4
  use crate::drag_drop;
3
5
  use crate::external_drop;
@@ -1331,27 +1333,27 @@ pub(crate) fn dispatch_external_drop_event(
1331
1333
  })
1332
1334
  }
1333
1335
 
1334
- #[no_mangle]
1336
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
1335
1337
  pub extern "C" fn __fui_key_buffer() -> *const u8 {
1336
1338
  std::ptr::addr_of_mut!(KEY_BUFFER).cast::<u8>()
1337
1339
  }
1338
1340
 
1339
- #[no_mangle]
1341
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
1340
1342
  pub extern "C" fn __fui_key_buffer_size() -> u32 {
1341
1343
  256
1342
1344
  }
1343
1345
 
1344
- #[no_mangle]
1346
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
1345
1347
  pub extern "C" fn __fui_text_buffer() -> *const u8 {
1346
1348
  std::ptr::addr_of_mut!(TEXT_BUFFER).cast::<u8>()
1347
1349
  }
1348
1350
 
1349
- #[no_mangle]
1351
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
1350
1352
  pub extern "C" fn __fui_text_buffer_size() -> u32 {
1351
1353
  (16 * 1024) as u32
1352
1354
  }
1353
1355
 
1354
- #[no_mangle]
1356
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
1355
1357
  pub extern "C" fn __fui_on_pointer_event_with_metadata(
1356
1358
  event_type: u32,
1357
1359
  handle: u64,
@@ -1391,7 +1393,7 @@ pub extern "C" fn __fui_on_pointer_event_with_metadata(
1391
1393
  )
1392
1394
  }
1393
1395
 
1394
- #[no_mangle]
1396
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
1395
1397
  pub extern "C" fn __fui_on_wheel_event(
1396
1398
  handle: u64,
1397
1399
  x: f32,
@@ -1412,8 +1414,10 @@ pub extern "C" fn __fui_on_wheel_event(
1412
1414
  )
1413
1415
  }
1414
1416
 
1415
- #[no_mangle]
1416
- pub extern "C" fn __fui_on_key_event(
1417
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
1418
+ /// # Safety
1419
+ /// `key_ptr` must be null for an empty key or point to `key_len` readable bytes.
1420
+ pub unsafe extern "C" fn __fui_on_key_event(
1417
1421
  event_type: u32,
1418
1422
  key_ptr: *const u8,
1419
1423
  key_len: u32,
@@ -1435,13 +1439,15 @@ pub extern "C" fn __fui_on_key_event(
1435
1439
  )
1436
1440
  }
1437
1441
 
1438
- #[no_mangle]
1442
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
1439
1443
  pub extern "C" fn __fui_on_focus_changed(handle: u64, focused: bool) {
1440
1444
  dispatch_focus_changed(NodeHandle::from_raw(handle), focused);
1441
1445
  }
1442
1446
 
1443
- #[no_mangle]
1444
- pub extern "C" fn __fui_on_text_changed(handle: u64, text_ptr: *const u8, text_len: u32) {
1447
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
1448
+ /// # Safety
1449
+ /// `text_ptr` must be null for empty text or point to `text_len` readable bytes.
1450
+ pub unsafe extern "C" fn __fui_on_text_changed(handle: u64, text_ptr: *const u8, text_len: u32) {
1445
1451
  let text = if text_ptr.is_null() || text_len == 0 {
1446
1452
  String::new()
1447
1453
  } else {
@@ -1451,8 +1457,10 @@ pub extern "C" fn __fui_on_text_changed(handle: u64, text_ptr: *const u8, text_l
1451
1457
  dispatch_text_changed(NodeHandle::from_raw(handle), text);
1452
1458
  }
1453
1459
 
1454
- #[no_mangle]
1455
- pub extern "C" fn __fui_on_text_replaced(
1460
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
1461
+ /// # Safety
1462
+ /// `text_ptr` must be null for empty replacement text or point to `text_len` readable bytes.
1463
+ pub unsafe extern "C" fn __fui_on_text_replaced(
1456
1464
  handle: u64,
1457
1465
  start: u32,
1458
1466
  end: u32,
@@ -1468,13 +1476,15 @@ pub extern "C" fn __fui_on_text_replaced(
1468
1476
  dispatch_text_replaced(NodeHandle::from_raw(handle), start, end, text);
1469
1477
  }
1470
1478
 
1471
- #[no_mangle]
1479
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
1472
1480
  pub extern "C" fn __fui_on_selection_changed(handle: u64, start: u32, end: u32) {
1473
1481
  dispatch_selection_changed(NodeHandle::from_raw(handle), start, end);
1474
1482
  }
1475
1483
 
1476
- #[no_mangle]
1477
- pub extern "C" fn __fui_on_cross_selection_changed(
1484
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
1485
+ /// # Safety
1486
+ /// `text_ptr` must be null for empty text or point to `text_len` readable bytes.
1487
+ pub unsafe extern "C" fn __fui_on_cross_selection_changed(
1478
1488
  handle: u64,
1479
1489
  text_ptr: *const u8,
1480
1490
  text_len: u32,
@@ -1488,17 +1498,17 @@ pub extern "C" fn __fui_on_cross_selection_changed(
1488
1498
  dispatch_cross_selection_changed(NodeHandle::from_raw(handle), text);
1489
1499
  }
1490
1500
 
1491
- #[no_mangle]
1501
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
1492
1502
  pub extern "C" fn __fui_resolve_gesture_owner(handle: u64) -> u64 {
1493
1503
  resolve_gesture_owner(NodeHandle::from_raw(handle)).raw()
1494
1504
  }
1495
1505
 
1496
- #[no_mangle]
1506
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
1497
1507
  pub extern "C" fn __fui_get_gesture_intent(handle: u64) -> u32 {
1498
1508
  get_gesture_intent(NodeHandle::from_raw(handle)) as u32
1499
1509
  }
1500
1510
 
1501
- #[no_mangle]
1511
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
1502
1512
  pub extern "C" fn __fui_on_gesture_event(
1503
1513
  handle: u64,
1504
1514
  phase: u32,
@@ -1523,27 +1533,27 @@ pub extern "C" fn __fui_on_gesture_event(
1523
1533
  )
1524
1534
  }
1525
1535
 
1526
- #[no_mangle]
1536
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
1527
1537
  pub extern "C" fn __fui_resolve_long_press_owner(handle: u64) -> u64 {
1528
1538
  resolve_long_press_owner(NodeHandle::from_raw(handle)).raw()
1529
1539
  }
1530
1540
 
1531
- #[no_mangle]
1541
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
1532
1542
  pub extern "C" fn __fui_get_long_press_minimum_duration_ms(handle: u64) -> i32 {
1533
1543
  get_long_press_minimum_duration_ms(NodeHandle::from_raw(handle))
1534
1544
  }
1535
1545
 
1536
- #[no_mangle]
1546
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
1537
1547
  pub extern "C" fn __fui_get_long_press_movement_tolerance(handle: u64) -> f32 {
1538
1548
  get_long_press_movement_tolerance(NodeHandle::from_raw(handle))
1539
1549
  }
1540
1550
 
1541
- #[no_mangle]
1551
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
1542
1552
  pub extern "C" fn __fui_long_press_continues_pointer_events(handle: u64) -> bool {
1543
1553
  registered_node(handle).is_some_and(|node| node.has_drag_source())
1544
1554
  }
1545
1555
 
1546
- #[no_mangle]
1556
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
1547
1557
  pub extern "C" fn __fui_on_long_press_event(
1548
1558
  handle: u64,
1549
1559
  scene_x: f32,
@@ -1564,8 +1574,10 @@ pub extern "C" fn __fui_on_long_press_event(
1564
1574
  )
1565
1575
  }
1566
1576
 
1567
- #[no_mangle]
1568
- pub extern "C" fn __fui_on_external_drag_event(
1577
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
1578
+ /// # Safety
1579
+ /// `payload_ptr` must be null for an empty payload or point to `payload_len` readable bytes.
1580
+ pub unsafe extern "C" fn __fui_on_external_drag_event(
1569
1581
  event_type: u32,
1570
1582
  handle: u64,
1571
1583
  x: f32,
@@ -1595,7 +1607,7 @@ pub extern "C" fn __fui_on_external_drag_event(
1595
1607
  effect as u32
1596
1608
  }
1597
1609
 
1598
- #[no_mangle]
1610
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
1599
1611
  pub extern "C" fn fui_dispatch_custom_draw(handle: u64, canvas_ptr: usize) {
1600
1612
  if let Some(node) = resolve_node(NodeHandle::from_raw(handle)) {
1601
1613
  node.handle_custom_draw(canvas_ptr);
package/src/fetch.rs CHANGED
@@ -337,8 +337,10 @@ pub fn reset_fetch_runtime() {
337
337
  });
338
338
  }
339
339
 
340
- #[no_mangle]
341
- pub extern "C" fn __fui_on_fetch_complete(
340
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
341
+ /// # Safety
342
+ /// `payload_ptr` must be null for an empty payload or point to `payload_len` readable bytes.
343
+ pub unsafe extern "C" fn __fui_on_fetch_complete(
342
344
  request_id: u32,
343
345
  ok: bool,
344
346
  status: i32,
@@ -361,8 +363,14 @@ pub extern "C" fn __fui_on_fetch_complete(
361
363
  );
362
364
  }
363
365
 
364
- #[no_mangle]
365
- pub extern "C" fn __fui_on_fetch_error(request_id: u32, payload_ptr: *const u8, payload_len: u32) {
366
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
367
+ /// # Safety
368
+ /// `payload_ptr` must be null for an empty payload or point to `payload_len` readable bytes.
369
+ pub unsafe extern "C" fn __fui_on_fetch_error(
370
+ request_id: u32,
371
+ payload_ptr: *const u8,
372
+ payload_len: u32,
373
+ ) {
366
374
  let message = if payload_ptr.is_null() || payload_len == 0 {
367
375
  "Fetch request failed.".to_string()
368
376
  } else {
@@ -413,7 +421,9 @@ mod tests {
413
421
  }
414
422
  bytes
415
423
  };
416
- super::__fui_on_fetch_complete(1, true, 200, payload.as_ptr(), payload.len() as u32);
424
+ unsafe {
425
+ super::__fui_on_fetch_complete(1, true, 200, payload.as_ptr(), payload.len() as u32);
426
+ }
417
427
  assert_eq!(&*result.borrow(), "OK");
418
428
  drop(request);
419
429
  }
@@ -464,7 +474,9 @@ mod tests {
464
474
  }
465
475
  bytes
466
476
  };
467
- super::__fui_on_fetch_complete(1, true, 200, payload.as_ptr(), payload.len() as u32);
477
+ unsafe {
478
+ super::__fui_on_fetch_complete(1, true, 200, payload.as_ptr(), payload.len() as u32);
479
+ }
468
480
  assert_eq!(&*result.borrow(), "");
469
481
  drop(request);
470
482
  }
@@ -493,7 +505,9 @@ mod tests {
493
505
  result_clone.replace(event.message);
494
506
  })
495
507
  .start();
496
- super::__fui_on_fetch_error(1, std::ptr::null(), 0);
508
+ unsafe {
509
+ super::__fui_on_fetch_error(1, std::ptr::null(), 0);
510
+ }
497
511
  assert_eq!(&*result.borrow(), "Fetch request failed.");
498
512
  drop(request);
499
513
  }
package/src/ffi.rs CHANGED
@@ -1,3 +1,5 @@
1
+ #![allow(clippy::too_many_arguments, clippy::type_complexity)]
2
+
1
3
  pub use crate::generated::ffi::*;
2
4
 
3
5
  #[cfg(all(not(target_arch = "wasm32"), not(feature = "native-runtime")))]
@@ -3433,7 +3435,7 @@ pub unsafe fn fui_canvas_create_offscreen(width: u32, height: u32) -> u32 {
3433
3435
  #[cfg(all(not(target_arch = "wasm32"), not(feature = "native-runtime")))]
3434
3436
  pub unsafe fn fui_canvas_get_offscreen_ptr(offscreen_id: u32) -> usize {
3435
3437
  push_call(Call::CanvasGetOffscreenPtr { offscreen_id });
3436
- (0x1000 + offscreen_id as usize) as usize
3438
+ 0x1000 + offscreen_id as usize
3437
3439
  }
3438
3440
 
3439
3441
  #[cfg(all(not(target_arch = "wasm32"), not(feature = "native-runtime")))]
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
@@ -24,6 +24,7 @@ pub mod frame_scheduler;
24
24
  pub mod frame_signal;
25
25
  #[doc(hidden)]
26
26
  pub mod generated;
27
+ pub mod host_events;
27
28
  pub mod host_services;
28
29
  pub mod image_sampling;
29
30
  pub(crate) mod keyboard_scroll;
@@ -77,6 +78,46 @@ macro_rules! fui_app {
77
78
  };
78
79
  }
79
80
 
81
+ #[cfg(feature = "worker-runtime")]
82
+ #[macro_export]
83
+ macro_rules! fui_worker {
84
+ ($($entry:ident => $job:ty),+ $(,)?) => {
85
+ $(
86
+ #[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."]
87
+ #[no_mangle]
88
+ pub unsafe extern "C" fn $entry(input_ptr: usize, input_len: u32) {
89
+ ::std::thread_local! {
90
+ static ACTIVE_JOB: ::std::cell::RefCell<Option<$job>> =
91
+ const { ::std::cell::RefCell::new(None) };
92
+ }
93
+ let input = unsafe {
94
+ $crate::WorkerRuntime::entry_input(input_ptr, input_len)
95
+ };
96
+ ACTIVE_JOB.with(|slot| {
97
+ let mut active = slot.borrow_mut();
98
+ if active.is_none() {
99
+ $crate::worker_runtime::reset_worker_runtime();
100
+ }
101
+ let mut job = active.take().unwrap_or_default();
102
+ if $crate::WorkerJob::resume(&mut job, input) {
103
+ *active = Some(job);
104
+ }
105
+ });
106
+ }
107
+ )+
108
+
109
+ #[no_mangle]
110
+ pub extern "C" fn __fui_worker_text_buffer() -> usize {
111
+ $crate::worker_runtime::worker_text_buffer_ptr()
112
+ }
113
+
114
+ #[no_mangle]
115
+ pub extern "C" fn __fui_worker_text_buffer_size() -> u32 {
116
+ $crate::worker_runtime::worker_text_buffer_size()
117
+ }
118
+ };
119
+ }
120
+
80
121
  #[macro_export]
81
122
  macro_rules! fui_managed_app {
82
123
  ($page_ty:ty, $build_page:expr, $get_root:expr) => {
@@ -435,6 +476,9 @@ pub mod prelude {
435
476
  pub use crate::focus_visibility::show_keyboard_focus_for_key_event;
436
477
  pub use crate::frame_scheduler::{mark_needs_commit, on_loaded, LoadedEventArgs};
437
478
  pub use crate::fui_component;
479
+ #[cfg(feature = "worker-runtime")]
480
+ pub use crate::fui_worker;
481
+ pub use crate::host_events::HostEventSubscription;
438
482
  pub use crate::image_sampling::{ImageSampling, ImageSamplingMode};
439
483
  pub use crate::logger;
440
484
  pub use crate::navigation;
@@ -442,9 +486,10 @@ pub mod prelude {
442
486
  auto, column, custom_drawable, fill, flex_box, grid, image, pct, portal, px, row,
443
487
  scroll_box, scroll_view, svg, text, viewport_height, viewport_width, virtual_list, Border,
444
488
  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,
489
+ FlexBox, FlexBoxSurface, GradientStop, Grid, GridTrack, HasFlexBoxRoot, Image, ImageNode,
490
+ Length, Node, PresenterHostStyle, ScrollBar, ScrollBarStyle, ScrollBarVisibility,
491
+ ScrollBox, ScrollState, ScrollView, Shadow, Svg, SvgNode, Text, TextCore, TextNode,
492
+ ThemeBindable, VirtualList,
448
493
  };
449
494
  pub use crate::persisted;
450
495
  pub use crate::platform;
@@ -553,6 +598,7 @@ pub use file::{
553
598
  pub use focus_visibility::show_keyboard_focus_for_key_event;
554
599
  pub use frame_scheduler::{mark_needs_commit, on_loaded, LoadedEventArgs};
555
600
  pub use frame_signal::{frame_time_signal, FrameTimeSignalHandle};
601
+ pub use host_events::HostEventSubscription;
556
602
  pub use image_sampling::{ImageSampling, ImageSamplingMode};
557
603
  pub use logger::*;
558
604
  pub use navigation::*;
@@ -561,8 +607,8 @@ pub use node::{
561
607
  scroll_view, svg, text, viewport_height, viewport_width, virtual_list, Border, Child,
562
608
  ContextMenuEventArgs, Corners, CustomDrawable, DrawableInvalidator, EdgeInsets, FlexBox,
563
609
  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,
610
+ PresenterHostStyle, ScrollBar, ScrollBarStyle, ScrollBarVisibility, ScrollBox, ScrollState,
611
+ ScrollView, Shadow, Svg, SvgNode, Text, TextCore, TextNode, ThemeBindable, VirtualList,
566
612
  };
567
613
  pub use persisted::*;
568
614
  pub use platform::*;
@@ -592,8 +638,5 @@ pub use worker::{Worker, WorkerCompletedEventArgs, WorkerErrorEventArgs, WorkerP
592
638
  pub use worker_job::{WorkerJob, WorkerJobState};
593
639
  #[cfg(feature = "worker-runtime")]
594
640
  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,
641
+ file_read_chunk, file_worker_write_chunk, reset_worker_runtime, WorkerRuntime,
599
642
  };