1#[cfg(not(feature = "std"))]
19use alloc::string::{String, ToString};
20use alloc::{
21 boxed::Box,
22 collections::{btree_map::BTreeMap, btree_set::BTreeSet},
23 vec::Vec,
24};
25use core::{
26 cmp::Ordering,
27 ffi::c_void,
28 hash::{Hash, Hasher},
29 ops,
30 sync::atomic::{AtomicI64, AtomicUsize, Ordering as AtomicOrdering},
31};
32
33use 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};
41use rust_fontconfig::FcFontCache;
42
43use 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
64pub const DEFAULT_TITLE: &str = "Azul App";
65
66static LAST_WINDOW_ID: AtomicI64 = AtomicI64::new(0);
67
68#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
70#[repr(transparent)]
71pub struct WindowId {
72 pub id: i64,
73}
74
75impl Default for WindowId {
76 fn default() -> Self {
77 Self::new()
78 }
79}
80
81impl WindowId {
82 pub fn new() -> Self {
83 Self {
84 id: LAST_WINDOW_ID.fetch_add(1, AtomicOrdering::SeqCst),
85 }
86 }
87}
88
89static LAST_ICON_KEY: AtomicUsize = AtomicUsize::new(0);
90
91#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
95#[repr(C)]
96pub struct IconKey {
97 icon_id: usize,
98}
99
100impl Default for IconKey {
101 fn default() -> Self {
102 Self::new()
103 }
104}
105
106impl IconKey {
107 pub fn new() -> Self {
108 Self {
109 icon_id: LAST_ICON_KEY.fetch_add(1, AtomicOrdering::SeqCst),
110 }
111 }
112}
113
114#[repr(C)]
115#[derive(PartialEq, Copy, Clone, Debug, PartialOrd, Ord, Eq, Hash)]
116pub struct RendererOptions {
117 pub vsync: Vsync,
118 pub srgb: Srgb,
119 pub hw_accel: HwAcceleration,
120}
121
122impl_option!(
123 RendererOptions,
124 OptionRendererOptions,
125 [PartialEq, Copy, Clone, Debug, PartialOrd, Ord, Eq, Hash]
126);
127
128impl Default for RendererOptions {
129 fn default() -> Self {
130 Self {
131 vsync: Vsync::Enabled,
132 srgb: Srgb::Disabled,
133 hw_accel: HwAcceleration::DontCare,
138 }
139 }
140}
141
142impl RendererOptions {
143 #[must_use] pub const fn new(vsync: Vsync, srgb: Srgb, hw_accel: HwAcceleration) -> Self {
144 Self {
145 vsync,
146 srgb,
147 hw_accel,
148 }
149 }
150}
151
152#[repr(C)]
153#[derive(PartialEq, Copy, Clone, Debug, PartialOrd, Ord, Eq, Hash)]
154pub enum Vsync {
155 Enabled,
156 Disabled,
157 DontCare,
158}
159
160impl Vsync {
161 #[must_use] pub const fn is_enabled(&self) -> bool {
162 matches!(self, Self::Enabled)
163 }
164}
165
166#[repr(C)]
167#[derive(PartialEq, Copy, Clone, Debug, PartialOrd, Ord, Eq, Hash)]
168pub enum Srgb {
169 Enabled,
170 Disabled,
171 DontCare,
172}
173impl Srgb {
174 #[must_use] pub const fn is_enabled(&self) -> bool {
175 matches!(self, Self::Enabled)
176 }
177}
178
179#[repr(C)]
180#[derive(PartialEq, Copy, Clone, Debug, PartialOrd, Ord, Eq, Hash)]
181pub enum HwAcceleration {
182 Enabled,
183 Disabled,
184 DontCare,
185}
186impl HwAcceleration {
187 #[must_use] pub const fn is_enabled(&self) -> bool {
188 matches!(self, Self::Enabled)
189 }
190}
191
192#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
193#[repr(C, u8)]
194pub 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
206unsafe impl Send for RawWindowHandle {}
210
211#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
212#[repr(C)]
213pub 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)]
221pub 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)]
228pub struct XlibHandle {
229 pub window: u64,
231 pub display: *mut c_void,
232}
233
234#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
235#[repr(C)]
236pub struct XcbHandle {
237 pub window: u32,
239 pub connection: *mut c_void,
241}
242
243#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
244#[repr(C)]
245pub struct WaylandHandle {
246 pub surface: *mut c_void,
248 pub display: *mut c_void,
250}
251
252#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
253#[repr(C)]
254pub struct WindowsHandle {
255 pub hwnd: *mut c_void,
257 pub hinstance: *mut c_void,
259}
260
261#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
262#[repr(C)]
263pub struct WebHandle {
264 pub id: u32,
270}
271
272#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
273#[repr(C)]
274pub struct AndroidHandle {
275 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)]
282pub 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
322pub type ScanCode = u32;
324
325#[derive(Default, Debug, Clone, PartialEq, Eq)]
327#[repr(C)]
328pub struct KeyboardState {
329 pub current_virtual_keycode: OptionVirtualKeyCode,
335 pub pressed_virtual_keycodes: VirtualKeyCodeVec,
346 pub pressed_scancodes: ScanCodeVec,
352}
353
354impl KeyboardState {
355 #[must_use] pub fn shift_down(&self) -> bool {
356 self.is_key_down(VirtualKeyCode::LShift) || self.is_key_down(VirtualKeyCode::RShift)
357 }
358 #[must_use] pub fn ctrl_down(&self) -> bool {
359 self.is_key_down(VirtualKeyCode::LControl) || self.is_key_down(VirtualKeyCode::RControl)
360 }
361 #[must_use] pub fn alt_down(&self) -> bool {
362 self.is_key_down(VirtualKeyCode::LAlt) || self.is_key_down(VirtualKeyCode::RAlt)
363 }
364 #[must_use] pub fn super_down(&self) -> bool {
365 self.is_key_down(VirtualKeyCode::LWin) || self.is_key_down(VirtualKeyCode::RWin)
366 }
367 #[must_use] pub fn primary_down(&self) -> bool {
373 if cfg!(target_os = "macos") {
374 self.super_down()
375 } else {
376 self.ctrl_down()
377 }
378 }
379 #[must_use] pub fn is_key_down(&self, key: VirtualKeyCode) -> bool {
380 self.pressed_virtual_keycodes.iter().any(|k| *k == key)
381 }
382
383 #[must_use] pub fn matches_accelerator(&self, chord: &[AcceleratorKey]) -> bool {
389 chord.iter().all(|a| a.matches(self))
390 }
391}
392
393impl_option!(
394 KeyboardState,
395 OptionKeyboardState,
396 copy = false,
397 [Debug, Clone, PartialEq, Eq]
398);
399
400impl_option!(
402 u32,
403 OptionChar,
404 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
405);
406impl_option!(
407 VirtualKeyCode,
408 OptionVirtualKeyCode,
409 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
410);
411
412impl_vec!(VirtualKeyCode, VirtualKeyCodeVec, VirtualKeyCodeVecDestructor, VirtualKeyCodeVecDestructorType, VirtualKeyCodeVecSlice, OptionVirtualKeyCode);
413impl_vec_debug!(VirtualKeyCode, VirtualKeyCodeVec);
414impl_vec_partialord!(VirtualKeyCode, VirtualKeyCodeVec);
415impl_vec_ord!(VirtualKeyCode, VirtualKeyCodeVec);
416impl_vec_clone!(
417 VirtualKeyCode,
418 VirtualKeyCodeVec,
419 VirtualKeyCodeVecDestructor
420);
421impl_vec_partialeq!(VirtualKeyCode, VirtualKeyCodeVec);
422impl_vec_eq!(VirtualKeyCode, VirtualKeyCodeVec);
423impl_vec_hash!(VirtualKeyCode, VirtualKeyCodeVec);
424impl_vec_mut!(VirtualKeyCode, VirtualKeyCodeVec);
425
426impl_vec_as_hashmap!(VirtualKeyCode, VirtualKeyCodeVec);
427
428impl_vec!(ScanCode, ScanCodeVec, ScanCodeVecDestructor, ScanCodeVecDestructorType, ScanCodeVecSlice, OptionU32);
429impl_vec_debug!(ScanCode, ScanCodeVec);
430impl_vec_partialord!(ScanCode, ScanCodeVec);
431impl_vec_ord!(ScanCode, ScanCodeVec);
432impl_vec_clone!(ScanCode, ScanCodeVec, ScanCodeVecDestructor);
433impl_vec_partialeq!(ScanCode, ScanCodeVec);
434impl_vec_eq!(ScanCode, ScanCodeVec);
435impl_vec_hash!(ScanCode, ScanCodeVec);
436impl_vec_mut!(ScanCode, ScanCodeVec);
437
438impl_vec_as_hashmap!(ScanCode, ScanCodeVec);
439
440#[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Eq)]
442#[repr(C)]
443pub struct MouseState {
444 pub mouse_cursor_type: OptionMouseCursorType,
446 pub cursor_position: CursorPosition,
449 pub is_cursor_locked: bool,
452 pub left_down: bool,
454 pub right_down: bool,
456 pub middle_down: bool,
458}
459
460impl MouseState {
461 #[must_use] pub const fn matches(&self, context: &ContextMenuMouseButton) -> bool {
462 use self::ContextMenuMouseButton::{Left, Right, Middle};
463 match context {
464 Left => self.left_down,
465 Right => self.right_down,
466 Middle => self.middle_down,
467 }
468 }
469}
470
471impl_option!(
472 MouseState,
473 OptionMouseState,
474 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd]
475);
476
477impl_option!(
478 MouseCursorType,
479 OptionMouseCursorType,
480 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
481);
482
483impl Default for MouseState {
484 fn default() -> Self {
485 Self {
486 mouse_cursor_type: Some(MouseCursorType::Default).into(),
487 cursor_position: CursorPosition::default(),
488 is_cursor_locked: false,
489 left_down: false,
490 right_down: false,
491 middle_down: false,
492 }
493 }
494}
495
496#[derive(Debug, Clone, PartialEq, PartialOrd, Hash, Eq, Ord)]
497#[repr(C)]
498pub struct VirtualKeyCodeCombo {
499 pub keys: VirtualKeyCodeVec,
500}
501
502impl_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)]
512pub enum ContextMenuMouseButton {
513 #[default]
514 Right,
515 Middle,
516 Left,
517}
518
519
520impl MouseState {
521 #[must_use] pub const fn mouse_down(&self) -> bool {
523 self.right_down || self.left_down || self.middle_down
524 }
525
526 #[must_use] pub const fn button_state(&self) -> crate::events::MouseButtonState {
528 crate::events::MouseButtonState {
529 left_down: self.left_down,
530 right_down: self.right_down,
531 middle_down: self.middle_down,
532 }
533 }
534}
535
536impl From<&MouseState> for crate::events::MouseButtonState {
537 fn from(s: &MouseState) -> Self {
538 s.button_state()
539 }
540}
541
542impl crate::events::MouseButtonState {
543 #[must_use] pub const fn any_down(&self) -> bool {
545 self.left_down || self.right_down || self.middle_down
546 }
547}
548
549#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd)]
556#[repr(C)]
557pub struct ScrollResult {
558 pub scrolled_nodes: usize,
560 pub remaining_delta: LogicalPosition,
562 pub hit_scrollbar: bool,
564}
565
566#[must_use] pub fn process_system_scroll(delta: LogicalPosition, hit_scrollbar: bool) -> ScrollResult {
575 let consumed = delta.x != 0.0 || delta.y != 0.0;
576 ScrollResult {
577 scrolled_nodes: usize::from(consumed),
578 remaining_delta: LogicalPosition::zero(),
579 hit_scrollbar,
580 }
581}
582
583#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd)]
584#[repr(C, u8)]
585#[derive(Default)]
586pub enum CursorPosition {
587 OutOfWindow(LogicalPosition),
588 #[default]
589 Uninitialized,
590 InWindow(LogicalPosition),
591}
592
593
594impl CursorPosition {
595 #[must_use] pub const fn get_position(&self) -> Option<LogicalPosition> {
596 match self {
597 Self::InWindow(logical_pos) => Some(*logical_pos),
598 Self::OutOfWindow(_) | Self::Uninitialized => None,
599 }
600 }
601
602 #[must_use] pub const fn is_inside_window(&self) -> bool {
603 self.get_position().is_some()
604 }
605}
606
607#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
610#[repr(C)]
611pub struct DebugState {
612 pub profiler_dbg: bool,
613 pub render_target_dbg: bool,
614 pub texture_cache_dbg: bool,
615 pub gpu_time_queries: bool,
616 pub gpu_sample_queries: bool,
617 pub disable_batching: bool,
618 pub epochs: bool,
619 pub echo_driver_messages: bool,
620 pub show_overdraw: bool,
621 pub gpu_cache_dbg: bool,
622 pub texture_cache_dbg_clear_evicted: bool,
623 pub picture_caching_dbg: bool,
624 pub primitive_dbg: bool,
625 pub zoom_dbg: bool,
626 pub small_screen: bool,
627 pub disable_opaque_pass: bool,
628 pub disable_alpha_pass: bool,
629 pub disable_clip_masks: bool,
630 pub disable_text_prims: bool,
631 pub disable_gradient_prims: bool,
632 pub obscure_images: bool,
633 pub glyph_flashing: bool,
634 pub smart_profiler: bool,
635 pub invalidation_dbg: bool,
636 pub tile_cache_logging_dbg: bool,
637 pub profiler_capture: bool,
638 pub force_picture_invalidation: bool,
639}
640
641#[derive(Debug, Default, Clone, PartialEq)]
642#[repr(C)]
643pub struct TouchState {
644 pub num_touches: usize,
646 pub touch_points: TouchPointVec,
649}
650
651#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
653#[repr(C)]
654pub struct TouchPoint {
655 pub id: u64,
657 pub position: LogicalPosition,
659 pub force: f32,
662}
663
664impl_option!(
665 TouchPoint,
666 OptionTouchPoint,
667 [Debug, Copy, Clone, PartialEq, PartialOrd]
668);
669
670impl_vec!(TouchPoint, TouchPointVec, TouchPointVecDestructor, TouchPointVecDestructorType, TouchPointVecSlice, OptionTouchPoint);
671impl_vec_debug!(TouchPoint, TouchPointVec);
672impl_vec_clone!(TouchPoint, TouchPointVec, TouchPointVecDestructor);
673impl_vec_partialeq!(TouchPoint, TouchPointVec);
674
675#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Hash, Ord, Eq)]
677#[repr(C)]
678#[derive(Default)]
679pub enum WindowTheme {
680 DarkMode,
681 #[default]
682 LightMode,
683}
684
685
686impl_option!(
687 WindowTheme,
688 OptionWindowTheme,
689 [Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash]
690);
691
692#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
703#[repr(C)]
704pub struct MonitorId {
705 pub index: usize,
707 pub hash: u64,
709}
710
711impl MonitorId {
712 pub const PRIMARY: Self = Self { index: 0, hash: 0 };
714
715 #[must_use] pub const fn new(index: usize) -> Self {
717 Self { index, hash: 0 }
718 }
719
720 #[must_use] pub const fn from_index_and_hash(index: usize, hash: u64) -> Self {
722 Self { index, hash }
723 }
724
725 #[must_use] pub fn from_properties(
731 index: usize,
732 name: &str,
733 position: LayoutPoint,
734 size: LayoutSize,
735 ) -> Self {
736 use core::hash::{Hash, Hasher};
737
738 struct FnvHasher(u64);
740
741 impl Hasher for FnvHasher {
742 fn write(&mut self, bytes: &[u8]) {
743 const FNV_PRIME: u64 = 0x0100_0000_01b3;
744 for &byte in bytes {
745 self.0 ^= u64::from(byte);
746 self.0 = self.0.wrapping_mul(FNV_PRIME);
747 }
748 }
749
750 fn finish(&self) -> u64 {
751 self.0
752 }
753 }
754
755 const FNV_OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
756 let mut hasher = FnvHasher(FNV_OFFSET_BASIS);
757
758 name.hash(&mut hasher);
760 (position.x as i64).hash(&mut hasher);
761 (position.y as i64).hash(&mut hasher);
762 (size.width as i64).hash(&mut hasher);
763 (size.height as i64).hash(&mut hasher);
764
765 Self {
766 index,
767 hash: hasher.finish(),
768 }
769 }
770}
771
772impl_option!(
773 MonitorId,
774 OptionMonitorId,
775 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
776);
777
778#[derive(Debug, PartialEq, PartialOrd, Clone)]
780#[repr(C)]
781pub struct Monitor {
782 pub monitor_id: MonitorId,
784 pub monitor_name: OptionString,
786 pub size: LayoutSize,
788 pub position: LayoutPoint,
790 pub scale_factor: f64,
792 pub work_area: LayoutRect,
794 pub video_modes: VideoModeVec,
796 pub is_primary_monitor: bool,
798}
799
800impl_option!(
801 Monitor,
802 OptionMonitor,
803 copy = false,
804 [Debug, PartialEq, PartialOrd, Clone]
805);
806
807impl_vec!(Monitor, MonitorVec, MonitorVecDestructor, MonitorVecDestructorType, MonitorVecSlice, OptionMonitor);
808impl_vec_debug!(Monitor, MonitorVec);
809impl_vec_clone!(Monitor, MonitorVec, MonitorVecDestructor);
810impl_vec_partialeq!(Monitor, MonitorVec);
811impl_vec_partialord!(Monitor, MonitorVec);
812
813impl Hash for Monitor {
814 fn hash<H>(&self, state: &mut H)
815 where
816 H: Hasher,
817 {
818 self.monitor_id.hash(state);
819 }
820}
821
822impl Default for Monitor {
823 fn default() -> Self {
824 Self {
825 monitor_id: MonitorId::PRIMARY,
826 monitor_name: OptionString::None,
827 size: LayoutSize::zero(),
828 position: LayoutPoint::zero(),
829 scale_factor: 1.0,
830 work_area: LayoutRect::zero(),
831 video_modes: Vec::new().into(),
832 is_primary_monitor: false,
833 }
834 }
835}
836#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
837#[repr(C)]
838pub struct VideoMode {
839 pub size: LayoutSize,
840 pub bit_depth: u16,
841 pub refresh_rate: u16,
842}
843
844impl_option!(
845 VideoMode,
846 OptionVideoMode,
847 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
848);
849
850impl_vec!(VideoMode, VideoModeVec, VideoModeVecDestructor, VideoModeVecDestructorType, VideoModeVecSlice, OptionVideoMode);
851impl_vec_clone!(VideoMode, VideoModeVec, VideoModeVecDestructor);
852impl_vec_debug!(VideoMode, VideoModeVec);
853impl_vec_partialeq!(VideoMode, VideoModeVec);
854impl_vec_partialord!(VideoMode, VideoModeVec);
855
856#[derive(Debug, Copy, Clone, PartialEq, Eq)]
858#[repr(C, u8)]
859#[derive(Default)]
860pub enum WindowPosition {
861 #[default]
862 Uninitialized,
863 Initialized(PhysicalPositionI32),
866 RelativeToParentWindow(PhysicalPositionI32),
874}
875#[allow(variant_size_differences)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
878#[repr(C, u8)]
879#[derive(Default)]
881pub enum ImePosition {
882 #[default]
883 Uninitialized,
884 Initialized(LogicalRect),
885}
886
887
888#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
889#[repr(C)]
890pub struct WindowFlags {
891 pub frame: WindowFrame,
893 pub decorations: WindowDecorations,
895 pub background_material: WindowBackgroundMaterial,
897 pub window_type: WindowType,
899 pub close_requested: bool,
902 pub is_visible: bool,
904 pub is_always_on_top: bool,
906 pub is_resizable: bool,
908 pub has_focus: bool,
910 pub smooth_scroll_enabled: bool,
912 pub autotab_enabled: bool,
914 pub has_decorations: bool,
917 pub use_native_menus: bool,
920 pub use_native_context_menus: bool,
923 pub is_top_level: bool,
927 pub prevent_system_sleep: bool,
931 pub fullscreen_mode: FullScreenMode,
940}
941
942impl_option!(
943 WindowFlags,
944 OptionWindowFlags,
945 copy = false,
946 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
947);
948
949#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
951#[repr(C)]
952pub enum WindowType {
953 Normal,
955 Menu,
957 Tooltip,
959 Dialog,
961}
962
963impl Default for WindowType {
964 fn default() -> Self {
965 Self::Normal
966 }
967}
968
969#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
971#[repr(C)]
972pub enum WindowFrame {
973 Normal,
974 Minimized,
975 Maximized,
976 Fullscreen,
977}
978
979#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
981#[repr(C)]
982pub enum WindowDecorations {
983 Normal,
985 NoTitle,
988 NoTitleAutoInject,
996 NoControls,
998 None,
1000}
1001
1002impl Default for WindowDecorations {
1003 fn default() -> Self {
1004 Self::Normal
1005 }
1006}
1007
1008#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
1010#[repr(C)]
1011pub enum WindowBackgroundMaterial {
1012 Opaque,
1014 Transparent,
1016 Sidebar,
1018 Menu,
1020 HUD,
1022 Titlebar,
1024 MicaAlt,
1026}
1027
1028impl Default for WindowBackgroundMaterial {
1029 fn default() -> Self {
1030 Self::Opaque
1031 }
1032}
1033
1034impl Default for WindowFlags {
1035 fn default() -> Self {
1036 Self {
1037 frame: WindowFrame::Normal,
1038 decorations: WindowDecorations::Normal,
1039 background_material: WindowBackgroundMaterial::Opaque,
1040 window_type: WindowType::Normal,
1041 close_requested: false,
1042 is_visible: true,
1043 is_always_on_top: false,
1044 is_resizable: true,
1045 has_focus: true,
1046 smooth_scroll_enabled: true,
1047 autotab_enabled: true,
1048 has_decorations: false,
1049 use_native_menus: cfg!(any(target_os = "windows", target_os = "macos")),
1052 use_native_context_menus: cfg!(any(target_os = "windows", target_os = "macos")),
1053 is_top_level: false,
1054 prevent_system_sleep: false,
1055 fullscreen_mode: FullScreenMode::FastFullScreen,
1056 }
1057 }
1058}
1059
1060impl WindowFlags {
1061 #[inline]
1063 #[must_use] pub fn is_menu_window(&self) -> bool {
1064 self.window_type == WindowType::Menu
1065 }
1066
1067 #[inline]
1069 #[must_use] pub fn is_tooltip_window(&self) -> bool {
1070 self.window_type == WindowType::Tooltip
1071 }
1072
1073 #[inline]
1075 #[must_use] pub fn is_dialog_window(&self) -> bool {
1076 self.window_type == WindowType::Dialog
1077 }
1078
1079 #[inline]
1081 #[must_use] pub const fn window_has_focus(&self) -> bool {
1082 self.has_focus
1083 }
1084
1085 #[inline]
1087 #[must_use] pub const fn is_close_requested(&self) -> bool {
1088 self.close_requested
1089 }
1090
1091 #[inline]
1093 #[must_use] pub const fn has_csd(&self) -> bool {
1094 self.has_decorations
1095 }
1096
1097 #[inline]
1099 #[must_use] pub const fn use_native_menus(&self) -> bool {
1100 self.use_native_menus
1101 }
1102
1103 #[inline]
1105 #[must_use] pub const fn use_native_context_menus(&self) -> bool {
1106 self.use_native_context_menus
1107 }
1108}
1109
1110#[derive(Debug, Default, Clone, PartialEq, PartialOrd)]
1112#[repr(C)]
1113pub struct PlatformSpecificOptions {
1114 pub windows_options: WindowsWindowOptions,
1115 pub linux_options: LinuxWindowOptions,
1116 pub mac_options: MacWindowOptions,
1117 pub wasm_options: WasmWindowOptions,
1118}
1119
1120unsafe impl Sync for PlatformSpecificOptions {}
1123#[allow(clippy::non_send_fields_in_send_ty)] unsafe impl Send for PlatformSpecificOptions {}
1125
1126#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
1127#[repr(C)]
1128pub struct WindowsWindowOptions {
1129 pub allow_drag_and_drop: bool,
1131 pub no_redirection_bitmap: bool,
1133 pub window_icon: OptionWindowIcon,
1135 pub taskbar_icon: OptionTaskBarIcon,
1139 }
1144
1145impl Default for WindowsWindowOptions {
1146 fn default() -> Self {
1147 Self {
1148 allow_drag_and_drop: true,
1149 no_redirection_bitmap: false,
1150 window_icon: OptionWindowIcon::None,
1151 taskbar_icon: OptionTaskBarIcon::None,
1152 }
1153 }
1154}
1155
1156#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1159#[repr(C)]
1160#[derive(Default)]
1161pub enum XWindowType {
1162 Desktop,
1166 Dock,
1169 Toolbar,
1171 Menu,
1173 Utility,
1175 Splash,
1177 Dialog,
1179 DropdownMenu,
1182 PopupMenu,
1185 Tooltip,
1188 Notification,
1191 Combo,
1194 Dnd,
1197 #[default]
1199 Normal,
1200}
1201
1202impl_option!(
1203 XWindowType,
1204 OptionXWindowType,
1205 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
1206);
1207
1208
1209#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
1210#[repr(C)]
1211#[derive(Default)]
1212pub enum UserAttentionType {
1213 #[default]
1214 None,
1215 Critical,
1216 Informational,
1217}
1218
1219
1220#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
1222#[repr(C)]
1223pub struct LinuxDecorationsState {
1224 pub is_dragging_titlebar: bool,
1225 pub close_button_hover: bool,
1226 pub maximize_button_hover: bool,
1227 pub minimize_button_hover: bool,
1228}
1229
1230impl_option!(
1231 LinuxDecorationsState,
1232 OptionLinuxDecorationsState,
1233 [Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash]
1234);
1235
1236#[derive(Debug, Default, Clone, PartialEq, PartialOrd)]
1237#[repr(C)]
1238pub struct LinuxWindowOptions {
1239 pub wayland_theme: OptionWaylandTheme,
1240 pub window_icon: OptionWindowIcon,
1241 pub x11_gtk_theme_variant: OptionString,
1244 pub wayland_app_id: OptionString,
1251 pub x11_wm_classes: StringPairVec,
1254 pub x11_window_types: XWindowTypeVec,
1257 pub x11_visual: OptionX11Visual,
1259 pub x11_resize_increments: OptionLogicalSize,
1262 pub x11_base_size: OptionLogicalSize,
1265 pub x11_screen: OptionI32,
1267 pub request_user_attention: UserAttentionType,
1268 pub x11_decorations_state: OptionLinuxDecorationsState,
1270 pub x11_override_redirect: bool,
1273}
1274
1275pub type X11Visual = *const c_void;
1276impl_option!(
1277 X11Visual,
1278 OptionX11Visual,
1279 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
1280);
1281
1282#[derive(Debug, Default, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1284#[repr(C)]
1285pub struct AzStringPair {
1286 pub key: AzString,
1287 pub value: AzString,
1288}
1289
1290impl_option!(
1291 AzStringPair,
1292 OptionStringPair,
1293 copy = false,
1294 [Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash]
1295);
1296
1297impl_vec!(AzStringPair, StringPairVec, StringPairVecDestructor, StringPairVecDestructorType, StringPairVecSlice, OptionStringPair);
1298impl_vec_mut!(AzStringPair, StringPairVec);
1299impl_vec_debug!(AzStringPair, StringPairVec);
1300impl_vec_partialord!(AzStringPair, StringPairVec);
1301impl_vec_ord!(AzStringPair, StringPairVec);
1302impl_vec_clone!(AzStringPair, StringPairVec, StringPairVecDestructor);
1303impl_vec_partialeq!(AzStringPair, StringPairVec);
1304impl_vec_eq!(AzStringPair, StringPairVec);
1305impl_vec_hash!(AzStringPair, StringPairVec);
1306
1307impl_option!(
1308 StringPairVec,
1309 OptionStringPairVec,
1310 copy = false,
1311 [Debug, Clone, PartialOrd, PartialEq, Ord, Eq, Hash]
1312);
1313
1314impl StringPairVec {
1315 #[must_use] pub fn get_key(&self, search_key: &str) -> Option<&AzString> {
1316 self.as_ref().iter().find_map(|v| {
1317 if v.key.as_str() == search_key {
1318 Some(&v.value)
1319 } else {
1320 None
1321 }
1322 })
1323 }
1324 pub fn get_key_mut(&mut self, search_key: &str) -> Option<&mut AzStringPair> {
1325 self.as_mut()
1326 .iter_mut()
1327 .find(|v| v.key.as_str() == search_key)
1328 }
1329 pub fn insert_kv<I: Into<AzString>>(&mut self, key: I, value: I) {
1330 let key = key.into();
1331 let value = value.into();
1332 match self.get_key_mut(key.as_str()) {
1333 None => {}
1334 Some(s) => {
1335 s.value = value;
1336 return;
1337 }
1338 }
1339 self.push(AzStringPair { key, value });
1340 }
1341}
1342
1343impl_vec!(XWindowType, XWindowTypeVec, XWindowTypeVecDestructor, XWindowTypeVecDestructorType, XWindowTypeVecSlice, OptionXWindowType);
1344impl_vec_debug!(XWindowType, XWindowTypeVec);
1345impl_vec_partialord!(XWindowType, XWindowTypeVec);
1346impl_vec_ord!(XWindowType, XWindowTypeVec);
1347impl_vec_clone!(XWindowType, XWindowTypeVec, XWindowTypeVecDestructor);
1348impl_vec_partialeq!(XWindowType, XWindowTypeVec);
1349impl_vec_eq!(XWindowType, XWindowTypeVec);
1350impl_vec_hash!(XWindowType, XWindowTypeVec);
1351
1352impl_option!(
1353 WaylandTheme,
1354 OptionWaylandTheme,
1355 copy = false,
1356 [Debug, Clone, PartialEq, PartialOrd]
1357);
1358
1359#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1361#[repr(C)]
1362#[allow(clippy::pub_underscore_fields)]
1364pub struct MacWindowOptions {
1365 pub _reserved: u8,
1367}
1368
1369#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1371#[repr(C)]
1372#[allow(clippy::pub_underscore_fields)]
1374pub struct WasmWindowOptions {
1375 pub _reserved: u8,
1377}
1378
1379#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1380#[repr(C)]
1381#[derive(Default)]
1382pub enum FullScreenMode {
1383 SlowFullScreen,
1386 #[default]
1389 FastFullScreen,
1390 SlowWindowed,
1393 FastWindowed,
1396}
1397
1398
1399#[derive(Debug, Clone, PartialEq, PartialOrd)]
1402#[repr(C)]
1403pub struct WaylandTheme {
1404 pub title_bar_active_background_color: ColorU,
1405 pub title_bar_active_separator_color: ColorU,
1406 pub title_bar_active_text_color: ColorU,
1407 pub title_bar_inactive_background_color: ColorU,
1408 pub title_bar_inactive_separator_color: ColorU,
1409 pub title_bar_inactive_text_color: ColorU,
1410 pub maximize_idle_foreground_inactive_color: ColorU,
1411 pub minimize_idle_foreground_inactive_color: ColorU,
1412 pub close_idle_foreground_inactive_color: ColorU,
1413 pub maximize_hovered_foreground_inactive_color: ColorU,
1414 pub minimize_hovered_foreground_inactive_color: ColorU,
1415 pub close_hovered_foreground_inactive_color: ColorU,
1416 pub maximize_disabled_foreground_inactive_color: ColorU,
1417 pub minimize_disabled_foreground_inactive_color: ColorU,
1418 pub close_disabled_foreground_inactive_color: ColorU,
1419 pub maximize_idle_background_inactive_color: ColorU,
1420 pub minimize_idle_background_inactive_color: ColorU,
1421 pub close_idle_background_inactive_color: ColorU,
1422 pub maximize_hovered_background_inactive_color: ColorU,
1423 pub minimize_hovered_background_inactive_color: ColorU,
1424 pub close_hovered_background_inactive_color: ColorU,
1425 pub maximize_disabled_background_inactive_color: ColorU,
1426 pub minimize_disabled_background_inactive_color: ColorU,
1427 pub close_disabled_background_inactive_color: ColorU,
1428 pub maximize_idle_foreground_active_color: ColorU,
1429 pub minimize_idle_foreground_active_color: ColorU,
1430 pub close_idle_foreground_active_color: ColorU,
1431 pub maximize_hovered_foreground_active_color: ColorU,
1432 pub minimize_hovered_foreground_active_color: ColorU,
1433 pub close_hovered_foreground_active_color: ColorU,
1434 pub maximize_disabled_foreground_active_color: ColorU,
1435 pub minimize_disabled_foreground_active_color: ColorU,
1436 pub close_disabled_foreground_active_color: ColorU,
1437 pub maximize_idle_background_active_color: ColorU,
1438 pub minimize_idle_background_active_color: ColorU,
1439 pub close_idle_background_active_color: ColorU,
1440 pub maximize_hovered_background_active_color: ColorU,
1441 pub minimize_hovered_background_active_color: ColorU,
1442 pub close_hovered_background_active_color: ColorU,
1443 pub maximize_disabled_background_active_color: ColorU,
1444 pub minimize_disabled_background_active_color: ColorU,
1445 pub close_disabled_background_active_color: ColorU,
1446 pub title_bar_font: AzString,
1447 pub title_bar_font_size: f32,
1448}
1449
1450#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd)]
1451#[repr(C)]
1452pub struct WindowSize {
1453 pub dimensions: LogicalSize,
1456 pub dpi: u32,
1458 pub min_dimensions: OptionLogicalSize,
1460 pub max_dimensions: OptionLogicalSize,
1462}
1463
1464impl WindowSize {
1465 #[allow(clippy::cast_possible_truncation)] #[must_use] pub fn get_layout_size(&self) -> LayoutSize {
1467 LayoutSize::new(
1468 libm::roundf(self.dimensions.width) as isize,
1469 libm::roundf(self.dimensions.height) as isize,
1470 )
1471 }
1472
1473 #[must_use] pub const fn get_logical_size(&self) -> LogicalSize {
1475 self.dimensions
1476 }
1477
1478 #[must_use] pub fn get_physical_size(&self) -> PhysicalSize<u32> {
1479 self.dimensions
1480 .to_physical(self.get_hidpi_factor().inner.get())
1481 }
1482
1483 #[allow(clippy::cast_precision_loss)] #[must_use] pub fn get_hidpi_factor(&self) -> DpiScaleFactor {
1485 let dpi = if self.dpi == 0 { 96 } else { self.dpi };
1490 DpiScaleFactor {
1491 inner: FloatValue::new(dpi as f32 / 96.0),
1492 }
1493 }
1494}
1495
1496impl Default for WindowSize {
1497 fn default() -> Self {
1498 Self {
1499 dimensions: LogicalSize::new(640.0, 480.0),
1500 dpi: 96,
1501 min_dimensions: None.into(),
1502 max_dimensions: None.into(),
1503 }
1504 }
1505}
1506
1507#[repr(C)]
1508#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
1509pub enum RendererType {
1510 Hardware,
1512 Software,
1514}
1515
1516impl_option!(
1517 RendererType,
1518 OptionRendererType,
1519 [Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash]
1520);
1521
1522#[derive(Debug, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
1523pub enum UpdateFocusWarning {
1524 FocusInvalidDomId(DomId),
1525 FocusInvalidNodeId(NodeHierarchyItemId),
1526 CouldNotFindFocusNode(CssPath),
1527}
1528
1529impl ::core::fmt::Display for UpdateFocusWarning {
1530 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
1531 use self::UpdateFocusWarning::{FocusInvalidDomId, FocusInvalidNodeId, CouldNotFindFocusNode};
1532 match self {
1533 FocusInvalidDomId(dom_id) => write!(f, "Focusing on DOM with invalid ID: {dom_id:?}"),
1534 FocusInvalidNodeId(node_id) => {
1535 write!(f, "Focusing on node with invalid ID: {node_id}")
1536 }
1537 CouldNotFindFocusNode(css_path) => {
1538 write!(f, "Could not find focus node for path: {css_path}")
1539 }
1540 }
1541 }
1542}
1543
1544#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1546#[repr(C, u8)]
1547pub enum AcceleratorKey {
1548 Ctrl,
1549 Alt,
1550 Shift,
1551 Key(VirtualKeyCode),
1552}
1553
1554impl AcceleratorKey {
1555 #[must_use] pub fn matches(&self, keyboard_state: &KeyboardState) -> bool {
1559 use self::AcceleratorKey::{Ctrl, Alt, Shift, Key};
1560 match self {
1561 Ctrl => keyboard_state.ctrl_down(),
1562 Alt => keyboard_state.alt_down(),
1563 Shift => keyboard_state.shift_down(),
1564 Key(k) => keyboard_state.is_key_down(*k),
1565 }
1566 }
1567}
1568
1569#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1571#[repr(C)]
1572pub enum VirtualKeyCode {
1573 Key1,
1574 Key2,
1575 Key3,
1576 Key4,
1577 Key5,
1578 Key6,
1579 Key7,
1580 Key8,
1581 Key9,
1582 Key0,
1583 A,
1584 B,
1585 C,
1586 D,
1587 E,
1588 F,
1589 G,
1590 H,
1591 I,
1592 J,
1593 K,
1594 L,
1595 M,
1596 N,
1597 O,
1598 P,
1599 Q,
1600 R,
1601 S,
1602 T,
1603 U,
1604 V,
1605 W,
1606 X,
1607 Y,
1608 Z,
1609 Escape,
1610 F1,
1611 F2,
1612 F3,
1613 F4,
1614 F5,
1615 F6,
1616 F7,
1617 F8,
1618 F9,
1619 F10,
1620 F11,
1621 F12,
1622 F13,
1623 F14,
1624 F15,
1625 F16,
1626 F17,
1627 F18,
1628 F19,
1629 F20,
1630 F21,
1631 F22,
1632 F23,
1633 F24,
1634 Snapshot,
1635 Scroll,
1636 Pause,
1637 Insert,
1638 Home,
1639 Delete,
1640 End,
1641 PageDown,
1642 PageUp,
1643 Left,
1644 Up,
1645 Right,
1646 Down,
1647 Back,
1648 Return,
1649 Space,
1650 Compose,
1651 Caret,
1652 Numlock,
1653 Numpad0,
1654 Numpad1,
1655 Numpad2,
1656 Numpad3,
1657 Numpad4,
1658 Numpad5,
1659 Numpad6,
1660 Numpad7,
1661 Numpad8,
1662 Numpad9,
1663 NumpadAdd,
1664 NumpadDivide,
1665 NumpadDecimal,
1666 NumpadComma,
1667 NumpadEnter,
1668 NumpadEquals,
1669 NumpadMultiply,
1670 NumpadSubtract,
1671 AbntC1,
1672 AbntC2,
1673 Apostrophe,
1674 Apps,
1675 Asterisk,
1676 At,
1677 Ax,
1678 Backslash,
1679 Calculator,
1680 Capital,
1681 Colon,
1682 Comma,
1683 Convert,
1684 Equals,
1685 Grave,
1686 Kana,
1687 Kanji,
1688 LAlt,
1689 LBracket,
1690 LControl,
1691 LShift,
1692 LWin,
1693 Mail,
1694 MediaSelect,
1695 MediaStop,
1696 Minus,
1697 Mute,
1698 MyComputer,
1699 NavigateForward,
1700 NavigateBackward,
1701 NextTrack,
1702 NoConvert,
1703 OEM102,
1704 Period,
1705 PlayPause,
1706 Plus,
1707 Power,
1708 PrevTrack,
1709 RAlt,
1710 RBracket,
1711 RControl,
1712 RShift,
1713 RWin,
1714 Semicolon,
1715 Slash,
1716 Sleep,
1717 Stop,
1718 Sysrq,
1719 Tab,
1720 Underline,
1721 Unlabeled,
1722 VolumeDown,
1723 VolumeUp,
1724 Wake,
1725 WebBack,
1726 WebFavorites,
1727 WebForward,
1728 WebHome,
1729 WebRefresh,
1730 WebSearch,
1731 WebStop,
1732 Yen,
1733 Copy,
1734 Paste,
1735 Cut,
1736}
1737
1738impl VirtualKeyCode {
1739 #[must_use]
1747 #[allow(clippy::too_many_lines)] pub const fn from_u32(v: u32) -> Option<Self> {
1749 match v {
1750 0 => Some(Self::Key1),
1751 1 => Some(Self::Key2),
1752 2 => Some(Self::Key3),
1753 3 => Some(Self::Key4),
1754 4 => Some(Self::Key5),
1755 5 => Some(Self::Key6),
1756 6 => Some(Self::Key7),
1757 7 => Some(Self::Key8),
1758 8 => Some(Self::Key9),
1759 9 => Some(Self::Key0),
1760 10 => Some(Self::A),
1761 11 => Some(Self::B),
1762 12 => Some(Self::C),
1763 13 => Some(Self::D),
1764 14 => Some(Self::E),
1765 15 => Some(Self::F),
1766 16 => Some(Self::G),
1767 17 => Some(Self::H),
1768 18 => Some(Self::I),
1769 19 => Some(Self::J),
1770 20 => Some(Self::K),
1771 21 => Some(Self::L),
1772 22 => Some(Self::M),
1773 23 => Some(Self::N),
1774 24 => Some(Self::O),
1775 25 => Some(Self::P),
1776 26 => Some(Self::Q),
1777 27 => Some(Self::R),
1778 28 => Some(Self::S),
1779 29 => Some(Self::T),
1780 30 => Some(Self::U),
1781 31 => Some(Self::V),
1782 32 => Some(Self::W),
1783 33 => Some(Self::X),
1784 34 => Some(Self::Y),
1785 35 => Some(Self::Z),
1786 36 => Some(Self::Escape),
1787 37 => Some(Self::F1),
1788 38 => Some(Self::F2),
1789 39 => Some(Self::F3),
1790 40 => Some(Self::F4),
1791 41 => Some(Self::F5),
1792 42 => Some(Self::F6),
1793 43 => Some(Self::F7),
1794 44 => Some(Self::F8),
1795 45 => Some(Self::F9),
1796 46 => Some(Self::F10),
1797 47 => Some(Self::F11),
1798 48 => Some(Self::F12),
1799 49 => Some(Self::F13),
1800 50 => Some(Self::F14),
1801 51 => Some(Self::F15),
1802 52 => Some(Self::F16),
1803 53 => Some(Self::F17),
1804 54 => Some(Self::F18),
1805 55 => Some(Self::F19),
1806 56 => Some(Self::F20),
1807 57 => Some(Self::F21),
1808 58 => Some(Self::F22),
1809 59 => Some(Self::F23),
1810 60 => Some(Self::F24),
1811 61 => Some(Self::Snapshot),
1812 62 => Some(Self::Scroll),
1813 63 => Some(Self::Pause),
1814 64 => Some(Self::Insert),
1815 65 => Some(Self::Home),
1816 66 => Some(Self::Delete),
1817 67 => Some(Self::End),
1818 68 => Some(Self::PageDown),
1819 69 => Some(Self::PageUp),
1820 70 => Some(Self::Left),
1821 71 => Some(Self::Up),
1822 72 => Some(Self::Right),
1823 73 => Some(Self::Down),
1824 74 => Some(Self::Back),
1825 75 => Some(Self::Return),
1826 76 => Some(Self::Space),
1827 77 => Some(Self::Compose),
1828 78 => Some(Self::Caret),
1829 79 => Some(Self::Numlock),
1830 80 => Some(Self::Numpad0),
1831 81 => Some(Self::Numpad1),
1832 82 => Some(Self::Numpad2),
1833 83 => Some(Self::Numpad3),
1834 84 => Some(Self::Numpad4),
1835 85 => Some(Self::Numpad5),
1836 86 => Some(Self::Numpad6),
1837 87 => Some(Self::Numpad7),
1838 88 => Some(Self::Numpad8),
1839 89 => Some(Self::Numpad9),
1840 90 => Some(Self::NumpadAdd),
1841 91 => Some(Self::NumpadDivide),
1842 92 => Some(Self::NumpadDecimal),
1843 93 => Some(Self::NumpadComma),
1844 94 => Some(Self::NumpadEnter),
1845 95 => Some(Self::NumpadEquals),
1846 96 => Some(Self::NumpadMultiply),
1847 97 => Some(Self::NumpadSubtract),
1848 98 => Some(Self::AbntC1),
1849 99 => Some(Self::AbntC2),
1850 100 => Some(Self::Apostrophe),
1851 101 => Some(Self::Apps),
1852 102 => Some(Self::Asterisk),
1853 103 => Some(Self::At),
1854 104 => Some(Self::Ax),
1855 105 => Some(Self::Backslash),
1856 106 => Some(Self::Calculator),
1857 107 => Some(Self::Capital),
1858 108 => Some(Self::Colon),
1859 109 => Some(Self::Comma),
1860 110 => Some(Self::Convert),
1861 111 => Some(Self::Equals),
1862 112 => Some(Self::Grave),
1863 113 => Some(Self::Kana),
1864 114 => Some(Self::Kanji),
1865 115 => Some(Self::LAlt),
1866 116 => Some(Self::LBracket),
1867 117 => Some(Self::LControl),
1868 118 => Some(Self::LShift),
1869 119 => Some(Self::LWin),
1870 120 => Some(Self::Mail),
1871 121 => Some(Self::MediaSelect),
1872 122 => Some(Self::MediaStop),
1873 123 => Some(Self::Minus),
1874 124 => Some(Self::Mute),
1875 125 => Some(Self::MyComputer),
1876 126 => Some(Self::NavigateForward),
1877 127 => Some(Self::NavigateBackward),
1878 128 => Some(Self::NextTrack),
1879 129 => Some(Self::NoConvert),
1880 130 => Some(Self::OEM102),
1881 131 => Some(Self::Period),
1882 132 => Some(Self::PlayPause),
1883 133 => Some(Self::Plus),
1884 134 => Some(Self::Power),
1885 135 => Some(Self::PrevTrack),
1886 136 => Some(Self::RAlt),
1887 137 => Some(Self::RBracket),
1888 138 => Some(Self::RControl),
1889 139 => Some(Self::RShift),
1890 140 => Some(Self::RWin),
1891 141 => Some(Self::Semicolon),
1892 142 => Some(Self::Slash),
1893 143 => Some(Self::Sleep),
1894 144 => Some(Self::Stop),
1895 145 => Some(Self::Sysrq),
1896 146 => Some(Self::Tab),
1897 147 => Some(Self::Underline),
1898 148 => Some(Self::Unlabeled),
1899 149 => Some(Self::VolumeDown),
1900 150 => Some(Self::VolumeUp),
1901 151 => Some(Self::Wake),
1902 152 => Some(Self::WebBack),
1903 153 => Some(Self::WebFavorites),
1904 154 => Some(Self::WebForward),
1905 155 => Some(Self::WebHome),
1906 156 => Some(Self::WebRefresh),
1907 157 => Some(Self::WebSearch),
1908 158 => Some(Self::WebStop),
1909 159 => Some(Self::Yen),
1910 160 => Some(Self::Copy),
1911 161 => Some(Self::Paste),
1912 162 => Some(Self::Cut),
1913 _ => None,
1914 }
1915 }
1916
1917 #[must_use] pub const fn get_lowercase(&self) -> Option<char> {
1918 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};
1919 match self {
1920 A => Some('a'),
1921 B => Some('b'),
1922 C => Some('c'),
1923 D => Some('d'),
1924 E => Some('e'),
1925 F => Some('f'),
1926 G => Some('g'),
1927 H => Some('h'),
1928 I => Some('i'),
1929 J => Some('j'),
1930 K => Some('k'),
1931 L => Some('l'),
1932 M => Some('m'),
1933 N => Some('n'),
1934 O => Some('o'),
1935 P => Some('p'),
1936 Q => Some('q'),
1937 R => Some('r'),
1938 S => Some('s'),
1939 T => Some('t'),
1940 U => Some('u'),
1941 V => Some('v'),
1942 W => Some('w'),
1943 X => Some('x'),
1944 Y => Some('y'),
1945 Z => Some('z'),
1946 Key0 | Numpad0 => Some('0'),
1947 Key1 | Numpad1 => Some('1'),
1948 Key2 | Numpad2 => Some('2'),
1949 Key3 | Numpad3 => Some('3'),
1950 Key4 | Numpad4 => Some('4'),
1951 Key5 | Numpad5 => Some('5'),
1952 Key6 | Numpad6 => Some('6'),
1953 Key7 | Numpad7 => Some('7'),
1954 Key8 | Numpad8 => Some('8'),
1955 Key9 | Numpad9 => Some('9'),
1956 Minus => Some('-'),
1957 Asterisk => Some('*'),
1958 At => Some('@'),
1959 Period => Some('.'),
1960 Semicolon => Some(';'),
1961 Slash => Some('/'),
1962 Caret => Some('^'),
1963 _ => None,
1964 }
1965 }
1966}
1967
1968#[derive(Debug, Clone)]
1970#[repr(C)]
1971pub struct SmallWindowIconBytes {
1972 pub key: IconKey,
1973 pub rgba_bytes: U8Vec,
1974}
1975
1976#[derive(Debug, Clone)]
1978#[repr(C)]
1979pub struct LargeWindowIconBytes {
1980 pub key: IconKey,
1981 pub rgba_bytes: U8Vec,
1982}
1983
1984#[derive(Debug, Clone)]
1986#[repr(C, u8)]
1987pub enum WindowIcon {
1988 Small(SmallWindowIconBytes),
1989 Large(LargeWindowIconBytes),
1991}
1992
1993impl_option!(
1994 WindowIcon,
1995 OptionWindowIcon,
1996 copy = false,
1997 [Debug, Clone, PartialOrd, PartialEq, Eq, Hash, Ord]
1998);
1999
2000impl WindowIcon {
2001 #[must_use] pub const fn get_key(&self) -> IconKey {
2002 match &self {
2003 Self::Small(SmallWindowIconBytes { key, .. })
2004 | Self::Large(LargeWindowIconBytes { key, .. }) => *key,
2005 }
2006 }
2007}
2008impl PartialEq for WindowIcon {
2011 fn eq(&self, rhs: &Self) -> bool {
2012 self.get_key() == rhs.get_key()
2013 }
2014}
2015
2016impl PartialOrd for WindowIcon {
2017 fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
2018 Some((self.get_key()).cmp(&rhs.get_key()))
2019 }
2020}
2021
2022impl Eq for WindowIcon {}
2023
2024impl Ord for WindowIcon {
2025 fn cmp(&self, rhs: &Self) -> Ordering {
2026 (self.get_key()).cmp(&rhs.get_key())
2027 }
2028}
2029
2030impl Hash for WindowIcon {
2031 fn hash<H>(&self, state: &mut H)
2032 where
2033 H: Hasher,
2034 {
2035 self.get_key().hash(state);
2036 }
2037}
2038
2039#[derive(Debug, Clone)]
2041#[repr(C)]
2042pub struct TaskBarIcon {
2043 pub key: IconKey,
2044 pub rgba_bytes: U8Vec,
2045}
2046
2047impl_option!(
2048 TaskBarIcon,
2049 OptionTaskBarIcon,
2050 copy = false,
2051 [Debug, Clone, PartialOrd, PartialEq, Eq, Hash, Ord]
2052);
2053
2054impl PartialEq for TaskBarIcon {
2055 fn eq(&self, rhs: &Self) -> bool {
2056 self.key == rhs.key
2057 }
2058}
2059
2060impl PartialOrd for TaskBarIcon {
2061 fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
2062 Some((self.key).cmp(&rhs.key))
2063 }
2064}
2065
2066impl Eq for TaskBarIcon {}
2067
2068impl Ord for TaskBarIcon {
2069 fn cmp(&self, rhs: &Self) -> Ordering {
2070 (self.key).cmp(&rhs.key)
2071 }
2072}
2073
2074impl Hash for TaskBarIcon {
2075 fn hash<H>(&self, state: &mut H)
2076 where
2077 H: Hasher,
2078 {
2079 self.key.hash(state);
2080 }
2081}
2082
2083#[cfg(test)]
2084#[allow(clippy::float_cmp)] mod audit_tests {
2086 use super::*;
2087
2088 #[test]
2089 fn hidpi_factor_guards_zero_dpi() {
2090 let ws = WindowSize { dpi: 0, ..WindowSize::default() };
2093 let factor = ws.get_hidpi_factor().inner.get();
2094 assert_eq!(factor, 1.0);
2095
2096 let ws2 = WindowSize { dpi: 192, ..WindowSize::default() };
2097 assert_eq!(ws2.get_hidpi_factor().inner.get(), 2.0);
2098 }
2099
2100 #[test]
2101 fn virtual_keycode_from_u32_roundtrips() {
2102 for vk in [
2104 VirtualKeyCode::Key1,
2105 VirtualKeyCode::A,
2106 VirtualKeyCode::Z,
2107 VirtualKeyCode::Left,
2108 VirtualKeyCode::Back,
2109 VirtualKeyCode::Delete,
2110 VirtualKeyCode::LControl,
2111 VirtualKeyCode::Cut,
2112 ] {
2113 assert_eq!(VirtualKeyCode::from_u32(vk as u32), Some(vk));
2114 }
2115 assert_eq!(VirtualKeyCode::from_u32(10_000), None);
2117 }
2118}
2119
2120#[cfg(test)]
2121#[allow(clippy::float_cmp)] mod autotest_generated {
2123 use alloc::{format, string::String, vec};
2124
2125 use super::*;
2126
2127 const LAST_VK: u32 = 162;
2129
2130 #[derive(Default)]
2133 struct TestHasher(u64);
2134
2135 impl Hasher for TestHasher {
2136 fn write(&mut self, bytes: &[u8]) {
2137 for &b in bytes {
2138 self.0 = self.0.rotate_left(5) ^ u64::from(b);
2139 }
2140 }
2141 fn finish(&self) -> u64 {
2142 self.0
2143 }
2144 }
2145
2146 fn hash_of<T: Hash>(value: &T) -> u64 {
2147 let mut h = TestHasher::default();
2148 value.hash(&mut h);
2149 h.finish()
2150 }
2151
2152 fn keyboard_with(keys: &[VirtualKeyCode]) -> KeyboardState {
2153 KeyboardState {
2154 pressed_virtual_keycodes: keys.to_vec().into(),
2155 ..KeyboardState::default()
2156 }
2157 }
2158
2159 fn pair(key: &str, value: &str) -> AzStringPair {
2160 AzStringPair {
2161 key: key.into(),
2162 value: value.into(),
2163 }
2164 }
2165
2166 #[test]
2171 fn window_id_new_is_unique_and_monotonic() {
2172 let mut ids = BTreeSet::new();
2175 let mut prev = WindowId::new();
2176 assert!(ids.insert(prev));
2177 for _ in 0..1000 {
2178 let next = WindowId::new();
2179 assert!(next.id > prev.id, "WindowId counter must strictly increase");
2180 assert!(ids.insert(next), "WindowId::new() handed out a duplicate");
2181 prev = next;
2182 }
2183 assert_ne!(WindowId::default(), WindowId::default());
2185 }
2186
2187 #[test]
2188 fn icon_key_new_is_unique_and_monotonic() {
2189 let mut keys = BTreeSet::new();
2190 let mut prev = IconKey::new();
2191 assert!(keys.insert(prev));
2192 for _ in 0..1000 {
2193 let next = IconKey::new();
2194 assert!(
2195 next.icon_id > prev.icon_id,
2196 "IconKey counter must strictly increase"
2197 );
2198 assert!(keys.insert(next), "IconKey::new() handed out a duplicate");
2199 prev = next;
2200 }
2201 assert_ne!(IconKey::default(), IconKey::default());
2202 }
2203
2204 #[test]
2209 fn renderer_options_new_preserves_every_combination() {
2210 let vsyncs = [Vsync::Enabled, Vsync::Disabled, Vsync::DontCare];
2211 let srgbs = [Srgb::Enabled, Srgb::Disabled, Srgb::DontCare];
2212 let accels = [
2213 HwAcceleration::Enabled,
2214 HwAcceleration::Disabled,
2215 HwAcceleration::DontCare,
2216 ];
2217 for v in vsyncs {
2218 for s in srgbs {
2219 for a in accels {
2220 let o = RendererOptions::new(v, s, a);
2221 assert_eq!(o.vsync, v);
2222 assert_eq!(o.srgb, s);
2223 assert_eq!(o.hw_accel, a);
2224 assert_eq!(o, RendererOptions::new(v, s, a));
2226 }
2227 }
2228 }
2229 }
2230
2231 #[test]
2232 fn renderer_options_default_does_not_force_cpu_rendering() {
2233 let d = RendererOptions::default();
2236 assert_eq!(d.hw_accel, HwAcceleration::DontCare);
2237 assert!(!d.hw_accel.is_enabled());
2238 assert!(d.vsync.is_enabled());
2239 assert!(!d.srgb.is_enabled());
2240 }
2241
2242 #[test]
2243 fn tri_state_is_enabled_only_for_enabled_variant() {
2244 assert!(Vsync::Enabled.is_enabled());
2245 assert!(!Vsync::Disabled.is_enabled());
2246 assert!(!Vsync::DontCare.is_enabled());
2247
2248 assert!(Srgb::Enabled.is_enabled());
2249 assert!(!Srgb::Disabled.is_enabled());
2250 assert!(!Srgb::DontCare.is_enabled());
2251
2252 assert!(HwAcceleration::Enabled.is_enabled());
2253 assert!(!HwAcceleration::Disabled.is_enabled());
2254 assert!(!HwAcceleration::DontCare.is_enabled());
2255 }
2256
2257 #[test]
2262 fn keyboard_state_default_has_no_key_down() {
2263 let k = KeyboardState::default();
2264 assert!(!k.shift_down());
2265 assert!(!k.ctrl_down());
2266 assert!(!k.alt_down());
2267 assert!(!k.super_down());
2268 assert!(!k.primary_down());
2269 for v in 0..=LAST_VK {
2271 let vk = VirtualKeyCode::from_u32(v).expect("discriminant in range");
2272 assert!(!k.is_key_down(vk));
2273 }
2274 }
2275
2276 #[test]
2277 fn keyboard_state_modifiers_accept_either_side() {
2278 for (left, right, probe) in [
2279 (
2280 VirtualKeyCode::LShift,
2281 VirtualKeyCode::RShift,
2282 KeyboardState::shift_down as fn(&KeyboardState) -> bool,
2283 ),
2284 (
2285 VirtualKeyCode::LControl,
2286 VirtualKeyCode::RControl,
2287 KeyboardState::ctrl_down as fn(&KeyboardState) -> bool,
2288 ),
2289 (
2290 VirtualKeyCode::LAlt,
2291 VirtualKeyCode::RAlt,
2292 KeyboardState::alt_down as fn(&KeyboardState) -> bool,
2293 ),
2294 (
2295 VirtualKeyCode::LWin,
2296 VirtualKeyCode::RWin,
2297 KeyboardState::super_down as fn(&KeyboardState) -> bool,
2298 ),
2299 ] {
2300 assert!(probe(&keyboard_with(&[left])), "left variant must register");
2301 assert!(
2302 probe(&keyboard_with(&[right])),
2303 "right variant must register"
2304 );
2305 assert!(probe(&keyboard_with(&[left, right])));
2306 assert!(!probe(&keyboard_with(&[VirtualKeyCode::A])));
2308 }
2309 }
2310
2311 #[test]
2312 fn keyboard_state_primary_down_follows_platform() {
2313 let ctrl = keyboard_with(&[VirtualKeyCode::LControl]);
2314 let cmd = keyboard_with(&[VirtualKeyCode::LWin]);
2315 if cfg!(target_os = "macos") {
2316 assert!(cmd.primary_down(), "Cmd (super) is PRIMARY on macOS");
2317 assert!(!ctrl.primary_down());
2318 } else {
2319 assert!(ctrl.primary_down(), "Ctrl is PRIMARY off macOS");
2320 assert!(!cmd.primary_down());
2321 }
2322 for k in [&ctrl, &cmd, &KeyboardState::default()] {
2324 assert_eq!(
2325 k.primary_down(),
2326 if cfg!(target_os = "macos") {
2327 k.super_down()
2328 } else {
2329 k.ctrl_down()
2330 }
2331 );
2332 }
2333 }
2334
2335 #[test]
2336 fn is_key_down_handles_duplicates_and_large_state() {
2337 let dup = keyboard_with(&[VirtualKeyCode::S; 512]);
2339 assert!(dup.is_key_down(VirtualKeyCode::S));
2340 assert!(!dup.is_key_down(VirtualKeyCode::A));
2341
2342 let all: alloc::vec::Vec<VirtualKeyCode> = (0..=LAST_VK)
2344 .map(|v| VirtualKeyCode::from_u32(v).expect("discriminant in range"))
2345 .collect();
2346 let everything = keyboard_with(&all);
2347 for vk in &all {
2348 assert!(everything.is_key_down(*vk));
2349 }
2350 assert!(everything.shift_down() && everything.ctrl_down());
2351 assert!(everything.alt_down() && everything.super_down());
2352 }
2353
2354 #[test]
2359 fn empty_chord_matches_trivially() {
2360 assert!(KeyboardState::default().matches_accelerator(&[]));
2362 assert!(keyboard_with(&[VirtualKeyCode::A]).matches_accelerator(&[]));
2363 }
2364
2365 #[test]
2366 fn matches_accelerator_requires_every_entry() {
2367 let state = keyboard_with(&[
2368 VirtualKeyCode::LControl,
2369 VirtualKeyCode::LShift,
2370 VirtualKeyCode::S,
2371 ]);
2372 assert!(state.matches_accelerator(&[
2373 AcceleratorKey::Ctrl,
2374 AcceleratorKey::Shift,
2375 AcceleratorKey::Key(VirtualKeyCode::S),
2376 ]));
2377 assert!(!state.matches_accelerator(&[
2379 AcceleratorKey::Ctrl,
2380 AcceleratorKey::Alt,
2381 AcceleratorKey::Key(VirtualKeyCode::S),
2382 ]));
2383 assert!(!state.matches_accelerator(&[
2385 AcceleratorKey::Ctrl,
2386 AcceleratorKey::Key(VirtualKeyCode::Q),
2387 ]));
2388 assert!(state.matches_accelerator(&[
2390 AcceleratorKey::Key(VirtualKeyCode::S),
2391 AcceleratorKey::Shift,
2392 AcceleratorKey::Ctrl,
2393 ]));
2394 }
2395
2396 #[test]
2397 fn matches_accelerator_survives_huge_chord() {
2398 let state = keyboard_with(&[VirtualKeyCode::LShift]);
2400 let long_ok = vec![AcceleratorKey::Shift; 10_000];
2401 assert!(state.matches_accelerator(&long_ok));
2402
2403 let mut long_bad = vec![AcceleratorKey::Shift; 10_000];
2406 long_bad.push(AcceleratorKey::Ctrl);
2407 assert!(!state.matches_accelerator(&long_bad));
2408 }
2409
2410 #[test]
2411 fn accelerator_key_matches_each_variant() {
2412 let empty = KeyboardState::default();
2413 for a in [
2414 AcceleratorKey::Ctrl,
2415 AcceleratorKey::Alt,
2416 AcceleratorKey::Shift,
2417 AcceleratorKey::Key(VirtualKeyCode::A),
2418 ] {
2419 assert!(!a.matches(&empty), "nothing matches an empty keyboard state");
2420 }
2421 assert!(AcceleratorKey::Ctrl.matches(&keyboard_with(&[VirtualKeyCode::RControl])));
2422 assert!(AcceleratorKey::Alt.matches(&keyboard_with(&[VirtualKeyCode::RAlt])));
2423 assert!(AcceleratorKey::Shift.matches(&keyboard_with(&[VirtualKeyCode::RShift])));
2424 assert!(
2425 AcceleratorKey::Key(VirtualKeyCode::F24).matches(&keyboard_with(&[VirtualKeyCode::F24]))
2426 );
2427 assert!(!AcceleratorKey::Ctrl.matches(&keyboard_with(&[VirtualKeyCode::C])));
2429 }
2430
2431 #[test]
2436 fn mouse_state_matches_context_button() {
2437 let base = MouseState::default();
2438 assert!(!base.matches(&ContextMenuMouseButton::Left));
2439 assert!(!base.matches(&ContextMenuMouseButton::Right));
2440 assert!(!base.matches(&ContextMenuMouseButton::Middle));
2441
2442 for (ctx, ms) in [
2443 (
2444 ContextMenuMouseButton::Left,
2445 MouseState {
2446 left_down: true,
2447 ..MouseState::default()
2448 },
2449 ),
2450 (
2451 ContextMenuMouseButton::Right,
2452 MouseState {
2453 right_down: true,
2454 ..MouseState::default()
2455 },
2456 ),
2457 (
2458 ContextMenuMouseButton::Middle,
2459 MouseState {
2460 middle_down: true,
2461 ..MouseState::default()
2462 },
2463 ),
2464 ] {
2465 assert!(ms.matches(&ctx), "{ctx:?} must match its own button");
2466 let others = [
2468 ContextMenuMouseButton::Left,
2469 ContextMenuMouseButton::Right,
2470 ContextMenuMouseButton::Middle,
2471 ];
2472 for other in others {
2473 assert_eq!(ms.matches(&other), other == ctx);
2474 }
2475 }
2476 }
2477
2478 #[test]
2479 fn mouse_down_and_button_state_agree_for_all_8_combinations() {
2480 for bits in 0u8..8 {
2481 let (l, r, m) = (bits & 1 != 0, bits & 2 != 0, bits & 4 != 0);
2482 let ms = MouseState {
2483 left_down: l,
2484 right_down: r,
2485 middle_down: m,
2486 ..MouseState::default()
2487 };
2488 assert_eq!(ms.mouse_down(), l || r || m);
2489
2490 let snapshot = ms.button_state();
2491 assert_eq!(snapshot.left_down, l);
2492 assert_eq!(snapshot.right_down, r);
2493 assert_eq!(snapshot.middle_down, m);
2494 assert_eq!(snapshot.any_down(), ms.mouse_down());
2496 assert_eq!(crate::events::MouseButtonState::from(&ms), snapshot);
2497 }
2498 assert!(!MouseState::default().mouse_down());
2500 assert!(!MouseState::default().button_state().any_down());
2501 }
2502
2503 #[test]
2508 fn process_system_scroll_zero_and_negative_zero_consume_nothing() {
2509 let r = process_system_scroll(LogicalPosition::zero(), false);
2510 assert_eq!(r.scrolled_nodes, 0);
2511 assert_eq!(r.remaining_delta, LogicalPosition::zero());
2512 assert!(!r.hit_scrollbar);
2513
2514 let neg_zero = process_system_scroll(LogicalPosition::new(-0.0, -0.0), true);
2516 assert_eq!(neg_zero.scrolled_nodes, 0);
2517 assert!(neg_zero.hit_scrollbar, "hit_scrollbar is echoed verbatim");
2518 }
2519
2520 #[test]
2521 fn process_system_scroll_counts_any_nonzero_axis() {
2522 for delta in [
2523 LogicalPosition::new(1.0, 0.0),
2524 LogicalPosition::new(0.0, -1.0),
2525 LogicalPosition::new(-3.5, 7.25),
2526 LogicalPosition::new(f32::MIN, 0.0),
2527 LogicalPosition::new(0.0, f32::MAX),
2528 LogicalPosition::new(f32::INFINITY, f32::NEG_INFINITY),
2529 LogicalPosition::new(f32::MIN_POSITIVE, 0.0),
2530 ] {
2531 let r = process_system_scroll(delta, false);
2532 assert_eq!(r.scrolled_nodes, 1, "{delta:?} must count as consumed");
2533 assert_eq!(r.remaining_delta, LogicalPosition::zero());
2535 }
2536 }
2537
2538 #[test]
2539 fn process_system_scroll_does_not_panic_on_nan() {
2540 for delta in [
2543 LogicalPosition::new(f32::NAN, 0.0),
2544 LogicalPosition::new(0.0, f32::NAN),
2545 LogicalPosition::new(f32::NAN, f32::NAN),
2546 ] {
2547 let r = process_system_scroll(delta, true);
2548 assert!(r.scrolled_nodes <= 1);
2549 assert_eq!(r.remaining_delta, LogicalPosition::zero());
2550 assert!(r.hit_scrollbar);
2551 }
2552 }
2553
2554 #[test]
2555 fn scroll_result_default_is_inert() {
2556 let d = ScrollResult::default();
2557 assert_eq!(d.scrolled_nodes, 0);
2558 assert_eq!(d.remaining_delta, LogicalPosition::zero());
2559 assert!(!d.hit_scrollbar);
2560 }
2561
2562 #[test]
2567 fn cursor_position_get_position_only_inside_window() {
2568 let p = LogicalPosition::new(12.0, 34.0);
2569 assert_eq!(CursorPosition::InWindow(p).get_position(), Some(p));
2570 assert_eq!(CursorPosition::OutOfWindow(p).get_position(), None);
2571 assert_eq!(CursorPosition::Uninitialized.get_position(), None);
2572 assert_eq!(CursorPosition::default(), CursorPosition::Uninitialized);
2574 assert_eq!(CursorPosition::default().get_position(), None);
2575 }
2576
2577 #[test]
2578 fn cursor_position_is_inside_window_agrees_with_get_position() {
2579 for c in [
2580 CursorPosition::Uninitialized,
2581 CursorPosition::InWindow(LogicalPosition::zero()),
2582 CursorPosition::OutOfWindow(LogicalPosition::zero()),
2583 CursorPosition::InWindow(LogicalPosition::new(f32::MIN, f32::MAX)),
2584 CursorPosition::OutOfWindow(LogicalPosition::new(f32::INFINITY, f32::NAN)),
2585 ] {
2586 assert_eq!(c.is_inside_window(), c.get_position().is_some());
2587 }
2588 let nan_pos = CursorPosition::InWindow(LogicalPosition::new(f32::NAN, f32::INFINITY));
2590 assert!(nan_pos.is_inside_window());
2591 let got = nan_pos.get_position().expect("InWindow always yields a position");
2592 assert!(got.x.is_nan());
2593 assert!(got.y.is_infinite());
2594 }
2595
2596 #[test]
2601 fn monitor_id_constructors_preserve_fields_at_extremes() {
2602 assert_eq!(MonitorId::PRIMARY, MonitorId { index: 0, hash: 0 });
2603 assert_eq!(MonitorId::new(0), MonitorId::PRIMARY);
2604
2605 for index in [0usize, 1, 42, usize::MAX] {
2606 let m = MonitorId::new(index);
2607 assert_eq!(m.index, index);
2608 assert_eq!(m.hash, 0, "new() documents hash == 0");
2609
2610 for hash in [0u64, 1, u64::MAX] {
2611 let m = MonitorId::from_index_and_hash(index, hash);
2612 assert_eq!(m.index, index);
2613 assert_eq!(m.hash, hash);
2614 }
2615 }
2616 assert_ne!(MonitorId::new(1), MonitorId::new(2));
2618 assert_ne!(
2619 MonitorId::from_index_and_hash(1, 7),
2620 MonitorId::from_index_and_hash(1, 8)
2621 );
2622 }
2623
2624 #[test]
2625 fn monitor_id_from_properties_is_stable_and_index_independent() {
2626 let pos = LayoutPoint::new(-1920, 0);
2627 let size = LayoutSize::new(2560, 1440);
2628
2629 let a = MonitorId::from_properties(0, "HDMI-1", pos, size);
2630 let b = MonitorId::from_properties(0, "HDMI-1", pos, size);
2631 assert_eq!(a, b, "hash must be stable across calls (persistable)");
2632
2633 let reindexed = MonitorId::from_properties(7, "HDMI-1", pos, size);
2635 assert_eq!(reindexed.hash, a.hash);
2636 assert_eq!(reindexed.index, 7);
2637 assert_ne!(reindexed, a, "index is still part of identity");
2638 }
2639
2640 #[test]
2641 fn monitor_id_from_properties_is_sensitive_to_each_property() {
2642 let pos = LayoutPoint::new(0, 0);
2643 let size = LayoutSize::new(1920, 1080);
2644 let base = MonitorId::from_properties(0, "DP-1", pos, size);
2645
2646 assert_ne!(
2648 base.hash,
2649 MonitorId::from_properties(0, "DP-2", pos, size).hash
2650 );
2651 assert_ne!(
2652 base.hash,
2653 MonitorId::from_properties(0, "DP-1", LayoutPoint::new(1, 0), size).hash
2654 );
2655 assert_ne!(
2656 base.hash,
2657 MonitorId::from_properties(0, "DP-1", LayoutPoint::new(0, 1), size).hash
2658 );
2659 assert_ne!(
2660 base.hash,
2661 MonitorId::from_properties(0, "DP-1", pos, LayoutSize::new(1921, 1080)).hash
2662 );
2663 assert_ne!(
2664 base.hash,
2665 MonitorId::from_properties(0, "DP-1", pos, LayoutSize::new(1920, 1081)).hash
2666 );
2667 assert_ne!(
2669 base.hash,
2670 MonitorId::from_properties(0, "DP-1", pos, LayoutSize::new(1080, 1920)).hash
2671 );
2672 }
2673
2674 #[test]
2675 fn monitor_id_from_properties_handles_hostile_inputs() {
2676 let size = LayoutSize::new(isize::MAX, isize::MIN);
2677 let pos = LayoutPoint::new(isize::MIN, isize::MAX);
2678
2679 for name in ["", " ", "\t\n", "\u{1F600}", "e\u{301}", "é", "a\0b"] {
2681 let m = MonitorId::from_properties(3, name, pos, size);
2682 assert_eq!(m.index, 3);
2683 assert_eq!(m, MonitorId::from_properties(3, name, pos, size));
2685 }
2686
2687 assert_ne!(
2689 MonitorId::from_properties(0, "e\u{301}", pos, size).hash,
2690 MonitorId::from_properties(0, "é", pos, size).hash
2691 );
2692
2693 let huge = "x".repeat(1_000_000);
2695 let h1 = MonitorId::from_properties(0, &huge, LayoutPoint::zero(), LayoutSize::zero());
2696 let h2 = MonitorId::from_properties(0, &huge, LayoutPoint::zero(), LayoutSize::zero());
2697 assert_eq!(h1, h2);
2698 assert_ne!(
2699 h1.hash,
2700 MonitorId::from_properties(0, "x", LayoutPoint::zero(), LayoutSize::zero()).hash
2701 );
2702 }
2703
2704 #[test]
2709 fn window_flags_type_predicates_are_mutually_exclusive() {
2710 for (ty, menu, tooltip, dialog) in [
2711 (WindowType::Normal, false, false, false),
2712 (WindowType::Menu, true, false, false),
2713 (WindowType::Tooltip, false, true, false),
2714 (WindowType::Dialog, false, false, true),
2715 ] {
2716 let f = WindowFlags {
2717 window_type: ty,
2718 ..WindowFlags::default()
2719 };
2720 assert_eq!(f.is_menu_window(), menu);
2721 assert_eq!(f.is_tooltip_window(), tooltip);
2722 assert_eq!(f.is_dialog_window(), dialog);
2723 let count = u8::from(f.is_menu_window())
2725 + u8::from(f.is_tooltip_window())
2726 + u8::from(f.is_dialog_window());
2727 assert!(count <= 1);
2728 }
2729 }
2730
2731 #[test]
2732 fn window_flags_bool_getters_mirror_their_fields() {
2733 let d = WindowFlags::default();
2735 assert!(d.window_has_focus());
2736 assert!(!d.is_close_requested());
2737 assert!(!d.has_csd());
2738 assert_eq!(
2739 d.use_native_menus(),
2740 cfg!(any(target_os = "windows", target_os = "macos"))
2741 );
2742 assert_eq!(
2743 d.use_native_context_menus(),
2744 cfg!(any(target_os = "windows", target_os = "macos"))
2745 );
2746
2747 for b in [false, true] {
2749 let f = WindowFlags {
2750 has_focus: b,
2751 close_requested: b,
2752 has_decorations: b,
2753 use_native_menus: b,
2754 use_native_context_menus: b,
2755 ..WindowFlags::default()
2756 };
2757 assert_eq!(f.window_has_focus(), b);
2758 assert_eq!(f.is_close_requested(), b);
2759 assert_eq!(f.has_csd(), b);
2760 assert_eq!(f.use_native_menus(), b);
2761 assert_eq!(f.use_native_context_menus(), b);
2762 }
2763 }
2764
2765 #[test]
2770 fn get_key_on_empty_vec_is_none() {
2771 let empty = StringPairVec::new();
2772 assert!(empty.get_key("").is_none());
2773 assert!(empty.get_key("anything").is_none());
2774
2775 let mut empty_mut = StringPairVec::new();
2776 assert!(empty_mut.get_key_mut("").is_none());
2777 assert!(empty_mut.get_key_mut("anything").is_none());
2778 }
2779
2780 #[test]
2781 fn get_key_valid_minimal_and_missing() {
2782 let v = StringPairVec::from_vec(vec![pair("WM_CLASS", "azul")]);
2783 assert_eq!(
2784 v.get_key("WM_CLASS").map(AzString::as_str),
2785 Some("azul"),
2786 "positive control"
2787 );
2788 assert!(v.get_key("wm_class").is_none(), "lookup is case-sensitive");
2789 assert!(v.get_key("WM_CLAS").is_none());
2790 assert!(v.get_key("WM_CLASS ").is_none(), "no trimming is performed");
2791 assert!(v.get_key(" WM_CLASS").is_none());
2792 assert!(v.get_key("WM_CLASS;garbage").is_none());
2793 }
2794
2795 #[test]
2796 fn get_key_handles_garbage_whitespace_and_boundary_numbers() {
2797 let v = StringPairVec::from_vec(vec![
2798 pair("", "empty-key"),
2799 pair(" ", "spaces"),
2800 pair("\t\n", "tabs"),
2801 pair("0", "zero"),
2802 pair("-0", "neg-zero"),
2803 pair("9223372036854775807", "i64-max"),
2804 pair("NaN", "nan"),
2805 pair("inf", "inf"),
2806 ]);
2807
2808 assert_eq!(v.get_key("").map(AzString::as_str), Some("empty-key"));
2810 assert_eq!(v.get_key(" ").map(AzString::as_str), Some("spaces"));
2811 assert_eq!(v.get_key("\t\n").map(AzString::as_str), Some("tabs"));
2812
2813 assert_eq!(v.get_key("0").map(AzString::as_str), Some("zero"));
2815 assert_eq!(v.get_key("-0").map(AzString::as_str), Some("neg-zero"));
2816 assert_eq!(
2817 v.get_key("9223372036854775807").map(AzString::as_str),
2818 Some("i64-max")
2819 );
2820 assert_eq!(v.get_key("NaN").map(AzString::as_str), Some("nan"));
2821 assert_eq!(v.get_key("inf").map(AzString::as_str), Some("inf"));
2822 assert!(v.get_key("nan").is_none());
2823
2824 assert!(v.get_key("\u{0}\u{1}\u{7f}\\x\"';--").is_none());
2826 assert!(v.get_key(&"[".repeat(10_000)).is_none());
2827 assert!(v.get_key(&"{\"a\":".repeat(10_000)).is_none());
2828 }
2829
2830 #[test]
2831 fn get_key_handles_unicode_without_panicking() {
2832 let v = StringPairVec::from_vec(vec![
2833 pair("\u{1F600}", "grin"),
2834 pair("é", "precomposed"),
2835 pair("日本語", "jp"),
2836 ]);
2837 assert_eq!(v.get_key("\u{1F600}").map(AzString::as_str), Some("grin"));
2838 assert_eq!(v.get_key("日本語").map(AzString::as_str), Some("jp"));
2839 assert_eq!(v.get_key("é").map(AzString::as_str), Some("precomposed"));
2841 assert!(v.get_key("e\u{301}").is_none());
2842 assert!(v.get_key("日本").is_none());
2844 }
2845
2846 #[test]
2847 fn get_key_handles_extremely_long_input() {
2848 let huge = "k".repeat(1_000_000);
2849 let mut v = StringPairVec::from_vec(vec![pair("short", "1")]);
2850
2851 assert!(v.get_key(&huge).is_none());
2853
2854 v.push(AzStringPair {
2856 key: huge.as_str().into(),
2857 value: "big".into(),
2858 });
2859 assert_eq!(v.get_key(&huge).map(AzString::as_str), Some("big"));
2860 assert!(v.get_key(&"k".repeat(999_999)).is_none());
2862 assert!(v.get_key(&"k".repeat(1_000_001)).is_none());
2863 }
2864
2865 #[test]
2866 fn get_key_returns_first_of_duplicate_keys() {
2867 let v = StringPairVec::from_vec(vec![
2868 pair("dup", "first"),
2869 pair("dup", "second"),
2870 pair("dup", "third"),
2871 ]);
2872 assert_eq!(v.get_key("dup").map(AzString::as_str), Some("first"));
2873 }
2874
2875 #[test]
2876 fn get_key_mut_mutates_in_place() {
2877 let mut v = StringPairVec::from_vec(vec![pair("a", "1"), pair("b", "2")]);
2878 {
2879 let entry = v.get_key_mut("b").expect("b is present");
2880 entry.value = "changed".into();
2881 }
2882 assert_eq!(v.get_key("b").map(AzString::as_str), Some("changed"));
2883 assert_eq!(v.get_key("a").map(AzString::as_str), Some("1"));
2884 assert!(v.get_key_mut("missing").is_none());
2885 assert_eq!(v.len(), 2, "get_key_mut must not add entries");
2886
2887 {
2889 let entry = v.get_key_mut("a").expect("a is present");
2890 entry.key = "z".into();
2891 }
2892 assert!(v.get_key("a").is_none());
2893 assert_eq!(v.get_key("z").map(AzString::as_str), Some("1"));
2894 }
2895
2896 #[test]
2897 fn insert_kv_updates_existing_and_appends_new() {
2898 let mut v = StringPairVec::new();
2899 v.insert_kv("k", "v1");
2900 assert_eq!(v.len(), 1);
2901 assert_eq!(v.get_key("k").map(AzString::as_str), Some("v1"));
2902
2903 v.insert_kv("k", "v2");
2905 assert_eq!(v.len(), 1, "insert_kv must not duplicate an existing key");
2906 assert_eq!(v.get_key("k").map(AzString::as_str), Some("v2"));
2907
2908 v.insert_kv("other", "x");
2910 assert_eq!(v.len(), 2);
2911 assert_eq!(v.get_key("k").map(AzString::as_str), Some("v2"));
2912 assert_eq!(v.get_key("other").map(AzString::as_str), Some("x"));
2913
2914 for i in 0..100 {
2916 v.insert_kv(String::from("k"), format!("gen{i}"));
2917 }
2918 assert_eq!(v.len(), 2);
2919 assert_eq!(v.get_key("k").map(AzString::as_str), Some("gen99"));
2920 }
2921
2922 #[test]
2923 fn insert_kv_accepts_hostile_keys_and_values() {
2924 let mut v = StringPairVec::new();
2925 v.insert_kv("", "");
2926 assert_eq!(v.len(), 1);
2927 assert_eq!(v.get_key("").map(AzString::as_str), Some(""));
2928
2929 v.insert_kv("\u{1F600}", "😀");
2930 assert_eq!(v.get_key("\u{1F600}").map(AzString::as_str), Some("😀"));
2931
2932 v.insert_kv(" ", "\t\n");
2933 assert_eq!(v.get_key(" ").map(AzString::as_str), Some("\t\n"));
2934
2935 let huge_key = "K".repeat(100_000);
2937 let huge_val = "V".repeat(100_000);
2938 v.insert_kv(huge_key.clone(), huge_val.clone());
2939 let before = v.len();
2940 assert_eq!(
2941 v.get_key(&huge_key).map(AzString::as_str),
2942 Some(huge_val.as_str())
2943 );
2944 v.insert_kv(huge_key.clone(), String::from("small"));
2945 assert_eq!(v.len(), before, "long key must hit the update path");
2946 assert_eq!(v.get_key(&huge_key).map(AzString::as_str), Some("small"));
2947 }
2948
2949 #[test]
2950 fn insert_kv_only_updates_the_first_of_pre_existing_duplicates() {
2951 let mut v = StringPairVec::from_vec(vec![pair("dup", "first"), pair("dup", "second")]);
2954 v.insert_kv("dup", "updated");
2955 assert_eq!(v.len(), 2, "no new entry is appended");
2956 assert_eq!(v.get_key("dup").map(AzString::as_str), Some("updated"));
2957 assert_eq!(
2958 v.get(1).expect("second entry still present").value.as_str(),
2959 "second",
2960 "the shadowed duplicate is left untouched"
2961 );
2962 }
2963
2964 #[test]
2969 fn window_size_get_logical_size_is_the_identity() {
2970 for dims in [
2971 LogicalSize::zero(),
2972 LogicalSize::new(640.0, 480.0),
2973 LogicalSize::new(-1.0, -2.0),
2974 LogicalSize::new(f32::MAX, f32::MIN),
2975 LogicalSize::new(f32::INFINITY, f32::MIN_POSITIVE),
2976 ] {
2977 let ws = WindowSize {
2978 dimensions: dims,
2979 ..WindowSize::default()
2980 };
2981 assert_eq!(ws.get_logical_size(), dims);
2982 }
2983 let d = WindowSize::default();
2985 assert_eq!(d.get_logical_size(), LogicalSize::new(640.0, 480.0));
2986 assert_eq!(d.dpi, 96);
2987 }
2988
2989 #[test]
2990 fn window_size_get_layout_size_rounds_half_away_from_zero() {
2991 for (w, h, ew, eh) in [
2992 (0.0f32, 0.0f32, 0isize, 0isize),
2993 (640.0, 480.0, 640, 480),
2994 (640.4, 480.4, 640, 480),
2995 (640.6, 480.6, 641, 481),
2996 (640.5, 639.5, 641, 640),
2997 (-0.5, -1.5, -1, -2),
2998 ] {
2999 let ws = WindowSize {
3000 dimensions: LogicalSize::new(w, h),
3001 ..WindowSize::default()
3002 };
3003 assert_eq!(ws.get_layout_size(), LayoutSize::new(ew, eh), "{w}x{h}");
3004 }
3005 }
3006
3007 #[test]
3008 fn window_size_get_layout_size_saturates_on_non_finite() {
3009 let nan = WindowSize {
3011 dimensions: LogicalSize::new(f32::NAN, f32::NAN),
3012 ..WindowSize::default()
3013 };
3014 assert_eq!(nan.get_layout_size(), LayoutSize::new(0, 0));
3015
3016 let inf = WindowSize {
3017 dimensions: LogicalSize::new(f32::INFINITY, f32::NEG_INFINITY),
3018 ..WindowSize::default()
3019 };
3020 assert_eq!(
3021 inf.get_layout_size(),
3022 LayoutSize::new(isize::MAX, isize::MIN)
3023 );
3024
3025 let max = WindowSize {
3026 dimensions: LogicalSize::new(f32::MAX, f32::MIN),
3027 ..WindowSize::default()
3028 };
3029 let ls = max.get_layout_size();
3030 assert!(ls.width > 0 && ls.height < 0, "sign is preserved: {ls:?}");
3031 }
3032
3033 #[test]
3034 fn window_size_get_physical_size_saturates_instead_of_wrapping() {
3035 let neg = WindowSize {
3037 dimensions: LogicalSize::new(-100.0, -0.4),
3038 ..WindowSize::default()
3039 };
3040 assert_eq!(neg.get_physical_size(), PhysicalSize::new(0, 0));
3041
3042 let nan = WindowSize {
3044 dimensions: LogicalSize::new(f32::NAN, f32::INFINITY),
3045 ..WindowSize::default()
3046 };
3047 assert_eq!(nan.get_physical_size(), PhysicalSize::new(0, u32::MAX));
3048
3049 let huge = WindowSize {
3051 dimensions: LogicalSize::new(f32::MAX, f32::MAX),
3052 dpi: 384,
3053 ..WindowSize::default()
3054 };
3055 assert_eq!(
3056 huge.get_physical_size(),
3057 PhysicalSize::new(u32::MAX, u32::MAX)
3058 );
3059
3060 let normal = WindowSize::default();
3062 assert_eq!(normal.get_physical_size(), PhysicalSize::new(640, 480));
3063 let retina = WindowSize {
3064 dpi: 192,
3065 ..WindowSize::default()
3066 };
3067 assert_eq!(retina.get_physical_size(), PhysicalSize::new(1280, 960));
3068 }
3069
3070 #[test]
3071 fn window_size_get_hidpi_factor_is_never_zero_or_negative() {
3072 for dpi in [
3074 0u32,
3075 1,
3076 47,
3077 48,
3078 95,
3079 96,
3080 97,
3081 120,
3082 144,
3083 192,
3084 384,
3085 u32::from(u16::MAX),
3086 u32::MAX,
3087 ] {
3088 let ws = WindowSize {
3089 dpi,
3090 ..WindowSize::default()
3091 };
3092 let f = ws.get_hidpi_factor().inner.get();
3093 assert!(
3094 f.is_finite() && f > 0.0,
3095 "dpi {dpi} produced a non-positive / non-finite scale factor: {f}"
3096 );
3097 }
3098
3099 for (dpi, expected) in [(0u32, 1.0f32), (96, 1.0), (144, 1.5), (192, 2.0), (384, 4.0)] {
3101 let ws = WindowSize {
3102 dpi,
3103 ..WindowSize::default()
3104 };
3105 assert_eq!(ws.get_hidpi_factor().inner.get(), expected, "dpi {dpi}");
3106 }
3107
3108 let odd = WindowSize {
3110 dpi: 100,
3111 ..WindowSize::default()
3112 };
3113 let f = odd.get_hidpi_factor().inner.get();
3114 assert!((f - 100.0 / 96.0).abs() < 0.002, "dpi 100 -> {f}");
3115 }
3116
3117 #[test]
3122 fn virtual_keycode_from_u32_roundtrips_every_discriminant() {
3123 for v in 0..=LAST_VK {
3124 let vk = VirtualKeyCode::from_u32(v)
3125 .unwrap_or_else(|| panic!("discriminant {v} is missing from the from_u32 table"));
3126 assert_eq!(vk as u32, v, "from_u32({v}) does not round-trip");
3127 }
3128 assert_eq!(VirtualKeyCode::Key1 as u32, 0);
3130 assert_eq!(VirtualKeyCode::Cut as u32, LAST_VK);
3131 }
3132
3133 #[test]
3134 fn virtual_keycode_from_u32_rejects_out_of_range() {
3135 for v in [
3136 LAST_VK + 1,
3137 LAST_VK + 2,
3138 255,
3139 256,
3140 1024,
3141 i32::MAX as u32,
3142 u32::MAX - 1,
3143 u32::MAX,
3144 ] {
3145 assert_eq!(
3146 VirtualKeyCode::from_u32(v),
3147 None,
3148 "{v} must not decode to a keycode"
3149 );
3150 }
3151 }
3152
3153 #[test]
3154 fn virtual_keycode_get_lowercase_never_panics_and_maps_letters_and_digits() {
3155 for v in 0..=LAST_VK {
3157 let vk = VirtualKeyCode::from_u32(v).expect("discriminant in range");
3158 if let Some(c) = vk.get_lowercase() {
3159 assert!(c.is_ascii(), "{vk:?} produced a non-ASCII char {c:?}");
3160 assert!(!c.is_ascii_uppercase(), "{vk:?} must yield lowercase");
3161 }
3162 }
3163
3164 for (i, expected) in ('a'..='z').enumerate() {
3166 let vk = VirtualKeyCode::from_u32(10 + i as u32).expect("letter range");
3167 assert_eq!(vk.get_lowercase(), Some(expected));
3168 }
3169
3170 for (top, pad, c) in [
3172 (VirtualKeyCode::Key0, VirtualKeyCode::Numpad0, '0'),
3173 (VirtualKeyCode::Key1, VirtualKeyCode::Numpad1, '1'),
3174 (VirtualKeyCode::Key5, VirtualKeyCode::Numpad5, '5'),
3175 (VirtualKeyCode::Key9, VirtualKeyCode::Numpad9, '9'),
3176 ] {
3177 assert_eq!(top.get_lowercase(), Some(c));
3178 assert_eq!(pad.get_lowercase(), Some(c));
3179 }
3180
3181 assert_eq!(VirtualKeyCode::Minus.get_lowercase(), Some('-'));
3183 assert_eq!(VirtualKeyCode::Period.get_lowercase(), Some('.'));
3184 assert_eq!(VirtualKeyCode::Slash.get_lowercase(), Some('/'));
3185 assert_eq!(VirtualKeyCode::Caret.get_lowercase(), Some('^'));
3186
3187 for vk in [
3189 VirtualKeyCode::LShift,
3190 VirtualKeyCode::RControl,
3191 VirtualKeyCode::Escape,
3192 VirtualKeyCode::F12,
3193 VirtualKeyCode::Space,
3194 VirtualKeyCode::Return,
3195 VirtualKeyCode::Back,
3196 ] {
3197 assert_eq!(vk.get_lowercase(), None, "{vk:?}");
3198 }
3199 }
3200
3201 #[test]
3206 fn window_icon_get_key_returns_the_stored_key() {
3207 let small_key = IconKey::new();
3208 let large_key = IconKey::new();
3209
3210 let small = WindowIcon::Small(SmallWindowIconBytes {
3211 key: small_key,
3212 rgba_bytes: vec![0u8; 16 * 16 * 4].into(),
3213 });
3214 let large = WindowIcon::Large(LargeWindowIconBytes {
3215 key: large_key,
3216 rgba_bytes: vec![255u8; 32 * 32 * 4].into(),
3217 });
3218
3219 assert_eq!(small.get_key(), small_key);
3220 assert_eq!(large.get_key(), large_key);
3221
3222 let empty = WindowIcon::Small(SmallWindowIconBytes {
3224 key: small_key,
3225 rgba_bytes: vec![].into(),
3226 });
3227 assert_eq!(empty.get_key(), small_key);
3228 }
3229
3230 #[test]
3231 fn window_icon_identity_is_the_key_alone() {
3232 let key = IconKey::new();
3235 let a = WindowIcon::Small(SmallWindowIconBytes {
3236 key,
3237 rgba_bytes: vec![0u8; 4].into(),
3238 });
3239 let b = WindowIcon::Small(SmallWindowIconBytes {
3240 key,
3241 rgba_bytes: vec![7u8; 1024].into(),
3242 });
3243 let c = WindowIcon::Large(LargeWindowIconBytes {
3245 key,
3246 rgba_bytes: vec![9u8; 32 * 32 * 4].into(),
3247 });
3248
3249 assert_eq!(a, b);
3250 assert_eq!(a, c);
3251 assert_eq!(a.cmp(&c), Ordering::Equal);
3252 assert_eq!(a.partial_cmp(&b), Some(Ordering::Equal));
3253 assert_eq!(hash_of(&a), hash_of(&b));
3255 assert_eq!(hash_of(&a), hash_of(&c));
3256
3257 let older = WindowIcon::Small(SmallWindowIconBytes {
3259 key,
3260 rgba_bytes: vec![0u8; 4].into(),
3261 });
3262 let newer = WindowIcon::Small(SmallWindowIconBytes {
3263 key: IconKey::new(),
3264 rgba_bytes: vec![0u8; 4].into(),
3265 });
3266 assert_ne!(older, newer);
3267 assert_eq!(older.cmp(&newer), Ordering::Less);
3268 }
3269
3270 #[test]
3275 fn update_focus_warning_display_is_non_empty_for_every_variant() {
3276 let dom = format!("{}", UpdateFocusWarning::FocusInvalidDomId(DomId::ROOT_ID));
3277 assert!(dom.contains("invalid ID"), "{dom}");
3278 assert!(!dom.is_empty());
3279
3280 let node = format!(
3281 "{}",
3282 UpdateFocusWarning::FocusInvalidNodeId(NodeHierarchyItemId::NONE)
3283 );
3284 assert!(node.contains("invalid ID"), "{node}");
3285
3286 let huge = format!(
3288 "{}",
3289 UpdateFocusWarning::FocusInvalidNodeId(NodeHierarchyItemId::from_raw(usize::MAX))
3290 );
3291 assert!(!huge.is_empty());
3292
3293 let path = format!(
3294 "{}",
3295 UpdateFocusWarning::CouldNotFindFocusNode(CssPath::default())
3296 );
3297 assert!(
3298 path.starts_with("Could not find focus node for path:"),
3299 "{path}"
3300 );
3301 }
3302}