azul_core/
window.rs

1#[cfg(not(feature = "std"))]
2use alloc::string::{String, ToString};
3use alloc::{
4    boxed::Box,
5    collections::{btree_map::BTreeMap, btree_set::BTreeSet},
6    vec::Vec,
7};
8use core::{
9    cmp::Ordering,
10    ffi::c_void,
11    hash::{Hash, Hasher},
12    ops,
13    sync::atomic::{AtomicI64, AtomicUsize, Ordering as AtomicOrdering},
14};
15
16use azul_css::{
17    AzString, ColorU, CssPath, CssProperty, FloatValue, LayoutPoint, LayoutRect, LayoutSize,
18    OptionAzString, OptionF32, OptionI32, U8Vec,
19};
20use rust_fontconfig::FcFontCache;
21
22use crate::{
23    app_resources::{
24        DpiScaleFactor, Epoch, GlTextureCache, IdNamespace, ImageCache, ImageMask, ImageRef,
25        RendererResources, ResourceUpdate,
26    },
27    callbacks::{
28        Callback, CallbackType, DocumentId, DomNodeId, HitTestItem, LayoutCallback,
29        LayoutCallbackType, OptionCallback, PipelineId, RefAny, ScrollPosition, Update,
30        UpdateImageType,
31    },
32    display_list::RenderCallbacks,
33    dom::NodeHierarchy,
34    gl::OptionGlContextPtr,
35    id_tree::NodeId,
36    styled_dom::{DomId, NodeHierarchyItemId},
37    task::{ExternalSystemCallbacks, Instant, Thread, ThreadId, Timer, TimerId},
38    ui_solver::{
39        ExternalScrollId, HitTest, LayoutResult, OverflowingScrollNode, QuickResizeResult,
40    },
41    window_state::RelayoutFn,
42    FastBTreeSet, FastHashMap,
43};
44
45pub const DEFAULT_TITLE: &str = "Azul App";
46
47static LAST_WINDOW_ID: AtomicI64 = AtomicI64::new(0);
48
49/// Each default callback is identified by its ID (not by it's function pointer),
50/// since multiple IDs could point to the same function.
51#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
52#[repr(transparent)]
53pub struct WindowId {
54    pub id: i64,
55}
56
57impl WindowId {
58    pub fn new() -> Self {
59        WindowId {
60            id: LAST_WINDOW_ID.fetch_add(1, AtomicOrdering::SeqCst),
61        }
62    }
63}
64
65static LAST_ICON_KEY: AtomicUsize = AtomicUsize::new(0);
66
67/// Key that is used for checking whether a window icon has changed -
68/// this way azul doesn't need to diff the actual bytes, just the icon key.
69/// Use `IconKey::new()` to generate a new, unique key
70#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
71#[repr(C)]
72pub struct IconKey {
73    id: usize,
74}
75
76impl IconKey {
77    pub fn new() -> Self {
78        Self {
79            id: LAST_ICON_KEY.fetch_add(1, AtomicOrdering::SeqCst),
80        }
81    }
82}
83
84#[repr(C)]
85#[derive(PartialEq, Copy, Clone, Debug, PartialOrd, Ord, Eq, Hash)]
86pub struct RendererOptions {
87    pub vsync: Vsync,
88    pub srgb: Srgb,
89    pub hw_accel: HwAcceleration,
90}
91
92impl_option!(
93    RendererOptions,
94    OptionRendererOptions,
95    [PartialEq, Copy, Clone, Debug, PartialOrd, Ord, Eq, Hash]
96);
97
98impl Default for RendererOptions {
99    fn default() -> Self {
100        Self {
101            vsync: Vsync::Enabled,
102            srgb: Srgb::Disabled,
103            hw_accel: HwAcceleration::Enabled,
104        }
105    }
106}
107
108impl RendererOptions {
109    pub const fn new(vsync: Vsync, srgb: Srgb, hw_accel: HwAcceleration) -> Self {
110        Self {
111            vsync,
112            srgb,
113            hw_accel,
114        }
115    }
116}
117
118#[repr(C)]
119#[derive(PartialEq, Copy, Clone, Debug, PartialOrd, Ord, Eq, Hash)]
120pub enum Vsync {
121    Enabled,
122    Disabled,
123    DontCare,
124}
125impl Vsync {
126    pub const fn is_enabled(&self) -> bool {
127        match self {
128            Vsync::Enabled => true,
129            _ => false,
130        }
131    }
132}
133
134#[repr(C)]
135#[derive(PartialEq, Copy, Clone, Debug, PartialOrd, Ord, Eq, Hash)]
136pub enum Srgb {
137    Enabled,
138    Disabled,
139    DontCare,
140}
141impl Srgb {
142    pub const fn is_enabled(&self) -> bool {
143        match self {
144            Srgb::Enabled => true,
145            _ => false,
146        }
147    }
148}
149
150#[repr(C)]
151#[derive(PartialEq, Copy, Clone, Debug, PartialOrd, Ord, Eq, Hash)]
152pub enum HwAcceleration {
153    Enabled,
154    Disabled,
155    DontCare,
156}
157impl HwAcceleration {
158    pub const fn is_enabled(&self) -> bool {
159        match self {
160            HwAcceleration::Enabled => true,
161            _ => false,
162        }
163    }
164}
165
166#[derive(Debug, Copy, Clone, PartialEq, Eq)]
167pub enum ProcessEventResult {
168    DoNothing = 0,
169    ShouldReRenderCurrentWindow = 1,
170    ShouldUpdateDisplayListCurrentWindow = 2,
171    // GPU transforms changed: do another hit-test and recurse
172    // until nothing has changed anymore
173    UpdateHitTesterAndProcessAgain = 3,
174    // Only refresh the display (in case of pure scroll or GPU-only events)
175    ShouldRegenerateDomCurrentWindow = 4,
176    ShouldRegenerateDomAllWindows = 5,
177}
178
179impl ProcessEventResult {
180    pub fn order(&self) -> usize {
181        use self::ProcessEventResult::*;
182        match self {
183            DoNothing => 0,
184            ShouldReRenderCurrentWindow => 1,
185            ShouldUpdateDisplayListCurrentWindow => 2,
186            UpdateHitTesterAndProcessAgain => 3,
187            ShouldRegenerateDomCurrentWindow => 4,
188            ShouldRegenerateDomAllWindows => 5,
189        }
190    }
191}
192
193impl PartialOrd for ProcessEventResult {
194    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
195        self.order().partial_cmp(&other.order())
196    }
197}
198
199impl Ord for ProcessEventResult {
200    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
201        self.order().cmp(&other.order())
202    }
203}
204
205impl ProcessEventResult {
206    pub fn max_self(self, other: Self) -> Self {
207        self.max(other)
208    }
209}
210
211#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
212#[repr(C, u8)]
213pub enum RawWindowHandle {
214    IOS(IOSHandle),
215    MacOS(MacOSHandle),
216    Xlib(XlibHandle),
217    Xcb(XcbHandle),
218    Wayland(WaylandHandle),
219    Windows(WindowsHandle),
220    Web(WebHandle),
221    Android(AndroidHandle),
222    Unsupported,
223}
224
225unsafe impl Send for RawWindowHandle {}
226
227#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
228#[repr(C)]
229pub struct IOSHandle {
230    pub ui_window: *mut c_void,
231    pub ui_view: *mut c_void,
232    pub ui_view_controller: *mut c_void,
233}
234
235#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
236#[repr(C)]
237pub struct MacOSHandle {
238    pub ns_window: *mut c_void,
239    pub ns_view: *mut c_void,
240}
241
242#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
243#[repr(C)]
244pub struct XlibHandle {
245    /// An Xlib Window
246    pub window: u64,
247    pub display: *mut c_void,
248}
249
250#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
251#[repr(C)]
252pub struct XcbHandle {
253    /// An X11 xcb_window_t.
254    pub window: u32,
255    /// A pointer to an X server xcb_connection_t.
256    pub connection: *mut c_void,
257}
258
259#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
260#[repr(C)]
261pub struct WaylandHandle {
262    /// A pointer to a wl_surface
263    pub surface: *mut c_void,
264    /// A pointer to a wl_display.
265    pub display: *mut c_void,
266}
267
268#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
269#[repr(C)]
270pub struct WindowsHandle {
271    /// A Win32 HWND handle.
272    pub hwnd: *mut c_void,
273    /// The HINSTANCE associated with this type's HWND.
274    pub hinstance: *mut c_void,
275}
276
277#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
278#[repr(C)]
279pub struct WebHandle {
280    /// An ID value inserted into the data attributes of the canvas element as 'raw-handle'
281    ///
282    /// When accessing from JS, the attribute will automatically be called rawHandle. Each canvas
283    /// created by the windowing system should be assigned their own unique ID.
284    /// 0 should be reserved for invalid / null IDs.
285    pub id: u32,
286}
287
288#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
289#[repr(C)]
290pub struct AndroidHandle {
291    /// A pointer to an ANativeWindow.
292    pub a_native_window: *mut c_void,
293}
294
295#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
296#[repr(C)]
297pub enum MouseCursorType {
298    Default,
299    Crosshair,
300    Hand,
301    Arrow,
302    Move,
303    Text,
304    Wait,
305    Help,
306    Progress,
307    NotAllowed,
308    ContextMenu,
309    Cell,
310    VerticalText,
311    Alias,
312    Copy,
313    NoDrop,
314    Grab,
315    Grabbing,
316    AllScroll,
317    ZoomIn,
318    ZoomOut,
319    EResize,
320    NResize,
321    NeResize,
322    NwResize,
323    SResize,
324    SeResize,
325    SwResize,
326    WResize,
327    EwResize,
328    NsResize,
329    NeswResize,
330    NwseResize,
331    ColResize,
332    RowResize,
333}
334
335impl Default for MouseCursorType {
336    fn default() -> Self {
337        MouseCursorType::Default
338    }
339}
340
341/// Hardware-dependent keyboard scan code.
342pub type ScanCode = u32;
343
344/// Determines which keys are pressed currently (modifiers, etc.)
345#[derive(Default, Debug, Clone, PartialEq)]
346#[repr(C)]
347pub struct KeyboardState {
348    /// Currently pressed key, already converted to a `char` - (READONLY)
349    pub current_char: OptionChar,
350    /// Same as `current_char`, but .
351    ///
352    /// **DO NOT USE THIS FOR TEXT INPUT, USE `current_char` and `On::TextInput` instead.**
353    /// For example entering `à` will fire a `VirtualKeyCode::Grave`, then `VirtualKeyCode::A`,
354    /// so to correctly combine characters, use the `current_char` field.
355    pub current_virtual_keycode: OptionVirtualKeyCode,
356    /// Currently pressed virtual keycodes (READONLY) - it can happen that more t
357    ///
358    /// This is essentially an "extension" of `current_scancodes` - `current_keys` stores the
359    /// characters, but what if the pressed key is not a character (such as `ArrowRight` or
360    /// `PgUp`)?
361    ///
362    /// Note that this can have an overlap, so pressing "a" on the keyboard will insert
363    /// both a `VirtualKeyCode::A` into `current_virtual_keycodes` and an `"a"` as a char into
364    /// `current_keys`.
365    pub pressed_virtual_keycodes: VirtualKeyCodeVec,
366    /// Same as `current_virtual_keycodes`, but the scancode identifies the physical key pressed,
367    /// independent of the keyboard layout. The scancode does not change if the user adjusts the
368    /// host's keyboard map. Use when the physical location of the key is more important than
369    /// the key's host GUI semantics, such as for movement controls in a first-person game
370    /// (German keyboard: Z key, UK keyboard: Y key, etc.)
371    pub pressed_scancodes: ScanCodeVec,
372}
373
374impl KeyboardState {
375    pub fn shift_down(&self) -> bool {
376        self.is_key_down(VirtualKeyCode::LShift) || self.is_key_down(VirtualKeyCode::RShift)
377    }
378    pub fn ctrl_down(&self) -> bool {
379        self.is_key_down(VirtualKeyCode::LControl) || self.is_key_down(VirtualKeyCode::RControl)
380    }
381    pub fn alt_down(&self) -> bool {
382        self.is_key_down(VirtualKeyCode::LAlt) || self.is_key_down(VirtualKeyCode::RAlt)
383    }
384    pub fn super_down(&self) -> bool {
385        self.is_key_down(VirtualKeyCode::LWin) || self.is_key_down(VirtualKeyCode::RWin)
386    }
387    pub fn is_key_down(&self, key: VirtualKeyCode) -> bool {
388        self.pressed_virtual_keycodes.iter().any(|k| *k == key)
389    }
390}
391
392impl_option!(
393    KeyboardState,
394    OptionKeyboardState,
395    copy = false,
396    [Debug, Clone, PartialEq]
397);
398
399// char is not ABI-stable, use u32 instead
400impl_option!(
401    u32,
402    OptionChar,
403    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
404);
405impl_option!(
406    VirtualKeyCode,
407    OptionVirtualKeyCode,
408    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
409);
410
411impl_vec!(
412    VirtualKeyCode,
413    VirtualKeyCodeVec,
414    VirtualKeyCodeVecDestructor
415);
416impl_vec_debug!(VirtualKeyCode, VirtualKeyCodeVec);
417impl_vec_partialord!(VirtualKeyCode, VirtualKeyCodeVec);
418impl_vec_ord!(VirtualKeyCode, VirtualKeyCodeVec);
419impl_vec_clone!(
420    VirtualKeyCode,
421    VirtualKeyCodeVec,
422    VirtualKeyCodeVecDestructor
423);
424impl_vec_partialeq!(VirtualKeyCode, VirtualKeyCodeVec);
425impl_vec_eq!(VirtualKeyCode, VirtualKeyCodeVec);
426impl_vec_hash!(VirtualKeyCode, VirtualKeyCodeVec);
427
428impl_vec_as_hashmap!(VirtualKeyCode, VirtualKeyCodeVec);
429
430impl_vec!(ScanCode, ScanCodeVec, ScanCodeVecDestructor);
431impl_vec_debug!(ScanCode, ScanCodeVec);
432impl_vec_partialord!(ScanCode, ScanCodeVec);
433impl_vec_ord!(ScanCode, ScanCodeVec);
434impl_vec_clone!(ScanCode, ScanCodeVec, ScanCodeVecDestructor);
435impl_vec_partialeq!(ScanCode, ScanCodeVec);
436impl_vec_eq!(ScanCode, ScanCodeVec);
437impl_vec_hash!(ScanCode, ScanCodeVec);
438
439impl_vec_as_hashmap!(ScanCode, ScanCodeVec);
440
441/// Mouse position, cursor type, user scroll input, etc.
442#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
443#[repr(C)]
444pub struct MouseState {
445    /// Current mouse cursor type, set to `None` if the cursor is hidden. (READWRITE)
446    pub mouse_cursor_type: OptionMouseCursorType,
447    /// Where is the mouse cursor currently? Set to `None` if the window is not focused.
448    /// (READWRITE)
449    pub cursor_position: CursorPosition,
450    /// Is the mouse cursor locked to the current window (important for applications like games)?
451    /// (READWRITE)
452    pub is_cursor_locked: bool,
453    /// Is the left mouse button down? (READONLY)
454    pub left_down: bool,
455    /// Is the right mouse button down? (READONLY)
456    pub right_down: bool,
457    /// Is the middle mouse button down? (READONLY)
458    pub middle_down: bool,
459    /// Scroll amount in pixels in the horizontal direction. Gets reset to 0 after every frame
460    /// (READONLY)
461    pub scroll_x: OptionF32,
462    /// Scroll amount in pixels in the vertical direction. Gets reset to 0 after every frame
463    /// (READONLY)
464    pub scroll_y: OptionF32,
465}
466
467impl MouseState {
468    pub fn matches(&self, context: &ContextMenuMouseButton) -> bool {
469        use self::ContextMenuMouseButton::*;
470        match context {
471            Left => self.left_down,
472            Right => self.right_down,
473            Middle => self.middle_down,
474        }
475    }
476}
477
478impl_option!(
479    MouseState,
480    OptionMouseState,
481    [Debug, Copy, Clone, PartialEq, PartialOrd]
482);
483
484impl_option!(
485    MouseCursorType,
486    OptionMouseCursorType,
487    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
488);
489
490impl Default for MouseState {
491    fn default() -> Self {
492        Self {
493            mouse_cursor_type: Some(MouseCursorType::Default).into(),
494            cursor_position: CursorPosition::default(),
495            is_cursor_locked: false,
496            left_down: false,
497            right_down: false,
498            middle_down: false,
499            scroll_x: None.into(),
500            scroll_y: None.into(),
501        }
502    }
503}
504
505impl MouseState {
506    /// Returns whether any mouse button (left, right or center) is currently held down
507    pub fn mouse_down(&self) -> bool {
508        self.right_down || self.left_down || self.middle_down
509    }
510
511    pub fn get_scroll_x(&self) -> f32 {
512        self.scroll_x.as_option().copied().unwrap_or(0.0)
513    }
514
515    pub fn get_scroll_y(&self) -> f32 {
516        self.scroll_y.as_option().copied().unwrap_or(0.0)
517    }
518
519    pub fn get_scroll(&self) -> (f32, f32) {
520        (self.get_scroll_x(), self.get_scroll_y())
521    }
522
523    pub fn get_scroll_amount(&self) -> Option<(f32, f32)> {
524        const SCROLL_THRESHOLD: f32 = 0.5; // px
525
526        if self.scroll_x.is_none() && self.scroll_y.is_none() {
527            return None;
528        }
529
530        let scroll_x = self.get_scroll_x();
531        let scroll_y = self.get_scroll_y();
532
533        if libm::fabsf(scroll_x) < SCROLL_THRESHOLD && libm::fabsf(scroll_y) < SCROLL_THRESHOLD {
534            return None;
535        }
536
537        Some((scroll_x, scroll_y))
538    }
539
540    /// Function reset the `scroll_x` and `scroll_y` to `None` to clear the scroll amount
541    pub fn reset_scroll_to_zero(&mut self) {
542        self.scroll_x = OptionF32::None;
543        self.scroll_y = OptionF32::None;
544    }
545}
546
547// TODO: returned by process_system_scroll
548#[derive(Debug)]
549pub struct ScrollResult {}
550
551#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
552#[repr(C, u8)]
553pub enum CursorPosition {
554    OutOfWindow(LogicalPosition),
555    Uninitialized,
556    InWindow(LogicalPosition),
557}
558
559impl Default for CursorPosition {
560    fn default() -> CursorPosition {
561        CursorPosition::Uninitialized
562    }
563}
564
565impl CursorPosition {
566    pub fn get_position(&self) -> Option<LogicalPosition> {
567        match self {
568            CursorPosition::InWindow(logical_pos) => Some(*logical_pos),
569            CursorPosition::OutOfWindow(_) | CursorPosition::Uninitialized => None,
570        }
571    }
572
573    pub fn is_inside_window(&self) -> bool {
574        self.get_position().is_some()
575    }
576}
577
578/// Toggles webrender debug flags (will make stuff appear on
579/// the screen that you might not want to - used for debugging purposes)
580#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
581#[repr(C)]
582pub struct DebugState {
583    pub profiler_dbg: bool,
584    pub render_target_dbg: bool,
585    pub texture_cache_dbg: bool,
586    pub gpu_time_queries: bool,
587    pub gpu_sample_queries: bool,
588    pub disable_batching: bool,
589    pub epochs: bool,
590    pub echo_driver_messages: bool,
591    pub show_overdraw: bool,
592    pub gpu_cache_dbg: bool,
593    pub texture_cache_dbg_clear_evicted: bool,
594    pub picture_caching_dbg: bool,
595    pub primitive_dbg: bool,
596    pub zoom_dbg: bool,
597    pub small_screen: bool,
598    pub disable_opaque_pass: bool,
599    pub disable_alpha_pass: bool,
600    pub disable_clip_masks: bool,
601    pub disable_text_prims: bool,
602    pub disable_gradient_prims: bool,
603    pub obscure_images: bool,
604    pub glyph_flashing: bool,
605    pub smart_profiler: bool,
606    pub invalidation_dbg: bool,
607    pub tile_cache_logging_dbg: bool,
608    pub profiler_capture: bool,
609    pub force_picture_invalidation: bool,
610}
611
612#[derive(Debug, Default)]
613pub struct ScrollStates(pub FastHashMap<ExternalScrollId, ScrollState>);
614
615impl ScrollStates {
616    /// Special rendering function that skips building a layout and only does
617    /// hit-testing and rendering - called on pure scroll events, since it's
618    /// significantly less CPU-intensive to just render the last display list instead of
619    /// re-layouting on every single scroll event.
620    #[must_use]
621    pub fn should_scroll_render(
622        &mut self,
623        (scroll_x, scroll_y): &(f32, f32),
624        hit_test: &FullHitTest,
625    ) -> bool {
626        let mut should_scroll_render = false;
627
628        for hit_test in hit_test.hovered_nodes.values() {
629            for scroll_hit_test_item in hit_test.scroll_hit_test_nodes.values() {
630                self.scroll_node(&scroll_hit_test_item.scroll_node, *scroll_x, *scroll_y);
631                should_scroll_render = true;
632                break; // only scroll first node that was hit
633            }
634        }
635
636        should_scroll_render
637    }
638
639    pub fn new() -> ScrollStates {
640        ScrollStates::default()
641    }
642
643    pub fn get_scroll_position(&self, scroll_id: &ExternalScrollId) -> Option<LogicalPosition> {
644        self.0.get(&scroll_id).map(|entry| entry.get())
645    }
646
647    /// Set the scroll amount - does not update the `entry.used_this_frame`,
648    /// since that is only relevant when we are actually querying the renderer.
649    pub fn set_scroll_position(
650        &mut self,
651        node: &OverflowingScrollNode,
652        scroll_position: LogicalPosition,
653    ) {
654        self.0
655            .entry(node.parent_external_scroll_id)
656            .or_insert_with(|| ScrollState::default())
657            .set(scroll_position.x, scroll_position.y, &node.child_rect);
658    }
659
660    /// Updating (add to) the existing scroll amount does not update the `entry.used_this_frame`,
661    /// since that is only relevant when we are actually querying the renderer.
662    pub fn scroll_node(
663        &mut self,
664        node: &OverflowingScrollNode,
665        scroll_by_x: f32,
666        scroll_by_y: f32,
667    ) {
668        self.0
669            .entry(node.parent_external_scroll_id)
670            .or_insert_with(|| ScrollState::default())
671            .add(scroll_by_x, scroll_by_y, &node.child_rect);
672    }
673}
674
675#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
676pub struct ScrollState {
677    /// Amount in pixel that the current node is scrolled
678    pub scroll_position: LogicalPosition,
679}
680
681impl ScrollState {
682    /// Return the current position of the scroll state
683    pub fn get(&self) -> LogicalPosition {
684        self.scroll_position
685    }
686
687    /// Add a scroll X / Y onto the existing scroll state
688    pub fn add(&mut self, x: f32, y: f32, child_rect: &LogicalRect) {
689        self.scroll_position.x = (self.scroll_position.x + x)
690            .max(0.0)
691            .min(child_rect.size.width);
692        self.scroll_position.y = (self.scroll_position.y + y)
693            .max(0.0)
694            .min(child_rect.size.height);
695    }
696
697    /// Set the scroll state to a new position
698    pub fn set(&mut self, x: f32, y: f32, child_rect: &LogicalRect) {
699        self.scroll_position.x = x.max(0.0).min(child_rect.size.width);
700        self.scroll_position.y = y.max(0.0).min(child_rect.size.height);
701    }
702}
703
704impl Default for ScrollState {
705    fn default() -> Self {
706        ScrollState {
707            scroll_position: LogicalPosition::zero(),
708        }
709    }
710}
711
712/// Overwrites all fields of the `FullWindowState` with the fields of the `WindowState`,
713/// but leaves the extra fields such as `.hover_nodes` untouched
714pub fn update_full_window_state(
715    full_window_state: &mut FullWindowState,
716    window_state: &WindowState,
717) {
718    full_window_state.title = window_state.title.clone();
719    full_window_state.size = window_state.size.into();
720    full_window_state.position = window_state.position.into();
721    full_window_state.flags = window_state.flags;
722    full_window_state.debug_state = window_state.debug_state;
723    full_window_state.keyboard_state = window_state.keyboard_state.clone();
724    full_window_state.mouse_state = window_state.mouse_state;
725    full_window_state.ime_position = window_state.ime_position.into();
726    full_window_state.platform_specific_options = window_state.platform_specific_options.clone();
727}
728
729#[derive(Debug)]
730pub struct WindowInternal {
731    /// Currently loaded fonts and images present in this renderer (window)
732    pub renderer_resources: RendererResources,
733    /// Renderer type: Hardware-with-software-fallback, pure software or pure hardware renderer?
734    pub renderer_type: Option<RendererType>,
735    /// Windows state of the window of (current frame - 1): initialized to None on startup
736    pub previous_window_state: Option<FullWindowState>,
737    /// Window state of this current window (current frame): initialized to the state of
738    /// WindowCreateOptions
739    pub current_window_state: FullWindowState,
740    /// A "document" in WebRender usually corresponds to one tab (i.e. in Azuls case, the whole
741    /// window).
742    pub document_id: DocumentId,
743    /// ID namespace under which every font / image for this window is registered
744    pub id_namespace: IdNamespace,
745    /// The "epoch" is a frame counter, to remove outdated images, fonts and OpenGL textures
746    /// when they're not in use anymore.
747    pub epoch: Epoch,
748    /// Currently active, layouted rectangles and styled DOMs
749    pub layout_results: Vec<LayoutResult>,
750    /// Currently GL textures inside the active CachedDisplayList
751    pub gl_texture_cache: GlTextureCache,
752    /// States of scrolling animations, updated every frame
753    pub scroll_states: ScrollStates,
754    /// Timer ID -> Timer + Win32 pointer map (created using SetTimer)
755    pub timers: BTreeMap<TimerId, Timer>,
756    /// List of threads running in the background
757    pub threads: BTreeMap<ThreadId, Thread>,
758}
759
760impl WindowInternal {
761    pub fn get_dpi_scale_factor(&self) -> DpiScaleFactor {
762        DpiScaleFactor {
763            inner: FloatValue::new(self.current_window_state.size.get_hidpi_factor()),
764        }
765    }
766}
767
768#[derive(Debug, Clone, PartialEq)]
769pub struct FullHitTest {
770    pub hovered_nodes: BTreeMap<DomId, HitTest>,
771    pub focused_node: Option<(DomId, NodeId)>,
772}
773
774impl FullHitTest {
775    pub fn empty(focused_node: Option<DomNodeId>) -> Self {
776        Self {
777            hovered_nodes: BTreeMap::new(),
778            focused_node: focused_node.and_then(|f| Some((f.dom, f.node.into_crate_internal()?))),
779        }
780    }
781}
782
783#[derive(Debug, Clone, Default, PartialEq)]
784pub struct CursorTypeHitTest {
785    /// closest-node is used for determining the cursor: property
786    /// The node is guaranteed to have a non-default cursor: property,
787    /// so that the cursor icon can be set accordingly
788    pub cursor_node: Option<(DomId, NodeId)>,
789    /// Mouse cursor type to set (if cursor_node is None, this is set to
790    /// `MouseCursorType::Default`)
791    pub cursor_icon: MouseCursorType,
792}
793
794impl CursorTypeHitTest {
795    pub fn new(hit_test: &FullHitTest, layout_results: &[LayoutResult]) -> Self {
796        use azul_css::StyleCursor;
797
798        let mut cursor_node = None;
799        let mut cursor_icon = MouseCursorType::Default;
800
801        for (dom_id, hit_nodes) in hit_test.hovered_nodes.iter() {
802            for (node_id, _) in hit_nodes.regular_hit_test_nodes.iter() {
803                // if the node has a non-default cursor: property, insert it
804                let styled_dom = &layout_results[dom_id.inner].styled_dom;
805                let node_data_container = styled_dom.node_data.as_container();
806                if let Some(cursor_prop) = styled_dom.get_css_property_cache().get_cursor(
807                    &node_data_container[*node_id],
808                    node_id,
809                    &styled_dom.styled_nodes.as_container()[*node_id].state,
810                ) {
811                    cursor_node = Some((*dom_id, *node_id));
812                    cursor_icon = match cursor_prop.get_property().copied().unwrap_or_default() {
813                        StyleCursor::Alias => MouseCursorType::Alias,
814                        StyleCursor::AllScroll => MouseCursorType::AllScroll,
815                        StyleCursor::Cell => MouseCursorType::Cell,
816                        StyleCursor::ColResize => MouseCursorType::ColResize,
817                        StyleCursor::ContextMenu => MouseCursorType::ContextMenu,
818                        StyleCursor::Copy => MouseCursorType::Copy,
819                        StyleCursor::Crosshair => MouseCursorType::Crosshair,
820                        StyleCursor::Default => MouseCursorType::Default,
821                        StyleCursor::EResize => MouseCursorType::EResize,
822                        StyleCursor::EwResize => MouseCursorType::EwResize,
823                        StyleCursor::Grab => MouseCursorType::Grab,
824                        StyleCursor::Grabbing => MouseCursorType::Grabbing,
825                        StyleCursor::Help => MouseCursorType::Help,
826                        StyleCursor::Move => MouseCursorType::Move,
827                        StyleCursor::NResize => MouseCursorType::NResize,
828                        StyleCursor::NsResize => MouseCursorType::NsResize,
829                        StyleCursor::NeswResize => MouseCursorType::NeswResize,
830                        StyleCursor::NwseResize => MouseCursorType::NwseResize,
831                        StyleCursor::Pointer => MouseCursorType::Hand,
832                        StyleCursor::Progress => MouseCursorType::Progress,
833                        StyleCursor::RowResize => MouseCursorType::RowResize,
834                        StyleCursor::SResize => MouseCursorType::SResize,
835                        StyleCursor::SeResize => MouseCursorType::SeResize,
836                        StyleCursor::Text => MouseCursorType::Text,
837                        StyleCursor::Unset => MouseCursorType::Default,
838                        StyleCursor::VerticalText => MouseCursorType::VerticalText,
839                        StyleCursor::WResize => MouseCursorType::WResize,
840                        StyleCursor::Wait => MouseCursorType::Wait,
841                        StyleCursor::ZoomIn => MouseCursorType::ZoomIn,
842                        StyleCursor::ZoomOut => MouseCursorType::ZoomOut,
843                    }
844                }
845            }
846        }
847
848        Self {
849            cursor_node,
850            cursor_icon,
851        }
852    }
853}
854pub struct WindowInternalInit {
855    pub window_create_options: WindowCreateOptions,
856    pub document_id: DocumentId,
857    pub id_namespace: IdNamespace,
858}
859
860impl WindowInternal {
861    /// Initializes the `WindowInternal` on window creation. Calls the layout() method once to
862    /// initializes the layout
863    #[cfg(feature = "std")]
864    pub fn new<F>(
865        mut init: WindowInternalInit,
866        data: &mut RefAny,
867        image_cache: &ImageCache,
868        gl_context: &OptionGlContextPtr,
869        all_resource_updates: &mut Vec<ResourceUpdate>,
870        callbacks: &RenderCallbacks,
871        fc_cache_real: &mut FcFontCache,
872        relayout_fn: RelayoutFn,
873        hit_test_func: F,
874    ) -> Self
875    where
876        F: Fn(&FullWindowState, &ScrollStates, &[LayoutResult]) -> FullHitTest,
877    {
878        use crate::{
879            callbacks::LayoutCallbackInfo,
880            display_list::SolvedLayout,
881            window_state::{NodesToCheck, StyleAndLayoutChanges},
882        };
883
884        let mut inital_renderer_resources = RendererResources::default();
885
886        let epoch = Epoch::new();
887
888        let styled_dom = {
889            let layout_callback = &mut init.window_create_options.state.layout_callback;
890            let mut layout_info = LayoutCallbackInfo::new(
891                init.window_create_options.state.size,
892                init.window_create_options.state.theme,
893                image_cache,
894                gl_context,
895                &fc_cache_real,
896            );
897
898            match layout_callback {
899                LayoutCallback::Raw(r) => (r.cb)(data, &mut layout_info),
900                LayoutCallback::Marshaled(m) => {
901                    let marshal_data = &mut m.marshal_data;
902                    (m.cb.cb)(marshal_data, data, &mut layout_info)
903                }
904            }
905        };
906
907        let mut current_window_state = FullWindowState::from_window_state(
908            /* window_state: */ &init.window_create_options.state,
909            /* dropped_file: */ None,
910            /* hovered_file: */ None,
911            /* focused_node: */ None,
912            /* last_hit_test: */ FullHitTest::empty(/* current_focus */ None),
913        );
914
915        let SolvedLayout { mut layout_results } = SolvedLayout::new(
916            styled_dom,
917            epoch,
918            &init.document_id,
919            &current_window_state,
920            all_resource_updates,
921            init.id_namespace,
922            image_cache,
923            &fc_cache_real,
924            &callbacks,
925            &mut inital_renderer_resources,
926            DpiScaleFactor {
927                inner: FloatValue::new(init.window_create_options.state.size.get_hidpi_factor()),
928            },
929        );
930
931        let scroll_states = ScrollStates::default();
932
933        // apply the changes for the first frame:
934        // simulate an event as if the cursor has moved over the hovered elements
935        let ht = hit_test_func(&current_window_state, &scroll_states, &layout_results);
936        current_window_state.last_hit_test = ht.clone();
937
938        let nodes_to_check = NodesToCheck::simulated_mouse_move(
939            &ht,
940            None, // focused_node
941            current_window_state.mouse_state.mouse_down(),
942        );
943
944        let _ = StyleAndLayoutChanges::new(
945            &nodes_to_check,
946            &mut layout_results,
947            &image_cache,
948            &mut inital_renderer_resources,
949            current_window_state.size.get_layout_size(),
950            &init.document_id,
951            Some(&BTreeMap::new()),
952            Some(&BTreeMap::new()),
953            &None,
954            relayout_fn,
955        );
956
957        let gl_texture_cache = GlTextureCache::new(
958            &mut layout_results,
959            gl_context,
960            init.id_namespace,
961            &init.document_id,
962            epoch,
963            current_window_state.size.get_hidpi_factor(),
964            image_cache,
965            &fc_cache_real,
966            callbacks,
967            all_resource_updates,
968            &mut inital_renderer_resources,
969        );
970
971        WindowInternal {
972            renderer_resources: inital_renderer_resources,
973            renderer_type: gl_context.as_ref().map(|r| r.renderer_type),
974            id_namespace: init.id_namespace,
975            previous_window_state: None,
976            current_window_state,
977            document_id: init.document_id,
978            epoch, // = 0
979            layout_results,
980            gl_texture_cache,
981            timers: BTreeMap::new(),
982            threads: BTreeMap::new(),
983            scroll_states,
984        }
985    }
986
987    /// Calls the layout function again and updates the self.internal.gl_texture_cache field
988    pub fn regenerate_styled_dom<F>(
989        &mut self,
990        data: &mut RefAny,
991        image_cache: &ImageCache,
992        gl_context: &OptionGlContextPtr,
993        all_resource_updates: &mut Vec<ResourceUpdate>,
994        current_window_dpi: DpiScaleFactor,
995        callbacks: &RenderCallbacks,
996        fc_cache_real: &mut FcFontCache,
997        relayout_fn: RelayoutFn,
998        mut hit_test_func: F,
999    ) where
1000        F: FnMut(&FullWindowState, &ScrollStates, &[LayoutResult]) -> FullHitTest,
1001    {
1002        use crate::{
1003            callbacks::LayoutCallbackInfo,
1004            display_list::SolvedLayout,
1005            gl::gl_textures_remove_epochs_from_pipeline,
1006            styled_dom::DefaultCallbacksCfg,
1007            window_state::{NodesToCheck, StyleAndLayoutChanges},
1008        };
1009
1010        let id_namespace = self.id_namespace;
1011
1012        let mut styled_dom = {
1013            let layout_callback = &mut self.current_window_state.layout_callback;
1014            let mut layout_info = LayoutCallbackInfo::new(
1015                self.current_window_state.size,
1016                self.current_window_state.theme,
1017                image_cache,
1018                gl_context,
1019                &fc_cache_real,
1020            );
1021
1022            match layout_callback {
1023                LayoutCallback::Raw(r) => (r.cb)(data, &mut layout_info),
1024                LayoutCallback::Marshaled(m) => {
1025                    let marshal_data = &mut m.marshal_data;
1026                    (m.cb.cb)(marshal_data, data, &mut layout_info)
1027                }
1028            }
1029        };
1030
1031        styled_dom.insert_default_system_callbacks(DefaultCallbacksCfg {
1032            smooth_scroll: self.current_window_state.flags.smooth_scroll_enabled,
1033            enable_autotab: self.current_window_state.flags.autotab_enabled,
1034        });
1035
1036        let SolvedLayout { mut layout_results } = SolvedLayout::new(
1037            styled_dom,
1038            self.epoch,
1039            &self.document_id,
1040            &self.current_window_state,
1041            all_resource_updates,
1042            id_namespace,
1043            image_cache,
1044            &fc_cache_real,
1045            callbacks,
1046            &mut self.renderer_resources,
1047            current_window_dpi,
1048        );
1049
1050        // apply the changes for the first frame
1051        let ht = hit_test_func(
1052            &self.current_window_state,
1053            &self.scroll_states,
1054            &layout_results,
1055        );
1056        self.current_window_state.last_hit_test = ht.clone();
1057
1058        // hit_test
1059        let nodes_to_check = NodesToCheck::simulated_mouse_move(
1060            &ht,
1061            self.current_window_state.focused_node,
1062            self.current_window_state.mouse_state.mouse_down(),
1063        );
1064
1065        let sl = StyleAndLayoutChanges::new(
1066            &nodes_to_check,
1067            &mut layout_results,
1068            &image_cache,
1069            &mut self.renderer_resources,
1070            self.current_window_state.size.get_layout_size(),
1071            &self.document_id,
1072            Some(&BTreeMap::new()),
1073            Some(&BTreeMap::new()),
1074            &None,
1075            relayout_fn,
1076        );
1077
1078        // inserts the new textures for the next frame
1079        let gl_texture_cache = GlTextureCache::new(
1080            &mut layout_results,
1081            gl_context,
1082            self.id_namespace,
1083            &self.document_id,
1084            self.epoch,
1085            self.current_window_state.size.get_hidpi_factor(),
1086            image_cache,
1087            &fc_cache_real,
1088            callbacks,
1089            all_resource_updates,
1090            &mut self.renderer_resources,
1091        );
1092
1093        // removes the last frames' OpenGL textures
1094        gl_textures_remove_epochs_from_pipeline(&self.document_id, self.epoch);
1095
1096        // Delete unused font and image keys (that were not used in this frame)
1097        self.renderer_resources.do_gc(
1098            all_resource_updates,
1099            image_cache,
1100            &layout_results,
1101            &gl_texture_cache,
1102        );
1103
1104        // Increment epoch here!
1105        self.epoch.increment();
1106        self.layout_results = layout_results;
1107        self.gl_texture_cache = gl_texture_cache;
1108    }
1109
1110    /// Returns a copy of the current scroll states + scroll positions
1111    pub fn get_current_scroll_states(
1112        &self,
1113    ) -> BTreeMap<DomId, BTreeMap<NodeHierarchyItemId, ScrollPosition>> {
1114        self.layout_results
1115            .iter()
1116            .enumerate()
1117            .filter_map(|(dom_id, layout_result)| {
1118                let scroll_positions = layout_result
1119                    .scrollable_nodes
1120                    .overflowing_nodes
1121                    .iter()
1122                    .filter_map(|(node_id, overflowing_node)| {
1123                        let scroll_position = ScrollPosition {
1124                            parent_rect: overflowing_node.parent_rect,
1125                            children_rect: overflowing_node.child_rect,
1126                        };
1127                        Some((*node_id, scroll_position))
1128                    })
1129                    .collect::<BTreeMap<_, _>>();
1130
1131                if scroll_positions.is_empty() {
1132                    None
1133                } else {
1134                    Some((DomId { inner: dom_id }, scroll_positions))
1135                }
1136            })
1137            .collect()
1138    }
1139
1140    /// Returns the overflowing size of the root body node. If WindowCreateOptions.size_to_content
1141    /// is set, the window size should be adjusted to this size before the window is shown.
1142    pub fn get_content_size(&self) -> LogicalSize {
1143        let layout_result = match self.layout_results.get(0) {
1144            Some(s) => s,
1145            None => return LogicalSize::zero(),
1146        };
1147        let root_width =
1148            layout_result.width_calculated_rects.as_ref()[NodeId::ZERO].overflow_width();
1149        let root_height =
1150            layout_result.height_calculated_rects.as_ref()[NodeId::ZERO].overflow_height();
1151        LogicalSize::new(root_width, root_height)
1152    }
1153
1154    /// Does a full re-layout (without calling layout()) again:
1155    /// called in simple resize() scenarios
1156    pub fn do_quick_resize(
1157        &mut self,
1158        image_cache: &ImageCache,
1159        callbacks: &RenderCallbacks,
1160        relayout_fn: RelayoutFn,
1161        fc_cache: &FcFontCache,
1162        gl_context: &OptionGlContextPtr,
1163        window_size: &WindowSize,
1164        window_theme: WindowTheme,
1165    ) -> QuickResizeResult {
1166        LayoutResult::do_quick_resize(
1167            self.id_namespace,
1168            self.document_id,
1169            self.epoch,
1170            DomId::ROOT_ID,
1171            image_cache,
1172            gl_context,
1173            &mut self.layout_results,
1174            &mut self.gl_texture_cache,
1175            &mut self.renderer_resources,
1176            callbacks,
1177            relayout_fn,
1178            fc_cache,
1179            window_size,
1180            window_theme,
1181        )
1182    }
1183
1184    /// Returns whether the size or position of the window changed (if true,
1185    /// the caller needs to update the monitor field), since the window may have
1186    /// moved to a different monitor
1187    pub fn may_have_changed_monitor(&self) -> bool {
1188        let previous = match self.previous_window_state.as_ref() {
1189            None => return true,
1190            Some(s) => s,
1191        };
1192        let current = &self.current_window_state;
1193
1194        previous.size.dimensions != current.size.dimensions && previous.position != current.position
1195    }
1196
1197    pub fn get_layout_size(&self) -> LayoutSize {
1198        LayoutSize::new(
1199            libm::roundf(self.current_window_state.size.dimensions.width) as isize,
1200            libm::roundf(self.current_window_state.size.dimensions.height) as isize,
1201        )
1202    }
1203
1204    /// Returns the menu bar set on the LayoutResults[0] node 0 or None
1205    pub fn get_menu_bar<'a>(&'a self) -> Option<&'a Box<Menu>> {
1206        let lr = self.layout_results.get(0)?;
1207        let ndc = lr.styled_dom.node_data.as_container();
1208        let nd = ndc.get_extended_lifetime(NodeId::ZERO)?;
1209        let mb = nd.get_menu_bar();
1210        mb
1211    }
1212
1213    /// Returns the current context menu on the nearest hit node
1214    /// or None if no context menu was found
1215    pub fn get_context_menu<'a>(&'a self) -> Option<(&'a Box<Menu>, HitTestItem, DomNodeId)> {
1216        let mut context_menu = None;
1217        let hit_test = &self.current_window_state.last_hit_test;
1218
1219        for (dom_id, hit_test) in hit_test.hovered_nodes.iter() {
1220            let layout_result = self.layout_results.get(dom_id.inner)?;
1221            for (node_id, hit) in hit_test.regular_hit_test_nodes.iter() {
1222                let ndc = layout_result.styled_dom.node_data.as_container();
1223                if let Some(cm) = ndc
1224                    .get_extended_lifetime(*node_id)
1225                    .and_then(|node| node.get_context_menu())
1226                {
1227                    if self
1228                        .current_window_state
1229                        .mouse_state
1230                        .matches(&cm.context_mouse_btn)
1231                    {
1232                        let domnode = DomNodeId {
1233                            dom: *dom_id,
1234                            node: NodeHierarchyItemId::from_crate_internal(Some(*node_id)),
1235                        };
1236                        context_menu = Some((cm, hit.clone(), domnode));
1237                    }
1238                }
1239            }
1240        }
1241        context_menu
1242    }
1243
1244    /// Runs a single timer, similar to CallbacksOfHitTest.call()
1245    ///
1246    /// NOTE: The timer has to be selected first by the calling code and verified
1247    /// that it is ready to run
1248    pub fn run_single_timer(
1249        &mut self,
1250        timer_id: usize,
1251        frame_start: Instant,
1252        current_window_handle: &RawWindowHandle,
1253        gl_context: &OptionGlContextPtr,
1254        image_cache: &mut ImageCache,
1255        system_fonts: &mut FcFontCache,
1256        system_callbacks: &ExternalSystemCallbacks,
1257    ) -> CallCallbacksResult {
1258        use crate::{callbacks::CallbackInfo, task::TerminateTimer};
1259
1260        let mut ret = CallCallbacksResult {
1261            should_scroll_render: false,
1262            callbacks_update_screen: Update::DoNothing,
1263            modified_window_state: None,
1264            css_properties_changed: None,
1265            words_changed: None,
1266            images_changed: None,
1267            image_masks_changed: None,
1268            nodes_scrolled_in_callbacks: None,
1269            update_focused_node: None,
1270            timers: None,
1271            threads: None,
1272            timers_removed: None,
1273            threads_removed: None,
1274            windows_created: Vec::new(),
1275            cursor_changed: false,
1276        };
1277
1278        let mut ret_modified_window_state: WindowState = self.current_window_state.clone().into();
1279        let ret_window_state = ret_modified_window_state.clone();
1280        let mut ret_timers = FastHashMap::new();
1281        let mut ret_timers_removed = FastBTreeSet::new();
1282        let mut ret_threads = FastHashMap::new();
1283        let mut ret_threads_removed = FastBTreeSet::new();
1284        let mut ret_words_changed = BTreeMap::new();
1285        let mut ret_images_changed = BTreeMap::new();
1286        let mut ret_image_masks_changed = BTreeMap::new();
1287        let mut ret_css_properties_changed = BTreeMap::new();
1288        let mut ret_nodes_scrolled_in_callbacks = BTreeMap::new();
1289
1290        let mut should_terminate = TerminateTimer::Continue;
1291        let mut new_focus_target = None;
1292
1293        let current_scroll_states = self.get_current_scroll_states();
1294
1295        if let Some(timer) = self.timers.get_mut(&TimerId { id: timer_id }) {
1296            let mut stop_propagation = false;
1297
1298            // TODO: store the hit DOM of the timer?
1299            let hit_dom_node = match timer.node_id.into_option() {
1300                Some(s) => s,
1301                None => DomNodeId {
1302                    dom: DomId::ROOT_ID,
1303                    node: NodeHierarchyItemId::from_crate_internal(None),
1304                },
1305            };
1306            let cursor_relative_to_item = OptionLogicalPosition::None;
1307            let cursor_in_viewport = OptionLogicalPosition::None;
1308
1309            let callback_info = CallbackInfo::new(
1310                &self.layout_results,
1311                &self.renderer_resources,
1312                &self.previous_window_state,
1313                &self.current_window_state,
1314                &mut ret_modified_window_state,
1315                gl_context,
1316                image_cache,
1317                system_fonts,
1318                &mut ret_timers,
1319                &mut ret_threads,
1320                &mut ret_timers_removed,
1321                &mut ret_threads_removed,
1322                current_window_handle,
1323                &mut ret.windows_created,
1324                system_callbacks,
1325                &mut stop_propagation,
1326                &mut new_focus_target,
1327                &mut ret_words_changed,
1328                &mut ret_images_changed,
1329                &mut ret_image_masks_changed,
1330                &mut ret_css_properties_changed,
1331                &current_scroll_states,
1332                &mut ret_nodes_scrolled_in_callbacks,
1333                hit_dom_node,
1334                cursor_relative_to_item,
1335                cursor_in_viewport,
1336            );
1337
1338            let tcr = timer.invoke(
1339                callback_info,
1340                frame_start.clone(),
1341                system_callbacks.get_system_time_fn,
1342            );
1343
1344            ret.callbacks_update_screen = tcr.should_update;
1345            should_terminate = tcr.should_terminate;
1346
1347            if !ret_timers.is_empty() {
1348                ret.timers = Some(ret_timers);
1349            }
1350            if !ret_threads.is_empty() {
1351                ret.threads = Some(ret_threads);
1352            }
1353            if ret_modified_window_state != ret_window_state {
1354                ret.modified_window_state = Some(ret_modified_window_state);
1355            }
1356            if !ret_threads_removed.is_empty() {
1357                ret.threads_removed = Some(ret_threads_removed);
1358            }
1359            if !ret_timers_removed.is_empty() {
1360                ret.timers_removed = Some(ret_timers_removed);
1361            }
1362            if !ret_words_changed.is_empty() {
1363                ret.words_changed = Some(ret_words_changed);
1364            }
1365            if !ret_images_changed.is_empty() {
1366                ret.images_changed = Some(ret_images_changed);
1367            }
1368            if !ret_image_masks_changed.is_empty() {
1369                ret.image_masks_changed = Some(ret_image_masks_changed);
1370            }
1371            if !ret_css_properties_changed.is_empty() {
1372                ret.css_properties_changed = Some(ret_css_properties_changed);
1373            }
1374            if !ret_nodes_scrolled_in_callbacks.is_empty() {
1375                ret.nodes_scrolled_in_callbacks = Some(ret_nodes_scrolled_in_callbacks);
1376            }
1377        }
1378
1379        if let Some(ft) = new_focus_target {
1380            if let Ok(new_focus_node) =
1381                ft.resolve(&self.layout_results, self.current_window_state.focused_node)
1382            {
1383                ret.update_focused_node = Some(new_focus_node);
1384            }
1385        }
1386
1387        if should_terminate == TerminateTimer::Terminate {
1388            ret.timers_removed
1389                .get_or_insert_with(|| BTreeSet::new())
1390                .insert(TimerId { id: timer_id });
1391        }
1392
1393        return ret;
1394    }
1395
1396    #[cfg(feature = "std")]
1397    pub fn run_all_threads(
1398        &mut self,
1399        data: &mut RefAny,
1400        current_window_handle: &RawWindowHandle,
1401        gl_context: &OptionGlContextPtr,
1402        image_cache: &mut ImageCache,
1403        system_fonts: &mut FcFontCache,
1404        system_callbacks: &ExternalSystemCallbacks,
1405    ) -> CallCallbacksResult {
1406        use crate::{
1407            callbacks::CallbackInfo,
1408            task::{
1409                OptionThreadReceiveMsg, OptionThreadSendMsg, ThreadReceiveMsg, ThreadReceiver,
1410                ThreadSendMsg, ThreadWriteBackMsg,
1411            },
1412        };
1413
1414        let mut ret = CallCallbacksResult {
1415            should_scroll_render: false,
1416            callbacks_update_screen: Update::DoNothing,
1417            modified_window_state: None,
1418            css_properties_changed: None,
1419            words_changed: None,
1420            images_changed: None,
1421            image_masks_changed: None,
1422            nodes_scrolled_in_callbacks: None,
1423            update_focused_node: None,
1424            timers: None,
1425            threads: None,
1426            timers_removed: None,
1427            threads_removed: None,
1428            windows_created: Vec::new(),
1429            cursor_changed: false,
1430        };
1431
1432        let mut ret_modified_window_state: WindowState = self.current_window_state.clone().into();
1433        let ret_window_state = ret_modified_window_state.clone();
1434        let mut ret_timers = FastHashMap::new();
1435        let mut ret_timers_removed = FastBTreeSet::new();
1436        let mut ret_threads = FastHashMap::new();
1437        let mut ret_threads_removed = FastBTreeSet::new();
1438        let mut ret_words_changed = BTreeMap::new();
1439        let mut ret_images_changed = BTreeMap::new();
1440        let mut ret_image_masks_changed = BTreeMap::new();
1441        let mut ret_css_properties_changed = BTreeMap::new();
1442        let mut ret_nodes_scrolled_in_callbacks = BTreeMap::new();
1443        let mut new_focus_target = None;
1444        let mut stop_propagation = false;
1445        let current_scroll_states = self.get_current_scroll_states();
1446
1447        for (thread_id, thread) in self.threads.iter_mut() {
1448            let hit_dom_node = DomNodeId {
1449                dom: DomId::ROOT_ID,
1450                node: NodeHierarchyItemId::from_crate_internal(None),
1451            };
1452            let cursor_relative_to_item = OptionLogicalPosition::None;
1453            let cursor_in_viewport = OptionLogicalPosition::None;
1454
1455            let thread = &mut *match thread.ptr.lock().ok() {
1456                Some(s) => s,
1457                None => {
1458                    ret.threads_removed
1459                        .get_or_insert_with(|| BTreeSet::default())
1460                        .insert(*thread_id);
1461                    continue;
1462                }
1463            };
1464
1465            let _ = thread.sender_send(ThreadSendMsg::Tick);
1466            let update = thread.receiver_try_recv();
1467            let msg = match update {
1468                OptionThreadReceiveMsg::None => continue,
1469                OptionThreadReceiveMsg::Some(s) => s,
1470            };
1471
1472            let ThreadWriteBackMsg { mut data, callback } = match msg {
1473                ThreadReceiveMsg::Update(update_screen) => {
1474                    ret.callbacks_update_screen.max_self(update_screen);
1475                    continue;
1476                }
1477                ThreadReceiveMsg::WriteBack(t) => t,
1478            };
1479
1480            let mut callback_info = CallbackInfo::new(
1481                &self.layout_results,
1482                &self.renderer_resources,
1483                &self.previous_window_state,
1484                &self.current_window_state,
1485                &mut ret_modified_window_state,
1486                gl_context,
1487                image_cache,
1488                system_fonts,
1489                &mut ret_timers,
1490                &mut ret_threads,
1491                &mut ret_timers_removed,
1492                &mut ret_threads_removed,
1493                current_window_handle,
1494                &mut ret.windows_created,
1495                system_callbacks,
1496                &mut stop_propagation,
1497                &mut new_focus_target,
1498                &mut ret_words_changed,
1499                &mut ret_images_changed,
1500                &mut ret_image_masks_changed,
1501                &mut ret_css_properties_changed,
1502                &current_scroll_states,
1503                &mut ret_nodes_scrolled_in_callbacks,
1504                hit_dom_node,
1505                cursor_relative_to_item,
1506                cursor_in_viewport,
1507            );
1508
1509            let callback_update =
1510                (callback.cb)(&mut thread.writeback_data, &mut data, &mut callback_info);
1511            ret.callbacks_update_screen.max_self(callback_update);
1512
1513            if thread.is_finished() {
1514                ret.threads_removed
1515                    .get_or_insert_with(|| BTreeSet::default())
1516                    .insert(*thread_id);
1517            }
1518        }
1519
1520        if !ret_timers.is_empty() {
1521            ret.timers = Some(ret_timers);
1522        }
1523        if !ret_threads.is_empty() {
1524            ret.threads = Some(ret_threads);
1525        }
1526        if ret_modified_window_state != ret_window_state {
1527            ret.modified_window_state = Some(ret_modified_window_state);
1528        }
1529        if !ret_threads_removed.is_empty() {
1530            ret.threads_removed = Some(ret_threads_removed);
1531        }
1532        if !ret_timers_removed.is_empty() {
1533            ret.timers_removed = Some(ret_timers_removed);
1534        }
1535        if !ret_words_changed.is_empty() {
1536            ret.words_changed = Some(ret_words_changed);
1537        }
1538        if !ret_images_changed.is_empty() {
1539            ret.images_changed = Some(ret_images_changed);
1540        }
1541        if !ret_image_masks_changed.is_empty() {
1542            ret.image_masks_changed = Some(ret_image_masks_changed);
1543        }
1544        if !ret_css_properties_changed.is_empty() {
1545            ret.css_properties_changed = Some(ret_css_properties_changed);
1546        }
1547        if !ret_nodes_scrolled_in_callbacks.is_empty() {
1548            ret.nodes_scrolled_in_callbacks = Some(ret_nodes_scrolled_in_callbacks);
1549        }
1550
1551        if let Some(ft) = new_focus_target {
1552            if let Ok(new_focus_node) =
1553                ft.resolve(&self.layout_results, self.current_window_state.focused_node)
1554            {
1555                ret.update_focused_node = Some(new_focus_node);
1556            }
1557        }
1558
1559        return ret;
1560    }
1561
1562    // Invokes the create or shutdown callback (or any single callback for that matter)
1563    // Used to invoke on_window_create() and on_window_shutdown() callbacks
1564    pub fn invoke_single_callback(
1565        &mut self,
1566        callback: &mut Callback,
1567        data: &mut RefAny,
1568        current_window_handle: &RawWindowHandle,
1569        gl_context: &OptionGlContextPtr,
1570        image_cache: &mut ImageCache,
1571        system_fonts: &mut FcFontCache,
1572        system_callbacks: &ExternalSystemCallbacks,
1573    ) -> CallCallbacksResult {
1574        let hit_dom_node = DomNodeId {
1575            dom: DomId::ROOT_ID,
1576            node: NodeHierarchyItemId::from_crate_internal(None),
1577        };
1578
1579        use crate::callbacks::CallbackInfo;
1580
1581        let mut ret = CallCallbacksResult {
1582            should_scroll_render: false,
1583            callbacks_update_screen: Update::DoNothing,
1584            modified_window_state: None,
1585            css_properties_changed: None,
1586            words_changed: None,
1587            images_changed: None,
1588            image_masks_changed: None,
1589            nodes_scrolled_in_callbacks: None,
1590            update_focused_node: None,
1591            timers: None,
1592            threads: None,
1593            timers_removed: None,
1594            threads_removed: None,
1595            windows_created: Vec::new(),
1596            cursor_changed: false,
1597        };
1598
1599        let mut ret_modified_window_state: WindowState = self.current_window_state.clone().into();
1600        let ret_window_state = ret_modified_window_state.clone();
1601        let mut ret_timers = FastHashMap::new();
1602        let mut ret_timers_removed = FastBTreeSet::new();
1603        let mut ret_threads = FastHashMap::new();
1604        let mut ret_threads_removed = FastBTreeSet::new();
1605        let mut ret_words_changed = BTreeMap::new();
1606        let mut ret_images_changed = BTreeMap::new();
1607        let mut ret_image_masks_changed = BTreeMap::new();
1608        let mut ret_css_properties_changed = BTreeMap::new();
1609        let mut ret_nodes_scrolled_in_callbacks = BTreeMap::new();
1610        let mut new_focus_target = None;
1611        let mut stop_propagation = false;
1612        let current_scroll_states = self.get_current_scroll_states();
1613
1614        let cursor_relative_to_item = OptionLogicalPosition::None;
1615        let cursor_in_viewport = OptionLogicalPosition::None;
1616
1617        let mut callback_info = CallbackInfo::new(
1618            &self.layout_results,
1619            &self.renderer_resources,
1620            &self.previous_window_state,
1621            &self.current_window_state,
1622            &mut ret_modified_window_state,
1623            gl_context,
1624            image_cache,
1625            system_fonts,
1626            &mut ret_timers,
1627            &mut ret_threads,
1628            &mut ret_timers_removed,
1629            &mut ret_threads_removed,
1630            current_window_handle,
1631            &mut ret.windows_created,
1632            system_callbacks,
1633            &mut stop_propagation,
1634            &mut new_focus_target,
1635            &mut ret_words_changed,
1636            &mut ret_images_changed,
1637            &mut ret_image_masks_changed,
1638            &mut ret_css_properties_changed,
1639            &current_scroll_states,
1640            &mut ret_nodes_scrolled_in_callbacks,
1641            hit_dom_node,
1642            cursor_relative_to_item,
1643            cursor_in_viewport,
1644        );
1645
1646        ret.callbacks_update_screen = (callback.cb)(data, &mut callback_info);
1647
1648        if !ret_timers.is_empty() {
1649            ret.timers = Some(ret_timers);
1650        }
1651        if !ret_threads.is_empty() {
1652            ret.threads = Some(ret_threads);
1653        }
1654        if ret_modified_window_state != ret_window_state {
1655            ret.modified_window_state = Some(ret_modified_window_state);
1656        }
1657        if !ret_threads_removed.is_empty() {
1658            ret.threads_removed = Some(ret_threads_removed);
1659        }
1660        if !ret_timers_removed.is_empty() {
1661            ret.timers_removed = Some(ret_timers_removed);
1662        }
1663        if !ret_words_changed.is_empty() {
1664            ret.words_changed = Some(ret_words_changed);
1665        }
1666        if !ret_images_changed.is_empty() {
1667            ret.images_changed = Some(ret_images_changed);
1668        }
1669        if !ret_image_masks_changed.is_empty() {
1670            ret.image_masks_changed = Some(ret_image_masks_changed);
1671        }
1672        if !ret_css_properties_changed.is_empty() {
1673            ret.css_properties_changed = Some(ret_css_properties_changed);
1674        }
1675        if !ret_nodes_scrolled_in_callbacks.is_empty() {
1676            ret.nodes_scrolled_in_callbacks = Some(ret_nodes_scrolled_in_callbacks);
1677        }
1678
1679        if let Some(ft) = new_focus_target {
1680            if let Ok(new_focus_node) =
1681                ft.resolve(&self.layout_results, self.current_window_state.focused_node)
1682            {
1683                ret.update_focused_node = Some(new_focus_node);
1684            }
1685        }
1686
1687        return ret;
1688    }
1689
1690    pub fn invoke_menu_callback(
1691        &mut self,
1692        menu_callback: &mut MenuCallback,
1693        hit_dom_node: DomNodeId,
1694        current_window_handle: &RawWindowHandle,
1695        gl_context: &OptionGlContextPtr,
1696        image_cache: &mut ImageCache,
1697        system_fonts: &mut FcFontCache,
1698        system_callbacks: &ExternalSystemCallbacks,
1699    ) -> CallCallbacksResult {
1700        use crate::callbacks::CallbackInfo;
1701
1702        let mut ret = CallCallbacksResult {
1703            should_scroll_render: false,
1704            callbacks_update_screen: Update::DoNothing,
1705            modified_window_state: None,
1706            css_properties_changed: None,
1707            words_changed: None,
1708            images_changed: None,
1709            image_masks_changed: None,
1710            nodes_scrolled_in_callbacks: None,
1711            update_focused_node: None,
1712            timers: None,
1713            threads: None,
1714            timers_removed: None,
1715            threads_removed: None,
1716            windows_created: Vec::new(),
1717            cursor_changed: false,
1718        };
1719
1720        let mut ret_modified_window_state: WindowState = self.current_window_state.clone().into();
1721        let ret_window_state = ret_modified_window_state.clone();
1722        let mut ret_timers = FastHashMap::new();
1723        let mut ret_timers_removed = FastBTreeSet::new();
1724        let mut ret_threads = FastHashMap::new();
1725        let mut ret_threads_removed = FastBTreeSet::new();
1726        let mut ret_words_changed = BTreeMap::new();
1727        let mut ret_images_changed = BTreeMap::new();
1728        let mut ret_image_masks_changed = BTreeMap::new();
1729        let mut ret_css_properties_changed = BTreeMap::new();
1730        let mut ret_nodes_scrolled_in_callbacks = BTreeMap::new();
1731        let mut new_focus_target = None;
1732        let mut stop_propagation = false;
1733        let current_scroll_states = self.get_current_scroll_states();
1734
1735        let cursor_relative_to_item = OptionLogicalPosition::None;
1736        let cursor_in_viewport = OptionLogicalPosition::None;
1737
1738        let mut callback_info = CallbackInfo::new(
1739            &self.layout_results,
1740            &self.renderer_resources,
1741            &self.previous_window_state,
1742            &self.current_window_state,
1743            &mut ret_modified_window_state,
1744            gl_context,
1745            image_cache,
1746            system_fonts,
1747            &mut ret_timers,
1748            &mut ret_threads,
1749            &mut ret_timers_removed,
1750            &mut ret_threads_removed,
1751            current_window_handle,
1752            &mut ret.windows_created,
1753            system_callbacks,
1754            &mut stop_propagation,
1755            &mut new_focus_target,
1756            &mut ret_words_changed,
1757            &mut ret_images_changed,
1758            &mut ret_image_masks_changed,
1759            &mut ret_css_properties_changed,
1760            &current_scroll_states,
1761            &mut ret_nodes_scrolled_in_callbacks,
1762            hit_dom_node,
1763            cursor_relative_to_item,
1764            cursor_in_viewport,
1765        );
1766
1767        ret.callbacks_update_screen =
1768            (menu_callback.callback.cb)(&mut menu_callback.data, &mut callback_info);
1769
1770        if !ret_timers.is_empty() {
1771            ret.timers = Some(ret_timers);
1772        }
1773        if !ret_threads.is_empty() {
1774            ret.threads = Some(ret_threads);
1775        }
1776        if ret_modified_window_state != ret_window_state {
1777            ret.modified_window_state = Some(ret_modified_window_state);
1778        }
1779        if !ret_threads_removed.is_empty() {
1780            ret.threads_removed = Some(ret_threads_removed);
1781        }
1782        if !ret_timers_removed.is_empty() {
1783            ret.timers_removed = Some(ret_timers_removed);
1784        }
1785        if !ret_words_changed.is_empty() {
1786            ret.words_changed = Some(ret_words_changed);
1787        }
1788        if !ret_images_changed.is_empty() {
1789            ret.images_changed = Some(ret_images_changed);
1790        }
1791        if !ret_image_masks_changed.is_empty() {
1792            ret.image_masks_changed = Some(ret_image_masks_changed);
1793        }
1794        if !ret_css_properties_changed.is_empty() {
1795            ret.css_properties_changed = Some(ret_css_properties_changed);
1796        }
1797        if !ret_nodes_scrolled_in_callbacks.is_empty() {
1798            ret.nodes_scrolled_in_callbacks = Some(ret_nodes_scrolled_in_callbacks);
1799        }
1800
1801        if let Some(ft) = new_focus_target {
1802            if let Ok(new_focus_node) =
1803                ft.resolve(&self.layout_results, self.current_window_state.focused_node)
1804            {
1805                ret.update_focused_node = Some(new_focus_node);
1806            }
1807        }
1808
1809        return ret;
1810    }
1811}
1812
1813#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd, Hash, Ord, Eq)]
1814#[repr(C)]
1815pub struct TouchState {
1816    /// TODO: not yet implemented
1817    pub unimplemented: u8,
1818}
1819
1820/// State, size, etc of the window, for comparing to the last frame
1821#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Hash, Ord, Eq)]
1822#[repr(C)]
1823pub enum WindowTheme {
1824    DarkMode,
1825    LightMode,
1826}
1827
1828impl Default for WindowTheme {
1829    fn default() -> WindowTheme {
1830        WindowTheme::LightMode // sorry!
1831    }
1832}
1833
1834impl_option!(
1835    WindowTheme,
1836    OptionWindowTheme,
1837    [Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash]
1838);
1839
1840#[derive(Debug, PartialEq, PartialOrd, Clone)]
1841#[repr(C)]
1842pub struct Monitor {
1843    pub id: usize,
1844    pub name: OptionAzString,
1845    pub size: LayoutSize,
1846    pub position: LayoutPoint,
1847    pub scale_factor: f64,
1848    pub video_modes: VideoModeVec,
1849    pub is_primary_monitor: bool,
1850}
1851
1852impl_vec!(Monitor, MonitorVec, MonitorVecDestructor);
1853impl_vec_debug!(Monitor, MonitorVec);
1854impl_vec_clone!(Monitor, MonitorVec, MonitorVecDestructor);
1855impl_vec_partialeq!(Monitor, MonitorVec);
1856impl_vec_partialord!(Monitor, MonitorVec);
1857
1858impl core::hash::Hash for Monitor {
1859    fn hash<H>(&self, state: &mut H)
1860    where
1861        H: core::hash::Hasher,
1862    {
1863        self.id.hash(state)
1864    }
1865}
1866
1867impl Default for Monitor {
1868    fn default() -> Self {
1869        Monitor {
1870            id: 0,
1871            name: OptionAzString::None,
1872            size: LayoutSize::zero(),
1873            position: LayoutPoint::zero(),
1874            scale_factor: 1.0,
1875            video_modes: Vec::new().into(),
1876            is_primary_monitor: false,
1877        }
1878    }
1879}
1880#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1881#[repr(C)]
1882pub struct VideoMode {
1883    pub size: LayoutSize,
1884    pub bit_depth: u16,
1885    pub refresh_rate: u16,
1886}
1887
1888impl_vec!(VideoMode, VideoModeVec, VideoModeVecDestructor);
1889impl_vec_clone!(VideoMode, VideoModeVec, VideoModeVecDestructor);
1890impl_vec_debug!(VideoMode, VideoModeVec);
1891impl_vec_partialeq!(VideoMode, VideoModeVec);
1892impl_vec_partialord!(VideoMode, VideoModeVec);
1893
1894#[derive(Debug, Clone, PartialEq)]
1895#[repr(C)]
1896pub struct WindowState {
1897    pub title: AzString,
1898    /// Theme of this window (dark or light) - can be set / overridden by the user
1899    ///
1900    /// Usually the operating system will set this field. On change, it will
1901    /// emit a `WindowEventFilter::ThemeChanged` event
1902    pub theme: WindowTheme,
1903    /// Size of the window + max width / max height: 800 x 600 by default
1904    pub size: WindowSize,
1905    /// The x and y position, or None to let the WM decide where to put the window (default)
1906    pub position: WindowPosition,
1907    /// Flags such as whether the window is minimized / maximized, fullscreen, etc.
1908    pub flags: WindowFlags,
1909    /// Mostly used for debugging, shows WebRender-builtin graphs on the screen.
1910    /// Used for performance monitoring and displaying frame times (rendering-only).
1911    pub debug_state: DebugState,
1912    /// Current keyboard state - NOTE: mutating this field (currently) does nothing
1913    /// (doesn't get synchronized with OS-level window)!
1914    pub keyboard_state: KeyboardState,
1915    /// Current mouse state
1916    pub mouse_state: MouseState,
1917    /// Stores all states of currently connected touch input devices, pencils, tablets, etc.
1918    pub touch_state: TouchState,
1919    /// Sets location of IME candidate box in client area coordinates
1920    /// relative to the top left of the window.
1921    pub ime_position: ImePosition,
1922    /// Which monitor the window is currently residing on
1923    pub monitor: Monitor,
1924    /// Window options that can only be set on a certain platform
1925    /// (`WindowsWindowOptions` / `LinuxWindowOptions` / `MacWindowOptions`).
1926    pub platform_specific_options: PlatformSpecificOptions,
1927    /// Whether this window has SRGB / vsync / hardware acceleration
1928    pub renderer_options: RendererOptions,
1929    /// Color of the window background (can be transparent if necessary)
1930    pub background_color: ColorU,
1931    /// The `layout()` function for this window, stored as a callback function pointer,
1932    /// There are multiple reasons for doing this (instead of requiring `T: Layout` everywhere):
1933    ///
1934    /// - It seperates the `Dom` from the `Layout` trait, making it possible to split the UI
1935    ///   solving and styling into reusable crates
1936    /// - It's less typing work (prevents having to type `<T: Layout>` everywhere)
1937    /// - It's potentially more efficient to compile (less type-checking required)
1938    /// - It's a preparation for the C ABI, in which traits don't exist (for language bindings). In
1939    ///   the C ABI "traits" are simply structs with function pointers (and void* instead of T)
1940    pub layout_callback: LayoutCallback,
1941    /// Optional callback to run when the window closes
1942    pub close_callback: OptionCallback,
1943}
1944
1945impl_option!(
1946    WindowState,
1947    OptionWindowState,
1948    copy = false,
1949    [Debug, Clone, PartialEq]
1950);
1951
1952#[derive(Debug, Copy, Clone, PartialEq)]
1953#[repr(C, u8)]
1954pub enum WindowPosition {
1955    Uninitialized,
1956    Initialized(PhysicalPositionI32),
1957}
1958
1959impl Default for WindowPosition {
1960    fn default() -> WindowPosition {
1961        WindowPosition::Uninitialized
1962    }
1963}
1964
1965#[derive(Debug, Copy, Clone, PartialEq)]
1966#[repr(C, u8)]
1967pub enum ImePosition {
1968    Uninitialized,
1969    Initialized(LogicalPosition),
1970}
1971
1972impl Default for ImePosition {
1973    fn default() -> ImePosition {
1974        ImePosition::Uninitialized
1975    }
1976}
1977
1978#[derive(Debug, Clone, PartialEq)]
1979pub struct FullWindowState {
1980    /// Theme of this window (dark or light) - can be set / overridden by the user
1981    ///
1982    /// Usually the operating system will set this field. On change, it will
1983    /// emit a `WindowEventFilter::ThemeChanged` event
1984    pub theme: WindowTheme,
1985    /// Current title of the window
1986    pub title: AzString,
1987    /// Size of the window + max width / max height: 800 x 600 by default
1988    pub size: WindowSize,
1989    /// The x and y position, or None to let the WM decide where to put the window (default)
1990    pub position: WindowPosition,
1991    /// Flags such as whether the window is minimized / maximized, fullscreen, etc.
1992    pub flags: WindowFlags,
1993    /// Mostly used for debugging, shows WebRender-builtin graphs on the screen.
1994    /// Used for performance monitoring and displaying frame times (rendering-only).
1995    pub debug_state: DebugState,
1996    /// Current keyboard state - NOTE: mutating this field (currently) does nothing
1997    /// (doesn't get synchronized with OS-level window)!
1998    pub keyboard_state: KeyboardState,
1999    /// Current mouse state
2000    pub mouse_state: MouseState,
2001    /// Stores all states of currently connected touch input devices, pencils, tablets, etc.
2002    pub touch_state: TouchState,
2003    /// Sets location of IME candidate box in client area coordinates
2004    /// relative to the top left of the window.
2005    pub ime_position: ImePosition,
2006    /// Window options that can only be set on a certain platform
2007    /// (`WindowsWindowOptions` / `LinuxWindowOptions` / `MacWindowOptions`).
2008    pub platform_specific_options: PlatformSpecificOptions,
2009    /// Information about vsync and hardware acceleration
2010    pub renderer_options: RendererOptions,
2011    /// Background color of the window
2012    pub background_color: ColorU,
2013    /// The `layout()` function for this window, stored as a callback function pointer,
2014    /// There are multiple reasons for doing this (instead of requiring `T: Layout` everywhere):
2015    ///
2016    /// - It seperates the `Dom` from the `Layout` trait, making it possible to split the UI
2017    ///   solving and styling into reusable crates
2018    /// - It's less typing work (prevents having to type `<T: Layout>` everywhere)
2019    /// - It's potentially more efficient to compile (less type-checking required)
2020    /// - It's a preparation for the C ABI, in which traits don't exist (for language bindings). In
2021    ///   the C ABI "traits" are simply structs with function pointers (and void* instead of T)
2022    pub layout_callback: LayoutCallback,
2023    /// Callback to run before the window closes. If this callback returns `DoNothing`,
2024    /// the window won't close, otherwise it'll close regardless
2025    pub close_callback: OptionCallback,
2026    // --
2027    /// Current monitor
2028    pub monitor: Monitor,
2029    /// Whether there is a file currently hovering over the window
2030    pub hovered_file: Option<AzString>, // Option<PathBuf>
2031    /// Whether there was a file currently dropped on the window
2032    pub dropped_file: Option<AzString>, // Option<PathBuf>
2033    /// What node is currently hovered over, default to None. Only necessary internal
2034    /// to the crate, for emitting `On::FocusReceived` and `On::FocusLost` events,
2035    /// as well as styling `:focus` elements
2036    pub focused_node: Option<DomNodeId>,
2037    /// Last hit-test that was performed: necessary because the
2038    /// events are stored in a queue and only storing the hovered
2039    /// nodes is not sufficient to correctly determine events
2040    pub last_hit_test: FullHitTest,
2041}
2042
2043impl Default for FullWindowState {
2044    fn default() -> Self {
2045        Self {
2046            theme: WindowTheme::default(),
2047            title: AzString::from_const_str(DEFAULT_TITLE),
2048            size: WindowSize::default(),
2049            position: WindowPosition::Uninitialized,
2050            flags: WindowFlags::default(),
2051            debug_state: DebugState::default(),
2052            keyboard_state: KeyboardState::default(),
2053            mouse_state: MouseState::default(),
2054            touch_state: TouchState::default(),
2055            ime_position: ImePosition::Uninitialized,
2056            platform_specific_options: PlatformSpecificOptions::default(),
2057            background_color: ColorU::WHITE,
2058            layout_callback: LayoutCallback::default(),
2059            close_callback: OptionCallback::None,
2060            renderer_options: RendererOptions::default(),
2061            monitor: Monitor::default(),
2062            // --
2063            hovered_file: None,
2064            dropped_file: None,
2065            focused_node: None,
2066            last_hit_test: FullHitTest::empty(None),
2067        }
2068    }
2069}
2070
2071impl FullWindowState {
2072    pub fn get_mouse_state(&self) -> &MouseState {
2073        &self.mouse_state
2074    }
2075
2076    pub fn get_keyboard_state(&self) -> &KeyboardState {
2077        &self.keyboard_state
2078    }
2079
2080    pub fn get_hovered_file(&self) -> Option<&AzString> {
2081        self.hovered_file.as_ref()
2082    }
2083
2084    pub fn get_dropped_file(&self) -> Option<&AzString> {
2085        self.dropped_file.as_ref()
2086    }
2087
2088    pub fn get_scroll_amount(&self) -> Option<(f32, f32)> {
2089        self.mouse_state.get_scroll_amount()
2090    }
2091
2092    pub fn layout_callback_changed(&self, other: &Option<Self>) -> bool {
2093        match other {
2094            Some(s) => self.layout_callback != s.layout_callback,
2095            None => false,
2096        }
2097    }
2098
2099    /// Creates a FullWindowState from a regular WindowState,
2100    /// fills non-available fields with the given values
2101    ///
2102    /// You need to pass the extra fields explicitly in order
2103    /// to prevent state management bugs
2104    pub fn from_window_state(
2105        window_state: &WindowState,
2106        dropped_file: Option<AzString>,
2107        hovered_file: Option<AzString>,
2108        focused_node: Option<DomNodeId>,
2109        last_hit_test: FullHitTest,
2110    ) -> Self {
2111        Self {
2112            monitor: window_state.monitor.clone(),
2113            theme: window_state.theme,
2114            title: window_state.title.clone(),
2115            size: window_state.size,
2116            position: window_state.position.into(),
2117            flags: window_state.flags,
2118            debug_state: window_state.debug_state,
2119            keyboard_state: window_state.keyboard_state.clone(),
2120            mouse_state: window_state.mouse_state,
2121            touch_state: window_state.touch_state,
2122            ime_position: window_state.ime_position.into(),
2123            platform_specific_options: window_state.platform_specific_options.clone(),
2124            background_color: window_state.background_color,
2125            layout_callback: window_state.layout_callback.clone(),
2126            close_callback: window_state.close_callback,
2127            renderer_options: window_state.renderer_options,
2128            dropped_file,
2129            hovered_file,
2130            focused_node,
2131            last_hit_test,
2132        }
2133    }
2134
2135    pub fn process_system_scroll(&mut self, scroll_states: &ScrollStates) -> Option<ScrollResult> {
2136        let (x, y) = self.mouse_state.get_scroll_amount()?;
2137        // TODO
2138        Some(ScrollResult {})
2139    }
2140}
2141
2142impl From<FullWindowState> for WindowState {
2143    fn from(full_window_state: FullWindowState) -> WindowState {
2144        WindowState {
2145            monitor: full_window_state.monitor.clone(),
2146            theme: full_window_state.theme,
2147            title: full_window_state.title.into(),
2148            size: full_window_state.size,
2149            position: full_window_state.position.into(),
2150            flags: full_window_state.flags,
2151            debug_state: full_window_state.debug_state,
2152            keyboard_state: full_window_state.keyboard_state,
2153            mouse_state: full_window_state.mouse_state,
2154            touch_state: full_window_state.touch_state,
2155            ime_position: full_window_state.ime_position.into(),
2156            platform_specific_options: full_window_state.platform_specific_options,
2157            background_color: full_window_state.background_color,
2158            layout_callback: full_window_state.layout_callback,
2159            close_callback: full_window_state.close_callback,
2160            renderer_options: full_window_state.renderer_options,
2161        }
2162    }
2163}
2164
2165#[derive(Debug)]
2166pub struct CallCallbacksResult {
2167    /// Whether the UI should be rendered anyways due to a (programmatic or user input) scroll
2168    /// event
2169    pub should_scroll_render: bool,
2170    /// Whether the callbacks say to rebuild the UI or not
2171    pub callbacks_update_screen: Update,
2172    /// WindowState that was (potentially) modified in the callbacks
2173    pub modified_window_state: Option<WindowState>,
2174    /// If a word changed (often times the case with text input), we don't need to relayout /
2175    /// rerender the whole screen. The result is passed to the `relayout()` function, which
2176    /// will only change the single node that was modified
2177    pub words_changed: Option<BTreeMap<DomId, BTreeMap<NodeId, AzString>>>,
2178    /// A callback can "exchange" and image for a new one without requiring a new display list to
2179    /// be rebuilt. This is important for animated images, especially video.
2180    pub images_changed: Option<BTreeMap<DomId, BTreeMap<NodeId, (ImageRef, UpdateImageType)>>>,
2181    /// Same as images, clip masks can be changed in callbacks, often the case with vector
2182    /// animations
2183    pub image_masks_changed: Option<BTreeMap<DomId, BTreeMap<NodeId, ImageMask>>>,
2184    /// If the focus target changes in the callbacks, the function will automatically
2185    /// restyle the DOM and set the new focus target
2186    pub css_properties_changed: Option<BTreeMap<DomId, BTreeMap<NodeId, Vec<CssProperty>>>>,
2187    /// If the callbacks have scrolled any nodes, the new scroll position will be stored here
2188    pub nodes_scrolled_in_callbacks:
2189        Option<BTreeMap<DomId, BTreeMap<NodeHierarchyItemId, LogicalPosition>>>,
2190    /// Whether the focused node was changed from the callbacks
2191    pub update_focused_node: Option<Option<DomNodeId>>,
2192    /// Timers that were added in the callbacks
2193    pub timers: Option<FastHashMap<TimerId, Timer>>,
2194    /// Tasks that were added in the callbacks
2195    pub threads: Option<FastHashMap<ThreadId, Thread>>,
2196    /// Timers that were added in the callbacks
2197    pub timers_removed: Option<FastBTreeSet<TimerId>>,
2198    /// Tasks that were added in the callbacks
2199    pub threads_removed: Option<FastBTreeSet<ThreadId>>,
2200    /// Windows that were created in the callbacks
2201    pub windows_created: Vec<WindowCreateOptions>,
2202    /// Whether the cursor changed in the callbacks
2203    pub cursor_changed: bool,
2204}
2205
2206impl CallCallbacksResult {
2207    pub fn cursor_changed(&self) -> bool {
2208        self.cursor_changed
2209    }
2210    pub fn focus_changed(&self) -> bool {
2211        self.update_focused_node.is_some()
2212    }
2213}
2214
2215#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2216#[repr(C)]
2217pub struct WindowFlags {
2218    /// Is the window currently maximized, minimized or fullscreen
2219    pub frame: WindowFrame,
2220    /// Is the window about to close on the next frame?
2221    pub is_about_to_close: bool,
2222    /// Does the window have decorations (close, minimize, maximize, title bar)?
2223    pub has_decorations: bool,
2224    /// Is the window currently visible?
2225    pub is_visible: bool,
2226    /// Is the window always on top?
2227    pub is_always_on_top: bool,
2228    /// Whether the window is resizable
2229    pub is_resizable: bool,
2230    /// Whether the window has focus or not (mutating this will request user attention)
2231    pub has_focus: bool,
2232    /// Whether the window has an "extended frame", i.e. the title bar is not rendered
2233    /// and the maximize / minimize / close buttons bleed into the window content
2234    pub has_extended_window_frame: bool,
2235    /// Whether or not the compositor should blur the application background
2236    pub has_blur_behind_window: bool,
2237    /// Is smooth scrolling enabled for this window?
2238    pub smooth_scroll_enabled: bool,
2239    /// Is automatic TAB switching supported?
2240    pub autotab_enabled: bool,
2241}
2242
2243#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
2244#[repr(C)]
2245pub enum WindowFrame {
2246    Normal,
2247    Minimized,
2248    Maximized,
2249    Fullscreen,
2250}
2251
2252impl Default for WindowFlags {
2253    fn default() -> Self {
2254        Self {
2255            frame: WindowFrame::Normal,
2256            is_about_to_close: false,
2257            has_decorations: true,
2258            is_visible: true,
2259            is_always_on_top: false,
2260            is_resizable: true,
2261            has_focus: true,
2262            has_extended_window_frame: false,
2263            has_blur_behind_window: false,
2264            smooth_scroll_enabled: true,
2265            autotab_enabled: true,
2266        }
2267    }
2268}
2269
2270#[derive(Debug, Default, Clone, PartialEq, PartialOrd)]
2271#[repr(C)]
2272pub struct PlatformSpecificOptions {
2273    pub windows_options: WindowsWindowOptions,
2274    pub linux_options: LinuxWindowOptions,
2275    pub mac_options: MacWindowOptions,
2276    pub wasm_options: WasmWindowOptions,
2277}
2278
2279unsafe impl Sync for PlatformSpecificOptions {}
2280unsafe impl Send for PlatformSpecificOptions {}
2281
2282#[derive(Debug, Clone, PartialEq, PartialOrd)]
2283#[repr(C)]
2284pub struct WindowsWindowOptions {
2285    /// STARTUP ONLY: Whether the window should allow drag + drop operations (default: true)
2286    pub allow_drag_and_drop: bool,
2287    /// STARTUP ONLY: Sets `WS_EX_NOREDIRECTIONBITMAP`
2288    pub no_redirection_bitmap: bool,
2289    /// STARTUP ONLY: Window icon (decoded bytes), appears at the top right corner of the window
2290    pub window_icon: OptionWindowIcon,
2291    /// READWRITE: Taskbar icon (decoded bytes), usually 256x256x4 bytes large (`ICON_BIG`).
2292    ///
2293    /// Can be changed in callbacks / at runtime.
2294    pub taskbar_icon: OptionTaskBarIcon,
2295    /// STARTUP ONLY: Pointer (casted to void pointer) to a HWND handle
2296    pub parent_window: OptionHwndHandle,
2297}
2298
2299impl Default for WindowsWindowOptions {
2300    fn default() -> WindowsWindowOptions {
2301        WindowsWindowOptions {
2302            allow_drag_and_drop: true,
2303            no_redirection_bitmap: false,
2304            window_icon: OptionWindowIcon::None,
2305            taskbar_icon: OptionTaskBarIcon::None,
2306            parent_window: OptionHwndHandle::None,
2307        }
2308    }
2309}
2310
2311/// Note: this should be a *mut HWND
2312type HwndHandle = *mut c_void;
2313
2314impl_option!(
2315    HwndHandle,
2316    OptionHwndHandle,
2317    copy = false,
2318    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
2319);
2320
2321/// X window type. Maps directly to
2322/// [`_NET_WM_WINDOW_TYPE`](https://specifications.freedesktop.org/wm-spec/wm-spec-1.5.html).
2323#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2324#[repr(C)]
2325pub enum XWindowType {
2326    /// A desktop feature. This can include a single window containing desktop icons with the same
2327    /// dimensions as the screen, allowing the desktop environment to have full control of the
2328    /// desktop, without the need for proxying root window clicks.
2329    Desktop,
2330    /// A dock or panel feature. Typically a Window Manager would keep such windows on top of all
2331    /// other windows.
2332    Dock,
2333    /// Toolbar windows. "Torn off" from the main application.
2334    Toolbar,
2335    /// Pinnable menu windows. "Torn off" from the main application.
2336    Menu,
2337    /// A small persistent utility window, such as a palette or toolbox.
2338    Utility,
2339    /// The window is a splash screen displayed as an application is starting up.
2340    Splash,
2341    /// This is a dialog window.
2342    Dialog,
2343    /// A dropdown menu that usually appears when the user clicks on an item in a menu bar.
2344    /// This property is typically used on override-redirect windows.
2345    DropdownMenu,
2346    /// A popup menu that usually appears when the user right clicks on an object.
2347    /// This property is typically used on override-redirect windows.
2348    PopupMenu,
2349    /// A tooltip window. Usually used to show additional information when hovering over an object
2350    /// with the cursor. This property is typically used on override-redirect windows.
2351    Tooltip,
2352    /// The window is a notification.
2353    /// This property is typically used on override-redirect windows.
2354    Notification,
2355    /// This should be used on the windows that are popped up by combo boxes.
2356    /// This property is typically used on override-redirect windows.
2357    Combo,
2358    /// This indicates the the window is being dragged.
2359    /// This property is typically used on override-redirect windows.
2360    Dnd,
2361    /// This is a normal, top-level window.
2362    Normal,
2363}
2364
2365impl Default for XWindowType {
2366    fn default() -> Self {
2367        XWindowType::Normal
2368    }
2369}
2370
2371#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
2372#[repr(C)]
2373pub enum UserAttentionType {
2374    None,
2375    Critical,
2376    Informational,
2377}
2378
2379impl Default for UserAttentionType {
2380    fn default() -> UserAttentionType {
2381        UserAttentionType::None
2382    }
2383}
2384
2385#[derive(Debug, Default, Clone, PartialEq, PartialOrd)]
2386#[repr(C)]
2387pub struct LinuxWindowOptions {
2388    /// (Unimplemented) - Can only be set at window creation, can't be changed in callbacks.
2389    pub x11_visual: OptionX11Visual,
2390    /// (Unimplemented) - Can only be set at window creation, can't be changed in callbacks.
2391    pub x11_screen: OptionI32,
2392    /// Build window with `WM_CLASS` hint; defaults to the name of the binary. Only relevant on
2393    /// X11. Can only be set at window creation, can't be changed in callbacks.
2394    pub x11_wm_classes: StringPairVec,
2395    /// Build window with override-redirect flag; defaults to false. Only relevant on X11.
2396    /// Can only be set at window creation, can't be changed in callbacks.
2397    pub x11_override_redirect: bool,
2398    /// Build window with `_NET_WM_WINDOW_TYPE` hint; defaults to `Normal`. Only relevant on X11.
2399    /// Can only be set at window creation, can't be changed in callbacks.
2400    pub x11_window_types: XWindowTypeVec,
2401    /// Build window with `_GTK_THEME_VARIANT` hint set to the specified value. Currently only
2402    /// relevant on X11. Can only be set at window creation, can't be changed in callbacks.
2403    pub x11_gtk_theme_variant: OptionAzString,
2404    /// Build window with resize increment hint. Only implemented on X11.
2405    /// Can only be set at window creation, can't be changed in callbacks.
2406    pub x11_resize_increments: OptionLogicalSize,
2407    /// Build window with base size hint. Only implemented on X11.
2408    /// Can only be set at window creation, can't be changed in callbacks.
2409    pub x11_base_size: OptionLogicalSize,
2410    /// Build window with a given application ID. It should match the `.desktop` file distributed
2411    /// with your program. Only relevant on Wayland.
2412    /// Can only be set at window creation, can't be changed in callbacks.
2413    ///
2414    /// For details about application ID conventions, see the
2415    /// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id)
2416    pub wayland_app_id: OptionAzString,
2417    pub wayland_theme: OptionWaylandTheme,
2418    pub request_user_attention: UserAttentionType,
2419    pub window_icon: OptionWindowIcon,
2420}
2421
2422type X11Visual = *const c_void;
2423impl_option!(
2424    X11Visual,
2425    OptionX11Visual,
2426    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
2427);
2428
2429#[derive(Debug, Default, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
2430#[repr(C)]
2431pub struct AzStringPair {
2432    pub key: AzString,
2433    pub value: AzString,
2434}
2435
2436impl_vec!(AzStringPair, StringPairVec, StringPairVecDestructor);
2437impl_vec_mut!(AzStringPair, StringPairVec);
2438impl_vec_debug!(AzStringPair, StringPairVec);
2439impl_vec_partialord!(AzStringPair, StringPairVec);
2440impl_vec_ord!(AzStringPair, StringPairVec);
2441impl_vec_clone!(AzStringPair, StringPairVec, StringPairVecDestructor);
2442impl_vec_partialeq!(AzStringPair, StringPairVec);
2443impl_vec_eq!(AzStringPair, StringPairVec);
2444impl_vec_hash!(AzStringPair, StringPairVec);
2445
2446impl_option!(
2447    StringPairVec,
2448    OptionStringPairVec,
2449    copy = false,
2450    [Debug, Clone, PartialOrd, PartialEq, Ord, Eq, Hash]
2451);
2452
2453impl StringPairVec {
2454    pub fn get_key(&self, search_key: &str) -> Option<&AzString> {
2455        self.as_ref().iter().find_map(|v| {
2456            if v.key.as_str() == search_key {
2457                Some(&v.value)
2458            } else {
2459                None
2460            }
2461        })
2462    }
2463    pub fn get_key_mut(&mut self, search_key: &str) -> Option<&mut AzStringPair> {
2464        self.as_mut()
2465            .iter_mut()
2466            .find(|v| v.key.as_str() == search_key)
2467    }
2468    pub fn insert_kv<I: Into<AzString>>(&mut self, key: I, value: I) {
2469        let key = key.into();
2470        let value = value.into();
2471        match self.get_key_mut(key.as_str()) {
2472            None => {}
2473            Some(s) => {
2474                s.value = value;
2475                return;
2476            }
2477        }
2478        self.push(AzStringPair { key, value });
2479    }
2480}
2481
2482impl_vec!(XWindowType, XWindowTypeVec, XWindowTypeVecDestructor);
2483impl_vec_debug!(XWindowType, XWindowTypeVec);
2484impl_vec_partialord!(XWindowType, XWindowTypeVec);
2485impl_vec_ord!(XWindowType, XWindowTypeVec);
2486impl_vec_clone!(XWindowType, XWindowTypeVec, XWindowTypeVecDestructor);
2487impl_vec_partialeq!(XWindowType, XWindowTypeVec);
2488impl_vec_eq!(XWindowType, XWindowTypeVec);
2489impl_vec_hash!(XWindowType, XWindowTypeVec);
2490
2491impl_option!(
2492    WaylandTheme,
2493    OptionWaylandTheme,
2494    copy = false,
2495    [Debug, Clone, PartialEq, PartialOrd]
2496);
2497
2498#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
2499#[repr(C)]
2500pub struct MacWindowOptions {
2501    pub reserved: u8,
2502}
2503
2504#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
2505#[repr(C)]
2506pub struct WasmWindowOptions {
2507    // empty for now, single field must be present for ABI compat - always set to 0
2508    pub _reserved: u8,
2509}
2510
2511impl WindowState {
2512    /// Creates a new, default `WindowState` with the given CSS style
2513    pub fn new(callback: LayoutCallbackType) -> Self {
2514        use crate::callbacks::LayoutCallbackInner;
2515        Self {
2516            layout_callback: LayoutCallback::Raw(LayoutCallbackInner { cb: callback }),
2517            ..Default::default()
2518        }
2519    }
2520
2521    /// Returns the current keyboard keyboard state. We don't want the library
2522    /// user to be able to modify this state, only to read it.
2523    pub fn get_mouse_state(&self) -> &MouseState {
2524        &self.mouse_state
2525    }
2526
2527    /// Returns the current windows mouse state. We don't want the library
2528    /// user to be able to modify this state, only to read it.
2529    pub fn get_keyboard_state(&self) -> &KeyboardState {
2530        &self.keyboard_state
2531    }
2532
2533    /// Returns the physical (width, height) in pixel of this window
2534    pub fn get_physical_size(&self) -> (usize, usize) {
2535        (
2536            self.size.dimensions.width as usize,
2537            self.size.dimensions.height as usize,
2538        )
2539    }
2540
2541    /// Returns the current HiDPI factor for this window.
2542    pub fn get_hidpi_factor(&self) -> f32 {
2543        self.size.get_hidpi_factor()
2544    }
2545}
2546
2547#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2548#[repr(C)]
2549pub enum FullScreenMode {
2550    /// - macOS: If the window is in windowed mode, transitions it slowly to fullscreen mode
2551    /// - other: Does the same as `FastFullScreen`.
2552    SlowFullScreen,
2553    /// Window should immediately go into fullscreen mode (on macOS this is not the default
2554    /// behaviour).
2555    FastFullScreen,
2556    /// - macOS: If the window is in fullscreen mode, transitions slowly back to windowed state.
2557    /// - other: Does the same as `FastWindowed`.
2558    SlowWindowed,
2559    /// If the window is in fullscreen mode, will immediately go back to windowed mode (on macOS
2560    /// this is not the default behaviour).
2561    FastWindowed,
2562}
2563
2564#[derive(Debug, Clone, PartialEq, PartialOrd)]
2565#[repr(C)]
2566// Translation type because in winit 24.0 the WinitWaylandTheme is a trait instead
2567// of a struct, which makes things more complicated
2568pub struct WaylandTheme {
2569    pub title_bar_active_background_color: [u8; 4],
2570    pub title_bar_active_separator_color: [u8; 4],
2571    pub title_bar_active_text_color: [u8; 4],
2572    pub title_bar_inactive_background_color: [u8; 4],
2573    pub title_bar_inactive_separator_color: [u8; 4],
2574    pub title_bar_inactive_text_color: [u8; 4],
2575    pub maximize_idle_foreground_inactive_color: [u8; 4],
2576    pub minimize_idle_foreground_inactive_color: [u8; 4],
2577    pub close_idle_foreground_inactive_color: [u8; 4],
2578    pub maximize_hovered_foreground_inactive_color: [u8; 4],
2579    pub minimize_hovered_foreground_inactive_color: [u8; 4],
2580    pub close_hovered_foreground_inactive_color: [u8; 4],
2581    pub maximize_disabled_foreground_inactive_color: [u8; 4],
2582    pub minimize_disabled_foreground_inactive_color: [u8; 4],
2583    pub close_disabled_foreground_inactive_color: [u8; 4],
2584    pub maximize_idle_background_inactive_color: [u8; 4],
2585    pub minimize_idle_background_inactive_color: [u8; 4],
2586    pub close_idle_background_inactive_color: [u8; 4],
2587    pub maximize_hovered_background_inactive_color: [u8; 4],
2588    pub minimize_hovered_background_inactive_color: [u8; 4],
2589    pub close_hovered_background_inactive_color: [u8; 4],
2590    pub maximize_disabled_background_inactive_color: [u8; 4],
2591    pub minimize_disabled_background_inactive_color: [u8; 4],
2592    pub close_disabled_background_inactive_color: [u8; 4],
2593    pub maximize_idle_foreground_active_color: [u8; 4],
2594    pub minimize_idle_foreground_active_color: [u8; 4],
2595    pub close_idle_foreground_active_color: [u8; 4],
2596    pub maximize_hovered_foreground_active_color: [u8; 4],
2597    pub minimize_hovered_foreground_active_color: [u8; 4],
2598    pub close_hovered_foreground_active_color: [u8; 4],
2599    pub maximize_disabled_foreground_active_color: [u8; 4],
2600    pub minimize_disabled_foreground_active_color: [u8; 4],
2601    pub close_disabled_foreground_active_color: [u8; 4],
2602    pub maximize_idle_background_active_color: [u8; 4],
2603    pub minimize_idle_background_active_color: [u8; 4],
2604    pub close_idle_background_active_color: [u8; 4],
2605    pub maximize_hovered_background_active_color: [u8; 4],
2606    pub minimize_hovered_background_active_color: [u8; 4],
2607    pub close_hovered_background_active_color: [u8; 4],
2608    pub maximize_disabled_background_active_color: [u8; 4],
2609    pub minimize_disabled_background_active_color: [u8; 4],
2610    pub close_disabled_background_active_color: [u8; 4],
2611    pub title_bar_font: AzString,
2612    pub title_bar_font_size: f32,
2613}
2614
2615#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
2616#[repr(C)]
2617pub struct WindowSize {
2618    /// Width and height of the window, in logical
2619    /// units (may not correspond to the physical on-screen size)
2620    pub dimensions: LogicalSize,
2621    /// Actual DPI value (default: 96)
2622    pub dpi: u32,
2623    /// Minimum dimensions of the window
2624    pub min_dimensions: OptionLogicalSize,
2625    /// Maximum dimensions of the window
2626    pub max_dimensions: OptionLogicalSize,
2627}
2628
2629impl WindowSize {
2630    pub fn get_layout_size(&self) -> LayoutSize {
2631        LayoutSize::new(
2632            libm::roundf(self.dimensions.width) as isize,
2633            libm::roundf(self.dimensions.height) as isize,
2634        )
2635    }
2636
2637    /// Get the actual logical size
2638    pub fn get_logical_size(&self) -> LogicalSize {
2639        self.dimensions
2640    }
2641
2642    pub fn get_physical_size(&self) -> PhysicalSize<u32> {
2643        self.dimensions.to_physical(self.get_hidpi_factor())
2644    }
2645
2646    pub fn get_hidpi_factor(&self) -> f32 {
2647        self.dpi as f32 / 96.0
2648    }
2649}
2650
2651impl Default for WindowSize {
2652    fn default() -> Self {
2653        Self {
2654            #[cfg(not(feature = "glow"))]
2655            dimensions: LogicalSize::new(640.0, 480.0),
2656            dpi: 96,
2657            min_dimensions: None.into(),
2658            max_dimensions: None.into(),
2659        }
2660    }
2661}
2662
2663impl Default for WindowState {
2664    fn default() -> Self {
2665        FullWindowState::default().into()
2666    }
2667}
2668
2669#[derive(Debug, Clone)]
2670#[repr(C)]
2671pub struct WindowCreateOptions {
2672    // Initial window state
2673    pub state: WindowState,
2674    /// If set, the first UI redraw will be called with a size of (0, 0) and the
2675    /// window size depends on the size of the overflowing UI. This is good for
2676    /// windows that do not want to take up unnecessary extra space
2677    pub size_to_content: bool,
2678    /// Renderer type: Hardware-with-software-fallback, pure software or pure hardware renderer?
2679    pub renderer: OptionRendererOptions,
2680    /// Override the default window theme (set to `None` to use the OS-provided theme)
2681    pub theme: OptionWindowTheme,
2682    /// Optional callback to run when the window has been created (runs only once on startup)
2683    pub create_callback: OptionCallback,
2684    /// If set to true, will hot-reload the UI every 200ms, useful in combination with
2685    /// `StyledDom::from_file()` to hot-reload the UI from a file while developing.
2686    pub hot_reload: bool,
2687}
2688
2689impl Default for WindowCreateOptions {
2690    fn default() -> Self {
2691        Self {
2692            state: WindowState::default(),
2693            size_to_content: false,
2694            renderer: OptionRendererOptions::None,
2695            theme: OptionWindowTheme::None,
2696            create_callback: OptionCallback::None,
2697            hot_reload: false,
2698        }
2699    }
2700}
2701
2702impl WindowCreateOptions {
2703    pub fn new(callback: LayoutCallbackType) -> Self {
2704        Self {
2705            state: WindowState::new(callback),
2706            ..WindowCreateOptions::default()
2707        }
2708    }
2709    pub fn renderer_types(&self) -> Vec<RendererType> {
2710        match self.renderer.into_option() {
2711            Some(s) => match s.hw_accel {
2712                HwAcceleration::DontCare => vec![RendererType::Hardware, RendererType::Software],
2713                HwAcceleration::Enabled => vec![RendererType::Hardware],
2714                HwAcceleration::Disabled => vec![RendererType::Software],
2715            },
2716            None => vec![RendererType::Hardware, RendererType::Software],
2717        }
2718    }
2719}
2720
2721#[repr(C)]
2722#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
2723pub enum RendererType {
2724    /// Force hardware rendering
2725    Hardware,
2726    /// Force software rendering
2727    Software,
2728}
2729
2730impl_option!(
2731    RendererType,
2732    OptionRendererType,
2733    [Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash]
2734);
2735
2736#[derive(Debug, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
2737pub enum UpdateFocusWarning {
2738    FocusInvalidDomId(DomId),
2739    FocusInvalidNodeId(NodeHierarchyItemId),
2740    CouldNotFindFocusNode(CssPath),
2741}
2742
2743impl ::core::fmt::Display for UpdateFocusWarning {
2744    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
2745        use self::UpdateFocusWarning::*;
2746        match self {
2747            FocusInvalidDomId(dom_id) => write!(f, "Focusing on DOM with invalid ID: {:?}", dom_id),
2748            FocusInvalidNodeId(node_id) => {
2749                write!(f, "Focusing on node with invalid ID: {}", node_id)
2750            }
2751            CouldNotFindFocusNode(css_path) => {
2752                write!(f, "Could not find focus node for path: {}", css_path)
2753            }
2754        }
2755    }
2756}
2757
2758#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
2759#[repr(C)]
2760pub struct LogicalRect {
2761    pub origin: LogicalPosition,
2762    pub size: LogicalSize,
2763}
2764
2765impl core::fmt::Debug for LogicalRect {
2766    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
2767        write!(f, "{} @ {}", self.size, self.origin)
2768    }
2769}
2770
2771impl core::fmt::Display for LogicalRect {
2772    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
2773        write!(f, "{} @ {}", self.size, self.origin)
2774    }
2775}
2776
2777impl LogicalRect {
2778    pub const fn zero() -> Self {
2779        Self::new(LogicalPosition::zero(), LogicalSize::zero())
2780    }
2781    pub const fn new(origin: LogicalPosition, size: LogicalSize) -> Self {
2782        Self { origin, size }
2783    }
2784
2785    #[inline]
2786    pub fn scale_for_dpi(&mut self, scale_factor: f32) {
2787        self.origin.x *= scale_factor;
2788        self.origin.y *= scale_factor;
2789        self.size.width *= scale_factor;
2790        self.size.height *= scale_factor;
2791    }
2792
2793    #[inline(always)]
2794    pub fn max_x(&self) -> f32 {
2795        self.origin.x + self.size.width
2796    }
2797    #[inline(always)]
2798    pub fn min_x(&self) -> f32 {
2799        self.origin.x
2800    }
2801    #[inline(always)]
2802    pub fn max_y(&self) -> f32 {
2803        self.origin.y + self.size.height
2804    }
2805    #[inline(always)]
2806    pub fn min_y(&self) -> f32 {
2807        self.origin.y
2808    }
2809
2810    /// Faster union for a Vec<LayoutRect>
2811    #[inline]
2812    pub fn union<I: Iterator<Item = Self>>(mut rects: I) -> Option<Self> {
2813        let first = rects.next()?;
2814
2815        let mut max_width = first.size.width;
2816        let mut max_height = first.size.height;
2817        let mut min_x = first.origin.x;
2818        let mut min_y = first.origin.y;
2819
2820        while let Some(Self {
2821            origin: LogicalPosition { x, y },
2822            size: LogicalSize { width, height },
2823        }) = rects.next()
2824        {
2825            let cur_lower_right_x = x + width;
2826            let cur_lower_right_y = y + height;
2827            max_width = max_width.max(cur_lower_right_x - min_x);
2828            max_height = max_height.max(cur_lower_right_y - min_y);
2829            min_x = min_x.min(x);
2830            min_y = min_y.min(y);
2831        }
2832
2833        Some(Self {
2834            origin: LogicalPosition { x: min_x, y: min_y },
2835            size: LogicalSize {
2836                width: max_width,
2837                height: max_height,
2838            },
2839        })
2840    }
2841
2842    /// Same as `contains()`, but returns the (x, y) offset of the hit point
2843    ///
2844    /// On a regular computer this function takes ~3.2ns to run
2845    #[inline]
2846    pub fn hit_test(&self, other: &LogicalPosition) -> Option<LogicalPosition> {
2847        let dx_left_edge = other.x - self.min_x();
2848        let dx_right_edge = self.max_x() - other.x;
2849        let dy_top_edge = other.y - self.min_y();
2850        let dy_bottom_edge = self.max_y() - other.y;
2851        if dx_left_edge > 0.0 && dx_right_edge > 0.0 && dy_top_edge > 0.0 && dy_bottom_edge > 0.0 {
2852            Some(LogicalPosition::new(dx_left_edge, dy_top_edge))
2853        } else {
2854            None
2855        }
2856    }
2857
2858    pub fn to_layout_rect(&self) -> LayoutRect {
2859        LayoutRect {
2860            origin: LayoutPoint::new(
2861                libm::roundf(self.origin.x) as isize,
2862                libm::roundf(self.origin.y) as isize,
2863            ),
2864            size: LayoutSize::new(
2865                libm::roundf(self.size.width) as isize,
2866                libm::roundf(self.size.height) as isize,
2867            ),
2868        }
2869    }
2870}
2871
2872impl_vec!(LogicalRect, LogicalRectVec, LogicalRectVecDestructor);
2873impl_vec_clone!(LogicalRect, LogicalRectVec, LogicalRectVecDestructor);
2874impl_vec_debug!(LogicalRect, LogicalRectVec);
2875impl_vec_partialeq!(LogicalRect, LogicalRectVec);
2876impl_vec_partialord!(LogicalRect, LogicalRectVec);
2877impl_vec_ord!(LogicalRect, LogicalRectVec);
2878impl_vec_hash!(LogicalRect, LogicalRectVec);
2879impl_vec_eq!(LogicalRect, LogicalRectVec);
2880
2881use core::ops::{AddAssign, SubAssign};
2882
2883#[derive(Default, Copy, Clone, PartialEq, PartialOrd)]
2884#[repr(C)]
2885pub struct LogicalPosition {
2886    pub x: f32,
2887    pub y: f32,
2888}
2889
2890impl LogicalPosition {
2891    pub fn scale_for_dpi(&mut self, scale_factor: f32) {
2892        self.x *= scale_factor;
2893        self.y *= scale_factor;
2894    }
2895}
2896
2897impl SubAssign<LogicalPosition> for LogicalPosition {
2898    fn sub_assign(&mut self, other: LogicalPosition) {
2899        self.x -= other.x;
2900        self.y -= other.y;
2901    }
2902}
2903
2904impl AddAssign<LogicalPosition> for LogicalPosition {
2905    fn add_assign(&mut self, other: LogicalPosition) {
2906        self.x += other.x;
2907        self.y += other.y;
2908    }
2909}
2910
2911impl core::fmt::Debug for LogicalPosition {
2912    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
2913        write!(f, "({}, {})", self.x, self.y)
2914    }
2915}
2916
2917impl core::fmt::Display for LogicalPosition {
2918    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
2919        write!(f, "({}, {})", self.x, self.y)
2920    }
2921}
2922
2923impl ops::Add for LogicalPosition {
2924    type Output = Self;
2925
2926    #[inline]
2927    fn add(self, other: Self) -> Self {
2928        Self {
2929            x: self.x + other.x,
2930            y: self.y + other.y,
2931        }
2932    }
2933}
2934
2935impl ops::Sub for LogicalPosition {
2936    type Output = Self;
2937
2938    #[inline]
2939    fn sub(self, other: Self) -> Self {
2940        Self {
2941            x: self.x - other.x,
2942            y: self.y - other.y,
2943        }
2944    }
2945}
2946
2947const DECIMAL_MULTIPLIER: f32 = 1000.0;
2948
2949impl_option!(
2950    LogicalPosition,
2951    OptionLogicalPosition,
2952    [Debug, Copy, Clone, PartialEq, PartialOrd]
2953);
2954
2955impl Ord for LogicalPosition {
2956    fn cmp(&self, other: &LogicalPosition) -> Ordering {
2957        let self_x = (self.x * DECIMAL_MULTIPLIER) as usize;
2958        let self_y = (self.y * DECIMAL_MULTIPLIER) as usize;
2959        let other_x = (other.x * DECIMAL_MULTIPLIER) as usize;
2960        let other_y = (other.y * DECIMAL_MULTIPLIER) as usize;
2961        self_x.cmp(&other_x).then(self_y.cmp(&other_y))
2962    }
2963}
2964
2965impl Eq for LogicalPosition {}
2966
2967impl Hash for LogicalPosition {
2968    fn hash<H>(&self, state: &mut H)
2969    where
2970        H: Hasher,
2971    {
2972        let self_x = (self.x * DECIMAL_MULTIPLIER) as usize;
2973        let self_y = (self.y * DECIMAL_MULTIPLIER) as usize;
2974        self_x.hash(state);
2975        self_y.hash(state);
2976    }
2977}
2978
2979#[derive(Default, Copy, Clone, PartialEq, PartialOrd)]
2980#[repr(C)]
2981pub struct LogicalSize {
2982    pub width: f32,
2983    pub height: f32,
2984}
2985
2986impl LogicalSize {
2987    pub fn scale_for_dpi(&mut self, scale_factor: f32) -> Self {
2988        self.width *= scale_factor;
2989        self.height *= scale_factor;
2990        *self
2991    }
2992}
2993
2994impl core::fmt::Debug for LogicalSize {
2995    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
2996        write!(f, "{}x{}", self.width, self.height)
2997    }
2998}
2999
3000impl core::fmt::Display for LogicalSize {
3001    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
3002        write!(f, "{}x{}", self.width, self.height)
3003    }
3004}
3005
3006impl_option!(
3007    LogicalSize,
3008    OptionLogicalSize,
3009    [Debug, Copy, Clone, PartialEq, PartialOrd]
3010);
3011
3012impl Ord for LogicalSize {
3013    fn cmp(&self, other: &LogicalSize) -> Ordering {
3014        let self_width = (self.width * DECIMAL_MULTIPLIER) as usize;
3015        let self_height = (self.height * DECIMAL_MULTIPLIER) as usize;
3016        let other_width = (other.width * DECIMAL_MULTIPLIER) as usize;
3017        let other_height = (other.height * DECIMAL_MULTIPLIER) as usize;
3018        self_width
3019            .cmp(&other_width)
3020            .then(self_height.cmp(&other_height))
3021    }
3022}
3023
3024impl Eq for LogicalSize {}
3025
3026impl Hash for LogicalSize {
3027    fn hash<H>(&self, state: &mut H)
3028    where
3029        H: Hasher,
3030    {
3031        let self_width = (self.width * DECIMAL_MULTIPLIER) as usize;
3032        let self_height = (self.height * DECIMAL_MULTIPLIER) as usize;
3033        self_width.hash(state);
3034        self_height.hash(state);
3035    }
3036}
3037
3038#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3039#[repr(C)]
3040pub struct PhysicalPosition<T> {
3041    pub x: T,
3042    pub y: T,
3043}
3044
3045impl<T: ::core::fmt::Display> ::core::fmt::Debug for PhysicalPosition<T> {
3046    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
3047        write!(f, "({}, {})", self.x, self.y)
3048    }
3049}
3050
3051pub type PhysicalPositionI32 = PhysicalPosition<i32>;
3052impl_option!(
3053    PhysicalPositionI32,
3054    OptionPhysicalPositionI32,
3055    [Debug, Copy, Clone, PartialEq, PartialOrd]
3056);
3057
3058#[derive(Ord, Hash, Eq, Copy, Clone, PartialEq, PartialOrd)]
3059#[repr(C)]
3060pub struct PhysicalSize<T> {
3061    pub width: T,
3062    pub height: T,
3063}
3064
3065impl<T: ::core::fmt::Display> ::core::fmt::Debug for PhysicalSize<T> {
3066    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
3067        write!(f, "{}x{}", self.width, self.height)
3068    }
3069}
3070
3071pub type PhysicalSizeU32 = PhysicalSize<u32>;
3072impl_option!(
3073    PhysicalSizeU32,
3074    OptionPhysicalSizeU32,
3075    [Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash]
3076);
3077pub type PhysicalSizeF32 = PhysicalSize<f32>;
3078impl_option!(
3079    PhysicalSizeF32,
3080    OptionPhysicalSizeF32,
3081    [Debug, Copy, Clone, PartialEq, PartialOrd]
3082);
3083
3084impl LogicalPosition {
3085    #[inline(always)]
3086    pub const fn new(x: f32, y: f32) -> Self {
3087        Self { x, y }
3088    }
3089    #[inline(always)]
3090    pub const fn zero() -> Self {
3091        Self::new(0.0, 0.0)
3092    }
3093    #[inline(always)]
3094    pub fn to_physical(self, hidpi_factor: f32) -> PhysicalPosition<u32> {
3095        PhysicalPosition {
3096            x: (self.x * hidpi_factor) as u32,
3097            y: (self.y * hidpi_factor) as u32,
3098        }
3099    }
3100}
3101
3102impl<T> PhysicalPosition<T> {
3103    #[inline(always)]
3104    pub const fn new(x: T, y: T) -> Self {
3105        Self { x, y }
3106    }
3107}
3108
3109impl PhysicalPosition<i32> {
3110    #[inline(always)]
3111    pub const fn zero() -> Self {
3112        Self::new(0, 0)
3113    }
3114    #[inline(always)]
3115    pub fn to_logical(self, hidpi_factor: f32) -> LogicalPosition {
3116        LogicalPosition {
3117            x: self.x as f32 / hidpi_factor,
3118            y: self.y as f32 / hidpi_factor,
3119        }
3120    }
3121}
3122
3123impl PhysicalPosition<f64> {
3124    #[inline(always)]
3125    pub const fn zero() -> Self {
3126        Self::new(0.0, 0.0)
3127    }
3128    #[inline(always)]
3129    pub fn to_logical(self, hidpi_factor: f32) -> LogicalPosition {
3130        LogicalPosition {
3131            x: self.x as f32 / hidpi_factor,
3132            y: self.y as f32 / hidpi_factor,
3133        }
3134    }
3135}
3136
3137impl LogicalSize {
3138    #[inline(always)]
3139    pub const fn new(width: f32, height: f32) -> Self {
3140        Self { width, height }
3141    }
3142    #[inline(always)]
3143    pub const fn zero() -> Self {
3144        Self::new(0.0, 0.0)
3145    }
3146    #[inline(always)]
3147    pub fn to_physical(self, hidpi_factor: f32) -> PhysicalSize<u32> {
3148        PhysicalSize {
3149            width: (self.width * hidpi_factor) as u32,
3150            height: (self.height * hidpi_factor) as u32,
3151        }
3152    }
3153}
3154
3155impl<T> PhysicalSize<T> {
3156    #[inline(always)]
3157    pub const fn new(width: T, height: T) -> Self {
3158        Self { width, height }
3159    }
3160}
3161
3162impl PhysicalSize<u32> {
3163    #[inline(always)]
3164    pub const fn zero() -> Self {
3165        Self::new(0, 0)
3166    }
3167    #[inline(always)]
3168    pub fn to_logical(self, hidpi_factor: f32) -> LogicalSize {
3169        LogicalSize {
3170            width: self.width as f32 / hidpi_factor,
3171            height: self.height as f32 / hidpi_factor,
3172        }
3173    }
3174}
3175
3176/// Utility function for easier creation of a keymap - i.e. `[vec![Ctrl, S], my_function]`
3177#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3178#[repr(C, u8)]
3179pub enum AcceleratorKey {
3180    Ctrl,
3181    Alt,
3182    Shift,
3183    Key(VirtualKeyCode),
3184}
3185
3186impl AcceleratorKey {
3187    /// Checks if the current keyboard state contains the given char or modifier,
3188    /// i.e. if the keyboard state currently has the shift key pressed and the
3189    /// accelerator key is `Shift`, evaluates to true, otherwise to false.
3190    pub fn matches(&self, keyboard_state: &KeyboardState) -> bool {
3191        use self::AcceleratorKey::*;
3192        match self {
3193            Ctrl => keyboard_state.ctrl_down(),
3194            Alt => keyboard_state.alt_down(),
3195            Shift => keyboard_state.shift_down(),
3196            Key(k) => keyboard_state.is_key_down(*k),
3197        }
3198    }
3199}
3200
3201/// Symbolic name for a keyboard key, does NOT take the keyboard locale into account
3202#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
3203#[repr(C)]
3204pub enum VirtualKeyCode {
3205    Key1,
3206    Key2,
3207    Key3,
3208    Key4,
3209    Key5,
3210    Key6,
3211    Key7,
3212    Key8,
3213    Key9,
3214    Key0,
3215    A,
3216    B,
3217    C,
3218    D,
3219    E,
3220    F,
3221    G,
3222    H,
3223    I,
3224    J,
3225    K,
3226    L,
3227    M,
3228    N,
3229    O,
3230    P,
3231    Q,
3232    R,
3233    S,
3234    T,
3235    U,
3236    V,
3237    W,
3238    X,
3239    Y,
3240    Z,
3241    Escape,
3242    F1,
3243    F2,
3244    F3,
3245    F4,
3246    F5,
3247    F6,
3248    F7,
3249    F8,
3250    F9,
3251    F10,
3252    F11,
3253    F12,
3254    F13,
3255    F14,
3256    F15,
3257    F16,
3258    F17,
3259    F18,
3260    F19,
3261    F20,
3262    F21,
3263    F22,
3264    F23,
3265    F24,
3266    Snapshot,
3267    Scroll,
3268    Pause,
3269    Insert,
3270    Home,
3271    Delete,
3272    End,
3273    PageDown,
3274    PageUp,
3275    Left,
3276    Up,
3277    Right,
3278    Down,
3279    Back,
3280    Return,
3281    Space,
3282    Compose,
3283    Caret,
3284    Numlock,
3285    Numpad0,
3286    Numpad1,
3287    Numpad2,
3288    Numpad3,
3289    Numpad4,
3290    Numpad5,
3291    Numpad6,
3292    Numpad7,
3293    Numpad8,
3294    Numpad9,
3295    NumpadAdd,
3296    NumpadDivide,
3297    NumpadDecimal,
3298    NumpadComma,
3299    NumpadEnter,
3300    NumpadEquals,
3301    NumpadMultiply,
3302    NumpadSubtract,
3303    AbntC1,
3304    AbntC2,
3305    Apostrophe,
3306    Apps,
3307    Asterisk,
3308    At,
3309    Ax,
3310    Backslash,
3311    Calculator,
3312    Capital,
3313    Colon,
3314    Comma,
3315    Convert,
3316    Equals,
3317    Grave,
3318    Kana,
3319    Kanji,
3320    LAlt,
3321    LBracket,
3322    LControl,
3323    LShift,
3324    LWin,
3325    Mail,
3326    MediaSelect,
3327    MediaStop,
3328    Minus,
3329    Mute,
3330    MyComputer,
3331    NavigateForward,
3332    NavigateBackward,
3333    NextTrack,
3334    NoConvert,
3335    OEM102,
3336    Period,
3337    PlayPause,
3338    Plus,
3339    Power,
3340    PrevTrack,
3341    RAlt,
3342    RBracket,
3343    RControl,
3344    RShift,
3345    RWin,
3346    Semicolon,
3347    Slash,
3348    Sleep,
3349    Stop,
3350    Sysrq,
3351    Tab,
3352    Underline,
3353    Unlabeled,
3354    VolumeDown,
3355    VolumeUp,
3356    Wake,
3357    WebBack,
3358    WebFavorites,
3359    WebForward,
3360    WebHome,
3361    WebRefresh,
3362    WebSearch,
3363    WebStop,
3364    Yen,
3365    Copy,
3366    Paste,
3367    Cut,
3368}
3369
3370impl VirtualKeyCode {
3371    pub fn get_lowercase(&self) -> Option<char> {
3372        use self::VirtualKeyCode::*;
3373        match self {
3374            A => Some('a'),
3375            B => Some('b'),
3376            C => Some('c'),
3377            D => Some('d'),
3378            E => Some('e'),
3379            F => Some('f'),
3380            G => Some('g'),
3381            H => Some('h'),
3382            I => Some('i'),
3383            J => Some('j'),
3384            K => Some('k'),
3385            L => Some('l'),
3386            M => Some('m'),
3387            N => Some('n'),
3388            O => Some('o'),
3389            P => Some('p'),
3390            Q => Some('q'),
3391            R => Some('r'),
3392            S => Some('s'),
3393            T => Some('t'),
3394            U => Some('u'),
3395            V => Some('v'),
3396            W => Some('w'),
3397            X => Some('x'),
3398            Y => Some('y'),
3399            Z => Some('z'),
3400            Key0 | Numpad0 => Some('0'),
3401            Key1 | Numpad1 => Some('1'),
3402            Key2 | Numpad2 => Some('2'),
3403            Key3 | Numpad3 => Some('3'),
3404            Key4 | Numpad4 => Some('4'),
3405            Key5 | Numpad5 => Some('5'),
3406            Key6 | Numpad6 => Some('6'),
3407            Key7 | Numpad7 => Some('7'),
3408            Key8 | Numpad8 => Some('8'),
3409            Key9 | Numpad9 => Some('9'),
3410            Minus => Some('-'),
3411            Asterisk => Some('´'),
3412            At => Some('@'),
3413            Period => Some('.'),
3414            Semicolon => Some(';'),
3415            Slash => Some('/'),
3416            Caret => Some('^'),
3417            _ => None,
3418        }
3419    }
3420}
3421
3422/// 16x16x4 bytes icon
3423#[derive(Debug, Clone)]
3424#[repr(C)]
3425pub struct SmallWindowIconBytes {
3426    pub key: IconKey,
3427    pub rgba_bytes: U8Vec,
3428}
3429
3430/// 16x16x4 bytes icon
3431#[derive(Debug, Clone)]
3432#[repr(C)]
3433pub struct LargeWindowIconBytes {
3434    pub key: IconKey,
3435    pub rgba_bytes: U8Vec,
3436}
3437
3438// Window icon that usually appears in the top-left corner of the window
3439#[derive(Debug, Clone)]
3440#[repr(C, u8)]
3441pub enum WindowIcon {
3442    Small(SmallWindowIconBytes),
3443    /// 32x32x4 bytes icon
3444    Large(LargeWindowIconBytes),
3445}
3446
3447impl_option!(
3448    WindowIcon,
3449    OptionWindowIcon,
3450    copy = false,
3451    [Debug, Clone, PartialOrd, PartialEq, Eq, Hash, Ord]
3452);
3453
3454impl WindowIcon {
3455    pub fn get_key(&self) -> IconKey {
3456        match &self {
3457            WindowIcon::Small(SmallWindowIconBytes { key, .. }) => *key,
3458            WindowIcon::Large(LargeWindowIconBytes { key, .. }) => *key,
3459        }
3460    }
3461}
3462// -- Only compare the IconKey (for WindowIcon and TaskBarIcon)
3463
3464impl PartialEq for WindowIcon {
3465    fn eq(&self, rhs: &Self) -> bool {
3466        self.get_key() == rhs.get_key()
3467    }
3468}
3469
3470impl PartialOrd for WindowIcon {
3471    fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
3472        Some((self.get_key()).cmp(&rhs.get_key()))
3473    }
3474}
3475
3476impl Eq for WindowIcon {}
3477
3478impl Ord for WindowIcon {
3479    fn cmp(&self, rhs: &Self) -> Ordering {
3480        (self.get_key()).cmp(&rhs.get_key())
3481    }
3482}
3483
3484impl Hash for WindowIcon {
3485    fn hash<H>(&self, state: &mut H)
3486    where
3487        H: Hasher,
3488    {
3489        self.get_key().hash(state);
3490    }
3491}
3492
3493/// 256x256x4 bytes window icon
3494#[derive(Debug, Clone)]
3495#[repr(C)]
3496pub struct TaskBarIcon {
3497    pub key: IconKey,
3498    pub rgba_bytes: U8Vec,
3499}
3500
3501impl_option!(
3502    TaskBarIcon,
3503    OptionTaskBarIcon,
3504    copy = false,
3505    [Debug, Clone, PartialOrd, PartialEq, Eq, Hash, Ord]
3506);
3507
3508impl PartialEq for TaskBarIcon {
3509    fn eq(&self, rhs: &Self) -> bool {
3510        self.key == rhs.key
3511    }
3512}
3513
3514impl PartialOrd for TaskBarIcon {
3515    fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
3516        Some((self.key).cmp(&rhs.key))
3517    }
3518}
3519
3520impl Eq for TaskBarIcon {}
3521
3522impl Ord for TaskBarIcon {
3523    fn cmp(&self, rhs: &Self) -> Ordering {
3524        (self.key).cmp(&rhs.key)
3525    }
3526}
3527
3528impl Hash for TaskBarIcon {
3529    fn hash<H>(&self, state: &mut H)
3530    where
3531        H: Hasher,
3532    {
3533        self.key.hash(state);
3534    }
3535}
3536
3537/// Menu struct (context menu, dropdown menu, context menu)
3538///
3539/// Modeled after the Windows API
3540#[derive(Debug, Default, Clone, PartialEq, PartialOrd, Hash, Eq, Ord)]
3541#[repr(C)]
3542pub struct Menu {
3543    pub items: MenuItemVec,
3544    pub position: MenuPopupPosition,
3545    pub context_mouse_btn: ContextMenuMouseButton,
3546}
3547
3548impl_option!(
3549    Menu,
3550    OptionMenu,
3551    copy = false,
3552    [Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord]
3553);
3554
3555impl Menu {
3556    pub fn new(items: MenuItemVec) -> Self {
3557        Self {
3558            items,
3559            position: MenuPopupPosition::AutoCursor,
3560            context_mouse_btn: ContextMenuMouseButton::Right,
3561        }
3562    }
3563}
3564
3565#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Hash, Eq, Ord)]
3566#[repr(C)]
3567pub enum ContextMenuMouseButton {
3568    Right,
3569    Middle,
3570    Left,
3571}
3572
3573impl Default for ContextMenuMouseButton {
3574    fn default() -> Self {
3575        ContextMenuMouseButton::Right
3576    }
3577}
3578
3579impl Menu {
3580    pub fn swap_with_default(&mut self) -> Self {
3581        let mut new = Self::default();
3582        core::mem::swap(&mut new, self);
3583        new
3584    }
3585}
3586
3587/// Position of where the menu should popup on the screen
3588///
3589/// Ignored for application-level menus
3590#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Hash, Eq, Ord)]
3591#[repr(C)]
3592pub enum MenuPopupPosition {
3593    // relative to cursor
3594    BottomLeftOfCursor,
3595    BottomRightOfCursor,
3596    TopLeftOfCursor,
3597    TopRightOfCursor,
3598
3599    // relative to the rect that was clicked on
3600    BottomOfHitRect,
3601    LeftOfHitRect,
3602    TopOfHitRect,
3603    RightOfHitRect,
3604
3605    // calculate the position based on how much space
3606    // is available for the context menu to either side
3607    // of the screen
3608    AutoCursor,
3609    AutoHitRect,
3610}
3611
3612impl Default for MenuPopupPosition {
3613    fn default() -> Self {
3614        Self::AutoCursor
3615    }
3616}
3617
3618impl Menu {
3619    pub fn get_hash(&self) -> u64 {
3620        use highway::{HighwayHash, HighwayHasher, Key};
3621        let mut hasher = HighwayHasher::new(Key([0; 4]));
3622        self.hash(&mut hasher);
3623        hasher.finalize64()
3624    }
3625}
3626
3627#[derive(Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord)]
3628#[repr(C, u8)]
3629pub enum MenuItem {
3630    /// Regular menu item
3631    String(StringMenuItem),
3632    /// Separator line, only rendered when the direction is vertical
3633    Separator,
3634    /// Breaks the menu item into separate lines if laid out horizontally
3635    BreakLine,
3636}
3637
3638impl_vec!(MenuItem, MenuItemVec, MenuItemVecDestructor);
3639impl_vec_clone!(MenuItem, MenuItemVec, MenuItemVecDestructor);
3640impl_vec_debug!(MenuItem, MenuItemVec);
3641impl_vec_partialeq!(MenuItem, MenuItemVec);
3642impl_vec_partialord!(MenuItem, MenuItemVec);
3643impl_vec_hash!(MenuItem, MenuItemVec);
3644impl_vec_eq!(MenuItem, MenuItemVec);
3645impl_vec_ord!(MenuItem, MenuItemVec);
3646
3647#[derive(Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord)]
3648#[repr(C)]
3649pub struct StringMenuItem {
3650    /// Label of the menu
3651    pub label: AzString,
3652    /// Optional accelerator combination
3653    /// (ex. "CTRL + X" = [VirtualKeyCode::Ctrl, VirtualKeyCode::X]) for keyboard shortcut
3654    pub accelerator: OptionVirtualKeyCodeCombo,
3655    /// Optional callback to call
3656    pub callback: OptionMenuCallback,
3657    /// State (normal, greyed, disabled)
3658    pub state: MenuItemState,
3659    /// Optional icon for the menu entry
3660    pub icon: OptionMenuItemIcon,
3661    /// Sub-menus of this item (separators and line-breaks can't have sub-menus)
3662    pub children: MenuItemVec,
3663}
3664
3665impl StringMenuItem {
3666    pub fn new(label: AzString) -> Self {
3667        StringMenuItem {
3668            label,
3669            accelerator: None.into(),
3670            callback: None.into(),
3671            state: MenuItemState::Normal,
3672            icon: None.into(),
3673            children: MenuItemVec::from_const_slice(&[]),
3674        }
3675    }
3676
3677    pub fn swap_with_default(&mut self) -> Self {
3678        let mut default = Self {
3679            label: AzString::from_const_str(""),
3680            accelerator: None.into(),
3681            callback: None.into(),
3682            state: MenuItemState::Normal,
3683            icon: None.into(),
3684            children: Vec::new().into(),
3685        };
3686        core::mem::swap(&mut default, self);
3687        default
3688    }
3689
3690    pub fn with_children(mut self, children: MenuItemVec) -> Self {
3691        self.children = children;
3692        self
3693    }
3694
3695    pub fn with_callback(mut self, data: RefAny, callback: CallbackType) -> Self {
3696        self.callback = Some(MenuCallback {
3697            data,
3698            callback: Callback { cb: callback },
3699        })
3700        .into();
3701        self
3702    }
3703}
3704
3705#[derive(Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord)]
3706#[repr(C)]
3707pub struct VirtualKeyCodeCombo {
3708    pub keys: VirtualKeyCodeVec,
3709}
3710
3711impl_option!(
3712    VirtualKeyCodeCombo,
3713    OptionVirtualKeyCodeCombo,
3714    copy = false,
3715    [Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord]
3716);
3717
3718/// Menu callback: What data / function pointer should
3719/// be called when the menu item is clicked?
3720#[derive(Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord)]
3721#[repr(C)]
3722pub struct MenuCallback {
3723    pub callback: Callback,
3724    pub data: RefAny,
3725}
3726
3727impl_option!(
3728    MenuCallback,
3729    OptionMenuCallback,
3730    copy = false,
3731    [Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord]
3732);
3733
3734#[derive(Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord)]
3735#[repr(C, u8)]
3736pub enum MenuItemIcon {
3737    /// Menu item shows a checkbox (either checked or not)
3738    Checkbox(bool),
3739    /// Menu item shows a custom image, usually in 16x16 format
3740    Image(ImageRef),
3741}
3742
3743impl_option!(
3744    MenuItemIcon,
3745    OptionMenuItemIcon,
3746    copy = false,
3747    [Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord]
3748);
3749
3750/// Describes the state of the menu
3751#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Hash, Eq, Ord)]
3752#[repr(C)]
3753pub enum MenuItemState {
3754    /// Normal menu item (default)
3755    Normal,
3756    /// Menu item is greyed out and clicking it does nothing
3757    Greyed,
3758    /// Menu item is disabled, but NOT greyed out
3759    Disabled,
3760}