diff --git a/crates/bevy_math/src/geometry.rs b/crates/bevy_math/src/geometry.rs index 5688ca86ae..45fa658e82 100644 --- a/crates/bevy_math/src/geometry.rs +++ b/crates/bevy_math/src/geometry.rs @@ -2,7 +2,7 @@ use std::ops::{Add, AddAssign}; use glam::Vec2; #[derive(Copy, Clone, PartialEq, Debug)] -pub struct Size { +pub struct Size { pub width: T, pub height: T, } diff --git a/crates/bevy_text/src/draw.rs b/crates/bevy_text/src/draw.rs index 315000eb56..7a3ca538e7 100644 --- a/crates/bevy_text/src/draw.rs +++ b/crates/bevy_text/src/draw.rs @@ -1,5 +1,5 @@ use crate::{Font, FontAtlasSet}; -use ab_glyph::{FontVec, Glyph, PxScale, PxScaleFont, ScaleFont}; +use ab_glyph::{Glyph, PxScale, ScaleFont}; use bevy_asset::Assets; use bevy_math::{Mat4, Vec2, Vec3}; use bevy_render::{ @@ -17,7 +17,6 @@ use bevy_sprite::{TextureAtlas, TextureAtlasSprite}; pub struct TextStyle { pub font_size: f32, pub color: Color, - pub align: TextAlign, } impl Default for TextStyle { @@ -25,23 +24,10 @@ impl Default for TextStyle { Self { color: Color::WHITE, 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 font: &'a Font, pub font_atlas_set: &'a FontAtlasSet, @@ -54,26 +40,6 @@ pub struct DrawableText<'a> { pub text: &'a str, } -fn get_text_width(text: &str, scaled_font: &PxScaleFont<&&FontVec>) -> f32 { - let mut last_glyph: Option = 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> { fn draw(&mut self, draw: &mut Draw, context: &mut DrawContext) -> Result<(), DrawError> { context.set_pipeline( @@ -109,17 +75,6 @@ impl<'a> Drawable for DrawableText<'a> { let scale = PxScale::from(self.style.font_size); let scaled_font = ab_glyph::Font::as_scaled(&font, scale); 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 = None; // set local per-character bindings diff --git a/crates/bevy_text/src/font_atlas_set.rs b/crates/bevy_text/src/font_atlas_set.rs index 8ef4981792..5f5af44ad4 100644 --- a/crates/bevy_text/src/font_atlas_set.rs +++ b/crates/bevy_text/src/font_atlas_set.rs @@ -1,5 +1,5 @@ use crate::{Font, FontAtlas}; -use ab_glyph::ScaleFont; +use ab_glyph::{Glyph, ScaleFont}; use bevy_asset::{Assets, Handle}; use bevy_core::FloatOrd; use bevy_math::Vec2; @@ -49,24 +49,35 @@ impl FontAtlasSet { textures: &mut Assets, font_size: f32, text: &str, - ) { + ) -> f32 { let font = fonts.get(&self.font).unwrap(); let scaled_font = ab_glyph::Font::as_scaled(&font.font, font_size); let font_atlas = self .font_atlases .entry(FloatOrd(font_size)) .or_insert_with(|| FontAtlas::new(textures, texture_atlases, Vec2::new(512.0, 512.0))); + + let mut last_glyph: Option = None; + let mut width = 0.0; for character in text.chars() { - if character.is_control() || font_atlas.get_char_index(character).is_some() { + if character.is_control() { continue; } - let glyph = scaled_font.scaled_glyph(character); - if let Some(outlined_glyph) = scaled_font.outline_glyph(glyph) { - let glyph_texture = Font::get_outlined_glyph_texture(outlined_glyph); - font_atlas.add_char(textures, texture_atlases, character, &glyph_texture); + if let Some(last_glyph) = last_glyph.take() { + width += scaled_font.kern(last_glyph.id, glyph.id); } + 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 { diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index 204b0ec32b..7cf2b118cc 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -11,7 +11,7 @@ pub use font_atlas_set::*; pub use font_loader::*; pub mod prelude { - pub use crate::{Font, TextAlign, TextStyle}; + pub use crate::{Font, TextStyle}; } use bevy_app::prelude::*; diff --git a/crates/bevy_ui/src/entity.rs b/crates/bevy_ui/src/entity.rs index 17cf4bac1b..2f8cb99645 100644 --- a/crates/bevy_ui/src/entity.rs +++ b/crates/bevy_ui/src/entity.rs @@ -2,7 +2,7 @@ use super::Node; use crate::{ render::UI_PIPELINE_HANDLE, widget::{Button, Text}, - Click, FocusPolicy, Hover, Style, + Click, FocusPolicy, Hover, Style, CalculatedSize, }; use bevy_asset::Handle; use bevy_ecs::Bundle; @@ -68,6 +68,7 @@ pub struct TextComponents { pub style: Style, pub draw: Draw, pub text: Text, + pub calculated_size: CalculatedSize, pub focus_policy: FocusPolicy, pub transform: Transform, pub local_transform: LocalTransform, @@ -83,6 +84,7 @@ impl Default for TextComponents { }, text: Default::default(), node: Default::default(), + calculated_size: Default::default(), style: Default::default(), transform: Default::default(), local_transform: Default::default(), diff --git a/crates/bevy_ui/src/flex/mod.rs b/crates/bevy_ui/src/flex/mod.rs index 9083f8fd3a..9690f906d6 100644 --- a/crates/bevy_ui/src/flex/mod.rs +++ b/crates/bevy_ui/src/flex/mod.rs @@ -1,6 +1,6 @@ mod convert; -use crate::{Style, Node}; +use crate::{CalculatedSize, Node, Style}; use bevy_ecs::{Changed, Entity, Query, Res, ResMut, With, Without}; use bevy_math::Vec2; use bevy_transform::prelude::{Children, LocalTransform, Parent}; @@ -10,7 +10,6 @@ use stretch::Stretch; pub struct FlexSurface { entity_to_stretch: HashMap, - stretch_to_entity: HashMap, window_nodes: HashMap, stretch: Stretch, } @@ -19,7 +18,6 @@ impl Default for FlexSurface { fn default() -> Self { Self { entity_to_stretch: Default::default(), - stretch_to_entity: Default::default(), window_nodes: Default::default(), stretch: Stretch::new(), } @@ -30,12 +28,10 @@ impl FlexSurface { pub fn upsert_node(&mut self, entity: Entity, style: &Style) { let mut added = false; let stretch = &mut self.stretch; - let stretch_to_entity = &mut self.stretch_to_entity; let stretch_style = style.into(); let stretch_node = self.entity_to_stretch.entry(entity).or_insert_with(|| { added = true; let stretch_node = stretch.new_node(stretch_style, Vec::new()).unwrap(); - stretch_to_entity.insert(stretch_node, entity); 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) { let mut stretch_children = Vec::with_capacity(children.len()); for child in children.iter() { @@ -63,10 +97,7 @@ impl FlexSurface { let stretch = &mut self.stretch; let node = self.window_nodes.entry(window.id).or_insert_with(|| { stretch - .new_node( - stretch::style::Style::default(), - Vec::new(), - ) + .new_node(stretch::style::Style::default(), Vec::new()) .unwrap() }); @@ -120,7 +151,8 @@ pub fn flex_node_system( windows: Res, mut flex_surface: ResMut, mut root_node_query: Query>>, - mut node_query: Query)>>, + mut node_query: Query, Option<&CalculatedSize>)>>, + mut changed_size_query: Query)>>, mut children_query: Query)>>, mut node_transform_query: Query<(Entity, &mut Node, &mut LocalTransform, Option<&Parent>)>, ) { @@ -130,9 +162,17 @@ pub fn flex_node_system( } // 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 - 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 diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index 16d7fc264b..649c3a4f60 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -39,7 +39,7 @@ impl AppPlugin for UiPlugin { // 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, 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()); let resources = app.resources(); diff --git a/crates/bevy_ui/src/node.rs b/crates/bevy_ui/src/node.rs index 019477019e..ecc42fc0b7 100644 --- a/crates/bevy_ui/src/node.rs +++ b/crates/bevy_ui/src/node.rs @@ -41,7 +41,11 @@ impl AddAssign for Val { Val::Percent(value) => *value += rhs, } } - +} + +#[derive(Default, Copy, Clone)] +pub struct CalculatedSize { + pub size: Size, } #[derive(Clone, PartialEq, Debug)] diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index 654a57edf2..58ba23d3dd 100644 --- a/crates/bevy_ui/src/widget/text.rs +++ b/crates/bevy_ui/src/widget/text.rs @@ -1,6 +1,7 @@ -use crate::Node; +use crate::{CalculatedSize, Node}; 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::{ draw::{Draw, DrawContext, Drawable}, renderer::{AssetRenderResourceBindings, RenderResourceBindings}, @@ -22,9 +23,9 @@ pub fn text_system( fonts: Res>, mut font_atlas_sets: ResMut>, mut texture_atlases: ResMut>, - mut query: Query<&Text>, + mut query: Query<(Changed, &mut CalculatedSize)>, ) { - for text in &mut query.iter() { + for (text, mut calculated_size) in &mut query.iter() { let font_atlases = font_atlas_sets .get_or_insert_with(Handle::from_id(text.font.id), || { 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 // 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. - font_atlases.add_glyphs_to_atlas( + let width = font_atlases.add_glyphs_to_atlas( &fonts, &mut texture_atlases, &mut textures, text.style.font_size, &text.value, ); + + calculated_size.size = Size::new(width, text.style.font_size); } } diff --git a/examples/game/breakout.rs b/examples/game/breakout.rs index c466d07d43..b610bc0d0e 100644 --- a/examples/game/breakout.rs +++ b/examples/game/breakout.rs @@ -71,7 +71,6 @@ fn setup( style: TextStyle { color: Color::rgb(0.2, 0.2, 0.8).into(), font_size: 40.0, - ..Default::default() }, }, style: Style { diff --git a/examples/ui/button.rs b/examples/ui/button.rs index 92e42b942f..91e6fd5b86 100644 --- a/examples/ui/button.rs +++ b/examples/ui/button.rs @@ -95,13 +95,13 @@ fn setup( .spawn(UiCameraComponents::default()) .spawn(ButtonComponents { style: Style { - size: Size::new(Val::Px(150.0), Val::Px(70.0)), - align_self: AlignSelf::Center, - margin: Rect { - left: Val::Auto, - right: Val::Auto, - ..Default::default() - }, + size: Size::new(Val::Px(150.0), Val::Px(65.0)), + // center button + margin: Rect::all(Val::Auto), + // horizontally center child text + justify_content: JustifyContent::Center, + // vertically center child text + align_items: AlignItems::Center, ..Default::default() }, material: button_materials.normal, @@ -109,21 +109,12 @@ fn setup( }) .with_children(|parent| { 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 { value: "Button".to_string(), font: asset_server.load("assets/fonts/FiraSans-Bold.ttf").unwrap(), style: TextStyle { font_size: 40.0, color: Color::rgb(0.8, 0.8, 0.8), - align: TextAlign::Center, }, }, ..Default::default() diff --git a/examples/ui/font_atlas_debug.rs b/examples/ui/font_atlas_debug.rs index ea69b4c185..95bd8c6635 100644 --- a/examples/ui/font_atlas_debug.rs +++ b/examples/ui/font_atlas_debug.rs @@ -81,7 +81,6 @@ fn setup(mut commands: Commands, asset_server: Res, mut state: ResM style: TextStyle { font_size: 60.0, color: Color::WHITE, - ..Default::default() }, }, ..Default::default() diff --git a/examples/ui/text.rs b/examples/ui/text.rs index c13cec8918..165b5b2c8f 100644 --- a/examples/ui/text.rs +++ b/examples/ui/text.rs @@ -26,17 +26,19 @@ fn setup(mut commands: Commands, asset_server: Res) { let font_handle = asset_server.load("assets/fonts/FiraSans-Bold.ttf").unwrap(); commands // 2d camera - .spawn(Camera2dComponents::default()) + .spawn(UiCameraComponents::default()) // texture .spawn(TextComponents { - node: Node::default(), + style: Style { + align_self: AlignSelf::FlexEnd, + ..Default::default() + }, text: Text { value: "FPS:".to_string(), font: font_handle, style: TextStyle { font_size: 60.0, color: Color::WHITE, - align: TextAlign::Left, }, }, ..Default::default() diff --git a/examples/ui/ui.rs b/examples/ui/ui.rs index daf4b96a87..afb929c510 100644 --- a/examples/ui/ui.rs +++ b/examples/ui/ui.rs @@ -61,12 +61,7 @@ fn setup( // text parent.spawn(TextComponents { style: Style { - size: Size::new(Val::Px(100.0), Val::Px(30.0)), - margin: Rect { - left: Val::Px(5.0), - top: Val::Px(5.0), - ..Default::default() - }, + margin: Rect::all(Val::Px(5.0)), ..Default::default() }, text: Text { @@ -77,7 +72,6 @@ fn setup( style: TextStyle { font_size: 30.0, color: Color::WHITE, - ..Default::default() }, }, ..Default::default() @@ -103,7 +97,7 @@ fn setup( bottom: Val::Px(10.0), ..Default::default() }, - border: Rect::all(Val::Px(10.0)), + border: Rect::all(Val::Px(20.0)), ..Default::default() }, material: materials.add(Color::rgb(0.1, 0.1, 1.0).into()),