clustered_decals/helpers/
widgets.rs1use bevy::{ecs::system::EntityCommands, prelude::*};
6
7#[derive(Clone, Event, Deref, DerefMut)]
10pub struct WidgetClickEvent<T>(T);
11
12#[derive(Clone, Component, Deref, DerefMut)]
15pub struct WidgetClickSender<T>(T)
16where
17 T: Clone + Send + Sync + 'static;
18
19#[derive(Clone, Copy, Component)]
21pub struct RadioButton;
22
23#[derive(Clone, Copy, Component)]
25pub struct RadioButtonText;
26
27pub const BUTTON_BORDER: UiRect = UiRect::all(Val::Px(1.0));
29
30pub const BUTTON_BORDER_COLOR: BorderColor = BorderColor(Color::WHITE);
32
33pub const BUTTON_BORDER_RADIUS_SIZE: Val = Val::Px(6.0);
35
36pub const BUTTON_PADDING: UiRect = UiRect::axes(Val::Px(12.0), Val::Px(6.0));
38
39pub 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
53pub 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 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
107pub 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 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
144pub 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
163pub 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
181pub fn update_ui_radio_button(background_color: &mut BackgroundColor, selected: bool) {
184 background_color.0 = if selected { Color::WHITE } else { Color::BLACK };
185}
186
187pub 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}