diff --git a/Source/ImGui/Private/ImGuiContextManager.cpp b/Source/ImGui/Private/ImGuiContextManager.cpp index 8958e2d..6145c5e 100644 --- a/Source/ImGui/Private/ImGuiContextManager.cpp +++ b/Source/ImGui/Private/ImGuiContextManager.cpp @@ -72,7 +72,7 @@ void FImGuiContextManager::Tick(float DeltaSeconds) if (ContextData.CanTick()) { ContextData.ContextProxy.SetAsCurrent(); - ContextData.ContextProxy.Tick(DeltaSeconds); + ContextData.ContextProxy.Tick(DeltaSeconds, &DrawMultiContextEvent); } } } @@ -105,7 +105,7 @@ FImGuiContextManager::FContextData& FImGuiContextManager::GetStandaloneWorldCont } #endif // !WITH_EDITOR -FImGuiContextManager::FContextData& FImGuiContextManager::GetWorldContextData(const UWorld& World) +FImGuiContextManager::FContextData& FImGuiContextManager::GetWorldContextData(const UWorld& World, int32* OutIndex) { using namespace Utilities; @@ -140,5 +140,9 @@ FImGuiContextManager::FContextData& FImGuiContextManager::GetWorldContextData(co } #endif + if (OutIndex) + { + *OutIndex = Index; + } return *Data; } diff --git a/Source/ImGui/Private/ImGuiContextManager.h b/Source/ImGui/Private/ImGuiContextManager.h index 8757ee3..68c888f 100644 --- a/Source/ImGui/Private/ImGuiContextManager.h +++ b/Source/ImGui/Private/ImGuiContextManager.h @@ -34,6 +34,9 @@ public: // Get or create ImGui context proxy for given world. FORCEINLINE FImGuiContextProxy& GetWorldContextProxy(const UWorld& World) { return GetWorldContextData(World).ContextProxy; } + // Get or create ImGui context proxy for given world. Additionally get context index for that proxy. + FORCEINLINE FImGuiContextProxy& GetWorldContextProxy(const UWorld& World, int32& OutContextIndex) { return GetWorldContextData(World, &OutContextIndex).ContextProxy; } + // Get context proxy by index, or null if context with that index doesn't exist. FORCEINLINE FImGuiContextProxy* GetContextProxy(int32 ContextIndex) { @@ -41,6 +44,10 @@ public: return Data ? &(Data->ContextProxy) : nullptr; } + // Delegate called for all contexts in manager, right after calling context specific draw event. Allows listeners + // draw the same content to multiple contexts. + FSimpleMulticastDelegate& OnDrawMultiContext() { return DrawMultiContextEvent; } + void Tick(float DeltaSeconds); private: @@ -87,9 +94,11 @@ private: FContextData& GetStandaloneWorldContextData(); #endif - FContextData& GetWorldContextData(const UWorld& World); + FContextData& GetWorldContextData(const UWorld& World, int32* OutContextIndex = nullptr); TMap Contexts; FImGuiDemo ImGuiDemo; + + FSimpleMulticastDelegate DrawMultiContextEvent; }; diff --git a/Source/ImGui/Private/ImGuiContextProxy.cpp b/Source/ImGui/Private/ImGuiContextProxy.cpp index 4b824df..3fc61aa 100644 --- a/Source/ImGui/Private/ImGuiContextProxy.cpp +++ b/Source/ImGui/Private/ImGuiContextProxy.cpp @@ -117,7 +117,7 @@ FImGuiContextProxy::~FImGuiContextProxy() } } -void FImGuiContextProxy::Tick(float DeltaSeconds) +void FImGuiContextProxy::Tick(float DeltaSeconds, FSimpleMulticastDelegate* SharedDrawEvent) { if (bIsFrameStarted) { @@ -126,6 +126,10 @@ void FImGuiContextProxy::Tick(float DeltaSeconds) { DrawEvent.Broadcast(); } + if (SharedDrawEvent && SharedDrawEvent->IsBound()) + { + SharedDrawEvent->Broadcast(); + } // Ending frame will produce render output that we capture and store for later use. This also puts context to // state in which it does not allow to draw controls, so we want to immediately start a new frame. diff --git a/Source/ImGui/Private/ImGuiContextProxy.h b/Source/ImGui/Private/ImGuiContextProxy.h index 77cdc8a..b07534a 100644 --- a/Source/ImGui/Private/ImGuiContextProxy.h +++ b/Source/ImGui/Private/ImGuiContextProxy.h @@ -53,7 +53,8 @@ public: FSimpleMulticastDelegate& OnDraw() { return DrawEvent; } // Tick to advance context to the next frame. - void Tick(float DeltaSeconds); + // @param SharedDrawEvent - Shared draw event provided from outside to be called right after context own event + void Tick(float DeltaSeconds, FSimpleMulticastDelegate* SharedDrawEvent = nullptr); private: diff --git a/Source/ImGui/Private/ImGuiModule.cpp b/Source/ImGui/Private/ImGuiModule.cpp index e9da305..9adc679 100644 --- a/Source/ImGui/Private/ImGuiModule.cpp +++ b/Source/ImGui/Private/ImGuiModule.cpp @@ -3,6 +3,8 @@ #include "ImGuiPrivatePCH.h" #include "ImGuiModuleManager.h" +#include "Utilities/WorldContext.h" +#include "Utilities/WorldContextIndex.h" #include @@ -10,8 +12,77 @@ #define LOCTEXT_NAMESPACE "FImGuiModule" +struct EDelegateCategory +{ + enum + { + // Default per-context draw events. + Default, + + // Multi-context draw event defined in context manager. + MultiContext + }; +}; + static FImGuiModuleManager* ModuleManager = nullptr; +#if WITH_EDITOR +FImGuiDelegateHandle FImGuiModule::AddEditorImGuiDelegate(const FImGuiDelegate& Delegate) +{ + checkf(ModuleManager, TEXT("Null pointer to internal module implementation. Is module available?")); + + return { ModuleManager->GetContextManager().GetEditorContextProxy().OnDraw().Add(Delegate), + EDelegateCategory::Default, Utilities::EDITOR_CONTEXT_INDEX }; +} +#endif + +FImGuiDelegateHandle FImGuiModule::AddWorldImGuiDelegate(const FImGuiDelegate& Delegate) +{ + checkf(ModuleManager, TEXT("Null pointer to internal module implementation. Is module available?")); + +#if WITH_EDITOR + checkf(GEngine, TEXT("Null GEngine. AddWorldImGuiDelegate should be only called with GEngine initialized.")); + + const FWorldContext* WorldContext = Utilities::GetWorldContext(GEngine->GameViewport); + if (!WorldContext) + { + WorldContext = Utilities::GetWorldContextFromNetMode(ENetMode::NM_DedicatedServer); + } + + checkf(WorldContext, TEXT("Couldn't find current world. AddWorldImGuiDelegate should be only called from a valid world.")); + + int32 Index; + FImGuiContextProxy& Proxy = ModuleManager->GetContextManager().GetWorldContextProxy(*WorldContext->World(), Index); +#else + const int32 Index = Utilities::STANDALONE_GAME_CONTEXT_INDEX; + FImGuiContextProxy& Proxy = ModuleManager->GetContextManager().GetWorldContextProxy(); +#endif + + return{ Proxy.OnDraw().Add(Delegate), EDelegateCategory::Default, Index }; +} + +FImGuiDelegateHandle FImGuiModule::AddMultiContextImGuiDelegate(const FImGuiDelegate& Delegate) +{ + checkf(ModuleManager, TEXT("Null pointer to internal module implementation. Is module available?")); + + return { ModuleManager->GetContextManager().OnDrawMultiContext().Add(Delegate), EDelegateCategory::MultiContext }; +} + +void FImGuiModule::RemoveImGuiDelegate(const FImGuiDelegateHandle& Handle) +{ + if (ModuleManager) + { + if (Handle.Category == EDelegateCategory::MultiContext) + { + ModuleManager->GetContextManager().OnDrawMultiContext().Remove(Handle.Handle); + } + else if (auto* Proxy = ModuleManager->GetContextManager().GetContextProxy(Handle.Index)) + { + Proxy->OnDraw().Remove(Handle.Handle); + } + } +} + void FImGuiModule::StartupModule() { checkf(!ModuleManager, TEXT("Instance of Module Manager already exists. Instance should be created only during module startup.")); @@ -23,12 +94,12 @@ void FImGuiModule::StartupModule() void FImGuiModule::ShutdownModule() { checkf(ModuleManager, TEXT("Null Module Manager. Manager instance should be deleted during module shutdown.")); - + // Before we shutdown we need to delete manager that will do all necessary cleanup. delete ModuleManager; ModuleManager = nullptr; } #undef LOCTEXT_NAMESPACE - + IMPLEMENT_MODULE(FImGuiModule, ImGui) diff --git a/Source/ImGui/Public/ImGuiDelegates.h b/Source/ImGui/Public/ImGuiDelegates.h new file mode 100644 index 0000000..a4514dc --- /dev/null +++ b/Source/ImGui/Public/ImGuiDelegates.h @@ -0,0 +1,55 @@ +// Distributed under the MIT License (MIT) (see accompanying LICENSE file) + +#pragma once + +#include + + +/** Delegate that allows to subscribe for ImGui events. */ +typedef FSimpleMulticastDelegate::FDelegate FImGuiDelegate; + +/** + * Handle to ImGui delegate. Contains additional information locating delegates in different contexts. + */ +class FImGuiDelegateHandle +{ +public: + + FImGuiDelegateHandle() = default; + + bool IsValid() const + { + return Handle.IsValid(); + } + + void Reset() + { + Handle.Reset(); + Index = 0; + } + +private: + + FImGuiDelegateHandle(const FDelegateHandle& InHandle, int32 InCategory, int32 InIndex = 0) + : Handle(InHandle) + , Category(InCategory) + , Index(InIndex) + { + } + + friend bool operator==(const FImGuiDelegateHandle& Lhs, const FImGuiDelegateHandle& Rhs) + { + return Lhs.Handle == Rhs.Handle && Lhs.Category == Rhs.Category && Lhs.Index == Rhs.Index; + } + + friend bool operator!=(const FImGuiDelegateHandle& Lhs, const FImGuiDelegateHandle& Rhs) + { + return !(Lhs == Rhs); + } + + FDelegateHandle Handle; + int32 Category = 0; + int32 Index = 0; + + friend class FImGuiModule; +}; diff --git a/Source/ImGui/Public/ImGuiModule.h b/Source/ImGui/Public/ImGuiModule.h index 58cdf01..acb0112 100644 --- a/Source/ImGui/Public/ImGuiModule.h +++ b/Source/ImGui/Public/ImGuiModule.h @@ -2,6 +2,8 @@ #pragma once +#include "ImGuiDelegates.h" + #include @@ -9,6 +11,64 @@ class FImGuiModule : public IModuleInterface { public: + /** + * Singleton-like access to this module's interface. This is just for convenience! + * Beware of calling this during the shutdown phase, though. Your module might have been unloaded already. + * + * @return Returns singleton instance, loading the module on demand if needed + */ + static inline FImGuiModule& Get() + { + return FModuleManager::LoadModuleChecked("ImGui"); + } + + /** + * Checks to see if this module is loaded and ready. It is only valid to call Get() if IsAvailable() returns true. + * + * @return True if the module is loaded and ready to use + */ + static inline bool IsAvailable() + { + return FModuleManager::Get().IsModuleLoaded("ImGui"); + } + +#if WITH_EDITOR + /** + * Add a delegate called at the end of editor debug frame to draw debug controls in its ImGui context, creating + * that context on demand. + * + * @param Delegate - Delegate that we want to add (@see FImGuiDelegate::Create...) + * @returns Returns handle that can be used to remove delegate (@see RemoveImGuiDelegate) + */ + virtual FImGuiDelegateHandle AddEditorImGuiDelegate(const FImGuiDelegate& Delegate); +#endif + + /** + * Add a delegate called at the end of current world debug frame to draw debug controls in its ImGui context, + * creating that context on demand. + * This function will throw if called outside of a world context (i.e. current world cannot be found). + * + * @param Delegate - Delegate that we want to add (@see FImGuiDelegate::Create...) + * @returns Returns handle that can be used to remove delegate (@see RemoveImGuiDelegate) + */ + virtual FImGuiDelegateHandle AddWorldImGuiDelegate(const FImGuiDelegate& Delegate); + + /** + * Add shared delegate called for each ImGui context at the end of debug frame, after calling context specific + * delegate. This delegate will be used for any ImGui context, created before or after it is registered. + * + * @param Delegate - Delegate that we want to add (@see FImGuiDelegate::Create...) + * @returns Returns handle that can be used to remove delegate (@see RemoveImGuiDelegate) + */ + virtual FImGuiDelegateHandle AddMultiContextImGuiDelegate(const FImGuiDelegate& Delegate); + + /** + * Remove delegate added with any version of Add...ImGuiDelegate + * + * @param Handle - Delegate handle that was returned by adding function + */ + virtual void RemoveImGuiDelegate(const FImGuiDelegateHandle& Handle); + /** IModuleInterface implementation */ virtual void StartupModule() override; virtual void ShutdownModule() override;