Skip to main content

azul_core/
callbacks.rs

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