use crate::Tone; // Names this module's prose links to, resolved for rustdoc. #[allow(unused_imports)] use crate::{Figure, Meter}; /// What is in a region right now. /// /// The state, not the shimmer. Whether pending paints a skeleton, a spinner or /// nothing at all is renderer policy, the same class of decision that got /// `Fill::fallback` deleted from this crate. goingson and Balanced Breakfast /// each grew a skeleton with differently-named parts; both keep them, as the /// webview renderer's expression of [`Readiness::Pending`]. audiofiles has none /// and needs none, because an immediate-mode renderer simply repaints. /// /// # Four states and not two /// /// Naming only `Ready` and `Pending` leaves a screen whose list came back empty /// with nothing to say about it, so it renders an empty region or invents its /// own placeholder text and neither says what it is. Left to the apps, the /// class family drifts: `empty-state`, `empty-state--error`, `error-state` and /// six more. /// /// The four are one axis because they are mutually exclusive: a region shows its /// content, or a sign that it is coming, or a sign that there is none, or a sign /// that it broke. Never two. That is the test for one enum against several /// fields, and it is why this grew rather than a new member arriving beside it. /// /// # What is not here /// /// **The message.** "No projects yet" is content, and this names a state. It /// lives with whatever holds the region — in quasi's case a `Slot` — alongside /// the action that leads out of the emptiness, since an address is the one thing /// this crate never names. /// /// **How much room it gets.** goingson's `--compact`, `--dashboard` and /// `--padded` are the same state at three sizes, and a size is /// `makeover-geometry`'s question. Naming them here would be this crate stating /// values again. /// /// **The icon.** Presentation, and each host has its own answer or none. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[non_exhaustive] pub enum Readiness { /// The content is here. Ready, /// The content is on its way. /// /// For a region that changes *after* the first paint, and never for the /// first paint itself: see "First paint is final paint" in the crate header. /// A host that renders once, with its data already in hand, has nothing to /// say this about, and a screen arriving in this state is describing a /// moment its host should not have been in. /// /// What stands in occupies the geometry the content will occupy. A stand-in /// sized to itself rather than to what replaces it is the reflow the rule /// forbids, arriving one repaint later. Pending, /// The content arrived and there is none of it. /// /// Not a failure. An empty list is the normal state of a new install, and a /// renderer that drew it in a danger tone would be reporting a fault where /// there is none. Empty, /// The content did not arrive. Failed, } impl Readiness { /// Whether the region draws its own content, or something standing in for /// it. /// /// The question every renderer asks first, so it is answered once here /// rather than by a `matches!` in each. A state added later is a stand-in /// until proven otherwise: falling back to drawing content that may not be /// there is the worse of the two mistakes. #[must_use] pub const fn shows_content(self) -> bool { matches!(self, Self::Ready) } /// What the state means, for a renderer choosing a colour. /// /// Derived rather than carried, which is the opposite of [`Meter`] and /// [`Figure`], and the difference is worth stating: a proportion's meaning /// depends on what is being counted and only the app knows it, while /// "nothing here yet" and "this broke" mean the same thing in every app that /// will ever have them. #[must_use] pub const fn tone(self) -> Tone { match self { Self::Failed => Tone::Danger, _ => Tone::Neutral, } } } /// An action is waiting on something that resolves once, in expected finite /// time. /// /// The control-side sibling of [`Readiness`]. That enum names four states for a /// region and named nothing at all for the button that is currently doing what /// it was clicked for, so the in-flight treatment is hand-written wherever it /// exists: the MNW server carries 57 in-flight indicators against 2 guards /// against a second press, which is the spinner mostly present and the guard /// mostly absent, on a codebase whose money path is a purchase button. /// /// # What is described here, and what is not /// /// The fact is that there is an outstanding thing which will complete. Not that /// the address is remote: a heavy local query waits too, and a server calling a /// payment provider is not the browser leaving the app. Not that the call is /// slow either, which is a judgement about a call rather than a property of one. /// /// Resolving **once** is the boundary, and it is what separates this from a /// screen that keeps changing. A live screen never resolves and has no name in /// this crate yet. /// /// # One mark, two renderings /// /// | what reads it | what it does | /// |---|---| /// | a control that was pressed | goes busy and refuses a second press until it resolves | /// | a region fed by it | stands in as [`Readiness::Pending`], then fills | /// /// The two were on the table separately and both were taken. Controls alone /// leaves a slow region hand-split into its own route, which is what MNW's user /// dashboard does with its payout summary; regions alone leaves the purchase /// button unguarded. /// /// # A quantity when it is measured, never a duration /// /// [`amount`](Self::amount) is stated only when it is a measured fact about the /// payload. An upload's file length, yes; a round trip to a payment provider, /// [`None`]. A duration is described nowhere, and a renderer may not manufacture /// one from the amount either: a determinate bar shows what is done over what /// there is, plus the time it has taken so far, and never a remaining time, an /// arrival time or a rate extrapolated forwards. A prediction is wrong the /// moment the transfer stalls, and being confidently wrong is worse than being /// honestly indeterminate. /// /// This is why the crate refuses to say how long an undo stays offered and /// accepts a byte count here. The refusal is about naming a decision that /// belongs to the renderer; a file's length is not a decision, nobody chose it. /// /// # Not [`Meter`] /// /// [`Meter`] is how much of a set is done, and its own docs refuse the progress /// of an operation on the grounds that a description is built once and dropped /// while an operation runs between renders. That refusal stands. This names the /// operation and its size, which is all that is known before it starts; how much /// of it has gone through is the renderer's to observe live, and nothing round /// trips through a description to say so. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] #[non_exhaustive] pub struct Awaiting { /// Total work to get through, when it is a measured fact about the payload. /// /// `None` when the wait has no countable size, which is the common case and /// the default. /// /// Unit-agnostic on purpose. Bytes for an upload, rows for an import; what /// is being counted is the app's business and a renderer draws a proportion /// either way. pub amount: Option, } impl Awaiting { /// A wait with no countable size. #[must_use] pub const fn unmeasured() -> Self { Self { amount: None } } /// A wait whose size is known. /// /// Reach for it only with a measured figure. An estimate written in here is /// a prediction wearing a fact's clothes, and the renderer has no way to /// tell the two apart. #[must_use] pub const fn of(amount: u64) -> Self { Self { amount: Some(amount), } } /// Whether there is a proportion to draw. /// /// The question every renderer asks first, answered once here rather than by /// a `matches!` in each. False means indeterminate, which is the honest /// drawing when nothing countable was measured. #[must_use] pub const fn is_determinate(self) -> bool { self.amount.is_some() } } /// When a picture is needed. /// /// A claim about *importance and position* rather than a fetch mechanism, which /// is why it is the description's to make: only the app knows whether a picture /// is the first thing on the screen or the fortieth thing down a list. /// /// # Eager is the default, and that is a correctness choice /// /// Emitting the webview's `loading="lazy"` for every picture reads one /// consumer's habit as a rule. Deferring a picture that is on screen at first paint does not /// save anything -- it is needed immediately either way -- and it delays the /// arrival, so the space it eventually takes is claimed later and the shift is /// more visible, not less. /// /// So the safe answer is the default and the optimisation is opted into. A /// carousel is the case that proves the two cannot be one setting for the /// renderer to choose: its first frame is on screen and its other frames are /// not, in the same widget, at the same moment. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] #[non_exhaustive] pub enum Loading { /// Needed with the screen. Fetch it now. #[default] Eager, /// Not on screen yet. It can wait until it is near. Lazy, }