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
1565pub const CSS_BREAKPOINTS: &[f32] = &[320.0, 480.0, 640.0, 768.0, 1024.0, 1280.0, 1440.0, 1920.0];
1576
1577#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd)]
1578#[repr(C)]
1579pub struct WindowSize {
1580 pub dimensions: LogicalSize,
1583 pub dpi: u32,
1585 pub min_dimensions: OptionLogicalSize,
1587 pub max_dimensions: OptionLogicalSize,
1589}
1590
1591impl WindowSize {
1592 #[allow(clippy::cast_possible_truncation)] #[must_use] pub fn get_layout_size(&self) -> LayoutSize {
1594 LayoutSize::new(
1595 libm::roundf(self.dimensions.width) as isize,
1596 libm::roundf(self.dimensions.height) as isize,
1597 )
1598 }
1599
1600 #[must_use] pub const fn get_logical_size(&self) -> LogicalSize {
1602 self.dimensions
1603 }
1604
1605 #[must_use] pub fn get_physical_size(&self) -> PhysicalSize<u32> {
1606 self.dimensions
1607 .to_physical(self.get_hidpi_factor().inner.get())
1608 }
1609
1610 #[allow(clippy::cast_precision_loss)] #[must_use] pub fn get_hidpi_factor(&self) -> DpiScaleFactor {
1612 let dpi = if self.dpi == 0 { 96 } else { self.dpi };
1617 DpiScaleFactor {
1618 inner: FloatValue::new(dpi as f32 / 96.0),
1619 }
1620 }
1621}
1622
1623impl Default for WindowSize {
1624 fn default() -> Self {
1625 Self {
1626 dimensions: LogicalSize::new(640.0, 480.0),
1627 dpi: 96,
1628 min_dimensions: None.into(),
1629 max_dimensions: None.into(),
1630 }
1631 }
1632}
1633
1634#[repr(C)]
1635#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
1636pub enum RendererType {
1637 Hardware,
1639 Software,
1641}
1642
1643impl_option!(
1644 RendererType,
1645 OptionRendererType,
1646 [Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash]
1647);
1648
1649#[derive(Debug, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
1650pub enum UpdateFocusWarning {
1651 FocusInvalidDomId(DomId),
1652 FocusInvalidNodeId(NodeHierarchyItemId),
1653 CouldNotFindFocusNode(CssPath),
1654}
1655
1656impl ::core::fmt::Display for UpdateFocusWarning {
1657 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
1658 use self::UpdateFocusWarning::{FocusInvalidDomId, FocusInvalidNodeId, CouldNotFindFocusNode};
1659 match self {
1660 FocusInvalidDomId(dom_id) => write!(f, "Focusing on DOM with invalid ID: {dom_id:?}"),
1661 FocusInvalidNodeId(node_id) => {
1662 write!(f, "Focusing on node with invalid ID: {node_id}")
1663 }
1664 CouldNotFindFocusNode(css_path) => {
1665 write!(f, "Could not find focus node for path: {css_path}")
1666 }
1667 }
1668 }
1669}
1670
1671#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1673#[repr(C, u8)]
1674pub enum AcceleratorKey {
1675 Ctrl,
1676 Alt,
1677 Shift,
1678 Key(VirtualKeyCode),
1679}
1680
1681impl AcceleratorKey {
1682 #[must_use] pub fn matches(&self, keyboard_state: &KeyboardState) -> bool {
1686 use self::AcceleratorKey::{Ctrl, Alt, Shift, Key};
1687 match self {
1688 Ctrl => keyboard_state.ctrl_down(),
1689 Alt => keyboard_state.alt_down(),
1690 Shift => keyboard_state.shift_down(),
1691 Key(k) => keyboard_state.is_key_down(*k),
1692 }
1693 }
1694}
1695
1696#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1698#[repr(C)]
1699pub enum VirtualKeyCode {
1700 Key1,
1701 Key2,
1702 Key3,
1703 Key4,
1704 Key5,
1705 Key6,
1706 Key7,
1707 Key8,
1708 Key9,
1709 Key0,
1710 A,
1711 B,
1712 C,
1713 D,
1714 E,
1715 F,
1716 G,
1717 H,
1718 I,
1719 J,
1720 K,
1721 L,
1722 M,
1723 N,
1724 O,
1725 P,
1726 Q,
1727 R,
1728 S,
1729 T,
1730 U,
1731 V,
1732 W,
1733 X,
1734 Y,
1735 Z,
1736 Escape,
1737 F1,
1738 F2,
1739 F3,
1740 F4,
1741 F5,
1742 F6,
1743 F7,
1744 F8,
1745 F9,
1746 F10,
1747 F11,
1748 F12,
1749 F13,
1750 F14,
1751 F15,
1752 F16,
1753 F17,
1754 F18,
1755 F19,
1756 F20,
1757 F21,
1758 F22,
1759 F23,
1760 F24,
1761 Snapshot,
1762 Scroll,
1763 Pause,
1764 Insert,
1765 Home,
1766 Delete,
1767 End,
1768 PageDown,
1769 PageUp,
1770 Left,
1771 Up,
1772 Right,
1773 Down,
1774 Back,
1775 Return,
1776 Space,
1777 Compose,
1778 Caret,
1779 Numlock,
1780 Numpad0,
1781 Numpad1,
1782 Numpad2,
1783 Numpad3,
1784 Numpad4,
1785 Numpad5,
1786 Numpad6,
1787 Numpad7,
1788 Numpad8,
1789 Numpad9,
1790 NumpadAdd,
1791 NumpadDivide,
1792 NumpadDecimal,
1793 NumpadComma,
1794 NumpadEnter,
1795 NumpadEquals,
1796 NumpadMultiply,
1797 NumpadSubtract,
1798 AbntC1,
1799 AbntC2,
1800 Apostrophe,
1801 Apps,
1802 Asterisk,
1803 At,
1804 Ax,
1805 Backslash,
1806 Calculator,
1807 Capital,
1808 Colon,
1809 Comma,
1810 Convert,
1811 Equals,
1812 Grave,
1813 Kana,
1814 Kanji,
1815 LAlt,
1816 LBracket,
1817 LControl,
1818 LShift,
1819 LWin,
1820 Mail,
1821 MediaSelect,
1822 MediaStop,
1823 Minus,
1824 Mute,
1825 MyComputer,
1826 NavigateForward,
1827 NavigateBackward,
1828 NextTrack,
1829 NoConvert,
1830 OEM102,
1831 Period,
1832 PlayPause,
1833 Plus,
1834 Power,
1835 PrevTrack,
1836 RAlt,
1837 RBracket,
1838 RControl,
1839 RShift,
1840 RWin,
1841 Semicolon,
1842 Slash,
1843 Sleep,
1844 Stop,
1845 Sysrq,
1846 Tab,
1847 Underline,
1848 Unlabeled,
1849 VolumeDown,
1850 VolumeUp,
1851 Wake,
1852 WebBack,
1853 WebFavorites,
1854 WebForward,
1855 WebHome,
1856 WebRefresh,
1857 WebSearch,
1858 WebStop,
1859 Yen,
1860 Copy,
1861 Paste,
1862 Cut,
1863}
1864
1865impl VirtualKeyCode {
1866 #[must_use]
1874 #[allow(clippy::too_many_lines)] pub const fn from_u32(v: u32) -> Option<Self> {
1876 match v {
1877 0 => Some(Self::Key1),
1878 1 => Some(Self::Key2),
1879 2 => Some(Self::Key3),
1880 3 => Some(Self::Key4),
1881 4 => Some(Self::Key5),
1882 5 => Some(Self::Key6),
1883 6 => Some(Self::Key7),
1884 7 => Some(Self::Key8),
1885 8 => Some(Self::Key9),
1886 9 => Some(Self::Key0),
1887 10 => Some(Self::A),
1888 11 => Some(Self::B),
1889 12 => Some(Self::C),
1890 13 => Some(Self::D),
1891 14 => Some(Self::E),
1892 15 => Some(Self::F),
1893 16 => Some(Self::G),
1894 17 => Some(Self::H),
1895 18 => Some(Self::I),
1896 19 => Some(Self::J),
1897 20 => Some(Self::K),
1898 21 => Some(Self::L),
1899 22 => Some(Self::M),
1900 23 => Some(Self::N),
1901 24 => Some(Self::O),
1902 25 => Some(Self::P),
1903 26 => Some(Self::Q),
1904 27 => Some(Self::R),
1905 28 => Some(Self::S),
1906 29 => Some(Self::T),
1907 30 => Some(Self::U),
1908 31 => Some(Self::V),
1909 32 => Some(Self::W),
1910 33 => Some(Self::X),
1911 34 => Some(Self::Y),
1912 35 => Some(Self::Z),
1913 36 => Some(Self::Escape),
1914 37 => Some(Self::F1),
1915 38 => Some(Self::F2),
1916 39 => Some(Self::F3),
1917 40 => Some(Self::F4),
1918 41 => Some(Self::F5),
1919 42 => Some(Self::F6),
1920 43 => Some(Self::F7),
1921 44 => Some(Self::F8),
1922 45 => Some(Self::F9),
1923 46 => Some(Self::F10),
1924 47 => Some(Self::F11),
1925 48 => Some(Self::F12),
1926 49 => Some(Self::F13),
1927 50 => Some(Self::F14),
1928 51 => Some(Self::F15),
1929 52 => Some(Self::F16),
1930 53 => Some(Self::F17),
1931 54 => Some(Self::F18),
1932 55 => Some(Self::F19),
1933 56 => Some(Self::F20),
1934 57 => Some(Self::F21),
1935 58 => Some(Self::F22),
1936 59 => Some(Self::F23),
1937 60 => Some(Self::F24),
1938 61 => Some(Self::Snapshot),
1939 62 => Some(Self::Scroll),
1940 63 => Some(Self::Pause),
1941 64 => Some(Self::Insert),
1942 65 => Some(Self::Home),
1943 66 => Some(Self::Delete),
1944 67 => Some(Self::End),
1945 68 => Some(Self::PageDown),
1946 69 => Some(Self::PageUp),
1947 70 => Some(Self::Left),
1948 71 => Some(Self::Up),
1949 72 => Some(Self::Right),
1950 73 => Some(Self::Down),
1951 74 => Some(Self::Back),
1952 75 => Some(Self::Return),
1953 76 => Some(Self::Space),
1954 77 => Some(Self::Compose),
1955 78 => Some(Self::Caret),
1956 79 => Some(Self::Numlock),
1957 80 => Some(Self::Numpad0),
1958 81 => Some(Self::Numpad1),
1959 82 => Some(Self::Numpad2),
1960 83 => Some(Self::Numpad3),
1961 84 => Some(Self::Numpad4),
1962 85 => Some(Self::Numpad5),
1963 86 => Some(Self::Numpad6),
1964 87 => Some(Self::Numpad7),
1965 88 => Some(Self::Numpad8),
1966 89 => Some(Self::Numpad9),
1967 90 => Some(Self::NumpadAdd),
1968 91 => Some(Self::NumpadDivide),
1969 92 => Some(Self::NumpadDecimal),
1970 93 => Some(Self::NumpadComma),
1971 94 => Some(Self::NumpadEnter),
1972 95 => Some(Self::NumpadEquals),
1973 96 => Some(Self::NumpadMultiply),
1974 97 => Some(Self::NumpadSubtract),
1975 98 => Some(Self::AbntC1),
1976 99 => Some(Self::AbntC2),
1977 100 => Some(Self::Apostrophe),
1978 101 => Some(Self::Apps),
1979 102 => Some(Self::Asterisk),
1980 103 => Some(Self::At),
1981 104 => Some(Self::Ax),
1982 105 => Some(Self::Backslash),
1983 106 => Some(Self::Calculator),
1984 107 => Some(Self::Capital),
1985 108 => Some(Self::Colon),
1986 109 => Some(Self::Comma),
1987 110 => Some(Self::Convert),
1988 111 => Some(Self::Equals),
1989 112 => Some(Self::Grave),
1990 113 => Some(Self::Kana),
1991 114 => Some(Self::Kanji),
1992 115 => Some(Self::LAlt),
1993 116 => Some(Self::LBracket),
1994 117 => Some(Self::LControl),
1995 118 => Some(Self::LShift),
1996 119 => Some(Self::LWin),
1997 120 => Some(Self::Mail),
1998 121 => Some(Self::MediaSelect),
1999 122 => Some(Self::MediaStop),
2000 123 => Some(Self::Minus),
2001 124 => Some(Self::Mute),
2002 125 => Some(Self::MyComputer),
2003 126 => Some(Self::NavigateForward),
2004 127 => Some(Self::NavigateBackward),
2005 128 => Some(Self::NextTrack),
2006 129 => Some(Self::NoConvert),
2007 130 => Some(Self::OEM102),
2008 131 => Some(Self::Period),
2009 132 => Some(Self::PlayPause),
2010 133 => Some(Self::Plus),
2011 134 => Some(Self::Power),
2012 135 => Some(Self::PrevTrack),
2013 136 => Some(Self::RAlt),
2014 137 => Some(Self::RBracket),
2015 138 => Some(Self::RControl),
2016 139 => Some(Self::RShift),
2017 140 => Some(Self::RWin),
2018 141 => Some(Self::Semicolon),
2019 142 => Some(Self::Slash),
2020 143 => Some(Self::Sleep),
2021 144 => Some(Self::Stop),
2022 145 => Some(Self::Sysrq),
2023 146 => Some(Self::Tab),
2024 147 => Some(Self::Underline),
2025 148 => Some(Self::Unlabeled),
2026 149 => Some(Self::VolumeDown),
2027 150 => Some(Self::VolumeUp),
2028 151 => Some(Self::Wake),
2029 152 => Some(Self::WebBack),
2030 153 => Some(Self::WebFavorites),
2031 154 => Some(Self::WebForward),
2032 155 => Some(Self::WebHome),
2033 156 => Some(Self::WebRefresh),
2034 157 => Some(Self::WebSearch),
2035 158 => Some(Self::WebStop),
2036 159 => Some(Self::Yen),
2037 160 => Some(Self::Copy),
2038 161 => Some(Self::Paste),
2039 162 => Some(Self::Cut),
2040 _ => None,
2041 }
2042 }
2043
2044 #[must_use] pub const fn get_lowercase(&self) -> Option<char> {
2045 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};
2046 match self {
2047 A => Some('a'),
2048 B => Some('b'),
2049 C => Some('c'),
2050 D => Some('d'),
2051 E => Some('e'),
2052 F => Some('f'),
2053 G => Some('g'),
2054 H => Some('h'),
2055 I => Some('i'),
2056 J => Some('j'),
2057 K => Some('k'),
2058 L => Some('l'),
2059 M => Some('m'),
2060 N => Some('n'),
2061 O => Some('o'),
2062 P => Some('p'),
2063 Q => Some('q'),
2064 R => Some('r'),
2065 S => Some('s'),
2066 T => Some('t'),
2067 U => Some('u'),
2068 V => Some('v'),
2069 W => Some('w'),
2070 X => Some('x'),
2071 Y => Some('y'),
2072 Z => Some('z'),
2073 Key0 | Numpad0 => Some('0'),
2074 Key1 | Numpad1 => Some('1'),
2075 Key2 | Numpad2 => Some('2'),
2076 Key3 | Numpad3 => Some('3'),
2077 Key4 | Numpad4 => Some('4'),
2078 Key5 | Numpad5 => Some('5'),
2079 Key6 | Numpad6 => Some('6'),
2080 Key7 | Numpad7 => Some('7'),
2081 Key8 | Numpad8 => Some('8'),
2082 Key9 | Numpad9 => Some('9'),
2083 Minus => Some('-'),
2084 Asterisk => Some('*'),
2085 At => Some('@'),
2086 Period => Some('.'),
2087 Semicolon => Some(';'),
2088 Slash => Some('/'),
2089 Caret => Some('^'),
2090 _ => None,
2091 }
2092 }
2093}
2094
2095#[derive(Debug, Clone)]
2097#[repr(C)]
2098pub struct SmallWindowIconBytes {
2099 pub key: IconKey,
2100 pub rgba_bytes: U8Vec,
2101}
2102
2103#[derive(Debug, Clone)]
2105#[repr(C)]
2106pub struct LargeWindowIconBytes {
2107 pub key: IconKey,
2108 pub rgba_bytes: U8Vec,
2109}
2110
2111#[derive(Debug, Clone)]
2113#[repr(C, u8)]
2114pub enum WindowIcon {
2115 Small(SmallWindowIconBytes),
2116 Large(LargeWindowIconBytes),
2118}
2119
2120impl_option!(
2121 WindowIcon,
2122 OptionWindowIcon,
2123 copy = false,
2124 [Debug, Clone, PartialOrd, PartialEq, Eq, Hash, Ord]
2125);
2126
2127impl WindowIcon {
2128 #[must_use] pub const fn get_key(&self) -> IconKey {
2129 match &self {
2130 Self::Small(SmallWindowIconBytes { key, .. })
2131 | Self::Large(LargeWindowIconBytes { key, .. }) => *key,
2132 }
2133 }
2134}
2135impl PartialEq for WindowIcon {
2138 fn eq(&self, rhs: &Self) -> bool {
2139 self.get_key() == rhs.get_key()
2140 }
2141}
2142
2143impl PartialOrd for WindowIcon {
2144 fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
2145 Some((self.get_key()).cmp(&rhs.get_key()))
2146 }
2147}
2148
2149impl Eq for WindowIcon {}
2150
2151impl Ord for WindowIcon {
2152 fn cmp(&self, rhs: &Self) -> Ordering {
2153 (self.get_key()).cmp(&rhs.get_key())
2154 }
2155}
2156
2157impl Hash for WindowIcon {
2158 fn hash<H>(&self, state: &mut H)
2159 where
2160 H: Hasher,
2161 {
2162 self.get_key().hash(state);
2163 }
2164}
2165
2166#[derive(Debug, Clone)]
2168#[repr(C)]
2169pub struct TaskBarIcon {
2170 pub key: IconKey,
2171 pub rgba_bytes: U8Vec,
2172}
2173
2174impl_option!(
2175 TaskBarIcon,
2176 OptionTaskBarIcon,
2177 copy = false,
2178 [Debug, Clone, PartialOrd, PartialEq, Eq, Hash, Ord]
2179);
2180
2181impl PartialEq for TaskBarIcon {
2182 fn eq(&self, rhs: &Self) -> bool {
2183 self.key == rhs.key
2184 }
2185}
2186
2187impl PartialOrd for TaskBarIcon {
2188 fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
2189 Some((self.key).cmp(&rhs.key))
2190 }
2191}
2192
2193impl Eq for TaskBarIcon {}
2194
2195impl Ord for TaskBarIcon {
2196 fn cmp(&self, rhs: &Self) -> Ordering {
2197 (self.key).cmp(&rhs.key)
2198 }
2199}
2200
2201impl Hash for TaskBarIcon {
2202 fn hash<H>(&self, state: &mut H)
2203 where
2204 H: Hasher,
2205 {
2206 self.key.hash(state);
2207 }
2208}
2209
2210#[cfg(test)]
2211#[allow(clippy::float_cmp)] mod audit_tests {
2213 use super::*;
2214
2215 #[test]
2216 fn hidpi_factor_guards_zero_dpi() {
2217 let ws = WindowSize { dpi: 0, ..WindowSize::default() };
2220 let factor = ws.get_hidpi_factor().inner.get();
2221 assert_eq!(factor, 1.0);
2222
2223 let ws2 = WindowSize { dpi: 192, ..WindowSize::default() };
2224 assert_eq!(ws2.get_hidpi_factor().inner.get(), 2.0);
2225 }
2226
2227 #[test]
2228 fn virtual_keycode_from_u32_roundtrips() {
2229 for vk in [
2231 VirtualKeyCode::Key1,
2232 VirtualKeyCode::A,
2233 VirtualKeyCode::Z,
2234 VirtualKeyCode::Left,
2235 VirtualKeyCode::Back,
2236 VirtualKeyCode::Delete,
2237 VirtualKeyCode::LControl,
2238 VirtualKeyCode::Cut,
2239 ] {
2240 assert_eq!(VirtualKeyCode::from_u32(vk as u32), Some(vk));
2241 }
2242 assert_eq!(VirtualKeyCode::from_u32(10_000), None);
2244 }
2245}
2246
2247#[cfg(test)]
2248#[allow(clippy::float_cmp)] mod autotest_generated {
2250 use alloc::{format, string::String, vec};
2251
2252 use super::*;
2253
2254 const LAST_VK: u32 = 162;
2256
2257 #[derive(Default)]
2260 struct TestHasher(u64);
2261
2262 impl Hasher for TestHasher {
2263 fn write(&mut self, bytes: &[u8]) {
2264 for &b in bytes {
2265 self.0 = self.0.rotate_left(5) ^ u64::from(b);
2266 }
2267 }
2268 fn finish(&self) -> u64 {
2269 self.0
2270 }
2271 }
2272
2273 fn hash_of<T: Hash>(value: &T) -> u64 {
2274 let mut h = TestHasher::default();
2275 value.hash(&mut h);
2276 h.finish()
2277 }
2278
2279 fn keyboard_with(keys: &[VirtualKeyCode]) -> KeyboardState {
2280 KeyboardState {
2281 pressed_virtual_keycodes: keys.to_vec().into(),
2282 ..KeyboardState::default()
2283 }
2284 }
2285
2286 fn pair(key: &str, value: &str) -> AzStringPair {
2287 AzStringPair {
2288 key: key.into(),
2289 value: value.into(),
2290 }
2291 }
2292
2293 #[test]
2298 fn window_id_new_is_unique_and_monotonic() {
2299 let mut ids = BTreeSet::new();
2302 let mut prev = WindowId::new();
2303 assert!(ids.insert(prev));
2304 for _ in 0..1000 {
2305 let next = WindowId::new();
2306 assert!(next.id > prev.id, "WindowId counter must strictly increase");
2307 assert!(ids.insert(next), "WindowId::new() handed out a duplicate");
2308 prev = next;
2309 }
2310 assert_ne!(WindowId::default(), WindowId::default());
2312 }
2313
2314 #[test]
2315 fn icon_key_new_is_unique_and_monotonic() {
2316 let mut keys = BTreeSet::new();
2317 let mut prev = IconKey::new();
2318 assert!(keys.insert(prev));
2319 for _ in 0..1000 {
2320 let next = IconKey::new();
2321 assert!(
2322 next.icon_id > prev.icon_id,
2323 "IconKey counter must strictly increase"
2324 );
2325 assert!(keys.insert(next), "IconKey::new() handed out a duplicate");
2326 prev = next;
2327 }
2328 assert_ne!(IconKey::default(), IconKey::default());
2329 }
2330
2331 #[test]
2336 fn renderer_options_new_preserves_every_combination() {
2337 let vsyncs = [Vsync::Enabled, Vsync::Disabled, Vsync::DontCare];
2338 let srgbs = [Srgb::Enabled, Srgb::Disabled, Srgb::DontCare];
2339 let accels = [
2340 HwAcceleration::Enabled,
2341 HwAcceleration::Disabled,
2342 HwAcceleration::DontCare,
2343 ];
2344 for v in vsyncs {
2345 for s in srgbs {
2346 for a in accels {
2347 let o = RendererOptions::new(v, s, a);
2348 assert_eq!(o.vsync, v);
2349 assert_eq!(o.srgb, s);
2350 assert_eq!(o.hw_accel, a);
2351 assert_eq!(o, RendererOptions::new(v, s, a));
2353 }
2354 }
2355 }
2356 }
2357
2358 #[test]
2359 fn renderer_options_default_does_not_force_cpu_rendering() {
2360 let d = RendererOptions::default();
2363 assert_eq!(d.hw_accel, HwAcceleration::DontCare);
2364 assert!(!d.hw_accel.is_enabled());
2365 assert!(d.vsync.is_enabled());
2366 assert!(!d.srgb.is_enabled());
2367 }
2368
2369 #[test]
2370 fn tri_state_is_enabled_only_for_enabled_variant() {
2371 assert!(Vsync::Enabled.is_enabled());
2372 assert!(!Vsync::Disabled.is_enabled());
2373 assert!(!Vsync::DontCare.is_enabled());
2374
2375 assert!(Srgb::Enabled.is_enabled());
2376 assert!(!Srgb::Disabled.is_enabled());
2377 assert!(!Srgb::DontCare.is_enabled());
2378
2379 assert!(HwAcceleration::Enabled.is_enabled());
2380 assert!(!HwAcceleration::Disabled.is_enabled());
2381 assert!(!HwAcceleration::DontCare.is_enabled());
2382 }
2383
2384 #[test]
2389 fn keyboard_state_default_has_no_key_down() {
2390 let k = KeyboardState::default();
2391 assert!(!k.shift_down());
2392 assert!(!k.ctrl_down());
2393 assert!(!k.alt_down());
2394 assert!(!k.super_down());
2395 assert!(!k.primary_down());
2396 for v in 0..=LAST_VK {
2398 let vk = VirtualKeyCode::from_u32(v).expect("discriminant in range");
2399 assert!(!k.is_key_down(vk));
2400 }
2401 }
2402
2403 #[test]
2404 fn keyboard_state_modifiers_accept_either_side() {
2405 for (left, right, probe) in [
2406 (
2407 VirtualKeyCode::LShift,
2408 VirtualKeyCode::RShift,
2409 KeyboardState::shift_down as fn(&KeyboardState) -> bool,
2410 ),
2411 (
2412 VirtualKeyCode::LControl,
2413 VirtualKeyCode::RControl,
2414 KeyboardState::ctrl_down as fn(&KeyboardState) -> bool,
2415 ),
2416 (
2417 VirtualKeyCode::LAlt,
2418 VirtualKeyCode::RAlt,
2419 KeyboardState::alt_down as fn(&KeyboardState) -> bool,
2420 ),
2421 (
2422 VirtualKeyCode::LWin,
2423 VirtualKeyCode::RWin,
2424 KeyboardState::super_down as fn(&KeyboardState) -> bool,
2425 ),
2426 ] {
2427 assert!(probe(&keyboard_with(&[left])), "left variant must register");
2428 assert!(
2429 probe(&keyboard_with(&[right])),
2430 "right variant must register"
2431 );
2432 assert!(probe(&keyboard_with(&[left, right])));
2433 assert!(!probe(&keyboard_with(&[VirtualKeyCode::A])));
2435 }
2436 }
2437
2438 #[test]
2439 fn keyboard_state_primary_down_follows_platform() {
2440 let ctrl = keyboard_with(&[VirtualKeyCode::LControl]);
2441 let cmd = keyboard_with(&[VirtualKeyCode::LWin]);
2442 if cfg!(target_os = "macos") {
2443 assert!(cmd.primary_down(), "Cmd (super) is PRIMARY on macOS");
2444 assert!(!ctrl.primary_down());
2445 } else {
2446 assert!(ctrl.primary_down(), "Ctrl is PRIMARY off macOS");
2447 assert!(!cmd.primary_down());
2448 }
2449 for k in [&ctrl, &cmd, &KeyboardState::default()] {
2451 assert_eq!(
2452 k.primary_down(),
2453 if cfg!(target_os = "macos") {
2454 k.super_down()
2455 } else {
2456 k.ctrl_down()
2457 }
2458 );
2459 }
2460 }
2461
2462 #[test]
2463 fn is_key_down_handles_duplicates_and_large_state() {
2464 let dup = keyboard_with(&[VirtualKeyCode::S; 512]);
2466 assert!(dup.is_key_down(VirtualKeyCode::S));
2467 assert!(!dup.is_key_down(VirtualKeyCode::A));
2468
2469 let all: alloc::vec::Vec<VirtualKeyCode> = (0..=LAST_VK)
2471 .map(|v| VirtualKeyCode::from_u32(v).expect("discriminant in range"))
2472 .collect();
2473 let everything = keyboard_with(&all);
2474 for vk in &all {
2475 assert!(everything.is_key_down(*vk));
2476 }
2477 assert!(everything.shift_down() && everything.ctrl_down());
2478 assert!(everything.alt_down() && everything.super_down());
2479 }
2480
2481 #[test]
2486 fn empty_chord_matches_trivially() {
2487 assert!(KeyboardState::default().matches_accelerator(&[]));
2489 assert!(keyboard_with(&[VirtualKeyCode::A]).matches_accelerator(&[]));
2490 }
2491
2492 #[test]
2493 fn matches_accelerator_requires_every_entry() {
2494 let state = keyboard_with(&[
2495 VirtualKeyCode::LControl,
2496 VirtualKeyCode::LShift,
2497 VirtualKeyCode::S,
2498 ]);
2499 assert!(state.matches_accelerator(&[
2500 AcceleratorKey::Ctrl,
2501 AcceleratorKey::Shift,
2502 AcceleratorKey::Key(VirtualKeyCode::S),
2503 ]));
2504 assert!(!state.matches_accelerator(&[
2506 AcceleratorKey::Ctrl,
2507 AcceleratorKey::Alt,
2508 AcceleratorKey::Key(VirtualKeyCode::S),
2509 ]));
2510 assert!(!state.matches_accelerator(&[
2512 AcceleratorKey::Ctrl,
2513 AcceleratorKey::Key(VirtualKeyCode::Q),
2514 ]));
2515 assert!(state.matches_accelerator(&[
2517 AcceleratorKey::Key(VirtualKeyCode::S),
2518 AcceleratorKey::Shift,
2519 AcceleratorKey::Ctrl,
2520 ]));
2521 }
2522
2523 #[test]
2524 fn matches_accelerator_survives_huge_chord() {
2525 let state = keyboard_with(&[VirtualKeyCode::LShift]);
2527 let long_ok = vec![AcceleratorKey::Shift; 10_000];
2528 assert!(state.matches_accelerator(&long_ok));
2529
2530 let mut long_bad = vec![AcceleratorKey::Shift; 10_000];
2533 long_bad.push(AcceleratorKey::Ctrl);
2534 assert!(!state.matches_accelerator(&long_bad));
2535 }
2536
2537 #[test]
2538 fn accelerator_key_matches_each_variant() {
2539 let empty = KeyboardState::default();
2540 for a in [
2541 AcceleratorKey::Ctrl,
2542 AcceleratorKey::Alt,
2543 AcceleratorKey::Shift,
2544 AcceleratorKey::Key(VirtualKeyCode::A),
2545 ] {
2546 assert!(!a.matches(&empty), "nothing matches an empty keyboard state");
2547 }
2548 assert!(AcceleratorKey::Ctrl.matches(&keyboard_with(&[VirtualKeyCode::RControl])));
2549 assert!(AcceleratorKey::Alt.matches(&keyboard_with(&[VirtualKeyCode::RAlt])));
2550 assert!(AcceleratorKey::Shift.matches(&keyboard_with(&[VirtualKeyCode::RShift])));
2551 assert!(
2552 AcceleratorKey::Key(VirtualKeyCode::F24).matches(&keyboard_with(&[VirtualKeyCode::F24]))
2553 );
2554 assert!(!AcceleratorKey::Ctrl.matches(&keyboard_with(&[VirtualKeyCode::C])));
2556 }
2557
2558 #[test]
2563 fn mouse_state_matches_context_button() {
2564 let base = MouseState::default();
2565 assert!(!base.matches(&ContextMenuMouseButton::Left));
2566 assert!(!base.matches(&ContextMenuMouseButton::Right));
2567 assert!(!base.matches(&ContextMenuMouseButton::Middle));
2568
2569 for (ctx, ms) in [
2570 (
2571 ContextMenuMouseButton::Left,
2572 MouseState {
2573 left_down: true,
2574 ..MouseState::default()
2575 },
2576 ),
2577 (
2578 ContextMenuMouseButton::Right,
2579 MouseState {
2580 right_down: true,
2581 ..MouseState::default()
2582 },
2583 ),
2584 (
2585 ContextMenuMouseButton::Middle,
2586 MouseState {
2587 middle_down: true,
2588 ..MouseState::default()
2589 },
2590 ),
2591 ] {
2592 assert!(ms.matches(&ctx), "{ctx:?} must match its own button");
2593 let others = [
2595 ContextMenuMouseButton::Left,
2596 ContextMenuMouseButton::Right,
2597 ContextMenuMouseButton::Middle,
2598 ];
2599 for other in others {
2600 assert_eq!(ms.matches(&other), other == ctx);
2601 }
2602 }
2603 }
2604
2605 #[test]
2606 fn mouse_down_and_button_state_agree_for_all_8_combinations() {
2607 for bits in 0u8..8 {
2608 let (l, r, m) = (bits & 1 != 0, bits & 2 != 0, bits & 4 != 0);
2609 let ms = MouseState {
2610 left_down: l,
2611 right_down: r,
2612 middle_down: m,
2613 ..MouseState::default()
2614 };
2615 assert_eq!(ms.mouse_down(), l || r || m);
2616
2617 let snapshot = ms.button_state();
2618 assert_eq!(snapshot.left_down, l);
2619 assert_eq!(snapshot.right_down, r);
2620 assert_eq!(snapshot.middle_down, m);
2621 assert_eq!(snapshot.any_down(), ms.mouse_down());
2623 assert_eq!(crate::events::MouseButtonState::from(&ms), snapshot);
2624 }
2625 assert!(!MouseState::default().mouse_down());
2627 assert!(!MouseState::default().button_state().any_down());
2628 }
2629
2630 #[test]
2635 fn process_system_scroll_zero_and_negative_zero_consume_nothing() {
2636 let r = process_system_scroll(LogicalPosition::zero(), false);
2637 assert_eq!(r.scrolled_nodes, 0);
2638 assert_eq!(r.remaining_delta, LogicalPosition::zero());
2639 assert!(!r.hit_scrollbar);
2640
2641 let neg_zero = process_system_scroll(LogicalPosition::new(-0.0, -0.0), true);
2643 assert_eq!(neg_zero.scrolled_nodes, 0);
2644 assert!(neg_zero.hit_scrollbar, "hit_scrollbar is echoed verbatim");
2645 }
2646
2647 #[test]
2648 fn process_system_scroll_counts_any_nonzero_axis() {
2649 for delta in [
2650 LogicalPosition::new(1.0, 0.0),
2651 LogicalPosition::new(0.0, -1.0),
2652 LogicalPosition::new(-3.5, 7.25),
2653 LogicalPosition::new(f32::MIN, 0.0),
2654 LogicalPosition::new(0.0, f32::MAX),
2655 LogicalPosition::new(f32::INFINITY, f32::NEG_INFINITY),
2656 LogicalPosition::new(f32::MIN_POSITIVE, 0.0),
2657 ] {
2658 let r = process_system_scroll(delta, false);
2659 assert_eq!(r.scrolled_nodes, 1, "{delta:?} must count as consumed");
2660 assert_eq!(r.remaining_delta, LogicalPosition::zero());
2662 }
2663 }
2664
2665 #[test]
2666 fn process_system_scroll_does_not_panic_on_nan() {
2667 for delta in [
2670 LogicalPosition::new(f32::NAN, 0.0),
2671 LogicalPosition::new(0.0, f32::NAN),
2672 LogicalPosition::new(f32::NAN, f32::NAN),
2673 ] {
2674 let r = process_system_scroll(delta, true);
2675 assert!(r.scrolled_nodes <= 1);
2676 assert_eq!(r.remaining_delta, LogicalPosition::zero());
2677 assert!(r.hit_scrollbar);
2678 }
2679 }
2680
2681 #[test]
2682 fn scroll_result_default_is_inert() {
2683 let d = ScrollResult::default();
2684 assert_eq!(d.scrolled_nodes, 0);
2685 assert_eq!(d.remaining_delta, LogicalPosition::zero());
2686 assert!(!d.hit_scrollbar);
2687 }
2688
2689 #[test]
2694 fn cursor_position_get_position_only_inside_window() {
2695 let p = LogicalPosition::new(12.0, 34.0);
2696 assert_eq!(CursorPosition::InWindow(p).get_position(), Some(p));
2697 assert_eq!(CursorPosition::OutOfWindow(p).get_position(), None);
2698 assert_eq!(CursorPosition::Uninitialized.get_position(), None);
2699 assert_eq!(CursorPosition::default(), CursorPosition::Uninitialized);
2701 assert_eq!(CursorPosition::default().get_position(), None);
2702 }
2703
2704 #[test]
2705 fn cursor_position_is_inside_window_agrees_with_get_position() {
2706 for c in [
2707 CursorPosition::Uninitialized,
2708 CursorPosition::InWindow(LogicalPosition::zero()),
2709 CursorPosition::OutOfWindow(LogicalPosition::zero()),
2710 CursorPosition::InWindow(LogicalPosition::new(f32::MIN, f32::MAX)),
2711 CursorPosition::OutOfWindow(LogicalPosition::new(f32::INFINITY, f32::NAN)),
2712 ] {
2713 assert_eq!(c.is_inside_window(), c.get_position().is_some());
2714 }
2715 let nan_pos = CursorPosition::InWindow(LogicalPosition::new(f32::NAN, f32::INFINITY));
2717 assert!(nan_pos.is_inside_window());
2718 let got = nan_pos.get_position().expect("InWindow always yields a position");
2719 assert!(got.x.is_nan());
2720 assert!(got.y.is_infinite());
2721 }
2722
2723 #[test]
2728 fn monitor_id_constructors_preserve_fields_at_extremes() {
2729 assert_eq!(MonitorId::PRIMARY, MonitorId { index: 0, hash: 0 });
2730 assert_eq!(MonitorId::new(0), MonitorId::PRIMARY);
2731
2732 for index in [0usize, 1, 42, usize::MAX] {
2733 let m = MonitorId::new(index);
2734 assert_eq!(m.index, index);
2735 assert_eq!(m.hash, 0, "new() documents hash == 0");
2736
2737 for hash in [0u64, 1, u64::MAX] {
2738 let m = MonitorId::from_index_and_hash(index, hash);
2739 assert_eq!(m.index, index);
2740 assert_eq!(m.hash, hash);
2741 }
2742 }
2743 assert_ne!(MonitorId::new(1), MonitorId::new(2));
2745 assert_ne!(
2746 MonitorId::from_index_and_hash(1, 7),
2747 MonitorId::from_index_and_hash(1, 8)
2748 );
2749 }
2750
2751 #[test]
2752 fn monitor_id_from_properties_is_stable_and_index_independent() {
2753 let pos = LayoutPoint::new(-1920, 0);
2754 let size = LayoutSize::new(2560, 1440);
2755
2756 let a = MonitorId::from_properties(0, "HDMI-1", pos, size);
2757 let b = MonitorId::from_properties(0, "HDMI-1", pos, size);
2758 assert_eq!(a, b, "hash must be stable across calls (persistable)");
2759
2760 let reindexed = MonitorId::from_properties(7, "HDMI-1", pos, size);
2762 assert_eq!(reindexed.hash, a.hash);
2763 assert_eq!(reindexed.index, 7);
2764 assert_ne!(reindexed, a, "index is still part of identity");
2765 }
2766
2767 #[test]
2768 fn monitor_id_from_properties_is_sensitive_to_each_property() {
2769 let pos = LayoutPoint::new(0, 0);
2770 let size = LayoutSize::new(1920, 1080);
2771 let base = MonitorId::from_properties(0, "DP-1", pos, size);
2772
2773 assert_ne!(
2775 base.hash,
2776 MonitorId::from_properties(0, "DP-2", pos, size).hash
2777 );
2778 assert_ne!(
2779 base.hash,
2780 MonitorId::from_properties(0, "DP-1", LayoutPoint::new(1, 0), size).hash
2781 );
2782 assert_ne!(
2783 base.hash,
2784 MonitorId::from_properties(0, "DP-1", LayoutPoint::new(0, 1), size).hash
2785 );
2786 assert_ne!(
2787 base.hash,
2788 MonitorId::from_properties(0, "DP-1", pos, LayoutSize::new(1921, 1080)).hash
2789 );
2790 assert_ne!(
2791 base.hash,
2792 MonitorId::from_properties(0, "DP-1", pos, LayoutSize::new(1920, 1081)).hash
2793 );
2794 assert_ne!(
2796 base.hash,
2797 MonitorId::from_properties(0, "DP-1", pos, LayoutSize::new(1080, 1920)).hash
2798 );
2799 }
2800
2801 #[test]
2802 fn monitor_id_from_properties_handles_hostile_inputs() {
2803 let size = LayoutSize::new(isize::MAX, isize::MIN);
2804 let pos = LayoutPoint::new(isize::MIN, isize::MAX);
2805
2806 for name in ["", " ", "\t\n", "\u{1F600}", "e\u{301}", "é", "a\0b"] {
2808 let m = MonitorId::from_properties(3, name, pos, size);
2809 assert_eq!(m.index, 3);
2810 assert_eq!(m, MonitorId::from_properties(3, name, pos, size));
2812 }
2813
2814 assert_ne!(
2816 MonitorId::from_properties(0, "e\u{301}", pos, size).hash,
2817 MonitorId::from_properties(0, "é", pos, size).hash
2818 );
2819
2820 let huge = "x".repeat(1_000_000);
2822 let h1 = MonitorId::from_properties(0, &huge, LayoutPoint::zero(), LayoutSize::zero());
2823 let h2 = MonitorId::from_properties(0, &huge, LayoutPoint::zero(), LayoutSize::zero());
2824 assert_eq!(h1, h2);
2825 assert_ne!(
2826 h1.hash,
2827 MonitorId::from_properties(0, "x", LayoutPoint::zero(), LayoutSize::zero()).hash
2828 );
2829 }
2830
2831 #[test]
2836 fn window_flags_type_predicates_are_mutually_exclusive() {
2837 for (ty, menu, tooltip, dialog) in [
2838 (WindowType::Normal, false, false, false),
2839 (WindowType::Menu, true, false, false),
2840 (WindowType::Tooltip, false, true, false),
2841 (WindowType::Dialog, false, false, true),
2842 ] {
2843 let f = WindowFlags {
2844 window_type: ty,
2845 ..WindowFlags::default()
2846 };
2847 assert_eq!(f.is_menu_window(), menu);
2848 assert_eq!(f.is_tooltip_window(), tooltip);
2849 assert_eq!(f.is_dialog_window(), dialog);
2850 let count = u8::from(f.is_menu_window())
2852 + u8::from(f.is_tooltip_window())
2853 + u8::from(f.is_dialog_window());
2854 assert!(count <= 1);
2855 }
2856 }
2857
2858 #[test]
2859 fn window_flags_bool_getters_mirror_their_fields() {
2860 let d = WindowFlags::default();
2862 assert!(d.window_has_focus());
2863 assert!(!d.is_close_requested());
2864 assert!(!d.has_csd());
2865 assert_eq!(
2866 d.use_native_menus(),
2867 cfg!(any(target_os = "windows", target_os = "macos"))
2868 );
2869 assert_eq!(
2870 d.use_native_context_menus(),
2871 cfg!(any(target_os = "windows", target_os = "macos"))
2872 );
2873
2874 for b in [false, true] {
2876 let f = WindowFlags {
2877 has_focus: b,
2878 close_requested: b,
2879 has_decorations: b,
2880 use_native_menus: b,
2881 use_native_context_menus: b,
2882 ..WindowFlags::default()
2883 };
2884 assert_eq!(f.window_has_focus(), b);
2885 assert_eq!(f.is_close_requested(), b);
2886 assert_eq!(f.has_csd(), b);
2887 assert_eq!(f.use_native_menus(), b);
2888 assert_eq!(f.use_native_context_menus(), b);
2889 }
2890 }
2891
2892 #[test]
2897 fn get_key_on_empty_vec_is_none() {
2898 let empty = StringPairVec::new();
2899 assert!(empty.get_key("").is_none());
2900 assert!(empty.get_key("anything").is_none());
2901
2902 let mut empty_mut = StringPairVec::new();
2903 assert!(empty_mut.get_key_mut("").is_none());
2904 assert!(empty_mut.get_key_mut("anything").is_none());
2905 }
2906
2907 #[test]
2908 fn get_key_valid_minimal_and_missing() {
2909 let v = StringPairVec::from_vec(vec![pair("WM_CLASS", "azul")]);
2910 assert_eq!(
2911 v.get_key("WM_CLASS").map(AzString::as_str),
2912 Some("azul"),
2913 "positive control"
2914 );
2915 assert!(v.get_key("wm_class").is_none(), "lookup is case-sensitive");
2916 assert!(v.get_key("WM_CLAS").is_none());
2917 assert!(v.get_key("WM_CLASS ").is_none(), "no trimming is performed");
2918 assert!(v.get_key(" WM_CLASS").is_none());
2919 assert!(v.get_key("WM_CLASS;garbage").is_none());
2920 }
2921
2922 #[test]
2923 fn get_key_handles_garbage_whitespace_and_boundary_numbers() {
2924 let v = StringPairVec::from_vec(vec![
2925 pair("", "empty-key"),
2926 pair(" ", "spaces"),
2927 pair("\t\n", "tabs"),
2928 pair("0", "zero"),
2929 pair("-0", "neg-zero"),
2930 pair("9223372036854775807", "i64-max"),
2931 pair("NaN", "nan"),
2932 pair("inf", "inf"),
2933 ]);
2934
2935 assert_eq!(v.get_key("").map(AzString::as_str), Some("empty-key"));
2937 assert_eq!(v.get_key(" ").map(AzString::as_str), Some("spaces"));
2938 assert_eq!(v.get_key("\t\n").map(AzString::as_str), Some("tabs"));
2939
2940 assert_eq!(v.get_key("0").map(AzString::as_str), Some("zero"));
2942 assert_eq!(v.get_key("-0").map(AzString::as_str), Some("neg-zero"));
2943 assert_eq!(
2944 v.get_key("9223372036854775807").map(AzString::as_str),
2945 Some("i64-max")
2946 );
2947 assert_eq!(v.get_key("NaN").map(AzString::as_str), Some("nan"));
2948 assert_eq!(v.get_key("inf").map(AzString::as_str), Some("inf"));
2949 assert!(v.get_key("nan").is_none());
2950
2951 assert!(v.get_key("\u{0}\u{1}\u{7f}\\x\"';--").is_none());
2953 assert!(v.get_key(&"[".repeat(10_000)).is_none());
2954 assert!(v.get_key(&"{\"a\":".repeat(10_000)).is_none());
2955 }
2956
2957 #[test]
2958 fn get_key_handles_unicode_without_panicking() {
2959 let v = StringPairVec::from_vec(vec![
2960 pair("\u{1F600}", "grin"),
2961 pair("é", "precomposed"),
2962 pair("日本語", "jp"),
2963 ]);
2964 assert_eq!(v.get_key("\u{1F600}").map(AzString::as_str), Some("grin"));
2965 assert_eq!(v.get_key("日本語").map(AzString::as_str), Some("jp"));
2966 assert_eq!(v.get_key("é").map(AzString::as_str), Some("precomposed"));
2968 assert!(v.get_key("e\u{301}").is_none());
2969 assert!(v.get_key("日本").is_none());
2971 }
2972
2973 #[test]
2974 fn get_key_handles_extremely_long_input() {
2975 let huge = "k".repeat(1_000_000);
2976 let mut v = StringPairVec::from_vec(vec![pair("short", "1")]);
2977
2978 assert!(v.get_key(&huge).is_none());
2980
2981 v.push(AzStringPair {
2983 key: huge.as_str().into(),
2984 value: "big".into(),
2985 });
2986 assert_eq!(v.get_key(&huge).map(AzString::as_str), Some("big"));
2987 assert!(v.get_key(&"k".repeat(999_999)).is_none());
2989 assert!(v.get_key(&"k".repeat(1_000_001)).is_none());
2990 }
2991
2992 #[test]
2993 fn get_key_returns_first_of_duplicate_keys() {
2994 let v = StringPairVec::from_vec(vec![
2995 pair("dup", "first"),
2996 pair("dup", "second"),
2997 pair("dup", "third"),
2998 ]);
2999 assert_eq!(v.get_key("dup").map(AzString::as_str), Some("first"));
3000 }
3001
3002 #[test]
3003 fn get_key_mut_mutates_in_place() {
3004 let mut v = StringPairVec::from_vec(vec![pair("a", "1"), pair("b", "2")]);
3005 {
3006 let entry = v.get_key_mut("b").expect("b is present");
3007 entry.value = "changed".into();
3008 }
3009 assert_eq!(v.get_key("b").map(AzString::as_str), Some("changed"));
3010 assert_eq!(v.get_key("a").map(AzString::as_str), Some("1"));
3011 assert!(v.get_key_mut("missing").is_none());
3012 assert_eq!(v.len(), 2, "get_key_mut must not add entries");
3013
3014 {
3016 let entry = v.get_key_mut("a").expect("a is present");
3017 entry.key = "z".into();
3018 }
3019 assert!(v.get_key("a").is_none());
3020 assert_eq!(v.get_key("z").map(AzString::as_str), Some("1"));
3021 }
3022
3023 #[test]
3024 fn insert_kv_updates_existing_and_appends_new() {
3025 let mut v = StringPairVec::new();
3026 v.insert_kv("k", "v1");
3027 assert_eq!(v.len(), 1);
3028 assert_eq!(v.get_key("k").map(AzString::as_str), Some("v1"));
3029
3030 v.insert_kv("k", "v2");
3032 assert_eq!(v.len(), 1, "insert_kv must not duplicate an existing key");
3033 assert_eq!(v.get_key("k").map(AzString::as_str), Some("v2"));
3034
3035 v.insert_kv("other", "x");
3037 assert_eq!(v.len(), 2);
3038 assert_eq!(v.get_key("k").map(AzString::as_str), Some("v2"));
3039 assert_eq!(v.get_key("other").map(AzString::as_str), Some("x"));
3040
3041 for i in 0..100 {
3043 v.insert_kv(String::from("k"), format!("gen{i}"));
3044 }
3045 assert_eq!(v.len(), 2);
3046 assert_eq!(v.get_key("k").map(AzString::as_str), Some("gen99"));
3047 }
3048
3049 #[test]
3050 fn insert_kv_accepts_hostile_keys_and_values() {
3051 let mut v = StringPairVec::new();
3052 v.insert_kv("", "");
3053 assert_eq!(v.len(), 1);
3054 assert_eq!(v.get_key("").map(AzString::as_str), Some(""));
3055
3056 v.insert_kv("\u{1F600}", "😀");
3057 assert_eq!(v.get_key("\u{1F600}").map(AzString::as_str), Some("😀"));
3058
3059 v.insert_kv(" ", "\t\n");
3060 assert_eq!(v.get_key(" ").map(AzString::as_str), Some("\t\n"));
3061
3062 let huge_key = "K".repeat(100_000);
3064 let huge_val = "V".repeat(100_000);
3065 v.insert_kv(huge_key.clone(), huge_val.clone());
3066 let before = v.len();
3067 assert_eq!(
3068 v.get_key(&huge_key).map(AzString::as_str),
3069 Some(huge_val.as_str())
3070 );
3071 v.insert_kv(huge_key.clone(), String::from("small"));
3072 assert_eq!(v.len(), before, "long key must hit the update path");
3073 assert_eq!(v.get_key(&huge_key).map(AzString::as_str), Some("small"));
3074 }
3075
3076 #[test]
3077 fn insert_kv_only_updates_the_first_of_pre_existing_duplicates() {
3078 let mut v = StringPairVec::from_vec(vec![pair("dup", "first"), pair("dup", "second")]);
3081 v.insert_kv("dup", "updated");
3082 assert_eq!(v.len(), 2, "no new entry is appended");
3083 assert_eq!(v.get_key("dup").map(AzString::as_str), Some("updated"));
3084 assert_eq!(
3085 v.get(1).expect("second entry still present").value.as_str(),
3086 "second",
3087 "the shadowed duplicate is left untouched"
3088 );
3089 }
3090
3091 #[test]
3096 fn window_size_get_logical_size_is_the_identity() {
3097 for dims in [
3098 LogicalSize::zero(),
3099 LogicalSize::new(640.0, 480.0),
3100 LogicalSize::new(-1.0, -2.0),
3101 LogicalSize::new(f32::MAX, f32::MIN),
3102 LogicalSize::new(f32::INFINITY, f32::MIN_POSITIVE),
3103 ] {
3104 let ws = WindowSize {
3105 dimensions: dims,
3106 ..WindowSize::default()
3107 };
3108 assert_eq!(ws.get_logical_size(), dims);
3109 }
3110 let d = WindowSize::default();
3112 assert_eq!(d.get_logical_size(), LogicalSize::new(640.0, 480.0));
3113 assert_eq!(d.dpi, 96);
3114 }
3115
3116 #[test]
3117 fn window_size_get_layout_size_rounds_half_away_from_zero() {
3118 for (w, h, ew, eh) in [
3119 (0.0f32, 0.0f32, 0isize, 0isize),
3120 (640.0, 480.0, 640, 480),
3121 (640.4, 480.4, 640, 480),
3122 (640.6, 480.6, 641, 481),
3123 (640.5, 639.5, 641, 640),
3124 (-0.5, -1.5, -1, -2),
3125 ] {
3126 let ws = WindowSize {
3127 dimensions: LogicalSize::new(w, h),
3128 ..WindowSize::default()
3129 };
3130 assert_eq!(ws.get_layout_size(), LayoutSize::new(ew, eh), "{w}x{h}");
3131 }
3132 }
3133
3134 #[test]
3135 fn window_size_get_layout_size_saturates_on_non_finite() {
3136 let nan = WindowSize {
3138 dimensions: LogicalSize::new(f32::NAN, f32::NAN),
3139 ..WindowSize::default()
3140 };
3141 assert_eq!(nan.get_layout_size(), LayoutSize::new(0, 0));
3142
3143 let inf = WindowSize {
3144 dimensions: LogicalSize::new(f32::INFINITY, f32::NEG_INFINITY),
3145 ..WindowSize::default()
3146 };
3147 assert_eq!(
3148 inf.get_layout_size(),
3149 LayoutSize::new(isize::MAX, isize::MIN)
3150 );
3151
3152 let max = WindowSize {
3153 dimensions: LogicalSize::new(f32::MAX, f32::MIN),
3154 ..WindowSize::default()
3155 };
3156 let ls = max.get_layout_size();
3157 assert!(ls.width > 0 && ls.height < 0, "sign is preserved: {ls:?}");
3158 }
3159
3160 #[test]
3161 fn window_size_get_physical_size_saturates_instead_of_wrapping() {
3162 let neg = WindowSize {
3164 dimensions: LogicalSize::new(-100.0, -0.4),
3165 ..WindowSize::default()
3166 };
3167 assert_eq!(neg.get_physical_size(), PhysicalSize::new(0, 0));
3168
3169 let nan = WindowSize {
3171 dimensions: LogicalSize::new(f32::NAN, f32::INFINITY),
3172 ..WindowSize::default()
3173 };
3174 assert_eq!(nan.get_physical_size(), PhysicalSize::new(0, u32::MAX));
3175
3176 let huge = WindowSize {
3178 dimensions: LogicalSize::new(f32::MAX, f32::MAX),
3179 dpi: 384,
3180 ..WindowSize::default()
3181 };
3182 assert_eq!(
3183 huge.get_physical_size(),
3184 PhysicalSize::new(u32::MAX, u32::MAX)
3185 );
3186
3187 let normal = WindowSize::default();
3189 assert_eq!(normal.get_physical_size(), PhysicalSize::new(640, 480));
3190 let retina = WindowSize {
3191 dpi: 192,
3192 ..WindowSize::default()
3193 };
3194 assert_eq!(retina.get_physical_size(), PhysicalSize::new(1280, 960));
3195 }
3196
3197 #[test]
3198 fn window_size_get_hidpi_factor_is_never_zero_or_negative() {
3199 for dpi in [
3201 0u32,
3202 1,
3203 47,
3204 48,
3205 95,
3206 96,
3207 97,
3208 120,
3209 144,
3210 192,
3211 384,
3212 u32::from(u16::MAX),
3213 u32::MAX,
3214 ] {
3215 let ws = WindowSize {
3216 dpi,
3217 ..WindowSize::default()
3218 };
3219 let f = ws.get_hidpi_factor().inner.get();
3220 assert!(
3221 f.is_finite() && f > 0.0,
3222 "dpi {dpi} produced a non-positive / non-finite scale factor: {f}"
3223 );
3224 }
3225
3226 for (dpi, expected) in [(0u32, 1.0f32), (96, 1.0), (144, 1.5), (192, 2.0), (384, 4.0)] {
3228 let ws = WindowSize {
3229 dpi,
3230 ..WindowSize::default()
3231 };
3232 assert_eq!(ws.get_hidpi_factor().inner.get(), expected, "dpi {dpi}");
3233 }
3234
3235 let odd = WindowSize {
3237 dpi: 100,
3238 ..WindowSize::default()
3239 };
3240 let f = odd.get_hidpi_factor().inner.get();
3241 assert!((f - 100.0 / 96.0).abs() < 0.002, "dpi 100 -> {f}");
3242 }
3243
3244 #[test]
3249 fn virtual_keycode_from_u32_roundtrips_every_discriminant() {
3250 for v in 0..=LAST_VK {
3251 let vk = VirtualKeyCode::from_u32(v)
3252 .unwrap_or_else(|| panic!("discriminant {v} is missing from the from_u32 table"));
3253 assert_eq!(vk as u32, v, "from_u32({v}) does not round-trip");
3254 }
3255 assert_eq!(VirtualKeyCode::Key1 as u32, 0);
3257 assert_eq!(VirtualKeyCode::Cut as u32, LAST_VK);
3258 }
3259
3260 #[test]
3261 fn virtual_keycode_from_u32_rejects_out_of_range() {
3262 for v in [
3263 LAST_VK + 1,
3264 LAST_VK + 2,
3265 255,
3266 256,
3267 1024,
3268 i32::MAX as u32,
3269 u32::MAX - 1,
3270 u32::MAX,
3271 ] {
3272 assert_eq!(
3273 VirtualKeyCode::from_u32(v),
3274 None,
3275 "{v} must not decode to a keycode"
3276 );
3277 }
3278 }
3279
3280 #[test]
3281 fn virtual_keycode_get_lowercase_never_panics_and_maps_letters_and_digits() {
3282 for v in 0..=LAST_VK {
3284 let vk = VirtualKeyCode::from_u32(v).expect("discriminant in range");
3285 if let Some(c) = vk.get_lowercase() {
3286 assert!(c.is_ascii(), "{vk:?} produced a non-ASCII char {c:?}");
3287 assert!(!c.is_ascii_uppercase(), "{vk:?} must yield lowercase");
3288 }
3289 }
3290
3291 for (i, expected) in ('a'..='z').enumerate() {
3293 let vk = VirtualKeyCode::from_u32(10 + i as u32).expect("letter range");
3294 assert_eq!(vk.get_lowercase(), Some(expected));
3295 }
3296
3297 for (top, pad, c) in [
3299 (VirtualKeyCode::Key0, VirtualKeyCode::Numpad0, '0'),
3300 (VirtualKeyCode::Key1, VirtualKeyCode::Numpad1, '1'),
3301 (VirtualKeyCode::Key5, VirtualKeyCode::Numpad5, '5'),
3302 (VirtualKeyCode::Key9, VirtualKeyCode::Numpad9, '9'),
3303 ] {
3304 assert_eq!(top.get_lowercase(), Some(c));
3305 assert_eq!(pad.get_lowercase(), Some(c));
3306 }
3307
3308 assert_eq!(VirtualKeyCode::Minus.get_lowercase(), Some('-'));
3310 assert_eq!(VirtualKeyCode::Period.get_lowercase(), Some('.'));
3311 assert_eq!(VirtualKeyCode::Slash.get_lowercase(), Some('/'));
3312 assert_eq!(VirtualKeyCode::Caret.get_lowercase(), Some('^'));
3313
3314 for vk in [
3316 VirtualKeyCode::LShift,
3317 VirtualKeyCode::RControl,
3318 VirtualKeyCode::Escape,
3319 VirtualKeyCode::F12,
3320 VirtualKeyCode::Space,
3321 VirtualKeyCode::Return,
3322 VirtualKeyCode::Back,
3323 ] {
3324 assert_eq!(vk.get_lowercase(), None, "{vk:?}");
3325 }
3326 }
3327
3328 #[test]
3333 fn window_icon_get_key_returns_the_stored_key() {
3334 let small_key = IconKey::new();
3335 let large_key = IconKey::new();
3336
3337 let small = WindowIcon::Small(SmallWindowIconBytes {
3338 key: small_key,
3339 rgba_bytes: vec![0u8; 16 * 16 * 4].into(),
3340 });
3341 let large = WindowIcon::Large(LargeWindowIconBytes {
3342 key: large_key,
3343 rgba_bytes: vec![255u8; 32 * 32 * 4].into(),
3344 });
3345
3346 assert_eq!(small.get_key(), small_key);
3347 assert_eq!(large.get_key(), large_key);
3348
3349 let empty = WindowIcon::Small(SmallWindowIconBytes {
3351 key: small_key,
3352 rgba_bytes: vec![].into(),
3353 });
3354 assert_eq!(empty.get_key(), small_key);
3355 }
3356
3357 #[test]
3358 fn window_icon_identity_is_the_key_alone() {
3359 let key = IconKey::new();
3362 let a = WindowIcon::Small(SmallWindowIconBytes {
3363 key,
3364 rgba_bytes: vec![0u8; 4].into(),
3365 });
3366 let b = WindowIcon::Small(SmallWindowIconBytes {
3367 key,
3368 rgba_bytes: vec![7u8; 1024].into(),
3369 });
3370 let c = WindowIcon::Large(LargeWindowIconBytes {
3372 key,
3373 rgba_bytes: vec![9u8; 32 * 32 * 4].into(),
3374 });
3375
3376 assert_eq!(a, b);
3377 assert_eq!(a, c);
3378 assert_eq!(a.cmp(&c), Ordering::Equal);
3379 assert_eq!(a.partial_cmp(&b), Some(Ordering::Equal));
3380 assert_eq!(hash_of(&a), hash_of(&b));
3382 assert_eq!(hash_of(&a), hash_of(&c));
3383
3384 let older = WindowIcon::Small(SmallWindowIconBytes {
3386 key,
3387 rgba_bytes: vec![0u8; 4].into(),
3388 });
3389 let newer = WindowIcon::Small(SmallWindowIconBytes {
3390 key: IconKey::new(),
3391 rgba_bytes: vec![0u8; 4].into(),
3392 });
3393 assert_ne!(older, newer);
3394 assert_eq!(older.cmp(&newer), Ordering::Less);
3395 }
3396
3397 #[test]
3402 fn update_focus_warning_display_is_non_empty_for_every_variant() {
3403 let dom = format!("{}", UpdateFocusWarning::FocusInvalidDomId(DomId::ROOT_ID));
3404 assert!(dom.contains("invalid ID"), "{dom}");
3405 assert!(!dom.is_empty());
3406
3407 let node = format!(
3408 "{}",
3409 UpdateFocusWarning::FocusInvalidNodeId(NodeHierarchyItemId::NONE)
3410 );
3411 assert!(node.contains("invalid ID"), "{node}");
3412
3413 let huge = format!(
3415 "{}",
3416 UpdateFocusWarning::FocusInvalidNodeId(NodeHierarchyItemId::from_raw(usize::MAX))
3417 );
3418 assert!(!huge.is_empty());
3419
3420 let path = format!(
3421 "{}",
3422 UpdateFocusWarning::CouldNotFindFocusNode(CssPath::default())
3423 );
3424 assert!(
3425 path.starts_with("Could not find focus node for path:"),
3426 "{path}"
3427 );
3428 }
3429}