// Copyright Epic Games, Inc. All Rights Reserved. #include "UICommandsScriptingSubsystem.h" #include "Framework/Commands/InputBindingManager.h" #include "Framework/Commands/UICommandList.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 FScriptingCommand::MakeUICommandInfo() const { FInputBindingManager& BindingManager = FInputBindingManager::Get(); const FName CommandName = GetFullName(); const TSharedPtr 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 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 Context = BindingManager.GetContextByName(CommandInfo.ContextName); const TSharedPtr 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 ScriptingCommand) { const TSharedPtr Command = FInputBindingManager::Get().FindCommandInContext( ScriptingCommand->CommandInfo.ContextName, ScriptingCommand->GetFullName()); if (Command.IsValid()) { CleanupPointerArray(CommandLists); for (TWeakPtr 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 ScriptingCommand) { const TSharedPtr Command = FInputBindingManager::Get().FindCommandInContext( ScriptingCommand->CommandInfo.ContextName, ScriptingCommand->GetFullName()); if (Command.IsValid()) { CleanupPointerArray(CommandLists); for (TWeakPtr 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 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 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 CommandList) { for (const TSharedPtr& ScriptingCommand : ScriptingCommands) { if (ScriptingCommand.IsValid()) { const TSharedPtr CommandInfo = FInputBindingManager::Get().FindCommandInContext( ContextName, ScriptingCommand->GetFullName()); if (CommandInfo.IsValid()) { CommandList->MapAction(CommandInfo, ScriptingCommand->OnExecuteAction, ScriptingCommand->OnCanExecuteAction); } } } } void FScriptingCommandsContext::UnmapAllCommands(const TSharedRef CommandList) { for (const TSharedPtr& ScriptingCommand : ScriptingCommands) { if (ScriptingCommand.IsValid()) { const TSharedPtr 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 CommandList) { FScriptingCommandsContext& CommandsContext = CommandsInContext.FindOrAdd(ContextName, ContextName); CommandsContext.RegisterCommandList(CommandList); } void UUICommandsScriptingSubsystem::UnregisterCommandListForContext(const FName ContextName, const TSharedRef 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& 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* ScriptingCommand = CommandSet->ScriptingCommands.FindByPredicate([CommandInfo]( const TSharedPtr 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& 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& CommandSet : CommandSets) { for (const TSharedPtr& ScriptingCommand : CommandSet.Value.ScriptingCommands) { if (ScriptingCommand.IsValid()) { UnregisterScriptingCommand(ScriptingCommand.ToSharedRef()); } } } CommandSets.Reset(); } TArray UUICommandsScriptingSubsystem::GetAvailableContexts() const { TArray 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 UUICommandsScriptingSubsystem::GetRegisteredCommands() const { TArray OutCommandsInfo; for (const TPair& CommandSet : CommandSets) { for (const TSharedPtr& 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* ExistingCommand = CommandSets[CommandInfo.Set].ScriptingCommands.FindByPredicate( [CommandInfo, bCheckInputChord](const TSharedPtr 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 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 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 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); }