Added handling of special input cases:

- Different handling of 'Esc' key. If ImGui has active items then it is used to exit edit mode, otherwise it is passed to editor to cancel session.
- Suppressing keyboard input when console is opened and retaking it when it is closed.
- Fixed issue with losing input when console is closed.
This commit is contained in:
Sebastian 2017-09-16 21:52:14 +01:00
parent c47d911f22
commit 5625365310
4 changed files with 71 additions and 18 deletions

View File

@ -39,6 +39,7 @@ FImGuiContextProxy::FImGuiContextProxy()
FImGuiContextProxy::FImGuiContextProxy(FImGuiContextProxy&& Other) FImGuiContextProxy::FImGuiContextProxy(FImGuiContextProxy&& Other)
: Context(std::move(Other.Context)) : Context(std::move(Other.Context))
, bHasActiveItem(Other.bHasActiveItem)
, DrawEvent(std::move(Other.DrawEvent)) , DrawEvent(std::move(Other.DrawEvent))
, InputState(std::move(Other.InputState)) , InputState(std::move(Other.InputState))
, DrawLists(std::move(Other.DrawLists)) , DrawLists(std::move(Other.DrawLists))
@ -50,6 +51,7 @@ FImGuiContextProxy& FImGuiContextProxy::operator=(FImGuiContextProxy&& Other)
{ {
Context = std::move(Other.Context); Context = std::move(Other.Context);
Other.Context = nullptr; Other.Context = nullptr;
bHasActiveItem = Other.bHasActiveItem;
DrawEvent = std::move(Other.DrawEvent); DrawEvent = std::move(Other.DrawEvent);
InputState = std::move(Other.InputState); InputState = std::move(Other.InputState);
DrawLists = std::move(Other.DrawLists); DrawLists = std::move(Other.DrawLists);
@ -91,6 +93,9 @@ void FImGuiContextProxy::Tick(float DeltaSeconds)
// Begin a new frame and set the context back to a state in which it allows to draw controls. // Begin a new frame and set the context back to a state in which it allows to draw controls.
BeginFrame(DeltaSeconds); BeginFrame(DeltaSeconds);
// Update context information.
bHasActiveItem = ImGui::IsAnyItemActive();
} }
void FImGuiContextProxy::BeginFrame(float DeltaTime) void FImGuiContextProxy::BeginFrame(float DeltaTime)

View File

@ -39,6 +39,8 @@ public:
// Set this context as current ImGui context. // Set this context as current ImGui context.
void SetAsCurrent() { ImGui::SetCurrentContext(Context); } void SetAsCurrent() { ImGui::SetCurrentContext(Context); }
bool HasActiveItem() const { return bHasActiveItem; }
// Delegate called right before ending the frame to allows listeners draw their controls. // Delegate called right before ending the frame to allows listeners draw their controls.
FSimpleMulticastDelegate& OnDraw() { return DrawEvent; } FSimpleMulticastDelegate& OnDraw() { return DrawEvent; }
@ -54,6 +56,8 @@ private:
ImGuiContext* Context = nullptr; ImGuiContext* Context = nullptr;
bool bHasActiveItem = false;
bool bIsFrameStarted = false; bool bIsFrameStarted = false;
FSimpleMulticastDelegate DrawEvent; FSimpleMulticastDelegate DrawEvent;
const FImGuiInputState* InputState = nullptr; const FImGuiInputState* InputState = nullptr;

View File

@ -11,6 +11,8 @@
#include "TextureManager.h" #include "TextureManager.h"
#include "Utilities/ScopeGuards.h" #include "Utilities/ScopeGuards.h"
#include <Engine/Console.h>
// High enough z-order guarantees that ImGui output is rendered on top of the game UI. // High enough z-order guarantees that ImGui output is rendered on top of the game UI.
constexpr int32 IMGUI_WIDGET_Z_ORDER = 10000; constexpr int32 IMGUI_WIDGET_Z_ORDER = 10000;
@ -98,6 +100,11 @@ void SImGuiWidget::Tick(const FGeometry& AllottedGeometry, const double InCurren
FReply SImGuiWidget::OnKeyChar(const FGeometry& MyGeometry, const FCharacterEvent& CharacterEvent) FReply SImGuiWidget::OnKeyChar(const FGeometry& MyGeometry, const FCharacterEvent& CharacterEvent)
{ {
if (IsConsoleOpened())
{
return FReply::Unhandled();
}
InputState.AddCharacter(CharacterEvent.GetCharacter()); InputState.AddCharacter(CharacterEvent.GetCharacter());
return FReply::Handled(); return FReply::Handled();
@ -105,6 +112,11 @@ FReply SImGuiWidget::OnKeyChar(const FGeometry& MyGeometry, const FCharacterEven
FReply SImGuiWidget::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& KeyEvent) FReply SImGuiWidget::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& KeyEvent)
{ {
if (IsConsoleOpened() || IgnoreKeyEvent(KeyEvent))
{
return FReply::Unhandled();
}
InputState.SetKeyDown(ImGuiInterops::GetKeyIndex(KeyEvent), true); InputState.SetKeyDown(ImGuiInterops::GetKeyIndex(KeyEvent), true);
CopyModifierKeys(KeyEvent); CopyModifierKeys(KeyEvent);
@ -119,10 +131,13 @@ FReply SImGuiWidget::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& Key
FReply SImGuiWidget::OnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& KeyEvent) FReply SImGuiWidget::OnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& KeyEvent)
{ {
// Even if we don't send new keystrokes to ImGui, we still handle key up events, to make sure that we clear keys
// pressed before suppressing keyboard input.
InputState.SetKeyDown(ImGuiInterops::GetKeyIndex(KeyEvent), false); InputState.SetKeyDown(ImGuiInterops::GetKeyIndex(KeyEvent), false);
CopyModifierKeys(KeyEvent); CopyModifierKeys(KeyEvent);
return FReply::Handled(); // If console is opened we notify key change but we also let event trough, so it can be handled by console.
return IsConsoleOpened() ? FReply::Unhandled() : FReply::Handled();
} }
FReply SImGuiWidget::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) FReply SImGuiWidget::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
@ -175,6 +190,7 @@ FReply SImGuiWidget::OnFocusReceived(const FGeometry& MyGeometry, const FFocusEv
// 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, true);
FSlateApplication::Get().ResetToDefaultPointerInputSettings();
return FReply::Handled(); return FReply::Handled();
} }
@ -230,6 +246,32 @@ void SImGuiWidget::CopyModifierKeys(const FPointerEvent& MouseEvent)
} }
} }
bool SImGuiWidget::IsConsoleOpened() const
{
return GameViewport->ViewportConsole && GameViewport->ViewportConsole->ConsoleState != NAME_None;
}
bool SImGuiWidget::IgnoreKeyEvent(const FKeyEvent& KeyEvent) const
{
// Ignore console open/close events.
if (KeyEvent.GetKey() == EKeys::Tilde)
{
return true;
}
// Ignore escape keys unless they are needed to cancel operations in ImGui.
if (KeyEvent.GetKey() == EKeys::Escape)
{
auto* ContextProxy = ModuleManager->GetContextManager().GetContextProxy(ContextIndex);
if (!ContextProxy || !ContextProxy->HasActiveItem())
{
return true;
}
}
return false;
}
void SImGuiWidget::ResetInputState() void SImGuiWidget::ResetInputState()
{ {
bInputEnabled = false; bInputEnabled = false;
@ -258,24 +300,9 @@ void SImGuiWidget::UpdateInputEnabled()
SetVisibilityFromInputEnabled(); SetVisibilityFromInputEnabled();
// Setup input to show cursor and to pass keyboard/user focus between viewport and widget. Note that we should if (!bInputEnabled)
// only pass focus if it is inside of the parent viewport, otherwise we would be stealing from other viewports {
// or windows.
auto& Slate = FSlateApplication::Get(); auto& Slate = FSlateApplication::Get();
if (bInputEnabled)
{
const auto& ViewportWidget = GameViewport->GetGameViewportWidget();
if (ViewportWidget->HasKeyboardFocus() || ViewportWidget->HasFocusedDescendants())
{
// Remember where is user focus, so we will have an option to restore it.
PreviousUserFocusedWidget = Slate.GetUserFocusedWidget(Slate.GetUserIndexForKeyboard());
Slate.ResetToDefaultPointerInputSettings();
Slate.SetKeyboardFocus(SharedThis(this));
}
}
else
{
if (Slate.GetKeyboardFocusedWidget().Get() == this) if (Slate.GetKeyboardFocusedWidget().Get() == this)
{ {
Slate.ResetToDefaultPointerInputSettings(); Slate.ResetToDefaultPointerInputSettings();
@ -288,6 +315,19 @@ void SImGuiWidget::UpdateInputEnabled()
UpdateInputMode(false, false); UpdateInputMode(false, false);
} }
} }
// Note: Some widgets, like console, can reset focus to viewport after we already grabbed it. If we detect that
// viewport has a focus while input is enabled we will take it.
if (bInputEnabled && !HasKeyboardFocus() && !IsConsoleOpened())
{
const auto& ViewportWidget = GameViewport->GetGameViewportWidget();
if (ViewportWidget->HasKeyboardFocus() || ViewportWidget->HasFocusedDescendants())
{
auto& Slate = FSlateApplication::Get();
PreviousUserFocusedWidget = Slate.GetUserFocusedWidget(Slate.GetUserIndexForKeyboard());
Slate.SetKeyboardFocus(SharedThis(this));
}
}
} }
void SImGuiWidget::UpdateInputMode(bool bNeedKeyboard, bool bNeedMouse) void SImGuiWidget::UpdateInputMode(bool bNeedKeyboard, bool bNeedMouse)

View File

@ -86,6 +86,10 @@ private:
FORCEINLINE void CopyModifierKeys(const FInputEvent& InputEvent); FORCEINLINE void CopyModifierKeys(const FInputEvent& InputEvent);
FORCEINLINE void CopyModifierKeys(const FPointerEvent& MouseEvent); FORCEINLINE void CopyModifierKeys(const FPointerEvent& MouseEvent);
bool IsConsoleOpened() const;
bool IgnoreKeyEvent(const FKeyEvent& KeyEvent) const;
void ResetInputState(); void ResetInputState();
// Update visibility based on input enabled state. // Update visibility based on input enabled state.