add Standard implementation of SystemResources
this uses 32-bit colour graphics via RgbaBitmap
This commit is contained in:
parent
503f822a87
commit
2cf763bb73
|
@ -11,6 +11,7 @@ pub use crate::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
res::{
|
res::{
|
||||||
dos_like::*,
|
dos_like::*,
|
||||||
|
standard::*,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
utils::prelude::*,
|
utils::prelude::*,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::graphics::bitmap::general::{GeneralBitmap, GeneralBlitMethod};
|
use crate::graphics::bitmap::general::{GeneralBitmap, GeneralBlitMethod};
|
||||||
use crate::graphics::bitmap::indexed::IndexedBitmap;
|
use crate::graphics::bitmap::indexed::IndexedBitmap;
|
||||||
|
use crate::graphics::bitmap::rgb::RgbaBitmap;
|
||||||
use crate::math::rect::Rect;
|
use crate::math::rect::Rect;
|
||||||
use crate::system::input_devices::mouse::Mouse;
|
use crate::system::input_devices::mouse::Mouse;
|
||||||
|
|
||||||
|
@ -242,4 +243,41 @@ impl DefaultMouseCursorBitmaps<IndexedBitmap> for CustomMouseCursor<IndexedBitma
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DefaultMouseCursorBitmaps<RgbaBitmap> for CustomMouseCursor<RgbaBitmap> {
|
||||||
|
fn get_default() -> MouseCursorBitmap<RgbaBitmap> {
|
||||||
|
#[rustfmt::skip]
|
||||||
|
const CURSOR_PIXELS: [u32; DEFAULT_MOUSE_CURSOR_WIDTH * DEFAULT_MOUSE_CURSOR_HEIGHT] = [
|
||||||
|
0x00000000, 0x00000000, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff,
|
||||||
|
0x00000000, 0xffffffff, 0x00000000, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff,
|
||||||
|
0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff,
|
||||||
|
0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff,
|
||||||
|
0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff,
|
||||||
|
0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff,
|
||||||
|
0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff,
|
||||||
|
0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff,
|
||||||
|
0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff,
|
||||||
|
0x00000000, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff,
|
||||||
|
0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff,
|
||||||
|
0x00000000, 0xffffffff, 0x00000000, 0x00000000, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff,
|
||||||
|
0x00000000, 0x00000000, 0xffff00ff, 0xffff00ff, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff,
|
||||||
|
0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff,
|
||||||
|
0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0x00000000, 0xffffffff, 0xffffffff, 0x00000000, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff,
|
||||||
|
0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0x00000000, 0x00000000, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff, 0xffff00ff
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut cursor = RgbaBitmap::new(
|
||||||
|
DEFAULT_MOUSE_CURSOR_WIDTH as u32,
|
||||||
|
DEFAULT_MOUSE_CURSOR_HEIGHT as u32,
|
||||||
|
).unwrap();
|
||||||
|
cursor.pixels_mut().copy_from_slice(&CURSOR_PIXELS);
|
||||||
|
|
||||||
|
MouseCursorBitmap {
|
||||||
|
cursor,
|
||||||
|
hotspot_x: DEFAULT_MOUSE_CURSOR_HOTSPOT_X,
|
||||||
|
hotspot_y: DEFAULT_MOUSE_CURSOR_HOTSPOT_Y,
|
||||||
|
transparent_color: 0xffff00ff,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: other DefaultMouseCursorBitmaps impl's here ... when the other bitmap types are added
|
// TODO: other DefaultMouseCursorBitmaps impl's here ... when the other bitmap types are added
|
||||||
|
|
|
@ -5,6 +5,7 @@ use crate::audio::device::AudioDeviceError;
|
||||||
use crate::system::event::SystemEvent;
|
use crate::system::event::SystemEvent;
|
||||||
|
|
||||||
pub mod dos_like;
|
pub mod dos_like;
|
||||||
|
pub mod standard;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum SystemResourcesError {
|
pub enum SystemResourcesError {
|
||||||
|
|
252
ggdt/src/system/res/standard.rs
Normal file
252
ggdt/src/system/res/standard.rs
Normal file
|
@ -0,0 +1,252 @@
|
||||||
|
use byte_slice_cast::AsByteSlice;
|
||||||
|
|
||||||
|
use crate::{DEFAULT_SCALE_FACTOR, SCREEN_HEIGHT, SCREEN_WIDTH};
|
||||||
|
use crate::audio::{Audio, TARGET_AUDIO_CHANNELS, TARGET_AUDIO_FREQUENCY};
|
||||||
|
use crate::audio::queue::AudioQueue;
|
||||||
|
use crate::graphics::bitmap::rgb::RgbaBitmap;
|
||||||
|
use crate::graphics::font::BitmaskFont;
|
||||||
|
use crate::system::event::{SystemEvent, SystemEventHandler};
|
||||||
|
use crate::system::input_devices::InputDevice;
|
||||||
|
use crate::system::input_devices::keyboard::Keyboard;
|
||||||
|
use crate::system::input_devices::mouse::cursor::CustomMouseCursor;
|
||||||
|
use crate::system::input_devices::mouse::Mouse;
|
||||||
|
use crate::system::res::{SystemResources, SystemResourcesConfig, SystemResourcesError};
|
||||||
|
|
||||||
|
pub struct StandardConfig {
|
||||||
|
screen_width: u32,
|
||||||
|
screen_height: u32,
|
||||||
|
initial_scale_factor: u32,
|
||||||
|
integer_scaling: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StandardConfig {
|
||||||
|
/// Returns a new [`DosLikeConfig`] with a default configuration.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
StandardConfig {
|
||||||
|
screen_width: SCREEN_WIDTH,
|
||||||
|
screen_height: SCREEN_HEIGHT,
|
||||||
|
initial_scale_factor: DEFAULT_SCALE_FACTOR,
|
||||||
|
integer_scaling: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: add customization ability for setting different screen dimensions instead of it being hardcoded
|
||||||
|
|
||||||
|
/// Sets an integer scaling factor for the [`System`] being built to up-scale the virtual
|
||||||
|
/// framebuffer to when displaying it on screen.
|
||||||
|
pub fn scale_factor(mut self, scale_factor: u32) -> Self {
|
||||||
|
self.initial_scale_factor = scale_factor;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enables or disables restricting the final rendered output to always be integer scaled,
|
||||||
|
/// even if that result will not fully fill the area of the window.
|
||||||
|
pub fn integer_scaling(mut self, enable: bool) -> Self {
|
||||||
|
self.integer_scaling = enable;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SystemResourcesConfig for StandardConfig {
|
||||||
|
type SystemResourcesType = Standard;
|
||||||
|
|
||||||
|
fn build(
|
||||||
|
self,
|
||||||
|
_video_subsystem: &sdl2::VideoSubsystem,
|
||||||
|
audio_subsystem: &sdl2::AudioSubsystem,
|
||||||
|
mut window: sdl2::video::Window
|
||||||
|
) -> Result<Self::SystemResourcesType, SystemResourcesError> {
|
||||||
|
let texture_pixel_size = 4; // 32-bit ARGB format
|
||||||
|
|
||||||
|
let window_width = self.screen_width * self.initial_scale_factor;
|
||||||
|
let window_height = self.screen_height * self.initial_scale_factor;
|
||||||
|
if let Err(error) = window.set_size(window_width, window_height) {
|
||||||
|
return Err(SystemResourcesError::SDLError(error.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// turn the window into a canvas (under the hood, an SDL Renderer that owns the window)
|
||||||
|
|
||||||
|
let canvas_builder = window.into_canvas();
|
||||||
|
let mut sdl_canvas = match canvas_builder.build() {
|
||||||
|
Ok(canvas) => canvas,
|
||||||
|
Err(error) => return Err(SystemResourcesError::SDLError(error.to_string())),
|
||||||
|
};
|
||||||
|
if let Err(error) = sdl_canvas.set_logical_size(self.screen_width, self.screen_height) {
|
||||||
|
return Err(SystemResourcesError::SDLError(error.to_string()));
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: newer versions of rust-sdl2 support this directly off the WindowCanvas struct
|
||||||
|
unsafe {
|
||||||
|
sdl2::sys::SDL_RenderSetIntegerScale(
|
||||||
|
sdl_canvas.raw(),
|
||||||
|
if self.integer_scaling {
|
||||||
|
sdl2::sys::SDL_bool::SDL_TRUE
|
||||||
|
} else {
|
||||||
|
sdl2::sys::SDL_bool::SDL_FALSE
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create an SDL texture which we will be uploading to every frame to display the
|
||||||
|
// application's framebuffer
|
||||||
|
|
||||||
|
let sdl_texture = match sdl_canvas.create_texture_streaming(
|
||||||
|
Some(sdl2::pixels::PixelFormatEnum::ARGB8888),
|
||||||
|
self.screen_width,
|
||||||
|
self.screen_height,
|
||||||
|
) {
|
||||||
|
Ok(texture) => texture,
|
||||||
|
Err(error) => return Err(SystemResourcesError::SDLError(error.to_string())),
|
||||||
|
};
|
||||||
|
let sdl_texture_pitch = (sdl_texture.query().width * texture_pixel_size) as usize;
|
||||||
|
|
||||||
|
// create the Bitmap object that will be exposed to the application acting as the system
|
||||||
|
// backbuffer
|
||||||
|
|
||||||
|
let framebuffer = match RgbaBitmap::new(SCREEN_WIDTH, SCREEN_HEIGHT) {
|
||||||
|
Ok(bmp) => bmp,
|
||||||
|
Err(error) => return Err(SystemResourcesError::SDLError(error.to_string())),
|
||||||
|
};
|
||||||
|
|
||||||
|
// create the default font, initialized to the VGA BIOS default font.
|
||||||
|
|
||||||
|
let font = match BitmaskFont::new_vga_font() {
|
||||||
|
Ok(font) => font,
|
||||||
|
Err(error) => return Err(SystemResourcesError::SDLError(error.to_string())),
|
||||||
|
};
|
||||||
|
|
||||||
|
// create audio device and queue
|
||||||
|
|
||||||
|
let audio_spec = sdl2::audio::AudioSpecDesired {
|
||||||
|
freq: Some(TARGET_AUDIO_FREQUENCY as i32),
|
||||||
|
channels: Some(TARGET_AUDIO_CHANNELS),
|
||||||
|
samples: None,
|
||||||
|
};
|
||||||
|
let mut audio = Audio::new(audio_spec, &audio_subsystem)?;
|
||||||
|
audio.resume();
|
||||||
|
let audio_queue = AudioQueue::new(&audio);
|
||||||
|
|
||||||
|
// create all of the input device objects
|
||||||
|
|
||||||
|
let keyboard = Keyboard::new();
|
||||||
|
let mouse = Mouse::new();
|
||||||
|
let cursor = CustomMouseCursor::new();
|
||||||
|
|
||||||
|
Ok(Standard {
|
||||||
|
sdl_canvas,
|
||||||
|
sdl_texture,
|
||||||
|
sdl_texture_pitch,
|
||||||
|
audio,
|
||||||
|
audio_queue,
|
||||||
|
video: framebuffer,
|
||||||
|
font,
|
||||||
|
keyboard,
|
||||||
|
mouse,
|
||||||
|
cursor,
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Standard {
|
||||||
|
sdl_canvas: sdl2::render::WindowCanvas,
|
||||||
|
sdl_texture: sdl2::render::Texture,
|
||||||
|
sdl_texture_pitch: usize,
|
||||||
|
|
||||||
|
/// An [`Audio`] instance that allows interacting with the system's audio output device.
|
||||||
|
pub audio: Audio,
|
||||||
|
|
||||||
|
/// An [`AudioQueue`] instance that can queue up playback/stop commands to be issued to the
|
||||||
|
/// system's [`Audio`] instance a bit more flexibly. If you use this, your application must
|
||||||
|
/// manually call [`AudioQueue::apply`] or [`AudioQueue::apply_to_device`] in your loop to
|
||||||
|
/// flush the queued commands, otherwise this queue will not do anything.
|
||||||
|
pub audio_queue: AudioQueue,
|
||||||
|
|
||||||
|
/// The primary backbuffer [`Bitmap`] that will be rendered to the screen whenever
|
||||||
|
/// [`System::display`] is called. Regardless of the actual window size, this bitmap is always
|
||||||
|
/// [`SCREEN_WIDTH`]x[`SCREEN_HEIGHT`] pixels in size.
|
||||||
|
pub video: RgbaBitmap,
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
|
||||||
|
/// 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<RgbaBitmap>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for Standard {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("Standard")
|
||||||
|
.field("audio", &self.audio)
|
||||||
|
.field("audio_queue", &self.audio_queue)
|
||||||
|
.field("video", &self.video)
|
||||||
|
.field("font", &self.font)
|
||||||
|
.field("keyboard", &self.keyboard)
|
||||||
|
.field("mouse", &self.mouse)
|
||||||
|
.finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SystemResources for Standard {
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display(&mut self) -> Result<(), SystemResourcesError> {
|
||||||
|
self.cursor.render(&mut self.video);
|
||||||
|
|
||||||
|
let texture_pixels = self.video.pixels().as_byte_slice();
|
||||||
|
if let Err(error) = self.sdl_texture.update(None, texture_pixels, self.sdl_texture_pitch) {
|
||||||
|
return Err(SystemResourcesError::SDLError(error.to_string()));
|
||||||
|
}
|
||||||
|
self.sdl_canvas.clear();
|
||||||
|
if let Err(error) = self.sdl_canvas.copy(&self.sdl_texture, None, None) {
|
||||||
|
return Err(SystemResourcesError::SDLError(error));
|
||||||
|
}
|
||||||
|
self.sdl_canvas.present();
|
||||||
|
|
||||||
|
self.cursor.hide(&mut self.video);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_event_state(&mut self) -> Result<(), SystemResourcesError> {
|
||||||
|
self.keyboard.update();
|
||||||
|
self.mouse.update();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_event(&mut self, event: &SystemEvent) -> Result<bool, SystemResourcesError> {
|
||||||
|
if self.keyboard.handle_event(event) {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
if self.mouse.handle_event(event) {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn width(&self) -> u32 {
|
||||||
|
self.video.width()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn height(&self) -> u32 {
|
||||||
|
self.video.height()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue