@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.
- package/COMMERCIAL.md +7 -0
- package/Cargo.toml +34 -0
- package/LICENSE.md +9 -0
- package/README.md +88 -0
- package/package.json +54 -0
- package/scripts/build.sh +227 -0
- package/scripts/check-runtime-dependency.sh +41 -0
- package/scripts/framework-host-services.ts +40 -0
- package/scripts/generate-host-events.ts +17 -0
- package/scripts/generate-host-services.ts +28 -0
- package/scripts/hostgen/common.ts +73 -0
- package/scripts/hostgen/registry.ts +159 -0
- package/scripts/hostgen/rust-host-events.ts +171 -0
- package/scripts/hostgen/rust-host-services.ts +257 -0
- package/src/animation.rs +625 -0
- package/src/app.rs +324 -0
- package/src/assets.rs +572 -0
- package/src/bindings/mod.rs +1 -0
- package/src/bindings/ui.rs +705 -0
- package/src/bitmap.rs +317 -0
- package/src/bridge_callbacks.rs +242 -0
- package/src/context_menu_manager.rs +332 -0
- package/src/controls/anti_selection_area.rs +54 -0
- package/src/controls/button.rs +542 -0
- package/src/controls/checkbox.rs +372 -0
- package/src/controls/combobox.rs +1574 -0
- package/src/controls/context_menu.rs +1367 -0
- package/src/controls/control_template_set.rs +46 -0
- package/src/controls/control_tokens.rs +596 -0
- package/src/controls/dialog.rs +590 -0
- package/src/controls/dropdown.rs +1010 -0
- package/src/controls/form.rs +229 -0
- package/src/controls/internal/button_presenter.rs +142 -0
- package/src/controls/internal/checkbox_indicator_presenter.rs +210 -0
- package/src/controls/internal/dropdown_chevron_presenter.rs +134 -0
- package/src/controls/internal/dropdown_field_presenter.rs +269 -0
- package/src/controls/internal/dropdown_option_row_presenter.rs +184 -0
- package/src/controls/internal/mod.rs +13 -0
- package/src/controls/internal/pressable_indicator_presenter.rs +26 -0
- package/src/controls/internal/pressable_labeled_control.rs +492 -0
- package/src/controls/internal/radio_indicator_presenter.rs +208 -0
- package/src/controls/internal/selectable_popup_list.rs +597 -0
- package/src/controls/internal/slider_presenter.rs +282 -0
- package/src/controls/internal/switch_indicator_presenter.rs +235 -0
- package/src/controls/internal/text_input_core.rs +1074 -0
- package/src/controls/internal/text_input_presenter.rs +108 -0
- package/src/controls/mod.rs +235 -0
- package/src/controls/nav_link.rs +395 -0
- package/src/controls/popup.rs +191 -0
- package/src/controls/progress_bar.rs +287 -0
- package/src/controls/radio_button.rs +348 -0
- package/src/controls/radio_group.rs +283 -0
- package/src/controls/selection_area.rs +82 -0
- package/src/controls/shared.rs +68 -0
- package/src/controls/slider.rs +701 -0
- package/src/controls/switch.rs +293 -0
- package/src/controls/templating.rs +49 -0
- package/src/controls/tests.rs +3905 -0
- package/src/controls/text_area.rs +207 -0
- package/src/controls/text_input.rs +192 -0
- package/src/debug.rs +146 -0
- package/src/drag_drop.rs +424 -0
- package/src/drag_gesture.rs +258 -0
- package/src/drawing.rs +385 -0
- package/src/event.rs +1603 -0
- package/src/external_drop.rs +467 -0
- package/src/fetch.rs +500 -0
- package/src/ffi.rs +3542 -0
- package/src/file.rs +1677 -0
- package/src/focus_adorner.rs +307 -0
- package/src/focus_visibility.rs +121 -0
- package/src/frame_scheduler.rs +159 -0
- package/src/frame_signal.rs +64 -0
- package/src/generated/ffi.rs +798 -0
- package/src/generated/framework_host_services.rs +74 -0
- package/src/generated/mod.rs +2 -0
- package/src/host_services.rs +162 -0
- package/src/image_sampling.rs +78 -0
- package/src/keyboard_scroll.rs +137 -0
- package/src/keyboard_scroll_tracker.rs +385 -0
- package/src/lib.rs +547 -0
- package/src/logger.rs +56 -0
- package/src/mobile_text_selection_toolbar.rs +1034 -0
- package/src/navigation.rs +48 -0
- package/src/node/core.rs +2210 -0
- package/src/node/custom_drawable.rs +149 -0
- package/src/node/flex_box.rs +874 -0
- package/src/node/grid.rs +304 -0
- package/src/node/helpers.rs +442 -0
- package/src/node/image.rs +506 -0
- package/src/node/mod.rs +315 -0
- package/src/node/scroll_bar.rs +845 -0
- package/src/node/scroll_box.rs +514 -0
- package/src/node/scroll_state.rs +121 -0
- package/src/node/scroll_view.rs +557 -0
- package/src/node/svg_node.rs +474 -0
- package/src/node/text_node.rs +633 -0
- package/src/node/virtual_list.rs +678 -0
- package/src/panic_hook.rs +36 -0
- package/src/persisted.rs +274 -0
- package/src/platform.rs +556 -0
- package/src/popup_presenter.rs +344 -0
- package/src/selection_handle_adorner.rs +838 -0
- package/src/signal.rs +134 -0
- package/src/text.rs +1065 -0
- package/src/theme.rs +571 -0
- package/src/timers.rs +106 -0
- package/src/tool_tip.rs +167 -0
- package/src/tool_tip_manager.rs +691 -0
- package/src/transitions.rs +41 -0
- package/src/typography.rs +520 -0
- package/src/viewport.rs +108 -0
- package/src/worker.rs +308 -0
- package/src/worker_job.rs +82 -0
- package/src/worker_runtime.rs +172 -0
package/src/bitmap.rs
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
use crate::assets;
|
|
2
|
+
use crate::drawing::DrawContext;
|
|
3
|
+
use crate::ffi;
|
|
4
|
+
use crate::frame_scheduler::on_loaded;
|
|
5
|
+
use crate::logger::error;
|
|
6
|
+
use crate::node::Node;
|
|
7
|
+
use crate::text::TextLayout;
|
|
8
|
+
use std::cell::RefCell;
|
|
9
|
+
use std::rc::Rc;
|
|
10
|
+
|
|
11
|
+
const MAX_DIRTY_RECTS: usize = 16;
|
|
12
|
+
|
|
13
|
+
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
|
14
|
+
pub struct BitmapTextReadyEventArgs;
|
|
15
|
+
|
|
16
|
+
impl BitmapTextReadyEventArgs {
|
|
17
|
+
pub const EMPTY: Self = Self;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
struct BitmapState {
|
|
21
|
+
width: u32,
|
|
22
|
+
height: u32,
|
|
23
|
+
texture_id: u32,
|
|
24
|
+
pixel_bytes: Vec<u8>,
|
|
25
|
+
offscreen_id: u32,
|
|
26
|
+
canvas_used: bool,
|
|
27
|
+
draw_context: Option<DrawContext>,
|
|
28
|
+
disposed: bool,
|
|
29
|
+
dirty_rects: Vec<(u32, u32, u32, u32)>,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
#[derive(Clone)]
|
|
33
|
+
pub struct Bitmap {
|
|
34
|
+
inner: Rc<RefCell<BitmapState>>,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
impl Bitmap {
|
|
38
|
+
pub fn new(width: u32, height: u32) -> Self {
|
|
39
|
+
assert!(
|
|
40
|
+
width > 0 && height > 0,
|
|
41
|
+
"Bitmap width and height must be greater than zero."
|
|
42
|
+
);
|
|
43
|
+
let byte_len = (width as usize)
|
|
44
|
+
.checked_mul(height as usize)
|
|
45
|
+
.and_then(|value| value.checked_mul(4))
|
|
46
|
+
.expect("Bitmap byte length overflow.");
|
|
47
|
+
let texture_id = assets::allocate_dynamic_texture_id();
|
|
48
|
+
let offscreen_id = unsafe { ffi::fui_canvas_create_offscreen(width, height) };
|
|
49
|
+
Self {
|
|
50
|
+
inner: Rc::new(RefCell::new(BitmapState {
|
|
51
|
+
width,
|
|
52
|
+
height,
|
|
53
|
+
texture_id,
|
|
54
|
+
pixel_bytes: vec![0; byte_len],
|
|
55
|
+
offscreen_id,
|
|
56
|
+
canvas_used: false,
|
|
57
|
+
draw_context: None,
|
|
58
|
+
disposed: false,
|
|
59
|
+
dirty_rects: Vec::new(),
|
|
60
|
+
})),
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
pub fn width(&self) -> u32 {
|
|
65
|
+
self.inner.borrow().width
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
pub fn height(&self) -> u32 {
|
|
69
|
+
self.inner.borrow().height
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
pub fn texture_id(&self) -> u32 {
|
|
73
|
+
self.inner.borrow().texture_id
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
pub fn pixels(&self) -> std::cell::RefMut<'_, Vec<u8>> {
|
|
77
|
+
std::cell::RefMut::map(self.inner.borrow_mut(), |state| {
|
|
78
|
+
assert!(!state.disposed, "Bitmap.pixels() called after dispose.");
|
|
79
|
+
&mut state.pixel_bytes
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
pub fn pixel_ptr(&self) -> usize {
|
|
84
|
+
let state = self.inner.borrow();
|
|
85
|
+
if state.pixel_bytes.is_empty() {
|
|
86
|
+
0
|
|
87
|
+
} else {
|
|
88
|
+
state.pixel_bytes.as_ptr() as usize
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
pub fn canvas(&self) -> DrawContext {
|
|
93
|
+
let mut state = self.inner.borrow_mut();
|
|
94
|
+
assert!(!state.disposed, "Bitmap.canvas() called after dispose.");
|
|
95
|
+
state.canvas_used = true;
|
|
96
|
+
if let Some(context) = &state.draw_context {
|
|
97
|
+
return context.clone();
|
|
98
|
+
}
|
|
99
|
+
let ptr = unsafe { ffi::fui_canvas_get_offscreen_ptr(state.offscreen_id) };
|
|
100
|
+
let context = DrawContext::new(ptr);
|
|
101
|
+
state.draw_context = Some(context.clone());
|
|
102
|
+
context
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
pub fn render<T: Node>(&self, node: &T, x: f32, y: f32, scale: f32) {
|
|
106
|
+
let state = self.inner.borrow();
|
|
107
|
+
assert!(!state.disposed, "Bitmap.render() called after dispose.");
|
|
108
|
+
let handle = node.handle().raw();
|
|
109
|
+
if handle == 0 {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
unsafe {
|
|
113
|
+
ffi::fui_render_node_to_rgba(
|
|
114
|
+
handle,
|
|
115
|
+
state.width,
|
|
116
|
+
state.height,
|
|
117
|
+
state.pixel_bytes.as_ptr() as usize,
|
|
118
|
+
state.pixel_bytes.len() as u32,
|
|
119
|
+
scale,
|
|
120
|
+
x,
|
|
121
|
+
y,
|
|
122
|
+
)
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
pub fn render_text_layout(&self, layout: &TextLayout, x: f32, y: f32, scale: f32) {
|
|
127
|
+
if !layout.is_ready() {
|
|
128
|
+
error(
|
|
129
|
+
"TextLayout",
|
|
130
|
+
"Bitmap.render_text_layout() called before the TextLayout was ready; register on_ready and render after the callback.",
|
|
131
|
+
);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
let node = layout.draw_node();
|
|
135
|
+
self.render(&node, x, y, scale);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
pub fn prepare_text<T: Node>(node: &T) {
|
|
139
|
+
crate::bindings::ui::prepare_node(node.handle().raw());
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
pub fn on_text_ready<T: Node + Clone + 'static>(
|
|
143
|
+
&self,
|
|
144
|
+
node: &T,
|
|
145
|
+
callback: impl FnOnce(BitmapTextReadyEventArgs) + 'static,
|
|
146
|
+
) -> &Self {
|
|
147
|
+
let node = node.clone();
|
|
148
|
+
on_loaded(move |_| {
|
|
149
|
+
Self::prepare_text(&node);
|
|
150
|
+
callback(BitmapTextReadyEventArgs::EMPTY);
|
|
151
|
+
});
|
|
152
|
+
self
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
pub fn dirty_rect(&self, x: u32, y: u32, w: u32, h: u32) -> &Self {
|
|
156
|
+
let mut state = self.inner.borrow_mut();
|
|
157
|
+
if w == 0 || h == 0 || x >= state.width || y >= state.height {
|
|
158
|
+
return self;
|
|
159
|
+
}
|
|
160
|
+
let cw = (x + w).min(state.width) - x;
|
|
161
|
+
let ch = (y + h).min(state.height) - y;
|
|
162
|
+
if state.dirty_rects.len() < MAX_DIRTY_RECTS {
|
|
163
|
+
state.dirty_rects.push((x, y, cw, ch));
|
|
164
|
+
}
|
|
165
|
+
self
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
pub fn clear_dirty_rects(&self) -> &Self {
|
|
169
|
+
self.inner.borrow_mut().dirty_rects.clear();
|
|
170
|
+
self
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
pub fn has_dirty_rects(&self) -> bool {
|
|
174
|
+
!self.inner.borrow().dirty_rects.is_empty()
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
pub fn commit(&self) -> u32 {
|
|
178
|
+
let mut state = self.inner.borrow_mut();
|
|
179
|
+
assert!(!state.disposed, "Bitmap.commit() called after dispose.");
|
|
180
|
+
if state.canvas_used {
|
|
181
|
+
if let Some(context) = &state.draw_context {
|
|
182
|
+
context.flush();
|
|
183
|
+
}
|
|
184
|
+
unsafe {
|
|
185
|
+
ffi::fui_canvas_read_offscreen_pixels(
|
|
186
|
+
state.offscreen_id,
|
|
187
|
+
state.pixel_bytes.as_mut_ptr() as usize,
|
|
188
|
+
state.width,
|
|
189
|
+
state.height,
|
|
190
|
+
)
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
if state.dirty_rects.is_empty() {
|
|
194
|
+
unsafe {
|
|
195
|
+
ffi::fui_bitmap_commit(
|
|
196
|
+
state.texture_id,
|
|
197
|
+
state.pixel_bytes.as_ptr() as usize,
|
|
198
|
+
state.pixel_bytes.len() as u32,
|
|
199
|
+
state.width,
|
|
200
|
+
state.height,
|
|
201
|
+
)
|
|
202
|
+
};
|
|
203
|
+
} else {
|
|
204
|
+
let dirty_rects = std::mem::take(&mut state.dirty_rects);
|
|
205
|
+
for (x, y, w, h) in dirty_rects {
|
|
206
|
+
let mut rect_bytes = vec![0u8; (w as usize) * (h as usize) * 4];
|
|
207
|
+
for row in 0..h as usize {
|
|
208
|
+
let src = (((y as usize + row) * state.width as usize) + x as usize) * 4;
|
|
209
|
+
let dst = row * w as usize * 4;
|
|
210
|
+
let len = w as usize * 4;
|
|
211
|
+
rect_bytes[dst..dst + len].copy_from_slice(&state.pixel_bytes[src..src + len]);
|
|
212
|
+
}
|
|
213
|
+
unsafe {
|
|
214
|
+
ffi::fui_bitmap_commit_dirty(
|
|
215
|
+
state.texture_id,
|
|
216
|
+
rect_bytes.as_ptr() as usize,
|
|
217
|
+
rect_bytes.len() as u32,
|
|
218
|
+
state.width,
|
|
219
|
+
state.height,
|
|
220
|
+
x,
|
|
221
|
+
y,
|
|
222
|
+
w,
|
|
223
|
+
h,
|
|
224
|
+
)
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
assets::mark_texture_asset_ready(state.texture_id, state.width as f32, state.height as f32);
|
|
229
|
+
state.texture_id
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
pub fn dispose(&self) {
|
|
233
|
+
let mut state = self.inner.borrow_mut();
|
|
234
|
+
if state.disposed {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
state.disposed = true;
|
|
238
|
+
unsafe { ffi::fui_canvas_destroy_offscreen(state.offscreen_id) };
|
|
239
|
+
unsafe { ffi::fui_bitmap_release(state.texture_id) };
|
|
240
|
+
state.pixel_bytes.clear();
|
|
241
|
+
state.draw_context = None;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
impl Drop for Bitmap {
|
|
246
|
+
fn drop(&mut self) {
|
|
247
|
+
if Rc::strong_count(&self.inner) == 1 {
|
|
248
|
+
self.dispose();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
#[cfg(test)]
|
|
254
|
+
mod tests {
|
|
255
|
+
use super::Bitmap;
|
|
256
|
+
use crate::drawing::Paint;
|
|
257
|
+
use crate::ffi::{self, Call};
|
|
258
|
+
|
|
259
|
+
#[test]
|
|
260
|
+
fn bitmap_canvas_commit_flushes_offscreen_and_marks_asset_ready() {
|
|
261
|
+
ffi::test::reset();
|
|
262
|
+
let bitmap = Bitmap::new(32, 24);
|
|
263
|
+
let canvas = bitmap.canvas();
|
|
264
|
+
canvas.draw_rect(0.0, 0.0, 10.0, 12.0, Paint::fill(0xFF00FFFF));
|
|
265
|
+
bitmap.commit();
|
|
266
|
+
|
|
267
|
+
let calls = ffi::test::take_calls();
|
|
268
|
+
assert!(calls.iter().any(|call| matches!(
|
|
269
|
+
call,
|
|
270
|
+
Call::CanvasCreateOffscreen {
|
|
271
|
+
width: 32,
|
|
272
|
+
height: 24,
|
|
273
|
+
..
|
|
274
|
+
}
|
|
275
|
+
)));
|
|
276
|
+
assert!(calls
|
|
277
|
+
.iter()
|
|
278
|
+
.any(|call| matches!(call, Call::CanvasDrawBatch { .. })));
|
|
279
|
+
assert!(calls.iter().any(|call| matches!(
|
|
280
|
+
call,
|
|
281
|
+
Call::CanvasReadOffscreenPixels {
|
|
282
|
+
width: 32,
|
|
283
|
+
height: 24,
|
|
284
|
+
..
|
|
285
|
+
}
|
|
286
|
+
)));
|
|
287
|
+
assert!(calls.iter().any(|call| matches!(
|
|
288
|
+
call,
|
|
289
|
+
Call::BitmapCommit {
|
|
290
|
+
width: 32,
|
|
291
|
+
height: 24,
|
|
292
|
+
..
|
|
293
|
+
}
|
|
294
|
+
)));
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
#[test]
|
|
298
|
+
fn bitmap_dirty_commit_uses_subrect_upload() {
|
|
299
|
+
ffi::test::reset();
|
|
300
|
+
let bitmap = Bitmap::new(8, 6);
|
|
301
|
+
bitmap.dirty_rect(2, 1, 3, 2).commit();
|
|
302
|
+
|
|
303
|
+
let calls = ffi::test::take_calls();
|
|
304
|
+
assert!(calls.iter().any(|call| matches!(
|
|
305
|
+
call,
|
|
306
|
+
Call::BitmapCommitDirty {
|
|
307
|
+
full_width: 8,
|
|
308
|
+
full_height: 6,
|
|
309
|
+
sub_x: 2,
|
|
310
|
+
sub_y: 1,
|
|
311
|
+
sub_w: 3,
|
|
312
|
+
sub_h: 2,
|
|
313
|
+
..
|
|
314
|
+
}
|
|
315
|
+
)));
|
|
316
|
+
}
|
|
317
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
use crate::animation;
|
|
2
|
+
use crate::assets;
|
|
3
|
+
use crate::bindings::ui;
|
|
4
|
+
use crate::context_menu_manager;
|
|
5
|
+
use crate::event;
|
|
6
|
+
use crate::frame_signal;
|
|
7
|
+
use crate::viewport;
|
|
8
|
+
use crate::Application;
|
|
9
|
+
use std::cell::{Cell, RefCell};
|
|
10
|
+
|
|
11
|
+
#[derive(Clone, Debug, Default, PartialEq)]
|
|
12
|
+
pub struct ScrollEvent {
|
|
13
|
+
pub handle: u64,
|
|
14
|
+
pub offset_x: f32,
|
|
15
|
+
pub offset_y: f32,
|
|
16
|
+
pub content_width: f32,
|
|
17
|
+
pub content_height: f32,
|
|
18
|
+
pub viewport_width: f32,
|
|
19
|
+
pub viewport_height: f32,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
#[derive(Clone, Debug, Default, PartialEq)]
|
|
23
|
+
pub struct ContextMenuRequest {
|
|
24
|
+
pub handle: u64,
|
|
25
|
+
pub x: f32,
|
|
26
|
+
pub y: f32,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
|
30
|
+
pub struct AssetFailure {
|
|
31
|
+
pub id: u32,
|
|
32
|
+
pub error: String,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
#[derive(Clone, Debug, Default, PartialEq)]
|
|
36
|
+
pub struct AssetReady {
|
|
37
|
+
pub id: u32,
|
|
38
|
+
pub width: f32,
|
|
39
|
+
pub height: f32,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
thread_local! {
|
|
43
|
+
static CURRENT_ROUTE: RefCell<String> = const { RefCell::new(String::new()) };
|
|
44
|
+
static LAST_SCROLL: RefCell<Option<ScrollEvent>> = const { RefCell::new(None) };
|
|
45
|
+
static LAST_CONTEXT_MENU: RefCell<Option<ContextMenuRequest>> = const { RefCell::new(None) };
|
|
46
|
+
static CONTEXT_MENU_VISIBLE: Cell<bool> = const { Cell::new(false) };
|
|
47
|
+
static LAST_FONT_LOADED: Cell<Option<u32>> = const { Cell::new(None) };
|
|
48
|
+
static LAST_SVG_LOADED: RefCell<Option<AssetReady>> = const { RefCell::new(None) };
|
|
49
|
+
static LAST_SVG_FAILED: RefCell<Option<AssetFailure>> = const { RefCell::new(None) };
|
|
50
|
+
static LAST_TEXTURE_LOADED: RefCell<Option<AssetReady>> = const { RefCell::new(None) };
|
|
51
|
+
static LAST_TEXTURE_FAILED: RefCell<Option<AssetFailure>> = const { RefCell::new(None) };
|
|
52
|
+
static PERSIST_CAPTURE_COUNT: Cell<u32> = const { Cell::new(0) };
|
|
53
|
+
static PERSIST_RESTORE_COUNT: Cell<u32> = const { Cell::new(0) };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
fn read_utf8(ptr: *const u8, len: u32) -> String {
|
|
57
|
+
if ptr.is_null() || len == 0 {
|
|
58
|
+
return String::new();
|
|
59
|
+
}
|
|
60
|
+
let bytes = unsafe { std::slice::from_raw_parts(ptr, len as usize) };
|
|
61
|
+
String::from_utf8_lossy(bytes).into_owned()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
pub fn current_route() -> String {
|
|
65
|
+
CURRENT_ROUTE.with(|route| route.borrow().clone())
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
pub fn last_scroll_event() -> Option<ScrollEvent> {
|
|
69
|
+
LAST_SCROLL.with(|event| event.borrow().clone())
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
pub fn last_context_menu_request() -> Option<ContextMenuRequest> {
|
|
73
|
+
LAST_CONTEXT_MENU.with(|event| event.borrow().clone())
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
pub fn is_context_menu_visible() -> bool {
|
|
77
|
+
CONTEXT_MENU_VISIBLE.with(Cell::get)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
pub fn last_font_loaded() -> Option<u32> {
|
|
81
|
+
LAST_FONT_LOADED.with(Cell::get)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
pub fn last_svg_loaded() -> Option<AssetReady> {
|
|
85
|
+
LAST_SVG_LOADED.with(|event| event.borrow().clone())
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
pub fn last_svg_failed() -> Option<AssetFailure> {
|
|
89
|
+
LAST_SVG_FAILED.with(|event| event.borrow().clone())
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
pub fn last_texture_loaded() -> Option<AssetReady> {
|
|
93
|
+
LAST_TEXTURE_LOADED.with(|event| event.borrow().clone())
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
pub fn last_texture_failed() -> Option<AssetFailure> {
|
|
97
|
+
LAST_TEXTURE_FAILED.with(|event| event.borrow().clone())
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
pub fn persisted_capture_count() -> u32 {
|
|
101
|
+
PERSIST_CAPTURE_COUNT.with(Cell::get)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
pub fn persisted_restore_count() -> u32 {
|
|
105
|
+
PERSIST_RESTORE_COUNT.with(Cell::get)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
#[no_mangle]
|
|
109
|
+
pub extern "C" fn __fui_on_viewport_changed(width: f32, height: f32) {
|
|
110
|
+
ui::resize_window(width, height);
|
|
111
|
+
viewport::set_viewport_size(width, height);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
#[no_mangle]
|
|
115
|
+
pub extern "C" fn __fui_on_frame(timestamp_ms: f64) {
|
|
116
|
+
frame_signal::set_frame_time(timestamp_ms);
|
|
117
|
+
animation::tick_animations(timestamp_ms);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
#[no_mangle]
|
|
121
|
+
pub extern "C" fn __fui_on_route_changed(route_ptr: *const u8, route_len: u32) {
|
|
122
|
+
let route = read_utf8(route_ptr, route_len);
|
|
123
|
+
CURRENT_ROUTE.with(|slot| slot.replace(route));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
#[no_mangle]
|
|
127
|
+
pub extern "C" fn __fui_on_scroll(
|
|
128
|
+
handle: u64,
|
|
129
|
+
offset_x: f32,
|
|
130
|
+
offset_y: f32,
|
|
131
|
+
content_width: f32,
|
|
132
|
+
content_height: f32,
|
|
133
|
+
viewport_width: f32,
|
|
134
|
+
viewport_height: f32,
|
|
135
|
+
) {
|
|
136
|
+
LAST_SCROLL.with(|slot| {
|
|
137
|
+
slot.replace(Some(ScrollEvent {
|
|
138
|
+
handle,
|
|
139
|
+
offset_x,
|
|
140
|
+
offset_y,
|
|
141
|
+
content_width,
|
|
142
|
+
content_height,
|
|
143
|
+
viewport_width,
|
|
144
|
+
viewport_height,
|
|
145
|
+
}));
|
|
146
|
+
});
|
|
147
|
+
event::dispatch_scroll(
|
|
148
|
+
crate::node::NodeHandle::from_raw(handle),
|
|
149
|
+
offset_x,
|
|
150
|
+
offset_y,
|
|
151
|
+
content_width,
|
|
152
|
+
content_height,
|
|
153
|
+
viewport_width,
|
|
154
|
+
viewport_height,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
#[no_mangle]
|
|
159
|
+
pub extern "C" fn __fui_can_show_context_menu(handle: u64) -> bool {
|
|
160
|
+
context_menu_manager::can_show_for_handle(handle)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
#[no_mangle]
|
|
164
|
+
pub extern "C" fn __fui_on_context_menu(handle: u64, x: f32, y: f32) {
|
|
165
|
+
LAST_CONTEXT_MENU.with(|slot| slot.replace(Some(ContextMenuRequest { handle, x, y })));
|
|
166
|
+
let shown = context_menu_manager::show_for_current_selection(handle, x, y);
|
|
167
|
+
CONTEXT_MENU_VISIBLE.with(|visible| visible.set(shown));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
#[no_mangle]
|
|
171
|
+
pub extern "C" fn __fui_hide_active_context_menu() {
|
|
172
|
+
context_menu_manager::hide_active_menu();
|
|
173
|
+
CONTEXT_MENU_VISIBLE.with(|visible| visible.set(false));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
#[no_mangle]
|
|
177
|
+
pub extern "C" fn __fui_on_font_loaded(font_id: u32) {
|
|
178
|
+
LAST_FONT_LOADED.with(|slot| slot.set(Some(font_id)));
|
|
179
|
+
assets::on_font_loaded(font_id);
|
|
180
|
+
crate::typography::notify_font_loaded(font_id);
|
|
181
|
+
crate::text::notify_font_loaded(font_id);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
#[no_mangle]
|
|
185
|
+
pub extern "C" fn __fui_on_svg_loaded(svg_id: u32, width: f32, height: f32) {
|
|
186
|
+
LAST_SVG_LOADED.with(|slot| {
|
|
187
|
+
slot.replace(Some(AssetReady {
|
|
188
|
+
id: svg_id,
|
|
189
|
+
width,
|
|
190
|
+
height,
|
|
191
|
+
}))
|
|
192
|
+
});
|
|
193
|
+
assets::on_svg_loaded(svg_id, width, height);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
#[no_mangle]
|
|
197
|
+
pub extern "C" fn __fui_on_svg_failed(svg_id: u32, error_ptr: *const u8, error_len: u32) {
|
|
198
|
+
let error = read_utf8(error_ptr, error_len);
|
|
199
|
+
LAST_SVG_FAILED.with(|slot| {
|
|
200
|
+
slot.replace(Some(AssetFailure {
|
|
201
|
+
id: svg_id,
|
|
202
|
+
error: error.clone(),
|
|
203
|
+
}))
|
|
204
|
+
});
|
|
205
|
+
assets::on_svg_failed(svg_id, error);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
#[no_mangle]
|
|
209
|
+
pub extern "C" fn __fui_on_texture_loaded(texture_id: u32, width: f32, height: f32) {
|
|
210
|
+
LAST_TEXTURE_LOADED.with(|slot| {
|
|
211
|
+
slot.replace(Some(AssetReady {
|
|
212
|
+
id: texture_id,
|
|
213
|
+
width,
|
|
214
|
+
height,
|
|
215
|
+
}))
|
|
216
|
+
});
|
|
217
|
+
assets::on_texture_loaded(texture_id, width, height);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
#[no_mangle]
|
|
221
|
+
pub extern "C" fn __fui_on_texture_failed(texture_id: u32, error_ptr: *const u8, error_len: u32) {
|
|
222
|
+
let error = read_utf8(error_ptr, error_len);
|
|
223
|
+
LAST_TEXTURE_FAILED.with(|slot| {
|
|
224
|
+
slot.replace(Some(AssetFailure {
|
|
225
|
+
id: texture_id,
|
|
226
|
+
error: error.clone(),
|
|
227
|
+
}))
|
|
228
|
+
});
|
|
229
|
+
assets::on_texture_failed(texture_id, error);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
#[no_mangle]
|
|
233
|
+
pub extern "C" fn __fui_capture_persisted_ui_state() {
|
|
234
|
+
PERSIST_CAPTURE_COUNT.with(|count| count.set(count.get() + 1));
|
|
235
|
+
Application::capture_persisted_ui_state();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
#[no_mangle]
|
|
239
|
+
pub extern "C" fn __fui_restore_persisted_ui_state() {
|
|
240
|
+
PERSIST_RESTORE_COUNT.with(|count| count.set(count.get() + 1));
|
|
241
|
+
Application::restore_persisted_ui_state();
|
|
242
|
+
}
|