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];