第一次提交
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "UI/Actions/GUIS_AsyncAction_CreateWidget.h"
|
||||
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "Blueprint/WidgetBlueprintLibrary.h"
|
||||
#include "Engine/AssetManager.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "Engine/StreamableManager.h"
|
||||
#include "UI/GUIS_GameUIFunctionLibrary.h"
|
||||
#include "UObject/Stack.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GUIS_AsyncAction_CreateWidget)
|
||||
|
||||
class UUserWidget;
|
||||
|
||||
static const FName InputFilterReason_Template = FName(TEXT("CreatingWidgetAsync"));
|
||||
|
||||
UGUIS_AsyncAction_CreateWidget::UGUIS_AsyncAction_CreateWidget(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
, bSuspendInputUntilComplete(true)
|
||||
{
|
||||
}
|
||||
|
||||
UGUIS_AsyncAction_CreateWidget* UGUIS_AsyncAction_CreateWidget::CreateWidgetAsync(UObject* InWorldContextObject, TSoftClassPtr<UUserWidget> InUserWidgetSoftClass, APlayerController* InOwningPlayer,
|
||||
bool bSuspendInputUntilComplete)
|
||||
{
|
||||
if (InUserWidgetSoftClass.IsNull())
|
||||
{
|
||||
FFrame::KismetExecutionMessage(TEXT("CreateWidgetAsync was passed a null UserWidgetSoftClass"), ELogVerbosity::Error);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UWorld* World = GEngine->GetWorldFromContextObject(InWorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
|
||||
|
||||
UGUIS_AsyncAction_CreateWidget* Action = NewObject<UGUIS_AsyncAction_CreateWidget>();
|
||||
Action->UserWidgetSoftClass = InUserWidgetSoftClass;
|
||||
Action->OwningPlayer = InOwningPlayer;
|
||||
Action->World = World;
|
||||
Action->GameInstance = World->GetGameInstance();
|
||||
Action->bSuspendInputUntilComplete = bSuspendInputUntilComplete;
|
||||
Action->RegisterWithGameInstance(World);
|
||||
|
||||
return Action;
|
||||
}
|
||||
|
||||
void UGUIS_AsyncAction_CreateWidget::Activate()
|
||||
{
|
||||
SuspendInputToken = bSuspendInputUntilComplete ? UGUIS_GameUIFunctionLibrary::SuspendInputForPlayer(OwningPlayer.Get(), InputFilterReason_Template) : NAME_None;
|
||||
|
||||
TWeakObjectPtr<UGUIS_AsyncAction_CreateWidget> LocalWeakThis(this);
|
||||
StreamingHandle = UAssetManager::Get().GetStreamableManager().RequestAsyncLoad(
|
||||
UserWidgetSoftClass.ToSoftObjectPath(),
|
||||
FStreamableDelegate::CreateUObject(this, &ThisClass::OnWidgetLoaded),
|
||||
FStreamableManager::AsyncLoadHighPriority
|
||||
);
|
||||
|
||||
// Setup a cancel delegate so that we can resume input if this handler is canceled.
|
||||
StreamingHandle->BindCancelDelegate(FStreamableDelegate::CreateWeakLambda(this,
|
||||
[this]()
|
||||
{
|
||||
UGUIS_GameUIFunctionLibrary::ResumeInputForPlayer(OwningPlayer.Get(), SuspendInputToken);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
void UGUIS_AsyncAction_CreateWidget::Cancel()
|
||||
{
|
||||
Super::Cancel();
|
||||
|
||||
if (StreamingHandle.IsValid())
|
||||
{
|
||||
StreamingHandle->CancelHandle();
|
||||
StreamingHandle.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_AsyncAction_CreateWidget::OnWidgetLoaded()
|
||||
{
|
||||
if (bSuspendInputUntilComplete)
|
||||
{
|
||||
UGUIS_GameUIFunctionLibrary::ResumeInputForPlayer(OwningPlayer.Get(), SuspendInputToken);
|
||||
}
|
||||
|
||||
// If the load as successful, create it, otherwise don't complete this.
|
||||
TSubclassOf<UUserWidget> UserWidgetClass = UserWidgetSoftClass.Get();
|
||||
if (UserWidgetClass)
|
||||
{
|
||||
UUserWidget* UserWidget = UWidgetBlueprintLibrary::Create(World.Get(), UserWidgetClass, OwningPlayer.Get());
|
||||
OnComplete.Broadcast(UserWidget);
|
||||
}
|
||||
|
||||
StreamingHandle.Reset();
|
||||
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "UI/Actions/GUIS_AsyncAction_PushContentToUILayer.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include "UI/GUIS_GameUILayout.h"
|
||||
#include "UObject/Stack.h"
|
||||
#include "Widgets/CommonActivatableWidgetContainer.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GUIS_AsyncAction_PushContentToUILayer)
|
||||
|
||||
UGUIS_AsyncAction_PushContentToUILayer::UGUIS_AsyncAction_PushContentToUILayer(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
}
|
||||
|
||||
UGUIS_AsyncAction_PushContentToUILayer* UGUIS_AsyncAction_PushContentToUILayer::PushContentToUILayer(UGUIS_GameUILayout* UILayout,
|
||||
TSoftClassPtr<UCommonActivatableWidget> InWidgetClass, FGameplayTag InLayerName,
|
||||
bool bSuspendInputUntilComplete)
|
||||
{
|
||||
if (!IsValid(UILayout))
|
||||
{
|
||||
FFrame::KismetExecutionMessage(TEXT("PushContentToUILayer was passed a invalid Layout"), ELogVerbosity::Error);
|
||||
return nullptr;
|
||||
}
|
||||
if (InWidgetClass.IsNull())
|
||||
{
|
||||
FFrame::KismetExecutionMessage(TEXT("PushContentToUILayer was passed a null WidgetClass"), ELogVerbosity::Error);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (UWorld* World = GEngine->GetWorldFromContextObject(UILayout->GetWorld(), EGetWorldErrorMode::LogAndReturnNull))
|
||||
{
|
||||
UGUIS_AsyncAction_PushContentToUILayer* Action = NewObject<UGUIS_AsyncAction_PushContentToUILayer>();
|
||||
Action->WidgetClass = InWidgetClass;
|
||||
Action->RootLayout = UILayout;
|
||||
Action->OwningPlayerPtr = UILayout->GetOwningPlayer();
|
||||
Action->LayerName = InLayerName;
|
||||
Action->bSuspendInputUntilComplete = bSuspendInputUntilComplete;
|
||||
Action->RegisterWithGameInstance(World);
|
||||
|
||||
return Action;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UGUIS_AsyncAction_PushContentToUILayer* UGUIS_AsyncAction_PushContentToUILayer::PushContentToUILayerForPlayer(APlayerController* PlayerController,
|
||||
TSoftClassPtr<UCommonActivatableWidget> InWidgetClass,
|
||||
FGameplayTag InLayerName, bool bSuspendInputUntilComplete)
|
||||
{
|
||||
if (!IsValid(PlayerController))
|
||||
{
|
||||
FFrame::KismetExecutionMessage(TEXT("PushContentToUILayerForPlayer was passed a invalid PlayerController"), ELogVerbosity::Error);
|
||||
return nullptr;
|
||||
}
|
||||
if (InWidgetClass.IsNull())
|
||||
{
|
||||
FFrame::KismetExecutionMessage(TEXT("PushContentToUILayer was passed a null WidgetClass"), ELogVerbosity::Error);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UGUIS_GameUILayout* UILayout = UGUIS_GameUIFunctionLibrary::GetGameUILayoutForPlayer(PlayerController);
|
||||
|
||||
if (UILayout == nullptr)
|
||||
{
|
||||
FFrame::KismetExecutionMessage(TEXT("PushContentToUILayerForPlayer failed to find UILayout for player."), ELogVerbosity::Error);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (UWorld* World = GEngine->GetWorldFromContextObject(UILayout->GetWorld(), EGetWorldErrorMode::LogAndReturnNull))
|
||||
{
|
||||
UGUIS_AsyncAction_PushContentToUILayer* Action = NewObject<UGUIS_AsyncAction_PushContentToUILayer>();
|
||||
Action->WidgetClass = InWidgetClass;
|
||||
Action->RootLayout = UILayout;
|
||||
Action->OwningPlayerPtr = PlayerController;
|
||||
Action->LayerName = InLayerName;
|
||||
Action->bSuspendInputUntilComplete = bSuspendInputUntilComplete;
|
||||
Action->RegisterWithGameInstance(World);
|
||||
|
||||
return Action;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
void UGUIS_AsyncAction_PushContentToUILayer::Cancel()
|
||||
{
|
||||
Super::Cancel();
|
||||
|
||||
if (StreamingHandle.IsValid())
|
||||
{
|
||||
StreamingHandle->CancelHandle();
|
||||
StreamingHandle.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_AsyncAction_PushContentToUILayer::Activate()
|
||||
{
|
||||
// if (UGUIS_GameUILayout* RootLayout = UGUIS_GameUILayout::GetPrimaryGameLayout(OwningPlayerPtr.Get()))
|
||||
if (RootLayout.IsValid())
|
||||
{
|
||||
TWeakObjectPtr<UGUIS_AsyncAction_PushContentToUILayer> WeakThis = this;
|
||||
StreamingHandle = RootLayout->PushWidgetToLayerStackAsync<UCommonActivatableWidget>(LayerName, bSuspendInputUntilComplete, WidgetClass,
|
||||
[this, WeakThis](EGUIS_AsyncWidgetLayerState State, UCommonActivatableWidget* Widget)
|
||||
{
|
||||
if (WeakThis.IsValid())
|
||||
{
|
||||
switch (State)
|
||||
{
|
||||
case EGUIS_AsyncWidgetLayerState::Initialize:
|
||||
BeforePush.Broadcast(Widget);
|
||||
break;
|
||||
case EGUIS_AsyncWidgetLayerState::AfterPush:
|
||||
AfterPush.Broadcast(Widget);
|
||||
SetReadyToDestroy();
|
||||
break;
|
||||
case EGUIS_AsyncWidgetLayerState::Canceled:
|
||||
SetReadyToDestroy();
|
||||
break;
|
||||
}
|
||||
}
|
||||
SetReadyToDestroy();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "UI/Actions/GUIS_AsyncAction_ShowModel.h"
|
||||
#include "GUIS_GenericUISystemSettings.h"
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "UI/GUIS_GameUIFunctionLibrary.h"
|
||||
#include "UI/GUIS_GameUILayout.h"
|
||||
#include "UI/GUIS_GameplayTags.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GUIS_AsyncAction_ShowModel)
|
||||
|
||||
UGUIS_AsyncAction_ShowModel::UGUIS_AsyncAction_ShowModel(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
}
|
||||
|
||||
// UGUIS_AsyncAction_ShowModel* UGUIS_AsyncAction_ShowModel::ShowModal(UObject* InWorldContextObject, FGameplayTag ModalTag, UGUIS_ModalDefinition* ModalDefinition)
|
||||
// {
|
||||
// const UGUIS_GameUIData* UIData = UGUIS_GenericUISystemSettings::GetGameUIData();
|
||||
// if (!UIData)
|
||||
// return nullptr;
|
||||
//
|
||||
// const TSoftClassPtr<UGUIS_GameModalWidget> SoftModalWidgetClass = UIData->FindWidgetClassForModal(ModalTag);
|
||||
// if (SoftModalWidgetClass.IsNull())
|
||||
// return nullptr;
|
||||
// const TSubclassOf<UGUIS_GameModalWidget> ModalWidgetClass = SoftModalWidgetClass.LoadSynchronous();
|
||||
// if (ModalWidgetClass == nullptr)
|
||||
// return nullptr;
|
||||
//
|
||||
// UGUIS_AsyncAction_ShowModel* Action = NewObject<UGUIS_AsyncAction_ShowModel>();
|
||||
// Action->ModalWidgetClass = ModalWidgetClass;
|
||||
// Action->WorldContextObject = InWorldContextObject;
|
||||
// Action->ModalDefinition = ModalDefinition;
|
||||
// Action->RegisterWithGameInstance(InWorldContextObject);
|
||||
//
|
||||
// return Action;
|
||||
// }
|
||||
|
||||
UGUIS_AsyncAction_ShowModel* UGUIS_AsyncAction_ShowModel::ShowModal(UObject* InWorldContextObject, TSoftClassPtr<UGUIS_ModalDefinition> ModalDefinition)
|
||||
{
|
||||
if (ModalDefinition.IsNull())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ModalDefinition.LoadSynchronous();
|
||||
|
||||
const UGUIS_ModalDefinition* Modal = ModalDefinition->GetDefaultObject<UGUIS_ModalDefinition>();
|
||||
if (Modal == nullptr)
|
||||
return nullptr;
|
||||
|
||||
if (Modal->ModalWidget.IsNull())
|
||||
return nullptr;
|
||||
|
||||
const TSubclassOf<UGUIS_GameModalWidget> ModalWidgetClass = Modal->ModalWidget.LoadSynchronous();
|
||||
if (ModalWidgetClass == nullptr)
|
||||
return nullptr;
|
||||
|
||||
UGUIS_AsyncAction_ShowModel* Action = NewObject<UGUIS_AsyncAction_ShowModel>();
|
||||
Action->ModalWidgetClass = ModalWidgetClass;
|
||||
Action->WorldContextObject = InWorldContextObject;
|
||||
Action->ModalDefinition = Modal;
|
||||
Action->RegisterWithGameInstance(InWorldContextObject);
|
||||
|
||||
return Action;
|
||||
}
|
||||
|
||||
void UGUIS_AsyncAction_ShowModel::Activate()
|
||||
{
|
||||
if (WorldContextObject && !TargetPlayerController)
|
||||
{
|
||||
if (UUserWidget* UserWidget = Cast<UUserWidget>(WorldContextObject))
|
||||
{
|
||||
TargetPlayerController = UserWidget->GetOwningPlayer<APlayerController>();
|
||||
}
|
||||
else if (APlayerController* PC = Cast<APlayerController>(WorldContextObject))
|
||||
{
|
||||
TargetPlayerController = PC;
|
||||
}
|
||||
else if (UWorld* World = WorldContextObject->GetWorld())
|
||||
{
|
||||
if (UGameInstance* GameInstance = World->GetGameInstance<UGameInstance>())
|
||||
{
|
||||
TargetPlayerController = GameInstance->GetPrimaryPlayerController(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (TargetPlayerController)
|
||||
{
|
||||
if (UGUIS_GameUILayout* Layout = UGUIS_GameUIFunctionLibrary::GetGameUILayoutForPlayer(TargetPlayerController))
|
||||
{
|
||||
FGUIS_ModalActionResultSignature ResultCallback = FGUIS_ModalActionResultSignature::CreateUObject(this, &UGUIS_AsyncAction_ShowModel::HandleModalAction);
|
||||
const UGUIS_ModalDefinition* TempDescriptor = ModalDefinition;
|
||||
Layout->PushWidgetToLayerStack<UGUIS_GameModalWidget>(GUIS_GameUILayerTags::Modal, ModalWidgetClass, [TempDescriptor, ResultCallback](UGUIS_GameModalWidget& ModalInstance)
|
||||
{
|
||||
ModalInstance.SetupModal(TempDescriptor, ResultCallback);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// If we couldn't make the confirmation, just handle an unknown result and broadcast nothing
|
||||
HandleModalAction(GUIS_GameModalActionTags::Unknown);
|
||||
}
|
||||
|
||||
|
||||
void UGUIS_AsyncAction_ShowModel::HandleModalAction(FGameplayTag ModalActionTag)
|
||||
{
|
||||
OnModalAction.Broadcast(ModalActionTag);
|
||||
SetReadyToDestroy();
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "UI/Actions/GUIS_UIAction.h"
|
||||
|
||||
|
||||
UGUIS_UIAction::UGUIS_UIAction(const FObjectInitializer& ObjectInitializer): Super(ObjectInitializer)
|
||||
{
|
||||
}
|
||||
|
||||
bool UGUIS_UIAction::IsCompatible(const UObject* Data) const
|
||||
{
|
||||
return IsCompatibleInternal(Data);
|
||||
}
|
||||
|
||||
|
||||
bool UGUIS_UIAction::CanInvokeInternal_Implementation(const UObject* Data, APlayerController* PlayerController) const
|
||||
{
|
||||
// 与其他验证不同, 这个默认不通过, Override里修改
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UGUIS_UIAction::CanInvoke(const UObject* Data, APlayerController* PlayerController) const
|
||||
{
|
||||
return CanInvokeInternal(Data, PlayerController);
|
||||
}
|
||||
|
||||
void UGUIS_UIAction::InvokeAction(const UObject* Data, APlayerController* PlayerController) const
|
||||
{
|
||||
// if (CanInvoke(Data, User))
|
||||
{
|
||||
InvokeActionInternal(Data, PlayerController);
|
||||
}
|
||||
}
|
||||
|
||||
FText UGUIS_UIAction::GetActionName() const
|
||||
{
|
||||
return DisplayName;
|
||||
}
|
||||
|
||||
FName UGUIS_UIAction::GetActionID() const
|
||||
{
|
||||
return ActionID;
|
||||
}
|
||||
|
||||
bool UGUIS_UIAction::IsCompatibleInternal_Implementation(const UObject* Data) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void UGUIS_UIAction::InvokeActionInternal_Implementation(const UObject* Data, APlayerController* PlayerController) const
|
||||
{
|
||||
}
|
||||
|
||||
UWorld* UGUIS_UIAction::GetWorld() const
|
||||
{
|
||||
if (UObject* Outer = GetOuter())
|
||||
{
|
||||
return Outer->GetWorld();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "UI/Actions/GUIS_UIActionFactory.h"
|
||||
#include "Misc/DataValidation.h"
|
||||
#include "UI/Actions/GUIS_UIAction.h"
|
||||
|
||||
TArray<UGUIS_UIAction*> UGUIS_UIActionFactory::FindAvailableUIActionsForData(const UObject* Data) const
|
||||
{
|
||||
TArray<UGUIS_UIAction*> Ret;
|
||||
for (UGUIS_UIAction* Action : PotentialActions)
|
||||
{
|
||||
if (Action != nullptr && Action->IsCompatible(Data))
|
||||
{
|
||||
Ret.Add(Action);
|
||||
}
|
||||
}
|
||||
return Ret;
|
||||
}
|
||||
|
||||
|
||||
#if WITH_EDITOR
|
||||
EDataValidationResult UGUIS_UIActionFactory::IsDataValid(FDataValidationContext& Context) const
|
||||
{
|
||||
FText ValidationMessage;
|
||||
for (int32 i = 0; i < PotentialActions.Num(); i++)
|
||||
{
|
||||
if (PotentialActions[0] == nullptr)
|
||||
{
|
||||
Context.AddError(FText::FromString(FString::Format(TEXT("Invalid action on index:{0}"), {i})));
|
||||
return EDataValidationResult::Invalid;
|
||||
}
|
||||
}
|
||||
return Super::IsDataValid(Context);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,139 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "UI/Actions/GUIS_UIActionWidget.h"
|
||||
|
||||
#include "GUIS_LogChannels.h"
|
||||
#include "Input/CommonUIInputTypes.h"
|
||||
#include "UI/GUIS_GameplayTags.h"
|
||||
#include "UI/Actions/GUIS_AsyncAction_ShowModel.h"
|
||||
#include "UI/Actions/GUIS_UIActionFactory.h"
|
||||
|
||||
void UGUIS_UIActionWidget::SetAssociatedData(UObject* Data)
|
||||
{
|
||||
if (Data == nullptr)
|
||||
{
|
||||
UnregisterActions();
|
||||
}
|
||||
AssociatedData = Data;
|
||||
}
|
||||
|
||||
void UGUIS_UIActionWidget::RegisterActions()
|
||||
{
|
||||
if (!AssociatedData.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsValid(ActionFactory))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TArray<UGUIS_UIAction*> Actions = ActionFactory->FindAvailableUIActionsForData(AssociatedData.Get());
|
||||
|
||||
for (const UGUIS_UIAction* Action : Actions)
|
||||
{
|
||||
if (Action->CanInvoke(AssociatedData.Get(), GetOwningPlayer()))
|
||||
{
|
||||
FBindUIActionArgs BindArgs(Action->GetInputActionData(), Action->GetShouldDisplayInActionBar(),
|
||||
FSimpleDelegate::CreateLambda([this,Action]()
|
||||
{
|
||||
HandleUIAction(Action);
|
||||
}));
|
||||
|
||||
ActionBindings.Add(RegisterUIActionBinding(BindArgs));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_UIActionWidget::RegisterActionsWithFactory(TSoftObjectPtr<UGUIS_UIActionFactory> InActionFactory)
|
||||
{
|
||||
if (InActionFactory.IsNull())
|
||||
{
|
||||
UE_LOG(LogGUIS, Warning, TEXT("Passed invalid action factory!"))
|
||||
return;
|
||||
}
|
||||
|
||||
UGUIS_UIActionFactory* Factory = InActionFactory.LoadSynchronous();
|
||||
|
||||
if (Factory == nullptr)
|
||||
{
|
||||
UE_LOG(LogGUIS, Warning, TEXT("Failed to load action factory!"))
|
||||
return;
|
||||
}
|
||||
|
||||
ActionFactory = Factory;
|
||||
|
||||
RegisterActions();
|
||||
}
|
||||
|
||||
void UGUIS_UIActionWidget::UnregisterActions()
|
||||
{
|
||||
for (FUIActionBindingHandle& ActionBinding : ActionBindings)
|
||||
{
|
||||
ActionBinding.Unregister();
|
||||
}
|
||||
|
||||
ActionBindings.Empty();
|
||||
CancelAction();
|
||||
}
|
||||
|
||||
void UGUIS_UIActionWidget::CancelAction()
|
||||
{
|
||||
if (ModalTask)
|
||||
{
|
||||
ModalTask->OnModalAction.RemoveDynamic(this, &ThisClass::HandleModalAction);
|
||||
ModalTask->Cancel();
|
||||
ModalTask = nullptr;
|
||||
}
|
||||
CurrentAction = nullptr;
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
const FText UGUIS_UIActionWidget::GetPaletteCategory()
|
||||
{
|
||||
return FText::FromString(TEXT("Generic UI"));
|
||||
}
|
||||
#endif
|
||||
|
||||
void UGUIS_UIActionWidget::HandleUIAction(const UGUIS_UIAction* Action)
|
||||
{
|
||||
if (ModalTask && ModalTask->IsActive())
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (AssociatedData.IsValid())
|
||||
{
|
||||
if (Action->GetRequiresConfirmation() && !Action->GetConfirmationModalClass().IsNull())
|
||||
{
|
||||
ModalTask = UGUIS_AsyncAction_ShowModel::ShowModal(GetWorld(), Action->GetConfirmationModalClass());
|
||||
CurrentAction = Action;
|
||||
ModalTask->OnModalAction.AddDynamic(this, &ThisClass::HandleModalAction);
|
||||
ModalTask->Activate();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Action->CanInvoke(AssociatedData.Get(), GetOwningPlayer()))
|
||||
{
|
||||
Action->InvokeAction(AssociatedData.Get(), GetOwningPlayer());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_UIActionWidget::HandleModalAction(FGameplayTag ActionTag)
|
||||
{
|
||||
if (ActionTag == GUIS_GameModalActionTags::Yes || ActionTag == GUIS_GameModalActionTags::Ok)
|
||||
{
|
||||
if (CurrentAction && CurrentAction->CanInvoke(AssociatedData.Get(), GetOwningPlayer()))
|
||||
{
|
||||
CurrentAction->InvokeAction(AssociatedData.Get(), GetOwningPlayer());
|
||||
}
|
||||
CancelAction();
|
||||
}
|
||||
if (ActionTag == GUIS_GameModalActionTags::No)
|
||||
{
|
||||
CancelAction();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "UI/Common/GUIS_DetailSectionsBuilder.h"
|
||||
|
||||
TArray<TSoftClassPtr<UGUIS_ListEntryDetailSection>> UGUIS_DetailSectionsBuilder::GatherDetailSections_Implementation(const UObject* Data)
|
||||
{
|
||||
TArray<TSoftClassPtr<UGUIS_ListEntryDetailSection>> Sections;
|
||||
return Sections;
|
||||
}
|
||||
|
||||
|
||||
TArray<TSoftClassPtr<UGUIS_ListEntryDetailSection>> UGUIS_DetailSectionBuilder_Class::GatherDetailSections_Implementation(const UObject* Data)
|
||||
{
|
||||
TArray<TSoftClassPtr<UGUIS_ListEntryDetailSection>> Sections;
|
||||
|
||||
// Find extensions for it using the super chain of the setting so that we get any
|
||||
// class based extensions for this setting.
|
||||
for (UClass* Class = Data->GetClass(); Class; Class = Class->GetSuperClass())
|
||||
{
|
||||
FGUIS_EntryDetailsClassSections* ExtensionForClass = SectionsForClasses.Find(Class);
|
||||
if (ExtensionForClass)
|
||||
{
|
||||
Sections.Append(ExtensionForClass->Sections);
|
||||
}
|
||||
}
|
||||
|
||||
return Sections;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "UI/Common/GUIS_ListEntry.h"
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "UI/Common/GUIS_ListEntryDetailSection.h"
|
||||
|
||||
|
||||
void UGUIS_ListEntryDetailSection::SetListItemObject(UObject* ListItemObject)
|
||||
{
|
||||
NativeOnListItemObjectSet(ListItemObject);
|
||||
}
|
||||
|
||||
void UGUIS_ListEntryDetailSection::NativeOnListItemObjectSet(UObject* ListItemObject)
|
||||
{
|
||||
OnListItemObjectSet(ListItemObject);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "UI/Common/GUIS_ListEntryDetailView.h"
|
||||
#include "Components/VerticalBox.h"
|
||||
#include "Components/VerticalBoxSlot.h"
|
||||
#include "Engine/AssetManager.h"
|
||||
#include "Engine/StreamableManager.h"
|
||||
#include "UI/Common/GUIS_ListEntryDetailSection.h"
|
||||
#include "UI/Common/GUIS_DetailSectionsBuilder.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GUIS_ListEntryDetailView)
|
||||
|
||||
#define LOCTEXT_NAMESPACE "EntryDetailsView"
|
||||
|
||||
UGUIS_ListEntryDetailView::UGUIS_ListEntryDetailView(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
, ExtensionWidgetPool(*this)
|
||||
{
|
||||
}
|
||||
|
||||
void UGUIS_ListEntryDetailView::ReleaseSlateResources(bool bReleaseChildren)
|
||||
{
|
||||
Super::ReleaseSlateResources(bReleaseChildren);
|
||||
|
||||
ExtensionWidgetPool.ReleaseAllSlateResources();
|
||||
}
|
||||
|
||||
void UGUIS_ListEntryDetailView::NativeOnInitialized()
|
||||
{
|
||||
Super::NativeOnInitialized();
|
||||
|
||||
if (!IsDesignTime())
|
||||
{
|
||||
SetListItemObject(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_ListEntryDetailView::NativeConstruct()
|
||||
{
|
||||
Super::NativeConstruct();
|
||||
}
|
||||
|
||||
void UGUIS_ListEntryDetailView::SetListItemObject(UObject* InListItemObject)
|
||||
{
|
||||
// Ignore requests to show the same setting multiple times in a row.
|
||||
if (InListItemObject && InListItemObject == CurrentListItemObject)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentListItemObject = InListItemObject;
|
||||
|
||||
if (Box_DetailSections)
|
||||
{
|
||||
// First release the widgets back into the pool.
|
||||
for (UWidget* ChildExtension : Box_DetailSections->GetAllChildren())
|
||||
{
|
||||
ExtensionWidgetPool.Release(Cast<UUserWidget>(ChildExtension));
|
||||
}
|
||||
|
||||
// Remove the widgets from their container.
|
||||
Box_DetailSections->ClearChildren();
|
||||
|
||||
if (InListItemObject)
|
||||
{
|
||||
TArray<TSoftClassPtr<UGUIS_ListEntryDetailSection>> SectionClasses;
|
||||
if (SectionsBuilder)
|
||||
{
|
||||
SectionClasses = SectionsBuilder->GatherDetailSections(InListItemObject);
|
||||
}
|
||||
|
||||
if (StreamingHandle.IsValid())
|
||||
{
|
||||
StreamingHandle->CancelHandle();
|
||||
}
|
||||
|
||||
bool bEverythingAlreadyLoaded = true;
|
||||
|
||||
TArray<FSoftObjectPath> SectionPaths;
|
||||
SectionPaths.Reserve(SectionClasses.Num());
|
||||
for (TSoftClassPtr<UGUIS_ListEntryDetailSection> SoftClassPtr : SectionClasses)
|
||||
{
|
||||
bEverythingAlreadyLoaded &= SoftClassPtr.IsValid();
|
||||
SectionPaths.Add(SoftClassPtr.ToSoftObjectPath());
|
||||
}
|
||||
|
||||
if (bEverythingAlreadyLoaded)
|
||||
{
|
||||
for (TSoftClassPtr<UGUIS_ListEntryDetailSection> SoftClassPtr : SectionClasses)
|
||||
{
|
||||
CreateDetailsExtension(InListItemObject, SoftClassPtr.Get());
|
||||
}
|
||||
|
||||
ExtensionWidgetPool.ReleaseInactiveSlateResources();
|
||||
}
|
||||
else
|
||||
{
|
||||
TWeakObjectPtr<UObject> SettingPtr = InListItemObject;
|
||||
|
||||
StreamingHandle = UAssetManager::GetStreamableManager().RequestAsyncLoad(
|
||||
MoveTemp(SectionPaths),
|
||||
FStreamableDelegate::CreateWeakLambda(this, [this, SettingPtr, SectionClasses]
|
||||
{
|
||||
for (TSoftClassPtr<UGUIS_ListEntryDetailSection> SoftClassPtr : SectionClasses)
|
||||
{
|
||||
CreateDetailsExtension(SettingPtr.Get(), SoftClassPtr.Get());
|
||||
}
|
||||
|
||||
ExtensionWidgetPool.ReleaseInactiveSlateResources();
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_ListEntryDetailView::SetSectionsBuilder(UGUIS_DetailSectionsBuilder* NewBuilder)
|
||||
{
|
||||
SectionsBuilder = NewBuilder;
|
||||
}
|
||||
|
||||
void UGUIS_ListEntryDetailView::CreateDetailsExtension(UObject* InData, TSubclassOf<UGUIS_ListEntryDetailSection> SectionClass)
|
||||
{
|
||||
if (InData && SectionClass)
|
||||
{
|
||||
if (UGUIS_ListEntryDetailSection* Section = ExtensionWidgetPool.GetOrCreateInstance(SectionClass))
|
||||
{
|
||||
Section->SetListItemObject(InData);
|
||||
UVerticalBoxSlot* ExtensionSlot = Box_DetailSections->AddChildToVerticalBox(Section);
|
||||
ExtensionSlot->SetHorizontalAlignment(HAlign_Fill);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "UI/Common/GUIS_ListView.h"
|
||||
|
||||
#include "UI/Common/GUIS_WidgetFactory.h"
|
||||
|
||||
#if WITH_EDITOR
|
||||
#include "Editor/WidgetCompilerLog.h"
|
||||
#endif
|
||||
|
||||
|
||||
UGUIS_ListView::UGUIS_ListView(const FObjectInitializer& ObjectInitializer): Super(ObjectInitializer)
|
||||
{
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
void UGUIS_ListView::ValidateCompiledDefaults(IWidgetCompilerLog& InCompileLog) const
|
||||
{
|
||||
Super::ValidateCompiledDefaults(InCompileLog);
|
||||
// if (EntryWidgetFactories.Num() == 0)
|
||||
// {
|
||||
// InCompileLog.Error(FText::Format(FText::FromString("{0} has no Entry widget Factories defined, can't create widgets without them."), FText::FromString(GetName())));
|
||||
// }
|
||||
}
|
||||
#endif
|
||||
|
||||
void UGUIS_ListView::SetEntryWidgetFactories(TArray<UGUIS_WidgetFactory*> NewFactories)
|
||||
{
|
||||
EntryWidgetFactories = NewFactories;
|
||||
}
|
||||
|
||||
UUserWidget& UGUIS_ListView::OnGenerateEntryWidgetInternal(UObject* Item, TSubclassOf<UUserWidget> DesiredEntryClass, const TSharedRef<STableViewBase>& OwnerTable)
|
||||
{
|
||||
TSubclassOf<UUserWidget> WidgetClass = DesiredEntryClass;
|
||||
|
||||
for (const UGUIS_WidgetFactory* Factory : EntryWidgetFactories)
|
||||
{
|
||||
if (Factory)
|
||||
{
|
||||
if (const TSubclassOf<UUserWidget> EntryClass = Factory->FindWidgetClassForData(Item))
|
||||
{
|
||||
WidgetClass = EntryClass;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Super::OnGenerateEntryWidgetInternal(Item, WidgetClass, OwnerTable);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "UI/Common/GUIS_TileView.h"
|
||||
|
||||
#include "UI/Common/GUIS_WidgetFactory.h"
|
||||
|
||||
#if WITH_EDITOR
|
||||
#include "Editor/WidgetCompilerLog.h"
|
||||
#endif
|
||||
|
||||
|
||||
UGUIS_TileView::UGUIS_TileView(const FObjectInitializer& ObjectInitializer): Super(ObjectInitializer)
|
||||
{
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
void UGUIS_TileView::ValidateCompiledDefaults(IWidgetCompilerLog& InCompileLog) const
|
||||
{
|
||||
Super::ValidateCompiledDefaults(InCompileLog);
|
||||
// if (EntryWidgetFactories.Num() == 0)
|
||||
// {
|
||||
// InCompileLog.Error(FText::Format(FText::FromString("{0} has no Entry widget Factories defined, can't create widgets without them."), FText::FromString(GetName())));
|
||||
// }
|
||||
}
|
||||
#endif
|
||||
|
||||
void UGUIS_TileView::SetEntryWidgetFactories(TArray<UGUIS_WidgetFactory*> NewFactories)
|
||||
{
|
||||
EntryWidgetFactories = NewFactories;
|
||||
}
|
||||
|
||||
UUserWidget& UGUIS_TileView::OnGenerateEntryWidgetInternal(UObject* Item, TSubclassOf<UUserWidget> DesiredEntryClass, const TSharedRef<STableViewBase>& OwnerTable)
|
||||
{
|
||||
TSubclassOf<UUserWidget> WidgetClass = DesiredEntryClass;
|
||||
|
||||
for (const UGUIS_WidgetFactory* Factory : EntryWidgetFactories)
|
||||
{
|
||||
if (Factory)
|
||||
{
|
||||
if (const TSubclassOf<UUserWidget> EntryClass = Factory->FindWidgetClassForData(Item))
|
||||
{
|
||||
WidgetClass = EntryClass;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Super::OnGenerateEntryWidgetInternal(Item, WidgetClass, OwnerTable);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "UI/Common/GUIS_UserWidgetInterface.h"
|
||||
|
||||
|
||||
// Add default functionality here for any IGUIS_UserWidgetInterface functions that are not pure virtual.
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "UI/Common/GUIS_WidgetFactory.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "Misc/DataValidation.h"
|
||||
|
||||
|
||||
TSubclassOf<UUserWidget> UGUIS_WidgetFactory::FindWidgetClassForData_Implementation(const UObject* Data) const
|
||||
{
|
||||
return TSubclassOf<UUserWidget>();
|
||||
}
|
||||
|
||||
UGUIS_WidgetFactory::UGUIS_WidgetFactory()
|
||||
{
|
||||
}
|
||||
|
||||
bool UGUIS_WidgetFactory::OnDataValidation_Implementation(FText& ValidationMessage) const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
EDataValidationResult UGUIS_WidgetFactory::IsDataValid(FDataValidationContext& Context) const
|
||||
{
|
||||
FText ValidationMessage;
|
||||
if (!OnDataValidation(ValidationMessage))
|
||||
{
|
||||
Context.AddError(ValidationMessage);
|
||||
return EDataValidationResult::Invalid;
|
||||
}
|
||||
return Super::IsDataValid(Context);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "UI/Foundation/GUIS_ButtonBase.h"
|
||||
|
||||
#include "CommonActionWidget.h"
|
||||
|
||||
|
||||
void UGUIS_ButtonBase::NativePreConstruct()
|
||||
{
|
||||
Super::NativePreConstruct();
|
||||
|
||||
OnUpdateButtonStyle();
|
||||
RefreshButtonText();
|
||||
}
|
||||
|
||||
void UGUIS_ButtonBase::UpdateInputActionWidget()
|
||||
{
|
||||
Super::UpdateInputActionWidget();
|
||||
|
||||
OnUpdateButtonStyle();
|
||||
RefreshButtonText();
|
||||
}
|
||||
|
||||
void UGUIS_ButtonBase::SetButtonText(const FText& InText)
|
||||
{
|
||||
bOverride_ButtonText = !InText.IsEmpty();
|
||||
ButtonText = InText;
|
||||
RefreshButtonText();
|
||||
}
|
||||
|
||||
void UGUIS_ButtonBase::RefreshButtonText()
|
||||
{
|
||||
if (!bOverride_ButtonText || ButtonText.IsEmpty())
|
||||
{
|
||||
if (InputActionWidget)
|
||||
{
|
||||
const FText ActionDisplayText = InputActionWidget->GetDisplayText();
|
||||
if (!ActionDisplayText.IsEmpty())
|
||||
{
|
||||
OnUpdateButtonText(ActionDisplayText);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OnUpdateButtonText(ButtonText);
|
||||
}
|
||||
|
||||
|
||||
void UGUIS_ButtonBase::OnInputMethodChanged(ECommonInputType CurrentInputType)
|
||||
{
|
||||
Super::OnInputMethodChanged(CurrentInputType);
|
||||
|
||||
OnUpdateButtonStyle();
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
const FText UGUIS_ButtonBase::GetPaletteCategory()
|
||||
{
|
||||
return PaletteCategory;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "UI/Foundation/GUIS_TabButtonBase.h"
|
||||
|
||||
#include "CommonLazyImage.h"
|
||||
|
||||
|
||||
// void UGUIS_TabButtonBase::SetIconFromLazyObject(TSoftObjectPtr<UObject> LazyObject)
|
||||
// {
|
||||
// if (LazyImage_Icon)
|
||||
// {
|
||||
// LazyImage_Icon->SetBrushFromLazyDisplayAsset(LazyObject);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// void UGUIS_TabButtonBase::SetIconBrush(const FSlateBrush& Brush)
|
||||
// {
|
||||
// if (LazyImage_Icon)
|
||||
// {
|
||||
// LazyImage_Icon->SetBrush(Brush);
|
||||
// LazyImage_Icon->SetVisibility(ESlateVisibility::Visible);
|
||||
// }
|
||||
// }
|
||||
|
||||
// void UGUIS_TabButtonBase::SetTabLabelInfo_Implementation(const FGUIS_TabDescriptor& TabLabelInfo)
|
||||
// {
|
||||
// SetButtonText(TabLabelInfo.TabText);
|
||||
// SetIconBrush(TabLabelInfo.IconBrush);
|
||||
// }
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "UI/Foundation/GUIS_TabDefinition.h"
|
||||
@@ -0,0 +1,267 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "UI/Foundation/GUIS_TabListWidgetBase.h"
|
||||
|
||||
#include "CommonAnimatedSwitcher.h"
|
||||
#include "CommonButtonBase.h"
|
||||
#include "CommonActivatableWidget.h"
|
||||
#include "GUIS_LogChannels.h"
|
||||
#include "Editor/WidgetCompilerLog.h"
|
||||
#include "UI/Foundation/GUIS_TabDefinition.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GUIS_TabListWidgetBase)
|
||||
|
||||
void UGUIS_TabListWidgetBase::NativeOnInitialized()
|
||||
{
|
||||
Super::NativeOnInitialized();
|
||||
}
|
||||
|
||||
void UGUIS_TabListWidgetBase::NativeConstruct()
|
||||
{
|
||||
Super::NativeConstruct();
|
||||
|
||||
SetupTabs();
|
||||
}
|
||||
|
||||
void UGUIS_TabListWidgetBase::NativeDestruct()
|
||||
{
|
||||
for (FGUIS_TabDescriptor& TabInfo : PreregisteredTabInfoArray)
|
||||
{
|
||||
if (TabInfo.CreatedTabContentWidget)
|
||||
{
|
||||
TabInfo.CreatedTabContentWidget->RemoveFromParent();
|
||||
TabInfo.CreatedTabContentWidget = nullptr;
|
||||
}
|
||||
}
|
||||
Super::NativeDestruct();
|
||||
}
|
||||
|
||||
FGUIS_TabDescriptor::FGUIS_TabDescriptor()
|
||||
{
|
||||
bHidden = false;
|
||||
}
|
||||
|
||||
UGUIS_TabListWidgetBase::UGUIS_TabListWidgetBase()
|
||||
{
|
||||
bAutoListenForInput = false;
|
||||
bDeferRebuildingTabList = true;
|
||||
}
|
||||
|
||||
bool UGUIS_TabListWidgetBase::GetPreregisteredTabInfo(const FName TabNameId, FGUIS_TabDescriptor& OutTabInfo)
|
||||
{
|
||||
const FGUIS_TabDescriptor* const FoundTabInfo = PreregisteredTabInfoArray.FindByPredicate([&](const FGUIS_TabDescriptor& TabInfo) -> bool
|
||||
{
|
||||
return TabInfo.TabId == TabNameId;
|
||||
});
|
||||
|
||||
if (!FoundTabInfo)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
OutTabInfo = *FoundTabInfo;
|
||||
return true;
|
||||
}
|
||||
|
||||
int32 UGUIS_TabListWidgetBase::GetPreregisteredTabIndex(FName TabNameId) const
|
||||
{
|
||||
for (int32 i = 0; i < PreregisteredTabInfoArray.Num(); ++i)
|
||||
{
|
||||
if (PreregisteredTabInfoArray[i].TabId == TabNameId)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return INDEX_NONE;
|
||||
}
|
||||
|
||||
bool UGUIS_TabListWidgetBase::FindPreregisteredTabInfo(const FName TabNameId, FGUIS_TabDescriptor& OutTabInfo)
|
||||
{
|
||||
return GetPreregisteredTabInfo(TabNameId, OutTabInfo);
|
||||
}
|
||||
|
||||
void UGUIS_TabListWidgetBase::SetTabHiddenState(FName TabNameId, bool bHidden)
|
||||
{
|
||||
for (FGUIS_TabDescriptor& TabInfo : PreregisteredTabInfoArray)
|
||||
{
|
||||
if (TabInfo.TabId == TabNameId)
|
||||
{
|
||||
TabInfo.bHidden = bHidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool UGUIS_TabListWidgetBase::RegisterDynamicTab(const FGUIS_TabDescriptor& TabDescriptor)
|
||||
{
|
||||
// If it's hidden we just ignore it.
|
||||
if (TabDescriptor.bHidden)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
PendingTabLabelInfoMap.Add(TabDescriptor.TabId, TabDescriptor);
|
||||
|
||||
return RegisterTab(TabDescriptor.TabId, TabDescriptor.TabButtonType.LoadSynchronous(), TabDescriptor.CreatedTabContentWidget);
|
||||
}
|
||||
|
||||
void UGUIS_TabListWidgetBase::HandlePreLinkedSwitcherChanged()
|
||||
{
|
||||
for (const FGUIS_TabDescriptor& TabInfo : PreregisteredTabInfoArray)
|
||||
{
|
||||
// Remove tab content widget from linked switcher, as it is being disassociated.
|
||||
if (TabInfo.CreatedTabContentWidget)
|
||||
{
|
||||
TabInfo.CreatedTabContentWidget->RemoveFromParent();
|
||||
}
|
||||
}
|
||||
|
||||
Super::HandlePreLinkedSwitcherChanged();
|
||||
}
|
||||
|
||||
void UGUIS_TabListWidgetBase::HandlePostLinkedSwitcherChanged()
|
||||
{
|
||||
if (!IsDesignTime() && GetCachedWidget().IsValid())
|
||||
{
|
||||
// Don't bother making tabs if we're in the designer or haven't been constructed yet
|
||||
SetupTabs();
|
||||
}
|
||||
|
||||
Super::HandlePostLinkedSwitcherChanged();
|
||||
}
|
||||
|
||||
void UGUIS_TabListWidgetBase::HandleTabCreation_Implementation(FName TabId, UCommonButtonBase* TabButton)
|
||||
{
|
||||
FGUIS_TabDescriptor* TabInfoPtr = nullptr;
|
||||
|
||||
FGUIS_TabDescriptor TabInfo;
|
||||
if (GetPreregisteredTabInfo(TabId, TabInfo))
|
||||
{
|
||||
TabInfoPtr = &TabInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
TabInfoPtr = PendingTabLabelInfoMap.Find(TabId);
|
||||
}
|
||||
|
||||
if (TabButton->GetClass()->ImplementsInterface(UGUIS_TabButtonInterface::StaticClass()))
|
||||
{
|
||||
if (ensureMsgf(TabInfoPtr, TEXT("A tab button was created with id %s but no label info was specified. RegisterDynamicTab should be used over RegisterTab to provide label info."),
|
||||
*TabId.ToString()))
|
||||
{
|
||||
IGUIS_TabButtonInterface::Execute_SetTabLabelInfo(TabButton, *TabInfoPtr);
|
||||
}
|
||||
}
|
||||
|
||||
PendingTabLabelInfoMap.Remove(TabId);
|
||||
}
|
||||
|
||||
bool UGUIS_TabListWidgetBase::IsFirstTabActive() const
|
||||
{
|
||||
if (PreregisteredTabInfoArray.Num() > 0)
|
||||
{
|
||||
return GetActiveTab() == PreregisteredTabInfoArray[0].TabId;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UGUIS_TabListWidgetBase::IsLastTabActive() const
|
||||
{
|
||||
if (PreregisteredTabInfoArray.Num() > 0)
|
||||
{
|
||||
return GetActiveTab() == PreregisteredTabInfoArray.Last().TabId;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UGUIS_TabListWidgetBase::IsTabVisible(FName TabId)
|
||||
{
|
||||
if (const UCommonButtonBase* Button = GetTabButtonBaseByID(TabId))
|
||||
{
|
||||
const ESlateVisibility TabVisibility = Button->GetVisibility();
|
||||
return (TabVisibility == ESlateVisibility::Visible
|
||||
|| TabVisibility == ESlateVisibility::HitTestInvisible
|
||||
|| TabVisibility == ESlateVisibility::SelfHitTestInvisible);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int32 UGUIS_TabListWidgetBase::GetVisibleTabCount()
|
||||
{
|
||||
int32 Result = 0;
|
||||
const int32 TabCount = GetTabCount();
|
||||
for (int32 Index = 0; Index < TabCount; Index++)
|
||||
{
|
||||
if (IsTabVisible(GetTabIdAtIndex(Index)))
|
||||
{
|
||||
Result++;
|
||||
}
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
void UGUIS_TabListWidgetBase::SetupTabs()
|
||||
{
|
||||
for (FGUIS_TabDescriptor& TabInfo : PreregisteredTabInfoArray)
|
||||
{
|
||||
if (TabInfo.bHidden)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the tab content hasn't been created already, create it.
|
||||
if (!TabInfo.CreatedTabContentWidget && !TabInfo.TabContentType.IsNull())
|
||||
{
|
||||
TabInfo.CreatedTabContentWidget = CreateWidget<UCommonUserWidget>(GetOwningPlayer(), TabInfo.TabContentType.LoadSynchronous());
|
||||
OnTabContentCreatedNative.Broadcast(TabInfo.TabId, Cast<UCommonUserWidget>(TabInfo.CreatedTabContentWidget));
|
||||
OnTabContentCreated.Broadcast(TabInfo.TabId, Cast<UCommonUserWidget>(TabInfo.CreatedTabContentWidget));
|
||||
}
|
||||
|
||||
if (UCommonAnimatedSwitcher* CurrentLinkedSwitcher = GetLinkedSwitcher())
|
||||
{
|
||||
// Add the tab content to the newly linked switcher.
|
||||
if (!CurrentLinkedSwitcher->HasChild(TabInfo.CreatedTabContentWidget))
|
||||
{
|
||||
CurrentLinkedSwitcher->AddChild(TabInfo.CreatedTabContentWidget);
|
||||
}
|
||||
}
|
||||
|
||||
// If the tab is not already registered, register it.
|
||||
if (GetTabButtonBaseByID(TabInfo.TabId) == nullptr)
|
||||
{
|
||||
RegisterTab(TabInfo.TabId, TabInfo.TabButtonType.LoadSynchronous(), TabInfo.CreatedTabContentWidget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
void UGUIS_TabListWidgetBase::PostLoad()
|
||||
{
|
||||
if (!TabDefinitions_DEPRECATED.IsEmpty())
|
||||
{
|
||||
for (TObjectPtr<UDEPRECATED_GUIS_TabDefinition> Def : TabDefinitions_DEPRECATED)
|
||||
{
|
||||
if (Def)
|
||||
{
|
||||
FGUIS_TabDescriptor Tab;
|
||||
Tab.TabId = Def->TabId;
|
||||
Tab.IconBrush = Def->IconBrush;
|
||||
Tab.TabButtonType = Def->TabButtonType;
|
||||
Tab.TabText = Def->TabText;
|
||||
PreregisteredTabInfoArray.Add(Tab);
|
||||
}
|
||||
}
|
||||
TabDefinitions_DEPRECATED.Empty();
|
||||
}
|
||||
|
||||
Super::PostLoad();
|
||||
}
|
||||
|
||||
void UGUIS_TabListWidgetBase::ValidateCompiledDefaults(class IWidgetCompilerLog& CompileLog) const
|
||||
{
|
||||
Super::ValidateCompiledDefaults(CompileLog);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "UI/GUIS_ActivatableWidget.h"
|
||||
|
||||
#include "Editor/WidgetCompilerLog.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GUIS_ActivatableWidget)
|
||||
|
||||
#define LOCTEXT_NAMESPACE "GGF"
|
||||
|
||||
UGUIS_ActivatableWidget::UGUIS_ActivatableWidget(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
}
|
||||
|
||||
void UGUIS_ActivatableWidget::SetIsBackHandler(bool bNewState)
|
||||
{
|
||||
}
|
||||
|
||||
TOptional<FUIInputConfig> UGUIS_ActivatableWidget::GetDesiredInputConfig() const
|
||||
{
|
||||
switch (InputConfig)
|
||||
{
|
||||
case EGUIS_ActivatableWidgetInputMode::GameAndMenu:
|
||||
return FUIInputConfig(ECommonInputMode::All, GameMouseCaptureMode);
|
||||
case EGUIS_ActivatableWidgetInputMode::Game:
|
||||
return FUIInputConfig(ECommonInputMode::Game, GameMouseCaptureMode);
|
||||
case EGUIS_ActivatableWidgetInputMode::Menu:
|
||||
return FUIInputConfig(ECommonInputMode::Menu, EMouseCaptureMode::NoCapture);
|
||||
case EGUIS_ActivatableWidgetInputMode::Default:
|
||||
default:
|
||||
return TOptional<FUIInputConfig>();
|
||||
}
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
|
||||
void UGUIS_ActivatableWidget::ValidateCompiledWidgetTree(const UWidgetTree& BlueprintWidgetTree, class IWidgetCompilerLog& CompileLog) const
|
||||
{
|
||||
Super::ValidateCompiledWidgetTree(BlueprintWidgetTree, CompileLog);
|
||||
|
||||
if (!GetClass()->IsFunctionImplementedInScript(GET_FUNCTION_NAME_CHECKED(UGUIS_ActivatableWidget, BP_GetDesiredFocusTarget)))
|
||||
{
|
||||
if (GetParentNativeClass(GetClass()) == StaticClass())
|
||||
{
|
||||
CompileLog.Warning(LOCTEXT("ValidateGetDesiredFocusTarget_Warning", "GetDesiredFocusTarget wasn't implemented, you're going to have trouble using gamepads on this screen."));
|
||||
}
|
||||
else
|
||||
{
|
||||
//TODO - Note for now, because we can't guarantee it isn't implemented in a native subclass of this one.
|
||||
CompileLog.Note(LOCTEXT("ValidateGetDesiredFocusTarget_Note",
|
||||
"GetDesiredFocusTarget wasn't implemented, you're going to have trouble using gamepads on this screen. If it was implemented in the native base class you can ignore this message."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "UI/GUIS_GameUIContext.h"
|
||||
@@ -0,0 +1,263 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "UI/GUIS_GameUIFunctionLibrary.h"
|
||||
#include "CommonInputSubsystem.h"
|
||||
#include "CommonInputTypeEnum.h"
|
||||
#include "GUIS_LogChannels.h"
|
||||
#include "Components/ListView.h"
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "UI/GUIS_GameUILayout.h"
|
||||
#include "UI/GUIS_GameUIPolicy.h"
|
||||
#include "UI/GUIS_GameUISubsystem.h"
|
||||
#include "Widgets/CommonActivatableWidgetContainer.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GUIS_GameUIFunctionLibrary)
|
||||
|
||||
int32 UGUIS_GameUIFunctionLibrary::InputSuspensions = 0;
|
||||
|
||||
ECommonInputType UGUIS_GameUIFunctionLibrary::GetOwningPlayerInputType(const UUserWidget* WidgetContextObject)
|
||||
{
|
||||
if (WidgetContextObject)
|
||||
{
|
||||
if (const UCommonInputSubsystem* InputSubsystem = UCommonInputSubsystem::Get(WidgetContextObject->GetOwningLocalPlayer()))
|
||||
{
|
||||
return InputSubsystem->GetCurrentInputType();
|
||||
}
|
||||
}
|
||||
|
||||
return ECommonInputType::Count;
|
||||
}
|
||||
|
||||
bool UGUIS_GameUIFunctionLibrary::IsOwningPlayerUsingTouch(const UUserWidget* WidgetContextObject)
|
||||
{
|
||||
if (WidgetContextObject)
|
||||
{
|
||||
if (const UCommonInputSubsystem* InputSubsystem = UCommonInputSubsystem::Get(WidgetContextObject->GetOwningLocalPlayer()))
|
||||
{
|
||||
return InputSubsystem->GetCurrentInputType() == ECommonInputType::Touch;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UGUIS_GameUIFunctionLibrary::IsOwningPlayerUsingGamepad(const UUserWidget* WidgetContextObject)
|
||||
{
|
||||
if (WidgetContextObject)
|
||||
{
|
||||
if (const UCommonInputSubsystem* InputSubsystem = UCommonInputSubsystem::Get(WidgetContextObject->GetOwningLocalPlayer()))
|
||||
{
|
||||
return InputSubsystem->GetCurrentInputType() == ECommonInputType::Gamepad;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
UCommonActivatableWidget* UGUIS_GameUIFunctionLibrary::PushContentToUILayer_ForPlayer(const APlayerController* PlayerController, FGameplayTag LayerName,
|
||||
TSubclassOf<UCommonActivatableWidget> WidgetClass)
|
||||
{
|
||||
if (!ensure(PlayerController) || !ensure(WidgetClass != nullptr))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UGUIS_GameUILayout* UILayout = GetGameUILayoutForPlayer(PlayerController);
|
||||
|
||||
if (UILayout == nullptr)
|
||||
{
|
||||
FFrame::KismetExecutionMessage(TEXT("PushContentToUILayer_ForPlayer failed to find UILayout for player."), ELogVerbosity::Error);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return UILayout->PushWidgetToLayerStack(LayerName, WidgetClass);
|
||||
}
|
||||
|
||||
void UGUIS_GameUIFunctionLibrary::PopContentFromUILayer_ForPlayer(const APlayerController* PlayerController, FGameplayTag LayerName, int32 RemainNum)
|
||||
{
|
||||
if (!ensure(PlayerController))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UGUIS_GameUILayout* UILayout = GetGameUILayoutForPlayer(PlayerController);
|
||||
|
||||
if (UILayout == nullptr)
|
||||
{
|
||||
FFrame::KismetExecutionMessage(TEXT("PopContentFromUILayer_ForPlayer failed to find UILayout for player."), ELogVerbosity::Error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (UCommonActivatableWidgetContainerBase* Layer = UILayout->GetLayerWidget(LayerName))
|
||||
{
|
||||
const TArray<UCommonActivatableWidget*>& List = Layer->GetWidgetList();
|
||||
|
||||
int32 MinIdx = RemainNum >= 1 ? RemainNum - 1 : 0;
|
||||
|
||||
for (int32 i = List.Num() - 1; i >= MinIdx; i--)
|
||||
{
|
||||
Layer->RemoveWidget(*List[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// void UGUIS_GameUIFunctionLibrary::PushStreamedContentToLayer_ForPlayer(const ULocalPlayer* LocalPlayer, FGameplayTag LayerName, TSoftClassPtr<UCommonActivatableWidget> WidgetClass)
|
||||
// {
|
||||
// if (!ensure(LocalPlayer) || !ensure(!WidgetClass.IsNull()))
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// if (UGameUIManagerSubsystem* UIManager = LocalPlayer->GetGameInstance()->GetSubsystem<UGameUIManagerSubsystem>())
|
||||
// {
|
||||
// if (UGameUIPolicy* Policy = UIManager->GetCurrentUIPolicy())
|
||||
// {
|
||||
// if (UPrimaryGameLayout* RootLayout = Policy->GetRootLayout(CastChecked<UCommonLocalPlayer>(LocalPlayer)))
|
||||
// {
|
||||
// const bool bSuspendInputUntilComplete = true;
|
||||
// RootLayout->PushWidgetToLayerStackAsync(LayerName, bSuspendInputUntilComplete, WidgetClass);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
void UGUIS_GameUIFunctionLibrary::PopContentFromUILayer(UCommonActivatableWidget* ActivatableWidget)
|
||||
{
|
||||
if (!ActivatableWidget)
|
||||
{
|
||||
// Ignore request to pop an already deleted widget
|
||||
return;
|
||||
}
|
||||
|
||||
if (const APlayerController* PlayerController = ActivatableWidget->GetOwningPlayer())
|
||||
{
|
||||
if (UGUIS_GameUILayout* UILayout = GetGameUILayoutForPlayer(PlayerController))
|
||||
{
|
||||
UE_LOG(LogGUIS, Verbose, TEXT("Popped content:%s from ui layer."), *GetNameSafe(ActivatableWidget))
|
||||
UILayout->FindAndRemoveWidgetFromLayer(ActivatableWidget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_GameUIFunctionLibrary::PopContentsFromUILayer(TArray<UCommonActivatableWidget*> ActivatableWidgets, bool bReverse)
|
||||
{
|
||||
if (bReverse)
|
||||
{
|
||||
for (int32 i = ActivatableWidgets.Num() - 1; i >= 0; i--)
|
||||
{
|
||||
PopContentFromUILayer(ActivatableWidgets[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int32 i = 0; i < ActivatableWidgets.Num(); i++)
|
||||
{
|
||||
PopContentFromUILayer(ActivatableWidgets[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ULocalPlayer* UGUIS_GameUIFunctionLibrary::GetLocalPlayerFromController(APlayerController* PlayerController)
|
||||
{
|
||||
if (PlayerController)
|
||||
{
|
||||
return Cast<ULocalPlayer>(PlayerController->Player);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UGUIS_GameUILayout* UGUIS_GameUIFunctionLibrary::GetGameUILayoutForPlayer(const APlayerController* PlayerController)
|
||||
{
|
||||
if (!IsValid(PlayerController))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
if (ULocalPlayer* LocalPlayer = Cast<ULocalPlayer>(PlayerController->GetLocalPlayer()))
|
||||
{
|
||||
const ULocalPlayer* CommonLocalPlayer = CastChecked<ULocalPlayer>(LocalPlayer);
|
||||
if (const UGameInstance* GameInstance = CommonLocalPlayer->GetGameInstance())
|
||||
{
|
||||
if (UGUIS_GameUISubsystem* UIManager = GameInstance->GetSubsystem<UGUIS_GameUISubsystem>())
|
||||
{
|
||||
if (const UGUIS_GameUIPolicy* Policy = UIManager->GetCurrentUIPolicy())
|
||||
{
|
||||
if (UGUIS_GameUILayout* RootLayout = Policy->GetRootLayout(CommonLocalPlayer))
|
||||
{
|
||||
return RootLayout;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FName UGUIS_GameUIFunctionLibrary::SuspendInputForPlayer(APlayerController* PlayerController, FName SuspendReason)
|
||||
{
|
||||
return SuspendInputForPlayer(PlayerController ? PlayerController->GetLocalPlayer() : nullptr, SuspendReason);
|
||||
}
|
||||
|
||||
FName UGUIS_GameUIFunctionLibrary::SuspendInputForPlayer(ULocalPlayer* LocalPlayer, FName SuspendReason)
|
||||
{
|
||||
if (UCommonInputSubsystem* CommonInputSubsystem = UCommonInputSubsystem::Get(LocalPlayer))
|
||||
{
|
||||
InputSuspensions++;
|
||||
FName SuspendToken = SuspendReason;
|
||||
SuspendToken.SetNumber(InputSuspensions);
|
||||
|
||||
CommonInputSubsystem->SetInputTypeFilter(ECommonInputType::MouseAndKeyboard, SuspendToken, true);
|
||||
CommonInputSubsystem->SetInputTypeFilter(ECommonInputType::Gamepad, SuspendToken, true);
|
||||
CommonInputSubsystem->SetInputTypeFilter(ECommonInputType::Touch, SuspendToken, true);
|
||||
|
||||
return SuspendToken;
|
||||
}
|
||||
|
||||
return NAME_None;
|
||||
}
|
||||
|
||||
void UGUIS_GameUIFunctionLibrary::ResumeInputForPlayer(APlayerController* PlayerController, FName SuspendToken)
|
||||
{
|
||||
ResumeInputForPlayer(PlayerController ? PlayerController->GetLocalPlayer() : nullptr, SuspendToken);
|
||||
}
|
||||
|
||||
void UGUIS_GameUIFunctionLibrary::ResumeInputForPlayer(ULocalPlayer* LocalPlayer, FName SuspendToken)
|
||||
{
|
||||
if (SuspendToken == NAME_None)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (UCommonInputSubsystem* CommonInputSubsystem = UCommonInputSubsystem::Get(LocalPlayer))
|
||||
{
|
||||
CommonInputSubsystem->SetInputTypeFilter(ECommonInputType::MouseAndKeyboard, SuspendToken, false);
|
||||
CommonInputSubsystem->SetInputTypeFilter(ECommonInputType::Gamepad, SuspendToken, false);
|
||||
CommonInputSubsystem->SetInputTypeFilter(ECommonInputType::Touch, SuspendToken, false);
|
||||
}
|
||||
}
|
||||
|
||||
UObject* UGUIS_GameUIFunctionLibrary::GetTypedListItem(TScriptInterface<IUserObjectListEntry> UserObjectListEntry, TSubclassOf<UObject> DesiredClass)
|
||||
{
|
||||
UUserWidget* EntryWidget = Cast<UUserWidget>(UserObjectListEntry.GetObject());
|
||||
if (!IsValid(EntryWidget))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
UListView* OwningListView = Cast<UListView>(UUserListEntryLibrary::GetOwningListView(EntryWidget));
|
||||
if (!IsValid(OwningListView))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UObject* ListItem = *OwningListView->ItemFromEntryWidget(*EntryWidget);
|
||||
|
||||
if (ListItem->GetClass()->IsChildOf(DesiredClass))
|
||||
{
|
||||
return ListItem;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool UGUIS_GameUIFunctionLibrary::GetTypedListItemSafely(TScriptInterface<IUserObjectListEntry> UserObjectListEntry, TSubclassOf<UObject> DesiredClass, UObject*& OutItem)
|
||||
{
|
||||
OutItem = GetTypedListItem(UserObjectListEntry, DesiredClass);
|
||||
return OutItem != nullptr;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "UI/GUIS_GameUILayout.h"
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "Kismet/GameplayStatics.h"
|
||||
#include "GUIS_LogChannels.h"
|
||||
#include "Widgets/CommonActivatableWidgetContainer.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GUIS_GameUILayout)
|
||||
|
||||
class UObject;
|
||||
|
||||
UGUIS_GameUILayout::UGUIS_GameUILayout(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
}
|
||||
|
||||
void UGUIS_GameUILayout::SetIsDormant(bool InDormant)
|
||||
{
|
||||
if (bIsDormant != InDormant)
|
||||
{
|
||||
const ULocalPlayer* LP = GetOwningLocalPlayer();
|
||||
const int32 PlayerId = LP ? LP->GetControllerId() : -1;
|
||||
const TCHAR* OldDormancyStr = bIsDormant ? TEXT("Dormant") : TEXT("Not-Dormant");
|
||||
const TCHAR* NewDormancyStr = InDormant ? TEXT("Dormant") : TEXT("Not-Dormant");
|
||||
const TCHAR* PrimaryPlayerStr = LP && LP->IsPrimaryPlayer() ? TEXT("[Primary]") : TEXT("[Non-Primary]");
|
||||
UE_LOG(LogGUIS, Display, TEXT("%s PrimaryGameLayout Dormancy changed for [%d] from [%s] to [%s]"), PrimaryPlayerStr, PlayerId, OldDormancyStr, NewDormancyStr);
|
||||
|
||||
bIsDormant = InDormant;
|
||||
OnIsDormantChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_GameUILayout::OnIsDormantChanged()
|
||||
{
|
||||
//@TODO NDarnell Determine what to do with dormancy, in the past we treated dormancy as a way to shutoff rendering
|
||||
//and the view for the other local players when we force multiple players to use the player view of a single player.
|
||||
|
||||
//if (ULocalPlayer* LocalPlayer = GetOwningLocalPlayer<ULocalPlayer>())
|
||||
//{
|
||||
// // When the root layout is dormant, we don't want to render anything from the owner's view either
|
||||
// LocalPlayer->SetIsPlayerViewEnabled(!bIsDormant);
|
||||
//}
|
||||
|
||||
//SetVisibility(bIsDormant ? ESlateVisibility::Collapsed : ESlateVisibility::SelfHitTestInvisible);
|
||||
|
||||
//OnLayoutDormancyChanged().Broadcast(bIsDormant);
|
||||
}
|
||||
|
||||
void UGUIS_GameUILayout::RegisterLayer(FGameplayTag LayerTag, UCommonActivatableWidgetContainerBase* LayerWidget)
|
||||
{
|
||||
if (!IsDesignTime())
|
||||
{
|
||||
LayerWidget->OnTransitioningChanged.AddUObject(this, &UGUIS_GameUILayout::OnWidgetStackTransitioning);
|
||||
// TODO: Consider allowing a transition duration, we currently set it to 0, because if it's not 0, the
|
||||
// transition effect will cause focus to not transition properly to the new widgets when using
|
||||
// gamepad always.
|
||||
LayerWidget->SetTransitionDuration(0.0);
|
||||
|
||||
Layers.Add(LayerTag, LayerWidget);
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_GameUILayout::OnWidgetStackTransitioning(UCommonActivatableWidgetContainerBase* Widget, bool bIsTransitioning)
|
||||
{
|
||||
if (bIsTransitioning)
|
||||
{
|
||||
const FName SuspendToken = UGUIS_GameUIFunctionLibrary::SuspendInputForPlayer(GetOwningLocalPlayer(), TEXT("GlobalStackTransion"));
|
||||
SuspendInputTokens.Add(SuspendToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ensure(SuspendInputTokens.Num() > 0))
|
||||
{
|
||||
const FName SuspendToken = SuspendInputTokens.Pop();
|
||||
UGUIS_GameUIFunctionLibrary::ResumeInputForPlayer(GetOwningLocalPlayer(), SuspendToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_GameUILayout::FindAndRemoveWidgetFromLayer(UCommonActivatableWidget* ActivatableWidget)
|
||||
{
|
||||
// We're not sure what layer the widget is on so go searching.
|
||||
for (const auto& LayerKVP : Layers)
|
||||
{
|
||||
LayerKVP.Value->RemoveWidget(*ActivatableWidget);
|
||||
}
|
||||
}
|
||||
|
||||
UCommonActivatableWidgetContainerBase* UGUIS_GameUILayout::GetLayerWidget(FGameplayTag LayerName)
|
||||
{
|
||||
return Layers.FindRef(LayerName);
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "UI/GUIS_GameUIPolicy.h"
|
||||
#include "UI/GUIS_GameUISubsystem.h"
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "Framework/Application/SlateApplication.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include "GUIS_LogChannels.h"
|
||||
#include "Input/CommonUIInputTypes.h"
|
||||
#include "UI/GUIS_GameUIContext.h"
|
||||
#include "UI/GUIS_GameUILayout.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GUIS_GameUIPolicy)
|
||||
|
||||
// Static
|
||||
UGUIS_GameUIPolicy* UGUIS_GameUIPolicy::GetGameUIPolicy(const UObject* WorldContextObject)
|
||||
{
|
||||
if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull))
|
||||
{
|
||||
if (UGameInstance* GameInstance = World->GetGameInstance())
|
||||
{
|
||||
if (UGUIS_GameUISubsystem* UIManager = UGameInstance::GetSubsystem<UGUIS_GameUISubsystem>(GameInstance))
|
||||
{
|
||||
return UIManager->GetCurrentUIPolicy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UGUIS_GameUISubsystem* UGUIS_GameUIPolicy::GetOwningSubsystem() const
|
||||
{
|
||||
return Cast<UGUIS_GameUISubsystem>(GetOuter());
|
||||
}
|
||||
|
||||
UWorld* UGUIS_GameUIPolicy::GetWorld() const
|
||||
{
|
||||
if (UGUIS_GameUISubsystem* Subsystem = GetOwningSubsystem())
|
||||
{
|
||||
return Subsystem->GetGameInstance()->GetWorld();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UGUIS_GameUILayout* UGUIS_GameUIPolicy::GetRootLayout(const ULocalPlayer* LocalPlayer) const
|
||||
{
|
||||
const FGUIS_RootViewportLayoutInfo* LayoutInfo = RootViewportLayouts.FindByKey(LocalPlayer);
|
||||
return LayoutInfo ? LayoutInfo->RootLayout : nullptr;
|
||||
}
|
||||
|
||||
UGUIS_GameUIContext* UGUIS_GameUIPolicy::GetContext(const ULocalPlayer* LocalPlayer, TSubclassOf<UGUIS_GameUIContext> ContextClass)
|
||||
{
|
||||
if (const FGUIS_RootViewportLayoutInfo* LayoutInfo = RootViewportLayouts.FindByKey(LocalPlayer))
|
||||
{
|
||||
for (int32 i = 0; i < LayoutInfo->Contexts.Num(); i++)
|
||||
{
|
||||
if (LayoutInfo->Contexts[i] && LayoutInfo->Contexts[i]->GetClass() == ContextClass)
|
||||
{
|
||||
return LayoutInfo->Contexts[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool UGUIS_GameUIPolicy::AddContext(const ULocalPlayer* LocalPlayer, UGUIS_GameUIContext* NewContext)
|
||||
{
|
||||
if (FGUIS_RootViewportLayoutInfo* LayoutInfo = RootViewportLayouts.FindByKey(LocalPlayer))
|
||||
{
|
||||
if (const UObject* ExistingContext = GetContext(LocalPlayer, NewContext->GetClass()))
|
||||
{
|
||||
UE_LOG(LogGUIS, Warning, TEXT("[%s] is trying to add repeat context of type(%s) for %s, which is not allowed!"), *GetName(), *NewContext->GetClass()->GetName(), *GetNameSafe(LocalPlayer));
|
||||
return false;
|
||||
}
|
||||
LayoutInfo->Contexts.Add(NewContext);
|
||||
UE_LOG(LogGUIS, Verbose, TEXT("[%s] registered context of type(%s) for %s."), *GetName(), *NewContext->GetClass()->GetName(), *GetNameSafe(LocalPlayer));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
UGUIS_GameUIContext* UGUIS_GameUIPolicy::FindContext(const ULocalPlayer* LocalPlayer, TSubclassOf<UGUIS_GameUIContext> ContextClass)
|
||||
{
|
||||
if (FGUIS_RootViewportLayoutInfo* LayoutInfo = RootViewportLayouts.FindByKey(LocalPlayer))
|
||||
{
|
||||
for (int32 i = 0; i < LayoutInfo->Contexts.Num(); i++)
|
||||
{
|
||||
if (LayoutInfo->Contexts[i] && LayoutInfo->Contexts[i]->GetClass() == ContextClass)
|
||||
{
|
||||
return LayoutInfo->Contexts[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void UGUIS_GameUIPolicy::RemoveContext(const ULocalPlayer* LocalPlayer, TSubclassOf<UGUIS_GameUIContext> ContextClass)
|
||||
{
|
||||
if (FGUIS_RootViewportLayoutInfo* LayoutInfo = RootViewportLayouts.FindByKey(LocalPlayer))
|
||||
{
|
||||
int32 FoundContext = INDEX_NONE;
|
||||
for (int32 i = 0; i < LayoutInfo->Contexts.Num(); i++)
|
||||
{
|
||||
if (LayoutInfo->Contexts[i] && LayoutInfo->Contexts[i]->GetClass() == ContextClass)
|
||||
{
|
||||
FoundContext = i;
|
||||
UE_LOG(LogGUIS, Verbose, TEXT("[%s] unregistered context of type(%s) for %s."), *GetName(), *LayoutInfo->Contexts[i]->GetClass()->GetName(), *GetNameSafe(LocalPlayer));
|
||||
break;
|
||||
}
|
||||
}
|
||||
LayoutInfo->Contexts.RemoveAt(FoundContext);
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_GameUIPolicy::AddUIAction(const ULocalPlayer* LocalPlayer, UCommonUserWidget* Target, const FDataTableRowHandle& InputAction, bool bShouldDisplayInActionBar,
|
||||
const FGUIS_UIActionExecutedDelegate& Callback, FGUIS_UIActionBindingHandle& BindingHandle)
|
||||
{
|
||||
if (FGUIS_RootViewportLayoutInfo* LayoutInfo = RootViewportLayouts.FindByKey(LocalPlayer))
|
||||
{
|
||||
if (IsValid(Target))
|
||||
{
|
||||
FBindUIActionArgs BindArgs(InputAction, bShouldDisplayInActionBar, FSimpleDelegate::CreateLambda([InputAction, Callback]()
|
||||
{
|
||||
Callback.ExecuteIfBound(InputAction.RowName);
|
||||
}));
|
||||
BindingHandle.Handle = Target->RegisterUIActionBinding(BindArgs);
|
||||
LayoutInfo->BindingHandles.Add(BindingHandle.Handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_GameUIPolicy::RemoveUIAction(const ULocalPlayer* LocalPlayer, FGUIS_UIActionBindingHandle& BindingHandle)
|
||||
{
|
||||
if (FGUIS_RootViewportLayoutInfo* LayoutInfo = RootViewportLayouts.FindByKey(LocalPlayer))
|
||||
{
|
||||
if (BindingHandle.Handle.IsValid())
|
||||
{
|
||||
UE_LOG(LogGUIS, Display, TEXT("Unregister binding for %s"), *BindingHandle.Handle.GetDisplayName().ToString())
|
||||
|
||||
BindingHandle.Handle.Unregister();
|
||||
LayoutInfo->BindingHandles.Remove(BindingHandle.Handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_GameUIPolicy::NotifyPlayerAdded(ULocalPlayer* LocalPlayer)
|
||||
{
|
||||
NotifyPlayerRemoved(LocalPlayer);
|
||||
|
||||
if (FGUIS_RootViewportLayoutInfo* LayoutInfo = RootViewportLayouts.FindByKey(LocalPlayer))
|
||||
{
|
||||
AddLayoutToViewport(LocalPlayer, LayoutInfo->RootLayout);
|
||||
LayoutInfo->bAddedToViewport = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
CreateLayoutWidget(LocalPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_GameUIPolicy::NotifyPlayerRemoved(ULocalPlayer* LocalPlayer)
|
||||
{
|
||||
if (FGUIS_RootViewportLayoutInfo* LayoutInfo = RootViewportLayouts.FindByKey(LocalPlayer))
|
||||
{
|
||||
RemoveLayoutFromViewport(LocalPlayer, LayoutInfo->RootLayout);
|
||||
LayoutInfo->bAddedToViewport = false;
|
||||
|
||||
LayoutInfo->Contexts.Empty();
|
||||
|
||||
if (LocalMultiplayerInteractionMode == EGUIS_LocalMultiplayerInteractionMode::SingleToggle && !LocalPlayer->IsPrimaryPlayer())
|
||||
{
|
||||
UGUIS_GameUILayout* RootLayout = LayoutInfo->RootLayout;
|
||||
if (RootLayout && !RootLayout->IsDormant())
|
||||
{
|
||||
// We're removing a secondary player's root while it's in control - transfer control back to the primary player's root
|
||||
RootLayout->SetIsDormant(true);
|
||||
for (const FGUIS_RootViewportLayoutInfo& RootLayoutInfo : RootViewportLayouts)
|
||||
{
|
||||
if (RootLayoutInfo.LocalPlayer->IsPrimaryPlayer())
|
||||
{
|
||||
if (UGUIS_GameUILayout* PrimaryRootLayout = RootLayoutInfo.RootLayout)
|
||||
{
|
||||
PrimaryRootLayout->SetIsDormant(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_GameUIPolicy::NotifyPlayerDestroyed(ULocalPlayer* LocalPlayer)
|
||||
{
|
||||
NotifyPlayerRemoved(LocalPlayer);
|
||||
const int32 LayoutInfoIdx = RootViewportLayouts.IndexOfByKey(LocalPlayer);
|
||||
if (LayoutInfoIdx != INDEX_NONE)
|
||||
{
|
||||
UGUIS_GameUILayout* Layout = RootViewportLayouts[LayoutInfoIdx].RootLayout;
|
||||
RootViewportLayouts.RemoveAt(LayoutInfoIdx);
|
||||
|
||||
RemoveLayoutFromViewport(LocalPlayer, Layout);
|
||||
|
||||
OnRootLayoutReleased(LocalPlayer, Layout);
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_GameUIPolicy::AddLayoutToViewport(ULocalPlayer* LocalPlayer, UGUIS_GameUILayout* Layout)
|
||||
{
|
||||
UE_LOG(LogGUIS, Log, TEXT("[%s] is adding player [%s]'s root layout [%s] to the viewport"), *GetName(), *GetNameSafe(LocalPlayer), *GetNameSafe(Layout));
|
||||
|
||||
Layout->SetPlayerContext(FLocalPlayerContext(LocalPlayer));
|
||||
Layout->AddToPlayerScreen(1000);
|
||||
|
||||
OnRootLayoutAddedToViewport(LocalPlayer, Layout);
|
||||
}
|
||||
|
||||
void UGUIS_GameUIPolicy::RemoveLayoutFromViewport(ULocalPlayer* LocalPlayer, UGUIS_GameUILayout* Layout)
|
||||
{
|
||||
TWeakPtr<SWidget> LayoutSlateWidget = Layout->GetCachedWidget();
|
||||
if (LayoutSlateWidget.IsValid())
|
||||
{
|
||||
UE_LOG(LogGUIS, Log, TEXT("[%s] is removing player [%s]'s root layout [%s] from the viewport"), *GetName(), *GetNameSafe(LocalPlayer), *GetNameSafe(Layout));
|
||||
|
||||
Layout->RemoveFromParent();
|
||||
if (LayoutSlateWidget.IsValid())
|
||||
{
|
||||
UE_LOG(LogGUIS, Log, TEXT("Player [%s]'s root layout [%s] has been removed from the viewport, but other references to its underlying Slate widget still exist. Noting in case we leak it."),
|
||||
*GetNameSafe(LocalPlayer), *GetNameSafe(Layout));
|
||||
}
|
||||
|
||||
OnRootLayoutRemovedFromViewport(LocalPlayer, Layout);
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_GameUIPolicy::OnRootLayoutAddedToViewport(ULocalPlayer* LocalPlayer, UGUIS_GameUILayout* Layout)
|
||||
{
|
||||
#if WITH_EDITOR
|
||||
if (GIsEditor && LocalPlayer->IsPrimaryPlayer())
|
||||
{
|
||||
// So our controller will work in PIE without needing to click in the viewport
|
||||
FSlateApplication::Get().SetUserFocusToGameViewport(0);
|
||||
}
|
||||
#endif
|
||||
BP_OnRootLayoutAddedToViewport(LocalPlayer, Layout);
|
||||
}
|
||||
|
||||
void UGUIS_GameUIPolicy::OnRootLayoutRemovedFromViewport(ULocalPlayer* LocalPlayer, UGUIS_GameUILayout* Layout)
|
||||
{
|
||||
BP_OnRootLayoutRemovedFromViewport(LocalPlayer, Layout);
|
||||
}
|
||||
|
||||
void UGUIS_GameUIPolicy::OnRootLayoutReleased(ULocalPlayer* LocalPlayer, UGUIS_GameUILayout* Layout)
|
||||
{
|
||||
BP_OnRootLayoutReleased(LocalPlayer, Layout);
|
||||
}
|
||||
|
||||
void UGUIS_GameUIPolicy::RequestPrimaryControl(UGUIS_GameUILayout* Layout)
|
||||
{
|
||||
if (LocalMultiplayerInteractionMode == EGUIS_LocalMultiplayerInteractionMode::SingleToggle && Layout->IsDormant())
|
||||
{
|
||||
for (const FGUIS_RootViewportLayoutInfo& LayoutInfo : RootViewportLayouts)
|
||||
{
|
||||
UGUIS_GameUILayout* RootLayout = LayoutInfo.RootLayout;
|
||||
if (RootLayout && !RootLayout->IsDormant())
|
||||
{
|
||||
RootLayout->SetIsDormant(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Layout->SetIsDormant(false);
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_GameUIPolicy::CreateLayoutWidget(ULocalPlayer* LocalPlayer)
|
||||
{
|
||||
if (APlayerController* PlayerController = LocalPlayer->GetPlayerController(GetWorld()))
|
||||
{
|
||||
TSubclassOf<UGUIS_GameUILayout> LayoutWidgetClass = GetLayoutWidgetClass(LocalPlayer);
|
||||
if (ensure(LayoutWidgetClass && !LayoutWidgetClass->HasAnyClassFlags(CLASS_Abstract)))
|
||||
{
|
||||
UGUIS_GameUILayout* NewLayoutObject = CreateWidget<UGUIS_GameUILayout>(PlayerController, LayoutWidgetClass);
|
||||
RootViewportLayouts.Emplace(LocalPlayer, NewLayoutObject, true);
|
||||
|
||||
AddLayoutToViewport(LocalPlayer, NewLayoutObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TSubclassOf<UGUIS_GameUILayout> UGUIS_GameUIPolicy::GetLayoutWidgetClass(ULocalPlayer* LocalPlayer)
|
||||
{
|
||||
return LayoutClass.LoadSynchronous();
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "UI/GUIS_GameUIStructLibrary.h"
|
||||
#include "Engine/LocalPlayer.h"
|
||||
#include "UI/GUIS_GameUIContext.h"
|
||||
#include "UI/GUIS_GameUILayout.h"
|
||||
|
||||
FGUIS_UIContextBindingHandle::FGUIS_UIContextBindingHandle(ULocalPlayer* InLocalPlayer, UClass* InContextClass)
|
||||
{
|
||||
LocalPlayer = InLocalPlayer;
|
||||
ContextClass = InContextClass;
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "UI/GUIS_GameUISubsystem.h"
|
||||
#include "GameFramework/Pawn.h"
|
||||
#include "GUIS_GenericUISystemSettings.h"
|
||||
#include "CommonUserWidget.h"
|
||||
#include "GUIS_LogChannels.h"
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "Input/CommonUIInputTypes.h"
|
||||
#include "UI/GUIS_GameUIContext.h"
|
||||
#include "UI/GUIS_GameUIPolicy.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GUIS_GameUISubsystem)
|
||||
|
||||
class FSubsystemCollectionBase;
|
||||
class UClass;
|
||||
|
||||
void UGUIS_GameUISubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||||
{
|
||||
Super::Initialize(Collection);
|
||||
|
||||
if (UGUIS_GenericUISystemSettings::Get()->GameUIPolicyClass.IsNull())
|
||||
{
|
||||
UE_LOG(LogGUIS, Error, TEXT("GUIS_GameUISubsystem::Initialize Failed, Missing GameUIPolicyClass in GenericUISystemSettings!!!"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CurrentPolicy)
|
||||
{
|
||||
TSubclassOf<UGUIS_GameUIPolicy> PolicyClass = UGUIS_GenericUISystemSettings::Get()->GameUIPolicyClass.LoadSynchronous();
|
||||
if (PolicyClass)
|
||||
{
|
||||
UGUIS_GameUIPolicy* NewPolicy = NewObject<UGUIS_GameUIPolicy>(this, PolicyClass);
|
||||
if (NewPolicy)
|
||||
{
|
||||
SwitchToPolicy(NewPolicy);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogGUIS, Error, TEXT("GUIS_GameUISubsystem::Initialize Failed, failed to create Game UI Policy from class:%s!"), *PolicyClass->GetName());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogGUIS, Error, TEXT("GUIS_GameUISubsystem::Initialize Failed, Missing GameUIPolicyClass in GenericUISystemSettings!!!"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_GameUISubsystem::Deinitialize()
|
||||
{
|
||||
Super::Deinitialize();
|
||||
|
||||
SwitchToPolicy(nullptr);
|
||||
}
|
||||
|
||||
bool UGUIS_GameUISubsystem::ShouldCreateSubsystem(UObject* Outer) const
|
||||
{
|
||||
if (CastChecked<UGameInstance>(Outer)->IsDedicatedServerInstance())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
TArray<UClass*> ChildClasses;
|
||||
GetDerivedClasses(GetClass(), ChildClasses, false);
|
||||
|
||||
if (ChildClasses.Num() == 0)
|
||||
{
|
||||
UE_LOG(LogGUIS, Display, TEXT("No override implementation found for UGUIS_GameUISubsystem, So creating it."))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void UGUIS_GameUISubsystem::AddPlayer(ULocalPlayer* LocalPlayer)
|
||||
{
|
||||
NotifyPlayerAdded(LocalPlayer);
|
||||
}
|
||||
|
||||
void UGUIS_GameUISubsystem::RemovePlayer(ULocalPlayer* LocalPlayer)
|
||||
{
|
||||
NotifyPlayerDestroyed(LocalPlayer);
|
||||
}
|
||||
|
||||
void UGUIS_GameUISubsystem::NotifyPlayerAdded(ULocalPlayer* LocalPlayer)
|
||||
{
|
||||
if (ensure(LocalPlayer) && CurrentPolicy)
|
||||
{
|
||||
CurrentPolicy->NotifyPlayerAdded(LocalPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_GameUISubsystem::NotifyPlayerRemoved(ULocalPlayer* LocalPlayer)
|
||||
{
|
||||
if (LocalPlayer && CurrentPolicy)
|
||||
{
|
||||
CurrentPolicy->NotifyPlayerRemoved(LocalPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_GameUISubsystem::NotifyPlayerDestroyed(ULocalPlayer* LocalPlayer)
|
||||
{
|
||||
if (LocalPlayer && CurrentPolicy)
|
||||
{
|
||||
CurrentPolicy->NotifyPlayerDestroyed(LocalPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_GameUISubsystem::RegisterUIActionBinding(UCommonUserWidget* Target, FDataTableRowHandle InputAction, bool bShouldDisplayInActionBar, const FGUIS_UIActionExecutedDelegate& Callback,
|
||||
FGUIS_UIActionBindingHandle& BindingHandle)
|
||||
{
|
||||
if (IsValid(Target))
|
||||
{
|
||||
FBindUIActionArgs BindArgs(InputAction, bShouldDisplayInActionBar, FSimpleDelegate::CreateLambda([InputAction, Callback]()
|
||||
{
|
||||
Callback.ExecuteIfBound(InputAction.RowName);
|
||||
}));
|
||||
|
||||
BindingHandle.Handle = Target->RegisterUIActionBinding(BindArgs);
|
||||
BindingHandles.Add(BindingHandle.Handle);
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_GameUISubsystem::UnregisterBinding(FGUIS_UIActionBindingHandle& BindingHandle)
|
||||
{
|
||||
if (BindingHandle.Handle.IsValid())
|
||||
{
|
||||
UE_LOG(LogGUIS, Display, TEXT("Unregister binding for %s"), *BindingHandle.Handle.GetDisplayName().ToString())
|
||||
|
||||
BindingHandle.Handle.Unregister();
|
||||
BindingHandles.Remove(BindingHandle.Handle);
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_GameUISubsystem::RegisterUIActionBindingForPlayer(ULocalPlayer* LocalPlayer, UCommonUserWidget* Target, FDataTableRowHandle InputAction, bool bShouldDisplayInActionBar,
|
||||
const FGUIS_UIActionExecutedDelegate& Callback, FGUIS_UIActionBindingHandle& BindingHandle)
|
||||
{
|
||||
if (LocalPlayer && CurrentPolicy)
|
||||
{
|
||||
CurrentPolicy->AddUIAction(LocalPlayer, Target, InputAction, bShouldDisplayInActionBar, Callback, BindingHandle);
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_GameUISubsystem::UnregisterUIActionBindingForPlayer(ULocalPlayer* LocalPlayer, FGUIS_UIActionBindingHandle& BindingHandle)
|
||||
{
|
||||
if (LocalPlayer && CurrentPolicy)
|
||||
{
|
||||
CurrentPolicy->RemoveUIAction(LocalPlayer, BindingHandle);
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_GameUISubsystem::RegisterUIContextForPlayer(ULocalPlayer* LocalPlayer, UGUIS_GameUIContext* Context, FGUIS_UIContextBindingHandle& BindingHandle)
|
||||
{
|
||||
if (LocalPlayer && CurrentPolicy && Context)
|
||||
{
|
||||
if (CurrentPolicy->AddContext(LocalPlayer, Context))
|
||||
{
|
||||
BindingHandle = FGUIS_UIContextBindingHandle(LocalPlayer, Context->GetClass());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_GameUISubsystem::RegisterUIContextForActor(AActor* Actor, UGUIS_GameUIContext* Context, FGUIS_UIContextBindingHandle& BindingHandle)
|
||||
{
|
||||
if (!IsValid(Actor))
|
||||
{
|
||||
UE_LOG(LogGUIS, Error, TEXT("Trying to register ui context for invalid pawn!"))
|
||||
return;
|
||||
}
|
||||
APawn* Pawn = Cast<APawn>(Actor);
|
||||
if (Pawn == nullptr || !Pawn->IsLocallyControlled())
|
||||
{
|
||||
UE_LOG(LogGUIS, Error, TEXT("Trying to register ui context for actor(%s) which is not locally controlled pawn!"), *Pawn->GetName())
|
||||
return;
|
||||
}
|
||||
APlayerController* PC = Cast<APlayerController>(Pawn->GetController());
|
||||
if (PC == nullptr)
|
||||
{
|
||||
UE_LOG(LogGUIS, Error, TEXT("Trying to register ui context for pawn(%s) which doesn't have valid player controller"), *Pawn->GetName())
|
||||
return;
|
||||
}
|
||||
RegisterUIContextForPlayer(PC->GetLocalPlayer(), Context, BindingHandle);
|
||||
}
|
||||
|
||||
bool UGUIS_GameUISubsystem::FindUIContextForPlayer(ULocalPlayer* LocalPlayer, TSubclassOf<UGUIS_GameUIContext> ContextClass, UGUIS_GameUIContext*& OutContext)
|
||||
{
|
||||
if (LocalPlayer && CurrentPolicy && ContextClass != nullptr)
|
||||
{
|
||||
if (UGUIS_GameUIContext* Context = CurrentPolicy->GetContext(LocalPlayer, ContextClass))
|
||||
{
|
||||
if (Context->GetClass() == ContextClass)
|
||||
{
|
||||
OutContext = Context;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UGUIS_GameUISubsystem::FindUIContextFromHandle(FGUIS_UIContextBindingHandle& BindingHandle, TSubclassOf<UGUIS_GameUIContext> ContextClass, UGUIS_GameUIContext*& OutContext)
|
||||
{
|
||||
if (BindingHandle.LocalPlayer && BindingHandle.ContextClass && CurrentPolicy)
|
||||
{
|
||||
OutContext = CurrentPolicy->FindContext(BindingHandle.LocalPlayer, BindingHandle.ContextClass);
|
||||
}
|
||||
return OutContext != nullptr;
|
||||
}
|
||||
|
||||
void UGUIS_GameUISubsystem::UnregisterUIContextForPlayer(FGUIS_UIContextBindingHandle& BindingHandle)
|
||||
{
|
||||
if (BindingHandle.LocalPlayer && BindingHandle.ContextClass && CurrentPolicy)
|
||||
{
|
||||
CurrentPolicy->RemoveContext(BindingHandle.LocalPlayer, BindingHandle.ContextClass);
|
||||
BindingHandle.LocalPlayer = nullptr;
|
||||
BindingHandle.ContextClass = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_GameUISubsystem::SwitchToPolicy(UGUIS_GameUIPolicy* InPolicy)
|
||||
{
|
||||
if (CurrentPolicy != InPolicy)
|
||||
{
|
||||
CurrentPolicy = InPolicy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "UI/GUIS_GameplayTags.h"
|
||||
|
||||
namespace GUIS_GameModalActionTags
|
||||
{
|
||||
UE_DEFINE_GAMEPLAY_TAG(Ok, "GUIS.Modal.Action.Ok");
|
||||
UE_DEFINE_GAMEPLAY_TAG(Cancel, "GUIS.Modal.Action.Cancel");
|
||||
UE_DEFINE_GAMEPLAY_TAG(Yes, "GUIS.Modal.Action.Yes");
|
||||
UE_DEFINE_GAMEPLAY_TAG(No, "GUIS.Modal.Action.No");
|
||||
UE_DEFINE_GAMEPLAY_TAG(Unknown, "GUIS.Modal.Action.Unknown");
|
||||
}
|
||||
|
||||
namespace GUIS_GameUILayerTags
|
||||
{
|
||||
UE_DEFINE_GAMEPLAY_TAG(Modal, "GUIS.Layer.Modal");
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "UI/Mobile/GUIS_JoystickWidget.h"
|
||||
|
||||
#include "CommonHardwareVisibilityBorder.h"
|
||||
#include "Components/Image.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GUIS_JoystickWidget)
|
||||
|
||||
#define LOCTEXT_NAMESPACE "GUIS_Joystick"
|
||||
|
||||
UGUIS_JoystickWidget::UGUIS_JoystickWidget(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
SetConsumePointerInput(true);
|
||||
}
|
||||
|
||||
FReply UGUIS_JoystickWidget::NativeOnTouchStarted(const FGeometry& InGeometry, const FPointerEvent& InGestureEvent)
|
||||
{
|
||||
Super::NativeOnTouchStarted(InGeometry, InGestureEvent);
|
||||
|
||||
TouchOrigin = InGestureEvent.GetScreenSpacePosition();
|
||||
|
||||
FReply Reply = FReply::Handled();
|
||||
if (!HasMouseCaptureByUser(InGestureEvent.GetUserIndex(), InGestureEvent.GetPointerIndex()))
|
||||
{
|
||||
Reply.CaptureMouse(GetCachedWidget().ToSharedRef());
|
||||
}
|
||||
return Reply;
|
||||
}
|
||||
|
||||
FReply UGUIS_JoystickWidget::NativeOnTouchMoved(const FGeometry& InGeometry, const FPointerEvent& InGestureEvent)
|
||||
{
|
||||
Super::NativeOnTouchMoved(InGeometry, InGestureEvent);
|
||||
HandleTouchDelta(InGeometry, InGestureEvent);
|
||||
|
||||
FReply Reply = FReply::Handled();
|
||||
if (!HasMouseCaptureByUser(InGestureEvent.GetUserIndex(), InGestureEvent.GetPointerIndex()))
|
||||
{
|
||||
Reply.CaptureMouse(GetCachedWidget().ToSharedRef());
|
||||
}
|
||||
return Reply;
|
||||
}
|
||||
|
||||
FReply UGUIS_JoystickWidget::NativeOnTouchEnded(const FGeometry& InGeometry, const FPointerEvent& InGestureEvent)
|
||||
{
|
||||
StopInputSimulation();
|
||||
return FReply::Handled().ReleaseMouseCapture();
|
||||
}
|
||||
|
||||
void UGUIS_JoystickWidget::NativeOnMouseLeave(const FPointerEvent& InMouseEvent)
|
||||
{
|
||||
Super::NativeOnMouseLeave(InMouseEvent);
|
||||
StopInputSimulation();
|
||||
}
|
||||
|
||||
void UGUIS_JoystickWidget::NativeTick(const FGeometry& MyGeometry, float InDeltaTime)
|
||||
{
|
||||
Super::NativeTick(MyGeometry, InDeltaTime);
|
||||
|
||||
if (!CommonVisibilityBorder || CommonVisibilityBorder->IsVisible())
|
||||
{
|
||||
// Move the inner stick icon around with the vector
|
||||
if (JoystickForeground && JoystickBackground)
|
||||
{
|
||||
JoystickForeground->SetRenderTranslation(
|
||||
(bNegateYAxis ? FVector2D(1.0f, -1.0f) : FVector2D(1.0f)) *
|
||||
StickVector *
|
||||
(JoystickBackground->GetDesiredSize() * 0.5f)
|
||||
);
|
||||
}
|
||||
InputKeyValue2D(StickVector);
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_JoystickWidget::HandleTouchDelta(const FGeometry& InGeometry, const FPointerEvent& InGestureEvent)
|
||||
{
|
||||
const FVector2D& ScreenSpacePos = InGestureEvent.GetScreenSpacePosition();
|
||||
|
||||
// The center of the geo locally is just its size
|
||||
FVector2D LocalStickCenter = InGeometry.GetAbsoluteSize();
|
||||
|
||||
FVector2D ScreenSpaceStickCenter = InGeometry.LocalToAbsolute(LocalStickCenter);
|
||||
// Get the offset from the origin
|
||||
FVector2D MoveStickOffset = (ScreenSpacePos - ScreenSpaceStickCenter);
|
||||
if (bNegateYAxis)
|
||||
{
|
||||
MoveStickOffset *= FVector2D(1.0f, -1.0f);
|
||||
}
|
||||
|
||||
FVector2D MoveStickDir = FVector2D::ZeroVector;
|
||||
float MoveStickLength = 0.0f;
|
||||
MoveStickOffset.ToDirectionAndLength(MoveStickDir, MoveStickLength);
|
||||
|
||||
MoveStickLength = FMath::Min(MoveStickLength, StickRange);
|
||||
MoveStickOffset = MoveStickDir * MoveStickLength;
|
||||
|
||||
StickVector = MoveStickOffset / StickRange;
|
||||
}
|
||||
|
||||
void UGUIS_JoystickWidget::StopInputSimulation()
|
||||
{
|
||||
TouchOrigin = FVector2D::ZeroVector;
|
||||
StickVector = FVector2D::ZeroVector;
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,183 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "UI/Mobile/GUIS_SimulatedInputWidget.h"
|
||||
#include "Runtime/Launch/Resources/Version.h"
|
||||
#include "EnhancedInputSubsystems.h"
|
||||
#include "GUIS_LogChannels.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GUIS_SimulatedInputWidget)
|
||||
|
||||
#define LOCTEXT_NAMESPACE "GUIS_SimulatedInputWidget"
|
||||
|
||||
UGUIS_SimulatedInputWidget::UGUIS_SimulatedInputWidget(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
SetConsumePointerInput(true);
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
const FText UGUIS_SimulatedInputWidget::GetPaletteCategory()
|
||||
{
|
||||
return LOCTEXT("PalleteCategory", "Generic UI");
|
||||
}
|
||||
#endif // WITH_EDITOR
|
||||
|
||||
void UGUIS_SimulatedInputWidget::NativeConstruct()
|
||||
{
|
||||
// Find initial key, then listen for any changes to control mappings
|
||||
QueryKeyToSimulate();
|
||||
|
||||
if (UEnhancedInputLocalPlayerSubsystem* System = GetEnhancedInputSubsystem())
|
||||
{
|
||||
System->ControlMappingsRebuiltDelegate.AddUniqueDynamic(this, &UGUIS_SimulatedInputWidget::OnControlMappingsRebuilt);
|
||||
}
|
||||
|
||||
Super::NativeConstruct();
|
||||
}
|
||||
|
||||
void UGUIS_SimulatedInputWidget::NativeDestruct()
|
||||
{
|
||||
if (UEnhancedInputLocalPlayerSubsystem* System = GetEnhancedInputSubsystem())
|
||||
{
|
||||
System->ControlMappingsRebuiltDelegate.RemoveAll(this);
|
||||
}
|
||||
|
||||
Super::NativeDestruct();
|
||||
}
|
||||
|
||||
FReply UGUIS_SimulatedInputWidget::NativeOnTouchEnded(const FGeometry& InGeometry, const FPointerEvent& InGestureEvent)
|
||||
{
|
||||
FlushSimulatedInput();
|
||||
|
||||
return Super::NativeOnTouchEnded(InGeometry, InGestureEvent);
|
||||
}
|
||||
|
||||
UEnhancedInputLocalPlayerSubsystem* UGUIS_SimulatedInputWidget::GetEnhancedInputSubsystem() const
|
||||
{
|
||||
if (APlayerController* PC = GetOwningPlayer())
|
||||
{
|
||||
if (ULocalPlayer* LP = GetOwningLocalPlayer())
|
||||
{
|
||||
return LP->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UEnhancedPlayerInput* UGUIS_SimulatedInputWidget::GetPlayerInput() const
|
||||
{
|
||||
if (UEnhancedInputLocalPlayerSubsystem* System = GetEnhancedInputSubsystem())
|
||||
{
|
||||
return System->GetPlayerInput();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void UGUIS_SimulatedInputWidget::InputKeyValue(const FVector& Value)
|
||||
{
|
||||
const APlayerController* PC = GetOwningPlayer();
|
||||
const FPlatformUserId UserId = PC ? PC->GetPlatformUserId() : PLATFORMUSERID_NONE;
|
||||
// If we have an associated input action then we can use it
|
||||
if (AssociatedAction)
|
||||
{
|
||||
if (UEnhancedInputLocalPlayerSubsystem* System = GetEnhancedInputSubsystem())
|
||||
{
|
||||
// We don't want to apply any modifiers or triggers to this action, but they are required for the function signature
|
||||
TArray<UInputModifier*> Modifiers;
|
||||
TArray<UInputTrigger*> Triggers;
|
||||
System->InjectInputVectorForAction(AssociatedAction, Value, Modifiers, Triggers);
|
||||
}
|
||||
}
|
||||
// In case there is no associated input action, we can attempt to simulate input on the fallback key
|
||||
else if (UEnhancedPlayerInput* Input = GetPlayerInput())
|
||||
{
|
||||
#if ENGINE_MINOR_VERSION < 6
|
||||
if (KeyToSimulate.IsValid())
|
||||
{
|
||||
FInputKeyParams Params;
|
||||
Params.Delta = Value;
|
||||
Params.Key = KeyToSimulate;
|
||||
Params.NumSamples = 1;
|
||||
Params.DeltaTime = GetWorld()->GetDeltaSeconds();
|
||||
Params.bIsGamepadOverride = KeyToSimulate.IsGamepadKey();
|
||||
|
||||
Input->InputKey(Params);
|
||||
}
|
||||
#else
|
||||
const FInputDeviceId DeviceToSimulate = IPlatformInputDeviceMapper::Get().GetPrimaryInputDeviceForUser(UserId);
|
||||
if(KeyToSimulate.IsValid())
|
||||
{
|
||||
const float DeltaTime = GetWorld()->GetDeltaSeconds();
|
||||
auto SimulateKeyPress = [Input, DeltaTime, DeviceToSimulate](const FKey& KeyToSim, const float Value, const EInputEvent Event)
|
||||
{
|
||||
FInputKeyEventArgs Args = FInputKeyEventArgs::CreateSimulated(
|
||||
KeyToSim,
|
||||
Event,
|
||||
Value,
|
||||
KeyToSim.IsAnalog() ? 1 : 0,
|
||||
DeviceToSimulate);
|
||||
|
||||
Args.DeltaTime = DeltaTime;
|
||||
|
||||
Input->InputKey(Args);
|
||||
};
|
||||
|
||||
// For keys which are the "root" of the key pair (such as Mouse2D
|
||||
// being made up of the MouseX and MouseY keys) we should call InputKey for each key in the pair,
|
||||
// not the paired key itself. This is so that the events accumulate correctly in
|
||||
// the key state map of UPlayerInput. All input events
|
||||
// from the message handler and viewport client work this way, so when we simulate key inputs, we should
|
||||
// do so as well.
|
||||
if (const EKeys::FPairedKeyDetails* PairDetails = EKeys::GetPairedKeyDetails(KeyToSimulate))
|
||||
{
|
||||
SimulateKeyPress(PairDetails->XKeyDetails->GetKey(), Value.X, IE_Axis);
|
||||
SimulateKeyPress(PairDetails->YKeyDetails->GetKey(), Value.Y, IE_Axis);
|
||||
}
|
||||
else
|
||||
{
|
||||
SimulateKeyPress(KeyToSimulate, Value.X, IE_Pressed);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogGUIS, Error, TEXT("'%s' is attempting to simulate input but has no player input!"), *GetNameSafe(this));
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_SimulatedInputWidget::InputKeyValue2D(const FVector2D& Value)
|
||||
{
|
||||
InputKeyValue(FVector(Value.X, Value.Y, 0.0));
|
||||
}
|
||||
|
||||
void UGUIS_SimulatedInputWidget::FlushSimulatedInput()
|
||||
{
|
||||
if (UEnhancedPlayerInput* Input = GetPlayerInput())
|
||||
{
|
||||
Input->FlushPressedKeys();
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_SimulatedInputWidget::QueryKeyToSimulate()
|
||||
{
|
||||
if (UEnhancedInputLocalPlayerSubsystem* System = GetEnhancedInputSubsystem())
|
||||
{
|
||||
TArray<FKey> Keys = System->QueryKeysMappedToAction(AssociatedAction);
|
||||
if (!Keys.IsEmpty() && Keys[0].IsValid())
|
||||
{
|
||||
KeyToSimulate = Keys[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
KeyToSimulate = FallbackBindingKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_SimulatedInputWidget::OnControlMappingsRebuilt()
|
||||
{
|
||||
QueryKeyToSimulate();
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "UI/Modal/GUIS_GameModal.h"
|
||||
|
||||
#include "CommonButtonBase.h"
|
||||
#include "CommonTextBlock.h"
|
||||
#include "Components/DynamicEntryBox.h"
|
||||
#include "UI/Foundation/GUIS_ButtonBase.h"
|
||||
#include "UI/Modal/GUIS_GameModalTypes.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GUIS_GameModal)
|
||||
|
||||
#define LOCTEXT_NAMESPACE "GUIS_GameModal"
|
||||
|
||||
UGUIS_GameModalWidget::UGUIS_GameModalWidget()
|
||||
{
|
||||
bIsModal = true;
|
||||
}
|
||||
|
||||
void UGUIS_GameModalWidget::SetupModal(const UGUIS_ModalDefinition* ModalDefinition, FGUIS_ModalActionResultSignature ModalActionCallback)
|
||||
{
|
||||
OnModalActionCallback = ModalActionCallback;
|
||||
|
||||
EntryBox_Buttons->Reset<UGUIS_ButtonBase>([](UGUIS_ButtonBase& Button)
|
||||
{
|
||||
Button.OnClicked().Clear();
|
||||
});
|
||||
|
||||
Text_Header->SetText(ModalDefinition->Header);
|
||||
Text_Body->SetText(ModalDefinition->Body);
|
||||
|
||||
for (const auto& Pair : ModalDefinition->ModalActions)
|
||||
{
|
||||
UGUIS_ButtonBase* Button = EntryBox_Buttons->CreateEntry<UGUIS_ButtonBase>(!Pair.Value.ButtonType.IsNull() ? Pair.Value.ButtonType.LoadSynchronous() : nullptr);
|
||||
Button->SetTriggeringInputAction(Pair.Value.InputAction);
|
||||
Button->OnClicked().AddUObject(this, &ThisClass::CloseModal, Pair.Key);
|
||||
if (!Pair.Value.DisplayText.IsEmpty())
|
||||
{
|
||||
Button->SetButtonText(Pair.Value.DisplayText);
|
||||
}
|
||||
}
|
||||
|
||||
OnSetupModal(ModalDefinition);
|
||||
}
|
||||
|
||||
void UGUIS_GameModalWidget::CloseModal(FGameplayTag ModalActionResult)
|
||||
{
|
||||
DeactivateWidget();
|
||||
OnModalActionCallback.ExecuteIfBound(ModalActionResult);
|
||||
}
|
||||
|
||||
void UGUIS_GameModalWidget::KillModal()
|
||||
{
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "UI/Modal/GUIS_GameModalTypes.h"
|
||||
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "Engine/LocalPlayer.h"
|
||||
#include "UObject/UObjectHash.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GUIS_GameModalTypes)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user