use super::drawable::Drawable;
use super::style::{StyleParams, StyleRegistry};
use super::text::TextCache;
use crate::{
Embedded,
poly2d::Poly2D,
vm::{Atom, GeoId, VM},
};
pub struct UiRenderer {
styles: StyleRegistry,
text: TextCache,
next_id: u32,
tinted_tiles_cache: std::collections::HashMap<(uuid::Uuid, [u8; 16]), uuid::Uuid>, }
impl UiRenderer {
pub fn new() -> Self {
let font_bytes = Embedded::get("ui_font.ttf").map(|d| d.data.to_vec());
Self {
styles: StyleRegistry::new(),
text: TextCache::new(font_bytes),
next_id: 0,
tinted_tiles_cache: std::collections::HashMap::new(),
}
}
pub fn text_cache(&self) -> &TextCache {
&self.text
}
fn alloc_id(&mut self) -> GeoId {
let id = self.next_id;
self.next_id = self.next_id.wrapping_add(1);
GeoId::Unknown(id)
}
pub fn render(&mut self, vm: &mut VM, drawables: &[Drawable]) {
self.render_internal(vm, drawables, true);
}
pub fn render_no_clear(&mut self, vm: &mut VM, drawables: &[Drawable]) {
self.render_internal(vm, drawables, false);
}
fn render_internal(&mut self, vm: &mut VM, drawables: &[Drawable], clear: bool) {
if clear {
vm.execute(Atom::ClearGeometry);
}
for d in drawables {
match d {
Drawable::Quad {
tile_id,
rect,
uv,
layer,
tint,
..
} => {
let verts = quad_verts(*rect);
let tinted_tile_id = self.ensure_tinted_tile(vm, *tile_id, *tint);
let poly = Poly2D::poly(
self.alloc_id(),
tinted_tile_id,
verts,
uv.to_vec(),
vec![(0, 1, 2), (0, 2, 3)],
)
.with_layer(*layer);
vm.execute(Atom::AddPoly { poly });
}
Drawable::Rect {
rect,
fill,
border,
radius_px,
border_px,
layer,
..
} => {
let verts = quad_verts(*rect);
let style = StyleParams {
fill: *fill,
border: *border,
radius_px: *radius_px,
border_px: *border_px,
};
let style_id = self.styles.ensure_style(vm, style);
let tile_id = self.styles.tile_id(style_id).expect("missing style tile");
let uv = vec![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
let poly = Poly2D::poly(
self.alloc_id(),
tile_id,
verts,
uv,
vec![(0, 1, 2), (0, 2, 3)],
)
.with_layer(*layer);
vm.execute(Atom::AddPoly { poly });
}
Drawable::Text {
id: _,
text,
origin,
px_size,
color,
layer,
} => {
let glyphs = self.text.layout_positions(text, *px_size);
let start_x = origin[0];
let start_y = origin[1];
for g in glyphs {
let Some(entry) = self.text.ensure_glyph(vm, g.parent, *px_size, *color)
else {
continue;
};
let x0 = start_x + g.x;
let y0 = start_y + g.y;
let w = g.width as f32;
let h = g.height as f32;
if w <= 0.0 || h <= 0.0 {
continue;
}
let verts = vec![[x0, y0], [x0 + w, y0], [x0 + w, y0 + h], [x0, y0 + h]];
let uv = vec![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
let poly = Poly2D::poly(
self.alloc_id(),
entry.tile_id,
verts,
uv,
vec![(0, 1, 2), (0, 2, 3)],
)
.with_layer(*layer);
vm.execute(Atom::AddPoly { poly });
}
}
}
}
self.styles.build_if_dirty(vm);
self.text.build_if_dirty(vm);
}
fn ensure_tinted_tile(
&mut self,
vm: &mut VM,
base_tile_id: uuid::Uuid,
tint: vek::Vec4<f32>,
) -> uuid::Uuid {
if (tint.x - 1.0).abs() < 0.001
&& (tint.y - 1.0).abs() < 0.001
&& (tint.z - 1.0).abs() < 0.001
&& (tint.w - 1.0).abs() < 0.001
{
return base_tile_id;
}
let mut tint_bytes = [0u8; 16];
tint_bytes[0..4].copy_from_slice(&tint.x.to_le_bytes());
tint_bytes[4..8].copy_from_slice(&tint.y.to_le_bytes());
tint_bytes[8..12].copy_from_slice(&tint.z.to_le_bytes());
tint_bytes[12..16].copy_from_slice(&tint.w.to_le_bytes());
let cache_key = (base_tile_id, tint_bytes);
if let Some(&cached_id) = self.tinted_tiles_cache.get(&cache_key) {
return cached_id;
}
let tile_data = vm.get_tile_data(base_tile_id);
if tile_data.is_none() {
return base_tile_id;
}
let (width, height, rgba_data) = tile_data.unwrap();
let mut tinted_data = rgba_data.clone();
for i in (0..tinted_data.len()).step_by(4) {
tinted_data[i] = ((tinted_data[i] as f32 / 255.0 * tint.x) * 255.0) as u8; tinted_data[i + 1] = ((tinted_data[i + 1] as f32 / 255.0 * tint.y) * 255.0) as u8; tinted_data[i + 2] = ((tinted_data[i + 2] as f32 / 255.0 * tint.z) * 255.0) as u8; tinted_data[i + 3] = ((tinted_data[i + 3] as f32 / 255.0 * tint.w) * 255.0) as u8; }
let tinted_tile_id = uuid::Uuid::new_v4();
vm.execute(Atom::AddTile {
id: tinted_tile_id,
width,
height,
frames: vec![tinted_data],
material_frames: Some(vec![super::create_tile_material(width, height)]),
});
vm.execute(Atom::BuildAtlas);
self.tinted_tiles_cache.insert(cache_key, tinted_tile_id);
tinted_tile_id
}
}
fn quad_verts(rect: [f32; 4]) -> Vec<[f32; 2]> {
let [x, y, w, h] = rect;
vec![[x, y], [x + w, y], [x + w, y + h], [x, y + h]]
}