azul_core/callbacks.rs
1//! Callback types for the Azul UI framework.
2//!
3//! This module defines the callback infrastructure used by the event system,
4//! layout engine, and virtual view rendering. Key design patterns:
5//!
6//! - **Core vs Layout callback split**: `CoreCallbackType` and
7//! `CoreRenderImageCallbackType` store function pointers as `usize` to avoid
8//! circular dependencies between `azul-core` and `azul-layout`. The actual
9//! function pointer types are defined in `azul-layout` and transmuted at
10//! invocation time.
11//!
12//! - **FFI callable pattern**: Callback structs carry an optional
13//! `ctx: OptionRefAny` field that holds a foreign callable (e.g. a Python
14//! function object). The `extern "C"` trampoline stored in `cb` extracts
15//! both the user data and the foreign callable from `RefAny` and dispatches
16//! the call. Native Rust code sets `ctx` to `None`.
17//!
18//! - **Info structs**: `LayoutCallbackInfo`, `VirtualViewCallbackInfo`, and
19//! the layout-side `CallbackInfo` provide read-only access to framework
20//! resources (fonts, images, GL context, window size) during callback
21//! invocation.
22
23#[cfg(not(feature = "std"))]
24use alloc::string::ToString;
25use alloc::{alloc::Layout, boxed::Box, collections::BTreeMap, sync::Arc, vec::Vec};
26use core::{
27 ffi::c_void,
28 fmt,
29 sync::atomic::{AtomicUsize, Ordering as AtomicOrdering},
30};
31#[cfg(feature = "std")]
32use std::hash::Hash;
33
34use azul_css::{
35 css::{CssPath, CssPropertyValue},
36 props::{
37 basic::{
38 AnimationInterpolationFunction, FontRef, InterpolateResolver, LayoutRect, LayoutSize,
39 },
40 property::{CssProperty, CssPropertyType},
41 },
42 system::SystemStyle,
43 AzString,
44};
45use rust_fontconfig::{FcFontCache, OwnedFontSource};
46
47use crate::{
48 dom::{Dom, DomId, DomNodeId, EventFilter, OptionDom},
49 geom::{LogicalPosition, LogicalRect, LogicalSize, OptionLogicalPosition, PhysicalSize},
50 gl::OptionGlContextPtr,
51 hit_test::OverflowingScrollNode,
52 id::{NodeDataContainer, NodeDataContainerRef, NodeDataContainerRefMut, NodeId},
53 prop_cache::CssPropertyCache,
54 refany::{OptionRefAny, RefAny},
55 resources::{
56 DpiScaleFactor, FontInstanceKey, IdNamespace, ImageCache, ImageMask, ImageRef,
57 RendererResources,
58 },
59 styled_dom::{
60 NodeHierarchyItemId, NodeHierarchyItemVec, StyledNode,
61 StyledNodeVec,
62 },
63 task::{
64 Duration as AzDuration, GetSystemTimeCallback, Instant as AzInstant, Instant,
65 TerminateTimer, ThreadId, ThreadReceiver, ThreadSendMsg, TimerId,
66 },
67 window::{
68 AzStringPair, KeyboardState, MouseState, OptionChar, RawWindowHandle, UpdateFocusWarning,
69 WindowFlags, WindowSize, WindowTheme,
70 },
71 FastBTreeSet, OrderedMap,
72};
73
74/// Specifies if the screen should be updated after the callback function has returned
75#[repr(C)]
76#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
77pub enum Update {
78 /// The screen does not need to redraw after the callback has been called
79 DoNothing,
80 /// After the callback is called, the screen needs to redraw (layout() function being called
81 /// again)
82 RefreshDom,
83 /// The layout has to be re-calculated for all windows
84 RefreshDomAllWindows,
85}
86
87impl Update {
88 pub fn max_self(&mut self, other: Self) {
89 if *self == Update::DoNothing && other != Update::DoNothing {
90 *self = other;
91 } else if *self == Update::RefreshDom && other == Update::RefreshDomAllWindows {
92 *self = other;
93 }
94 }
95}
96
97// -- layout callback
98
99/// Callback function pointer (has to be a function pointer in
100/// order to be compatible with C APIs later on).
101///
102/// IMPORTANT: The callback needs to deallocate the `RefAnyPtr` and `LayoutCallbackInfoPtr`,
103/// otherwise that memory is leaked. If you use the official auto-generated
104/// bindings, this is already done for you.
105///
106/// NOTE: The original callback was `fn(&self, LayoutCallbackInfo) -> Dom`
107/// which then evolved to `fn(&RefAny, LayoutCallbackInfo) -> Dom`.
108/// The indirection is necessary because of the memory management
109/// around the C API
110///
111/// The memory management across the callback boundary is handled by
112/// the caller (see `LayoutCallback` and `LayoutCallbackInfo`).
113pub type LayoutCallbackType = extern "C" fn(RefAny, LayoutCallbackInfo) -> crate::dom::Dom;
114
115extern "C" fn default_layout_callback(_: RefAny, _: LayoutCallbackInfo) -> crate::dom::Dom {
116 crate::dom::Dom::create_body()
117}
118
119/// Wrapper around the layout callback
120///
121/// For FFI languages (Python, Java, etc.), the RefAny contains both:
122/// - The user's application data
123/// - The callback function object from the foreign language
124///
125/// The trampoline function (stored in `cb`) knows how to extract both
126/// from the RefAny and invoke the foreign callback with the user data.
127#[repr(C)]
128pub struct LayoutCallback {
129 pub cb: LayoutCallbackType,
130 /// For FFI: stores the foreign callable (e.g., PyFunction)
131 /// Native Rust code sets this to None
132 pub ctx: OptionRefAny,
133}
134
135impl_callback!(LayoutCallback, LayoutCallbackType);
136
137impl LayoutCallback {
138 pub fn create<I: Into<Self>>(cb: I) -> Self {
139 cb.into()
140 }
141}
142
143// Host-invoker plumbing for managed-FFI bindings (Lua, Ruby, Perl, …):
144// expands to a static `az_layout_callback_thunk` (the `cb` we hand to the
145// framework when the host calls `LayoutCallback::create_from_host_handle`),
146// an `AzLayoutCallback_createFromHostHandle` C-ABI export, plus the
147// `AzApp_setLayoutCallbackInvoker` setter the host calls once at module
148// load. See `crate::host_invoker` for the design.
149crate::impl_managed_callback! {
150 wrapper: LayoutCallback,
151 info_ty: LayoutCallbackInfo,
152 return_ty: crate::dom::Dom,
153 default_ret: crate::dom::Dom::create_body(),
154 invoker_static: LAYOUT_CALLBACK_INVOKER,
155 invoker_ty: AzLayoutCallbackInvoker,
156 thunk_fn: az_layout_callback_thunk,
157 setter_fn: AzApp_setLayoutCallbackInvoker,
158 from_handle_fn: AzLayoutCallback_createFromHostHandle,
159}
160
161impl Default for LayoutCallback {
162 fn default() -> Self {
163 Self {
164 cb: default_layout_callback,
165 ctx: OptionRefAny::None,
166 }
167 }
168}
169
170// -- virtualized view callback
171
172pub type VirtualViewCallbackType = extern "C" fn(RefAny, VirtualViewCallbackInfo) -> VirtualViewReturn;
173
174/// Callback that, given a rectangle area on the screen, returns the DOM
175/// appropriate for that bounds (useful for infinite lists)
176#[repr(C)]
177pub struct VirtualViewCallback {
178 pub cb: VirtualViewCallbackType,
179 /// For FFI: stores the foreign callable (e.g., PyFunction)
180 /// Native Rust code sets this to None
181 pub ctx: OptionRefAny,
182}
183impl_callback!(VirtualViewCallback, VirtualViewCallbackType);
184
185// Host-invoker plumbing for VirtualViewCallback. See `crate::host_invoker`.
186crate::impl_managed_callback! {
187 wrapper: VirtualViewCallback,
188 info_ty: VirtualViewCallbackInfo,
189 return_ty: VirtualViewReturn,
190 default_ret: VirtualViewReturn::default(),
191 invoker_static: VIRTUAL_VIEW_CALLBACK_INVOKER,
192 invoker_ty: AzVirtualViewCallbackInvoker,
193 thunk_fn: az_virtual_view_callback_thunk,
194 setter_fn: AzApp_setVirtualViewCallbackInvoker,
195 from_handle_fn: AzVirtualViewCallback_createFromHostHandle,
196}
197
198impl VirtualViewCallback {
199 pub fn create(cb: VirtualViewCallbackType) -> Self {
200 Self {
201 cb,
202 ctx: OptionRefAny::None,
203 }
204 }
205}
206
207/// Reason why a VirtualView callback is being invoked.
208///
209/// This helps the callback optimize its behavior based on why it's being called.
210#[derive(Debug, Clone, Copy, PartialEq, Eq)]
211#[repr(C, u8)]
212pub enum VirtualViewCallbackReason {
213 /// Initial render - first time the VirtualView appears
214 InitialRender,
215 /// Parent DOM was recreated (cache invalidated)
216 DomRecreated,
217 /// Window/VirtualView bounds expanded beyond current scroll_size
218 BoundsExpanded,
219 /// Scroll position is near an edge (within `EDGE_THRESHOLD`, currently 200px)
220 EdgeScrolled(EdgeType),
221 /// Scroll position extends beyond current scroll_size
222 ScrollBeyondContent,
223}
224
225/// Which edge triggered a scroll-based re-invocation
226#[derive(Debug, Clone, Copy, PartialEq, Eq)]
227#[repr(C)]
228pub enum EdgeType {
229 Top,
230 Bottom,
231 Left,
232 Right,
233}
234
235#[derive(Debug)]
236#[repr(C)]
237pub struct VirtualViewCallbackInfo {
238 pub reason: VirtualViewCallbackReason,
239 pub system_fonts: *const FcFontCache,
240 pub image_cache: *const ImageCache,
241 pub window_theme: WindowTheme,
242 pub bounds: HidpiAdjustedBounds,
243 pub scroll_size: LogicalSize,
244 pub scroll_offset: LogicalPosition,
245 pub virtual_scroll_size: LogicalSize,
246 pub virtual_scroll_offset: LogicalPosition,
247 /// Pointer to the callable (OptionRefAny) for FFI language bindings (Python, etc.)
248 /// Set by the caller before invoking the callback. Native Rust callbacks have this as null.
249 callable_ptr: *const OptionRefAny,
250 /// Extension for future ABI stability (mutable data)
251 _abi_mut: *mut c_void,
252}
253
254impl Clone for VirtualViewCallbackInfo {
255 fn clone(&self) -> Self {
256 Self {
257 reason: self.reason,
258 system_fonts: self.system_fonts,
259 image_cache: self.image_cache,
260 window_theme: self.window_theme,
261 bounds: self.bounds,
262 scroll_size: self.scroll_size,
263 scroll_offset: self.scroll_offset,
264 virtual_scroll_size: self.virtual_scroll_size,
265 virtual_scroll_offset: self.virtual_scroll_offset,
266 callable_ptr: self.callable_ptr,
267 _abi_mut: self._abi_mut,
268 }
269 }
270}
271
272impl VirtualViewCallbackInfo {
273 pub fn new<'a>(
274 reason: VirtualViewCallbackReason,
275 system_fonts: &'a FcFontCache,
276 image_cache: &'a ImageCache,
277 window_theme: WindowTheme,
278 bounds: HidpiAdjustedBounds,
279 scroll_size: LogicalSize,
280 scroll_offset: LogicalPosition,
281 virtual_scroll_size: LogicalSize,
282 virtual_scroll_offset: LogicalPosition,
283 ) -> Self {
284 Self {
285 reason,
286 system_fonts: system_fonts as *const FcFontCache,
287 image_cache: image_cache as *const ImageCache,
288 window_theme,
289 bounds,
290 scroll_size,
291 scroll_offset,
292 virtual_scroll_size,
293 virtual_scroll_offset,
294 callable_ptr: core::ptr::null(),
295 _abi_mut: core::ptr::null_mut(),
296 }
297 }
298
299 /// Set the callable pointer for FFI language bindings
300 pub fn set_callable_ptr(&mut self, callable: &OptionRefAny) {
301 self.callable_ptr = callable as *const OptionRefAny;
302 }
303
304 /// Get the callable for FFI language bindings (Python, etc.)
305 pub fn get_ctx(&self) -> OptionRefAny {
306 if self.callable_ptr.is_null() {
307 OptionRefAny::None
308 } else {
309 unsafe { (*self.callable_ptr).clone() }
310 }
311 }
312
313 pub fn get_bounds(&self) -> HidpiAdjustedBounds {
314 self.bounds
315 }
316
317 fn internal_get_system_fonts<'a>(&'a self) -> &'a FcFontCache {
318 unsafe { &*self.system_fonts }
319 }
320 fn internal_get_image_cache<'a>(&'a self) -> &'a ImageCache {
321 unsafe { &*self.image_cache }
322 }
323}
324
325/// Return value for a VirtualView rendering callback.
326///
327/// Contains two size/offset pairs for lazy loading and virtualization:
328///
329/// - `scroll_size` / `scroll_offset`: Size and position of actually rendered content
330/// - `virtual_scroll_size` / `virtual_scroll_offset`: Size for scrollbar representation
331///
332/// The callback is re-invoked on: initial render, parent DOM recreation, window expansion
333/// beyond `scroll_size`, or scrolling near content edges (`EDGE_THRESHOLD`, currently 200px).
334///
335/// Return `OptionDom::None` to keep the current DOM and only update scroll bounds.
336#[derive(Debug, Clone, PartialEq)]
337#[repr(C)]
338pub struct VirtualViewReturn {
339 /// The DOM with actual rendered content, or None to keep current DOM.
340 ///
341 /// - `OptionDom::Some(dom)` - Replace current content with this new DOM
342 /// - `OptionDom::None` - Keep using the previous DOM, only update scroll bounds
343 ///
344 /// Returning `None` is an optimization when the callback determines that the
345 /// current content is sufficient (e.g., already rendered ahead of scroll position).
346 pub dom: OptionDom,
347
348 /// Size of the actual rendered content rectangle.
349 ///
350 /// This is the size of the content in the `dom` field (if Some). It may be smaller than
351 /// `virtual_scroll_size` if only a subset of content is rendered (virtualization).
352 ///
353 /// **Example**: For a table showing rows 10-30, this might be 600px tall
354 /// (20 rows x 30px each).
355 pub scroll_size: LogicalSize,
356
357 /// Offset of the actual rendered content within the virtual coordinate space.
358 ///
359 /// This positions the rendered content within the larger virtual space. For
360 /// virtualized content, this will be non-zero to indicate where the rendered
361 /// "window" starts.
362 ///
363 /// **Example**: For a table showing rows 10-30, this might be y=300
364 /// (row 10 starts 300px from the top).
365 pub scroll_offset: LogicalPosition,
366
367 /// Size of the virtual content rectangle (for scrollbar sizing).
368 ///
369 /// This is the size the scrollbar will represent. It can be much larger than
370 /// `scroll_size` to enable lazy loading and virtualization.
371 ///
372 /// **Example**: For a 1000-row table, this might be 30,000px tall
373 /// (1000 rows x 30px each), even though only 20 rows are actually rendered.
374 pub virtual_scroll_size: LogicalSize,
375
376 /// Offset of the virtual content (usually zero).
377 ///
378 /// This is typically `(0, 0)` since the virtual space usually starts at the origin.
379 /// Advanced use cases might use this for complex virtualization scenarios.
380 pub virtual_scroll_offset: LogicalPosition,
381}
382
383impl Default for VirtualViewReturn {
384 fn default() -> VirtualViewReturn {
385 VirtualViewReturn {
386 dom: OptionDom::None,
387 scroll_size: LogicalSize::zero(),
388 scroll_offset: LogicalPosition::zero(),
389 virtual_scroll_size: LogicalSize::zero(),
390 virtual_scroll_offset: LogicalPosition::zero(),
391 }
392 }
393}
394
395impl VirtualViewReturn {
396 /// Creates a new VirtualViewReturn with updated DOM content.
397 ///
398 /// Use this when the callback has rendered new content to display.
399 ///
400 /// # Arguments
401 /// - `dom` - The new DOM to render
402 /// - `scroll_size` - Size of the actual rendered content
403 /// - `scroll_offset` - Position of rendered content in virtual space
404 /// - `virtual_scroll_size` - Size for scrollbar representation
405 /// - `virtual_scroll_offset` - Usually `LogicalPosition::zero()`
406 pub fn with_dom(
407 dom: Dom,
408 scroll_size: LogicalSize,
409 scroll_offset: LogicalPosition,
410 virtual_scroll_size: LogicalSize,
411 virtual_scroll_offset: LogicalPosition,
412 ) -> Self {
413 Self {
414 dom: OptionDom::Some(dom),
415 scroll_size,
416 scroll_offset,
417 virtual_scroll_size,
418 virtual_scroll_offset,
419 }
420 }
421
422 /// Creates a return value that keeps the current DOM unchanged.
423 ///
424 /// Use this when the callback determines that the existing content
425 /// is sufficient (e.g., already rendered ahead of scroll position).
426 /// This is an optimization to avoid rebuilding the DOM unnecessarily.
427 ///
428 /// # Arguments
429 /// - `scroll_size` - Size of the current rendered content
430 /// - `scroll_offset` - Position of current content in virtual space
431 /// - `virtual_scroll_size` - Size for scrollbar representation
432 /// - `virtual_scroll_offset` - Usually `LogicalPosition::zero()`
433 pub fn keep_current(
434 scroll_size: LogicalSize,
435 scroll_offset: LogicalPosition,
436 virtual_scroll_size: LogicalSize,
437 virtual_scroll_offset: LogicalPosition,
438 ) -> Self {
439 Self {
440 dom: OptionDom::None,
441 scroll_size,
442 scroll_offset,
443 virtual_scroll_size,
444 virtual_scroll_offset,
445 }
446 }
447
448}
449
450// -- thread callback
451
452// -- timer callback
453
454#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
455#[repr(C)]
456pub struct TimerCallbackReturn {
457 pub should_update: Update,
458 pub should_terminate: TerminateTimer,
459}
460
461impl TimerCallbackReturn {
462 /// Creates a new TimerCallbackReturn with the given update and terminate flags.
463 pub fn create(should_update: Update, should_terminate: TerminateTimer) -> Self {
464 Self {
465 should_update,
466 should_terminate,
467 }
468 }
469
470 /// Timer continues running, no DOM update needed.
471 pub fn continue_unchanged() -> Self {
472 Self {
473 should_update: Update::DoNothing,
474 should_terminate: TerminateTimer::Continue,
475 }
476 }
477
478 /// Timer continues running and DOM should be refreshed.
479 pub fn continue_and_refresh_dom() -> Self {
480 Self {
481 should_update: Update::RefreshDom,
482 should_terminate: TerminateTimer::Continue,
483 }
484 }
485
486 /// Timer should stop, no DOM update needed.
487 pub fn terminate_unchanged() -> Self {
488 Self {
489 should_update: Update::DoNothing,
490 should_terminate: TerminateTimer::Terminate,
491 }
492 }
493
494 /// Timer should stop and DOM should be refreshed.
495 pub fn terminate_and_refresh_dom() -> Self {
496 Self {
497 should_update: Update::RefreshDom,
498 should_terminate: TerminateTimer::Terminate,
499 }
500 }
501}
502
503impl Default for TimerCallbackReturn {
504 fn default() -> Self {
505 Self::continue_unchanged()
506 }
507}
508
509/// Gives the `layout()` function access to the `RendererResources` and the `Window`
510/// (for querying images and fonts, as well as width / height)
511#[derive(Debug)]
512#[repr(C)]
513/// Reference data container for LayoutCallbackInfo (all read-only fields)
514///
515/// This struct consolidates all readonly references that layout callbacks need to query state.
516/// By grouping these into a single struct, we reduce the number of parameters to
517/// LayoutCallbackInfo::new() from 6 to 2, making the API more maintainable and easier to extend.
518///
519/// This is pure syntax sugar - the struct lives on the stack in the caller and is passed by
520/// reference.
521pub struct LayoutCallbackInfoRefData<'a> {
522 /// Allows the layout() function to reference image IDs
523 pub image_cache: &'a ImageCache,
524 /// OpenGL context so that the layout() function can render textures
525 pub gl_context: &'a OptionGlContextPtr,
526 /// Reference to the system font cache
527 pub system_fonts: &'a FcFontCache,
528 /// Platform-specific system style (colors, spacing, etc.)
529 /// Used for CSD rendering and menu windows.
530 pub system_style: Arc<SystemStyle>,
531 /// Active route match (if routing is configured).
532 /// Contains the matched pattern and extracted parameters.
533 pub active_route: Option<&'a crate::resources::RouteMatch>,
534}
535
536/// What triggered the current `layout()` invocation.
537///
538/// The framework re-invokes the layout callback for any change that may
539/// produce a structurally different DOM (resize across a CSS breakpoint,
540/// theme toggle, route switch, callback returning `Update::RefreshDom`).
541/// `LayoutCallbackInfo::relayout_reason()` exposes which trigger this
542/// particular call corresponds to so the callback can branch — for
543/// example, skip expensive analytics on `Resize` calls.
544#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
545#[repr(C)]
546pub enum RelayoutReason {
547 /// First layout call for this window.
548 Initial,
549 /// A user callback returned `Update::RefreshDom`.
550 RefreshDom,
551 /// Window size changed across a CSS breakpoint or DPI scale change.
552 /// The callback can branch on `info.window_width_*` to emit a
553 /// different tree (e.g. hamburger menu vs sidebar).
554 Resize,
555 /// System theme changed (light/dark).
556 ThemeChange,
557 /// `CallbackInfo::switch_route` or `set_route_param` produced a new
558 /// route match. The callback should branch on
559 /// `info.get_active_route()`.
560 RouteChange,
561 /// Catch-all for relayouts that don't fit one of the above categories.
562 Other,
563}
564
565impl Default for RelayoutReason {
566 fn default() -> Self { RelayoutReason::Initial }
567}
568
569#[repr(C)]
570pub struct LayoutCallbackInfo {
571 /// Single reference to all readonly reference data
572 /// This consolidates 4 individual parameters into 1, improving API ergonomics
573 ref_data: *const LayoutCallbackInfoRefData<'static>,
574 /// Window size (so that apps can return a different UI depending on
575 /// the window size - mobile / desktop view). Should be later removed
576 /// in favor of "resize" handlers and @media queries.
577 pub window_size: WindowSize,
578 /// Registers whether the UI is dependent on the window theme
579 pub theme: WindowTheme,
580 /// What triggered this `layout()` call. Read via `relayout_reason()`.
581 pub relayout_reason: RelayoutReason,
582 /// Pointer to the callable (OptionRefAny) for FFI language bindings (Python, etc.)
583 /// Set by the caller before invoking the callback. Native Rust callbacks have this as null.
584 callable_ptr: *const OptionRefAny,
585 /// Extension for future ABI stability (mutable data)
586 _abi_mut: *mut core::ffi::c_void,
587}
588
589impl Clone for LayoutCallbackInfo {
590 fn clone(&self) -> Self {
591 Self {
592 ref_data: self.ref_data,
593 window_size: self.window_size,
594 theme: self.theme,
595 relayout_reason: self.relayout_reason,
596 callable_ptr: self.callable_ptr,
597 _abi_mut: self._abi_mut,
598 }
599 }
600}
601
602impl core::fmt::Debug for LayoutCallbackInfo {
603 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
604 f.debug_struct("LayoutCallbackInfo")
605 .field("window_size", &self.window_size)
606 .field("theme", &self.theme)
607 .field("relayout_reason", &self.relayout_reason)
608 .finish_non_exhaustive()
609 }
610}
611
612impl LayoutCallbackInfo {
613 pub fn new<'a>(
614 ref_data: &'a LayoutCallbackInfoRefData<'a>,
615 window_size: WindowSize,
616 theme: WindowTheme,
617 ) -> Self {
618 Self::new_with_reason(ref_data, window_size, theme, RelayoutReason::Initial)
619 }
620
621 pub fn new_with_reason<'a>(
622 ref_data: &'a LayoutCallbackInfoRefData<'a>,
623 window_size: WindowSize,
624 theme: WindowTheme,
625 relayout_reason: RelayoutReason,
626 ) -> Self {
627 Self {
628 // SAFETY: We cast away the lifetime 'a to 'static because LayoutCallbackInfo
629 // only lives for the duration of the callback, which is shorter than 'a
630 ref_data: ref_data as *const LayoutCallbackInfoRefData<'a>
631 as *const LayoutCallbackInfoRefData<'static>,
632 window_size,
633 theme,
634 relayout_reason,
635 callable_ptr: core::ptr::null(),
636 _abi_mut: core::ptr::null_mut(),
637 }
638 }
639
640 /// Returns what triggered the current `layout()` invocation.
641 pub fn relayout_reason(&self) -> RelayoutReason {
642 self.relayout_reason
643 }
644
645 /// Set the callable pointer for FFI language bindings
646 pub fn set_callable_ptr(&mut self, callable: &OptionRefAny) {
647 self.callable_ptr = callable as *const OptionRefAny;
648 }
649
650 /// Get the callable for FFI language bindings (Python, etc.)
651 pub fn get_ctx(&self) -> OptionRefAny {
652 if self.callable_ptr.is_null() {
653 OptionRefAny::None
654 } else {
655 unsafe { (*self.callable_ptr).clone() }
656 }
657 }
658
659 /// Get a clone of the system style Arc
660 pub fn get_system_style(&self) -> Arc<SystemStyle> {
661 unsafe { (*self.ref_data).system_style.clone() }
662 }
663
664 fn internal_get_image_cache<'a>(&'a self) -> &'a ImageCache {
665 unsafe { (*self.ref_data).image_cache }
666 }
667 fn internal_get_system_fonts<'a>(&'a self) -> &'a FcFontCache {
668 unsafe { (*self.ref_data).system_fonts }
669 }
670 fn internal_get_gl_context<'a>(&'a self) -> &'a OptionGlContextPtr {
671 unsafe { (*self.ref_data).gl_context }
672 }
673
674 pub fn get_gl_context(&self) -> OptionGlContextPtr {
675 self.internal_get_gl_context().clone()
676 }
677
678 pub fn get_system_fonts(&self) -> Vec<AzStringPair> {
679 let fc_cache = self.internal_get_system_fonts();
680
681 fc_cache
682 .list()
683 .into_iter()
684 .filter_map(|(pattern, font_id)| {
685 let source = fc_cache.get_font_by_id(&font_id)?;
686 match source {
687 OwnedFontSource::Memory(_) => None,
688 OwnedFontSource::Disk(d) => Some((pattern.name.as_ref()?.clone(), d.path.clone())),
689 }
690 })
691 .map(|(k, v)| AzStringPair {
692 key: k.into(),
693 value: v.into(),
694 })
695 .collect()
696 }
697
698 pub fn get_image(&self, image_id: &AzString) -> Option<ImageRef> {
699 self.internal_get_image_cache()
700 .get_css_image_id(image_id)
701 .cloned()
702 }
703
704 /// Get the active route match (pattern + extracted parameters).
705 ///
706 /// Returns `None` if no routes are configured or no route is active.
707 pub fn get_active_route(&self) -> Option<&crate::resources::RouteMatch> {
708 unsafe { (*self.ref_data).active_route }
709 }
710
711 /// Get a route parameter by key (e.g. `get_route_param("id")` for `/user/:id`).
712 ///
713 /// Returns `None` if no route is active or the parameter doesn't exist.
714 pub fn get_route_param(&self, key: &str) -> Option<&AzString> {
715 self.get_active_route()?.get_param(key)
716 }
717
718 // Responsive layout helper methods
719 /// Returns true if the window width is less than the given pixel value
720 pub fn window_width_less_than(&self, px: f32) -> bool {
721 self.window_size.dimensions.width < px
722 }
723
724 /// Returns true if the window width is greater than the given pixel value
725 pub fn window_width_greater_than(&self, px: f32) -> bool {
726 self.window_size.dimensions.width > px
727 }
728
729 /// Returns true if the window width is between min and max (inclusive)
730 pub fn window_width_between(&self, min_px: f32, max_px: f32) -> bool {
731 let width = self.window_size.dimensions.width;
732 width >= min_px && width <= max_px
733 }
734
735 /// Returns true if the window height is less than the given pixel value
736 pub fn window_height_less_than(&self, px: f32) -> bool {
737 self.window_size.dimensions.height < px
738 }
739
740 /// Returns true if the window height is greater than the given pixel value
741 pub fn window_height_greater_than(&self, px: f32) -> bool {
742 self.window_size.dimensions.height > px
743 }
744
745 /// Returns true if the window height is between min and max (inclusive)
746 pub fn window_height_between(&self, min_px: f32, max_px: f32) -> bool {
747 let height = self.window_size.dimensions.height;
748 height >= min_px && height <= max_px
749 }
750
751 /// Returns the current window width in pixels
752 pub fn get_window_width(&self) -> f32 {
753 self.window_size.dimensions.width
754 }
755
756 /// Returns the current window height in pixels
757 pub fn get_window_height(&self) -> f32 {
758 self.window_size.dimensions.height
759 }
760
761 /// Returns the current window DPI scale factor (1.0 = 96 DPI, 2.0 = 192 DPI)
762 pub fn get_dpi_factor(&self) -> f32 {
763 self.window_size.dpi as f32 / 96.0
764 }
765}
766
767/// Information about the bounds of a laid-out div rectangle.
768///
769/// Necessary when invoking `VirtualViewCallbacks` and `RenderImageCallbacks`, so
770/// that they can change what their content is based on their size.
771#[derive(Debug, Copy, Clone)]
772#[repr(C)]
773pub struct HidpiAdjustedBounds {
774 pub logical_size: LogicalSize,
775 pub hidpi_factor: DpiScaleFactor,
776}
777
778impl HidpiAdjustedBounds {
779 #[inline(always)]
780 pub fn from_bounds(bounds: LayoutSize, hidpi_factor: DpiScaleFactor) -> Self {
781 let logical_size = LogicalSize::new(bounds.width as f32, bounds.height as f32);
782 Self {
783 logical_size,
784 hidpi_factor,
785 }
786 }
787
788 pub fn get_physical_size(&self) -> PhysicalSize<u32> {
789 self.get_logical_size()
790 .to_physical(self.get_hidpi_factor().inner.get())
791 }
792
793 pub fn get_logical_size(&self) -> LogicalSize {
794 self.logical_size
795 }
796
797 pub fn get_hidpi_factor(&self) -> DpiScaleFactor {
798 self.hidpi_factor
799 }
800}
801
802/// Defines the focus_targeted node ID for the next frame
803#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
804#[repr(C, u8)]
805pub enum FocusTarget {
806 Id(DomNodeId),
807 Path(FocusTargetPath),
808 Previous,
809 Next,
810 First,
811 Last,
812 NoFocus,
813}
814
815#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
816#[repr(C)]
817pub struct FocusTargetPath {
818 pub dom: DomId,
819 pub css_path: CssPath,
820}
821
822// -- normal callback
823
824// core callback types (usize-based placeholders)
825//
826// These types use `usize` instead of function pointers to avoid creating
827// a circular dependency between azul-core and azul-layout.
828//
829// The actual function pointers will be stored in azul-layout, which will
830// use unsafe code to transmute between usize and the real function pointers.
831//
832// IMPORTANT: The memory layout must be identical to the real types!
833//
834// Naming convention: "Core" prefix indicates these are the low-level types
835
836/// Core callback type - uses usize instead of function pointer to avoid circular dependencies.
837///
838/// **IMPORTANT**: This is NOT actually a usize at runtime - it's a function pointer that is
839/// cast to usize for storage in the data model. When invoking the callback, this usize is
840/// unsafely cast back to the actual function pointer type:
841/// `extern "C" fn(RefAny, CallbackInfo) -> Update`
842///
843/// This design allows azul-core to store callbacks without depending on azul-layout's CallbackInfo
844/// type. The actual function pointer type is defined in azul-layout as `CallbackType`.
845pub type CoreCallbackType = usize;
846
847/// Stores a callback as usize (actually a function pointer cast to usize)
848///
849/// **IMPORTANT**: The `cb` field stores a function pointer disguised as usize to avoid
850/// circular dependencies between azul-core and azul-layout. When creating a CoreCallback,
851/// you can directly assign a function pointer - Rust will implicitly cast it to usize.
852/// When invoking, the usize must be unsafely cast back to the function pointer type.
853///
854/// Must return an `Update` that denotes if the screen should be redrawn.
855#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
856#[repr(C)]
857pub struct CoreCallback {
858 pub cb: CoreCallbackType,
859 /// For FFI: stores the foreign callable (e.g., PyFunction)
860 /// Native Rust code sets this to None
861 pub ctx: OptionRefAny,
862}
863
864/// Allow creating CoreCallback from a raw function pointer (as usize)
865/// Sets callable to None (for native Rust/C usage)
866impl From<CoreCallbackType> for CoreCallback {
867 fn from(cb: CoreCallbackType) -> Self {
868 CoreCallback {
869 cb,
870 ctx: OptionRefAny::None,
871 }
872 }
873}
874
875impl_option!(
876 CoreCallback,
877 OptionCoreCallback,
878 [Debug, Eq, Clone, PartialEq, PartialOrd, Ord, Hash]
879);
880
881/// Data associated with a callback (event filter, callback, and user data)
882#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
883#[repr(C)]
884pub struct CoreCallbackData {
885 pub event: EventFilter,
886 pub callback: CoreCallback,
887 pub refany: RefAny,
888}
889
890impl_option!(
891 CoreCallbackData,
892 OptionCoreCallbackData,
893 copy = false,
894 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
895);
896
897impl_vec!(CoreCallbackData, CoreCallbackDataVec, CoreCallbackDataVecDestructor, CoreCallbackDataVecDestructorType, CoreCallbackDataVecSlice, OptionCoreCallbackData);
898impl_vec_clone!(
899 CoreCallbackData,
900 CoreCallbackDataVec,
901 CoreCallbackDataVecDestructor
902);
903impl_vec_mut!(CoreCallbackData, CoreCallbackDataVec);
904impl_vec_debug!(CoreCallbackData, CoreCallbackDataVec);
905impl_vec_partialord!(CoreCallbackData, CoreCallbackDataVec);
906impl_vec_ord!(CoreCallbackData, CoreCallbackDataVec);
907impl_vec_partialeq!(CoreCallbackData, CoreCallbackDataVec);
908impl_vec_eq!(CoreCallbackData, CoreCallbackDataVec);
909impl_vec_hash!(CoreCallbackData, CoreCallbackDataVec);
910
911impl CoreCallbackDataVec {
912 #[inline]
913 pub fn as_container<'a>(&'a self) -> NodeDataContainerRef<'a, CoreCallbackData> {
914 NodeDataContainerRef {
915 internal: self.as_ref(),
916 }
917 }
918 #[inline]
919 pub fn as_container_mut<'a>(&'a mut self) -> NodeDataContainerRefMut<'a, CoreCallbackData> {
920 NodeDataContainerRefMut {
921 internal: self.as_mut(),
922 }
923 }
924}
925
926// -- image rendering callback
927
928/// Image rendering callback type - uses usize instead of function pointer
929pub type CoreRenderImageCallbackType = usize;
930
931/// Callback that returns a rendered OpenGL texture (usize placeholder)
932#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
933#[repr(C)]
934pub struct CoreRenderImageCallback {
935 pub cb: CoreRenderImageCallbackType,
936 /// For FFI: stores the foreign callable (e.g., PyFunction)
937 /// Native Rust code sets this to None
938 pub ctx: OptionRefAny,
939}
940
941/// Allow creating CoreRenderImageCallback from a raw function pointer (as usize)
942/// Sets callable to None (for native Rust/C usage)
943impl From<CoreRenderImageCallbackType> for CoreRenderImageCallback {
944 fn from(cb: CoreRenderImageCallbackType) -> Self {
945 CoreRenderImageCallback {
946 cb,
947 ctx: OptionRefAny::None,
948 }
949 }
950}
951
952/// Image callback with associated data
953#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
954#[repr(C)]
955pub struct CoreImageCallback {
956 pub refany: RefAny,
957 pub callback: CoreRenderImageCallback,
958}
959
960impl_option!(
961 CoreImageCallback,
962 OptionCoreImageCallback,
963 copy = false,
964 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
965);