Skip to main content

max / quasi

4.5 KB · 129 lines History Blame Raw
1 //! What the reader has done that a walk over the description has to know.
2 //!
3 //! Three walks read a screen and they have to agree exactly: the focus walk
4 //! decides how many stops there are, the height walk decides how many rows each
5 //! node wants, and the drawing counts stops as it paints. A description alone is
6 //! not enough for any of them, because two things about a screen are the
7 //! reader's rather than the handler's:
8 //!
9 //! - which regions and which questions do not apply right now, from
10 //! [`crate::reveal::hidden`] (`079a011e`, `8fdb814c`), and
11 //! - how many slots a repeating question stands in, from [`View::standing`]
12 //! (`60d1753c`).
13 //!
14 //! They are one type because they are one fact — what the reader has done
15 //! since the screen arrived — and because a walk that took one and not the
16 //! other is a walk that can disagree with the one beside it about how many
17 //! stops a screen has, which is a focus ring painted on the wrong control.
18 //!
19 //! [`Local::none`] is the honest answer for a caller with no view: every region
20 //! applies and every repeating question stands in the slots the description
21 //! described. That is what a screen drew before either member existed.
22
23 use quasi_router::Field;
24
25 use crate::View;
26
27 /// What does not apply right now, on one screen, as the reader has left it.
28 ///
29 /// Regions and questions in one type because they are one answer to one
30 /// question and are computed in one walk. A caller holding the regions and not
31 /// the fields would draw a box the caret cannot stop on, which is the same
32 /// disagreement [`Local`] exists to prevent one turn further out.
33 #[derive(Debug, Clone, Default)]
34 pub struct Hidden<'a> {
35 /// The regions that are not out, by [`Slot::id`].
36 ///
37 /// [`Slot::id`]: quasi_router::Slot::id
38 pub regions: Vec<&'a str>,
39 /// The questions that are not out, by [`Field::name`].
40 pub fields: Vec<&'a str>,
41 }
42
43 impl Hidden<'_> {
44 /// Everything applies, which is what a caller with nothing to evaluate
45 /// against hands on and what a walk that must see the whole description
46 /// passes deliberately.
47 #[must_use]
48 pub const fn none() -> Self {
49 Self {
50 regions: Vec::new(),
51 fields: Vec::new(),
52 }
53 }
54 }
55
56 /// The reader's own half of a screen, as the walks over it need it.
57 #[derive(Debug, Clone, Copy)]
58 pub struct Local<'a> {
59 /// The regions and questions that do not apply right now.
60 hidden: &'a Hidden<'a>,
61 /// What the reader has typed, ticked and added, when there is one to read.
62 view: Option<&'a View>,
63 }
64
65 impl<'a> Local<'a> {
66 /// Nothing hidden, and every repeating question in the slots the
67 /// description gave it.
68 #[must_use]
69 pub const fn none() -> Self {
70 Self {
71 hidden: &NOTHING,
72 view: None,
73 }
74 }
75
76 /// These regions out, and the reader's slots counted from this view.
77 #[must_use]
78 pub const fn of(hidden: &'a Hidden<'a>, view: &'a View) -> Self {
79 Self {
80 hidden,
81 view: Some(view),
82 }
83 }
84
85 /// These regions out, and nothing known about what the reader has added.
86 ///
87 /// What a caller answering "how many stops does this screen describe"
88 /// passes: the two halves are independent, and a walk that wants one of
89 /// them should not have to invent the other.
90 #[must_use]
91 pub const fn hiding(hidden: &'a Hidden<'a>) -> Self {
92 Self { hidden, view: None }
93 }
94
95 /// The reader's own state, when the caller had one to hand over.
96 #[must_use]
97 pub const fn view(&self) -> Option<&'a View> {
98 self.view
99 }
100
101 /// Whether this region does not apply right now.
102 #[must_use]
103 pub fn out(&self, id: &str) -> bool {
104 self.hidden.regions.contains(&id)
105 }
106
107 /// Whether this question does not apply right now.
108 ///
109 /// By name, which is what the description carries and what a submit sends
110 /// the value under.
111 #[must_use]
112 pub fn field_out(&self, name: &str) -> bool {
113 self.hidden.fields.contains(&name)
114 }
115
116 /// How many slots this question stands in.
117 ///
118 /// `1` for an ordinary field, which is what makes every walk able to loop
119 /// over the slots of anything without asking whether there are any.
120 #[must_use]
121 pub fn standing(&self, field: &Field) -> usize {
122 self.view
123 .map_or_else(|| field.slots(), |view| view.standing(field))
124 }
125 }
126
127 /// The empty answer, so [`Local::none`] can stay a `const fn`.
128 static NOTHING: Hidden<'static> = Hidden::none();
129