max / quasi
| 1 | //! The terminal renderer for quasi. |
| 2 | //! |
| 3 | //! <!-- wiki: quasi-overview --> |
| 4 | //! |
| 5 | //! # Why this exists |
| 6 | //! |
| 7 | //! `quasi-router`'s own diagram says `renderer: webview | tui | egui`, and until |
| 8 | //! this crate two of those three did not exist. `quasi-webview` was the only |
| 9 | //! renderer of the screen tree, and both hosts are webview hosts: `quasi-axum` |
| 10 | //! serves the markup over HTTP, `quasi-tauri` serves the same markup over a |
| 11 | //! custom protocol. So every finding that has ever shaped the vocabulary came |
| 12 | //! from a webview port. |
| 13 | //! |
| 14 | //! That is the failure `makeover-layout`'s own header names. A webview can |
| 15 | //! express anything, so it never pushes back, and a vocabulary derived from the |
| 16 | //! renderer that can express everything comes out CSS-shaped with adapters |
| 17 | //! bolted onto the constrained renderers afterwards. The counter-principle is |
| 18 | //! to let the constrained consumer set the vocabulary, and quasi was the one |
| 19 | //! place it was not being applied. |
| 20 | //! |
| 21 | //! This crate is the constrained consumer. What it cannot draw is the point of |
| 22 | //! it: every place a description says something a terminal has no way to honour |
| 23 | //! is a finding, and the findings are the deliverable. |
| 24 | //! |
| 25 | //! # What it is not |
| 26 | //! |
| 27 | //! Not an implementation of `quasi_http::Serves`. That trait answers a `String` |
| 28 | //! and a content type, which is an HTTP host's contract rather than a |
| 29 | //! renderer's; a terminal answers cells in a buffer. The two share the |
| 30 | //! description and nothing else, which is worth knowing before reaching for the |
| 31 | //! trait's name. |
| 32 | //! |
| 33 | //! # The shape |
| 34 | //! |
| 35 | //! Flow layout, top to bottom. Every node answers a height for a width and then |
| 36 | //! draws into the rect it was given, which is the smallest thing that composes |
| 37 | //! and is what a description with no geometry in it can support. Nothing here |
| 38 | //! measures twice. |
| 39 | //! |
| 40 | //! # Drawing takes two arguments, and that is the first finding it produced |
| 41 | //! |
| 42 | //! A screen and a [`View`]. The description says what the app offers; the view |
| 43 | //! says what the user has done to it since it arrived — what is typed, what has |
| 44 | //! focus, how far a pane is scrolled. None of the three is in a description and |
| 45 | //! none of them belongs there, and a webview never had to say so because the |
| 46 | //! browser holds all three without being asked. `39057019` is the finding and |
| 47 | //! [`View`] carries the argument. |
| 48 | //! |
| 49 | //! A host with nothing to say passes `&View::new()`, which draws exactly what |
| 50 | //! the description says. |
| 51 | |
| 52 | |
| 53 | |
| 54 | |
| 55 | |
| 56 | |
| 57 | |
| 58 | |
| 59 | |
| 60 | |
| 61 | pub use ; |
| 62 | pub use ; |
| 63 | pub use View; |
| 64 | |
| 65 | use PieceStyle; |
| 66 | use TableStyle; |
| 67 | use ; |
| 68 | use ; |
| 69 | use Buffer; |
| 70 | use Rect; |
| 71 | |
| 72 | /// A terminal renderer for a described screen. |
| 73 | /// |
| 74 | /// Holds what a drawing needs and no screen state: the theme's colours, the |
| 75 | /// terminal's colour fidelity, and the table and piece styles derived from |
| 76 | /// both. A host makes one and keeps it. |
| 77 | |
| 78 | |
| 79 | theme: Theme, |
| 80 | palette: Palette, |
| 81 | table: TableStyle, |
| 82 | piece: PieceStyle, |
| 83 | |
| 84 | |
| 85 | |
| 86 | /// A renderer drawing in this theme, at this terminal's fidelity. |
| 87 | |
| 88 | |
| 89 | let theme = theme.for_terminal; |
| 90 | Self |
| 91 | palette: theme.palette, |
| 92 | table: from_theme, |
| 93 | piece: from_theme, |
| 94 | theme, |
| 95 | |
| 96 | |
| 97 | |
| 98 | /// The colours this renderer draws in. |
| 99 | |
| 100 | pub const |
| 101 | &self.theme |
| 102 | |
| 103 | |
| 104 | /// The depth palette, for a host painting its own chrome around a screen. |
| 105 | |
| 106 | pub const |
| 107 | &self.palette |
| 108 | |
| 109 | |
| 110 | /// Draw a whole screen into `area`, in the state `view` says it is in. |
| 111 | /// |
| 112 | /// The title is not drawn. A window title is the host's to set, the same |
| 113 | /// way a webview host puts it in `<title>` rather than in the document, and |
| 114 | /// a terminal that painted it would be spending a row on something the |
| 115 | /// terminal emulator already has a place for. |
| 116 | /// |
| 117 | /// [`Screen::discovery`] is declined outright: og:type, an indexability |
| 118 | /// flag and a canonical URL are facts about being crawled, and nothing |
| 119 | /// crawls a terminal. |
| 120 | |
| 121 | let mut pass = Pass |
| 122 | tui: self, |
| 123 | view, |
| 124 | seq: 0, |
| 125 | ; |
| 126 | let mut rest = area; |
| 127 | |
| 128 | // Notices first and at the top, because a notice belongs to the screen |
| 129 | // rather than to a place in it. A webview leaves where they land to the |
| 130 | // stylesheet; a terminal has no stylesheet, so this is the renderer |
| 131 | // deciding, and the top of the screen is the one place a message about |
| 132 | // the whole screen can go without claiming a region. |
| 133 | for notice in &screen.notices |
| 134 | let used = draw; |
| 135 | rest = below; |
| 136 | |
| 137 | |
| 138 | screen_regions; |
| 139 | |
| 140 | |
| 141 | /// Draw one node into `area`, and answer the rows it used. |
| 142 | /// |
| 143 | /// Never draws outside `area` and never below it: a node handed less room |
| 144 | /// than it wants is cut off at the bottom, which is what a terminal does |
| 145 | /// with everything. A [`Node::Region`] is the one that scrolls, and it |
| 146 | /// reads its offset off the view. |
| 147 | |
| 148 | draw |
| 149 | &mut Pass |
| 150 | tui: self, |
| 151 | view, |
| 152 | seq: 0, |
| 153 | , |
| 154 | node, |
| 155 | area, |
| 156 | buf, |
| 157 | |
| 158 | |
| 159 | |
| 160 | /// The rows `node` wants at `width`. |
| 161 | |
| 162 | |
| 163 | height |
| 164 | |
| 165 | |
| 166 | /// The tones, headings and marks the drawings take. |
| 167 | /// |
| 168 | /// The tone and heading maps used to be two methods here. Nothing in either |
| 169 | /// was about a described screen — both are `makeover-layout` in and a |
| 170 | /// ratatui `Style` out — so they went to `makeover-tui` 0.16.0 with the |
| 171 | /// drawings that read them, and every terminal app in the tree now says a |
| 172 | /// danger tone the same way. |
| 173 | pub const |
| 174 | &self.piece |
| 175 | |
| 176 | |
| 177 | |
| 178 | /// One drawing, as it walks the screen. |
| 179 | /// |
| 180 | /// Carries the count of reachable things passed so far, which is how the |
| 181 | /// drawing knows whether the thing it is about to draw is the focused one. The |
| 182 | /// count has to advance at exactly the points [`focus::spots`] records one, and |
| 183 | /// that agreement is asserted by a test rather than trusted: the two walks are |
| 184 | /// separate because one needs a rect and the other does not, and a walk that |
| 185 | /// counted differently would light the wrong control. |
| 186 | pub |
| 187 | tui: &'a Tui, |
| 188 | view: &'a View, |
| 189 | seq: usize, |
| 190 | |
| 191 | |
| 192 | |
| 193 | /// Take the next reachable position, and say whether it is the focused one. |
| 194 | |
| 195 | let mine = self.seq; |
| 196 | self.seq += 1; |
| 197 | mine == self.view.focus |
| 198 | |
| 199 | |
| 200 | |
| 201 | /// What is left of `area` after `used` rows from the top. |
| 202 | |
| 203 | let used = used.min; |
| 204 | Rect |
| 205 | x: area.x, |
| 206 | y: area.y + used, |
| 207 | width: area.width, |
| 208 | height: area.height - used, |
| 209 | |
| 210 | |
| 211 |