Skip to content

Widget Library

Summary: The Widget Library provides reusable UI components following MVVM patterns. Key widgets include UPlayerHUDWidget for in-game HUD, UItemWidget for item display with drag-drop, UItemTooltipWidget for detailed tooltips, and USkillSlotWidget for ability slots. Widgets use BindWidget for child discovery and FieldNotify for ViewModel binding.

Table of Contents


Widget Categories

Overview Table

Category Base Class Purpose Example Widgets
HUD UUserWidget In-game display PlayerHUD, Skillbar, ResourceBar
Status UUserWidget Buff/debuff icons, owner panel + enemy overhead StatusEffectPanel, StatusEffectEntry, Status
Granted Powers UCommonUserWidget Echo mods, blessings, procs, conditionals GrantedPowersTray, PowerList, PowerCard
Inventory UCommonUserWidget Item management Inventory, Item, ItemDropCatcher
Tooltip UCommonUserWidget + IToolTip Hover information ItemTooltip, SkillTooltip
Crafting UUserWidget Recipe/result + greed Crafting, CraftingModifierEntry
Character UUserWidget Character stats Character, StatEntry
Game Menu UUserWidget In-game ESC menu GameMenu
Glyph UCommonUserWidget Glyph socketing GlyphWall, StonePlate, GlyphSocket
Modal UUserWidget Blocking prompts / paged sequences Modal
Utility UCommonUserWidget Reusable components BezierCurve, ModifierEntry

Widget Hierarchy

AEternalGameHUD
         |
         v
+---------------------------+
|    UEternalUISubsystem    |
+---------------------------+
         |
         +-- UHUDController
         |        |
         |        v
         |   PlayerHUDWidget
         |        +-- ScreenEffectsWidget (full-screen overlay, ZOrder -10)
         |        +-- SkillbarWidget
         |        |       +-- SkillSlotWidget (x5)
         |        +-- StatusEffectPanelWidget
         |        |       +-- StatusEffectEntryWidget (dynamic)
         |        +-- GrantedPowersTrayWidget
         |        |       +-- GrantedPowerEntryWidget (dynamic)
         |        +-- Vitals bars (health / stamina / poise, in-spec, no widget class)
         |
         +-- UStatusController              (world-space, per enemy)
         |        |
         |        v
         |   StatusWidget  (health + trailing bar, stance bar on Elite+)
         |        +-- StatusEffectEntryWidget (overhead status icons)
         |
         +-- UInventoryController
         |        |
         |        v
         |   InventoryWidget
         |        +-- ItemWidget (grid)
         |
         +-- USkillTooltipController
         |        |
         |        v
         |   SkillTooltipWidget
         |        +-- EffectEntryWidget (dynamic)
         |
         +-- UItemTooltipController
         |        |
         |        v
         |   ItemTooltipWidget
         |        +-- ModifierEntryWidget (dynamic)
         |        +-- ScalingAttributeEntryWidget (dynamic)
         |
         +-- UGlyphController
                  |
                  v
             GlyphWallWidget
                  +-- StonePlateWidget
                          +-- GlyphSocketWidget
                                  +-- ItemWidget (glyph)

HUD Widgets

UPlayerHUDWidget

Main HUD container compositing all in-game UI elements.

+------------------------------------------------+
|                 PlayerHUDWidget                |
+------------------------------------------------+
|                                                |
|  [Health Bar] [Stamina Bar] [Poise Bar]        |
|                                                |
|  [Status Effect Icons...]                      |
|                                                |
|                                                |
|                                                |
|           +-------------------+                |
|           |    Skillbar       |                |
|           | [Q] [LMB] [RMB] [E] [Consumable]  |
|           +-------------------+                |
+------------------------------------------------+
Child Widget Purpose
Skillbar Directional skill layout
StatusEffectPanel Active buff/debuff icons
NotificationContainer Floating messages
ScreenEffects Low-health vignette + big-hit pulse overlay (hit-test invisible, below HUD chrome)

USkillbarWidget

Directional skill layout for abilities.

+---------------------------------------+
|            SkillbarWidget             |
+---------------------------------------+
|                                       |
|            [TopSlot - Q]              |
|                                       |
|  [LeftSlot - LMB]    [RightSlot - RMB]|
|                                       |
|           [BottomSlot - E]            |
|                                       |
|        [ConsumableSlot - 1]           |
+---------------------------------------+
Slot Input Typical Use
LeftSlot LMB Primary attack
RightSlot RMB Secondary attack
TopSlot Q Ability 1
BottomSlot E Ability 2
ConsumableSlot 1 Health potion

USkillSlotWidget

Individual skill slot with icon, input label, and cooldown.

+------------------+
| SkillSlotWidget  |
+------------------+
|   +----------+   |
|   | SkillIcon|   |
|   +----------+   |
|                  |
|   [InputLabel]   |
|                  |
|   [CooldownBar]  |  (optional)
+------------------+
Element Purpose
SkillIcon Ability texture
InputLabel Key binding text (Q, LMB, etc.)
CooldownBar Visual cooldown progress

The cooldown sweep reads the ability's own GetCooldownTimeRemainingAndDuration, which USkillAbility overrides for every slotted skill — see Universal Skill Cooldowns. A skill whose class does not override those virtuals renders no sweep, however its CooldownDuration is authored.

Vitals bars

There is no UResourceBarWidget class. The HUD v3 rebuild replaced the per-resource widget with bars authored directly in the W_PlayerHUD WidgetForge spec and driven by UPlayerHUDWidget from the HUD ViewModel.

+------------------------------------------------------+
|  HealthBarTrack   (texture: T_UI_Bar_Track_M)        |
|  +------------------------------------------------+  |
|  | HealthBarGhost  (MI_UI_BarFill_*, eases after)  |  |
|  | HealthBarFill   (MI_UI_BarFill_*, snaps)        |  |
|  +------------------------------------------------+  |
|      HealthCurrentText / HealthMaxText  (in-bar)     |
+------------------------------------------------------+
   ... same shape for Stamina, plus a Poise bar with ticks
Aspect How it works
Fill A material instance whose Percent scalar the widget drives — not a UProgressBar
Trailing "ghost" A second fill image behind the main one, easing toward the value in both directions (damage trail on health, spend trail on stamina)
Numbers Drawn inside the bar rather than beside it
Poise ticks Marks at the 10% / 40% / 70% thresholds; the last-crossed threshold's tick lights gold. The thresholds come from FPoiseConfiguration's shared constants so bar and FSM cannot drift
Binds All BindWidgetOptional, so a spec that omits a bar degrades rather than failing to compile

Moving the bars into the spec is what let the whole vitals block be re-arted (track/fill textures, bottom scrim) without touching C++.

UScreenEffectsWidget

Full-screen hit/status feedback overlay (ScreenHitEffects): low-health vignette + big-hit pulse. Owner-side presentation only — zero new replication; the HUD exists only on the owning client.

Owning-client ASC (health deltas, State.Health.Below35 / State.Dead loose tags)
        |
        v
UScreenEffectsController (HUD sub-controller: intensity curve, big-hit
        |                  detection, death ramp, Eternal.ScreenFX.* CVars)
        v
UScreenEffectsViewModel (FieldNotify: intensity, tint, pulse params,
        |                hit-pulse counter, death state)
        v
UScreenEffectsWidget --> dynamic MID on VignetteImage (M_UI_ScreenFX_Vignette)
Aspect Behavior
Visibility HitTestInvisible enforced in NativeConstruct; ZOrder -10 (under HUD chrome, above world; death modal is a separate layer above)
Low-health channel Continuous intensity ramp (default 50% -> 15% health ratio, curve-overridable); slow swell (< 3 Hz photosensitivity cap) gated on State.Health.Below35
Big-hit channel Single ~250 ms envelope when one health delta >= configurable fraction of MaxHealth
Channel composition Max, never summed (capped intensity)
Death Ramps to ceiling and freezes; pulse stops; refreshes on respawn
Tuning /Game/DataAssets/UI/ScreenEffectsConfig (UScreenEffectsConfig), wired via UIConfig.ScreenEffectsConfig; null config falls back to matching code defaults
Accessibility Eternal.ScreenFX.Enabled / Eternal.ScreenFX.Intensity CVars apply immediately (console-variable sink), pending a settings menu

Status Effect Widgets

The same entry widget renders status effects in two places — the player's own panel and an enemy's overhead bar — so everything below is written once and shared.

UStatusEffectPanelWidget (player HUD)      UStatusWidget (world-space, per enemy)
        |                                            |
        +-- UStatusEffectEntryWidget  <--------------+
                    |
                    +-- StatusIcon      (+ a dark offset duplicate behind it, faking a drop shadow)
                    +-- StackText       bottom-right badge
                    +-- CountdownText   bottom-left, whole seconds rounded up

One icon per status tag

Entries are grouped by status tag, not by effect instance: three stacks of bleed are one icon carrying a depth badge and one countdown (the longest remaining), never three icons. The consequence is that a group can be carried by several different effects that disagree about what the icon should annotate — Status.Poison sits on both the poison DoT and the corruption ground hazard, for example.

EStatusOverlayMode is that annotation policy, authored per effect:

Mode Shows
None Silent icon — neither badge nor countdown
Countdown Countdown only, for effects that cannot stack or whose depth is not player-actionable
StackCount ("Stacks + Countdown") Both — a stacking debuff's threat is how deep it is and how long it lasts, so neither number stands alone

Modes are folded, never picked. Both controllers used to resolve a group's mode from whichever effect they happened to look at first, so the poison icon showed or hid its countdown depending on application order. The modes form an explicit information ladder — silent, then countdown, then stacks-and-countdown — and the richest wins. The rank has to be spelled out because the enumerator order is authored-asset order, not information order; the enumerator names themselves are load-bearing for the same reason (they are what serializes into effect assets, so renaming one silently drops those assets to the default).

Two presentation rules fall out of the small icon size: a lone "1" is noise, so the badge stays empty until a group actually carries more than one; and a durationless effect gets no countdown rather than a frozen number.

Tooltips resolve the owner, not the local player

A status tooltip pairs the hovered entry's effect handle with an ability system component — and the entry widget backs both the player's panel and an enemy's overhead bar. Pairing the handle with the local player's ASC produced an empty tooltip frame whenever the hovered icon belonged to an enemy. The handle carries its own owner, so the tooltip asks it. (Handle ids come from a process-global counter and never collide, so this was always a blank tooltip, never another effect's data.)

Tooltips are also rebuilt on hover rather than on open: UWidget::SetToolTip wraps a UUserWidget in an SToolTip, so a widget implementing IToolTip never receives OnOpening. A tooltip built once at bind time goes stale — or blank — the moment the effect instance it was built from expires while the group lives on.

Validation

A status-panel effect must name one status (Status.*), not a category. A registered but non-Status tag passes tag validation and does render, but the panel keys one icon per status tag, so a second effect under the same category tag would silently collapse into the first. The ability validator now rejects it. See Content Validation.

The world-space status widget

UStatusWidget is the floating readout over an enemy: health with a trailing bar, its status icons, and — on Elite tier and above — the stance bar that fills toward a poise break. It is created per target by UStatusController and lives on a UStatusViewModel registered per enemy under a GUID-derived name, which sidesteps the tag-keyed registration collision the shared status entries use.

Concern Behavior
Spawn SpawnStatus (damage number + widget) or RevealStatus (widget only, no number)
Stance data The replicated Poise / MaxPoise attributes — not GetPoisePercentage(), which reads the server-only poise component and returns a flat 1.0 on clients
Broken state The break label replaces the bar rather than stacking on it
Tier gate Elite+; AEternalEnemy.ThreatTier had to start replicating for clients to apply it at all
Lifetime Refreshed on each spawn/reveal, self-expiring afterwards

RevealStatus exists because a parry deals no health damage: the widget's only other spawn path is the damage-number cue, which returns early on zero magnitude, so the entire parried-attacker feedback loop was invisible. See Poise System.


Granted Powers Widgets

The player's non-ability powers — Echo Mods, Greed Blessings, procs, conditionals, chants — surface on two surfaces sharing one snapshot type (FPowerEntry, an immutable snapshot owned by the list ViewModel).

Widget Where Job
UGrantedPowersTrayWidget HUD Compact live tray; flashes and plays a proc sound when a power fires, collapses the tail into an overflow count
UGrantedPowerEntryWidget HUD tray One tray entry: icon, proc flash, hover tooltip
UPowerListWidget Character sheet One list in the Modifiers section (Granted Powers or Triggered Effects); rebuilds a fixed-column grid on entry-set change
UPowerCardWidget Character sheet One card: name, type tag, effect line, source line, and a Desire meter when sealed

Cards take no ViewModel. The list owns the entries and pushes each snapshot in through SetEntry, so a card is a pure renderer — which is why applying the card's WidgetForge spec does not invalidate the panels that use it (they reference it as a class, not as an embedded instance).

Card presentation rules

  • Type is carried by colour, one per entry type, applied to the name, the top-right tag and the rim light.
  • Sealed cards are not dimmed. Dimming made the SEALED tag itself illegible; instead the type colour is replaced by a neutral grey. The absence of a type colour is what reads as sealed, and the tag stays readable saying so.
  • The rim light is an additive UI material, not a tinted image. A tinted UImage can never blow out — alpha blending caps at the source colour — so a lamp/bloom look needs an additive material driven through a dynamic material instance. Colour genuinely varies per card; brightness does not, which is why brightness belongs on the material rather than being written from C++ every SetEntry.
  • A styled CommonTextBlock cannot take an inline colour override — it re-derives from its style every SynchronizeProperties. Contrast problems are fixed on the style asset or with a new style, never inline.

Inventory Widgets

UInventoryWidget

Main inventory container with item grid.

+--------------------------------+
|       InventoryWidget          |
+--------------------------------+
| Item Level: 45                 |
+--------------------------------+
| +----+ +----+ +----+ +----+   |
| |Item| |Item| |Item| |Item|   |
| +----+ +----+ +----+ +----+   |
| +----+ +----+ +----+ +----+   |
| |Item| |Item| |    | |    |   |
| +----+ +----+ +----+ +----+   |
+--------------------------------+
Element Purpose
ItemLevel Average equipped item level
ItemGrid UniformGridPanel of ItemWidgets

UItemWidget

Reusable item display with drag-drop and tooltips.

+------------------+
|    ItemWidget    |
+------------------+
| +------------+   |
| |            |   |
| | ItemIcon   |   |
| |            |   |
| +------------+   |
|                  |
|       [x5]       |  Stack count (if stackable)
+------------------+
Feature How It Works
Tooltip Creates ItemTooltipWidget on hover
Drag Creates UItemDragDropPayload
Stack Display Shows count for stackable items
Detailed Mode Toggle with modifier key
Cursor Pickup Plain LMB release (armed a drag but never dragged, no modifiers) → TryPickUpToCursor()InventoryController::PickUpItem
Cursor Follow SetCursorFollowMode(true) makes the widget the held-item visual: NativeTick calls SnapToCursor() (centre under cursor), HitTestInvisible, added straight to the viewport by the controller (no separate overlay class)

UItemDropCatcherWidget

Full-screen widget catching item drops to world.

+------------------------------------------+
|         ItemDropCatcherWidget            |
|  (Covers entire viewport)                |
|                                          |
|  Drop item here = spawn in world         |
|                                          |
+------------------------------------------+
Behavior Description
Z-Order Lowest, behind all panels
OnDrop Extracts payload, calls InventoryController
OnItemDroppedToWorld Blueprint event for VFX
Cursor Hold Shared with the cursor-pickup model: NativeOnMouseButtonDown LMB → DropHeldItemToWorld, RMB → cancel

Cursor Pickup (Held-Item) Support

The click-to-hold ("item on cursor") model reuses the existing widgets rather than adding new panels. See the Inventory System — Cursor Pickup Model for the server-side design; the UI side is:

Widget Role
UItemWidget (cursor-follow mode) Doubles as the held-item visual — no dedicated overlay widget class
UItemGridWidget (preview-phase routing) Intercepts clicks while a hold is active and dispatches place/swap/merge

UItemGridWidget routes held clicks in the tunnel (preview) phase via NativeOnPreviewMouseButtonDown, so the grid sees the click before any child ItemWidget — occupied and empty cells route identically, and drag vs. hold are mutually exclusive by construction:

NativeOnPreviewMouseButtonDown  (only while Controller->IsHoldingItem())
   RMB → Controller->CancelHeldItem()          (item reappears at its anchor)
   LMB → HandleHeldClick(geometry, event)
             ComputeDropTopLeft(FPointerEvent)   [shared snap math, generalized from FDragDropEvent]
             ResolveDropAction(.., bDisplaceToCursor = true)   [shared with the drag path]
             dispatch → PlaceHeldItemInContainer | PlaceHeldItemOntoOccupant | MergeHeldItemInto
                        | PlaceHeldItemInEquipmentSlot | keep-holding (Reject)

NativeOnMouseMove calls UpdateHeldHoverPreview while holding, reusing the drag preview paint (green/yellow/red; equipment slots highlight the whole slot; merge highlights the target stack).


Tooltip Widgets

UItemTooltipWidget

Comprehensive item information tooltip.

+--------------------------------+
|       ItemTooltipWidget        |
+--------------------------------+
| [Icon] ITEM NAME               |
|        Item Level 45           |
|        One-Handed Sword        |
|        Tainted    <- greed tint|  (collapsed when Clean)
+--------------------------------+
| Damage: 120-180                |
| Attack Speed: 1.4              |
+--------------------------------+
| IMPLICIT MODIFIERS             |
|   +15% Critical Strike         |
+--------------------------------+
| PREFIX MODIFIERS               |
|   +25 Strength                 |
|   +12% Physical Damage         |
+--------------------------------+
| SUFFIX MODIFIERS               |
|   +12% Fire Resistance         |
+--------------------------------+
| BLESSING (Consumed, Alt mode)  |
|   +8% Fire Damage              |
+--------------------------------+
| SCALING: [S] Str  [B] Dex      |
+--------------------------------+
Section Widget / Element When Shown
Modifier Entries ModifierEntryWidget (dynamic) Always (per modifier list)
Scaling Attributes ScalingAttributeEntryWidget (dynamic) Weapons
Greed State GreedStateText (tinted line) Collapsed when Clean; C++ tints + shows it for Touched/Tainted/Corrupting/Consumed
Blessing BlessingHeaderText + BlessingModifiersBox Consumed items only, in detailed/Alt mode (mirrors the Echo section)

The greed line and Blessing header tints are read from the shared FGreedStateColors table, so they match the crafting window exactly. See the Greed System for the state model.

USkillTooltipWidget

Ability information tooltip.

+--------------------------------+
|       SkillTooltipWidget       |
+--------------------------------+
| [Icon] ABILITY NAME            |
|        Melee Attack            |
+--------------------------------+
| Deals 150-200 physical damage  |
| to enemies in front of you.    |
+--------------------------------+
| Stamina Cost: 25               |
| Cooldown: 0.5s                 |
+--------------------------------+
| COMBO CHAIN (detailed mode)    |
|   1: 100% damage, 100% stamina |
|   2: 120% damage, 110% stamina |
|   3: 150% damage, 120% stamina |
+--------------------------------+
Property Source
Static Data UpdateFromAbility() - name, icon, base description
Dynamic Data RefreshDynamicValues() - damage, costs, combos

UModifierEntryWidget

Individual modifier display line.

+--------------------------------+
|      ModifierEntryWidget       |
+--------------------------------+
| +25% Critical Strike Chance    |
|                           [T3] |  (detailed mode)
+--------------------------------+

UScalingAttributeEntryWidget

Weapon scaling rank display.

+------------------+
|  [S] Strength    |
|  +15% damage     |
+------------------+
Rank Meaning
S Exceptional scaling
A Very good scaling
B Good scaling
C Average scaling
D Poor scaling
E Minimal scaling

UStatEntryTooltipWidget

Breakdown tooltip for a character-sheet stat row ("where on my gear does this stat come from"). It has no ViewModel — the owning UStatEntryWidget pushes content on hover via SetupTooltip(name, value, description, sources, footer). The per-source lines come from UStatEntryViewModel::GetStatSources(), a TArray<FStatSourceEntry> that the CharacterController pre-formats (see Character Widgets).

+--------------------------------+
|     StatEntryTooltipWidget     |
+--------------------------------+
| Strength                125    |  Header (name + total)
| Raw physical power...          |  Description
+--------------------------------+
|   Base                 +10     |
|   Vessel Plate         +37     |  Source rows (StatSourceRowWidget)
|   Curse of Weakness     -7     |  (negative → NegativeValueColor)
+--------------------------------+
| From gear and attributes       |  Context footer
+--------------------------------+
Element Bound As Notes
HeaderNameText / HeaderValueText BindWidget Stat name + total
DescriptionText BindWidget Stat description
SourcesSection BindWidget Divider + rows + footer; collapsed when the stat has no gear sources
SourcesBox BindWidget Container the per-source rows are created into
FooterText BindWidget Context line ("from your gear" / "from your attributes")
SourceRowClass EditDefaultsOnly TSubclassOf<UStatSourceRowWidget> for one line

UStatSourceRowWidget

One line of the breakdown: contributor name + its pre-formatted contribution. No ViewModel — rows are transient display children created by UStatEntryTooltipWidget. SetSource(FStatSourceEntry) fills SourceNameText and SourceValueText; a negative contribution (bNegative) uses NegativeValueColor (the value is a plain UTextBlock so the per-row color override sticks).


Crafting Widgets

UCraftingWidget

Crafting window bound to UCraftingViewModel for recipe state, resource display, and the result preview. Beyond the recipe/result surface, it renders the greed state of the target item so the player sees corruption build up as they craft.

+--------------------------------+
|        CraftingWidget          |
+--------------------------------+
|  Recipe: Lure of Cinders       |
|  Cost: 75 / 120                |
|  [ Tainted ]   <- greed badge  |  (collapsed when Clean)
+--------------------------------+
|  +~~~~~~~~~~~~~~~~~~~~~~~~~~+   |
|  | GreedStateImage overlay |   |  corruption MID, eased between states
|  | (paint-mask band)       |   |
|  +~~~~~~~~~~~~~~~~~~~~~~~~~~+   |
|  | RESULT PREVIEW          |   |
|  +-------------------------+   |
|                                |
|        [ Craft ]  [ X ]        |
+--------------------------------+

The greed surface is two bound widgets: a text badge and an image overlay. Both are optional binds, so a Blueprint that omits them simply shows no greed feedback.

Property Purpose
GreedStateText Greed badge (Touched/Tainted/Corrupting/Consumed); collapsed when Clean. Tinted from the shared color table.
GreedStateImage Corruption overlay image layered over the panel; its dynamic material is driven by the greed state.
GreedOverlayMaterial EditAnywhere class-default material applied to GreedStateImage at NativeConstruct. Falls back to the image brush's own material when left unset.
GreedStateMID Cached UMaterialInstanceDynamic created from the overlay material; the live target of every parameter write.
GreedTweenTime Seconds to ease between greed states (default 0.45); a hard cut at 0 would snap.

Overlay material contract. GreedStateMID exposes two parameters the widget drives:

Parameter Type Meaning
Progress Scalar Corruption fill amount (0 = Clean, adds nothing; rises toward 1 with greed)
StateColor Vector Tint for the current greed state, from FGreedStateColors

Easing & lifecycle. When the greed state changes the widget sets new Progress/StateColor targets and starts a self-stopping FTSTicker (TickGreedTween) that eases GreedFillCurrent/GreedColorCurrent toward them, then removes itself once settled — no per-frame NativeTick cost while idle. NativeDestruct resets the tween state (bGreedInitialized, current/target fill and color, ticker handle), so reopening the window on a different item snaps to that item's state instead of animating from the previous one.

Element Bound As Notes
RecipeNameText / RecipeDescriptionText BindWidget Selected recipe summary
ResourceCostText / PlayerResourceText BindWidget Cost vs. available
CraftButton / CloseButton BindWidget Execute / dismiss
ModifiersPanel / ModifierEntriesContainer BindWidgetOptional ModifyExisting result preview
ItemDescriptionPanel BindWidgetOptional CreateNew / TransformItem result preview
GreedStateText / GreedStateImage BindWidgetOptional Greed badge + corruption overlay

Character Widgets

UStatEntryWidget

Reusable stat row display.

+--------------------------------+
|       StatEntryWidget          |
+--------------------------------+
| Strength:              125     |
+--------------------------------+
Feature Behavior
Hover Shows stat tooltip with breakdown
Tooltip StatEntryTooltipWidget (per-source breakdown)

Spec Ownership (WidgetForge)

Every widget with a spec in Tools/WidgetForge/specs/ is co-authored through the WidgetForge pipeline — the directory is the list, so consult it rather than any enumeration kept here. Declarative specs live in Tools/WidgetForge/specs/*.spec.json (see Tools/WidgetForge/SPEC_FORMAT.md and Documentation/11_Tools/05_WidgetForge.md). Applying a spec (WidgetForge.Apply) rebuilds the whole widget tree from scratch (deterministic / idempotent; the Blueprint graph and animations are untouched). The widget asset is the source of truth: editing these widgets by hand in the UMG designer is safe — Apply refuses (status:"stale") whenever the widget changed since the spec was last exported, so designer edits can never be silently clobbered. The agent workflow is Export-first: refresh the spec from the widget, edit the spec, then Apply.


Glyph Widgets

UGlyphWallWidget

Container for stone plate display.

+--------------------------------+
|       GlyphWallWidget          |
+--------------------------------+
|  +------------------------+    |
|  |    StonePlateWidget    |    |
|  |   [O] [O] [O]          |    |  Glyph sockets
|  +------------------------+    |
|                                |
| INDIVIDUAL EFFECTS:            |
|   +10% Fire Damage             |
|   +5% Cast Speed               |
|                                |
| CHANT EFFECTS:                 |
|   (Complete patterns for bonus)|
+--------------------------------+
|        [Close Button]          |
+--------------------------------+

UGlyphSocketWidget

Socket on stone plate for glyph placement.

+-----------+
|  Socket   |
|    [O]    |  Empty
+-----------+

+-----------+
|  Socket   |
|  [Glyph]  |  Filled with ItemWidget
+-----------+
Delegate Triggered When
OnGlyphDroppedOnSocket Glyph dragged onto socket
OnGlyphRemovedFromSocket Right-click to remove

W_Modal

The single reusable blocking prompt: build disclaimer, game intro, death screen, and any future confirm. Content is pushed by UModalController — single page or a paged sequence — so the widget itself is pure chrome.

+--------------------------------------------------+
|  (scrim dims everything behind)                  |
|      +----------------------------------+        |
|      |   TITLE            (TS_ModalTitle)|       |
|      |                                   |       |
|      |   Body copy, rich-text markup     |       |
|      |   supported.       (TS_ModalBody) |       |
|      |                                   |       |
|      |     [ Cancel ]      [ Confirm ]   |       |
|      +----------------------------------+        |
+--------------------------------------------------+
Aspect Detail
Authoring WidgetForge-managed — Tools/WidgetForge/specs/W_Modal.spec.json
Chrome Matches the GameMenu look: scrim, T_UI_Panel_TornDark body plate, T_UI_ButtonPlate_M buttons
Typography TS_ModalTitle / TS_ModalBody / TS_ModalButton CommonTextStyles, declared in Tools/WidgetForge/specs/TextStyles.styles.json
Buttons Empty Confirm/Cancel text hides that button; on any page but the last, a button advances the sequence instead of closing

The earlier RetainerBox + MI_UI_Modal material layer was dropped — the torn-plate textures carry the look directly, so the modal no longer pays for an offscreen render target.

See UI Architecture § Modal Content for where the text comes from.


Utility Widgets

UBezierCurveWidget

Custom painting widget for visual connections.

+--------------------------------+
|      BezierCurveWidget         |
+--------------------------------+
|                                |
|    O-----.                     |
|           '----.               |
|                 '----O         |
|                                |
+--------------------------------+
Property Purpose
Connections Array of FBezierConnection
StartPoint/EndPoint Connection endpoints
Color Line color
Thickness Line width

Common Patterns

ViewModel Binding Pattern

Binding Flow:
+--------------------------------+
| NativeConstruct()              |
|   Find ViewModel from MVVM     |
|   BindViewModel()              |
+--------------------------------+
         |
         v
+--------------------------------+
| BindViewModel()                |
|   AddFieldValueChangedDelegate |
|   for each property needed     |
|   Store FDelegateHandle        |
+--------------------------------+
         |
         v
+--------------------------------+
| OnPropertyChanged()            |
|   Read new value from VM       |
|   Update widget display        |
+--------------------------------+
         |
         v
+--------------------------------+
| NativeDestruct()               |
|   UnbindViewModel()            |
|   RemoveFieldValueChanged...   |
+--------------------------------+

Dynamic Child Creation Pattern

When child widgets need to be created based on data:

Dynamic Creation Flow:
+--------------------------------+
| OnPropertyChanged()            |
|   ClearChildren()              |
|   Clear widget map             |
+--------------------------------+
         |
         v
+--------------------------------+
| For each item in array:        |
|   CreateWidget<ChildType>()    |
|   BindToViewModel()            |
|   AddChild()                   |
|   Add to widget map            |
+--------------------------------+

Tooltip Display Pattern

Tooltip Flow:
+--------------------------------+
| NativeOnMouseEnter()           |
|   Create TooltipWidget         |
|   InitializeFromData()         |
|   SetToolTip(Widget)           |
+--------------------------------+
         |
         v
+--------------------------------+
| NativeOnMouseLeave()           |
|   SetToolTip(nullptr)          |
+--------------------------------+

State-color source

Greed tints are never hand-picked per widget. Both the item tooltip (greed line + Blessing header) and the crafting window (badge + overlay StateColor) read the single shared FGreedStateColors table, which UGreedSubsystem populates from UGreedConfigDataAsset (DA_GreedConfig) on init. Designers retune colors in that one data asset (no recompile) and every greed surface updates together — the tooltip and crafting window can never drift apart.

DA_GreedConfig (designer-tunable)
        |
        v  (UGreedSubsystem, on init)
  FGreedStateColors  <- single shared lookup: EGreedState -> FLinearColor
        |
        +--> ItemTooltipWidget  (greed line / Blessing header tint)
        +--> CraftingWidget     (greed badge + overlay StateColor)

See the Greed System for the state model and config asset.


API Reference

Widget Lifecycle Methods

Method When Called Purpose
NativeConstruct() Widget added to viewport Bind ViewModel, setup delegates
NativeDestruct() Widget removed from viewport Unbind, cleanup
NativeTick() Every frame Animations, interpolation

Common Widget Properties

Widget Key Properties
UPlayerHUDWidget Skillbar, StatusEffectPanel, NotificationContainer
USkillSlotWidget SkillIcon, InputLabel, CooldownBar
UItemWidget ItemIconImage, StackCountText, TooltipWidgetClass
UItemTooltipWidget ItemNameText, ModifiersBox, ScalingAttributesBox, GreedStateText, BlessingHeaderText, BlessingModifiersBox
USkillTooltipWidget AbilityNameText, AbilityDescriptionText, CostText
UStatEntryTooltipWidget HeaderNameText, HeaderValueText, DescriptionText, SourcesSection, SourcesBox, FooterText, SourceRowClass
UCraftingWidget RecipeNameText, CraftButton, GreedStateText, GreedStateImage, GreedOverlayMaterial

Widget Delegates

Widget Delegate Purpose
UGlyphSocketWidget OnGlyphDroppedOnSocket Glyph placed
UGlyphSocketWidget OnGlyphRemovedFromSocket Glyph removed
USkillSlotWidget OnTooltipRequested Show ability tooltip

Source References

Widget File
UPlayerHUDWidget Source/ProjectEternal/Public/UI/Widgets/HUD/PlayerHUDWidget.h
USkillbarWidget Source/ProjectEternal/Public/UI/Widgets/HUD/SkillbarWidget.h
USkillSlotWidget Source/ProjectEternal/Public/UI/Widgets/HUD/SkillSlotWidget.h
Vitals bars (health/stamina/poise fills, ghosts, in-bar numbers, poise ticks) Source/ProjectEternal/Public/UI/Widgets/HUD/PlayerHUDWidget.h
UScreenEffectsWidget Source/ProjectEternal/Public/UI/Widgets/HUD/ScreenEffectsWidget.h
UScreenEffectsController / UScreenEffectsViewModel Source/ProjectEternal/Public/UI/Controllers/ScreenEffectsController.h, Source/ProjectEternal/Public/UI/ViewModels/HUD/ScreenEffectsViewModel.h
UScreenEffectsConfig Source/ProjectEternal/Public/UI/Data/ScreenEffectsConfig.h
UStatusEffectPanelWidget Source/ProjectEternal/Public/UI/Widgets/HUD/StatusEffectPanelWidget.h
UStatusEffectEntryWidget Source/ProjectEternal/Public/UI/Widgets/HUD/StatusEffectEntryWidget.h
UStatusEffectEntryViewModel (stack badge, countdown) Source/ProjectEternal/Public/UI/ViewModels/HUD/StatusEffectEntryViewModel.h
StatusEffectDisplay helpers (MergeOverlayModes, BuildStackText, BuildCountdownText) Source/ProjectEternal/Public/UI/StatusEffectDisplayHelper.h
EStatusOverlayMode, per-effect display block Source/ProjectEternal/Public/Abilities/EternalGameplayEffect.h
UStatusWidget (world-space health + stance bar) Source/ProjectEternal/Public/UI/Widgets/HUD/StatusWidget.h
UStatusController (SpawnStatus / RevealStatus / BindTargetStance) Source/ProjectEternal/Public/UI/Controllers/StatusController.h
UGrantedPowersTrayWidget / UGrantedPowerEntryWidget Source/ProjectEternal/Public/UI/Widgets/HUD/GrantedPowersTrayWidget.h, Source/ProjectEternal/Public/UI/Widgets/HUD/GrantedPowerEntryWidget.h
UPowerListWidget / UPowerCardWidget Source/ProjectEternal/Public/UI/Widgets/Character/PowerListWidget.h, Source/ProjectEternal/Public/UI/Widgets/Character/PowerCardWidget.h
UInventoryWidget Source/ProjectEternal/Public/UI/Widgets/Inventory/InventoryWidget.h
UItemGridWidget Source/ProjectEternal/Public/UI/Widgets/Inventory/ItemGridWidget.h
UItemWidget Source/ProjectEternal/Public/UI/Widgets/Items/ItemWidget.h
UItemTooltipWidget Source/ProjectEternal/Public/UI/Widgets/Tooltip/ItemTooltipWidget.h
UItemTooltipWidget greed binds (GreedStateText, BlessingHeaderText, BlessingModifiersBox) Source/ProjectEternal/Public/UI/Widgets/Tooltip/ItemTooltipWidget.h
USkillTooltipWidget Source/ProjectEternal/Public/UI/Widgets/Tooltip/SkillTooltipWidget.h
UStatEntryTooltipWidget Source/ProjectEternal/Public/UI/Widgets/Tooltip/StatEntryTooltipWidget.h
UStatSourceRowWidget Source/ProjectEternal/Public/UI/Widgets/Tooltip/StatSourceRowWidget.h
UStatEntryViewModel / FStatSourceEntry Source/ProjectEternal/Public/UI/ViewModels/Character/StatEntryViewModel.h
UCraftingWidget Source/ProjectEternal/Public/UI/Widgets/Crafting/CraftingWidget.h
FGreedStateColors (shared tint table) Source/ProjectEternal/Public/Greed/Types/GreedStateColors.h
UGlyphWallWidget Source/ProjectEternal/Public/UI/Widgets/Glyph/GlyphWallWidget.h
UGlyphSocketWidget Source/ProjectEternal/Public/UI/Widgets/Glyph/GlyphSocketWidget.h
UBezierCurveWidget Source/ProjectEternal/Public/UI/Widgets/Common/BezierCurveWidget.h


Recent Changes

Date Change Impact
2024-12 SkillSlotWidget supports dynamic tooltip refresh Calls controller's RefreshTooltipValues() when tooltip becomes visible
2024-12 SkillTooltipWidget displays combo and charge info Detailed mode shows combo step multipliers and charge level bonuses
2024-12 Ability display types moved to AbilityDisplayTypes.h ESkillType, FComboStepDisplayInfo, FChargeLevelDisplayInfo now in Abilities module
2026-06-17 Greed presentation in tooltip + crafting window ItemTooltipWidget gains a tinted greed-state line (collapsed when Clean) and a Consumed-only Blessing section (Alt mode); new Crafting Widgets section documents UCraftingWidget's greed badge + corruption overlay (GreedOverlayMaterial/GreedStateMID, Progress/StateColor params, GreedTweenTime FTSTicker easing, NativeDestruct reset). Both surfaces read the shared FGreedStateColors table sourced from DA_GreedConfig
2026-07-02 Cursor-pickup UI (click-to-hold) UItemWidget doubles as the held-item cursor visual (SetCursorFollowMode/TryPickUpToCursor); UItemGridWidget routes held clicks in the tunnel phase (NativeOnPreviewMouseButtonDownHandleHeldClick, shared ComputeDropTopLeft/ResolveDropAction); ItemDropCatcherWidget LMB→drop / RMB→cancel while holding. See Inventory System doc for the server model
2026-07-02 Stat-source breakdown tooltip New UStatEntryTooltipWidget + UStatSourceRowWidget (no ViewModel; content pushed by UStatEntryWidget on hover via SetupTooltip); UStatEntryViewModel exposes GetStatSources (FStatSourceEntry) + GetSourcesFooter, pre-formatted by CharacterController
2026-07-02 WidgetForge spec ownership W_Character / W_StatEntry / W_StatEntryTooltip are authored from Tools/WidgetForge/specs/*.spec.json (SPEC_FORMAT.md); applying a spec rebuilds the widget tree from scratch, so manual editor edits to these widgets are overwritten on the next compile
2026-07-02 WidgetForge co-authoring Widget-as-truth model shipped: full-fidelity Export, staleness gate (Apply refuses stale when the widget changed since the spec's export; -Force to override), PIE guard, token lint, renamedFrom rename safety. Manual UMG edits to spec-managed widgets are now safe; see Documentation/11_Tools/05_WidgetForge.md
2026-07-03 ScreenHitEffects overlay (Tiers 1+2) New UScreenEffectsWidget in W_PlayerHUD (ZOrder -10, HitTestInvisible): low-health vignette (continuous ramp + sub-3Hz swell) and big-hit pulse, driven by UScreenEffectsController -> UScreenEffectsViewModel over the owning ASC only. Tuned via ScreenEffectsConfig data asset; Eternal.ScreenFX.Enabled/.Intensity accessibility CVars. W_PlayerHUD + W_ScreenEffects are WidgetForge-managed (Tools/WidgetForge/specs/). Tier 3 per-ailment accents deferred
2026-07-20 Granted Powers display shipped HUD tray (UGrantedPowersTrayWidget + entries, reactive proc flash, tray-level proc sound, overflow count, hover tooltips) and character-sheet sections (UPowerListWidget + UPowerCardWidget, entries pushed as immutable FPowerEntry snapshots — cards carry no ViewModel of their own)
2026-07-21 HUD v3 art pass Bar track/fill + bottom-scrim textures, menu-strip band plate and glyphs, redrawn attack-mark stamps for 22px legibility, corner marks on weapon skill slots, and a single controller-side icon resolution for skill slots (item art wins over ability art; desired sizes pre-fit so icons no longer flash oversized on spawn). W_SkillPanel / W_SkillSlot adopted into WidgetForge
2026-07-29 Game Menu widget W_GameMenu — ESC overlay with Change Preset (inline confirm, returns to character select) and Resume; UGameMenuController + UGameMenuViewModel; GameMenuWidgetClass on UIConfig
2026-07-27 Status overlay pass (FSH-445/446/447/448) Status entries gained a stack badge and a whole-second countdown driven by the authored EStatusOverlayMode; group modes are folded on an information ladder instead of resolved by application order; tooltips resolve the ASC that owns the hovered effect (an enemy's overhead icon produced a blank frame before) and rebuild on hover, because UMG wraps tooltips in SToolTip so IToolTip::OnOpening never fires
2026-07-27 Power card + HUD icon sizing Torn-plate card header, additive rim-light material driven per entry type through a dynamic material instance, sealed cards no longer dimmed (neutral colour carries the state so the SEALED tag stays legible), DesireBar onto the shared bar textures, and a contrast bump on the micro/note text styles (a styled CommonTextBlock re-derives colour from its style, so overrides must be made on the style asset)
2026-08-06 W_Modal rebuilt on GameMenu chrome Scrim + T_UI_Panel_TornDark + T_UI_ButtonPlate_M; the RetainerBox/MI_UI_Modal layer was dropped; type moved onto TS_ModalTitle/TS_ModalBody/TS_ModalButton CommonTextStyles declared in TextStyles.styles.json. New Modal Widgets section
2026-08-06 Preset card selected state UPresetCardWidget lifts the selected card onto a bright torn-parchment plate with dark ink text; unselected cards keep the dark plate and recede behind a scrim
2026-08-06 Menu strip unseen-item badge UMenuStripWidget pulses a badge on the inventory glyph while the inventory reports unseen items, bound to OnUnseenCountChanged; the pulse is held at 1 Hz, well under the photosensitivity cap
2026-08-06 Spec ownership list de-enumerated The hand-maintained widget list drifted; Tools/WidgetForge/specs/ is now cited as the list itself
2026-07-27 World-space stance bar UStatusWidget gains a poise/stance bar on Elite+ targets, bound to the replicated Poise/MaxPoise attributes; the break label replaces the bar rather than stacking on it. UStatusController::RevealStatus spawns the widget without a damage number so a parry can reveal it