diff --git a/libretrogd/src/base/mod.rs b/libretrogd/src/base/mod.rs index d039609..aadbbae 100644 --- a/libretrogd/src/base/mod.rs +++ b/libretrogd/src/base/mod.rs @@ -189,17 +189,9 @@ where let mut states = States::new(); states.push(initial_state)?; - let mut is_running = true; let mut last_ticks = app.core().system().ticks(); - while is_running && !states.is_empty() { - app.core().system_mut().do_events_with(|event| match event { - SystemEvent::Quit => { - is_running = false; - } - _ => {} - }); - + while !app.core().system_mut().do_events() && !states.is_empty() { last_ticks = app.core().update_frame_delta(last_ticks); states.update(&mut app)?; states.render(&mut app); diff --git a/libretrogd/src/system/event.rs b/libretrogd/src/system/event.rs index 886d8a7..20aed31 100644 --- a/libretrogd/src/system/event.rs +++ b/libretrogd/src/system/event.rs @@ -188,12 +188,15 @@ impl From for SystemEvent { } } +/// Common trait for implementing a handler of [`SystemEvent`]s that are polled during the +/// application's main loop. pub trait SystemEventHandler { /// Processes the data from the given [`SystemEvent`]. Returns true if the processing actually - /// recognized the passed event, or false if the event was ignored. + /// recognized the passed event and handled it, or false if the event was ignored. fn handle_event(&mut self, event: &SystemEvent) -> bool; } +/// An interator for SDL2 system events, polled via [`SystemEventPump`]. pub struct SystemEventIterator<'a> { iter: sdl2::event::EventPollIterator<'a>, } @@ -206,6 +209,8 @@ impl Iterator for SystemEventIterator<'_> { } } +/// Provides an event pump iterator that wraps over SDL2 events, allowing applications to respond +/// to all events each frame as [`SystemEvent`] instances. pub struct SystemEventPump { sdl_event_pump: sdl2::EventPump, } @@ -217,6 +222,8 @@ impl SystemEventPump { } } + /// Returns an iterator over [`SystemEvent`]s that have been generated since the last time + /// events were polled (usually, in the previous frame). pub fn poll_iter(&mut self) -> SystemEventIterator { self.sdl_event_pump.pump_events(); SystemEventIterator { iter: self.sdl_event_pump.poll_iter() } diff --git a/libretrogd/src/system/input_devices/keyboard/mod.rs b/libretrogd/src/system/input_devices/keyboard/mod.rs index 328723e..4d1b4f6 100644 --- a/libretrogd/src/system/input_devices/keyboard/mod.rs +++ b/libretrogd/src/system/input_devices/keyboard/mod.rs @@ -75,8 +75,10 @@ impl InputDevice for Keyboard { }; } } +} - fn handle_event(&mut self, event: &SystemEvent) { +impl SystemEventHandler for Keyboard { + fn handle_event(&mut self, event: &SystemEvent) -> bool { match event { SystemEvent::Keyboard(KeyboardEvent::KeyDown { scancode, .. }) => { if let Some(scancode) = scancode { @@ -87,13 +89,15 @@ impl InputDevice for Keyboard { _ => ButtonState::Pressed, }; } + true } SystemEvent::Keyboard(KeyboardEvent::KeyUp { scancode, .. }) => { if let Some(scancode) = scancode { self.keyboard[*scancode as usize] = ButtonState::Released; } + true } - _ => (), + _ => false, } } -} +} \ No newline at end of file diff --git a/libretrogd/src/system/input_devices/mod.rs b/libretrogd/src/system/input_devices/mod.rs index 07c9e28..73d47a8 100644 --- a/libretrogd/src/system/input_devices/mod.rs +++ b/libretrogd/src/system/input_devices/mod.rs @@ -1,4 +1,4 @@ -use crate::system::SystemEvent; +use crate::system::{SystemEvent, SystemEventHandler}; pub mod keyboard; pub mod mouse; @@ -17,9 +17,29 @@ pub trait InputDevice { /// input device. Normally this should be called on the device before all of this frame's /// input events have been processed via `handle_event`. fn update(&mut self); - - /// Processes the data from the given [`SystemEvent`] if it is relevant for this input device. - /// You should pass in all events received every frame and let the input device figure out if it - /// is relevant to it or not. - fn handle_event(&mut self, event: &SystemEvent); } + +/// Container for all available input devices available for applications to use. +pub struct InputDevices { + pub keyboard: keyboard::Keyboard, + pub mouse: mouse::Mouse, +} + +impl InputDevice for InputDevices { + fn update(&mut self) { + self.keyboard.update(); + self.mouse.update(); + } +} + +impl SystemEventHandler for InputDevices { + fn handle_event(&mut self, event: &SystemEvent) -> bool { + if self.keyboard.handle_event(event) { + return true; + } + if self.mouse.handle_event(event) { + return true; + } + false + } +} \ No newline at end of file diff --git a/libretrogd/src/system/input_devices/mouse/mod.rs b/libretrogd/src/system/input_devices/mouse/mod.rs index 5d7ca48..992bf8a 100644 --- a/libretrogd/src/system/input_devices/mouse/mod.rs +++ b/libretrogd/src/system/input_devices/mouse/mod.rs @@ -292,16 +292,18 @@ impl InputDevice for Mouse { } } } +} - fn handle_event(&mut self, event: &SystemEvent) { +impl SystemEventHandler for Mouse { + fn handle_event(&mut self, event: &SystemEvent) -> bool { match event { SystemEvent::Mouse(MouseEvent::MouseMotion { - x, - y, - x_delta, - y_delta, - buttons, - }) => { + x, + y, + x_delta, + y_delta, + buttons, + }) => { self.x = *x; self.y = *y; self.x_delta = *x_delta; @@ -312,14 +314,17 @@ impl InputDevice for Mouse { self.update_button_state(MouseButton::Right as u32, buttons.contains(MouseButtons::RIGHT_BUTTON)); self.update_button_state(MouseButton::X1 as u32, buttons.contains(MouseButtons::X1)); self.update_button_state(MouseButton::X2 as u32, buttons.contains(MouseButtons::X2)); + true } SystemEvent::Mouse(MouseEvent::MouseButtonDown { button, .. }) => { self.update_button_state(*button as u32, true); + true } SystemEvent::Mouse(MouseEvent::MouseButtonUp { button, .. }) => { self.update_button_state(*button as u32, false); + true } - _ => (), + _ => false, } } -} +} \ No newline at end of file diff --git a/libretrogd/src/system/mod.rs b/libretrogd/src/system/mod.rs index 20e2fe9..552cb3a 100644 --- a/libretrogd/src/system/mod.rs +++ b/libretrogd/src/system/mod.rs @@ -317,6 +317,11 @@ impl SystemBuilder { let keyboard = Keyboard::new(); let mouse = Mouse::new(); + let input_devices = InputDevices { + keyboard, + mouse, + }; + Ok(System { sdl_context, sdl_audio_subsystem, @@ -331,8 +336,7 @@ impl SystemBuilder { video: framebuffer, palette, font, - keyboard, - mouse, + input_devices, event_pump, target_framerate: self.target_framerate, target_framerate_delta: None, @@ -381,13 +385,11 @@ pub struct System { /// A pre-loaded [`Font`] that can be used for text rendering. pub font: BitmaskFont, - /// The current keyboard state. To ensure it is updated each frame, you should call - /// [`System::do_events`] or [`System::do_events_with`] each frame. - pub keyboard: Keyboard, - - /// The current mouse state. To ensure it is updated each frame, you should call - /// [`System::do_events`] or [`System::do_events_with`] each frame. - pub mouse: Mouse, + /// Contains instances representing the current state of the input devices available. + /// To ensure these are updated each frame, ensure that you are either calling + /// [`System::do_events`] or manually implementing an event polling loop which calls + /// [`InputDevices::update`] and [`InputDevices::handle_event`]. + pub input_devices: InputDevices, pub event_pump: SystemEventPump, } @@ -400,8 +402,8 @@ impl std::fmt::Debug for System { .field("video", &self.video) .field("palette", &self.palette) .field("font", &self.font) - .field("keyboard", &self.keyboard) - .field("mouse", &self.mouse) + //.field("keyboard", &self.keyboard) + //.field("mouse", &self.mouse) .field("target_framerate", &self.target_framerate) .field("target_framerate_delta", &self.target_framerate_delta) .field("next_tick", &self.next_tick) @@ -415,7 +417,7 @@ impl System { /// will block to wait for V-sync. Otherwise, if a target framerate was configured a delay /// might be used to try to meet that framerate. pub fn display(&mut self) -> Result<(), SystemError> { - self.mouse.render_cursor(&mut self.video); + self.input_devices.mouse.render_cursor(&mut self.video); // convert application framebuffer to 32-bit RGBA pixels, and then upload it to the SDL // texture so it will be displayed on screen @@ -436,7 +438,7 @@ impl System { } self.sdl_canvas.present(); - self.mouse.hide_cursor(&mut self.video); + self.input_devices.mouse.hide_cursor(&mut self.video); // if a specific target framerate is desired, apply some loop timing/delay to achieve it // TODO: do this better. delaying when running faster like this is a poor way to do this.. @@ -470,26 +472,54 @@ impl System { } /// Checks for and responds to all SDL2 events waiting in the queue. Each event is passed to - /// all [`InputDevice`]'s automatically to ensure input device state is up to date. - pub fn do_events(&mut self) { - self.do_events_with(|_event| {}); - } - - /// Same as [`System::do_events`] but also takes a function which will be called for each - /// SDL2 event being processed (after everything else has already processed it), allowing - /// your application to also react to any events received. - pub fn do_events_with(&mut self, mut f: F) - where - F: FnMut(&SystemEvent), - { - self.keyboard.update(); - self.mouse.update(); + /// all [`InputDevice`]'s automatically to ensure input device state is up to date. Returns + /// true if a [`SystemEvent::Quit`] event is encountered, in which case, the application + /// should quit. Otherwise, returns false. + /// + /// ``` + /// use libretrogd::system::*; + /// + /// let mut system = SystemBuilder::new().window_title("Example").build()?; + /// + /// while !system.do_events() { + /// // ... the body of your main loop here ... + /// } + /// ``` + /// + /// If your application needs to react to [`SystemEvent`]s, then instead of using + /// [`System::do_events`], you should instead manually take care of event polling in your + /// main loop. For example: + /// + /// ``` + /// use libretrogd::system::*; + /// + /// let mut system = SystemBuilder::new().window_title("Example").build()?; + /// + /// 'mainloop: loop { + /// system.input_devices.update(); + /// for event in system.event_pump.poll_iter() { + /// system.input_devices.handle_event(&event); + /// match event { + /// SystemEvent::Quit => { + /// break 'mainloop + /// }, + /// _ => {}, + /// } + /// } + /// + /// // ...the rest of the body of your main loop here ... + /// } + /// ``` + pub fn do_events(&mut self) -> bool { + let mut should_quit = false; + self.input_devices.update(); for event in self.event_pump.poll_iter() { - let event = event.into(); - self.keyboard.handle_event(&event); - self.mouse.handle_event(&event); - f(&event); + self.input_devices.handle_event(&event); + if event == SystemEvent::Quit { + should_quit = true; + } } + should_quit } pub fn ticks(&self) -> u64 {