163 lines
6.2 KiB
Rust
163 lines
6.2 KiB
Rust
use crate::{Font, FontAtlasSet};
|
|
use ab_glyph::{Glyph, PxScale, ScaleFont};
|
|
use bevy_asset::Assets;
|
|
use bevy_math::{Mat4, Vec3};
|
|
use bevy_render::{
|
|
draw::{Draw, DrawContext, DrawError, Drawable},
|
|
mesh,
|
|
pipeline::PipelineSpecialization,
|
|
render_resource::{
|
|
AssetRenderResourceBindings, BindGroup, BufferUsage, RenderResourceBindings,
|
|
RenderResourceId,
|
|
},
|
|
Color,
|
|
};
|
|
use bevy_sprite::{TextureAtlas, TextureAtlasSprite};
|
|
|
|
pub struct TextStyle {
|
|
pub font_size: f32,
|
|
pub color: Color,
|
|
}
|
|
|
|
pub struct DrawableText<'a> {
|
|
font: &'a Font,
|
|
font_atlas_set: &'a FontAtlasSet,
|
|
texture_atlases: &'a Assets<TextureAtlas>,
|
|
render_resource_bindings: &'a mut RenderResourceBindings,
|
|
asset_render_resource_bindings: &'a mut AssetRenderResourceBindings,
|
|
position: Vec3,
|
|
style: &'a TextStyle,
|
|
text: &'a str,
|
|
}
|
|
|
|
impl<'a> DrawableText<'a> {
|
|
pub fn new(
|
|
font: &'a Font,
|
|
font_atlas_set: &'a FontAtlasSet,
|
|
texture_atlases: &'a Assets<TextureAtlas>,
|
|
render_resource_bindings: &'a mut RenderResourceBindings,
|
|
asset_render_resource_bindings: &'a mut AssetRenderResourceBindings,
|
|
position: Vec3,
|
|
style: &'a TextStyle,
|
|
text: &'a str,
|
|
) -> Self {
|
|
Self {
|
|
font,
|
|
font_atlas_set,
|
|
texture_atlases,
|
|
render_resource_bindings,
|
|
asset_render_resource_bindings,
|
|
position,
|
|
style,
|
|
text,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> Drawable for DrawableText<'a> {
|
|
fn draw(&mut self, draw: &mut Draw, context: &mut DrawContext) -> Result<(), DrawError> {
|
|
context.set_pipeline(
|
|
draw,
|
|
bevy_sprite::SPRITE_SHEET_PIPELINE_HANDLE,
|
|
// TODO: remove this shader def specialization when its easier to manually bind global render resources to specific bind groups
|
|
&PipelineSpecialization::default(),
|
|
)?;
|
|
|
|
let render_resource_context = &**context.render_resource_context;
|
|
if let Some(RenderResourceId::Buffer(quad_vertex_buffer)) = render_resource_context
|
|
.get_asset_resource(bevy_sprite::QUAD_HANDLE, mesh::VERTEX_BUFFER_ASSET_INDEX)
|
|
{
|
|
draw.set_vertex_buffer(0, quad_vertex_buffer, 0);
|
|
}
|
|
let mut indices = 0..0;
|
|
if let Some(RenderResourceId::Buffer(quad_index_buffer)) = render_resource_context
|
|
.get_asset_resource(bevy_sprite::QUAD_HANDLE, mesh::INDEX_BUFFER_ASSET_INDEX)
|
|
{
|
|
draw.set_index_buffer(quad_index_buffer, 0);
|
|
if let Some(buffer_info) = render_resource_context.get_buffer_info(quad_index_buffer) {
|
|
indices = 0..(buffer_info.size / 2) as u32;
|
|
} else {
|
|
panic!("expected buffer type");
|
|
}
|
|
}
|
|
|
|
// set global bindings
|
|
context.set_bind_groups_from_bindings(draw, &mut [self.render_resource_bindings])?;
|
|
|
|
// NOTE: this uses ab_glyph apis directly. it _might_ be a good idea to add our own layer on top
|
|
let font = &self.font.font;
|
|
let scale = PxScale::from(self.style.font_size);
|
|
let scaled_font = ab_glyph::Font::as_scaled(&font, scale);
|
|
let mut caret = self.position;
|
|
let mut last_glyph: Option<Glyph> = None;
|
|
|
|
// set local per-character bindings
|
|
for character in self.text.chars() {
|
|
if character.is_control() {
|
|
continue;
|
|
}
|
|
|
|
let glyph = scaled_font.scaled_glyph(character);
|
|
if let Some(last_glyph) = last_glyph.take() {
|
|
caret.set_x(caret.x() + scaled_font.kern(last_glyph.id, glyph.id));
|
|
}
|
|
if let Some(glyph_atlas_info) = self
|
|
.font_atlas_set
|
|
.get_glyph_atlas_info(self.style.font_size, character)
|
|
{
|
|
if let Some(outlined) = scaled_font.outline_glyph(glyph.clone()) {
|
|
let texture_atlas = self
|
|
.texture_atlases
|
|
.get(&glyph_atlas_info.texture_atlas)
|
|
.unwrap();
|
|
let glyph_rect = texture_atlas.textures[glyph_atlas_info.char_index as usize];
|
|
let glyph_width = glyph_rect.width();
|
|
let glyph_height = glyph_rect.height();
|
|
let atlas_render_resource_bindings = self
|
|
.asset_render_resource_bindings
|
|
.get_mut(glyph_atlas_info.texture_atlas)
|
|
.unwrap();
|
|
context.set_bind_groups_from_bindings(
|
|
draw,
|
|
&mut [atlas_render_resource_bindings],
|
|
)?;
|
|
|
|
let bounds = outlined.px_bounds();
|
|
let offset = scaled_font.descent() + glyph_height;
|
|
let transform = Mat4::from_translation(
|
|
caret
|
|
+ Vec3::new(
|
|
0.0 + glyph_width / 2.0 + bounds.min.x,
|
|
glyph_height / 2.0 - bounds.min.y - offset,
|
|
0.0,
|
|
),
|
|
);
|
|
let sprite = TextureAtlasSprite {
|
|
index: glyph_atlas_info.char_index,
|
|
color: self.style.color,
|
|
};
|
|
|
|
let transform_buffer = context
|
|
.shared_buffers
|
|
.get_buffer(&transform, BufferUsage::UNIFORM)
|
|
.unwrap();
|
|
let sprite_buffer = context
|
|
.shared_buffers
|
|
.get_buffer(&sprite, BufferUsage::UNIFORM)
|
|
.unwrap();
|
|
let sprite_bind_group = BindGroup::build()
|
|
.add_binding(0, transform_buffer)
|
|
.add_binding(1, sprite_buffer)
|
|
.finish();
|
|
context.create_bind_group_resource(2, &sprite_bind_group)?;
|
|
draw.set_bind_group(2, &sprite_bind_group);
|
|
draw.draw_indexed(indices.clone(), 0, 0..1);
|
|
}
|
|
}
|
|
caret.set_x(caret.x() + scaled_font.h_advance(glyph.id));
|
|
last_glyph = Some(glyph);
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|