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