Skip to main content

makeover_layout/
act.rs

1use crate::{State, Tone};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
4pub struct Act<'a> {
5    /// What the control says.
6    pub label: &'a str,
7    /// The key that reaches it where a host has keys.
8    ///
9    /// The one member written for a terminal before there was one. A webview
10    /// hangs it off `accesskey` or ignores it; a terminal has nothing else to
11    /// offer, so this is the whole of how a control is reached there.
12    pub key: Option<&'a str>,
13    /// What pressing it means. [`Tone::Danger`] is the destructive one.
14    pub tone: Tone,
15    /// Disabled, or nothing said.
16    ///
17    /// [`State::Disabled`] is what changes what a renderer may do: see
18    /// [`State::suppresses_interaction`], which is what says a disabled control
19    /// is drawn and not reachable. A control's focus is not sayable here at
20    /// all: see the crate header, "Reach, focus and the focus ring".
21    pub state: Option<State>,
22    /// A sentence that is always true of this control, shown rather than hunted
23    /// for.
24    ///
25    /// Standing help, not a message and not a tooltip. Half the hosts that read
26    /// this have no pointer: a hover is one spelling of it, and the shipped
27    /// apps reached for that spelling only because egui and a browser both had
28    /// one. What is being said is that the sentence is true, never that it is
29    /// hidden until a pointer arrives.
30    ///
31    /// # Why it is here rather than a layer up
32    ///
33    /// A hint left to `quasi_router::Act` alone means each renderer draws it
34    /// for itself: `makeover_tui` had no hint to read, so quasi-tui built
35    /// the muted line, and quasi-immediate called `on_hover_text` outside
36    /// [`crate::Act`] rather than inside it. `Field::hint` was here the whole
37    /// time, so the same idea sat at two layers depending on which thing
38    /// carried it, and a host that was not quasi could say it of a field and
39    /// not of a control.
40    ///
41    /// What kept it out was price rather than doubt: this crate declares
42    /// `links`, so a member here moves 25 manifests across 12 repos. That is a
43    /// release's forward-fix pass, which is a cost and was being read as a
44    /// barrier.
45    ///
46    /// # What a renderer owes it
47    ///
48    /// Somewhere to put it, or nothing. Dropping it is legitimate; drawing it
49    /// *instead of* the label is not, and neither is drawing it in a way that
50    /// takes it out of the accessible tree, which is the failure `title` alone
51    /// has on a browser. Nothing may live only in a hint.
52    ///
53    /// `None` is a control whose label is the whole of it, which is nearly all
54    /// of them.
55    pub hint: Option<&'a str>,
56    /// Whether pressing it commits: the one control that locks in what the
57    /// reader has staged.
58    ///
59    /// A form's submit, or the verb over a set of ticked rows. Wiki
60    /// `explicit-commit-affordance` is the ruling: changing a control stages a
61    /// change, and a separate, visible control commits it, and the commit is
62    /// what the reader sees. So the control has to be told apart from the
63    /// controls beside it that only navigate or stage, and wiki
64    /// `look-restoration` says how far that goes: the mark goes on the control
65    /// that commits and on nothing else, not on every control that writes.
66    ///
67    /// # What a renderer owes it
68    ///
69    /// A spelling the reader can pick out at a glance, and the same one every
70    /// time. A webview draws the default-button ring the Platinum specimen
71    /// picked and never shipped; a terminal writes `[ Label ]` where an
72    /// ordinary control is `< Label >`; an immediate-mode host strokes a frame
73    /// round the button. Nothing else changes: a committing control takes its
74    /// tone, its key, its state and its hint exactly as any other.
75    ///
76    /// # Why a member and not a renderer's inference
77    ///
78    /// Each renderer could guess it from the node a control sits in, and each
79    /// was left to: `makeover-tui` grew `filled_act` for a form's submit, which
80    /// no caller in the tree reached, while the webview drew every submit like
81    /// every other button. A fact the description has is said here once, which
82    /// is the reason [`hint`](Self::hint) is here too.
83    ///
84    /// `false` on nearly every control.
85    pub commits: bool,
86    /// Whether this is the act the screen is *for*: the one thing a reader
87    /// came to this screen to do.
88    ///
89    /// Reply on a mail reader, Start or Complete on a task, Queue on a
90    /// compose. At most one per screen, and most screens have none, because a
91    /// list is for reading.
92    ///
93    /// # Why it is not [`commits`](Self::commits)
94    ///
95    /// They answer different questions and a screen can want both on different
96    /// controls. `commits` is "pressing this locks in what you staged", which
97    /// wiki `explicit-commit-affordance` and `look-restoration` reserve for the
98    /// control that commits *and nothing else*. A sub-form's Add commits
99    /// honestly under that rule, so on goingson's task overview the boldest
100    /// control on the screen was a subtask's Add while the screen's own
101    /// Start/Complete was an ordinary button. Widening `commits` to mean
102    /// "primary" would break a settled ruling and put every sub-form's Add back
103    /// in competition; this is the other axis instead.
104    ///
105    /// The two compose. A form that *is* the screen's work carries both, and a
106    /// renderer draws both marks on it.
107    ///
108    /// # Why a member and not a renderer's inference
109    ///
110    /// [`commits`](Self::commits)' own test, applied to itself: which act a
111    /// screen is for is a fact the description has and no renderer can work
112    /// out. The nearest guess, that a band's acts outrank a body's, gets the
113    /// mail reader and the compose window wrong: their primary act sits in a
114    /// pane. It also makes emphasis a function of where a control happens to
115    /// sit.
116    ///
117    /// # What a renderer owes it
118    ///
119    /// A spelling distinct from the commit mark, since a control may wear
120    /// both. The suite's answer is that **leading fills and committing
121    /// outlines**: a webview gives it the `--action` ground with
122    /// `--content-on-action` on it while the commit ring stays a frame outside
123    /// the bevel; a terminal reverses it, keeping `[ ]` for the commit; an
124    /// immediate-mode host fills the button rather than stroking it. Fill and
125    /// frame are separable, so a control that is both reads as both.
126    ///
127    /// `false` on nearly every control, and on every control of a screen that
128    /// is only for reading.
129    pub leading: bool,
130}
131
132impl<'a> Act<'a> {
133    /// An ordinary control, reachable, with no key.
134    #[must_use]
135    pub const fn new(label: &'a str) -> Self {
136        Self {
137            label,
138            key: None,
139            tone: Tone::Neutral,
140            state: None,
141            hint: None,
142            commits: false,
143            leading: false,
144        }
145    }
146
147    /// The control that commits what the reader staged; see
148    /// [`commits`](Self::commits).
149    #[must_use]
150    pub const fn committing(mut self) -> Self {
151        self.commits = true;
152        self
153    }
154
155    /// The act this screen is for; see [`leading`](Self::leading).
156    ///
157    /// Separate from [`committing`](Self::committing) and freely combined with
158    /// it: a form that is the screen's own work is both.
159    #[must_use]
160    pub const fn leading(mut self) -> Self {
161        self.leading = true;
162        self
163    }
164
165    /// The sentence that is always true of it; see [`hint`](Self::hint).
166    ///
167    /// A renderer with nowhere to put it drops it, so this must never be the
168    /// only place a fact appears.
169    #[must_use]
170    pub const fn hinted(mut self, hint: &'a str) -> Self {
171        self.hint = Some(hint);
172        self
173    }
174
175    /// The key that reaches it.
176    #[must_use]
177    pub const fn key(mut self, key: &'a str) -> Self {
178        self.key = Some(key);
179        self
180    }
181
182    /// What pressing it means.
183    #[must_use]
184    pub const fn tone(mut self, tone: Tone) -> Self {
185        self.tone = tone;
186        self
187    }
188
189    /// Focus, or disabled.
190    #[must_use]
191    pub const fn state(mut self, state: State) -> Self {
192        self.state = Some(state);
193        self
194    }
195
196    /// Whether the control is drawn and does not answer.
197    #[must_use]
198    pub fn disabled(&self) -> bool {
199        self.state.is_some_and(State::suppresses_interaction)
200    }
201}