numples 1.2.0

Yet another colourful sudoku playing game.
Documentation
use bevy::prelude::*;

use crate::consts::{
    APP_VERSION,
    SELECTED_COLOR,
    TITLE,
    TITLE_COLOR,
    UNSELECTED_COLOR,
    VERSION_COLOR,
};
use crate::events::NumplesEvent;
use crate::fonts::TitleFont;
use crate::game::Level;
use crate::gameover::GameOverCheck;
use crate::gameplay::{Gameplay, MustResetClock, Paused};
use crate::persistence::Persistence;
use crate::states::GameState;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Component)]
pub struct TitleScene;

impl TitleScene {
    pub fn load_title_scene(
        mut commands: Commands,
        title_font: Res<TitleFont>,
        mut game_over: ResMut<GameOverCheck>,
    ) {
        game_over.0 = false;

        // Title Label
        commands.spawn((
            Self,
            Text::new(TITLE.to_string()),
            TextFont {
                font: title_font.font().clone_weak(),
                font_size: 48.0,
                ..default()
            },
            TextShadow::default(),
            TextColor(TITLE_COLOR.clone()),
            TextLayout::new_with_justify(JustifyText::Center),
            Node {
                position_type: PositionType::Absolute,
                top: Val::Px(0.0),
                width: Val::Percent(100.0),
                ..default()
            },
        ));

        commands.spawn((
            Self,
            Text::new(APP_VERSION.to_string()),
            TextFont {
                font: title_font.clone_weak(),
                font_size: 18.0,
                ..default()
            },
            TextColor(VERSION_COLOR),
            TextLayout::new_with_justify(JustifyText::Right),
            Node {
                position_type: PositionType::Absolute,
                top: Val::Px(50.0),
                width: Val::Percent(83.5),
                ..default()
            },
        ));

        let mut y = 100. / 6.;
        for level in Level::levels() {
            let level_number: u8 = level.into();
            commands
                .spawn((
                    Self,
                    level,
                    Text::new(format!("{}. {}", level_number, level.to_string())),
                    TextFont {
                        font: title_font.font().clone_weak(),
                        font_size: 32.0,
                        ..default()
                    },
                    TextColor(UNSELECTED_COLOR.clone()),
                    TextLayout::new_with_justify(JustifyText::Center),
                    Node {
                        position_type: PositionType::Absolute,
                        align_items: AlignItems::Center,
                        top: Val::Percent(y),
                        width: Val::Percent(100.0),
                        height: Val::Percent(12.0),
                        ..default()
                    },
                ))
                .observe(Self::pointer_over_observer)
                .observe(Self::pointer_out_observer)
                .observe(Self::pointer_click_observer)
            ;
            y += 100. / 8.;
        }
        commands
            .spawn((
                Self,
                Text::new("C-o. Load Board"),
                TextFont {
                    font: title_font.font().clone_weak(),
                    font_size: 32.0,
                    ..default()
                },
                TextColor(UNSELECTED_COLOR.clone()),
                TextLayout::new_with_justify(JustifyText::Center),
                Node {
                    position_type: PositionType::Absolute,
                    align_items: AlignItems::Center,
                    top: Val::Percent(y),
                    width: Val::Percent(100.0),
                    height: Val::Percent(12.0),
                    ..default()
                },
            ))
            .observe(Self::pointer_over_observer)
            .observe(Self::pointer_out_observer)
            .observe(Self::load_file_observer)
        ;
    }

    fn pointer_over_observer(
        mut trigger: Trigger<Pointer<Over>>,
        mut query: Query<(&Self, &mut TextColor, &mut BackgroundColor)>,
    ) {
        if let Ok((_, mut text_color, mut bg)) = query.get_mut(trigger.target()) {
            text_color.0 = SELECTED_COLOR.clone();
            bg.0 = Color::Srgba(Srgba { red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0 });
        }
        trigger.propagate(false);
    }

    fn pointer_out_observer(
        mut trigger: Trigger<Pointer<Out>>,
        mut query: Query<(&Self, &mut TextColor, &mut BackgroundColor)>,
    ) {
        if let Ok((_, mut text_color, mut bg)) = query.get_mut(trigger.target()) {
            text_color.0 = UNSELECTED_COLOR.clone();
            bg.0 = Color::Srgba(Srgba { red: 1.0, green: 1.0, blue: 1.0, alpha: 0.0 });
        }
        trigger.propagate(true);
    }

    fn pointer_click_observer(
        mut trigger: Trigger<Pointer<Click>>,
        query: Query<(&Self, &Level)>,
        mut event_writer: EventWriter<NumplesEvent>,
    ) {
        if let Ok((_, &level)) = query.get(trigger.target()) {
            event_writer.write(NumplesEvent::StartGame(level));
        }
        trigger.propagate(false);
    }

    fn load_file_observer(
        mut trigger: Trigger<Pointer<Click>>,
        mut persistence: ResMut<Persistence>,
    ) {
        persistence.load = true;
        trigger.propagate(false);
    }

    pub fn unload_title_scene(
        mut commands: Commands,
        entities: Query<Entity, With<Self>>,
    ) {
        for entity in &entities {
            commands.entity(entity).despawn();
        }
    }

    pub fn event_handle(
        mut commands: Commands,
        mut events: EventReader<NumplesEvent>,
        mut next_state: ResMut<NextState<GameState>>,
        mut paused: ResMut<Paused>,
    ) {
        for event in events.read() {
            match event {
                NumplesEvent::StartGame(level) => {
                    commands.spawn((Gameplay, *level));
                    commands.spawn(MustResetClock);
                    paused.0 = false;
                    next_state.set(GameState::Playing);
                }

                _ => (),
            }
        }
    }
}