1
//! Window configuration types, input state, and platform-specific options.
2
//!
3
//! This module defines the core types used by the windowing system:
4
//!
5
//! - **Window configuration**: [`WindowSize`], [`WindowFlags`], [`WindowPosition`],
6
//!   [`RendererOptions`], [`PlatformSpecificOptions`]
7
//! - **Input state**: [`KeyboardState`], [`MouseState`], [`TouchState`], [`CursorPosition`]
8
//! - **Monitor/display info**: [`Monitor`], [`MonitorId`], [`VideoMode`]
9
//! - **Virtual key codes**: [`VirtualKeyCode`], [`ScanCode`]
10
//! - **Window icons**: [`WindowIcon`], [`TaskBarIcon`]
11
//! - **Platform options**: [`WindowsWindowOptions`], [`LinuxWindowOptions`],
12
//!   [`MacWindowOptions`], [`WasmWindowOptions`]
13
//!
14
//! These types are consumed by the platform shell backends in
15
//! `dll/src/desktop/shell2/{windows,macos,linux}/` and by
16
//! `layout/src/window_state.rs` for state management.
17

            
18
#[cfg(not(feature = "std"))]
19
use alloc::string::{String, ToString};
20
use alloc::{
21
    boxed::Box,
22
    collections::{btree_map::BTreeMap, btree_set::BTreeSet},
23
    vec::Vec,
24
};
25
use core::{
26
    cmp::Ordering,
27
    ffi::c_void,
28
    hash::{Hash, Hasher},
29
    ops,
30
    sync::atomic::{AtomicI64, AtomicUsize, Ordering as AtomicOrdering},
31
};
32

            
33
use azul_css::{
34
    css::CssPath,
35
    props::{
36
        basic::{ColorU, FloatValue, LayoutPoint, LayoutRect, LayoutSize},
37
        property::CssProperty,
38
    },
39
    AzString, LayoutDebugMessage, OptionF32, OptionI32, OptionString, OptionU32, U8Vec,
40
};
41
use rust_fontconfig::FcFontCache;
42

            
43
use crate::{
44
    callbacks::{LayoutCallback, LayoutCallbackType, Update},
45
    dom::{DomId, DomNodeId, NodeHierarchy},
46
    geom::{
47
        LogicalPosition, LogicalRect, LogicalSize, OptionLogicalSize, PhysicalPositionI32,
48
        PhysicalSize,
49
    },
50
    gl::OptionGlContextPtr,
51
    hit_test::{ExternalScrollId, OverflowingScrollNode},
52
    id::{NodeDataContainer, NodeId},
53
    refany::OptionRefAny,
54
    resources::{
55
        DpiScaleFactor, Epoch, GlTextureCache, IdNamespace, ImageCache, ImageMask, ImageRef,
56
        RendererResources, ResourceUpdate,
57
    },
58
    selection::SelectionState,
59
    styled_dom::NodeHierarchyItemId,
60
    task::{Instant, ThreadId, TimerId},
61
    FastBTreeSet, OrderedMap,
62
};
63

            
64
pub const DEFAULT_TITLE: &str = "Azul App";
65

            
66
static LAST_WINDOW_ID: AtomicI64 = AtomicI64::new(0);
67

            
68
/// Unique identifier for a window, auto-assigned via atomic counter.
69
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
70
#[repr(transparent)]
71
pub struct WindowId {
72
    pub id: i64,
73
}
74

            
75
impl Default for WindowId {
76
2
    fn default() -> Self {
77
2
        Self::new()
78
2
    }
79
}
80

            
81
impl WindowId {
82
1003
    pub fn new() -> Self {
83
1003
        Self {
84
1003
            id: LAST_WINDOW_ID.fetch_add(1, AtomicOrdering::SeqCst),
85
1003
        }
86
1003
    }
87
}
88

            
89
static LAST_ICON_KEY: AtomicUsize = AtomicUsize::new(0);
90

            
91
/// Key that is used for checking whether a window icon has changed -
92
/// this way azul doesn't need to diff the actual bytes, just the icon key.
93
/// Use `IconKey::new()` to generate a new, unique key
94
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
95
#[repr(C)]
96
pub struct IconKey {
97
    icon_id: usize,
98
}
99

            
100
impl Default for IconKey {
101
2
    fn default() -> Self {
102
2
        Self::new()
103
2
    }
104
}
105

            
106
impl IconKey {
107
1007
    pub fn new() -> Self {
108
1007
        Self {
109
1007
            icon_id: LAST_ICON_KEY.fetch_add(1, AtomicOrdering::SeqCst),
110
1007
        }
111
1007
    }
112
}
113

            
114
#[repr(C)]
115
#[derive(PartialEq, Copy, Clone, Debug, PartialOrd, Ord, Eq, Hash)]
116
pub struct RendererOptions {
117
    pub vsync: Vsync,
118
    pub srgb: Srgb,
119
    pub hw_accel: HwAcceleration,
120
}
121

            
122
impl_option!(
123
    RendererOptions,
124
    OptionRendererOptions,
125
    [PartialEq, Copy, Clone, Debug, PartialOrd, Ord, Eq, Hash]
126
);
127

            
128
impl Default for RendererOptions {
129
77875
    fn default() -> Self {
130
77875
        Self {
131
77875
            vsync: Vsync::Enabled,
132
77875
            srgb: Srgb::Disabled,
133
77875
            // DontCare defers the choice to AZ_BACKEND / the desktop default,
134
77875
            // which is now CPU (software) rendering on all platforms — matching
135
77875
            // what the headless e2e tests render. GPU is re-selectable via
136
77875
            // AZ_BACKEND=gpu / AZ_BACKEND=auto or HwAcceleration::Enabled.
137
77875
            hw_accel: HwAcceleration::DontCare,
138
77875
        }
139
77875
    }
140
}
141

            
142
impl RendererOptions {
143
54
    #[must_use] pub const fn new(vsync: Vsync, srgb: Srgb, hw_accel: HwAcceleration) -> Self {
144
54
        Self {
145
54
            vsync,
146
54
            srgb,
147
54
            hw_accel,
148
54
        }
149
54
    }
150
}
151

            
152
#[repr(C)]
153
#[derive(PartialEq, Copy, Clone, Debug, PartialOrd, Ord, Eq, Hash)]
154
pub enum Vsync {
155
    Enabled,
156
    Disabled,
157
    DontCare,
158
}
159

            
160
impl Vsync {
161
4
    #[must_use] pub const fn is_enabled(&self) -> bool {
162
4
        matches!(self, Self::Enabled)
163
4
    }
164
}
165

            
166
#[repr(C)]
167
#[derive(PartialEq, Copy, Clone, Debug, PartialOrd, Ord, Eq, Hash)]
168
pub enum Srgb {
169
    Enabled,
170
    Disabled,
171
    DontCare,
172
}
173
impl Srgb {
174
4
    #[must_use] pub const fn is_enabled(&self) -> bool {
175
4
        matches!(self, Self::Enabled)
176
4
    }
177
}
178

            
179
#[repr(C)]
180
#[derive(PartialEq, Copy, Clone, Debug, PartialOrd, Ord, Eq, Hash)]
181
pub enum HwAcceleration {
182
    Enabled,
183
    Disabled,
184
    DontCare,
185
}
186
impl HwAcceleration {
187
4
    #[must_use] pub const fn is_enabled(&self) -> bool {
188
4
        matches!(self, Self::Enabled)
189
4
    }
190
}
191

            
192
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
193
#[repr(C, u8)]
194
pub enum RawWindowHandle {
195
    IOS(IOSHandle),
196
    MacOS(MacOSHandle),
197
    Xlib(XlibHandle),
198
    Xcb(XcbHandle),
199
    Wayland(WaylandHandle),
200
    Windows(WindowsHandle),
201
    Web(WebHandle),
202
    Android(AndroidHandle),
203
    Unsupported,
204
}
205

            
206
// SAFETY: RawWindowHandle contains raw pointers that are only used as opaque
207
// identifiers for platform window handles. The handle values are not
208
// dereferenced across threads; they are passed to platform APIs on the main thread.
209
unsafe impl Send for RawWindowHandle {}
210

            
211
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
212
#[repr(C)]
213
pub struct IOSHandle {
214
    pub ui_window: *mut c_void,
215
    pub ui_view: *mut c_void,
216
    pub ui_view_controller: *mut c_void,
217
}
218

            
219
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
220
#[repr(C)]
221
pub struct MacOSHandle {
222
    pub ns_window: *mut c_void,
223
    pub ns_view: *mut c_void,
224
}
225

            
226
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
227
#[repr(C)]
228
pub struct XlibHandle {
229
    /// An Xlib Window
230
    pub window: u64,
231
    pub display: *mut c_void,
232
}
233

            
234
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
235
#[repr(C)]
236
pub struct XcbHandle {
237
    /// An X11 `xcb_window_t`.
238
    pub window: u32,
239
    /// A pointer to an X server `xcb_connection_t`.
240
    pub connection: *mut c_void,
241
}
242

            
243
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
244
#[repr(C)]
245
pub struct WaylandHandle {
246
    /// A pointer to a `wl_surface`
247
    pub surface: *mut c_void,
248
    /// A pointer to a `wl_display`.
249
    pub display: *mut c_void,
250
}
251

            
252
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
253
#[repr(C)]
254
pub struct WindowsHandle {
255
    /// A Win32 HWND handle.
256
    pub hwnd: *mut c_void,
257
    /// The HINSTANCE associated with this type's HWND.
258
    pub hinstance: *mut c_void,
259
}
260

            
261
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
262
#[repr(C)]
263
pub struct WebHandle {
264
    /// An ID value inserted into the data attributes of the canvas element as 'raw-handle'
265
    ///
266
    /// When accessing from JS, the attribute will automatically be called rawHandle. Each canvas
267
    /// created by the windowing system should be assigned their own unique ID.
268
    /// 0 should be reserved for invalid / null IDs.
269
    pub id: u32,
270
}
271

            
272
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
273
#[repr(C)]
274
pub struct AndroidHandle {
275
    /// A pointer to an `ANativeWindow`.
276
    pub a_native_window: *mut c_void,
277
}
278

            
279
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
280
#[repr(C)]
281
#[derive(Default)]
282
pub enum MouseCursorType {
283
    #[default]
284
    Default,
285
    Crosshair,
286
    Hand,
287
    Arrow,
288
    Move,
289
    Text,
290
    Wait,
291
    Help,
292
    Progress,
293
    NotAllowed,
294
    ContextMenu,
295
    Cell,
296
    VerticalText,
297
    Alias,
298
    Copy,
299
    NoDrop,
300
    Grab,
301
    Grabbing,
302
    AllScroll,
303
    ZoomIn,
304
    ZoomOut,
305
    EResize,
306
    NResize,
307
    NeResize,
308
    NwResize,
309
    SResize,
310
    SeResize,
311
    SwResize,
312
    WResize,
313
    EwResize,
314
    NsResize,
315
    NeswResize,
316
    NwseResize,
317
    ColResize,
318
    RowResize,
319
}
320

            
321

            
322
/// Hardware-dependent keyboard scan code.
323
pub type ScanCode = u32;
324

            
325
/// Determines which keys are pressed currently (modifiers, etc.)
326
#[derive(Default, Debug, Clone, PartialEq, Eq)]
327
#[repr(C)]
328
pub struct KeyboardState {
329
    /// Currently pressed virtual keycode - **DO NOT USE THIS FOR TEXT INPUT**.
330
    ///
331
    /// For text input, use the `text_input` parameter in callbacks.
332
    /// For example entering `à` will fire a `VirtualKeyCode::Grave`, then `VirtualKeyCode::A`,
333
    /// so to correctly combine characters, the framework handles text composition internally.
334
    pub current_virtual_keycode: OptionVirtualKeyCode,
335
    /// Currently pressed virtual keycodes (READONLY) - it can happen that more than one key is
336
    /// pressed
337
    ///
338
    /// This is essentially an "extension" of `current_scancodes` - `current_keys` stores the
339
    /// characters, but what if the pressed key is not a character (such as `ArrowRight` or
340
    /// `PgUp`)?
341
    ///
342
    /// Note that this can have an overlap, so pressing "a" on the keyboard will insert
343
    /// both a `VirtualKeyCode::A` into `current_virtual_keycodes` and text input will be handled
344
    /// by the framework automatically for contenteditable nodes.
345
    pub pressed_virtual_keycodes: VirtualKeyCodeVec,
346
    /// Same as `current_virtual_keycodes`, but the scancode identifies the physical key pressed,
347
    /// independent of the keyboard layout. The scancode does not change if the user adjusts the
348
    /// host's keyboard map. Use when the physical location of the key is more important than
349
    /// the key's host GUI semantics, such as for movement controls in a first-person game
350
    /// (German keyboard: Z key, UK keyboard: Y key, etc.)
351
    pub pressed_scancodes: ScanCodeVec,
352
}
353

            
354
impl KeyboardState {
355
112333
    #[must_use] pub fn shift_down(&self) -> bool {
356
112333
        self.is_key_down(VirtualKeyCode::LShift) || self.is_key_down(VirtualKeyCode::RShift)
357
112333
    }
358
92356
    #[must_use] pub fn ctrl_down(&self) -> bool {
359
92356
        self.is_key_down(VirtualKeyCode::LControl) || self.is_key_down(VirtualKeyCode::RControl)
360
92356
    }
361
92332
    #[must_use] pub fn alt_down(&self) -> bool {
362
92332
        self.is_key_down(VirtualKeyCode::LAlt) || self.is_key_down(VirtualKeyCode::RAlt)
363
92332
    }
364
1268
    #[must_use] pub fn super_down(&self) -> bool {
365
1268
        self.is_key_down(VirtualKeyCode::LWin) || self.is_key_down(VirtualKeyCode::RWin)
366
1268
    }
367
    /// The platform's PRIMARY shortcut modifier: Cmd (super) on macOS, Ctrl
368
    /// everywhere else (MWA-A2). Every standard editing shortcut
369
    /// (copy / cut / paste / select-all / undo / redo) keys off this —
370
    /// hardcoding `ctrl_down()` made Cmd+C/X/V/A/Z dead on macOS, where Cmd
371
    /// arrives as LWin/super.
372
16
    #[must_use] pub fn primary_down(&self) -> bool {
373
16
        if cfg!(target_os = "macos") {
374
            self.super_down()
375
        } else {
376
16
            self.ctrl_down()
377
        }
378
16
    }
379
476871
    #[must_use] pub fn is_key_down(&self, key: VirtualKeyCode) -> bool {
380
834488
        self.pressed_virtual_keycodes.iter().any(|k| *k == key)
381
476871
    }
382

            
383
    /// Returns `true` iff every entry of `chord` is currently active in this
384
    /// keyboard state. Used by accelerator/keymap registrations to evaluate
385
    /// shortcuts like `[Ctrl, Shift, Key(VirtualKeyCode::S)]`.
386
    ///
387
    /// An empty chord matches trivially.
388
8
    #[must_use] pub fn matches_accelerator(&self, chord: &[AcceleratorKey]) -> bool {
389
20011
        chord.iter().all(|a| a.matches(self))
390
8
    }
391
}
392

            
393
impl_option!(
394
    KeyboardState,
395
    OptionKeyboardState,
396
    copy = false,
397
    [Debug, Clone, PartialEq, Eq]
398
);
399

            
400
// char is not ABI-stable, use u32 instead
401
impl_option!(
402
    u32,
403
    OptionChar,
404
    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
405
);
406
impl_option!(
407
    VirtualKeyCode,
408
    OptionVirtualKeyCode,
409
    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
410
);
411

            
412
impl_vec!(VirtualKeyCode, VirtualKeyCodeVec, VirtualKeyCodeVecDestructor, VirtualKeyCodeVecDestructorType, VirtualKeyCodeVecSlice, OptionVirtualKeyCode);
413
impl_vec_debug!(VirtualKeyCode, VirtualKeyCodeVec);
414
impl_vec_partialord!(VirtualKeyCode, VirtualKeyCodeVec);
415
impl_vec_ord!(VirtualKeyCode, VirtualKeyCodeVec);
416
impl_vec_clone!(
417
    VirtualKeyCode,
418
    VirtualKeyCodeVec,
419
    VirtualKeyCodeVecDestructor
420
);
421
impl_vec_partialeq!(VirtualKeyCode, VirtualKeyCodeVec);
422
impl_vec_eq!(VirtualKeyCode, VirtualKeyCodeVec);
423
impl_vec_hash!(VirtualKeyCode, VirtualKeyCodeVec);
424
impl_vec_mut!(VirtualKeyCode, VirtualKeyCodeVec);
425

            
426
impl_vec_as_hashmap!(VirtualKeyCode, VirtualKeyCodeVec);
427

            
428
impl_vec!(ScanCode, ScanCodeVec, ScanCodeVecDestructor, ScanCodeVecDestructorType, ScanCodeVecSlice, OptionU32);
429
impl_vec_debug!(ScanCode, ScanCodeVec);
430
impl_vec_partialord!(ScanCode, ScanCodeVec);
431
impl_vec_ord!(ScanCode, ScanCodeVec);
432
impl_vec_clone!(ScanCode, ScanCodeVec, ScanCodeVecDestructor);
433
impl_vec_partialeq!(ScanCode, ScanCodeVec);
434
impl_vec_eq!(ScanCode, ScanCodeVec);
435
impl_vec_hash!(ScanCode, ScanCodeVec);
436
impl_vec_mut!(ScanCode, ScanCodeVec);
437

            
438
impl_vec_as_hashmap!(ScanCode, ScanCodeVec);
439

            
440
/// Mouse position, cursor type, user scroll input, etc.
441
#[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Eq)]
442
#[repr(C)]
443
pub struct MouseState {
444
    /// Current mouse cursor type, set to `None` if the cursor is hidden. (READWRITE)
445
    pub mouse_cursor_type: OptionMouseCursorType,
446
    /// Where is the mouse cursor currently? Set to `None` if the window is not focused.
447
    /// (READWRITE)
448
    pub cursor_position: CursorPosition,
449
    /// Is the mouse cursor locked to the current window (important for applications like games)?
450
    /// (READWRITE)
451
    pub is_cursor_locked: bool,
452
    /// Is the left mouse button down? (READONLY)
453
    pub left_down: bool,
454
    /// Is the right mouse button down? (READONLY)
455
    pub right_down: bool,
456
    /// Is the middle mouse button down? (READONLY)
457
    pub middle_down: bool,
458
}
459

            
460
impl MouseState {
461
15
    #[must_use] pub const fn matches(&self, context: &ContextMenuMouseButton) -> bool {
462
        use self::ContextMenuMouseButton::{Left, Right, Middle};
463
15
        match context {
464
5
            Left => self.left_down,
465
5
            Right => self.right_down,
466
5
            Middle => self.middle_down,
467
        }
468
15
    }
469
}
470

            
471
impl_option!(
472
    MouseState,
473
    OptionMouseState,
474
    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd]
475
);
476

            
477
impl_option!(
478
    MouseCursorType,
479
    OptionMouseCursorType,
480
    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
481
);
482

            
483
impl Default for MouseState {
484
77908
    fn default() -> Self {
485
77908
        Self {
486
77908
            mouse_cursor_type: Some(MouseCursorType::Default).into(),
487
77908
            cursor_position: CursorPosition::default(),
488
77908
            is_cursor_locked: false,
489
77908
            left_down: false,
490
77908
            right_down: false,
491
77908
            middle_down: false,
492
77908
        }
493
77908
    }
494
}
495

            
496
#[derive(Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord)]
497
#[repr(C)]
498
pub struct VirtualKeyCodeCombo {
499
    pub keys: VirtualKeyCodeVec,
500
}
501

            
502
impl_option!(
503
    VirtualKeyCodeCombo,
504
    OptionVirtualKeyCodeCombo,
505
    copy = false,
506
    [Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord]
507
);
508

            
509
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Hash, Eq, Ord)]
510
#[repr(C)]
511
#[derive(Default)]
512
pub enum ContextMenuMouseButton {
513
    #[default]
514
    Right,
515
    Middle,
516
    Left,
517
}
518

            
519

            
520
impl MouseState {
521
    /// Returns whether any mouse button (left, right or center) is currently held down
522
2325
    #[must_use] pub const fn mouse_down(&self) -> bool {
523
2325
        self.right_down || self.left_down || self.middle_down
524
2325
    }
525

            
526
    /// Snapshot the button-down flags as a `MouseButtonState` for drag tracking.
527
18
    #[must_use] pub const fn button_state(&self) -> crate::events::MouseButtonState {
528
18
        crate::events::MouseButtonState {
529
18
            left_down: self.left_down,
530
18
            right_down: self.right_down,
531
18
            middle_down: self.middle_down,
532
18
        }
533
18
    }
534
}
535

            
536
impl From<&MouseState> for crate::events::MouseButtonState {
537
9
    fn from(s: &MouseState) -> Self {
538
9
        s.button_state()
539
9
    }
540
}
541

            
542
impl crate::events::MouseButtonState {
543
    /// Returns true if any of the tracked buttons is held down.
544
11
    #[must_use] pub const fn any_down(&self) -> bool {
545
11
        self.left_down || self.right_down || self.middle_down
546
11
    }
547
}
548

            
549
/// Result of dispatching a scroll delta into the system scroll-handling pipeline.
550
///
551
/// Returned by [`process_system_scroll`]. Higher layers can use the
552
/// [`ScrollResult::remaining_delta`] to forward un-consumed scroll to a parent
553
/// container, and [`ScrollResult::hit_scrollbar`] to distinguish scrollbar-drag
554
/// scrolling from wheel-on-content scrolling for hit-testing purposes.
555
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd)]
556
#[repr(C)]
557
pub struct ScrollResult {
558
    /// Number of scrollable nodes whose offset was updated by this dispatch.
559
    pub scrolled_nodes: usize,
560
    /// Delta that could not be consumed (overscroll). May be forwarded to a parent.
561
    pub remaining_delta: LogicalPosition,
562
    /// `true` if the dispatch hit a native scrollbar (drag), `false` for wheel/touch.
563
    pub hit_scrollbar: bool,
564
}
565

            
566
/// Dispatch a system scroll event and return a [`ScrollResult`] describing what
567
/// happened.
568
///
569
/// This is the entry point used by headless integration tests and embedders that
570
/// drive scroll programmatically. The richer per-document scroll handling lives
571
/// in `LayoutWindow::process_scroll`; this helper packages a delta into a
572
/// `ScrollResult` for return to callers so the result type is observable from
573
/// the public API.
574
12
#[must_use] pub fn process_system_scroll(delta: LogicalPosition, hit_scrollbar: bool) -> ScrollResult {
575
12
    let consumed = delta.x != 0.0 || delta.y != 0.0;
576
12
    ScrollResult {
577
12
        scrolled_nodes: usize::from(consumed),
578
12
        remaining_delta: LogicalPosition::zero(),
579
12
        hit_scrollbar,
580
12
    }
581
12
}
582

            
583
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd)]
584
#[repr(C, u8)]
585
#[derive(Default)]
586
pub enum CursorPosition {
587
    OutOfWindow(LogicalPosition),
588
    #[default]
589
    Uninitialized,
590
    InWindow(LogicalPosition),
591
}
592

            
593

            
594
impl CursorPosition {
595
1809
    #[must_use] pub const fn get_position(&self) -> Option<LogicalPosition> {
596
1809
        match self {
597
627
            Self::InWindow(logical_pos) => Some(*logical_pos),
598
1182
            Self::OutOfWindow(_) | Self::Uninitialized => None,
599
        }
600
1809
    }
601

            
602
6
    #[must_use] pub const fn is_inside_window(&self) -> bool {
603
6
        self.get_position().is_some()
604
6
    }
605
}
606

            
607
/// Toggles webrender debug flags (will make stuff appear on
608
/// the screen that you might not want to - used for debugging purposes)
609
///
610
/// Every field here maps onto a `webrender::DebugFlags` bit except
611
/// `show_hit_test_areas`, which is azul's own overlay. Populate it from the
612
/// environment with [`DebugState::from_az_overlay_env`] — see that function for
613
/// the verb list and why the hit-test overlay is no longer `debug_assertions`-only.
614
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
615
#[repr(C)]
616
pub struct DebugState {
617
    /// Paint a translucent red rectangle over every hit-test area.
618
    ///
619
    /// azul's own overlay, not a webrender flag: the compositor draws it while
620
    /// emitting `DisplayListItem::HitTestArea`, so it shows exactly the regions
621
    /// the hit tester will actually consider — which is the question you have
622
    /// when a click does nothing, or lands on the wrong node.
623
    pub show_hit_test_areas: bool,
624
    pub profiler_dbg: bool,
625
    pub render_target_dbg: bool,
626
    pub texture_cache_dbg: bool,
627
    pub gpu_time_queries: bool,
628
    pub gpu_sample_queries: bool,
629
    pub disable_batching: bool,
630
    pub epochs: bool,
631
    pub echo_driver_messages: bool,
632
    pub show_overdraw: bool,
633
    pub gpu_cache_dbg: bool,
634
    pub texture_cache_dbg_clear_evicted: bool,
635
    pub picture_caching_dbg: bool,
636
    pub primitive_dbg: bool,
637
    pub zoom_dbg: bool,
638
    pub small_screen: bool,
639
    pub disable_opaque_pass: bool,
640
    pub disable_alpha_pass: bool,
641
    pub disable_clip_masks: bool,
642
    pub disable_text_prims: bool,
643
    pub disable_gradient_prims: bool,
644
    pub obscure_images: bool,
645
    pub glyph_flashing: bool,
646
    pub smart_profiler: bool,
647
    pub invalidation_dbg: bool,
648
    pub tile_cache_logging_dbg: bool,
649
    pub profiler_capture: bool,
650
    pub force_picture_invalidation: bool,
651
}
652

            
653
impl DebugState {
654
    /// Build a `DebugState` from the `AZ_OVERLAY` environment variable.
655
    ///
656
    /// `AZ_OVERLAY` is a comma-separated list of verbs, e.g.
657
    ///
658
    /// ```text
659
    /// AZ_OVERLAY=hit-test
660
    /// AZ_OVERLAY=hit-test,overdraw,profiler
661
    /// AZ_OVERLAY=list          # print the verbs and exit-code nothing
662
    /// ```
663
    ///
664
    /// WHY THIS EXISTS: the hit-test overlay used to be `#[cfg(debug_assertions)]`
665
    /// in the compositor, so a debug build painted every hit-test area red and a
666
    /// release build painted none, with no way to ask for either. That is a
667
    /// debug/release divergence in VISUAL OUTPUT — running hello-world showed a
668
    /// red window and the reasonable first guess was "this linked the wrong
669
    /// DLL". It was not. An overlay you cannot turn on when you need it, and
670
    /// cannot turn off when you do not, is worse than no overlay.
671
    ///
672
    /// Available in RELEASE builds too, deliberately: the moment you need to see
673
    /// hit-test regions or overdraw is usually on the build a user is running.
674
    ///
675
    /// Unknown verbs are reported and ignored rather than fatal — a typo in a
676
    /// debugging aid must not stop the app you are trying to debug.
677
    /// Reading the environment needs std; on `no_std` there is no environment to
678
    /// read, so the overlay is simply off. `from_overlay_spec` stays available
679
    /// everywhere, so a `no_std` embedder can still enable overlays explicitly.
680
    #[cfg(feature = "std")]
681
    #[must_use]
682
    pub fn from_az_overlay_env() -> Self {
683
        std::env::var("AZ_OVERLAY")
684
            .map_or_else(|_| Self::default(), |v| Self::from_overlay_spec(v.as_str()))
685
    }
686

            
687
    /// `no_std`: there is no environment, so no overlay.
688
    #[cfg(not(feature = "std"))]
689
    #[must_use]
690
    pub fn from_az_overlay_env() -> Self {
691
        Self::default()
692
    }
693

            
694
    /// The parser behind [`DebugState::from_az_overlay_env`], separated so it is
695
    /// testable without touching the process environment.
696
    #[must_use]
697
    pub fn from_overlay_spec(spec: &str) -> Self {
698
        let mut s = Self::default();
699
        for raw in spec.split(',') {
700
            let verb = raw.trim().to_ascii_lowercase();
701
            if verb.is_empty() {
702
                continue;
703
            }
704
            match verb.as_str() {
705
                // azul's own overlay.
706
                "hit-test" | "hittest" => s.show_hit_test_areas = true,
707
                // webrender flags, named for what they SHOW rather than for the
708
                // flag constant, because the constant names are not obvious.
709
                "profiler" => s.profiler_dbg = true,
710
                "smart-profiler" => s.smart_profiler = true,
711
                "overdraw" => s.show_overdraw = true,
712
                "render-targets" => s.render_target_dbg = true,
713
                "texture-cache" => s.texture_cache_dbg = true,
714
                "gpu-cache" => s.gpu_cache_dbg = true,
715
                "picture-caching" => s.picture_caching_dbg = true,
716
                "primitives" => s.primitive_dbg = true,
717
                "invalidation" => s.invalidation_dbg = true,
718
                "epochs" => s.epochs = true,
719
                "zoom" => s.zoom_dbg = true,
720
                "glyph-flashing" => s.glyph_flashing = true,
721
                "obscure-images" => s.obscure_images = true,
722
                "gpu-time" => s.gpu_time_queries = true,
723
                "gpu-samples" => s.gpu_sample_queries = true,
724
                "echo-driver" => s.echo_driver_messages = true,
725
                // Diagnostic switches that DISABLE a stage — for bisecting which
726
                // stage is responsible for a visual artefact.
727
                "no-batching" => s.disable_batching = true,
728
                "no-opaque-pass" => s.disable_opaque_pass = true,
729
                "no-alpha-pass" => s.disable_alpha_pass = true,
730
                "no-clip-masks" => s.disable_clip_masks = true,
731
                "no-text" => s.disable_text_prims = true,
732
                "no-gradients" => s.disable_gradient_prims = true,
733
                "all" => {
734
                    s.show_hit_test_areas = true;
735
                    s.profiler_dbg = true;
736
                    s.show_overdraw = true;
737
                    s.primitive_dbg = true;
738
                }
739
                other => {
740
                    // Not fatal: a typo in a debugging aid must not stop the app.
741
                    #[cfg(feature = "std")]
742
                    eprintln!(
743
                        "[azul] AZ_OVERLAY: unknown verb {other:?}. Known: hit-test, profiler, \
744
                         smart-profiler, overdraw, render-targets, texture-cache, gpu-cache, \
745
                         picture-caching, primitives, invalidation, epochs, zoom, glyph-flashing, \
746
                         obscure-images, gpu-time, gpu-samples, echo-driver, no-batching, \
747
                         no-opaque-pass, no-alpha-pass, no-clip-masks, no-text, no-gradients, all"
748
                    );
749
                }
750
            }
751
        }
752
        s
753
    }
754
}
755

            
756
#[derive(Debug, Default, Clone, PartialEq)]
757
#[repr(C)]
758
pub struct TouchState {
759
    /// Number of active touch points (kept in sync with `touch_points.len()`).
760
    pub num_touches: usize,
761
    /// Currently active touch points (one entry per finger / stylus).
762
    /// Backends update this on touch start / move / end events.
763
    pub touch_points: TouchPointVec,
764
}
765

            
766
/// Single touch point (finger, stylus, etc.)
767
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
768
#[repr(C)]
769
pub struct TouchPoint {
770
    /// Unique identifier for this touch point (persists across move events)
771
    pub id: u64,
772
    /// Current position of the touch point in logical coordinates
773
    pub position: LogicalPosition,
774
    /// Force/pressure of the touch (0.0 = no pressure, 1.0 = maximum pressure)
775
    /// Set to 0.5 if pressure is not available
776
    pub force: f32,
777
}
778

            
779
impl_option!(
780
    TouchPoint,
781
    OptionTouchPoint,
782
    [Debug, Copy, Clone, PartialEq, PartialOrd]
783
);
784

            
785
impl_vec!(TouchPoint, TouchPointVec, TouchPointVecDestructor, TouchPointVecDestructorType, TouchPointVecSlice, OptionTouchPoint);
786
impl_vec_debug!(TouchPoint, TouchPointVec);
787
impl_vec_clone!(TouchPoint, TouchPointVec, TouchPointVecDestructor);
788
impl_vec_partialeq!(TouchPoint, TouchPointVec);
789

            
790
/// State, size, etc of the window, for comparing to the last frame
791
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Hash, Ord, Eq)]
792
#[repr(C)]
793
#[derive(Default)]
794
pub enum WindowTheme {
795
    DarkMode,
796
    #[default]
797
    LightMode,
798
}
799

            
800

            
801
impl_option!(
802
    WindowTheme,
803
    OptionWindowTheme,
804
    [Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash]
805
);
806

            
807
/// Identifies a specific monitor/display
808
///
809
/// Contains both an index (for fast current-session lookup) and a stable hash
810
/// (for persistence across app restarts and monitor reconfigurations).
811
///
812
/// - `index`: Runtime index (0-based), may change if monitors are added/removed
813
/// - `hash`: Stable identifier based on monitor properties (name, size, position)
814
///
815
/// Applications can serialize `hash` to remember which monitor a window was on,
816
/// then search for matching hash on next launch, falling back to index or PRIMARY.
817
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
818
#[repr(C)]
819
pub struct MonitorId {
820
    /// Runtime index of the monitor (may change between sessions)
821
    pub index: usize,
822
    /// Stable hash of monitor properties (for persistence)
823
    pub hash: u64,
824
}
825

            
826
impl MonitorId {
827
    /// Primary/default monitor (index 0, hash 0)
828
    pub const PRIMARY: Self = Self { index: 0, hash: 0 };
829

            
830
    /// Create a `MonitorId` from index only (hash will be 0)
831
7
    #[must_use] pub const fn new(index: usize) -> Self {
832
7
        Self { index, hash: 0 }
833
7
    }
834

            
835
    /// Create a `MonitorId` from index and hash
836
14
    #[must_use] pub const fn from_index_and_hash(index: usize, hash: u64) -> Self {
837
14
        Self { index, hash }
838
14
    }
839

            
840
    /// Create a stable monitor ID from monitor properties
841
    ///
842
    /// Uses FNV-1a hash of: name + position + size
843
    /// This ensures the hash is stable across app restarts as long as
844
    /// the monitor configuration doesn't change significantly
845
29
    #[must_use] pub fn from_properties(
846
29
        index: usize,
847
29
        name: &str,
848
29
        position: LayoutPoint,
849
29
        size: LayoutSize,
850
29
    ) -> Self {
851
        use core::hash::{Hash, Hasher};
852

            
853
        // FNV-1a hash (simple, fast, good distribution)
854
        struct FnvHasher(u64);
855

            
856
        impl Hasher for FnvHasher {
857
174
            fn write(&mut self, bytes: &[u8]) {
858
                const FNV_PRIME: u64 = 0x0100_0000_01b3;
859
2001217
                for &byte in bytes {
860
2001043
                    self.0 ^= u64::from(byte);
861
2001043
                    self.0 = self.0.wrapping_mul(FNV_PRIME);
862
2001043
                }
863
174
            }
864

            
865
29
            fn finish(&self) -> u64 {
866
29
                self.0
867
29
            }
868
        }
869

            
870
        const FNV_OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
871
29
        let mut hasher = FnvHasher(FNV_OFFSET_BASIS);
872

            
873
        // Hash the monitor properties
874
29
        name.hash(&mut hasher);
875
29
        (position.x as i64).hash(&mut hasher);
876
29
        (position.y as i64).hash(&mut hasher);
877
29
        (size.width as i64).hash(&mut hasher);
878
29
        (size.height as i64).hash(&mut hasher);
879

            
880
29
        Self {
881
29
            index,
882
29
            hash: hasher.finish(),
883
29
        }
884
29
    }
885
}
886

            
887
impl_option!(
888
    MonitorId,
889
    OptionMonitorId,
890
    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
891
);
892

            
893
/// Complete information about a monitor/display
894
#[derive(Debug, PartialEq, PartialOrd, Clone)]
895
#[repr(C)]
896
pub struct Monitor {
897
    /// Unique identifier for this monitor (stable across frames)
898
    pub monitor_id: MonitorId,
899
    /// Human-readable name (e.g., "\\.\DISPLAY1", "HDMI-1", "Built-in Retina Display")
900
    pub monitor_name: OptionString,
901
    /// Physical size of the monitor in logical pixels
902
    pub size: LayoutSize,
903
    /// Position of the monitor in the virtual screen coordinate system
904
    pub position: LayoutPoint,
905
    /// DPI scale factor (1.0 = 96 DPI, 2.0 = 192 DPI for Retina)
906
    pub scale_factor: f64,
907
    /// Work area (monitor bounds minus taskbars/panels) in logical pixels
908
    pub work_area: LayoutRect,
909
    /// Available video modes for this monitor
910
    pub video_modes: VideoModeVec,
911
    /// Whether this is the primary/main monitor
912
    pub is_primary_monitor: bool,
913
}
914

            
915
impl_option!(
916
    Monitor,
917
    OptionMonitor,
918
    copy = false,
919
    [Debug, PartialEq, PartialOrd, Clone]
920
);
921

            
922
impl_vec!(Monitor, MonitorVec, MonitorVecDestructor, MonitorVecDestructorType, MonitorVecSlice, OptionMonitor);
923
impl_vec_debug!(Monitor, MonitorVec);
924
impl_vec_clone!(Monitor, MonitorVec, MonitorVecDestructor);
925
impl_vec_partialeq!(Monitor, MonitorVec);
926
impl_vec_partialord!(Monitor, MonitorVec);
927

            
928
impl Hash for Monitor {
929
    fn hash<H>(&self, state: &mut H)
930
    where
931
        H: Hasher,
932
    {
933
        self.monitor_id.hash(state);
934
    }
935
}
936

            
937
impl Default for Monitor {
938
3
    fn default() -> Self {
939
3
        Self {
940
3
            monitor_id: MonitorId::PRIMARY,
941
3
            monitor_name: OptionString::None,
942
3
            size: LayoutSize::zero(),
943
3
            position: LayoutPoint::zero(),
944
3
            scale_factor: 1.0,
945
3
            work_area: LayoutRect::zero(),
946
3
            video_modes: Vec::new().into(),
947
3
            is_primary_monitor: false,
948
3
        }
949
3
    }
950
}
951
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
952
#[repr(C)]
953
pub struct VideoMode {
954
    pub size: LayoutSize,
955
    pub bit_depth: u16,
956
    pub refresh_rate: u16,
957
}
958

            
959
impl_option!(
960
    VideoMode,
961
    OptionVideoMode,
962
    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
963
);
964

            
965
impl_vec!(VideoMode, VideoModeVec, VideoModeVecDestructor, VideoModeVecDestructorType, VideoModeVecSlice, OptionVideoMode);
966
impl_vec_clone!(VideoMode, VideoModeVec, VideoModeVecDestructor);
967
impl_vec_debug!(VideoMode, VideoModeVec);
968
impl_vec_partialeq!(VideoMode, VideoModeVec);
969
impl_vec_partialord!(VideoMode, VideoModeVec);
970

            
971
/// Position of the window on screen
972
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
973
#[repr(C, u8)]
974
#[derive(Default)]
975
pub enum WindowPosition {
976
    #[default]
977
    Uninitialized,
978
    /// Absolute position on the virtual screen (physical px). The default for
979
    /// top-level windows.
980
    Initialized(PhysicalPositionI32),
981
    /// Offset (physical px) from the PARENT window's top-left corner. Used by
982
    /// child windows (menus, dropdowns, popups) together with
983
    /// `WindowCreateOptions.parent_window_id`: the backend resolves the final
984
    /// screen position as `parent_top_left + offset`. This is robust where
985
    /// absolute screen coordinates aren't available — notably Wayland, whose
986
    /// `xdg_popup` / subsurface protocol positions relative to the parent. Falls
987
    /// back to absolute (`offset` from origin) if there is no parent.
988
    RelativeToParentWindow(PhysicalPositionI32),
989
}
990
#[allow(variant_size_differences)] // repr(C,u8) FFI enum: boxing the large variant would change the C ABI (api.json bindings); size disparity accepted
991

            
992
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
993
#[repr(C, u8)]
994
/// IME composition window rectangle (cursor position + height)
995
#[derive(Default)]
996
pub enum ImePosition {
997
    #[default]
998
    Uninitialized,
999
    Initialized(LogicalRect),
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
#[repr(C)]
pub struct WindowFlags {
    /// Is the window currently maximized, minimized or fullscreen
    pub frame: WindowFrame,
    /// Window decoration style (title bar, native controls)
    pub decorations: WindowDecorations,
    /// Compositor blur/transparency effect material
    pub background_material: WindowBackgroundMaterial,
    /// Window type classification (Normal, Menu, Tooltip, Dialog)
    pub window_type: WindowType,
    /// User clicked the close button (set by `WindowDelegate`, checked by event loop)
    /// The `close_callback` can set this to false to prevent closing
    pub close_requested: bool,
    /// Is the window currently visible?
    pub is_visible: bool,
    /// Is the window always on top?
    pub is_always_on_top: bool,
    /// Whether the window is resizable
    pub is_resizable: bool,
    /// Whether the window has focus or not (mutating this will request user attention)
    pub has_focus: bool,
    /// Is smooth scrolling enabled for this window?
    pub smooth_scroll_enabled: bool,
    /// Is automatic TAB switching supported?
    pub autotab_enabled: bool,
    /// Enable client-side decorations (custom titlebar with CSD)
    /// Only effective when decorations == `WindowDecorations::None`
    pub has_decorations: bool,
    /// Use native menus (Win32 HMENU, macOS `NSMenu`) instead of Azul window-based menus
    /// Default: true on Windows/macOS, false on Linux
    pub use_native_menus: bool,
    /// Use native context menus instead of Azul window-based context menus
    /// Default: true on Windows/macOS, false on Linux
    pub use_native_context_menus: bool,
    /// Keep window above all others (even from other applications)
    /// Platform-specific: Uses `SetWindowPos(HWND_TOPMOST)` on Windows, [`NSWindow` setLevel:] on
    /// macOS, _`NET_WM_STATE_ABOVE` on X11, `zwlr_layer_shell` on Wayland
    pub is_top_level: bool,
    /// Prevent system from sleeping while window is open
    /// Platform-specific: Uses `SetThreadExecutionState` on Windows, `IOPMAssertionCreateWithName` on
    /// macOS, org.freedesktop.ScreenSaver.Inhibit on Linux
    pub prevent_system_sleep: bool,
    /// Desired fullscreen-transition style.
    ///
    /// On macOS this controls whether entering/leaving fullscreen plays the
    /// system animation (`Slow*`) or transitions immediately (`Fast*`). On
    /// other platforms `Slow*` and `Fast*` behave identically.
    ///
    /// The actual current frame state still lives in [`WindowFlags::frame`]; this
    /// field only describes how the next transition should be performed.
    pub fullscreen_mode: FullScreenMode,
}
impl_option!(
    WindowFlags,
    OptionWindowFlags,
    copy = false,
    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
);
/// Window type classification for behavior control
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
#[repr(C)]
pub enum WindowType {
    /// Normal application window
    Normal,
    /// Menu popup window (always-on-top, frameless, auto-closes on focus loss)
    Menu,
    /// Tooltip window (always-on-top, no interaction)
    Tooltip,
    /// Dialog window (blocks parent window)
    Dialog,
}
impl Default for WindowType {
    fn default() -> Self {
        Self::Normal
    }
}
/// Window frame state (normal, minimized, maximized, fullscreen)
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
#[repr(C)]
pub enum WindowFrame {
    Normal,
    Minimized,
    Maximized,
    Fullscreen,
}
/// Window decoration style
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
#[repr(C)]
pub enum WindowDecorations {
    /// Full decorations: title bar with controls
    Normal,
    /// No title text but controls visible (extended frame).
    /// The application must draw its own title text.
    NoTitle,
    /// Like `NoTitle`, but the framework auto-injects a `Titlebar`
    /// at the top of the user's DOM after calling the layout callback.
    ///
    /// The injected titlebar reads `TitlebarMetrics` from `SystemStyle` for
    /// correct padding around the OS-drawn window control buttons, uses the
    /// system title font, and carries the `__azul-native-titlebar` class for
    /// automatic window-drag activation.
    NoTitleAutoInject,
    /// No controls visible but title bar area present
    NoControls,
    /// No decorations at all (borderless)
    None,
}
impl Default for WindowDecorations {
    fn default() -> Self {
        Self::Normal
    }
}
/// Compositor blur/transparency effects for window background
#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
#[repr(C)]
pub enum WindowBackgroundMaterial {
    /// No transparency or blur
    Opaque,
    /// Transparent without blur
    Transparent,
    /// macOS: Sidebar material, Windows: Acrylic light
    Sidebar,
    /// macOS: Menu material, Windows: Acrylic
    Menu,
    /// macOS: HUD material, Windows: Acrylic dark
    HUD,
    /// macOS: Titlebar material, Windows: Mica
    Titlebar,
    /// Windows: Mica Alt material
    MicaAlt,
}
impl Default for WindowBackgroundMaterial {
    fn default() -> Self {
        Self::Opaque
    }
}
impl Default for WindowFlags {
77881
    fn default() -> Self {
77881
        Self {
77881
            frame: WindowFrame::Normal,
77881
            decorations: WindowDecorations::Normal,
77881
            background_material: WindowBackgroundMaterial::Opaque,
77881
            window_type: WindowType::Normal,
77881
            close_requested: false,
77881
            is_visible: true,
77881
            is_always_on_top: false,
77881
            is_resizable: true,
77881
            has_focus: true,
77881
            smooth_scroll_enabled: true,
77881
            autotab_enabled: true,
77881
            has_decorations: false,
77881
            // Native menus are the default on platforms that support them (Windows/macOS)
77881
            // The platform layer will override this appropriately
77881
            use_native_menus: cfg!(any(target_os = "windows", target_os = "macos")),
77881
            use_native_context_menus: cfg!(any(target_os = "windows", target_os = "macos")),
77881
            is_top_level: false,
77881
            prevent_system_sleep: false,
77881
            fullscreen_mode: FullScreenMode::FastFullScreen,
77881
        }
77881
    }
}
impl WindowFlags {
    /// Check if window is a menu popup
    #[inline]
8
    #[must_use] pub fn is_menu_window(&self) -> bool {
8
        self.window_type == WindowType::Menu
8
    }
    /// Check if window is a tooltip
    #[inline]
8
    #[must_use] pub fn is_tooltip_window(&self) -> bool {
8
        self.window_type == WindowType::Tooltip
8
    }
    /// Check if window is a dialog
    #[inline]
8
    #[must_use] pub fn is_dialog_window(&self) -> bool {
8
        self.window_type == WindowType::Dialog
8
    }
    /// Check if window currently has focus
    #[inline]
3
    #[must_use] pub const fn window_has_focus(&self) -> bool {
3
        self.has_focus
3
    }
    /// Check if close was requested via callback
    #[inline]
3
    #[must_use] pub const fn is_close_requested(&self) -> bool {
3
        self.close_requested
3
    }
    /// Check if window has client-side decorations enabled
    #[inline]
3
    #[must_use] pub const fn has_csd(&self) -> bool {
3
        self.has_decorations
3
    }
    /// Check if native menus should be used
    #[inline]
3
    #[must_use] pub const fn use_native_menus(&self) -> bool {
3
        self.use_native_menus
3
    }
    /// Check if native context menus should be used
    #[inline]
3
    #[must_use] pub const fn use_native_context_menus(&self) -> bool {
3
        self.use_native_context_menus
3
    }
}
/// Platform-specific window configuration options (Windows, Linux, macOS, WASM)
#[derive(Debug, Default, Clone, PartialEq, PartialOrd)]
#[repr(C)]
pub struct PlatformSpecificOptions {
    pub windows_options: WindowsWindowOptions,
    pub linux_options: LinuxWindowOptions,
    pub mac_options: MacWindowOptions,
    pub wasm_options: WasmWindowOptions,
}
// SAFETY: PlatformSpecificOptions contains raw pointers (X11Visual) that are
// opaque platform handles, not dereferenced across threads.
unsafe impl Sync for PlatformSpecificOptions {}
#[allow(clippy::non_send_fields_in_send_ty)] // opaque platform handles, not dereferenced across threads (see note above)
unsafe impl Send for PlatformSpecificOptions {}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
#[repr(C)]
pub struct WindowsWindowOptions {
    /// STARTUP ONLY: Whether the window should allow drag + drop operations (default: true)
    pub allow_drag_and_drop: bool,
    /// STARTUP ONLY: Sets `WS_EX_NOREDIRECTIONBITMAP`
    pub no_redirection_bitmap: bool,
    /// STARTUP ONLY: Window icon (decoded bytes), appears at the top right corner of the window
    pub window_icon: OptionWindowIcon,
    /// READWRITE: Taskbar icon (decoded bytes), usually 256x256x4 bytes large (`ICON_BIG`).
    ///
    /// Can be changed in callbacks / at runtime.
    pub taskbar_icon: OptionTaskBarIcon,
    // NOTE: the old Windows-specific `parent_window: OptionHwndHandle` field was
    // removed in favor of the cross-platform `WindowCreateOptions.parent_window_id`
    // (+ `WindowPosition::RelativeToParentWindow`), which every backend resolves
    // through its window registry. One parenting model for all platforms.
}
impl Default for WindowsWindowOptions {
77874
    fn default() -> Self {
77874
        Self {
77874
            allow_drag_and_drop: true,
77874
            no_redirection_bitmap: false,
77874
            window_icon: OptionWindowIcon::None,
77874
            taskbar_icon: OptionTaskBarIcon::None,
77874
        }
77874
    }
}
/// X window type. Maps directly to
/// [`_NET_WM_WINDOW_TYPE`](https://specifications.freedesktop.org/wm-spec/wm-spec-1.5.html).
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
#[derive(Default)]
pub enum XWindowType {
    /// A desktop feature. This can include a single window containing desktop icons with the same
    /// dimensions as the screen, allowing the desktop environment to have full control of the
    /// desktop, without the need for proxying root window clicks.
    Desktop,
    /// A dock or panel feature. Typically a Window Manager would keep such windows on top of all
    /// other windows.
    Dock,
    /// Toolbar windows. "Torn off" from the main application.
    Toolbar,
    /// Pinnable menu windows. "Torn off" from the main application.
    Menu,
    /// A small persistent utility window, such as a palette or toolbox.
    Utility,
    /// The window is a splash screen displayed as an application is starting up.
    Splash,
    /// This is a dialog window.
    Dialog,
    /// A dropdown menu that usually appears when the user clicks on an item in a menu bar.
    /// This property is typically used on override-redirect windows.
    DropdownMenu,
    /// A popup menu that usually appears when the user right clicks on an object.
    /// This property is typically used on override-redirect windows.
    PopupMenu,
    /// A tooltip window. Usually used to show additional information when hovering over an object
    /// with the cursor. This property is typically used on override-redirect windows.
    Tooltip,
    /// The window is a notification.
    /// This property is typically used on override-redirect windows.
    Notification,
    /// This should be used on the windows that are popped up by combo boxes.
    /// This property is typically used on override-redirect windows.
    Combo,
    /// This indicates the the window is being dragged.
    /// This property is typically used on override-redirect windows.
    Dnd,
    /// This is a normal, top-level window.
    #[default]
    Normal,
}
impl_option!(
    XWindowType,
    OptionXWindowType,
    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
);
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
#[repr(C)]
#[derive(Default)]
pub enum UserAttentionType {
    #[default]
    None,
    Critical,
    Informational,
}
/// State for tracking hover and interaction with Linux window decoration elements (CSD).
#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
#[repr(C)]
pub struct LinuxDecorationsState {
    pub is_dragging_titlebar: bool,
    pub close_button_hover: bool,
    pub maximize_button_hover: bool,
    pub minimize_button_hover: bool,
}
impl_option!(
    LinuxDecorationsState,
    OptionLinuxDecorationsState,
    [Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash]
);
#[derive(Debug, Default, Clone, PartialEq, PartialOrd)]
#[repr(C)]
pub struct LinuxWindowOptions {
    pub wayland_theme: OptionWaylandTheme,
    pub window_icon: OptionWindowIcon,
    /// Build window with `_GTK_THEME_VARIANT` hint set to the specified value. Currently only
    /// relevant on X11. Can only be set at window creation, can't be changed in callbacks.
    pub x11_gtk_theme_variant: OptionString,
    /// Build window with a given application ID. It should match the `.desktop` file distributed
    /// with your program. Only relevant on Wayland.
    /// Can only be set at window creation, can't be changed in callbacks.
    ///
    /// For details about application ID conventions, see the
    /// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id)
    pub wayland_app_id: OptionString,
    /// Build window with `WM_CLASS` hint; defaults to the name of the binary. Only relevant on
    /// X11. Can only be set at window creation, can't be changed in callbacks.
    pub x11_wm_classes: StringPairVec,
    /// Build window with `_NET_WM_WINDOW_TYPE` hint; defaults to `Normal`. Only relevant on X11.
    /// Can only be set at window creation, can't be changed in callbacks.
    pub x11_window_types: XWindowTypeVec,
    /// (Unimplemented) - Can only be set at window creation, can't be changed in callbacks.
    pub x11_visual: OptionX11Visual,
    /// Build window with resize increment hint. Only implemented on X11.
    /// Can only be set at window creation, can't be changed in callbacks.
    pub x11_resize_increments: OptionLogicalSize,
    /// Build window with base size hint. Only implemented on X11.
    /// Can only be set at window creation, can't be changed in callbacks.
    pub x11_base_size: OptionLogicalSize,
    /// (Unimplemented) - Can only be set at window creation, can't be changed in callbacks.
    pub x11_screen: OptionI32,
    pub request_user_attention: UserAttentionType,
    /// X11-specific: Client-side decoration state (drag position, button hover, etc.)
    pub x11_decorations_state: OptionLinuxDecorationsState,
    /// Build window with override-redirect flag; defaults to false. Only relevant on X11.
    /// Can only be set at window creation, can't be changed in callbacks.
    pub x11_override_redirect: bool,
}
pub type X11Visual = *const c_void;
impl_option!(
    X11Visual,
    OptionX11Visual,
    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
);
/// A key-value pair of strings, used for X11 `WM_CLASS` and other platform properties
#[derive(Debug, Default, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[repr(C)]
pub struct AzStringPair {
    pub key: AzString,
    pub value: AzString,
}
impl_option!(
    AzStringPair,
    OptionStringPair,
    copy = false,
    [Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash]
);
impl_vec!(AzStringPair, StringPairVec, StringPairVecDestructor, StringPairVecDestructorType, StringPairVecSlice, OptionStringPair);
impl_vec_mut!(AzStringPair, StringPairVec);
impl_vec_debug!(AzStringPair, StringPairVec);
impl_vec_partialord!(AzStringPair, StringPairVec);
impl_vec_ord!(AzStringPair, StringPairVec);
impl_vec_clone!(AzStringPair, StringPairVec, StringPairVecDestructor);
impl_vec_partialeq!(AzStringPair, StringPairVec);
impl_vec_eq!(AzStringPair, StringPairVec);
impl_vec_hash!(AzStringPair, StringPairVec);
impl_option!(
    StringPairVec,
    OptionStringPairVec,
    copy = false,
    [Debug, Clone, PartialOrd, PartialEq, Ord, Eq, Hash]
);
impl StringPairVec {
1466902
    #[must_use] pub fn get_key(&self, search_key: &str) -> Option<&AzString> {
2900983
        self.as_ref().iter().find_map(|v| {
2888618
            if v.key.as_str() == search_key {
290584
                Some(&v.value)
            } else {
2598034
                None
            }
2888618
        })
1466902
    }
114
    pub fn get_key_mut(&mut self, search_key: &str) -> Option<&mut AzStringPair> {
114
        self.as_mut()
114
            .iter_mut()
118
            .find(|v| v.key.as_str() == search_key)
114
    }
109
    pub fn insert_kv<I: Into<AzString>>(&mut self, key: I, value: I) {
109
        let key = key.into();
109
        let value = value.into();
109
        match self.get_key_mut(key.as_str()) {
6
            None => {}
103
            Some(s) => {
103
                s.value = value;
103
                return;
            }
        }
6
        self.push(AzStringPair { key, value });
109
    }
}
impl_vec!(XWindowType, XWindowTypeVec, XWindowTypeVecDestructor, XWindowTypeVecDestructorType, XWindowTypeVecSlice, OptionXWindowType);
impl_vec_debug!(XWindowType, XWindowTypeVec);
impl_vec_partialord!(XWindowType, XWindowTypeVec);
impl_vec_ord!(XWindowType, XWindowTypeVec);
impl_vec_clone!(XWindowType, XWindowTypeVec, XWindowTypeVecDestructor);
impl_vec_partialeq!(XWindowType, XWindowTypeVec);
impl_vec_eq!(XWindowType, XWindowTypeVec);
impl_vec_hash!(XWindowType, XWindowTypeVec);
impl_option!(
    WaylandTheme,
    OptionWaylandTheme,
    copy = false,
    [Debug, Clone, PartialEq, PartialOrd]
);
/// macOS-specific window options (reserved for future use)
#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[repr(C)]
// `_`-prefixed fields are C-ABI/api.json names; cannot rename.
#[allow(clippy::pub_underscore_fields)]
pub struct MacWindowOptions {
    // empty for now, single field must be present for ABI compat - always set to 0
    pub _reserved: u8,
}
/// WASM/web-specific window options (reserved for future use)
#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[repr(C)]
// `_`-prefixed fields are C-ABI/api.json names; cannot rename.
#[allow(clippy::pub_underscore_fields)]
pub struct WasmWindowOptions {
    // empty for now, single field must be present for ABI compat - always set to 0
    pub _reserved: u8,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
#[derive(Default)]
pub enum FullScreenMode {
    /// - macOS: If the window is in windowed mode, transitions it slowly to fullscreen mode
    /// - other: Does the same as `FastFullScreen`.
    SlowFullScreen,
    /// Window should immediately go into fullscreen mode (on macOS this is not the default
    /// behaviour).
    #[default]
    FastFullScreen,
    /// - macOS: If the window is in fullscreen mode, transitions slowly back to windowed state.
    /// - other: Does the same as `FastWindowed`.
    SlowWindowed,
    /// If the window is in fullscreen mode, will immediately go back to windowed mode (on macOS
    /// this is not the default behaviour).
    FastWindowed,
}
// Translation type because in winit 24.0 the WinitWaylandTheme is a trait instead
// of a struct, which makes things more complicated
#[derive(Debug, Clone, PartialEq, PartialOrd)]
#[repr(C)]
pub struct WaylandTheme {
    pub title_bar_active_background_color: ColorU,
    pub title_bar_active_separator_color: ColorU,
    pub title_bar_active_text_color: ColorU,
    pub title_bar_inactive_background_color: ColorU,
    pub title_bar_inactive_separator_color: ColorU,
    pub title_bar_inactive_text_color: ColorU,
    pub maximize_idle_foreground_inactive_color: ColorU,
    pub minimize_idle_foreground_inactive_color: ColorU,
    pub close_idle_foreground_inactive_color: ColorU,
    pub maximize_hovered_foreground_inactive_color: ColorU,
    pub minimize_hovered_foreground_inactive_color: ColorU,
    pub close_hovered_foreground_inactive_color: ColorU,
    pub maximize_disabled_foreground_inactive_color: ColorU,
    pub minimize_disabled_foreground_inactive_color: ColorU,
    pub close_disabled_foreground_inactive_color: ColorU,
    pub maximize_idle_background_inactive_color: ColorU,
    pub minimize_idle_background_inactive_color: ColorU,
    pub close_idle_background_inactive_color: ColorU,
    pub maximize_hovered_background_inactive_color: ColorU,
    pub minimize_hovered_background_inactive_color: ColorU,
    pub close_hovered_background_inactive_color: ColorU,
    pub maximize_disabled_background_inactive_color: ColorU,
    pub minimize_disabled_background_inactive_color: ColorU,
    pub close_disabled_background_inactive_color: ColorU,
    pub maximize_idle_foreground_active_color: ColorU,
    pub minimize_idle_foreground_active_color: ColorU,
    pub close_idle_foreground_active_color: ColorU,
    pub maximize_hovered_foreground_active_color: ColorU,
    pub minimize_hovered_foreground_active_color: ColorU,
    pub close_hovered_foreground_active_color: ColorU,
    pub maximize_disabled_foreground_active_color: ColorU,
    pub minimize_disabled_foreground_active_color: ColorU,
    pub close_disabled_foreground_active_color: ColorU,
    pub maximize_idle_background_active_color: ColorU,
    pub minimize_idle_background_active_color: ColorU,
    pub close_idle_background_active_color: ColorU,
    pub maximize_hovered_background_active_color: ColorU,
    pub minimize_hovered_background_active_color: ColorU,
    pub close_hovered_background_active_color: ColorU,
    pub maximize_disabled_background_active_color: ColorU,
    pub minimize_disabled_background_active_color: ColorU,
    pub close_disabled_background_active_color: ColorU,
    pub title_bar_font: AzString,
    pub title_bar_font_size: f32,
}
/// The global CSS viewport breakpoints for `@media`-style conditions.
///
/// The dynamic-selector system evaluates against these, and they are one of
/// the three signals the resize fast path checks: crossing any of these
/// (on either axis) re-invokes the
/// app's `layout()`; staying between them re-flows the existing DOM.
///
/// Lived in `azul-dll`'s shell (`shell2::common::CSS_BREAKPOINTS`, still
/// re-exported there) until the headless E2E runner needed the same resize
/// decision — the list is engine policy, not shell policy.
pub const CSS_BREAKPOINTS: &[f32] = &[320.0, 480.0, 640.0, 768.0, 1024.0, 1280.0, 1440.0, 1920.0];
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd)]
#[repr(C)]
pub struct WindowSize {
    /// Width and height of the window, in logical
    /// units (may not correspond to the physical on-screen size)
    pub dimensions: LogicalSize,
    /// Actual DPI value (default: 96)
    pub dpi: u32,
    /// Minimum dimensions of the window
    pub min_dimensions: OptionLogicalSize,
    /// Maximum dimensions of the window
    pub max_dimensions: OptionLogicalSize,
}
impl WindowSize {
    #[allow(clippy::cast_possible_truncation)] // bounded DPI/dimension/number conversion
9
    #[must_use] pub fn get_layout_size(&self) -> LayoutSize {
9
        LayoutSize::new(
9
            libm::roundf(self.dimensions.width) as isize,
9
            libm::roundf(self.dimensions.height) as isize,
        )
9
    }
    /// Get the actual logical size
15
    #[must_use] pub const fn get_logical_size(&self) -> LogicalSize {
15
        self.dimensions
15
    }
26
    #[must_use] pub fn get_physical_size(&self) -> PhysicalSize<u32> {
26
        self.dimensions
26
            .to_physical(self.get_hidpi_factor().inner.get())
26
    }
    #[allow(clippy::cast_precision_loss)] // bounded DPI/dimension/number conversion
47716
    #[must_use] pub fn get_hidpi_factor(&self) -> DpiScaleFactor {
        // Guard against `dpi == 0` (uninitialized / misreporting platform),
        // which would yield a 0.0 scale factor and later divide-by-zero when
        // converting physical <-> logical sizes (`to_logical` divides by this).
        // Fall back to the standard 96 DPI (scale 1.0).
47716
        let dpi = if self.dpi == 0 { 96 } else { self.dpi };
47716
        DpiScaleFactor {
47716
            inner: FloatValue::new(dpi as f32 / 96.0),
47716
        }
47716
    }
}
impl Default for WindowSize {
77939
    fn default() -> Self {
77939
        Self {
77939
            dimensions: LogicalSize::new(640.0, 480.0),
77939
            dpi: 96,
77939
            min_dimensions: None.into(),
77939
            max_dimensions: None.into(),
77939
        }
77939
    }
}
#[repr(C)]
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
pub enum RendererType {
    /// Force hardware rendering
    Hardware,
    /// Force software rendering
    Software,
}
impl_option!(
    RendererType,
    OptionRendererType,
    [Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash]
);
#[derive(Debug, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
pub enum UpdateFocusWarning {
    FocusInvalidDomId(DomId),
    FocusInvalidNodeId(NodeHierarchyItemId),
    CouldNotFindFocusNode(CssPath),
}
impl ::core::fmt::Display for UpdateFocusWarning {
4
    fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
        use self::UpdateFocusWarning::{FocusInvalidDomId, FocusInvalidNodeId, CouldNotFindFocusNode};
4
        match self {
1
            FocusInvalidDomId(dom_id) => write!(f, "Focusing on DOM with invalid ID: {dom_id:?}"),
2
            FocusInvalidNodeId(node_id) => {
2
                write!(f, "Focusing on node with invalid ID: {node_id}")
            }
1
            CouldNotFindFocusNode(css_path) => {
1
                write!(f, "Could not find focus node for path: {css_path}")
            }
        }
4
    }
}
/// Utility function for easier creation of a keymap - i.e. `[vec![Ctrl, S], my_function]`
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C, u8)]
pub enum AcceleratorKey {
    Ctrl,
    Alt,
    Shift,
    Key(VirtualKeyCode),
}
impl AcceleratorKey {
    /// Checks if the current keyboard state contains the given char or modifier,
    /// i.e. if the keyboard state currently has the shift key pressed and the
    /// accelerator key is `Shift`, evaluates to true, otherwise to false.
20020
    #[must_use] pub fn matches(&self, keyboard_state: &KeyboardState) -> bool {
        use self::AcceleratorKey::{Ctrl, Alt, Shift, Key};
20020
        match self {
8
            Ctrl => keyboard_state.ctrl_down(),
3
            Alt => keyboard_state.alt_down(),
20004
            Shift => keyboard_state.shift_down(),
5
            Key(k) => keyboard_state.is_key_down(*k),
        }
20020
    }
}
/// Symbolic name for a keyboard key, does NOT take the keyboard locale into account
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub enum VirtualKeyCode {
    Key1,
    Key2,
    Key3,
    Key4,
    Key5,
    Key6,
    Key7,
    Key8,
    Key9,
    Key0,
    A,
    B,
    C,
    D,
    E,
    F,
    G,
    H,
    I,
    J,
    K,
    L,
    M,
    N,
    O,
    P,
    Q,
    R,
    S,
    T,
    U,
    V,
    W,
    X,
    Y,
    Z,
    Escape,
    F1,
    F2,
    F3,
    F4,
    F5,
    F6,
    F7,
    F8,
    F9,
    F10,
    F11,
    F12,
    F13,
    F14,
    F15,
    F16,
    F17,
    F18,
    F19,
    F20,
    F21,
    F22,
    F23,
    F24,
    Snapshot,
    Scroll,
    Pause,
    Insert,
    Home,
    Delete,
    End,
    PageDown,
    PageUp,
    Left,
    Up,
    Right,
    Down,
    Back,
    Return,
    Space,
    Compose,
    Caret,
    Numlock,
    Numpad0,
    Numpad1,
    Numpad2,
    Numpad3,
    Numpad4,
    Numpad5,
    Numpad6,
    Numpad7,
    Numpad8,
    Numpad9,
    NumpadAdd,
    NumpadDivide,
    NumpadDecimal,
    NumpadComma,
    NumpadEnter,
    NumpadEquals,
    NumpadMultiply,
    NumpadSubtract,
    AbntC1,
    AbntC2,
    Apostrophe,
    Apps,
    Asterisk,
    At,
    Ax,
    Backslash,
    Calculator,
    Capital,
    Colon,
    Comma,
    Convert,
    Equals,
    Grave,
    Kana,
    Kanji,
    LAlt,
    LBracket,
    LControl,
    LShift,
    LWin,
    Mail,
    MediaSelect,
    MediaStop,
    Minus,
    Mute,
    MyComputer,
    NavigateForward,
    NavigateBackward,
    NextTrack,
    NoConvert,
    OEM102,
    Period,
    PlayPause,
    Plus,
    Power,
    PrevTrack,
    RAlt,
    RBracket,
    RControl,
    RShift,
    RWin,
    Semicolon,
    Slash,
    Sleep,
    Stop,
    Sysrq,
    Tab,
    Underline,
    Unlabeled,
    VolumeDown,
    VolumeUp,
    Wake,
    WebBack,
    WebFavorites,
    WebForward,
    WebHome,
    WebRefresh,
    WebSearch,
    WebStop,
    Yen,
    Copy,
    Paste,
    Cut,
}
impl VirtualKeyCode {
    /// Reconstructs a `VirtualKeyCode` from its `as u32` discriminant.
    ///
    /// This enum is a fieldless `#[repr(C)]` enum with no explicit discriminants,
    /// so the discriminants are assigned sequentially in declaration order and
    /// `VariantN as u32` round-trips through this table. Used to recover the key
    /// of a keyboard *event* from its `key_code` (which is stored as
    /// `VirtualKeyCode as u32`) instead of reading live keyboard state.
    #[must_use]
    #[allow(clippy::too_many_lines)] // exhaustive keycode match table
2779
    pub const fn from_u32(v: u32) -> Option<Self> {
2779
        match v {
7
            0 => Some(Self::Key1),
6
            1 => Some(Self::Key2),
6
            2 => Some(Self::Key3),
6
            3 => Some(Self::Key4),
6
            4 => Some(Self::Key5),
6
            5 => Some(Self::Key6),
6
            6 => Some(Self::Key7),
6
            7 => Some(Self::Key8),
6
            8 => Some(Self::Key9),
6
            9 => Some(Self::Key0),
9
            10 => Some(Self::A),
8
            11 => Some(Self::B),
12
            12 => Some(Self::C),
8
            13 => Some(Self::D),
7
            14 => Some(Self::E),
7
            15 => Some(Self::F),
7
            16 => Some(Self::G),
7
            17 => Some(Self::H),
7
            18 => Some(Self::I),
7
            19 => Some(Self::J),
7
            20 => Some(Self::K),
7
            21 => Some(Self::L),
7
            22 => Some(Self::M),
7
            23 => Some(Self::N),
7
            24 => Some(Self::O),
7
            25 => Some(Self::P),
8
            26 => Some(Self::Q),
7
            27 => Some(Self::R),
7
            28 => Some(Self::S),
7
            29 => Some(Self::T),
7
            30 => Some(Self::U),
8
            31 => Some(Self::V),
7
            32 => Some(Self::W),
8
            33 => Some(Self::X),
8
            34 => Some(Self::Y),
9
            35 => Some(Self::Z),
6
            36 => Some(Self::Escape),
6
            37 => Some(Self::F1),
6
            38 => Some(Self::F2),
6
            39 => Some(Self::F3),
6
            40 => Some(Self::F4),
7
            41 => Some(Self::F5),
6
            42 => Some(Self::F6),
6
            43 => Some(Self::F7),
6
            44 => Some(Self::F8),
6
            45 => Some(Self::F9),
6
            46 => Some(Self::F10),
6
            47 => Some(Self::F11),
6
            48 => Some(Self::F12),
6
            49 => Some(Self::F13),
6
            50 => Some(Self::F14),
6
            51 => Some(Self::F15),
6
            52 => Some(Self::F16),
6
            53 => Some(Self::F17),
6
            54 => Some(Self::F18),
6
            55 => Some(Self::F19),
6
            56 => Some(Self::F20),
6
            57 => Some(Self::F21),
6
            58 => Some(Self::F22),
6
            59 => Some(Self::F23),
6
            60 => Some(Self::F24),
6
            61 => Some(Self::Snapshot),
6
            62 => Some(Self::Scroll),
6
            63 => Some(Self::Pause),
6
            64 => Some(Self::Insert),
7
            65 => Some(Self::Home),
10
            66 => Some(Self::Delete),
7
            67 => Some(Self::End),
6
            68 => Some(Self::PageDown),
6
            69 => Some(Self::PageUp),
9
            70 => Some(Self::Left),
7
            71 => Some(Self::Up),
8
            72 => Some(Self::Right),
7
            73 => Some(Self::Down),
11
            74 => Some(Self::Back),
6
            75 => Some(Self::Return),
7
            76 => Some(Self::Space),
6
            77 => Some(Self::Compose),
6
            78 => Some(Self::Caret),
6
            79 => Some(Self::Numlock),
6
            80 => Some(Self::Numpad0),
6
            81 => Some(Self::Numpad1),
6
            82 => Some(Self::Numpad2),
6
            83 => Some(Self::Numpad3),
6
            84 => Some(Self::Numpad4),
6
            85 => Some(Self::Numpad5),
6
            86 => Some(Self::Numpad6),
6
            87 => Some(Self::Numpad7),
6
            88 => Some(Self::Numpad8),
6
            89 => Some(Self::Numpad9),
6
            90 => Some(Self::NumpadAdd),
6
            91 => Some(Self::NumpadDivide),
6
            92 => Some(Self::NumpadDecimal),
6
            93 => Some(Self::NumpadComma),
6
            94 => Some(Self::NumpadEnter),
6
            95 => Some(Self::NumpadEquals),
6
            96 => Some(Self::NumpadMultiply),
6
            97 => Some(Self::NumpadSubtract),
6
            98 => Some(Self::AbntC1),
6
            99 => Some(Self::AbntC2),
6
            100 => Some(Self::Apostrophe),
6
            101 => Some(Self::Apps),
6
            102 => Some(Self::Asterisk),
6
            103 => Some(Self::At),
6
            104 => Some(Self::Ax),
6
            105 => Some(Self::Backslash),
6
            106 => Some(Self::Calculator),
6
            107 => Some(Self::Capital),
6
            108 => Some(Self::Colon),
6
            109 => Some(Self::Comma),
6
            110 => Some(Self::Convert),
6
            111 => Some(Self::Equals),
6
            112 => Some(Self::Grave),
6
            113 => Some(Self::Kana),
6
            114 => Some(Self::Kanji),
6
            115 => Some(Self::LAlt),
6
            116 => Some(Self::LBracket),
7
            117 => Some(Self::LControl),
6
            118 => Some(Self::LShift),
6
            119 => Some(Self::LWin),
6
            120 => Some(Self::Mail),
6
            121 => Some(Self::MediaSelect),
6
            122 => Some(Self::MediaStop),
6
            123 => Some(Self::Minus),
6
            124 => Some(Self::Mute),
6
            125 => Some(Self::MyComputer),
6
            126 => Some(Self::NavigateForward),
6
            127 => Some(Self::NavigateBackward),
6
            128 => Some(Self::NextTrack),
6
            129 => Some(Self::NoConvert),
6
            130 => Some(Self::OEM102),
6
            131 => Some(Self::Period),
6
            132 => Some(Self::PlayPause),
6
            133 => Some(Self::Plus),
6
            134 => Some(Self::Power),
6
            135 => Some(Self::PrevTrack),
6
            136 => Some(Self::RAlt),
6
            137 => Some(Self::RBracket),
6
            138 => Some(Self::RControl),
6
            139 => Some(Self::RShift),
6
            140 => Some(Self::RWin),
6
            141 => Some(Self::Semicolon),
6
            142 => Some(Self::Slash),
6
            143 => Some(Self::Sleep),
6
            144 => Some(Self::Stop),
6
            145 => Some(Self::Sysrq),
6
            146 => Some(Self::Tab),
6
            147 => Some(Self::Underline),
6
            148 => Some(Self::Unlabeled),
6
            149 => Some(Self::VolumeDown),
6
            150 => Some(Self::VolumeUp),
6
            151 => Some(Self::Wake),
6
            152 => Some(Self::WebBack),
6
            153 => Some(Self::WebFavorites),
6
            154 => Some(Self::WebForward),
6
            155 => Some(Self::WebHome),
6
            156 => Some(Self::WebRefresh),
6
            157 => Some(Self::WebSearch),
6
            158 => Some(Self::WebStop),
6
            159 => Some(Self::Yen),
6
            160 => Some(Self::Copy),
6
            161 => Some(Self::Paste),
7
            162 => Some(Self::Cut),
1737
            _ => None,
        }
2779
    }
208
    #[must_use] pub const fn get_lowercase(&self) -> Option<char> {
        use self::VirtualKeyCode::{A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, Key0, Numpad0, Key1, Numpad1, Key2, Numpad2, Key3, Numpad3, Key4, Numpad4, Key5, Numpad5, Key6, Numpad6, Key7, Numpad7, Key8, Numpad8, Key9, Numpad9, Minus, Asterisk, At, Period, Semicolon, Slash, Caret};
208
        match self {
2
            A => Some('a'),
2
            B => Some('b'),
2
            C => Some('c'),
2
            D => Some('d'),
2
            E => Some('e'),
2
            F => Some('f'),
2
            G => Some('g'),
2
            H => Some('h'),
2
            I => Some('i'),
2
            J => Some('j'),
2
            K => Some('k'),
2
            L => Some('l'),
2
            M => Some('m'),
2
            N => Some('n'),
2
            O => Some('o'),
2
            P => Some('p'),
2
            Q => Some('q'),
2
            R => Some('r'),
2
            S => Some('s'),
2
            T => Some('t'),
2
            U => Some('u'),
2
            V => Some('v'),
2
            W => Some('w'),
2
            X => Some('x'),
2
            Y => Some('y'),
2
            Z => Some('z'),
4
            Key0 | Numpad0 => Some('0'),
4
            Key1 | Numpad1 => Some('1'),
2
            Key2 | Numpad2 => Some('2'),
2
            Key3 | Numpad3 => Some('3'),
2
            Key4 | Numpad4 => Some('4'),
4
            Key5 | Numpad5 => Some('5'),
2
            Key6 | Numpad6 => Some('6'),
2
            Key7 | Numpad7 => Some('7'),
2
            Key8 | Numpad8 => Some('8'),
4
            Key9 | Numpad9 => Some('9'),
2
            Minus => Some('-'),
1
            Asterisk => Some('*'),
1
            At => Some('@'),
2
            Period => Some('.'),
1
            Semicolon => Some(';'),
2
            Slash => Some('/'),
2
            Caret => Some('^'),
117
            _ => None,
        }
208
    }
}
/// 16x16x4 bytes icon
#[derive(Debug, Clone)]
#[repr(C)]
pub struct SmallWindowIconBytes {
    pub key: IconKey,
    pub rgba_bytes: U8Vec,
}
/// 32x32x4 bytes icon
#[derive(Debug, Clone)]
#[repr(C)]
pub struct LargeWindowIconBytes {
    pub key: IconKey,
    pub rgba_bytes: U8Vec,
}
// Window icon that usually appears in the top-left corner of the window
#[derive(Debug, Clone)]
#[repr(C, u8)]
pub enum WindowIcon {
    Small(SmallWindowIconBytes),
    /// 32x32x4 bytes icon
    Large(LargeWindowIconBytes),
}
impl_option!(
    WindowIcon,
    OptionWindowIcon,
    copy = false,
    [Debug, Clone, PartialOrd, PartialEq, Eq, Hash, Ord]
);
impl WindowIcon {
19
    #[must_use] pub const fn get_key(&self) -> IconKey {
19
        match &self {
15
            Self::Small(SmallWindowIconBytes { key, .. })
19
            | Self::Large(LargeWindowIconBytes { key, .. }) => *key,
        }
19
    }
}
// -- Only compare the IconKey (for WindowIcon and TaskBarIcon)
impl PartialEq for WindowIcon {
3
    fn eq(&self, rhs: &Self) -> bool {
3
        self.get_key() == rhs.get_key()
3
    }
}
impl PartialOrd for WindowIcon {
1
    fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
1
        Some((self.get_key()).cmp(&rhs.get_key()))
1
    }
}
impl Eq for WindowIcon {}
impl Ord for WindowIcon {
2
    fn cmp(&self, rhs: &Self) -> Ordering {
2
        (self.get_key()).cmp(&rhs.get_key())
2
    }
}
impl Hash for WindowIcon {
4
    fn hash<H>(&self, state: &mut H)
4
    where
4
        H: Hasher,
    {
4
        self.get_key().hash(state);
4
    }
}
/// 256x256x4 bytes window icon
#[derive(Debug, Clone)]
#[repr(C)]
pub struct TaskBarIcon {
    pub key: IconKey,
    pub rgba_bytes: U8Vec,
}
impl_option!(
    TaskBarIcon,
    OptionTaskBarIcon,
    copy = false,
    [Debug, Clone, PartialOrd, PartialEq, Eq, Hash, Ord]
);
impl PartialEq for TaskBarIcon {
    fn eq(&self, rhs: &Self) -> bool {
        self.key == rhs.key
    }
}
impl PartialOrd for TaskBarIcon {
    fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
        Some((self.key).cmp(&rhs.key))
    }
}
impl Eq for TaskBarIcon {}
impl Ord for TaskBarIcon {
    fn cmp(&self, rhs: &Self) -> Ordering {
        (self.key).cmp(&rhs.key)
    }
}
impl Hash for TaskBarIcon {
    fn hash<H>(&self, state: &mut H)
    where
        H: Hasher,
    {
        self.key.hash(state);
    }
}
/// A built-in system dialog the engine presents on the app's behalf.
///
/// Invoked via `CallbackInfo::invoke_system_dialog`. These dialogs are
/// rendered by azul itself in a new window that is ALWAYS CPU-rendered — a
/// dialog reporting a problem (possibly a GPU problem) must not depend on
/// the GPU working.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub enum SysDialogType {
    /// "Report a problem": a message box (user text + optional screenshot of
    /// the current window + optional system information) mailed to
    /// `AppConfig.report_problem`, or saved to disk when no address is set.
    ReportProblem,
    /// "Check for updates": runs the update check on a background thread,
    /// shows the release's Markdown changelog, and — only where the install
    /// permits self-update and the user consents — downloads and applies it.
    /// Package-managed installs get a "update via your package manager" note.
    UpdateVersion,
    /// "Data collection": the telemetry consent dialog. Lists EVERY
    /// instrument the app can record with per-metric checkmarks, the four
    /// signal switches (crashes / logs / metrics / app state on crash), and
    /// "remember for all azul apps" (writes the machine-wide shared config).
    TelemetryConsent,
    /// "Graphics check": shows what the engine's GPU probe found (vendor /
    /// renderer / verdict) and, when GPU rendering is unusable, per-platform
    /// driver guidance - for apps that NEED working video acceleration.
    GpuCheck,
}
#[cfg(test)]
#[allow(clippy::float_cmp)] // exact-value assertions on hidpi scale factors
mod audit_tests {
    use super::*;
    #[test]
1
    fn hidpi_factor_guards_zero_dpi() {
        // dpi == 0 must not produce a 0.0 scale factor (later divide-by-zero
        // in to_logical); it falls back to 96 DPI (scale 1.0).
1
        let ws = WindowSize { dpi: 0, ..WindowSize::default() };
1
        let factor = ws.get_hidpi_factor().inner.get();
1
        assert_eq!(factor, 1.0);
1
        let ws2 = WindowSize { dpi: 192, ..WindowSize::default() };
1
        assert_eq!(ws2.get_hidpi_factor().inner.get(), 2.0);
1
    }
    #[test]
1
    fn virtual_keycode_from_u32_roundtrips() {
        // A representative spread across the enum, including first/last.
8
        for vk in [
1
            VirtualKeyCode::Key1,
1
            VirtualKeyCode::A,
1
            VirtualKeyCode::Z,
1
            VirtualKeyCode::Left,
1
            VirtualKeyCode::Back,
1
            VirtualKeyCode::Delete,
1
            VirtualKeyCode::LControl,
1
            VirtualKeyCode::Cut,
        ] {
8
            assert_eq!(VirtualKeyCode::from_u32(vk as u32), Some(vk));
        }
        // Out of range -> None (no UB, no panic).
1
        assert_eq!(VirtualKeyCode::from_u32(10_000), None);
1
    }
}
#[cfg(test)]
#[allow(clippy::float_cmp)] // exact-value assertions on saturating float->int conversions
mod autotest_generated {
    use alloc::{format, string::String, vec};
    use super::*;
    /// Highest valid `VirtualKeyCode` discriminant (`Cut`, the last declared variant).
    const LAST_VK: u32 = 162;
    /// Deterministic, `no_std`-safe hasher so hash/eq consistency can be checked
    /// without pulling in `std::collections::hash_map::DefaultHasher`.
    #[derive(Default)]
    struct TestHasher(u64);
    impl Hasher for TestHasher {
        fn write(&mut self, bytes: &[u8]) {
            for &b in bytes {
                self.0 = self.0.rotate_left(5) ^ u64::from(b);
            }
        }
        fn finish(&self) -> u64 {
            self.0
        }
    }
    fn hash_of<T: Hash>(value: &T) -> u64 {
        let mut h = TestHasher::default();
        value.hash(&mut h);
        h.finish()
    }
    fn keyboard_with(keys: &[VirtualKeyCode]) -> KeyboardState {
        KeyboardState {
            pressed_virtual_keycodes: keys.to_vec().into(),
            ..KeyboardState::default()
        }
    }
    fn pair(key: &str, value: &str) -> AzStringPair {
        AzStringPair {
            key: key.into(),
            value: value.into(),
        }
    }
    // ---------------------------------------------------------------------
    // Constructors: WindowId / IconKey (atomic counters)
    // ---------------------------------------------------------------------
    #[test]
    fn window_id_new_is_unique_and_monotonic() {
        // The counter is process-global and shared with other tests in this
        // binary, so only *relative* properties may be asserted.
        let mut ids = BTreeSet::new();
        let mut prev = WindowId::new();
        assert!(ids.insert(prev));
        for _ in 0..1000 {
            let next = WindowId::new();
            assert!(next.id > prev.id, "WindowId counter must strictly increase");
            assert!(ids.insert(next), "WindowId::new() handed out a duplicate");
            prev = next;
        }
        // Default delegates to new(): two defaults are never the same window.
        assert_ne!(WindowId::default(), WindowId::default());
    }
    #[test]
    fn icon_key_new_is_unique_and_monotonic() {
        let mut keys = BTreeSet::new();
        let mut prev = IconKey::new();
        assert!(keys.insert(prev));
        for _ in 0..1000 {
            let next = IconKey::new();
            assert!(
                next.icon_id > prev.icon_id,
                "IconKey counter must strictly increase"
            );
            assert!(keys.insert(next), "IconKey::new() handed out a duplicate");
            prev = next;
        }
        assert_ne!(IconKey::default(), IconKey::default());
    }
    // ---------------------------------------------------------------------
    // RendererOptions + Vsync / Srgb / HwAcceleration predicates
    // ---------------------------------------------------------------------
    #[test]
    fn renderer_options_new_preserves_every_combination() {
        let vsyncs = [Vsync::Enabled, Vsync::Disabled, Vsync::DontCare];
        let srgbs = [Srgb::Enabled, Srgb::Disabled, Srgb::DontCare];
        let accels = [
            HwAcceleration::Enabled,
            HwAcceleration::Disabled,
            HwAcceleration::DontCare,
        ];
        for v in vsyncs {
            for s in srgbs {
                for a in accels {
                    let o = RendererOptions::new(v, s, a);
                    assert_eq!(o.vsync, v);
                    assert_eq!(o.srgb, s);
                    assert_eq!(o.hw_accel, a);
                    // Constructed value must round-trip through equality/copy.
                    assert_eq!(o, RendererOptions::new(v, s, a));
                }
            }
        }
    }
    #[test]
    fn renderer_options_default_does_not_force_cpu_rendering() {
        // Regression guard: hw_accel must be DontCare (auto), NOT Disabled -
        // Disabled silently forced every app into CPU rendering.
        let d = RendererOptions::default();
        assert_eq!(d.hw_accel, HwAcceleration::DontCare);
        assert!(!d.hw_accel.is_enabled());
        assert!(d.vsync.is_enabled());
        assert!(!d.srgb.is_enabled());
    }
    #[test]
    fn tri_state_is_enabled_only_for_enabled_variant() {
        assert!(Vsync::Enabled.is_enabled());
        assert!(!Vsync::Disabled.is_enabled());
        assert!(!Vsync::DontCare.is_enabled());
        assert!(Srgb::Enabled.is_enabled());
        assert!(!Srgb::Disabled.is_enabled());
        assert!(!Srgb::DontCare.is_enabled());
        assert!(HwAcceleration::Enabled.is_enabled());
        assert!(!HwAcceleration::Disabled.is_enabled());
        assert!(!HwAcceleration::DontCare.is_enabled());
    }
    // ---------------------------------------------------------------------
    // KeyboardState getters / predicates
    // ---------------------------------------------------------------------
    #[test]
    fn keyboard_state_default_has_no_key_down() {
        let k = KeyboardState::default();
        assert!(!k.shift_down());
        assert!(!k.ctrl_down());
        assert!(!k.alt_down());
        assert!(!k.super_down());
        assert!(!k.primary_down());
        // Every single keycode reports "not down" on an empty state.
        for v in 0..=LAST_VK {
            let vk = VirtualKeyCode::from_u32(v).expect("discriminant in range");
            assert!(!k.is_key_down(vk));
        }
    }
    #[test]
    fn keyboard_state_modifiers_accept_either_side() {
        for (left, right, probe) in [
            (
                VirtualKeyCode::LShift,
                VirtualKeyCode::RShift,
                KeyboardState::shift_down as fn(&KeyboardState) -> bool,
            ),
            (
                VirtualKeyCode::LControl,
                VirtualKeyCode::RControl,
                KeyboardState::ctrl_down as fn(&KeyboardState) -> bool,
            ),
            (
                VirtualKeyCode::LAlt,
                VirtualKeyCode::RAlt,
                KeyboardState::alt_down as fn(&KeyboardState) -> bool,
            ),
            (
                VirtualKeyCode::LWin,
                VirtualKeyCode::RWin,
                KeyboardState::super_down as fn(&KeyboardState) -> bool,
            ),
        ] {
            assert!(probe(&keyboard_with(&[left])), "left variant must register");
            assert!(
                probe(&keyboard_with(&[right])),
                "right variant must register"
            );
            assert!(probe(&keyboard_with(&[left, right])));
            // An unrelated key must not light up a modifier.
            assert!(!probe(&keyboard_with(&[VirtualKeyCode::A])));
        }
    }
    #[test]
    fn keyboard_state_primary_down_follows_platform() {
        let ctrl = keyboard_with(&[VirtualKeyCode::LControl]);
        let cmd = keyboard_with(&[VirtualKeyCode::LWin]);
        if cfg!(target_os = "macos") {
            assert!(cmd.primary_down(), "Cmd (super) is PRIMARY on macOS");
            assert!(!ctrl.primary_down());
        } else {
            assert!(ctrl.primary_down(), "Ctrl is PRIMARY off macOS");
            assert!(!cmd.primary_down());
        }
        // On every platform, primary_down agrees with one of the two modifiers.
        for k in [&ctrl, &cmd, &KeyboardState::default()] {
            assert_eq!(
                k.primary_down(),
                if cfg!(target_os = "macos") {
                    k.super_down()
                } else {
                    k.ctrl_down()
                }
            );
        }
    }
    #[test]
    fn is_key_down_handles_duplicates_and_large_state() {
        // Same key pressed many times (backends can push duplicates).
        let dup = keyboard_with(&[VirtualKeyCode::S; 512]);
        assert!(dup.is_key_down(VirtualKeyCode::S));
        assert!(!dup.is_key_down(VirtualKeyCode::A));
        // Every key held down at once: no panic, all report true.
        let all: alloc::vec::Vec<VirtualKeyCode> = (0..=LAST_VK)
            .map(|v| VirtualKeyCode::from_u32(v).expect("discriminant in range"))
            .collect();
        let everything = keyboard_with(&all);
        for vk in &all {
            assert!(everything.is_key_down(*vk));
        }
        assert!(everything.shift_down() && everything.ctrl_down());
        assert!(everything.alt_down() && everything.super_down());
    }
    // ---------------------------------------------------------------------
    // AcceleratorKey / matches_accelerator
    // ---------------------------------------------------------------------
    #[test]
    fn empty_chord_matches_trivially() {
        // Documented: "An empty chord matches trivially."
        assert!(KeyboardState::default().matches_accelerator(&[]));
        assert!(keyboard_with(&[VirtualKeyCode::A]).matches_accelerator(&[]));
    }
    #[test]
    fn matches_accelerator_requires_every_entry() {
        let state = keyboard_with(&[
            VirtualKeyCode::LControl,
            VirtualKeyCode::LShift,
            VirtualKeyCode::S,
        ]);
        assert!(state.matches_accelerator(&[
            AcceleratorKey::Ctrl,
            AcceleratorKey::Shift,
            AcceleratorKey::Key(VirtualKeyCode::S),
        ]));
        // One missing entry (Alt) is enough to reject the whole chord.
        assert!(!state.matches_accelerator(&[
            AcceleratorKey::Ctrl,
            AcceleratorKey::Alt,
            AcceleratorKey::Key(VirtualKeyCode::S),
        ]));
        // Wrong key, right modifiers.
        assert!(!state.matches_accelerator(&[
            AcceleratorKey::Ctrl,
            AcceleratorKey::Key(VirtualKeyCode::Q),
        ]));
        // Order must not matter.
        assert!(state.matches_accelerator(&[
            AcceleratorKey::Key(VirtualKeyCode::S),
            AcceleratorKey::Shift,
            AcceleratorKey::Ctrl,
        ]));
    }
    #[test]
    fn matches_accelerator_survives_huge_chord() {
        // A pathologically long chord must terminate (linear scan, no recursion).
        let state = keyboard_with(&[VirtualKeyCode::LShift]);
        let long_ok = vec![AcceleratorKey::Shift; 10_000];
        assert!(state.matches_accelerator(&long_ok));
        // 10k satisfiable entries with a single unsatisfiable one at the very end:
        // `all()` must still reach it and return false.
        let mut long_bad = vec![AcceleratorKey::Shift; 10_000];
        long_bad.push(AcceleratorKey::Ctrl);
        assert!(!state.matches_accelerator(&long_bad));
    }
    #[test]
    fn accelerator_key_matches_each_variant() {
        let empty = KeyboardState::default();
        for a in [
            AcceleratorKey::Ctrl,
            AcceleratorKey::Alt,
            AcceleratorKey::Shift,
            AcceleratorKey::Key(VirtualKeyCode::A),
        ] {
            assert!(!a.matches(&empty), "nothing matches an empty keyboard state");
        }
        assert!(AcceleratorKey::Ctrl.matches(&keyboard_with(&[VirtualKeyCode::RControl])));
        assert!(AcceleratorKey::Alt.matches(&keyboard_with(&[VirtualKeyCode::RAlt])));
        assert!(AcceleratorKey::Shift.matches(&keyboard_with(&[VirtualKeyCode::RShift])));
        assert!(
            AcceleratorKey::Key(VirtualKeyCode::F24).matches(&keyboard_with(&[VirtualKeyCode::F24]))
        );
        // Modifier accelerators are NOT satisfied by the letter of the same name.
        assert!(!AcceleratorKey::Ctrl.matches(&keyboard_with(&[VirtualKeyCode::C])));
    }
    // ---------------------------------------------------------------------
    // MouseState / MouseButtonState
    // ---------------------------------------------------------------------
    #[test]
    fn mouse_state_matches_context_button() {
        let base = MouseState::default();
        assert!(!base.matches(&ContextMenuMouseButton::Left));
        assert!(!base.matches(&ContextMenuMouseButton::Right));
        assert!(!base.matches(&ContextMenuMouseButton::Middle));
        for (ctx, ms) in [
            (
                ContextMenuMouseButton::Left,
                MouseState {
                    left_down: true,
                    ..MouseState::default()
                },
            ),
            (
                ContextMenuMouseButton::Right,
                MouseState {
                    right_down: true,
                    ..MouseState::default()
                },
            ),
            (
                ContextMenuMouseButton::Middle,
                MouseState {
                    middle_down: true,
                    ..MouseState::default()
                },
            ),
        ] {
            assert!(ms.matches(&ctx), "{ctx:?} must match its own button");
            // ...and only its own button.
            let others = [
                ContextMenuMouseButton::Left,
                ContextMenuMouseButton::Right,
                ContextMenuMouseButton::Middle,
            ];
            for other in others {
                assert_eq!(ms.matches(&other), other == ctx);
            }
        }
    }
    #[test]
    fn mouse_down_and_button_state_agree_for_all_8_combinations() {
        for bits in 0u8..8 {
            let (l, r, m) = (bits & 1 != 0, bits & 2 != 0, bits & 4 != 0);
            let ms = MouseState {
                left_down: l,
                right_down: r,
                middle_down: m,
                ..MouseState::default()
            };
            assert_eq!(ms.mouse_down(), l || r || m);
            let snapshot = ms.button_state();
            assert_eq!(snapshot.left_down, l);
            assert_eq!(snapshot.right_down, r);
            assert_eq!(snapshot.middle_down, m);
            // any_down is exactly mouse_down, and the From impl is the same snapshot.
            assert_eq!(snapshot.any_down(), ms.mouse_down());
            assert_eq!(crate::events::MouseButtonState::from(&ms), snapshot);
        }
        // Default MouseState has no button held.
        assert!(!MouseState::default().mouse_down());
        assert!(!MouseState::default().button_state().any_down());
    }
    // ---------------------------------------------------------------------
    // process_system_scroll (numeric)
    // ---------------------------------------------------------------------
    #[test]
    fn process_system_scroll_zero_and_negative_zero_consume_nothing() {
        let r = process_system_scroll(LogicalPosition::zero(), false);
        assert_eq!(r.scrolled_nodes, 0);
        assert_eq!(r.remaining_delta, LogicalPosition::zero());
        assert!(!r.hit_scrollbar);
        // -0.0 == 0.0 under IEEE-754, so a negative-zero delta must also be a no-op.
        let neg_zero = process_system_scroll(LogicalPosition::new(-0.0, -0.0), true);
        assert_eq!(neg_zero.scrolled_nodes, 0);
        assert!(neg_zero.hit_scrollbar, "hit_scrollbar is echoed verbatim");
    }
    #[test]
    fn process_system_scroll_counts_any_nonzero_axis() {
        for delta in [
            LogicalPosition::new(1.0, 0.0),
            LogicalPosition::new(0.0, -1.0),
            LogicalPosition::new(-3.5, 7.25),
            LogicalPosition::new(f32::MIN, 0.0),
            LogicalPosition::new(0.0, f32::MAX),
            LogicalPosition::new(f32::INFINITY, f32::NEG_INFINITY),
            LogicalPosition::new(f32::MIN_POSITIVE, 0.0),
        ] {
            let r = process_system_scroll(delta, false);
            assert_eq!(r.scrolled_nodes, 1, "{delta:?} must count as consumed");
            // Overscroll is never reported by this helper.
            assert_eq!(r.remaining_delta, LogicalPosition::zero());
        }
    }
    #[test]
    fn process_system_scroll_does_not_panic_on_nan() {
        // NaN != 0.0 is `true`, so a NaN delta is currently treated as consumed.
        // The contract asserted here is only "terminates, no panic, bounded count".
        for delta in [
            LogicalPosition::new(f32::NAN, 0.0),
            LogicalPosition::new(0.0, f32::NAN),
            LogicalPosition::new(f32::NAN, f32::NAN),
        ] {
            let r = process_system_scroll(delta, true);
            assert!(r.scrolled_nodes <= 1);
            assert_eq!(r.remaining_delta, LogicalPosition::zero());
            assert!(r.hit_scrollbar);
        }
    }
    #[test]
    fn scroll_result_default_is_inert() {
        let d = ScrollResult::default();
        assert_eq!(d.scrolled_nodes, 0);
        assert_eq!(d.remaining_delta, LogicalPosition::zero());
        assert!(!d.hit_scrollbar);
    }
    // ---------------------------------------------------------------------
    // CursorPosition
    // ---------------------------------------------------------------------
    #[test]
    fn cursor_position_get_position_only_inside_window() {
        let p = LogicalPosition::new(12.0, 34.0);
        assert_eq!(CursorPosition::InWindow(p).get_position(), Some(p));
        assert_eq!(CursorPosition::OutOfWindow(p).get_position(), None);
        assert_eq!(CursorPosition::Uninitialized.get_position(), None);
        // Default (as used by MouseState::default) is Uninitialized.
        assert_eq!(CursorPosition::default(), CursorPosition::Uninitialized);
        assert_eq!(CursorPosition::default().get_position(), None);
    }
    #[test]
    fn cursor_position_is_inside_window_agrees_with_get_position() {
        for c in [
            CursorPosition::Uninitialized,
            CursorPosition::InWindow(LogicalPosition::zero()),
            CursorPosition::OutOfWindow(LogicalPosition::zero()),
            CursorPosition::InWindow(LogicalPosition::new(f32::MIN, f32::MAX)),
            CursorPosition::OutOfWindow(LogicalPosition::new(f32::INFINITY, f32::NAN)),
        ] {
            assert_eq!(c.is_inside_window(), c.get_position().is_some());
        }
        // Extreme / non-finite coordinates are passed through, not sanitized.
        let nan_pos = CursorPosition::InWindow(LogicalPosition::new(f32::NAN, f32::INFINITY));
        assert!(nan_pos.is_inside_window());
        let got = nan_pos.get_position().expect("InWindow always yields a position");
        assert!(got.x.is_nan());
        assert!(got.y.is_infinite());
    }
    // ---------------------------------------------------------------------
    // MonitorId constructors
    // ---------------------------------------------------------------------
    #[test]
    fn monitor_id_constructors_preserve_fields_at_extremes() {
        assert_eq!(MonitorId::PRIMARY, MonitorId { index: 0, hash: 0 });
        assert_eq!(MonitorId::new(0), MonitorId::PRIMARY);
        for index in [0usize, 1, 42, usize::MAX] {
            let m = MonitorId::new(index);
            assert_eq!(m.index, index);
            assert_eq!(m.hash, 0, "new() documents hash == 0");
            for hash in [0u64, 1, u64::MAX] {
                let m = MonitorId::from_index_and_hash(index, hash);
                assert_eq!(m.index, index);
                assert_eq!(m.hash, hash);
            }
        }
        // index and hash are independent coordinates of identity.
        assert_ne!(MonitorId::new(1), MonitorId::new(2));
        assert_ne!(
            MonitorId::from_index_and_hash(1, 7),
            MonitorId::from_index_and_hash(1, 8)
        );
    }
    #[test]
    fn monitor_id_from_properties_is_stable_and_index_independent() {
        let pos = LayoutPoint::new(-1920, 0);
        let size = LayoutSize::new(2560, 1440);
        let a = MonitorId::from_properties(0, "HDMI-1", pos, size);
        let b = MonitorId::from_properties(0, "HDMI-1", pos, size);
        assert_eq!(a, b, "hash must be stable across calls (persistable)");
        // The hash intentionally covers only the properties, not the runtime index.
        let reindexed = MonitorId::from_properties(7, "HDMI-1", pos, size);
        assert_eq!(reindexed.hash, a.hash);
        assert_eq!(reindexed.index, 7);
        assert_ne!(reindexed, a, "index is still part of identity");
    }
    #[test]
    fn monitor_id_from_properties_is_sensitive_to_each_property() {
        let pos = LayoutPoint::new(0, 0);
        let size = LayoutSize::new(1920, 1080);
        let base = MonitorId::from_properties(0, "DP-1", pos, size);
        // Changing any single property must change the hash.
        assert_ne!(
            base.hash,
            MonitorId::from_properties(0, "DP-2", pos, size).hash
        );
        assert_ne!(
            base.hash,
            MonitorId::from_properties(0, "DP-1", LayoutPoint::new(1, 0), size).hash
        );
        assert_ne!(
            base.hash,
            MonitorId::from_properties(0, "DP-1", LayoutPoint::new(0, 1), size).hash
        );
        assert_ne!(
            base.hash,
            MonitorId::from_properties(0, "DP-1", pos, LayoutSize::new(1921, 1080)).hash
        );
        assert_ne!(
            base.hash,
            MonitorId::from_properties(0, "DP-1", pos, LayoutSize::new(1920, 1081)).hash
        );
        // A swapped width/height is a different monitor, not the same one.
        assert_ne!(
            base.hash,
            MonitorId::from_properties(0, "DP-1", pos, LayoutSize::new(1080, 1920)).hash
        );
    }
    #[test]
    fn monitor_id_from_properties_handles_hostile_inputs() {
        let size = LayoutSize::new(isize::MAX, isize::MIN);
        let pos = LayoutPoint::new(isize::MIN, isize::MAX);
        // Empty / whitespace / unicode / NUL-containing names must not panic.
        for name in ["", "   ", "\t\n", "\u{1F600}", "e\u{301}", "é", "a\0b"] {
            let m = MonitorId::from_properties(3, name, pos, size);
            assert_eq!(m.index, 3);
            // Same input -> same hash, even at isize extremes.
            assert_eq!(m, MonitorId::from_properties(3, name, pos, size));
        }
        // Byte-exact name comparison: combining-mark and precomposed forms differ.
        assert_ne!(
            MonitorId::from_properties(0, "e\u{301}", pos, size).hash,
            MonitorId::from_properties(0, "é", pos, size).hash
        );
        // A 1M-char monitor name must terminate quickly (FNV-1a is linear).
        let huge = "x".repeat(1_000_000);
        let h1 = MonitorId::from_properties(0, &huge, LayoutPoint::zero(), LayoutSize::zero());
        let h2 = MonitorId::from_properties(0, &huge, LayoutPoint::zero(), LayoutSize::zero());
        assert_eq!(h1, h2);
        assert_ne!(
            h1.hash,
            MonitorId::from_properties(0, "x", LayoutPoint::zero(), LayoutSize::zero()).hash
        );
    }
    // ---------------------------------------------------------------------
    // WindowFlags predicates / getters
    // ---------------------------------------------------------------------
    #[test]
    fn window_flags_type_predicates_are_mutually_exclusive() {
        for (ty, menu, tooltip, dialog) in [
            (WindowType::Normal, false, false, false),
            (WindowType::Menu, true, false, false),
            (WindowType::Tooltip, false, true, false),
            (WindowType::Dialog, false, false, true),
        ] {
            let f = WindowFlags {
                window_type: ty,
                ..WindowFlags::default()
            };
            assert_eq!(f.is_menu_window(), menu);
            assert_eq!(f.is_tooltip_window(), tooltip);
            assert_eq!(f.is_dialog_window(), dialog);
            // At most one classification can ever be true at once.
            let count = u8::from(f.is_menu_window())
                + u8::from(f.is_tooltip_window())
                + u8::from(f.is_dialog_window());
            assert!(count <= 1);
        }
    }
    #[test]
    fn window_flags_bool_getters_mirror_their_fields() {
        // Default: focused, no close request, no CSD.
        let d = WindowFlags::default();
        assert!(d.window_has_focus());
        assert!(!d.is_close_requested());
        assert!(!d.has_csd());
        assert_eq!(
            d.use_native_menus(),
            cfg!(any(target_os = "windows", target_os = "macos"))
        );
        assert_eq!(
            d.use_native_context_menus(),
            cfg!(any(target_os = "windows", target_os = "macos"))
        );
        // Every getter is a pure mirror of its field, in both states.
        for b in [false, true] {
            let f = WindowFlags {
                has_focus: b,
                close_requested: b,
                has_decorations: b,
                use_native_menus: b,
                use_native_context_menus: b,
                ..WindowFlags::default()
            };
            assert_eq!(f.window_has_focus(), b);
            assert_eq!(f.is_close_requested(), b);
            assert_eq!(f.has_csd(), b);
            assert_eq!(f.use_native_menus(), b);
            assert_eq!(f.use_native_context_menus(), b);
        }
    }
    // ---------------------------------------------------------------------
    // StringPairVec: get_key / get_key_mut / insert_kv
    // ---------------------------------------------------------------------
    #[test]
    fn get_key_on_empty_vec_is_none() {
        let empty = StringPairVec::new();
        assert!(empty.get_key("").is_none());
        assert!(empty.get_key("anything").is_none());
        let mut empty_mut = StringPairVec::new();
        assert!(empty_mut.get_key_mut("").is_none());
        assert!(empty_mut.get_key_mut("anything").is_none());
    }
    #[test]
    fn get_key_valid_minimal_and_missing() {
        let v = StringPairVec::from_vec(vec![pair("WM_CLASS", "azul")]);
        assert_eq!(
            v.get_key("WM_CLASS").map(AzString::as_str),
            Some("azul"),
            "positive control"
        );
        assert!(v.get_key("wm_class").is_none(), "lookup is case-sensitive");
        assert!(v.get_key("WM_CLAS").is_none());
        assert!(v.get_key("WM_CLASS ").is_none(), "no trimming is performed");
        assert!(v.get_key(" WM_CLASS").is_none());
        assert!(v.get_key("WM_CLASS;garbage").is_none());
    }
    #[test]
    fn get_key_handles_garbage_whitespace_and_boundary_numbers() {
        let v = StringPairVec::from_vec(vec![
            pair("", "empty-key"),
            pair("   ", "spaces"),
            pair("\t\n", "tabs"),
            pair("0", "zero"),
            pair("-0", "neg-zero"),
            pair("9223372036854775807", "i64-max"),
            pair("NaN", "nan"),
            pair("inf", "inf"),
        ]);
        // Empty and whitespace-only keys are ordinary keys - looked up verbatim.
        assert_eq!(v.get_key("").map(AzString::as_str), Some("empty-key"));
        assert_eq!(v.get_key("   ").map(AzString::as_str), Some("spaces"));
        assert_eq!(v.get_key("\t\n").map(AzString::as_str), Some("tabs"));
        // Numeric-looking keys are compared as strings: "0" and "-0" are distinct.
        assert_eq!(v.get_key("0").map(AzString::as_str), Some("zero"));
        assert_eq!(v.get_key("-0").map(AzString::as_str), Some("neg-zero"));
        assert_eq!(
            v.get_key("9223372036854775807").map(AzString::as_str),
            Some("i64-max")
        );
        assert_eq!(v.get_key("NaN").map(AzString::as_str), Some("nan"));
        assert_eq!(v.get_key("inf").map(AzString::as_str), Some("inf"));
        assert!(v.get_key("nan").is_none());
        // Random non-grammar bytes / control chars / deep bracket nesting: None, no panic.
        assert!(v.get_key("\u{0}\u{1}\u{7f}\\x\"';--").is_none());
        assert!(v.get_key(&"[".repeat(10_000)).is_none());
        assert!(v.get_key(&"{\"a\":".repeat(10_000)).is_none());
    }
    #[test]
    fn get_key_handles_unicode_without_panicking() {
        let v = StringPairVec::from_vec(vec![
            pair("\u{1F600}", "grin"),
            pair("é", "precomposed"),
            pair("日本語", "jp"),
        ]);
        assert_eq!(v.get_key("\u{1F600}").map(AzString::as_str), Some("grin"));
        assert_eq!(v.get_key("日本語").map(AzString::as_str), Some("jp"));
        // No unicode normalization: decomposed "e" + combining acute != "é".
        assert_eq!(v.get_key("é").map(AzString::as_str), Some("precomposed"));
        assert!(v.get_key("e\u{301}").is_none());
        // A prefix of a multi-byte key must not match (no byte-slicing bugs).
        assert!(v.get_key("日本").is_none());
    }
    #[test]
    fn get_key_handles_extremely_long_input() {
        let huge = "k".repeat(1_000_000);
        let mut v = StringPairVec::from_vec(vec![pair("short", "1")]);
        // Searching for a 1M-char key that is not present: linear, terminates.
        assert!(v.get_key(&huge).is_none());
        // ...and one that IS present.
        v.push(AzStringPair {
            key: huge.as_str().into(),
            value: "big".into(),
        });
        assert_eq!(v.get_key(&huge).map(AzString::as_str), Some("big"));
        // Off-by-one on a 1M-char key must not match.
        assert!(v.get_key(&"k".repeat(999_999)).is_none());
        assert!(v.get_key(&"k".repeat(1_000_001)).is_none());
    }
    #[test]
    fn get_key_returns_first_of_duplicate_keys() {
        let v = StringPairVec::from_vec(vec![
            pair("dup", "first"),
            pair("dup", "second"),
            pair("dup", "third"),
        ]);
        assert_eq!(v.get_key("dup").map(AzString::as_str), Some("first"));
    }
    #[test]
    fn get_key_mut_mutates_in_place() {
        let mut v = StringPairVec::from_vec(vec![pair("a", "1"), pair("b", "2")]);
        {
            let entry = v.get_key_mut("b").expect("b is present");
            entry.value = "changed".into();
        }
        assert_eq!(v.get_key("b").map(AzString::as_str), Some("changed"));
        assert_eq!(v.get_key("a").map(AzString::as_str), Some("1"));
        assert!(v.get_key_mut("missing").is_none());
        assert_eq!(v.len(), 2, "get_key_mut must not add entries");
        // Mutating the KEY through get_key_mut is possible and re-targets lookups.
        {
            let entry = v.get_key_mut("a").expect("a is present");
            entry.key = "z".into();
        }
        assert!(v.get_key("a").is_none());
        assert_eq!(v.get_key("z").map(AzString::as_str), Some("1"));
    }
    #[test]
    fn insert_kv_updates_existing_and_appends_new() {
        let mut v = StringPairVec::new();
        v.insert_kv("k", "v1");
        assert_eq!(v.len(), 1);
        assert_eq!(v.get_key("k").map(AzString::as_str), Some("v1"));
        // Re-inserting the same key overwrites in place instead of appending.
        v.insert_kv("k", "v2");
        assert_eq!(v.len(), 1, "insert_kv must not duplicate an existing key");
        assert_eq!(v.get_key("k").map(AzString::as_str), Some("v2"));
        // A different key appends.
        v.insert_kv("other", "x");
        assert_eq!(v.len(), 2);
        assert_eq!(v.get_key("k").map(AzString::as_str), Some("v2"));
        assert_eq!(v.get_key("other").map(AzString::as_str), Some("x"));
        // Repeated inserts of the same key never grow the vec.
        for i in 0..100 {
            v.insert_kv(String::from("k"), format!("gen{i}"));
        }
        assert_eq!(v.len(), 2);
        assert_eq!(v.get_key("k").map(AzString::as_str), Some("gen99"));
    }
    #[test]
    fn insert_kv_accepts_hostile_keys_and_values() {
        let mut v = StringPairVec::new();
        v.insert_kv("", "");
        assert_eq!(v.len(), 1);
        assert_eq!(v.get_key("").map(AzString::as_str), Some(""));
        v.insert_kv("\u{1F600}", "😀");
        assert_eq!(v.get_key("\u{1F600}").map(AzString::as_str), Some("😀"));
        v.insert_kv("   ", "\t\n");
        assert_eq!(v.get_key("   ").map(AzString::as_str), Some("\t\n"));
        // Very long key + value: no hang, and the update path still finds it.
        let huge_key = "K".repeat(100_000);
        let huge_val = "V".repeat(100_000);
        v.insert_kv(huge_key.clone(), huge_val.clone());
        let before = v.len();
        assert_eq!(
            v.get_key(&huge_key).map(AzString::as_str),
            Some(huge_val.as_str())
        );
        v.insert_kv(huge_key.clone(), String::from("small"));
        assert_eq!(v.len(), before, "long key must hit the update path");
        assert_eq!(v.get_key(&huge_key).map(AzString::as_str), Some("small"));
    }
    #[test]
    fn insert_kv_only_updates_the_first_of_pre_existing_duplicates() {
        // Duplicates can only arrive via push()/from_vec(); insert_kv updates the
        // first match (get_key_mut semantics) and leaves the shadowed one stale.
        let mut v = StringPairVec::from_vec(vec![pair("dup", "first"), pair("dup", "second")]);
        v.insert_kv("dup", "updated");
        assert_eq!(v.len(), 2, "no new entry is appended");
        assert_eq!(v.get_key("dup").map(AzString::as_str), Some("updated"));
        assert_eq!(
            v.get(1).expect("second entry still present").value.as_str(),
            "second",
            "the shadowed duplicate is left untouched"
        );
    }
    // ---------------------------------------------------------------------
    // WindowSize getters (numeric saturation)
    // ---------------------------------------------------------------------
    #[test]
    fn window_size_get_logical_size_is_the_identity() {
        for dims in [
            LogicalSize::zero(),
            LogicalSize::new(640.0, 480.0),
            LogicalSize::new(-1.0, -2.0),
            LogicalSize::new(f32::MAX, f32::MIN),
            LogicalSize::new(f32::INFINITY, f32::MIN_POSITIVE),
        ] {
            let ws = WindowSize {
                dimensions: dims,
                ..WindowSize::default()
            };
            assert_eq!(ws.get_logical_size(), dims);
        }
        // Default is the documented 640x480 @ 96 DPI.
        let d = WindowSize::default();
        assert_eq!(d.get_logical_size(), LogicalSize::new(640.0, 480.0));
        assert_eq!(d.dpi, 96);
    }
    #[test]
    fn window_size_get_layout_size_rounds_half_away_from_zero() {
        for (w, h, ew, eh) in [
            (0.0f32, 0.0f32, 0isize, 0isize),
            (640.0, 480.0, 640, 480),
            (640.4, 480.4, 640, 480),
            (640.6, 480.6, 641, 481),
            (640.5, 639.5, 641, 640),
            (-0.5, -1.5, -1, -2),
        ] {
            let ws = WindowSize {
                dimensions: LogicalSize::new(w, h),
                ..WindowSize::default()
            };
            assert_eq!(ws.get_layout_size(), LayoutSize::new(ew, eh), "{w}x{h}");
        }
    }
    #[test]
    fn window_size_get_layout_size_saturates_on_non_finite() {
        // `as isize` saturates: NaN -> 0, +inf -> isize::MAX, -inf -> isize::MIN.
        let nan = WindowSize {
            dimensions: LogicalSize::new(f32::NAN, f32::NAN),
            ..WindowSize::default()
        };
        assert_eq!(nan.get_layout_size(), LayoutSize::new(0, 0));
        let inf = WindowSize {
            dimensions: LogicalSize::new(f32::INFINITY, f32::NEG_INFINITY),
            ..WindowSize::default()
        };
        assert_eq!(
            inf.get_layout_size(),
            LayoutSize::new(isize::MAX, isize::MIN)
        );
        let max = WindowSize {
            dimensions: LogicalSize::new(f32::MAX, f32::MIN),
            ..WindowSize::default()
        };
        let ls = max.get_layout_size();
        assert!(ls.width > 0 && ls.height < 0, "sign is preserved: {ls:?}");
    }
    #[test]
    fn window_size_get_physical_size_saturates_instead_of_wrapping() {
        // Negative logical sizes clamp to 0 (u32 cast drops the sign).
        let neg = WindowSize {
            dimensions: LogicalSize::new(-100.0, -0.4),
            ..WindowSize::default()
        };
        assert_eq!(neg.get_physical_size(), PhysicalSize::new(0, 0));
        // NaN -> 0, +inf -> u32::MAX (saturating float->int cast, no UB).
        let nan = WindowSize {
            dimensions: LogicalSize::new(f32::NAN, f32::INFINITY),
            ..WindowSize::default()
        };
        assert_eq!(nan.get_physical_size(), PhysicalSize::new(0, u32::MAX));
        // f32::MAX at 4x scale overflows u32 -> saturates, never wraps to a small value.
        let huge = WindowSize {
            dimensions: LogicalSize::new(f32::MAX, f32::MAX),
            dpi: 384,
            ..WindowSize::default()
        };
        assert_eq!(
            huge.get_physical_size(),
            PhysicalSize::new(u32::MAX, u32::MAX)
        );
        // The normal path: 96 DPI is 1:1, 192 DPI doubles.
        let normal = WindowSize::default();
        assert_eq!(normal.get_physical_size(), PhysicalSize::new(640, 480));
        let retina = WindowSize {
            dpi: 192,
            ..WindowSize::default()
        };
        assert_eq!(retina.get_physical_size(), PhysicalSize::new(1280, 960));
    }
    #[test]
    fn window_size_get_hidpi_factor_is_never_zero_or_negative() {
        // A 0.0 factor would divide-by-zero in to_logical(); the getter guards dpi == 0.
        for dpi in [
            0u32,
            1,
            47,
            48,
            95,
            96,
            97,
            120,
            144,
            192,
            384,
            u32::from(u16::MAX),
            u32::MAX,
        ] {
            let ws = WindowSize {
                dpi,
                ..WindowSize::default()
            };
            let f = ws.get_hidpi_factor().inner.get();
            assert!(
                f.is_finite() && f > 0.0,
                "dpi {dpi} produced a non-positive / non-finite scale factor: {f}"
            );
        }
        // Exactly representable factors must be exact (no quantization drift).
        for (dpi, expected) in [(0u32, 1.0f32), (96, 1.0), (144, 1.5), (192, 2.0), (384, 4.0)] {
            let ws = WindowSize {
                dpi,
                ..WindowSize::default()
            };
            assert_eq!(ws.get_hidpi_factor().inner.get(), expected, "dpi {dpi}");
        }
        // Non-representable factors stay within FloatValue's 1/1000 quantization.
        let odd = WindowSize {
            dpi: 100,
            ..WindowSize::default()
        };
        let f = odd.get_hidpi_factor().inner.get();
        assert!((f - 100.0 / 96.0).abs() < 0.002, "dpi 100 -> {f}");
    }
    // ---------------------------------------------------------------------
    // VirtualKeyCode: from_u32 / get_lowercase
    // ---------------------------------------------------------------------
    #[test]
    fn virtual_keycode_from_u32_roundtrips_every_discriminant() {
        for v in 0..=LAST_VK {
            let vk = VirtualKeyCode::from_u32(v)
                .unwrap_or_else(|| panic!("discriminant {v} is missing from the from_u32 table"));
            assert_eq!(vk as u32, v, "from_u32({v}) does not round-trip");
        }
        // First and last declared variants anchor the table.
        assert_eq!(VirtualKeyCode::Key1 as u32, 0);
        assert_eq!(VirtualKeyCode::Cut as u32, LAST_VK);
    }
    #[test]
    fn virtual_keycode_from_u32_rejects_out_of_range() {
        for v in [
            LAST_VK + 1,
            LAST_VK + 2,
            255,
            256,
            1024,
            i32::MAX as u32,
            u32::MAX - 1,
            u32::MAX,
        ] {
            assert_eq!(
                VirtualKeyCode::from_u32(v),
                None,
                "{v} must not decode to a keycode"
            );
        }
    }
    #[test]
    fn virtual_keycode_get_lowercase_never_panics_and_maps_letters_and_digits() {
        // Exhaustive: no keycode may panic, and any produced char is ASCII.
        for v in 0..=LAST_VK {
            let vk = VirtualKeyCode::from_u32(v).expect("discriminant in range");
            if let Some(c) = vk.get_lowercase() {
                assert!(c.is_ascii(), "{vk:?} produced a non-ASCII char {c:?}");
                assert!(!c.is_ascii_uppercase(), "{vk:?} must yield lowercase");
            }
        }
        // Letters A..Z are discriminants 10..=35 and map to 'a'..='z'.
        for (i, expected) in ('a'..='z').enumerate() {
            let vk = VirtualKeyCode::from_u32(10 + i as u32).expect("letter range");
            assert_eq!(vk.get_lowercase(), Some(expected));
        }
        // Digits: both the top row and the numpad map to the same char.
        for (top, pad, c) in [
            (VirtualKeyCode::Key0, VirtualKeyCode::Numpad0, '0'),
            (VirtualKeyCode::Key1, VirtualKeyCode::Numpad1, '1'),
            (VirtualKeyCode::Key5, VirtualKeyCode::Numpad5, '5'),
            (VirtualKeyCode::Key9, VirtualKeyCode::Numpad9, '9'),
        ] {
            assert_eq!(top.get_lowercase(), Some(c));
            assert_eq!(pad.get_lowercase(), Some(c));
        }
        // Punctuation that IS mapped.
        assert_eq!(VirtualKeyCode::Minus.get_lowercase(), Some('-'));
        assert_eq!(VirtualKeyCode::Period.get_lowercase(), Some('.'));
        assert_eq!(VirtualKeyCode::Slash.get_lowercase(), Some('/'));
        assert_eq!(VirtualKeyCode::Caret.get_lowercase(), Some('^'));
        // Non-character keys have no lowercase form.
        for vk in [
            VirtualKeyCode::LShift,
            VirtualKeyCode::RControl,
            VirtualKeyCode::Escape,
            VirtualKeyCode::F12,
            VirtualKeyCode::Space,
            VirtualKeyCode::Return,
            VirtualKeyCode::Back,
        ] {
            assert_eq!(vk.get_lowercase(), None, "{vk:?}");
        }
    }
    // ---------------------------------------------------------------------
    // WindowIcon::get_key + key-only Eq/Ord/Hash
    // ---------------------------------------------------------------------
    #[test]
    fn window_icon_get_key_returns_the_stored_key() {
        let small_key = IconKey::new();
        let large_key = IconKey::new();
        let small = WindowIcon::Small(SmallWindowIconBytes {
            key: small_key,
            rgba_bytes: vec![0u8; 16 * 16 * 4].into(),
        });
        let large = WindowIcon::Large(LargeWindowIconBytes {
            key: large_key,
            rgba_bytes: vec![255u8; 32 * 32 * 4].into(),
        });
        assert_eq!(small.get_key(), small_key);
        assert_eq!(large.get_key(), large_key);
        // Empty payloads are legal and must not panic.
        let empty = WindowIcon::Small(SmallWindowIconBytes {
            key: small_key,
            rgba_bytes: vec![].into(),
        });
        assert_eq!(empty.get_key(), small_key);
    }
    #[test]
    fn window_icon_identity_is_the_key_alone() {
        // The whole point of IconKey: diff the key, not the bytes. Two icons with
        // the same key compare equal even though their pixels differ.
        let key = IconKey::new();
        let a = WindowIcon::Small(SmallWindowIconBytes {
            key,
            rgba_bytes: vec![0u8; 4].into(),
        });
        let b = WindowIcon::Small(SmallWindowIconBytes {
            key,
            rgba_bytes: vec![7u8; 1024].into(),
        });
        // ...even across the Small/Large variants.
        let c = WindowIcon::Large(LargeWindowIconBytes {
            key,
            rgba_bytes: vec![9u8; 32 * 32 * 4].into(),
        });
        assert_eq!(a, b);
        assert_eq!(a, c);
        assert_eq!(a.cmp(&c), Ordering::Equal);
        assert_eq!(a.partial_cmp(&b), Some(Ordering::Equal));
        // Hash must agree with Eq, or icons break as BTreeMap/HashMap keys.
        assert_eq!(hash_of(&a), hash_of(&b));
        assert_eq!(hash_of(&a), hash_of(&c));
        // Different keys: unequal, and ordered by key.
        let older = WindowIcon::Small(SmallWindowIconBytes {
            key,
            rgba_bytes: vec![0u8; 4].into(),
        });
        let newer = WindowIcon::Small(SmallWindowIconBytes {
            key: IconKey::new(),
            rgba_bytes: vec![0u8; 4].into(),
        });
        assert_ne!(older, newer);
        assert_eq!(older.cmp(&newer), Ordering::Less);
    }
    // ---------------------------------------------------------------------
    // UpdateFocusWarning: Display
    // ---------------------------------------------------------------------
    #[test]
    fn update_focus_warning_display_is_non_empty_for_every_variant() {
        let dom = format!("{}", UpdateFocusWarning::FocusInvalidDomId(DomId::ROOT_ID));
        assert!(dom.contains("invalid ID"), "{dom}");
        assert!(!dom.is_empty());
        let node = format!(
            "{}",
            UpdateFocusWarning::FocusInvalidNodeId(NodeHierarchyItemId::NONE)
        );
        assert!(node.contains("invalid ID"), "{node}");
        // Edge values: a zero DomId, a raw-encoded huge node id, an empty CssPath.
        let huge = format!(
            "{}",
            UpdateFocusWarning::FocusInvalidNodeId(NodeHierarchyItemId::from_raw(usize::MAX))
        );
        assert!(!huge.is_empty());
        let path = format!(
            "{}",
            UpdateFocusWarning::CouldNotFindFocusNode(CssPath::default())
        );
        assert!(
            path.starts_with("Could not find focus node for path:"),
            "{path}"
        );
    }
}