Skip to main content

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);