//! Demonstrates the behavior-only tab widgets in `bevy_ui_widgets`. use bevy::{ ecs::template::{EntityTemplate, OptionTemplate}, input_focus::{ tab_navigation::{TabGroup, TabNavigationPlugin}, InputFocus, InputFocusVisible, }, picking::hover::Hovered, prelude::*, ui::{InteractionDisabled, Selected}, ui_widgets::{ tablist_self_update, ControlOrientation, SelectedTab, Tab, TabActivation, TabList, ValueChange, }, }; #[derive(Component, Default, Clone)] struct ShowcaseTab; #[derive(Resource, Default)] struct ControlledSelection(Option); fn main() { App::new() .add_plugins((DefaultPlugins, TabNavigationPlugin)) .init_resource::() .add_systems(Startup, showcase.spawn()) .add_systems(Update, update_tab_styles) .run(); } fn showcase() -> impl SceneList { bsn_list! { Camera2d -- Node { width: percent(100), height: percent(100), padding: UiRect::all(px(24)), display: Display::Flex, flex_direction: FlexDirection::Column, align_items: AlignItems::Start, row_gap: px(18), overflow: Overflow::scroll_y(), } BackgroundColor(Color::srgb(0.06, 0.07, 0.09)) TabGroup Children [ @section_label("Horizontal automatic - self-updating") -- #automatic @tab_strip(ControlOrientation::Horizontal) TabList { orientation: ControlOrientation::Horizontal, activation: TabActivation::Automatic, } @selected_tab(#automatic_general) on(tablist_self_update) Children [ #automatic_general @tab_header("General") -- @tab_header("Rendering") -- @tab_header("Disabled") InteractionDisabled ] -- @section_label("Horizontal manual - focus and selection are separate") -- @tab_strip(ControlOrientation::Horizontal) TabList @selected_tab(#manual_scene) on(tablist_self_update) Children [ #manual_scene @tab_header("Scene") -- @tab_header("Assets") -- @tab_header("Inspector") ] -- @section_label("Vertical manual") -- @tab_strip(ControlOrientation::Vertical) TabList { orientation: ControlOrientation::Vertical, activation: TabActivation::Manual, } @selected_tab(#vertical_transform) on(tablist_self_update) Children [ #vertical_transform @tab_header("Transform") -- @tab_header("Visibility") -- @tab_header("Metadata") ] -- @section_label("Controlled - observer updates external state") -- @tab_strip(ControlOrientation::Horizontal) TabList @selected_tab(#controlled_a) on(controlled_selection) Children [ #controlled_a @tab_header("External A") -- @tab_header("External B") ] ] } } fn tab_strip(orientation: ControlOrientation) -> impl Scene { let flex_direction = match orientation { ControlOrientation::Horizontal => FlexDirection::Row, ControlOrientation::Vertical => FlexDirection::Column, }; bsn! { Node { display: Display::Flex, flex_direction, align_items: AlignItems::Stretch, } BackgroundColor(Color::srgb(0.10, 0.11, 0.14)) } } fn tab_header(label: &'static str) -> impl Scene { bsn! { ShowcaseTab Tab Hovered Node { min_width: px(112), min_height: px(36), padding: UiRect::axes(px(12), px(8)), border: UiRect::all(px(1)), align_items: AlignItems::Center, justify_content: JustifyContent::Center, } BackgroundColor(Color::srgb(0.13, 0.14, 0.18)) BorderColor::all(Color::srgb(0.24, 0.25, 0.30)) Children [ Text(label) TextFont { font_size: FontSize::Px(16.0) } TextColor(Color::srgb(0.88, 0.89, 0.92)) ] } } fn selected_tab(tab: EntityTemplate) -> impl Scene { let tab = OptionTemplate::Some(tab); bsn! { SelectedTab({tab}) } } fn section_label(label: &'static str) -> impl Scene { bsn! { Text(label) TextFont { font_size: FontSize::Px(17.0) } TextColor(Color::srgb(0.72, 0.76, 0.84)) } } fn controlled_selection( change: On>>, mut state: ResMut, mut commands: Commands, ) { state.0 = change.value; commands.entity(change.source).insert(SelectedTab(state.0)); } fn update_tab_styles( focus: Res, focus_visible: Res, mut tabs: Query< ( Entity, &Hovered, Has, Has, &mut BackgroundColor, &mut BorderColor, ), With, >, ) { for (entity, hovered, selected, disabled, mut background, mut border) in &mut tabs { background.0 = match (disabled, selected, hovered.get()) { (true, _, _) => Color::srgb(0.10, 0.10, 0.11), (false, true, _) => Color::srgb(0.18, 0.34, 0.52), (false, false, true) => Color::srgb(0.20, 0.21, 0.26), _ => Color::srgb(0.13, 0.14, 0.18), }; border.set_all(if focus_visible.0 && focus.get() == Some(entity) { Color::srgb(0.45, 0.78, 1.0) } else { Color::srgb(0.24, 0.25, 0.30) }); } }