Skip to content

Commit f574c2c

Browse files
authored
Render text in 2D scenes (#1122)
Render text in 2D scenes
1 parent c32c78f commit f574c2c

File tree

15 files changed

+288
-90
lines changed

15 files changed

+288
-90
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@ path = "examples/2d/texture_atlas.rs"
102102
name = "contributors"
103103
path = "examples/2d/contributors.rs"
104104

105+
[[example]]
106+
name = "text2d"
107+
path = "examples/2d/text2d.rs"
108+
105109
[[example]]
106110
name = "load_gltf"
107111
path = "examples/3d/load_gltf.rs"

crates/bevy_render/src/render_graph/base.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use bevy_reflect::{Reflect, ReflectComponent};
1414
use bevy_window::WindowId;
1515

1616
/// A component that indicates that an entity should be drawn in the "main pass"
17-
#[derive(Default, Reflect)]
17+
#[derive(Clone, Debug, Default, Reflect)]
1818
#[reflect(Component)]
1919
pub struct MainPass;
2020

crates/bevy_render/src/render_graph/nodes/pass_node.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ pub struct PassNode<Q: WorldQuery> {
3737

3838
impl<Q: WorldQuery> fmt::Debug for PassNode<Q> {
3939
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
40-
f.debug_struct("PassNose")
40+
f.debug_struct("PassNode")
4141
.field("descriptor", &self.descriptor)
4242
.field("inputs", &self.inputs)
4343
.field("cameras", &self.cameras)

crates/bevy_text/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ bevy_math = { path = "../bevy_math", version = "0.4.0" }
2222
bevy_reflect = { path = "../bevy_reflect", version = "0.4.0", features = ["bevy"] }
2323
bevy_render = { path = "../bevy_render", version = "0.4.0" }
2424
bevy_sprite = { path = "../bevy_sprite", version = "0.4.0" }
25+
bevy_transform = { path = "../bevy_transform", version = "0.4.0" }
2526
bevy_utils = { path = "../bevy_utils", version = "0.4.0" }
2627

2728
# other

crates/bevy_text/src/lib.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ mod font_atlas_set;
66
mod font_loader;
77
mod glyph_brush;
88
mod pipeline;
9+
mod text;
10+
mod text2d;
911

1012
pub use draw::*;
1113
pub use error::*;
@@ -15,15 +17,17 @@ pub use font_atlas_set::*;
1517
pub use font_loader::*;
1618
pub use glyph_brush::*;
1719
pub use pipeline::*;
20+
pub use text::*;
21+
pub use text2d::*;
1822

1923
pub mod prelude {
20-
pub use crate::{Font, TextAlignment, TextError, TextStyle};
24+
pub use crate::{Font, Text, Text2dBundle, TextAlignment, TextError, TextStyle};
2125
pub use glyph_brush_layout::{HorizontalAlign, VerticalAlign};
2226
}
2327

2428
use bevy_app::prelude::*;
2529
use bevy_asset::AddAsset;
26-
use bevy_ecs::Entity;
30+
use bevy_ecs::{Entity, IntoSystem};
2731

2832
pub type DefaultTextPipeline = TextPipeline<Entity>;
2933

@@ -35,6 +39,11 @@ impl Plugin for TextPlugin {
3539
app.add_asset::<Font>()
3640
.add_asset::<FontAtlasSet>()
3741
.init_asset_loader::<FontLoader>()
38-
.add_resource(DefaultTextPipeline::default());
42+
.add_resource(DefaultTextPipeline::default())
43+
.add_system_to_stage(bevy_app::stage::POST_UPDATE, text2d_system.system())
44+
.add_system_to_stage(
45+
bevy_render::stage::DRAW,
46+
text2d::draw_text2d_system.system(),
47+
);
3948
}
4049
}

crates/bevy_text/src/text.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
use bevy_asset::Handle;
2+
use bevy_math::Size;
3+
4+
use crate::{Font, TextStyle};
5+
6+
#[derive(Debug, Default, Clone)]
7+
pub struct Text {
8+
pub value: String,
9+
pub font: Handle<Font>,
10+
pub style: TextStyle,
11+
}
12+
13+
#[derive(Default, Copy, Clone, Debug)]
14+
pub struct CalculatedSize {
15+
pub size: Size,
16+
}

crates/bevy_text/src/text2d.rs

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
use bevy_asset::Assets;
2+
use bevy_ecs::{Bundle, Changed, Entity, Local, Query, QuerySet, Res, ResMut, With};
3+
use bevy_math::{Size, Vec3};
4+
use bevy_render::{
5+
draw::{DrawContext, Drawable},
6+
mesh::Mesh,
7+
prelude::{Draw, Msaa, Texture, Visible},
8+
render_graph::base::MainPass,
9+
renderer::RenderResourceBindings,
10+
};
11+
use bevy_sprite::{TextureAtlas, QUAD_HANDLE};
12+
use bevy_transform::prelude::{GlobalTransform, Transform};
13+
use glyph_brush_layout::{HorizontalAlign, VerticalAlign};
14+
15+
use crate::{
16+
CalculatedSize, DefaultTextPipeline, DrawableText, Font, FontAtlasSet, Text, TextError,
17+
};
18+
19+
/// The bundle of components needed to draw text in a 2D scene via the Camera2dBundle.
20+
#[derive(Bundle, Clone, Debug)]
21+
pub struct Text2dBundle {
22+
pub draw: Draw,
23+
pub visible: Visible,
24+
pub text: Text,
25+
pub transform: Transform,
26+
pub global_transform: GlobalTransform,
27+
pub main_pass: MainPass,
28+
pub calculated_size: CalculatedSize,
29+
}
30+
31+
impl Default for Text2dBundle {
32+
fn default() -> Self {
33+
Self {
34+
draw: Draw {
35+
..Default::default()
36+
},
37+
visible: Visible {
38+
is_transparent: true,
39+
..Default::default()
40+
},
41+
text: Default::default(),
42+
transform: Default::default(),
43+
global_transform: Default::default(),
44+
main_pass: MainPass {},
45+
calculated_size: CalculatedSize {
46+
size: Size::default(),
47+
},
48+
}
49+
}
50+
}
51+
52+
/// System for drawing text in a 2D scene via the Camera2dBundle. Included in the default
53+
/// `TextPlugin`. Position is determined by the `Transform`'s translation, though scale and rotation
54+
/// are ignored.
55+
pub fn draw_text2d_system(
56+
mut context: DrawContext,
57+
msaa: Res<Msaa>,
58+
meshes: Res<Assets<Mesh>>,
59+
mut render_resource_bindings: ResMut<RenderResourceBindings>,
60+
text_pipeline: Res<DefaultTextPipeline>,
61+
mut query: Query<
62+
(
63+
Entity,
64+
&mut Draw,
65+
&Visible,
66+
&Text,
67+
&GlobalTransform,
68+
&CalculatedSize,
69+
),
70+
With<MainPass>,
71+
>,
72+
) {
73+
let font_quad = meshes.get(&QUAD_HANDLE).unwrap();
74+
let vertex_buffer_descriptor = font_quad.get_vertex_buffer_descriptor();
75+
76+
for (entity, mut draw, visible, text, global_transform, calculated_size) in query.iter_mut() {
77+
if !visible.is_visible {
78+
continue;
79+
}
80+
81+
let (width, height) = (calculated_size.size.width, calculated_size.size.height);
82+
83+
if let Some(text_glyphs) = text_pipeline.get_glyphs(&entity) {
84+
let position = global_transform.translation
85+
+ match text.style.alignment.vertical {
86+
VerticalAlign::Top => Vec3::zero(),
87+
VerticalAlign::Center => Vec3::new(0.0, -height * 0.5, 0.0),
88+
VerticalAlign::Bottom => Vec3::new(0.0, -height, 0.0),
89+
}
90+
+ match text.style.alignment.horizontal {
91+
HorizontalAlign::Left => Vec3::new(-width, 0.0, 0.0),
92+
HorizontalAlign::Center => Vec3::new(-width * 0.5, 0.0, 0.0),
93+
HorizontalAlign::Right => Vec3::zero(),
94+
};
95+
96+
let mut drawable_text = DrawableText {
97+
render_resource_bindings: &mut render_resource_bindings,
98+
position,
99+
msaa: &msaa,
100+
text_glyphs: &text_glyphs.glyphs,
101+
font_quad_vertex_descriptor: &vertex_buffer_descriptor,
102+
style: &text.style,
103+
};
104+
105+
drawable_text.draw(&mut draw, &mut context).unwrap();
106+
}
107+
}
108+
}
109+
110+
#[derive(Debug, Default)]
111+
pub struct QueuedText2d {
112+
entities: Vec<Entity>,
113+
}
114+
115+
/// Updates the TextGlyphs with the new computed glyphs from the layout
116+
pub fn text2d_system(
117+
mut queued_text: Local<QueuedText2d>,
118+
mut textures: ResMut<Assets<Texture>>,
119+
fonts: Res<Assets<Font>>,
120+
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
121+
mut font_atlas_set_storage: ResMut<Assets<FontAtlasSet>>,
122+
mut text_pipeline: ResMut<DefaultTextPipeline>,
123+
mut text_queries: QuerySet<(
124+
Query<Entity, Changed<Text>>,
125+
Query<(&Text, &mut CalculatedSize)>,
126+
)>,
127+
) {
128+
// Adds all entities where the text or the style has changed to the local queue
129+
for entity in text_queries.q0_mut().iter_mut() {
130+
queued_text.entities.push(entity);
131+
}
132+
133+
if queued_text.entities.is_empty() {
134+
return;
135+
}
136+
137+
// Computes all text in the local queue
138+
let mut new_queue = Vec::new();
139+
let query = text_queries.q1_mut();
140+
for entity in queued_text.entities.drain(..) {
141+
if let Ok((text, mut calculated_size)) = query.get_mut(entity) {
142+
match text_pipeline.queue_text(
143+
entity,
144+
text.font.clone(),
145+
&fonts,
146+
&text.value,
147+
text.style.font_size,
148+
text.style.alignment,
149+
Size::new(f32::MAX, f32::MAX),
150+
&mut *font_atlas_set_storage,
151+
&mut *texture_atlases,
152+
&mut *textures,
153+
) {
154+
Err(TextError::NoSuchFont) => {
155+
// There was an error processing the text layout, let's add this entity to the queue for further processing
156+
new_queue.push(entity);
157+
}
158+
Err(e @ TextError::FailedToAddGlyph(_)) => {
159+
panic!("Fatal error when processing text: {}.", e);
160+
}
161+
Ok(()) => {
162+
let text_layout_info = text_pipeline.get_glyphs(&entity).expect(
163+
"Failed to get glyphs from the pipeline that have just been computed",
164+
);
165+
calculated_size.size = text_layout_info.size;
166+
}
167+
}
168+
}
169+
}
170+
171+
queued_text.entities = new_queue;
172+
}

crates/bevy_ui/src/entity.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use super::Node;
22
use crate::{
33
render::UI_PIPELINE_HANDLE,
4-
widget::{Button, Image, Text},
5-
CalculatedSize, FocusPolicy, Interaction, Style,
4+
widget::{Button, Image},
5+
FocusPolicy, Interaction, Style,
66
};
77
use bevy_asset::Handle;
88
use bevy_ecs::Bundle;
@@ -15,6 +15,7 @@ use bevy_render::{
1515
prelude::Visible,
1616
};
1717
use bevy_sprite::{ColorMaterial, QUAD_HANDLE};
18+
use bevy_text::{CalculatedSize, Text};
1819
use bevy_transform::prelude::{GlobalTransform, Transform};
1920

2021
#[derive(Bundle, Clone, Debug)]

crates/bevy_ui/src/flex/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
mod convert;
22

3-
use crate::{CalculatedSize, Node, Style};
3+
use crate::{Node, Style};
44
use bevy_ecs::{Changed, Entity, Query, Res, ResMut, With, Without};
55
use bevy_math::Vec2;
6+
use bevy_text::CalculatedSize;
67
use bevy_transform::prelude::{Children, Parent, Transform};
78
use bevy_utils::HashMap;
89
use bevy_window::{Window, WindowId, Windows};

crates/bevy_ui/src/lib.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,7 @@ pub use node::*;
1616
pub use render::*;
1717

1818
pub mod prelude {
19-
pub use crate::{
20-
entity::*,
21-
node::*,
22-
widget::{Button, Text},
23-
Anchors, Interaction, Margins,
24-
};
19+
pub use crate::{entity::*, node::*, widget::Button, Anchors, Interaction, Margins};
2520
}
2621

2722
use bevy_app::prelude::*;

0 commit comments

Comments
 (0)