From 4f58c7bb39532e3fc91f6abc392f2e90a8d241e7 Mon Sep 17 00:00:00 2001 From: Pascal Thomet Date: Wed, 18 Sep 2024 17:48:21 +0200 Subject: [PATCH] Added AddDockableWindow() / RemoveDockableWindow() --- src/hello_imgui/doc_api.md | 8 ++ src/hello_imgui/doc_params.md | 2 +- src/hello_imgui/docking_params.h | 2 +- src/hello_imgui/hello_imgui.h | 8 ++ .../backend_impls/abstract_runner.cpp | 15 +++ src/hello_imgui/internal/docking_details.cpp | 99 +++++++++++++++++++ .../hello_imgui_demodocking.main.cpp | 38 +++---- 7 files changed, 146 insertions(+), 26 deletions(-) diff --git a/src/hello_imgui/doc_api.md b/src/hello_imgui/doc_api.md index 9157234c..89bde37d 100644 --- a/src/hello_imgui/doc_api.md +++ b/src/hello_imgui/doc_api.md @@ -324,6 +324,14 @@ void SwitchLayout(const std::string& layoutName); // `CurrentLayoutName()`: returns the name of the current layout std::string CurrentLayoutName(); + +// `AddDockableWindow()`: will add a dockable window to the current layout. +// Will dock the window to the dockspace it belongs to. +void AddDockableWindow(const DockableWindow& dockableWindow); + +// `RemoveDockableWindow()`: will remove a dockable window from the current layout. +void RemoveDockableWindow(const std::string& dockableWindowName); + ``` ---- diff --git a/src/hello_imgui/doc_params.md b/src/hello_imgui/doc_params.md index 1637d8b8..d03c0d56 100644 --- a/src/hello_imgui/doc_params.md +++ b/src/hello_imgui/doc_params.md @@ -1163,7 +1163,7 @@ struct DockableWindow { // --------------- Main params ------------------- - // `label`: _string_. Title of the window. + // `label`: _string_. Title of the window. It should be unique! Use "##" to add a unique suffix if needed. std::string label; // `dockSpaceName`: _DockSpaceName (aka string)_. diff --git a/src/hello_imgui/docking_params.h b/src/hello_imgui/docking_params.h index c7914dfb..104b78b9 100644 --- a/src/hello_imgui/docking_params.h +++ b/src/hello_imgui/docking_params.h @@ -210,7 +210,7 @@ struct DockableWindow { // --------------- Main params ------------------- - // `label`: _string_. Title of the window. + // `label`: _string_. Title of the window. It should be unique! Use "##" to add a unique suffix if needed. std::string label; // `dockSpaceName`: _DockSpaceName (aka string)_. diff --git a/src/hello_imgui/hello_imgui.h b/src/hello_imgui/hello_imgui.h index 1fd03ebc..0a2cb15c 100644 --- a/src/hello_imgui/hello_imgui.h +++ b/src/hello_imgui/hello_imgui.h @@ -138,6 +138,14 @@ void SwitchLayout(const std::string& layoutName); // `CurrentLayoutName()`: returns the name of the current layout std::string CurrentLayoutName(); + +// `AddDockableWindow()`: will add a dockable window to the current layout. +// Will dock the window to the dockspace it belongs to. +void AddDockableWindow(const DockableWindow& dockableWindow); + +// `RemoveDockableWindow()`: will remove a dockable window from the current layout. +void RemoveDockableWindow(const std::string& dockableWindowName); + // @@md diff --git a/src/hello_imgui/internal/backend_impls/abstract_runner.cpp b/src/hello_imgui/internal/backend_impls/abstract_runner.cpp index 1bd4a320..859587bc 100644 --- a/src/hello_imgui/internal/backend_impls/abstract_runner.cpp +++ b/src/hello_imgui/internal/backend_impls/abstract_runner.cpp @@ -86,6 +86,14 @@ bool ShouldRemoteDisplay(); // Encapsulated inside docking_details.cpp void ShowThemeTweakGuiWindow_Static(); +// Encapsulated inside docking_details.cpp +namespace AddDockableWindowHelper +{ + void Callback_1_GuiRender(); + void Callback_2_PreNewFrame(); +} + + struct AbstractRunnerStatics { @@ -1323,6 +1331,10 @@ void AbstractRunner::CreateFramesAndRender(bool insideReentrantCall) fnLoadAdditionalFontDuringExecution_UserCallback(); // User callback } + // Handle AddDockableWindow(): this call should be done before ImGui::NewFrame + if (!insideReentrantCall) + AddDockableWindowHelper::Callback_2_PreNewFrame(); + if ((params.callbacks.PreNewFrame) && !insideReentrantCall) params.callbacks.PreNewFrame(); @@ -1341,6 +1353,9 @@ void AbstractRunner::CreateFramesAndRender(bool insideReentrantCall) fnDrawCustomBackgroundOrClearColor_UserCallback(); // User callback } + // Handle AddDockableWindow(): this call should be done when ImGui is accepting widgets + AddDockableWindowHelper::Callback_1_GuiRender(); + // iii/ At the end of the second frame, we measure the size of the widgets and use it as the application window size, // if the user required auto size // ==> Note: RenderGui() may measure the size of the window and resize it if mIdxFrame==1 diff --git a/src/hello_imgui/internal/docking_details.cpp b/src/hello_imgui/internal/docking_details.cpp index 238c808a..8a1c4785 100644 --- a/src/hello_imgui/internal/docking_details.cpp +++ b/src/hello_imgui/internal/docking_details.cpp @@ -7,6 +7,7 @@ #include "hello_imgui/internal/functional_utils.h" #include "imgui_internal.h" #include +#include #include #include @@ -608,4 +609,102 @@ std::optional DockingParams::dockSpaceIdFromName(const std::string& doc } +// `AddDockableWindow()` implementation and helper +namespace AddDockableWindowHelper +{ + // Adding dockable windows is a three-step process: + // - First, the user calls `AddDockableWindow()`: the dockable window is added to gDockableWindowsToAdd + // with the state `DockableWindowAdditionState::Waiting` + // - Then, in the first callback, the dockable window is added to ImGui as a dummy window: + // we call `ImGui::Begin()` and `ImGui::End()` to create the window, but we don't draw anything in it, + // then we call `ImGui::DockBuilderDockWindow()` to dock the window to the correct dockspace + // - Finally, in the second callback, the dockable window is added to HelloImGui::RunnerParams.dockingParams.dockableWindows + + enum class DockableWindowAdditionState + { + Waiting, + AddedAsDummyToImGui, + AddedToHelloImGui + }; + + struct DockableWindowWaitingForAddition + { + DockableWindow dockableWindow; + DockableWindowAdditionState state = DockableWindowAdditionState::Waiting; + }; + + std::vector gDockableWindowsToAdd; + + void AddDockableWindow(const DockableWindow& dockableWindow) + { + gDockableWindowsToAdd.push_back({dockableWindow, DockableWindowAdditionState::Waiting}); + } + + void Callback_1_GuiRender() + { + for (auto & dockableWindow: gDockableWindowsToAdd) + { + if (dockableWindow.state == DockableWindowAdditionState::Waiting) + { + ImGui::Begin(dockableWindow.dockableWindow.label.c_str()); + ImGui::Dummy(ImVec2(10, 10)); + ImGui::End(); + + auto dockId = HelloImGui::GetRunnerParams()->dockingParams.dockSpaceIdFromName(dockableWindow.dockableWindow.dockSpaceName); + if (dockId.has_value()) + ImGui::DockBuilderDockWindow(dockableWindow.dockableWindow.label.c_str(), dockId.value()); + + dockableWindow.state = DockableWindowAdditionState::AddedAsDummyToImGui; + } + } + } + + void Callback_2_PreNewFrame() + { + for (auto & dockableWindow: gDockableWindowsToAdd) + { + if (dockableWindow.state == DockableWindowAdditionState::AddedAsDummyToImGui) + { + HelloImGui::GetRunnerParams()->dockingParams.dockableWindows.push_back(dockableWindow.dockableWindow); + dockableWindow.state = DockableWindowAdditionState::AddedToHelloImGui; + } + } + + // Remove the dockable windows that have been added to HelloImGui + gDockableWindowsToAdd.erase( // typical C++ shenanigans + std::remove_if( + gDockableWindowsToAdd.begin(), + gDockableWindowsToAdd.end(), + [](const DockableWindowWaitingForAddition& dockableWindow) { + return dockableWindow.state == DockableWindowAdditionState::AddedToHelloImGui; + } + ), + gDockableWindowsToAdd.end() + ); + } + +} // namespace AddDockableWindowHelper + + +void AddDockableWindow(const DockableWindow& dockableWindow) +{ + AddDockableWindowHelper::AddDockableWindow(dockableWindow); +} + +void RemoveDockableWindow(const std::string& dockableWindowName) +{ + auto& dockableWindows = HelloImGui::GetRunnerParams()->dockingParams.dockableWindows; + dockableWindows.erase( + std::remove_if( + dockableWindows.begin(), + dockableWindows.end(), + [&dockableWindowName](const DockableWindow& dockableWindow) { + return dockableWindow.label == dockableWindowName; + } + ), + dockableWindows.end() + ); +} + + } // namespace HelloImGui diff --git a/src/hello_imgui_demos/hello_imgui_demodocking/hello_imgui_demodocking.main.cpp b/src/hello_imgui_demos/hello_imgui_demodocking/hello_imgui_demodocking.main.cpp index 4bdb8f9f..fdf332ad 100644 --- a/src/hello_imgui_demos/hello_imgui_demodocking/hello_imgui_demodocking.main.cpp +++ b/src/hello_imgui_demos/hello_imgui_demodocking/hello_imgui_demodocking.main.cpp @@ -178,26 +178,27 @@ void DemoHideWindow(AppState& appState) } } -// Display a button that will show an additional window +// Display a button that will add another dockable window during execution void DemoShowAdditionalWindow(AppState& appState) { - // Notes: - // - it is *not* possible to modify the content of the vector runnerParams.dockingParams.dockableWindows - // from the code inside a window's `GuiFunction` (since this GuiFunction will be called while iterating on this vector!) - // - there are two ways to dynamically add windows: - // * either make them initially invisible, and exclude them from the view menu (such as shown here) - // * or modify runnerParams.dockingParams.dockableWindows inside the callback RunnerCallbacks.PreNewFrame + // In order to add a dockable window during execution, you should use + // HelloImGui::AddDockableWindow() + + // Note: you should not modify manually the content of the vector runnerParams.dockingParams.dockableWindows + // (since HelloImGui is constantly looping on it) const char* windowName = "Additional Window"; ImGui::PushFont(appState.TitleFont->font); ImGui::Text("Dynamically add window"); ImGui::PopFont(); if (ImGui::Button("Show additional window")) { - auto additionalWindowPtr = HelloImGui::GetRunnerParams()->dockingParams.dockableWindowOfName(windowName); - if (additionalWindowPtr) - { - // additionalWindowPtr->includeInViewMenu = true; - additionalWindowPtr->isVisible = true; - } + HelloImGui::DockableWindow additionalWindow; + additionalWindow.label = "Additional Window"; + additionalWindow.includeInViewMenu = false; // this window is not shown in the view menu, + additionalWindow.rememberIsVisible = false; // its visibility is not saved in the settings file, + additionalWindow.dockSpaceName = "MiscSpace"; // when shown, it will appear in MiscSpace. + additionalWindow.GuiFunction = [] { ImGui::Text("This is the additional window"); }; + HelloImGui::AddDockableWindow(additionalWindow); } + if (ImGui::IsItemHovered()) ImGui::SetTooltip("By clicking this button, you can show an additional window"); } @@ -729,16 +730,6 @@ std::vector CreateDockableWindows(AppState& appState dearImGuiDemoWindow.imGuiWindowFlags = ImGuiWindowFlags_MenuBar; dearImGuiDemoWindow.GuiFunction = [] { ImGui::ShowDemoWindow(); }; - // additionalWindow is initially not visible (and not mentioned in the view menu). - // it will be opened only if the user chooses to display it - HelloImGui::DockableWindow additionalWindow; - additionalWindow.label = "Additional Window"; - additionalWindow.isVisible = false; // this window is initially hidden, - additionalWindow.includeInViewMenu = false; // it is not shown in the view menu, - additionalWindow.rememberIsVisible = false; // its visibility is not saved in the settings file, - additionalWindow.dockSpaceName = "MiscSpace"; // when shown, it will appear in BottomSpace. - additionalWindow.GuiFunction = [] { ImGui::Text("This is the additional window"); }; - // alternativeThemeWindow HelloImGui::DockableWindow alternativeThemeWindow; // Since this window applies a theme, We need to call "ImGui::Begin" ourselves so @@ -753,7 +744,6 @@ std::vector CreateDockableWindows(AppState& appState layoutCustomizationWindow, logsWindow, dearImGuiDemoWindow, - additionalWindow, alternativeThemeWindow }; return dockableWindows;