mirror of
https://github.com/kevinporetti/UnrealImGui.git
synced 2025-01-18 08:20:32 +00:00
Added support for session reloading and updated contexts and widgets management:
- Added to ImGui Context Proxy a name that is mapped to ini file set in ImGui context. - ImGui Context Manager generates unique context names from world type and context index. - Refactored ImGui Context Manager to have a cleaner separation between editor and non-editor bits. - Fixed context update rules in ImGui Context Manager. - Changed widgets management in ImGui Module Manager to allow automatic garbage collection after viewports are closed and manual removal when module is shutting down. - ImGui Widgets are in full control of communication with context proxies. - Added basic world context utilities. - Refactored world context index utilities and replaced ambiguous 'default context index' with 'editor' and 'game' ones.
This commit is contained in:
parent
7e756c7cb5
commit
1a6aa98f51
@ -4,68 +4,141 @@
|
||||
|
||||
#include "ImGuiContextManager.h"
|
||||
|
||||
#include "ImGuiImplementation.h"
|
||||
#include "Utilities/WorldContext.h"
|
||||
#include "Utilities/WorldContextIndex.h"
|
||||
|
||||
FImGuiContextProxy& FImGuiContextManager::GetWorldContextProxy(UWorld& World)
|
||||
#include <imgui.h>
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
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());
|
||||
|
||||
// Name for editor ImGui context.
|
||||
FORCEINLINE FString GetEditorContextName()
|
||||
{
|
||||
return TEXT("Editor");
|
||||
}
|
||||
|
||||
// Name for world ImGui context.
|
||||
FORCEINLINE FString GetWorldContextName(const UWorld& World)
|
||||
{
|
||||
using namespace Utilities;
|
||||
|
||||
const FWorldContext* WorldContext = GetWorldContext(World);
|
||||
switch (WorldContext->WorldType)
|
||||
{
|
||||
case EWorldType::PIE:
|
||||
return FString::Printf(TEXT("PIEContext%d"), GetWorldContextIndex(*WorldContext));
|
||||
case EWorldType::Game:
|
||||
return TEXT("Game");
|
||||
case EWorldType::Editor:
|
||||
return TEXT("Editor");
|
||||
default:
|
||||
return TEXT("Other");
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
FORCEINLINE FString GetWorldContextName()
|
||||
{
|
||||
return TEXT("Game");
|
||||
}
|
||||
|
||||
FORCEINLINE FString GetWorldContextName(const UWorld&)
|
||||
{
|
||||
return TEXT("Game");
|
||||
}
|
||||
|
||||
#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;
|
||||
FImGuiContextManager::~FImGuiContextManager()
|
||||
{
|
||||
Contexts.Empty();
|
||||
ImGui::Shutdown();
|
||||
}
|
||||
|
||||
void FImGuiContextManager::Tick(float DeltaSeconds)
|
||||
{
|
||||
FContextData* Data = Contexts.Find(1);
|
||||
if (!Data || !Data->World.IsValid())
|
||||
// In editor, worlds can get invalid. We could remove corresponding entries, but that would mean resetting ImGui
|
||||
// context every time when PIE session is restarted. Instead we freeze contexts until their worlds are re-created.
|
||||
|
||||
for (auto& Pair : Contexts)
|
||||
{
|
||||
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);
|
||||
auto& ContextData = Pair.Value;
|
||||
if (ContextData.CanTick())
|
||||
{
|
||||
ContextData.ContextProxy.SetAsCurrent();
|
||||
ContextData.ContextProxy.Tick(DeltaSeconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FImGuiContextManager::FContextData& FImGuiContextManager::FindOrAddContextData(int32 Index)
|
||||
#if WITH_EDITOR
|
||||
FImGuiContextManager::FContextData& FImGuiContextManager::GetEditorContextData()
|
||||
{
|
||||
FContextData* Data = Contexts.Find(Index);
|
||||
FContextData* Data = Contexts.Find(Utilities::EDITOR_CONTEXT_INDEX);
|
||||
|
||||
if (!Data)
|
||||
if (UNLIKELY(!Data))
|
||||
{
|
||||
Data = &Contexts.Add(Index);
|
||||
if (!Data->ContextProxy.OnDraw().IsBoundToObject(&ImGuiDemo))
|
||||
{
|
||||
Data->ContextProxy.OnDraw().AddRaw(&ImGuiDemo, &FImGuiDemo::DrawControls);
|
||||
}
|
||||
Data = &Contexts.Emplace(Utilities::EDITOR_CONTEXT_INDEX, FContextData{ GetEditorContextName(), ImGuiDemo });
|
||||
}
|
||||
|
||||
return *Data;
|
||||
}
|
||||
#endif // WITH_EDITOR
|
||||
|
||||
#if !WITH_EDITOR
|
||||
FImGuiContextManager::FContextData& FImGuiContextManager::GetStandaloneWorldContextData()
|
||||
{
|
||||
FContextData* Data = Contexts.Find(Utilities::STANDALONE_GAME_CONTEXT_INDEX);
|
||||
|
||||
if (UNLIKELY(!Data))
|
||||
{
|
||||
Data = &Contexts.Emplace(Utilities::STANDALONE_GAME_CONTEXT_INDEX, FContextData{ GetWorldContextName(), ImGuiDemo });
|
||||
}
|
||||
|
||||
return *Data;
|
||||
}
|
||||
#endif // !WITH_EDITOR
|
||||
|
||||
FImGuiContextManager::FContextData& FImGuiContextManager::GetWorldContextData(const UWorld& World)
|
||||
{
|
||||
using namespace Utilities;
|
||||
|
||||
const FWorldContext* WorldContext = GetWorldContext(World);
|
||||
const int32 Index = GetWorldContextIndex(*WorldContext);
|
||||
|
||||
checkf(Index != Utilities::INVALID_CONTEXT_INDEX, TEXT("Couldn't find context index for world %s: WorldType = %d"),
|
||||
*World.GetName(), World.WorldType);
|
||||
|
||||
#if WITH_EDITOR
|
||||
checkf(!GEngine->IsEditor() || Index != Utilities::EDITOR_CONTEXT_INDEX,
|
||||
TEXT("Context index %d is reserved for editor and cannot be used for world %s: WorldType = %d, NetMode = %d"),
|
||||
Index, *World.GetName(), World.WorldType, World.GetNetMode());
|
||||
#endif
|
||||
|
||||
FContextData* Data = Contexts.Find(Index);
|
||||
|
||||
#if WITH_EDITOR
|
||||
if (UNLIKELY(!Data))
|
||||
{
|
||||
Data = &Contexts.Emplace(Index, FContextData{ GetWorldContextName(World), ImGuiDemo, WorldContext->PIEInstance });
|
||||
}
|
||||
else
|
||||
{
|
||||
// Because we allow (for the sake of continuity) to map different PIE instances to the same index.
|
||||
Data->PIEInstance = WorldContext->PIEInstance;
|
||||
}
|
||||
#else
|
||||
if (UNLIKELY(!Data))
|
||||
{
|
||||
Data = &Contexts.Emplace(Index, FContextData{ GetWorldContextName(World), ImGuiDemo });
|
||||
}
|
||||
#endif
|
||||
|
||||
return *Data;
|
||||
}
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
#include "ImGuiContextProxy.h"
|
||||
#include "ImGuiDemo.h"
|
||||
#include "Utilities/WorldContextIndex.h"
|
||||
|
||||
|
||||
// Manages ImGui context proxies.
|
||||
@ -20,17 +19,20 @@ public:
|
||||
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; }
|
||||
~FImGuiContextManager();
|
||||
|
||||
#if WITH_EDITOR
|
||||
// Get or create editor ImGui context proxy.
|
||||
FORCEINLINE FImGuiContextProxy& GetEditorContextProxy() { return GetEditorContextData().ContextProxy; }
|
||||
#endif
|
||||
|
||||
#if !WITH_EDITOR
|
||||
// Get or create standalone game ImGui context proxy.
|
||||
FORCEINLINE FImGuiContextProxy& GetWorldContextProxy() { return GetStandaloneWorldContextData().ContextProxy; }
|
||||
#endif
|
||||
|
||||
// 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);
|
||||
FORCEINLINE FImGuiContextProxy& GetWorldContextProxy(const UWorld& World) { return GetWorldContextData(World).ContextProxy; }
|
||||
|
||||
// Get context proxy by index, or null if context with that index doesn't exist.
|
||||
FORCEINLINE FImGuiContextProxy* GetContextProxy(int32 ContextIndex)
|
||||
@ -43,13 +45,49 @@ public:
|
||||
|
||||
private:
|
||||
|
||||
#if WITH_EDITOR
|
||||
|
||||
struct FContextData
|
||||
{
|
||||
TWeakObjectPtr<UWorld> World;
|
||||
FContextData(const FString& ContextName, FImGuiDemo& Demo, int32 InPIEInstance = -1)
|
||||
: PIEInstance(InPIEInstance)
|
||||
, ContextProxy(ContextName)
|
||||
{
|
||||
ContextProxy.OnDraw().AddRaw(&Demo, &FImGuiDemo::DrawControls);
|
||||
}
|
||||
|
||||
FORCEINLINE bool CanTick() const { return PIEInstance < 0 || GEngine->GetWorldContextFromPIEInstance(PIEInstance); }
|
||||
|
||||
int32 PIEInstance = -1;
|
||||
FImGuiContextProxy ContextProxy;
|
||||
};
|
||||
|
||||
FContextData& FindOrAddContextData(int32 Index);
|
||||
#else
|
||||
|
||||
struct FContextData
|
||||
{
|
||||
FContextData(const FString& ContextName, FImGuiDemo& Demo)
|
||||
: ContextProxy(ContextName)
|
||||
{
|
||||
ContextProxy.OnDraw().AddRaw(&Demo, &FImGuiDemo::DrawControls);
|
||||
}
|
||||
|
||||
FORCEINLINE bool CanTick() const { return true; }
|
||||
|
||||
FImGuiContextProxy ContextProxy;
|
||||
};
|
||||
|
||||
#endif // WITH_EDITOR
|
||||
|
||||
#if WITH_EDITOR
|
||||
FContextData& GetEditorContextData();
|
||||
#endif
|
||||
|
||||
#if !WITH_EDITOR
|
||||
FContextData& GetStandaloneWorldContextData();
|
||||
#endif
|
||||
|
||||
FContextData& GetWorldContextData(const UWorld& World);
|
||||
|
||||
TMap<int32, FContextData> Contexts;
|
||||
|
||||
|
@ -11,7 +11,29 @@
|
||||
static constexpr float DEFAULT_CANVAS_WIDTH = 3840.f;
|
||||
static constexpr float DEFAULT_CANVAS_HEIGHT = 2160.f;
|
||||
|
||||
FImGuiContextProxy::FImGuiContextProxy()
|
||||
|
||||
namespace
|
||||
{
|
||||
FString GetSaveDirectory()
|
||||
{
|
||||
FString Directory = FPaths::Combine(*FPaths::GameSavedDir(), TEXT("ImGui"));
|
||||
|
||||
// Make sure that directory is created.
|
||||
IPlatformFile::GetPlatformPhysical().CreateDirectory(*Directory);
|
||||
|
||||
return Directory;
|
||||
}
|
||||
|
||||
FString GetIniFile(const FString& Name)
|
||||
{
|
||||
static FString SaveDirectory = GetSaveDirectory();
|
||||
return FPaths::Combine(SaveDirectory, Name + TEXT(".ini"));
|
||||
}
|
||||
}
|
||||
|
||||
FImGuiContextProxy::FImGuiContextProxy(const FString& InName)
|
||||
: Name(InName)
|
||||
, IniFilename(TCHAR_TO_ANSI(*GetIniFile(InName)))
|
||||
{
|
||||
// Create context.
|
||||
Context = ImGui::CreateContext();
|
||||
@ -22,6 +44,9 @@ FImGuiContextProxy::FImGuiContextProxy()
|
||||
// Start initialization.
|
||||
ImGuiIO& IO = ImGui::GetIO();
|
||||
|
||||
// Set session data storage.
|
||||
IO.IniFilename = IniFilename.c_str();
|
||||
|
||||
// Use pre-defined canvas size.
|
||||
IO.DisplaySize = { DEFAULT_CANVAS_WIDTH, DEFAULT_CANVAS_HEIGHT };
|
||||
|
||||
@ -53,18 +78,26 @@ FImGuiContextProxy::FImGuiContextProxy(FImGuiContextProxy&& Other)
|
||||
, DrawEvent(std::move(Other.DrawEvent))
|
||||
, InputState(std::move(Other.InputState))
|
||||
, DrawLists(std::move(Other.DrawLists))
|
||||
, Name(std::move(Other.Name))
|
||||
, IniFilename(std::move(Other.IniFilename))
|
||||
{
|
||||
Other.Context = nullptr;
|
||||
}
|
||||
|
||||
FImGuiContextProxy& FImGuiContextProxy::operator=(FImGuiContextProxy&& Other)
|
||||
{
|
||||
Context = std::move(Other.Context);
|
||||
Other.Context = nullptr;
|
||||
// Swapping context so it can be destroyed with the other object.
|
||||
using std::swap;
|
||||
swap(Context, Other.Context);
|
||||
|
||||
// Just moving remaining data that doesn't affect cleanup.
|
||||
bHasActiveItem = Other.bHasActiveItem;
|
||||
DrawEvent = std::move(Other.DrawEvent);
|
||||
InputState = std::move(Other.InputState);
|
||||
DrawLists = std::move(Other.DrawLists);
|
||||
Name = std::move(Other.Name);
|
||||
IniFilename = std::move(Other.IniFilename);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -75,14 +108,12 @@ FImGuiContextProxy::~FImGuiContextProxy()
|
||||
// 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.
|
||||
// Save context data and destroy.
|
||||
ImGuiImplementation::SaveCurrentContextIniSettings(IniFilename.c_str());
|
||||
ImGui::DestroyContext(Context);
|
||||
|
||||
// Set default context in ImGui to keep global context pointer valid.
|
||||
ImGui::SetCurrentContext(&GetDefaultContext());
|
||||
ImGui::SetCurrentContext(&ImGuiImplementation::GetDefaultContext());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,8 @@
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
|
||||
class FImGuiInputState;
|
||||
|
||||
@ -15,7 +17,7 @@ class FImGuiContextProxy
|
||||
{
|
||||
public:
|
||||
|
||||
FImGuiContextProxy();
|
||||
FImGuiContextProxy(const FString& Name);
|
||||
~FImGuiContextProxy();
|
||||
|
||||
FImGuiContextProxy(const FImGuiContextProxy&) = delete;
|
||||
@ -24,6 +26,9 @@ public:
|
||||
FImGuiContextProxy(FImGuiContextProxy&& Other);
|
||||
FImGuiContextProxy& operator=(FImGuiContextProxy&& Other);
|
||||
|
||||
// Get the name of this context.
|
||||
const FString& GetName() const { return Name; }
|
||||
|
||||
// Get draw data from the last frame.
|
||||
const TArray<FImGuiDrawList>& GetDrawData() const { return DrawLists; }
|
||||
|
||||
@ -33,6 +38,9 @@ public:
|
||||
// Set input state to be used by this context.
|
||||
void SetInputState(const FImGuiInputState* SourceInputState) { InputState = SourceInputState; }
|
||||
|
||||
// If context is currently using input state to remove then remove that binding.
|
||||
void RemoveInputState(const FImGuiInputState* InputStateToRemove) { if (InputState == InputStateToRemove) InputState = nullptr; }
|
||||
|
||||
// Is this context the current ImGui context.
|
||||
bool IsCurrentContext() const { return ImGui::GetCurrentContext() == Context; }
|
||||
|
||||
@ -63,4 +71,7 @@ private:
|
||||
const FImGuiInputState* InputState = nullptr;
|
||||
|
||||
TArray<FImGuiDrawList> DrawLists;
|
||||
|
||||
FString Name;
|
||||
std::string IniFilename;
|
||||
};
|
||||
|
@ -22,9 +22,17 @@
|
||||
#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()
|
||||
namespace ImGuiImplementation
|
||||
{
|
||||
return GImDefaultContext;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
void SaveCurrentContextIniSettings(const char* Filename)
|
||||
{
|
||||
SaveIniSettingsToDisk(Filename);
|
||||
}
|
||||
}
|
@ -5,5 +5,12 @@
|
||||
#include <imgui.h>
|
||||
|
||||
|
||||
// Get default context created by ImGui framework.
|
||||
ImGuiContext& GetDefaultContext();
|
||||
// Gives access to selected ImGui implementation features.
|
||||
namespace ImGuiImplementation
|
||||
{
|
||||
// Get default context created by ImGui framework.
|
||||
ImGuiContext& GetDefaultContext();
|
||||
|
||||
// Save current context settings.
|
||||
void SaveCurrentContextIniSettings(const char* Filename);
|
||||
}
|
||||
|
@ -5,15 +5,13 @@
|
||||
#include "ImGuiModuleManager.h"
|
||||
|
||||
#include "ImGuiInteroperability.h"
|
||||
#include "Utilities/WorldContextIndex.h"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
|
||||
FImGuiModuleManager::FImGuiModuleManager()
|
||||
{
|
||||
// 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);
|
||||
|
||||
@ -37,6 +35,15 @@ FImGuiModuleManager::~FImGuiModuleManager()
|
||||
ViewportCreatedHandle.Reset();
|
||||
}
|
||||
|
||||
// Remove still active widgets (important during hot-reloading).
|
||||
for (auto& Widget : Widgets)
|
||||
{
|
||||
if (Widget.IsValid())
|
||||
{
|
||||
Widget.Pin()->Detach();
|
||||
}
|
||||
}
|
||||
|
||||
// Deactivate this manager.
|
||||
Uninitialize();
|
||||
}
|
||||
@ -141,24 +148,21 @@ 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())
|
||||
TSharedPtr<SImGuiWidget> Widget;
|
||||
SAssignNew(Widget, SImGuiWidget).ModuleManager(this).GameViewport(GameViewport)
|
||||
.ContextIndex(Utilities::GetWorldContextIndex(GameViewport));
|
||||
|
||||
if (TWeakPtr<SImGuiWidget>* Slot = Widgets.FindByPredicate([](auto& Widget) { return !Widget.IsValid(); }))
|
||||
{
|
||||
SAssignNew(ViewportWidget, SImGuiWidget).ModuleManager(this).ContextIndex(ContextIndex);
|
||||
check(ViewportWidget.IsValid());
|
||||
*Slot = Widget;
|
||||
}
|
||||
else
|
||||
{
|
||||
Widgets.Emplace(Widget);
|
||||
}
|
||||
|
||||
// Bind widget's input to context for this world.
|
||||
Proxy.SetInputState(&ViewportWidget->GetInputState());
|
||||
|
||||
// We should always have one viewport per context index at a time (this will be validated by widget).
|
||||
ViewportWidget->AttachToViewport(GameViewport);
|
||||
}
|
||||
|
||||
void FImGuiModuleManager::AddWidgetToAllViewports()
|
||||
|
@ -61,8 +61,8 @@ private:
|
||||
// Manager for textures resources.
|
||||
FTextureManager TextureManager;
|
||||
|
||||
// Slate widgets that we attach to game viewports.
|
||||
TMap<int32, TSharedPtr<SImGuiWidget>> ViewportWidgets;
|
||||
// Slate widgets that we created.
|
||||
TArray<TWeakPtr<SImGuiWidget>> Widgets;
|
||||
|
||||
FDelegateHandle TickDelegateHandle;
|
||||
FDelegateHandle ViewportCreatedHandle;
|
||||
|
@ -47,10 +47,16 @@ BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
void SImGuiWidget::Construct(const FArguments& InArgs)
|
||||
{
|
||||
checkf(InArgs._ModuleManager, TEXT("Null Module Manager argument"));
|
||||
checkf(InArgs._GameViewport, TEXT("Null Game Viewport argument"));
|
||||
|
||||
ModuleManager = InArgs._ModuleManager;
|
||||
GameViewport = InArgs._GameViewport;
|
||||
ContextIndex = InArgs._ContextIndex;
|
||||
|
||||
// NOTE: We could allow null game viewports (for instance to attach to non-viewport widgets) but we would need
|
||||
// to modify a few functions that assume valid viewport pointer.
|
||||
GameViewport->AddViewportWidgetContent(SharedThis(this), IMGUI_WIDGET_Z_ORDER);
|
||||
|
||||
// Disable mouse cursor over this widget as we will use ImGui to draw it.
|
||||
SetCursor(EMouseCursor::None);
|
||||
|
||||
@ -60,10 +66,11 @@ void SImGuiWidget::Construct(const FArguments& InArgs)
|
||||
// Register to get post-update notifications, so we can clean frame updates.
|
||||
ModuleManager->OnPostImGuiUpdate().AddRaw(this, &SImGuiWidget::OnPostImGuiUpdate);
|
||||
|
||||
// Register self-debug function.
|
||||
// Bind this widget to its context proxy.
|
||||
auto* ContextProxy = ModuleManager->GetContextManager().GetContextProxy(ContextIndex);
|
||||
checkf(ContextProxy, TEXT("Missing context during widget construction: ContextIndex = %d"), ContextIndex);
|
||||
ContextProxy->OnDraw().AddRaw(this, &SImGuiWidget::OnDebugDraw);
|
||||
ContextProxy->SetInputState(&InputState);
|
||||
}
|
||||
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
|
||||
@ -73,27 +80,20 @@ SImGuiWidget::~SImGuiWidget()
|
||||
if (auto* ContextProxy = ModuleManager->GetContextManager().GetContextProxy(ContextIndex))
|
||||
{
|
||||
ContextProxy->OnDraw().RemoveAll(this);
|
||||
ContextProxy->RemoveInputState(&InputState);
|
||||
}
|
||||
|
||||
// Unregister from post-update notifications.
|
||||
ModuleManager->OnPostImGuiUpdate().RemoveAll(this);
|
||||
}
|
||||
|
||||
void SImGuiWidget::AttachToViewport(UGameViewportClient* InGameViewport, bool bResetInput)
|
||||
void SImGuiWidget::Detach()
|
||||
{
|
||||
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)
|
||||
if (GameViewport.IsValid())
|
||||
{
|
||||
ResetInputState();
|
||||
GameViewport->RemoveViewportWidgetContent(SharedThis(this));
|
||||
GameViewport.Reset();
|
||||
}
|
||||
|
||||
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)
|
||||
@ -284,13 +284,6 @@ bool SImGuiWidget::IgnoreKeyEvent(const FKeyEvent& KeyEvent) const
|
||||
return false;
|
||||
}
|
||||
|
||||
void SImGuiWidget::ResetInputState()
|
||||
{
|
||||
bInputEnabled = false;
|
||||
SetVisibilityFromInputEnabled();
|
||||
UpdateInputMode(false, false);
|
||||
}
|
||||
|
||||
void SImGuiWidget::SetVisibilityFromInputEnabled()
|
||||
{
|
||||
// If we don't use input disable hit test to make this widget invisible for cursors hit detection.
|
||||
@ -479,12 +472,14 @@ void SImGuiWidget::OnDebugDraw()
|
||||
bool bDebug = CVars::DebugWidget.GetValueOnGameThread() > 0;
|
||||
if (bDebug)
|
||||
{
|
||||
ImGui::SetNextWindowSize(ImVec2(380, 340), ImGuiSetCond_Once);
|
||||
ImGui::SetNextWindowSize(ImVec2(380, 360), ImGuiSetCond_Once);
|
||||
if (ImGui::Begin("ImGui Widget Debug", &bDebug))
|
||||
{
|
||||
ImGui::Columns(2, nullptr, false);
|
||||
|
||||
TwoColumns::Value("Context Index", ContextIndex);
|
||||
FImGuiContextProxy* ContextProxy = ModuleManager->GetContextManager().GetContextProxy(ContextIndex);
|
||||
TwoColumns::Value("Context Name", ContextProxy ? *ContextProxy->GetName() : TEXT("< Null >"));
|
||||
TwoColumns::Value("Game Viewport", *GameViewport->GetName());
|
||||
|
||||
ImGui::Separator();
|
||||
|
@ -19,6 +19,7 @@ public:
|
||||
SLATE_BEGIN_ARGS(SImGuiWidget)
|
||||
{}
|
||||
SLATE_ARGUMENT(FImGuiModuleManager*, ModuleManager)
|
||||
SLATE_ARGUMENT(UGameViewportClient*, GameViewport)
|
||||
SLATE_ARGUMENT(int32, ContextIndex)
|
||||
SLATE_END_ARGS()
|
||||
|
||||
@ -35,12 +36,8 @@ public:
|
||||
// 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);
|
||||
// Detach widget from viewport assigned during construction (effectively allowing to dispose this widget).
|
||||
void Detach();
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
// SWidget overrides
|
||||
@ -92,8 +89,6 @@ private:
|
||||
|
||||
bool IgnoreKeyEvent(const FKeyEvent& KeyEvent) const;
|
||||
|
||||
void ResetInputState();
|
||||
|
||||
// Update visibility based on input enabled state.
|
||||
void SetVisibilityFromInputEnabled();
|
||||
|
||||
|
24
Source/ImGui/Private/Utilities/WorldContext.cpp
Normal file
24
Source/ImGui/Private/Utilities/WorldContext.cpp
Normal file
@ -0,0 +1,24 @@
|
||||
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
|
||||
|
||||
#include "ImGuiPrivatePCH.h"
|
||||
|
||||
#include "WorldContext.h"
|
||||
|
||||
|
||||
namespace Utilities
|
||||
{
|
||||
const FWorldContext* GetWorldContextFromNetMode(ENetMode NetMode)
|
||||
{
|
||||
checkf(GEngine, TEXT("GEngine required to get list of worlds."));
|
||||
|
||||
for (const FWorldContext& WorldContext : GEngine->GetWorldContexts())
|
||||
{
|
||||
if (WorldContext.World() && WorldContext.World()->GetNetMode() == NetMode)
|
||||
{
|
||||
return &WorldContext;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
}
|
41
Source/ImGui/Private/Utilities/WorldContext.h
Normal file
41
Source/ImGui/Private/Utilities/WorldContext.h
Normal file
@ -0,0 +1,41 @@
|
||||
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Core.h>
|
||||
#include <Engine.h>
|
||||
|
||||
|
||||
// Utilities helping to get a World Context.
|
||||
|
||||
namespace Utilities
|
||||
{
|
||||
template<typename T>
|
||||
FORCEINLINE const FWorldContext* GetWorldContext(const TWeakObjectPtr<T>& Obj)
|
||||
{
|
||||
return Obj.IsValid() ? GetWorldContext(*Obj.Get()) : nullptr;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
FORCEINLINE const FWorldContext* GetWorldContext(const T* Obj)
|
||||
{
|
||||
return Obj ? GetWorldContext(*Obj) : nullptr;
|
||||
}
|
||||
|
||||
FORCEINLINE const FWorldContext* GetWorldContext(const UGameInstance& GameInstance)
|
||||
{
|
||||
return GameInstance.GetWorldContext();
|
||||
}
|
||||
|
||||
FORCEINLINE const FWorldContext* GetWorldContext(const UGameViewportClient& GameViewportClient)
|
||||
{
|
||||
return GetWorldContext(GameViewportClient.GetGameInstance());
|
||||
}
|
||||
|
||||
FORCEINLINE const FWorldContext* GetWorldContext(const UWorld& World)
|
||||
{
|
||||
return GetWorldContext(World.GetGameInstance());
|
||||
}
|
||||
|
||||
const FWorldContext* GetWorldContextFromNetMode(ENetMode NetMode);
|
||||
}
|
@ -2,8 +2,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Core.h>
|
||||
#include <Engine.h>
|
||||
#include "Utilities/WorldContext.h"
|
||||
|
||||
|
||||
// Utilities mapping worlds to indices that we use to identify ImGui contexts.
|
||||
@ -12,51 +11,34 @@
|
||||
|
||||
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;
|
||||
|
||||
// Standalone context index.
|
||||
static constexpr int32 STANDALONE_GAME_CONTEXT_INDEX = 0;
|
||||
|
||||
#if WITH_EDITOR
|
||||
|
||||
template<typename T>
|
||||
FORCEINLINE int32 GetWorldContextIndex(const TWeakObjectPtr<T>& Obj)
|
||||
{
|
||||
return Obj.IsValid() ? GetWorldContextIndex(*Obj.Get()) : INVALID_CONTEXT_INDEX;
|
||||
}
|
||||
// Editor context index.
|
||||
static constexpr int32 EDITOR_CONTEXT_INDEX = 0;
|
||||
|
||||
template<typename T>
|
||||
FORCEINLINE int32 GetWorldContextIndex(const T* Obj)
|
||||
FORCEINLINE int32 GetWorldContextIndex(const T& Obj)
|
||||
{
|
||||
return Obj ? GetWorldContextIndex(*Obj) : INVALID_CONTEXT_INDEX;
|
||||
const FWorldContext* WorldContext = GetWorldContext(Obj);
|
||||
return WorldContext ? GetWorldContextIndex(*WorldContext) : 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 standalone game (WorldType = Game) we have only one context with index 0 (see GAME_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());
|
||||
return WorldContext.WorldType == EWorldType::PIE ? FMath::Max(WorldContext.PIEInstance, 1) : STANDALONE_GAME_CONTEXT_INDEX;
|
||||
}
|
||||
|
||||
#else
|
||||
@ -65,7 +47,7 @@ namespace Utilities
|
||||
constexpr int32 GetWorldContextIndex(const T&)
|
||||
{
|
||||
// The only option is standalone game with one context.
|
||||
return DEFAULT_CONTEXT_INDEX;
|
||||
return STANDALONE_GAME_CONTEXT_INDEX;
|
||||
}
|
||||
|
||||
#endif // #if WITH_EDITOR
|
||||
|
Loading…
Reference in New Issue
Block a user