use ab_glyph::FontArc;
use glyph_brush::ab_glyph::PxScale;
use glyph_brush::{
ab_glyph, BrushAction, BrushError, DefaultSectionHasher, FontId, GlyphBrush, GlyphBrushBuilder,
HorizontalAlign, Layout, OwnedSection, OwnedText, VerticalAlign,
};
use image::{ImageBuffer, Luma};
use std::sync::Arc;
use anyhow::Result;
use super::super::resources::vulkan::shaders::*;
use crate::prelude::*;
use glam::f32::{vec2, Vec2};
#[derive(Clone)]
pub struct LabelCreateInfo {
pub transform: Transform,
pub appearance: Appearance,
pub text: String,
pub scale: Vec2,
pub align: Direction,
}
impl LabelCreateInfo {
#[inline]
pub fn transform(mut self, transform: Transform) -> Self {
self.transform = transform;
self
}
#[inline]
pub fn appearance(mut self, appearance: Appearance) -> Self {
self.appearance = appearance;
self
}
#[inline]
pub fn text<T>(mut self, text: T) -> Self
where
T: Into<String>,
{
self.text = text.into();
self
}
#[inline]
pub fn scale<T>(mut self, scale: T) -> Self
where
T: Into<Vec2>,
{
self.scale = scale.into();
self
}
#[inline]
pub fn align<T>(mut self, align: T) -> Self
where
T: Into<Direction>,
{
self.align = align.into();
self
}
}
impl Default for LabelCreateInfo {
fn default() -> Self {
Self {
transform: Transform::default(),
appearance: Appearance::default(),
text: String::new(),
scale: vec2(25.0, 25.0),
align: Direction::Nw,
}
}
}
#[derive(Clone)]
pub struct Label<Object> {
pub object: Object,
pub font: Font,
pub text: String,
pub scale: Vec2,
pub align: Direction,
section: OwnedSection<Extra>,
}
impl Label<NewObject> {
pub fn new(font: &Font, create_info: LabelCreateInfo) -> Self {
let mut object = NewObject::new();
object.transform = create_info.transform;
object.appearance = create_info.appearance;
Self {
object,
font: font.clone(),
text: create_info.text,
scale: create_info.scale,
align: create_info.align,
section: OwnedSection::default(),
}
}
pub fn init(mut self, layer: &Arc<Layer>) -> Result<Label<Object>> {
let mut labelifier = LABELIFIER.lock();
self.update_section(
labelifier.increment_tasks(),
self.object.appearance.get_transform().size,
);
let object = self.object.init(layer)?;
let label = Label {
object,
font: self.font,
text: self.text,
scale: self.scale,
align: self.align,
section: self.section,
};
labelifier.queue(label.clone());
Ok(label)
}
pub fn init_with_parent(
mut self,
layer: &Arc<Layer>,
parent: &Object,
) -> Result<Label<Object>> {
let mut labelifier = LABELIFIER.lock();
self.update_section(
labelifier.increment_tasks(),
self.object.appearance.get_transform().size,
);
let object = self.object.init_with_parent(layer, parent)?;
let label = Label {
object,
font: self.font,
text: self.text,
scale: self.scale,
align: self.align,
section: self.section,
};
labelifier.queue(label.clone());
Ok(label)
}
pub fn init_with_optional_parent(
mut self,
layer: &Arc<Layer>,
parent: Option<&Object>,
) -> Result<Label<Object>> {
let mut labelifier = LABELIFIER.lock();
self.update_section(
labelifier.increment_tasks(),
self.object.appearance.get_transform().size,
);
let object = self.object.init_with_optional_parent(layer, parent)?;
let label = Label {
object,
font: self.font,
text: self.text,
scale: self.scale,
align: self.align,
section: self.section,
};
labelifier.queue(label.clone());
Ok(label)
}
}
impl<T> Label<T> {
fn update_section(&mut self, id: usize, size: Vec2) {
let dimensions: (f32, f32) = ((1000.0 * size[0]), (1000.0 * size[1]));
let text = OwnedText {
text: self.text.clone(),
scale: PxScale {
x: self.scale.x,
y: self.scale.y,
},
font_id: self.font.id(),
extra: Extra { id },
};
let (h, v): (HorizontalAlign, VerticalAlign) = self.align.into();
let x = match h {
HorizontalAlign::Left => 0.0,
HorizontalAlign::Center => dimensions.0 * 0.5,
HorizontalAlign::Right => dimensions.0,
};
let y = match v {
VerticalAlign::Top => 0.0,
VerticalAlign::Center => dimensions.1 * 0.5,
VerticalAlign::Bottom => dimensions.1,
};
self.section = OwnedSection::default()
.with_bounds(dimensions)
.with_layout(Layout::default().h_align(h).v_align(v))
.with_screen_position((x, y))
.add_text(text);
}
}
impl Label<Object> {
pub fn update(&mut self) {
self.object.update();
}
pub fn update_text(&mut self, text: impl Into<String>) {
self.text = text.into();
self.sync();
}
pub fn sync(&mut self) {
let mut labelifier = LABELIFIER.lock();
self.update_section(
labelifier.increment_tasks(),
self.object.appearance.get_transform().size,
);
labelifier.queue(self.clone());
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
struct TextVertex {
rect: [Vertex; 4],
extra: Extra,
}
impl TextVertex {
pub fn indices(&self, id: u32) -> Vec<u32> {
vec![id, 1 + id, 2 + id, 1 + id, 2 + id, 3 + id]
}
}
#[derive(Clone, Copy, Debug, PartialEq, Default)]
struct Extra {
id: usize,
}
impl std::hash::Hash for Extra {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
state.write_usize(self.id);
}
}
pub(crate) struct Labelifier {
material: Material,
glyph_brush: GlyphBrush<TextVertex, Extra, FontArc, DefaultSectionHasher>,
cache_pixel_buffer: ImageBuffer<Luma<u8>, Vec<u8>>,
queued: Vec<DrawTask>,
tasks: usize,
ready: bool,
}
impl Labelifier {
pub fn new() -> Result<Self> {
let glyph_brush = GlyphBrushBuilder::using_fonts(vec![]).build(); let cache_pixel_buffer = ImageBuffer::from_pixel(
glyph_brush.texture_dimensions().0,
glyph_brush.texture_dimensions().1,
image::Luma([0u8]),
);
let dimensions = glyph_brush.texture_dimensions();
let settings = TextureSettings {
srgb: false,
sampler: Sampler::default(),
};
let texture = Texture::from_raw(
cache_pixel_buffer.as_raw(),
dimensions,
Format::R8,
1,
settings,
)?;
let resources = &RESOURCES;
let vulkan = resources.vulkan().clone();
let text_shaders = Shaders::from_modules(
vertex_shader(vulkan.device.clone())?,
text_fragment_shader(vulkan.device.clone())?,
"main",
);
let material_settings = MaterialSettingsBuilder::default()
.texture(texture)
.build()?;
let material = Material::new_with_shaders(material_settings, &text_shaders, false, vec![])?;
Ok(Self {
material,
glyph_brush,
cache_pixel_buffer,
queued: vec![],
ready: false,
tasks: 0,
})
}
fn increment_tasks(&mut self) -> usize {
let tasks = self.tasks;
self.tasks += 1;
tasks
}
fn update_each_object(&mut self, brush_action: BrushAction<TextVertex>) -> Result<()> {
let BrushAction::Draw(text_vertices) = brush_action else {
return Ok(());
};
for text_vertex in text_vertices {
let task = &mut self.queued[text_vertex.extra.id];
task.indices
.append(&mut text_vertex.indices(task.vertices.len() as u32));
task.vertices.extend_from_slice(&text_vertex.rect);
}
let settings = TextureSettings {
srgb: false,
sampler: Sampler::default(),
};
self.material.texture = Some(Texture::from_raw(
self.cache_pixel_buffer.as_raw(),
self.glyph_brush.texture_dimensions(),
Format::R8,
1,
settings,
)?);
let queued = std::mem::take(&mut self.queued);
for task in queued.into_iter() {
let mut label = task.label.clone();
let visible = if task.vertices.is_empty() {
false
} else {
let model = ModelData::new(task.into_data())?;
label.object.appearance.set_model(Model::Custom(model));
true
};
label
.object
.appearance
.set_material(Some(self.material.clone()));
label.object.appearance.set_visible(visible);
let node = label.object.as_node();
let mut object = node.lock();
object.object = label.object.clone();
}
Ok(())
}
pub fn update(&mut self) -> Result<()> {
if !self.ready {
return Ok(());
}
let brush_action: glyph_brush::BrushAction<TextVertex> = loop {
let result = self.glyph_brush.process_queued(
|rect, src_data| {
let width = (rect.max[0] - rect.min[0]) as usize;
let height = (rect.max[1] - rect.min[1]) as usize;
for y in 0..height {
for x in 0..width {
let src_index = y * width + x;
let pixel = Luma([src_data[src_index]]);
self.cache_pixel_buffer.put_pixel(
rect.min[0] + x as u32,
rect.min[1] + y as u32,
pixel,
)
}
}
},
to_vertex,
);
match result {
Ok(brush_action) => {
break brush_action;
}
Err(BrushError::TextureTooSmall { suggested }) => {
dbg!(suggested);
self.glyph_brush.resize_texture(suggested.0, suggested.1);
let mut new_buffer =
ImageBuffer::from_pixel(suggested.0, suggested.1, Luma([0u8]));
for y in 0..self.cache_pixel_buffer.height() {
for x in 0..self.cache_pixel_buffer.width() {
let pixel = self.cache_pixel_buffer.get_pixel(x, y);
new_buffer.put_pixel(x, y, *pixel);
}
}
self.cache_pixel_buffer = new_buffer;
}
}
};
Self::update_each_object(self, brush_action)?;
self.tasks = 0;
self.queued = vec![];
self.ready = false;
Ok(())
}
pub fn queue(&mut self, label: Label<Object>) {
self.ready = true;
self.glyph_brush.queue(label.section.to_borrowed());
self.queued.push(DrawTask {
label,
vertices: vec![],
indices: vec![],
});
}
}
fn to_vertex(
glyph_brush::GlyphVertex {
tex_coords,
pixel_coords,
bounds,
extra,
}: glyph_brush::GlyphVertex<Extra>,
) -> TextVertex {
let rect = glyph_brush::Rectangle {
min: [
(pixel_coords.min.x / bounds.width() - 0.5) * 2.0,
(pixel_coords.min.y / bounds.height() - 0.5) * 2.0,
],
max: [
(pixel_coords.max.x / bounds.width() - 0.5) * 2.0,
(pixel_coords.max.y / bounds.height() - 0.5) * 2.0,
],
};
TextVertex {
rect: [
tvert(rect.min[0], rect.min[1], tex_coords.min.x, tex_coords.min.y),
tvert(rect.min[0], rect.max[1], tex_coords.min.x, tex_coords.max.y),
tvert(rect.max[0], rect.min[1], tex_coords.max.x, tex_coords.min.y),
tvert(rect.max[0], rect.max[1], tex_coords.max.x, tex_coords.max.y),
],
extra: *extra,
}
}
struct DrawTask {
pub label: Label<Object>,
pub vertices: Vec<Vertex>,
pub indices: Vec<u32>,
}
impl DrawTask {
pub fn into_data(self) -> Data {
Data::Dynamic {
vertices: self.vertices,
indices: self.indices,
}
}
}
#[derive(Clone)]
pub struct Font {
id: FontId,
}
impl Font {
pub fn from_vec(data: impl Into<Vec<u8>>) -> Result<Self> {
let labelifier = &LABELIFIER;
let font = FontArc::try_from_vec(data.into())?;
let id = labelifier.lock().glyph_brush.add_font(font);
Ok(Self { id })
}
pub fn from_slice(data: &'static [u8]) -> Result<Self> {
let labelifier = &LABELIFIER;
let font = FontArc::try_from_slice(data)?;
let id = labelifier.lock().glyph_brush.add_font(font);
Ok(Self { id })
}
pub fn id(&self) -> FontId {
self.id
}
}