| 45 |
45 |
|
//!
|
| 46 |
46 |
|
//! - **Generic against bespoke.** Is this furniture any app would have, or is
|
| 47 |
47 |
|
//! it this app's own? A rule that withholds a word until a second app has
|
| 48 |
|
- |
//! duplicated the code guarantees the duplication. Bespoke keeps
|
| 49 |
|
- |
//! [`Region::Bespoke`].
|
|
48 |
+ |
//! duplicated the code guarantees the duplication. What the app owns
|
|
49 |
+ |
//! keeps [`Region::Handover`] and [`Region::Ceded`].
|
| 50 |
50 |
|
//! - **Every host has an honest answer.** A member no renderer can express
|
| 51 |
51 |
|
//! without borrowing one host's idiom is not a description.
|
| 52 |
52 |
|
//! - **It can be laid out before it is filled.** See *First paint is final
|
| 201 |
201 |
|
//! columns, weeks as rows, blanks for the offset. If a screen wants one,
|
| 202 |
202 |
|
//! measure the members it needs before adding any.
|
| 203 |
203 |
|
//!
|
| 204 |
|
- |
//! [`Region::Bespoke`] remains for the genuinely app-owned. The description
|
|
204 |
+ |
//! [`Region::Handover`] and [`Region::Ceded`] remain for the genuinely
|
|
205 |
+ |
//! app-owned. The description
|
| 205 |
206 |
|
//! names the *place* and the app owns the contents, so a screen containing a
|
| 206 |
207 |
|
//! timeline is still a whole screen and still routable. Without it, the screens
|
| 207 |
208 |
|
//! that make an app worth using would need a second, undescribed path beside
|
| 1899 |
1900 |
|
/// The thing `makeover-geometry` deliberately does not name: it names the space
|
| 1900 |
1901 |
|
/// *between* things by relationship, and nothing named the things. Six named
|
| 1901 |
1902 |
|
/// members, taken from what the two webview apps actually use, plus
|
| 1902 |
|
- |
/// [`Region::Bespoke`] for the parts no description should reach. Both apps'
|
|
1903 |
+ |
/// [`Region::Handover`] and [`Region::Ceded`] for the parts no description
|
|
1904 |
+ |
/// should reach. Both apps'
|
| 1903 |
1905 |
|
/// `layout.css` currently names exactly two things, `.raised` and `.well`, so
|
| 1904 |
1906 |
|
/// this layer is absent rather than divergent, which makes it the cheapest of
|
| 1905 |
1907 |
|
/// the schemas to add and the easiest to over-build.
|
| 1955 |
1957 |
|
/// body, the way it already was. A group of related toggles with no heading
|
| 1956 |
1958 |
|
/// is a real thing and a mandatory slot would forbid it.
|
| 1957 |
1959 |
|
///
|
| 1958 |
|
- |
/// **A depth.** [`Depth::Flat`], on [`Bespoke`](Self::Bespoke)'s reasoning:
|
|
1960 |
+ |
/// **A depth.** [`Depth::Flat`], on [`Handover`](Self::Handover)'s reasoning:
|
| 1959 |
1961 |
|
/// it inherits, and an app that wants its group in a well puts it in a
|
| 1960 |
1962 |
|
/// [`Pane`](Self::Pane), which composes rather than adding a knob here.
|
| 1961 |
1963 |
|
///
|
| 2008 |
2010 |
|
TabGroup,
|
| 2009 |
2011 |
|
/// Content over a scrim, taking input until dismissed.
|
| 2010 |
2012 |
|
Modal,
|
| 2011 |
|
- |
/// A region this crate names the *place* of and nothing else. The app owns
|
| 2012 |
|
- |
/// what goes in it.
|
|
2013 |
+ |
/// A region this crate names the *place* of, whose contents the app still
|
|
2014 |
+ |
/// owes every host.
|
| 2013 |
2015 |
|
///
|
| 2014 |
2016 |
|
/// The escape hatch, and the thing that keeps the description honest about
|
| 2015 |
2017 |
|
/// its own limits. A day-plan timeline, a kanban board, a calendar and the
|
| 2026 |
2028 |
|
/// So the description says "a thing called `day-plan` goes here" and stops.
|
| 2027 |
2029 |
|
/// The name is opaque: this crate never interprets it, and no renderer is
|
| 2028 |
2030 |
|
/// expected to know what it means beyond handing the space over.
|
| 2029 |
|
- |
Bespoke {
|
|
2031 |
+ |
///
|
|
2032 |
+ |
/// # What separates it from [`Ceded`](Self::Ceded)
|
|
2033 |
+ |
///
|
|
2034 |
+ |
/// **A fill is owed here in every host's currency.** A renderer handed one
|
|
2035 |
+ |
/// of these and given nothing to put in it is looking at a hole the app
|
|
2036 |
+ |
/// meant to fill, and saying so is the honest drawing. [`owed`](Self::owed)
|
|
2037 |
+ |
/// is how it asks.
|
|
2038 |
+ |
///
|
|
2039 |
+ |
/// That is the whole of the split. Before it there was one opaque member,
|
|
2040 |
+ |
/// so a region the description had given up on and a region nobody had
|
|
2041 |
+ |
/// converted yet were the same value, and both drew as a silently empty
|
|
2042 |
+ |
/// box on the two renderers that answer no fill.
|
|
2043 |
+ |
Handover {
|
|
2044 |
+ |
/// What the app calls it. Never interpreted here.
|
|
2045 |
+ |
name: &'a str,
|
|
2046 |
+ |
},
|
|
2047 |
+ |
/// A region this crate names the place of, whose contents no host is owed.
|
|
2048 |
+ |
///
|
|
2049 |
+ |
/// The other half of the old single opaque member. The app has decided this
|
|
2050 |
+ |
/// space is not the description's to fill and is not going to become so:
|
|
2051 |
+ |
/// a chart, a waveform, a rendered picture of domain data with marks
|
|
2052 |
+ |
/// painted over it at positions no description knows.
|
|
2053 |
+ |
///
|
|
2054 |
+ |
/// **Silence is the correct drawing.** A renderer with no fill for this
|
|
2055 |
+ |
/// draws nothing and is right to; unlike [`Handover`](Self::Handover) there
|
|
2056 |
+ |
/// is nothing missing. That is what makes the pair worth two members rather
|
|
2057 |
+ |
/// than a flag: the two want opposite behaviour from a renderer that cannot
|
|
2058 |
+ |
/// fill them, and one name cannot carry both.
|
|
2059 |
+ |
///
|
|
2060 |
+ |
/// The measured sites are MNW's analytics charts, which already carry the
|
|
2061 |
+ |
/// ruling that a bar chart is not describable and should not be, and
|
|
2062 |
+ |
/// audiofiles' waveform, whose exclusion had no vocabulary to live in and
|
|
2063 |
+ |
/// was recorded in a doc comment instead.
|
|
2064 |
+ |
Ceded {
|
| 2030 |
2065 |
|
/// What the app calls it. Never interpreted here.
|
| 2031 |
2066 |
|
name: &'a str,
|
| 2032 |
2067 |
|
},
|
| 2033 |
2068 |
|
/// A named assembly of things the vocabulary already says.
|
| 2034 |
2069 |
|
///
|
| 2035 |
|
- |
/// The third tier, between a primitive and [`Bespoke`](Self::Bespoke).
|
|
2070 |
+ |
/// The third tier, between a primitive and the two opaque members.
|
| 2036 |
2071 |
|
///
|
| 2037 |
2072 |
|
/// # What separates it from the two members either side
|
| 2038 |
2073 |
|
///
|
| 2041 |
2076 |
|
/// that test — a terminal has no carousel — which is the same refusal
|
| 2042 |
2077 |
|
/// `Node::Html` got and is why the carousel sat unsayable for months.
|
| 2043 |
2078 |
|
///
|
| 2044 |
|
- |
/// [`Bespoke`](Self::Bespoke) fails it from the other side. Bespoke is for
|
|
2079 |
+ |
/// [`Handover`](Self::Handover) fails it from the other side. It is for
|
| 2045 |
2080 |
|
/// what one app owns and nobody will build twice, and it carries *no*
|
| 2046 |
2081 |
|
/// contents: the description names the place and stops. A carousel is
|
| 2047 |
2082 |
|
/// furniture any app would have, and every part of it — an ordered set of
|
| 2063 |
2098 |
|
/// knowing every widget. An unrecognised widget degrades to its assembly
|
| 2064 |
2099 |
|
/// instead of failing, so a second or third party can name one without
|
| 2065 |
2100 |
|
/// three renderers releasing in lockstep to accept it. Contrast
|
| 2066 |
|
- |
/// [`Bespoke`](Self::Bespoke), which no renderer can degrade: there is
|
|
2101 |
+ |
/// [`Handover`](Self::Handover), which no renderer can degrade: there is
|
| 2067 |
2102 |
|
/// nothing under it to fall back to.
|
| 2068 |
2103 |
|
///
|
| 2069 |
2104 |
|
/// # What it does not do
|
| 2108 |
2143 |
|
// depth set here would be this crate deciding that a carousel is
|
| 2109 |
2144 |
|
// raised on every host, which is the kind of value the deferral
|
| 2110 |
2145 |
|
// rule exists to refuse.
|
| 2111 |
|
- |
Self::Bespoke { .. } | Self::Widget { .. } => Depth::Flat,
|
|
2146 |
+ |
Self::Handover { .. } | Self::Ceded { .. } | Self::Widget { .. } => Depth::Flat,
|
| 2112 |
2147 |
|
}
|
| 2113 |
2148 |
|
}
|
| 2114 |
2149 |
|
|
| 2120 |
2155 |
|
/// is exactly one opaque member and there should stay exactly one.
|
| 2121 |
2156 |
|
///
|
| 2122 |
2157 |
|
/// [`Widget`](Self::Widget) is described, and that is the whole of what
|
| 2123 |
|
- |
/// separates it from [`Bespoke`](Self::Bespoke) here. Both carry a name
|
| 2124 |
|
- |
/// this crate never interprets; only one of them carries contents under it.
|
|
2158 |
+ |
/// separates it from the two opaque members here. All three carry a name
|
|
2159 |
+ |
/// this crate never interprets; only the widget carries contents under it.
|
| 2125 |
2160 |
|
/// A renderer that does not recognise a widget's name still walks its body,
|
| 2126 |
2161 |
|
/// so there is nothing for it to hand over and nothing it cannot draw.
|
| 2127 |
2162 |
|
#[must_use]
|
| 2128 |
2163 |
|
pub const fn described(self) -> bool {
|
| 2129 |
|
- |
!matches!(self, Self::Bespoke { .. })
|
|
2164 |
+ |
!matches!(self, Self::Handover { .. } | Self::Ceded { .. })
|
|
2165 |
+ |
}
|
|
2166 |
+ |
|
|
2167 |
+ |
/// Whether a fill is owed here, for a renderer that has none.
|
|
2168 |
+ |
///
|
|
2169 |
+ |
/// The question the single opaque member could not answer. True for
|
|
2170 |
+ |
/// [`Handover`](Self::Handover): the app meant to fill this and a renderer
|
|
2171 |
+ |
/// with nothing to put in it should say so. False for everything else,
|
|
2172 |
+ |
/// [`Ceded`](Self::Ceded) included, where silence is the correct drawing
|
|
2173 |
+ |
/// because nothing is missing.
|
|
2174 |
+ |
///
|
|
2175 |
+ |
/// A method rather than a `matches!` at each renderer, for
|
|
2176 |
+ |
/// [`described`](Self::described)'s reason: three renderers writing the
|
|
2177 |
+ |
/// same match is three chances to disagree about what an empty region
|
|
2178 |
+ |
/// means.
|
|
2179 |
+ |
#[must_use]
|
|
2180 |
+ |
pub const fn owed(self) -> bool {
|
|
2181 |
+ |
matches!(self, Self::Handover { .. })
|
| 2130 |
2182 |
|
}
|
| 2131 |
2183 |
|
|
| 2132 |
2184 |
|
/// The name an app gave this region, if it gave one.
|
| 2133 |
2185 |
|
///
|
| 2134 |
|
- |
/// [`Bespoke`](Self::Bespoke) and [`Widget`](Self::Widget) are the two
|
| 2135 |
|
- |
/// members that carry a name, for two different purposes: one says what the
|
| 2136 |
|
- |
/// app will fill the space with, the other says what the assembly under it
|
| 2137 |
|
- |
/// is called. A renderer dispatching on either wants the string without
|
|
2186 |
+ |
/// [`Handover`](Self::Handover), [`Ceded`](Self::Ceded) and
|
|
2187 |
+ |
/// [`Widget`](Self::Widget) are the members that carry a name, for two
|
|
2188 |
+ |
/// different purposes: the first two say what the app puts in the space,
|
|
2189 |
+ |
/// the third says what the assembly under it is called. A renderer dispatching on either wants the string without
|
| 2138 |
2190 |
|
/// caring which member it came from, and writing that `matches!` at each
|
| 2139 |
2191 |
|
/// renderer is how the two drift apart.
|
| 2140 |
2192 |
|
#[must_use]
|
| 2141 |
2193 |
|
pub const fn name(self) -> Option<&'a str> {
|
| 2142 |
2194 |
|
match self {
|
| 2143 |
|
- |
Self::Bespoke { name } | Self::Widget { name } => Some(name),
|
|
2195 |
+ |
Self::Handover { name } | Self::Ceded { name } | Self::Widget { name } => Some(name),
|
| 2144 |
2196 |
|
// Spelled out rather than a wildcard, so a member added later has
|
| 2145 |
2197 |
|
// to answer whether it carries a name instead of inheriting `None`
|
| 2146 |
2198 |
|
// by sitting under a `_`.
|
| 5852 |
5904 |
|
// renderer that does not lay them across still draws every column.
|
| 5853 |
5905 |
|
// Stacking them vertically is honouring this member, not degrading it.
|
| 5854 |
5906 |
|
assert!(Region::Columns.described());
|
| 5855 |
|
- |
assert!(!Region::Bespoke { name: "timeline" }.described());
|
|
5907 |
+ |
assert!(!Region::Handover { name: "timeline" }.described());
|
| 5856 |
5908 |
|
}
|
| 5857 |
5909 |
|
|
| 5858 |
5910 |
|
#[test]
|
| 6003 |
6055 |
|
] {
|
| 6004 |
6056 |
|
assert!(r.described(), "{r:?} should be describable");
|
| 6005 |
6057 |
|
}
|
| 6006 |
|
- |
assert!(!Region::Bespoke { name: "day-plan" }.described());
|
|
6058 |
+ |
assert!(!Region::Handover { name: "day-plan" }.described());
|
|
6059 |
+ |
}
|
|
6060 |
+ |
|
|
6061 |
+ |
#[test]
|
|
6062 |
+ |
fn a_region_nobody_converted_yet_is_not_a_region_the_app_gave_up_on() {
|
|
6063 |
+ |
// The whole of why this is two members. Both are opaque and neither is
|
|
6064 |
+ |
// described, so the old single member made them one value; a renderer
|
|
6065 |
+ |
// with no fill drew both as an empty box and said nothing either way.
|
|
6066 |
+ |
assert!(Region::Handover { name: "day-plan" }.owed());
|
|
6067 |
+ |
assert!(
|
|
6068 |
+ |
!Region::Ceded {
|
|
6069 |
+ |
name: "revenue-chart"
|
|
6070 |
+ |
}
|
|
6071 |
+ |
.owed()
|
|
6072 |
+ |
);
|
|
6073 |
+ |
|
|
6074 |
+ |
// Everything that carries its own contents owes nothing, which is the
|
|
6075 |
+ |
// reading a renderer needs for the members it already draws.
|
|
6076 |
+ |
assert!(!Region::Pane.owed());
|
|
6077 |
+ |
assert!(!Region::Widget { name: "carousel" }.owed());
|
|
6078 |
+ |
|
|
6079 |
+ |
// Opaqueness is the axis they still share.
|
|
6080 |
+ |
assert!(!Region::Ceded { name: "waveform" }.described());
|
|
6081 |
+ |
assert_eq!(Region::Ceded { name: "waveform" }.name(), Some("waveform"));
|
|
6082 |
+ |
assert_eq!(Region::Ceded { name: "waveform" }.depth(), Depth::Flat);
|
| 6007 |
6083 |
|
}
|
| 6008 |
6084 |
|
|
| 6009 |
6085 |
|
#[test]
|
| 6047 |
6123 |
|
// Writing that `matches!` at each renderer is how the two drift apart.
|
| 6048 |
6124 |
|
assert_eq!(Region::Widget { name: "carousel" }.name(), Some("carousel"));
|
| 6049 |
6125 |
|
assert_eq!(
|
| 6050 |
|
- |
Region::Bespoke { name: "day-plan" }.name(),
|
|
6126 |
+ |
Region::Handover { name: "day-plan" }.name(),
|
| 6051 |
6127 |
|
Some("day-plan")
|
| 6052 |
6128 |
|
);
|
| 6053 |
6129 |
|
|
| 6068 |
6144 |
|
fn a_bespoke_region_inherits_its_depth_rather_than_choosing_one() {
|
| 6069 |
6145 |
|
// The app owns the contents, not the placement. An app that wants its
|
| 6070 |
6146 |
|
// timeline in a well frames it in a Pane.
|
| 6071 |
|
- |
assert_eq!(Region::Bespoke { name: "day-plan" }.depth(), Depth::Flat);
|
| 6072 |
|
- |
assert_eq!(Region::Bespoke { name: "kanban" }.depth(), Depth::Flat);
|
|
6147 |
+ |
assert_eq!(Region::Handover { name: "day-plan" }.depth(), Depth::Flat);
|
|
6148 |
+ |
assert_eq!(Region::Handover { name: "kanban" }.depth(), Depth::Flat);
|
| 6073 |
6149 |
|
}
|
| 6074 |
6150 |
|
|
| 6075 |
6151 |
|
#[test]
|
| 6079 |
6155 |
|
// interesting four need a second path beside the router.
|
| 6080 |
6156 |
|
let day_plan = [
|
| 6081 |
6157 |
|
Region::Band,
|
| 6082 |
|
- |
Region::Bespoke { name: "day-plan" },
|
|
6158 |
+ |
Region::Handover { name: "day-plan" },
|
| 6083 |
6159 |
|
Region::Sidebar,
|
| 6084 |
6160 |
|
];
|
| 6085 |
6161 |
|
assert_eq!(day_plan.iter().filter(|r| r.described()).count(), 2);
|