Skip to main content

makeover_layout/
act.rs

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