//! The terminal renderer for quasi. //! //! //! //! # Why this exists //! //! `quasi-router`'s own diagram says `renderer: webview | tui | egui`, and until //! this crate two of those three did not exist. `quasi-webview` was the only //! renderer of the screen tree, and both hosts are webview hosts: `quasi-axum` //! serves the markup over HTTP, `quasi-tauri` serves the same markup over a //! custom protocol. So every finding that has ever shaped the vocabulary came //! from a webview port. //! //! That is the failure `makeover-layout`'s own header names. A webview can //! express anything, so it never pushes back, and a vocabulary derived from the //! renderer that can express everything comes out CSS-shaped with adapters //! bolted onto the constrained renderers afterwards. The counter-principle is //! to let the constrained consumer set the vocabulary, and quasi was the one //! place it was not being applied. //! //! This crate is the constrained consumer. What it cannot draw is the point of //! it: every place a description says something a terminal has no way to honour //! is a finding, and the findings are the deliverable. //! //! # What it is not //! //! Not an implementation of `quasi_http::Serves`. That trait answers a `String` //! and a content type, which is an HTTP host's contract rather than a //! renderer's; a terminal answers cells in a buffer. The two share the //! description and nothing else, which is worth knowing before reaching for the //! trait's name. //! //! # The shape //! //! Flow layout, top to bottom. Every node answers a height for a width and then //! draws into the rect it was given, which is the smallest thing that composes //! and is what a description with no geometry in it can support. Nothing here //! measures twice. //! //! # Drawing takes two arguments, and that is the first finding it produced //! //! A screen and a [`View`]. The description says what the app offers; the view //! says what the user has done to it since it arrived — what is typed, what has //! focus, how far a pane is scrolled. None of the three is in a description and //! none of them belongs there, and a webview never had to say so because the //! browser holds all three without being asked. `39057019` is the finding and //! [`View`] carries the argument. //! //! A host with nothing to say passes `&View::new()`, which draws exactly what //! the description says. mod focus; mod node; mod region; mod runtime; mod view; #[cfg(test)] mod tests; pub use focus::{FieldSpot, Spot, spots}; pub use runtime::{Key, Runtime, Step}; pub use view::View; use makeover_tui::table::TableStyle; use makeover_tui::widget::WidgetStyle; use makeover_tui::{Fidelity, Palette, Theme}; use quasi_router::{Node, Screen}; use ratatui::buffer::Buffer; use ratatui::layout::Rect; /// A terminal renderer for a described screen. /// /// Holds what a drawing needs and no screen state: the theme's colours, the /// terminal's colour fidelity, and the table and widget styles derived from /// both. A host makes one and keeps it. #[derive(Debug, Clone)] pub struct Tui { theme: Theme, palette: Palette, table: TableStyle, widget: WidgetStyle, } impl Tui { /// A renderer drawing in this theme, at this terminal's fidelity. #[must_use] pub fn new(theme: Theme, fidelity: Fidelity) -> Self { let theme = theme.for_terminal(fidelity); Self { palette: theme.palette(fidelity), table: TableStyle::from_theme(&theme), widget: WidgetStyle::from_theme(&theme), theme, } } /// The colours this renderer draws in. #[must_use] pub const fn theme(&self) -> &Theme { &self.theme } /// The depth palette, for a host painting its own chrome around a screen. #[must_use] pub const fn palette(&self) -> &Palette { &self.palette } /// Draw a whole screen into `area`, in the state `view` says it is in. /// /// The title is not drawn. A window title is the host's to set, the same /// way a webview host puts it in `