diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index 728a863b703cf..22baff082580d 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -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. @@ -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 { + 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 { @@ -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, }) } } @@ -726,16 +738,21 @@ pub fn camera_system( || 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() @@ -760,6 +777,8 @@ pub fn camera_system( 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; } } @@ -773,6 +792,7 @@ pub fn camera_system( #[derive(Component, ExtractComponent, Clone, Copy, Reflect)] #[reflect_value(Component, Default)] pub struct CameraMainTextureUsages(pub TextureUsages); + impl Default for CameraMainTextureUsages { fn default() -> Self { Self( diff --git a/crates/bevy_text/src/pipeline.rs b/crates/bevy_text/src/pipeline.rs index edd76a2de9ec0..fdbf3a8f7f5c4 100644 --- a/crates/bevy_text/src/pipeline.rs +++ b/crates/bevy_text/src/pipeline.rs @@ -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) -> FontId { let brush = &mut self.brush; diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index b9fffe39e63c7..b0ad2f2c62803 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -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::() - .register_type::(); + .register_type::() + .register_type::(); 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` // In practice, they run independently since `bevy_render::camera_update_system` diff --git a/crates/bevy_ui/src/node_bundles.rs b/crates/bevy_ui/src/node_bundles.rs index 17dddacc87253..753ff62ad23dd 100644 --- a/crates/bevy_ui/src/node_bundles.rs +++ b/crates/bevy_ui/src/node_bundles.rs @@ -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. @@ -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")] @@ -233,6 +236,7 @@ impl Default for TextBundle { z_index: Default::default(), // Transparent background background_color: BackgroundColor(Color::NONE), + text_scaling_info: TextScalingInfo::default(), } } } diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index 09ee5049781fe..37fa7dd69881d 100644 --- a/crates/bevy_ui/src/widget/text.rs +++ b/crates/bevy_ui/src/widget/text.rs @@ -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 @@ -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, fonts: Res>, - windows: Query<&Window, With>, + default_ui_camera: DefaultUiCamera, + camera_query: Query<(Entity, &Camera)>, ui_scale: Res, - mut text_query: Query<(Ref, &mut ContentSize, &mut TextFlags), With>, + mut text_query: Query< + ( + Ref, + &mut ContentSize, + &mut TextFlags, + &mut TextScalingInfo, + Option<&TargetCamera>, + ), + With, + >, ) { - 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); } } @@ -218,49 +235,25 @@ fn queue_text( #[allow(clippy::too_many_arguments)] pub fn text_system( mut textures: ResMut>, - mut last_scale_factor: Local, fonts: Res>, - windows: Query<&Window, With>, text_settings: Res, - ui_scale: Res, mut texture_atlases: ResMut>, mut font_atlas_sets: ResMut, mut text_pipeline: ResMut, - mut text_query: Query<(Ref, &Text, &mut TextLayoutInfo, &mut TextFlags)>, + mut text_query: Query<( + Ref, + &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,