use std::path::PathBuf;
use std::rc::Rc;
use std::sync::{Arc, Mutex};
use sdl3::Sdl;
use sdl3::pixels::{Color, PixelFormat};
use sdl3::render::{Canvas, FRect};
use sdl3::video::Window;
use quick_error::ResultExt;
use crate::resources::font::{DummyFont, Font};
use crate::resources::image::Image;
use crate::resources::{self, LoadableResource, ResourceManager};
pub trait HasWindow {
fn window_mut(&mut self) -> &mut crate::Window;
}
pub struct Graphics {
pub canvas: Canvas<Window>,
pub sdl_context: Sdl,
pub texture_creator: Rc<sdl3::render::TextureCreator<sdl3::video::WindowContext>>,
pub ttf_context: sdl3::ttf::Sdl3TtfContext,
resource_manager: Arc<Mutex<ResourceManager>>,
}
#[derive(Default, Debug)]
pub struct WindowConfig {
pub resizable: bool,
pub fullscreen: bool,
pub centered: bool
}
pub struct GraphicsBuilder<F>
where
F: Fn(&mut Graphics)
{
resource_manager: Option<Arc<Mutex<ResourceManager>>>,
dimensions: (u32, u32),
name: String,
initializer: Option<F>,
config: WindowConfig
}
impl<F> GraphicsBuilder<F>
where
F: Fn(&mut Graphics)
{
pub fn new() -> GraphicsBuilder<F> {
GraphicsBuilder {
resource_manager: None,
dimensions: (0, 0),
name: "".to_string(),
initializer: None,
config: WindowConfig {
resizable: false,
fullscreen: false,
centered: false
}
}
}
pub fn resource_manager(mut self, resource_manager: Arc<Mutex<ResourceManager>>) -> GraphicsBuilder<F> {
self.resource_manager = Some(resource_manager);
self
}
pub fn dimensions(mut self, dimensions: (u32, u32)) -> GraphicsBuilder<F> {
self.dimensions = dimensions;
self
}
pub fn window_name(mut self, name: String) -> GraphicsBuilder<F> {
self.name = name;
self
}
pub fn initializer(mut self, initializer: F) -> GraphicsBuilder<F> where
F: Fn(&mut Graphics)
{
self.initializer = Some(initializer);
self
}
pub fn resizable(mut self, resizable: bool) -> GraphicsBuilder<F> {
self.config.resizable = resizable;
self
}
pub fn fullscreen(mut self, fullscreen: bool) -> GraphicsBuilder<F> {
self.config.fullscreen = fullscreen;
self
}
pub fn centered(mut self, centered: bool) -> GraphicsBuilder<F> {
self.config.centered = centered;
self
}
pub fn build(self) -> anyhow::Result<Graphics> {
Ok(Graphics::new(
self.name,
self.dimensions,
self.resource_manager.expect("no resource manager provided"),
self.initializer.expect("no resource initializer provided"),
self.config
).unwrap())
}
}
impl<F> Default for GraphicsBuilder<F>
where
F: Fn(&mut Graphics)
{
fn default() -> Self {
Self::new()
}
}
impl Graphics {
pub fn new<F>(
name: String,
dimensions: (u32, u32),
resource_manager: Arc<Mutex<ResourceManager>>,
resource_initialization: F,
config: WindowConfig
) -> Result<Graphics, Box<dyn std::error::Error>> where
F: Fn(&mut Graphics) {
let sdl_context = sdl3::init()?;
let ttf_context = sdl3::ttf::init().map_err(|e| e.to_string())?;
let video_subsystem = sdl_context.video()?;
let mut builder = video_subsystem
.window(&name, dimensions.0, dimensions.1);
let _ = if config.centered { builder.position_centered() } else { &mut builder };
let _ = if config.resizable { builder.resizable() } else { &mut builder };
let _ = if config.fullscreen { builder.fullscreen() } else { &mut builder };
let window = builder
.build()
.map_err(|e| e.to_string())?;
let canvas = window.into_canvas();
let texture_creator = canvas.texture_creator();
let mut graphics = Graphics {
canvas,
sdl_context,
texture_creator: Rc::new(texture_creator),
ttf_context,
resource_manager,
};
resource_initialization(&mut graphics);
Ok(graphics)
}
pub fn draw_image(&mut self, path: String, position: (f32, f32)) -> anyhow::Result<()> {
let manager = self.resource_manager.clone();
let mut manager = manager
.try_lock()
.context("failed to lock resource_manager")
.unwrap();
if !manager.is_cached(path.clone()) {
let texture = Image::load(PathBuf::from(path.clone()), "".to_string(), self, None);
manager.cache_asset(texture?)?; }
let image: &Image = resources::downcast_ref(manager.get_asset(path).unwrap())?;
let dst_rect = FRect::new(
position.0,
position.1,
image.width as f32,
image.height as f32,
);
self.canvas
.copy_ex(
&image.texture,
None,
Some(dst_rect),
0.0,
None,
false,
false,
)
.unwrap();
Ok(())
}
pub fn draw_text(
&mut self,
text: String,
position: (f32, f32),
font: String,
color: Color,
size: f32,
) -> anyhow::Result<()> {
let manager = self.resource_manager.clone();
let mut manager = manager
.try_lock()
.context("failed to lock resource_manager")
.unwrap();
let cache_key = format!(
"{}|{}|{}|{:x?}",
font,
text,
size,
color.to_u32(&PixelFormat::RGBA32)
);
let font: &DummyFont = {
let asset = manager.get_asset(font)?;
resources::downcast_ref(asset)?
};
let font_key = format!("{}|{}", font.name(), size);
if !manager.is_cached(font_key.clone()) {
let asset = Font::load(font.path.clone(), font_key.clone(), self, Some(size));
manager.cache_asset(asset?)?;
}
if !manager.is_cached(cache_key.clone()) {
let font = resources::downcast_ref::<Font>(manager.get_asset(font_key)?)?;
let surface = font
.buffer
.render(&text)
.blended(color)
.map_err(|e| anyhow::anyhow!("render error: {e}"))?;
let image = Image::load_from_surface(cache_key.clone(), self, surface);
manager.cache_asset(image?)?;
}
let texture: &Image = resources::downcast_ref(manager.get_asset(cache_key)?)?;
let dst_rect = FRect::new(
position.0,
position.1,
texture.width as f32,
texture.height as f32,
);
self.canvas
.copy_ex(
&texture.texture,
None,
Some(dst_rect),
0.0,
None,
false,
false,
)
.unwrap();
Ok(())
}
}