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.
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
| Widget | Draws |
|---|---|
UEDInventoryGridWidget | A full spatial board with footprints, rotation and cell drop targets. |
UEDInventoryListWidget | A simple list. |
UEDInventoryBarWidget | Fixed cells from a list or slot layout — hotbars, equipment columns, hands rows. |
UEDInventoryCellWidget | One cell of a fixed view; a precise drop target. |
UEDInventorySlotWidget | One item: icon, count, rarity tint, drag source. |
UEDInventoryTooltipWidget | Name, description, rarity, weight, value, durability, stats. |
UEDInventoryContextMenuWidget | Right-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.
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:
| Semantic | When |
|---|---|
| Move | The target cell is free. |
| Merge | The target holds the same item and has room — the behaviour players expect instead of a rejected move. |
| Swap | The target holds something else. |
| Split | A 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
- Replication — why the binding rule exists
- Troubleshooting — empty panels and what they mean