Skip to main content

max / quasi

3.4 KB · 89 lines History Blame Raw
1 //! The frame a mount puts around a screen, in cells.
2 //!
3 //! [`Frame`] is host-agnostic and lives in `quasi-router`; what is here is the
4 //! terminal's answer to it — the notices this mount holds, and the verbs it
5 //! offers.
6 //!
7 //! # At the bottom, and measured before the screen is drawn
8 //!
9 //! A terminal has no stylesheet to defer placement to, so the renderer decides,
10 //! the same way it already decides that notices go at the top. The bottom is
11 //! where a verb row belongs on a screen read downward, and the status line sits
12 //! above the verbs because that is the reading order the measured window has:
13 //! what happened, then what to do next.
14 //!
15 //! The rows come off the screen's area rather than being painted over it. A
16 //! frame drawn on top would cover whatever the last region put there, which on
17 //! a full list is a row of content the user is looking at.
18 //!
19 //! # A verb is an act, drawn the way every act is drawn
20 //!
21 //! Each one goes through [`crate::node`], so a verb that is disabled is muted,
22 //! one that has been pressed is drawn busy, and one that is focused is lit,
23 //! with no second implementation of any of it. What it costs is a row per verb
24 //! rather than a row of verbs, which is what a form's submit already costs and
25 //! is the same trade a terminal makes everywhere else.
26
27 use quasi_router::{Frame, Node, Screen};
28 use ratatui::buffer::Buffer;
29 use ratatui::layout::Rect;
30
31 use crate::{Local, Pass, Tui, below};
32
33 /// The rows this frame wants at `width`.
34 ///
35 /// Asked before the screen is drawn, so the two never have to agree about
36 /// anything afterwards: whatever this answers is taken off the bottom, and the
37 /// screen is handed the rest.
38 pub(crate) fn rows(tui: &Tui, frame: &Frame, screen: &Screen, width: u16) -> u16 {
39 if frame.bare() {
40 return 0;
41 }
42 let resting: u16 = held(frame, screen)
43 .map(|notice| crate::node::height(tui, notice, width, &Local::none()))
44 .sum();
45 let verbs: u16 = frame
46 .verbs
47 .iter()
48 .map(|verb| crate::node::height(tui, &Node::Act(verb.clone()), width, &Local::none()))
49 .sum();
50 resting + verbs
51 }
52
53 /// Draw it.
54 ///
55 /// Takes the same [`Pass`] the screen was drawn with, so the count of reachable
56 /// things carries on rather than restarting. A verb is reachable, and a restart
57 /// would light one whenever the caret was on the screen's first control.
58 pub(crate) fn draw(
59 pass: &mut Pass<'_>,
60 frame: &Frame,
61 screen: &Screen,
62 area: Rect,
63 buf: &mut Buffer,
64 ) {
65 if frame.bare() {
66 return;
67 }
68 let mut rest = area;
69 for notice in held(frame, screen) {
70 let used = crate::node::draw(pass, notice, rest, buf);
71 rest = below(rest, used);
72 }
73 // In declaration order, which is the order `crate::focus` appends them in.
74 // The two walks are separate and their agreement is asserted by a test,
75 // exactly as it is for the screen.
76 for verb in &frame.verbs {
77 let used = crate::node::draw(pass, &Node::Act(verb.clone()), rest, buf);
78 rest = below(rest, used);
79 }
80 }
81
82 /// The notices this mount holds, in the order the screen raised them.
83 ///
84 /// [`Frame::holds`] is the rule and it is the router's, so the three renderers
85 /// cannot each decide which kind of message belongs in a status line.
86 fn held<'a>(frame: &'a Frame, screen: &'a Screen) -> impl Iterator<Item = &'a Node> {
87 screen.notices.iter().filter(|one| frame.holds(one))
88 }
89