From 15c5bdbd9f1e7a425514f592b795d660a767ca9c Mon Sep 17 00:00:00 2001 From: shuo <shuoli84@gmail.com> Date: Sun, 2 Oct 2022 18:41:12 +0800 Subject: [PATCH] Add an example of menu hover animation (#59) --- examples/menu.rs | 119 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 114 insertions(+), 5 deletions(-) diff --git a/examples/menu.rs b/examples/menu.rs index 3156ff9..731b49a 100644 --- a/examples/menu.rs +++ b/examples/menu.rs @@ -3,6 +3,22 @@ use bevy_inspector_egui::WorldInspectorPlugin; use bevy_tweening::{lens::*, *}; use std::time::Duration; +const NORMAL_COLOR: Color = Color::rgba(162. / 255., 226. / 255., 95. / 255., 1.); +const HOVER_COLOR: Color = Color::AZURE; +const CLICK_COLOR: Color = Color::ALICE_BLUE; +const TEXT_COLOR: Color = Color::rgba(83. / 255., 163. / 255., 130. / 255., 1.); +const INIT_TRANSITION_DONE: u64 = 1; + +/// The menu in this example has two set of animations: +/// one for appearance, one for interaction. Interaction animations +/// are only enabled after appearance animations finished. +/// +/// The logic is handled as: +/// 1. Appearance animations send a `TweenComplete` event with `INIT_TRANSITION_DONE` +/// 2. The `enable_interaction_after_initial_animation` system adds a label component +/// `InitTransitionDone` to any button component which completed its appearance animation, +/// to mark it as active. +/// 3. The `interaction` system only queries buttons with a `InitTransitionDone` marker. fn main() { App::default() .insert_resource(WindowDescriptor { @@ -14,6 +30,8 @@ fn main() { }) .add_plugins(DefaultPlugins) .add_system(bevy::window::close_on_esc) + .add_system(interaction) + .add_system(enable_interaction_after_initial_animation) .add_plugin(TweeningPlugin) .add_plugin(WorldInspectorPlugin::new()) .add_startup_system(setup) @@ -45,7 +63,12 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) { .insert(Name::new("menu")) .with_children(|container| { let mut start_time_ms = 0; - for text in &["Continue", "New Game", "Settings", "Quit"] { + 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), @@ -53,16 +76,19 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) { start: Vec3::splat(0.01), end: Vec3::ONE, }, - ); + ) + .with_completed_event(INIT_TRANSITION_DONE); + let animator = if start_time_ms > 0 { let delay = Delay::new(Duration::from_millis(start_time_ms)); Animator::new(delay.then(tween_scale)) } else { Animator::new(tween_scale) }; + start_time_ms += 500; container - .spawn_bundle(NodeBundle { + .spawn_bundle(ButtonBundle { node: Node { size: Vec2::new(300., 80.), }, @@ -76,12 +102,13 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) { justify_content: JustifyContent::Center, ..default() }, - color: UiColor(Color::rgb_u8(162, 226, 95)), + color: UiColor(NORMAL_COLOR), transform: Transform::from_scale(Vec3::splat(0.01)), ..default() }) .insert(Name::new(format!("button:{}", text))) .insert(animator) + .insert(label) .with_children(|parent| { parent.spawn_bundle(TextBundle { text: Text::from_section( @@ -89,7 +116,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) { TextStyle { font: font.clone(), font_size: 48.0, - color: Color::rgb_u8(83, 163, 130), + color: TEXT_COLOR, }, ) .with_alignment(TextAlignment { @@ -102,3 +129,85 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) { } }); } + +fn enable_interaction_after_initial_animation( + mut commands: Commands, + mut reader: EventReader<TweenCompleted>, +) { + for event in reader.iter() { + if event.user_data == INIT_TRANSITION_DONE { + commands.entity(event.entity).insert(InitTransitionDone); + } + } +} + +#[derive(Component)] +struct InitTransitionDone; + +#[derive(Component, Clone, Copy)] +enum ButtonLabel { + Continue, + NewGame, + Settings, + Quit, +} + +fn interaction( + mut interaction_query: Query< + ( + &mut Animator<Transform>, + &Transform, + &Interaction, + &mut UiColor, + &ButtonLabel, + ), + (Changed<Interaction>, With<InitTransitionDone>), + >, +) { + for (mut animator, transform, interaction, mut color, button_label) in &mut interaction_query { + match *interaction { + Interaction::Clicked => { + *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(); + animator.set_tweenable(Tween::new( + EaseFunction::QuadraticIn, + Duration::from_millis(200), + TransformScaleLens { + start: Vec3::ONE, + end: Vec3::splat(1.1), + }, + )); + } + Interaction::None => { + *color = NORMAL_COLOR.into(); + let start_scale = transform.scale; + + animator.set_tweenable(Tween::new( + EaseFunction::QuadraticIn, + Duration::from_millis(200), + TransformScaleLens { + start: start_scale, + end: Vec3::ONE, + }, + )); + } + } + } +} -- GitLab