From a90f217ab38a3ce0470aa2c4fc9d4f1dccb4a88c Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 30 Jul 2018 22:05:59 +0100 Subject: [PATCH] Added 'ImGui.SwitchInputMode' command and configurable key binding to DebugExecBindings. --- Source/ImGui/Private/ImGuiInputHandler.cpp | 30 ++++++ Source/ImGui/Private/ImGuiModule.cpp | 15 +++ Source/ImGui/Private/ImGuiSettings.cpp | 25 ++++- Source/ImGui/Private/ImGuiSettings.h | 38 ++++++++ .../Private/Utilities/DebugExecBindings.cpp | 95 +++++++++++++++++++ .../Private/Utilities/DebugExecBindings.h | 10 ++ Source/ImGui/Public/ImGuiInputHandler.h | 10 +- 7 files changed, 219 insertions(+), 4 deletions(-) create mode 100644 Source/ImGui/Private/Utilities/DebugExecBindings.cpp create mode 100644 Source/ImGui/Private/Utilities/DebugExecBindings.h diff --git a/Source/ImGui/Private/ImGuiInputHandler.cpp b/Source/ImGui/Private/ImGuiInputHandler.cpp index d1e78bb..de5fd96 100644 --- a/Source/ImGui/Private/ImGuiInputHandler.cpp +++ b/Source/ImGui/Private/ImGuiInputHandler.cpp @@ -6,6 +6,7 @@ #include "ImGuiContextProxy.h" #include "ImGuiModuleManager.h" +#include "ImGuiSettings.h" #include #include @@ -25,6 +26,13 @@ DEFINE_LOG_CATEGORY_STATIC(LogImGuiInputHandler, Warning, All); FImGuiInputResponse UImGuiInputHandler::OnKeyDown(const FKeyEvent& KeyEvent) { + // If this is an input mode switch event then handle it here and consume. + if (IsSwitchInputModeEvent(KeyEvent)) + { + FImGuiModule::Get().ToggleInputMode(); + return FImGuiInputResponse().RequestConsume(); + } + // Ignore console events, so we don't block it from opening. if (IsConsoleEvent(KeyEvent)) { @@ -68,6 +76,28 @@ bool UImGuiInputHandler::IsStopPlaySessionEvent(const FKeyEvent& KeyEvent) const } #endif // WITH_EDITOR +namespace +{ + bool IsMatching(ECheckBoxState CheckBoxState, bool bValue) + { + return (CheckBoxState == ECheckBoxState::Undetermined) || ((CheckBoxState == ECheckBoxState::Checked) == bValue); + } + + bool AreModifiersMatching(const FImGuiKeyInfo& KeyInfo, const FKeyEvent& KeyEvent) + { + return IsMatching(KeyInfo.Shift, KeyEvent.IsShiftDown()) + && IsMatching(KeyInfo.Ctrl, KeyEvent.IsControlDown()) + && IsMatching(KeyInfo.Alt, KeyEvent.IsAltDown()) + && IsMatching(KeyInfo.Cmd, KeyEvent.IsCommandDown()); + } +} + +bool UImGuiInputHandler::IsSwitchInputModeEvent(const FKeyEvent& KeyEvent) const +{ + const FImGuiKeyInfo KeyInfo = GetDefault()->GetSwitchInputModeKey(); + return (KeyEvent.GetKey() == KeyInfo.Key) && AreModifiersMatching(KeyInfo, KeyEvent); +} + bool UImGuiInputHandler::HasImGuiActiveItem() const { FImGuiContextProxy* ContextProxy = ModuleManager->GetContextManager().GetContextProxy(ContextIndex); diff --git a/Source/ImGui/Private/ImGuiModule.cpp b/Source/ImGui/Private/ImGuiModule.cpp index 16fb50d..83f20fb 100644 --- a/Source/ImGui/Private/ImGuiModule.cpp +++ b/Source/ImGui/Private/ImGuiModule.cpp @@ -22,6 +22,21 @@ namespace CVars extern TAutoConsoleVariable ShowDemo; } +namespace Commands +{ + const TCHAR* SwitchInputMode = TEXT("ImGui.SwitchInputMode"); +} + +void SwitchImGuiInputMode() +{ + FImGuiModule::Get().ToggleInputMode(); +} + +FAutoConsoleCommand SwitchInputModeCommand = FAutoConsoleCommand( + Commands::SwitchInputMode, + TEXT("Changes ImGui input mode."), + FConsoleCommandDelegate::CreateStatic(SwitchImGuiInputMode)); + struct EDelegateCategory { enum diff --git a/Source/ImGui/Private/ImGuiSettings.cpp b/Source/ImGui/Private/ImGuiSettings.cpp index b64e338..534dd73 100644 --- a/Source/ImGui/Private/ImGuiSettings.cpp +++ b/Source/ImGui/Private/ImGuiSettings.cpp @@ -3,6 +3,7 @@ #include "ImGuiPrivatePCH.h" #include "ImGuiSettings.h" +#include "Utilities/DebugExecBindings.h" UImGuiSettings::UImGuiSettings() @@ -19,6 +20,21 @@ UImGuiSettings::~UImGuiSettings() #endif } +namespace Commands +{ + extern const TCHAR* SwitchInputMode; +} + +void UImGuiSettings::PostInitProperties() +{ + Super::PostInitProperties(); + + // Instead of saving binding to input config, we manually update DebugExecBindings from here. This has an advantage + // that there is no ambiguity where settings are stored and more importantly, it works out of the box in packed + // and staged builds. + DebugExecBindings::UpdatePlayerInputs(SwitchInputModeKey, Commands::SwitchInputMode); +} + #if WITH_EDITOR void UImGuiSettings::RegisterPropertyChangedDelegate() @@ -38,13 +54,16 @@ void UImGuiSettings::OnPropertyChanged(class UObject* ObjectBeingModified, struc { if (ObjectBeingModified == this) { - static const FName ImGuiInputHandlerPropertyName = GET_MEMBER_NAME_CHECKED(UImGuiSettings, ImGuiInputHandlerClass); - const FName UpdatedPropertyName = PropertyChangedEvent.MemberProperty ? PropertyChangedEvent.MemberProperty->GetFName() : NAME_None; - if (UpdatedPropertyName == ImGuiInputHandlerPropertyName) + + if (UpdatedPropertyName == GET_MEMBER_NAME_CHECKED(UImGuiSettings, ImGuiInputHandlerClass)) { OnImGuiInputHandlerClassChanged.Broadcast(); } + else if (UpdatedPropertyName == GET_MEMBER_NAME_CHECKED(UImGuiSettings, SwitchInputModeKey)) + { + DebugExecBindings::UpdatePlayerInputs(SwitchInputModeKey, Commands::SwitchInputMode); + } } } diff --git a/Source/ImGui/Private/ImGuiSettings.h b/Source/ImGui/Private/ImGuiSettings.h index 315e7fe..39daccd 100644 --- a/Source/ImGui/Private/ImGuiSettings.h +++ b/Source/ImGui/Private/ImGuiSettings.h @@ -19,6 +19,31 @@ #include "ImGuiSettings.generated.h" +/** + * Struct containing key information that can be used for key binding. Using 'Undetermined' value for modifier keys + * means that those keys should be ignored when testing for a match. + */ +USTRUCT() +struct FImGuiKeyInfo +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere) + FKey Key; + + UPROPERTY(EditAnywhere) + ECheckBoxState Shift = ECheckBoxState::Undetermined; + + UPROPERTY(EditAnywhere) + ECheckBoxState Ctrl = ECheckBoxState::Undetermined; + + UPROPERTY(EditAnywhere) + ECheckBoxState Alt = ECheckBoxState::Undetermined; + + UPROPERTY(EditAnywhere) + ECheckBoxState Cmd = ECheckBoxState::Undetermined; +}; + // Settings for ImGui module. UCLASS(config=ImGui, defaultconfig) class UImGuiSettings : public UObject @@ -33,9 +58,14 @@ public: // Path to custom implementation of ImGui Input Handler. const FStringClassReference& GetImGuiInputHandlerClass() const { return ImGuiInputHandlerClass; } + // Get mapping for 'ImGui.SwitchInputMode' command. + const FImGuiKeyInfo& GetSwitchInputModeKey() const { return SwitchInputModeKey; } + // Delegate raised when ImGuiInputHandlerClass property has changed. FSimpleMulticastDelegate OnImGuiInputHandlerClassChanged; + virtual void PostInitProperties() override; + protected: // Path to own implementation of ImGui Input Handler allowing to customize handling of keyboard and gamepad input. @@ -43,8 +73,16 @@ protected: UPROPERTY(EditAnywhere, config, Category = "Input", meta = (MetaClass = "ImGuiInputHandler")) FStringClassReference ImGuiInputHandlerClass; + // Define a custom key binding to 'ImGui.SwitchInputMode' command. Mapping will be only set if Key property in this + // structure is set to a valid key. Modifier keys can be either completely ignored (Undetermined), required to be + // pressed (Checked) or required to be not pressed (Unchecked). + UPROPERTY(EditAnywhere, config, Category = "Input") + FImGuiKeyInfo SwitchInputModeKey; + private: + void UpdateSwitchInputModeBinding(); + #if WITH_EDITOR void RegisterPropertyChangedDelegate(); diff --git a/Source/ImGui/Private/Utilities/DebugExecBindings.cpp b/Source/ImGui/Private/Utilities/DebugExecBindings.cpp new file mode 100644 index 0000000..dcccff3 --- /dev/null +++ b/Source/ImGui/Private/Utilities/DebugExecBindings.cpp @@ -0,0 +1,95 @@ +// Distributed under the MIT License (MIT) (see accompanying LICENSE file) + +#include "ImGuiPrivatePCH.h" + +#include "DebugExecBindings.h" +#include "ImGuiSettings.h" + +#include + + +namespace +{ + FKeyBind CreateKeyBind(const FImGuiKeyInfo& KeyInfo, const FString& Command) + { + FKeyBind KeyBind; + KeyBind.Command = Command; + KeyBind.Key = KeyInfo.Key; + KeyBind.bDisabled = false; + +#define FILL_MODIFIER_DATA(KeyInfoProperty, BindProperty, BindIgnoreProperty)\ + if (KeyInfo.KeyInfoProperty == ECheckBoxState::Undetermined)\ + {\ + KeyBind.BindProperty = KeyBind.BindIgnoreProperty = false;\ + }\ + else\ + {\ + KeyBind.BindProperty = (KeyInfo.KeyInfoProperty == ECheckBoxState::Checked);\ + KeyBind.BindIgnoreProperty = !KeyBind.BindProperty;\ + } + + FILL_MODIFIER_DATA(Shift, Shift, bIgnoreShift); + FILL_MODIFIER_DATA(Ctrl, Control, bIgnoreCtrl); + FILL_MODIFIER_DATA(Alt, Alt, bIgnoreAlt); + FILL_MODIFIER_DATA(Cmd, Cmd, bIgnoreCmd); + +#undef FILL_MODIFIER_DATA + + return KeyBind; + } + + bool IsBindable(const FKey& Key) + { + return Key.IsValid() && Key != EKeys::AnyKey && !Key.IsFloatAxis() && !Key.IsVectorAxis() + && !Key.IsGamepadKey() && !Key.IsModifierKey() && !Key.IsMouseButton(); + } + + void UpdatePlayerInput(UPlayerInput* PlayerInput, const FKeyBind& KeyBind) + { + const int32 Index = PlayerInput->DebugExecBindings.IndexOfByPredicate([&](const FKeyBind& PlayerKeyBind) + { + return PlayerKeyBind.Command.Equals(KeyBind.Command, ESearchCase::IgnoreCase); + }); + + if (IsBindable(KeyBind.Key)) + { + if (Index != INDEX_NONE) + { + PlayerInput->DebugExecBindings[Index] = KeyBind; + } + else + { + PlayerInput->DebugExecBindings.Add(KeyBind); + } + } + else + { + if (Index != INDEX_NONE) + { + PlayerInput->DebugExecBindings.RemoveAt(Index); + } + } + } +} + +namespace DebugExecBindings +{ + void UpdatePlayerInputs(const FImGuiKeyInfo& KeyInfo, const FString& Command) + { + checkf(!Command.IsEmpty(), TEXT("Empty command.")); + + const FKeyBind KeyBind = CreateKeyBind(KeyInfo, Command); + + // Update default player input, so changes will be visible in all PIE sessions created after this point. + if (UPlayerInput* DefaultPlayerInput = GetMutableDefault()) + { + UpdatePlayerInput(DefaultPlayerInput, KeyBind); + } + + // Update all existing player inputs to see changes in running PIE session. + for (TObjectIterator It; It; ++It) + { + UpdatePlayerInput(*It, KeyBind); + } + } +} diff --git a/Source/ImGui/Private/Utilities/DebugExecBindings.h b/Source/ImGui/Private/Utilities/DebugExecBindings.h new file mode 100644 index 0000000..905ae9e --- /dev/null +++ b/Source/ImGui/Private/Utilities/DebugExecBindings.h @@ -0,0 +1,10 @@ +// Distributed under the MIT License (MIT) (see accompanying LICENSE file) + +#pragma once + +struct FImGuiKeyInfo; + +namespace DebugExecBindings +{ + void UpdatePlayerInputs(const FImGuiKeyInfo& KeyInfo, const FString& Command); +} diff --git a/Source/ImGui/Public/ImGuiInputHandler.h b/Source/ImGui/Public/ImGuiInputHandler.h index 34f5c10..5622d66 100644 --- a/Source/ImGui/Public/ImGuiInputHandler.h +++ b/Source/ImGui/Public/ImGuiInputHandler.h @@ -158,7 +158,7 @@ protected: * Checks whether this is a key event that can open console. * * @param KeyEvent - Key event to test. - * @returns True, if this key event can open console. + * @returns True, if this key event can open console. */ bool IsConsoleEvent(const FKeyEvent& KeyEvent) const; @@ -172,6 +172,14 @@ protected: bool IsStopPlaySessionEvent(const FKeyEvent& KeyEvent) const; #endif + /** + * Checks whether this key event can switch ImGui input mode (as defined in settings). + * + * @param KeyEvent - Key event to test. + * @returns True, if this key is bound to 'ImGui.SwitchInputMode' command that switches ImGui input mode. + */ + bool IsSwitchInputModeEvent(const FKeyEvent& KeyEvent) const; + /** * Checks whether corresponding ImGui context has an active item (holding cursor focus). *