1use std::any::Any;
2use std::borrow::Cow;
3use std::process::Command;
4use std::sync::Arc;
5use std::sync::atomic::{AtomicU64, Ordering};
6use std::sync::mpsc::Sender;
7use std::time::Instant;
8
9use crossterm::event::{KeyEvent, MouseEvent};
10use pito_footer::{Confirm, Hint, InputBar, Notice, Tone};
11use ratatui::{Frame, layout::Rect, style::Style, text::Line};
12
13use crate::activity::{Activity, Change};
14use crate::command::{Heard, Run, Stream};
15use crate::copy::COPY_MAX;
16use crate::head::Focus;
17use crate::label::Span;
18use crate::layer::{Event as Layered, Modal, Takeover, Toast};
19use crate::layout::Layout;
20use crate::model::Item;
21use crate::palette::Palette;
22use crate::pick::Pick;
23use crate::wake::{Wake, Waker};
24use crate::words::Words;
25
26pub type Job<E> = Box<dyn FnOnce() -> E + Send + 'static>;
27
28#[derive(Debug, Clone, PartialEq, Eq)]
29#[non_exhaustive]
30pub enum Phase<'a> {
31 Ready,
32 Loading(Cow<'a, str>),
33 Trouble(Cow<'a, [Line<'a>]>),
34}
35
36pub trait Screen<E>: Any {
37 fn draw(&mut self, frame: &mut Frame, area: Rect, palette: &Palette);
38
39 fn moment(&mut self, _now: Instant) {}
40
41 fn phase(&self) -> Phase<'_> {
42 Phase::Ready
43 }
44
45 fn load(&mut self) -> Option<Job<E>> {
46 None
47 }
48
49 fn loaded(&mut self, _event: E, _cx: &mut Cx<'_, E>) {}
50
51 fn sources(&self) -> &[&str] {
52 &[]
53 }
54
55 fn cancel(&mut self) {}
56
57 fn start(&mut self, _cx: &mut Cx<'_, E>) {}
58
59 fn entered(&mut self, _cx: &mut Cx<'_, E>) {}
60
61 fn key(&mut self, _key: KeyEvent, _cx: &mut Cx<'_, E>) {}
62
63 fn paste(&mut self, _text: &str, _cx: &mut Cx<'_, E>) {}
64
65 fn mouse(&mut self, _mouse: MouseEvent, _area: Rect, _cx: &mut Cx<'_, E>) {}
66
67 fn event(&mut self, _event: E, _cx: &mut Cx<'_, E>) {}
68
69 fn answer(&mut self, _yes: bool, _cx: &mut Cx<'_, E>) {}
70
71 fn picked(&mut self, _choice: Option<usize>, _cx: &mut Cx<'_, E>) {}
72
73 fn heard(&mut self, _id: u64, _heard: Heard, _cx: &mut Cx<'_, E>) {}
74
75 fn layer(&mut self, _id: u64, _event: Layered, _cx: &mut Cx<'_, E>) {}
76
77 fn claim(&mut self, _key: KeyEvent, _cx: &mut Cx<'_, E>) -> bool {
78 false
79 }
80
81 fn back(&mut self, _cx: &mut Cx<'_, E>) {}
82
83 fn hints(&self) -> Vec<Hint<'_>> {
84 Vec::new()
85 }
86
87 fn facts(&self) -> Vec<(String, Style)> {
88 Vec::new()
89 }
90
91 fn status(&self) -> Option<(String, Style)> {
92 None
93 }
94
95 fn status_parts(&self, _room: u16) -> Vec<(String, Style)> {
96 self.status().into_iter().collect()
97 }
98
99 fn lead(&self, _room: u16) -> Vec<(String, Style)> {
100 Vec::new()
101 }
102
103 fn left(&self, _room: u16, _help: Option<&str>) -> Option<Vec<(String, Style)>> {
104 None
105 }
106
107 fn after_tabs(&self) -> Vec<(String, Style)> {
108 Vec::new()
109 }
110
111 fn caption(&self) -> Option<Vec<(String, Style)>> {
112 None
113 }
114
115 fn notice(&self) -> Option<Notice<'_>> {
116 None
117 }
118
119 fn legend(&self) -> Option<&str> {
120 None
121 }
122
123 fn typing(&self) -> bool {
124 false
125 }
126
127 fn input(&self) -> Option<InputBar<'_>> {
128 None
129 }
130
131 fn crumb(&self) -> Option<String> {
132 None
133 }
134
135 fn selected(&self) -> usize {
136 0
137 }
138
139 fn busy(&self) -> usize {
140 0
141 }
142
143 fn animating(&self) -> bool {
144 false
145 }
146
147 fn deadline(&self) -> Option<Instant> {
148 None
149 }
150
151 fn tick(&mut self, _cx: &mut Cx<'_, E>) {}
152}
153
154pub trait Band {
155 fn height(&self, room: u16, now: Instant) -> u16;
156
157 fn draw(&mut self, frame: &mut Frame, area: Rect, palette: &Palette, now: Instant);
158
159 fn deadline(&self, _now: Instant) -> Option<Instant> {
160 None
161 }
162}
163
164#[derive(Debug, Clone, PartialEq, Eq)]
165pub(crate) struct Asking {
166 pub(crate) screen: usize,
167 pub(crate) question: Cow<'static, str>,
168 pub(crate) confirm: Confirm,
169}
170
171pub(crate) struct Picking {
172 pub(crate) screen: usize,
173 pub(crate) pick: Pick,
174}
175
176pub(crate) struct Opened {
177 pub(crate) screen: usize,
178 pub(crate) modal: Modal,
179}
180
181pub(crate) type Shown = Box<dyn Fn(Item) -> bool>;
182
183pub(crate) struct Outbox<E> {
184 pub(crate) sender: Sender<Wake<E>>,
185 pub(crate) jobs: Vec<(usize, u64, Job<E>)>,
186 pub(crate) epoch: Arc<AtomicU64>,
187 pub(crate) bump: bool,
188 pub(crate) copies: Vec<String>,
189 pub(crate) said: Option<(Cow<'static, str>, Tone)>,
190 pub(crate) asking: Option<Asking>,
191 pub(crate) picking: Option<Picking>,
192 pub(crate) go: Option<usize>,
193 pub(crate) changes: Vec<Change>,
194 pub(crate) runs: Vec<Run>,
195 pub(crate) stops: Vec<u64>,
196 pub(crate) quit: bool,
197 pub(crate) reload: Vec<usize>,
198 pub(crate) changed: Vec<String>,
199 pub(crate) palette: Option<Palette>,
200 pub(crate) words: Option<Words>,
201 pub(crate) hints: Option<Vec<Hint<'static>>>,
202 pub(crate) title: Option<Vec<Span>>,
203 pub(crate) layout: Option<Layout>,
204 pub(crate) role: Option<Option<Cow<'static, str>>>,
205 pub(crate) show: Option<Shown>,
206 pub(crate) group: Option<usize>,
207 pub(crate) toasts: Vec<Toast>,
208 pub(crate) modals: Vec<Opened>,
209 pub(crate) takeover: Option<(usize, Takeover)>,
210 pub(crate) gate: Option<bool>,
211}
212
213impl<E> Outbox<E> {
214 pub(crate) fn new(sender: Sender<Wake<E>>) -> Self {
215 Outbox {
216 sender,
217 jobs: Vec::new(),
218 epoch: Arc::new(AtomicU64::new(0)),
219 bump: false,
220 copies: Vec::new(),
221 said: None,
222 asking: None,
223 picking: None,
224 go: None,
225 changes: Vec::new(),
226 runs: Vec::new(),
227 stops: Vec::new(),
228 quit: false,
229 reload: Vec::new(),
230 changed: Vec::new(),
231 palette: None,
232 words: None,
233 hints: None,
234 title: None,
235 layout: None,
236 role: None,
237 show: None,
238 group: None,
239 toasts: Vec::new(),
240 modals: Vec::new(),
241 takeover: None,
242 gate: None,
243 }
244 }
245
246 pub(crate) fn stamp(&self) -> u64 {
247 self.epoch.load(Ordering::SeqCst)
248 }
249}
250
251pub struct Cx<'a, E> {
252 pub(crate) screen: usize,
253 pub(crate) now: Instant,
254 pub(crate) key: Option<KeyEvent>,
255 pub(crate) focus: Focus,
256 pub(crate) typing: bool,
257 pub(crate) out: &'a mut Outbox<E>,
258}
259
260impl<E: Send + 'static> Cx<'_, E> {
261 pub fn screen(&self) -> usize {
262 self.screen
263 }
264
265 pub fn now(&self) -> Instant {
266 self.now
267 }
268
269 pub fn key(&self) -> Option<KeyEvent> {
270 self.key
271 }
272
273 pub fn focus(&self) -> Focus {
274 self.focus
275 }
276
277 pub fn typing(&self) -> bool {
278 self.typing
279 }
280
281 pub fn detach(&mut self, work: impl FnOnce() -> E + Send + 'static) {
282 let stamp = self.out.stamp();
283 self.out.jobs.push((self.screen, stamp, Box::new(work)));
284 }
285
286 pub fn waker(&self) -> Waker<E> {
287 Waker::new(self.out.sender.clone(), self.screen).epoch(Arc::clone(&self.out.epoch))
288 }
289
290 pub fn epoch(&mut self) {
291 self.out.bump = true;
292 }
293
294 pub fn say(&mut self, text: impl Into<Cow<'static, str>>, tone: Tone) {
295 self.out.said = Some((text.into(), tone));
296 }
297
298 pub fn hush(&mut self) {
299 self.out.said = None;
300 }
301
302 pub fn confirm(&mut self, question: impl Into<Cow<'static, str>>) {
303 self.confirm_with(question, Confirm::new());
304 }
305
306 pub fn confirm_with(&mut self, question: impl Into<Cow<'static, str>>, confirm: Confirm) {
307 self.out.asking = Some(Asking {
308 screen: self.screen,
309 question: question.into(),
310 confirm,
311 });
312 }
313
314 pub fn pick(&mut self, pick: Pick) {
315 self.out.picking = Some(Picking {
316 screen: self.screen,
317 pick,
318 });
319 }
320
321 pub fn copy(&mut self, text: impl Into<String>) -> bool {
322 let text = text.into();
323 if text.len() > COPY_MAX {
324 return false;
325 }
326 self.out.copies.push(text);
327 true
328 }
329
330 pub fn go(&mut self, screen: usize) {
331 self.out.go = Some(screen);
332 }
333
334 pub fn activity(&mut self, activity: Activity) {
335 self.out.changes.push(Change::Put(activity));
336 }
337
338 pub fn activities(&mut self, activities: impl IntoIterator<Item = Activity>) {
339 let all = activities.into_iter().collect();
340 self.out.changes.push(Change::All(all));
341 }
342
343 pub fn run(&mut self, id: u64, label: impl Into<String>, command: Command) {
344 self.run_from(id, label, command, Stream::Stdout);
345 }
346
347 pub fn run_from(
348 &mut self,
349 id: u64,
350 label: impl Into<String>,
351 command: Command,
352 progress: Stream,
353 ) {
354 let activity = Activity::new(id, label, self.now);
355 self.out.changes.push(Change::Run(activity.clone()));
356 self.out.runs.push(Run {
357 screen: self.screen,
358 activity,
359 command,
360 progress,
361 keep: true,
362 });
363 }
364
365 pub fn stop(&mut self, id: u64) {
366 self.out.stops.push(id);
367 }
368
369 pub fn forget(&mut self, id: u64) {
370 for run in self.out.runs.iter_mut().filter(|run| run.activity.id == id) {
371 run.keep = false;
372 }
373 self.out.changes.push(Change::Forget(id));
374 }
375
376 pub fn band(&mut self, shown: bool) {
377 self.out.changes.push(Change::Band(shown));
378 }
379
380 pub fn quit(&mut self) {
381 self.out.quit = true;
382 }
383
384 pub fn reload(&mut self) {
385 self.out.reload.push(self.screen);
386 }
387
388 pub fn changed(&mut self, source: impl Into<String>) {
389 self.out.changed.push(source.into());
390 }
391
392 pub fn palette(&mut self, palette: Palette) {
393 self.out.palette = Some(palette);
394 }
395
396 pub fn words(&mut self, words: Words) {
397 self.out.words = Some(words);
398 }
399
400 pub fn hints(&mut self, hints: Vec<Hint<'static>>) {
401 self.out.hints = Some(hints);
402 }
403
404 pub fn title(&mut self, text: impl Into<Cow<'static, str>>) {
405 self.out.title = Some(vec![(text.into(), Style::new())]);
406 }
407
408 pub fn title_spans<T: Into<Cow<'static, str>>>(
409 &mut self,
410 spans: impl IntoIterator<Item = (T, Style)>,
411 ) {
412 let spans = spans
413 .into_iter()
414 .map(|(text, style)| (text.into(), style))
415 .collect();
416 self.out.title = Some(spans);
417 }
418
419 pub fn layout(&mut self, layout: Layout) {
420 self.out.layout = Some(layout);
421 }
422
423 pub fn role(&mut self, role: Option<Cow<'static, str>>) {
424 self.out.role = Some(role);
425 }
426
427 pub fn show(&mut self, shown: impl Fn(Item) -> bool + 'static) {
428 self.out.show = Some(Box::new(shown));
429 }
430
431 pub fn go_group(&mut self, group: usize) {
432 self.out.group = Some(group);
433 }
434
435 pub fn toast(&mut self, toast: Toast) {
436 self.out.toasts.push(toast);
437 }
438
439 pub fn open(&mut self, modal: Modal) {
440 self.out.modals.retain(|opened| opened.modal.id != modal.id);
441 self.out.modals.push(Opened {
442 screen: self.screen,
443 modal,
444 });
445 }
446
447 pub fn alert(
448 &mut self,
449 id: u64,
450 title: impl Into<Cow<'static, str>>,
451 lines: impl IntoIterator<Item = Line<'static>>,
452 ) {
453 self.open(Modal::alert(id, title, lines));
454 }
455
456 pub fn modal(&mut self, id: u64) -> Option<&mut Modal> {
457 self.out
458 .modals
459 .iter_mut()
460 .find(|opened| opened.modal.id == id)
461 .map(|opened| &mut opened.modal)
462 }
463
464 pub fn close(&mut self, id: u64) {
465 self.out.modals.retain(|opened| opened.modal.id != id);
466 }
467
468 pub fn takeover(&mut self, takeover: Takeover) {
469 self.out.takeover = Some((self.screen, takeover));
470 }
471
472 pub fn end_takeover(&mut self) {
473 self.out.takeover = None;
474 }
475
476 pub fn gate(&mut self, shut: bool) {
477 self.out.gate = Some(shut);
478 }
479}