// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "Subsystems/EngineSubsystem.h" #include "Framework/Commands/UICommandList.h" #include "Framework/Commands/UICommandInfo.h" #include "Framework/Commands/InputChord.h" #include "UICommandsScriptingSubsystem.generated.h" DECLARE_LOG_CATEGORY_EXTERN(LogCommandsScripting, Log, All); /** * The data defining a scripting command. At the exception of its delegates. */ USTRUCT(BlueprintType) struct FScriptingCommandInfo { GENERATED_BODY() /** The editor context this command is bound to */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Editor Scripting | Commands") FName ContextName; /** The command set this command belongs to. This is to avoid conflicts and could refer to the owner of the command */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Editor Scripting | Commands") FName Set; /** The command name. Must be unique in its set. */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Editor Scripting | Commands") FName Name; /** The command label or what name will be displayed for it */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Editor Scripting | Commands") FText Label; /** The description of the command */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Editor Scripting | Commands") FText Description; /** The input chord to bound to the command */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Editor Scripting | Commands") FInputChord InputChord; /** Compares this command info with the given one per context, set, name and optionally input chord */ bool Equals(const FScriptingCommandInfo& InCommandInfo, const bool bCheckInputChord = false) const; /** Builds a full name in the format context.set.command_name to avoid conflicts with commands registered in different contexts/sets */ FName GetFullName() const { // We append the set name and command name to avoid conflicts between commands registered by different scripts/components return *(ContextName.ToString() + "." + Set.ToString() + "." + Name.ToString()); } }; /** Exposing FExecuteAction as dynamic */ DECLARE_DYNAMIC_DELEGATE_OneParam(FExecuteCommand, FScriptingCommandInfo, CommandInfo); /** Exposing FCanExecuteAction as dynamic */ DECLARE_DYNAMIC_DELEGATE_RetVal_OneParam(bool, FCanExecuteCommand, FScriptingCommandInfo, CommandInfo); /** * All the internal data related to a scripting command as well as methods to easily expose the command to the binding manager */ USTRUCT() struct FScriptingCommand { GENERATED_BODY() FScriptingCommand() = default; FScriptingCommand(const FScriptingCommandInfo& InCommandInfo, const FExecuteAction InOnExecuteAction, const FCanExecuteAction InOnCanExecuteAction) : CommandInfo(InCommandInfo), OnExecuteAction(InOnExecuteAction), OnCanExecuteAction(InOnCanExecuteAction) {} /** The command definition */ FScriptingCommandInfo CommandInfo; /** The delegate to call when executing the command */ FExecuteAction OnExecuteAction; /** The delegate to call to check whether the command can be executed */ FCanExecuteAction OnCanExecuteAction; FORCEINLINE bool operator==(const FScriptingCommand& Other) const { return Other.CommandInfo.Name == CommandInfo.Name; } FORCEINLINE bool operator!=(const FScriptingCommand& Other) const { return !(*this == Other); } FORCEINLINE bool operator==(const FName OtherName) const { return OtherName == CommandInfo.Name; } FORCEINLINE bool operator!=(const FName OtherName) const { return !(*this == OtherName); } /** Builds a full name in the format context.set.command_name to avoid conflicts with commands registered in different contexts/sets */ FName GetFullName() const { return CommandInfo.GetFullName(); } /** * Registers a new UICommandInfo in the associated context through the binding manager. * At this point, the command is exposed to the manager but not mapped to any command list. */ TSharedPtr MakeUICommandInfo() const; /** Unregisters this command's UICommandInfo from its associated context through the binding manager */ bool UnregisterUICommandInfo() const; }; /** * The list of commands and UI Command Lists associated with a context. * This enables easier management of commands within registered contexts and their UI Command Lists. */ USTRUCT() struct FScriptingCommandsContext { GENERATED_BODY() FScriptingCommandsContext() = default; FScriptingCommandsContext(const FName InContextName) : ContextName(InContextName) {} /** The context these command lists are bound to */ FName ContextName; /** An array of command list associated to the given context */ TArray> CommandLists; /** The commands bound in these command lists */ TArray> ScriptingCommands; public: /** Maps the given command to all the command lists of this context */ bool MapCommand(const TSharedRef ScriptingCommand); /** Unmaps the given command from all the command lists of this context */ bool UnmapCommand(const TSharedRef ScriptingCommand); /** Registers a command list in this context then map to it all commands existing within this context */ bool RegisterCommandList(const TSharedRef CommandList); /** Unregisters command list from this context then unmap from it all commands existing within this context */ bool UnregisterCommandList(const TSharedRef CommandList); /** Maps all commands existing within this context to the given command list */ void MapAllCommands(TSharedRef CommandList); /** Unmaps all commands existing within this context from the given command list */ void UnmapAllCommands(TSharedRef CommandList); }; /** * To avoid conflicts between scripts, each command is associated to a command set which must be registered manually. */ USTRUCT() struct FScriptingCommandSet { GENERATED_BODY() /** The commands in this set */ TArray> ScriptingCommands; /** Whether the commands in this set are enabled */ bool bCanExecuteCommands = true; }; /** * UEditorInputSubsystem * Subsystem for dynamically registering editor commands through scripting */ UCLASS() class SLATESCRIPTINGCOMMANDS_API UUICommandsScriptingSubsystem : public UEngineSubsystem { GENERATED_BODY() public: /** * USubsystem Interface: handles initialization of instances of the system * This is where the subsystem binds to the CommandListRegistered delegate of the InputBindingManager * * To expose command lists from external systems to this subsystem, call FInputBindingManager::RegisterCommandList * from the external system. The binding manager will then broadcast the registered command list to all subscribers. */ virtual void Initialize(FSubsystemCollectionBase& Collection) override; /** USubsystem Interface: handles deinitialization of instances of the system */ virtual void Deinitialize() override; /** * Registers a command list in the given context. Optionally registering the context itself if it does not exist * Note the context should already be existing and registered through the binding manager. * * This should be called by modules and tools looking to expose their command lists to the subsystem. * * @param ContextName The context name as defined in the binding manager * @param CommandList The command list to register in this context */ void RegisterCommandListForContext(const FName ContextName, TSharedRef CommandList); /** * Unregisters a command list from the given context. * @param ContextName The context name as defined in the binding manager * @param CommandList The command list to register in this context */ void UnregisterCommandListForContext(const FName ContextName, TSharedRef CommandList); /** * Unregisters all command lists from the given context. * @param ContextName The context name as defined in the binding manager */ bool UnregisterContext(const FName ContextName); public: /** * Registers a command within the given context and set. * The set must be registered beforehand. * @param CommandInfo The command infos such as name, label, description and input chord. * @param OnExecuteCommand The delegate to be executed for handling this command. * @param bOverrideExisting Whether existing command with matching context, set and name should be overriden * @return Whether the command was succesfully registered */ UFUNCTION(BlueprintCallable, Category = "Editor Scripting | Commands |") bool RegisterCommand(FScriptingCommandInfo CommandInfo, FExecuteCommand OnExecuteCommand, bool bOverrideExisting = false); /** * Registers a command within the given context and set. * The set must be registered beforehand. * @param CommandInfo The command infos such as name, label, description and input chord. * @param OnExecuteCommand The delegate to be executed for handling this command. * @param OnCanExecuteCommand The delegate to be executed for checking if this command can be executed. * @param bOverrideExisting Whether existing command with matching context, set and name should be overriden * @return Whether the command was successfully registered */ UFUNCTION(BlueprintCallable, Category = "Editor Scripting | Commands |") bool RegisterCommandChecked(FScriptingCommandInfo CommandInfo, FExecuteCommand OnExecuteCommand, FCanExecuteCommand OnCanExecuteCommand, bool bOverrideExisting = false); /** * Unregisters a command previously registered. The command name, set and context will be used for comparison. * @return Whether the command was successfully unregistered. */ UFUNCTION(BlueprintCallable, Category = "Editor Scripting | Commands |") bool UnregisterCommand(FScriptingCommandInfo CommandInfo); /** Checks whether the given command is registered within the subsystem. Using name, set and context for comparison */ UFUNCTION(BlueprintCallable, Category = "Editor Scripting | Commands |") bool IsCommandRegistered(FScriptingCommandInfo CommandInfo, bool bCheckInputChord = true) const; /** Retrieves the list of command info for all commands currently registered in the subsystem */ UFUNCTION(BlueprintCallable, Category = "Editor Scripting | Commands | Global") TArray GetRegisteredCommands() const; /** Checks whether commands registered in the subsystem can be executed */ UFUNCTION(BlueprintCallable, Category = "Editor Scripting | Commands | Global") bool CanExecuteCommands() const; /** Sets whether commands registered in the subsystem can be executed */ UFUNCTION(BlueprintCallable, Category = "Editor Scripting | Commands | Global") void SetCanExecuteCommands(bool bShouldExecuteCommands); /** * Unregisters all commands dynamically registered within all contexts and sets. * @warning this will unregister all commands currently registered by this subsystem */ UFUNCTION(BlueprintCallable, Category = "Editor Scripting | Commands | Global") void UnregisterAllSets(); /** * Registers a new command set * @return Whether the set did not already exist and was successfully registered */ UFUNCTION(BlueprintCallable, Category = "Editor Scripting | Commands | Sets") bool RegisterCommandSet(FName SetName); /** Checks whether the given set is currently registered in the subsystem */ UFUNCTION(BlueprintCallable, Category = "Editor Scripting | Commands | Sets") bool IsCommandSetRegistered(FName SetName) const; /** * Unregisters the corresponding command set with all commands registered within it * @return Whether the command set existed and was successfully unregistered */ UFUNCTION(BlueprintCallable, Category = "Editor Scripting | Commands | Sets") bool UnregisterCommandSet(FName SetName); /** Enables or disables execution of commands registered within the given set */ UFUNCTION(BlueprintCallable, Category = "Editor Scripting | Commands | Sets", Meta = (DisplayName = "Set Can Execute Commands")) void SetCanSetExecuteCommands(FName SetName, bool bShouldExecuteCommands); /** Checks whether commands in the given set can be executed. This will also check CanExecuteCommands at a global scope */ UFUNCTION(BlueprintCallable, Category = "Editor Scripting | Commands | Sets", Meta = (DisplayName = "Can Execute Commands")) bool CanSetExecuteCommands(const FName SetName) const; /** * Retrieves the list of names for all contexts currently registered in the subsystem. * This does not check whether the contexts are bound to any UI Command List. */ UFUNCTION(BlueprintCallable, Category = "Editor Scripting | Commands | Contexts") TArray GetAvailableContexts() const; /** * Checks whether the context with the given name is currently registered in the subsystem * This does not check whether the context is bound to any UI Command List. */ UFUNCTION(BlueprintCallable, Category = "Editor Scripting | Commands | Contexts") bool IsContextRegistered(FName ContextName) const; /** * Retrieves the number of UI Command Lists registered within this context through the subsystem. * UI Command Lists are typically used to bind the list of commands associated with a single UI (i.e. a single viewport). */ UFUNCTION(BlueprintCallable, Category = "Editor Scripting | Commands | Contexts") int GetBindingCountForContext(FName ContextName); /** * Checks whether the given input chord is already mapped to a command in the given context. * This includes commands not registered through the subsystem. */ UFUNCTION(BlueprintCallable, Category = "Editor Scripting | Commands | Contexts") bool IsInputChordMapped(FName ContextName, FInputChord InputChord) const; private: /** The internal method to dynamically registers an editor command */ bool RegisterNewScriptingCommand(FScriptingCommandInfo CommandInfo, FExecuteCommand OnExecuteCommand, FCanExecuteAction OnCanExecuteAction, bool bOverrideExisting = false); /** Registers a context command in its command lists. This assumes the command is already contained in a set */ bool RegisterScriptingCommand(const TSharedRef ScriptingCommand); /** Unregisters a context command from its command lists. This assumes the command is already contained in a set */ bool UnregisterScriptingCommand(const TSharedRef ScriptingCommand); /** The delegate bound to registered commands. It will broadcast back to the given dynamic delegate */ UFUNCTION() static void HandleExecuteAction(FExecuteCommand OnExecuteAction, const FScriptingCommandInfo CommandInfo); /** * The delegate used to check whether a command can be executed. * @return true if the given delegate returns true and the given set and all subsystem commands are enabled */ UFUNCTION() bool HandleCanExecuteAction(FCanExecuteCommand OnCanExecuteAction, const FScriptingCommandInfo CommandInfo) const; /** * The default delegate to check if a command can be executed (when the user does not provide a specific delegate). * @return true if the given set and all subsystem commands are enabled */ UFUNCTION() bool DefaultCanExecuteAction(FName SetName) const; private: /** The list of commands contexts. Each context contains its own list of registered commands and UI command lists */ TMap CommandsInContext; /** The list of command sets. Each set contains its own list of registered commands */ TMap CommandSets; /** Whether the commands for all the sets of the subsystem can be executed */ bool bCanExecuteCommands = true; };