From 5625365310d8457eede734a389dc2e2d0b004038 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sat, 16 Sep 2017 21:52:14 +0100 Subject: [PATCH] Added handling of special input cases: - Different handling of 'Esc' key. If ImGui has active items then it is used to exit edit mode, otherwise it is passed to editor to cancel session. - Suppressing keyboard input when console is opened and retaking it when it is closed. - Fixed issue with losing input when console is closed. --- Source/ImGui/Private/ImGuiContextProxy.cpp | 5 ++ Source/ImGui/Private/ImGuiContextProxy.h | 4 ++ Source/ImGui/Private/SImGuiWidget.cpp | 76 +++++++++++++++++----- Source/ImGui/Private/SImGuiWidget.h | 4 ++ 4 files changed, 71 insertions(+), 18 deletions(-) diff --git a/Source/ImGui/Private/ImGuiContextProxy.cpp b/Source/ImGui/Private/ImGuiContextProxy.cpp index 901d574..198a8b9 100644 --- a/Source/ImGui/Private/ImGuiContextProxy.cpp +++ b/Source/ImGui/Private/ImGuiContextProxy.cpp @@ -39,6 +39,7 @@ FImGuiContextProxy::FImGuiContextProxy() FImGuiContextProxy::FImGuiContextProxy(FImGuiContextProxy&& Other) : Context(std::move(Other.Context)) + , bHasActiveItem(Other.bHasActiveItem) , DrawEvent(std::move(Other.DrawEvent)) , InputState(std::move(Other.InputState)) , DrawLists(std::move(Other.DrawLists)) @@ -50,6 +51,7 @@ FImGuiContextProxy& FImGuiContextProxy::operator=(FImGuiContextProxy&& Other) { Context = std::move(Other.Context); Other.Context = nullptr; + bHasActiveItem = Other.bHasActiveItem; DrawEvent = std::move(Other.DrawEvent); InputState = std::move(Other.InputState); DrawLists = std::move(Other.DrawLists); @@ -91,6 +93,9 @@ void FImGuiContextProxy::Tick(float DeltaSeconds) // Begin a new frame and set the context back to a state in which it allows to draw controls. BeginFrame(DeltaSeconds); + + // Update context information. + bHasActiveItem = ImGui::IsAnyItemActive(); } void FImGuiContextProxy::BeginFrame(float DeltaTime) diff --git a/Source/ImGui/Private/ImGuiContextProxy.h b/Source/ImGui/Private/ImGuiContextProxy.h index 19e4b17..570ffd9 100644 --- a/Source/ImGui/Private/ImGuiContextProxy.h +++ b/Source/ImGui/Private/ImGuiContextProxy.h @@ -39,6 +39,8 @@ public: // Set this context as current ImGui context. void SetAsCurrent() { ImGui::SetCurrentContext(Context); } + bool HasActiveItem() const { return bHasActiveItem; } + // Delegate called right before ending the frame to allows listeners draw their controls. FSimpleMulticastDelegate& OnDraw() { return DrawEvent; } @@ -54,6 +56,8 @@ private: ImGuiContext* Context = nullptr; + bool bHasActiveItem = false; + bool bIsFrameStarted = false; FSimpleMulticastDelegate DrawEvent; const FImGuiInputState* InputState = nullptr; diff --git a/Source/ImGui/Private/SImGuiWidget.cpp b/Source/ImGui/Private/SImGuiWidget.cpp index fddfe82..7ee009c 100644 --- a/Source/ImGui/Private/SImGuiWidget.cpp +++ b/Source/ImGui/Private/SImGuiWidget.cpp @@ -11,6 +11,8 @@ #include "TextureManager.h" #include "Utilities/ScopeGuards.h" +#include + // High enough z-order guarantees that ImGui output is rendered on top of the game UI. constexpr int32 IMGUI_WIDGET_Z_ORDER = 10000; @@ -98,6 +100,11 @@ void SImGuiWidget::Tick(const FGeometry& AllottedGeometry, const double InCurren FReply SImGuiWidget::OnKeyChar(const FGeometry& MyGeometry, const FCharacterEvent& CharacterEvent) { + if (IsConsoleOpened()) + { + return FReply::Unhandled(); + } + InputState.AddCharacter(CharacterEvent.GetCharacter()); return FReply::Handled(); @@ -105,6 +112,11 @@ FReply SImGuiWidget::OnKeyChar(const FGeometry& MyGeometry, const FCharacterEven FReply SImGuiWidget::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& KeyEvent) { + if (IsConsoleOpened() || IgnoreKeyEvent(KeyEvent)) + { + return FReply::Unhandled(); + } + InputState.SetKeyDown(ImGuiInterops::GetKeyIndex(KeyEvent), true); CopyModifierKeys(KeyEvent); @@ -119,10 +131,13 @@ FReply SImGuiWidget::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& Key FReply SImGuiWidget::OnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& KeyEvent) { + // 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. InputState.SetKeyDown(ImGuiInterops::GetKeyIndex(KeyEvent), false); CopyModifierKeys(KeyEvent); - return FReply::Handled(); + // 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() : FReply::Handled(); } FReply SImGuiWidget::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) @@ -175,6 +190,7 @@ FReply SImGuiWidget::OnFocusReceived(const FGeometry& MyGeometry, const FFocusEv // area it won't generate events but we freeze its state until it either comes back or input is completely lost. UpdateInputMode(true, true); + FSlateApplication::Get().ResetToDefaultPointerInputSettings(); return FReply::Handled(); } @@ -230,6 +246,32 @@ void SImGuiWidget::CopyModifierKeys(const FPointerEvent& MouseEvent) } } +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::ResetInputState() { bInputEnabled = false; @@ -258,24 +300,9 @@ void SImGuiWidget::UpdateInputEnabled() SetVisibilityFromInputEnabled(); - // Setup input to show cursor and to pass keyboard/user focus between viewport and widget. Note that we should - // only pass focus if it is inside of the parent viewport, otherwise we would be stealing from other viewports - // or windows. - auto& Slate = FSlateApplication::Get(); - if (bInputEnabled) - { - const auto& ViewportWidget = GameViewport->GetGameViewportWidget(); - if (ViewportWidget->HasKeyboardFocus() || ViewportWidget->HasFocusedDescendants()) - { - // Remember where is user focus, so we will have an option to restore it. - PreviousUserFocusedWidget = Slate.GetUserFocusedWidget(Slate.GetUserIndexForKeyboard()); - - Slate.ResetToDefaultPointerInputSettings(); - Slate.SetKeyboardFocus(SharedThis(this)); - } - } - else + if (!bInputEnabled) { + auto& Slate = FSlateApplication::Get(); if (Slate.GetKeyboardFocusedWidget().Get() == this) { Slate.ResetToDefaultPointerInputSettings(); @@ -288,6 +315,19 @@ void SImGuiWidget::UpdateInputEnabled() UpdateInputMode(false, false); } } + + // Note: Some widgets, like console, can reset focus to viewport after we already grabbed it. If we detect that + // viewport has a focus while input is enabled we will take it. + if (bInputEnabled && !HasKeyboardFocus() && !IsConsoleOpened()) + { + const auto& ViewportWidget = GameViewport->GetGameViewportWidget(); + if (ViewportWidget->HasKeyboardFocus() || ViewportWidget->HasFocusedDescendants()) + { + auto& Slate = FSlateApplication::Get(); + PreviousUserFocusedWidget = Slate.GetUserFocusedWidget(Slate.GetUserIndexForKeyboard()); + Slate.SetKeyboardFocus(SharedThis(this)); + } + } } void SImGuiWidget::UpdateInputMode(bool bNeedKeyboard, bool bNeedMouse) diff --git a/Source/ImGui/Private/SImGuiWidget.h b/Source/ImGui/Private/SImGuiWidget.h index 20f1ead..d4f9bf5 100644 --- a/Source/ImGui/Private/SImGuiWidget.h +++ b/Source/ImGui/Private/SImGuiWidget.h @@ -86,6 +86,10 @@ private: FORCEINLINE void CopyModifierKeys(const FInputEvent& InputEvent); FORCEINLINE void CopyModifierKeys(const FPointerEvent& MouseEvent); + bool IsConsoleOpened() const; + + bool IgnoreKeyEvent(const FKeyEvent& KeyEvent) const; + void ResetInputState(); // Update visibility based on input enabled state.