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 ...)
This commit is contained in:
Gered 2023-03-07 22:33:10 -05:00
parent 888b5057e4
commit 28d1b7230e
3 changed files with 209 additions and 171 deletions

View file

@ -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,
)
}
}

View file

@ -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 {

View file

@ -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(())
}