mirror of
https://github.com/kevinporetti/UnrealImGui.git
synced 2025-01-18 08:20:32 +00:00
Added Multi-PIE support:
- Added ImGui Context Manager to create and manage ImGui Context Proxies. - Changed ImGui Context Proxy to dynamically create context and allow pairing with input state. - Changed ImGui Module Manager to create one widget per context. - Changed ImGui Widget to work in different input modes. - Changed ImGui Input State to allow partial reset (only mouse or keyboard).
This commit is contained in:
parent
53387cf88b
commit
77bb73dbce
71
Source/ImGui/Private/ImGuiContextManager.cpp
Normal file
71
Source/ImGui/Private/ImGuiContextManager.cpp
Normal file
@ -0,0 +1,71 @@
|
||||
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
|
||||
|
||||
#include "ImGuiPrivatePCH.h"
|
||||
|
||||
#include "ImGuiContextManager.h"
|
||||
|
||||
|
||||
FImGuiContextProxy& FImGuiContextManager::GetWorldContextProxy(UWorld& World)
|
||||
{
|
||||
const int32 Index = Utilities::GetWorldContextIndex(World);
|
||||
checkf(Index != Utilities::INVALID_CONTEXT_INDEX, TEXT("Couldn't resolve context index for world %s: WorldType = %d"),
|
||||
*World.GetName(), World.WorldType);
|
||||
|
||||
#if WITH_EDITOR
|
||||
// Make sure that PIE worlds don't try to use editor context.
|
||||
checkf(!GEngine->IsEditor() || Index != Utilities::DEFAULT_CONTEXT_INDEX, TEXT("Index for world %s "
|
||||
"was resolved to the default context index %d, which in editor is reserved for editor context. PIE worlds "
|
||||
"should use values that start from 1. WorldType = %d, NetMode = %d"), *World.GetName(),
|
||||
Utilities::DEFAULT_CONTEXT_INDEX, World.WorldType, World.GetNetMode());
|
||||
#endif // WITH_EDITOR
|
||||
|
||||
FContextData& Data = FindOrAddContextData(Index);
|
||||
|
||||
// Track worlds to make sure that different worlds don't try to use the same context in the same time.
|
||||
if (!Data.World.IsValid())
|
||||
{
|
||||
Data.World = &World;
|
||||
}
|
||||
else
|
||||
{
|
||||
checkf(Data.World == &World, TEXT("Two different worlds, %s and %s, resolved to the same world context index %d."),
|
||||
*Data.World->GetName(), *World.GetName(), Index);
|
||||
}
|
||||
|
||||
return Data.ContextProxy;
|
||||
}
|
||||
|
||||
void FImGuiContextManager::Tick(float DeltaSeconds)
|
||||
{
|
||||
FContextData* Data = Contexts.Find(1);
|
||||
if (!Data || !Data->World.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& Entry : Contexts)
|
||||
{
|
||||
FImGuiContextProxy& ContextProxy = Entry.Value.ContextProxy;
|
||||
|
||||
ContextProxy.SetAsCurrent();
|
||||
|
||||
// Tick context proxy to end the old frame and starts a new one.
|
||||
ContextProxy.Tick(DeltaSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
FImGuiContextManager::FContextData& FImGuiContextManager::FindOrAddContextData(int32 Index)
|
||||
{
|
||||
FContextData* Data = Contexts.Find(Index);
|
||||
|
||||
if (!Data)
|
||||
{
|
||||
Data = &Contexts.Add(Index);
|
||||
if (!Data->ContextProxy.OnDraw().IsBoundToObject(&ImGuiDemo))
|
||||
{
|
||||
Data->ContextProxy.OnDraw().AddRaw(&ImGuiDemo, &FImGuiDemo::DrawControls);
|
||||
}
|
||||
}
|
||||
|
||||
return *Data;
|
||||
}
|
57
Source/ImGui/Private/ImGuiContextManager.h
Normal file
57
Source/ImGui/Private/ImGuiContextManager.h
Normal file
@ -0,0 +1,57 @@
|
||||
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ImGuiContextProxy.h"
|
||||
#include "ImGuiDemo.h"
|
||||
#include "Utilities/WorldContextIndex.h"
|
||||
|
||||
|
||||
// Manages ImGui context proxies.
|
||||
class FImGuiContextManager
|
||||
{
|
||||
public:
|
||||
|
||||
FImGuiContextManager() = default;
|
||||
|
||||
FImGuiContextManager(const FImGuiContextManager&) = delete;
|
||||
FImGuiContextManager& operator=(const FImGuiContextManager&) = delete;
|
||||
|
||||
FImGuiContextManager(FImGuiContextManager&&) = delete;
|
||||
FImGuiContextManager& operator=(FImGuiContextManager&&) = delete;
|
||||
|
||||
// Get or create default ImGui context proxy. In editor this is the editor context proxy and in standalone game
|
||||
// context proxy for the only world and the same value as returned from GetWorldContextProxy.
|
||||
//
|
||||
// If proxy doesn't exist then it will be created and initialized.
|
||||
FImGuiContextProxy& GetDefaultContextProxy() { return FindOrAddContextData(Utilities::DEFAULT_CONTEXT_INDEX).ContextProxy; }
|
||||
|
||||
// Get or create ImGui context proxy for given world.
|
||||
//
|
||||
// If proxy doesn't yet exist then it will be created and initialized. If proxy already exists then associated
|
||||
// world data will be updated.
|
||||
FImGuiContextProxy& GetWorldContextProxy(UWorld& World);
|
||||
|
||||
// Get context proxy by index, or null if context with that index doesn't exist.
|
||||
FORCEINLINE FImGuiContextProxy* GetContextProxy(int32 ContextIndex)
|
||||
{
|
||||
FContextData* Data = Contexts.Find(ContextIndex);
|
||||
return Data ? &(Data->ContextProxy) : nullptr;
|
||||
}
|
||||
|
||||
void Tick(float DeltaSeconds);
|
||||
|
||||
private:
|
||||
|
||||
struct FContextData
|
||||
{
|
||||
TWeakObjectPtr<UWorld> World;
|
||||
FImGuiContextProxy ContextProxy;
|
||||
};
|
||||
|
||||
FContextData& FindOrAddContextData(int32 Index);
|
||||
|
||||
TMap<int32, FContextData> Contexts;
|
||||
|
||||
FImGuiDemo ImGuiDemo;
|
||||
};
|
@ -4,17 +4,26 @@
|
||||
|
||||
#include "ImGuiContextProxy.h"
|
||||
|
||||
#include "ImGuiImplementation.h"
|
||||
#include "ImGuiInteroperability.h"
|
||||
|
||||
|
||||
static constexpr float DEFAULT_CANVAS_WIDTH = 3840.f;
|
||||
static constexpr float DEFAULT_CANVAS_HEIGHT = 2160.f;
|
||||
|
||||
FImGuiContextProxy::FImGuiContextProxy()
|
||||
{
|
||||
// Create context.
|
||||
Context = ImGui::CreateContext();
|
||||
|
||||
// Set this context in ImGui for initialization (any allocations will be tracked in this context).
|
||||
SetAsCurrent();
|
||||
|
||||
// Start initialization.
|
||||
ImGuiIO& IO = ImGui::GetIO();
|
||||
|
||||
// Use pre-defined canvas size.
|
||||
IO.DisplaySize.x = DEFAULT_CANVAS_WIDTH;
|
||||
IO.DisplaySize.y = DEFAULT_CANVAS_HEIGHT;
|
||||
IO.DisplaySize = { DEFAULT_CANVAS_WIDTH, DEFAULT_CANVAS_HEIGHT };
|
||||
|
||||
// Load texture atlas.
|
||||
unsigned char* Pixels;
|
||||
@ -28,12 +37,44 @@ FImGuiContextProxy::FImGuiContextProxy()
|
||||
BeginFrame();
|
||||
}
|
||||
|
||||
FImGuiContextProxy::~FImGuiContextProxy()
|
||||
FImGuiContextProxy::FImGuiContextProxy(FImGuiContextProxy&& Other)
|
||||
: Context(std::move(Other.Context))
|
||||
, DrawEvent(std::move(Other.DrawEvent))
|
||||
, InputState(std::move(Other.InputState))
|
||||
, DrawLists(std::move(Other.DrawLists))
|
||||
{
|
||||
ImGui::Shutdown();
|
||||
Other.Context = nullptr;
|
||||
}
|
||||
|
||||
void FImGuiContextProxy::Tick(float DeltaSeconds, const FImGuiInputState* InputState)
|
||||
FImGuiContextProxy& FImGuiContextProxy::operator=(FImGuiContextProxy&& Other)
|
||||
{
|
||||
Context = std::move(Other.Context);
|
||||
Other.Context = nullptr;
|
||||
DrawEvent = std::move(Other.DrawEvent);
|
||||
InputState = std::move(Other.InputState);
|
||||
DrawLists = std::move(Other.DrawLists);
|
||||
return *this;
|
||||
}
|
||||
|
||||
FImGuiContextProxy::~FImGuiContextProxy()
|
||||
{
|
||||
if (Context)
|
||||
{
|
||||
// Set this context in ImGui for de-initialization (any de-allocations will be tracked in this context).
|
||||
SetAsCurrent();
|
||||
|
||||
// Shutdown to save data etc.
|
||||
ImGui::Shutdown();
|
||||
|
||||
// Destroy the context.
|
||||
ImGui::DestroyContext(Context);
|
||||
|
||||
// Set default context in ImGui to keep global context pointer valid.
|
||||
ImGui::SetCurrentContext(&GetDefaultContext());
|
||||
}
|
||||
}
|
||||
|
||||
void FImGuiContextProxy::Tick(float DeltaSeconds)
|
||||
{
|
||||
if (bIsFrameStarted)
|
||||
{
|
||||
@ -49,10 +90,10 @@ void FImGuiContextProxy::Tick(float DeltaSeconds, const FImGuiInputState* InputS
|
||||
}
|
||||
|
||||
// Begin a new frame and set the context back to a state in which it allows to draw controls.
|
||||
BeginFrame(DeltaSeconds, InputState);
|
||||
BeginFrame(DeltaSeconds);
|
||||
}
|
||||
|
||||
void FImGuiContextProxy::BeginFrame(float DeltaTime, const FImGuiInputState* InputState)
|
||||
void FImGuiContextProxy::BeginFrame(float DeltaTime)
|
||||
{
|
||||
if (!bIsFrameStarted)
|
||||
{
|
||||
|
@ -10,8 +10,7 @@
|
||||
class FImGuiInputState;
|
||||
|
||||
// Represents a single ImGui context. All the context updates should be done through this proxy. During update it
|
||||
// broadcasts draw events to allow listeners draw their controls. After update it stores produced draw data.
|
||||
// TODO: Add dynamically created contexts, so we can have a better support for multi-PIE.
|
||||
// broadcasts draw events to allow listeners draw their controls. After update it stores draw data.
|
||||
class FImGuiContextProxy
|
||||
{
|
||||
public:
|
||||
@ -22,30 +21,42 @@ public:
|
||||
FImGuiContextProxy(const FImGuiContextProxy&) = delete;
|
||||
FImGuiContextProxy& operator=(const FImGuiContextProxy&) = delete;
|
||||
|
||||
FImGuiContextProxy(FImGuiContextProxy&&) = delete;
|
||||
FImGuiContextProxy& operator=(FImGuiContextProxy&&) = delete;
|
||||
FImGuiContextProxy(FImGuiContextProxy&& Other);
|
||||
FImGuiContextProxy& operator=(FImGuiContextProxy&& Other);
|
||||
|
||||
// Get draw data from the last frame.
|
||||
const TArray<FImGuiDrawList>& GetDrawData() const { return DrawLists; }
|
||||
|
||||
// Get input state used by this context.
|
||||
const FImGuiInputState* GetInputState() const { return InputState; }
|
||||
|
||||
// Set input state to be used by this context.
|
||||
void SetInputState(const FImGuiInputState* SourceInputState) { InputState = SourceInputState; }
|
||||
|
||||
// Is this context the current ImGui context.
|
||||
bool IsCurrentContext() const { return ImGui::GetCurrentContext() == Context; }
|
||||
|
||||
// Set this context as current ImGui context.
|
||||
void SetAsCurrent() { ImGui::SetCurrentContext(Context); }
|
||||
|
||||
// Delegate called right before ending the frame to allows listeners draw their controls.
|
||||
FSimpleMulticastDelegate& OnDraw() { return DrawEvent; }
|
||||
|
||||
// Tick to advance context to the next frame.
|
||||
// @param DeltaSeconds - Time delta in seconds (will be passed to ImGui)
|
||||
// @param InputState - Input state for ImGui IO or null if there is no input for this context
|
||||
void Tick(float DeltaSeconds, const FImGuiInputState* InputState = nullptr);
|
||||
void Tick(float DeltaSeconds);
|
||||
|
||||
private:
|
||||
|
||||
void BeginFrame(float DeltaTime = 1.f / 60.f, const FImGuiInputState* InputState = nullptr);
|
||||
void BeginFrame(float DeltaTime = 1.f / 60.f);
|
||||
void EndFrame();
|
||||
|
||||
void UpdateDrawData(ImDrawData* DrawData);
|
||||
|
||||
TArray<FImGuiDrawList> DrawLists;
|
||||
|
||||
FSimpleMulticastDelegate DrawEvent;
|
||||
ImGuiContext* Context = nullptr;
|
||||
|
||||
bool bIsFrameStarted = false;
|
||||
FSimpleMulticastDelegate DrawEvent;
|
||||
const FImGuiInputState* InputState = nullptr;
|
||||
|
||||
TArray<FImGuiDrawList> DrawLists;
|
||||
};
|
||||
|
@ -20,3 +20,11 @@
|
||||
#if PLATFORM_WINDOWS
|
||||
#include <HideWindowsPlatformTypes.h>
|
||||
#endif // PLATFORM_WINDOWS
|
||||
|
||||
|
||||
// This is exposing ImGui default context for the whole module.
|
||||
// This is assuming that we don't define custom GImGui and therefore have GImDefaultContext defined in imgui.cpp.
|
||||
ImGuiContext& GetDefaultContext()
|
||||
{
|
||||
return GImDefaultContext;
|
||||
}
|
||||
|
9
Source/ImGui/Private/ImGuiImplementation.h
Normal file
9
Source/ImGui/Private/ImGuiImplementation.h
Normal file
@ -0,0 +1,9 @@
|
||||
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
|
||||
// Get default context created by ImGui framework.
|
||||
ImGuiContext& GetDefaultContext();
|
@ -45,15 +45,24 @@ void FImGuiInputState::SetMouseDown(uint32 MouseIndex, bool bIsDown)
|
||||
}
|
||||
}
|
||||
|
||||
void FImGuiInputState::ResetState()
|
||||
void FImGuiInputState::Reset(bool bKeyboard, bool bMouse)
|
||||
{
|
||||
ClearCharacters();
|
||||
ClearKeys();
|
||||
if (bKeyboard)
|
||||
{
|
||||
ClearCharacters();
|
||||
ClearKeys();
|
||||
}
|
||||
|
||||
ClearMouseButtons();
|
||||
ClearMouseAnalogue();
|
||||
if (bMouse)
|
||||
{
|
||||
ClearMouseButtons();
|
||||
ClearMouseAnalogue();
|
||||
}
|
||||
|
||||
ClearModifierKeys();
|
||||
if (bKeyboard && bMouse)
|
||||
{
|
||||
ClearModifierKeys();
|
||||
}
|
||||
}
|
||||
|
||||
void FImGuiInputState::ClearUpdateState()
|
||||
|
@ -98,7 +98,13 @@ public:
|
||||
void SetAltDown(bool bIsDown) { bIsAltDown = bIsDown; }
|
||||
|
||||
// Reset state and mark as dirty.
|
||||
void ResetState();
|
||||
void ResetState() { Reset(true, true); }
|
||||
|
||||
// Reset keyboard state and mark as dirty.
|
||||
void ResetKeyboardState() { Reset(true, false); }
|
||||
|
||||
// Reset mouse state and mark as dirty.
|
||||
void ResetMouseState() { Reset(false, true); }
|
||||
|
||||
// Clear part of the state that is meant to be updated in every frame like: accumulators, buffers and information
|
||||
// about dirty parts of keys or mouse buttons arrays.
|
||||
@ -106,6 +112,8 @@ public:
|
||||
|
||||
private:
|
||||
|
||||
void Reset(bool bKeyboard, bool bMouse);
|
||||
|
||||
void ClearCharacters();
|
||||
void ClearKeys();
|
||||
void ClearMouseButtons();
|
||||
|
@ -11,8 +11,8 @@
|
||||
|
||||
FImGuiModuleManager::FImGuiModuleManager()
|
||||
{
|
||||
// Bind ImGui demo to proxy, so it can draw controls in its context.
|
||||
ContextProxy.OnDraw().AddRaw(&ImGuiDemo, &FImGuiDemo::DrawControls);
|
||||
// Make sure that default ImGui context is setup.
|
||||
ContextManager.GetDefaultContextProxy();
|
||||
|
||||
// Typically we will use viewport created events to add widget to new game viewports.
|
||||
ViewportCreatedHandle = UGameViewportClient::OnViewportCreated().AddRaw(this, &FImGuiModuleManager::OnViewportCreated);
|
||||
@ -117,8 +117,8 @@ void FImGuiModuleManager::Tick(float DeltaSeconds)
|
||||
{
|
||||
if (IsInUpdateThread())
|
||||
{
|
||||
// Update context proxy to advance to next frame.
|
||||
ContextProxy.Tick(DeltaSeconds, ViewportWidget.IsValid() ? &ViewportWidget->GetInputState() : nullptr);
|
||||
// Update context manager to advance all ImGui contexts to the next frame.
|
||||
ContextManager.Tick(DeltaSeconds);
|
||||
|
||||
// Inform that we finished updating ImGui, so other subsystems can react.
|
||||
PostImGuiUpdateEvent.Broadcast();
|
||||
@ -141,15 +141,24 @@ void FImGuiModuleManager::AddWidgetToViewport(UGameViewportClient* GameViewport)
|
||||
checkf(GameViewport, TEXT("Null game viewport."));
|
||||
checkf(FSlateApplication::IsInitialized(), TEXT("Slate should be initialized before we can add widget to game viewports."));
|
||||
|
||||
const int32 ContextIndex = Utilities::GetWorldContextIndex(GameViewport);
|
||||
|
||||
// This makes sure that context for this world is created.
|
||||
auto& Proxy = ContextManager.GetWorldContextProxy(*GameViewport->GetWorld());
|
||||
|
||||
// Get widget for this world.
|
||||
auto& ViewportWidget = ViewportWidgets.FindOrAdd(ContextIndex);
|
||||
if (!ViewportWidget.IsValid())
|
||||
{
|
||||
SAssignNew(ViewportWidget, SImGuiWidget).ModuleManager(this);
|
||||
checkf(ViewportWidget.IsValid(), TEXT("Failed to create SImGuiWidget."));
|
||||
SAssignNew(ViewportWidget, SImGuiWidget).ModuleManager(this).ContextIndex(ContextIndex);
|
||||
check(ViewportWidget.IsValid());
|
||||
}
|
||||
|
||||
// Bind widget's input to context for this world.
|
||||
Proxy.SetInputState(&ViewportWidget->GetInputState());
|
||||
|
||||
// High enough z-order guarantees that ImGui output is rendered on top of the game UI.
|
||||
constexpr int32 IMGUI_WIDGET_Z_ORDER = 10000;
|
||||
|
||||
GameViewport->AddViewportWidgetContent(SNew(SWeakWidget).PossiblyNullContent(ViewportWidget), IMGUI_WIDGET_Z_ORDER);
|
||||
}
|
||||
|
||||
|
@ -2,8 +2,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ImGuiContextProxy.h"
|
||||
#include "ImGuiDemo.h"
|
||||
#include "ImGuiContextManager.h"
|
||||
#include "SImGuiWidget.h"
|
||||
#include "TextureManager.h"
|
||||
|
||||
@ -16,8 +15,8 @@ class FImGuiModuleManager
|
||||
|
||||
public:
|
||||
|
||||
// Get ImGui context proxy.
|
||||
FImGuiContextProxy& GetContextProxy() { return ContextProxy; }
|
||||
// Get ImGui contexts manager.
|
||||
FImGuiContextManager& GetContextManager() { return ContextManager; }
|
||||
|
||||
// Get texture resources manager.
|
||||
FTextureManager& GetTextureManager() { return TextureManager; }
|
||||
@ -56,18 +55,14 @@ private:
|
||||
// Event that we call after ImGui is updated.
|
||||
FSimpleMulticastDelegate PostImGuiUpdateEvent;
|
||||
|
||||
// Proxy controlling ImGui context.
|
||||
FImGuiContextProxy ContextProxy;
|
||||
|
||||
// ImWidget that draws ImGui demo.
|
||||
FImGuiDemo ImGuiDemo;
|
||||
// Manager for ImGui contexts.
|
||||
FImGuiContextManager ContextManager;
|
||||
|
||||
// Manager for textures resources.
|
||||
FTextureManager TextureManager;
|
||||
|
||||
// Slate widget that we attach to created game viewports (widget without per-viewport state can be attached to
|
||||
// multiple viewports).
|
||||
TSharedPtr<SImGuiWidget> ViewportWidget;
|
||||
// Slate widgets that we attach to game viewports.
|
||||
TMap<int32, TSharedPtr<SImGuiWidget>> ViewportWidgets;
|
||||
|
||||
FDelegateHandle TickDelegateHandle;
|
||||
FDelegateHandle ViewportCreatedHandle;
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
#include "SImGuiWidget.h"
|
||||
|
||||
#include "ImGuiContextManager.h"
|
||||
#include "ImGuiContextProxy.h"
|
||||
#include "ImGuiInteroperability.h"
|
||||
#include "ImGuiModuleManager.h"
|
||||
@ -16,6 +17,7 @@ void SImGuiWidget::Construct(const FArguments& InArgs)
|
||||
{
|
||||
checkf(InArgs._ModuleManager, TEXT("Null Module Manager argument"));
|
||||
ModuleManager = InArgs._ModuleManager;
|
||||
ContextIndex = InArgs._ContextIndex;
|
||||
|
||||
ModuleManager->OnPostImGuiUpdate().AddRaw(this, &SImGuiWidget::OnPostImGuiUpdate);
|
||||
}
|
||||
@ -95,6 +97,48 @@ FReply SImGuiWidget::OnMouseMove(const FGeometry& MyGeometry, const FPointerEven
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
FReply SImGuiWidget::OnFocusReceived(const FGeometry& MyGeometry, const FFocusEvent& FocusEvent)
|
||||
{
|
||||
Super::OnFocusReceived(MyGeometry, FocusEvent);
|
||||
|
||||
// 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.
|
||||
UpdateInputMode(true, true);
|
||||
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
void SImGuiWidget::OnFocusLost(const FFocusEvent& FocusEvent)
|
||||
{
|
||||
Super::OnFocusLost(FocusEvent);
|
||||
|
||||
UpdateInputMode(false, IsHovered());
|
||||
}
|
||||
|
||||
void SImGuiWidget::OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
|
||||
{
|
||||
Super::OnMouseEnter(MyGeometry, MouseEvent);
|
||||
|
||||
// If mouse enters while input is active then we need to update mouse buttons because there is a chance that we
|
||||
// missed some events.
|
||||
if (InputMode != EInputMode::None)
|
||||
{
|
||||
for (const FKey& Button : { EKeys::LeftMouseButton, EKeys::MiddleMouseButton, EKeys::RightMouseButton, EKeys::ThumbMouseButton, EKeys::ThumbMouseButton2 })
|
||||
{
|
||||
InputState.SetMouseDown(ImGuiInterops::GetMouseIndex(Button), MouseEvent.IsMouseButtonDown(Button));
|
||||
}
|
||||
}
|
||||
|
||||
UpdateInputMode(HasKeyboardFocus(), true);
|
||||
}
|
||||
|
||||
void SImGuiWidget::OnMouseLeave(const FPointerEvent& MouseEvent)
|
||||
{
|
||||
Super::OnMouseLeave(MouseEvent);
|
||||
|
||||
UpdateInputMode(HasKeyboardFocus(), false);
|
||||
}
|
||||
|
||||
void SImGuiWidget::CopyModifierKeys(const FInputEvent& InputEvent)
|
||||
{
|
||||
InputState.SetControlDown(InputEvent.IsControlDown());
|
||||
@ -102,47 +146,85 @@ void SImGuiWidget::CopyModifierKeys(const FInputEvent& InputEvent)
|
||||
InputState.SetAltDown(InputEvent.IsAltDown());
|
||||
}
|
||||
|
||||
void SImGuiWidget::CopyModifierKeys(const FPointerEvent& MouseEvent)
|
||||
{
|
||||
if (InputMode == EInputMode::MouseOnly)
|
||||
{
|
||||
CopyModifierKeys(static_cast<const FInputEvent&>(MouseEvent));
|
||||
}
|
||||
}
|
||||
|
||||
void SImGuiWidget::UpdateInputMode(bool bNeedKeyboard, bool bNeedMouse)
|
||||
{
|
||||
const EInputMode NewInputMode =
|
||||
bNeedKeyboard ? EInputMode::MouseAndKeyboard :
|
||||
bNeedMouse ? EInputMode::MouseOnly :
|
||||
EInputMode::None;
|
||||
|
||||
if (InputMode != NewInputMode)
|
||||
{
|
||||
// We need to reset input components if we are either fully shutting down or we are downgrading from full to
|
||||
// mouse-only input mode.
|
||||
if (NewInputMode == EInputMode::None)
|
||||
{
|
||||
InputState.ResetState();
|
||||
}
|
||||
else if (InputMode == EInputMode::MouseAndKeyboard)
|
||||
{
|
||||
InputState.ResetKeyboardState();
|
||||
}
|
||||
|
||||
InputMode = NewInputMode;
|
||||
}
|
||||
}
|
||||
|
||||
void SImGuiWidget::OnPostImGuiUpdate()
|
||||
{
|
||||
InputState.ClearUpdateState();
|
||||
if (InputMode != EInputMode::None)
|
||||
{
|
||||
InputState.ClearUpdateState();
|
||||
}
|
||||
}
|
||||
|
||||
int32 SImGuiWidget::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect,
|
||||
FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& WidgetStyle, bool bParentEnabled) const
|
||||
{
|
||||
// Calculate offset that will transform vertex positions to screen space - rounded to avoid half pixel offsets.
|
||||
const FVector2D VertexPositionOffset{ FMath::RoundToFloat(MyClippingRect.Left), FMath::RoundToFloat(MyClippingRect.Top) };
|
||||
|
||||
// Convert clipping rectangle to format required by Slate vertex.
|
||||
const FSlateRotatedRect VertexClippingRect{ MyClippingRect };
|
||||
|
||||
for (const auto& DrawList : ModuleManager->GetContextProxy().GetDrawData())
|
||||
if (FImGuiContextProxy* ContextProxy = ModuleManager->GetContextManager().GetContextProxy(ContextIndex))
|
||||
{
|
||||
DrawList.CopyVertexData(VertexBuffer, VertexPositionOffset, VertexClippingRect);
|
||||
// Calculate offset that will transform vertex positions to screen space - rounded to avoid half pixel offsets.
|
||||
const FVector2D VertexPositionOffset{ FMath::RoundToFloat(MyClippingRect.Left), FMath::RoundToFloat(MyClippingRect.Top) };
|
||||
|
||||
// Get access to the Slate scissor rectangle defined in Slate Core API, so we can customize elements drawing.
|
||||
extern SLATECORE_API TOptional<FShortRect> GSlateScissorRect;
|
||||
// Convert clipping rectangle to format required by Slate vertex.
|
||||
const FSlateRotatedRect VertexClippingRect{ MyClippingRect };
|
||||
|
||||
auto GSlateScissorRectSaver = ScopeGuards::MakeStateSaver(GSlateScissorRect);
|
||||
|
||||
int IndexBufferOffset = 0;
|
||||
for (int CommandNb = 0; CommandNb < DrawList.NumCommands(); CommandNb++)
|
||||
for (const auto& DrawList : ContextProxy->GetDrawData())
|
||||
{
|
||||
const auto& DrawCommand = DrawList.GetCommand(CommandNb);
|
||||
DrawList.CopyVertexData(VertexBuffer, VertexPositionOffset, VertexClippingRect);
|
||||
|
||||
DrawList.CopyIndexData(IndexBuffer, IndexBufferOffset, DrawCommand.NumElements);
|
||||
// Get access to the Slate scissor rectangle defined in Slate Core API, so we can customize elements drawing.
|
||||
extern SLATECORE_API TOptional<FShortRect> GSlateScissorRect;
|
||||
|
||||
// Advance offset by number of copied elements to position it for the next command.
|
||||
IndexBufferOffset += DrawCommand.NumElements;
|
||||
auto GSlateScissorRectSaver = ScopeGuards::MakeStateSaver(GSlateScissorRect);
|
||||
|
||||
// Get texture resource handle for this draw command (null index will be also mapped to a valid texture).
|
||||
const FSlateResourceHandle& Handle = ModuleManager->GetTextureManager().GetTextureHandle(DrawCommand.TextureId);
|
||||
int IndexBufferOffset = 0;
|
||||
for (int CommandNb = 0; CommandNb < DrawList.NumCommands(); CommandNb++)
|
||||
{
|
||||
const auto& DrawCommand = DrawList.GetCommand(CommandNb);
|
||||
|
||||
// Transform clipping rectangle to screen space and set in Slate, to apply it to elements that we draw.
|
||||
GSlateScissorRect = FShortRect{ DrawCommand.ClippingRect.OffsetBy(MyClippingRect.GetTopLeft()).IntersectionWith(MyClippingRect) };
|
||||
DrawList.CopyIndexData(IndexBuffer, IndexBufferOffset, DrawCommand.NumElements);
|
||||
|
||||
// Add elements to the list.
|
||||
FSlateDrawElement::MakeCustomVerts(OutDrawElements, LayerId, Handle, VertexBuffer, IndexBuffer, nullptr, 0, 0);
|
||||
// Advance offset by number of copied elements to position it for the next command.
|
||||
IndexBufferOffset += DrawCommand.NumElements;
|
||||
|
||||
// Get texture resource handle for this draw command (null index will be also mapped to a valid texture).
|
||||
const FSlateResourceHandle& Handle = ModuleManager->GetTextureManager().GetTextureHandle(DrawCommand.TextureId);
|
||||
|
||||
// Transform clipping rectangle to screen space and set in Slate, to apply it to elements that we draw.
|
||||
GSlateScissorRect = FShortRect{ DrawCommand.ClippingRect.OffsetBy(MyClippingRect.GetTopLeft()).IntersectionWith(MyClippingRect) };
|
||||
|
||||
// Add elements to the list.
|
||||
FSlateDrawElement::MakeCustomVerts(OutDrawElements, LayerId, Handle, VertexBuffer, IndexBuffer, nullptr, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,12 +19,17 @@ public:
|
||||
SLATE_BEGIN_ARGS(SImGuiWidget)
|
||||
{}
|
||||
SLATE_ARGUMENT(FImGuiModuleManager*, ModuleManager)
|
||||
SLATE_ARGUMENT(int32, ContextIndex)
|
||||
SLATE_END_ARGS()
|
||||
|
||||
void Construct(const FArguments& InArgs);
|
||||
|
||||
~SImGuiWidget();
|
||||
|
||||
// Get index of the context that this widget is targeting.
|
||||
int32 GetContextIndex() const { return ContextIndex; }
|
||||
|
||||
// Get input state associated with this widget.
|
||||
const FImGuiInputState& GetInputState() const { return InputState; }
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
@ -49,9 +54,28 @@ public:
|
||||
|
||||
virtual FReply OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
|
||||
|
||||
virtual FReply OnFocusReceived(const FGeometry& MyGeometry, const FFocusEvent& FocusEvent) override;
|
||||
|
||||
virtual void OnFocusLost(const FFocusEvent& FocusEvent) override;
|
||||
|
||||
virtual void OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
|
||||
|
||||
virtual void OnMouseLeave(const FPointerEvent& MouseEvent) override;
|
||||
|
||||
private:
|
||||
|
||||
enum class EInputMode
|
||||
{
|
||||
None,
|
||||
MouseOnly,
|
||||
MouseAndKeyboard
|
||||
};
|
||||
|
||||
FORCEINLINE void CopyModifierKeys(const FInputEvent& InputEvent);
|
||||
FORCEINLINE void CopyModifierKeys(const FPointerEvent& MouseEvent);
|
||||
|
||||
// Determine new input mode based on requirement hints.
|
||||
void UpdateInputMode(bool bNeedKeyboard, bool bNeedMouse);
|
||||
|
||||
void OnPostImGuiUpdate();
|
||||
|
||||
@ -64,5 +88,9 @@ private:
|
||||
mutable TArray<FSlateVertex> VertexBuffer;
|
||||
mutable TArray<SlateIndex> IndexBuffer;
|
||||
|
||||
int32 ContextIndex = 0;
|
||||
|
||||
EInputMode InputMode = EInputMode::None;
|
||||
|
||||
FImGuiInputState InputState;
|
||||
};
|
||||
|
72
Source/ImGui/Private/Utilities/WorldContextIndex.h
Normal file
72
Source/ImGui/Private/Utilities/WorldContextIndex.h
Normal file
@ -0,0 +1,72 @@
|
||||
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Core.h>
|
||||
#include <Engine.h>
|
||||
|
||||
|
||||
// Utilities mapping worlds to indices that we use to identify ImGui contexts.
|
||||
// Editor and standalone games have context index 0 while PIE worlds have indices starting from 1 for server and 2+ for
|
||||
// clients.
|
||||
|
||||
namespace Utilities
|
||||
{
|
||||
// Default context index for non PIE worlds.
|
||||
static constexpr int32 DEFAULT_CONTEXT_INDEX = 0;
|
||||
|
||||
// Invalid context index for parameters that cannot be resolved to a valid world.
|
||||
static constexpr int32 INVALID_CONTEXT_INDEX = -1;
|
||||
|
||||
#if WITH_EDITOR
|
||||
|
||||
template<typename T>
|
||||
FORCEINLINE int32 GetWorldContextIndex(const TWeakObjectPtr<T>& Obj)
|
||||
{
|
||||
return Obj.IsValid() ? GetWorldContextIndex(*Obj.Get()) : INVALID_CONTEXT_INDEX;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
FORCEINLINE int32 GetWorldContextIndex(const T* Obj)
|
||||
{
|
||||
return Obj ? GetWorldContextIndex(*Obj) : INVALID_CONTEXT_INDEX;
|
||||
}
|
||||
|
||||
FORCEINLINE int32 GetWorldContextIndex(const FWorldContext& WorldContext)
|
||||
{
|
||||
// In standalone game (WorldType = Game) we have only one context with index 0 (see DEFAULT_CONTEXT_INDEX).
|
||||
|
||||
// In editor, we keep 0 for editor and use PIEInstance to index worlds. In simulation or standalone single-PIE
|
||||
// sessions PIEInstance is 0, but since there is only one world we can change it without causing any conflicts.
|
||||
// In single-PIE with dedicated server or multi-PIE sessions worlds have PIEInstance starting from 1 for server
|
||||
// and 2+ for clients, what maps directly to our index.
|
||||
|
||||
return WorldContext.WorldType == EWorldType::PIE ? FMath::Max(WorldContext.PIEInstance, 1) : DEFAULT_CONTEXT_INDEX;
|
||||
}
|
||||
|
||||
FORCEINLINE int32 GetWorldContextIndex(const UGameInstance& GameInstance)
|
||||
{
|
||||
return GetWorldContextIndex(GameInstance.GetWorldContext());
|
||||
}
|
||||
|
||||
FORCEINLINE int32 GetWorldContextIndex(const UGameViewportClient& GameViewportClient)
|
||||
{
|
||||
return GetWorldContextIndex(GameViewportClient.GetGameInstance());
|
||||
}
|
||||
|
||||
FORCEINLINE int32 GetWorldContextIndex(const UWorld& World)
|
||||
{
|
||||
return GetWorldContextIndex(World.GetGameInstance());
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
template<typename T>
|
||||
constexpr int32 GetWorldContextIndex(const T&)
|
||||
{
|
||||
// The only option is standalone game with one context.
|
||||
return DEFAULT_CONTEXT_INDEX;
|
||||
}
|
||||
|
||||
#endif // #if WITH_EDITOR
|
||||
}
|
Loading…
Reference in New Issue
Block a user