Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/ui/text_debug.rs
6596 views
1
//! Shows various text layout options.
2
3
use std::{collections::VecDeque, time::Duration};
4
5
use bevy::{
6
color::palettes::css::*,
7
diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin},
8
prelude::*,
9
ui::widget::TextUiWriter,
10
window::PresentMode,
11
};
12
13
fn main() {
14
App::new()
15
.add_plugins((
16
DefaultPlugins.set(WindowPlugin {
17
primary_window: Some(Window {
18
present_mode: PresentMode::AutoNoVsync,
19
..default()
20
}),
21
..default()
22
}),
23
FrameTimeDiagnosticsPlugin::default(),
24
))
25
.add_systems(Startup, infotext_system)
26
.add_systems(Update, change_text_system)
27
.run();
28
}
29
30
#[derive(Component)]
31
struct TextChanges;
32
33
fn infotext_system(mut commands: Commands, asset_server: Res<AssetServer>) {
34
let font = asset_server.load("fonts/FiraSans-Bold.ttf");
35
let background_color = MAROON.into();
36
commands.spawn(Camera2d);
37
38
let root_uinode = commands
39
.spawn(Node {
40
width: percent(100),
41
height: percent(100),
42
justify_content: JustifyContent::SpaceBetween,
43
..default()
44
})
45
.id();
46
47
let left_column = commands
48
.spawn(Node {
49
flex_direction: FlexDirection::Column,
50
justify_content: JustifyContent::SpaceBetween,
51
align_items: AlignItems::Start,
52
flex_grow: 1.,
53
margin: UiRect::axes(px(15), px(5)),
54
..default()
55
}).with_children(|builder| {
56
builder.spawn((
57
Text::new("This is\ntext with\nline breaks\nin the top left."),
58
TextFont {
59
font: font.clone(),
60
font_size: 25.0,
61
..default()
62
},
63
BackgroundColor(background_color)
64
));
65
builder.spawn((
66
Text::new(
67
"This text is right-justified. The `Justify` component controls the horizontal alignment of the lines of multi-line text relative to each other, and does not affect the text node's position in the UI layout.",
68
),
69
TextFont {
70
font: font.clone(),
71
font_size: 25.0,
72
..default()
73
},
74
TextColor(YELLOW.into()),
75
TextLayout::new_with_justify(Justify::Right),
76
Node {
77
max_width: px(300),
78
..default()
79
},
80
BackgroundColor(background_color)
81
));
82
builder.spawn((
83
Text::new(
84
"This\ntext has\nline breaks and also a set width in the bottom left."),
85
TextFont {
86
font: font.clone(),
87
font_size: 25.0,
88
..default()
89
},
90
Node {
91
max_width: px(300),
92
..default()
93
},
94
BackgroundColor(background_color)
95
)
96
);
97
}).id();
98
99
let right_column = commands
100
.spawn(Node {
101
flex_direction: FlexDirection::Column,
102
justify_content: JustifyContent::SpaceBetween,
103
align_items: AlignItems::End,
104
flex_grow: 1.,
105
margin: UiRect::axes(px(15), px(5)),
106
..default()
107
})
108
.with_children(|builder| {
109
builder.spawn((
110
Text::new("This text is very long, has a limited width, is center-justified, is positioned in the top right and is also colored pink."),
111
TextFont {
112
font: font.clone(),
113
font_size: 33.0,
114
..default()
115
},
116
TextColor(Color::srgb(0.8, 0.2, 0.7)),
117
TextLayout::new_with_justify(Justify::Center),
118
Node {
119
max_width: px(400),
120
..default()
121
},
122
BackgroundColor(background_color),
123
));
124
125
builder.spawn((
126
Text::new("This text is left-justified and is vertically positioned to distribute the empty space equally above and below it."),
127
TextFont {
128
font: font.clone(),
129
font_size: 29.0,
130
..default()
131
},
132
TextColor(YELLOW.into()),
133
TextLayout::new_with_justify(Justify::Left),
134
Node {
135
max_width: px(300),
136
..default()
137
},
138
BackgroundColor(background_color),
139
));
140
141
builder.spawn((
142
Text::new("This text is fully justified and is positioned in the same way."),
143
TextFont {
144
font: font.clone(),
145
font_size: 29.0,
146
..default()
147
},
148
TextLayout::new_with_justify(Justify::Justified),
149
TextColor(GREEN_YELLOW.into()),
150
Node {
151
max_width: px(300),
152
..default()
153
},
154
BackgroundColor(background_color),
155
));
156
157
builder
158
.spawn((
159
Text::default(),
160
TextFont {
161
font: font.clone(),
162
font_size: 21.0,
163
..default()
164
},
165
TextChanges,
166
BackgroundColor(background_color),
167
))
168
.with_children(|p| {
169
p.spawn((
170
TextSpan::new("\nThis text changes in the bottom right"),
171
TextFont {
172
font: font.clone(),
173
font_size: 21.0,
174
..default()
175
},
176
));
177
p.spawn((
178
TextSpan::new(" this text has zero font size"),
179
TextFont {
180
font: font.clone(),
181
font_size: 0.0,
182
..default()
183
},
184
TextColor(BLUE.into()),
185
));
186
p.spawn((
187
TextSpan::new("\nThis text changes in the bottom right - "),
188
TextFont {
189
font: font.clone(),
190
font_size: 21.0,
191
..default()
192
},
193
TextColor(RED.into()),
194
));
195
p.spawn((
196
TextSpan::default(),
197
TextFont {
198
font: font.clone(),
199
font_size: 21.0,
200
..default()
201
},
202
TextColor(ORANGE_RED.into()),
203
));
204
p.spawn((
205
TextSpan::new(" fps, "),
206
TextFont {
207
font: font.clone(),
208
font_size: 10.0,
209
..default()
210
},
211
TextColor(YELLOW.into()),
212
));
213
p.spawn((
214
TextSpan::default(),
215
TextFont {
216
font: font.clone(),
217
font_size: 21.0,
218
..default()
219
},
220
TextColor(LIME.into()),
221
));
222
p.spawn((
223
TextSpan::new(" ms/frame"),
224
TextFont {
225
font: font.clone(),
226
font_size: 42.0,
227
..default()
228
},
229
TextColor(BLUE.into()),
230
));
231
p.spawn((
232
TextSpan::new(" this text has negative font size"),
233
TextFont {
234
font: font.clone(),
235
font_size: -42.0,
236
..default()
237
},
238
TextColor(BLUE.into()),
239
));
240
});
241
})
242
.id();
243
commands
244
.entity(root_uinode)
245
.add_children(&[left_column, right_column]);
246
}
247
248
fn change_text_system(
249
mut fps_history: Local<VecDeque<f64>>,
250
mut time_history: Local<VecDeque<Duration>>,
251
time: Res<Time>,
252
diagnostics: Res<DiagnosticsStore>,
253
query: Query<Entity, With<TextChanges>>,
254
mut writer: TextUiWriter,
255
) {
256
time_history.push_front(time.elapsed());
257
time_history.truncate(120);
258
let avg_fps = (time_history.len() as f64)
259
/ (time_history.front().copied().unwrap_or_default()
260
- time_history.back().copied().unwrap_or_default())
261
.as_secs_f64()
262
.max(0.0001);
263
fps_history.push_front(avg_fps);
264
fps_history.truncate(120);
265
let fps_variance = std_deviation(fps_history.make_contiguous()).unwrap_or_default();
266
267
for entity in &query {
268
let mut fps = 0.0;
269
if let Some(fps_diagnostic) = diagnostics.get(&FrameTimeDiagnosticsPlugin::FPS)
270
&& let Some(fps_smoothed) = fps_diagnostic.smoothed()
271
{
272
fps = fps_smoothed;
273
}
274
275
let mut frame_time = time.delta_secs_f64();
276
if let Some(frame_time_diagnostic) =
277
diagnostics.get(&FrameTimeDiagnosticsPlugin::FRAME_TIME)
278
&& let Some(frame_time_smoothed) = frame_time_diagnostic.smoothed()
279
{
280
frame_time = frame_time_smoothed;
281
}
282
283
*writer.text(entity, 0) =
284
format!("{avg_fps:.1} avg fps, {fps_variance:.1} frametime variance",);
285
286
*writer.text(entity, 1) = format!(
287
"\nThis text changes in the bottom right - {fps:.1} fps, {frame_time:.3} ms/frame",
288
);
289
290
*writer.text(entity, 4) = format!("{fps:.1}");
291
292
*writer.text(entity, 6) = format!("{frame_time:.3}");
293
}
294
}
295
296
fn mean(data: &[f64]) -> Option<f64> {
297
let sum = data.iter().sum::<f64>();
298
let count = data.len();
299
300
match count {
301
positive if positive > 0 => Some(sum / count as f64),
302
_ => None,
303
}
304
}
305
306
fn std_deviation(data: &[f64]) -> Option<f64> {
307
match (mean(data), data.len()) {
308
(Some(data_mean), count) if count > 0 => {
309
let variance = data
310
.iter()
311
.map(|value| {
312
let diff = data_mean - *value;
313
314
diff * diff
315
})
316
.sum::<f64>()
317
/ count as f64;
318
319
Some(variance.sqrt())
320
}
321
_ => None,
322
}
323
}
324
325