Moved the whole input handling from SImGuiWidget to UImGuiInputHandler:

- Moved responsibility for updating input state from the widget to the input handler.
- Changed the widget to fully delegate input events to the input handler and only manage its own input state.
- Changed the input handler interface to allow handling of all the necessary input events.
- Changed the input handler interface to use FReply as a response type and removed obsolete FImGuiInputResponse.
This commit is contained in:
Sebastian 2019-06-30 18:25:58 +01:00
parent 852a501022
commit 979903722a
4 changed files with 409 additions and 378 deletions

View File

@ -5,6 +5,7 @@
#include "ImGuiInputHandler.h" #include "ImGuiInputHandler.h"
#include "ImGuiContextProxy.h" #include "ImGuiContextProxy.h"
#include "ImGuiInputState.h"
#include "ImGuiModuleManager.h" #include "ImGuiModuleManager.h"
#include "ImGuiModuleSettings.h" #include "ImGuiModuleSettings.h"
@ -20,44 +21,182 @@
DEFINE_LOG_CATEGORY(LogImGuiInputHandler); DEFINE_LOG_CATEGORY(LogImGuiInputHandler);
static FImGuiInputResponse IgnoreResponse{ false, false }; namespace
FImGuiInputResponse UImGuiInputHandler::OnKeyDown(const FKeyEvent& KeyEvent)
{ {
// Ignore console events, so we don't block it from opening. FReply ToReply(bool bConsume)
if (IsConsoleEvent(KeyEvent))
{ {
return IgnoreResponse; return bConsume ? FReply::Handled() : FReply::Unhandled();
} }
}
FReply UImGuiInputHandler::OnKeyChar(const struct FCharacterEvent& CharacterEvent)
{
InputState->AddCharacter(CharacterEvent.GetCharacter());
return ToReply(!ModuleManager->GetProperties().IsKeyboardInputShared());
}
FReply UImGuiInputHandler::OnKeyDown(const FKeyEvent& KeyEvent)
{
if (KeyEvent.GetKey().IsGamepadKey())
{
bool bConsume = false;
if (InputState->IsGamepadNavigationEnabled())
{
InputState->SetGamepadNavigationKey(KeyEvent, true);
bConsume = !ModuleManager->GetProperties().IsGamepadInputShared();
}
return ToReply(bConsume);
}
else
{
// Ignore console events, so we don't block it from opening.
if (IsConsoleEvent(KeyEvent))
{
return ToReply(false);
}
#if WITH_EDITOR #if WITH_EDITOR
// If there is no active ImGui control that would get precedence and this key event is bound to a stop play session // If there is no active ImGui control that would get precedence and this key event is bound to a stop play session
// command, then ignore that event and let the command execute. // command, then ignore that event and let the command execute.
if (!HasImGuiActiveItem() && IsStopPlaySessionEvent(KeyEvent)) if (!HasImGuiActiveItem() && IsStopPlaySessionEvent(KeyEvent))
{ {
return IgnoreResponse; return ToReply(false);
} }
#endif // WITH_EDITOR #endif // WITH_EDITOR
const FImGuiInputResponse Response = GetDefaultKeyboardResponse(); const bool bConsume = !ModuleManager->GetProperties().IsKeyboardInputShared();
// With shared input we can leave command bindings for DebugExec to handle, otherwise we need to do it here. // With shared input we can leave command bindings for DebugExec to handle, otherwise we need to do it here.
if (Response.HasConsumeRequest() && IsToggleInputEvent(KeyEvent)) if (bConsume && IsToggleInputEvent(KeyEvent))
{
ModuleManager->GetProperties().ToggleInput();
}
InputState->SetKeyDown(KeyEvent, true);
CopyModifierKeys(KeyEvent);
return ToReply(bConsume);
}
}
FReply UImGuiInputHandler::OnKeyUp(const FKeyEvent& KeyEvent)
{
if (KeyEvent.GetKey().IsGamepadKey())
{ {
ModuleManager->GetProperties().ToggleInput(); bool bConsume = false;
if (InputState->IsGamepadNavigationEnabled())
{
InputState->SetGamepadNavigationKey(KeyEvent, false);
bConsume = !ModuleManager->GetProperties().IsGamepadInputShared();
}
return ToReply(bConsume);
}
else
{
InputState->SetKeyDown(KeyEvent, false);
CopyModifierKeys(KeyEvent);
return ToReply(!ModuleManager->GetProperties().IsKeyboardInputShared());
}
}
FReply UImGuiInputHandler::OnAnalogValueChanged(const FAnalogInputEvent& AnalogInputEvent)
{
bool bConsume = false;
if (AnalogInputEvent.GetKey().IsGamepadKey() && InputState->IsGamepadNavigationEnabled())
{
InputState->SetGamepadNavigationAxis(AnalogInputEvent, AnalogInputEvent.GetAnalogValue());
bConsume = !ModuleManager->GetProperties().IsGamepadInputShared();
} }
return Response; return ToReply(bConsume);
} }
FImGuiInputResponse UImGuiInputHandler::GetDefaultKeyboardResponse() const FReply UImGuiInputHandler::OnMouseButtonDown(const FPointerEvent& MouseEvent)
{ {
return FImGuiInputResponse{ true, !ModuleManager->GetProperties().IsKeyboardInputShared() }; InputState->SetMouseDown(MouseEvent, true);
return ToReply(true);
} }
FImGuiInputResponse UImGuiInputHandler::GetDefaultGamepadResponse() const FReply UImGuiInputHandler::OnMouseButtonDoubleClick(const FPointerEvent& MouseEvent)
{ {
return FImGuiInputResponse{ true, !ModuleManager->GetProperties().IsGamepadInputShared() }; InputState->SetMouseDown(MouseEvent, true);
return ToReply(true);
}
FReply UImGuiInputHandler::OnMouseButtonUp(const FPointerEvent& MouseEvent)
{
InputState->SetMouseDown(MouseEvent, false);
return ToReply(true);
}
FReply UImGuiInputHandler::OnMouseWheel(const FPointerEvent& MouseEvent)
{
InputState->AddMouseWheelDelta(MouseEvent.GetWheelDelta());
return ToReply(true);
}
FReply UImGuiInputHandler::OnMouseMove(const FVector2D& MousePosition)
{
InputState->SetMousePosition(MousePosition);
return ToReply(true);
}
void UImGuiInputHandler::OnKeyboardInputEnabled()
{
bKeyboardInputEnabled = true;
}
void UImGuiInputHandler::OnKeyboardInputDisabled()
{
if (bKeyboardInputEnabled)
{
bKeyboardInputEnabled = false;
InputState->ResetKeyboard();
}
}
void UImGuiInputHandler::OnGamepadInputEnabled()
{
bGamepadInputEnabled = true;
}
void UImGuiInputHandler::OnGamepadInputDisabled()
{
if (bGamepadInputEnabled)
{
bGamepadInputEnabled = false;
InputState->ResetGamepadNavigation();
}
}
void UImGuiInputHandler::OnMouseInputEnabled()
{
if (!bMouseInputEnabled)
{
bMouseInputEnabled = true;
UpdateInputStatePointer();
}
}
void UImGuiInputHandler::OnMouseInputDisabled()
{
if (bMouseInputEnabled)
{
bMouseInputEnabled = false;
InputState->ResetMouse();
UpdateInputStatePointer();
}
}
void UImGuiInputHandler::CopyModifierKeys(const FInputEvent& InputEvent)
{
InputState->SetControlDown(InputEvent.IsControlDown());
InputState->SetShiftDown(InputEvent.IsShiftDown());
InputState->SetAltDown(InputEvent.IsAltDown());
} }
bool UImGuiInputHandler::IsConsoleEvent(const FKeyEvent& KeyEvent) const bool UImGuiInputHandler::IsConsoleEvent(const FKeyEvent& KeyEvent) const
@ -113,12 +252,47 @@ bool UImGuiInputHandler::HasImGuiActiveItem() const
return ContextProxy && ContextProxy->HasActiveItem(); return ContextProxy && ContextProxy->HasActiveItem();
} }
void UImGuiInputHandler::UpdateInputStatePointer()
{
InputState->SetMousePointer(bMouseInputEnabled && ModuleManager->GetSettings().UseSoftwareCursor());
}
void UImGuiInputHandler::OnSoftwareCursorChanged(bool)
{
UpdateInputStatePointer();
}
void UImGuiInputHandler::OnPostImGuiUpdate()
{
InputState->ClearUpdateState();
// TODO Replace with delegates after adding property change events.
InputState->SetKeyboardNavigationEnabled(ModuleManager->GetProperties().IsKeyboardNavigationEnabled());
InputState->SetGamepadNavigationEnabled(ModuleManager->GetProperties().IsGamepadNavigationEnabled());
const auto& PlatformApplication = FSlateApplication::Get().GetPlatformApplication();
InputState->SetGamepad(PlatformApplication.IsValid() && PlatformApplication->IsGamepadAttached());
}
void UImGuiInputHandler::Initialize(FImGuiModuleManager* InModuleManager, UGameViewportClient* InGameViewport, int32 InContextIndex) void UImGuiInputHandler::Initialize(FImGuiModuleManager* InModuleManager, UGameViewportClient* InGameViewport, int32 InContextIndex)
{ {
ModuleManager = InModuleManager; ModuleManager = InModuleManager;
GameViewport = InGameViewport; GameViewport = InGameViewport;
ContextIndex = InContextIndex; ContextIndex = InContextIndex;
auto* ContextProxy = ModuleManager->GetContextManager().GetContextProxy(ContextIndex);
checkf(ContextProxy, TEXT("Missing context during initialization of input handler: ContextIndex = %d"), ContextIndex);
InputState = &ContextProxy->GetInputState();
// Register to get post-update notifications, so we can clean frame updates.
ModuleManager->OnPostImGuiUpdate().AddUObject(this, &UImGuiInputHandler::OnPostImGuiUpdate);
auto& Settings = ModuleManager->GetSettings();
if (!Settings.OnUseSoftwareCursorChanged.IsBoundToObject(this))
{
Settings.OnUseSoftwareCursorChanged.AddUObject(this, &UImGuiInputHandler::OnSoftwareCursorChanged);
}
#if WITH_EDITOR #if WITH_EDITOR
StopPlaySessionCommandInfo = FInputBindingManager::Get().FindCommandInContext("PlayWorld", "StopPlaySession"); StopPlaySessionCommandInfo = FInputBindingManager::Get().FindCommandInContext("PlayWorld", "StopPlaySession");
if (!StopPlaySessionCommandInfo.IsValid()) if (!StopPlaySessionCommandInfo.IsValid())
@ -128,3 +302,14 @@ void UImGuiInputHandler::Initialize(FImGuiModuleManager* InModuleManager, UGameV
} }
#endif // WITH_EDITOR #endif // WITH_EDITOR
} }
void UImGuiInputHandler::BeginDestroy()
{
Super::BeginDestroy();
if (ModuleManager)
{
ModuleManager->GetSettings().OnUseSoftwareCursorChanged.RemoveAll(this);
}
}

View File

@ -7,10 +7,8 @@
#include "ImGuiContextManager.h" #include "ImGuiContextManager.h"
#include "ImGuiContextProxy.h" #include "ImGuiContextProxy.h"
#include "ImGuiImplementation.h"
#include "ImGuiInputHandler.h" #include "ImGuiInputHandler.h"
#include "ImGuiInputHandlerFactory.h" #include "ImGuiInputHandlerFactory.h"
#include "ImGuiInputState.h"
#include "ImGuiInteroperability.h" #include "ImGuiInteroperability.h"
#include "ImGuiModuleManager.h" #include "ImGuiModuleManager.h"
#include "ImGuiModuleSettings.h" #include "ImGuiModuleSettings.h"
@ -69,34 +67,28 @@ void SImGuiWidget::Construct(const FArguments& InArgs)
GameViewport = InArgs._GameViewport; GameViewport = InArgs._GameViewport;
ContextIndex = InArgs._ContextIndex; ContextIndex = InArgs._ContextIndex;
// Disable mouse cursor over this widget as we will use ImGui to draw it. // Register to get post-update notifications.
SetCursor(EMouseCursor::None);
// Sync visibility with default input enabled state.
UpdateVisibility();
// Register to get post-update notifications, so we can clean frame updates.
ModuleManager->OnPostImGuiUpdate().AddRaw(this, &SImGuiWidget::OnPostImGuiUpdate); ModuleManager->OnPostImGuiUpdate().AddRaw(this, &SImGuiWidget::OnPostImGuiUpdate);
// Bind this widget to its context proxy. // Register debug delegate.
auto* ContextProxy = ModuleManager->GetContextManager().GetContextProxy(ContextIndex); auto* ContextProxy = ModuleManager->GetContextManager().GetContextProxy(ContextIndex);
checkf(ContextProxy, TEXT("Missing context during widget construction: ContextIndex = %d"), ContextIndex); checkf(ContextProxy, TEXT("Missing context during widget construction: ContextIndex = %d"), ContextIndex);
#if IMGUI_WIDGET_DEBUG #if IMGUI_WIDGET_DEBUG
ContextProxy->OnDraw().AddRaw(this, &SImGuiWidget::OnDebugDraw); ContextProxy->OnDraw().AddRaw(this, &SImGuiWidget::OnDebugDraw);
#endif // IMGUI_WIDGET_DEBUG #endif // IMGUI_WIDGET_DEBUG
InputState = &ContextProxy->GetInputState();
// Register for settings change. // Register for settings change.
RegisterImGuiSettingsDelegates(); RegisterImGuiSettingsDelegates();
// Get initial settings.
const auto& Settings = ModuleManager->GetSettings(); const auto& Settings = ModuleManager->GetSettings();
SetHideMouseCursor(Settings.UseSoftwareCursor());
// Cache locally software cursor mode.
SetUseSoftwareCursor(Settings.UseSoftwareCursor());
// Create ImGui Input Handler.
CreateInputHandler(Settings.GetImGuiInputHandlerClass()); CreateInputHandler(Settings.GetImGuiInputHandlerClass());
// Initialize state.
UpdateVisibility();
UpdateMouseCursor();
ChildSlot ChildSlot
[ [
SAssignNew(CanvasControlWidget, SImGuiCanvasControl).OnTransformChanged(this, &SImGuiWidget::SetImGuiTransform) SAssignNew(CanvasControlWidget, SImGuiCanvasControl).OnTransformChanged(this, &SImGuiWidget::SetImGuiTransform)
@ -130,139 +122,74 @@ void SImGuiWidget::Tick(const FGeometry& AllottedGeometry, const double InCurren
{ {
Super::Tick(AllottedGeometry, InCurrentTime, InDeltaTime); Super::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);
// Note: Moving that update to console variable sink or callback might seem like a better alternative but input UpdateInputState();
// setup in this function is better handled here. HandleWindowFocusLost();
UpdateInputEnabled();
}
namespace
{
FReply ToSlateReply(const FImGuiInputResponse& HandlingResponse)
{
return HandlingResponse.HasConsumeRequest() ? FReply::Handled() : FReply::Unhandled();
}
} }
FReply SImGuiWidget::OnKeyChar(const FGeometry& MyGeometry, const FCharacterEvent& CharacterEvent) FReply SImGuiWidget::OnKeyChar(const FGeometry& MyGeometry, const FCharacterEvent& CharacterEvent)
{ {
const FImGuiInputResponse Response = InputHandler->OnKeyChar(CharacterEvent); return InputHandler->OnKeyChar(CharacterEvent);
if (Response.HasProcessingRequest())
{
InputState->AddCharacter(CharacterEvent.GetCharacter());
}
return ToSlateReply(Response);
} }
FReply SImGuiWidget::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& KeyEvent) FReply SImGuiWidget::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& KeyEvent)
{ {
if (KeyEvent.GetKey().IsGamepadKey()) UpdateCanvasControlMode(KeyEvent);
{ return InputHandler->OnKeyDown(KeyEvent);
if (InputState->IsGamepadNavigationEnabled())
{
const FImGuiInputResponse Response = InputHandler->OnGamepadKeyDown(KeyEvent);
if (Response.HasProcessingRequest())
{
InputState->SetGamepadNavigationKey(KeyEvent, true);
}
return ToSlateReply(Response);
}
else
{
return Super::OnKeyDown(MyGeometry, KeyEvent);
}
}
else
{
UpdateCanvasControlMode(KeyEvent);
const FImGuiInputResponse Response = InputHandler->OnKeyDown(KeyEvent);
if (Response.HasProcessingRequest())
{
InputState->SetKeyDown(KeyEvent, true);
CopyModifierKeys(KeyEvent);
}
return ToSlateReply(Response);
}
} }
FReply SImGuiWidget::OnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& KeyEvent) FReply SImGuiWidget::OnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& KeyEvent)
{ {
if (KeyEvent.GetKey().IsGamepadKey()) UpdateCanvasControlMode(KeyEvent);
{ return InputHandler->OnKeyUp(KeyEvent);
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 ToSlateReply(InputHandler->OnGamepadKeyUp(KeyEvent));
}
else
{
return Super::OnKeyUp(MyGeometry, KeyEvent);
}
}
else
{
UpdateCanvasControlMode(KeyEvent);
// Always handle key up events to protect from leaving accidental keys not cleared in ImGui input state.
InputState->SetKeyDown(KeyEvent, false);
CopyModifierKeys(KeyEvent);
return ToSlateReply(InputHandler->OnKeyUp(KeyEvent));
}
} }
FReply SImGuiWidget::OnAnalogValueChanged(const FGeometry& MyGeometry, const FAnalogInputEvent& AnalogInputEvent) FReply SImGuiWidget::OnAnalogValueChanged(const FGeometry& MyGeometry, const FAnalogInputEvent& AnalogInputEvent)
{ {
if (AnalogInputEvent.GetKey().IsGamepadKey() && InputState->IsGamepadNavigationEnabled()) return InputHandler->OnAnalogValueChanged(AnalogInputEvent);
{
const FImGuiInputResponse Response = InputHandler->OnGamepadAxis(AnalogInputEvent);
if (Response.HasProcessingRequest())
{
InputState->SetGamepadNavigationAxis(AnalogInputEvent, AnalogInputEvent.GetAnalogValue());
}
return ToSlateReply(Response);
}
else
{
return Super::OnAnalogValueChanged(MyGeometry, AnalogInputEvent);
}
} }
FReply SImGuiWidget::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) FReply SImGuiWidget::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{ {
InputState->SetMouseDown(MouseEvent, true); return InputHandler->OnMouseButtonDown(MouseEvent).LockMouseToWidget(SharedThis(this));
return FReply::Handled();
} }
FReply SImGuiWidget::OnMouseButtonDoubleClick(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) FReply SImGuiWidget::OnMouseButtonDoubleClick(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{ {
InputState->SetMouseDown(MouseEvent, true); return InputHandler->OnMouseButtonDoubleClick(MouseEvent).LockMouseToWidget(SharedThis(this));
return FReply::Handled(); }
namespace
{
bool NeedMouseLock(const FPointerEvent& MouseEvent)
{
#if FROM_ENGINE_VERSION(4, 20)
return FSlateApplication::Get().GetPressedMouseButtons().Num() > 0;
#else
return MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton) || MouseEvent.IsMouseButtonDown(EKeys::MiddleMouseButton)
|| MouseEvent.IsMouseButtonDown(EKeys::RightMouseButton);
#endif
}
} }
FReply SImGuiWidget::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) FReply SImGuiWidget::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{ {
InputState->SetMouseDown(MouseEvent, false); FReply Reply = InputHandler->OnMouseButtonUp(MouseEvent);
return FReply::Handled(); if (!NeedMouseLock(MouseEvent))
{
Reply.ReleaseMouseLock();
}
return Reply;
} }
FReply SImGuiWidget::OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) FReply SImGuiWidget::OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{ {
InputState->AddMouseWheelDelta(MouseEvent.GetWheelDelta()); return InputHandler->OnMouseWheel(MouseEvent);
return FReply::Handled();
} }
FReply SImGuiWidget::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) FReply SImGuiWidget::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{ {
const FSlateRenderTransform ImGuiToScreen = ImGuiTransform.Concatenate(MyGeometry.GetAccumulatedRenderTransform()); const FSlateRenderTransform ImGuiToScreen = ImGuiTransform.Concatenate(MyGeometry.GetAccumulatedRenderTransform());
InputState->SetMousePosition(ImGuiToScreen.Inverse().TransformPoint(MouseEvent.GetScreenSpacePosition())); return InputHandler->OnMouseMove(ImGuiToScreen.Inverse().TransformPoint(MouseEvent.GetScreenSpacePosition()));
return FReply::Handled();
} }
FReply SImGuiWidget::OnFocusReceived(const FGeometry& MyGeometry, const FFocusEvent& FocusEvent) FReply SImGuiWidget::OnFocusReceived(const FGeometry& MyGeometry, const FFocusEvent& FocusEvent)
@ -271,9 +198,9 @@ FReply SImGuiWidget::OnFocusReceived(const FGeometry& MyGeometry, const FFocusEv
IMGUI_WIDGET_LOG(VeryVerbose, TEXT("ImGui Widget %d - Focus Received."), ContextIndex); IMGUI_WIDGET_LOG(VeryVerbose, TEXT("ImGui Widget %d - Focus Received."), ContextIndex);
// If widget has a keyboard focus we always maintain mouse input. Technically, if mouse is outside of the widget bForegroundWindow = GameViewport->Viewport->IsForegroundWindow();
// area it won't generate events but we freeze its state until it either comes back or input is completely lost. InputHandler->OnKeyboardInputEnabled();
UpdateInputMode(true, IsDirectlyHovered()); InputHandler->OnGamepadInputEnabled();
FSlateApplication::Get().ResetToDefaultPointerInputSettings(); FSlateApplication::Get().ResetToDefaultPointerInputSettings();
return FReply::Handled(); return FReply::Handled();
@ -285,7 +212,8 @@ void SImGuiWidget::OnFocusLost(const FFocusEvent& FocusEvent)
IMGUI_WIDGET_LOG(VeryVerbose, TEXT("ImGui Widget %d - Focus Lost."), ContextIndex); IMGUI_WIDGET_LOG(VeryVerbose, TEXT("ImGui Widget %d - Focus Lost."), ContextIndex);
UpdateInputMode(false, IsDirectlyHovered()); InputHandler->OnKeyboardInputDisabled();
InputHandler->OnGamepadInputDisabled();
} }
void SImGuiWidget::OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) void SImGuiWidget::OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
@ -294,17 +222,7 @@ void SImGuiWidget::OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent
IMGUI_WIDGET_LOG(VeryVerbose, TEXT("ImGui Widget %d - Mouse Enter."), ContextIndex); IMGUI_WIDGET_LOG(VeryVerbose, TEXT("ImGui Widget %d - Mouse Enter."), ContextIndex);
// If mouse enters while input is active then we need to update mouse buttons because there is a chance that we InputHandler->OnMouseInputEnabled();
// missed some events.
if (InputMode != EInputMode::None)
{
for (const FKey& Button : { EKeys::LeftMouseButton, EKeys::MiddleMouseButton, EKeys::RightMouseButton, EKeys::ThumbMouseButton, EKeys::ThumbMouseButton2 })
{
InputState->SetMouseDown(Button, MouseEvent.IsMouseButtonDown(Button));
}
}
UpdateInputMode(HasKeyboardFocus(), true);
} }
void SImGuiWidget::OnMouseLeave(const FPointerEvent& MouseEvent) void SImGuiWidget::OnMouseLeave(const FPointerEvent& MouseEvent)
@ -313,23 +231,7 @@ void SImGuiWidget::OnMouseLeave(const FPointerEvent& MouseEvent)
IMGUI_WIDGET_LOG(VeryVerbose, TEXT("ImGui Widget %d - Mouse Leave."), ContextIndex); IMGUI_WIDGET_LOG(VeryVerbose, TEXT("ImGui Widget %d - Mouse Leave."), ContextIndex);
// We don't get any events when application loses focus, but often this is followed by OnMouseLeave, so we can use InputHandler->OnMouseInputDisabled();
// this event to immediately disable keyboard input if application lost focus.
UpdateInputMode(HasKeyboardFocus() && GameViewport->Viewport->IsForegroundWindow(), false);
}
FCursorReply SImGuiWidget::OnCursorQuery(const FGeometry& MyGeometry, const FPointerEvent& CursorEvent) const
{
EMouseCursor::Type MouseCursor = EMouseCursor::None;
if (!bUseSoftwareCursor)
{
if (FImGuiContextProxy* ContextProxy = ModuleManager->GetContextManager().GetContextProxy(ContextIndex))
{
MouseCursor = ContextProxy->GetMouseCursor();
}
}
return FCursorReply::Cursor(MouseCursor);
} }
void SImGuiWidget::CreateInputHandler(const FStringClassReference& HandlerClassReference) void SImGuiWidget::CreateInputHandler(const FStringClassReference& HandlerClassReference)
@ -361,7 +263,7 @@ void SImGuiWidget::RegisterImGuiSettingsDelegates()
} }
if (!Settings.OnUseSoftwareCursorChanged.IsBoundToObject(this)) if (!Settings.OnUseSoftwareCursorChanged.IsBoundToObject(this))
{ {
Settings.OnUseSoftwareCursorChanged.AddRaw(this, &SImGuiWidget::SetUseSoftwareCursor); Settings.OnUseSoftwareCursorChanged.AddRaw(this, &SImGuiWidget::SetHideMouseCursor);
} }
} }
@ -373,11 +275,13 @@ void SImGuiWidget::UnregisterImGuiSettingsDelegates()
Settings.OnUseSoftwareCursorChanged.RemoveAll(this); Settings.OnUseSoftwareCursorChanged.RemoveAll(this);
} }
void SImGuiWidget::CopyModifierKeys(const FInputEvent& InputEvent) void SImGuiWidget::SetHideMouseCursor(bool bHide)
{ {
InputState->SetControlDown(InputEvent.IsControlDown()); if (bHideMouseCursor != bHide)
InputState->SetShiftDown(InputEvent.IsShiftDown()); {
InputState->SetAltDown(InputEvent.IsAltDown()); bHideMouseCursor = bHide;
UpdateMouseCursor();
}
} }
bool SImGuiWidget::IsConsoleOpened() const bool SImGuiWidget::IsConsoleOpened() const
@ -394,6 +298,19 @@ void SImGuiWidget::UpdateVisibility()
ContextIndex, *GetVisibility().ToString()); ContextIndex, *GetVisibility().ToString());
} }
void SImGuiWidget::UpdateMouseCursor()
{
if (!bHideMouseCursor)
{
const FImGuiContextProxy* ContextProxy = ModuleManager->GetContextManager().GetContextProxy(ContextIndex);
SetCursor(ContextProxy ? ContextProxy->GetMouseCursor() : EMouseCursor::Default);
}
else
{
SetCursor(EMouseCursor::None);
}
}
ULocalPlayer* SImGuiWidget::GetLocalPlayer() const ULocalPlayer* SImGuiWidget::GetLocalPlayer() const
{ {
if (GameViewport.IsValid()) if (GameViewport.IsValid())
@ -453,28 +370,39 @@ void SImGuiWidget::ReturnFocus()
PreviousUserFocusedWidget.Reset(); PreviousUserFocusedWidget.Reset();
} }
void SImGuiWidget::UpdateInputEnabled() void SImGuiWidget::UpdateInputState()
{ {
const bool bEnabled = ModuleManager && ModuleManager->GetProperties().IsInputEnabled(); const bool bEnabled = ModuleManager && ModuleManager->GetProperties().IsInputEnabled();
if (bInputEnabled != bEnabled) if (bInputEnabled != bEnabled)
{ {
IMGUI_WIDGET_LOG(Log, TEXT("ImGui Widget %d - Input Enabled changed to '%s'."),
ContextIndex, TEXT_BOOL(bEnabled));
bInputEnabled = bEnabled; bInputEnabled = bEnabled;
IMGUI_WIDGET_LOG(Log, TEXT("ImGui Widget %d - Input Enabled changed to '%s'."),
ContextIndex, TEXT_BOOL(bInputEnabled));
UpdateVisibility(); UpdateVisibility();
UpdateMouseCursor();
if (!bInputEnabled) if (bInputEnabled)
{
// We won't get mouse enter, if viewport is already hovered.
if (GameViewport->GetGameViewportWidget()->IsHovered())
{
InputHandler->OnMouseInputEnabled();
}
// Focus is handled later as it can depend on additional factors.
}
else
{ {
ReturnFocus(); ReturnFocus();
UpdateInputMode(false, false);
} }
} }
// Note: Some widgets, like console, can reset focus to viewport after we already grabbed it. If we detect that // We should request a focus, if we are in the input mode and don't have one. But we should wait, if this is not
// viewport has a focus while input is enabled we will take it. // a foreground window (application), if viewport doesn't have a focus or if console is opened. Note that this
if (bInputEnabled && !HasKeyboardFocus() && !IsConsoleOpened()) // will keep this widget from releasing focus to viewport or other widgets as long as we are in the input mode.
if (bInputEnabled && GameViewport->Viewport->IsForegroundWindow() && !HasKeyboardFocus() && !IsConsoleOpened())
{ {
const auto& ViewportWidget = GameViewport->GetGameViewportWidget(); const auto& ViewportWidget = GameViewport->GetGameViewportWidget();
if (ViewportWidget->HasKeyboardFocus() || ViewportWidget->HasFocusedDescendants()) if (ViewportWidget->HasKeyboardFocus() || ViewportWidget->HasFocusedDescendants())
@ -482,52 +410,33 @@ void SImGuiWidget::UpdateInputEnabled()
TakeFocus(); TakeFocus();
} }
} }
// We don't get any events when application loses focus (we get OnMouseLeave but not always) but we fix it with
// this manual check. We still allow the above code to run, even if we need to suppress keyboard input right after
// that.
if (bInputEnabled && !GameViewport->Viewport->IsForegroundWindow() && InputMode == EInputMode::Full)
{
UpdateInputMode(false, IsDirectlyHovered());
}
if (bInputEnabled)
{
InputState->SetKeyboardNavigationEnabled(ModuleManager && ModuleManager->GetProperties().IsKeyboardNavigationEnabled());
InputState->SetGamepadNavigationEnabled(ModuleManager && ModuleManager->GetProperties().IsGamepadNavigationEnabled());
const auto& Application = FSlateApplication::Get().GetPlatformApplication();
InputState->SetGamepad(Application.IsValid() && Application->IsGamepadAttached());
}
} }
void SImGuiWidget::UpdateInputMode(bool bHasKeyboardFocus, bool bHasMousePointer) void SImGuiWidget::HandleWindowFocusLost()
{ {
const EInputMode NewInputMode = // We can use window foreground status to notify about application losing or receiving focus. In some situations
bHasKeyboardFocus ? EInputMode::Full : // we get mouse leave or enter events, but they are only sent if mouse pointer is inside of the viewport.
bHasMousePointer ? EInputMode::MousePointerOnly : if (bInputEnabled && HasKeyboardFocus())
EInputMode::None;
if (InputMode != NewInputMode)
{ {
IMGUI_WIDGET_LOG(Verbose, TEXT("ImGui Widget %d - Input Mode changed from '%s' to '%s'."), if (bForegroundWindow != GameViewport->Viewport->IsForegroundWindow())
ContextIndex, TEXT_INPUT_MODE(InputMode), TEXT_INPUT_MODE(NewInputMode));
// We need to reset input components if we are either fully shutting down or we are downgrading from full to
// mouse-only input mode.
if (NewInputMode == EInputMode::None)
{ {
InputState->Reset(); bForegroundWindow = !bForegroundWindow;
}
else if (InputMode == EInputMode::Full)
{
InputState->ResetKeyboard();
InputState->ResetGamepadNavigation();
}
InputMode = NewInputMode; IMGUI_WIDGET_LOG(VeryVerbose, TEXT("ImGui Widget %d - Updating input after %s foreground window status."),
ContextIndex, bForegroundWindow ? TEXT("getting") : TEXT("losing"));
if (bForegroundWindow)
{
InputHandler->OnKeyboardInputEnabled();
InputHandler->OnGamepadInputEnabled();
}
else
{
InputHandler->OnKeyboardInputDisabled();
InputHandler->OnGamepadInputDisabled();
}
}
} }
InputState->SetMousePointer(bUseSoftwareCursor && bHasMousePointer);
} }
void SImGuiWidget::UpdateCanvasControlMode(const FInputEvent& InputEvent) void SImGuiWidget::UpdateCanvasControlMode(const FInputEvent& InputEvent)
@ -538,6 +447,7 @@ void SImGuiWidget::UpdateCanvasControlMode(const FInputEvent& InputEvent)
void SImGuiWidget::OnPostImGuiUpdate() void SImGuiWidget::OnPostImGuiUpdate()
{ {
ImGuiRenderTransform = ImGuiTransform; ImGuiRenderTransform = ImGuiTransform;
UpdateMouseCursor();
} }
namespace namespace
@ -755,6 +665,8 @@ namespace Styles
void SImGuiWidget::OnDebugDraw() void SImGuiWidget::OnDebugDraw()
{ {
FImGuiContextProxy* ContextProxy = ModuleManager->GetContextManager().GetContextProxy(ContextIndex);
if (CVars::DebugWidget.GetValueOnGameThread() > 0) if (CVars::DebugWidget.GetValueOnGameThread() > 0)
{ {
bool bDebug = true; bool bDebug = true;
@ -766,7 +678,6 @@ void SImGuiWidget::OnDebugDraw()
TwoColumns::CollapsingGroup("Context", [&]() TwoColumns::CollapsingGroup("Context", [&]()
{ {
TwoColumns::Value("Context Index", ContextIndex); TwoColumns::Value("Context Index", ContextIndex);
FImGuiContextProxy* ContextProxy = ModuleManager->GetContextManager().GetContextProxy(ContextIndex);
TwoColumns::Value("Context Name", ContextProxy ? *ContextProxy->GetName() : TEXT("< Null >")); TwoColumns::Value("Context Name", ContextProxy ? *ContextProxy->GetName() : TEXT("< Null >"));
TwoColumns::Value("Game Viewport", *GameViewport->GetName()); TwoColumns::Value("Game Viewport", *GameViewport->GetName());
}); });
@ -774,8 +685,6 @@ void SImGuiWidget::OnDebugDraw()
TwoColumns::CollapsingGroup("Input Mode", [&]() TwoColumns::CollapsingGroup("Input Mode", [&]()
{ {
TwoColumns::Value("Input Enabled", bInputEnabled); TwoColumns::Value("Input Enabled", bInputEnabled);
TwoColumns::Value("Input Mode", TEXT_INPUT_MODE(InputMode));
TwoColumns::Value("Input Has Mouse Pointer", InputState->HasMousePointer());
}); });
TwoColumns::CollapsingGroup("Widget", [&]() TwoColumns::CollapsingGroup("Widget", [&]()
@ -807,8 +716,10 @@ void SImGuiWidget::OnDebugDraw()
} }
} }
if (CVars::DebugInput.GetValueOnGameThread() > 0) if (ContextProxy && CVars::DebugInput.GetValueOnGameThread() > 0)
{ {
FImGuiInputState& InputState = ContextProxy->GetInputState();
bool bDebug = true; bool bDebug = true;
ImGui::SetNextWindowSize(ImVec2(460, 480), ImGuiSetCond_Once); ImGui::SetNextWindowSize(ImVec2(460, 480), ImGuiSetCond_Once);
if (ImGui::Begin("ImGui Input State", &bDebug)) if (ImGui::Begin("ImGui Input State", &bDebug))
@ -832,7 +743,7 @@ void SImGuiWidget::OnDebugDraw()
{ {
const FKey& Key = Keys[Idx]; const FKey& Key = Keys[Idx];
const uint32 KeyIndex = ImGuiInterops::GetKeyIndex(Key); const uint32 KeyIndex = ImGuiInterops::GetKeyIndex(Key);
Styles::TextHighlight(InputState->GetKeys()[KeyIndex], [&]() Styles::TextHighlight(InputState.GetKeys()[KeyIndex], [&]()
{ {
TwoColumns::Value(*Key.GetDisplayName().ToString(), KeyIndex); TwoColumns::Value(*Key.GetDisplayName().ToString(), KeyIndex);
}); });
@ -847,9 +758,9 @@ void SImGuiWidget::OnDebugDraw()
Columns::CollapsingGroup("Modifier Keys", 4, [&]() Columns::CollapsingGroup("Modifier Keys", 4, [&]()
{ {
Styles::TextHighlight(InputState->IsShiftDown(), [&]() { ImGui::Text("Shift"); }); ImGui::NextColumn(); Styles::TextHighlight(InputState.IsShiftDown(), [&]() { ImGui::Text("Shift"); }); ImGui::NextColumn();
Styles::TextHighlight(InputState->IsControlDown(), [&]() { ImGui::Text("Control"); }); ImGui::NextColumn(); Styles::TextHighlight(InputState.IsControlDown(), [&]() { ImGui::Text("Control"); }); ImGui::NextColumn();
Styles::TextHighlight(InputState->IsAltDown(), [&]() { ImGui::Text("Alt"); }); ImGui::NextColumn(); Styles::TextHighlight(InputState.IsAltDown(), [&]() { ImGui::Text("Alt"); }); ImGui::NextColumn();
ImGui::NextColumn(); ImGui::NextColumn();
}); });
@ -872,7 +783,7 @@ void SImGuiWidget::OnDebugDraw()
{ {
const FKey& Button = Buttons[Idx]; const FKey& Button = Buttons[Idx];
const uint32 MouseIndex = ImGuiInterops::GetMouseIndex(Button); const uint32 MouseIndex = ImGuiInterops::GetMouseIndex(Button);
Styles::TextHighlight(InputState->GetMouseButtons()[MouseIndex], [&]() Styles::TextHighlight(InputState.GetMouseButtons()[MouseIndex], [&]()
{ {
TwoColumns::Value(*Button.GetDisplayName().ToString(), MouseIndex); TwoColumns::Value(*Button.GetDisplayName().ToString(), MouseIndex);
}); });
@ -887,9 +798,9 @@ void SImGuiWidget::OnDebugDraw()
Columns::CollapsingGroup("Mouse Axes", 4, [&]() Columns::CollapsingGroup("Mouse Axes", 4, [&]()
{ {
TwoColumns::Value("Position X", InputState->GetMousePosition().X); TwoColumns::Value("Position X", InputState.GetMousePosition().X);
TwoColumns::Value("Position Y", InputState->GetMousePosition().Y); TwoColumns::Value("Position Y", InputState.GetMousePosition().Y);
TwoColumns::Value("Wheel Delta", InputState->GetMouseWheelDelta()); TwoColumns::Value("Wheel Delta", InputState.GetMouseWheelDelta());
ImGui::NextColumn(); ImGui::NextColumn(); ImGui::NextColumn(); ImGui::NextColumn();
}); });

View File

@ -11,7 +11,6 @@
// Hide ImGui Widget debug in non-developer mode. // Hide ImGui Widget debug in non-developer mode.
#define IMGUI_WIDGET_DEBUG IMGUI_MODULE_DEVELOPER #define IMGUI_WIDGET_DEBUG IMGUI_MODULE_DEVELOPER
class FImGuiInputState;
class FImGuiModuleManager; class FImGuiModuleManager;
class SImGuiCanvasControl; class SImGuiCanvasControl;
class UImGuiInputHandler; class UImGuiInputHandler;
@ -71,42 +70,31 @@ public:
virtual void OnMouseLeave(const FPointerEvent& MouseEvent) override; virtual void OnMouseLeave(const FPointerEvent& MouseEvent) override;
virtual FCursorReply OnCursorQuery(const FGeometry& MyGeometry, const FPointerEvent& CursorEvent) const override;
private: private:
enum class EInputMode : uint8
{
None,
// Mouse pointer only without user focus
MousePointerOnly,
// Full input with user focus (mouse, keyboard and depending on navigation mode gamepad)
Full
};
void CreateInputHandler(const FStringClassReference& HandlerClassReference); void CreateInputHandler(const FStringClassReference& HandlerClassReference);
void ReleaseInputHandler(); void ReleaseInputHandler();
void SetUseSoftwareCursor(bool bUse) { bUseSoftwareCursor = bUse; }
void RegisterImGuiSettingsDelegates(); void RegisterImGuiSettingsDelegates();
void UnregisterImGuiSettingsDelegates(); void UnregisterImGuiSettingsDelegates();
FORCEINLINE void CopyModifierKeys(const FInputEvent& InputEvent); void SetHideMouseCursor(bool bHide);
bool IsConsoleOpened() const; bool IsConsoleOpened() const;
// Update visibility based on input enabled state. // Update visibility based on input state.
void UpdateVisibility(); void UpdateVisibility();
// Update cursor based on input state.
void UpdateMouseCursor();
ULocalPlayer* GetLocalPlayer() const; ULocalPlayer* GetLocalPlayer() const;
void TakeFocus(); void TakeFocus();
void ReturnFocus(); void ReturnFocus();
void UpdateInputEnabled(); // Update input state.
void UpdateInputState();
// Determine new input mode based on hints. void HandleWindowFocusLost();
void UpdateInputMode(bool bHasKeyboardFocus, bool bHasMousePointer);
void UpdateCanvasControlMode(const FInputEvent& InputEvent); void UpdateCanvasControlMode(const FInputEvent& InputEvent);
@ -134,13 +122,9 @@ private:
int32 ContextIndex = 0; int32 ContextIndex = 0;
FImGuiInputState* InputState;
EInputMode InputMode = EInputMode::None;
bool bInputEnabled = false; bool bInputEnabled = false;
bool bForegroundWindow = false;
// Whether or not ImGui should draw its own cursor. bool bHideMouseCursor = true;
bool bUseSoftwareCursor = false;
TSharedPtr<SImGuiCanvasControl> CanvasControlWidget; TSharedPtr<SImGuiCanvasControl> CanvasControlWidget;
TWeakPtr<SWidget> PreviousUserFocusedWidget; TWeakPtr<SWidget> PreviousUserFocusedWidget;

View File

@ -21,77 +21,9 @@ class FUICommandInfo;
#endif // WITH_EDITOR #endif // WITH_EDITOR
/** 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, * Handles input and sends it to the input state, which is copied to the ImGui IO at the beginning of the frame.
* primarily to support shortcuts in ImGui input mode. Since mouse is not really needed for this functionality and * Implementation of the input handler can be changed in the ImGui project settings by changing ImGuiInputHandlerClass.
* 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/Extensions/ImGuiInputHandlerClass property to set custom implementation.
*/ */
UCLASS() UCLASS()
class IMGUI_API UImGuiInputHandler : public UObject class IMGUI_API UImGuiInputHandler : public UObject
@ -101,77 +33,85 @@ class IMGUI_API UImGuiInputHandler : public UObject
public: public:
/** /**
* Called when handling character events. * Called to handle character events.
* * @returns Response whether the event was handled
* @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 GetDefaultKeyboardResponse(); } virtual FReply OnKeyChar(const struct FCharacterEvent& CharacterEvent);
/** /**
* Called when handling keyboard key down events. * Called to handle key down events.
* * @returns Response whether the event was handled
* @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); virtual FReply OnKeyDown(const FKeyEvent& KeyEvent);
/** /**
* Called when handling keyboard key up events. * Called to handle key up events.
* * @returns Response whether the event was handled
* 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 GetDefaultKeyboardResponse(); } virtual FReply OnKeyUp(const FKeyEvent& KeyEvent);
/** /**
* Called when handling gamepad key down events. * Called to handle analog value change events.
* * @returns Response whether the event was handled
* @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 GetDefaultGamepadResponse(); } virtual FReply OnAnalogValueChanged(const FAnalogInputEvent& AnalogInputEvent);
/** /**
* Called when handling gamepad key up events. * Called to handle mouse button down events.
* * @returns Response whether the event was handled
* 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 GetDefaultGamepadResponse(); } virtual FReply OnMouseButtonDown(const FPointerEvent& MouseEvent);
/** /**
* Called when handling gamepad analog events. * Called to handle mouse button double-click events.
* * @returns Response whether the event was handled
* @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 GetDefaultGamepadResponse(); } virtual FReply OnMouseButtonDoubleClick(const FPointerEvent& MouseEvent);
/**
* Called to handle mouse button up events.
* @returns Response whether the event was handled
*/
virtual FReply OnMouseButtonUp(const FPointerEvent& MouseEvent);
/**
* Called to handle mouse wheel events.
* @returns Response whether the event was handled
*/
virtual FReply OnMouseWheel(const FPointerEvent& MouseEvent);
/**
* Called to handle mouse move events.
* @param Mouse position (in ImGui space)
* @returns Response whether the event was handled
*/
virtual FReply OnMouseMove(const FVector2D& MousePosition);
/** Called to handle activation of the keyboard input. */
virtual void OnKeyboardInputEnabled();
/** Called to handle deactivation of the keyboard input. */
virtual void OnKeyboardInputDisabled();
/** Called to handle activation of the gamepad input. */
virtual void OnGamepadInputEnabled();
/** Called to handle deactivation of the gamepad input. */
virtual void OnGamepadInputDisabled();
/** Called to handle activation of the mouse input. */
virtual void OnMouseInputEnabled();
/** Called to handle deactivation of the mouse input. */
virtual void OnMouseInputDisabled();
protected: protected:
/** /** Copy state of modifier keys to input state. */
* Get default keyboard response, with consume request based on IsKeyboardInputShared property. void CopyModifierKeys(const FInputEvent& InputEvent);
*
* @returns Default response for keyboard inputs.
*/
FImGuiInputResponse GetDefaultKeyboardResponse() const;
/**
* Get default gamepad response, with consume request based on IsGamepadInputShared property.
*
* @returns Default response for gamepad inputs.
*/
FImGuiInputResponse GetDefaultGamepadResponse() const;
/** /**
* Checks whether this is a key event that can open console. * Checks whether this is a key event that can open console.
*
* @param KeyEvent - Key event to test. * @param KeyEvent - Key event to test.
* @returns True, if this key event can open console. * @returns True, if this key event can open console.
*/ */
@ -180,7 +120,6 @@ protected:
#if WITH_EDITOR #if WITH_EDITOR
/** /**
* Checks whether this is a key event that can stop PIE session. * Checks whether this is a key event that can stop PIE session.
*
* @param KeyEvent - Key event to test. * @param KeyEvent - Key event to test.
* @returns True, if this key event can stop PIE session. * @returns True, if this key event can stop PIE session.
*/ */
@ -189,7 +128,6 @@ protected:
/** /**
* Checks whether this key event can toggle ImGui input (as defined in settings). * Checks whether this key event can toggle ImGui input (as defined in settings).
*
* @param KeyEvent - Key event to test. * @param KeyEvent - Key event to test.
* @returns True, if this key is bound to 'ImGui.ToggleInput' command that switches ImGui input mode. * @returns True, if this key is bound to 'ImGui.ToggleInput' command that switches ImGui input mode.
*/ */
@ -197,15 +135,28 @@ protected:
/** /**
* Checks whether corresponding ImGui context has an active item (holding cursor focus). * Checks whether corresponding ImGui context has an active item (holding cursor focus).
*
* @returns True, if corresponding context has an active item. * @returns True, if corresponding context has an active item.
*/ */
bool HasImGuiActiveItem() const; bool HasImGuiActiveItem() const;
private: private:
void UpdateInputStatePointer();
void OnSoftwareCursorChanged(bool);
void OnPostImGuiUpdate();
void Initialize(FImGuiModuleManager* InModuleManager, UGameViewportClient* InGameViewport, int32 InContextIndex); void Initialize(FImGuiModuleManager* InModuleManager, UGameViewportClient* InGameViewport, int32 InContextIndex);
virtual void BeginDestroy() override;
class FImGuiInputState* InputState = nullptr;
bool bMouseInputEnabled = false;
bool bKeyboardInputEnabled = false;
bool bGamepadInputEnabled = false;
FImGuiModuleManager* ModuleManager = nullptr; FImGuiModuleManager* ModuleManager = nullptr;
TWeakObjectPtr<UGameViewportClient> GameViewport; TWeakObjectPtr<UGameViewportClient> GameViewport;