registration.rs raw

//! The open widget registry: how a widget an application wrote in Rust makes
//! itself constructible by name.
//!
//! A widget crate declares its interface in a `.gdw` manifest and registers the
//! Rust side from it with [`register_widget!`](crate::register_widget), which
//! emits an [`inventory::submit!`] of a [`WidgetRegistration`]. The framework's
//! own builtins register the same way (from their descriptors). Every
//! registration linked into the binary is collected here, so the interpreter
//! builds a widget it has no compile-time knowledge of — a `container` and a
//! `markdown` through one mechanism, sourced from the link.
//!
//! The registration is fn-pointer-based, so an `inventory::submit!` item can be
//! the `Sync` static that mechanism requires — it holds no heap handle.

use std::any::Any;

use crate::widget::Widget;

/// One property a registered widget can be given: the name a `.gdc` writes and
/// a thunk that applies a boxed value through the widget's setter.
///
/// The thunk is generated with the concrete widget and value types in hand, so
/// the setter call it makes is type-checked by rustc — a manifest that names a
/// setter the widget does not have, or the wrong value type for it, is a
/// compile error at the generated call, not a silent runtime miss.
pub struct SetterEntry {
    /// The keyword a `.gdc` writes (`corner-radius` → `:corner-radius`).
    pub prop: &'static str,

    /// Apply a boxed value to a widget. A widget or value that is not what the
    /// thunk expects applies nothing — the erased spelling of the mismatch that
    /// makes a mistargeted `Commands::mutate` a no-op.
    pub apply: fn(&mut dyn Widget, Box<dyn Any>),
}

/// Everything the runtime needs to build one registered widget and drive its
/// properties: its `.gdc`/theme name, a constructor, and a setter per property.
///
/// One is submitted per [`register_widget!`](crate::register_widget); the whole
/// linked set is [`registered_widgets`].
pub struct WidgetRegistration {
    /// The name a `.gdc` node and a theme selector write.
    pub name: &'static str,

    /// Build a fresh widget. Registered widgets are `Default`, so this is
    /// always `|| Box::new(T::default())`.
    pub construct: fn() -> Box<dyn Widget>,

    /// One [`SetterEntry`] per property the manifest declared.
    pub setters: &'static [SetterEntry],
}

inventory::collect!(WidgetRegistration);

/// Every widget registered with `register_widget!` across the linked crates.
pub fn registered_widgets() -> impl Iterator<Item = &'static WidgetRegistration> {
    inventory::iter::<WidgetRegistration>()
}

/// The registration of one widget by name, if it is linked in.
pub fn registered_widget(name: &str) -> Option<&'static WidgetRegistration> {
    registered_widgets().find(|registration| registration.name == name)
}