Added 'ImGui.SwitchInputMode' command and configurable key binding to DebugExecBindings.

This commit is contained in:
Sebastian 2018-07-30 22:05:59 +01:00
parent eabff7b839
commit a90f217ab3
7 changed files with 219 additions and 4 deletions

View File

@ -6,6 +6,7 @@
#include "ImGuiContextProxy.h"
#include "ImGuiModuleManager.h"
#include "ImGuiSettings.h"
#include <Engine/Console.h>
#include <Input/Events.h>
@ -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<UImGuiSettings>()->GetSwitchInputModeKey();
return (KeyEvent.GetKey() == KeyInfo.Key) && AreModifiersMatching(KeyInfo, KeyEvent);
}
bool UImGuiInputHandler::HasImGuiActiveItem() const
{
FImGuiContextProxy* ContextProxy = ModuleManager->GetContextManager().GetContextProxy(ContextIndex);

View File

@ -22,6 +22,21 @@ namespace CVars
extern TAutoConsoleVariable<int32> 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

View File

@ -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);
}
}
}

View File

@ -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();

View File

@ -0,0 +1,95 @@
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
#include "ImGuiPrivatePCH.h"
#include "DebugExecBindings.h"
#include "ImGuiSettings.h"
#include <GameFramework/PlayerInput.h>
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<UPlayerInput>())
{
UpdatePlayerInput(DefaultPlayerInput, KeyBind);
}
// Update all existing player inputs to see changes in running PIE session.
for (TObjectIterator<UPlayerInput> It; It; ++It)
{
UpdatePlayerInput(*It, KeyBind);
}
}
}

View File

@ -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);
}

View File

@ -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).
*