Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

text_system split #7779

Merged
merged 48 commits into from
Apr 17, 2023
Merged
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
3a09af9
changes:
ickshonpe Feb 17, 2023
b1712b8
cargo fmt
ickshonpe Feb 17, 2023
080aae7
fix lints
ickshonpe Feb 17, 2023
9c4c01d
cargo fmt
ickshonpe Feb 17, 2023
8934a6f
impl Measure for Fn
ickshonpe Feb 18, 2023
fc9b2d9
Changes:
ickshonpe Feb 19, 2023
f7d20ea
cargo fmt --all
ickshonpe Feb 19, 2023
13091a3
remove `text_constraint` function
ickshonpe Feb 21, 2023
cfdb649
Merge branch 'split-text-system' of https://github.com/ickshonpe/bevy…
ickshonpe Feb 21, 2023
621a117
Merge branch 'add-TextLayoutInfo-to-TextBundle' into split-text-system
ickshonpe Feb 21, 2023
3981c7f
add TextLayoutInfo to default TextBundle
ickshonpe Feb 21, 2023
b004072
changes:
ickshonpe Feb 21, 2023
af7b6f2
changes:
ickshonpe Feb 21, 2023
2280bb0
changes:
ickshonpe Feb 21, 2023
f510899
Clean up and bug fixes.
ickshonpe Feb 21, 2023
95d7c96
simplified text size code
ickshonpe Feb 21, 2023
91a05d2
fix derivable impls
ickshonpe Feb 21, 2023
1ee6233
added `compute_geometry` function to pipeline
ickshonpe Feb 27, 2023
5944e4b
Merge branch 'main' into split-text-system
ickshonpe Mar 14, 2023
540f51e
fixed remaining bugs from merge
ickshonpe Mar 14, 2023
ac267a9
Renamed `IntrinsicSize` back to `CalculatedSize`, too many changes in…
ickshonpe Mar 14, 2023
70f5422
Implemented one pass text `MeasureFunc`
ickshonpe Mar 14, 2023
1fe7ac7
update layout on screensize change
ickshonpe Mar 16, 2023
d919ac7
Merge branch 'main' into split-text-system
ickshonpe Mar 21, 2023
8a4c73d
Cleaned up unused code and fields.
ickshonpe Mar 21, 2023
5ae0d4d
Merge branch 'main' into split-text-system
ickshonpe Mar 21, 2023
711036b
cargo fmt --all
ickshonpe Mar 21, 2023
74afca0
Merge branch 'main' into split-text-system
ickshonpe Mar 31, 2023
941c018
changes:
ickshonpe Mar 31, 2023
89eb505
taffy dependency version to 0.3.10
ickshonpe Mar 31, 2023
ffb59a8
removed some unused code
ickshonpe Mar 31, 2023
cbb1d17
reuse section texts vec when computing min and max content size
ickshonpe Mar 31, 2023
8547ce2
changes:
ickshonpe Apr 14, 2023
76ea8a0
Merge branch 'main' into split-text-system
ickshonpe Apr 14, 2023
6c15af9
formatting
ickshonpe Apr 14, 2023
08aa11c
Added a constructor function to `TextMeasureInfo`
ickshonpe Apr 14, 2023
410a4ec
Added a comment explaining the font coordinate calculations.
ickshonpe Apr 14, 2023
a04e100
fix misnamed variable
ickshonpe Apr 14, 2023
af1f990
fix misnamed variables
ickshonpe Apr 14, 2023
10e2ddc
Merge branch 'split-text-system' of https://github.com/ickshonpe/bevy…
ickshonpe Apr 14, 2023
ee1d1f3
use `with_capacity` to construct the vecs with the correct length
ickshonpe Apr 14, 2023
8f1eb15
fixed extra space
ickshonpe Apr 14, 2023
2ec10d8
Update crates/bevy_ui/src/widget/image.rs
ickshonpe Apr 15, 2023
be4053c
added `Debug` derive to new types
ickshonpe Apr 15, 2023
7526fc6
Added comment to `text_layout_info` field of `Text2dBundle`
ickshonpe Apr 15, 2023
e33bc19
Update crates/bevy_text/src/pipeline.rs
ickshonpe Apr 15, 2023
68c6e98
fixed needless borrow
ickshonpe Apr 15, 2023
2779750
Merge branch 'split-text-system' of https://github.com/ickshonpe/bevy…
ickshonpe Apr 15, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 140 additions & 1 deletion crates/bevy_text/src/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use bevy_render::texture::Image;
use bevy_sprite::TextureAtlas;
use bevy_utils::HashMap;

use glyph_brush_layout::{FontId, SectionText};
use glyph_brush_layout::{FontId, GlyphPositioner, SectionGeometry, SectionText};

use crate::{
error::TextError, glyph_brush::GlyphBrush, scale_value, BreakLineOn, Font, FontAtlasSet,
Expand Down Expand Up @@ -92,6 +92,9 @@ impl TextPipeline {
for sg in &section_glyphs {
let scaled_font = scaled_fonts[sg.section_index];
let glyph = &sg.glyph;
// The fonts use a coordinate system increasing upwards so ascent is a positive value
// and descent is negative, but Bevy UI uses a downwards increasing coordinate system,
ickshonpe marked this conversation as resolved.
Show resolved Hide resolved
// so we have to subtract from the baseline position to get the minimum and maximum values.
min_x = min_x.min(glyph.position.x);
min_y = min_y.min(glyph.position.y - scaled_font.ascent());
max_x = max_x.max(glyph.position.x + scaled_font.h_advance(glyph.id));
Expand All @@ -114,4 +117,140 @@ impl TextPipeline {

Ok(TextLayoutInfo { glyphs, size })
}

pub fn create_text_measure(
&mut self,
fonts: &Assets<Font>,
sections: &[TextSection],
scale_factor: f64,
text_alignment: TextAlignment,
linebreak_behaviour: BreakLineOn,
) -> Result<TextMeasureInfo, TextError> {
let mut auto_fonts = Vec::new();
let mut scaled_fonts = Vec::new();
ickshonpe marked this conversation as resolved.
Show resolved Hide resolved
let sections = sections
.iter()
.enumerate()
.map(|(i, section)| {
let font = fonts
.get(&section.style.font)
.ok_or(TextError::NoSuchFont)?;
let font_size = scale_value(section.style.font_size, scale_factor);
auto_fonts.push(font.font.clone());
let px_scale_font = ab_glyph::Font::into_scaled(font.font.clone(), font_size);
scaled_fonts.push(px_scale_font);
ickshonpe marked this conversation as resolved.
Show resolved Hide resolved

let section = TextMeasureSection {
font_id: FontId(i),
scale: PxScale::from(font_size),
text: section.value.clone(),
};

Ok(section)
})
.collect::<Result<Vec<_>, _>>()?;

Ok(TextMeasureInfo::new(
auto_fonts,
scaled_fonts,
sections,
text_alignment,
linebreak_behaviour.into(),
))
}
}

#[derive(Clone)]
ickshonpe marked this conversation as resolved.
Show resolved Hide resolved
pub struct TextMeasureSection {
pub text: String,
pub scale: PxScale,
pub font_id: FontId,
}

#[derive(Clone)]
ickshonpe marked this conversation as resolved.
Show resolved Hide resolved
pub struct TextMeasureInfo {
pub fonts: Vec<ab_glyph::FontArc>,
pub scaled_fonts: Vec<ab_glyph::PxScaleFont<ab_glyph::FontArc>>,
pub sections: Vec<TextMeasureSection>,
pub text_alignment: TextAlignment,
pub linebreak_behaviour: glyph_brush_layout::BuiltInLineBreaker,
pub min_width_content_size: Vec2,
pub max_width_content_size: Vec2,
}

impl TextMeasureInfo {
fn new(
fonts: Vec<ab_glyph::FontArc>,
scaled_fonts: Vec<ab_glyph::PxScaleFont<ab_glyph::FontArc>>,
sections: Vec<TextMeasureSection>,
text_alignment: TextAlignment,
linebreak_behaviour: glyph_brush_layout::BuiltInLineBreaker,
) -> Self {
let mut info = Self {
fonts,
scaled_fonts,
sections,
text_alignment,
linebreak_behaviour,
min_width_content_size: Vec2::ZERO,
max_width_content_size: Vec2::ZERO,
};

let section_texts = info.prepare_section_texts();
let min =
info.compute_size_from_section_texts(&section_texts, Vec2::new(0.0, f32::INFINITY));
let max = info.compute_size_from_section_texts(
&section_texts,
Vec2::new(f32::INFINITY, f32::INFINITY),
);
info.min_width_content_size = min;
info.max_width_content_size = max;
info
}

fn prepare_section_texts(&self) -> Vec<SectionText> {
self.sections
.iter()
.map(|section| SectionText {
font_id: section.font_id,
scale: section.scale,
text: &section.text,
})
.collect::<Vec<_>>()
}

fn compute_size_from_section_texts(&self, sections: &[SectionText], bounds: Vec2) -> Vec2 {
let geom = SectionGeometry {
bounds: (bounds.x, bounds.y),
..Default::default()
};
let section_glyphs = glyph_brush_layout::Layout::default()
.h_align(self.text_alignment.into())
.line_breaker(self.linebreak_behaviour)
.calculate_glyphs(&self.fonts, &geom, &sections);

let mut min_x: f32 = std::f32::MAX;
let mut min_y: f32 = std::f32::MAX;
let mut max_x: f32 = std::f32::MIN;
let mut max_y: f32 = std::f32::MIN;

for sg in section_glyphs {
let scaled_font = &self.scaled_fonts[sg.section_index];
let glyph = &sg.glyph;
// The fonts use a coordinate system increasing upwards so ascent is a positive value
// and descent is negative, but Bevy UI uses a downwards increasing coordinate system,
ickshonpe marked this conversation as resolved.
Show resolved Hide resolved
// so we have to subtract from the baseline position to get the minimum and maximum values.
min_x = min_x.min(glyph.position.x);
ickshonpe marked this conversation as resolved.
Show resolved Hide resolved
min_y = min_y.min(glyph.position.y - scaled_font.ascent());
max_x = max_x.max(glyph.position.x + scaled_font.h_advance(glyph.id));
max_y = max_y.max(glyph.position.y - scaled_font.descent());
ickshonpe marked this conversation as resolved.
Show resolved Hide resolved
}

Vec2::new(max_x - min_x, max_y - min_y)
}

pub fn compute_size(&self, bounds: Vec2) -> Vec2 {
let sections = self.prepare_section_texts();
self.compute_size_from_section_texts(&sections, bounds)
}
}
20 changes: 5 additions & 15 deletions crates/bevy_text/src/text2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use bevy_ecs::{
event::EventReader,
prelude::With,
reflect::ReflectComponent,
system::{Commands, Local, Query, Res, ResMut},
system::{Local, Query, Res, ResMut},
};
use bevy_math::{Vec2, Vec3};
use bevy_reflect::Reflect;
Expand Down Expand Up @@ -72,6 +72,7 @@ pub struct Text2dBundle {
pub visibility: Visibility,
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering.
pub computed_visibility: ComputedVisibility,
pub text_layout_info: TextLayoutInfo,
ickshonpe marked this conversation as resolved.
Show resolved Hide resolved
}

pub fn extract_text2d_sprite(
Expand Down Expand Up @@ -147,7 +148,6 @@ pub fn extract_text2d_sprite(
/// It does not modify or observe existing ones.
#[allow(clippy::too_many_arguments)]
pub fn update_text2d_layout(
mut commands: Commands,
// Text items which should be reprocessed again, generally when the font hasn't loaded yet.
mut queue: Local<HashSet<Entity>>,
mut textures: ResMut<Assets<Image>>,
Expand All @@ -159,12 +159,7 @@ pub fn update_text2d_layout(
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
mut font_atlas_set_storage: ResMut<Assets<FontAtlasSet>>,
mut text_pipeline: ResMut<TextPipeline>,
mut text_query: Query<(
Entity,
Ref<Text>,
Ref<Text2dBounds>,
Option<&mut TextLayoutInfo>,
)>,
mut text_query: Query<(Entity, Ref<Text>, Ref<Text2dBounds>, &mut TextLayoutInfo)>,
) {
// We need to consume the entire iterator, hence `last`
let factor_changed = scale_factor_changed.iter().last().is_some();
Expand All @@ -175,7 +170,7 @@ pub fn update_text2d_layout(
.map(|window| window.resolution.scale_factor())
.unwrap_or(1.0);

for (entity, text, bounds, text_layout_info) in &mut text_query {
for (entity, text, bounds, mut text_layout_info) in &mut text_query {
if factor_changed || text.is_changed() || bounds.is_changed() || queue.remove(&entity) {
let text_bounds = Vec2::new(
scale_value(bounds.size.x, scale_factor),
Expand Down Expand Up @@ -204,12 +199,7 @@ pub fn update_text2d_layout(
Err(e @ TextError::FailedToAddGlyph(_)) => {
panic!("Fatal error when processing text: {e}.");
}
Ok(info) => match text_layout_info {
Some(mut t) => *t = info,
None => {
commands.entity(entity).insert(info);
}
},
Ok(info) => *text_layout_info = info,
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_ui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ bevy_window = { path = "../bevy_window", version = "0.11.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.11.0-dev" }

# other
taffy = { version = "0.3.5", default-features = false, features = ["std"] }
taffy = { version = "0.3.10", default-features = false, features = ["std"] }
serde = { version = "1", features = ["derive"] }
smallvec = { version = "1.6", features = ["union", "const_generics"] }
bytemuck = { version = "1.5", features = ["derive"] }
Expand Down
52 changes: 21 additions & 31 deletions crates/bevy_ui/src/flex/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,45 +97,35 @@ impl FlexSurface {
&mut self,
entity: Entity,
style: &Style,
calculated_size: CalculatedSize,
calculated_size: &CalculatedSize,
context: &LayoutContext,
) {
let taffy = &mut self.taffy;
let taffy_style = convert::from_style(context, style);
let scale_factor = context.scale_factor;
let measure = taffy::node::MeasureFunc::Boxed(Box::new(
move |constraints: Size<Option<f32>>, _available: Size<AvailableSpace>| {
let mut size = Size {
width: (scale_factor * calculated_size.size.x as f64) as f32,
height: (scale_factor * calculated_size.size.y as f64) as f32,
};
match (constraints.width, constraints.height) {
(None, None) => {}
(Some(width), None) => {
if calculated_size.preserve_aspect_ratio {
size.height = width * size.height / size.width;
}
size.width = width;
}
(None, Some(height)) => {
if calculated_size.preserve_aspect_ratio {
size.width = height * size.width / size.height;
}
size.height = height;
}
(Some(width), Some(height)) => {
size.width = width;
size.height = height;
}
let measure = calculated_size.measure.dyn_clone();
ickshonpe marked this conversation as resolved.
Show resolved Hide resolved
let measure_func = taffy::node::MeasureFunc::Boxed(Box::new(
move |constraints: Size<Option<f32>>, available: Size<AvailableSpace>| {
let size = measure.measure(
constraints.width,
constraints.height,
available.width,
available.height,
);
taffy::geometry::Size {
width: size.x,
height: size.y,
}
size
},
));
if let Some(taffy_node) = self.entity_to_taffy.get(&entity) {
self.taffy.set_style(*taffy_node, taffy_style).unwrap();
self.taffy.set_measure(*taffy_node, Some(measure)).unwrap();
self.taffy
.set_measure(*taffy_node, Some(measure_func))
.unwrap();
} else {
let taffy_node = taffy.new_leaf_with_measure(taffy_style, measure).unwrap();
let taffy_node = taffy
.new_leaf_with_measure(taffy_style, measure_func)
.unwrap();
self.entity_to_taffy.insert(entity, taffy_node);
}
}
Expand Down Expand Up @@ -307,7 +297,7 @@ pub fn flex_node_system(
for (entity, style, calculated_size) in &query {
// TODO: remove node from old hierarchy if its root has changed
if let Some(calculated_size) = calculated_size {
flex_surface.upsert_leaf(entity, style, *calculated_size, viewport_values);
flex_surface.upsert_leaf(entity, style, calculated_size, viewport_values);
} else {
flex_surface.upsert_node(entity, style, viewport_values);
}
Expand All @@ -322,7 +312,7 @@ pub fn flex_node_system(
}

for (entity, style, calculated_size) in &changed_size_query {
flex_surface.upsert_leaf(entity, style, *calculated_size, &viewport_values);
flex_surface.upsert_leaf(entity, style, calculated_size, &viewport_values);
}

// clean up removed nodes
Expand Down
11 changes: 7 additions & 4 deletions crates/bevy_ui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ mod ui_node;
#[cfg(feature = "bevy_text")]
mod accessibility;
pub mod camera_config;
pub mod measurement;
pub mod node_bundles;
pub mod update;
pub mod widget;
Expand All @@ -24,17 +25,20 @@ use bevy_render::extract_component::ExtractComponentPlugin;
pub use flex::*;
pub use focus::*;
pub use geometry::*;
pub use measurement::*;
pub use render::*;
pub use ui_node::*;

#[doc(hidden)]
pub mod prelude {
#[doc(hidden)]
pub use crate::{
camera_config::*, geometry::*, node_bundles::*, ui_node::*, widget::*, Interaction, UiScale,
camera_config::*, geometry::*, node_bundles::*, ui_node::*, widget::Button, widget::Label,
Interaction, UiScale,
};
}

use crate::prelude::UiCameraConfig;
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use bevy_input::InputSystem;
Expand All @@ -43,8 +47,6 @@ use stack::ui_stack_system;
pub use stack::UiStack;
use update::update_clipping_system;

use crate::prelude::UiCameraConfig;

/// The basic plugin for Bevy UI
#[derive(Default)]
pub struct UiPlugin;
Expand Down Expand Up @@ -114,7 +116,7 @@ impl Plugin for UiPlugin {
#[cfg(feature = "bevy_text")]
app.add_systems(
PostUpdate,
widget::text_system
widget::measure_text_system
.before(UiSystem::Flex)
// Potential conflict: `Assets<Image>`
// In practice, they run independently since `bevy_render::camera_update_system`
Expand Down Expand Up @@ -149,6 +151,7 @@ impl Plugin for UiPlugin {
.before(TransformSystem::TransformPropagate),
ui_stack_system.in_set(UiSystem::Stack),
update_clipping_system.after(TransformSystem::TransformPropagate),
widget::text_system.after(UiSystem::Flex),
),
);

Expand Down
Loading