use std::collections::HashMap;
use glyphon::{Attrs, Buffer, Cache, Color, Family, FontSystem, Metrics, Resolution, Shaping,
SwashCache, TextArea, TextAtlas, TextBounds, TextRenderer, Viewport};
use crate::Camera;
static FALLBACK_FONT: &[u8] = include_bytes!("../assets/Inter-Bold.ttf");
pub struct LabelDraw<'a> {
pub key: u32,
pub text: &'a str,
pub pos: [f32; 2],
pub radius: f32,
pub opacity: f32,
}
#[derive(Clone, Copy)]
struct PlacedLabel { key: u32, pos: [f32; 2], radius: f32, opacity: f32 }
const LABEL_CACHE_CAP: usize = 2048;
pub struct Labels {
font_system: FontSystem,
swash: SwashCache,
atlas: TextAtlas,
viewport: Viewport,
renderer: TextRenderer,
shaped: HashMap<u32, Buffer>,
placed: Vec<PlacedLabel>,
}
impl Labels {
pub fn new(device: &wgpu::Device, queue: &wgpu::Queue, format: wgpu::TextureFormat) -> Self {
let cache = Cache::new(device);
let mut atlas = TextAtlas::new(device, queue, &cache, format);
let viewport = Viewport::new(device, &cache);
let renderer = TextRenderer::new(&mut atlas, device, wgpu::MultisampleState::default(), None);
let mut font_system = FontSystem::new();
let db = font_system.db_mut();
db.load_font_data(FALLBACK_FONT.to_vec());
let family = db.faces().next().map(|face| face.families[0].0.clone());
if let Some(family) = family {
db.set_sans_serif_family(family.clone());
db.set_serif_family(family.clone());
db.set_monospace_family(family.clone());
db.set_cursive_family(family.clone());
db.set_fantasy_family(family);
}
Self {
font_system,
swash: SwashCache::new(),
atlas,
viewport,
renderer,
shaped: HashMap::new(),
placed: Vec::new(),
}
}
fn project(cam: &Camera, world: [f32; 2]) -> (f32, f32) {
let relx = (world[0] - cam.center[0]) * cam.zoom;
let rely = (world[1] - cam.center[1]) * cam.zoom;
let sx = cam.viewport[0] * 0.5 + relx;
let sy = cam.viewport[1] * 0.5 - rely; (sx, sy)
}
pub fn set_labels(&mut self, labels: &[LabelDraw]) {
if self.shaped.len() > LABEL_CACHE_CAP {
self.shaped.clear();
}
self.placed.clear();
let Self { shaped, font_system, placed, .. } = self;
for l in labels {
shaped.entry(l.key).or_insert_with(|| {
let mut buf = Buffer::new(font_system, Metrics::new(14.0, 18.0));
buf.set_text(font_system, l.text, Attrs::new().family(Family::SansSerif), Shaping::Advanced);
buf.shape_until_scroll(font_system, false);
buf
});
placed.push(PlacedLabel { key: l.key, pos: l.pos, radius: l.radius, opacity: l.opacity });
}
}
pub fn prepare(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, cam: &Camera) -> Result<(), String> {
self.viewport.update(queue, Resolution { width: cam.viewport[0] as u32, height: cam.viewport[1] as u32 });
let areas: Vec<TextArea> = self.placed.iter().filter_map(|p| {
let buf = self.shaped.get(&p.key)?;
let (sx, sy) = Self::project(cam, p.pos);
Some(TextArea {
buffer: buf,
left: sx + p.radius * cam.zoom + 4.0,
top: sy - 9.0,
scale: 1.0,
bounds: TextBounds { left: 0, top: 0, right: cam.viewport[0] as i32, bottom: cam.viewport[1] as i32 },
default_color: Color::rgba(210, 214, 220, (cam.fade * p.opacity * 255.0) as u8),
custom_glyphs: &[],
})
}).collect();
self.renderer.prepare(device, queue, &mut self.font_system, &mut self.atlas, &self.viewport, areas, &mut self.swash).map_err(|e| e.to_string())?;
Ok(())
}
pub fn draw<'a>(&'a self, rpass: &mut wgpu::RenderPass<'a>) -> Result<(), String> {
self.renderer.render(&self.atlas, &self.viewport, rpass).map_err(|e| e.to_string())?;
Ok(())
}
}