//! This example shows how to create a node with a shadow and adjust its settings interactively. use bevy::{ color::palettes::css::*, prelude::*, time::Time, window::RequestRedraw, winit::WinitSettings, }; const NORMAL_BUTTON: Color = Color::srgb(0.15, 0.15, 0.15); const HOVERED_BUTTON: Color = Color::srgb(0.25, 0.25, 0.25); const PRESSED_BUTTON: Color = Color::srgb(0.35, 0.75, 0.35); const SHAPE_DEFAULT_SETTINGS: ShapeSettings = ShapeSettings { index: 0 }; const SHADOW_DEFAULT_SETTINGS: ShadowSettings = ShadowSettings { x_offset: 20.0, y_offset: 20.0, blur: 10.0, spread: 15.0, count: 1, samples: 6, }; const SHAPES: &[(&str, fn(&mut Node, &mut BorderRadius))] = &[ ("1", |node, radius| { node.width = Val::Px(164.); node.height = Val::Px(164.); *radius = BorderRadius::ZERO; }), ("2", |node, radius| { node.width = Val::Px(164.); node.height = Val::Px(164.); *radius = BorderRadius::all(Val::Px(41.)); }), ("3", |node, radius| { node.width = Val::Px(164.); node.height = Val::Px(164.); *radius = BorderRadius::MAX; }), ("4", |node, radius| { node.width = Val::Px(240.); node.height = Val::Px(80.); *radius = BorderRadius::all(Val::Px(32.)); }), ("5", |node, radius| { node.width = Val::Px(80.); node.height = Val::Px(240.); *radius = BorderRadius::all(Val::Px(32.)); }), ]; #[derive(Resource, Default)] struct ShapeSettings { index: usize, } #[derive(Resource, Default)] struct ShadowSettings { x_offset: f32, y_offset: f32, blur: f32, spread: f32, count: usize, samples: u32, } #[derive(Component)] struct ShadowNode; #[derive(Component, PartialEq, Clone, Copy)] enum SettingsButton { XOffsetInc, XOffsetDec, YOffsetInc, YOffsetDec, BlurInc, BlurDec, SpreadInc, SpreadDec, CountInc, CountDec, ShapePrev, ShapeNext, Reset, SamplesInc, SamplesDec, } #[derive(Component, Clone, Copy, PartialEq, Eq, Debug)] enum SettingType { XOffset, YOffset, Blur, Spread, Count, Shape, Samples, } impl SettingType { fn label(&self) -> &str { match self { SettingType::XOffset => "X Offset", SettingType::YOffset => "Y Offset", SettingType::Blur => "Blur", SettingType::Spread => "Spread", SettingType::Count => "Count", SettingType::Shape => "Shape", SettingType::Samples => "Samples", } } } #[derive(Resource, Default)] struct HeldButton { button: Option, pressed_at: Option, last_repeat: Option, } fn main() { App::new() .add_plugins(DefaultPlugins) .insert_resource(WinitSettings::desktop_app()) .insert_resource(SHADOW_DEFAULT_SETTINGS) .insert_resource(SHAPE_DEFAULT_SETTINGS) .insert_resource(HeldButton::default()) .add_systems(Startup, setup) .add_systems( Update, ( button_system, button_color_system, update_shape.run_if(resource_changed::), update_shadow.run_if(resource_changed::), update_shadow_samples.run_if(resource_changed::), button_repeat_system, ), ) .run(); } // --- UI Setup --- fn setup( mut commands: Commands, asset_server: Res, shadow: Res, shape: Res, ) { commands.spawn((Camera2d, BoxShadowSamples(shadow.samples))); // Spawn shape node commands .spawn(( Node { width: Val::Percent(100.0), height: Val::Percent(100.0), align_items: AlignItems::Center, justify_content: JustifyContent::Center, ..default() }, BackgroundColor(GRAY.into()), )) .insert(children![{ let mut node = Node { width: Val::Px(164.), height: Val::Px(164.), border: UiRect::all(Val::Px(1.)), align_items: AlignItems::Center, justify_content: JustifyContent::Center, ..default() }; let mut radius = BorderRadius::ZERO; SHAPES[shape.index % SHAPES.len()].1(&mut node, &mut radius); ( node, BorderColor::all(WHITE.into()), radius, BackgroundColor(Color::srgb(0.21, 0.21, 0.21)), BoxShadow(vec![ShadowStyle { color: Color::BLACK.with_alpha(0.8), x_offset: Val::Px(shadow.x_offset), y_offset: Val::Px(shadow.y_offset), spread_radius: Val::Px(shadow.spread), blur_radius: Val::Px(shadow.blur), }]), ShadowNode, ) }]); // Settings Panel commands .spawn(( Node { flex_direction: FlexDirection::Column, position_type: PositionType::Absolute, left: Val::Px(24.0), bottom: Val::Px(24.0), width: Val::Px(270.0), padding: UiRect::all(Val::Px(16.0)), ..default() }, BackgroundColor(Color::srgb(0.12, 0.12, 0.12).with_alpha(0.85)), BorderColor::all(Color::WHITE.with_alpha(0.15)), BorderRadius::all(Val::Px(12.0)), ZIndex(10), )) .insert(children![ build_setting_row( SettingType::Shape, SettingsButton::ShapePrev, SettingsButton::ShapeNext, shape.index as f32, &asset_server, ), build_setting_row( SettingType::XOffset, SettingsButton::XOffsetDec, SettingsButton::XOffsetInc, shadow.x_offset, &asset_server, ), build_setting_row( SettingType::YOffset, SettingsButton::YOffsetDec, SettingsButton::YOffsetInc, shadow.y_offset, &asset_server, ), build_setting_row( SettingType::Blur, SettingsButton::BlurDec, SettingsButton::BlurInc, shadow.blur, &asset_server, ), build_setting_row( SettingType::Spread, SettingsButton::SpreadDec, SettingsButton::SpreadInc, shadow.spread, &asset_server, ), build_setting_row( SettingType::Count, SettingsButton::CountDec, SettingsButton::CountInc, shadow.count as f32, &asset_server, ), // Add BoxShadowSamples as a setting row build_setting_row( SettingType::Samples, SettingsButton::SamplesDec, SettingsButton::SamplesInc, shadow.samples as f32, &asset_server, ), // Reset button ( Node { flex_direction: FlexDirection::Row, align_items: AlignItems::Center, height: Val::Px(36.0), margin: UiRect::top(Val::Px(12.0)), ..default() }, children![( Button, Node { width: Val::Px(90.), height: Val::Px(32.), justify_content: JustifyContent::Center, align_items: AlignItems::Center, ..default() }, BackgroundColor(NORMAL_BUTTON), BorderRadius::all(Val::Px(8.)), SettingsButton::Reset, children![( Text::new("Reset"), TextFont { font: asset_server.load("fonts/FiraSans-Bold.ttf"), font_size: 16.0, ..default() }, )], )], ), ]); } // --- UI Helper Functions --- // Helper to return an input to the children! macro for a setting row fn build_setting_row( setting_type: SettingType, dec: SettingsButton, inc: SettingsButton, value: f32, asset_server: &Res, ) -> impl Bundle { let value_text = match setting_type { SettingType::Shape => SHAPES[value as usize % SHAPES.len()].0.to_string(), SettingType::Count => format!("{}", value as usize), _ => format!("{value:.1}"), }; ( Node { flex_direction: FlexDirection::Row, align_items: AlignItems::Center, height: Val::Px(32.0), ..default() }, children![ ( Node { width: Val::Px(80.0), justify_content: JustifyContent::FlexEnd, align_items: AlignItems::Center, ..default() }, // Attach SettingType to the value label node, not the parent row children![( Text::new(setting_type.label()), TextFont { font: asset_server.load("fonts/FiraSans-Bold.ttf"), font_size: 16.0, ..default() }, )], ), ( Button, Node { width: Val::Px(28.), height: Val::Px(28.), margin: UiRect::left(Val::Px(8.)), justify_content: JustifyContent::Center, align_items: AlignItems::Center, ..default() }, BackgroundColor(Color::WHITE), BorderRadius::all(Val::Px(6.)), dec, children![( Text::new(if setting_type == SettingType::Shape { "<" } else { "-" }), TextFont { font: asset_server.load("fonts/FiraSans-Bold.ttf"), font_size: 18.0, ..default() }, )], ), ( Node { width: Val::Px(48.), height: Val::Px(28.), margin: UiRect::horizontal(Val::Px(8.)), justify_content: JustifyContent::Center, align_items: AlignItems::Center, ..default() }, BorderRadius::all(Val::Px(6.)), children![{ ( Text::new(value_text), TextFont { font: asset_server.load("fonts/FiraSans-Bold.ttf"), font_size: 16.0, ..default() }, setting_type, ) }], ), ( Button, Node { width: Val::Px(28.), height: Val::Px(28.), justify_content: JustifyContent::Center, align_items: AlignItems::Center, ..default() }, BackgroundColor(Color::WHITE), BorderRadius::all(Val::Px(6.)), inc, children![( Text::new(if setting_type == SettingType::Shape { ">" } else { "+" }), TextFont { font: asset_server.load("fonts/FiraSans-Bold.ttf"), font_size: 18.0, ..default() }, )], ), ], ) } // --- SYSTEMS --- // Update the shadow node's BoxShadow on resource changes fn update_shadow( shadow: Res, mut query: Query<&mut BoxShadow, With>, mut label_query: Query<(&mut Text, &SettingType)>, ) { for mut box_shadow in &mut query { *box_shadow = BoxShadow(generate_shadows(&shadow)); } // Update value labels for shadow settings for (mut text, setting) in &mut label_query { let value = match setting { SettingType::XOffset => format!("{:.1}", shadow.x_offset), SettingType::YOffset => format!("{:.1}", shadow.y_offset), SettingType::Blur => format!("{:.1}", shadow.blur), SettingType::Spread => format!("{:.1}", shadow.spread), SettingType::Count => format!("{}", shadow.count), SettingType::Shape => continue, SettingType::Samples => format!("{}", shadow.samples), }; *text = Text::new(value); } } fn update_shadow_samples( shadow: Res, mut query: Query<&mut BoxShadowSamples, With>, ) { for mut samples in &mut query { samples.0 = shadow.samples; } } fn generate_shadows(shadow: &ShadowSettings) -> Vec { match shadow.count { 1 => vec![make_shadow( BLACK.into(), shadow.x_offset, shadow.y_offset, shadow.spread, shadow.blur, )], 2 => vec![ make_shadow( BLUE.into(), shadow.x_offset, shadow.y_offset, shadow.spread, shadow.blur, ), make_shadow( YELLOW.into(), -shadow.x_offset, -shadow.y_offset, shadow.spread, shadow.blur, ), ], 3 => vec![ make_shadow( BLUE.into(), shadow.x_offset, shadow.y_offset, shadow.spread, shadow.blur, ), make_shadow( YELLOW.into(), -shadow.x_offset, -shadow.y_offset, shadow.spread, shadow.blur, ), make_shadow( RED.into(), shadow.y_offset, -shadow.x_offset, shadow.spread, shadow.blur, ), ], _ => vec![], } } fn make_shadow(color: Color, x_offset: f32, y_offset: f32, spread: f32, blur: f32) -> ShadowStyle { ShadowStyle { color: color.with_alpha(0.8), x_offset: Val::Px(x_offset), y_offset: Val::Px(y_offset), spread_radius: Val::Px(spread), blur_radius: Val::Px(blur), } } // Update shape of ShadowNode if shape selection changed fn update_shape( shape: Res, mut query: Query<(&mut Node, &mut BorderRadius), With>, mut label_query: Query<(&mut Text, &SettingType)>, ) { for (mut node, mut radius) in &mut query { SHAPES[shape.index % SHAPES.len()].1(&mut node, &mut radius); } for (mut text, kind) in &mut label_query { if *kind == SettingType::Shape { *text = Text::new(SHAPES[shape.index % SHAPES.len()].0); } } } // Handles button interactions for all settings fn button_system( mut interaction_query: Query< (&Interaction, &SettingsButton), (Changed, With