diff --git a/Source/ImGui/Private/ImGuiContextProxy.cpp b/Source/ImGui/Private/ImGuiContextProxy.cpp index 198a8b9..129c928 100644 --- a/Source/ImGui/Private/ImGuiContextProxy.cpp +++ b/Source/ImGui/Private/ImGuiContextProxy.cpp @@ -25,9 +25,19 @@ FImGuiContextProxy::FImGuiContextProxy() // Use pre-defined canvas size. IO.DisplaySize = { DEFAULT_CANVAS_WIDTH, DEFAULT_CANVAS_HEIGHT }; - // Load texture atlas. + // When GetTexData is called for the first time it builds atlas texture and copies mouse cursor data to context. + // When multiple contexts share atlas then only the first one will get mouse data. A simple workaround is to use + // a temporary atlas if shared one is already built. unsigned char* Pixels; - IO.Fonts->GetTexDataAsRGBA32(&Pixels, nullptr, nullptr); + const bool bIsAltasBuilt = IO.Fonts->TexPixelsAlpha8 != nullptr; + if (bIsAltasBuilt) + { + ImFontAtlas().GetTexDataAsRGBA32(&Pixels, nullptr, nullptr); + } + else + { + IO.Fonts->GetTexDataAsRGBA32(&Pixels, nullptr, nullptr); + } // Initialize key mapping, so context can correctly interpret input state. ImGuiInterops::SetUnrealKeyMap(IO); diff --git a/Source/ImGui/Private/ImGuiInputState.h b/Source/ImGui/Private/ImGuiInputState.h index 853de90..f369e86 100644 --- a/Source/ImGui/Private/ImGuiInputState.h +++ b/Source/ImGui/Private/ImGuiInputState.h @@ -76,6 +76,13 @@ public: // @param Position - New mouse position void SetMousePosition(const FVector2D& Position) { MousePosition = Position; } + // Check whether input has active mouse pointer. + bool HasMousePointer() const { return bHasMousePointer; } + + // Set whether input has active mouse pointer. + // @param bHasPointer - True, if input has active mouse pointer + void SetMousePointer(bool bHasPointer) { bHasMousePointer = bHasPointer; } + // Get Control down state. bool IsControlDown() const { return bIsControlDown; } @@ -132,6 +139,8 @@ private: FKeysArray KeysDown; FKeysIndexRange KeysUpdateRange; + bool bHasMousePointer = false; + bool bIsControlDown = false; bool bIsShiftDown = false; bool bIsAltDown = false; diff --git a/Source/ImGui/Private/ImGuiInteroperability.cpp b/Source/ImGui/Private/ImGuiInteroperability.cpp index 044058d..1ba8e1a 100644 --- a/Source/ImGui/Private/ImGuiInteroperability.cpp +++ b/Source/ImGui/Private/ImGuiInteroperability.cpp @@ -146,6 +146,9 @@ namespace ImGuiInterops static const uint32 LeftAlt = GetKeyIndex(EKeys::LeftAlt); static const uint32 RightAlt = GetKeyIndex(EKeys::RightAlt); + // Check whether we need to draw cursor. + IO.MouseDrawCursor = InputState.HasMousePointer(); + // Copy mouse position. IO.MousePos.x = InputState.GetMousePosition().X; IO.MousePos.y = InputState.GetMousePosition().Y; diff --git a/Source/ImGui/Private/SImGuiWidget.cpp b/Source/ImGui/Private/SImGuiWidget.cpp index 7ee009c..f48f113 100644 --- a/Source/ImGui/Private/SImGuiWidget.cpp +++ b/Source/ImGui/Private/SImGuiWidget.cpp @@ -20,7 +20,11 @@ constexpr int32 IMGUI_WIDGET_Z_ORDER = 10000; DEFINE_LOG_CATEGORY_STATIC(LogImGuiWidget, Warning, All); -#define TEXT_INPUT_MODE(Val) ((Val) == EInputMode::MouseAndKeyboard ? TEXT("MouseAndKeyboard") : (Val) == EInputMode::MouseOnly ? TEXT("MouseOnly") : TEXT("None")) +#define TEXT_INPUT_MODE(Val) (\ + (Val) == EInputMode::MouseAndKeyboard ? TEXT("MouseAndKeyboard") :\ + (Val) == EInputMode::MousePointerOnly ? TEXT("MousePointerOnly") :\ + TEXT("None")) + #define TEXT_BOOL(Val) ((Val) ? TEXT("true") : TEXT("false")) @@ -47,6 +51,9 @@ void SImGuiWidget::Construct(const FArguments& InArgs) ModuleManager = InArgs._ModuleManager; ContextIndex = InArgs._ContextIndex; + // Disable mouse cursor over this widget as we will use ImGui to draw it. + SetCursor(EMouseCursor::None); + // Sync visibility with default input enabled state. SetVisibilityFromInputEnabled(); @@ -93,6 +100,8 @@ void SImGuiWidget::Tick(const FGeometry& AllottedGeometry, const double InCurren { Super::Tick(AllottedGeometry, InCurrentTime, InDeltaTime); + UpdateMouseStatus(); + // Note: Moving that update to console variable sink or callback might seem like a better alternative but input // setup in this function is better handled here. UpdateInputEnabled(); @@ -177,6 +186,9 @@ FReply SImGuiWidget::OnMouseMove(const FGeometry& MyGeometry, const FPointerEven InputState.SetMousePosition(MouseEvent.GetScreenSpacePosition() - MyGeometry.AbsolutePosition); CopyModifierKeys(MouseEvent); + // This event is called in every frame when we have a mouse, so we can use it to raise notifications. + NotifyMouseEvent(); + return FReply::Handled(); } @@ -188,7 +200,7 @@ FReply SImGuiWidget::OnFocusReceived(const FGeometry& MyGeometry, const FFocusEv // If widget has a keyboard focus we always maintain mouse input. Technically, if mouse is outside of the widget // area it won't generate events but we freeze its state until it either comes back or input is completely lost. - UpdateInputMode(true, true); + UpdateInputMode(true, IsDirectlyHovered()); FSlateApplication::Get().ResetToDefaultPointerInputSettings(); return FReply::Handled(); @@ -200,7 +212,7 @@ void SImGuiWidget::OnFocusLost(const FFocusEvent& FocusEvent) UE_LOG(LogImGuiWidget, VeryVerbose, TEXT("ImGui Widget %d - Focus Lost."), ContextIndex); - UpdateInputMode(false, IsHovered()); + UpdateInputMode(false, IsDirectlyHovered()); } void SImGuiWidget::OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) @@ -240,7 +252,7 @@ void SImGuiWidget::CopyModifierKeys(const FInputEvent& InputEvent) void SImGuiWidget::CopyModifierKeys(const FPointerEvent& MouseEvent) { - if (InputMode == EInputMode::MouseOnly) + if (InputMode == EInputMode::MousePointerOnly) { CopyModifierKeys(static_cast(MouseEvent)); } @@ -330,11 +342,11 @@ void SImGuiWidget::UpdateInputEnabled() } } -void SImGuiWidget::UpdateInputMode(bool bNeedKeyboard, bool bNeedMouse) +void SImGuiWidget::UpdateInputMode(bool bHasKeyboardFocus, bool bHasMousePointer) { const EInputMode NewInputMode = - bNeedKeyboard ? EInputMode::MouseAndKeyboard : - bNeedMouse ? EInputMode::MouseOnly : + bHasKeyboardFocus ? EInputMode::MouseAndKeyboard : + bHasMousePointer ? EInputMode::MousePointerOnly : EInputMode::None; if (InputMode != NewInputMode) @@ -354,6 +366,26 @@ void SImGuiWidget::UpdateInputMode(bool bNeedKeyboard, bool bNeedMouse) } InputMode = NewInputMode; + + ClearMouseEventNotification(); + } + + InputState.SetMousePointer(bHasMousePointer); +} + +void SImGuiWidget::UpdateMouseStatus() +{ + // Note: Mouse leave events can get lost if other viewport takes mouse capture (for instance console is opened by + // different viewport when this widget is hovered). With that we lose a chance to cleanup and hide ImGui pointer. + // We could either update ImGui pointer in every frame or like below, use mouse events to catch when mouse is lost. + + if (InputMode == EInputMode::MousePointerOnly) + { + if (!HasMouseEventNotification()) + { + UpdateInputMode(false, IsDirectlyHovered()); + } + ClearMouseEventNotification(); } } @@ -447,7 +479,7 @@ void SImGuiWidget::OnDebugDraw() bool bDebug = CVars::DebugWidget.GetValueOnGameThread() > 0; if (bDebug) { - ImGui::SetNextWindowSize(ImVec2(380, 320), ImGuiSetCond_Once); + ImGui::SetNextWindowSize(ImVec2(380, 340), ImGuiSetCond_Once); if (ImGui::Begin("ImGui Widget Debug", &bDebug)) { ImGui::Columns(2, nullptr, false); @@ -459,6 +491,7 @@ void SImGuiWidget::OnDebugDraw() TwoColumns::Value("Input Enabled", bInputEnabled); TwoColumns::Value("Input Mode", TEXT_INPUT_MODE(InputMode)); + TwoColumns::Value("Input Has Mouse Pointer", InputState.HasMousePointer()); ImGui::Separator(); diff --git a/Source/ImGui/Private/SImGuiWidget.h b/Source/ImGui/Private/SImGuiWidget.h index d4f9bf5..a1df1d3 100644 --- a/Source/ImGui/Private/SImGuiWidget.h +++ b/Source/ImGui/Private/SImGuiWidget.h @@ -79,7 +79,9 @@ private: enum class EInputMode : uint8 { None, - MouseOnly, + // Mouse pointer only without user focus + MousePointerOnly, + // Full input with user focus MouseAndKeyboard }; @@ -98,8 +100,14 @@ private: // Update input enabled state from console variable. void UpdateInputEnabled(); - // Determine new input mode based on requirement hints. - void UpdateInputMode(bool bNeedKeyboard, bool bNeedMouse); + // Determine new input mode based on hints. + void UpdateInputMode(bool bHasKeyboardFocus, bool bHasMousePointer); + + void UpdateMouseStatus(); + + FORCEINLINE bool HasMouseEventNotification() const { return bReceivedMouseEvent; } + FORCEINLINE void NotifyMouseEvent() { bReceivedMouseEvent = true; } + FORCEINLINE void ClearMouseEventNotification() { bReceivedMouseEvent = false; } void OnPostImGuiUpdate(); @@ -119,6 +127,7 @@ private: EInputMode InputMode = EInputMode::None; bool bInputEnabled = false; + bool bReceivedMouseEvent = false; FImGuiInputState InputState;