# Naming your Rust widget from a `.gdc`: the `.gdw` manifest The [builtin widgets](widgets.md) are Rust code, and so is every control a real app eventually needs that the framework does not ship — a chart, a canvas, a map, a document view. You write one in Rust, in your own crate (see [Writing a widget in Rust](writing-widgets.md)), and then **name it from a `.gdc`** exactly as you name a `container`: ```lisp (chart :source "revenue.csv" :on-point (inspect payload)) ``` To get there you write one small file — a `.gdw` manifest — describing the widget's interface, and connect it to your Rust type with one macro call. This is exactly how the framework's own widgets work: there is no privileged path, and `container` is registered under the same contract your `chart` is. The framework's markdown widget lives in `guiduck-markdown`, a crate that depends on `guiduck-core` and `pulldown-cmark` and nothing else guiduck ships — nothing in `guiduck-core`, the component compiler, or either code-generation back end knows the word "markdown". It is spelled in one manifest, and there only. ## The problem a manifest solves The component compiler runs as a **proc-macro** at build time, and a proc-macro cannot see another crate's types. There is no reflection: nothing in the build can discover your `Widget` impl, read its method names, or learn what type a setter takes. So the compiler cannot, on its own, know that `chart` means your `ChartView`, that it has a `set_source` method, or that the method takes a `String`. A `.gdw` manifest is how you tell it. It is a small **data** file that lists the widget's name, the properties and events it offers, and the Rust type behind it. The compiler reads the manifest, type-checks every `.gdc` that uses the widget against it, and generates the code that drives your setters. Think of a manifest as a typed `use` statement for the widget vocabulary: it says "this name is a widget, and here is what it accepts." Nothing in the manifest is trusted blindly. A manifest is a *promise* about your Rust type, and `register_widget!` (next) generates code that actually calls your methods — so rustc checks the promise against reality. A manifest that names a setter your widget lacks, or a wrong type, is a compile error. A manifest is an s-expression file, like a `.gdc` component or a `.gdt` theme. Its extension is `.gdw`. ## The two files and the line that connects them A widget you name from a `.gdc` is, in **your** crate: 1. a Rust type that implements `Widget` — the implementation (see [Writing a widget in Rust](writing-widgets.md)); 2. a `.gdw` manifest — the interface; joined by one macro call: ```rust guiduck_core::register_widget!(ChartView, "widgets/chart.gdw"); ``` `register_widget!` is the linchpin. At build time it reads the manifest and: - **generates the runtime factory** — a function that builds a `ChartView::default()` and, for each property the manifest declares, calls the matching setter with the incoming value. Because a widget is built this way — default, then setters — the framework can construct your widget *without naming its type*, which is what lets one code path serve every widget and what the hot-reload interpreter needs (it builds a widget it has never heard of). - **type-checks that factory against your real methods.** The generated setter calls name `ChartView` and the value types the manifest declared, so if the manifest and your Rust disagree, rustc reports it *at this line* — the manifest can never validate a `.gdc` against a lie. - **registers the factory in a link-time table**, so any application that links your crate can build the widget, in a compiled build and under hot reload alike — the app declares your widget in a `.gdc` and never has to hand the framework a constructor. The manifest path is relative to your crate root. That is the entire connection: implement `Widget`, write the `.gdw`, call `register_widget!`. A widget crate's whole relationship with the framework is a single dependency, `guiduck-core` — it re-exports `register_widget!`, so you need no facade crate, no macro crate, no interpreter, and no build script. ## The grammar ```lisp (widget chart (type "::my_charts::ChartView") (prop source String) (prop stacked bool) (event on-point :payload i64) (token line-color Brush)) ``` A `.gdw` file holds one or more `(widget …)` forms — a crate's whole widget set can be one file. Each form is a lowercase name followed by these items. ### `(type "…")` — required, exactly one The Rust path used to construct the widget. It is a **string**, not a symbol: the reader ends symbols at `:`, so a path like `::my_charts::ChartView` cannot be one, and the quotes are the grammar being honest about its own lexer rather than special-casing it. **The compiler never resolves this path.** It is carried verbatim into generated code, and rustc reports a bad path against the construction it generates. That is the whole point of the split: the interface is data the compiler can check, and the implementation stays Rust that the compiler you already trust checks. ### `(prop name Type)` — a settable property Each prop becomes a property a `.gdc` may set once at build or drive from a binding. `Type` is one of: | Type | What it is | | --- | --- | | `i32`, `i64`, `f32`, `f64`, `bool`, `String` | the `.gdc` scalars | | `Brush` | a solid color or gradient | | `Graphic` | vector geometry or a file's pixels (an `(asset …)` / `(path …)`) | | `RichText` | a string with styled spans (a `(rich …)`) | | `ScrollAxes` | one of `vertical` / `horizontal` / `both` | This is the framework's own writable property vocabulary — the *same* set the builtins draw from. A `(prop icon Graphic)` on your widget is checked, set, and bound by exactly the code that serves an `image`'s `:source`, so your widget is not a second-class citizen: it can take a themeable graphic or rich text for the same reason an `image` and a `text` can. (Records are file-local to a `.gdc`, and lists have no setter convention, so neither crosses this boundary — data-driving a widget is composition in the `.gdc`, with `for`, not a prop type.) **The setter convention.** A prop named `source` is set by calling `set_source`; `line-color` maps to `set_line_color` (kebab-case to snake_case). If your method is not spelled that way, name it: ```lisp (prop scale f32 :setter set_zoom) ``` This is the same override the builtins use — `:text` is `set_content` on a `text-input` but `set_text` on a `text`. Reach for it when the method's own name is the right one and the convention would miss it, not to satisfy the convention by renaming a good method. ### `(event on-name)` / `(event on-name :payload Type)` An event your widget raises, wired in a `.gdc` to a handler. Names start with `on-`. A payload-less event is a bare request: ```lisp (event on-dismiss) ``` A payload event delivers a value the wire's `payload` keyword reads: ```lisp (event on-point :payload i64) ``` ```lisp (chart :on-point (inspect payload)) ``` The payload `Type` is a scalar (`i32`, `i64`, `f32`, `f64`, `bool`, `String`). What the value *means* is the application's: guiduck delivers it and the handler interprets it. Your widget raises the event by pushing an `EventData::User { name, payload }` from an input hook — see [Reporting to the application](writing-widgets.md#reporting-to-the-application). ### `(query name :payload Type :setter method :returns "Path")` A query is a wire whose handler **returns a value to the widget**, rather than being dispatched as a fire-and-forget event. The markdown widget's image resolution is what this exists for — the document holds link targets and image sources, opaque strings only the application can turn into pixels: ```lisp (query get-image :payload String :setter set_image_resolver :returns "::guiduck_markdown::ImageResult") ``` A `.gdc` wires it like an event — with the payload: ```lisp (markdown :source page :get-image (load payload)) ``` but the handler returns the query's type, and codegen installs it as a resolver through the `:setter` rather than registering it as an event: ```rust fn load(&mut self, cx: PageQueryCx, url: &str) -> guiduck_markdown::ImageResult { … } ``` So the widget calls the handler synchronously when it needs the value. On your Rust type the contract is a method `fn set_image_resolver(&mut self, impl Fn(&PayloadType) -> ReturnType + 'static)`. `:returns` is a **verbatim Rust path** (like `(type …)`) — the compiler never resolves it; rustc checks it against the generated closure. The return type must be `Default` (its default is the "no value" answer the interpreter falls back to when it cannot reassemble the handler's context). A query handler receives a **read-only context** (`XQueryCx`, not `XCx`): it may read the component's props and state to inform its answer but cannot `set` state. That is deliberate — a query *answers*, it does not *act*, and it runs inside the widget's layout pass, a phase where mutating state would be out of order with the frame pipeline. (An event handler, which runs at the designed mutation point, gets the writable context and mutates state freely.) ### `(token name Type)` — a theme token A value the widget reads from its computed [style](theming.md), resolved through the same style engine every builtin uses. `Type` is `Brush`, `Number`, `Str`, or `Graphic`. A theme sets it with an ordinary rule: ```lisp (rule (chart .revenue) :line-color "#3b6ea5") ``` Tokens are for anything that is a *convention* rather than a law — "a link is blue and underlined", "a bullet is •", "the line is 2px". A widget that hard-coded those could not be themed. A `Graphic` token lets a theme swap vector geometry for a raster (or the reverse) with no widget change, exactly as a `checkbox`'s check mark can be replaced. On the Rust side you read tokens in `apply_style` — see [Theming](writing-widgets.md#theming-apply_style). ## Structural capabilities Beyond properties, events, and tokens, a `.gdw` can declare a few facts about how the widget fits into the tree. These used to be things only the framework's own widgets could say; they are ordinary manifest forms now, so an application's widget can be a list row, a panel a parent builds, or a menu item. ### `(parents name…)` — where the widget may appear Restricts the widget to being a direct child of the named widgets. A row that only makes sense inside a list, a tab inside a tab bar: ```lisp (widget tab (type "::my_tabs::Tab") (parents tab-bar)) ``` Writing `(tab …)` anywhere but inside a `(tab-bar …)` is then a compile error that names where it belongs. Omit the form for a widget that may appear anywhere (the common case). The names are checked against the real vocabulary, so a typo in a parent name is caught too. ### `(internal)` — a widget a parent builds Marks a widget that no `.gdc` writes, because a parent widget builds it in Rust — the popup panel a menu opens is the framework's example. An `(internal)` widget still has a name (so a theme can target it) and still takes tokens, but it is rejected in widget position in a `.gdc`. Most application widgets are *not* internal; you need this only for a sub-widget your own widget constructs. ### `(accel :setter method)` — a keyboard accelerator Declares that the widget carries a menu-style accelerator (`:accel "Ctrl+S"` in a `.gdc`). This is specialized — it is what a menu-item-like widget uses — and it does two things at once: the `:setter` method receives the accelerator's rendered text, and the tree registers the keystroke so it fires the widget's `:on-select` wire even while the widget is not on screen. Reach for it only if you are building something menu-item-shaped. ### Deferred content is a Rust capability, not a manifest one A widget whose children are a *popup* — built when it opens, not at mount — opts in through two `Widget` trait methods (`defers_content` and `set_content`), with **no manifest syntax at all**. Whether your widget defers its content is its own runtime answer, invisible to the `.gdc` that writes it, so it belongs with the implementation. See [Deferred content](writing-widgets.md#deferred-content-popups). This is the same seam menus, dropdowns, and dialogs use, and it is open to your widgets too. The one thing genuinely reserved to the framework is the closed set of *typed* events its own controls emit (a `checkbox`'s toggle, a `text`'s link). Your widget's events are `(event …)` declarations that ride the general `EventData::User` path — which is all an application ever needs, and what the markdown widget's `on-link` uses. ## The contract your Rust widget owes A registered widget is **`Default + Widget` plus one setter per declared prop**. There are no constructor arguments — a prop *is* a setter call. This is what lets one code path serve builtins and app widgets alike, and what the runtime interpreter needs, since it constructs a widget it has never heard of and cannot pass arguments to. So a prop must be settable at build *and* re-settable from a binding through the same method. Give the widget sensible defaults via `Default`, and let each `set_*` both initialize and update, comparing-and-setting so a binding that re-sets the current value marks nothing and does not loop. The full implementation guide, including this contract, is [Writing a widget in Rust](writing-widgets.md). ## Using the widget from an application An application names your widget in a `.gdc` with **no configuration at all**. It depends on your crate (which it must, to link the `register_widget!` registration and to name the type in generated code), and `include_component!` finds your `.gdw` automatically: it runs `cargo metadata` for the crate being built and reads the widget directory of every resolved dependency, so a crates.io, git, path, or workspace dependency all resolve the same way. There is nothing to copy and nothing to point at. The one requirement on **your** side, as the widget's author, is that the `.gdw` ships in your package and sits where discovery looks. The default is a `widgets/` directory under your crate root — where `register_widget!("widgets/chart.gdw")` already reads it from, and which Cargo includes in a published package by default. To use a different directory, name it in your `Cargo.toml`: ```toml [package.metadata.guiduck] widget-path = ["widgets", "vendor/widgets"] ``` Directories are probed in order, first hit wins — the same search-path mechanism the [component](components.md) and [asset](widgets.md#image) paths use, under a different key. Editing a `.gdw` rebuilds the crate exactly as editing a `.gdc` does (the manifest is tracked as a build input), and in dev mode a live edit that adds a `(chart …)` to a running `.gdc` resolves without a rebuild, because the whole linked widget vocabulary is available to the interpreter. ## A complete example A widget crate, depending only on `guiduck-core`: ```rust // my-widgets/src/lib.rs use guiduck_core::{Widget, register_widget}; #[derive(Default)] pub struct NoteView { /* … */ } impl NoteView { pub fn set_title(&mut self, title: String) { /* compare-and-set, mark dirt */ } pub fn set_zoom(&mut self, scale: f32) { /* … */ } } impl Widget for NoteView { /* paint, measure, on_pointer, … */ } // Reads the manifest, generates the rustc-checked factory, and registers it. register_widget!(NoteView, "widgets/note.gdw"); ``` Its manifest, `my-widgets/widgets/note.gdw`: ```lisp (widget note (type "::my_widgets::NoteView") (prop title String) (prop scale f32 :setter set_zoom) (event on-open :payload String) (event on-dismiss) (token ink Brush)) ``` An application that depends on `my-widgets` writes it in a `.gdc`, with a theme dressing its token — checked exactly the way a builtin is: ```lisp (note :title "Draft" :scale zoom :on-open (open-note payload) :on-dismiss close-note :class "pinned") ``` ```lisp (rule (note .pinned) :ink "#b58900") ``` No `widget-path` in the application, no copied files: the `note.gdw` shipped in `my-widgets` is discovered through the dependency. ## Diagnostics A malformed manifest is a compile error pointing into the `.gdw`: a missing or duplicate `(type …)`, an unknown prop or token type (the message lists the ones it knows), an event name without `on-`, a prop declared twice, a name that redeclares a property every widget already has (`:width`, `:class`, `:autofocus`, `:on-click`, …), a capitalized widget name (that would be a component reference, so nothing could ever write the widget), or a name that collides with a builtin or another manifest. A stale *type path* or *setter name* is caught one level down, by rustc, against the code the manifest generates — which is the division the whole design turns on: the compiler checks the interface, rustc checks that the interface tells the truth about the implementation.