Files
UnrealEngineUWP/Engine/Plugins/Slate/SlateScripting/Source/SlateScriptingCommands/Private/UICommandsScriptingSubsystem.cpp
anatole menichetti b6d019e71c Adding SlateScripting plugin, SlateScriptingCommands module and UICommandsScriptingSubsystem to register commands dynamically within existing UI Command LIsts through scripting. Sequencer, Curve Editor, Content Browser, Main Frame, Level Editor and Level Viewports are currently the only systems exposing the command lists.
#jira UE-118789

#ROBOMERGE-AUTHOR: anatole.menichetti
#ROBOMERGE-SOURCE: CL 17735664 in //UE5/Main/...
#ROBOMERGE-BOT: STARSHIP (Main -> Release-Engine-Test) (v879-17706426)

[CL 17735675 by anatole menichetti in ue5-release-engine-test branch]
2021-10-06 11:07:42 -04:00

514 lines
17 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "UICommandsScriptingSubsystem.h"
#include "Framework/Commands/UIAction.h"
#include "Framework/Commands/InputBindingManager.h"
DEFINE_LOG_CATEGORY(LogCommandsScripting)
bool FScriptingCommandInfo::Equals(const FScriptingCommandInfo& InCommandInfo, const bool bCheckInputChord) const
{
return InCommandInfo.Name == Name && InCommandInfo.Set == Set && InCommandInfo.ContextName == ContextName &&
(!bCheckInputChord || InCommandInfo.InputChord == InputChord);
}
TSharedPtr<FUICommandInfo> FScriptingCommand::MakeUICommandInfo() const
{
FInputBindingManager& BindingManager = FInputBindingManager::Get();
const FName CommandName = GetFullName();
const TSharedPtr<FBindingContext> Context = BindingManager.GetContextByName(CommandInfo.ContextName);
if (!Context.IsValid())
{
UE_LOG(LogCommandsScripting, Error, TEXT("Context is not registered in the Input Binding Manager: %s"), *CommandInfo.ContextName.ToString())
return nullptr;
}
if (BindingManager.FindCommandInContext(CommandInfo.ContextName, CommandName))
{
UE_LOG(LogCommandsScripting, Error, TEXT("%s: Command already registered in context %s"), *CommandName.ToString(), *CommandInfo.ContextName.ToString())
return nullptr;
}
if (BindingManager.FindCommandInContext(CommandInfo.ContextName, CommandInfo.InputChord, false))
{
UE_LOG(LogCommandsScripting, Error, TEXT("Input Chord already mapped in context: %s"), *CommandInfo.ContextName.ToString())
return nullptr;
}
TSharedPtr<FUICommandInfo> NewCommand;
FUICommandInfo::MakeCommandInfo(
Context.ToSharedRef(),
NewCommand,
CommandName,
CommandInfo.Label,
CommandInfo.Description,
FSlateIcon(),
EUserInterfaceActionType::Button,
CommandInfo.InputChord
);
return NewCommand;
}
bool FScriptingCommand::UnregisterUICommandInfo() const
{
FInputBindingManager& BindingManager = FInputBindingManager::Get();
const TSharedPtr<FBindingContext> Context = BindingManager.GetContextByName(CommandInfo.ContextName);
const TSharedPtr<FUICommandInfo> Command = BindingManager.FindCommandInContext(CommandInfo.ContextName, GetFullName());
if (Context.IsValid() && Command.IsValid())
{
FUICommandInfo::UnregisterCommandInfo(Context.ToSharedRef(), Command.ToSharedRef());
return true;
}
return false;
}
bool FScriptingCommandsContext::MapCommand(const TSharedRef<FScriptingCommand> ScriptingCommand)
{
const TSharedPtr<FUICommandInfo> Command = FInputBindingManager::Get().FindCommandInContext(
ScriptingCommand->CommandInfo.ContextName, ScriptingCommand->GetFullName());
if (Command.IsValid())
{
CleanupPointerArray(CommandLists);
for (TWeakPtr<FUICommandList> CommandList : CommandLists)
{
CommandList.Pin()->MapAction(Command, ScriptingCommand->OnExecuteAction, ScriptingCommand->OnCanExecuteAction);
}
ScriptingCommands.AddUnique(ScriptingCommand);
return true;
}
UE_LOG(LogCommandsScripting, Error, TEXT("Could not map command: %s. The command could not be found in the Input Binding Manager"), *ScriptingCommand->GetFullName().ToString())
return false;
}
bool FScriptingCommandsContext::UnmapCommand(const TSharedRef<FScriptingCommand> ScriptingCommand)
{
const TSharedPtr<FUICommandInfo> Command = FInputBindingManager::Get().FindCommandInContext(
ScriptingCommand->CommandInfo.ContextName, ScriptingCommand->GetFullName());
if (Command.IsValid())
{
CleanupPointerArray(CommandLists);
for (TWeakPtr<FUICommandList> CommandList : CommandLists)
{
CommandList.Pin()->UnmapAction(Command);
}
ScriptingCommands.Remove(ScriptingCommand);
return true;
}
UE_LOG(LogCommandsScripting, Error, TEXT("Could not unmap command: %s. The command could not be found in the Input Binding Manager"), *ScriptingCommand->GetFullName().ToString())
return false;
}
bool FScriptingCommandsContext::RegisterCommandList(const TSharedRef<FUICommandList> CommandList)
{
CleanupPointerArray(CommandLists);
if (!CommandLists.Contains(CommandList))
{
CommandLists.Add(CommandList);
MapAllCommands(CommandList);
return true;
}
UE_LOG(LogCommandsScripting, Warning, TEXT("Trying to register an already registered command list"))
return false;
}
bool FScriptingCommandsContext::UnregisterCommandList(const TSharedRef<FUICommandList> CommandList)
{
CleanupPointerArray(CommandLists);
if (CommandLists.Remove(CommandList) > 0)
{
UnmapAllCommands(CommandList);
return true;
}
UE_LOG(LogCommandsScripting, Warning, TEXT("Trying to unregister a non registered command list"))
return false;
}
void FScriptingCommandsContext::MapAllCommands(const TSharedRef<FUICommandList> CommandList)
{
for (const TSharedPtr<FScriptingCommand>& ScriptingCommand : ScriptingCommands)
{
if (ScriptingCommand.IsValid())
{
const TSharedPtr<FUICommandInfo> CommandInfo = FInputBindingManager::Get().FindCommandInContext(
ContextName, ScriptingCommand->GetFullName());
if (CommandInfo.IsValid())
{
CommandList->MapAction(CommandInfo, ScriptingCommand->OnExecuteAction, ScriptingCommand->OnCanExecuteAction);
}
}
}
}
void FScriptingCommandsContext::UnmapAllCommands(const TSharedRef<FUICommandList> CommandList)
{
for (const TSharedPtr<FScriptingCommand>& ScriptingCommand : ScriptingCommands)
{
if (ScriptingCommand.IsValid())
{
const TSharedPtr<FUICommandInfo> CommandInfo = FInputBindingManager::Get().FindCommandInContext(
ContextName, ScriptingCommand->GetFullName());
if (CommandInfo.IsValid())
{
CommandList->UnmapAction(CommandInfo);
}
}
}
}
void UUICommandsScriptingSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
FInputBindingManager::Get().OnRegisterCommandList.AddUObject(this, &UUICommandsScriptingSubsystem::RegisterCommandListForContext);
FInputBindingManager::Get().OnUnregisterCommandList.AddUObject(this, &UUICommandsScriptingSubsystem::UnregisterCommandListForContext);
}
void UUICommandsScriptingSubsystem::Deinitialize()
{
UnregisterAllSets();
}
void UUICommandsScriptingSubsystem::RegisterCommandListForContext(const FName ContextName,
const TSharedRef<FUICommandList> CommandList)
{
FScriptingCommandsContext& CommandsContext = CommandsInContext.FindOrAdd(ContextName, ContextName);
CommandsContext.RegisterCommandList(CommandList);
}
void UUICommandsScriptingSubsystem::UnregisterCommandListForContext(const FName ContextName,
const TSharedRef<FUICommandList> CommandList)
{
FScriptingCommandsContext *CommandsContext = CommandsInContext.Find(ContextName);
if (CommandsContext)
{
CommandsContext->UnregisterCommandList(CommandList);
}
}
bool UUICommandsScriptingSubsystem::UnregisterContext(const FName ContextName)
{
FScriptingCommandsContext *CommandsContext = CommandsInContext.Find(ContextName);
if (CommandsContext)
{
CleanupPointerArray(CommandsContext->CommandLists);
for (const TWeakPtr<FUICommandList>& CommandList : CommandsContext->CommandLists)
{
CommandsContext->UnmapAllCommands(CommandList.Pin().ToSharedRef());
}
CommandsInContext.Remove(ContextName);
return true;
}
UE_LOG(LogCommandsScripting, Warning, TEXT("Trying to unregister a non registered context"))
return false;
}
bool UUICommandsScriptingSubsystem::RegisterCommand(const FScriptingCommandInfo CommandInfo,
const FExecuteCommand OnExecuteCommand,
const bool bOverrideExisting)
{
// Registers the command with default CanExecute (just checking if command set and subsystem are enabled).
const FCanExecuteAction OnCanExecuteAction = FCanExecuteAction::CreateUObject(this,
&UUICommandsScriptingSubsystem::DefaultCanExecuteAction, CommandInfo.Set);
return RegisterNewScriptingCommand(CommandInfo, OnExecuteCommand, OnCanExecuteAction, bOverrideExisting);
}
bool UUICommandsScriptingSubsystem::RegisterCommandChecked(const FScriptingCommandInfo CommandInfo,
const FExecuteCommand OnExecuteCommand,
const FCanExecuteCommand OnCanExecuteCommand,
const bool bOverrideExisting)
{
// Registers the command with the given CanExecute delegate (still checking if command set and subsystem are enabled)
const FCanExecuteAction OnCanExecuteAction = FCanExecuteAction::CreateUObject(this,
&UUICommandsScriptingSubsystem::HandleCanExecuteAction, OnCanExecuteCommand, CommandInfo);
return RegisterNewScriptingCommand(CommandInfo, OnExecuteCommand, OnCanExecuteAction, bOverrideExisting);
}
bool UUICommandsScriptingSubsystem::UnregisterCommand(FScriptingCommandInfo CommandInfo)
{
if (FScriptingCommandSet* CommandSet = CommandSets.Find(CommandInfo.Set))
{
TSharedPtr<FScriptingCommand>* ScriptingCommand = CommandSet->ScriptingCommands.FindByPredicate([CommandInfo](
const TSharedPtr<FScriptingCommand> Command) { return Command.IsValid() && Command->CommandInfo.Equals(CommandInfo);});
if (ScriptingCommand && ScriptingCommand->IsValid())
{
UnregisterScriptingCommand(ScriptingCommand->ToSharedRef());
CommandSet->ScriptingCommands.RemoveSingle(*ScriptingCommand);
return true;
}
UE_LOG(LogCommandsScripting, Error, TEXT("Command not registered: %s"), *CommandInfo.GetFullName().ToString())
return false;
}
UE_LOG(LogCommandsScripting, Error, TEXT("Command Set not registered: %s"), *CommandInfo.Set.ToString())
return false;
}
bool UUICommandsScriptingSubsystem::UnregisterCommandSet(const FName SetName)
{
if (const FScriptingCommandSet* CommandSet = CommandSets.Find(SetName))
{
for (const TSharedPtr<FScriptingCommand>& ScriptingCommand : CommandSet->ScriptingCommands)
{
if (ScriptingCommand.IsValid())
{
UnregisterScriptingCommand(ScriptingCommand.ToSharedRef());
}
}
CommandSets.Remove(SetName);
return true;
}
UE_LOG(LogCommandsScripting, Error, TEXT("Command Set not registered: %s"), *SetName.ToString())
return false;
}
void UUICommandsScriptingSubsystem::UnregisterAllSets()
{
for (const TPair<FName, FScriptingCommandSet>& CommandSet : CommandSets)
{
for (const TSharedPtr<FScriptingCommand>& ScriptingCommand : CommandSet.Value.ScriptingCommands)
{
if (ScriptingCommand.IsValid())
{
UnregisterScriptingCommand(ScriptingCommand.ToSharedRef());
}
}
}
CommandSets.Reset();
}
TArray<FName> UUICommandsScriptingSubsystem::GetAvailableContexts() const
{
TArray<FName> OutContexts;
CommandsInContext.GetKeys(OutContexts);
return OutContexts;
}
bool UUICommandsScriptingSubsystem::IsContextRegistered(const FName ContextName) const
{
return CommandsInContext.Contains(ContextName);
}
int UUICommandsScriptingSubsystem::GetBindingCountForContext(const FName ContextName)
{
if (IsContextRegistered(ContextName))
{
CleanupPointerArray(CommandsInContext[ContextName].CommandLists);
return CommandsInContext[ContextName].CommandLists.Num();
}
UE_LOG(LogCommandsScripting, Error, TEXT("Context not registered: %s"), *ContextName.ToString())
return 0;
}
TArray<FScriptingCommandInfo> UUICommandsScriptingSubsystem::GetRegisteredCommands() const
{
TArray<FScriptingCommandInfo> OutCommandsInfo;
for (const TPair<FName, FScriptingCommandSet>& CommandSet : CommandSets)
{
for (const TSharedPtr<FScriptingCommand>& ScriptingCommand : CommandSet.Value.ScriptingCommands)
{
if (ScriptingCommand.IsValid())
{
OutCommandsInfo.Add(ScriptingCommand->CommandInfo);
}
}
}
return OutCommandsInfo;
}
bool UUICommandsScriptingSubsystem::IsCommandRegistered(const FScriptingCommandInfo CommandInfo, const bool bCheckInputChord) const
{
if (CommandSets.Contains(CommandInfo.Set))
{
const TSharedPtr<FScriptingCommand>* ExistingCommand = CommandSets[CommandInfo.Set].ScriptingCommands.FindByPredicate(
[CommandInfo, bCheckInputChord](const TSharedPtr<FScriptingCommand> ScriptingCommand)
{
return ScriptingCommand.IsValid() && ScriptingCommand->CommandInfo.Equals(CommandInfo, bCheckInputChord);
});
return ExistingCommand && ExistingCommand->IsValid();
}
return false;
}
auto UUICommandsScriptingSubsystem::IsInputChordMapped(const FName ContextName, const FInputChord InputChord) const -> bool
{
if (!IsContextRegistered(ContextName))
{
UE_LOG(LogCommandsScripting, Error, TEXT("Context not registered: %s"), *ContextName.ToString())
return false;
}
return FInputBindingManager::Get().FindCommandInContext(ContextName, InputChord, false) != nullptr;
}
void UUICommandsScriptingSubsystem::SetCanSetExecuteCommands(const FName SetName, const bool bShouldExecuteCommands)
{
if (FScriptingCommandSet* CommandSet = CommandSets.Find(SetName))
{
CommandSet->bCanExecuteCommands = bShouldExecuteCommands;
}
else
{
UE_LOG(LogCommandsScripting, Error, TEXT("Command Set not registered: %s"), *SetName.ToString())
}
}
bool UUICommandsScriptingSubsystem::CanSetExecuteCommands(const FName SetName) const
{
if (const FScriptingCommandSet* CommandSet = CommandSets.Find(SetName))
{
return bCanExecuteCommands && CommandSet->bCanExecuteCommands;
}
UE_LOG(LogCommandsScripting, Error, TEXT("Command Set not registered: %s"), *SetName.ToString())
return false;
}
void UUICommandsScriptingSubsystem::SetCanExecuteCommands(const bool bShouldExecuteCommands)
{
bCanExecuteCommands = bShouldExecuteCommands;
}
bool UUICommandsScriptingSubsystem::IsCommandSetRegistered(const FName SetName) const
{
return CommandSets.Contains(SetName);
}
bool UUICommandsScriptingSubsystem::RegisterCommandSet(const FName SetName)
{
if (IsCommandSetRegistered(SetName))
{
UE_LOG(LogCommandsScripting, Warning, TEXT("Command Set already registered: %s"), *SetName.ToString())
return false;
}
CommandSets.Add(SetName);
return true;
}
bool UUICommandsScriptingSubsystem::CanExecuteCommands() const
{
return bCanExecuteCommands;
}
bool UUICommandsScriptingSubsystem::RegisterNewScriptingCommand(const FScriptingCommandInfo CommandInfo,
const FExecuteCommand OnExecuteCommand,
const FCanExecuteAction OnCanExecuteAction,
const bool bOverrideExisting)
{
if (!IsCommandSetRegistered(CommandInfo.Set))
{
UE_LOG(LogCommandsScripting, Error, TEXT("Trying to register a command in a non registered Command Set: %s"), *CommandInfo.Set.ToString())
return false;
}
// Bind the given OnExecute delegate to the non dynamic one expected internally
const FExecuteAction OnExecuteAction = FExecuteAction::CreateStatic(
&UUICommandsScriptingSubsystem::HandleExecuteAction, OnExecuteCommand, CommandInfo);
// Create the command data to be cached by the subsystem
const TSharedPtr<FScriptingCommand> ScriptingCommand = MakeShareable(new FScriptingCommand(CommandInfo, OnExecuteAction, OnCanExecuteAction));
// Unregister any command previously registered with this name to overwrite it
if (IsCommandRegistered(CommandInfo, false))
{
if (!bOverrideExisting)
{
UE_LOG(LogCommandsScripting, Warning, TEXT("Previously registered command %s won't be overriden"), *CommandInfo.GetFullName().ToString())
return false;
}
if (!UnregisterCommand(CommandInfo))
{
UE_LOG(LogCommandsScripting, Error, TEXT("Could not override command: %s"), *CommandInfo.GetFullName().ToString())
return false;
}
}
// Register the command without checking if it has been bound to a command list (if it wasn't, it will when possible)
if (RegisterScriptingCommand(ScriptingCommand.ToSharedRef()))
{
// Add the command to our list of registered commands
CommandSets[CommandInfo.Set].ScriptingCommands.AddUnique(ScriptingCommand);
return true;
}
return false;
}
bool UUICommandsScriptingSubsystem::RegisterScriptingCommand(const TSharedRef<FScriptingCommand> ScriptingCommand)
{
FScriptingCommandsContext* CommandsContext = CommandsInContext.Find(ScriptingCommand->CommandInfo.ContextName);
if (CommandsContext)
{
return ScriptingCommand->MakeUICommandInfo() && CommandsContext->MapCommand(ScriptingCommand);
}
UE_LOG(LogCommandsScripting, Error, TEXT("Context not registered: %s"), *ScriptingCommand->CommandInfo.ContextName.ToString())
return false;
}
bool UUICommandsScriptingSubsystem::UnregisterScriptingCommand(const TSharedRef<FScriptingCommand> ScriptingCommand)
{
FScriptingCommandsContext* CommandsContext = CommandsInContext.Find(ScriptingCommand->CommandInfo.ContextName);
if (CommandsContext)
{
return CommandsContext->UnmapCommand(ScriptingCommand) && ScriptingCommand->UnregisterUICommandInfo();
}
UE_LOG(LogCommandsScripting, Error, TEXT("Context not registered: %s"), *ScriptingCommand->CommandInfo.ContextName.ToString())
return false;
}
void UUICommandsScriptingSubsystem::HandleExecuteAction(const FExecuteCommand OnExecuteAction, const FScriptingCommandInfo CommandInfo)
{
OnExecuteAction.Execute(CommandInfo);
}
bool UUICommandsScriptingSubsystem::HandleCanExecuteAction(const FCanExecuteCommand OnCanExecuteAction, const FScriptingCommandInfo CommandInfo) const
{
return CanSetExecuteCommands(CommandInfo.Set) && OnCanExecuteAction.Execute(CommandInfo);
}
bool UUICommandsScriptingSubsystem::DefaultCanExecuteAction(const FName SetName) const
{
return CanSetExecuteCommands(SetName);
}