From 28d1b7230e3dff0048dc07ffea9b7d2a9896c77a Mon Sep 17 00:00:00 2001 From: gered Date: Tue, 7 Mar 2023 22:33:10 -0500 Subject: [PATCH] move custom mouse cursor functionality to separate struct this is to not have the main Mouse struct be tied to our indexed-colour Bitmap graphics implementation (i'm not currently ready to try to generify this either ...) --- ggdt/src/system/input_devices/mouse/cursor.rs | 197 ++++++++++++++++++ ggdt/src/system/input_devices/mouse/mod.rs | 171 +-------------- ggdt/src/system/res/dos_like.rs | 12 +- 3 files changed, 209 insertions(+), 171 deletions(-) create mode 100644 ggdt/src/system/input_devices/mouse/cursor.rs diff --git a/ggdt/src/system/input_devices/mouse/cursor.rs b/ggdt/src/system/input_devices/mouse/cursor.rs new file mode 100644 index 0000000..9c8c1c1 --- /dev/null +++ b/ggdt/src/system/input_devices/mouse/cursor.rs @@ -0,0 +1,197 @@ +use crate::graphics::*; +use crate::math::*; + +use super::*; + +const DEFAULT_MOUSE_CURSOR_HOTSPOT_X: u32 = 0; +const DEFAULT_MOUSE_CURSOR_HOTSPOT_Y: u32 = 0; +const DEFAULT_MOUSE_CURSOR_WIDTH: usize = 16; +const DEFAULT_MOUSE_CURSOR_HEIGHT: usize = 16; + +#[rustfmt::skip] +const DEFAULT_MOUSE_CURSOR: [u8; DEFAULT_MOUSE_CURSOR_WIDTH * DEFAULT_MOUSE_CURSOR_HEIGHT] = [ + 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x0f, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x0f, 0x0f, 0x00, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x0f, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0xff, 0xff, 0x00, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +]; + +#[derive(Debug)] +pub struct CustomMouseCursor { + last_x: i32, + last_y: i32, + cursor: Bitmap, + cursor_background: Bitmap, + cursor_hotspot_x: u32, + cursor_hotspot_y: u32, + cursor_enabled: bool, +} + +impl CustomMouseCursor { + pub fn new() -> Self { + let (cursor, cursor_background, cursor_hotspot_x, cursor_hotspot_y) = Self::get_default_mouse_cursor(); + + CustomMouseCursor { + last_x: 0, + last_y: 0, + cursor, + cursor_background, + cursor_hotspot_x, + cursor_hotspot_y, + cursor_enabled: false, + } + } + + /// Returns a reference to the current mouse cursor bitmap. + #[inline] + pub fn cursor_bitmap(&self) -> &Bitmap { + &self.cursor + } + + /// Returns the current mouse cursor's "hotspot" x coordinate. + #[inline] + pub fn cursor_hotspot_x(&self) -> u32 { + self.cursor_hotspot_x + } + + /// Returns the current mouse cursor's "hotspot" y coordinate. + #[inline] + pub fn cursor_hotspot_y(&self) -> u32 { + self.cursor_hotspot_y + } + + /// Returns true if mouse cursor bitmap rendering is enabled. + #[inline] + pub fn is_cursor_enabled(&self) -> bool { + self.cursor_enabled + } + + /// Enables or disables mouse cursor bitmap rendering. + #[inline] + pub fn enable_cursor(&mut self, enable: bool) { + self.cursor_enabled = enable; + } + + /// Sets the [`Bitmap`] used to display the mouse cursor and the "hotspot" coordinate. The + /// bitmap provided here should be set up to use color 255 as the transparent color. + /// + /// # Arguments + /// + /// * `cursor`: the bitmap to be used to display the mouse cursor on screen + /// * `hotspot_x`: the "hotspot" x coordinate + /// * `hotspot_y`: the "hotspot" y coordinate. + pub fn set_mouse_cursor(&mut self, cursor: Bitmap, hotspot_x: u32, hotspot_y: u32) { + self.cursor = cursor; + self.cursor_background = Bitmap::new(self.cursor.width(), self.cursor.height()).unwrap(); + self.cursor_hotspot_x = hotspot_x; + self.cursor_hotspot_y = hotspot_y; + } + + /// Resets the mouse cursor bitmap and "hotspot" coordinate back to the default settings. + pub fn set_default_mouse_cursor(&mut self) { + let (cursor, background, hotspot_x, hotspot_y) = Self::get_default_mouse_cursor(); + self.cursor = cursor; + self.cursor_background = background; + self.cursor_hotspot_x = hotspot_x; + self.cursor_hotspot_y = hotspot_y; + } + + #[inline] + fn get_cursor_render_position(&self) -> (i32, i32) { + ( + self.last_x - self.cursor_hotspot_x as i32, + self.last_y - self.cursor_hotspot_y as i32, + ) + } + + /// Renders the mouse cursor bitmap onto the destination bitmap at the mouse's current + /// position. The destination bitmap specified is usually the [`SystemResources`]'s video + /// backbuffer bitmap. The background on the destination bitmap is saved internally and a + /// subsequent call to [`Self::hide`] will restore the background. + /// + /// If mouse cursor rendering is not currently enabled, this method does nothing. + /// + /// Applications will not normally need to call this method, as if mouse cursor rendering is + /// enabled, this will be automatically handled by [`SystemResources::display`]. + /// + /// [`SystemResources`]: crate::system::SystemResources + /// [`SystemResources::display`]: crate::system::SystemResources::display + pub fn render(&mut self, dest: &mut Bitmap) { + if !self.cursor_enabled { + return; + } + + let (x, y) = self.get_cursor_render_position(); + + // preserve existing background first + self.cursor_background.blit_region( + BlitMethod::Solid, + &dest, + &Rect::new(x, y, self.cursor.width(), self.cursor.height()), + 0, + 0, + ); + + dest.blit(BlitMethod::Transparent(255), &self.cursor, x, y); + } + + /// Restores the original destination bitmap contents where the mouse cursor bitmap was + /// rendered to during the previous call to [`Self::render`]. The destination bitmap + /// specified is usually the [`SystemResources`]'s video backbuffer bitmap. + /// + /// If mouse cursor rendering is not currently enabled, this method does nothing. + /// + /// Applications will not normally need to call this method, as if mouse cursor rendering is + /// enabled, this will be automatically handled by [`SystemResources::display`]. + /// + /// [`SystemResources`]: crate::system::SystemResources + /// [`SystemResources::display`]: crate::system::SystemResources::display + pub fn hide(&mut self, dest: &mut Bitmap) { + if !self.cursor_enabled { + return; + } + + let (x, y) = self.get_cursor_render_position(); + dest.blit(BlitMethod::Solid, &self.cursor_background, x, y); + } + + /// Updates current state from the given [`Mouse`] device's state, ensuring that subsequent calls to render + /// a custom mouse cursor reflect the current mouse state. Application's should not normally need to call + /// this directly as it will be called by [`SystemResources::update`]. + /// + /// [`SystemResources::update`]: crate::system::SystemResources::update + pub fn update(&mut self, mouse: &Mouse) { + self.last_x = mouse.x; + self.last_y = mouse.y; + } + + fn get_default_mouse_cursor() -> (Bitmap, Bitmap, u32, u32) { + let mut cursor = Bitmap::new( + DEFAULT_MOUSE_CURSOR_WIDTH as u32, + DEFAULT_MOUSE_CURSOR_HEIGHT as u32, + ) + .unwrap(); + cursor.pixels_mut().copy_from_slice(&DEFAULT_MOUSE_CURSOR); + + let cursor_background = Bitmap::new(cursor.width(), cursor.height()).unwrap(); + + ( + cursor, + cursor_background, + DEFAULT_MOUSE_CURSOR_HOTSPOT_X, + DEFAULT_MOUSE_CURSOR_HOTSPOT_Y, + ) + } +} \ No newline at end of file diff --git a/ggdt/src/system/input_devices/mouse/mod.rs b/ggdt/src/system/input_devices/mouse/mod.rs index 1647b2d..931bc99 100644 --- a/ggdt/src/system/input_devices/mouse/mod.rs +++ b/ggdt/src/system/input_devices/mouse/mod.rs @@ -5,36 +5,13 @@ use crate::system::MouseEvent; use super::*; pub use self::buttons::*; +pub use self::cursor::*; pub mod buttons; +pub mod cursor; const MAX_BUTTONS: usize = 32; -const DEFAULT_MOUSE_CURSOR_HOTSPOT_X: u32 = 0; -const DEFAULT_MOUSE_CURSOR_HOTSPOT_Y: u32 = 0; -const DEFAULT_MOUSE_CURSOR_WIDTH: usize = 16; -const DEFAULT_MOUSE_CURSOR_HEIGHT: usize = 16; - -#[rustfmt::skip] -const DEFAULT_MOUSE_CURSOR: [u8; DEFAULT_MOUSE_CURSOR_WIDTH * DEFAULT_MOUSE_CURSOR_HEIGHT] = [ - 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0x00, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0x00, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0x00, 0x0f, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0x00, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0x00, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0x00, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0x00, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0x00, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0x00, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, - 0x00, 0x0f, 0x0f, 0x00, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0x00, 0x0f, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0x00, 0x00, 0xff, 0xff, 0x00, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0x0f, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff -]; - /// Holds the current state of the mouse. /// /// Must be explicitly updated each frame by calling `handle_event` each frame for all SDL2 events @@ -49,29 +26,16 @@ pub struct Mouse { x_delta: i32, y_delta: i32, buttons: [ButtonState; MAX_BUTTONS], - cursor: Bitmap, - cursor_background: Bitmap, - cursor_hotspot_x: u32, - cursor_hotspot_y: u32, - cursor_enabled: bool, } impl Mouse { pub fn new() -> Mouse { - let (cursor, cursor_background, cursor_hotspot_x, cursor_hotspot_y) = - Self::get_default_mouse_cursor(); - Mouse { x: 0, y: 0, x_delta: 0, y_delta: 0, buttons: [ButtonState::Idle; MAX_BUTTONS], - cursor, - cursor_background, - cursor_hotspot_x, - cursor_hotspot_y, - cursor_enabled: false, } } @@ -131,137 +95,6 @@ impl Mouse { self.buttons[button] == ButtonState::Released } - /// Returns a reference to the current mouse cursor bitmap. - #[inline] - pub fn cursor_bitmap(&self) -> &Bitmap { - &self.cursor - } - - /// Returns the current mouse cursor's "hotspot" x coordinate. - #[inline] - pub fn cursor_hotspot_x(&self) -> u32 { - self.cursor_hotspot_x - } - - /// Returns the current mouse cursor's "hotspot" y coordinate. - #[inline] - pub fn cursor_hotspot_y(&self) -> u32 { - self.cursor_hotspot_y - } - - /// Returns true if mouse cursor bitmap rendering is enabled. - #[inline] - pub fn is_cursor_enabled(&self) -> bool { - self.cursor_enabled - } - - /// Enables or disables mouse cursor bitmap rendering. - #[inline] - pub fn enable_cursor(&mut self, enable: bool) { - self.cursor_enabled = enable; - } - - /// Sets the [`Bitmap`] used to display the mouse cursor and the "hotspot" coordinate. The - /// bitmap provided here should be set up to use color 255 as the transparent color. - /// - /// # Arguments - /// - /// * `cursor`: the bitmap to be used to display the mouse cursor on screen - /// * `hotspot_x`: the "hotspot" x coordinate - /// * `hotspot_y`: the "hotspot" y coordinate. - pub fn set_mouse_cursor(&mut self, cursor: Bitmap, hotspot_x: u32, hotspot_y: u32) { - self.cursor = cursor; - self.cursor_background = Bitmap::new(self.cursor.width(), self.cursor.height()).unwrap(); - self.cursor_hotspot_x = hotspot_x; - self.cursor_hotspot_y = hotspot_y; - } - - /// Resets the mouse cursor bitmap and "hotspot" coordinate back to the default settings. - pub fn set_default_mouse_cursor(&mut self) { - let (cursor, background, hotspot_x, hotspot_y) = Self::get_default_mouse_cursor(); - self.cursor = cursor; - self.cursor_background = background; - self.cursor_hotspot_x = hotspot_x; - self.cursor_hotspot_y = hotspot_y; - } - - fn get_default_mouse_cursor() -> (Bitmap, Bitmap, u32, u32) { - let mut cursor = Bitmap::new( - DEFAULT_MOUSE_CURSOR_WIDTH as u32, - DEFAULT_MOUSE_CURSOR_HEIGHT as u32, - ) - .unwrap(); - cursor.pixels_mut().copy_from_slice(&DEFAULT_MOUSE_CURSOR); - - let cursor_background = Bitmap::new(cursor.width(), cursor.height()).unwrap(); - - ( - cursor, - cursor_background, - DEFAULT_MOUSE_CURSOR_HOTSPOT_X, - DEFAULT_MOUSE_CURSOR_HOTSPOT_Y, - ) - } - - #[inline] - fn get_cursor_render_position(&self) -> (i32, i32) { - ( - self.x - self.cursor_hotspot_x as i32, - self.y - self.cursor_hotspot_y as i32, - ) - } - - /// Renders the mouse cursor bitmap onto the destination bitmap at the mouse's current - /// position. The destination bitmap specified is assumed to be the [`System`]'s video - /// backbuffer bitmap. The background on the destination bitmap is saved internally and a - /// subsequent call to [`Mouse::hide_cursor`] will restore the background. - /// - /// If mouse cursor rendering is not currently enabled, this method does nothing. - /// - /// Applications will not normally need to call this method, as if mouse cursor rendering is - /// enabled, this will be automatically handled by [`System::display`]. - /// - /// [`System`]: crate::System - /// [`System::display`]: crate::System::display - pub fn render_cursor(&mut self, dest: &mut Bitmap) { - if !self.cursor_enabled { - return; - } - - let (x, y) = self.get_cursor_render_position(); - - // preserve existing background first - self.cursor_background.blit_region( - BlitMethod::Solid, - &dest, - &Rect::new(x, y, self.cursor.width(), self.cursor.height()), - 0, - 0, - ); - - dest.blit(BlitMethod::Transparent(255), &self.cursor, x, y); - } - - /// Restores the original destination bitmap contents where the mouse cursor bitmap was - /// rendered to during the previous call to [`Mouse::render_cursor`]. The destination bitmap - /// specified is assumed to be the [`System`]'s video backbuffer bitmap. - /// - /// If mouse cursor rendering is not currently enabled, this method does nothing. - /// - /// Applications will not normally need to call this method, as if mouse cursor rendering is - /// enabled, this will be automatically handled by [`System::display`]. - /// - /// [`System`]: crate::System - /// [`System::display`]: crate::System::display - pub fn hide_cursor(&mut self, dest: &mut Bitmap) { - if !self.cursor_enabled { - return; - } - - let (x, y) = self.get_cursor_render_position(); - dest.blit(BlitMethod::Solid, &self.cursor_background, x, y); - } - fn update_button_state(&mut self, button: u32, is_pressed: bool) { let button_state = &mut self.buttons[button as usize]; *button_state = if is_pressed { diff --git a/ggdt/src/system/res/dos_like.rs b/ggdt/src/system/res/dos_like.rs index 9718aad..b01c7d9 100644 --- a/ggdt/src/system/res/dos_like.rs +++ b/ggdt/src/system/res/dos_like.rs @@ -165,6 +165,7 @@ impl SystemResourcesConfig for DosLikeConfig { let keyboard = Keyboard::new(); let mouse = Mouse::new(); + let cursor = CustomMouseCursor::new(); Ok(DosLike { sdl_canvas, @@ -178,6 +179,7 @@ impl SystemResourcesConfig for DosLikeConfig { font, keyboard, mouse, + cursor, }) } } @@ -218,6 +220,10 @@ pub struct DosLike { /// 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, + + /// Manages custom mouse cursor graphics and state. Use this to set/unset a custom mouse cursor bitmap. + /// When set, rendering should occur automatically during calls to [`SystemResources::display`]. + pub cursor: CustomMouseCursor, } impl std::fmt::Debug for DosLike { @@ -236,6 +242,8 @@ impl std::fmt::Debug for DosLike { impl SystemResources for DosLike { fn update(&mut self) -> Result<(), SystemResourcesError> { + self.cursor.update(&self.mouse); + match self.audio_queue.apply(&mut self.audio) { Ok(_) => Ok(()), Err(error) => Err(SystemResourcesError::AudioDeviceError(error)) @@ -245,7 +253,7 @@ impl SystemResources for DosLike { /// Takes the `video` backbuffer bitmap and `palette` and renders it to the window, up-scaled /// to fill the window (preserving aspect ratio of course). fn display(&mut self) -> Result<(), SystemResourcesError> { - self.mouse.render_cursor(&mut self.video); + self.cursor.render(&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 @@ -262,7 +270,7 @@ impl SystemResources for DosLike { } self.sdl_canvas.present(); - self.mouse.hide_cursor(&mut self.video); + self.cursor.hide(&mut self.video); Ok(()) }