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:
@@ -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
|
||||
|
||||
|
||||
Binary file not shown.
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 (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<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())
|
||||
{
|
||||
|
||||
@@ -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<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",
|
||||
"UMG",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"GameplayAbilities",
|
||||
"GameplayTags",
|
||||
"GameplayTasks"
|
||||
|
||||
@@ -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<FInteractionOption>& 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<FInteractionOption>& Options, int32 HighlightedIndex)
|
||||
void UInteractionContextMenuWidget::PopulateOptionsList(const TArray<FInteractionOption>& Options)
|
||||
{
|
||||
if (!OptionsBox)
|
||||
if (!OptionsList)
|
||||
{
|
||||
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());
|
||||
Row->SetText(Options[Index].DisplayName);
|
||||
Row->SetColorAndOpacity(FSlateColor(Index == HighlightedIndex ? HighlightedColor : NormalColor));
|
||||
OptionsBox->AddChildToVerticalBox(Row);
|
||||
UInteractionOptionListItem* Item = NewObject<UInteractionOptionListItem>(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<FInteractionOption>& 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();
|
||||
}
|
||||
|
||||
@@ -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<FInteractionOption>& 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<FInteractionOption>& Options);
|
||||
|
||||
/** Selects ListItems[Index] on OptionsList, which drives each row's IUserObjectListEntry::NativeOnItemSelectionChanged. */
|
||||
void SelectHighlighted(int32 Index);
|
||||
|
||||
UFUNCTION()
|
||||
void HandleFocusedInteractableChanged(AActor* FocusedActor, const TArray<FInteractionOption>& 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<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))
|
||||
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()
|
||||
TArray<FInteractionOption> CachedOptions;
|
||||
|
||||
/** UObject wrappers backing OptionsList - one per CachedOptions entry, rebuilt in PopulateOptionsList. */
|
||||
UPROPERTY()
|
||||
TArray<TObjectPtr<UInteractionOptionListItem>> ListItems;
|
||||
|
||||
UPROPERTY()
|
||||
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;
|
||||
};
|
||||
Reference in New Issue
Block a user