| 290 |
290 |
|
}
|
| 291 |
291 |
|
}
|
| 292 |
292 |
|
|
|
293 |
+ |
/// One figure with a caption, owned.
|
|
294 |
+ |
///
|
|
295 |
+ |
/// The borrowed original is [`layout::Figure`], and everything it says applies:
|
|
296 |
+ |
/// the value is text because only the app knows whether the number is a
|
|
297 |
+ |
/// percentage, a duration or a ratio, and the tone is carried because no
|
|
298 |
+ |
/// renderer can work out that a streak of zero is worth colouring.
|
|
299 |
+ |
///
|
|
300 |
+ |
/// What it calls, if it calls anything, is not here. That is an address, which
|
|
301 |
+ |
/// `makeover-layout` never names, and it rides beside the figure in
|
|
302 |
+ |
/// [`Node::Stats`] the way [`Row::activate`] rides beside a row's parts.
|
|
303 |
+ |
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
304 |
+ |
pub struct Figure {
|
|
305 |
+ |
/// The number, formatted the way the app means it to read.
|
|
306 |
+ |
pub value: String,
|
|
307 |
+ |
/// What it counts. The caption under the value.
|
|
308 |
+ |
pub caption: String,
|
|
309 |
+ |
/// What the figure means. [`layout::Tone::Neutral`] is an ordinary fact.
|
|
310 |
+ |
pub tone: layout::Tone,
|
|
311 |
+ |
}
|
|
312 |
+ |
|
|
313 |
+ |
impl Figure {
|
|
314 |
+ |
/// A figure that is an ordinary fact.
|
|
315 |
+ |
pub fn new(value: impl Into<String>, caption: impl Into<String>) -> Self {
|
|
316 |
+ |
Self {
|
|
317 |
+ |
value: value.into(),
|
|
318 |
+ |
caption: caption.into(),
|
|
319 |
+ |
tone: layout::Tone::Neutral,
|
|
320 |
+ |
}
|
|
321 |
+ |
}
|
|
322 |
+ |
|
|
323 |
+ |
/// What the figure means.
|
|
324 |
+ |
#[must_use]
|
|
325 |
+ |
pub const fn tone(mut self, tone: layout::Tone) -> Self {
|
|
326 |
+ |
self.tone = tone;
|
|
327 |
+ |
self
|
|
328 |
+ |
}
|
|
329 |
+ |
|
|
330 |
+ |
/// Borrow as the description layer's own type.
|
|
331 |
+ |
#[must_use]
|
|
332 |
+ |
pub fn as_layout(&self) -> layout::Figure<'_> {
|
|
333 |
+ |
layout::Figure {
|
|
334 |
+ |
value: &self.value,
|
|
335 |
+ |
caption: &self.caption,
|
|
336 |
+ |
tone: self.tone,
|
|
337 |
+ |
}
|
|
338 |
+ |
}
|
|
339 |
+ |
}
|
|
340 |
+ |
|
|
341 |
+ |
/// How much of a set is done, owned.
|
|
342 |
+ |
///
|
|
343 |
+ |
/// The borrowed original is [`layout::Meter`], which arrived at 0.10.0 for this.
|
|
344 |
+ |
/// Before it, a screen with a progress bar concatenated the two numbers into its
|
|
345 |
+ |
/// heading — "Subtasks 3/7" — which keeps both facts and loses the reading, the
|
|
346 |
+ |
/// same way a toned status badge read as prose before [`Row::tokens`].
|
|
347 |
+ |
///
|
|
348 |
+ |
/// Its own struct as of 0.11.0, having been the inline payload of
|
|
349 |
+ |
/// [`Node::Meter`]. Extracted for the reason [`Tag`] was: a row can carry one
|
|
350 |
+ |
/// now ([`Row::meter`], against `makeover-layout`'s `RowPart::Proportion`), and
|
|
351 |
+ |
/// the alternative was defining the same four fields twice and watching them
|
|
352 |
+ |
/// drift.
|
|
353 |
+ |
///
|
|
354 |
+ |
/// Carries the pair rather than a percentage for the reason [`layout::Meter`]
|
|
355 |
+ |
/// gives: a bar that is full because it landed exactly and one that is full
|
|
356 |
+ |
/// because it ran over are the same width and not the same fact.
|
|
357 |
+ |
///
|
|
358 |
+ |
/// This is a proportion of a set and not the progress of an operation. A running
|
|
359 |
+ |
/// timer or a fetch is imperative and live, and a screen is described once per
|
|
360 |
+ |
/// answer; [`layout::Readiness::Pending`] and a [`layout::Notice::Toast`] are
|
|
361 |
+ |
/// what those get.
|
|
362 |
+ |
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
363 |
+ |
pub struct Meter {
|
|
364 |
+ |
/// How much is done. May exceed [`total`](Self::total).
|
|
365 |
+ |
pub done: u32,
|
|
366 |
+ |
/// How much there is to do.
|
|
367 |
+ |
pub total: u32,
|
|
368 |
+ |
/// What the proportion means. No renderer can derive this.
|
|
369 |
+ |
pub tone: layout::Tone,
|
|
370 |
+ |
/// What is being counted: "subtasks", "tasks". The noun, not the ratio.
|
|
371 |
+ |
pub label: Option<String>,
|
|
372 |
+ |
}
|
|
373 |
+ |
|
|
374 |
+ |
impl Meter {
|
|
375 |
+ |
/// A proportion, untoned and unlabelled.
|
|
376 |
+ |
#[must_use]
|
|
377 |
+ |
pub const fn new(done: u32, total: u32) -> Self {
|
|
378 |
+ |
Self {
|
|
379 |
+ |
done,
|
|
380 |
+ |
total,
|
|
381 |
+ |
tone: layout::Tone::Neutral,
|
|
382 |
+ |
label: None,
|
|
383 |
+ |
}
|
|
384 |
+ |
}
|
|
385 |
+ |
|
|
386 |
+ |
/// What the proportion means.
|
|
387 |
+ |
#[must_use]
|
|
388 |
+ |
pub const fn tone(mut self, tone: layout::Tone) -> Self {
|
|
389 |
+ |
self.tone = tone;
|
|
390 |
+ |
self
|
|
391 |
+ |
}
|
|
392 |
+ |
|
|
393 |
+ |
/// What is being counted. The noun, not the ratio.
|
|
394 |
+ |
#[must_use]
|
|
395 |
+ |
pub fn label(mut self, label: impl Into<String>) -> Self {
|
|
396 |
+ |
self.label = Some(label.into());
|
|
397 |
+ |
self
|
|
398 |
+ |
}
|
|
399 |
+ |
|
|
400 |
+ |
/// Borrow as the description layer's own type.
|
|
401 |
+ |
#[must_use]
|
|
402 |
+ |
pub fn as_layout(&self) -> layout::Meter<'_> {
|
|
403 |
+ |
layout::Meter {
|
|
404 |
+ |
done: self.done,
|
|
405 |
+ |
total: self.total,
|
|
406 |
+ |
tone: self.tone,
|
|
407 |
+ |
label: self.label.as_deref(),
|
|
408 |
+ |
}
|
|
409 |
+ |
}
|
|
410 |
+ |
}
|
|
411 |
+ |
|
| 293 |
412 |
|
/// One field of a form, owned.
|
| 294 |
413 |
|
///
|
| 295 |
414 |
|
/// The borrowed original is [`layout::Field`], and everything it says about
|
| 337 |
456 |
|
pub options: Vec<Choice>,
|
| 338 |
457 |
|
/// Whether the form refuses to submit without it.
|
| 339 |
458 |
|
pub required: bool,
|
|
459 |
+ |
/// The longest the value may be, in characters.
|
|
460 |
+ |
///
|
|
461 |
+ |
/// The borrowed original's [`layout::Field::max_length`], and everything it
|
|
462 |
+ |
/// says applies: the description carries the rule, the renderer emits its
|
|
463 |
+ |
/// host's idiom, and deciding a value is wrong stays with whoever validated.
|
|
464 |
+ |
pub max_length: Option<u32>,
|
|
465 |
+ |
/// The lowest value accepted, written the way the host writes one.
|
|
466 |
+ |
pub min: Option<String>,
|
|
467 |
+ |
/// The highest value accepted. See [`min`](Self::min).
|
|
468 |
+ |
pub max: Option<String>,
|
| 340 |
469 |
|
/// Whether the field lives behind a "more options" disclosure.
|
| 341 |
470 |
|
pub extended: bool,
|
| 342 |
471 |
|
/// What to put back in the box: what was submitted, when a submission was
|
| 378 |
507 |
|
placeholder: None,
|
| 379 |
508 |
|
options: Vec::new(),
|
| 380 |
509 |
|
required: false,
|
|
510 |
+ |
max_length: None,
|
|
511 |
+ |
min: None,
|
|
512 |
+ |
max: None,
|
| 381 |
513 |
|
extended: false,
|
| 382 |
514 |
|
value: None,
|
| 383 |
515 |
|
changes: None,
|
| 483 |
615 |
|
placeholder: self.placeholder.as_deref(),
|
| 484 |
616 |
|
options: &options,
|
| 485 |
617 |
|
required: self.required,
|
|
618 |
+ |
max_length: self.max_length,
|
|
619 |
+ |
min: self.min.as_deref(),
|
|
620 |
+ |
max: self.max.as_deref(),
|
| 486 |
621 |
|
extended: self.extended,
|
| 487 |
622 |
|
})
|
| 488 |
623 |
|
}
|
| 770 |
905 |
|
/// [`current`](Self::current). goingson's contacts and tasks screens both
|
| 771 |
906 |
|
/// drive bulk actions from it.
|
| 772 |
907 |
|
pub selected: Option<bool>,
|
|
908 |
+ |
/// How much of a set this row's thing has done.
|
|
909 |
+ |
///
|
|
910 |
+ |
/// `da5666ae`, and [`RowPart::Proportion`](layout::RowPart::Proportion) is
|
|
911 |
+ |
/// the part it renders into. [`layout::Meter`] arrived at 0.10.0 and closed
|
|
912 |
+ |
/// two of the seven sites that wanted it; the other five sit in rows, and a
|
|
913 |
+ |
/// row holds no nodes by the 2026-08-08 ruling — the door through which a
|
|
914 |
+ |
/// description becomes a templating language. So this carries the
|
|
915 |
+ |
/// *description of a bar* rather than a node, exactly as
|
|
916 |
+ |
/// [`tokens`](Self::tokens) carries tags rather than nodes.
|
|
917 |
+ |
///
|
|
918 |
+ |
/// Owned rather than borrowed for the reason everything here is. Without it
|
|
919 |
+ |
/// a row flattens the proportion into [`meta`](Self::meta) as "3/7
|
|
920 |
+ |
/// subtasks", keeping both numbers and losing the reading.
|
|
921 |
+ |
pub meter: Option<Meter>,
|
| 773 |
922 |
|
/// What ticking this row calls, if ticking it is the write.
|
| 774 |
923 |
|
///
|
| 775 |
924 |
|
/// `14612ed8`, part of it. [`selected`](Self::selected) says whether the row
|
| 798 |
947 |
|
}
|
| 799 |
948 |
|
}
|
| 800 |
949 |
|
|
|
950 |
+ |
/// How much of this row's set is done.
|
|
951 |
+ |
#[must_use]
|
|
952 |
+ |
pub fn meter(mut self, meter: Meter) -> Self {
|
|
953 |
+ |
self.meter = Some(meter);
|
|
954 |
+ |
self
|
|
955 |
+ |
}
|
|
956 |
+ |
|
| 801 |
957 |
|
/// A tick that is the write, in the state it is currently in.
|
| 802 |
958 |
|
///
|
| 803 |
959 |
|
/// Sets [`selected`](Self::selected) and [`toggle`](Self::toggle) together,
|
| 970 |
1126 |
|
///
|
| 971 |
1127 |
|
/// Almost always carries a [`Field::changes`], because a control with no
|
| 972 |
1128 |
|
/// form around it and no route on it collects a value nothing reads.
|
| 973 |
|
- |
Field(Field),
|
|
1129 |
+ |
///
|
|
1130 |
+ |
/// Boxed because it is the only member holding a whole struct by value, and
|
|
1131 |
+ |
/// [`Field`] is the largest one here — every other member holds a `Vec`, a
|
|
1132 |
+ |
/// `String` or a small enum. Unboxed it decides the size of every [`Node`]
|
|
1133 |
+ |
/// in every list, and of the [`Response`](crate::Response) that carries one.
|
|
1134 |
+ |
Field(Box<Field>),
|
| 974 |
1135 |
|
/// Fields, and the route that submits them.
|
| 975 |
1136 |
|
Form {
|
| 976 |
1137 |
|
/// Where the answers go. Almost always a [`Method::Post`].
|
| 1006 |
1167 |
|
action: Option<Action>,
|
| 1007 |
1168 |
|
},
|
| 1008 |
1169 |
|
/// How much of a set is done.
|
|
1170 |
+ |
Meter(Meter),
|
|
1171 |
+ |
/// A value with a caption, several of them as one strip.
|
| 1009 |
1172 |
|
///
|
| 1010 |
|
- |
/// Against `makeover-layout`'s [`layout::Meter`], which arrived at 0.10.0
|
| 1011 |
|
- |
/// for this. Before it, a screen with a progress bar concatenated the two
|
| 1012 |
|
- |
/// numbers into its heading — "Subtasks 3/7" — which keeps both facts and
|
| 1013 |
|
- |
/// loses the reading, the same way a toned status badge read as prose before
|
| 1014 |
|
- |
/// [`Row::tokens`].
|
|
1173 |
+ |
/// `93c6a174`. Against `makeover-layout`'s [`layout::Figure`], which arrived
|
|
1174 |
+ |
/// at 0.11.0 for this. The dashboard shape: a large value over a small
|
|
1175 |
+ |
/// caption, several in a row. goingson had five of them across five screens
|
|
1176 |
+ |
/// with five class vocabularies for the one shape, and the port had been
|
|
1177 |
+ |
/// making each out of a [`Row`] with the caption as `primary` and the figure
|
|
1178 |
+ |
/// as `meta`, which reads backwards — a row's primary slot means the thing
|
|
1179 |
+ |
/// itself, and here the thing is the number.
|
| 1015 |
1180 |
|
///
|
| 1016 |
|
- |
/// Owned rather than borrowed for the reason every node here is, and
|
| 1017 |
|
- |
/// carrying the pair rather than a percentage for the reason
|
| 1018 |
|
- |
/// [`layout::Meter`] gives: a bar that is full because it landed exactly and
|
| 1019 |
|
- |
/// one that is full because it ran over are the same width and not the same
|
| 1020 |
|
- |
/// fact.
|
|
1181 |
+ |
/// # Why the set is the node and not each figure
|
| 1021 |
1182 |
|
///
|
| 1022 |
|
- |
/// This is a proportion of a set and not the progress of an operation. A
|
| 1023 |
|
- |
/// running timer or a fetch is imperative and live, and a screen is
|
| 1024 |
|
- |
/// described once per answer; [`layout::Readiness::Pending`] and a
|
| 1025 |
|
- |
/// [`layout::Notice::Toast`] are what those get.
|
| 1026 |
|
- |
Meter {
|
| 1027 |
|
- |
/// How much is done. May exceed [`total`](Self::Meter::total).
|
| 1028 |
|
- |
done: u32,
|
| 1029 |
|
- |
/// How much there is to do.
|
| 1030 |
|
- |
total: u32,
|
| 1031 |
|
- |
/// What the proportion means. No renderer can derive this.
|
| 1032 |
|
- |
tone: layout::Tone,
|
| 1033 |
|
- |
/// What is being counted: "subtasks", "tasks". The noun, not the ratio.
|
| 1034 |
|
- |
label: Option<String>,
|
|
1183 |
+ |
/// Four tiles in a strip and four tiles down a column are different things,
|
|
1184 |
+ |
/// and a renderer handed one at a time cannot tell it is looking at a set.
|
|
1185 |
+ |
/// The objection to that is real and is answered by what is already here: a
|
|
1186 |
+ |
/// node whose value is its grouping sounds like a layout instruction, and
|
|
1187 |
+ |
/// [`List`](Self::List) and [`Table`](Self::Table) have been exactly that
|
|
1188 |
+ |
/// since the beginning without anyone calling them one.
|
|
1189 |
+ |
///
|
|
1190 |
+ |
/// # Why the action is here and not on the figure
|
|
1191 |
+ |
///
|
|
1192 |
+ |
/// One of goingson's five is a control — sync's "Not Applied: 3" opens the
|
|
1193 |
+ |
/// list. `makeover-layout` cannot name an action at all, so the figure it
|
|
1194 |
+ |
/// describes carries none, and this pairs the description with the address
|
|
1195 |
+ |
/// the same way [`Row`] pairs its parts with [`Row::activate`].
|
|
1196 |
+ |
Stats {
|
|
1197 |
+ |
/// The figures, in order, and what each one calls if it calls anything.
|
|
1198 |
+ |
figures: Vec<(Figure, Option<Action>)>,
|
| 1035 |
1199 |
|
},
|
| 1036 |
1200 |
|
/// A region inside a region.
|
| 1037 |
1201 |
|
Region(Slot),
|
| 1107 |
1271 |
|
}
|
| 1108 |
1272 |
|
|
| 1109 |
1273 |
|
/// A proportion of a set, untoned and unlabelled.
|
|
1274 |
+ |
#[must_use]
|
| 1110 |
1275 |
|
pub const fn meter(done: u32, total: u32) -> Self {
|
| 1111 |
|
- |
Self::Meter {
|
| 1112 |
|
- |
done,
|
| 1113 |
|
- |
total,
|
| 1114 |
|
- |
tone: layout::Tone::Neutral,
|
| 1115 |
|
- |
label: None,
|
| 1116 |
|
- |
}
|
|
1276 |
+ |
Self::Meter(Meter::new(done, total))
|
| 1117 |
1277 |
|
}
|
| 1118 |
1278 |
|
|
| 1119 |
|
- |
/// Read a [`Self::Meter`] as the description layer's own type.
|
| 1120 |
|
- |
///
|
| 1121 |
|
- |
/// A callback rather than a return, for the reason [`Field::with_layout`]
|
| 1122 |
|
- |
/// is one: [`layout::Meter`] borrows its label and ours owns it. `None` for
|
| 1123 |
|
- |
/// every other member, so a renderer matching on the node can ask without
|
| 1124 |
|
- |
/// first proving what it has.
|
| 1125 |
|
- |
pub fn with_meter<R>(&self, f: impl FnOnce(layout::Meter<'_>) -> R) -> Option<R> {
|
| 1126 |
|
- |
let Self::Meter {
|
| 1127 |
|
- |
done,
|
| 1128 |
|
- |
total,
|
| 1129 |
|
- |
tone,
|
| 1130 |
|
- |
label,
|
| 1131 |
|
- |
} = self
|
| 1132 |
|
- |
else {
|
| 1133 |
|
- |
return None;
|
| 1134 |
|
- |
};
|
| 1135 |
|
- |
Some(f(layout::Meter {
|
| 1136 |
|
- |
done: *done,
|
| 1137 |
|
- |
total: *total,
|
| 1138 |
|
- |
tone: *tone,
|
| 1139 |
|
- |
label: label.as_deref(),
|
| 1140 |
|
- |
}))
|
|
1279 |
+ |
/// One control on its own, outside any form.
|
|
1280 |
+ |
pub fn field(field: Field) -> Self {
|
|
1281 |
+ |
Self::Field(Box::new(field))
|
|
1282 |
+ |
}
|
|
1283 |
+ |
|
|
1284 |
+ |
/// A strip of figures, none of which answers a click.
|
|
1285 |
+ |
pub fn stats(figures: impl IntoIterator<Item = Figure>) -> Self {
|
|
1286 |
+ |
Self::Stats {
|
|
1287 |
+ |
figures: figures.into_iter().map(|figure| (figure, None)).collect(),
|
|
1288 |
+ |
}
|
| 1141 |
1289 |
|
}
|
| 1142 |
1290 |
|
}
|
| 1143 |
1291 |
|
|