mirror of
https://github.com/kevinporetti/UnrealImGui.git
synced 2025-01-18 16:30: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 "ImGuiContextProxy.h"
|
||||||
|
|
||||||
|
#include "ImGuiImplementation.h"
|
||||||
|
#include "ImGuiInteroperability.h"
|
||||||
|
|
||||||
|
|
||||||
static constexpr float DEFAULT_CANVAS_WIDTH = 3840.f;
|
static constexpr float DEFAULT_CANVAS_WIDTH = 3840.f;
|
||||||
static constexpr float DEFAULT_CANVAS_HEIGHT = 2160.f;
|
static constexpr float DEFAULT_CANVAS_HEIGHT = 2160.f;
|
||||||
|
|
||||||
FImGuiContextProxy::FImGuiContextProxy()
|
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();
|
ImGuiIO& IO = ImGui::GetIO();
|
||||||
|
|
||||||
// Use pre-defined canvas size.
|
// Use pre-defined canvas size.
|
||||||
IO.DisplaySize.x = DEFAULT_CANVAS_WIDTH;
|
IO.DisplaySize = { DEFAULT_CANVAS_WIDTH, DEFAULT_CANVAS_HEIGHT };
|
||||||
IO.DisplaySize.y = DEFAULT_CANVAS_HEIGHT;
|
|
||||||
|
|
||||||
// Load texture atlas.
|
// Load texture atlas.
|
||||||
unsigned char* Pixels;
|
unsigned char* Pixels;
|
||||||
@ -28,12 +37,44 @@ FImGuiContextProxy::FImGuiContextProxy()
|
|||||||
BeginFrame();
|
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)
|
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.
|
// 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)
|
if (!bIsFrameStarted)
|
||||||
{
|
{
|
||||||
|
@ -10,8 +10,7 @@
|
|||||||
class FImGuiInputState;
|
class FImGuiInputState;
|
||||||
|
|
||||||
// Represents a single ImGui context. All the context updates should be done through this proxy. During update it
|
// 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.
|
// broadcasts draw events to allow listeners draw their controls. After update it stores draw data.
|
||||||
// TODO: Add dynamically created contexts, so we can have a better support for multi-PIE.
|
|
||||||
class FImGuiContextProxy
|
class FImGuiContextProxy
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -22,30 +21,42 @@ public:
|
|||||||
FImGuiContextProxy(const FImGuiContextProxy&) = delete;
|
FImGuiContextProxy(const FImGuiContextProxy&) = delete;
|
||||||
FImGuiContextProxy& operator=(const FImGuiContextProxy&) = delete;
|
FImGuiContextProxy& operator=(const FImGuiContextProxy&) = delete;
|
||||||
|
|
||||||
FImGuiContextProxy(FImGuiContextProxy&&) = delete;
|
FImGuiContextProxy(FImGuiContextProxy&& Other);
|
||||||
FImGuiContextProxy& operator=(FImGuiContextProxy&&) = delete;
|
FImGuiContextProxy& operator=(FImGuiContextProxy&& Other);
|
||||||
|
|
||||||
// Get draw data from the last frame.
|
// Get draw data from the last frame.
|
||||||
const TArray<FImGuiDrawList>& GetDrawData() const { return DrawLists; }
|
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.
|
// Delegate called right before ending the frame to allows listeners draw their controls.
|
||||||
FSimpleMulticastDelegate& OnDraw() { return DrawEvent; }
|
FSimpleMulticastDelegate& OnDraw() { return DrawEvent; }
|
||||||
|
|
||||||
// Tick to advance context to the next frame.
|
// Tick to advance context to the next frame.
|
||||||
// @param DeltaSeconds - Time delta in seconds (will be passed to ImGui)
|
void Tick(float DeltaSeconds);
|
||||||
// @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);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
void BeginFrame(float DeltaTime = 1.f / 60.f, const FImGuiInputState* InputState = nullptr);
|
void BeginFrame(float DeltaTime = 1.f / 60.f);
|
||||||
void EndFrame();
|
void EndFrame();
|
||||||
|
|
||||||
void UpdateDrawData(ImDrawData* DrawData);
|
void UpdateDrawData(ImDrawData* DrawData);
|
||||||
|
|
||||||
TArray<FImGuiDrawList> DrawLists;
|
ImGuiContext* Context = nullptr;
|
||||||
|
|
||||||
FSimpleMulticastDelegate DrawEvent;
|
|
||||||
|
|
||||||
bool bIsFrameStarted = false;
|
bool bIsFrameStarted = false;
|
||||||
|
FSimpleMulticastDelegate DrawEvent;
|
||||||
|
const FImGuiInputState* InputState = nullptr;
|
||||||
|
|
||||||
|
TArray<FImGuiDrawList> DrawLists;
|
||||||
};
|
};
|
||||||
|
@ -20,3 +20,11 @@
|
|||||||
#if PLATFORM_WINDOWS
|
#if PLATFORM_WINDOWS
|
||||||
#include <HideWindowsPlatformTypes.h>
|
#include <HideWindowsPlatformTypes.h>
|
||||||
#endif // PLATFORM_WINDOWS
|
#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)
|
||||||
{
|
{
|
||||||
|
if (bKeyboard)
|
||||||
|
{
|
||||||
ClearCharacters();
|
ClearCharacters();
|
||||||
ClearKeys();
|
ClearKeys();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bMouse)
|
||||||
|
{
|
||||||
ClearMouseButtons();
|
ClearMouseButtons();
|
||||||
ClearMouseAnalogue();
|
ClearMouseAnalogue();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bKeyboard && bMouse)
|
||||||
|
{
|
||||||
ClearModifierKeys();
|
ClearModifierKeys();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FImGuiInputState::ClearUpdateState()
|
void FImGuiInputState::ClearUpdateState()
|
||||||
|
@ -98,7 +98,13 @@ public:
|
|||||||
void SetAltDown(bool bIsDown) { bIsAltDown = bIsDown; }
|
void SetAltDown(bool bIsDown) { bIsAltDown = bIsDown; }
|
||||||
|
|
||||||
// Reset state and mark as dirty.
|
// 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
|
// 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.
|
// about dirty parts of keys or mouse buttons arrays.
|
||||||
@ -106,6 +112,8 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
void Reset(bool bKeyboard, bool bMouse);
|
||||||
|
|
||||||
void ClearCharacters();
|
void ClearCharacters();
|
||||||
void ClearKeys();
|
void ClearKeys();
|
||||||
void ClearMouseButtons();
|
void ClearMouseButtons();
|
||||||
|
@ -11,8 +11,8 @@
|
|||||||
|
|
||||||
FImGuiModuleManager::FImGuiModuleManager()
|
FImGuiModuleManager::FImGuiModuleManager()
|
||||||
{
|
{
|
||||||
// Bind ImGui demo to proxy, so it can draw controls in its context.
|
// Make sure that default ImGui context is setup.
|
||||||
ContextProxy.OnDraw().AddRaw(&ImGuiDemo, &FImGuiDemo::DrawControls);
|
ContextManager.GetDefaultContextProxy();
|
||||||
|
|
||||||
// Typically we will use viewport created events to add widget to new game viewports.
|
// Typically we will use viewport created events to add widget to new game viewports.
|
||||||
ViewportCreatedHandle = UGameViewportClient::OnViewportCreated().AddRaw(this, &FImGuiModuleManager::OnViewportCreated);
|
ViewportCreatedHandle = UGameViewportClient::OnViewportCreated().AddRaw(this, &FImGuiModuleManager::OnViewportCreated);
|
||||||
@ -117,8 +117,8 @@ void FImGuiModuleManager::Tick(float DeltaSeconds)
|
|||||||
{
|
{
|
||||||
if (IsInUpdateThread())
|
if (IsInUpdateThread())
|
||||||
{
|
{
|
||||||
// Update context proxy to advance to next frame.
|
// Update context manager to advance all ImGui contexts to the next frame.
|
||||||
ContextProxy.Tick(DeltaSeconds, ViewportWidget.IsValid() ? &ViewportWidget->GetInputState() : nullptr);
|
ContextManager.Tick(DeltaSeconds);
|
||||||
|
|
||||||
// Inform that we finished updating ImGui, so other subsystems can react.
|
// Inform that we finished updating ImGui, so other subsystems can react.
|
||||||
PostImGuiUpdateEvent.Broadcast();
|
PostImGuiUpdateEvent.Broadcast();
|
||||||
@ -141,15 +141,24 @@ void FImGuiModuleManager::AddWidgetToViewport(UGameViewportClient* GameViewport)
|
|||||||
checkf(GameViewport, TEXT("Null game viewport."));
|
checkf(GameViewport, TEXT("Null game viewport."));
|
||||||
checkf(FSlateApplication::IsInitialized(), TEXT("Slate should be initialized before we can add widget to game viewports."));
|
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())
|
if (!ViewportWidget.IsValid())
|
||||||
{
|
{
|
||||||
SAssignNew(ViewportWidget, SImGuiWidget).ModuleManager(this);
|
SAssignNew(ViewportWidget, SImGuiWidget).ModuleManager(this).ContextIndex(ContextIndex);
|
||||||
checkf(ViewportWidget.IsValid(), TEXT("Failed to create SImGuiWidget."));
|
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.
|
// 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;
|
||||||
|
|
||||||
GameViewport->AddViewportWidgetContent(SNew(SWeakWidget).PossiblyNullContent(ViewportWidget), IMGUI_WIDGET_Z_ORDER);
|
GameViewport->AddViewportWidgetContent(SNew(SWeakWidget).PossiblyNullContent(ViewportWidget), IMGUI_WIDGET_Z_ORDER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,8 +2,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "ImGuiContextProxy.h"
|
#include "ImGuiContextManager.h"
|
||||||
#include "ImGuiDemo.h"
|
|
||||||
#include "SImGuiWidget.h"
|
#include "SImGuiWidget.h"
|
||||||
#include "TextureManager.h"
|
#include "TextureManager.h"
|
||||||
|
|
||||||
@ -16,8 +15,8 @@ class FImGuiModuleManager
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
// Get ImGui context proxy.
|
// Get ImGui contexts manager.
|
||||||
FImGuiContextProxy& GetContextProxy() { return ContextProxy; }
|
FImGuiContextManager& GetContextManager() { return ContextManager; }
|
||||||
|
|
||||||
// Get texture resources manager.
|
// Get texture resources manager.
|
||||||
FTextureManager& GetTextureManager() { return TextureManager; }
|
FTextureManager& GetTextureManager() { return TextureManager; }
|
||||||
@ -56,18 +55,14 @@ private:
|
|||||||
// Event that we call after ImGui is updated.
|
// Event that we call after ImGui is updated.
|
||||||
FSimpleMulticastDelegate PostImGuiUpdateEvent;
|
FSimpleMulticastDelegate PostImGuiUpdateEvent;
|
||||||
|
|
||||||
// Proxy controlling ImGui context.
|
// Manager for ImGui contexts.
|
||||||
FImGuiContextProxy ContextProxy;
|
FImGuiContextManager ContextManager;
|
||||||
|
|
||||||
// ImWidget that draws ImGui demo.
|
|
||||||
FImGuiDemo ImGuiDemo;
|
|
||||||
|
|
||||||
// Manager for textures resources.
|
// Manager for textures resources.
|
||||||
FTextureManager TextureManager;
|
FTextureManager TextureManager;
|
||||||
|
|
||||||
// Slate widget that we attach to created game viewports (widget without per-viewport state can be attached to
|
// Slate widgets that we attach to game viewports.
|
||||||
// multiple viewports).
|
TMap<int32, TSharedPtr<SImGuiWidget>> ViewportWidgets;
|
||||||
TSharedPtr<SImGuiWidget> ViewportWidget;
|
|
||||||
|
|
||||||
FDelegateHandle TickDelegateHandle;
|
FDelegateHandle TickDelegateHandle;
|
||||||
FDelegateHandle ViewportCreatedHandle;
|
FDelegateHandle ViewportCreatedHandle;
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include "SImGuiWidget.h"
|
#include "SImGuiWidget.h"
|
||||||
|
|
||||||
|
#include "ImGuiContextManager.h"
|
||||||
#include "ImGuiContextProxy.h"
|
#include "ImGuiContextProxy.h"
|
||||||
#include "ImGuiInteroperability.h"
|
#include "ImGuiInteroperability.h"
|
||||||
#include "ImGuiModuleManager.h"
|
#include "ImGuiModuleManager.h"
|
||||||
@ -16,6 +17,7 @@ void SImGuiWidget::Construct(const FArguments& InArgs)
|
|||||||
{
|
{
|
||||||
checkf(InArgs._ModuleManager, TEXT("Null Module Manager argument"));
|
checkf(InArgs._ModuleManager, TEXT("Null Module Manager argument"));
|
||||||
ModuleManager = InArgs._ModuleManager;
|
ModuleManager = InArgs._ModuleManager;
|
||||||
|
ContextIndex = InArgs._ContextIndex;
|
||||||
|
|
||||||
ModuleManager->OnPostImGuiUpdate().AddRaw(this, &SImGuiWidget::OnPostImGuiUpdate);
|
ModuleManager->OnPostImGuiUpdate().AddRaw(this, &SImGuiWidget::OnPostImGuiUpdate);
|
||||||
}
|
}
|
||||||
@ -95,6 +97,48 @@ FReply SImGuiWidget::OnMouseMove(const FGeometry& MyGeometry, const FPointerEven
|
|||||||
return FReply::Handled();
|
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)
|
void SImGuiWidget::CopyModifierKeys(const FInputEvent& InputEvent)
|
||||||
{
|
{
|
||||||
InputState.SetControlDown(InputEvent.IsControlDown());
|
InputState.SetControlDown(InputEvent.IsControlDown());
|
||||||
@ -102,21 +146,58 @@ void SImGuiWidget::CopyModifierKeys(const FInputEvent& InputEvent)
|
|||||||
InputState.SetAltDown(InputEvent.IsAltDown());
|
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()
|
void SImGuiWidget::OnPostImGuiUpdate()
|
||||||
{
|
{
|
||||||
|
if (InputMode != EInputMode::None)
|
||||||
|
{
|
||||||
InputState.ClearUpdateState();
|
InputState.ClearUpdateState();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int32 SImGuiWidget::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect,
|
int32 SImGuiWidget::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect,
|
||||||
FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& WidgetStyle, bool bParentEnabled) const
|
FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& WidgetStyle, bool bParentEnabled) const
|
||||||
{
|
{
|
||||||
|
if (FImGuiContextProxy* ContextProxy = ModuleManager->GetContextManager().GetContextProxy(ContextIndex))
|
||||||
|
{
|
||||||
// Calculate offset that will transform vertex positions to screen space - rounded to avoid half pixel offsets.
|
// 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) };
|
const FVector2D VertexPositionOffset{ FMath::RoundToFloat(MyClippingRect.Left), FMath::RoundToFloat(MyClippingRect.Top) };
|
||||||
|
|
||||||
// Convert clipping rectangle to format required by Slate vertex.
|
// Convert clipping rectangle to format required by Slate vertex.
|
||||||
const FSlateRotatedRect VertexClippingRect{ MyClippingRect };
|
const FSlateRotatedRect VertexClippingRect{ MyClippingRect };
|
||||||
|
|
||||||
for (const auto& DrawList : ModuleManager->GetContextProxy().GetDrawData())
|
for (const auto& DrawList : ContextProxy->GetDrawData())
|
||||||
{
|
{
|
||||||
DrawList.CopyVertexData(VertexBuffer, VertexPositionOffset, VertexClippingRect);
|
DrawList.CopyVertexData(VertexBuffer, VertexPositionOffset, VertexClippingRect);
|
||||||
|
|
||||||
@ -145,6 +226,7 @@ int32 SImGuiWidget::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeo
|
|||||||
FSlateDrawElement::MakeCustomVerts(OutDrawElements, LayerId, Handle, VertexBuffer, IndexBuffer, nullptr, 0, 0);
|
FSlateDrawElement::MakeCustomVerts(OutDrawElements, LayerId, Handle, VertexBuffer, IndexBuffer, nullptr, 0, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return LayerId;
|
return LayerId;
|
||||||
}
|
}
|
||||||
|
@ -19,12 +19,17 @@ public:
|
|||||||
SLATE_BEGIN_ARGS(SImGuiWidget)
|
SLATE_BEGIN_ARGS(SImGuiWidget)
|
||||||
{}
|
{}
|
||||||
SLATE_ARGUMENT(FImGuiModuleManager*, ModuleManager)
|
SLATE_ARGUMENT(FImGuiModuleManager*, ModuleManager)
|
||||||
|
SLATE_ARGUMENT(int32, ContextIndex)
|
||||||
SLATE_END_ARGS()
|
SLATE_END_ARGS()
|
||||||
|
|
||||||
void Construct(const FArguments& InArgs);
|
void Construct(const FArguments& InArgs);
|
||||||
|
|
||||||
~SImGuiWidget();
|
~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; }
|
const FImGuiInputState& GetInputState() const { return InputState; }
|
||||||
|
|
||||||
//----------------------------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------------------------
|
||||||
@ -49,9 +54,28 @@ public:
|
|||||||
|
|
||||||
virtual FReply OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
|
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:
|
private:
|
||||||
|
|
||||||
|
enum class EInputMode
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
MouseOnly,
|
||||||
|
MouseAndKeyboard
|
||||||
|
};
|
||||||
|
|
||||||
FORCEINLINE void CopyModifierKeys(const FInputEvent& InputEvent);
|
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();
|
void OnPostImGuiUpdate();
|
||||||
|
|
||||||
@ -64,5 +88,9 @@ private:
|
|||||||
mutable TArray<FSlateVertex> VertexBuffer;
|
mutable TArray<FSlateVertex> VertexBuffer;
|
||||||
mutable TArray<SlateIndex> IndexBuffer;
|
mutable TArray<SlateIndex> IndexBuffer;
|
||||||
|
|
||||||
|
int32 ContextIndex = 0;
|
||||||
|
|
||||||
|
EInputMode InputMode = EInputMode::None;
|
||||||
|
|
||||||
FImGuiInputState InputState;
|
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