fennel_engine/resources/
loadable.rs

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