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

Support window-independent scaling for text #12705

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 21 additions & 1 deletion crates/bevy_render/src/camera/camera.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ pub struct RenderTargetInfo {
/// When rendering to a window, typically it is a value greater or equal than 1.0,
/// representing the ratio between the size of the window in physical pixels and the logical size of the window.
pub scale_factor: f32,
pub scale_factor_changed: bool,
}

/// Holds internally computed [`Camera`] values.
Expand Down Expand Up @@ -324,6 +325,14 @@ impl Camera {
self.computed.target_info.as_ref().map(|t| t.scale_factor)
}

#[inline]
pub fn target_scaling_factor_changed(&self) -> Option<bool> {
self.computed
.target_info
.as_ref()
.map(|t| t.scale_factor_changed)
}

/// The projection matrix computed using this camera's [`CameraProjection`].
#[inline]
pub fn projection_matrix(&self) -> Mat4 {
Expand Down Expand Up @@ -630,18 +639,21 @@ impl NormalizedRenderTarget {
.map(|(_, window)| RenderTargetInfo {
physical_size: window.physical_size(),
scale_factor: window.resolution.scale_factor(),
scale_factor_changed: false,
}),
NormalizedRenderTarget::Image(image_handle) => {
let image = images.get(image_handle)?;
Some(RenderTargetInfo {
physical_size: image.size(),
scale_factor: 1.0,
scale_factor_changed: false,
})
}
NormalizedRenderTarget::TextureView(id) => {
manual_texture_views.get(id).map(|tex| RenderTargetInfo {
physical_size: tex.size,
scale_factor: 1.0,
scale_factor_changed: false,
})
}
}
Expand Down Expand Up @@ -726,16 +738,21 @@ pub fn camera_system<T: CameraProjection + Component>(
|| camera_projection.is_changed()
|| camera.computed.old_viewport_size != viewport_size
{
let new_computed_target_info = normalized_target.get_render_target_info(
let mut new_computed_target_info = normalized_target.get_render_target_info(
&windows,
&images,
&manual_texture_views,
);

// Check for the scale factor changing, and resize the viewport if needed.
// This can happen when the window is moved between monitors with different DPIs.
// Without this, the viewport will take a smaller portion of the window moved to
// a higher DPI monitor.
if normalized_target.is_changed(&scale_factor_changed_window_ids, &HashSet::new()) {
if let Some(info) = new_computed_target_info.as_mut() {
info.scale_factor_changed = true;
}

if let (Some(new_scale_factor), Some(old_scale_factor)) = (
new_computed_target_info
.as_ref()
Expand All @@ -760,6 +777,8 @@ pub fn camera_system<T: CameraProjection + Component>(
camera_projection.update(size.x, size.y);
camera.computed.projection_matrix = camera_projection.get_projection_matrix();
}
} else if let Some(info) = camera.computed.target_info.as_mut() {
info.scale_factor_changed = false;
}
}

Expand All @@ -773,6 +792,7 @@ pub fn camera_system<T: CameraProjection + Component>(
#[derive(Component, ExtractComponent, Clone, Copy, Reflect)]
#[reflect_value(Component, Default)]
pub struct CameraMainTextureUsages(pub TextureUsages);

impl Default for CameraMainTextureUsages {
fn default() -> Self {
Self(
Expand Down
17 changes: 17 additions & 0 deletions crates/bevy_text/src/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,23 @@ pub struct TextLayoutInfo {
pub logical_size: Vec2,
}

#[derive(Component, Clone, Debug, Reflect)]
#[reflect(Component)]
pub struct TextScalingInfo {
pub scale_factor: f32,
pub scale_factor_changed: bool,
}

impl Default for TextScalingInfo {
#[inline]
fn default() -> Self {
Self {
scale_factor: 1.0,
scale_factor_changed: false,
}
}
}

impl TextPipeline {
pub fn get_or_insert_font_id(&mut self, handle: &Handle<Font>, font: &Font) -> FontId {
let brush = &mut self.brush;
Expand Down
6 changes: 5 additions & 1 deletion crates/bevy_ui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,14 +182,18 @@ impl Plugin for UiPlugin {
fn build_text_interop(app: &mut App) {
use crate::widget::TextFlags;
use bevy_text::TextLayoutInfo;
use bevy_text::TextScalingInfo;

app.register_type::<TextLayoutInfo>()
.register_type::<TextFlags>();
.register_type::<TextFlags>()
.register_type::<TextScalingInfo>();

app.add_systems(
PostUpdate,
(
widget::measure_text_system
// For spawning target camera on child nodes by update_target_camera to take effect.
.after(apply_deferred)
.before(UiSystem::Layout)
// Potential conflict: `Assets<Image>`
// In practice, they run independently since `bevy_render::camera_update_system`
Expand Down
6 changes: 5 additions & 1 deletion crates/bevy_ui/src/node_bundles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ use bevy_ecs::bundle::Bundle;
use bevy_render::view::{InheritedVisibility, ViewVisibility, Visibility};
use bevy_sprite::TextureAtlas;
#[cfg(feature = "bevy_text")]
use bevy_text::{BreakLineOn, JustifyText, Text, TextLayoutInfo, TextSection, TextStyle};
use bevy_text::{
BreakLineOn, JustifyText, Text, TextLayoutInfo, TextScalingInfo, TextSection, TextStyle,
};
use bevy_transform::prelude::{GlobalTransform, Transform};

/// The basic UI node.
Expand Down Expand Up @@ -212,6 +214,7 @@ pub struct TextBundle {
pub z_index: ZIndex,
/// The background color that will fill the containing node
pub background_color: BackgroundColor,
pub text_scaling_info: TextScalingInfo,
}

#[cfg(feature = "bevy_text")]
Expand All @@ -233,6 +236,7 @@ impl Default for TextBundle {
z_index: Default::default(),
// Transparent background
background_color: BackgroundColor(Color::NONE),
text_scaling_info: TextScalingInfo::default(),
}
}
}
Expand Down
115 changes: 54 additions & 61 deletions crates/bevy_ui/src/widget/text.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
use crate::{ContentSize, FixedMeasure, Measure, Node, UiScale};
use crate::{ContentSize, DefaultUiCamera, FixedMeasure, Measure, Node, TargetCamera, UiScale};
use bevy_asset::Assets;
use bevy_ecs::{
entity::Entity,
prelude::{Component, DetectChanges},
query::With,
reflect::ReflectComponent,
system::{Local, Query, Res, ResMut},
system::{Query, Res, ResMut},
world::{Mut, Ref},
};
use bevy_math::Vec2;
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::texture::Image;
use bevy_render::{camera::Camera, texture::Image};
use bevy_sprite::TextureAtlasLayout;
use bevy_text::{
scale_value, BreakLineOn, Font, FontAtlasSets, Text, TextError, TextLayoutInfo,
TextMeasureInfo, TextPipeline, TextSettings, YAxisOrientation,
TextMeasureInfo, TextPipeline, TextScalingInfo, TextSettings, YAxisOrientation,
};
use bevy_window::{PrimaryWindow, Window};
use taffy::style::AvailableSpace;

/// Text system flags
Expand Down Expand Up @@ -117,32 +117,49 @@ fn create_text_measure(
/// 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)
/// method should be called when only changing the `Text`'s colors.
pub fn measure_text_system(
mut last_scale_factor: Local<f32>,
fonts: Res<Assets<Font>>,
windows: Query<&Window, With<PrimaryWindow>>,
default_ui_camera: DefaultUiCamera,
camera_query: Query<(Entity, &Camera)>,
ui_scale: Res<UiScale>,
mut text_query: Query<(Ref<Text>, &mut ContentSize, &mut TextFlags), With<Node>>,
mut text_query: Query<
(
Ref<Text>,
&mut ContentSize,
&mut TextFlags,
&mut TextScalingInfo,
Option<&TargetCamera>,
),
With<Node>,
>,
) {
let window_scale_factor = windows
.get_single()
.map(|window| window.resolution.scale_factor())
.unwrap_or(1.);
#[allow(clippy::float_cmp)]
for (text, content_size, text_flags, mut text_scaling_info, target_camera) in &mut text_query {
let Some(camera_entity) = target_camera
.map(TargetCamera::entity)
.or(default_ui_camera.get())
else {
continue;
};

let scale_factor = ui_scale.0 * window_scale_factor;
let camera = camera_query.get(camera_entity).ok();

#[allow(clippy::float_cmp)]
if *last_scale_factor == scale_factor {
// scale factor unchanged, only create new measure funcs for modified text
for (text, content_size, text_flags) in &mut text_query {
if text.is_changed() || text_flags.needs_new_measure_func || content_size.is_added() {
create_text_measure(&fonts, scale_factor, text, content_size, text_flags);
}
}
} else {
// scale factor changed, create new measure funcs for all text
*last_scale_factor = scale_factor;
let scale_factor_changed = camera
.and_then(|(_, c)| c.target_scaling_factor_changed())
.unwrap_or(false);
let scale_factor = camera
.and_then(|(_, c)| c.target_scaling_factor())
.unwrap_or(1.0)
* ui_scale.0;

for (text, content_size, text_flags) in &mut text_query {
text_scaling_info.scale_factor = scale_factor;
text_scaling_info.scale_factor_changed = scale_factor_changed;

// Only create new measure for modified text.
if scale_factor_changed
|| text.is_changed()
|| text_flags.needs_new_measure_func
|| content_size.is_added()
{
create_text_measure(&fonts, scale_factor, text, content_size, text_flags);
}
}
Expand Down Expand Up @@ -218,49 +235,25 @@ fn queue_text(
#[allow(clippy::too_many_arguments)]
pub fn text_system(
mut textures: ResMut<Assets<Image>>,
mut last_scale_factor: Local<f32>,
fonts: Res<Assets<Font>>,
windows: Query<&Window, With<PrimaryWindow>>,
text_settings: Res<TextSettings>,
ui_scale: Res<UiScale>,
mut texture_atlases: ResMut<Assets<TextureAtlasLayout>>,
mut font_atlas_sets: ResMut<FontAtlasSets>,
mut text_pipeline: ResMut<TextPipeline>,
mut text_query: Query<(Ref<Node>, &Text, &mut TextLayoutInfo, &mut TextFlags)>,
mut text_query: Query<(
Ref<Node>,
&Text,
&mut TextLayoutInfo,
&mut TextFlags,
&TextScalingInfo,
)>,
) {
// TODO: Support window-independent scaling: https://github.com/bevyengine/bevy/issues/5621
let window_scale_factor = windows
.get_single()
.map(|window| window.resolution.scale_factor())
.unwrap_or(1.);

let scale_factor = ui_scale.0 * window_scale_factor;
let inverse_scale_factor = scale_factor.recip();
if *last_scale_factor == scale_factor {
// Scale factor unchanged, only recompute text for modified text nodes
for (node, text, text_layout_info, text_flags) in &mut text_query {
if node.is_changed() || text_flags.needs_recompute {
queue_text(
&fonts,
&mut text_pipeline,
&mut font_atlas_sets,
&mut texture_atlases,
&mut textures,
&text_settings,
scale_factor,
inverse_scale_factor,
text,
node,
text_flags,
text_layout_info,
);
}
}
} else {
// Scale factor changed, recompute text for all text nodes
*last_scale_factor = scale_factor;
for (node, text, text_layout_info, text_flags, text_scaling_info) in &mut text_query {
let scale_factor_changed = text_scaling_info.scale_factor_changed;
let scale_factor = text_scaling_info.scale_factor;
let inverse_scale_factor = scale_factor.recip();

for (node, text, text_layout_info, text_flags) in &mut text_query {
if scale_factor_changed || node.is_changed() || text_flags.needs_recompute {
queue_text(
&fonts,
&mut text_pipeline,
Expand Down
Loading