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)]
615#[repr(C)]
616pub struct DebugState {
617 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
653impl DebugState {
654 #[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 #[cfg(not(feature = "std"))]
689 #[must_use]
690 pub fn from_az_overlay_env() -> Self {
691 Self::default()
692 }
693
694 #[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 "hit-test" | "hittest" => s.show_hit_test_areas = true,
707 "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 "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 #[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)]
758pub struct TouchState {
759 pub num_touches: usize,
761 pub touch_points: TouchPointVec,
764}
765
766#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
768#[repr(C)]
769pub struct TouchPoint {
770 pub id: u64,
772 pub position: LogicalPosition,
774 pub force: f32,
777}
778
779impl_option!(
780 TouchPoint,
781 OptionTouchPoint,
782 [Debug, Copy, Clone, PartialEq, PartialOrd]
783);
784
785impl_vec!(TouchPoint, TouchPointVec, TouchPointVecDestructor, TouchPointVecDestructorType, TouchPointVecSlice, OptionTouchPoint);
786impl_vec_debug!(TouchPoint, TouchPointVec);
787impl_vec_clone!(TouchPoint, TouchPointVec, TouchPointVecDestructor);
788impl_vec_partialeq!(TouchPoint, TouchPointVec);
789
790#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Hash, Ord, Eq)]
792#[repr(C)]
793#[derive(Default)]
794pub enum WindowTheme {
795 DarkMode,
796 #[default]
797 LightMode,
798}
799
800
801impl_option!(
802 WindowTheme,
803 OptionWindowTheme,
804 [Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash]
805);
806
807#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
818#[repr(C)]
819pub struct MonitorId {
820 pub index: usize,
822 pub hash: u64,
824}
825
826impl MonitorId {
827 pub const PRIMARY: Self = Self { index: 0, hash: 0 };
829
830 #[must_use] pub const fn new(index: usize) -> Self {
832 Self { index, hash: 0 }
833 }
834
835 #[must_use] pub const fn from_index_and_hash(index: usize, hash: u64) -> Self {
837 Self { index, hash }
838 }
839
840 #[must_use] pub fn from_properties(
846 index: usize,
847 name: &str,
848 position: LayoutPoint,
849 size: LayoutSize,
850 ) -> Self {
851 use core::hash::{Hash, Hasher};
852
853 struct FnvHasher(u64);
855
856 impl Hasher for FnvHasher {
857 fn write(&mut self, bytes: &[u8]) {
858 const FNV_PRIME: u64 = 0x0100_0000_01b3;
859 for &byte in bytes {
860 self.0 ^= u64::from(byte);
861 self.0 = self.0.wrapping_mul(FNV_PRIME);
862 }
863 }
864
865 fn finish(&self) -> u64 {
866 self.0
867 }
868 }
869
870 const FNV_OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
871 let mut hasher = FnvHasher(FNV_OFFSET_BASIS);
872
873 name.hash(&mut hasher);
875 (position.x as i64).hash(&mut hasher);
876 (position.y as i64).hash(&mut hasher);
877 (size.width as i64).hash(&mut hasher);
878 (size.height as i64).hash(&mut hasher);
879
880 Self {
881 index,
882 hash: hasher.finish(),
883 }
884 }
885}
886
887impl_option!(
888 MonitorId,
889 OptionMonitorId,
890 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
891);
892
893#[derive(Debug, PartialEq, PartialOrd, Clone)]
895#[repr(C)]
896pub struct Monitor {
897 pub monitor_id: MonitorId,
899 pub monitor_name: OptionString,
901 pub size: LayoutSize,
903 pub position: LayoutPoint,
905 pub scale_factor: f64,
907 pub work_area: LayoutRect,
909 pub video_modes: VideoModeVec,
911 pub is_primary_monitor: bool,
913}
914
915impl_option!(
916 Monitor,
917 OptionMonitor,
918 copy = false,
919 [Debug, PartialEq, PartialOrd, Clone]
920);
921
922impl_vec!(Monitor, MonitorVec, MonitorVecDestructor, MonitorVecDestructorType, MonitorVecSlice, OptionMonitor);
923impl_vec_debug!(Monitor, MonitorVec);
924impl_vec_clone!(Monitor, MonitorVec, MonitorVecDestructor);
925impl_vec_partialeq!(Monitor, MonitorVec);
926impl_vec_partialord!(Monitor, MonitorVec);
927
928impl 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
937impl Default for Monitor {
938 fn default() -> Self {
939 Self {
940 monitor_id: MonitorId::PRIMARY,
941 monitor_name: OptionString::None,
942 size: LayoutSize::zero(),
943 position: LayoutPoint::zero(),
944 scale_factor: 1.0,
945 work_area: LayoutRect::zero(),
946 video_modes: Vec::new().into(),
947 is_primary_monitor: false,
948 }
949 }
950}
951#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
952#[repr(C)]
953pub struct VideoMode {
954 pub size: LayoutSize,
955 pub bit_depth: u16,
956 pub refresh_rate: u16,
957}
958
959impl_option!(
960 VideoMode,
961 OptionVideoMode,
962 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
963);
964
965impl_vec!(VideoMode, VideoModeVec, VideoModeVecDestructor, VideoModeVecDestructorType, VideoModeVecSlice, OptionVideoMode);
966impl_vec_clone!(VideoMode, VideoModeVec, VideoModeVecDestructor);
967impl_vec_debug!(VideoMode, VideoModeVec);
968impl_vec_partialeq!(VideoMode, VideoModeVec);
969impl_vec_partialord!(VideoMode, VideoModeVec);
970
971#[derive(Debug, Copy, Clone, PartialEq, Eq)]
973#[repr(C, u8)]
974#[derive(Default)]
975pub enum WindowPosition {
976 #[default]
977 Uninitialized,
978 Initialized(PhysicalPositionI32),
981 RelativeToParentWindow(PhysicalPositionI32),
989}
990#[allow(variant_size_differences)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
993#[repr(C, u8)]
994#[derive(Default)]
996pub enum ImePosition {
997 #[default]
998 Uninitialized,
999 Initialized(LogicalRect),
1000}
1001
1002
1003#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
1004#[repr(C)]
1005pub struct WindowFlags {
1006 pub frame: WindowFrame,
1008 pub decorations: WindowDecorations,
1010 pub background_material: WindowBackgroundMaterial,
1012 pub window_type: WindowType,
1014 pub close_requested: bool,
1017 pub is_visible: bool,
1019 pub is_always_on_top: bool,
1021 pub is_resizable: bool,
1023 pub has_focus: bool,
1025 pub smooth_scroll_enabled: bool,
1027 pub autotab_enabled: bool,
1029 pub has_decorations: bool,
1032 pub use_native_menus: bool,
1035 pub use_native_context_menus: bool,
1038 pub is_top_level: bool,
1042 pub prevent_system_sleep: bool,
1046 pub fullscreen_mode: FullScreenMode,
1055}
1056
1057impl_option!(
1058 WindowFlags,
1059 OptionWindowFlags,
1060 copy = false,
1061 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
1062);
1063
1064#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
1066#[repr(C)]
1067pub enum WindowType {
1068 Normal,
1070 Menu,
1072 Tooltip,
1074 Dialog,
1076}
1077
1078impl Default for WindowType {
1079 fn default() -> Self {
1080 Self::Normal
1081 }
1082}
1083
1084#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
1086#[repr(C)]
1087pub enum WindowFrame {
1088 Normal,
1089 Minimized,
1090 Maximized,
1091 Fullscreen,
1092}
1093
1094#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
1096#[repr(C)]
1097pub enum WindowDecorations {
1098 Normal,
1100 NoTitle,
1103 NoTitleAutoInject,
1111 NoControls,
1113 None,
1115}
1116
1117impl Default for WindowDecorations {
1118 fn default() -> Self {
1119 Self::Normal
1120 }
1121}
1122
1123#[derive(Debug, Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
1125#[repr(C)]
1126pub enum WindowBackgroundMaterial {
1127 Opaque,
1129 Transparent,
1131 Sidebar,
1133 Menu,
1135 HUD,
1137 Titlebar,
1139 MicaAlt,
1141}
1142
1143impl Default for WindowBackgroundMaterial {
1144 fn default() -> Self {
1145 Self::Opaque
1146 }
1147}
1148
1149impl Default for WindowFlags {
1150 fn default() -> Self {
1151 Self {
1152 frame: WindowFrame::Normal,
1153 decorations: WindowDecorations::Normal,
1154 background_material: WindowBackgroundMaterial::Opaque,
1155 window_type: WindowType::Normal,
1156 close_requested: false,
1157 is_visible: true,
1158 is_always_on_top: false,
1159 is_resizable: true,
1160 has_focus: true,
1161 smooth_scroll_enabled: true,
1162 autotab_enabled: true,
1163 has_decorations: false,
1164 use_native_menus: cfg!(any(target_os = "windows", target_os = "macos")),
1167 use_native_context_menus: cfg!(any(target_os = "windows", target_os = "macos")),
1168 is_top_level: false,
1169 prevent_system_sleep: false,
1170 fullscreen_mode: FullScreenMode::FastFullScreen,
1171 }
1172 }
1173}
1174
1175impl WindowFlags {
1176 #[inline]
1178 #[must_use] pub fn is_menu_window(&self) -> bool {
1179 self.window_type == WindowType::Menu
1180 }
1181
1182 #[inline]
1184 #[must_use] pub fn is_tooltip_window(&self) -> bool {
1185 self.window_type == WindowType::Tooltip
1186 }
1187
1188 #[inline]
1190 #[must_use] pub fn is_dialog_window(&self) -> bool {
1191 self.window_type == WindowType::Dialog
1192 }
1193
1194 #[inline]
1196 #[must_use] pub const fn window_has_focus(&self) -> bool {
1197 self.has_focus
1198 }
1199
1200 #[inline]
1202 #[must_use] pub const fn is_close_requested(&self) -> bool {
1203 self.close_requested
1204 }
1205
1206 #[inline]
1208 #[must_use] pub const fn has_csd(&self) -> bool {
1209 self.has_decorations
1210 }
1211
1212 #[inline]
1214 #[must_use] pub const fn use_native_menus(&self) -> bool {
1215 self.use_native_menus
1216 }
1217
1218 #[inline]
1220 #[must_use] pub const fn use_native_context_menus(&self) -> bool {
1221 self.use_native_context_menus
1222 }
1223}
1224
1225#[derive(Debug, Default, Clone, PartialEq, PartialOrd)]
1227#[repr(C)]
1228pub struct PlatformSpecificOptions {
1229 pub windows_options: WindowsWindowOptions,
1230 pub linux_options: LinuxWindowOptions,
1231 pub mac_options: MacWindowOptions,
1232 pub wasm_options: WasmWindowOptions,
1233}
1234
1235unsafe impl Sync for PlatformSpecificOptions {}
1238#[allow(clippy::non_send_fields_in_send_ty)] unsafe impl Send for PlatformSpecificOptions {}
1240
1241#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
1242#[repr(C)]
1243pub struct WindowsWindowOptions {
1244 pub allow_drag_and_drop: bool,
1246 pub no_redirection_bitmap: bool,
1248 pub window_icon: OptionWindowIcon,
1250 pub taskbar_icon: OptionTaskBarIcon,
1254 }
1259
1260impl Default for WindowsWindowOptions {
1261 fn default() -> Self {
1262 Self {
1263 allow_drag_and_drop: true,
1264 no_redirection_bitmap: false,
1265 window_icon: OptionWindowIcon::None,
1266 taskbar_icon: OptionTaskBarIcon::None,
1267 }
1268 }
1269}
1270
1271#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1274#[repr(C)]
1275#[derive(Default)]
1276pub enum XWindowType {
1277 Desktop,
1281 Dock,
1284 Toolbar,
1286 Menu,
1288 Utility,
1290 Splash,
1292 Dialog,
1294 DropdownMenu,
1297 PopupMenu,
1300 Tooltip,
1303 Notification,
1306 Combo,
1309 Dnd,
1312 #[default]
1314 Normal,
1315}
1316
1317impl_option!(
1318 XWindowType,
1319 OptionXWindowType,
1320 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
1321);
1322
1323
1324#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
1325#[repr(C)]
1326#[derive(Default)]
1327pub enum UserAttentionType {
1328 #[default]
1329 None,
1330 Critical,
1331 Informational,
1332}
1333
1334
1335#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
1337#[repr(C)]
1338pub struct LinuxDecorationsState {
1339 pub is_dragging_titlebar: bool,
1340 pub close_button_hover: bool,
1341 pub maximize_button_hover: bool,
1342 pub minimize_button_hover: bool,
1343}
1344
1345impl_option!(
1346 LinuxDecorationsState,
1347 OptionLinuxDecorationsState,
1348 [Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash]
1349);
1350
1351#[derive(Debug, Default, Clone, PartialEq, PartialOrd)]
1352#[repr(C)]
1353pub struct LinuxWindowOptions {
1354 pub wayland_theme: OptionWaylandTheme,
1355 pub window_icon: OptionWindowIcon,
1356 pub x11_gtk_theme_variant: OptionString,
1359 pub wayland_app_id: OptionString,
1366 pub x11_wm_classes: StringPairVec,
1369 pub x11_window_types: XWindowTypeVec,
1372 pub x11_visual: OptionX11Visual,
1374 pub x11_resize_increments: OptionLogicalSize,
1377 pub x11_base_size: OptionLogicalSize,
1380 pub x11_screen: OptionI32,
1382 pub request_user_attention: UserAttentionType,
1383 pub x11_decorations_state: OptionLinuxDecorationsState,
1385 pub x11_override_redirect: bool,
1388}
1389
1390pub type X11Visual = *const c_void;
1391impl_option!(
1392 X11Visual,
1393 OptionX11Visual,
1394 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
1395);
1396
1397#[derive(Debug, Default, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1399#[repr(C)]
1400pub struct AzStringPair {
1401 pub key: AzString,
1402 pub value: AzString,
1403}
1404
1405impl_option!(
1406 AzStringPair,
1407 OptionStringPair,
1408 copy = false,
1409 [Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash]
1410);
1411
1412impl_vec!(AzStringPair, StringPairVec, StringPairVecDestructor, StringPairVecDestructorType, StringPairVecSlice, OptionStringPair);
1413impl_vec_mut!(AzStringPair, StringPairVec);
1414impl_vec_debug!(AzStringPair, StringPairVec);
1415impl_vec_partialord!(AzStringPair, StringPairVec);
1416impl_vec_ord!(AzStringPair, StringPairVec);
1417impl_vec_clone!(AzStringPair, StringPairVec, StringPairVecDestructor);
1418impl_vec_partialeq!(AzStringPair, StringPairVec);
1419impl_vec_eq!(AzStringPair, StringPairVec);
1420impl_vec_hash!(AzStringPair, StringPairVec);
1421
1422impl_option!(
1423 StringPairVec,
1424 OptionStringPairVec,
1425 copy = false,
1426 [Debug, Clone, PartialOrd, PartialEq, Ord, Eq, Hash]
1427);
1428
1429impl StringPairVec {
1430 #[must_use] pub fn get_key(&self, search_key: &str) -> Option<&AzString> {
1431 self.as_ref().iter().find_map(|v| {
1432 if v.key.as_str() == search_key {
1433 Some(&v.value)
1434 } else {
1435 None
1436 }
1437 })
1438 }
1439 pub fn get_key_mut(&mut self, search_key: &str) -> Option<&mut AzStringPair> {
1440 self.as_mut()
1441 .iter_mut()
1442 .find(|v| v.key.as_str() == search_key)
1443 }
1444 pub fn insert_kv<I: Into<AzString>>(&mut self, key: I, value: I) {
1445 let key = key.into();
1446 let value = value.into();
1447 match self.get_key_mut(key.as_str()) {
1448 None => {}
1449 Some(s) => {
1450 s.value = value;
1451 return;
1452 }
1453 }
1454 self.push(AzStringPair { key, value });
1455 }
1456}
1457
1458impl_vec!(XWindowType, XWindowTypeVec, XWindowTypeVecDestructor, XWindowTypeVecDestructorType, XWindowTypeVecSlice, OptionXWindowType);
1459impl_vec_debug!(XWindowType, XWindowTypeVec);
1460impl_vec_partialord!(XWindowType, XWindowTypeVec);
1461impl_vec_ord!(XWindowType, XWindowTypeVec);
1462impl_vec_clone!(XWindowType, XWindowTypeVec, XWindowTypeVecDestructor);
1463impl_vec_partialeq!(XWindowType, XWindowTypeVec);
1464impl_vec_eq!(XWindowType, XWindowTypeVec);
1465impl_vec_hash!(XWindowType, XWindowTypeVec);
1466
1467impl_option!(
1468 WaylandTheme,
1469 OptionWaylandTheme,
1470 copy = false,
1471 [Debug, Clone, PartialEq, PartialOrd]
1472);
1473
1474#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1476#[repr(C)]
1477#[allow(clippy::pub_underscore_fields)]
1479pub struct MacWindowOptions {
1480 pub _reserved: u8,
1482}
1483
1484#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1486#[repr(C)]
1487#[allow(clippy::pub_underscore_fields)]
1489pub struct WasmWindowOptions {
1490 pub _reserved: u8,
1492}
1493
1494#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1495#[repr(C)]
1496#[derive(Default)]
1497pub enum FullScreenMode {
1498 SlowFullScreen,
1501 #[default]
1504 FastFullScreen,
1505 SlowWindowed,
1508 FastWindowed,
1511}
1512
1513
1514#[derive(Debug, Clone, PartialEq, PartialOrd)]
1517#[repr(C)]
1518pub struct WaylandTheme {
1519 pub title_bar_active_background_color: ColorU,
1520 pub title_bar_active_separator_color: ColorU,
1521 pub title_bar_active_text_color: ColorU,
1522 pub title_bar_inactive_background_color: ColorU,
1523 pub title_bar_inactive_separator_color: ColorU,
1524 pub title_bar_inactive_text_color: ColorU,
1525 pub maximize_idle_foreground_inactive_color: ColorU,
1526 pub minimize_idle_foreground_inactive_color: ColorU,
1527 pub close_idle_foreground_inactive_color: ColorU,
1528 pub maximize_hovered_foreground_inactive_color: ColorU,
1529 pub minimize_hovered_foreground_inactive_color: ColorU,
1530 pub close_hovered_foreground_inactive_color: ColorU,
1531 pub maximize_disabled_foreground_inactive_color: ColorU,
1532 pub minimize_disabled_foreground_inactive_color: ColorU,
1533 pub close_disabled_foreground_inactive_color: ColorU,
1534 pub maximize_idle_background_inactive_color: ColorU,
1535 pub minimize_idle_background_inactive_color: ColorU,
1536 pub close_idle_background_inactive_color: ColorU,
1537 pub maximize_hovered_background_inactive_color: ColorU,
1538 pub minimize_hovered_background_inactive_color: ColorU,
1539 pub close_hovered_background_inactive_color: ColorU,
1540 pub maximize_disabled_background_inactive_color: ColorU,
1541 pub minimize_disabled_background_inactive_color: ColorU,
1542 pub close_disabled_background_inactive_color: ColorU,
1543 pub maximize_idle_foreground_active_color: ColorU,
1544 pub minimize_idle_foreground_active_color: ColorU,
1545 pub close_idle_foreground_active_color: ColorU,
1546 pub maximize_hovered_foreground_active_color: ColorU,
1547 pub minimize_hovered_foreground_active_color: ColorU,
1548 pub close_hovered_foreground_active_color: ColorU,
1549 pub maximize_disabled_foreground_active_color: ColorU,
1550 pub minimize_disabled_foreground_active_color: ColorU,
1551 pub close_disabled_foreground_active_color: ColorU,
1552 pub maximize_idle_background_active_color: ColorU,
1553 pub minimize_idle_background_active_color: ColorU,
1554 pub close_idle_background_active_color: ColorU,
1555 pub maximize_hovered_background_active_color: ColorU,
1556 pub minimize_hovered_background_active_color: ColorU,
1557 pub close_hovered_background_active_color: ColorU,
1558 pub maximize_disabled_background_active_color: ColorU,
1559 pub minimize_disabled_background_active_color: ColorU,
1560 pub close_disabled_background_active_color: ColorU,
1561 pub title_bar_font: AzString,
1562 pub title_bar_font_size: f32,
1563}
1564
1565#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd)]
1566#[repr(C)]
1567pub struct WindowSize {
1568 pub dimensions: LogicalSize,
1571 pub dpi: u32,
1573 pub min_dimensions: OptionLogicalSize,
1575 pub max_dimensions: OptionLogicalSize,
1577}
1578
1579impl WindowSize {
1580 #[allow(clippy::cast_possible_truncation)] #[must_use] pub fn get_layout_size(&self) -> LayoutSize {
1582 LayoutSize::new(
1583 libm::roundf(self.dimensions.width) as isize,
1584 libm::roundf(self.dimensions.height) as isize,
1585 )
1586 }
1587
1588 #[must_use] pub const fn get_logical_size(&self) -> LogicalSize {
1590 self.dimensions
1591 }
1592
1593 #[must_use] pub fn get_physical_size(&self) -> PhysicalSize<u32> {
1594 self.dimensions
1595 .to_physical(self.get_hidpi_factor().inner.get())
1596 }
1597
1598 #[allow(clippy::cast_precision_loss)] #[must_use] pub fn get_hidpi_factor(&self) -> DpiScaleFactor {
1600 let dpi = if self.dpi == 0 { 96 } else { self.dpi };
1605 DpiScaleFactor {
1606 inner: FloatValue::new(dpi as f32 / 96.0),
1607 }
1608 }
1609}
1610
1611impl Default for WindowSize {
1612 fn default() -> Self {
1613 Self {
1614 dimensions: LogicalSize::new(640.0, 480.0),
1615 dpi: 96,
1616 min_dimensions: None.into(),
1617 max_dimensions: None.into(),
1618 }
1619 }
1620}
1621
1622#[repr(C)]
1623#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
1624pub enum RendererType {
1625 Hardware,
1627 Software,
1629}
1630
1631impl_option!(
1632 RendererType,
1633 OptionRendererType,
1634 [Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash]
1635);
1636
1637#[derive(Debug, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
1638pub enum UpdateFocusWarning {
1639 FocusInvalidDomId(DomId),
1640 FocusInvalidNodeId(NodeHierarchyItemId),
1641 CouldNotFindFocusNode(CssPath),
1642}
1643
1644impl ::core::fmt::Display for UpdateFocusWarning {
1645 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
1646 use self::UpdateFocusWarning::{FocusInvalidDomId, FocusInvalidNodeId, CouldNotFindFocusNode};
1647 match self {
1648 FocusInvalidDomId(dom_id) => write!(f, "Focusing on DOM with invalid ID: {dom_id:?}"),
1649 FocusInvalidNodeId(node_id) => {
1650 write!(f, "Focusing on node with invalid ID: {node_id}")
1651 }
1652 CouldNotFindFocusNode(css_path) => {
1653 write!(f, "Could not find focus node for path: {css_path}")
1654 }
1655 }
1656 }
1657}
1658
1659#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1661#[repr(C, u8)]
1662pub enum AcceleratorKey {
1663 Ctrl,
1664 Alt,
1665 Shift,
1666 Key(VirtualKeyCode),
1667}
1668
1669impl AcceleratorKey {
1670 #[must_use] pub fn matches(&self, keyboard_state: &KeyboardState) -> bool {
1674 use self::AcceleratorKey::{Ctrl, Alt, Shift, Key};
1675 match self {
1676 Ctrl => keyboard_state.ctrl_down(),
1677 Alt => keyboard_state.alt_down(),
1678 Shift => keyboard_state.shift_down(),
1679 Key(k) => keyboard_state.is_key_down(*k),
1680 }
1681 }
1682}
1683
1684#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1686#[repr(C)]
1687pub enum VirtualKeyCode {
1688 Key1,
1689 Key2,
1690 Key3,
1691 Key4,
1692 Key5,
1693 Key6,
1694 Key7,
1695 Key8,
1696 Key9,
1697 Key0,
1698 A,
1699 B,
1700 C,
1701 D,
1702 E,
1703 F,
1704 G,
1705 H,
1706 I,
1707 J,
1708 K,
1709 L,
1710 M,
1711 N,
1712 O,
1713 P,
1714 Q,
1715 R,
1716 S,
1717 T,
1718 U,
1719 V,
1720 W,
1721 X,
1722 Y,
1723 Z,
1724 Escape,
1725 F1,
1726 F2,
1727 F3,
1728 F4,
1729 F5,
1730 F6,
1731 F7,
1732 F8,
1733 F9,
1734 F10,
1735 F11,
1736 F12,
1737 F13,
1738 F14,
1739 F15,
1740 F16,
1741 F17,
1742 F18,
1743 F19,
1744 F20,
1745 F21,
1746 F22,
1747 F23,
1748 F24,
1749 Snapshot,
1750 Scroll,
1751 Pause,
1752 Insert,
1753 Home,
1754 Delete,
1755 End,
1756 PageDown,
1757 PageUp,
1758 Left,
1759 Up,
1760 Right,
1761 Down,
1762 Back,
1763 Return,
1764 Space,
1765 Compose,
1766 Caret,
1767 Numlock,
1768 Numpad0,
1769 Numpad1,
1770 Numpad2,
1771 Numpad3,
1772 Numpad4,
1773 Numpad5,
1774 Numpad6,
1775 Numpad7,
1776 Numpad8,
1777 Numpad9,
1778 NumpadAdd,
1779 NumpadDivide,
1780 NumpadDecimal,
1781 NumpadComma,
1782 NumpadEnter,
1783 NumpadEquals,
1784 NumpadMultiply,
1785 NumpadSubtract,
1786 AbntC1,
1787 AbntC2,
1788 Apostrophe,
1789 Apps,
1790 Asterisk,
1791 At,
1792 Ax,
1793 Backslash,
1794 Calculator,
1795 Capital,
1796 Colon,
1797 Comma,
1798 Convert,
1799 Equals,
1800 Grave,
1801 Kana,
1802 Kanji,
1803 LAlt,
1804 LBracket,
1805 LControl,
1806 LShift,
1807 LWin,
1808 Mail,
1809 MediaSelect,
1810 MediaStop,
1811 Minus,
1812 Mute,
1813 MyComputer,
1814 NavigateForward,
1815 NavigateBackward,
1816 NextTrack,
1817 NoConvert,
1818 OEM102,
1819 Period,
1820 PlayPause,
1821 Plus,
1822 Power,
1823 PrevTrack,
1824 RAlt,
1825 RBracket,
1826 RControl,
1827 RShift,
1828 RWin,
1829 Semicolon,
1830 Slash,
1831 Sleep,
1832 Stop,
1833 Sysrq,
1834 Tab,
1835 Underline,
1836 Unlabeled,
1837 VolumeDown,
1838 VolumeUp,
1839 Wake,
1840 WebBack,
1841 WebFavorites,
1842 WebForward,
1843 WebHome,
1844 WebRefresh,
1845 WebSearch,
1846 WebStop,
1847 Yen,
1848 Copy,
1849 Paste,
1850 Cut,
1851}
1852
1853impl VirtualKeyCode {
1854 #[must_use]
1862 #[allow(clippy::too_many_lines)] pub const fn from_u32(v: u32) -> Option<Self> {
1864 match v {
1865 0 => Some(Self::Key1),
1866 1 => Some(Self::Key2),
1867 2 => Some(Self::Key3),
1868 3 => Some(Self::Key4),
1869 4 => Some(Self::Key5),
1870 5 => Some(Self::Key6),
1871 6 => Some(Self::Key7),
1872 7 => Some(Self::Key8),
1873 8 => Some(Self::Key9),
1874 9 => Some(Self::Key0),
1875 10 => Some(Self::A),
1876 11 => Some(Self::B),
1877 12 => Some(Self::C),
1878 13 => Some(Self::D),
1879 14 => Some(Self::E),
1880 15 => Some(Self::F),
1881 16 => Some(Self::G),
1882 17 => Some(Self::H),
1883 18 => Some(Self::I),
1884 19 => Some(Self::J),
1885 20 => Some(Self::K),
1886 21 => Some(Self::L),
1887 22 => Some(Self::M),
1888 23 => Some(Self::N),
1889 24 => Some(Self::O),
1890 25 => Some(Self::P),
1891 26 => Some(Self::Q),
1892 27 => Some(Self::R),
1893 28 => Some(Self::S),
1894 29 => Some(Self::T),
1895 30 => Some(Self::U),
1896 31 => Some(Self::V),
1897 32 => Some(Self::W),
1898 33 => Some(Self::X),
1899 34 => Some(Self::Y),
1900 35 => Some(Self::Z),
1901 36 => Some(Self::Escape),
1902 37 => Some(Self::F1),
1903 38 => Some(Self::F2),
1904 39 => Some(Self::F3),
1905 40 => Some(Self::F4),
1906 41 => Some(Self::F5),
1907 42 => Some(Self::F6),
1908 43 => Some(Self::F7),
1909 44 => Some(Self::F8),
1910 45 => Some(Self::F9),
1911 46 => Some(Self::F10),
1912 47 => Some(Self::F11),
1913 48 => Some(Self::F12),
1914 49 => Some(Self::F13),
1915 50 => Some(Self::F14),
1916 51 => Some(Self::F15),
1917 52 => Some(Self::F16),
1918 53 => Some(Self::F17),
1919 54 => Some(Self::F18),
1920 55 => Some(Self::F19),
1921 56 => Some(Self::F20),
1922 57 => Some(Self::F21),
1923 58 => Some(Self::F22),
1924 59 => Some(Self::F23),
1925 60 => Some(Self::F24),
1926 61 => Some(Self::Snapshot),
1927 62 => Some(Self::Scroll),
1928 63 => Some(Self::Pause),
1929 64 => Some(Self::Insert),
1930 65 => Some(Self::Home),
1931 66 => Some(Self::Delete),
1932 67 => Some(Self::End),
1933 68 => Some(Self::PageDown),
1934 69 => Some(Self::PageUp),
1935 70 => Some(Self::Left),
1936 71 => Some(Self::Up),
1937 72 => Some(Self::Right),
1938 73 => Some(Self::Down),
1939 74 => Some(Self::Back),
1940 75 => Some(Self::Return),
1941 76 => Some(Self::Space),
1942 77 => Some(Self::Compose),
1943 78 => Some(Self::Caret),
1944 79 => Some(Self::Numlock),
1945 80 => Some(Self::Numpad0),
1946 81 => Some(Self::Numpad1),
1947 82 => Some(Self::Numpad2),
1948 83 => Some(Self::Numpad3),
1949 84 => Some(Self::Numpad4),
1950 85 => Some(Self::Numpad5),
1951 86 => Some(Self::Numpad6),
1952 87 => Some(Self::Numpad7),
1953 88 => Some(Self::Numpad8),
1954 89 => Some(Self::Numpad9),
1955 90 => Some(Self::NumpadAdd),
1956 91 => Some(Self::NumpadDivide),
1957 92 => Some(Self::NumpadDecimal),
1958 93 => Some(Self::NumpadComma),
1959 94 => Some(Self::NumpadEnter),
1960 95 => Some(Self::NumpadEquals),
1961 96 => Some(Self::NumpadMultiply),
1962 97 => Some(Self::NumpadSubtract),
1963 98 => Some(Self::AbntC1),
1964 99 => Some(Self::AbntC2),
1965 100 => Some(Self::Apostrophe),
1966 101 => Some(Self::Apps),
1967 102 => Some(Self::Asterisk),
1968 103 => Some(Self::At),
1969 104 => Some(Self::Ax),
1970 105 => Some(Self::Backslash),
1971 106 => Some(Self::Calculator),
1972 107 => Some(Self::Capital),
1973 108 => Some(Self::Colon),
1974 109 => Some(Self::Comma),
1975 110 => Some(Self::Convert),
1976 111 => Some(Self::Equals),
1977 112 => Some(Self::Grave),
1978 113 => Some(Self::Kana),
1979 114 => Some(Self::Kanji),
1980 115 => Some(Self::LAlt),
1981 116 => Some(Self::LBracket),
1982 117 => Some(Self::LControl),
1983 118 => Some(Self::LShift),
1984 119 => Some(Self::LWin),
1985 120 => Some(Self::Mail),
1986 121 => Some(Self::MediaSelect),
1987 122 => Some(Self::MediaStop),
1988 123 => Some(Self::Minus),
1989 124 => Some(Self::Mute),
1990 125 => Some(Self::MyComputer),
1991 126 => Some(Self::NavigateForward),
1992 127 => Some(Self::NavigateBackward),
1993 128 => Some(Self::NextTrack),
1994 129 => Some(Self::NoConvert),
1995 130 => Some(Self::OEM102),
1996 131 => Some(Self::Period),
1997 132 => Some(Self::PlayPause),
1998 133 => Some(Self::Plus),
1999 134 => Some(Self::Power),
2000 135 => Some(Self::PrevTrack),
2001 136 => Some(Self::RAlt),
2002 137 => Some(Self::RBracket),
2003 138 => Some(Self::RControl),
2004 139 => Some(Self::RShift),
2005 140 => Some(Self::RWin),
2006 141 => Some(Self::Semicolon),
2007 142 => Some(Self::Slash),
2008 143 => Some(Self::Sleep),
2009 144 => Some(Self::Stop),
2010 145 => Some(Self::Sysrq),
2011 146 => Some(Self::Tab),
2012 147 => Some(Self::Underline),
2013 148 => Some(Self::Unlabeled),
2014 149 => Some(Self::VolumeDown),
2015 150 => Some(Self::VolumeUp),
2016 151 => Some(Self::Wake),
2017 152 => Some(Self::WebBack),
2018 153 => Some(Self::WebFavorites),
2019 154 => Some(Self::WebForward),
2020 155 => Some(Self::WebHome),
2021 156 => Some(Self::WebRefresh),
2022 157 => Some(Self::WebSearch),
2023 158 => Some(Self::WebStop),
2024 159 => Some(Self::Yen),
2025 160 => Some(Self::Copy),
2026 161 => Some(Self::Paste),
2027 162 => Some(Self::Cut),
2028 _ => None,
2029 }
2030 }
2031
2032 #[must_use] pub const fn get_lowercase(&self) -> Option<char> {
2033 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};
2034 match self {
2035 A => Some('a'),
2036 B => Some('b'),
2037 C => Some('c'),
2038 D => Some('d'),
2039 E => Some('e'),
2040 F => Some('f'),
2041 G => Some('g'),
2042 H => Some('h'),
2043 I => Some('i'),
2044 J => Some('j'),
2045 K => Some('k'),
2046 L => Some('l'),
2047 M => Some('m'),
2048 N => Some('n'),
2049 O => Some('o'),
2050 P => Some('p'),
2051 Q => Some('q'),
2052 R => Some('r'),
2053 S => Some('s'),
2054 T => Some('t'),
2055 U => Some('u'),
2056 V => Some('v'),
2057 W => Some('w'),
2058 X => Some('x'),
2059 Y => Some('y'),
2060 Z => Some('z'),
2061 Key0 | Numpad0 => Some('0'),
2062 Key1 | Numpad1 => Some('1'),
2063 Key2 | Numpad2 => Some('2'),
2064 Key3 | Numpad3 => Some('3'),
2065 Key4 | Numpad4 => Some('4'),
2066 Key5 | Numpad5 => Some('5'),
2067 Key6 | Numpad6 => Some('6'),
2068 Key7 | Numpad7 => Some('7'),
2069 Key8 | Numpad8 => Some('8'),
2070 Key9 | Numpad9 => Some('9'),
2071 Minus => Some('-'),
2072 Asterisk => Some('*'),
2073 At => Some('@'),
2074 Period => Some('.'),
2075 Semicolon => Some(';'),
2076 Slash => Some('/'),
2077 Caret => Some('^'),
2078 _ => None,
2079 }
2080 }
2081}
2082
2083#[derive(Debug, Clone)]
2085#[repr(C)]
2086pub struct SmallWindowIconBytes {
2087 pub key: IconKey,
2088 pub rgba_bytes: U8Vec,
2089}
2090
2091#[derive(Debug, Clone)]
2093#[repr(C)]
2094pub struct LargeWindowIconBytes {
2095 pub key: IconKey,
2096 pub rgba_bytes: U8Vec,
2097}
2098
2099#[derive(Debug, Clone)]
2101#[repr(C, u8)]
2102pub enum WindowIcon {
2103 Small(SmallWindowIconBytes),
2104 Large(LargeWindowIconBytes),
2106}
2107
2108impl_option!(
2109 WindowIcon,
2110 OptionWindowIcon,
2111 copy = false,
2112 [Debug, Clone, PartialOrd, PartialEq, Eq, Hash, Ord]
2113);
2114
2115impl WindowIcon {
2116 #[must_use] pub const fn get_key(&self) -> IconKey {
2117 match &self {
2118 Self::Small(SmallWindowIconBytes { key, .. })
2119 | Self::Large(LargeWindowIconBytes { key, .. }) => *key,
2120 }
2121 }
2122}
2123impl PartialEq for WindowIcon {
2126 fn eq(&self, rhs: &Self) -> bool {
2127 self.get_key() == rhs.get_key()
2128 }
2129}
2130
2131impl PartialOrd for WindowIcon {
2132 fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
2133 Some((self.get_key()).cmp(&rhs.get_key()))
2134 }
2135}
2136
2137impl Eq for WindowIcon {}
2138
2139impl Ord for WindowIcon {
2140 fn cmp(&self, rhs: &Self) -> Ordering {
2141 (self.get_key()).cmp(&rhs.get_key())
2142 }
2143}
2144
2145impl Hash for WindowIcon {
2146 fn hash<H>(&self, state: &mut H)
2147 where
2148 H: Hasher,
2149 {
2150 self.get_key().hash(state);
2151 }
2152}
2153
2154#[derive(Debug, Clone)]
2156#[repr(C)]
2157pub struct TaskBarIcon {
2158 pub key: IconKey,
2159 pub rgba_bytes: U8Vec,
2160}
2161
2162impl_option!(
2163 TaskBarIcon,
2164 OptionTaskBarIcon,
2165 copy = false,
2166 [Debug, Clone, PartialOrd, PartialEq, Eq, Hash, Ord]
2167);
2168
2169impl PartialEq for TaskBarIcon {
2170 fn eq(&self, rhs: &Self) -> bool {
2171 self.key == rhs.key
2172 }
2173}
2174
2175impl PartialOrd for TaskBarIcon {
2176 fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
2177 Some((self.key).cmp(&rhs.key))
2178 }
2179}
2180
2181impl Eq for TaskBarIcon {}
2182
2183impl Ord for TaskBarIcon {
2184 fn cmp(&self, rhs: &Self) -> Ordering {
2185 (self.key).cmp(&rhs.key)
2186 }
2187}
2188
2189impl Hash for TaskBarIcon {
2190 fn hash<H>(&self, state: &mut H)
2191 where
2192 H: Hasher,
2193 {
2194 self.key.hash(state);
2195 }
2196}
2197
2198#[cfg(test)]
2199#[allow(clippy::float_cmp)] mod audit_tests {
2201 use super::*;
2202
2203 #[test]
2204 fn hidpi_factor_guards_zero_dpi() {
2205 let ws = WindowSize { dpi: 0, ..WindowSize::default() };
2208 let factor = ws.get_hidpi_factor().inner.get();
2209 assert_eq!(factor, 1.0);
2210
2211 let ws2 = WindowSize { dpi: 192, ..WindowSize::default() };
2212 assert_eq!(ws2.get_hidpi_factor().inner.get(), 2.0);
2213 }
2214
2215 #[test]
2216 fn virtual_keycode_from_u32_roundtrips() {
2217 for vk in [
2219 VirtualKeyCode::Key1,
2220 VirtualKeyCode::A,
2221 VirtualKeyCode::Z,
2222 VirtualKeyCode::Left,
2223 VirtualKeyCode::Back,
2224 VirtualKeyCode::Delete,
2225 VirtualKeyCode::LControl,
2226 VirtualKeyCode::Cut,
2227 ] {
2228 assert_eq!(VirtualKeyCode::from_u32(vk as u32), Some(vk));
2229 }
2230 assert_eq!(VirtualKeyCode::from_u32(10_000), None);
2232 }
2233}
2234
2235#[cfg(test)]
2236#[allow(clippy::float_cmp)] mod autotest_generated {
2238 use alloc::{format, string::String, vec};
2239
2240 use super::*;
2241
2242 const LAST_VK: u32 = 162;
2244
2245 #[derive(Default)]
2248 struct TestHasher(u64);
2249
2250 impl Hasher for TestHasher {
2251 fn write(&mut self, bytes: &[u8]) {
2252 for &b in bytes {
2253 self.0 = self.0.rotate_left(5) ^ u64::from(b);
2254 }
2255 }
2256 fn finish(&self) -> u64 {
2257 self.0
2258 }
2259 }
2260
2261 fn hash_of<T: Hash>(value: &T) -> u64 {
2262 let mut h = TestHasher::default();
2263 value.hash(&mut h);
2264 h.finish()
2265 }
2266
2267 fn keyboard_with(keys: &[VirtualKeyCode]) -> KeyboardState {
2268 KeyboardState {
2269 pressed_virtual_keycodes: keys.to_vec().into(),
2270 ..KeyboardState::default()
2271 }
2272 }
2273
2274 fn pair(key: &str, value: &str) -> AzStringPair {
2275 AzStringPair {
2276 key: key.into(),
2277 value: value.into(),
2278 }
2279 }
2280
2281 #[test]
2286 fn window_id_new_is_unique_and_monotonic() {
2287 let mut ids = BTreeSet::new();
2290 let mut prev = WindowId::new();
2291 assert!(ids.insert(prev));
2292 for _ in 0..1000 {
2293 let next = WindowId::new();
2294 assert!(next.id > prev.id, "WindowId counter must strictly increase");
2295 assert!(ids.insert(next), "WindowId::new() handed out a duplicate");
2296 prev = next;
2297 }
2298 assert_ne!(WindowId::default(), WindowId::default());
2300 }
2301
2302 #[test]
2303 fn icon_key_new_is_unique_and_monotonic() {
2304 let mut keys = BTreeSet::new();
2305 let mut prev = IconKey::new();
2306 assert!(keys.insert(prev));
2307 for _ in 0..1000 {
2308 let next = IconKey::new();
2309 assert!(
2310 next.icon_id > prev.icon_id,
2311 "IconKey counter must strictly increase"
2312 );
2313 assert!(keys.insert(next), "IconKey::new() handed out a duplicate");
2314 prev = next;
2315 }
2316 assert_ne!(IconKey::default(), IconKey::default());
2317 }
2318
2319 #[test]
2324 fn renderer_options_new_preserves_every_combination() {
2325 let vsyncs = [Vsync::Enabled, Vsync::Disabled, Vsync::DontCare];
2326 let srgbs = [Srgb::Enabled, Srgb::Disabled, Srgb::DontCare];
2327 let accels = [
2328 HwAcceleration::Enabled,
2329 HwAcceleration::Disabled,
2330 HwAcceleration::DontCare,
2331 ];
2332 for v in vsyncs {
2333 for s in srgbs {
2334 for a in accels {
2335 let o = RendererOptions::new(v, s, a);
2336 assert_eq!(o.vsync, v);
2337 assert_eq!(o.srgb, s);
2338 assert_eq!(o.hw_accel, a);
2339 assert_eq!(o, RendererOptions::new(v, s, a));
2341 }
2342 }
2343 }
2344 }
2345
2346 #[test]
2347 fn renderer_options_default_does_not_force_cpu_rendering() {
2348 let d = RendererOptions::default();
2351 assert_eq!(d.hw_accel, HwAcceleration::DontCare);
2352 assert!(!d.hw_accel.is_enabled());
2353 assert!(d.vsync.is_enabled());
2354 assert!(!d.srgb.is_enabled());
2355 }
2356
2357 #[test]
2358 fn tri_state_is_enabled_only_for_enabled_variant() {
2359 assert!(Vsync::Enabled.is_enabled());
2360 assert!(!Vsync::Disabled.is_enabled());
2361 assert!(!Vsync::DontCare.is_enabled());
2362
2363 assert!(Srgb::Enabled.is_enabled());
2364 assert!(!Srgb::Disabled.is_enabled());
2365 assert!(!Srgb::DontCare.is_enabled());
2366
2367 assert!(HwAcceleration::Enabled.is_enabled());
2368 assert!(!HwAcceleration::Disabled.is_enabled());
2369 assert!(!HwAcceleration::DontCare.is_enabled());
2370 }
2371
2372 #[test]
2377 fn keyboard_state_default_has_no_key_down() {
2378 let k = KeyboardState::default();
2379 assert!(!k.shift_down());
2380 assert!(!k.ctrl_down());
2381 assert!(!k.alt_down());
2382 assert!(!k.super_down());
2383 assert!(!k.primary_down());
2384 for v in 0..=LAST_VK {
2386 let vk = VirtualKeyCode::from_u32(v).expect("discriminant in range");
2387 assert!(!k.is_key_down(vk));
2388 }
2389 }
2390
2391 #[test]
2392 fn keyboard_state_modifiers_accept_either_side() {
2393 for (left, right, probe) in [
2394 (
2395 VirtualKeyCode::LShift,
2396 VirtualKeyCode::RShift,
2397 KeyboardState::shift_down as fn(&KeyboardState) -> bool,
2398 ),
2399 (
2400 VirtualKeyCode::LControl,
2401 VirtualKeyCode::RControl,
2402 KeyboardState::ctrl_down as fn(&KeyboardState) -> bool,
2403 ),
2404 (
2405 VirtualKeyCode::LAlt,
2406 VirtualKeyCode::RAlt,
2407 KeyboardState::alt_down as fn(&KeyboardState) -> bool,
2408 ),
2409 (
2410 VirtualKeyCode::LWin,
2411 VirtualKeyCode::RWin,
2412 KeyboardState::super_down as fn(&KeyboardState) -> bool,
2413 ),
2414 ] {
2415 assert!(probe(&keyboard_with(&[left])), "left variant must register");
2416 assert!(
2417 probe(&keyboard_with(&[right])),
2418 "right variant must register"
2419 );
2420 assert!(probe(&keyboard_with(&[left, right])));
2421 assert!(!probe(&keyboard_with(&[VirtualKeyCode::A])));
2423 }
2424 }
2425
2426 #[test]
2427 fn keyboard_state_primary_down_follows_platform() {
2428 let ctrl = keyboard_with(&[VirtualKeyCode::LControl]);
2429 let cmd = keyboard_with(&[VirtualKeyCode::LWin]);
2430 if cfg!(target_os = "macos") {
2431 assert!(cmd.primary_down(), "Cmd (super) is PRIMARY on macOS");
2432 assert!(!ctrl.primary_down());
2433 } else {
2434 assert!(ctrl.primary_down(), "Ctrl is PRIMARY off macOS");
2435 assert!(!cmd.primary_down());
2436 }
2437 for k in [&ctrl, &cmd, &KeyboardState::default()] {
2439 assert_eq!(
2440 k.primary_down(),
2441 if cfg!(target_os = "macos") {
2442 k.super_down()
2443 } else {
2444 k.ctrl_down()
2445 }
2446 );
2447 }
2448 }
2449
2450 #[test]
2451 fn is_key_down_handles_duplicates_and_large_state() {
2452 let dup = keyboard_with(&[VirtualKeyCode::S; 512]);
2454 assert!(dup.is_key_down(VirtualKeyCode::S));
2455 assert!(!dup.is_key_down(VirtualKeyCode::A));
2456
2457 let all: alloc::vec::Vec<VirtualKeyCode> = (0..=LAST_VK)
2459 .map(|v| VirtualKeyCode::from_u32(v).expect("discriminant in range"))
2460 .collect();
2461 let everything = keyboard_with(&all);
2462 for vk in &all {
2463 assert!(everything.is_key_down(*vk));
2464 }
2465 assert!(everything.shift_down() && everything.ctrl_down());
2466 assert!(everything.alt_down() && everything.super_down());
2467 }
2468
2469 #[test]
2474 fn empty_chord_matches_trivially() {
2475 assert!(KeyboardState::default().matches_accelerator(&[]));
2477 assert!(keyboard_with(&[VirtualKeyCode::A]).matches_accelerator(&[]));
2478 }
2479
2480 #[test]
2481 fn matches_accelerator_requires_every_entry() {
2482 let state = keyboard_with(&[
2483 VirtualKeyCode::LControl,
2484 VirtualKeyCode::LShift,
2485 VirtualKeyCode::S,
2486 ]);
2487 assert!(state.matches_accelerator(&[
2488 AcceleratorKey::Ctrl,
2489 AcceleratorKey::Shift,
2490 AcceleratorKey::Key(VirtualKeyCode::S),
2491 ]));
2492 assert!(!state.matches_accelerator(&[
2494 AcceleratorKey::Ctrl,
2495 AcceleratorKey::Alt,
2496 AcceleratorKey::Key(VirtualKeyCode::S),
2497 ]));
2498 assert!(!state.matches_accelerator(&[
2500 AcceleratorKey::Ctrl,
2501 AcceleratorKey::Key(VirtualKeyCode::Q),
2502 ]));
2503 assert!(state.matches_accelerator(&[
2505 AcceleratorKey::Key(VirtualKeyCode::S),
2506 AcceleratorKey::Shift,
2507 AcceleratorKey::Ctrl,
2508 ]));
2509 }
2510
2511 #[test]
2512 fn matches_accelerator_survives_huge_chord() {
2513 let state = keyboard_with(&[VirtualKeyCode::LShift]);
2515 let long_ok = vec![AcceleratorKey::Shift; 10_000];
2516 assert!(state.matches_accelerator(&long_ok));
2517
2518 let mut long_bad = vec![AcceleratorKey::Shift; 10_000];
2521 long_bad.push(AcceleratorKey::Ctrl);
2522 assert!(!state.matches_accelerator(&long_bad));
2523 }
2524
2525 #[test]
2526 fn accelerator_key_matches_each_variant() {
2527 let empty = KeyboardState::default();
2528 for a in [
2529 AcceleratorKey::Ctrl,
2530 AcceleratorKey::Alt,
2531 AcceleratorKey::Shift,
2532 AcceleratorKey::Key(VirtualKeyCode::A),
2533 ] {
2534 assert!(!a.matches(&empty), "nothing matches an empty keyboard state");
2535 }
2536 assert!(AcceleratorKey::Ctrl.matches(&keyboard_with(&[VirtualKeyCode::RControl])));
2537 assert!(AcceleratorKey::Alt.matches(&keyboard_with(&[VirtualKeyCode::RAlt])));
2538 assert!(AcceleratorKey::Shift.matches(&keyboard_with(&[VirtualKeyCode::RShift])));
2539 assert!(
2540 AcceleratorKey::Key(VirtualKeyCode::F24).matches(&keyboard_with(&[VirtualKeyCode::F24]))
2541 );
2542 assert!(!AcceleratorKey::Ctrl.matches(&keyboard_with(&[VirtualKeyCode::C])));
2544 }
2545
2546 #[test]
2551 fn mouse_state_matches_context_button() {
2552 let base = MouseState::default();
2553 assert!(!base.matches(&ContextMenuMouseButton::Left));
2554 assert!(!base.matches(&ContextMenuMouseButton::Right));
2555 assert!(!base.matches(&ContextMenuMouseButton::Middle));
2556
2557 for (ctx, ms) in [
2558 (
2559 ContextMenuMouseButton::Left,
2560 MouseState {
2561 left_down: true,
2562 ..MouseState::default()
2563 },
2564 ),
2565 (
2566 ContextMenuMouseButton::Right,
2567 MouseState {
2568 right_down: true,
2569 ..MouseState::default()
2570 },
2571 ),
2572 (
2573 ContextMenuMouseButton::Middle,
2574 MouseState {
2575 middle_down: true,
2576 ..MouseState::default()
2577 },
2578 ),
2579 ] {
2580 assert!(ms.matches(&ctx), "{ctx:?} must match its own button");
2581 let others = [
2583 ContextMenuMouseButton::Left,
2584 ContextMenuMouseButton::Right,
2585 ContextMenuMouseButton::Middle,
2586 ];
2587 for other in others {
2588 assert_eq!(ms.matches(&other), other == ctx);
2589 }
2590 }
2591 }
2592
2593 #[test]
2594 fn mouse_down_and_button_state_agree_for_all_8_combinations() {
2595 for bits in 0u8..8 {
2596 let (l, r, m) = (bits & 1 != 0, bits & 2 != 0, bits & 4 != 0);
2597 let ms = MouseState {
2598 left_down: l,
2599 right_down: r,
2600 middle_down: m,
2601 ..MouseState::default()
2602 };
2603 assert_eq!(ms.mouse_down(), l || r || m);
2604
2605 let snapshot = ms.button_state();
2606 assert_eq!(snapshot.left_down, l);
2607 assert_eq!(snapshot.right_down, r);
2608 assert_eq!(snapshot.middle_down, m);
2609 assert_eq!(snapshot.any_down(), ms.mouse_down());
2611 assert_eq!(crate::events::MouseButtonState::from(&ms), snapshot);
2612 }
2613 assert!(!MouseState::default().mouse_down());
2615 assert!(!MouseState::default().button_state().any_down());
2616 }
2617
2618 #[test]
2623 fn process_system_scroll_zero_and_negative_zero_consume_nothing() {
2624 let r = process_system_scroll(LogicalPosition::zero(), false);
2625 assert_eq!(r.scrolled_nodes, 0);
2626 assert_eq!(r.remaining_delta, LogicalPosition::zero());
2627 assert!(!r.hit_scrollbar);
2628
2629 let neg_zero = process_system_scroll(LogicalPosition::new(-0.0, -0.0), true);
2631 assert_eq!(neg_zero.scrolled_nodes, 0);
2632 assert!(neg_zero.hit_scrollbar, "hit_scrollbar is echoed verbatim");
2633 }
2634
2635 #[test]
2636 fn process_system_scroll_counts_any_nonzero_axis() {
2637 for delta in [
2638 LogicalPosition::new(1.0, 0.0),
2639 LogicalPosition::new(0.0, -1.0),
2640 LogicalPosition::new(-3.5, 7.25),
2641 LogicalPosition::new(f32::MIN, 0.0),
2642 LogicalPosition::new(0.0, f32::MAX),
2643 LogicalPosition::new(f32::INFINITY, f32::NEG_INFINITY),
2644 LogicalPosition::new(f32::MIN_POSITIVE, 0.0),
2645 ] {
2646 let r = process_system_scroll(delta, false);
2647 assert_eq!(r.scrolled_nodes, 1, "{delta:?} must count as consumed");
2648 assert_eq!(r.remaining_delta, LogicalPosition::zero());
2650 }
2651 }
2652
2653 #[test]
2654 fn process_system_scroll_does_not_panic_on_nan() {
2655 for delta in [
2658 LogicalPosition::new(f32::NAN, 0.0),
2659 LogicalPosition::new(0.0, f32::NAN),
2660 LogicalPosition::new(f32::NAN, f32::NAN),
2661 ] {
2662 let r = process_system_scroll(delta, true);
2663 assert!(r.scrolled_nodes <= 1);
2664 assert_eq!(r.remaining_delta, LogicalPosition::zero());
2665 assert!(r.hit_scrollbar);
2666 }
2667 }
2668
2669 #[test]
2670 fn scroll_result_default_is_inert() {
2671 let d = ScrollResult::default();
2672 assert_eq!(d.scrolled_nodes, 0);
2673 assert_eq!(d.remaining_delta, LogicalPosition::zero());
2674 assert!(!d.hit_scrollbar);
2675 }
2676
2677 #[test]
2682 fn cursor_position_get_position_only_inside_window() {
2683 let p = LogicalPosition::new(12.0, 34.0);
2684 assert_eq!(CursorPosition::InWindow(p).get_position(), Some(p));
2685 assert_eq!(CursorPosition::OutOfWindow(p).get_position(), None);
2686 assert_eq!(CursorPosition::Uninitialized.get_position(), None);
2687 assert_eq!(CursorPosition::default(), CursorPosition::Uninitialized);
2689 assert_eq!(CursorPosition::default().get_position(), None);
2690 }
2691
2692 #[test]
2693 fn cursor_position_is_inside_window_agrees_with_get_position() {
2694 for c in [
2695 CursorPosition::Uninitialized,
2696 CursorPosition::InWindow(LogicalPosition::zero()),
2697 CursorPosition::OutOfWindow(LogicalPosition::zero()),
2698 CursorPosition::InWindow(LogicalPosition::new(f32::MIN, f32::MAX)),
2699 CursorPosition::OutOfWindow(LogicalPosition::new(f32::INFINITY, f32::NAN)),
2700 ] {
2701 assert_eq!(c.is_inside_window(), c.get_position().is_some());
2702 }
2703 let nan_pos = CursorPosition::InWindow(LogicalPosition::new(f32::NAN, f32::INFINITY));
2705 assert!(nan_pos.is_inside_window());
2706 let got = nan_pos.get_position().expect("InWindow always yields a position");
2707 assert!(got.x.is_nan());
2708 assert!(got.y.is_infinite());
2709 }
2710
2711 #[test]
2716 fn monitor_id_constructors_preserve_fields_at_extremes() {
2717 assert_eq!(MonitorId::PRIMARY, MonitorId { index: 0, hash: 0 });
2718 assert_eq!(MonitorId::new(0), MonitorId::PRIMARY);
2719
2720 for index in [0usize, 1, 42, usize::MAX] {
2721 let m = MonitorId::new(index);
2722 assert_eq!(m.index, index);
2723 assert_eq!(m.hash, 0, "new() documents hash == 0");
2724
2725 for hash in [0u64, 1, u64::MAX] {
2726 let m = MonitorId::from_index_and_hash(index, hash);
2727 assert_eq!(m.index, index);
2728 assert_eq!(m.hash, hash);
2729 }
2730 }
2731 assert_ne!(MonitorId::new(1), MonitorId::new(2));
2733 assert_ne!(
2734 MonitorId::from_index_and_hash(1, 7),
2735 MonitorId::from_index_and_hash(1, 8)
2736 );
2737 }
2738
2739 #[test]
2740 fn monitor_id_from_properties_is_stable_and_index_independent() {
2741 let pos = LayoutPoint::new(-1920, 0);
2742 let size = LayoutSize::new(2560, 1440);
2743
2744 let a = MonitorId::from_properties(0, "HDMI-1", pos, size);
2745 let b = MonitorId::from_properties(0, "HDMI-1", pos, size);
2746 assert_eq!(a, b, "hash must be stable across calls (persistable)");
2747
2748 let reindexed = MonitorId::from_properties(7, "HDMI-1", pos, size);
2750 assert_eq!(reindexed.hash, a.hash);
2751 assert_eq!(reindexed.index, 7);
2752 assert_ne!(reindexed, a, "index is still part of identity");
2753 }
2754
2755 #[test]
2756 fn monitor_id_from_properties_is_sensitive_to_each_property() {
2757 let pos = LayoutPoint::new(0, 0);
2758 let size = LayoutSize::new(1920, 1080);
2759 let base = MonitorId::from_properties(0, "DP-1", pos, size);
2760
2761 assert_ne!(
2763 base.hash,
2764 MonitorId::from_properties(0, "DP-2", pos, size).hash
2765 );
2766 assert_ne!(
2767 base.hash,
2768 MonitorId::from_properties(0, "DP-1", LayoutPoint::new(1, 0), size).hash
2769 );
2770 assert_ne!(
2771 base.hash,
2772 MonitorId::from_properties(0, "DP-1", LayoutPoint::new(0, 1), size).hash
2773 );
2774 assert_ne!(
2775 base.hash,
2776 MonitorId::from_properties(0, "DP-1", pos, LayoutSize::new(1921, 1080)).hash
2777 );
2778 assert_ne!(
2779 base.hash,
2780 MonitorId::from_properties(0, "DP-1", pos, LayoutSize::new(1920, 1081)).hash
2781 );
2782 assert_ne!(
2784 base.hash,
2785 MonitorId::from_properties(0, "DP-1", pos, LayoutSize::new(1080, 1920)).hash
2786 );
2787 }
2788
2789 #[test]
2790 fn monitor_id_from_properties_handles_hostile_inputs() {
2791 let size = LayoutSize::new(isize::MAX, isize::MIN);
2792 let pos = LayoutPoint::new(isize::MIN, isize::MAX);
2793
2794 for name in ["", " ", "\t\n", "\u{1F600}", "e\u{301}", "é", "a\0b"] {
2796 let m = MonitorId::from_properties(3, name, pos, size);
2797 assert_eq!(m.index, 3);
2798 assert_eq!(m, MonitorId::from_properties(3, name, pos, size));
2800 }
2801
2802 assert_ne!(
2804 MonitorId::from_properties(0, "e\u{301}", pos, size).hash,
2805 MonitorId::from_properties(0, "é", pos, size).hash
2806 );
2807
2808 let huge = "x".repeat(1_000_000);
2810 let h1 = MonitorId::from_properties(0, &huge, LayoutPoint::zero(), LayoutSize::zero());
2811 let h2 = MonitorId::from_properties(0, &huge, LayoutPoint::zero(), LayoutSize::zero());
2812 assert_eq!(h1, h2);
2813 assert_ne!(
2814 h1.hash,
2815 MonitorId::from_properties(0, "x", LayoutPoint::zero(), LayoutSize::zero()).hash
2816 );
2817 }
2818
2819 #[test]
2824 fn window_flags_type_predicates_are_mutually_exclusive() {
2825 for (ty, menu, tooltip, dialog) in [
2826 (WindowType::Normal, false, false, false),
2827 (WindowType::Menu, true, false, false),
2828 (WindowType::Tooltip, false, true, false),
2829 (WindowType::Dialog, false, false, true),
2830 ] {
2831 let f = WindowFlags {
2832 window_type: ty,
2833 ..WindowFlags::default()
2834 };
2835 assert_eq!(f.is_menu_window(), menu);
2836 assert_eq!(f.is_tooltip_window(), tooltip);
2837 assert_eq!(f.is_dialog_window(), dialog);
2838 let count = u8::from(f.is_menu_window())
2840 + u8::from(f.is_tooltip_window())
2841 + u8::from(f.is_dialog_window());
2842 assert!(count <= 1);
2843 }
2844 }
2845
2846 #[test]
2847 fn window_flags_bool_getters_mirror_their_fields() {
2848 let d = WindowFlags::default();
2850 assert!(d.window_has_focus());
2851 assert!(!d.is_close_requested());
2852 assert!(!d.has_csd());
2853 assert_eq!(
2854 d.use_native_menus(),
2855 cfg!(any(target_os = "windows", target_os = "macos"))
2856 );
2857 assert_eq!(
2858 d.use_native_context_menus(),
2859 cfg!(any(target_os = "windows", target_os = "macos"))
2860 );
2861
2862 for b in [false, true] {
2864 let f = WindowFlags {
2865 has_focus: b,
2866 close_requested: b,
2867 has_decorations: b,
2868 use_native_menus: b,
2869 use_native_context_menus: b,
2870 ..WindowFlags::default()
2871 };
2872 assert_eq!(f.window_has_focus(), b);
2873 assert_eq!(f.is_close_requested(), b);
2874 assert_eq!(f.has_csd(), b);
2875 assert_eq!(f.use_native_menus(), b);
2876 assert_eq!(f.use_native_context_menus(), b);
2877 }
2878 }
2879
2880 #[test]
2885 fn get_key_on_empty_vec_is_none() {
2886 let empty = StringPairVec::new();
2887 assert!(empty.get_key("").is_none());
2888 assert!(empty.get_key("anything").is_none());
2889
2890 let mut empty_mut = StringPairVec::new();
2891 assert!(empty_mut.get_key_mut("").is_none());
2892 assert!(empty_mut.get_key_mut("anything").is_none());
2893 }
2894
2895 #[test]
2896 fn get_key_valid_minimal_and_missing() {
2897 let v = StringPairVec::from_vec(vec![pair("WM_CLASS", "azul")]);
2898 assert_eq!(
2899 v.get_key("WM_CLASS").map(AzString::as_str),
2900 Some("azul"),
2901 "positive control"
2902 );
2903 assert!(v.get_key("wm_class").is_none(), "lookup is case-sensitive");
2904 assert!(v.get_key("WM_CLAS").is_none());
2905 assert!(v.get_key("WM_CLASS ").is_none(), "no trimming is performed");
2906 assert!(v.get_key(" WM_CLASS").is_none());
2907 assert!(v.get_key("WM_CLASS;garbage").is_none());
2908 }
2909
2910 #[test]
2911 fn get_key_handles_garbage_whitespace_and_boundary_numbers() {
2912 let v = StringPairVec::from_vec(vec![
2913 pair("", "empty-key"),
2914 pair(" ", "spaces"),
2915 pair("\t\n", "tabs"),
2916 pair("0", "zero"),
2917 pair("-0", "neg-zero"),
2918 pair("9223372036854775807", "i64-max"),
2919 pair("NaN", "nan"),
2920 pair("inf", "inf"),
2921 ]);
2922
2923 assert_eq!(v.get_key("").map(AzString::as_str), Some("empty-key"));
2925 assert_eq!(v.get_key(" ").map(AzString::as_str), Some("spaces"));
2926 assert_eq!(v.get_key("\t\n").map(AzString::as_str), Some("tabs"));
2927
2928 assert_eq!(v.get_key("0").map(AzString::as_str), Some("zero"));
2930 assert_eq!(v.get_key("-0").map(AzString::as_str), Some("neg-zero"));
2931 assert_eq!(
2932 v.get_key("9223372036854775807").map(AzString::as_str),
2933 Some("i64-max")
2934 );
2935 assert_eq!(v.get_key("NaN").map(AzString::as_str), Some("nan"));
2936 assert_eq!(v.get_key("inf").map(AzString::as_str), Some("inf"));
2937 assert!(v.get_key("nan").is_none());
2938
2939 assert!(v.get_key("\u{0}\u{1}\u{7f}\\x\"';--").is_none());
2941 assert!(v.get_key(&"[".repeat(10_000)).is_none());
2942 assert!(v.get_key(&"{\"a\":".repeat(10_000)).is_none());
2943 }
2944
2945 #[test]
2946 fn get_key_handles_unicode_without_panicking() {
2947 let v = StringPairVec::from_vec(vec![
2948 pair("\u{1F600}", "grin"),
2949 pair("é", "precomposed"),
2950 pair("日本語", "jp"),
2951 ]);
2952 assert_eq!(v.get_key("\u{1F600}").map(AzString::as_str), Some("grin"));
2953 assert_eq!(v.get_key("日本語").map(AzString::as_str), Some("jp"));
2954 assert_eq!(v.get_key("é").map(AzString::as_str), Some("precomposed"));
2956 assert!(v.get_key("e\u{301}").is_none());
2957 assert!(v.get_key("日本").is_none());
2959 }
2960
2961 #[test]
2962 fn get_key_handles_extremely_long_input() {
2963 let huge = "k".repeat(1_000_000);
2964 let mut v = StringPairVec::from_vec(vec![pair("short", "1")]);
2965
2966 assert!(v.get_key(&huge).is_none());
2968
2969 v.push(AzStringPair {
2971 key: huge.as_str().into(),
2972 value: "big".into(),
2973 });
2974 assert_eq!(v.get_key(&huge).map(AzString::as_str), Some("big"));
2975 assert!(v.get_key(&"k".repeat(999_999)).is_none());
2977 assert!(v.get_key(&"k".repeat(1_000_001)).is_none());
2978 }
2979
2980 #[test]
2981 fn get_key_returns_first_of_duplicate_keys() {
2982 let v = StringPairVec::from_vec(vec![
2983 pair("dup", "first"),
2984 pair("dup", "second"),
2985 pair("dup", "third"),
2986 ]);
2987 assert_eq!(v.get_key("dup").map(AzString::as_str), Some("first"));
2988 }
2989
2990 #[test]
2991 fn get_key_mut_mutates_in_place() {
2992 let mut v = StringPairVec::from_vec(vec![pair("a", "1"), pair("b", "2")]);
2993 {
2994 let entry = v.get_key_mut("b").expect("b is present");
2995 entry.value = "changed".into();
2996 }
2997 assert_eq!(v.get_key("b").map(AzString::as_str), Some("changed"));
2998 assert_eq!(v.get_key("a").map(AzString::as_str), Some("1"));
2999 assert!(v.get_key_mut("missing").is_none());
3000 assert_eq!(v.len(), 2, "get_key_mut must not add entries");
3001
3002 {
3004 let entry = v.get_key_mut("a").expect("a is present");
3005 entry.key = "z".into();
3006 }
3007 assert!(v.get_key("a").is_none());
3008 assert_eq!(v.get_key("z").map(AzString::as_str), Some("1"));
3009 }
3010
3011 #[test]
3012 fn insert_kv_updates_existing_and_appends_new() {
3013 let mut v = StringPairVec::new();
3014 v.insert_kv("k", "v1");
3015 assert_eq!(v.len(), 1);
3016 assert_eq!(v.get_key("k").map(AzString::as_str), Some("v1"));
3017
3018 v.insert_kv("k", "v2");
3020 assert_eq!(v.len(), 1, "insert_kv must not duplicate an existing key");
3021 assert_eq!(v.get_key("k").map(AzString::as_str), Some("v2"));
3022
3023 v.insert_kv("other", "x");
3025 assert_eq!(v.len(), 2);
3026 assert_eq!(v.get_key("k").map(AzString::as_str), Some("v2"));
3027 assert_eq!(v.get_key("other").map(AzString::as_str), Some("x"));
3028
3029 for i in 0..100 {
3031 v.insert_kv(String::from("k"), format!("gen{i}"));
3032 }
3033 assert_eq!(v.len(), 2);
3034 assert_eq!(v.get_key("k").map(AzString::as_str), Some("gen99"));
3035 }
3036
3037 #[test]
3038 fn insert_kv_accepts_hostile_keys_and_values() {
3039 let mut v = StringPairVec::new();
3040 v.insert_kv("", "");
3041 assert_eq!(v.len(), 1);
3042 assert_eq!(v.get_key("").map(AzString::as_str), Some(""));
3043
3044 v.insert_kv("\u{1F600}", "😀");
3045 assert_eq!(v.get_key("\u{1F600}").map(AzString::as_str), Some("😀"));
3046
3047 v.insert_kv(" ", "\t\n");
3048 assert_eq!(v.get_key(" ").map(AzString::as_str), Some("\t\n"));
3049
3050 let huge_key = "K".repeat(100_000);
3052 let huge_val = "V".repeat(100_000);
3053 v.insert_kv(huge_key.clone(), huge_val.clone());
3054 let before = v.len();
3055 assert_eq!(
3056 v.get_key(&huge_key).map(AzString::as_str),
3057 Some(huge_val.as_str())
3058 );
3059 v.insert_kv(huge_key.clone(), String::from("small"));
3060 assert_eq!(v.len(), before, "long key must hit the update path");
3061 assert_eq!(v.get_key(&huge_key).map(AzString::as_str), Some("small"));
3062 }
3063
3064 #[test]
3065 fn insert_kv_only_updates_the_first_of_pre_existing_duplicates() {
3066 let mut v = StringPairVec::from_vec(vec![pair("dup", "first"), pair("dup", "second")]);
3069 v.insert_kv("dup", "updated");
3070 assert_eq!(v.len(), 2, "no new entry is appended");
3071 assert_eq!(v.get_key("dup").map(AzString::as_str), Some("updated"));
3072 assert_eq!(
3073 v.get(1).expect("second entry still present").value.as_str(),
3074 "second",
3075 "the shadowed duplicate is left untouched"
3076 );
3077 }
3078
3079 #[test]
3084 fn window_size_get_logical_size_is_the_identity() {
3085 for dims in [
3086 LogicalSize::zero(),
3087 LogicalSize::new(640.0, 480.0),
3088 LogicalSize::new(-1.0, -2.0),
3089 LogicalSize::new(f32::MAX, f32::MIN),
3090 LogicalSize::new(f32::INFINITY, f32::MIN_POSITIVE),
3091 ] {
3092 let ws = WindowSize {
3093 dimensions: dims,
3094 ..WindowSize::default()
3095 };
3096 assert_eq!(ws.get_logical_size(), dims);
3097 }
3098 let d = WindowSize::default();
3100 assert_eq!(d.get_logical_size(), LogicalSize::new(640.0, 480.0));
3101 assert_eq!(d.dpi, 96);
3102 }
3103
3104 #[test]
3105 fn window_size_get_layout_size_rounds_half_away_from_zero() {
3106 for (w, h, ew, eh) in [
3107 (0.0f32, 0.0f32, 0isize, 0isize),
3108 (640.0, 480.0, 640, 480),
3109 (640.4, 480.4, 640, 480),
3110 (640.6, 480.6, 641, 481),
3111 (640.5, 639.5, 641, 640),
3112 (-0.5, -1.5, -1, -2),
3113 ] {
3114 let ws = WindowSize {
3115 dimensions: LogicalSize::new(w, h),
3116 ..WindowSize::default()
3117 };
3118 assert_eq!(ws.get_layout_size(), LayoutSize::new(ew, eh), "{w}x{h}");
3119 }
3120 }
3121
3122 #[test]
3123 fn window_size_get_layout_size_saturates_on_non_finite() {
3124 let nan = WindowSize {
3126 dimensions: LogicalSize::new(f32::NAN, f32::NAN),
3127 ..WindowSize::default()
3128 };
3129 assert_eq!(nan.get_layout_size(), LayoutSize::new(0, 0));
3130
3131 let inf = WindowSize {
3132 dimensions: LogicalSize::new(f32::INFINITY, f32::NEG_INFINITY),
3133 ..WindowSize::default()
3134 };
3135 assert_eq!(
3136 inf.get_layout_size(),
3137 LayoutSize::new(isize::MAX, isize::MIN)
3138 );
3139
3140 let max = WindowSize {
3141 dimensions: LogicalSize::new(f32::MAX, f32::MIN),
3142 ..WindowSize::default()
3143 };
3144 let ls = max.get_layout_size();
3145 assert!(ls.width > 0 && ls.height < 0, "sign is preserved: {ls:?}");
3146 }
3147
3148 #[test]
3149 fn window_size_get_physical_size_saturates_instead_of_wrapping() {
3150 let neg = WindowSize {
3152 dimensions: LogicalSize::new(-100.0, -0.4),
3153 ..WindowSize::default()
3154 };
3155 assert_eq!(neg.get_physical_size(), PhysicalSize::new(0, 0));
3156
3157 let nan = WindowSize {
3159 dimensions: LogicalSize::new(f32::NAN, f32::INFINITY),
3160 ..WindowSize::default()
3161 };
3162 assert_eq!(nan.get_physical_size(), PhysicalSize::new(0, u32::MAX));
3163
3164 let huge = WindowSize {
3166 dimensions: LogicalSize::new(f32::MAX, f32::MAX),
3167 dpi: 384,
3168 ..WindowSize::default()
3169 };
3170 assert_eq!(
3171 huge.get_physical_size(),
3172 PhysicalSize::new(u32::MAX, u32::MAX)
3173 );
3174
3175 let normal = WindowSize::default();
3177 assert_eq!(normal.get_physical_size(), PhysicalSize::new(640, 480));
3178 let retina = WindowSize {
3179 dpi: 192,
3180 ..WindowSize::default()
3181 };
3182 assert_eq!(retina.get_physical_size(), PhysicalSize::new(1280, 960));
3183 }
3184
3185 #[test]
3186 fn window_size_get_hidpi_factor_is_never_zero_or_negative() {
3187 for dpi in [
3189 0u32,
3190 1,
3191 47,
3192 48,
3193 95,
3194 96,
3195 97,
3196 120,
3197 144,
3198 192,
3199 384,
3200 u32::from(u16::MAX),
3201 u32::MAX,
3202 ] {
3203 let ws = WindowSize {
3204 dpi,
3205 ..WindowSize::default()
3206 };
3207 let f = ws.get_hidpi_factor().inner.get();
3208 assert!(
3209 f.is_finite() && f > 0.0,
3210 "dpi {dpi} produced a non-positive / non-finite scale factor: {f}"
3211 );
3212 }
3213
3214 for (dpi, expected) in [(0u32, 1.0f32), (96, 1.0), (144, 1.5), (192, 2.0), (384, 4.0)] {
3216 let ws = WindowSize {
3217 dpi,
3218 ..WindowSize::default()
3219 };
3220 assert_eq!(ws.get_hidpi_factor().inner.get(), expected, "dpi {dpi}");
3221 }
3222
3223 let odd = WindowSize {
3225 dpi: 100,
3226 ..WindowSize::default()
3227 };
3228 let f = odd.get_hidpi_factor().inner.get();
3229 assert!((f - 100.0 / 96.0).abs() < 0.002, "dpi 100 -> {f}");
3230 }
3231
3232 #[test]
3237 fn virtual_keycode_from_u32_roundtrips_every_discriminant() {
3238 for v in 0..=LAST_VK {
3239 let vk = VirtualKeyCode::from_u32(v)
3240 .unwrap_or_else(|| panic!("discriminant {v} is missing from the from_u32 table"));
3241 assert_eq!(vk as u32, v, "from_u32({v}) does not round-trip");
3242 }
3243 assert_eq!(VirtualKeyCode::Key1 as u32, 0);
3245 assert_eq!(VirtualKeyCode::Cut as u32, LAST_VK);
3246 }
3247
3248 #[test]
3249 fn virtual_keycode_from_u32_rejects_out_of_range() {
3250 for v in [
3251 LAST_VK + 1,
3252 LAST_VK + 2,
3253 255,
3254 256,
3255 1024,
3256 i32::MAX as u32,
3257 u32::MAX - 1,
3258 u32::MAX,
3259 ] {
3260 assert_eq!(
3261 VirtualKeyCode::from_u32(v),
3262 None,
3263 "{v} must not decode to a keycode"
3264 );
3265 }
3266 }
3267
3268 #[test]
3269 fn virtual_keycode_get_lowercase_never_panics_and_maps_letters_and_digits() {
3270 for v in 0..=LAST_VK {
3272 let vk = VirtualKeyCode::from_u32(v).expect("discriminant in range");
3273 if let Some(c) = vk.get_lowercase() {
3274 assert!(c.is_ascii(), "{vk:?} produced a non-ASCII char {c:?}");
3275 assert!(!c.is_ascii_uppercase(), "{vk:?} must yield lowercase");
3276 }
3277 }
3278
3279 for (i, expected) in ('a'..='z').enumerate() {
3281 let vk = VirtualKeyCode::from_u32(10 + i as u32).expect("letter range");
3282 assert_eq!(vk.get_lowercase(), Some(expected));
3283 }
3284
3285 for (top, pad, c) in [
3287 (VirtualKeyCode::Key0, VirtualKeyCode::Numpad0, '0'),
3288 (VirtualKeyCode::Key1, VirtualKeyCode::Numpad1, '1'),
3289 (VirtualKeyCode::Key5, VirtualKeyCode::Numpad5, '5'),
3290 (VirtualKeyCode::Key9, VirtualKeyCode::Numpad9, '9'),
3291 ] {
3292 assert_eq!(top.get_lowercase(), Some(c));
3293 assert_eq!(pad.get_lowercase(), Some(c));
3294 }
3295
3296 assert_eq!(VirtualKeyCode::Minus.get_lowercase(), Some('-'));
3298 assert_eq!(VirtualKeyCode::Period.get_lowercase(), Some('.'));
3299 assert_eq!(VirtualKeyCode::Slash.get_lowercase(), Some('/'));
3300 assert_eq!(VirtualKeyCode::Caret.get_lowercase(), Some('^'));
3301
3302 for vk in [
3304 VirtualKeyCode::LShift,
3305 VirtualKeyCode::RControl,
3306 VirtualKeyCode::Escape,
3307 VirtualKeyCode::F12,
3308 VirtualKeyCode::Space,
3309 VirtualKeyCode::Return,
3310 VirtualKeyCode::Back,
3311 ] {
3312 assert_eq!(vk.get_lowercase(), None, "{vk:?}");
3313 }
3314 }
3315
3316 #[test]
3321 fn window_icon_get_key_returns_the_stored_key() {
3322 let small_key = IconKey::new();
3323 let large_key = IconKey::new();
3324
3325 let small = WindowIcon::Small(SmallWindowIconBytes {
3326 key: small_key,
3327 rgba_bytes: vec![0u8; 16 * 16 * 4].into(),
3328 });
3329 let large = WindowIcon::Large(LargeWindowIconBytes {
3330 key: large_key,
3331 rgba_bytes: vec![255u8; 32 * 32 * 4].into(),
3332 });
3333
3334 assert_eq!(small.get_key(), small_key);
3335 assert_eq!(large.get_key(), large_key);
3336
3337 let empty = WindowIcon::Small(SmallWindowIconBytes {
3339 key: small_key,
3340 rgba_bytes: vec![].into(),
3341 });
3342 assert_eq!(empty.get_key(), small_key);
3343 }
3344
3345 #[test]
3346 fn window_icon_identity_is_the_key_alone() {
3347 let key = IconKey::new();
3350 let a = WindowIcon::Small(SmallWindowIconBytes {
3351 key,
3352 rgba_bytes: vec![0u8; 4].into(),
3353 });
3354 let b = WindowIcon::Small(SmallWindowIconBytes {
3355 key,
3356 rgba_bytes: vec![7u8; 1024].into(),
3357 });
3358 let c = WindowIcon::Large(LargeWindowIconBytes {
3360 key,
3361 rgba_bytes: vec![9u8; 32 * 32 * 4].into(),
3362 });
3363
3364 assert_eq!(a, b);
3365 assert_eq!(a, c);
3366 assert_eq!(a.cmp(&c), Ordering::Equal);
3367 assert_eq!(a.partial_cmp(&b), Some(Ordering::Equal));
3368 assert_eq!(hash_of(&a), hash_of(&b));
3370 assert_eq!(hash_of(&a), hash_of(&c));
3371
3372 let older = WindowIcon::Small(SmallWindowIconBytes {
3374 key,
3375 rgba_bytes: vec![0u8; 4].into(),
3376 });
3377 let newer = WindowIcon::Small(SmallWindowIconBytes {
3378 key: IconKey::new(),
3379 rgba_bytes: vec![0u8; 4].into(),
3380 });
3381 assert_ne!(older, newer);
3382 assert_eq!(older.cmp(&newer), Ordering::Less);
3383 }
3384
3385 #[test]
3390 fn update_focus_warning_display_is_non_empty_for_every_variant() {
3391 let dom = format!("{}", UpdateFocusWarning::FocusInvalidDomId(DomId::ROOT_ID));
3392 assert!(dom.contains("invalid ID"), "{dom}");
3393 assert!(!dom.is_empty());
3394
3395 let node = format!(
3396 "{}",
3397 UpdateFocusWarning::FocusInvalidNodeId(NodeHierarchyItemId::NONE)
3398 );
3399 assert!(node.contains("invalid ID"), "{node}");
3400
3401 let huge = format!(
3403 "{}",
3404 UpdateFocusWarning::FocusInvalidNodeId(NodeHierarchyItemId::from_raw(usize::MAX))
3405 );
3406 assert!(!huge.is_empty());
3407
3408 let path = format!(
3409 "{}",
3410 UpdateFocusWarning::CouldNotFindFocusNode(CssPath::default())
3411 );
3412 assert!(
3413 path.starts_with("Could not find focus node for path:"),
3414 "{path}"
3415 );
3416 }
3417}