1use std::any::Any;
2use std::borrow::Cow;
3use std::io;
4use std::sync::Arc;
5use std::sync::atomic::{AtomicUsize, Ordering};
6use std::sync::mpsc::{self, Receiver, RecvTimeoutError, Sender};
7use std::thread;
8use std::time::{Duration, Instant};
9
10use crossterm::event::{Event, KeyEvent, KeyEventKind, MouseButton, MouseEvent, MouseEventKind};
11use pito_footer::{
12 Answer, Footer, Guard, Help, Hint, Key, Mode, QuitGuard, Tone as Said, WINDOW, Wording,
13};
14use pito_header::{Action, Group, Header, Nav, NavKeys, Section, Step as Move};
15use pito_list::Step;
16use ratatui::layout::Rect;
17use ratatui::style::{Color, Style};
18
19use crate::activity::{Activities, Activity, Board, Change, State};
20use crate::command::{self, Exit, Heard, Report, Run};
21use crate::copy;
22use crate::head::{Focus, Head, Numbers, Row};
23use crate::label::{Label, Span};
24use crate::layer::{Event as Layered, Outcome, Toast};
25use crate::layout::Layout;
26use crate::model::{Item, Model};
27use crate::pace::{Pace, wait_until};
28use crate::palette::Palette;
29use crate::screen::{Band, Cx, Job, Outbox, Phase, Screen};
30use crate::signal;
31use crate::term::{Modes, Term, restore};
32use crate::wake::{Wake, Waker, listen};
33use crate::words::Words;
34
35const SETTLE: usize = 64;
36const QUIT: &[Key] = &[Key::Ctrl('c')];
37const STOP: &[Key] = &[Key::Esc];
38
39pub type HeaderLook = for<'a> fn(Header<'a>) -> Header<'a>;
40pub type FooterLook = for<'a> fn(Footer<'a>, u16) -> Footer<'a>;
41
42type KeyHook<E> = Box<dyn FnMut(KeyEvent, &mut Cx<'_, E>) -> bool>;
43type DrawHook = Box<dyn FnMut(Duration)>;
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum Flow {
47 Stay,
48 Quit,
49}
50
51pub(crate) enum Role {
52 Tab,
53 Overlay(Nav, &'static [Key]),
54 Gate,
55 App,
56}
57
58pub(crate) struct Slot<E> {
59 pub(crate) screen: Box<dyn Screen<E>>,
60 pub(crate) role: Role,
61 pub(crate) started: bool,
62 pub(crate) stale: bool,
63 pub(crate) generation: u64,
64 pub(crate) reading: Option<u64>,
65 pub(crate) since: Option<Instant>,
66 pub(crate) glass: Option<Instant>,
67 pub(crate) label: String,
68 pub(crate) ticked: Option<Instant>,
69}
70
71impl<E> Slot<E> {
72 fn new(screen: Box<dyn Screen<E>>, role: Role) -> Self {
73 Slot {
74 screen,
75 role,
76 started: false,
77 stale: true,
78 generation: 0,
79 reading: None,
80 since: None,
81 glass: None,
82 label: String::new(),
83 ticked: None,
84 }
85 }
86}
87
88enum Digit {
89 Nav,
90 Pass,
91 Done(Flow),
92}
93
94pub struct Tui<E> {
95 pub(crate) name: Cow<'static, str>,
96 pub(crate) version: Cow<'static, str>,
97 pub(crate) title: Vec<Span>,
98 pub(crate) palette: Palette,
99 pub(crate) drawn: Palette,
100 mono: bool,
101 roles: Vec<(Cow<'static, str>, Style)>,
102 role: Option<Cow<'static, str>>,
103 pub(crate) words: Words,
104 pub(crate) hints: Vec<Hint<'static>>,
105 pub(crate) min: (u16, u16),
106 pub(crate) model: Model,
107 pub(crate) head: Option<Head>,
108 pub(crate) layout: Layout,
109 pub(crate) focus: Focus,
110 focus_keys: (&'static [Key], &'static [Key]),
111 pub(crate) screens: Vec<Slot<E>>,
112 pub(crate) open: Option<usize>,
113 pub(crate) board: Option<usize>,
114 pub(crate) band: Option<Box<dyn Band>>,
115 pub(crate) top_band: Option<Box<dyn Band>>,
116 pub(crate) gated: bool,
117 pressed: Option<KeyEvent>,
118 through: &'static [Key],
119 running: Arc<AtomicUsize>,
120 commands: Vec<command::Handle>,
121 pub(crate) help: Option<Help>,
122 pub(crate) quit: QuitGuard,
123 mode: Mode,
124 window: Duration,
125 triggers: &'static [Key],
126 stop: &'static [Key],
127 sticky: bool,
128 pub(crate) quit_tone: Said,
129 pub(crate) quit_hints: bool,
130 eager: bool,
131 pub(crate) keep_facts: bool,
132 pub(crate) lit_again: bool,
133 pub(crate) out: Outbox<E>,
134 pub(crate) inbox: Receiver<Wake<E>>,
135 pub(crate) moment: Instant,
136 modes: Modes,
137 pub(crate) header_look: Option<HeaderLook>,
138 pub(crate) footer_look: Option<FooterLook>,
139 on_key: Option<KeyHook<E>>,
140 after: Option<DrawHook>,
141 pub(crate) content: Rect,
142 pub(crate) top: Rect,
143 pub(crate) rows: Vec<(Row, Rect)>,
144 pub(crate) toasts: Vec<(Instant, Toast)>,
145 pub(crate) taken: Option<Instant>,
146 pub(crate) delay: Duration,
147 pub(crate) least: Duration,
148 pub(crate) version_fit: bool,
149}
150
151impl<E: Send + 'static> Tui<E> {
152 pub fn new(
153 name: impl Into<Cow<'static, str>>,
154 version: impl Into<Cow<'static, str>>,
155 accent: Color,
156 ) -> Self {
157 let (sender, inbox) = mpsc::channel();
158 let words = Words::new();
159 let name = name.into();
160 let palette = Palette::new(accent);
161 let mut tui = Tui {
162 title: vec![(name.clone(), Style::new())],
163 name,
164 version: version.into(),
165 palette,
166 drawn: palette,
167 mono: false,
168 roles: Vec::new(),
169 role: None,
170 quit: QuitGuard::new(Wording::new(words.again.clone())),
171 words,
172 hints: Vec::new(),
173 min: (40, 12),
174 model: Model::new(),
175 head: None,
176 layout: Layout::new(),
177 focus: Focus::Content,
178 focus_keys: (&[], &[]),
179 screens: Vec::new(),
180 open: None,
181 board: None,
182 band: None,
183 top_band: None,
184 gated: false,
185 pressed: None,
186 through: &[],
187 running: Arc::new(AtomicUsize::new(0)),
188 commands: Vec::new(),
189 help: Some(Help::new(true)),
190 mode: Mode::Ask,
191 window: WINDOW,
192 triggers: QUIT,
193 stop: STOP,
194 sticky: false,
195 quit_tone: Said::Accent,
196 quit_hints: true,
197 eager: false,
198 keep_facts: false,
199 lit_again: true,
200 out: Outbox::new(sender),
201 inbox,
202 moment: Instant::now(),
203 modes: Modes::new(),
204 header_look: None,
205 footer_look: None,
206 on_key: None,
207 after: None,
208 content: Rect::default(),
209 top: Rect::default(),
210 rows: Vec::new(),
211 toasts: Vec::new(),
212 taken: None,
213 delay: Duration::ZERO,
214 least: Duration::ZERO,
215 version_fit: false,
216 };
217 tui.rearm();
218 tui
219 }
220
221 pub fn palette(mut self, palette: Palette) -> Self {
222 self.set_palette(palette);
223 self
224 }
225
226 pub fn words(mut self, words: Words) -> Self {
227 self.set_words(words);
228 self
229 }
230
231 pub fn hints(mut self, hints: impl IntoIterator<Item = Hint<'static>>) -> Self {
232 self.hints = hints.into_iter().collect();
233 self
234 }
235
236 pub fn min_size(mut self, width: u16, height: u16) -> Self {
237 self.min = (width, height);
238 self
239 }
240
241 pub fn title(mut self, text: impl Into<Cow<'static, str>>) -> Self {
242 self.title = vec![(text.into(), Style::new())];
243 self
244 }
245
246 pub fn title_spans<T: Into<Cow<'static, str>>>(
247 mut self,
248 spans: impl IntoIterator<Item = (T, Style)>,
249 ) -> Self {
250 self.title = spans
251 .into_iter()
252 .map(|(text, style)| (text.into(), style))
253 .collect();
254 self
255 }
256
257 pub fn layout(mut self, layout: Layout) -> Self {
258 self.layout = layout;
259 self
260 }
261
262 pub fn head(mut self, head: Head) -> Self {
263 self.head = Some(head);
264 self
265 }
266
267 pub fn role(mut self, name: impl Into<Cow<'static, str>>, style: Style) -> Self {
268 let name = name.into();
269 self.roles.retain(|(kept, _)| *kept != name);
270 self.roles.push((name, style));
271 self.repaint();
272 self
273 }
274
275 pub fn monochrome(mut self, mono: bool) -> Self {
276 self.mono = mono;
277 self.repaint();
278 self
279 }
280
281 pub fn group(mut self, label: impl Into<Label>) -> Self {
282 self.model.add_group(label.into());
283 self
284 }
285
286 pub fn group_keys(mut self, keys: &'static [Key]) -> Self {
287 self.model.group_keys(keys);
288 self
289 }
290
291 pub fn screen(mut self, label: impl Into<Label>, screen: impl Screen<E>) -> Self {
292 let index = self.model.tabs.len();
293 self.screens
294 .insert(index, Slot::new(Box::new(screen), Role::Tab));
295 if let Some(board) = self.board.as_mut() {
296 *board += 1;
297 }
298 self.model.add_tab(label.into(), index);
299 self
300 }
301
302 pub fn show(mut self, shown: impl Fn(Item) -> bool) -> Self {
303 self.model.show(&shown);
304 self
305 }
306
307 pub fn overlay(
308 mut self,
309 section: Section,
310 keys: &'static [Key],
311 screen: impl Screen<E>,
312 ) -> Self {
313 let role = Role::Overlay(lone(section), keys);
314 self.screens.push(Slot::new(Box::new(screen), role));
315 self
316 }
317
318 pub fn app(mut self, screen: impl Screen<E>) -> Self {
319 self.screens.retain(|slot| !matches!(slot.role, Role::App));
320 self.reboard();
321 self.screens.push(Slot::new(Box::new(screen), Role::App));
322 self
323 }
324
325 pub fn gate(mut self, screen: impl Screen<E>, shut: bool) -> Self {
326 self.screens.retain(|slot| !matches!(slot.role, Role::Gate));
327 self.reboard();
328 self.screens.push(Slot::new(Box::new(screen), Role::Gate));
329 self.gated = shut;
330 self
331 }
332
333 fn reboard(&mut self) {
334 let board = self.screens.iter().position(|slot| {
335 let any: &dyn Any = slot.screen.as_ref();
336 any.is::<Board>()
337 });
338 if self.board.is_some() {
339 self.board = board;
340 }
341 }
342
343 pub fn activities(mut self, words: Activities) -> Self {
344 let role = Role::Overlay(lone(words.section.clone()), words.keys);
345 match self.board {
346 Some(index) => {
347 if let Some(board) = self.board_mut() {
348 board.words = words;
349 }
350 self.screens[index].role = role;
351 }
352 None => {
353 self.board = Some(self.screens.len());
354 let board = Board::new(words, self.moment);
355 self.screens.push(Slot::new(Box::new(board), role));
356 }
357 }
358 self
359 }
360
361 pub fn band(mut self, band: impl Band + 'static) -> Self {
362 self.band = Some(Box::new(band));
363 self
364 }
365
366 pub fn top_band(mut self, band: impl Band + 'static) -> Self {
367 self.top_band = Some(Box::new(band));
368 self
369 }
370
371 pub(crate) fn board(&self) -> Option<&Board> {
372 let slot = self.screens.get(self.board?)?;
373 let any: &dyn Any = slot.screen.as_ref();
374 any.downcast_ref::<Board>()
375 }
376
377 fn board_mut(&mut self) -> Option<&mut Board> {
378 let slot = self.screens.get_mut(self.board?)?;
379 let any: &mut dyn Any = slot.screen.as_mut();
380 any.downcast_mut::<Board>()
381 }
382
383 pub fn nav_keys(mut self, keys: NavKeys) -> Self {
384 self.model.set_keys(keys);
385 self
386 }
387
388 pub fn focus_keys(mut self, next: &'static [Key], previous: &'static [Key]) -> Self {
389 self.focus_keys = (next, previous);
390 self
391 }
392
393 pub fn help(mut self, help: Option<Help>) -> Self {
394 self.help = help;
395 self
396 }
397
398 pub fn quit(mut self, mode: Mode) -> Self {
399 self.mode = mode;
400 self.rearm();
401 self
402 }
403
404 pub fn quit_window(mut self, window: Duration) -> Self {
405 self.window = window;
406 self.rearm();
407 self
408 }
409
410 pub fn quit_keys(mut self, keys: &'static [Key]) -> Self {
411 self.triggers = keys;
412 self.rearm();
413 self
414 }
415
416 pub fn quit_sticky(mut self, sticky: bool) -> Self {
417 self.sticky = sticky;
418 self
419 }
420
421 pub fn quit_tone(mut self, tone: Said) -> Self {
422 self.quit_tone = tone;
423 self
424 }
425
426 pub fn quit_hints(mut self, shown: bool) -> Self {
427 self.quit_hints = shown;
428 self
429 }
430
431 pub fn stop_keys(mut self, keys: &'static [Key]) -> Self {
432 self.stop = keys;
433 self
434 }
435
436 pub fn through_keys(mut self, keys: &'static [Key]) -> Self {
437 self.through = keys;
438 self
439 }
440
441 pub fn eager(mut self, eager: bool) -> Self {
442 self.eager = eager;
443 self
444 }
445
446 pub fn keep_facts(mut self, keep: bool) -> Self {
447 self.keep_facts = keep;
448 self
449 }
450
451 pub fn lit_again(mut self, lit: bool) -> Self {
452 self.lit_again = lit;
453 self
454 }
455
456 pub fn hourglass_timing(mut self, delay: Duration, least: Duration) -> Self {
457 self.delay = delay;
458 self.least = least;
459 self
460 }
461
462 pub fn version_fit(mut self, fit: bool) -> Self {
463 self.version_fit = fit;
464 self
465 }
466
467 pub fn modes(mut self, modes: Modes) -> Self {
468 self.modes = modes;
469 self
470 }
471
472 pub fn header_look(mut self, look: HeaderLook) -> Self {
473 self.header_look = Some(look);
474 self
475 }
476
477 pub fn footer_look(mut self, look: FooterLook) -> Self {
478 self.footer_look = Some(look);
479 self
480 }
481
482 pub fn on_key(mut self, hook: impl FnMut(KeyEvent, &mut Cx<'_, E>) -> bool + 'static) -> Self {
483 self.on_key = Some(Box::new(hook));
484 self
485 }
486
487 pub fn after_draw(mut self, hook: impl FnMut(Duration) + 'static) -> Self {
488 self.after = Some(Box::new(hook));
489 self
490 }
491
492 fn rearm(&mut self) {
493 self.quit = QuitGuard::new(Wording::new(self.words.again.clone()))
494 .mode(self.mode)
495 .window(self.window)
496 .triggers(self.triggers);
497 }
498
499 fn repaint(&mut self) {
500 let mut palette = self.palette;
501 let role = self
502 .role
503 .as_ref()
504 .and_then(|name| self.roles.iter().find(|(kept, _)| kept == name));
505 if let Some((_, style)) = role {
506 palette.title = *style;
507 palette.glass = *style;
508 palette.shimmer = *style;
509 }
510 self.drawn = if self.mono { palette.mono() } else { palette };
511 }
512
513 pub fn set_palette(&mut self, palette: Palette) {
514 self.palette = palette;
515 self.repaint();
516 }
517
518 pub fn set_words(&mut self, words: Words) {
519 *self.quit.wording_mut() = Wording::new(words.again.clone());
520 self.words = words;
521 }
522
523 pub fn set_layout(&mut self, layout: Layout) {
524 self.layout = layout;
525 }
526
527 pub fn current_palette(&self) -> &Palette {
528 &self.palette
529 }
530
531 pub(crate) fn slot_of(&self, wanted: fn(&Role) -> bool) -> Option<usize> {
532 self.screens.iter().position(|slot| wanted(&slot.role))
533 }
534
535 pub(crate) fn gate_slot(&self) -> Option<usize> {
536 self.slot_of(|role| matches!(role, Role::Gate))
537 }
538
539 pub(crate) fn app_slot(&self) -> Option<usize> {
540 self.slot_of(|role| matches!(role, Role::App))
541 }
542
543 pub fn current(&self) -> Option<usize> {
544 if self.gated
545 && let Some(gate) = self.gate_slot()
546 {
547 return Some(gate);
548 }
549 self.open.or_else(|| self.model.current_screen())
550 }
551
552 pub fn names(&self) -> Vec<&str> {
553 self.screens
554 .iter()
555 .enumerate()
556 .map(|(index, slot)| match &slot.role {
557 Role::Tab => self
558 .model
559 .tabs
560 .iter()
561 .find(|tab| tab.screen == index)
562 .map_or("", |tab| tab.label.text()),
563 Role::Overlay(nav, _) => nav.section().map_or("", Section::name),
564 Role::Gate | Role::App => "",
565 })
566 .collect()
567 }
568
569 pub(crate) fn walkable(&self, index: usize) -> bool {
570 self.screens
571 .get(index)
572 .is_some_and(|slot| matches!(slot.role, Role::Tab | Role::Overlay(..)))
573 }
574
575 pub fn find<T: Screen<E>>(&self) -> Option<&T> {
576 self.screens.iter().find_map(|slot| {
577 let any: &dyn Any = slot.screen.as_ref();
578 any.downcast_ref::<T>()
579 })
580 }
581
582 pub fn find_mut<T: Screen<E>>(&mut self) -> Option<&mut T> {
583 self.screens.iter_mut().find_map(|slot| {
584 let any: &mut dyn Any = slot.screen.as_mut();
585 any.downcast_mut::<T>()
586 })
587 }
588
589 pub fn sender(&self) -> Sender<Wake<E>> {
590 self.out.sender.clone()
591 }
592
593 pub fn waker(&self, screen: usize) -> Waker<E> {
594 Waker::new(self.out.sender.clone(), screen).epoch(Arc::clone(&self.out.epoch))
595 }
596
597 pub fn go(&mut self, screen: usize) -> bool {
598 match self.screens.get(screen).map(|slot| &slot.role) {
599 Some(Role::Tab) => {
600 if !self.model.go_screen(screen) {
601 return false;
602 }
603 self.open = None;
604 }
605 Some(Role::Overlay(..)) => self.open = Some(screen),
606 Some(Role::Gate | Role::App) | None => return false,
607 }
608 self.focus = Focus::Content;
609 self.enter();
610 true
611 }
612
613 pub(crate) fn reveal(&mut self) {
614 self.gated = false;
615 }
616
617 pub fn changed(&mut self, source: &str) {
618 for slot in &mut self.screens {
619 if slot.screen.sources().contains(&source) {
620 slot.stale = true;
621 }
622 }
623 }
624
625 pub fn start(&mut self) {
626 if let Some(app) = self.app_slot() {
627 self.begin(app);
628 }
629 if let Some(index) = self.current() {
630 self.begin(index);
631 }
632 self.follow();
633 }
634
635 pub fn busy(&self) -> usize {
636 let screens: usize = self.screens.iter().map(|slot| slot.screen.busy()).sum();
637 screens + self.running.load(Ordering::SeqCst)
638 }
639
640 pub fn key(&mut self, key: KeyEvent) -> Flow {
641 if key.kind == KeyEventKind::Release {
642 return Flow::Stay;
643 }
644 self.pressed = Some(key);
645 let flow = self.route(key);
646 self.pressed = None;
647 flow
648 }
649
650 fn overlay_for(&self, key: Key) -> Option<usize> {
651 self.screens
652 .iter()
653 .position(|slot| matches!(&slot.role, Role::Overlay(_, keys) if keys.contains(&key)))
654 }
655
656 pub(crate) fn typing_now(&self) -> bool {
657 if let Some(top) = self.out.modals.last() {
658 return top.modal.value(top.modal.focused()).is_some();
659 }
660 self.current()
661 .and_then(|index| self.screens.get(index))
662 .is_some_and(|slot| slot.screen.typing() || matches!(slot.role, Role::Gate))
663 }
664
665 fn route(&mut self, key: KeyEvent) -> Flow {
666 let busy = self.busy();
667 let pressed: Key = key.into();
668 let sticky = self.sticky
669 && self.quit.armed()
670 && self.quit.asking().is_none()
671 && !self.triggers.contains(&pressed);
672 if !sticky {
673 match self.quit.key(pressed, self.moment, busy > 0) {
674 Guard::Quit => return Flow::Quit,
675 Guard::Held => {
676 self.word_quit();
677 return Flow::Stay;
678 }
679 _ => {}
680 }
681 }
682 self.out.said = None;
683 self.follow();
684 if let Some((owner, takeover)) = self.out.takeover.as_ref() {
685 if takeover.cancel.contains(&pressed) {
686 let (owner, id) = (*owner, takeover.id);
687 self.out.takeover = None;
688 self.call(owner, |screen, cx| screen.layer(id, Layered::Closed, cx));
689 return self.apply();
690 }
691 return Flow::Stay;
692 }
693 if !self.out.modals.is_empty() {
694 return self.modal_key(key);
695 }
696 let through = self.through.contains(&pressed);
697 if let Some(asking) = self.out.asking.as_mut().filter(|_| !through) {
698 if let Some(answer) = asking.confirm.key(pressed) {
699 let screen = asking.screen;
700 self.out.asking = None;
701 let yes = matches!(answer, Answer::Yes);
702 self.call(screen, |screen, cx| screen.answer(yes, cx));
703 }
704 return self.apply();
705 }
706 if let Some(picking) = self.out.picking.as_mut() {
707 let choice = if picking.pick.cancel.contains(&pressed) {
708 Some(None)
709 } else if let Step::Open(index) = picking.pick.rows.key(key.into()) {
710 Some(Some(index))
711 } else {
712 None
713 };
714 if let Some(choice) = choice {
715 let screen = picking.screen;
716 self.out.picking = None;
717 self.call(screen, |screen, cx| screen.picked(choice, cx));
718 }
719 return self.apply();
720 }
721 if let Some(app) = self.app_slot()
722 && self.call(app, |screen, cx| screen.claim(key, cx)) == Some(true)
723 {
724 return self.apply();
725 }
726 let Some(index) = self.current() else {
727 return Flow::Stay;
728 };
729 if self.gated {
730 self.call(index, |screen, cx| screen.key(key, cx));
731 return self.apply();
732 }
733 if self.screens[index].screen.typing() {
734 self.call(index, |screen, cx| screen.key(key, cx));
735 return self.apply();
736 }
737 if self.help.as_mut().is_some_and(|help| help.key(pressed)) {
738 return Flow::Stay;
739 }
740 if through && self.out.asking.is_some() {
741 return self.press(index, key);
742 }
743 if let Some(flow) = self.focused(pressed) {
744 return flow;
745 }
746 if let Some(overlay) = self.overlay_for(pressed) {
747 if self.open == Some(overlay) {
748 self.open = None;
749 } else {
750 self.go(overlay);
751 }
752 return self.apply();
753 }
754 if let Some(open) = self.open {
755 if self.model.keys.back.contains(&key.into())
756 && !(self.loading(open) && self.stop.contains(&pressed))
757 {
758 self.leave(open);
759 return self.apply();
760 }
761 } else {
762 if let Some(heading) = self.model.heading_for(pressed) {
763 if self.model.go_heading(heading) {
764 self.enter();
765 }
766 return self.apply();
767 }
768 match self.digit(pressed) {
769 Digit::Done(flow) => return flow,
770 Digit::Pass => return self.press(index, key),
771 Digit::Nav => {}
772 }
773 if let Some(action) = self.model.nav.action(key.into()) {
774 self.navigate(action);
775 return self.apply();
776 }
777 }
778 if self.loading(index) && self.stop.contains(&pressed) {
779 self.cancel(index);
780 return Flow::Stay;
781 }
782 self.press(index, key)
783 }
784
785 fn digit(&mut self, key: Key) -> Digit {
786 let Some(head) = self.head.as_ref() else {
787 return Digit::Nav;
788 };
789 let Key::Char(digit @ '0'..='9') = key else {
790 return Digit::Nav;
791 };
792 if !self.model.keys.digits {
793 return Digit::Nav;
794 }
795 let number = if digit == '0' {
796 10
797 } else {
798 digit as usize - '0' as usize
799 };
800 let lone = head.lone;
801 let swallow = self.model.keys.swallow_digits;
802 let missing = if swallow {
803 Digit::Done(Flow::Stay)
804 } else {
805 Digit::Pass
806 };
807 match head.numbers {
808 Numbers::Off => Digit::Pass,
809 Numbers::Across => {
810 let Some(tab) = self.model.visible.get(number - 1) else {
811 return Digit::Nav;
812 };
813 let heading = self.model.tabs[*tab].group;
814 let group = self.model.groups.iter().position(|kept| *kept == heading);
815 if lone && group.is_some_and(|group| self.model.lone(group)) {
816 return missing;
817 }
818 Digit::Nav
819 }
820 Numbers::InGroup => {
821 let group = self.model.nav.place().group;
822 let count = self.model.group_tabs().len();
823 if (lone && count == 1) || number > count {
824 return missing;
825 }
826 if self.model.nav.go_to(group, number - 1).is_some() {
827 self.enter();
828 }
829 Digit::Done(self.apply())
830 }
831 }
832 }
833
834 pub(crate) fn ring(&self) -> Vec<Focus> {
835 let mut ring = Vec::new();
836 let Some(head) = self.head.as_ref() else {
837 return ring;
838 };
839 if self.open.is_some() || self.gated {
840 return ring;
841 }
842 let slot = self
843 .current()
844 .and_then(|index| self.screens.get(index))
845 .is_some_and(|slot| !slot.screen.after_tabs().is_empty());
846 for row in &head.rows {
847 match row {
848 Row::Groups if self.model.groups.len() > 1 => ring.push(Focus::Groups),
849 Row::Sections if self.model.nav.count() > 0 => {
850 ring.push(Focus::Sections);
851 if slot {
852 ring.push(Focus::Slot);
853 }
854 }
855 _ => {}
856 }
857 }
858 ring
859 }
860
861 fn focused(&mut self, key: Key) -> Option<Flow> {
862 let ring = self.ring();
863 if ring.is_empty() {
864 self.focus = Focus::Content;
865 return None;
866 }
867 let at = ring.iter().position(|focus| *focus == self.focus);
868 if at.is_none() {
869 self.focus = Focus::Content;
870 }
871 let stops = ring.len() + 1;
872 let index = at.unwrap_or(ring.len());
873 let step = |by: isize| {
874 let next = (index as isize + by).rem_euclid(stops as isize) as usize;
875 ring.get(next).copied().unwrap_or(Focus::Content)
876 };
877 let (next, previous) = self.focus_keys;
878 if next.contains(&key) {
879 self.focus = step(1);
880 return Some(Flow::Stay);
881 }
882 if previous.contains(&key) {
883 self.focus = step(-1);
884 return Some(Flow::Stay);
885 }
886 let action = match (self.focus, key) {
887 (Focus::Content, _) => return None,
888 (_, Key::Esc | Key::Enter) => {
889 self.focus = Focus::Content;
890 return Some(Flow::Stay);
891 }
892 (_, Key::Up) => {
893 if index > 0 {
894 self.focus = ring[index - 1];
895 }
896 return Some(Flow::Stay);
897 }
898 (_, Key::Down) => {
899 self.focus = step(1);
900 return Some(Flow::Stay);
901 }
902 (Focus::Groups, Key::Left) => Action::PrevGroup,
903 (Focus::Groups, Key::Right) => Action::NextGroup,
904 (Focus::Sections, Key::Left) => Action::PrevSection,
905 (Focus::Sections, Key::Right) => Action::NextSection,
906 _ => return None,
907 };
908 self.navigate(action);
909 Some(self.apply())
910 }
911
912 fn modal_key(&mut self, key: KeyEvent) -> Flow {
913 let Some(top) = self.out.modals.last_mut() else {
914 return Flow::Stay;
915 };
916 let (owner, id) = (top.screen, top.modal.id);
917 match top.modal.key(key) {
918 Outcome::Stay => Flow::Stay,
919 Outcome::Heard(event) => {
920 self.call(owner, |screen, cx| screen.layer(id, event, cx));
921 self.apply()
922 }
923 Outcome::Close => {
924 self.out.modals.pop();
925 self.call(owner, |screen, cx| screen.layer(id, Layered::Closed, cx));
926 self.apply()
927 }
928 }
929 }
930
931 fn press(&mut self, index: usize, key: KeyEvent) -> Flow {
932 if let Some(mut hook) = self.on_key.take() {
933 let typing = self.typing_now();
934 let mut cx = Cx {
935 screen: index,
936 now: self.moment,
937 key: self.pressed,
938 focus: self.focus,
939 typing,
940 out: &mut self.out,
941 };
942 let taken = hook(key, &mut cx);
943 self.on_key = Some(hook);
944 if taken {
945 return self.apply();
946 }
947 }
948 self.call(index, |screen, cx| screen.key(key, cx));
949 self.apply()
950 }
951
952 pub fn paste(&mut self, text: &str) {
953 if self.out.takeover.is_some()
954 || self.out.asking.is_some()
955 || self.out.picking.is_some()
956 || self.quit.asking().is_some()
957 {
958 return;
959 }
960 if let Some(top) = self.out.modals.last_mut() {
961 let (owner, id) = (top.screen, top.modal.id);
962 if let Outcome::Heard(event) = top.modal.paste(text) {
963 self.call(owner, |screen, cx| screen.layer(id, event, cx));
964 self.apply();
965 }
966 return;
967 }
968 if let Some(index) = self.current() {
969 self.call(index, |screen, cx| screen.paste(text, cx));
970 self.apply();
971 }
972 }
973
974 pub fn mouse(&mut self, mouse: MouseEvent) {
975 if self.out.takeover.is_some() || !self.out.modals.is_empty() {
976 return;
977 }
978 let Some(index) = self.current() else {
979 return;
980 };
981 let (column, row) = (mouse.column, mouse.row);
982 let down = mouse.kind == MouseEventKind::Down(MouseButton::Left);
983 if down && !self.gated && self.head.is_some() {
984 let hit = self
985 .rows
986 .iter()
987 .find(|(_, line)| line.contains((column, row).into()))
988 .copied();
989 if let Some((kind, line)) = hit {
990 self.click(kind, line, column);
991 return;
992 }
993 }
994 if down && !self.gated && self.head.is_none() && self.top.contains((column, row).into()) {
995 let facts = self.screens[index].screen.facts();
996 let title = self.title_text();
997 let place = self
998 .header(&title, &facts, &[], &[])
999 .hit(self.top, column, row);
1000 if let Some(place) = place
1001 && self.model.nav.go_to(place.group, place.section).is_some()
1002 {
1003 self.open = None;
1004 self.enter();
1005 }
1006 return;
1007 }
1008 let area = self.content;
1009 self.call(index, |screen, cx| screen.mouse(mouse, area, cx));
1010 self.apply();
1011 }
1012
1013 fn click(&mut self, kind: Row, line: Rect, column: u16) {
1014 let moved = match kind {
1015 Row::Groups => self
1016 .group_labels()
1017 .hit(line, column)
1018 .and_then(|group| self.model.groups.get(group).copied())
1019 .is_some_and(|heading| self.model.go_heading(heading)),
1020 Row::Sections if self.open.is_none() && self.model.nav.depth() == 0 => {
1021 let group = self.model.nav.place().group;
1022 let tail = self.tail();
1023 self.section_labels(&tail)
1024 .hit(line, column)
1025 .is_some_and(|section| self.model.nav.go_to(group, section).is_some())
1026 }
1027 _ => false,
1028 };
1029 if moved {
1030 self.open = None;
1031 self.enter();
1032 }
1033 }
1034
1035 pub fn event(&mut self, screen: usize, event: E) {
1036 self.call(screen, |target, cx| target.event(event, cx));
1037 self.apply();
1038 }
1039
1040 pub fn loaded(&mut self, screen: usize, generation: u64, event: E) -> bool {
1041 let Some(slot) = self.screens.get_mut(screen) else {
1042 return false;
1043 };
1044 if slot.reading == Some(generation) {
1045 slot.reading = None;
1046 }
1047 if generation != slot.generation {
1048 return false;
1049 }
1050 self.call(screen, |target, cx| target.loaded(event, cx));
1051 self.apply();
1052 true
1053 }
1054
1055 pub fn handle(&mut self, wake: Wake<E>) -> Flow {
1056 match wake {
1057 Wake::Input(Event::Key(key)) => return self.key(key),
1058 Wake::Input(Event::Paste(text)) => self.paste(&text),
1059 Wake::Input(Event::Mouse(mouse)) => self.mouse(mouse),
1060 Wake::Event(screen, event) => self.event(screen, event),
1061 Wake::Stamped(stamp, screen, event) => {
1062 if stamp == self.out.stamp() {
1063 self.event(screen, event);
1064 }
1065 }
1066 Wake::Loaded(screen, generation, event) => {
1067 self.loaded(screen, generation, event);
1068 }
1069 Wake::Activity(activity) => {
1070 self.out.changes.push(Change::Put(activity));
1071 self.apply();
1072 }
1073 Wake::Command(screen, report) => return self.heard(screen, report),
1074 Wake::Signal(_) => return Flow::Quit,
1075 _ => {}
1076 }
1077 Flow::Stay
1078 }
1079
1080 fn heard(&mut self, screen: usize, report: Report) -> Flow {
1081 let Report { activity, heard } = report;
1082 let id = activity.id;
1083 let mut end = None;
1084 for heard in heard {
1085 if let Heard::Exit(exit) = &heard {
1086 end = Some(exit.state());
1087 }
1088 self.call(screen, |target, cx| target.heard(id, heard, cx));
1089 }
1090 self.out.changes.push(Change::Ran(activity));
1091 self.out
1092 .changes
1093 .extend(end.map(|state| Change::End(id, state)));
1094 self.apply()
1095 }
1096
1097 fn navigate(&mut self, action: Action) {
1098 match action {
1099 Action::Back => {
1100 if let Some(Move::Back { .. }) = self.model.nav.apply(Action::Back)
1101 && let Some(index) = self.current()
1102 {
1103 self.call(index, |screen, cx| screen.back(cx));
1104 }
1105 }
1106 action => {
1107 if let Some(Move::Moved(_)) = self.model.nav.apply(action) {
1108 self.enter();
1109 }
1110 }
1111 }
1112 }
1113
1114 fn leave(&mut self, index: usize) {
1115 let Some(Role::Overlay(nav, _)) = self.screens.get_mut(index).map(|slot| &mut slot.role)
1116 else {
1117 return;
1118 };
1119 if nav.back().is_some() {
1120 self.call(index, |screen, cx| screen.back(cx));
1121 } else {
1122 self.open = None;
1123 }
1124 }
1125
1126 fn enter(&mut self) {
1127 if let Some(index) = self.current() {
1128 self.begin(index);
1129 self.call(index, |screen, cx| screen.entered(cx));
1130 self.apply();
1131 }
1132 }
1133
1134 fn begin(&mut self, index: usize) {
1135 if self.screens[index].started {
1136 return;
1137 }
1138 self.screens[index].started = true;
1139 self.call(index, |screen, cx| screen.start(cx));
1140 self.apply();
1141 }
1142
1143 pub(crate) fn follow(&mut self) {
1144 let Some(index) = self.current() else {
1145 return;
1146 };
1147 let slot = &mut self.screens[index];
1148 let crumb = slot.screen.crumb();
1149 let nav = match &mut slot.role {
1150 Role::Overlay(nav, _) => nav,
1151 Role::Tab => &mut self.model.nav,
1152 Role::Gate | Role::App => return,
1153 };
1154 if nav.title() == crumb.as_deref() {
1155 return;
1156 }
1157 let selected = slot.screen.selected();
1158 nav.close();
1159 if let Some(crumb) = crumb {
1160 nav.open(crumb, selected);
1161 }
1162 }
1163
1164 pub(crate) fn word_quit(&mut self) {
1165 if self.quit.asking().is_none() {
1166 return;
1167 }
1168 let ask = (self.words.busy)(self.busy());
1169 self.quit.wording_mut().ask = ask.into();
1170 }
1171
1172 pub(crate) fn loading(&self, index: usize) -> bool {
1173 self.screens
1174 .get(index)
1175 .is_some_and(|slot| matches!(slot.screen.phase(), Phase::Loading(_)))
1176 }
1177
1178 fn cancel(&mut self, index: usize) {
1179 let slot = &mut self.screens[index];
1180 slot.generation += 1;
1181 slot.reading = None;
1182 slot.screen.cancel();
1183 }
1184
1185 fn call<R>(
1186 &mut self,
1187 index: usize,
1188 work: impl FnOnce(&mut dyn Screen<E>, &mut Cx<'_, E>) -> R,
1189 ) -> Option<R> {
1190 let typing = self.typing_now();
1191 let slot = self.screens.get_mut(index)?;
1192 let mut cx = Cx {
1193 screen: index,
1194 now: self.moment,
1195 key: self.pressed,
1196 focus: self.focus,
1197 typing,
1198 out: &mut self.out,
1199 };
1200 Some(work(slot.screen.as_mut(), &mut cx))
1201 }
1202
1203 fn apply(&mut self) -> Flow {
1204 if let Some(palette) = self.out.palette.take() {
1205 self.palette = palette;
1206 self.repaint();
1207 }
1208 if let Some(role) = self.out.role.take() {
1209 self.role = role;
1210 self.repaint();
1211 }
1212 if let Some(words) = self.out.words.take() {
1213 self.set_words(words);
1214 }
1215 if let Some(hints) = self.out.hints.take() {
1216 self.hints = hints;
1217 }
1218 if let Some(title) = self.out.title.take() {
1219 self.title = title;
1220 }
1221 if let Some(layout) = self.out.layout.take() {
1222 self.layout = layout;
1223 }
1224 if std::mem::take(&mut self.out.bump) {
1225 self.out.epoch.fetch_add(1, Ordering::SeqCst);
1226 self.out.jobs.clear();
1227 for slot in &mut self.screens {
1228 slot.generation += 1;
1229 slot.reading = None;
1230 slot.stale = true;
1231 }
1232 }
1233 for index in std::mem::take(&mut self.out.reload) {
1234 if let Some(slot) = self.screens.get_mut(index) {
1235 slot.stale = true;
1236 }
1237 }
1238 for source in std::mem::take(&mut self.out.changed) {
1239 self.changed(&source);
1240 }
1241 let moment = self.moment;
1242 for change in std::mem::take(&mut self.out.changes) {
1243 if let Change::Forget(id) = change {
1244 for handle in self.commands.iter().filter(|handle| handle.id == id) {
1245 handle.forget();
1246 }
1247 }
1248 if let Some(board) = self.board_mut() {
1249 board.change(change, moment);
1250 }
1251 }
1252 for id in std::mem::take(&mut self.out.stops) {
1253 let stopped = self.stop(id);
1254 self.show_stopped(stopped);
1255 }
1256 for toast in std::mem::take(&mut self.out.toasts) {
1257 self.toasts.push((moment + toast.lasting, toast));
1258 }
1259 match (&self.out.takeover, self.taken) {
1260 (Some(_), None) => self.taken = Some(moment),
1261 (None, Some(_)) => self.taken = None,
1262 _ => {}
1263 }
1264 if let Some(shown) = self.out.show.take()
1265 && self.model.show(&*shown)
1266 && self.open.is_none()
1267 {
1268 self.focus = Focus::Content;
1269 self.enter();
1270 }
1271 if let Some(heading) = self.out.group.take()
1272 && self.model.go_heading(heading)
1273 {
1274 self.open = None;
1275 self.focus = Focus::Content;
1276 self.enter();
1277 }
1278 if let Some(shut) = self.out.gate.take()
1279 && shut != self.gated
1280 && self.gate_slot().is_some()
1281 {
1282 self.gated = shut;
1283 self.focus = Focus::Content;
1284 self.enter();
1285 }
1286 if let Some(screen) = self.out.go.take() {
1287 self.go(screen);
1288 }
1289 if std::mem::take(&mut self.out.quit) {
1290 return Flow::Quit;
1291 }
1292 Flow::Stay
1293 }
1294
1295 fn stop(&mut self, id: u64) -> Vec<Activity> {
1296 let moment = self.moment;
1297 let mut stopped: Vec<Activity> = self
1298 .commands
1299 .iter()
1300 .filter(|handle| handle.id == id)
1301 .filter_map(|handle| handle.stop(moment))
1302 .collect();
1303 stopped.extend(self.unqueue(|run| run.activity.id == id));
1304 stopped
1305 }
1306
1307 fn unqueue(&mut self, which: impl Fn(&Run) -> bool) -> Vec<Activity> {
1308 let moment = self.moment;
1309 let (queued, runs): (Vec<Run>, Vec<Run>) = std::mem::take(&mut self.out.runs)
1310 .into_iter()
1311 .partition(which);
1312 self.out.runs = runs;
1313 queued
1314 .into_iter()
1315 .map(|run| {
1316 let mut activity = run.activity;
1317 command::halt(&mut activity, moment);
1318 let report = Report {
1319 activity: activity.clone(),
1320 heard: vec![Heard::Exit(Exit::Stopped)],
1321 };
1322 let _ = self.out.sender.send(Wake::Command(run.screen, report));
1323 activity
1324 })
1325 .collect()
1326 }
1327
1328 fn show_stopped(&mut self, stopped: Vec<Activity>) {
1329 let moment = self.moment;
1330 let Some(board) = self.board_mut() else {
1331 return;
1332 };
1333 for activity in stopped {
1334 let id = activity.id;
1335 board.change(Change::Ran(activity), moment);
1336 board.change(Change::End(id, State::Stopped), moment);
1337 }
1338 }
1339
1340 fn end(&mut self) {
1341 let moment = self.tick();
1342 let mut stopped = command::end(&self.commands, moment);
1343 stopped.extend(self.unqueue(|_| true));
1344 self.show_stopped(stopped);
1345 }
1346
1347 pub(crate) fn reads(&mut self) -> Vec<(usize, u64, Job<E>)> {
1348 let mut reads = Vec::new();
1349 for (index, slot) in self.screens.iter_mut().enumerate() {
1350 if !slot.stale || slot.reading.is_some() || !(slot.started || self.eager) {
1351 continue;
1352 }
1353 slot.stale = false;
1354 if let Some(job) = slot.screen.load() {
1355 slot.generation += 1;
1356 slot.reading = Some(slot.generation);
1357 reads.push((index, slot.generation, job));
1358 }
1359 }
1360 reads
1361 }
1362
1363 pub fn pump(&mut self) {
1364 for (screen, generation, job) in self.reads() {
1365 let sender = self.out.sender.clone();
1366 thread::spawn(move || {
1367 let _ = sender.send(Wake::Loaded(screen, generation, job()));
1368 });
1369 }
1370 for (screen, stamp, job) in self.out.jobs.drain(..) {
1371 let sender = self.out.sender.clone();
1372 thread::spawn(move || {
1373 let _ = sender.send(Wake::Stamped(stamp, screen, job()));
1374 });
1375 }
1376 self.commands.retain(|handle| !handle.over());
1377 let board = self.board.is_some();
1378 for mut run in self.out.runs.drain(..) {
1379 run.keep &= board;
1380 let sender = self.out.sender.clone();
1381 let handle = command::spawn(run, sender, Arc::clone(&self.running));
1382 self.commands.extend(handle);
1383 }
1384 }
1385
1386 pub fn settle(&mut self) {
1387 for _ in 0..SETTLE {
1388 let mut woke = false;
1389 for (screen, generation, job) in self.reads() {
1390 self.loaded(screen, generation, job());
1391 woke = true;
1392 }
1393 for (screen, stamp, job) in std::mem::take(&mut self.out.jobs) {
1394 let event = job();
1395 if stamp == self.out.stamp() {
1396 self.event(screen, event);
1397 }
1398 woke = true;
1399 }
1400 while let Ok(wake) = self.inbox.try_recv() {
1401 self.handle(wake);
1402 woke = true;
1403 }
1404 if self.ticks().is_some() {
1405 woke = true;
1406 }
1407 if !woke {
1408 return;
1409 }
1410 }
1411 }
1412
1413 pub fn advance(&mut self, by: Duration) -> Flow {
1414 self.moment += by;
1415 self.quit.tick(self.moment);
1416 self.ticks().unwrap_or(Flow::Stay)
1417 }
1418
1419 pub(crate) fn tick(&mut self) -> Instant {
1420 self.moment = Instant::now();
1421 self.quit.tick(self.moment);
1422 self.moment
1423 }
1424
1425 fn due(&self, slot: &Slot<E>) -> Option<Instant> {
1426 if !(slot.started || self.eager) {
1427 return None;
1428 }
1429 slot.screen
1430 .deadline()
1431 .filter(|deadline| slot.ticked != Some(*deadline))
1432 }
1433
1434 pub(crate) fn ticks(&mut self) -> Option<Flow> {
1435 let moment = self.moment;
1436 let before = self.toasts.len();
1437 self.toasts.retain(|(until, _)| *until > moment);
1438 let mut flow = (self.toasts.len() != before).then_some(Flow::Stay);
1439 for index in 0..self.screens.len() {
1440 let Some(deadline) = self.due(&self.screens[index]) else {
1441 continue;
1442 };
1443 if deadline > self.moment {
1444 continue;
1445 }
1446 self.screens[index].ticked = Some(deadline);
1447 self.call(index, |screen, cx| screen.tick(cx));
1448 let quit = self.apply() == Flow::Quit || flow == Some(Flow::Quit);
1449 flow = Some(if quit { Flow::Quit } else { Flow::Stay });
1450 }
1451 flow
1452 }
1453
1454 pub(crate) fn animating(&self) -> bool {
1455 if self.out.takeover.is_some() {
1456 return true;
1457 }
1458 self.current()
1459 .and_then(|index| self.screens.get(index))
1460 .is_some_and(|slot| slot.screen.animating() || slot.glass.is_some())
1461 }
1462
1463 fn glass_due(&self) -> Option<Instant> {
1464 let slot = self.screens.get(self.current()?)?;
1465 let loading = matches!(slot.screen.phase(), Phase::Loading(_));
1466 match (loading, slot.since, slot.glass) {
1467 (true, Some(since), None) => Some(since + self.delay),
1468 (false, _, Some(shown)) => Some(shown + self.least),
1469 _ => None,
1470 }
1471 }
1472
1473 pub(crate) fn deadlines(&self) -> [Option<Instant>; 5] {
1474 let screens = self.screens.iter().filter_map(|slot| self.due(slot)).min();
1475 let open = self.open.is_some() && self.open == self.board;
1476 let board = self.board().and_then(|board| board.wake(self.moment, open));
1477 let bands = [&self.band, &self.top_band]
1478 .into_iter()
1479 .flatten()
1480 .filter_map(|band| band.deadline(self.moment));
1481 let bands = bands.chain(board).min();
1482 let toasts = self.toasts.iter().map(|(until, _)| *until).min();
1483 [
1484 self.quit.deadline(),
1485 screens,
1486 bands,
1487 toasts,
1488 self.glass_due(),
1489 ]
1490 }
1491
1492 pub fn run(mut self) -> io::Result<()> {
1493 if std::env::var_os("NO_COLOR").is_some_and(|value| !value.is_empty()) {
1494 self.mono = true;
1495 self.repaint();
1496 }
1497 let mut term = Term::enter(self.modes)?;
1498 listen(self.out.sender.clone());
1499 self.run_in(&mut term)
1500 }
1501
1502 pub fn run_in(&mut self, term: &mut Term) -> io::Result<()> {
1503 let sender = self.out.sender.clone();
1504 let live = signal::live(move |signal| {
1505 let _ = sender.send(Wake::Signal(signal));
1506 });
1507 let ran = self.serve(term);
1508 self.end();
1509 if let Some(signal) = live.end() {
1510 restore();
1511 signal::die(signal);
1512 }
1513 ran
1514 }
1515
1516 fn serve(&mut self, term: &mut Term) -> io::Result<()> {
1517 self.tick();
1518 self.start();
1519 self.pump();
1520 let mut pace = Pace::default();
1521 let mut dirty = true;
1522 loop {
1523 let moment = self.tick();
1524 if dirty && pace.ready(moment) {
1525 let began = Instant::now();
1526 term.draw(|frame| self.draw(frame))?;
1527 pace.drew(moment);
1528 dirty = false;
1529 if let Some(after) = self.after.as_mut() {
1530 after(began.elapsed());
1531 }
1532 }
1533 let moment = self.tick();
1534 dirty = dirty || self.animating();
1535 let until = wait_until(moment, dirty, &pace, &self.deadlines());
1536 let first = match until {
1537 Some(at) => match self
1538 .inbox
1539 .recv_timeout(at.saturating_duration_since(moment))
1540 {
1541 Ok(wake) => Some(wake),
1542 Err(RecvTimeoutError::Timeout) => None,
1543 Err(RecvTimeoutError::Disconnected) => return Ok(()),
1544 },
1545 None => match self.inbox.recv() {
1546 Ok(wake) => Some(wake),
1547 Err(_) => return Ok(()),
1548 },
1549 };
1550 dirty = true;
1551 let mut next = first;
1552 while let Some(wake) = next {
1553 self.tick();
1554 match wake {
1555 Wake::Lost(error) => return Err(error),
1556 wake => {
1557 if self.handle(wake) == Flow::Quit {
1558 return Ok(());
1559 }
1560 }
1561 }
1562 next = self.inbox.try_recv().ok();
1563 }
1564 self.tick();
1565 if self.ticks() == Some(Flow::Quit) {
1566 return Ok(());
1567 }
1568 self.pump();
1569 for text in std::mem::take(&mut self.out.copies) {
1570 copy::copy(&text);
1571 }
1572 }
1573 }
1574}
1575
1576fn lone(section: Section) -> Nav {
1577 Nav::new(vec![Group::new("").section(section)]).keys(NavKeys::NONE)
1578}