display_and_visibility/
display_and_visibility.rs

1//! Demonstrates how Display and Visibility work in the UI.
2
3use bevy::{
4    color::palettes::css::{DARK_CYAN, DARK_GRAY, YELLOW},
5    ecs::{component::Mutable, hierarchy::ChildSpawnerCommands},
6    prelude::*,
7};
8
9const PALETTE: [&str; 4] = ["27496D", "466B7A", "669DB3", "ADCBE3"];
10const HIDDEN_COLOR: Color = Color::srgb(1.0, 0.7, 0.7);
11
12fn main() {
13    App::new()
14        .add_plugins(DefaultPlugins)
15        .add_systems(Startup, setup)
16        .add_systems(
17            Update,
18            (
19                buttons_handler::<Display>,
20                buttons_handler::<Visibility>,
21                text_hover,
22            ),
23        )
24        .run();
25}
26
27#[derive(Component)]
28struct Target<T> {
29    id: Entity,
30    phantom: std::marker::PhantomData<T>,
31}
32
33impl<T> Target<T> {
34    fn new(id: Entity) -> Self {
35        Self {
36            id,
37            phantom: std::marker::PhantomData,
38        }
39    }
40}
41
42trait TargetUpdate {
43    type TargetComponent: Component<Mutability = Mutable>;
44    const NAME: &'static str;
45    fn update_target(&self, target: &mut Self::TargetComponent) -> String;
46}
47
48impl TargetUpdate for Target<Display> {
49    type TargetComponent = Node;
50    const NAME: &'static str = "Display";
51    fn update_target(&self, node: &mut Self::TargetComponent) -> String {
52        node.display = match node.display {
53            Display::Flex => Display::None,
54            Display::None => Display::Flex,
55            Display::Block | Display::Grid => unreachable!(),
56        };
57        format!("{}::{:?} ", Self::NAME, node.display)
58    }
59}
60
61impl TargetUpdate for Target<Visibility> {
62    type TargetComponent = Visibility;
63    const NAME: &'static str = "Visibility";
64    fn update_target(&self, visibility: &mut Self::TargetComponent) -> String {
65        *visibility = match *visibility {
66            Visibility::Inherited => Visibility::Visible,
67            Visibility::Visible => Visibility::Hidden,
68            Visibility::Hidden => Visibility::Inherited,
69        };
70        format!("{}::{visibility:?}", Self::NAME)
71    }
72}
73
74fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
75    let palette: [Color; 4] = PALETTE.map(|hex| Srgba::hex(hex).unwrap().into());
76
77    let text_font = TextFont {
78        font: asset_server.load("fonts/FiraSans-Bold.ttf"),
79        ..default()
80    };
81
82    commands.spawn(Camera2d);
83    commands
84        .spawn((
85            Node {
86                width: percent(100),
87                height: percent(100),
88                flex_direction: FlexDirection::Column,
89                align_items: AlignItems::Center,
90                justify_content: JustifyContent::SpaceEvenly,
91                ..Default::default()
92            },
93            BackgroundColor(Color::BLACK),
94        ))
95        .with_children(|parent| {
96            parent.spawn((
97                Text::new("Use the panel on the right to change the Display and Visibility properties for the respective nodes of the panel on the left"),
98                text_font.clone(),
99                TextLayout::new_with_justify(Justify::Center),
100                Node {
101                    margin: UiRect::bottom(px(10)),
102                    ..Default::default()
103                },
104            ));
105
106            parent
107                .spawn(Node {
108                    width: percent(100),
109                    ..default()
110                })
111                .with_children(|parent| {
112                    let mut target_ids = vec![];
113                    parent
114                        .spawn(Node {
115                            width: percent(50),
116                            height: px(520),
117                            justify_content: JustifyContent::Center,
118                            ..default()
119                        })
120                        .with_children(|parent| {
121                            target_ids = spawn_left_panel(parent, &palette);
122                        });
123
124                    parent
125                        .spawn(Node {
126                            width: percent(50),
127                            justify_content: JustifyContent::Center,
128                            ..default()
129                        })
130                        .with_children(|parent| {
131                            spawn_right_panel(parent, text_font, &palette, target_ids);
132                        });
133                });
134
135            parent
136                .spawn(Node {
137                    flex_direction: FlexDirection::Row,
138                    align_items: AlignItems::Start,
139                    justify_content: JustifyContent::Start,
140                    column_gap: px(10),
141                    ..default()
142                })
143                .with_children(|builder| {
144                    let text_font = TextFont {
145                        font: asset_server.load("fonts/FiraSans-Bold.ttf"),
146                        ..default()
147                    };
148
149                    builder.spawn((
150                        Text::new("Display::None\nVisibility::Hidden\nVisibility::Inherited"),
151                        text_font.clone(),
152                        TextColor(HIDDEN_COLOR),
153                        TextLayout::new_with_justify(Justify::Center),
154                    ));
155                    builder.spawn((
156                        Text::new("-\n-\n-"),
157                        text_font.clone(),
158                        TextColor(DARK_GRAY.into()),
159                        TextLayout::new_with_justify(Justify::Center),
160                    ));
161                    builder.spawn((Text::new("The UI Node and its descendants will not be visible and will not be allotted any space in the UI layout.\nThe UI Node will not be visible but will still occupy space in the UI layout.\nThe UI node will inherit the visibility property of its parent. If it has no parent it will be visible."), text_font));
162                });
163        });
164}
165
166fn spawn_left_panel(builder: &mut ChildSpawnerCommands, palette: &[Color; 4]) -> Vec<Entity> {
167    let mut target_ids = vec![];
168    builder
169        .spawn((
170            Node {
171                padding: UiRect::all(px(10)),
172                ..default()
173            },
174            BackgroundColor(Color::WHITE),
175        ))
176        .with_children(|parent| {
177            parent
178                .spawn((Node::default(), BackgroundColor(Color::BLACK)))
179                .with_children(|parent| {
180                    let id = parent
181                        .spawn((
182                            Node {
183                                align_items: AlignItems::FlexEnd,
184                                justify_content: JustifyContent::FlexEnd,
185                                ..default()
186                            },
187                            BackgroundColor(palette[0]),
188                            Outline {
189                                width: px(4),
190                                color: DARK_CYAN.into(),
191                                offset: px(10),
192                            },
193                        ))
194                        .with_children(|parent| {
195                            parent.spawn(Node {
196                                width: px(100),
197                                height: px(500),
198                                ..default()
199                            });
200
201                            let id = parent
202                                .spawn((
203                                    Node {
204                                        height: px(400),
205                                        align_items: AlignItems::FlexEnd,
206                                        justify_content: JustifyContent::FlexEnd,
207                                        ..default()
208                                    },
209                                    BackgroundColor(palette[1]),
210                                ))
211                                .with_children(|parent| {
212                                    parent.spawn(Node {
213                                        width: px(100),
214                                        height: px(400),
215                                        ..default()
216                                    });
217
218                                    let id = parent
219                                        .spawn((
220                                            Node {
221                                                height: px(300),
222                                                align_items: AlignItems::FlexEnd,
223                                                justify_content: JustifyContent::FlexEnd,
224                                                ..default()
225                                            },
226                                            BackgroundColor(palette[2]),
227                                        ))
228                                        .with_children(|parent| {
229                                            parent.spawn(Node {
230                                                width: px(100),
231                                                height: px(300),
232                                                ..default()
233                                            });
234
235                                            let id = parent
236                                                .spawn((
237                                                    Node {
238                                                        width: px(200),
239                                                        height: px(200),
240                                                        ..default()
241                                                    },
242                                                    BackgroundColor(palette[3]),
243                                                ))
244                                                .id();
245                                            target_ids.push(id);
246                                        })
247                                        .id();
248                                    target_ids.push(id);
249                                })
250                                .id();
251                            target_ids.push(id);
252                        })
253                        .id();
254                    target_ids.push(id);
255                });
256        });
257    target_ids
258}
259
260fn spawn_right_panel(
261    parent: &mut ChildSpawnerCommands,
262    text_font: TextFont,
263    palette: &[Color; 4],
264    mut target_ids: Vec<Entity>,
265) {
266    let spawn_buttons = |parent: &mut ChildSpawnerCommands, target_id| {
267        spawn_button::<Display>(parent, text_font.clone(), target_id);
268        spawn_button::<Visibility>(parent, text_font.clone(), target_id);
269    };
270    parent
271        .spawn((
272            Node {
273                padding: UiRect::all(px(10)),
274                ..default()
275            },
276            BackgroundColor(Color::WHITE),
277        ))
278        .with_children(|parent| {
279            parent
280                .spawn((
281                    Node {
282                        width: px(500),
283                        height: px(500),
284                        flex_direction: FlexDirection::Column,
285                        align_items: AlignItems::FlexEnd,
286                        justify_content: JustifyContent::SpaceBetween,
287                        padding: UiRect {
288                            left: px(5),
289                            top: px(5),
290                            ..default()
291                        },
292                        ..default()
293                    },
294                    BackgroundColor(palette[0]),
295                    Outline {
296                        width: px(4),
297                        color: DARK_CYAN.into(),
298                        offset: px(10),
299                    },
300                ))
301                .with_children(|parent| {
302                    spawn_buttons(parent, target_ids.pop().unwrap());
303
304                    parent
305                        .spawn((
306                            Node {
307                                width: px(400),
308                                height: px(400),
309                                flex_direction: FlexDirection::Column,
310                                align_items: AlignItems::FlexEnd,
311                                justify_content: JustifyContent::SpaceBetween,
312                                padding: UiRect {
313                                    left: px(5),
314                                    top: px(5),
315                                    ..default()
316                                },
317                                ..default()
318                            },
319                            BackgroundColor(palette[1]),
320                        ))
321                        .with_children(|parent| {
322                            spawn_buttons(parent, target_ids.pop().unwrap());
323
324                            parent
325                                .spawn((
326                                    Node {
327                                        width: px(300),
328                                        height: px(300),
329                                        flex_direction: FlexDirection::Column,
330                                        align_items: AlignItems::FlexEnd,
331                                        justify_content: JustifyContent::SpaceBetween,
332                                        padding: UiRect {
333                                            left: px(5),
334                                            top: px(5),
335                                            ..default()
336                                        },
337                                        ..default()
338                                    },
339                                    BackgroundColor(palette[2]),
340                                ))
341                                .with_children(|parent| {
342                                    spawn_buttons(parent, target_ids.pop().unwrap());
343
344                                    parent
345                                        .spawn((
346                                            Node {
347                                                width: px(200),
348                                                height: px(200),
349                                                align_items: AlignItems::FlexStart,
350                                                justify_content: JustifyContent::SpaceBetween,
351                                                flex_direction: FlexDirection::Column,
352                                                padding: UiRect {
353                                                    left: px(5),
354                                                    top: px(5),
355                                                    ..default()
356                                                },
357                                                ..default()
358                                            },
359                                            BackgroundColor(palette[3]),
360                                        ))
361                                        .with_children(|parent| {
362                                            spawn_buttons(parent, target_ids.pop().unwrap());
363
364                                            parent.spawn(Node {
365                                                width: px(100),
366                                                height: px(100),
367                                                ..default()
368                                            });
369                                        });
370                                });
371                        });
372                });
373        });
374}
375
376fn spawn_button<T>(parent: &mut ChildSpawnerCommands, text_font: TextFont, target: Entity)
377where
378    T: Default + std::fmt::Debug + Send + Sync + 'static,
379    Target<T>: TargetUpdate,
380{
381    parent
382        .spawn((
383            Button,
384            Node {
385                align_self: AlignSelf::FlexStart,
386                padding: UiRect::axes(px(5), px(1)),
387                ..default()
388            },
389            BackgroundColor(Color::BLACK.with_alpha(0.5)),
390            Target::<T>::new(target),
391        ))
392        .with_children(|builder| {
393            builder.spawn((
394                Text(format!("{}::{:?}", Target::<T>::NAME, T::default())),
395                text_font,
396                TextLayout::new_with_justify(Justify::Center),
397            ));
398        });
399}
400
401fn buttons_handler<T>(
402    mut left_panel_query: Query<&mut <Target<T> as TargetUpdate>::TargetComponent>,
403    mut visibility_button_query: Query<(&Target<T>, &Interaction, &Children), Changed<Interaction>>,
404    mut text_query: Query<(&mut Text, &mut TextColor)>,
405) where
406    T: Send + Sync,
407    Target<T>: TargetUpdate + Component,
408{
409    for (target, interaction, children) in visibility_button_query.iter_mut() {
410        if matches!(interaction, Interaction::Pressed) {
411            let mut target_value = left_panel_query.get_mut(target.id).unwrap();
412            for &child in children {
413                if let Ok((mut text, mut text_color)) = text_query.get_mut(child) {
414                    **text = target.update_target(target_value.as_mut());
415                    text_color.0 = if text.contains("None") || text.contains("Hidden") {
416                        Color::srgb(1.0, 0.7, 0.7)
417                    } else {
418                        Color::WHITE
419                    };
420                }
421            }
422        }
423    }
424}
425
426fn text_hover(
427    mut button_query: Query<(&Interaction, &mut BackgroundColor, &Children), Changed<Interaction>>,
428    mut text_query: Query<(&Text, &mut TextColor)>,
429) {
430    for (interaction, mut color, children) in button_query.iter_mut() {
431        match interaction {
432            Interaction::Hovered => {
433                *color = Color::BLACK.with_alpha(0.6).into();
434                for &child in children {
435                    if let Ok((_, mut text_color)) = text_query.get_mut(child) {
436                        // Bypass change detection to avoid recomputation of the text when only changing the color
437                        text_color.bypass_change_detection().0 = YELLOW.into();
438                    }
439                }
440            }
441            _ => {
442                *color = Color::BLACK.with_alpha(0.5).into();
443                for &child in children {
444                    if let Ok((text, mut text_color)) = text_query.get_mut(child) {
445                        text_color.bypass_change_detection().0 =
446                            if text.contains("None") || text.contains("Hidden") {
447                                HIDDEN_COLOR
448                            } else {
449                                Color::WHITE
450                            };
451                    }
452                }
453            }
454        }
455    }
456}