fennel_engine/resources/
loadable.rs

1use anyhow::bail;
2use image::ImageReader;
3use sdl3::{
4    pixels::PixelFormat,
5    render::Texture, surface::Surface
6};
7use std::{
8    cell::{Ref, RefCell},
9    path::PathBuf,
10    rc::Rc,
11};
12
13use crate::{graphics::Graphics, resources::LoadableResource};
14
15/// Simple image asset that stores its file location.
16pub struct Image {
17    /// Resource name (can be filesystem path to the image or something else)
18    pub name: String,
19    /// Vector of bytes containing the image pixels
20    pub buffer: Rc<RefCell<Vec<u8>>>,
21    /// SDL3 texture for caching
22    pub texture: Rc<Texture<'static>>,
23    /// Image width
24    pub width: u32,
25    /// Image heiht
26    pub height: u32,
27}
28
29/// Simple font asset
30pub struct Font {
31    /// Filesystem path to the font.
32    pub path: PathBuf,
33    /// Font family name
34    pub family_name: String,
35    /// Point size
36    pub size: f32,
37    /// Vector of bytes containing the font data
38    pub buffer: Rc<sdl3::ttf::Font<'static>>,
39}
40
41impl Image {
42    pub fn load_from_surface(
43        name: String,
44        graphics: &mut Graphics,
45        surface: Surface
46    ) -> anyhow::Result<Box<dyn LoadableResource>> {
47        let texture = unsafe {
48            std::mem::transmute::<sdl3::render::Texture<'_>, sdl3::render::Texture<'static>>(
49                graphics.texture_creator.create_texture_from_surface(&surface)?,
50            )
51        };
52
53        Ok(Box::new(Self {
54            name,
55            buffer: Rc::new(RefCell::new(vec![])), // wtf
56            texture: Rc::new(texture),
57            width: surface.width(),
58            height: surface.height()
59        }))
60    }
61}
62
63impl LoadableResource for Image {
64    /// Construct an `Image` from `path` and return it as a boxed trait object.
65    fn load(
66        path: PathBuf,
67        graphics: &mut Graphics,
68        _size: Option<f32>
69    ) -> anyhow::Result<Box<dyn LoadableResource>> {
70        let img = ImageReader::open(&path)?.decode()?;
71        let mut buffer = img.to_rgba8().into_raw();
72        let surface = sdl3::surface::Surface::from_data(
73            &mut buffer,
74            img.width(),
75            img.height(),
76            img.width() * 4,
77            PixelFormat::RGBA32,
78        )?;
79
80        let texture = unsafe {
81            std::mem::transmute::<sdl3::render::Texture<'_>, sdl3::render::Texture<'static>>(
82                graphics.texture_creator.create_texture_from_surface(surface)?,
83            )
84        };
85
86        Ok(Box::new(Self {
87            name: path.to_string_lossy().to_string(),
88            buffer: Rc::new(RefCell::new(buffer)),
89            texture: Rc::new(texture),
90            width: img.width(),
91            height: img.height(),
92        }))
93    }
94
95    fn name(&self) -> String {
96        self.name.clone()
97    }
98
99    fn as_mut_slice(&self) -> Option<&mut [u8]> {
100        let mut mut_ref = self.buffer.borrow_mut();
101        // even more evil shit that PROBABLY :) should be safe because as we know in normal conditions only
102        // one thread should access (graphics, audio, ..) its respecting resources
103        // otherwise have a SEGFAULT >:3
104        unsafe { Some(&mut *(mut_ref.as_mut_slice() as *mut [u8])) }
105    }
106
107    fn as_slice(&self) -> Option<Ref<'_, [u8]>> {
108        Some(Ref::map(self.buffer.borrow(), |v| v.as_slice()))
109    }
110}
111
112impl LoadableResource for Font {
113    fn load(
114            path: PathBuf,
115            graphics: &mut Graphics,
116            size: Option<f32>
117        ) -> anyhow::Result<Box<dyn LoadableResource>>
118        where
119            Self: Sized {
120        if size.is_none() {
121            bail!("no font size was provided");
122        }
123
124        let font = graphics.ttf_context.load_font(&path, size.unwrap())?;
125        Ok(Box::new(Self {
126            path,
127            family_name: font.face_family_name().expect("failed to get font family name"),
128            size: size.unwrap(),
129            buffer: Rc::new(font)
130        }))
131    }
132
133    fn name(&self) -> String {
134        format!("{} {}", self.family_name, self.size)
135    }
136}