ui: feed computed text position into bevy_ui flex

and remove TextAlign because it is now redundant
This commit is contained in:
Carter Anderson 2020-07-27 21:04:04 -07:00
parent 1f006c348d
commit cf9501a50e
14 changed files with 104 additions and 104 deletions

View File

@ -2,7 +2,7 @@ use std::ops::{Add, AddAssign};
use glam::Vec2; use glam::Vec2;
#[derive(Copy, Clone, PartialEq, Debug)] #[derive(Copy, Clone, PartialEq, Debug)]
pub struct Size<T> { pub struct Size<T=f32> {
pub width: T, pub width: T,
pub height: T, pub height: T,
} }

View File

@ -1,5 +1,5 @@
use crate::{Font, FontAtlasSet}; use crate::{Font, FontAtlasSet};
use ab_glyph::{FontVec, Glyph, PxScale, PxScaleFont, ScaleFont}; use ab_glyph::{Glyph, PxScale, ScaleFont};
use bevy_asset::Assets; use bevy_asset::Assets;
use bevy_math::{Mat4, Vec2, Vec3}; use bevy_math::{Mat4, Vec2, Vec3};
use bevy_render::{ use bevy_render::{
@ -17,7 +17,6 @@ use bevy_sprite::{TextureAtlas, TextureAtlasSprite};
pub struct TextStyle { pub struct TextStyle {
pub font_size: f32, pub font_size: f32,
pub color: Color, pub color: Color,
pub align: TextAlign,
} }
impl Default for TextStyle { impl Default for TextStyle {
@ -25,23 +24,10 @@ impl Default for TextStyle {
Self { Self {
color: Color::WHITE, color: Color::WHITE,
font_size: 12.0, font_size: 12.0,
align: TextAlign::default(),
} }
} }
} }
pub enum TextAlign {
Left,
Center,
Right,
}
impl Default for TextAlign {
fn default() -> Self {
TextAlign::Left
}
}
pub struct DrawableText<'a> { pub struct DrawableText<'a> {
pub font: &'a Font, pub font: &'a Font,
pub font_atlas_set: &'a FontAtlasSet, pub font_atlas_set: &'a FontAtlasSet,
@ -54,26 +40,6 @@ pub struct DrawableText<'a> {
pub text: &'a str, pub text: &'a str,
} }
fn get_text_width(text: &str, scaled_font: &PxScaleFont<&&FontVec>) -> f32 {
let mut last_glyph: Option<Glyph> = None;
let mut position = 0.0;
for character in text.chars() {
if character.is_control() {
continue;
}
let glyph = scaled_font.scaled_glyph(character);
if let Some(last_glyph) = last_glyph.take() {
position += scaled_font.kern(last_glyph.id, glyph.id);
}
position += scaled_font.h_advance(glyph.id);
last_glyph = Some(glyph);
}
position
}
impl<'a> Drawable for DrawableText<'a> { impl<'a> Drawable for DrawableText<'a> {
fn draw(&mut self, draw: &mut Draw, context: &mut DrawContext) -> Result<(), DrawError> { fn draw(&mut self, draw: &mut Draw, context: &mut DrawContext) -> Result<(), DrawError> {
context.set_pipeline( context.set_pipeline(
@ -109,17 +75,6 @@ impl<'a> Drawable for DrawableText<'a> {
let scale = PxScale::from(self.style.font_size); let scale = PxScale::from(self.style.font_size);
let scaled_font = ab_glyph::Font::as_scaled(&font, scale); let scaled_font = ab_glyph::Font::as_scaled(&font, scale);
let mut caret = self.position; let mut caret = self.position;
match self.style.align {
TextAlign::Left => { /* already aligned left by default */ }
TextAlign::Center => {
*caret.x_mut() +=
self.container_size.x() / 2.0 - get_text_width(&self.text, &scaled_font) / 2.0
}
TextAlign::Right => {
*caret.x_mut() += self.container_size.x() - get_text_width(&self.text, &scaled_font)
}
}
let mut last_glyph: Option<Glyph> = None; let mut last_glyph: Option<Glyph> = None;
// set local per-character bindings // set local per-character bindings

View File

@ -1,5 +1,5 @@
use crate::{Font, FontAtlas}; use crate::{Font, FontAtlas};
use ab_glyph::ScaleFont; use ab_glyph::{Glyph, ScaleFont};
use bevy_asset::{Assets, Handle}; use bevy_asset::{Assets, Handle};
use bevy_core::FloatOrd; use bevy_core::FloatOrd;
use bevy_math::Vec2; use bevy_math::Vec2;
@ -49,24 +49,35 @@ impl FontAtlasSet {
textures: &mut Assets<Texture>, textures: &mut Assets<Texture>,
font_size: f32, font_size: f32,
text: &str, text: &str,
) { ) -> f32 {
let font = fonts.get(&self.font).unwrap(); let font = fonts.get(&self.font).unwrap();
let scaled_font = ab_glyph::Font::as_scaled(&font.font, font_size); let scaled_font = ab_glyph::Font::as_scaled(&font.font, font_size);
let font_atlas = self let font_atlas = self
.font_atlases .font_atlases
.entry(FloatOrd(font_size)) .entry(FloatOrd(font_size))
.or_insert_with(|| FontAtlas::new(textures, texture_atlases, Vec2::new(512.0, 512.0))); .or_insert_with(|| FontAtlas::new(textures, texture_atlases, Vec2::new(512.0, 512.0)));
let mut last_glyph: Option<Glyph> = None;
let mut width = 0.0;
for character in text.chars() { for character in text.chars() {
if character.is_control() || font_atlas.get_char_index(character).is_some() { if character.is_control() {
continue; continue;
} }
let glyph = scaled_font.scaled_glyph(character); let glyph = scaled_font.scaled_glyph(character);
if let Some(outlined_glyph) = scaled_font.outline_glyph(glyph) { if let Some(last_glyph) = last_glyph.take() {
let glyph_texture = Font::get_outlined_glyph_texture(outlined_glyph); width += scaled_font.kern(last_glyph.id, glyph.id);
font_atlas.add_char(textures, texture_atlases, character, &glyph_texture);
} }
if font_atlas.get_char_index(character).is_none() {
if let Some(outlined_glyph) = scaled_font.outline_glyph(glyph.clone()) {
let glyph_texture = Font::get_outlined_glyph_texture(outlined_glyph);
font_atlas.add_char(textures, texture_atlases, character, &glyph_texture);
}
}
width += scaled_font.h_advance(glyph.id);
last_glyph = Some(glyph);
} }
width
} }
pub fn get_glyph_atlas_info(&self, font_size: f32, character: char) -> Option<GlyphAtlasInfo> { pub fn get_glyph_atlas_info(&self, font_size: f32, character: char) -> Option<GlyphAtlasInfo> {

View File

@ -11,7 +11,7 @@ pub use font_atlas_set::*;
pub use font_loader::*; pub use font_loader::*;
pub mod prelude { pub mod prelude {
pub use crate::{Font, TextAlign, TextStyle}; pub use crate::{Font, TextStyle};
} }
use bevy_app::prelude::*; use bevy_app::prelude::*;

View File

@ -2,7 +2,7 @@ use super::Node;
use crate::{ use crate::{
render::UI_PIPELINE_HANDLE, render::UI_PIPELINE_HANDLE,
widget::{Button, Text}, widget::{Button, Text},
Click, FocusPolicy, Hover, Style, Click, FocusPolicy, Hover, Style, CalculatedSize,
}; };
use bevy_asset::Handle; use bevy_asset::Handle;
use bevy_ecs::Bundle; use bevy_ecs::Bundle;
@ -68,6 +68,7 @@ pub struct TextComponents {
pub style: Style, pub style: Style,
pub draw: Draw, pub draw: Draw,
pub text: Text, pub text: Text,
pub calculated_size: CalculatedSize,
pub focus_policy: FocusPolicy, pub focus_policy: FocusPolicy,
pub transform: Transform, pub transform: Transform,
pub local_transform: LocalTransform, pub local_transform: LocalTransform,
@ -83,6 +84,7 @@ impl Default for TextComponents {
}, },
text: Default::default(), text: Default::default(),
node: Default::default(), node: Default::default(),
calculated_size: Default::default(),
style: Default::default(), style: Default::default(),
transform: Default::default(), transform: Default::default(),
local_transform: Default::default(), local_transform: Default::default(),

View File

@ -1,6 +1,6 @@
mod convert; mod convert;
use crate::{Style, Node}; use crate::{CalculatedSize, Node, Style};
use bevy_ecs::{Changed, Entity, Query, Res, ResMut, With, Without}; use bevy_ecs::{Changed, Entity, Query, Res, ResMut, With, Without};
use bevy_math::Vec2; use bevy_math::Vec2;
use bevy_transform::prelude::{Children, LocalTransform, Parent}; use bevy_transform::prelude::{Children, LocalTransform, Parent};
@ -10,7 +10,6 @@ use stretch::Stretch;
pub struct FlexSurface { pub struct FlexSurface {
entity_to_stretch: HashMap<Entity, stretch::node::Node>, entity_to_stretch: HashMap<Entity, stretch::node::Node>,
stretch_to_entity: HashMap<stretch::node::Node, Entity>,
window_nodes: HashMap<WindowId, stretch::node::Node>, window_nodes: HashMap<WindowId, stretch::node::Node>,
stretch: Stretch, stretch: Stretch,
} }
@ -19,7 +18,6 @@ impl Default for FlexSurface {
fn default() -> Self { fn default() -> Self {
Self { Self {
entity_to_stretch: Default::default(), entity_to_stretch: Default::default(),
stretch_to_entity: Default::default(),
window_nodes: Default::default(), window_nodes: Default::default(),
stretch: Stretch::new(), stretch: Stretch::new(),
} }
@ -30,12 +28,10 @@ impl FlexSurface {
pub fn upsert_node(&mut self, entity: Entity, style: &Style) { pub fn upsert_node(&mut self, entity: Entity, style: &Style) {
let mut added = false; let mut added = false;
let stretch = &mut self.stretch; let stretch = &mut self.stretch;
let stretch_to_entity = &mut self.stretch_to_entity;
let stretch_style = style.into(); let stretch_style = style.into();
let stretch_node = self.entity_to_stretch.entry(entity).or_insert_with(|| { let stretch_node = self.entity_to_stretch.entry(entity).or_insert_with(|| {
added = true; added = true;
let stretch_node = stretch.new_node(stretch_style, Vec::new()).unwrap(); let stretch_node = stretch.new_node(stretch_style, Vec::new()).unwrap();
stretch_to_entity.insert(stretch_node, entity);
stretch_node stretch_node
}); });
@ -46,6 +42,44 @@ impl FlexSurface {
} }
} }
pub fn upsert_leaf(&mut self, entity: Entity, style: &Style, calculated_size: CalculatedSize) {
let mut added = false;
let stretch = &mut self.stretch;
let stretch_style = style.into();
let stretch_node = self.entity_to_stretch.entry(entity).or_insert_with(|| {
added = true;
let stretch_node = stretch
.new_leaf(
stretch_style,
Box::new(move |_| {
Ok(stretch::geometry::Size {
width: calculated_size.size.width,
height: calculated_size.size.height,
})
}),
)
.unwrap();
stretch_node
});
if !added {
self.stretch
.set_style(*stretch_node, stretch_style)
.unwrap();
self.stretch
.set_measure(
*stretch_node,
Some(Box::new(move |_| {
Ok(stretch::geometry::Size {
width: calculated_size.size.width,
height: calculated_size.size.height,
})
})),
)
.unwrap();
}
}
pub fn update_children(&mut self, entity: Entity, children: &Children) { pub fn update_children(&mut self, entity: Entity, children: &Children) {
let mut stretch_children = Vec::with_capacity(children.len()); let mut stretch_children = Vec::with_capacity(children.len());
for child in children.iter() { for child in children.iter() {
@ -63,10 +97,7 @@ impl FlexSurface {
let stretch = &mut self.stretch; let stretch = &mut self.stretch;
let node = self.window_nodes.entry(window.id).or_insert_with(|| { let node = self.window_nodes.entry(window.id).or_insert_with(|| {
stretch stretch
.new_node( .new_node(stretch::style::Style::default(), Vec::new())
stretch::style::Style::default(),
Vec::new(),
)
.unwrap() .unwrap()
}); });
@ -120,7 +151,8 @@ pub fn flex_node_system(
windows: Res<Windows>, windows: Res<Windows>,
mut flex_surface: ResMut<FlexSurface>, mut flex_surface: ResMut<FlexSurface>,
mut root_node_query: Query<With<Node, Without<Parent, Entity>>>, mut root_node_query: Query<With<Node, Without<Parent, Entity>>>,
mut node_query: Query<With<Node, (Entity, Changed<Style>)>>, mut node_query: Query<With<Node, (Entity, Changed<Style>, Option<&CalculatedSize>)>>,
mut changed_size_query: Query<With<Node, (Entity, &Style, Changed<CalculatedSize>)>>,
mut children_query: Query<With<Node, (Entity, Changed<Children>)>>, mut children_query: Query<With<Node, (Entity, Changed<Children>)>>,
mut node_transform_query: Query<(Entity, &mut Node, &mut LocalTransform, Option<&Parent>)>, mut node_transform_query: Query<(Entity, &mut Node, &mut LocalTransform, Option<&Parent>)>,
) { ) {
@ -130,9 +162,17 @@ pub fn flex_node_system(
} }
// update changed nodes // update changed nodes
for (entity, style) in &mut node_query.iter() { for (entity, style, calculated_size) in &mut node_query.iter() {
// TODO: remove node from old hierarchy if its root has changed // TODO: remove node from old hierarchy if its root has changed
flex_surface.upsert_node(entity, &style); if let Some(calculated_size) = calculated_size {
flex_surface.upsert_leaf(entity, &style, *calculated_size);
} else {
flex_surface.upsert_node(entity, &style);
}
}
for (entity, style, calculated_size) in &mut changed_size_query.iter() {
flex_surface.upsert_leaf(entity, &style, *calculated_size);
} }
// TODO: handle removed nodes // TODO: handle removed nodes

View File

@ -39,7 +39,7 @@ impl AppPlugin for UiPlugin {
// add these stages to front because these must run before transform update systems // add these stages to front because these must run before transform update systems
.add_system_to_stage_front(stage::POST_UPDATE, flex_node_system.system()) .add_system_to_stage_front(stage::POST_UPDATE, flex_node_system.system())
.add_system_to_stage_front(stage::POST_UPDATE, ui_z_system.system()) .add_system_to_stage_front(stage::POST_UPDATE, ui_z_system.system())
.add_system_to_stage(stage::POST_UPDATE, widget::text_system.system()) .add_system_to_stage_front(stage::POST_UPDATE, widget::text_system.system())
.add_system_to_stage(bevy_render::stage::DRAW, widget::draw_text_system.system()); .add_system_to_stage(bevy_render::stage::DRAW, widget::draw_text_system.system());
let resources = app.resources(); let resources = app.resources();

View File

@ -41,7 +41,11 @@ impl AddAssign<f32> for Val {
Val::Percent(value) => *value += rhs, Val::Percent(value) => *value += rhs,
} }
} }
}
#[derive(Default, Copy, Clone)]
pub struct CalculatedSize {
pub size: Size,
} }
#[derive(Clone, PartialEq, Debug)] #[derive(Clone, PartialEq, Debug)]

View File

@ -1,6 +1,7 @@
use crate::Node; use crate::{CalculatedSize, Node};
use bevy_asset::{Assets, Handle}; use bevy_asset::{Assets, Handle};
use bevy_ecs::{Query, Res, ResMut}; use bevy_ecs::{Changed, Query, Res, ResMut};
use bevy_math::Size;
use bevy_render::{ use bevy_render::{
draw::{Draw, DrawContext, Drawable}, draw::{Draw, DrawContext, Drawable},
renderer::{AssetRenderResourceBindings, RenderResourceBindings}, renderer::{AssetRenderResourceBindings, RenderResourceBindings},
@ -22,9 +23,9 @@ pub fn text_system(
fonts: Res<Assets<Font>>, fonts: Res<Assets<Font>>,
mut font_atlas_sets: ResMut<Assets<FontAtlasSet>>, mut font_atlas_sets: ResMut<Assets<FontAtlasSet>>,
mut texture_atlases: ResMut<Assets<TextureAtlas>>, mut texture_atlases: ResMut<Assets<TextureAtlas>>,
mut query: Query<&Text>, mut query: Query<(Changed<Text>, &mut CalculatedSize)>,
) { ) {
for text in &mut query.iter() { for (text, mut calculated_size) in &mut query.iter() {
let font_atlases = font_atlas_sets let font_atlases = font_atlas_sets
.get_or_insert_with(Handle::from_id(text.font.id), || { .get_or_insert_with(Handle::from_id(text.font.id), || {
FontAtlasSet::new(text.font) FontAtlasSet::new(text.font)
@ -35,13 +36,15 @@ pub fn text_system(
// resource generation needs to happen AFTER the render graph systems. maybe draw systems should execute within the // resource generation needs to happen AFTER the render graph systems. maybe draw systems should execute within the
// render graph so ordering like this can be taken into account? Maybe the RENDER_GRAPH_SYSTEMS stage should be removed entirely // render graph so ordering like this can be taken into account? Maybe the RENDER_GRAPH_SYSTEMS stage should be removed entirely
// in favor of node.update()? Regardless, in the immediate short term the current approach is fine. // in favor of node.update()? Regardless, in the immediate short term the current approach is fine.
font_atlases.add_glyphs_to_atlas( let width = font_atlases.add_glyphs_to_atlas(
&fonts, &fonts,
&mut texture_atlases, &mut texture_atlases,
&mut textures, &mut textures,
text.style.font_size, text.style.font_size,
&text.value, &text.value,
); );
calculated_size.size = Size::new(width, text.style.font_size);
} }
} }

View File

@ -71,7 +71,6 @@ fn setup(
style: TextStyle { style: TextStyle {
color: Color::rgb(0.2, 0.2, 0.8).into(), color: Color::rgb(0.2, 0.2, 0.8).into(),
font_size: 40.0, font_size: 40.0,
..Default::default()
}, },
}, },
style: Style { style: Style {

View File

@ -95,13 +95,13 @@ fn setup(
.spawn(UiCameraComponents::default()) .spawn(UiCameraComponents::default())
.spawn(ButtonComponents { .spawn(ButtonComponents {
style: Style { style: Style {
size: Size::new(Val::Px(150.0), Val::Px(70.0)), size: Size::new(Val::Px(150.0), Val::Px(65.0)),
align_self: AlignSelf::Center, // center button
margin: Rect { margin: Rect::all(Val::Auto),
left: Val::Auto, // horizontally center child text
right: Val::Auto, justify_content: JustifyContent::Center,
..Default::default() // vertically center child text
}, align_items: AlignItems::Center,
..Default::default() ..Default::default()
}, },
material: button_materials.normal, material: button_materials.normal,
@ -109,21 +109,12 @@ fn setup(
}) })
.with_children(|parent| { .with_children(|parent| {
parent.spawn(TextComponents { parent.spawn(TextComponents {
style: Style {
size: Size::new(Val::Percent(100.0), Val::Percent(100.0)),
margin: Rect {
bottom: Val::Px(10.0),
..Default::default()
},
..Default::default()
},
text: Text { text: Text {
value: "Button".to_string(), value: "Button".to_string(),
font: asset_server.load("assets/fonts/FiraSans-Bold.ttf").unwrap(), font: asset_server.load("assets/fonts/FiraSans-Bold.ttf").unwrap(),
style: TextStyle { style: TextStyle {
font_size: 40.0, font_size: 40.0,
color: Color::rgb(0.8, 0.8, 0.8), color: Color::rgb(0.8, 0.8, 0.8),
align: TextAlign::Center,
}, },
}, },
..Default::default() ..Default::default()

View File

@ -81,7 +81,6 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, mut state: ResM
style: TextStyle { style: TextStyle {
font_size: 60.0, font_size: 60.0,
color: Color::WHITE, color: Color::WHITE,
..Default::default()
}, },
}, },
..Default::default() ..Default::default()

View File

@ -26,17 +26,19 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
let font_handle = asset_server.load("assets/fonts/FiraSans-Bold.ttf").unwrap(); let font_handle = asset_server.load("assets/fonts/FiraSans-Bold.ttf").unwrap();
commands commands
// 2d camera // 2d camera
.spawn(Camera2dComponents::default()) .spawn(UiCameraComponents::default())
// texture // texture
.spawn(TextComponents { .spawn(TextComponents {
node: Node::default(), style: Style {
align_self: AlignSelf::FlexEnd,
..Default::default()
},
text: Text { text: Text {
value: "FPS:".to_string(), value: "FPS:".to_string(),
font: font_handle, font: font_handle,
style: TextStyle { style: TextStyle {
font_size: 60.0, font_size: 60.0,
color: Color::WHITE, color: Color::WHITE,
align: TextAlign::Left,
}, },
}, },
..Default::default() ..Default::default()

View File

@ -61,12 +61,7 @@ fn setup(
// text // text
parent.spawn(TextComponents { parent.spawn(TextComponents {
style: Style { style: Style {
size: Size::new(Val::Px(100.0), Val::Px(30.0)), margin: Rect::all(Val::Px(5.0)),
margin: Rect {
left: Val::Px(5.0),
top: Val::Px(5.0),
..Default::default()
},
..Default::default() ..Default::default()
}, },
text: Text { text: Text {
@ -77,7 +72,6 @@ fn setup(
style: TextStyle { style: TextStyle {
font_size: 30.0, font_size: 30.0,
color: Color::WHITE, color: Color::WHITE,
..Default::default()
}, },
}, },
..Default::default() ..Default::default()
@ -103,7 +97,7 @@ fn setup(
bottom: Val::Px(10.0), bottom: Val::Px(10.0),
..Default::default() ..Default::default()
}, },
border: Rect::all(Val::Px(10.0)), border: Rect::all(Val::Px(20.0)),
..Default::default() ..Default::default()
}, },
material: materials.add(Color::rgb(0.1, 0.1, 1.0).into()), material: materials.add(Color::rgb(0.1, 0.1, 1.0).into()),