Neue Methode `RefreshFocusedOptions()` hinzugefügt, um die Optionen des fokussierten Akteurs nach einer Interaktion zu aktualisieren. Änderungen an der Navigation im Kontextmenü: Begrenzte Navigation ersetzt zyklische Navigation. `FInteractionOption` erweitert um `TextColorOverride` und `HighlightColorOverride`, um individuelle Farben für Optionen zu ermöglichen. UI-Widgets (`InteractionOptionEntryWidget`) angepasst, um Hintergrund- und Hervorhebungsfarben dynamisch zu setzen. Hinweistext für sekundäre Interaktionen zeigt nun spezifische Optionennamen an.
179 lines
5.8 KiB
C++
179 lines
5.8 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "InteractionPromptWidget.h"
|
|
#include "Interaction/InteractionComponent.h"
|
|
#include "Interaction/Interactable.h"
|
|
#include "Components/TextBlock.h"
|
|
#include "Components/Widget.h"
|
|
#include "SegmentedBarWidget.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 UInteractionPromptWidget::NativeConstruct()
|
|
{
|
|
Super::NativeConstruct();
|
|
SetPromptVisible(TextContainer, PromptText, false);
|
|
if (InfoText)
|
|
{
|
|
InfoText->SetVisibility(ESlateVisibility::Collapsed);
|
|
}
|
|
if (WBP_InteractionsSlider)
|
|
{
|
|
WBP_InteractionsSlider->SetVisibility(ESlateVisibility::Collapsed);
|
|
// USegmentedBarWidget defaults to full (it's shared with the health/stamina bars), but
|
|
// the hold bar should start empty and only fill as OnInteractHoldProgressChanged reports.
|
|
WBP_InteractionsSlider->SetSegments(0.0f, 0.0f);
|
|
}
|
|
}
|
|
|
|
void UInteractionPromptWidget::InitializeInteraction(UInteractionComponent* InInteractionComponent)
|
|
{
|
|
UE_LOG(LogKingshearthLegacy, Log, TEXT("[Interaction] Prompt widget InitializeInteraction: component=%s, PromptText bound: %s, InfoText bound: %s, TextContainer bound: %s, Slider bound: %s"),
|
|
*GetNameSafe(InInteractionComponent),
|
|
PromptText ? TEXT("yes") : TEXT("NO"),
|
|
InfoText ? TEXT("yes") : TEXT("NO"),
|
|
TextContainer ? TEXT("yes") : TEXT("NO"),
|
|
WBP_InteractionsSlider ? TEXT("yes") : TEXT("NO"));
|
|
|
|
if (InteractionComponent)
|
|
{
|
|
InteractionComponent->OnFocusedInteractableChanged.RemoveDynamic(this, &UInteractionPromptWidget::HandleFocusedInteractableChanged);
|
|
InteractionComponent->OnContextMenuOpened.RemoveDynamic(this, &UInteractionPromptWidget::HandleContextMenuOpened);
|
|
InteractionComponent->OnContextMenuClosed.RemoveDynamic(this, &UInteractionPromptWidget::HandleContextMenuClosed);
|
|
InteractionComponent->OnInteractHoldProgressChanged.RemoveDynamic(this, &UInteractionPromptWidget::HandleHoldProgressChanged);
|
|
}
|
|
|
|
InteractionComponent = InInteractionComponent;
|
|
|
|
if (InteractionComponent)
|
|
{
|
|
InteractionComponent->OnFocusedInteractableChanged.AddDynamic(this, &UInteractionPromptWidget::HandleFocusedInteractableChanged);
|
|
InteractionComponent->OnContextMenuOpened.AddDynamic(this, &UInteractionPromptWidget::HandleContextMenuOpened);
|
|
InteractionComponent->OnContextMenuClosed.AddDynamic(this, &UInteractionPromptWidget::HandleContextMenuClosed);
|
|
InteractionComponent->OnInteractHoldProgressChanged.AddDynamic(this, &UInteractionPromptWidget::HandleHoldProgressChanged);
|
|
}
|
|
}
|
|
|
|
void UInteractionPromptWidget::HandleHoldProgressChanged(float Progress)
|
|
{
|
|
if (WBP_InteractionsSlider)
|
|
{
|
|
WBP_InteractionsSlider->SetSegments(Progress, 0.0f);
|
|
}
|
|
}
|
|
|
|
void UInteractionPromptWidget::HandleFocusedInteractableChanged(AActor* FocusedActor, const TArray<FInteractionOption>& Options)
|
|
{
|
|
UE_LOG(LogKingshearthLegacy, Log, TEXT("[Interaction] Prompt widget HandleFocusedInteractableChanged: actor='%s' options=%d"),
|
|
*GetNameSafe(FocusedActor), Options.Num());
|
|
|
|
LastFocusedActor = FocusedActor;
|
|
LastFocusedOptions = Options;
|
|
UpdatePromptVisibility();
|
|
}
|
|
|
|
void UInteractionPromptWidget::HandleContextMenuOpened(const TArray<FInteractionOption>& Options, int32 InitialHighlightedIndex)
|
|
{
|
|
UpdatePromptVisibility();
|
|
}
|
|
|
|
void UInteractionPromptWidget::HandleContextMenuClosed()
|
|
{
|
|
UpdatePromptVisibility();
|
|
}
|
|
|
|
void UInteractionPromptWidget::UpdatePromptVisibility()
|
|
{
|
|
if (!PromptText)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const auto HideAll = [this]()
|
|
{
|
|
SetPromptVisible(TextContainer, PromptText, false);
|
|
if (InfoText)
|
|
{
|
|
InfoText->SetVisibility(ESlateVisibility::Collapsed);
|
|
}
|
|
if (WBP_InteractionsSlider)
|
|
{
|
|
WBP_InteractionsSlider->SetVisibility(ESlateVisibility::Collapsed);
|
|
}
|
|
};
|
|
|
|
// 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())
|
|
{
|
|
HideAll();
|
|
return;
|
|
}
|
|
|
|
AActor* FocusedActor = LastFocusedActor.Get();
|
|
if (!FocusedActor || LastFocusedOptions.Num() == 0)
|
|
{
|
|
HideAll();
|
|
return;
|
|
}
|
|
|
|
// Primary line: "{Verb} with {ObjectName}" (or PromptOverride verbatim, if the primary
|
|
// option set one). Hint line: "Press E" always, plus 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());
|
|
}
|
|
|
|
FString Hint = TEXT("Press E");
|
|
if (LastFocusedOptions.Num() == 2)
|
|
{
|
|
Hint += FString::Printf(TEXT("\nHold E for %s"), *LastFocusedOptions[1].DisplayName.ToString());
|
|
}
|
|
else if (LastFocusedOptions.Num() >= 3)
|
|
{
|
|
Hint += TEXT("\nHold E for Context Menu");
|
|
}
|
|
|
|
PromptText->SetText(FText::FromString(Prompt));
|
|
SetPromptVisible(TextContainer, PromptText, true);
|
|
|
|
if (InfoText)
|
|
{
|
|
InfoText->SetText(FText::FromString(Hint));
|
|
InfoText->SetVisibility(ESlateVisibility::HitTestInvisible);
|
|
}
|
|
|
|
if (WBP_InteractionsSlider)
|
|
{
|
|
WBP_InteractionsSlider->SetVisibility(ESlateVisibility::HitTestInvisible);
|
|
WBP_InteractionsSlider->SetSegments(0.0f, 0.0f);
|
|
}
|
|
}
|