use std::sync::{Arc, Mutex, MutexGuard};
use cotis_defaults::element_configs::text_config::TextConfig;
use cotis_utils::math::Dimensions;
use glyphon::{
Attrs, Buffer, Cache, Color, Family, FontSystem, Metrics, Resolution, Shaping, SwashCache,
TextArea, TextAtlas, TextBounds, TextRenderer, Viewport,
};
use wgpu::MultisampleState;
use crate::pipeline::UIPosition;
struct QueuedTextLine {
line: Buffer,
left: f32,
top: f32,
color: Color,
bounds: Option<(UIPosition, UIPosition)>,
}
pub struct TextState {
font_system: Arc<Mutex<FontSystem>>,
swash_cache: SwashCache,
viewport: Viewport,
atlas: TextAtlas,
text_renderer: TextRenderer,
measurement_buffer: Buffer,
lines: Vec<QueuedTextLine>,
dpi_scale: f32,
}
impl TextState {
pub fn new(
device: &wgpu::Device,
queue: &wgpu::Queue,
pixel_format: wgpu::TextureFormat,
size: (u32, u32),
dpi_scale: f32,
) -> Self {
let font_system = Arc::new(Mutex::new(FontSystem::new()));
let swash_cache = SwashCache::new();
let cache = Cache::new(device);
let viewport = Viewport::new(device, &cache);
let mut atlas = TextAtlas::new(device, queue, &cache, pixel_format);
let text_renderer = TextRenderer::new(
&mut atlas,
device,
MultisampleState::default(),
Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth32Float,
depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Less,
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
);
let measurement_buffer = {
let mut locked = lock_font_system(&font_system);
Buffer::new(&mut locked, Metrics::new(30.0, 42.0))
};
let _ = size;
Self {
font_system,
swash_cache,
viewport,
atlas,
text_renderer,
measurement_buffer,
lines: Vec::new(),
dpi_scale,
}
}
pub fn set_dpi_scale(&mut self, dpi_scale: f32) {
self.dpi_scale = dpi_scale;
}
pub fn font_system(&self) -> Arc<Mutex<FontSystem>> {
self.font_system.clone()
}
#[allow(clippy::too_many_arguments)]
pub fn queue_text(
&mut self,
text: &str,
font_size: f32,
line_height: f32,
position: UIPosition,
bounds: Option<(UIPosition, UIPosition)>,
color: Color,
draw_order: f32,
) {
let mut font_system = lock_font_system(&self.font_system);
let mut line = Buffer::new(&mut font_system, Metrics::new(font_size, line_height));
line.set_text(
&mut font_system,
text,
Attrs::new()
.family(Family::SansSerif)
.metadata((draw_order * 10_000.0) as usize),
Shaping::Advanced,
);
line.shape_until_scroll(&mut font_system, false);
self.lines.push(QueuedTextLine {
line,
left: position.x,
top: position.y,
color,
bounds,
});
}
pub fn flush(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
render_pass: &mut wgpu::RenderPass<'_>,
surface_config: &wgpu::SurfaceConfiguration,
) {
if self.lines.is_empty() {
return;
}
self.atlas.trim();
self.viewport.update(
queue,
Resolution {
width: surface_config.width,
height: surface_config.height,
},
);
let areas = self.lines.iter().map(|text_line| TextArea {
buffer: &text_line.line,
left: text_line.left,
top: text_line.top,
scale: 1.0,
bounds: match text_line.bounds {
Some((position, bounds)) => TextBounds {
left: position.x as i32,
top: position.y as i32,
right: (position.x + bounds.x) as i32,
bottom: (position.y + bounds.y) as i32,
},
None => TextBounds {
left: 0,
top: 0,
right: surface_config.width as i32,
bottom: surface_config.height as i32,
},
},
default_color: text_line.color,
custom_glyphs: &[],
});
let mut font_system = lock_font_system(&self.font_system);
if let Err(error) = self.text_renderer.prepare_with_depth(
device,
queue,
&mut font_system,
&mut self.atlas,
&self.viewport,
areas,
&mut self.swash_cache,
|metadata| metadata as f32 / 10_000.0,
) {
log::warn!("glyphon prepare failed: {error}");
self.lines.clear();
return;
}
if let Err(error) = self
.text_renderer
.render(&self.atlas, &self.viewport, render_pass)
{
log::warn!("glyphon render failed: {error}");
}
self.lines.clear();
}
pub fn measure(&mut self, text: &str, config: &TextConfig) -> Dimensions {
let font_size = config.font_size * self.dpi_scale;
let line_height = if config.line_height > 0.0 {
config.line_height * self.dpi_scale
} else {
font_size * 1.5
};
let mut font_system = lock_font_system(&self.font_system);
self.measurement_buffer.set_metrics_and_size(
&mut font_system,
Metrics::new(font_size, line_height),
None,
None,
);
self.measurement_buffer.set_text(
&mut font_system,
text,
Attrs::new().family(Family::SansSerif),
Shaping::Advanced,
);
self.measurement_buffer
.shape_until_scroll(&mut font_system, false);
let line_width = self
.measurement_buffer
.layout_runs()
.next()
.map(|run| run.line_w)
.unwrap_or(0.0);
Dimensions::new(line_width, self.measurement_buffer.metrics().line_height)
}
}
fn lock_font_system(font_system: &Arc<Mutex<FontSystem>>) -> MutexGuard<'_, FontSystem> {
match font_system.lock() {
Ok(guard) => guard,
Err(error) => {
log::error!("font system mutex poisoned: {error}");
error.into_inner()
}
}
}