Reference · Updated 2552.07.26.14.59
Command Bar — Back & Accept (Hardcoded Approach)
How SystemLink's on-screen prompt strip (◀ Back, Ⓐ Select) is built. Decision (2026-07-20): hardcode it. The bar only ever needs Back and maybe Accept, so we skip CommonUI's dynami
How SystemLink's on-screen prompt strip (◀ Back, Ⓐ Select) is built. Decision (2026-07-20): hardcode it. The bar only ever needs Back and maybe Accept, so we skip CommonUI's dynamic CommonBoundActionBar and build a fixed strip. The dynamic bar is reframed as a MenuKit-product upgrade (see the last section).
#1. The key fact: the bar is display-only
Back and Accept already work without any command bar:
Bcloses the menu viabIsBackHandler(set in theUSLScreenWidgetconstructor).
Aclicks the focused button via CommonUI's default click action.
The command bar changes nothing about behavior — it only shows the player which buttons do what. That's why hardcoding the display is safe: we're not reimplementing input, just drawing two labels + glyphs.
#2. Design: two building blocks
| Asset | Type | Role |
|---|---|---|
WBP_SL_CommandPrompt | UserWidget | One prompt = glyph + label. Reusable, two exposed vars. |
WBP_SL_CommandBar | UserWidget | A HorizontalBox holding the prompts (Back, Accept). |
Use a CommonActionWidget for the glyph — not a plain image or the letter "B". The CommonActionWidget auto-resolves the correct controller glyph for the current gamepad from the input action. That is the one thing worth keeping from CommonUI; everything else (the router, dynamic spawning) we drop.
The command bar is gamepad-only (decision 2026-07-20). On mouse/keyboard the whole prompt strip is
hidden (WBP_SL_CommandBar collapses its prompts when the active device is KBM), so no keyboard glyphs are
needed — there is noCD_SL_KBM. See "Gamepad-only visibility" below. (We briefly authored aCD_SL_KBM
key-cap set, then removed it once the bar was hidden on KBM — the glyphs would never render.)
#Step 1 — Build WBP_SL_CommandPrompt (the reusable prompt)
- Create
WBP_SL_CommandPrompt(/Game/SystemLink/UI/Menus/, parentUserWidget).
- Tree:
HorizontalBox (root)
├─ Glyph [CommonActionWidget]
├─ Spacer (size ~8)
└─ Label [CommonTextBlock] (use Style_… text style to taste)
- Add two instance-editable variables (eye icon open):
InputAction— typeInput Action(object reference, i.e.UInputAction).PromptLabel— typeText.
- In the PreConstruct graph:
Glyph → Set Enhanced Input Action (InputAction)— resolves the glyph brush.Label → Set Text (PromptLabel).
- Compile + Save.
Doing this in PreConstruct means the designer preview updates per instance, and the runtime widget sets
itself up on construction — see the timing note in Step 3.
Why noDT_SL_InputActionsrow?CommonActionWidgetresolves the glyph either from an
EnhancedInputAction(aUInputAction) or from the legacyInputActionsDataTable array — never both.
UCommonActionWidget::GetIcon() is a strict ternary: with enhanced input enabled (it is —
bEnableEnhancedInputSupport=True), a setEnhancedInputActionwins and the DataTable array is ignored.
So we set only theUInputAction;DT_SL_InputActionsis the legacy alternative, not an extra requirement.
(The glyph brush comes fromCD_SL_Xbox; the key comes fromIMC_SL_UI— the DataTable is not in this path.)
If you ever see a prompt with both anInputActionsrow and anEnhancedInputActionset, the row is inert.
#Step 2 — Build WBP_SL_CommandBar (the strip)
- Open
WBP_SL_CommandBar(already exists as the old placeholder — replace its contents).
2. Delete the placeholder TextBlocks (`B BACK | = OPTIONS | R SOCIAL`). |
|---|
- Tree:
HorizontalBox (root, Right-aligned or Fill to taste)
├─ WBP_SL_CommandPrompt "Accept" InputAction = IA_SL_UI_Confirm, PromptLabel = "Select"
├─ Spacer (size ~24)
└─ WBP_SL_CommandPrompt "Back" InputAction = IA_SL_UI_Back, PromptLabel = "Back"
- Order right→left is a Halo convention (Back rightmost). Flip if you prefer.
- Set each prompt's two exposed vars in the Details panel per the table above.
- Style the root (padding, background) with the angled Halo panel material if desired.
- Compile + Save.
Labels:IA_SL_UI_Back.ActionDescriptionis"Back"andIA_SL_UI_Confirm.ActionDescriptionis
"Select". You can either type the label intoPromptLabel(simplest, done above) or drive it from the
action's description later. Hardcoding the text here is fine.
#Step 3 — Place it so it appears with a menu
The bar must be present/constructed while a menu is up (that's when the glyph can resolve — see the footgun). Two options; A is recommended for reliability and simplicity.
#Option A (recommended) — embed in each screen
Drop WBP_SL_CommandBar into the bottom of each screen's tree (WBP_SL_PauseMenu, WBP_SL_Settings, …), anchored bottom-center or bottom-right.
- Pro: constructed exactly when the screen activates → the UI input context (
IMC_SL_UI) is already
applied, so the glyph resolves correctly. Visible iff the screen is visible — no visibility logic.
- Pro: per-screen freedom — a confirm modal can show
Select + Back, a pure info screen justBack.
- Con: one instance per screen (trivial — it's a single dropped widget; the vars are already set inside
WBP_SL_CommandBar).
#Option B — one shared bar in the layout
Place WBP_SL_CommandBar in WBP_SL_PrimaryGameLayout's root Overlay (Overlay_54), as the last child (top z-order), Vertical = Bottom. Then in the layout graph:
- Bind
MenuStack → On Displayed Widget Changed: setWBP_SL_CommandBarvisibility toVisiblewhen a widget
is active, Collapsed when none.
- Also re-resolve the glyph on show (call the prompt's
Glyph → Set Enhanced Input Actionagain, or a
small RefreshGlyph function) — a bar constructed before IMC_SL_UI is applied may cache an empty glyph. This extra refresh is the price of the shared approach, and the reason A is recommended.
#Step 4 — Verify in PIE (the gate)
- PIE with a gamepad.
- Press Start → pause menu opens.
- Expected: the strip shows
Ⓐ SelectandⒷ Backat the bottom.
- Press B → menu closes → strip goes away (with the screen, Option A).
- On keyboard/mouse (move the mouse to flip the active device): the whole strip is hidden.
Pass criteria: strip appears with the menu on a gamepad (both glyphs render), is hidden on KBM, and B closes.
#Gamepad-only visibility
The command bar is meaningless on mouse/keyboard (the player clicks directly and Esc is obvious), so it's hidden whenever the active device is KBM. This is driven off the UCommonInputSubsystem — not the input actions or the CommonActionWidget:
OnInputMethodChanged— a BlueprintAssignable delegate on the subsystem; fires with the new
ECommonInputType when the device switches.
GetCurrentInputType()— reads the device right now (for the initial state).
WBP_SL_CommandBar graph (Event Construct):
Get Owning Local Player→Get Local Player Subsystem(Class =CommonInputSubsystem). *(That node is
hidden if the palette's Context Sensitive box is checked — uncheck it, or hold Ctrl while searching.)*
Is Validguard →Bind Event to On Input Method Changed→ a custom event.
- Both the custom event and a one-time
Get Current Input Typeon construct funnel through a single
SetInputType/refresh function that sets the prompt visibility: Gamepad → Visible, everything else → Collapsed. Call it from both paths so the initial state is right (the delegate only fires on change).
Do the bind on Event Construct, not Pre-Construct — Pre-Construct also runs in the designer with no valid
local player. Make the refresh a symmetric if/else (re-shows on returning to a pad, not just hide-on-KBM).
Because the bar never draws on KBM, no CD_SL_KBM glyph set is needed — only CD_SL_Xbox is registered.
#Troubleshooting / footguns
- Glyph blank on gamepad too → the
CommonActionWidgetresolved beforeIMC_SL_UIwas live. CommonUI
resolves glyphs via QueryKeysMappedToAction, which only sees currently-applied input contexts. Option A (embed in screen) avoids this because the screen applies Menu input on activate before its children resolve. If on Option B, add the RefreshGlyph call from Step 3B.
- Designer preview is not proof.
UCommonActionWidget::GetIcon()has an editor-only branch that renders
DesignTimeKey and bypasses the runtime path — it will show a perfect glyph even when the runtime one is broken. Validate in PIE only.
- Glyph is the wrong button → check the prompt's
InputActionvar points at the rightIA_SL_UI_*action,
and that action is mapped in IMC_SL_UI.
- Nothing shows at all (Option B) → visibility binding not firing, or the bar is under an opaque menu
background (it's not the last child of the root Overlay).
- Don't wire click/behavior into these prompts. They are pure display. B and A already function via
bIsBackHandler / the default click action. A command prompt is not a button.
#Cleanup
WBP_SL_BoundActionButton(built for the dynamic bar) is not used by this approach. Keep it parked for
the MenuKit upgrade below, or delete it if you want the tree clean (confirm zero referencers first).
- The old placeholder text in
WBP_SL_CommandBaris replaced in Step 2.
#Later: the dynamic bar as a MenuKit upgrade
For SystemLink the game, hardcoded Back/Accept is the right call — simpler, and it moves us toward the Settings-menu gate instead of re-fighting the CommonUI glyph/router plumbing.
For MenuKit the Fab product, the dynamic CommonBoundActionBar is the more valuable, expected form — buyers' screens have varied actions and want the bar to populate itself. The groundwork already exists (WBP_SL_BoundActionButton tree is built; USLScreenWidget sets bIsBackActionDisplayedInActionBar), so swapping the hardcoded strip for the dynamic bar is a self-contained later task, not a rewrite. Treat it as a MenuKit feature, not a SystemLink blocker.
Prior revision of this doc described that dynamic-bar wiring in full; it's preserved in git history if the
MenuKit upgrade needs it.