Context Menus
The context menu system lets actors provide right-click menus that are rendered as a native Slate widget (SContextMenuWidget). Any actor implementing IContextMenuProvider can supply menu items.
Native UI: The context menu is fully native Slate (zinc/indigo design tokens), positioned directly at the cursor in C++ and clamped to the viewport. It no longer uses a CEF web overlay.
Architecture
Alt + Right-Click
│
▼
Line Trace (from cursor position)
│
▼
Hit Actor → implements IContextMenuProvider?
│
▼ Yes
UContextMenuManager::TryOpenContextMenu()
│
▼
SContextMenuWidget::ShowMenu(displayName, items, cursorPos, viewportSize)
(native Slate panel positioned at the cursor, clamped on-screen)
│
▼
User clicks an item → OnSelect(actionId) callback
│
▼
IContextMenuProvider::OnContextMenuAction(ActionId)
C++ Interface — IContextMenuProvider
UINTERFACE(MinimalAPI, Blueprintable)
class UContextMenuProvider : public UInterface
{
GENERATED_BODY()
};
class IContextMenuProvider
{
GENERATED_BODY()
public:
virtual FString GetContextMenuDisplayName() const = 0;
virtual TArray<FContextMenuItem> GetContextMenuItems() const = 0;
virtual void OnContextMenuAction(const FString& ActionId) = 0;
};
FContextMenuItem
USTRUCT(BlueprintType)
struct FContextMenuItem
{
GENERATED_BODY()
UPROPERTY() FString ActionId;
UPROPERTY() FString Label;
UPROPERTY() FString Icon; // Key into icon map (search, trash, edit, etc.)
UPROPERTY() bool bEnabled;
UPROPERTY() bool bDestructive;
};
Example — ADynamicBlock
ADynamicBlock implements IContextMenuProvider to provide block-specific actions:
FString ADynamicBlock::GetContextMenuDisplayName() const
{
return FString::Printf(TEXT("Block (%s)"), *BlockId);
}
TArray<FContextMenuItem> ADynamicBlock::GetContextMenuItems() const
{
return {
{ "inspect", "Inspect Block", "search", true, false },
{ "copy", "Copy Block", "copy", true, false },
{ "delete", "Delete Block", "trash", true, true },
};
}
void ADynamicBlock::OnContextMenuAction(const FString& ActionId)
{
if (ActionId == "delete") { Destroy(); }
// Handle other actions...
}
Example — AReplicatedBlock
Live, server-authoritative blocks (AReplicatedBlock) implement IContextMenuProvider to expose build-mode editing actions:
TArray<FContextMenuItem> AReplicatedBlock::GetContextMenuItems() const
{
return {
{ "edit", "Edit", "edit", true, false },
{ "duplicate", "Duplicate", "copy", true, false },
{ "delete", "Delete", "trash", true, true },
};
}
OnContextMenuAction routes these to the local player controller: edit →
AGridsPlayerController::BeginEditBlock (opens the Edit Object panel),
delete → ServerRequestBlockRemove, and duplicate → ServerRequestBlockPlace
with the same shape, texture, rotation, and scale.
Rendering — native Slate
The menu is rendered by SContextMenuWidget (Source/Grids/Edge/UI/SContextMenuWidget.h/.cpp), added to the viewport once by UContextMenuManager and reused. ShowMenu builds a SGridsPanel with one SGridsButton per FContextMenuItem:
- Destructive items use the
Destructivebutton variant (red); others useGhost. - Disabled items (
bEnabled = false) render greyed-out and non-clickable. - A transparent full-screen click-catcher behind the panel dismisses the menu on any outside click;
Escapealso dismisses. - Positioning is computed in C++ against the real viewport size, so the menu always appears at the cursor and is clamped fully on-screen (no first-layout corner-pinning).
Selecting an item raises the OnSelect(actionId) callback, which UContextMenuManager forwards to IContextMenuProvider::OnContextMenuAction on the target actor, then dismisses the menu.
The Icon field on FContextMenuItem is currently unused by the native renderer (the former web overlay mapped icon keys to emoji); labels are shown without icons.