use cosmic_text::{Attrs, AttrsOwned, Buffer, Family, FontSystem, Metrics, Shaping, SwashCache}; use palette::Srgba; use showbits_assets::{UNIFONT, UNIFONT_JP, UNIFONT_NAME, UNIFONT_SIZE, UNIFONT_UPPER}; use taffy::{ prelude::{AvailableSpace, Size}, Layout, }; use crate::{color, Rect, Vec2, View, Widget}; // https://github.com/DioxusLabs/taffy/blob/main/examples/cosmic_text.rs pub struct FontStuff { font_system: FontSystem, swash_cache: SwashCache, } impl FontStuff { pub fn new() -> Self { let mut font_system = FontSystem::new(); let db = font_system.db_mut(); db.load_font_data(UNIFONT.to_vec()); db.load_font_data(UNIFONT_JP.to_vec()); db.load_font_data(UNIFONT_UPPER.to_vec()); db.set_monospace_family(UNIFONT_NAME); Self { font_system, swash_cache: SwashCache::new(), } } } impl Default for FontStuff { fn default() -> Self { Self::new() } } pub trait HasFontStuff { fn font_stuff(&mut self) -> &mut FontStuff; } pub struct Text { metrics: Metrics, default_attrs: AttrsOwned, chunks: Vec<(AttrsOwned, String)>, shaping: Shaping, color: Srgba, } impl Text { pub const fn default_metrics() -> Metrics { Metrics::new(UNIFONT_SIZE, UNIFONT_SIZE) } pub fn default_attrs<'a>() -> Attrs<'a> { Attrs::new().family(Family::Monospace) } pub fn new() -> Self { Self { metrics: Self::default_metrics(), default_attrs: AttrsOwned::new(Self::default_attrs()), chunks: vec![], shaping: Shaping::Advanced, color: color::BLACK, } } pub fn with_metrics(mut self, metrics: Metrics) -> Self { self.metrics = metrics; self } pub fn with_font_size(mut self, size: f32) -> Self { self.metrics.font_size = size; self } pub fn with_line_height(mut self, height: f32) -> Self { self.metrics.line_height = height; self } pub fn with_default_attrs(mut self, attrs: Attrs<'_>) -> Self { self.default_attrs = AttrsOwned::new(attrs); self } pub fn with_shaping(mut self, shaping: Shaping) -> Self { self.shaping = shaping; self } pub fn with_color(mut self, color: Srgba) -> Self { self.color = color; self } pub fn and_plain(mut self, text: S) -> Self { let chunk = (self.default_attrs.clone(), text.to_string()); self.chunks.push(chunk); self } pub fn and_rich(mut self, attrs: Attrs<'_>, text: S) -> Self { let chunk = (AttrsOwned::new(attrs), text.to_string()); self.chunks.push(chunk); self } pub fn and_chunks(mut self, chunks: I) -> Self where I: IntoIterator, { self.chunks.extend(chunks); self } pub fn widget(self, font_stuff: &mut FontStuff) -> impl Widget { let fs = &mut font_stuff.font_system; let mut buffer = Buffer::new_empty(self.metrics); buffer.set_size(fs, f32::INFINITY, f32::INFINITY); let spans = self .chunks .iter() .map(|(attrs, text)| (text as &str, attrs.as_attrs())); buffer.set_rich_text(fs, spans, self.default_attrs.as_attrs(), self.shaping); TextWidget { buffer, color: self.color, } } } impl Default for Text { fn default() -> Self { Self::new() } } struct TextWidget { buffer: Buffer, color: Srgba, } impl Widget for TextWidget { fn size( &mut self, ctx: &mut C, known: Size>, available: Size, ) -> Size { let width = known.width.unwrap_or(match available.width { AvailableSpace::Definite(width) => width, AvailableSpace::MinContent => 0.0, AvailableSpace::MaxContent => f32::INFINITY, }); let fs = &mut ctx.font_stuff().font_system; self.buffer.set_size(fs, width, f32::INFINITY); self.buffer.shape_until_scroll(fs, false); let runs = self.buffer.layout_runs(); let height = runs.len() as f32 * self.buffer.metrics().line_height; let width = runs .map(|run| run.line_w) .max_by(|a, b| a.partial_cmp(b).unwrap()) .unwrap_or(0.0); // If we don't round up here, the layout rounding may round down our // size slightly. This may lead to more line breaks, moving some words // below our visible area. let width = width.ceil(); let height = height.ceil(); Size { width, height } } fn draw_below( &mut self, ctx: &mut C, view: &mut View<'_>, _layout: &Layout, ) -> anyhow::Result<()> { let size = view.size(); let FontStuff { font_system: fs, swash_cache: sc, } = ctx.font_stuff(); self.buffer.set_size(fs, size.x as f32, size.y as f32); self.buffer.shape_until_scroll(fs, true); let color = color::to_text_color(self.color); self.buffer.draw(fs, sc, color, |x, y, w, h, color| { let color = color::from_text_color(color); let area = Rect::from_nw(Vec2::new(x, y), Vec2::from_u32(w, h)); view.rect(area, color); }); Ok(()) } }