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}