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

Mark ghost nodes as experimental and partially feature flag them #15961

Merged
merged 11 commits into from
Oct 16, 2024
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,9 @@ reflect_functions = ["bevy_internal/reflect_functions"]
# Enable winit custom cursor support
custom_cursor = ["bevy_internal/custom_cursor"]

# Experimental support for nodes that are ignored for UI layouting
ghost_nodes = ["bevy_internal/ghost_nodes"]

[dependencies]
bevy_internal = { path = "crates/bevy_internal", version = "0.15.0-dev", default-features = false }

Expand Down Expand Up @@ -3081,6 +3084,7 @@ wasm = true
name = "ghost_nodes"
path = "examples/ui/ghost_nodes.rs"
doc-scrape-examples = true
required-features = ["ghost_nodes"]

[package.metadata.example.ghost_nodes]
name = "Ghost Nodes"
Expand Down
3 changes: 3 additions & 0 deletions crates/bevy_internal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,9 @@ reflect_functions = [
# Enable winit custom cursor support
custom_cursor = ["bevy_winit/custom_cursor"]

# Experimental support for nodes that are ignored for UI layouting
ghost_nodes = ["bevy_ui/ghost_nodes"]

[dependencies]
# bevy
bevy_a11y = { path = "../bevy_a11y", version = "0.15.0-dev" }
Expand Down
3 changes: 3 additions & 0 deletions crates/bevy_ui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ default = ["bevy_ui_picking_backend"]
serialize = ["serde", "smallvec/serde", "bevy_math/serialize"]
bevy_ui_picking_backend = ["bevy_picking"]

# Experimental features
ghost_nodes = []

[lints]
workspace = true

Expand Down
3 changes: 2 additions & 1 deletion crates/bevy_ui/src/accessibility.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::{
experimental::UiChildren,
prelude::{Button, Label},
widget::TextUiReader,
Node, UiChildren, UiImage,
Node, UiImage,
};
use bevy_a11y::{
accesskit::{NodeBuilder, Rect, Role},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use bevy_hierarchy::{Children, HierarchyQueryExt, Parent};
use bevy_reflect::prelude::*;
use bevy_render::view::Visibility;
use bevy_transform::prelude::Transform;
use core::marker::PhantomData;
use smallvec::SmallVec;

use crate::Node;
Expand All @@ -14,10 +15,30 @@ use crate::Node;
/// The UI systems will traverse past these and treat their first non-ghost descendants as direct children of their first non-ghost ancestor.
///
/// Any components necessary for transform and visibility propagation will be added automatically.
#[derive(Component, Default, Debug, Copy, Clone, Reflect)]
///
/// Instances of this type cannot be constructed unless the `ghost_nodes` feature is enabled.
#[derive(Component, Debug, Copy, Clone, Reflect)]
#[cfg_attr(feature = "ghost_nodes", derive(Default))]
#[reflect(Component, Debug)]
#[require(Visibility, Transform)]
pub struct GhostNode;
pub struct GhostNode {
// This is a workaround to ensure that GhostNode is only constructable when the appropriate feature flag is enabled
#[reflect(ignore)]
unconstructable: PhantomData<()>, // Spooky!
}
alice-i-cecile marked this conversation as resolved.
Show resolved Hide resolved

#[cfg(feature = "ghost_nodes")]
impl GhostNode {
/// Creates a new ghost node.
///
/// This method is only available when the `ghost_node` feature is enabled,
/// and will eventually be deprecated then removed in favor of simply using `GhostNode` as no meaningful data is stored.
pub const fn new() -> Self {
GhostNode {
unconstructable: PhantomData,
}
}
}

/// System param that allows iteration of all UI root nodes.
///
Expand Down Expand Up @@ -140,7 +161,7 @@ impl<'w, 's> Iterator for UiChildrenIter<'w, 's> {
}
}

#[cfg(test)]
#[cfg(all(test, feature = "ghost_nodes"))]
mod tests {
use bevy_ecs::{
prelude::Component,
Expand All @@ -165,18 +186,20 @@ mod tests {
.with_children(|parent| {
parent.spawn((A(2), NodeBundle::default()));
parent
.spawn((A(3), GhostNode))
.spawn((A(3), GhostNode::new()))
.with_child((A(4), NodeBundle::default()));
});

// Ghost root
world.spawn((A(5), GhostNode)).with_children(|parent| {
parent.spawn((A(6), NodeBundle::default()));
parent
.spawn((A(7), GhostNode))
.with_child((A(8), NodeBundle::default()))
.with_child(A(9));
});
world
.spawn((A(5), GhostNode::new()))
.with_children(|parent| {
parent.spawn((A(6), NodeBundle::default()));
parent
.spawn((A(7), GhostNode::new()))
.with_child((A(8), NodeBundle::default()))
.with_child(A(9));
});

let mut system_state = SystemState::<(UiRootNodes, Query<&A>)>::new(world);
let (ui_root_nodes, a_query) = system_state.get(world);
Expand All @@ -191,15 +214,15 @@ mod tests {
let world = &mut World::new();

let n1 = world.spawn((A(1), NodeBundle::default())).id();
let n2 = world.spawn((A(2), GhostNode)).id();
let n3 = world.spawn((A(3), GhostNode)).id();
let n2 = world.spawn((A(2), GhostNode::new())).id();
let n3 = world.spawn((A(3), GhostNode::new())).id();
let n4 = world.spawn((A(4), NodeBundle::default())).id();
let n5 = world.spawn((A(5), NodeBundle::default())).id();

let n6 = world.spawn((A(6), GhostNode)).id();
let n7 = world.spawn((A(7), GhostNode)).id();
let n6 = world.spawn((A(6), GhostNode::new())).id();
let n7 = world.spawn((A(7), GhostNode::new())).id();
let n8 = world.spawn((A(8), NodeBundle::default())).id();
let n9 = world.spawn((A(9), GhostNode)).id();
let n9 = world.spawn((A(9), GhostNode::new())).id();
let n10 = world.spawn((A(10), NodeBundle::default())).id();

let no_ui = world.spawn_empty().id();
Expand Down
17 changes: 17 additions & 0 deletions crates/bevy_ui/src/experimental/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//! Experimental features are not yet stable and may change or be removed in the future.
//!
//! These features are not recommended for production use, but are available to ease experimentation
//! within Bevy's ecosystem. Please let us know how you are using these features and what you would
//! like to see improved!
//!
//! These may be feature-flagged: check the `Cargo.toml` for `bevy_ui` to see what options
//! are available.
//!
//! # Warning
//!
//! Be careful when using these features, especially in concert with third-party crates,
//! as they may not be fully supported, functional or stable.

mod ghost_hierarchy;

pub use ghost_hierarchy::*;
3 changes: 2 additions & 1 deletion crates/bevy_ui/src/layout/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::{
experimental::{UiChildren, UiRootNodes},
BorderRadius, ContentSize, DefaultUiCamera, Display, Node, Outline, OverflowAxis,
ScrollPosition, Style, TargetCamera, UiChildren, UiRootNodes, UiScale,
ScrollPosition, Style, TargetCamera, UiScale,
};
use bevy_ecs::{
change_detection::{DetectChanges, DetectChangesMut},
Expand Down
5 changes: 3 additions & 2 deletions crates/bevy_ui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,18 @@ pub mod picking_backend;
use bevy_derive::{Deref, DerefMut};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
mod accessibility;
// This module is not re-exported, but is instead made public.
// This is intended to discourage accidental use of the experimental API.
pub mod experimental;
mod focus;
mod geometry;
mod ghost_hierarchy;
mod layout;
mod render;
mod stack;
mod ui_node;

pub use focus::*;
pub use geometry::*;
pub use ghost_hierarchy::*;
pub use layout::*;
pub use measurement::*;
pub use render::*;
Expand Down
5 changes: 4 additions & 1 deletion crates/bevy_ui/src/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
use bevy_ecs::prelude::*;
use bevy_utils::HashSet;

use crate::{GlobalZIndex, Node, UiChildren, UiRootNodes, ZIndex};
use crate::{
experimental::{UiChildren, UiRootNodes},
GlobalZIndex, Node, ZIndex,
};

/// The current UI stack, which contains all UI nodes ordered by their depth (back-to-front).
///
Expand Down
5 changes: 4 additions & 1 deletion crates/bevy_ui/src/update.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
//! This module contains systems that update the UI when something changes

use crate::{CalculatedClip, Display, OverflowAxis, Style, TargetCamera, UiChildren, UiRootNodes};
use crate::{
experimental::{UiChildren, UiRootNodes},
CalculatedClip, Display, OverflowAxis, Style, TargetCamera,
};

use super::Node;
use bevy_ecs::{
Expand Down
1 change: 1 addition & 0 deletions docs/cargo_features.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ The default feature set enables most of the expected features of a game engine,
|ff|Farbfeld image format support|
|file_watcher|Enables watching the filesystem for Bevy Asset hot-reloading|
|flac|FLAC audio format support|
|ghost_nodes|Experimental support for nodes that are ignored for UI layouting|
|gif|GIF image format support|
|glam_assert|Enable assertions to check the validity of parameters passed to glam|
|ico|ICO image format support|
Expand Down
30 changes: 20 additions & 10 deletions examples/ui/ghost_nodes.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
//! This example demonstrates the use of Ghost Nodes.
//!
//! UI layout will ignore ghost nodes, and treat their children as if they were direct descendants of the first non-ghost ancestor.
//!
//! # Warning
//!
//! This is an experimental feature, and should be used with caution,
//! especially in concert with 3rd party plugins or systems that may not be aware of ghost nodes.
//!
//! To add [`GhostNode`] components to entities, you must enable the `ghost_nodes` feature flag,
//! as they are otherwise unconstructable even though the type is defined.

use bevy::{prelude::*, ui::GhostNode, winit::WinitSettings};
use bevy::{prelude::*, ui::experimental::GhostNode, winit::WinitSettings};

fn main() {
App::new()
Expand All @@ -22,14 +30,16 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2d);

// Ghost UI root
commands.spawn(GhostNode).with_children(|ghost_root| {
ghost_root
.spawn(NodeBundle::default())
.with_child(create_label(
"This text node is rendered under a ghost root",
font_handle.clone(),
));
});
commands
.spawn(GhostNode::new())
.with_children(|ghost_root| {
ghost_root
.spawn(NodeBundle::default())
.with_child(create_label(
"This text node is rendered under a ghost root",
font_handle.clone(),
));
});

// Normal UI root
commands
Expand All @@ -48,7 +58,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
.spawn((NodeBundle::default(), Counter(0)))
.with_children(|layout_parent| {
layout_parent
.spawn((GhostNode, Counter(0)))
.spawn((GhostNode::new(), Counter(0)))
.with_children(|ghost_parent| {
// Ghost children using a separate counter state
// These buttons are being treated as children of layout_parent in the context of UI
Expand Down