From c6c5d83b2a7ba005d8fa990ee493907a183f8768 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 16 Oct 2024 14:55:44 -0400 Subject: [PATCH 01/11] Move ghost node code into `experimental` module --- crates/bevy_ui/src/accessibility.rs | 3 ++- crates/bevy_ui/src/{ => experimental}/ghost_hierarchy.rs | 0 crates/bevy_ui/src/experimental/mod.rs | 3 +++ crates/bevy_ui/src/layout/mod.rs | 3 ++- crates/bevy_ui/src/lib.rs | 5 +++-- crates/bevy_ui/src/stack.rs | 5 ++++- crates/bevy_ui/src/update.rs | 5 ++++- examples/ui/ghost_nodes.rs | 2 +- 8 files changed, 19 insertions(+), 7 deletions(-) rename crates/bevy_ui/src/{ => experimental}/ghost_hierarchy.rs (100%) create mode 100644 crates/bevy_ui/src/experimental/mod.rs diff --git a/crates/bevy_ui/src/accessibility.rs b/crates/bevy_ui/src/accessibility.rs index 2a5944f40a371..0e6b1780aaedf 100644 --- a/crates/bevy_ui/src/accessibility.rs +++ b/crates/bevy_ui/src/accessibility.rs @@ -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}, diff --git a/crates/bevy_ui/src/ghost_hierarchy.rs b/crates/bevy_ui/src/experimental/ghost_hierarchy.rs similarity index 100% rename from crates/bevy_ui/src/ghost_hierarchy.rs rename to crates/bevy_ui/src/experimental/ghost_hierarchy.rs diff --git a/crates/bevy_ui/src/experimental/mod.rs b/crates/bevy_ui/src/experimental/mod.rs new file mode 100644 index 0000000000000..74f9860db47d6 --- /dev/null +++ b/crates/bevy_ui/src/experimental/mod.rs @@ -0,0 +1,3 @@ +mod ghost_hierarchy; + +pub use ghost_hierarchy::*; diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index b81e3fe09cea6..7f191fcf1b35b 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -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}, diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index 9ac4087e75293..1da41b7ab5189 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -23,9 +23,11 @@ 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; @@ -33,7 +35,6 @@ mod ui_node; pub use focus::*; pub use geometry::*; -pub use ghost_hierarchy::*; pub use layout::*; pub use measurement::*; pub use render::*; diff --git a/crates/bevy_ui/src/stack.rs b/crates/bevy_ui/src/stack.rs index a8d535fa2ff27..a89add3d6fa1c 100644 --- a/crates/bevy_ui/src/stack.rs +++ b/crates/bevy_ui/src/stack.rs @@ -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). /// diff --git a/crates/bevy_ui/src/update.rs b/crates/bevy_ui/src/update.rs index 22647e25023d0..e2d878d39e747 100644 --- a/crates/bevy_ui/src/update.rs +++ b/crates/bevy_ui/src/update.rs @@ -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::{ diff --git a/examples/ui/ghost_nodes.rs b/examples/ui/ghost_nodes.rs index 99ff0956bc3a8..7de7ba17e2576 100644 --- a/examples/ui/ghost_nodes.rs +++ b/examples/ui/ghost_nodes.rs @@ -2,7 +2,7 @@ //! //! UI layout will ignore ghost nodes, and treat their children as if they were direct descendants of the first non-ghost ancestor. -use bevy::{prelude::*, ui::GhostNode, winit::WinitSettings}; +use bevy::{prelude::*, ui::experimental::GhostNode, winit::WinitSettings}; fn main() { App::new() From 05c3ba43787dd3105357ccbcf4172354b784e08f Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 16 Oct 2024 14:58:21 -0400 Subject: [PATCH 02/11] Add module docs for experimental features --- crates/bevy_ui/src/experimental/mod.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/crates/bevy_ui/src/experimental/mod.rs b/crates/bevy_ui/src/experimental/mod.rs index 74f9860db47d6..a8f2740c30424 100644 --- a/crates/bevy_ui/src/experimental/mod.rs +++ b/crates/bevy_ui/src/experimental/mod.rs @@ -1,3 +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::*; From ef4e6d1959dddc07e5be3dd843177709c26de6d3 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 16 Oct 2024 15:03:22 -0400 Subject: [PATCH 03/11] Add a spooky field to GhostNode --- .../src/experimental/ghost_hierarchy.rs | 47 +++++++++++++------ examples/ui/ghost_nodes.rs | 20 ++++---- 2 files changed, 44 insertions(+), 23 deletions(-) diff --git a/crates/bevy_ui/src/experimental/ghost_hierarchy.rs b/crates/bevy_ui/src/experimental/ghost_hierarchy.rs index ad5a88948052a..5cfa6d18e84bf 100644 --- a/crates/bevy_ui/src/experimental/ghost_hierarchy.rs +++ b/crates/bevy_ui/src/experimental/ghost_hierarchy.rs @@ -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; @@ -17,7 +18,23 @@ use crate::Node; #[derive(Component, Default, Debug, Copy, Clone, Reflect)] #[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! +} + +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 fn new() -> Self { + GhostNode { + unconstructable: PhantomData, + } + } +} /// System param that allows iteration of all UI root nodes. /// @@ -165,18 +182,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); @@ -191,15 +210,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(); diff --git a/examples/ui/ghost_nodes.rs b/examples/ui/ghost_nodes.rs index 7de7ba17e2576..d1db59fd6fc09 100644 --- a/examples/ui/ghost_nodes.rs +++ b/examples/ui/ghost_nodes.rs @@ -22,14 +22,16 @@ fn setup(mut commands: Commands, asset_server: Res) { 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 @@ -48,7 +50,7 @@ fn setup(mut commands: Commands, asset_server: Res) { .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 From 8191c214e89940a39894d5a6dfc73e5004bf2cdf Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 16 Oct 2024 15:12:57 -0400 Subject: [PATCH 04/11] Feature flag the constructors --- Cargo.toml | 4 ++++ crates/bevy_internal/Cargo.toml | 3 +++ crates/bevy_ui/Cargo.toml | 3 +++ crates/bevy_ui/src/experimental/ghost_hierarchy.rs | 8 ++++++-- 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 42f7ccf41ad57..04990d8aed279 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 } @@ -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" diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index 2f077379ed970..62f9bc2a10669 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -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" } diff --git a/crates/bevy_ui/Cargo.toml b/crates/bevy_ui/Cargo.toml index 87304ac959c09..cc6098b02fed5 100644 --- a/crates/bevy_ui/Cargo.toml +++ b/crates/bevy_ui/Cargo.toml @@ -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 diff --git a/crates/bevy_ui/src/experimental/ghost_hierarchy.rs b/crates/bevy_ui/src/experimental/ghost_hierarchy.rs index 5cfa6d18e84bf..98fc6801d99ed 100644 --- a/crates/bevy_ui/src/experimental/ghost_hierarchy.rs +++ b/crates/bevy_ui/src/experimental/ghost_hierarchy.rs @@ -15,7 +15,10 @@ 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 { @@ -24,6 +27,7 @@ pub struct GhostNode { unconstructable: PhantomData<()>, // Spooky! } +#[cfg(feature = "ghost_nodes")] impl GhostNode { /// Creates a new ghost node. /// @@ -157,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, From 59f7045880c63a7b4cbd4b716a0d4a25c8731dca Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 16 Oct 2024 15:14:21 -0400 Subject: [PATCH 05/11] Add warning to example --- examples/ui/ghost_nodes.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/examples/ui/ghost_nodes.rs b/examples/ui/ghost_nodes.rs index d1db59fd6fc09..3f68f8559bff7 100644 --- a/examples/ui/ghost_nodes.rs +++ b/examples/ui/ghost_nodes.rs @@ -1,6 +1,14 @@ //! 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::experimental::GhostNode, winit::WinitSettings}; From 68d9c56b7a11515c71d1fcf99ac036308528a851 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 16 Oct 2024 15:41:23 -0400 Subject: [PATCH 06/11] Doc links --- examples/ui/ghost_nodes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ui/ghost_nodes.rs b/examples/ui/ghost_nodes.rs index 3f68f8559bff7..c88650e45da78 100644 --- a/examples/ui/ghost_nodes.rs +++ b/examples/ui/ghost_nodes.rs @@ -7,7 +7,7 @@ //! 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, +//! 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::experimental::GhostNode, winit::WinitSettings}; From 0b7ad7fef4075a0fc8cb849bf56cf2e6350a25e1 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 16 Oct 2024 16:53:09 -0400 Subject: [PATCH 07/11] Unbalanced backticks --- examples/ui/ghost_nodes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ui/ghost_nodes.rs b/examples/ui/ghost_nodes.rs index c88650e45da78..0460c4c30ab2e 100644 --- a/examples/ui/ghost_nodes.rs +++ b/examples/ui/ghost_nodes.rs @@ -7,7 +7,7 @@ //! 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, +//! 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::experimental::GhostNode, winit::WinitSettings}; From e76178a60b6a4941967998aa2098c25f7218ef7e Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 16 Oct 2024 16:53:37 -0400 Subject: [PATCH 08/11] Make new constructor const --- crates/bevy_ui/src/experimental/ghost_hierarchy.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ui/src/experimental/ghost_hierarchy.rs b/crates/bevy_ui/src/experimental/ghost_hierarchy.rs index 98fc6801d99ed..067952e9f1a0f 100644 --- a/crates/bevy_ui/src/experimental/ghost_hierarchy.rs +++ b/crates/bevy_ui/src/experimental/ghost_hierarchy.rs @@ -33,7 +33,7 @@ impl GhostNode { /// /// 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 fn new() -> Self { + pub const fn new() -> Self { GhostNode { unconstructable: PhantomData, } From 2a6938fdf84a5fe79b1619018e8db84d4ef34d1a Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 16 Oct 2024 17:07:31 -0400 Subject: [PATCH 09/11] Update cargo_features.md --- docs/cargo_features.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/cargo_features.md b/docs/cargo_features.md index 527ef205f0ee7..5fb465fd33989 100644 --- a/docs/cargo_features.md +++ b/docs/cargo_features.md @@ -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| From 45f6aae045a46ed98cd2093d0f2e1847bd070110 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 16 Oct 2024 17:11:22 -0400 Subject: [PATCH 10/11] Spooky tuple strats Co-authored-by: Zachary Harrold --- crates/bevy_ui/src/experimental/ghost_hierarchy.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ui/src/experimental/ghost_hierarchy.rs b/crates/bevy_ui/src/experimental/ghost_hierarchy.rs index 067952e9f1a0f..e2176d644aef7 100644 --- a/crates/bevy_ui/src/experimental/ghost_hierarchy.rs +++ b/crates/bevy_ui/src/experimental/ghost_hierarchy.rs @@ -21,11 +21,12 @@ use crate::Node; #[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 + #[cfg(not(feature = "ghost_nodes"))] #[reflect(ignore)] unconstructable: PhantomData<()>, // Spooky! -} +); #[cfg(feature = "ghost_nodes")] impl GhostNode { From 508a7b1c454efac55c4b8d7779b92fe094de7b11 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 16 Oct 2024 17:13:29 -0400 Subject: [PATCH 11/11] Revert "Spooky tuple strats" This reverts commit 45f6aae045a46ed98cd2093d0f2e1847bd070110. --- crates/bevy_ui/src/experimental/ghost_hierarchy.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ui/src/experimental/ghost_hierarchy.rs b/crates/bevy_ui/src/experimental/ghost_hierarchy.rs index e2176d644aef7..067952e9f1a0f 100644 --- a/crates/bevy_ui/src/experimental/ghost_hierarchy.rs +++ b/crates/bevy_ui/src/experimental/ghost_hierarchy.rs @@ -21,12 +21,11 @@ use crate::Node; #[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 - #[cfg(not(feature = "ghost_nodes"))] #[reflect(ignore)] unconstructable: PhantomData<()>, // Spooky! -); +} #[cfg(feature = "ghost_nodes")] impl GhostNode {