use crate::{
color::{Color, ColorRgb},
draw::erase_rect,
fps_counter::{FpsCounter, update_fps_counter},
fps_limiter::{self, FpsLimiter, wait_for_next_frame},
frame::{FramePair, compose_frame_buffer, draw_to_terminal},
layer::{Layer, LayerIndex, create_layer},
particle::{ParticleState, update_and_draw_particles},
};
use crossterm::{cursor, event, execute, terminal};
use std::{
io::{self},
time::Duration,
};
pub struct Engine {
pub delta_time: f32,
pub game_time: f32,
pub stdout: io::Stdout,
pub(crate) default_blending_color: Color,
pub(crate) fps_counter: FpsCounter,
pub(crate) max_layer_index: usize,
pub(crate) frame: FramePair,
pub(crate) fps_limiter: FpsLimiter,
pub(crate) particle_state: Vec<ParticleState>,
title: &'static str,
}
impl Engine {
pub fn new(cols: u16, rows: u16) -> Self {
Self {
delta_time: 0.01667,
game_time: 0.0,
title: "my-awesome-terminal",
stdout: io::stdout(),
max_layer_index: 0,
frame: FramePair::new(cols, rows),
fps_limiter: FpsLimiter::new(60, 0.001, 0.002),
fps_counter: FpsCounter::new(0.3),
particle_state: Vec::with_capacity(512),
default_blending_color: {
match termbg::rgb(Duration::from_millis(100)) {
Ok(rgb) => Color::new(rgb.r as u8, rgb.g as u8, rgb.b as u8, 255),
Err(_) => Color::BLACK,
}
},
}
}
pub fn title(mut self, value: &'static str) -> Self {
self.title = value;
self
}
pub fn limit_fps(mut self, value: u32) -> Self {
fps_limiter::limit_fps(&mut self.fps_limiter, value);
self
}
}
pub fn override_default_blending_color(engine: &mut Engine, color: ColorRgb) {
engine.default_blending_color = color.into();
}
pub fn init(engine: &mut Engine) -> io::Result<()> {
let layer_count = engine.max_layer_index + 1;
if engine.frame.layered_draw_queue.len() < layer_count {
engine
.frame
.layered_draw_queue
.resize_with(layer_count, Layer::new);
}
terminal::enable_raw_mode()?;
execute!(
engine.stdout,
terminal::EnterAlternateScreen,
terminal::SetTitle(engine.title),
event::EnableMouseCapture,
cursor::Hide,
)?;
Ok(())
}
pub fn exit_cleanup(engine: &mut Engine) -> io::Result<()> {
terminal::disable_raw_mode()?;
execute!(
engine.stdout,
terminal::LeaveAlternateScreen,
terminal::EnableLineWrap,
cursor::Show,
event::DisableMouseCapture
)?;
Ok(())
}
pub fn start_frame(engine: &mut Engine) {
engine.delta_time = wait_for_next_frame(&mut engine.fps_limiter);
update_fps_counter(&mut engine.fps_counter, engine.delta_time);
let lowest_layer_index: LayerIndex = create_layer(engine, 0);
erase_rect(
engine,
lowest_layer_index,
0,
0,
engine.frame.width as i16,
engine.frame.height as i16,
);
}
pub fn end_frame(engine: &mut Engine) -> io::Result<()> {
update_and_draw_particles(engine);
let height = engine.frame.height;
let width = engine.frame.width;
let (current, layered) = engine.frame.current_mut_and_layered_mut();
compose_frame_buffer(
current,
layered.iter_mut().flat_map(|v| v.0.drain(..)),
width,
height,
engine.default_blending_color,
);
let diff_products = engine.frame.diff();
draw_to_terminal(&mut engine.stdout, diff_products)?;
engine.frame.swap_frames();
engine.game_time += engine.delta_time;
Ok(())
}