From 9608cb94527fc7bbd11c230e5c9e439a88b61b82 Mon Sep 17 00:00:00 2001 From: Nicola Papale Date: Wed, 6 Jul 2022 20:00:30 +0200 Subject: [PATCH 1/4] Rename CameraUi In bevy 0.7, `CameraUi` was a component specifically added to cameras that display the UI. Since camera-driven rendering was merged, it actually does the opposite! This will make it difficult for current users to adapt to 0.8. To avoid unnecessary confusion, we rename `CameraUi` into `CameraUiConfig`. --- crates/bevy_ui/src/entity.rs | 20 +++++++++++++++----- crates/bevy_ui/src/lib.rs | 4 ++-- crates/bevy_ui/src/render/mod.rs | 9 +++------ crates/bevy_ui/src/render/render_pass.rs | 6 +++--- 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/crates/bevy_ui/src/entity.rs b/crates/bevy_ui/src/entity.rs index d62575c260845..8d1186855a7cb 100644 --- a/crates/bevy_ui/src/entity.rs +++ b/crates/bevy_ui/src/entity.rs @@ -136,18 +136,28 @@ impl Default for ButtonBundle { } } } +/// Configuration for cameras related to UI. +/// +/// When a [`Camera`] doesn't have the [`CameraUiConfig`] component, +/// it will display the UI by default. +/// +/// [`Camera`]: bevy_render::camera::Camera #[derive(Component, Clone)] -pub struct CameraUi { - pub is_enabled: bool, +pub struct CameraUiConfig { + /// Whether to output UI to this camera view. + /// + /// When a `Camera` doesn't have the [`CameraUiConfig`] component, + /// it will display the UI by default. + pub show_ui: bool, } -impl Default for CameraUi { +impl Default for CameraUiConfig { fn default() -> Self { - Self { is_enabled: true } + Self { show_ui: true } } } -impl ExtractComponent for CameraUi { +impl ExtractComponent for CameraUiConfig { type Query = &'static Self; type Filter = With; diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index 135cca188eb49..19778cb4e665b 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -33,7 +33,7 @@ use bevy_transform::TransformSystem; use bevy_window::ModifiesWindows; use update::{ui_z_system, update_clipping_system}; -use crate::prelude::CameraUi; +use crate::prelude::CameraUiConfig; /// The basic plugin for Bevy UI #[derive(Default)] @@ -50,7 +50,7 @@ pub enum UiSystem { impl Plugin for UiPlugin { fn build(&self, app: &mut App) { - app.add_plugin(ExtractComponentPlugin::::default()) + app.add_plugin(ExtractComponentPlugin::::default()) .init_resource::() .register_type::() .register_type::() diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 43469a71efcb3..c039cfffc7237 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -5,7 +5,7 @@ use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d}; pub use pipeline::*; pub use render_pass::*; -use crate::{prelude::CameraUi, CalculatedClip, Node, UiColor, UiImage}; +use crate::{prelude::CameraUiConfig, CalculatedClip, Node, UiColor, UiImage}; use bevy_app::prelude::*; use bevy_asset::{load_internal_asset, AssetEvent, Assets, Handle, HandleUntyped}; use bevy_ecs::prelude::*; @@ -227,14 +227,11 @@ pub struct DefaultCameraView(pub Entity); pub fn extract_default_ui_camera_view( mut commands: Commands, render_world: Res, - query: Query<(Entity, &Camera, Option<&CameraUi>), With>, + query: Query<(Entity, &Camera, Option<&CameraUiConfig>), With>, ) { for (entity, camera, camera_ui) in query.iter() { // ignore cameras with disabled ui - if let Some(&CameraUi { - is_enabled: false, .. - }) = camera_ui - { + if matches!(camera_ui, Some(&CameraUiConfig { show_ui: false, .. })) { continue; } if let (Some(logical_size), Some(physical_size)) = ( diff --git a/crates/bevy_ui/src/render/render_pass.rs b/crates/bevy_ui/src/render/render_pass.rs index 36a51b61a7c34..e4e7566163108 100644 --- a/crates/bevy_ui/src/render/render_pass.rs +++ b/crates/bevy_ui/src/render/render_pass.rs @@ -1,5 +1,5 @@ use super::{UiBatch, UiImageBindGroups, UiMeta}; -use crate::{prelude::CameraUi, DefaultCameraView}; +use crate::{prelude::CameraUiConfig, DefaultCameraView}; use bevy_ecs::{ prelude::*, system::{lifetimeless::*, SystemParamItem}, @@ -20,7 +20,7 @@ pub struct UiPassNode { ( &'static RenderPhase, &'static ViewTarget, - Option<&'static CameraUi>, + Option<&'static CameraUiConfig>, ), With, >, @@ -66,7 +66,7 @@ impl Node for UiPassNode { return Ok(()); } // Don't render UI for cameras where it is explicitly disabled - if let Some(&CameraUi { is_enabled: false }) = camera_ui { + if matches!(camera_ui, Some(&CameraUiConfig { show_ui: false })) { return Ok(()); } From 5f297a4261ed6f53cda902c4fbed4cce42dcb086 Mon Sep 17 00:00:00 2001 From: Nicola Papale Date: Thu, 7 Jul 2022 09:24:25 +0200 Subject: [PATCH 2/4] Add component to control UI camera position Extend `CameraUiConfig` to include info about the UI camera position in the "ui world". This allows fancy effects like moving UI, which was possible before the migration to camera-driven rendering. This reverts the regression caused by #4765 preventing users from moving the UI camera. --- crates/bevy_ui/src/entity.rs | 45 +++++++++++----- crates/bevy_ui/src/lib.rs | 16 ++++-- crates/bevy_ui/src/render/mod.rs | 42 ++++----------- crates/bevy_ui/src/render/render_pass.rs | 20 ++----- crates/bevy_ui/src/update.rs | 67 +++++++++++++++++++++++- examples/ui/ui.rs | 31 ++++++++++- 6 files changed, 154 insertions(+), 67 deletions(-) diff --git a/crates/bevy_ui/src/entity.rs b/crates/bevy_ui/src/entity.rs index 8d1186855a7cb..b6309377e6436 100644 --- a/crates/bevy_ui/src/entity.rs +++ b/crates/bevy_ui/src/entity.rs @@ -4,12 +4,10 @@ use crate::{ widget::{Button, ImageMode}, CalculatedSize, FocusPolicy, Interaction, Node, Style, UiColor, UiImage, }; -use bevy_ecs::{ - bundle::Bundle, - prelude::{Component, With}, - query::QueryItem, -}; -use bevy_render::{camera::Camera, extract_component::ExtractComponent, view::Visibility}; +use bevy_ecs::{bundle::Bundle, prelude::Component}; +use bevy_math::Vec2; +use bevy_reflect::Reflect; +use bevy_render::{camera::OrthographicProjection, view::Visibility}; use bevy_text::Text; use bevy_transform::prelude::{GlobalTransform, Transform}; @@ -136,32 +134,51 @@ impl Default for ButtonBundle { } } } + /// Configuration for cameras related to UI. /// /// When a [`Camera`] doesn't have the [`CameraUiConfig`] component, /// it will display the UI by default. /// /// [`Camera`]: bevy_render::camera::Camera -#[derive(Component, Clone)] +#[derive(Component, Reflect, Clone, Debug)] pub struct CameraUiConfig { /// Whether to output UI to this camera view. /// /// When a `Camera` doesn't have the [`CameraUiConfig`] component, /// it will display the UI by default. pub show_ui: bool, + /// Scale of the UI. + pub scale: f32, + /// Position of the camera compared to the UI. + pub position: Vec2, } impl Default for CameraUiConfig { fn default() -> Self { - Self { show_ui: true } + Self { + show_ui: true, + scale: 1.0, + position: Vec2::ZERO, + } } } -impl ExtractComponent for CameraUiConfig { - type Query = &'static Self; - type Filter = With; - - fn extract_component(item: QueryItem) -> Self { - item.clone() +/// Data related to the UI camera attached to this camera. +#[derive(Component, Clone, Debug)] +pub struct UiCameraRenderInfo { + pub(crate) projection: OrthographicProjection, + pub(crate) position: Vec2, + // Used to only update the data when the + pub(crate) old_logical_size: Vec2, +} +impl UiCameraRenderInfo { + /// The orthographic projection used by the UI camera. + pub fn projection(&self) -> &OrthographicProjection { + &self.projection + } + /// The position of the UI camera in UI space. + pub fn position(&self) -> &Vec2 { + &self.position } } diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index 19778cb4e665b..632e1f3522c17 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -12,7 +12,7 @@ pub mod entity; pub mod update; pub mod widget; -use bevy_render::extract_component::ExtractComponentPlugin; +use bevy_core_pipeline::{core_2d::Camera2d, prelude::Camera3d}; pub use flex::*; pub use focus::*; pub use geometry::*; @@ -27,11 +27,11 @@ pub mod prelude { use crate::Size; use bevy_app::prelude::*; -use bevy_ecs::schedule::{ParallelSystemDescriptorCoercion, SystemLabel}; +use bevy_ecs::schedule::{ParallelSystemDescriptorCoercion, SystemLabel, SystemSet}; use bevy_input::InputSystem; use bevy_transform::TransformSystem; use bevy_window::ModifiesWindows; -use update::{ui_z_system, update_clipping_system}; +use update::{ui_z_system, update_clipping_system, update_ui_camera_data}; use crate::prelude::CameraUiConfig; @@ -50,8 +50,8 @@ pub enum UiSystem { impl Plugin for UiPlugin { fn build(&self, app: &mut App) { - app.add_plugin(ExtractComponentPlugin::::default()) - .init_resource::() + app.init_resource::() + .register_type::() .register_type::() .register_type::() .register_type::() @@ -105,6 +105,12 @@ impl Plugin for UiPlugin { .after(UiSystem::Flex) .before(TransformSystem::TransformPropagate), ) + .add_system_set_to_stage( + CoreStage::PostUpdate, + SystemSet::new() + .with_system(update_ui_camera_data::) + .with_system(update_ui_camera_data::), + ) .add_system_to_stage( CoreStage::PostUpdate, update_clipping_system.after(TransformSystem::TransformPropagate), diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index c039cfffc7237..bd234c8eca9e6 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -5,14 +5,14 @@ use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d}; pub use pipeline::*; pub use render_pass::*; -use crate::{prelude::CameraUiConfig, CalculatedClip, Node, UiColor, UiImage}; +use crate::{prelude::UiCameraRenderInfo, CalculatedClip, Node, UiColor, UiImage}; use bevy_app::prelude::*; use bevy_asset::{load_internal_asset, AssetEvent, Assets, Handle, HandleUntyped}; use bevy_ecs::prelude::*; use bevy_math::{Mat4, Vec2, Vec3, Vec4Swizzles}; use bevy_reflect::TypeUuid; use bevy_render::{ - camera::{Camera, CameraProjection, DepthCalculation, OrthographicProjection, WindowOrigin}, + camera::{Camera, CameraProjection}, color::Color, render_asset::RenderAssets, render_graph::{RenderGraph, RunGraphOnViewNode, SlotInfo, SlotType}, @@ -70,14 +70,8 @@ pub fn build_ui_render(app: &mut App) { .init_resource::() .init_resource::>() .add_render_command::() - .add_system_to_stage( - RenderStage::Extract, - extract_default_ui_camera_view::, - ) - .add_system_to_stage( - RenderStage::Extract, - extract_default_ui_camera_view::, - ) + .add_system_to_stage(RenderStage::Extract, extract_ui_camera_view::) + .add_system_to_stage(RenderStage::Extract, extract_ui_camera_view::) .add_system_to_stage( RenderStage::Extract, extract_uinodes.label(RenderUiSystem::ExtractNode), @@ -215,7 +209,7 @@ pub fn extract_uinodes( /// as ui elements are "stacked on top of each other", they are within the camera's view /// and have room to grow. // TODO: Consider computing this value at runtime based on the maximum z-value. -const UI_CAMERA_FAR: f32 = 1000.0; +pub(crate) const UI_CAMERA_FAR: f32 = 1000.0; // This value is subtracted from the far distance for the camera's z-position to ensure nodes at z == 0.0 are rendered // TODO: Evaluate if we still need this. @@ -224,36 +218,22 @@ const UI_CAMERA_TRANSFORM_OFFSET: f32 = -0.1; #[derive(Component)] pub struct DefaultCameraView(pub Entity); -pub fn extract_default_ui_camera_view( +pub fn extract_ui_camera_view( mut commands: Commands, render_world: Res, - query: Query<(Entity, &Camera, Option<&CameraUiConfig>), With>, + query: Query<(Entity, &Camera, &UiCameraRenderInfo), With>, ) { for (entity, camera, camera_ui) in query.iter() { - // ignore cameras with disabled ui - if matches!(camera_ui, Some(&CameraUiConfig { show_ui: false, .. })) { - continue; - } - if let (Some(logical_size), Some(physical_size)) = ( - camera.logical_viewport_size(), - camera.physical_viewport_size(), - ) { - let mut projection = OrthographicProjection { - far: UI_CAMERA_FAR, - window_origin: WindowOrigin::BottomLeft, - depth_calculation: DepthCalculation::ZDifference, - ..Default::default() - }; - projection.update(logical_size.x, logical_size.y); + if let Some(physical_size) = camera.physical_viewport_size() { // This roundabout approach is required because spawn().id() won't work in this context let default_camera_view = render_world.entities().reserve_entity(); commands .get_or_spawn(default_camera_view) .insert(ExtractedView { - projection: projection.get_projection_matrix(), + projection: camera_ui.projection().get_projection_matrix(), transform: GlobalTransform::from_xyz( - 0.0, - 0.0, + camera_ui.position().x, + camera_ui.position().y, UI_CAMERA_FAR + UI_CAMERA_TRANSFORM_OFFSET, ), width: physical_size.x, diff --git a/crates/bevy_ui/src/render/render_pass.rs b/crates/bevy_ui/src/render/render_pass.rs index e4e7566163108..968b9ffd400e8 100644 --- a/crates/bevy_ui/src/render/render_pass.rs +++ b/crates/bevy_ui/src/render/render_pass.rs @@ -1,5 +1,5 @@ use super::{UiBatch, UiImageBindGroups, UiMeta}; -use crate::{prelude::CameraUiConfig, DefaultCameraView}; +use crate::DefaultCameraView; use bevy_ecs::{ prelude::*, system::{lifetimeless::*, SystemParamItem}, @@ -16,14 +16,8 @@ use bevy_render::{ use bevy_utils::FloatOrd; pub struct UiPassNode { - ui_view_query: QueryState< - ( - &'static RenderPhase, - &'static ViewTarget, - Option<&'static CameraUiConfig>, - ), - With, - >, + ui_view_query: + QueryState<(&'static RenderPhase, &'static ViewTarget), With>, default_camera_view_query: QueryState<&'static DefaultCameraView>, } @@ -56,7 +50,7 @@ impl Node for UiPassNode { ) -> Result<(), NodeRunError> { let input_view_entity = graph.get_input_entity(Self::IN_VIEW)?; - let (transparent_phase, target, camera_ui) = + let (transparent_phase, target) = if let Ok(result) = self.ui_view_query.get_manual(world, input_view_entity) { result } else { @@ -65,10 +59,6 @@ impl Node for UiPassNode { if transparent_phase.items.is_empty() { return Ok(()); } - // Don't render UI for cameras where it is explicitly disabled - if matches!(camera_ui, Some(&CameraUiConfig { show_ui: false })) { - return Ok(()); - } // use the "default" view entity if it is defined let view_entity = if let Ok(default_view) = self @@ -77,7 +67,7 @@ impl Node for UiPassNode { { default_view.0 } else { - input_view_entity + return Ok(()); }; let pass_descriptor = RenderPassDescriptor { label: Some("ui_pass"), diff --git a/crates/bevy_ui/src/update.rs b/crates/bevy_ui/src/update.rs index 8af7d72caca62..d6559e7d3bbd0 100644 --- a/crates/bevy_ui/src/update.rs +++ b/crates/bevy_ui/src/update.rs @@ -1,15 +1,22 @@ //! This module contains systems that update the UI when something changes -use crate::{CalculatedClip, Overflow, Style}; +use crate::{ + prelude::{CameraUiConfig, UiCameraRenderInfo}, + CalculatedClip, Overflow, Style, UI_CAMERA_FAR, +}; use super::Node; use bevy_ecs::{ entity::Entity, + prelude::{ChangeTrackers, Changed, Component, Or}, query::{With, Without}, system::{Commands, Query}, }; use bevy_hierarchy::{Children, Parent}; use bevy_math::Vec2; +use bevy_render::camera::{ + Camera, CameraProjection, DepthCalculation, OrthographicProjection, WindowOrigin, +}; use bevy_sprite::Rect; use bevy_transform::components::{GlobalTransform, Transform}; @@ -131,6 +138,64 @@ fn update_clipping( } } +pub fn update_ui_camera_data( + mut commands: Commands, + mut query: Query< + ( + Entity, + &Camera, + Option<&CameraUiConfig>, + Option<&mut UiCameraRenderInfo>, + Option>, + ), + (With, Or<(Changed, Changed)>), + >, +) { + for (entity, camera, config, render_info, config_changed) in query.iter_mut() { + if matches!(config, Some(&CameraUiConfig { show_ui: false, .. })) { + commands.entity(entity).remove::(); + continue; + } + let logical_size = if let Some(logical_size) = camera.logical_viewport_size() { + logical_size + } else { + commands.entity(entity).remove::(); + continue; + }; + // skip work if there is no changes. + if let (Some(projection), Some(config_changed)) = (&render_info, config_changed) { + if projection.old_logical_size == logical_size && !config_changed.is_changed() { + continue; + } + } + + let (view_pos, scale) = if let Some(config) = config { + (config.position, config.scale) + } else { + (Vec2::new(0.0, 0.0), 1.0) + }; + let mut new_projection = OrthographicProjection { + far: UI_CAMERA_FAR, + scale, + window_origin: WindowOrigin::BottomLeft, + depth_calculation: DepthCalculation::ZDifference, + ..Default::default() + }; + new_projection.update(logical_size.x, logical_size.y); + if let Some(mut info) = render_info { + info.projection = new_projection; + info.position = view_pos; + info.old_logical_size = logical_size; + } else { + commands.entity(entity).insert(UiCameraRenderInfo { + projection: new_projection, + position: view_pos, + old_logical_size: logical_size, + }); + } + } +} + #[cfg(test)] mod tests { use bevy_ecs::{ diff --git a/examples/ui/ui.rs b/examples/ui/ui.rs index 5746b9e7306dc..44b8c1b3ba2ea 100644 --- a/examples/ui/ui.rs +++ b/examples/ui/ui.rs @@ -13,12 +13,18 @@ fn main() { .insert_resource(WinitSettings::desktop_app()) .add_startup_system(setup) .add_system(mouse_scroll) + .add_system(change_ui_camera) .run(); } fn setup(mut commands: Commands, asset_server: Res) { // Camera - commands.spawn_bundle(Camera2dBundle::default()); + commands + .spawn_bundle(Camera2dBundle::default()) + .insert(CameraUiConfig { + show_ui: true, + ..default() + }); // root node commands @@ -309,6 +315,29 @@ struct ScrollingList { position: f32, } +fn change_ui_camera( + mouse: Res>, + keyboard: Res>, + mut ui_config: Query<&mut CameraUiConfig>, +) { + for mut config in ui_config.iter_mut() { + if mouse.just_pressed(MouseButton::Left) { + config.show_ui = !config.show_ui; + } + if keyboard.pressed(KeyCode::A) { + config.position.x -= 1.0; + } + if keyboard.pressed(KeyCode::D) { + config.position.x += 1.0; + } + if keyboard.pressed(KeyCode::W) { + config.scale *= 0.99; + } + if keyboard.pressed(KeyCode::S) { + config.scale *= 1.01; + } + } +} fn mouse_scroll( mut mouse_wheel_events: EventReader, mut query_list: Query<(&mut ScrollingList, &mut Style, &Children, &Node)>, From 8301ddcc508b50c1cc188ab69fb36323825b93ad Mon Sep 17 00:00:00 2001 From: Nicola Papale Date: Thu, 7 Jul 2022 12:56:33 +0200 Subject: [PATCH 3/4] Use arrow key in ui example --- examples/ui/ui.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/ui/ui.rs b/examples/ui/ui.rs index 44b8c1b3ba2ea..afc3d306facd8 100644 --- a/examples/ui/ui.rs +++ b/examples/ui/ui.rs @@ -324,16 +324,16 @@ fn change_ui_camera( if mouse.just_pressed(MouseButton::Left) { config.show_ui = !config.show_ui; } - if keyboard.pressed(KeyCode::A) { + if keyboard.pressed(KeyCode::Left) { config.position.x -= 1.0; } - if keyboard.pressed(KeyCode::D) { + if keyboard.pressed(KeyCode::Right) { config.position.x += 1.0; } - if keyboard.pressed(KeyCode::W) { + if keyboard.pressed(KeyCode::Up) { config.scale *= 0.99; } - if keyboard.pressed(KeyCode::S) { + if keyboard.pressed(KeyCode::Down) { config.scale *= 1.01; } } From b57f562835dcabcb90f4b3b8459708e1067d8881 Mon Sep 17 00:00:00 2001 From: Nicola Papale Date: Thu, 7 Jul 2022 15:57:29 +0200 Subject: [PATCH 4/4] Merge CameraUiRenderInfo and CameraUiConfig Having them separate caused update delay, so we remove them. --- crates/bevy_ui/src/entity.rs | 62 +++++++++++++---------------- crates/bevy_ui/src/lib.rs | 14 +------ crates/bevy_ui/src/render/mod.rs | 21 ++++++---- crates/bevy_ui/src/update.rs | 68 +------------------------------- 4 files changed, 46 insertions(+), 119 deletions(-) diff --git a/crates/bevy_ui/src/entity.rs b/crates/bevy_ui/src/entity.rs index b6309377e6436..aaa331e82a350 100644 --- a/crates/bevy_ui/src/entity.rs +++ b/crates/bevy_ui/src/entity.rs @@ -2,12 +2,14 @@ use crate::{ widget::{Button, ImageMode}, - CalculatedSize, FocusPolicy, Interaction, Node, Style, UiColor, UiImage, + CalculatedSize, FocusPolicy, Interaction, Node, Style, UiColor, UiImage, UI_CAMERA_FAR, }; use bevy_ecs::{bundle::Bundle, prelude::Component}; use bevy_math::Vec2; -use bevy_reflect::Reflect; -use bevy_render::{camera::OrthographicProjection, view::Visibility}; +use bevy_render::{ + camera::{DepthCalculation, OrthographicProjection, WindowOrigin}, + view::Visibility, +}; use bevy_text::Text; use bevy_transform::prelude::{GlobalTransform, Transform}; @@ -135,50 +137,42 @@ impl Default for ButtonBundle { } } -/// Configuration for cameras related to UI. -/// -/// When a [`Camera`] doesn't have the [`CameraUiConfig`] component, -/// it will display the UI by default. -/// -/// [`Camera`]: bevy_render::camera::Camera -#[derive(Component, Reflect, Clone, Debug)] -pub struct CameraUiConfig { - /// Whether to output UI to this camera view. - /// - /// When a `Camera` doesn't have the [`CameraUiConfig`] component, - /// it will display the UI by default. +/// Data related to the UI camera attached to this camera. +#[derive(Component, Clone, Debug)] +pub struct UiCamera { + /// Toggle whether this camera should display UI pub show_ui: bool, - /// Scale of the UI. - pub scale: f32, - /// Position of the camera compared to the UI. + /// The position of the UI camera in UI space. pub position: Vec2, + pub(crate) projection: OrthographicProjection, } - -impl Default for CameraUiConfig { +impl Default for UiCamera { fn default() -> Self { Self { show_ui: true, - scale: 1.0, position: Vec2::ZERO, + projection: OrthographicProjection { + far: UI_CAMERA_FAR, + window_origin: WindowOrigin::BottomLeft, + depth_calculation: DepthCalculation::ZDifference, + ..Default::default() + }, } } } - -/// Data related to the UI camera attached to this camera. -#[derive(Component, Clone, Debug)] -pub struct UiCameraRenderInfo { - pub(crate) projection: OrthographicProjection, - pub(crate) position: Vec2, - // Used to only update the data when the - pub(crate) old_logical_size: Vec2, -} -impl UiCameraRenderInfo { +impl UiCamera { /// The orthographic projection used by the UI camera. pub fn projection(&self) -> &OrthographicProjection { &self.projection } - /// The position of the UI camera in UI space. - pub fn position(&self) -> &Vec2 { - &self.position + pub fn set_scale(&mut self, scale: f32) { + // We can update the projection scale without running `projection.update(local_size)` + // because update is not affected by scale, unless: + // 1. window_origin = Center AND + // 2. scaling_mode is WindowSize AND + // 3. scale = 1.0 + // which is currently not possible, since projection is read-only + // and its window_origin field never set to Center. + self.projection.scale = scale; } } diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index 632e1f3522c17..b5641ebdea1cf 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -12,7 +12,6 @@ pub mod entity; pub mod update; pub mod widget; -use bevy_core_pipeline::{core_2d::Camera2d, prelude::Camera3d}; pub use flex::*; pub use focus::*; pub use geometry::*; @@ -27,13 +26,11 @@ pub mod prelude { use crate::Size; use bevy_app::prelude::*; -use bevy_ecs::schedule::{ParallelSystemDescriptorCoercion, SystemLabel, SystemSet}; +use bevy_ecs::schedule::{ParallelSystemDescriptorCoercion, SystemLabel}; use bevy_input::InputSystem; use bevy_transform::TransformSystem; use bevy_window::ModifiesWindows; -use update::{ui_z_system, update_clipping_system, update_ui_camera_data}; - -use crate::prelude::CameraUiConfig; +use update::{ui_z_system, update_clipping_system}; /// The basic plugin for Bevy UI #[derive(Default)] @@ -51,7 +48,6 @@ pub enum UiSystem { impl Plugin for UiPlugin { fn build(&self, app: &mut App) { app.init_resource::() - .register_type::() .register_type::() .register_type::() .register_type::() @@ -105,12 +101,6 @@ impl Plugin for UiPlugin { .after(UiSystem::Flex) .before(TransformSystem::TransformPropagate), ) - .add_system_set_to_stage( - CoreStage::PostUpdate, - SystemSet::new() - .with_system(update_ui_camera_data::) - .with_system(update_ui_camera_data::), - ) .add_system_to_stage( CoreStage::PostUpdate, update_clipping_system.after(TransformSystem::TransformPropagate), diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index bd234c8eca9e6..5b18db18800ce 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -5,7 +5,7 @@ use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d}; pub use pipeline::*; pub use render_pass::*; -use crate::{prelude::UiCameraRenderInfo, CalculatedClip, Node, UiColor, UiImage}; +use crate::{prelude::UiCamera, CalculatedClip, Node, UiColor, UiImage}; use bevy_app::prelude::*; use bevy_asset::{load_internal_asset, AssetEvent, Assets, Handle, HandleUntyped}; use bevy_ecs::prelude::*; @@ -221,19 +221,26 @@ pub struct DefaultCameraView(pub Entity); pub fn extract_ui_camera_view( mut commands: Commands, render_world: Res, - query: Query<(Entity, &Camera, &UiCameraRenderInfo), With>, + mut query: Query<(Entity, &Camera, Option<&mut UiCamera>), With>, ) { - for (entity, camera, camera_ui) in query.iter() { - if let Some(physical_size) = camera.physical_viewport_size() { + for (entity, camera, camera_ui) in query.iter_mut() { + let mut camera_ui = camera_ui.as_deref().cloned().unwrap_or_default(); + if !camera_ui.show_ui { + continue; + } + let physical = camera.physical_viewport_size(); + let logical = camera.logical_viewport_size(); + if let (Some(physical_size), Some(logical_size)) = (physical, logical) { // This roundabout approach is required because spawn().id() won't work in this context let default_camera_view = render_world.entities().reserve_entity(); + camera_ui.projection.update(logical_size.x, logical_size.y); commands .get_or_spawn(default_camera_view) .insert(ExtractedView { - projection: camera_ui.projection().get_projection_matrix(), + projection: camera_ui.projection.get_projection_matrix(), transform: GlobalTransform::from_xyz( - camera_ui.position().x, - camera_ui.position().y, + camera_ui.position.x, + camera_ui.position.y, UI_CAMERA_FAR + UI_CAMERA_TRANSFORM_OFFSET, ), width: physical_size.x, diff --git a/crates/bevy_ui/src/update.rs b/crates/bevy_ui/src/update.rs index d6559e7d3bbd0..bb87963c923cd 100644 --- a/crates/bevy_ui/src/update.rs +++ b/crates/bevy_ui/src/update.rs @@ -1,22 +1,16 @@ //! This module contains systems that update the UI when something changes -use crate::{ - prelude::{CameraUiConfig, UiCameraRenderInfo}, - CalculatedClip, Overflow, Style, UI_CAMERA_FAR, -}; +use crate::{CalculatedClip, Overflow, Style}; use super::Node; use bevy_ecs::{ entity::Entity, - prelude::{ChangeTrackers, Changed, Component, Or}, query::{With, Without}, system::{Commands, Query}, }; use bevy_hierarchy::{Children, Parent}; use bevy_math::Vec2; -use bevy_render::camera::{ - Camera, CameraProjection, DepthCalculation, OrthographicProjection, WindowOrigin, -}; + use bevy_sprite::Rect; use bevy_transform::components::{GlobalTransform, Transform}; @@ -138,64 +132,6 @@ fn update_clipping( } } -pub fn update_ui_camera_data( - mut commands: Commands, - mut query: Query< - ( - Entity, - &Camera, - Option<&CameraUiConfig>, - Option<&mut UiCameraRenderInfo>, - Option>, - ), - (With, Or<(Changed, Changed)>), - >, -) { - for (entity, camera, config, render_info, config_changed) in query.iter_mut() { - if matches!(config, Some(&CameraUiConfig { show_ui: false, .. })) { - commands.entity(entity).remove::(); - continue; - } - let logical_size = if let Some(logical_size) = camera.logical_viewport_size() { - logical_size - } else { - commands.entity(entity).remove::(); - continue; - }; - // skip work if there is no changes. - if let (Some(projection), Some(config_changed)) = (&render_info, config_changed) { - if projection.old_logical_size == logical_size && !config_changed.is_changed() { - continue; - } - } - - let (view_pos, scale) = if let Some(config) = config { - (config.position, config.scale) - } else { - (Vec2::new(0.0, 0.0), 1.0) - }; - let mut new_projection = OrthographicProjection { - far: UI_CAMERA_FAR, - scale, - window_origin: WindowOrigin::BottomLeft, - depth_calculation: DepthCalculation::ZDifference, - ..Default::default() - }; - new_projection.update(logical_size.x, logical_size.y); - if let Some(mut info) = render_info { - info.projection = new_projection; - info.position = view_pos; - info.old_logical_size = logical_size; - } else { - commands.entity(entity).insert(UiCameraRenderInfo { - projection: new_projection, - position: view_pos, - old_logical_size: logical_size, - }); - } - } -} - #[cfg(test)] mod tests { use bevy_ecs::{