# Objective 1. Nodes with `Display::None` set are removed from the layout and have no position or size. Outlines should not be drawn for a node with `Display::None` set. 2. The outline and border colors are checked for transparency together. If only one of the two is transparent, both will get queued. 3. The `node.is_empty()` check is insufficient to check if a border is present since a non-zero sized node can have a zero width border. ## Solution 1. Add a check to `extract_uinode_borders` and ignore the node if `Display::None` is set. 2. Filter the border and outline optional components by `is_fully_transparent`. 3. Check if all the border widths are zero instead. ## Testing I added dark cyan outlines around the left and right sections in the `display_and_visibility` example. If you run the example and set the outermost node to `Display::None` on the right, then you'll see the that the outline on the left disappears.
		
			
				
	
	
		
			459 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			459 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
//! Demonstrates how Display and Visibility work in the UI.
 | 
						|
 | 
						|
use bevy::{
 | 
						|
    color::palettes::css::{DARK_CYAN, DARK_GRAY, YELLOW},
 | 
						|
    prelude::*,
 | 
						|
    winit::WinitSettings,
 | 
						|
};
 | 
						|
 | 
						|
const PALETTE: [&str; 4] = ["27496D", "466B7A", "669DB3", "ADCBE3"];
 | 
						|
const HIDDEN_COLOR: Color = Color::srgb(1.0, 0.7, 0.7);
 | 
						|
 | 
						|
fn main() {
 | 
						|
    App::new()
 | 
						|
        .add_plugins(DefaultPlugins)
 | 
						|
        // Only run the app when there is user input. This will significantly reduce CPU/GPU use.
 | 
						|
        .insert_resource(WinitSettings::desktop_app())
 | 
						|
        .add_systems(Startup, setup)
 | 
						|
        .add_systems(
 | 
						|
            Update,
 | 
						|
            (
 | 
						|
                buttons_handler::<Display>,
 | 
						|
                buttons_handler::<Visibility>,
 | 
						|
                text_hover,
 | 
						|
            ),
 | 
						|
        )
 | 
						|
        .run();
 | 
						|
}
 | 
						|
 | 
						|
#[derive(Component)]
 | 
						|
struct Target<T> {
 | 
						|
    id: Entity,
 | 
						|
    phantom: std::marker::PhantomData<T>,
 | 
						|
}
 | 
						|
 | 
						|
impl<T> Target<T> {
 | 
						|
    fn new(id: Entity) -> Self {
 | 
						|
        Self {
 | 
						|
            id,
 | 
						|
            phantom: std::marker::PhantomData,
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
trait TargetUpdate {
 | 
						|
    type TargetComponent: Component;
 | 
						|
    const NAME: &'static str;
 | 
						|
    fn update_target(&self, target: &mut Self::TargetComponent) -> String;
 | 
						|
}
 | 
						|
 | 
						|
impl TargetUpdate for Target<Display> {
 | 
						|
    type TargetComponent = Node;
 | 
						|
    const NAME: &'static str = "Display";
 | 
						|
    fn update_target(&self, node: &mut Self::TargetComponent) -> String {
 | 
						|
        node.display = match node.display {
 | 
						|
            Display::Flex => Display::None,
 | 
						|
            Display::None => Display::Flex,
 | 
						|
            Display::Block | Display::Grid => unreachable!(),
 | 
						|
        };
 | 
						|
        format!("{}::{:?} ", Self::NAME, node.display)
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
impl TargetUpdate for Target<Visibility> {
 | 
						|
    type TargetComponent = Visibility;
 | 
						|
    const NAME: &'static str = "Visibility";
 | 
						|
    fn update_target(&self, visibility: &mut Self::TargetComponent) -> String {
 | 
						|
        *visibility = match *visibility {
 | 
						|
            Visibility::Inherited => Visibility::Visible,
 | 
						|
            Visibility::Visible => Visibility::Hidden,
 | 
						|
            Visibility::Hidden => Visibility::Inherited,
 | 
						|
        };
 | 
						|
        format!("{}::{visibility:?}", Self::NAME)
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
 | 
						|
    let palette: [Color; 4] = PALETTE.map(|hex| Srgba::hex(hex).unwrap().into());
 | 
						|
 | 
						|
    let text_font = TextFont {
 | 
						|
        font: asset_server.load("fonts/FiraSans-Bold.ttf"),
 | 
						|
        ..default()
 | 
						|
    };
 | 
						|
 | 
						|
    commands.spawn(Camera2d);
 | 
						|
    commands
 | 
						|
        .spawn((
 | 
						|
            Node {
 | 
						|
                width: Val::Percent(100.),
 | 
						|
                height: Val::Percent(100.),
 | 
						|
                flex_direction: FlexDirection::Column,
 | 
						|
                align_items: AlignItems::Center,
 | 
						|
                justify_content: JustifyContent::SpaceEvenly,
 | 
						|
                ..Default::default()
 | 
						|
            },
 | 
						|
            BackgroundColor(Color::BLACK),
 | 
						|
        ))
 | 
						|
        .with_children(|parent| {
 | 
						|
            parent.spawn((
 | 
						|
                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"),
 | 
						|
                text_font.clone(),
 | 
						|
                TextLayout::new_with_justify(JustifyText::Center),
 | 
						|
                Node {
 | 
						|
                    margin: UiRect::bottom(Val::Px(10.)),
 | 
						|
                    ..Default::default()
 | 
						|
                },
 | 
						|
            ));
 | 
						|
 | 
						|
            parent
 | 
						|
                .spawn(Node {
 | 
						|
                    width: Val::Percent(100.),
 | 
						|
                    ..default()
 | 
						|
                })
 | 
						|
                .with_children(|parent| {
 | 
						|
                    let mut target_ids = vec![];
 | 
						|
                    parent
 | 
						|
                        .spawn(Node {
 | 
						|
                            width: Val::Percent(50.),
 | 
						|
                            height: Val::Px(520.),
 | 
						|
                            justify_content: JustifyContent::Center,
 | 
						|
                            ..default()
 | 
						|
                        })
 | 
						|
                        .with_children(|parent| {
 | 
						|
                            target_ids = spawn_left_panel(parent, &palette);
 | 
						|
                        });
 | 
						|
 | 
						|
                    parent
 | 
						|
                        .spawn(Node {
 | 
						|
                            width: Val::Percent(50.),
 | 
						|
                            justify_content: JustifyContent::Center,
 | 
						|
                            ..default()
 | 
						|
                        })
 | 
						|
                        .with_children(|parent| {
 | 
						|
                            spawn_right_panel(parent, text_font, &palette, target_ids);
 | 
						|
                        });
 | 
						|
                });
 | 
						|
 | 
						|
            parent
 | 
						|
                .spawn(Node {
 | 
						|
                    flex_direction: FlexDirection::Row,
 | 
						|
                    align_items: AlignItems::Start,
 | 
						|
                    justify_content: JustifyContent::Start,
 | 
						|
                    column_gap: Val::Px(10.),
 | 
						|
                    ..default()
 | 
						|
                })
 | 
						|
                .with_children(|builder| {
 | 
						|
                    let text_font = TextFont {
 | 
						|
                        font: asset_server.load("fonts/FiraSans-Bold.ttf"),
 | 
						|
                        ..default()
 | 
						|
                    };
 | 
						|
 | 
						|
                    builder.spawn((
 | 
						|
                        Text::new("Display::None\nVisibility::Hidden\nVisibility::Inherited"),
 | 
						|
                        text_font.clone(),
 | 
						|
                        TextColor(HIDDEN_COLOR),
 | 
						|
                        TextLayout::new_with_justify(JustifyText::Center),
 | 
						|
                    ));
 | 
						|
                    builder.spawn((
 | 
						|
                        Text::new("-\n-\n-"),
 | 
						|
                        text_font.clone(),
 | 
						|
                        TextColor(DARK_GRAY.into()),
 | 
						|
                        TextLayout::new_with_justify(JustifyText::Center),
 | 
						|
                    ));
 | 
						|
                    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));
 | 
						|
                });
 | 
						|
        });
 | 
						|
}
 | 
						|
 | 
						|
fn spawn_left_panel(builder: &mut ChildBuilder, palette: &[Color; 4]) -> Vec<Entity> {
 | 
						|
    let mut target_ids = vec![];
 | 
						|
    builder
 | 
						|
        .spawn((
 | 
						|
            Node {
 | 
						|
                padding: UiRect::all(Val::Px(10.)),
 | 
						|
                ..default()
 | 
						|
            },
 | 
						|
            BackgroundColor(Color::WHITE),
 | 
						|
        ))
 | 
						|
        .with_children(|parent| {
 | 
						|
            parent
 | 
						|
                .spawn((Node::default(), BackgroundColor(Color::BLACK)))
 | 
						|
                .with_children(|parent| {
 | 
						|
                    let id = parent
 | 
						|
                        .spawn((
 | 
						|
                            Node {
 | 
						|
                                align_items: AlignItems::FlexEnd,
 | 
						|
                                justify_content: JustifyContent::FlexEnd,
 | 
						|
                                ..default()
 | 
						|
                            },
 | 
						|
                            BackgroundColor(palette[0]),
 | 
						|
                            Outline {
 | 
						|
                                width: Val::Px(4.),
 | 
						|
                                color: DARK_CYAN.into(),
 | 
						|
                                offset: Val::Px(10.),
 | 
						|
                            },
 | 
						|
                        ))
 | 
						|
                        .with_children(|parent| {
 | 
						|
                            parent.spawn(Node {
 | 
						|
                                width: Val::Px(100.),
 | 
						|
                                height: Val::Px(500.),
 | 
						|
                                ..default()
 | 
						|
                            });
 | 
						|
 | 
						|
                            let id = parent
 | 
						|
                                .spawn((
 | 
						|
                                    Node {
 | 
						|
                                        height: Val::Px(400.),
 | 
						|
                                        align_items: AlignItems::FlexEnd,
 | 
						|
                                        justify_content: JustifyContent::FlexEnd,
 | 
						|
                                        ..default()
 | 
						|
                                    },
 | 
						|
                                    BackgroundColor(palette[1]),
 | 
						|
                                ))
 | 
						|
                                .with_children(|parent| {
 | 
						|
                                    parent.spawn(Node {
 | 
						|
                                        width: Val::Px(100.),
 | 
						|
                                        height: Val::Px(400.),
 | 
						|
                                        ..default()
 | 
						|
                                    });
 | 
						|
 | 
						|
                                    let id = parent
 | 
						|
                                        .spawn((
 | 
						|
                                            Node {
 | 
						|
                                                height: Val::Px(300.),
 | 
						|
                                                align_items: AlignItems::FlexEnd,
 | 
						|
                                                justify_content: JustifyContent::FlexEnd,
 | 
						|
                                                ..default()
 | 
						|
                                            },
 | 
						|
                                            BackgroundColor(palette[2]),
 | 
						|
                                        ))
 | 
						|
                                        .with_children(|parent| {
 | 
						|
                                            parent.spawn(Node {
 | 
						|
                                                width: Val::Px(100.),
 | 
						|
                                                height: Val::Px(300.),
 | 
						|
                                                ..default()
 | 
						|
                                            });
 | 
						|
 | 
						|
                                            let id = parent
 | 
						|
                                                .spawn((
 | 
						|
                                                    Node {
 | 
						|
                                                        width: Val::Px(200.),
 | 
						|
                                                        height: Val::Px(200.),
 | 
						|
                                                        ..default()
 | 
						|
                                                    },
 | 
						|
                                                    BackgroundColor(palette[3]),
 | 
						|
                                                ))
 | 
						|
                                                .id();
 | 
						|
                                            target_ids.push(id);
 | 
						|
                                        })
 | 
						|
                                        .id();
 | 
						|
                                    target_ids.push(id);
 | 
						|
                                })
 | 
						|
                                .id();
 | 
						|
                            target_ids.push(id);
 | 
						|
                        })
 | 
						|
                        .id();
 | 
						|
                    target_ids.push(id);
 | 
						|
                });
 | 
						|
        });
 | 
						|
    target_ids
 | 
						|
}
 | 
						|
 | 
						|
fn spawn_right_panel(
 | 
						|
    parent: &mut ChildBuilder,
 | 
						|
    text_font: TextFont,
 | 
						|
    palette: &[Color; 4],
 | 
						|
    mut target_ids: Vec<Entity>,
 | 
						|
) {
 | 
						|
    let spawn_buttons = |parent: &mut ChildBuilder, target_id| {
 | 
						|
        spawn_button::<Display>(parent, text_font.clone(), target_id);
 | 
						|
        spawn_button::<Visibility>(parent, text_font.clone(), target_id);
 | 
						|
    };
 | 
						|
    parent
 | 
						|
        .spawn((
 | 
						|
            Node {
 | 
						|
                padding: UiRect::all(Val::Px(10.)),
 | 
						|
                ..default()
 | 
						|
            },
 | 
						|
            BackgroundColor(Color::WHITE),
 | 
						|
        ))
 | 
						|
        .with_children(|parent| {
 | 
						|
            parent
 | 
						|
                .spawn((
 | 
						|
                    Node {
 | 
						|
                        width: Val::Px(500.),
 | 
						|
                        height: Val::Px(500.),
 | 
						|
                        flex_direction: FlexDirection::Column,
 | 
						|
                        align_items: AlignItems::FlexEnd,
 | 
						|
                        justify_content: JustifyContent::SpaceBetween,
 | 
						|
                        padding: UiRect {
 | 
						|
                            left: Val::Px(5.),
 | 
						|
                            top: Val::Px(5.),
 | 
						|
                            ..default()
 | 
						|
                        },
 | 
						|
                        ..default()
 | 
						|
                    },
 | 
						|
                    BackgroundColor(palette[0]),
 | 
						|
                    Outline {
 | 
						|
                        width: Val::Px(4.),
 | 
						|
                        color: DARK_CYAN.into(),
 | 
						|
                        offset: Val::Px(10.),
 | 
						|
                    },
 | 
						|
                ))
 | 
						|
                .with_children(|parent| {
 | 
						|
                    spawn_buttons(parent, target_ids.pop().unwrap());
 | 
						|
 | 
						|
                    parent
 | 
						|
                        .spawn((
 | 
						|
                            Node {
 | 
						|
                                width: Val::Px(400.),
 | 
						|
                                height: Val::Px(400.),
 | 
						|
                                flex_direction: FlexDirection::Column,
 | 
						|
                                align_items: AlignItems::FlexEnd,
 | 
						|
                                justify_content: JustifyContent::SpaceBetween,
 | 
						|
                                padding: UiRect {
 | 
						|
                                    left: Val::Px(5.),
 | 
						|
                                    top: Val::Px(5.),
 | 
						|
                                    ..default()
 | 
						|
                                },
 | 
						|
                                ..default()
 | 
						|
                            },
 | 
						|
                            BackgroundColor(palette[1]),
 | 
						|
                        ))
 | 
						|
                        .with_children(|parent| {
 | 
						|
                            spawn_buttons(parent, target_ids.pop().unwrap());
 | 
						|
 | 
						|
                            parent
 | 
						|
                                .spawn((
 | 
						|
                                    Node {
 | 
						|
                                        width: Val::Px(300.),
 | 
						|
                                        height: Val::Px(300.),
 | 
						|
                                        flex_direction: FlexDirection::Column,
 | 
						|
                                        align_items: AlignItems::FlexEnd,
 | 
						|
                                        justify_content: JustifyContent::SpaceBetween,
 | 
						|
                                        padding: UiRect {
 | 
						|
                                            left: Val::Px(5.),
 | 
						|
                                            top: Val::Px(5.),
 | 
						|
                                            ..default()
 | 
						|
                                        },
 | 
						|
                                        ..default()
 | 
						|
                                    },
 | 
						|
                                    BackgroundColor(palette[2]),
 | 
						|
                                ))
 | 
						|
                                .with_children(|parent| {
 | 
						|
                                    spawn_buttons(parent, target_ids.pop().unwrap());
 | 
						|
 | 
						|
                                    parent
 | 
						|
                                        .spawn((
 | 
						|
                                            Node {
 | 
						|
                                                width: Val::Px(200.),
 | 
						|
                                                height: Val::Px(200.),
 | 
						|
                                                align_items: AlignItems::FlexStart,
 | 
						|
                                                justify_content: JustifyContent::SpaceBetween,
 | 
						|
                                                flex_direction: FlexDirection::Column,
 | 
						|
                                                padding: UiRect {
 | 
						|
                                                    left: Val::Px(5.),
 | 
						|
                                                    top: Val::Px(5.),
 | 
						|
                                                    ..default()
 | 
						|
                                                },
 | 
						|
                                                ..default()
 | 
						|
                                            },
 | 
						|
                                            BackgroundColor(palette[3]),
 | 
						|
                                        ))
 | 
						|
                                        .with_children(|parent| {
 | 
						|
                                            spawn_buttons(parent, target_ids.pop().unwrap());
 | 
						|
 | 
						|
                                            parent.spawn(Node {
 | 
						|
                                                width: Val::Px(100.),
 | 
						|
                                                height: Val::Px(100.),
 | 
						|
                                                ..default()
 | 
						|
                                            });
 | 
						|
                                        });
 | 
						|
                                });
 | 
						|
                        });
 | 
						|
                });
 | 
						|
        });
 | 
						|
}
 | 
						|
 | 
						|
fn spawn_button<T>(parent: &mut ChildBuilder, text_font: TextFont, target: Entity)
 | 
						|
where
 | 
						|
    T: Default + std::fmt::Debug + Send + Sync + 'static,
 | 
						|
    Target<T>: TargetUpdate,
 | 
						|
{
 | 
						|
    parent
 | 
						|
        .spawn((
 | 
						|
            Button,
 | 
						|
            Node {
 | 
						|
                align_self: AlignSelf::FlexStart,
 | 
						|
                padding: UiRect::axes(Val::Px(5.), Val::Px(1.)),
 | 
						|
                ..default()
 | 
						|
            },
 | 
						|
            BackgroundColor(Color::BLACK.with_alpha(0.5)),
 | 
						|
            Target::<T>::new(target),
 | 
						|
        ))
 | 
						|
        .with_children(|builder| {
 | 
						|
            builder.spawn((
 | 
						|
                Text(format!("{}::{:?}", Target::<T>::NAME, T::default())),
 | 
						|
                text_font,
 | 
						|
                TextLayout::new_with_justify(JustifyText::Center),
 | 
						|
            ));
 | 
						|
        });
 | 
						|
}
 | 
						|
 | 
						|
fn buttons_handler<T>(
 | 
						|
    mut left_panel_query: Query<&mut <Target<T> as TargetUpdate>::TargetComponent>,
 | 
						|
    mut visibility_button_query: Query<(&Target<T>, &Interaction, &Children), Changed<Interaction>>,
 | 
						|
    mut text_query: Query<(&mut Text, &mut TextColor)>,
 | 
						|
) where
 | 
						|
    T: Send + Sync,
 | 
						|
    Target<T>: TargetUpdate + Component,
 | 
						|
{
 | 
						|
    for (target, interaction, children) in visibility_button_query.iter_mut() {
 | 
						|
        if matches!(interaction, Interaction::Pressed) {
 | 
						|
            let mut target_value = left_panel_query.get_mut(target.id).unwrap();
 | 
						|
            for &child in children {
 | 
						|
                if let Ok((mut text, mut text_color)) = text_query.get_mut(child) {
 | 
						|
                    **text = target.update_target(target_value.as_mut());
 | 
						|
                    text_color.0 = if text.contains("None") || text.contains("Hidden") {
 | 
						|
                        Color::srgb(1.0, 0.7, 0.7)
 | 
						|
                    } else {
 | 
						|
                        Color::WHITE
 | 
						|
                    };
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
fn text_hover(
 | 
						|
    mut button_query: Query<(&Interaction, &mut BackgroundColor, &Children), Changed<Interaction>>,
 | 
						|
    mut text_query: Query<(&Text, &mut TextColor)>,
 | 
						|
) {
 | 
						|
    for (interaction, mut color, children) in button_query.iter_mut() {
 | 
						|
        match interaction {
 | 
						|
            Interaction::Hovered => {
 | 
						|
                *color = Color::BLACK.with_alpha(0.6).into();
 | 
						|
                for &child in children {
 | 
						|
                    if let Ok((_, mut text_color)) = text_query.get_mut(child) {
 | 
						|
                        // Bypass change detection to avoid recomputation of the text when only changing the color
 | 
						|
                        text_color.bypass_change_detection().0 = YELLOW.into();
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
            _ => {
 | 
						|
                *color = Color::BLACK.with_alpha(0.5).into();
 | 
						|
                for &child in children {
 | 
						|
                    if let Ok((text, mut text_color)) = text_query.get_mut(child) {
 | 
						|
                        text_color.bypass_change_detection().0 =
 | 
						|
                            if text.contains("None") || text.contains("Hidden") {
 | 
						|
                                HIDDEN_COLOR
 | 
						|
                            } else {
 | 
						|
                                Color::WHITE
 | 
						|
                            };
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 |