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) {
state.write_usize(self.text.len());
state.write(self.text.as_bytes());
self.font.as_ref().map(|f| f.id()).hash(state);
state.write_u32(self.scale.to_bits());
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,
}
}
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) {
for s in &self.sections {
s.hash(state);
}
self.max_width.map(|m| m.to_bits()).hash(state);
self.max_height.map(|m| m.to_bits()).hash(state);
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
}
}