1use stakker::{ActorOwn, CX, Stakker, StopCause, actor, fwd_to, ret_shutdown, ret_some_to, stop};
4use stakker_mio::MioPoll;
5use stakker_mio::mio::{Events, Poll};
6use stakker_tui::{Key, Output, TermShare, Terminal, sizer::SimpleSizer};
7use std::io::Write;
8use std::time::{Duration, Instant};
9
10fn main() -> std::io::Result<()> {
13 let mut stakker = Stakker::new(Instant::now());
14 let s = &mut stakker;
15 let miopoll = MioPoll::new(s, Poll::new()?, Events::with_capacity(1024), 0)?;
16
17 let _app = actor!(s, App::init(), ret_shutdown!(s));
18
19 const MAXDELAY: Duration = Duration::from_secs(60);
20 let mut idle_pending = s.run(Instant::now(), false);
21 let mut io_pending = false;
22 let mut activity;
23 while s.not_shutdown() {
24 let maxdur = s.next_wait_max(Instant::now(), MAXDELAY, idle_pending || io_pending);
25 (activity, io_pending) = miopoll.poll(maxdur)?;
26 idle_pending = s.run(Instant::now(), !activity);
27 }
28 if let Some(reason) = s.shutdown_reason() {
29 if reason.has_error() {
30 println!("{reason}");
31 }
32 }
33
34 Ok(())
35}
36
37struct App {
38 _terminal: ActorOwn<Terminal>,
39 tshare: Option<TermShare>,
40}
41
42impl App {
43 fn init(cx: CX![]) -> Option<Self> {
44 let _terminal = actor!(
45 cx,
46 Terminal::init(
47 SimpleSizer::new(),
48 fwd_to!([cx], resize() as (Option<TermShare>)),
49 fwd_to!([cx], input() as (Key))
50 ),
51 ret_some_to!([cx], |_, cx, cause: StopCause| {
52 println!("Terminal actor failed: {cause}");
53 stop!(cx);
54 })
55 );
56 Some(Self {
57 _terminal,
58 tshare: None,
59 })
60 }
61
62 fn resize(&mut self, cx: CX![], tshare: Option<TermShare>) {
63 self.tshare = tshare;
64 if self.tshare.is_some() {
65 self.redraw(cx);
66 }
67 }
68
69 fn redraw(&mut self, cx: CX![]) {
70 if let Some(ref tshare) = self.tshare {
71 let o = tshare.output(cx);
72 o.attr_99().cursor_show().scroll_up().save_cleanup();
73 o.cursor_hide();
74 Self::draw(o);
75 }
76 }
77
78 fn input(&mut self, cx: CX![], key: Key) {
79 match key {
80 Key::Ctrl('L') => self.redraw(cx),
81 Key::Ctrl('C') => stop!(cx),
82 _ => {
83 use std::str::FromStr;
84 let s = format!("{key}");
85 let k = Key::from_str(&s);
86 assert!(k.is_ok(), "Round-trip failed for {s}");
87 let k = k.unwrap();
88 assert_eq!(k, key, "Mismatch for {key} -> {s} -> {k}");
89 let hfb = if s.starts_with("M-") {
90 172
91 } else if s.starts_with("C-") {
92 141
93 } else if matches!(key, Key::Pr(_)) {
94 151
95 } else {
96 162
97 };
98 if let Some(ref tshare) = self.tshare {
99 let o = tshare.output(cx);
100 o.hfb(hfb).text(&s);
101 o.hfb(71).text(" ");
102 o.flush();
103 }
104 }
105 }
106 }
107
108 fn draw(o: &mut Output) {
109 o.clear_all_99();
110 let sx = o.sx();
111 let sy = o.sy();
112 if sy < 24 || sx < 80 {
113 o.hfb(162);
114 o.at(o.sy() >> 1, (o.sx() - 30) >> 1);
115 write!(o, " Terminal too small: {} x {} ", o.sy(), o.sx()).unwrap();
116 o.hfb(99);
117 o.flush();
118 return;
119 }
120
121 const TEXT: [char; 17] = [
122 'T', 'h', 'i', 's', ' ', 'i', 's', ' ', 'a', ' ', 't', 'e', 's', 't', '.', ' ', ' ',
123 ];
124 let mut last_hfb = 99;
125 for y in 0..sy {
126 o.at(y, 0);
127 for x in 0..sx {
128 let hfb = ((x * 8) / sx + (y & 7) * 10 + ((y >> 3) & 1) * 100) as u16;
129 if hfb != last_hfb {
130 o.hfb(hfb);
131 last_hfb = hfb;
132 }
133 o.char(TEXT[(1000 + x - y) as usize % TEXT.len()]);
134 }
135 }
136 o.at(sy - 2, (sx - 60) / 2)
137 .hfb(162)
138 .text("[ Type to show decoding, Ctrl-C to exit, Ctrl-L to redraw ]")
139 .hfb(99);
140 o.at(0, 0);
141 o.flush();
142 }
143}