From 0d4e6a98228e720180211da9d3cf8021dbaf4a76 Mon Sep 17 00:00:00 2001 From: shivshank Date: Sat, 28 Jul 2018 16:48:23 -0400 Subject: [PATCH] Add support for basic input handling without breakout --- src/breakout.rs | 65 ++++++++++++++++++++++++++++++++++++- src/core.rs | 85 +++++++++++++++++++++++++++++++++++++++++++++++-- src/lib.rs | 23 +++++++++++-- 3 files changed, 168 insertions(+), 5 deletions(-) diff --git a/src/breakout.rs b/src/breakout.rs index f179a67..6374a9c 100644 --- a/src/breakout.rs +++ b/src/breakout.rs @@ -1,8 +1,71 @@ -use glutin::{GlWindow, EventsLoop}; +use glutin::{ + GlWindow, + EventsLoop, + VirtualKeyCode, + MouseButton, + ModifiersState, +}; use core::Framebuffer; +use std::collections::HashMap; + pub struct GlutinBreakout { pub events_loop: EventsLoop, pub gl_window: GlWindow, pub fb: Framebuffer, } + +pub struct BasicInput { + /// The mouse position in buffer coordinates + pub mouse_pos: (usize, usize), + /// Stores whether a mouse button was down and is down, in that order. + /// + /// If a button has not been pressed yet it will not be in the map. + pub mouse: HashMap, + /// Stores the previous and current "key down" states, in that order. + /// + /// If a key has not been pressed yet it will not be in the map. + pub keys: HashMap, + pub modifiers: ModifiersState, + pub resized: bool, +} + +impl BasicInput { + /// If the mouse was pressed this last frame. + pub fn mouse_pressed(&self, button: MouseButton) -> bool { + &(false, true) == self.mouse.get(&button).unwrap_or(&(false, false)) + } + + /// If the mouse is currently down. + pub fn mouse_is_down(&self, button: MouseButton) -> bool { + if let &(_, true) = self.mouse.get(&button).unwrap_or(&(false, false)) { + true + } else { + false + } + } + + /// If the mouse was released this last frame. + pub fn mouse_released(&self, button: MouseButton) -> bool { + &(true, false) == self.mouse.get(&button).unwrap_or(&(false, false)) + } + + /// If the key was pressed this last frame. + pub fn key_pressed(&self, button: VirtualKeyCode) -> bool { + &(false, true) == self.keys.get(&button).unwrap_or(&(false, false)) + } + + /// If the key is currently down. + pub fn key_is_down(&self, button: VirtualKeyCode) -> bool { + if let &(_, true) = self.keys.get(&button).unwrap_or(&(false, false)) { + true + } else { + false + } + } + + /// If the key was released this last frame. + pub fn key_released(&self, button: VirtualKeyCode) -> bool { + &(true, false) == self.keys.get(&button).unwrap_or(&(false, false)) + } +} diff --git a/src/core.rs b/src/core.rs index 98347be..ab5f9a1 100644 --- a/src/core.rs +++ b/src/core.rs @@ -1,4 +1,4 @@ -use breakout::GlutinBreakout; +use breakout::{GlutinBreakout, BasicInput}; use rustic_gl; @@ -13,12 +13,13 @@ use glutin::{ VirtualKeyCode, ElementState, }; -use glutin::dpi::LogicalSize; +use glutin::dpi::{LogicalSize, LogicalPosition}; use gl; use gl::types::*; use std::mem::size_of_val; +use std::collections::HashMap; /// Create a context using glutin given a configuration. pub fn init_glutin_context( @@ -132,6 +133,7 @@ pub fn init_framebuffer( vao, vbo, texture_format, + did_draw: false, } } @@ -242,6 +244,83 @@ impl Internal { } } + pub fn glutin_handle_basic_input bool>( + &mut self, mut handler: F + ) { + let mut running = true; + let mut input = BasicInput { + // Not sure how to set mouse pos at start + mouse_pos: (0, 0), + mouse: HashMap::new(), + keys: HashMap::new(), + modifiers: Default::default(), + resized: false, + }; + while running { + let mut new_size = None; + let mut new_mouse_pos: Option = None; + self.events_loop.poll_events(|event| { + // Copy the current states into the previous state for input + for (_, val) in &mut input.keys { + val.0 = val.1; + } + for (_, val) in &mut input.mouse { + val.0 = val.1; + } + match event { + Event::WindowEvent { event, .. } => match event { + WindowEvent::CloseRequested => running = false, + WindowEvent::KeyboardInput { input: event_input, .. } => { + if let Some(vk) = event_input.virtual_keycode { + let key = input.keys.entry(vk) + .or_insert((false, false)); + key.1 = event_input.state == ElementState::Pressed; + } + input.modifiers = event_input.modifiers; + } + WindowEvent::CursorMoved { position, modifiers, ..} => { + new_mouse_pos = Some(position); + input.modifiers = modifiers; + } + WindowEvent::MouseInput { state, button, modifiers, .. } => { + let button = input.mouse.entry(button) + .or_insert((false, false)); + button.1 = state == ElementState::Pressed; + input.modifiers = modifiers; + } + WindowEvent::Resized(logical_size) => { + new_size = Some(logical_size); + } + _ => {}, + }, + _ => {}, + } + }); + if let Some(size) = new_size { + let dpi_factor = self.gl_window.get_hidpi_factor(); + let (x, y) = size.to_physical(dpi_factor).into(); + self.resize_viewport(x, y); + input.resized = false; + } + if let Some(pos) = new_mouse_pos { + let dpi_factor = self.gl_window.get_hidpi_factor(); + let (x, y): (f64, f64) = pos.to_physical(dpi_factor).into(); + let x_scale = self.fb.buffer_width as f64 / (self.fb.vp_width as f64); + let y_scale = self.fb.buffer_height as f64 / (self.fb.vp_height as f64); + let mouse_pos = ((x * x_scale).floor() as usize, (y * y_scale).floor() as usize); + input.mouse_pos = mouse_pos; + } + + if running { + running = handler(&mut self.fb, &input); + if self.fb.did_draw { + self.gl_window.swap_buffers().unwrap(); + self.fb.did_draw = false; + } + } + } + } + pub fn glutin_breakout(self) -> GlutinBreakout { GlutinBreakout { events_loop: self.events_loop, @@ -278,6 +357,7 @@ pub struct Framebuffer { pub vao: GLuint, pub vbo: GLuint, pub texture_format: (BufferFormat, GLenum), + pub did_draw: bool, } impl Framebuffer { @@ -375,6 +455,7 @@ impl Framebuffer { gl::BindVertexArray(0); gl::UseProgram(0); } + self.did_draw = true; } pub fn relink_program(&mut self) { diff --git a/src/lib.rs b/src/lib.rs index 8e9ebcc..5a48f64 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -72,9 +72,9 @@ pub mod config; pub mod core; pub mod breakout; -pub use breakout::GlutinBreakout; +pub use breakout::{GlutinBreakout, BasicInput}; pub use config::Config; -pub use core::{Internal, BufferFormat}; +pub use core::{Internal, BufferFormat, Framebuffer}; use core::ToGlType; @@ -304,6 +304,25 @@ impl MiniGlFb { self.internal.persist_and_redraw(redraw); } + /// Provides an easy interface for rudimentary input handling. + /// + /// Automatically handles close events and partially handles resizes (the caller chooses if + /// a redraw is necessary). + /// + /// Polls for window events and summarizes the input events for you each frame. See + /// `BasicInput` for the information that is provided to you. You will need to use some + /// glutin types (which just wraps the crate winit's input types), so glutin is re-expoted + /// by this library. You can access it via `use mini_gl_fb::glutin`. + /// + /// You can cause the handler to exit by returning false from it. This does not kill the + /// window, so as long as you still have it in scope, you can actually keep using it and, + /// for example, resume handling input but with a different handler callback. + pub fn glutin_handle_basic_input bool>( + &mut self, handler: F + ) { + self.internal.glutin_handle_basic_input(handler); + } + /// Need full access to Glutin's event handling? No problem! /// /// Hands you the event loop and the window we created, so you can handle events however you