API_UI - frankischilling/crust GitHub Wiki
UI API
c2.ui exposes retained declarative plugin UI.
Crust renders the UI in Rust/egui. Lua only describes surfaces and reacts to events.
Namespace
c2.ui.register_window(spec)
c2.ui.update_window(id, spec)
c2.ui.open_window(id)
c2.ui.close_window(id)
c2.ui.unregister_window(id)
c2.ui.register_settings_page(spec)
c2.ui.update_settings_page(id, spec)
c2.ui.unregister_settings_page(id)
c2.ui.register_host_panel(spec)
c2.ui.update_host_panel(id, spec)
c2.ui.unregister_host_panel(id)
Surfaces
Window spec
{
id = "showcase",
title = "Plugin Window",
open = true,
resizable = true,
scroll = false,
default_width = 520,
default_height = 420,
min_width = 320,
min_height = 220,
max_width = 900,
max_height = 700,
style = { ... },
children = { ... }
}
Rules:
- Only
idis required. All other window-spec fields are optional. titledefaults toid.opendefaults totrue.resizabledefaults totrue.scrolldefaults tofalse.default_width,default_height,min_width,min_height,max_width, andmax_heightare optional numeric window bounds and size hints.styleis optional.max_widthandmax_heightare window bounds, not style-table hints.childrenis the retained widget tree and defaults to an empty list when omitted.- Each plugin only mutates its own windows.
Settings-page spec
{
id = "demo_settings",
title = "Settings Demo",
summary = "Optional short summary shown in Settings",
style = { ... },
children = { ... }
}
Rules:
- Only
idis required. All other settings-page fields are optional. titledefaults toid.summaryis optional.styleis optional.childrendefaults to an empty list when omitted.- Settings pages appear inside the shared Plugins area of the Settings window.
Host-panel spec
{
id = "appearance_tools",
slot = "settings.appearance",
title = "Plugin Controls",
summary = "Optional short description for the plugin-owned block",
order = 100,
style = { ... },
children = { ... }
}
Rules:
idandslotare required. All other host-panel fields are optional.slotmust be one of the documented host extension points.titleis optional; when present, Crust renders it as the block heading.summaryis optional helper text under the heading.orderdefaults to0; lower numbers render earlier within the same slot.styleis optional.childrendefaults to an empty list when omitted.- Host panels render as separate plugin-owned blocks inside stable Crust UI slots.
Supported slot values:
settings.integrationssettings.appearancesettings.chatsidebar.topchannel_header
Functions
c2.ui.register_window(spec)
Registers or replaces one floating plugin-owned window.
c2.ui.update_window(id, spec)
Replaces the retained window with id id. The first argument is the source of
truth for the window id.
c2.ui.open_window(id)
Marks a registered window as open. Unknown window ids are ignored.
c2.ui.close_window(id)
Marks a registered window as closed. Unknown window ids are ignored.
c2.ui.unregister_window(id)
Removes a retained window.
c2.ui.register_settings_page(spec)
Registers or replaces one plugin-owned settings page.
c2.ui.update_settings_page(id, spec)
Replaces the retained settings page with id id. The first argument is the
source of truth for the settings-page id.
c2.ui.unregister_settings_page(id)
Removes a retained settings page.
c2.ui.register_host_panel(spec)
Registers or replaces one plugin-owned host panel in a named Crust UI slot.
c2.ui.update_host_panel(id, spec)
Replaces the retained host panel with id id. The first argument is the source
of truth for the host-panel id.
c2.ui.unregister_host_panel(id)
Removes a retained host panel.
Widget Schema
Every widget is a plain Lua table. type is required; the rest of the common
fields are optional unless a widget-specific note says otherwise.
{
type = "button",
id = "save",
title = "Optional heading/title text",
text = "Optional display text",
action = "optional_action_name",
url = "https://example.invalid",
placeholder = "placeholder text",
value = "string|boolean|number|array-of-strings",
progress = 0.5,
min = 0,
max = 100,
step = 1,
rows = { ... },
children = { ... },
options = { ... },
items = { ... },
columns = { ... },
form_key = "optional_form_field_name",
host_form = true,
submit = false,
open = true,
style = { ... }
}
Common widget notes:
- Unknown widget types are ignored.
idis optional globally, but strongly recommended for action routing and callback handling.children,rows,options,items, andcolumnsare only used by the widget types that declare them.openis only used by widgets such ascollapsible.
Layout widgets
column: vertical stack ofchildrenrow: wrapped horizontal layout ofchildrengroup: framed container forchildrencard: same retained schema asgroup, styled as a framed cardgrid: grid layout forchildrenscroll: vertical scroll area aroundchildrenseparator: horizontal separator linespacer: vertical gap; usesstyle.heightorstyle.min_heightcollapsible: collapsible section;titleortextis the header
Display widgets
text: plain label textheading: larger section headinglabel: same display path astextbadge: filled badge/pill labelimage: image URL fromurlorstyle.image_urlprogress: progress bar fromprogressor numericvalue
Action widgets
buttonicon_buttonlink_button
Action widget notes:
idis strongly recommended.actionis optional but useful for routing callback logic.submit = trueemitsPluginUiSubmitinstead ofPluginUiAction.link_buttonusesurlfor the destination and still emits a plugin UI event.icon_buttoncurrently uses the same renderer asbutton.style.iconis currently treated as fallback button text, not a separate icon slot.
Input widgets
text_inputtext_areapassword_inputcheckboxtoggleradio_groupselectslider
Input widget notes:
radio_groupandselectuseoptions.slideruses numericmin,max, and optional positivestep.valueis the controlled value whenhost_formis false.idorform_keyis required if you expect change or submit payloads to be useful.
Structured display widgets
list: usesitemstable: usescolumnsandrows
Nested Value Tables
options
{
label = "Ocean",
value = "ocean",
description = "Optional help text"
}
items
String shorthand is allowed:
items = { "alpha", "beta" }
Full form:
{
label = "Clicks",
value = "3",
note = "Optional note"
}
columns
{
id = "field",
title = "Field",
align = "left"
}
rows
table.rows is an array of arrays. Each cell may be a string, boolean,
number, or array of strings.
Style Table
style is accepted by surfaces and widgets. All style fields are optional.
{
visible = true,
enabled = true,
width = 240,
height = 32,
min_width = 120,
min_height = 24,
max_width = 640,
max_height = 480,
padding = 8,
align = "left|center|right",
text_role = "muted",
emphasis = "strong|bold|small",
border_color = "#RRGGBB",
fill_color = "#RRGGBB" or "#RRGGBBAA",
severity = "info|success|warning|danger|error",
icon = "optional icon text",
image_url = "https://example.invalid/image.png"
}
Current host behavior:
visiblehides windows, settings pages, and widgets whenfalseenableddisables interactive widgets and can disable all children of a window or settings pagewidthandheightare currently used as rendering hints for text inputs, buttons, images, progress bars, and spacersmin_heightis used byspacer;min_width/min_heightwindow bounds live on the window spec insteadmax_widthandmax_heightinsidestyleare currently accepted but do not usually affect widget layoutpadding,align,border_color, and table-columnalignare currently accepted but may not affect renderingfill_color/severitycurrently affect grouped containers, settings-page frames, badges, and styled texttext_role/emphasisaffect text rendering- unsupported hints are ignored safely
State Model
Controlled mode
When host_form is omitted or false:
- Lua owns the widget value through
value - Crust emits
PluginUiChange - if the plugin does not re-register the updated retained value, the widget falls back to the old retained value on the next render
- the plugin should call
c2.ui.update_window(...)orc2.ui.update_settings_page(...)with the new retained value
Host-form mode
When host_form = true:
- Crust stores transient field values for that surface
idorform_keyidentifies the form field- change events still fire
- submit/action events include
form_values - only host-form fields are included in
form_values - transient host-form state is cleaned up when the surface disappears
UI Events
See API_Events.md for the callback payload reference.
Plugin UI callback payloads use surface_kind, surface_id, widget_id, and
form_values consistently across action, change, and submit events.
surface_kind is currently:
windowsettings_pagehost_panel
The UI-related event kinds are:
c2.EventType.PluginUiActionc2.EventType.PluginUiChangec2.EventType.PluginUiSubmitc2.EventType.PluginUiWindowClosed
Example
c2.ui.register_window({
id = "demo",
title = "Demo Window",
open = false,
children = {
{ type = "heading", text = "Hello UI" },
{
type = "text_input",
id = "name",
form_key = "name",
host_form = true,
placeholder = "Name"
},
{
type = "button",
id = "save",
text = "Save",
action = "save",
submit = true
}
}
})
c2.register_command("uidemo", function(ctx)
c2.ui.open_window("demo")
end)
c2.register_callback(c2.EventType.PluginUiSubmit, function(ev)
if ev.surface_id ~= "demo" then
return
end
c2.add_system_message("system", "Saved name: " .. tostring(ev.form_values.name or ""))
end)
For fuller examples, see
plugins/ui_window_showcase_plugin/init.lua
and
plugins/ui_settings_demo_plugin/init.lua,
plus
plugins/ui_host_panels_demo_plugin/init.lua.