Architecture overview
How SUI Designer is wired internally — from .sui file to running ScreenPanel.
Table of contents
The big picture
┌─────────────────┐ asset open ┌──────────────────────┐
│ .sui file │ ───────────────▶│ SuiAsset │
│ (JSON on disk) │ asset save │ (GameResource) │
└─────────────────┘ ◀───────────────└──────┬───────────────┘
│ Document
▼
┌──────────────────────────┐
│ SuiDesignerController │
│ • Document │
│ • Selection │
│ • Dirty flag │
│ • Command stack │
└────┬─────────────────────┘
│
┌───────────────────────────────┼───────────────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Hierarchy │ │ Canvas │ │ Details │
│ widget │ │ widget │ │ widget │
└─────────────┘ └──────┬───────┘ └─────────────┘
│
│ paint
▼
┌─────────────────────┐
│ SuiCanvasRenderer │ ─── Editor.Paint (Qt)
│ + SuiLayoutSolver │
│ + SuiFlexLayout │
└─────────────────────┘
│ Ctrl+B
▼
┌─────────────────────┐
│ SuiGenerationPipeline│
│ → Razor + SCSS │
└──────┬──────────────┘
│
┌───────────┴────────────┐
▼ ▼
┌───────────────────┐ ┌─────────────────────┐
│ SuiCompileWriter │ │ SuiPreviewCacheWriter│ ── Test in Play
│ → Code/UI/ │ │ → Code/_sui_preview/ │
└───────────────────┘ └─────────────────────┘
Subsystems
Persistence (Code/Runtime/)
.sui files are persisted via SuiAsset — a Sandbox GameResource. The asset holds a single SuiDocument which is the in-memory representation: a header (id, name, schema version), a list of SuiElement nodes (flat with ParentId), and per-document settings (canvas size, output folder, generated-file manifest).
Read more: Document model.
Controller (Editor/SuiDesignerController.cs)
Single source of truth while a document is open. Owns:
- The active
SuiDocument - The selection (single + multi-set)
- The dirty flag
- The command stack (
SuiCommandStack)
Widgets read state via the controller and mutate it ONLY through Execute(ISuiCommand). This keeps undo, dirty-tracking, and event propagation coherent — no widget directly pokes another widget. Events the controller fires:
DocumentChanged— document was loaded/swappedSelectionChanged— selection set or primary changedDirtyChanged— dirty flag flippedCommandsChanged— undo/redo state changed
Commands (Editor/Commands/)
Every mutation against the document is an ISuiCommand. The stack stores Apply/Undo pairs so Ctrl+Z works for free. See Undo / Redo and commands.
Widgets (Editor/Widgets/)
UI components that the designer window docks together. Each widget reads from the controller and emits commands. Notable widgets:
SuiHierarchyWidget— left treeSuiPaletteWidget— palette of element typesSuiCanvasWidget— canvas viewport + interactionSuiDetailsWidget— right inspectorSuiCompileResultsWidget— bottom tab showing generator outputSuiCodeTabWidget— code preview tab (Razor + SCSS read-only)
Canvas (Editor/Canvas/)
The visual surface. Two layers:
- Layout —
SuiLayoutSolver(Absolute / Anchor / Pivot math) +SuiFlexLayout(flex container math). Produces a finalRectfor every element relative to the canvas. - Renderer —
SuiCanvasRendererwalks the resolved tree and paints viaEditor.Paint(Qt). Independent of the runtime CSS render — uses the same SCSS values but its own implementation.
Read more: Canvas renderer · Layout solver.
Generator (Code/Generation/)
Pure functions: take a SuiDocument + SuiGenerationMode, return an in-memory SuiGenerationResult (Razor string + SCSS string + diagnostics). Does not touch the filesystem.
Two modes:
Final— emits@import "<ClassName>.User.scss"at the bottom. For real compile.Preview— no User import. For Test in Play.
Read more: Generator pipeline.
Compile writer (Editor/SuiCompileWriter.cs)
Takes the generator result and writes to disk under the user-chosen output folder. Owns:
- File ownership via
SUI:GENERATEDheader - The User.scss sidecar (created once, never overwritten)
- Backups under
.sui-backups/outsideCode/ - The manifest at
.sui-manifest/<DocumentId>.json
Read more: Compile writer.
Preview system (Editor/SuiPreviewLauncher.cs + Code/Runtime/SuiPreview*.cs)
Test in Play workflow:
- Generator runs in
Previewmode. SuiPreviewCacheWriterwrites toCode/_sui_preview/<ClassName>/.- Engine hot-reloads → new
PanelComponenttype registered inTypeLibrary. - Launcher polls for the type, sets
SuiPreviewState.PendingTypeFullName. - Opens
sui_preview/preview_stage.sceneand plays it. SuiPreviewMount(in-scene component) reads the state, creates a ScreenPanel, attaches the generated PanelComponent.
Read more: Preview system.
File / namespace map
| Folder | Namespace | Contents |
|---|---|---|
Code/Runtime/ | SboxUiDesigner.Runtime | Document model, asset, validator, enums, preview mount/state |
Code/Generation/ | SboxUiDesigner.Generation | Pipeline, Razor + SCSS emitters, allowed-property list |
Editor/ | SboxUiDesigner.EditorUi | Window, controller, compile writer, preview launcher |
Editor/Canvas/ | SboxUiDesigner.EditorUi.Canvas | Layout solver, flex layout, renderer, viewport, error banner |
Editor/Commands/ | SboxUiDesigner.EditorUi.Commands | Command interface + concrete commands |
Editor/Widgets/ | SboxUiDesigner.EditorUi.Widgets | All dockable widgets |
Editor/Tools/ | SboxUiDesigner.EditorUi.Tools | Tools menu actions (Clean caches, etc.) |
Why this split?
Code/Runtime/is the only namespace allowed to ship in the game build. Anything inEditor/*is gated behind#if EDITORstyle restrictions (Sandbox auto-excludes Editor code from the runtime DLL).Code/Generation/is also runtime-safe — it has no Editor dependencies — so it can be reused if SUI ever needs to generate at runtime (currently it doesn’t, but the boundary is preserved).- The controller owns the document and the command stack. Widgets are thin views over that state. This is what makes undo, dirty tracking, and selection multicast work cleanly.
See also
- Document model — the data
- Generator pipeline —
.sui→ Razor + SCSS - Compile writer — writing to disk safely