Changed interface to register ImGui debug delegates:

- Added new FImGuiDelegates interface for ImGui debug delegates.
- Added FImGuiDelegatesContainer for ImGui delegates.
- Added code preserving delegates during hot-reloading and moving them to a new module.
- Depreciated old FImGuiModule delegates interface and FImGuiDelegateHandle.
- Delegates registered with depreciated interface are redirected and get benefit of being preserved during hot-reloading. This can be controlled with IMGUI_REDIRECT_OBSOLETE_DELEGATES.
- Added IMGUI_WITH_OBSOLETE_DELEGATES allowing to strip depreciated interface from builds.
- Modified context manager and context proxy to work with FImGuiDelegatesContainer.
This commit is contained in:
Sebastian 2019-04-10 20:19:11 +01:00
parent 2b5d871609
commit 867a34e640
13 changed files with 253 additions and 17 deletions

View File

@ -5,6 +5,13 @@ Versions marked as 'unofficial' are labelled only for the needs of this changelo
Change History Change History
-------------- --------------
Version: 1.15 (2019/04)
- Added new FImGuiDelegates interface for ImGui debug delegates.
- Added code preserving delegates during hot-reloading and moving them to a new module.
- DEPRECIATED old FImGuiModule delegates interface and FImGuiDelegateHandle.
- Delegates registered with depreciated interface are redirected and get benefit of being preserved during hot-reloading. This can be controlled with IMGUI_REDIRECT_OBSOLETE_DELEGATES.
- Added IMGUI_WITH_OBSOLETE_DELEGATES allowing to strip depreciated interface from builds (that interface will be officially removed in one of later releases).
Version: 1.14 (2019/03) Version: 1.14 (2019/03)
- Added SImGuiLayout to resets layout for SImGuiWidget. - Added SImGuiLayout to resets layout for SImGuiWidget.
- Refactored rendering in SImGuiWidget to take advantage of layout reset. - Refactored rendering in SImGuiWidget to take advantage of layout reset.

View File

@ -1,7 +1,7 @@
{ {
"FileVersion": 3, "FileVersion": 3,
"Version": 1, "Version": 1,
"VersionName": "1.14", "VersionName": "1.15",
"FriendlyName": "ImGui", "FriendlyName": "ImGui",
"Description": "", "Description": "",
"Category": "Debug", "Category": "Debug",

View File

@ -4,6 +4,7 @@
#include "ImGuiContextManager.h" #include "ImGuiContextManager.h"
#include "ImGuiDelegatesContainer.h"
#include "ImGuiImplementation.h" #include "ImGuiImplementation.h"
#include "Utilities/ScopeGuards.h" #include "Utilities/ScopeGuards.h"
#include "Utilities/WorldContext.h" #include "Utilities/WorldContext.h"
@ -89,6 +90,11 @@ void FImGuiContextManager::Tick(float DeltaSeconds)
{ {
ContextData.ContextProxy.Tick(DeltaSeconds); ContextData.ContextProxy.Tick(DeltaSeconds);
} }
else
{
// Clear to make sure that we don't store objects registered for world that is no longer valid.
FImGuiDelegatesContainer::Get().OnWorldDebug(Pair.Key).Clear();
}
} }
} }

View File

@ -72,7 +72,7 @@ private:
{ {
FContextData(const FString& ContextName, int32 ContextIndex, FSimpleMulticastDelegate& SharedDrawEvent, ImFontAtlas& FontAtlas, int32 InPIEInstance = -1) FContextData(const FString& ContextName, int32 ContextIndex, FSimpleMulticastDelegate& SharedDrawEvent, ImFontAtlas& FontAtlas, int32 InPIEInstance = -1)
: PIEInstance(InPIEInstance) : PIEInstance(InPIEInstance)
, ContextProxy(ContextName, &SharedDrawEvent, &FontAtlas) , ContextProxy(ContextName, ContextIndex, &SharedDrawEvent, &FontAtlas)
{ {
} }
@ -87,7 +87,7 @@ private:
struct FContextData struct FContextData
{ {
FContextData(const FString& ContextName, int32 ContextIndex, FSimpleMulticastDelegate& SharedDrawEvent, ImFontAtlas& FontAtlas) FContextData(const FString& ContextName, int32 ContextIndex, FSimpleMulticastDelegate& SharedDrawEvent, ImFontAtlas& FontAtlas)
: ContextProxy(ContextName, &SharedDrawEvent, &FontAtlas) : ContextProxy(ContextName, ContextIndex, &SharedDrawEvent, &FontAtlas)
{ {
} }

View File

@ -4,6 +4,7 @@
#include "ImGuiContextProxy.h" #include "ImGuiContextProxy.h"
#include "ImGuiDelegatesContainer.h"
#include "ImGuiImplementation.h" #include "ImGuiImplementation.h"
#include "ImGuiInteroperability.h" #include "ImGuiInteroperability.h"
@ -52,8 +53,9 @@ FImGuiContextProxy::FImGuiContextPtr::~FImGuiContextPtr()
} }
} }
FImGuiContextProxy::FImGuiContextProxy(const FString& InName, FSimpleMulticastDelegate* InSharedDrawEvent, ImFontAtlas* InFontAtlas) FImGuiContextProxy::FImGuiContextProxy(const FString& InName, int32 InContextIndex, FSimpleMulticastDelegate* InSharedDrawEvent, ImFontAtlas* InFontAtlas)
: Name(InName) : Name(InName)
, ContextIndex(InContextIndex)
, SharedDrawEvent(InSharedDrawEvent) , SharedDrawEvent(InSharedDrawEvent)
, IniFilename(TCHAR_TO_ANSI(*GetIniFile(InName))) , IniFilename(TCHAR_TO_ANSI(*GetIniFile(InName)))
{ {
@ -91,22 +93,13 @@ void FImGuiContextProxy::Draw()
// Broadcast draw event to allow listeners to draw their controls to this context. // Broadcast draw event to allow listeners to draw their controls to this context.
#if DRAW_EVENTS_ORDER_WORLD_BEFORE_MULTI_CONTEXT #if DRAW_EVENTS_ORDER_WORLD_BEFORE_MULTI_CONTEXT
if (DrawEvent.IsBound()) BroadcastWorldTick();
{
DrawEvent.Broadcast();
}
#endif // DRAW_EVENTS_ORDER_WORLD_BEFORE_MULTI_CONTEXT #endif // DRAW_EVENTS_ORDER_WORLD_BEFORE_MULTI_CONTEXT
if (SharedDrawEvent && SharedDrawEvent->IsBound()) BroadcastMultiContextTick();
{
SharedDrawEvent->Broadcast();
}
#if !DRAW_EVENTS_ORDER_WORLD_BEFORE_MULTI_CONTEXT #if !DRAW_EVENTS_ORDER_WORLD_BEFORE_MULTI_CONTEXT
if (DrawEvent.IsBound()) BroadcastWorldTick();
{
DrawEvent.Broadcast();
}
#endif // !DRAW_EVENTS_ORDER_WORLD_BEFORE_MULTI_CONTEXT #endif // !DRAW_EVENTS_ORDER_WORLD_BEFORE_MULTI_CONTEXT
} }
} }
@ -192,3 +185,34 @@ void FImGuiContextProxy::UpdateDrawData(ImDrawData* DrawData)
DrawLists.Empty(); DrawLists.Empty();
} }
} }
void FImGuiContextProxy::BroadcastWorldTick()
{
if (DrawEvent.IsBound())
{
DrawEvent.Broadcast();
}
if (ContextIndex != Utilities::INVALID_CONTEXT_INDEX)
{
FSimpleMulticastDelegate& WorldDebugEvent = FImGuiDelegatesContainer::Get().OnWorldDebug(ContextIndex);
if (WorldDebugEvent.IsBound())
{
WorldDebugEvent.Broadcast();
}
}
}
void FImGuiContextProxy::BroadcastMultiContextTick()
{
if (SharedDrawEvent && SharedDrawEvent->IsBound())
{
SharedDrawEvent->Broadcast();
}
FSimpleMulticastDelegate& MultiContextDebugEvent = FImGuiDelegatesContainer::Get().OnMultiContextDebug();
if (MultiContextDebugEvent.IsBound())
{
MultiContextDebugEvent.Broadcast();
}
}

View File

@ -4,6 +4,8 @@
#include "ImGuiDrawData.h" #include "ImGuiDrawData.h"
#include "Utilities/WorldContextIndex.h"
#include <ICursor.h> #include <ICursor.h>
#include <imgui.h> #include <imgui.h>
@ -41,7 +43,7 @@ class FImGuiContextProxy
public: public:
FImGuiContextProxy(const FString& Name, FSimpleMulticastDelegate* InSharedDrawEvent, ImFontAtlas* InFontAtlas); FImGuiContextProxy(const FString& Name, int32 InContextIndex, FSimpleMulticastDelegate* InSharedDrawEvent, ImFontAtlas* InFontAtlas);
FImGuiContextProxy(const FImGuiContextProxy&) = delete; FImGuiContextProxy(const FImGuiContextProxy&) = delete;
FImGuiContextProxy& operator=(const FImGuiContextProxy&) = delete; FImGuiContextProxy& operator=(const FImGuiContextProxy&) = delete;
@ -96,6 +98,9 @@ private:
void UpdateDrawData(ImDrawData* DrawData); void UpdateDrawData(ImDrawData* DrawData);
void BroadcastWorldTick();
void BroadcastMultiContextTick();
FImGuiContextPtr Context; FImGuiContextPtr Context;
FVector2D DisplaySize = FVector2D::ZeroVector; FVector2D DisplaySize = FVector2D::ZeroVector;
@ -115,6 +120,8 @@ private:
TArray<FImGuiDrawList> DrawLists; TArray<FImGuiDrawList> DrawLists;
int32 ContextIndex = Utilities::INVALID_CONTEXT_INDEX;
FString Name; FString Name;
std::string IniFilename; std::string IniFilename;
}; };

View File

@ -0,0 +1,22 @@
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
#include "ImGuiPrivatePCH.h"
#include "ImGuiDelegates.h"
#include "ImGuiDelegatesContainer.h"
FSimpleMulticastDelegate& FImGuiDelegates::OnWorldDebug()
{
return OnWorldDebug(GWorld);
}
FSimpleMulticastDelegate& FImGuiDelegates::OnWorldDebug(UWorld* World)
{
return FImGuiDelegatesContainer::Get().OnWorldDebug(World);
}
FSimpleMulticastDelegate& FImGuiDelegates::OnMultiContextDebug()
{
return FImGuiDelegatesContainer::Get().OnMultiContextDebug();
}

View File

@ -0,0 +1,37 @@
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
#include "ImGuiPrivatePCH.h"
#include "ImGuiDelegatesContainer.h"
#include "Utilities/WorldContextIndex.h"
FImGuiDelegatesContainer FImGuiDelegatesContainer::DefaultInstance;
FImGuiDelegatesContainer* FImGuiDelegatesContainer::InstancePtr = &FImGuiDelegatesContainer::DefaultInstance;
void FImGuiDelegatesContainer::MoveContainer(FImGuiDelegatesContainer& Dst)
{
// Only move data if pointer points to default instance, otherwise our data has already been moved and we only
// keep pointer to a more recent version.
if (InstancePtr == &DefaultInstance)
{
Dst = MoveTemp(DefaultInstance);
DefaultInstance.Clear();
}
// Update pointer to the most recent version.
InstancePtr = &Dst;
}
FSimpleMulticastDelegate& FImGuiDelegatesContainer::OnWorldDebug(UWorld* World)
{
return OnWorldDebug(Utilities::GetWorldContextIndex(*World));
}
void FImGuiDelegatesContainer::Clear()
{
WorldDelegates.Empty();
MultiContextDelegate.Clear();
}

View File

@ -0,0 +1,40 @@
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
#pragma once
#include <Containers/Map.h>
#include <Delegates/Delegate.h>
struct FImGuiDelegatesContainer
{
public:
// Get the current instance (can change during hot-reloading).
static FImGuiDelegatesContainer& Get() { return *InstancePtr; }
// If this is an active container move its data to a destination and redirect all future calls to that instance.
static void MoveContainer(FImGuiDelegatesContainer& Dst);
// Get delegate to ImGui world debug event from known world instance.
FSimpleMulticastDelegate& OnWorldDebug(UWorld* World);
// Get delegate to ImGui world debug event from known context index.
FSimpleMulticastDelegate& OnWorldDebug(int32 ContextIndex) { return WorldDelegates.FindOrAdd(ContextIndex); }
// Get delegate to ImGui multi-context debug event.
FSimpleMulticastDelegate& OnMultiContextDebug() { return MultiContextDelegate; }
private:
void Clear();
TMap<int32, FSimpleMulticastDelegate> WorldDelegates;
FSimpleMulticastDelegate MultiContextDelegate;
// Default container instance.
static FImGuiDelegatesContainer DefaultInstance;
// Pointer to the container instance that can be overwritten during hot-reloading.
static FImGuiDelegatesContainer* InstancePtr;
};

View File

@ -4,6 +4,7 @@
#include "ImGuiModuleManager.h" #include "ImGuiModuleManager.h"
#include "ImGuiDelegatesContainer.h"
#include "ImGuiTextureHandle.h" #include "ImGuiTextureHandle.h"
#include "TextureManager.h" #include "TextureManager.h"
#include "Utilities/WorldContext.h" #include "Utilities/WorldContext.h"
@ -16,6 +17,8 @@
#include <IPluginManager.h> #include <IPluginManager.h>
#define IMGUI_REDIRECT_OBSOLETE_DELEGATES 1
#define LOCTEXT_NAMESPACE "FImGuiModule" #define LOCTEXT_NAMESPACE "FImGuiModule"
@ -38,18 +41,29 @@ static FImGuiModuleManager* ImGuiModuleManager = nullptr;
static FImGuiEditor* ImGuiEditor = nullptr; static FImGuiEditor* ImGuiEditor = nullptr;
#endif #endif
#if IMGUI_WITH_OBSOLETE_DELEGATES
#if WITH_EDITOR #if WITH_EDITOR
FImGuiDelegateHandle FImGuiModule::AddEditorImGuiDelegate(const FImGuiDelegate& Delegate) FImGuiDelegateHandle FImGuiModule::AddEditorImGuiDelegate(const FImGuiDelegate& Delegate)
{ {
#if IMGUI_REDIRECT_OBSOLETE_DELEGATES
return { FImGuiDelegatesContainer::Get().OnWorldDebug(Utilities::EDITOR_CONTEXT_INDEX).Add(Delegate),
EDelegateCategory::Default, Utilities::EDITOR_CONTEXT_INDEX };
#else
checkf(ImGuiModuleManager, TEXT("Null pointer to internal module implementation. Is module available?")); checkf(ImGuiModuleManager, TEXT("Null pointer to internal module implementation. Is module available?"));
return { ImGuiModuleManager->GetContextManager().GetEditorContextProxy().OnDraw().Add(Delegate), return { ImGuiModuleManager->GetContextManager().GetEditorContextProxy().OnDraw().Add(Delegate),
EDelegateCategory::Default, Utilities::EDITOR_CONTEXT_INDEX }; EDelegateCategory::Default, Utilities::EDITOR_CONTEXT_INDEX };
#endif // IMGUI_REDIRECT_OBSOLETE_DELEGATES
} }
#endif #endif
FImGuiDelegateHandle FImGuiModule::AddWorldImGuiDelegate(const FImGuiDelegate& Delegate) FImGuiDelegateHandle FImGuiModule::AddWorldImGuiDelegate(const FImGuiDelegate& Delegate)
{ {
#if IMGUI_REDIRECT_OBSOLETE_DELEGATES
const int32 ContextIndex = Utilities::GetWorldContextIndex((UWorld*)GWorld);
return { FImGuiDelegatesContainer::Get().OnWorldDebug(ContextIndex).Add(Delegate), EDelegateCategory::Default, ContextIndex };
#else
checkf(ImGuiModuleManager, TEXT("Null pointer to internal module implementation. Is module available?")); checkf(ImGuiModuleManager, TEXT("Null pointer to internal module implementation. Is module available?"));
#if WITH_EDITOR #if WITH_EDITOR
@ -71,17 +85,32 @@ FImGuiDelegateHandle FImGuiModule::AddWorldImGuiDelegate(const FImGuiDelegate& D
#endif #endif
return{ Proxy.OnDraw().Add(Delegate), EDelegateCategory::Default, Index }; return{ Proxy.OnDraw().Add(Delegate), EDelegateCategory::Default, Index };
#endif // IMGUI_REDIRECT_OBSOLETE_DELEGATES
} }
FImGuiDelegateHandle FImGuiModule::AddMultiContextImGuiDelegate(const FImGuiDelegate& Delegate) FImGuiDelegateHandle FImGuiModule::AddMultiContextImGuiDelegate(const FImGuiDelegate& Delegate)
{ {
#if IMGUI_REDIRECT_OBSOLETE_DELEGATES
return { FImGuiDelegatesContainer::Get().OnMultiContextDebug().Add(Delegate), EDelegateCategory::MultiContext };
#else
checkf(ImGuiModuleManager, TEXT("Null pointer to internal module implementation. Is module available?")); checkf(ImGuiModuleManager, TEXT("Null pointer to internal module implementation. Is module available?"));
return { ImGuiModuleManager->GetContextManager().OnDrawMultiContext().Add(Delegate), EDelegateCategory::MultiContext }; return { ImGuiModuleManager->GetContextManager().OnDrawMultiContext().Add(Delegate), EDelegateCategory::MultiContext };
#endif
} }
void FImGuiModule::RemoveImGuiDelegate(const FImGuiDelegateHandle& Handle) void FImGuiModule::RemoveImGuiDelegate(const FImGuiDelegateHandle& Handle)
{ {
#if IMGUI_REDIRECT_OBSOLETE_DELEGATES
if (Handle.Category == EDelegateCategory::MultiContext)
{
FImGuiDelegatesContainer::Get().OnMultiContextDebug().Remove(Handle.Handle);
}
else
{
FImGuiDelegatesContainer::Get().OnWorldDebug(Handle.Index).Remove(Handle.Handle);
}
#else
if (ImGuiModuleManager) if (ImGuiModuleManager)
{ {
if (Handle.Category == EDelegateCategory::MultiContext) if (Handle.Category == EDelegateCategory::MultiContext)
@ -93,8 +122,11 @@ void FImGuiModule::RemoveImGuiDelegate(const FImGuiDelegateHandle& Handle)
Proxy->OnDraw().Remove(Handle.Handle); Proxy->OnDraw().Remove(Handle.Handle);
} }
} }
#endif
} }
#endif // IMGUI_WITH_OBSOLETE_DELEGATES
FImGuiTextureHandle FImGuiModule::FindTextureHandle(const FName& Name) FImGuiTextureHandle FImGuiModule::FindTextureHandle(const FName& Name)
{ {
const TextureIndex Index = ImGuiModuleManager->GetTextureManager().FindTextureIndex(Name); const TextureIndex Index = ImGuiModuleManager->GetTextureManager().FindTextureIndex(Name);
@ -154,6 +186,10 @@ void FImGuiModule::ShutdownModule()
// deleted. This can cause troubles after hot-reload when code in other modules calls ImGui interface functions // deleted. This can cause troubles after hot-reload when code in other modules calls ImGui interface functions
// which are statically bound to the obsolete module. To keep ImGui code functional we can redirect context handle // which are statically bound to the obsolete module. To keep ImGui code functional we can redirect context handle
// to point to the new module. // to point to the new module.
// When shutting down during hot-reloading, we might want to rewire handles used in statically bound functions
// or move data to a new module.
FModuleManager::Get().OnModulesChanged().AddLambda([this] (FName Name, EModuleChangeReason Reason) FModuleManager::Get().OnModulesChanged().AddLambda([this] (FName Name, EModuleChangeReason Reason)
{ {
if (Reason == EModuleChangeReason::ModuleLoaded && Name == "ImGui") if (Reason == EModuleChangeReason::ModuleLoaded && Name == "ImGui")
@ -161,8 +197,11 @@ void FImGuiModule::ShutdownModule()
FImGuiModule& LoadedModule = FImGuiModule::Get(); FImGuiModule& LoadedModule = FImGuiModule::Get();
if (&LoadedModule != this) if (&LoadedModule != this)
{ {
// Statically bound functions will be still made to the obsolete module so we need to
ImGuiImplementation::SetImGuiContextHandle(LoadedModule.GetImGuiContextHandle()); ImGuiImplementation::SetImGuiContextHandle(LoadedModule.GetImGuiContextHandle());
FImGuiDelegatesContainer::MoveContainer(LoadedModule.GetDelegatesContainer());
if (bMoveProperties) if (bMoveProperties)
{ {
bMoveProperties = false; bMoveProperties = false;
@ -184,6 +223,11 @@ ImGuiContext** FImGuiModule::GetImGuiContextHandle()
{ {
return ImGuiImplementation::GetImGuiContextHandle(); return ImGuiImplementation::GetImGuiContextHandle();
} }
FImGuiDelegatesContainer& FImGuiModule::GetDelegatesContainer()
{
return FImGuiDelegatesContainer::Get();
}
#endif #endif
FImGuiModuleProperties& FImGuiModule::GetProperties() FImGuiModuleProperties& FImGuiModule::GetProperties()

View File

@ -56,6 +56,11 @@ namespace Utilities
return (World.WorldType == EWorldType::Editor) ? EDITOR_CONTEXT_INDEX : GetWorldContextIndex(World.GetGameInstance()); return (World.WorldType == EWorldType::Editor) ? EDITOR_CONTEXT_INDEX : GetWorldContextIndex(World.GetGameInstance());
} }
FORCEINLINE int32 GetWorldContextIndex(const UWorld* World)
{
return World ? GetWorldContextIndex(*World) : INVALID_CONTEXT_INDEX;
}
#else #else
template<typename T> template<typename T>

View File

@ -5,6 +5,43 @@
#include <Core.h> #include <Core.h>
/**
* Delegates to ImGui debug events called. World delegates are called once per frame to draw debug for owning world
* and are automatically cleared when that world becomes invalid. Multi-context delegates are called once for every
* world that needs to be debugged.
* In engine version 4.18 or later delegates are called during OnWorldPostActorTick event while in older versions
* 4.18 during OnWorldTickStart event. Calling behaviours can be changed in build configuration.
*/
class IMGUI_API FImGuiDelegates
{
public:
/**
* Get a delegate to ImGui world debug event for current world (GWorld).
* @returns Simple multicast delegate to debug events called once per frame to debug current world
*/
static FSimpleMulticastDelegate& OnWorldDebug();
/**
* Get a delegate to ImGui world debug event for given world.
* @param World - World for which we need a delegate
* @returns Simple multicast delegate to debug events called once per frame to debug given world
*/
static FSimpleMulticastDelegate& OnWorldDebug(UWorld* World);
/**
* Get a delegate to ImGui multi-context debug event.
* @returns Simple multicast delegate to debug events called once per frame for every world to debug
*/
static FSimpleMulticastDelegate& OnMultiContextDebug();
};
/** Enable to support legacy ImGui delegates API. */
#define IMGUI_WITH_OBSOLETE_DELEGATES 1
#if IMGUI_WITH_OBSOLETE_DELEGATES
/** Delegate that allows to subscribe for ImGui events. */ /** Delegate that allows to subscribe for ImGui events. */
typedef FSimpleMulticastDelegate::FDelegate FImGuiDelegate; typedef FSimpleMulticastDelegate::FDelegate FImGuiDelegate;
@ -53,3 +90,5 @@ private:
friend class FImGuiModule; friend class FImGuiModule;
}; };
#endif // IMGUI_WITH_OBSOLETE_DELEGATES

View File

@ -34,6 +34,8 @@ public:
return FModuleManager::Get().IsModuleLoaded("ImGui"); return FModuleManager::Get().IsModuleLoaded("ImGui");
} }
#if IMGUI_WITH_OBSOLETE_DELEGATES
#if WITH_EDITOR #if WITH_EDITOR
/** /**
* Add a delegate called at the end of editor debug frame to draw debug controls in its ImGui context, creating * Add a delegate called at the end of editor debug frame to draw debug controls in its ImGui context, creating
@ -71,6 +73,8 @@ public:
*/ */
virtual void RemoveImGuiDelegate(const FImGuiDelegateHandle& Handle); virtual void RemoveImGuiDelegate(const FImGuiDelegateHandle& Handle);
#endif // #if IMGUI_WITH_OBSOLETE_DELEGATES
/** /**
* If it exists, get a handle to the texture with given resource name. * If it exists, get a handle to the texture with given resource name.
* *
@ -165,5 +169,6 @@ public:
#if WITH_EDITOR #if WITH_EDITOR
virtual void SetProperties(const FImGuiModuleProperties& Properties); virtual void SetProperties(const FImGuiModuleProperties& Properties);
virtual struct ImGuiContext** GetImGuiContextHandle(); virtual struct ImGuiContext** GetImGuiContextHandle();
virtual struct FImGuiDelegatesContainer& GetDelegatesContainer();
#endif #endif
}; };