Skip to content

Commit 3d9b1e4

Browse files
authored
make UI text rendering camera driven (#13697)
# Objective - Fixes #13687 ## Solution - Text rendering in UI is still dependent on the `PrimaryWIndow` - implements #10559 for text rendering There are other parts of UI that are still `PrimaryWindow` dependent, if the changes here are OK I'll apply them everywhere. I'm not a fan of the `EntityHashMap` here to hold the scale factors, but it seems the quick and easy fix ## Testing - Run example `multiple_windows` on a screen with a scale factor different than 1, close the primary window
1 parent 52215ce commit 3d9b1e4

File tree

1 file changed

+75
-60
lines changed

1 file changed

+75
-60
lines changed

crates/bevy_ui/src/widget/text.rs

+75-60
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
use crate::{ContentSize, FixedMeasure, Measure, Node, NodeMeasure, UiScale};
1+
use crate::{
2+
ContentSize, DefaultUiCamera, FixedMeasure, Measure, Node, NodeMeasure, TargetCamera, UiScale,
3+
};
24
use bevy_asset::Assets;
35
use bevy_ecs::{
6+
entity::{Entity, EntityHashMap},
47
prelude::{Component, DetectChanges},
58
query::With,
69
reflect::ReflectComponent,
@@ -9,13 +12,13 @@ use bevy_ecs::{
912
};
1013
use bevy_math::Vec2;
1114
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
12-
use bevy_render::texture::Image;
15+
use bevy_render::{camera::Camera, texture::Image};
1316
use bevy_sprite::TextureAtlasLayout;
1417
use bevy_text::{
1518
scale_value, BreakLineOn, Font, FontAtlasSets, Text, TextError, TextLayoutInfo,
1619
TextMeasureInfo, TextPipeline, TextSettings, YAxisOrientation,
1720
};
18-
use bevy_window::{PrimaryWindow, Window};
21+
use bevy_utils::Entry;
1922
use taffy::style::AvailableSpace;
2023

2124
/// Text system flags
@@ -112,41 +115,54 @@ fn create_text_measure(
112115
/// A `Measure` is used by the UI's layout algorithm to determine the appropriate amount of space
113116
/// to provide for the text given the fonts, the text itself and the constraints of the layout.
114117
///
115-
/// * All measures are regenerated if the primary window's scale factor or [`UiScale`] is changed.
118+
/// * Measures are regenerated if the target camera's scale factor (or primary window if no specific target) or [`UiScale`] is changed.
116119
/// * Changes that only modify the colors of a `Text` do not require a new `Measure`. This system
117120
/// is only able to detect that a `Text` component has changed and will regenerate the `Measure` on
118121
/// color changes. This can be expensive, particularly for large blocks of text, and the [`bypass_change_detection`](bevy_ecs::change_detection::DetectChangesMut::bypass_change_detection)
119122
/// method should be called when only changing the `Text`'s colors.
120123
pub fn measure_text_system(
121-
mut last_scale_factor: Local<f32>,
124+
mut last_scale_factors: Local<EntityHashMap<f32>>,
122125
fonts: Res<Assets<Font>>,
123-
windows: Query<&Window, With<PrimaryWindow>>,
126+
camera_query: Query<(Entity, &Camera)>,
127+
default_ui_camera: DefaultUiCamera,
124128
ui_scale: Res<UiScale>,
125-
mut text_query: Query<(Ref<Text>, &mut ContentSize, &mut TextFlags), With<Node>>,
129+
mut text_query: Query<
130+
(
131+
Ref<Text>,
132+
&mut ContentSize,
133+
&mut TextFlags,
134+
Option<&TargetCamera>,
135+
),
136+
With<Node>,
137+
>,
126138
) {
127-
let window_scale_factor = windows
128-
.get_single()
129-
.map(|window| window.resolution.scale_factor())
130-
.unwrap_or(1.);
131-
132-
let scale_factor = ui_scale.0 * window_scale_factor;
133-
134-
#[allow(clippy::float_cmp)]
135-
if *last_scale_factor == scale_factor {
136-
// scale factor unchanged, only create new measure funcs for modified text
137-
for (text, content_size, text_flags) in &mut text_query {
138-
if text.is_changed() || text_flags.needs_new_measure_func || content_size.is_added() {
139-
create_text_measure(&fonts, scale_factor, text, content_size, text_flags);
140-
}
141-
}
142-
} else {
143-
// scale factor changed, create new measure funcs for all text
144-
*last_scale_factor = scale_factor;
139+
let mut scale_factors: EntityHashMap<f32> = EntityHashMap::default();
145140

146-
for (text, content_size, text_flags) in &mut text_query {
141+
for (text, content_size, text_flags, camera) in &mut text_query {
142+
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
143+
else {
144+
continue;
145+
};
146+
let scale_factor = match scale_factors.entry(camera_entity) {
147+
Entry::Occupied(entry) => *entry.get(),
148+
Entry::Vacant(entry) => *entry.insert(
149+
camera_query
150+
.get(camera_entity)
151+
.ok()
152+
.and_then(|(_, c)| c.target_scaling_factor())
153+
.unwrap_or(1.0)
154+
* ui_scale.0,
155+
),
156+
};
157+
if last_scale_factors.get(&camera_entity) != Some(&scale_factor)
158+
|| text.is_changed()
159+
|| text_flags.needs_new_measure_func
160+
|| content_size.is_added()
161+
{
147162
create_text_measure(&fonts, scale_factor, text, content_size, text_flags);
148163
}
149164
}
165+
*last_scale_factors = scale_factors;
150166
}
151167

152168
#[allow(clippy::too_many_arguments)]
@@ -219,49 +235,47 @@ fn queue_text(
219235
#[allow(clippy::too_many_arguments)]
220236
pub fn text_system(
221237
mut textures: ResMut<Assets<Image>>,
222-
mut last_scale_factor: Local<f32>,
238+
mut last_scale_factors: Local<EntityHashMap<f32>>,
223239
fonts: Res<Assets<Font>>,
224-
windows: Query<&Window, With<PrimaryWindow>>,
240+
camera_query: Query<(Entity, &Camera)>,
241+
default_ui_camera: DefaultUiCamera,
225242
text_settings: Res<TextSettings>,
226243
ui_scale: Res<UiScale>,
227244
mut texture_atlases: ResMut<Assets<TextureAtlasLayout>>,
228245
mut font_atlas_sets: ResMut<FontAtlasSets>,
229246
mut text_pipeline: ResMut<TextPipeline>,
230-
mut text_query: Query<(Ref<Node>, &Text, &mut TextLayoutInfo, &mut TextFlags)>,
247+
mut text_query: Query<(
248+
Ref<Node>,
249+
&Text,
250+
&mut TextLayoutInfo,
251+
&mut TextFlags,
252+
Option<&TargetCamera>,
253+
)>,
231254
) {
232-
// TODO: Support window-independent scaling: https://github.com/bevyengine/bevy/issues/5621
233-
let window_scale_factor = windows
234-
.get_single()
235-
.map(|window| window.resolution.scale_factor())
236-
.unwrap_or(1.);
255+
let mut scale_factors: EntityHashMap<f32> = EntityHashMap::default();
237256

238-
let scale_factor = ui_scale.0 * window_scale_factor;
239-
let inverse_scale_factor = scale_factor.recip();
240-
if *last_scale_factor == scale_factor {
241-
// Scale factor unchanged, only recompute text for modified text nodes
242-
for (node, text, text_layout_info, text_flags) in &mut text_query {
243-
if node.is_changed() || text_flags.needs_recompute {
244-
queue_text(
245-
&fonts,
246-
&mut text_pipeline,
247-
&mut font_atlas_sets,
248-
&mut texture_atlases,
249-
&mut textures,
250-
&text_settings,
251-
scale_factor,
252-
inverse_scale_factor,
253-
text,
254-
node,
255-
text_flags,
256-
text_layout_info,
257-
);
258-
}
259-
}
260-
} else {
261-
// Scale factor changed, recompute text for all text nodes
262-
*last_scale_factor = scale_factor;
257+
for (node, text, text_layout_info, text_flags, camera) in &mut text_query {
258+
let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_ui_camera.get())
259+
else {
260+
continue;
261+
};
262+
let scale_factor = match scale_factors.entry(camera_entity) {
263+
Entry::Occupied(entry) => *entry.get(),
264+
Entry::Vacant(entry) => *entry.insert(
265+
camera_query
266+
.get(camera_entity)
267+
.ok()
268+
.and_then(|(_, c)| c.target_scaling_factor())
269+
.unwrap_or(1.0)
270+
* ui_scale.0,
271+
),
272+
};
273+
let inverse_scale_factor = scale_factor.recip();
263274

264-
for (node, text, text_layout_info, text_flags) in &mut text_query {
275+
if last_scale_factors.get(&camera_entity) != Some(&scale_factor)
276+
|| node.is_changed()
277+
|| text_flags.needs_recompute
278+
{
265279
queue_text(
266280
&fonts,
267281
&mut text_pipeline,
@@ -278,4 +292,5 @@ pub fn text_system(
278292
);
279293
}
280294
}
295+
*last_scale_factors = scale_factors;
281296
}

0 commit comments

Comments
 (0)