clustered_decals/helpers/
widgets.rs

1//! Simple widgets for example UI.
2//!
3//! Unlike other examples, which demonstrate an application, this demonstrates a plugin library.
4
5use bevy::{ecs::system::EntityCommands, prelude::*};
6
7/// An event that's sent whenever the user changes one of the settings by
8/// clicking a radio button.
9#[derive(Clone, Event, Deref, DerefMut)]
10pub struct WidgetClickEvent<T>(T);
11
12/// A marker component that we place on all widgets that send
13/// [`WidgetClickEvent`]s of the given type.
14#[derive(Clone, Component, Deref, DerefMut)]
15pub struct WidgetClickSender<T>(T)
16where
17    T: Clone + Send + Sync + 'static;
18
19/// A marker component that we place on all radio `Button`s.
20#[derive(Clone, Copy, Component)]
21pub struct RadioButton;
22
23/// A marker component that we place on all `Text` inside radio buttons.
24#[derive(Clone, Copy, Component)]
25pub struct RadioButtonText;
26
27/// The size of the border that surrounds buttons.
28pub const BUTTON_BORDER: UiRect = UiRect::all(Val::Px(1.0));
29
30/// The color of the border that surrounds buttons.
31pub const BUTTON_BORDER_COLOR: BorderColor = BorderColor(Color::WHITE);
32
33/// The amount of rounding to apply to button corners.
34pub const BUTTON_BORDER_RADIUS_SIZE: Val = Val::Px(6.0);
35
36/// The amount of space between the edge of the button and its label.
37pub const BUTTON_PADDING: UiRect = UiRect::axes(Val::Px(12.0), Val::Px(6.0));
38
39/// Returns a [`Node`] appropriate for the outer main UI node.
40///
41/// This UI is in the bottom left corner and has flex column support
42pub fn main_ui_node() -> Node {
43    Node {
44        flex_direction: FlexDirection::Column,
45        position_type: PositionType::Absolute,
46        row_gap: Val::Px(6.0),
47        left: Val::Px(10.0),
48        bottom: Val::Px(10.0),
49        ..default()
50    }
51}
52
53/// Spawns a single radio button that allows configuration of a setting.
54///
55/// The type parameter specifies the value that will be packaged up and sent in
56/// a [`WidgetClickEvent`] when the radio button is clicked.
57pub fn spawn_option_button<T>(
58    parent: &mut ChildSpawnerCommands,
59    option_value: T,
60    option_name: &str,
61    is_selected: bool,
62    is_first: bool,
63    is_last: bool,
64) where
65    T: Clone + Send + Sync + 'static,
66{
67    let (bg_color, fg_color) = if is_selected {
68        (Color::WHITE, Color::BLACK)
69    } else {
70        (Color::BLACK, Color::WHITE)
71    };
72
73    // Add the button node.
74    parent
75        .spawn((
76            Button,
77            Node {
78                border: BUTTON_BORDER.with_left(if is_first { Val::Px(1.0) } else { Val::Px(0.0) }),
79                justify_content: JustifyContent::Center,
80                align_items: AlignItems::Center,
81                padding: BUTTON_PADDING,
82                ..default()
83            },
84            BUTTON_BORDER_COLOR,
85            BorderRadius::ZERO
86                .with_left(if is_first {
87                    BUTTON_BORDER_RADIUS_SIZE
88                } else {
89                    Val::Px(0.0)
90                })
91                .with_right(if is_last {
92                    BUTTON_BORDER_RADIUS_SIZE
93                } else {
94                    Val::Px(0.0)
95                }),
96            BackgroundColor(bg_color),
97        ))
98        .insert(RadioButton)
99        .insert(WidgetClickSender(option_value.clone()))
100        .with_children(|parent| {
101            spawn_ui_text(parent, option_name, fg_color)
102                .insert(RadioButtonText)
103                .insert(WidgetClickSender(option_value));
104        });
105}
106
107/// Spawns the buttons that allow configuration of a setting.
108///
109/// The user may change the setting to any one of the labeled `options`. The
110/// value of the given type parameter will be packaged up and sent as a
111/// [`WidgetClickEvent`] when one of the radio buttons is clicked.
112pub fn spawn_option_buttons<T>(
113    parent: &mut ChildSpawnerCommands,
114    title: &str,
115    options: &[(T, &str)],
116) where
117    T: Clone + Send + Sync + 'static,
118{
119    // Add the parent node for the row.
120    parent
121        .spawn(Node {
122            align_items: AlignItems::Center,
123            ..default()
124        })
125        .with_children(|parent| {
126            spawn_ui_text(parent, title, Color::BLACK).insert(Node {
127                width: Val::Px(125.0),
128                ..default()
129            });
130
131            for (option_index, (option_value, option_name)) in options.iter().cloned().enumerate() {
132                spawn_option_button(
133                    parent,
134                    option_value,
135                    option_name,
136                    option_index == 0,
137                    option_index == 0,
138                    option_index == options.len() - 1,
139                );
140            }
141        });
142}
143
144/// Spawns text for the UI.
145///
146/// Returns the `EntityCommands`, which allow further customization of the text
147/// style.
148pub fn spawn_ui_text<'a>(
149    parent: &'a mut ChildSpawnerCommands,
150    label: &str,
151    color: Color,
152) -> EntityCommands<'a> {
153    parent.spawn((
154        Text::new(label),
155        TextFont {
156            font_size: 18.0,
157            ..default()
158        },
159        TextColor(color),
160    ))
161}
162
163/// Checks for clicks on the radio buttons and sends `RadioButtonChangeEvent`s
164/// as necessary.
165pub fn handle_ui_interactions<T>(
166    mut interactions: Query<
167        (&Interaction, &WidgetClickSender<T>),
168        (With<Button>, With<RadioButton>),
169    >,
170    mut widget_click_events: EventWriter<WidgetClickEvent<T>>,
171) where
172    T: Clone + Send + Sync + 'static,
173{
174    for (interaction, click_event) in interactions.iter_mut() {
175        if *interaction == Interaction::Pressed {
176            widget_click_events.write(WidgetClickEvent((**click_event).clone()));
177        }
178    }
179}
180
181/// Updates the style of the button part of a radio button to reflect its
182/// selected status.
183pub fn update_ui_radio_button(background_color: &mut BackgroundColor, selected: bool) {
184    background_color.0 = if selected { Color::WHITE } else { Color::BLACK };
185}
186
187/// Updates the color of the label of a radio button to reflect its selected
188/// status.
189pub fn update_ui_radio_button_text(entity: Entity, writer: &mut TextUiWriter, selected: bool) {
190    let text_color = if selected { Color::BLACK } else { Color::WHITE };
191
192    writer.for_each_color(entity, |mut color| {
193        color.0 = text_color;
194    });
195}