1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
use std::time::Duration;

use bevy::{prelude::*, utils::Instant};
use bevy_asset_loader::asset_collection::AssetCollection;
use keyframe::functions::*;

use crate::AppState;

const FADE_IN_TIME: f32 = 2.5;
const ZOOM_OUT_TIME: f32 = 2.0;
const FULL_FADE_FINISHED: f32 = 4.0;
const FADE_OUT_TIME: f32 = 1.0;
const QUIT_TIME: f32 = 0.5;
const LOGO_FINISHED_SCALE: f32 = 0.2;

pub struct LogoPlugin;

#[derive(AssetCollection, Resource)]
pub struct LogoAssets {
    #[asset(path = "logo.ktx2")]
    logo_texture: Handle<Image>,
}

#[derive(Resource)]
struct LogoData {
    logo_entity: Entity,
    camera_entity: Entity,
}

impl Plugin for LogoPlugin {
    fn build(&self, app: &mut App) {
        app.add_systems(OnEnter(AppState::Logo), load_logo)
            .add_systems(Update, fade_in_logo.run_if(in_state(AppState::Logo)))
            .add_systems(OnExit(AppState::Logo), cleanup_logo);
    }
}

#[derive(Component)]
struct LogoTimer(Instant, Instant);

impl LogoTimer {
    fn elapsed_secs(&self) -> f32 {
        self.0.elapsed().as_secs_f32()
    }

    fn finished(&self) -> bool {
        Instant::now() > self.1
    }
}

fn load_logo(mut commands: Commands, assets: Res<LogoAssets>) {
    let now = Instant::now();
    commands.insert_resource(ClearColor(Color::BLACK));
    let sprite_entity = commands
        .spawn((
            SpriteBundle {
                texture: assets.logo_texture.clone(),
                sprite: Sprite {
                    color: Color::rgba(1.0, 1.0, 1.0, 0.0),
                    ..Default::default()
                },
                transform: Transform::from_scale(Vec3::ONE),
                ..Default::default()
            },
            LogoTimer(
                now,
                now + Duration::from_secs_f32(QUIT_TIME + FULL_FADE_FINISHED + FADE_OUT_TIME),
            ),
        ))
        .id();
    let camera_entity = commands.spawn(Camera2dBundle::default()).id();
    commands.insert_resource(LogoData {
        logo_entity: sprite_entity,
        camera_entity,
    });
}

fn fade_in_logo(
    mut query: Query<(&mut Sprite, &mut Transform, &LogoTimer)>,
    mut next_state: ResMut<NextState<AppState>>,
    keyboard_input: Res<ButtonInput<KeyCode>>,
) {
    let (mut sprite, mut transform, timer) = query.single_mut();
    let elapsed = timer.elapsed_secs();

    if timer.finished() || keyboard_input.get_pressed().len() > 0 {
        next_state.set(AppState::LoadingMenu);
    } else if elapsed > FULL_FADE_FINISHED {
        sprite.color.set_a(keyframe::ease_with_scaled_time(
            EaseInCubic,
            FULL_FADE_FINISHED,
            0.0,
            elapsed,
            FADE_OUT_TIME + FULL_FADE_FINISHED,
        ));
    } else {
        sprite.color.set_a(keyframe::ease_with_scaled_time(
            Linear,
            0.0,
            1.0,
            elapsed,
            FADE_IN_TIME,
        ));
        transform.scale = Vec3::splat(keyframe::ease_with_scaled_time(
            EaseOutQuint,
            2.0,
            LOGO_FINISHED_SCALE,
            elapsed,
            ZOOM_OUT_TIME,
        ));
    }
}

fn cleanup_logo(mut commands: Commands, logo_data: Res<LogoData>) {
    commands.entity(logo_data.logo_entity).despawn_recursive();
    commands.entity(logo_data.camera_entity).despawn_recursive();
    commands.remove_resource::<LogoData>();
    commands.remove_resource::<LogoAssets>();
}