diff --git a/examples/input.rs b/examples/input.rs new file mode 100644 index 0000000..514300d --- /dev/null +++ b/examples/input.rs @@ -0,0 +1,256 @@ +//! A simple example that demonstrates capturing window and input events. +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}; +use coffee::{Game, Result, Timer}; + +use std::collections::HashMap; + +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, + key_state: HashMap, + mouse_state: HashMap, + 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), + key_state: HashMap::new(), + mouse_state: HashMap::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, + keys_down: String, + mousebuttons_down: String, +} + +impl InputExample { + const MAX_TEXTSIZE: usize = 40; +} + +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(Self::MAX_TEXTSIZE), + keys_down: String::new(), + mousebuttons_down: 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 } => { + input.key_state.insert( + key_code, + match state { + input::ButtonState::Pressed => true, + input::ButtonState::Released => false, + }, + ); + } + 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 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; + + 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() < Self::MAX_TEXTSIZE + { + self.text_buffer.push_str(&input.text_buffer); + } + } + } + } + input.text_buffer.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); + + // 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); + + 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()); + } +} diff --git a/src/graphics/window/event.rs b/src/graphics/window/event.rs index 2a49651..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); @@ -46,15 +47,47 @@ 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(position) => { + f(Event::Moved(position)) + } _ => {} }, _ => (), 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, + }, } 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;