Skip to main content

max / quasi

3.6 KB · 80 lines History Blame Raw
1 //! What a renderer is, for the one question where the three differ.
2 //!
3 //! <!-- wiki: quasi-overview -->
4 //!
5 //! The renderers share no trait and looking for one is the wrong move, which
6 //! this crate's header already says. This is not that: it is a single fact
7 //! about a renderer that a *description* has no way to ask and that the
8 //! [`Destination::Local`](crate::Destination::Local) mark needs an answer to.
9
10 /// Whether a renderer has to be told what is local.
11 ///
12 /// [`Destination::Local`](crate::Destination::Local).
13 ///
14 /// ```text
15 /// Client -> quasi-immediate (egui), quasi-tui may ignore the mark
16 /// Hybrid -> quasi-webview must read it
17 /// ```
18 ///
19 /// # Why classify at all, rather than let each renderer ignore what it likes
20 ///
21 /// A client app has no real need to model the two differently, especially an
22 /// immediate-mode one. Every frame redraws from memory, so arrow keys moving a
23 /// highlight is not a special kind of thing there — it is what a list does. The
24 /// split exists because a browser has no built-in stateful control, so someone
25 /// has to say whether a keystroke goes to the server.
26 ///
27 /// Two of three renderers can therefore skip the whole area, and the choice was
28 /// between saying why and leaving it to convention. Convention erodes: the next
29 /// person to read `quasi-tui` and find no handling for a mark the vocabulary
30 /// carries reads it as an omission and fixes it, and the fix is a distinction
31 /// that means nothing in a terminal. Naming the class states the reason in the
32 /// one place both renderers can point at.
33 ///
34 /// Rejected: every renderer models the split — uniform, and egui and the TUI
35 /// carry a distinction that means nothing to them. Rejected: local interaction
36 /// is browser-only with the others degrading — cheapest, and audiofiles is a
37 /// live egui consumer, so the loss would be real rather than hypothetical.
38 ///
39 /// # What this is not
40 ///
41 /// Not a capability list and not a feature flag. It answers one question and
42 /// gaining a second variant is a sign something else is being smuggled in.
43 /// Whether a terminal can open a browser, whether a host can upload — those are
44 /// [`Destination::External`](crate::Destination::External) and
45 /// [`Action::by_host`](crate::Action::by_host), which say their piece on the
46 /// action rather than about the renderer.
47 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
48 pub enum Renderer {
49 /// Everything is local by construction: the renderer holds the state it
50 /// draws and redraws it from memory.
51 ///
52 /// A local action is something such a renderer already performs natively —
53 /// moving a highlight, closing a notice — so the mark tells it nothing it
54 /// did not know. It is free to ignore it, and the vocabulary promises it
55 /// loses nothing by doing so.
56 ///
57 /// What it may not do is dispatch one. There is no route on a local
58 /// destination, so [`Destination::route`](crate::Destination::route)
59 /// answers `None` and the renderer's own dispatch already declines; that is
60 /// the floor, and it holds without any code being written against this
61 /// type.
62 Client,
63
64 /// The split is real and must be honoured.
65 ///
66 /// A hybrid renderer emits markup that some other runtime performs later,
67 /// so it has to decide at emit time whether a keystroke becomes a request.
68 /// Getting that wrong is not a slower screen, it is a route asked on every
69 /// arrow key.
70 Hybrid,
71 }
72
73 impl Renderer {
74 /// Whether this renderer has to read the locality mark.
75 #[must_use]
76 pub const fn reads_locality(self) -> bool {
77 matches!(self, Self::Hybrid)
78 }
79 }
80