Skip to main content

max / audiofiles

Declare naming's form, and open audiofiles to the declaration transition The first declared shape in audiofiles, and the reason it is the smallest one in the tree is that this tree renders through egui rather than a webview. If the form is quietly HTML-shaped, the cheapest place to find out is a fifteen line modal. Nothing was lost and nothing was earned. The region, the form, its action, its button and the one text field all had spellings already, and the hint and the error are placed by a loop over their Option rather than by a predicate beside a total reader. Adds quasi-declare to the workspace, pinned to the same 0.1 the other two trees name.
Author: Max Johnson <me@maxj.phd> · 2026-09-04 20:35 UTC
Signed with PGP, not checked
Commit: a3fa684f2ec298206a78edd23668d9af29f94983
Parent: ef67eff
3 files changed, +24 insertions, -17 deletions
M Cargo.toml +1
@@ -28,6 +28,7 @@
28 28 # behind a `quasi` feature until 2026-08-25, when the last flip landed.
29 29 quasi-router = { git = "https://makenot.work/git/max/quasi.git", version = "0.101" }
30 30 quasi-immediate = { git = "https://makenot.work/git/max/quasi.git", version = "0.101" }
31 + quasi-declare = { git = "https://makenot.work/git/max/quasi.git", version = "0.1" }
31 32 egui = { version = "0.35", default-features = false, features = ["default_fonts"] }
32 33 egui_extras = { version = "0.35", default-features = false }
33 34 eframe = { version = "0.35", default-features = false, features = ["default_fonts", "glow"] }
@@ -17,6 +17,7 @@
17 17 audiofiles-rhai = { workspace = true, optional = true }
18 18 quasi-router = { workspace = true }
19 19 quasi-immediate = { workspace = true }
20 + quasi-declare = { workspace = true }
20 21 audiofiles-sync = { workspace = true }
21 22 egui = { workspace = true }
22 23 makeover-layout = { workspace = true }
@@ -60,10 +60,10 @@
60 60 //! renderer policy — where the caret goes is a fact about a host with a caret —
61 61 //! and neither is described. A host that has no focus to give loses nothing.
62 62
63 - use quasi_router::layout::{FieldKind, Tone};
63 + use quasi_declare::declare;
64 + use quasi_router::layout::Tone;
64 65 use quasi_router::{
65 - Act, Action, Field, Node, Outcome, RegionKind, Request, Response, RouteError, Router, Screen,
66 - Slot,
66 + Act, Action, Node, Outcome, RegionKind, Request, Response, RouteError, Router, Screen, Slot,
67 67 };
68 68
69 69 use super::Panels;
@@ -314,21 +314,26 @@
314 314 ))
315 315 }
316 316
317 - /// The one question, in a region of its own so a refusal can replace it.
318 - fn form(asking: &Asking, value: &str, error: Option<&str>) -> Node {
319 - let mut field = Field::new(FieldKind::Text, NAME, asking.label).value(value);
320 - if let Some(hint) = asking.hint {
321 - field = field.hint(hint);
322 - }
323 - if let Some(error) = error {
324 - field = field.error(error);
325 - }
317 + declare! {
318 + /// The one question, in a region of its own so a refusal can replace it.
319 + ///
320 + /// The hint and the error are each placed by a loop over their `Option`
321 + /// rather than by a predicate and a total reader: a setting is an item and
322 + /// a loop is an item, so `for x in <option> { <setting> x; }` says the
323 + /// whole of an optional setting in one line.
324 + shape form(asking: &Asking, value: &str, error: Option<&str>) -> Node;
326 325
327 - Node::Region(Slot::new(FORM, RegionKind::Group).with(Node::Form {
328 - fields: vec![field],
329 - submit: asking.submit.to_owned(),
330 - action: Action::post(asking.action.clone()),
331 - }))
326 + region FORM as Group {
327 + form post asking.action.clone() {
328 + submit asking.submit;
329 +
330 + field Text NAME asking.label {
331 + value value;
332 + for &said in asking.hint.iter() { hint said; }
333 + for &why in error.iter() { error why; }
334 + }
335 + }
336 + }
332 337 }
333 338
334 339 /// The screen a finished or cancelled modal leaves behind.