diff --git a/crates/bevy_ui/src/entity.rs b/crates/bevy_ui/src/entity.rs index d62575c260845..aaa331e82a350 100644 --- a/crates/bevy_ui/src/entity.rs +++ b/crates/bevy_ui/src/entity.rs @@ -2,14 +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, With}, - query::QueryItem, +use bevy_ecs::{bundle::Bundle, prelude::Component}; +use bevy_math::Vec2; +use bevy_render::{ + camera::{DepthCalculation, OrthographicProjection, WindowOrigin}, + view::Visibility, }; -use bevy_render::{camera::Camera, extract_component::ExtractComponent, view::Visibility}; use bevy_text::Text; use bevy_transform::prelude::{GlobalTransform, Transform}; @@ -136,22 +136,43 @@ impl Default for ButtonBundle { } } } -#[derive(Component, Clone)] -pub struct CameraUi { - pub is_enabled: bool, -} -impl Default for CameraUi { +/// 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, + /// The position of the UI camera in UI space. + pub position: Vec2, + pub(crate) projection: OrthographicProjection, +} +impl Default for UiCamera { fn default() -> Self { - Self { is_enabled: true } + Self { + show_ui: true, + position: Vec2::ZERO, + projection: OrthographicProjection { + far: UI_CAMERA_FAR, + window_origin: WindowOrigin::BottomLeft, + depth_calculation: DepthCalculation::ZDifference, + ..Default::default() + }, + } } } - -impl ExtractComponent for CameraUi { - type Query = &'static Self; - type Filter = With; - - fn extract_component(item: QueryItem) -> Self { - item.clone() +impl UiCamera { + /// The orthographic projection used by the UI camera. + pub fn projection(&self) -> &OrthographicProjection { + &self.projection + } + 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 135cca188eb49..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_render::extract_component::ExtractComponentPlugin; pub use flex::*; pub use focus::*; pub use geometry::*; @@ -33,8 +32,6 @@ use bevy_transform::TransformSystem; use bevy_window::ModifiesWindows; use update::{ui_z_system, update_clipping_system}; -use crate::prelude::CameraUi; - /// The basic plugin for Bevy UI #[derive(Default)] pub struct UiPlugin; @@ -50,8 +47,7 @@ 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::() diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 43469a71efcb3..5b18db18800ce 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::CameraUi, 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::*; 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,39 +218,29 @@ 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<&CameraUi>), With>, + mut query: Query<(Entity, &Camera, Option<&mut UiCamera>), With>, ) { - for (entity, camera, camera_ui) in query.iter() { - // ignore cameras with disabled ui - if let Some(&CameraUi { - is_enabled: false, .. - }) = camera_ui - { + 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; } - 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); + 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: 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 36a51b61a7c34..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::CameraUi, 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 CameraUi>, - ), - 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 let Some(&CameraUi { is_enabled: false }) = camera_ui { - 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..bb87963c923cd 100644 --- a/crates/bevy_ui/src/update.rs +++ b/crates/bevy_ui/src/update.rs @@ -10,6 +10,7 @@ use bevy_ecs::{ }; use bevy_hierarchy::{Children, Parent}; use bevy_math::Vec2; + use bevy_sprite::Rect; use bevy_transform::components::{GlobalTransform, Transform}; diff --git a/examples/ui/ui.rs b/examples/ui/ui.rs index 5746b9e7306dc..afc3d306facd8 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::Left) { + config.position.x -= 1.0; + } + if keyboard.pressed(KeyCode::Right) { + config.position.x += 1.0; + } + if keyboard.pressed(KeyCode::Up) { + config.scale *= 0.99; + } + if keyboard.pressed(KeyCode::Down) { + config.scale *= 1.01; + } + } +} fn mouse_scroll( mut mouse_wheel_events: EventReader, mut query_list: Query<(&mut ScrollingList, &mut Style, &Children, &Node)>,