azul_core/
callbacks.rs

1use std::{
2    fmt,
3    sync::atomic::{AtomicUsize, Ordering},
4    collections::BTreeMap,
5    rc::Rc,
6    any::Any,
7    hash::Hash,
8    cell::{Ref as StdRef, RefMut as StdRefMut, RefCell},
9    ffi::c_void,
10};
11use azul_css::{LayoutPoint, LayoutRect, LayoutSize, CssPath};
12#[cfg(feature = "css_parser")]
13use azul_css_parser::CssPathParseError;
14use crate::{
15    FastHashMap,
16    app_resources::{AppResources, IdNamespace, Words, WordPositions, ScaledWords, LayoutedGlyphs},
17    dom::{Dom, DomPtr, DomId, TagId, NodeType, NodeData},
18    display_list::CachedDisplayList,
19    ui_state::UiState,
20    ui_description::UiDescription,
21    ui_solver::{PositionedRectangle, LayoutedRectangle, ScrolledNodes, LayoutResult},
22    id_tree::{NodeId, Node, NodeHierarchy},
23    window::{
24        WindowSize, WindowState, FullWindowState, CallbacksOfHitTest,
25        KeyboardState, MouseState, LogicalSize, PhysicalSize,
26        UpdateFocusWarning, CallCallbacksResult, ScrollStates,
27    },
28    task::{Timer, TerminateTimer, Task, TimerId},
29};
30
31#[cfg(feature = "opengl")]
32use crate::gl::Texture;
33#[cfg(feature = "opengl")]
34use gleam::gl::Gl;
35
36/// A callback function has to return if the screen should be updated after the
37/// function has run.
38///
39/// NOTE: This is currently a typedef for `Option<()>`, so that you can use
40/// the `?` operator in callbacks (to simply not redraw if there is an error).
41/// This was an enum previously, but since Rust doesn't have a "custom try" operator,
42/// this led to a lot of usability problems. In the future, this might change back
43/// to an enum therefore the constants "Redraw" and "DontRedraw" are not capitalized,
44/// to minimize breakage.
45pub type UpdateScreen = Option<()>;
46/// After the callback is called, the screen needs to redraw
47/// (layout() function being called again).
48#[allow(non_upper_case_globals)]
49pub const Redraw: Option<()> = Some(());
50/// The screen does not need to redraw after the callback has been called.
51#[allow(non_upper_case_globals)]
52pub const DontRedraw: Option<()> = None;
53
54/// # The two-way binding system
55///
56/// A fundamental problem in UI development is where and how to store
57/// states of widgets, without impacting reusability, extensability or
58/// performance. Azul solves this problem using a type-erased
59/// `Rc<RefCell<Box<Any>>>` type (`RefAny`), whic can be up and downcasted to
60/// a `Rc<RefCell<Box<T>>>` type (`Ref<T>`). `Ref` and `RefAny` exist mostly to
61/// reduce typing and to prevent multiple mutable access to the inner
62/// `RefCell` at compile time. Azul stores all `RefAny`s inside the `Dom` tree
63/// and does NOT clone or mutate them at all. Only user-defined callbacks
64/// or the default callbacks have access to the `RefAny` data.
65///
66/// # Overriding the default behaviour of widgets
67///
68/// While Rust does not support inheritance with language constructs such
69/// as `@override` (Java) or the `override` keyword in C#, emulating structs
70/// that can change their behaviour at runtime is quite easy. Imagine a
71/// struct in which all methods are stored as public function pointers
72/// inside the struct itself:
73///
74/// ```rust
75/// // The struct has all methods as function pointers,
76/// // so that they can be "overridden" and exchanged with other
77/// // implementations if necessary
78/// struct A {
79///     pub function_a: fn(&A, i32) -> i32,
80///     pub function_b: fn(&A) -> &'static str,
81/// }
82///
83/// impl A {
84///     pub fn default_impl_a(&self, num: i32) -> i32 { num + num }
85///     pub fn default_impl_b(&self) -> &'static str { "default b method!" }
86///
87///     // Don't call default_impl_a() directly, just the function pointer
88///     pub fn do_a(&self, num: i32) -> i32 { (self.function_a)(self, num) }
89///     pub fn do_b(&self) -> &'static str { (self.function_b)(self) }
90/// }
91///
92/// // Here we provide the default ("base class") implementation
93/// impl Default for A {
94///     fn default() -> A {
95///         A {
96///             function_a: A::default_impl_a,
97///             function_b: A::default_impl_b,
98///         }
99///     }
100/// }
101///
102/// // Alternative function that will override the original method
103/// fn override_a(_: &A, num: i32) -> i32 { num * num }
104///
105/// fn main() {
106///     let mut a = A::default();
107///     println!("{}", a.do_a(5)); // prints "10" (5 + 5)
108///     println!("{}", a.do_b());  // prints "default b method"
109///
110///     a.function_a = override_a; // Here we override the behaviour
111///     println!("{}", a.do_a(5)); // prints "25" (5 * 5)
112///     println!("{}", a.do_b());  // still prints "default b method", since method isn't overridden
113/// }
114/// ```
115///
116/// Applied to widgets, the "A" class (a placeholder for a "Button", "Table" or other widget)
117/// can look something like this:
118///
119/// ```rust,no_run
120/// fn layout(&self, _: &LayoutInfo) -> Dom {
121///     Spreadsheet::new()
122///         .override_oncellchange(my_func_1)
123///         .override_onworkspacechange(my_func_2)
124///         .override_oncellselect(my_func_3)
125///     .dom()
126/// }
127/// ```
128///
129/// The spreadsheet has some "default" event handlers, which can be exchanged for custom
130/// implementations via an open API. The benefit is that functions can be mixed and matched,
131/// and widgets can be composed of sub-widgets as well as be re-used. Another benefit is that
132/// now the widget can react to "custom" events such as "oncellchange" or "oncellselect",
133/// without Azul knowing that such events even exist. The downside is that this coding style
134/// requires more work on behalf of the widget designer (but not the user).
135#[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
136pub struct Ref<T: 'static>(Rc<RefCell<T>>);
137
138impl<T: 'static> Clone for Ref<T> {
139    fn clone(&self) -> Self {
140        Ref(self.0.clone())
141    }
142}
143
144impl<T: 'static + Hash> Hash for Ref<T> {
145    fn hash<H>(&self, state: &mut H) where H: ::std::hash::Hasher {
146        let self_ptr = Rc::into_raw(self.0.clone()) as *const c_void as usize;
147        state.write_usize(self_ptr);
148        self.0.borrow().hash(state)
149    }
150}
151
152impl<T: 'static> Ref<T> {
153
154    pub fn new(data: T) -> Self {
155        Ref(Rc::new(RefCell::new(data)))
156    }
157
158    pub fn borrow(&self) -> StdRef<T> {
159        self.0.borrow()
160    }
161
162    pub fn borrow_mut(&mut self) -> StdRefMut<T> {
163        self.0.borrow_mut()
164    }
165
166    pub fn get_type_name(&self) -> &'static str {
167        use std::any;
168        any::type_name::<T>()
169    }
170
171    pub fn upcast(self) -> RefAny {
172        use std::any;
173        RefAny {
174            ptr: self.0 as Rc<dyn Any>,
175            type_name: any::type_name::<T>(),
176        }
177    }
178}
179
180impl<T: 'static> From<Ref<T>> for RefAny {
181    fn from(r: Ref<T>) -> Self {
182        r.upcast()
183    }
184}
185
186#[derive(Debug)]
187pub struct RefAny {
188    ptr: Rc<dyn Any>,
189    type_name: &'static str,
190}
191
192impl Clone for RefAny {
193    fn clone(&self) -> Self {
194        RefAny {
195            ptr: self.ptr.clone(),
196            type_name: self.type_name,
197        }
198    }
199}
200
201/// Pointer to rust-allocated `Box<RefAny>` struct
202#[no_mangle] #[repr(C)] pub struct RefAnyPtr { pub ptr: *mut c_void }
203
204impl ::std::hash::Hash for RefAny {
205    fn hash<H>(&self, state: &mut H) where H: ::std::hash::Hasher {
206        let self_ptr = Rc::into_raw(self.ptr.clone()) as *const c_void as usize;
207        state.write_usize(self_ptr);
208    }
209}
210
211impl PartialEq for RefAny {
212    fn eq(&self, rhs: &Self) -> bool {
213        Rc::ptr_eq(&self.ptr, &rhs.ptr)
214    }
215}
216
217impl PartialOrd for RefAny {
218    fn partial_cmp(&self, rhs: &Self) -> Option<::std::cmp::Ordering> {
219        Some(self.cmp(rhs))
220    }
221}
222
223impl Ord for RefAny {
224    fn cmp(&self, rhs: &Self) -> ::std::cmp::Ordering {
225        let self_ptr = Rc::into_raw(self.ptr.clone()) as *const c_void as usize;
226        let rhs_ptr = Rc::into_raw(rhs.ptr.clone()) as *const c_void as usize;
227        self_ptr.cmp(&rhs_ptr)
228    }
229}
230
231impl Eq for RefAny { }
232
233impl RefAny {
234
235    /// Casts the type-erased pointer back to a `RefCell<T>`
236    pub fn downcast<T: 'static>(&self) -> Option<&RefCell<T>> {
237        self.ptr.downcast_ref::<RefCell<T>>()
238    }
239
240    /// Returns the compiler-generated string of the type (`std::any::type_name`).
241    /// Very useful for debugging
242    pub fn get_type_name(&self) -> &'static str {
243        self.type_name
244    }
245}
246
247/// This type carries no valuable semantics for WR. However, it reflects the fact that
248/// clients (Servo) may generate pipelines by different semi-independent sources.
249/// These pipelines still belong to the same `IdNamespace` and the same `DocumentId`.
250/// Having this extra Id field enables them to generate `PipelineId` without collision.
251pub type PipelineSourceId = u32;
252
253/// Callback function pointer (has to be a function pointer in
254/// order to be compatible with C APIs later on).
255///
256/// NOTE: The original callback was `fn(&self, LayoutInfo) -> Dom`
257/// which then evolved to `fn(&RefAny, LayoutInfo) -> Dom`.
258/// The indirection is necessary because of the memory management
259/// around the C API
260///
261/// See azul-core/ui_state.rs:298 for how the memory is managed
262/// across the callback boundary.
263pub type LayoutCallback = fn(RefAnyPtr, LayoutInfoPtr) -> DomPtr;
264
265/// Information about a scroll frame, given to the user by the framework
266#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
267pub struct ScrollPosition {
268    /// How big is the scroll rect (i.e. the union of all children)?
269    pub scroll_frame_rect: LayoutRect,
270    /// How big is the parent container (so that things like "scroll to left edge" can be implemented)?
271    pub parent_rect: LayoutedRectangle,
272    /// Where (measured from the top left corner) is the frame currently scrolled to?
273    pub scroll_location: LayoutPoint,
274}
275
276#[derive(Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
277pub struct DocumentId {
278    pub namespace_id: IdNamespace,
279    pub id: u32
280}
281
282impl ::std::fmt::Display for DocumentId {
283    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
284        write!(f, "DocumentId {{ ns: {}, id: {} }}", self.namespace_id, self.id)
285    }
286}
287
288impl ::std::fmt::Debug for DocumentId {
289    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
290        write!(f, "{}", self)
291    }
292}
293
294
295#[derive(Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
296pub struct PipelineId(pub PipelineSourceId, pub u32);
297
298impl ::std::fmt::Display for PipelineId {
299    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
300        write!(f, "PipelineId({}, {})", self.0, self.1)
301    }
302}
303
304impl ::std::fmt::Debug for PipelineId {
305    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
306        write!(f, "{}", self)
307    }
308}
309
310static LAST_PIPELINE_ID: AtomicUsize = AtomicUsize::new(0);
311
312impl PipelineId {
313    pub const DUMMY: PipelineId = PipelineId(0, 0);
314
315    pub fn new() -> Self {
316        PipelineId(LAST_PIPELINE_ID.fetch_add(1, Ordering::SeqCst) as u32, 0)
317    }
318}
319
320#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
321pub struct HitTestItem {
322    /// The pipeline that the display item that was hit belongs to.
323    pub pipeline: PipelineId,
324    /// The tag of the hit display item.
325    pub tag: TagId,
326    /// The hit point in the coordinate space of the "viewport" of the display item.
327    /// The viewport is the scroll node formed by the root reference frame of the display item's pipeline.
328    pub point_in_viewport: LayoutPoint,
329    /// The coordinates of the original hit test point relative to the origin of this item.
330    /// This is useful for calculating things like text offsets in the client.
331    pub point_relative_to_item: LayoutPoint,
332}
333
334/// Implements `Display, Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Hash`
335/// for a Callback with a `.0` field:
336///
337/// ```
338/// struct MyCallback(fn (&T));
339///
340/// // impl Display, Debug, etc. for MyCallback
341/// impl_callback!(MyCallback);
342/// ```
343///
344/// This is necessary to work around for https://github.com/rust-lang/rust/issues/54508
345#[macro_export]
346macro_rules! impl_callback {($callback_value:ident) => (
347
348    impl ::std::fmt::Display for $callback_value {
349        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
350            write!(f, "{:?}", self)
351        }
352    }
353
354    impl ::std::fmt::Debug for $callback_value {
355        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
356            let callback = stringify!($callback_value);
357            write!(f, "{} @ 0x{:x}", callback, self.0 as usize)
358        }
359    }
360
361    impl Clone for $callback_value {
362        fn clone(&self) -> Self {
363            $callback_value(self.0.clone())
364        }
365    }
366
367    impl ::std::hash::Hash for $callback_value {
368        fn hash<H>(&self, state: &mut H) where H: ::std::hash::Hasher {
369            state.write_usize(self.0 as usize);
370        }
371    }
372
373    impl PartialEq for $callback_value {
374        fn eq(&self, rhs: &Self) -> bool {
375            self.0 as usize == rhs.0 as usize
376        }
377    }
378
379    impl PartialOrd for $callback_value {
380        fn partial_cmp(&self, other: &Self) -> Option<::std::cmp::Ordering> {
381            Some((self.0 as usize).cmp(&(other.0 as usize)))
382        }
383    }
384
385    impl Ord for $callback_value {
386        fn cmp(&self, other: &Self) -> ::std::cmp::Ordering {
387            (self.0 as usize).cmp(&(other.0 as usize))
388        }
389    }
390
391    impl Eq for $callback_value { }
392
393    impl Copy for $callback_value { }
394)}
395
396macro_rules! impl_get_gl_context {() => {
397    /// Returns a reference-counted pointer to the OpenGL context
398    #[cfg(feature = "opengl")]
399    pub fn get_gl_context(&self) -> Rc<dyn Gl> {
400        self.gl_context.clone()
401    }
402};}
403
404// -- normal callback
405
406/// Stores a function pointer that is executed when the given UI element is hit
407///
408/// Must return an `UpdateScreen` that denotes if the screen should be redrawn.
409/// The style is not affected by this, so if you make changes to the window's style
410/// inside the function, the screen will not be automatically redrawn, unless you return
411/// an `UpdateScreen::Redraw` from the function
412pub struct Callback(pub CallbackType);
413impl_callback!(Callback);
414
415/// Information about the callback that is passed to the callback whenever a callback is invoked
416pub struct CallbackInfo<'a> {
417    /// Your data (the global struct which all callbacks will have access to)
418    pub state: &'a RefAny,
419    /// State of the current window that the callback was called on (read only!)
420    pub current_window_state: &'a FullWindowState,
421    /// User-modifiable state of the window that the callback was called on
422    pub modifiable_window_state: &'a mut WindowState,
423    /// Currently active, layouted rectangles
424    pub layout_result: &'a BTreeMap<DomId, LayoutResult>,
425    /// Nodes that overflow their parents and are able to scroll
426    pub scrolled_nodes: &'a BTreeMap<DomId, ScrolledNodes>,
427    /// Current display list active in this window (useful for debugging)
428    pub cached_display_list: &'a CachedDisplayList,
429    /// An Rc to the OpenGL context, in order to be able to render to OpenGL textures
430    #[cfg(feature = "opengl")]
431    pub gl_context: Rc<dyn Gl>,
432    /// See [`AppState.resources`](./struct.AppState.html#structfield.resources)
433    pub resources : &'a mut AppResources,
434    /// Currently running timers (polling functions, run on the main thread)
435    pub timers: &'a mut FastHashMap<TimerId, Timer>,
436    /// Currently running tasks (asynchronous functions running each on a different thread)
437    pub tasks: &'a mut Vec<Task>,
438    /// UiState containing the necessary data for testing what
439    pub ui_state: &'a BTreeMap<DomId, UiState>,
440    /// Sets whether the event should be propagated to the parent hit node or not
441    pub stop_propagation: &'a mut bool,
442    /// The callback can change the focus_target - note that the focus_target is set before the
443    /// next frames' layout() function is invoked, but the current frames callbacks are not affected.
444    pub focus_target: &'a mut Option<FocusTarget>,
445    /// Immutable (!) reference to where the nodes are currently scrolled (current position)
446    pub current_scroll_states: &'a BTreeMap<DomId, BTreeMap<NodeId, ScrollPosition>>,
447    /// Mutable map where a user can set where he wants the nodes to be scrolled to (for the next frame)
448    pub nodes_scrolled_in_callback: &'a mut BTreeMap<DomId, BTreeMap<NodeId, LayoutPoint>>,
449    /// The ID of the DOM + the node that was hit. You can use this to query
450    /// information about the node, but please don't hard-code any if / else
451    /// statements based on the `NodeId`
452    pub hit_dom_node: (DomId, NodeId),
453    /// The (x, y) position of the mouse cursor, **relative to top left of the element that was hit**.
454    pub cursor_relative_to_item: Option<(f32, f32)>,
455    /// The (x, y) position of the mouse cursor, **relative to top left of the window**.
456    pub cursor_in_viewport: Option<(f32, f32)>,
457}
458pub type CallbackReturn = UpdateScreen;
459pub type CallbackType = fn(CallbackInfo) -> CallbackReturn;
460
461impl<'a> fmt::Debug for CallbackInfo<'a> {
462    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
463        write!(f, "CallbackInfo {{
464            data: {{ .. }}, \
465            current_window_state: {:?}, \
466            modifiable_window_state: {:?}, \
467            layout_result: {:?}, \
468            scrolled_nodes: {:?}, \
469            cached_display_list: {:?}, \
470            gl_context: {{ .. }}, \
471            resources: {{ .. }}, \
472            timers: {{ .. }}, \
473            tasks: {{ .. }}, \
474            ui_state: {:?}, \
475            focus_target: {:?}, \
476            current_scroll_states: {:?}, \
477            nodes_scrolled_in_callback: {:?}, \
478            hit_dom_node: {:?}, \
479            cursor_relative_to_item: {:?}, \
480            cursor_in_viewport: {:?}, \
481        }}",
482            self.current_window_state,
483            self.modifiable_window_state,
484            self.layout_result,
485            self.scrolled_nodes,
486            self.cached_display_list,
487            self.ui_state,
488            self.focus_target,
489            self.current_scroll_states,
490            self.nodes_scrolled_in_callback,
491            self.hit_dom_node,
492            self.cursor_relative_to_item,
493            self.cursor_in_viewport,
494        )
495    }
496}
497
498impl<'a> CallbackInfo<'a> {
499    /// Sets whether the event should be propagated to the parent hit node or not
500    ///
501    /// Similar to `e.stopPropagation()` in JavaScript
502    pub fn stop_propagation(&mut self) {
503        *self.stop_propagation = true;
504    }
505}
506
507// -- opengl callback
508
509/// Callbacks that returns a rendered OpenGL texture
510#[cfg(feature = "opengl")]
511pub struct GlCallback(pub GlCallbackType);
512#[cfg(feature = "opengl")]
513impl_callback!(GlCallback);
514
515pub struct GlCallbackInfo<'a> {
516    pub state: &'a RefAny,
517    pub layout_info: LayoutInfo<'a>,
518    pub bounds: HidpiAdjustedBounds,
519}
520
521#[cfg(feature = "opengl")]
522pub type GlCallbackReturn = Option<Texture>;
523#[cfg(feature = "opengl")]
524pub type GlCallbackType = fn(GlCallbackInfo) -> GlCallbackReturn;
525
526// -- iframe callback
527
528/// Callback that, given a rectangle area on the screen, returns the DOM
529/// appropriate for that bounds (useful for infinite lists)
530pub struct IFrameCallback(pub IFrameCallbackType);
531impl_callback!(IFrameCallback);
532
533pub struct IFrameCallbackInfo<'a> {
534    pub state: &'a RefAny,
535    pub layout_info: LayoutInfo<'a>,
536    pub bounds: HidpiAdjustedBounds,
537}
538
539pub type IFrameCallbackReturn = Option<Dom>; // todo: return virtual scrolling frames!
540pub type IFrameCallbackType = fn(IFrameCallbackInfo) -> IFrameCallbackReturn;
541
542// -- timer callback
543
544/// Callback that can runs on every frame on the main thread - can modify the app data model
545pub struct TimerCallback(pub TimerCallbackType);
546impl_callback!(TimerCallback);
547pub struct TimerCallbackInfo<'a> {
548    pub state: &'a mut RefAny,
549    pub app_resources: &'a mut AppResources,
550}
551pub type TimerCallbackReturn = (UpdateScreen, TerminateTimer);
552pub type TimerCallbackType = fn(TimerCallbackInfo) -> TimerCallbackReturn;
553
554/// Pointer to rust-allocated `Box<LayoutInfo<'a>>` struct
555#[no_mangle] #[repr(C)] pub struct LayoutInfoPtr { pub ptr: *mut c_void }
556
557/// Gives the `layout()` function access to the `AppResources` and the `Window`
558/// (for querying images and fonts, as well as width / height)
559pub struct LayoutInfo<'a> {
560    /// Window size (so that apps can return a different UI depending on
561    /// the window size - mobile / desktop view). Should be later removed
562    /// in favor of "resize" handlers and @media queries.
563    pub window_size: &'a WindowSize,
564    /// Optimization for resizing: If a DOM has no Iframes and the window size
565    /// does not change the state of the UI, then resizing the window will not
566    /// result in calls to the .layout() function (since the resulting UI would
567    /// stay the same).
568    ///
569    /// Stores "stops" in logical pixels where the UI needs to be re-generated
570    /// should the width of the window change.
571    pub window_size_width_stops: &'a mut Vec<f32>,
572    /// Same as `window_size_width_stops` but for the height of the window.
573    pub window_size_height_stops: &'a mut Vec<f32>,
574    /// An Rc to the original OpenGL context - this is only so that
575    /// the user can create textures and other OpenGL content in the window
576    #[cfg(feature = "opengl")]
577    pub gl_context: Rc<dyn Gl>,
578    /// Allows the layout() function to reference app resources such as FontIDs or ImageIDs
579    pub resources: &'a AppResources,
580}
581
582impl<'a> LayoutInfo<'a> {
583    impl_get_gl_context!();
584}
585
586impl<'a> LayoutInfo<'a> {
587
588    /// Returns whether the window width is larger than `width`,
589    /// but sets an internal "dirty" flag - so that the UI is re-generated when
590    /// the window is resized above or below `width`.
591    ///
592    /// For example:
593    ///
594    /// ```rust,no_run,ignore
595    /// fn layout(info: LayoutInfo<T>) -> Dom {
596    ///     if info.window_width_larger_than(720.0) {
597    ///         render_desktop_ui()
598    ///     } else {
599    ///         render_mobile_ui()
600    ///     }
601    /// }
602    /// ```
603    ///
604    /// Here, the UI is dependent on the width of the window, so if the window
605    /// resizes above or below 720px, the `layout()` function needs to be called again.
606    /// Internally Azul stores the `720.0` and only calls the `.layout()` function
607    /// again if the window resizes above or below the value.
608    ///
609    /// NOTE: This should be later depreceated into `On::Resize` handlers and
610    /// `@media` queries.
611    pub fn window_width_larger_than(&mut self, width: f32) -> bool {
612        self.window_size_width_stops.push(width);
613        self.window_size.get_logical_size().width > width
614    }
615
616    pub fn window_width_smaller_than(&mut self, width: f32) -> bool {
617        self.window_size_width_stops.push(width);
618        self.window_size.get_logical_size().width < width
619    }
620
621    pub fn window_height_larger_than(&mut self, height: f32) -> bool {
622        self.window_size_height_stops.push(height);
623        self.window_size.get_logical_size().height > height
624    }
625
626    pub fn window_height_smaller_than(&mut self, height: f32) -> bool {
627        self.window_size_height_stops.push(height);
628        self.window_size.get_logical_size().height < height
629    }
630}
631
632/// Information about the bounds of a laid-out div rectangle.
633///
634/// Necessary when invoking `IFrameCallbacks` and `GlCallbacks`, so
635/// that they can change what their content is based on their size.
636#[derive(Debug, Copy, Clone)]
637pub struct HidpiAdjustedBounds {
638    pub logical_size: LogicalSize,
639    pub hidpi_factor: f32,
640}
641
642impl HidpiAdjustedBounds {
643
644    #[inline(always)]
645    pub fn from_bounds(bounds: LayoutSize, hidpi_factor: f32) -> Self {
646        let logical_size = LogicalSize::new(bounds.width, bounds.height);
647        Self {
648            logical_size,
649            hidpi_factor,
650        }
651    }
652
653    pub fn get_physical_size(&self) -> PhysicalSize<u32> {
654        self.get_logical_size().to_physical(self.hidpi_factor)
655    }
656
657    pub fn get_logical_size(&self) -> LogicalSize {
658        // NOTE: hidpi factor, not winit_hidpi_factor!
659        LogicalSize::new(
660            self.logical_size.width * self.hidpi_factor,
661            self.logical_size.height * self.hidpi_factor
662        )
663    }
664
665    pub fn get_hidpi_factor(&self) -> f32 {
666        self.hidpi_factor
667    }
668}
669
670/// Defines the focus_targeted node ID for the next frame
671#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
672pub enum FocusTarget {
673    Id((DomId, NodeId)),
674    Path((DomId, CssPath)),
675    NoFocus,
676}
677
678impl FocusTarget {
679    pub fn resolve(
680        &self,
681        ui_descriptions: &BTreeMap<DomId, UiDescription>,
682        ui_states: &BTreeMap<DomId, UiState>,
683    ) -> Result<Option<(DomId, NodeId)>, UpdateFocusWarning> {
684
685        use crate::callbacks::FocusTarget::*;
686        use crate::style::matches_html_element;
687
688        match self {
689            Id((dom_id, node_id)) => {
690                let ui_state = ui_states.get(&dom_id).ok_or(UpdateFocusWarning::FocusInvalidDomId(dom_id.clone()))?;
691                let _ = ui_state.dom.arena.node_data.get(*node_id).ok_or(UpdateFocusWarning::FocusInvalidNodeId(*node_id))?;
692                Ok(Some((dom_id.clone(), *node_id)))
693            },
694            NoFocus => Ok(None),
695            Path((dom_id, css_path)) => {
696                let ui_state = ui_states.get(&dom_id).ok_or(UpdateFocusWarning::FocusInvalidDomId(dom_id.clone()))?;
697                let ui_description = ui_descriptions.get(&dom_id).ok_or(UpdateFocusWarning::FocusInvalidDomId(dom_id.clone()))?;
698                let html_node_tree = &ui_description.html_tree;
699                let node_hierarchy = &ui_state.dom.arena.node_hierarchy;
700                let node_data = &ui_state.dom.arena.node_data;
701                let resolved_node_id = html_node_tree
702                    .linear_iter()
703                    .find(|node_id| matches_html_element(css_path, *node_id, &node_hierarchy, &node_data, &html_node_tree))
704                    .ok_or(UpdateFocusWarning::CouldNotFindFocusNode(css_path.clone()))?;
705                Ok(Some((dom_id.clone(), resolved_node_id)))
706            },
707        }
708    }
709}
710
711impl<'a> CallbackInfo<'a> {
712    impl_callback_info_api!();
713    impl_task_api!();
714    impl_get_gl_context!();
715}
716
717/// Iterator that, starting from a certain starting point, returns the
718/// parent node until it gets to the root node.
719pub struct ParentNodesIterator<'a> {
720    ui_state: &'a BTreeMap<DomId, UiState>,
721    current_item: (DomId, NodeId),
722}
723
724impl<'a> ParentNodesIterator<'a> {
725
726    /// Returns what node ID the iterator is currently processing
727    pub fn current_node(&self) -> (DomId, NodeId) {
728        self.current_item.clone()
729    }
730
731    /// Returns the offset into the parent of the current node or None if the item has no parent
732    pub fn current_index_in_parent(&self) -> Option<usize> {
733        let node_layout = &self.ui_state[&self.current_item.0].dom.arena.node_hierarchy;
734        if node_layout[self.current_item.1].parent.is_some() {
735            Some(node_layout.get_index_in_parent(self.current_item.1))
736        } else {
737            None
738        }
739    }
740}
741
742impl<'a> Iterator for ParentNodesIterator<'a> {
743    type Item = (DomId, NodeId);
744
745    /// For each item in the current item path, returns the index of the item in the parent
746    fn next(&mut self) -> Option<(DomId, NodeId)> {
747        let parent_node_id = self.ui_state[&self.current_item.0].dom.arena.node_hierarchy[self.current_item.1].parent?;
748        self.current_item.1 = parent_node_id;
749        Some((self.current_item.0.clone(), parent_node_id))
750    }
751}
752
753/// The actual function that calls the callback in their proper hierarchy and order
754#[cfg(feature = "opengl")]
755pub fn call_callbacks(
756    callbacks_filter_list: &BTreeMap<DomId, CallbacksOfHitTest>,
757    ui_state_map: &BTreeMap<DomId, UiState>,
758    ui_description_map: &BTreeMap<DomId, UiDescription>,
759    timers: &mut FastHashMap<TimerId, Timer>,
760    tasks: &mut Vec<Task>,
761    scroll_states: &BTreeMap<DomId, BTreeMap<NodeId, ScrollPosition>>,
762    modifiable_scroll_states: &mut ScrollStates,
763    full_window_state: &mut FullWindowState,
764    layout_result: &BTreeMap<DomId, LayoutResult>,
765    scrolled_nodes: &BTreeMap<DomId, ScrolledNodes>,
766    cached_display_list: &CachedDisplayList,
767    gl_context: Rc<dyn Gl>,
768    resources: &mut AppResources,
769) -> CallCallbacksResult {
770
771    let mut ret = CallCallbacksResult {
772        needs_restyle_hover_active: callbacks_filter_list.values().any(|v| v.needs_redraw_anyways),
773        needs_relayout_hover_active: callbacks_filter_list.values().any(|v| v.needs_relayout_anyways),
774        needs_restyle_focus_changed: false,
775        should_scroll_render: false,
776        callbacks_update_screen: DontRedraw,
777        modified_window_state: full_window_state.clone().into(),
778    };
779    let mut new_focus_target = None;
780    let mut nodes_scrolled_in_callbacks = BTreeMap::new();
781
782    // Run all callbacks (front to back)
783
784    for (dom_id, callbacks_of_hit_test) in callbacks_filter_list.iter() {
785
786        let ui_state = match ui_state_map.get(dom_id) {
787            Some(s) => s,
788            None => continue,
789        };
790
791        // In order to implement bubbling properly, the events have to be re-sorted a bit
792        // TODO: Put this in the CallbacksOfHitTest construction
793        let mut callbacks_grouped_by_event_type = BTreeMap::new();
794
795        for (node_id, determine_callback_result) in callbacks_of_hit_test.nodes_with_callbacks.iter() {
796            for (event_filter, callback) in determine_callback_result.normal_callbacks.iter() {
797                callbacks_grouped_by_event_type
798                    .entry(event_filter)
799                    .or_insert_with(|| Vec::new())
800                    .push((node_id, callback));
801            }
802        }
803
804        'outer: for (event_filter, callback_nodes) in callbacks_grouped_by_event_type {
805
806            // The (node_id, callback)s are sorted by depth from top to bottom.
807            // If one event wants to prevent bubbling, the entire event is canceled.
808            // It is assumed that there aren't any two nodes that have the same event filter.
809            for (node_id, _) in callback_nodes {
810
811                let mut new_focus = None;
812                let mut stop_propagation = false;
813                let hit_item = &callbacks_of_hit_test.nodes_with_callbacks[&node_id].hit_test_item;
814
815                let callback = ui_state.get_dom().arena.node_data
816                    .get(*node_id)
817                    .map(|nd| nd.get_callbacks())
818                    .and_then(|dc| dc.iter().find_map(|(evt, cb)| if evt == event_filter { Some(cb) } else { None }));
819
820                let (callback, callback_ptr) = match callback {
821                    Some(s) => s,
822                    None => continue,
823                };
824
825                // Invoke callback
826                let callback_return = (callback.0)(CallbackInfo {
827                    state: callback_ptr,
828                    current_window_state: &full_window_state,
829                    modifiable_window_state: &mut ret.modified_window_state,
830                    layout_result,
831                    scrolled_nodes,
832                    cached_display_list,
833                    gl_context: gl_context.clone(),
834                    resources,
835                    timers,
836                    tasks,
837                    ui_state: ui_state_map,
838                    stop_propagation: &mut stop_propagation,
839                    focus_target: &mut new_focus,
840                    current_scroll_states: scroll_states,
841                    nodes_scrolled_in_callback: &mut nodes_scrolled_in_callbacks,
842                    hit_dom_node: (dom_id.clone(), *node_id),
843                    cursor_relative_to_item: hit_item.as_ref().map(|hi| (hi.point_relative_to_item.x, hi.point_relative_to_item.y)),
844                    cursor_in_viewport: hit_item.as_ref().map(|hi| (hi.point_in_viewport.x, hi.point_in_viewport.y)),
845                });
846
847                if callback_return == Redraw {
848                    ret.callbacks_update_screen = Redraw;
849                }
850
851                if let Some(new_focus) = new_focus.clone() {
852                    new_focus_target = Some(new_focus);
853                }
854
855                if stop_propagation {
856                    continue 'outer;
857                }
858            }
859        }
860    }
861
862    // Scroll nodes from programmatic callbacks
863    for (dom_id, callback_scrolled_nodes) in nodes_scrolled_in_callbacks {
864        let scrolled_nodes = match scrolled_nodes.get(&dom_id) {
865            Some(s) => s,
866            None => continue,
867        };
868
869        for (scroll_node_id, scroll_position) in &callback_scrolled_nodes {
870            let overflowing_node = match scrolled_nodes.overflowing_nodes.get(&scroll_node_id) {
871                Some(s) => s,
872                None => continue,
873            };
874
875            modifiable_scroll_states.set_scroll_position(&overflowing_node, *scroll_position);
876            ret.should_scroll_render = true;
877        }
878    }
879
880    let new_focus_node = new_focus_target.and_then(|ft| ft.resolve(&ui_description_map, &ui_state_map).ok()?);
881    let focus_has_not_changed = full_window_state.focused_node == new_focus_node;
882    if !focus_has_not_changed {
883        // TODO: Emit proper On::FocusReceived / On::FocusLost events!
884    }
885
886    // Update the FullWindowState that we got from the frame event (updates window dimensions and DPI)
887    full_window_state.focused_node = new_focus_node;
888
889    ret
890}