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

User UI camera control #5252

Closed
wants to merge 6 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
10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1427,6 +1427,16 @@ description = "Illustrates various features of Bevy UI"
category = "UI (User Interface)"
wasm = true

[[example]]
name = "ui_camera_movement"
path = "examples/ui/ui_camera_movement.rs"

[package.metadata.example.ui_camera_movement]
name = "UI Camera control"
description = "Illustrates how to move and zoom the UI camera"
category = "UI (User Interface)"
wasm = true

# Window
[[example]]
name = "clear_color"
Expand Down
89 changes: 54 additions & 35 deletions crates/bevy_ui/src/entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@ use crate::{
widget::{Button, ImageMode},
CalculatedSize, FocusPolicy, Interaction, Node, Style, UiColor, UiImage,
};
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::Camera, extract_component::ExtractComponent, prelude::ComputedVisibility,
view::Visibility,
camera::{OrthographicProjection, WindowOrigin},
prelude::ComputedVisibility,
view::{RenderLayers, Visibility},
};
use bevy_text::{Text, TextAlignment, TextSection, TextStyle};
use bevy_transform::prelude::{GlobalTransform, Transform};
Expand All @@ -37,6 +35,8 @@ pub struct NodeBundle {
pub visibility: Visibility,
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
pub computed_visibility: ComputedVisibility,
/// The UI camera layers this node is visible in.
pub render_layers: RenderLayers,
}

/// A UI node that is an image
Expand Down Expand Up @@ -64,6 +64,8 @@ pub struct ImageBundle {
pub visibility: Visibility,
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
pub computed_visibility: ComputedVisibility,
/// The ui camera layers this image is visible in.
pub render_layers: RenderLayers,
}

/// A UI node that is text
Expand All @@ -87,6 +89,8 @@ pub struct TextBundle {
pub visibility: Visibility,
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
pub computed_visibility: ComputedVisibility,
/// The ui camera layers this text is visible in.
pub render_layers: RenderLayers,
}

impl TextBundle {
Expand Down Expand Up @@ -135,12 +139,13 @@ impl Default for TextBundle {
global_transform: Default::default(),
visibility: Default::default(),
computed_visibility: Default::default(),
render_layers: Default::default(),
}
}
}

/// A UI node that is a button
#[derive(Bundle, Clone, Debug)]
#[derive(Bundle, Clone, Debug, Default)]
pub struct ButtonBundle {
/// Describes the size of the node
pub node: Node,
Expand All @@ -164,51 +169,65 @@ pub struct ButtonBundle {
pub visibility: Visibility,
/// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering
pub computed_visibility: ComputedVisibility,
/// The ui camera layers this button is visible in.
pub render_layers: RenderLayers,
}

impl Default for ButtonBundle {
fn default() -> Self {
ButtonBundle {
button: Button,
interaction: Default::default(),
focus_policy: Default::default(),
node: Default::default(),
style: Default::default(),
color: Default::default(),
image: Default::default(),
transform: Default::default(),
global_transform: Default::default(),
visibility: Default::default(),
computed_visibility: Default::default(),
}
}
}
/// Configuration for cameras related to UI.
///
/// When a [`Camera`] doesn't have the [`UiCameraConfig`] component,
/// it will display the UI by default.
///
/// Note that the projection is available as the [`UiCameraProjection`] component,
/// and is updated in the [`update_ui_camera_projection`] system.
///
/// [`Camera`]: bevy_render::camera::Camera
#[derive(Component, Clone)]
/// [`update_ui_camera_projection`]: crate::update::update_ui_camera_projection
#[derive(Component, Debug, Clone)]
pub struct UiCameraConfig {
/// Whether to output UI to this camera view.
///
/// When a `Camera` doesn't have the [`UiCameraConfig`] component,
/// it will display the UI by default.
pub show_ui: bool,
/// The ui camera layers this camera can see.
pub ui_render_layers: RenderLayers,
/// The position of the UI camera in UI space.
pub position: Vec2,
/// The scale of this camera's UI.
pub scale: f32,
/// The window origin of the UI camera's perspective.
pub window_origin: WindowOrigin,
}

impl Default for UiCameraConfig {
fn default() -> Self {
Self { show_ui: true }
/// The projection data for the UI camera.
///
/// This is read-only, use [`UiCameraProjection::projection`]
/// to get the projection of the UI camera attached to this camera.
///
/// This component is on a [`Camera`] entity with a set UI camera.
///
/// Note that the projection is updated in the [`update_ui_camera_projection`] system.
///
/// [`Camera`]: bevy_render::camera::Camera
/// [`update_ui_camera_projection`]: crate::update::update_ui_camera_projection
#[derive(Component, Debug)]
pub struct UiCameraProjection(pub(crate) OrthographicProjection);
impl UiCameraProjection {
/// The projection of the UI camera attached to this camera.
pub fn projection(&self) -> &OrthographicProjection {
&self.0
}
}

impl ExtractComponent for UiCameraConfig {
type Query = &'static Self;
type Filter = With<Camera>;

fn extract_component(item: QueryItem<Self::Query>) -> Self {
item.clone()
impl Default for UiCameraConfig {
fn default() -> Self {
Self {
show_ui: true,
ui_render_layers: Default::default(),
position: Vec2::ZERO,
scale: 1.0,
window_origin: WindowOrigin::BottomLeft,
}
}
}
31 changes: 21 additions & 10 deletions crates/bevy_ui/src/focus.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{entity::UiCameraConfig, CalculatedClip, Node};
use crate::{entity::UiCameraConfig, prelude::UiCameraProjection, CalculatedClip, Node};
use bevy_ecs::{
entity::Entity,
prelude::Component,
Expand All @@ -12,7 +12,7 @@ use bevy_render::camera::{Camera, RenderTarget};
use bevy_render::view::ComputedVisibility;
use bevy_transform::components::GlobalTransform;
use bevy_utils::FloatOrd;
use bevy_window::Windows;
use bevy_window::{Window, Windows};
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;

Expand Down Expand Up @@ -67,7 +67,7 @@ pub struct State {
/// Entities with a hidden [`ComputedVisibility`] are always treated as released.
pub fn ui_focus_system(
mut state: Local<State>,
camera: Query<(&Camera, Option<&UiCameraConfig>)>,
camera: Query<(&Camera, Option<&UiCameraConfig>, &UiCameraProjection)>,
windows: Res<Windows>,
mouse_button_input: Res<Input<MouseButton>>,
touches_input: Res<Touches>,
Expand Down Expand Up @@ -108,20 +108,31 @@ pub fn ui_focus_system(
let is_ui_disabled =
|camera_ui| matches!(camera_ui, Some(&UiCameraConfig { show_ui: false, .. }));

let curosr_position =
|(window, cam_position, projection): (&Window, Vec2, &UiCameraProjection)| {
let position = match window.cursor_position() {
Some(pos) => pos,
None => touches_input.first_pressed_position()?,
};
// Adapt the cursor position based on UI cam position
let projection = projection.projection();
let proj_offset = Vec2::new(projection.left, projection.bottom);
Some((position + proj_offset) * projection.scale + cam_position)
};
let cursor_position = camera
.iter()
.filter(|(_, camera_ui)| !is_ui_disabled(*camera_ui))
.filter_map(|(camera, _)| {
.filter(|(_, camera_ui, _)| !is_ui_disabled(*camera_ui))
.filter_map(|(camera, ui_config, proj)| {
if let RenderTarget::Window(window_id) = camera.target {
Some(window_id)
let ui_cam_position = ui_config.map_or(Vec2::ZERO, |c| c.position);
Some((window_id, ui_cam_position, proj))
} else {
None
}
})
.filter_map(|window_id| windows.get(window_id))
.filter(|window| window.is_focused())
.find_map(|window| window.cursor_position())
.or_else(|| touches_input.first_pressed_position());
.filter_map(|(window_id, pos, proj)| windows.get(window_id).map(|w| (w, pos, proj)))
.filter(|(window, _, _)| window.is_focused())
.find_map(curosr_position);

let mut moused_over_z_sorted_nodes = node_query
.iter_mut()
Expand Down
29 changes: 23 additions & 6 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 @@ -28,11 +27,12 @@ pub mod prelude {
use bevy_app::prelude::*;
use bevy_ecs::schedule::{ParallelSystemDescriptorCoercion, SystemLabel};
use bevy_input::InputSystem;
use bevy_render::view::VisibilitySystems;
use bevy_transform::TransformSystem;
use bevy_window::ModifiesWindows;
use update::{ui_z_system, update_clipping_system};

use crate::prelude::UiCameraConfig;
use update::{
ui_z_system, update_clipping_system, update_layer_visibility, update_ui_camera_projection,
};

/// The basic plugin for Bevy UI
#[derive(Default)]
Expand All @@ -45,12 +45,19 @@ pub enum UiSystem {
Flex,
/// After this label, input interactions with UI entities have been updated for this frame
Focus,
/// Update the [`ComputedVisibility`] component of [`Node`] entities to reflect
/// their visibility in accordance to UI cameras.
///
/// [`ComputedVisibility`]: bevy_render::view::ComputedVisibility
LayerVisibility,
/// Update UI camera projection to fit changes to the viewport logical size
/// or configurated UI scale.
UiCameraProjection,
}

impl Plugin for UiPlugin {
fn build(&self, app: &mut App) {
app.add_plugin(ExtractComponentPlugin::<UiCameraConfig>::default())
.init_resource::<FlexSurface>()
app.init_resource::<FlexSurface>()
.register_type::<AlignContent>()
.register_type::<AlignItems>()
.register_type::<AlignSelf>()
Expand Down Expand Up @@ -91,13 +98,23 @@ impl Plugin for UiPlugin {
CoreStage::PostUpdate,
widget::image_node_system.before(UiSystem::Flex),
)
.add_system_to_stage(
CoreStage::PostUpdate,
update_ui_camera_projection.label(UiSystem::UiCameraProjection),
)
.add_system_to_stage(
CoreStage::PostUpdate,
flex_node_system
.label(UiSystem::Flex)
.before(TransformSystem::TransformPropagate)
.after(ModifiesWindows),
)
.add_system_to_stage(
CoreStage::PostUpdate,
update_layer_visibility
.label(UiSystem::LayerVisibility)
.after(VisibilitySystems::CheckVisibility),
)
.add_system_to_stage(
CoreStage::PostUpdate,
ui_z_system
Expand Down
Loading