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, Page, 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 page: Page,
41 spacing: i32,
42 spacing_on: bool,
43}
44
45impl App {
46 fn init(cx: CX![]) -> Option<Self> {
47 let _terminal = actor!(
48 cx,
49 Terminal::init(
50 SimpleSizer::new(),
51 fwd_to!([cx], resize() as (Option<TermShare>)),
52 fwd_to!([cx], input() as (Key))
53 ),
54 ret_some_to!([cx], |_, cx, cause: StopCause| {
55 println!("Terminal actor failed: {cause}");
56 stop!(cx);
57 })
58 );
59 Some(Self {
60 _terminal,
61 tshare: None,
62 page: Page::empty(),
63 spacing: 0,
64 spacing_on: false,
65 })
66 }
67
68 fn resize(&mut self, cx: CX![], tshare: Option<TermShare>) {
69 self.tshare = tshare;
70
71 if let Some(ref tshare) = self.tshare {
72 let o = tshare.output(cx);
73 o.attr_99().cursor_show().scroll_up().save_cleanup();
74 o.cursor_hide();
75 self.page = o.new_page();
76 }
77
78 self.redraw(cx, true);
79 }
80
81 fn input(&mut self, cx: CX![], key: Key) {
82 match key {
83 Key::Ctrl('L') => self.redraw(cx, true),
84 Key::Ctrl('C') => stop!(cx),
85 _ => {
86 if self.spacing_on {
87 self.spacing_on = false;
88 } else {
89 self.spacing_on = true;
90 self.spacing = ((self.spacing + 1) % 20).max(3);
91 }
92 self.redraw(cx, false);
93 }
94 }
95 }
96
97 fn redraw(&mut self, cx: CX![], full: bool) {
98 let pg = &mut self.page;
99 let sx = pg.sx();
100 let sy = pg.sy();
101
102 if sy < 24 || sx < 80 {
103 let mut r = pg.full();
104 r.clear_all_99();
105 r.at(sy >> 1, (sx - 30) >> 1).hfb(162);
106 write!(r, " Terminal too small: {sy} x {sx} ").unwrap();
107 return;
108 }
109
110 const TEXT: &str = "This is a test. ";
111 const TEXTLEN: usize = TEXT.len();
112 let mid = sx >> 1;
113 for y in 0..sy {
114 let mut r = pg.region(y, 0, 1, sx + TEXTLEN as i32 * 2);
115 r.at(0, y % TEXTLEN as i32 - TEXTLEN as i32).hfb(99);
116 while r.get_x() < sx {
117 r.text(TEXT);
118 }
119 if self.spacing_on {
120 let mut x = (sx + sy) / 2 - y;
121 while x > 0 {
122 x -= self.spacing
123 }
124 r.at(0, x);
125 while r.get_x() < sx {
126 r.text("/");
127 r.skip(self.spacing - 1);
128 }
129 }
130 let shift = y * 6 / sy;
131 for x in 0..sx {
132 r.set_hfb(0, x, (((x - mid) >> shift) & 3) as u16 + 70);
133 }
134 }
135 pg.full()
136 .at(sy - 2, (sx - 40) >> 1)
137 .hfb(6)
138 .text(" PRESS ANY KEY TO CONTINUE, Ctrl-C TO END ");
139
140 self.update(cx, full);
141 }
142
143 fn update(&mut self, cx: CX![], full: bool) {
144 if let Some(ref tshare) = self.tshare {
145 let o = tshare.output(cx);
146 o.update_to(&mut self.page, full);
147 }
148 }
149}