@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/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "fui-rs"
3
- version = "0.1.17"
3
+ version = "0.1.18"
4
4
  edition = "2021"
5
5
  license = "AGPL-3.0-only OR LicenseRef-EffinDom-Commercial"
6
6
 
package/README.md CHANGED
@@ -74,6 +74,75 @@ fui_app!(FlexBox, build_page);
74
74
  | Browser file/fetch/worker bridges | Available |
75
75
  | Host services/events generator support | Available |
76
76
 
77
+ ## Recycled virtual-list rows
78
+
79
+ `VirtualList` creates a fixed retained row pool. Use `item_template` once to
80
+ construct typed row state, then update that state from `on_bind_item` whenever a
81
+ pool slot is assigned a new item index:
82
+
83
+ ```rust
84
+ struct ContactRow {
85
+ name: TextNode,
86
+ }
87
+
88
+ let contacts = virtual_list(10_000, 28.0)
89
+ .item_template(|container| {
90
+ let name = text("");
91
+ container.child(&name);
92
+ ContactRow { name }
93
+ });
94
+ contacts.on_bind_item(|row, index| {
95
+ row.name.text(format!("Contact {index}"));
96
+ });
97
+ ```
98
+
99
+ The template is not rerun while scrolling. Do not key recycled rows by pointer
100
+ or create controls inside `on_bind_item`.
101
+
102
+ ## Scrollbar styling
103
+
104
+ Apply common scrollbar chrome without leaving fluent `ScrollBox` construction:
105
+
106
+ ```rust
107
+ let content = scroll_box().scrollbar_style(
108
+ ScrollBarStyle::new()
109
+ .track_width(10.0)
110
+ .thumb_width(7.0)
111
+ .thumb_corner_radius(3.5),
112
+ );
113
+ ```
114
+
115
+ Use `vertical_scrollbar()` or `horizontal_scrollbar()` afterward for an
116
+ axis-specific override.
117
+
118
+ ## Host-event lifetime
119
+
120
+ Generated `on_*` host-event functions return `HostEventSubscription`. Retain
121
+ the guard for exactly as long as the handler should remain active; dropping it
122
+ unsubscribes automatically. Replacing a handler is generation-safe, so dropping
123
+ an older guard cannot remove its replacement.
124
+
125
+ ## Worker entrypoints
126
+
127
+ Enable the `worker-runtime` feature, implement `Default + WorkerJob`, and let
128
+ the SDK emit resumable entries plus the shared callback-buffer ABI:
129
+
130
+ ```rust
131
+ use fui::prelude::*;
132
+
133
+ #[derive(Default)]
134
+ struct PrimeJob {
135
+ state: WorkerJobState,
136
+ }
137
+
138
+ impl WorkerJob for PrimeJob {
139
+ fn state(&mut self) -> &mut WorkerJobState { &mut self.state }
140
+ fn run(&mut self) { self.complete("done"); }
141
+ }
142
+
143
+ fui_worker!(primeWorker => PrimeJob);
144
+ ```
145
+
77
146
  ## Architecture
78
147
 
79
148
  FUI-RS builds Rust retained UI objects into the EffinDom v2 runtime through the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effindomv2/fui-rs",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
4
4
  "private": false,
5
5
  "license": "AGPL-3.0-only OR LicenseRef-EffinDom-Commercial",
6
6
  "description": "EffinDom v2 Rust frontend framework SDK and host generators",
@@ -38,9 +38,9 @@ function rustValueType(type: HostServiceTypeName): string {
38
38
 
39
39
  function callbackType(args: readonly HostServiceTypeName[]): string {
40
40
  if (args.length === 0) {
41
- return "Box<dyn Fn()>";
41
+ return "Rc<dyn Fn()>";
42
42
  }
43
- return `Box<dyn Fn(${args.map((arg) => rustValueType(arg)).join(", ")})>`;
43
+ return `Rc<dyn Fn(${args.map((arg) => rustValueType(arg)).join(", ")})>`;
44
44
  }
45
45
 
46
46
  function emitExportArgs(args: readonly HostServiceTypeName[]): string {
@@ -125,7 +125,21 @@ export async function generateRustHostEventsFile(
125
125
  "#![allow(dead_code)]",
126
126
  "#![allow(non_snake_case)]",
127
127
  "",
128
- "use std::cell::RefCell;",
128
+ "use fui::HostEventSubscription;",
129
+ "use std::cell::{Cell, RefCell};",
130
+ "use std::rc::Rc;",
131
+ "",
132
+ "thread_local! {",
133
+ " static NEXT_HOST_EVENT_SUBSCRIPTION_ID: Cell<u64> = const { Cell::new(1) };",
134
+ "}",
135
+ "",
136
+ "fn next_host_event_subscription_id() -> u64 {",
137
+ " NEXT_HOST_EVENT_SUBSCRIPTION_ID.with(|next| {",
138
+ " let id = next.get();",
139
+ " next.set(id.wrapping_add(1).max(1));",
140
+ " id",
141
+ " })",
142
+ "}",
129
143
  "",
130
144
  ];
131
145
  methods.forEach((method) => {
@@ -133,36 +147,52 @@ export async function generateRustHostEventsFile(
133
147
  const callbackTy = callbackType(method.args);
134
148
  lines.push("thread_local! {");
135
149
  lines.push(
136
- ` static ${handlerName.toUpperCase()}_HANDLER: RefCell<Option<${callbackTy}>> = const { RefCell::new(None) };`,
150
+ ` static ${handlerName.toUpperCase()}_HANDLER: RefCell<Option<(u64, ${callbackTy})>> = const { RefCell::new(None) };`,
137
151
  );
138
152
  lines.push("}");
139
153
  lines.push("");
140
154
  lines.push(
141
- `pub fn on_${handlerName}(callback: impl Fn(${method.args.map((arg) => rustValueType(arg)).join(", ")}) + 'static) {`,
155
+ `pub fn on_${handlerName}(callback: impl Fn(${method.args.map((arg) => rustValueType(arg)).join(", ")}) + 'static) -> HostEventSubscription {`,
142
156
  );
157
+ lines.push(" let subscription_id = next_host_event_subscription_id();");
143
158
  lines.push(
144
- ` ${handlerName.toUpperCase()}_HANDLER.with(|slot| *slot.borrow_mut() = Some(Box::new(callback)));`,
159
+ ` ${handlerName.toUpperCase()}_HANDLER.with(|slot| *slot.borrow_mut() = Some((subscription_id, Rc::new(callback))));`,
145
160
  );
161
+ lines.push(" HostEventSubscription::new(move || {");
162
+ lines.push(` ${handlerName.toUpperCase()}_HANDLER.with(|slot| {`);
163
+ lines.push(" let should_clear = slot.borrow().as_ref().is_some_and(|(id, _)| *id == subscription_id);");
164
+ lines.push(" if should_clear {");
165
+ lines.push(" *slot.borrow_mut() = None;");
166
+ lines.push(" }");
167
+ lines.push(" });");
168
+ lines.push(" })");
146
169
  lines.push("}");
147
170
  lines.push("");
148
171
  lines.push(`pub fn clear_${handlerName}() {`);
149
172
  lines.push(` ${handlerName.toUpperCase()}_HANDLER.with(|slot| *slot.borrow_mut() = None);`);
150
173
  lines.push("}");
151
174
  lines.push("");
175
+ const hasPointerArgs = method.args.some((type) =>
176
+ type === "string" || type === "bytes" || type.endsWith("_array")
177
+ );
178
+ if (hasPointerArgs) {
179
+ lines.push("/// # Safety");
180
+ lines.push("/// Pointer arguments must reference readable WASM memory for their declared lengths.");
181
+ }
152
182
  lines.push("#[no_mangle]");
153
- lines.push(`pub extern "C" fn ${method.exportName}(${emitExportArgs(method.args)}) {`);
154
- lines.push(` ${handlerName.toUpperCase()}_HANDLER.with(|slot| {`);
155
- lines.push(" let handler = slot.borrow();");
156
- lines.push(" let Some(callback) = handler.as_ref() else {");
157
- lines.push(" return;");
158
- lines.push(" };");
183
+ lines.push(`pub ${hasPointerArgs ? "unsafe " : ""}extern "C" fn ${method.exportName}(${emitExportArgs(method.args)}) {`);
184
+ lines.push(` let callback = ${handlerName.toUpperCase()}_HANDLER.with(|slot| {`);
185
+ lines.push(" slot.borrow().as_ref().map(|(_, callback)| callback.clone())");
186
+ lines.push(" });");
187
+ lines.push(" let Some(callback) = callback else {");
188
+ lines.push(" return;");
189
+ lines.push(" };");
159
190
  lines.push(...emitDecodedArgs(method.args));
160
191
  if (method.args.length === 0) {
161
192
  lines.push(" callback();");
162
193
  } else {
163
194
  lines.push(` callback(${method.args.map((_arg, index) => `arg${String(index)}`).join(", ")});`);
164
195
  }
165
- lines.push(" });");
166
196
  lines.push("}");
167
197
  lines.push("");
168
198
  });
package/src/app.rs CHANGED
@@ -102,6 +102,12 @@ pub struct ApplicationRegistration {
102
102
  build_page_fn: Rc<dyn Fn() -> FlexBox>,
103
103
  }
104
104
 
105
+ impl Default for ApplicationRegistration {
106
+ fn default() -> Self {
107
+ Self::new()
108
+ }
109
+ }
110
+
105
111
  impl ApplicationRegistration {
106
112
  pub fn new() -> Self {
107
113
  Self {
@@ -318,6 +324,7 @@ impl Node for NodeRefMount {
318
324
  fn build_self(&self) {}
319
325
  }
320
326
 
327
+ #[cfg(not(feature = "worker-runtime"))]
321
328
  #[no_mangle]
322
329
  pub extern "C" fn __flushRenders() {
323
330
  Application::flush_renders();
package/src/assets.rs CHANGED
@@ -332,7 +332,7 @@ pub fn release_svg_asset(svg_id: u32) {
332
332
  }
333
333
  let remaining = SVG_REF_COUNTS.with(|ref_counts| decrement_ref_count(ref_counts, svg_id));
334
334
  let pinned = PINNED_SVG_IDS.with(|ids| ids.borrow().contains(&svg_id));
335
- if remaining > 0 || remaining < 0 || pinned {
335
+ if remaining != 0 || pinned {
336
336
  return;
337
337
  }
338
338
  let url = get_svg_record(svg_id).borrow().url.clone();
@@ -352,7 +352,7 @@ pub fn release_texture_asset(texture_id: u32) {
352
352
  let remaining =
353
353
  TEXTURE_REF_COUNTS.with(|ref_counts| decrement_ref_count(ref_counts, texture_id));
354
354
  let pinned = PINNED_TEXTURE_IDS.with(|ids| ids.borrow().contains(&texture_id));
355
- if remaining > 0 || remaining < 0 || pinned {
355
+ if remaining != 0 || pinned {
356
356
  return;
357
357
  }
358
358
  let url = get_texture_record(texture_id).borrow().url.clone();
@@ -1,3 +1,5 @@
1
+ #![allow(clippy::too_many_arguments)]
2
+
1
3
  use crate::ffi;
2
4
  use crate::ffi::{GridUnit, ImageSamplingKind};
3
5
 
@@ -105,25 +105,27 @@ pub fn persisted_restore_count() -> u32 {
105
105
  PERSIST_RESTORE_COUNT.with(Cell::get)
106
106
  }
107
107
 
108
- #[no_mangle]
108
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
109
109
  pub extern "C" fn __fui_on_viewport_changed(width: f32, height: f32) {
110
110
  ui::resize_window(width, height);
111
111
  viewport::set_viewport_size(width, height);
112
112
  }
113
113
 
114
- #[no_mangle]
114
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
115
115
  pub extern "C" fn __fui_on_frame(timestamp_ms: f64) {
116
116
  frame_signal::set_frame_time(timestamp_ms);
117
117
  animation::tick_animations(timestamp_ms);
118
118
  }
119
119
 
120
- #[no_mangle]
121
- pub extern "C" fn __fui_on_route_changed(route_ptr: *const u8, route_len: u32) {
120
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
121
+ /// # Safety
122
+ /// `route_ptr` must be null for an empty route or point to `route_len` readable bytes.
123
+ pub unsafe extern "C" fn __fui_on_route_changed(route_ptr: *const u8, route_len: u32) {
122
124
  let route = read_utf8(route_ptr, route_len);
123
125
  CURRENT_ROUTE.with(|slot| slot.replace(route));
124
126
  }
125
127
 
126
- #[no_mangle]
128
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
127
129
  pub extern "C" fn __fui_on_scroll(
128
130
  handle: u64,
129
131
  offset_x: f32,
@@ -155,25 +157,25 @@ pub extern "C" fn __fui_on_scroll(
155
157
  );
156
158
  }
157
159
 
158
- #[no_mangle]
160
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
159
161
  pub extern "C" fn __fui_can_show_context_menu(handle: u64) -> bool {
160
162
  context_menu_manager::can_show_for_handle(handle)
161
163
  }
162
164
 
163
- #[no_mangle]
165
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
164
166
  pub extern "C" fn __fui_on_context_menu(handle: u64, x: f32, y: f32) {
165
167
  LAST_CONTEXT_MENU.with(|slot| slot.replace(Some(ContextMenuRequest { handle, x, y })));
166
168
  let shown = context_menu_manager::show_for_current_selection(handle, x, y);
167
169
  CONTEXT_MENU_VISIBLE.with(|visible| visible.set(shown));
168
170
  }
169
171
 
170
- #[no_mangle]
172
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
171
173
  pub extern "C" fn __fui_hide_active_context_menu() {
172
174
  context_menu_manager::hide_active_menu();
173
175
  CONTEXT_MENU_VISIBLE.with(|visible| visible.set(false));
174
176
  }
175
177
 
176
- #[no_mangle]
178
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
177
179
  pub extern "C" fn __fui_on_font_loaded(font_id: u32) {
178
180
  LAST_FONT_LOADED.with(|slot| slot.set(Some(font_id)));
179
181
  assets::on_font_loaded(font_id);
@@ -182,7 +184,7 @@ pub extern "C" fn __fui_on_font_loaded(font_id: u32) {
182
184
  crate::frame_scheduler::mark_needs_commit();
183
185
  }
184
186
 
185
- #[no_mangle]
187
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
186
188
  pub extern "C" fn __fui_on_svg_loaded(svg_id: u32, width: f32, height: f32) {
187
189
  LAST_SVG_LOADED.with(|slot| {
188
190
  slot.replace(Some(AssetReady {
@@ -194,8 +196,10 @@ pub extern "C" fn __fui_on_svg_loaded(svg_id: u32, width: f32, height: f32) {
194
196
  assets::on_svg_loaded(svg_id, width, height);
195
197
  }
196
198
 
197
- #[no_mangle]
198
- pub extern "C" fn __fui_on_svg_failed(svg_id: u32, error_ptr: *const u8, error_len: u32) {
199
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
200
+ /// # Safety
201
+ /// `error_ptr` must be null for an empty message or point to `error_len` readable bytes.
202
+ pub unsafe extern "C" fn __fui_on_svg_failed(svg_id: u32, error_ptr: *const u8, error_len: u32) {
199
203
  let error = read_utf8(error_ptr, error_len);
200
204
  LAST_SVG_FAILED.with(|slot| {
201
205
  slot.replace(Some(AssetFailure {
@@ -206,7 +210,7 @@ pub extern "C" fn __fui_on_svg_failed(svg_id: u32, error_ptr: *const u8, error_l
206
210
  assets::on_svg_failed(svg_id, error);
207
211
  }
208
212
 
209
- #[no_mangle]
213
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
210
214
  pub extern "C" fn __fui_on_texture_loaded(texture_id: u32, width: f32, height: f32) {
211
215
  LAST_TEXTURE_LOADED.with(|slot| {
212
216
  slot.replace(Some(AssetReady {
@@ -218,8 +222,14 @@ pub extern "C" fn __fui_on_texture_loaded(texture_id: u32, width: f32, height: f
218
222
  assets::on_texture_loaded(texture_id, width, height);
219
223
  }
220
224
 
221
- #[no_mangle]
222
- pub extern "C" fn __fui_on_texture_failed(texture_id: u32, error_ptr: *const u8, error_len: u32) {
225
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
226
+ /// # Safety
227
+ /// `error_ptr` must be null for an empty message or point to `error_len` readable bytes.
228
+ pub unsafe extern "C" fn __fui_on_texture_failed(
229
+ texture_id: u32,
230
+ error_ptr: *const u8,
231
+ error_len: u32,
232
+ ) {
223
233
  let error = read_utf8(error_ptr, error_len);
224
234
  LAST_TEXTURE_FAILED.with(|slot| {
225
235
  slot.replace(Some(AssetFailure {
@@ -230,13 +240,13 @@ pub extern "C" fn __fui_on_texture_failed(texture_id: u32, error_ptr: *const u8,
230
240
  assets::on_texture_failed(texture_id, error);
231
241
  }
232
242
 
233
- #[no_mangle]
243
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
234
244
  pub extern "C" fn __fui_capture_persisted_ui_state() {
235
245
  PERSIST_CAPTURE_COUNT.with(|count| count.set(count.get() + 1));
236
246
  Application::capture_persisted_ui_state();
237
247
  }
238
248
 
239
- #[no_mangle]
249
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
240
250
  pub extern "C" fn __fui_restore_persisted_ui_state() {
241
251
  PERSIST_RESTORE_COUNT.with(|count| count.set(count.get() + 1));
242
252
  Application::restore_persisted_ui_state();
@@ -374,6 +374,7 @@ impl LabeledControlTextStyle for Button {
374
374
  }
375
375
  }
376
376
 
377
+ #[allow(clippy::too_many_arguments)]
377
378
  fn sync_button_visual_state(
378
379
  root: &FlexBox,
379
380
  presenter: &Rc<RefCell<Rc<dyn ButtonPresenter>>>,
@@ -409,6 +409,7 @@ impl CheckboxEventTarget {
409
409
  }
410
410
  }
411
411
 
412
+ #[allow(clippy::too_many_arguments)]
412
413
  fn apply_checkbox_state(
413
414
  presenter: &Rc<RefCell<Rc<dyn CheckboxIndicatorPresenter>>>,
414
415
  weak_root: &Rc<WeakNodeRef>,
@@ -28,6 +28,9 @@ use crate::{app, frame_scheduler, ThemeBindable};
28
28
  use std::cell::{Cell, RefCell};
29
29
  use std::rc::{Rc, Weak};
30
30
 
31
+ type ComboBoxChangedCallback = Rc<dyn Fn(crate::controls::ComboBoxChangedEventArgs<ComboBoxItem>)>;
32
+ type ComboBoxTextChangedCallback = Rc<dyn Fn(TextChangedEventArgs)>;
33
+
31
34
  const DEFAULT_PANEL_BACKGROUND_BLUR_SIGMA: f32 = 10.0;
32
35
 
33
36
  fn strings_equal_ignore_case(left: &str, right: &str) -> bool {
@@ -211,9 +214,8 @@ struct ComboBoxShared {
211
214
  popup_panel_background_blur_overridden: Cell<bool>,
212
215
  suppress_editor_changed: Cell<bool>,
213
216
  last_auto_complete_text_value: RefCell<String>,
214
- changed_callback:
215
- RefCell<Option<Rc<dyn Fn(crate::controls::ComboBoxChangedEventArgs<ComboBoxItem>)>>>,
216
- text_changed_callback: RefCell<Option<Rc<dyn Fn(TextChangedEventArgs)>>>,
217
+ changed_callback: RefCell<Option<ComboBoxChangedCallback>>,
218
+ text_changed_callback: RefCell<Option<ComboBoxTextChangedCallback>>,
217
219
  theme_guard: RefCell<Option<SubscriptionGuard>>,
218
220
  focus_visibility_guard: RefCell<Option<SubscriptionGuard>>,
219
221
  }
@@ -26,6 +26,8 @@ use crate::ThemeBindable;
26
26
  use std::cell::{Cell, RefCell};
27
27
  use std::rc::{Rc, Weak};
28
28
 
29
+ type DropdownChangedCallback = Rc<dyn Fn(DropdownChangedEventArgs<DropdownItem>)>;
30
+
29
31
  const DEFAULT_PANEL_BACKGROUND_BLUR_SIGMA: f32 = 10.0;
30
32
 
31
33
  fn is_activation_key(event: &KeyEventArgs) -> bool {
@@ -110,7 +112,7 @@ struct DropdownShared {
110
112
  popup_panel_background_blur_sigma_value: Cell<f32>,
111
113
  popup_panel_color_overridden: Cell<bool>,
112
114
  popup_panel_background_blur_overridden: Cell<bool>,
113
- changed_callback: RefCell<Option<Rc<dyn Fn(DropdownChangedEventArgs<DropdownItem>)>>>,
115
+ changed_callback: RefCell<Option<DropdownChangedCallback>>,
114
116
  theme_guard: RefCell<Option<SubscriptionGuard>>,
115
117
  focus_visibility_guard: RefCell<Option<SubscriptionGuard>>,
116
118
  }
@@ -372,6 +372,7 @@ impl WeakPressableLabeledControl {
372
372
  }
373
373
  }
374
374
 
375
+ #[allow(clippy::too_many_arguments)]
375
376
  fn sync_base_theme_parts(
376
377
  root: &FlexBox,
377
378
  label_node: &TextCore,
@@ -25,6 +25,10 @@ use std::rc::{Rc, Weak};
25
25
 
26
26
  const UNLIMITED_TEXT_LENGTH: i32 = i32::MAX;
27
27
 
28
+ type TextChangedCallback = Rc<dyn Fn(TextChangedEventArgs)>;
29
+ type SelectionChangedCallback = Rc<dyn Fn(SelectionChangedEventArgs)>;
30
+ type FocusChangedCallback = Rc<dyn Fn(FocusChangedEventArgs)>;
31
+
28
32
  #[derive(Clone, Copy)]
29
33
  pub(crate) struct TextInputProfile {
30
34
  max_lines: i32,
@@ -140,9 +144,9 @@ pub struct TextInputCore {
140
144
  selection_start_bytes: Cell<u32>,
141
145
  selection_end_bytes: Cell<u32>,
142
146
  focused_state: Cell<bool>,
143
- changed_callback: RefCell<Option<Rc<dyn Fn(TextChangedEventArgs)>>>,
144
- selection_changed_callback: RefCell<Option<Rc<dyn Fn(SelectionChangedEventArgs)>>>,
145
- focus_changed_callback: RefCell<Option<Rc<dyn Fn(FocusChangedEventArgs)>>>,
147
+ changed_callback: RefCell<Option<TextChangedCallback>>,
148
+ selection_changed_callback: RefCell<Option<SelectionChangedCallback>>,
149
+ focus_changed_callback: RefCell<Option<FocusChangedCallback>>,
146
150
  theme_guard: RefCell<Option<SubscriptionGuard>>,
147
151
  focus_visibility_guard: RefCell<Option<SubscriptionGuard>>,
148
152
  }
@@ -383,6 +383,7 @@ impl RadioEventTarget {
383
383
  }
384
384
  }
385
385
 
386
+ #[allow(clippy::too_many_arguments)]
386
387
  fn apply_radio_checked(
387
388
  presenter: &Rc<RefCell<Rc<dyn RadioIndicatorPresenter>>>,
388
389
  weak_root: &Rc<WeakNodeRef>,
@@ -673,6 +673,7 @@ impl SliderEventTarget {
673
673
  }
674
674
  }
675
675
 
676
+ #[allow(clippy::too_many_arguments)]
676
677
  fn create_slider_visual_state(
677
678
  value: f32,
678
679
  min: f32,
@@ -324,6 +324,7 @@ impl SwitchEventTarget {
324
324
  }
325
325
  }
326
326
 
327
+ #[allow(clippy::too_many_arguments)]
327
328
  fn apply_switch_checked(
328
329
  presenter: &Rc<RefCell<Rc<dyn SwitchIndicatorPresenter>>>,
329
330
  weak_root: &Rc<WeakNodeRef>,
@@ -213,7 +213,9 @@ fn press_enter(node_ref: &crate::node::NodeRef) {
213
213
  }
214
214
 
215
215
  fn key_event(event_type: KeyEventType, key: &str, modifiers: u32) -> bool {
216
- event::__fui_on_key_event(event_type as u32, key.as_ptr(), key.len() as u32, modifiers)
216
+ unsafe {
217
+ event::__fui_on_key_event(event_type as u32, key.as_ptr(), key.len() as u32, modifiers)
218
+ }
217
219
  }
218
220
 
219
221
  fn blur(node_ref: &crate::node::NodeRef) {
@@ -1407,7 +1409,7 @@ fn retained_dropdown_keeps_theme_subscription_after_wrapper_drop() {
1407
1409
  let mut theme = current_theme();
1408
1410
  theme.colors.surface = 0x135724FF;
1409
1411
  theme.colors.border = 0x246813FF;
1410
- theme.colors.text_primary = 0xABCDEFff;
1412
+ theme.colors.text_primary = 0xABCDEFFF;
1411
1413
  use_custom_theme(theme.clone());
1412
1414
  let calls = ffi::test::take_calls();
1413
1415
 
@@ -4212,7 +4214,9 @@ fn text_input_text_replaced_event_updates_value_and_emits_changed() {
4212
4214
  })
4213
4215
  .expect("expected text input editor node id to be applied");
4214
4216
 
4215
- event::__fui_on_text_replaced(editor_handle, 0, 0, b"hello".as_ptr(), 5);
4217
+ unsafe {
4218
+ event::__fui_on_text_replaced(editor_handle, 0, 0, b"hello".as_ptr(), 5);
4219
+ }
4216
4220
 
4217
4221
  assert_eq!(input.value(), "hello");
4218
4222
  assert_eq!(&*last_text.borrow(), "hello");
@@ -4474,7 +4478,9 @@ fn text_input_text_replacement_clamps_selection_bytes_from_char_positions() {
4474
4478
  })
4475
4479
  .expect("expected text input editor node id to be applied");
4476
4480
 
4477
- event::__fui_on_text_replaced(editor_handle, 1, 5, std::ptr::null(), 0);
4481
+ unsafe {
4482
+ event::__fui_on_text_replaced(editor_handle, 1, 5, std::ptr::null(), 0);
4483
+ }
4478
4484
 
4479
4485
  assert_eq!(input.value(), "ab");
4480
4486
  assert_eq!(input.selection_start(), 0);
@@ -4660,16 +4666,22 @@ fn text_area_readonly_disabled_placeholder_and_line_height_follow_text_input_cor
4660
4666
  if *handle == editor_handle && (*line_height - 26.0).abs() < f32::EPSILON
4661
4667
  )));
4662
4668
 
4663
- event::__fui_on_text_replaced(editor_handle, 0, 0, b"Hello\nworld".as_ptr(), 11);
4669
+ unsafe {
4670
+ event::__fui_on_text_replaced(editor_handle, 0, 0, b"Hello\nworld".as_ptr(), 11);
4671
+ }
4664
4672
  assert_eq!(input.value(), "Hello\nworld");
4665
4673
 
4666
- event::__fui_on_text_changed(editor_handle, b"alpha\nbeta".as_ptr(), 10);
4674
+ unsafe {
4675
+ event::__fui_on_text_changed(editor_handle, b"alpha\nbeta".as_ptr(), 10);
4676
+ }
4667
4677
  assert_eq!(input.value(), "alpha\nbeta");
4668
4678
  event::__fui_on_selection_changed(editor_handle, 6, 10);
4669
4679
  assert_eq!(input.selection_start(), 6);
4670
4680
  assert_eq!(input.selection_end(), 10);
4671
4681
 
4672
- event::__fui_on_text_replaced(editor_handle, 0, 11, std::ptr::null(), 0);
4682
+ unsafe {
4683
+ event::__fui_on_text_replaced(editor_handle, 0, 11, std::ptr::null(), 0);
4684
+ }
4673
4685
  assert_eq!(input.value(), "");
4674
4686
  Application::unmount();
4675
4687
  }
package/src/debug.rs CHANGED
@@ -2,6 +2,7 @@ use crate::event::{self, PointerType};
2
2
  use crate::ffi::{self, KeyEventType, PointerEventType};
3
3
  use crate::node::NodeHandle;
4
4
 
5
+ #[allow(clippy::too_many_arguments)]
5
6
  pub fn pointer_event(
6
7
  event_type: PointerEventType,
7
8
  handle: NodeHandle,
@@ -42,7 +43,7 @@ pub fn key_event(event_type: KeyEventType, key: &str, modifiers: u32) -> bool {
42
43
  event::dispatch_key_event(event_type, key.to_string(), modifiers)
43
44
  }
44
45
 
45
- #[no_mangle]
46
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
46
47
  pub extern "C" fn __fui_debug_pointer_event(
47
48
  event_type: u32,
48
49
  handle: u64,
@@ -74,8 +75,10 @@ pub extern "C" fn __fui_debug_pointer_event(
74
75
  );
75
76
  }
76
77
 
77
- #[no_mangle]
78
- pub extern "C" fn __fui_debug_key_event(
78
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
79
+ /// # Safety
80
+ /// `key_ptr` must be null for an empty key or point to `key_len` readable bytes.
81
+ pub unsafe extern "C" fn __fui_debug_key_event(
79
82
  event_type: u32,
80
83
  key_ptr: *const u8,
81
84
  key_len: u32,
@@ -97,12 +100,12 @@ pub extern "C" fn __fui_debug_key_event(
97
100
  );
98
101
  }
99
102
 
100
- #[no_mangle]
103
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
101
104
  pub extern "C" fn __fui_debug_focus_changed(handle: u64, focused: bool) {
102
105
  event::dispatch_focus_changed(NodeHandle::from_raw(handle), focused);
103
106
  }
104
107
 
105
- #[no_mangle]
108
+ #[cfg_attr(not(feature = "worker-runtime"), no_mangle)]
106
109
  pub extern "C" fn __fui_debug_scroll(
107
110
  handle: u64,
108
111
  offset_x: f32,
package/src/drag_drop.rs CHANGED
@@ -399,9 +399,7 @@ fn finish_session(
399
399
  notify_target_leave: bool,
400
400
  ) {
401
401
  let target = with_state(|state| {
402
- let Some(active) = state.active_session.as_ref() else {
403
- return None;
404
- };
402
+ let active = state.active_session.as_ref()?;
405
403
  if !Rc::ptr_eq(&active.inner, &session.inner) {
406
404
  return None;
407
405
  }
package/src/drawing.rs CHANGED
@@ -1,3 +1,5 @@
1
+ #![allow(clippy::too_many_arguments)]
2
+
1
3
  use crate::ffi;
2
4
  use crate::image_sampling::ImageSampling;
3
5
  use crate::logger::error;
@@ -78,6 +80,12 @@ pub struct Path {
78
80
  resource: Rc<PathResource>,
79
81
  }
80
82
 
83
+ impl Default for Path {
84
+ fn default() -> Self {
85
+ Self::new()
86
+ }
87
+ }
88
+
81
89
  impl Path {
82
90
  pub fn new() -> Self {
83
91
  let id = unsafe { ffi::fui_path_create() };