pub enum WidgetSpec {
Show 13 variants
Row {
children: Vec<WidgetSpec>,
key: Option<String>,
},
Col {
children: Vec<WidgetSpec>,
key: Option<String>,
},
HintBar {
entries: Vec<HintEntry>,
key: Option<String>,
},
Toggle {
checked: bool,
label: String,
focused: bool,
key: Option<String>,
},
Button {
label: String,
focused: bool,
intent: ButtonKind,
key: Option<String>,
disabled: bool,
},
Spacer {
cols: u32,
flex: bool,
key: Option<String>,
},
List {
items: Vec<TextPropertyEntry>,
item_keys: Vec<String>,
selected_index: i32,
visible_rows: u32,
focusable: bool,
key: Option<String>,
},
Tree {
nodes: Vec<TreeNode>,
item_keys: Vec<String>,
selected_index: i32,
visible_rows: u32,
expanded_keys: Vec<String>,
checkable: bool,
key: Option<String>,
},
Text {
value: String,
cursor_byte: i32,
focused: bool,
label: String,
placeholder: Option<String>,
rows: u32,
field_width: u32,
max_visible_chars: u32,
full_width: bool,
completions: Vec<CompletionItem>,
completions_visible_rows: u32,
key: Option<String>,
},
LabeledSection {
label: String,
child: Box<WidgetSpec>,
width_pct: Option<u32>,
key: Option<String>,
},
WindowEmbed {
window_id: u32,
rows: u32,
key: Option<String>,
},
Raw {
entries: Vec<TextPropertyEntry>,
key: Option<String>,
},
Overlay {
child: Box<WidgetSpec>,
key: Option<String>,
},
}Expand description
Declarative widget tree. Each variant is one node; nested
composition is via Row { children } / Col { children }.
key is the stable identifier used by the reconciler to match a
node across MountWidgetPanel / UpdateWidgetPanel calls — when
the plugin re-emits a Spec, instance state (cursor offset, scroll,
expanded keys, hover) is preserved on nodes whose key matches.
Plugins should provide stable keys for any widget that owns
instance state; stateless widgets (HintBar, Toggle, Button,
Spacer) can omit it.
Variants§
Row
Horizontal layout: children laid out left-to-right.
Col
Vertical layout: children stacked top-to-bottom.
HintBar
Keyboard-hint footer (one row, comma-separated <keys> <label> items).
Toggle
Boolean toggle, rendered as [v] label / [ ] label. The
focused flag controls the focus-styling overlay; the host
will own focus once the keymap layer is wired (today the
plugin passes it explicitly per render).
Button
Action button, rendered as [ Label ] (or [ Label ] with
emphasized styling for Primary/Danger). Focused buttons
flip foreground/background using the active menu theme keys.
intent is the button’s visual role (Normal / Primary /
Danger); the field is named intent rather than kind
because kind is the discriminator for the outer WidgetSpec
tag.
Fields
intent: ButtonKinddisabled: boolWhen true, the button renders in a muted style, is dropped from the Tab cycle, and clicks on it are ignored. Use for actions that aren’t currently available against the surrounding state (e.g. “Archive” on the base session). The button still occupies its layout cell so the surrounding row doesn’t reshuffle when the disabled flag flips.
Spacer
Horizontal whitespace eater. In a Row, produces cols
spaces (or fills remaining width if flex: true); in a
Col, produces cols blank lines (flex is ignored).
flex: true distributes the row’s leftover width — panel width - sum(non-flex child widths) — across flex spacers.
With multiple flex spacers in one row the leftover splits
evenly. With no leftover (children already exceed panel
width), the flex spacer collapses to zero.
List
Vertical list of pre-rendered rows with host-managed selection styling, click routing, and virtual scrolling.
The plugin passes the full dataset of items + a
visible_rows count (typically the panel’s available
height). The host owns the scroll offset as widget instance
state, keyed by the spec’s key — so a key is required for
any List that should preserve scroll across re-renders. The
scroll offset auto-clamps to keep selected_index in view;
plugins never compute scroll math.
Each item is one rendered row (TextPropertyEntry).
item_keys is a parallel array of stable per-item identifiers
the plugin uses to map a click event back to its model
(e.g. "file:5/match:23"); the array length must match
items.len(). Missing keys default to empty string.
selected_index is the absolute index into items
(-1 for no selection); the host paints the selected row
with ui.menu_active_bg extended to line end. Clicks fire
widget_event { event_type: "select", payload: { index, key } }
where index is the absolute (not visible-window) index.
Fields
items: Vec<TextPropertyEntry>visible_rows: u32Number of rows of the panel’s available height the list should occupy. Plugin computes from its viewport. The host shows up to this many items per render.
focusable: boolWhether Tab / Shift+Tab will land focus on this
list. Defaults to true (lists are normal tabbable
widgets). Picker-style usage typically sets this to
false so Tab moves between the filter input and
the action buttons, while Up/Down on the focused
filter still forwards to the list via host smart-key
dispatch.
Tree
Hierarchical list with host-managed expand/collapse, selection styling, click routing, and virtual scrolling.
The plugin emits its tree as a depth-first flat list of
TreeNodes (each carrying a depth and has_children flag)
plus a parallel item_keys array. The host filters out
descendants of collapsed nodes when rendering the visible
window, so the plugin always emits the full tree — toggling
expansion is host-owned (instance state) rather than the
plugin re-emitting on every ▶/▼ press.
expanded_keys is initial-only (seeded into instance state
on first render); subsequent expansion changes flow through
WidgetCommand::Key (Right/Left) or click on the disclosure
glyph — neither requires the plugin to re-emit. Plugins that
need to react to expansion changes listen for
widget_event { event_type: "expand" }.
selected_index is the absolute index into nodes
(initial-only; instance state takes over). Click on a row
fires widget_event { event_type: "select", payload: { index, key } }; click on the disclosure column fires
widget_event { event_type: "expand", payload: { index, key, expanded } }. Enter/Space on the focused tree fires
widget_event { event_type: "activate", payload: { index, key } }.
Fields
expanded_keys: Vec<String>Initial-only set of expanded item keys. Once the widget
has rendered, the host’s instance-state expanded_keys
is authoritative; updating this field on subsequent specs
has no effect (use WidgetMutation::SetExpandedKeys to
override host state).
checkable: boolWhen true, every node with checked: Some(_) renders a
[v] / [ ] glyph and emits a toggle hit area over
the glyph. Click on the glyph fires widget_event { event_type: "toggle", payload: { key, checked: <new> } };
the plugin updates its model and pushes the new state
back via WidgetMutation::SetCheckedKeys.
Text
Single-line text input, rendered as [value] with a cursor
highlight at the byte position given by cursor_byte (when
cursor_byte >= 0). When value is empty and the input is
not focused, placeholder (if set) is shown instead.
v1 is a render-only widget: the host owns visual cursor
styling and theme-keyed focus, but the plugin still owns the
value string and cursor position. Keystrokes (Backspace,
arrows, character input) flow through the plugin’s existing
defineMode + mode_text_input plumbing; the plugin re-emits
the spec on every change. The keymap-routing layer (host
claims widget keys before the plugin sees them) lands in a
later commit.
Text input — single-line (rows == 1, default) or multi-line
(rows > 1). The host owns value and cursor_byte as
instance state once the widget renders for the first time;
the spec’s values are initial-only.
Single-line vs multi-line behaviour is selected by rows:
rows == 1— renders as[value]with the cursor pinned to a constantfield_width(head-truncate when the value exceeds it).Enteradvances focus (form-like UX).Up/Downare no-ops.Home/Endjump to the start / end of the whole value.rows > 1— renders asrowslines tall (padded with blanks whenvalueis shorter).Enterinserts a newline at the cursor.Up/Downmove between lines (clamped to each line’s column count).Home/Endjump within the current line. The host auto-scrolls vertically to keep the cursor’s line visible.
Smart-key dispatch (WidgetCommand::Key) selects the right
behaviour from rows. Plugins that want a different Enter
binding intercept the key in their own mode binding before
dispatching it through the smart-key router.
label (when non-empty) renders inline before [ for
single-line, and as a row above the editing region for
multi-line. placeholder shows when value is empty and
the field is unfocused (first row only for multi-line).
field_width controls visible column width: 0 = auto-fit
(single-line) or panel width (multi-line). max_visible_chars
is a single-line soft cap applied after the field-width pad
(0 = no cap; ignored when rows > 1).
Fields
value: StringInitial text. Spec value is read at first render only; instance state takes over thereafter.
cursor_byte: i32Initial byte-offset cursor within value. Negative
(encoded as i32 in JSON) means “no cursor” — clamped
to [0, value.len()] host-side.
rows: u32Number of visible rows of editing region. 0 falls back
to 1 (single-line). 1 = single-line behaviour;
>= 2 = multi-line behaviour. See the type-level doc
for the per-mode semantics.
field_width: u32Visible column width. 0 = auto-fit (single-line) or
panel width (multi-line). When set, single-line
head-truncates with … and multi-line tail-truncates
per-line.
max_visible_chars: u32Single-line soft cap on visible chars after the
field_width pad. 0 = no cap. Ignored when rows > 1.
full_width: boolStretch the visible field to fill the available
width of the enclosing container. Overrides
field_width when set: the renderer computes
panel_width - label_overhead - bracket_overhead as
the effective visible width. Multi-line widgets
already fill the panel width by default; this flag is
most useful for single-line inputs inside a
LabeledSection or a flexible row.
completions: Vec<CompletionItem>Optional completion candidates. When non-empty AND
label is non-empty (the chrome trigger), the
renderer paints a popup directly under the input,
inside a unified box: the input’s normal ╰─...─╯
bottom border becomes a dimmed ┄ separator, the
labeled section’s side borders extend down through
the candidate rows, and a single ╰─...─╯ bottom
closes the whole block. Candidates render left-
aligned with the input’s text (the position right
after [), with the host-managed selected index
highlighted.
Smart-key dispatch on a focused Text-with-completions:
Up/Down moves selection (host-internal, no event),
Tab fires completion_accept with the selected
candidate, Enter / Escape fire completion_dismiss
(the dispatcher’s normal “Enter focus-advance / Esc
close panel” only runs once the popup is closed).
Plugins push candidates in response to the text
widget’s change event via
WidgetMutation::SetCompletions. An empty items
closes the popup.
completions_visible_rows: u32How many candidate rows the popup paints at once
when it opens. Excess candidates stay reachable
via Up/Down (host auto-scrolls to keep selection
in view) or the mouse wheel; a thumb glyph paints
in the right edge of the popup whenever there’s
more to scroll. 0 (default) falls back to 5.
LabeledSection
Visual grouping container: renders a rounded thin border
around a single child widget, with label printed as a
top-left legend overlapping the border (HTML <fieldset>
semantics).
Layout (border drawn with ╭─╮│╰─╯):
╭─ Label ──────────────────╮
│ <child rendered content> │
╰──────────────────────────╯Width: the section always occupies the full panel_width
passed down by its parent container. The child is rendered
with panel_width - 4 (two border columns + two padding
columns) so widgets that honour full_width size
themselves to the inner area.
The child can be any single WidgetSpec — typically a
Text input, but a Toggle/Button/nested Col also
works. Focus, hit areas and cursor positions bubble up
from the child unchanged, shifted by the section’s border
offset (1 row down, 2 columns in).
Fields
label: StringLegend text printed in the top border. Empty = no legend (the top border becomes one unbroken line).
child: Box<WidgetSpec>The single wrapped widget. Boxed because WidgetSpec
is recursive.
width_pct: Option<u32>When this section is a Block child of a Row, request
width_pct percent of the row’s panel_width instead
of the equal-split default. Multiple siblings with
width_pct set sum to ≤ 100; the remainder splits
equally among siblings without an explicit width.
Out-of-range values (0 or > 100) fall back to the
equal-split path.
WindowEmbed
Reserve a rectangle in the widget layout for the host to
natively paint the editor Window identified by
window_id. The widget itself renders only blank lines
so subsequent passes (split tree, terminal grids, syntax
highlighting, decorations) can be drawn into the
reserved cells by the existing per-window render path.
rows controls the embed’s height. Width is whatever
the parent container allocates (panel_width for a
direct Col child; the block’s column_width inside a
Row’s horizontal-zip path). Used by Orchestrator’s open
dialog so the preview pane shows a live render of the
highlighted session.
Fields
Raw
Imperative-virtual-buffer escape hatch. The plugin supplies
TextPropertyEntry[] exactly as it would for
setVirtualBufferContent; the host inlines those entries into
the rendered panel without further interpretation. Used during
migration to wrap existing hand-rolled rendering inside a new
widget panel.
Overlay
Float child over the rest of the layout instead of
consuming vertical space. Placed inside a Col, the
overlay anchors at the row it would have occupied if it
were a regular child — but the rows below it DO NOT
shift down. At paint time the overlay is drawn last,
over whatever’s beneath it, like a tooltip / popup.
Use case: dropdown completions, hover popups, transient hints that should appear right next to the focused widget without reflowing the rest of the panel each time they show / hide.
Hit testing: overlays paint on top, so clicks inside an
overlay’s region go to the overlay (not whatever’s
underneath). Tab cycle: the host’s collect_tabbable
walks into the overlay’s child like any other widget;
give the child a key if you want it focusable, or
leave it keyless to keep it out of the cycle.
Implementations§
Source§impl WidgetSpec
impl WidgetSpec
Sourcepub fn children(&self) -> Box<dyn Iterator<Item = &WidgetSpec> + '_>
pub fn children(&self) -> Box<dyn Iterator<Item = &WidgetSpec> + '_>
Iterate this widget’s immediate child specs in declaration
order. Container kinds (Row, Col, LabeledSection)
return their nested children; leaf kinds return an empty
iterator.
Generic tree walkers (focus dispatch, hit-area lookup, scrollable-widget detection, instance-state mutation) call this instead of pattern-matching every container variant by hand, so adding a new container kind is a single update here rather than touching every walker. The box is the price for returning an iterator whose type depends on the variant; the allocation is single-digit-byte and dwarfed by everything else in the dispatch path.
Sourcepub fn children_mut(&mut self) -> Box<dyn Iterator<Item = &mut WidgetSpec> + '_>
pub fn children_mut(&mut self) -> Box<dyn Iterator<Item = &mut WidgetSpec> + '_>
Mutable counterpart of [children]. Same set of container
kinds, same semantics — the iterator yields exclusive
references so walkers that mutate (e.g. set_*_in_spec)
can recurse generically.
Trait Implementations§
Source§impl Clone for WidgetSpec
impl Clone for WidgetSpec
Source§fn clone(&self) -> WidgetSpec
fn clone(&self) -> WidgetSpec
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for WidgetSpec
impl Debug for WidgetSpec
Source§impl<'de> Deserialize<'de> for WidgetSpec
impl<'de> Deserialize<'de> for WidgetSpec
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Source§impl Serialize for WidgetSpec
impl Serialize for WidgetSpec
Source§impl TS for WidgetSpec
impl TS for WidgetSpec
Source§type WithoutGenerics = WidgetSpec
type WithoutGenerics = WidgetSpec
WithoutGenerics should just be Self.
If the type does have generic parameters, then all generic parameters must be replaced with
a dummy type, e.g ts_rs::Dummy or (). The only requirement for these dummy types is that
EXPORT_TO must be None. Read moreSource§type OptionInnerType = WidgetSpec
type OptionInnerType = WidgetSpec
std::option::Option<T>, then this associated type is set to T.
All other implementations of TS should set this type to Self instead.Source§fn docs() -> Option<String>
fn docs() -> Option<String>
TS is derived, docs are
automatically read from your doc comments or #[doc = ".."] attributesSource§fn decl_concrete(cfg: &Config) -> String
fn decl_concrete(cfg: &Config) -> String
TS::decl().
If this type is not generic, then this function is equivalent to TS::decl().Source§fn decl(cfg: &Config) -> String
fn decl(cfg: &Config) -> String
type User = { user_id: number, ... }.
This function will panic if the type has no declaration. Read moreSource§fn inline(cfg: &Config) -> String
fn inline(cfg: &Config) -> String
{ user_id: number }.
This function will panic if the type cannot be inlined.Source§fn inline_flattened(cfg: &Config) -> String
fn inline_flattened(cfg: &Config) -> String
Source§fn visit_generics(v: &mut impl TypeVisitor)where
Self: 'static,
fn visit_generics(v: &mut impl TypeVisitor)where
Self: 'static,
Source§fn output_path() -> Option<PathBuf>
fn output_path() -> Option<PathBuf>
T should be exported, relative to the output directory.
The returned path does not include any base directory. Read moreSource§fn visit_dependencies(v: &mut impl TypeVisitor)where
Self: 'static,
fn visit_dependencies(v: &mut impl TypeVisitor)where
Self: 'static,
Source§fn dependencies(cfg: &Config) -> Vec<Dependency>where
Self: 'static,
fn dependencies(cfg: &Config) -> Vec<Dependency>where
Self: 'static,
Source§fn export(cfg: &Config) -> Result<(), ExportError>where
Self: 'static,
fn export(cfg: &Config) -> Result<(), ExportError>where
Self: 'static,
TS::export_all. Read moreSource§fn export_all(cfg: &Config) -> Result<(), ExportError>where
Self: 'static,
fn export_all(cfg: &Config) -> Result<(), ExportError>where
Self: 'static,
TS::export. Read more