Skip to content

Commit

Permalink
Initial port of bevy_ui_navigation to bevy
Browse files Browse the repository at this point in the history
  • Loading branch information
nicopap committed Jul 19, 2022
1 parent 44e9cd4 commit 8c7efe5
Show file tree
Hide file tree
Showing 23 changed files with 3,249 additions and 23 deletions.
43 changes: 43 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1427,6 +1427,49 @@ description = "Illustrates various features of Bevy UI"
category = "UI (User Interface)"
wasm = true


# UI navigation
[[example]]
name = "locking"
path = "examples/ui_navigation/locking.rs"

[package.metadata.example.locking]
name = "UI locking"
description = "Illustrates UI locking"
category = "UI navigation"
wasm = true

[[example]]
name = "menu_navigation"
path = "examples/ui_navigation/menu_navigation.rs"

[package.metadata.example.menu_navigation]
name = "menu navigation"
description = "Illustrates movement between menus"
category = "UI navigation"
wasm = true

[[example]]
name = "nav_3d"
path = "examples/ui_navigation/nav_3d.rs"

[package.metadata.example.nav_3d]
name = "UI in 3d world"
description = "Illustrates using Focusable on 3d entities"
category = "UI navigation"
wasm = true

[[example]]
name = "off_screen_focusables"
path = "examples/ui_navigation/off_screen_focusables.rs"

[package.metadata.example.off_screen_focusables]
name = "Focusable wrapping"
description = "Illustrates wrapping within a menu"
category = "UI navigation"
wasm = true


# Window
[[example]]
name = "clear_color"
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_internal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ bevy_dynamic_plugin = { path = "../bevy_dynamic_plugin", optional = true, versio
bevy_sprite = { path = "../bevy_sprite", optional = true, version = "0.8.0-dev" }
bevy_text = { path = "../bevy_text", optional = true, version = "0.8.0-dev" }
bevy_ui = { path = "../bevy_ui", optional = true, version = "0.8.0-dev" }
bevy_ui_navigation = { path = "../bevy_ui_navigation", optional = true, version = "0.8.0-dev" }
bevy_winit = { path = "../bevy_winit", optional = true, version = "0.8.0-dev" }
bevy_gilrs = { path = "../bevy_gilrs", optional = true, version = "0.8.0-dev" }

Expand Down
6 changes: 6 additions & 0 deletions crates/bevy_internal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,12 @@ pub mod ui {
pub use bevy_ui::*;
}

#[cfg(feature = "bevy_ui")]
pub mod ui_navigation {
//! User interface navigation.
pub use bevy_ui_navigation::*;
}

#[cfg(feature = "bevy_winit")]
pub mod winit {
//! Window creation, configuration, and handling
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_ui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ bevy_sprite = { path = "../bevy_sprite", version = "0.8.0-dev" }
bevy_text = { path = "../bevy_text", version = "0.8.0-dev" }
bevy_transform = { path = "../bevy_transform", version = "0.8.0-dev" }
bevy_window = { path = "../bevy_window", version = "0.8.0-dev" }
bevy_ui_navigation = { path = "../bevy_ui_navigation", version = "0.8.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.8.0-dev" }

# other
Expand Down
26 changes: 4 additions & 22 deletions crates/bevy_ui/src/entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use bevy_render::{
};
use bevy_text::Text;
use bevy_transform::prelude::{GlobalTransform, Transform};
use bevy_ui_navigation::Focusable;

/// The basic UI node
#[derive(Bundle, Clone, Debug, Default)]
Expand Down Expand Up @@ -106,18 +107,16 @@ impl Default for TextBundle {
}

/// 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,
/// Marker component that signals this node is a button
pub button: Button,
/// Describes the style including flexbox settings
pub style: Style,
/// Describes whether and how the button has been interacted with by the input
pub interaction: Interaction,
/// Whether this node should block interaction with lower nodes
pub focus_policy: FocusPolicy,
/// How this button fits with other ones
pub focusable: Focusable,
/// The color of the node
pub color: UiColor,
/// The image of the node
Expand All @@ -132,23 +131,6 @@ pub struct ButtonBundle {
pub computed_visibility: ComputedVisibility,
}

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,
Expand Down
3 changes: 2 additions & 1 deletion crates/bevy_ui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
//! Spawn UI elements with [`entity::ButtonBundle`], [`entity::ImageBundle`], [`entity::TextBundle`] and [`entity::NodeBundle`]
//! This UI is laid out with the Flexbox paradigm (see <https://cssreference.io/flexbox/> ) except the vertical axis is inverted
mod flex;
mod focus;
// mod focus;
mod geometry;
mod navigation;
mod render;
mod ui_node;

Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_ui/src/navigation/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
mod queries;
mod systems;
105 changes: 105 additions & 0 deletions crates/bevy_ui/src/navigation/queries.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#[derive(Debug, Clone, Copy)]
pub struct Rect {
pub min: Vec2,
pub max: Vec2,
}
/// Camera modifiers for movement cycling.
///
/// This is only used by the cycling routine to find [`Focusable`]s at the
/// opposite side of the screen. It's expected to contain the ui camera
/// projection screen boundaries and position. See implementation of
/// [`systems::update_boundaries`](crate::systems::update_boundaries) to
/// see how to implement it yourself.
///
/// # Note
///
/// This is a [resource](Res). It is optional and will log a warning if
/// a cycling request is made and it does not exist.
#[derive(Debug)]
pub struct ScreenBoundaries {
pub position: Vec2,
pub screen_edge: Rect,
pub scale: f32,
}

/// System parameter for the default cursor navigation system.
///
/// It uses the bevy [`GlobalTransform`] to compute relative positions
/// and change focus to the correct entity.
/// It uses the [`ScreenBoundaries`] resource to compute screen boundaries
/// and move the cursor accordingly when it reaches a screen border
/// in a cycling menu.
#[derive(SystemParam)]
pub struct UiProjectionQuery<'w, 's> {
boundaries: Option<Res<'w, ScreenBoundaries>>,
transforms: Query<'w, 's, &'static GlobalTransform>,
}
impl<'w, 's> MoveParam for UiProjectionQuery<'w, 's> {
fn resolve_2d<'a>(
&self,
focused: Entity,
direction: events::Direction,
cycles: bool,
siblings: &'a [Entity],
) -> Option<&'a Entity> {
use events::Direction::*;

let pos_of = |entity: Entity| {
self.transforms
.get(entity)
.expect("Focusable entities must have a GlobalTransform component")
.translation()
.xy()
};
let focused_pos = pos_of(focused);
let closest = siblings.iter().filter(|sibling| {
direction.is_in(focused_pos, pos_of(**sibling)) && **sibling != focused
});
let closest = max_by_in_iter(closest, |s| -focused_pos.distance_squared(pos_of(**s)));
match (closest, self.boundaries.as_ref()) {
(None, None) if cycles => {
warn!(
"Tried to move in {direction:?} from Focusable {focused:?} while no other \
Focusables were there. There were no `Res<ScreenBoundaries>`, so we couldn't \
compute the screen edges for cycling. Make sure you either add the \
bevy_ui_navigation::systems::update_boundaries system to your app or implement \
your own routine to manage a `Res<ScreenBoundaries>`."
);
None
}
(None, Some(boundaries)) if cycles => {
let (x, y) = (boundaries.position.x, boundaries.position.y);
let edge = boundaries.screen_edge;
let scale = boundaries.scale;
let focused_pos = match direction {
// NOTE: up/down axises are inverted in bevy
North => Vec2::new(focused_pos.x, y - scale * edge.min.y),
South => Vec2::new(focused_pos.x, y + scale * edge.max.y),
East => Vec2::new(x - edge.min.x * scale, focused_pos.y),
West => Vec2::new(x + edge.max.x * scale, focused_pos.y),
};
max_by_in_iter(siblings.iter(), |s| {
-focused_pos.distance_squared(pos_of(**s))
})
}
(anyelse, _) => anyelse,
}
}
}

pub type NavigationPlugin<'w, 's> = GenericNavigationPlugin<UiProjectionQuery<'w, 's>>;

/// The navigation plugin and the default input scheme.
///
/// Add it to your app with `.add_plugins(DefaultNavigationPlugins)`.
///
/// This provides default implementations for input handling, if you want
/// your own custom input handling, you should use [`NavigationPlugin`] and
/// provide your own input handling systems.
pub struct DefaultNavigationPlugins;
impl PluginGroup for DefaultNavigationPlugins {
fn build(&mut self, group: &mut bevy::app::PluginGroupBuilder) {
group.add(GenericNavigationPlugin::<UiProjectionQuery>::new());
group.add(DefaultNavigationSystems);
}
}
Loading

0 comments on commit 8c7efe5

Please sign in to comment.