How-To: Show a Context Menu
Goal: pop an action menu (Use / Drop / Inspect) at the cursor or anchored to a world object, and run the chosen action. Set Request.Context.Target and the menu gathers its UCrimsonContextAction list from the target itself via CrimsonCommon's UCrimsonContextActionGatherer - the exact gather the server re-runs when validating the pick. The same widgets serve inventory items, doors, NPCs, anything.
UI.Layer.ContextMenu layer (the tag ships natively in CrimsonCommon - add a stack for it exactly like the Quick Start layers). Menu widget Blueprints exist (step 1) and are set as project defaults in Project Settings -> Crimson -> Crimson UI (DefaultModalContextMenuClass / DefaultWorldContextMenuClass), or passed per request. The player controller has a UCrimsonContextActionPlayerComponent (server dispatch).1. Build the menu widgets once
| Widget | Parent | Notes |
|---|---|---|
| Entry button | UCrimsonContextMenuEntryWidget | A UCommonButtonBase + IUserObjectListEntry. Implement its OnEntryViewModelSet event and bind visuals to the entry VM's FieldNotify properties: DisplayName, Tooltip, DisabledReason, Icon, bIsEnabled, bIsDestructive, bIsFocused. Hover-to-focus and click-to-confirm are wired natively - no graph needed. |
| Modal menu | UCrimsonContextMenuWidget_Modal | Must contain a widget named exactly ContentPanel (required - the BP fails to compile without it), anchored top-left with auto size; C++ moves it to the cursor / anchor via render translation, flips + clamps at viewport edges, and closes on outside clicks. Inside it, add a Dynamic Entry Box named exactly EntryBox with Entry Widget Class = your entry BP - C++ creates the entries automatically (no virtualization, sizes to content). A UListView named EntryList is also supported for long scrollable menus, but it must be given a bounded height. |
| World menu | UCrimsonContextMenuWidget_World | Same required ContentPanel + EntryList content as the modal, but each tick C++ projects a USceneComponent's world position to the screen and moves ContentPanel there; closes itself when the anchor dies. |
Images/CrimsonUI/howto-contextmenu-widgets.pngBoth menu bases derive from UCrimsonActivatableWidget with InputConfig = GameAndMenu and GameMouseCaptureMode = NoCapture, so gameplay input keeps flowing while the mouse drives the menu immediately (with viewport capture, the first click would only release capture instead of hitting a button; note mouse-look is suspended while a menu is open). Mouse interaction needs no wiring. For gamepad/keyboard, bind the UI.Action.ContextMenu.* tags (or your own input events) to the BlueprintCallable methods: ConfirmFocusedEntry, RequestClose, CycleNext, CyclePrevious. Up/Down navigation is CommonUI's normal focus chain.
ContentPanel: it only generates the rows that fit its near-zero initial geometry. Use a Dynamic Entry Box named `EntryBox` instead (or give the list a bounded height via a Size Box). Completely empty - the entry host is not named exactly EntryBox / EntryList (C++ only auto-populates a bound one; a warning is logged at activation), or you used a CrimsonListView without Factory Rules. Stuck at the top-left / offset from the target - ContentPanel is not anchored top-left with auto size; C++ positions it via render translation, so any other anchoring adds an offset.2. Build the request and show it
Images/CrimsonUI/howto-contextmenu-show-bp.pngOther request fields: Header (title text), InputModeOverride (per-menu ECrimsonWidgetInputMode), ScreenAnchor (fixed position when not anchoring to cursor), bCloseOnClickOutside (default true), WorldScreenOffset, and Actions (optional explicit list - only for menus not backed by a provider; auto-gather is the recommended path). CloseAll() dismisses any open menu; GetActiveWidget() returns it.
UI.Layer.ContextMenu at the cursor (or over the target), entries show your action names grouped by section (destructive last), clicking an entry runs it on the server, clicking outside closes the menu, and disabled entries render greyed with their DisabledReason and are skipped by Cycle Next/Previous.3. Optional: confirmation, refresh, payloads
- Confirmation friction - set an action's
ConfirmationModetoDialogand confirming routes throughUCrimsonMessagingSubsystem(Yes/No) before executing.HoldToConfirmentries should implement a hold in the entry BP and callConfirmEntryBypassingConfirmationwhen it completes; a plain click falls back to the dialog. - Refresh while open - call
RefreshEntrieson the menu widget when relevant state changes (e.g. from an inventory-changed handler). Auto-gathered menus re-gather and preserve focus; explicit-list menus re-evaluate availability. The menu also closes itself if the target is destroyed. - Payloads -
ConfirmEntryWithPayload(Index, FInstancedStruct)sends per-invocation arguments (e.g. a split quantity from your own quantity dialog) toExecuteActionon the server. - Post-execute behavior - each action's
CloseBehaviorpicksCloseMenu(default),KeepOpen, orRefreshMenu. - Suppress gameplay while open - set
EffectWhileOpen(an Infinite GE granting e.g.Camera.Blocked; applied to the owning pawn's ASC on open, removed on any close path) orLooseTagsWhileOpenon the menu widget's class defaults. Pair withActivationBlockedTagson the abilities to block (camera turn, attack...). - World-menu lifetime & feel - per-asset options on
UCrimsonContextMenuWidget_World:AutoCloseDistance(close when the pawn walks away),bCloseOnClickOutside(dialog-border pattern),bScaleWithDistance,bFadeWhenOccluded.
UCrimsonCoreContextMenuSubsystem fills the request context for items/actors and adds the default-action fast path (ExecuteDefaultActionForItem / ForActor for double-click / tap). This page is the plugin-agnostic UI half.4. Optional: radial (gamepad wheel) presentation
Set PresentationMode = Radial for the hold-open / flick-stick / release-confirm wheel (Rust / RDR2 world interaction). Subclass UCrimsonContextMenuWidget_Radial (derives from _Modal, so click-outside and clamping come along): center ContentPanel, leave bAnchorToCursor = false, and place each entry widget at GetEntryDirectionByIndex(Index) * Radius - entry 0 sits at 12 o'clock, proceeding clockwise. Feed the left stick into SelectEntryByDirection each tick (dead-zone and boundary hysteresis are handled in C++) and call ConfirmFocusedEntry on button release. Mouse click/hover on wedges works with no extra wiring. Set the class as DefaultRadialContextMenuClass in Crimson UI settings.
Images/CrimsonUI/howto-contextmenu-radial.pngMaxRadialEntries. Use the radial for world interaction and the Modal list for inventories.See also
- How-To: World-Space Indicators - passive (non-focusable) world-anchored UI.
- Concept: Input Routing & Activatable Widgets - why the menu stays
GameAndMenu. - How-To: Show Confirmation Dialogs - the dialog system
ConfirmationMode = Dialogroutes through.