Skip to main content

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},
5    ecs::{component::Mutable, template::EntityTemplate},
6    feathers::{
7        controls::{FeathersListRow, FeathersSelect, OptionIndex},
8        display::caption,
9        theme::UiTheme,
10        FeathersPlugins,
11    },
12    prelude::*,
13    ui::Selected,
14    ui_widgets::ValueChange,
15};
16
17#[path = "../../helpers/theme.rs"]
18mod theme;
19
20const PALETTE: [&str; 4] = ["27496D", "466B7A", "669DB3", "ADCBE3"];
21const HIDDEN_COLOR: Color = Color::srgb(1.0, 0.7, 0.7);
22
23fn main() {
24    App::new()
25        .add_plugins((DefaultPlugins, FeathersPlugins))
26        .insert_resource(UiTheme(theme::basic_example_theme(Color::WHITE)))
27        .add_systems(Startup, setup)
28        .add_observer(on_value_change::<NodeDisplaySetting>)
29        .add_observer(on_value_change::<NodeVisibilitySetting>)
30        .run();
31}
32
33/// A component attached to an option select that will change the target entity
34/// with a given setting `T`.
35/// The way the change is implemented is via the `TargetUpdate<T>` trait. Changing the
36/// value of the option select will alter the target entity.
37/// id is an `Option` in order for this to derive `Default` for usage in bsn!,
38/// but the id should always be present.
39#[derive(Component, Clone, Default)]
40struct Target<T> {
41    id: Option<Entity>,
42    phantom: std::marker::PhantomData<T>,
43}
44
45impl<T> Target<T> {
46    fn new(id: Entity) -> Self {
47        Self {
48            id: Some(id),
49            phantom: std::marker::PhantomData,
50        }
51    }
52}
53
54/// The trait to be used in conjunction with the `Target` struct.
55/// This trait specifies the `TargetComponent` which will be modified.
56/// `T` is a setting option type that will influence how the `TargetComponent` is updated.
57trait TargetUpdate<T: Component + Clone + Default + PartialEq> {
58    type TargetComponent: Component<Mutability = Mutable>;
59    fn update_target(&self, target: &mut Self::TargetComponent, value: &T);
60}
61
62#[derive(Component, Clone, Debug, Default, PartialEq)]
63enum NodeDisplaySetting {
64    #[default]
65    Flex,
66    None,
67}
68
69#[derive(Component, Clone, Debug, Default, PartialEq)]
70enum NodeVisibilitySetting {
71    #[default]
72    Inherited,
73    Visible,
74    Hidden,
75}
76
77/// For `Display`, this impl of `TargetUpdate` will affect this entity's `Node` component's display property.
78impl TargetUpdate<NodeDisplaySetting> for Target<NodeDisplaySetting> {
79    type TargetComponent = Node;
80    fn update_target(&self, node: &mut Self::TargetComponent, value: &NodeDisplaySetting) {
81        node.display = match value {
82            NodeDisplaySetting::Flex => Display::Flex,
83            NodeDisplaySetting::None => Display::None,
84        };
85    }
86}
87
88/// For `Visibility`, this impl of `TargetUpdate` will affect this entity's `Visibility` component.
89impl TargetUpdate<NodeVisibilitySetting> for Target<NodeVisibilitySetting> {
90    type TargetComponent = Visibility;
91    fn update_target(&self, visibility: &mut Self::TargetComponent, value: &NodeVisibilitySetting) {
92        *visibility = match value {
93            NodeVisibilitySetting::Inherited => Visibility::Inherited,
94            NodeVisibilitySetting::Visible => Visibility::Visible,
95            NodeVisibilitySetting::Hidden => Visibility::Hidden,
96        };
97    }
98}
99
100fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
101    let palette: [Color; 4] = PALETTE.map(|hex| Srgba::hex(hex).unwrap().into());
102
103    let text_font = TextFont {
104        font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
105        ..default()
106    };
107
108    commands.spawn(Camera2d);
109
110    let panels_child = commands.spawn_scene(panels(&palette)).id();
111
112    commands
113        .spawn((
114            Node {
115                width: percent(100),
116                height: percent(100),
117                flex_direction: FlexDirection::Column,
118                align_items: AlignItems::Center,
119                justify_content: JustifyContent::SpaceEvenly,
120                ..Default::default()
121            },
122            BackgroundColor(Color::BLACK),
123        ))
124        .add_child(panels_child)
125        .with_children(|parent| {
126            parent.spawn((
127                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"),
128                text_font.clone(),
129                TextLayout::justify(Justify::Center),
130                Node {
131                    margin: UiRect::bottom(px(10)),
132                    ..Default::default()
133                },
134            ));
135
136            parent
137                .spawn(Node {
138                    flex_direction: FlexDirection::Row,
139                    align_items: AlignItems::Start,
140                    justify_content: JustifyContent::Start,
141                    column_gap: px(10),
142                    ..default()
143                })
144                .with_children(|builder| {
145                    let text_font = TextFont {
146                        font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
147                        ..default()
148                    };
149
150                    builder.spawn((
151                        Text::new("Display::None\nVisibility::Hidden\nVisibility::Inherited"),
152                        text_font.clone(),
153                        TextColor(HIDDEN_COLOR),
154                        TextLayout::justify(Justify::Center),
155                    ));
156                    builder.spawn((
157                        Text::new("-\n-\n-"),
158                        text_font.clone(),
159                        TextColor(DARK_GRAY.into()),
160                        TextLayout::justify(Justify::Center),
161                    ));
162                    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));
163                });
164        });
165}
166
167/// Returns the main interactable of the example as a scene.
168/// The left panel changes `Display` and `Visibility` based on actions taken
169/// in the right panel.
170fn panels(palette: &[Color; 4]) -> impl Scene {
171    let left_panel_node_base = |height_px: i32, palette_index: usize| {
172        bsn! {
173            Node {
174                height: px(height_px),
175                align_items: AlignItems::FlexEnd,
176                justify_content: JustifyContent::FlexEnd,
177            }
178            BackgroundColor(palette[palette_index])
179        }
180    };
181
182    let right_panel_node_base = |width_height_px: i32, palette_index: usize| {
183        bsn! {
184            Node {
185                width: px(width_height_px),
186                height: px(width_height_px),
187                flex_direction: FlexDirection::Column,
188                align_items: AlignItems::FlexEnd,
189                justify_content: JustifyContent::SpaceBetween,
190                padding: UiRect {
191                    left: px(5),
192                    top: px(5),
193                },
194            }
195            BackgroundColor(palette[palette_index])
196        }
197    };
198
199    // This must be defined as a big scene because
200    // the right panel has entity references to affect look of the left panel.
201    // The entity references must exist in the same `bsn!` block in order to be valid.
202    bsn! {
203        Node {
204            width: percent(100)
205        }
206        Children [
207            #LeftPanel
208            Node {
209                width: percent(50),
210                height: px(520),
211                justify_content: JustifyContent::Center,
212            }
213            Children [
214                Node {
215                    padding: UiRect::all(px(10)),
216                }
217                BackgroundColor(Color::WHITE)
218                Children [
219                    Node
220                    BackgroundColor(Color::BLACK)
221                    Children [
222                        #LeftGrandParent
223                        @left_panel_node_base(500, 0)
224                        Outline {
225                            width: px(4),
226                            color: DARK_CYAN,
227                            offset: px(10),
228                        }
229                        Children [
230                            Node {
231                                width: px(100),
232                                height: px(500),
233                            }
234                            --
235                            #LeftParent
236                            @left_panel_node_base(400, 1)
237                            Children [
238                                Node {
239                                    width: px(100),
240                                    height: px(400),
241                                }
242                                --
243                                #LeftChild
244                                @left_panel_node_base(300, 2)
245                                Children [
246                                    Node {
247                                        width: px(100),
248                                        height: px(300),
249                                    }
250                                    --
251                                    #LeftGrandChild
252                                    Node {
253                                        width: px(200),
254                                        height: px(200),
255
256                                    }
257                                    BackgroundColor(palette[3])
258                                ]
259                            ]
260                        ]
261                    ]
262                ]
263            ]
264            --
265            #RightPanel
266            Node {
267                width: percent(50),
268                justify_content: JustifyContent::Center,
269            }
270            Children [
271                Node {
272                    padding: UiRect::all(px(10)),
273                }
274                BackgroundColor(Color::WHITE)
275                Children [
276                    #RightGrandParent
277                    @right_panel_node_base(500, 0)
278                    Outline {
279                        width: px(4),
280                        color: DARK_CYAN,
281                        offset: px(10),
282                    }
283                    Children [
284                        @feathers_select_display(#LeftGrandParent)
285                        --
286                        @feathers_select_visibility(#LeftGrandParent)
287                        --
288                        #RightParent
289                        @right_panel_node_base(400, 1)
290                        Children [
291                            @feathers_select_display(#LeftParent)
292                            --
293                            @feathers_select_visibility(#LeftParent)
294                            --
295                            #RightChild
296                            @right_panel_node_base(300, 2)
297                            Children [
298                                @feathers_select_display(#LeftChild)
299                                --
300                                @feathers_select_visibility(#LeftChild)
301                                --
302                                #RightGrandChild
303                                @right_panel_node_base(200, 3)
304                                Children [
305                                    @feathers_select_display(#LeftGrandChild)
306                                    --
307                                    @feathers_select_visibility(#LeftGrandChild)
308                                    --
309                                    Node {
310                                        width: px(100),
311                                        height: px(100),
312                                    }
313                                ]
314                            ]
315                        ]
316                    ]
317                ]
318            ]
319        ]
320    }
321}
322
323/// A dropdown select that will execute a target action on the provided `target` via `on_value_change`.
324/// This select will update the display property on the Node of the target.
325fn feathers_select_display(target: EntityTemplate) -> impl Scene {
326    bsn! {
327        @select_base::<NodeDisplaySetting>(target)
328        @FeathersSelect {
329            @options: {
330                bsn_list! {
331                    @FeathersListRow Selected OptionIndex(0) NodeDisplaySetting::Flex Children[@caption(format!("Display::{:?}", Display::Flex))]
332                    --
333                    @FeathersListRow OptionIndex(1) NodeDisplaySetting::None Children[@caption(format!("Display::{:?}", Display::None))]
334                }
335            }
336        }
337    }
338}
339
340/// A dropdown select that will execute a target action on the provided `target` via `on_value_change`.
341/// This select will update the Visibility component directly on the target.
342fn feathers_select_visibility(target: EntityTemplate) -> impl Scene {
343    bsn! {
344        @select_base::<NodeVisibilitySetting>(target)
345        @FeathersSelect {
346            @options: {
347                bsn_list! {
348                    @FeathersListRow Selected OptionIndex(0) NodeVisibilitySetting::Inherited Children[@caption(format!("Visibility::{:?}", Visibility::Inherited))]
349                    --
350                    @FeathersListRow OptionIndex(1) NodeVisibilitySetting::Visible Children[@caption(format!("Visibility::{:?}", Visibility::Visible))]
351                    --
352                    @FeathersListRow OptionIndex(2) NodeVisibilitySetting::Hidden Children[@caption(format!("Visibility::{:?}", Visibility::Hidden))]
353                }
354            }
355        }
356    }
357}
358
359/// Observer that reacts to value changes of a `FeathersSelect` for the `T` setting,
360/// and updates the target entity accordingly.
361fn on_value_change<T: Component + Default + Clone + PartialEq + Send + Sync>(
362    event: On<ValueChange<Entity>>,
363    setting_value_q: Query<&T>,
364    select_q: Query<(&Children, &Target<T>), With<FeathersSelect>>,
365    mut target_component_query: Query<&mut <Target<T> as TargetUpdate<T>>::TargetComponent>,
366    mut commands: Commands,
367) where
368    Target<T>: TargetUpdate<T>,
369{
370    let Ok(value) = setting_value_q.get(event.value) else {
371        return;
372    };
373    let Ok((children, target)) = select_q.get(event.source) else {
374        return;
375    };
376
377    let Ok(mut target_value) = target_component_query.get_mut(target.id.unwrap()) else {
378        return;
379    };
380
381    target.update_target(target_value.as_mut(), value);
382
383    // Update selected status of children
384    for child in children {
385        if let Ok(child_value) = setting_value_q.get(*child)
386            && *child_value == *value
387        {
388            commands.entity(*child).insert(Selected);
389        } else {
390            commands.entity(*child).remove::<Selected>();
391        }
392    }
393}
394
395/// A scene of the `Node`, `BackgroundColor`, and `Target<T>` for the given target.
396fn select_base<T>(target: EntityTemplate) -> impl Scene
397where
398    T: Default + Clone + Component + PartialEq + Unpin + Send + Sync + 'static,
399    Target<T>: TargetUpdate<T>,
400{
401    bsn! {
402        Node {
403            align_self: AlignSelf::FlexStart,
404            padding: UiRect::axes(px(5), px(1)),
405        }
406        BackgroundColor({Color::BLACK.with_alpha(0.5)})
407        template(move |ctx| match target {
408            EntityTemplate::Entity(ent) => Ok(Target::<T>::new(ent)),
409            EntityTemplate::SceneEntityReference(scene_entity_reference) => Ok(Target::<T>::new(ctx.get_entity(scene_entity_reference))),
410            EntityTemplate::None => Err(BevyError::error("Did not set up example correctly!"))
411        })
412    }
413}