| 199 |
199 |
|
//! goes. That is the boundary this crate is defined by, and four findings moved
|
| 200 |
200 |
|
//! across it rather than being answered here.
|
| 201 |
201 |
|
//!
|
|
202 |
+ |
//! 0.19.0 narrows [`State`] to [`State::Disabled`] alone. `State::Focus` is
|
|
203 |
+ |
//! gone: a description never states what has focus, because what focus *is*
|
|
204 |
+ |
//! differs per host and every renderer had already decided for itself — the
|
|
205 |
+ |
//! webview draws it from `:focus-visible`, egui refused the variant outright,
|
|
206 |
+ |
//! and quasi-tui honoured it once at startup and overrode it thereafter.
|
|
207 |
+ |
//!
|
|
208 |
+ |
//! # Reach, focus and the focus ring
|
|
209 |
+ |
//!
|
|
210 |
+ |
//! Three terms, and no others, for what 0.19.0 moved out of the description.
|
|
211 |
+ |
//! **Reach** is which things can take focus and in what order; a browser reads
|
|
212 |
+ |
//! it off the document, a TUI derives it from draw order, egui from its own id
|
|
213 |
+ |
//! stack. **Focus** is which reached thing has the keyboard right now: the
|
|
214 |
+ |
//! renderer's, live, never described and never round-tripped through a
|
|
215 |
+ |
//! description. The **focus ring** is the visible cue; the token (`focus-ring`,
|
|
216 |
+ |
//! derived by `makeover` from the action colour) is the one shared artifact and
|
|
217 |
+ |
//! the drawing is the renderer's. Retired as names for any of this: "focus
|
|
218 |
+ |
//! stroke", "focus cue", "wants focus". "Caret" is a different thing — the text
|
|
219 |
+ |
//! cursor inside a field — and keeps its name.
|
|
220 |
+ |
//!
|
| 202 |
221 |
|
//! # Where the description stops
|
| 203 |
222 |
|
//!
|
| 204 |
223 |
|
//! The bespoke widgets, a day-plan timeline and a kanban board and a calendar,
|
| 491 |
510 |
|
/// # The principle this encodes
|
| 492 |
511 |
|
///
|
| 493 |
512 |
|
/// A primitive owns every state it implies. A renderer that emits a hover rule
|
| 494 |
|
- |
/// for a thing owes disabled, focus and the capability answer for that same
|
| 495 |
|
- |
/// thing, because anything less exports the completion work to N consumers who
|
| 496 |
|
- |
/// will each do it differently.
|
|
513 |
+ |
/// for a thing owes disabled and the capability answer for that same thing,
|
|
514 |
+ |
/// because anything less exports the completion work to N consumers who will
|
|
515 |
+ |
/// each do it differently.
|
|
516 |
+ |
///
|
|
517 |
+ |
/// Focus is not on that list and was removed from this axis in 0.19.0. It is
|
|
518 |
+ |
/// the renderer's, decided after the description; see the crate header, "Reach,
|
|
519 |
+ |
/// focus and the focus ring", for the three terms and who owns each.
|
| 497 |
520 |
|
///
|
| 498 |
521 |
|
/// `#[non_exhaustive]` for the reason [`Fill`] and [`Depth`] carry it: growth
|
| 499 |
522 |
|
/// must not be a lockstep event across the three renderers.
|
| 500 |
523 |
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
| 501 |
524 |
|
#[non_exhaustive]
|
| 502 |
525 |
|
pub enum State {
|
| 503 |
|
- |
/// Keyboard focus, as distinct from the pointer having landed on something.
|
| 504 |
|
- |
///
|
| 505 |
|
- |
/// One ring, not one per primitive. Where the ring sits is [`Depth`]'s
|
| 506 |
|
- |
/// question and not a per-component choice: a well takes it inside its own
|
| 507 |
|
- |
/// edge and a raised surface takes it outside. That is one decision with
|
| 508 |
|
- |
/// two renderings rather than one decision per component, which is how the
|
| 509 |
|
- |
/// three apps ended up with three rings.
|
| 510 |
|
- |
Focus,
|
| 511 |
526 |
|
/// Present, visible, and not answering.
|
| 512 |
527 |
|
///
|
| 513 |
528 |
|
/// Not the same as absent, and deliberately not a [`Fill`]: a disabled
|
| 525 |
540 |
|
/// means resolving it once per consumer and disagreeing.
|
| 526 |
541 |
|
#[must_use]
|
| 527 |
542 |
|
pub const fn suppresses_interaction(self) -> bool {
|
|
543 |
+ |
// A match rather than a bare `true`, so a member added to this
|
|
544 |
+ |
// `#[non_exhaustive]` axis has to answer the question rather than
|
|
545 |
+ |
// inheriting an answer.
|
| 528 |
546 |
|
match self {
|
| 529 |
547 |
|
Self::Disabled => true,
|
| 530 |
|
- |
Self::Focus => false,
|
| 531 |
548 |
|
}
|
| 532 |
549 |
|
}
|
| 533 |
550 |
|
}
|
| 535 |
552 |
|
impl Intent for State {
|
| 536 |
553 |
|
fn token(self) -> &'static str {
|
| 537 |
554 |
|
match self {
|
| 538 |
|
- |
// Already derived by `makeover` from `action.primary`, and unused
|
| 539 |
|
- |
// until now for the same reason `hover-surface` was: nothing
|
| 540 |
|
- |
// emitted the rule that would consume it.
|
| 541 |
|
- |
Self::Focus => "focus-ring",
|
| 542 |
555 |
|
// Reusing the muted content intent rather than minting a
|
| 543 |
556 |
|
// `disabled` colour. Disabled is a reduction and not a status, and
|
| 544 |
557 |
|
// `makeover-webview`'s progress rules already record the reading
|
| 2305 |
2318 |
|
|
| 2306 |
2319 |
|
#[test]
|
| 2307 |
2320 |
|
fn an_act_is_reachable_until_it_is_disabled() {
|
| 2308 |
|
- |
// The one member a renderer must branch on. Focus is a state and does
|
| 2309 |
|
- |
// not stop the control answering, which is the distinction `State`
|
| 2310 |
|
- |
// makes and every hand-rolled button in the tree had to remember.
|
|
2321 |
+ |
// The one member a renderer must branch on, and since 0.19.0 the only
|
|
2322 |
+ |
// member there is. A stated state is not by itself a reason to stop
|
|
2323 |
+ |
// answering, which is the distinction `State` makes and every
|
|
2324 |
+ |
// hand-rolled button in the tree had to remember.
|
| 2311 |
2325 |
|
assert!(!Act::new("Save").disabled());
|
| 2312 |
|
- |
assert!(!Act::new("Save").state(State::Focus).disabled());
|
| 2313 |
2326 |
|
assert!(Act::new("Save").state(State::Disabled).disabled());
|
| 2314 |
2327 |
|
}
|
| 2315 |
2328 |
|
|
| 2369 |
2382 |
|
|
| 2370 |
2383 |
|
#[test]
|
| 2371 |
2384 |
|
fn only_disabled_stops_answering() {
|
| 2372 |
|
- |
// Focus is a thing you can still click. Getting this backwards is how
|
| 2373 |
|
- |
// a focus ring ends up on something inert.
|
| 2374 |
|
- |
assert!(!State::Focus.suppresses_interaction());
|
|
2385 |
+ |
// Kept in spirit from the version where `Focus` was the counter-example:
|
|
2386 |
+ |
// suppressing interaction is `Disabled`'s alone, so a member added here
|
|
2387 |
+ |
// later does not get to inherit it by being a state.
|
| 2375 |
2388 |
|
assert!(State::Disabled.suppresses_interaction());
|
| 2376 |
2389 |
|
}
|
| 2377 |
2390 |
|
|
| 2378 |
2391 |
|
#[test]
|
| 2379 |
|
- |
fn both_states_resolve_against_intents_makeover_already_derives() {
|
| 2380 |
|
- |
// Neither needs a new token, so this costs no `makeover` release.
|
| 2381 |
|
- |
assert_eq!(State::Focus.token(), "focus-ring");
|
|
2392 |
+ |
fn disabled_resolves_against_an_intent_makeover_already_derives() {
|
|
2393 |
+ |
// No new token, so this costs no `makeover` release.
|
| 2382 |
2394 |
|
assert_eq!(State::Disabled.token(), "content-muted");
|
| 2383 |
2395 |
|
}
|
| 2384 |
2396 |
|
|
| 2486 |
2498 |
|
Edge::Dark.token(),
|
| 2487 |
2499 |
|
Tone::Danger.token(),
|
| 2488 |
2500 |
|
Tone::Neutral.token(),
|
| 2489 |
|
- |
State::Focus.token(),
|
| 2490 |
2501 |
|
State::Disabled.token(),
|
| 2491 |
2502 |
|
] {
|
| 2492 |
2503 |
|
assert!(!t.starts_with('#'), "{t} looks like a value");
|