mirror of
https://github.com/kevinporetti/UnrealImGui.git
synced 2025-01-18 16:30:32 +00:00
Fixed issues with passing input focus between viewport and ImGui Widget.
This commit is contained in:
parent
f696393089
commit
c47d911f22
@ -157,9 +157,8 @@ void FImGuiModuleManager::AddWidgetToViewport(UGameViewportClient* GameViewport)
|
|||||||
// Bind widget's input to context for this world.
|
// Bind widget's input to context for this world.
|
||||||
Proxy.SetInputState(&ViewportWidget->GetInputState());
|
Proxy.SetInputState(&ViewportWidget->GetInputState());
|
||||||
|
|
||||||
// High enough z-order guarantees that ImGui output is rendered on top of the game UI.
|
// We should always have one viewport per context index at a time (this will be validated by widget).
|
||||||
constexpr int32 IMGUI_WIDGET_Z_ORDER = 10000;
|
ViewportWidget->AttachToViewport(GameViewport);
|
||||||
GameViewport->AddViewportWidgetContent(SNew(SWeakWidget).PossiblyNullContent(ViewportWidget), IMGUI_WIDGET_Z_ORDER);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void FImGuiModuleManager::AddWidgetToAllViewports()
|
void FImGuiModuleManager::AddWidgetToAllViewports()
|
||||||
|
@ -12,6 +12,10 @@
|
|||||||
#include "Utilities/ScopeGuards.h"
|
#include "Utilities/ScopeGuards.h"
|
||||||
|
|
||||||
|
|
||||||
|
// High enough z-order guarantees that ImGui output is rendered on top of the game UI.
|
||||||
|
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::MouseOnly ? TEXT("MouseOnly") : TEXT("None"))
|
||||||
@ -66,6 +70,23 @@ SImGuiWidget::~SImGuiWidget()
|
|||||||
ModuleManager->OnPostImGuiUpdate().RemoveAll(this);
|
ModuleManager->OnPostImGuiUpdate().RemoveAll(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SImGuiWidget::AttachToViewport(UGameViewportClient* InGameViewport, bool bResetInput)
|
||||||
|
{
|
||||||
|
checkf(InGameViewport, TEXT("Null InGameViewport"));
|
||||||
|
checkf(!GameViewport.IsValid() || GameViewport.Get() == InGameViewport,
|
||||||
|
TEXT("Widget is attached to another game viewport and will be available for reuse only after this session ")
|
||||||
|
TEXT("ends. ContextIndex = %d, CurrentGameViewport = %s, InGameViewport = %s"),
|
||||||
|
ContextIndex, *GameViewport->GetName(), InGameViewport->GetName());
|
||||||
|
|
||||||
|
if (bResetInput)
|
||||||
|
{
|
||||||
|
ResetInputState();
|
||||||
|
}
|
||||||
|
|
||||||
|
GameViewport = InGameViewport;
|
||||||
|
GameViewport->AddViewportWidgetContent(SNew(SWeakWidget).PossiblyNullContent(SharedThis(this)), IMGUI_WIDGET_Z_ORDER);
|
||||||
|
}
|
||||||
|
|
||||||
void SImGuiWidget::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
|
void SImGuiWidget::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
|
||||||
{
|
{
|
||||||
Super::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);
|
Super::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);
|
||||||
@ -209,6 +230,13 @@ void SImGuiWidget::CopyModifierKeys(const FPointerEvent& MouseEvent)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SImGuiWidget::ResetInputState()
|
||||||
|
{
|
||||||
|
bInputEnabled = false;
|
||||||
|
SetVisibilityFromInputEnabled();
|
||||||
|
UpdateInputMode(false, false);
|
||||||
|
}
|
||||||
|
|
||||||
void SImGuiWidget::SetVisibilityFromInputEnabled()
|
void SImGuiWidget::SetVisibilityFromInputEnabled()
|
||||||
{
|
{
|
||||||
// If we don't use input disable hit test to make this widget invisible for cursors hit detection.
|
// If we don't use input disable hit test to make this widget invisible for cursors hit detection.
|
||||||
@ -230,21 +258,33 @@ void SImGuiWidget::UpdateInputEnabled()
|
|||||||
|
|
||||||
SetVisibilityFromInputEnabled();
|
SetVisibilityFromInputEnabled();
|
||||||
|
|
||||||
// Setup input to show cursor and take focus when we use input or clear state and pass focus back to viewport
|
// Setup input to show cursor and to pass keyboard/user focus between viewport and widget. Note that we should
|
||||||
// when we don't.
|
// 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)
|
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.ResetToDefaultPointerInputSettings();
|
||||||
Slate.SetKeyboardFocus(SharedThis(this));
|
Slate.SetKeyboardFocus(SharedThis(this));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (Slate.GetKeyboardFocusedWidget().Get() == this)
|
if (Slate.GetKeyboardFocusedWidget().Get() == this)
|
||||||
{
|
{
|
||||||
Slate.SetUserFocusToGameViewport(Slate.GetUserIndexForKeyboard());
|
Slate.ResetToDefaultPointerInputSettings();
|
||||||
|
Slate.SetUserFocus(Slate.GetUserIndexForKeyboard(),
|
||||||
|
PreviousUserFocusedWidget.IsValid() ? PreviousUserFocusedWidget.Pin() : GameViewport->GetGameViewportWidget());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PreviousUserFocusedWidget.Reset();
|
||||||
|
|
||||||
UpdateInputMode(false, false);
|
UpdateInputMode(false, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -338,6 +378,11 @@ FVector2D SImGuiWidget::ComputeDesiredSize(float) const
|
|||||||
// Controls tweaked for 2-columns layout.
|
// Controls tweaked for 2-columns layout.
|
||||||
namespace TwoColumns
|
namespace TwoColumns
|
||||||
{
|
{
|
||||||
|
static void GroupName(const char* Name)
|
||||||
|
{
|
||||||
|
ImGui::TextColored({ 0.5f, 0.5f, 0.5f, 1.f }, Name); ImGui::NextColumn(); ImGui::NextColumn();
|
||||||
|
}
|
||||||
|
|
||||||
static void Value(const char* Label, int Value)
|
static void Value(const char* Label, int Value)
|
||||||
{
|
{
|
||||||
ImGui::Text("%s:", Label); ImGui::NextColumn();
|
ImGui::Text("%s:", Label); ImGui::NextColumn();
|
||||||
@ -362,12 +407,13 @@ void SImGuiWidget::OnDebugDraw()
|
|||||||
bool bDebug = CVars::DebugWidget.GetValueOnGameThread() > 0;
|
bool bDebug = CVars::DebugWidget.GetValueOnGameThread() > 0;
|
||||||
if (bDebug)
|
if (bDebug)
|
||||||
{
|
{
|
||||||
ImGui::SetNextWindowSize(ImVec2(300, 200), ImGuiSetCond_Once);
|
ImGui::SetNextWindowSize(ImVec2(380, 320), 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);
|
||||||
|
|
||||||
TwoColumns::Value("Context Index", ContextIndex);
|
TwoColumns::Value("Context Index", ContextIndex);
|
||||||
|
TwoColumns::Value("Game Viewport", *GameViewport->GetName());
|
||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
||||||
@ -376,10 +422,33 @@ void SImGuiWidget::OnDebugDraw()
|
|||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
||||||
|
const float GroupIndent = 5.f;
|
||||||
|
|
||||||
|
TwoColumns::GroupName("Widget");
|
||||||
|
ImGui::Indent(GroupIndent);
|
||||||
|
{
|
||||||
TwoColumns::Value("Visibility", *GetVisibility().ToString());
|
TwoColumns::Value("Visibility", *GetVisibility().ToString());
|
||||||
TwoColumns::Value("Is Hovered", IsHovered());
|
TwoColumns::Value("Is Hovered", IsHovered());
|
||||||
TwoColumns::Value("Is Directly Hovered", IsDirectlyHovered());
|
TwoColumns::Value("Is Directly Hovered", IsDirectlyHovered());
|
||||||
TwoColumns::Value("Has Keyboard Input", HasKeyboardFocus());
|
TwoColumns::Value("Has Keyboard Input", HasKeyboardFocus());
|
||||||
|
}
|
||||||
|
ImGui::Unindent(GroupIndent);
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
TwoColumns::GroupName("Viewport Widget");
|
||||||
|
ImGui::Indent(GroupIndent);
|
||||||
|
{
|
||||||
|
const auto& ViewportWidget = GameViewport->GetGameViewportWidget();
|
||||||
|
TwoColumns::Value("Is Hovered", ViewportWidget->IsHovered());
|
||||||
|
TwoColumns::Value("Is Directly Hovered", ViewportWidget->IsDirectlyHovered());
|
||||||
|
TwoColumns::Value("Has Mouse Capture", ViewportWidget->HasMouseCapture());
|
||||||
|
TwoColumns::Value("Has Keyboard Input", ViewportWidget->HasKeyboardFocus());
|
||||||
|
TwoColumns::Value("Has Focused Descendants", ViewportWidget->HasFocusedDescendants());
|
||||||
|
auto Widget = PreviousUserFocusedWidget.Pin();
|
||||||
|
TwoColumns::Value("Previous User Focused", Widget.IsValid() ? *Widget->GetTypeAsString() : TEXT("None"));
|
||||||
|
}
|
||||||
|
ImGui::Unindent(GroupIndent);
|
||||||
|
|
||||||
ImGui::Columns(1);
|
ImGui::Columns(1);
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,16 @@ public:
|
|||||||
// Get input state associated with this widget.
|
// Get input state associated with this widget.
|
||||||
const FImGuiInputState& GetInputState() const { return InputState; }
|
const FImGuiInputState& GetInputState() const { return InputState; }
|
||||||
|
|
||||||
|
// Get the game viewport to which this widget is attached.
|
||||||
|
const TWeakObjectPtr<UGameViewportClient>& GetGameViewport() const { return GameViewport; }
|
||||||
|
|
||||||
|
// Attach this widget to a target game viewport.
|
||||||
|
// Widget can be attached to only one viewport at a time but can be reused after its last viewport becomes invalid
|
||||||
|
// at the end of a session. Widgets are weakly attached, so once destroyed they are automatically removed.
|
||||||
|
// @param InGameViewport - Target game viewport
|
||||||
|
// @param bResetInput - If true (default), input will be reset back to a default state
|
||||||
|
void AttachToViewport(UGameViewportClient* InGameViewport, bool bResetInput = true);
|
||||||
|
|
||||||
//----------------------------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------------------------
|
||||||
// SWidget overrides
|
// SWidget overrides
|
||||||
//----------------------------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------------------------
|
||||||
@ -76,6 +86,8 @@ 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);
|
||||||
|
|
||||||
|
void ResetInputState();
|
||||||
|
|
||||||
// Update visibility based on input enabled state.
|
// Update visibility based on input enabled state.
|
||||||
void SetVisibilityFromInputEnabled();
|
void SetVisibilityFromInputEnabled();
|
||||||
|
|
||||||
@ -94,6 +106,7 @@ private:
|
|||||||
void OnDebugDraw();
|
void OnDebugDraw();
|
||||||
|
|
||||||
FImGuiModuleManager* ModuleManager = nullptr;
|
FImGuiModuleManager* ModuleManager = nullptr;
|
||||||
|
TWeakObjectPtr<UGameViewportClient> GameViewport;
|
||||||
|
|
||||||
mutable TArray<FSlateVertex> VertexBuffer;
|
mutable TArray<FSlateVertex> VertexBuffer;
|
||||||
mutable TArray<SlateIndex> IndexBuffer;
|
mutable TArray<SlateIndex> IndexBuffer;
|
||||||
@ -104,4 +117,6 @@ private:
|
|||||||
bool bInputEnabled = false;
|
bool bInputEnabled = false;
|
||||||
|
|
||||||
FImGuiInputState InputState;
|
FImGuiInputState InputState;
|
||||||
|
|
||||||
|
TWeakPtr<SWidget> PreviousUserFocusedWidget;
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user