Skip to main content

ghost_nodes/
ghost_nodes.rs

1//! This example demonstrates the use of Ghost Nodes.
2//!
3//! UI layout will ignore ghost nodes, and treat their children as if they were direct descendants of the first non-ghost ancestor.
4//!
5//! # Warning
6//!
7//! This is an experimental feature, and should be used with caution,
8//! especially in concert with 3rd party plugins or systems that may not be aware of ghost nodes.
9//!
10//! In order to use [`GhostNode`]s you must enable the `ghost_nodes` feature flag.
11
12use bevy::{
13    prelude::*,
14    text::FontSourceTemplate,
15    ui::experimental::GhostNode,
16    ui_widgets::{Activate, Button},
17};
18
19fn main() {
20    App::new()
21        .add_plugins(DefaultPlugins)
22        .add_systems(Startup, scene.spawn())
23        .run();
24}
25
26#[derive(Component, Clone, Default)]
27struct Counter(i32);
28
29fn scene() -> impl SceneList {
30    bsn_list! {
31        Camera2d
32        --
33        @ghost_root()
34        --
35        @normal_root()
36    }
37}
38
39/// Ghost UI root
40fn ghost_root() -> impl Scene {
41    bsn! {
42        GhostNode
43        Children [
44            Node
45            Children [ @label("This text node is rendered under a ghost root") ]
46        ]
47    }
48}
49
50/// Normal UI root
51fn normal_root() -> impl Scene {
52    bsn! {
53        Node {
54            width: percent(100),
55            height: percent(100),
56            align_items: AlignItems::Center,
57            justify_content: JustifyContent::Center,
58        }
59        Children [
60            Node Counter(0)
61            Children [
62                // Ghost children using a separate counter state.
63                // These buttons are being treated as children of the layout parent
64                // in the context of UI, but they share the ghost node's counter.
65                GhostNode Counter(0)
66                Children [ @button() -- @button() ]
67                --
68                // A normal child using the layout parent counter
69                @button()
70            ]
71        ]
72    }
73}
74
75fn button() -> impl Scene {
76    bsn! {
77        Button
78        // Bump the counter belonging to this button's parent, then refresh every
79        // label under that parent. Buttons sharing a parent share a counter, so
80        // pressing either ghost child updates both of their labels.
81        on(|activate: On<Activate>,
82            child_of_query: Query<&ChildOf>,
83            children_query: Query<&Children>,
84            mut counter_query: Query<&mut Counter>,
85            mut text_query: Query<&mut Text>| {
86            let Ok(parent) = child_of_query.get(activate.entity).map(ChildOf::parent) else {
87                return;
88            };
89            let Ok(mut counter) = counter_query.get_mut(parent) else {
90                return;
91            };
92            counter.0 += 1;
93            let value = counter.0.to_string();
94
95            let Ok(siblings) = children_query.get(parent) else {
96                return;
97            };
98            for &sibling in siblings {
99                let Ok(labels) = children_query.get(sibling) else {
100                    continue;
101                };
102                for &label in labels {
103                    if let Ok(mut text) = text_query.get_mut(label) {
104                        **text = value.clone();
105                    }
106                }
107            }
108        })
109        Node {
110            width: px(150),
111            height: px(65),
112            border: px(5),
113            // horizontally center child text
114            justify_content: JustifyContent::Center,
115            // vertically center child text
116            align_items: AlignItems::Center,
117            border_radius: BorderRadius::MAX,
118        }
119        BorderColor::from(Color::BLACK)
120        BackgroundColor(Color::srgb(0.15, 0.15, 0.15))
121        Children [ @label("0") ]
122    }
123}
124
125fn label(text: &str) -> impl Scene {
126    bsn! {
127        Text(text)
128        TextFont {
129            font: FontSourceTemplate::Handle("fonts/FiraSans-Bold.ttf"),
130            font_size: px(33.0),
131        }
132        TextColor(Color::srgb(0.9, 0.9, 0.9))
133    }
134}