max / quasi
| 1 | //! What the user can reach, and the order they reach it in. |
| 2 | //! |
| 3 | //! Nothing in a description says this. A webview never had to ask: the browser |
| 4 | //! builds the tab order out of the document, and the document is the drawing, so |
| 5 | //! the order falls out of the markup a renderer already emitted. A terminal |
| 6 | //! draws cells, and a cell knows nothing about the one before it. |
| 7 | //! |
| 8 | //! So focus order is this renderer's policy, and the policy is: **draw order**. |
| 9 | //! A thing is reachable when the description gives it something to call, and it |
| 10 | //! comes after whatever was drawn above it. That is the same rule the browser |
| 11 | //! applies to a document with no `tabindex` in it, which is the shape every |
| 12 | //! screen here has. |
| 13 | //! |
| 14 | //! The walk below mirrors [`crate::node::draw`] step for step, and it has to: |
| 15 | //! the drawing counts reachable things as it passes them and lights the one |
| 16 | //! whose number matches, so a walk that visited them in another order would |
| 17 | //! light the wrong one. The two are kept together deliberately rather than |
| 18 | //! being derived from one traversal, because the drawing needs a rect and this |
| 19 | //! needs nothing, and threading a rect through a walk that has no use for one |
| 20 | //! was the worse of the two couplings. |
| 21 | //! |
| 22 | //! # What is reachable |
| 23 | //! |
| 24 | //! Anything the description gives an address to, plus the two affordances that |
| 25 | //! are addresses in everything but name: a row that can be ticked, and a field |
| 26 | //! that takes typing. A [`Node::Meter`] and a [`Node::Figure`] are readouts and |
| 27 | //! are skipped, and a disabled [`Act`] is drawn and passed over, which is what |
| 28 | //! `disabled` means on every host. |
| 29 | //! |
| 30 | //! # A table row is one stop, and its controls are inside it |
| 31 | //! |
| 32 | //! Everywhere else a reachable thing is its own stop. A table row is the one |
| 33 | //! exception: it is a single stop, and the controls in its cells hang off it as |
| 34 | //! [`Spot::Row`]'s `inside`. A key steps in, cycles them, and steps back out. |
| 35 | //! |
| 36 | //! Not a preference. `makeover_tui::table` decides column widths, drops the |
| 37 | //! columns a narrow window cannot fit, and answers no coordinates back, so |
| 38 | //! nothing here knows where in a row a control was drawn and a flat focus |
| 39 | //! order would be a list of places with no places in it. Two steps buys what a |
| 40 | //! flat order cannot, and it is close to what a screen reader already gives |
| 41 | //! this shape, so a reader moving between the terminal and the webview finds |
| 42 | //! the same model. It is preferred over adding per-cell rects to a published |
| 43 | //! `makeover-tui` and over hand-laying the table here; per-cell rects stay |
| 44 | //! available underneath this later without changing what a user does. |
| 45 | //! |
| 46 | //! # Reach is this module; focus is the view's |
| 47 | //! |
| 48 | //! Both are this renderer's, and neither is describable. **Reach** is what this |
| 49 | //! module computes: which things can take focus, and in what order. **Focus** is |
| 50 | //! which reached thing holds the keyboard right now, and it lives in |
| 51 | //! [`crate::View`] because it is a fact about where the user has walked rather |
| 52 | //! than about the screen. The **focus ring** is what the drawing paints on it. |
| 53 | //! |
| 54 | //! The runtime now starts on the first reach unconditionally, which is what it |
| 55 | //! did in practice anyway once the user pressed anything. The three terms are |
| 56 | //! defined once in `makeover_layout`'s crate header, "Reach, focus and the |
| 57 | //! focus ring". |
| 58 | |
| 59 | use makeover_layout as layout; |
| 60 | use ; |
| 61 | |
| 62 | use crateLocal; |
| 63 | |
| 64 | /// One thing the user can reach, and what reaching it offers. |
| 65 | |
| 66 | |
| 67 | /// A control. Enter calls it, after its confirmation when it has one. |
| 68 | Act |
| 69 | /// What it calls. |
| 70 | action: Action, |
| 71 | /// What to ask first, if anything. |
| 72 | confirm: , |
| 73 | /// The key that reaches it without walking there. |
| 74 | /// |
| 75 | /// The one place the description already anticipated a terminal, and |
| 76 | /// the runtime is what finally binds it. |
| 77 | key: , |
| 78 | /// The screen's selection this acts on, if it acts on one. |
| 79 | /// |
| 80 | /// The commit half of a staged tick. The runtime reads the set the view |
| 81 | /// is holding and sends it with the call, which is the whole of what |
| 82 | /// makes a bulk action work without a line of gathering code. |
| 83 | over: , |
| 84 | /// The names of the values it asked for first, in order. |
| 85 | /// |
| 86 | /// `Act::asks`. The boxes stand beside the control here rather than |
| 87 | /// behind a disclosure, so they are ordinary field stops; what the |
| 88 | /// press needs is which of them belong to it, and this is that list. |
| 89 | /// Empty for the ordinary control. |
| 90 | asks: , |
| 91 | /// The box the press writes into, and what lands there. |
| 92 | /// |
| 93 | /// `Act::fills`. Where inside the box is this renderer's, and a |
| 94 | /// terminal has no caret inside a field to insert at -- `d52884b0` is |
| 95 | /// why the view holds none -- so the value goes on the end. The |
| 96 | /// vocabulary says that is not wrong: a description names the |
| 97 | /// destination and never the position. |
| 98 | fills: , |
| 99 | /// The value the press puts on the clipboard. |
| 100 | /// |
| 101 | /// `Act::copies`. A terminal owns no clipboard any more than it owns a |
| 102 | /// route, so this reaches the host as [`Step::Copy`](crate::Step::Copy) |
| 103 | /// rather than being written here. |
| 104 | copies: , |
| 105 | , |
| 106 | /// Text that goes somewhere. Enter follows it. |
| 107 | Link |
| 108 | /// Where it goes. |
| 109 | action: Action, |
| 110 | , |
| 111 | /// A question. Typing edits it; Enter leaves it alone. |
| 112 | Field, |
| 113 | /// The control that answers a whole form. |
| 114 | Submit |
| 115 | /// Where the answers go. |
| 116 | action: Action, |
| 117 | /// The names the form submits, in order, so the runtime can gather the |
| 118 | /// values it is holding for them. |
| 119 | names: , |
| 120 | , |
| 121 | /// A row of a list. |
| 122 | Row |
| 123 | /// What opening it calls. |
| 124 | activate: , |
| 125 | /// What ticking it calls, when the tick is itself the write. |
| 126 | toggle: , |
| 127 | /// Whether it is ticked, and whether it can be. |
| 128 | ticked: , |
| 129 | /// Whether it is part of a live selection, and whether it can be. |
| 130 | /// |
| 131 | /// `Row::chosen`'s three states, carried through so the runtime can |
| 132 | /// bind the two keys a terminal has for building one and say which of |
| 133 | /// them was pressed. Distinct from |
| 134 | /// [`ticked`](Self::Row::ticked) all the way down: a tick stages and a |
| 135 | /// choice is in force. |
| 136 | chosen: , |
| 137 | /// What its tick contributes to the screen's selection. |
| 138 | /// |
| 139 | /// `None` on a row that names nothing, which on a screen holding a |
| 140 | /// selection is the dead affordance `5f2b8753` was filed for: the box |
| 141 | /// is drawn, the key is bound, and the tick has nowhere to go. The |
| 142 | /// runtime declines to bind the key in that case rather than binding it |
| 143 | /// to nothing. |
| 144 | value: , |
| 145 | /// What it offers without showing: reached by a key here, by |
| 146 | /// right-click on a pointer host. |
| 147 | menu: , |
| 148 | /// The branch this row is, keyed the way a fold is remembered. |
| 149 | /// |
| 150 | /// `None` on a leaf, which is every row described before `Row::open` |
| 151 | /// existed. A branch is reachable even when nothing opens or ticks it: |
| 152 | /// the disclosure is the affordance, and a row drawing a chevron the |
| 153 | /// caret cannot land on is the dead affordance `5f2b8753` was filed |
| 154 | /// for, one member along. |
| 155 | branch: , |
| 156 | /// Whether that branch is open, as the reader has left it. |
| 157 | open: bool, |
| 158 | /// The controls drawn inside the row, in the order they are drawn. |
| 159 | /// |
| 160 | /// And only ever populated for a table row. A list row's run |
| 161 | /// contributes stops of its own right after the row, because a |
| 162 | /// terminal draws a list itself and knows where every part of a line |
| 163 | /// ended up. A table is laid out by `makeover_tui::table`, which |
| 164 | /// decides column widths, drops the columns a narrow window has no |
| 165 | /// room for and answers no coordinates back, so nothing here can say |
| 166 | /// where in a row a control landed. The row is therefore one stop and |
| 167 | /// these are what stepping into it reaches: **the row is the stop, and |
| 168 | /// a key cycles what is inside it**, rather than per-cell rects or |
| 169 | /// hand-laid tables. |
| 170 | inside: , |
| 171 | , |
| 172 | /// The way to the rows a list is not showing. |
| 173 | More |
| 174 | /// What asking for more calls. |
| 175 | action: Action, |
| 176 | , |
| 177 | /// The control that adds a slot to a repeating question, or takes one away. |
| 178 | /// |
| 179 | /// A stop rather than a key binding, because the description says what |
| 180 | /// both controls are called and a named control is what a reader can find; |
| 181 | /// a chord would be a fact about this renderer that no description could |
| 182 | /// have written. It calls no route: pressing it is a change to what the |
| 183 | /// reader is holding, which is the third of the three things the member is |
| 184 | /// for. |
| 185 | Repeat |
| 186 | /// The question, as it was described. |
| 187 | /// |
| 188 | /// The whole field rather than its name, because the floor, the ceiling |
| 189 | /// and the values the description offered all decide what a press does, |
| 190 | /// and the runtime holding only a name would have to walk the screen |
| 191 | /// again to find them. |
| 192 | field: , |
| 193 | /// The slot this takes away, or `None` for the control that adds one. |
| 194 | at: , |
| 195 | , |
| 196 | |
| 197 | |
| 198 | /// A question, and everything the runtime needs to hold what is typed into it. |
| 199 | |
| 200 | |
| 201 | /// The name the value is submitted under. |
| 202 | pub name: String, |
| 203 | /// What kind of value it takes. |
| 204 | pub kind: FieldKind, |
| 205 | /// What the description offers back, which is what an untouched buffer |
| 206 | /// starts from. |
| 207 | /// |
| 208 | /// Always `None` for a [`layout::FieldKind::Secret`], and that is the whole |
| 209 | /// of `39057019`: the description refuses to carry one, on purpose, so the |
| 210 | /// runtime's buffer is the only place the typed value has ever lived. |
| 211 | pub value: , |
| 212 | /// The values on offer, for the kinds that offer any. |
| 213 | pub options: , |
| 214 | /// The longest value it will take, in characters. |
| 215 | pub max_length: , |
| 216 | /// What setting it calls, for a control that writes on its own. |
| 217 | pub writes: , |
| 218 | /// What it asks about its own value as it is typed, how long each waits |
| 219 | /// first, and what rides along. [`quasi_router::Field::consults`]. |
| 220 | /// |
| 221 | /// Several where the box raises several questions, and empty for the field |
| 222 | /// that asks nothing, which is nearly all of them. |
| 223 | pub consults: , |
| 224 | /// The question whose answer is this field's own list of candidates. |
| 225 | /// |
| 226 | /// [`quasi_router::Field::suggests`]. Beside [`consults`](Self::consults) |
| 227 | /// rather than in it, because the answer belongs to this control instead of |
| 228 | /// to a region: the runtime holds the candidates on the view, the drawing |
| 229 | /// puts them under the box, and picking one writes the value. |
| 230 | pub suggests: , |
| 231 | |
| 232 | |
| 233 | |
| 234 | /// What Enter does here, when it does anything. |
| 235 | /// |
| 236 | /// A field answers `None`: Enter in a text box is not a submit here, the |
| 237 | /// way it is in a browser, because a terminal has no implicit submit and |
| 238 | /// guessing one would fire a form from the first field the user typed in. |
| 239 | |
| 240 | |
| 241 | match self |
| 242 | SelfAct |
| 243 | | SelfLink |
| 244 | | SelfSubmit |
| 245 | | SelfMore => Some, |
| 246 | SelfRow => activate.as_ref, |
| 247 | // Neither answers with a route: a field is typed into, and a |
| 248 | // repeat control changes how many boxes there are. |
| 249 | SelfField | SelfRepeat => None, |
| 250 | |
| 251 | |
| 252 | |
| 253 | /// The question this stands on, when it is one. |
| 254 | |
| 255 | pub const |
| 256 | match self |
| 257 | SelfField => Some, |
| 258 | _ => None, |
| 259 | |
| 260 | |
| 261 | |
| 262 | |
| 263 | /// A reachable thing, and the region it is in. |
| 264 | /// |
| 265 | /// The region is here because scrolling needs it. A key that scrolls has to |
| 266 | /// scroll something, and the only non-arbitrary answer is the region the user is |
| 267 | /// working in, which is the region their focus is in. Carrying it on the walk |
| 268 | /// that already visits every reachable thing is cheaper than a second walk that |
| 269 | /// would be free to disagree with this one. |
| 270 | |
| 271 | |
| 272 | /// The [`Slot::id`] of the region holding it. |
| 273 | pub region: String, |
| 274 | /// What it is. |
| 275 | pub spot: Spot, |
| 276 | |
| 277 | |
| 278 | /// Everything reachable on `screen`, in draw order, with its region. |
| 279 | /// |
| 280 | /// `hidden` names the regions that do not apply right now, from |
| 281 | /// [`crate::reveal::hidden`], and they are walked past: a control the drawing |
| 282 | /// left out is not a place the caret can go. An empty slice walks everything |
| 283 | /// the screen describes, which is what a caller reading the description rather |
| 284 | /// than the picture wants -- pruning what was typed, seeding what was ticked, |
| 285 | /// and gathering what a submit sends. |
| 286 | |
| 287 | |
| 288 | // A frame that rests nothing, so every notice floats and every notice is |
| 289 | // drawn at the top. [`reaches_framed`]'s own docs already say this is that |
| 290 | // function with a frame that offers nothing; sharing the walk is what makes |
| 291 | // the sentence true rather than nearly true. |
| 292 | reaches_framed |
| 293 | |
| 294 | |
| 295 | /// Everything reachable on `screen`, in draw order. See [`reaches`] for |
| 296 | /// `hidden`. |
| 297 | |
| 298 | |
| 299 | reaches |
| 300 | .into_iter |
| 301 | .map |
| 302 | .collect |
| 303 | |
| 304 | |
| 305 | /// The region a frame's verbs are reported under. |
| 306 | /// |
| 307 | /// A frame is not a [`Slot`] and has no id of its own, so [`Reach::region`] |
| 308 | /// needs a name for it. Reserved rather than derived: a screen carrying a |
| 309 | /// region with this id would collide, and a name nothing in the tree uses is |
| 310 | /// cheaper than a rule about which ids a screen may not have. |
| 311 | pub const FRAME_REGION: &str = "quasi-frame"; |
| 312 | |
| 313 | /// The region a notice's own control is reported under. |
| 314 | /// |
| 315 | /// Reserved for [`FRAME_REGION`]'s reason. A notice belongs to the screen |
| 316 | /// rather than to a place in it -- that is why every renderer draws them |
| 317 | /// outside the regions -- so there is no [`Slot::id`] to report one under. |
| 318 | /// |
| 319 | /// Nothing aims a fragment here. A notice arrives with the answer that raised |
| 320 | /// it, so there is no address for one to be replaced at. |
| 321 | pub const NOTICE_REGION: &str = "quasi-notice"; |
| 322 | |
| 323 | /// The region the band's own contents are reported under. |
| 324 | /// |
| 325 | /// Reserved for [`FRAME_REGION`]'s reason. The band is chrome and has no |
| 326 | /// address: it is built once beside the router, and nothing answers into it. |
| 327 | pub const BAND_REGION: &str = "quasi-band"; |
| 328 | |
| 329 | /// The region a nav place's reach is reported under. |
| 330 | /// |
| 331 | /// A reserved name for the same reason [`FRAME_REGION`] is one: the nav has no |
| 332 | /// address, because nothing answers into it. It is built once and never |
| 333 | /// replaced, so there is nothing for a fragment to aim at. |
| 334 | pub const NAV_REGION: &str = "quasi-nav"; |
| 335 | |
| 336 | /// Everything reachable on `screen`, then the verbs `frame` offers. |
| 337 | /// |
| 338 | /// The frame's verbs come last because they are drawn last: the order here is |
| 339 | /// the order the drawing counts in, and the two disagreeing lights the wrong |
| 340 | /// control. |
| 341 | /// |
| 342 | /// [`reaches`] is this with the frame that offers nothing, so a host that |
| 343 | |
| 344 | |
| 345 | let mut found = Vecnew; |
| 346 | |
| 347 | // The floating notices first, because they are drawn first: above the |
| 348 | // regions and below the tab line. `bde35298` is what put anything reachable |
| 349 | // in one -- a notice was a sentence until `Node::Notice` grew an act, so |
| 350 | // this walk had nothing to find here and did not look. |
| 351 | for notice in screen.notices.iter.filter |
| 352 | node_spots; |
| 353 | |
| 354 | |
| 355 | for slot in cratereachable |
| 356 | slot_spots; |
| 357 | |
| 358 | |
| 359 | // Then the footer, in the order `crate::frame::draw` writes it: the notices |
| 360 | // the mount rests, then its verbs. |
| 361 | for notice in screen.notices.iter.filter |
| 362 | node_spots; |
| 363 | |
| 364 | for verb in &frame.verbs |
| 365 | push_act; |
| 366 | |
| 367 | found |
| 368 | |
| 369 | |
| 370 | /// Everything reachable on `screen`, the verbs `frame` offers, then the panel |
| 371 | /// the app keeps on screen. |
| 372 | /// |
| 373 | /// The panel comes last because it is drawn last, which is the rule |
| 374 | /// [`reaches_framed`] already states: the order here is the order the drawing |
| 375 | /// counts in. |
| 376 | /// |
| 377 | /// Its reaches are reported under the panel's own id rather than a reserved |
| 378 | /// name. That is the difference from a frame: a frame has no address and needs |
| 379 | /// [`FRAME_REGION`], and a panel has one because an answer aims at it. |
| 380 | /// |
| 381 | /// [`reaches_framed`] is this with the chrome that keeps nothing on screen, so |
| 382 | |
| 383 | |
| 384 | screen: &Screen, |
| 385 | frame: &Frame, |
| 386 | chrome: &Chrome, |
| 387 | local: &, |
| 388 | |
| 389 | let mut found = Vecnew; |
| 390 | |
| 391 | // The tab line first, because it is drawn first. A terminal user reaches |
| 392 | // the places by the same walk that reaches everything else; without this |
| 393 | // the nav would be a row they can read and cannot use, which is the failure |
| 394 | // `Screen::selection` was added to end one level down. |
| 395 | // |
| 396 | // Only the row that is drawn: the places, and the sub-places of the one the |
| 397 | // screen is in. Counting a sub-place of a tab the user is not on would put |
| 398 | // a stop in the walk with nothing on screen at it. |
| 399 | // The brand before the places, because it is drawn at the head of that same |
| 400 | // row. It goes somewhere -- home, for every app that has ever had one -- so |
| 401 | // a walk that skipped it would draw a link the reader cannot follow. |
| 402 | if let Some = chrome.band.as_ref.and_then |
| 403 | found.push |
| 404 | region: BAND_REGION.to_string, |
| 405 | spot: Act |
| 406 | action: brand.action.clone, |
| 407 | confirm: None, |
| 408 | key: None, |
| 409 | over: None, |
| 410 | asks: Vecnew, |
| 411 | fills: None, |
| 412 | copies: None, |
| 413 | , |
| 414 | ; |
| 415 | |
| 416 | for place in &chrome.nav |
| 417 | push_place; |
| 418 | |
| 419 | if let Some = chrome |
| 420 | .nav |
| 421 | .iter |
| 422 | .find |
| 423 | |
| 424 | for inner in &open.within |
| 425 | push_place; |
| 426 | |
| 427 | |
| 428 | |
| 429 | // Then the band's own box, because it is drawn on the row under the places. |
| 430 | // A search field in the header is a field like any other -- that is what |
| 431 | // `Band::search` being a `Field` buys -- so it is reached by the same walk |
| 432 | // and edited by the same buffer, rather than being a row the reader can see |
| 433 | // and not use. |
| 434 | if let Some = chrome.band.as_ref.and_then |
| 435 | node_spots |
| 436 | &Field, |
| 437 | BAND_REGION, |
| 438 | local, |
| 439 | &mut found, |
| 440 | ; |
| 441 | |
| 442 | |
| 443 | found.extend; |
| 444 | for panel in &chrome.panels |
| 445 | node_spots; |
| 446 | |
| 447 | found |
| 448 | |
| 449 | |
| 450 | /// One place, as somewhere the walk can stop. |
| 451 | |
| 452 | found.push |
| 453 | region: NAV_REGION.to_string, |
| 454 | spot: Act |
| 455 | action: place.action.clone, |
| 456 | confirm: None, |
| 457 | key: None, |
| 458 | over: None, |
| 459 | asks: Vecnew, |
| 460 | fills: None, |
| 461 | copies: None, |
| 462 | , |
| 463 | ; |
| 464 | |
| 465 | |
| 466 | /// A region's reachable things. |
| 467 | /// |
| 468 | /// A region that is still loading has none. It is drawn as the word "Loading" |
| 469 | /// and nothing under it is on screen, so anything counted here would be a |
| 470 | /// focusable the user cannot see. |
| 471 | |
| 472 | if matches! |
| 473 | return; |
| 474 | |
| 475 | // A region that does not apply right now is not drawn, so nothing in it is |
| 476 | // a place the caret can go. `079a011e`, and the same list the drawing |
| 477 | // reads: the two walks agreeing about which regions are on the screen is |
| 478 | // what keeps the highlighted control the one the reader is on. |
| 479 | if local.out |
| 480 | return; |
| 481 | |
| 482 | // Every member, at every width. The walk has no width and deliberately |
| 483 | // keeps none: its other two callers prune what the user typed and seed |
| 484 | // what they ticked, and both of those would be wrong to forget a field |
| 485 | // because the window is narrow at the moment they run. |
| 486 | // |
| 487 | // The cost is that a member ranked below `Priority::Essential` can still |
| 488 | // take focus in a window narrow enough to have dropped it, so Tab reaches |
| 489 | // something that is not on the screen. Recorded rather than papered over: |
| 490 | // fixing it means the navigation walk knowing the width the drawing knows, |
| 491 | // which is a wider change than the member that revealed it. |
| 492 | // The row the region says its members share, then its body, which is the |
| 493 | // order they are drawn in. The caret and the drawing agreeing about that |
| 494 | // order is the invariant this walk exists to keep. |
| 495 | for placed in slot.run.iter.flat_map |
| 496 | node_spots; |
| 497 | |
| 498 | |
| 499 | // A region whose children are answers to one question reaches two controls |
| 500 | // the body does not hold: each slot's own remove, and the add under the |
| 501 | // lot. `f7abbc08`. In the order `region::repeating_body` draws them, which |
| 502 | // is the invariant the walk-matches-drawing test checks. |
| 503 | if let Some = slot.repeating.as_deref |
| 504 | let standing = slot.body.len; |
| 505 | for placed in slot.body.iter |
| 506 | node_spots; |
| 507 | if let Some = crateremoves_of |
| 508 | let act = cratebounded; |
| 509 | push_act; |
| 510 | |
| 511 | |
| 512 | let add = cratebounded; |
| 513 | push_act; |
| 514 | return; |
| 515 | |
| 516 | |
| 517 | for placed in slot.body.iter |
| 518 | node_spots; |
| 519 | |
| 520 | |
| 521 | |
| 522 | /// One node's reachable things, in the order it draws them. |
| 523 | pub |
| 524 | // Everything below reads better saying what it found rather than how it is |
| 525 | // recorded, and the region is the same for every one of them. |
| 526 | |
| 527 | => |
| 528 | found.push |
| 529 | region: region.to_string, |
| 530 | spot: $spot, |
| 531 | |
| 532 | }; |
| 533 | |
| 534 | |
| 535 | match node |
| 536 | Act => push_act, |
| 537 | |
| 538 | Link => push! |
| 539 | action: action.clone, |
| 540 | , |
| 541 | |
| 542 | // A chip carries a route and is drawn as a bracketed label with no |
| 543 | // second target in it, which `node.rs` already declined: the `x` a |
| 544 | // webview hangs on a chip is a control inside a span. Reaching the chip |
| 545 | // is reaching its action, which is the part a terminal can honour. |
| 546 | Token => |
| 547 | if let Chip = tag.kind |
| 548 | && let Some = tag.action.clone |
| 549 | |
| 550 | push! |
| 551 | action, |
| 552 | confirm: None, |
| 553 | key: None, |
| 554 | over: None, |
| 555 | asks: Vecnew, |
| 556 | fills: None, |
| 557 | copies: None, |
| 558 | ; |
| 559 | |
| 560 | |
| 561 | |
| 562 | // A stand-in's way out, when it has one. Spelt as two arms rather than |
| 563 | // one holding an `if let`, so that the empty case is named here and not |
| 564 | // swept into the catch-all below with the members this renderer has |
| 565 | // never heard of. |
| 566 | StandIn |
| 567 | act: Some, .. | Notice => |
| 568 | push_act; |
| 569 | |
| 570 | StandIn => |
| 571 | |
| 572 | Field => push_field, |
| 573 | |
| 574 | Form |
| 575 | action, fields, .. |
| 576 | => |
| 577 | for field in fields |
| 578 | push_field; |
| 579 | |
| 580 | push! |
| 581 | action: action.clone, |
| 582 | // Every name the form sends, which for a question answered N |
| 583 | // times is N of them. `60d1753c`: one submit carries every |
| 584 | // instance, so the names are asked of the same count the boxes |
| 585 | // were drawn from. |
| 586 | names: fields |
| 587 | .iter |
| 588 | .flat_map |
| 589 | .collect, |
| 590 | ; |
| 591 | |
| 592 | |
| 593 | // Reachable exactly as a list's rows are. A placement changes where a |
| 594 | // row is drawn, not whether it can be reached, and the terminal draws |
| 595 | // these in the order given -- so tab order and reading order agree |
| 596 | // without this having to know anything about the axis. |
| 597 | Timeline => |
| 598 | for entry in entries |
| 599 | push_row; |
| 600 | |
| 601 | |
| 602 | |
| 603 | // A table's rows are reachable and its cells are not, so a control in a |
| 604 | // cell is reached by stepping into the row rather than by tabbing to |
| 605 | // it. See [`Spot::Row`]'s `inside` for why the layout leaves no other |
| 606 | // way, and [`inside`] for the walk. |
| 607 | Table |
| 608 | columns, |
| 609 | rows, |
| 610 | more, |
| 611 | .. |
| 612 | => |
| 613 | // A row a shut branch covers is not on the screen, so it is not |
| 614 | // reachable and takes no stop. The drawing makes the same reading |
| 615 | // from the same function, which is what keeps the caret on the row |
| 616 | // it is painted on. |
| 617 | for in crateshowing |
| 618 | // **How a row is entered is the one thing the two arrangements |
| 619 | // disagree about.** A list row's controls are stops of their |
| 620 | // own, walked straight after the row; a table row's cells are |
| 621 | // reached by stepping *into* the row, because the grid leaves |
| 622 | // no other way. One node since the 2026-09-06 collapse, and |
| 623 | // this is where it forks. |
| 624 | if columns.is_empty |
| 625 | push_row; |
| 626 | continue; |
| 627 | |
| 628 | // A tickable row is reachable even when nothing opens it: the |
| 629 | // tick is the affordance, and a row that draws a box a reader |
| 630 | // cannot reach is the dead affordance `5f2b8753` was filed for, |
| 631 | // one node over. A row that only offers a menu is reachable for |
| 632 | // the same reason -- the menu is the affordance, and in a |
| 633 | // terminal it is reached from the row and nowhere else, so a row |
| 634 | // the caret cannot land on holds acts nothing can get at. A row |
| 635 | // that only carries controls in its cells is the third case and |
| 636 | // is `27f2331e`: nothing opens it and nothing ticks it, and |
| 637 | // until the caret could land on it the Remove button in its |
| 638 | // last cell was drawn and unreachable. |
| 639 | // `toggle` stays `None` because a table row's tick is always a |
| 640 | // member of a set -- `Row::ticking` is the only constructor for |
| 641 | // it. |
| 642 | if row_reachable |
| 643 | push! |
| 644 | activate: cells.activate.clone, |
| 645 | toggle: None, |
| 646 | ticked: cells.selected, |
| 647 | chosen: cells.chosen, |
| 648 | value: cells.value.clone, |
| 649 | menu: cells.menu.clone, |
| 650 | branch: branch_key, |
| 651 | open: open_now, |
| 652 | inside: inside |
| 653 | .into_iter |
| 654 | .filter_map |
| 655 | inside_spot |
| 656 | |
| 657 | .collect, |
| 658 | ; |
| 659 | |
| 660 | |
| 661 | |
| 662 | // Prev, then the pages, then Next -- `node::rest_pieces`' own |
| 663 | // order, which is the order the line is drawn in and the order the |
| 664 | // webview prints them. Read off that function rather than written |
| 665 | // out again, because the drawing claims one position per reachable |
| 666 | // piece and a second list here is a second answer to how many there |
| 667 | // are. |
| 668 | // |
| 669 | // This was the list arm's alone until the collapse, and `draw_table` |
| 670 | // has drawn a table's pager the whole time. So a paged table's Prev |
| 671 | // and Next were on the screen and the caret could not land on |
| 672 | // either. Same class as `27f2331e` one node over: drawn by one pass |
| 673 | // and unknown to the other. |
| 674 | if let Some = more |
| 675 | for piece in craterest_pieces |
| 676 | if let Some = piece.action |
| 677 | push! |
| 678 | action: action.clone, |
| 679 | ; |
| 680 | |
| 681 | |
| 682 | |
| 683 | |
| 684 | |
| 685 | Region => slot_spots, |
| 686 | |
| 687 | // Readouts and prose. Nothing to call, so nothing to stop on. |
| 688 | Heading |
| 689 | | Text |
| 690 | | Rich |
| 691 | | Figure |
| 692 | // A picture carries no address of its own -- `src` is where the bytes |
| 693 | // are, not somewhere the reader goes -- so there is nothing to stop on. |
| 694 | // A picture that is meant to be clicked is one inside a `Link`. |
| 695 | | Image |
| 696 | | Notice |
| 697 | | Meter |
| 698 | | Stats => |
| 699 | |
| 700 | // A member added since this renderer last learned the vocabulary. |
| 701 | // `Node` is `#[non_exhaustive]`, so this arm is what lets that land |
| 702 | // without a lockstep release here. |
| 703 | // |
| 704 | // Contributing no reach is the safe read, and it is the only honest |
| 705 | // one: a `Reach` says where a key press goes, and this cannot know |
| 706 | // where. Guessing a `Spot` would send a press to the wrong action, |
| 707 | // whereas an unreached node is merely something the reader tabs past. |
| 708 | // `node.rs` draws `UNDRAWN` in the same case, so the reader sees that |
| 709 | // something is there and sees that it does not respond, rather than |
| 710 | // being handed a stop that goes nowhere. |
| 711 | _ => |
| 712 | |
| 713 | |
| 714 | |
| 715 | /// Whether the caret can land on this table row at all. |
| 716 | /// |
| 717 | /// One function because two would drift: [`node_spots`] decides what the caret |
| 718 | /// can land on and `node.rs` decides what the drawing counts, and a row counted |
| 719 | /// in one and not the other shifts every stop below the table by one. |
| 720 | /// Whether a row offers anything of its own. |
| 721 | /// |
| 722 | /// The row's members only: what it holds in its cells is a separate question, |
| 723 | /// and the two containers answer it differently on purpose. A table row steps |
| 724 | /// into its cells ([`inside`]); a list row's controls are stops of their own, |
| 725 | /// walked straight after it. |
| 726 | /// |
| 727 | /// One predicate since the 2026-09-05 collapse, and it closes a gap. This was |
| 728 | /// written twice, and the copy here did not test `toggle` because a table row |
| 729 | /// could not carry one. Now that a row is a row, a table row that is a |
| 730 | /// checklist item can, and without this it would have been unreachable in a |
| 731 | /// terminal: nothing else about such a row is set, so no other clause fires. |
| 732 | pub |
| 733 | row.open.is_some |
| 734 | || row.activate.is_some |
| 735 | || row.toggle.is_some |
| 736 | || row.selected.is_some |
| 737 | || row.chosen.is_some |
| 738 | || !row.menu.is_empty |
| 739 | |
| 740 | |
| 741 | pub |
| 742 | row_offers || !inside.is_empty |
| 743 | |
| 744 | |
| 745 | /// The parts of a table row the caret can step into, as `(column, part)` |
| 746 | /// positions into [`Row::cells`] and [`Cell::content`]. |
| 747 | /// |
| 748 | /// Positions rather than [`Spot`]s, for the same reason [`row_reachable`] is one |
| 749 | /// function: the drawing needs to know *which* part to paint lit and the reach |
| 750 | /// walk needs to know what pressing it calls, and the two have to be the same |
| 751 | /// list read twice or the ring lands on the wrong control. |
| 752 | /// |
| 753 | /// [`Cell::parts`]: quasi_router::Cell |
| 754 | pub |
| 755 | let mut found = Vecnew; |
| 756 | for in cells.cells.iter.enumerate |
| 757 | for in cell.content.iter.enumerate |
| 758 | if inside_spot.is_some |
| 759 | found.push; |
| 760 | |
| 761 | |
| 762 | |
| 763 | found |
| 764 | |
| 765 | |
| 766 | /// What stepping onto one part of a cell reaches, when it reaches anything. |
| 767 | /// |
| 768 | /// The three inline members that carry an address, matching what [`node_spots`] |
| 769 | /// makes reachable in a list row's run: a control, text that goes somewhere, and |
| 770 | /// a chip that is a route rather than a label. A disabled control is drawn and |
| 771 | /// passed over here exactly as it is everywhere else. |
| 772 | /// |
| 773 | /// # An act that asks for a value first is not reachable here |
| 774 | /// |
| 775 | /// [`Act::asks`] puts boxes beside the control, and a cell is one line inside a |
| 776 | /// column of a width this walk does not know, so the boxes are not drawn. A stop |
| 777 | /// on the control would fire it with nothing gathered under the names it asked |
| 778 | /// for, which is worse than not stopping: the row still opens, and the reader |
| 779 | /// can see that the control did not answer. No described screen puts one in a |
| 780 | /// cell today -- the measured consumers are bulk bars -- and the day one does, |
| 781 | /// what it wants is the cell's own line rather than a stop bound to a silent |
| 782 | /// send. |
| 783 | |
| 784 | match node |
| 785 | Act |
| 786 | if !act.state.is_some_and |
| 787 | && act.asks.is_empty => |
| 788 | |
| 789 | Some |
| 790 | action: act.action.clone, |
| 791 | confirm: act.confirm.clone, |
| 792 | key: act.key.clone, |
| 793 | over: act.over.clone, |
| 794 | asks: Vecnew, |
| 795 | fills: act.fills.clone, |
| 796 | copies: act.copies.clone, |
| 797 | |
| 798 | |
| 799 | Link => Some |
| 800 | action: action.clone, |
| 801 | , |
| 802 | Token => match |
| 803 | => Some |
| 804 | action: action.clone, |
| 805 | confirm: None, |
| 806 | key: None, |
| 807 | over: None, |
| 808 | asks: Vecnew, |
| 809 | fills: None, |
| 810 | copies: None, |
| 811 | , |
| 812 | _ => None, |
| 813 | , |
| 814 | _ => None, |
| 815 | |
| 816 | |
| 817 | |
| 818 | /// A control, unless it is disabled, and the questions it asks before it fires. |
| 819 | /// |
| 820 | /// The questions come first, in the order they are drawn: they sit above the |
| 821 | /// control in `node.rs`, and a reader tabbing forward should reach the box |
| 822 | /// before the button that sends what is in it. |
| 823 | |
| 824 | if act.state.is_some_and |
| 825 | return; |
| 826 | |
| 827 | for field in &act.asks |
| 828 | push_field; |
| 829 | |
| 830 | found.push |
| 831 | region: region.to_string, |
| 832 | spot: Act |
| 833 | action: act.action.clone, |
| 834 | confirm: act.confirm.clone, |
| 835 | key: act.key.clone, |
| 836 | over: act.over.clone, |
| 837 | // Every name the press sends, which for a question answered N |
| 838 | // times is N of them, exactly as a form's submit gathers them. |
| 839 | asks: act |
| 840 | .asks |
| 841 | .iter |
| 842 | .flat_map |
| 843 | .collect, |
| 844 | fills: act.fills.clone, |
| 845 | copies: act.copies.clone, |
| 846 | , |
| 847 | ; |
| 848 | |
| 849 | |
| 850 | /// A question, unless it is hidden. |
| 851 | /// |
| 852 | /// A hidden field draws nothing and is submitted with the form, so stopping on |
| 853 | /// it would be a stop on a blank row. |
| 854 | /// |
| 855 | /// A question answered N times is N stops and the controls that change N, in |
| 856 | /// the order the drawing paints them: `60d1753c`. Both walks read the same |
| 857 | /// count off the same [`Local`], which is what keeps the caret and the picture |
| 858 | /// agreeing about how many stops a repeating question has. |
| 859 | |
| 860 | // A question that does not apply is not on the screen, so the caret does |
| 861 | // not stop on it. `8fdb814c`, and it is the region rule one level down: the |
| 862 | // walk that reads a submission passes `Local::none`, so what was typed into |
| 863 | // it is kept and still sent. |
| 864 | if local.field_out |
| 865 | return; |
| 866 | |
| 867 | let Some = &field.repeats else |
| 868 | push_one; |
| 869 | return; |
| 870 | ; |
| 871 | let standing = local.standing; |
| 872 | for at in 0..standing |
| 873 | for slot in field.instance_fields |
| 874 | push_one; |
| 875 | |
| 876 | if repeat.fewer |
| 877 | found.push |
| 878 | region: region.to_string, |
| 879 | spot: Repeat |
| 880 | field: Boxnew, |
| 881 | at: Some, |
| 882 | , |
| 883 | ; |
| 884 | |
| 885 | |
| 886 | if repeat.more |
| 887 | found.push |
| 888 | region: region.to_string, |
| 889 | spot: Repeat |
| 890 | field: Boxnew, |
| 891 | at: None, |
| 892 | , |
| 893 | ; |
| 894 | |
| 895 | |
| 896 | |
| 897 | /// The names one field submits under: its own, or one per slot. |
| 898 | /// |
| 899 | /// The count is the reader's, so this is not a function of the description |
| 900 | /// alone, which is the whole reason a submit's name list is built during the |
| 901 | /// walk rather than off the form. |
| 902 | |
| 903 | if field.repeats.is_none |
| 904 | return vec!; |
| 905 | |
| 906 | |
| 907 | .flat_map |
| 908 | field |
| 909 | .instance_fields |
| 910 | .into_iter |
| 911 | .map |
| 912 | . |
| 913 | |
| 914 | .collect |
| 915 | |
| 916 | |
| 917 | /// One box, whether it stands alone or is one slot of a repeating question. |
| 918 | |
| 919 | if matches! |
| 920 | return; |
| 921 | |
| 922 | found.push |
| 923 | region: region.to_string, |
| 924 | spot: Field |
| 925 | name: field.name.clone, |
| 926 | kind: field.kind, |
| 927 | value: field.value.clone, |
| 928 | options: field |
| 929 | .options |
| 930 | .iter |
| 931 | .map |
| 932 | .collect, |
| 933 | max_length: field.max_length, |
| 934 | writes: field.writes.clone, |
| 935 | consults: field.consults.clone, |
| 936 | suggests: field.suggests.clone, |
| 937 | , |
| 938 | ; |
| 939 | |
| 940 | |
| 941 | /// What a fold on this row is remembered by, if it is a branch at all. |
| 942 | |
| 943 | row.open.is_some.then |
| 944 | |
| 945 | |
| 946 | /// Whether this row's branch is open, as the reader has left it. |
| 947 | /// |
| 948 | /// `false` on a leaf, which nothing reads: the runtime looks at `branch` first. |
| 949 | |
| 950 | match |
| 951 | => view.open, |
| 952 | => described.unwrap_or, |
| 953 | |
| 954 | |
| 955 | |
| 956 | /// A row: the row itself when the description gives it something to do, then |
| 957 | /// whatever its run carries. |
| 958 | /// |
| 959 | /// Two stops and not one, because they are two things. A row that opens a |
| 960 | /// detail pane and also shows a Remove button offers both, and a terminal that |
| 961 | /// collapsed them would make the button unreachable or the row unopenable. A |
| 962 | /// row that only shows things is passed over entirely, which is the difference |
| 963 | /// between a list and a menu. |
| 964 | /// |
| 965 | /// The row comes first because it is the whole line and the controls sit on it. |
| 966 | |
| 967 | if row_offers |
| 968 | found.push |
| 969 | region: region.to_string, |
| 970 | spot: Row |
| 971 | activate: row.activate.clone, |
| 972 | toggle: row.toggle.clone, |
| 973 | ticked: row.selected, |
| 974 | chosen: row.chosen, |
| 975 | value: row.value.clone, |
| 976 | menu: row.menu.clone, |
| 977 | branch: branch_key, |
| 978 | open: open_now, |
| 979 | // Empty, always. The run below is walked straight after this, |
| 980 | // so a list row's controls are stops of their own and there is |
| 981 | // nothing to step into. |
| 982 | inside: Vecnew, |
| 983 | , |
| 984 | ; |
| 985 | |
| 986 | for cell in &row.cells |
| 987 | for node in &cell.content |
| 988 | node_spots; |
| 989 | |
| 990 | |
| 991 | |
| 992 |