summaryrefslogtreecommitdiff
path: root/engine/src/windowing
diff options
context:
space:
mode:
Diffstat (limited to 'engine/src/windowing')
-rw-r--r--engine/src/windowing/dpi.rs217
-rw-r--r--engine/src/windowing/keyboard.rs826
-rw-r--r--engine/src/windowing/monitor.rs50
-rw-r--r--engine/src/windowing/mouse.rs197
-rw-r--r--engine/src/windowing/window.rs291
-rw-r--r--engine/src/windowing/window/platform.rs12
6 files changed, 1593 insertions, 0 deletions
diff --git a/engine/src/windowing/dpi.rs b/engine/src/windowing/dpi.rs
new file mode 100644
index 0000000..e3b1be1
--- /dev/null
+++ b/engine/src/windowing/dpi.rs
@@ -0,0 +1,217 @@
+use crate::reflection::Reflection;
+
+macro_rules! gen_struct_from_to_impls {
+ ($struct: ident, fields=($($field: ident),*)) => {
+ impl<Pixel> From<winit::dpi::$struct<Pixel>> for $struct<Pixel>
+ {
+ fn from(source: winit::dpi::$struct<Pixel>) -> Self
+ {
+ Self {
+ $($field: source.$field),*
+ }
+ }
+ }
+
+ impl<Pixel> From<$struct<Pixel>> for winit::dpi::$struct<Pixel>
+ {
+ fn from(source: $struct<Pixel>) -> Self
+ {
+ Self {
+ $($field: source.$field),*
+ }
+ }
+ }
+ };
+}
+
+macro_rules! gen_enum_from_to_impls {
+ ($enum: ident, variants=($($variant: ident),*)) => {
+ impl From<winit::dpi::$enum> for $enum
+ {
+ fn from(source: winit::dpi::$enum) -> Self
+ {
+ match source {
+ $(winit::dpi::$enum::$variant(pos) => Self::$variant(pos.into())),*
+ }
+ }
+ }
+
+ impl From<$enum> for winit::dpi::$enum
+ {
+ fn from(source: $enum) -> Self
+ {
+ match source {
+ $($enum::$variant(pos) => Self::$variant(pos.into())),*
+ }
+ }
+ }
+ };
+}
+
+#[derive(Debug, Default, Clone, Copy, PartialEq, Reflection)]
+#[reflection(impl_with_generics(<u32>, <i32>, <f32>, <f64>))]
+pub struct PhysicalPosition<Pixel>
+{
+ pub x: Pixel,
+ pub y: Pixel,
+}
+
+impl<Pixel> PhysicalPosition<Pixel>
+{
+ pub fn to_logical<LogicalPixel>(
+ &self,
+ scale_factor: f64,
+ ) -> LogicalPosition<LogicalPixel>
+ where
+ Pixel: Into<f64> + Clone,
+ LogicalPixel: From<f64>,
+ {
+ let x = self.x.clone().into();
+ let y = self.y.clone().into();
+
+ LogicalPosition {
+ x: (x / scale_factor).into(),
+ y: (y / scale_factor).into(),
+ }
+ }
+
+ pub fn try_convert_from<SourcePixel>(
+ source: PhysicalPosition<SourcePixel>,
+ ) -> Result<Self, Pixel::Error>
+ where
+ Pixel: TryFrom<SourcePixel>,
+ {
+ Ok(Self {
+ x: source.x.try_into()?,
+ y: source.y.try_into()?,
+ })
+ }
+}
+
+gen_struct_from_to_impls!(PhysicalPosition, fields = (x, y));
+
+#[derive(Debug, Default, Clone, Copy, PartialEq, Reflection)]
+#[reflection(impl_with_generics(<f64>))]
+pub struct LogicalPosition<Pixel>
+{
+ pub x: Pixel,
+ pub y: Pixel,
+}
+
+impl<Pixel> LogicalPosition<Pixel>
+{
+ pub fn try_convert_from<SourcePixel>(
+ source: LogicalPosition<SourcePixel>,
+ ) -> Result<Self, Pixel::Error>
+ where
+ Pixel: TryFrom<SourcePixel>,
+ {
+ Ok(Self {
+ x: source.x.try_into()?,
+ y: source.y.try_into()?,
+ })
+ }
+}
+
+gen_struct_from_to_impls!(LogicalPosition, fields = (x, y));
+
+#[derive(Debug, Clone, Copy, PartialEq, Reflection)]
+pub enum Position
+{
+ Physical(PhysicalPosition<i32>),
+ Logical(LogicalPosition<f64>),
+}
+
+impl Default for Position
+{
+ fn default() -> Self
+ {
+ Self::Logical(LogicalPosition::default())
+ }
+}
+
+gen_enum_from_to_impls!(Position, variants = (Physical, Logical));
+
+#[derive(Debug, Default, Clone, Copy, PartialEq, Reflection)]
+#[reflection(impl_with_generics(<u32>))]
+pub struct PhysicalSize<Pixel>
+{
+ pub width: Pixel,
+ pub height: Pixel,
+}
+
+impl<Pixel> PhysicalSize<Pixel>
+{
+ pub fn to_logical<LogicalPixel>(&self, scale_factor: f64) -> LogicalSize<LogicalPixel>
+ where
+ Pixel: Into<f64> + Clone,
+ LogicalPixel: From<f64>,
+ {
+ let width = self.width.clone().into();
+ let height = self.height.clone().into();
+
+ LogicalSize {
+ width: (width / scale_factor).into(),
+ height: (height / scale_factor).into(),
+ }
+ }
+}
+
+impl<Pixel> PhysicalSize<Pixel>
+{
+ pub fn try_convert_from<SourcePixel>(
+ source: PhysicalSize<SourcePixel>,
+ ) -> Result<Self, Pixel::Error>
+ where
+ Pixel: TryFrom<SourcePixel>,
+ {
+ Ok(Self {
+ width: source.width.try_into()?,
+ height: source.height.try_into()?,
+ })
+ }
+}
+
+gen_struct_from_to_impls!(PhysicalSize, fields = (width, height));
+
+#[derive(Debug, Default, Clone, Copy, PartialEq, Reflection)]
+#[reflection(impl_with_generics(<f64>))]
+pub struct LogicalSize<Pixel>
+{
+ pub width: Pixel,
+ pub height: Pixel,
+}
+
+impl<Pixel> LogicalSize<Pixel>
+{
+ pub fn try_convert_from<SourcePixel>(
+ source: LogicalSize<SourcePixel>,
+ ) -> Result<Self, Pixel::Error>
+ where
+ Pixel: TryFrom<SourcePixel>,
+ {
+ Ok(Self {
+ width: source.width.try_into()?,
+ height: source.height.try_into()?,
+ })
+ }
+}
+
+gen_struct_from_to_impls!(LogicalSize, fields = (width, height));
+
+#[derive(Debug, Clone, Copy, PartialEq, Reflection)]
+pub enum Size
+{
+ Physical(PhysicalSize<u32>),
+ Logical(LogicalSize<f64>),
+}
+
+impl Default for Size
+{
+ fn default() -> Self
+ {
+ Self::Logical(LogicalSize::default())
+ }
+}
+
+gen_enum_from_to_impls!(Size, variants = (Physical, Logical));
diff --git a/engine/src/windowing/keyboard.rs b/engine/src/windowing/keyboard.rs
new file mode 100644
index 0000000..dffa95e
--- /dev/null
+++ b/engine/src/windowing/keyboard.rs
@@ -0,0 +1,826 @@
+use util_macros::VariantArr;
+
+use crate::ecs::Sole;
+use crate::util::BitArray;
+
+#[derive(Debug, Default, Sole)]
+pub struct Keyboard
+{
+ keys: BitArray<{ (Key::KEYS.len() * BITS_PER_KEY).div_ceil(8) }, BITS_PER_KEY>,
+ text_keys: String,
+}
+
+impl Keyboard
+{
+ /// Returns whether the given key was just pressed this frame. This function will
+ /// return `false` if the key was also pressed the previous frame.
+ pub fn just_pressed(&self, key: Key) -> bool
+ {
+ self.get_key_state(key) == KeyState::Pressed
+ && self.get_prev_key_state(key) == KeyState::Released
+ }
+
+ /// Returns whether the given key was just released this frame. This function will
+ /// return `false` if the key was also released the previous frame.
+ pub fn just_released(&self, key: Key) -> bool
+ {
+ self.get_key_state(key) == KeyState::Released
+ && self.get_prev_key_state(key) == KeyState::Pressed
+ }
+
+ /// Returns whether the given key is currently pressed.
+ pub fn pressed(&self, key: Key) -> bool
+ {
+ self.get_key_state(key) == KeyState::Pressed
+ }
+
+ /// Returns whether the given key is currently released.
+ pub fn released(&self, key: Key) -> bool
+ {
+ self.get_key_state(key) == KeyState::Released
+ }
+
+ /// Returns a iterator of key & key state pairs where each of the keys have a
+ /// different state than they had the previous frame.
+ pub fn new_key_states(&self) -> impl Iterator<Item = (Key, KeyState)> + use<'_>
+ {
+ self.keys.iter_occupied().filter_map(|(key_index, bits)| {
+ if bits == KEY_CURR_PRESSED_BITS | KEY_PREV_PRESSED_BITS {
+ return None;
+ }
+
+ let key = Key::KEYS[key_index];
+
+ let curr_state = match bits & KEY_CURR_PRESSED_BITS {
+ KEY_CURR_PRESSED_BITS => KeyState::Pressed,
+ 0 => KeyState::Released,
+ _ => unreachable!(),
+ };
+
+ Some((key, curr_state))
+ })
+ }
+
+ #[must_use]
+ pub fn get_key_state(&self, key: Key) -> KeyState
+ {
+ let bits = self.keys.get(key as usize);
+
+ let state = match bits & KEY_CURR_PRESSED_BITS {
+ KEY_CURR_PRESSED_BITS => KeyState::Pressed,
+ 0 => KeyState::Released,
+ _ => unreachable!(),
+ };
+
+ state
+ }
+
+ #[must_use]
+ pub fn get_prev_key_state(&self, key: Key) -> KeyState
+ {
+ let bits = self.keys.get(key as usize);
+
+ let state = match bits & KEY_PREV_PRESSED_BITS {
+ KEY_PREV_PRESSED_BITS => KeyState::Pressed,
+ 0 => KeyState::Released,
+ _ => unreachable!(),
+ };
+
+ state
+ }
+
+ pub fn text_keys(&self) -> &str
+ {
+ &self.text_keys
+ }
+
+ pub fn set_key_state(&mut self, key: Key, key_state: KeyState)
+ {
+ self.keys.clear_and_set(
+ key as usize,
+ match key_state {
+ KeyState::Pressed => KEY_CURR_PRESSED_BITS,
+ KeyState::Released => 0,
+ },
+ KEY_CURR_PRESSED_BITS,
+ );
+ }
+
+ pub fn make_key_states_previous(&mut self)
+ {
+ for byte in self.keys.bytes_mut() {
+ *byte = (*byte >> 1) & 0b01010101 | *byte & 0b10101010;
+ }
+ }
+
+ pub fn set_text_keys(&mut self, text_keys: impl IntoIterator<Item = char>)
+ {
+ self.text_keys.clear();
+ self.text_keys.extend(text_keys);
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, VariantArr)]
+#[variant_arr(name = KEYS)]
+#[non_exhaustive]
+pub enum Key
+{
+ /// <kbd>`</kbd> on a US keyboard. This is also called a backtick or grave.
+ /// This is the <kbd>半角</kbd>/<kbd>全角</kbd>/<kbd>漢字</kbd>
+ /// (hankaku/zenkaku/kanji) key on Japanese keyboards
+ Backquote,
+ /// Used for both the US <kbd>\\</kbd> (on the 101-key layout) and also for the key
+ /// located between the <kbd>"</kbd> and <kbd>Enter</kbd> keys on row C of the 102-,
+ /// 104- and 106-key layouts.
+ /// Labeled <kbd>#</kbd> on a UK (102) keyboard.
+ Backslash,
+ /// <kbd>[</kbd> on a US keyboard.
+ BracketLeft,
+ /// <kbd>]</kbd> on a US keyboard.
+ BracketRight,
+ /// <kbd>,</kbd> on a US keyboard.
+ Comma,
+ /// <kbd>0</kbd> on a US keyboard.
+ Digit0,
+ /// <kbd>1</kbd> on a US keyboard.
+ Digit1,
+ /// <kbd>2</kbd> on a US keyboard.
+ Digit2,
+ /// <kbd>3</kbd> on a US keyboard.
+ Digit3,
+ /// <kbd>4</kbd> on a US keyboard.
+ Digit4,
+ /// <kbd>5</kbd> on a US keyboard.
+ Digit5,
+ /// <kbd>6</kbd> on a US keyboard.
+ Digit6,
+ /// <kbd>7</kbd> on a US keyboard.
+ Digit7,
+ /// <kbd>8</kbd> on a US keyboard.
+ Digit8,
+ /// <kbd>9</kbd> on a US keyboard.
+ Digit9,
+ /// <kbd>=</kbd> on a US keyboard.
+ Equal,
+ /// Located between the left <kbd>Shift</kbd> and <kbd>Z</kbd> keys.
+ /// Labeled <kbd>\\</kbd> on a UK keyboard.
+ IntlBackslash,
+ /// Located between the <kbd>/</kbd> and right <kbd>Shift</kbd> keys.
+ /// Labeled <kbd>\\</kbd> (ro) on a Japanese keyboard.
+ IntlRo,
+ /// Located between the <kbd>=</kbd> and <kbd>Backspace</kbd> keys.
+ /// Labeled <kbd>¥</kbd> (yen) on a Japanese keyboard. <kbd>\\</kbd> on a
+ /// Russian keyboard.
+ IntlYen,
+ /// <kbd>a</kbd> on a US keyboard.
+ /// Labeled <kbd>q</kbd> on an AZERTY (e.g., French) keyboard.
+ A,
+ /// <kbd>b</kbd> on a US keyboard.
+ B,
+ /// <kbd>c</kbd> on a US keyboard.
+ C,
+ /// <kbd>d</kbd> on a US keyboard.
+ D,
+ /// <kbd>e</kbd> on a US keyboard.
+ E,
+ /// <kbd>f</kbd> on a US keyboard.
+ F,
+ /// <kbd>g</kbd> on a US keyboard.
+ G,
+ /// <kbd>h</kbd> on a US keyboard.
+ H,
+ /// <kbd>i</kbd> on a US keyboard.
+ I,
+ /// <kbd>j</kbd> on a US keyboard.
+ J,
+ /// <kbd>k</kbd> on a US keyboard.
+ K,
+ /// <kbd>l</kbd> on a US keyboard.
+ L,
+ /// <kbd>m</kbd> on a US keyboard.
+ M,
+ /// <kbd>n</kbd> on a US keyboard.
+ N,
+ /// <kbd>o</kbd> on a US keyboard.
+ O,
+ /// <kbd>p</kbd> on a US keyboard.
+ P,
+ /// <kbd>q</kbd> on a US keyboard.
+ /// Labeled <kbd>a</kbd> on an AZERTY (e.g., French) keyboard.
+ Q,
+ /// <kbd>r</kbd> on a US keyboard.
+ R,
+ /// <kbd>s</kbd> on a US keyboard.
+ S,
+ /// <kbd>t</kbd> on a US keyboard.
+ T,
+ /// <kbd>u</kbd> on a US keyboard.
+ U,
+ /// <kbd>v</kbd> on a US keyboard.
+ V,
+ /// <kbd>w</kbd> on a US keyboard.
+ /// Labeled <kbd>z</kbd> on an AZERTY (e.g., French) keyboard.
+ W,
+ /// <kbd>x</kbd> on a US keyboard.
+ X,
+ /// <kbd>y</kbd> on a US keyboard.
+ /// Labeled <kbd>z</kbd> on a QWERTZ (e.g., German) keyboard.
+ Y,
+ /// <kbd>z</kbd> on a US keyboard.
+ /// Labeled <kbd>w</kbd> on an AZERTY (e.g., French) keyboard, and <kbd>y</kbd> on a
+ /// QWERTZ (e.g., German) keyboard.
+ Z,
+ /// <kbd>-</kbd> on a US keyboard.
+ Minus,
+ /// <kbd>.</kbd> on a US keyboard.
+ Period,
+ /// <kbd>'</kbd> on a US keyboard.
+ Quote,
+ /// <kbd>;</kbd> on a US keyboard.
+ Semicolon,
+ /// <kbd>/</kbd> on a US keyboard.
+ Slash,
+ /// <kbd>Alt</kbd>, <kbd>Option</kbd>, or <kbd>⌥</kbd>.
+ AltLeft,
+ /// <kbd>Alt</kbd>, <kbd>Option</kbd>, or <kbd>⌥</kbd>.
+ /// This is labeled <kbd>AltGr</kbd> on many keyboard layouts.
+ AltRight,
+ /// <kbd>Backspace</kbd> or <kbd>⌫</kbd>.
+ /// Labeled <kbd>Delete</kbd> on Apple keyboards.
+ Backspace,
+ /// <kbd>CapsLock</kbd> or <kbd>⇪</kbd>
+ CapsLock,
+ /// The application context menu key, which is typically found between the right
+ /// <kbd>Super</kbd> key and the right <kbd>Control</kbd> key.
+ ContextMenu,
+ /// <kbd>Control</kbd> or <kbd>⌃</kbd>
+ ControlLeft,
+ /// <kbd>Control</kbd> or <kbd>⌃</kbd>
+ ControlRight,
+ /// <kbd>Enter</kbd> or <kbd>↵</kbd>. Labeled <kbd>Return</kbd> on Apple keyboards.
+ Enter,
+ /// The Windows, <kbd>⌘</kbd>, <kbd>Command</kbd>, or other OS symbol key.
+ SuperLeft,
+ /// The Windows, <kbd>⌘</kbd>, <kbd>Command</kbd>, or other OS symbol key.
+ SuperRight,
+ /// <kbd>Shift</kbd> or <kbd>⇧</kbd>
+ ShiftLeft,
+ /// <kbd>Shift</kbd> or <kbd>⇧</kbd>
+ ShiftRight,
+ /// <kbd> </kbd> (space)
+ Space,
+ /// <kbd>Tab</kbd> or <kbd>⇥</kbd>
+ Tab,
+ /// Japanese: <kbd>変</kbd> (henkan)
+ Convert,
+ /// Japanese: <kbd>カタカナ</kbd>/<kbd>ひらがな</kbd>/<kbd>ローマ字</kbd>
+ /// (katakana/hiragana/romaji)
+ KanaMode,
+ /// Korean: HangulMode <kbd>한/영</kbd> (han/yeong)
+ ///
+ /// Japanese (Mac keyboard): <kbd>か</kbd> (kana)
+ Lang1,
+ /// Korean: Hanja <kbd>한</kbd> (hanja)
+ ///
+ /// Japanese (Mac keyboard): <kbd>英</kbd> (eisu)
+ Lang2,
+ /// Japanese (word-processing keyboard): Katakana
+ Lang3,
+ /// Japanese (word-processing keyboard): Hiragana
+ Lang4,
+ /// Japanese (word-processing keyboard): Zenkaku/Hankaku
+ Lang5,
+ /// Japanese: <kbd>無変換</kbd> (muhenkan)
+ NonConvert,
+ /// <kbd>⌦</kbd>. The forward delete key.
+ /// Note that on Apple keyboards, the key labelled <kbd>Delete</kbd> on the main part
+ /// of the keyboard is encoded as [`Backspace`].
+ ///
+ /// [`Backspace`]: Self::Backspace
+ Delete,
+ /// <kbd>Page Down</kbd>, <kbd>End</kbd>, or <kbd>↘</kbd>
+ End,
+ /// <kbd>Help</kbd>. Not present on standard PC keyboards.
+ Help,
+ /// <kbd>Home</kbd> or <kbd>↖</kbd>
+ Home,
+ /// <kbd>Insert</kbd> or <kbd>Ins</kbd>. Not present on Apple keyboards.
+ Insert,
+ /// <kbd>Page Down</kbd>, <kbd>PgDn</kbd>, or <kbd>⇟</kbd>
+ PageDown,
+ /// <kbd>Page Up</kbd>, <kbd>PgUp</kbd>, or <kbd>⇞</kbd>
+ PageUp,
+ /// <kbd>↓</kbd>
+ ArrowDown,
+ /// <kbd>←</kbd>
+ ArrowLeft,
+ /// <kbd>→</kbd>
+ ArrowRight,
+ /// <kbd>↑</kbd>
+ ArrowUp,
+ /// On the Mac, this is used for the numpad <kbd>Clear</kbd> key.
+ NumLock,
+ /// <kbd>0 Ins</kbd> on a keyboard. <kbd>0</kbd> on a phone or remote control
+ Numpad0,
+ /// <kbd>1 End</kbd> on a keyboard. <kbd>1</kbd> or <kbd>1 QZ</kbd> on a phone or
+ /// remote control
+ Numpad1,
+ /// <kbd>2 ↓</kbd> on a keyboard. <kbd>2 ABC</kbd> on a phone or remote control
+ Numpad2,
+ /// <kbd>3 PgDn</kbd> on a keyboard. <kbd>3 DEF</kbd> on a phone or remote control
+ Numpad3,
+ /// <kbd>4 ←</kbd> on a keyboard. <kbd>4 GHI</kbd> on a phone or remote control
+ Numpad4,
+ /// <kbd>5</kbd> on a keyboard. <kbd>5 JKL</kbd> on a phone or remote control
+ Numpad5,
+ /// <kbd>6 →</kbd> on a keyboard. <kbd>6 MNO</kbd> on a phone or remote control
+ Numpad6,
+ /// <kbd>7 Home</kbd> on a keyboard. <kbd>7 PQRS</kbd> or <kbd>7 PRS</kbd> on a phone
+ /// or remote control
+ Numpad7,
+ /// <kbd>8 ↑</kbd> on a keyboard. <kbd>8 TUV</kbd> on a phone or remote control
+ Numpad8,
+ /// <kbd>9 PgUp</kbd> on a keyboard. <kbd>9 WXYZ</kbd> or <kbd>9 WXY</kbd> on a phone
+ /// or remote control
+ Numpad9,
+ /// <kbd>+</kbd>
+ NumpadAdd,
+ /// Found on the Microsoft Natural Keyboard.
+ NumpadBackspace,
+ /// <kbd>C</kbd> or <kbd>A</kbd> (All Clear). Also for use with numpads that have a
+ /// <kbd>Clear</kbd> key that is separate from the <kbd>NumLock</kbd> key. On the
+ /// Mac, the numpad <kbd>Clear</kbd> key is encoded as [`NumLock`].
+ ///
+ /// [`NumLock`]: Self::NumLock
+ NumpadClear,
+ /// <kbd>C</kbd> (Clear Entry)
+ NumpadClearEntry,
+ /// <kbd>,</kbd> (thousands separator). For locales where the thousands separator
+ /// is a "." (e.g., Brazil), this key may generate a <kbd>.</kbd>.
+ NumpadComma,
+ /// <kbd>. Del</kbd>. For locales where the decimal separator is "," (e.g.,
+ /// Brazil), this key may generate a <kbd>,</kbd>.
+ NumpadDecimal,
+ /// <kbd>/</kbd>
+ NumpadDivide,
+ NumpadEnter,
+ /// <kbd>=</kbd>
+ NumpadEqual,
+ /// <kbd>#</kbd> on a phone or remote control device. This key is typically found
+ /// below the <kbd>9</kbd> key and to the right of the <kbd>0</kbd> key.
+ NumpadHash,
+ /// <kbd>M</kbd> Add current entry to the value stored in memory.
+ NumpadMemoryAdd,
+ /// <kbd>M</kbd> Clear the value stored in memory.
+ NumpadMemoryClear,
+ /// <kbd>M</kbd> Replace the current entry with the value stored in memory.
+ NumpadMemoryRecall,
+ /// <kbd>M</kbd> Replace the value stored in memory with the current entry.
+ NumpadMemoryStore,
+ /// <kbd>M</kbd> Subtract current entry from the value stored in memory.
+ NumpadMemorySubtract,
+ /// <kbd>*</kbd> on a keyboard. For use with numpads that provide mathematical
+ /// operations (<kbd>+</kbd>, <kbd>-</kbd> <kbd>*</kbd> and <kbd>/</kbd>).
+ ///
+ /// Use `NumpadStar` for the <kbd>*</kbd> key on phones and remote controls.
+ NumpadMultiply,
+ /// <kbd>(</kbd> Found on the Microsoft Natural Keyboard.
+ NumpadParenLeft,
+ /// <kbd>)</kbd> Found on the Microsoft Natural Keyboard.
+ NumpadParenRight,
+ /// <kbd>*</kbd> on a phone or remote control device.
+ ///
+ /// This key is typically found below the <kbd>7</kbd> key and to the left of
+ /// the <kbd>0</kbd> key.
+ ///
+ /// Use <kbd>"NumpadMultiply"</kbd> for the <kbd>*</kbd> key on
+ /// numeric keypads.
+ NumpadStar,
+ /// <kbd>-</kbd>
+ NumpadSubtract,
+ /// <kbd>Esc</kbd> or <kbd>⎋</kbd>
+ Escape,
+ /// <kbd>Fn</kbd> This is typically a hardware key that does not generate a separate
+ /// code.
+ Fn,
+ /// <kbd>FLock</kbd> or <kbd>FnLock</kbd>. Function Lock key. Found on the Microsoft
+ /// Natural Keyboard.
+ FnLock,
+ /// <kbd>PrtScr SysRq</kbd> or <kbd>Print Screen</kbd>
+ PrintScreen,
+ /// <kbd>Scroll Lock</kbd>
+ ScrollLock,
+ /// <kbd>Pause Break</kbd>
+ Pause,
+ /// Some laptops place this key to the left of the <kbd>↑</kbd> key.
+ ///
+ /// This also the "back" button (triangle) on Android.
+ BrowserBack,
+ BrowserFavorites,
+ /// Some laptops place this key to the right of the <kbd>↑</kbd> key.
+ BrowserForward,
+ /// The "home" button on Android.
+ BrowserHome,
+ BrowserRefresh,
+ BrowserSearch,
+ BrowserStop,
+ /// <kbd>Eject</kbd> or <kbd>⏏</kbd>. This key is placed in the function section on
+ /// some Apple keyboards.
+ Eject,
+ /// Sometimes labelled <kbd>My Computer</kbd> on the keyboard
+ LaunchApp1,
+ /// Sometimes labelled <kbd>Calculator</kbd> on the keyboard
+ LaunchApp2,
+ LaunchMail,
+ MediaPlayPause,
+ MediaSelect,
+ MediaStop,
+ MediaTrackNext,
+ MediaTrackPrevious,
+ /// This key is placed in the function section on some Apple keyboards, replacing the
+ /// <kbd>Eject</kbd> key.
+ Power,
+ Sleep,
+ AudioVolumeDown,
+ AudioVolumeMute,
+ AudioVolumeUp,
+ WakeUp,
+ // Legacy modifier key. Also called "Super" in certain places.
+ Meta,
+ // Legacy modifier key.
+ Hyper,
+ Turbo,
+ Abort,
+ Resume,
+ Suspend,
+ /// Found on Sun’s USB keyboard.
+ Again,
+ /// Found on Sun’s USB keyboard.
+ Copy,
+ /// Found on Sun’s USB keyboard.
+ Cut,
+ /// Found on Sun’s USB keyboard.
+ Find,
+ /// Found on Sun’s USB keyboard.
+ Open,
+ /// Found on Sun’s USB keyboard.
+ Paste,
+ /// Found on Sun’s USB keyboard.
+ Props,
+ /// Found on Sun’s USB keyboard.
+ Select,
+ /// Found on Sun’s USB keyboard.
+ Undo,
+ /// Use for dedicated <kbd>ひらがな</kbd> key found on some Japanese word processing
+ /// keyboards.
+ Hiragana,
+ /// Use for dedicated <kbd>カタカナ</kbd> key found on some Japanese word processing
+ /// keyboards.
+ Katakana,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F1,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F2,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F3,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F4,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F5,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F6,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F7,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F8,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F9,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F10,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F11,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F12,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F13,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F14,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F15,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F16,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F17,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F18,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F19,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F20,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F21,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F22,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F23,
+ /// General-purpose function key.
+ /// Usually found at the top of the keyboard.
+ F24,
+ /// General-purpose function key.
+ F25,
+ /// General-purpose function key.
+ F26,
+ /// General-purpose function key.
+ F27,
+ /// General-purpose function key.
+ F28,
+ /// General-purpose function key.
+ F29,
+ /// General-purpose function key.
+ F30,
+ /// General-purpose function key.
+ F31,
+ /// General-purpose function key.
+ F32,
+ /// General-purpose function key.
+ F33,
+ /// General-purpose function key.
+ F34,
+ /// General-purpose function key.
+ F35,
+}
+
+impl TryFrom<winit::keyboard::KeyCode> for Key
+{
+ type Error = UnknownKeyCodeError;
+
+ fn try_from(key_code: winit::keyboard::KeyCode) -> Result<Self, Self::Error>
+ {
+ match key_code {
+ winit::keyboard::KeyCode::Backquote => Ok(Self::Backquote),
+ winit::keyboard::KeyCode::Backslash => Ok(Self::Backslash),
+ winit::keyboard::KeyCode::BracketLeft => Ok(Self::BracketLeft),
+ winit::keyboard::KeyCode::BracketRight => Ok(Self::BracketRight),
+ winit::keyboard::KeyCode::Comma => Ok(Self::Comma),
+ winit::keyboard::KeyCode::Digit0 => Ok(Self::Digit0),
+ winit::keyboard::KeyCode::Digit1 => Ok(Self::Digit1),
+ winit::keyboard::KeyCode::Digit2 => Ok(Self::Digit2),
+ winit::keyboard::KeyCode::Digit3 => Ok(Self::Digit3),
+ winit::keyboard::KeyCode::Digit4 => Ok(Self::Digit4),
+ winit::keyboard::KeyCode::Digit5 => Ok(Self::Digit5),
+ winit::keyboard::KeyCode::Digit6 => Ok(Self::Digit6),
+ winit::keyboard::KeyCode::Digit7 => Ok(Self::Digit7),
+ winit::keyboard::KeyCode::Digit8 => Ok(Self::Digit8),
+ winit::keyboard::KeyCode::Digit9 => Ok(Self::Digit9),
+ winit::keyboard::KeyCode::Equal => Ok(Self::Equal),
+ winit::keyboard::KeyCode::IntlBackslash => Ok(Self::IntlBackslash),
+ winit::keyboard::KeyCode::IntlRo => Ok(Self::IntlRo),
+ winit::keyboard::KeyCode::IntlYen => Ok(Self::IntlYen),
+ winit::keyboard::KeyCode::KeyA => Ok(Self::A),
+ winit::keyboard::KeyCode::KeyB => Ok(Self::B),
+ winit::keyboard::KeyCode::KeyC => Ok(Self::C),
+ winit::keyboard::KeyCode::KeyD => Ok(Self::D),
+ winit::keyboard::KeyCode::KeyE => Ok(Self::E),
+ winit::keyboard::KeyCode::KeyF => Ok(Self::F),
+ winit::keyboard::KeyCode::KeyG => Ok(Self::G),
+ winit::keyboard::KeyCode::KeyH => Ok(Self::H),
+ winit::keyboard::KeyCode::KeyI => Ok(Self::I),
+ winit::keyboard::KeyCode::KeyJ => Ok(Self::J),
+ winit::keyboard::KeyCode::KeyK => Ok(Self::K),
+ winit::keyboard::KeyCode::KeyL => Ok(Self::L),
+ winit::keyboard::KeyCode::KeyM => Ok(Self::M),
+ winit::keyboard::KeyCode::KeyN => Ok(Self::N),
+ winit::keyboard::KeyCode::KeyO => Ok(Self::O),
+ winit::keyboard::KeyCode::KeyP => Ok(Self::P),
+ winit::keyboard::KeyCode::KeyQ => Ok(Self::Q),
+ winit::keyboard::KeyCode::KeyR => Ok(Self::R),
+ winit::keyboard::KeyCode::KeyS => Ok(Self::S),
+ winit::keyboard::KeyCode::KeyT => Ok(Self::T),
+ winit::keyboard::KeyCode::KeyU => Ok(Self::U),
+ winit::keyboard::KeyCode::KeyV => Ok(Self::V),
+ winit::keyboard::KeyCode::KeyW => Ok(Self::W),
+ winit::keyboard::KeyCode::KeyX => Ok(Self::X),
+ winit::keyboard::KeyCode::KeyY => Ok(Self::Y),
+ winit::keyboard::KeyCode::KeyZ => Ok(Self::Z),
+ winit::keyboard::KeyCode::Minus => Ok(Self::Minus),
+ winit::keyboard::KeyCode::Period => Ok(Self::Period),
+ winit::keyboard::KeyCode::Quote => Ok(Self::Quote),
+ winit::keyboard::KeyCode::Semicolon => Ok(Self::Semicolon),
+ winit::keyboard::KeyCode::Slash => Ok(Self::Slash),
+ winit::keyboard::KeyCode::AltLeft => Ok(Self::AltLeft),
+ winit::keyboard::KeyCode::AltRight => Ok(Self::AltRight),
+ winit::keyboard::KeyCode::Backspace => Ok(Self::Backspace),
+ winit::keyboard::KeyCode::CapsLock => Ok(Self::CapsLock),
+ winit::keyboard::KeyCode::ContextMenu => Ok(Self::ContextMenu),
+ winit::keyboard::KeyCode::ControlLeft => Ok(Self::ControlLeft),
+ winit::keyboard::KeyCode::ControlRight => Ok(Self::ControlRight),
+ winit::keyboard::KeyCode::Enter => Ok(Self::Enter),
+ winit::keyboard::KeyCode::SuperLeft => Ok(Self::SuperLeft),
+ winit::keyboard::KeyCode::SuperRight => Ok(Self::SuperRight),
+ winit::keyboard::KeyCode::ShiftLeft => Ok(Self::ShiftLeft),
+ winit::keyboard::KeyCode::ShiftRight => Ok(Self::ShiftRight),
+ winit::keyboard::KeyCode::Space => Ok(Self::Space),
+ winit::keyboard::KeyCode::Tab => Ok(Self::Tab),
+ winit::keyboard::KeyCode::Convert => Ok(Self::Convert),
+ winit::keyboard::KeyCode::KanaMode => Ok(Self::KanaMode),
+ winit::keyboard::KeyCode::Lang1 => Ok(Self::Lang1),
+ winit::keyboard::KeyCode::Lang2 => Ok(Self::Lang2),
+ winit::keyboard::KeyCode::Lang3 => Ok(Self::Lang3),
+ winit::keyboard::KeyCode::Lang4 => Ok(Self::Lang4),
+ winit::keyboard::KeyCode::Lang5 => Ok(Self::Lang5),
+ winit::keyboard::KeyCode::NonConvert => Ok(Self::NonConvert),
+ winit::keyboard::KeyCode::Delete => Ok(Self::Delete),
+ winit::keyboard::KeyCode::End => Ok(Self::End),
+ winit::keyboard::KeyCode::Help => Ok(Self::Help),
+ winit::keyboard::KeyCode::Home => Ok(Self::Home),
+ winit::keyboard::KeyCode::Insert => Ok(Self::Insert),
+ winit::keyboard::KeyCode::PageDown => Ok(Self::PageDown),
+ winit::keyboard::KeyCode::PageUp => Ok(Self::PageUp),
+ winit::keyboard::KeyCode::ArrowDown => Ok(Self::ArrowDown),
+ winit::keyboard::KeyCode::ArrowLeft => Ok(Self::ArrowLeft),
+ winit::keyboard::KeyCode::ArrowRight => Ok(Self::ArrowRight),
+ winit::keyboard::KeyCode::ArrowUp => Ok(Self::ArrowUp),
+ winit::keyboard::KeyCode::NumLock => Ok(Self::NumLock),
+ winit::keyboard::KeyCode::Numpad0 => Ok(Self::Numpad0),
+ winit::keyboard::KeyCode::Numpad1 => Ok(Self::Numpad1),
+ winit::keyboard::KeyCode::Numpad2 => Ok(Self::Numpad2),
+ winit::keyboard::KeyCode::Numpad3 => Ok(Self::Numpad3),
+ winit::keyboard::KeyCode::Numpad4 => Ok(Self::Numpad4),
+ winit::keyboard::KeyCode::Numpad5 => Ok(Self::Numpad5),
+ winit::keyboard::KeyCode::Numpad6 => Ok(Self::Numpad6),
+ winit::keyboard::KeyCode::Numpad7 => Ok(Self::Numpad7),
+ winit::keyboard::KeyCode::Numpad8 => Ok(Self::Numpad8),
+ winit::keyboard::KeyCode::Numpad9 => Ok(Self::Numpad9),
+ winit::keyboard::KeyCode::NumpadAdd => Ok(Self::NumpadAdd),
+ winit::keyboard::KeyCode::NumpadBackspace => Ok(Self::NumpadBackspace),
+ winit::keyboard::KeyCode::NumpadClear => Ok(Self::NumpadClear),
+ winit::keyboard::KeyCode::NumpadClearEntry => Ok(Self::NumpadClearEntry),
+ winit::keyboard::KeyCode::NumpadComma => Ok(Self::NumpadComma),
+ winit::keyboard::KeyCode::NumpadDecimal => Ok(Self::NumpadDecimal),
+ winit::keyboard::KeyCode::NumpadDivide => Ok(Self::NumpadDivide),
+ winit::keyboard::KeyCode::NumpadEnter => Ok(Self::NumpadEnter),
+ winit::keyboard::KeyCode::NumpadEqual => Ok(Self::NumpadEqual),
+ winit::keyboard::KeyCode::NumpadHash => Ok(Self::NumpadHash),
+ winit::keyboard::KeyCode::NumpadMemoryAdd => Ok(Self::NumpadMemoryAdd),
+ winit::keyboard::KeyCode::NumpadMemoryClear => Ok(Self::NumpadMemoryClear),
+ winit::keyboard::KeyCode::NumpadMemoryRecall => Ok(Self::NumpadMemoryRecall),
+ winit::keyboard::KeyCode::NumpadMemoryStore => Ok(Self::NumpadMemoryStore),
+ winit::keyboard::KeyCode::NumpadMemorySubtract => {
+ Ok(Self::NumpadMemorySubtract)
+ }
+ winit::keyboard::KeyCode::NumpadMultiply => Ok(Self::NumpadMultiply),
+ winit::keyboard::KeyCode::NumpadParenLeft => Ok(Self::NumpadParenLeft),
+ winit::keyboard::KeyCode::NumpadParenRight => Ok(Self::NumpadParenRight),
+ winit::keyboard::KeyCode::NumpadStar => Ok(Self::NumpadStar),
+ winit::keyboard::KeyCode::NumpadSubtract => Ok(Self::NumpadSubtract),
+ winit::keyboard::KeyCode::Escape => Ok(Self::Escape),
+ winit::keyboard::KeyCode::Fn => Ok(Self::Fn),
+ winit::keyboard::KeyCode::FnLock => Ok(Self::FnLock),
+ winit::keyboard::KeyCode::PrintScreen => Ok(Self::PrintScreen),
+ winit::keyboard::KeyCode::ScrollLock => Ok(Self::ScrollLock),
+ winit::keyboard::KeyCode::Pause => Ok(Self::Pause),
+ winit::keyboard::KeyCode::BrowserBack => Ok(Self::BrowserBack),
+ winit::keyboard::KeyCode::BrowserFavorites => Ok(Self::BrowserFavorites),
+ winit::keyboard::KeyCode::BrowserForward => Ok(Self::BrowserForward),
+ winit::keyboard::KeyCode::BrowserHome => Ok(Self::BrowserHome),
+ winit::keyboard::KeyCode::BrowserRefresh => Ok(Self::BrowserRefresh),
+ winit::keyboard::KeyCode::BrowserSearch => Ok(Self::BrowserSearch),
+ winit::keyboard::KeyCode::BrowserStop => Ok(Self::BrowserStop),
+ winit::keyboard::KeyCode::Eject => Ok(Self::Eject),
+ winit::keyboard::KeyCode::LaunchApp1 => Ok(Self::LaunchApp1),
+ winit::keyboard::KeyCode::LaunchApp2 => Ok(Self::LaunchApp2),
+ winit::keyboard::KeyCode::LaunchMail => Ok(Self::LaunchMail),
+ winit::keyboard::KeyCode::MediaPlayPause => Ok(Self::MediaPlayPause),
+ winit::keyboard::KeyCode::MediaSelect => Ok(Self::MediaSelect),
+ winit::keyboard::KeyCode::MediaStop => Ok(Self::MediaStop),
+ winit::keyboard::KeyCode::MediaTrackNext => Ok(Self::MediaTrackNext),
+ winit::keyboard::KeyCode::MediaTrackPrevious => Ok(Self::MediaTrackPrevious),
+ winit::keyboard::KeyCode::Power => Ok(Self::Power),
+ winit::keyboard::KeyCode::Sleep => Ok(Self::Sleep),
+ winit::keyboard::KeyCode::AudioVolumeDown => Ok(Self::AudioVolumeDown),
+ winit::keyboard::KeyCode::AudioVolumeMute => Ok(Self::AudioVolumeMute),
+ winit::keyboard::KeyCode::AudioVolumeUp => Ok(Self::AudioVolumeUp),
+ winit::keyboard::KeyCode::WakeUp => Ok(Self::WakeUp),
+ winit::keyboard::KeyCode::Meta => Ok(Self::Meta),
+ winit::keyboard::KeyCode::Hyper => Ok(Self::Hyper),
+ winit::keyboard::KeyCode::Turbo => Ok(Self::Turbo),
+ winit::keyboard::KeyCode::Abort => Ok(Self::Abort),
+ winit::keyboard::KeyCode::Resume => Ok(Self::Resume),
+ winit::keyboard::KeyCode::Suspend => Ok(Self::Suspend),
+ winit::keyboard::KeyCode::Again => Ok(Self::Again),
+ winit::keyboard::KeyCode::Copy => Ok(Self::Copy),
+ winit::keyboard::KeyCode::Cut => Ok(Self::Cut),
+ winit::keyboard::KeyCode::Find => Ok(Self::Find),
+ winit::keyboard::KeyCode::Open => Ok(Self::Open),
+ winit::keyboard::KeyCode::Paste => Ok(Self::Paste),
+ winit::keyboard::KeyCode::Props => Ok(Self::Props),
+ winit::keyboard::KeyCode::Select => Ok(Self::Select),
+ winit::keyboard::KeyCode::Undo => Ok(Self::Undo),
+ winit::keyboard::KeyCode::Hiragana => Ok(Self::Hiragana),
+ winit::keyboard::KeyCode::Katakana => Ok(Self::Katakana),
+ winit::keyboard::KeyCode::F1 => Ok(Self::F1),
+ winit::keyboard::KeyCode::F2 => Ok(Self::F2),
+ winit::keyboard::KeyCode::F3 => Ok(Self::F3),
+ winit::keyboard::KeyCode::F4 => Ok(Self::F4),
+ winit::keyboard::KeyCode::F5 => Ok(Self::F5),
+ winit::keyboard::KeyCode::F6 => Ok(Self::F6),
+ winit::keyboard::KeyCode::F7 => Ok(Self::F7),
+ winit::keyboard::KeyCode::F8 => Ok(Self::F8),
+ winit::keyboard::KeyCode::F9 => Ok(Self::F9),
+ winit::keyboard::KeyCode::F10 => Ok(Self::F10),
+ winit::keyboard::KeyCode::F11 => Ok(Self::F11),
+ winit::keyboard::KeyCode::F12 => Ok(Self::F12),
+ winit::keyboard::KeyCode::F13 => Ok(Self::F13),
+ winit::keyboard::KeyCode::F14 => Ok(Self::F14),
+ winit::keyboard::KeyCode::F15 => Ok(Self::F15),
+ winit::keyboard::KeyCode::F16 => Ok(Self::F16),
+ winit::keyboard::KeyCode::F17 => Ok(Self::F17),
+ winit::keyboard::KeyCode::F18 => Ok(Self::F18),
+ winit::keyboard::KeyCode::F19 => Ok(Self::F19),
+ winit::keyboard::KeyCode::F20 => Ok(Self::F20),
+ winit::keyboard::KeyCode::F21 => Ok(Self::F21),
+ winit::keyboard::KeyCode::F22 => Ok(Self::F22),
+ winit::keyboard::KeyCode::F23 => Ok(Self::F23),
+ winit::keyboard::KeyCode::F24 => Ok(Self::F24),
+ winit::keyboard::KeyCode::F25 => Ok(Self::F25),
+ winit::keyboard::KeyCode::F26 => Ok(Self::F26),
+ winit::keyboard::KeyCode::F27 => Ok(Self::F27),
+ winit::keyboard::KeyCode::F28 => Ok(Self::F28),
+ winit::keyboard::KeyCode::F29 => Ok(Self::F29),
+ winit::keyboard::KeyCode::F30 => Ok(Self::F30),
+ winit::keyboard::KeyCode::F31 => Ok(Self::F31),
+ winit::keyboard::KeyCode::F32 => Ok(Self::F32),
+ winit::keyboard::KeyCode::F33 => Ok(Self::F33),
+ winit::keyboard::KeyCode::F34 => Ok(Self::F34),
+ winit::keyboard::KeyCode::F35 => Ok(Self::F35),
+ _ => Err(UnknownKeyCodeError),
+ }
+ }
+}
+
+#[derive(Debug, thiserror::Error)]
+#[error("Unknown key code")]
+pub struct UnknownKeyCodeError;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum KeyState
+{
+ Pressed,
+ Released,
+}
+
+impl KeyState
+{
+ #[must_use]
+ #[inline]
+ pub fn is_pressed(&self) -> bool
+ {
+ matches!(self, Self::Pressed)
+ }
+
+ #[must_use]
+ #[inline]
+ pub fn is_released(&self) -> bool
+ {
+ matches!(self, Self::Released)
+ }
+}
+
+impl From<winit::event::ElementState> for KeyState
+{
+ fn from(element_state: winit::event::ElementState) -> Self
+ {
+ match element_state {
+ winit::event::ElementState::Pressed => Self::Pressed,
+ winit::event::ElementState::Released => Self::Released,
+ }
+ }
+}
+
+const BITS_PER_KEY: usize = 2;
+
+const KEY_CURR_PRESSED_BITS: u8 = 0b10;
+const KEY_PREV_PRESSED_BITS: u8 = 0b01;
diff --git a/engine/src/windowing/monitor.rs b/engine/src/windowing/monitor.rs
new file mode 100644
index 0000000..894448a
--- /dev/null
+++ b/engine/src/windowing/monitor.rs
@@ -0,0 +1,50 @@
+use crate::windowing::dpi::{PhysicalPosition, PhysicalSize};
+
+/// Handle to a monitor that may or may not exist any longer.
+#[derive(Debug, Clone)]
+pub struct Handle
+{
+ inner: winit::monitor::MonitorHandle,
+}
+
+impl Handle
+{
+ /// Returns a human-readable name of the monitor.
+ #[inline]
+ pub fn name(&self) -> Option<String>
+ {
+ self.inner.name()
+ }
+
+ /// Returns the monitor's resolution.
+ #[inline]
+ pub fn size(&self) -> PhysicalSize<u32>
+ {
+ self.inner.size().into()
+ }
+
+ /// Returns the top-left corner position of the monitor relative to the larger full
+ /// screen area.
+ #[inline]
+ pub fn position(&self) -> PhysicalPosition<i32>
+ {
+ self.inner.position().into()
+ }
+
+ /// Returns the scale factor of the underlying monitor.
+ #[inline]
+ pub fn scale_factor(&self) -> f64
+ {
+ self.inner.scale_factor()
+ }
+}
+
+impl Handle
+{
+ pub(super) fn from_winit_monitor_handle(
+ monitor: winit::monitor::MonitorHandle,
+ ) -> Self
+ {
+ Self { inner: monitor }
+ }
+}
diff --git a/engine/src/windowing/mouse.rs b/engine/src/windowing/mouse.rs
new file mode 100644
index 0000000..3a43e79
--- /dev/null
+++ b/engine/src/windowing/mouse.rs
@@ -0,0 +1,197 @@
+use std::collections::HashMap;
+
+use crate::ecs::Sole;
+use crate::reflection::Reflection;
+use crate::vector::Vec2;
+use crate::windowing::dpi::PhysicalPosition;
+
+#[derive(Debug, Default, Clone, Sole, Reflection)]
+#[non_exhaustive]
+pub struct Mouse
+{
+ /// Change in coordinates this tick. Unit is unspecified and platforms may use
+ /// different units. Updated automatically by the [`windowing extension`].
+ ///
+ /// [`windowing extension`]: crate::windowing
+ pub curr_tick_position_delta: Vec2<f64>,
+
+ /// Coordinates in pixels relative to the top-left corner of the window. May have
+ /// been affected by cursor acceleration
+ pub position: PhysicalPosition<f64>,
+
+ pub curr_tick_scroll_delta: ScrollDelta,
+}
+
+#[derive(Debug, Clone, Default, Reflection)]
+pub struct ScrollDelta
+{
+ pub vert_lines: f32,
+ pub hor_lines: f32,
+}
+
+impl ScrollDelta
+{
+ pub fn is_zero(&self) -> bool
+ {
+ self.vert_lines == 0.0 && self.hor_lines == 0.0
+ }
+}
+
+/// Mouse buttons.
+#[derive(Debug, Default, Sole)]
+pub struct Buttons
+{
+ map: HashMap<Button, ButtonData>,
+}
+
+impl Buttons
+{
+ pub fn get(&self, button: Button) -> ButtonState
+ {
+ let Some(button_data) = self.map.get(&button) else {
+ return ButtonState::Released;
+ };
+
+ button_data.current_state
+ }
+
+ pub fn get_previous(&self, button: Button) -> ButtonState
+ {
+ let Some(button_data) = self.map.get(&button) else {
+ return ButtonState::Released;
+ };
+
+ button_data.previous_state
+ }
+
+ /// Returns a iterator that yields buttons and their current states. Only buttons with
+ /// states is included.
+ pub fn all_current(&self) -> impl Iterator<Item = (Button, ButtonState)> + use<'_>
+ {
+ self.map
+ .iter()
+ .map(|(button, button_data)| (button.clone(), button_data.current_state))
+ }
+
+ pub fn set(&mut self, button: Button, button_state: ButtonState)
+ {
+ let button_data = self.map.entry(button).or_default();
+
+ button_data.current_state = button_state;
+ }
+
+ pub fn set_previous(&mut self, button: Button, button_state: ButtonState)
+ {
+ let button_data = self.map.entry(button).or_default();
+
+ button_data.previous_state = button_state;
+ }
+
+ pub fn set_previous_to_current(&mut self, button: Button)
+ {
+ let button_data = self.map.entry(button).or_default();
+
+ button_data.previous_state = button_data.current_state;
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum Button
+{
+ Left,
+ Right,
+ Middle,
+ Back,
+ Forward,
+ Other(u16),
+}
+
+impl intmap::IntKey for Button
+{
+ type Int = u32;
+
+ const PRIME: Self::Int = <u32 as intmap::IntKey>::PRIME;
+
+ fn into_int(self) -> Self::Int
+ {
+ match &self {
+ Self::Left => 0,
+ Self::Right => 1,
+ Self::Middle => 2,
+ Self::Back => 3,
+ Self::Forward => 4,
+ Self::Other(other) => 5 + *other as u32,
+ }
+ }
+}
+
+impl From<winit::event::MouseButton> for Button
+{
+ fn from(mouse_button: winit::event::MouseButton) -> Self
+ {
+ match mouse_button {
+ winit::event::MouseButton::Left => Self::Left,
+ winit::event::MouseButton::Right => Self::Right,
+ winit::event::MouseButton::Middle => Self::Middle,
+ winit::event::MouseButton::Back => Self::Back,
+ winit::event::MouseButton::Forward => Self::Forward,
+ winit::event::MouseButton::Other(other_mouse_button) => {
+ Self::Other(other_mouse_button)
+ }
+ }
+ }
+}
+
+/// Mouse button state.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum ButtonState
+{
+ Pressed,
+ Released,
+}
+
+impl ButtonState
+{
+ #[must_use]
+ #[inline]
+ pub fn is_pressed(&self) -> bool
+ {
+ matches!(self, Self::Pressed)
+ }
+
+ #[must_use]
+ #[inline]
+ pub fn is_released(&self) -> bool
+ {
+ matches!(self, Self::Released)
+ }
+}
+
+impl From<winit::event::ElementState> for ButtonState
+{
+ fn from(element_state: winit::event::ElementState) -> Self
+ {
+ match element_state {
+ winit::event::ElementState::Pressed => Self::Pressed,
+ winit::event::ElementState::Released => Self::Released,
+ }
+ }
+}
+
+#[derive(Debug)]
+struct ButtonData
+{
+ current_state: ButtonState,
+ previous_state: ButtonState,
+}
+
+impl Default for ButtonData
+{
+ fn default() -> Self
+ {
+ Self {
+ current_state: ButtonState::Released,
+ previous_state: ButtonState::Released,
+ }
+ }
+}
diff --git a/engine/src/windowing/window.rs b/engine/src/windowing/window.rs
new file mode 100644
index 0000000..6d7a464
--- /dev/null
+++ b/engine/src/windowing/window.rs
@@ -0,0 +1,291 @@
+use std::borrow::Cow;
+
+use crate::ecs::Component;
+use crate::image::Image;
+use crate::reflection::Reflection;
+use crate::windowing::dpi::{PhysicalSize, Position, Size};
+
+pub mod platform;
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct Id
+{
+ inner: winit::window::WindowId,
+}
+
+impl intmap::IntKey for Id
+{
+ type Int = u64;
+
+ const PRIME: Self::Int = <u64 as intmap::IntKey>::PRIME;
+
+ fn into_int(self) -> Self::Int
+ {
+ self.inner.into()
+ }
+}
+
+impl Id
+{
+ pub(crate) fn from_inner(inner: winit::window::WindowId) -> Self
+ {
+ Self { inner }
+ }
+}
+
+#[derive(Debug, Component, Clone, Reflection)]
+#[non_exhaustive]
+pub struct CreationAttributes
+{
+ pub title: Cow<'static, str>,
+ pub transparent: bool,
+ pub maximized: bool,
+ pub fullscreen: Option<Fullscreen>,
+ pub visible: bool,
+ pub resizable: bool,
+ pub position: Option<Position>,
+ pub inner_size: Option<Size>,
+ pub icon: Option<Icon>,
+
+ pub x_visual_id: Option<XVisualID>,
+
+ // These do not have equivalents in winit::window::WindowAttributes
+ pub cursor_visible: bool,
+ pub cursor_grab_mode: CursorGrabMode,
+}
+
+macro_rules! gen_creation_attrs_with_fn {
+ ($field: ident, $field_type: ty) => {
+ paste::paste! {
+ impl CreationAttributes
+ {
+ pub fn [<with_ $field>](mut self, new: impl Into<$field_type>) -> Self
+ {
+ self.$field = new.into();
+ self
+ }
+ }
+ }
+ };
+}
+
+gen_creation_attrs_with_fn!(title, Cow<'static, str>);
+gen_creation_attrs_with_fn!(transparent, bool);
+gen_creation_attrs_with_fn!(maximized, bool);
+gen_creation_attrs_with_fn!(fullscreen, Option<Fullscreen>);
+gen_creation_attrs_with_fn!(visible, bool);
+gen_creation_attrs_with_fn!(resizable, bool);
+gen_creation_attrs_with_fn!(position, Option<Position>);
+gen_creation_attrs_with_fn!(inner_size, Option<Size>);
+gen_creation_attrs_with_fn!(icon, Option<Icon>);
+
+gen_creation_attrs_with_fn!(x_visual_id, Option<XVisualID>);
+
+gen_creation_attrs_with_fn!(cursor_visible, bool);
+gen_creation_attrs_with_fn!(cursor_grab_mode, CursorGrabMode);
+
+impl CreationAttributes
+{
+ #[tracing::instrument(skip_all)]
+ pub(crate) fn into_window_attrs(self) -> winit::window::WindowAttributes
+ {
+ let mut window_attrs = winit::window::WindowAttributes::default()
+ .with_title(self.title.into_owned())
+ .with_transparent(self.transparent)
+ .with_maximized(self.maximized)
+ .with_fullscreen(match self.fullscreen {
+ Some(Fullscreen::Borderless) => {
+ Some(winit::window::Fullscreen::Borderless(None))
+ }
+ None => None,
+ })
+ .with_visible(self.visible)
+ .with_resizable(self.resizable)
+ .with_window_icon(match self.icon {
+ Some(Icon::Image(image)) => {
+ let image_dimens = image.dimensions();
+
+ let result = winit::window::Icon::from_rgba(
+ image.into_rgba8().into_bytes(),
+ image_dimens.width,
+ image_dimens.height,
+ );
+
+ if let Err(err) = &result {
+ tracing::error!("Invalid icon image: {err}");
+ }
+
+ result.ok()
+ }
+ #[cfg(windows)]
+ Some(Icon::WindowsResource(resource_name)) => {
+ use winit::platform::windows::IconExtWindows;
+
+ let result = winit::window::Icon::from_resource_name(
+ resource_name.as_ref(),
+ None,
+ );
+
+ if let Err(err) = &result {
+ tracing::error!(
+ "Invalid icon image in resource {resource_name}: {err}"
+ );
+ }
+
+ result.ok()
+ }
+ None => None,
+ });
+
+ window_attrs.position = self.position.map(Into::into);
+ window_attrs.inner_size = self.inner_size.map(Into::into);
+
+ #[cfg(target_os = "linux")]
+ if let Some(visual_id) = self.x_visual_id {
+ use winit::platform::x11::WindowAttributesExtX11;
+
+ return window_attrs.with_x11_visual(visual_id);
+ }
+
+ window_attrs
+ }
+
+ pub(crate) fn apply_extra_attrs_to_window(&self, window: &winit::window::Window)
+ {
+ window.set_cursor_visible(self.cursor_visible);
+ }
+}
+
+impl Default for CreationAttributes
+{
+ fn default() -> Self
+ {
+ Self {
+ title: "Unnamed window".into(),
+ transparent: false,
+ maximized: false,
+ fullscreen: None,
+ visible: true,
+ resizable: true,
+ position: None,
+ inner_size: None,
+ icon: None,
+ x_visual_id: None,
+ cursor_visible: true,
+ cursor_grab_mode: CursorGrabMode::None,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Reflection)]
+#[non_exhaustive]
+pub enum Fullscreen
+{
+ Borderless,
+}
+
+/// Window icon
+#[derive(Debug, Clone, Reflection)]
+#[non_exhaustive]
+pub enum Icon
+{
+ Image(Image),
+
+ #[cfg(windows)]
+ WindowsResource(Cow<'static, str>),
+}
+
+#[derive(Debug, Default, Component, Clone, Copy, Reflection)]
+pub struct CreationReady;
+
+#[derive(Debug, Component, Reflection)]
+#[non_exhaustive]
+pub struct Window
+{
+ pub title: String,
+ pub cursor_visible: bool,
+ pub cursor_grab_mode: CursorGrabMode,
+ pub inner_size: PhysicalSize<u32>,
+ wid: Id,
+ scale_factor: f64,
+}
+
+impl Window
+{
+ pub fn wid(&self) -> Id
+ {
+ self.wid
+ }
+
+ pub fn scale_factor(&self) -> f64
+ {
+ self.scale_factor
+ }
+
+ pub(crate) fn new(
+ winit_window: &winit::window::Window,
+ creation_attrs: &CreationAttributes,
+ ) -> Self
+ {
+ Self {
+ title: creation_attrs.title.clone().into_owned(),
+ cursor_visible: creation_attrs.cursor_visible,
+ cursor_grab_mode: creation_attrs.cursor_grab_mode,
+ wid: Id::from_inner(winit_window.id()),
+ inner_size: winit_window.inner_size().into(),
+ scale_factor: winit_window.scale_factor(),
+ }
+ }
+
+ #[must_use]
+ pub(crate) fn apply(&self, winit_window: &winit::window::Window) -> ApplyResults
+ {
+ winit_window.set_title(&self.title);
+ winit_window.set_cursor_visible(self.cursor_visible);
+
+ let curr_inner_size = winit_window.inner_size().clone().into();
+
+ let inner_size_request_result = match winit_window.request_inner_size(
+ winit::dpi::Size::Physical(self.inner_size.clone().into()),
+ ) {
+ // The comparison of curr_inner_size is in case the user's windowing system
+ // lies about using the requested inner size
+ None if curr_inner_size == self.inner_size => Ok(()),
+ None => Err(curr_inner_size),
+ Some(inner_size) => Err(inner_size.into()),
+ };
+
+ ApplyResults {
+ inner_size_request: inner_size_request_result,
+ }
+ }
+
+ pub(crate) fn set_scale_factor(&mut self, scale_factor: f64)
+ {
+ self.scale_factor = scale_factor;
+ }
+}
+
+#[derive(Debug, Component, Reflection)]
+pub struct Closed;
+
+#[derive(
+ Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Reflection,
+)]
+pub enum CursorGrabMode
+{
+ #[default]
+ None,
+
+ /// The cursor is locked to a specific position in the window.
+ Locked,
+}
+
+/// A unique identifier for an X11 visual.
+pub type XVisualID = u32;
+
+#[derive(Debug)]
+pub(crate) struct ApplyResults
+{
+ pub inner_size_request: Result<(), PhysicalSize<u32>>,
+}
diff --git a/engine/src/windowing/window/platform.rs b/engine/src/windowing/window/platform.rs
new file mode 100644
index 0000000..1f38f3b
--- /dev/null
+++ b/engine/src/windowing/window/platform.rs
@@ -0,0 +1,12 @@
+#[cfg(target_os = "linux")]
+pub mod x11
+{
+ use std::ffi::c_void;
+
+ pub type XlibErrorHook = Box<dyn Fn(*mut c_void, *mut c_void) -> bool + Send + Sync>;
+
+ pub fn register_xlib_error_hook(hook: XlibErrorHook)
+ {
+ winit::platform::x11::register_xlib_error_hook(hook);
+ }
+}