max / quasi
| 1 | //! What the app offers on every screen, rather than on one of them. |
| 2 | //! |
| 3 | //! A [`Screen`](crate::Screen) names one place. Everything here outlives any one |
| 4 | //! of them: a command palette reachable from anywhere, a global shortcut, the |
| 5 | //! help overlay that lists the shortcuts. None of that is a fact about the |
| 6 | //! screen the user happens to be on, and describing it per screen means |
| 7 | //! repeating it on every screen or reimplementing it per host. |
| 8 | //! |
| 9 | //! # Why this is not [`RegionKind::Modal`](crate::RegionKind::Modal) |
| 10 | //! |
| 11 | //! That is a modal a screen *contains*, which is how a confirmation is drawn: |
| 12 | //! the screen carries it, and it goes when the screen goes. Chrome belongs to |
| 13 | //! the app, so it is reachable from screens that know nothing about it. The |
| 14 | //! same move [`Screen::notices`](crate::Screen::notices) made one level down, |
| 15 | //! when a notice stopped belonging to a region and started belonging to the |
| 16 | //! screen. |
| 17 | //! |
| 18 | //! # What it costs, and what it does not |
| 19 | //! |
| 20 | //! An overlay's *contents* were always sayable: a query, a result list, a |
| 21 | //! keyboard walk through it, an [`Act`](crate::Act) that navigates. What was |
| 22 | //! missing was a way to say "fetched from a route, and drawn over what is under |
| 23 | //! it", which is [`Outcome::Over`](crate::Outcome::Over) and not a second |
| 24 | //! description tree. So an overlay is a [`Screen`](crate::Screen) like any |
| 25 | //! other, and this module is only the way in. |
| 26 | //! |
| 27 | //! A toast stack needs nothing from here: [`Screen::notices`](crate::Screen::notices) |
| 28 | //! and [`Message`](crate::Message) already carry notices, and how they stack is |
| 29 | //! renderer policy. An app-modal is the overlay case with one region in it. |
| 30 | |
| 31 | use crateAction; |
| 32 | |
| 33 | /// The affordances the app offers from every screen. |
| 34 | /// |
| 35 | /// Built once by the app and held beside the [`Router`](crate::Router), never |
| 36 | /// per request. That is what "outlives any one screen" means concretely: a |
| 37 | /// request answers with a screen, and this is not part of that answer. |
| 38 | /// |
| 39 | /// Beside the router rather than inside it, decided while building this: a |
| 40 | /// `Router` is a route table, and a key binding is not a route. The two are |
| 41 | /// held together by whatever the host is, which already holds both. Nothing |
| 42 | /// here would break if it moved inside, so this is a tidiness argument rather |
| 43 | /// than a correctness one. |
| 44 | /// |
| 45 | /// An app declaring no chrome behaves exactly as it did before this existed. |
| 46 | |
| 47 | |
| 48 | /// The keys that work from anywhere. |
| 49 | pub bindings: , |
| 50 | |
| 51 | |
| 52 | /// A key that works from every screen, and what it calls. |
| 53 | |
| 54 | |
| 55 | /// The key, as text. |
| 56 | /// |
| 57 | /// Text rather than a modelled chord — "ctrl+k", "?" — for the reason |
| 58 | /// [`Act::key`](crate::Act::key) is: the vocabulary of keys is the host's, |
| 59 | /// and a description that modelled it would be naming one host's keyboard. |
| 60 | /// A renderer that does not know a name ignores it, which is what a webview |
| 61 | /// does with a key a terminal wants. |
| 62 | pub key: String, |
| 63 | /// What a shortcuts list shows for it. |
| 64 | /// |
| 65 | /// The reason this is a struct and not a `(String, Action)` pair. A help |
| 66 | /// overlay that lists the bindings is otherwise a second, hand-written copy |
| 67 | /// of them, free to drift from what the keys actually do. |
| 68 | pub label: String, |
| 69 | /// What pressing it calls. |
| 70 | /// |
| 71 | /// Ordinarily a route answering with [`Response::over`](crate::Response::over), |
| 72 | /// which is what makes the palette an overlay rather than a navigation. It |
| 73 | /// is not required to: a binding that navigates is a binding that navigates. |
| 74 | pub action: Action, |
| 75 | |
| 76 | |
| 77 | |
| 78 | /// No chrome. What an app that declares none has. |
| 79 | |
| 80 | |
| 81 | Selfdefault |
| 82 | |
| 83 | |
| 84 | /// Add a key that works from every screen. |
| 85 | |
| 86 | |
| 87 | mut self, |
| 88 | key: impl , |
| 89 | label: impl , |
| 90 | action: Action, |
| 91 | |
| 92 | self.bindings.push |
| 93 | key: key.into, |
| 94 | label: label.into, |
| 95 | action, |
| 96 | ; |
| 97 | self |
| 98 | |
| 99 | |
| 100 | /// What the key calls, if anything claimed it. |
| 101 | /// |
| 102 | /// First match wins, so an app that binds one key twice gets the one it |
| 103 | /// declared first rather than an error. Matching is exact: normalising |
| 104 | /// "ctrl+k" against "Ctrl+K" would be this crate deciding what a key name |
| 105 | /// looks like, which is the host's to decide. |
| 106 | |
| 107 | |
| 108 | self.bindings.iter.find |
| 109 | |
| 110 | |
| 111 | |
| 112 | |
| 113 | |
| 114 | use *; |
| 115 | |
| 116 | |
| 117 | |
| 118 | let chrome = new; |
| 119 | assert!; |
| 120 | assert!; |
| 121 | |
| 122 | |
| 123 | |
| 124 | |
| 125 | let chrome = new |
| 126 | .bind |
| 127 | .bind; |
| 128 | let found = chrome.bound.expect; |
| 129 | assert_eq!; |
| 130 | assert_eq!; |
| 131 | assert_eq!; |
| 132 | |
| 133 | |
| 134 | |
| 135 | |
| 136 | let chrome = new.bind; |
| 137 | // Exact match: normalising case or modifier order would be this crate |
| 138 | // deciding what a key name looks like. |
| 139 | assert!; |
| 140 | assert!; |
| 141 | |
| 142 | |
| 143 | |
| 144 | |
| 145 | let chrome = new |
| 146 | .bind |
| 147 | .bind; |
| 148 | assert_eq!; |
| 149 | |
| 150 | |
| 151 |