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

Add component to control UI camera position #5243

Closed
wants to merge 4 commits into from
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
59 changes: 40 additions & 19 deletions crates/bevy_ui/src/entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -136,22 +136,43 @@ impl Default for ButtonBundle {
}
}
}
#[derive(Component, Clone)]
pub struct CameraUi {
pub is_enabled: bool,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this wasn't used anymore? good spot

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The meaning of CameraUi changed in #4745:

  • before: A marker component used to specify which camera was the one for rendering UI
  • after: A component to specify whether the camera it is attached to displays the UI (when not present, shows UI)

So technically still used, but for something almost opposite of what it used to.

I renamed it CameraUiConfig in #5234, because if you were previously relying on CameraUi, your game would still compile, but really do the opposite of what it was doing before.

I built on CameraUiConfig in this PR, because I thought it made sense as an API for manipulating the UI camera associated with actual 2d or 3d camera.

}

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<Camera>;

fn extract_component(item: QueryItem<Self::Query>) -> 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;
}
}
6 changes: 1 addition & 5 deletions crates/bevy_ui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand All @@ -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;
Expand All @@ -50,8 +47,7 @@ pub enum UiSystem {

impl Plugin for UiPlugin {
fn build(&self, app: &mut App) {
app.add_plugin(ExtractComponentPlugin::<CameraUi>::default())
.init_resource::<FlexSurface>()
app.init_resource::<FlexSurface>()
.register_type::<AlignContent>()
.register_type::<AlignItems>()
.register_type::<AlignSelf>()
Expand Down
50 changes: 17 additions & 33 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::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},
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,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<T: Component>(
pub fn extract_ui_camera_view<T: Component>(
mut commands: Commands,
render_world: Res<RenderWorld>,
query: Query<(Entity, &Camera, Option<&CameraUi>), With<T>>,
mut query: Query<(Entity, &Camera, Option<&mut UiCamera>), With<T>>,
) {
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,
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::CameraUi, 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 CameraUi>,
),
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 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
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
1 change: 1 addition & 0 deletions crates/bevy_ui/src/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down
31 changes: 30 additions & 1 deletion examples/ui/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<AssetServer>) {
// Camera
commands.spawn_bundle(Camera2dBundle::default());
commands
.spawn_bundle(Camera2dBundle::default())
.insert(CameraUiConfig {
show_ui: true,
..default()
});

// root node
commands
Expand Down Expand Up @@ -309,6 +315,29 @@ struct ScrollingList {
position: f32,
}

fn change_ui_camera(
mouse: Res<Input<MouseButton>>,
keyboard: Res<Input<KeyCode>>,
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<MouseWheel>,
mut query_list: Query<(&mut ScrollingList, &mut Style, &Children, &Node)>,
Expand Down