CommonUI Integration
Summary: Project Eternal uses CommonUI for consistent input handling and widget patterns. Widgets extend UCommonUserWidget for gamepad navigation and input consistency. The IToolTip interface integrates with Unreal's native tooltip system. Input routing uses Enhanced Input through UEternalInputSubsystem, with mouse events broadcast as delegates for widget subscription.
Table of Contents
Why CommonUI
Design Goals
- Input Consistency: Same interaction patterns across mouse, keyboard, gamepad
- Platform Parity: Widgets work correctly on PC, console, and handheld
- Native Tooltip Support: IToolTip interface hooks into Unreal's tooltip system
- Focus Management: CommonUI handles focus chains and navigation
Key Tradeoffs
| Decision |
Benefit |
Cost |
| CommonUI for Interactive |
Consistent input handling |
Additional dependency |
| UUserWidget for Containers |
Lighter weight for layout-only |
Manual input handling if needed |
| IToolTip Interface |
Native tooltip timing and positioning |
Must implement all interface methods |
| Delegate Broadcasts |
Widgets decouple from input system |
Extra subscription boilerplate |
+---------------------------+
| UUserWidget | Unreal Base
+---------------------------+
|
+------------------------+
| |
v v
+---------------------------+ +---------------------------+
| UCommonUserWidget | | UUserWidget |
| (CommonUI) | | (Standard) |
+---------------------------+ +---------------------------+
| - Gamepad navigation | | - Layout containers |
| - Input consistency | | - Non-interactive |
| - Platform awareness | | - Lighter weight |
+---------------------------+ +---------------------------+
| |
v v
+---------------------------+ +---------------------------+
| Interactive Widgets: | | Container Widgets: |
| - ItemWidget | | - PlayerHUDWidget |
| - ItemDropCatcherWidget | | - InventoryWidget |
| - GlyphWallWidget | | - CharacterWidget |
| - GlyphSocketWidget | | - CraftingWidget |
| - StonePlateWidget | | - SkillbarWidget |
| - AutomapWidget | +---------------------------+
| - ItemTooltipWidget |
| - SkillTooltipWidget |
| - StatusEffectTooltipWidget
+---------------------------+
Selection Criteria
| Use CommonUserWidget When |
Use Standard UserWidget When |
| Widget handles mouse/touch input |
Widget is layout-only |
| Widget needs gamepad navigation |
Widget contains other widgets |
| Widget implements IToolTip |
Widget has no direct interaction |
| Widget supports drag-drop |
Widget is purely visual |
Player Input Device
|
v
+------------------------+
| Enhanced Input System |
+------------------------+
|
v
+------------------------+
| UEternalInputSubsystem |
| (LocalPlayerSubsystem) |
+------------------------+
|
+---> UI Toggles --> Controllers --> Widgets
|
+---> Mouse Clicks --> Broadcast Delegates
| |
| v
| [Subscribed Widgets]
| - ItemWidget
| - CraftingWidget
| - etc.
|
+---> Gameplay --> PlayerController/Pawn
Mouse Event Broadcasting
Why Broadcasts? Widgets don't need direct coupling to input system. They subscribe to what they care about.
Input Event Flow:
+---------------------------+
| Player clicks LMB |
+---------------------------+
|
v
+---------------------------+
| EnhancedInputComponent |
| triggers LMB action |
+---------------------------+
|
v
+---------------------------+
| EternalInputSubsystem |
| HandleLeftMouseClick() |
+---------------------------+
|
v
+---------------------------+
| OnLeftMouseClick |
| .Broadcast() |
+---------------------------+
|
+------+------+
| |
v v
[Widget A] [Widget B]
OnLeftClick OnLeftClick
Widgets subscribe in NativeConstruct, unsubscribe in NativeDestruct:
Widget Subscription Lifecycle:
+---------------------------+
| NativeConstruct() |
| Get LocalPlayer |
| Get InputSubsystem |
| AddDynamic(OnLeftClick) |
| AddDynamic(OnRightClick)|
+---------------------------+
|
v
[Widget Active]
|
v
+---------------------------+
| NativeDestruct() |
| RemoveDynamic(...) |
+---------------------------+
Tooltip widgets implement Unreal's native IToolTip interface for proper timing and positioning.
+---------------------------+
| UCommonUserWidget |
| + |
| IToolTip |
+---------------------------+
|
v
+---------------------------+
| Tooltip Widget Types |
| |
| - ItemTooltipWidget |
| - SkillTooltipWidget |
| - StatusEffectTooltipWidget
+---------------------------+
| Method |
Purpose |
Typical Implementation |
| AsWidget() |
Return Slate widget |
TakeWidget() |
| GetContentWidget() |
Return content for sizing |
Same as AsWidget or cached |
| SetContentWidget() |
Set external content |
Store in member |
| IsEmpty() |
Check if has data |
TooltipData == nullptr |
| IsInteractive() |
Allow mouse interaction |
Usually false |
| OnOpening() |
Called when shown |
Refresh display |
| OnClosed() |
Called when hidden |
Optional cleanup |
Mouse enters widget
|
v
+---------------------------+
| UItemWidget |
| NativeOnMouseEnter() |
+---------------------------+
|
v
+---------------------------+
| UpdateTooltip() |
| Create TooltipWidget |
| InitializeFromData() |
| SetToolTip(Widget) |
+---------------------------+
|
v
+---------------------------+
| Unreal Tooltip System |
| Applies delay |
| Positions tooltip |
| Calls OnOpening() |
+---------------------------+
|
v
+---------------------------+
| TooltipWidget visible |
+---------------------------+
Mouse leaves widget
|
v
+---------------------------+
| NativeOnMouseLeave() |
| SetToolTip(nullptr) |
+---------------------------+
|
v
+---------------------------+
| Unreal calls OnClosed() |
+---------------------------+
Drag-Drop Support
Drag-Drop Architecture
+---------------------------+
| Drag Source | (e.g., ItemWidget)
+---------------------------+
|
| Create UDragDropOperation
| with Payload
v
+---------------------------+
| UDragDropOperation |
| - Payload: UItemDragDropPayload
| - DefaultDragVisual |
| - Pivot |
+---------------------------+
|
v
+---------------------------+
| Drop Target | (e.g., GlyphSocketWidget,
| | ItemDropCatcherWidget)
+---------------------------+
|
v
+---------------------------+
| NativeOnDrop() |
| Extract payload |
| Validate drop |
| Execute action |
+---------------------------+
UItemDragDropPayload
Carries item data during drag operations:
| Property |
Type |
Purpose |
| Item |
UItemObject* |
The dragged item |
| ItemContainerComponent |
UItemContainerComponent* |
Source container |
| TopLeftIndex |
int32 |
Grid position in source |
Drop Targets
| Widget |
Accepts |
Action |
| UItemDropCatcherWidget |
Any item |
Drop item to world |
| UGlyphSocketWidget |
Glyph items |
Socket glyph in plate |
| InventorySlot |
Any item |
Move/swap items |
| EquipmentSlot |
Equipment |
Equip item |
Full-Screen Drop Catcher
The ItemDropCatcherWidget covers the viewport to catch drops that miss valid targets:
+------------------------------------------+
| ItemDropCatcherWidget |
| (Full viewport, lowest Z-order) |
| |
| +---------------------------+ |
| | Inventory Panel | |
| | (Higher Z-order) | |
| | | |
| | [Item] [Item] [Item] | |
| | [Item] [Item] [Item] | |
| +---------------------------+ |
| |
| Drop here = item goes to world |
+------------------------------------------+
Widgets declare child widget references with automatic UMG binding:
| Meta Specifier |
Behavior |
BindWidget |
Required - widget must exist in UMG |
BindWidgetOptional |
Optional - can be null |
Widget Class Hierarchy:
+--------------------------------+
| C++ Widget Class |
| UPROPERTY(BindWidget) |
| UTextBlock* ItemNameText; |
+--------------------------------+
|
v
+--------------------------------+
| Blueprint Widget (BP_MyWidget)|
| [ItemNameText] TextBlock |
| (Name matches property) |
+--------------------------------+
Binding Flow:
+--------------------------------+
| NativeConstruct() |
| If (CraftButton) |
| CraftButton->OnClicked |
| .AddDynamic(this, |
| &OnCraftButtonClicked) |
+--------------------------------+
|
v
+--------------------------------+
| User clicks button |
+--------------------------------+
|
v
+--------------------------------+
| OnCraftButtonClicked() |
| Call controller method |
+--------------------------------+
|
v
+--------------------------------+
| NativeDestruct() |
| (Delegates auto-cleaned) |
+--------------------------------+
API Reference
| Method |
When Called |
Purpose |
| NativeOnMouseEnter() |
Mouse enters bounds |
Start hover effects, show tooltip |
| NativeOnMouseLeave() |
Mouse exits bounds |
End hover, hide tooltip |
| NativeOnMouseButtonDown() |
Mouse button pressed |
Start drag, hide tooltip |
| NativeOnMouseButtonUp() |
Mouse button released |
End drag |
| NativeOnDrop() |
Drop operation completes |
Handle dropped item |
| NativeOnDragEnter() |
Drag enters bounds |
Show drop preview |
| NativeOnDragLeave() |
Drag exits bounds |
Hide drop preview |
| Method |
Return |
Purpose |
| AsWidget() |
TSharedRef |
Return Slate widget |
| GetContentWidget() |
TSharedRef |
Return content widget |
| SetContentWidget() |
void |
Set external content |
| IsEmpty() |
bool |
Check if tooltip has data |
| IsInteractive() |
bool |
Allow mouse over tooltip |
| OnOpening() |
void |
Called when tooltip shown |
| OnClosed() |
void |
Called when tooltip hidden |
| Delegate |
Signature |
Purpose |
| OnLeftMouseClick |
FOnLeftMouseClick |
LMB press |
| OnRightMouseClick |
FOnRightMouseClick |
RMB press |
| Type |
Properties |
| UCommonTextBlock |
Text rendering with CommonUI styling |
| UCommonRichTextBlock |
Formatted text with styling support |
| UCommonButtonBase |
Button with CommonUI input handling |
Source References
| Class |
File |
Line |
| UItemWidget |
Source/ProjectEternal/Public/UI/Widgets/Items/ItemWidget.h |
1 |
| UItemDropCatcherWidget |
Source/ProjectEternal/Public/UI/Widgets/Items/ItemDropCatcherWidget.h |
1 |
| UItemTooltipWidget |
Source/ProjectEternal/Public/UI/Widgets/Tooltip/ItemTooltipWidget.h |
1 |
| USkillTooltipWidget |
Source/ProjectEternal/Public/UI/Widgets/Tooltip/SkillTooltipWidget.h |
1 |
| UGlyphSocketWidget |
Source/ProjectEternal/Public/UI/Widgets/Glyph/GlyphSocketWidget.h |
1 |
| UEternalInputSubsystem |
Source/ProjectEternal/Public/Input/EternalInputSubsystem.h |
1 |
| UItemDragDropPayload |
Source/ProjectEternal/Public/UI/Widgets/Items/ItemDragDropPayload.h |
1 |
Recent Changes
| Date |
Change |
Impact |
| 2024-12 |
SkillTooltipController uses SetCombatComponent() for dependency injection |
Controller no longer discovers CombatComponent from pawn; must be explicitly provided |
| 2024-12 |
SkillTooltipViewModel RefreshDynamicValues() |
Dynamic data (damage, costs) recalculated on demand, not on every hover |