// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CommonUserWidget.h" #include "Widgets/CommonActivatableWidgetContainer.h" #include "GameplayTagContainer.h" #include "Engine/AssetManager.h" #include "CommonUIExtensions.h" #include "PrimaryGameLayout.generated.h" class UCommonActivatableWidget; class UCommonActivatableWidgetStack; class UCommonActivatableWidgetContainerBase; /** * The state of an async load operation for the UI. */ enum class EAsyncWidgetLayerState : uint8 { Canceled, Initialize, AfterPush }; /** * The primary game UI layout of your game. This widget class represents how to layout, push and display all layers * of the UI for a single player. Each player in a split-screen game will receive their own primary game layout. */ UCLASS(Abstract, meta = (DisableNativeTick)) class COMMONGAME_API UPrimaryGameLayout : public UCommonUserWidget { GENERATED_BODY() public: static UPrimaryGameLayout* GetPrimaryGameLayoutForPrimaryPlayer(const UObject* WorldContextObject); static UPrimaryGameLayout* GetPrimaryGameLayout(APlayerController* PlayerController); static UPrimaryGameLayout* GetPrimaryGameLayout(ULocalPlayer* LocalPlayer); public: UPrimaryGameLayout(const FObjectInitializer& ObjectInitializer); /** A dormant root layout is collapsed and responds only to persistent actions registered by the owning player */ void SetIsDormant(bool Dormant); bool IsDormant() const { return bIsDormant; } public: template TSharedPtr PushWidgetToLayerStackAsync(FGameplayTag LayerName, bool bSuspendInputUntilComplete, TSoftClassPtr ActivatableWidgetClass) { return PushWidgetToLayerStackAsync(LayerName, bSuspendInputUntilComplete, ActivatableWidgetClass, [](EAsyncWidgetLayerState, ActivatableWidgetT*) {}); } template TSharedPtr PushWidgetToLayerStackAsync(FGameplayTag LayerName, bool bSuspendInputUntilComplete, TSoftClassPtr ActivatableWidgetClass, TFunction StateFunc) { static_assert(TIsDerivedFrom::IsDerived, "Only CommonActivatableWidgets can be used here"); static FName NAME_PushingWidgetToLayer("PushingWidgetToLayer"); const FName SuspendInputToken = bSuspendInputUntilComplete ? UCommonUIExtensions::SuspendInputForPlayer(GetOwningPlayer(), NAME_PushingWidgetToLayer) : NAME_None; FStreamableManager& StreamableManager = UAssetManager::Get().GetStreamableManager(); TSharedPtr StreamingHandle = StreamableManager.RequestAsyncLoad(ActivatableWidgetClass.ToSoftObjectPath(), FStreamableDelegate::CreateWeakLambda(this, [this, LayerName, ActivatableWidgetClass, StateFunc, SuspendInputToken]() { UCommonUIExtensions::ResumeInputForPlayer(GetOwningPlayer(), SuspendInputToken); ActivatableWidgetT* Widget = PushWidgetToLayerStack(LayerName, ActivatableWidgetClass.Get(), [StateFunc](ActivatableWidgetT& WidgetToInit) { StateFunc(EAsyncWidgetLayerState::Initialize, &WidgetToInit); }); StateFunc(EAsyncWidgetLayerState::AfterPush, Widget); }) ); // Setup a cancel delegate so that we can resume input if this handler is canceled. StreamingHandle->BindCancelDelegate(FStreamableDelegate::CreateWeakLambda(this, [this, StateFunc, SuspendInputToken]() { UCommonUIExtensions::ResumeInputForPlayer(GetOwningPlayer(), SuspendInputToken); StateFunc(EAsyncWidgetLayerState::Canceled, nullptr); }) ); return StreamingHandle; } template ActivatableWidgetT* PushWidgetToLayerStack(FGameplayTag LayerName, UClass* ActivatableWidgetClass) { return PushWidgetToLayerStack(LayerName, ActivatableWidgetClass, [](ActivatableWidgetT&) {}); } template ActivatableWidgetT* PushWidgetToLayerStack(FGameplayTag LayerName, UClass* ActivatableWidgetClass, TFunctionRef InitInstanceFunc) { static_assert(TIsDerivedFrom::IsDerived, "Only CommonActivatableWidgets can be used here"); if (UCommonActivatableWidgetContainerBase* Layer = GetLayerWidget(LayerName)) { return Layer->AddWidget(ActivatableWidgetClass, InitInstanceFunc); } return nullptr; } // Find the widget if it exists on any of the layers and remove it from the layer. void FindAndRemoveWidgetFromLayer(UCommonActivatableWidget* ActivatableWidget); // Get the layer widget for the given layer tag. UCommonActivatableWidgetContainerBase* GetLayerWidget(FGameplayTag LayerName); protected: /** Register a layer that widgets can be pushed onto. */ UFUNCTION(BlueprintCallable, Category="Layer") void RegisterLayer(UPARAM(meta = (Categories = "UI.Layer")) FGameplayTag LayerTag, UCommonActivatableWidgetContainerBase* LayerWidget); virtual void OnIsDormantChanged(); void OnWidgetStackTransitioning(UCommonActivatableWidgetContainerBase* Widget, bool bIsTransitioning); private: bool bIsDormant = false; // Lets us keep track of all suspended input tokens so that multiple async UIs can be loading and we correctly suspend // for the duration of all of them. TArray SuspendInputTokens; // The registered layers for the primary layout. UPROPERTY(Transient, meta = (Categories = "UI.Layer")) TMap Layers; };