//! The property registry: which widgets exist, which properties each //! accepts, and what each property *is* — static layout style, a settable //! widget property, an event wire, or a query. //! //! # One descriptor, keyed by name //! //! Every widget — a framework builtin or one an application wrote in Rust and //! declared in a `.gdw` manifest ([`crate::manifest`]) — is one //! [`WidgetDescriptor`], resolved by name. There is no privileged description: //! the builtins are parsed from an embedded `builtins.gdw` in the very same //! grammar an application uses, so a `container` and a `markdown` reach the //! compiler as the same kind of thing. The [`Cow`] fields let a parsed //! descriptor's owned strings and the universal tables' borrowed ones share a //! type, and every view — [`WidgetDescriptor::lookup`], //! [`WidgetDescriptor::theme_value_type`], [`WidgetDescriptor::known_props`] — //! reads a `&WidgetDescriptor` without caring where it came from. A widget is //! *just a name* to the rest of the compiler; //! the framework's own compile-time rules (where a widget may appear) are //! descriptor data, applicable to a builtin and an application's widget alike. //! Whether a widget defers its content is not among them — that is a runtime //! property of the widget, decided by its `Widget::defers_content`. //! //! This is the compiler's half of the property-kind table; the widget setters //! in guiduck-core are the runtime half (each setter marks its own dirt class). //! What every widget shares — the universal layout properties, the pointer //! events, `:class`/`:tooltip`/`:enabled` — lives in the global tables below and //! is unioned in by [`WidgetDescriptor::lookup`] for every widget. use std::borrow::Cow; use std::sync::LazyLock; use crate::ast::TypeKind; use crate::diagnostics::Diagnostic; use crate::sexpr::Span; /// Layout (taffy) properties. Static: reactive layout comes from the style /// engine, not from bindings. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum LayoutProp { Width, Height, Padding, Gap, Direction, AlignItems, JustifyContent, AlignSelf, Grow, Shrink, Basis, } /// Event wires the framework defines. A builtin's own event and every widget's /// universal pointer events name one of these; both back ends map it to the /// runtime `EventKind`, so this closed set is genuinely load-bearing. A /// manifest's event is not one of these — it dispatches as `EventKind::User` /// (see [`EventRef`]). #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum EventProp { Click, /// `:on-counted-click` — the settled outcome of a click burst, one /// dispatch once the ≤200ms window closes, carrying the final count in /// `event.click-count`. Unlike `:on-click` it never fires on the first /// press of a double, so a handler can act on a confirmed single or a /// double: `(edit event.click-count)`. CountedClick, PointerEnter, PointerLeave, PointerDown, PointerUp, /// `:on-change` — the widget's value changed through user input. The /// wired handler receives the new value, so it must be declared with a /// payload: `(handler edited String)`. Changed, /// `:on-close` — the user asked to dismiss (Escape). Carries no payload; /// it is a *request*, and the app answers it by clearing `:open`, which /// is what keeps the binding the single source of truth. Close, /// `:on-toggle` — a toggle control flipped through user input. The wired /// handler receives the new checked state: `(handler set-flag bool)`. Toggled, /// `:on-select` — a menu item was chosen, by pointer, by Enter on the /// highlighted row, or by its accelerator. Carries no payload: what was /// chosen is the wire's own arguments (`:on-select (open f.id)`). Select, /// `:on-link` — a `(link …)` span inside a paragraph was clicked. The /// wired handler receives the span's target: `(handler navigate String)`. /// /// A span rather than a widget, because wrapping a link across a line /// break needs the link and the prose around it to be *one* parley layout; /// a link that were its own widget would break the line at its own edges /// instead of between words. Link, /// `:on-file-drop` — a file was dropped on the window. The wired handler /// receives its path: `(handler import String)`. One dispatch per file; /// the framework carries the path and the app interprets it. FileDrop, /// `:on-focus-change` — this widget gained or lost focus. The wired /// handler receives the new state: `(handler editing-changed bool)`. /// /// The report half of `:focused`. It fires for every transition whatever /// caused it — a click, Tab, the focus invariant, a disabled widget being /// evicted — which is what lets an application drive `:focused` from its /// own state and still keep that state true when the user moves focus /// some other way. FocusChange, /// `:on-key` — a key the focused widget did not consume, offered to its /// own wire and then to its ancestors'. Carries no payload: which key it /// was is `event.key`, so the wire reads /// `:on-key (maybe-commit event.key)`. /// /// It sits *after* the focused widget's own handling, so a text input's /// Ctrl+C is still the text input's, and *before* overlay dismissal and /// the accelerator table, because a wire written on the focused widget is /// more specific than either. A handler claims the key with /// `cx.consume()`. Key, /// `:on-value-change` — a control whose value is a number moved. The /// wired handler receives the new value: `(handler set-volume f64)`. /// /// Distinct from `:on-change`, which carries a string: a slider and a text /// field both "change", and conflating them would make one wire mean two /// payload types. ValueChanged, /// `:on-scroll` — a scroll container's position moved because the reader /// moved it. It carries no payload: the new position is two numbers, and /// the wire reads them by name (`event.offset-x`, `event.offset-y`) rather /// than pretending a scroll is one value. Scrolled, } impl EventProp { /// The payload type the event delivers to its handler, if any. This is /// the compiler's half of the contract; the runtime half is the /// `EventData` variant the widget emits. /// /// Both back ends key their payload plumbing on this rather than on which /// event it is, so an event that delivers a `String` needs no arm of its /// own in either. pub fn payload(self) -> Option { match self { Self::Changed | Self::Link | Self::FileDrop => Some(PayloadKind::String), Self::Toggled | Self::FocusChange => Some(PayloadKind::Bool), Self::ValueChanged => Some(PayloadKind::Number), _ => None, } } /// The framework per-widget event a `.gdw`'s `(event NAME :builtin)` names, /// or `None` if the name is not one. The universal pointer events /// (`on-click`, `on-pointer-…`, `on-file-drop`) are not here: every widget /// has them already through [`COMMON_EVENT_DECLS`], so a widget's own /// `:builtin` events are the semantic ones only it emits. pub fn from_widget_event(name: &str) -> Option { Some(match name { "on-change" => Self::Changed, "on-close" => Self::Close, "on-toggle" => Self::Toggled, "on-select" => Self::Select, "on-link" => Self::Link, "on-value-change" => Self::ValueChanged, "on-scroll" => Self::Scrolled, _ => return None, }) } } /// Payload types events can deliver (a subset of the `.gdc` type set). #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum PayloadKind { String, Bool, /// An `f64`. A handler may declare `f32` or `f64`; the narrowing happens /// where the argument is re-typed, as for every other numeric. Number, } /// The value type a themable visual property expects. Themes carry a widget's /// *appearance tokens* — brushes, metrics, vector marks — which the widget's /// paint code reads from computed style and places. This is a superset of the /// settable widget-property set: it also covers paint-only tokens that only a /// theme sets (focus rings, the checkbox mark), which is why theming resolves /// against this table rather than the property registry. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum ThemeValueType { /// A color/brush. Brush, /// A scalar metric (width, radius, size). Number, /// A string (a font family). Str, /// Something to draw into a rectangle — `(path …)` / `(svg-path …)` /// geometry, or an `(asset "…")` file's pixels, interchangeably. Marks /// like the checkbox check take one. Graphic, } /// The type of a value a widget property carries — which is to say, what the /// Rust setter for it takes. /// /// This is the vocabulary a `.gdc` can put into a widget, and it is one /// vocabulary: a `.gdw` manifest declares from it exactly as a builtin /// descriptor does. The non-scalar members are the framework's own value /// types, which is why an application's widget can take one — a `markdown` /// with a `(prop icon Graphic)` is not a special case, it is the same case. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum PropTy { /// One of the `.gdc` scalar types: `i32`, `i64`, `f32`, `f64`, `bool`, /// `String`. Scalar(TypeKind), /// A color/brush, from a `"#rrggbb"` literal or an `if` choosing between /// them. Brush, /// Something to draw: an `(asset "…")` file's pixels or `(path …)` /// geometry, interchangeably. Graphic, /// A paragraph's content: a string, or a `(rich …)` with styled runs. /// Plain text is the case with no runs, so one setter takes both. RichText, /// Which axes a scroll area scrolls. ScrollAxes, } impl PropTy { /// The type name a `.gdw` writes for this, if a manifest may declare it. pub fn manifest_name(self) -> Option<&'static str> { Some(match self { Self::Scalar(kind) => kind.rust_name(), Self::Brush => "Brush", Self::Graphic => "Graphic", Self::RichText => "RichText", Self::ScrollAxes => "ScrollAxes", }) } /// Whether the property accepts a reactive expression. /// /// A graphic does not: its bytes are found and embedded at build time, so /// there is no path a runtime value could name. An axis does not: it is /// read at insert, to shape the layout node itself. pub fn bindable(self) -> bool { !matches!(self, Self::Graphic | Self::ScrollAxes) } } /// One event wire a widget offers. #[derive(Clone, Debug, PartialEq, Eq)] pub enum EventRef { /// A framework event, mapped to the runtime `EventKind` by both back ends. Builtin(EventProp), /// A manifest event: dispatched as `EventKind::User`, carrying the payload /// the manifest declared (if any). User { payload: Option }, } /// What one of a widget's own properties *is*: how a `.gdc` value written for /// it is checked, lowered, and routed to the Rust type. /// /// Builtins and manifests both declare from this one vocabulary — the whole /// point of a single descriptor. The scroll axis is an ordinary /// [`Setter`](Self::Setter) of [`PropTy::ScrollAxes`], so `(prop axis /// ScrollAxes)` declares it like any other; only [`Self::Accel`] is special (it /// registers a keystroke as well as setting a string). #[derive(Clone, Debug, PartialEq, Eq)] pub enum PropDeclKind { /// A universal layout property. Only the global [`COMMON_LAYOUT_DECLS`] /// table produces this; a widget's own props never carry it. Layout(LayoutProp), /// A settable property: call `method` with a value of `ty`. A static value /// and a binding both flow through it, dispatched on `ty` — one lowering /// for a builtin's `:background` and a manifest's `(prop icon Graphic)` /// alike. Setter { method: Cow<'static, str>, ty: PropTy, }, /// An event wire. Event(EventRef), /// A query wire: the handler *returns* a value the widget installs as a /// resolver, through `setter`. `:get-image` on the markdown widget is what /// this exists for. Query { setter: Cow<'static, str>, payload: TypeKind, returns: Cow<'static, str>, }, /// `:autofocus true` — the widget takes initial focus at mount. At most /// one per component; only on focusable widgets. Autofocus, /// `:accel "Ctrl+S"` — a keyboard accelerator for a menu item. Its string /// half is set through `method`; its keystroke half registers in the /// shortcut table at mount. Static. Accel { method: Cow<'static, str> }, } /// One property a widget accepts, beyond the universal ones. /// /// The [`Self::name`] is the keyword a `.gdc` writes (`corner-radius` → /// `:corner-radius`), and the key both [`WidgetDescriptor::lookup`] and the /// interpreter's factory table use. #[derive(Clone, Debug, PartialEq, Eq)] pub struct PropDecl { pub name: Cow<'static, str>, pub kind: PropDeclKind, } impl PropDecl { /// The Rust setter this property drives and the type it takes, if it has /// one. Events, queries, and the autofocus marker do not: they are wired, /// not set. fn setter(&self) -> Option<(&str, &str, PropTy)> { match &self.kind { PropDeclKind::Setter { method, ty } => Some((&self.name, method, *ty)), PropDeclKind::Accel { method } => { Some((&self.name, method, PropTy::Scalar(TypeKind::String))) } PropDeclKind::Layout(_) | PropDeclKind::Event(_) | PropDeclKind::Query { .. } | PropDeclKind::Autofocus => None, } } } /// A property a widget takes that no file writes, because its value is a fact /// about the file's *structure* that only the compiler knows. It is an ordinary /// setter — that is the point of naming it here rather than special-casing a /// method name in each back end — and it never appears in the `.gdc` property /// vocabulary. /// /// A `menu` is a bar title or a submenu row depending on what encloses it. pub const SUBMENU_PROP: &str = "submenu"; pub const SUBMENU_METHOD: &str = "set_submenu"; /// The widget whose title/submenu duality is a framework rule: a `menu`. const MENU: &str = "menu"; /// A named group of themable tokens. A descriptor holds a list of groups so a /// builtin's shared appearance bundle could compose with its own; a parsed /// manifest is one group, the tokens it lists. type ThemeToken = (Cow<'static, str>, ThemeValueType); type TokenGroup = Cow<'static, [ThemeToken]>; /// Everything the registry knows about one widget — a builtin parsed from the /// embedded manifest, or an application's, parsed from its own. Both own their /// strings; the [`Cow`] fields share a type with the borrowed universal tables. /// The descriptor holds only what is *its own*: the properties and tokens /// common to every widget live in the global tables below and are never copied /// in here. /// /// A widget is identified by [`Self::name`] alone — there is no builtin handle. /// The framework's own rules ([`Self::required_parents`], [`Self::is_writable`]) /// are fields here, so an application's widget answers them the same way a /// builtin does (with the defaults an app widget has: writable, no placement /// rule). Whether a widget defers its content is *not* here — it is the /// widget's own runtime answer (the `Widget::defers_content` trait method), /// invisible to a manifest. #[derive(Clone, Debug, PartialEq, Eq)] pub struct WidgetDescriptor { /// The name a `.gdc` or a theme selector writes. pub name: Cow<'static, str>, /// Where a manifest declared the name, for the collision diagnostics a /// [`Registry`] raises. `None` for a builtin — it has no source span. pub name_span: Option, /// The Rust path codegen constructs. **Never resolved by this compiler** — /// a proc-macro cannot see another crate's items — so it is carried /// verbatim and rustc reports a bad path against the generated /// construction. pub type_path: Cow<'static, str>, /// The widget names this one may appear directly inside; empty for /// anywhere. A builtin's placement rule and a `.gdw`'s `(parents …)` produce /// this identically — the [`Cow`] borrows for a builtin and owns for a /// manifest. The names are checked against the real vocabulary by /// [`Registry::default`]'s construction test, which recovers the typo-safety /// a closed enum would have given. pub required_parents: Cow<'static, [Cow<'static, str>]>, /// Whether a `.gdc` may write this widget. A `menu-panel` exists so a /// theme can name the popup a menu builds; nobody writes one. pub is_writable: bool, /// Properties this widget accepts beyond the universal ones — the `.gdc` /// vocabulary, and (for its settable members) how to drive the Rust type. pub props: Cow<'static, [PropDecl]>, /// The token groups a theme may set on this widget, searched in order. pub theme_tokens: Cow<'static, [TokenGroup]>, } impl WidgetDescriptor { /// Resolve a property name against this widget: the universal layout /// properties, the universal pointer events, then its own props — the same /// search for a builtin and a manifest widget alike. /// /// `:class`/`:tooltip`/`:enabled` are not here: `validate` lowers them /// before it consults the registry, because they are structural rather /// than per-widget. `:autofocus` *is* here, in [`COMMON_UNIVERSAL_DECLS`]: /// any widget accepts it, and it no-ops on one that is not focusable (the /// tree's `set_focus` filters a non-focusable target), so it is a universal /// capability rather than a per-control property. pub fn lookup(&self, prop: &str) -> Option<&PropDecl> { COMMON_LAYOUT_DECLS .iter() .chain(COMMON_EVENT_DECLS.iter()) .chain(COMMON_UNIVERSAL_DECLS.iter()) .chain(self.props.iter()) .find(|decl| decl.name == prop) } /// The type of a themable visual property on this widget, or `None` if it /// has no such token. pub fn theme_value_type(&self, name: &str) -> Option { self.theme_tokens() .find(|(token, _)| *token == name) .map(|(_, ty)| ty) } /// Every themable token, flattened across the groups. pub fn theme_tokens(&self) -> impl Iterator { self.theme_tokens .iter() .flat_map(|group| group.iter()) .map(|(name, ty)| (name.as_ref(), *ty)) } /// Property names accepted by this widget, for "did you mean" diagnostics — /// derived from the same tables [`Self::lookup`] consults, so the list is /// exactly what the widget takes. pub fn known_props(&self) -> Vec<&str> { COMMON_LAYOUT_DECLS .iter() .map(|decl| decl.name.as_ref()) .chain(self.props.iter().map(|decl| decl.name.as_ref())) .chain(COMMON_EVENT_DECLS.iter().map(|decl| decl.name.as_ref())) .chain(COMMON_UNIVERSAL_DECLS.iter().map(|decl| decl.name.as_ref())) .chain(UNIVERSAL_PROPS.iter().copied()) .collect() } /// How to build this widget and set each of its properties: the Rust type /// path, then a `(prop, method, ty)` for every settable property — its own /// setters plus a menu's framework-supplied submenu flag, which no file /// writes but the tree drives all the same. (Deferred content is not a /// setter here: the tree hands every widget its content builder through /// [`Widget::set_content`], and the widget decides at runtime.) /// /// This is the whole of what the interpreter's factory table needs, and it /// is one description for a builtin and a `markdown` alike — so there is no /// second account of how a `container` is built to drift from this one. pub fn setters(&self) -> Vec<(&str, &str, PropTy)> { let mut setters: Vec<(&str, &str, PropTy)> = self.props.iter().filter_map(PropDecl::setter).collect(); if self.name == MENU { setters.push((SUBMENU_PROP, SUBMENU_METHOD, PropTy::Scalar(TypeKind::Bool))); } setters } } /// A builtin event declaration. const fn event(name: &'static str, event: EventProp) -> PropDecl { PropDecl { name: Cow::Borrowed(name), kind: PropDeclKind::Event(EventRef::Builtin(event)), } } /// The `:autofocus` marker. const AUTOFOCUS: PropDecl = PropDecl { name: Cow::Borrowed("autofocus"), kind: PropDeclKind::Autofocus, }; /// Properties every widget accepts through [`WidgetDescriptor::lookup`] but that /// are not layout or events. `:autofocus` is the one: a widget takes initial /// focus if it is focusable, and the request is silently ignored if it is not, /// so there is no reason to gate it per-widget — a control declares nothing to /// be autofocusable, and an application widget that is focusable gets it for /// free. const COMMON_UNIVERSAL_DECLS: &[PropDecl] = &[AUTOFOCUS]; /// Layout properties every widget accepts. const COMMON_LAYOUT_DECLS: &[PropDecl] = &[ PropDecl { name: Cow::Borrowed("width"), kind: PropDeclKind::Layout(LayoutProp::Width), }, PropDecl { name: Cow::Borrowed("height"), kind: PropDeclKind::Layout(LayoutProp::Height), }, PropDecl { name: Cow::Borrowed("padding"), kind: PropDeclKind::Layout(LayoutProp::Padding), }, PropDecl { name: Cow::Borrowed("gap"), kind: PropDeclKind::Layout(LayoutProp::Gap), }, PropDecl { name: Cow::Borrowed("direction"), kind: PropDeclKind::Layout(LayoutProp::Direction), }, PropDecl { name: Cow::Borrowed("align-items"), kind: PropDeclKind::Layout(LayoutProp::AlignItems), }, PropDecl { name: Cow::Borrowed("justify-content"), kind: PropDeclKind::Layout(LayoutProp::JustifyContent), }, PropDecl { name: Cow::Borrowed("align-self"), kind: PropDeclKind::Layout(LayoutProp::AlignSelf), }, PropDecl { name: Cow::Borrowed("grow"), kind: PropDeclKind::Layout(LayoutProp::Grow), }, PropDecl { name: Cow::Borrowed("shrink"), kind: PropDeclKind::Layout(LayoutProp::Shrink), }, PropDecl { name: Cow::Borrowed("basis"), kind: PropDeclKind::Layout(LayoutProp::Basis), }, ]; /// Events every widget accepts: the pointer family, plus the two the *tree* /// reports rather than any widget — focus and unconsumed keys, which are /// facts about a widget's place in the tree rather than about what it is. const COMMON_EVENT_DECLS: &[PropDecl] = &[ event("on-click", EventProp::Click), event("on-counted-click", EventProp::CountedClick), event("on-file-drop", EventProp::FileDrop), event("on-focus-change", EventProp::FocusChange), event("on-key", EventProp::Key), event("on-pointer-enter", EventProp::PointerEnter), event("on-pointer-leave", EventProp::PointerLeave), event("on-pointer-down", EventProp::PointerDown), event("on-pointer-up", EventProp::PointerUp), ]; /// Properties every widget accepts that are not a [`PropDecl`] at all: /// `validate` lowers them before it consults the tables, because they are /// structural rather than per-widget — `:class` keys theme matching, /// `:tooltip` is the tree's to show, `:enabled` gates interaction, `:focused` /// drives which widget has focus. They appear here so /// [`WidgetDescriptor::known_props`] tells the truth. const UNIVERSAL_PROPS: &[&str] = &["class", "tooltip", "enabled", "focused"]; /// Whether a name belongs to the vocabulary *every* widget has: the layout /// properties, the pointer events, and the structural universals. /// /// A `.gdw` manifest cannot redeclare one — [`WidgetDescriptor::lookup`] /// consults these tables first, so the declaration would be silently dead — and /// this reads the same tables the lookup does. pub fn is_universal_prop(name: &str) -> bool { COMMON_LAYOUT_DECLS.iter().any(|decl| decl.name == name) || COMMON_EVENT_DECLS.iter().any(|decl| decl.name == name) || COMMON_UNIVERSAL_DECLS.iter().any(|decl| decl.name == name) || UNIVERSAL_PROPS.contains(&name) } /// The Rust setter a property name implies by convention: `link-color` → /// `set_link_color`. A manifest's `:setter` overrides it, exactly as a builtin /// descriptor names a method the convention would miss — `:text` is /// `set_content` on a `text-input` but `set_text` on a `text`. pub fn setter_name(prop: &str) -> String { format!("set_{}", prop.replace('-', "_")) } /// The widget vocabulary of one compilation: the builtins, plus whatever /// `.gdw` manifests an application put on its widget search path. /// /// The builtins are not copied in — they live once in the process-wide /// [`BUILTINS`], parsed from the embedded manifest, and [`Self::resolve`] /// borrows from there for a builtin and from the owned [`Self::widgets`] for a /// declared one. A registry therefore owns only what is genuinely owned, and /// [`Registry::default`] costs nothing. #[derive(Clone, Debug, Default)] pub struct Registry { widgets: Vec, } impl Registry { /// Add a declared widget, rejecting a name the vocabulary already has. /// /// Shadowing is never allowed, in either direction: a `.gdw` cannot /// redefine `button` (the framework's rules key on the builtin names), and /// two manifests cannot both claim `markdown`. pub fn insert(&mut self, widget: WidgetDescriptor) -> Result<(), Diagnostic> { let clash = if is_builtin(&widget.name) { Some("a builtin widget") } else if self.widgets.iter().any(|w| w.name == widget.name) { Some("another widget manifest") } else { None }; if let Some(what) = clash { return Err(Diagnostic::new( format!("`{}` is already the name of {what}", widget.name), widget .name_span .expect("a declared widget carries its name span"), )); } self.widgets.push(widget); Ok(()) } /// The declared widget of this name, if any. pub fn user(&self, name: &str) -> Option<&WidgetDescriptor> { self.widgets.iter().find(|w| w.name == name) } /// Every declared widget, in declaration order. pub fn user_widgets(&self) -> &[WidgetDescriptor] { &self.widgets } /// Every descriptor in the vocabulary: the builtins, then the declared /// widgets. One list, which is what lets the runtime factory table be /// built by one loop that cannot tell a `container` from a `markdown`. pub fn descriptors(&self) -> impl Iterator { BUILTINS.iter().chain(self.widgets.iter()) } /// Resolve a name in widget position (or in a theme selector) to its /// descriptor — a builtin's `&'static` row, or a declared widget's owned /// entry. Builtins first, since a manifest can never take one of their /// names. pub fn resolve(&self, name: &str) -> Option<&WidgetDescriptor> { BUILTINS .iter() .find(|d| d.name == name) .or_else(|| self.user(name)) } /// Every widget a `.gdc` may write, comma-separated — for the "unknown /// widget" diagnostic. Generated from the vocabulary in force, so it names /// the application's own widgets too. pub fn writable_names(&self) -> String { self.descriptors() .filter(|d| d.is_writable) .map(|d| d.name.as_ref()) .collect::>() .join(", ") } } /// The framework's builtins, parsed once from the embedded manifest. /// /// This is the whole point of the exercise: a builtin is described in the same /// `.gdw` grammar an application writes, so there is no privileged path. The /// manifest is `include_str!`d into the compiler — no cross-crate boundary, so /// no cycle — and parsed like any other. A static so a reference into it is /// `'static`, matching what the const table gave. The parse cannot fail on a /// shipped binary: [`builtins.gdw`](builtins.gdw) is compiled into it and a test /// parses it, so the only failure it can have is one the test catches first. static BUILTINS: LazyLock> = LazyLock::new(|| { crate::manifest::compile_manifest(include_str!("builtins.gdw")) .expect("the builtin widget manifest parses") }); /// Every builtin descriptor, for callers that want the framework's own set /// without an application's declarations — the theme-coverage tests, chiefly. pub fn builtins() -> impl Iterator { BUILTINS.iter() } /// A builtin descriptor by name. pub fn builtin(name: &str) -> Option<&'static WidgetDescriptor> { BUILTINS.iter().find(|d| d.name == name) } /// Whether a name is one of the framework's builtins. pub fn is_builtin(name: &str) -> bool { builtin(name).is_some() } #[cfg(test)] mod tests { use super::*; /// The builtins are the parsed `.gdw`, not a hand-written table — this is /// the one thing that proves the manifest is what the compiler runs on. /// (The migration equality against the retired const table proved the /// translation faithful; from here the standing suite — goldens, the m13 /// EXACT differentials, every theme test — is the gate, exercising every /// one of them through the parsed descriptors.) #[test] fn the_builtins_are_the_parsed_manifest() { // Forces the parse; a malformed `builtins.gdw` panics here (and in // every other test, which all reach the registry). let names: Vec<&str> = builtins().map(|d| d.name.as_ref()).collect(); assert_eq!(names.len(), 28, "every builtin declared: {names:?}"); assert_eq!(names[0], "container", "order is the diagnostic vocabulary"); // The capabilities that used to be the const table's alone, now read // back off the manifest: placement, non-writability, the accelerator. let menu_item = builtin("menu-item").expect("declared"); assert_eq!( menu_item.required_parents.as_ref(), &[ Cow::Borrowed("menu"), Cow::Borrowed("context-menu"), Cow::Borrowed("dropdown"), Cow::Borrowed("combo-box") ], ); assert!( matches!( menu_item.lookup("accel").map(|d| &d.kind), Some(PropDeclKind::Accel { .. }) ), "the accelerator survived the round trip through the grammar", ); assert!( !builtin("menu-panel").expect("declared").is_writable, "`(internal)` kept the panel out of the writable vocabulary", ); } }