cotis-raylib 0.1.0-alpha

Raylib-backed renderer for Cotis
Documentation
//! Raylib window renderer and handle types.
//!
//! [`RaylibRender`] owns the raylib window, drawing thread, font table, and image texture cache.
//! It implements [`CotisRenderer`](cotis::renders::CotisRenderer) in [`render_trait`] for
//! [`crate::drawables::RaylibDrawable`] command streams.
//!
//! Application code usually constructs a [`RaylibRender`] via [`RaylibRender::auto_start`] or
//! [`RaylibRender::new`], then passes it to [`CotisApp`](cotis::cotis_app::CotisApp).

use crate::fonts::ScalableFont;
use cotis::renders::RenderCompatibleWith;
use raylib::color::Color;
use raylib::consts::TextureFilter;
use raylib::error::RaylibError;
use raylib::prelude::Texture2D;
use raylib::{RaylibBuilder, RaylibHandle, RaylibThread};
use std::collections::HashMap;
use std::ops::{Deref, DerefMut};
use std::sync::{Arc, Mutex, MutexGuard};

pub mod render_trait;

/// Shared, mutex-protected raylib handle used across threads for input polling.
pub type SharedRaylibHandle = Arc<Mutex<RayRenderHandle>>;

/// Mutex-guarded wrapper around [`RaylibHandle`].
///
/// Implements [`Deref`] and [`DerefMut`] to the inner handle so raylib methods can be called
/// directly on `&RayRenderHandle`. Input traits from `cotis-utils` are implemented on this type in
/// [`crate::interactivity`].
pub struct RayRenderHandle(pub(crate) RaylibHandle);

impl Deref for RayRenderHandle {
    type Target = RaylibHandle;
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}
impl DerefMut for RayRenderHandle {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

/// Primary raylib-backed renderer for Cotis applications.
///
/// Owns the window handle (behind a mutex), the raylib drawing thread, loaded fonts, and a
/// path-keyed texture cache. Implements [`CotisRenderer`](cotis::renders::CotisRenderer) for
/// [`crate::drawables::RaylibDrawable`] streams and Cotis context traits in [`crate::cotis`].
///
/// # Construction
///
/// - [`RaylibRender::auto_start`] — gray transparent background, 1000×1000 resizable window.
/// - [`RaylibRender::new`] — custom clear color, same window defaults.
/// - [`RaylibRender::new_custom`] — full control via [`RaylibBuilder`].
pub struct RaylibRender {
    rl: Arc<Mutex<RayRenderHandle>>,
    thread: RaylibThread,
    fonts: Arc<Mutex<HashMap<usize, ScalableFont>>>,
    font_max_id: usize,
    image_cache: HashMap<String, Arc<Texture2D>>,
    /// Clear color used at the start of each frame.
    pub background_color: Color,
}

impl<Manager> RenderCompatibleWith<Manager> for RaylibRender {
    fn init(&mut self, _layout_manager: &mut Manager) {}
}

impl RaylibRender {
    /// Creates a renderer with a gray transparent background and a default 1000×1000 resizable window.
    pub fn auto_start() -> Self {
        Self::new(Color::new(100, 100, 100, 0))
    }

    /// Creates a renderer with the given background clear color and a default 1000×1000 resizable window.
    pub fn new(background_color: Color) -> Self {
        let (rl, thread) = raylib::init()
            .resizable()
            .size(1000, 1000)
            .title("Raylib")
            .build();
        Self {
            rl: Arc::new(Mutex::new(RayRenderHandle(rl))),
            thread,
            fonts: Default::default(),
            font_max_id: 0,
            image_cache: Default::default(),
            background_color,
        }
    }

    /// Creates a renderer from a custom [`RaylibBuilder`].
    pub fn new_custom(raylib_builder: RaylibBuilder, background_color: Color) -> Self {
        let (rl, thread) = raylib_builder.build();
        Self {
            rl: Arc::new(Mutex::new(RayRenderHandle(rl))),
            thread,
            fonts: Default::default(),
            font_max_id: 0,
            image_cache: Default::default(),
            background_color,
        }
    }

    /// Locks and returns the raylib handle.
    ///
    /// # Panics
    ///
    /// Panics if the mutex is poisoned.
    pub fn get_handle(&mut self) -> MutexGuard<'_, RayRenderHandle> {
        self.rl.lock().unwrap()
    }

    /// Locks and returns the raylib handle (immutable receiver).
    ///
    /// # Panics
    ///
    /// Panics if the mutex is poisoned.
    pub fn lock_handle(&self) -> MutexGuard<'_, RayRenderHandle> {
        self.rl.lock().unwrap()
    }

    /// Returns the raylib drawing thread.
    ///
    /// Must be passed to drawing calls on the thread that created the window.
    pub fn get_thread(&self) -> &RaylibThread {
        &self.thread
    }

    /// Returns the raylib drawing thread.
    ///
    /// Alias for [`Self::get_thread`].
    pub fn thread(&self) -> &RaylibThread {
        &self.thread
    }

    /// Returns the locked handle and drawing thread together for low-level drawing.
    ///
    /// # Panics
    ///
    /// Panics if the mutex is poisoned.
    pub fn get_raylib(&mut self) -> (MutexGuard<'_, RayRenderHandle>, &mut RaylibThread) {
        (self.rl.lock().unwrap(), &mut self.thread)
    }

    /// Returns whether the window close flag is set.
    ///
    /// # Panics
    ///
    /// Panics if the mutex is poisoned.
    pub fn window_should_close(&self) -> bool {
        self.rl.lock().unwrap().window_should_close()
    }

    /// Loads a font at size 16 and registers it in the font table.
    ///
    /// Returns a font ID to store in [`TextConfig::font_id`](cotis_defaults::element_configs::text_config::TextConfig::font_id).
    /// IDs are assigned in load order starting at `0` and are never reused.
    ///
    /// # Errors
    ///
    /// Returns [`RaylibError`] if raylib cannot load the font file.
    pub fn load_font_and_keep(&mut self, file_name: &str) -> Result<usize, RaylibError> {
        self.load_font_ex_and_keep(file_name, 16, None, None)
    }

    pub(crate) fn load_new_font_sizes(&mut self) {
        let mut fonts = self.fonts.lock().unwrap();
        for font in fonts.values_mut() {
            font.load_queued_fonts(&mut self.rl.lock().unwrap(), &self.thread)
                .unwrap();
        }
    }

    /// Loads a font with custom size, optional character subset, and texture filter.
    ///
    /// Returns a font ID (0-based, monotonic) for use in [`TextConfig::font_id`](cotis_defaults::element_configs::text_config::TextConfig::font_id).
    /// Additional pixel sizes requested during drawing are queued and loaded at the start of the
    /// next frame.
    ///
    /// # Errors
    ///
    /// Returns [`RaylibError`] if raylib cannot load the font file.
    pub fn load_font_ex_and_keep(
        &mut self,
        file_name: &str,
        font_size: i32,
        chars: Option<&str>,
        filter: Option<TextureFilter>,
    ) -> Result<usize, RaylibError> {
        let mut rl = self.rl.lock().unwrap();
        self.fonts.lock().unwrap().insert(
            self.font_max_id,
            ScalableFont::new(
                file_name.to_string(),
                chars.map(|s| s.to_string()),
                filter,
                &mut rl,
                &self.thread,
                font_size as usize,
            )?,
        );
        self.font_max_id += 1;
        Ok(self.font_max_id - 1)
    }

    /// Returns the elapsed time between the previous and current frame, in seconds.
    ///
    /// # Panics
    ///
    /// Panics if the mutex is poisoned.
    pub fn delta_time(&self) -> f32 {
        self.rl.lock().unwrap().get_frame_time()
    }

    /// Returns a clone of the shared raylib handle for cross-thread input polling.
    ///
    /// Pair with [`Self::thread`] when calling raylib drawing APIs from the owning thread.
    pub fn get_shared_handle(&self) -> SharedRaylibHandle {
        self.rl.clone()
    }

    /// Returns a clone of the shared raylib handle.
    ///
    /// Alias for [`Self::get_shared_handle`].
    pub fn shared_handle(&self) -> SharedRaylibHandle {
        self.get_shared_handle()
    }

    /// Returns the path-keyed texture cache populated by [`translate_generic_image`](RaylibRender::translate_generic_image).
    pub fn image_cache(&self) -> &HashMap<String, Arc<Texture2D>> {
        &self.image_cache
    }

    /// Returns a mutable reference to the texture cache for manual preload or inspection.
    pub fn image_cache_mut(&mut self) -> &mut HashMap<String, Arc<Texture2D>> {
        &mut self.image_cache
    }

    /// Returns the shared font table used during drawing and text measurement.
    pub fn fonts(&self) -> Arc<Mutex<HashMap<usize, ScalableFont>>> {
        self.fonts.clone()
    }
}