1use 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#[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
54trait 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
77impl 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
88impl 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
167fn 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 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
323fn 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
340fn 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
359fn 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 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
395fn 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}