UnrealImGui/Source/ImGui/Private/ImGuiModule.cpp

315 lines
9.4 KiB
C++

// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
#include "ImGuiModule.h"
#include "ImGuiDelegatesContainer.h"
#include "ImGuiModuleManager.h"
#include "ImGuiInputPreprocessor.h"
#include "TextureManager.h"
#include "Utilities/WorldContext.h"
#include "Utilities/WorldContextIndex.h"
#if WITH_EDITOR
#include "ImGuiImplementation.h"
#include "Editor/ImGuiEditor.h"
#endif
#include <Interfaces/IPluginManager.h>
#define LOCTEXT_NAMESPACE "FImGuiModule"
struct EDelegateCategory
{
enum
{
// Default per-context draw events.
Default,
// Multi-context draw event defined in context manager.
MultiContext
};
};
FImGuiModuleManager* ImGuiModuleManager = nullptr;
#if WITH_EDITOR
static FImGuiEditor* ImGuiEditor = nullptr;
#endif
#if IMGUI_WITH_OBSOLETE_DELEGATES
#if WITH_EDITOR
FImGuiDelegateHandle FImGuiModule::AddEditorImGuiDelegate(const FImGuiDelegate& Delegate)
{
return { FImGuiDelegatesContainer::Get().OnWorldDebug(Utilities::EDITOR_CONTEXT_INDEX).Add(Delegate),
EDelegateCategory::Default, Utilities::EDITOR_CONTEXT_INDEX };
}
#endif
FImGuiDelegateHandle FImGuiModule::AddWorldImGuiDelegate(const FImGuiDelegate& Delegate)
{
const int32 ContextIndex = Utilities::GetWorldContextIndex((UWorld*)GWorld);
return { FImGuiDelegatesContainer::Get().OnWorldDebug(ContextIndex).Add(Delegate), EDelegateCategory::Default, ContextIndex };
}
FImGuiDelegateHandle FImGuiModule::AddMultiContextImGuiDelegate(const FImGuiDelegate& Delegate)
{
return { FImGuiDelegatesContainer::Get().OnMultiContextDebug().Add(Delegate), EDelegateCategory::MultiContext };
}
void FImGuiModule::RemoveImGuiDelegate(const FImGuiDelegateHandle& Handle)
{
if (Handle.Category == EDelegateCategory::MultiContext)
{
FImGuiDelegatesContainer::Get().OnMultiContextDebug().Remove(Handle.Handle);
}
else
{
FImGuiDelegatesContainer::Get().OnWorldDebug(Handle.Index).Remove(Handle.Handle);
}
}
#endif // IMGUI_WITH_OBSOLETE_DELEGATES
FImGuiTextureHandle FImGuiModule::FindTextureHandle(const FName& Name)
{
const TextureIndex Index = ImGuiModuleManager->GetTextureManager().FindTextureIndex(Name);
return (Index != INDEX_NONE) ? FImGuiTextureHandle{ Name, ImGuiInterops::ToImTextureID(Index) } : FImGuiTextureHandle{};
}
FImGuiTextureHandle FImGuiModule::RegisterTexture(const FName& Name, class UTexture2D* Texture, bool bMakeUnique)
{
FTextureManager& TextureManager = ImGuiModuleManager->GetTextureManager();
checkf(!bMakeUnique || TextureManager.FindTextureIndex(Name) == INDEX_NONE,
TEXT("Trying to register a texture with a name '%s' that is already used. Chose a different name ")
TEXT("or use bMakeUnique false, to update existing texture resources."), *Name.ToString());
const TextureIndex Index = TextureManager.CreateTextureResources(Name, Texture);
return FImGuiTextureHandle{ Name, ImGuiInterops::ToImTextureID(Index) };
}
void FImGuiModule::ReleaseTexture(const FImGuiTextureHandle& Handle)
{
if (Handle.IsValid())
{
ImGuiModuleManager->GetTextureManager().ReleaseTextureResources(ImGuiInterops::ToTextureIndex(Handle.GetTextureId()));
}
}
void FImGuiModule::StartupModule()
{
// Initialize handles to allow cross-module redirections. Other handles will always look for parents in the active
// module, which means that we can only redirect to started modules. We don't have to worry about self-referencing
// as local handles are guaranteed to be constructed before initializing pointers.
// This supports in-editor recompilation and hot-reloading after compiling from the command line. The latter method
// theoretically doesn't support plug-ins and will not load re-compiled module, but its handles will still redirect
// to the active one.
#if WITH_EDITOR
ImGuiContextHandle = &ImGuiImplementation::GetContextHandle();
DelegatesContainerHandle = &FImGuiDelegatesContainer::GetHandle();
#endif
// Create managers that implements module logic.
checkf(!ImGuiModuleManager, TEXT("Instance of the ImGui Module Manager already exists. Instance should be created only during module startup."));
ImGuiModuleManager = new FImGuiModuleManager();
ImGuiInputPreprocessor = MakeShared<FImGuiInputPreprocessor>(*ImGuiModuleManager);
FSlateApplication::Get().RegisterInputPreProcessor(ImGuiInputPreprocessor, 0);
#if WITH_EDITOR
checkf(!ImGuiEditor, TEXT("Instance of the ImGui Editor already exists. Instance should be created only during module startup."));
ImGuiEditor = new FImGuiEditor();
#endif
}
void FImGuiModule::ShutdownModule()
{
// In editor store data that we want to move to hot-reloaded module.
#if WITH_EDITOR
static bool bMoveProperties = true;
static FImGuiModuleProperties PropertiesToMove = ImGuiModuleManager->GetProperties();
#endif
// Before we shutdown we need to delete managers that will do all the necessary cleanup.
#if WITH_EDITOR
checkf(ImGuiEditor, TEXT("Null ImGui Editor. ImGui editor instance should be deleted during module shutdown."));
delete ImGuiEditor;
ImGuiEditor = nullptr;
#endif
checkf(ImGuiModuleManager, TEXT("Null ImGui Module Manager. Module manager instance should be deleted during module shutdown."));
delete ImGuiModuleManager;
ImGuiModuleManager = nullptr;
#if WITH_EDITOR
// When shutting down we leave the global ImGui context pointer and handle pointing to resources that are already
// 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
// 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)
{
if (Reason == EModuleChangeReason::ModuleLoaded && Name == "ImGui")
{
FImGuiModule& LoadedModule = FImGuiModule::Get();
if (&LoadedModule != this)
{
// Statically bound functions can be bound to the obsolete module, so we need to manually redirect.
if (LoadedModule.ImGuiContextHandle)
{
ImGuiImplementation::SetParentContextHandle(*LoadedModule.ImGuiContextHandle);
}
if (LoadedModule.DelegatesContainerHandle)
{
FImGuiDelegatesContainer::MoveContainer(*LoadedModule.DelegatesContainerHandle);
}
if (bMoveProperties)
{
bMoveProperties = false;
LoadedModule.SetProperties(PropertiesToMove);
}
}
}
});
#endif // WITH_EDITOR
}
#if WITH_EDITOR
void FImGuiModule::SetProperties(const FImGuiModuleProperties& Properties)
{
ImGuiModuleManager->GetProperties() = Properties;
}
#endif
FImGuiModuleProperties& FImGuiModule::GetProperties()
{
return ImGuiModuleManager->GetProperties();
}
const FImGuiModuleProperties& FImGuiModule::GetProperties() const
{
return ImGuiModuleManager->GetProperties();
}
bool FImGuiModule::IsInputMode() const
{
return ImGuiModuleManager && ImGuiModuleManager->GetProperties().IsInputEnabled();
}
void FImGuiModule::SetInputMode(bool bEnabled)
{
if (ImGuiModuleManager)
{
ImGuiModuleManager->GetProperties().SetInputEnabled(bEnabled);
}
}
void FImGuiModule::ToggleInputMode()
{
if (ImGuiModuleManager)
{
ImGuiModuleManager->GetProperties().ToggleInput();
}
}
bool FImGuiModule::IsShowingDemo() const
{
return ImGuiModuleManager && ImGuiModuleManager->GetProperties().ShowDemo();
}
void FImGuiModule::SetShowDemo(bool bShow)
{
if (ImGuiModuleManager)
{
ImGuiModuleManager->GetProperties().SetShowDemo(bShow);
}
}
void FImGuiModule::ToggleShowDemo()
{
if (ImGuiModuleManager)
{
ImGuiModuleManager->GetProperties().ToggleDemo();
}
}
//----------------------------------------------------------------------------------------------------
// Runtime loader
//----------------------------------------------------------------------------------------------------
#if !WITH_EDITOR && RUNTIME_LOADER_ENABLED
class FImGuiModuleLoader
{
FImGuiModuleLoader()
{
if (!Load())
{
FModuleManager::Get().OnModulesChanged().AddRaw(this, &FImGuiModuleLoader::LoadAndRelease);
}
}
// For different engine versions.
static FORCEINLINE bool IsValid(const TSharedPtr<IModuleInterface>& Ptr) { return Ptr.IsValid(); }
static FORCEINLINE bool IsValid(const IModuleInterface* const Ptr) { return Ptr != nullptr; }
bool Load()
{
return IsValid(FModuleManager::Get().LoadModule(ModuleName));
}
void LoadAndRelease(FName Name, EModuleChangeReason Reason)
{
// Avoid handling own load event.
if (Name != ModuleName)
{
// Try loading until success and then release.
if (Load())
{
FModuleManager::Get().OnModulesChanged().RemoveAll(this);
}
}
}
static FName ModuleName;
static FImGuiModuleLoader Instance;
};
FName FImGuiModuleLoader::ModuleName = "ImGui";
// In monolithic builds this will start loading process.
FImGuiModuleLoader FImGuiModuleLoader::Instance;
#endif // !WITH_EDITOR && RUNTIME_LOADER_ENABLED
//----------------------------------------------------------------------------------------------------
// Partial implementations of other classes that needs access to ImGuiModuleManager
//----------------------------------------------------------------------------------------------------
bool FImGuiTextureHandle::HasValidEntry() const
{
const TextureIndex Index = ImGuiInterops::ToTextureIndex(TextureId);
return Index != INDEX_NONE && ImGuiModuleManager && ImGuiModuleManager->GetTextureManager().GetTextureName(Index) == Name;
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FImGuiModule, ImGui)