From f18e1f0b6862ebc319b38a908e82c37d513ab99d Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 13 Mar 2018 23:42:37 +0000 Subject: [PATCH] Changed to hardware cursor as a default cursor in input mode and added ImGui.DrawMouseCursor console variable to allow to switch to ImGui cursor. Using ImGui to draw cursor helps to reduce visual error between mouse cursor and position in ImGui. But it has been reported that this works really bad when frame-rate drops and becomes unusable below ~20 FPS. Taking that into account hardware cursor seems like a better default option. Old behaviour can be enabled with ImGui.DrawMouseCursor console variable. --- README.md | 5 ++-- Source/ImGui/Private/ImGuiContextProxy.cpp | 8 +++-- Source/ImGui/Private/ImGuiContextProxy.h | 5 ++++ .../ImGui/Private/ImGuiInteroperability.cpp | 24 +++++++++++++++ Source/ImGui/Private/ImGuiInteroperability.h | 4 +++ Source/ImGui/Private/SImGuiWidget.cpp | 30 +++++++++++++++---- Source/ImGui/Private/SImGuiWidget.h | 2 ++ 7 files changed, 68 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 48585d8..473e33a 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ The base aim of the project is to provide a basic integration of Dear ImGui, wit This is a work in progress but it supports key Unreal features, like Multi-PIE sessions etc. -When running Multi-PIE session, each world gets its own ImGui context where world specific data can be visualised. When world update begins contexts are switched automatically, so using ImGui during objects update should be as easy as calling ImGui API functions. +When running Multi-PIE session, each world gets its own ImGui context to draw world specific data. When world update begins contexts are switched automatically, so using ImGui during objects update should be as easy as calling ImGui API functions. For scenarios where automatic context switching above is not enough I'm planning to add mechanism allowing to explicitly select contexts. After that I plan to add example project, more usability features, better documentation and integration of Remote ImGui which enables using ImGui from a browser and to investigate possibility of opening ImGui for Blueprints. @@ -51,7 +51,8 @@ You should now be able to use ImGui. *Console variables:* -- **ImGui.InputEnabled** - Allows to enable or disable ImGui input mode. 0: disabled (default); 1: enabled, input is routed to ImGui and with a few exceptions is consumed. Note: this is going to be supported by a keyboard short-cut, but in the meantime ImGui input can be enabled/disabled using console. +- **ImGui.InputEnabled** - Enable or disable ImGui input mode. 0: disabled (default); 1: enabled, input is routed to ImGui and with a few exceptions is consumed. Note: this is going to be supported by a keyboard short-cut, but in the meantime ImGui input can be enabled/disabled using console. +- **ImGui.DrawMouseCursor** - Whether or not mouse cursor in input mode should be drawn by ImGui. 0: disabled, hardware cursor will be used (default); 1: enabled, ImGui will take care for drawing mouse cursor. - **ImGui.ShowDemo** - Show ImGui demo. 0: disabled (default); 1: enabled. - **ImGui.Debug.Widget** - Show self-debug for the widget that renders ImGui output. 0: disabled (default); 1: enabled. diff --git a/Source/ImGui/Private/ImGuiContextProxy.cpp b/Source/ImGui/Private/ImGuiContextProxy.cpp index 6127920..61281da 100644 --- a/Source/ImGui/Private/ImGuiContextProxy.cpp +++ b/Source/ImGui/Private/ImGuiContextProxy.cpp @@ -144,11 +144,13 @@ void FImGuiContextProxy::Tick(float DeltaSeconds, FSimpleMulticastDelegate* Shar EndFrame(); } + // Update context information (some data, like mouse cursor, may be cleaned in new frame, so we should collect it + // beforehand). + bHasActiveItem = ImGui::IsAnyItemActive(); + MouseCursor = ImGuiInterops::ToSlateMouseCursor(ImGui::GetMouseCursor()); + // 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 b07534a..9bb80c9 100644 --- a/Source/ImGui/Private/ImGuiContextProxy.h +++ b/Source/ImGui/Private/ImGuiContextProxy.h @@ -4,6 +4,8 @@ #include "ImGuiDrawData.h" +#include + #include #include @@ -49,6 +51,8 @@ public: bool HasActiveItem() const { return bHasActiveItem; } + EMouseCursor::Type GetMouseCursor() const { return MouseCursor; } + // Delegate called right before ending the frame to allows listeners draw their controls. FSimpleMulticastDelegate& OnDraw() { return DrawEvent; } @@ -66,6 +70,7 @@ private: ImGuiContext* Context = nullptr; bool bHasActiveItem = false; + EMouseCursor::Type MouseCursor = EMouseCursor::None; bool bIsFrameStarted = false; FSimpleMulticastDelegate DrawEvent; diff --git a/Source/ImGui/Private/ImGuiInteroperability.cpp b/Source/ImGui/Private/ImGuiInteroperability.cpp index 940e769..de49ad4 100644 --- a/Source/ImGui/Private/ImGuiInteroperability.cpp +++ b/Source/ImGui/Private/ImGuiInteroperability.cpp @@ -133,6 +133,30 @@ namespace ImGuiInterops return -1; } + EMouseCursor::Type ToSlateMouseCursor(ImGuiMouseCursor MouseCursor) + { + switch (MouseCursor) + { + case ImGuiMouseCursor_Arrow: + return EMouseCursor::Default; + case ImGuiMouseCursor_TextInput: + return EMouseCursor::TextEditBeam; + case ImGuiMouseCursor_Move: + return EMouseCursor::CardinalCross; + case ImGuiMouseCursor_ResizeNS: + return EMouseCursor::ResizeUpDown; + case ImGuiMouseCursor_ResizeEW: + return EMouseCursor::ResizeLeftRight; + case ImGuiMouseCursor_ResizeNESW: + return EMouseCursor::ResizeSouthWest; + case ImGuiMouseCursor_ResizeNWSE: + return EMouseCursor::ResizeSouthEast; + case ImGuiMouseCursor_None: + default: + return EMouseCursor::None; + } + } + //==================================================================================================== // Input State Copying //==================================================================================================== diff --git a/Source/ImGui/Private/ImGuiInteroperability.h b/Source/ImGui/Private/ImGuiInteroperability.h index 7cf1578..86e311b 100644 --- a/Source/ImGui/Private/ImGuiInteroperability.h +++ b/Source/ImGui/Private/ImGuiInteroperability.h @@ -4,6 +4,8 @@ #include "TextureManager.h" +#include + #include @@ -52,6 +54,8 @@ namespace ImGuiInterops return GetMouseIndex(MouseEvent.GetEffectingButton()); } + EMouseCursor::Type ToSlateMouseCursor(ImGuiMouseCursor MouseCursor); + //==================================================================================================== // Input State Copying diff --git a/Source/ImGui/Private/SImGuiWidget.cpp b/Source/ImGui/Private/SImGuiWidget.cpp index de128cc..b689b10 100644 --- a/Source/ImGui/Private/SImGuiWidget.cpp +++ b/Source/ImGui/Private/SImGuiWidget.cpp @@ -33,21 +33,27 @@ DEFINE_LOG_CATEGORY_STATIC(LogImGuiWidget, Warning, All); namespace CVars { TAutoConsoleVariable InputEnabled(TEXT("ImGui.InputEnabled"), 0, - TEXT("Allows to enable or disable ImGui input mode.\n") + TEXT("Enable or disable ImGui input mode.\n") TEXT("0: disabled (default)\n") - TEXT("1: enabled, input is routed to ImGui and with a few exceptions is consumed."), + TEXT("1: enabled, input is routed to ImGui and with a few exceptions is consumed"), + ECVF_Default); + + TAutoConsoleVariable DrawMouseCursor(TEXT("ImGui.DrawMouseCursor"), 0, + TEXT("Whether or not mouse cursor in input mode should be drawn by ImGui.\n") + TEXT("0: disabled, hardware cursor will be used (default)\n") + TEXT("1: enabled, ImGui will take care for drawing mouse cursor"), ECVF_Default); TAutoConsoleVariable DebugWidget(TEXT("ImGui.Debug.Widget"), 0, TEXT("Show debug for SImGuiWidget.\n") TEXT("0: disabled (default)\n") - TEXT("1: enabled."), + 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."), + TEXT("1: enabled"), ECVF_Default); } @@ -189,6 +195,20 @@ 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 (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) { InputState.SetMousePosition(MouseEvent.GetScreenSpacePosition() - MyGeometry.AbsolutePosition); @@ -381,7 +401,7 @@ void SImGuiWidget::UpdateInputMode(bool bHasKeyboardFocus, bool bHasMousePointer ClearMouseEventNotification(); } - InputState.SetMousePointer(bHasMousePointer); + InputState.SetMousePointer(bHasMousePointer && CVars::DrawMouseCursor.GetValueOnGameThread() > 0); } void SImGuiWidget::UpdateMouseStatus() diff --git a/Source/ImGui/Private/SImGuiWidget.h b/Source/ImGui/Private/SImGuiWidget.h index c417b67..58d791e 100644 --- a/Source/ImGui/Private/SImGuiWidget.h +++ b/Source/ImGui/Private/SImGuiWidget.h @@ -61,6 +61,8 @@ 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;