// 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 TSharedPtr PushWidgetToLayerStackAsync(FGameplayTag LayerName, bool bSuspendInputUntilComplete, TSoftClassPtr ActivatableWidgetClass) { return PushWidgetToLayerStackAsync(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 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 ? UGUIS_GameUIFunctionLibrary::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]() { UGUIS_GameUIFunctionLibrary::ResumeInputForPlayer(GetOwningPlayer(), SuspendInputToken); ActivatableWidgetT *Widget = PushWidgetToLayerStack( 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 ActivatableWidgetT *PushWidgetToLayerStack(FGameplayTag LayerName, UClass *ActivatableWidgetClass) { return PushWidgetToLayerStack(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 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; } /** * 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 SuspendInputTokens; /** * Registered layers for the primary layout. * 主布局的注册层。 */ UPROPERTY(Transient, meta = (Categories = "UI.Layer,GUIS.Layer")) TMap> Layers; };