# The Rust side `include_component!(Name)` expands a component file into typed Rust. This page is what that code looks like from your side, plus dev mode and the escape hatch of building trees in plain Rust. ## What gets generated For `(component Counter …)`: | Item | What it is | | --- | --- | | `CounterProps` | One plain field per prop; implements `Default` when every prop has a `:default` | | `CounterCx` | The handler capability surface (below) | | `trait CounterLogic` | One method per handler, plus one factory per instantiated component type | | `struct Counter` | `mount(...)`, and one public field per prop, state, and output | ## Handlers and `Cx` Each `(handler name)` becomes `fn name(&mut self, cx: CounterCx)`; a payload declaration adds the value (`value: &str` for `String`, by value for the numeric and bool types). `Cx` carries, flat, one field per declaration — the same single namespace as the file: - props as `ReadSignal` — read with `.get()`, not writable from inside the component; - states as `Signal` — `.get()` / `.set(v)` / `.update(f)`; - outputs as `Emitter` — `.emit(&value)` delivers to every subscriber. Reads inside handlers usually want `.get_untracked()` — handlers are not reactive contexts, so there is nothing to track: ```rust fn increment(&mut self, cx: CounterCx) { let step = cx.step.get_untracked(); // a prop cx.count.set(cx.count.get_untracked() + step); // a state cx.stepped.emit(&step); // an output } ``` Signal writes are equality-gated: setting the current value again is a no-op and wakes nothing. `Cx` also carries `cx.consume()`: called from an event handler, it stops the event reaching handlers later in the dispatch (a bubbling ancestor, a deeper capture), so an inner `:on-click` can keep a click from also firing the container around it. It is opt-in — a handler that never calls it changes nothing — and a no-op off the event path (an instance output, a query). ## Seeding state at mount A `:init` is a literal, and a handler only runs in answer to an event — so a component whose data comes from outside the component language (a file, a database, a network fetch) has nothing to fill it with. The generated `Logic` trait carries one method the file does not declare: ```rust fn mounted(&mut self, cx: WikiCx) { cx.page.set(self.store.current_page()); cx.blocks.set(self.store.load().into_iter().map(…).collect()); } ``` It runs once, after the subtree is built and every binding and wire is attached — so a signal it sets is already being watched, and the component's first frame shows the loaded data rather than the `:init` values. It is also where anything that should run while the component is on screen gets started. The default does nothing, so a component that needs no seeding declares no hook. Nested instances are mounted during the parent's walk, so a child's hook runs before its parent's. `mounted` is the framework's method name: a handler, or a child component whose factory method would take it, is a compile error naming the collision rather than a silently shadowed hook. ## Mounting ```rust let mut tree = WidgetTree::new(); let counter = Counter::mount( &mut tree, None, // parent widget, None = at the root CounterProps { label: "ones".into(), step: 1 }, Logic, ); guiduck::run("app", tree)?; ``` The returned struct is the embedding side's handle: - `counter.root` — the subtree's root `WidgetId`. - `counter.step`, `counter.count` — props and states as **writable** signals. Props are live: `counter.step.set(5)` after mount updates every binding that reads it. (Inside the component they are read-only; driving them is the owner's privilege.) - `counter.stepped` — outputs, for subscribing: ```rust counter.stepped.subscribe(|step| println!("stepped by {step}")); ``` A component whose file declares a `(slot)` also gets `mount_with_slot(tree, parent, props, logic, slot)`, where `slot` is a closure `FnOnce(&mut WidgetTree, Option)` invoked at the slot position to build the projected content; plain `mount` leaves the slot empty. ## Logic factories When a component instantiates others, its `Logic` trait asks you for each child's logic — one factory method per child *type*, called once per instance: ```rust impl CounterPanelLogic for PanelLogic { fn tally(&mut self, cx: CounterPanelCx, value: i32) { … } fn counter_button(&mut self) -> impl CounterButtonLogic { ButtonLogic } } ``` A missing factory is a compile error, exactly like a missing handler. A component with no handlers and no factories has an empty `Logic` trait which `()` implements — pure-presentation components mount with `Counter::mount(tree, parent, props, ())`-style calls, no boilerplate type. ## Signals outside components `guiduck::core::signals` is a small fine-grained reactive runtime you can use directly: `Signal` (a `Copy` handle), `Memo`, `Effect`, and `Scope` for bulk disposal. Effects run once at creation and again after any of the signals they read change; the frame scheduler flushes them before styling and layout, and diamond dependencies run an effect at most once per flush. ## Overlays `WidgetTree::open_overlay(style, options)` opens a layer above the base tree and returns its root widget — mount anything under it, including a `.gdc` component. `OverlayOptions::dialog()` is modal (an input barrier that also traps Tab and focus, restored on close) and Escape-dismissed; `OverlayOptions::popup(anchor, AnchorSide::Below)` and `popup_at(point)` are light-dismiss (an outside press closes them and is consumed). Anchored placement flips to the opposite side at the viewport edge and clamps. `close_overlay(root)` tears the layer down and runs the options' `on_close`. Event handlers open overlays through the command queue (`ctx.push_command(move |tree| { … })`), since they cannot borrow the tree directly. ## Shortcuts `WidgetTree::on_shortcut(Keystroke::ctrl("s"), || { … })` registers an application accelerator, which lasts as long as the tree. Routing order is fixed, and each step is there to stop the one below it from stealing something: the focused widget sees the key first (a text input's Ctrl+C is the text input's, and a bare-letter shortcut can never steal typing), then that widget's `:on-key` wire and its ancestors', then overlay dismissal (Escape), then shortcuts, then Tab traversal. A menu item's `:accel` is registered the same way but **owned by the menu**, so it goes when the menu does. That matters for hot reload: a registration outliving the subtree that made it would shadow the one that replaced it, and since the first match wins, the key you just deleted from the file would go on firing the handler the file no longer names. ## Timers Widgets request wakes instead of reading clocks: `Widget::take_wake` returns "call my `on_timer` in this long", the shell turns it into a `WaitUntil` on the event loop, and delivers `WidgetTree::tick(now)` when it comes due. An idle app with no pending wakes still renders zero frames; a focused text input blinks its caret this way. Tests drive `next_wake(now)`/`tick(now)` with fabricated times, so timed behavior is fully deterministic headlessly. ## Dev mode: hot reload The interpreter builds the *same* tree from the *same* compiled IR that the macro's generated code builds — a differential test suite holds the two paths to identical output, under input and not merely at mount. That guarantee is what lets the framework choose between them for you. **`mount` is the entry point an application's `main` wants:** ```rust Counter::mount(&mut tree, None, CounterProps::default(), Logic); guiduck::run("counter", tree)?; ``` It takes structure from the live `.gdc` while you are building the app, and from the compiled component in anything you ship — no branch in `main`, no path to supply, nothing to poll. The rule is: a development build (`debug_assertions`) whose source file is still where the build found it. Both halves matter — the profile is what distinguishes building the app from testing what you will ship, and the file check is what makes a debug binary harmless on someone else's machine, where the path does not exist. `GUIDUCK_LIVE=1` or `=0` overrides either way, for iterating in a release profile or shipping a debug build deliberately. It returns nothing: the live path has no typed handle to give, because its state lives in the interpreter and is rebuilt on every reload. When you need the component back — to drive a signal from Rust — call `mount_compiled`, which is also what a test should call: `mount` in a development build would quietly exercise the interpreted path rather than the one under test. ### Leaving the interpreter out The interpreter and its file watcher are behind the facade's default-on `hot-reload` feature. Turning it off drops `guiduck-component-rt` and eight transitive crates (`notify`, `mio`, `inotify`, `walkdir` and friends), and `mount` always takes the compiled path: ```toml guiduck = { version = "0.2", default-features = false } ``` A Cargo feature is not a build profile, and this is the one thing to be clear about: dependency resolution happens before a profile is chosen, so switching it off removes hot reload from *every* build of that graph, debug included. There is no way to have it in debug and not in release — what you get for free instead is that the *watcher never runs* in a release build, because `mount` gates on `debug_assertions`. Leave the feature on unless your dependency tree matters to you more than reloading does. Both specific entry points remain, and are how a caller picks a path on purpose: ```rust let counter = Counter::mount_compiled(&mut tree, None, props, Logic); counter.count.set(7); ``` ```rust use guiduck::rt::DevMount; let spec = Counter::dev_spec(CounterProps::default(), Logic); let mut dev = DevMount::new(&mut tree, None, "components/Counter.gdc", spec)?; ``` - Saving the file (or any component file it transitively instantiates) rebuilds the subtree. Structure, layout, bindings, and styles come from the live file; **handlers are always your compiled Rust**, dispatched through a generated registry. - State survives reloads: top-level by name and type, nested instances by identity (`:key` or occurrence — see [the component language](components.md#hot-reload-and-instance-identity)). - A save that doesn't compile keeps the previous UI and prints the diagnostics; fixing the file heals it. - Component references resolve through the same search path the typed build used (baked into `dev_spec`), with the mounted file's own directory as a fallback. A `DevMount` handed to `tree.set_live_reload(dev)` is polled by the shell on every wake, and given a waker so a save applies as you make it — which is all `mount` does. Driving it yourself is still available: `dev.poll(&mut tree)` when the loop wakes, `dev.set_waker(...)` for the immediate wake, `dev.reload_now(&mut tree)` to force one. The `m5_dev` example is that longhand pattern. ## Checking the two paths agree The guarantee the switch rests on is checkable for your own components, with the oracle guiduck uses on its own: ```rust use guiduck::testing::{assert_paths_agree, Step}; assert_paths_agree(&mut compiled, &mut interpreted, viewport, &[ Step::ctrl(Key::Character("s".into())), Step::Click(point), ]); ``` It replays the script through both trees and compares after every step. Drive input rather than only mounting: two freshly-mounted scenes agree whenever the *drawing* matches, and are blind to a difference in what was *registered* — a menu item renders its accelerator identically whether or not the keystroke was ever wired up. ## Comparing two trees `WidgetTree::structural_diff(&other)` walks two trees in lockstep and describes the first place they part company — widget type, classes, tooltip, enabled, **attached handler count**, child count and layout rectangle at every position, plus the tree-level registrations (shortcuts, overlays, dialogs, open menus, pending wakes, accessibility owners) that belong to no node. It returns a path rather than a bool, because "these differ" is not something you can act on and `root > child 2: handler count 3 vs 4` is. It exists for the question a scene comparison cannot answer. Two trees that *draw* the same thing may still differ in what was **registered** — a menu item renders its accelerator identically whether or not the keystroke was ever wired up — and the invariant a hot reload has to meet is stated with it: *a reloaded tree is indistinguishable from a freshly mounted one.* ```rust assert_eq!(fresh.structural_diff(&reloaded), None); ``` ## Plain Rust, no `.gdc` Components are a layer over an ordinary retained widget API — you can use that API directly, and mix freely with mounted components: ```rust use guiduck::core::{Container, Text, WidgetTree, taffy}; let mut tree = WidgetTree::new(); let root = tree.insert( Container::new().background(guiduck::paint::Color::WHITE), taffy::Style { padding: taffy::Rect::length(16.0_f32), ..Default::default() }, None, ); let label = tree.insert(Text::new("hi"), taffy::Style::default(), Some(root)); let count = guiduck::core::signals::Signal::new(0); tree.bind::(label, move || format!("count: {}", count.get()), |w, v| w.set_text(v)); tree.on_event(root, guiduck::core::EventKind::Click, move |_ctx, _ev| { count.set(count.get_untracked() + 1); }); ``` `tree.bind` is the same mechanism component bindings compile to: a reactive effect computing a value, applied through the widget's setter, which classifies the damage (paint-only vs. layout) so the frame does the minimum. The examples `m1_widgets`, `m2_counter`, and `m9_scroll` are all built this way. ## The shell `guiduck::run(title, source)` opens a window and drives the event loop; any `FrameSource` works, and `WidgetTree` is one. The renderer is chosen by `GUIDUCK_BACKEND` (`vello`, the default, or `software`). Closing the window exits. `GUIDUCK_TRACE_FRAMES=1` logs each rendered frame — an idle app logs nothing, and the test suite enforces that.