mirror of
https://github.com/kevinporetti/UnrealImGui.git
synced 2025-01-18 08:20:32 +00:00
Added ImGui Input Handler and ImGui Settings:
- Added ImGui Input Handler class that allows to customize handling of the keyboard and gamepad input. - Added ImGui Settings to allow specify custom input handler implementation. - Added editor support for ImGui Settings. - Input handling in ImGui Widget is divided for querying the handler (more customizable) and actual input processing based on the handler’s response (fixed and simplified). - Removed a need for checking console state in different input handling functions in ImGui Widget by suppressing keyboard focus support when console is opened.
This commit is contained in:
parent
6ddc7f2805
commit
5968c3ce84
@ -11,7 +11,13 @@ public class ImGui : ModuleRules
|
||||
public ImGui(TargetInfo Target)
|
||||
#endif
|
||||
{
|
||||
|
||||
|
||||
#if WITH_FORWARDED_MODULE_RULES_CTOR
|
||||
bool bBuildEditor = Target.bBuildEditor;
|
||||
#else
|
||||
bool bBuildEditor = (Target.Type == TargetRules.TargetType.Editor);
|
||||
#endif
|
||||
|
||||
PublicIncludePaths.AddRange(
|
||||
new string[] {
|
||||
"ImGui/Public",
|
||||
@ -19,8 +25,8 @@ public class ImGui : ModuleRules
|
||||
// ... add public include paths required here ...
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
PrivateIncludePaths.AddRange(
|
||||
new string[] {
|
||||
"ImGui/Private",
|
||||
@ -28,8 +34,8 @@ public class ImGui : ModuleRules
|
||||
// ... add other private include paths required here ...
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
@ -38,8 +44,8 @@ public class ImGui : ModuleRules
|
||||
// ... add other public dependencies that you statically link with here ...
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
@ -51,8 +57,19 @@ public class ImGui : ModuleRules
|
||||
// ... add private dependencies that you statically link with here ...
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
if (bBuildEditor)
|
||||
{
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Settings",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
DynamicallyLoadedModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
|
106
Source/ImGui/Private/Editor/ImGuiEditor.cpp
Normal file
106
Source/ImGui/Private/Editor/ImGuiEditor.cpp
Normal file
@ -0,0 +1,106 @@
|
||||
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
|
||||
|
||||
#include "ImGuiPrivatePCH.h"
|
||||
|
||||
#if WITH_EDITOR
|
||||
|
||||
#include "ImGuiEditor.h"
|
||||
|
||||
#include "ImGuiSettings.h"
|
||||
|
||||
#include <ISettingsModule.h>
|
||||
|
||||
|
||||
#define LOCTEXT_NAMESPACE "ImGuiEditor"
|
||||
|
||||
#define SETTINGS_CONTAINER TEXT("Project"), TEXT("Plugins"), TEXT("ImGui")
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
ISettingsModule* GetSettingsModule()
|
||||
{
|
||||
return FModuleManager::GetModulePtr<ISettingsModule>("Settings");
|
||||
}
|
||||
}
|
||||
|
||||
FImGuiEditor::FImGuiEditor()
|
||||
{
|
||||
Register();
|
||||
|
||||
// As a side effect of being part of the ImGui module, we need to support deferred registration (only executed if
|
||||
// module is loaded manually at the very early stage).
|
||||
if (!IsRegistrationCompleted())
|
||||
{
|
||||
CreateRegistrator();
|
||||
}
|
||||
}
|
||||
|
||||
FImGuiEditor::~FImGuiEditor()
|
||||
{
|
||||
Unregister();
|
||||
}
|
||||
|
||||
void FImGuiEditor::Register()
|
||||
{
|
||||
// Only register after UImGuiSettings class is initialized (necessary to check in early loading stages).
|
||||
if (!bSettingsRegistered && UImGuiSettings::StaticClass()->IsValidLowLevelFast())
|
||||
{
|
||||
if (ISettingsModule* SettingsModule = GetSettingsModule())
|
||||
{
|
||||
bSettingsRegistered = true;
|
||||
|
||||
SettingsModule->RegisterSettings(SETTINGS_CONTAINER,
|
||||
LOCTEXT("ImGuiSettingsName", "ImGui"),
|
||||
LOCTEXT("ImGuiSettingsDescription", "Configure the Unreal ImGui plugin."),
|
||||
GetMutableDefault<UImGuiSettings>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FImGuiEditor::Unregister()
|
||||
{
|
||||
if (bSettingsRegistered)
|
||||
{
|
||||
bSettingsRegistered = false;
|
||||
|
||||
if (ISettingsModule* SettingsModule = GetSettingsModule())
|
||||
{
|
||||
SettingsModule->UnregisterSettings(SETTINGS_CONTAINER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FImGuiEditor::CreateRegistrator()
|
||||
{
|
||||
if (!RegistratorHandle.IsValid())
|
||||
{
|
||||
RegistratorHandle = FModuleManager::Get().OnModulesChanged().AddLambda([this](FName Name, EModuleChangeReason Reason)
|
||||
{
|
||||
if (Reason == EModuleChangeReason::ModuleLoaded)
|
||||
{
|
||||
Register();
|
||||
}
|
||||
|
||||
if (IsRegistrationCompleted())
|
||||
{
|
||||
ReleaseRegistrator();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void FImGuiEditor::ReleaseRegistrator()
|
||||
{
|
||||
if (RegistratorHandle.IsValid())
|
||||
{
|
||||
FModuleManager::Get().OnModulesChanged().Remove(RegistratorHandle);
|
||||
RegistratorHandle.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#undef SETTINGS_CONTAINER
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
#endif // WITH_EDITOR
|
30
Source/ImGui/Private/Editor/ImGuiEditor.h
Normal file
30
Source/ImGui/Private/Editor/ImGuiEditor.h
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#if WITH_EDITOR
|
||||
|
||||
// Registers module's settings in editor (due to a small size of this code we don't use a separate editor module).
|
||||
class FImGuiEditor
|
||||
{
|
||||
public:
|
||||
|
||||
FImGuiEditor();
|
||||
~FImGuiEditor();
|
||||
|
||||
private:
|
||||
|
||||
bool IsRegistrationCompleted() const { return bSettingsRegistered; }
|
||||
|
||||
void Register();
|
||||
void Unregister();
|
||||
|
||||
void CreateRegistrator();
|
||||
void ReleaseRegistrator();
|
||||
|
||||
FDelegateHandle RegistratorHandle;
|
||||
|
||||
bool bSettingsRegistered = false;
|
||||
};
|
||||
|
||||
#endif // WITH_EDITOR
|
42
Source/ImGui/Private/ImGuiInputHandler.cpp
Normal file
42
Source/ImGui/Private/ImGuiInputHandler.cpp
Normal file
@ -0,0 +1,42 @@
|
||||
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
|
||||
|
||||
#include "ImGuiPrivatePCH.h"
|
||||
|
||||
#include "ImGuiInputHandler.h"
|
||||
|
||||
#include "ImGuiContextProxy.h"
|
||||
#include "ImGuiModuleManager.h"
|
||||
|
||||
#include <Engine/Console.h>
|
||||
#include <Input/Events.h>
|
||||
|
||||
|
||||
FImGuiInputResponse UImGuiInputHandler::OnKeyDown(const FKeyEvent& KeyEvent)
|
||||
{
|
||||
// Ignore console open events, so we don't block it from opening.
|
||||
if (KeyEvent.GetKey() == EKeys::Tilde)
|
||||
{
|
||||
return FImGuiInputResponse{ false, false };
|
||||
}
|
||||
|
||||
// Ignore escape event, if they are not meant for ImGui control.
|
||||
if (KeyEvent.GetKey() == EKeys::Escape && !HasImGuiActiveItem())
|
||||
{
|
||||
return FImGuiInputResponse{ false, false };
|
||||
}
|
||||
|
||||
return DefaultResponse();
|
||||
}
|
||||
|
||||
bool UImGuiInputHandler::HasImGuiActiveItem() const
|
||||
{
|
||||
FImGuiContextProxy* ContextProxy = ModuleManager->GetContextManager().GetContextProxy(ContextIndex);
|
||||
return ContextProxy && ContextProxy->HasActiveItem();
|
||||
}
|
||||
|
||||
void UImGuiInputHandler::Initialize(FImGuiModuleManager* InModuleManager, UGameViewportClient* InGameViewport, int32 InContextIndex)
|
||||
{
|
||||
ModuleManager = InModuleManager;
|
||||
GameViewport = InGameViewport;
|
||||
ContextIndex = InContextIndex;
|
||||
}
|
55
Source/ImGui/Private/ImGuiInputHandlerFactory.cpp
Normal file
55
Source/ImGui/Private/ImGuiInputHandlerFactory.cpp
Normal file
@ -0,0 +1,55 @@
|
||||
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
|
||||
|
||||
#include "ImGuiPrivatePCH.h"
|
||||
|
||||
#include "ImGuiInputHandlerFactory.h"
|
||||
|
||||
#include "ImGuiInputHandler.h"
|
||||
#include "ImGuiSettings.h"
|
||||
|
||||
|
||||
DEFINE_LOG_CATEGORY_STATIC(LogImGuiInputHandlerFactory, Warning, All);
|
||||
|
||||
UImGuiInputHandler* FImGuiInputHandlerFactory::NewHandler(FImGuiModuleManager* ModuleManager, UGameViewportClient* GameViewport, int32 ContextIndex)
|
||||
{
|
||||
UClass* HandlerClass = nullptr;
|
||||
if (UImGuiSettings* Settings = GetMutableDefault<UImGuiSettings>())
|
||||
{
|
||||
const auto& HandlerClassReference = Settings->GetImGuiInputHandlerClass();
|
||||
if (HandlerClassReference.IsValid())
|
||||
{
|
||||
HandlerClass = HandlerClassReference.TryLoadClass<UImGuiInputHandler>();
|
||||
|
||||
if (!HandlerClass)
|
||||
{
|
||||
UE_LOG(LogImGuiInputHandlerFactory, Error, TEXT("Couldn't load ImGui Input Handler class '%s'."), *HandlerClassReference.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!HandlerClass)
|
||||
{
|
||||
HandlerClass = UImGuiInputHandler::StaticClass();
|
||||
}
|
||||
|
||||
UImGuiInputHandler* Handler = NewObject<UImGuiInputHandler>(GameViewport, HandlerClass);
|
||||
if (Handler)
|
||||
{
|
||||
Handler->Initialize(ModuleManager, GameViewport, ContextIndex);
|
||||
Handler->AddToRoot();
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogImGuiInputHandlerFactory, Error, TEXT("Failed attempt to create Input Handler: HandlerClass = %s."), *GetNameSafe(HandlerClass));
|
||||
}
|
||||
|
||||
return Handler;
|
||||
}
|
||||
|
||||
void FImGuiInputHandlerFactory::ReleaseHandler(UImGuiInputHandler* Handler)
|
||||
{
|
||||
if (Handler)
|
||||
{
|
||||
Handler->RemoveFromRoot();
|
||||
}
|
||||
}
|
16
Source/ImGui/Private/ImGuiInputHandlerFactory.h
Normal file
16
Source/ImGui/Private/ImGuiInputHandlerFactory.h
Normal file
@ -0,0 +1,16 @@
|
||||
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
|
||||
|
||||
#pragma once
|
||||
|
||||
class FImGuiModuleManager;
|
||||
class UGameViewportClient;
|
||||
class UImGuiInputHandler;
|
||||
|
||||
class FImGuiInputHandlerFactory
|
||||
{
|
||||
public:
|
||||
|
||||
static UImGuiInputHandler* NewHandler(FImGuiModuleManager* ModuleManager, UGameViewportClient* GameViewport, int32 ContextIndex);
|
||||
|
||||
static void ReleaseHandler(UImGuiInputHandler* Handler);
|
||||
};
|
@ -6,6 +6,10 @@
|
||||
#include "Utilities/WorldContext.h"
|
||||
#include "Utilities/WorldContextIndex.h"
|
||||
|
||||
#if WITH_EDITOR
|
||||
#include "Editor/ImGuiEditor.h"
|
||||
#endif
|
||||
|
||||
#include <IPluginManager.h>
|
||||
|
||||
|
||||
@ -32,6 +36,10 @@ struct EDelegateCategory
|
||||
|
||||
static FImGuiModuleManager* ModuleManager = nullptr;
|
||||
|
||||
#if WITH_EDITOR
|
||||
static FImGuiEditor* ModuleEditor = nullptr;
|
||||
#endif
|
||||
|
||||
#if WITH_EDITOR
|
||||
FImGuiDelegateHandle FImGuiModule::AddEditorImGuiDelegate(const FImGuiDelegate& Delegate)
|
||||
{
|
||||
@ -91,17 +99,28 @@ void FImGuiModule::RemoveImGuiDelegate(const FImGuiDelegateHandle& Handle)
|
||||
|
||||
void FImGuiModule::StartupModule()
|
||||
{
|
||||
checkf(!ModuleManager, TEXT("Instance of Module Manager already exists. Instance should be created only during module startup."));
|
||||
// Create managers that implements module logic.
|
||||
|
||||
// Create module manager that implements modules logic.
|
||||
checkf(!ModuleManager, TEXT("Instance of the Module Manager already exists. Instance should be created only during module startup."));
|
||||
ModuleManager = new FImGuiModuleManager();
|
||||
|
||||
#if WITH_EDITOR
|
||||
checkf(!ModuleEditor, TEXT("Instance of the Module Editor already exists. Instance should be created only during module startup."));
|
||||
ModuleEditor = new FImGuiEditor();
|
||||
#endif
|
||||
}
|
||||
|
||||
void FImGuiModule::ShutdownModule()
|
||||
{
|
||||
checkf(ModuleManager, TEXT("Null Module Manager. Manager instance should be deleted during module shutdown."));
|
||||
// Before we shutdown we need to delete managers that will do all the necessary cleanup.
|
||||
|
||||
// Before we shutdown we need to delete manager that will do all necessary cleanup.
|
||||
#if WITH_EDITOR
|
||||
checkf(ModuleEditor, TEXT("Null Module Editor. Module editor instance should be deleted during module shutdown."));
|
||||
delete ModuleEditor;
|
||||
ModuleEditor = nullptr;
|
||||
#endif
|
||||
|
||||
checkf(ModuleManager, TEXT("Null Module Manager. Module manager instance should be deleted during module shutdown."));
|
||||
delete ModuleManager;
|
||||
ModuleManager = nullptr;
|
||||
}
|
||||
|
51
Source/ImGui/Private/ImGuiSettings.cpp
Normal file
51
Source/ImGui/Private/ImGuiSettings.cpp
Normal file
@ -0,0 +1,51 @@
|
||||
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
|
||||
|
||||
#include "ImGuiPrivatePCH.h"
|
||||
|
||||
#include "ImGuiSettings.h"
|
||||
|
||||
|
||||
UImGuiSettings::UImGuiSettings()
|
||||
{
|
||||
#if WITH_EDITOR
|
||||
RegisterPropertyChangedDelegate();
|
||||
#endif
|
||||
}
|
||||
|
||||
UImGuiSettings::~UImGuiSettings()
|
||||
{
|
||||
#if WITH_EDITOR
|
||||
UnregisterPropertyChangedDelegate();
|
||||
#endif
|
||||
}
|
||||
|
||||
#if WITH_EDITOR
|
||||
|
||||
void UImGuiSettings::RegisterPropertyChangedDelegate()
|
||||
{
|
||||
if (!FCoreUObjectDelegates::OnObjectPropertyChanged.IsBoundToObject(this))
|
||||
{
|
||||
FCoreUObjectDelegates::OnObjectPropertyChanged.AddUObject(this, &UImGuiSettings::OnPropertyChanged);
|
||||
}
|
||||
}
|
||||
|
||||
void UImGuiSettings::UnregisterPropertyChangedDelegate()
|
||||
{
|
||||
FCoreUObjectDelegates::OnObjectPropertyChanged.RemoveAll(this);
|
||||
}
|
||||
|
||||
void UImGuiSettings::OnPropertyChanged(class UObject* ObjectBeingModified, struct FPropertyChangedEvent& PropertyChangedEvent)
|
||||
{
|
||||
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)
|
||||
{
|
||||
OnImGuiInputHandlerClassChanged.Broadcast();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // WITH_EDITOR
|
56
Source/ImGui/Private/ImGuiSettings.h
Normal file
56
Source/ImGui/Private/ImGuiSettings.h
Normal file
@ -0,0 +1,56 @@
|
||||
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ImGuiInputHandler.h"
|
||||
|
||||
#include <Delegates/Delegate.h>
|
||||
#include <UObject/Object.h>
|
||||
|
||||
// Select right soft class reference header to avoid warning (new header contains FSoftClassPath to FStringClassReference
|
||||
// typedef, so we will use that as a common denominator).
|
||||
#include <Runtime/Launch/Resources/Version.h>
|
||||
#if (ENGINE_MAJOR_VERSION < 4 || (ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION < 18))
|
||||
#include <StringClassReference.h>
|
||||
#else
|
||||
#include <UObject/SoftObjectPath.h>
|
||||
#endif
|
||||
|
||||
#include "ImGuiSettings.generated.h"
|
||||
|
||||
|
||||
// Settings for ImGui module.
|
||||
UCLASS(config=ImGui, defaultconfig)
|
||||
class UImGuiSettings : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
UImGuiSettings();
|
||||
~UImGuiSettings();
|
||||
|
||||
// Path to custom implementation of ImGui Input Handler.
|
||||
const FStringClassReference& GetImGuiInputHandlerClass() const { return ImGuiInputHandlerClass; }
|
||||
|
||||
// Delegate raised when ImGuiInputHandlerClass property has changed.
|
||||
FSimpleMulticastDelegate OnImGuiInputHandlerClassChanged;
|
||||
|
||||
protected:
|
||||
|
||||
// Path to own implementation of ImGui Input Handler allowing to customize handling of keyboard and gamepad input.
|
||||
// If not set then default handler is used.
|
||||
UPROPERTY(EditAnywhere, config, Category = "Input", meta = (MetaClass = "ImGuiInputHandler"))
|
||||
FStringClassReference ImGuiInputHandlerClass;
|
||||
|
||||
private:
|
||||
|
||||
#if WITH_EDITOR
|
||||
|
||||
void RegisterPropertyChangedDelegate();
|
||||
void UnregisterPropertyChangedDelegate();
|
||||
|
||||
void OnPropertyChanged(class UObject* ObjectBeingModified, struct FPropertyChangedEvent& PropertyChangedEvent);
|
||||
|
||||
#endif // WITH_EDITOR
|
||||
};
|
@ -7,8 +7,11 @@
|
||||
#include "ImGuiContextManager.h"
|
||||
#include "ImGuiContextProxy.h"
|
||||
#include "ImGuiImplementation.h"
|
||||
#include "ImGuiInputHandler.h"
|
||||
#include "ImGuiInputHandlerFactory.h"
|
||||
#include "ImGuiInteroperability.h"
|
||||
#include "ImGuiModuleManager.h"
|
||||
#include "ImGuiSettings.h"
|
||||
#include "TextureManager.h"
|
||||
#include "Utilities/Arrays.h"
|
||||
#include "Utilities/ScopeGuards.h"
|
||||
@ -106,11 +109,19 @@ void SImGuiWidget::Construct(const FArguments& InArgs)
|
||||
checkf(ContextProxy, TEXT("Missing context during widget construction: ContextIndex = %d"), ContextIndex);
|
||||
ContextProxy->OnDraw().AddRaw(this, &SImGuiWidget::OnDebugDraw);
|
||||
ContextProxy->SetInputState(&InputState);
|
||||
|
||||
// Create ImGui Input Handler.
|
||||
CreateInputHandler();
|
||||
RegisterInputHandlerChangedDelegate();
|
||||
}
|
||||
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
|
||||
SImGuiWidget::~SImGuiWidget()
|
||||
{
|
||||
// Release ImGui Input Handler.
|
||||
UnregisterInputHandlerChangedDelegate();
|
||||
ReleaseInputHandler();
|
||||
|
||||
// Remove binding between this widget and its context proxy.
|
||||
if (auto* ContextProxy = ModuleManager->GetContextManager().GetContextProxy(ContextIndex))
|
||||
{
|
||||
@ -142,16 +153,23 @@ void SImGuiWidget::Tick(const FGeometry& AllottedGeometry, const double InCurren
|
||||
UpdateInputEnabled();
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
FReply ToSlateReply(const FImGuiInputResponse& HandlingResponse)
|
||||
{
|
||||
return HandlingResponse.HasConsumeRequest() ? FReply::Handled() : FReply::Unhandled();
|
||||
}
|
||||
}
|
||||
|
||||
FReply SImGuiWidget::OnKeyChar(const FGeometry& MyGeometry, const FCharacterEvent& CharacterEvent)
|
||||
{
|
||||
if (IsConsoleOpened())
|
||||
const FImGuiInputResponse Response = InputHandler->OnKeyChar(CharacterEvent);
|
||||
if (Response.HasProcessingRequest())
|
||||
{
|
||||
return FReply::Unhandled();
|
||||
InputState.AddCharacter(CharacterEvent.GetCharacter());
|
||||
}
|
||||
|
||||
InputState.AddCharacter(CharacterEvent.GetCharacter());
|
||||
|
||||
return FReply::Handled();
|
||||
return ToSlateReply(Response);
|
||||
}
|
||||
|
||||
FReply SImGuiWidget::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& KeyEvent)
|
||||
@ -160,9 +178,13 @@ FReply SImGuiWidget::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& Key
|
||||
{
|
||||
if (InputState.IsGamepadNavigationEnabled())
|
||||
{
|
||||
InputState.SetGamepadNavigationKey(KeyEvent, true);
|
||||
const FImGuiInputResponse Response = InputHandler->OnGamepadKeyDown(KeyEvent);
|
||||
if (Response.HasProcessingRequest())
|
||||
{
|
||||
InputState.SetGamepadNavigationKey(KeyEvent, true);
|
||||
}
|
||||
|
||||
return FReply::Handled();
|
||||
return ToSlateReply(Response);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -171,17 +193,16 @@ FReply SImGuiWidget::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& Key
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IsConsoleOpened() || IgnoreKeyEvent(KeyEvent))
|
||||
{
|
||||
return FReply::Unhandled();
|
||||
}
|
||||
|
||||
InputState.SetKeyDown(KeyEvent, true);
|
||||
CopyModifierKeys(KeyEvent);
|
||||
|
||||
UpdateCanvasMapMode(KeyEvent);
|
||||
|
||||
return WithMouseLockRequests(FReply::Handled());
|
||||
const FImGuiInputResponse Response = InputHandler->OnKeyDown(KeyEvent);
|
||||
if (Response.HasProcessingRequest())
|
||||
{
|
||||
InputState.SetKeyDown(KeyEvent, true);
|
||||
CopyModifierKeys(KeyEvent);
|
||||
}
|
||||
|
||||
return WithMouseLockRequests(ToSlateReply(Response));
|
||||
}
|
||||
}
|
||||
|
||||
@ -191,9 +212,10 @@ FReply SImGuiWidget::OnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& KeyEv
|
||||
{
|
||||
if (InputState.IsGamepadNavigationEnabled())
|
||||
{
|
||||
// Always handle key up events to protect from leaving accidental keys not cleared in ImGui input state.
|
||||
InputState.SetGamepadNavigationKey(KeyEvent, false);
|
||||
|
||||
return FReply::Handled();
|
||||
return ToSlateReply(InputHandler->OnGamepadKeyUp(KeyEvent));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -202,15 +224,13 @@ FReply SImGuiWidget::OnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& KeyEv
|
||||
}
|
||||
else
|
||||
{
|
||||
// Even if we don't send new keystrokes to ImGui, we still handle key up events, to make sure that we clear keys
|
||||
// pressed before suppressing keyboard input.
|
||||
UpdateCanvasMapMode(KeyEvent);
|
||||
|
||||
// Always handle key up events to protect from leaving accidental keys not cleared in ImGui input state.
|
||||
InputState.SetKeyDown(KeyEvent, false);
|
||||
CopyModifierKeys(KeyEvent);
|
||||
|
||||
UpdateCanvasMapMode(KeyEvent);
|
||||
|
||||
// If console is opened we notify key change but we also let event trough, so it can be handled by console.
|
||||
return IsConsoleOpened() ? FReply::Unhandled() : WithMouseLockRequests(FReply::Handled());
|
||||
return WithMouseLockRequests(ToSlateReply(InputHandler->OnKeyUp(KeyEvent)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -218,9 +238,13 @@ FReply SImGuiWidget::OnAnalogValueChanged(const FGeometry& MyGeometry, const FAn
|
||||
{
|
||||
if (AnalogInputEvent.GetKey().IsGamepadKey() && InputState.IsGamepadNavigationEnabled())
|
||||
{
|
||||
InputState.SetGamepadNavigationAxis(AnalogInputEvent, AnalogInputEvent.GetAnalogValue());
|
||||
const FImGuiInputResponse Response = InputHandler->OnGamepadAxis(AnalogInputEvent);
|
||||
if (Response.HasProcessingRequest())
|
||||
{
|
||||
InputState.SetGamepadNavigationAxis(AnalogInputEvent, AnalogInputEvent.GetAnalogValue());
|
||||
}
|
||||
|
||||
return FReply::Handled();
|
||||
return ToSlateReply(Response);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -275,24 +299,6 @@ FReply SImGuiWidget::OnMouseWheel(const FGeometry& MyGeometry, const FPointerEve
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
FCursorReply SImGuiWidget::OnCursorQuery(const FGeometry& MyGeometry, const FPointerEvent& CursorEvent) const
|
||||
{
|
||||
EMouseCursor::Type MouseCursor = EMouseCursor::None;
|
||||
if (MouseCursorOverride != EMouseCursor::None)
|
||||
{
|
||||
MouseCursor = MouseCursorOverride;
|
||||
}
|
||||
else if (CVars::DrawMouseCursor.GetValueOnGameThread() <= 0)
|
||||
{
|
||||
if (FImGuiContextProxy* ContextProxy = ModuleManager->GetContextManager().GetContextProxy(ContextIndex))
|
||||
{
|
||||
MouseCursor = ContextProxy->GetMouseCursor();
|
||||
}
|
||||
}
|
||||
|
||||
return FCursorReply::Cursor(MouseCursor);
|
||||
}
|
||||
|
||||
FReply SImGuiWidget::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
{
|
||||
if (bCanvasMapMode)
|
||||
@ -365,6 +371,66 @@ void SImGuiWidget::OnMouseLeave(const FPointerEvent& MouseEvent)
|
||||
UpdateInputMode(HasKeyboardFocus() && GameViewport->Viewport->IsForegroundWindow(), false);
|
||||
}
|
||||
|
||||
FCursorReply SImGuiWidget::OnCursorQuery(const FGeometry& MyGeometry, const FPointerEvent& CursorEvent) const
|
||||
{
|
||||
EMouseCursor::Type MouseCursor = EMouseCursor::None;
|
||||
if (MouseCursorOverride != EMouseCursor::None)
|
||||
{
|
||||
MouseCursor = MouseCursorOverride;
|
||||
}
|
||||
else if (CVars::DrawMouseCursor.GetValueOnGameThread() <= 0)
|
||||
{
|
||||
if (FImGuiContextProxy* ContextProxy = ModuleManager->GetContextManager().GetContextProxy(ContextIndex))
|
||||
{
|
||||
MouseCursor = ContextProxy->GetMouseCursor();
|
||||
}
|
||||
}
|
||||
|
||||
return FCursorReply::Cursor(MouseCursor);
|
||||
}
|
||||
|
||||
void SImGuiWidget::CreateInputHandler()
|
||||
{
|
||||
if (!InputHandler.IsValid())
|
||||
{
|
||||
InputHandler = FImGuiInputHandlerFactory::NewHandler(ModuleManager, GameViewport.Get(), ContextIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void SImGuiWidget::ReleaseInputHandler()
|
||||
{
|
||||
if (InputHandler.IsValid())
|
||||
{
|
||||
FImGuiInputHandlerFactory::ReleaseHandler(InputHandler.Get());
|
||||
InputHandler.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
void SImGuiWidget::RecreateInputHandler()
|
||||
{
|
||||
ReleaseInputHandler();
|
||||
CreateInputHandler();
|
||||
}
|
||||
|
||||
void SImGuiWidget::RegisterInputHandlerChangedDelegate()
|
||||
{
|
||||
if (UImGuiSettings* Settings = GetMutableDefault<UImGuiSettings>())
|
||||
{
|
||||
if (!Settings->OnImGuiInputHandlerClassChanged.IsBoundToObject(this))
|
||||
{
|
||||
Settings->OnImGuiInputHandlerClassChanged.AddRaw(this, &SImGuiWidget::RecreateInputHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SImGuiWidget::UnregisterInputHandlerChangedDelegate()
|
||||
{
|
||||
if (UImGuiSettings* Settings = GetMutableDefault<UImGuiSettings>())
|
||||
{
|
||||
Settings->OnImGuiInputHandlerClassChanged.RemoveAll(this);
|
||||
}
|
||||
}
|
||||
|
||||
FReply SImGuiWidget::WithMouseLockRequests(FReply&& Reply)
|
||||
{
|
||||
const bool bNeedMouseLock = bCanvasDragging || bFrameDragging;
|
||||
@ -404,27 +470,6 @@ bool SImGuiWidget::IsConsoleOpened() const
|
||||
return GameViewport->ViewportConsole && GameViewport->ViewportConsole->ConsoleState != NAME_None;
|
||||
}
|
||||
|
||||
bool SImGuiWidget::IgnoreKeyEvent(const FKeyEvent& KeyEvent) const
|
||||
{
|
||||
// Ignore console open/close events.
|
||||
if (KeyEvent.GetKey() == EKeys::Tilde)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ignore escape keys unless they are needed to cancel operations in ImGui.
|
||||
if (KeyEvent.GetKey() == EKeys::Escape)
|
||||
{
|
||||
auto* ContextProxy = ModuleManager->GetContextManager().GetContextProxy(ContextIndex);
|
||||
if (!ContextProxy || !ContextProxy->HasActiveItem())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void SImGuiWidget::SetMouseCursorOverride(EMouseCursor::Type InMouseCursorOverride)
|
||||
{
|
||||
if (MouseCursorOverride != InMouseCursorOverride)
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
|
||||
class FImGuiModuleManager;
|
||||
class UImGuiInputHandler;
|
||||
|
||||
// Slate widget for rendering ImGui output and storing Slate inputs.
|
||||
class SImGuiWidget : public SLeafWidget
|
||||
@ -45,7 +46,7 @@ public:
|
||||
|
||||
virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override;
|
||||
|
||||
virtual bool SupportsKeyboardFocus() const override { return bInputEnabled; }
|
||||
virtual bool SupportsKeyboardFocus() const override { return bInputEnabled && !IsConsoleOpened(); }
|
||||
|
||||
virtual FReply OnKeyChar(const FGeometry& MyGeometry, const FCharacterEvent& CharacterEvent) override;
|
||||
|
||||
@ -63,8 +64,6 @@ public:
|
||||
|
||||
virtual FReply OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
|
||||
|
||||
virtual FCursorReply OnCursorQuery(const FGeometry& MyGeometry, const FPointerEvent& CursorEvent) const override;
|
||||
|
||||
virtual FReply OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
|
||||
|
||||
virtual FReply OnFocusReceived(const FGeometry& MyGeometry, const FFocusEvent& FocusEvent) override;
|
||||
@ -75,6 +74,8 @@ public:
|
||||
|
||||
virtual void OnMouseLeave(const FPointerEvent& MouseEvent) override;
|
||||
|
||||
virtual FCursorReply OnCursorQuery(const FGeometry& MyGeometry, const FPointerEvent& CursorEvent) const override;
|
||||
|
||||
private:
|
||||
|
||||
enum class EInputMode : uint8
|
||||
@ -86,6 +87,13 @@ private:
|
||||
Full
|
||||
};
|
||||
|
||||
void CreateInputHandler();
|
||||
void ReleaseInputHandler();
|
||||
void RecreateInputHandler();
|
||||
|
||||
void RegisterInputHandlerChangedDelegate();
|
||||
void UnregisterInputHandlerChangedDelegate();
|
||||
|
||||
// If needed, add to event reply a mouse lock or unlock request.
|
||||
FORCEINLINE FReply WithMouseLockRequests(FReply&& Reply);
|
||||
|
||||
@ -94,8 +102,6 @@ private:
|
||||
|
||||
bool IsConsoleOpened() const;
|
||||
|
||||
bool IgnoreKeyEvent(const FKeyEvent& KeyEvent) const;
|
||||
|
||||
void SetMouseCursorOverride(EMouseCursor::Type InMouseCursorOverride);
|
||||
|
||||
// Update visibility based on input enabled state.
|
||||
@ -147,6 +153,7 @@ private:
|
||||
|
||||
FImGuiModuleManager* ModuleManager = nullptr;
|
||||
TWeakObjectPtr<UGameViewportClient> GameViewport;
|
||||
TWeakObjectPtr<UImGuiInputHandler> InputHandler;
|
||||
|
||||
mutable TArray<FSlateVertex> VertexBuffer;
|
||||
mutable TArray<SlateIndex> IndexBuffer;
|
||||
|
168
Source/ImGui/Public/ImGuiInputHandler.h
Normal file
168
Source/ImGui/Public/ImGuiInputHandler.h
Normal file
@ -0,0 +1,168 @@
|
||||
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <CoreMinimal.h>
|
||||
#include <UObject/Object.h>
|
||||
|
||||
#include "ImGuiInputHandler.generated.h"
|
||||
|
||||
|
||||
class FImGuiModuleManager;
|
||||
class UGameViewportClient;
|
||||
|
||||
struct FAnalogInputEvent;
|
||||
struct FCharacterEvent;
|
||||
struct FKeyEvent;
|
||||
|
||||
/** Response used by ImGui Input Handler to communicate input handling requests. */
|
||||
struct IMGUI_API FImGuiInputResponse
|
||||
{
|
||||
/** Create empty response with no requests. */
|
||||
FImGuiInputResponse() = default;
|
||||
|
||||
/**
|
||||
* Create response with custom request configuration.
|
||||
*
|
||||
* @param bInProcess - State of the processing request.
|
||||
* @param bInConsume - State of the consume request.
|
||||
*/
|
||||
FImGuiInputResponse(bool bInProcess, bool bInConsume)
|
||||
: bProcess(bInProcess)
|
||||
, bConsume(bInConsume)
|
||||
{}
|
||||
|
||||
/**
|
||||
* Check whether this response contains processing request.
|
||||
*
|
||||
* @returns True, if processing was requested and false otherwise.
|
||||
*/
|
||||
FORCEINLINE bool HasProcessingRequest() const { return bProcess; }
|
||||
|
||||
/**
|
||||
* Check whether this response contains consume request.
|
||||
*
|
||||
* @returns True, if consume was requested and false otherwise.
|
||||
*/
|
||||
FORCEINLINE bool HasConsumeRequest() const { return bConsume; }
|
||||
|
||||
/**
|
||||
* Set the processing request.
|
||||
*
|
||||
* @param bInProcess - True, to request input processing (implicit) and false otherwise.
|
||||
* @returns Reference to this response (for chaining requests).
|
||||
*/
|
||||
FORCEINLINE FImGuiInputResponse& RequestProcessing(bool bInProcess = true) { bProcess = bInProcess; return *this; }
|
||||
|
||||
/**
|
||||
* Set the consume request.
|
||||
*
|
||||
* @param bInConsume - True, to request input consume (implicit) and false otherwise.
|
||||
* @returns Reference to this response (for chaining requests).
|
||||
*/
|
||||
FORCEINLINE FImGuiInputResponse& RequestConsume(bool bInConsume = true) { bConsume = bInConsume; return *this; }
|
||||
|
||||
private:
|
||||
|
||||
bool bProcess = false;
|
||||
|
||||
bool bConsume = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines behaviour when handling input events. It allows to customize handling of the keyboard and gamepad input,
|
||||
* primarily to support shortcuts in ImGui input mode. Since mouse is not really needed for this functionality and
|
||||
* mouse pointer state and focus are closely connected to input mode, mouse events are left out of this interface.
|
||||
*
|
||||
* When receiving keyboard and gamepad events ImGui Widget calls input handler to query expected behaviour. By default,
|
||||
* with a few exceptions (see @ OnKeyDown) all events are expected to be processed and consumed. Custom implementations
|
||||
* may tweak that behaviour and/or inject custom code.
|
||||
*
|
||||
* Note that returned response is only treated as a hint. In current implementation all consume requests are respected
|
||||
* but to protect from locking ImGui input states, key up events are always processed. Decision about blocking certain
|
||||
* inputs can be taken during key down events and processing corresponding key up events should not make difference.
|
||||
*
|
||||
* Also note that input handler functions are only called when ImGui Widget is receiving input events, what can be for
|
||||
* instance suppressed by opening console.
|
||||
*
|
||||
* See @ Project Settings/Plugins/ImGui/Input/ImGuiInputHandlerClass property to set custom implementation.
|
||||
*/
|
||||
UCLASS()
|
||||
class IMGUI_API UImGuiInputHandler : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Called when handling character events.
|
||||
*
|
||||
* @returns Response with rules how input should be handled. Default implementation contains requests to process
|
||||
* and consume this event.
|
||||
*/
|
||||
virtual FImGuiInputResponse OnKeyChar(const struct FCharacterEvent& CharacterEvent) { return DefaultResponse(); }
|
||||
|
||||
/**
|
||||
* Called when handling keyboard key down events.
|
||||
*
|
||||
* @returns Response with rules how input should be handled. Default implementation contains requests to process
|
||||
* and consume most of the key, but unlike other cases it requests to ignore certain events, like those that are
|
||||
* needed to open console or close PIE session in editor.
|
||||
*/
|
||||
virtual FImGuiInputResponse OnKeyDown(const FKeyEvent& KeyEvent);
|
||||
|
||||
/**
|
||||
* Called when handling keyboard key up events.
|
||||
*
|
||||
* Note that regardless of returned response, key up events are always processed by ImGui Widget.
|
||||
*
|
||||
* @returns Response with rules how input should be handled. Default implementation contains requests to consume
|
||||
* this event.
|
||||
*/
|
||||
virtual FImGuiInputResponse OnKeyUp(const FKeyEvent& KeyEvent) { return DefaultResponse(); }
|
||||
|
||||
/**
|
||||
* Called when handling gamepad key down events.
|
||||
*
|
||||
* @returns Response with rules how input should be handled. Default implementation contains requests to process
|
||||
* and consume this event.
|
||||
*/
|
||||
virtual FImGuiInputResponse OnGamepadKeyDown(const FKeyEvent& GamepadKeyEvent) { return DefaultResponse(); }
|
||||
|
||||
/**
|
||||
* Called when handling gamepad key up events.
|
||||
*
|
||||
* Note that regardless of returned response, key up events are always processed by ImGui Widget.
|
||||
*
|
||||
* @returns Response with rules how input should be handled. Default implementation contains requests to consume
|
||||
* this event.
|
||||
*/
|
||||
virtual FImGuiInputResponse OnGamepadKeyUp(const FKeyEvent& GamepadKeyEvent) { return DefaultResponse(); }
|
||||
|
||||
/**
|
||||
* Called when handling gamepad analog events.
|
||||
*
|
||||
* @returns Response with rules how input should be handled. Default implementation contains requests to process
|
||||
* and consume this event.
|
||||
*/
|
||||
virtual FImGuiInputResponse OnGamepadAxis(const FAnalogInputEvent& GamepadAxisEvent) { return DefaultResponse(); }
|
||||
|
||||
protected:
|
||||
|
||||
/** Checks whether corresponding ImGui context has an active item. */
|
||||
bool HasImGuiActiveItem() const;
|
||||
|
||||
private:
|
||||
|
||||
void Initialize(FImGuiModuleManager* InModuleManager, UGameViewportClient* InGameViewport, int32 InContextIndex);
|
||||
|
||||
FORCEINLINE FImGuiInputResponse DefaultResponse() { return FImGuiInputResponse{ true, true }; }
|
||||
|
||||
FImGuiModuleManager* ModuleManager = nullptr;
|
||||
|
||||
TWeakObjectPtr<UGameViewportClient> GameViewport;
|
||||
|
||||
int32 ContextIndex = -1;
|
||||
|
||||
friend class FImGuiInputHandlerFactory;
|
||||
};
|
Loading…
Reference in New Issue
Block a user