use std::time::Duration;
use bevy::{color::palettes::css::*, prelude::*};
use bevy_inspector_egui::{bevy_egui::EguiPlugin, quick::WorldInspectorPlugin};
use bevy_tweening::{lens::*, *};
mod utils;
const NORMAL_COLOR: Color = Color::srgba(162. / 255., 226. / 255., 95. / 255., 1.);
const HOVER_COLOR: Color = Color::Srgba(AZURE);
const CLICK_COLOR: Color = Color::Srgba(ALICE_BLUE);
const TEXT_COLOR: Color = Color::srgba(83. / 255., 163. / 255., 130. / 255., 1.);
#[derive(Component)]
struct InitialAnimMarker;
fn main() {
App::default()
.add_plugins((
DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
title: "Menu".to_string(),
resolution: bevy::window::WindowResolution::new(800, 400),
present_mode: bevy::window::PresentMode::Fifo, ..default()
}),
..default()
}),
EguiPlugin::default(),
WorldInspectorPlugin::new(),
TweeningPlugin,
))
.add_systems(Update, utils::close_on_esc)
.add_systems(Update, interaction)
.add_systems(Startup, setup)
.run();
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2d);
let font = asset_server.load("fonts/FiraMono-Regular.ttf");
commands
.spawn((
Name::new("menu"),
Node {
position_type: PositionType::Absolute,
left: Val::Px(0.),
right: Val::Px(0.),
top: Val::Px(0.),
bottom: Val::Px(0.),
margin: UiRect::all(Val::Px(16.)),
padding: UiRect::all(Val::Px(16.)),
flex_direction: FlexDirection::Column,
align_content: AlignContent::Center,
align_items: AlignItems::Center,
align_self: AlignSelf::Center,
justify_content: JustifyContent::Center,
..default()
},
))
.with_children(|container| {
let mut start_time_ms = 0;
for (text, label) in [
("Continue", ButtonLabel::Continue),
("New Game", ButtonLabel::NewGame),
("Settings", ButtonLabel::Settings),
("Quit", ButtonLabel::Quit),
] {
let tween_scale = Tween::new(
EaseFunction::BounceOut,
Duration::from_secs(2),
UiTransformScaleLens {
start: Vec2::splat(0.01),
end: Vec2::ONE,
},
)
.with_cycle_completed_event(true);
let target = container
.spawn((
Name::new(format!("button:{}", text)),
Button,
Node {
min_width: Val::Px(300.),
min_height: Val::Px(80.),
margin: UiRect::all(Val::Px(8.)),
padding: UiRect::all(Val::Px(8.)),
align_content: AlignContent::Center,
align_items: AlignItems::Center,
align_self: AlignSelf::Center,
justify_content: JustifyContent::Center,
..default()
},
BackgroundColor(NORMAL_COLOR),
UiTransform::from_scale(Vec2::splat(0.01)),
label,
children![(
Text::new(text.to_string()),
TextFont {
font: font.clone(),
font_size: 48.0,
..default()
},
TextColor(TEXT_COLOR),
TextLayout::new_with_justify(Justify::Center),
)],
))
.id();
let tweenable = if start_time_ms > 0 {
let delay = Delay::new(Duration::from_millis(start_time_ms));
delay.then(tween_scale).into_boxed()
} else {
tween_scale.into_boxed()
};
container
.spawn((
InitialAnimMarker,
TweenAnim::new(tweenable),
AnimTarget::component::<UiTransform>(target),
))
.observe(enable_interaction_after_initial_animation);
start_time_ms += 500;
}
});
}
fn enable_interaction_after_initial_animation(
trigger: On<AnimCompletedEvent>,
mut commands: Commands,
q_names: Query<&Name>,
) {
if let AnimTargetKind::Component {
entity: target_entity,
} = &trigger.target
{
let name = q_names
.get(*target_entity)
.ok()
.map(Into::into)
.unwrap_or(format!("{:?}", target_entity));
println!("Button on entity {name} completed initial animation, activating...",);
let anim_entity = commands
.spawn(AnimTarget::component::<UiTransform>(*target_entity))
.id();
commands
.entity(*target_entity)
.insert(HoverAnim(anim_entity));
}
}
#[derive(Component)]
struct HoverAnim(pub Entity);
#[derive(Component, Clone, Copy)]
enum ButtonLabel {
Continue,
NewGame,
Settings,
Quit,
}
fn interaction(
mut commands: Commands,
mut interaction_query: Query<
(
&UiTransform,
&Interaction,
&mut BackgroundColor,
&ButtonLabel,
&HoverAnim,
),
Changed<Interaction>,
>,
) {
for (transform, interaction, mut color, button_label, hover_anim) in &mut interaction_query {
let anim_entity = hover_anim.0;
match *interaction {
Interaction::Pressed => {
*color = CLICK_COLOR.into();
match button_label {
ButtonLabel::Continue => {
println!("Continue clicked");
}
ButtonLabel::NewGame => {
println!("NewGame clicked");
}
ButtonLabel::Settings => {
println!("Settings clicked");
}
ButtonLabel::Quit => {
println!("Quit clicked");
}
}
}
Interaction::Hovered => {
*color = HOVER_COLOR.into();
let tween = Tween::new(
EaseFunction::QuadraticIn,
Duration::from_millis(200),
UiTransformScaleLens {
start: transform.scale,
end: Vec2::splat(1.1),
},
);
commands.entity(anim_entity).insert(TweenAnim::new(tween));
}
Interaction::None => {
*color = NORMAL_COLOR.into();
let tween = Tween::new(
EaseFunction::QuadraticIn,
Duration::from_millis(200),
UiTransformScaleLens {
start: transform.scale,
end: Vec2::ONE,
},
);
commands.entity(anim_entity).insert(TweenAnim::new(tween));
}
}
}
}