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 if key == Key::Ctrl('L') {
80 self.redraw(cx);
81 } else {
82 stop!(cx);
83 }
84 }
85
86 fn draw(o: &mut Output) {
87 o.clear_all_99();
88 if o.sy() < 24 || o.sx() < 80 {
89 o.hfb(162);
90 o.at(o.sy() >> 1, (o.sx() - 30) >> 1);
91 write!(o, " Terminal too small: {} x {} ", o.sy(), o.sx()).unwrap();
92 o.hfb(99);
93 o.flush();
94 return;
95 }
96
97 let x0 = (o.sx() - 80) >> 1;
98 let y0 = (o.sy() - 24) >> 1;
99 let mut yy = y0;
100 let c256 = o.features().colour_256;
101 o.at(yy, x0).hfb(99).text("Colour mode:");
102 o.at(yy, x0 + 15)
103 .hfb(199)
104 .text(if c256 { "256-colour" } else { "8-colour" });
105 yy += 2;
106
107 o.at(yy, x0).hfb(99).text("Attributes:");
108 yy += 1;
109 const TEXT: [&str; 4] = ["White:", "Brown:", "Blue:", "Black:"];
110 const BASE_HFB: [u16; 4] = [70, 60, 10, 3];
111 for i in 0..4 {
112 let hfb = BASE_HFB[i];
113 o.at(yy, x0 + 2).hfb(99).text(TEXT[i]);
114 o.at(yy, x0 + 16).hfb(hfb).text(" norm ");
115 o.at(yy, x0 + 22).hfb(100 + hfb).text("bold ");
116 o.at(yy, x0 + 27).hfb(200 + hfb).text("undl ");
117 o.at(yy, x0 + 32).hfb(300 + hfb).text("both ");
118 yy += 1;
119 }
120 o.at(yy + 1, x0)
121 .hfb(60)
122 .text(" (On 8-colour expect bold/ul to");
123 o.at(yy + 2, x0)
124 .hfb(60)
125 .text(" be emulated as colour changes.)");
126 let y1a = yy + 3;
127
128 yy = y0;
129 o.at(yy, x0 + 40)
130 .hfb(99)
131 .text("Base, bold and background colours:");
132 o.at(yy + 1, x0 + 42).hfb(7).text(" black ");
133 o.at(yy + 2, x0 + 42).hfb(10).text(" blue ");
134 o.at(yy + 3, x0 + 42).hfb(20).text(" red ");
135 o.at(yy + 4, x0 + 42).hfb(30).text(" magenta ");
136 o.at(yy + 5, x0 + 42).hfb(40).text(" green ");
137 o.at(yy + 6, x0 + 42).hfb(50).text(" cyan ");
138 o.at(yy + 7, x0 + 42).hfb(60).text(" yellow ");
139 o.at(yy + 8, x0 + 42).hfb(70).text(" white ");
140 o.at(yy + 1, x0 + 54).hfb(100).text(" black ");
141 o.at(yy + 2, x0 + 54).hfb(110).text(" blue ");
142 o.at(yy + 3, x0 + 54).hfb(120).text(" red ");
143 o.at(yy + 4, x0 + 54).hfb(130).text(" magenta ");
144 o.at(yy + 5, x0 + 54).hfb(140).text(" green ");
145 o.at(yy + 6, x0 + 54).hfb(150).text(" cyan ");
146 o.at(yy + 7, x0 + 54).hfb(160).text(" yellow ");
147 o.at(yy + 8, x0 + 54).hfb(170).text(" white ");
148 o.at(yy + 1, x0 + 66).hfb(70).text(" black ");
149 o.at(yy + 2, x0 + 66).hfb(71).text(" blue ");
150 o.at(yy + 3, x0 + 66).hfb(72).text(" red ");
151 o.at(yy + 4, x0 + 66).hfb(73).text(" magenta ");
152 o.at(yy + 5, x0 + 66).hfb(4).text(" green ");
153 o.at(yy + 6, x0 + 66).hfb(5).text(" cyan ");
154 o.at(yy + 7, x0 + 66).hfb(6).text(" yellow ");
155 o.at(yy + 8, x0 + 66).hfb(7).text(" white ");
156 o.at(yy + 10, x0 + 40)
157 .hfb(60)
158 .text(" (Expect all 16 colours to be");
159 o.at(yy + 11, x0 + 40)
160 .hfb(60)
161 .text(" visible in all cases.)");
162 let y1b = yy + 12;
163 let end = y1a.max(y1b);
164
165 yy = end + 10;
166 o.at(yy - 1, x0).hfb(99).text("Hue wheel");
167 o.at(yy, x0);
168 if o.features().colour_256 {
169 const RED: [i32; 7] = [255, 255, 0, 0, 0, 255, 255];
170 const GREEN: [i32; 7] = [0, 255, 255, 255, 0, 0, 0];
171 const BLUE: [i32; 7] = [0, 0, 0, 255, 255, 255, 0];
172 for x in 0..78 {
173 let i = x / 13;
174 let off = (x - i * 13) as i32;
175 let r = RED[i] + (RED[i + 1] - RED[i]) * off / 13;
176 let g = GREEN[i] + (GREEN[i + 1] - GREEN[i]) * off / 13;
177 let b = BLUE[i] + (BLUE[i + 1] - BLUE[i]) * off / 13;
178 let rgb = (r << 16) + (g << 8) + b;
179 let hfb = o.hfb_alloc(false, false, 0xFFFFFF, rgb as u32);
180 o.hfb(hfb).spaces(1);
181 }
182 } else {
183 o.hfb(2).spaces(13);
184 o.hfb(6).spaces(13);
185 o.hfb(4).spaces(13);
186 o.hfb(5).spaces(13);
187 o.hfb(1).spaces(13);
188 o.hfb(3).spaces(13);
189 }
190
191 yy = end + 1;
192 let mut xx = x0 + 40;
193
194 o.at(yy, xx).hfb(99).text("ASCII");
195 for a in 0..96 {
196 o.at(yy + 1 + (a >> 4), xx + 1 + (a & 15))
197 .char(char::from_u32((a + 32) as u32).unwrap());
198 }
199
200 xx += 20;
201 o.at(yy, xx).hfb(99).text("Latin-1");
202 for a in 0..96 {
203 o.at(yy + 1 + (a >> 4), xx + 1 + (a & 15))
204 .char(char::from_u32((a + 160) as u32).unwrap());
205 }
206
207 const BOXTITLE1: &str = "\
208Single Single Double Thick";
209 const BOXTITLE2: &str = "\
210+Double +Thick +Single +Single";
211
212 const BOXTEST: &str = "\
213+-+-+-+ +-+-+-+ +=+=+=+ +*+*+*+
214| | H | | | $ | H H | H $ $ | $
215+-+-+-+ +-+-+-+ +=+=+=+ +*+*+*+
216| | H | | | $ | H H | H $ $ | $
217+=+=+=+ +*+*+*+ +-+-+-+ +-+-+-+
218| | H | | | $ | H H | H $ $ | $
219+-+-+-+ +-+-+-+ +=+=+=+ +*+*+*+";
220
221 let boxtest = Output::box_from_ascii_art(BOXTEST);
222 yy = end + 1;
223 xx = x0 + 2;
224 o.at(yy - 2, xx).text(BOXTITLE1);
225 o.at(yy - 1, xx).text(BOXTITLE2);
226 for line in boxtest.split('\n') {
227 o.at(yy, xx).text(line);
228 yy += 1;
229 }
230
231 o.flush();
232 }
233}