Skip to main content

max / quasi

5.6 KB · 154 lines History Blame Raw
1 //! What the user has done to a screen since it arrived.
2 //!
3 //! Two of the five things `quasi-tui`'s `View` holds, and the other three are
4 //! egui's. That is the whole difference between the two crates' state, and it is
5 //! worth saying which is which so the next reader does not go looking for the
6 //! missing ones:
7 //!
8 //! | Fact | terminal | here |
9 //! |---|---|---|
10 //! | what is typed | `View` | **`View`** |
11 //! | what is ticked | `View` | **`View`** |
12 //! | what has focus | `View` | egui's id stack |
13 //! | how far a pane is scrolled | `View` | `egui::ScrollArea` |
14 //! | where back goes | `Runtime` | [`Runtime`](crate::Runtime) |
15 //!
16 //! **Why typing is not egui's, when focus is.** egui holds widget state against
17 //! an id, and a described field is rebuilt from the description every frame; its
18 //! `TextEdit` needs a `&mut String` that outlives the frame, and the description
19 //! deliberately does not carry the value. So the buffer is the app's, held here.
20 //! That is the same conclusion `makeover-immediate`'s `Filling` reached one layer
21 //! down and for the same reason.
22
23 use std::collections::{BTreeMap, BTreeSet};
24
25 use quasi_router::{Node, Params, Screen};
26
27 /// What the user has done to a screen since it arrived.
28 ///
29 /// A host makes one beside the screen it is holding and keeps the two together.
30 /// Empty is the honest starting state and it draws exactly what the description
31 /// says.
32 #[derive(Debug, Clone, Default, PartialEq, Eq)]
33 pub struct View {
34 /// What has been typed, by [`Field::name`](quasi_router::Field::name).
35 ///
36 /// Absent means untouched, which is different from present and empty: one
37 /// draws the description's value and the other draws a box the user has
38 /// cleared.
39 edits: BTreeMap<String, String>,
40 /// What has been ticked, by [`Row::value`](quasi_router::Row::value).
41 ticked: BTreeSet<String>,
42 }
43
44 impl View {
45 /// Nothing typed and nothing ticked.
46 #[must_use]
47 pub fn new() -> Self {
48 Self::default()
49 }
50
51 /// What a field is showing: what was typed, or what the description offers.
52 #[must_use]
53 pub fn showing<'a>(&'a self, name: &str, described: Option<&'a str>) -> &'a str {
54 self.edits
55 .get(name)
56 .map_or(described.unwrap_or_default(), String::as_str)
57 }
58
59 /// The buffer a text control writes through, seeded from the description.
60 ///
61 /// `&mut` because that is what an immediate-mode text control takes: there
62 /// is no DOM to read the value back out of afterwards. Seeding on first
63 /// touch rather than up front is what keeps "untouched" distinguishable
64 /// from "cleared".
65 pub fn buffer(&mut self, name: &str, described: Option<&str>) -> &mut String {
66 self.edits
67 .entry(name.to_owned())
68 .or_insert_with(|| described.unwrap_or_default().to_owned())
69 }
70
71 /// What has been typed into a field, if anything has.
72 #[must_use]
73 pub fn edit(&self, name: &str) -> Option<&str> {
74 self.edits.get(name).map(String::as_str)
75 }
76
77 /// Put a value in, as a host restoring one would.
78 pub fn set(&mut self, name: impl Into<String>, value: impl Into<String>) {
79 self.edits.insert(name.into(), value.into());
80 }
81
82 /// Whether a row's value is in the screen's selection.
83 #[must_use]
84 pub fn is_ticked(&self, value: &str) -> bool {
85 self.ticked.contains(value)
86 }
87
88 /// Add or remove a row's value from the selection.
89 pub fn tick(&mut self, value: &str) {
90 if !self.ticked.remove(value) {
91 self.ticked.insert(value.to_owned());
92 }
93 }
94
95 /// Everything ticked, in a stable order.
96 pub fn ticks(&self) -> impl Iterator<Item = &str> {
97 self.ticked.iter().map(String::as_str)
98 }
99
100 /// The rows a new screen says are already ticked.
101 ///
102 /// Applied once on arrival rather than read on every draw: after this the
103 /// user's ticks are the truth, and a description that kept overriding them
104 /// would undo a tick the moment anything redrew.
105 pub fn seed(&mut self, screen: &Screen) {
106 for slot in &screen.slots {
107 for node in &slot.body {
108 if let Node::List { rows, .. } = node {
109 for row in rows {
110 if let (Some(true), Some(value)) = (row.selected, row.value.as_ref()) {
111 self.ticked.insert(value.clone());
112 }
113 }
114 }
115 }
116 }
117 }
118
119 /// Forget everything, for a screen that has been replaced.
120 pub fn reset(&mut self) {
121 self.edits.clear();
122 self.ticked.clear();
123 }
124
125 /// The values a form submits, by the names it declared.
126 ///
127 /// Every declared name is sent, including the ones nothing was typed into,
128 /// because a form that omits an untouched field is a form that cannot clear
129 /// one. What is sent for those is whatever the description offered.
130 #[must_use]
131 pub fn submission(&self, names: &[String], described: &BTreeMap<String, String>) -> Params {
132 let mut params = Params::new();
133 for name in names {
134 let value = self
135 .edits
136 .get(name)
137 .or_else(|| described.get(name))
138 .map_or("", String::as_str);
139 params = params.with(name.clone(), value.to_owned());
140 }
141 params
142 }
143
144 /// What the screen's selection sends with an action taken over it.
145 #[must_use]
146 pub fn gathering(&self, under: &str) -> Params {
147 let mut params = Params::new();
148 for value in self.ticks() {
149 params = params.with(under.to_owned(), value.to_owned());
150 }
151 params
152 }
153 }
154