Skip to main content

User interface

The UI module is MVVM. A view model observes a container and caches a view-friendly snapshot; a view renders it and requests changes. Views never mutate a container — presentation stays decoupled from the model.

The binding rule

This is the single most important thing on this page.

// Right — "this actor's hotbar". True before the container exists.
View->SetSourceInventory(Inventory, HotbarTag);

// Only for a container you already hold: a chest being looted, a preview.
View->SetSource(ChestContainer);

On a client the actor's containers arrive after the HUD is built. A view bound to the pointer that exists at that moment is bound to nothing, and stays empty for the whole session while the inventory behind it works perfectly. Bound by role, the view attaches itself the moment the container replicates in and detaches if it goes away.

This does not reproduce in standalone

In a single-process game the container already exists when the HUD is built, so pointer binding appears to work. It fails only with a real client attached — which is also when it is hardest to debug, because the gameplay debugger collects on the server and shows you a perfectly healthy inventory.

The widgets

WidgetDraws
UEDInventoryGridWidgetA full spatial board with footprints, rotation and cell drop targets.
UEDInventoryListWidgetA simple list.
UEDInventoryBarWidgetFixed cells from a list or slot layout — hotbars, equipment columns, hands rows.
UEDInventoryCellWidgetOne cell of a fixed view; a precise drop target.
UEDInventorySlotWidgetOne item: icon, count, rarity tint, drag source.
UEDInventoryTooltipWidgetName, description, rarity, weight, value, durability, stats.
UEDInventoryContextMenuWidgetRight-click actions.

All are Blueprintable. Subclass them as Widget Blueprints and lay them out in the designer; the C++ base does behaviour, your Blueprint does looks.

Set Slot Widget Class and Cell Widget Class on a view so it knows what to spawn.

A bar view accepts a SlotFilter, which is how one equipment container feeds two panels — an armour column and a hands row — from the same data.

Design-time cells

A bar with no container draws nothing at runtime, on purpose: an unattached view must not look like an empty inventory. FallbackSlotCount fills the widget designer with cells so you have something to lay out against, and applies only there.

The view model

UEDInventoryViewModel* Model = View->GetViewModel();

Model->GetEntries(); // the cached snapshot, in slot-address order
Model->FindEntry(Handle, OutEntry);
Model->OnChanged.AddDynamic(...); // rebuild here

Model->RequestMove(Handle, TargetContainer, Address);
Model->RequestTransfer(Handle, TargetContainer, Count); // -1 = the whole entry
Model->RequestSwap(Handle, OtherContainer, OtherHandle);
Model->RequestSplit(Handle, Amount);
Model->RequestSort(EEDSortMode::ByCategory);
Model->CycleSortMode(); // advances and re-sorts; returns the new mode

Every Request* is routed and validated server-side. Entries are cached in slot-address order rather than array order, because a FastArray's client-side element order is not guaranteed to match the server's — addresses are the portable ordering.

Drag and drop

Dropping is classified before it is performed, so the result matches what the player expected:

SemanticWhen
MoveThe target cell is free.
MergeThe target holds the same item and has room — the behaviour players expect instead of a rejected move.
SwapThe target holds something else.
SplitA modifier key is held.

Grid drops are footprint-aware: a 1×3 sword lands in a vertical gap no matter which of its cells you were pointing at.

Dragging an item out of the UI fires a cancelled hook, which is where drop-to-world belongs. SetQuickTransferCounterpart gives a view its double-click / shift-click partner, so a storage screen can move items between the two halves with one click.

Building a storage screen

Setup (StashContainer)
├─ Set Source Inventory (PlayerPanel, Inventory, Inventory.Container.Main)
├─ Set Source Inventory (EquipPanel, Inventory, Inventory.Container.Equipment)
├─ Set Source Inventory (HotbarRow, Inventory, Inventory.Container.Hotbar)
├─ Set Source (StashPanel, StashContainer) ← the chest: it already exists
├─ Set Quick Transfer Counterpart (PlayerPanel ⇄ StashPanel)
└─ Open Session (Inventory, StashContainer) ← closes itself when you walk away

Binding the session is what makes the screen close when the player walks away — see The inventory manager.

Icons and tooltips

Icons stream asynchronously; a slot shows nothing until its texture is resolved rather than blocking the frame. Rarity tinting comes from the item's Rarity fragment, so it needs no per-widget wiring.

Tooltips read fragments directly, which is why an item with a Durability fragment gets a durability bar and one without does not — no per-item UI configuration.

Where next