├── .gitignore ├── .travis.yml ├── Cargo.toml ├── license.txt ├── examples └── example.rs ├── readme.md └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - nightly 4 | - stable 5 | 6 | script: 7 | - cargo test 8 | - cargo test --release 9 | - cd examples 10 | - cargo build 11 | - cargo build --release 12 | 13 | branches: 14 | only: 15 | - master 16 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "winit_input_helper" 3 | repository = "https://github.com/rukai/winit_input_helper" 4 | version = "0.6.0" 5 | edition = "2018" 6 | authors = ["Rukai "] 7 | license = "MIT" 8 | description = "Processes winit events, allowing input state to be queried at any time." 9 | keywords = ["winit", "input", "helper", "state", "cache"] 10 | categories = ["game-engines", "os", "gui"] 11 | 12 | [dependencies] 13 | winit = "0.22.0" 14 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Lucas Kent 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/example.rs: -------------------------------------------------------------------------------- 1 | use winit::event::VirtualKeyCode; 2 | use winit::event_loop::{ControlFlow, EventLoop}; 3 | use winit::window::WindowBuilder; 4 | use winit_input_helper::WinitInputHelper; 5 | 6 | fn main() { 7 | let mut input = WinitInputHelper::new(); 8 | 9 | let event_loop = EventLoop::new(); 10 | let _window = WindowBuilder::new().build(&event_loop).unwrap(); 11 | 12 | event_loop.run(move |event, _, control_flow| { 13 | // Pass every event to the WindowInputHelper. 14 | // It will return true when the last event has been processed and it is time to run your application logic. 15 | if input.update(event) { 16 | // query keypresses this update 17 | if input.key_pressed(VirtualKeyCode::A) { 18 | println!("The 'A' key was pressed on the keyboard"); 19 | } 20 | 21 | if input.key_released(VirtualKeyCode::Q) || input.quit() { 22 | *control_flow = ControlFlow::Exit; 23 | return; 24 | } 25 | 26 | // query the change in mouse this update 27 | let mouse_diff = input.mouse_diff(); 28 | if mouse_diff != (0.0, 0.0) { 29 | println!("The mouse diff is: {:?}", mouse_diff); 30 | println!("The mouse position is: {:?}", input.mouse()); 31 | } 32 | 33 | // You are expected to control your own timing within this block. 34 | // Usually via rendering with vsync. 35 | // render(); 36 | } 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Winit Input Helper 2 | [![Build Status](https://travis-ci.org/rukai/winit_input_helper.svg?branch=master)](https://travis-ci.org/rukai/winit_input_helper) [![dependency status](https://deps.rs/repo/github/rukai/winit_input_helper/status.svg)](https://deps.rs/repo/github/rukai/winit_input_helper) [![Crates.io](https://img.shields.io/crates/v/winit_input_helper.svg)](https://crates.io/crates/winit_input_helper) [![Docs](https://docs.rs/winit_input_helper/badge.svg)](https://docs.rs/winit_input_helper) 3 | 4 | Processes and stores winit events, allowing input state to be queried at any time. 5 | 6 | ## How to use 7 | 8 | Each event is passed to the `WinitInputHelper` via the `update` method. 9 | 10 | The current input state can then be accessed via methods such as `key_pressed`, `key_released`, `key_held`, `mouse`, `mouse_diff` etc. 11 | 12 | To see all available methods look at [docs.rs](https://docs.rs/winit_input_helper) 13 | 14 | ```rust 15 | use winit::event::VirtualKeyCode; 16 | use winit::event_loop::{ControlFlow, EventLoop}; 17 | use winit::window::WindowBuilder; 18 | use winit_input_helper::WinitInputHelper; 19 | 20 | fn main() { 21 | let mut input = WinitInputHelper::new(); 22 | 23 | let event_loop = EventLoop::new(); 24 | let _window = WindowBuilder::new().build(&event_loop).unwrap(); 25 | 26 | event_loop.run(move |event, _, control_flow| { 27 | // Pass every event to the WindowInputHelper. 28 | // It will return true when the last event has been processed and it is time to run your application logic. 29 | if input.update(event) { 30 | // query keypresses this update 31 | if input.key_pressed(VirtualKeyCode::A) { 32 | println!("The 'A' key was pressed on the keyboard"); 33 | } 34 | 35 | if input.key_released(VirtualKeyCode::Q) || input.quit() { 36 | *control_flow = ControlFlow::Exit; 37 | return; 38 | } 39 | 40 | // query the change in mouse this update 41 | let mouse_diff = input.mouse_diff(); 42 | if mouse_diff != (0.0, 0.0) { 43 | println!("The mouse diff is: {:?}", mouse_diff); 44 | println!("The mouse position is: {:?}", input.mouse()); 45 | } 46 | 47 | // You are expected to control your own timing within this block. 48 | // Usually via rendering with vsync. 49 | // render(); 50 | } 51 | }); 52 | } 53 | ``` 54 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use winit::event::{Event, WindowEvent, VirtualKeyCode, ElementState, MouseButton, MouseScrollDelta}; 2 | use winit::dpi::PhysicalSize; 3 | 4 | use std::path::PathBuf; 5 | 6 | /// The main struct of the API. 7 | /// 8 | /// Create with `WinitInputHelper::new`. 9 | /// Call `update` for every `winit::event::Event` you receive from winit. 10 | /// Run your application logic when `update` returns true, callng any of the accessor methods you need. 11 | #[derive(Clone)] 12 | pub struct WinitInputHelper { 13 | current: Option, 14 | dropped_file: Option, 15 | window_resized: Option>, 16 | scale_factor_changed: Option, 17 | quit: bool, 18 | } 19 | 20 | impl WinitInputHelper { 21 | pub fn new() -> WinitInputHelper { 22 | WinitInputHelper { 23 | current: Some(CurrentInput::new()), 24 | dropped_file: None, 25 | window_resized: None, 26 | scale_factor_changed: None, 27 | quit: false, 28 | } 29 | } 30 | 31 | /// Pass every winit event to this function and run your application logic when it returns true. 32 | /// 33 | /// The following winit events are handled: 34 | /// * `Event::NewEvents` clears all internal state. 35 | /// * `Event::MainEventsCleared` causes this function to return true. 36 | /// * `Event::WindowEvent` updates internal state, this will affect the result of accessor methods immediately. 37 | pub fn update(&mut self, event: Event) -> bool { 38 | match &event { 39 | Event::NewEvents (_) => { 40 | self.dropped_file = None; 41 | self.window_resized = None; 42 | self.scale_factor_changed = None; 43 | if let Some(ref mut current) = self.current { 44 | current.step(); 45 | } 46 | 47 | false 48 | } 49 | Event::MainEventsCleared => { 50 | true 51 | } 52 | _ => { 53 | if let Event::WindowEvent { event, .. } = event { 54 | match event { 55 | WindowEvent::CloseRequested | 56 | WindowEvent::Destroyed => { self.quit = true } 57 | WindowEvent::Focused (false) => { self.current = None } 58 | WindowEvent::Focused (true) => { self.current = Some(CurrentInput::new()) } 59 | WindowEvent::DroppedFile (ref path) => { self.dropped_file = Some(path.clone()) } 60 | WindowEvent::Resized (ref size) => { self.window_resized = Some(size.clone()) } 61 | WindowEvent::ScaleFactorChanged { scale_factor, .. } => { self.scale_factor_changed = Some(scale_factor) } 62 | _ => { } 63 | } 64 | if let Some(ref mut current) = self.current { 65 | current.handle_event(event); 66 | } 67 | } 68 | 69 | false 70 | } 71 | } 72 | } 73 | 74 | /// Returns true when the specified keyboard key goes from "not pressed" to "pressed" 75 | /// Otherwise returns false 76 | pub fn key_pressed(&self, check_key_code: VirtualKeyCode) -> bool { 77 | if let Some(ref current) = self.current { 78 | for action in ¤t.key_actions { 79 | if let &KeyAction::Pressed(key_code) = action { 80 | if key_code == check_key_code { 81 | return true; 82 | } 83 | } 84 | } 85 | } 86 | false 87 | } 88 | 89 | /// Returns true when the specified mouse button goes from "not pressed" to "pressed" 90 | /// Otherwise returns false 91 | /// 92 | /// Left => 0 93 | /// Right => 1 94 | /// Middle => 2 95 | /// Other => 3..255 96 | pub fn mouse_pressed(&self, check_mouse_button: usize) -> bool { 97 | // TODO: Take MouseButton instead of usize 98 | if let Some(ref current) = self.current { 99 | for action in ¤t.mouse_actions { 100 | if let &MouseAction::Pressed(key_code) = action { 101 | if key_code == check_mouse_button { 102 | return true; 103 | } 104 | } 105 | } 106 | } 107 | false 108 | } 109 | 110 | /// Returns true when the specified keyboard key goes from "pressed" to "not pressed" 111 | /// Otherwise returns false 112 | pub fn key_released(&self, check_key_code: VirtualKeyCode) -> bool { 113 | if let Some(ref current) = self.current { 114 | for action in ¤t.key_actions { 115 | if let &KeyAction::Released(key_code) = action { 116 | if key_code == check_key_code { 117 | return true; 118 | } 119 | } 120 | } 121 | } 122 | false 123 | } 124 | 125 | /// Returns true when the specified mouse button goes from "pressed" to "not pressed" 126 | /// Otherwise returns false 127 | /// 128 | /// Left => 0 129 | /// Right => 1 130 | /// Middle => 2 131 | /// Other => 3..255 132 | pub fn mouse_released(&self, check_mouse_button: usize) -> bool { 133 | // TODO: Take MouseButton instead of usize 134 | if let Some(ref current) = self.current { 135 | for action in ¤t.mouse_actions { 136 | if let &MouseAction::Released(key_code) = action { 137 | if key_code == check_mouse_button { 138 | return true; 139 | } 140 | } 141 | } 142 | } 143 | false 144 | } 145 | 146 | /// Returns true while the specified keyboard key remains "pressed" 147 | /// Otherwise returns false 148 | pub fn key_held(&self, key_code: VirtualKeyCode) -> bool { 149 | match self.current { 150 | Some (ref current) => current.key_held[key_code as usize], 151 | None => false 152 | } 153 | } 154 | 155 | /// Returns true while the specified mouse button remains "pressed" 156 | /// Otherwise returns false 157 | /// 158 | /// Left => 0 159 | /// Right => 1 160 | /// Middle => 2 161 | /// Other => 3..255 162 | pub fn mouse_held(&self, mouse_button: usize) -> bool { 163 | // TODO: Take MouseButton instead of usize 164 | match self.current { 165 | Some (ref current) => current.mouse_held[mouse_button as usize], 166 | None => false 167 | } 168 | } 169 | 170 | /// Returns true while any shift key is held on the keyboard 171 | /// Otherwise returns false 172 | pub fn held_shift(&self) -> bool { 173 | return self.key_held(VirtualKeyCode::LShift) || self.key_held(VirtualKeyCode::RShift); 174 | } 175 | 176 | /// Returns true while any control key is held on the keyboard 177 | /// Otherwise returns false 178 | pub fn held_control(&self) -> bool { 179 | return self.key_held(VirtualKeyCode::LControl) || self.key_held(VirtualKeyCode::RControl); 180 | } 181 | 182 | /// Returns true while any alt key is held on the keyboard 183 | /// Otherwise returns false 184 | pub fn held_alt(&self) -> bool { 185 | return self.key_held(VirtualKeyCode::LAlt) || self.key_held(VirtualKeyCode::RAlt); 186 | } 187 | 188 | /// Returns `0.0` if the mouse is outside of the window. 189 | /// Otherwise returns the amount scrolled by the mouse in between the last two `update*()` calls 190 | pub fn scroll_diff(&self) -> f32 { 191 | match self.current { 192 | Some(ref current) => current.scroll_diff, 193 | None => 0.0 194 | } 195 | } 196 | 197 | /// Returns `None` when the mouse is outside of the window. 198 | /// Otherwise returns the mouse coordinates in pixels 199 | pub fn mouse(&self) -> Option<(f32, f32)> { 200 | match self.current { 201 | Some(ref current) => current.mouse_point, 202 | None => None 203 | } 204 | } 205 | 206 | /// Returns the difference in mouse coordinates between the last two `update*()` calls 207 | /// Returns `(0.0, 0.0)` if the mouse is outside of the window. 208 | pub fn mouse_diff(&self) -> (f32, f32) { 209 | if let Some(ref current_input) = self.current { 210 | if let Some(cur) = current_input.mouse_point { 211 | if let Some(prev) = current_input.mouse_point_prev { 212 | return (cur.0 - prev.0, cur.1 - prev.1); 213 | } 214 | } 215 | } 216 | (0.0, 0.0) 217 | } 218 | 219 | /// Returns `None` when the mouse is outside of the window. 220 | /// Otherwise returns the resolution of the window. 221 | pub fn resolution(&self) -> Option<(u32, u32)> { 222 | match self.current { 223 | Some(ref current) => Some(current.resolution), 224 | None => None 225 | } 226 | } 227 | 228 | /// Returns the characters pressed since the last `update*()`. 229 | /// The earlier the character was pressed, the lower the index in the Vec. 230 | pub fn text(&self) -> Vec { 231 | match self.current { 232 | Some(ref current) => current.text.clone(), 233 | None => vec!() 234 | } 235 | } 236 | 237 | /// Returns the path to a file that has been drag-and-dropped onto the window. 238 | pub fn dropped_file(&self) -> Option { 239 | self.dropped_file.clone() 240 | } 241 | 242 | /// Returns the current window size if it was resized between the last two `update*()` calls. 243 | /// Otherwise returns `None` 244 | pub fn window_resized(&self) -> Option> { 245 | self.window_resized.clone() 246 | } 247 | 248 | /// Returns the current window size if it was resized between the last two `update*()` calls. 249 | /// Otherwise returns `None` 250 | pub fn scale_factor_changed(&self) -> Option { 251 | self.scale_factor_changed 252 | } 253 | 254 | /// Returns true if the OS has requested the application to quit. 255 | /// Otherwise returns false. 256 | pub fn quit(&self) -> bool { 257 | self.quit 258 | } 259 | } 260 | 261 | /// Stores a character or a backspace. 262 | /// 263 | /// TODO: Either: 264 | /// * remove this struct and just use backspace character instead 265 | /// * move keypresses like Home, End, Left, Right, Up, Down, Return to this enum 266 | /// (advantage of using this struct is it retains sub-frame keypress ordering) 267 | #[derive(Clone)] 268 | pub enum TextChar { 269 | Char (char), 270 | Back, 271 | } 272 | 273 | #[derive(Clone)] 274 | struct CurrentInput { 275 | pub mouse_actions: Vec, 276 | pub key_actions: Vec, 277 | pub key_held: [bool; 255], 278 | pub mouse_held: [bool; 255], 279 | pub mouse_point: Option<(f32, f32)>, 280 | pub mouse_point_prev: Option<(f32, f32)>, 281 | pub scroll_diff: f32, 282 | pub scale_factor: f64, 283 | pub resolution: (u32, u32), 284 | pub text: Vec, 285 | } 286 | 287 | impl CurrentInput { 288 | pub fn new() -> CurrentInput { 289 | CurrentInput { 290 | mouse_actions: vec!(), 291 | key_actions: vec!(), 292 | key_held: [false; 255], 293 | mouse_held: [false; 255], 294 | mouse_point: None, 295 | mouse_point_prev: None, 296 | scroll_diff: 0.0, 297 | scale_factor: 1.0, 298 | resolution: (1, 1), 299 | text: vec!(), 300 | } 301 | } 302 | 303 | pub fn step(&mut self) { 304 | self.mouse_actions = vec!(); 305 | self.key_actions = vec!(); 306 | self.scroll_diff = 0.0; 307 | self.mouse_point_prev = self.mouse_point; 308 | self.text.clear(); 309 | } 310 | 311 | pub fn handle_event(&mut self, event: WindowEvent) { 312 | match event { 313 | WindowEvent::KeyboardInput { input, .. } => { 314 | if let Some(keycode) = input.virtual_keycode { 315 | match input.state { 316 | ElementState::Pressed => { 317 | self.key_held[keycode as usize] = true; 318 | self.key_actions.push(KeyAction::Pressed(keycode)); 319 | if let VirtualKeyCode::Back = keycode { 320 | self.text.push(TextChar::Back); 321 | } 322 | } 323 | ElementState::Released => { 324 | self.key_held[keycode as usize] = false; 325 | self.key_actions.push(KeyAction::Released(keycode)); 326 | } 327 | } 328 | } 329 | } 330 | WindowEvent::ReceivedCharacter (c) => { 331 | if c != '\x08' && c != '\r' && c != '\n' { 332 | self.text.push(TextChar::Char(c)); 333 | } 334 | } 335 | WindowEvent::CursorMoved { position, .. } => { 336 | self.mouse_point = Some((position.x as f32, position.y as f32)); 337 | } 338 | WindowEvent::MouseInput { state: ElementState::Pressed, button, .. } => { 339 | let button = mouse_button_to_int(button); 340 | self.mouse_held[button] = true; 341 | self.mouse_actions.push(MouseAction::Pressed(button)); 342 | } 343 | WindowEvent::MouseInput { state: ElementState::Released, button, .. } => { 344 | let button = mouse_button_to_int(button); 345 | self.mouse_held[button] = false; 346 | self.mouse_actions.push(MouseAction::Released(button)); 347 | } 348 | WindowEvent::MouseWheel { delta, .. } => { 349 | // I just took this from three-rs, no idea why this magic number was chosen ¯\_(ツ)_/¯ 350 | const PIXELS_PER_LINE: f64 = 38.0; 351 | 352 | match delta { 353 | MouseScrollDelta::LineDelta (_, y) => { self.scroll_diff += y; } 354 | MouseScrollDelta::PixelDelta (delta) => { self.scroll_diff += (delta.y / PIXELS_PER_LINE) as f32 } 355 | } 356 | } 357 | WindowEvent::Resized (resolution) => { 358 | self.resolution = resolution.into(); 359 | } 360 | WindowEvent::ScaleFactorChanged { scale_factor, .. } => { 361 | self.scale_factor = scale_factor; 362 | } 363 | _ => {} 364 | } 365 | } 366 | } 367 | 368 | #[derive(Clone)] 369 | enum KeyAction { 370 | Pressed (VirtualKeyCode), 371 | Released (VirtualKeyCode), 372 | } 373 | 374 | #[derive(Clone)] 375 | enum MouseAction { 376 | Pressed (usize), 377 | Released (usize), 378 | } 379 | 380 | fn mouse_button_to_int(button: MouseButton) -> usize { 381 | match button { 382 | MouseButton::Left => 0, 383 | MouseButton::Right => 1, 384 | MouseButton::Middle => 2, 385 | MouseButton::Other(byte) => byte as usize 386 | } 387 | } 388 | --------------------------------------------------------------------------------