diff --git a/Config/DefaultGame.ini b/Config/DefaultGame.ini index a9f6caa..29bf27f 100644 --- a/Config/DefaultGame.ini +++ b/Config/DefaultGame.ini @@ -1,8 +1,9 @@ [/Script/EngineSettings.GeneralProjectSettings] ProjectID=CE954E444D2A872317AAC38182169D03 -ProjectName=Third Person Game Template +ProjectName=Kingsharth Legacy [ConsoleVariables] CommonUI.CheckKeyboardFocusAndParentage=1 CommonUI.DisallowUserFocusedWidgetForPendingFocusRecipient=1 CommonUI.FallbackToDesiredOnAutoRestoreFailure=1 + diff --git a/Content/Testing/BP_Campfire.uasset b/Content/Testing/BP_Campfire.uasset index cb62416..c4cfddc 100644 Binary files a/Content/Testing/BP_Campfire.uasset and b/Content/Testing/BP_Campfire.uasset differ diff --git a/Content/ThirdPerson/Blueprints/BP_ThirdPersonCharacter.uasset b/Content/ThirdPerson/Blueprints/BP_ThirdPersonCharacter.uasset index 67623f4..b34e369 100644 Binary files a/Content/ThirdPerson/Blueprints/BP_ThirdPersonCharacter.uasset and b/Content/ThirdPerson/Blueprints/BP_ThirdPersonCharacter.uasset differ diff --git a/Content/UI/WBP_InteractionContextMenu.uasset b/Content/UI/WBP_InteractionContextMenu.uasset index 1cc374f..7cd0fee 100644 Binary files a/Content/UI/WBP_InteractionContextMenu.uasset and b/Content/UI/WBP_InteractionContextMenu.uasset differ diff --git a/Content/UI/WBP_InteractionOptionEntry.uasset b/Content/UI/WBP_InteractionOptionEntry.uasset new file mode 100644 index 0000000..0680b24 Binary files /dev/null and b/Content/UI/WBP_InteractionOptionEntry.uasset differ diff --git a/Source/KingshearthLegacy/Interaction/InteractionComponent.cpp b/Source/KingshearthLegacy/Interaction/InteractionComponent.cpp index aef11eb..db42270 100644 --- a/Source/KingshearthLegacy/Interaction/InteractionComponent.cpp +++ b/Source/KingshearthLegacy/Interaction/InteractionComponent.cpp @@ -39,10 +39,11 @@ bool UInteractionComponent::PerformInteractionTrace(FHitResult& OutHit) const if (Owner->Implements()) { - if (IInteractionLookupProvider::Execute_InteractionLookupLoop(Owner, OutHit)) - { - return true; - } + // 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 + // 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(); @@ -87,8 +88,19 @@ void UInteractionComponent::UpdateFocus() { FHitResult Hit; const bool bHit = PerformInteractionTrace(Hit); + const bool bImplementsInteractable = bHit && Hit.GetActor() && Hit.GetActor()->Implements(); + AActor* NewFocusedActor = bImplementsInteractable ? Hit.GetActor() : nullptr; - AActor* NewFocusedActor = (bHit && Hit.GetActor() && Hit.GetActor()->Implements()) ? 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()) { diff --git a/Source/KingshearthLegacy/Interaction/InteractionTypes.h b/Source/KingshearthLegacy/Interaction/InteractionTypes.h index 5765a30..8172338 100644 --- a/Source/KingshearthLegacy/Interaction/InteractionTypes.h +++ b/Source/KingshearthLegacy/Interaction/InteractionTypes.h @@ -5,6 +5,8 @@ #include "CoreMinimal.h" #include "InteractionTypes.generated.h" +class UTexture2D; + /** * A single interaction an IInteractable currently offers. Order matters: * 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 */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction") FName ActionID; + + /** Optional - shown by UInteractionOptionEntryWidget's Icon image when set, hidden otherwise. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Interaction") + TObjectPtr 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; }; diff --git a/Source/KingshearthLegacy/KingshearthLegacy.Build.cs b/Source/KingshearthLegacy/KingshearthLegacy.Build.cs index 136e06a..d53dece 100644 --- a/Source/KingshearthLegacy/KingshearthLegacy.Build.cs +++ b/Source/KingshearthLegacy/KingshearthLegacy.Build.cs @@ -19,6 +19,7 @@ public class KingshearthLegacy : ModuleRules "GameplayStateTreeModule", "UMG", "Slate", + "SlateCore", "GameplayAbilities", "GameplayTags", "GameplayTasks" diff --git a/Source/KingshearthLegacy/UI/InteractionContextMenuWidget.cpp b/Source/KingshearthLegacy/UI/InteractionContextMenuWidget.cpp index dc353dc..f22ec64 100644 --- a/Source/KingshearthLegacy/UI/InteractionContextMenuWidget.cpp +++ b/Source/KingshearthLegacy/UI/InteractionContextMenuWidget.cpp @@ -1,25 +1,41 @@ // Copyright Epic Games, Inc. All Rights Reserved. #include "InteractionContextMenuWidget.h" +#include "InteractionOptionListItem.h" #include "Interaction/InteractionComponent.h" #include "Interaction/Interactable.h" -#include "Components/VerticalBox.h" +#include "Components/ListView.h" #include "Components/TextBlock.h" -#include "Blueprint/WidgetTree.h" +#include "Components/Widget.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() { Super::NativeConstruct(); - if (PromptText) - { - PromptText->SetVisibility(ESlateVisibility::Collapsed); - } + SetPromptVisible(TextContainer, PromptText, false); - 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& Options) +{ + LastFocusedActor = FocusedActor; + LastFocusedOptions = Options; + UpdatePromptVisibility(); +} + +void UInteractionContextMenuWidget::UpdatePromptVisibility() { if (!PromptText) { 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; } - // "{Verb} with {ObjectName}" on line one, "Press E" always, then a hint for the - // hold behaviour only when there is one (2 options = secondary, 3+ = Context Menu). - const FText ObjectName = IInteractable::Execute_GetInteractableDisplayName(FocusedActor); - FString Prompt = ObjectName.IsEmpty() - ? Options[0].DisplayName.ToString() - : FString::Printf(TEXT("%s with %s"), *Options[0].DisplayName.ToString(), *ObjectName.ToString()); + AActor* FocusedActor = LastFocusedActor.Get(); + 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); + Prompt = ObjectName.IsEmpty() + ? PrimaryOption.DisplayName.ToString() + : FString::Printf(TEXT("%s with %s"), *PrimaryOption.DisplayName.ToString(), *ObjectName.ToString()); + } Prompt += TEXT("\nPress E"); - if (Options.Num() == 2) + if (LastFocusedOptions.Num() == 2) { Prompt += TEXT("\nHold E for secondary interaction"); } - else if (Options.Num() >= 3) + else if (LastFocusedOptions.Num() >= 3) { Prompt += TEXT("\nHold E for Context Menu"); } PromptText->SetText(FText::FromString(Prompt)); - PromptText->SetVisibility(ESlateVisibility::HitTestInvisible); + SetPromptVisible(TextContainer, PromptText, true); } -void UInteractionContextMenuWidget::RefreshOptions(const TArray& Options, int32 HighlightedIndex) +void UInteractionContextMenuWidget::PopulateOptionsList(const TArray& Options) { - if (!OptionsBox) + if (!OptionsList) { return; } - OptionsBox->ClearChildren(); + OptionsList->ClearListItems(); + ListItems.Reset(Options.Num()); - for (int32 Index = 0; Index < Options.Num(); ++Index) + TArray ItemObjects; + ItemObjects.Reserve(Options.Num()); + + for (const FInteractionOption& OptionEntry : Options) { - UTextBlock* Row = WidgetTree->ConstructWidget(UTextBlock::StaticClass()); - Row->SetText(Options[Index].DisplayName); - Row->SetColorAndOpacity(FSlateColor(Index == HighlightedIndex ? HighlightedColor : NormalColor)); - OptionsBox->AddChildToVerticalBox(Row); + UInteractionOptionListItem* Item = NewObject(this); + Item->Option = OptionEntry; + ListItems.Add(Item); + 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& Options, int32 InitialHighlightedIndex) { - UE_LOG(LogKingshearthLegacy, Log, TEXT("[Interaction] Widget received OnContextMenuOpened: %d option(s), OptionsBox bound: %s"), - Options.Num(), OptionsBox ? TEXT("yes") : TEXT("NO - add a VerticalBox named exactly \"OptionsBox\" in this widget's Designer tree")); + UE_LOG(LogKingshearthLegacy, Log, TEXT("[Interaction] Widget received OnContextMenuOpened: %d option(s), OptionsList bound: %s"), + Options.Num(), OptionsList ? TEXT("yes") : TEXT("NO - add a ListView named exactly \"OptionsList\" in this widget's Designer tree")); 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) { - RefreshOptions(CachedOptions, NewHighlightedIndex); + SelectHighlighted(NewHighlightedIndex); } void UInteractionContextMenuWidget::HandleContextMenuClosed() { CachedOptions.Reset(); + ListItems.Reset(); - if (OptionsBox) + if (OptionsList) { - OptionsBox->ClearChildren(); - OptionsBox->SetVisibility(ESlateVisibility::Collapsed); + OptionsList->ClearListItems(); + OptionsList->SetVisibility(ESlateVisibility::Collapsed); } + + UpdatePromptVisibility(); } diff --git a/Source/KingshearthLegacy/UI/InteractionContextMenuWidget.h b/Source/KingshearthLegacy/UI/InteractionContextMenuWidget.h index 99ad1c1..3dc3abe 100644 --- a/Source/KingshearthLegacy/UI/InteractionContextMenuWidget.h +++ b/Source/KingshearthLegacy/UI/InteractionContextMenuWidget.h @@ -8,8 +8,10 @@ #include "InteractionContextMenuWidget.generated.h" class UInteractionComponent; -class UVerticalBox; +class UInteractionOptionListItem; +class UListView; class UTextBlock; +class UWidget; /** * 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 * IInteractable, regardless of option count or whether Interact is even pressed) and * 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 * you want in the Designer, named exactly as below; all population/highlight/show-hide * logic lives here in C++. @@ -37,8 +39,11 @@ protected: virtual void NativeConstruct() override; - /** Rebuilds OptionsBox's rows from Options, styling HighlightedIndex differently from the rest. */ - void RefreshOptions(const TArray& Options, int32 HighlightedIndex); + /** Rebuilds OptionsList's items from Options; ListItems is kept alive here so the ListView's UObjects don't get GC'd. */ + void PopulateOptionsList(const TArray& Options); + + /** Selects ListItems[Index] on OptionsList, which drives each row's IUserObjectListEntry::NativeOnItemSelectionChanged. */ + void SelectHighlighted(int32 Index); UFUNCTION() void HandleFocusedInteractableChanged(AActor* FocusedActor, const TArray& Options); @@ -52,25 +57,45 @@ protected: UFUNCTION() 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. */ UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional = true)) TObjectPtr 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)) - TObjectPtr OptionsBox; + TObjectPtr 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 OptionsList; UPROPERTY() TArray CachedOptions; + /** UObject wrappers backing OptionsList - one per CachedOptions entry, rebuilt in PopulateOptionsList. */ + UPROPERTY() + TArray> ListItems; + UPROPERTY() TObjectPtr 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 LastFocusedActor; + + UPROPERTY() + TArray LastFocusedOptions; }; diff --git a/Source/KingshearthLegacy/UI/InteractionOptionEntryWidget.cpp b/Source/KingshearthLegacy/UI/InteractionOptionEntryWidget.cpp new file mode 100644 index 0000000..5a70655 --- /dev/null +++ b/Source/KingshearthLegacy/UI/InteractionOptionEntryWidget.cpp @@ -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(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(Highlight.Get())) + { + HighlightImage->SetColorAndOpacity(HighlightColor); + } + else if (UBorder* HighlightBorder = Cast(Highlight.Get())) + { + HighlightBorder->SetBrushColor(HighlightColor); + } + } + + if (InteractionText) + { + InteractionText->SetColorAndOpacity(bHighlighted ? HighlightedTextColor : NormalTextColor); + } +} diff --git a/Source/KingshearthLegacy/UI/InteractionOptionEntryWidget.h b/Source/KingshearthLegacy/UI/InteractionOptionEntryWidget.h new file mode 100644 index 0000000..465d392 --- /dev/null +++ b/Source/KingshearthLegacy/UI/InteractionOptionEntryWidget.h @@ -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 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 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 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; +}; diff --git a/Source/KingshearthLegacy/UI/InteractionOptionListItem.h b/Source/KingshearthLegacy/UI/InteractionOptionListItem.h new file mode 100644 index 0000000..fd1bb85 --- /dev/null +++ b/Source/KingshearthLegacy/UI/InteractionOptionListItem.h @@ -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; +};