cotis-raylib 0.1.0-alpha

Raylib-backed renderer for Cotis
Documentation
//! Image loading traits and texture cache integration for [`crate::renderer::RaylibRender`].
//!
//! Path-based images from `cotis-defaults` implement [`RaylibLoadableImage`] and
//! [`GenericCompatibleImage`]. Custom [`Image`](cotis_defaults::render_commands::Image) element data
//! can integrate by implementing these traits and using [`RaylibRender::translate_generic_image`] to
//! populate the renderer cache.
//!
//! # Custom image extension
//!
//! ```rust,ignore
//! use std::sync::Arc;
//! use cotis_raylib::drawables::RaylibDrawContext;
//! use cotis_raylib::raylib_images::{GenericCompatibleImage, RaylibLoadableImage};
//! use cotis_raylib::renderer::RaylibRender;
//! use raylib::prelude::Texture2D;
//!
//! struct MyImage { path: String }
//!
//! impl RaylibLoadableImage for MyImage {
//!     fn get_filename(&self) -> &str { &self.path }
//! }
//!
//! impl GenericCompatibleImage for MyImage {
//!     fn load_texture(&self, _ctx: &mut RaylibDrawContext<'_, '_>) {}
//!     fn get_texture(&self, ctx: &RaylibDrawContext<'_, '_>) -> Arc<Texture2D> {
//!         ctx.image_cache.expect("cache required").get(&self.path).unwrap().clone()
//!     }
//! }
//!
//! // Preload before drawing:
//! // render.translate_generic_image(&my_image);
//! ```

use crate::drawables::RaylibDrawContext;
use crate::renderer::RaylibRender;
use raylib::consts::TextureFilter;
use raylib::prelude::{RaylibTexture2D, Texture2D};
use std::fmt::{Debug, Formatter};
use std::ops::Deref;
use std::sync::Arc;

/// Path-based image data that can be loaded into the renderer texture cache.
pub trait RaylibLoadableImage {
    /// Filesystem path used as the cache key and passed to raylib's texture loader.
    fn get_filename(&self) -> &str;
}

impl RaylibRender {
    /// Loads a texture from a path-based image, caching the result by file path.
    ///
    /// On cache hit, returns the existing texture. On miss, loads via raylib with
    /// [`TextureFilter::TEXTURE_FILTER_POINT`] filtering.
    ///
    /// # Panics
    ///
    /// Panics if raylib cannot load the file at the given path.
    pub fn translate_generic_image(
        &mut self,
        image: &dyn RaylibLoadableImage,
    ) -> Box<dyn Texture2DHolder> {
        let path = image.get_filename();
        if self.image_cache().contains_key(path) {
            return Box::new(self.image_cache().get(path).unwrap().clone());
        }
        let texture: Arc<Texture2D> = {
            let mut rl = self.lock_handle();
            let res = rl.load_texture(self.thread(), path);
            match res {
                Ok(tex) => {
                    tex.set_texture_filter(self.thread(), TextureFilter::TEXTURE_FILTER_POINT);
                    tex.into()
                }
                Err(_) => panic!("[Raylib Error]: Failed to load path-based image from: {path}"),
            }
        };
        self.image_cache_mut().insert(path.to_owned(), texture);
        Box::new(self.image_cache().get(path).unwrap().clone())
    }
}

/// Abstraction over owned and borrowed raylib textures.
pub trait Texture2DHolder {
    /// Returns the underlying raylib texture.
    fn texture(&self) -> &Texture2D;
}

impl Texture2DHolder for Texture2D {
    fn texture(&self) -> &Texture2D {
        self
    }
}
impl<T: Texture2DHolder> Texture2DHolder for &T {
    fn texture(&self) -> &Texture2D {
        (*self).texture()
    }
}

impl Deref for dyn Texture2DHolder {
    type Target = Texture2D;

    fn deref(&self) -> &Self::Target {
        self.texture()
    }
}

impl Debug for dyn Texture2DHolder {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.write_str("dyn Texture2DHolder")
    }
}

/// Two-phase image draw: optional per-frame setup, then texture lookup from cache.
pub trait GenericCompatibleImage: RaylibLoadableImage {
    /// Called before [`Self::get_texture`] during drawing (no-op for path-based types).
    fn load_texture(&self, ctx: &mut RaylibDrawContext<'_, '_>);

    /// Returns the texture to draw. Path-based types read from [`RaylibDrawContext::image_cache`].
    ///
    /// # Panics
    ///
    /// Panics if `image_cache` is `None` or the path was not preloaded.
    fn get_texture(&self, ctx: &RaylibDrawContext<'_, '_>) -> Arc<Texture2D>;
}