use crate::backend::Backend;
use crate::terminal::Terminal;
use core::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Flow {
Continue,
Exit,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Frame {
pub delta: Duration,
pub frame: u64,
}
pub trait App<B: Backend> {
fn update(&mut self, term: &mut Terminal<B>, frame: &Frame) -> Flow;
}
#[must_use]
pub fn step<B: Backend, A: App<B>>(term: &mut Terminal<B>, app: &mut A, frame: &Frame) -> Flow {
app.update(term, frame)
}
#[cfg(feature = "std")]
pub fn run_blocking<B, A>(mut term: Terminal<B>, mut app: A)
where
B: Backend,
A: App<B>,
{
let mut frame_count = 0u64;
let mut last = std::time::Instant::now();
loop {
let now = std::time::Instant::now();
let delta = now.duration_since(last);
last = now;
let frame = Frame {
delta,
frame: frame_count,
};
frame_count = frame_count.wrapping_add(1);
if step(&mut term, &mut app, &frame) == Flow::Exit {
return;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::backend::Headless;
use crate::event::{Event, KeyCode, KeyEvent, KeyModifiers};
struct Counter {
frames: u64,
}
impl App<Headless> for Counter {
fn update(&mut self, term: &mut Terminal<Headless>, frame: &Frame) -> Flow {
self.frames += 1;
term.put(0, 0, '#');
term.present().expect("present");
if term.has_input() || frame.frame >= 100 {
Flow::Exit
} else {
Flow::Continue
}
}
}
#[test]
fn run_blocking_exits_on_flow_exit() {
let mut backend = Headless::new(4, 1);
backend.push_event(Event::Key(KeyEvent::new(
KeyCode::Char('q'),
KeyModifiers::NONE,
)));
let term = Terminal::new(backend);
let app = Counter { frames: 0 };
run_blocking(term, app);
}
#[test]
fn step_forwards_to_update() {
let mut term = Terminal::new(Headless::new(2, 1));
let mut app = Counter { frames: 0 };
let frame = Frame {
delta: Duration::ZERO,
frame: 200,
};
let flow = step(&mut term, &mut app, &frame);
assert_eq!(flow, Flow::Exit); assert_eq!(app.frames, 1);
}
}