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}
87
88impl<'a> Act<'a> {
89    /// An ordinary control, reachable, with no key.
90    #[must_use]
91    pub const fn new(label: &'a str) -> Self {
92        Self {
93            label,
94            key: None,
95            tone: Tone::Neutral,
96            state: None,
97            hint: None,
98            commits: false,
99        }
100    }
101
102    /// The control that commits what the reader staged; see
103    /// [`commits`](Self::commits).
104    #[must_use]
105    pub const fn committing(mut self) -> Self {
106        self.commits = true;
107        self
108    }
109
110    /// The sentence that is always true of it; see [`hint`](Self::hint).
111    ///
112    /// A renderer with nowhere to put it drops it, so this must never be the
113    /// only place a fact appears.
114    #[must_use]
115    pub const fn hinted(mut self, hint: &'a str) -> Self {
116        self.hint = Some(hint);
117        self
118    }
119
120    /// The key that reaches it.
121    #[must_use]
122    pub const fn key(mut self, key: &'a str) -> Self {
123        self.key = Some(key);
124        self
125    }
126
127    /// What pressing it means.
128    #[must_use]
129    pub const fn tone(mut self, tone: Tone) -> Self {
130        self.tone = tone;
131        self
132    }
133
134    /// Focus, or disabled.
135    #[must_use]
136    pub const fn state(mut self, state: State) -> Self {
137        self.state = Some(state);
138        self
139    }
140
141    /// Whether the control is drawn and does not answer.
142    #[must_use]
143    pub fn disabled(&self) -> bool {
144        self.state.is_some_and(State::suppresses_interaction)
145    }
146}