Using ImGui to draw mouse cursor.

This commit is contained in:
Sebastian 2017-09-21 22:09:03 +01:00
parent 5625365310
commit 9c62196cbb
5 changed files with 77 additions and 13 deletions

View File

@ -25,9 +25,19 @@ FImGuiContextProxy::FImGuiContextProxy()
// Use pre-defined canvas size. // Use pre-defined canvas size.
IO.DisplaySize = { DEFAULT_CANVAS_WIDTH, DEFAULT_CANVAS_HEIGHT }; 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; unsigned char* Pixels;
const bool bIsAltasBuilt = IO.Fonts->TexPixelsAlpha8 != nullptr;
if (bIsAltasBuilt)
{
ImFontAtlas().GetTexDataAsRGBA32(&Pixels, nullptr, nullptr);
}
else
{
IO.Fonts->GetTexDataAsRGBA32(&Pixels, nullptr, nullptr); IO.Fonts->GetTexDataAsRGBA32(&Pixels, nullptr, nullptr);
}
// Initialize key mapping, so context can correctly interpret input state. // Initialize key mapping, so context can correctly interpret input state.
ImGuiInterops::SetUnrealKeyMap(IO); ImGuiInterops::SetUnrealKeyMap(IO);

View File

@ -76,6 +76,13 @@ public:
// @param Position - New mouse position // @param Position - New mouse position
void SetMousePosition(const FVector2D& Position) { MousePosition = 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. // Get Control down state.
bool IsControlDown() const { return bIsControlDown; } bool IsControlDown() const { return bIsControlDown; }
@ -132,6 +139,8 @@ private:
FKeysArray KeysDown; FKeysArray KeysDown;
FKeysIndexRange KeysUpdateRange; FKeysIndexRange KeysUpdateRange;
bool bHasMousePointer = false;
bool bIsControlDown = false; bool bIsControlDown = false;
bool bIsShiftDown = false; bool bIsShiftDown = false;
bool bIsAltDown = false; bool bIsAltDown = false;

View File

@ -146,6 +146,9 @@ namespace ImGuiInterops
static const uint32 LeftAlt = GetKeyIndex(EKeys::LeftAlt); static const uint32 LeftAlt = GetKeyIndex(EKeys::LeftAlt);
static const uint32 RightAlt = GetKeyIndex(EKeys::RightAlt); static const uint32 RightAlt = GetKeyIndex(EKeys::RightAlt);
// Check whether we need to draw cursor.
IO.MouseDrawCursor = InputState.HasMousePointer();
// Copy mouse position. // Copy mouse position.
IO.MousePos.x = InputState.GetMousePosition().X; IO.MousePos.x = InputState.GetMousePosition().X;
IO.MousePos.y = InputState.GetMousePosition().Y; IO.MousePos.y = InputState.GetMousePosition().Y;

View File

@ -20,7 +20,11 @@ constexpr int32 IMGUI_WIDGET_Z_ORDER = 10000;
DEFINE_LOG_CATEGORY_STATIC(LogImGuiWidget, Warning, All); 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")) #define TEXT_BOOL(Val) ((Val) ? TEXT("true") : TEXT("false"))
@ -47,6 +51,9 @@ void SImGuiWidget::Construct(const FArguments& InArgs)
ModuleManager = InArgs._ModuleManager; ModuleManager = InArgs._ModuleManager;
ContextIndex = InArgs._ContextIndex; 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. // Sync visibility with default input enabled state.
SetVisibilityFromInputEnabled(); SetVisibilityFromInputEnabled();
@ -93,6 +100,8 @@ void SImGuiWidget::Tick(const FGeometry& AllottedGeometry, const double InCurren
{ {
Super::Tick(AllottedGeometry, InCurrentTime, InDeltaTime); Super::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);
UpdateMouseStatus();
// Note: Moving that update to console variable sink or callback might seem like a better alternative but input // 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. // setup in this function is better handled here.
UpdateInputEnabled(); UpdateInputEnabled();
@ -177,6 +186,9 @@ FReply SImGuiWidget::OnMouseMove(const FGeometry& MyGeometry, const FPointerEven
InputState.SetMousePosition(MouseEvent.GetScreenSpacePosition() - MyGeometry.AbsolutePosition); InputState.SetMousePosition(MouseEvent.GetScreenSpacePosition() - MyGeometry.AbsolutePosition);
CopyModifierKeys(MouseEvent); 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(); 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 // 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. // 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(); FSlateApplication::Get().ResetToDefaultPointerInputSettings();
return FReply::Handled(); return FReply::Handled();
@ -200,7 +212,7 @@ void SImGuiWidget::OnFocusLost(const FFocusEvent& FocusEvent)
UE_LOG(LogImGuiWidget, VeryVerbose, TEXT("ImGui Widget %d - Focus Lost."), ContextIndex); 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) 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) void SImGuiWidget::CopyModifierKeys(const FPointerEvent& MouseEvent)
{ {
if (InputMode == EInputMode::MouseOnly) if (InputMode == EInputMode::MousePointerOnly)
{ {
CopyModifierKeys(static_cast<const FInputEvent&>(MouseEvent)); CopyModifierKeys(static_cast<const FInputEvent&>(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 = const EInputMode NewInputMode =
bNeedKeyboard ? EInputMode::MouseAndKeyboard : bHasKeyboardFocus ? EInputMode::MouseAndKeyboard :
bNeedMouse ? EInputMode::MouseOnly : bHasMousePointer ? EInputMode::MousePointerOnly :
EInputMode::None; EInputMode::None;
if (InputMode != NewInputMode) if (InputMode != NewInputMode)
@ -354,6 +366,26 @@ void SImGuiWidget::UpdateInputMode(bool bNeedKeyboard, bool bNeedMouse)
} }
InputMode = NewInputMode; 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; bool bDebug = CVars::DebugWidget.GetValueOnGameThread() > 0;
if (bDebug) if (bDebug)
{ {
ImGui::SetNextWindowSize(ImVec2(380, 320), ImGuiSetCond_Once); ImGui::SetNextWindowSize(ImVec2(380, 340), ImGuiSetCond_Once);
if (ImGui::Begin("ImGui Widget Debug", &bDebug)) if (ImGui::Begin("ImGui Widget Debug", &bDebug))
{ {
ImGui::Columns(2, nullptr, false); ImGui::Columns(2, nullptr, false);
@ -459,6 +491,7 @@ void SImGuiWidget::OnDebugDraw()
TwoColumns::Value("Input Enabled", bInputEnabled); TwoColumns::Value("Input Enabled", bInputEnabled);
TwoColumns::Value("Input Mode", TEXT_INPUT_MODE(InputMode)); TwoColumns::Value("Input Mode", TEXT_INPUT_MODE(InputMode));
TwoColumns::Value("Input Has Mouse Pointer", InputState.HasMousePointer());
ImGui::Separator(); ImGui::Separator();

View File

@ -79,7 +79,9 @@ private:
enum class EInputMode : uint8 enum class EInputMode : uint8
{ {
None, None,
MouseOnly, // Mouse pointer only without user focus
MousePointerOnly,
// Full input with user focus
MouseAndKeyboard MouseAndKeyboard
}; };
@ -98,8 +100,14 @@ private:
// Update input enabled state from console variable. // Update input enabled state from console variable.
void UpdateInputEnabled(); void UpdateInputEnabled();
// Determine new input mode based on requirement hints. // Determine new input mode based on hints.
void UpdateInputMode(bool bNeedKeyboard, bool bNeedMouse); 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(); void OnPostImGuiUpdate();
@ -119,6 +127,7 @@ private:
EInputMode InputMode = EInputMode::None; EInputMode InputMode = EInputMode::None;
bool bInputEnabled = false; bool bInputEnabled = false;
bool bReceivedMouseEvent = false;
FImGuiInputState InputState; FImGuiInputState InputState;