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(any(not(feature = "worker-runtime"), feature = "native-runtime"), no_mangle)]
113pub extern "C" fn __fui_on_viewport_changed(width: f32, height: f32) {
114 ui::resize_window(width, height);
115 viewport::set_viewport_size(width, height);
116}
117
118#[cfg_attr(any(not(feature = "worker-runtime"), feature = "native-runtime"), no_mangle)]
119pub extern "C" fn __fui_on_frame(timestamp_ms: f64) {
120 frame_signal::set_frame_time(timestamp_ms);
121 animation::tick_animations(timestamp_ms);
122}
123
124#[cfg_attr(any(not(feature = "worker-runtime"), feature = "native-runtime"), no_mangle)]
125pub unsafe extern "C" fn __fui_on_route_changed(route_ptr: *const u8, route_len: u32) {
128 let route = read_utf8(route_ptr, route_len);
129 CURRENT_ROUTE.with(|slot| slot.replace(route));
130}
131
132#[cfg_attr(any(not(feature = "worker-runtime"), feature = "native-runtime"), no_mangle)]
133pub extern "C" fn __fui_on_scroll(
134 handle: u64,
135 offset_x: f32,
136 offset_y: f32,
137 content_width: f32,
138 content_height: f32,
139 viewport_width: f32,
140 viewport_height: f32,
141) {
142 LAST_SCROLL.with(|slot| {
143 slot.replace(Some(ScrollEvent {
144 handle,
145 offset_x,
146 offset_y,
147 content_width,
148 content_height,
149 viewport_width,
150 viewport_height,
151 }));
152 });
153 event::dispatch_scroll(
154 crate::node::NodeHandle::from_raw(handle),
155 offset_x,
156 offset_y,
157 content_width,
158 content_height,
159 viewport_width,
160 viewport_height,
161 );
162}
163
164#[cfg_attr(any(not(feature = "worker-runtime"), feature = "native-runtime"), no_mangle)]
165pub extern "C" fn __fui_can_show_context_menu(handle: u64) -> bool {
166 context_menu_manager::can_show_for_handle(handle)
167}
168
169#[cfg_attr(any(not(feature = "worker-runtime"), feature = "native-runtime"), no_mangle)]
170pub extern "C" fn __fui_on_context_menu(handle: u64, x: f32, y: f32) {
171 LAST_CONTEXT_MENU.with(|slot| slot.replace(Some(ContextMenuRequest { handle, x, y })));
172 context_menu_manager::show_for_current_selection(handle, x, y);
173}
174
175#[cfg_attr(any(not(feature = "worker-runtime"), feature = "native-runtime"), no_mangle)]
176pub extern "C" fn __fui_hide_active_context_menu() {
177 context_menu_manager::hide_active_menu();
178}
179
180#[cfg_attr(any(not(feature = "worker-runtime"), feature = "native-runtime"), no_mangle)]
181pub extern "C" fn __fui_on_font_loaded(font_id: u32) {
182 LAST_FONT_LOADED.with(|slot| slot.set(Some(font_id)));
183 assets::on_font_loaded(font_id);
184 crate::typography::notify_font_loaded(font_id);
185 crate::text::notify_font_loaded(font_id);
186 crate::frame_scheduler::mark_needs_commit();
187}
188
189#[cfg_attr(any(not(feature = "worker-runtime"), feature = "native-runtime"), no_mangle)]
190pub extern "C" fn __fui_on_svg_loaded(svg_id: u32, width: f32, height: f32) {
191 LAST_SVG_LOADED.with(|slot| {
192 slot.replace(Some(AssetReady {
193 id: svg_id,
194 width,
195 height,
196 }))
197 });
198 assets::on_svg_loaded(svg_id, width, height);
199}
200
201#[cfg_attr(any(not(feature = "worker-runtime"), feature = "native-runtime"), no_mangle)]
202pub unsafe extern "C" fn __fui_on_svg_failed(svg_id: u32, error_ptr: *const u8, error_len: u32) {
205 let error = read_utf8(error_ptr, error_len);
206 LAST_SVG_FAILED.with(|slot| {
207 slot.replace(Some(AssetFailure {
208 id: svg_id,
209 error: error.clone(),
210 }))
211 });
212 assets::on_svg_failed(svg_id, error);
213}
214
215#[cfg_attr(any(not(feature = "worker-runtime"), feature = "native-runtime"), no_mangle)]
216pub extern "C" fn __fui_on_texture_loaded(texture_id: u32, width: f32, height: f32) {
217 LAST_TEXTURE_LOADED.with(|slot| {
218 slot.replace(Some(AssetReady {
219 id: texture_id,
220 width,
221 height,
222 }))
223 });
224 assets::on_texture_loaded(texture_id, width, height);
225}
226
227#[cfg_attr(any(not(feature = "worker-runtime"), feature = "native-runtime"), no_mangle)]
228pub unsafe extern "C" fn __fui_on_texture_failed(
231 texture_id: u32,
232 error_ptr: *const u8,
233 error_len: u32,
234) {
235 let error = read_utf8(error_ptr, error_len);
236 LAST_TEXTURE_FAILED.with(|slot| {
237 slot.replace(Some(AssetFailure {
238 id: texture_id,
239 error: error.clone(),
240 }))
241 });
242 assets::on_texture_failed(texture_id, error);
243}
244
245#[cfg_attr(any(not(feature = "worker-runtime"), feature = "native-runtime"), no_mangle)]
246pub extern "C" fn __fui_capture_persisted_ui_state() {
247 PERSIST_CAPTURE_COUNT.with(|count| count.set(count.get() + 1));
248 Application::capture_persisted_ui_state();
249}
250
251#[cfg_attr(any(not(feature = "worker-runtime"), feature = "native-runtime"), no_mangle)]
252pub extern "C" fn __fui_restore_persisted_ui_state() {
253 PERSIST_RESTORE_COUNT.with(|count| count.set(count.get() + 1));
254 Application::restore_persisted_ui_state();
255}