tmaze 1.15.1

Simple multiplatform maze solving game for terminal written entirely in Rust
Documentation
use std::time::{Duration, Instant};

use cmaze::core::Dims;

use crossterm::event::{read, KeyCode, KeyEvent, KeyEventKind};

use crate::{
    data::SaveData,
    logging,
    renderer::{drawable::Drawable, Renderer},
    settings::Settings,
};

#[cfg(feature = "sound")]
use crate::sound::{track::MusicTrack, SoundPlayer};

#[cfg(feature = "sound")]
use rodio::Source;

use super::{
    activity::{Activities, Activity, ActivityResult, Change},
    event::Event,
};

#[allow(dead_code)]
pub struct App {
    renderer: Renderer,
    activities: Activities,
    data: AppData,
}

pub struct AppData {
    pub settings: Settings,
    pub save: SaveData,
    pub use_data: AppStateData,
    pub screen_size: Dims,
    app_start: Instant,

    #[cfg(feature = "sound")]
    sound_player: SoundPlayer,
    #[cfg(feature = "sound")]
    bgm_track: Option<MusicTrack>,
}

impl AppData {
    pub fn from_start(&self) -> Duration {
        self.app_start.elapsed()
    }

    #[cfg(feature = "sound")]
    pub fn play_bgm(&mut self, track: MusicTrack) {
        if let Some(prev_track) = self.bgm_track {
            if prev_track == track {
                return;
            }
        }

        if !self.settings.get_enable_audio() || !self.settings.get_enable_music() {
            return;
        }

        let volume = self.settings.get_audio_volume() * self.settings.get_music_volume();
        self.sound_player.sink().set_volume(volume);

        self.bgm_track = Some(track);
        let track = track.get_track().repeat_infinite();
        self.sound_player.play_track(Box::new(track));
    }
}

impl App {
    pub fn new(base_activity: Activity) -> Self {
        let mut s = Self::empty();
        s.activities.push(base_activity);
        s
    }

    pub fn empty() -> Self {
        let renderer = Renderer::new().expect("Failed to create renderer");
        let activities = Activities::empty();

        let settings = Settings::load(Settings::default_path()).expect("Failed to load settings");
        let save = SaveData::load().expect("Failed to load save data");
        let use_data = AppStateData::default();
        let app_start = Instant::now();
        let frame_size = renderer.frame_size();

        logging::init();

        #[cfg(feature = "sound")]
        let sound_player = SoundPlayer::new();

        Self {
            renderer,
            activities,
            data: AppData {
                app_start,
                settings,
                save,
                use_data,
                screen_size: frame_size,

                #[cfg(feature = "sound")]
                sound_player,
                #[cfg(feature = "sound")]
                bgm_track: None,
            },
        }
    }

    pub fn run(&mut self) -> Option<ActivityResult> {
        log::trace!("Starting main loop");

        let rem_events = 'mainloop: loop {
            let mut events = vec![];

            let mut delay = 45;
            while let Ok(true) = crossterm::event::poll(Duration::from_millis(delay)) {
                let event = read().unwrap();

                self.renderer.on_event(&event);
                self.data.screen_size = self.renderer.frame_size();

                match event {
                    crossterm::event::Event::Key(KeyEvent {
                        code: KeyCode::F(3),
                        kind: KeyEventKind::Press,
                        ..
                    }) => self.data.use_data.show_debug = !self.data.use_data.show_debug,
                    event => events.push(Event::Term(event)),
                }

                // just so we read all events in the frame
                delay = 1;
            }

            while let Some(change) = match self.activities.active_mut() {
                Some(active) => {
                    log::trace!("Active activity: '{}'", active.name());
                    active
                }
                None => break 'mainloop events,
            }
            .update(std::mem::take(&mut events), &mut self.data)
            {
                match change {
                    Change::Push(activity) => {
                        log::trace!(
                            "Pushed new activity `{}/{}`",
                            activity.source(),
                            activity.name()
                        );
                        self.activities.push(activity);
                    }
                    Change::Pop { n, res } => {
                        self.activities.pop_n(n);
                        events.push(Event::ActiveAfterPop(res));
                        log::trace!("Popped {} activities", n);
                    }
                    Change::PopUntil { name, res } => {
                        self.activities.pop_until(&name);
                        events.push(Event::ActiveAfterPop(res));
                        log::trace!("Popped until '{}'", name);
                    }
                    Change::Replace(activity) => self.activities.replace(activity),
                    Change::ReplaceAt { index, activity } => {
                        self.activities.replace_at(index, activity);
                    }
                }
            }

            self.activities
                .active()
                .expect("No active active")
                .screen()
                .draw(self.renderer.frame())
                .unwrap();

            logging::get_logger().draw(Dims(0, 0), self.renderer.frame());

            // TODO: let activities show debug info and about the app itself
            // then we can draw it here

            self.renderer.show().unwrap();
        };

        log::trace!("Main loop ended");

        rem_events.into_iter().find_map(|e| match e {
            Event::ActiveAfterPop(Some(res)) => Some(res),
            _ => None,
        })
    }

    pub fn activity_count(&self) -> usize {
        self.activities.len()
    }

    pub fn activities(&self) -> &Activities {
        &self.activities
    }

    pub fn activities_mut(&mut self) -> &mut Activities {
        &mut self.activities
    }

    pub fn active_name(&self) -> Option<&str> {
        self.activities.active().map(|a| a.name())
    }

    pub fn data(&self) -> &AppData {
        &self.data
    }

    pub fn data_mut(&mut self) -> &mut AppData {
        &mut self.data
    }
}

#[derive(Default)]
pub struct AppStateData {
    pub last_selected_preset: Option<usize>,
    pub show_debug: bool,
}