From 0ed45d03e0fe20dcbac4ac487462aa9921a5010f Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Thu, 4 Jun 2026 08:54:16 +0800 Subject: [PATCH 1/2] feat(docking): DockBuilder surface + real split-layout demo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous demo pre-docked both panels into one tab group — visually just a tab bar, no visible docking. Export the minimal DockBuilder surface (DockBuilderSplitNode/DockWindow/Finish + ImGuiDir-typed Dir_* constants, gated by the docking feature; imgui_internal.h enters the GMF only when the feature is on) and rebuild the example as an IDE-style first-frame layout: Scene | Viewport | Inspector with Console split below — four real dock nodes, drag any tab to re-split/stack/float. Default (feature-off) build/tests unchanged. --- examples/docking/src/main.cpp | 49 +++++++++++++++++++++++++---------- src/core.cppm | 17 ++++++++++++ 2 files changed, 53 insertions(+), 13 deletions(-) diff --git a/examples/docking/src/main.cpp b/examples/docking/src/main.cpp index e6fa048..1696068 100644 --- a/examples/docking/src/main.cpp +++ b/examples/docking/src/main.cpp @@ -1,26 +1,49 @@ // Docking demo — `imgui = { ..., features = ["docking"] }`. -// The imgui.app facade auto-enables ImGuiConfigFlags_DockingEnable when the -// feature is active; DockSpaceOverViewport turns the whole window into a -// dock host, so the two panels below can be dragged into / split around it. +// +// On the first frame, DockBuilder splits the dockspace into an IDE-style +// layout (Scene | Viewport | Inspector, Console at the bottom). Every pane is +// a real dock node: drag any tab to re-split, stack, or float it — the +// docking previews/overlays appear while dragging. import imgui.core; import imgui.app; int main() { return ImGui::App::run({.title = "mcpp imgui docking demo"}, [] { - const auto dockspace = ImGui::DockSpaceOverViewport(); + auto dockspace = ImGui::DockSpaceOverViewport(); - // Pre-dock both panels into the dockspace on first run; they remain - // freely draggable/splittable afterwards. - ImGui::SetNextWindowDockID(dockspace, ImGui::Cond_FirstUseEver); - ImGui::Begin("Toolbox"); - ImGui::TextUnformatted("Drag this panel onto the dockspace."); - ImGui::TextUnformatted("Docked: "); - ImGui::TextUnformatted(ImGui::IsWindowDocked() ? "yes" : "no"); + static bool layout_built = false; + if (!layout_built) { + layout_built = true; + const auto root = dockspace; + auto left = ImGui::DockBuilderSplitNode( + dockspace, ImGui::Dir_Left, 0.22f, nullptr, &dockspace); + auto down = ImGui::DockBuilderSplitNode( + dockspace, ImGui::Dir_Down, 0.28f, nullptr, &dockspace); + auto right = ImGui::DockBuilderSplitNode( + dockspace, ImGui::Dir_Right, 0.30f, nullptr, &dockspace); + ImGui::DockBuilderDockWindow("Scene", left); + ImGui::DockBuilderDockWindow("Console", down); + ImGui::DockBuilderDockWindow("Inspector", right); + ImGui::DockBuilderDockWindow("Viewport", dockspace); + ImGui::DockBuilderFinish(root); + } + + ImGui::Begin("Scene"); + ImGui::TextUnformatted("Scene tree pane (left split)."); + ImGui::TextUnformatted(ImGui::IsWindowDocked() ? "docked: yes" : "docked: no"); + ImGui::End(); + + ImGui::Begin("Viewport"); + ImGui::TextUnformatted("Central pane. Drag any tab to"); + ImGui::TextUnformatted("re-split / stack / float it."); ImGui::End(); - ImGui::SetNextWindowDockID(dockspace, ImGui::Cond_FirstUseEver); ImGui::Begin("Inspector"); - ImGui::TextUnformatted("Split me against the Toolbox."); + ImGui::TextUnformatted("Properties pane (right split)."); + ImGui::End(); + + ImGui::Begin("Console"); + ImGui::TextUnformatted("Log pane (bottom split)."); ImGui::End(); }); } diff --git a/src/core.cppm b/src/core.cppm index d4d1edc..0c14974 100644 --- a/src/core.cppm +++ b/src/core.cppm @@ -1,6 +1,10 @@ module; #include +#ifdef MCPP_FEATURE_DOCKING +// DockBuilder (programmatic dock layouts) lives in the internal header. +#include +#endif export module imgui.core; @@ -42,9 +46,22 @@ export namespace ImGui { using ::ImGui::GetWindowDockID; using ::ImGui::IsWindowDocked; + // DockBuilder — programmatic dock layouts (e.g. an IDE-style default + // split). Upstream ships it in imgui_internal.h; the docking feature + // exports the minimal stable surface needed to build layouts. + using ::ImGui::DockBuilderSplitNode; + using ::ImGui::DockBuilderDockWindow; + using ::ImGui::DockBuilderFinish; + // Unscoped global enumerators, re-exported as namespaced constants for // module consumers. inline constexpr int ConfigFlags_DockingEnable = ::ImGuiConfigFlags_DockingEnable; inline constexpr int Cond_FirstUseEver = ::ImGuiCond_FirstUseEver; + // Typed as the underlying enum so they pass straight into + // DockBuilderSplitNode without the consumer naming ImGuiDir. + inline constexpr ::ImGuiDir Dir_Left = ::ImGuiDir_Left; + inline constexpr ::ImGuiDir Dir_Right = ::ImGuiDir_Right; + inline constexpr ::ImGuiDir Dir_Up = ::ImGuiDir_Up; + inline constexpr ::ImGuiDir Dir_Down = ::ImGuiDir_Down; } #endif From adc72a6226db2cfd0383924e6ee20ac68acabb75 Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Thu, 4 Jun 2026 08:57:21 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20viewports=20feature=20=E2=80=94=20p?= =?UTF-8?q?anels=20detach=20into=20real=20OS=20windows?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New independent `viewports` feature (usually combined with docking): - core exports GetMainViewport/UpdatePlatformWindows/ RenderPlatformWindowsDefault/SetNextWindowPos + ConfigFlags_ViewportsEnable (gated by MCPP_FEATURE_VIEWPORTS). - imgui.app facade enables ViewportsEnable and renders platform windows each frame (with GL context backup/restore); GlfwOpenGL3 gains GetCurrentContext. - docking example requests ["docking", "viewports"] and spawns a 'Detached' panel outside the main window — verified: a separate OS window appears in the X window tree and renders (drag back inside to re-dock). Default (feature-off) build/tests unchanged. --- examples/docking/mcpp.toml | 2 +- examples/docking/src/main.cpp | 19 ++++++++++++++++--- mcpp.toml | 3 +++ src/app.cppm | 14 ++++++++++++++ src/backends/glfw_opengl3.cppm | 4 ++++ src/backends/platform_glfw.cppm | 4 ++++ src/core.cppm | 15 +++++++++++++++ 7 files changed, 57 insertions(+), 4 deletions(-) diff --git a/examples/docking/mcpp.toml b/examples/docking/mcpp.toml index 92859bb..4c94905 100644 --- a/examples/docking/mcpp.toml +++ b/examples/docking/mcpp.toml @@ -5,4 +5,4 @@ description = "Dockable-windows demo using the imgui `docking` feature" license = "MIT" [dependencies] -imgui = { path = "../..", features = ["docking"] } +imgui = { path = "../..", features = ["docking", "viewports"] } diff --git a/examples/docking/src/main.cpp b/examples/docking/src/main.cpp index 1696068..7fcbe4d 100644 --- a/examples/docking/src/main.cpp +++ b/examples/docking/src/main.cpp @@ -1,9 +1,11 @@ -// Docking demo — `imgui = { ..., features = ["docking"] }`. +// Docking + multi-viewport demo — +// imgui = { ..., features = ["docking", "viewports"] } // // On the first frame, DockBuilder splits the dockspace into an IDE-style // layout (Scene | Viewport | Inspector, Console at the bottom). Every pane is -// a real dock node: drag any tab to re-split, stack, or float it — the -// docking previews/overlays appear while dragging. +// a real dock node: drag any tab to re-split, stack, or float it. +// With `viewports`, drag a tab OUTSIDE the main window and it detaches into +// its own OS window; the "Detached" panel below starts outside on purpose. import imgui.core; import imgui.app; @@ -45,5 +47,16 @@ int main() { ImGui::Begin("Console"); ImGui::TextUnformatted("Log pane (bottom split)."); ImGui::End(); + + // Starts OUTSIDE the main window: with `viewports` this is a real, + // separate OS window from the very first frame. + const auto* main_vp = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos( + ImVec2{main_vp->Pos.x + main_vp->Size.x + 60.0f, main_vp->Pos.y + 80.0f}, + ImGui::Cond_FirstUseEver); + ImGui::Begin("Detached"); + ImGui::TextUnformatted("I live in my own OS window."); + ImGui::TextUnformatted("Drag me back inside to re-dock."); + ImGui::End(); }); } diff --git a/mcpp.toml b/mcpp.toml index 180a05f..37bced4 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -27,6 +27,9 @@ default = [] # docking: export the Dock* API surface and auto-enable # ImGuiConfigFlags_DockingEnable in the imgui.app facade. docking = [] +# viewports: ImGui windows dragged outside the main window detach into real +# OS windows (multi-viewport). Usually combined with docking. +viewports = [] [dependencies] compat.imgui = "1.92.8-docking" diff --git a/src/app.cppm b/src/app.cppm index 3d5d55c..d74ea00 100644 --- a/src/app.cppm +++ b/src/app.cppm @@ -57,6 +57,11 @@ export namespace ImGui::App { // `docking` feature: enable dockable windows out of the box. ImGui::GetIO().ConfigFlags |= ImGui::ConfigFlags_DockingEnable; #endif +#ifdef MCPP_FEATURE_VIEWPORTS + // `viewports` feature: windows dragged outside the main window + // detach into real OS windows. + ImGui::GetIO().ConfigFlags |= ImGui::ConfigFlags_ViewportsEnable; +#endif if (!Backend::Init(window)) { const auto error = Backend::LastError(); @@ -84,6 +89,15 @@ export namespace ImGui::App { Backend::ClearColor(0.10f, 0.10f, 0.12f, 1.0f); Backend::ClearColorBuffer(); Backend::RenderDrawData(ImGui::GetDrawData()); +#ifdef MCPP_FEATURE_VIEWPORTS + // Render the detached OS windows, then restore the main context. + { + auto* backup = Backend::GetCurrentContext(); + ImGui::UpdatePlatformWindows(); + ImGui::RenderPlatformWindowsDefault(); + Backend::MakeContextCurrent(backup); + } +#endif Backend::SwapBuffers(window); } diff --git a/src/backends/glfw_opengl3.cppm b/src/backends/glfw_opengl3.cppm index a8b5be2..1ce77b3 100644 --- a/src/backends/glfw_opengl3.cppm +++ b/src/backends/glfw_opengl3.cppm @@ -62,6 +62,10 @@ export namespace ImGui::Backend { GlfwPlatform::MakeContextCurrent(window); } + static Window* GetCurrentContext() { + return GlfwPlatform::GetCurrentContext(); + } + static void SwapInterval(int interval) { GlfwPlatform::SwapInterval(interval); } diff --git a/src/backends/platform_glfw.cppm b/src/backends/platform_glfw.cppm index 5318b92..66d590e 100644 --- a/src/backends/platform_glfw.cppm +++ b/src/backends/platform_glfw.cppm @@ -71,6 +71,10 @@ export namespace ImGui::Backend { glfwMakeContextCurrent(window); } + static Window* GetCurrentContext() { + return glfwGetCurrentContext(); + } + static FbSize FramebufferSize(Window* window) { int width = 0; int height = 0; diff --git a/src/core.cppm b/src/core.cppm index 0c14974..81aa700 100644 --- a/src/core.cppm +++ b/src/core.cppm @@ -65,3 +65,18 @@ export namespace ImGui { inline constexpr ::ImGuiDir Dir_Down = ::ImGuiDir_Down; } #endif + +// Multi-viewport surface — gated by the `viewports` feature: ImGui windows +// dragged (or positioned) outside the main window become real OS windows. +#ifdef MCPP_FEATURE_VIEWPORTS +export using ImGuiViewport = ::ImGuiViewport; + +export namespace ImGui { + using ::ImGui::GetMainViewport; + using ::ImGui::UpdatePlatformWindows; + using ::ImGui::RenderPlatformWindowsDefault; + using ::ImGui::SetNextWindowPos; + + inline constexpr int ConfigFlags_ViewportsEnable = ::ImGuiConfigFlags_ViewportsEnable; +} +#endif