use bevy::{app::AppExit, prelude::*};
use bevy_round_ui::prelude::*;
fn main() {
App::new()
.add_plugins((DefaultPlugins, BevyRoundUiDefaultPlugins))
.init_resource::<ButtonStyle>()
.add_systems(Startup, setup)
.add_systems(Update, (handle_button_interactions, handle_button_actions))
.run();
}
const PANEL_BACKGROUND_COLOR: &str = "#5cb3af";
const PANEL_BORDER_COLOR: &str = "#ffffff";
const PANEL_WIDTH: f32 = 300.0;
const BUTTON_HEIGHT: f32 = 40.0;
const BUTTON_OFFSET_SIZE: f32 = 5.0;
#[derive(Resource)]
pub struct ButtonStyle {
pub default_material: Handle<RoundRectUiMaterial>,
pub default_padding: UiRect,
pub hover_material: Handle<RoundRectUiMaterial>,
pub hover_padding: UiRect,
pub press_material: Handle<RoundRectUiMaterial>,
pub press_padding: UiRect,
}
impl FromWorld for ButtonStyle {
fn from_world(world: &mut World) -> Self {
let cell = world.cell();
let mut materials = cell
.get_resource_mut::<Assets<RoundRectUiMaterial>>()
.expect("Failed to get Assets<RoundRectMaterial>");
let border_radius = RoundUiBorder::all(15.);
Self {
default_material: materials.add(RoundRectUiMaterial {
background_color: Color::hex("#F76161").unwrap(),
border_color: Color::hex("#A53A3D").unwrap(),
border_radius: border_radius.into(),
offset: RoundUiOffset::bottom(BUTTON_OFFSET_SIZE).into(),
}),
default_padding: UiRect::bottom(Val::Px(BUTTON_OFFSET_SIZE)),
hover_material: materials.add(RoundRectUiMaterial {
background_color: Color::hex("#F61A39").unwrap(),
border_color: Color::hex("#A0102A").unwrap(),
border_radius: border_radius.into(),
offset: RoundUiOffset::bottom(BUTTON_OFFSET_SIZE).into(),
}),
hover_padding: UiRect::bottom(Val::Px(BUTTON_OFFSET_SIZE)),
press_material: materials.add(RoundRectUiMaterial {
background_color: Color::hex("#A0102A").unwrap(),
border_color: Color::NONE,
border_radius: border_radius.into(),
offset: RoundUiOffset::top(BUTTON_OFFSET_SIZE).into(),
}),
press_padding: UiRect::top(Val::Px(BUTTON_OFFSET_SIZE)),
}
}
}
#[derive(Component, Debug)]
enum ButtonAction {
Play,
Settings,
Quit,
}
#[derive(Component)]
pub struct RoundButton;
fn setup(
mut commands: Commands,
button_style: Res<ButtonStyle>,
mut superellipse_materials: ResMut<Assets<SuperellipseUiMaterial>>,
) {
commands.spawn(Camera2dBundle::default());
let panel_material = superellipse_materials.add(SuperellipseUiMaterial {
background_color: Color::hex(PANEL_BACKGROUND_COLOR).unwrap(),
border_color: Color::hex(PANEL_BORDER_COLOR).unwrap(),
border_radius: RoundUiBorder::all(20.0).into(),
border_thickness: 6.0,
});
commands
.spawn(NodeBundle {
style: Style {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
..default()
},
..default()
})
.with_children(|p| {
p.spawn(MaterialNodeBundle {
material: panel_material,
style: Style {
width: Val::Px(PANEL_WIDTH),
padding: UiRect::axes(Val::Px(40.), Val::Px(60.)),
flex_direction: FlexDirection::Column,
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
..default()
},
..default()
})
.with_children(|p| {
p.spawn(NodeBundle {
style: Style {
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
margin: UiRect::bottom(Val::Px(40.)),
..default()
},
..default()
})
.with_children(|p| {
p.spawn(TextBundle::from_section(
"MENU",
TextStyle {
color: Color::WHITE,
font_size: 40.,
..default()
},
));
});
spawn_button(p, &button_style, "Play", ButtonAction::Play);
spawn_button(p, &button_style, "Settings", ButtonAction::Settings);
spawn_button(p, &button_style, "Quit", ButtonAction::Quit);
});
});
}
fn spawn_button(
parent: &mut ChildBuilder,
button_style: &ButtonStyle,
text: impl Into<String>,
extras: impl Bundle,
) -> Entity {
parent
.spawn((
RoundButton,
MaterialNodeBundle {
material: button_style.default_material.clone(),
style: Style {
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
width: Val::Percent(100.),
height: Val::Px(BUTTON_HEIGHT),
margin: UiRect::top(Val::Px(10.)),
..default()
},
..default()
},
extras,
Interaction::default(),
))
.with_children(|p| {
p.spawn(TextBundle::from_section(
text,
TextStyle {
color: Color::WHITE,
font_size: 20.,
..default()
},
));
})
.id()
}
#[allow(clippy::type_complexity)]
fn handle_button_interactions(
mut interaction_query: Query<
(&Interaction, &mut Handle<RoundRectUiMaterial>, &mut Style),
(Changed<Interaction>, With<RoundButton>),
>,
button_style: Res<ButtonStyle>,
) {
for (interaction, mut material, mut style) in &mut interaction_query {
let (material_handle, padding) = match *interaction {
Interaction::Pressed => (
button_style.press_material.clone(),
button_style.press_padding,
),
Interaction::Hovered => (
button_style.hover_material.clone(),
button_style.hover_padding,
),
Interaction::None => (
button_style.default_material.clone(),
button_style.default_padding,
),
};
*material = material_handle;
style.padding = padding;
}
}
fn handle_button_actions(
interaction_query: Query<(&Interaction, &ButtonAction), Changed<Interaction>>,
mut app_exit_events: EventWriter<AppExit>,
) {
for (interaction, action) in &interaction_query {
if *interaction == Interaction::Pressed {
println!("Button pressed: {action:?}");
match action {
ButtonAction::Play => (),
ButtonAction::Settings => (),
ButtonAction::Quit => {
app_exit_events.send(AppExit);
}
}
}
}
}