Inventory UI
A complete, drop-in inventory UI built on CrimsonInventory + CrimsonUI. Lives under Source/CrimsonCore/Public/UserInterface/Inventory/. All view-model bindings are wired in C++ only via CRIMSON_BIND_FIELD — Blueprint authors do not (and cannot) wire MVVM in the UMG binding graph; they implement visual events only.
OnXVisual event reactions. The base classes route every UPROPERTY(FieldNotify, ...) through CRIMSON_BIND_FIELD; a stray UMG binding will fire alongside the C++ one and the order is undefined.What ships
| Surface | Files | Role |
|---|---|---|
| HUD overlay (always-on) | HUD/CrimsonInventoryHUDLayer.{h,cpp} | UCommonUserWidget plugged into a HUD slot extension point. Hosts a UCrimsonListView of capacity bars and one of currency totals. |
| Toast spawner (always-on) | HUD/CrimsonInventoryToastSpawner.{h,cpp} + CrimsonInventoryToastSettings.h | ULocalPlayerSubsystem + project settings. Listens to inventory message events and pushes one UCrimsonInventoryToastViewModel per event into a HUD slot via UCrimsonUIExtensionSubsystem::RegisterExtensionAsData. Retires the extension on a per-toast timer. |
| Inventory screen (push-on-open) | Screens/CrimsonInventoryScreen.{h,cpp} | UCrimsonActivatableWidget. The player's own inventory pane. Pushed onto UI.Layer.Menu via UCrimsonUIExtensions::PushContentToLayer_ForPlayer. |
| Container screen (push-on-open) | Screens/CrimsonInventoryContainerScreen.{h,cpp} | UCrimsonActivatableWidget. Two-pane variant — player inventory on one side, container (chest, stash, vendor) on the other. |
| Tooltips (per-slot, per-item-def) | Widgets/CrimsonItemTooltipWidget.{h,cpp} | Abstract UCommonUserWidget base. Item defs declare which tooltip subclass to use via the UCrimsonInventoryItemFragment_Tooltip fragment. The slot widget resolves the class and calls SetToolTip(Widget). |
| Entry / capacity / currency / toast widgets | Slots/CrimsonInventorySlotWidget.{h,cpp}, Slots/CrimsonInventoryGridSlotWidget.{h,cpp}, Widgets/CrimsonCapacityBarWidget.{h,cpp}, Widgets/CrimsonCurrencyEntryWidget.{h,cpp}, Widgets/CrimsonInventoryToastWidget.{h,cpp} | List-entry widgets bound to per-row VMs. All implement IUserObjectListEntry; the list view (or extension point, for toasts) routes the data object into NativeOnListItemObjectSet, which calls CRIMSON_BIND_FIELD on each FieldNotify property. |
| Drag-drop + factory | Bases/CrimsonInventoryDragDropOperation.{h,cpp}, Factories/CrimsonInventorySlotFactory.{h,cpp} | Drag op carries source inventory + entry id + footprint + rotation; ApplyToTarget dispatches Routing->MoveItem or Routing->TransferItem. The factory maps an entry VM (footprint > 1×1) to the grid slot widget class; everything else uses the flat slot widget class. |
| View-models | ViewModels/CrimsonInventoryViewModel.{h,cpp}, ViewModels/CrimsonInventoryEntryViewModel.{h,cpp}, ViewModels/CrimsonCapacityBarViewModel.h, ViewModels/CrimsonCurrencyEntryViewModel.{h,cpp}, ViewModels/CrimsonInventoryToastViewModel.h | Screen VM rebuilds entry/currency/capacity child VMs from FastArray + tag-stack containers on StackChanged / InventoryUpdated / ConstraintChanged / CurrencyChanged messages. |
| MVVM binding helpers | Bases/CrimsonViewModelBindingHelpers.h | FCrimsonFieldBindingTracker + CRIMSON_BIND_FIELD macro — one-liner subscribe/track for any FieldNotify property; UnsubscribeAll on widget teardown. |
Why it's structured this way
UPROPERTY(FieldNotify, ...) on a VM is subscribed by INotifyFieldValueChanged::AddFieldValueChangedDelegate from the widget's NativeOnInitialized / NativeOnListItemObjectSet. Subscriptions go through one FCrimsonFieldBindingTracker per widget instance and are released in NativeDestruct / NativeOnEntryReleased. BP MVVM graphs are silent on data flow (no compile-time check, no log when a binding silently breaks); the macro path makes every binding a grep-able call site.UCrimsonInventoryRoutingComponent on the owning PlayerController. The routing component owns every Server_* RPC. The UI never mutates the inventory directly.UCrimsonInventoryHUDLayer, toasts) are plain UCommonUserWidgets registered into HUD slot tags via UGFA_AddWidget's Widgets array. The host UCrimsonHUDLayout (already on UI.Layer.Game) places UCrimsonUIExtensionPointWidgets with matching tags. Activatable menu screens (inventory + container) push themselves onto UI.Layer.Menu via UCrimsonUIExtensions::PushContentToLayer_ForPlayer. Nested activatable widgets would push input config twice — both screens are flat by design.UCrimsonInventoryToastViewModel registered as data on a slot tag. The slot's UCrimsonUIExtensionPointWidget instantiates one widget per registration; the spawner unregisters after DurationSeconds, and the slot retires the widget. A persistent tray with its own list view would duplicate work the extension point already does and gate the toast lifetime on a frame loop instead of the message event.UCrimsonInventoryItemFragment_Tooltip::TooltipWidgetClass. The slot resolves the class on bind (fragment override → DefaultTooltipWidgetClass), creates the widget, calls BindToEntry(VM), and hands it to UE via SetToolTip(Widget) — UE auto-shows on hover and tears it down on slot recycle. Weapon, consumable, and crafting-material tooltips diverge enough that one widget class with conditional sections is messier than dedicated subclasses.TSoftClassPtr<UUserWidget> (engine-only type) so the inventory plugin has no dependency on any UI plugin. CrimsonCore is the only place allowed to bridge CrimsonInput, CrimsonUI, and CrimsonInventory.How it works at runtime
Read flow — replication and message subsystem fanning out to widgets:
┌─────────────────────────────────────────────────┐server only: │ UCrimsonInventoryManagerComponent │ on PlayerState│ FastArray entries + tag-stack constraint caps │└────────────────────────┬────────────────────────┘│ replicates▼┌─────────────────────────────────────────────────┐│ UCrimsonMessageSubsystem (per world) ││ StackChanged · InventoryUpdated · ││ ConstraintChanged · CurrencyChanged · ││ ConstraintFailureMessage · ItemDropped │└──────────────────┬──────────────────────────────┘│┌──────────────────────┼──────────────────────────────┐▼ ▼ ▼UCrimsonInventoryHUDLayer UCrimsonInventoryScreen UCrimsonInventoryToastSpawner(slotted in HUD) (pushed to UI.Layer.Menu) (per-LocalPlayer subsystem)│ │ │▼ ▼ ▼UCrimsonInventoryViewModel · UCrimsonInventoryViewModel · UCrimsonInventoryToastViewModelrebuilds child VMs rebuilds child VMs (one per event)│ │ │FieldNotify ▶ widget FieldNotify ▶ widget RegisterExtensionAsData(SlotTag)via CRIMSON_BIND_FIELD via CRIMSON_BIND_FIELD │▼UCrimsonUIExtensionPointWidgetinstantiates UCrimsonInventoryToastWidgetand routes data via IUserObjectListEntry│timer fires → UnregisterExtension
Mutation flow (client → server):
Slot widget / action handler / drag-drop│▼UCrimsonInventoryEntryViewModel::RequestMoveTo / RequestSplit / RequestDrop / RequestToggleLock│▼UCrimsonInventoryRoutingComponent (on PlayerController)│ Server_* RPC▼UCrimsonInventoryManagerComponent (on PlayerState, server-only mutation)│▼ FastArray + message broadcastreplication → clients → UCrimsonInventoryViewModel.Refresh()
Setup
These steps assume CrimsonCore is already installed per Integration above, and the player has a UCrimsonInventoryManagerComponent on the PlayerState and a UCrimsonInventoryRoutingComponent on the PlayerController (UGFA_AddInventory handles both — see the CrimsonInventory wiki page for the constraint/currency table setup).
1. Define the HUD slot tags
In your project's tags ini (or via the Gameplay Tag editor), add three child tags under HUD.Slot.*:
[/Script/GameplayTags.GameplayTagsList]GameplayTagList=(Tag="HUD.Slot.Inventory.Capacity")GameplayTagList=(Tag="HUD.Slot.Inventory.Currency")GameplayTagList=(Tag="HUD.Slot.Inventory.Toasts")
HUD.Slot.* works. The names above match the rest of the docs; you can change them as long as the GFA, settings, and extension-point widget all agree.2. Create BP subclasses of the abstract widgets
Under Content/UI/Inventory/, create Blueprint subclasses for every abstract widget. Each one needs only its OnXVisual events implemented — the C++ base wires the VM bindings.
| BP class | Parent | What to do in BP |
|---|---|---|
BP_InventoryHUDLayer | UCrimsonInventoryHUDLayer | Bind two UCrimsonListViews to CapacityListView and CurrencyListView in the widget tree. |
BP_InventorySlotWidget | UCrimsonInventorySlotWidget | Implement OnEntryBound, OnStackCountVisual, OnIconVisual, OnNameVisual, OnLockVisual, OnHighlightVisual. Set DefaultTooltipWidgetClass in defaults. |
BP_InventoryGridSlotWidget | UCrimsonInventoryGridSlotWidget | Same as flat slot, plus footprint / rotation visual events. |
BP_CapacityBarWidget | UCrimsonCapacityBarWidget | Implement OnNameVisual, OnCurrentVisual, OnMaxVisual, OnPercentVisual. |
BP_CurrencyEntryWidget | UCrimsonCurrencyEntryWidget | Implement OnNameVisual, OnAmountVisual, OnIconVisual. |
BP_InventoryToastWidget | UCrimsonInventoryToastWidget | Implement OnTitleVisual, OnSubtitleVisual, OnIconVisual, OnAmountVisual, OnSeverityVisual. Play a fade animation on OnToastBound. |
BP_InventoryScreen | UCrimsonInventoryScreen | Bind a UCrimsonTileView to EntryTileView. Set the tile view's EntryWidgetClass via a UCrimsonInventorySlotFactory (FlatSlotWidgetClass → BP_InventorySlotWidget; GridSlotWidgetClass → BP_InventoryGridSlotWidget). |
BP_InventoryContainerScreen | UCrimsonInventoryContainerScreen | Bind PlayerEntryTileView and ContainerEntryTileView. Same factory pattern. |
BP_WeaponTooltip etc. | UCrimsonItemTooltipWidget | One subclass per item category (weapon / consumable / crafting / ...). Implement OnEntryBound + the title/icon/type/description/rarity visual events. |
3. Build the HUD layout
Create BP_HUDLayout : UCrimsonHUDLayout (in CrimsonUI). Place three UCrimsonUIExtensionPointWidgets in the layout, one per slot tag from step 1:
| Slot | ExtensionPointTag | DataClasses | GetWidgetClassForData |
|---|---|---|---|
| Capacity | HUD.Slot.Inventory.Capacity | UUserWidget | (BP-registered) |
| Currency | HUD.Slot.Inventory.Currency | UUserWidget | (BP-registered) |
| Toasts | HUD.Slot.Inventory.Toasts | UCrimsonInventoryToastViewModel | Return BP_InventoryToastWidget |
4. Wire the HUD widgets via UGFA_AddWidget
In your experience definition's actions, configure a UGFA_AddWidget instance:
GFA_AddWidgetLayout:[0] LayerID = HUD.Layer.GameLayoutClass = BP_HUDLayout // UCrimsonHUDLayout subclassWidgets:[0] SlotID = HUD.Slot.Inventory.CapacityWidgetClass = BP_InventoryHUDLayer[1] SlotID = HUD.Slot.Inventory.CurrencyWidgetClass = BP_InventoryHUDLayer // same widget — owns both lists// No entry for the toast slot — the spawner populates it at runtime.
BP_InventoryHUDLayer queries both capacity and currency from the same VM. Slotting it into one slot is enough. If your HUD design wants them in physically separate places, split it into two BP subclasses (each binding only the relevant list view) and register each into its own slot.5. Configure toasts
Open Project Settings → Crimson → Crimson Inventory Toasts and set:
| Setting | Value |
|---|---|
ToastSlotTag | HUD.Slot.Inventory.Toasts — must match the extension point tag from step 3. |
DefaultDurationSeconds | Lifetime for info / pickup / drop toasts. Default 3.0. |
ErrorDurationSeconds | Lifetime for error toasts (constraint failed). Default 4.5. |
bShowCurrencyToasts | Off by default — currency change is usually represented by the dedicated currency HUD widget. |
RegistrationPriority | Higher = displayed first in the slot. Default 0. |
ToastSlotTag doesn't match a UCrimsonUIExtensionPointWidget in your HUD layout, RegisterExtensionAsData still succeeds — but no widget is ever instantiated. Verify both tags match at setup time. The UCrimsonInventoryToastSpawner ULocalPlayerSubsystem is auto-created when the local player joins; no manual instantiation is needed.6. Wire the inventory screen open action
Pick a key (typically I) and a UInputAction for inventory toggle. From your player controller or a CommonUI bound action, push the screen onto the menu layer:
UCrimsonUIExtensions::PushContentToLayer_ForPlayer(PC->GetLocalPlayer(),FGameplayTag::RequestGameplayTag(TEXT("UI.Layer.Menu")),BP_InventoryScreen);
The screen finds the player's inventory + routing components automatically and binds. In BP_InventoryScreen's defaults, set UIInputConfig to a UCrimsonInputConfig containing UInputActions tagged UIInputTag.Inventory.Drop, Split, Lock, Rotate, QuickTransfer (names are conventions). Then set the matching DropTag / SplitTag / LockTag / RotateTag / QuickTransferTag properties to those gameplay tags.
7. Wire the container screen
When UCrimsonInventoryRoutingComponent::OpenContainer fires OnContainerOpened(UCrimsonInventoryManagerComponent* ContainerInv) on the requesting client, push the container screen and inject the container inventory before initialization completes:
UCommonActivatableWidget* Pushed = UCrimsonUIExtensions::PushContentToLayer_ForPlayer(LP, MenuLayerTag, BP_InventoryContainerScreen);if (auto* Screen = Cast<UCrimsonInventoryContainerScreen>(Pushed)){Screen->SetContainerInventory(ContainerInv); // MUST run before NativeOnInitialized fires}
PushContentToLayer_ForPlayer returns the widget instance before NativeOnInitialized runs. Call SetContainerInventory(...) immediately after the push call — if it runs later, the right pane stays empty.8. Wire per-item tooltips
On each UCrimsonInventoryItemDefinition asset, add an instanced UCrimsonInventoryItemFragment_Tooltip fragment and set:
| Field | Purpose |
|---|---|
TooltipWidgetClass | The tooltip BP for this item category (e.g. BP_WeaponTooltip for swords, BP_ConsumableTooltip for potions). TSoftClassPtr<UUserWidget> — sync-loaded by the slot on bind. |
Description | Long-form item description rendered via OnDescriptionVisual. |
RarityColor | Accent color rendered via OnRarityColorVisual. |
Set DefaultTooltipWidgetClass on each of your slot BPs as a fallback used when an item def has no Tooltip fragment.
LoadSynchronous() on the soft class ptr. For frequently-shown items this is fine; for rarely-shown items consider preloading via your asset manager's primary-asset bundle to avoid hitches on first hover.9. (Optional) Slot focus tracking
The inventory screen and container screen track which entry is "focused" so their input actions (Drop / Split / Lock / Rotate / QuickTransfer) know which entry to act on. Slot widgets call Screen->SetFocusedEntry(...) on their parent screen — wire this from your BP slot's OnHovered event (single-pane screen) or OnFocused event (container screen, which also takes a ECrimsonContainerPane argument).
Customization knobs
| Class | Override point | What it controls |
|---|---|---|
UCrimsonInventorySlotWidget | ResolveTooltipWidgetClass (BlueprintNativeEvent) | Per-slot tooltip class resolution. Default = fragment → DefaultTooltipWidgetClass. Override for rarity-tiered or faction-tiered tooltip classes. |
UCrimsonInventorySlotWidget | GetDragQuantity (BlueprintNativeEvent) | Quantity carried by a drag from this slot. Default = full stack. Override for Ctrl+drag = 1. |
UCrimsonInventorySlotFactory | GetWidgetClassForData (BlueprintNativeEvent) | Mapping from entry VM → list-entry widget class. Default = footprint > 1×1 → grid widget, else flat widget. Override for additional slot widget variants. |
UCrimsonInventoryToastSpawner | Subclass + override OnStackChanged / OnConstraintFailed / OnCurrencyChanged / OnItemDropped | Custom toast filter logic (rarity-gated, type-gated, dedupe). |
UCrimsonItemTooltipWidget | OnEntryBound (BlueprintImplementableEvent) | Read additional fragments off the item def to render category-specific data (weapon stats, consumable effects, etc.). |
UCrimsonInventoryContainerScreen | SetActivePane, SetFocusedEntry (BlueprintCallable) | Active-pane + focused-entry tracking for QuickTransfer. Call from BP slot focus events. |
Notes & pitfalls
UIInputConfig (a UCrimsonInputConfig* data asset) is intentionally not named InputConfig because the parent UCrimsonActivatableWidget already has an InputConfig property of a different type (the ECrimsonWidgetInputMode enum that controls input mode push on activation). The two serve unrelated jobs.Plugins/CrimsonCore/Source/CrimsonCore/Public/UserInterface/Inventory/ (headers) and Plugins/CrimsonCore/Source/CrimsonCore/Private/UserInterface/Inventory/ (sources). Item-fragment header is in CrimsonInventory: Plugins/CrimsonInventory/Source/CrimsonInventory/Public/ItemFragments/CrimsonInventoryItemFragment_Tooltip.h.