Skip to main content

denise_forms/
build.rs

1//! Turning a parsed form into a live widget tree.
2
3use std::collections::HashMap;
4
5use denise::{Rect, Size};
6use denise_ui::widgets::describe::{
7    ALIGNMENTS, FITS, ORIENTATIONS, PRESENCES, Payload, Property, PropertyKind, RADII, ROLES,
8    Value, WidgetInfo, role_from_name,
9};
10use denise_ui::widgets::{
11    Alert, Avatar, Badge, Button, Carousel, Checkbox, Collapse, Column, Divider, Fit, Image, Label,
12    List, ListItem, MenuBar, Panel, Progress, RadialProgress, RadioGroup, Rating, Select, Slider,
13    Spinner, Table, Tabs, TextInput, Timeline, TimelineItem, Toggle, Tree, TreeItem, Video,
14};
15use denise_ui::{Anchors, Dock, NodeId, Ui};
16use kdl::{KdlDocument, KdlNode, KdlValue};
17
18use crate::error::{At, Error, Reason};
19use crate::form::{Form, FormKind, MAX_DEPTH, Placement};
20
21/// Pixels for a picture a form named, as [`Wiring::asset`] hands them back.
22#[derive(Clone, Debug)]
23pub struct Picture {
24    /// Premultiplied `0xAARRGGBB`, which is `denise-ui`'s contract exactly.
25    pub pixels: Vec<u32>,
26    /// The picture's own size.
27    pub size: Size,
28}
29
30/// A message a form named, in the shape the widget holding it needs.
31///
32/// Widgets do not all take a message the same way, and none of them takes a
33/// closure: a `Button` holds an `M`, a `Checkbox` a `fn(bool) -> M`, a `List` a
34/// `fn(usize) -> M`, a `Slider` a `fn(f32) -> M`. Those are **function
35/// pointers**, so nothing this crate could build from a name would fit — but an
36/// enum's tuple variant already is one:
37///
38/// ```
39/// # use denise_forms::Handler;
40/// #[derive(Clone, Copy)]
41/// enum Message {
42///     Save,
43///     Notify(bool),
44/// }
45///
46/// let save = Handler::Plain(Message::Save);
47/// // `Message::Notify` *is* a `fn(bool) -> Message`.
48/// let notify = Handler::Bool(Message::Notify);
49/// # let _ = (save, notify);
50/// ```
51#[derive(Clone, Copy, Debug)]
52pub enum Handler<M> {
53    /// The message itself, for a widget that holds one: a button, a select, a
54    /// text field's submit.
55    Plain(M),
56    /// `fn(bool) -> M` — a checkbox, a toggle, a collapse.
57    Bool(fn(bool) -> M),
58    /// `fn(usize) -> M` — anything that selects one of several.
59    Index(fn(usize) -> M),
60    /// `fn(f32) -> M` — a slider, a rating.
61    Number(fn(f32) -> M),
62}
63
64impl<M> Handler<M> {
65    fn wanted(payload: Payload) -> &'static str {
66        match payload {
67            Payload::None => "the message itself",
68            Payload::Bool => "a `fn(bool) -> M`",
69            Payload::Index => "a `fn(usize) -> M`",
70            Payload::Number => "a `fn(f32) -> M`",
71        }
72    }
73}
74
75/// What an application supplies a form that this crate cannot: its own message
76/// type, and its own pictures.
77///
78/// A plain closure implements this, which is all most forms need. Implement it on
79/// a type when the form also names pictures.
80pub trait Wiring<M> {
81    /// Turns a message name from the file into a message of the application's
82    /// own type. `payload` says which shape the widget needs.
83    fn message(&mut self, name: &str, payload: Payload) -> Option<Handler<M>>;
84
85    /// Loads a picture, by a path **relative to the form file**.
86    ///
87    /// The default has none, so a form naming a picture in an application that
88    /// supplied no loader fails with the path in the message rather than drawing
89    /// a hole. This crate decodes nothing and does not depend on `denise-image`:
90    /// that keeps a board with its pictures compiled in from linking a decoder it
91    /// will never call.
92    fn asset(&mut self, path: &str) -> Option<Picture> {
93        let _ = path;
94        None
95    }
96}
97
98impl<M, F> Wiring<M> for F
99where
100    F: FnMut(&str, Payload) -> Option<Handler<M>>,
101{
102    fn message(&mut self, name: &str, payload: Payload) -> Option<Handler<M>> {
103        self(name, payload)
104    }
105}
106
107/// One node the form put in the tree, and where in the file it came from.
108///
109/// A designer needs both halves: the [`NodeId`] to hit-test and draw a selection
110/// around, and the [`path`](Placed::path) to edit when the selection moves. The
111/// path is a list of child indices from the `form` node down, which is stable
112/// across a rebuild in a way a byte offset is not — every edit shifts the offsets
113/// after it, and the whole point is to edit and carry on.
114#[derive(Clone, Debug, PartialEq, Eq)]
115pub struct Placed {
116    /// The node in the tree.
117    pub id: NodeId,
118    /// Its parent in the tree, or `None` for a node directly under the form.
119    pub parent: Option<NodeId>,
120    /// What kind of widget it is.
121    pub kind: &'static str,
122    /// The name the file gave it, if it gave one.
123    pub name: Option<String>,
124    /// Child indices from the `form` node's children down to this node.
125    pub path: Vec<usize>,
126}
127
128/// What a form built, so an application can find what it made.
129#[derive(Clone, Debug, Default)]
130pub struct Built {
131    names: HashMap<String, NodeId>,
132    placed: Vec<Placed>,
133    pages: Vec<Page>,
134}
135
136/// One `tab`'s page: the container its subtree was built into.
137///
138/// Only a `tab` carrying children has one. A designer needs these because a
139/// page that is not showing is not in the tree's order at all — nothing in it
140/// paints, answers a press or takes the caret — so reaching the second tab's
141/// contents means showing that page first.
142#[derive(Clone, Debug)]
143pub struct Page {
144    /// The `tab` node's path in the document.
145    pub path: Vec<usize>,
146    /// Which tab it is, counting every `tab` in the strip. What `selected`
147    /// names.
148    pub ordinal: usize,
149    /// The container the page's widgets were built into.
150    pub id: NodeId,
151}
152
153impl Built {
154    /// See [`Form::build`] for one of these being made and read.
155    /// The node a form gave this name, if it gave one that name.
156    pub fn node(&self, name: &str) -> Option<NodeId> {
157        self.names.get(name).copied()
158    }
159
160    /// See [`Form::build`] for one of these being made and read.
161    /// Every name the form gave a node, in no particular order.
162    pub fn names(&self) -> impl Iterator<Item = (&str, NodeId)> {
163        self.names.iter().map(|(name, &id)| (name.as_str(), id))
164    }
165
166    /// Every `tab` page the form built, in file order.
167    ///
168    /// Empty for a form whose tabs are bare labels, which is every form written
169    /// before a `tab` could hold anything. See [`Page`].
170    pub fn pages(&self) -> &[Page] {
171        &self.pages
172    }
173
174    /// Every node the form built, in file order.
175    ///
176    /// See [`Form::build`] for one of these being made and read.
177    /// Includes the ones with no name: a designer selects what a person clicked
178    /// on, and most of what a person clicks on was never named.
179    pub fn placed(&self) -> &[Placed] {
180        &self.placed
181    }
182
183    /// See [`Form::build`] for one of these being made and read.
184    /// The node at a path, if the form put one there.
185    pub fn at(&self, path: &[usize]) -> Option<&Placed> {
186        self.placed.iter().find(|p| p.path == path)
187    }
188
189    /// See [`Form::build`] for one of these being made and read.
190    /// How many nodes were named.
191    pub fn len(&self) -> usize {
192        self.names.len()
193    }
194
195    /// See [`Form::build`] for one of these being made and read.
196    /// Whether the form named nothing.
197    pub fn is_empty(&self) -> bool {
198        self.names.is_empty()
199    }
200}
201
202const ANCHOR_EDGES: &[&str] = &["left", "top", "right", "bottom"];
203const DOCK_SIDES: &[&str] = &["top", "bottom", "left", "right", "fill"];
204
205/// Anywhere on a form's surface, and then some.
206///
207/// A rectangle is advice to an editor, not a rule: a node may sit outside its
208/// parent and the tree will clip it, which is occasionally what somebody means.
209const ANYWHERE: PropertyKind = PropertyKind::Int {
210    min: -8192,
211    max: 8192,
212};
213
214/// The properties the `form` node itself carries, whatever kind it is.
215///
216/// Not `version`, which is the file format's rather than the form's and is not
217/// somebody's to edit; and not the title, which is the node's *argument* rather
218/// than a property. Everything else about a form is here, which is what lets an
219/// inspector show a form the same way it shows a widget — from a descriptor,
220/// with no list of its own.
221pub const FORM_PROPERTIES: &[Property] = &[
222    Property::new(
223        "name",
224        PropertyKind::Text,
225        "What the application calls this form. Names what the typed layer generates.",
226    ),
227    Property::new(
228        "kind",
229        PropertyKind::Enum(FormKind::NAMES),
230        "What this form is for: a screen, a window, a dialog, a drawer, a shelf, or a fragment.",
231    ),
232    Property::new(
233        "width",
234        PropertyKind::Int { min: 1, max: 8192 },
235        "The width the form was designed at, in logical pixels.",
236    ),
237    Property::new(
238        "height",
239        PropertyKind::Int { min: 1, max: 8192 },
240        "The height the form was designed at, in logical pixels.",
241    ),
242    Property::new(
243        "theme",
244        PropertyKind::Enum(crate::form::THEMES),
245        "Which built-in theme the form is drawn with.",
246    ),
247    Property::new(
248        "background",
249        PropertyKind::Enum(denise_ui::widgets::ROLES),
250        "The surface the form is drawn on.",
251    ),
252    Property::new(
253        "scaling",
254        PropertyKind::Enum(crate::form::Scaling::NAMES),
255        "Whether this form may be drawn at another size: none, proportional or stretch.",
256    ),
257];
258
259/// What only a window has.
260const WINDOW_PROPERTIES: &[Property] = &[
261    Property::new(
262        "resizable",
263        PropertyKind::Bool,
264        "Whether the window may be resized. Windows only.",
265    ),
266    Property::new(
267        "min-width",
268        PropertyKind::Int { min: 0, max: 8192 },
269        "The narrowest the window may be made. Windows only.",
270    ),
271    Property::new(
272        "min-height",
273        PropertyKind::Int { min: 0, max: 8192 },
274        "The shortest the window may be made. Windows only.",
275    ),
276];
277
278/// What only a dialog has.
279const DIALOG_PROPERTIES: &[Property] = &[Property::new(
280    "dim",
281    PropertyKind::Int { min: 0, max: 255 },
282    "How dark the backdrop behind the dialog is, 0 to 255. Dialogs only.",
283)];
284
285/// What comes in from an edge: a drawer and a shelf, which differ in modality
286/// and not in shape.
287const EDGE_PROPERTIES: &[Property] = &[
288    Property::new(
289        "side",
290        PropertyKind::Enum(denise_ui::widgets::SIDES),
291        "Which edge it comes in from.",
292    ),
293    Property::new(
294        "extent",
295        PropertyKind::Int { min: 1, max: 8192 },
296        "How far it comes in. Required; across the other axis it covers the surface.",
297    ),
298];
299
300/// The properties a form of this kind carries **and no other kind does**.
301///
302/// A `resizable` on a screen is not a property with no effect; it is a mistake,
303/// and saying so is the whole reason this is a function of the kind rather than
304/// one long list.
305/// ```
306/// # use denise_forms::{FORM_PROPERTIES, FormKind, form_property, kind_properties};
307/// // Everything every form has.
308/// assert!(FORM_PROPERTIES.iter().any(|it| it.name == "width"));
309///
310/// // And what only this kind has.
311/// assert!(kind_properties(FormKind::Window).iter().any(|it| it.name == "resizable"));
312/// assert!(kind_properties(FormKind::Screen).is_empty());
313///
314/// // `form_property` is the two together, which is what "may a form of this
315/// // kind say this?" means.
316/// assert!(form_property(FormKind::Window, "resizable").is_some());
317/// assert!(form_property(FormKind::Screen, "resizable").is_none());
318/// assert!(form_property(FormKind::Screen, "width").is_some());
319/// ```
320pub const fn kind_properties(kind: FormKind) -> &'static [Property] {
321    match kind {
322        FormKind::Window => WINDOW_PROPERTIES,
323        FormKind::Dialog => DIALOG_PROPERTIES,
324        FormKind::Drawer | FormKind::Shelf => EDGE_PROPERTIES,
325        FormKind::Screen | FormKind::Fragment => &[],
326    }
327}
328
329/// Whether the `form` node may carry this property, given its kind.
330/// See [`kind_properties`].
331pub fn form_property(kind: FormKind, name: &str) -> Option<&'static Property> {
332    FORM_PROPERTIES
333        .iter()
334        .chain(kind_properties(kind))
335        .find(|property| property.name == name)
336}
337
338/// The properties the *tree* owns rather than the widget.
339///
340/// Geometry, visibility, ordering, placement. A widget's descriptor never
341/// mentions them, so they are checked against this list before a widget is asked
342/// whether it has heard of them.
343///
344/// Described the same way a widget describes its own, and for the same reason:
345/// the designer's inspector draws an editor per [`Property`] and has no table of
346/// its own, so `x` and `dock` get one from here exactly as `role` gets one from
347/// the widget.
348pub const NODE_PROPERTIES: &[Property] = &[
349    Property::new(
350        "name",
351        PropertyKind::Text,
352        "What the application calls this node. Unique within the form.",
353    ),
354    Property::new("x", ANYWHERE, "Left edge, relative to the parent."),
355    Property::new("y", ANYWHERE, "Top edge, relative to the parent."),
356    Property::new(
357        "w",
358        PropertyKind::Int { min: 0, max: 8192 },
359        "Width in pixels.",
360    ),
361    Property::new(
362        "h",
363        PropertyKind::Int { min: 0, max: 8192 },
364        "Height in pixels.",
365    ),
366    Property::new(
367        "visible",
368        PropertyKind::Bool,
369        "Drawn and able to be touched, or neither.",
370    ),
371    Property::new(
372        "enabled",
373        PropertyKind::Bool,
374        "Takes input, or is greyed out and does not.",
375    ),
376    Property::new(
377        "z",
378        PropertyKind::Int {
379            min: -1000,
380            max: 1000,
381        },
382        "Paint order among siblings; higher is nearer the front.",
383    ),
384    Property::new(
385        "tooltip",
386        PropertyKind::Text,
387        "What resting the pointer on this node says.",
388    ),
389    Property::new(
390        "scroll",
391        PropertyKind::Bool,
392        "Whether children reaching past this node can be scrolled to.",
393    ),
394    Property::new(
395        "stack",
396        PropertyKind::Int { min: 0, max: 1000 },
397        "Stacks the children down the node with this many pixels between them.",
398    ),
399    Property::new(
400        "focus",
401        PropertyKind::Bool,
402        "Whether this node holds the caret when the form opens. One per form.",
403    ),
404    Property::new(
405        "anchor",
406        PropertyKind::Text,
407        "Edges held as the parent resizes: any of left, top, right, bottom.",
408    ),
409    Property::new(
410        "dock",
411        PropertyKind::Enum(DOCK_SIDES),
412        "An edge of the parent this node takes for itself, before the rest are placed.",
413    ),
414];
415
416/// The tree-owned property of this name, if there is one.
417/// ```
418/// # use denise_forms::node_property;
419/// // The tree owns geometry and visibility; no widget declares them.
420/// assert!(node_property("x").is_some());
421/// assert!(node_property("dock").is_some());
422/// // A widget's own property is not one of these.
423/// assert!(node_property("role").is_none());
424/// ```
425pub fn node_property(name: &str) -> Option<&'static Property> {
426    NODE_PROPERTIES.iter().find(|p| p.name == name)
427}
428
429/// Child nodes that are a parent's *content* rather than nodes of their own.
430const COLLECTIONS: &[&str] = &[
431    "option", "item", "column", "row", "event", "picture", "tab", "title",
432];
433
434/// The block a designer's placeholder content lives in.
435///
436/// A `table`'s rows and a `timeline`'s events are four names somebody typed so
437/// the widget looks like itself on a canvas; the application supplies the real
438/// ones. Written here, they are skipped by every build except a designer's, so
439/// they never reach a kiosk. See [`PropertyKind::Placeholder`].
440pub const DESIGN: &str = "design";
441
442/// Whether `kind` reads a collection called `name` from a `design` block.
443///
444/// Asked of the widget's own descriptor rather than a table kept here, which is
445/// the same rule the rest of the schema follows: a widget publishes what it
446/// takes, and nothing enumerates widgets.
447pub(crate) fn is_placeholder(kind: &str, name: &str) -> bool {
448    denise_ui::widgets::all()
449        .iter()
450        .find(|info| info.kind == kind)
451        .is_some_and(|info| {
452            info.properties
453                .iter()
454                .any(|p| p.name == name && p.kind == PropertyKind::Placeholder)
455        })
456}
457
458/// Whether a widget of this kind can hold nodes of their own.
459///
460/// Two do. Everything else either has no children or has *content* — a `select`
461/// holds `option`s, a `table` holds `column`s — which is not the same thing: a
462/// designer dropping a button on a `select` has missed, and dropping one on a
463/// `panel` means it.
464/// ```
465/// # use denise_forms::owns_children;
466/// assert!(owns_children("panel"));
467/// assert!(owns_children("collapse"));
468/// // Content is not children: a `select` holds options, and dropping a button
469/// // on one has missed.
470/// assert!(!owns_children("select"));
471/// assert!(!owns_children("label"));
472/// ```
473pub fn owns_children(kind: &str) -> bool {
474    matches!(kind, "panel" | "collapse")
475}
476
477/// The kinds that carry their text as the node's argument.
478///
479/// `label "Heading"` rather than `label text="Heading"`. Both build the same
480/// thing; the first is how every form in this repository is written, and is what
481/// [`seed`] produces.
482const ARGUMENT: &[&str] = &[
483    "label", "badge", "divider", "alert", "button", "checkbox", "toggle", "collapse",
484];
485
486/// How big a new widget of this kind should start out.
487///
488/// **Authoring defaults, not intrinsic sizes.** This toolkit has no layout engine
489/// and nothing here has a size of its own: a button is whatever rectangle the
490/// form gives it. These are the rectangles that make a dropped widget look like
491/// what it is, so that somebody can see what they placed before they resize it —
492/// which is a question about writing forms, and so this crate's, rather than a
493/// question about widgets.
494/// ```
495/// # use denise_forms::default_size;
496/// // A button is wider than it is tall; an avatar is square.
497/// let button = default_size("button");
498/// assert!(button.width > button.height);
499/// let avatar = default_size("avatar");
500/// assert_eq!(avatar.width, avatar.height);
501/// // A kind nobody has heard of still gets something you can see and click.
502/// assert!(default_size("banana").width > 0);
503/// ```
504pub fn default_size(kind: &str) -> Size {
505    let (width, height) = match kind {
506        "alert" => (320, 36),
507        "avatar" => (40, 40),
508        "badge" => (60, 20),
509        "button" => (100, 32),
510        "carousel" => (224, 120),
511        "checkbox" | "toggle" => (200, 24),
512        "collapse" => (224, 40),
513        "divider" => (160, 16),
514        "image" => (120, 90),
515        "list" => (200, 160),
516        "panel" => (200, 120),
517        "progress" => (200, 8),
518        "radial-progress" => (48, 48),
519        "radio-group" => (220, 76),
520        "rating" => (140, 24),
521        "select" | "text-input" => (220, 34),
522        "slider" => (200, 24),
523        "spinner" => (24, 24),
524        "table" => (320, 180),
525        "tree" => (220, 180),
526        "tabs" => (320, 36),
527        "menubar" => (320, 28),
528        "timeline" => (220, 140),
529        "video" => (160, 90),
530        // `label`, and anything this list has not heard of.
531        _ => (120, 20),
532    };
533    Size::new(width, height)
534}
535
536/// The smallest node of this kind that a form can actually hold, as file text.
537///
538/// What a designer writes when somebody drops a widget on the canvas. A rectangle
539/// is the most of it — but "a rect and nothing else" is not true of every widget,
540/// because three of them have a property the builder *requires*: an `alert` has
541/// no colour to draw itself in without a `role`, a `slider` has no range without
542/// `min` and `max`, and an `image` has nothing to draw without a `src`. A node
543/// missing one of those parses and then will not build, so a designer that wrote
544/// one would place a widget and break the form.
545///
546/// `select` and `collapse` were a fourth and fifth until #118, and they were the
547/// awkward ones: what they lacked was not a number but a *message*, so the seed
548/// had to invent a name nobody had asked for. Both have an inert constructor
549/// now, so a dropped one carries no message at all.
550///
551/// This lives beside the code that raises those requirements, so the two cannot
552/// drift; a test seeds every widget in [`all`](denise_ui::widgets::all), builds
553/// the result, and fails if a new one needs something this does not give it.
554///
555/// ```
556/// # use denise_forms::{seed, Form};
557/// use denise::Rect;
558///
559/// assert_eq!(
560///     seed("button", Rect::new(16, 24, 100, 32)),
561///     r#"button "button" x=16 y=24 w=100 h=32"#,
562/// );
563/// ```
564pub fn seed(kind: &str, rect: Rect) -> String {
565    let mut node = String::from(kind);
566    if ARGUMENT.contains(&kind) {
567        // The kind, as a placeholder. A label dropped with nothing to say draws
568        // nothing, and a widget you cannot see is a widget you cannot find
569        // again the moment you click somewhere else.
570        node.push_str(&format!(" {:?}", kind));
571    }
572    node.push_str(&format!(
573        " x={} y={} w={} h={}",
574        rect.x, rect.y, rect.width, rect.height
575    ));
576    // Only what the engine *requires*, and nothing a person would have to
577    // delete. `select` and `collapse` were here until #118 gave them inert
578    // constructors: they had to be seeded with a message nobody wanted, named
579    // after nothing, because a form file could not build either without one.
580    node.push_str(match kind {
581        "alert" => " role=info",
582        "slider" => " min=0 max=100",
583        // A path that is not there yet. An engine that cannot load it says so;
584        // a designer draws a hole and carries on.
585        "image" => " src=\"picture.png\"",
586        _ => "",
587    });
588    node
589}
590
591/// A whole form file with nothing in it yet, writing **only** what is not a
592/// default.
593///
594/// What *File → New* produces. A form that spelled out every default would read
595/// as a form somebody had made decisions about, and the next person would have
596/// to check each one against the schema to find out that none of them meant
597/// anything. The exception is `extent`, which a drawer and a shelf must say:
598/// this picks a third of the axis it comes in along, which is a drawer somebody
599/// will recognise rather than one they have to fix before they can see it.
600/// ```
601/// # use denise::Size;
602/// # use denise_forms::{Form, FormKind, seed_form};
603/// // A screen is every default but its size, so it says nothing else.
604/// let screen = seed_form("Untitled", FormKind::Screen, Size::new(800, 480));
605/// assert_eq!(screen, "form \"Untitled\" version=1 width=800 height=480\n");
606///
607/// // What comes in from an edge has to say how far, so this picks one.
608/// let drawer = seed_form("Filters", FormKind::Drawer, Size::new(1024, 600));
609/// let form = Form::parse(&drawer)?;
610/// assert_eq!(form.kind(), FormKind::Drawer);
611/// assert_eq!(form.extent(), 1024 / 3);
612/// # Ok::<(), denise_forms::Error>(())
613/// ```
614pub fn seed_form(title: &str, kind: FormKind, size: Size) -> String {
615    let mut out = format!("form {title:?} version={}", crate::form::VERSION);
616    if kind != FormKind::Screen {
617        out.push_str(&format!(" kind={}", FormKind::NAMES[kind as usize]));
618    }
619    out.push_str(&format!(" width={} height={}", size.width, size.height));
620    if matches!(kind, FormKind::Drawer | FormKind::Shelf) {
621        let along = match kind.default_side() {
622            denise_ui::Side::Above | denise_ui::Side::Below => size.height,
623            denise_ui::Side::Before | denise_ui::Side::After => size.width,
624        };
625        out.push_str(&format!(" extent={}", (along / 3).max(1)));
626    }
627    out.push('\n');
628    out
629}
630
631impl Form {
632    /// ```
633    /// # use denise_forms::{Form, Handler, Payload};
634    /// # use denise_ui::Ui;
635    /// #[derive(Clone, Copy, PartialEq, Debug)]
636    /// enum Message {
637    ///     Greet,
638    /// }
639    ///
640    /// let form = Form::parse(
641    ///     r#"form "Hello" version=1 width=320 height=120 { button "Greet" name=go x=8 y=8 w=90 h=30 on-press=greet }"#,
642    /// )?;
643    ///
644    /// let mut ui: Ui<Message> = Ui::new(form.size(), form.theme());
645    /// let root = ui.root();
646    ///
647    /// // The one thing a file cannot hold: this application's own message type.
648    /// let built = form.build(&mut ui, root, &mut |name: &str, payload: Payload| {
649    ///     match (name, payload) {
650    ///         ("greet", Payload::None) => Some(Handler::Plain(Message::Greet)),
651    ///         _ => None,
652    ///     }
653    /// })?;
654    ///
655    /// // What the file named, by the name it used.
656    /// let button = built.node("go").expect("the form names it `go`");
657    /// assert_eq!(built.len(), 1);
658    /// assert!(!built.is_empty());
659    ///
660    /// // And everything it put on screen, named or not, in file order.
661    /// assert_eq!(built.placed().len(), 1);
662    /// assert_eq!(built.at(&[0]).map(|node| node.kind), Some("button"));
663    /// assert_eq!(built.at(&[0]).map(|node| node.id), Some(button));
664    /// assert_eq!(
665    ///     built.names().map(|(name, _)| name).collect::<Vec<_>>(),
666    ///     vec!["go"],
667    /// );
668    /// # Ok::<(), denise_forms::Error>(())
669    /// ```
670    ///
671    /// Builds this form into `ui` under `parent`.
672    ///
673    /// Nodes are added in file order, so paint order is file order. See the
674    /// [crate documentation](crate) for what `wiring` supplies and why.
675    ///
676    /// # Errors
677    ///
678    /// Every failure carries a line and a column. See [`Reason`](crate::Reason)
679    /// for the whole list.
680    pub fn build<M: Clone + 'static>(
681        &self,
682        ui: &mut Ui<M>,
683        parent: NodeId,
684        wiring: &mut impl Wiring<M>,
685    ) -> Result<Built, Error> {
686        self.build_fitted(
687            ui,
688            parent,
689            Placement {
690                x: 1.0,
691                y: 1.0,
692                rect: Rect::from_size(self.size()),
693            },
694            wiring,
695        )
696    }
697
698    /// Builds this form at `scale`: every rectangle and every length in it
699    /// multiplied once, on the way in.
700    ///
701    /// The DPI answer this toolkit gives, for a form. An application computing
702    /// its own rectangles multiplies them itself — three lines, and
703    /// `examples/hello` is those three lines. A form file has no application
704    /// doing that, so the multiplying goes where the rectangles are computed,
705    /// which is here.
706    ///
707    /// **Two things the caller still has to do**, because neither belongs to a
708    /// subtree:
709    ///
710    /// ```no_run
711    /// # use denise::{Size, theme};
712    /// # use denise_forms::Form;
713    /// # use denise_ui::{Ui, Void};
714    /// # let form = Form::parse("").unwrap();
715    /// # let scale = 2.0;
716    /// // The theme's metrics, or every widget is the old size inside a new
717    /// // rectangle — a 2x button with a 6px corner on it.
718    /// let mut ui: Ui<Void> = Ui::new(Size::new(1920, 1080), form.theme().scaled(scale));
719    /// ```
720    ///
721    /// ...and putting the form where it goes, which is [`Form::fit`].
722    ///
723    /// **Text scales like a rectangle here, and that is a choice.** A 1024x600
724    /// form on a 1920x1080 panel gets 16 px text at 30 px. That is right when
725    /// the panel is the same screen at a higher density and wrong when it is a
726    /// bigger screen meant to show more. This does the first one. The second is
727    /// not a multiplication and no file can express it.
728    ///
729    /// # Errors
730    ///
731    /// The same as [`Form::build`]; scaling adds no failure of its own.
732    pub fn build_scaled<M: Clone + 'static>(
733        &self,
734        ui: &mut Ui<M>,
735        parent: NodeId,
736        scale: f32,
737        wiring: &mut impl Wiring<M>,
738    ) -> Result<Built, Error> {
739        self.build_fitted(
740            ui,
741            parent,
742            Placement {
743                x: scale,
744                y: scale,
745                rect: Rect::from_size(self.size()).scaled(scale),
746            },
747            wiring,
748        )
749    }
750
751    /// Builds the form **and the placeholder content a designer needs to see**.
752    ///
753    /// Every other build skips `design { … }` blocks, so a `table` comes up with
754    /// its columns and no rows and a `timeline` with no events: those are the
755    /// application's to supply, and a kiosk should not carry four names somebody
756    /// typed to make a canvas look right. A designer is the one caller that
757    /// wants them, because a table drawn with no rows is not a table anybody can
758    /// lay out against.
759    ///
760    /// `scale` is [`Form::build_scaled`]'s, so a designer's canvas magnifies the
761    /// same way.
762    ///
763    /// ```
764    /// # use denise_forms::{Form, Payload, Handler, Wiring};
765    /// # use denise_ui::{Ui, Void};
766    /// let source = r#"
767    /// form "F" version=1 width=200 height=80 {
768    ///     table name=t x=0 y=0 w=200 h=80 {
769    ///         column "Name"
770    ///         design {
771    ///             row "Ada"
772    ///         }
773    ///     }
774    /// }
775    /// "#;
776    /// let form = Form::parse(source)?;
777    /// let mut wiring = |_: &str, _: Payload| None::<Handler<Void>>;
778    ///
779    /// // What ships: the column, and no rows at all.
780    /// let mut ui: Ui<Void> = Ui::new(form.size(), form.theme());
781    /// let root = ui.root();
782    /// form.build(&mut ui, root, &mut wiring)?;
783    ///
784    /// // What the designer draws.
785    /// let mut canvas: Ui<Void> = Ui::new(form.size(), form.theme());
786    /// let root = canvas.root();
787    /// form.build_with_design(&mut canvas, root, 1.0, &mut wiring)?;
788    /// # Ok::<(), denise_forms::Error>(())
789    /// ```
790    pub fn build_with_design<M: Clone + 'static>(
791        &self,
792        ui: &mut Ui<M>,
793        parent: NodeId,
794        scale: f32,
795        wiring: &mut impl Wiring<M>,
796    ) -> Result<Built, Error> {
797        self.build_inner(
798            ui,
799            parent,
800            Placement {
801                x: scale,
802                y: scale,
803                rect: Rect::from_size(self.size()).scaled(scale),
804            },
805            wiring,
806            true,
807        )
808    }
809
810    /// Builds this form at a [`Fit`] — a factor per axis, which is what
811    /// [`Scaling::Stretch`](crate::Scaling::Stretch) needs and [`Form::fit`] works
812    /// out.
813    ///
814    /// Only [`Placement::x`] and [`Placement::y`] are read. [`Placement::rect`] is where the
815    /// *caller* puts the node this builds into, and is none of this method's
816    /// business: a form is built under whatever `parent` it is given.
817    ///
818    /// ```
819    /// # use denise::{Rect, Size, theme};
820    /// # use denise_forms::Form;
821    /// # use denise_ui::{Ui, Void, widgets::Panel};
822    /// let form = Form::parse(
823    ///     r#"form "F" version=1 width=200 height=100 scaling=proportional {
824    ///         label "Hi" name=hi x=10 y=10 w=100 h=20 size=16
825    ///     }"#,
826    /// )?;
827    ///
828    /// let surface = Size::new(400, 400);
829    /// let fit = form.fit(surface);
830    ///
831    /// // The theme is scaled once, here, and the form is built into a panel at
832    /// // the rectangle the fit worked out.
833    /// let mut ui: Ui<Void> = Ui::new(surface, form.theme().scaled(fit.uniform()));
834    /// let root = ui.root();
835    /// let stage = ui.add(root, Panel::filled(form.background()), fit.rect).unwrap();
836    /// let mut nothing = |_: &str, _: denise_forms::Payload| None;
837    /// let built = form.build_fitted(&mut ui, stage, fit, &mut nothing)?;
838    ///
839    /// let hi = built.node("hi").expect("the form names it");
840    /// assert_eq!(ui.layout(hi), Some(Rect::new(20, 20, 200, 40)), "twice as big");
841    /// assert_eq!(
842    ///     ui.get_property(hi, "size"),
843    ///     Some(denise_ui::widgets::Value::Int(32)),
844    ///     "and so is the text",
845    /// );
846    /// # Ok::<(), denise_forms::Error>(())
847    /// ```
848    ///
849    /// # Errors
850    ///
851    /// The same as [`Form::build`].
852    pub fn build_fitted<M: Clone + 'static>(
853        &self,
854        ui: &mut Ui<M>,
855        parent: NodeId,
856        fit: Placement,
857        wiring: &mut impl Wiring<M>,
858    ) -> Result<Built, Error> {
859        self.build_inner(ui, parent, fit, wiring, false)
860    }
861
862    /// See [`Form::build_fitted`] and [`Form::build_with_design`].
863    fn build_inner<M: Clone + 'static>(
864        &self,
865        ui: &mut Ui<M>,
866        parent: NodeId,
867        fit: Placement,
868        wiring: &mut impl Wiring<M>,
869        designing: bool,
870    ) -> Result<Built, Error> {
871        let mut builder = Builder {
872            form: self,
873            ui,
874            wiring,
875            fit,
876            designing,
877            built: Built::default(),
878            focused: None,
879        };
880        let children: Vec<&KdlNode> = self
881            .root()
882            .children()
883            .map(|d| d.nodes().iter().collect())
884            .unwrap_or_default();
885        for (index, node) in children.into_iter().enumerate() {
886            builder.node(node, parent, 0, &[index])?;
887        }
888        // The caret goes last, once every node exists: a form may name a field
889        // that appears after the one before it in the file.
890        let focused = builder.focused;
891        let built = builder.built;
892        if let Some(id) = focused {
893            ui.focus(Some(id));
894        }
895        Ok(built)
896    }
897}
898
899struct Builder<'a, M: 'static, W> {
900    form: &'a Form,
901    ui: &'a mut Ui<M>,
902    wiring: &'a mut W,
903    /// What every rectangle and every length is multiplied by on the way in.
904    /// `1.0` on both axes for [`Form::build`], which is why that is this with
905    /// nothing else said.
906    fit: Placement,
907    /// Whether the `design` blocks are read. False for every build but a
908    /// designer's, which is what keeps placeholder rows out of a kiosk.
909    designing: bool,
910    built: Built,
911    focused: Option<NodeId>,
912}
913
914impl<M: Clone + 'static, W: Wiring<M>> Builder<'_, M, W> {
915    fn err(&self, node: &KdlNode, reason: Reason) -> Error {
916        Error::new(self.form.at_node(node), reason)
917    }
918
919    /// Builds one node and everything under it.
920    fn node(
921        &mut self,
922        node: &KdlNode,
923        parent: NodeId,
924        depth: usize,
925        path: &[usize],
926    ) -> Result<(), Error> {
927        if depth >= MAX_DEPTH {
928            return Err(self.err(node, Reason::TooDeep { limit: MAX_DEPTH }));
929        }
930        let kind = node.name().value();
931        if COLLECTIONS.contains(&kind) {
932            // Reaching here means a collection node is somewhere its parent does
933            // not read it — `option` outside a `select`, say — which is a typo
934            // that would otherwise vanish silently.
935            return Err(self.err(
936                node,
937                Reason::UnexpectedChild {
938                    parent: String::from("form"),
939                    found: kind.to_string(),
940                },
941            ));
942        }
943        let info = *denise_ui::widgets::all()
944            .iter()
945            .find(|w| w.kind == kind)
946            .ok_or_else(|| {
947                self.err(
948                    node,
949                    Reason::UnknownWidget {
950                        found: kind.to_string(),
951                    },
952                )
953            })?;
954
955        self.check_properties(node, &info)?;
956        let rect = self.rect(node)?;
957        let id = self.construct(node, &info, parent, rect)?;
958        self.apply_properties(node, &info, id)?;
959        self.apply_node_properties(node, id)?;
960        self.built.placed.push(Placed {
961            id,
962            parent: (depth > 0).then_some(parent),
963            kind: info.kind,
964            name: self.string(node, "name"),
965            path: path.to_vec(),
966        });
967
968        // Children that are not the parent's own content are nodes in their own
969        // right. A widget that cannot lay children out says so.
970        if let Some(children) = node.children() {
971            let owns_children = owns_children(kind);
972            // Which `tab` this is, counted over **every** tab and not only the
973            // ones carrying a page: `selected` is an index into the strip, so a
974            // file part-way through gaining pages must still show the right
975            // one.
976            let mut tabs_seen = 0usize;
977            for (index, child) in children.nodes().iter().enumerate() {
978                let name = child.name().value();
979                if name == DESIGN {
980                    self.check_design(child, kind)?;
981                    continue;
982                }
983                // Placeholder content written where the engine would load it,
984                // which is the shape this format used to have and the one thing
985                // `design` exists to stop.
986                if is_placeholder(kind, name) {
987                    return Err(self.err(
988                        child,
989                        Reason::PlaceholderOutside {
990                            kind: kind.to_string(),
991                            found: name.to_string(),
992                        },
993                    ));
994                }
995                // A `tab` carrying children is a **page**: the labels are the
996                // strip's content, and what is nested under one is a subtree
997                // the file now describes. Everything else in COLLECTIONS is
998                // content and has no children to walk.
999                if name == "tab" && kind == "tabs" {
1000                    let ordinal = tabs_seen;
1001                    tabs_seen += 1;
1002                    if child.children().is_some_and(|b| !b.nodes().is_empty()) {
1003                        let mut below = path.to_vec();
1004                        below.push(index);
1005                        self.page(child, node, id, depth, &below, ordinal)?;
1006                    }
1007                    continue;
1008                }
1009                if COLLECTIONS.contains(&name) {
1010                    continue;
1011                }
1012                if !owns_children {
1013                    return Err(self.err(
1014                        child,
1015                        Reason::UnexpectedChild {
1016                            parent: kind.to_string(),
1017                            found: name.to_string(),
1018                        },
1019                    ));
1020                }
1021                let mut below = path.to_vec();
1022                below.push(index);
1023                self.node(child, id, depth + 1, &below)?;
1024            }
1025        }
1026        Ok(())
1027    }
1028
1029    /// A `design` block holds this widget's placeholder collections and nothing
1030    /// else.
1031    ///
1032    /// Narrow on purpose. `design` is not a general "ignore this" block — a
1033    /// widget hidden in one would be a widget the file describes and the engine
1034    /// never builds, which is a bigger idea than #160 asked for and a worse one
1035    /// to discover by accident.
1036    fn check_design(&self, block: &KdlNode, kind: &str) -> Result<(), Error> {
1037        let Some(children) = block.children() else {
1038            return Ok(());
1039        };
1040        for child in children.nodes() {
1041            let name = child.name().value();
1042            if !is_placeholder(kind, name) {
1043                return Err(self.err(
1044                    child,
1045                    Reason::UnexpectedChild {
1046                        parent: format!("{kind}'s `design`"),
1047                        found: name.to_string(),
1048                    },
1049                ));
1050            }
1051        }
1052        Ok(())
1053    }
1054
1055    /// Every property in the file is one the tree owns or one the widget declares.
1056    fn check_properties(&self, node: &KdlNode, info: &WidgetInfo) -> Result<(), Error> {
1057        for entry in node.entries() {
1058            let Some(name) = entry.name() else {
1059                continue;
1060            };
1061            let name = name.value();
1062            if node_property(name).is_some() || info.property(name).is_some() {
1063                continue;
1064            }
1065            return Err(Error::new(
1066                self.form.at(entry.span().offset()),
1067                Reason::UnknownProperty {
1068                    kind: info.kind,
1069                    found: name.to_string(),
1070                    accepted: info.properties,
1071                },
1072            ));
1073        }
1074        Ok(())
1075    }
1076
1077    fn rect(&self, node: &KdlNode) -> Result<Rect, Error> {
1078        let mut axes = [0i32; 4];
1079        for (slot, name) in axes.iter_mut().zip(["x", "y", "w", "h"]) {
1080            let value = node
1081                .get(name)
1082                .and_then(KdlValue::as_integer)
1083                .ok_or_else(|| {
1084                    self.err(
1085                        node,
1086                        Reason::Missing {
1087                            kind: node.name().value().to_string(),
1088                            name: match name {
1089                                "x" => "x",
1090                                "y" => "y",
1091                                "w" => "w",
1092                                _ => "h",
1093                            },
1094                        },
1095                    )
1096                })?;
1097            *slot = i32::try_from(value).unwrap_or(i32::MAX);
1098        }
1099        // By its edges rather than its width and height: two panels designed to
1100        // touch still touch at a fractional scale. See [`Rect::scaled_by`].
1101        Ok(Rect::new(axes[0], axes[1], axes[2], axes[3]).scaled_by(self.fit.x, self.fit.y))
1102    }
1103
1104    /// The node's single positional argument, as a string.
1105    fn arg(&self, node: &KdlNode) -> Option<String> {
1106        node.entries()
1107            .iter()
1108            .find(|e| e.name().is_none())
1109            .and_then(|e| e.value().as_string())
1110            .map(str::to_string)
1111    }
1112
1113    fn string(&self, node: &KdlNode, name: &str) -> Option<String> {
1114        node.get(name)
1115            .and_then(KdlValue::as_string)
1116            .map(str::to_string)
1117    }
1118
1119    fn number(&self, node: &KdlNode, name: &str) -> Option<f32> {
1120        node.get(name).and_then(|v| {
1121            v.as_float()
1122                .map(|f| f as f32)
1123                .or_else(|| v.as_integer().map(|i| i as f32))
1124        })
1125    }
1126
1127    /// A message the file named, in the shape this widget needs.
1128    fn handler(
1129        &mut self,
1130        node: &KdlNode,
1131        property: &str,
1132        payload: Payload,
1133    ) -> Result<Option<Handler<M>>, Error> {
1134        let Some(name) = self.string(node, property) else {
1135            return Ok(None);
1136        };
1137        match self.wiring.message(&name, payload) {
1138            Some(handler) => Ok(Some(handler)),
1139            None => Err(self.err(node, Reason::UnknownMessage { found: name })),
1140        }
1141    }
1142
1143    fn plain(&self, node: &KdlNode, name: &str, handler: Handler<M>) -> Result<M, Error> {
1144        match handler {
1145            Handler::Plain(message) => Ok(message),
1146            _ => Err(self.wrong(node, name, Payload::None)),
1147        }
1148    }
1149
1150    fn on_bool(
1151        &self,
1152        node: &KdlNode,
1153        name: &str,
1154        handler: Handler<M>,
1155    ) -> Result<fn(bool) -> M, Error> {
1156        match handler {
1157            Handler::Bool(f) => Ok(f),
1158            _ => Err(self.wrong(node, name, Payload::Bool)),
1159        }
1160    }
1161
1162    fn on_index(
1163        &self,
1164        node: &KdlNode,
1165        name: &str,
1166        handler: Handler<M>,
1167    ) -> Result<fn(usize) -> M, Error> {
1168        match handler {
1169            Handler::Index(f) => Ok(f),
1170            _ => Err(self.wrong(node, name, Payload::Index)),
1171        }
1172    }
1173
1174    fn on_number(
1175        &self,
1176        node: &KdlNode,
1177        name: &str,
1178        handler: Handler<M>,
1179    ) -> Result<fn(f32) -> M, Error> {
1180        match handler {
1181            Handler::Number(f) => Ok(f),
1182            _ => Err(self.wrong(node, name, Payload::Number)),
1183        }
1184    }
1185
1186    fn wrong(&self, node: &KdlNode, property: &str, payload: Payload) -> Error {
1187        self.err(
1188            node,
1189            Reason::WrongMessage {
1190                found: self.string(node, property).unwrap_or_default(),
1191                wanted: Handler::<M>::wanted(payload),
1192            },
1193        )
1194    }
1195
1196    fn required(&self, node: &KdlNode, name: &'static str) -> Error {
1197        self.err(
1198            node,
1199            Reason::Missing {
1200                kind: node.name().value().to_string(),
1201                name,
1202            },
1203        )
1204    }
1205
1206    /// The child nodes of one collection kind.
1207    /// The child nodes of `node` called `name`.
1208    ///
1209    /// A placeholder collection lives one level down, in the `design` block,
1210    /// and is read only when the caller asked for it — so an application's
1211    /// build sees no rows at all and a kiosk carries none.
1212    fn collection<'n>(&self, node: &'n KdlNode, name: &str) -> Vec<&'n KdlNode> {
1213        let holder = if is_placeholder(node.name().value(), name) {
1214            if !self.designing {
1215                return Vec::new();
1216            }
1217            let Some(design) = self.design_block(node) else {
1218                return Vec::new();
1219            };
1220            design
1221        } else {
1222            let Some(children) = node.children() else {
1223                return Vec::new();
1224            };
1225            children
1226        };
1227        holder
1228            .nodes()
1229            .iter()
1230            .filter(|n| n.name().value() == name)
1231            .collect()
1232    }
1233
1234    /// Builds one tab's page: a container under the strip, and its subtree.
1235    ///
1236    /// The page fills what is left of the `tabs` node below the strip band, the
1237    /// way a `collapse`'s body fills what is left below its header. So a
1238    /// widget written at `y=0` inside a tab sits just under the strip, and a
1239    /// tab's rectangles are read the same way as any other container's.
1240    ///
1241    /// Only the selected page is visible. `selected` is the tab the
1242    /// *application* starts on; which page a designer is looking at is the
1243    /// designer's business and stays out of the file.
1244    fn page(
1245        &mut self,
1246        tab: &KdlNode,
1247        tabs: &KdlNode,
1248        strip: NodeId,
1249        depth: usize,
1250        path: &[usize],
1251        ordinal: usize,
1252    ) -> Result<(), Error> {
1253        let Some(bounds) = self.ui.bounds(strip) else {
1254            return Ok(());
1255        };
1256        // The band the strip draws in, read from the same place the widget
1257        // reads it: `Tabs::strip_height` is the theme's field height, and the
1258        // theme this tree holds is already in the units these rectangles are
1259        // in. Scaling it again here would put every page at twice the offset
1260        // the strip is drawn at.
1261        let band = self.ui.theme().metrics.size_field.max(1);
1262        let rect = Rect::new(0, band, bounds.width, (bounds.height - band).max(0));
1263        let page = self
1264            .ui
1265            .add(strip, Panel::bare(), rect)
1266            .ok_or_else(|| self.err(tab, Reason::TreeRefused))?;
1267
1268        if let Some(children) = tab.children() {
1269            for (index, child) in children.nodes().iter().enumerate() {
1270                let mut below = path.to_vec();
1271                below.push(index);
1272                self.node(child, page, depth + 1, &below)?;
1273            }
1274        }
1275
1276        // After the children, not before: hiding a node propagates to the
1277        // subtree it has *at the time*, and one hidden first would have its
1278        // pages added back into view behind it.
1279        let selected = tabs
1280            .get("selected")
1281            .and_then(KdlValue::as_integer)
1282            .unwrap_or(0);
1283        let shown = usize::try_from(selected).unwrap_or(0) == ordinal;
1284        self.ui.set_visible(page, shown);
1285        self.built.pages.push(Page {
1286            path: path.to_vec(),
1287            ordinal,
1288            id: page,
1289        });
1290        Ok(())
1291    }
1292
1293    /// Whether any `tab` under `node` carries a page of its own.
1294    fn has_pages(&self, node: &KdlNode) -> bool {
1295        self.collection(node, "tab").into_iter().any(|tab| {
1296            tab.children()
1297                .is_some_and(|block| !block.nodes().is_empty())
1298        })
1299    }
1300
1301    /// The `design` block of `node`, if it wrote one.
1302    fn design_block<'n>(&self, node: &'n KdlNode) -> Option<&'n KdlDocument> {
1303        node.children()?
1304            .nodes()
1305            .iter()
1306            .find(|n| n.name().value() == DESIGN)?
1307            .children()
1308    }
1309
1310    fn strings(&self, node: &KdlNode, name: &str) -> Vec<String> {
1311        self.collection(node, name)
1312            .into_iter()
1313            .map(|n| self.arg(n).unwrap_or_default())
1314            .collect()
1315    }
1316
1317    fn picture(&mut self, node: &KdlNode, path: &str) -> Result<Picture, Error> {
1318        self.wiring.asset(path).ok_or_else(|| {
1319            Error::new(
1320                self.form.at_node(node),
1321                Reason::Asset {
1322                    path: path.to_string(),
1323                },
1324            )
1325        })
1326    }
1327}
1328
1329// The construction match is long because there are twenty-five widgets and no
1330// two constructors are alike. It is deliberately not clever: a table of
1331// constructors would need one type for all of them, and they differ in exactly
1332// the way that would make that a lie.
1333impl<M: Clone + 'static, W: Wiring<M>> Builder<'_, M, W> {
1334    fn construct(
1335        &mut self,
1336        node: &KdlNode,
1337        info: &WidgetInfo,
1338        parent: NodeId,
1339        rect: Rect,
1340    ) -> Result<NodeId, Error> {
1341        let text = self.arg(node).unwrap_or_default();
1342        let id = match info.kind {
1343            "label" => self.ui.add(parent, Label::new(text), rect),
1344            "panel" => self.ui.add(parent, Panel::default(), rect),
1345            "badge" => self.ui.add(parent, Badge::new(text), rect),
1346            "divider" => {
1347                let divider = if self.arg(node).is_some() {
1348                    Divider::labelled(text)
1349                } else {
1350                    Divider::new()
1351                };
1352                self.ui.add(parent, divider, rect)
1353            }
1354            "alert" => {
1355                let role = self
1356                    .string(node, "role")
1357                    .ok_or_else(|| self.required(node, "role"))?;
1358                let role = role_from_name(&role).ok_or_else(|| {
1359                    self.err(
1360                        node,
1361                        Reason::NotAName {
1362                            name: String::from("colour role"),
1363                            found: role.clone(),
1364                            accepted: ROLES,
1365                        },
1366                    )
1367                })?;
1368                self.ui.add(parent, Alert::new(role, text), rect)
1369            }
1370            "spinner" => self.ui.add(parent, Spinner::new(), rect),
1371            "video" => self.ui.add(parent, Video::new(), rect),
1372            "progress" => {
1373                let value = self.number(node, "value").unwrap_or(0.0);
1374                self.ui.add(parent, Progress::new(value), rect)
1375            }
1376            "radial-progress" => {
1377                let value = self.number(node, "value").unwrap_or(0.0);
1378                self.ui.add(parent, RadialProgress::new(value), rect)
1379            }
1380            "button" => {
1381                let button = match self.handler(node, "on-press", Payload::None)? {
1382                    Some(h) => Button::new(text, self.plain(node, "on-press", h)?),
1383                    None => Button::inert(text),
1384                };
1385                self.ui.add(parent, button, rect)
1386            }
1387            "text-input" => {
1388                let mut field = TextInput::<M>::new();
1389                if let Some(h) = self.handler(node, "on-submit", Payload::None)? {
1390                    field = field.with_submit(self.plain(node, "on-submit", h)?);
1391                }
1392                self.ui.add(parent, field, rect)
1393            }
1394            "checkbox" => {
1395                let widget = match self.handler(node, "on-change", Payload::Bool)? {
1396                    Some(h) => Checkbox::new(text, self.on_bool(node, "on-change", h)?),
1397                    None => Checkbox::inert(text),
1398                };
1399                self.ui.add(parent, widget, rect)
1400            }
1401            "toggle" => {
1402                let widget = match self.handler(node, "on-change", Payload::Bool)? {
1403                    Some(h) => Toggle::new(text, self.on_bool(node, "on-change", h)?),
1404                    None => Toggle::inert(text),
1405                };
1406                self.ui.add(parent, widget, rect)
1407            }
1408            "slider" => {
1409                let min = self
1410                    .number(node, "min")
1411                    .ok_or_else(|| self.required(node, "min"))?;
1412                let max = self
1413                    .number(node, "max")
1414                    .ok_or_else(|| self.required(node, "max"))?;
1415                let value = self.number(node, "value").unwrap_or(min);
1416                let widget = match self.handler(node, "on-change", Payload::Number)? {
1417                    Some(h) => Slider::new(min, max, value, self.on_number(node, "on-change", h)?),
1418                    None => Slider::inert(min, max, value),
1419                };
1420                self.ui.add(parent, widget, rect)
1421            }
1422            "rating" => {
1423                let value = self.number(node, "value").unwrap_or(0.0);
1424                let widget = match self.handler(node, "on-change", Payload::Number)? {
1425                    Some(h) => Rating::new(value, self.on_number(node, "on-change", h)?),
1426                    None => Rating::display(value),
1427                };
1428                self.ui.add(parent, widget, rect)
1429            }
1430            "radio-group" => {
1431                let options = self.strings(node, "option");
1432                let widget = match self.handler(node, "on-change", Payload::Index)? {
1433                    Some(h) => RadioGroup::new(options, self.on_index(node, "on-change", h)?),
1434                    None => RadioGroup::inert(options),
1435                };
1436                self.ui.add(parent, widget, rect)
1437            }
1438            "menubar" => {
1439                let titles = self.strings(node, "title");
1440                // Without `on-open` there is nothing to press: the bar reports
1441                // which title was chosen and the application opens the menu, so
1442                // an inert one is titles and nothing else. See `MenuBar::inert`.
1443                let widget = match self.handler(node, "on-open", Payload::Index)? {
1444                    Some(h) => MenuBar::new(titles, self.on_index(node, "on-open", h)?),
1445                    None => MenuBar::inert(titles),
1446                };
1447                self.ui.add(parent, widget, rect)
1448            }
1449            "tabs" => {
1450                let labels = self.strings(node, "tab");
1451                let widget = match self.handler(node, "on-change", Payload::Index)? {
1452                    Some(h) => Tabs::new(labels, self.on_index(node, "on-change", h)?),
1453                    None => Tabs::inert(labels),
1454                };
1455                // A `tab` carrying children makes this a strip *over a page*,
1456                // drawn in a band along the top with the page below it. A file
1457                // whose tabs are bare labels is what a `tabs` node has always
1458                // been, and is untouched. See `Tabs::over_pages`.
1459                let widget = if self.has_pages(node) {
1460                    widget.over_pages()
1461                } else {
1462                    widget
1463                };
1464                self.ui.add(parent, widget, rect)
1465            }
1466            "select" => {
1467                let options = self.strings(node, "option");
1468                // Without `on-change` the list cannot be opened — the popup is a
1469                // scene the application pushes — so an inert one shows what is
1470                // chosen and stays shut. See `Select::inert`.
1471                let widget = match self.handler(node, "on-change", Payload::None)? {
1472                    Some(h) => Select::new(options, self.plain(node, "on-change", h)?),
1473                    None => Select::inert(options),
1474                };
1475                self.ui.add(parent, widget, rect)
1476            }
1477            "collapse" => {
1478                // An inert one folds itself, so a decorative section needs no
1479                // message. See `Collapse::inert`.
1480                let widget = match self.handler(node, "on-toggle", Payload::Bool)? {
1481                    Some(h) => Collapse::new(text, self.on_bool(node, "on-toggle", h)?),
1482                    None => Collapse::inert(text),
1483                };
1484                self.ui.add(parent, widget, rect)
1485            }
1486            "list" => {
1487                let items: Vec<ListItem> = self
1488                    .collection(node, "item")
1489                    .into_iter()
1490                    .map(|n| {
1491                        let mut item = ListItem::new(self.arg(n).unwrap_or_default());
1492                        if let Some(leading) = self.string(n, "leading") {
1493                            item = item.with_leading(leading);
1494                        }
1495                        if let Some(trailing) = self.string(n, "trailing") {
1496                            item = item.with_trailing(trailing);
1497                        }
1498                        if n.get("enabled").and_then(KdlValue::as_bool) == Some(false) {
1499                            item = item.disabled();
1500                        }
1501                        item
1502                    })
1503                    .collect();
1504                let mut widget = match self.handler(node, "on-select", Payload::Index)? {
1505                    Some(h) => List::new(items, self.on_index(node, "on-select", h)?),
1506                    None => List::inert(items),
1507                };
1508                if let Some(h) = self.handler(node, "on-activate", Payload::Index)? {
1509                    widget = widget.on_activate(self.on_index(node, "on-activate", h)?);
1510                }
1511                self.ui.add(parent, widget, rect)
1512            }
1513            "tree" => {
1514                let items: Vec<TreeItem> = self
1515                    .collection(node, "item")
1516                    .into_iter()
1517                    .map(|n| {
1518                        let mut item = TreeItem::new(self.arg(n).unwrap_or_default());
1519                        if let Some(depth) = n.get("depth").and_then(KdlValue::as_integer) {
1520                            item = item.at_depth(depth.clamp(0, i128::from(u16::MAX)) as u16);
1521                        }
1522                        if n.get("open").and_then(KdlValue::as_bool) == Some(false) {
1523                            item = item.shut();
1524                        }
1525                        if let Some(leading) = self.string(n, "leading") {
1526                            item = item.with_leading(leading);
1527                        }
1528                        if let Some(trailing) = self.string(n, "trailing") {
1529                            item = item.with_trailing(trailing);
1530                        }
1531                        if n.get("enabled").and_then(KdlValue::as_bool) == Some(false) {
1532                            item = item.disabled();
1533                        }
1534                        item
1535                    })
1536                    .collect();
1537                let mut widget = match self.handler(node, "on-select", Payload::Index)? {
1538                    Some(h) => Tree::new(items, self.on_index(node, "on-select", h)?),
1539                    None => Tree::inert(items),
1540                };
1541                if let Some(h) = self.handler(node, "on-activate", Payload::Index)? {
1542                    widget = widget.on_activate(self.on_index(node, "on-activate", h)?);
1543                }
1544                if let Some(h) = self.handler(node, "on-toggle", Payload::Index)? {
1545                    widget = widget.on_toggle(self.on_index(node, "on-toggle", h)?);
1546                }
1547                self.ui.add(parent, widget, rect)
1548            }
1549            "table" => {
1550                let columns: Vec<Column> = self
1551                    .collection(node, "column")
1552                    .into_iter()
1553                    .map(|n| {
1554                        let title = self.arg(n).unwrap_or_default();
1555                        let mut column = match n.get("width").and_then(KdlValue::as_integer) {
1556                            Some(width) => Column::new(title, width as i32),
1557                            None => Column::flex(title),
1558                        };
1559                        match n.get("align").and_then(KdlValue::as_string) {
1560                            Some("end") => column = column.align_end(),
1561                            Some("center") => column = column.align_center(),
1562                            _ => {}
1563                        }
1564                        column
1565                    })
1566                    .collect();
1567                let rows: Vec<Vec<String>> = self
1568                    .collection(node, "row")
1569                    .into_iter()
1570                    .map(|n| {
1571                        n.entries()
1572                            .iter()
1573                            .filter(|e| e.name().is_none())
1574                            .map(|e| e.value().as_string().unwrap_or_default().to_string())
1575                            .collect()
1576                    })
1577                    .collect();
1578                let mut widget = match self.handler(node, "on-select", Payload::Index)? {
1579                    Some(h) => Table::new(columns, self.on_index(node, "on-select", h)?),
1580                    None => Table::inert(columns),
1581                };
1582                widget = widget.with_rows(rows);
1583                if let Some(h) = self.handler(node, "on-activate", Payload::Index)? {
1584                    widget = widget.on_activate(self.on_index(node, "on-activate", h)?);
1585                }
1586                self.ui.add(parent, widget, rect)
1587            }
1588            "timeline" => {
1589                let events: Vec<TimelineItem> = self
1590                    .collection(node, "event")
1591                    .into_iter()
1592                    .map(|n| {
1593                        let mut item = TimelineItem::new(self.arg(n).unwrap_or_default());
1594                        if let Some(time) = self.string(n, "time") {
1595                            item = item.with_time(time);
1596                        }
1597                        if let Some(role) =
1598                            self.string(n, "role").as_deref().and_then(role_from_name)
1599                        {
1600                            item = item.with_role(role);
1601                        }
1602                        if n.get("pending").and_then(KdlValue::as_bool) == Some(true) {
1603                            item = item.pending();
1604                        }
1605                        item
1606                    })
1607                    .collect();
1608                self.ui.add(parent, Timeline::new(events), rect)
1609            }
1610            "image" => {
1611                let path = self
1612                    .string(node, "src")
1613                    .ok_or_else(|| self.required(node, "src"))?;
1614                let picture = self.picture(node, &path)?;
1615                self.ui
1616                    .add(parent, Image::new(picture.pixels, picture.size), rect)
1617            }
1618            "avatar" => {
1619                let avatar = match self.string(node, "src") {
1620                    Some(path) => {
1621                        let picture = self.picture(node, &path)?;
1622                        Avatar::new(picture.pixels, picture.size)
1623                    }
1624                    None => {
1625                        Avatar::initials(self.string(node, "initials").unwrap_or_default().as_str())
1626                    }
1627                };
1628                self.ui.add(parent, avatar, rect)
1629            }
1630            "carousel" => {
1631                let mut widget = match self.handler(node, "on-change", Payload::Index)? {
1632                    Some(h) => Carousel::new(self.on_index(node, "on-change", h)?),
1633                    None => Carousel::inert(),
1634                };
1635                for picture_node in self.collection(node, "picture") {
1636                    let path = self
1637                        .string(picture_node, "src")
1638                        .ok_or_else(|| self.required(picture_node, "src"))?;
1639                    let picture = self.picture(picture_node, &path)?;
1640                    let fit = match self.string(picture_node, "fit").as_deref() {
1641                        Some("fill") => Fit::Fill,
1642                        Some("cover") => Fit::Cover,
1643                        Some("center") => Fit::Center,
1644                        _ => Fit::Contain,
1645                    };
1646                    widget = widget.with_picture_fit(picture.pixels, picture.size, fit);
1647                }
1648                self.ui.add(parent, widget, rect)
1649            }
1650            other => {
1651                // `all()` matched a kind this match does not, which means a
1652                // widget joined the catalogue and not the builder.
1653                return Err(self.err(
1654                    node,
1655                    Reason::UnknownWidget {
1656                        found: other.to_string(),
1657                    },
1658                ));
1659            }
1660        };
1661        id.ok_or_else(|| self.err(node, Reason::TreeRefused))
1662    }
1663
1664    /// Applies every widget property the file gives, **in descriptor order**.
1665    ///
1666    /// Descriptor order rather than file order, and deliberately: a slider's
1667    /// `value` is clamped into its `min`/`max`, so a file that wrote them the
1668    /// other way round would otherwise land somewhere else than one that did not.
1669    /// The widget publishes the order it wants to be told things in, and this
1670    /// obeys it — so two files that say the same thing build the same tree.
1671    fn apply_properties(
1672        &mut self,
1673        node: &KdlNode,
1674        info: &WidgetInfo,
1675        id: NodeId,
1676    ) -> Result<(), Error> {
1677        for property in info.properties {
1678            if !property.is_settable() {
1679                // A message or an asset; both were given to the constructor.
1680                continue;
1681            }
1682            let Some(entry) = node
1683                .entries()
1684                .iter()
1685                .find(|e| e.name().map(kdl::KdlIdentifier::value) == Some(property.name))
1686            else {
1687                continue;
1688            };
1689            let at = self.form.at(entry.span().offset());
1690            let mut value = self.convert(at, info.kind, property, entry.value())?;
1691            if property.pixels {
1692                value = self.lengthened(value);
1693            }
1694            if let Some(Err(error)) = self.ui.set_property(id, property.name, value) {
1695                return Err(Error::new(
1696                    at,
1697                    Reason::WrongType {
1698                        kind: info.kind,
1699                        name: property.name.to_string(),
1700                        wanted: match error.mismatch {
1701                            denise_ui::widgets::Mismatch::WrongType { expected } => expected.noun(),
1702                            _ => "something else",
1703                        },
1704                    },
1705                ));
1706            }
1707        }
1708        Ok(())
1709    }
1710
1711    /// One length, at the scale this form is being built at.
1712    ///
1713    /// Which numbers are lengths is the **widget's** to say, not this crate's:
1714    /// see [`Property::pixels`](denise_ui::widgets::Property::pixels). A text
1715    /// size is a length and doubles at 2x; a duration in milliseconds is not and
1716    /// does not; a selected index is not and would be nonsense if it did.
1717    ///
1718    /// [`Placement::uniform`] rather than the axis factors, because none of these is
1719    /// horizontal or vertical: a text size is a size, and a border is as thick
1720    /// on the top as on the left.
1721    fn lengthened(&self, value: Value) -> Value {
1722        let scale = self.fit.uniform();
1723        match value {
1724            // At least one: a border that rounded to nothing at 0.75x has been
1725            // deleted rather than scaled, and the same for a one-pixel divider.
1726            // Zero stays zero, because zero was somebody saying "none".
1727            Value::Int(n) if n != 0 => {
1728                let scaled = (n as f32 * scale + 0.5) as i32;
1729                Value::Int(if n > 0 { scaled.max(1) } else { scaled.min(-1) })
1730            }
1731            Value::Float(f) => Value::Float(f * scale),
1732            other => other,
1733        }
1734    }
1735
1736    /// A value from the file, in the shape the property takes.
1737    fn convert(
1738        &self,
1739        at: At,
1740        kind: &'static str,
1741        property: &Property,
1742        value: &KdlValue,
1743    ) -> Result<Value, Error> {
1744        let wrong = |wanted: &'static str| {
1745            Error::new(
1746                at,
1747                Reason::WrongType {
1748                    kind,
1749                    name: property.name.to_string(),
1750                    wanted,
1751                },
1752            )
1753        };
1754        Ok(match property.kind {
1755            PropertyKind::Text | PropertyKind::Color => {
1756                Value::text(value.as_string().ok_or_else(|| wrong("a string"))?)
1757            }
1758            PropertyKind::Bool => {
1759                Value::Bool(value.as_bool().ok_or_else(|| wrong("true or false"))?)
1760            }
1761            PropertyKind::Int { .. } => {
1762                let number = value.as_integer().ok_or_else(|| wrong("a whole number"))?;
1763                Value::Int(i32::try_from(number).map_err(|_| wrong("a whole number"))?)
1764            }
1765            PropertyKind::Float { .. } => {
1766                let number = value
1767                    .as_float()
1768                    .map(|f| f as f32)
1769                    .or_else(|| value.as_integer().map(|i| i as f32))
1770                    .ok_or_else(|| wrong("a number"))?;
1771                Value::Float(number)
1772            }
1773            PropertyKind::Enum(names) => {
1774                let found = value
1775                    .as_string()
1776                    .ok_or_else(|| wrong("one of the listed names"))?;
1777                let name = names.iter().copied().find(|n| *n == found).ok_or_else(|| {
1778                    Error::new(
1779                        at,
1780                        Reason::NotAName {
1781                            name: property.name.to_string(),
1782                            found: found.to_string(),
1783                            accepted: names,
1784                        },
1785                    )
1786                })?;
1787                Value::Enum(name)
1788            }
1789            // Filtered out by `is_settable` before this is reached.
1790            PropertyKind::Message(_) | PropertyKind::Asset => return Err(wrong("nothing here")),
1791            _ => return Err(wrong("a value this crate does not know")),
1792        })
1793    }
1794
1795    /// The properties the tree owns.
1796    fn apply_node_properties(&mut self, node: &KdlNode, id: NodeId) -> Result<(), Error> {
1797        if let Some(name) = self.string(node, "name") {
1798            if self.built.names.contains_key(&name) {
1799                return Err(self.err(node, Reason::DuplicateName { name }));
1800            }
1801            self.built.names.insert(name, id);
1802        }
1803        if let Some(text) = self.string(node, "tooltip") {
1804            self.ui.set_tooltip(id, text);
1805        }
1806        if let Some(z) = node.get("z").and_then(KdlValue::as_integer) {
1807            self.ui.set_z(id, z as i32);
1808        }
1809        if node.get("scroll").and_then(KdlValue::as_bool) == Some(true) {
1810            self.ui.set_scrollable(id, true);
1811        }
1812        if let Some(spacing) = node.get("stack").and_then(KdlValue::as_integer) {
1813            self.ui.set_stack(id, spacing as i32);
1814        }
1815        if let Some(anchor) = self.string(node, "anchor") {
1816            let mut anchors = Anchors::new(false, false, false, false);
1817            for edge in anchor.split_whitespace() {
1818                match edge {
1819                    "left" => anchors.left = true,
1820                    "top" => anchors.top = true,
1821                    "right" => anchors.right = true,
1822                    "bottom" => anchors.bottom = true,
1823                    other => {
1824                        return Err(self.err(
1825                            node,
1826                            Reason::NotAName {
1827                                name: String::from("anchor edge"),
1828                                found: other.to_string(),
1829                                accepted: ANCHOR_EDGES,
1830                            },
1831                        ));
1832                    }
1833                }
1834            }
1835            self.ui.set_anchors(id, anchors);
1836        }
1837        if let Some(dock) = self.string(node, "dock") {
1838            let side = match dock.as_str() {
1839                "top" => Dock::Top,
1840                "bottom" => Dock::Bottom,
1841                "left" => Dock::Left,
1842                "right" => Dock::Right,
1843                "fill" => Dock::Fill,
1844                other => {
1845                    return Err(self.err(
1846                        node,
1847                        Reason::NotAName {
1848                            name: String::from("dock side"),
1849                            found: other.to_string(),
1850                            accepted: DOCK_SIDES,
1851                        },
1852                    ));
1853                }
1854            };
1855            self.ui.set_dock(id, Some(side));
1856        }
1857        if node.get("enabled").and_then(KdlValue::as_bool) == Some(false) {
1858            self.ui.set_enabled(id, false);
1859        }
1860        if node.get("focus").and_then(KdlValue::as_bool) == Some(true) {
1861            if self.focused.is_some() {
1862                return Err(self.err(node, Reason::TwoFocuses));
1863            }
1864            self.focused = Some(id);
1865        }
1866        // Last: a hidden node's children still had to be built and placed, and
1867        // hiding it first would have them laid out against a node with no bounds.
1868        if node.get("visible").and_then(KdlValue::as_bool) == Some(false) {
1869            self.ui.set_visible(id, false);
1870        }
1871        Ok(())
1872    }
1873}
1874
1875// Unused-import guard: these name tables are the ones the schema documents, and
1876// referencing them here keeps a rename in `denise-ui` from silently drifting.
1877const _: &[&[&str]] = &[ALIGNMENTS, FITS, ORIENTATIONS, PRESENCES, RADII];