├── .github └── workflows │ └── integration.yml ├── .gitignore ├── CHANGES.md ├── Cargo.toml ├── README.md ├── examples ├── cache.rs └── echo.rs └── src ├── event.rs ├── event ├── convert.rs ├── gamepad.rs ├── keyboard.rs └── pointer.rs ├── event_cache.rs ├── event_stream.rs ├── lib.rs ├── run.rs ├── settings.rs └── window.rs /.github/workflows/integration.yml: -------------------------------------------------------------------------------- 1 | name: Integration 2 | on: 3 | pull_request: 4 | paths: 5 | - 'src/**.rs' 6 | - 'Cargo.toml' 7 | - '.github/workflows/integration.yml' 8 | jobs: 9 | formatting: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: hecrj/setup-rust-action@v1 14 | with: 15 | rust-version: stable 16 | components: rustfmt 17 | - name: Check Formatting 18 | run: cargo fmt --all -- --check 19 | 20 | test: 21 | runs-on: ${{ matrix.os }} 22 | strategy: 23 | matrix: 24 | os: [ubuntu-latest, windows-latest, macOS-latest] 25 | rust: [stable, nightly] 26 | steps: 27 | - uses: hecrj/setup-rust-action@v1 28 | with: 29 | rust-version: ${{ matrix.rust }} 30 | - name: Install libudev 31 | if: matrix.os == 'ubuntu-latest' 32 | run: sudo apt update && sudo apt install libudev-dev 33 | - uses: actions/checkout@master 34 | - name: Test 35 | run: cargo test --examples 36 | - name: Test With Event Cache 37 | run: cargo test --examples --all-features 38 | 39 | clippy: 40 | strategy: 41 | matrix: 42 | os: [ubuntu-latest, windows-latest, macOS-latest] 43 | runs-on: ${{ matrix.os }} 44 | steps: 45 | - uses: hecrj/setup-rust-action@v1 46 | with: 47 | rust-version: stable 48 | components: clippy 49 | - name: Install libudev 50 | if: matrix.os == 'ubuntu-latest' 51 | run: sudo apt update && sudo apt install libudev-dev 52 | - uses: actions/checkout@master 53 | - name: Clippy 54 | run: cargo clippy -- -D warnings 55 | - name: Clippy with features 56 | run: cargo clippy --all-features -- -D warnings 57 | 58 | clippy-web: 59 | runs-on: macOS-latest 60 | steps: 61 | - uses: hecrj/setup-rust-action@v1 62 | with: 63 | rust-version: stable 64 | targets: wasm32-unknown-unknown 65 | - uses: actions/checkout@master 66 | - name: Check web 67 | run: cargo check --target wasm32-unknown-unknown 68 | - name: Check with features 69 | run: cargo check --target wasm32-unknown-unknown --all-features 70 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v0.2.0 4 | - Always enable gl, removing the need for the `gl` feature 5 | - Remove `run_gl` because all runs are now GL 6 | - Add `get_proc_address` for desktop GL, and `webgl_context` for web GL 7 | - Remove `glow` dependency; use the new context methods to construct glow contexts 8 | 9 | ## v0.1.6 10 | - Automatically focus the canvas on window creation on web 11 | 12 | ## v0.1.5 13 | - Fix alpha-blending settings on the web 14 | 15 | ## v0.1.4 16 | - Add Clone, PartialEq derivations to Settings 17 | - Add Copy, Clone, PartialEq, Eq, Hash to CursorIcon 18 | 19 | ## v0.1.3 20 | - Fix a bug in ResizedEvent's `logical_size` calculation 21 | - Upgraded winit to version 0.22 (and glutin to 0.24) 22 | 23 | ## v0.1.2 24 | - Fix logical vs physical coordinates in mouse move events 25 | 26 | ## v0.1.1 27 | - docs.rs build fix 28 | 29 | ## v0.1.0 30 | - [Breaking] Reworked the events API for forwards-compatibility 31 | - Set the tab title on web 32 | - Fix building when features are disabled 33 | - Add an optional module to cache event states 34 | 35 | ## v0.1.0-alpha10 36 | - Fixed a bug where the docs said "logical sizes" but was actually using physical sizes 37 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "blinds" 3 | description = "Wrap an async abstraction over a window" 4 | version = "0.2.0" 5 | authors = ["Ryan Goldstein "] 6 | categories = ["game-development", "web-programming"] 7 | keywords = ["async", "window"] 8 | license = "MIT/Apache-2.0" 9 | edition = "2018" 10 | readme = "README.md" 11 | repository = "https://github.com/ryanisaacg/blinds" 12 | 13 | [package.metadata.docs.rs] 14 | features = ["event-cache", "favicon", "gamepad"] 15 | 16 | [features] 17 | default = ["favicon", "gamepad"] 18 | 19 | event-cache = ["enum-map", "rustc-hash"] 20 | favicon = ["image"] 21 | gamepad = ["gilrs"] 22 | 23 | [dependencies] 24 | enum-map = { version = "0.6.2", default-features = false, optional = true } 25 | futures-util = { version = "0.3.1", default-features = false } 26 | futures-executor = { version = "0.3.1", default-features = false, features = ["std"] } 27 | gilrs = { version = "0.8", optional = true } 28 | image = { version = "0.23", optional = true, default-features = false } 29 | mint = "0.5" 30 | rustc-hash = { version = "1.1.0", optional = true } 31 | 32 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 33 | glutin = "0.26.0" 34 | winit = "0.24.0" 35 | 36 | [target.'cfg(target_arch = "wasm32")'.dependencies] 37 | js-sys = "0.3.22" 38 | wasm-bindgen = "0.2" 39 | web-sys = { version = "0.3.22", features = ["HtmlHeadElement", "WebGlRenderingContext"] } 40 | winit = { version = "0.24.0", features = ["web-sys"] } 41 | 42 | [[example]] 43 | name = "cache" 44 | required-features = ["event-cache"] 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # blinds 2 | 3 | `blinds` covers up the details of your windowing for you, by providing an async API. 4 | 5 | ```rust 6 | use blinds::{run, Event, EventStream, Key, Settings, Window}; 7 | 8 | fn main() { 9 | run(Settings::default(), app); 10 | } 11 | 12 | async fn app(_window: Window, mut events: EventStream) { 13 | loop { 14 | while let Some(ev) = events.next_event().await { 15 | println!("{:?}", ev); 16 | } 17 | } 18 | } 19 | ``` 20 | -------------------------------------------------------------------------------- /examples/cache.rs: -------------------------------------------------------------------------------- 1 | use blinds::{run, CachedEventStream, EventStream, Key, Settings, Window}; 2 | 3 | fn main() { 4 | run(Settings::default(), app); 5 | } 6 | 7 | async fn app(_window: Window, events: EventStream) { 8 | let mut events = CachedEventStream::new(events); 9 | loop { 10 | while let Some(ev) = events.next_event().await { 11 | println!("{:?}", ev); 12 | } 13 | if events.cache().key(Key::Escape) { 14 | break; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/echo.rs: -------------------------------------------------------------------------------- 1 | use blinds::{run, Event, EventStream, Key, Settings, Window}; 2 | 3 | fn main() { 4 | run(Settings::default(), app); 5 | } 6 | 7 | async fn app(_window: Window, mut events: EventStream) { 8 | 'outer: loop { 9 | while let Some(ev) = events.next_event().await { 10 | match ev { 11 | Event::KeyboardInput(e) if e.key() == Key::Escape => { 12 | break 'outer; 13 | } 14 | ev => println!("{:?}", ev), 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/event.rs: -------------------------------------------------------------------------------- 1 | use mint::Vector2; 2 | mod convert; 3 | mod gamepad; 4 | mod keyboard; 5 | mod pointer; 6 | 7 | pub(crate) use self::convert::*; 8 | pub use self::gamepad::*; 9 | pub use self::keyboard::*; 10 | pub use self::pointer::*; 11 | 12 | #[derive(Clone, Debug)] 13 | #[non_exhaustive] 14 | /// An indicator something has changed or input has been dispatched 15 | pub enum Event { 16 | /// The size of the window has changed, see [`Window::size`] 17 | /// 18 | /// [`Window::size`]: crate::Window::size 19 | Resized(ResizedEvent), 20 | /// The scale factor of the window has changed, see [`Window::scale_factor`] 21 | /// 22 | /// [`Window::scale_factor`]: crate::Window::scale_factor 23 | ScaleFactorChanged(ScaleFactorChangedEvent), 24 | /// The window has gained operating system focus (true), or lost it (false) 25 | FocusChanged(FocusChangedEvent), 26 | /// The user typed a character, used for text input 27 | /// 28 | /// Don't use keyboard events for text! Depending on how the user's operating system and 29 | /// keyboard layout are configured, different keys may produce different Unicode characters. 30 | ReceivedCharacter(ReceivedCharacterEvent), 31 | /// A key has been pressed, released, or held down 32 | /// 33 | /// Operating systems often have key repeat settings that cause duplicate events to be 34 | /// generated for a single press. 35 | KeyboardInput(KeyboardEvent), 36 | /// A pointer entered the window 37 | PointerEntered(PointerEnteredEvent), 38 | /// A pointer has exited the window 39 | PointerLeft(PointerLeftEvent), 40 | /// A pointer has a new position, relative to the window's top-left 41 | PointerMoved(PointerMovedEvent), 42 | /// A button on a pointer, likely a mouse, has produced an input 43 | PointerInput(PointerInputEvent), 44 | /// The mousewheel has scrolled, either in lines or pixels (depending on the input method) 45 | ScrollInput(ScrollDelta), 46 | /// The keyboard modifiers (e.g. shift, alt, ctrl) have changed 47 | ModifiersChanged(ModifiersChangedEvent), 48 | /// A gamepad has been connected 49 | GamepadConnected(GamepadConnectedEvent), 50 | /// A gamepad has been disconnected 51 | GamepadDisconnected(GamepadDisconnectedEvent), 52 | /// A gamepad button has been pressed or released 53 | GamepadButton(GamepadButtonEvent), 54 | /// A gamepad axis has changed its value 55 | GamepadAxis(GamepadAxisEvent), 56 | } 57 | 58 | #[derive(Clone, Debug)] 59 | /// See [`Event::Resized`] 60 | pub struct ResizedEvent { 61 | pub(crate) size: Vector2, 62 | } 63 | 64 | impl ResizedEvent { 65 | /// The new logical size of the window, taking into account DPI 66 | pub fn logical_size(&self) -> Vector2 { 67 | self.size 68 | } 69 | } 70 | 71 | #[derive(Clone, Debug)] 72 | /// See [`Event::ScaleFactorChanged`] 73 | pub struct ScaleFactorChangedEvent { 74 | pub(crate) scale: f32, 75 | } 76 | 77 | impl ScaleFactorChangedEvent { 78 | pub fn scale_factor(&self) -> f32 { 79 | self.scale 80 | } 81 | } 82 | 83 | #[derive(Clone, Debug)] 84 | /// See [`Event::FocusChanged`] 85 | pub struct FocusChangedEvent { 86 | pub(crate) focus: bool, 87 | } 88 | 89 | impl FocusChangedEvent { 90 | pub fn is_focused(&self) -> bool { 91 | self.focus 92 | } 93 | } 94 | 95 | #[derive(Clone, Debug)] 96 | /// See [`Event::ReceivedCharacter`] 97 | pub struct ReceivedCharacterEvent { 98 | pub(crate) chr: char, 99 | } 100 | 101 | impl ReceivedCharacterEvent { 102 | /// The character entered by the user 103 | pub fn character(&self) -> char { 104 | self.chr 105 | } 106 | } 107 | 108 | #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] 109 | /// A change in the event modifiers like shift, control, alt, or 'logo' 110 | /// 111 | /// See [`Event::ModifiersChanged`] 112 | pub struct ModifiersChangedEvent { 113 | shift: bool, 114 | ctrl: bool, 115 | alt: bool, 116 | logo: bool, 117 | } 118 | 119 | impl ModifiersChangedEvent { 120 | pub fn shift(self) -> bool { 121 | self.shift 122 | } 123 | 124 | pub fn ctrl(self) -> bool { 125 | self.ctrl 126 | } 127 | 128 | pub fn alt(self) -> bool { 129 | self.alt 130 | } 131 | 132 | /// Windows, Command, etc. 133 | pub fn logo(self) -> bool { 134 | self.logo 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/event/convert.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::window::WindowContents; 3 | use winit::event::{ElementState, WindowEvent}; 4 | 5 | pub(crate) fn window_event(event: WindowEvent, window: &WindowContents) -> Option { 6 | use WindowEvent::*; 7 | Some(match event { 8 | Resized(ls) => Event::Resized(ResizedEvent { 9 | size: ps_to_logical_vec(ls, window.scale()), 10 | }), 11 | ScaleFactorChanged { scale_factor, .. } => { 12 | Event::ScaleFactorChanged(ScaleFactorChangedEvent { 13 | scale: scale_factor as f32, 14 | }) 15 | } 16 | ReceivedCharacter(chr) => Event::ReceivedCharacter(ReceivedCharacterEvent { chr }), 17 | Focused(focus) => Event::FocusChanged(FocusChangedEvent { focus }), 18 | KeyboardInput { 19 | input: 20 | winit::event::KeyboardInput { 21 | state, 22 | virtual_keycode: Some(key), 23 | .. 24 | }, 25 | .. 26 | } => Event::KeyboardInput(KeyboardEvent { 27 | key: key.into(), 28 | is_down: state == ElementState::Pressed, 29 | }), 30 | CursorMoved { 31 | device_id, 32 | position, 33 | .. 34 | } => Event::PointerMoved(PointerMovedEvent { 35 | id: PointerId(device_id), 36 | location: pp_to_logical_vec(position, window.scale()), 37 | }), 38 | CursorEntered { device_id, .. } => { 39 | Event::PointerEntered(PointerEnteredEvent(PointerId(device_id))) 40 | } 41 | CursorLeft { device_id, .. } => Event::PointerLeft(PointerLeftEvent(PointerId(device_id))), 42 | MouseWheel { delta, .. } => Event::ScrollInput(delta.into()), 43 | MouseInput { 44 | device_id, 45 | button, 46 | state, 47 | .. 48 | } => Event::PointerInput(PointerInputEvent { 49 | id: PointerId(device_id), 50 | button: button.into(), 51 | is_down: state == ElementState::Pressed, 52 | }), 53 | ModifiersChanged(state) => Event::ModifiersChanged(convert_modifiers(state)), 54 | _ => return None, 55 | }) 56 | } 57 | 58 | #[cfg(feature = "gilrs")] 59 | pub(crate) fn gamepad_event(event: gilrs::Event) -> Option { 60 | use gilrs::ev::EventType::*; 61 | let gilrs::Event { id, event, .. } = event; 62 | let id = GamepadId(id); 63 | Some(match event { 64 | ButtonPressed(btn, _) => Event::GamepadButton(GamepadButtonEvent { 65 | id, 66 | button: convert_gilrs_button(btn)?, 67 | is_down: true, 68 | is_repeat: false, 69 | }), 70 | ButtonRepeated(btn, _) => Event::GamepadButton(GamepadButtonEvent { 71 | id, 72 | button: convert_gilrs_button(btn)?, 73 | is_down: true, 74 | is_repeat: true, 75 | }), 76 | ButtonReleased(btn, _) => Event::GamepadButton(GamepadButtonEvent { 77 | id, 78 | button: convert_gilrs_button(btn)?, 79 | is_down: false, 80 | is_repeat: false, 81 | }), 82 | AxisChanged(axis, value, _) => Event::GamepadAxis(GamepadAxisEvent { 83 | id, 84 | axis: convert_gilrs_axis(axis)?, 85 | value, 86 | }), 87 | Connected => Event::GamepadConnected(GamepadConnectedEvent(id)), 88 | Disconnected => Event::GamepadDisconnected(GamepadDisconnectedEvent(id)), 89 | ButtonChanged(_, _, _) | Dropped => return None, 90 | }) 91 | } 92 | 93 | fn convert_modifiers(modifiers: winit::event::ModifiersState) -> ModifiersChangedEvent { 94 | ModifiersChangedEvent { 95 | shift: modifiers.shift(), 96 | ctrl: modifiers.ctrl(), 97 | alt: modifiers.alt(), 98 | logo: modifiers.logo(), 99 | } 100 | } 101 | 102 | fn ps_to_logical_vec( 103 | ls: winit::dpi::PhysicalSize

, 104 | scale: f32, 105 | ) -> Vector2 { 106 | Vector2 { 107 | x: ls.width.cast::() / scale, 108 | y: ls.height.cast::() / scale, 109 | } 110 | } 111 | 112 | fn pp_to_logical_vec( 113 | ls: winit::dpi::PhysicalPosition

, 114 | scale: f32, 115 | ) -> Vector2 { 116 | Vector2 { 117 | x: ls.x.cast::() / scale, 118 | y: ls.y.cast::() / scale, 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/event/gamepad.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::Ordering; 2 | 3 | #[derive(Clone, Debug)] 4 | /// See [`Event::GamepadConnected`] 5 | /// 6 | /// [`Event::GamepadConnected`]: crate::event::Event::GamepadConnected 7 | pub struct GamepadConnectedEvent(pub(crate) GamepadId); 8 | 9 | impl GamepadConnectedEvent { 10 | pub fn gamepad(&self) -> &GamepadId { 11 | &self.0 12 | } 13 | } 14 | 15 | #[derive(Clone, Debug)] 16 | /// See [`Event::GamepadDisconnected`] 17 | /// 18 | /// [`Event::GamepadDisconnected`]: crate::event::Event::GamepadDisconnected 19 | pub struct GamepadDisconnectedEvent(pub(crate) GamepadId); 20 | 21 | impl GamepadDisconnectedEvent { 22 | pub fn gamepad(&self) -> &GamepadId { 23 | &self.0 24 | } 25 | } 26 | 27 | #[derive(Clone, Debug)] 28 | /// See [`Event::GamepadButton`] 29 | /// 30 | /// [`Event::GamepadButton`]: crate::event::Event::GamepadButton 31 | pub struct GamepadButtonEvent { 32 | pub(crate) id: GamepadId, 33 | pub(crate) button: GamepadButton, 34 | pub(crate) is_down: bool, 35 | pub(crate) is_repeat: bool, 36 | } 37 | 38 | impl GamepadButtonEvent { 39 | /// Which gamepad generated the event 40 | pub fn gamepad(&self) -> &GamepadId { 41 | &self.id 42 | } 43 | 44 | pub fn button(&self) -> GamepadButton { 45 | self.button 46 | } 47 | 48 | /// If the button is now down, either repeating or down for the first time 49 | pub fn is_down(&self) -> bool { 50 | self.is_down 51 | } 52 | 53 | /// If this event is a repeat of a previous down event 54 | pub fn is_repeat(&self) -> bool { 55 | self.is_repeat 56 | } 57 | } 58 | 59 | #[derive(Clone, Debug)] 60 | /// See [`Event::GamepadAxis`] 61 | /// 62 | /// [`Event::GamepadAxis`]: crate::event::Event::GamepadAxis 63 | pub struct GamepadAxisEvent { 64 | pub(crate) id: GamepadId, 65 | pub(crate) axis: GamepadAxis, 66 | pub(crate) value: f32, 67 | } 68 | 69 | impl GamepadAxisEvent { 70 | /// Which gamepad generated the event 71 | pub fn gamepad(&self) -> &GamepadId { 72 | &self.id 73 | } 74 | 75 | pub fn axis(&self) -> GamepadAxis { 76 | self.axis 77 | } 78 | 79 | pub fn value(&self) -> f32 { 80 | self.value 81 | } 82 | } 83 | 84 | #[derive(Clone, PartialEq, Eq, Debug, Hash)] 85 | /// A unique ID for a gamepad that persists after the device is unplugged 86 | pub struct GamepadId( 87 | #[cfg(feature = "gilrs")] pub(crate) gilrs::GamepadId, 88 | #[cfg(not(feature = "gilrs"))] usize, 89 | ); 90 | 91 | impl PartialOrd for GamepadId { 92 | fn partial_cmp(&self, other: &Self) -> Option { 93 | Some(self.cmp(other)) 94 | } 95 | } 96 | 97 | impl Ord for GamepadId { 98 | fn cmp(&self, other: &Self) -> Ordering { 99 | let a: usize = self.0.into(); 100 | let b: usize = other.0.into(); 101 | a.cmp(&b) 102 | } 103 | } 104 | 105 | #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] 106 | #[cfg_attr(feature = "enum-map", derive(enum_map::Enum))] 107 | /// A button on a standard (d-pad, 2-stick, 4-button, 4-trigger) gamepad 108 | pub enum GamepadButton { 109 | Start, 110 | Select, 111 | 112 | /// The north face button. 113 | /// 114 | /// * Nintendo: X 115 | /// * Playstation: Triangle 116 | /// * XBox: Y 117 | North, 118 | /// The south face button. 119 | /// 120 | /// * Nintendo: B 121 | /// * Playstation: X 122 | /// * XBox: A 123 | South, 124 | /// The east face button. 125 | /// 126 | /// * Nintendo: A 127 | /// * Playstation: Circle 128 | /// * XBox: B 129 | East, 130 | /// The west face button. 131 | /// 132 | /// * Nintendo: Y 133 | /// * Playstation: Square 134 | /// * XBox: X 135 | West, 136 | 137 | /// The left stick was pressed in as a button 138 | LeftStick, 139 | /// The right stick was pressed in as a button 140 | RightStick, 141 | 142 | LeftTrigger, 143 | RightTrigger, 144 | 145 | LeftShoulder, 146 | RightShoulder, 147 | 148 | DPadUp, 149 | DPadDown, 150 | DPadLeft, 151 | DPadRight, 152 | } 153 | 154 | #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] 155 | #[cfg_attr(feature = "enum-map", derive(enum_map::Enum))] 156 | /// The stick axes of a gamepad 157 | pub enum GamepadAxis { 158 | LeftStickX, 159 | LeftStickY, 160 | 161 | RightStickX, 162 | RightStickY, 163 | } 164 | 165 | #[cfg(feature = "gilrs")] 166 | pub(crate) fn convert_gilrs_button(event: gilrs::ev::Button) -> Option { 167 | use gilrs::ev::Button::*; 168 | Some(match event { 169 | South => GamepadButton::South, 170 | East => GamepadButton::East, 171 | North => GamepadButton::North, 172 | West => GamepadButton::West, 173 | LeftTrigger => GamepadButton::LeftShoulder, 174 | LeftTrigger2 => GamepadButton::LeftShoulder, 175 | RightTrigger => GamepadButton::RightShoulder, 176 | RightTrigger2 => GamepadButton::RightTrigger, 177 | Select => GamepadButton::Select, 178 | Start => GamepadButton::Start, 179 | LeftThumb => GamepadButton::LeftStick, 180 | RightThumb => GamepadButton::RightStick, 181 | DPadUp => GamepadButton::DPadUp, 182 | DPadDown => GamepadButton::DPadDown, 183 | DPadLeft => GamepadButton::DPadLeft, 184 | DPadRight => GamepadButton::DPadRight, 185 | 186 | C | Z | Unknown | Mode => return None, 187 | }) 188 | } 189 | 190 | #[cfg(feature = "gilrs")] 191 | pub(crate) fn convert_gilrs_axis(axis: gilrs::ev::Axis) -> Option { 192 | use gilrs::ev::Axis::*; 193 | 194 | Some(match axis { 195 | LeftStickX => GamepadAxis::LeftStickX, 196 | LeftStickY => GamepadAxis::LeftStickY, 197 | RightStickX => GamepadAxis::RightStickX, 198 | RightStickY => GamepadAxis::RightStickY, 199 | 200 | LeftZ | RightZ | DPadX | DPadY | Unknown => return None, 201 | }) 202 | } 203 | -------------------------------------------------------------------------------- /src/event/keyboard.rs: -------------------------------------------------------------------------------- 1 | use winit::event::VirtualKeyCode; 2 | 3 | #[derive(Clone, Debug)] 4 | /// See [`Event::KeyboardInput`] 5 | /// 6 | /// [`Event::KeyboardInput`]: crate::event::Event::KeyboardInput 7 | pub struct KeyboardEvent { 8 | pub(crate) key: Key, 9 | pub(crate) is_down: bool, 10 | } 11 | 12 | impl KeyboardEvent { 13 | pub fn key(&self) -> Key { 14 | self.key 15 | } 16 | 17 | /// If the key is now down, either repeating or down for the first time 18 | pub fn is_down(&self) -> bool { 19 | self.is_down 20 | } 21 | } 22 | 23 | #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] 24 | #[cfg_attr(feature = "enum-map", derive(enum_map::Enum))] 25 | /// A key location on a keyboard 26 | pub enum Key { 27 | Key1, 28 | Key2, 29 | Key3, 30 | Key4, 31 | Key5, 32 | Key6, 33 | Key7, 34 | Key8, 35 | Key9, 36 | Key0, 37 | A, 38 | B, 39 | C, 40 | D, 41 | E, 42 | F, 43 | G, 44 | H, 45 | I, 46 | J, 47 | K, 48 | L, 49 | M, 50 | N, 51 | O, 52 | P, 53 | Q, 54 | R, 55 | S, 56 | T, 57 | U, 58 | V, 59 | W, 60 | X, 61 | Y, 62 | Z, 63 | Escape, 64 | F1, 65 | F2, 66 | F3, 67 | F4, 68 | F5, 69 | F6, 70 | F7, 71 | F8, 72 | F9, 73 | F10, 74 | F11, 75 | F12, 76 | F13, 77 | F14, 78 | F15, 79 | F16, 80 | F17, 81 | F18, 82 | F19, 83 | F20, 84 | F21, 85 | F22, 86 | F23, 87 | F24, 88 | Snapshot, 89 | Scroll, 90 | Pause, 91 | Insert, 92 | Home, 93 | Delete, 94 | End, 95 | PageDown, 96 | PageUp, 97 | Left, 98 | Up, 99 | Right, 100 | Down, 101 | Back, 102 | Return, 103 | Space, 104 | Compose, 105 | Caret, 106 | Numlock, 107 | Numpad0, 108 | Numpad1, 109 | Numpad2, 110 | Numpad3, 111 | Numpad4, 112 | Numpad5, 113 | Numpad6, 114 | Numpad7, 115 | Numpad8, 116 | Numpad9, 117 | NumpadAdd, 118 | NumpadDivide, 119 | NumpadDecimal, 120 | NumpadComma, 121 | NumpadEnter, 122 | NumpadEquals, 123 | NumpadMultiply, 124 | NumpadSubtract, 125 | AbntC1, 126 | AbntC2, 127 | Apostrophe, 128 | Apps, 129 | Asterisk, 130 | At, 131 | Ax, 132 | Backslash, 133 | Calculator, 134 | Capital, 135 | Colon, 136 | Comma, 137 | Convert, 138 | Equals, 139 | Grave, 140 | Kana, 141 | Kanji, 142 | LAlt, 143 | LBracket, 144 | LControl, 145 | LShift, 146 | LWin, 147 | Mail, 148 | MediaSelect, 149 | MediaStop, 150 | Minus, 151 | Mute, 152 | MyComputer, 153 | NavigateForward, 154 | NavigateBackward, 155 | NextTrack, 156 | NoConvert, 157 | OEM102, 158 | Period, 159 | PlayPause, 160 | Plus, 161 | Power, 162 | PrevTrack, 163 | RAlt, 164 | RBracket, 165 | RControl, 166 | RShift, 167 | RWin, 168 | Semicolon, 169 | Slash, 170 | Sleep, 171 | Stop, 172 | Sysrq, 173 | Tab, 174 | Underline, 175 | Unlabeled, 176 | VolumeDown, 177 | VolumeUp, 178 | Wake, 179 | WebBack, 180 | WebFavorites, 181 | WebForward, 182 | WebHome, 183 | WebRefresh, 184 | WebSearch, 185 | WebStop, 186 | Yen, 187 | Copy, 188 | Paste, 189 | Cut, 190 | } 191 | 192 | impl From for Key { 193 | fn from(key: VirtualKeyCode) -> Key { 194 | match key { 195 | VirtualKeyCode::Key1 => Key::Key1, 196 | VirtualKeyCode::Key2 => Key::Key2, 197 | VirtualKeyCode::Key3 => Key::Key3, 198 | VirtualKeyCode::Key4 => Key::Key4, 199 | VirtualKeyCode::Key5 => Key::Key5, 200 | VirtualKeyCode::Key6 => Key::Key6, 201 | VirtualKeyCode::Key7 => Key::Key7, 202 | VirtualKeyCode::Key8 => Key::Key8, 203 | VirtualKeyCode::Key9 => Key::Key9, 204 | VirtualKeyCode::Key0 => Key::Key0, 205 | VirtualKeyCode::A => Key::A, 206 | VirtualKeyCode::B => Key::B, 207 | VirtualKeyCode::C => Key::C, 208 | VirtualKeyCode::D => Key::D, 209 | VirtualKeyCode::E => Key::E, 210 | VirtualKeyCode::F => Key::F, 211 | VirtualKeyCode::G => Key::G, 212 | VirtualKeyCode::H => Key::H, 213 | VirtualKeyCode::I => Key::I, 214 | VirtualKeyCode::J => Key::J, 215 | VirtualKeyCode::K => Key::K, 216 | VirtualKeyCode::L => Key::L, 217 | VirtualKeyCode::M => Key::M, 218 | VirtualKeyCode::N => Key::N, 219 | VirtualKeyCode::O => Key::O, 220 | VirtualKeyCode::P => Key::P, 221 | VirtualKeyCode::Q => Key::Q, 222 | VirtualKeyCode::R => Key::R, 223 | VirtualKeyCode::S => Key::S, 224 | VirtualKeyCode::T => Key::T, 225 | VirtualKeyCode::U => Key::U, 226 | VirtualKeyCode::V => Key::V, 227 | VirtualKeyCode::W => Key::W, 228 | VirtualKeyCode::X => Key::X, 229 | VirtualKeyCode::Y => Key::Y, 230 | VirtualKeyCode::Z => Key::Z, 231 | VirtualKeyCode::Escape => Key::Escape, 232 | VirtualKeyCode::F1 => Key::F1, 233 | VirtualKeyCode::F2 => Key::F2, 234 | VirtualKeyCode::F3 => Key::F3, 235 | VirtualKeyCode::F4 => Key::F4, 236 | VirtualKeyCode::F5 => Key::F5, 237 | VirtualKeyCode::F6 => Key::F6, 238 | VirtualKeyCode::F7 => Key::F7, 239 | VirtualKeyCode::F8 => Key::F8, 240 | VirtualKeyCode::F9 => Key::F9, 241 | VirtualKeyCode::F10 => Key::F10, 242 | VirtualKeyCode::F11 => Key::F11, 243 | VirtualKeyCode::F12 => Key::F12, 244 | VirtualKeyCode::F13 => Key::F13, 245 | VirtualKeyCode::F14 => Key::F14, 246 | VirtualKeyCode::F15 => Key::F15, 247 | VirtualKeyCode::F16 => Key::F16, 248 | VirtualKeyCode::F17 => Key::F17, 249 | VirtualKeyCode::F18 => Key::F18, 250 | VirtualKeyCode::F19 => Key::F19, 251 | VirtualKeyCode::F20 => Key::F20, 252 | VirtualKeyCode::F21 => Key::F21, 253 | VirtualKeyCode::F22 => Key::F22, 254 | VirtualKeyCode::F23 => Key::F23, 255 | VirtualKeyCode::F24 => Key::F24, 256 | VirtualKeyCode::Snapshot => Key::Snapshot, 257 | VirtualKeyCode::Scroll => Key::Scroll, 258 | VirtualKeyCode::Pause => Key::Pause, 259 | VirtualKeyCode::Insert => Key::Insert, 260 | VirtualKeyCode::Home => Key::Home, 261 | VirtualKeyCode::Delete => Key::Delete, 262 | VirtualKeyCode::End => Key::End, 263 | VirtualKeyCode::PageDown => Key::PageDown, 264 | VirtualKeyCode::PageUp => Key::PageUp, 265 | VirtualKeyCode::Left => Key::Left, 266 | VirtualKeyCode::Up => Key::Up, 267 | VirtualKeyCode::Right => Key::Right, 268 | VirtualKeyCode::Down => Key::Down, 269 | VirtualKeyCode::Back => Key::Back, 270 | VirtualKeyCode::Return => Key::Return, 271 | VirtualKeyCode::Space => Key::Space, 272 | VirtualKeyCode::Compose => Key::Compose, 273 | VirtualKeyCode::Caret => Key::Caret, 274 | VirtualKeyCode::Numlock => Key::Numlock, 275 | VirtualKeyCode::Numpad0 => Key::Numpad0, 276 | VirtualKeyCode::Numpad1 => Key::Numpad1, 277 | VirtualKeyCode::Numpad2 => Key::Numpad2, 278 | VirtualKeyCode::Numpad3 => Key::Numpad3, 279 | VirtualKeyCode::Numpad4 => Key::Numpad4, 280 | VirtualKeyCode::Numpad5 => Key::Numpad5, 281 | VirtualKeyCode::Numpad6 => Key::Numpad6, 282 | VirtualKeyCode::Numpad7 => Key::Numpad7, 283 | VirtualKeyCode::Numpad8 => Key::Numpad8, 284 | VirtualKeyCode::Numpad9 => Key::Numpad9, 285 | VirtualKeyCode::NumpadAdd => Key::NumpadAdd, 286 | VirtualKeyCode::NumpadDivide => Key::NumpadDivide, 287 | VirtualKeyCode::NumpadDecimal => Key::NumpadDecimal, 288 | VirtualKeyCode::NumpadComma => Key::NumpadComma, 289 | VirtualKeyCode::NumpadEnter => Key::NumpadEnter, 290 | VirtualKeyCode::NumpadEquals => Key::NumpadEquals, 291 | VirtualKeyCode::NumpadMultiply => Key::NumpadMultiply, 292 | VirtualKeyCode::NumpadSubtract => Key::NumpadSubtract, 293 | VirtualKeyCode::AbntC1 => Key::AbntC1, 294 | VirtualKeyCode::AbntC2 => Key::AbntC2, 295 | VirtualKeyCode::Apostrophe => Key::Apostrophe, 296 | VirtualKeyCode::Apps => Key::Apps, 297 | VirtualKeyCode::Asterisk => Key::Asterisk, 298 | VirtualKeyCode::At => Key::At, 299 | VirtualKeyCode::Ax => Key::Ax, 300 | VirtualKeyCode::Backslash => Key::Backslash, 301 | VirtualKeyCode::Calculator => Key::Calculator, 302 | VirtualKeyCode::Capital => Key::Capital, 303 | VirtualKeyCode::Colon => Key::Colon, 304 | VirtualKeyCode::Comma => Key::Comma, 305 | VirtualKeyCode::Convert => Key::Convert, 306 | VirtualKeyCode::Equals => Key::Equals, 307 | VirtualKeyCode::Grave => Key::Grave, 308 | VirtualKeyCode::Kana => Key::Kana, 309 | VirtualKeyCode::Kanji => Key::Kanji, 310 | VirtualKeyCode::LAlt => Key::LAlt, 311 | VirtualKeyCode::LBracket => Key::LBracket, 312 | VirtualKeyCode::LControl => Key::LControl, 313 | VirtualKeyCode::LShift => Key::LShift, 314 | VirtualKeyCode::LWin => Key::LWin, 315 | VirtualKeyCode::Mail => Key::Mail, 316 | VirtualKeyCode::MediaSelect => Key::MediaSelect, 317 | VirtualKeyCode::MediaStop => Key::MediaStop, 318 | VirtualKeyCode::Minus => Key::Minus, 319 | VirtualKeyCode::Mute => Key::Mute, 320 | VirtualKeyCode::MyComputer => Key::MyComputer, 321 | VirtualKeyCode::NavigateForward => Key::NavigateForward, 322 | VirtualKeyCode::NavigateBackward => Key::NavigateBackward, 323 | VirtualKeyCode::NextTrack => Key::NextTrack, 324 | VirtualKeyCode::NoConvert => Key::NoConvert, 325 | VirtualKeyCode::OEM102 => Key::OEM102, 326 | VirtualKeyCode::Period => Key::Period, 327 | VirtualKeyCode::PlayPause => Key::PlayPause, 328 | VirtualKeyCode::Plus => Key::Plus, 329 | VirtualKeyCode::Power => Key::Power, 330 | VirtualKeyCode::PrevTrack => Key::PrevTrack, 331 | VirtualKeyCode::RAlt => Key::RAlt, 332 | VirtualKeyCode::RBracket => Key::RBracket, 333 | VirtualKeyCode::RControl => Key::RControl, 334 | VirtualKeyCode::RShift => Key::RShift, 335 | VirtualKeyCode::RWin => Key::RWin, 336 | VirtualKeyCode::Semicolon => Key::Semicolon, 337 | VirtualKeyCode::Slash => Key::Slash, 338 | VirtualKeyCode::Sleep => Key::Sleep, 339 | VirtualKeyCode::Stop => Key::Stop, 340 | VirtualKeyCode::Sysrq => Key::Sysrq, 341 | VirtualKeyCode::Tab => Key::Tab, 342 | VirtualKeyCode::Underline => Key::Underline, 343 | VirtualKeyCode::Unlabeled => Key::Unlabeled, 344 | VirtualKeyCode::VolumeDown => Key::VolumeDown, 345 | VirtualKeyCode::VolumeUp => Key::VolumeUp, 346 | VirtualKeyCode::Wake => Key::Wake, 347 | VirtualKeyCode::WebBack => Key::WebBack, 348 | VirtualKeyCode::WebFavorites => Key::WebFavorites, 349 | VirtualKeyCode::WebForward => Key::WebForward, 350 | VirtualKeyCode::WebHome => Key::WebHome, 351 | VirtualKeyCode::WebRefresh => Key::WebRefresh, 352 | VirtualKeyCode::WebSearch => Key::WebSearch, 353 | VirtualKeyCode::WebStop => Key::WebStop, 354 | VirtualKeyCode::Yen => Key::Yen, 355 | VirtualKeyCode::Copy => Key::Copy, 356 | VirtualKeyCode::Paste => Key::Paste, 357 | VirtualKeyCode::Cut => Key::Cut, 358 | } 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /src/event/pointer.rs: -------------------------------------------------------------------------------- 1 | use mint::Vector2; 2 | use winit::dpi::PhysicalPosition; 3 | use winit::event::{DeviceId, MouseScrollDelta as MSD}; 4 | 5 | #[derive(Clone, Debug)] 6 | /// See [`Event::PointerEntered`] 7 | /// 8 | /// [`Event::PointerEntered`]: crate::event::Event::PointerEntered 9 | pub struct PointerEnteredEvent(pub(crate) PointerId); 10 | 11 | impl PointerEnteredEvent { 12 | pub fn pointer(&self) -> &PointerId { 13 | &self.0 14 | } 15 | } 16 | 17 | #[derive(Clone, Debug)] 18 | /// See [`Event::PointerLeft`] 19 | /// 20 | /// [`Event::PointerLeft`]: crate::event::Event::PointerLeft 21 | pub struct PointerLeftEvent(pub(crate) PointerId); 22 | 23 | impl PointerLeftEvent { 24 | pub fn pointer(&self) -> &PointerId { 25 | &self.0 26 | } 27 | } 28 | 29 | #[derive(Clone, Debug)] 30 | /// See [`Event::PointerMoved`] 31 | /// 32 | /// [`Event::PointerMoved`]: crate::event::Event::PointerMoved 33 | pub struct PointerMovedEvent { 34 | pub(crate) id: PointerId, 35 | pub(crate) location: Vector2, 36 | } 37 | 38 | impl PointerMovedEvent { 39 | pub fn pointer(&self) -> &PointerId { 40 | &self.id 41 | } 42 | 43 | /// The logical location of the pointer, relative to the top-left of the window 44 | pub fn location(&self) -> Vector2 { 45 | self.location 46 | } 47 | } 48 | 49 | #[derive(Clone, Debug)] 50 | /// See [`Event::PointerInput`] 51 | /// 52 | /// [`Event::PointerInput`]: crate::event::Event::PointerInput 53 | pub struct PointerInputEvent { 54 | pub(crate) id: PointerId, 55 | pub(crate) button: MouseButton, 56 | pub(crate) is_down: bool, 57 | } 58 | 59 | impl PointerInputEvent { 60 | pub fn pointer(&self) -> &PointerId { 61 | &self.id 62 | } 63 | 64 | pub fn button(&self) -> MouseButton { 65 | self.button 66 | } 67 | 68 | /// If the button is now down, either repeating or down for the first time 69 | pub fn is_down(&self) -> bool { 70 | self.is_down 71 | } 72 | } 73 | 74 | #[derive(Clone, Copy, PartialOrd, PartialEq, Eq, Ord, Debug, Hash)] 75 | /// A unique ID for multiple mouse pointers 76 | pub struct PointerId(pub(crate) DeviceId); 77 | 78 | #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] 79 | /// A button on a standard 3-button mouse 80 | pub enum MouseButton { 81 | Left, 82 | Middle, 83 | Right, 84 | Other(u16), 85 | } 86 | 87 | impl From for MouseButton { 88 | fn from(mb: winit::event::MouseButton) -> Self { 89 | match mb { 90 | winit::event::MouseButton::Left => MouseButton::Left, 91 | winit::event::MouseButton::Right => MouseButton::Right, 92 | winit::event::MouseButton::Middle => MouseButton::Middle, 93 | winit::event::MouseButton::Other(x) => MouseButton::Other(x), 94 | } 95 | } 96 | } 97 | 98 | #[derive(Copy, Clone, Debug, PartialEq)] 99 | /// A measure of how much was scrolled in an event 100 | pub enum ScrollDelta { 101 | /// This many lines of text were scrolled 102 | Lines(Vector2), 103 | /// This many input pixels were scrolled 104 | Pixels(Vector2), 105 | } 106 | 107 | impl From for ScrollDelta { 108 | fn from(msd: MSD) -> Self { 109 | match msd { 110 | MSD::LineDelta(x, y) => Self::Lines(Vector2 { x, y }), 111 | MSD::PixelDelta(PhysicalPosition { x, y }) => Self::Pixels(Vector2 { 112 | x: x as f32, 113 | y: y as f32, 114 | }), 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/event_cache.rs: -------------------------------------------------------------------------------- 1 | //! An optional module to store the current state of input devices 2 | //! 3 | //! While the least error-prone way of handling input is to process events as they come in and 4 | //! update your application's state accordingly, sometimes it is convenient or ergonomic to refer 5 | //! to the global state of the input devices. The [`EventCache`] and [`CachedEventStream`] are 6 | //! designed to make this easy and avoid some non-obvious pitfalls. 7 | use crate::{ 8 | Event, EventStream, GamepadAxis, GamepadButton, GamepadId, Key, MouseButton, PointerId, 9 | }; 10 | 11 | use enum_map::EnumMap; 12 | use mint::Vector2; 13 | use rustc_hash::FxHashMap; 14 | 15 | /// A wrapper around [`EventStream`] and [`EventCache`] for convenience 16 | /// 17 | /// This is entirely equivalent to using a normal [`EventStream`] and passing all of its events 18 | /// into an [`EventCache`] 19 | pub struct CachedEventStream { 20 | events: EventStream, 21 | cache: EventCache, 22 | } 23 | 24 | impl CachedEventStream { 25 | pub fn new(events: EventStream) -> CachedEventStream { 26 | CachedEventStream { 27 | events, 28 | cache: EventCache::new(), 29 | } 30 | } 31 | 32 | /// See [`EventStream::next_event`] 33 | pub async fn next_event(&mut self) -> Option { 34 | let event = self.events.next_event().await; 35 | if let Some(ev) = &event { 36 | self.cache.process_event(ev); 37 | } 38 | 39 | event 40 | } 41 | 42 | pub fn cache(&self) -> &EventCache { 43 | &self.cache 44 | } 45 | } 46 | 47 | /// A struct that stores all input event values 48 | /// 49 | /// While this is mostly takes care of book keeping necessary to store input event state, it also 50 | /// handles cases like the window losing focus (which should completely reset input state). 51 | /// 52 | /// It is capable of tracking individual [`pointer`]s and [`gamepad`]s, as well as the global 53 | /// [`key`] state and [`mouse`]. 54 | /// 55 | /// [`pointer`]: EventCache::pointer 56 | /// [`gamepad`]: EventCache::gamepad 57 | /// [`key`]: EventCache::key 58 | /// [`mouse`]: EventCache::mouse 59 | #[derive(Default)] 60 | pub struct EventCache { 61 | keys: EnumMap, 62 | global_pointer: PointerState, 63 | pointers: FxHashMap, 64 | gamepads: FxHashMap, 65 | } 66 | 67 | impl EventCache { 68 | pub fn new() -> EventCache { 69 | EventCache::default() 70 | } 71 | 72 | /// Take an event and update the internal state to reflect it 73 | pub fn process_event(&mut self, event: &Event) { 74 | use Event::*; 75 | match event { 76 | KeyboardInput(ev) => { 77 | self.keys[ev.key()] = ev.is_down(); 78 | } 79 | PointerEntered(ev) => self.ensure_pointer_exists(*ev.pointer()), 80 | PointerLeft(ev) => self.ensure_pointer_exists(*ev.pointer()), 81 | PointerMoved(ev) => { 82 | let pointer = *ev.pointer(); 83 | self.ensure_pointer_exists(pointer); 84 | self.global_pointer.location = ev.location(); 85 | self.pointers 86 | .get_mut(&pointer) 87 | .expect("Internal error: pointer failed to exist") 88 | .location = ev.location(); 89 | } 90 | PointerInput(ev) => { 91 | let pointer = *ev.pointer(); 92 | self.ensure_pointer_exists(pointer); 93 | self.global_pointer 94 | .process_button(ev.button(), ev.is_down()); 95 | self.pointers 96 | .get_mut(&pointer) 97 | .expect("Internal error: pointer failed to exist") 98 | .process_button(ev.button(), ev.is_down()); 99 | } 100 | GamepadConnected(ev) => self.ensure_gamepad_exists(ev.gamepad().clone()), 101 | GamepadDisconnected(ev) => self.ensure_gamepad_exists(ev.gamepad().clone()), 102 | GamepadButton(ev) => { 103 | let gamepad = ev.gamepad(); 104 | self.ensure_gamepad_exists(gamepad.clone()); 105 | self.gamepads 106 | .get_mut(gamepad) 107 | .expect("Internal error: gamepad failed to exist") 108 | .buttons[ev.button()] = ev.is_down(); 109 | } 110 | GamepadAxis(ev) => { 111 | let gamepad = ev.gamepad(); 112 | self.ensure_gamepad_exists(gamepad.clone()); 113 | self.gamepads 114 | .get_mut(gamepad) 115 | .expect("Internal error: gamepad failed to exist") 116 | .axes[ev.axis()] = ev.value(); 117 | } 118 | FocusChanged(ev) if !ev.is_focused() => { 119 | self.clear(); 120 | } 121 | _ => (), 122 | } 123 | } 124 | 125 | fn ensure_pointer_exists(&mut self, id: PointerId) { 126 | self.pointers.insert(id, PointerState::default()); 127 | } 128 | 129 | fn ensure_gamepad_exists(&mut self, id: GamepadId) { 130 | self.gamepads.insert(id, GamepadState::default()); 131 | } 132 | 133 | /// Clear all of the state 134 | pub fn clear(&mut self) { 135 | self.keys.clear(); 136 | self.global_pointer.clear(); 137 | self.pointers.clear(); 138 | self.gamepads.clear(); 139 | } 140 | 141 | /// Check if a given key is down 142 | pub fn key(&self, key: Key) -> bool { 143 | self.keys[key] 144 | } 145 | 146 | /// The state of the global mouse 147 | /// 148 | /// Under a system with touch input or with multiple cursors, this may report erratic results. 149 | /// The state here is tracked for every pointer event, regardless of pointer ID. 150 | pub fn mouse(&self) -> &PointerState { 151 | &self.global_pointer 152 | } 153 | 154 | /// The state of the given pointer 155 | #[allow(clippy::trivially_copy_pass_by_ref)] 156 | pub fn pointer(&self, id: &PointerId) -> Option<&PointerState> { 157 | self.pointers.get(id) 158 | } 159 | 160 | /// The pointer ID and values that have been tracked 161 | pub fn pointers(&self) -> impl Iterator { 162 | self.pointers.iter() 163 | } 164 | 165 | /// The state of the given gamepad 166 | pub fn gamepad(&self, id: &GamepadId) -> Option<&GamepadState> { 167 | self.gamepads.get(id) 168 | } 169 | 170 | /// The gamepad ID and values that have been tracked 171 | pub fn gamepads(&self) -> impl Iterator { 172 | self.gamepads.iter() 173 | } 174 | } 175 | 176 | pub struct PointerState { 177 | left: bool, 178 | right: bool, 179 | middle: bool, 180 | location: Vector2, 181 | other: FxHashMap, 182 | } 183 | 184 | impl PointerState { 185 | pub fn left(&self) -> bool { 186 | self.left 187 | } 188 | 189 | pub fn right(&self) -> bool { 190 | self.right 191 | } 192 | 193 | pub fn middle(&self) -> bool { 194 | self.middle 195 | } 196 | 197 | pub fn other(&self, button: u16) -> bool { 198 | self.other.get(&button).copied().unwrap_or(false) 199 | } 200 | 201 | pub fn location(&self) -> Vector2 { 202 | self.location 203 | } 204 | 205 | fn clear(&mut self) { 206 | self.left = false; 207 | self.right = false; 208 | self.middle = false; 209 | self.other.clear(); 210 | } 211 | 212 | fn process_button(&mut self, button: MouseButton, is_down: bool) { 213 | match button { 214 | MouseButton::Left => self.left = is_down, 215 | MouseButton::Right => self.right = is_down, 216 | MouseButton::Middle => self.middle = is_down, 217 | MouseButton::Other(idx) => { 218 | self.other.insert(idx, is_down); 219 | } 220 | } 221 | } 222 | } 223 | 224 | impl Default for PointerState { 225 | fn default() -> PointerState { 226 | PointerState { 227 | left: false, 228 | right: false, 229 | middle: false, 230 | location: Vector2 { x: 0.0, y: 0.0 }, 231 | other: FxHashMap::default(), 232 | } 233 | } 234 | } 235 | 236 | #[derive(Default)] 237 | pub struct GamepadState { 238 | buttons: EnumMap, 239 | axes: EnumMap, 240 | } 241 | 242 | impl GamepadState { 243 | pub fn button(&self, button: GamepadButton) -> bool { 244 | self.buttons[button] 245 | } 246 | 247 | pub fn axis(&self, axis: GamepadAxis) -> f32 { 248 | self.axes[axis] 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /src/event_stream.rs: -------------------------------------------------------------------------------- 1 | use crate::event::Event; 2 | 3 | use futures_util::future::poll_fn; 4 | use std::cell::RefCell; 5 | use std::collections::VecDeque; 6 | use std::future::Future; 7 | use std::sync::Arc; 8 | use std::task::{Poll, Waker}; 9 | 10 | /// The source of events for a `blinds` application 11 | /// 12 | /// An `EventStream` instance is supplied by [`run`], so creating one is not necessary. Use the 13 | /// [`next_event`] function to wait for [`Event`]s. 14 | /// 15 | /// [`next_event`]: EventStream::next_event 16 | /// [`Event`]: Event 17 | /// [`run`]: crate::run() 18 | pub struct EventStream { 19 | buffer: Arc>, 20 | } 21 | 22 | impl EventStream { 23 | pub(crate) fn new() -> EventStream { 24 | EventStream { 25 | buffer: Arc::new(RefCell::new(EventBuffer { 26 | events: VecDeque::new(), 27 | waker: None, 28 | ready: false, 29 | })), 30 | } 31 | } 32 | 33 | pub(crate) fn buffer(&self) -> Arc> { 34 | self.buffer.clone() 35 | } 36 | 37 | /// Returns a future that will provide the next [`Event`], or None if the events are exhausted 38 | /// 39 | /// If there are no events, the Future will wait until new events are received, allowing the OS 40 | /// or browser to take back control of the event loop. If this doesn't get run, desktop windows 41 | /// will freeze and browser windows will lock up, so it's important to call and `.await` the 42 | /// Future even if the events are ignored. 43 | /// 44 | /// [`Event`]: Event 45 | pub fn next_event(&mut self) -> impl Future> + '_ { 46 | poll_fn(move |cx| { 47 | let mut buffer = self.buffer.borrow_mut(); 48 | match buffer.events.pop_front() { 49 | Some(event) => Poll::Ready(Some(event)), 50 | None => { 51 | if buffer.ready { 52 | buffer.ready = false; 53 | Poll::Ready(None) 54 | } else { 55 | buffer.waker = Some(cx.waker().clone()); 56 | Poll::Pending 57 | } 58 | } 59 | } 60 | }) 61 | } 62 | } 63 | 64 | pub(crate) struct EventBuffer { 65 | events: VecDeque, 66 | waker: Option, 67 | ready: bool, 68 | } 69 | 70 | impl EventBuffer { 71 | pub fn push(&mut self, event: Event) { 72 | self.events.push_back(event); 73 | self.mark_ready(); 74 | } 75 | 76 | pub fn mark_ready(&mut self) { 77 | if let Some(waker) = self.waker.take() { 78 | waker.wake(); 79 | } 80 | self.ready = true; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! `blinds` covers up the details of your windowing for you, by providing an async API. 2 | //! 3 | //! A quick example of some code that prints all incoming events: 4 | //! ```no_run 5 | //! use blinds::{run, Event, EventStream, Key, Settings, Window}; 6 | //! 7 | //! run(Settings::default(), app); 8 | //! 9 | //! async fn app(_window: Window, mut events: EventStream) { 10 | //! loop { 11 | //! while let Some(ev) = events.next_event().await { 12 | //! println!("{:?}", ev); 13 | //! } 14 | //! } 15 | //! } 16 | //! ``` 17 | //! 18 | //! The core of blinds is [`run`], which executes your app and provides your [`Window`] and 19 | //! [`EventStream`] instances. 20 | //! 21 | //! [`run`]: run() 22 | //! [`Window`]: Window 23 | //! [`EventStream`]: EventStream 24 | mod event_stream; 25 | mod run; 26 | mod settings; 27 | mod window; 28 | 29 | pub mod event; 30 | #[cfg(feature = "event-cache")] 31 | pub mod event_cache; 32 | 33 | pub use self::event::{Event, GamepadAxis, GamepadButton, GamepadId, Key, MouseButton, PointerId}; 34 | #[cfg(feature = "event-cache")] 35 | pub use self::event_cache::{CachedEventStream, EventCache}; 36 | pub use self::event_stream::EventStream; 37 | pub use self::run::run; 38 | pub use self::settings::{CursorIcon, Settings}; 39 | pub use self::window::Window; 40 | 41 | pub(crate) use self::event_stream::EventBuffer; 42 | pub(crate) use self::window::WindowContents; 43 | -------------------------------------------------------------------------------- /src/run.rs: -------------------------------------------------------------------------------- 1 | use crate::event::*; 2 | use crate::{EventBuffer, EventStream, Settings, Window, WindowContents}; 3 | use futures_executor::LocalPool; 4 | use futures_util::task::LocalSpawnExt; 5 | use std::cell::RefCell; 6 | use std::future::Future; 7 | use std::sync::Arc; 8 | use winit::event::Event as WinitEvent; 9 | use winit::event_loop::{ControlFlow, EventLoop}; 10 | 11 | /// The entry point for a blinds-based application 12 | /// 13 | /// `run` acts as the executor for your async application, and it handles your event loop on both 14 | /// desktop and web. It is a single-threaded executor, because wasm doesn't support multithreading 15 | /// at the moment. 16 | /// 17 | /// Currently blinds only supports one window, and `settings` determines how it will be 18 | /// constructed. 19 | pub fn run(settings: Settings, app: F) -> ! 20 | where 21 | T: 'static + Future, 22 | F: 'static + FnOnce(Window, EventStream) -> T, 23 | { 24 | let stream = EventStream::new(); 25 | let buffer = stream.buffer(); 26 | 27 | let event_loop = EventLoop::new(); 28 | let window = Arc::new(WindowContents::new(&event_loop, settings)); 29 | let pool = LocalPool::new(); 30 | pool.spawner() 31 | .spawn_local(app(Window(window.clone()), stream)) 32 | .expect("Failed to start application"); 33 | 34 | do_run(event_loop, window, pool, buffer) 35 | } 36 | 37 | fn do_run( 38 | event_loop: EventLoop<()>, 39 | window: Arc, 40 | mut pool: LocalPool, 41 | buffer: Arc>, 42 | ) -> ! { 43 | #[cfg(feature = "gilrs")] 44 | let mut gilrs = gilrs::Gilrs::new(); 45 | 46 | let mut finished = pool.try_run_one(); 47 | 48 | event_loop.run(move |event, _, ctrl| { 49 | match event { 50 | WinitEvent::NewEvents(winit::event::StartCause::Init) => { 51 | *ctrl = ControlFlow::Poll; 52 | } 53 | WinitEvent::WindowEvent { event, .. } => { 54 | if let winit::event::WindowEvent::CloseRequested = &event { 55 | *ctrl = ControlFlow::Exit; 56 | } 57 | if let winit::event::WindowEvent::Resized(size) = &event { 58 | window.resize(*size); 59 | } 60 | if let Some(event) = window_event(event, &window) { 61 | buffer.borrow_mut().push(event); 62 | } 63 | } 64 | WinitEvent::LoopDestroyed | WinitEvent::MainEventsCleared => { 65 | buffer.borrow_mut().mark_ready(); 66 | #[cfg(feature = "gilrs")] 67 | process_gilrs_events(&mut gilrs, &buffer); 68 | finished = pool.try_run_one(); 69 | } 70 | _ => (), 71 | } 72 | if finished { 73 | *ctrl = ControlFlow::Exit; 74 | } 75 | }) 76 | } 77 | 78 | #[cfg(feature = "gilrs")] 79 | fn process_gilrs_events( 80 | gilrs: &mut Result, 81 | buffer: &Arc>, 82 | ) { 83 | if let Ok(gilrs) = gilrs.as_mut() { 84 | while let Some(ev) = gilrs.next_event() { 85 | if let Some(ev) = gamepad_event(ev) { 86 | buffer.borrow_mut().push(ev); 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/settings.rs: -------------------------------------------------------------------------------- 1 | use mint::Vector2; 2 | 3 | /// The various options to pass to the Window and/or GL context 4 | #[derive(Clone, PartialEq)] 5 | pub struct Settings { 6 | /// The size of the window 7 | pub size: Vector2, 8 | /// If the cursor should be visible over the application, or if the cursor should be hidden 9 | pub cursor_icon: Option, 10 | /// If the application should be fullscreen 11 | pub fullscreen: bool, 12 | /// The icon on the window or the favicon on the tab 13 | #[cfg(feature = "image")] 14 | pub icon_path: Option<&'static str>, 15 | /// How many samples to do for MSAA 16 | /// 17 | /// By default it is None; if it is Some, it should be a non-zero power of two 18 | /// 19 | /// Does nothing on web currently 20 | pub multisampling: Option, 21 | /// Enable or disable vertical sync 22 | /// 23 | /// Does nothing on web; defaults to true 24 | pub vsync: bool, 25 | /// If the window can be resized by the user 26 | /// 27 | /// Does nothing on web; defaults to false 28 | pub resizable: bool, 29 | /// The title of your application 30 | pub title: &'static str, 31 | } 32 | 33 | impl Default for Settings { 34 | fn default() -> Settings { 35 | Settings { 36 | size: Vector2 { 37 | x: 1024.0, 38 | y: 768.0, 39 | }, 40 | cursor_icon: Some(CursorIcon::Default), 41 | fullscreen: false, 42 | #[cfg(feature = "image")] 43 | icon_path: None, 44 | multisampling: None, 45 | vsync: true, 46 | resizable: false, 47 | title: "", 48 | } 49 | } 50 | } 51 | 52 | /// The options for the cursor icon 53 | #[derive(Copy, Clone, PartialEq, Eq, Hash)] 54 | pub enum CursorIcon { 55 | Default, 56 | Crosshair, 57 | Hand, 58 | Arrow, 59 | Move, 60 | Text, 61 | Wait, 62 | Help, 63 | Progress, 64 | NotAllowed, 65 | ContextMenu, 66 | Cell, 67 | VerticalText, 68 | Alias, 69 | Copy, 70 | NoDrop, 71 | Grab, 72 | Grabbing, 73 | AllScroll, 74 | ZoomIn, 75 | ZoomOut, 76 | EResize, 77 | NResize, 78 | NeResize, 79 | NwResize, 80 | SResize, 81 | SeResize, 82 | SwResize, 83 | WResize, 84 | EwResize, 85 | NsResize, 86 | NeswResize, 87 | NwseResize, 88 | ColResize, 89 | RowResize, 90 | } 91 | 92 | impl Default for CursorIcon { 93 | fn default() -> CursorIcon { 94 | CursorIcon::Default 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/window.rs: -------------------------------------------------------------------------------- 1 | use crate::{CursorIcon, Settings}; 2 | #[cfg(not(target_arch = "wasm32"))] 3 | use glutin::{PossiblyCurrent, WindowedContext}; 4 | use mint::Vector2; 5 | use std::sync::Arc; 6 | use winit::dpi::{LogicalSize, PhysicalSize}; 7 | use winit::event_loop::EventLoop; 8 | use winit::monitor::MonitorHandle; 9 | use winit::window::{Fullscreen, Window as WinitWindow, WindowBuilder}; 10 | 11 | /// The Window for your blinds application 12 | pub struct Window(pub(crate) Arc); 13 | 14 | pub(crate) struct WindowContents { 15 | #[cfg(target_arch = "wasm32")] 16 | window: WinitWindow, 17 | #[cfg(not(target_arch = "wasm32"))] 18 | window: WindowedContext, 19 | } 20 | 21 | fn fullscreen_convert(fullscreen: bool, monitor: Option) -> Option { 22 | if fullscreen { 23 | Some(Fullscreen::Borderless(monitor)) 24 | } else { 25 | None 26 | } 27 | } 28 | 29 | #[cfg(target_arch = "wasm32")] 30 | fn insert_canvas(window: &WinitWindow, _settings: &Settings) -> web_sys::HtmlCanvasElement { 31 | use winit::platform::web::WindowExtWebSys; 32 | let canvas = window.canvas(); 33 | let window = web_sys::window().expect("Failed to obtain window"); 34 | let document = window.document().expect("Failed to obtain document"); 35 | 36 | document 37 | .body() 38 | .expect("Document has no body node") 39 | .append_child(&canvas) 40 | .expect("Failed to insert canvas"); 41 | 42 | canvas.focus().unwrap(); 43 | 44 | #[cfg(feature = "favicon")] 45 | { 46 | if let Some(path) = _settings.icon_path { 47 | let head = document.head().expect("Failed to find head node"); 48 | let element = document 49 | .create_element("link") 50 | .expect("Failed to create link element"); 51 | element 52 | .set_attribute("rel", "shortcut icon") 53 | .expect("Failed to create favicon element"); 54 | element 55 | .set_attribute("type", "image/png") 56 | .expect("Failed to create favicon element"); 57 | element 58 | .set_attribute("href", path) 59 | .expect("Failed to create favicon element"); 60 | head.append_child(&element).expect("Failed to add favicon"); 61 | } 62 | } 63 | 64 | canvas 65 | } 66 | 67 | fn settings_to_wb(el: &EventLoop<()>, settings: &Settings) -> WindowBuilder { 68 | #[cfg(feature = "image")] 69 | let icon = settings.icon_path.map(|path| { 70 | let img = image::open(path).expect("Failed to load image"); 71 | let rgba = img.to_rgba8(); 72 | let (width, height) = rgba.dimensions(); 73 | let buffer = rgba.into_raw(); 74 | 75 | winit::window::Icon::from_rgba(buffer, width, height).expect("Bad image data") 76 | }); 77 | #[cfg(not(feature = "image"))] 78 | let icon = None; 79 | 80 | let scale = el.primary_monitor().map_or(1.0, |m| m.scale_factor()); 81 | 82 | WindowBuilder::new() 83 | .with_inner_size(PhysicalSize { 84 | width: settings.size.x as f64 * scale, 85 | height: settings.size.y as f64 * scale, 86 | }) 87 | .with_resizable(settings.resizable) 88 | .with_fullscreen(fullscreen_convert( 89 | settings.fullscreen, 90 | el.primary_monitor(), 91 | )) 92 | .with_title(settings.title) 93 | .with_window_icon(icon) 94 | } 95 | 96 | impl WindowContents { 97 | pub(crate) fn new(el: &EventLoop<()>, settings: Settings) -> WindowContents { 98 | let wb = settings_to_wb(el, &settings); 99 | #[cfg(target_arch = "wasm32")] 100 | let window = { 101 | let window = wb.build(el).expect("Failed to create window"); 102 | insert_canvas(&window, &settings); 103 | WindowContents { window } 104 | }; 105 | #[cfg(not(target_arch = "wasm32"))] 106 | let window = { 107 | let mut cb = glutin::ContextBuilder::new().with_vsync(settings.vsync); 108 | if let Some(msaa) = settings.multisampling { 109 | cb = cb.with_multisampling(msaa); 110 | } 111 | let window = cb.build_windowed(wb, el).expect("Failed to create window"); 112 | let window = unsafe { window.make_current().expect("Failed to acquire GL context") }; 113 | WindowContents { window } 114 | }; 115 | window.set_cursor_icon(settings.cursor_icon); 116 | window.set_title(settings.title); 117 | 118 | window 119 | } 120 | 121 | fn set_cursor_icon(&self, icon: Option) { 122 | match icon { 123 | Some(icon) => { 124 | self.window().set_cursor_visible(true); 125 | self.window().set_cursor_icon(icon_to_winit(icon)); 126 | } 127 | None => { 128 | self.window().set_cursor_visible(false); 129 | } 130 | } 131 | } 132 | 133 | fn set_title(&self, title: &str) { 134 | #[cfg(not(target_arch = "wasm32"))] 135 | self.window().set_title(title); 136 | 137 | #[cfg(target_arch = "wasm32")] 138 | web_sys::window() 139 | .expect("Failed to obtain window") 140 | .document() 141 | .expect("Failed to obtain document") 142 | .set_title(title); 143 | } 144 | 145 | pub(crate) fn resize(&self, _size: PhysicalSize) { 146 | #[cfg(not(target_arch = "wasm32"))] 147 | self.window.resize(_size); 148 | } 149 | 150 | pub(crate) fn scale(&self) -> f32 { 151 | self.window().scale_factor() as f32 152 | } 153 | 154 | #[inline] 155 | fn window(&self) -> &WinitWindow { 156 | #[cfg(target_arch = "wasm32")] 157 | return &self.window; 158 | #[cfg(not(target_arch = "wasm32"))] 159 | return self.window.window(); 160 | } 161 | } 162 | 163 | impl Window { 164 | /// Set the cursor icon to some value, or set it to invisible (None) 165 | pub fn set_cursor_icon(&self, icon: Option) { 166 | self.0.set_cursor_icon(icon); 167 | } 168 | 169 | /// Get the size of the window in logical units 170 | /// 171 | /// On a high-dpi display, this doesn't correspond to physical pixels and must be multiplied by 172 | /// [`scale`] when passing sizes to functions like `glViewport`. 173 | /// 174 | /// [`scale`]: Window::scale_factor 175 | pub fn size(&self) -> Vector2 { 176 | let size = self.0.window().inner_size(); 177 | let size: LogicalSize = size.to_logical(self.0.window().scale_factor()); 178 | Vector2 { 179 | x: size.width as f32, 180 | y: size.height as f32, 181 | } 182 | } 183 | 184 | /// The DPI scale factor of the window 185 | /// 186 | /// For a good example of DPI scale factors, see the [winit docs] on the subject 187 | /// 188 | /// [winit docs]: winit::dpi 189 | pub fn scale_factor(&self) -> f32 { 190 | self.0.scale() 191 | } 192 | 193 | /// Set the size of the inside of the window in logical units 194 | pub fn set_size(&self, size: Vector2) { 195 | let scale = self.0.window().scale_factor(); 196 | self.0.window().set_inner_size( 197 | LogicalSize { 198 | width: size.x as f64, 199 | height: size.y as f64, 200 | } 201 | .to_physical::(scale), 202 | ); 203 | } 204 | 205 | /// Set the title of the window or browser tab 206 | pub fn set_title(&self, title: &str) { 207 | self.0.set_title(title); 208 | } 209 | 210 | /// Set if the window should be fullscreen or not 211 | /// 212 | /// On desktop, it will instantly become fullscreen (borderless windowed on Windows and Linux, 213 | /// and fullscreen on macOS). On web, it will become fullscreen after the next user 214 | /// interaction, due to browser API restrictions. 215 | pub fn set_fullscreen(&self, fullscreen: bool) { 216 | self.0.window().set_fullscreen(fullscreen_convert( 217 | fullscreen, 218 | self.0.window().current_monitor(), 219 | )); 220 | } 221 | 222 | #[cfg(not(target_arch = "wasm32"))] 223 | /// Return the address of a given OpenGL function 224 | pub fn get_proc_address(&self, func: &str) -> *const core::ffi::c_void { 225 | self.0.window.get_proc_address(func) 226 | } 227 | 228 | #[cfg(target_arch = "wasm32")] 229 | /// Create a WebGL context from the backing canvas 230 | pub fn webgl_context(&self) -> web_sys::WebGlRenderingContext { 231 | use js_sys::{Map, Object}; 232 | use wasm_bindgen::{JsCast, JsValue}; 233 | use winit::platform::web::WindowExtWebSys; 234 | let map = Map::new(); 235 | map.set(&JsValue::from_str("premultipliedAlpha"), &JsValue::FALSE); 236 | map.set(&JsValue::from_str("alpha"), &JsValue::FALSE); 237 | let props = Object::from_entries(&map).expect("Failed to create object"); 238 | 239 | self.0 240 | .window 241 | .canvas() 242 | .get_context_with_context_options("webgl", &props) 243 | .expect("Failed to acquire a WebGL rendering context") 244 | .expect("Failed to acquire a WebGL rendering context") 245 | .dyn_into::() 246 | .expect("WebGL context of unexpected type") 247 | } 248 | 249 | /// Draw the OpenGL frame to the screen 250 | /// 251 | /// If vsync is enabled, this will block until the frame is completed on desktop. On web, there 252 | /// is no way to control vsync, or to manually control presentation, so this function is a 253 | /// no-op. 254 | pub fn present(&self) { 255 | #[cfg(not(target_arch = "wasm32"))] 256 | self.0 257 | .window 258 | .swap_buffers() 259 | .expect("Failed to swap buffers") 260 | } 261 | } 262 | 263 | fn icon_to_winit(cursor: CursorIcon) -> winit::window::CursorIcon { 264 | use CursorIcon::*; 265 | match cursor { 266 | Default => winit::window::CursorIcon::Default, 267 | Crosshair => winit::window::CursorIcon::Crosshair, 268 | Hand => winit::window::CursorIcon::Hand, 269 | Arrow => winit::window::CursorIcon::Arrow, 270 | Move => winit::window::CursorIcon::Move, 271 | Text => winit::window::CursorIcon::Text, 272 | Wait => winit::window::CursorIcon::Wait, 273 | Help => winit::window::CursorIcon::Help, 274 | Progress => winit::window::CursorIcon::Progress, 275 | NotAllowed => winit::window::CursorIcon::NotAllowed, 276 | ContextMenu => winit::window::CursorIcon::ContextMenu, 277 | Cell => winit::window::CursorIcon::Cell, 278 | VerticalText => winit::window::CursorIcon::VerticalText, 279 | Alias => winit::window::CursorIcon::Alias, 280 | Copy => winit::window::CursorIcon::Copy, 281 | NoDrop => winit::window::CursorIcon::NoDrop, 282 | Grab => winit::window::CursorIcon::Grab, 283 | Grabbing => winit::window::CursorIcon::Grabbing, 284 | AllScroll => winit::window::CursorIcon::AllScroll, 285 | ZoomIn => winit::window::CursorIcon::ZoomIn, 286 | ZoomOut => winit::window::CursorIcon::ZoomOut, 287 | EResize => winit::window::CursorIcon::EResize, 288 | NResize => winit::window::CursorIcon::NResize, 289 | NeResize => winit::window::CursorIcon::NeResize, 290 | NwResize => winit::window::CursorIcon::NwResize, 291 | SResize => winit::window::CursorIcon::SResize, 292 | SeResize => winit::window::CursorIcon::SeResize, 293 | SwResize => winit::window::CursorIcon::SwResize, 294 | WResize => winit::window::CursorIcon::WResize, 295 | EwResize => winit::window::CursorIcon::EwResize, 296 | NsResize => winit::window::CursorIcon::NsResize, 297 | NeswResize => winit::window::CursorIcon::NeswResize, 298 | NwseResize => winit::window::CursorIcon::NwseResize, 299 | ColResize => winit::window::CursorIcon::ColResize, 300 | RowResize => winit::window::CursorIcon::RowResize, 301 | } 302 | } 303 | --------------------------------------------------------------------------------