diff --git a/CHANGES.md b/CHANGES.md index 400d8ad..dc9079a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,10 @@ Versions marked as 'unofficial' are labelled only for the needs of this changelo Change History -------------- +Version: 1.17 (2019/04) +- Added support for sharing with game mouse input. +- Refactorization of input handling, with changes in SImGuiWidget and compatibility breaking changes in UImGuiInputHandler. + Version: 1.16 (2019/05) - Fixed issue with SImGuiLayout blocking mouse input for other Slate widgets, which was introduced by refactorization of widgets (version 1.14, commit c144658f). diff --git a/README.md b/README.md index c5d7faa..7daf2f0 100644 --- a/README.md +++ b/README.md @@ -172,9 +172,7 @@ In input mode, ImGui will consume all input events. The reason behind the input It is possible to modify rules to share keyboard or gamepad inputs. -The default behaviour can be configured in [input settings](#input) and changed runtime using `Keyboard Input Shared` and `Gamepad Input Shared` [properties](#properties) or `ImGui.ToggleKeyboardInputSharing`and `ImGui.ToggleGamepadInputSharing` [commands](#console-commands). - ->*More work is needed for mouse input. Originally I didn't plan this feature so most probably I will come back to it after refactoring input handling. Ideally, I would like something that is more customizable from code with potentially a few implementations rather than one implementation with growing number of properties. In the meantime, if more control is needed, then `SImGuiWidget` is a good place to look at.* +The default behaviour can be configured in [input settings](#input) and changed during runtime by modifying `Keyboard Input Shared`, `Gamepad Input Shared` and `Mouse Input Shared` [properties](#properties) or `ImGui.ToggleKeyboardInputSharing`, `ImGui.ToggleGamepadInputSharing` and `ImGui.ToggleMouseInputSharing` [commands](#console-commands). #### Keyboard and gamepad navigation diff --git a/Source/ImGui/Private/ImGuiContextProxy.cpp b/Source/ImGui/Private/ImGuiContextProxy.cpp index 73cbb2c..bf21891 100644 --- a/Source/ImGui/Private/ImGuiContextProxy.cpp +++ b/Source/ImGui/Private/ImGuiContextProxy.cpp @@ -7,6 +7,7 @@ #include "ImGuiDelegatesContainer.h" #include "ImGuiImplementation.h" #include "ImGuiInteroperability.h" +#include "Utilities/Arrays.h" #include @@ -136,6 +137,7 @@ void FImGuiContextProxy::Tick(float DeltaSeconds) // Update context information (some data, like mouse cursor, may be cleaned in new frame, so we should collect it // beforehand). bHasActiveItem = ImGui::IsAnyItemActive(); + bIsMouseHoveringAnyWindow = ImGui::IsMouseHoveringAnyWindow(); MouseCursor = ImGuiInterops::ToSlateMouseCursor(ImGui::GetMouseCursor()); DisplaySize = ImGuiInterops::ToVector2D(ImGui::GetIO().DisplaySize); diff --git a/Source/ImGui/Private/ImGuiContextProxy.h b/Source/ImGui/Private/ImGuiContextProxy.h index f20291e..b03e539 100644 --- a/Source/ImGui/Private/ImGuiContextProxy.h +++ b/Source/ImGui/Private/ImGuiContextProxy.h @@ -65,13 +65,16 @@ public: // Set this context as current ImGui context. void SetAsCurrent() { ImGui::SetCurrentContext(Context.Get()); } - // Context display size (read once per frame during context update and cached here for easy access). + // Context display size (read once per frame during context update). const FVector2D& GetDisplaySize() const { return DisplaySize; } - // Whether this context has an active item (read once per frame during context update and cached here for easy access). + // Whether this context has an active item (read once per frame during context update). bool HasActiveItem() const { return bHasActiveItem; } - // Cursor type desired by this context (this is updated during ImGui frame and cached here during context update, before it is reset). + // Whether this context has mouse hovering any window (read once per frame during context update). + bool IsMouseHoveringAnyWindow() const { return bIsMouseHoveringAnyWindow; } + + // Cursor type desired by this context (updated once per frame during context update). EMouseCursor::Type GetMouseCursor() const { return MouseCursor; } // Delegate called right before ending the frame to allows listeners draw their controls. @@ -105,6 +108,7 @@ private: EMouseCursor::Type MouseCursor = EMouseCursor::None; bool bHasActiveItem = false; + bool bIsMouseHoveringAnyWindow = false; bool bIsFrameStarted = false; bool bIsDrawEarlyDebugCalled = false; diff --git a/Source/ImGui/Private/ImGuiModuleCommands.cpp b/Source/ImGui/Private/ImGuiModuleCommands.cpp index 84f4179..42f5148 100644 --- a/Source/ImGui/Private/ImGuiModuleCommands.cpp +++ b/Source/ImGui/Private/ImGuiModuleCommands.cpp @@ -12,6 +12,7 @@ const TCHAR* const FImGuiModuleCommands::ToggleKeyboardNavigation = TEXT("ImGui. const TCHAR* const FImGuiModuleCommands::ToggleGamepadNavigation = TEXT("ImGui.ToggleGamepadNavigation"); const TCHAR* const FImGuiModuleCommands::ToggleKeyboardInputSharing = TEXT("ImGui.ToggleKeyboardInputSharing"); const TCHAR* const FImGuiModuleCommands::ToggleGamepadInputSharing = TEXT("ImGui.ToggleGamepadInputSharing"); +const TCHAR* const FImGuiModuleCommands::ToggleMouseInputSharing = TEXT("ImGui.ToggleMouseInputSharing"); const TCHAR* const FImGuiModuleCommands::ToggleDemo = TEXT("ImGui.ToggleDemo"); FImGuiModuleCommands::FImGuiModuleCommands(FImGuiModuleProperties& InProperties) @@ -31,6 +32,9 @@ FImGuiModuleCommands::FImGuiModuleCommands(FImGuiModuleProperties& InProperties) , ToggleGamepadInputSharingCommand(ToggleGamepadInputSharing, TEXT("Toggle ImGui gamepad input sharing."), FConsoleCommandDelegate::CreateRaw(this, &FImGuiModuleCommands::ToggleGamepadInputSharingImpl)) + , ToggleMouseInputSharingCommand(ToggleMouseInputSharing, + TEXT("Toggle ImGui mouse input sharing."), + FConsoleCommandDelegate::CreateRaw(this, &FImGuiModuleCommands::ToggleMouseInputSharingImpl)) , ToggleDemoCommand(ToggleDemo, TEXT("Toggle ImGui demo."), FConsoleCommandDelegate::CreateRaw(this, &FImGuiModuleCommands::ToggleDemoImpl)) @@ -67,6 +71,11 @@ void FImGuiModuleCommands::ToggleGamepadInputSharingImpl() Properties.ToggleGamepadInputSharing(); } +void FImGuiModuleCommands::ToggleMouseInputSharingImpl() +{ + Properties.ToggleMouseInputSharing(); +} + void FImGuiModuleCommands::ToggleDemoImpl() { Properties.ToggleDemo(); diff --git a/Source/ImGui/Private/ImGuiModuleCommands.h b/Source/ImGui/Private/ImGuiModuleCommands.h index 6eba968..7ecb3e4 100644 --- a/Source/ImGui/Private/ImGuiModuleCommands.h +++ b/Source/ImGui/Private/ImGuiModuleCommands.h @@ -18,6 +18,7 @@ public: static const TCHAR* const ToggleGamepadNavigation; static const TCHAR* const ToggleKeyboardInputSharing; static const TCHAR* const ToggleGamepadInputSharing; + static const TCHAR* const ToggleMouseInputSharing; static const TCHAR* const ToggleDemo; FImGuiModuleCommands(FImGuiModuleProperties& InProperties); @@ -31,6 +32,7 @@ private: void ToggleGamepadNavigationImpl(); void ToggleKeyboardInputSharingImpl(); void ToggleGamepadInputSharingImpl(); + void ToggleMouseInputSharingImpl(); void ToggleDemoImpl(); FImGuiModuleProperties& Properties; @@ -40,5 +42,6 @@ private: FAutoConsoleCommand ToggleGamepadNavigationCommand; FAutoConsoleCommand ToggleKeyboardInputSharingCommand; FAutoConsoleCommand ToggleGamepadInputSharingCommand; + FAutoConsoleCommand ToggleMouseInputSharingCommand; FAutoConsoleCommand ToggleDemoCommand; }; diff --git a/Source/ImGui/Private/ImGuiModuleSettings.cpp b/Source/ImGui/Private/ImGuiModuleSettings.cpp index bc68918..d2a6b6f 100644 --- a/Source/ImGui/Private/ImGuiModuleSettings.cpp +++ b/Source/ImGui/Private/ImGuiModuleSettings.cpp @@ -100,6 +100,7 @@ void FImGuiModuleSettings::UpdateSettings() SetImGuiInputHandlerClass(SettingsObject->ImGuiInputHandlerClass); SetShareKeyboardInput(SettingsObject->bShareKeyboardInput); SetShareGamepadInput(SettingsObject->bShareGamepadInput); + SetShareMouseInput(SettingsObject->bShareMouseInput); SetUseSoftwareCursor(SettingsObject->bUseSoftwareCursor); SetToggleInputKey(SettingsObject->ToggleInput); } @@ -132,6 +133,15 @@ void FImGuiModuleSettings::SetShareGamepadInput(bool bShare) } } +void FImGuiModuleSettings::SetShareMouseInput(bool bShare) +{ + if (bShareMouseInput != bShare) + { + bShareMouseInput = bShare; + Properties.SetMouseInputShared(bShare); + } +} + void FImGuiModuleSettings::SetUseSoftwareCursor(bool bUse) { if (bUseSoftwareCursor != bUse) diff --git a/Source/ImGui/Private/ImGuiModuleSettings.h b/Source/ImGui/Private/ImGuiModuleSettings.h index 7e32993..df0d9b4 100644 --- a/Source/ImGui/Private/ImGuiModuleSettings.h +++ b/Source/ImGui/Private/ImGuiModuleSettings.h @@ -83,6 +83,12 @@ protected: UPROPERTY(EditAnywhere, config, Category = "Input") bool bShareGamepadInput = false; + // Whether ImGui should share mouse input with game. + // This defines initial behaviour which can be later changed using 'ImGui.ToggleMouseInputSharing' command or + // module properties interface. + UPROPERTY(EditAnywhere, config, Category = "Input") + bool bShareMouseInput = false; + // If true, then in input mode ImGui will draw its own cursor in place of the hardware one. // When disabled (default) there is a noticeable difference between cursor position seen by ImGui and position on // the screen. Enabling this option removes that effect but with lower frame-rates UI becomes quickly unusable. @@ -155,6 +161,7 @@ private: void SetImGuiInputHandlerClass(const FStringClassReference& ClassReference); void SetShareKeyboardInput(bool bShare); void SetShareGamepadInput(bool bShare); + void SetShareMouseInput(bool bShare); void SetUseSoftwareCursor(bool bUse); void SetToggleInputKey(const FImGuiKeyInfo& KeyInfo); @@ -169,5 +176,6 @@ private: FImGuiKeyInfo ToggleInputKey; bool bShareKeyboardInput = false; bool bShareGamepadInput = false; + bool bShareMouseInput = false; bool bUseSoftwareCursor = false; }; diff --git a/Source/ImGui/Private/Widgets/SImGuiWidget.cpp b/Source/ImGui/Private/Widgets/SImGuiWidget.cpp index b04b3a2..1fa872f 100644 --- a/Source/ImGui/Private/Widgets/SImGuiWidget.cpp +++ b/Source/ImGui/Private/Widgets/SImGuiWidget.cpp @@ -123,6 +123,7 @@ void SImGuiWidget::Tick(const FGeometry& AllottedGeometry, const double InCurren Super::Tick(AllottedGeometry, InCurrentTime, InDeltaTime); UpdateInputState(); + UpdateTransparentMouseInput(AllottedGeometry); HandleWindowFocusLost(); } @@ -291,8 +292,9 @@ bool SImGuiWidget::IsConsoleOpened() const void SImGuiWidget::UpdateVisibility() { - // If we don't use input disable hit test to make this widget invisible for cursors hit detection. - SetVisibility(bInputEnabled ? EVisibility::Visible : EVisibility::HitTestInvisible); + // Make sure that we do not occlude other widgets, if input is disabled or if mouse is set to work in a transparent + // mode (hit-test invisible). + SetVisibility(bInputEnabled && !bTransparentMouseInput ? EVisibility::Visible : EVisibility::HitTestInvisible); IMGUI_WIDGET_LOG(VeryVerbose, TEXT("ImGui Widget %d - Visibility updated to '%s'."), ContextIndex, *GetVisibility().ToString()); @@ -372,13 +374,26 @@ void SImGuiWidget::ReturnFocus() void SImGuiWidget::UpdateInputState() { - const bool bEnabled = ModuleManager && ModuleManager->GetProperties().IsInputEnabled(); - if (bInputEnabled != bEnabled) + auto& Properties = ModuleManager->GetProperties(); + auto* ContextPropxy = ModuleManager->GetContextManager().GetContextProxy(ContextIndex); + + const bool bEnableTransparentMouseInput = Properties.IsMouseInputShared() && !ContextPropxy->IsMouseHoveringAnyWindow(); + if (bTransparentMouseInput != bEnableTransparentMouseInput) + { + bTransparentMouseInput = bEnableTransparentMouseInput; + if (bInputEnabled) + { + UpdateVisibility(); + } + } + + const bool bEnableInput = Properties.IsInputEnabled(); + if (bInputEnabled != bEnableInput) { IMGUI_WIDGET_LOG(Log, TEXT("ImGui Widget %d - Input Enabled changed to '%s'."), - ContextIndex, TEXT_BOOL(bEnabled)); + ContextIndex, TEXT_BOOL(bEnableInput)); - bInputEnabled = bEnabled; + bInputEnabled = bEnableInput; UpdateVisibility(); UpdateMouseCursor(); @@ -391,23 +406,47 @@ void SImGuiWidget::UpdateInputState() InputHandler->OnMouseInputEnabled(); } - // Focus is handled later as it can depend on additional factors. + TakeFocus(); } else { ReturnFocus(); } } - - // 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 - // a foreground window (application), if viewport doesn't have a focus or if console is opened. Note that this - // 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()) + else if(bInputEnabled) { const auto& ViewportWidget = GameViewport->GetGameViewportWidget(); - if (ViewportWidget->HasKeyboardFocus() || ViewportWidget->HasFocusedDescendants()) + + if (bTransparentMouseInput) { - TakeFocus(); + // If mouse is in transparent input mode and focus is lost to viewport, let viewport keep it and disable + // the whole input to match that state. + if (GameViewport->GetGameViewportWidget()->HasMouseCapture()) + { + Properties.SetInputEnabled(false); + UpdateInputState(); + } + } + else + { + // Widget tends to lose keyboard focus after console is opened. With non-transparent mouse we can fix that + // by manually restoring it. + if (!HasKeyboardFocus() && !IsConsoleOpened() && (ViewportWidget->HasKeyboardFocus() || ViewportWidget->HasFocusedDescendants())) + { + TakeFocus(); + } + } + } +} + +void SImGuiWidget::UpdateTransparentMouseInput(const FGeometry& AllottedGeometry) +{ + if (bInputEnabled && bTransparentMouseInput) + { + if (!GameViewport->GetGameViewportWidget()->HasMouseCapture()) + { + const FSlateRenderTransform ImGuiToScreen = ImGuiTransform.Concatenate(AllottedGeometry.GetAccumulatedRenderTransform()); + InputHandler->OnMouseMove(ImGuiToScreen.Inverse().TransformPoint(FSlateApplication::Get().GetCursorPos())); } } } diff --git a/Source/ImGui/Private/Widgets/SImGuiWidget.h b/Source/ImGui/Private/Widgets/SImGuiWidget.h index 1ecc243..4806224 100644 --- a/Source/ImGui/Private/Widgets/SImGuiWidget.h +++ b/Source/ImGui/Private/Widgets/SImGuiWidget.h @@ -94,6 +94,7 @@ private: // Update input state. void UpdateInputState(); + void UpdateTransparentMouseInput(const FGeometry& AllottedGeometry); void HandleWindowFocusLost(); void UpdateCanvasControlMode(const FInputEvent& InputEvent); @@ -125,6 +126,7 @@ private: bool bInputEnabled = false; bool bForegroundWindow = false; bool bHideMouseCursor = true; + bool bTransparentMouseInput = false; TSharedPtr CanvasControlWidget; TWeakPtr PreviousUserFocusedWidget; diff --git a/Source/ImGui/Public/ImGuiModuleProperties.h b/Source/ImGui/Public/ImGuiModuleProperties.h index feb3737..41e26ab 100644 --- a/Source/ImGui/Public/ImGuiModuleProperties.h +++ b/Source/ImGui/Public/ImGuiModuleProperties.h @@ -53,6 +53,15 @@ public: /** Toggle whether gamepad input should be shared with game. */ void ToggleGamepadInputSharing() { SetGamepadInputShared(!IsGamepadInputShared()); } + /** Check whether mouse input is shared with game. */ + bool IsMouseInputShared() const { return bMouseInputShared; } + + /** Set whether mouse input should be shared with game. */ + void SetMouseInputShared(bool bShared) { bMouseInputShared = bShared; } + + /** Toggle whether mouse input should be shared with game. */ + void ToggleMouseInputSharing() { SetMouseInputShared(!IsMouseInputShared()); } + /** Check whether ImGui demo is visible. */ bool ShowDemo() const { return bShowDemo; } @@ -71,6 +80,7 @@ private: bool bKeyboardInputShared = false; bool bGamepadInputShared = false; + bool bMouseInputShared = false; bool bShowDemo = false; };