Declarative UI
Plugins never receive the ImGui context. They send a JSON layout to the Host; the Plugin Manager renders it with the Shire ImGui context and sends interaction notifications back to the plugin.
Layout
ui.setLayout replaces the plugin's layout:
{
"layout": [
{"type": "text", "id": "title", "text": "Player Information"},
{"type": "section", "id": "pos_section", "label": "Position"},
{"type": "text", "id": "x", "text": "X 123.5"},
{"type": "toggle", "id": "enabled", "label": "Enabled", "value": true},
{"type": "slider", "id": "speed", "label": "Speed", "value": 5, "min": 0, "max": 10},
{"type": "select", "id": "mode", "label": "Mode", "options": ["A", "B"], "value": "A"},
{"type": "input", "id": "name", "label": "Name", "value": "text"},
{"type": "progress", "id": "progress", "label": "Progress", "text": "50%", "value": 0.5},
{"type": "button", "id": "refresh", "label": "Refresh"},
{"type": "separator", "id": "sep"}
]
}
Components
| Type | Required fields | Notes |
|---|---|---|
text |
id, text |
wraps in the manager |
section |
id, label |
separator + heading |
separator |
none (id optional) | thin divider |
button |
id, label |
sends a UI event when clicked |
toggle |
id, label, value (bool) |
sends the new boolean |
slider |
id, label, value, min, max |
sends the new number while dragging |
input |
id, label, value |
sends on edit completion |
select |
id, label, options, value |
sends the selected string |
progress |
id, label, value (0..1), optional text |
display only |
Ids are limited to 64 characters from [A-Za-z0-9._-]; duplicate ids are
rejected. Layouts are capped at 64 components and 16 KiB of label/text/value
data. Text is rendered, never executed.
Events
When the user interacts, the Host sends:
{
"jsonrpc": "2.0",
"method": "shire.uiEvent",
"params": {"component": "enabled", "type": "toggle", "value": true}
}
The SDKs expose on_ui_event (Python), onUiEvent (TypeScript/C#) or
poll_event/run (C++/Rust). A handler can call ui.setLayout or
ui.update to refresh the screen.
def on_ui(component, kind, value):
if component == "enabled":
settings["enabled"] = bool(value)
plugin.set_ui(build_layout())
plugin.on_ui_event(on_ui)
Updating one component
ui.update avoids resending the whole layout:
{"id": "progress", "value": 0.75}
The Host updates the component in its copy. The manager reads the updated snapshot on the next frame.
Where it appears
Open the manager with F9 or Config → Plugins → Open Plugin Manager.
Select a plugin; its layout appears under Plugin UI. The manager window
uses the same ImGui context, fonts and theme as the rest of Shire. It is a
normal ImGui window, so it is included in the input-capture rects like the main
menu.
Limits and behavior
- A plugin can have one layout at a time.
- A layout update is validated and copied; a malformed component rejects the
whole update with
-32602. - A plugin that is stopped has its layout removed from the manager when the record is removed; disabled plugins keep their saved layout in memory.
- The manager is the only renderer. A plugin cannot draw arbitrary ImGui, create windows, capture global input or access textures.
- If a plugin needs a control that is not listed, it can combine existing components or request a new native operation through the Host roadmap.