# UI_WORKFLOW — UMG/Slate UI authoring guide for AI agents > **READ THIS BEFORE EDITING ANY `WBP_*` / UMG / SLATE / UI.** > If you start a UI task without following this file, you will produce a generic > placeholder ("prototype look"). This document is the contract that turns > "ugly UMG demo" into "shippable UI". ## 0. Quick decision flow ``` user asks for UI work │ ├─ Is there a DA_UITheme asset in /Game/UI/Theme/ ? ── no ─→ STEP A. Build theme first. │ (one-time setup, ~5 min) ├─ Are there base components in /Game/UI/Components/ ? ── no ─→ STEP B. Build component library. │ ├─ Did the user give a reference image / mood / brand? ── no ─→ STEP C. Ask for one before coding. │ └─ All three present? ─→ STEP D. Compose screen → screenshot → iterate. ``` You **must not** invent palettes, spacing, or component shapes on the fly. If a token is missing, you ADD it to the theme. If a component is missing, you CREATE it in the library. The screen WBP only composes. --- ## STEP A — The Theme (one DataAsset to rule them all) ### A.1 Class Create C++ DataAsset `UProjectUITheme : UPrimaryDataAsset` (use `cpp_scaffold_op`): ```cpp USTRUCT(BlueprintType) struct FProjectUIPalette { GENERATED_BODY() UPROPERTY(EditAnywhere) FLinearColor Primary = FLinearColor::White; UPROPERTY(EditAnywhere) FLinearColor Accent = FLinearColor(1, 0.5, 0.1, 1); UPROPERTY(EditAnywhere) FLinearColor Danger = FLinearColor::Red; UPROPERTY(EditAnywhere) FLinearColor Surface = FLinearColor(0.05, 0.05, 0.05, 1); UPROPERTY(EditAnywhere) FLinearColor SurfaceAlt = FLinearColor(0.1, 0.1, 0.1, 1); UPROPERTY(EditAnywhere) FLinearColor Ink = FLinearColor(0.9, 0.9, 0.85, 1); UPROPERTY(EditAnywhere) FLinearColor Muted = FLinearColor(0.5, 0.5, 0.45, 1); }; USTRUCT(BlueprintType) struct FProjectUIType { GENERATED_BODY() UPROPERTY(EditAnywhere) FSlateFontInfo H1; // 48pt display UPROPERTY(EditAnywhere) FSlateFontInfo H2; // 32pt headings UPROPERTY(EditAnywhere) FSlateFontInfo Body; // 16pt text UPROPERTY(EditAnywhere) FSlateFontInfo Caption;// 12pt small UPROPERTY(EditAnywhere) FSlateFontInfo Mono; // monospace / terminal text }; UCLASS() class PROJECTUI_API UProjectUITheme : public UPrimaryDataAsset { GENERATED_BODY() public: UPROPERTY(EditAnywhere, Category="Theme") FProjectUIPalette Palette; UPROPERTY(EditAnywhere, Category="Theme") FProjectUIType Type; // Spacing rhythm — use index, not raw px. UPROPERTY(EditAnywhere, Category="Theme") TArray Spacing = {4, 8, 12, 16, 24, 32, 48, 64}; UPROPERTY(EditAnywhere, Category="Theme") float CornerRadius = 4.f; UPROPERTY(EditAnywhere, Category="Theme") float BorderWeight = 1.f; // Named brushes — referenced by their FName key from WBPs. UPROPERTY(EditAnywhere, Category="Theme") TMap Brushes; }; ``` ### A.2 Instance Create instance at `/Game/UI/Theme/DA_UITheme` (one per visual mode — fork as `DA_UITheme_` if a project needs several). Example tuning: | Token | Value | Why | |---|---|---| | Palette.Surface | `(0.04, 0.05, 0.05, 1)` | near-black base | | Palette.SurfaceAlt | `(0.09, 0.1, 0.09, 1)` | row striping | | Palette.Ink | `(0.87, 0.91, 0.83, 1)` | off-white, never pure | | Palette.Accent | `(0.95, 0.62, 0.21, 1)` | amber highlight | | Palette.Danger | `(0.78, 0.10, 0.10, 1)` | muted red, not hex red | | Palette.Muted | `(0.45, 0.50, 0.45, 1)` | green-grey | | Type.H1 | font `Roboto Mono Bold`, 48, letter spacing +1 | display | | Type.Body | font `Inter Regular`, 16 | readable body | | CornerRadius | 0 | sharp / brutalist | | BorderWeight | 1.5 | thicker → more readable | ### A.3 Access Every WBP holds `UPROPERTY(EditDefaultsOnly) UProjectUITheme* Theme;` set in Class Defaults to `DA_UITheme`. No hardcoded colors anywhere. If you catch yourself typing `FLinearColor(0.7, 0.1, 0.1, 1)` — stop, add a token to the theme instead. --- ## STEP B — Component Library Build these once, reuse forever. All under `/Game/UI/Components/`. ### Required base set | Component | Purpose | Variants | |---|---|---| | `WBP_Button` | All clickable | Primary, Ghost, Danger, Icon-only | | `WBP_Card` | Container w/ header/body/footer | Flat, Raised, Outlined | | `WBP_HUDPanel` | Framed panel (corner brackets, overlay channel) | Top, Bottom, Floating | | `WBP_StatBar` | Numeric stat bar (health/energy/progress) | Horizontal, Vertical, Circular | | `WBP_Label` | Text wrapper that auto-picks Type token | H1, H2, Body, Caption, Mono | | `WBP_Divider` | 1px line | Solid, Dashed, Glitchy | | `WBP_Icon` | Wraps `Image` with theme tint | size=spacing token | | `WBP_ListRow` | One row of a list, themed | Selected/Hovered states wired | | `WBP_Modal` | Centered overlay with backdrop | Confirm, Form, Info | | `WBP_Toast` | Bottom-slide notification | Info, Warn, Error | ### Authoring rules - **No hardcoded sizes** — use `Theme->Spacing[idx]` (e.g. padding = `Spacing[3]` = 16px). - **Every interactive widget has 4 states wired**: Normal, Hovered, Pressed, Disabled. Hooked from theme brushes, not duplicated. - **Each component exposes `SetVariant(EVariant)` and `SetState(EState)` BP-callable funcs** — so the screen WBP composes by setting flags, not by hand-tweaking child widgets. - **Animation:** every state transition has a 80-150ms ease. Define one `WidgetAnimation` per state in `WBP_Button`, copy pattern to others. No animation = feels dead. ### Optional stylized overlays - `WBP_HUDPanel` can expose a Niagara/Material overlay channel — bind project-specific material instances (e.g. `MI_UI_Scanline`, `MI_UI_Noise`) as defaults if your project has them. - `WBP_Label` Mono variant can use a chromatic-aberration material instance. - Background of every HUD piece: prefer a themed `MI_UI_Surface` material over a flat solid color when a stylized look is wanted. --- ## STEP C — Reference parsing protocol When user supplies a reference image, **do not** start coding. First emit: ``` REFERENCE TOKENS ================ Palette (6 colors HEX, ordered by dominance): 1. Primary surface: #__ 2. Surface alt: #__ 3. Ink (text): #__ 4. Accent: #__ 5. Danger/warning: #__ 6. Muted/secondary: #__ Typography: H1 size: __pt weight: __ tracking: __ Body size:__pt weight: __ Family hint: __ (serif/sans/mono/condensed) Spacing rhythm (3 visible gaps in px): __ __ __ → suggested Spacing array: [__, __, __, __, __, __, __, __] Corner radius observed: __px (0 = sharp / brutalist) Border weight observed: __px Visual hierarchy (one sentence): __ Mood (3 adjectives): __, __, __ Departures from current DA_UITheme: - Theme.Palette.__ should become #__ (was #__) - Theme.Spacing[__] should become __ (was __) ``` Then ask the user: "apply to existing theme or fork as `DA_UITheme_`?" Once approved, use `audio_op set_property` / direct asset edits to mutate the theme DataAsset. Never edit individual WBPs to match a ref — change the theme, all WBPs follow. --- ## STEP D — The Vision Loop (most important) This is what makes AI UI not suck. After ANY structural change, you MUST: ``` 1. widget_op apply_tree / set_property / ... (your edit) 2. widget_op compile_save {bp_path} 3. widget_op screenshot {bp_path, size: [1920, 1080]} 4. Read the returned png_path. If you are a vision-capable model: open the file, look at the result, list 3 specific problems before next edit. 5. Loop until no problems remain. ``` ### Hard rule - **Maximum 2 blind edits** (without screenshot) per session. After that, MUST screenshot. If a user reports "it looks bad", your first action is `widget_op screenshot`, not asking for clarification. - **Before declaring "done"**, last action MUST be a screenshot, and you MUST describe what you see (not what you intended). - The screenshot landing path is `/Saved/MCP/widgets/.png`. Always attach this to your turn output. ### What to look for in the screenshot - **Alignment grid** — are baselines and gutters consistent? Use a mental 8px grid. - **Hierarchy** — can you ID primary action in <1s by squinting? - **Density** — is it crammed (Unity-tutorial vibe) or breathing? - **Color count** — is anything outside the theme palette? If yes, that's a bug. - **Edge cases** — empty list, longest possible label (16 chars), worst-case localization (German is +30%). --- ## STEP E — Screen composition When user asks for a new screen (`WBP_MainMenu`, `WBP_HUD_InGame`, ...): 1. Sketch hierarchy in text first: ``` WBP_MainMenu (RootCanvas) WBP_HUDPanel "title-bar" (anchored top, span) WBP_Label H1 "GAME TITLE" WBP_Card "menu-list" (centered, 480x600) WBP_Button Primary "Continue" WBP_Button Ghost "New Game" WBP_Button Ghost "Settings" WBP_Button Danger "Quit" WBP_Label Caption "v0.4.2 • Build 2026-05-28" (bottom-left) ``` 2. Confirm with user (one screen = one confirmation step, not per widget). 3. Build with `widget_op apply_tree` (single transaction, single undo). 4. Compile + screenshot. 5. Iterate per Step D loop. --- ## Naming & paths (enforced) | Asset | Path | Naming | |---|---|---| | Theme DataAsset | `/Game/UI/Theme/` | `DA_UITheme_*` | | Base components | `/Game/UI/Components/` | `WBP_` | | Screens | `/Game/UI/Screens/` | `WBP_` (no prefix like "Menu_" — flat) | | Materials | `/Game/UI/Materials/` | `M_UI_*`, `MI_UI_*` | | Icons | `/Game/UI/Icons/` | `T_UI_Icon__` | | Frame brushes | `/Game/UI/Frames/` | `T_UI_Frame_*` | If asked to create UI assets outside these paths, ask why. Probably a mistake. --- ## Things NEVER to do (anti-patterns AI agents fall into) 1. ❌ Hardcoded color `FLinearColor(0.2, 0.2, 0.2)` anywhere. → Use theme. 2. ❌ Hardcoded padding `Padding=8`. → Use `Theme->Spacing[1]`. 3. ❌ One giant CanvasPanel screen with absolute coords for everything. → Use HorizontalBox/VerticalBox/Grid composition. 4. ❌ "Just add a `Border` widget" for a card. → Use `WBP_Card` component. 5. ❌ Per-screen unique button styling. → Add a variant to `WBP_Button`. 6. ❌ Default UE font (Roboto). → Always pick from `Theme->Type`. 7. ❌ Default `Button` widget visible to the user. → Always wrap in `WBP_Button` because state animations live there. 8. ❌ Skip Step D. → If you didn't screenshot, you didn't finish. 9. ❌ Generate icons inline as text "≡" or "✕". → Use `WBP_Icon` with a real Texture2D. --- ## Required first action for any UI session ``` discover_op find_bp_by_parent { parent_class_path: "/Script/ProjectUI.ProjectUITheme" } ``` - 0 hits → STEP A (build theme). - 1+ hits → fetch the `DA_UITheme` palette via `validation_op get_references` or read directly, cache token names in your working memory. ``` discover_op find_bp_by_parent { parent_class_path: "/Script/UMG.UserWidget" } ``` List existing components. If `/Game/UI/Components/WBP_Button` missing → STEP B. Then and only then proceed to the user's actual request. --- ## Why this works Without this guide, AI generates UMG by sampling "what UMG typically looks like on GitHub" — boring, generic, grey. With this guide: - **Decisions are constrained** (theme tokens, component variants) → no more "AI inventing aesthetics from scratch every time". - **Composition replaces design** → LLMs are great at composing; bad at painting. - **Vision loop closes the feedback gap** → AI sees its output, judges it, fixes it. - **References parse to data**, not vibes → reproducible, debuggable, diffable. Result: shippable UI in 1-2 iterations instead of 15.