From 6c67e22812c5e5a34f679cd3c5c776abcaaa0de2 Mon Sep 17 00:00:00 2001 From: Jonathan Albert <47063257+jn-albert@users.noreply.github.com> Date: Thu, 25 Apr 2019 18:44:12 -0400 Subject: [PATCH 1/2] Add numerous events and input display example --- examples/input.rs | 316 +++++++++++++++++++++++++++++++++++ src/graphics/window/event.rs | 37 +++- src/input.rs | 36 ++++ 3 files changed, 388 insertions(+), 1 deletion(-) create mode 100644 examples/input.rs diff --git a/examples/input.rs b/examples/input.rs new file mode 100644 index 0000000..ca66d9d --- /dev/null +++ b/examples/input.rs @@ -0,0 +1,316 @@ +//! A simple example that demonstrates capturing window and input events. +use coffee::{Game, Result, Timer}; +use coffee::graphics::{ + Batch, Color, Font, Gpu, Image, Point, Rectangle, Sprite, + Text, Window, WindowSettings +}; +use coffee::input; +use coffee::load::{loading_screen, Join, LoadingScreen, Task}; + +fn main() -> Result<()> { + InputExample::run(WindowSettings { + title: String::from("Input Example - Coffee"), + size: (720, 240), + resizable: false, + }) +} + +struct Input { + cursor_position: Point, + mouse_wheel: Point, + pressed_keys: Vec, + released_keys: Vec, + pressed_mouse: Vec, + released_mouse: Vec, + text_buffer: String, +} + +impl Input { + fn new() -> Input { + Input { + cursor_position: Point::new(0.0, 0.0), + mouse_wheel: Point::new(0.0, 0.0), + pressed_keys: Vec::new(), + released_keys: Vec::new(), + pressed_mouse: Vec::new(), + released_mouse: Vec::new(), + text_buffer: String::new(), + } + } +} + +struct View { + palette: Image, + font: Font, +} + +impl View { + const COLORS: [Color; 1] = [ + Color { + r: 1.0, + g: 0.0, + b: 0.0, + a: 1.0, + }, + ]; + + fn load() -> Task { + ( + Task::using_gpu(|gpu| Image::from_colors(gpu, &Self::COLORS)), + Font::load(include_bytes!( + "../resources/font/Inconsolata-Regular.ttf" + )), + ) + .join() + .map(|(palette, font)| View { + palette, + font, + }) + } +} + +struct InputExample { + mouse_x: f32, + mouse_y: f32, + wheel_x: f32, + wheel_y: f32, + text_buffer: String, + pressed_keys: String, + released_keys: String, + pressed_mousebuttons: String, + released_mousebuttons: String, +} + +impl InputExample { + +} + +impl Game for InputExample { + type View = View; + type Input = Input; + + const TICKS_PER_SECOND: u16 = 10; + + fn new(window: &mut Window) -> Result<(InputExample, Self::View, Self::Input)> { + let task = Task::stage("Loading font...", View::load()); + + let mut loading_screen = loading_screen::ProgressBar::new(window.gpu()); + let view = loading_screen.run(task, window)?; + + Ok((InputExample{ + mouse_x: 0.0, + mouse_y: 0.0, + wheel_x: 0.0, + wheel_y: 0.0, + text_buffer: String::with_capacity(256), + pressed_keys: String::new(), + released_keys: String::new(), + pressed_mousebuttons: String::new(), + released_mousebuttons: String::new(), + }, view, Input::new())) + } + + fn on_input(&self, input: &mut Input, event: input::Event) { + match event { + input::Event::CursorMoved { x, y } => { + input.cursor_position = Point::new(x, y); + } + input::Event::TextInput { character } => { + input.text_buffer.push(character); + } + input::Event::MouseWheel { delta_x, delta_y } => { + input.mouse_wheel = Point::new(delta_x, delta_y); + } + input::Event::KeyboardInput { + key_code, + state + } => { + match state { + input::ButtonState::Pressed => input.pressed_keys.push(key_code), + input::ButtonState::Released => input.released_keys.push(key_code), + }; + } + input::Event::MouseInput { + state, + button + } => { + match state { + input::ButtonState::Pressed => input.pressed_mouse.push(button), + input::ButtonState::Released => input.released_mouse.push(button), + }; + } + _ => {} + } + } + + fn update(&mut self, _view: &Self::View, _window: &Window) { + } + + fn interact(&mut self, input: &mut Input, _view: &mut View, _gpu: &mut Gpu) { + self.mouse_x = input.cursor_position.x; + self.mouse_y = input.cursor_position.y; + + self.wheel_x = input.mouse_wheel.x; + self.wheel_y = input.mouse_wheel.y; + input.mouse_wheel.x = 0.0; + input.mouse_wheel.y = 0.0; + + if !input.text_buffer.is_empty() { + for c in input.text_buffer.chars() { + match c { + '\u{0008}' => { + self.text_buffer.pop(); + } + _ => { + if self.text_buffer.chars().count() < 30 { + self.text_buffer.push_str(&input.text_buffer); + } + } + } + } + input.text_buffer.clear(); + } + + self.pressed_keys = input.pressed_keys.iter() + .map(|k| format!("{:?}", k)) + .collect::>() + .join(", "); + + self.released_keys = input.released_keys.iter() + .map(|k| format!("{:?}", k)) + .collect::>() + .join(", "); + + input.pressed_keys.clear(); + input.released_keys.clear(); + + self.pressed_mousebuttons = input.pressed_mouse.iter() + .map(|k| format!("{:?}", k)) + .collect::>() + .join(", "); + + self.released_mousebuttons = input.released_mouse.iter() + .map(|k| format!("{:?}", k)) + .collect::>() + .join(", "); + + input.pressed_mouse.clear(); + input.released_mouse.clear(); + } + + fn draw(&self, view: &mut Self::View, window: &mut Window, _timer: &Timer) { + let mut frame = window.frame(); + frame.clear(Color::BLACK); + + view.font.add(Text { + content: String::from("Text Buffer (type):"), + position: Point::new(20.0, frame.height() - 40.0), + bounds: (frame.width(), frame.height()), + size: 20.0, + color: Color::WHITE, + }); + + view.font.add(Text { + content: self.text_buffer.clone(), + position: Point::new(280.0, frame.height() - 40.0), + bounds: (frame.width(), frame.height()), + size: 20.0, + color: Color::WHITE, + }); + + view.font.add(Text { + content: String::from("Pressed keys:"), + position: Point::new(20.0, 20.0), + bounds: (frame.width(), frame.height()), + size: 20.0, + color: Color::WHITE, + }); + + view.font.add(Text { + content: self.pressed_keys.clone(), + position: Point::new(280.0, 20.0), + bounds: (frame.width(), frame.height()), + size: 20.0, + color: Color::from_rgb(0, 255, 0), + }); + + view.font.add(Text { + content: String::from("Released keys:"), + position: Point::new(20.0, 50.0), + bounds: (frame.width(), frame.height()), + size: 20.0, + color: Color::WHITE, + }); + + view.font.add(Text { + content: self.released_keys.clone(), + position: Point::new(280.0, 50.0), + bounds: (frame.width(), frame.height()), + size: 20.0, + color: Color::from_rgb(255, 0, 0), + }); + + view.font.add(Text { + content: String::from("Mouse wheel scroll:"), + position: Point::new(20.0, 80.0), + bounds: (frame.width(), frame.height()), + size: 20.0, + color: Color::WHITE, + }); + + view.font.add(Text { + content: format!("{}, {}", self.wheel_x, self.wheel_y), + position: Point::new(280.0, 80.0), + bounds: (frame.width(), frame.height()), + size: 20.0, + color: Color::WHITE, + }); + + view.font.add(Text { + content: String::from("Pressed mouse buttons:"), + position: Point::new(20.0, 110.0), + bounds: (frame.width(), frame.height()), + size: 20.0, + color: Color::WHITE, + }); + + view.font.add(Text { + content: self.pressed_mousebuttons.clone(), + position: Point::new(280.0, 110.0), + bounds: (frame.width(), frame.height()), + size: 20.0, + color: Color::from_rgb(0, 255, 0), + }); + + view.font.add(Text { + content: String::from("Released mouse buttons:"), + position: Point::new(20.0, 140.0), + bounds: (frame.width(), frame.height()), + size: 20.0, + color: Color::WHITE, + }); + + view.font.add(Text { + content: self.released_mousebuttons.clone(), + position: Point::new(280.0, 140.0), + bounds: (frame.width(), frame.height()), + size: 20.0, + color: Color::from_rgb(255, 0, 0), + }); + + view.font.draw(&mut frame); + + let mut batch = Batch::new(view.palette.clone()); + // Draw a small square at the mouse cursor's position. + batch.add(Sprite { + source: Rectangle { + x: 0, + y: 0, + width: 6, + height: 6, + }, + position: Point::new(self.mouse_x - 3.0, self.mouse_y - 3.0) + }); + batch.draw(Point::new(0.0, 0.0), &mut frame.as_target()); + } +} \ No newline at end of file diff --git a/src/graphics/window/event.rs b/src/graphics/window/event.rs index 2a49651..fee706f 100644 --- a/src/graphics/window/event.rs +++ b/src/graphics/window/event.rs @@ -46,15 +46,50 @@ impl EventLoop { state, button, })), + winit::WindowEvent::MouseWheel { delta, .. } => match delta + { + winit::MouseScrollDelta::LineDelta(x, y) => { + f(Event::Input(input::Event::MouseWheel { + delta_x: x, + delta_y: y, + })) + } + _ => {} + }, + + winit::WindowEvent::ReceivedCharacter(codepoint) => { + f(Event::Input(input::Event::TextInput { + character: codepoint, + })) + } winit::WindowEvent::CursorMoved { position, .. } => { f(Event::CursorMoved(position)) } - winit::WindowEvent::CloseRequested => { + winit::WindowEvent::CursorEntered { .. } => { + f(Event::Input(input::Event::CursorEntered)) + } + winit::WindowEvent::CursorLeft { .. } => { + f(Event::Input(input::Event::CursorLeft)) + } + winit::WindowEvent::CloseRequested { .. } => { f(Event::CloseRequested) } winit::WindowEvent::Resized(logical_size) => { f(Event::Resized(logical_size)) } + winit::WindowEvent::Focused(focus) => { + f(Event::Input(if focus == true { + input::Event::WindowFocused + } else { + input::Event::WindowUnfocused + })) + } + winit::WindowEvent::Moved( + winit::dpi::LogicalPosition { x, y }, + ) => f(Event::Input(input::Event::WindowMoved { + x: x as f32, + y: y as f32, + })), _ => {} }, _ => (), diff --git a/src/input.rs b/src/input.rs index cd60286..e7e2d4f 100644 --- a/src/input.rs +++ b/src/input.rs @@ -29,6 +29,12 @@ pub enum Event { key_code: KeyCode, }, + /// Text was entered. + TextInput { + /// The character entered + character: char, + }, + /// The mouse cursor was moved CursorMoved { /// The X coordinate of the mouse position @@ -38,6 +44,12 @@ pub enum Event { y: f32, }, + /// The mouse cursor entered the game window. + CursorEntered, + + /// The mouse cursor left the game window. + CursorLeft, + /// A mouse button was pressed or released. MouseInput { /// The state of the button @@ -46,4 +58,28 @@ pub enum Event { /// The button identifier button: MouseButton, }, + + /// The mouse wheel was scrolled. + MouseWheel { + /// The number of horizontal lines scrolled + delta_x: f32, + + /// The number of vertical lines scrolled + delta_y: f32, + }, + + /// The game window gained focus. + WindowFocused, + + /// The game window lost focus. + WindowUnfocused, + + /// The game window was moved. + WindowMoved { + /// The new X coordinate of the window + x: f32, + + /// The new Y coordinate of the window + y: f32, + }, } From 36be3111dbc39792bbb9d907e53d80ce041e9e0f Mon Sep 17 00:00:00 2001 From: Jonathan Albert <47063257+jn-albert@users.noreply.github.com> Date: Fri, 26 Apr 2019 21:03:50 -0400 Subject: [PATCH 2/2] Revised example; fixed logical->physical coords --- examples/input.rs | 302 ++++++++++++++--------------------- src/graphics/window/event.rs | 10 +- src/lib.rs | 11 ++ 3 files changed, 136 insertions(+), 187 deletions(-) diff --git a/examples/input.rs b/examples/input.rs index ca66d9d..514300d 100644 --- a/examples/input.rs +++ b/examples/input.rs @@ -1,11 +1,13 @@ //! A simple example that demonstrates capturing window and input events. -use coffee::{Game, Result, Timer}; use coffee::graphics::{ - Batch, Color, Font, Gpu, Image, Point, Rectangle, Sprite, - Text, Window, WindowSettings + Batch, Color, Font, Gpu, Image, Point, Rectangle, Sprite, Text, Window, + WindowSettings, }; use coffee::input; use coffee::load::{loading_screen, Join, LoadingScreen, Task}; +use coffee::{Game, Result, Timer}; + +use std::collections::HashMap; fn main() -> Result<()> { InputExample::run(WindowSettings { @@ -18,10 +20,8 @@ fn main() -> Result<()> { struct Input { cursor_position: Point, mouse_wheel: Point, - pressed_keys: Vec, - released_keys: Vec, - pressed_mouse: Vec, - released_mouse: Vec, + key_state: HashMap, + mouse_state: HashMap, text_buffer: String, } @@ -30,10 +30,8 @@ impl Input { Input { cursor_position: Point::new(0.0, 0.0), mouse_wheel: Point::new(0.0, 0.0), - pressed_keys: Vec::new(), - released_keys: Vec::new(), - pressed_mouse: Vec::new(), - released_mouse: Vec::new(), + key_state: HashMap::new(), + mouse_state: HashMap::new(), text_buffer: String::new(), } } @@ -45,14 +43,12 @@ struct View { } impl View { - const COLORS: [Color; 1] = [ - Color { - r: 1.0, - g: 0.0, - b: 0.0, - a: 1.0, - }, - ]; + const COLORS: [Color; 1] = [Color { + r: 1.0, + g: 0.0, + b: 0.0, + a: 1.0, + }]; fn load() -> Task { ( @@ -62,10 +58,7 @@ impl View { )), ) .join() - .map(|(palette, font)| View { - palette, - font, - }) + .map(|(palette, font)| View { palette, font }) } } @@ -75,14 +68,12 @@ struct InputExample { wheel_x: f32, wheel_y: f32, text_buffer: String, - pressed_keys: String, - released_keys: String, - pressed_mousebuttons: String, - released_mousebuttons: String, + keys_down: String, + mousebuttons_down: String, } impl InputExample { - + const MAX_TEXTSIZE: usize = 40; } impl Game for InputExample { @@ -91,23 +82,27 @@ impl Game for InputExample { const TICKS_PER_SECOND: u16 = 10; - fn new(window: &mut Window) -> Result<(InputExample, Self::View, Self::Input)> { + fn new( + window: &mut Window, + ) -> Result<(InputExample, Self::View, Self::Input)> { let task = Task::stage("Loading font...", View::load()); let mut loading_screen = loading_screen::ProgressBar::new(window.gpu()); let view = loading_screen.run(task, window)?; - Ok((InputExample{ - mouse_x: 0.0, + Ok(( + InputExample { + mouse_x: 0.0, mouse_y: 0.0, wheel_x: 0.0, wheel_y: 0.0, - text_buffer: String::with_capacity(256), - pressed_keys: String::new(), - released_keys: String::new(), - pressed_mousebuttons: String::new(), - released_mousebuttons: String::new(), - }, view, Input::new())) + text_buffer: String::with_capacity(Self::MAX_TEXTSIZE), + keys_down: String::new(), + mousebuttons_down: String::new(), + }, + view, + Input::new(), + )) } fn on_input(&self, input: &mut Input, event: input::Event) { @@ -121,48 +116,52 @@ impl Game for InputExample { input::Event::MouseWheel { delta_x, delta_y } => { input.mouse_wheel = Point::new(delta_x, delta_y); } - input::Event::KeyboardInput { - key_code, - state - } => { - match state { - input::ButtonState::Pressed => input.pressed_keys.push(key_code), - input::ButtonState::Released => input.released_keys.push(key_code), - }; + input::Event::KeyboardInput { key_code, state } => { + input.key_state.insert( + key_code, + match state { + input::ButtonState::Pressed => true, + input::ButtonState::Released => false, + }, + ); } - input::Event::MouseInput { - state, - button - } => { - match state { - input::ButtonState::Pressed => input.pressed_mouse.push(button), - input::ButtonState::Released => input.released_mouse.push(button), - }; + input::Event::MouseInput { state, button } => { + input.mouse_state.insert( + button, + match state { + input::ButtonState::Pressed => true, + input::ButtonState::Released => false, + }, + ); } _ => {} } } - fn update(&mut self, _view: &Self::View, _window: &Window) { - } + fn update(&mut self, _view: &Self::View, _window: &Window) {} - fn interact(&mut self, input: &mut Input, _view: &mut View, _gpu: &mut Gpu) { + fn interact( + &mut self, + input: &mut Input, + _view: &mut View, + _gpu: &mut Gpu, + ) { self.mouse_x = input.cursor_position.x; self.mouse_y = input.cursor_position.y; self.wheel_x = input.mouse_wheel.x; self.wheel_y = input.mouse_wheel.y; - input.mouse_wheel.x = 0.0; - input.mouse_wheel.y = 0.0; if !input.text_buffer.is_empty() { for c in input.text_buffer.chars() { match c { + // Match ASCII backspace and delete from the text buffer '\u{0008}' => { self.text_buffer.pop(); } _ => { - if self.text_buffer.chars().count() < 30 { + if self.text_buffer.chars().count() < Self::MAX_TEXTSIZE + { self.text_buffer.push_str(&input.text_buffer); } } @@ -171,132 +170,73 @@ impl Game for InputExample { input.text_buffer.clear(); } - self.pressed_keys = input.pressed_keys.iter() - .map(|k| format!("{:?}", k)) - .collect::>() - .join(", "); - - self.released_keys = input.released_keys.iter() - .map(|k| format!("{:?}", k)) - .collect::>() - .join(", "); - - input.pressed_keys.clear(); - input.released_keys.clear(); - - self.pressed_mousebuttons = input.pressed_mouse.iter() - .map(|k| format!("{:?}", k)) - .collect::>() - .join(", "); - - self.released_mousebuttons = input.released_mouse.iter() - .map(|k| format!("{:?}", k)) - .collect::>() - .join(", "); - - input.pressed_mouse.clear(); - input.released_mouse.clear(); + self.keys_down = input + .key_state + .iter() + .filter(|(_, &v)| v == true) + .map(|(k, _)| format!("{:?}", k)) + .collect::>() + .join(", "); + + self.mousebuttons_down = input + .mouse_state + .iter() + .filter(|(_, &v)| v == true) + .map(|(k, _)| format!("{:?}", k)) + .collect::>() + .join(", "); } fn draw(&self, view: &mut Self::View, window: &mut Window, _timer: &Timer) { let mut frame = window.frame(); frame.clear(Color::BLACK); - view.font.add(Text { - content: String::from("Text Buffer (type):"), - position: Point::new(20.0, frame.height() - 40.0), - bounds: (frame.width(), frame.height()), - size: 20.0, - color: Color::WHITE, - }); - - view.font.add(Text { - content: self.text_buffer.clone(), - position: Point::new(280.0, frame.height() - 40.0), - bounds: (frame.width(), frame.height()), - size: 20.0, - color: Color::WHITE, - }); - - view.font.add(Text { - content: String::from("Pressed keys:"), - position: Point::new(20.0, 20.0), - bounds: (frame.width(), frame.height()), - size: 20.0, - color: Color::WHITE, - }); - - view.font.add(Text { - content: self.pressed_keys.clone(), - position: Point::new(280.0, 20.0), - bounds: (frame.width(), frame.height()), - size: 20.0, - color: Color::from_rgb(0, 255, 0), - }); - - view.font.add(Text { - content: String::from("Released keys:"), - position: Point::new(20.0, 50.0), - bounds: (frame.width(), frame.height()), - size: 20.0, - color: Color::WHITE, - }); - - view.font.add(Text { - content: self.released_keys.clone(), - position: Point::new(280.0, 50.0), - bounds: (frame.width(), frame.height()), - size: 20.0, - color: Color::from_rgb(255, 0, 0), - }); - - view.font.add(Text { - content: String::from("Mouse wheel scroll:"), - position: Point::new(20.0, 80.0), - bounds: (frame.width(), frame.height()), - size: 20.0, - color: Color::WHITE, - }); - - view.font.add(Text { - content: format!("{}, {}", self.wheel_x, self.wheel_y), - position: Point::new(280.0, 80.0), - bounds: (frame.width(), frame.height()), - size: 20.0, - color: Color::WHITE, - }); - - view.font.add(Text { - content: String::from("Pressed mouse buttons:"), - position: Point::new(20.0, 110.0), - bounds: (frame.width(), frame.height()), - size: 20.0, - color: Color::WHITE, - }); - - view.font.add(Text { - content: self.pressed_mousebuttons.clone(), - position: Point::new(280.0, 110.0), - bounds: (frame.width(), frame.height()), - size: 20.0, - color: Color::from_rgb(0, 255, 0), - }); - - view.font.add(Text { - content: String::from("Released mouse buttons:"), - position: Point::new(20.0, 140.0), - bounds: (frame.width(), frame.height()), - size: 20.0, - color: Color::WHITE, - }); - - view.font.add(Text { - content: self.released_mousebuttons.clone(), - position: Point::new(280.0, 140.0), - bounds: (frame.width(), frame.height()), - size: 20.0, - color: Color::from_rgb(255, 0, 0), - }); + // This closure simplifies some of the boilerplate. + let mut add_aligned_text = + |label: String, content: String, x: f32, y: f32| { + view.font.add(Text { + content: label, + position: Point::new(x, y), + bounds: (frame.width(), frame.height()), + size: 20.0, + color: Color::WHITE, + }); + view.font.add(Text { + content: content, + position: Point::new(x + 260.0, y), + bounds: (frame.width(), frame.height()), + size: 20.0, + color: Color::WHITE, + }); + }; + + add_aligned_text( + String::from("Pressed keys:"), + self.keys_down.clone(), + 20.0, + 20.0, + ); + + add_aligned_text( + String::from("Text Buffer (type):"), + self.text_buffer.clone(), + 20.0, + 50.0, + ); + + add_aligned_text( + String::from("Pressed mouse buttons:"), + self.mousebuttons_down.clone(), + 20.0, + 80.0, + ); + + add_aligned_text( + String::from("Last mouse wheel scroll:"), + format!("{}, {}", self.wheel_x, self.wheel_y), + 20.0, + 110.0, + ); view.font.draw(&mut frame); @@ -309,8 +249,8 @@ impl Game for InputExample { width: 6, height: 6, }, - position: Point::new(self.mouse_x - 3.0, self.mouse_y - 3.0) + position: Point::new(self.mouse_x - 3.0, self.mouse_y - 3.0), }); batch.draw(Point::new(0.0, 0.0), &mut frame.as_target()); } -} \ No newline at end of file +} diff --git a/src/graphics/window/event.rs b/src/graphics/window/event.rs index fee706f..d459b56 100644 --- a/src/graphics/window/event.rs +++ b/src/graphics/window/event.rs @@ -6,6 +6,7 @@ pub(crate) enum Event { Resized(winit::dpi::LogicalSize), Input(input::Event), CursorMoved(winit::dpi::LogicalPosition), + Moved(winit::dpi::LogicalPosition), } pub struct EventLoop(winit::EventsLoop); @@ -84,12 +85,9 @@ impl EventLoop { input::Event::WindowUnfocused })) } - winit::WindowEvent::Moved( - winit::dpi::LogicalPosition { x, y }, - ) => f(Event::Input(input::Event::WindowMoved { - x: x as f32, - y: y as f32, - })), + winit::WindowEvent::Moved(position) => { + f(Event::Moved(position)) + } _ => {} }, _ => (), diff --git a/src/lib.rs b/src/lib.rs index 359931b..bb92aac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -301,6 +301,17 @@ pub trait Game { }, ) } + window::Event::Moved(logical_position) => { + let position = logical_position.to_physical(window.dpi()); + + game.on_input( + input, + input::Event::WindowMoved { + x: position.x as f32, + y: position.y as f32, + }, + ) + } window::Event::CloseRequested => { if game.on_close_request(input) { *alive = false;