max / quasi
| 1 | //! The half a webview host never writes. |
| 2 | //! |
| 3 | //! `quasi-axum` and `quasi-tauri` both answer a request with markup and stop. |
| 4 | //! Everything between one request and the next — which control is under the |
| 5 | //! caret, what the user has typed into it, what a key means, where the back |
| 6 | //! button goes — is the browser's, and neither adapter contains a line of it. |
| 7 | //! A terminal has no browser under it, so this is that half, written out. |
| 8 | //! |
| 9 | //! # It does not own the router |
| 10 | //! |
| 11 | //! [`Runtime`] turns keys into [`Request`]s and applies [`Response`]s, and it |
| 12 | //! never calls a handler. The host holds the router and the state and does the |
| 13 | //! calling, which keeps this free of the state type and makes every binding |
| 14 | //! below testable without standing up an app. |
| 15 | //! |
| 16 | //! ```text |
| 17 | //! key ──► Runtime::key ──► Step::Call(request) |
| 18 | //! │ |
| 19 | //! host: router.handle(&state, request) |
| 20 | //! │ |
| 21 | //! Runtime::apply ◄── Response |
| 22 | //! ``` |
| 23 | //! |
| 24 | //! # The bindings are this renderer's, and the description reaches two of them |
| 25 | //! |
| 26 | //! Nothing in a description says what Tab does, so the table below is policy. |
| 27 | //! The two exceptions are the two the vocabulary already carries: [`Act::key`] |
| 28 | //! names the key that reaches a control, and [`Act::confirm`] names the question |
| 29 | //! to ask before doing it. Both were drawn and declined by the drawing half, |
| 30 | //! and this is where they are honoured. |
| 31 | //! |
| 32 | //! | Key | What it does | |
| 33 | //! |---|---| |
| 34 | //! | Tab, Down | the next reachable thing | |
| 35 | //! | `BackTab`, Up | the previous one | |
| 36 | //! | Enter | call what is under the caret | |
| 37 | //! | Space | tick the row under the caret | |
| 38 | //! | `PageUp`, `PageDown` | scroll the region the caret is in | |
| 39 | //! | Backspace | take a character back out of a field | |
| 40 | //! | printable | type into a field, or reach the control that named the key | |
| 41 | //! | Escape | step out of a table row, dismiss the overlay, or go back | |
| 42 | //! | Left, Right | step into the table row under the caret and cycle its controls | |
| 43 | //! |
| 44 | //! # Right steps into a table row |
| 45 | //! |
| 46 | //! A table row is one stop with the controls in its cells inside it rather |
| 47 | //! than beside it, which [`crate::focus`]'s header explains; this is the key |
| 48 | //! that gets at them. Right steps in and cycles forward, Left cycles back, |
| 49 | //! Escape steps out, and Enter presses whatever the caret has stepped onto |
| 50 | //! instead of opening the row. |
| 51 | //! |
| 52 | //! Right and Left rather than Enter, because Enter is taken: `Row::activate` |
| 53 | //! already means "this row opens", and every measured table with a control in a |
| 54 | //! cell also opens its rows. They are the keys a grid is walked with everywhere |
| 55 | //! else, and the reader is already holding them for the same shape. |
| 56 | //! |
| 57 | //! They mean this before they mean the other thing they mean. A region showing |
| 58 | //! one child at a time is moved with the same two keys, and the caret standing |
| 59 | //! on a row with controls in it wins, because the thing under the caret beats |
| 60 | //! the screen-wide search that arm falls back to. A screen with a table inside a |
| 61 | //! carousel therefore steps between frames from any stop but that one, which is |
| 62 | //! the cost, and it is the smaller of the two: the frames are reachable from |
| 63 | //! anywhere else on the screen and the controls in the row are reachable from |
| 64 | //! nowhere else. |
| 65 | |
| 66 | use makeover_layout as layout; |
| 67 | use |
| 68 | Accepted, Action, Choosing, Chrome, Consult, Frame, Locating, Message, Method, Node, Outcome, |
| 69 | Params, Request, Response, Screen, Slot, safe_file_name, |
| 70 | ; |
| 71 | use Buffer; |
| 72 | use Rect; |
| 73 | |
| 74 | use crate; |
| 75 | use crate::; |
| 76 | |
| 77 | /// A key, named the way this crate wants to talk about one. |
| 78 | /// |
| 79 | /// Not crossterm's, deliberately. A host maps its own events onto this in a |
| 80 | /// dozen lines, and in exchange the bindings below are testable without a |
| 81 | /// terminal and this crate does not make every consumer take a backend it might |
| 82 | /// not be using. |
| 83 | |
| 84 | |
| 85 | /// A character the user typed. |
| 86 | Char, |
| 87 | /// Confirm, follow, submit. |
| 88 | Enter, |
| 89 | /// Forward through the reachable things. |
| 90 | Tab, |
| 91 | /// Backward through them. |
| 92 | BackTab, |
| 93 | /// Take a character back. |
| 94 | Backspace, |
| 95 | /// Out, back, never mind. |
| 96 | Escape, |
| 97 | /// Up one reachable thing. |
| 98 | Up, |
| 99 | /// Down one reachable thing. |
| 100 | Down, |
| 101 | /// A screen's worth backwards. |
| 102 | PageUp, |
| 103 | /// A screen's worth forwards. |
| 104 | PageDown, |
| 105 | /// Back one child, in a region showing one at a time. |
| 106 | Left, |
| 107 | /// On one child, in a region showing one at a time. |
| 108 | Right, |
| 109 | |
| 110 | |
| 111 | /// How a press was meant, as the payload it travels in. |
| 112 | /// |
| 113 | /// A terminal has no modifiers on this crate's `Key`, so the two readings it |
| 114 | /// can offer are the two keys it binds: Enter is the plain one and Space is |
| 115 | /// "and also this one". `Choosing::Through` -- a run from the app's pointer to |
| 116 | /// here -- has no key yet and is not invented one: shift-Enter is not |
| 117 | /// expressible while `Key` carries no modifiers, and widening `Key` is a |
| 118 | /// change to every host that drives this renderer. |
| 119 | |
| 120 | new.with |
| 121 | |
| 122 | |
| 123 | /// What the host should do about a key. |
| 124 | |
| 125 | |
| 126 | /// Nothing left to do but redraw. |
| 127 | Idle, |
| 128 | /// Ask the router this, then hand the answer to [`Runtime::apply`]. |
| 129 | Call, |
| 130 | /// Ask this question. The next key answers it: `y` or Enter does the thing, |
| 131 | /// anything else does not. |
| 132 | Ask, |
| 133 | /// Somewhere outside the app. The host opens it, and nothing comes back. |
| 134 | Open, |
| 135 | /// This value goes on the clipboard. The host puts it there, and nothing |
| 136 | /// comes back. |
| 137 | /// |
| 138 | /// [`Act::copies`] says a press copies something, and this is the |
| 139 | /// terminal's half. [`Open`](Self::Open)'s shape and for its reason: this |
| 140 | /// crate turns keys into requests and owns no I/O, so writing OSC 52 or |
| 141 | /// reaching a system clipboard is the host's, exactly as opening a URL is. |
| 142 | /// |
| 143 | /// A copying act's action is local -- [`Act::copying`] makes it so -- and |
| 144 | /// this is what the press produces, so nothing is asked and nothing is |
| 145 | /// lost. An act that copies *and* names a route is not describable through |
| 146 | /// this runtime in one step: the copy is what comes back and the call does |
| 147 | /// not happen. No measured site is one. |
| 148 | /// |
| 149 | /// [`Act::copies`]: quasi_router::Act::copies |
| 150 | /// [`Act::copying`]: quasi_router::Act::copying |
| 151 | Copy, |
| 152 | /// Ask the router these, each not until its own `after` has passed with no |
| 153 | /// further key. A later one of these replaces an outstanding one whole |
| 154 | /// rather than queueing behind it. |
| 155 | /// |
| 156 | /// [`Field::consults`], and the waits are the description's own. Until it |
| 157 | /// existed this runtime had no way to say "later" and the comment on |
| 158 | /// [`Runtime::after_typing`] said why it would not invent one: a delay |
| 159 | /// picked here is a delay the webview renderer disagrees with, and the two |
| 160 | /// hosts then debounce the same field differently. |
| 161 | /// |
| 162 | /// The clock stays with the host, exactly as the router does. This crate |
| 163 | /// turns keys into requests and owns no I/O, so "wait 500ms, and drop this |
| 164 | /// if another key arrives" is the host's timer to run — the same division |
| 165 | /// that keeps [`Call`](Self::Call) from calling anything. |
| 166 | /// |
| 167 | /// # Several, and still no scheduler here |
| 168 | /// |
| 169 | /// `N8`. One box can raise more than one question: MNW's discover search |
| 170 | /// asks a suggestion route after 200ms and a results route after 150. The |
| 171 | /// runtime said it did not want a queue and still does not have one — this |
| 172 | /// is one keystroke producing one `Step`, and what varies is how many |
| 173 | /// questions that keystroke posed. The host runs a timer per entry, which |
| 174 | /// is the timer it was already running, and replacement stays wholesale: |
| 175 | /// every question here is about the same value, so a newer keystroke |
| 176 | /// invalidates all of them together. |
| 177 | /// |
| 178 | /// [`Field::consults`]: quasi_router::Field::consults |
| 179 | CallAfter |
| 180 | /// The questions this keystroke posed, in the order the description |
| 181 | /// asked them. |
| 182 | asks: , |
| 183 | , |
| 184 | |
| 185 | |
| 186 | /// One question and how long the value must stand still before it is asked. |
| 187 | /// |
| 188 | /// [`Step::CallAfter`]'s member. A pair rather than two parallel lists, so a |
| 189 | /// host cannot pick up a request and the wrong wait. |
| 190 | |
| 191 | |
| 192 | /// What to ask. |
| 193 | pub request: Request, |
| 194 | /// How long the value must stand still first. |
| 195 | pub after: Duration, |
| 196 | |
| 197 | |
| 198 | /// A file a route answered with, for the host to put somewhere. |
| 199 | /// |
| 200 | /// [`Outcome::File`] says what the file is and never where it goes, so this |
| 201 | /// runtime does not write it: this crate turns keys into requests and owns no |
| 202 | /// I/O, the same division that keeps [`Step::Call`] from calling anything and |
| 203 | /// [`Step::Open`] from opening anything. |
| 204 | /// |
| 205 | /// The host drains it with [`Runtime::handed`] after [`Runtime::apply`]. On a |
| 206 | /// terminal the ruling says what to do with it — write it into the working |
| 207 | /// directory under [`name`](Self::name) — and a host with somewhere better to |
| 208 | /// put it is free to ask. |
| 209 | |
| 210 | |
| 211 | /// The suggested file name, already through [`safe_file_name`]. |
| 212 | /// |
| 213 | /// Sanitised here rather than by each host, because the host that forgets |
| 214 | /// is the one that writes `../../.ssh/authorized_keys` beside the process. |
| 215 | pub name: String, |
| 216 | /// What kind of file it is. |
| 217 | pub kind: Accepted, |
| 218 | /// The file. |
| 219 | pub bytes: , |
| 220 | |
| 221 | |
| 222 | /// How the layer on top is drawn over the one under it. |
| 223 | /// |
| 224 | /// The two outcomes that layer a screen want different boxes, and this is the |
| 225 | /// whole of the difference between them here: everything else -- the stack, |
| 226 | /// the dedupe, the dismissal, the keys -- is one code path. |
| 227 | |
| 228 | pub |
| 229 | /// Centred and inset on all four sides. `Outcome::Over`: a palette, a help |
| 230 | /// overlay, an app-modal dialog, all of which are about the app rather than |
| 231 | /// about anything on the screen. |
| 232 | Over, |
| 233 | /// A compact box in the half of the screen the subject is not in. |
| 234 | /// `Outcome::Anchored`. |
| 235 | /// |
| 236 | /// # Why a half and not a position |
| 237 | /// |
| 238 | /// This renderer has no coordinates to anchor to. `makeover_tui::table` |
| 239 | /// decides its own column widths and answers none back, and |
| 240 | /// `crate::focus`' header says why per-cell rects are not being added: Max |
| 241 | /// ruled against them. So a terminal cannot draw a menu *at* the control |
| 242 | /// the way egui can, and inventing a cell to point at would be this |
| 243 | /// renderer guessing. |
| 244 | /// |
| 245 | /// What it can promise is that the menu does not cover what it was opened |
| 246 | /// on. The reach order is the one ordering this renderer does have, so a |
| 247 | /// subject in the first half of it puts the menu low and one in the second |
| 248 | /// half puts it high. That is proximity said in the only currency a |
| 249 | /// terminal has, and it is honest about being a half rather than a point. |
| 250 | Anchored |
| 251 | /// Whether the menu sits in the lower half. |
| 252 | low: bool, |
| 253 | , |
| 254 | |
| 255 | |
| 256 | /// A screen and what the user has done to it. |
| 257 | /// |
| 258 | /// The pair is the unit an overlay needs: an overlay has its own reach, its own |
| 259 | /// focus, its own edits and its own scroll, so it holds a [`View`] of its own |
| 260 | /// rather than borrowing the one underneath. Sharing it is the bug the overlay |
| 261 | /// tests exist to catch — dismissing a palette would take the user's typing and |
| 262 | /// scroll position with it. |
| 263 | |
| 264 | |
| 265 | screen: Screen, |
| 266 | view: View, |
| 267 | /// The request that opened the overlay this layer was displaced by. |
| 268 | /// |
| 269 | /// Saved on the way down and restored on the way up, so a nested overlay |
| 270 | /// dismissing back to an outer one restores the *outer* one's identity |
| 271 | /// rather than losing it. See [`Runtime::over`]. |
| 272 | over: , |
| 273 | /// How the layer this one was displaced by was drawn. Saved and restored |
| 274 | /// with `over`, and for its reason. See [`Runtime::laid`]. |
| 275 | laid: , |
| 276 | |
| 277 | |
| 278 | /// A screen, what the user has done to it, and how they got here. |
| 279 | |
| 280 | |
| 281 | screen: Screen, |
| 282 | view: View, |
| 283 | /// What the app offers from every screen. Matched before this runtime's own |
| 284 | /// key table, so a screen cannot capture the key that opens the palette. |
| 285 | chrome: Chrome, |
| 286 | /// The layers this one is drawn over, outermost first. |
| 287 | /// |
| 288 | /// Empty on an ordinary screen. `screen` and `view` above are always the |
| 289 | /// ACTIVE layer, so every key, every gather and every draw works on the |
| 290 | /// overlay once one is open, with no second code path. |
| 291 | /// |
| 292 | /// Separate from `history` on purpose: an overlay is not a place. Opening |
| 293 | /// one pushes here and leaves history alone, and dismissing one pops here |
| 294 | /// and reveals the screen the user never left. |
| 295 | under: , |
| 296 | /// The places behind this one, most recent last. |
| 297 | /// |
| 298 | /// Requests rather than addresses, because going back means asking again |
| 299 | /// and a request is what asking takes. [`Address`](quasi_router::Address) |
| 300 | /// carries a string for a browser's address bar, which a terminal does not |
| 301 | /// have. |
| 302 | history: , |
| 303 | /// The request that produced the screen currently showing. |
| 304 | here: , |
| 305 | /// The request that produced the overlay currently on top, if one is. |
| 306 | /// |
| 307 | /// Five presses of a help key were five Escapes. |
| 308 | /// |
| 309 | /// Nothing in the description had to grow for it. The runtime is handed the |
| 310 | /// request it fired, so the identity the dedupe needs is already in hand -- |
| 311 | /// this is `here`'s shape for a layer that is not a place, which is why |
| 312 | /// `remember` is not called for one. |
| 313 | /// |
| 314 | /// A webview never had the bug: `Outcome::Over` lands in one overlay |
| 315 | /// container and replaces what is in it. Two of three hosts stacking was |
| 316 | /// one description behaving two ways, which is the drift this stack exists |
| 317 | /// to end. |
| 318 | over: , |
| 319 | /// Where the layer on top is drawn, when one is. |
| 320 | /// |
| 321 | /// An `Outcome::Over` is a palette and takes the inset box in the middle; |
| 322 | /// an `Outcome::Anchored` is a menu and takes a compact box out of the |
| 323 | /// subject's way. Kept beside `over` rather than on `Layer`, because it is |
| 324 | /// a fact about the layer on top and `Layer` holds the ones underneath. |
| 325 | /// |
| 326 | /// `None` whenever `over` is, and the two move together. |
| 327 | pub(crate) laid: , |
| 328 | /// A control waiting on its own question being answered. |
| 329 | asked: , |
| 330 | /// The awaiting call this runtime has dispatched and not yet been answered |
| 331 | /// about, with the request that went out. |
| 332 | /// |
| 333 | /// Only an action carrying [`Action::awaiting`] lands here: the |
| 334 | /// description is what says which calls are worth locking a control for, |
| 335 | /// so this renderer does not decide that a route is slow. |
| 336 | /// |
| 337 | /// The pair rather than the action alone, because the refusal is against the |
| 338 | /// request that is outstanding: pressing the same control again is the |
| 339 | /// double submit, and pressing a different one is a different call this has |
| 340 | /// no business refusing. |
| 341 | outstanding: , |
| 342 | /// Something to say once the screen it belongs to has arrived. |
| 343 | saying: , |
| 344 | /// What this mount puts around whatever screen is showing. |
| 345 | /// |
| 346 | /// Held beside the screen rather than arriving with one, which is what |
| 347 | /// makes it the mount's: it survives every answer that replaces the screen |
| 348 | /// inside it, the way `chrome` above survives every navigation. The |
| 349 | /// difference between the two is lifetime — chrome is the app's and this |
| 350 | /// is one place the app puts a screen up. |
| 351 | frame: Frame, |
| 352 | /// When this last handed out [`Runtime::refreshes`], so the cadence is kept |
| 353 | /// here rather than by every host that draws a live screen. |
| 354 | /// |
| 355 | /// `None` until the first call, which makes the first ask immediate: a |
| 356 | /// region that waited out a whole period before its first answer would be a |
| 357 | /// slower screen than the one liveness replaces. |
| 358 | refreshed: , |
| 359 | /// When each toast on the screen was raised, in the order the toasts sit in |
| 360 | /// `screen.notices`. |
| 361 | /// |
| 362 | /// The description says a toast goes away on its own and never says when, |
| 363 | /// so the when is kept here: one instant per transient notice, and |
| 364 | /// [`expires_at`](Runtime::expires_at) takes away the ones whose time is |
| 365 | /// up. Banners have no entry, because nothing about a banner is on a |
| 366 | /// clock. |
| 367 | /// |
| 368 | /// Positional rather than keyed, because a notice has no identity to key |
| 369 | /// on and does not need one: a toast joins the screen at the end of the |
| 370 | /// list ([`announce`](Runtime::announce)) or arrives inside a whole screen, |
| 371 | /// and both are handled where they happen rather than guessed at here. |
| 372 | raised: , |
| 373 | /// A file a route answered with and the host has not taken yet. |
| 374 | /// |
| 375 | /// Drained by [`handed`](Runtime::handed) rather than returned from |
| 376 | /// [`apply`](Runtime::apply), because a file and a follow-up request are |
| 377 | /// not alternatives: an answer is one or the other, and widening `apply`'s |
| 378 | /// return type to say so would change every host for the case that cannot |
| 379 | /// happen. |
| 380 | /// |
| 381 | /// One at a time, and a second overwrites the first. Two files from one |
| 382 | /// answer is not a thing the vocabulary can say. |
| 383 | handed: , |
| 384 | /// A place a route asked for and the host has not gone looking for yet. |
| 385 | /// |
| 386 | /// [`handed`](Self::handed)'s twin, drained by |
| 387 | /// [`locating`](Runtime::locating) and here for the same reason: this crate |
| 388 | /// turns keys into requests and owns no I/O, and asking a person where to |
| 389 | /// write is I/O of the most blocking kind. |
| 390 | /// |
| 391 | /// One at a time, and a second replaces the first. A picker is modal |
| 392 | /// wherever there is one, and on a terminal there is nothing to open at |
| 393 | /// all — see [`locating`](Runtime::locating) for what this host does with |
| 394 | /// one instead. |
| 395 | locating: , |
| 396 | |
| 397 | |
| 398 | |
| 399 | /// Start on this screen, with nothing typed and nothing behind it. |
| 400 | |
| 401 | |
| 402 | let mut runtime = Self |
| 403 | screen, |
| 404 | view: new, |
| 405 | chrome: new, |
| 406 | under: Vecnew, |
| 407 | laid: None, |
| 408 | history: Vecnew, |
| 409 | here: None, |
| 410 | over: None, |
| 411 | asked: None, |
| 412 | outstanding: None, |
| 413 | saying: None, |
| 414 | frame: new, |
| 415 | refreshed: None, |
| 416 | raised: Vecnew, |
| 417 | handed: None, |
| 418 | locating: None, |
| 419 | ; |
| 420 | runtime.view.seed; |
| 421 | runtime.open_caret; |
| 422 | runtime.reraise; |
| 423 | runtime |
| 424 | |
| 425 | |
| 426 | /// Declare what the app offers from every screen. |
| 427 | /// |
| 428 | /// Held beside the screen rather than arriving with one, which is what |
| 429 | /// makes it chrome: the bindings outlive every answer this runtime applies. |
| 430 | |
| 431 | |
| 432 | self.chrome = chrome; |
| 433 | self |
| 434 | |
| 435 | |
| 436 | /// Declare what this mount puts around the screen. |
| 437 | /// |
| 438 | /// A mount with two ways of showing one screen builds two runtimes |
| 439 | /// carrying two frames, which is what it already does for everything else |
| 440 | /// it holds across frames. The screen inside them says nothing about |
| 441 | /// either. |
| 442 | |
| 443 | |
| 444 | self.frame = frame; |
| 445 | self |
| 446 | |
| 447 | |
| 448 | /// What the app offers from every screen. |
| 449 | /// |
| 450 | /// The panel is what a caller reads off this: an answer aimed at it lands |
| 451 | /// here rather than on the screen, so this is where its current contents |
| 452 | /// are. |
| 453 | |
| 454 | pub const |
| 455 | &self.chrome |
| 456 | |
| 457 | |
| 458 | /// What this mount puts around the screen. |
| 459 | |
| 460 | pub const |
| 461 | &self.frame |
| 462 | |
| 463 | |
| 464 | /// Whether an overlay is open over the screen. |
| 465 | |
| 466 | pub const |
| 467 | !self.under.is_empty |
| 468 | |
| 469 | |
| 470 | /// The screen being shown. |
| 471 | /// |
| 472 | /// The overlay's, when one is open. That is what "being shown" means, and |
| 473 | /// it is what every key this runtime handles is working on. |
| 474 | |
| 475 | pub const |
| 476 | &self.screen |
| 477 | |
| 478 | |
| 479 | /// What the user has done to it. |
| 480 | |
| 481 | pub const |
| 482 | &self.view |
| 483 | |
| 484 | |
| 485 | /// The request that produced the screen showing now. |
| 486 | /// |
| 487 | /// `None` before the first navigation: a runtime is built from a screen |
| 488 | /// rather than from an address, so the opening screen has no request behind |
| 489 | /// it until the host performs one. |
| 490 | |
| 491 | pub const |
| 492 | self.here.as_ref |
| 493 | |
| 494 | |
| 495 | /// Ask for this screen again. |
| 496 | /// |
| 497 | /// **What a described screen has no other way to say: the thing it is about |
| 498 | /// changed, and nothing the user did to this screen changed it.** A route |
| 499 | /// answers a screen built from the state at the moment it was asked, and |
| 500 | /// that answer is kept until something fires. So a host whose state moves |
| 501 | /// underneath a screen — a background job reporting progress, a write the |
| 502 | /// host applies after the frame — has a screen describing a past it can |
| 503 | /// neither notice nor correct. |
| 504 | /// |
| 505 | /// This is the host saying so. It is deliberately not a description member: |
| 506 | /// nothing in [`Screen`] claims a refresh rate, because how often a fact |
| 507 | /// goes stale is a property of the app holding it rather than of the screen |
| 508 | /// showing it. |
| 509 | /// |
| 510 | /// [`Step::Idle`] when there is nothing to ask for, which is the opening |
| 511 | /// screen before any navigation. History is untouched: asking for the screen |
| 512 | /// you are on again is not going anywhere. |
| 513 | |
| 514 | |
| 515 | match &self.here |
| 516 | Some => Call, |
| 517 | None => Idle, |
| 518 | |
| 519 | |
| 520 | |
| 521 | /// The calls this screen's regions are waiting on, for the host to perform. |
| 522 | /// |
| 523 | /// A webview gets this for free: every region fed by a call carries a |
| 524 | /// trigger and the browser asks as soon as the element exists. A terminal |
| 525 | /// has nobody to do that, so the host asks for these after putting a screen |
| 526 | /// up, hands each answer back to [`apply`](Self::apply), and the region |
| 527 | /// fills where its stand-in was. |
| 528 | /// |
| 529 | /// Ask again after applying an answer rather than keeping the list: a |
| 530 | /// fragment landing clears the region's feed, so what comes back is what is |
| 531 | /// still outstanding. |
| 532 | |
| 533 | |
| 534 | self.screen |
| 535 | .feeds |
| 536 | .into_iter |
| 537 | .filter_map |
| 538 | .collect |
| 539 | |
| 540 | |
| 541 | /// The calls this screen's live regions re-ask, when it is time to ask. |
| 542 | /// |
| 543 | /// [`feeds`](Self::feeds)' counterpart and never overlapping it: a feed |
| 544 | /// arrives once and a refresh never stops. Empty until |
| 545 | /// [`CADENCE`](crate::CADENCE) has passed since the last time this handed |
| 546 | /// anything back, so a host may call it as often as it likes and the rate |
| 547 | /// stays this crate's. |
| 548 | /// |
| 549 | /// The pacing is here rather than in each host for the reason the number |
| 550 | /// is: a host that timed its own polling would be a host the other |
| 551 | /// renderers disagree with, and every app would rebuild the same timer. |
| 552 | /// |
| 553 | /// # Two shapes of answer |
| 554 | /// |
| 555 | /// A live region naming a call comes back as that call, and its answer is a |
| 556 | /// fragment for that region. A live region naming none comes back as the |
| 557 | /// screen's own address, because re-reading state the host already holds |
| 558 | /// means building the description again — and that is a whole screen, not a |
| 559 | /// fragment. Both are requests, and a host performs them the same way. |
| 560 | |
| 561 | |
| 562 | self.refreshes_at |
| 563 | |
| 564 | |
| 565 | /// [`refreshes`](Self::refreshes) against a clock the caller holds. |
| 566 | /// |
| 567 | /// The seam a test needs, and the one an event loop that already knows what |
| 568 | /// time it is should reach for rather than asking again. |
| 569 | |
| 570 | |
| 571 | let due = self |
| 572 | .refreshed |
| 573 | .is_none_or; |
| 574 | if !due |
| 575 | return Vecnew; |
| 576 | |
| 577 | let mut out: = self |
| 578 | .screen |
| 579 | .refreshes |
| 580 | .into_iter |
| 581 | .filter_map |
| 582 | .collect; |
| 583 | // A live region that names no call is re-read by asking the screen's own |
| 584 | // address again, which is what re-reading means for a host that retains |
| 585 | // a description rather than a document. The audiofiles sync panel is |
| 586 | // that case: its state is the app's own and moves when an OAuth callback |
| 587 | // lands in another process, so there is no fragment to fetch and the |
| 588 | // whole screen is rebuilt from what is true now. |
| 589 | // |
| 590 | // Only when nothing else answered. A screen with a live region that does |
| 591 | // name a call has already been given the narrower ask, and adding the |
| 592 | // address to it would rebuild the screen the fragment was about to land |
| 593 | // in. |
| 594 | if out.is_empty |
| 595 | && self.screen.is_live |
| 596 | && let Some = &self.here |
| 597 | |
| 598 | out.push; |
| 599 | |
| 600 | // Stamped even when the screen has nothing live, so a still screen is |
| 601 | // not re-walked on every frame an egui host draws. |
| 602 | self.refreshed = Some; |
| 603 | out |
| 604 | |
| 605 | |
| 606 | /// Whether anything on this screen changes without the user. |
| 607 | /// |
| 608 | /// True for a live region whether or not it names a call, which is the |
| 609 | /// difference from [`refreshes`](Self::refreshes): a region reading state |
| 610 | /// the host already holds has nothing to ask for and still has to be |
| 611 | /// redrawn. |
| 612 | |
| 613 | |
| 614 | self.screen.is_live |
| 615 | |
| 616 | |
| 617 | /// How long a host may wait before this screen has to be drawn again for |
| 618 | /// its own sake, if it holds anything that goes stale on its own. |
| 619 | /// |
| 620 | /// A terminal redraws on an event, and a readout derived from the current |
| 621 | /// time is the case with no event to redraw on. What comes back is the |
| 622 | /// finest cadence the screen's readouts demand, so a host blocking on |
| 623 | /// input gives this as its timeout and every readout on the screen moves |
| 624 | /// on one tick. |
| 625 | /// |
| 626 | /// A toast counts too, and its answer is what is left of its |
| 627 | /// [`LINGER`](crate::LINGER) rather than a cadence: it is drawn again once, |
| 628 | /// to not be drawn at all. |
| 629 | /// |
| 630 | /// `None` for a screen holding none, which is nearly all of them, and the |
| 631 | /// host may then block for as long as it likes. Independent of |
| 632 | /// [`refreshes`](Self::refreshes): that one asks something over a network |
| 633 | /// on [`CADENCE`](crate::CADENCE), and this is arithmetic the terminal does |
| 634 | /// itself. A screen with both takes the shorter answer for its timeout and |
| 635 | /// the two stay separate facts. |
| 636 | |
| 637 | |
| 638 | self.tick_in_at |
| 639 | |
| 640 | |
| 641 | /// [`tick_in`](Self::tick_in) against a clock the caller holds. |
| 642 | /// |
| 643 | /// The seam a test needs, and the one an event loop that already knows what |
| 644 | /// time it is should reach for rather than asking again. |
| 645 | |
| 646 | |
| 647 | let clocks = self.screen.clocks.into_iter.map.min; |
| 648 | let toasts = self |
| 649 | .raised |
| 650 | .iter |
| 651 | .map |
| 652 | .min; |
| 653 | clocks.into_iter.chain.min |
| 654 | |
| 655 | |
| 656 | /// Take away every toast whose time is up, and say whether one went. |
| 657 | /// |
| 658 | /// `Notice::Toast` says the message goes away on its own, and this is the |
| 659 | /// terminal keeping that promise: a browser has a timer per element and a |
| 660 | /// terminal has an event loop, so the loop asks this once a turn the way |
| 661 | /// it already asks [`refreshes`](Self::refreshes), and |
| 662 | /// [`tick_in`](Self::tick_in) is what wakes a loop that is only blocking on |
| 663 | /// a key. |
| 664 | /// |
| 665 | /// A banner is never touched. It goes when the condition it reports is |
| 666 | /// fixed, which is a route's business and not a clock's. |
| 667 | |
| 668 | self.expires_at |
| 669 | |
| 670 | |
| 671 | /// [`expires`](Self::expires) against a clock the caller holds. |
| 672 | |
| 673 | if self.raised.is_empty |
| 674 | return false; |
| 675 | |
| 676 | let raised = take; |
| 677 | let mut ages = raised.into_iter; |
| 678 | let mut kept = Vecnew; |
| 679 | let before = self.screen.notices.len; |
| 680 | self.screen.notices.retain |
| 681 | let Notice = node else |
| 682 | return true; |
| 683 | ; |
| 684 | // `eea7ba88`. One call rather than a transience check and a |
| 685 | // constant: `None` is a notice with no lifetime, which is a banner, |
| 686 | // and it is kept for the same reason the check used to keep it. |
| 687 | let Some = notice_lifetime else |
| 688 | return true; |
| 689 | ; |
| 690 | // A toast with no instant beside it is one this runtime never saw |
| 691 | // raised, which nothing in the crate produces. It is given now |
| 692 | // rather than dropped: an unexplained toast on the screen is a |
| 693 | // smaller wrong than a message the user never got to read. |
| 694 | let at = ages.next.unwrap_or; |
| 695 | let up = now.duration_since >= lifetime; |
| 696 | if !up |
| 697 | kept.push; |
| 698 | |
| 699 | !up |
| 700 | ; |
| 701 | self.raised = kept; |
| 702 | self.screen.notices.len != before |
| 703 | |
| 704 | |
| 705 | /// Start every toast on the screen lingering from now. |
| 706 | /// |
| 707 | /// A whole screen arriving brings whatever notices it was described with, |
| 708 | /// and a screen coming back out from under an overlay is in front of the |
| 709 | /// user again. Both are the moment the reading starts, so both reset the |
| 710 | /// clock rather than trying to remember one from before. |
| 711 | |
| 712 | let toasts = self |
| 713 | .screen |
| 714 | .notices |
| 715 | .iter |
| 716 | .filter |
| 717 | .count; |
| 718 | self.raised = vec!; |
| 719 | |
| 720 | |
| 721 | /// What the control that was pressed is waiting on, if one is. |
| 722 | /// |
| 723 | /// The amount rides on it when the description measured one, which is what |
| 724 | /// a host drawing its own progress reads. Nothing here turns it into a time: |
| 725 | /// a bar shows what is done over what there is and how long it has taken, |
| 726 | /// and predicts nothing. |
| 727 | |
| 728 | |
| 729 | self.outstanding |
| 730 | .as_ref |
| 731 | .and_then |
| 732 | |
| 733 | |
| 734 | /// Everything reachable on it, in focus order. |
| 735 | /// |
| 736 | /// The regions that do not apply right now are left out, which is what the |
| 737 | /// drawing does with them: the caret stops on what is on the screen. |
| 738 | |
| 739 | |
| 740 | let hidden = cratehidden; |
| 741 | cratereaches_chromed |
| 742 | &self.screen, |
| 743 | &self.frame, |
| 744 | &self.chrome, |
| 745 | &crateof, |
| 746 | |
| 747 | |
| 748 | |
| 749 | /// Put the caret where the screen said it starts. |
| 750 | /// |
| 751 | /// Arrival behaviour and nothing else, which is the whole of what |
| 752 | /// [`Screen::opens_at`] is: a screen cannot pull the caret back on a |
| 753 | /// redraw, so this runs where [`View::seed`] runs and nowhere else. A |
| 754 | /// screen that names nothing leaves the caret at the first stop, which is |
| 755 | /// where a new screen has always started. |
| 756 | /// |
| 757 | /// A name no field on the screen carries moves nothing. Not an error, for |
| 758 | /// the reason the member states: the screen is the app's and so is the |
| 759 | /// name. |
| 760 | |
| 761 | let Some = self.screen.opens_at.clone else |
| 762 | return; |
| 763 | ; |
| 764 | let reaches = self.reaches; |
| 765 | let Some = reaches |
| 766 | .iter |
| 767 | .position |
| 768 | else |
| 769 | return; |
| 770 | ; |
| 771 | self.view.focus_on; |
| 772 | |
| 773 | |
| 774 | /// Everything the screen describes, whether or not it is out right now. |
| 775 | /// |
| 776 | /// What a submit reads.: a region that does not apply is left off the |
| 777 | /// screen here and its boxes still hold what was typed into them, exactly |
| 778 | /// as a browser keeps the value of a hidden input and sends it. The caret |
| 779 | /// walk and this one differ only in that, and each is used where it is the |
| 780 | /// honest answer. |
| 781 | |
| 782 | cratereaches_chromed |
| 783 | &self.screen, |
| 784 | &self.frame, |
| 785 | &self.chrome, |
| 786 | &crateof, |
| 787 | |
| 788 | |
| 789 | |
| 790 | /// Whether the caret is in a field, which is what decides whether a |
| 791 | /// printable key is a shortcut or a character. |
| 792 | /// |
| 793 | /// A host wanting `q` to quit asks this first. Quitting is the host's and |
| 794 | /// not a binding here, because a key that closes the app is a fact about |
| 795 | /// the app rather than about the screen. |
| 796 | |
| 797 | |
| 798 | self.focused.is_some_and |
| 799 | |
| 800 | |
| 801 | /// Whether a question is waiting to be answered. |
| 802 | |
| 803 | pub const |
| 804 | self.asked.is_some |
| 805 | |
| 806 | |
| 807 | /// Draw it. |
| 808 | |
| 809 | // What is under it first, outermost first, then this one over the top. |
| 810 | // An overlay that painted only itself would be a screen swap wearing |
| 811 | // another name. |
| 812 | for layer in &self.under |
| 813 | tui.screen; |
| 814 | |
| 815 | let area = if self.under.is_empty |
| 816 | area |
| 817 | else |
| 818 | // `ae8e8836`. A palette takes the middle and a menu takes a corner |
| 819 | // out of the subject's way. `Laid::Over` is the fallback for a |
| 820 | // stack that predates the member as well as for an anchor that |
| 821 | // named nothing, which is the same box this always drew. |
| 822 | let inset = match self.laid |
| 823 | Some => Selfanchored_area, |
| 824 | Some | None => Selfoverlay_area, |
| 825 | ; |
| 826 | // Clear what is under it inside its own bounds, so the overlay |
| 827 | // reads as being over the screen rather than mixed into it. |
| 828 | for y in inset.top..inset.bottom |
| 829 | for x in inset.left..inset.right |
| 830 | buf.reset; |
| 831 | |
| 832 | |
| 833 | inset |
| 834 | ; |
| 835 | tui.chromed |
| 836 | &self.screen, |
| 837 | &self.frame, |
| 838 | &self.chrome, |
| 839 | &self.view, |
| 840 | area, |
| 841 | buf, |
| 842 | ; |
| 843 | |
| 844 | |
| 845 | /// Where an overlay sits inside the screen it is over. |
| 846 | /// |
| 847 | /// Inset on all four sides so the screen underneath stays visible around |
| 848 | /// it, which is the whole visual claim an overlay makes. Proportional |
| 849 | /// rather than fixed: a palette 4 rows from the edge of an 80x24 terminal |
| 850 | /// is a different thing from one 4 rows from the edge of a 200x60. |
| 851 | |
| 852 | let pad_x = |
| 853 | .max |
| 854 | .min; |
| 855 | let pad_y = |
| 856 | .max |
| 857 | .min; |
| 858 | Rect |
| 859 | x: area.x + pad_x, |
| 860 | y: area.y + pad_y, |
| 861 | width: area.width.saturating_sub, |
| 862 | height: area.height.saturating_sub, |
| 863 | |
| 864 | |
| 865 | |
| 866 | /// Put a screen over the one showing, keeping that one whole underneath. |
| 867 | /// |
| 868 | /// The shared half of `Outcome::Over` and `Outcome::Anchored`, which differ |
| 869 | /// only in the box they take. Everything else about a layer -- the stack, |
| 870 | /// the dedupe, the seeding, the dismissal -- is one path, and the two |
| 871 | /// outcomes drifting apart here is what would make one description behave |
| 872 | /// two ways. |
| 873 | /// |
| 874 | /// # The dedupe |
| 875 | /// |
| 876 | /// A binding that opens an overlay is asked at the top of every frame and |
| 877 | /// an open overlay does not suppress it, so this pushed a second copy of |
| 878 | /// the same help over the first and it took an Escape per press to get |
| 879 | /// back. The guard is the request rather than the screen, because every |
| 880 | /// press is a fresh route call and the two screen values are equal by |
| 881 | /// accident rather than by identity. |
| 882 | /// |
| 883 | /// The top layer only. A screen raised from within another one is a |
| 884 | /// different request and still stacks, which is what a confirm over a |
| 885 | /// palette is. |
| 886 | |
| 887 | if self.over.as_ref == Some |
| 888 | return; |
| 889 | |
| 890 | let under = Layer |
| 891 | screen: replace, |
| 892 | view: replace, |
| 893 | over: self.over.replace, |
| 894 | laid: self.laid.replace, |
| 895 | ; |
| 896 | self.under.push; |
| 897 | self.view.seed; |
| 898 | self.open_caret; |
| 899 | self.reraise; |
| 900 | self.announce; |
| 901 | |
| 902 | |
| 903 | /// Where an anchored menu sits inside the screen it is over. |
| 904 | /// |
| 905 | /// A compact box against one edge rather than the centred inset an overlay |
| 906 | /// takes, because a menu is a short list of verbs and a box the size of a |
| 907 | /// palette around six of them reads as a screen. Half the width and half |
| 908 | /// the height, in the half `low` names, so the subject stays visible in the |
| 909 | /// other one. See [`Laid::Anchored`] for why a half is the honest unit. |
| 910 | |
| 911 | let width = .max; |
| 912 | let height = .max; |
| 913 | // Against the left edge, inset by one, so a menu and the screen under |
| 914 | // it never share a column of glyphs. The vertical half is the half the |
| 915 | // subject is not in. |
| 916 | let x = area.x + u16from; |
| 917 | let y = if low |
| 918 | area.y + area.height.saturating_sub |
| 919 | else |
| 920 | area.y |
| 921 | ; |
| 922 | Rect |
| 923 | x, |
| 924 | y, |
| 925 | width: width.min, |
| 926 | height, |
| 927 | |
| 928 | |
| 929 | |
| 930 | /// Whether the thing the user is on sits in the first half of the reach |
| 931 | /// order, which is what puts an anchored menu low. |
| 932 | /// |
| 933 | /// The reach order is the only ordering this renderer has; see |
| 934 | /// [`Laid::Anchored`]. A screen with nothing reachable answers `false` and |
| 935 | /// the menu goes high, which is where a menu with no subject to avoid may |
| 936 | /// as well be. |
| 937 | |
| 938 | let reach = self.reaches.len; |
| 939 | let at = self.view.focus; |
| 940 | reach > 0 && at < reach && at * 2 < reach |
| 941 | |
| 942 | |
| 943 | /// Put a message on the screen, from the host rather than from a route. |
| 944 | /// |
| 945 | /// The host has things to say that no handler knows about: a route that |
| 946 | /// failed, an address it will not open, a device that is not there. Without |
| 947 | /// this they would go to stderr, which on a terminal app is underneath the |
| 948 | /// alternate screen and therefore nowhere. |
| 949 | |
| 950 | self.screen.notices.push |
| 951 | kind: Banner, |
| 952 | tone: Danger, |
| 953 | text: text.into, |
| 954 | // Nothing to do about it. What the host says here is a report -- |
| 955 | // a route that failed, an address it will not open -- and there is |
| 956 | // no route it could offer that would undo any of them. |
| 957 | act: None, |
| 958 | ; |
| 959 | |
| 960 | |
| 961 | /// What is under the caret. |
| 962 | |
| 963 | |
| 964 | let mut reaches = self.reaches; |
| 965 | if self.view.focus >= reaches.len |
| 966 | return None; |
| 967 | |
| 968 | Some |
| 969 | |
| 970 | |
| 971 | /// Take a key, and say what the host should do about it. |
| 972 | |
| 973 | // A question owns the keyboard until it is answered. Anything that is |
| 974 | // not yes is no, which is the safe way round for a prompt that is only |
| 975 | // ever raised by something destructive. |
| 976 | if let Some = self.asked.take |
| 977 | return match key |
| 978 | // The selection was gathered when the question was raised, not |
| 979 | // now. Nothing can tick while a prompt owns the keyboard, so |
| 980 | // the two are the same set -- and reading it here would mean |
| 981 | // the answer depended on state the user could not see. |
| 982 | Char | Enter => self.send, |
| 983 | _ => Idle, |
| 984 | ; |
| 985 | |
| 986 | |
| 987 | // The app's own keys, before this runtime's table and before any |
| 988 | // screen's `Act::key`. An affordance available everywhere is not |
| 989 | // available everywhere if a screen can capture its key. |
| 990 | // |
| 991 | // Not while typing: a field has the keyboard, and a binding on a |
| 992 | // printable key would otherwise be unreachable as a character. A |
| 993 | // binding naming a key no field can consume still lands. |
| 994 | if !self.editing || !matches! |
| 995 | let pressed = Selfkey_name; |
| 996 | if let Some = pressed.as_deref.and_then |
| 997 | return self.call; |
| 998 | |
| 999 | |
| 1000 | |
| 1001 | let reaches = self.reaches; |
| 1002 | let count = reaches.len; |
| 1003 | let here = reaches |
| 1004 | .get |
| 1005 | .map; |
| 1006 | // What the caret is actually on, which is the row until it has stepped |
| 1007 | // into one. Every arm below that acts on the caret takes this, so |
| 1008 | // pressing Enter inside a row fires the control rather than opening the |
| 1009 | // row; the arms that act on the row itself take `here` and say so. |
| 1010 | let caret = match |
| 1011 | => |
| 1012 | inside.get.cloned.or_else |
| 1013 | |
| 1014 | _ => here.clone, |
| 1015 | ; |
| 1016 | |
| 1017 | // A field with an open suggestion list owns four keys while it is |
| 1018 | // open, and gives them all back the moment it closes. Arrows move the |
| 1019 | // highlight rather than the focus, Enter takes the highlighted |
| 1020 | // candidate, and Escape puts the list away before it means anything |
| 1021 | // else -- which is the innermost-thing-first rule Escape already |
| 1022 | // follows below, one level further in. |
| 1023 | // |
| 1024 | // Enter with nothing highlighted is deliberately not captured: a list |
| 1025 | // arrives with no highlight, so a form stays submittable from a box the |
| 1026 | // user has typed into and paused in. |
| 1027 | if let Some = caret.as_ref.and_then |
| 1028 | && self.view.suggesting.is_some |
| 1029 | |
| 1030 | match key |
| 1031 | Down => |
| 1032 | self.view.highlight; |
| 1033 | return Idle; |
| 1034 | |
| 1035 | Up => |
| 1036 | self.view.highlight; |
| 1037 | return Idle; |
| 1038 | |
| 1039 | Escape => |
| 1040 | self.view.unsuggest; |
| 1041 | return Idle; |
| 1042 | |
| 1043 | Enter => |
| 1044 | if let Some = self.view.pick |
| 1045 | // A candidate that says what picking it does has that |
| 1046 | // performed, and nothing is written into the box. |
| 1047 | // `ed1fa86f`: local is the default, not the definition. |
| 1048 | // |
| 1049 | // Performed exactly as a control's action is, with no |
| 1050 | // payload rule invented for this one place -- an action |
| 1051 | // already carries its own params and the view it was |
| 1052 | // offered under. MNW's search box is the case that |
| 1053 | // decides it: its candidates are `Action::get(url)` to |
| 1054 | // a project, item or creator page, and appending the |
| 1055 | // typed value to that address would be a description |
| 1056 | // saying something the route never asked for. |
| 1057 | if let Some = candidate.picks |
| 1058 | return self.send; |
| 1059 | |
| 1060 | let value = candidate.value; |
| 1061 | let name = field.name.clone; |
| 1062 | self.view.set; |
| 1063 | // What a pick costs is what a settled value costs: the |
| 1064 | // webview sends `change` for the same reason, and a |
| 1065 | // field that writes when it changes has just changed. |
| 1066 | // What it does not do is re-ask the questions a field |
| 1067 | // asks *while it is being typed into*, which is why |
| 1068 | // `after_typing` is not called here. |
| 1069 | if let Some = field.writes.clone |
| 1070 | let mut payload = new; |
| 1071 | payload.insert; |
| 1072 | return self.send; |
| 1073 | |
| 1074 | return Idle; |
| 1075 | |
| 1076 | |
| 1077 | _ => |
| 1078 | |
| 1079 | |
| 1080 | |
| 1081 | match key |
| 1082 | Tab | Down => |
| 1083 | let wrote = self.leaving; |
| 1084 | self.view.advance; |
| 1085 | wrote.unwrap_or |
| 1086 | |
| 1087 | BackTab | Up => |
| 1088 | let wrote = self.leaving; |
| 1089 | self.view.advance; |
| 1090 | wrote.unwrap_or |
| 1091 | |
| 1092 | |
| 1093 | PageDown | PageUp => |
| 1094 | // The region the caret is in, because it is the one the user is |
| 1095 | // working in. A screen with focus nowhere scrolls nothing, |
| 1096 | // which is honest: there is no "the pane" on a screen with |
| 1097 | // several. |
| 1098 | if let Some = reaches.get |
| 1099 | let rows = if matches! |
| 1100 | 10 |
| 1101 | else |
| 1102 | -10 |
| 1103 | ; |
| 1104 | self.view.scroll_by; |
| 1105 | |
| 1106 | Idle |
| 1107 | |
| 1108 | |
| 1109 | Left | Right => |
| 1110 | // A branch under the caret first. `ccaa7e4b`: these are the |
| 1111 | // keys every tree in a terminal already answers, and a reader |
| 1112 | // who has walked onto a folder presses them expecting it to |
| 1113 | // open. Right opens and Left shuts, so a press that changes |
| 1114 | // nothing is one that says the branch was already that way. |
| 1115 | // |
| 1116 | // Ahead of the two readings below because a row that is a |
| 1117 | // branch is a tree first: stepping into its cells is what Tab |
| 1118 | // and Enter are for, and a carousel elsewhere on the screen is |
| 1119 | // not what the caret is on. |
| 1120 | if let Some |
| 1121 | branch: Some, |
| 1122 | open, |
| 1123 | .. |
| 1124 | = &here |
| 1125 | && *open != matches! |
| 1126 | |
| 1127 | let branch = branch.clone; |
| 1128 | self.view.fold; |
| 1129 | // The rows under it arrived or went away, so the stops |
| 1130 | // moved under the caret. Held to what is reachable now, for |
| 1131 | // the reason `prune` clamps it. |
| 1132 | let stops = self.reaches.len; |
| 1133 | self.view |
| 1134 | .focus_on; |
| 1135 | return Idle; |
| 1136 | |
| 1137 | // The table row under the caret next, when it has controls in |
| 1138 | // it. `27f2331e`: the row is one stop and these are the keys |
| 1139 | // that get inside it. Ahead of the region below because the |
| 1140 | // thing under the caret beats a search of the whole screen -- |
| 1141 | // see this module's header for what that costs. |
| 1142 | if let Some = &here |
| 1143 | && !inside.is_empty |
| 1144 | |
| 1145 | let steps = if matches! else ; |
| 1146 | self.view.step_inside; |
| 1147 | return Idle; |
| 1148 | |
| 1149 | // The region the caret is in, when that region shows one child |
| 1150 | // at a time, and otherwise the first one on the screen that |
| 1151 | // does. The fallback is not a convenience: a carousel's frames |
| 1152 | // are pictures, so there is nothing reachable inside one and |
| 1153 | // focus can never be in it. Without this the one widget that |
| 1154 | // asked for these keys could not be reached by them. |
| 1155 | if let Some = self.moving |
| 1156 | let steps = if matches! else ; |
| 1157 | let slot = slot.clone; |
| 1158 | self.view.show_by; |
| 1159 | // `dfbc88ce`. A tab's panel is a route, and the strip is |
| 1160 | // what calls it: the browser puts the address on the button |
| 1161 | // and htmx fires on the press, and this is the same press. |
| 1162 | // Only when the panel is empty, so pressing back to a tab |
| 1163 | // already read is free, which is what the reader means by |
| 1164 | // going back to it. |
| 1165 | if let Some = self |
| 1166 | .view |
| 1167 | .shown |
| 1168 | .and_then |
| 1169 | .and_then |
| 1170 | Region => panel.asked_for, |
| 1171 | _ => None, |
| 1172 | |
| 1173 | && let Some = Selfrequest_for |
| 1174 | |
| 1175 | return Call; |
| 1176 | |
| 1177 | |
| 1178 | Idle |
| 1179 | |
| 1180 | |
| 1181 | // The innermost thing first, all the way out: Escape steps out of |
| 1182 | // a table row, then closes what is on top, then goes back. Which is |
| 1183 | // what Escape means everywhere else it is bound. The short circuit |
| 1184 | // is what makes it one at a time: a press that left a row does not |
| 1185 | // also dismiss the overlay the row is on. |
| 1186 | Escape => |
| 1187 | if self.view.leave || self.dismiss |
| 1188 | Idle |
| 1189 | else |
| 1190 | self.back |
| 1191 | |
| 1192 | |
| 1193 | |
| 1194 | Backspace => |
| 1195 | if let Some = caret.as_ref.and_then |
| 1196 | self.view.backspace; |
| 1197 | // A delete asks nothing -- see `after_typing`, which a |
| 1198 | // delete does not reach -- and it can still take the |
| 1199 | // candidates away: they are about a value that has just |
| 1200 | // stopped earning them. |
| 1201 | self.unearned; |
| 1202 | |
| 1203 | Idle |
| 1204 | |
| 1205 | |
| 1206 | Enter => match caret |
| 1207 | Some |
| 1208 | action, |
| 1209 | confirm, |
| 1210 | over, |
| 1211 | asks, |
| 1212 | fills, |
| 1213 | copies, |
| 1214 | .. |
| 1215 | => |
| 1216 | // A control over an empty selection is drawn disabled |
| 1217 | // (`commit_line`), and this is the other half of saying so: |
| 1218 | // pressing it does nothing rather than calling a route that |
| 1219 | // will act on nothing and answer "0 tasks completed". The |
| 1220 | // focus ring still stops on it, deliberately -- the reach |
| 1221 | // walk reads the description and the ticks are the view's, |
| 1222 | // so skipping it here would leave the two disagreeing about |
| 1223 | // how many stops a screen has. |
| 1224 | if over.is_some && self.view.ticks.next.is_none |
| 1225 | return Idle; |
| 1226 | |
| 1227 | let mut payload = self.gathering; |
| 1228 | payload.absorb; |
| 1229 | self.deposit; |
| 1230 | // Before the call, matching `deposit` above and the other |
| 1231 | // two renderers: the local half of a press happens whether |
| 1232 | // or not anything is asked, and a copying act asks nothing. |
| 1233 | if let Some = copies |
| 1234 | return Copy; |
| 1235 | |
| 1236 | match confirm |
| 1237 | Some => |
| 1238 | self.asked = Some; |
| 1239 | Ask |
| 1240 | |
| 1241 | None => self.send, |
| 1242 | |
| 1243 | |
| 1244 | Some => |
| 1245 | let payload = self.view.submission |
| 1246 | &names, |
| 1247 | // Every described box, including one in a region that |
| 1248 | // does not apply right now: a browser sends the value |
| 1249 | // of a hidden input, and one description submitted on |
| 1250 | // two hosts has to send the same form. `079a011e`. |
| 1251 | &self |
| 1252 | .described |
| 1253 | .into_iter |
| 1254 | .map |
| 1255 | ., |
| 1256 | ; |
| 1257 | self.send |
| 1258 | |
| 1259 | // The controls of a question answered N times. `60d1753c`: |
| 1260 | // pressing one changes how many boxes there are and asks |
| 1261 | // nothing of any route, which is the third of the three things |
| 1262 | // the member is for. The caret stays where it is, so a reader |
| 1263 | // adding three slots presses add three times without walking |
| 1264 | // back to it. |
| 1265 | Some => |
| 1266 | match at |
| 1267 | Some => self.view.remove_slot, |
| 1268 | None => self.view.add_slot, |
| 1269 | |
| 1270 | // The stops moved under the caret: a slot arrived above the |
| 1271 | // add control, or one went away above it. Held to what is |
| 1272 | // reachable now, for the reason `prune` clamps it. |
| 1273 | let stops = self.reaches.len; |
| 1274 | self.view |
| 1275 | .focus_on; |
| 1276 | Idle |
| 1277 | |
| 1278 | // A field takes Enter and does nothing with it. A browser |
| 1279 | // submits the form around it, and doing that here would fire a |
| 1280 | // write from the first box the user finished typing in; the |
| 1281 | // submit is one Tab away and says what it does. |
| 1282 | Some | None => Idle, |
| 1283 | // A row of a live selection says how the press was meant, which |
| 1284 | // for Enter is always the plain reading: this row and nothing |
| 1285 | // else. Space below is the other one a terminal has. `1894e95d`. |
| 1286 | Some |
| 1287 | activate: Some, |
| 1288 | chosen: Some, |
| 1289 | .. |
| 1290 | => |
| 1291 | let action = action.clone; |
| 1292 | self.send |
| 1293 | |
| 1294 | Some => match other.enters |
| 1295 | Some => self.call, |
| 1296 | None => Idle, |
| 1297 | , |
| 1298 | , |
| 1299 | |
| 1300 | // The row's own key, so it takes `here` and not `caret`: a tick |
| 1301 | // belongs to the row and there is nothing in a cell that could hold |
| 1302 | // one. A caret that has stepped into a row therefore ticks nothing, |
| 1303 | // and Escape is one press away from the row that does. |
| 1304 | Char if !self.editing => match here |
| 1305 | // A tick is a write when the description says it is, and |
| 1306 | // staged selection when it does not. `toggle` first, because a |
| 1307 | // row carrying one has said the tick *is* the write and that |
| 1308 | // claim beats the screen's set. |
| 1309 | Some |
| 1310 | toggle: Some, |
| 1311 | .. |
| 1312 | => self.call, |
| 1313 | // Otherwise it joins or leaves the set the screen names. The |
| 1314 | // hole `5f2b8753` was filed for was here: this used to be |
| 1315 | // `Step::Idle`, so the box was drawn, the key was bound, and |
| 1316 | // pressing it did nothing. |
| 1317 | // |
| 1318 | // Still idle when a row names no value or the screen holds no |
| 1319 | // set, which is the same description bug one step earlier. A |
| 1320 | // key bound to nothing is what this stopped doing, so it does |
| 1321 | // not start doing it again by accepting a tick that cannot be |
| 1322 | // read back. |
| 1323 | Some |
| 1324 | ticked: Some, |
| 1325 | value: Some, |
| 1326 | .. |
| 1327 | if self.screen.selection.is_some => |
| 1328 | self.view.tick; |
| 1329 | Idle |
| 1330 | |
| 1331 | // The terminal's ctrl-click. A live selection is the app's, so |
| 1332 | // unlike a tick this does not write into the view: it calls the |
| 1333 | // row's own address and says the press meant "and also this |
| 1334 | // one", and the app answers with a screen where it is chosen. |
| 1335 | // `1894e95d`. |
| 1336 | // |
| 1337 | // After the tick arms, deliberately. A row carrying both has |
| 1338 | // said two contradictory things and the staged reading is the |
| 1339 | // older one; nothing in the tree does, and the order is what |
| 1340 | // decides it if anything ever does. |
| 1341 | Some |
| 1342 | activate: Some, |
| 1343 | chosen: Some, |
| 1344 | .. |
| 1345 | => self.send, |
| 1346 | _ => Idle, |
| 1347 | , |
| 1348 | |
| 1349 | Char => |
| 1350 | if let Some = caret.as_ref.and_then.cloned |
| 1351 | self.type_into; |
| 1352 | return self.after_typing; |
| 1353 | |
| 1354 | // Not in a field, so the key is a shortcut if any control on |
| 1355 | // the screen claimed it. `Act::key` is text rather than a |
| 1356 | // modelled chord, so this is a string comparison against what |
| 1357 | // the description wrote, and a name this renderer does not |
| 1358 | // understand simply never matches. |
| 1359 | let pressed = ch.to_string; |
| 1360 | let claimed = reaches.iter.find_map |
| 1361 | // A key reaching a commit control over an empty selection |
| 1362 | // is refused for the same reason a press on it is. |
| 1363 | Act |
| 1364 | action, |
| 1365 | key: Some, |
| 1366 | over, |
| 1367 | asks, |
| 1368 | fills, |
| 1369 | copies, |
| 1370 | .. |
| 1371 | if *key == pressed |
| 1372 | && => |
| 1373 | |
| 1374 | Some |
| 1375 | action.clone, |
| 1376 | over.clone, |
| 1377 | asks.clone, |
| 1378 | fills.clone, |
| 1379 | copies.clone, |
| 1380 | |
| 1381 | |
| 1382 | _ => None, |
| 1383 | ; |
| 1384 | match claimed |
| 1385 | Some => |
| 1386 | let mut payload = self.gathering; |
| 1387 | payload.absorb; |
| 1388 | self.deposit; |
| 1389 | // The bound key and Enter are one press said two ways, |
| 1390 | // so a copy reaches the host from either. |
| 1391 | if let Some = copies |
| 1392 | return Copy; |
| 1393 | |
| 1394 | self.send |
| 1395 | |
| 1396 | None => Idle, |
| 1397 | |
| 1398 | |
| 1399 | |
| 1400 | |
| 1401 | |
| 1402 | /// Put fresh contents wherever this names, and say whether anywhere did. |
| 1403 | /// |
| 1404 | /// The panel is not on the screen and is addressable all the same, so an |
| 1405 | /// answer aimed at it lands in the chrome. Asked in that order rather than |
| 1406 | /// the other way round because the panel's id is the app's and a screen |
| 1407 | /// could carry a region with the same name, and the app's panel is the one |
| 1408 | /// that outlives the screen. |
| 1409 | |
| 1410 | if self.chrome.panel.is_some |
| 1411 | return self.chrome.replace; |
| 1412 | |
| 1413 | self.screen.replace |
| 1414 | |
| 1415 | |
| 1416 | /// Put what the router answered onto the screen. |
| 1417 | /// |
| 1418 | /// Answers with a follow-up request when the response says to go somewhere |
| 1419 | /// else, which the host performs the same way it performed the first one. |
| 1420 | /// `request` is what was asked, because whether an answer is a place is |
| 1421 | /// derived from it: a read that answered a whole screen is somewhere you |
| 1422 | /// can come back to, and a write is not. |
| 1423 | |
| 1424 | let Response |
| 1425 | outcome, |
| 1426 | notice, |
| 1427 | address, |
| 1428 | invalidates, |
| 1429 | = response; |
| 1430 | self.saying = notice.or; |
| 1431 | |
| 1432 | // Whatever was outstanding has been answered. Only the request that went |
| 1433 | // out clears it: an answer to something else arriving first leaves the |
| 1434 | // control locked, which is what it means for that control to still be |
| 1435 | // waiting. |
| 1436 | if self |
| 1437 | .outstanding |
| 1438 | .as_ref |
| 1439 | .is_some_and |
| 1440 | |
| 1441 | self.outstanding = None; |
| 1442 | self.view.awaiting; |
| 1443 | |
| 1444 | |
| 1445 | match outcome |
| 1446 | // Invalidations are not applied to a whole screen, matching what an |
| 1447 | // HTTP host does with them and for the same reason: every region is |
| 1448 | // being replaced already, so naming one of them again says nothing |
| 1449 | // the new screen does not. |
| 1450 | // Over what is already there. The layer underneath is put away |
| 1451 | // whole -- its screen and the view holding everything the user did |
| 1452 | // to it -- and comes back untouched when the overlay is dismissed. |
| 1453 | // |
| 1454 | // `remember` is deliberately not called: an overlay is not a place, |
| 1455 | // so history is left exactly as it was and Escape from the overlay |
| 1456 | // reveals rather than navigates. |
| 1457 | // The candidates for the box being typed into. Not a region and |
| 1458 | // not a screen: the list belongs to a control, so it lands on the |
| 1459 | // view beside what has been typed rather than in the description. |
| 1460 | Suggestions => |
| 1461 | self.view.suggested; |
| 1462 | None |
| 1463 | |
| 1464 | Over => |
| 1465 | self.layer; |
| 1466 | None |
| 1467 | |
| 1468 | // Over what is already there, at something on it. The same layering, |
| 1469 | // plus one decision: where the box goes. |
| 1470 | // |
| 1471 | // The anchor is resolved against the screen being covered rather |
| 1472 | // than against the one arriving, because that is the screen it |
| 1473 | // names -- a menu is anchored to what was showing when it opened. |
| 1474 | // `Screen::anchors` is where the walk lives, so all three renderers |
| 1475 | // answer the question the same way. |
| 1476 | // |
| 1477 | // An anchor naming nothing on that screen falls back to the overlay |
| 1478 | // box. That is a description bug and gets a description bug's |
| 1479 | // treatment everywhere else in this crate: the screen still draws, |
| 1480 | // and the loss is the placement rather than the menu. |
| 1481 | Anchored => |
| 1482 | let laid = if self.screen.anchors |
| 1483 | Anchored |
| 1484 | low: self.subject_is_high, |
| 1485 | |
| 1486 | else |
| 1487 | Over |
| 1488 | ; |
| 1489 | self.layer; |
| 1490 | None |
| 1491 | |
| 1492 | // A whole screen replaces everything, including any overlay open |
| 1493 | // over it. A route that answers with a screen is a navigation, and |
| 1494 | // navigating with a palette still floating over the destination is |
| 1495 | // the state nobody asked for. |
| 1496 | Screen => |
| 1497 | // Arriving where you already are is a refresh rather than a |
| 1498 | // navigation, and the difference is the whole of what the user |
| 1499 | // has done to the screen. `reset` and `seed` below are both |
| 1500 | // *arrival* behaviour, so a refresh runs neither: otherwise a |
| 1501 | // reload clears the field being typed into and puts back an |
| 1502 | // untick the moment anything redraws. |
| 1503 | let refreshed = self.here.as_ref == Some; |
| 1504 | self.under.clear; |
| 1505 | self.over = None; |
| 1506 | self.laid = None; |
| 1507 | self.remember; |
| 1508 | self.screen = screen; |
| 1509 | self.reraise; |
| 1510 | if !refreshed |
| 1511 | self.view.reset; |
| 1512 | // The rows a new screen says are already ticked. After this |
| 1513 | // the user's ticks are the truth, which is why it is applied |
| 1514 | // once on arrival rather than read on every draw. |
| 1515 | self.view.seed; |
| 1516 | // And where the caret starts, for the same reason and at the |
| 1517 | // same moment: a refresh runs neither, or a reload would |
| 1518 | // snatch the caret back out of whatever is being typed into. |
| 1519 | self.open_caret; |
| 1520 | |
| 1521 | self.announce; |
| 1522 | None |
| 1523 | |
| 1524 | Fragment => |
| 1525 | // A region that is not there is the description bug |
| 1526 | // `Screen::replace` describes, and a terminal can say so |
| 1527 | // rather than swallowing it: the region it named is gone, and |
| 1528 | // drawing nothing would look like a control that does nothing. |
| 1529 | // |
| 1530 | // The slots the answer invalidated go in the same way. On a |
| 1531 | // terminal that is the whole of what invalidation means: the |
| 1532 | // next frame redraws everything, so putting the new contents |
| 1533 | // on the screen is putting them in front of the user. What a |
| 1534 | // webview needs an out-of-band swap for, this gets for free. |
| 1535 | let mut missing: = Vecnew; |
| 1536 | if !self.land |
| 1537 | missing.push; |
| 1538 | |
| 1539 | for stale in invalidates |
| 1540 | if !self.land |
| 1541 | missing.push; |
| 1542 | |
| 1543 | |
| 1544 | if !missing.is_empty |
| 1545 | // One message naming all of them, rather than a banner per |
| 1546 | // region where only the last would survive. |
| 1547 | let named = missing |
| 1548 | .iter |
| 1549 | .map |
| 1550 | . |
| 1551 | .join; |
| 1552 | let subject = if missing.len == 1 else ; |
| 1553 | self.saying = Some |
| 1554 | kind: Banner, |
| 1555 | tone: Danger, |
| 1556 | text: format!, |
| 1557 | undo: None, |
| 1558 | ; |
| 1559 | |
| 1560 | self.view.prune; |
| 1561 | self.announce; |
| 1562 | None |
| 1563 | |
| 1564 | // The work was handed off and the region is waiting on it. A |
| 1565 | // retained-screen host needs no markup for this: the slot's |
| 1566 | // readiness is the axis, `region::draw` already writes "Loading" |
| 1567 | // from it, and the next thing the region is told puts content back. |
| 1568 | // |
| 1569 | // A region that is not there is the same description bug a fragment |
| 1570 | // naming one is, and gets the same banner. Said once here rather |
| 1571 | // than folded into the arm above: that one collects misses across |
| 1572 | // the answer and its invalidations, and there is only ever one |
| 1573 | // region here. |
| 1574 | // |
| 1575 | // The message goes in as a stand-in and this renderer will not draw |
| 1576 | // it, because a pending region returns before it reaches the body. |
| 1577 | // Applied anyway rather than dropped: what is on the screen is the |
| 1578 | // description, and a host that later draws a pending body — or a |
| 1579 | // test asking what the region holds — should find what the route |
| 1580 | // said rather than what was there before. |
| 1581 | Started => |
| 1582 | if !self.screen.started |
| 1583 | self.saying = Some |
| 1584 | kind: Banner, |
| 1585 | tone: Danger, |
| 1586 | text: format!, |
| 1587 | undo: None, |
| 1588 | ; |
| 1589 | |
| 1590 | self.view.prune; |
| 1591 | self.announce; |
| 1592 | None |
| 1593 | |
| 1594 | Goto => match self.call |
| 1595 | Call => Some, |
| 1596 | // An external destination is the host's to open, and there is |
| 1597 | // nothing to come back for. |
| 1598 | _ => None, |
| 1599 | , |
| 1600 | // A file for the host to put somewhere. Not a screen, not a |
| 1601 | // region, and not a place: what is showing stays showing, and the |
| 1602 | // answer leaves by `handed` rather than by the return value. |
| 1603 | File => |
| 1604 | self.handed = Some |
| 1605 | name: safe_file_name, |
| 1606 | kind, |
| 1607 | bytes, |
| 1608 | ; |
| 1609 | None |
| 1610 | |
| 1611 | // A place for the host to go and find. Not a screen, not a region |
| 1612 | // and not a place in the other sense either: what is showing stays |
| 1613 | // showing, and the ask leaves by `locating`. |
| 1614 | Locate => |
| 1615 | self.locating = Some; |
| 1616 | None |
| 1617 | |
| 1618 | |
| 1619 | |
| 1620 | |
| 1621 | /// Take the file the last answer handed over, if it handed one over. |
| 1622 | /// |
| 1623 | /// Called after [`apply`](Self::apply), the way |
| 1624 | /// [`refreshes`](Self::refreshes) is called after a draw: this drains, so a |
| 1625 | /// host that calls it twice gets the file once. A host that never calls it |
| 1626 | /// silently drops every download, which is why the file arrives here rather |
| 1627 | /// than being written where the runtime cannot be tested. |
| 1628 | /// |
| 1629 | /// What to do with it is the ruling's: write it into the working directory |
| 1630 | /// under [`Handed::name`]. See [`Handed`]. |
| 1631 | |
| 1632 | self.handed.take |
| 1633 | |
| 1634 | |
| 1635 | /// Take the place the last answer asked for, if it asked for one. |
| 1636 | /// |
| 1637 | /// [`handed`](Self::handed)'s twin: drained after |
| 1638 | /// [`apply`](Self::apply), once per ask, and dropped silently by a host |
| 1639 | /// that never calls it. Build the follow-up call with |
| 1640 | /// [`Locating::answered`] once the place is known and hand the answer back |
| 1641 | /// to [`apply`](Self::apply); a reader who backs out is not an answer and |
| 1642 | /// nothing goes to the router. |
| 1643 | /// |
| 1644 | /// # A terminal has no picker, and that is not a gap |
| 1645 | /// |
| 1646 | /// The other renderers open a native dialog. A terminal has none, and the |
| 1647 | /// honest reading is the one [`Handed`] already takes on this host: the |
| 1648 | /// place is the working directory unless the app has somewhere better, and |
| 1649 | /// a host that wants to ask can read [`Locating::prompt`] and ask however |
| 1650 | /// it asks — a prompt line, a picker of its own, an argument it was started |
| 1651 | /// with. What it must not do is guess and stay quiet, because the handle it |
| 1652 | /// answers with is where a long export is about to write hundreds of files. |
| 1653 | /// |
| 1654 | /// # The save shape, on a host with no save dialog |
| 1655 | /// |
| 1656 | /// [`Sought::Save`](quasi_router::Sought::Save) carries the name the |
| 1657 | /// dialog would have opened with, which is exactly what a terminal needs |
| 1658 | /// to answer without one: the working directory joined to |
| 1659 | /// [`safe_file_name`] of that name, said out loud so the reader can see |
| 1660 | /// where it went. The suggestion is frequently built from something they |
| 1661 | /// typed earlier, so it goes through the sanitiser here for the reason it |
| 1662 | /// does on a download: this host writes beside the process, and |
| 1663 | /// `../../.ssh/authorized_keys` is a name a description can hold. A host |
| 1664 | /// that would rather ask reads the name as the default in its own prompt. |
| 1665 | /// |
| 1666 | /// # One call, whatever was picked |
| 1667 | /// |
| 1668 | /// [`Locating::answered`] takes every |
| 1669 | /// [`Picked`](quasi_router::Picked) at once and builds one request, so an |
| 1670 | /// ask for several files is answered once with all of them. A host that |
| 1671 | /// calls it per file makes N requests out of one ask and turns a batched |
| 1672 | /// import into N imports. |
| 1673 | |
| 1674 | self.locating.take |
| 1675 | |
| 1676 | |
| 1677 | /// Go back, if there is anywhere to go. |
| 1678 | /// A key as the text a [`Chrome`] binding names it by. |
| 1679 | /// |
| 1680 | /// The same string comparison `Act::key` gets, for the same reason: the |
| 1681 | /// vocabulary of keys is the host's, and this host's names are these. A |
| 1682 | /// modifier this renderer cannot receive is a name that never matches, |
| 1683 | /// which is what a binding for another host should do here. |
| 1684 | |
| 1685 | Some |
| 1686 | Char => ch.to_string, |
| 1687 | Enter => "enter".into, |
| 1688 | Escape => "escape".into, |
| 1689 | Tab => "tab".into, |
| 1690 | BackTab => "backtab".into, |
| 1691 | Up => "up".into, |
| 1692 | Down => "down".into, |
| 1693 | PageUp => "pageup".into, |
| 1694 | PageDown => "pagedown".into, |
| 1695 | Backspace => "backspace".into, |
| 1696 | Left => "left".into, |
| 1697 | Right => "right".into, |
| 1698 | |
| 1699 | |
| 1700 | |
| 1701 | /// The region the arrow keys move, if the screen has one. |
| 1702 | /// |
| 1703 | /// The one the caret is in when that region shows one child at a time, and |
| 1704 | /// otherwise the first such region in draw order. Two rules rather than one |
| 1705 | /// because focus is not always a usable answer here: a carousel holds |
| 1706 | /// pictures, nothing in it is reachable, and a rule that only ever asked |
| 1707 | /// where the caret was would leave the widget that wanted these keys unable |
| 1708 | /// to be reached by them. |
| 1709 | /// |
| 1710 | /// A screen with two of these and no focus in either moves the first, which |
| 1711 | /// is arbitrary and is said out loud rather than hidden. Nothing in the tree |
| 1712 | /// has two yet; the screen that does is the one that will want a reachable |
| 1713 | /// control instead, and that is a `Spot` rather than a rule here. |
| 1714 | |
| 1715 | let here = reaches |
| 1716 | .get |
| 1717 | .and_then |
| 1718 | .filter; |
| 1719 | here.or_else |
| 1720 | |
| 1721 | |
| 1722 | /// This slot or the first under it that shows one child at a time. |
| 1723 | |
| 1724 | if slot.showing.selective |
| 1725 | return Some; |
| 1726 | |
| 1727 | slot.body.iter.find_map |
| 1728 | Region => Selfselective, |
| 1729 | _ => None, |
| 1730 | |
| 1731 | |
| 1732 | |
| 1733 | /// The slot under this address, anywhere on the screen. |
| 1734 | |
| 1735 | self.screen.slots.iter.find_map |
| 1736 | |
| 1737 | |
| 1738 | /// Close the overlay on top, if there is one. |
| 1739 | /// |
| 1740 | /// The layer underneath comes back exactly as it was left: its own focus, |
| 1741 | /// its own edits, its own scroll. That is the whole reason a layer carries |
| 1742 | /// its own [`View`], and history is not touched because an overlay was |
| 1743 | /// never a place. |
| 1744 | |
| 1745 | match self.under.pop |
| 1746 | Some => |
| 1747 | self.screen = layer.screen; |
| 1748 | self.view = layer.view; |
| 1749 | // The outer overlay's identity, or `None` back on the base |
| 1750 | // screen. Restored rather than cleared, so dismissing a confirm |
| 1751 | // raised over a palette leaves the palette still refusing to |
| 1752 | // stack itself. |
| 1753 | self.over = layer.over; |
| 1754 | self.laid = layer.laid; |
| 1755 | self.reraise; |
| 1756 | true |
| 1757 | |
| 1758 | None => false, |
| 1759 | |
| 1760 | |
| 1761 | |
| 1762 | /// Go back, if there is anywhere to go. |
| 1763 | /// |
| 1764 | /// What [`Destination::Back`](quasi_router::Destination::Back) is answered |
| 1765 | /// with, and what this runtime's own Escape reaches once there is no |
| 1766 | /// overlay left to dismiss. `Step::Idle` at the bottom of the stack: the |
| 1767 | /// first screen is not somewhere you arrived at. |
| 1768 | |
| 1769 | match self.history.pop |
| 1770 | Some => |
| 1771 | self.here = Some; |
| 1772 | Call |
| 1773 | |
| 1774 | None => Idle, |
| 1775 | |
| 1776 | |
| 1777 | |
| 1778 | /// Note where we were, before we leave it. |
| 1779 | /// |
| 1780 | /// The derivation the response's own documentation describes: a read that |
| 1781 | /// answered a screen is a place, everything else is not, and |
| 1782 | /// [`Address`](quasi_router::Address) is the override for the two cases the |
| 1783 | /// derivation cannot reach. |
| 1784 | /// |
| 1785 | /// Arriving where you already are is not leaving anywhere, which is what the |
| 1786 | /// first guard is for: [`reload`](Self::reload) answers the request that |
| 1787 | /// produced the screen showing, so without it every refresh would push a |
| 1788 | /// duplicate of the current place and `back` would walk through a stack of |
| 1789 | /// the screen it is already on. |
| 1790 | |
| 1791 | if self.here.as_ref == Some |
| 1792 | return; |
| 1793 | |
| 1794 | let place = match address |
| 1795 | Some => true, |
| 1796 | Some => false, |
| 1797 | Some => |
| 1798 | self.here = Some; |
| 1799 | return; |
| 1800 | |
| 1801 | None => request.method == Get, |
| 1802 | ; |
| 1803 | if place && let Some = self.here.replace |
| 1804 | self.history.push; |
| 1805 | |
| 1806 | |
| 1807 | |
| 1808 | /// Put whatever the response wanted said onto the screen it belongs to. |
| 1809 | |
| 1810 | let Some = self.saying.take else |
| 1811 | return; |
| 1812 | ; |
| 1813 | // The way back the response offered, as the control it becomes here. |
| 1814 | // `bde35298`: this used to be dropped, because `Node::Notice` had |
| 1815 | // nowhere to hang a control and the comment saying so was the whole of |
| 1816 | // the answer. The member exists now. |
| 1817 | let act = message.undo_act; |
| 1818 | let Message |
| 1819 | kind, tone, text, .. |
| 1820 | = message; |
| 1821 | if kind.transient |
| 1822 | self.raised.push; |
| 1823 | |
| 1824 | self.screen.notices.push |
| 1825 | kind, |
| 1826 | tone, |
| 1827 | text, |
| 1828 | act, |
| 1829 | ; |
| 1830 | |
| 1831 | |
| 1832 | /// Type into a field, honouring what the description says it will take. |
| 1833 | |
| 1834 | if matches! |
| 1835 | // A checkbox holds one of two values, so a key does not type into |
| 1836 | // it: any key flips it, which is what space does to one in a |
| 1837 | // browser and is the only sentence a box with two states can hear. |
| 1838 | let ticked = self.view.typed == SELECTED; |
| 1839 | let next = if ticked |
| 1840 | Stringnew |
| 1841 | else |
| 1842 | SELECTED.to_string |
| 1843 | ; |
| 1844 | self.view.set; |
| 1845 | return; |
| 1846 | |
| 1847 | |
| 1848 | // `Field::max_length` is a rule the description carries and every |
| 1849 | // renderer emits in its host's idiom. A browser stops accepting |
| 1850 | // characters, and so does this. |
| 1851 | if let Some = field.max_length |
| 1852 | && self.view.typed.chars.count >= limit as usize |
| 1853 | |
| 1854 | return; |
| 1855 | |
| 1856 | self.view.push; |
| 1857 | |
| 1858 | |
| 1859 | /// What a keystroke in a field costs: a write when the field writes as it |
| 1860 | /// changes, an ask when it consults something about what is being typed. |
| 1861 | /// |
| 1862 | /// A write beats a consult when a field carries both, because the write is |
| 1863 | /// what the keystroke *means* and the ask is about it. One keystroke |
| 1864 | /// produces one [`Step`], so the consult goes unasked in that case; no |
| 1865 | /// measured site carries both, and inventing a queue for a shape nothing |
| 1866 | /// has asked for is how this crate would grow a scheduler. |
| 1867 | /// |
| 1868 | /// The regions holding the box ask under the same rule and lose it the same |
| 1869 | /// way, which is worth stating because it is the less obvious half: a dial |
| 1870 | /// that writes on its own inside a panel that recomputes writes, and the |
| 1871 | /// panel is left to the next keystroke. The write cannot join the delayed |
| 1872 | /// set instead -- [`Step::CallAfter`] says a later one replaces an |
| 1873 | /// outstanding one whole, so a write parked in there is a write a keystroke |
| 1874 | /// can cancel. |
| 1875 | /// The write a field owes when the caret leaves it, if it owes one. |
| 1876 | /// |
| 1877 | /// `Field::writes` means the change is complete, and on a terminal a value |
| 1878 | /// built up character by character is complete when the reader walks off |
| 1879 | /// it. The same moment a browser raises `change`, which is what `quasi- |
| 1880 | /// webview` emits and what the pick site above already calls "what a |
| 1881 | /// settled value costs". |
| 1882 | /// |
| 1883 | /// `None` when there is nothing to say: not a field, no `changes`, or a |
| 1884 | /// value nobody altered. Walking through a form must not write every box it |
| 1885 | /// passes. |
| 1886 | |
| 1887 | let field = here.and_then?; |
| 1888 | if field.kind.offers_options || field.kind.takes_files || field.kind.offers_themes |
| 1889 | // Already written when it changed, for the reason `after_typing` |
| 1890 | // gives: one interaction, one whole answer. |
| 1891 | return None; |
| 1892 | |
| 1893 | let action = field.writes.clone?; |
| 1894 | let name = field.name.clone; |
| 1895 | // Seeded from the description when the box is untouched, which is what |
| 1896 | // is showing in it. `unwrap_or_default` here read an untouched box as |
| 1897 | // empty, so walking through a form wrote every prefilled field it |
| 1898 | // passed -- caught by `walking_through_a_box_nobody_altered_writes_nothing`. |
| 1899 | let described = field.value.as_deref.unwrap_or_default; |
| 1900 | let value = self.view.edit.unwrap_or.to_string; |
| 1901 | if !self.view.unwritten |
| 1902 | return None; |
| 1903 | |
| 1904 | self.view.wrote; |
| 1905 | let mut payload = new; |
| 1906 | payload.insert; |
| 1907 | Some |
| 1908 | |
| 1909 | |
| 1910 | |
| 1911 | let Some = here.and_then else |
| 1912 | return Idle; |
| 1913 | ; |
| 1914 | let name = field.name.clone; |
| 1915 | |
| 1916 | // `8032fe61`. A keystroke is not a write. `Field::writes` means the |
| 1917 | // change is complete, which is what `quasi-webview` has always emitted |
| 1918 | // it as -- `hx-trigger="change"`, raised on blur or Enter for a text |
| 1919 | // control -- and this fired on every character, so a search box was one |
| 1920 | // request per letter and a bounded number posted every value on the way |
| 1921 | // to the one being typed. |
| 1922 | // |
| 1923 | // What is left of the old shape is exactly the controls a browser fires |
| 1924 | // `change` for at once: a value chosen in one go was complete when it |
| 1925 | // changed. Everything the reader builds up waits for `leaving`. |
| 1926 | // A theme is picked in one gesture, so it belongs with the options |
| 1927 | // and not with the values a reader builds up character by character. |
| 1928 | let built_up = ! |
| 1929 | || field.kind.takes_files |
| 1930 | || field.kind.offers_themes; |
| 1931 | if let Some = field.writes.clone.filter |
| 1932 | let value = self.view.edit.unwrap_or_default.to_string; |
| 1933 | let mut payload = new; |
| 1934 | payload.insert; |
| 1935 | return self.send; |
| 1936 | |
| 1937 | |
| 1938 | // Asking about the value carries the value and nothing else. No ticks: |
| 1939 | // a consult is a question about this box, not a control over a set. |
| 1940 | // The question the field owns is asked alongside the ones it merely |
| 1941 | // asks, and it is asked the same way: the answer differs, not the |
| 1942 | // asking. Dropping under its floor closes the list rather than leaving |
| 1943 | // candidates for a value that no longer earns them. |
| 1944 | let questions: = field.suggests.iter.chain.collect; |
| 1945 | self.unearned; |
| 1946 | |
| 1947 | let value = self.view.edit.unwrap_or_default.to_string; |
| 1948 | let mut asks = Vecnew; |
| 1949 | for consult in questions |
| 1950 | // Too little to ask about yet, and asked per question rather than |
| 1951 | // for the field: the two questions about one box carry their own |
| 1952 | // floors, and MNW's do differ from each other in wait even where |
| 1953 | // the floors agree. |
| 1954 | if !consult.asks_about |
| 1955 | continue; |
| 1956 | |
| 1957 | // What else rides along, which is the half that made discover's |
| 1958 | // results route unsayable: it answers about the current filters, |
| 1959 | // and a question asked without them answers about a screen the |
| 1960 | // user is not looking at. |
| 1961 | let mut payload = self.contributed; |
| 1962 | payload.insert; |
| 1963 | match self.send |
| 1964 | Call => asks.push |
| 1965 | request, |
| 1966 | after: consult.after, |
| 1967 | , |
| 1968 | // A consult that names somewhere outside the app is not a |
| 1969 | // question this runtime can wait on. `Self::send` already |
| 1970 | // answers for it, and delaying it would be a delay before |
| 1971 | // opening a browser. |
| 1972 | other => return other, |
| 1973 | |
| 1974 | |
| 1975 | |
| 1976 | // The regions this box sits in, each asking about the whole set of |
| 1977 | // dials it contains rather than about this one value. `cb62a9dc`: on a |
| 1978 | // browser the question is one element's and the values are gathered by |
| 1979 | // containment; here the containment is walked instead, and the two |
| 1980 | // hosts gather the same set because `Slot::questions` is the only walk |
| 1981 | // either of them reads. |
| 1982 | match self.region_asks |
| 1983 | Ok => asks.extend, |
| 1984 | Err => return other, |
| 1985 | |
| 1986 | |
| 1987 | // `Idle` and not an empty delayed call: nothing is outstanding here |
| 1988 | // — the wait lives with the host, which is what keeps a clock out |
| 1989 | // of this crate — so a keystroke under every floor costs nothing |
| 1990 | // and deleting back under them asks nothing. |
| 1991 | if asks.is_empty |
| 1992 | return Idle; |
| 1993 | |
| 1994 | CallAfter |
| 1995 | |
| 1996 | |
| 1997 | /// What the regions holding this box ask when it moves. |
| 1998 | /// |
| 1999 | /// [`Slot::consults`](quasi_router::Slot::consults). Every region on the |
| 2000 | /// screen that both carries a question of its own and contains this field, |
| 2001 | /// at any depth, which is what makes a panel nested three groups deep |
| 2002 | /// recompute from a dial in one of them. |
| 2003 | /// |
| 2004 | /// The floor is read against the value that moved, never against the |
| 2005 | /// gathered set, which is [`Consult::asks_about`]'s own rule and the same |
| 2006 | /// thing the browser's `event.target.value` filter says. |
| 2007 | /// |
| 2008 | /// `Err` carries a step that is not a question this runtime can wait on -- |
| 2009 | /// an address outside the app -- exactly as the field's own consults hand |
| 2010 | /// one back. |
| 2011 | |
| 2012 | // Read whole before anything is sent, because sending takes the |
| 2013 | // runtime mutably and the questions are read out of the screen it |
| 2014 | // holds. What is kept is the two small things a question needs -- the |
| 2015 | // consult and the values -- rather than the regions themselves, so a |
| 2016 | // keystroke does not clone a subtree. |
| 2017 | let mut asking: = Vecnew; |
| 2018 | for slot in self.screen.consulting |
| 2019 | let inside = slot.questions; |
| 2020 | if !inside.iter.any |
| 2021 | continue; |
| 2022 | |
| 2023 | // The dials, read the way a submit reads a form: what the reader |
| 2024 | // typed, falling back to what the description offered, so an |
| 2025 | // untouched box still sends what it is showing. |
| 2026 | let mut held = new; |
| 2027 | for field in inside |
| 2028 | let value = self |
| 2029 | .view |
| 2030 | .edit |
| 2031 | .map |
| 2032 | .or_else; |
| 2033 | if let Some = value |
| 2034 | held = held.with; |
| 2035 | |
| 2036 | |
| 2037 | for consult in &slot.consults |
| 2038 | if !consult.asks_about |
| 2039 | continue; |
| 2040 | |
| 2041 | asking.push; |
| 2042 | |
| 2043 | |
| 2044 | |
| 2045 | let mut asks = Vecnew; |
| 2046 | for in asking |
| 2047 | // What rides along from outside the region goes in first, so a dial |
| 2048 | // inside it wins where a name sits on both sides: the region's own |
| 2049 | // contents are what the question is about. |
| 2050 | let mut payload = self.contributed; |
| 2051 | for in held.iter |
| 2052 | payload = payload.with; |
| 2053 | |
| 2054 | match self.send |
| 2055 | Call => asks.push |
| 2056 | request, |
| 2057 | after: consult.after, |
| 2058 | , |
| 2059 | other => return Err, |
| 2060 | |
| 2061 | |
| 2062 | Ok |
| 2063 | |
| 2064 | |
| 2065 | /// Close an open list whose value no longer clears the floor. |
| 2066 | /// |
| 2067 | /// The floor is the description's answer to "is there enough here to be |
| 2068 | /// worth asking about", and candidates left over a value that no longer |
| 2069 | /// clears it are an answer to a question the field would not ask now. |
| 2070 | |
| 2071 | let Some = field.suggests.as_ref else |
| 2072 | return; |
| 2073 | ; |
| 2074 | let value = self.view.edit.unwrap_or_default.to_string; |
| 2075 | if !owned.asks_about |
| 2076 | self.view.unsuggest; |
| 2077 | |
| 2078 | |
| 2079 | |
| 2080 | /// The values other controls contribute to a question, by name. |
| 2081 | /// |
| 2082 | /// [`Consult::sends`](quasi_router::Consult::sends). What the user has |
| 2083 | /// typed, falling back to what the description offered, which is the same |
| 2084 | /// order a submit reads them in — an untouched filter still sends what it |
| 2085 | /// is showing. |
| 2086 | /// |
| 2087 | /// A name nothing on the screen carries contributes nothing rather than an |
| 2088 | /// empty value, so a route can tell "not on this screen" from "on it and |
| 2089 | /// blank". That is the miss `Consult::sends` documents, seen from the one |
| 2090 | /// host where it is cheap to notice. |
| 2091 | |
| 2092 | let mut params = new; |
| 2093 | if names.is_empty |
| 2094 | return params; |
| 2095 | |
| 2096 | let spots = self.described; |
| 2097 | for name in names |
| 2098 | let described = spots.iter.find_map |
| 2099 | Field if &field.name == name => Some, |
| 2100 | _ => None, |
| 2101 | ; |
| 2102 | let Some = described else ; |
| 2103 | let value = self |
| 2104 | .view |
| 2105 | .edit |
| 2106 | .map |
| 2107 | .or_else; |
| 2108 | if let Some = value |
| 2109 | params = params.with; |
| 2110 | |
| 2111 | |
| 2112 | params |
| 2113 | |
| 2114 | |
| 2115 | /// The ticks a control acting over a selection sends with its call. |
| 2116 | /// |
| 2117 | /// Empty for a control that names no selection. A control that names one |
| 2118 | /// sends the whole set, whatever it called it: see [`Act::over`] for why |
| 2119 | /// the name is not matched against the screen's, which is that a webview |
| 2120 | /// rendering a fragment has no screen to match it against and the two |
| 2121 | /// hosts would then disagree about a typo. |
| 2122 | /// |
| 2123 | /// Empty is also what a control over a set nobody ticked sends, and the two |
| 2124 | /// are deliberately the same. A handler receives a bulk action over |
| 2125 | /// nothing, which is a case it has to handle regardless. |
| 2126 | /// |
| 2127 | /// [`Act::over`]: quasi_router::Act::over |
| 2128 | |
| 2129 | let mut payload = new; |
| 2130 | if over.is_some |
| 2131 | for value in self.view.ticks |
| 2132 | payload.insert; |
| 2133 | |
| 2134 | |
| 2135 | payload |
| 2136 | |
| 2137 | |
| 2138 | /// Put the act's value into the box it named. |
| 2139 | /// |
| 2140 | /// [`Act::fills`] names a field on this screen and the renderer decides |
| 2141 | /// where in it the value lands; here that is the end of what is already |
| 2142 | /// there. A terminal has no caret inside a field to insert at — `d52884b0` |
| 2143 | /// is why the view holds none — and the vocabulary calls appending correct |
| 2144 | /// rather than a fallback, because a description names a destination and |
| 2145 | /// never a position. |
| 2146 | /// |
| 2147 | /// After the payload is gathered, deliberately. A webview's htmx listener |
| 2148 | /// sits on the control and the fill script on the document, so there the |
| 2149 | /// press sends what the boxes held before it; doing it in the other order |
| 2150 | /// here would make one description send two different things on two hosts. |
| 2151 | /// |
| 2152 | /// From what the box is showing rather than from nothing: what has been |
| 2153 | /// typed if anything has, and otherwise what the description offered. A |
| 2154 | /// deposit that started from an empty string would silently discard the |
| 2155 | /// draft it was meant to add to, which is the exact defect the member was |
| 2156 | /// filed to stop the server-side workaround causing. |
| 2157 | /// |
| 2158 | /// A name no field on the screen carries writes an edit nothing reads, |
| 2159 | /// which is what a webview does with the same description: nothing is |
| 2160 | /// deposited and the act's own action still runs. A description naming a |
| 2161 | /// box that is not there is a description bug rather than a renderer's. |
| 2162 | /// |
| 2163 | /// [`Act::fills`]: quasi_router::Act::fills |
| 2164 | |
| 2165 | let Some = fill else |
| 2166 | return; |
| 2167 | ; |
| 2168 | let described = self.described; |
| 2169 | let offered = described.iter.find_map |
| 2170 | Field if field.name == fill.field => field.value.as_deref, |
| 2171 | _ => None, |
| 2172 | ; |
| 2173 | let mut value = self.view.showing.to_owned; |
| 2174 | value.push_str; |
| 2175 | self.view.set; |
| 2176 | |
| 2177 | |
| 2178 | /// The values a control asked for before it fired, by name. |
| 2179 | /// |
| 2180 | /// [`Act::asks`]. Read exactly as a submit reads a form's boxes, through |
| 2181 | /// the same view: an untouched box sends what the description offered, and |
| 2182 | /// an unticked checkbox sends nothing. A control that asked for nothing |
| 2183 | /// sends nothing, which is nearly all of them. |
| 2184 | /// |
| 2185 | /// [`Act::asks`]: quasi_router::Act::asks |
| 2186 | |
| 2187 | if names.is_empty |
| 2188 | return new; |
| 2189 | |
| 2190 | let spots: = self |
| 2191 | .described |
| 2192 | .into_iter |
| 2193 | .map |
| 2194 | .collect; |
| 2195 | self.view.submission |
| 2196 | |
| 2197 | |
| 2198 | /// An action as something the host can ask. |
| 2199 | |
| 2200 | self.send |
| 2201 | |
| 2202 | |
| 2203 | /// The request an action makes, or nothing when it names somewhere outside |
| 2204 | /// the app. |
| 2205 | /// |
| 2206 | /// Carries the action's own payload and the view it was offered under. What |
| 2207 | /// a control gathers on top of that is `send`'s, because only a press |
| 2208 | /// happens next to a selection. |
| 2209 | |
| 2210 | let path = action.destination.route?; |
| 2211 | Some |
| 2212 | method: action.method, |
| 2213 | path: path.to_string, |
| 2214 | captures: new, |
| 2215 | payload: action.params.clone, |
| 2216 | carried: action.carried.clone, |
| 2217 | |
| 2218 | |
| 2219 | |
| 2220 | /// An action, plus values the control is sending that are not on it. |
| 2221 | |
| 2222 | // Nothing to ask and nowhere to send anyone. This renderer is |
| 2223 | // `Renderer::Client`: it holds what it draws and redraws it from |
| 2224 | // memory, so whatever the local action names is something it already |
| 2225 | // does natively, and the mark tells it nothing it did not know. |
| 2226 | // |
| 2227 | // Handled rather than left to the guard below, which reads `route()` as |
| 2228 | // "not a route, therefore somewhere outside" and would hand the host an |
| 2229 | // empty address to open. `210574ca`. |
| 2230 | if action.destination.is_local |
| 2231 | return Idle; |
| 2232 | |
| 2233 | // Wherever the reader came from, which is this runtime's history and |
| 2234 | // not anything the description could have named. `33c27e81`. Before the |
| 2235 | // guard below for `Local`'s reason: `route()` is `None` here too, and |
| 2236 | // the guard would read that as "outside the app" and hand the host an |
| 2237 | // empty address to open. |
| 2238 | // |
| 2239 | // `back` dismisses nothing. An overlay is not a place -- `dismiss` says |
| 2240 | // so and keeps its own stack -- so a host that wants one key to do both |
| 2241 | // tries `dismiss` first, which is what this runtime's own Escape does. |
| 2242 | if action.destination.is_back |
| 2243 | return self.back; |
| 2244 | |
| 2245 | if action.destination.route.is_none |
| 2246 | return Open; |
| 2247 | |
| 2248 | // `a81384d4`. The description says this call is the host's, and this |
| 2249 | // host does not know the sequence behind it -- the case it was ruled on |
| 2250 | // is an upload, three requests to a browser and no file picker here to |
| 2251 | // start one. Calling the address anyway would ask a signing endpoint for |
| 2252 | // a screen and swap the JSON it answered with. |
| 2253 | // |
| 2254 | // Said rather than swallowed, the same as a fragment naming a region |
| 2255 | // that is not there: a control that is drawn, is reachable, and does |
| 2256 | // nothing when pressed is the hardest kind of bug to see from what is on |
| 2257 | // the screen. |
| 2258 | if action.by_host |
| 2259 | self.saying = Some |
| 2260 | kind: Banner, |
| 2261 | tone: Danger, |
| 2262 | text: "this app cannot do that here".to_string, |
| 2263 | undo: None, |
| 2264 | ; |
| 2265 | // Announced here rather than left for the next navigation, because |
| 2266 | // no navigation is coming: this key produced no request, so nothing |
| 2267 | // downstream would ever move the message onto the screen. |
| 2268 | self.announce; |
| 2269 | return Idle; |
| 2270 | |
| 2271 | // The whole screen is being replaced, so this is an arrival rather than |
| 2272 | // a swap: whatever is open over the screen is put away before the call |
| 2273 | // leaves, and what comes back stands where the screen stood. That is |
| 2274 | // what "pushes a screen" means on a terminal, and it is the same |
| 2275 | // sentence the webview says by emitting the anchor and no verb. |
| 2276 | // `00ee7af5`, ruled 2026-08-25. |
| 2277 | // |
| 2278 | // Before the request is built, and not on the answer: `Outcome::Screen` |
| 2279 | // clears the overlay stack already, and a navigating call that answers |
| 2280 | // with anything else would otherwise land under a palette still |
| 2281 | // floating over the place it left. |
| 2282 | if action.navigates |
| 2283 | self.under.clear; |
| 2284 | self.over = None; |
| 2285 | self.laid = None; |
| 2286 | |
| 2287 | // `Action::elsewhere` gets no branch here, and that is the answer |
| 2288 | // rather than the gap. A mount of its own in a terminal would be a |
| 2289 | // split or a tab, which is this renderer's furniture and not the |
| 2290 | // description's: a screen asking for one would be asking for a layout. |
| 2291 | // So the call falls through and is performed where it stands, which is |
| 2292 | // the degradation the member's docs promise. goingson `3fb2526a`. |
| 2293 | let mut payload = extra; |
| 2294 | payload.absorb; |
| 2295 | let Some = Selfrequest_for.map |
| 2296 | else |
| 2297 | return Idle; |
| 2298 | ; |
| 2299 | |
| 2300 | // An awaiting call locks the control that made it until the answer |
| 2301 | // arrives. A terminal has no browser to do this for it, and the second |
| 2302 | // press is the one that buys the same thing twice. |
| 2303 | // |
| 2304 | // The refusal is against the outstanding request rather than against |
| 2305 | // being busy at all: the rest of the screen keeps working, which is what |
| 2306 | // "this control is doing something" means as opposed to "the app is". |
| 2307 | if action.awaits |
| 2308 | if self |
| 2309 | .outstanding |
| 2310 | .as_ref |
| 2311 | .is_some_and |
| 2312 | |
| 2313 | return Idle; |
| 2314 | |
| 2315 | self.outstanding = Some; |
| 2316 | self.view.awaiting; |
| 2317 | |
| 2318 | Call |
| 2319 | |
| 2320 | |
| 2321 |