1use std::path::PathBuf;
9use std::rc::Rc;
10use std::sync::{Arc, Mutex};
11
12use sdl3::Sdl;
13use sdl3::pixels::{Color, PixelFormat};
14use sdl3::render::{Canvas, FRect};
15use sdl3::video::Window;
16
17use quick_error::ResultExt;
18
19use crate::resources::loadable::{Font, Image};
20use crate::resources::{self, LoadableResource, ResourceManager, loadable};
21
22pub trait HasWindow {
23 fn window_mut(&mut self) -> &mut crate::Window;
24}
25
26pub struct Graphics {
31 pub canvas: Canvas<Window>,
33 pub sdl_context: Sdl,
35 pub texture_creator: Rc<sdl3::render::TextureCreator<sdl3::video::WindowContext>>,
37 pub ttf_context: sdl3::ttf::Sdl3TtfContext,
39 resource_manager: Arc<Mutex<ResourceManager>>,
41}
42
43impl Graphics {
44 pub fn new(
60 name: String,
61 dimensions: (u32, u32),
62 resource_manager: Arc<Mutex<ResourceManager>>,
63 ) -> Result<Graphics, Box<dyn std::error::Error>> {
64 let sdl_context = sdl3::init()?;
68 let ttf_context = sdl3::ttf::init().map_err(|e| e.to_string())?;
69 let video_subsystem = sdl_context.video()?;
70
71 let window = video_subsystem
72 .window(&name, dimensions.0, dimensions.1)
73 .position_centered()
74 .resizable()
75 .build()
76 .map_err(|e| e.to_string())?;
77
78 let canvas = window.into_canvas();
79 let texture_creator = canvas.texture_creator();
80 Ok(Graphics {
81 canvas,
82 sdl_context,
83 texture_creator: Rc::new(texture_creator),
84 ttf_context,
85 resource_manager,
86 })
87 }
88
89 pub fn draw_image(&mut self, path: String, position: (f32, f32)) -> anyhow::Result<()> {
104 let manager = self.resource_manager.clone();
105 let mut manager = manager
106 .try_lock()
107 .context("failed to lock resource_manager")
108 .unwrap();
109
110 if !manager.is_cached(path.clone()) {
111 let texture = loadable::Image::load(PathBuf::from(path.clone()), self, None);
113 manager.cache_asset(texture?)?; }
115
116 let image: &Image = resources::as_concrete(manager.get_asset(path).unwrap())?;
117
118 let dst_rect = FRect::new(
119 position.0,
120 position.1,
121 image.width as f32,
122 image.height as f32,
123 );
124 self.canvas
125 .copy_ex(
126 &image.texture,
127 None,
128 Some(dst_rect),
129 0.0,
130 None,
131 false,
132 false,
133 )
134 .unwrap();
135
136 Ok(())
137 }
138
139 pub fn draw_text(
141 &mut self,
142 text: String,
143 position: (f32, f32),
144 font_path: String,
145 color: Color,
146 size: f32,
147 ) -> anyhow::Result<()> {
148 let manager = self.resource_manager.clone();
149 let mut manager = manager
150 .try_lock()
151 .context("failed to lock resource_manager")
152 .unwrap();
153
154 let cache_key = format!(
158 "{}|{}|{}|{:x?}",
159 font_path,
160 text,
161 size,
162 color.to_u32(&PixelFormat::RGBA32)
163 );
164 let font_key = format!("{font_path}|{size}");
165 let font: &Font = {
166 if !manager.is_cached(font_key.clone()) {
167 let asset = loadable::Font::load(font_path.clone().into(), self, Some(size));
168 manager.cache_asset(asset?)?;
169 }
170 let asset = manager.get_asset(font_key)?;
171 resources::as_concrete(asset)?
172 };
173
174 if !manager.is_cached(cache_key.clone()) {
175 let surface = font
176 .buffer
177 .render(&text)
178 .blended(color)
179 .map_err(|e| anyhow::anyhow!("render error: {e}"))?;
180 let image = Image::load_from_surface(cache_key.clone(), self, surface);
181 manager.cache_asset(image?)?;
182 }
183 let texture: &Image = resources::as_concrete(manager.get_asset(cache_key)?)?;
184
185 let dst_rect = FRect::new(
186 position.0,
187 position.1,
188 texture.width as f32,
189 texture.height as f32,
190 );
191 self.canvas
192 .copy_ex(
193 &texture.texture,
194 None,
195 Some(dst_rect),
196 0.0,
197 None,
198 false,
199 false,
200 )
201 .unwrap();
202 Ok(())
203 }
204}