第一次提交
This commit is contained in:
39
Plugins/GGS/Source/GenericUISystem/GenericUISystem.Build.cs
Normal file
39
Plugins/GGS/Source/GenericUISystem/GenericUISystem.Build.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class GenericUISystem : ModuleRules
|
||||
{
|
||||
public GenericUISystem(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core",
|
||||
"CommonUI"
|
||||
}
|
||||
);
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"ApplicationCore",
|
||||
"EnhancedInput",
|
||||
"PropertyPath",
|
||||
"GameplayTags",
|
||||
"UMG",
|
||||
"InputCore",
|
||||
"CommonInput",
|
||||
"DeveloperSettings",
|
||||
"ModularGameplay"
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GUIS_GenericUISystemSettings.h"
|
||||
|
||||
const UGUIS_GenericUISystemSettings* UGUIS_GenericUISystemSettings::Get()
|
||||
{
|
||||
return GetDefault<UGUIS_GenericUISystemSettings>();
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
|
||||
#include "GUIS_LogChannels.h"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogGUIS)
|
||||
DEFINE_LOG_CATEGORY(LogGUIS_Extension);
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "GenericUISystem.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FGenericUISystemModule"
|
||||
|
||||
void FGenericUISystemModule::StartupModule()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void FGenericUISystemModule::ShutdownModule()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
IMPLEMENT_MODULE(FGenericUISystemModule, GenericUISystem)
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "UIExtension/GUIS_GameUIExtensionPointWidget.h"
|
||||
#include "Widgets/SOverlay.h"
|
||||
#include "TimerManager.h"
|
||||
#include "Widgets/Text/STextBlock.h"
|
||||
#include "Editor/WidgetCompilerLog.h"
|
||||
#include "Misc/UObjectToken.h"
|
||||
#include "GameFramework/PlayerState.h"
|
||||
#include "UI/Common/GUIS_UserWidgetInterface.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GUIS_GameUIExtensionPointWidget)
|
||||
|
||||
#define LOCTEXT_NAMESPACE "UIExtension"
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
// UGUIS_GameUIExtensionPointWidget
|
||||
|
||||
UGUIS_GameUIExtensionPointWidget::UGUIS_GameUIExtensionPointWidget(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
}
|
||||
|
||||
void UGUIS_GameUIExtensionPointWidget::ReleaseSlateResources(bool bReleaseChildren)
|
||||
{
|
||||
ResetExtensionPoint();
|
||||
|
||||
Super::ReleaseSlateResources(bReleaseChildren);
|
||||
}
|
||||
|
||||
TSharedRef<SWidget> UGUIS_GameUIExtensionPointWidget::RebuildWidget()
|
||||
{
|
||||
if (!IsDesignTime() && ExtensionPointTag.IsValid())
|
||||
{
|
||||
ResetExtensionPoint();
|
||||
RegisterExtensionPoint();
|
||||
|
||||
RegisterForPlayerStateIfReady();
|
||||
}
|
||||
|
||||
if (IsDesignTime())
|
||||
{
|
||||
auto GetExtensionPointText = [this]()
|
||||
{
|
||||
return FText::Format(LOCTEXT("DesignTime_ExtensionPointLabel", "Extension Point\n{0}"), FText::FromName(ExtensionPointTag.GetTagName()));
|
||||
};
|
||||
|
||||
TSharedRef<SOverlay> MessageBox = SNew(SOverlay);
|
||||
|
||||
MessageBox->AddSlot()
|
||||
.Padding(5.0f)
|
||||
.HAlign(HAlign_Center)
|
||||
.VAlign(VAlign_Center)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Justification(ETextJustify::Center)
|
||||
.Text_Lambda(GetExtensionPointText)
|
||||
];
|
||||
|
||||
return MessageBox;
|
||||
}
|
||||
return Super::RebuildWidget();
|
||||
}
|
||||
|
||||
void UGUIS_GameUIExtensionPointWidget::RegisterForPlayerStateIfReady()
|
||||
{
|
||||
if (APlayerController* PC = GetOwningPlayer())
|
||||
{
|
||||
if (APlayerState* PS = PC->GetPlayerState<APlayerState>())
|
||||
{
|
||||
RegisterExtensionPointForPlayerState(GetOwningLocalPlayer(), PS);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GetWorld()->GetTimerManager().SetTimer(TimerHandle, this, &ThisClass::OnCheckPlayerState, 0.2f);
|
||||
}
|
||||
}
|
||||
|
||||
bool UGUIS_GameUIExtensionPointWidget::CheckPlayerState()
|
||||
{
|
||||
if (APlayerController* PC = GetOwningPlayer())
|
||||
{
|
||||
if (APlayerState* PS = PC->GetPlayerState<APlayerState>())
|
||||
{
|
||||
if (TimerHandle.IsValid())
|
||||
{
|
||||
TimerHandle.Invalidate();
|
||||
}
|
||||
RegisterExtensionPointForPlayerState(GetOwningLocalPlayer(), PS);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void UGUIS_GameUIExtensionPointWidget::OnCheckPlayerState()
|
||||
{
|
||||
if (APlayerController* PC = GetOwningPlayer())
|
||||
{
|
||||
if (APlayerState* PS = PC->GetPlayerState<APlayerState>())
|
||||
{
|
||||
if (TimerHandle.IsValid())
|
||||
{
|
||||
TimerHandle.Invalidate();
|
||||
}
|
||||
RegisterExtensionPointForPlayerState(GetOwningLocalPlayer(), PS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_GameUIExtensionPointWidget::ResetExtensionPoint()
|
||||
{
|
||||
ResetInternal();
|
||||
|
||||
ExtensionMapping.Reset();
|
||||
for (FGUIS_GameUIExtPointHandle& Handle : ExtensionPointHandles)
|
||||
{
|
||||
Handle.Unregister();
|
||||
}
|
||||
ExtensionPointHandles.Reset();
|
||||
}
|
||||
|
||||
void UGUIS_GameUIExtensionPointWidget::RegisterExtensionPoint()
|
||||
{
|
||||
if (UGUIS_ExtensionSubsystem* ExtensionSubsystem = GetWorld()->GetSubsystem<UGUIS_ExtensionSubsystem>())
|
||||
{
|
||||
TArray<UClass*> AllowedDataClasses = LoadAllowedDataClasses();
|
||||
|
||||
ExtensionPointHandles.Add(ExtensionSubsystem->RegisterExtensionPoint(
|
||||
ExtensionPointTag, ExtensionPointTagMatch, AllowedDataClasses,
|
||||
FExtendExtensionPointDelegate::CreateUObject(this, &ThisClass::OnAddOrRemoveExtension)
|
||||
));
|
||||
|
||||
ExtensionPointHandles.Add(ExtensionSubsystem->RegisterExtensionPointForContext(
|
||||
ExtensionPointTag, GetOwningLocalPlayer(), ExtensionPointTagMatch, AllowedDataClasses,
|
||||
FExtendExtensionPointDelegate::CreateUObject(this, &ThisClass::OnAddOrRemoveExtension)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_GameUIExtensionPointWidget::RegisterExtensionPointForPlayerState(ULocalPlayer* LocalPlayer, APlayerState* PlayerState)
|
||||
{
|
||||
if (UGUIS_ExtensionSubsystem* ExtensionSubsystem = GetWorld()->GetSubsystem<UGUIS_ExtensionSubsystem>())
|
||||
{
|
||||
TArray<UClass*> AllowedDataClasses = LoadAllowedDataClasses();
|
||||
|
||||
ExtensionPointHandles.Add(ExtensionSubsystem->RegisterExtensionPointForContext(
|
||||
ExtensionPointTag, PlayerState, ExtensionPointTagMatch, AllowedDataClasses,
|
||||
FExtendExtensionPointDelegate::CreateUObject(this, &ThisClass::OnAddOrRemoveExtension)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
TArray<UClass*> UGUIS_GameUIExtensionPointWidget::LoadAllowedDataClasses() const
|
||||
{
|
||||
TArray<UClass*> AllowedDataClasses;
|
||||
AllowedDataClasses.Add(UUserWidget::StaticClass());
|
||||
|
||||
for (const TSoftClassPtr<UObject>& DataClass : DataClasses)
|
||||
{
|
||||
if (!DataClass.IsNull())
|
||||
{
|
||||
AllowedDataClasses.Add(DataClass.LoadSynchronous());
|
||||
}
|
||||
}
|
||||
return AllowedDataClasses;
|
||||
}
|
||||
|
||||
void UGUIS_GameUIExtensionPointWidget::OnAddOrRemoveExtension(EGUIS_GameUIExtAction Action, const FGUIS_GameUIExtRequest& Request)
|
||||
{
|
||||
if (Action == EGUIS_GameUIExtAction::Added)
|
||||
{
|
||||
UObject* Data = Request.Data;
|
||||
TSubclassOf<UUserWidget> WidgetClass(Cast<UClass>(Data));
|
||||
if (WidgetClass)
|
||||
{
|
||||
UUserWidget* Widget = CreateEntryInternal(WidgetClass);
|
||||
ExtensionMapping.Add(Request.ExtensionHandle, Widget);
|
||||
|
||||
// Use UserWidgetInterface to notify it was registered.
|
||||
if (Widget->GetClass()->ImplementsInterface(UGUIS_UserWidgetInterface::StaticClass()))
|
||||
{
|
||||
if (AActor* Actor = Cast<AActor>(Request.ContextObject))
|
||||
{
|
||||
IGUIS_UserWidgetInterface::Execute_SetOwningActor(Widget, Actor);
|
||||
}
|
||||
IGUIS_UserWidgetInterface::Execute_OnActivated(Widget);
|
||||
}
|
||||
}
|
||||
else if (DataClasses.Num() > 0)
|
||||
{
|
||||
if (GetWidgetClassForData.IsBound())
|
||||
{
|
||||
WidgetClass = GetWidgetClassForData.Execute(Data);
|
||||
|
||||
// If the data is irrelevant they can just return no widget class.
|
||||
if (WidgetClass)
|
||||
{
|
||||
if (UUserWidget* Widget = CreateEntryInternal(WidgetClass))
|
||||
{
|
||||
ExtensionMapping.Add(Request.ExtensionHandle, Widget);
|
||||
ConfigureWidgetForData.ExecuteIfBound(Widget, Data);
|
||||
if (Widget->GetClass()->ImplementsInterface(UGUIS_UserWidgetInterface::StaticClass()))
|
||||
{
|
||||
if (AActor* Actor = Cast<AActor>(Request.ContextObject))
|
||||
{
|
||||
IGUIS_UserWidgetInterface::Execute_SetOwningActor(Widget, Actor);
|
||||
}
|
||||
IGUIS_UserWidgetInterface::Execute_OnActivated(Widget);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (UUserWidget* Extension = ExtensionMapping.FindRef(Request.ExtensionHandle))
|
||||
{
|
||||
if (Extension->GetClass()->ImplementsInterface(UGUIS_UserWidgetInterface::StaticClass()))
|
||||
{
|
||||
IGUIS_UserWidgetInterface::Execute_OnDeactivated(Extension);
|
||||
if (AActor* Actor = Cast<AActor>(Request.ContextObject))
|
||||
{
|
||||
IGUIS_UserWidgetInterface::Execute_SetOwningActor(Extension, nullptr);
|
||||
}
|
||||
}
|
||||
RemoveEntryInternal(Extension);
|
||||
ExtensionMapping.Remove(Request.ExtensionHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
void UGUIS_GameUIExtensionPointWidget::ValidateCompiledDefaults(IWidgetCompilerLog& CompileLog) const
|
||||
{
|
||||
Super::ValidateCompiledDefaults(CompileLog);
|
||||
|
||||
// We don't care if the CDO doesn't have a specific tag.
|
||||
if (!HasAnyFlags(RF_ClassDefaultObject))
|
||||
{
|
||||
if (!ExtensionPointTag.IsValid())
|
||||
{
|
||||
TSharedRef<FTokenizedMessage> Message = CompileLog.Error(FText::Format(
|
||||
LOCTEXT("UGUIS_GameUIExtensionPointWidget_NoTag", "{0} has no ExtensionPointTag specified - All extension points must specify a tag so they can be located."),
|
||||
FText::FromString(GetName())));
|
||||
Message->AddToken(FUObjectToken::Create(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
@@ -0,0 +1,414 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#include "UIExtension/GUIS_GameUIExtensionSubsystem.h"
|
||||
|
||||
#include "GUIS_LogChannels.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "UObject/Stack.h"
|
||||
|
||||
#include UE_INLINE_GENERATED_CPP_BY_NAME(GUIS_GameUIExtensionSubsystem)
|
||||
|
||||
class FSubsystemCollectionBase;
|
||||
|
||||
//=========================================================
|
||||
|
||||
void FGUIS_GameUIExtPointHandle::Unregister()
|
||||
{
|
||||
if (UGUIS_ExtensionSubsystem* ExtensionSourcePtr = ExtensionSource.Get())
|
||||
{
|
||||
ExtensionSourcePtr->UnregisterExtensionPoint(*this);
|
||||
ExtensionSource = nullptr;
|
||||
DataPtr.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
//=========================================================
|
||||
|
||||
FGUIS_GameUIExtHandle::FGUIS_GameUIExtHandle()
|
||||
{
|
||||
}
|
||||
|
||||
FGUIS_GameUIExtHandle::FGUIS_GameUIExtHandle(UGUIS_ExtensionSubsystem* InExtensionSource, const TSharedPtr<FGUIS_GameUIExt>& InDataPtr)
|
||||
{
|
||||
ExtensionSource = InExtensionSource;
|
||||
DataPtr = InDataPtr;
|
||||
}
|
||||
|
||||
void FGUIS_GameUIExtHandle::Unregister()
|
||||
{
|
||||
if (UGUIS_ExtensionSubsystem* ExtensionSourcePtr = ExtensionSource.Get())
|
||||
{
|
||||
ExtensionSourcePtr->UnregisterExtension(*this);
|
||||
ExtensionSource = nullptr;
|
||||
DataPtr.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
//=========================================================
|
||||
|
||||
bool FGUIS_GameUIExtPoint::DoesExtensionPassContract(const FGUIS_GameUIExt* Extension) const
|
||||
{
|
||||
if (UObject* DataPtr = Extension->Data)
|
||||
{
|
||||
const bool bMatchesContext =
|
||||
(ContextObject.IsExplicitlyNull() && Extension->ContextObject.IsExplicitlyNull()) ||
|
||||
ContextObject == Extension->ContextObject;
|
||||
|
||||
// Make sure the contexts match.
|
||||
if (bMatchesContext)
|
||||
{
|
||||
// The data can either be the literal class of the data type, or a instance of the class type.
|
||||
const UClass* DataClass = DataPtr->IsA(UClass::StaticClass()) ? Cast<UClass>(DataPtr) : DataPtr->GetClass();
|
||||
for (const UClass* AllowedDataClass : AllowedDataClasses)
|
||||
{
|
||||
if (DataClass->IsChildOf(AllowedDataClass) || DataClass->ImplementsInterface(AllowedDataClass))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
FGUIS_GameUIExtPointHandle::FGUIS_GameUIExtPointHandle()
|
||||
{
|
||||
}
|
||||
|
||||
FGUIS_GameUIExtPointHandle::FGUIS_GameUIExtPointHandle(UGUIS_ExtensionSubsystem* InExtensionSource, const TSharedPtr<FGUIS_GameUIExtPoint>& InDataPtr)
|
||||
{
|
||||
ExtensionSource = InExtensionSource;
|
||||
DataPtr = InDataPtr;
|
||||
}
|
||||
|
||||
//=========================================================
|
||||
|
||||
void UGUIS_ExtensionSubsystem::AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector)
|
||||
{
|
||||
Super::AddReferencedObjects(InThis, Collector);
|
||||
if (UGUIS_ExtensionSubsystem* ExtensionSubsystem = Cast<UGUIS_ExtensionSubsystem>(InThis))
|
||||
{
|
||||
for (auto MapIt = ExtensionSubsystem->ExtensionPointMap.CreateIterator(); MapIt; ++MapIt)
|
||||
{
|
||||
for (const TSharedPtr<FGUIS_GameUIExtPoint>& ValueElement : MapIt.Value())
|
||||
{
|
||||
Collector.AddReferencedObjects(ValueElement->AllowedDataClasses);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto MapIt = ExtensionSubsystem->ExtensionMap.CreateIterator(); MapIt; ++MapIt)
|
||||
{
|
||||
for (const TSharedPtr<FGUIS_GameUIExt>& ValueElement : MapIt.Value())
|
||||
{
|
||||
Collector.AddReferencedObject(ValueElement->Data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_ExtensionSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||||
{
|
||||
Super::Initialize(Collection);
|
||||
}
|
||||
|
||||
void UGUIS_ExtensionSubsystem::Deinitialize()
|
||||
{
|
||||
Super::Deinitialize();
|
||||
}
|
||||
|
||||
FGUIS_GameUIExtPointHandle UGUIS_ExtensionSubsystem::RegisterExtensionPoint(const FGameplayTag& ExtensionPointTag, EGUIS_GameUIExtPointMatchType ExtensionPointTagMatchType,
|
||||
const TArray<UClass*>& AllowedDataClasses, FExtendExtensionPointDelegate ExtensionCallback)
|
||||
{
|
||||
return RegisterExtensionPointForContext(ExtensionPointTag, nullptr, ExtensionPointTagMatchType, AllowedDataClasses, ExtensionCallback);
|
||||
}
|
||||
|
||||
FGUIS_GameUIExtPointHandle UGUIS_ExtensionSubsystem::RegisterExtensionPointForContext(const FGameplayTag& ExtensionPointTag, UObject* ContextObject,
|
||||
EGUIS_GameUIExtPointMatchType ExtensionPointTagMatchType,
|
||||
const TArray<UClass*>& AllowedDataClasses, FExtendExtensionPointDelegate ExtensionCallback)
|
||||
{
|
||||
if (!ExtensionPointTag.IsValid())
|
||||
{
|
||||
UE_LOG(LogGUIS_Extension, Warning, TEXT("Trying to register an invalid extension point."));
|
||||
return FGUIS_GameUIExtPointHandle();
|
||||
}
|
||||
|
||||
if (!ExtensionCallback.IsBound())
|
||||
{
|
||||
UE_LOG(LogGUIS_Extension, Warning, TEXT("Trying to register an invalid extension point."));
|
||||
return FGUIS_GameUIExtPointHandle();
|
||||
}
|
||||
|
||||
if (AllowedDataClasses.Num() == 0)
|
||||
{
|
||||
UE_LOG(LogGUIS_Extension, Warning, TEXT("Trying to register an invalid extension point."));
|
||||
return FGUIS_GameUIExtPointHandle();
|
||||
}
|
||||
|
||||
FExtensionPointList& List = ExtensionPointMap.FindOrAdd(ExtensionPointTag);
|
||||
|
||||
TSharedPtr<FGUIS_GameUIExtPoint>& Entry = List.Add_GetRef(MakeShared<FGUIS_GameUIExtPoint>());
|
||||
Entry->ExtensionPointTag = ExtensionPointTag;
|
||||
Entry->ContextObject = ContextObject;
|
||||
Entry->ExtensionPointTagMatchType = ExtensionPointTagMatchType;
|
||||
Entry->AllowedDataClasses = AllowedDataClasses;
|
||||
Entry->Callback = MoveTemp(ExtensionCallback);
|
||||
|
||||
UE_LOG(LogGUIS_Extension, Verbose, TEXT("Extension Point [%s] Registered"), *ExtensionPointTag.ToString());
|
||||
|
||||
NotifyExtensionPointOfExtensions(Entry);
|
||||
|
||||
return FGUIS_GameUIExtPointHandle(this, Entry);
|
||||
}
|
||||
|
||||
FGUIS_GameUIExtHandle UGUIS_ExtensionSubsystem::RegisterExtensionAsWidget(const FGameplayTag& ExtensionPointTag, TSubclassOf<UUserWidget> WidgetClass, int32 Priority)
|
||||
{
|
||||
return RegisterExtensionAsData(ExtensionPointTag, nullptr, WidgetClass, Priority);
|
||||
}
|
||||
|
||||
FGUIS_GameUIExtHandle UGUIS_ExtensionSubsystem::RegisterExtensionAsWidgetForContext(const FGameplayTag& ExtensionPointTag, UObject* ContextObject, TSubclassOf<UUserWidget> WidgetClass, int32 Priority)
|
||||
{
|
||||
return RegisterExtensionAsData(ExtensionPointTag, ContextObject, WidgetClass, Priority);
|
||||
}
|
||||
|
||||
FGUIS_GameUIExtHandle UGUIS_ExtensionSubsystem::RegisterExtensionAsData(const FGameplayTag& ExtensionPointTag, UObject* ContextObject, UObject* Data, int32 Priority)
|
||||
{
|
||||
if (!ExtensionPointTag.IsValid())
|
||||
{
|
||||
UE_LOG(LogGUIS_Extension, Warning, TEXT("Trying to register an invalid extension."));
|
||||
return FGUIS_GameUIExtHandle();
|
||||
}
|
||||
|
||||
if (!Data)
|
||||
{
|
||||
UE_LOG(LogGUIS_Extension, Warning, TEXT("Trying to register an invalid extension."));
|
||||
return FGUIS_GameUIExtHandle();
|
||||
}
|
||||
|
||||
FExtensionList& List = ExtensionMap.FindOrAdd(ExtensionPointTag);
|
||||
|
||||
TSharedPtr<FGUIS_GameUIExt>& Entry = List.Add_GetRef(MakeShared<FGUIS_GameUIExt>());
|
||||
Entry->ExtensionPointTag = ExtensionPointTag;
|
||||
Entry->ContextObject = ContextObject;
|
||||
Entry->Data = Data;
|
||||
Entry->Priority = Priority;
|
||||
|
||||
if (ContextObject)
|
||||
{
|
||||
UE_LOG(LogGUIS_Extension, Verbose, TEXT("Extension [%s] @ [%s] Registered"), *GetNameSafe(Data), *ExtensionPointTag.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogGUIS_Extension, Verbose, TEXT("Extension [%s] for [%s] @ [%s] Registered"), *GetNameSafe(Data), *GetNameSafe(ContextObject), *ExtensionPointTag.ToString());
|
||||
}
|
||||
|
||||
NotifyExtensionPointsOfExtension(EGUIS_GameUIExtAction::Added, Entry);
|
||||
|
||||
return FGUIS_GameUIExtHandle(this, Entry);
|
||||
}
|
||||
|
||||
void UGUIS_ExtensionSubsystem::NotifyExtensionPointOfExtensions(TSharedPtr<FGUIS_GameUIExtPoint>& ExtensionPoint)
|
||||
{
|
||||
for (FGameplayTag Tag = ExtensionPoint->ExtensionPointTag; Tag.IsValid(); Tag = Tag.RequestDirectParent())
|
||||
{
|
||||
if (const FExtensionList* ListPtr = ExtensionMap.Find(Tag))
|
||||
{
|
||||
// Copy in case there are removals while handling callbacks
|
||||
FExtensionList ExtensionArray(*ListPtr);
|
||||
|
||||
for (const TSharedPtr<FGUIS_GameUIExt>& Extension : ExtensionArray)
|
||||
{
|
||||
if (ExtensionPoint->DoesExtensionPassContract(Extension.Get()))
|
||||
{
|
||||
FGUIS_GameUIExtRequest Request = CreateExtensionRequest(Extension);
|
||||
ExtensionPoint->Callback.ExecuteIfBound(EGUIS_GameUIExtAction::Added, Request);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ExtensionPoint->ExtensionPointTagMatchType == EGUIS_GameUIExtPointMatchType::ExactMatch)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_ExtensionSubsystem::NotifyExtensionPointsOfExtension(EGUIS_GameUIExtAction Action, TSharedPtr<FGUIS_GameUIExt>& Extension)
|
||||
{
|
||||
bool bOnInitialTag = true;
|
||||
for (FGameplayTag Tag = Extension->ExtensionPointTag; Tag.IsValid(); Tag = Tag.RequestDirectParent())
|
||||
{
|
||||
if (const FExtensionPointList* ListPtr = ExtensionPointMap.Find(Tag))
|
||||
{
|
||||
// Copy in case there are removals while handling callbacks
|
||||
FExtensionPointList ExtensionPointArray(*ListPtr);
|
||||
|
||||
for (const TSharedPtr<FGUIS_GameUIExtPoint>& ExtensionPoint : ExtensionPointArray)
|
||||
{
|
||||
if (bOnInitialTag || (ExtensionPoint->ExtensionPointTagMatchType == EGUIS_GameUIExtPointMatchType::PartialMatch))
|
||||
{
|
||||
if (ExtensionPoint->DoesExtensionPassContract(Extension.Get()))
|
||||
{
|
||||
FGUIS_GameUIExtRequest Request = CreateExtensionRequest(Extension);
|
||||
ExtensionPoint->Callback.ExecuteIfBound(Action, Request);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bOnInitialTag = false;
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_ExtensionSubsystem::UnregisterExtension(const FGUIS_GameUIExtHandle& ExtensionHandle)
|
||||
{
|
||||
if (ExtensionHandle.IsValid())
|
||||
{
|
||||
checkf(ExtensionHandle.ExtensionSource == this, TEXT("Trying to unregister an extension that's not from this extension subsystem."));
|
||||
|
||||
TSharedPtr<FGUIS_GameUIExt> Extension = ExtensionHandle.DataPtr;
|
||||
if (FExtensionList* ListPtr = ExtensionMap.Find(Extension->ExtensionPointTag))
|
||||
{
|
||||
if (Extension->ContextObject.IsExplicitlyNull())
|
||||
{
|
||||
UE_LOG(LogGUIS_Extension, Verbose, TEXT("Extension [%s] @ [%s] Unregistered"), *GetNameSafe(Extension->Data), *Extension->ExtensionPointTag.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogGUIS_Extension, Verbose, TEXT("Extension [%s] for [%s] @ [%s] Unregistered"), *GetNameSafe(Extension->Data), *GetNameSafe(Extension->ContextObject.Get()),
|
||||
*Extension->ExtensionPointTag.ToString());
|
||||
}
|
||||
|
||||
NotifyExtensionPointsOfExtension(EGUIS_GameUIExtAction::Removed, Extension);
|
||||
|
||||
ListPtr->RemoveSwap(Extension);
|
||||
|
||||
if (ListPtr->Num() == 0)
|
||||
{
|
||||
ExtensionMap.Remove(Extension->ExtensionPointTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogGUIS_Extension, Warning, TEXT("Trying to unregister an invalid Handle."));
|
||||
}
|
||||
}
|
||||
|
||||
void UGUIS_ExtensionSubsystem::UnregisterExtensionPoint(const FGUIS_GameUIExtPointHandle& ExtensionPointHandle)
|
||||
{
|
||||
if (ExtensionPointHandle.IsValid())
|
||||
{
|
||||
check(ExtensionPointHandle.ExtensionSource == this);
|
||||
|
||||
const TSharedPtr<FGUIS_GameUIExtPoint> ExtensionPoint = ExtensionPointHandle.DataPtr;
|
||||
if (FExtensionPointList* ListPtr = ExtensionPointMap.Find(ExtensionPoint->ExtensionPointTag))
|
||||
{
|
||||
UE_LOG(LogGUIS_Extension, Verbose, TEXT("Extension Point [%s] Unregistered"), *ExtensionPoint->ExtensionPointTag.ToString());
|
||||
|
||||
ListPtr->RemoveSwap(ExtensionPoint);
|
||||
if (ListPtr->Num() == 0)
|
||||
{
|
||||
ExtensionPointMap.Remove(ExtensionPoint->ExtensionPointTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogGUIS_Extension, Warning, TEXT("Trying to unregister an invalid Handle."));
|
||||
}
|
||||
}
|
||||
|
||||
FGUIS_GameUIExtRequest UGUIS_ExtensionSubsystem::CreateExtensionRequest(const TSharedPtr<FGUIS_GameUIExt>& Extension)
|
||||
{
|
||||
FGUIS_GameUIExtRequest Request;
|
||||
Request.ExtensionHandle = FGUIS_GameUIExtHandle(this, Extension);
|
||||
Request.ExtensionPointTag = Extension->ExtensionPointTag;
|
||||
Request.Priority = Extension->Priority;
|
||||
Request.Data = Extension->Data;
|
||||
Request.ContextObject = Extension->ContextObject.Get();
|
||||
|
||||
return Request;
|
||||
}
|
||||
|
||||
FGUIS_GameUIExtPointHandle UGUIS_ExtensionSubsystem::K2_RegisterExtensionPoint(FGameplayTag ExtensionPointTag, EGUIS_GameUIExtPointMatchType ExtensionPointTagMatchType,
|
||||
const TArray<TSoftClassPtr<UClass>>& AllowedDataClasses,
|
||||
FExtendExtensionPointDynamicDelegate ExtensionCallback)
|
||||
{
|
||||
TArray<UClass*> LoadedClasses;
|
||||
|
||||
for (const TSoftClassPtr<UClass>& DataClass : AllowedDataClasses)
|
||||
{
|
||||
if (!DataClass.IsNull())
|
||||
{
|
||||
LoadedClasses.Add(DataClass.LoadSynchronous());
|
||||
}
|
||||
}
|
||||
return RegisterExtensionPoint(ExtensionPointTag, ExtensionPointTagMatchType, LoadedClasses, FExtendExtensionPointDelegate::CreateWeakLambda(
|
||||
ExtensionCallback.GetUObject(), [this, ExtensionCallback](EGUIS_GameUIExtAction Action, const FGUIS_GameUIExtRequest& Request)
|
||||
{
|
||||
ExtensionCallback.ExecuteIfBound(Action, Request);
|
||||
}));
|
||||
}
|
||||
|
||||
FGUIS_GameUIExtHandle UGUIS_ExtensionSubsystem::K2_RegisterExtensionAsWidget(FGameplayTag ExtensionPointTag, TSoftClassPtr<UUserWidget> WidgetClass, int32 Priority)
|
||||
{
|
||||
if (!WidgetClass.IsNull())
|
||||
{
|
||||
return RegisterExtensionAsWidget(ExtensionPointTag, WidgetClass.LoadSynchronous(), Priority);
|
||||
}
|
||||
return FGUIS_GameUIExtHandle();
|
||||
}
|
||||
|
||||
FGUIS_GameUIExtHandle UGUIS_ExtensionSubsystem::K2_RegisterExtensionAsWidgetForContext(FGameplayTag ExtensionPointTag, TSoftClassPtr<UUserWidget> WidgetClass, UObject* ContextObject, int32 Priority)
|
||||
{
|
||||
if (ContextObject && !WidgetClass.IsNull())
|
||||
{
|
||||
return RegisterExtensionAsWidgetForContext(ExtensionPointTag, ContextObject, WidgetClass.LoadSynchronous(), Priority);
|
||||
}
|
||||
FFrame::KismetExecutionMessage(TEXT("A null ContextObject was passed to Register Extension (Widget For Context)"), ELogVerbosity::Error);
|
||||
return FGUIS_GameUIExtHandle();
|
||||
}
|
||||
|
||||
FGUIS_GameUIExtHandle UGUIS_ExtensionSubsystem::K2_RegisterExtensionAsData(FGameplayTag ExtensionPointTag, UObject* Data, int32 Priority)
|
||||
{
|
||||
return RegisterExtensionAsData(ExtensionPointTag, nullptr, Data, Priority);
|
||||
}
|
||||
|
||||
FGUIS_GameUIExtHandle UGUIS_ExtensionSubsystem::K2_RegisterExtensionAsDataForContext(FGameplayTag ExtensionPointTag, UObject* ContextObject, UObject* Data, int32 Priority)
|
||||
{
|
||||
if (ContextObject)
|
||||
{
|
||||
return RegisterExtensionAsData(ExtensionPointTag, ContextObject, Data, Priority);
|
||||
}
|
||||
FFrame::KismetExecutionMessage(TEXT("A null ContextObject was passed to Register Extension (Data For Context)"), ELogVerbosity::Error);
|
||||
return FGUIS_GameUIExtHandle();
|
||||
}
|
||||
|
||||
//=========================================================
|
||||
|
||||
UGUIS_ExtensionFunctionLibrary::UGUIS_ExtensionFunctionLibrary()
|
||||
{
|
||||
}
|
||||
|
||||
void UGUIS_ExtensionFunctionLibrary::UnregisterExtension(FGUIS_GameUIExtHandle& Handle)
|
||||
{
|
||||
Handle.Unregister();
|
||||
}
|
||||
|
||||
bool UGUIS_ExtensionFunctionLibrary::IsValidExtension(FGUIS_GameUIExtHandle& Handle)
|
||||
{
|
||||
return Handle.IsValid();
|
||||
}
|
||||
|
||||
//=========================================================
|
||||
|
||||
void UGUIS_ExtensionFunctionLibrary::UnregisterExtensionPoint(FGUIS_GameUIExtPointHandle& Handle)
|
||||
{
|
||||
Handle.Unregister();
|
||||
}
|
||||
|
||||
bool UGUIS_ExtensionFunctionLibrary::IsValidExtensionPoint(FGUIS_GameUIExtPointHandle& Handle)
|
||||
{
|
||||
return Handle.IsValid();
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "Engine/DeveloperSettings.h"
|
||||
#include "GUIS_GenericUISystemSettings.generated.h"
|
||||
|
||||
class UGUIS_GameModalWidget;
|
||||
class UGUIS_GameUIPolicy;
|
||||
|
||||
/**
|
||||
* Developer settings for the Generic UI System.
|
||||
* 通用UI系统的开发者设置。
|
||||
*/
|
||||
UCLASS(Config = Game, defaultconfig, meta = (DisplayName = "Generic UI System Settings"))
|
||||
class GENERICUISYSTEM_API UGUIS_GenericUISystemSettings : public UDeveloperSettings
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Retrieves the Generic UI System settings.
|
||||
* 获取通用UI系统设置。
|
||||
* @return The UI system settings. UI系统设置。
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category="GUIS|Settings", meta = (DisplayName = "Get Generic UI System Settings"))
|
||||
static const UGUIS_GenericUISystemSettings* Get();
|
||||
|
||||
/**
|
||||
* Default UI policy class for the game layout.
|
||||
* 游戏布局的默认UI策略类。
|
||||
*/
|
||||
UPROPERTY(Config, EditAnywhere, BlueprintReadOnly, Category="GUIS|Settings")
|
||||
TSoftClassPtr<UGUIS_GameUIPolicy> GameUIPolicyClass;
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/Object.h"
|
||||
|
||||
GENERICUISYSTEM_API DECLARE_LOG_CATEGORY_EXTERN(LogGUIS, Log, All);
|
||||
GENERICUISYSTEM_API DECLARE_LOG_CATEGORY_EXTERN(LogGUIS_Extension, Log, All);
|
||||
26
Plugins/GGS/Source/GenericUISystem/Public/GenericUISystem.h
Normal file
26
Plugins/GGS/Source/GenericUISystem/Public/GenericUISystem.h
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
/**
|
||||
* Module interface for the Generic UI System.
|
||||
* 通用UI系统的模块接口。
|
||||
*/
|
||||
class FGenericUISystemModule : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Called when the module is loaded.
|
||||
* 模块加载时调用。
|
||||
*/
|
||||
virtual void StartupModule() override;
|
||||
|
||||
/**
|
||||
* Called when the module is unloaded.
|
||||
* 模块卸载时调用。
|
||||
*/
|
||||
virtual void ShutdownModule() override;
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/CancellableAsyncAction.h"
|
||||
#include "UObject/SoftObjectPtr.h"
|
||||
|
||||
#include "GUIS_AsyncAction_CreateWidget.generated.h"
|
||||
|
||||
class APlayerController;
|
||||
class UGameInstance;
|
||||
class UUserWidget;
|
||||
class UWorld;
|
||||
struct FFrame;
|
||||
struct FStreamableHandle;
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FGUIS_CreateWidgetSignature, UUserWidget *, UserWidget);
|
||||
|
||||
/**
|
||||
* Load the widget class asynchronously, the instance the widget after the loading completes, and return it on OnComplete.
|
||||
*/
|
||||
UCLASS(BlueprintType)
|
||||
class GENERICUISYSTEM_API UGUIS_AsyncAction_CreateWidget : public UCancellableAsyncAction
|
||||
{
|
||||
GENERATED_UCLASS_BODY()
|
||||
|
||||
public:
|
||||
virtual void Cancel() override;
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="GUIS", meta = (WorldContext = "WorldContextObject", BlueprintInternalUseOnly = "true"))
|
||||
static UGUIS_AsyncAction_CreateWidget *CreateWidgetAsync(UObject *WorldContextObject, TSoftClassPtr<UUserWidget> UserWidgetSoftClass, APlayerController *OwningPlayer,
|
||||
bool bSuspendInputUntilComplete = true);
|
||||
|
||||
virtual void Activate() override;
|
||||
|
||||
public:
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FGUIS_CreateWidgetSignature OnComplete;
|
||||
|
||||
private:
|
||||
void OnWidgetLoaded();
|
||||
|
||||
FName SuspendInputToken;
|
||||
TWeakObjectPtr<APlayerController> OwningPlayer;
|
||||
TWeakObjectPtr<UWorld> World;
|
||||
TWeakObjectPtr<UGameInstance> GameInstance;
|
||||
bool bSuspendInputUntilComplete;
|
||||
TSoftClassPtr<UUserWidget> UserWidgetSoftClass;
|
||||
TSharedPtr<FStreamableHandle> StreamingHandle;
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/CancellableAsyncAction.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "UObject/SoftObjectPtr.h"
|
||||
|
||||
#include "GUIS_AsyncAction_PushContentToUILayer.generated.h"
|
||||
|
||||
class UGUIS_GameUILayout;
|
||||
class APlayerController;
|
||||
class UCommonActivatableWidget;
|
||||
class UObject;
|
||||
struct FFrame;
|
||||
struct FStreamableHandle;
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FPushContentToLayerForPlayerAsyncDelegate, UCommonActivatableWidget *, UserWidget);
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
UCLASS(BlueprintType)
|
||||
class GENERICUISYSTEM_API UGUIS_AsyncAction_PushContentToUILayer : public UCancellableAsyncAction
|
||||
{
|
||||
GENERATED_UCLASS_BODY()
|
||||
virtual void Cancel() override;
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="GUIS", meta = (WorldContext = "WorldContextObject", BlueprintInternalUseOnly = "true"))
|
||||
static UGUIS_AsyncAction_PushContentToUILayer* PushContentToUILayer(UGUIS_GameUILayout* UILayout,
|
||||
UPARAM(meta = (AllowAbstract = false))
|
||||
TSoftClassPtr<UCommonActivatableWidget> WidgetClass,
|
||||
UPARAM(meta = (Categories = "UI.Layer,GUIS.Layer"))
|
||||
FGameplayTag LayerName, bool bSuspendInputUntilComplete = true);
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="GUIS", meta = (WorldContext = "WorldContextObject", BlueprintInternalUseOnly = "true"))
|
||||
static UGUIS_AsyncAction_PushContentToUILayer* PushContentToUILayerForPlayer(APlayerController* PlayerController,
|
||||
UPARAM(meta = (AllowAbstract = false))
|
||||
TSoftClassPtr<UCommonActivatableWidget> WidgetClass,
|
||||
UPARAM(meta = (Categories = "UI.Layer,GUIS.Layer"))
|
||||
FGameplayTag LayerName, bool bSuspendInputUntilComplete = true);
|
||||
|
||||
virtual void Activate() override;
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FPushContentToLayerForPlayerAsyncDelegate BeforePush;
|
||||
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FPushContentToLayerForPlayerAsyncDelegate AfterPush;
|
||||
|
||||
private:
|
||||
FGameplayTag LayerName;
|
||||
bool bSuspendInputUntilComplete = false;
|
||||
TWeakObjectPtr<APlayerController> OwningPlayerPtr;
|
||||
TWeakObjectPtr<UGUIS_GameUILayout> RootLayout;
|
||||
TSoftClassPtr<UCommonActivatableWidget> WidgetClass;
|
||||
|
||||
TSharedPtr<FStreamableHandle> StreamingHandle;
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "Engine/CancellableAsyncAction.h"
|
||||
#include "UI/Modal/GUIS_GameModal.h"
|
||||
#include "UObject/ObjectPtr.h"
|
||||
#include "GUIS_AsyncAction_ShowModel.generated.h"
|
||||
|
||||
enum class EGGF_DialogMessagingResult : uint8;
|
||||
class FText;
|
||||
class ULocalPlayer;
|
||||
struct FFrame;
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FGUIS_ModalResultSignature, FGameplayTag, Result);
|
||||
|
||||
/**
|
||||
* Allows easily triggering an async confirmation dialog in blueprints that you can then wait on the result.
|
||||
*/
|
||||
UCLASS()
|
||||
class UGUIS_AsyncAction_ShowModel : public UCancellableAsyncAction
|
||||
{
|
||||
GENERATED_UCLASS_BODY()
|
||||
|
||||
public:
|
||||
// UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="GUIS", meta = (BlueprintInternalUseOnly = "true", WorldContext = "InWorldContextObject"))
|
||||
// static UGUIS_AsyncAction_ShowModel* ShowModal(UObject* InWorldContextObject, FGameplayTag ModalTag, UGUIS_ModalDefinition* ModalDefinition);
|
||||
|
||||
/**
|
||||
* 给定一个Modal定义,然后显示该Modal。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="GUIS", meta = (BlueprintInternalUseOnly = "true", WorldContext = "InWorldContextObject"))
|
||||
static UGUIS_AsyncAction_ShowModel* ShowModal(UObject* InWorldContextObject, TSoftClassPtr<UGUIS_ModalDefinition> ModalDefinition);
|
||||
|
||||
virtual void Activate() override;
|
||||
|
||||
public:
|
||||
UPROPERTY(BlueprintAssignable)
|
||||
FGUIS_ModalResultSignature OnModalAction;
|
||||
|
||||
private:
|
||||
void HandleModalAction(FGameplayTag ModalActionTag);
|
||||
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UObject> WorldContextObject;
|
||||
|
||||
// UPROPERTY(Transient)
|
||||
// TObjectPtr<ULocalPlayer> TargetLocalPlayer;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<APlayerController> TargetPlayerController;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TSubclassOf<UGUIS_GameModalWidget> ModalWidgetClass;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<const UGUIS_ModalDefinition> ModalDefinition;
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Engine/DataTable.h"
|
||||
#include "GUIS_UIAction.generated.h"
|
||||
|
||||
class UGUIS_ModalDefinition;
|
||||
class UGUIS_UIAction;
|
||||
|
||||
/**
|
||||
* Base ui action for single data.
|
||||
*/
|
||||
UCLASS(Blueprintable, EditInlineNew, CollapseCategories, DefaultToInstanced, Abstract, Const)
|
||||
class GENERICUISYSTEM_API UGUIS_UIAction : public UObject
|
||||
{
|
||||
GENERATED_UCLASS_BODY()
|
||||
virtual UWorld* GetWorld() const override;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category="GUIS|UIAction")
|
||||
bool IsCompatible(const UObject* Data) const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category="GUIS|UIAction")
|
||||
bool CanInvoke(const UObject* Data, APlayerController* PlayerController) const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category="GUIS|UIAction")
|
||||
void InvokeAction(const UObject* Data, APlayerController* PlayerController) const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GUIS|UIAction")
|
||||
FText GetActionName() const;
|
||||
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GUIS|UIAction")
|
||||
FName GetActionID() const;
|
||||
|
||||
const FDataTableRowHandle& GetInputActionData() const { return InputActionData; }
|
||||
|
||||
bool GetShouldDisplayInActionBar() const { return bShouldDisplayInActionBar; }
|
||||
|
||||
bool GetRequiresConfirmation() const { return bRequiresConfirmation; }
|
||||
|
||||
TSoftClassPtr<UGUIS_ModalDefinition> GetConfirmationModalClass() const { return ConfirmationModalClass; };
|
||||
|
||||
protected:
|
||||
UFUNCTION(BlueprintNativeEvent, Category = "UIAction", meta = (DisplayName = "Is Compatible"))
|
||||
bool IsCompatibleInternal(const UObject* Data) const;
|
||||
|
||||
UFUNCTION(BlueprintNativeEvent, Category = "UIAction", meta = (DisplayName = "Can Invoke"))
|
||||
bool CanInvokeInternal(const UObject* Data, APlayerController* PlayerController) const;
|
||||
|
||||
UFUNCTION(BlueprintNativeEvent, Category = "UIAction", meta = (DisplayName = "Invoke Action"))
|
||||
void InvokeActionInternal(const UObject* Data, APlayerController* PlayerController) const;
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UIAction")
|
||||
FText DisplayName;
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UIAction")
|
||||
FName ActionID;
|
||||
|
||||
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UIAction", meta = (RowType = "/Script/CommonUI.CommonInputActionDataBase"))
|
||||
FDataTableRowHandle InputActionData;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="UIAction")
|
||||
bool bShouldDisplayInActionBar{true};
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="UIAction")
|
||||
bool bRequiresConfirmation{true};
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="UIAction", meta=(EditCondition="bRequiresConfirmation"))
|
||||
TSoftClassPtr<UGUIS_ModalDefinition> ConfirmationModalClass{nullptr};
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "Engine/DataAsset.h"
|
||||
#include "GUIS_UIActionFactory.generated.h"
|
||||
|
||||
class UGUIS_UIAction;
|
||||
/**
|
||||
* 提供一种通用的方式为UI对象选择合适的可用操作。
|
||||
*/
|
||||
UCLASS(BlueprintType, Const)
|
||||
class GENERICUISYSTEM_API UGUIS_UIActionFactory : public UDataAsset
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UFUNCTION(BlueprintCallable, Category="GUIS|UIAction")
|
||||
TArray<UGUIS_UIAction*> FindAvailableUIActionsForData(const UObject* Data) const;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* A list of potential actions for incoming data.
|
||||
* 针对传入数据的潜在可用ui操作。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GUIS", Instanced, meta=(TitleProperty="ActionId"))
|
||||
TArray<TObjectPtr<UGUIS_UIAction>> PotentialActions;
|
||||
|
||||
#if WITH_EDITOR
|
||||
virtual EDataValidationResult IsDataValid(FDataValidationContext& Context) const override;
|
||||
#endif
|
||||
};
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "CommonUserWidget.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "GUIS_UIAction.h"
|
||||
#include "GUIS_UIActionWidget.generated.h"
|
||||
|
||||
class UGUIS_AsyncAction_ShowModel;
|
||||
class UGUIS_UIActionFactory;
|
||||
|
||||
|
||||
/**
|
||||
* A widget which can associate data with register ui action dynamically based on passed-in data.
|
||||
* @attention There's no visual for this widget.
|
||||
* 此Widget可以关联一个数据,并为其自动注册可用的输入事件。
|
||||
*/
|
||||
UCLASS(ClassGroup = GUIS, meta=(Category="Generic UI System"), AutoExpandCategories=("GUIS"))
|
||||
class GENERICUISYSTEM_API UGUIS_UIActionWidget : public UCommonUserWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UFUNCTION(BlueprintCallable, Category="GUIS|UIAction")
|
||||
void SetAssociatedData(UObject* Data);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category="GUIS|UIAction")
|
||||
void RegisterActions();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category="GUIS|UIAction")
|
||||
virtual void RegisterActionsWithFactory(TSoftObjectPtr<UGUIS_UIActionFactory> InActionFactory);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category="GUIS|UIAction")
|
||||
virtual void UnregisterActions();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category="GUIS|UIAction")
|
||||
virtual void CancelAction();
|
||||
|
||||
|
||||
//~ Begin UWidget
|
||||
#if WITH_EDITOR
|
||||
virtual const FText GetPaletteCategory() override;
|
||||
#endif
|
||||
//~ End UWidget interface
|
||||
|
||||
protected:
|
||||
UFUNCTION()
|
||||
virtual void HandleUIAction(const UGUIS_UIAction* Action);
|
||||
|
||||
UFUNCTION()
|
||||
virtual void HandleModalAction(FGameplayTag ActionTag);
|
||||
|
||||
UPROPERTY()
|
||||
TWeakObjectPtr<UObject> AssociatedData;
|
||||
|
||||
/**
|
||||
* A factory to get available ui actions for associated data.
|
||||
* 该数据资产用于针对关联数据返回所有可用的ui操作。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="UIAction")
|
||||
TObjectPtr<UGUIS_UIActionFactory> ActionFactory;
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FGUIS_HandleUIActionSignature, const UGUIS_UIAction*, ActionReference);
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category="UIAction")
|
||||
FGUIS_HandleUIActionSignature OnHandleUIAction;
|
||||
|
||||
private:
|
||||
TArray<FUIActionBindingHandle> ActionBindings;
|
||||
|
||||
UPROPERTY()
|
||||
TObjectPtr<const UGUIS_UIAction> CurrentAction{nullptr};
|
||||
|
||||
UPROPERTY()
|
||||
TObjectPtr<UGUIS_AsyncAction_ShowModel> ModalTask{nullptr};
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Engine/DataAsset.h"
|
||||
#include "GUIS_DetailSectionsBuilder.generated.h"
|
||||
|
||||
class UGUIS_ListEntryDetailSection;
|
||||
class UGUIS_ListEntry;
|
||||
|
||||
/**
|
||||
* Base class for customizing how detail sections are gathered for an object.
|
||||
* 自定义如何为对象收集细节部分的基类。
|
||||
*/
|
||||
UCLASS(Abstract, Blueprintable, meta = (Category = "Generic UI"))
|
||||
class GENERICUISYSTEM_API UGUIS_DetailSectionsBuilder : public UDataAsset
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Gathers detail section classes for the specified data.
|
||||
* 为指定数据收集细节部分类。
|
||||
* @param Data The data object. 数据对象。
|
||||
* @return Array of detail section classes. 细节部分类数组。
|
||||
*/
|
||||
UFUNCTION(Blueprintable, BlueprintNativeEvent)
|
||||
TArray<TSoftClassPtr<UGUIS_ListEntryDetailSection>> GatherDetailSections(const UObject* Data);
|
||||
virtual TArray<TSoftClassPtr<UGUIS_ListEntryDetailSection>> GatherDetailSections_Implementation(const UObject* Data);
|
||||
};
|
||||
|
||||
/**
|
||||
* Structure for mapping object classes to detail section classes.
|
||||
* 将对象类映射到细节部分类的结构。
|
||||
*/
|
||||
USTRUCT()
|
||||
struct GENERICUISYSTEM_API FGUIS_EntryDetailsClassSections
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* Array of detail section classes for an object class.
|
||||
* 对象类的细节部分类数组。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category="GUIS")
|
||||
TArray<TSoftClassPtr<UGUIS_ListEntryDetailSection>> Sections;
|
||||
};
|
||||
|
||||
/**
|
||||
* Concrete class for mapping object classes to detail sections.
|
||||
* 将对象类映射到细节部分的实体类。
|
||||
*/
|
||||
UCLASS(NotBlueprintable)
|
||||
class GENERICUISYSTEM_API UGUIS_DetailSectionBuilder_Class : public UGUIS_DetailSectionsBuilder
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Gathers detail section classes for the specified data.
|
||||
* 为指定数据收集细节部分类。
|
||||
* @param Data The data object. 数据对象。
|
||||
* @return Array of detail section classes. 细节部分类数组。
|
||||
*/
|
||||
virtual TArray<TSoftClassPtr<UGUIS_ListEntryDetailSection>> GatherDetailSections_Implementation(const UObject* Data) override;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Mapping of object classes to their detail section configurations.
|
||||
* 对象类到其细节部分配置的映射。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, Category="GUIS", meta = (AllowAbstract))
|
||||
TMap<TSubclassOf<UObject>, FGUIS_EntryDetailsClassSections> SectionsForClasses;
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Blueprint/IUserObjectListEntry.h"
|
||||
#include "UI/Foundation/GUIS_ButtonBase.h"
|
||||
#include "GUIS_ListEntry.generated.h"
|
||||
|
||||
class UGUIS_UIAction;
|
||||
|
||||
/**
|
||||
* List entry widget representing an item in a ListView or TileView.
|
||||
* 表示ListView或TileView中项的列表入口小部件。
|
||||
*/
|
||||
UCLASS(Abstract, meta = (Category = "Generic UI", DisableNativeTick))
|
||||
class GENERICUISYSTEM_API UGUIS_ListEntry : public UGUIS_ButtonBase, public IUserObjectListEntry
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "CommonUserWidget.h"
|
||||
#include "GUIS_ListEntryDetailSection.generated.h"
|
||||
|
||||
/**
|
||||
* Detail section widget for composing a detail view.
|
||||
* 组成细节视图的细节部分小部件。
|
||||
*/
|
||||
UCLASS(Abstract, meta = (Category = "Generic UI"))
|
||||
class GENERICUISYSTEM_API UGUIS_ListEntryDetailSection : public UCommonUserWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Sets the object represented by this detail section.
|
||||
* 设置此细节部分表示的对象。
|
||||
* @param ListItemObject The object to display. 要显示的对象。
|
||||
*/
|
||||
void SetListItemObject(UObject* ListItemObject);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Called when the list item object is set.
|
||||
* 列表项对象设置时调用。
|
||||
* @param ListItemObject The object being set. 设置的对象。
|
||||
*/
|
||||
virtual void NativeOnListItemObjectSet(UObject* ListItemObject);
|
||||
|
||||
/**
|
||||
* Blueprint event for when the list item object is set.
|
||||
* 列表项对象设置时的蓝图事件。
|
||||
* @param ListItemObject The object being set. 设置的对象。
|
||||
*/
|
||||
UFUNCTION(BlueprintImplementableEvent)
|
||||
void OnListItemObjectSet(UObject* ListItemObject);
|
||||
};
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CommonUserWidget.h"
|
||||
#include "Blueprint/UserWidgetPool.h"
|
||||
#include "GUIS_ListEntryDetailView.generated.h"
|
||||
|
||||
class UGUIS_DetailSectionsBuilder;
|
||||
class UGUIS_ListEntryDetailSection;
|
||||
class UVerticalBox;
|
||||
class UGUIS_WidgetFactory;
|
||||
struct FStreamableHandle;
|
||||
|
||||
/**
|
||||
* Detail view containing multiple sections to represent an item (any UObject).
|
||||
* 包含多个细节部分的视图,用于展示一个项(任意UObject类型)。
|
||||
*/
|
||||
UCLASS(Abstract, meta=(Category = "Generic UI"))
|
||||
class GENERICUISYSTEM_API UGUIS_ListEntryDetailView : public UCommonUserWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor for the detail view widget.
|
||||
* 细节视图小部件构造函数。
|
||||
*/
|
||||
UGUIS_ListEntryDetailView(const FObjectInitializer& ObjectInitializer);
|
||||
|
||||
/**
|
||||
* Sets the object represented by this detail view as data.
|
||||
* 设置此细节视图表示的对象作为数据。
|
||||
* @param InListItemObject The object to display. 要显示的对象。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GUIS")
|
||||
void SetListItemObject(UObject* InListItemObject);
|
||||
|
||||
/**
|
||||
* Sets the associated detail sections builder.
|
||||
* 设置关联的细节部分构建器。
|
||||
* @param NewBuilder The detail sections builder. 细节部分构建器。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GUIS")
|
||||
void SetSectionsBuilder(UGUIS_DetailSectionsBuilder* NewBuilder);
|
||||
|
||||
/**
|
||||
* Releases Slate resources.
|
||||
* 释放Slate资源。
|
||||
* @param bReleaseChildren Whether to release child resources. 是否释放子资源。
|
||||
*/
|
||||
virtual void ReleaseSlateResources(bool bReleaseChildren) override;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Called when the widget is constructed.
|
||||
* 小部件构造时调用。
|
||||
*/
|
||||
virtual void NativeConstruct() override;
|
||||
|
||||
/**
|
||||
* Called when the widget is initialized.
|
||||
* 小部件初始化时调用。
|
||||
*/
|
||||
virtual void NativeOnInitialized() override;
|
||||
|
||||
/**
|
||||
* Creates a detail section extension for the specified data.
|
||||
* 为指定数据创建细节部分扩展。
|
||||
* @param InData The data object. 数据对象。
|
||||
* @param SectionClass The section widget class. 部分小部件类。
|
||||
*/
|
||||
void CreateDetailsExtension(UObject* InData, TSubclassOf<UGUIS_ListEntryDetailSection> SectionClass);
|
||||
|
||||
/**
|
||||
* Detail sections builder for displaying data based on widget specifications.
|
||||
* 根据小部件规格显示数据的细节部分构建器。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category="GUIS", meta = (AllowAbstract = false))
|
||||
TObjectPtr<UGUIS_DetailSectionsBuilder> SectionsBuilder;
|
||||
|
||||
/**
|
||||
* Pool for managing extension widgets.
|
||||
* 管理扩展小部件的池。
|
||||
*/
|
||||
UPROPERTY(Transient)
|
||||
FUserWidgetPool ExtensionWidgetPool;
|
||||
|
||||
/**
|
||||
* Current object represented by the detail view.
|
||||
* 细节视图当前表示的对象。
|
||||
*/
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UObject> CurrentListItemObject;
|
||||
|
||||
/**
|
||||
* Handle for streaming assets.
|
||||
* 流式加载资产的句柄。
|
||||
*/
|
||||
TSharedPtr<FStreamableHandle> StreamingHandle;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Vertical box for detail sections.
|
||||
* 细节部分的垂直框。
|
||||
*/
|
||||
UPROPERTY(BlueprintReadOnly, Category="GUIS", meta = (BindWidget, BlueprintProtected = true, AllowPrivateAccess = true))
|
||||
TObjectPtr<UVerticalBox> Box_DetailSections;
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "CommonListView.h"
|
||||
#include "GUIS_ListView.generated.h"
|
||||
|
||||
class UGUIS_WidgetFactory;
|
||||
|
||||
/**
|
||||
* Extended ListView allowing dynamic selection of entry widget class via data asset.
|
||||
* 通过数据资产动态选择入口小部件类的扩展ListView。
|
||||
*/
|
||||
UCLASS(Blueprintable, meta = (Category = "Generic UI"))
|
||||
class GENERICUISYSTEM_API UGUIS_ListView : public UCommonListView
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor for the ListView widget.
|
||||
* ListView小部件构造函数。
|
||||
*/
|
||||
UGUIS_ListView(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
|
||||
|
||||
#if WITH_EDITOR
|
||||
/**
|
||||
* Validates compiled defaults in the editor.
|
||||
* 在编辑器中验证编译默认值。
|
||||
* @param InCompileLog The widget compiler log. 小部件编译日志。
|
||||
*/
|
||||
virtual void ValidateCompiledDefaults(IWidgetCompilerLog& InCompileLog) const override;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Sets the entry widget factories for dynamic widget selection.
|
||||
* 设置动态小部件选择的入口小部件工厂。
|
||||
* @param NewFactories The array of widget factories. 小部件工厂数组。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "ListEntries")
|
||||
void SetEntryWidgetFactories(TArray<UGUIS_WidgetFactory*> NewFactories);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Generates an entry widget for the specified item.
|
||||
* 为指定项生成入口小部件。
|
||||
* @param Item The item to generate a widget for. 要生成小部件的项。
|
||||
* @param DesiredEntryClass The desired entry widget class. 期望的入口小部件类。
|
||||
* @param OwnerTable The owning table view. 所属表格视图。
|
||||
* @return The generated widget. 生成的小部件。
|
||||
*/
|
||||
virtual UUserWidget& OnGenerateEntryWidgetInternal(UObject* Item, TSubclassOf<UUserWidget> DesiredEntryClass, const TSharedRef<STableViewBase>& OwnerTable) override;
|
||||
|
||||
/**
|
||||
* Array of widget factories for dynamic entry widget selection.
|
||||
* 动态入口小部件选择的小部件工厂数组。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ListEntries", meta = (IsBindableEvent = "True"))
|
||||
TArray<TObjectPtr<UGUIS_WidgetFactory>> EntryWidgetFactories;
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "CommonTileView.h"
|
||||
#include "GUIS_TileView.generated.h"
|
||||
|
||||
class UGUIS_WidgetFactory;
|
||||
|
||||
/**
|
||||
* Extended TileView allowing dynamic selection of entry widget class via data asset.
|
||||
* 通过数据资产动态选择入口小部件类的扩展TileView。
|
||||
*/
|
||||
UCLASS(Blueprintable, meta = (Category = "Generic UI"))
|
||||
class GENERICUISYSTEM_API UGUIS_TileView : public UCommonTileView
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor for the TileView widget.
|
||||
* TileView小部件构造函数。
|
||||
*/
|
||||
UGUIS_TileView(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
|
||||
|
||||
#if WITH_EDITOR
|
||||
/**
|
||||
* Validates compiled defaults in the editor.
|
||||
* 在编辑器中验证编译默认值。
|
||||
* @param InCompileLog The widget compiler log. 小部件编译日志。
|
||||
*/
|
||||
virtual void ValidateCompiledDefaults(IWidgetCompilerLog& InCompileLog) const override;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Sets the entry widget factories for dynamic widget selection.
|
||||
* 设置动态小部件选择的入口小部件工厂。
|
||||
* @param NewFactories The array of widget factories. 小部件工厂数组。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "ListEntries")
|
||||
void SetEntryWidgetFactories(TArray<UGUIS_WidgetFactory*> NewFactories);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Generates an entry widget for the specified item.
|
||||
* 为指定项生成入口小部件。
|
||||
* @param Item The item to generate a widget for. 要生成小部件的项。
|
||||
* @param DesiredEntryClass The desired entry widget class. 期望的入口小部件类。
|
||||
* @param OwnerTable The owning table view. 所属表格视图。
|
||||
* @return The generated widget. 生成的小部件。
|
||||
*/
|
||||
virtual UUserWidget& OnGenerateEntryWidgetInternal(UObject* Item, TSubclassOf<UUserWidget> DesiredEntryClass, const TSharedRef<STableViewBase>& OwnerTable) override;
|
||||
|
||||
/**
|
||||
* Array of widget factories for dynamic entry widget selection.
|
||||
* 动态入口小部件选择的小部件工厂数组。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ListEntries", meta = (IsBindableEvent = "True"))
|
||||
TArray<TObjectPtr<UGUIS_WidgetFactory>> EntryWidgetFactories;
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/Interface.h"
|
||||
#include "GUIS_UserWidgetInterface.generated.h"
|
||||
|
||||
/**
|
||||
* Interface for UserWidget functionality.
|
||||
* 通用UserWidget功能的接口。
|
||||
* @note Designed for UserWidgets (except UCommonActivatableWidget and its derivatives).
|
||||
* @注意 专为UserWidget设计(不包括UCommonActivatableWidget及其派生类)。
|
||||
* @details Automatically called when used as an extension UI.
|
||||
* @细节 用作扩展UI时自动调用。
|
||||
*/
|
||||
UINTERFACE()
|
||||
class GENERICUISYSTEM_API UGUIS_UserWidgetInterface : public UInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
/**
|
||||
* Implementation class for UserWidget interface.
|
||||
* UserWidget接口的实现类。
|
||||
*/
|
||||
class GENERICUISYSTEM_API IGUIS_UserWidgetInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Retrieves the owning actor of the UserWidget.
|
||||
* 获取UserWidget的所属演员。
|
||||
* @return The logical owning actor. 逻辑所属演员。
|
||||
* @note Tracks data for an actor in the game world.
|
||||
* @注意 跟踪游戏世界中演员的数据。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GUIS")
|
||||
AActor* GetOwningActor();
|
||||
|
||||
/**
|
||||
* Sets the owning actor of the UserWidget.
|
||||
* 设置UserWidget的所属演员。
|
||||
* @param NewOwningActor The new owning actor. 新的所属演员。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GUIS")
|
||||
void SetOwningActor(AActor* NewOwningActor);
|
||||
|
||||
/**
|
||||
* Called when the UserWidget is activated.
|
||||
* UserWidget激活时调用。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GUIS")
|
||||
void OnActivated();
|
||||
|
||||
/**
|
||||
* Called when the UserWidget is deactivated.
|
||||
* UserWidget禁用时调用。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category="GUIS")
|
||||
void OnDeactivated();
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "Engine/DataAsset.h"
|
||||
#include "GUIS_WidgetFactory.generated.h"
|
||||
|
||||
class UUserWidget;
|
||||
|
||||
/**
|
||||
* Base class for selecting appropriate widget classes for objects.
|
||||
* 为对象选择合适小部件类的基类。
|
||||
*/
|
||||
UCLASS(Abstract, Blueprintable, BlueprintType, HideDropdown, Const)
|
||||
class GENERICUISYSTEM_API UGUIS_WidgetFactory : public UDataAsset
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Default constructor.
|
||||
* 默认构造函数。
|
||||
*/
|
||||
UGUIS_WidgetFactory();
|
||||
|
||||
/**
|
||||
* Finds the appropriate widget class for the given data.
|
||||
* 为给定数据查找合适的小部件类。
|
||||
* @param Data The data object. 数据对象。
|
||||
* @return The widget class. 小部件类。
|
||||
*/
|
||||
UFUNCTION(BlueprintNativeEvent, Category="GUIS")
|
||||
TSubclassOf<UUserWidget> FindWidgetClassForData(const UObject *Data) const;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Validates the data for the widget factory.
|
||||
* 验证小部件工厂的数据。
|
||||
* @param ValidationMessage The validation message (output). 验证消息(输出)。
|
||||
* @return True if valid, false otherwise. 如果有效返回true,否则返回false。
|
||||
*/
|
||||
UFUNCTION(BlueprintNativeEvent, Category="GUIS")
|
||||
bool OnDataValidation(FText &ValidationMessage) const;
|
||||
virtual bool OnDataValidation_Implementation(FText &ValidationMessage) const;
|
||||
|
||||
#if WITH_EDITOR
|
||||
/**
|
||||
* Validates data in the editor.
|
||||
* 在编辑器中验证数据。
|
||||
* @param Context The data validation context. 数据验证上下文。
|
||||
* @return The validation result. 验证结果。
|
||||
*/
|
||||
virtual EDataValidationResult IsDataValid(FDataValidationContext &Context) const override;
|
||||
#endif
|
||||
};
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "CommonButtonBase.h"
|
||||
#include "GUIS_ButtonBase.generated.h"
|
||||
|
||||
/**
|
||||
* Base Button
|
||||
* 基础按钮
|
||||
*/
|
||||
UCLASS(Abstract, BlueprintType, Blueprintable, meta=(Category = "Generic UI"))
|
||||
class GENERICUISYSTEM_API UGUIS_ButtonBase : public UCommonButtonBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* @param InText The override text to display on the button. 该Text会覆盖按钮上的文字。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GUIS")
|
||||
void SetButtonText(const FText& InText);
|
||||
|
||||
protected:
|
||||
// UUserWidget interface
|
||||
virtual void NativePreConstruct() override;
|
||||
// End of UUserWidget interface
|
||||
|
||||
// UCommonButtonBase interface
|
||||
virtual void UpdateInputActionWidget() override;
|
||||
virtual void OnInputMethodChanged(ECommonInputType CurrentInputType) override;
|
||||
// End of UCommonButtonBase interface
|
||||
|
||||
void RefreshButtonText();
|
||||
|
||||
// 在PreConstruct,InputActionWidget更新,以及ButtonText变化时会触发。
|
||||
UFUNCTION(BlueprintImplementableEvent)
|
||||
void OnUpdateButtonText(const FText& InText);
|
||||
|
||||
/**
|
||||
* Will use the text from InputActionWidget if not checked.
|
||||
* 在PreConstruct,InputActionWidget更新,以及ButtonText变化时会触发。
|
||||
*/
|
||||
UFUNCTION(BlueprintImplementableEvent)
|
||||
void OnUpdateButtonStyle();
|
||||
|
||||
/**
|
||||
* Will use the text from InputActionWidget if not checked.
|
||||
* 不勾选的情况下,会使用来自InputActionWidget的显示文字。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Button", meta = (ExposeOnSpawn = true, InlineEditConditionToggle))
|
||||
bool bOverride_ButtonText{true};
|
||||
|
||||
/**
|
||||
* The text to display on the button.
|
||||
* 按钮的显示文字。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Button", meta = (ExposeOnSpawn = true, EditCondition = "bOverride_ButtonText"))
|
||||
FText ButtonText;
|
||||
|
||||
#if WITH_EDITOR
|
||||
virtual const FText GetPaletteCategory() override;
|
||||
#endif // WITH_EDITOR
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GUIS_ButtonBase.h"
|
||||
#include "GUIS_TabListWidgetBase.h"
|
||||
#include "GUIS_TabButtonBase.generated.h"
|
||||
|
||||
class UCommonLazyImage;
|
||||
|
||||
/**
|
||||
* Button used for switching between tabs.
|
||||
* 用于切换选项卡的按钮。
|
||||
*/
|
||||
UCLASS(Abstract, Blueprintable, meta = (Category = "Generic UI", DisableNativeTick))
|
||||
class GENERICUISYSTEM_API UGUIS_TabButtonBase : public UGUIS_ButtonBase, public IGUIS_TabButtonInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "Engine/DataAsset.h"
|
||||
#include "Styling/SlateBrush.h"
|
||||
#include "GUIS_TabDefinition.generated.h"
|
||||
|
||||
class UCommonActivatableWidget;
|
||||
class UWidget;
|
||||
class UCommonUserWidget;
|
||||
class UCommonButtonBase;
|
||||
|
||||
/**
|
||||
* Base Tab definition.
|
||||
* @attention Deprecated as it's unstable.
|
||||
* 基础选项卡定义。
|
||||
* @注意 已经弃用
|
||||
*/
|
||||
UCLASS(Blueprintable, EditInlineNew, CollapseCategories, Const, DefaultToInstanced, Deprecated)
|
||||
class GENERICUISYSTEM_API UDEPRECATED_GUIS_TabDefinition : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Tab Definition")
|
||||
FName TabId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Tab Definition")
|
||||
FText TabText;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Tab Definition")
|
||||
FSlateBrush IconBrush;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Tab Definition", Transient)
|
||||
bool bHidden;
|
||||
|
||||
/**
|
||||
* A common button which implements GUIS_TabButtonInterface to received Label infomation.
|
||||
* 指定用作Tab按钮的Widget类型,该类型必须实现GUIS_TabButtonInterface以接收Label信息。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Tab Definition", meta = (MustImplement = "/Script/GenericUISystem.GUIS_TabButtonInterface", AllowAbstract = "false"))
|
||||
TSoftClassPtr<UCommonButtonBase> TabButtonType;
|
||||
|
||||
/**
|
||||
* 该所呈现的Widget(可选),如果有指定,那么需要调用
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Tab Definition")
|
||||
TSoftClassPtr<UCommonActivatableWidget> TabContentType;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UWidget> CreatedTabContentWidget;
|
||||
};
|
||||
@@ -0,0 +1,320 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CommonTabListWidgetBase.h"
|
||||
#include "GUIS_TabListWidgetBase.generated.h"
|
||||
|
||||
class UDEPRECATED_GUIS_TabDefinition;
|
||||
class UCommonButtonBase;
|
||||
class UCommonUserWidget;
|
||||
class UObject;
|
||||
class UWidget;
|
||||
struct FFrame;
|
||||
|
||||
/**
|
||||
* Structure defining a tab descriptor.
|
||||
* 定义选项卡描述的结构。
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct GENERICUISYSTEM_API FGUIS_TabDescriptor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
* 默认构造函数。
|
||||
*/
|
||||
FGUIS_TabDescriptor();
|
||||
|
||||
/**
|
||||
* Unique identifier for the tab.
|
||||
* 选项卡的唯一标识符。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Tab Definition")
|
||||
FName TabId;
|
||||
|
||||
/**
|
||||
* Display text for the tab.
|
||||
* 选项卡的显示文本。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Tab Definition")
|
||||
FText TabText;
|
||||
|
||||
/**
|
||||
* Icon brush for the tab.
|
||||
* 选项卡的图标画刷。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Tab Definition")
|
||||
FSlateBrush IconBrush;
|
||||
|
||||
/**
|
||||
* Whether the tab is hidden.
|
||||
* 选项卡是否隐藏。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Tab Definition", Transient)
|
||||
bool bHidden{false};
|
||||
|
||||
/**
|
||||
* Button type for the tab, must implement GUIS_TabButtonInterface.
|
||||
* 选项卡的按钮类型,必须实现GUIS_TabButtonInterface。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Tab Definition", meta = (MustImplement = "/Script/GenericUISystem.GUIS_TabButtonInterface", AllowAbstract = "false"))
|
||||
TSoftClassPtr<UCommonButtonBase> TabButtonType;
|
||||
|
||||
/**
|
||||
* Content widget type for the tab (optional).
|
||||
* 选项卡的内容小部件类型(可选)。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Tab Definition")
|
||||
TSoftClassPtr<UCommonUserWidget> TabContentType;
|
||||
|
||||
/**
|
||||
* The created content widget for the tab.
|
||||
* 选项卡的已创建内容小部件。
|
||||
*/
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UWidget> CreatedTabContentWidget;
|
||||
};
|
||||
|
||||
/**
|
||||
* Interface for tab buttons.
|
||||
* 选项卡按钮接口。
|
||||
*/
|
||||
UINTERFACE(BlueprintType)
|
||||
class GENERICUISYSTEM_API UGUIS_TabButtonInterface : public UInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
/**
|
||||
* Interface for tab buttons to receive and update tab label information.
|
||||
* 选项卡按钮接收和更新标签信息的接口。
|
||||
*/
|
||||
class GENERICUISYSTEM_API IGUIS_TabButtonInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Sets the deprecated tab definition (no longer used).
|
||||
* 设置已弃用的选项卡定义(不再使用)。
|
||||
* @param TabDefinition The tab definition. 选项卡定义。
|
||||
*/
|
||||
UFUNCTION(BlueprintNativeEvent, Category = "Tab Button", meta=(DeprecatedFunction, DeprecationMessage="Use Set TabLabelInfo"))
|
||||
void SetTabDefinition(const UDEPRECATED_GUIS_TabDefinition* TabDefinition);
|
||||
|
||||
/**
|
||||
* Sets the tab label information.
|
||||
* 设置选项卡标签信息。
|
||||
* @param TabDescriptor The tab descriptor. 选项卡描述。
|
||||
*/
|
||||
UFUNCTION(BlueprintNativeEvent, Category = "Tab Button")
|
||||
void SetTabLabelInfo(const FGUIS_TabDescriptor& TabDescriptor);
|
||||
};
|
||||
|
||||
/**
|
||||
* Tab list widget for managing and displaying tabs.
|
||||
* 管理和显示选项卡的选项卡列表小部件。
|
||||
* @note Can be linked to a switcher for dynamic tab switching.
|
||||
* @注意 可关联到切换器以实现动态选项卡切换。
|
||||
*/
|
||||
UCLASS(Blueprintable, BlueprintType, Abstract, meta = (DisableNativeTick))
|
||||
class GENERICUISYSTEM_API UGUIS_TabListWidgetBase : public UCommonTabListWidgetBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor for the tab list widget.
|
||||
* 选项卡列表小部件构造函数。
|
||||
*/
|
||||
UGUIS_TabListWidgetBase();
|
||||
|
||||
/**
|
||||
* Retrieves preregistered tab information by ID.
|
||||
* 通过ID获取预注册的选项卡信息。
|
||||
* @param TabNameId The tab ID. 选项卡ID。
|
||||
* @param OutTabInfo The tab descriptor (output). 选项卡描述(输出)。
|
||||
* @return True if found, false otherwise. 如果找到返回true,否则返回false。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GUIS|TabList")
|
||||
bool GetPreregisteredTabInfo(const FName TabNameId, FGUIS_TabDescriptor& OutTabInfo);
|
||||
|
||||
/**
|
||||
* Retrieves the index of a preregistered tab by ID.
|
||||
* 通过ID获取预注册选项卡的索引。
|
||||
* @param TabNameId The tab ID. 选项卡ID。
|
||||
* @return The tab index. 选项卡索引。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "GUIS|TabList")
|
||||
int32 GetPreregisteredTabIndex(FName TabNameId) const;
|
||||
|
||||
/**
|
||||
* Finds preregistered tab information by ID.
|
||||
* 通过ID查找预注册的选项卡信息。
|
||||
* @param TabNameId The tab ID. 选项卡ID。
|
||||
* @param OutTabInfo The tab descriptor (output). 选项卡描述(输出)。
|
||||
* @return True if found, false otherwise. 如果找到返回true,否则返回false。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "GUIS|TabList", meta=(ExpandBoolAsExecs="ReturnValue"))
|
||||
bool FindPreregisteredTabInfo(const FName TabNameId, FGUIS_TabDescriptor& OutTabInfo);
|
||||
|
||||
/**
|
||||
* Toggles the hidden state of a tab (call before associating a switcher).
|
||||
* 切换选项卡的隐藏状态(在关联切换器之前调用)。
|
||||
* @param TabNameId The tab ID. 选项卡ID。
|
||||
* @param bHidden Whether to hide the tab. 是否隐藏选项卡。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "GUIS|TabList")
|
||||
void SetTabHiddenState(FName TabNameId, bool bHidden);
|
||||
|
||||
/**
|
||||
* Registers a dynamic tab.
|
||||
* 注册动态选项卡。
|
||||
* @param TabDescriptor The tab descriptor. 选项卡描述。
|
||||
* @return True if successful, false otherwise. 如果成功返回true,否则返回false。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GUIS|TabList")
|
||||
bool RegisterDynamicTab(const FGUIS_TabDescriptor& TabDescriptor);
|
||||
|
||||
/**
|
||||
* Checks if the first tab is active.
|
||||
* 检查第一个选项卡是否激活。
|
||||
* @return True if the first tab is active. 如果第一个选项卡激活返回true。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GUIS|TabList")
|
||||
bool IsFirstTabActive() const;
|
||||
|
||||
/**
|
||||
* Checks if the last tab is active.
|
||||
* 检查最后一个选项卡是否激活。
|
||||
* @return True if the last tab is active. 如果最后一个选项卡激活返回true。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GUIS|TabList")
|
||||
bool IsLastTabActive() const;
|
||||
|
||||
/**
|
||||
* Checks if a tab is visible.
|
||||
* 检查选项卡是否可见。
|
||||
* @param TabId The tab ID. 选项卡ID。
|
||||
* @return True if the tab is visible. 如果选项卡可见返回true。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GUIS|TabList")
|
||||
bool IsTabVisible(FName TabId);
|
||||
|
||||
/**
|
||||
* Retrieves the number of visible tabs.
|
||||
* 获取可见选项卡的数量。
|
||||
* @return The number of visible tabs. 可见选项卡数量。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GUIS|TabList")
|
||||
int32 GetVisibleTabCount();
|
||||
|
||||
/**
|
||||
* Delegate for when a new tab is created.
|
||||
* 新选项卡创建时的委托。
|
||||
*/
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnTabContentCreated, FName, TabId, UCommonUserWidget *, TabWidget);
|
||||
|
||||
/**
|
||||
* Native delegate for tab creation events.
|
||||
* 选项卡创建事件的本地委托。
|
||||
*/
|
||||
DECLARE_EVENT_TwoParams(UGUIS_TabListWidgetBase, FOnTabContentCreatedNative, FName /* TabId */, UCommonUserWidget * /* TabWidget */);
|
||||
|
||||
/**
|
||||
* Broadcasts when a new tab is created.
|
||||
* 新选项卡创建时广播。
|
||||
*/
|
||||
UPROPERTY(BlueprintAssignable, Category="GUIS|TabList")
|
||||
FOnTabContentCreated OnTabContentCreated;
|
||||
|
||||
/**
|
||||
* Native event for tab creation.
|
||||
* 选项卡创建的本地事件。
|
||||
*/
|
||||
FOnTabContentCreatedNative OnTabContentCreatedNative;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Called when the widget is initialized.
|
||||
* 小部件初始化时调用。
|
||||
*/
|
||||
virtual void NativeOnInitialized() override;
|
||||
|
||||
/**
|
||||
* Called when the widget is constructed.
|
||||
* 小部件构造时调用。
|
||||
*/
|
||||
virtual void NativeConstruct() override;
|
||||
|
||||
/**
|
||||
* Called when the widget is destructed.
|
||||
* 小部件销毁时调用。
|
||||
*/
|
||||
virtual void NativeDestruct() override;
|
||||
|
||||
/**
|
||||
* Called before the linked switcher changes.
|
||||
* 关联切换器更改前调用。
|
||||
*/
|
||||
virtual void HandlePreLinkedSwitcherChanged() override;
|
||||
|
||||
/**
|
||||
* Called after the linked switcher changes.
|
||||
* 关联切换器更改后调用。
|
||||
*/
|
||||
virtual void HandlePostLinkedSwitcherChanged() override;
|
||||
|
||||
/**
|
||||
* Handles tab creation.
|
||||
* 处理选项卡创建。
|
||||
* @param TabId The tab ID. 选项卡ID。
|
||||
* @param TabButton The tab button. 选项卡按钮。
|
||||
*/
|
||||
virtual void HandleTabCreation_Implementation(FName TabId, UCommonButtonBase* TabButton) override;
|
||||
|
||||
/**
|
||||
* Sets up the tabs.
|
||||
* 设置选项卡。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GUIS|TabList")
|
||||
void SetupTabs();
|
||||
|
||||
/**
|
||||
* Deprecated array of tab definitions.
|
||||
* 已弃用的选项卡定义数组。
|
||||
*/
|
||||
UPROPERTY(Instanced, meta = (BlueprintProtected, TitleProperty = "TabId"))
|
||||
TArray<TObjectPtr<UDEPRECATED_GUIS_TabDefinition>> TabDefinitions_DEPRECATED;
|
||||
|
||||
/**
|
||||
* Array of preregistered tab descriptors.
|
||||
* 预注册选项卡描述数组。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="TabList", meta=(TitleProperty="TabId"))
|
||||
TArray<FGUIS_TabDescriptor> PreregisteredTabInfoArray;
|
||||
|
||||
/**
|
||||
* Map of pending tab label information for runtime-registered tabs.
|
||||
* 运行时注册选项卡的待处理标签信息映射。
|
||||
*/
|
||||
UPROPERTY()
|
||||
TMap<FName, FGUIS_TabDescriptor> PendingTabLabelInfoMap;
|
||||
|
||||
#if WITH_EDITOR
|
||||
/**
|
||||
* Called after loading in the editor.
|
||||
* 编辑器中加载后调用。
|
||||
*/
|
||||
virtual void PostLoad() override;
|
||||
|
||||
/**
|
||||
* Validates compiled defaults in the editor.
|
||||
* 在编辑器中验证编译默认值。
|
||||
* @param CompileLog The widget compiler log. 小部件编译日志。
|
||||
*/
|
||||
virtual void ValidateCompiledDefaults(class IWidgetCompilerLog& CompileLog) const override;
|
||||
#endif
|
||||
};
|
||||
@@ -0,0 +1,97 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CommonActivatableWidget.h"
|
||||
#include "GUIS_ActivatableWidget.generated.h"
|
||||
|
||||
struct FUIInputConfig;
|
||||
|
||||
/**
|
||||
* Enum defining input modes for activatable widgets.
|
||||
* 定义可激活小部件输入模式的枚举。
|
||||
*/
|
||||
UENUM()
|
||||
enum class EGUIS_ActivatableWidgetInputMode : uint8
|
||||
{
|
||||
/**
|
||||
* Default input mode.
|
||||
* 默认输入模式。
|
||||
*/
|
||||
Default,
|
||||
|
||||
/**
|
||||
* Allows both game and menu input.
|
||||
* 允许游戏和菜单输入。
|
||||
*/
|
||||
GameAndMenu,
|
||||
|
||||
/**
|
||||
* Game input only.
|
||||
* 仅游戏输入。
|
||||
*/
|
||||
Game,
|
||||
|
||||
/**
|
||||
* Menu input only.
|
||||
* 仅菜单输入。
|
||||
*/
|
||||
Menu
|
||||
};
|
||||
|
||||
/**
|
||||
* Activatable widget that manages input configuration when activated.
|
||||
* 可激活小部件,激活时管理输入配置。
|
||||
*/
|
||||
UCLASS(Abstract, Blueprintable)
|
||||
class GENERICUISYSTEM_API UGUIS_ActivatableWidget : public UCommonActivatableWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor for the activatable widget.
|
||||
* 可激活小部件构造函数。
|
||||
*/
|
||||
UGUIS_ActivatableWidget(const FObjectInitializer& ObjectInitializer);
|
||||
|
||||
/**
|
||||
* Sets whether the widget handles back navigation.
|
||||
* 设置小部件是否处理后退导航。
|
||||
* @param bNewState The new back handler state. 新的后退处理状态。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "GUIS|ActivatableWidget")
|
||||
void SetIsBackHandler(bool bNewState);
|
||||
|
||||
/**
|
||||
* Retrieves the desired input configuration.
|
||||
* 获取期望的输入配置。
|
||||
* @return The input configuration. 输入配置。
|
||||
*/
|
||||
virtual TOptional<FUIInputConfig> GetDesiredInputConfig() const override;
|
||||
|
||||
#if WITH_EDITOR
|
||||
/**
|
||||
* Validates the compiled widget tree in the editor.
|
||||
* 在编辑器中验证编译的小部件树。
|
||||
* @param BlueprintWidgetTree The widget tree to validate. 要验证的小部件树。
|
||||
* @param CompileLog The widget compiler log. 小部件编译日志。
|
||||
*/
|
||||
virtual void ValidateCompiledWidgetTree(const UWidgetTree& BlueprintWidgetTree, class IWidgetCompilerLog& CompileLog) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Desired input mode when the widget is activated.
|
||||
* 小部件激活时的期望输入模式。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, Category="Input")
|
||||
EGUIS_ActivatableWidgetInputMode InputConfig = EGUIS_ActivatableWidgetInputMode::Default;
|
||||
|
||||
/**
|
||||
* Mouse capture behavior for game input.
|
||||
* 游戏输入的鼠标捕获行为。
|
||||
*/
|
||||
UPROPERTY(EditDefaultsOnly, Category="Input")
|
||||
EMouseCaptureMode GameMouseCaptureMode = EMouseCaptureMode::CapturePermanently;
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "GUIS_GameUIContext.generated.h"
|
||||
|
||||
/**
|
||||
* Base class for UI context data shared across multiple UI elements.
|
||||
* 多个UI元素共享的UI上下文数据的基类。
|
||||
* @details Allows subclassing to add custom data for UI interactions.
|
||||
* @细节 允许子类化以添加用于UI交互的自定义数据。
|
||||
*/
|
||||
UCLASS(Abstract, Blueprintable, BlueprintType)
|
||||
class GENERICUISYSTEM_API UGUIS_GameUIContext : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
@@ -0,0 +1,191 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Blueprint/IUserObjectListEntry.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "UObject/SoftObjectPtr.h"
|
||||
|
||||
#include "GUIS_GameUIFunctionLibrary.generated.h"
|
||||
|
||||
class UGUIS_GameUILayout;
|
||||
enum class ECommonInputType : uint8;
|
||||
template <typename T>
|
||||
class TSubclassOf;
|
||||
|
||||
class APlayerController;
|
||||
class UCommonActivatableWidget;
|
||||
class ULocalPlayer;
|
||||
class UObject;
|
||||
class UUserWidget;
|
||||
struct FFrame;
|
||||
struct FGameplayTag;
|
||||
|
||||
/**
|
||||
* Common functions for Game UI.
|
||||
* 游戏UI的通用功能函数库。
|
||||
*/
|
||||
UCLASS()
|
||||
class GENERICUISYSTEM_API UGUIS_GameUIFunctionLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UGUIS_GameUIFunctionLibrary()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the input type of the owning player for the specified widget.
|
||||
* 获取指定控件所属玩家的输入类型。
|
||||
* @param WidgetContextObject The widget to query for input type. 要查询输入类型的控件。
|
||||
* @return The input type of the owning player. 所属玩家的输入类型。
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, BlueprintCosmetic, Category="GUIS", meta = (WorldContext = "WidgetContextObject"))
|
||||
static ECommonInputType GetOwningPlayerInputType(const UUserWidget* WidgetContextObject);
|
||||
|
||||
/**
|
||||
* Checks if the owning player is using touch input.
|
||||
* 检查所属玩家是否使用触摸输入。
|
||||
* @param WidgetContextObject The widget to query for input type. 要查询输入类型的控件。
|
||||
* @return True if the owning player is using touch input, false otherwise. 如果所属玩家使用触摸输入则返回true,否则返回false。
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, BlueprintCosmetic, Category="GUIS", meta = (WorldContext = "WidgetContextObject"))
|
||||
static bool IsOwningPlayerUsingTouch(const UUserWidget* WidgetContextObject);
|
||||
|
||||
/**
|
||||
* Checks if the owning player is using a gamepad.
|
||||
* 检查所属玩家是否使用游戏手柄。
|
||||
* @param WidgetContextObject The widget to query for input type. 要查询输入类型的控件。
|
||||
* @return True if the owning player is using a gamepad, false otherwise. 如果所属玩家使用游戏手柄则返回true,否则返回false。
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, BlueprintCosmetic, Category="GUIS", meta = (WorldContext = "WidgetContextObject"))
|
||||
static bool IsOwningPlayerUsingGamepad(const UUserWidget* WidgetContextObject);
|
||||
|
||||
/**
|
||||
* Pushes a widget to the specified UI layer for the given player.
|
||||
* 将控件推送到指定玩家的UI层。
|
||||
* @param PlayerController The player controller to associate with the UI layer. 与UI层关联的玩家控制器。
|
||||
* @param LayerName The tag identifying the UI layer. 标识UI层的标签。
|
||||
* @param WidgetClass The class of the widget to push. 要推送的控件类。
|
||||
* @return The created widget instance. 创建的控件实例。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="GUIS", meta=(DynamicOutputParam="ReturnValue", DeterminesOutputType="WidgetClass"))
|
||||
static UCommonActivatableWidget* PushContentToUILayer_ForPlayer(const APlayerController* PlayerController, UPARAM(meta = (Categories = "UI.Layer,GUIS.Layer"))
|
||||
FGameplayTag LayerName,
|
||||
UPARAM(meta = (AllowAbstract = false))
|
||||
TSubclassOf<UCommonActivatableWidget> WidgetClass);
|
||||
|
||||
/**
|
||||
* Pops content from the specified UI layer for the given player.
|
||||
* 从指定玩家的UI层中弹出内容。
|
||||
* @param PlayerController The player controller associated with the UI layer. 与UI层关联的玩家控制器。
|
||||
* @param LayerName The tag identifying the UI layer. 标识UI层的标签。
|
||||
* @param RemainNum Number of widgets to remain in the layer (-1 means remove all.). 保留在层中的控件数量(-1表示不保留)。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="GUIS")
|
||||
static void PopContentFromUILayer_ForPlayer(const APlayerController* PlayerController, UPARAM(meta = (Categories = "UI.Layer,GUIS.Layer"))
|
||||
FGameplayTag LayerName, int32 RemainNum = -1);
|
||||
|
||||
/**
|
||||
* Removes a specific activatable widget from the UI layer.
|
||||
* 从UI层中移除指定的可激活控件。
|
||||
* @param ActivatableWidget The widget to remove. 要移除的控件。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="GUIS", meta=(DefaultToSelf="ActivatableWidget"))
|
||||
static void PopContentFromUILayer(UCommonActivatableWidget* ActivatableWidget);
|
||||
|
||||
/**
|
||||
* Pops multiple activatable widgets from the UI layer.
|
||||
* 从UI层中弹出多个可激活控件。
|
||||
* @param ActivatableWidgets List of activatable widgets to pop. 要弹出的控件列表。
|
||||
* @param bReverse If true, pops in reverse array order. 如果为true,则按数组反向顺序弹出。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="GUIS", meta=(DefaultToSelf="ActivatableWidget"))
|
||||
static void PopContentsFromUILayer(TArray<UCommonActivatableWidget*> ActivatableWidgets, bool bReverse = true);
|
||||
|
||||
/**
|
||||
* Gets the local player associated with the given player controller.
|
||||
* 获取与指定玩家控制器关联的本地玩家。
|
||||
* @param PlayerController The player controller to query. 要查询的玩家控制器。
|
||||
* @return The associated local player. 关联的本地玩家。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, BlueprintCosmetic, Category="GUIS")
|
||||
static ULocalPlayer* GetLocalPlayerFromController(APlayerController* PlayerController);
|
||||
|
||||
/**
|
||||
* Gets the game UI layout for the specified player.
|
||||
* 获取指定玩家的游戏UI布局。
|
||||
* @param PlayerController The player controller to query. 要查询的玩家控制器。
|
||||
* @return The game UI layout for the player. 玩家的游戏UI布局。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="GUIS")
|
||||
static UGUIS_GameUILayout* GetGameUILayoutForPlayer(const APlayerController* PlayerController);
|
||||
|
||||
/**
|
||||
* Suspends input for the specified player with a reason.
|
||||
* 暂停指定玩家的输入并提供原因。
|
||||
* @param PlayerController The player controller to suspend input for. 要暂停输入的玩家控制器。
|
||||
* @param SuspendReason The reason for suspending input. 暂停输入的原因。
|
||||
* @return A token representing the suspension. 表示暂停的令牌。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="GUIS")
|
||||
static FName SuspendInputForPlayer(APlayerController* PlayerController, FName SuspendReason);
|
||||
|
||||
/**
|
||||
* Suspends input for the specified local player with a reason.
|
||||
* 暂停指定本地玩家的输入并提供原因。
|
||||
* @param LocalPlayer The local player to suspend input for. 要暂停输入的本地玩家。
|
||||
* @param SuspendReason The reason for suspending input. 暂停输入的原因。
|
||||
* @return A token representing the suspension. 表示暂停的令牌。
|
||||
*/
|
||||
static FName SuspendInputForPlayer(ULocalPlayer* LocalPlayer, FName SuspendReason);
|
||||
|
||||
/**
|
||||
* Resumes input for the specified player using the suspension token.
|
||||
* 使用暂停令牌恢复指定玩家的输入。
|
||||
* @param PlayerController The player controller to resume input for. 要恢复输入的玩家控制器。
|
||||
* @param SuspendToken The token from the suspension call. 暂停调用时返回的令牌。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="GUIS")
|
||||
static void ResumeInputForPlayer(APlayerController* PlayerController, FName SuspendToken);
|
||||
|
||||
/**
|
||||
* Resumes input for the specified local player using the suspension token.
|
||||
* 使用暂停令牌恢复指定本地玩家的输入。
|
||||
* @param LocalPlayer The local player to resume input for. 要恢复输入的本地玩家。
|
||||
* @param SuspendToken The token from the suspension call. 暂停调用时返回的令牌。
|
||||
*/
|
||||
static void ResumeInputForPlayer(ULocalPlayer* LocalPlayer, FName SuspendToken);
|
||||
|
||||
/**
|
||||
* Returns the typed item in the owning list view that this entry is currently assigned to represent.
|
||||
* 以指定类型的方式获取Entry要展示的Item。
|
||||
* @param UserObjectListEntry The list entry to query (defaults to self). 要查询的列表条目(默认值为自身)。
|
||||
* @param DesiredClass The type of ListItemObject. 列表项对象的类型。
|
||||
* @return The typed item object. 类型化的列表项对象。
|
||||
*/
|
||||
UFUNCTION(BlueprintPure, Category="GUIS",
|
||||
meta = (DeterminesOutputType = "DesiredClass", DynamicOutputParam = "ReturnValue", DefaultToSelf = UserObjectListEntry, DisplayName = "Get List item Object"))
|
||||
static UObject* GetTypedListItem(TScriptInterface<IUserObjectListEntry> UserObjectListEntry, TSubclassOf<UObject> DesiredClass);
|
||||
|
||||
/**
|
||||
* Safely returns the typed item in the owning list view that this entry is currently assigned to represent.
|
||||
* 安全地以指定类型的方式获取Entry要展示的Item。
|
||||
* @param UserObjectListEntry The list entry to query (defaults to self). 要查询的列表条目(默认值为自身)。
|
||||
* @param DesiredClass The type of ListItemObject. 列表项对象的类型。
|
||||
* @param OutItem The typed item object (output). 类型化的列表项对象(输出)。
|
||||
* @return True if the item was successfully retrieved, false otherwise. 如果成功获取项则返回true,否则返回false。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GUIS",
|
||||
meta = (ExpandBoolAsExecs = "ReturnValue", DeterminesOutputType = "DesiredClass", DynamicOutputParam = "OutItemObject", DefaultToSelf = UserObjectListEntry, DisplayName =
|
||||
"Get List item Object"))
|
||||
static bool GetTypedListItemSafely(TScriptInterface<IUserObjectListEntry> UserObjectListEntry, TSubclassOf<UObject> DesiredClass, UObject*& OutItem);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Tracks the number of active input suspensions.
|
||||
* 跟踪当前活动的输入暂停数量。
|
||||
*/
|
||||
static int32 InputSuspensions;
|
||||
};
|
||||
213
Plugins/GGS/Source/GenericUISystem/Public/UI/GUIS_GameUILayout.h
Normal file
213
Plugins/GGS/Source/GenericUISystem/Public/UI/GUIS_GameUILayout.h
Normal file
@@ -0,0 +1,213 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CommonActivatableWidget.h"
|
||||
#include "Engine/AssetManager.h"
|
||||
#include "Engine/StreamableManager.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "GUIS_GameUIFunctionLibrary.h"
|
||||
#include "Widgets/CommonActivatableWidgetContainer.h" // IWYU pragma: keep
|
||||
|
||||
#include "GUIS_GameUILayout.generated.h"
|
||||
|
||||
class APlayerController;
|
||||
class UClass;
|
||||
class UCommonActivatableWidgetContainerBase;
|
||||
class ULocalPlayer;
|
||||
class UObject;
|
||||
struct FFrame;
|
||||
|
||||
/**
|
||||
* Enumeration for the state of an async UI widget layer load operation.
|
||||
* 异步UI控件层加载操作状态的枚举。
|
||||
*/
|
||||
enum class EGUIS_AsyncWidgetLayerState : uint8
|
||||
{
|
||||
Canceled, // Operation was canceled. 操作被取消。
|
||||
Initialize, // Widget is being initialized. 控件正在初始化。
|
||||
AfterPush // Widget has been pushed to the layer. 控件已被推送到层。
|
||||
};
|
||||
|
||||
/**
|
||||
* Primary game UI layout for managing widget layers for a single player.
|
||||
* 管理单个玩家控件层的游戏主UI布局。
|
||||
* @details Handles the layout, pushing, and display of UI layers for a player in a split-screen game.
|
||||
* @细节 处理分屏游戏中玩家的UI层布局、推送和显示。
|
||||
*/
|
||||
UCLASS(Abstract, meta = (DisableNativeTick))
|
||||
class GENERICUISYSTEM_API UGUIS_GameUILayout : public UCommonUserWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor for the game UI layout.
|
||||
* 游戏UI布局的构造函数。
|
||||
*/
|
||||
UGUIS_GameUILayout(const FObjectInitializer &ObjectInitializer);
|
||||
|
||||
/**
|
||||
* Sets the dormant state of the layout, limiting it to persistent actions.
|
||||
* 设置布局的休眠状态,限制为持久动作。
|
||||
* @param Dormant True to set the layout as dormant, false otherwise. 如果设置为休眠则为true,否则为false。
|
||||
*/
|
||||
void SetIsDormant(bool Dormant);
|
||||
|
||||
/**
|
||||
* Checks if the layout is in a dormant state.
|
||||
* 检查布局是否处于休眠状态。
|
||||
* @return True if the layout is dormant, false otherwise. 如果布局休眠则返回true,否则返回false。
|
||||
*/
|
||||
bool IsDormant() const { return bIsDormant; }
|
||||
|
||||
public:
|
||||
/**
|
||||
* Asynchronously pushes a widget to a specified layer.
|
||||
* 异步将控件推送到指定层。
|
||||
* @param LayerName The tag of the layer to push to. 要推送到的层标签。
|
||||
* @param bSuspendInputUntilComplete Whether to suspend input until loading is complete. 是否在加载完成前暂停输入。
|
||||
* @param ActivatableWidgetClass The class of the widget to push. 要推送的控件类。
|
||||
* @return A handle to the async streaming operation. 异步流操作的句柄。
|
||||
*/
|
||||
template <typename ActivatableWidgetT = UCommonActivatableWidget>
|
||||
TSharedPtr<FStreamableHandle> PushWidgetToLayerStackAsync(FGameplayTag LayerName, bool bSuspendInputUntilComplete, TSoftClassPtr<UCommonActivatableWidget> ActivatableWidgetClass)
|
||||
{
|
||||
return PushWidgetToLayerStackAsync<ActivatableWidgetT>(LayerName, bSuspendInputUntilComplete, ActivatableWidgetClass, [](EGUIS_AsyncWidgetLayerState, ActivatableWidgetT *) {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously pushes a widget to a specified layer with a state callback.
|
||||
* 使用状态回调异步将控件推送到指定层。
|
||||
* @param LayerName The tag of the layer to push to. 要推送到的层标签。
|
||||
* @param bSuspendInputUntilComplete Whether to suspend input until loading is complete. 是否在加载完成前暂停输入。
|
||||
* @param ActivatableWidgetClass The class of the widget to push. 要推送的控件类。
|
||||
* @param StateFunc Callback for handling widget state changes. 处理控件状态变化的回调。
|
||||
* @return A handle to the async streaming operation. 异步流操作的句柄。
|
||||
*/
|
||||
template <typename ActivatableWidgetT = UCommonActivatableWidget>
|
||||
TSharedPtr<FStreamableHandle> PushWidgetToLayerStackAsync(FGameplayTag LayerName, bool bSuspendInputUntilComplete, TSoftClassPtr<UCommonActivatableWidget> ActivatableWidgetClass,
|
||||
TFunction<void(EGUIS_AsyncWidgetLayerState, ActivatableWidgetT *)> StateFunc)
|
||||
{
|
||||
static_assert(TIsDerivedFrom<ActivatableWidgetT, UCommonActivatableWidget>::IsDerived, "Only CommonActivatableWidgets can be used here");
|
||||
|
||||
static FName NAME_PushingWidgetToLayer("PushingWidgetToLayer");
|
||||
const FName SuspendInputToken = bSuspendInputUntilComplete ? UGUIS_GameUIFunctionLibrary::SuspendInputForPlayer(GetOwningPlayer(), NAME_PushingWidgetToLayer) : NAME_None;
|
||||
|
||||
FStreamableManager &StreamableManager = UAssetManager::Get().GetStreamableManager();
|
||||
TSharedPtr<FStreamableHandle> StreamingHandle = StreamableManager.RequestAsyncLoad(ActivatableWidgetClass.ToSoftObjectPath(), FStreamableDelegate::CreateWeakLambda(this,
|
||||
[this, LayerName, ActivatableWidgetClass, StateFunc, SuspendInputToken]()
|
||||
{
|
||||
UGUIS_GameUIFunctionLibrary::ResumeInputForPlayer(GetOwningPlayer(), SuspendInputToken);
|
||||
|
||||
ActivatableWidgetT *Widget = PushWidgetToLayerStack<ActivatableWidgetT>(
|
||||
LayerName, ActivatableWidgetClass.Get(), [StateFunc](ActivatableWidgetT &WidgetToInit)
|
||||
{ StateFunc(EGUIS_AsyncWidgetLayerState::Initialize, &WidgetToInit); });
|
||||
|
||||
StateFunc(EGUIS_AsyncWidgetLayerState::AfterPush, Widget);
|
||||
}));
|
||||
|
||||
// Setup a cancel delegate to resume input if the operation is canceled.
|
||||
StreamingHandle->BindCancelDelegate(FStreamableDelegate::CreateWeakLambda(this,
|
||||
[this, StateFunc, SuspendInputToken]()
|
||||
{
|
||||
UGUIS_GameUIFunctionLibrary::ResumeInputForPlayer(GetOwningPlayer(), SuspendInputToken);
|
||||
StateFunc(EGUIS_AsyncWidgetLayerState::Canceled, nullptr);
|
||||
}));
|
||||
|
||||
return StreamingHandle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes a widget to a specified layer.
|
||||
* 将控件推送到指定层。
|
||||
* @param LayerName The tag of the layer to push to. 要推送到的层标签。
|
||||
* @param ActivatableWidgetClass The class of the widget to push. 要推送的控件类。
|
||||
* @return Pointer to the pushed widget, or nullptr if failed. 推送的控件指针,失败时为nullptr。
|
||||
*/
|
||||
template <typename ActivatableWidgetT = UCommonActivatableWidget>
|
||||
ActivatableWidgetT *PushWidgetToLayerStack(FGameplayTag LayerName, UClass *ActivatableWidgetClass)
|
||||
{
|
||||
return PushWidgetToLayerStack<ActivatableWidgetT>(LayerName, ActivatableWidgetClass, [](ActivatableWidgetT &) {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes a widget to a specified layer with initialization.
|
||||
olduğuna * 将控件推送到指定层并进行初始化。
|
||||
* @param LayerName The tag of the layer to push to. 要推送到的层标签。
|
||||
* @param ActivatableWidgetClass The class of the widget to push. 要推送的控件类。
|
||||
* @param InitInstanceFunc Function to initialize the widget instance. 初始化控件实例的函数。
|
||||
* @return Pointer to the pushed widget, or nullptr if failed. 推送的控件指针,失败时为nullptr。
|
||||
*/
|
||||
template <typename ActivatableWidgetT = UCommonActivatableWidget>
|
||||
ActivatableWidgetT *PushWidgetToLayerStack(FGameplayTag LayerName, UClass *ActivatableWidgetClass, TFunctionRef<void(ActivatableWidgetT &)> InitInstanceFunc)
|
||||
{
|
||||
static_assert(TIsDerivedFrom<ActivatableWidgetT, UCommonActivatableWidget>::IsDerived, "Only CommonActivatableWidgets can be used here");
|
||||
|
||||
if (UCommonActivatableWidgetContainerBase *Layer = GetLayerWidget(LayerName))
|
||||
{
|
||||
return Layer->AddWidget<ActivatableWidgetT>(ActivatableWidgetClass, InitInstanceFunc);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and removes a widget from any layer.
|
||||
* 从任意层查找并移除控件。
|
||||
* @param ActivatableWidget The widget to remove. 要移除的控件。
|
||||
*/
|
||||
void FindAndRemoveWidgetFromLayer(UCommonActivatableWidget *ActivatableWidget);
|
||||
|
||||
/**
|
||||
* Retrieves the layer widget for a given layer tag.
|
||||
* 获取给定层标签的层控件。
|
||||
* @param LayerName The tag of the layer to retrieve. 要检索的层标签。
|
||||
* @return Pointer to the layer widget, or nullptr if not found. 层控件指针,未找到时为nullptr。
|
||||
*/
|
||||
UCommonActivatableWidgetContainerBase *GetLayerWidget(FGameplayTag LayerName);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Registers a layer for pushing widgets.
|
||||
* 注册一个用于推送控件的层。
|
||||
* @param LayerTag The tag identifying the layer. 标识层的标签。
|
||||
* @param LayerWidget The widget container for the layer. 层的控件容器。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GUIS")
|
||||
void RegisterLayer(UPARAM(meta = (Categories = "UI.Layer,GUIS.Layer")) FGameplayTag LayerTag, UCommonActivatableWidgetContainerBase *LayerWidget);
|
||||
|
||||
/**
|
||||
* Called when the dormant state changes.
|
||||
* 当休眠状态更改时调用。
|
||||
*/
|
||||
virtual void OnIsDormantChanged();
|
||||
|
||||
/**
|
||||
* Handles widget stack transitioning events.
|
||||
* 处理控件堆栈转换事件。
|
||||
* @param Widget The widget container transitioning. 转换的控件容器。
|
||||
* @param bIsTransitioning True if transitioning, false otherwise. 如果正在转换则为true,否则为false。
|
||||
*/
|
||||
void OnWidgetStackTransitioning(UCommonActivatableWidgetContainerBase *Widget, bool bIsTransitioning);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Indicates if the layout is dormant.
|
||||
* 指示布局是否休眠。
|
||||
*/
|
||||
bool bIsDormant = false;
|
||||
|
||||
/**
|
||||
* Tracks all suspended input tokens for async UI loading.
|
||||
* 跟踪异步UI加载的所有暂停输入令牌。
|
||||
*/
|
||||
TArray<FName> SuspendInputTokens;
|
||||
|
||||
/**
|
||||
* Registered layers for the primary layout.
|
||||
* 主布局的注册层。
|
||||
*/
|
||||
UPROPERTY(Transient, meta = (Categories = "UI.Layer,GUIS.Layer"))
|
||||
TMap<FGameplayTag, TObjectPtr<UCommonActivatableWidgetContainerBase>> Layers;
|
||||
};
|
||||
279
Plugins/GGS/Source/GenericUISystem/Public/UI/GUIS_GameUIPolicy.h
Normal file
279
Plugins/GGS/Source/GenericUISystem/Public/UI/GUIS_GameUIPolicy.h
Normal file
@@ -0,0 +1,279 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GUIS_GameUIStructLibrary.h"
|
||||
#include "Engine/World.h"
|
||||
#include "Engine/DataTable.h"
|
||||
#include "GUIS_GameUIPolicy.generated.h"
|
||||
|
||||
class UCommonUserWidget;
|
||||
class UGUIS_GameUIContext;
|
||||
class ULocalPlayer;
|
||||
class UGUIS_GameUISubsystem;
|
||||
class UGUIS_GameUILayout;
|
||||
|
||||
/**
|
||||
* Enumeration for local multiplayer UI interaction modes.
|
||||
* 本地多人UI交互模式的枚举。
|
||||
*/
|
||||
UENUM()
|
||||
enum class EGUIS_LocalMultiplayerInteractionMode : uint8
|
||||
{
|
||||
PrimaryOnly, // Fullscreen viewport for the primary player only. 仅为主玩家显示全屏视口。
|
||||
SingleToggle, // Fullscreen viewport with player swapping control. 全屏视口,玩家可切换控制。
|
||||
Simultaneous // Simultaneous viewports for all players. 为所有玩家同时显示视口。
|
||||
};
|
||||
|
||||
/**
|
||||
* Manages UI layouts for different local players.
|
||||
* 为不同本地玩家管理UI布局。
|
||||
* @details Controls the creation, addition, and removal of UI layouts and contexts.
|
||||
* @细节 控制UI布局和上下文的创建、添加和移除。
|
||||
*/
|
||||
UCLASS(Abstract, Blueprintable, Within = GUIS_GameUISubsystem)
|
||||
class GENERICUISYSTEM_API UGUIS_GameUIPolicy : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Retrieves the UI policy for the specified world context as a specific type.
|
||||
* 获取指定世界上下文的UI策略,转换为特定类型。
|
||||
* @param WorldContextObject The object providing world context. 提供世界上下文的对象。
|
||||
* @return The UI policy cast to the specified type. 转换为指定类型的UI策略。
|
||||
*/
|
||||
template <typename GameUIPolicyClass = UGUIS_GameUIPolicy>
|
||||
static GameUIPolicyClass* GetGameUIPolicyAs(const UObject* WorldContextObject)
|
||||
{
|
||||
return Cast<GameUIPolicyClass>(GetGameUIPolicy(WorldContextObject));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the UI policy for the specified world context.
|
||||
* 获取指定世界上下文的UI策略。
|
||||
* @param WorldContextObject The object providing world context. 提供世界上下文的对象。
|
||||
* @return The UI policy. UI策略。
|
||||
*/
|
||||
static UGUIS_GameUIPolicy* GetGameUIPolicy(const UObject* WorldContextObject);
|
||||
|
||||
/**
|
||||
* Retrieves the world associated with the UI policy.
|
||||
* 获取与UI策略关联的世界。
|
||||
* @return The associated world. 关联的世界。
|
||||
*/
|
||||
virtual UWorld* GetWorld() const override;
|
||||
|
||||
/**
|
||||
* Retrieves the owning game UI subsystem.
|
||||
* 获取所属的游戏UI子系统。
|
||||
* @return The owning subsystem. 所属子系统。
|
||||
*/
|
||||
UGUIS_GameUISubsystem* GetOwningSubsystem() const;
|
||||
|
||||
/**
|
||||
* Retrieves the root layout for a local player.
|
||||
* 获取本地玩家的根布局。
|
||||
* @param LocalPlayer The local player. 本地玩家。
|
||||
* @return The root layout for the player. 玩家的根布局。
|
||||
*/
|
||||
UGUIS_GameUILayout* GetRootLayout(const ULocalPlayer* LocalPlayer) const;
|
||||
|
||||
/**
|
||||
* Retrieves a UI context for a local player.
|
||||
* 获取本地玩家的UI上下文。
|
||||
* @param LocalPlayer The local player. 本地玩家。
|
||||
* @param ContextClass The class of the context to retrieve. 要检索的上下文类。
|
||||
* @return The UI context, or nullptr if not found. UI上下文,未找到时为nullptr。
|
||||
*/
|
||||
virtual UGUIS_GameUIContext* GetContext(const ULocalPlayer* LocalPlayer, TSubclassOf<UGUIS_GameUIContext> ContextClass);
|
||||
|
||||
/**
|
||||
* Adds a UI context for a local player.
|
||||
* 为本地玩家添加UI上下文。
|
||||
* @param LocalPlayer The local player. 本地玩家。
|
||||
* @param NewContext The UI context to add. 要添加的UI上下文。
|
||||
* @return True if the context was added, false otherwise. 如果上下文添加成功则返回true,否则返回false。
|
||||
*/
|
||||
virtual bool AddContext(const ULocalPlayer* LocalPlayer, UGUIS_GameUIContext* NewContext);
|
||||
|
||||
/**
|
||||
* Finds a UI context for a local player.
|
||||
* 查找本地玩家的UI上下文。
|
||||
* @param LocalPlayer The local player. 本地玩家。
|
||||
* @param ContextClass The class of the context to find. 要查找的上下文类。
|
||||
* @return The found UI context, or nullptr if not found. 找到的UI上下文,未找到时为nullptr。
|
||||
*/
|
||||
virtual UGUIS_GameUIContext* FindContext(const ULocalPlayer* LocalPlayer, TSubclassOf<UGUIS_GameUIContext> ContextClass);
|
||||
|
||||
/**
|
||||
* Removes a UI context for a local player.
|
||||
* 移除本地玩家的UI上下文。
|
||||
* @param LocalPlayer The local player. 本地玩家。
|
||||
* @param ContextClass The class of the context to remove. 要移除的上下文类。
|
||||
*/
|
||||
virtual void RemoveContext(const ULocalPlayer* LocalPlayer, TSubclassOf<UGUIS_GameUIContext> ContextClass);
|
||||
|
||||
/**
|
||||
* Adds a UI action binding for a local player.
|
||||
* 为本地玩家添加UI动作绑定。
|
||||
* @param LocalPlayer The local player. 本地玩家。
|
||||
* @param Target The target widget for the action. 动作的目标控件。
|
||||
* @param InputAction The input action to bind. 要绑定的输入动作。
|
||||
* @param bShouldDisplayInActionBar Whether to display in the action bar. 是否在动作栏中显示。
|
||||
* @param Callback Delegate called when the action is executed. 动作执行时调用的委托。
|
||||
* @param BindingHandle The handle for the binding. 绑定的句柄。
|
||||
*/
|
||||
virtual void AddUIAction(const ULocalPlayer* LocalPlayer, UCommonUserWidget* Target, const FDataTableRowHandle& InputAction, bool bShouldDisplayInActionBar,
|
||||
const FGUIS_UIActionExecutedDelegate& Callback,
|
||||
FGUIS_UIActionBindingHandle& BindingHandle);
|
||||
|
||||
/**
|
||||
* Removes a UI action binding for a local player.
|
||||
* 为本地玩家移除UI动作绑定。
|
||||
* @param LocalPlayer The local player. 本地玩家。
|
||||
* @param BindingHandle The handle of the binding to remove. 要移除的绑定句柄。
|
||||
*/
|
||||
virtual void RemoveUIAction(const ULocalPlayer* LocalPlayer, FGUIS_UIActionBindingHandle& BindingHandle);
|
||||
|
||||
/**
|
||||
* Gets the current multiplayer interaction mode.
|
||||
* 获取当前的多人交互模式。
|
||||
* @return The current interaction mode. 当前交互模式。
|
||||
*/
|
||||
EGUIS_LocalMultiplayerInteractionMode GetLocalMultiplayerInteractionMode() const { return LocalMultiplayerInteractionMode; }
|
||||
|
||||
/**
|
||||
* Requests primary control for a specific layout.
|
||||
* 为特定布局请求主要控制权。
|
||||
* @param Layout The layout requesting primary control. 请求主要控制权的布局。
|
||||
*/
|
||||
void RequestPrimaryControl(UGUIS_GameUILayout* Layout);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Adds a layout to the viewport for a local player.
|
||||
* 为本地玩家将布局添加到视口。
|
||||
* @param LocalPlayer The local player. 本地玩家。
|
||||
* @param Layout The layout to add. 要添加的布局。
|
||||
*/
|
||||
void AddLayoutToViewport(ULocalPlayer* LocalPlayer, UGUIS_GameUILayout* Layout);
|
||||
|
||||
/**
|
||||
* Removes a layout from the viewport for a local player.
|
||||
* 为本地玩家从视口中移除布局。
|
||||
* @param LocalPlayer The local player. 本地玩家。
|
||||
* @param Layout The layout to remove. 要移除的布局。
|
||||
*/
|
||||
void RemoveLayoutFromViewport(ULocalPlayer* LocalPlayer, UGUIS_GameUILayout* Layout);
|
||||
|
||||
/**
|
||||
* Called when a root layout is added to the viewport.
|
||||
* 当根布局添加到视口时调用。
|
||||
* @param LocalPlayer The local player. 本地玩家。
|
||||
* @param Layout The added layout. 添加的布局。
|
||||
*/
|
||||
virtual void OnRootLayoutAddedToViewport(ULocalPlayer* LocalPlayer, UGUIS_GameUILayout* Layout);
|
||||
|
||||
/**
|
||||
* Blueprint event for when a root layout is added to the viewport.
|
||||
* 当根布局添加到视口时的蓝图事件。
|
||||
* @param LocalPlayer The local player. 本地玩家。
|
||||
* @param Layout The added layout. 添加的布局。
|
||||
*/
|
||||
UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "OnRootLayoutAddedToViewport"))
|
||||
void BP_OnRootLayoutAddedToViewport(ULocalPlayer* LocalPlayer, UGUIS_GameUILayout* Layout);
|
||||
|
||||
/**
|
||||
* Called when a root layout is removed from the viewport.
|
||||
* 当根布局从视口中移除时调用。
|
||||
* @param LocalPlayer The local player. 本地玩家。
|
||||
* @param Layout The removed layout. 移除的布局。
|
||||
*/
|
||||
virtual void OnRootLayoutRemovedFromViewport(ULocalPlayer* LocalPlayer, UGUIS_GameUILayout* Layout);
|
||||
|
||||
/**
|
||||
* Blueprint event for when a root layout is removed from the viewport.
|
||||
* 当根布局从视口中移除时的蓝图事件。
|
||||
* @param LocalPlayer The local player. 本地玩家。
|
||||
* @param Layout The removed layout. 移除的布局。
|
||||
*/
|
||||
UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "OnRootLayoutRemovedFromViewport"))
|
||||
void BP_OnRootLayoutRemovedFromViewport(ULocalPlayer* LocalPlayer, UGUIS_GameUILayout* Layout);
|
||||
|
||||
/**
|
||||
* Called when a root layout is released.
|
||||
* 当根布局被释放时调用。
|
||||
* @param LocalPlayer The local player. 本地玩家。
|
||||
* @param Layout The released layout. 释放的布局。
|
||||
*/
|
||||
virtual void OnRootLayoutReleased(ULocalPlayer* LocalPlayer, UGUIS_GameUILayout* Layout);
|
||||
|
||||
/**
|
||||
* Blueprint event for when a root layout is released.
|
||||
* 当根布局被释放时的蓝图事件。
|
||||
* @param LocalPlayer The local player. 本地玩家。
|
||||
* @param Layout The released layout. 释放的布局。
|
||||
*/
|
||||
UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "OnRootLayoutReleased"))
|
||||
void BP_OnRootLayoutReleased(ULocalPlayer* LocalPlayer, UGUIS_GameUILayout* Layout);
|
||||
|
||||
/**
|
||||
* Creates a layout widget for a local player.
|
||||
* 为本地玩家创建布局控件。
|
||||
* @param LocalPlayer The local player. 本地玩家。
|
||||
*/
|
||||
void CreateLayoutWidget(ULocalPlayer* LocalPlayer);
|
||||
|
||||
/**
|
||||
* Retrieves the layout widget class for a local player.
|
||||
* 获取本地玩家的布局控件类。
|
||||
* @param LocalPlayer The local player. 本地玩家。
|
||||
* @return The layout widget class. 布局控件类。
|
||||
*/
|
||||
TSubclassOf<UGUIS_GameUILayout> GetLayoutWidgetClass(ULocalPlayer* LocalPlayer);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Current multiplayer interaction mode.
|
||||
* 当前的多人交互模式。
|
||||
*/
|
||||
EGUIS_LocalMultiplayerInteractionMode LocalMultiplayerInteractionMode = EGUIS_LocalMultiplayerInteractionMode::PrimaryOnly;
|
||||
|
||||
/**
|
||||
* The class used for the game UI layout.
|
||||
* 用于游戏UI布局的类。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category="GUIS")
|
||||
TSoftClassPtr<UGUIS_GameUILayout> LayoutClass;
|
||||
|
||||
/**
|
||||
* Array of root viewport layout information.
|
||||
* 根视口布局信息的数组。
|
||||
*/
|
||||
UPROPERTY(Transient)
|
||||
TArray<FGUIS_RootViewportLayoutInfo> RootViewportLayouts;
|
||||
|
||||
/**
|
||||
* Notifies when a player is added.
|
||||
* 当玩家被添加时通知。
|
||||
* @param LocalPlayer The added local player. 添加的本地玩家。
|
||||
*/
|
||||
void NotifyPlayerAdded(ULocalPlayer* LocalPlayer);
|
||||
|
||||
/**
|
||||
* Notifies when a player is removed.
|
||||
* 当玩家被移除时通知。
|
||||
* @param LocalPlayer The removed local player. 移除的本地玩家。
|
||||
*/
|
||||
void NotifyPlayerRemoved(ULocalPlayer* LocalPlayer);
|
||||
|
||||
/**
|
||||
* Notifies when a player is destroyed.
|
||||
* 当玩家被销毁时通知。
|
||||
* @param LocalPlayer The destroyed local player. 销毁的本地玩家。
|
||||
*/
|
||||
void NotifyPlayerDestroyed(ULocalPlayer* LocalPlayer);
|
||||
|
||||
friend class UGUIS_GameUISubsystem;
|
||||
};
|
||||
@@ -0,0 +1,146 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Input/UIActionBindingHandle.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "GUIS_GameUIStructLibrary.generated.h"
|
||||
|
||||
class UGUIS_GameUIContext;
|
||||
class UGUIS_GameUILayout;
|
||||
class ULocalPlayer;
|
||||
|
||||
/**
|
||||
* Delegate for handling UI action execution.
|
||||
* 处理UI动作执行的委托。
|
||||
* @param ActionName The name of the executed action. 执行的动作名称。
|
||||
*/
|
||||
DECLARE_DYNAMIC_DELEGATE_OneParam(FGUIS_UIActionExecutedDelegate, FName, ActionName);
|
||||
|
||||
/**
|
||||
* Struct for storing UI action binding information.
|
||||
* 存储UI动作绑定信息的结构体。
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct FGUIS_UIActionBindingHandle
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* Unique identifier for the binding.
|
||||
* 绑定的唯一标识符。
|
||||
*/
|
||||
FName Id;
|
||||
|
||||
/**
|
||||
* Handle for the UI action binding.
|
||||
* UI动作绑定的句柄。
|
||||
*/
|
||||
FUIActionBindingHandle Handle;
|
||||
};
|
||||
|
||||
/**
|
||||
* Struct for storing UI context binding information.
|
||||
* 存储UI上下文绑定信息的结构体。
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct FGUIS_UIContextBindingHandle
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
FGUIS_UIContextBindingHandle()
|
||||
{
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor for UI context binding handle.
|
||||
* UI上下文绑定句柄的构造函数。
|
||||
* @param InLocalPlayer The local player associated with the context. 与上下文关联的本地玩家。
|
||||
* @param InContextClass The class of the context. 上下文的类。
|
||||
*/
|
||||
FGUIS_UIContextBindingHandle(ULocalPlayer* InLocalPlayer, UClass* InContextClass);
|
||||
|
||||
/**
|
||||
* The local player associated with the context.
|
||||
* 与上下文关联的本地玩家。
|
||||
*/
|
||||
UPROPERTY()
|
||||
TObjectPtr<ULocalPlayer> LocalPlayer;
|
||||
|
||||
/**
|
||||
* The class of the UI context.
|
||||
* UI上下文的类。
|
||||
*/
|
||||
UPROPERTY()
|
||||
UClass* ContextClass{nullptr};
|
||||
};
|
||||
|
||||
/**
|
||||
* Struct for storing root viewport layout information.
|
||||
* 存储根视口布局信息的结构体。
|
||||
*/
|
||||
USTRUCT()
|
||||
struct FGUIS_RootViewportLayoutInfo
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* The local player associated with the layout.
|
||||
* 与布局关联的本地玩家。
|
||||
*/
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<ULocalPlayer> LocalPlayer = nullptr;
|
||||
|
||||
/**
|
||||
* The root layout widget.
|
||||
* 根布局控件。
|
||||
*/
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UGUIS_GameUILayout> RootLayout = nullptr;
|
||||
|
||||
/**
|
||||
* Indicates if the layout is added to the viewport.
|
||||
* 指示布局是否已添加到视口。
|
||||
*/
|
||||
UPROPERTY(Transient)
|
||||
bool bAddedToViewport = false;
|
||||
|
||||
/**
|
||||
* Array of UI contexts associated with the layout.
|
||||
* 与布局关联的UI上下文数组。
|
||||
*/
|
||||
UPROPERTY(Transient)
|
||||
TArray<TObjectPtr<UGUIS_GameUIContext>> Contexts;
|
||||
|
||||
/**
|
||||
* Array of UI action binding handles.
|
||||
* UI动作绑定句柄的数组。
|
||||
*/
|
||||
UPROPERTY(Transient)
|
||||
TArray<FUIActionBindingHandle> BindingHandles;
|
||||
|
||||
FGUIS_RootViewportLayoutInfo()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for root viewport layout information.
|
||||
* 根视口布局信息的构造函数。
|
||||
* @param InLocalPlayer The local player. 本地玩家。
|
||||
* @param InRootLayout The root layout widget. 根布局控件。
|
||||
* @param bIsInViewport Whether the layout is in the viewport. 布局是否在视口中。
|
||||
*/
|
||||
FGUIS_RootViewportLayoutInfo(ULocalPlayer* InLocalPlayer, UGUIS_GameUILayout* InRootLayout, bool bIsInViewport)
|
||||
: LocalPlayer(InLocalPlayer), RootLayout(InRootLayout), bAddedToViewport(bIsInViewport)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Equality operator to compare with a local player.
|
||||
* 与本地玩家比较的相等运算符。
|
||||
* @param OtherLocalPlayer The local player to compare with. 要比较的本地玩家。
|
||||
* @return True if the local players match, false otherwise. 如果本地玩家匹配则返回true,否则返回false。
|
||||
*/
|
||||
bool operator==(const ULocalPlayer* OtherLocalPlayer) const { return LocalPlayer == OtherLocalPlayer; }
|
||||
};
|
||||
@@ -0,0 +1,223 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Input/UIActionBindingHandle.h"
|
||||
#include "Subsystems/GameInstanceSubsystem.h"
|
||||
#include "GUIS_GameUIStructLibrary.h"
|
||||
#include "UObject/SoftObjectPtr.h"
|
||||
#include "GUIS_GameUISubsystem.generated.h"
|
||||
|
||||
class UCommonUserWidget;
|
||||
class UGUIS_GameUIContext;
|
||||
class FSubsystemCollectionBase;
|
||||
class ULocalPlayer;
|
||||
class UGUIS_GameUIPolicy;
|
||||
class UObject;
|
||||
|
||||
/**
|
||||
* Game UI subsystem for managing UI policies and player UI interactions.
|
||||
* 管理UI策略和玩家UI交互的游戏UI子系统。
|
||||
* @details Intended to be subclassed for game-specific UI functionality.
|
||||
* @细节 旨在为特定游戏的UI功能进行子类化。
|
||||
*/
|
||||
UCLASS()
|
||||
class GENERICUISYSTEM_API UGUIS_GameUISubsystem : public UGameInstanceSubsystem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UGUIS_GameUISubsystem()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the UI subsystem.
|
||||
* 初始化UI子系统。
|
||||
* @param Collection The subsystem collection base. 子系统集合基类。
|
||||
*/
|
||||
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
||||
|
||||
/**
|
||||
* Deinitializes the UI subsystem.
|
||||
* 取消初始化UI子系统。
|
||||
*/
|
||||
virtual void Deinitialize() override;
|
||||
|
||||
/**
|
||||
* Determines if the subsystem should be created.
|
||||
* 确定是否应创建子系统。
|
||||
* @param Outer The outer object. 外部对象。
|
||||
* @return True if the subsystem should be created, false otherwise. 如果应创建子系统则返回true,否则返回false。
|
||||
*/
|
||||
virtual bool ShouldCreateSubsystem(UObject* Outer) const override;
|
||||
|
||||
/**
|
||||
* Gets the current UI policy (const version).
|
||||
* 获取当前UI策略(常量版本)。
|
||||
* @return The current UI policy. 当前UI策略。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GUIS")
|
||||
const UGUIS_GameUIPolicy* GetCurrentUIPolicy() const { return CurrentPolicy; }
|
||||
|
||||
/**
|
||||
* Gets the current UI policy (non-const version).
|
||||
* 获取当前UI策略(非常量版本)。
|
||||
* @return The current UI policy. 当前UI策略。
|
||||
*/
|
||||
UGUIS_GameUIPolicy* GetCurrentUIPolicy() { return CurrentPolicy; }
|
||||
|
||||
/**
|
||||
* Adds a player to the UI subsystem.
|
||||
* 将玩家添加到UI子系统。
|
||||
* @param LocalPlayer The local player to add. 要添加的本地玩家。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GUIS")
|
||||
virtual void AddPlayer(ULocalPlayer* LocalPlayer);
|
||||
|
||||
/**
|
||||
* Removes a player from the UI subsystem.
|
||||
* 从UI子系统中移除玩家。
|
||||
* @param LocalPlayer The local player to remove. 要移除的本地玩家。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GUIS")
|
||||
virtual void RemovePlayer(ULocalPlayer* LocalPlayer);
|
||||
|
||||
/**
|
||||
* Notifies the subsystem when a player is added.
|
||||
* 当玩家被添加时通知子系统。
|
||||
* @param LocalPlayer The added local player. 添加的本地玩家。
|
||||
*/
|
||||
virtual void NotifyPlayerAdded(ULocalPlayer* LocalPlayer);
|
||||
|
||||
/**
|
||||
* Notifies the subsystem when a player is removed.
|
||||
* 当玩家被移除时通知子系统。
|
||||
* @param LocalPlayer The removed local player. 移除的本地玩家。
|
||||
*/
|
||||
virtual void NotifyPlayerRemoved(ULocalPlayer* LocalPlayer);
|
||||
|
||||
/**
|
||||
* Notifies the subsystem when a player is destroyed.
|
||||
* 当玩家被销毁时通知子系统。
|
||||
* @param LocalPlayer The destroyed local player. 销毁的本地玩家。
|
||||
*/
|
||||
virtual void NotifyPlayerDestroyed(ULocalPlayer* LocalPlayer);
|
||||
|
||||
/**
|
||||
* Registers a UI action binding for a widget (deprecated).
|
||||
* 为控件注册UI动作绑定(已弃用)。
|
||||
* @param Target The target widget. 目标控件。
|
||||
* @param InputAction The input action to bind. 要绑定的输入动作。
|
||||
* @param bShouldDisplayInActionBar Whether to display in the action bar. 是否在动作栏中显示。
|
||||
* @param Callback Delegate called when the action is executed. 动作执行时调用的委托。
|
||||
* @param BindingHandle The handle for the binding. 绑定的句柄。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GUIS", meta=(DefaultToSelf="Target", DeprecatedFunction, DeprecationMessage="Use RegisterUIActionBindingForPlayer"))
|
||||
void RegisterUIActionBinding(UCommonUserWidget* Target, FDataTableRowHandle InputAction, bool bShouldDisplayInActionBar, const FGUIS_UIActionExecutedDelegate& Callback,
|
||||
FGUIS_UIActionBindingHandle& BindingHandle);
|
||||
|
||||
/**
|
||||
* Unregisters a UI action binding (deprecated).
|
||||
* 取消注册UI动作绑定(已弃用)。
|
||||
* @param BindingHandle The handle of the binding to unregister. 要取消注册的绑定句柄。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GUIS", meta=(DeprecatedFunction, DeprecationMessage="Use UnregisterUIActionBindingForPlayer"))
|
||||
void UnregisterBinding(UPARAM(ref) FGUIS_UIActionBindingHandle& BindingHandle);
|
||||
|
||||
/**
|
||||
* Registers a UI action binding for a specific player.
|
||||
* 为特定玩家注册UI动作绑定。
|
||||
* @param LocalPlayer The local player. 本地玩家。
|
||||
* @param Target The target widget. 目标控件。
|
||||
* @param InputAction The input action to bind. 要绑定的输入动作。
|
||||
* @param bShouldDisplayInActionBar Whether to display in the action bar. 是否在动作栏中显示。
|
||||
* @param Callback Delegate called when the action is executed. 动作执行时调用的委托。
|
||||
* @param BindingHandle The handle for the binding. 绑定的句柄。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GUIS", meta=(DefaultToSelf="Target"))
|
||||
virtual void RegisterUIActionBindingForPlayer(ULocalPlayer* LocalPlayer, UCommonUserWidget* Target, FDataTableRowHandle InputAction, bool bShouldDisplayInActionBar,
|
||||
const FGUIS_UIActionExecutedDelegate& Callback,
|
||||
FGUIS_UIActionBindingHandle& BindingHandle);
|
||||
|
||||
/**
|
||||
* Unregisters a UI action binding for a specific player.
|
||||
* 为特定玩家取消注册UI动作绑定。
|
||||
* @param LocalPlayer The local player. 本地玩家。
|
||||
* @param BindingHandle The handle of the binding to unregister. 要取消注册的绑定句柄。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GUIS")
|
||||
virtual void UnregisterUIActionBindingForPlayer(ULocalPlayer* LocalPlayer, UPARAM(ref) FGUIS_UIActionBindingHandle& BindingHandle);
|
||||
|
||||
/**
|
||||
* Registers a UI context for a specific player.
|
||||
* 为特定玩家注册UI上下文。
|
||||
* @param LocalPlayer The local player. 本地玩家。
|
||||
* @param Context The UI context to register. 要注册的UI上下文。
|
||||
* @param BindingHandle The handle for the context binding. 上下文绑定的句柄。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GUIS", meta=(DefaultToSelf="LocalPlayer"))
|
||||
void RegisterUIContextForPlayer(ULocalPlayer* LocalPlayer, UGUIS_GameUIContext* Context, FGUIS_UIContextBindingHandle& BindingHandle);
|
||||
|
||||
/**
|
||||
* Registers a UI context for an actor.
|
||||
* 为演员注册UI上下文。
|
||||
* @param Actor The actor to associate with the context. 与上下文关联的演员。
|
||||
* @param Context The UI context to register. 要注册的UI上下文。
|
||||
* @param BindingHandle The handle for the context binding. 上下文绑定的句柄。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GUIS", meta=(DefaultToSelf="Actor"))
|
||||
void RegisterUIContextForActor(AActor* Actor, UGUIS_GameUIContext* Context, FGUIS_UIContextBindingHandle& BindingHandle);
|
||||
|
||||
/**
|
||||
* Finds a UI context for a specific player.
|
||||
* 为特定玩家查找UI上下文。
|
||||
* @param LocalPlayer The local player. 本地玩家。
|
||||
* @param ContextClass The class of the context to find. 要查找的上下文类。
|
||||
* @param OutContext The found UI context (output). 找到的UI上下文(输出)。
|
||||
* @return True if the context was found, false otherwise. 如果找到上下文则返回true,否则返回false。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GUIS", meta=(DefaultToSelf="LocalPlayer", DeterminesOutputType="ContextClass", DynamicOutputParam="OutContext", ExpandBoolAsExecs="ReturnValue"))
|
||||
bool FindUIContextForPlayer(ULocalPlayer* LocalPlayer, TSubclassOf<UGUIS_GameUIContext> ContextClass, UGUIS_GameUIContext*& OutContext);
|
||||
|
||||
/**
|
||||
* Finds a UI context from a binding handle.
|
||||
* 通过绑定句柄查找UI上下文。
|
||||
* @param BindingHandle The binding handle to query. 要查询的绑定句柄。
|
||||
* @param ContextClass The class of the context to find. 要查找的上下文类。
|
||||
* @param OutContext The found UI context (output). 找到的UI上下文(输出)。
|
||||
* @return True if the context was found, false otherwise. 如果找到上下文则返回true,否则返回false。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GUIS", meta=(DefaultToSelf="LocalPlayer", DeterminesOutputType="ContextClass", DynamicOutputParam="OutContext", ExpandBoolAsExecs="ReturnValue"))
|
||||
bool FindUIContextFromHandle(UPARAM(ref) FGUIS_UIContextBindingHandle& BindingHandle, TSubclassOf<UGUIS_GameUIContext> ContextClass, UGUIS_GameUIContext*& OutContext);
|
||||
|
||||
/**
|
||||
* Unregisters a UI context for a specific player.
|
||||
* 为特定玩家取消注册UI上下文。
|
||||
* @param BindingHandle The handle of the context binding to unregister. 要取消注册的上下文绑定句柄。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GUIS", meta=(DefaultToSelf="LocalPlayer"))
|
||||
void UnregisterUIContextForPlayer(UPARAM(ref) FGUIS_UIContextBindingHandle& BindingHandle);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Switches to a specified UI policy.
|
||||
* 切换到指定的UI策略。
|
||||
* @param InPolicy The UI policy to switch to. 要切换到的UI策略。
|
||||
*/
|
||||
void SwitchToPolicy(UGUIS_GameUIPolicy* InPolicy);
|
||||
|
||||
private:
|
||||
/**
|
||||
* The current UI policy in use.
|
||||
* 当前使用的UI策略。
|
||||
*/
|
||||
UPROPERTY(Transient)
|
||||
TObjectPtr<UGUIS_GameUIPolicy> CurrentPolicy = nullptr;
|
||||
|
||||
/**
|
||||
* Array of UI action binding handles.
|
||||
* UI动作绑定句柄的数组。
|
||||
*/
|
||||
TArray<FUIActionBindingHandle> BindingHandles;
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
#include "NativeGameplayTags.h"
|
||||
|
||||
/**
|
||||
* Namespace for modal action gameplay tags.
|
||||
* 模态动作游戏标签的命名空间。
|
||||
*/
|
||||
namespace GUIS_GameModalActionTags
|
||||
{
|
||||
/**
|
||||
* Tag for "Ok" modal action.
|
||||
* "确定"模态动作的标签。
|
||||
*/
|
||||
GENERICUISYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Ok);
|
||||
|
||||
/**
|
||||
* Tag for "Cancel" modal action.
|
||||
* "取消"模态动作的标签。
|
||||
*/
|
||||
GENERICUISYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Cancel);
|
||||
|
||||
/**
|
||||
* Tag for "Yes" modal action.
|
||||
* "是"模态动作的标签。
|
||||
*/
|
||||
GENERICUISYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Yes);
|
||||
|
||||
/**
|
||||
* Tag for "No" modal action.
|
||||
* "否"模态动作的标签。
|
||||
*/
|
||||
GENERICUISYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(No);
|
||||
|
||||
/**
|
||||
* Tag for unknown modal action.
|
||||
* 未知模态动作的标签。
|
||||
*/
|
||||
GENERICUISYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Unknown);
|
||||
}
|
||||
|
||||
/**
|
||||
* Namespace for UI layer gameplay tags.
|
||||
* UI层游戏标签的命名空间。
|
||||
*/
|
||||
namespace GUIS_GameUILayerTags
|
||||
{
|
||||
/**
|
||||
* Tag for modal UI layer.
|
||||
* 模态UI层的标签。
|
||||
*/
|
||||
GENERICUISYSTEM_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Modal);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "UI/Mobile/GUIS_SimulatedInputWidget.h"
|
||||
#include "GUIS_JoystickWidget.generated.h"
|
||||
|
||||
|
||||
class UImage;
|
||||
class UObject;
|
||||
struct FGeometry;
|
||||
struct FPointerEvent;
|
||||
|
||||
/**
|
||||
* A UMG wrapper for the lyra virtual joystick.
|
||||
*
|
||||
* This will calculate a 2D vector clamped between -1 and 1
|
||||
* to input as a key value to the player, simulating a gamepad analog stick.
|
||||
*
|
||||
* This is intended for use with and Enhanced Input player.
|
||||
*/
|
||||
UCLASS()
|
||||
class GENERICUISYSTEM_API UGUIS_JoystickWidget : public UGUIS_SimulatedInputWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UGUIS_JoystickWidget(const FObjectInitializer& ObjectInitializer);
|
||||
|
||||
//~ Begin UUserWidget
|
||||
virtual FReply NativeOnTouchStarted(const FGeometry& InGeometry, const FPointerEvent& InGestureEvent) override;
|
||||
virtual FReply NativeOnTouchMoved(const FGeometry& InGeometry, const FPointerEvent& InGestureEvent) override;
|
||||
virtual FReply NativeOnTouchEnded(const FGeometry& InGeometry, const FPointerEvent& InGestureEvent) override;
|
||||
virtual void NativeOnMouseLeave(const FPointerEvent& InMouseEvent) override;
|
||||
virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override;
|
||||
//~ End UUserWidget interface
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Calculate the delta position of the current touch from the origin.
|
||||
*
|
||||
* Input the associated gamepad key on the player
|
||||
*
|
||||
* Move the foreground joystick image in association with the given input to give the appearance that it
|
||||
* is moving along with the player's finger
|
||||
*/
|
||||
void HandleTouchDelta(const FGeometry& InGeometry, const FPointerEvent& InGestureEvent);
|
||||
|
||||
/** Flush any player input that has been injected and disable the use of this analog stick. */
|
||||
void StopInputSimulation();
|
||||
|
||||
/** How far can the inner image of the joystick be moved? */
|
||||
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category="GUIS")
|
||||
float StickRange = 50.0f;
|
||||
|
||||
/** Image to be used as the background of the joystick */
|
||||
UPROPERTY(BlueprintReadWrite, Category="GUIS", meta = (BindWidget))
|
||||
TObjectPtr<UImage> JoystickBackground;
|
||||
|
||||
/** Image to be used as the foreground of the joystick */
|
||||
UPROPERTY(BlueprintReadWrite, Category="GUIS", meta = (BindWidget))
|
||||
TObjectPtr<UImage> JoystickForeground;
|
||||
|
||||
/** Should we negate the Y-axis value of the joystick? This is common for "movement" sticks */
|
||||
UPROPERTY(BlueprintReadWrite, Category="GUIS", EditAnywhere)
|
||||
bool bNegateYAxis = false;
|
||||
|
||||
/** The origin of the touch. Set on NativeOnTouchStarted */
|
||||
UPROPERTY(Transient)
|
||||
FVector2D TouchOrigin = FVector2D::ZeroVector;
|
||||
|
||||
UPROPERTY(Transient)
|
||||
FVector2D StickVector = FVector2D::ZeroVector;
|
||||
};
|
||||
@@ -0,0 +1,93 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CommonUserWidget.h"
|
||||
#include "InputCoreTypes.h"
|
||||
#include "CommonUserWidget.h"
|
||||
#include "GUIS_SimulatedInputWidget.generated.h"
|
||||
|
||||
class UEnhancedInputLocalPlayerSubsystem;
|
||||
class UInputAction;
|
||||
class UCommonHardwareVisibilityBorder;
|
||||
class UEnhancedPlayerInput;
|
||||
|
||||
/**
|
||||
* A UMG widget with base functionality to inject input (keys or input actions)
|
||||
* to the enhanced input subsystem.
|
||||
*/
|
||||
UCLASS()
|
||||
class GENERICUISYSTEM_API UGUIS_SimulatedInputWidget : public UCommonUserWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UGUIS_SimulatedInputWidget(const FObjectInitializer& ObjectInitializer);
|
||||
|
||||
//~ Begin UWidget
|
||||
#if WITH_EDITOR
|
||||
virtual const FText GetPaletteCategory() override;
|
||||
#endif
|
||||
//~ End UWidget interface
|
||||
|
||||
//~ Begin UUserWidget
|
||||
virtual void NativeConstruct() override;
|
||||
virtual void NativeDestruct() override;
|
||||
virtual FReply NativeOnTouchEnded(const FGeometry& InGeometry, const FPointerEvent& InGestureEvent) override;
|
||||
//~ End UUserWidget interface
|
||||
|
||||
/** Get the enhanced input subsystem based on the owning local player of this widget. Will return null if there is no owning player */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GUIS")
|
||||
UEnhancedInputLocalPlayerSubsystem* GetEnhancedInputSubsystem() const;
|
||||
|
||||
/** Get the current player input from the current input subsystem */
|
||||
UEnhancedPlayerInput* GetPlayerInput() const;
|
||||
|
||||
/** */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GUIS")
|
||||
const UInputAction* GetAssociatedAction() const { return AssociatedAction; }
|
||||
|
||||
/** Returns the current key that will be used to input any values. */
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, Category="GUIS")
|
||||
FKey GetSimulatedKey() const { return KeyToSimulate; }
|
||||
|
||||
/**
|
||||
* Injects the given vector as an input to the current simulated key.
|
||||
* This calls "InputKey" on the current player.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GUIS")
|
||||
void InputKeyValue(const FVector& Value);
|
||||
|
||||
/**
|
||||
* Injects the given vector as an input to the current simulated key.
|
||||
* This calls "InputKey" on the current player.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GUIS")
|
||||
void InputKeyValue2D(const FVector2D& Value);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category="GUIS")
|
||||
void FlushSimulatedInput();
|
||||
|
||||
protected:
|
||||
/** Set the KeyToSimulate based on a query from enhanced input about what keys are mapped to the associated action */
|
||||
void QueryKeyToSimulate();
|
||||
|
||||
/** Called whenever control mappings change, so we have a chance to adapt our own keys */
|
||||
UFUNCTION()
|
||||
void OnControlMappingsRebuilt();
|
||||
|
||||
/** The common visibility border will allow you to specify UI for only specific platforms if desired */
|
||||
UPROPERTY(BlueprintReadWrite, Category="GUIS", meta = (BindWidget))
|
||||
TObjectPtr<UCommonHardwareVisibilityBorder> CommonVisibilityBorder = nullptr;
|
||||
|
||||
/** The associated input action that we should simulate input for */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GUIS")
|
||||
TObjectPtr<const UInputAction> AssociatedAction = nullptr;
|
||||
|
||||
/** The Key to simulate input for in the case where none are currently bound to the associated action */
|
||||
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category="GUIS")
|
||||
FKey FallbackBindingKey = EKeys::Gamepad_Right2D;
|
||||
|
||||
/** The key that should be input via InputKey on the player input */
|
||||
FKey KeyToSimulate;
|
||||
};
|
||||
@@ -0,0 +1,129 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CommonActivatableWidget.h"
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "GUIS_GameModalTypes.h"
|
||||
#include "GUIS_GameModal.generated.h"
|
||||
|
||||
class UCommonTextBlock;
|
||||
class UDynamicEntryBox;
|
||||
class UGUIS_GameModalWidget;
|
||||
|
||||
/**
|
||||
* Definition for a modal dialog.
|
||||
* 模态对话框的定义。
|
||||
*/
|
||||
UCLASS(Abstract, BlueprintType, Blueprintable, Const)
|
||||
class GENERICUISYSTEM_API UGUIS_ModalDefinition : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Header text for the modal.
|
||||
* 模态对话框的标题文本。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category="GUIS", BlueprintReadWrite)
|
||||
FText Header;
|
||||
|
||||
/**
|
||||
* Body text for the modal.
|
||||
* 模态对话框的正文文本。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category="GUIS", BlueprintReadWrite)
|
||||
FText Body;
|
||||
|
||||
/**
|
||||
* Widget class used to represent the modal.
|
||||
* 表示模态对话框的小部件类。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, Category="GUIS", BlueprintReadWrite)
|
||||
TSoftClassPtr<UGUIS_GameModalWidget> ModalWidget;
|
||||
|
||||
/**
|
||||
* Map of modal actions to their configurations.
|
||||
* 模态动作及其配置的映射。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GUIS", meta = (ForceInlineRow, Categories = "GUIS.Modal.Action"))
|
||||
TMap<FGameplayTag, FGUIS_GameModalAction> ModalActions;
|
||||
};
|
||||
|
||||
/**
|
||||
* Base widget for modal dialogs.
|
||||
* 模态对话框的基础小部件。
|
||||
* @note Must bind a DynamicEntryBox named "EntryBox_Buttons" for button registration.
|
||||
* @注意 必须绑定一个名为"EntryBox_Buttons"的DynamicEntryBox以注册按钮。
|
||||
*/
|
||||
UCLASS(Abstract, meta = (Category = "Generic UI"))
|
||||
class GENERICUISYSTEM_API UGUIS_GameModalWidget : public UCommonActivatableWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor for the modal widget.
|
||||
* 模态小部件构造函数。
|
||||
*/
|
||||
UGUIS_GameModalWidget();
|
||||
|
||||
/**
|
||||
* Sets up the modal with the provided definition.
|
||||
* 使用提供的定义设置模态对话框。
|
||||
* @param ModalDefinition The modal definition. 模态定义。
|
||||
* @param ModalActionCallback Callback for modal actions. 模态动作回调。
|
||||
*/
|
||||
virtual void SetupModal(const UGUIS_ModalDefinition* ModalDefinition, FGUIS_ModalActionResultSignature ModalActionCallback);
|
||||
|
||||
/**
|
||||
* Closes the modal with the specified result.
|
||||
* 以指定结果关闭模态对话框。
|
||||
* @param ModalActionResult The modal action result. 模态动作结果。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category="GUIS", meta = (Categories = "UI.Modal.Action,GUIS.Modal.Action"))
|
||||
void CloseModal(FGameplayTag ModalActionResult);
|
||||
|
||||
/**
|
||||
* Terminates the modal.
|
||||
* 终止模态对话框。
|
||||
*/
|
||||
virtual void KillModal();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Event to apply modal definition data to UI elements.
|
||||
* 将模态定义数据应用于UI元素的事件。
|
||||
* @param ModalDefinition The modal definition. 模态定义。
|
||||
*/
|
||||
UFUNCTION(BlueprintImplementableEvent, Category="GUIS")
|
||||
void OnSetupModal(const UGUIS_ModalDefinition* ModalDefinition);
|
||||
|
||||
/**
|
||||
* Callback for modal action results.
|
||||
* 模态动作结果的回调。
|
||||
*/
|
||||
FGUIS_ModalActionResultSignature OnModalActionCallback;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Dynamic entry box for modal buttons.
|
||||
* 模态按钮的动态入口框。
|
||||
*/
|
||||
UPROPERTY(Meta = (BindWidget))
|
||||
TObjectPtr<UDynamicEntryBox> EntryBox_Buttons;
|
||||
|
||||
/**
|
||||
* Text block for the modal header.
|
||||
* 模态标题的文本块。
|
||||
*/
|
||||
UPROPERTY(Meta = (BindWidget))
|
||||
TObjectPtr<UCommonTextBlock> Text_Header;
|
||||
|
||||
/**
|
||||
* Text block for the modal body.
|
||||
* 模态正文的文本块。
|
||||
*/
|
||||
UPROPERTY(Meta = (BindWidget))
|
||||
TObjectPtr<UCommonTextBlock> Text_Body;
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "Subsystems/LocalPlayerSubsystem.h"
|
||||
#include "Engine/DataTable.h"
|
||||
#include "GUIS_GameModalTypes.generated.h"
|
||||
|
||||
class UGUIS_ButtonBase;
|
||||
class FSubsystemCollectionBase;
|
||||
class UGUIS_ModalDefinition;
|
||||
class UObject;
|
||||
|
||||
/**
|
||||
* Delegate for modal action results.
|
||||
* 模态动作结果的委托。
|
||||
*/
|
||||
DECLARE_DELEGATE_OneParam(FGUIS_ModalActionResultSignature, FGameplayTag /* Result */);
|
||||
|
||||
/**
|
||||
* Configuration for a modal action.
|
||||
* 模态动作的配置。
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct GENERICUISYSTEM_API FGUIS_GameModalAction
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* Display text for the modal action.
|
||||
* 模态动作的显示文本。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GUIS")
|
||||
FText DisplayText;
|
||||
|
||||
/**
|
||||
* Button widget class for the modal action.
|
||||
* 模态动作的按钮小部件类。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GUIS", NoClear)
|
||||
TSoftClassPtr<UGUIS_ButtonBase> ButtonType;
|
||||
|
||||
/**
|
||||
* Input action associated with the modal action.
|
||||
* 模态动作关联的输入动作。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="GUIS", meta = (RowType = "/Script/CommonUI.CommonInputActionDataBase"))
|
||||
FDataTableRowHandle InputAction;
|
||||
};
|
||||
@@ -0,0 +1,173 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GUIS_GameUIExtensionSubsystem.h"
|
||||
#include "Components/DynamicEntryBoxBase.h"
|
||||
#include "GUIS_GameUIExtensionPointWidget.generated.h"
|
||||
|
||||
class IWidgetCompilerLog;
|
||||
|
||||
/**
|
||||
* Delegate for retrieving widget class for data.
|
||||
* 获取数据小部件类的委托。
|
||||
*/
|
||||
DECLARE_DYNAMIC_DELEGATE_RetVal_OneParam(TSubclassOf<UUserWidget>, FOnGetWidgetClassForData, UObject*, DataItem);
|
||||
|
||||
/**
|
||||
* Delegate for configuring widget for data.
|
||||
* 配置数据小部件的委托。
|
||||
*/
|
||||
DECLARE_DYNAMIC_DELEGATE_TwoParams(FOnConfigureWidgetForData, UUserWidget*, Widget, UObject*, DataItem);
|
||||
|
||||
/**
|
||||
* Widget representing a UI extension point in a layout.
|
||||
* 表示布局中UI扩展点的小部件。
|
||||
* @note Context is LocalPlayer.
|
||||
* @注意 上下文是LocalPlayer。
|
||||
*/
|
||||
UCLASS(meta=(Category = "Generic UI"))
|
||||
class GENERICUISYSTEM_API UGUIS_GameUIExtensionPointWidget : public UDynamicEntryBoxBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor for the extension point widget.
|
||||
* 扩展点小部件构造函数。
|
||||
*/
|
||||
UGUIS_GameUIExtensionPointWidget(const FObjectInitializer& ObjectInitializer);
|
||||
|
||||
/**
|
||||
* Releases Slate resources.
|
||||
* 释放Slate资源。
|
||||
* @param bReleaseChildren Whether to release child resources. 是否释放子资源。
|
||||
*/
|
||||
virtual void ReleaseSlateResources(bool bReleaseChildren) override;
|
||||
|
||||
/**
|
||||
* Rebuilds the Slate widget.
|
||||
* 重建Slate小部件。
|
||||
* @return The rebuilt Slate widget. 重建的Slate小部件。
|
||||
*/
|
||||
virtual TSharedRef<SWidget> RebuildWidget() override;
|
||||
|
||||
/**
|
||||
* Registers the widget for the player state if ready.
|
||||
* 如果准备好,为玩家状态注册小部件。
|
||||
*/
|
||||
void RegisterForPlayerStateIfReady();
|
||||
|
||||
/**
|
||||
* Checks the player state.
|
||||
* 检查玩家状态。
|
||||
* @return True if player state is valid, false otherwise. 如果玩家状态有效返回true,否则返回false。
|
||||
*/
|
||||
bool CheckPlayerState();
|
||||
|
||||
/**
|
||||
* Called to check the player state.
|
||||
* 检查玩家状态时调用。
|
||||
*/
|
||||
void OnCheckPlayerState();
|
||||
|
||||
/**
|
||||
* Timer handle for player state checks.
|
||||
* 玩家状态检查的定时器句柄。
|
||||
*/
|
||||
FTimerHandle TimerHandle;
|
||||
|
||||
#if WITH_EDITOR
|
||||
/**
|
||||
* Validates compiled defaults in the editor.
|
||||
* 在编辑器中验证编译默认值。
|
||||
* @param CompileLog The widget compiler log. 小部件编译日志。
|
||||
*/
|
||||
virtual void ValidateCompiledDefaults(IWidgetCompilerLog& CompileLog) const override;
|
||||
#endif
|
||||
|
||||
private:
|
||||
/**
|
||||
* Resets the extension point.
|
||||
* 重置扩展点。
|
||||
*/
|
||||
void ResetExtensionPoint();
|
||||
|
||||
/**
|
||||
* Registers the extension point.
|
||||
* 注册扩展点。
|
||||
*/
|
||||
void RegisterExtensionPoint();
|
||||
|
||||
/**
|
||||
* Registers the extension point for a specific player state.
|
||||
* 为特定玩家状态注册扩展点。
|
||||
* @param LocalPlayer The local player. 本地玩家。
|
||||
* @param PlayerState The player state. 玩家状态。
|
||||
*/
|
||||
void RegisterExtensionPointForPlayerState(ULocalPlayer* LocalPlayer, APlayerState* PlayerState);
|
||||
|
||||
/**
|
||||
* Loads allowed data classes.
|
||||
* 加载允许的数据类。
|
||||
* @return The allowed data classes. 允许的数据类。
|
||||
*/
|
||||
TArray<UClass*> LoadAllowedDataClasses() const;
|
||||
|
||||
/**
|
||||
* Called when an extension is added or removed.
|
||||
* 扩展添加或移除时调用。
|
||||
* @param Action The extension action (Added/Removed). 扩展动作(添加/移除)。
|
||||
* @param Request The extension request. 扩展请求。
|
||||
*/
|
||||
void OnAddOrRemoveExtension(EGUIS_GameUIExtAction Action, const FGUIS_GameUIExtRequest& Request);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Tag defining the extension point.
|
||||
* 定义扩展点的标签。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Extension")
|
||||
FGameplayTag ExtensionPointTag;
|
||||
|
||||
/**
|
||||
* Match type for the extension point tag.
|
||||
* 扩展点标签的匹配类型。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Extension")
|
||||
EGUIS_GameUIExtPointMatchType ExtensionPointTagMatch = EGUIS_GameUIExtPointMatchType::ExactMatch;
|
||||
|
||||
/**
|
||||
* Allowed data classes for the extension point.
|
||||
* 扩展点允许的数据类。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Extension")
|
||||
TArray<TSoftClassPtr<UObject>> DataClasses;
|
||||
|
||||
/**
|
||||
* Event to get the widget class for non-widget data.
|
||||
* 为非小部件数据获取小部件类的事件。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="UI Extension", meta=( IsBindableEvent="True" ))
|
||||
FOnGetWidgetClassForData GetWidgetClassForData;
|
||||
|
||||
/**
|
||||
* Event to configure widget instance for non-widget data.
|
||||
* 为非小部件数据配置小部件实例的事件。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="UI Extension", meta=( IsBindableEvent="True" ))
|
||||
FOnConfigureWidgetForData ConfigureWidgetForData;
|
||||
|
||||
/**
|
||||
* Array of extension point handles.
|
||||
* 扩展点句柄数组。
|
||||
*/
|
||||
TArray<FGUIS_GameUIExtPointHandle> ExtensionPointHandles;
|
||||
|
||||
/**
|
||||
* Mapping of extension handles to widgets.
|
||||
* 扩展句柄到小部件的映射。
|
||||
*/
|
||||
UPROPERTY(Transient)
|
||||
TMap<FGUIS_GameUIExtHandle, TObjectPtr<UUserWidget>> ExtensionMapping;
|
||||
};
|
||||
@@ -0,0 +1,617 @@
|
||||
// Copyright 2025 https://yuewu.dev/en All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GameplayTagContainer.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "Subsystems/WorldSubsystem.h"
|
||||
#include "GUIS_GameUIExtensionSubsystem.generated.h"
|
||||
|
||||
class UGUIS_ExtensionSubsystem;
|
||||
struct FGUIS_GameUIExtRequest;
|
||||
template <typename T>
|
||||
class TSubclassOf;
|
||||
template <typename T>
|
||||
class TSoftClassPtr;
|
||||
class FSubsystemCollectionBase;
|
||||
class UUserWidget;
|
||||
struct FFrame;
|
||||
|
||||
/**
|
||||
* Enum defining match rules for UI extension points.
|
||||
* 定义UI扩展点匹配规则的枚举。
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class EGUIS_GameUIExtPointMatchType : uint8
|
||||
{
|
||||
/**
|
||||
* Requires an exact match for the extension point tag.
|
||||
* 要求扩展点标签完全匹配。
|
||||
*/
|
||||
ExactMatch,
|
||||
|
||||
/**
|
||||
* Allows partial matches for the extension point tag.
|
||||
* 允许扩展点标签部分匹配。
|
||||
*/
|
||||
PartialMatch
|
||||
};
|
||||
|
||||
/**
|
||||
* Enum defining actions for UI extensions.
|
||||
* 定义UI扩展动作的枚举。
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class EGUIS_GameUIExtAction : uint8
|
||||
{
|
||||
/**
|
||||
* Extension is added.
|
||||
* 扩展被添加。
|
||||
*/
|
||||
Added,
|
||||
|
||||
/**
|
||||
* Extension is removed.
|
||||
* 扩展被移除。
|
||||
*/
|
||||
Removed
|
||||
};
|
||||
|
||||
/**
|
||||
* Delegate for extension point events.
|
||||
* 扩展点事件的委托。
|
||||
*/
|
||||
DECLARE_DELEGATE_TwoParams(FExtendExtensionPointDelegate, EGUIS_GameUIExtAction Action, const FGUIS_GameUIExtRequest& Request);
|
||||
|
||||
/**
|
||||
* Structure representing a UI extension.
|
||||
* 表示UI扩展的结构。
|
||||
*/
|
||||
struct FGUIS_GameUIExt : TSharedFromThis<FGUIS_GameUIExt>
|
||||
{
|
||||
/**
|
||||
* Tag identifying the extension point.
|
||||
* 标识扩展点的标签。
|
||||
*/
|
||||
FGameplayTag ExtensionPointTag;
|
||||
|
||||
/**
|
||||
* Priority of the extension.
|
||||
* 扩展的优先级。
|
||||
*/
|
||||
int32 Priority = INDEX_NONE;
|
||||
|
||||
/**
|
||||
* Context object for the extension.
|
||||
* 扩展的上下文对象。
|
||||
*/
|
||||
TWeakObjectPtr<UObject> ContextObject;
|
||||
|
||||
/**
|
||||
* Data object for the extension, kept alive by the subsystem.
|
||||
* 扩展的数据对象,由子系统保持存活。
|
||||
*/
|
||||
TObjectPtr<UObject> Data = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
* Structure representing a UI extension point.
|
||||
* 表示UI扩展点的结构。
|
||||
*/
|
||||
struct FGUIS_GameUIExtPoint : TSharedFromThis<FGUIS_GameUIExtPoint>
|
||||
{
|
||||
/**
|
||||
* Tag identifying the extension point.
|
||||
* 标识扩展点的标签。
|
||||
*/
|
||||
FGameplayTag ExtensionPointTag;
|
||||
|
||||
/**
|
||||
* Context object for the extension point.
|
||||
* 扩展点的上下文对象。
|
||||
*/
|
||||
TWeakObjectPtr<UObject> ContextObject;
|
||||
|
||||
/**
|
||||
* Match type for the extension point tag.
|
||||
* 扩展点标签的匹配类型。
|
||||
*/
|
||||
EGUIS_GameUIExtPointMatchType ExtensionPointTagMatchType = EGUIS_GameUIExtPointMatchType::ExactMatch;
|
||||
|
||||
/**
|
||||
* Allowed data classes for the extension point.
|
||||
* 扩展点允许的数据类。
|
||||
*/
|
||||
TArray<TObjectPtr<UClass>> AllowedDataClasses;
|
||||
|
||||
/**
|
||||
* Callback for extension point events.
|
||||
* 扩展点事件的回调。
|
||||
*/
|
||||
FExtendExtensionPointDelegate Callback;
|
||||
|
||||
/**
|
||||
* Checks if an extension matches the extension point.
|
||||
* 检查扩展是否与扩展点匹配。
|
||||
* @param Extension The extension to check. 要检查的扩展。
|
||||
* @return True if the extension matches, false otherwise. 如果扩展匹配返回true,否则返回false。
|
||||
*/
|
||||
bool DoesExtensionPassContract(const FGUIS_GameUIExt* Extension) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle for a UI extension point.
|
||||
* UI扩展点的句柄。
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct GENERICUISYSTEM_API FGUIS_GameUIExtPointHandle
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
* 默认构造函数。
|
||||
*/
|
||||
FGUIS_GameUIExtPointHandle();
|
||||
|
||||
/**
|
||||
* Unregisters the extension point.
|
||||
* 注销扩展点。
|
||||
*/
|
||||
void Unregister();
|
||||
|
||||
/**
|
||||
* Checks if the handle is valid.
|
||||
* 检查句柄是否有效。
|
||||
* @return True if valid, false otherwise. 如果有效返回true,否则返回false。
|
||||
*/
|
||||
bool IsValid() const { return DataPtr.IsValid(); }
|
||||
|
||||
/**
|
||||
* Equality operator for extension point handles.
|
||||
* 扩展点句柄的相等比较运算符。
|
||||
*/
|
||||
bool operator==(const FGUIS_GameUIExtPointHandle& Other) const { return DataPtr == Other.DataPtr; }
|
||||
|
||||
/**
|
||||
* Inequality operator for extension point handles.
|
||||
* 扩展点句柄的不等比较运算符。
|
||||
*/
|
||||
bool operator!=(const FGUIS_GameUIExtPointHandle& Other) const { return !operator==(Other); }
|
||||
|
||||
/**
|
||||
* Hash function for the extension point handle.
|
||||
* 扩展点句柄的哈希函数。
|
||||
* @param Handle The handle to hash. 要哈希的句柄。
|
||||
* @return The hash value. 哈希值。
|
||||
*/
|
||||
friend uint32 GetTypeHash(const FGUIS_GameUIExtPointHandle& Handle)
|
||||
{
|
||||
return PointerHash(Handle.DataPtr.Get());
|
||||
};
|
||||
|
||||
private:
|
||||
/**
|
||||
* The extension subsystem source.
|
||||
* 扩展子系统源。
|
||||
*/
|
||||
TWeakObjectPtr<UGUIS_ExtensionSubsystem> ExtensionSource;
|
||||
|
||||
/**
|
||||
* Shared pointer to the extension point data.
|
||||
* 扩展点数据的共享指针。
|
||||
*/
|
||||
TSharedPtr<FGUIS_GameUIExtPoint> DataPtr;
|
||||
|
||||
friend UGUIS_ExtensionSubsystem;
|
||||
|
||||
/**
|
||||
* Constructor for the extension point handle.
|
||||
* 扩展点句柄构造函数。
|
||||
* @param InExtensionSource The extension subsystem source. 扩展子系统源。
|
||||
* @param InDataPtr The extension point data. 扩展点数据。
|
||||
*/
|
||||
FGUIS_GameUIExtPointHandle(UGUIS_ExtensionSubsystem* InExtensionSource, const TSharedPtr<FGUIS_GameUIExtPoint>& InDataPtr);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct TStructOpsTypeTraits<FGUIS_GameUIExtPointHandle> : TStructOpsTypeTraitsBase2<FGUIS_GameUIExtPointHandle>
|
||||
{
|
||||
enum
|
||||
{
|
||||
WithCopy = true,
|
||||
WithIdenticalViaEquality = true,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle for a UI extension.
|
||||
* UI扩展的句柄。
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct GENERICUISYSTEM_API FGUIS_GameUIExtHandle
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
* 默认构造函数。
|
||||
*/
|
||||
FGUIS_GameUIExtHandle();
|
||||
|
||||
/**
|
||||
* Unregisters the extension.
|
||||
* 注销扩展。
|
||||
*/
|
||||
void Unregister();
|
||||
|
||||
/**
|
||||
* Checks if the handle is valid.
|
||||
* 检查句柄是否有效。
|
||||
* @return True if valid, false otherwise. 如果有效返回true,否则返回false。
|
||||
*/
|
||||
bool IsValid() const { return DataPtr.IsValid(); }
|
||||
|
||||
/**
|
||||
* Equality operator for extension handles.
|
||||
* 扩展句柄的相等比较运算符。
|
||||
*/
|
||||
bool operator==(const FGUIS_GameUIExtHandle& Other) const { return DataPtr == Other.DataPtr; }
|
||||
|
||||
/**
|
||||
* Inequality operator for extension handles.
|
||||
* 扩展句柄的不等比较运算符。
|
||||
*/
|
||||
bool operator!=(const FGUIS_GameUIExtHandle& Other) const { return !operator==(Other); }
|
||||
|
||||
/**
|
||||
* Hash function for the extension handle.
|
||||
* 扩展句柄的哈希函数。
|
||||
* @param Handle The handle to hash. 要哈希的句柄。
|
||||
* @return The hash value. 哈希值。
|
||||
*/
|
||||
friend uint32 GetTypeHash(FGUIS_GameUIExtHandle Handle) { return PointerHash(Handle.DataPtr.Get()); };
|
||||
|
||||
private:
|
||||
/**
|
||||
* The extension subsystem source.
|
||||
* 扩展子系统源。
|
||||
*/
|
||||
TWeakObjectPtr<UGUIS_ExtensionSubsystem> ExtensionSource;
|
||||
|
||||
/**
|
||||
* Shared pointer to the extension data.
|
||||
* 扩展数据的共享指针。
|
||||
*/
|
||||
TSharedPtr<FGUIS_GameUIExt> DataPtr;
|
||||
|
||||
friend UGUIS_ExtensionSubsystem;
|
||||
|
||||
/**
|
||||
* Constructor for the extension handle.
|
||||
* 扩展句柄构造函数。
|
||||
* @param InExtensionSource The extension subsystem source. 扩展子系统源。
|
||||
* @param InDataPtr The extension data. 扩展数据。
|
||||
*/
|
||||
FGUIS_GameUIExtHandle(UGUIS_ExtensionSubsystem* InExtensionSource, const TSharedPtr<FGUIS_GameUIExt>& InDataPtr);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct TStructOpsTypeTraits<FGUIS_GameUIExtHandle> : TStructOpsTypeTraitsBase2<FGUIS_GameUIExtHandle>
|
||||
{
|
||||
enum
|
||||
{
|
||||
WithCopy = true,
|
||||
WithIdenticalViaEquality = true,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Structure representing a UI extension request.
|
||||
* 表示UI扩展请求的结构。
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct FGUIS_GameUIExtRequest
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/**
|
||||
* Handle for the extension.
|
||||
* 扩展的句柄。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GUIS")
|
||||
FGUIS_GameUIExtHandle ExtensionHandle;
|
||||
|
||||
/**
|
||||
* Tag identifying the extension point.
|
||||
* 标识扩展点的标签。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GUIS")
|
||||
FGameplayTag ExtensionPointTag;
|
||||
|
||||
/**
|
||||
* Priority of the extension.
|
||||
* 扩展的优先级。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GUIS")
|
||||
int32 Priority = INDEX_NONE;
|
||||
|
||||
/**
|
||||
* Data object for the extension.
|
||||
* 扩展的数据对象。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GUIS")
|
||||
TObjectPtr<UObject> Data = nullptr;
|
||||
|
||||
/**
|
||||
* Context object for the extension.
|
||||
* 扩展的上下文对象。
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="GUIS")
|
||||
TObjectPtr<UObject> ContextObject = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
* Dynamic delegate for extension point events.
|
||||
* 扩展点事件的动态委托。
|
||||
*/
|
||||
DECLARE_DYNAMIC_DELEGATE_TwoParams(FExtendExtensionPointDynamicDelegate, EGUIS_GameUIExtAction, Action, const FGUIS_GameUIExtRequest&, ExtensionRequest);
|
||||
|
||||
/**
|
||||
* World subsystem for managing UI extensions.
|
||||
* 管理UI扩展的世界子系统。
|
||||
*/
|
||||
UCLASS()
|
||||
class GENERICUISYSTEM_API UGUIS_ExtensionSubsystem : public UWorldSubsystem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Registers an extension point.
|
||||
* 注册扩展点。
|
||||
* @param ExtensionPointTag The extension point tag. 扩展点标签。
|
||||
* @param ExtensionPointTagMatchType The match type for the tag. 标签匹配类型。
|
||||
* @param AllowedDataClasses The allowed data classes. 允许的数据类。
|
||||
* @param ExtensionCallback The callback for extension events. 扩展事件回调。
|
||||
* @return The extension point handle. 扩展点句柄。
|
||||
*/
|
||||
FGUIS_GameUIExtPointHandle RegisterExtensionPoint(const FGameplayTag& ExtensionPointTag, EGUIS_GameUIExtPointMatchType ExtensionPointTagMatchType, const TArray<UClass*>& AllowedDataClasses,
|
||||
FExtendExtensionPointDelegate ExtensionCallback);
|
||||
|
||||
/**
|
||||
* Registers an extension point for a specific context.
|
||||
* 为特定上下文注册扩展点。
|
||||
* @param ExtensionPointTag The extension point tag. 扩展点标签。
|
||||
* @param ContextObject The context object. 上下文对象。
|
||||
* @param ExtensionPointTagMatchType The match type for the tag. 标签匹配类型。
|
||||
* @param AllowedDataClasses The allowed data classes. 允许的数据类。
|
||||
* @param ExtensionCallback The callback for extension events. 扩展事件回调。
|
||||
* @return The extension point handle. 扩展点句柄。
|
||||
*/
|
||||
FGUIS_GameUIExtPointHandle RegisterExtensionPointForContext(const FGameplayTag& ExtensionPointTag, UObject* ContextObject, EGUIS_GameUIExtPointMatchType ExtensionPointTagMatchType,
|
||||
const TArray<UClass*>& AllowedDataClasses, FExtendExtensionPointDelegate ExtensionCallback);
|
||||
|
||||
/**
|
||||
* Registers a widget as a UI extension.
|
||||
* 将小部件注册为UI扩展。
|
||||
* @param ExtensionPointTag The extension point tag. 扩展点标签。
|
||||
* @param WidgetClass The widget class. 小部件类。
|
||||
* @param Priority The extension priority. 扩展优先级。
|
||||
* @return The extension handle. 扩展句柄。
|
||||
*/
|
||||
FGUIS_GameUIExtHandle RegisterExtensionAsWidget(const FGameplayTag& ExtensionPointTag, TSubclassOf<UUserWidget> WidgetClass, int32 Priority);
|
||||
|
||||
/**
|
||||
* Registers a widget as a UI extension for a specific context.
|
||||
* 为特定上下文将小部件注册为UI扩展。
|
||||
* @param ExtensionPointTag The extension point tag. 扩展点标签。
|
||||
* @param ContextObject The context object. 上下文对象。
|
||||
* @param WidgetClass The widget class. 小部件类。
|
||||
* @param Priority The extension priority. 扩展优先级。
|
||||
* @return The extension handle. 扩展句柄。
|
||||
*/
|
||||
FGUIS_GameUIExtHandle RegisterExtensionAsWidgetForContext(const FGameplayTag& ExtensionPointTag, UObject* ContextObject, TSubclassOf<UUserWidget> WidgetClass, int32 Priority);
|
||||
|
||||
/**
|
||||
* Registers data as a UI extension.
|
||||
* 将数据注册为UI扩展。
|
||||
* @param ExtensionPointTag The extension point tag. 扩展点标签。
|
||||
* @param ContextObject The context object. 上下文对象。
|
||||
* @param Data The data object. 数据对象。
|
||||
* @param Priority The extension priority. 扩展优先级。
|
||||
* @return The extension handle. 扩展句柄。
|
||||
*/
|
||||
FGUIS_GameUIExtHandle RegisterExtensionAsData(const FGameplayTag& ExtensionPointTag, UObject* ContextObject, UObject* Data, int32 Priority);
|
||||
|
||||
/**
|
||||
* Unregisters a UI extension.
|
||||
* 注销UI扩展。
|
||||
* @param ExtensionHandle The extension handle. 扩展句柄。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "UI Extension")
|
||||
void UnregisterExtension(const FGUIS_GameUIExtHandle& ExtensionHandle);
|
||||
|
||||
/**
|
||||
* Unregisters a UI extension point.
|
||||
* 注销UI扩展点。
|
||||
* @param ExtensionPointHandle The extension point handle. 扩展点句柄。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "UI Extension")
|
||||
void UnregisterExtensionPoint(const FGUIS_GameUIExtPointHandle& ExtensionPointHandle);
|
||||
|
||||
/**
|
||||
* Adds referenced objects to the garbage collector.
|
||||
* 将引用的对象添加到垃圾回收器。
|
||||
* @param InThis The subsystem instance. 子系统实例。
|
||||
* @param Collector The reference collector. 引用收集器。
|
||||
*/
|
||||
static void AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Initializes the subsystem.
|
||||
* 初始化子系统。
|
||||
* @param Collection The subsystem collection. 子系统集合。
|
||||
*/
|
||||
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
||||
|
||||
/**
|
||||
* Deinitializes the subsystem.
|
||||
* 反初始化子系统。
|
||||
*/
|
||||
virtual void Deinitialize() override;
|
||||
|
||||
/**
|
||||
* Notifies an extension point of new or removed extensions.
|
||||
* 通知扩展点有关新添加或移除的扩展。
|
||||
* @param ExtensionPoint The extension point to notify. 要通知的扩展点。
|
||||
*/
|
||||
void NotifyExtensionPointOfExtensions(TSharedPtr<FGUIS_GameUIExtPoint>& ExtensionPoint);
|
||||
|
||||
/**
|
||||
* Notifies extension points of an extension action.
|
||||
* 通知扩展点有关扩展动作。
|
||||
* @param Action The extension action (Added/Removed). 扩展动作(添加/移除)。
|
||||
* @param Extension The extension data. 扩展数据。
|
||||
*/
|
||||
void NotifyExtensionPointsOfExtension(EGUIS_GameUIExtAction Action, TSharedPtr<FGUIS_GameUIExt>& Extension);
|
||||
|
||||
/**
|
||||
* Registers an extension point (Blueprint version).
|
||||
* 注册扩展点(蓝图版本)。
|
||||
* @param ExtensionPointTag The extension point tag. 扩展点标签。
|
||||
* @param ExtensionPointTagMatchType The match type for the tag. 标签匹配类型。
|
||||
* @param AllowedDataClasses The allowed data classes. 允许的数据类。
|
||||
* @param ExtensionCallback The callback for extension events. 扩展事件回调。
|
||||
* @return The extension point handle. 扩展点句柄。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="UI Extension", meta = (DisplayName = "Register Extension Point"))
|
||||
FGUIS_GameUIExtPointHandle K2_RegisterExtensionPoint(FGameplayTag ExtensionPointTag, EGUIS_GameUIExtPointMatchType ExtensionPointTagMatchType,
|
||||
const TArray<TSoftClassPtr<UClass>>& AllowedDataClasses,
|
||||
FExtendExtensionPointDynamicDelegate ExtensionCallback);
|
||||
|
||||
/**
|
||||
* Registers a widget as a UI extension (Blueprint version).
|
||||
* 将小部件注册为UI扩展(蓝图版本)。
|
||||
* @param ExtensionPointTag The extension point tag. 扩展点标签。
|
||||
* @param WidgetClass The widget class. 小部件类。
|
||||
* @param Priority The extension priority. 扩展优先级。
|
||||
* @return The extension handle. 扩展句柄。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "UI Extension", meta = (DisplayName = "Register Extension (Widget)"))
|
||||
FGUIS_GameUIExtHandle K2_RegisterExtensionAsWidget(FGameplayTag ExtensionPointTag, TSoftClassPtr<UUserWidget> WidgetClass, int32 Priority = -1);
|
||||
|
||||
/**
|
||||
* Registers a widget as a UI extension for a specific context (Blueprint version).
|
||||
* 为特定上下文将小部件注册为UI扩展(蓝图版本)。
|
||||
* @param ExtensionPointTag The extension point tag. 扩展点标签。
|
||||
* @param WidgetClass The widget class. 小部件类。
|
||||
* @param ContextObject The context object. 上下文对象。
|
||||
* @param Priority The extension priority. 扩展优先级。
|
||||
* @return The extension handle. 扩展句柄。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "UI Extension", meta = (DisplayName = "Register Extension (Widget For Context)"))
|
||||
FGUIS_GameUIExtHandle K2_RegisterExtensionAsWidgetForContext(FGameplayTag ExtensionPointTag, TSoftClassPtr<UUserWidget> WidgetClass, UObject* ContextObject, int32 Priority = -1);
|
||||
|
||||
/**
|
||||
* Registers data as a UI extension (Blueprint version).
|
||||
* 将数据注册为UI扩展(蓝图版本)。
|
||||
* @param ExtensionPointTag The extension point tag. 扩展点标签。
|
||||
* @param Data The data object. 数据对象。
|
||||
* @param Priority The extension priority. 扩展优先级。
|
||||
* @return The extension handle. 扩展句柄。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="UI Extension", meta = (DisplayName = "Register Extension (Data)"))
|
||||
FGUIS_GameUIExtHandle K2_RegisterExtensionAsData(FGameplayTag ExtensionPointTag, UObject* Data, int32 Priority = -1);
|
||||
|
||||
/**
|
||||
* Registers data as a UI extension for a specific context (Blueprint version).
|
||||
* 为特定上下文将数据注册为UI扩展(蓝图版本)。
|
||||
* @param ExtensionPointTag The extension point tag. 扩展点标签。
|
||||
* @param ContextObject The context object. 上下文对象。
|
||||
* @param Data The data object. 数据对象。
|
||||
* @param Priority The extension priority. 扩展优先级。
|
||||
* @return The extension handle. 扩展句柄。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="UI Extension", meta = (DisplayName = "Register Extension (Data For Context)"))
|
||||
FGUIS_GameUIExtHandle K2_RegisterExtensionAsDataForContext(FGameplayTag ExtensionPointTag, UObject* ContextObject, UObject* Data, int32 Priority = -1);
|
||||
|
||||
/**
|
||||
* Creates an extension request from extension data.
|
||||
* 从扩展数据创建扩展请求。
|
||||
* @param Extension The extension data. 扩展数据。
|
||||
* @return The extension request. 扩展请求。
|
||||
*/
|
||||
FGUIS_GameUIExtRequest CreateExtensionRequest(const TSharedPtr<FGUIS_GameUIExt>& Extension);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Map of extension point tags to extension point lists.
|
||||
* 扩展点标签到扩展点列表的映射。
|
||||
*/
|
||||
using FExtensionPointList = TArray<TSharedPtr<FGUIS_GameUIExtPoint>>;
|
||||
TMap<FGameplayTag, FExtensionPointList> ExtensionPointMap;
|
||||
|
||||
/**
|
||||
* Map of extension tags to extension lists.
|
||||
* 扩展标签到扩展列表的映射。
|
||||
*/
|
||||
using FExtensionList = TArray<TSharedPtr<FGUIS_GameUIExt>>;
|
||||
TMap<FGameplayTag, FExtensionList> ExtensionMap;
|
||||
};
|
||||
|
||||
/**
|
||||
* Blueprint function library for UI extension operations.
|
||||
* UI扩展操作的蓝图函数库。
|
||||
*/
|
||||
UCLASS()
|
||||
class GENERICUISYSTEM_API UGUIS_ExtensionFunctionLibrary : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Default constructor.
|
||||
* 默认构造函数。
|
||||
*/
|
||||
UGUIS_ExtensionFunctionLibrary();
|
||||
|
||||
/**
|
||||
* Unregisters a UI extension.
|
||||
* 注销UI扩展。
|
||||
* @param Handle The extension handle. 扩展句柄。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "UI Extension")
|
||||
static void UnregisterExtension(UPARAM(ref)
|
||||
FGUIS_GameUIExtHandle& Handle);
|
||||
|
||||
/**
|
||||
* Checks if a UI extension is valid.
|
||||
* 检查UI扩展是否有效。
|
||||
* @param Handle The extension handle. 扩展句柄。
|
||||
* @return True if valid, false otherwise. 如果有效返回true,否则返回false。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, BlueprintCosmetic, Category = "UI Extension")
|
||||
static bool IsValidExtension(UPARAM(ref)
|
||||
FGUIS_GameUIExtHandle& Handle);
|
||||
|
||||
/**
|
||||
* Unregisters a UI extension point.
|
||||
* 注销UI扩展点。
|
||||
* @param Handle The extension point handle. 扩展点句柄。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "UI Extension")
|
||||
static void UnregisterExtensionPoint(UPARAM(ref)
|
||||
FGUIS_GameUIExtPointHandle& Handle);
|
||||
|
||||
/**
|
||||
* Checks if a UI extension point is valid.
|
||||
* 检查UI扩展点是否有效。
|
||||
* @param Handle The extension point handle. 扩展点句柄。
|
||||
* @return True if valid, false otherwise. 如果有效返回true,否则返回false。
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, BlueprintPure, BlueprintCosmetic, Category = "UI Extension")
|
||||
static bool IsValidExtensionPoint(UPARAM(ref)
|
||||
FGUIS_GameUIExtPointHandle& Handle);
|
||||
};
|
||||
Reference in New Issue
Block a user