makeover_layout/facts.rs
1// Names this module's prose links to, resolved for rustdoc.
2#[allow(unused_imports)]
3use crate::{Column, Figure, RowPart};
4
5/// One labelled fact: what it is called, and what it says.
6///
7/// The definition-list pair every record screen has and no member named. Five
8/// goingson panes were saying it as `list { row "Label" { meta value } }` --
9/// the task pane's metadata, events, contacts, the mail reader and settings'
10/// About -- and two of them carry a comment admitting the vocabulary had no
11/// word for a definition list. The task pane's own doc calls a row "the nearest
12/// thing the vocabulary has to a definition list", which is a workaround
13/// describing itself.
14///
15/// # Why this is not a [`Column`] pair, or a one-row table
16///
17/// A table's columns are a promise about *many* rows: they earn their headings,
18/// their sort, their narrowing rules and their floors because the same shape
19/// repeats down the screen. A pane of facts has one of each, so every one of
20/// those is machinery with nothing to do, and the heading row a table wants is
21/// exactly what a facts pane must not draw.
22///
23/// The measured symptom, which is what makes this a member rather than a
24/// preference: rendered as rows, each value starts after its own label, so
25/// there is no value column and the eye cannot run down it. Four widths of a
26/// ui-fuzz run found the same ragged edge on About, the event pane and the mail
27/// reader.
28///
29/// # The rule a renderer owes it
30///
31/// **The values line up.** That is the whole point and the one thing a row list
32/// cannot do: one label column wide enough for the longest label, every value
33/// starting at the same place. A webview does it with a description list, a
34/// terminal with two padded columns, egui with a two-column grid. None of them
35/// may fall back to running the value straight after the label.
36///
37/// **The value is the fact and reads like one.** goingson had this backwards:
38/// its values were drawn muted and its labels were not, so the part a person
39/// came for was the quieter of the two. The label is the noun and reads back;
40/// the value takes content. Same rule [`RowPart`] already states for a row's
41/// primary against its meta, and the same one [`Figure`] states for a value
42/// against its caption.
43///
44/// # What it is not
45///
46/// A form. A fact is read, not edited; the moment one of these needs to change
47/// it is a `Field` and it belongs in a `form`. Nothing here carries a name to
48/// submit under, deliberately, so the two cannot be confused at the call site.
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
50pub struct Fact<'a> {
51 /// What the fact is called. The noun, which reads back.
52 pub label: &'a str,
53 /// What it says, already formatted the way the app means it to read.
54 ///
55 /// Text, for [`Figure::value`]'s reason: only the app knows whether a date
56 /// reads as `2d ago` or `20 Sep`, and a renderer handed a timestamp would
57 /// have to guess. The five sites this member replaces were all formatting
58 /// before they built the row.
59 pub value: &'a str,
60}
61
62impl<'a> Fact<'a> {
63 /// A fact, from its label and its value.
64 #[must_use]
65 pub const fn new(label: &'a str, value: &'a str) -> Self {
66 Self { label, value }
67 }
68
69 /// Whether the value is worth drawing.
70 ///
71 /// A record screen reads its facts off a row that has holes in it -- no
72 /// project, no due date, no timezone -- and every one of the five sites was
73 /// guarding its row with a `when` for exactly that. A renderer skips an
74 /// empty fact rather than drawing a label with nothing after it, which is
75 /// the one reading that is never wanted: a labelled blank says the value is
76 /// missing, when what is true is that there is no such fact.
77 #[must_use]
78 pub const fn is_empty(&self) -> bool {
79 self.value.is_empty()
80 }
81}