Все способы обработать ипут кнопки в модуле Editor

Оверайд InputKey()

virtual bool InputKey(FEditorViewportClient* InViewportClient, FViewport* InViewport, FKey InKey, EInputEvent InEvent) override;

Базовая функция, оверайдить которую можно в любом модуле. Кей приходит только если никто другой не Занял кнопку, и фокус Модуль = модулю, где функция заоверайжена.

Что значит Занял кнопку ? Функция возращает боол. Если вернуть Тру - ипут остановится, и никаким другим модулем обрабатываться не будет. Т.е функции Ариала не сработают, например, Еск - не выполнит ничего, если инпут кей вернет тру.

Добавить шорткат

Создание команды

Самый "правильный" способ. Позволяет изменять кнопки в редакторе, и уже имеет все функции для лога информации о шорткате. Но при этом имеет ряд недостатков: 1. Поддерживает только IE_Pressed и IE_Repeat, но не IE_Released. 2. Работает, только если никто другой не заберет инпут Key, и не переключит фокус на себя.

Теперь как добавить. Каждый FUICommandInfo создается через переменную команды, которая регистрируется в:

class FBuildingToolsEditorCommands : public TCommands<FBuildingToolsEditorCommands>
{
public:
	FBuildingToolsEditorCommands()
		: TCommands<FBuildingToolsEditorCommands>(
			"BuildingToolsEditor",
			// Context name for fast lookup
			NSLOCTEXT("Contexts", "BuildingToolsEditor", "Building Tools"),
			// Localized context name for displaying
			NAME_None,
			//"LevelEditor" // Parent
			FEditorStyle::GetStyleSetName() // Icon Style Set
			)
	{
	}
	virtual void RegisterCommands() override;
	TSharedPtr<FUICommandInfo> Command;
};
void FBuildingToolsEditorCommands::RegisterCommands()
{
    // FInputChord() - сюда указывать кнопку, на которую запускать команду
    // EUserInterfaceActionType важна только для UI
    UI_COMMAND(Command, "UniqueIDName", "CommandDescription",
     EUserInterfaceActionType::Button, FInputChord());
     
   // А это если мы хотим не создавать все команды EditorCommands, а создавать
   // их где-то еще
   for (const auto Command : BuildingToolsModes::GetCommands())
	 {
	  	Command->RegisterUICommand(this);
	 }
}

Регистрация модуля c Командами

Но от куда берется сама FBuildingToolsEditorCommands ? Она регистрируется в классе модуля через статическую функцию:

void FBuildingToolsModule::StartupModule()
{
   FBuildingToolsEditorCommands::Register();
}

void FBuildingToolsModule::ShutdownModule()
{
	FBuildingToolsEditorCommands::Unregister();
}

Добавление функции в шорткат

Теперь нужно добавить к FUICommandInfo функцию, которую она запустит. Для этого нужно получить FBuildingToolsEdModeToolkit :

class FBuildingToolsEdModeToolkit : public FModeToolkit
{
public:
   FBuildingToolsEdModeToolkit();

   /** FModeToolkit interface */
   virtual void Init(const TSharedPtr<IToolkitHost>& InitToolkitHost) override;
};
void FBuildingToolsEdModeToolkit::Init(const TSharedPtr<IToolkitHost>& InitToolkitHost)
{
	FBuildingToolsEdMode* BuildingToolsEdMode = GetEditorMode();
	TSharedRef<FUICommandList> CommandList = BuildingToolsEdMode->GetUICommandList();

	//FIsActionChecked Полезно только если нужно определить - выбран ли он в UI
	CommandList->MapAction(Command,
	                       FUIAction(
		                       FExecuteAction::CreateSP(this, &FBuildingToolsEdModeToolkit::OnChangeMode, FName(Command)),
		                       FCanExecuteAction::CreateSP(this, &FBuildingToolsEdModeToolkit::IsModeEnabled, FName(Command)),
		                       FIsActionChecked::CreateSP(this, &FBuildingToolsEdModeToolkit::IsModeActive, FName(Command))));
			FModeToolkit::Init(InitToolkitHost);
}

FBuildingToolsEdMode* FBuildingToolsEdModeToolkit::GetEditorMode() const
{
	return (FBuildingToolsEdMode*)GLevelEditorModeTools().GetActiveMode(FBuildingToolsEdMode::EM_BuildingTools);
}

Как создать Toolkit

В основном модуле необходимо создать толкит, и засетить его:

void FBuildingToolsEdMode::Enter()
{
	if (!Toolkit.IsValid())
	{
		const TSharedPtr<FBuildingToolsEdModeToolkit> BuildingToolsToolkit = MakeShareable(new FBuildingToolsEdModeToolkit);
		Toolkit = BuildingToolsToolkit;
		Toolkit->Init(Owner->GetToolkitHost());
	}
}

Добавить шорткат не в Commands класс

Самая большая беда, что один КоммандИнфо должен быть зареган лишь единожды и никогда больше. Если зарегестрировать дважды - будет краш. Поэтому нужно убедиться, что каждый комманд инфо будет уникальный

Создание комманды-класса

Для этой уникальности поместим каждую комманду в класс обжект

UCLASS(abstract, Transient)
class BUILDINGTOOLS_API UBTCommand : public UObject
{
	GENERATED_BODY()

public:
	/** Registers the UI command for this mesh editor command */
	virtual void RegisterUICommand(class FBindingContext* BindingContext) PURE_VIRTUAL(,);

	/** Gets the UI command info for this command */
	const TSharedPtr<FUICommandInfo>& GetUICommandInfo() const
	{
		return UICommandInfo;
	}

protected:
	/** Our UI command for this action */
	TSharedPtr<FUICommandInfo> UICommandInfo;
};

Теперь создаем наследника и указываем всю инфу о команде:

void UBTInvertCommand::RegisterUICommand(FBindingContext* BindingContext)
{
	UI_COMMAND_EXT(BindingContext,
	               UICommandInfo,
	               "Invert Assets",
	               "Invert Assets",
	               "Invert Assets.",
	               EUserInterfaceActionType::Button,
	               FInputChord( EKeys::I, UBuildingToolsLibrary::GetAdditKey()));
}

Регестрируем комманды классы

Так же как и раньше, но не совсем. Как можно видеть в прошлом блоке - использовался именно UI_COMMAND_EXT. Это нужно для того, чтобы регестрировать комманды именно в других функциях.

void FBuildingToolsEditorCommands::RegisterCommands()
{
	HarvestCommands();
   // А это если мы хотим не создавать все команды EditorCommands, а создавать
   // их где-то еще
   for (const auto Command : Commands)
	 {
	  	Command->RegisterUICommand(this);
	 }
}

Но от куда у нас берется массив с командами:

TArray<UBTCommand*> Commands;

void FBuildingToolsEditorCommands::HarvestCommands()
{
	Commands.Reset();
	for (TObjectIterator<UBTCommand> CommandCDOIter(RF_NoFlags); CommandCDOIter; ++CommandCDOIter)
	{
		const UBTCommand* CommandCDO = *CommandCDOIter;
		if (!(CommandCDO->GetClass()->GetClassFlags() & CLASS_Abstract))
		{
			Commands.Add(NewObject<UBTCommand>(GetTransientPackage(), CommandCDO->GetClass()));
			Commands.Last()->AddToRoot();
		}
	}
}

Добавление функции на команду-класс

Добавление функции и удалять можно в любое время и в любой момент. Для этого достаточно ссылки на модуль с тоолкитом

	TSharedRef<FUICommandList> CommandList = (FBuildingToolsEdMode*)GLevelEditorModeTools().GetActiveMode(FBuildingToolsEdMode::EM_BuildingTools);
	for (auto& Command : FBuildingToolsEditorCommands::Get().Commands)
	{
		CommandList->UnmapAction(Command->GetUICommandInfo());
	}
	CommandList->MapAction(
		FBuildingToolsEditorCommands::GetUICommandInfo<UBTInvertCommand>(),
		FExecuteAction::CreateWeakLambda(this,
										[&]()
										{
											FloorActor->SetFloorMode(EBTFloorMode::Invert);
											UpdateConstructionModeDelegate.Execute();
										})
		);

Получение команды инфо по классу

НО для добавление МапАктион нужен КоммандИнфо класса. Для этого реалезуем Сатическую функцию получения:

template<typename InBTCommand>
const TSharedPtr<FUICommandInfo>& FBuildingToolsEditorCommands::GetUICommandInfo()
{
	static TSharedPtr<FUICommandInfo> UICommandInfoInvalid;
	for (UBTCommand* Command : Get().Commands)
	{
		if (Command->IsA(InBTCommand::StaticClass()))
		{
			return Command->GetUICommandInfo();
		}
	}
	return UICommandInfoInvalid;
}

Добавление IInputProcessor

Самый надежный способ принять инпут. Независимо от того, какой модуль сейчас в фокусе - Пре Инпут обработается перед этим. Для этого есть и ИнпутПроцессор. Создадим его:

class FBTInputProcessor : public IInputProcessor, public TSharedFromThis<FBTInputProcessor>
{
public:
	FBTInputProcessor(){};
	virtual ~FBTInputProcessor(){};

//Если не добавить тик, он будет считаться абстрактным
	virtual void Tick(const float DeltaTime, FSlateApplication& SlateApp, TSharedRef<ICursor> Cursor) override;
	
	/** Key down input */
	virtual bool HandleKeyDownEvent(FSlateApplication& SlateApp, const FKeyEvent& InKeyEvent) override;

	/** Key up input */
	virtual bool HandleKeyUpEvent(FSlateApplication& SlateApp, const FKeyEvent& InKeyEvent) override;

public:
	TDelegate<bool(FKey InKey, EInputEvent InEvent)> PreInputKeyDelegate;
};

Теперь зарегистрируем его. Если функция, зареганная в делегате вернет true - ипут дальше не пойдет. Если false - ипут идет дальше

	void FBuildingToolsEdMode::Enter()
{
	FEdMode::Enter();

	InputProcessor = MakeShareable(new FBTInputProcessor());
	FSlateApplication::Get().RegisterInputPreProcessor(InputProcessor);
	InputProcessor->PreInputKeyDelegate.BindSP(this, &FBuildingToolsEdMode::PreInputKey);
	}

Более простой способ

Есть обычный Pre Input Pressed делегат. Но он только на Pressed работает

FSlateApplication::Get().OnApplicationPreInputKeyDownListener();

Last updated