fennel_engine/
graphics.rs1use 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 struct Graphics {
27 pub canvas: Canvas<Window>,
29 pub sdl_context: Sdl,
31 pub texture_creator: Rc<sdl3::render::TextureCreator<sdl3::video::WindowContext>>,
33 pub ttf_context: sdl3::ttf::Sdl3TtfContext,
35 resource_manager: Arc<Mutex<ResourceManager>>,
37}
38
39impl Graphics {
40 pub fn new(
56 name: String,
57 dimensions: (u32, u32),
58 resource_manager: Arc<Mutex<ResourceManager>>,
59 ) -> Result<Graphics, Box<dyn std::error::Error>> {
60 let sdl_context = sdl3::init()?;
64 let ttf_context = sdl3::ttf::init().map_err(|e| e.to_string())?;
65 let video_subsystem = sdl_context.video()?;
66
67 let window = video_subsystem
68 .window(&name, dimensions.0, dimensions.1)
69 .position_centered()
70 .resizable()
71 .build()
72 .map_err(|e| e.to_string())?;
73
74 let canvas = window.into_canvas();
75 let texture_creator = canvas.texture_creator();
76 Ok(Graphics {
77 canvas,
78 sdl_context,
79 texture_creator: Rc::new(texture_creator),
80 ttf_context,
81 resource_manager,
82 })
83 }
84
85 pub fn draw_image(&mut self, path: String, position: (f32, f32)) -> anyhow::Result<()> {
100 let manager = self.resource_manager.clone();
101 let mut manager = manager
102 .try_lock()
103 .context("failed to lock resource_manager")
104 .unwrap();
105
106 if !manager.is_cached(path.clone()) {
107 let texture = loadable::Image::load(PathBuf::from(path.clone()), self, None);
109 manager.cache_asset(texture?)?; }
111
112 let image: &Image = resources::as_concrete(manager.get_asset(path).unwrap())?;
113
114 let dst_rect = FRect::new(
115 position.0,
116 position.1,
117 image.width as f32,
118 image.height as f32,
119 );
120 self.canvas
121 .copy_ex(
122 &image.texture,
123 None,
124 Some(dst_rect),
125 0.0,
126 None,
127 false,
128 false,
129 )
130 .unwrap();
131
132 Ok(())
133 }
134
135 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!(
156 "{}|{}|{}|{:x?}",
157 font_path,
158 text,
159 size,
160 color.to_u32(&PixelFormat::RGBA32)
161 );
162 let font_key = format!("{font_path}|{size}");
163 let font: &Font = {
164 if !manager.is_cached(font_key.clone()) {
165 let asset = loadable::Font::load(font_path.clone().into(), self, Some(size));
166 manager.cache_asset(asset?)?;
167 }
168 let asset = manager.get_asset(font_key)?;
169 resources::as_concrete(asset)?
170 };
171
172 if !manager.is_cached(cache_key.clone()) {
173 let surface = font
174 .buffer
175 .render(&text)
176 .blended(color)
177 .map_err(|e| anyhow::anyhow!("render error: {e}"))?;
178 let image = Image::load_from_surface(cache_key.clone(), self, surface);
179 manager.cache_asset(image?)?;
180 }
181 let texture: &Image = resources::as_concrete(manager.get_asset(cache_key)?)?;
182
183 let dst_rect = FRect::new(
184 position.0,
185 position.1,
186 texture.width as f32,
187 texture.height as f32,
188 );
189 self.canvas
190 .copy_ex(
191 &texture.texture,
192 None,
193 Some(dst_rect),
194 0.0,
195 None,
196 false,
197 false,
198 )
199 .unwrap();
200 Ok(())
201 }
202}