| 1156 |
1156 |
|
}
|
| 1157 |
1157 |
|
}
|
| 1158 |
1158 |
|
|
|
1159 |
+ |
/// Something the user can do, and what it costs to say so.
|
|
1160 |
+ |
///
|
|
1161 |
+ |
/// Added 0.17.0, out of `quasi-tui`: the terminal renderer had drawn one of
|
|
1162 |
+ |
/// these for months and every other consumer that wanted a button had written
|
|
1163 |
+ |
/// its own, because this layer named [`RowPart::Actions`] as a *slot* and never
|
|
1164 |
+ |
/// named the thing that goes in it. Beside [`Meter`] and [`Figure`] for the
|
|
1165 |
+ |
/// reason those are here: a renderer that is handed the parts has to decide how
|
|
1166 |
+ |
/// to say them, and a renderer that is handed a finished string has already had
|
|
1167 |
+ |
/// the decision made for it.
|
|
1168 |
+ |
///
|
|
1169 |
+ |
/// No address. Where a control goes is the app's business and every host
|
|
1170 |
+ |
/// follows it differently — an `hx-get`, a protocol URL, a function call — so
|
|
1171 |
+ |
/// the description says what the control *is* and the caller keeps what it
|
|
1172 |
+ |
/// does. That is the same split [`Choice`] makes.
|
|
1173 |
+ |
///
|
|
1174 |
+ |
/// No confirmation flag either, and that one is a finding rather than an
|
|
1175 |
+ |
/// omission: a question asked *after* a control is pressed belongs to whatever
|
|
1176 |
+ |
/// is holding the interaction, and a renderer that drew it would be asking
|
|
1177 |
+ |
/// before there was anything to answer.
|
|
1178 |
+ |
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
1179 |
+ |
pub struct Act<'a> {
|
|
1180 |
+ |
/// What the control says.
|
|
1181 |
+ |
pub label: &'a str,
|
|
1182 |
+ |
/// The key that reaches it where a host has keys.
|
|
1183 |
+ |
///
|
|
1184 |
+ |
/// The one member written for a terminal before there was one. A webview
|
|
1185 |
+ |
/// hangs it off `accesskey` or ignores it; a terminal has nothing else to
|
|
1186 |
+ |
/// offer, so this is the whole of how a control is reached there.
|
|
1187 |
+ |
pub key: Option<&'a str>,
|
|
1188 |
+ |
/// What pressing it means. [`Tone::Danger`] is the destructive one.
|
|
1189 |
+ |
pub tone: Tone,
|
|
1190 |
+ |
/// Focus, disabled, or neither.
|
|
1191 |
+ |
///
|
|
1192 |
+ |
/// [`State::Disabled`] is the one that changes what a renderer may do:
|
|
1193 |
+ |
/// see [`State::suppresses_interaction`], which is what says a disabled
|
|
1194 |
+ |
/// control is drawn and not reachable.
|
|
1195 |
+ |
pub state: Option<State>,
|
|
1196 |
+ |
}
|
|
1197 |
+ |
|
|
1198 |
+ |
impl<'a> Act<'a> {
|
|
1199 |
+ |
/// An ordinary control, reachable, with no key.
|
|
1200 |
+ |
#[must_use]
|
|
1201 |
+ |
pub const fn new(label: &'a str) -> Self {
|
|
1202 |
+ |
Self {
|
|
1203 |
+ |
label,
|
|
1204 |
+ |
key: None,
|
|
1205 |
+ |
tone: Tone::Neutral,
|
|
1206 |
+ |
state: None,
|
|
1207 |
+ |
}
|
|
1208 |
+ |
}
|
|
1209 |
+ |
|
|
1210 |
+ |
/// The key that reaches it.
|
|
1211 |
+ |
#[must_use]
|
|
1212 |
+ |
pub const fn key(mut self, key: &'a str) -> Self {
|
|
1213 |
+ |
self.key = Some(key);
|
|
1214 |
+ |
self
|
|
1215 |
+ |
}
|
|
1216 |
+ |
|
|
1217 |
+ |
/// What pressing it means.
|
|
1218 |
+ |
#[must_use]
|
|
1219 |
+ |
pub const fn tone(mut self, tone: Tone) -> Self {
|
|
1220 |
+ |
self.tone = tone;
|
|
1221 |
+ |
self
|
|
1222 |
+ |
}
|
|
1223 |
+ |
|
|
1224 |
+ |
/// Focus, or disabled.
|
|
1225 |
+ |
#[must_use]
|
|
1226 |
+ |
pub const fn state(mut self, state: State) -> Self {
|
|
1227 |
+ |
self.state = Some(state);
|
|
1228 |
+ |
self
|
|
1229 |
+ |
}
|
|
1230 |
+ |
|
|
1231 |
+ |
/// Whether the control is drawn and does not answer.
|
|
1232 |
+ |
#[must_use]
|
|
1233 |
+ |
pub fn disabled(&self) -> bool {
|
|
1234 |
+ |
self.state.is_some_and(State::suppresses_interaction)
|
|
1235 |
+ |
}
|
|
1236 |
+ |
}
|
|
1237 |
+ |
|
| 1159 |
1238 |
|
/// A named part of a screen.
|
| 1160 |
1239 |
|
///
|
| 1161 |
1240 |
|
/// The thing `makeover-geometry` deliberately does not name: it names the space
|
| 2037 |
2116 |
|
assert_eq!(Meter::new(9, 10).tone, Tone::Neutral);
|
| 2038 |
2117 |
|
}
|
| 2039 |
2118 |
|
|
|
2119 |
+ |
#[test]
|
|
2120 |
+ |
fn an_act_is_reachable_until_it_is_disabled() {
|
|
2121 |
+ |
// The one member a renderer must branch on. Focus is a state and does
|
|
2122 |
+ |
// not stop the control answering, which is the distinction `State`
|
|
2123 |
+ |
// makes and every hand-rolled button in the tree had to remember.
|
|
2124 |
+ |
assert!(!Act::new("Save").disabled());
|
|
2125 |
+ |
assert!(!Act::new("Save").state(State::Focus).disabled());
|
|
2126 |
+ |
assert!(Act::new("Save").state(State::Disabled).disabled());
|
|
2127 |
+ |
}
|
|
2128 |
+ |
|
|
2129 |
+ |
#[test]
|
|
2130 |
+ |
fn an_act_carries_its_key_because_a_terminal_has_nothing_else() {
|
|
2131 |
+ |
// No key is the ordinary case, and the webview hosts that ignore it
|
|
2132 |
+ |
// are why it stayed optional.
|
|
2133 |
+ |
assert_eq!(Act::new("Delete").key, None);
|
|
2134 |
+ |
let quit = Act::new("Quit").key("q").tone(Tone::Danger);
|
|
2135 |
+ |
assert_eq!(quit.key, Some("q"));
|
|
2136 |
+ |
assert_eq!(quit.tone, Tone::Danger);
|
|
2137 |
+ |
}
|
|
2138 |
+ |
|
| 2040 |
2139 |
|
#[test]
|
| 2041 |
2140 |
|
fn a_meter_does_not_overflow_on_large_counts() {
|
| 2042 |
2141 |
|
// done * 100 in u32 would wrap somewhere past 42 million. Counts that
|