milk_tea/
lib.rs

1pub mod area;
2pub mod draw_call;
3pub mod pair;
4pub mod rect;
5pub mod text_size;
6
7mod frame;
8
9pub use crossterm::{self, event, style};
10
11use area::Area;
12use crossterm::{cursor, event::Event, terminal, ExecutableCommand, QueueableCommand};
13use frame::Frame;
14use pair::Pair;
15use std::io;
16
17pub fn run<M, V, U>(mut model: M, view: V, update: U) -> io::Result<()>
18where
19    M: Model,
20    V: Fn(&M, &mut Area),
21    U: Fn(Event, &mut M),
22{
23    let mut stdout = io::stdout();
24
25    stdout.queue(terminal::EnterAlternateScreen)?;
26    stdout.execute(cursor::Hide)?;
27
28    let mut last_state = model.clone();
29    let mut last_frame = Frame::new();
30    let mut was_resized = true;
31
32    while !model.should_exit() {
33        if model != last_state || was_resized {
34            let size = Pair::from(terminal::size()?);
35
36            if size.x == 0 || size.y == 0 {
37                break;
38            }
39
40            let mut area = Area::new(size.as_rect());
41            view(&model, &mut area);
42
43            let calls = area.collect();
44
45            let frame = Frame::from_calls(&calls);
46            frame
47                .diff(was_resized, &last_frame)
48                .draw(was_resized, size, &mut stdout)?;
49
50            last_frame = frame;
51        }
52
53        last_state = model.clone();
54
55        let event = event::read()?;
56
57        if let Event::Resize(_, _) = event {
58            was_resized = true;
59        }
60
61        update(event, &mut model);
62    }
63
64    stdout.queue(cursor::Show)?;
65    stdout.execute(terminal::LeaveAlternateScreen)?;
66
67    Ok(())
68}
69
70pub trait Model: Clone + PartialEq + Eq {
71    fn should_exit(&self) -> bool;
72}