Added support for shared mouse input:

- With the mouse sharing enabled, SImGuiWidget can update mouse position without enabling hit-tests. Actual input mode depends whether mouse cursor is hovering any ImGui window or not.
- Added to context proxy interface to read whether that context has mouse hovering any window.
- Added boilerplate code to support mouse sharing settings, properties and commands.
This commit is contained in:
Sebastian 2019-07-08 20:46:28 +01:00
parent 979903722a
commit 10ce6386d4
11 changed files with 109 additions and 20 deletions

View File

@ -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).

View File

@ -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

View File

@ -7,6 +7,7 @@
#include "ImGuiDelegatesContainer.h"
#include "ImGuiImplementation.h"
#include "ImGuiInteroperability.h"
#include "Utilities/Arrays.h"
#include <Runtime/Launch/Resources/Version.h>
@ -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);

View File

@ -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;

View File

@ -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();

View File

@ -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;
};

View File

@ -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)

View File

@ -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;
};

View File

@ -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()));
}
}
}

View File

@ -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<SImGuiCanvasControl> CanvasControlWidget;
TWeakPtr<SWidget> PreviousUserFocusedWidget;

View File

@ -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;
};