Skip to main content

fui/
bridge_callbacks.rs

1use crate::animation;
2use crate::assets;
3use crate::bindings::ui;
4use crate::context_menu_manager;
5use crate::event;
6use crate::frame_signal;
7use crate::viewport;
8use crate::Application;
9use std::cell::{Cell, RefCell};
10
11#[derive(Clone, Debug, Default, PartialEq)]
12pub 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)]
23pub struct ContextMenuRequest {
24    pub handle: u64,
25    pub x: f32,
26    pub y: f32,
27}
28
29#[derive(Clone, Debug, Default, PartialEq, Eq)]
30pub struct AssetFailure {
31    pub id: u32,
32    pub error: String,
33}
34
35#[derive(Clone, Debug, Default, PartialEq)]
36pub struct AssetReady {
37    pub id: u32,
38    pub width: f32,
39    pub height: f32,
40}
41
42thread_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 LAST_FONT_LOADED: Cell<Option<u32>> = const { Cell::new(None) };
47    static LAST_SVG_LOADED: RefCell<Option<AssetReady>> = const { RefCell::new(None) };
48    static LAST_SVG_FAILED: RefCell<Option<AssetFailure>> = const { RefCell::new(None) };
49    static LAST_TEXTURE_LOADED: RefCell<Option<AssetReady>> = const { RefCell::new(None) };
50    static LAST_TEXTURE_FAILED: RefCell<Option<AssetFailure>> = const { RefCell::new(None) };
51    static PERSIST_CAPTURE_COUNT: Cell<u32> = const { Cell::new(0) };
52    static PERSIST_RESTORE_COUNT: Cell<u32> = const { Cell::new(0) };
53}
54
55fn read_utf8(ptr: *const u8, len: u32) -> String {
56    if ptr.is_null() || len == 0 {
57        return String::new();
58    }
59    let bytes = unsafe { std::slice::from_raw_parts(ptr, len as usize) };
60    String::from_utf8_lossy(bytes).into_owned()
61}
62
63pub fn current_route() -> String {
64    CURRENT_ROUTE.with(|route| route.borrow().clone())
65}
66
67pub fn last_scroll_event() -> Option<ScrollEvent> {
68    LAST_SCROLL.with(|event| event.borrow().clone())
69}
70
71pub fn last_context_menu_request() -> Option<ContextMenuRequest> {
72    LAST_CONTEXT_MENU.with(|event| event.borrow().clone())
73}
74
75pub fn is_context_menu_visible() -> bool {
76    crate::controls::ContextMenu::is_active_menu_visible()
77}
78
79#[doc(hidden)]
80pub fn is_tool_tip_visible() -> bool {
81    crate::tool_tip_manager::ToolTipManager::is_visible()
82}
83
84pub fn last_font_loaded() -> Option<u32> {
85    LAST_FONT_LOADED.with(Cell::get)
86}
87
88pub fn last_svg_loaded() -> Option<AssetReady> {
89    LAST_SVG_LOADED.with(|event| event.borrow().clone())
90}
91
92pub fn last_svg_failed() -> Option<AssetFailure> {
93    LAST_SVG_FAILED.with(|event| event.borrow().clone())
94}
95
96pub fn last_texture_loaded() -> Option<AssetReady> {
97    LAST_TEXTURE_LOADED.with(|event| event.borrow().clone())
98}
99
100pub fn last_texture_failed() -> Option<AssetFailure> {
101    LAST_TEXTURE_FAILED.with(|event| event.borrow().clone())
102}
103
104pub fn persisted_capture_count() -> u32 {
105    PERSIST_CAPTURE_COUNT.with(Cell::get)
106}
107
108pub fn persisted_restore_count() -> u32 {
109    PERSIST_RESTORE_COUNT.with(Cell::get)
110}
111
112#[cfg_attr(
113    any(not(feature = "worker-runtime"), feature = "native-runtime"),
114    no_mangle
115)]
116pub extern "C" fn __fui_on_viewport_changed(width: f32, height: f32) {
117    ui::resize_window(width, height);
118    viewport::set_viewport_size(width, height);
119}
120
121#[cfg_attr(
122    any(not(feature = "worker-runtime"), feature = "native-runtime"),
123    no_mangle
124)]
125pub extern "C" fn __fui_on_frame(timestamp_ms: f64) {
126    frame_signal::set_frame_time(timestamp_ms);
127    animation::tick_animations(timestamp_ms);
128}
129
130#[cfg_attr(
131    any(not(feature = "worker-runtime"), feature = "native-runtime"),
132    no_mangle
133)]
134pub extern "C" fn __fui_needs_animation_frame() -> bool {
135    animation::get_animation_manager().has_active_animations()
136}
137
138#[cfg_attr(
139    any(not(feature = "worker-runtime"), feature = "native-runtime"),
140    no_mangle
141)]
142/// # Safety
143/// `route_ptr` must be null for an empty route or point to `route_len` readable bytes.
144pub unsafe extern "C" fn __fui_on_route_changed(route_ptr: *const u8, route_len: u32) {
145    let route = read_utf8(route_ptr, route_len);
146    CURRENT_ROUTE.with(|slot| slot.replace(route));
147}
148
149#[cfg_attr(
150    any(not(feature = "worker-runtime"), feature = "native-runtime"),
151    no_mangle
152)]
153pub extern "C" fn __fui_on_scroll(
154    handle: u64,
155    offset_x: f32,
156    offset_y: f32,
157    content_width: f32,
158    content_height: f32,
159    viewport_width: f32,
160    viewport_height: f32,
161) {
162    LAST_SCROLL.with(|slot| {
163        slot.replace(Some(ScrollEvent {
164            handle,
165            offset_x,
166            offset_y,
167            content_width,
168            content_height,
169            viewport_width,
170            viewport_height,
171        }));
172    });
173    event::dispatch_scroll(
174        crate::node::NodeHandle::from_raw(handle),
175        offset_x,
176        offset_y,
177        content_width,
178        content_height,
179        viewport_width,
180        viewport_height,
181    );
182}
183
184#[cfg_attr(
185    any(not(feature = "worker-runtime"), feature = "native-runtime"),
186    no_mangle
187)]
188pub extern "C" fn __fui_can_show_context_menu(handle: u64) -> bool {
189    context_menu_manager::can_show_for_handle(handle)
190}
191
192#[cfg_attr(
193    any(not(feature = "worker-runtime"), feature = "native-runtime"),
194    no_mangle
195)]
196pub extern "C" fn __fui_on_context_menu(handle: u64, x: f32, y: f32) {
197    LAST_CONTEXT_MENU.with(|slot| slot.replace(Some(ContextMenuRequest { handle, x, y })));
198    context_menu_manager::show_for_current_selection(handle, x, y);
199}
200
201#[cfg_attr(
202    any(not(feature = "worker-runtime"), feature = "native-runtime"),
203    no_mangle
204)]
205pub extern "C" fn __fui_hide_active_context_menu() {
206    context_menu_manager::hide_active_menu();
207}
208
209#[cfg_attr(
210    any(not(feature = "worker-runtime"), feature = "native-runtime"),
211    no_mangle
212)]
213pub extern "C" fn __fui_on_font_loaded(font_id: u32) {
214    LAST_FONT_LOADED.with(|slot| slot.set(Some(font_id)));
215    assets::on_font_loaded(font_id);
216    crate::typography::notify_font_loaded(font_id);
217    crate::text::notify_font_loaded(font_id);
218    crate::frame_scheduler::mark_needs_commit();
219}
220
221#[cfg_attr(
222    any(not(feature = "worker-runtime"), feature = "native-runtime"),
223    no_mangle
224)]
225pub extern "C" fn __fui_on_svg_loaded(svg_id: u32, width: f32, height: f32) {
226    LAST_SVG_LOADED.with(|slot| {
227        slot.replace(Some(AssetReady {
228            id: svg_id,
229            width,
230            height,
231        }))
232    });
233    assets::on_svg_loaded(svg_id, width, height);
234}
235
236#[cfg_attr(
237    any(not(feature = "worker-runtime"), feature = "native-runtime"),
238    no_mangle
239)]
240/// # Safety
241/// `error_ptr` must be null for an empty message or point to `error_len` readable bytes.
242pub unsafe extern "C" fn __fui_on_svg_failed(svg_id: u32, error_ptr: *const u8, error_len: u32) {
243    let error = read_utf8(error_ptr, error_len);
244    LAST_SVG_FAILED.with(|slot| {
245        slot.replace(Some(AssetFailure {
246            id: svg_id,
247            error: error.clone(),
248        }))
249    });
250    assets::on_svg_failed(svg_id, error);
251}
252
253#[cfg_attr(
254    any(not(feature = "worker-runtime"), feature = "native-runtime"),
255    no_mangle
256)]
257pub extern "C" fn __fui_on_texture_loaded(texture_id: u32, width: f32, height: f32) {
258    LAST_TEXTURE_LOADED.with(|slot| {
259        slot.replace(Some(AssetReady {
260            id: texture_id,
261            width,
262            height,
263        }))
264    });
265    assets::on_texture_loaded(texture_id, width, height);
266}
267
268#[cfg_attr(
269    any(not(feature = "worker-runtime"), feature = "native-runtime"),
270    no_mangle
271)]
272/// # Safety
273/// `error_ptr` must be null for an empty message or point to `error_len` readable bytes.
274pub unsafe extern "C" fn __fui_on_texture_failed(
275    texture_id: u32,
276    error_ptr: *const u8,
277    error_len: u32,
278) {
279    let error = read_utf8(error_ptr, error_len);
280    LAST_TEXTURE_FAILED.with(|slot| {
281        slot.replace(Some(AssetFailure {
282            id: texture_id,
283            error: error.clone(),
284        }))
285    });
286    assets::on_texture_failed(texture_id, error);
287}
288
289#[cfg_attr(
290    any(not(feature = "worker-runtime"), feature = "native-runtime"),
291    no_mangle
292)]
293pub extern "C" fn __fui_capture_persisted_ui_state() {
294    PERSIST_CAPTURE_COUNT.with(|count| count.set(count.get() + 1));
295    Application::capture_persisted_ui_state();
296}
297
298#[cfg_attr(
299    any(not(feature = "worker-runtime"), feature = "native-runtime"),
300    no_mangle
301)]
302pub extern "C" fn __fui_restore_persisted_ui_state() {
303    PERSIST_RESTORE_COUNT.with(|count| count.set(count.get() + 1));
304    Application::restore_persisted_ui_state();
305}