1use core::fmt;
7
8#[cfg(target_os = "macos")]
9mod macos;
10
11#[cfg(target_os = "macos")]
12#[doc(hidden)]
13pub use macos::integration;
14#[cfg(target_os = "macos")]
15pub use macos::{Application, SurfaceTarget, Window};
16
17#[cfg(target_os = "linux")]
18mod linux;
19
20#[cfg(target_os = "linux")]
21#[doc(hidden)]
22pub use linux::integration;
23#[cfg(target_os = "linux")]
24pub use linux::{Application, SurfaceTarget, Window};
25
26#[cfg(target_os = "windows")]
27mod win32;
28
29#[cfg(target_os = "windows")]
30#[doc(hidden)]
31pub use win32::integration;
32#[cfg(target_os = "windows")]
33pub use win32::{Application, SurfaceTarget, Window};
34
35#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
37pub struct LogicalSize {
38 width: u32,
39 height: u32,
40}
41
42#[derive(Clone, Copy, Debug, Default, PartialEq)]
44pub struct LogicalPosition {
45 x: f64,
46 y: f64,
47}
48
49impl LogicalPosition {
50 #[must_use]
52 pub const fn new(x: f64, y: f64) -> Self {
53 Self { x, y }
54 }
55
56 #[must_use]
58 pub const fn x(self) -> f64 {
59 self.x
60 }
61
62 #[must_use]
64 pub const fn y(self) -> f64 {
65 self.y
66 }
67}
68
69impl LogicalSize {
70 #[must_use]
72 pub const fn new(width: u32, height: u32) -> Self {
73 Self { width, height }
74 }
75
76 #[must_use]
78 pub const fn width(self) -> u32 {
79 self.width
80 }
81
82 #[must_use]
84 pub const fn height(self) -> u32 {
85 self.height
86 }
87
88 #[must_use]
90 pub const fn is_empty(self) -> bool {
91 self.width == 0 || self.height == 0
92 }
93}
94
95#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
97pub struct PhysicalExtent {
98 width: u32,
99 height: u32,
100}
101
102impl PhysicalExtent {
103 #[must_use]
105 pub const fn new(width: u32, height: u32) -> Self {
106 Self { width, height }
107 }
108
109 #[must_use]
111 pub const fn width(self) -> u32 {
112 self.width
113 }
114
115 #[must_use]
117 pub const fn height(self) -> u32 {
118 self.height
119 }
120
121 #[must_use]
123 pub const fn is_empty(self) -> bool {
124 self.width == 0 || self.height == 0
125 }
126}
127
128#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
130pub struct WindowRevision(u64);
131
132impl WindowRevision {
133 #[cfg_attr(
134 not(any(target_os = "macos", target_os = "linux", target_os = "windows")),
135 allow(dead_code)
136 )]
137 pub(crate) const INITIAL: Self = Self(1);
138
139 #[cfg_attr(
140 not(any(target_os = "macos", target_os = "linux", target_os = "windows")),
141 allow(dead_code)
142 )]
143 pub(crate) const fn next(self) -> Self {
144 Self(self.0.saturating_add(1))
145 }
146
147 #[must_use]
149 pub const fn get(self) -> u64 {
150 self.0
151 }
152}
153
154#[derive(Clone, Copy, Debug, PartialEq)]
156pub struct WindowMetrics {
157 extent: PhysicalExtent,
158 scale_factor: f64,
159 revision: WindowRevision,
160}
161
162impl WindowMetrics {
163 #[cfg_attr(
164 not(any(target_os = "macos", target_os = "linux", target_os = "windows")),
165 allow(dead_code)
166 )]
167 pub(crate) const fn new(
168 extent: PhysicalExtent,
169 scale_factor: f64,
170 revision: WindowRevision,
171 ) -> Self {
172 Self {
173 extent,
174 scale_factor,
175 revision,
176 }
177 }
178
179 #[must_use]
181 pub const fn extent(self) -> PhysicalExtent {
182 self.extent
183 }
184
185 #[must_use]
187 pub const fn scale_factor(self) -> f64 {
188 self.scale_factor
189 }
190
191 #[must_use]
193 pub const fn revision(self) -> WindowRevision {
194 self.revision
195 }
196}
197
198#[derive(Clone, Debug, Eq, PartialEq)]
200pub struct WindowDescriptor {
201 title: String,
202 logical_size: LogicalSize,
203}
204
205#[derive(Clone, Copy, Debug, Eq, PartialEq)]
207pub enum ButtonState {
208 Pressed,
210 Released,
212}
213
214#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
219#[non_exhaustive]
220#[allow(missing_docs)]
221pub enum KeyCode {
222 KeyA,
223 KeyB,
224 KeyC,
225 KeyD,
226 KeyE,
227 KeyF,
228 KeyG,
229 KeyH,
230 KeyI,
231 KeyJ,
232 KeyK,
233 KeyL,
234 KeyM,
235 KeyN,
236 KeyO,
237 KeyP,
238 KeyQ,
239 KeyR,
240 KeyS,
241 KeyT,
242 KeyU,
243 KeyV,
244 KeyW,
245 KeyX,
246 KeyY,
247 KeyZ,
248 Digit0,
249 Digit1,
250 Digit2,
251 Digit3,
252 Digit4,
253 Digit5,
254 Digit6,
255 Digit7,
256 Digit8,
257 Digit9,
258 Escape,
259 Space,
260 Enter,
261 Tab,
262 Backspace,
263 Delete,
264 Insert,
265 Home,
266 End,
267 PageUp,
268 PageDown,
269 ArrowLeft,
270 ArrowRight,
271 ArrowUp,
272 ArrowDown,
273 Minus,
274 Equal,
275 BracketLeft,
276 BracketRight,
277 Backslash,
278 Semicolon,
279 Quote,
280 Backquote,
281 Comma,
282 Period,
283 Slash,
284 F1,
285 F2,
286 F3,
287 F4,
288 F5,
289 F6,
290 F7,
291 F8,
292 F9,
293 F10,
294 F11,
295 F12,
296 F13,
297 F14,
298 F15,
299 F16,
300 F17,
301 F18,
302 F19,
303 F20,
304 Numpad0,
305 Numpad1,
306 Numpad2,
307 Numpad3,
308 Numpad4,
309 Numpad5,
310 Numpad6,
311 Numpad7,
312 Numpad8,
313 Numpad9,
314 NumpadDecimal,
315 NumpadMultiply,
316 NumpadAdd,
317 NumpadSubtract,
318 NumpadDivide,
319 NumpadEnter,
320 NumpadEqual,
321 NumpadClear,
322 Unidentified(u32),
324}
325
326#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
328pub struct Modifiers(u8);
329
330impl Modifiers {
331 pub(crate) const SHIFT: u8 = 1 << 0;
332 pub(crate) const CONTROL: u8 = 1 << 1;
333 pub(crate) const ALT: u8 = 1 << 2;
334 pub(crate) const SUPER: u8 = 1 << 3;
335 pub(crate) const CAPS_LOCK: u8 = 1 << 4;
336 pub(crate) const FUNCTION: u8 = 1 << 5;
337
338 #[cfg_attr(not(target_os = "macos"), allow(dead_code))]
339 pub(crate) const fn from_bits(bits: u8) -> Self {
340 Self(bits)
341 }
342
343 #[must_use]
345 pub const fn shift(self) -> bool {
346 self.0 & Self::SHIFT != 0
347 }
348
349 #[must_use]
351 pub const fn control(self) -> bool {
352 self.0 & Self::CONTROL != 0
353 }
354
355 #[must_use]
357 pub const fn alt(self) -> bool {
358 self.0 & Self::ALT != 0
359 }
360
361 #[must_use]
363 pub const fn super_key(self) -> bool {
364 self.0 & Self::SUPER != 0
365 }
366
367 #[must_use]
369 pub const fn caps_lock(self) -> bool {
370 self.0 & Self::CAPS_LOCK != 0
371 }
372
373 #[must_use]
375 pub const fn function(self) -> bool {
376 self.0 & Self::FUNCTION != 0
377 }
378}
379
380#[derive(Clone, Copy, Debug, Eq, PartialEq)]
382#[allow(missing_docs)]
383pub enum PointerButton {
384 Primary,
385 Secondary,
386 Middle,
387 Other(u16),
388}
389
390#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
396pub enum CursorMode {
397 #[default]
399 Normal,
400 Captured,
403}
404
405#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
413pub enum WindowMode {
414 #[default]
416 Windowed,
417 Fullscreen,
419}
420
421#[cfg_attr(not(any(target_os = "macos", target_os = "linux")), allow(dead_code))]
428pub(crate) fn follow_confirmed_fullscreen(
429 confirmed: &core::cell::Cell<bool>,
430 requested: &core::cell::Cell<bool>,
431 now: bool,
432) {
433 if confirmed.replace(now) != now {
434 requested.set(now);
435 }
436}
437
438#[derive(Clone, Copy, Debug, PartialEq)]
440#[allow(missing_docs)]
441pub enum ScrollDelta {
442 Precise { x: f64, y: f64 },
444 Coarse { x: f64, y: f64 },
446}
447
448#[derive(Clone, Copy, Debug, PartialEq)]
450#[non_exhaustive]
451#[allow(missing_docs)]
452pub enum InputEvent {
453 FocusChanged { focused: bool },
455 Keyboard {
457 key: KeyCode,
458 state: ButtonState,
459 repeat: bool,
460 modifiers: Modifiers,
461 },
462 ModifiersChanged(Modifiers),
464 PointerMoved {
466 position: LogicalPosition,
467 modifiers: Modifiers,
468 },
469 PointerDelta {
472 delta_x: f64,
473 delta_y: f64,
474 modifiers: Modifiers,
475 },
476 PointerButton {
478 button: PointerButton,
479 state: ButtonState,
480 position: LogicalPosition,
481 modifiers: Modifiers,
482 },
483 Scroll {
485 delta: ScrollDelta,
486 position: LogicalPosition,
487 modifiers: Modifiers,
488 },
489}
490
491impl WindowDescriptor {
492 pub fn new(title: impl Into<String>, logical_size: LogicalSize) -> Self {
494 Self {
495 title: title.into(),
496 logical_size,
497 }
498 }
499
500 #[must_use]
502 pub fn title(&self) -> &str {
503 &self.title
504 }
505
506 #[must_use]
508 pub const fn logical_size(&self) -> LogicalSize {
509 self.logical_size
510 }
511}
512
513#[derive(Clone, Copy, Debug, PartialEq)]
515#[non_exhaustive]
516pub enum WindowEvent {
517 MetricsChanged(WindowMetrics),
519 RenderingSuspended,
521 RenderingResumed(WindowMetrics),
523 RedrawRequested(WindowMetrics),
525 CloseRequested,
527 Input(InputEvent),
529}
530
531#[derive(Clone, Copy, Debug, Eq, PartialEq)]
533pub enum PumpStatus {
534 Continue,
536 Exit,
538}
539
540#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
541impl Application {
542 pub fn wait_for_first_metrics(
552 &mut self,
553 window: &Window,
554 ) -> Result<WindowMetrics, PlatformError> {
555 loop {
556 if let Some(metrics) = window.rendering_metrics() {
557 return Ok(metrics);
558 }
559 let mut first = None;
560 let status = self.pump_events(window, |event| {
561 if let WindowEvent::RenderingResumed(metrics)
562 | WindowEvent::RedrawRequested(metrics) = event
563 {
564 first = Some(metrics);
565 }
566 Ok::<(), PlatformError>(())
567 })?;
568 if let Some(metrics) = first {
569 return Ok(metrics);
570 }
571 if status == PumpStatus::Exit {
572 return Err(PlatformError::lifecycle(
573 "window closed before drawable metrics became available",
574 ));
575 }
576 }
577 }
578}
579
580#[derive(Clone, Copy, Debug, Eq, PartialEq)]
582#[non_exhaustive]
583pub enum PlatformErrorKind {
584 InvalidRequest,
586 Unsupported,
588 Lifecycle,
590 NativeFailure,
592 Internal,
594}
595
596#[derive(Clone, Debug, Eq, PartialEq)]
598pub struct PlatformError {
599 kind: PlatformErrorKind,
600 message: String,
601}
602
603impl PlatformError {
604 #[cfg_attr(
605 not(any(target_os = "macos", target_os = "linux", target_os = "windows")),
606 allow(dead_code)
607 )]
608 pub(crate) fn new(message: impl Into<String>) -> Self {
609 Self::with_kind(PlatformErrorKind::NativeFailure, message)
610 }
611
612 pub(crate) fn with_kind(kind: PlatformErrorKind, message: impl Into<String>) -> Self {
613 Self {
614 kind,
615 message: message.into(),
616 }
617 }
618
619 pub(crate) fn invalid_request(message: impl Into<String>) -> Self {
620 Self::with_kind(PlatformErrorKind::InvalidRequest, message)
621 }
622
623 pub(crate) fn lifecycle(message: impl Into<String>) -> Self {
624 Self::with_kind(PlatformErrorKind::Lifecycle, message)
625 }
626
627 #[must_use]
629 pub const fn kind(&self) -> PlatformErrorKind {
630 self.kind
631 }
632
633 #[must_use]
635 pub fn message(&self) -> &str {
636 &self.message
637 }
638}
639
640impl fmt::Display for PlatformError {
641 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
642 formatter.write_str(&self.message)
643 }
644}
645
646impl std::error::Error for PlatformError {}
647
648#[cfg(test)]
649mod tests {
650 use core::cell::Cell;
651
652 use super::{
653 LogicalSize, PhysicalExtent, PlatformError, PlatformErrorKind, WindowDescriptor,
654 WindowRevision, follow_confirmed_fullscreen,
655 };
656
657 #[test]
658 fn confirmed_fullscreen_transitions_drag_the_requested_mode() {
659 let confirmed = Cell::new(false);
660 let requested = Cell::new(true);
661
662 follow_confirmed_fullscreen(&confirmed, &requested, false);
664 assert!(requested.get());
665
666 follow_confirmed_fullscreen(&confirmed, &requested, true);
668 assert!(requested.get());
669
670 follow_confirmed_fullscreen(&confirmed, &requested, false);
672 assert!(!requested.get());
673
674 follow_confirmed_fullscreen(&confirmed, &requested, true);
676 assert!(requested.get());
677 }
678
679 #[test]
680 fn platform_errors_preserve_kind_and_message() {
681 let error = PlatformError::invalid_request("bad window request");
682 assert_eq!(error.kind(), PlatformErrorKind::InvalidRequest);
683 assert_eq!(error.message(), "bad window request");
684 assert_eq!(error.to_string(), "bad window request");
685 }
686
687 #[test]
688 fn empty_extent_requires_a_zero_dimension() {
689 assert!(PhysicalExtent::new(0, 9).is_empty());
690 assert!(PhysicalExtent::new(7, 0).is_empty());
691 assert!(!PhysicalExtent::new(7, 9).is_empty());
692 }
693
694 #[test]
695 fn window_revisions_are_monotonic() {
696 let initial = WindowRevision::INITIAL;
697 assert_eq!(initial.get(), 1);
698 assert_eq!(initial.next().get(), 2);
699 }
700
701 #[test]
702 fn window_descriptor_preserves_game_intent() {
703 let descriptor = WindowDescriptor::new("Mulciber", LogicalSize::new(960, 540));
704 assert_eq!(descriptor.title(), "Mulciber");
705 assert_eq!(descriptor.logical_size(), LogicalSize::new(960, 540));
706 }
707}