Skip to content

Commit

Permalink
Add component to control UI camera position
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
nicopap committed Jul 7, 2022
1 parent 9608cb9 commit 5f297a4
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 67 deletions.
45 changes: 31 additions & 14 deletions crates/bevy_ui/src/entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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<Camera>;

fn extract_component(item: QueryItem<Self::Query>) -> 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
}
}
16 changes: 11 additions & 5 deletions crates/bevy_ui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand All @@ -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;

Expand All @@ -50,8 +50,8 @@ pub enum UiSystem {

impl Plugin for UiPlugin {
fn build(&self, app: &mut App) {
app.add_plugin(ExtractComponentPlugin::<CameraUiConfig>::default())
.init_resource::<FlexSurface>()
app.init_resource::<FlexSurface>()
.register_type::<CameraUiConfig>()
.register_type::<AlignContent>()
.register_type::<AlignItems>()
.register_type::<AlignSelf>()
Expand Down Expand Up @@ -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::<Camera2d>)
.with_system(update_ui_camera_data::<Camera3d>),
)
.add_system_to_stage(
CoreStage::PostUpdate,
update_clipping_system.after(TransformSystem::TransformPropagate),
Expand Down
42 changes: 11 additions & 31 deletions crates/bevy_ui/src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -70,14 +70,8 @@ pub fn build_ui_render(app: &mut App) {
.init_resource::<ExtractedUiNodes>()
.init_resource::<DrawFunctions<TransparentUi>>()
.add_render_command::<TransparentUi, DrawUi>()
.add_system_to_stage(
RenderStage::Extract,
extract_default_ui_camera_view::<Camera2d>,
)
.add_system_to_stage(
RenderStage::Extract,
extract_default_ui_camera_view::<Camera3d>,
)
.add_system_to_stage(RenderStage::Extract, extract_ui_camera_view::<Camera2d>)
.add_system_to_stage(RenderStage::Extract, extract_ui_camera_view::<Camera3d>)
.add_system_to_stage(
RenderStage::Extract,
extract_uinodes.label(RenderUiSystem::ExtractNode),
Expand Down Expand Up @@ -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.
Expand All @@ -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<T: Component>(
pub fn extract_ui_camera_view<T: Component>(
mut commands: Commands,
render_world: Res<RenderWorld>,
query: Query<(Entity, &Camera, Option<&CameraUiConfig>), With<T>>,
query: Query<(Entity, &Camera, &UiCameraRenderInfo), With<T>>,
) {
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,
Expand Down
20 changes: 5 additions & 15 deletions crates/bevy_ui/src/render/render_pass.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::{UiBatch, UiImageBindGroups, UiMeta};
use crate::{prelude::CameraUiConfig, DefaultCameraView};
use crate::DefaultCameraView;
use bevy_ecs::{
prelude::*,
system::{lifetimeless::*, SystemParamItem},
Expand All @@ -16,14 +16,8 @@ use bevy_render::{
use bevy_utils::FloatOrd;

pub struct UiPassNode {
ui_view_query: QueryState<
(
&'static RenderPhase<TransparentUi>,
&'static ViewTarget,
Option<&'static CameraUiConfig>,
),
With<ExtractedView>,
>,
ui_view_query:
QueryState<(&'static RenderPhase<TransparentUi>, &'static ViewTarget), With<ExtractedView>>,
default_camera_view_query: QueryState<&'static DefaultCameraView>,
}

Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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"),
Expand Down
67 changes: 66 additions & 1 deletion crates/bevy_ui/src/update.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand Down Expand Up @@ -131,6 +138,64 @@ fn update_clipping(
}
}

pub fn update_ui_camera_data<T: Component>(
mut commands: Commands,
mut query: Query<
(
Entity,
&Camera,
Option<&CameraUiConfig>,
Option<&mut UiCameraRenderInfo>,
Option<ChangeTrackers<CameraUiConfig>>,
),
(With<T>, Or<(Changed<Camera>, Changed<CameraUiConfig>)>),
>,
) {
for (entity, camera, config, render_info, config_changed) in query.iter_mut() {
if matches!(config, Some(&CameraUiConfig { show_ui: false, .. })) {
commands.entity(entity).remove::<UiCameraRenderInfo>();
continue;
}
let logical_size = if let Some(logical_size) = camera.logical_viewport_size() {
logical_size
} else {
commands.entity(entity).remove::<UiCameraRenderInfo>();
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::{
Expand Down
Loading

0 comments on commit 5f297a4

Please sign in to comment.