1use crate::number_input_f32::number_input_f32;
4use crate::number_input_i32::number_input_i32;
5use crate::radio::{feathers_option_buttons, main_ui_node_scene, RadioButtonOptionValue};
6use bevy::feathers::tokens::semantic::SURFACE_PANE_BODY;
7use bevy::{
8 color::palettes::css::*,
9 feathers::{
10 containers::{pane, pane_body},
11 controls::{FeathersButton, FeathersNumberInput, NumberInputPrecision, NumberInputValue},
12 dark_theme::create_dark_theme,
13 display::caption,
14 theme::{ThemeProps, UiTheme},
15 FeathersPlugins,
16 },
17 prelude::*,
18 ui_widgets::{radio_self_update, Activate, ValueChange},
19};
20
21#[path = "../../helpers/number_input_f32.rs"]
22mod number_input_f32;
23
24#[path = "../../helpers/number_input_i32.rs"]
25mod number_input_i32;
26
27#[path = "../../helpers/radio.rs"]
28mod radio;
29
30#[derive(Component, Clone, Copy, Default, PartialEq)]
32enum Shape {
33 #[default]
34 Square,
35 RoundedSquare,
36 Circle,
37 LongRectangle,
38 TallRectangle,
39}
40
41impl Shape {
42 fn change_node(&self, node: &mut Node) {
44 match *self {
45 Self::Square => {
46 node.width = px(164);
47 node.height = px(164);
48 node.border_radius = BorderRadius::ZERO;
49 }
50 Self::RoundedSquare => {
51 node.width = px(164);
52 node.height = px(164);
53 node.border_radius = BorderRadius::all(px(41));
54 }
55 Self::Circle => {
56 node.width = px(164);
57 node.height = px(164);
58 node.border_radius = BorderRadius::MAX;
59 }
60 Self::LongRectangle => {
61 node.width = px(240);
62 node.height = px(80);
63 node.border_radius = BorderRadius::all(px(32));
64 }
65 Self::TallRectangle => {
66 node.width = px(80);
67 node.height = px(240);
68 node.border_radius = BorderRadius::all(px(32));
69 }
70 }
71 }
72
73 const fn label(&self) -> &'static str {
75 match *self {
76 Shape::Square => "Square",
77 Shape::RoundedSquare => "Rounded Square",
78 Shape::Circle => "Circle",
79 Shape::LongRectangle => "Long Rectangle",
80 Shape::TallRectangle => "Tall Rectangle",
81 }
82 }
83}
84
85const SHAPE_OPTIONS: [(Shape, &str); 5] = [
86 (Shape::Square, Shape::Square.label()),
87 (Shape::RoundedSquare, Shape::RoundedSquare.label()),
88 (Shape::Circle, Shape::Circle.label()),
89 (Shape::LongRectangle, Shape::LongRectangle.label()),
90 (Shape::TallRectangle, Shape::TallRectangle.label()),
91];
92
93#[derive(Resource)]
95struct AppSettings {
96 shape: Shape,
97 x_offset: f32,
100 y_offset: f32,
101 blur: f32,
102 spread: f32,
103 count: usize,
105 samples: u32,
107}
108
109impl Default for AppSettings {
111 fn default() -> Self {
112 Self {
113 shape: Shape::default(),
114 x_offset: 20.0,
115 y_offset: 20.0,
116 blur: 10.0,
117 spread: 15.0,
118 count: 1,
119 samples: 6,
120 }
121 }
122}
123
124#[derive(Component, Clone, Default)]
125struct ShadowNode;
126
127#[derive(Component, Clone, Default)]
128struct SettingsPanel;
129
130#[derive(Component, Clone, Copy, Default, PartialEq, Eq, Debug)]
131enum AppNumberInputF32 {
132 #[default]
133 XOffset,
134 YOffset,
135 Blur,
136 Spread,
137}
138
139#[derive(Component, Clone, Copy, Default, PartialEq, Eq, Debug)]
140enum AppNumberInputI32 {
141 #[default]
142 Count,
143 Samples,
144}
145
146impl AppNumberInputF32 {
147 fn label(&self) -> &str {
148 match self {
149 AppNumberInputF32::XOffset => "X Offset",
150 AppNumberInputF32::YOffset => "Y Offset",
151 AppNumberInputF32::Blur => "Blur",
152 AppNumberInputF32::Spread => "Spread",
153 }
154 }
155}
156
157impl AppNumberInputI32 {
158 fn label(&self) -> &str {
159 match self {
160 AppNumberInputI32::Count => "Count (1 - 3)",
161 AppNumberInputI32::Samples => "Samples (0 - 15)",
162 }
163 }
164}
165
166fn main() {
167 App::new()
168 .add_plugins((DefaultPlugins, FeathersPlugins))
169 .insert_resource(UiTheme(get_example_theme()))
170 .init_resource::<AppSettings>()
171 .add_systems(Startup, setup)
172 .add_observer(on_value_change_i32_update_shadow)
173 .add_observer(on_value_change_f32_update_shadow)
174 .add_observer(on_value_change_update_shape)
175 .add_observer(radio_self_update)
176 .run();
177}
178
179fn setup(mut commands: Commands, app_settings: Res<AppSettings>) {
181 let mut node = Node {
183 width: px(164),
184 height: px(164),
185 border: UiRect::all(px(1)),
186 align_items: AlignItems::Center,
187 justify_content: JustifyContent::Center,
188 border_radius: BorderRadius::ZERO,
189 ..default()
190 };
191 app_settings.shape.change_node(&mut node);
192
193 commands.spawn_scene_list(bsn_list! {
194 Camera2d
196 BoxShadowSamples({app_settings.samples})
197 --
198 Node {
200 width: percent(100),
201 height: percent(100),
202 align_items: AlignItems::Center,
203 justify_content: JustifyContent::Center,
204 }
205 BackgroundColor(GRAY)
206 Children [
207 node
208 BorderColor::all(WHITE)
209 BackgroundColor(Color::srgb(0.21, 0.21, 0.21))
210 BoxShadow(vec![ShadowStyle {
211 color: Color::BLACK.with_alpha(0.8),
212 x_offset: px(app_settings.x_offset),
213 y_offset: px(app_settings.y_offset),
214 spread_radius: px(app_settings.spread),
215 blur_radius: px(app_settings.blur),
216 }])
217 ShadowNode
218 ]
219 --
220 @settings_panel_scene(&app_settings)
221 });
222}
223
224fn settings_panel_scene(app_settings: &AppSettings) -> impl Scene {
225 let selected_shape_index = SHAPE_OPTIONS
226 .iter()
227 .enumerate()
228 .find(|(_, (shape, _))| *shape == app_settings.shape)
229 .map_or(SHAPE_OPTIONS.len(), |(index, _)| index);
231
232 bsn! {
233 SettingsPanel
234 ZIndex(10)
235 @pane()
236 @main_ui_node_scene()
237 Children [
238 @pane_body()
239 Children [
240 @feathers_option_buttons(
241 "Shape",
242 &SHAPE_OPTIONS,
243 selected_shape_index,
244 )
245 --
246 @number_input_f32(
247 AppNumberInputF32::XOffset.label(),
248 Some(AppNumberInputF32::XOffset),
249 app_settings.x_offset,
250 NumberInputPrecision(0),
251 -200. ..=200.
252 )
253 --
254 @number_input_f32(
255 AppNumberInputF32::YOffset.label(),
256 Some(AppNumberInputF32::YOffset),
257 app_settings.y_offset,
258 NumberInputPrecision(0),
259 -200. ..=200.
260 )
261 --
262 @number_input_f32(
263 AppNumberInputF32::Blur.label(),
264 Some(AppNumberInputF32::Blur),
265 app_settings.blur,
266 NumberInputPrecision(0),
267 0. ..=100.
268 )
269 --
270 @number_input_f32(
271 AppNumberInputF32::Spread.label(),
272 Some(AppNumberInputF32::Spread),
273 app_settings.spread,
274 NumberInputPrecision(0),
275 -200. ..=200.
276 )
277 --
278 @number_input_i32(
279 AppNumberInputI32::Count.label(),
280 Some(AppNumberInputI32::Count),
281 app_settings.count as i32,
282 NumberInputPrecision(0),
283 1..=3
284 )
285 --
286 @number_input_i32(
287 AppNumberInputI32::Samples.label(),
288 Some(AppNumberInputI32::Samples),
289 app_settings.samples as i32,
290 NumberInputPrecision(0),
291 0..=15
292 )
293 --
294 @FeathersButton {
296 @caption: bsn! { @caption("Reset") }
297 }
298 on(on_activate_reset)
299 ]
300 ]
301 }
302}
303
304fn get_example_theme() -> ThemeProps {
305 let mut props = create_dark_theme();
306 if let Some(color) = props.semantic_base.get_mut(&SURFACE_PANE_BODY) {
308 color.set_alpha(0.9);
309 }
310 props
311}
312
313fn on_value_change_i32_update_shadow(
317 value_change: On<ValueChange<i32>>,
318 number_input_q: Query<&AppNumberInputI32, With<FeathersNumberInput>>,
319 mut commands: Commands,
320 mut app_settings: ResMut<AppSettings>,
321 mut box_shadow_q: Query<&mut BoxShadow, With<ShadowNode>>,
322 mut samples_q: Query<&mut BoxShadowSamples, With<Camera2d>>,
323) {
324 if let Ok(app_number_input_i32) = number_input_q.get(value_change.source) {
325 match app_number_input_i32 {
326 AppNumberInputI32::Count => {
327 app_settings.count = value_change.value as usize;
328 for mut box_shadow in &mut box_shadow_q {
329 *box_shadow = BoxShadow(generate_shadows(&app_settings));
330 }
331 }
332 AppNumberInputI32::Samples => {
333 app_settings.samples = value_change.value as u32;
334 for mut samples in &mut samples_q {
335 samples.0 = app_settings.samples;
336 }
337 }
338 }
339 }
340
341 commands
342 .entity(value_change.source)
343 .insert(NumberInputValue::I32(value_change.value));
344}
345
346fn on_value_change_f32_update_shadow(
348 value_change: On<ValueChange<f32>>,
349 number_input_q: Query<&AppNumberInputF32, With<FeathersNumberInput>>,
350 mut commands: Commands,
351 mut app_settings: ResMut<AppSettings>,
352 mut box_shadow_q: Query<&mut BoxShadow, With<ShadowNode>>,
353) {
354 if let Ok(app_number_input_i32) = number_input_q.get(value_change.source) {
355 match app_number_input_i32 {
356 AppNumberInputF32::XOffset => {
357 app_settings.x_offset = value_change.value;
358 }
359 AppNumberInputF32::YOffset => {
360 app_settings.y_offset = value_change.value;
361 }
362 AppNumberInputF32::Blur => {
363 app_settings.blur = value_change.value;
364 }
365 AppNumberInputF32::Spread => {
366 app_settings.spread = value_change.value;
367 }
368 }
369 for mut box_shadow in &mut box_shadow_q {
370 *box_shadow = BoxShadow(generate_shadows(&app_settings));
371 }
372 }
373
374 commands
375 .entity(value_change.source)
376 .insert(NumberInputValue::F32(value_change.value));
377}
378
379fn on_value_change_update_shape(
381 event: On<ValueChange<Entity>>,
382 new_value_query: Query<&RadioButtonOptionValue<Shape>>,
383 mut app_settings: ResMut<AppSettings>,
384 mut node_q: Query<&mut Node, With<ShadowNode>>,
385) {
386 let Ok(RadioButtonOptionValue(shape)) = new_value_query.get(event.value) else {
387 return;
388 };
389 app_settings.shape = *shape;
390
391 for mut node in &mut node_q {
392 app_settings.shape.change_node(&mut node);
393 }
394}
395
396fn on_activate_reset(
399 _event: On<Activate>,
400 mut commands: Commands,
401 mut app_settings: ResMut<AppSettings>,
402 settings_panel_q: Single<Entity, With<SettingsPanel>>,
403 mut node_q: Query<&mut Node, With<ShadowNode>>,
404 mut box_shadow_q: Query<&mut BoxShadow, With<ShadowNode>>,
405 mut samples_q: Query<&mut BoxShadowSamples, With<Camera2d>>,
406) {
407 *app_settings = AppSettings::default();
408
409 commands.entity(settings_panel_q.entity()).despawn();
412 commands.spawn_scene(settings_panel_scene(&app_settings));
413
414 for mut node in &mut node_q {
416 app_settings.shape.change_node(&mut node);
417 }
418
419 for mut box_shadow in &mut box_shadow_q {
420 *box_shadow = BoxShadow(generate_shadows(&app_settings));
421 }
422
423 for mut samples in &mut samples_q {
424 samples.0 = app_settings.samples;
425 }
426}
427
428fn generate_shadows(app_settings: &AppSettings) -> Vec<ShadowStyle> {
429 match app_settings.count {
430 1 => vec![make_shadow(
431 BLACK.into(),
432 app_settings.x_offset,
433 app_settings.y_offset,
434 app_settings.spread,
435 app_settings.blur,
436 )],
437 2 => vec![
438 make_shadow(
439 BLUE.into(),
440 app_settings.x_offset,
441 app_settings.y_offset,
442 app_settings.spread,
443 app_settings.blur,
444 ),
445 make_shadow(
446 YELLOW.into(),
447 -app_settings.x_offset,
448 -app_settings.y_offset,
449 app_settings.spread,
450 app_settings.blur,
451 ),
452 ],
453 3 => vec![
454 make_shadow(
455 BLUE.into(),
456 app_settings.x_offset,
457 app_settings.y_offset,
458 app_settings.spread,
459 app_settings.blur,
460 ),
461 make_shadow(
462 YELLOW.into(),
463 -app_settings.x_offset,
464 -app_settings.y_offset,
465 app_settings.spread,
466 app_settings.blur,
467 ),
468 make_shadow(
469 RED.into(),
470 app_settings.y_offset,
471 -app_settings.x_offset,
472 app_settings.spread,
473 app_settings.blur,
474 ),
475 ],
476 _ => vec![],
477 }
478}
479
480fn make_shadow(color: Color, x_offset: f32, y_offset: f32, spread: f32, blur: f32) -> ShadowStyle {
481 ShadowStyle {
482 color: color.with_alpha(0.8),
483 x_offset: px(x_offset),
484 y_offset: px(y_offset),
485 spread_radius: px(spread),
486 blur_radius: px(blur),
487 }
488}