Verbesserung der Interaktionslogik und UI-Optimierung

Die Änderungen umfassen:
- Projektname in `DefaultGame.ini` auf "Kingshearth Legacy" geändert.
- Neue Konsolenvariablen für CommonUI hinzugefügt.
- `ListView` ersetzt `VerticalBox` für Kontextmenü-Optionen.
- `FInteractionOption` um `Icon` und `PromptOverride` erweitert.
- Debugging-Logs in `InteractionComponent.cpp` hinzugefügt.
- Neue Klassen: `UInteractionOptionEntryWidget` und `UInteractionOptionListItem`.
- Verbesserte Eingabeaufforderungslogik und Sichtbarkeitssteuerung.
- Binärdateien (`.uasset`) aktualisiert und neue hinzugefügt.
This commit is contained in:
2026-09-05 19:01:41 +02:00
parent fd08fe4804
commit 0b34a9742a
13 changed files with 370 additions and 56 deletions
+2 -1
View File
@@ -1,8 +1,9 @@
[/Script/EngineSettings.GeneralProjectSettings] [/Script/EngineSettings.GeneralProjectSettings]
ProjectID=CE954E444D2A872317AAC38182169D03 ProjectID=CE954E444D2A872317AAC38182169D03
ProjectName=Third Person Game Template ProjectName=Kingsharth Legacy
[ConsoleVariables] [ConsoleVariables]
CommonUI.CheckKeyboardFocusAndParentage=1 CommonUI.CheckKeyboardFocusAndParentage=1
CommonUI.DisallowUserFocusedWidgetForPendingFocusRecipient=1 CommonUI.DisallowUserFocusedWidgetForPendingFocusRecipient=1
CommonUI.FallbackToDesiredOnAutoRestoreFailure=1 CommonUI.FallbackToDesiredOnAutoRestoreFailure=1
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -39,10 +39,11 @@ bool UInteractionComponent::PerformInteractionTrace(FHitResult& OutHit) const
if (Owner->Implements<UInteractionLookupProvider>()) if (Owner->Implements<UInteractionLookupProvider>())
{ {
if (IInteractionLookupProvider::Execute_InteractionLookupLoop(Owner, OutHit)) // Authoritative once implemented - a miss here means "nothing found", full stop.
{ // Don't silently fall through to the default trace below just because this one
return true; // came back empty; that would use a completely different (un-offset) trace and
} // could contradict what the override deliberately decided.
return IInteractionLookupProvider::Execute_InteractionLookupLoop(Owner, OutHit);
} }
FVector TraceStart = Owner->GetActorLocation(); FVector TraceStart = Owner->GetActorLocation();
@@ -87,8 +88,19 @@ void UInteractionComponent::UpdateFocus()
{ {
FHitResult Hit; FHitResult Hit;
const bool bHit = PerformInteractionTrace(Hit); const bool bHit = PerformInteractionTrace(Hit);
const bool bImplementsInteractable = bHit && Hit.GetActor() && Hit.GetActor()->Implements<UInteractable>();
AActor* NewFocusedActor = bImplementsInteractable ? Hit.GetActor() : nullptr;
AActor* NewFocusedActor = (bHit && Hit.GetActor() && Hit.GetActor()->Implements<UInteractable>()) ? Hit.GetActor() : nullptr; #if ENABLE_DRAW_DEBUG
if (bShowDebugTraces)
{
UE_LOG(LogKingshearthLegacy, Log, TEXT("[Interaction] Tick: bHit=%s hitActor='%s' implementsInteractable=%s currentFocused='%s'"),
bHit ? TEXT("true") : TEXT("false"),
*GetNameSafe(bHit ? Hit.GetActor() : nullptr),
bImplementsInteractable ? TEXT("true") : TEXT("false"),
*GetNameSafe(FocusedActor.Get()));
}
#endif
if (NewFocusedActor == FocusedActor.Get()) if (NewFocusedActor == FocusedActor.Get())
{ {
@@ -5,6 +5,8 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "InteractionTypes.generated.h" #include "InteractionTypes.generated.h"
class UTexture2D;
/** /**
* A single interaction an IInteractable currently offers. Order matters: * A single interaction an IInteractable currently offers. Order matters:
* index 0 is always the primary (tap) action, index 1 (if present) is the * index 0 is always the primary (tap) action, index 1 (if present) is the
@@ -22,4 +24,17 @@ struct FInteractionOption
/** Opaque id passed back to IInteractable::ExecuteInteraction - the object interprets it itself */ /** Opaque id passed back to IInteractable::ExecuteInteraction - the object interprets it itself */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction") UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction")
FName ActionID; FName ActionID;
/** Optional - shown by UInteractionOptionEntryWidget's Icon image when set, hidden otherwise. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction")
TObjectPtr<UTexture2D> Icon = nullptr;
/**
* Optional - when this is the primary (index 0) option and this is set, the ever-present
* prompt's first line uses this verbatim instead of building "{DisplayName} with
* {ObjectName}" - e.g. a chest can set this to "Open Chest" instead of ending up as
* "Open with Chest". The "Press E" / "Hold E for ..." lines are unaffected.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction")
FText PromptOverride;
}; };
@@ -19,6 +19,7 @@ public class KingshearthLegacy : ModuleRules
"GameplayStateTreeModule", "GameplayStateTreeModule",
"UMG", "UMG",
"Slate", "Slate",
"SlateCore",
"GameplayAbilities", "GameplayAbilities",
"GameplayTags", "GameplayTags",
"GameplayTasks" "GameplayTasks"
@@ -1,25 +1,41 @@
// Copyright Epic Games, Inc. All Rights Reserved. // Copyright Epic Games, Inc. All Rights Reserved.
#include "InteractionContextMenuWidget.h" #include "InteractionContextMenuWidget.h"
#include "InteractionOptionListItem.h"
#include "Interaction/InteractionComponent.h" #include "Interaction/InteractionComponent.h"
#include "Interaction/Interactable.h" #include "Interaction/Interactable.h"
#include "Components/VerticalBox.h" #include "Components/ListView.h"
#include "Components/TextBlock.h" #include "Components/TextBlock.h"
#include "Blueprint/WidgetTree.h" #include "Components/Widget.h"
#include "KingshearthLegacy.h" #include "KingshearthLegacy.h"
namespace
{
/** Toggles TextContainer when bound (so a background living alongside PromptText hides with it), else PromptText itself. */
void SetPromptVisible(UWidget* TextContainer, UTextBlock* PromptText, bool bVisible)
{
const ESlateVisibility Visibility = bVisible ? ESlateVisibility::HitTestInvisible : ESlateVisibility::Collapsed;
if (TextContainer)
{
TextContainer->SetVisibility(Visibility);
}
else if (PromptText)
{
PromptText->SetVisibility(Visibility);
}
}
}
void UInteractionContextMenuWidget::NativeConstruct() void UInteractionContextMenuWidget::NativeConstruct()
{ {
Super::NativeConstruct(); Super::NativeConstruct();
if (PromptText) SetPromptVisible(TextContainer, PromptText, false);
{
PromptText->SetVisibility(ESlateVisibility::Collapsed);
}
if (OptionsBox) if (OptionsList)
{ {
OptionsBox->SetVisibility(ESlateVisibility::Collapsed); OptionsList->SetVisibility(ESlateVisibility::Collapsed);
} }
} }
@@ -45,84 +61,139 @@ void UInteractionContextMenuWidget::InitializeInteraction(UInteractionComponent*
} }
void UInteractionContextMenuWidget::HandleFocusedInteractableChanged(AActor* FocusedActor, const TArray<FInteractionOption>& Options) void UInteractionContextMenuWidget::HandleFocusedInteractableChanged(AActor* FocusedActor, const TArray<FInteractionOption>& Options)
{
LastFocusedActor = FocusedActor;
LastFocusedOptions = Options;
UpdatePromptVisibility();
}
void UInteractionContextMenuWidget::UpdatePromptVisibility()
{ {
if (!PromptText) if (!PromptText)
{ {
return; return;
} }
if (!FocusedActor || Options.Num() == 0) // The Context Menu owns the screen while it's open - keep the ever-present prompt
// out of its way until OnContextMenuClosed brings us back here.
if (InteractionComponent && InteractionComponent->IsContextMenuOpen())
{ {
PromptText->SetVisibility(ESlateVisibility::Collapsed); SetPromptVisible(TextContainer, PromptText, false);
return; return;
} }
// "{Verb} with {ObjectName}" on line one, "Press E" always, then a hint for the AActor* FocusedActor = LastFocusedActor.Get();
// hold behaviour only when there is one (2 options = secondary, 3+ = Context Menu). if (!FocusedActor || LastFocusedOptions.Num() == 0)
{
SetPromptVisible(TextContainer, PromptText, false);
return;
}
// "{Verb} with {ObjectName}" on line one (or PromptOverride verbatim, if the primary
// option set one), "Press E" always, then a hint for the hold behaviour only when
// there is one (2 options = secondary, 3+ = Context Menu).
const FInteractionOption& PrimaryOption = LastFocusedOptions[0];
FString Prompt;
if (!PrimaryOption.PromptOverride.IsEmpty())
{
Prompt = PrimaryOption.PromptOverride.ToString();
}
else
{
const FText ObjectName = IInteractable::Execute_GetInteractableDisplayName(FocusedActor); const FText ObjectName = IInteractable::Execute_GetInteractableDisplayName(FocusedActor);
FString Prompt = ObjectName.IsEmpty() Prompt = ObjectName.IsEmpty()
? Options[0].DisplayName.ToString() ? PrimaryOption.DisplayName.ToString()
: FString::Printf(TEXT("%s with %s"), *Options[0].DisplayName.ToString(), *ObjectName.ToString()); : FString::Printf(TEXT("%s with %s"), *PrimaryOption.DisplayName.ToString(), *ObjectName.ToString());
}
Prompt += TEXT("\nPress E"); Prompt += TEXT("\nPress E");
if (Options.Num() == 2) if (LastFocusedOptions.Num() == 2)
{ {
Prompt += TEXT("\nHold E for secondary interaction"); Prompt += TEXT("\nHold E for secondary interaction");
} }
else if (Options.Num() >= 3) else if (LastFocusedOptions.Num() >= 3)
{ {
Prompt += TEXT("\nHold E for Context Menu"); Prompt += TEXT("\nHold E for Context Menu");
} }
PromptText->SetText(FText::FromString(Prompt)); PromptText->SetText(FText::FromString(Prompt));
PromptText->SetVisibility(ESlateVisibility::HitTestInvisible); SetPromptVisible(TextContainer, PromptText, true);
} }
void UInteractionContextMenuWidget::RefreshOptions(const TArray<FInteractionOption>& Options, int32 HighlightedIndex) void UInteractionContextMenuWidget::PopulateOptionsList(const TArray<FInteractionOption>& Options)
{ {
if (!OptionsBox) if (!OptionsList)
{ {
return; return;
} }
OptionsBox->ClearChildren(); OptionsList->ClearListItems();
ListItems.Reset(Options.Num());
for (int32 Index = 0; Index < Options.Num(); ++Index) TArray<UObject*> ItemObjects;
ItemObjects.Reserve(Options.Num());
for (const FInteractionOption& OptionEntry : Options)
{ {
UTextBlock* Row = WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass()); UInteractionOptionListItem* Item = NewObject<UInteractionOptionListItem>(this);
Row->SetText(Options[Index].DisplayName); Item->Option = OptionEntry;
Row->SetColorAndOpacity(FSlateColor(Index == HighlightedIndex ? HighlightedColor : NormalColor)); ListItems.Add(Item);
OptionsBox->AddChildToVerticalBox(Row); ItemObjects.Add(Item);
}
OptionsList->SetListItems(ItemObjects);
}
void UInteractionContextMenuWidget::SelectHighlighted(int32 Index)
{
if (!OptionsList)
{
return;
}
if (ListItems.IsValidIndex(Index))
{
OptionsList->SetSelectedItem(ListItems[Index]);
}
else
{
OptionsList->ClearSelection();
} }
} }
void UInteractionContextMenuWidget::HandleContextMenuOpened(const TArray<FInteractionOption>& Options, int32 InitialHighlightedIndex) void UInteractionContextMenuWidget::HandleContextMenuOpened(const TArray<FInteractionOption>& Options, int32 InitialHighlightedIndex)
{ {
UE_LOG(LogKingshearthLegacy, Log, TEXT("[Interaction] Widget received OnContextMenuOpened: %d option(s), OptionsBox bound: %s"), UE_LOG(LogKingshearthLegacy, Log, TEXT("[Interaction] Widget received OnContextMenuOpened: %d option(s), OptionsList bound: %s"),
Options.Num(), OptionsBox ? TEXT("yes") : TEXT("NO - add a VerticalBox named exactly \"OptionsBox\" in this widget's Designer tree")); Options.Num(), OptionsList ? TEXT("yes") : TEXT("NO - add a ListView named exactly \"OptionsList\" in this widget's Designer tree"));
CachedOptions = Options; CachedOptions = Options;
RefreshOptions(CachedOptions, InitialHighlightedIndex); PopulateOptionsList(CachedOptions);
SelectHighlighted(InitialHighlightedIndex);
if (OptionsBox) if (OptionsList)
{ {
OptionsBox->SetVisibility(ESlateVisibility::HitTestInvisible); OptionsList->SetVisibility(ESlateVisibility::Visible);
} }
UpdatePromptVisibility();
} }
void UInteractionContextMenuWidget::HandleContextMenuHighlightChanged(int32 NewHighlightedIndex) void UInteractionContextMenuWidget::HandleContextMenuHighlightChanged(int32 NewHighlightedIndex)
{ {
RefreshOptions(CachedOptions, NewHighlightedIndex); SelectHighlighted(NewHighlightedIndex);
} }
void UInteractionContextMenuWidget::HandleContextMenuClosed() void UInteractionContextMenuWidget::HandleContextMenuClosed()
{ {
CachedOptions.Reset(); CachedOptions.Reset();
ListItems.Reset();
if (OptionsBox) if (OptionsList)
{ {
OptionsBox->ClearChildren(); OptionsList->ClearListItems();
OptionsBox->SetVisibility(ESlateVisibility::Collapsed); OptionsList->SetVisibility(ESlateVisibility::Collapsed);
} }
UpdatePromptVisibility();
} }
@@ -8,8 +8,10 @@
#include "InteractionContextMenuWidget.generated.h" #include "InteractionContextMenuWidget.generated.h"
class UInteractionComponent; class UInteractionComponent;
class UVerticalBox; class UInteractionOptionListItem;
class UListView;
class UTextBlock; class UTextBlock;
class UWidget;
/** /**
* One widget covering both interaction UI needs so you only wire up a single thing * One widget covering both interaction UI needs so you only wire up a single thing
@@ -17,7 +19,7 @@ class UTextBlock;
* driven by OnFocusedInteractableChanged - shows any time the look trace is on an * driven by OnFocusedInteractableChanged - shows any time the look trace is on an
* IInteractable, regardless of option count or whether Interact is even pressed) and * IInteractable, regardless of option count or whether Interact is even pressed) and
* the Context Menu shown while holding Interact on a 3+-option IInteractable * the Context Menu shown while holding Interact on a 3+-option IInteractable
* (OptionsBox, driven by OnContextMenuOpened/HighlightChanged/Closed). Both bound * (OptionsList, driven by OnContextMenuOpened/HighlightChanged/Closed). Both bound
* widgets are optional and toggle their own visibility independently - add whichever * widgets are optional and toggle their own visibility independently - add whichever
* you want in the Designer, named exactly as below; all population/highlight/show-hide * you want in the Designer, named exactly as below; all population/highlight/show-hide
* logic lives here in C++. * logic lives here in C++.
@@ -37,8 +39,11 @@ protected:
virtual void NativeConstruct() override; virtual void NativeConstruct() override;
/** Rebuilds OptionsBox's rows from Options, styling HighlightedIndex differently from the rest. */ /** Rebuilds OptionsList's items from Options; ListItems is kept alive here so the ListView's UObjects don't get GC'd. */
void RefreshOptions(const TArray<FInteractionOption>& Options, int32 HighlightedIndex); void PopulateOptionsList(const TArray<FInteractionOption>& Options);
/** Selects ListItems[Index] on OptionsList, which drives each row's IUserObjectListEntry::NativeOnItemSelectionChanged. */
void SelectHighlighted(int32 Index);
UFUNCTION() UFUNCTION()
void HandleFocusedInteractableChanged(AActor* FocusedActor, const TArray<FInteractionOption>& Options); void HandleFocusedInteractableChanged(AActor* FocusedActor, const TArray<FInteractionOption>& Options);
@@ -52,25 +57,45 @@ protected:
UFUNCTION() UFUNCTION()
void HandleContextMenuClosed(); void HandleContextMenuClosed();
/** Text color used for the highlighted row. */
UPROPERTY(EditAnywhere, Category = "Interaction|Style")
FLinearColor HighlightedColor = FLinearColor(1.0f, 0.85f, 0.3f, 1.0f);
/** Text color used for every other row. */
UPROPERTY(EditAnywhere, Category = "Interaction|Style")
FLinearColor NormalColor = FLinearColor::White;
/** Ever-present prompt - shows the primary option's DisplayName any time the look trace is on an IInteractable. Optional, place it in the WBP subclass's Designer tree. */ /** Ever-present prompt - shows the primary option's DisplayName any time the look trace is on an IInteractable. Optional, place it in the WBP subclass's Designer tree. */
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true)) UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
TObjectPtr<UTextBlock> PromptText; TObjectPtr<UTextBlock> PromptText;
/** Container the Context Menu's option rows are built into. Optional, place it in the WBP subclass's Designer tree. */ /**
* Optional wrapper around PromptText (e.g. a panel/border providing its background) named
* exactly "TextContainer" in the Designer. When bound, its visibility is toggled instead of
* PromptText's own - so a background that lives alongside PromptText, not inside it, actually
* hides too. Falls back to toggling PromptText directly when this isn't bound.
*/
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true)) UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
TObjectPtr<UVerticalBox> OptionsBox; TObjectPtr<UWidget> TextContainer;
/**
* The Context Menu's option list. Optional, place it in the WBP subclass's Designer
* tree as a ListView, single-selection, with its Entry Widget Class set to a WBP
* subclass of UInteractionOptionEntryWidget - that class receives each row's data
* and highlighted state natively, no BP event graph logic needed.
*/
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
TObjectPtr<UListView> OptionsList;
UPROPERTY() UPROPERTY()
TArray<FInteractionOption> CachedOptions; TArray<FInteractionOption> CachedOptions;
/** UObject wrappers backing OptionsList - one per CachedOptions entry, rebuilt in PopulateOptionsList. */
UPROPERTY()
TArray<TObjectPtr<UInteractionOptionListItem>> ListItems;
UPROPERTY() UPROPERTY()
TObjectPtr<UInteractionComponent> InteractionComponent; TObjectPtr<UInteractionComponent> InteractionComponent;
private:
/** Rebuilds PromptText from LastFocused*, collapsing it while the Context Menu is open. Called on focus change and on menu close. */
void UpdatePromptVisibility();
TWeakObjectPtr<AActor> LastFocusedActor;
UPROPERTY()
TArray<FInteractionOption> LastFocusedOptions;
}; };
@@ -0,0 +1,84 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "InteractionOptionEntryWidget.h"
#include "InteractionOptionListItem.h"
#include "Components/Border.h"
#include "Components/Image.h"
#include "Components/TextBlock.h"
#include "Components/Widget.h"
void UInteractionOptionEntryWidget::NativeConstruct()
{
Super::NativeConstruct();
// The ListView only calls NativeOnItemSelectionChanged when a row's selection state
// actually changes, so a freshly-spawned row that has never been (de)selected would
// otherwise keep whatever visibility Highlight has in the Designer - force it into
// the correct "not highlighted" state right away instead.
ApplyHighlightVisuals();
}
void UInteractionOptionEntryWidget::NativeOnListItemObjectSet(UObject* ListItemObject)
{
if (const UInteractionOptionListItem* ListItem = Cast<UInteractionOptionListItem>(ListItemObject))
{
Option = ListItem->Option;
ApplyOptionVisuals();
// SListView recycles row widgets between items; a row reassigned from a previously-
// selected item to a new one doesn't necessarily get a fresh NativeOnItemSelectionChanged
// call in the same pass, so re-derive the real selection state here rather than trusting
// whatever bHighlighted this (possibly recycled) widget already had.
bHighlighted = IsListItemSelected();
ApplyHighlightVisuals();
}
}
void UInteractionOptionEntryWidget::NativeOnItemSelectionChanged(bool bIsSelected)
{
bHighlighted = bIsSelected;
ApplyHighlightVisuals();
}
void UInteractionOptionEntryWidget::ApplyOptionVisuals()
{
if (InteractionText)
{
InteractionText->SetText(Option.DisplayName);
}
if (Icon)
{
if (Option.Icon)
{
Icon->SetBrushFromTexture(Option.Icon);
Icon->SetVisibility(ESlateVisibility::HitTestInvisible);
}
else
{
Icon->SetVisibility(ESlateVisibility::Collapsed);
}
}
}
void UInteractionOptionEntryWidget::ApplyHighlightVisuals()
{
if (Highlight)
{
Highlight->SetVisibility(bHighlighted ? ESlateVisibility::HitTestInvisible : ESlateVisibility::Hidden);
if (UImage* HighlightImage = Cast<UImage>(Highlight.Get()))
{
HighlightImage->SetColorAndOpacity(HighlightColor);
}
else if (UBorder* HighlightBorder = Cast<UBorder>(Highlight.Get()))
{
HighlightBorder->SetBrushColor(HighlightColor);
}
}
if (InteractionText)
{
InteractionText->SetColorAndOpacity(bHighlighted ? HighlightedTextColor : NormalTextColor);
}
}
@@ -0,0 +1,81 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "Blueprint/IUserObjectListEntry.h"
#include "Interaction/InteractionTypes.h"
#include "InteractionOptionEntryWidget.generated.h"
class UImage;
class UTextBlock;
class UWidget;
/**
* One row of the Context Menu's OptionsList (a UListView). Assign a WBP
* subclass of this as that ListView's Entry Widget Class in the Designer -
* name your widgets exactly "InteractionText" (Text), "Icon" (Image) and
* "Highlight" (a Border, or an Image - either works, tinted with
* HighlightColor) and all population/highlight styling is handled here in
* C++, driven by the ListView itself via IUserObjectListEntry; no BP event
* graph logic needed.
*/
UCLASS(abstract)
class KINGSHEARTHLEGACY_API UInteractionOptionEntryWidget : public UUserWidget, public IUserObjectListEntry
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintPure, Category = "Interaction")
const FInteractionOption& GetOption() const { return Option; }
UFUNCTION(BlueprintPure, Category = "Interaction")
bool IsHighlighted() const { return bHighlighted; }
protected:
virtual void NativeConstruct() override;
/** IUserObjectListEntry - called by the owning ListView when this row is assigned an UInteractionOptionListItem. */
virtual void NativeOnListItemObjectSet(UObject* ListItemObject) override;
/** IUserObjectListEntry - called by the owning ListView when this row's selected state changes (drives the Context Menu highlight). */
virtual void NativeOnItemSelectionChanged(bool bIsSelected) override;
void ApplyOptionVisuals();
/** Applies bHighlighted to Highlight's visibility/tint and InteractionText's color. Called on construct (so freshly-spawned rows never default to "selected") and on every selection change. */
void ApplyHighlightVisuals();
/** Row label - bind a TextBlock named exactly "InteractionText" in the Designer. */
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
TObjectPtr<UTextBlock> InteractionText;
/** Option icon - bind an Image named exactly "Icon" in the Designer. Hidden automatically when the option has no Icon set. */
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
TObjectPtr<UImage> Icon;
/** Highlight background - bind a Border or an Image named exactly "Highlight" in the Designer. Shown and tinted HighlightColor only while this row is selected/highlighted. */
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true))
TObjectPtr<UWidget> Highlight;
/** Tint applied to Highlight while this row is highlighted. */
UPROPERTY(EditAnywhere, Category = "Interaction|Style")
FLinearColor HighlightColor = FLinearColor(1.0f, 0.85f, 0.3f, 1.0f);
/** InteractionText color while this row is NOT highlighted. */
UPROPERTY(EditAnywhere, Category = "Interaction|Style")
FLinearColor NormalTextColor = FLinearColor::White;
/** InteractionText color while this row IS highlighted. */
UPROPERTY(EditAnywhere, Category = "Interaction|Style")
FLinearColor HighlightedTextColor = FLinearColor::Black;
UPROPERTY(BlueprintReadOnly, Category = "Interaction")
FInteractionOption Option;
UPROPERTY(BlueprintReadOnly, Category = "Interaction")
bool bHighlighted = false;
};
@@ -0,0 +1,24 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/Object.h"
#include "Interaction/InteractionTypes.h"
#include "InteractionOptionListItem.generated.h"
/**
* Plain UObject wrapper around one FInteractionOption - UListView items must be
* UObjects, not structs, so UInteractionContextMenuWidget wraps each option in
* one of these before calling OptionsList->SetListItems.
*/
UCLASS()
class KINGSHEARTHLEGACY_API UInteractionOptionListItem : public UObject
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadOnly, Category = "Interaction")
FInteractionOption Option;
};