max / quasi
| 1 | //! A screen, what the user has done to it, and how they got here. |
| 2 | //! |
| 3 | //! The counterpart of `quasi-tui`'s runtime, and it holds less: reach, focus and |
| 4 | //! scroll are egui's, so what is left is history, the question a control asked, |
| 5 | //! the overlays stacked over the screen, and the app's own chrome. |
| 6 | //! |
| 7 | //! # Keys |
| 8 | //! |
| 9 | //! There is no key table here, and that is the difference from the terminal |
| 10 | //! rather than an omission. Tab, Enter, Space and the arrows are egui's own |
| 11 | //! walk over its own reach; a renderer that bound them again would be a second |
| 12 | //! party moving the keyboard. |
| 13 | //! |
| 14 | //! What is left is [`Chrome`], which egui cannot know about: an app-level |
| 15 | //! binding is a key that belongs to no widget, so it is read off the context |
| 16 | //! before the frame is drawn. |
| 17 | |
| 18 | use Ui; |
| 19 | use |
| 20 | Accepted, Action, Anchor, Chrome, Frame, Locating, Message, Method, Node, Outcome, Params, |
| 21 | Request, Response, Screen, safe_file_name, |
| 22 | ; |
| 23 | |
| 24 | use crate::; |
| 25 | |
| 26 | /// What the host should do next. |
| 27 | |
| 28 | |
| 29 | /// Nothing left to do but draw again. |
| 30 | Idle, |
| 31 | /// Ask the router this, then hand the answer to [`Runtime::apply`]. |
| 32 | Call, |
| 33 | /// Ask this question, then call [`Runtime::answer`] with what the user said. |
| 34 | Ask, |
| 35 | /// Somewhere outside the app. The host opens it, and nothing comes back. |
| 36 | Open, |
| 37 | /// This request belongs in a mount of its own. The host puts one up and |
| 38 | /// feeds it this. |
| 39 | /// |
| 40 | /// A mount here is an egui viewport, and opening one is the host's the |
| 41 | /// same way a file dialog is: this crate owns drawing and the requests |
| 42 | /// drawing produces, and a second native surface is not one of those. |
| 43 | /// |
| 44 | /// Distinct from [`Open`](Self::Open), which means somewhere outside the |
| 45 | /// app entirely -- a browser, a mail client, and a bare address because |
| 46 | /// nothing here will serve it. What this carries is a [`Request`] for this |
| 47 | /// app's own router, put up beside the screen that asked rather than inside |
| 48 | /// it: the new mount asks for its screen the way the first one did, and the |
| 49 | /// carried view rides along, so it comes up on the place the control was |
| 50 | /// offered under. |
| 51 | /// |
| 52 | /// A host with nowhere to put a second mount should call the address the |
| 53 | /// ordinary way and let the answer land where it stands. That is what a |
| 54 | /// terminal does with the same mark, and it is the degradation |
| 55 | /// [`Action::elsewhere`](quasi_router::Action::elsewhere) promises. |
| 56 | Mount, |
| 57 | |
| 58 | |
| 59 | /// A file a route answered with, for the host to put somewhere. |
| 60 | /// |
| 61 | /// [`Outcome::File`] says what the file is and never where it goes, so this |
| 62 | /// runtime does not write it: what this crate owns is drawing and the requests |
| 63 | /// drawing produces, and a filesystem is the host's the same way a browser's |
| 64 | /// download directory is. |
| 65 | /// |
| 66 | /// The host drains it with [`Runtime::handed`] after [`Runtime::apply`]. An |
| 67 | /// egui app with a native dialog should offer one; one without should write it |
| 68 | /// into the working directory under [`name`](Self::name), which is the ruling's |
| 69 | /// answer for a host with nowhere better. |
| 70 | /// |
| 71 | /// The twin of `quasi_tui::Handed`, deliberately duplicated rather than shared: |
| 72 | /// these two renderers already carry parallel `Step`s and parallel `Layer`s, and |
| 73 | /// a common crate for three fields would tie their release cadences together for |
| 74 | /// nothing. A change to one is a change to both. |
| 75 | |
| 76 | |
| 77 | /// The suggested file name, already through [`safe_file_name`]. |
| 78 | pub name: String, |
| 79 | /// What kind of file it is. |
| 80 | pub kind: Accepted, |
| 81 | /// The file. |
| 82 | pub bytes: , |
| 83 | |
| 84 | |
| 85 | /// A screen and what the user has done to it. |
| 86 | /// |
| 87 | /// The pair an overlay needs: an overlay has its own edits and its own ticks, so |
| 88 | /// it holds a [`View`] of its own rather than borrowing the one underneath. |
| 89 | |
| 90 | |
| 91 | screen: Screen, |
| 92 | view: View, |
| 93 | /// The request that opened the overlay this layer was displaced by. |
| 94 | /// |
| 95 | /// Saved on the way down and restored on the way up, so a nested overlay |
| 96 | /// dismissing back to an outer one restores the *outer* one's identity |
| 97 | /// rather than losing it. See [`Runtime::over`]. |
| 98 | over: , |
| 99 | /// What the layer this one was displaced by was anchored at, if anything. |
| 100 | /// Saved and restored with `over`, and for its reason. See |
| 101 | /// [`Runtime::anchor`]. |
| 102 | anchor: , |
| 103 | |
| 104 | |
| 105 | /// A screen, what the user has done to it, and how they got here. |
| 106 | |
| 107 | |
| 108 | screen: Screen, |
| 109 | view: View, |
| 110 | /// What the app offers from every screen. |
| 111 | chrome: Chrome, |
| 112 | /// The awaiting call this runtime has dispatched and not been answered |
| 113 | /// about, with the request that went out. |
| 114 | /// |
| 115 | /// Only an action carrying [`Action::awaiting`] lands here. The pair |
| 116 | /// rather than the action alone, because the answer that clears it is the |
| 117 | /// answer to that request: anything else arriving first leaves the control |
| 118 | /// waiting, which is what it is doing. |
| 119 | outstanding: , |
| 120 | /// The layers this one is drawn over, outermost first. |
| 121 | /// |
| 122 | /// Separate from `history` on purpose: an overlay is not a place. Opening |
| 123 | /// one pushes here and leaves history alone, and dismissing one reveals the |
| 124 | /// screen the user never left. |
| 125 | under: , |
| 126 | /// The places behind this one, most recent last. |
| 127 | history: , |
| 128 | /// The request that produced the screen currently showing. |
| 129 | here: , |
| 130 | /// The request that produced the overlay currently on top, if one is. |
| 131 | /// |
| 132 | /// Five presses of a help key were five Escapes. |
| 133 | /// |
| 134 | /// Nothing in the description had to grow for it. The runtime is handed the |
| 135 | /// request it fired, so the identity the dedupe needs is already in hand — |
| 136 | /// this is `here`'s shape for a layer that is not a place, which is why |
| 137 | /// `remember` is not called for one. |
| 138 | /// |
| 139 | /// A webview never had the bug: `Outcome::Over` lands in one overlay |
| 140 | /// container and replaces what is in it. Two of three hosts stacking was |
| 141 | /// one description behaving two ways, which is the drift this stack exists |
| 142 | /// to end. |
| 143 | over: , |
| 144 | /// What the layer on top is anchored at, when it is anchored at anything. |
| 145 | /// |
| 146 | /// `None` on an `Outcome::Over`, which is app-modal and belongs to no |
| 147 | /// point on the screen, and on no layer at all. Kept beside `over` rather |
| 148 | /// than on `Layer`, because it is a fact about the layer on top and |
| 149 | /// `Layer` holds the ones underneath. |
| 150 | /// |
| 151 | /// Held rather than resolved once: the rect is re-read every frame, so a |
| 152 | /// menu stays with its control while the window resizes and the layout |
| 153 | /// underneath moves. |
| 154 | pub(crate) anchor: , |
| 155 | /// A control waiting on its own question being answered. |
| 156 | asked: , |
| 157 | /// Something to say once the screen it belongs to has arrived. |
| 158 | saying: , |
| 159 | /// What this mount puts around whatever screen is showing. |
| 160 | /// |
| 161 | /// Held beside the screen rather than arriving with one, which is what |
| 162 | /// makes it the mount's: it survives every answer that replaces the screen |
| 163 | /// inside it, the way `chrome` does. The difference between the two is |
| 164 | /// lifetime — chrome is the app's, and this is one place the app puts a |
| 165 | /// screen up. |
| 166 | frame: Frame, |
| 167 | /// When this last handed out [`Runtime::refreshes`], so the cadence is kept |
| 168 | /// here rather than by every host that draws a live screen. |
| 169 | /// |
| 170 | /// `None` until the first call, which makes the first ask immediate: a |
| 171 | /// region that waited out a whole period before its first answer would be a |
| 172 | /// slower screen than the one liveness replaces. |
| 173 | refreshed: , |
| 174 | /// When each toast on the screen was raised, in the order the toasts sit in |
| 175 | /// `screen.notices`. |
| 176 | /// |
| 177 | /// The description says a toast goes away on its own and never says when, |
| 178 | /// so the when is kept here: one instant per transient notice, and |
| 179 | /// [`expires_at`](Runtime::expires_at) takes away the ones whose time is |
| 180 | /// up. Banners have no entry, because nothing about a banner is on a |
| 181 | /// clock. |
| 182 | /// |
| 183 | /// Positional rather than keyed, because a notice has no identity to key on |
| 184 | /// and does not need one: a toast joins the screen at the end of the list |
| 185 | /// ([`announce`](Runtime::announce)) or arrives inside a whole screen, and |
| 186 | /// both are handled where they happen rather than guessed at here. |
| 187 | raised: , |
| 188 | /// A file a route answered with and the host has not taken yet. |
| 189 | /// |
| 190 | /// Drained by [`handed`](Runtime::handed) rather than returned from |
| 191 | /// [`apply`](Runtime::apply): an answer is a follow-up request or a file |
| 192 | /// and never both, so widening `apply`'s return type would be saying |
| 193 | /// something the vocabulary cannot. One at a time; a second overwrites. |
| 194 | handed: , |
| 195 | /// A place a route asked for and the host has not gone looking for yet. |
| 196 | /// |
| 197 | /// Drained by [`locating`](Runtime::locating), for |
| 198 | /// [`handed`](Self::handed)'s reason and with the same shape: this crate |
| 199 | /// draws and turns input into requests, and a file dialog is neither. One |
| 200 | /// at a time, because a picker is modal on every host that has one — a |
| 201 | /// second ask arriving with one outstanding replaces it, which is what the |
| 202 | /// shipped `DialogManager` does by dropping the newer request. |
| 203 | locating: , |
| 204 | |
| 205 | |
| 206 | |
| 207 | /// Start on this screen, with nothing typed and nothing behind it. |
| 208 | |
| 209 | |
| 210 | let mut runtime = Self |
| 211 | screen, |
| 212 | view: new, |
| 213 | chrome: new, |
| 214 | under: Vecnew, |
| 215 | history: Vecnew, |
| 216 | here: None, |
| 217 | over: None, |
| 218 | anchor: None, |
| 219 | asked: None, |
| 220 | outstanding: None, |
| 221 | saying: None, |
| 222 | frame: new, |
| 223 | refreshed: None, |
| 224 | raised: Vecnew, |
| 225 | handed: None, |
| 226 | locating: None, |
| 227 | ; |
| 228 | runtime.view.seed; |
| 229 | runtime.view.open_at; |
| 230 | runtime.reraise; |
| 231 | runtime |
| 232 | |
| 233 | |
| 234 | /// Declare what the app offers from every screen. |
| 235 | |
| 236 | |
| 237 | self.chrome = chrome; |
| 238 | self |
| 239 | |
| 240 | |
| 241 | /// Declare what this mount puts around the screen. |
| 242 | /// |
| 243 | /// A mount with two ways of showing one screen builds two runtimes |
| 244 | /// carrying two frames, which is what it already does for everything else |
| 245 | /// it holds across frames. The screen inside them says nothing about |
| 246 | /// either. |
| 247 | |
| 248 | |
| 249 | self.frame = frame; |
| 250 | self |
| 251 | |
| 252 | |
| 253 | /// What the app offers from every screen. |
| 254 | /// |
| 255 | /// The panel is what a caller reads off this: an answer aimed at it lands |
| 256 | /// here rather than on the screen, so this is where its current contents |
| 257 | /// are. |
| 258 | |
| 259 | pub const |
| 260 | &self.chrome |
| 261 | |
| 262 | |
| 263 | /// What this mount puts around the screen. |
| 264 | |
| 265 | pub const |
| 266 | &self.frame |
| 267 | |
| 268 | |
| 269 | /// The screen being shown: the overlay's, when one is open. |
| 270 | |
| 271 | pub const |
| 272 | &self.screen |
| 273 | |
| 274 | |
| 275 | /// Whether an overlay is open over the screen. |
| 276 | |
| 277 | pub const |
| 278 | !self.under.is_empty |
| 279 | |
| 280 | |
| 281 | /// What the user has typed and ticked on it. |
| 282 | /// |
| 283 | /// The terminal's runtime has had this since the beginning; here it was |
| 284 | /// missing, which made "a refresh keeps what a navigation drops" a claim |
| 285 | /// nothing outside this module could check. |
| 286 | |
| 287 | pub const |
| 288 | &self.view |
| 289 | |
| 290 | |
| 291 | /// The same, to write into. |
| 292 | /// |
| 293 | /// What [`View::set`] documents itself for — "put a value in, as a host |
| 294 | /// restoring one would" — was unreachable for a host holding a [`Runtime`]: |
| 295 | /// `show` draws through the runtime's own view and nothing handed it out. |
| 296 | /// Restoring a draft into a screen is the host's job and this is how. |
| 297 | pub const |
| 298 | &mut self.view |
| 299 | |
| 300 | |
| 301 | /// Draw a frame, and answer what to do about it. |
| 302 | /// |
| 303 | /// The chrome's keys are read before the drawing, so a screen cannot capture |
| 304 | /// the key that opens the palette: the binding belongs to the app and the |
| 305 | /// widgets below have not been laid out yet. |
| 306 | /// |
| 307 | /// With one exception, and it is the reason most real shortcuts can be |
| 308 | /// declared at all: **a box that has the focus is answering the keyboard, |
| 309 | /// so the letters it eats never reach a binding.** Read before the drawing |
| 310 | /// still, from the focus the last frame ended with. Without it, a bare |
| 311 | /// letter declared as a shortcut stops the tag field, the rename pattern |
| 312 | /// and the search box accepting that letter. [`types`] is which keys are a |
| 313 | /// box's. |
| 314 | |
| 315 | // A live screen asks for the next frame itself. egui repaints when |
| 316 | // something asks it to, so a region whose contents move without the |
| 317 | // user has nobody to wake it, and the alternative every host reached |
| 318 | // for is repainting continuously. Asked before anything can return |
| 319 | // early, or a screen stops moving the moment a key is pressed. |
| 320 | if self.is_live |
| 321 | ui.ctx.request_repaint_after; |
| 322 | |
| 323 | |
| 324 | // A readout derived from the current time has the same problem one |
| 325 | // level down, and its own answer: nothing about the screen changes, so |
| 326 | // there is nothing to ask for and nothing to re-read. What goes stale |
| 327 | // is the arithmetic, and the fix is a frame. The finest cadence the |
| 328 | // screen's kinds demand, so every readout on it moves on one wake. |
| 329 | // A toast is the same shape of problem with the same answer, and this |
| 330 | // is the one place it can be taken away without an app remembering to: |
| 331 | // the deadline is the runtime's, and a frame is what notices it has |
| 332 | // passed. `4453bf82`. |
| 333 | self.expires; |
| 334 | if let Some = self.tick_in |
| 335 | ui.ctx.request_repaint_after; |
| 336 | |
| 337 | |
| 338 | if let Some = self.pressed_binding |
| 339 | return self.call; |
| 340 | |
| 341 | |
| 342 | // What is under it first, then this one over the top. An `Area` is what |
| 343 | // an overlay is in egui, and `Order::Foreground` is what puts it there. |
| 344 | for layer in &self.under |
| 345 | let mut view = layer.view.clone; |
| 346 | let hidden = cratehidden; |
| 347 | let mut pass = cratePass |
| 348 | immediate, |
| 349 | view: &mut view, |
| 350 | hidden: &hidden, |
| 351 | fired: None, |
| 352 | stirred: new, |
| 353 | ; |
| 354 | cratescreen_regions; |
| 355 | |
| 356 | |
| 357 | let fired = if self.under.is_empty |
| 358 | immediate.chromed |
| 359 | else |
| 360 | // `ae8e8836`. Where the box goes, which is the only thing an |
| 361 | // anchored layer does differently from an overlay. The rect is read |
| 362 | // now rather than remembered from when the menu opened, so it stays |
| 363 | // with its subject while the window resizes. |
| 364 | // |
| 365 | // Under the anchor and left-aligned with it, which is where a menu |
| 366 | // opened from a control goes on every desktop. egui keeps an `Area` |
| 367 | // on screen on its own, so a control near the bottom edge needs no |
| 368 | // arithmetic here. |
| 369 | // |
| 370 | // An anchor whose subject was not drawn this pass -- scrolled out of |
| 371 | // view, or a selection whose every ticked row is off screen -- |
| 372 | // resolves to nothing and the menu takes the overlay's place. That |
| 373 | // is the same degradation the other two renderers make, and it is |
| 374 | // visible rather than silent: the menu is somewhere, and it is the |
| 375 | // place a menu goes when there is nothing to put it beside. |
| 376 | let ticks: = self.view.ticks.map.collect; |
| 377 | let at = self |
| 378 | .anchor |
| 379 | .as_ref |
| 380 | .and_then; |
| 381 | |
| 382 | let mut fired = None; |
| 383 | let mut area = |
| 384 | new.order; |
| 385 | if let Some = at |
| 386 | area = area.fixed_pos; |
| 387 | |
| 388 | area.show |
| 389 | popup |
| 390 | .shadow |
| 391 | .show |
| 392 | fired = immediate.screen; |
| 393 | ; |
| 394 | ; |
| 395 | fired |
| 396 | ; |
| 397 | |
| 398 | // Escape closes what is on top before it goes back, which is what |
| 399 | // Escape means everywhere else it is bound. |
| 400 | if ui.ctx.input |
| 401 | if self.dismiss |
| 402 | return Idle; |
| 403 | |
| 404 | return self.back; |
| 405 | |
| 406 | |
| 407 | match fired |
| 408 | Some |
| 409 | action, |
| 410 | payload, |
| 411 | confirm, |
| 412 | => match confirm |
| 413 | Some => |
| 414 | self.asked = Some; |
| 415 | Ask |
| 416 | |
| 417 | None => self.send, |
| 418 | , |
| 419 | None => Idle, |
| 420 | |
| 421 | |
| 422 | |
| 423 | /// Put a message on the screen, from the host rather than from a route. |
| 424 | /// |
| 425 | /// The host has things to say that no handler knows about: a route that |
| 426 | /// failed, an address it will not open, a device that is not there. Without |
| 427 | /// this they go to stderr, which for a windowed app is nowhere at all. |
| 428 | |
| 429 | self.screen.notices.push |
| 430 | kind: Banner, |
| 431 | tone: Danger, |
| 432 | text: text.into, |
| 433 | // Nothing to do about it. What the host says here is a report -- |
| 434 | // a route that failed, an address it will not open -- and there is |
| 435 | // no route it could offer that would undo any of them. |
| 436 | act: None, |
| 437 | ; |
| 438 | |
| 439 | |
| 440 | /// Answer the question a control asked. |
| 441 | /// |
| 442 | /// Anything that is not yes is no, which is the safe way round for a prompt |
| 443 | /// only ever raised by something destructive. |
| 444 | |
| 445 | match self.asked.take |
| 446 | Some if yes => self.send, |
| 447 | _ => Idle, |
| 448 | |
| 449 | |
| 450 | |
| 451 | /// The request that produced the screen showing now. |
| 452 | /// |
| 453 | /// `None` before the first navigation: a runtime is built from a screen |
| 454 | /// rather than from an address, so the opening screen has no request behind |
| 455 | /// it until the host performs one. |
| 456 | |
| 457 | pub const |
| 458 | self.here.as_ref |
| 459 | |
| 460 | |
| 461 | /// Ask for this screen again. |
| 462 | /// |
| 463 | /// **What a described screen has no other way to say: the thing it is about |
| 464 | /// changed, and nothing the user did to this screen changed it.** A route |
| 465 | /// answers a screen built from the state at the moment it was asked, and |
| 466 | /// that answer is kept until something fires. So a host whose state moves |
| 467 | /// underneath a screen — a background job reporting progress, a write the |
| 468 | /// host applies after the frame — has a screen describing a past it can |
| 469 | /// neither notice nor correct. |
| 470 | /// |
| 471 | /// This is the host saying so. It is deliberately not a description member: |
| 472 | /// nothing in [`Screen`] claims a refresh rate, because how often a fact |
| 473 | /// goes stale is a property of the app holding it rather than of the screen |
| 474 | /// showing it. The host knows it started an export; the description does |
| 475 | /// not, and should not have to. |
| 476 | /// |
| 477 | /// [`Step::Idle`] when there is nothing to ask for, which is the opening |
| 478 | /// screen before any navigation. Nothing is lost by calling it then: the |
| 479 | /// screen showing is the one the host built. |
| 480 | /// |
| 481 | /// History is untouched. Asking for the screen you are on again is not |
| 482 | /// going anywhere, so [`back`](Self::back) still goes where it would have. |
| 483 | |
| 484 | |
| 485 | match &self.here |
| 486 | Some => Call, |
| 487 | None => Idle, |
| 488 | |
| 489 | |
| 490 | |
| 491 | /// Go back, if there is anywhere to go. |
| 492 | |
| 493 | match self.history.pop |
| 494 | Some => |
| 495 | self.here = Some; |
| 496 | Call |
| 497 | |
| 498 | None => Idle, |
| 499 | |
| 500 | |
| 501 | |
| 502 | /// Close the overlay on top, if there is one. |
| 503 | /// |
| 504 | /// The layer underneath comes back exactly as it was left. History is not |
| 505 | /// touched, because an overlay was never a place. |
| 506 | pub |
| 507 | match self.under.pop |
| 508 | Some => |
| 509 | self.screen = layer.screen; |
| 510 | self.view = layer.view; |
| 511 | // The outer overlay's identity, or `None` back on the base |
| 512 | // screen. Restored rather than cleared, so dismissing a confirm |
| 513 | // raised over a palette leaves the palette still refusing to |
| 514 | // stack itself. |
| 515 | self.over = layer.over; |
| 516 | self.anchor = layer.anchor; |
| 517 | self.reraise; |
| 518 | true |
| 519 | |
| 520 | None => false, |
| 521 | |
| 522 | |
| 523 | |
| 524 | /// The binding a key pressed this frame claims, if any. |
| 525 | /// |
| 526 | /// A key the box under the cursor is answering never reaches here. See |
| 527 | /// [`types`] for which keys those are and [`show`](Self::show) for why the |
| 528 | /// guard is the renderer's rather than the description's. |
| 529 | |
| 530 | if self.chrome.bindings.is_empty |
| 531 | return None; |
| 532 | |
| 533 | // Asked outside `input` because both borrow the context, and asked |
| 534 | // before the screen draws, so it is the focus the last frame ended |
| 535 | // with -- which is the frame the user was looking at when they pressed |
| 536 | // the key. |
| 537 | let typing = ctx.text_edit_focused; |
| 538 | ctx.input |
| 539 | self.chrome.bindings.iter.find_map |
| 540 | let = parse?; |
| 541 | if typing && types |
| 542 | return None; |
| 543 | |
| 544 | // Exact rather than logical, and the guard above is what makes |
| 545 | // it matter. `matches_logically` ignores a shift the pattern |
| 546 | // did not ask for, which is right for one control's `Act::key` |
| 547 | // and wrong for a table: an app that binds `f` and `shift+f` to |
| 548 | // different things -- audiofiles binds the forge and Find |
| 549 | // similar -- had the bare one answer both, first match wins. |
| 550 | // Undeclarable keys hid it; declaring them is what exposes it. |
| 551 | input |
| 552 | .key_pressed |
| 553 | .then |
| 554 | .and_then |
| 555 | |
| 556 | |
| 557 | |
| 558 | |
| 559 | /// Put fresh contents wherever this names, and say whether anywhere did. |
| 560 | /// |
| 561 | /// The panel is not on the screen and is addressable all the same, so an |
| 562 | /// answer aimed at it lands in the chrome. Asked in that order rather than |
| 563 | /// the other way round because the panel's id is the app's and a screen |
| 564 | /// could carry a region with the same name, and the app's panel is the one |
| 565 | /// that outlives the screen. |
| 566 | |
| 567 | if self.chrome.panel.is_some |
| 568 | return self.chrome.replace; |
| 569 | |
| 570 | self.screen.replace |
| 571 | |
| 572 | |
| 573 | /// Put what the router answered onto the screen. |
| 574 | |
| 575 | let Response |
| 576 | outcome, |
| 577 | notice, |
| 578 | address, |
| 579 | invalidates, |
| 580 | = response; |
| 581 | self.saying = notice.or; |
| 582 | |
| 583 | // Whatever was outstanding has been answered. |
| 584 | if self |
| 585 | .outstanding |
| 586 | .as_ref |
| 587 | .is_some_and |
| 588 | |
| 589 | self.outstanding = None; |
| 590 | self.view.await_on; |
| 591 | |
| 592 | |
| 593 | match outcome |
| 594 | // The candidates for the box being typed into. Not a region and not |
| 595 | // a screen: the list belongs to a control, so it lands on the view |
| 596 | // beside what has been typed. |
| 597 | Suggestions => |
| 598 | self.view.suggested; |
| 599 | None |
| 600 | |
| 601 | // Over what is already there. The layer underneath is put away |
| 602 | // whole and comes back untouched when the overlay is dismissed. |
| 603 | // `remember` is deliberately not called: an overlay is not a place. |
| 604 | Over => |
| 605 | self.layer; |
| 606 | None |
| 607 | |
| 608 | // Over what is already there, at something on it. The same layering, |
| 609 | // plus the anchor, which is held rather than resolved: the rect is |
| 610 | // read at draw time, every frame, so a menu stays with its subject |
| 611 | // while the layout underneath moves. |
| 612 | // |
| 613 | // The anchor is checked against the screen being covered rather than |
| 614 | // the one arriving, because that is the screen it names. An anchor |
| 615 | // naming nothing there is dropped here rather than carried and |
| 616 | // failed at draw time: the two are the same picture -- a menu drawn |
| 617 | // where the overlay goes -- and dropping it means the draw path has |
| 618 | // one question to ask instead of two. |
| 619 | Anchored => |
| 620 | let anchor = self.screen.anchors.then_some; |
| 621 | self.layer; |
| 622 | None |
| 623 | |
| 624 | Screen => |
| 625 | // Arriving where you already are is a refresh rather than a |
| 626 | // navigation, and the difference is the whole of what the user |
| 627 | // has done to the screen. `reset` and `seed` are both *arrival* |
| 628 | // behaviour: one drops what was typed and ticked, the other |
| 629 | // applies what the description says is ticked. Running either on |
| 630 | // a refresh would undo the user mid-sentence — a text field |
| 631 | // cleared on every reload, and an untick put back the moment |
| 632 | // anything redrew, which is the exact failure `View::seed`'s own |
| 633 | // documentation says it is applied once to avoid. |
| 634 | let refreshed = self.here.as_ref == Some; |
| 635 | // A navigation replaces everything, including any overlay open |
| 636 | // over it. |
| 637 | self.under.clear; |
| 638 | self.over = None; |
| 639 | self.anchor = None; |
| 640 | self.remember; |
| 641 | self.screen = screen; |
| 642 | self.reraise; |
| 643 | if !refreshed |
| 644 | self.view.reset; |
| 645 | self.view.seed; |
| 646 | // And where the caret starts, at the same moment and for |
| 647 | // the same reason: a refresh runs neither, or a reload |
| 648 | // would snatch the caret out of whatever is being typed. |
| 649 | self.view.open_at; |
| 650 | |
| 651 | self.announce; |
| 652 | None |
| 653 | |
| 654 | Fragment => |
| 655 | let mut missing: = Vecnew; |
| 656 | if !self.land |
| 657 | missing.push; |
| 658 | |
| 659 | for stale in invalidates |
| 660 | if !self.land |
| 661 | missing.push; |
| 662 | |
| 663 | |
| 664 | if !missing.is_empty |
| 665 | let named = missing |
| 666 | .iter |
| 667 | .map |
| 668 | . |
| 669 | .join; |
| 670 | let subject = if missing.len == 1 else ; |
| 671 | self.saying = Some |
| 672 | "{named} {subject} not on this screen" |
| 673 | ; |
| 674 | |
| 675 | self.announce; |
| 676 | None |
| 677 | |
| 678 | // The work was handed off and the region is waiting on it. Nothing |
| 679 | // is drawn from this answer: `node::region` reads the slot's |
| 680 | // readiness first and draws `widget::awaiting` from it, with |
| 681 | // whatever proportion the region's own feeding action described. |
| 682 | // That is the same wait a deferred load draws, which is the point — |
| 683 | // a reader cannot tell "this region has not arrived yet" from "the |
| 684 | // work behind this region is running", and there is no reason they |
| 685 | // should. |
| 686 | // |
| 687 | // A region that is not there is the description bug a fragment |
| 688 | // naming one is, and says so the same way. |
| 689 | Started => |
| 690 | if !self.screen.started |
| 691 | self.saying = Some; |
| 692 | |
| 693 | self.announce; |
| 694 | None |
| 695 | |
| 696 | // Somewhere else, which the host performs the same way it |
| 697 | // performed the first request. An external destination hands off |
| 698 | // and nothing comes back. |
| 699 | Goto => |
| 700 | let path = action.destination.route?; |
| 701 | let request = get.carrying; |
| 702 | self.remember; |
| 703 | Some |
| 704 | |
| 705 | // A file for the host to put somewhere. What is drawn stays drawn: |
| 706 | // this is not a screen, not a region and not a place, and the |
| 707 | // answer leaves by `handed` rather than by the return value. |
| 708 | File => |
| 709 | self.handed = Some |
| 710 | name: safe_file_name, |
| 711 | kind, |
| 712 | bytes, |
| 713 | ; |
| 714 | None |
| 715 | |
| 716 | // A place for the host to go and find. Nothing is drawn and nowhere |
| 717 | // is navigated to: the ask leaves by `locating`, the host opens its |
| 718 | // picker, and what the reader chose comes back as an ordinary |
| 719 | // request through `Locating::answered`. The screen underneath is |
| 720 | // untouched throughout, which is what makes a cancelled picker cost |
| 721 | // nothing. |
| 722 | Locate => |
| 723 | self.locating = Some; |
| 724 | None |
| 725 | |
| 726 | |
| 727 | |
| 728 | |
| 729 | /// Take the file the last answer handed over, if it handed one over. |
| 730 | /// |
| 731 | /// Called after [`apply`](Self::apply), the way |
| 732 | /// [`refreshes`](Self::refreshes) is called between frames: this drains, so |
| 733 | /// calling it twice gets the file once. A host that never calls it silently |
| 734 | /// drops every download, which is the cost of keeping the I/O out of here. |
| 735 | |
| 736 | self.handed.take |
| 737 | |
| 738 | |
| 739 | /// Take the place the last answer asked for, if it asked for one. |
| 740 | /// |
| 741 | /// [`handed`](Self::handed)'s twin and drained the same way: call it after |
| 742 | /// [`apply`](Self::apply), and calling it twice gets the ask once. A host |
| 743 | /// that never calls it silently drops every picker, and the import doors |
| 744 | /// do nothing when pressed. |
| 745 | /// |
| 746 | /// What to do with it is the host's, and on this renderer it is a native |
| 747 | /// dialog: open the picker [`Locating::sought`] names, titled |
| 748 | /// [`Locating::prompt`]. When the reader picks, build the follow-up call |
| 749 | /// with [`Locating::answered`] and hand its answer back to |
| 750 | /// [`apply`](Self::apply) — the same round trip every other control makes. |
| 751 | /// When they cancel, do nothing at all: a cancelled picker is not an answer |
| 752 | /// and there is nothing to tell the router about it. |
| 753 | /// |
| 754 | /// # One call, whatever was picked |
| 755 | /// |
| 756 | /// [`Locating::answered`] takes every |
| 757 | /// [`Picked`](quasi_router::Picked) at once and builds one request, so a |
| 758 | /// [`Sought::Files`](quasi_router::Sought::Files) ask is answered once with |
| 759 | /// all the files rather than once per file. A host that loops here is |
| 760 | /// turning one batched import into N imports, which is the regression the |
| 761 | /// batch was a fix for. |
| 762 | /// |
| 763 | /// # The save shape |
| 764 | /// |
| 765 | /// [`Sought::Save`](quasi_router::Sought::Save) is the dialog this |
| 766 | /// platform's file dialogs call Save: seed the name box with |
| 767 | /// [`name`](quasi_router::Sought::Save::name), filter to |
| 768 | /// [`accept`](quasi_router::Sought::Save::accept), and answer with what the |
| 769 | /// reader named. `rfd::FileDialog::set_file_name` and `save_file` are what |
| 770 | /// that is on a desktop egui host, which is every host this renderer has. |
| 771 | /// A host with no dialog at all writes under |
| 772 | /// [`safe_file_name`](quasi_router::safe_file_name) of the suggested name |
| 773 | /// and answers with the path it used, the way [`Handed`] is treated on a |
| 774 | /// host with nowhere better. What it must not do is answer nothing and |
| 775 | /// leave a pressed control looking broken. |
| 776 | |
| 777 | self.locating.take |
| 778 | |
| 779 | |
| 780 | /// Note where we were, before we leave it. |
| 781 | /// |
| 782 | /// Arriving where you already are is not leaving anywhere, which is what the |
| 783 | /// first guard is for: [`reload`](Self::reload) answers the request that |
| 784 | /// produced the screen showing, so without it every refresh would push a |
| 785 | /// duplicate of the current place and `back` would walk through a stack of |
| 786 | /// the screen it is already on. |
| 787 | |
| 788 | if self.here.as_ref == Some |
| 789 | return; |
| 790 | |
| 791 | let place = match address |
| 792 | Some => true, |
| 793 | Some => false, |
| 794 | Some => |
| 795 | self.here = Some; |
| 796 | return; |
| 797 | |
| 798 | None => request.method == Get, |
| 799 | ; |
| 800 | if place && let Some = self.here.replace |
| 801 | self.history.push; |
| 802 | |
| 803 | |
| 804 | |
| 805 | /// Put a screen over the one showing, keeping that one whole underneath. |
| 806 | /// |
| 807 | /// The shared half of `Outcome::Over` and `Outcome::Anchored`, which differ |
| 808 | /// only in whether there is an anchor. Everything else about a layer -- the |
| 809 | /// stack, the dedupe, the seeding, the dismissal -- is one path, and the two |
| 810 | /// outcomes drifting apart here is what would make one description behave |
| 811 | /// two ways. |
| 812 | /// |
| 813 | /// # The dedupe |
| 814 | /// |
| 815 | /// A binding that opens an overlay is asked at the top of every frame and |
| 816 | /// an open overlay does not suppress it, so this pushed a second copy of |
| 817 | /// the same help over the first and it took an Escape per press to get |
| 818 | /// back. The guard is the request rather than the screen, because every |
| 819 | /// press is a fresh route call and the two screen values are equal by |
| 820 | /// accident rather than by identity. |
| 821 | /// |
| 822 | /// The top layer only. A screen raised from within another one is a |
| 823 | /// different request and still stacks, which is what a confirm over a |
| 824 | /// palette is. |
| 825 | |
| 826 | if self.over.as_ref == Some |
| 827 | return; |
| 828 | |
| 829 | let under = Layer |
| 830 | screen: replace, |
| 831 | view: replace, |
| 832 | over: self.over.replace, |
| 833 | anchor: replace, |
| 834 | ; |
| 835 | self.under.push; |
| 836 | self.view.seed; |
| 837 | self.view.open_at; |
| 838 | self.reraise; |
| 839 | self.announce; |
| 840 | |
| 841 | |
| 842 | /// Put whatever the last answer said onto the screen it belongs to. |
| 843 | |
| 844 | let Some = self.saying.take else |
| 845 | return; |
| 846 | ; |
| 847 | // The way back the response offered, as the control it becomes here. |
| 848 | // `bde35298`, and quasi-tui does the same in its own `announce`: a host |
| 849 | // that converts a message into a node is the one that has to carry the |
| 850 | // undo across, and until `Node::Notice` grew an act both dropped it. |
| 851 | let act = message.undo_act; |
| 852 | let Message |
| 853 | kind, tone, text, .. |
| 854 | = message; |
| 855 | if kind.transient |
| 856 | self.raised.push; |
| 857 | |
| 858 | self.screen.notices.push |
| 859 | kind, |
| 860 | tone, |
| 861 | text, |
| 862 | act, |
| 863 | ; |
| 864 | |
| 865 | |
| 866 | /// A control's action as the next step. |
| 867 | /// |
| 868 | /// The action's own params ride with whatever the screen gathered, which is |
| 869 | /// what `absorb` is for: a control that names a value and a selection that |
| 870 | /// names members both belong in one payload. |
| 871 | |
| 872 | // Nothing to ask and nowhere to send anyone. This renderer is |
| 873 | // `Renderer::Client`: it holds what it draws and redraws it from |
| 874 | // memory, so whatever the local action names is something it already |
| 875 | // does natively, and the mark tells it nothing it did not know. |
| 876 | // |
| 877 | // Handled rather than left to the guard below, which reads `route()` as |
| 878 | // "not a route, therefore somewhere outside" and would hand the host an |
| 879 | // empty address to open. `210574ca`. |
| 880 | if action.destination.is_local |
| 881 | return Idle; |
| 882 | |
| 883 | // Wherever the reader came from, which is this runtime's history and |
| 884 | // not anything the description could have named. `33c27e81`. Before the |
| 885 | // guard below for `Local`'s reason: `route()` is `None` here too, and |
| 886 | // the guard would read that as "outside the app" and hand the host an |
| 887 | // empty address to open. |
| 888 | // |
| 889 | // `back` dismisses nothing, because it is a place and an overlay never |
| 890 | // was one. A host wanting Escape to close an overlay first binds that |
| 891 | // itself; `dismiss` is the method for it and stays separate. |
| 892 | if action.destination.is_back |
| 893 | return self.back; |
| 894 | |
| 895 | if action.destination.route.is_none |
| 896 | return Open; |
| 897 | |
| 898 | // A mount of its own, which here is a viewport. The request is built |
| 899 | // and handed over rather than made, because the point is that this |
| 900 | // runtime does not make it: the mount that goes up asks for its own |
| 901 | // screen, the way this one asked for the screen it is showing. |
| 902 | // |
| 903 | // Before the payload is absorbed, and that is deliberate. What a |
| 904 | // control collected belongs to the call it was collected for; a mount |
| 905 | // going up is not that call, and feeding it a half-filled form would be |
| 906 | // this renderer inventing a screen state nobody described. |
| 907 | // goingson `3fb2526a`. |
| 908 | if action.elsewhere |
| 909 | return Selfrequest_for.map_or; |
| 910 | |
| 911 | // The whole view is being replaced, so this is an arrival rather than a |
| 912 | // swap: whatever is open over the screen is put away before the call |
| 913 | // leaves, and what comes back stands where the screen stood. The same |
| 914 | // sentence the webview says by emitting the anchor and no verb, and the |
| 915 | // terminal by pushing a screen. `00ee7af5`, ruled 2026-08-25. |
| 916 | // |
| 917 | // Before the request is built, and not on the answer: `Outcome::Screen` |
| 918 | // clears the overlay stack already, and a navigating call that answers |
| 919 | // with anything else would otherwise land under an overlay still |
| 920 | // floating over the place it left. |
| 921 | if action.navigates |
| 922 | self.under.clear; |
| 923 | self.over = None; |
| 924 | self.anchor = None; |
| 925 | |
| 926 | let mut payload = extra; |
| 927 | payload.absorb; |
| 928 | let Some = Selfrequest_for.map |
| 929 | else |
| 930 | return Idle; |
| 931 | ; |
| 932 | |
| 933 | // An awaiting call locks the control that made it. egui redraws from |
| 934 | // this state every frame, so recording it here is the whole of the |
| 935 | // guard: `act_node` draws a disabled control, and a disabled control |
| 936 | // reports no click. |
| 937 | if action.awaits |
| 938 | if self |
| 939 | .outstanding |
| 940 | .as_ref |
| 941 | .is_some_and |
| 942 | |
| 943 | return Idle; |
| 944 | |
| 945 | self.outstanding = Some; |
| 946 | self.view.await_on; |
| 947 | |
| 948 | Call |
| 949 | |
| 950 | |
| 951 | /// A read of a route, for a binding that names one. |
| 952 | |
| 953 | self.send |
| 954 | |
| 955 | |
| 956 | /// The request an action makes, or nothing when it names somewhere outside |
| 957 | /// the app. |
| 958 | |
| 959 | let path = action.destination.route?; |
| 960 | Some |
| 961 | method: action.method, |
| 962 | path: path.to_string, |
| 963 | captures: new, |
| 964 | payload: action.params.clone, |
| 965 | carried: action.carried.clone, |
| 966 | |
| 967 | |
| 968 | |
| 969 | /// The calls this screen's regions are waiting on, for the host to perform. |
| 970 | /// |
| 971 | /// The counterpart of the browser's per-region trigger. A host asks for |
| 972 | /// these after putting a screen up, hands each answer back to |
| 973 | /// [`apply`](Self::apply), and the region fills where its spinner was. Ask |
| 974 | /// again after applying one rather than keeping the list: a fragment landing |
| 975 | /// clears that region's feed. |
| 976 | |
| 977 | |
| 978 | self.screen |
| 979 | .feeds |
| 980 | .into_iter |
| 981 | .filter_map |
| 982 | .collect |
| 983 | |
| 984 | |
| 985 | /// The calls this screen's live regions re-ask, when it is time to ask. |
| 986 | /// |
| 987 | /// [`feeds`](Self::feeds)' counterpart and never overlapping it: a feed |
| 988 | /// arrives once and a refresh never stops. Empty until |
| 989 | /// [`CADENCE`](crate::CADENCE) has passed since the last time this handed |
| 990 | /// anything back, so a host may call it as often as it likes and the rate |
| 991 | /// stays this crate's. |
| 992 | /// |
| 993 | /// The pacing is here rather than in each host for the reason the number |
| 994 | /// is: a host that timed its own polling would be a host the other |
| 995 | /// renderers disagree with, and every app would rebuild the same timer. |
| 996 | /// |
| 997 | /// # Two shapes of answer |
| 998 | /// |
| 999 | /// A live region naming a call comes back as that call, and its answer is a |
| 1000 | /// fragment for that region. A live region naming none comes back as the |
| 1001 | /// screen's own address, because re-reading state the host already holds |
| 1002 | /// means building the description again — and that is a whole screen, not a |
| 1003 | /// fragment. Both are requests, and a host performs them the same way. |
| 1004 | |
| 1005 | |
| 1006 | self.refreshes_at |
| 1007 | |
| 1008 | |
| 1009 | /// [`refreshes`](Self::refreshes) against a clock the caller holds. |
| 1010 | /// |
| 1011 | /// The seam a test needs, and the one an event loop that already knows what |
| 1012 | /// time it is should reach for rather than asking again. |
| 1013 | |
| 1014 | |
| 1015 | let due = self |
| 1016 | .refreshed |
| 1017 | .is_none_or; |
| 1018 | if !due |
| 1019 | return Vecnew; |
| 1020 | |
| 1021 | let mut out: = self |
| 1022 | .screen |
| 1023 | .refreshes |
| 1024 | .into_iter |
| 1025 | .filter_map |
| 1026 | .collect; |
| 1027 | // A live region that names no call is re-read by asking the screen's own |
| 1028 | // address again, which is what re-reading means for a host that retains |
| 1029 | // a description rather than a document. The audiofiles sync panel is |
| 1030 | // that case: its state is the app's own and moves when an OAuth callback |
| 1031 | // lands in another process, so there is no fragment to fetch and the |
| 1032 | // whole screen is rebuilt from what is true now. |
| 1033 | // |
| 1034 | // Only when nothing else answered. A screen with a live region that does |
| 1035 | // name a call has already been given the narrower ask, and adding the |
| 1036 | // address to it would rebuild the screen the fragment was about to land |
| 1037 | // in. |
| 1038 | if out.is_empty |
| 1039 | && self.screen.is_live |
| 1040 | && let Some = &self.here |
| 1041 | |
| 1042 | out.push; |
| 1043 | |
| 1044 | // Stamped even when the screen has nothing live, so a still screen is |
| 1045 | // not re-walked on every frame an egui host draws. |
| 1046 | self.refreshed = Some; |
| 1047 | out |
| 1048 | |
| 1049 | |
| 1050 | /// Whether anything on this screen changes without the user. |
| 1051 | /// |
| 1052 | /// True for a live region whether or not it names a call, which is the |
| 1053 | /// difference from [`refreshes`](Self::refreshes): a region reading state |
| 1054 | /// the host already holds has nothing to ask for and still has to be |
| 1055 | /// redrawn. |
| 1056 | |
| 1057 | |
| 1058 | self.screen.is_live |
| 1059 | |
| 1060 | |
| 1061 | /// How long this screen may sit before it has to be drawn again for its |
| 1062 | /// own sake, if it holds anything that goes stale on its own. |
| 1063 | /// |
| 1064 | /// What [`show`](Self::show) asks the context for. `None` for a screen |
| 1065 | /// holding no time-derived readout, which is nearly all of them, and this |
| 1066 | /// host then sleeps as it did before they existed. |
| 1067 | /// |
| 1068 | /// Independent of [`refreshes`](Self::refreshes): that one asks something |
| 1069 | /// over a network on [`CADENCE`](crate::CADENCE), and this is arithmetic |
| 1070 | /// egui redoes itself the moment it is handed a frame. |
| 1071 | |
| 1072 | |
| 1073 | self.tick_in_at |
| 1074 | |
| 1075 | |
| 1076 | /// [`tick_in`](Self::tick_in) against a clock the caller holds. |
| 1077 | /// |
| 1078 | /// The seam a test needs, and the one a frame that already knows what time |
| 1079 | /// it is should reach for rather than asking again. |
| 1080 | |
| 1081 | |
| 1082 | let clocks = self.screen.clocks.into_iter.map.min; |
| 1083 | let toasts = self |
| 1084 | .raised |
| 1085 | .iter |
| 1086 | .map |
| 1087 | .min; |
| 1088 | clocks.into_iter.chain.min |
| 1089 | |
| 1090 | |
| 1091 | /// Take away every toast whose time is up, and say whether one went. |
| 1092 | /// |
| 1093 | /// `Notice::Toast` says the message goes away on its own, and this is the |
| 1094 | /// host keeping that promise. [`show`](Self::show) calls it itself once a |
| 1095 | /// frame and asks the context for the frame that will find the deadline, |
| 1096 | /// so an app drawing this runtime gets it without doing anything: an |
| 1097 | /// immediate host is already redrawing, and what it lacked was somebody to |
| 1098 | /// take the message off the screen. |
| 1099 | /// |
| 1100 | /// A banner is never touched. It goes when the condition it reports is |
| 1101 | /// fixed, which is a route's business and not a clock's. |
| 1102 | |
| 1103 | self.expires_at |
| 1104 | |
| 1105 | |
| 1106 | /// [`expires`](Self::expires) against a clock the caller holds. |
| 1107 | |
| 1108 | if self.raised.is_empty |
| 1109 | return false; |
| 1110 | |
| 1111 | let raised = take; |
| 1112 | let mut ages = raised.into_iter; |
| 1113 | let mut kept = Vecnew; |
| 1114 | let before = self.screen.notices.len; |
| 1115 | self.screen.notices.retain |
| 1116 | let Notice = node else |
| 1117 | return true; |
| 1118 | ; |
| 1119 | // `eea7ba88`. One call rather than a transience check and a |
| 1120 | // constant: `None` is a notice with no lifetime, which is a banner, |
| 1121 | // and it is kept for the same reason the check used to keep it. |
| 1122 | let Some = notice_lifetime else |
| 1123 | return true; |
| 1124 | ; |
| 1125 | // A toast with no instant beside it is one this runtime never saw |
| 1126 | // raised, which nothing in the crate produces. It is given now |
| 1127 | // rather than dropped: an unexplained toast on the screen is a |
| 1128 | // smaller wrong than a message the user never got to read. |
| 1129 | let at = ages.next.unwrap_or; |
| 1130 | let up = now.duration_since >= lifetime; |
| 1131 | if !up |
| 1132 | kept.push; |
| 1133 | |
| 1134 | !up |
| 1135 | ; |
| 1136 | self.raised = kept; |
| 1137 | self.screen.notices.len != before |
| 1138 | |
| 1139 | |
| 1140 | /// Start every toast on the screen lingering from now. |
| 1141 | /// |
| 1142 | /// A whole screen arriving brings whatever notices it was described with, |
| 1143 | /// and a screen coming back out from under an overlay is in front of the |
| 1144 | /// user again. Both are the moment the reading starts, so both reset the |
| 1145 | /// clock rather than trying to remember one from before. |
| 1146 | |
| 1147 | let toasts = self |
| 1148 | .screen |
| 1149 | .notices |
| 1150 | .iter |
| 1151 | .filter |
| 1152 | .count; |
| 1153 | self.raised = vec!; |
| 1154 | |
| 1155 | |
| 1156 | /// What the control that was pressed is waiting on, if one is. |
| 1157 | /// |
| 1158 | /// Carries the amount when the description measured one. Nothing here turns |
| 1159 | /// it into a time: a bar shows what is done over what there is and how long |
| 1160 | /// it has taken, and predicts nothing. |
| 1161 | |
| 1162 | |
| 1163 | self.outstanding |
| 1164 | .as_ref |
| 1165 | .and_then |
| 1166 | |
| 1167 | |
| 1168 | |
| 1169 | /// A key name as egui's key and modifiers. |
| 1170 | /// |
| 1171 | /// The same string comparison `Act::key` gets, resolved against this host's |
| 1172 | /// keyboard rather than the description's idea of one. A name this renderer |
| 1173 | /// cannot read is `None`, which is what a binding written for another host |
| 1174 | /// should do here. |
| 1175 | /// Whether a key held with these modifiers is one a text box would swallow. |
| 1176 | /// |
| 1177 | /// Bare keys and shift-keys are what typing is made of, so a box with the |
| 1178 | /// focus is already answering them and a binding on one is asking for the same |
| 1179 | /// press twice. Anything held with ctrl, alt or command produces no character, |
| 1180 | /// which is why every desktop app puts its shortcuts there and why Cmd+Z keeps |
| 1181 | /// working mid-word. |
| 1182 | /// |
| 1183 | /// The alternative was the shipped app's rule -- suppress every binding while |
| 1184 | /// anything has focus -- and it is too broad in two directions at once: it kills |
| 1185 | /// `ctrl+t` while typing, and it fires on a *button* reached by Tab, which |
| 1186 | /// swallows no letters at all. `text_edit_focused` answers the narrower |
| 1187 | /// question, and this answers the other half of it. |
| 1188 | /// |
| 1189 | /// A `Binding` saying for itself that it is safe while typing was refused with |
| 1190 | /// the finding: which keys a box eats is a fact about this keyboard and this |
| 1191 | /// host, so a description that answered it would be answering for hosts it has |
| 1192 | /// never seen. |
| 1193 | pub |
| 1194 | ! |
| 1195 | |
| 1196 | |
| 1197 | |
| 1198 | let mut modifiers = NONE; |
| 1199 | let mut base = None; |
| 1200 | for part in key.split |
| 1201 | let part = part.trim; |
| 1202 | if part.is_empty |
| 1203 | return None; |
| 1204 | |
| 1205 | match part.to_ascii_lowercase.as_str |
| 1206 | "ctrl" | "control" => modifiers.ctrl = true, |
| 1207 | "alt" | "option" => modifiers.alt = true, |
| 1208 | "shift" => modifiers.shift = true, |
| 1209 | "meta" | "cmd" | "super" => modifiers.command = true, |
| 1210 | _ if base.is_some => return None, |
| 1211 | other => base = from_name.or_else, |
| 1212 | |
| 1213 | |
| 1214 | base.map |
| 1215 | |
| 1216 |