From f40efc265fdd449d3e92a8711ba561c6d7aaa6ae Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 15 Jan 2018 22:07:28 +0000 Subject: [PATCH] Fixed input issues when application loses focus and added debug code for ImGui input state. --- Source/ImGui/Private/SImGuiWidget.cpp | 287 ++++++++++++++++++++++---- 1 file changed, 247 insertions(+), 40 deletions(-) diff --git a/Source/ImGui/Private/SImGuiWidget.cpp b/Source/ImGui/Private/SImGuiWidget.cpp index 2b839b4..de128cc 100644 --- a/Source/ImGui/Private/SImGuiWidget.cpp +++ b/Source/ImGui/Private/SImGuiWidget.cpp @@ -13,6 +13,8 @@ #include +#include + // High enough z-order guarantees that ImGui output is rendered on top of the game UI. constexpr int32 IMGUI_WIDGET_Z_ORDER = 10000; @@ -41,6 +43,12 @@ namespace CVars TEXT("0: disabled (default)\n") TEXT("1: enabled."), ECVF_Default); + + TAutoConsoleVariable DebugInput(TEXT("ImGui.Debug.Input"), 0, + TEXT("Show debug for input state.\n") + TEXT("0: disabled (default)\n") + TEXT("1: enabled."), + ECVF_Default); } BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION @@ -240,7 +248,9 @@ void SImGuiWidget::OnMouseLeave(const FPointerEvent& MouseEvent) UE_LOG(LogImGuiWidget, VeryVerbose, TEXT("ImGui Widget %d - Mouse Leave."), ContextIndex); - UpdateInputMode(HasKeyboardFocus(), false); + // We don't get any events when application loses focus, but often this is followed by OnMouseLeave, so we can use + // this event to immediately disable keyboard input if application lost focus. + UpdateInputMode(HasKeyboardFocus() && GameViewport->Viewport->IsForegroundWindow(), false); } void SImGuiWidget::CopyModifierKeys(const FInputEvent& InputEvent) @@ -333,6 +343,14 @@ void SImGuiWidget::UpdateInputEnabled() Slate.SetKeyboardFocus(SharedThis(this)); } } + + // 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::MouseAndKeyboard) + { + UpdateInputMode(false, IsDirectlyHovered()); + } } void SImGuiWidget::UpdateInputMode(bool bHasKeyboardFocus, bool bHasMousePointer) @@ -453,74 +471,173 @@ FVector2D SImGuiWidget::ComputeDesiredSize(float) const return FVector2D{ 3840.f, 2160.f }; } +static TArray GetImGuiMappedKeys() +{ + TArray Keys; + Keys.Reserve(Utilities::ArraySize::value + 8); + + // ImGui IO key map. + Keys.Emplace(EKeys::Tab); + Keys.Emplace(EKeys::Left); + Keys.Emplace(EKeys::Right); + Keys.Emplace(EKeys::Up); + Keys.Emplace(EKeys::Down); + Keys.Emplace(EKeys::PageUp); + Keys.Emplace(EKeys::PageDown); + Keys.Emplace(EKeys::Home); + Keys.Emplace(EKeys::End); + Keys.Emplace(EKeys::Delete); + Keys.Emplace(EKeys::BackSpace); + Keys.Emplace(EKeys::Enter); + Keys.Emplace(EKeys::Escape); + Keys.Emplace(EKeys::A); + Keys.Emplace(EKeys::C); + Keys.Emplace(EKeys::V); + Keys.Emplace(EKeys::X); + Keys.Emplace(EKeys::Y); + Keys.Emplace(EKeys::Z); + + // Modifier keys. + Keys.Emplace(EKeys::LeftShift); + Keys.Emplace(EKeys::RightShift); + Keys.Emplace(EKeys::LeftControl); + Keys.Emplace(EKeys::RightControl); + Keys.Emplace(EKeys::LeftAlt); + Keys.Emplace(EKeys::RightAlt); + Keys.Emplace(EKeys::LeftCommand); + Keys.Emplace(EKeys::RightCommand); + + return Keys; +} + +// Column layout unitlities. +namespace Columns +{ + template + static void CollapsingGroup(const char* Name, int Columns, FunctorType&& DrawContent) + { + if (ImGui::CollapsingHeader(Name, ImGuiTreeNodeFlags_DefaultOpen)) + { + const int LastColumns = ImGui::GetColumnsCount(); + ImGui::Columns(Columns, nullptr, false); + DrawContent(); + ImGui::Columns(LastColumns); + } + } +} + // Controls tweaked for 2-columns layout. namespace TwoColumns { - static void GroupName(const char* Name) + template + static inline void CollapsingGroup(const char* Name, FunctorType&& DrawContent) { - ImGui::TextColored({ 0.5f, 0.5f, 0.5f, 1.f }, Name); ImGui::NextColumn(); ImGui::NextColumn(); + Columns::CollapsingGroup(Name, 2, std::forward(DrawContent)); } - static void Value(const char* Label, int Value) + namespace { - ImGui::Text("%s:", Label); ImGui::NextColumn(); + void LabelText(const char* Label) + { + ImGui::Text("%s:", Label); + } + + void LabelText(const wchar_t* Label) + { + ImGui::Text("%ls:", Label); + } + } + + template + static void Value(LabelType&& Label, int32 Value) + { + LabelText(Label); ImGui::NextColumn(); ImGui::Text("%d", Value); ImGui::NextColumn(); } - static void Value(const char* Label, bool bValue) + template + static void Value(LabelType&& Label, uint32 Value) { - ImGui::Text("%s:", Label); ImGui::NextColumn(); + LabelText(Label); ImGui::NextColumn(); + ImGui::Text("%u", Value); ImGui::NextColumn(); + } + + template + static void Value(LabelType&& Label, float Value) + { + LabelText(Label); ImGui::NextColumn(); + ImGui::Text("%f", Value); ImGui::NextColumn(); + } + + template + static void Value(LabelType&& Label, bool bValue) + { + LabelText(Label); ImGui::NextColumn(); ImGui::Text("%ls", TEXT_BOOL(bValue)); ImGui::NextColumn(); } - static void Value(const char* Label, const TCHAR* Value) + template + static void Value(LabelType&& Label, const TCHAR* Value) { - ImGui::Text("%s:", Label); ImGui::NextColumn(); + LabelText(Label); ImGui::NextColumn(); ImGui::Text("%ls", Value); ImGui::NextColumn(); } } +namespace Styles +{ + template + static void TextHighlight(bool bHighlight, FunctorType&& DrawContent) + { + if (bHighlight) + { + ImGui::PushStyleColor(ImGuiCol_Text, { 1.f, 1.f, 0.5f, 1.f }); + } + DrawContent(); + if (bHighlight) + { + ImGui::PopStyleColor(); + } + } +} + void SImGuiWidget::OnDebugDraw() { - bool bDebug = CVars::DebugWidget.GetValueOnGameThread() > 0; - if (bDebug) + if (CVars::DebugWidget.GetValueOnGameThread() > 0) { - ImGui::SetNextWindowSize(ImVec2(380, 360), ImGuiSetCond_Once); + bool bDebug = true; + ImGui::SetNextWindowSize(ImVec2(380, 480), ImGuiSetCond_Once); if (ImGui::Begin("ImGui Widget Debug", &bDebug)) { - ImGui::Columns(2, nullptr, false); + ImGui::Spacing(); - TwoColumns::Value("Context Index", ContextIndex); - FImGuiContextProxy* ContextProxy = ModuleManager->GetContextManager().GetContextProxy(ContextIndex); - TwoColumns::Value("Context Name", ContextProxy ? *ContextProxy->GetName() : TEXT("< Null >")); - TwoColumns::Value("Game Viewport", *GameViewport->GetName()); + TwoColumns::CollapsingGroup("Context", [&]() + { + TwoColumns::Value("Context Index", ContextIndex); + FImGuiContextProxy* ContextProxy = ModuleManager->GetContextManager().GetContextProxy(ContextIndex); + TwoColumns::Value("Context Name", ContextProxy ? *ContextProxy->GetName() : TEXT("< Null >")); + TwoColumns::Value("Game Viewport", *GameViewport->GetName()); + }); - ImGui::Separator(); + TwoColumns::CollapsingGroup("Input Mode", [&]() + { + TwoColumns::Value("Input Enabled", bInputEnabled); + TwoColumns::Value("Input Mode", TEXT_INPUT_MODE(InputMode)); + TwoColumns::Value("Input Has Mouse Pointer", InputState.HasMousePointer()); + }); - TwoColumns::Value("Input Enabled", bInputEnabled); - TwoColumns::Value("Input Mode", TEXT_INPUT_MODE(InputMode)); - TwoColumns::Value("Input Has Mouse Pointer", InputState.HasMousePointer()); - - ImGui::Separator(); - - const float GroupIndent = 5.f; - - TwoColumns::GroupName("Widget"); - ImGui::Indent(GroupIndent); + TwoColumns::CollapsingGroup("Widget", [&]() { TwoColumns::Value("Visibility", *GetVisibility().ToString()); TwoColumns::Value("Is Hovered", IsHovered()); TwoColumns::Value("Is Directly Hovered", IsDirectlyHovered()); TwoColumns::Value("Has Keyboard Input", HasKeyboardFocus()); - } - ImGui::Unindent(GroupIndent); + }); - ImGui::Separator(); - - TwoColumns::GroupName("Viewport Widget"); - ImGui::Indent(GroupIndent); + TwoColumns::CollapsingGroup("Viewport", [&]() { const auto& ViewportWidget = GameViewport->GetGameViewportWidget(); + TwoColumns::Value("Is Foreground Window", GameViewport->Viewport->IsForegroundWindow()); TwoColumns::Value("Is Hovered", ViewportWidget->IsHovered()); TwoColumns::Value("Is Directly Hovered", ViewportWidget->IsDirectlyHovered()); TwoColumns::Value("Has Mouse Capture", ViewportWidget->HasMouseCapture()); @@ -528,19 +645,109 @@ void SImGuiWidget::OnDebugDraw() TwoColumns::Value("Has Focused Descendants", ViewportWidget->HasFocusedDescendants()); auto Widget = PreviousUserFocusedWidget.Pin(); TwoColumns::Value("Previous User Focused", Widget.IsValid() ? *Widget->GetTypeAsString() : TEXT("None")); - } - ImGui::Unindent(GroupIndent); - - ImGui::Columns(1); + }); } ImGui::End(); if (!bDebug) { - CVars::DebugWidget->ClearFlags(ECVF_SetByConsole); - CVars::DebugWidget->Set(0); + CVars::DebugWidget->Set(0, ECVF_SetByConsole); } } + + if (CVars::DebugInput.GetValueOnGameThread() > 0) + { + bool bDebug = true; + ImGui::SetNextWindowSize(ImVec2(460, 480), ImGuiSetCond_Once); + if (ImGui::Begin("ImGui Input State", &bDebug)) + { + const ImVec4 HiglightColor{ 1.f, 1.f, 0.5f, 1.f }; + Columns::CollapsingGroup("Mapped Keys", 4, [&]() + { + static const auto& Keys = GetImGuiMappedKeys(); + + const int32 Num = Keys.Num(); + + // Simplified when slicing for two 2. + const int32 RowsNum = (Num + 1) / 2; + + for (int32 Row = 0; Row < RowsNum; Row++) + { + for (int32 Col = 0; Col < 2; Col++) + { + const int32 Idx = Row + Col * RowsNum; + if (Idx < Num) + { + const FKey& Key = Keys[Idx]; + const uint32 KeyIndex = ImGuiInterops::GetKeyIndex(Key); + Styles::TextHighlight(InputState.GetKeys()[KeyIndex], [&]() + { + TwoColumns::Value(*Key.GetDisplayName().ToString(), KeyIndex); + }); + } + else + { + ImGui::NextColumn(); ImGui::NextColumn(); + } + } + } + }); + + Columns::CollapsingGroup("Modifier Keys", 4, [&]() + { + Styles::TextHighlight(InputState.IsShiftDown(), [&]() { ImGui::Text("Shift"); }); ImGui::NextColumn(); + Styles::TextHighlight(InputState.IsControlDown(), [&]() { ImGui::Text("Control"); }); ImGui::NextColumn(); + Styles::TextHighlight(InputState.IsAltDown(), [&]() { ImGui::Text("Alt"); }); ImGui::NextColumn(); + ImGui::NextColumn(); + }); + + Columns::CollapsingGroup("Mouse Buttons", 4, [&]() + { + static const FKey Buttons[] = { EKeys::LeftMouseButton, EKeys::RightMouseButton, + EKeys::MiddleMouseButton, EKeys::ThumbMouseButton, EKeys::ThumbMouseButton2 }; + + const int32 Num = Utilities::GetArraySize(Buttons); + + // Simplified when slicing for two 2. + const int32 RowsNum = (Num + 1) / 2; + + for (int32 Row = 0; Row < RowsNum; Row++) + { + for (int32 Col = 0; Col < 2; Col++) + { + const int32 Idx = Row + Col * RowsNum; + if (Idx < Num) + { + const FKey& Button = Buttons[Idx]; + const uint32 MouseIndex = ImGuiInterops::GetMouseIndex(Button); + Styles::TextHighlight(InputState.GetMouseButtons()[MouseIndex], [&]() + { + TwoColumns::Value(*Button.GetDisplayName().ToString(), MouseIndex); + }); + } + else + { + ImGui::NextColumn(); ImGui::NextColumn(); + } + } + } + }); + + Columns::CollapsingGroup("Mouse Axes", 4, [&]() + { + TwoColumns::Value("Position X", InputState.GetMousePosition().X); + TwoColumns::Value("Position Y", InputState.GetMousePosition().Y); + TwoColumns::Value("Wheel Delta", InputState.GetMouseWheelDelta()); + ImGui::NextColumn(); ImGui::NextColumn(); + }); + + if (!bDebug) + { + CVars::DebugInput->Set(0, ECVF_SetByConsole); + } + } + ImGui::End(); + } } #undef TEXT_INPUT_MODE