//! The frame a mount puts around a screen, in cells. //! //! [`Frame`] is host-agnostic and lives in `quasi-router`; what is here is the //! terminal's answer to it — the notices this mount holds, and the verbs it //! offers. //! //! # At the bottom, and measured before the screen is drawn //! //! A terminal has no stylesheet to defer placement to, so the renderer decides, //! the same way it already decides that notices go at the top. The bottom is //! where a verb row belongs on a screen read downward, and the status line sits //! above the verbs because that is the reading order the measured window has: //! what happened, then what to do next. //! //! The rows come off the screen's area rather than being painted over it. A //! frame drawn on top would cover whatever the last region put there, which on //! a full list is a row of content the user is looking at. //! //! # A verb is an act, drawn the way every act is drawn //! //! Each one goes through [`crate::node`], so a verb that is disabled is muted, //! one that has been pressed is drawn busy, and one that is focused is lit, //! with no second implementation of any of it. What it costs is a row per verb //! rather than a row of verbs, which is what a form's submit already costs and //! is the same trade a terminal makes everywhere else. use quasi_router::{Frame, Node, Screen}; use ratatui::buffer::Buffer; use ratatui::layout::Rect; use crate::{Local, Pass, Tui, below}; /// The rows this frame wants at `width`. /// /// Asked before the screen is drawn, so the two never have to agree about /// anything afterwards: whatever this answers is taken off the bottom, and the /// screen is handed the rest. pub(crate) fn rows(tui: &Tui, frame: &Frame, screen: &Screen, width: u16) -> u16 { if frame.bare() { return 0; } let resting: u16 = held(frame, screen) .map(|notice| crate::node::height(tui, notice, width, &Local::none())) .sum(); let verbs: u16 = frame .verbs .iter() .map(|verb| crate::node::height(tui, &Node::Act(verb.clone()), width, &Local::none())) .sum(); resting + verbs } /// Draw it. /// /// Takes the same [`Pass`] the screen was drawn with, so the count of reachable /// things carries on rather than restarting. A verb is reachable, and a restart /// would light one whenever the caret was on the screen's first control. pub(crate) fn draw( pass: &mut Pass<'_>, frame: &Frame, screen: &Screen, area: Rect, buf: &mut Buffer, ) { if frame.bare() { return; } let mut rest = area; for notice in held(frame, screen) { let used = crate::node::draw(pass, notice, rest, buf); rest = below(rest, used); } // In declaration order, which is the order `crate::focus` appends them in. // The two walks are separate and their agreement is asserted by a test, // exactly as it is for the screen. for verb in &frame.verbs { let used = crate::node::draw(pass, &Node::Act(verb.clone()), rest, buf); rest = below(rest, used); } } /// The notices this mount holds, in the order the screen raised them. /// /// [`Frame::holds`] is the rule and it is the router's, so the three renderers /// cannot each decide which kind of message belongs in a status line. fn held<'a>(frame: &'a Frame, screen: &'a Screen) -> impl Iterator { screen.notices.iter().filter(|one| frame.holds(one)) }