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"))]
24
use alloc::string::ToString;
25
use alloc::{alloc::Layout, boxed::Box, collections::BTreeMap, sync::Arc, vec::Vec};
26
use core::{
27
    ffi::c_void,
28
    fmt,
29
    sync::atomic::{AtomicUsize, Ordering as AtomicOrdering},
30
};
31
#[cfg(feature = "std")]
32
use std::hash::Hash;
33

            
34
use 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
};
45
use rust_fontconfig::{FcFontCache, OwnedFontSource};
46

            
47
use 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)]
77
pub 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

            
87
impl 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`).
113
pub type LayoutCallbackType = extern "C" fn(RefAny, LayoutCallbackInfo) -> crate::dom::Dom;
114

            
115
extern "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)]
128
pub 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

            
135
impl_callback!(LayoutCallback, LayoutCallbackType);
136

            
137
impl 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.
149
crate::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

            
161
impl Default for LayoutCallback {
162
5833
    fn default() -> Self {
163
5833
        Self {
164
5833
            cb: default_layout_callback,
165
5833
            ctx: OptionRefAny::None,
166
5833
        }
167
5833
    }
168
}
169

            
170
// -- virtualized view callback
171

            
172
pub 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)]
177
pub 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
}
183
impl_callback!(VirtualViewCallback, VirtualViewCallbackType);
184

            
185
// Host-invoker plumbing for VirtualViewCallback. See `crate::host_invoker`.
186
crate::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

            
198
impl 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)]
212
pub 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)]
228
pub enum EdgeType {
229
    Top,
230
    Bottom,
231
    Left,
232
    Right,
233
}
234

            
235
#[derive(Debug)]
236
#[repr(C)]
237
pub 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

            
254
impl 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

            
272
impl 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)]
338
pub 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

            
383
impl 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

            
395
impl 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)]
456
pub struct TimerCallbackReturn {
457
    pub should_update: Update,
458
    pub should_terminate: TerminateTimer,
459
}
460

            
461
impl 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

            
503
impl 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.
521
pub 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)]
546
pub 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

            
565
impl Default for RelayoutReason {
566
    fn default() -> Self { RelayoutReason::Initial }
567
}
568

            
569
#[repr(C)]
570
pub 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

            
589
impl 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

            
602
impl 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

            
612
impl 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)]
773
pub struct HidpiAdjustedBounds {
774
    pub logical_size: LogicalSize,
775
    pub hidpi_factor: DpiScaleFactor,
776
}
777

            
778
impl 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)]
805
pub 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)]
817
pub 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`.
845
pub 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)]
857
pub 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)
866
impl From<CoreCallbackType> for CoreCallback {
867
    fn from(cb: CoreCallbackType) -> Self {
868
        CoreCallback {
869
            cb,
870
            ctx: OptionRefAny::None,
871
        }
872
    }
873
}
874

            
875
impl_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)]
884
pub struct CoreCallbackData {
885
    pub event: EventFilter,
886
    pub callback: CoreCallback,
887
    pub refany: RefAny,
888
}
889

            
890
impl_option!(
891
    CoreCallbackData,
892
    OptionCoreCallbackData,
893
    copy = false,
894
    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
895
);
896

            
897
impl_vec!(CoreCallbackData, CoreCallbackDataVec, CoreCallbackDataVecDestructor, CoreCallbackDataVecDestructorType, CoreCallbackDataVecSlice, OptionCoreCallbackData);
898
impl_vec_clone!(
899
    CoreCallbackData,
900
    CoreCallbackDataVec,
901
    CoreCallbackDataVecDestructor
902
);
903
impl_vec_mut!(CoreCallbackData, CoreCallbackDataVec);
904
impl_vec_debug!(CoreCallbackData, CoreCallbackDataVec);
905
impl_vec_partialord!(CoreCallbackData, CoreCallbackDataVec);
906
impl_vec_ord!(CoreCallbackData, CoreCallbackDataVec);
907
impl_vec_partialeq!(CoreCallbackData, CoreCallbackDataVec);
908
impl_vec_eq!(CoreCallbackData, CoreCallbackDataVec);
909
impl_vec_hash!(CoreCallbackData, CoreCallbackDataVec);
910

            
911
impl 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
929
pub 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)]
934
pub 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)
943
impl 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)]
955
pub struct CoreImageCallback {
956
    pub refany: RefAny,
957
    pub callback: CoreRenderImageCallback,
958
}
959

            
960
impl_option!(
961
    CoreImageCallback,
962
    OptionCoreImageCallback,
963
    copy = false,
964
    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
965
);