use core::fmt;
#[cfg(target_os = "macos")]
mod macos;
#[cfg(target_os = "macos")]
#[doc(hidden)]
pub use macos::integration;
#[cfg(target_os = "macos")]
pub use macos::{Application, SurfaceTarget, Window};
#[cfg(target_os = "linux")]
mod linux;
#[cfg(target_os = "linux")]
#[doc(hidden)]
pub use linux::integration;
#[cfg(target_os = "linux")]
pub use linux::{Application, SurfaceTarget, Window};
#[cfg(target_os = "windows")]
mod win32;
#[cfg(target_os = "windows")]
#[doc(hidden)]
pub use win32::integration;
#[cfg(target_os = "windows")]
pub use win32::{Application, SurfaceTarget, Window};
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct LogicalSize {
width: u32,
height: u32,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct LogicalPosition {
x: f64,
y: f64,
}
impl LogicalPosition {
#[must_use]
pub const fn new(x: f64, y: f64) -> Self {
Self { x, y }
}
#[must_use]
pub const fn x(self) -> f64 {
self.x
}
#[must_use]
pub const fn y(self) -> f64 {
self.y
}
}
impl LogicalSize {
#[must_use]
pub const fn new(width: u32, height: u32) -> Self {
Self { width, height }
}
#[must_use]
pub const fn width(self) -> u32 {
self.width
}
#[must_use]
pub const fn height(self) -> u32 {
self.height
}
#[must_use]
pub const fn is_empty(self) -> bool {
self.width == 0 || self.height == 0
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct PhysicalExtent {
width: u32,
height: u32,
}
impl PhysicalExtent {
#[must_use]
pub const fn new(width: u32, height: u32) -> Self {
Self { width, height }
}
#[must_use]
pub const fn width(self) -> u32 {
self.width
}
#[must_use]
pub const fn height(self) -> u32 {
self.height
}
#[must_use]
pub const fn is_empty(self) -> bool {
self.width == 0 || self.height == 0
}
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
pub struct WindowRevision(u64);
impl WindowRevision {
#[cfg_attr(
not(any(target_os = "macos", target_os = "linux", target_os = "windows")),
allow(dead_code)
)]
pub(crate) const INITIAL: Self = Self(1);
#[cfg_attr(
not(any(target_os = "macos", target_os = "linux", target_os = "windows")),
allow(dead_code)
)]
pub(crate) const fn next(self) -> Self {
Self(self.0.saturating_add(1))
}
#[must_use]
pub const fn get(self) -> u64 {
self.0
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct WindowMetrics {
extent: PhysicalExtent,
scale_factor: f64,
revision: WindowRevision,
}
impl WindowMetrics {
#[cfg_attr(
not(any(target_os = "macos", target_os = "linux", target_os = "windows")),
allow(dead_code)
)]
pub(crate) const fn new(
extent: PhysicalExtent,
scale_factor: f64,
revision: WindowRevision,
) -> Self {
Self {
extent,
scale_factor,
revision,
}
}
#[must_use]
pub const fn extent(self) -> PhysicalExtent {
self.extent
}
#[must_use]
pub const fn scale_factor(self) -> f64 {
self.scale_factor
}
#[must_use]
pub const fn revision(self) -> WindowRevision {
self.revision
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct WindowDescriptor {
title: String,
logical_size: LogicalSize,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ButtonState {
Pressed,
Released,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum KeyCode {
KeyA,
KeyB,
KeyC,
KeyD,
KeyE,
KeyF,
KeyG,
KeyH,
KeyI,
KeyJ,
KeyK,
KeyL,
KeyM,
KeyN,
KeyO,
KeyP,
KeyQ,
KeyR,
KeyS,
KeyT,
KeyU,
KeyV,
KeyW,
KeyX,
KeyY,
KeyZ,
Digit0,
Digit1,
Digit2,
Digit3,
Digit4,
Digit5,
Digit6,
Digit7,
Digit8,
Digit9,
Escape,
Space,
Enter,
Tab,
Backspace,
Delete,
Insert,
Home,
End,
PageUp,
PageDown,
ArrowLeft,
ArrowRight,
ArrowUp,
ArrowDown,
Minus,
Equal,
BracketLeft,
BracketRight,
Backslash,
Semicolon,
Quote,
Backquote,
Comma,
Period,
Slash,
F1,
F2,
F3,
F4,
F5,
F6,
F7,
F8,
F9,
F10,
F11,
F12,
F13,
F14,
F15,
F16,
F17,
F18,
F19,
F20,
Numpad0,
Numpad1,
Numpad2,
Numpad3,
Numpad4,
Numpad5,
Numpad6,
Numpad7,
Numpad8,
Numpad9,
NumpadDecimal,
NumpadMultiply,
NumpadAdd,
NumpadSubtract,
NumpadDivide,
NumpadEnter,
NumpadEqual,
NumpadClear,
Unidentified(u32),
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct Modifiers(u8);
impl Modifiers {
pub(crate) const SHIFT: u8 = 1 << 0;
pub(crate) const CONTROL: u8 = 1 << 1;
pub(crate) const ALT: u8 = 1 << 2;
pub(crate) const SUPER: u8 = 1 << 3;
pub(crate) const CAPS_LOCK: u8 = 1 << 4;
pub(crate) const FUNCTION: u8 = 1 << 5;
#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
pub(crate) const fn from_bits(bits: u8) -> Self {
Self(bits)
}
#[must_use]
pub const fn shift(self) -> bool {
self.0 & Self::SHIFT != 0
}
#[must_use]
pub const fn control(self) -> bool {
self.0 & Self::CONTROL != 0
}
#[must_use]
pub const fn alt(self) -> bool {
self.0 & Self::ALT != 0
}
#[must_use]
pub const fn super_key(self) -> bool {
self.0 & Self::SUPER != 0
}
#[must_use]
pub const fn caps_lock(self) -> bool {
self.0 & Self::CAPS_LOCK != 0
}
#[must_use]
pub const fn function(self) -> bool {
self.0 & Self::FUNCTION != 0
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[allow(missing_docs)]
pub enum PointerButton {
Primary,
Secondary,
Middle,
Other(u16),
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum CursorMode {
#[default]
Normal,
Captured,
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[allow(missing_docs)]
pub enum ScrollDelta {
Precise { x: f64, y: f64 },
Coarse { x: f64, y: f64 },
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
#[allow(missing_docs)]
pub enum InputEvent {
FocusChanged { focused: bool },
Keyboard {
key: KeyCode,
state: ButtonState,
repeat: bool,
modifiers: Modifiers,
},
ModifiersChanged(Modifiers),
PointerMoved {
position: LogicalPosition,
modifiers: Modifiers,
},
PointerDelta {
delta_x: f64,
delta_y: f64,
modifiers: Modifiers,
},
PointerButton {
button: PointerButton,
state: ButtonState,
position: LogicalPosition,
modifiers: Modifiers,
},
Scroll {
delta: ScrollDelta,
position: LogicalPosition,
modifiers: Modifiers,
},
}
impl WindowDescriptor {
pub fn new(title: impl Into<String>, logical_size: LogicalSize) -> Self {
Self {
title: title.into(),
logical_size,
}
}
#[must_use]
pub fn title(&self) -> &str {
&self.title
}
#[must_use]
pub const fn logical_size(&self) -> LogicalSize {
self.logical_size
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[non_exhaustive]
pub enum WindowEvent {
MetricsChanged(WindowMetrics),
RenderingSuspended,
RenderingResumed(WindowMetrics),
RedrawRequested(WindowMetrics),
CloseRequested,
Input(InputEvent),
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PumpStatus {
Continue,
Exit,
}
#[cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))]
impl Application {
pub fn wait_for_first_metrics(
&mut self,
window: &Window,
) -> Result<WindowMetrics, PlatformError> {
loop {
if let Some(metrics) = window.rendering_metrics() {
return Ok(metrics);
}
let mut first = None;
let status = self.pump_events(window, |event| {
if let WindowEvent::RenderingResumed(metrics)
| WindowEvent::RedrawRequested(metrics) = event
{
first = Some(metrics);
}
Ok::<(), PlatformError>(())
})?;
if let Some(metrics) = first {
return Ok(metrics);
}
if status == PumpStatus::Exit {
return Err(PlatformError::lifecycle(
"window closed before drawable metrics became available",
));
}
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum PlatformErrorKind {
InvalidRequest,
Unsupported,
Lifecycle,
NativeFailure,
Internal,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PlatformError {
kind: PlatformErrorKind,
message: String,
}
impl PlatformError {
#[cfg_attr(
not(any(target_os = "macos", target_os = "linux", target_os = "windows")),
allow(dead_code)
)]
pub(crate) fn new(message: impl Into<String>) -> Self {
Self::with_kind(PlatformErrorKind::NativeFailure, message)
}
pub(crate) fn with_kind(kind: PlatformErrorKind, message: impl Into<String>) -> Self {
Self {
kind,
message: message.into(),
}
}
pub(crate) fn invalid_request(message: impl Into<String>) -> Self {
Self::with_kind(PlatformErrorKind::InvalidRequest, message)
}
pub(crate) fn lifecycle(message: impl Into<String>) -> Self {
Self::with_kind(PlatformErrorKind::Lifecycle, message)
}
#[must_use]
pub const fn kind(&self) -> PlatformErrorKind {
self.kind
}
#[must_use]
pub fn message(&self) -> &str {
&self.message
}
}
impl fmt::Display for PlatformError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&self.message)
}
}
impl std::error::Error for PlatformError {}
#[cfg(test)]
mod tests {
use super::{
LogicalSize, PhysicalExtent, PlatformError, PlatformErrorKind, WindowDescriptor,
WindowRevision,
};
#[test]
fn platform_errors_preserve_kind_and_message() {
let error = PlatformError::invalid_request("bad window request");
assert_eq!(error.kind(), PlatformErrorKind::InvalidRequest);
assert_eq!(error.message(), "bad window request");
assert_eq!(error.to_string(), "bad window request");
}
#[test]
fn empty_extent_requires_a_zero_dimension() {
assert!(PhysicalExtent::new(0, 9).is_empty());
assert!(PhysicalExtent::new(7, 0).is_empty());
assert!(!PhysicalExtent::new(7, 9).is_empty());
}
#[test]
fn window_revisions_are_monotonic() {
let initial = WindowRevision::INITIAL;
assert_eq!(initial.get(), 1);
assert_eq!(initial.next().get(), 2);
}
#[test]
fn window_descriptor_preserves_game_intent() {
let descriptor = WindowDescriptor::new("Mulciber", LogicalSize::new(960, 540));
assert_eq!(descriptor.title(), "Mulciber");
assert_eq!(descriptor.logical_size(), LogicalSize::new(960, 540));
}
}