// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "Containers/Array.h" #include "Containers/Map.h" #include "CoreTypes.h" #include "Delegates/Delegate.h" #include "GameplayTagContainer.h" #include "Kismet/BlueprintFunctionLibrary.h" #include "Subsystems/WorldSubsystem.h" #include "Templates/SharedPointer.h" #include "Templates/SubclassOf.h" #include "Templates/TypeHash.h" #include "UObject/Class.h" #include "UObject/Object.h" #include "UObject/UObjectGlobals.h" #include "UObject/WeakObjectPtr.h" #include "UObject/WeakObjectPtrTemplates.h" #include "UIExtensionSystem.generated.h" class FSubsystemCollectionBase; class UUserWidget; struct FFrame; // Match rule for extension points UENUM(BlueprintType) enum class EUIExtensionPointMatch : uint8 { // An exact match will only receive extensions with exactly the same point // (e.g., registering for "A.B" will match a broadcast of A.B but not A.B.C) ExactMatch, // A partial match will receive any extensions rooted in the same point // (e.g., registering for "A.B" will match a broadcast of A.B as well as A.B.C) PartialMatch }; // Match rule for extension points UENUM(BlueprintType) enum class EUIExtensionAction : uint8 { Added, Removed }; DECLARE_DELEGATE_TwoParams(FExtendExtensionPointDelegate, EUIExtensionAction Action, const FUIExtensionRequest& Request); /* * */ struct FUIExtension : TSharedFromThis { public: /** The extension point this extension is intended for. */ FGameplayTag ExtensionPointTag; int32 Priority = INDEX_NONE; TWeakObjectPtr ContextObject; //Kept alive by UUIExtensionSubsystem::AddReferencedObjects UObject* Data = nullptr; }; /** * */ struct FUIExtensionPoint : TSharedFromThis { public: FGameplayTag ExtensionPointTag; TWeakObjectPtr ContextObject; EUIExtensionPointMatch ExtensionPointTagMatchType = EUIExtensionPointMatch::ExactMatch; TArray AllowedDataClasses; FExtendExtensionPointDelegate Callback; // Tests if the extension and the extension point match up, if they do then this extension point should learn // about this extension. bool DoesExtensionPassContract(const FUIExtension* Extension) const; }; /** * */ USTRUCT(BlueprintType) struct UIEXTENSION_API FUIExtensionPointHandle { GENERATED_BODY() public: FUIExtensionPointHandle() {} void Unregister(); bool IsValid() const { return DataPtr.IsValid(); } bool operator==(const FUIExtensionPointHandle& Other) const { return DataPtr == Other.DataPtr; } bool operator!=(const FUIExtensionPointHandle& Other) const { return !operator==(Other); } friend uint32 GetTypeHash(const FUIExtensionPointHandle& Handle) { return PointerHash(Handle.DataPtr.Get()); } private: TWeakObjectPtr ExtensionSource; TSharedPtr DataPtr; friend UUIExtensionSubsystem; FUIExtensionPointHandle(UUIExtensionSubsystem* InExtensionSource, const TSharedPtr& InDataPtr) : ExtensionSource(InExtensionSource), DataPtr(InDataPtr) {} }; template<> struct TStructOpsTypeTraits : public TStructOpsTypeTraitsBase2 { enum { WithCopy = true, // This ensures the opaque type is copied correctly in BPs WithIdenticalViaEquality = true, }; }; /** * */ USTRUCT(BlueprintType) struct UIEXTENSION_API FUIExtensionHandle { GENERATED_BODY() public: FUIExtensionHandle() {} void Unregister(); bool IsValid() const { return DataPtr.IsValid(); } bool operator==(const FUIExtensionHandle& Other) const { return DataPtr == Other.DataPtr; } bool operator!=(const FUIExtensionHandle& Other) const { return !operator==(Other); } friend FORCEINLINE uint32 GetTypeHash(FUIExtensionHandle Handle) { return PointerHash(Handle.DataPtr.Get()); } private: TWeakObjectPtr ExtensionSource; TSharedPtr DataPtr; friend UUIExtensionSubsystem; FUIExtensionHandle(UUIExtensionSubsystem* InExtensionSource, const TSharedPtr& InDataPtr) : ExtensionSource(InExtensionSource), DataPtr(InDataPtr) {} }; template<> struct TStructOpsTypeTraits : public TStructOpsTypeTraitsBase2 { enum { WithCopy = true, // This ensures the opaque type is copied correctly in BPs WithIdenticalViaEquality = true, }; }; /** * */ USTRUCT(BlueprintType) struct FUIExtensionRequest { GENERATED_BODY() public: UPROPERTY(EditAnywhere, BlueprintReadOnly) FUIExtensionHandle ExtensionHandle; UPROPERTY(EditAnywhere, BlueprintReadOnly) FGameplayTag ExtensionPointTag; UPROPERTY(EditAnywhere, BlueprintReadOnly) int32 Priority = INDEX_NONE; UPROPERTY(EditAnywhere, BlueprintReadOnly) TObjectPtr Data = nullptr; UPROPERTY(EditAnywhere, BlueprintReadOnly) TObjectPtr ContextObject = nullptr; }; DECLARE_DYNAMIC_DELEGATE_TwoParams(FExtendExtensionPointDynamicDelegate, EUIExtensionAction, Action, const FUIExtensionRequest&, ExtensionRequest); /** * */ UCLASS() class UIEXTENSION_API UUIExtensionSubsystem : public UWorldSubsystem { GENERATED_BODY() public: FUIExtensionPointHandle RegisterExtensionPoint(const FGameplayTag& ExtensionPointTag, EUIExtensionPointMatch ExtensionPointTagMatchType, const TArray& AllowedDataClasses, FExtendExtensionPointDelegate ExtensionCallback); FUIExtensionPointHandle RegisterExtensionPointForContext(const FGameplayTag& ExtensionPointTag, UObject* ContextObject, EUIExtensionPointMatch ExtensionPointTagMatchType, const TArray& AllowedDataClasses, FExtendExtensionPointDelegate ExtensionCallback); FUIExtensionHandle RegisterExtensionAsWidget(const FGameplayTag& ExtensionPointTag, TSubclassOf WidgetClass, int32 Priority); FUIExtensionHandle RegisterExtensionAsWidgetForContext(const FGameplayTag& ExtensionPointTag, UObject* ContextObject, TSubclassOf WidgetClass, int32 Priority); FUIExtensionHandle RegisterExtensionAsData(const FGameplayTag& ExtensionPointTag, UObject* ContextObject, UObject* Data, int32 Priority); UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "UI Extension") void UnregisterExtension(const FUIExtensionHandle& ExtensionHandle); UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "UI Extension") void UnregisterExtensionPoint(const FUIExtensionPointHandle& ExtensionPointHandle); static void AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector); protected: virtual void Initialize(FSubsystemCollectionBase& Collection) override; virtual void Deinitialize() override; void NotifyExtensionPointOfExtensions(TSharedPtr& ExtensionPoint); void NotifyExtensionPointsOfExtension(EUIExtensionAction Action, TSharedPtr& Extension); UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="UI Extension", meta = (DisplayName = "Register Extension Point")) FUIExtensionPointHandle K2_RegisterExtensionPoint(FGameplayTag ExtensionPointTag, EUIExtensionPointMatch ExtensionPointTagMatchType, const TArray& AllowedDataClasses, FExtendExtensionPointDynamicDelegate ExtensionCallback); UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "UI Extension", meta = (DisplayName = "Register Extension (Widget)")) FUIExtensionHandle K2_RegisterExtensionAsWidget(FGameplayTag ExtensionPointTag, TSubclassOf WidgetClass, int32 Priority = -1); /** * Registers the widget (as data) for a specific player. This means the extension points will receive a UIExtensionForPlayer data object * that they can look at to determine if it's for whatever they consider their player. */ UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "UI Extension", meta = (DisplayName = "Register Extension (Widget For Context)")) FUIExtensionHandle K2_RegisterExtensionAsWidgetForContext(FGameplayTag ExtensionPointTag, TSubclassOf WidgetClass, UObject* ContextObject, int32 Priority = -1); /** * Registers the extension as data for any extension point that can make use of it. */ UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="UI Extension", meta = (DisplayName = "Register Extension (Data)")) FUIExtensionHandle K2_RegisterExtensionAsData(FGameplayTag ExtensionPointTag, UObject* Data, int32 Priority = -1); /** * Registers the extension as data for any extension point that can make use of it. */ UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category="UI Extension", meta = (DisplayName = "Register Extension (Data For Context)")) FUIExtensionHandle K2_RegisterExtensionAsDataForContext(FGameplayTag ExtensionPointTag, UObject* ContextObject, UObject* Data, int32 Priority = -1); FUIExtensionRequest CreateExtensionRequest(const TSharedPtr& Extension); private: typedef TArray> FExtensionPointList; TMap ExtensionPointMap; typedef TArray> FExtensionList; TMap ExtensionMap; }; UCLASS() class UIEXTENSION_API UUIExtensionHandleFunctions : public UBlueprintFunctionLibrary { GENERATED_BODY() public: UUIExtensionHandleFunctions() { } UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "UI Extension") static void Unregister(UPARAM(ref) FUIExtensionHandle& Handle); UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "UI Extension") static bool IsValid(UPARAM(ref) FUIExtensionHandle& Handle); }; UCLASS() class UIEXTENSION_API UUIExtensionPointHandleFunctions : public UBlueprintFunctionLibrary { GENERATED_BODY() public: UUIExtensionPointHandleFunctions() { } UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "UI Extension") static void Unregister(UPARAM(ref) FUIExtensionPointHandle& Handle); UFUNCTION(BlueprintCallable, BlueprintCosmetic, Category = "UI Extension") static bool IsValid(UPARAM(ref) FUIExtensionPointHandle& Handle); };