From 1a6aa98f516ce3af235e10e06382b64397f2b08d Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 27 Sep 2017 21:16:54 +0100 Subject: [PATCH] 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. --- Source/ImGui/Private/ImGuiContextManager.cpp | 163 +++++++++++++----- Source/ImGui/Private/ImGuiContextManager.h | 62 +++++-- Source/ImGui/Private/ImGuiContextProxy.cpp | 47 ++++- Source/ImGui/Private/ImGuiContextProxy.h | 13 +- Source/ImGui/Private/ImGuiImplementation.cpp | 18 +- Source/ImGui/Private/ImGuiImplementation.h | 11 +- Source/ImGui/Private/ImGuiModuleManager.cpp | 36 ++-- Source/ImGui/Private/ImGuiModuleManager.h | 4 +- Source/ImGui/Private/SImGuiWidget.cpp | 37 ++-- Source/ImGui/Private/SImGuiWidget.h | 11 +- .../ImGui/Private/Utilities/WorldContext.cpp | 24 +++ Source/ImGui/Private/Utilities/WorldContext.h | 41 +++++ .../Private/Utilities/WorldContextIndex.h | 42 ++--- 13 files changed, 359 insertions(+), 150 deletions(-) create mode 100644 Source/ImGui/Private/Utilities/WorldContext.cpp create mode 100644 Source/ImGui/Private/Utilities/WorldContext.h diff --git a/Source/ImGui/Private/ImGuiContextManager.cpp b/Source/ImGui/Private/ImGuiContextManager.cpp index 0f9c6b6..8958e2d 100644 --- a/Source/ImGui/Private/ImGuiContextManager.cpp +++ b/Source/ImGui/Private/ImGuiContextManager.cpp @@ -4,68 +4,141 @@ #include "ImGuiContextManager.h" +#include "ImGuiImplementation.h" +#include "Utilities/WorldContext.h" +#include "Utilities/WorldContextIndex.h" -FImGuiContextProxy& FImGuiContextManager::GetWorldContextProxy(UWorld& World) +#include + + +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; +} diff --git a/Source/ImGui/Private/ImGuiContextManager.h b/Source/ImGui/Private/ImGuiContextManager.h index 1ee584a..8757ee3 100644 --- a/Source/ImGui/Private/ImGuiContextManager.h +++ b/Source/ImGui/Private/ImGuiContextManager.h @@ -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 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 Contexts; diff --git a/Source/ImGui/Private/ImGuiContextProxy.cpp b/Source/ImGui/Private/ImGuiContextProxy.cpp index 129c928..4b824df 100644 --- a/Source/ImGui/Private/ImGuiContextProxy.cpp +++ b/Source/ImGui/Private/ImGuiContextProxy.cpp @@ -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()); } } diff --git a/Source/ImGui/Private/ImGuiContextProxy.h b/Source/ImGui/Private/ImGuiContextProxy.h index 570ffd9..77cdc8a 100644 --- a/Source/ImGui/Private/ImGuiContextProxy.h +++ b/Source/ImGui/Private/ImGuiContextProxy.h @@ -6,6 +6,8 @@ #include +#include + 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& 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 DrawLists; + + FString Name; + std::string IniFilename; }; diff --git a/Source/ImGui/Private/ImGuiImplementation.cpp b/Source/ImGui/Private/ImGuiImplementation.cpp index 48cb660..f4550ef 100644 --- a/Source/ImGui/Private/ImGuiImplementation.cpp +++ b/Source/ImGui/Private/ImGuiImplementation.cpp @@ -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); + } +} \ No newline at end of file diff --git a/Source/ImGui/Private/ImGuiImplementation.h b/Source/ImGui/Private/ImGuiImplementation.h index 5959b3b..31aa441 100644 --- a/Source/ImGui/Private/ImGuiImplementation.h +++ b/Source/ImGui/Private/ImGuiImplementation.h @@ -5,5 +5,12 @@ #include -// 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); +} diff --git a/Source/ImGui/Private/ImGuiModuleManager.cpp b/Source/ImGui/Private/ImGuiModuleManager.cpp index 5995851..af579c5 100644 --- a/Source/ImGui/Private/ImGuiModuleManager.cpp +++ b/Source/ImGui/Private/ImGuiModuleManager.cpp @@ -5,15 +5,13 @@ #include "ImGuiModuleManager.h" #include "ImGuiInteroperability.h" +#include "Utilities/WorldContextIndex.h" #include 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 Widget; + SAssignNew(Widget, SImGuiWidget).ModuleManager(this).GameViewport(GameViewport) + .ContextIndex(Utilities::GetWorldContextIndex(GameViewport)); + + if (TWeakPtr* 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() diff --git a/Source/ImGui/Private/ImGuiModuleManager.h b/Source/ImGui/Private/ImGuiModuleManager.h index 92301d0..ee1b9ec 100644 --- a/Source/ImGui/Private/ImGuiModuleManager.h +++ b/Source/ImGui/Private/ImGuiModuleManager.h @@ -61,8 +61,8 @@ private: // Manager for textures resources. FTextureManager TextureManager; - // Slate widgets that we attach to game viewports. - TMap> ViewportWidgets; + // Slate widgets that we created. + TArray> Widgets; FDelegateHandle TickDelegateHandle; FDelegateHandle ViewportCreatedHandle; diff --git a/Source/ImGui/Private/SImGuiWidget.cpp b/Source/ImGui/Private/SImGuiWidget.cpp index f48f113..3307fa7 100644 --- a/Source/ImGui/Private/SImGuiWidget.cpp +++ b/Source/ImGui/Private/SImGuiWidget.cpp @@ -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(); diff --git a/Source/ImGui/Private/SImGuiWidget.h b/Source/ImGui/Private/SImGuiWidget.h index a1df1d3..c417b67 100644 --- a/Source/ImGui/Private/SImGuiWidget.h +++ b/Source/ImGui/Private/SImGuiWidget.h @@ -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& 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(); diff --git a/Source/ImGui/Private/Utilities/WorldContext.cpp b/Source/ImGui/Private/Utilities/WorldContext.cpp new file mode 100644 index 0000000..b0fb876 --- /dev/null +++ b/Source/ImGui/Private/Utilities/WorldContext.cpp @@ -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; + } +} diff --git a/Source/ImGui/Private/Utilities/WorldContext.h b/Source/ImGui/Private/Utilities/WorldContext.h new file mode 100644 index 0000000..d63a317 --- /dev/null +++ b/Source/ImGui/Private/Utilities/WorldContext.h @@ -0,0 +1,41 @@ +// Distributed under the MIT License (MIT) (see accompanying LICENSE file) + +#pragma once + +#include +#include + + +// Utilities helping to get a World Context. + +namespace Utilities +{ + template + FORCEINLINE const FWorldContext* GetWorldContext(const TWeakObjectPtr& Obj) + { + return Obj.IsValid() ? GetWorldContext(*Obj.Get()) : nullptr; + } + + template + 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); +} diff --git a/Source/ImGui/Private/Utilities/WorldContextIndex.h b/Source/ImGui/Private/Utilities/WorldContextIndex.h index 4c42a8d..6b2a6fb 100644 --- a/Source/ImGui/Private/Utilities/WorldContextIndex.h +++ b/Source/ImGui/Private/Utilities/WorldContextIndex.h @@ -2,8 +2,7 @@ #pragma once -#include -#include +#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 - FORCEINLINE int32 GetWorldContextIndex(const TWeakObjectPtr& Obj) - { - return Obj.IsValid() ? GetWorldContextIndex(*Obj.Get()) : INVALID_CONTEXT_INDEX; - } + // Editor context index. + static constexpr int32 EDITOR_CONTEXT_INDEX = 0; template - 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