1use bevy::{
4 ecs::template::{EntityTemplate, OptionTemplate},
5 input_focus::{
6 tab_navigation::{TabGroup, TabNavigationPlugin},
7 InputFocus, InputFocusVisible,
8 },
9 picking::hover::Hovered,
10 prelude::*,
11 ui::{InteractionDisabled, Selected},
12 ui_widgets::{
13 tablist_self_update, ControlOrientation, SelectedTab, Tab, TabActivation, TabList,
14 ValueChange,
15 },
16};
17
18#[derive(Component, Default, Clone)]
19struct ShowcaseTab;
20
21#[derive(Resource, Default)]
22struct ControlledSelection(Option<Entity>);
23
24fn main() {
25 App::new()
26 .add_plugins((DefaultPlugins, TabNavigationPlugin))
27 .init_resource::<ControlledSelection>()
28 .add_systems(Startup, showcase.spawn())
29 .add_systems(Update, update_tab_styles)
30 .run();
31}
32
33fn showcase() -> impl SceneList {
34 bsn_list! {
35 Camera2d
36 --
37 Node {
38 width: percent(100),
39 height: percent(100),
40 padding: UiRect::all(px(24)),
41 display: Display::Flex,
42 flex_direction: FlexDirection::Column,
43 align_items: AlignItems::Start,
44 row_gap: px(18),
45 overflow: Overflow::scroll_y(),
46 }
47 BackgroundColor(Color::srgb(0.06, 0.07, 0.09))
48 TabGroup
49 Children [
50 @section_label("Horizontal automatic - self-updating")
51 --
52 #automatic
53 @tab_strip(ControlOrientation::Horizontal)
54 TabList {
55 orientation: ControlOrientation::Horizontal,
56 activation: TabActivation::Automatic,
57 }
58 @selected_tab(#automatic_general)
59 on(tablist_self_update)
60 Children [
61 #automatic_general
62 @tab_header("General")
63 --
64 @tab_header("Rendering")
65 --
66 @tab_header("Disabled")
67 InteractionDisabled
68 ]
69 --
70 @section_label("Horizontal manual - focus and selection are separate")
71 --
72 @tab_strip(ControlOrientation::Horizontal)
73 TabList
74 @selected_tab(#manual_scene)
75 on(tablist_self_update)
76 Children [
77 #manual_scene @tab_header("Scene")
78 --
79 @tab_header("Assets")
80 --
81 @tab_header("Inspector")
82 ]
83 --
84 @section_label("Vertical manual")
85 --
86 @tab_strip(ControlOrientation::Vertical)
87 TabList {
88 orientation: ControlOrientation::Vertical,
89 activation: TabActivation::Manual,
90 }
91 @selected_tab(#vertical_transform)
92 on(tablist_self_update)
93 Children [
94 #vertical_transform @tab_header("Transform")
95 --
96 @tab_header("Visibility")
97 --
98 @tab_header("Metadata")
99 ]
100 --
101 @section_label("Controlled - observer updates external state")
102 --
103 @tab_strip(ControlOrientation::Horizontal)
104 TabList
105 @selected_tab(#controlled_a)
106 on(controlled_selection)
107 Children [
108 #controlled_a @tab_header("External A")
109 --
110 @tab_header("External B")
111 ]
112 ]
113 }
114}
115
116fn tab_strip(orientation: ControlOrientation) -> impl Scene {
117 let flex_direction = match orientation {
118 ControlOrientation::Horizontal => FlexDirection::Row,
119 ControlOrientation::Vertical => FlexDirection::Column,
120 };
121 bsn! {
122 Node {
123 display: Display::Flex,
124 flex_direction,
125 align_items: AlignItems::Stretch,
126 }
127 BackgroundColor(Color::srgb(0.10, 0.11, 0.14))
128 }
129}
130
131fn tab_header(label: &'static str) -> impl Scene {
132 bsn! {
133 ShowcaseTab
134 Tab
135 Hovered
136 Node {
137 min_width: px(112),
138 min_height: px(36),
139 padding: UiRect::axes(px(12), px(8)),
140 border: UiRect::all(px(1)),
141 align_items: AlignItems::Center,
142 justify_content: JustifyContent::Center,
143 }
144 BackgroundColor(Color::srgb(0.13, 0.14, 0.18))
145 BorderColor::all(Color::srgb(0.24, 0.25, 0.30))
146 Children [
147 Text(label)
148 TextFont {
149 font_size: FontSize::Px(16.0)
150 }
151 TextColor(Color::srgb(0.88, 0.89, 0.92))
152 ]
153 }
154}
155
156fn selected_tab(tab: EntityTemplate) -> impl Scene {
157 let tab = OptionTemplate::Some(tab);
158 bsn! {
159 SelectedTab({tab})
160 }
161}
162
163fn section_label(label: &'static str) -> impl Scene {
164 bsn! {
165 Text(label)
166 TextFont {
167 font_size: FontSize::Px(17.0)
168 }
169 TextColor(Color::srgb(0.72, 0.76, 0.84))
170 }
171}
172
173fn controlled_selection(
174 change: On<ValueChange<Option<Entity>>>,
175 mut state: ResMut<ControlledSelection>,
176 mut commands: Commands,
177) {
178 state.0 = change.value;
179 commands.entity(change.source).insert(SelectedTab(state.0));
180}
181
182fn update_tab_styles(
183 focus: Res<InputFocus>,
184 focus_visible: Res<InputFocusVisible>,
185 mut tabs: Query<
186 (
187 Entity,
188 &Hovered,
189 Has<Selected>,
190 Has<InteractionDisabled>,
191 &mut BackgroundColor,
192 &mut BorderColor,
193 ),
194 With<ShowcaseTab>,
195 >,
196) {
197 for (entity, hovered, selected, disabled, mut background, mut border) in &mut tabs {
198 background.0 = match (disabled, selected, hovered.get()) {
199 (true, _, _) => Color::srgb(0.10, 0.10, 0.11),
200 (false, true, _) => Color::srgb(0.18, 0.34, 0.52),
201 (false, false, true) => Color::srgb(0.20, 0.21, 0.26),
202 _ => Color::srgb(0.13, 0.14, 0.18),
203 };
204 border.set_all(if focus_visible.0 && focus.get() == Some(entity) {
205 Color::srgb(0.45, 0.78, 1.0)
206 } else {
207 Color::srgb(0.24, 0.25, 0.30)
208 });
209 }
210}