roast2d_internal 0.3.4

Roast2D internal crate
Documentation
use core::fmt;
use std::{hash::Hash, num::NonZeroUsize};

use crate::{color::Color, handle::Handle};
use glam::UVec2;
use lru::LruCache;

const TEXT_CACHE_SIZE: usize = 256;

#[derive(Default, PartialEq, Hash, Clone, Copy, Debug)]
pub enum WrapStyle {
    #[default]
    Word,
    Letter,
}

impl From<WrapStyle> for fontdue::layout::WrapStyle {
    fn from(value: WrapStyle) -> Self {
        match value {
            WrapStyle::Letter => fontdue::layout::WrapStyle::Letter,
            WrapStyle::Word => fontdue::layout::WrapStyle::Word,
        }
    }
}

#[derive(Clone, Debug)]
pub(crate) struct TextCache {
    cache: LruCache<Text, TextRenderCache>,
}

impl Default for TextCache {
    fn default() -> Self {
        let cache = LruCache::new(NonZeroUsize::new(TEXT_CACHE_SIZE).unwrap());
        Self { cache }
    }
}

impl TextCache {
    pub fn get(&mut self, text: &Text) -> Option<&TextRenderCache> {
        self.cache.get(text)
    }

    pub fn update(&mut self, text: Text, cache: TextRenderCache) {
        self.cache.push(text, cache);
    }
}

#[derive(Clone, Debug)]
pub(crate) struct TextRenderCache {
    pub(crate) texture: Handle,
    pub(crate) size: UVec2,
}

#[derive(Clone, Default, Debug, PartialEq)]
pub struct TextSection {
    pub(crate) text: String,
    pub(crate) font: Option<Handle>,
    pub(crate) scale: f32,
    pub(crate) color: Color,
}

impl Hash for TextSection {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        // text
        state.write_usize(self.text.len());
        state.write(self.text.as_bytes());

        // font
        self.font.as_ref().map(|f| f.id()).hash(state);

        // scale
        state.write_u32(self.scale.to_bits());

        // color
        state.write_u32(self.color.r.to_bits());
        state.write_u32(self.color.g.to_bits());
        state.write_u32(self.color.b.to_bits());
        state.write_u32(self.color.a.to_bits());
    }
}

impl Eq for TextSection {}

impl TextSection {
    pub fn new(text: String, scale: f32, color: Color) -> Self {
        Self {
            text,
            scale,
            color,
            font: None,
        }
    }

    /// Set font
    pub fn font(mut self, font: Handle) -> Self {
        self.font.replace(font);
        self
    }
}

#[derive(Clone, Default, PartialEq)]
pub struct Text {
    pub(crate) sections: Vec<TextSection>,
    pub(crate) max_width: Option<f32>,
    pub(crate) max_height: Option<f32>,
    pub(crate) wrap_style: WrapStyle,
}

impl Hash for Text {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        // sections
        for s in &self.sections {
            s.hash(state);
        }
        // max_width
        self.max_width.map(|m| m.to_bits()).hash(state);
        // max_height
        self.max_height.map(|m| m.to_bits()).hash(state);
        // wrap_style
        self.wrap_style.hash(state);
    }
}

impl Eq for Text {}

impl From<TextSection> for Text {
    fn from(section: TextSection) -> Self {
        Self::from_sections(vec![section])
    }
}

impl fmt::Debug for Text {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Text")
            .field("sections", &self.sections)
            .finish()
    }
}

impl Text {
    pub fn new(text: String, scale: f32, color: Color) -> Self {
        let section = TextSection::new(text, scale, color);
        Self::from_sections(vec![section])
    }

    pub fn from_sections(sections: Vec<TextSection>) -> Self {
        Self {
            sections,
            max_height: None,
            max_width: None,
            wrap_style: WrapStyle::Word,
        }
    }

    pub fn with_max_height(mut self, max_height: f32) -> Self {
        self.max_height.replace(max_height);
        self
    }

    pub fn with_max_width(mut self, max_width: f32) -> Self {
        self.max_width.replace(max_width);
        self
    }

    pub fn with_wrap_style(mut self, wrap_style: WrapStyle) -> Self {
        self.wrap_style = wrap_style;
        self
    }
}