Macro-based DSL for declaring Plushie widgets.
Supports two kinds of widget:
:native_widget- backed by a Rust crate implementing thePlushieWidgettrait. Requiresrust_crateandrust_constructordeclarations.:widget- pure Elixir widget. Features are detected at compile time based on what callbacks are defined:- Has
statedeclarations -> stateful (deferred view, state persistence via the runtime). - No
state-> stateless (immediate view innew/2). - Has
handle_event/2-> participates in event dispatch. - Has
subscribe/2-> widget-scoped subscriptions.
- Has
Usage
defmodule MyApp.Gauge do
use Plushie.Widget, :native_widget
widget :gauge
field :value, :float
field :min, :float, default: 0
field :max, :float, default: 100
field :color, Plushie.Type.Color, default: :blue
field :width, Plushie.Type.Length
field :height, Plushie.Type.Length
rust_crate "native/my_gauge"
rust_constructor "my_gauge::GaugeWidget::new()"
event :value_changed, fields: [value: :float]
command :set_value, value: :float
endGenerated code
The macro generates:
type_names/0- returns[:gauge](from thewidgetdeclaration)native_crate/0- returns therust_cratepath (native_widget only)rust_constructor/0- returns the Rust expression (native_widget only)new/2- creates a%Module{}struct ()- Setter functions per field for pipeline composition
with_options/2- applies keyword options via settersbuild/1- converts the struct to aui_node()map@type t,@type option- typespecs for dialyzerPlushie.Widgetprotocol implementation__event_specs__/0,__event_spec__/1- typed event metadata- Command functions (native_widget only) that wrap
Plushie.Command.widget_command/3
Field types
Field types are resolved via Plushie.Type.resolve/1. Primitive
atom shortcuts (:integer, :float, :string, :boolean,
:atom, :any, :map), Plushie.Type module names (e.g.
Plushie.Type.Color), and composite forms ({:list, :string})
are accepted. Values are stored raw; Tree.normalize/1 handles
wire encoding in a single pass.
Composite widgets
If the using module defines view/2 (leaf) or view/3 (container),
new/2 delegates to it after resolving props:
defmodule MyApp.LabeledInput do
use Plushie.Widget
widget :labeled_input
field :label, :string
def view(id, props) do
import Plushie.UI
column id: id do
text(props.label)
end
end
endview/2 vs view/3
Use view/2 for simple widgets:
def view(id, props) do
%{id: id, type: "text", props: %{content: props.label}, children: []}
endUse view/3 when the widget has state (declared via state).
The third argument is the widget's internal state map:
def view(id, props, state) do
fill = if state.hover, do: "#ff0", else: "#ccc"
...
endCommon options
Widgets that support accessibility or event rate limiting declare these as normal fields:
field :a11y, Plushie.Type.A11y, merge: true- accessibility overrides (seePlushie.Type.A11y). Themerge: trueoption makes the setter merge user values with widget defaults.field :event_rate, :integer- maximum events per second for coalescable events from this widget.
Summary
Types
A child element: either an already-resolved node map or a widget struct.
A UI tree node map. Every widget builder returns this shape.
Callbacks
Path to the Rust crate relative to the package root.
Full Rust constructor expression for the widget.
Node type atoms this widget handles.
Functions
Converts a widget struct to a ui_node() map via the Tree.Node protocol.
Types
A child element: either an already-resolved node map or a widget struct.
@type ui_node() :: Plushie.Tree.Node.ui_node()
A UI tree node map. Every widget builder returns this shape.