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::font::{DummyFont, Font};
20use crate::resources::image::Image;
21use crate::resources::{self, LoadableResource, ResourceManager};
22
23pub trait HasWindow {
24 fn window_mut(&mut self) -> &mut crate::Window;
25}
26
27pub struct Graphics {
32 pub canvas: Canvas<Window>,
34 pub sdl_context: Sdl,
36 pub texture_creator: Rc<sdl3::render::TextureCreator<sdl3::video::WindowContext>>,
38 pub ttf_context: sdl3::ttf::Sdl3TtfContext,
40 resource_manager: Arc<Mutex<ResourceManager>>,
42}
43
44#[derive(Default, Debug)]
46pub struct WindowConfig {
47 pub resizable: bool,
49 pub fullscreen: bool,
51 pub centered: bool
53}
54
55pub struct GraphicsBuilder<F>
57where
58 F: Fn(&mut Graphics)
59{
60 resource_manager: Option<Arc<Mutex<ResourceManager>>>,
61 dimensions: (u32, u32),
62 name: String,
63 initializer: Option<F>,
64 config: WindowConfig
65}
66
67impl<F> GraphicsBuilder<F>
68where
69 F: Fn(&mut Graphics)
70{
71 pub fn new() -> GraphicsBuilder<F> {
75 GraphicsBuilder {
76 resource_manager: None,
77 dimensions: (0, 0),
78 name: "".to_string(),
79 initializer: None,
80 config: WindowConfig {
81 resizable: false,
82 fullscreen: false,
83 centered: false
84 }
85 }
86 }
87
88 pub fn resource_manager(mut self, resource_manager: Arc<Mutex<ResourceManager>>) -> GraphicsBuilder<F> {
90 self.resource_manager = Some(resource_manager);
91 self
92 }
93
94 pub fn dimensions(mut self, dimensions: (u32, u32)) -> GraphicsBuilder<F> {
96 self.dimensions = dimensions;
97 self
98 }
99
100 pub fn window_name(mut self, name: String) -> GraphicsBuilder<F> {
102 self.name = name;
103 self
104 }
105
106 pub fn initializer(mut self, initializer: F) -> GraphicsBuilder<F> where
108 F: Fn(&mut Graphics)
109 {
110 self.initializer = Some(initializer);
111 self
112 }
113
114 pub fn resizable(mut self, resizable: bool) -> GraphicsBuilder<F> {
116 self.config.resizable = resizable;
117 self
118 }
119
120 pub fn fullscreen(mut self, fullscreen: bool) -> GraphicsBuilder<F> {
122 self.config.fullscreen = fullscreen;
123 self
124 }
125
126 pub fn centered(mut self, centered: bool) -> GraphicsBuilder<F> {
128 self.config.centered = centered;
129 self
130 }
131
132 pub fn build(self) -> anyhow::Result<Graphics> {
137 Ok(Graphics::new(
138 self.name,
139 self.dimensions,
140 self.resource_manager.expect("no resource manager provided"),
141 self.initializer.expect("no resource initializer provided"),
142 self.config
143 ).unwrap())
144 }
145}
146
147impl<F> Default for GraphicsBuilder<F>
148where
149 F: Fn(&mut Graphics)
150{
151 fn default() -> Self {
153 Self::new()
154 }
155}
156
157impl Graphics {
158 pub fn new<F>(
174 name: String,
175 dimensions: (u32, u32),
176 resource_manager: Arc<Mutex<ResourceManager>>,
177 resource_initialization: F,
178 config: WindowConfig
179 ) -> Result<Graphics, Box<dyn std::error::Error>> where
180 F: Fn(&mut Graphics) {
181 let sdl_context = sdl3::init()?;
185 let ttf_context = sdl3::ttf::init().map_err(|e| e.to_string())?;
186 let video_subsystem = sdl_context.video()?;
187
188 let mut builder = video_subsystem
189 .window(&name, dimensions.0, dimensions.1);
190
191 let _ = if config.centered { builder.position_centered() } else { &mut builder };
192 let _ = if config.resizable { builder.resizable() } else { &mut builder };
193 let _ = if config.fullscreen { builder.fullscreen() } else { &mut builder };
194
195 let window = builder
196 .build()
197 .map_err(|e| e.to_string())?;
198
199 let canvas = window.into_canvas();
200 let texture_creator = canvas.texture_creator();
201 let mut graphics = Graphics {
202 canvas,
203 sdl_context,
204 texture_creator: Rc::new(texture_creator),
205 ttf_context,
206 resource_manager,
207 };
208
209 resource_initialization(&mut graphics);
210
211 Ok(graphics)
212 }
213
214 pub fn draw_image(&mut self, path: String, position: (f32, f32)) -> anyhow::Result<()> {
229 let manager = self.resource_manager.clone();
230 let mut manager = manager
231 .try_lock()
232 .context("failed to lock resource_manager")
233 .unwrap();
234
235 if !manager.is_cached(path.clone()) {
236 let texture = Image::load(PathBuf::from(path.clone()), "".to_string(), self, None);
238 manager.cache_asset(texture?)?; }
240
241 let image: &Image = resources::downcast_ref(manager.get_asset(path).unwrap())?;
242
243 let dst_rect = FRect::new(
244 position.0,
245 position.1,
246 image.width as f32,
247 image.height as f32,
248 );
249 self.canvas
250 .copy_ex(
251 &image.texture,
252 None,
253 Some(dst_rect),
254 0.0,
255 None,
256 false,
257 false,
258 )
259 .unwrap();
260
261 Ok(())
262 }
263
264 pub fn draw_text(
266 &mut self,
267 text: String,
268 position: (f32, f32),
269 font: String,
270 color: Color,
271 size: f32,
272 ) -> anyhow::Result<()> {
273 let manager = self.resource_manager.clone();
274 let mut manager = manager
275 .try_lock()
276 .context("failed to lock resource_manager")
277 .unwrap();
278
279 let cache_key = format!(
283 "{}|{}|{}|{:x?}",
284 font,
285 text,
286 size,
287 color.to_u32(&PixelFormat::RGBA32)
288 );
289 let font: &DummyFont = {
290 let asset = manager.get_asset(font)?;
291 resources::downcast_ref(asset)?
292 };
293
294 let font_key = format!("{}|{}", font.name(), size);
295
296 if !manager.is_cached(font_key.clone()) {
297 let asset = Font::load(font.path.clone(), font_key.clone(), self, Some(size));
298 manager.cache_asset(asset?)?;
299 }
300
301 if !manager.is_cached(cache_key.clone()) {
302 let font = resources::downcast_ref::<Font>(manager.get_asset(font_key)?)?;
303 let surface = font
304 .buffer
305 .render(&text)
306 .blended(color)
307 .map_err(|e| anyhow::anyhow!("render error: {e}"))?;
308 let image = Image::load_from_surface(cache_key.clone(), self, surface);
309 manager.cache_asset(image?)?;
310 }
311 let texture: &Image = resources::downcast_ref(manager.get_asset(cache_key)?)?;
312
313 let dst_rect = FRect::new(
314 position.0,
315 position.1,
316 texture.width as f32,
317 texture.height as f32,
318 );
319 self.canvas
320 .copy_ex(
321 &texture.texture,
322 None,
323 Some(dst_rect),
324 0.0,
325 None,
326 false,
327 false,
328 )
329 .unwrap();
330 Ok(())
331 }
332}