[Scummvm-git-logs] scummvm master -> 9f223073536e7918ecd8f06ba5a4341a9f4062a3
scemino
noreply at scummvm.org
Fri May 24 14:37:54 UTC 2024
This automated email contains information about 2 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
4a1209da9f IMGUI: Use docking branch
9f22307353 DIRECTOR: Use imgui docking
Commit: 4a1209da9f293fb88fbc0a3b144a45a573aab815
https://github.com/scummvm/scummvm/commit/4a1209da9f293fb88fbc0a3b144a45a573aab815
Author: scemino (scemino74 at gmail.com)
Date: 2024-05-24T16:35:55+02:00
Commit Message:
IMGUI: Use docking branch
This is is imgui commit fee0210592b9cb5b1a778f005ffb1fe76b19ced0
Changed paths:
backends/graphics/openglsdl/openglsdl-graphics.cpp
backends/imgui/backends/imgui_impl_opengl3.cpp
backends/imgui/backends/imgui_impl_opengl3.h
backends/imgui/backends/imgui_impl_opengl3_loader.h
backends/imgui/backends/imgui_impl_sdl2.cpp
backends/imgui/backends/imgui_impl_sdl2.h
backends/imgui/imgui.cpp
backends/imgui/imgui.h
backends/imgui/imgui_demo.cpp
backends/imgui/imgui_draw.cpp
backends/imgui/imgui_internal.h
backends/imgui/imgui_tables.cpp
backends/imgui/imgui_widgets.cpp
backends/imgui/imstb_textedit.h
backends/imgui/imstb_truetype.h
diff --git a/backends/graphics/openglsdl/openglsdl-graphics.cpp b/backends/graphics/openglsdl/openglsdl-graphics.cpp
index d21cc9baeec..4036d4e9816 100644
--- a/backends/graphics/openglsdl/openglsdl-graphics.cpp
+++ b/backends/graphics/openglsdl/openglsdl-graphics.cpp
@@ -472,8 +472,13 @@ void OpenGLSdlGraphicsManager::refreshScreen() {
ImGui::NewFrame();
_callbacks.render();
ImGui::Render();
-
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
+
+ SDL_Window* backup_current_window = SDL_GL_GetCurrentWindow();
+ SDL_GLContext backup_current_context = SDL_GL_GetCurrentContext();
+ ImGui::UpdatePlatformWindows();
+ ImGui::RenderPlatformWindowsDefault();
+ SDL_GL_MakeCurrent(backup_current_window, backup_current_context);
}
#endif
@@ -616,11 +621,18 @@ bool OpenGLSdlGraphicsManager::setupMode(uint width, uint height) {
// Setup Dear ImGui
IMGUI_CHECKVERSION();
ImGui::CreateContext();
- ImGui_ImplSDL2_InitForOpenGL(_window->getSDLWindow(), _glContext);
- ImGui_ImplOpenGL3_Init("#version 110");
- ImGui::StyleColorsDark();
ImGuiIO &io = ImGui::GetIO();
+ io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking
+ io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows
+ ImGui::StyleColorsDark();
+ // When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular ones.
+ ImGuiStyle& style = ImGui::GetStyle();
+ style.WindowRounding = 0.0f;
+ style.Colors[ImGuiCol_WindowBg].w = 1.0f;
io.IniFilename = nullptr;
+ ImGui_ImplSDL2_InitForOpenGL(_window->getSDLWindow(), _glContext);
+ ImGui_ImplOpenGL3_Init("#version 110");
+
if (_callbacks.init) {
_callbacks.init();
}
diff --git a/backends/imgui/backends/imgui_impl_opengl3.cpp b/backends/imgui/backends/imgui_impl_opengl3.cpp
index 390d3ff3379..c288b577e2e 100644
--- a/backends/imgui/backends/imgui_impl_opengl3.cpp
+++ b/backends/imgui/backends/imgui_impl_opengl3.cpp
@@ -5,7 +5,8 @@
// Implemented features:
// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID!
-// [x] Renderer: Large meshes support (64k+ vertices) with 16-bit indices (Desktop OpenGL only).
+// [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices (Desktop OpenGL only).
+// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
// About WebGL/ES:
// - You need to '#define IMGUI_IMPL_OPENGL_ES2' or '#define IMGUI_IMPL_OPENGL_ES3' to use WebGL or OpenGL ES.
@@ -22,6 +23,7 @@
// CHANGELOG
// (minor and older changes stripped away, please see git history for details)
+// 2024-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.
// 2024-05-07: OpenGL: Update loader for Linux to support EGL/GLVND. (#7562)
// 2024-04-16: OpenGL: Detect ES3 contexts on desktop based on version string, to e.g. avoid calling glPolygonMode() on them. (#7447)
// 2024-01-09: OpenGL: Update GL3W based imgui_impl_opengl3_loader.h to load "libGL.so" and variants, fixing regression on distros missing a symlink.
@@ -248,6 +250,10 @@ static ImGui_ImplOpenGL3_Data* ImGui_ImplOpenGL3_GetBackendData()
return ImGui::GetCurrentContext() ? (ImGui_ImplOpenGL3_Data*)ImGui::GetIO().BackendRendererUserData : nullptr;
}
+// Forward Declarations
+static void ImGui_ImplOpenGL3_InitPlatformInterface();
+static void ImGui_ImplOpenGL3_ShutdownPlatformInterface();
+
// OpenGL vertex attribute state (for ES 1.0 and ES 2.0 only)
#ifndef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
struct ImGui_ImplOpenGL3_VtxAttribState
@@ -276,6 +282,7 @@ struct ImGui_ImplOpenGL3_VtxAttribState
bool ImGui_ImplOpenGL3_Init(const char* glsl_version)
{
ImGuiIO& io = ImGui::GetIO();
+ IMGUI_CHECKVERSION();
IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
// Initialize our loader
@@ -339,6 +346,7 @@ bool ImGui_ImplOpenGL3_Init(const char* glsl_version)
if (bd->GlVersion >= 320)
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
#endif
+ io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional)
// Store GLSL version string so we can refer to it later in case we recreate shaders.
// Note: GLSL version is NOT the same as GL version. Leave this to nullptr if unsure.
@@ -379,6 +387,9 @@ bool ImGui_ImplOpenGL3_Init(const char* glsl_version)
}
#endif
+ if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
+ ImGui_ImplOpenGL3_InitPlatformInterface();
+
return true;
}
@@ -388,10 +399,11 @@ void ImGui_ImplOpenGL3_Shutdown()
IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
ImGuiIO& io = ImGui::GetIO();
+ ImGui_ImplOpenGL3_ShutdownPlatformInterface();
ImGui_ImplOpenGL3_DestroyDeviceObjects();
io.BackendRendererName = nullptr;
io.BackendRendererUserData = nullptr;
- io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset;
+ io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasViewports);
IM_DELETE(bd);
}
@@ -750,7 +762,7 @@ bool ImGui_ImplOpenGL3_CreateDeviceObjects()
glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture);
glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &last_array_buffer);
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_BUFFER_PIXEL_UNPACK
- GLint last_pixel_unpack_buffer;
+ GLint last_pixel_unpack_buffer = 0;
if (bd->GlVersion >= 210) { glGetIntegerv(GL_PIXEL_UNPACK_BUFFER_BINDING, &last_pixel_unpack_buffer); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); }
#endif
#ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY
@@ -945,6 +957,34 @@ void ImGui_ImplOpenGL3_DestroyDeviceObjects()
ImGui_ImplOpenGL3_DestroyFontsTexture();
}
+//--------------------------------------------------------------------------------------------------------
+// MULTI-VIEWPORT / PLATFORM INTERFACE SUPPORT
+// This is an _advanced_ and _optional_ feature, allowing the backend to create and handle multiple viewports simultaneously.
+// If you are new to dear imgui or creating a new binding for dear imgui, it is recommended that you completely ignore this section first..
+//--------------------------------------------------------------------------------------------------------
+
+static void ImGui_ImplOpenGL3_RenderWindow(ImGuiViewport* viewport, void*)
+{
+ if (!(viewport->Flags & ImGuiViewportFlags_NoRendererClear))
+ {
+ ImVec4 clear_color = ImVec4(0.0f, 0.0f, 0.0f, 1.0f);
+ glClearColor(clear_color.x, clear_color.y, clear_color.z, clear_color.w);
+ glClear(GL_COLOR_BUFFER_BIT);
+ }
+ ImGui_ImplOpenGL3_RenderDrawData(viewport->DrawData);
+}
+
+static void ImGui_ImplOpenGL3_InitPlatformInterface()
+{
+ ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
+ platform_io.Renderer_RenderWindow = ImGui_ImplOpenGL3_RenderWindow;
+}
+
+static void ImGui_ImplOpenGL3_ShutdownPlatformInterface()
+{
+ ImGui::DestroyPlatformWindows();
+}
+
//-----------------------------------------------------------------------------
#if defined(__GNUC__)
diff --git a/backends/imgui/backends/imgui_impl_opengl3.h b/backends/imgui/backends/imgui_impl_opengl3.h
index d4abe38e7c5..bac3225f6a9 100644
--- a/backends/imgui/backends/imgui_impl_opengl3.h
+++ b/backends/imgui/backends/imgui_impl_opengl3.h
@@ -5,7 +5,8 @@
// Implemented features:
// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID!
-// [x] Renderer: Large meshes support (64k+ vertices) with 16-bit indices (Desktop OpenGL only).
+// [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices (Desktop OpenGL only).
+// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
// About WebGL/ES:
// - You need to '#define IMGUI_IMPL_OPENGL_ES2' or '#define IMGUI_IMPL_OPENGL_ES3' to use WebGL or OpenGL ES.
diff --git a/backends/imgui/backends/imgui_impl_opengl3_loader.h b/backends/imgui/backends/imgui_impl_opengl3_loader.h
index f6b38ed0835..e91af76e9eb 100644
--- a/backends/imgui/backends/imgui_impl_opengl3_loader.h
+++ b/backends/imgui/backends/imgui_impl_opengl3_loader.h
@@ -10,7 +10,7 @@
// THE REST OF YOUR APP SHOULD USE A DIFFERENT GL LOADER: ANY GL LOADER OF YOUR CHOICE.
//
// IF YOU GET BUILD ERRORS IN THIS FILE (commonly macro redefinitions or function redefinitions):
-// IT LIKELY MEANS THAT YOU ARE BUILDING 'imgui_impl_opengl3.cpp' OR INCUDING 'imgui_impl_opengl3_loader.h'
+// IT LIKELY MEANS THAT YOU ARE BUILDING 'imgui_impl_opengl3.cpp' OR INCLUDING 'imgui_impl_opengl3_loader.h'
// IN THE SAME COMPILATION UNIT AS ONE OF YOUR FILE WHICH IS USING A THIRD-PARTY OPENGL LOADER.
// (e.g. COULD HAPPEN IF YOU ARE DOING A UNITY/JUMBO BUILD, OR INCLUDING .CPP FILES FROM OTHERS)
// YOU SHOULD NOT BUILD BOTH IN THE SAME COMPILATION UNIT.
@@ -123,7 +123,7 @@ extern "C" {
** included as <GL/glcorearb.h>.
**
** glcorearb.h includes only APIs in the latest OpenGL core profile
-** implementation together with APIs in newer ARB extensions which
+** implementation together with APIs in newer ARB extensions which
** can be supported by the core profile. It does not, and never will
** include functionality removed from the core profile, such as
** fixed-function vertex and fragment processing.
diff --git a/backends/imgui/backends/imgui_impl_sdl2.cpp b/backends/imgui/backends/imgui_impl_sdl2.cpp
index 515497f8431..6670f83143d 100644
--- a/backends/imgui/backends/imgui_impl_sdl2.cpp
+++ b/backends/imgui/backends/imgui_impl_sdl2.cpp
@@ -9,7 +9,11 @@
// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy SDL_SCANCODE_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
-// [X] Platform: Basic IME support. App needs to call 'SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");' before SDL_CreateWindow()!.
+// [X] Platform: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
+// Issues:
+// [ ] Platform: Multi-viewport: Minimized windows seems to break mouse wheel events (at least under Windows).
+// [ ] Platform: Multi-viewport: ParentViewportID not honored, and so io.ConfigViewportsNoDefaultParent has no effect (minor).
+// [x] Platform: Basic IME support. App needs to call 'SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");' before SDL_CreateWindow()!.
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
@@ -21,6 +25,7 @@
// CHANGELOG
// (minor and older changes stripped away, please see git history for details)
+// 2024-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.
// 2024-02-14: Inputs: Handle gamepad disconnection. Added ImGui_ImplSDL2_SetGamepadMode().
// 2023-10-05: Inputs: Added support for extra ImGuiKey values: F13 to F24 function keys, app back/forward keys.
// 2023-04-06: Inputs: Avoid calling SDL_StartTextInput()/SDL_StopTextInput() as they don't only pertain to IME. It's unclear exactly what their relation is to IME. (#6306)
@@ -47,7 +52,7 @@
// 2022-01-10: Inputs: calling new io.AddKeyEvent(), io.AddKeyModsEvent() + io.SetKeyEventNativeData() API (1.87+). Support for full ImGuiKey range.
// 2021-08-17: Calling io.AddFocusEvent() on SDL_WINDOWEVENT_FOCUS_GAINED/SDL_WINDOWEVENT_FOCUS_LOST.
// 2021-07-29: Inputs: MousePos is correctly reported when the host platform window is hovered but not focused (using SDL_GetMouseFocus() + SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, requires SDL 2.0.5+)
-// 2021-06-29: *BREAKING CHANGE* Removed 'SDL_Window* window' parameter to ImGui_ImplSDL2_NewFrame() which was unnecessary.
+// 2021-06:29: *BREAKING CHANGE* Removed 'SDL_Window* window' parameter to ImGui_ImplSDL2_NewFrame() which was unnecessary.
// 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX).
// 2021-03-22: Rework global mouse pos availability check listing supported platforms explicitly, effectively fixing mouse access on Raspberry Pi. (#2837, #3950)
// 2020-05-25: Misc: Report a zero display-size when window is minimized, to be consistent with other backends.
@@ -90,6 +95,7 @@
#endif
// SDL
+// (the multi-viewports feature requires SDL features supported from SDL 2.0.4+. SDL 2.0.5+ is highly recommended)
#include <SDL.h>
#include <SDL_syswm.h>
#if defined(__APPLE__)
@@ -101,7 +107,15 @@
#else
#define SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE 0
#endif
+#define SDL_HAS_WINDOW_ALPHA SDL_VERSION_ATLEAST(2,0,5)
+#define SDL_HAS_ALWAYS_ON_TOP SDL_VERSION_ATLEAST(2,0,5)
+#define SDL_HAS_USABLE_DISPLAY_BOUNDS SDL_VERSION_ATLEAST(2,0,5)
+#define SDL_HAS_PER_MONITOR_DPI SDL_VERSION_ATLEAST(2,0,4)
#define SDL_HAS_VULKAN SDL_VERSION_ATLEAST(2,0,6)
+#define SDL_HAS_DISPLAY_EVENT SDL_VERSION_ATLEAST(2,0,9)
+#if !SDL_HAS_VULKAN
+static const Uint32 SDL_WINDOW_VULKAN = 0x10000000;
+#endif
// SDL Data
struct ImGui_ImplSDL2_Data
@@ -110,6 +124,8 @@ struct ImGui_ImplSDL2_Data
SDL_Renderer* Renderer;
Uint64 Time;
char* ClipboardTextData;
+ bool UseVulkan;
+ bool WantUpdateMonitors;
// Mouse handling
Uint32 MouseWindowID;
@@ -118,6 +134,7 @@ struct ImGui_ImplSDL2_Data
SDL_Cursor* MouseLastCursor;
int MouseLastLeaveFrame;
bool MouseCanUseGlobalState;
+ bool MouseCanReportHoveredViewport; // This is hard to use/unreliable on SDL so we'll set ImGuiBackendFlags_HasMouseHoveredViewport dynamically based on state.
// Gamepad handling
ImVector<SDL_GameController*> Gamepads;
@@ -136,6 +153,11 @@ static ImGui_ImplSDL2_Data* ImGui_ImplSDL2_GetBackendData()
return ImGui::GetCurrentContext() ? (ImGui_ImplSDL2_Data*)ImGui::GetIO().BackendPlatformUserData : nullptr;
}
+// Forward Declarations
+static void ImGui_ImplSDL2_UpdateMonitors();
+static void ImGui_ImplSDL2_InitPlatformInterface(SDL_Window* window, void* sdl_gl_context);
+static void ImGui_ImplSDL2_ShutdownPlatformInterface();
+
// Functions
static const char* ImGui_ImplSDL2_GetClipboardText(void*)
{
@@ -152,13 +174,13 @@ static void ImGui_ImplSDL2_SetClipboardText(void*, const char* text)
}
// Note: native IME will only display if user calls SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1") _before_ SDL_CreateWindow().
-static void ImGui_ImplSDL2_SetPlatformImeData(ImGuiViewport*, ImGuiPlatformImeData* data)
+static void ImGui_ImplSDL2_SetPlatformImeData(ImGuiViewport* viewport, ImGuiPlatformImeData* data)
{
if (data->WantVisible)
{
SDL_Rect r;
- r.x = (int)data->InputPos.x;
- r.y = (int)data->InputPos.y;
+ r.x = (int)(data->InputPos.x - viewport->Pos.x);
+ r.y = (int)(data->InputPos.y - viewport->Pos.y + data->InputLineHeight);
r.w = 1;
r.h = (int)data->InputLineHeight;
SDL_SetTextInputRect(&r);
@@ -322,6 +344,13 @@ bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event)
case SDL_MOUSEMOTION:
{
ImVec2 mouse_pos((float)event->motion.x, (float)event->motion.y);
+ if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
+ {
+ int window_x, window_y;
+ SDL_GetWindowPosition(SDL_GetWindowFromID(event->motion.windowID), &window_x, &window_y);
+ mouse_pos.x += window_x;
+ mouse_pos.y += window_y;
+ }
io.AddMouseSourceEvent(event->motion.which == SDL_TOUCH_MOUSEID ? ImGuiMouseSource_TouchScreen : ImGuiMouseSource_Mouse);
io.AddMousePosEvent(mouse_pos.x, mouse_pos.y);
return true;
@@ -373,6 +402,15 @@ bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event)
io.SetKeyEventNativeData(key, event->key.keysym.sym, event->key.keysym.scancode, event->key.keysym.scancode); // To support legacy indexing (<1.87 user code). Legacy backend uses SDLK_*** as indices to IsKeyXXX() functions.
return true;
}
+#if SDL_HAS_DISPLAY_EVENT
+ case SDL_DISPLAYEVENT:
+ {
+ // 2.0.26 has SDL_DISPLAYEVENT_CONNECTED/SDL_DISPLAYEVENT_DISCONNECTED/SDL_DISPLAYEVENT_ORIENTATION,
+ // so change of DPI/Scaling are not reflected in this event. (SDL3 has it)
+ bd->WantUpdateMonitors = true;
+ return true;
+ }
+#endif
case SDL_WINDOWEVENT:
{
// - When capturing mouse, SDL will send a bunch of conflicting LEAVE/ENTER event on every mouse move, but the final ENTER tends to be right.
@@ -390,8 +428,19 @@ bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event)
bd->MouseLastLeaveFrame = ImGui::GetFrameCount() + 1;
if (window_event == SDL_WINDOWEVENT_FOCUS_GAINED)
io.AddFocusEvent(true);
- else if (event->window.event == SDL_WINDOWEVENT_FOCUS_LOST)
+ else if (window_event == SDL_WINDOWEVENT_FOCUS_LOST)
io.AddFocusEvent(false);
+ if (window_event == SDL_WINDOWEVENT_CLOSE || window_event == SDL_WINDOWEVENT_MOVED || window_event == SDL_WINDOWEVENT_RESIZED)
+ if (ImGuiViewport* viewport = ImGui::FindViewportByPlatformHandle((void*)SDL_GetWindowFromID(event->window.windowID)))
+ {
+ if (window_event == SDL_WINDOWEVENT_CLOSE)
+ viewport->PlatformRequestClose = true;
+ if (window_event == SDL_WINDOWEVENT_MOVED)
+ viewport->PlatformRequestMove = true;
+ if (window_event == SDL_WINDOWEVENT_RESIZED)
+ viewport->PlatformRequestResize = true;
+ return true;
+ }
return true;
}
case SDL_CONTROLLERDEVICEADDED:
@@ -404,9 +453,10 @@ bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event)
return false;
}
-static bool ImGui_ImplSDL2_Init(SDL_Window* window, SDL_Renderer* renderer)
+static bool ImGui_ImplSDL2_Init(SDL_Window* window, SDL_Renderer* renderer, void* sdl_gl_context)
{
ImGuiIO& io = ImGui::GetIO();
+ IMGUI_CHECKVERSION();
IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!");
// Check and store if we are on a SDL backend that supports global mouse position
@@ -424,12 +474,23 @@ static bool ImGui_ImplSDL2_Init(SDL_Window* window, SDL_Renderer* renderer)
ImGui_ImplSDL2_Data* bd = IM_NEW(ImGui_ImplSDL2_Data)();
io.BackendPlatformUserData = (void*)bd;
io.BackendPlatformName = "imgui_impl_sdl2";
- io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional)
- io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used)
+ io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional)
+ io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used)
+ if (mouse_can_use_global_state)
+ io.BackendFlags |= ImGuiBackendFlags_PlatformHasViewports; // We can create multi-viewports on the Platform side (optional)
bd->Window = window;
bd->Renderer = renderer;
+
+ // SDL on Linux/OSX doesn't report events for unfocused windows (see https://github.com/ocornut/imgui/issues/4960)
+ // We will use 'MouseCanReportHoveredViewport' to set 'ImGuiBackendFlags_HasMouseHoveredViewport' dynamically each frame.
bd->MouseCanUseGlobalState = mouse_can_use_global_state;
+#ifndef __APPLE__
+ bd->MouseCanReportHoveredViewport = bd->MouseCanUseGlobalState;
+#else
+ bd->MouseCanReportHoveredViewport = false;
+#endif
+ bd->WantUpdateMonitors = true;
io.SetClipboardTextFn = ImGui_ImplSDL2_SetClipboardText;
io.GetClipboardTextFn = ImGui_ImplSDL2_GetClipboardText;
@@ -454,6 +515,7 @@ static bool ImGui_ImplSDL2_Init(SDL_Window* window, SDL_Renderer* renderer)
// Set platform dependent data in viewport
// Our mouse update function expect PlatformHandle to be filled for the main viewport
ImGuiViewport* main_viewport = ImGui::GetMainViewport();
+ main_viewport->PlatformHandle = (void*)window;
main_viewport->PlatformHandleRaw = nullptr;
SDL_SysWMinfo info;
SDL_VERSION(&info.version);
@@ -487,13 +549,17 @@ static bool ImGui_ImplSDL2_Init(SDL_Window* window, SDL_Renderer* renderer)
SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "0");
#endif
+ // We need SDL_CaptureMouse(), SDL_GetGlobalMouseState() from SDL 2.0.4+ to support multiple viewports.
+ // We left the call to ImGui_ImplSDL2_InitPlatformInterface() outside of #ifdef to avoid unused-function warnings.
+ if ((io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) && (io.BackendFlags & ImGuiBackendFlags_PlatformHasViewports))
+ ImGui_ImplSDL2_InitPlatformInterface(window, sdl_gl_context);
+
return true;
}
bool ImGui_ImplSDL2_InitForOpenGL(SDL_Window* window, void* sdl_gl_context)
{
- IM_UNUSED(sdl_gl_context); // Viewport branch will need this.
- return ImGui_ImplSDL2_Init(window, nullptr);
+ return ImGui_ImplSDL2_Init(window, nullptr, sdl_gl_context);
}
bool ImGui_ImplSDL2_InitForVulkan(SDL_Window* window)
@@ -501,7 +567,11 @@ bool ImGui_ImplSDL2_InitForVulkan(SDL_Window* window)
#if !SDL_HAS_VULKAN
IM_ASSERT(0 && "Unsupported");
#endif
- return ImGui_ImplSDL2_Init(window, nullptr);
+ if (!ImGui_ImplSDL2_Init(window, nullptr, nullptr))
+ return false;
+ ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData();
+ bd->UseVulkan = true;
+ return true;
}
bool ImGui_ImplSDL2_InitForD3D(SDL_Window* window)
@@ -509,22 +579,22 @@ bool ImGui_ImplSDL2_InitForD3D(SDL_Window* window)
#if !defined(_WIN32)
IM_ASSERT(0 && "Unsupported");
#endif
- return ImGui_ImplSDL2_Init(window, nullptr);
+ return ImGui_ImplSDL2_Init(window, nullptr, nullptr);
}
bool ImGui_ImplSDL2_InitForMetal(SDL_Window* window)
{
- return ImGui_ImplSDL2_Init(window, nullptr);
+ return ImGui_ImplSDL2_Init(window, nullptr, nullptr);
}
bool ImGui_ImplSDL2_InitForSDLRenderer(SDL_Window* window, SDL_Renderer* renderer)
{
- return ImGui_ImplSDL2_Init(window, renderer);
+ return ImGui_ImplSDL2_Init(window, renderer, nullptr);
}
bool ImGui_ImplSDL2_InitForOther(SDL_Window* window)
{
- return ImGui_ImplSDL2_Init(window, nullptr);
+ return ImGui_ImplSDL2_Init(window, nullptr, nullptr);
}
static void ImGui_ImplSDL2_CloseGamepads();
@@ -535,6 +605,8 @@ void ImGui_ImplSDL2_Shutdown()
IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?");
ImGuiIO& io = ImGui::GetIO();
+ ImGui_ImplSDL2_ShutdownPlatformInterface();
+
if (bd->ClipboardTextData)
SDL_free(bd->ClipboardTextData);
for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++)
@@ -543,10 +615,11 @@ void ImGui_ImplSDL2_Shutdown()
io.BackendPlatformName = nullptr;
io.BackendPlatformUserData = nullptr;
- io.BackendFlags &= ~(ImGuiBackendFlags_HasMouseCursors | ImGuiBackendFlags_HasSetMousePos | ImGuiBackendFlags_HasGamepad);
+ io.BackendFlags &= ~(ImGuiBackendFlags_HasMouseCursors | ImGuiBackendFlags_HasSetMousePos | ImGuiBackendFlags_HasGamepad | ImGuiBackendFlags_PlatformHasViewports | ImGuiBackendFlags_HasMouseHoveredViewport);
IM_DELETE(bd);
}
+// This code is incredibly messy because some of the functions we need for full viewport support are not available in SDL < 2.0.4.
static void ImGui_ImplSDL2_UpdateMouseData()
{
ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData();
@@ -557,25 +630,57 @@ static void ImGui_ImplSDL2_UpdateMouseData()
// SDL_CaptureMouse() let the OS know e.g. that our imgui drag outside the SDL window boundaries shouldn't e.g. trigger other operations outside
SDL_CaptureMouse((bd->MouseButtonsDown != 0) ? SDL_TRUE : SDL_FALSE);
SDL_Window* focused_window = SDL_GetKeyboardFocus();
- const bool is_app_focused = (bd->Window == focused_window);
+ const bool is_app_focused = (focused_window && (bd->Window == focused_window || ImGui::FindViewportByPlatformHandle((void*)focused_window)));
#else
+ SDL_Window* focused_window = bd->Window;
const bool is_app_focused = (SDL_GetWindowFlags(bd->Window) & SDL_WINDOW_INPUT_FOCUS) != 0; // SDL 2.0.3 and non-windowed systems: single-viewport only
#endif
+
if (is_app_focused)
{
// (Optional) Set OS mouse position from Dear ImGui if requested (rarely used, only when ImGuiConfigFlags_NavEnableSetMousePos is enabled by user)
if (io.WantSetMousePos)
- SDL_WarpMouseInWindow(bd->Window, (int)io.MousePos.x, (int)io.MousePos.y);
+ {
+#if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE
+ if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
+ SDL_WarpMouseGlobal((int)io.MousePos.x, (int)io.MousePos.y);
+ else
+#endif
+ SDL_WarpMouseInWindow(bd->Window, (int)io.MousePos.x, (int)io.MousePos.y);
+ }
// (Optional) Fallback to provide mouse position when focused (SDL_MOUSEMOTION already provides this when hovered or captured)
if (bd->MouseCanUseGlobalState && bd->MouseButtonsDown == 0)
{
- int window_x, window_y, mouse_x_global, mouse_y_global;
- SDL_GetGlobalMouseState(&mouse_x_global, &mouse_y_global);
- SDL_GetWindowPosition(bd->Window, &window_x, &window_y);
- io.AddMousePosEvent((float)(mouse_x_global - window_x), (float)(mouse_y_global - window_y));
+ // Single-viewport mode: mouse position in client window coordinates (io.MousePos is (0,0) when the mouse is on the upper-left corner of the app window)
+ // Multi-viewport mode: mouse position in OS absolute coordinates (io.MousePos is (0,0) when the mouse is on the upper-left of the primary monitor)
+ int mouse_x, mouse_y, window_x, window_y;
+ SDL_GetGlobalMouseState(&mouse_x, &mouse_y);
+ if (!(io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable))
+ {
+ SDL_GetWindowPosition(focused_window, &window_x, &window_y);
+ mouse_x -= window_x;
+ mouse_y -= window_y;
+ }
+ io.AddMousePosEvent((float)mouse_x, (float)mouse_y);
}
}
+
+ // (Optional) When using multiple viewports: call io.AddMouseViewportEvent() with the viewport the OS mouse cursor is hovering.
+ // If ImGuiBackendFlags_HasMouseHoveredViewport is not set by the backend, Dear imGui will ignore this field and infer the information using its flawed heuristic.
+ // - [!] SDL backend does NOT correctly ignore viewports with the _NoInputs flag.
+ // Some backend are not able to handle that correctly. If a backend report an hovered viewport that has the _NoInputs flag (e.g. when dragging a window
+ // for docking, the viewport has the _NoInputs flag in order to allow us to find the viewport under), then Dear ImGui is forced to ignore the value reported
+ // by the backend, and use its flawed heuristic to guess the viewport behind.
+ // - [X] SDL backend correctly reports this regardless of another viewport behind focused and dragged from (we need this to find a useful drag and drop target).
+ if (io.BackendFlags & ImGuiBackendFlags_HasMouseHoveredViewport)
+ {
+ ImGuiID mouse_viewport_id = 0;
+ if (SDL_Window* sdl_mouse_window = SDL_GetWindowFromID(bd->MouseWindowID))
+ if (ImGuiViewport* mouse_viewport = ImGui::FindViewportByPlatformHandle((void*)sdl_mouse_window))
+ mouse_viewport_id = mouse_viewport->ID;
+ io.AddMouseViewportEvent(mouse_viewport_id);
+ }
}
static void ImGui_ImplSDL2_UpdateMouseCursor()
@@ -709,6 +814,39 @@ static void ImGui_ImplSDL2_UpdateGamepads()
ImGui_ImplSDL2_UpdateGamepadAnalog(bd, io, ImGuiKey_GamepadRStickDown, SDL_CONTROLLER_AXIS_RIGHTY, +thumb_dead_zone, +32767);
}
+// FIXME: Note that doesn't update with DPI/Scaling change only as SDL2 doesn't have an event for it (SDL3 has).
+static void ImGui_ImplSDL2_UpdateMonitors()
+{
+ ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData();
+ ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
+ platform_io.Monitors.resize(0);
+ bd->WantUpdateMonitors = false;
+ int display_count = SDL_GetNumVideoDisplays();
+ for (int n = 0; n < display_count; n++)
+ {
+ // Warning: the validity of monitor DPI information on Windows depends on the application DPI awareness settings, which generally needs to be set in the manifest or at runtime.
+ ImGuiPlatformMonitor monitor;
+ SDL_Rect r;
+ SDL_GetDisplayBounds(n, &r);
+ monitor.MainPos = monitor.WorkPos = ImVec2((float)r.x, (float)r.y);
+ monitor.MainSize = monitor.WorkSize = ImVec2((float)r.w, (float)r.h);
+#if SDL_HAS_USABLE_DISPLAY_BOUNDS
+ SDL_GetDisplayUsableBounds(n, &r);
+ monitor.WorkPos = ImVec2((float)r.x, (float)r.y);
+ monitor.WorkSize = ImVec2((float)r.w, (float)r.h);
+#endif
+#if SDL_HAS_PER_MONITOR_DPI
+ // FIXME-VIEWPORT: On MacOS SDL reports actual monitor DPI scale, ignoring OS configuration. We may want to set
+ // DpiScale to cocoa_window.backingScaleFactor here.
+ float dpi = 0.0f;
+ if (!SDL_GetDisplayDPI(n, &dpi, nullptr, nullptr))
+ monitor.DpiScale = dpi / 96.0f;
+#endif
+ monitor.PlatformHandle = (void*)(intptr_t)n;
+ platform_io.Monitors.push_back(monitor);
+ }
+}
+
void ImGui_ImplSDL2_NewFrame()
{
ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData();
@@ -729,6 +867,10 @@ void ImGui_ImplSDL2_NewFrame()
if (w > 0 && h > 0)
io.DisplayFramebufferScale = ImVec2((float)display_w / w, (float)display_h / h);
+ // Update monitors
+ if (bd->WantUpdateMonitors)
+ ImGui_ImplSDL2_UpdateMonitors();
+
// Setup time step (we don't use SDL_GetTicks() because it is using millisecond resolution)
// (Accept SDL_GetPerformanceCounter() not returning a monotonically increasing value. Happens in VMs and Emscripten, see #6189, #6114, #3644)
static Uint64 frequency = SDL_GetPerformanceFrequency();
@@ -745,6 +887,13 @@ void ImGui_ImplSDL2_NewFrame()
io.AddMousePosEvent(-FLT_MAX, -FLT_MAX);
}
+ // Our io.AddMouseViewportEvent() calls will only be valid when not capturing.
+ // Technically speaking testing for 'bd->MouseButtonsDown == 0' would be more rygorous, but testing for payload reduces noise and potential side-effects.
+ if (bd->MouseCanReportHoveredViewport && ImGui::GetDragDropPayload() == nullptr)
+ io.BackendFlags |= ImGuiBackendFlags_HasMouseHoveredViewport;
+ else
+ io.BackendFlags &= ~ImGuiBackendFlags_HasMouseHoveredViewport;
+
ImGui_ImplSDL2_UpdateMouseData();
ImGui_ImplSDL2_UpdateMouseCursor();
@@ -752,6 +901,253 @@ void ImGui_ImplSDL2_NewFrame()
ImGui_ImplSDL2_UpdateGamepads();
}
+//--------------------------------------------------------------------------------------------------------
+// MULTI-VIEWPORT / PLATFORM INTERFACE SUPPORT
+// This is an _advanced_ and _optional_ feature, allowing the backend to create and handle multiple viewports simultaneously.
+// If you are new to dear imgui or creating a new binding for dear imgui, it is recommended that you completely ignore this section first..
+//--------------------------------------------------------------------------------------------------------
+
+// Helper structure we store in the void* RendererUserData field of each ImGuiViewport to easily retrieve our backend data.
+struct ImGui_ImplSDL2_ViewportData
+{
+ SDL_Window* Window;
+ Uint32 WindowID;
+ bool WindowOwned;
+ SDL_GLContext GLContext;
+
+ ImGui_ImplSDL2_ViewportData() { Window = nullptr; WindowID = 0; WindowOwned = false; GLContext = nullptr; }
+ ~ImGui_ImplSDL2_ViewportData() { IM_ASSERT(Window == nullptr && GLContext == nullptr); }
+};
+
+static void ImGui_ImplSDL2_CreateWindow(ImGuiViewport* viewport)
+{
+ ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData();
+ ImGui_ImplSDL2_ViewportData* vd = IM_NEW(ImGui_ImplSDL2_ViewportData)();
+ viewport->PlatformUserData = vd;
+
+ ImGuiViewport* main_viewport = ImGui::GetMainViewport();
+ ImGui_ImplSDL2_ViewportData* main_viewport_data = (ImGui_ImplSDL2_ViewportData*)main_viewport->PlatformUserData;
+
+ // Share GL resources with main context
+ bool use_opengl = (main_viewport_data->GLContext != nullptr);
+ SDL_GLContext backup_context = nullptr;
+ if (use_opengl)
+ {
+ backup_context = SDL_GL_GetCurrentContext();
+ SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);
+ SDL_GL_MakeCurrent(main_viewport_data->Window, main_viewport_data->GLContext);
+ }
+
+ Uint32 sdl_flags = 0;
+ sdl_flags |= use_opengl ? SDL_WINDOW_OPENGL : (bd->UseVulkan ? SDL_WINDOW_VULKAN : 0);
+ sdl_flags |= SDL_GetWindowFlags(bd->Window) & SDL_WINDOW_ALLOW_HIGHDPI;
+ sdl_flags |= SDL_WINDOW_HIDDEN;
+ sdl_flags |= (viewport->Flags & ImGuiViewportFlags_NoDecoration) ? SDL_WINDOW_BORDERLESS : 0;
+ sdl_flags |= (viewport->Flags & ImGuiViewportFlags_NoDecoration) ? 0 : SDL_WINDOW_RESIZABLE;
+#if !defined(_WIN32)
+ // See SDL hack in ImGui_ImplSDL2_ShowWindow().
+ sdl_flags |= (viewport->Flags & ImGuiViewportFlags_NoTaskBarIcon) ? SDL_WINDOW_SKIP_TASKBAR : 0;
+#endif
+#if SDL_HAS_ALWAYS_ON_TOP
+ sdl_flags |= (viewport->Flags & ImGuiViewportFlags_TopMost) ? SDL_WINDOW_ALWAYS_ON_TOP : 0;
+#endif
+ vd->Window = SDL_CreateWindow("No Title Yet", (int)viewport->Pos.x, (int)viewport->Pos.y, (int)viewport->Size.x, (int)viewport->Size.y, sdl_flags);
+ vd->WindowOwned = true;
+ if (use_opengl)
+ {
+ vd->GLContext = SDL_GL_CreateContext(vd->Window);
+ SDL_GL_SetSwapInterval(0);
+ }
+ if (use_opengl && backup_context)
+ SDL_GL_MakeCurrent(vd->Window, backup_context);
+
+ viewport->PlatformHandle = (void*)vd->Window;
+ viewport->PlatformHandleRaw = nullptr;
+ SDL_SysWMinfo info;
+ SDL_VERSION(&info.version);
+ if (SDL_GetWindowWMInfo(vd->Window, &info))
+ {
+#if defined(SDL_VIDEO_DRIVER_WINDOWS)
+ viewport->PlatformHandleRaw = info.info.win.window;
+#elif defined(__APPLE__) && defined(SDL_VIDEO_DRIVER_COCOA)
+ viewport->PlatformHandleRaw = (void*)info.info.cocoa.window;
+#endif
+ }
+}
+
+static void ImGui_ImplSDL2_DestroyWindow(ImGuiViewport* viewport)
+{
+ if (ImGui_ImplSDL2_ViewportData* vd = (ImGui_ImplSDL2_ViewportData*)viewport->PlatformUserData)
+ {
+ if (vd->GLContext && vd->WindowOwned)
+ SDL_GL_DeleteContext(vd->GLContext);
+ if (vd->Window && vd->WindowOwned)
+ SDL_DestroyWindow(vd->Window);
+ vd->GLContext = nullptr;
+ vd->Window = nullptr;
+ IM_DELETE(vd);
+ }
+ viewport->PlatformUserData = viewport->PlatformHandle = nullptr;
+}
+
+static void ImGui_ImplSDL2_ShowWindow(ImGuiViewport* viewport)
+{
+ ImGui_ImplSDL2_ViewportData* vd = (ImGui_ImplSDL2_ViewportData*)viewport->PlatformUserData;
+#if defined(_WIN32)
+ HWND hwnd = (HWND)viewport->PlatformHandleRaw;
+
+ // SDL hack: Hide icon from task bar
+ // Note: SDL 2.0.6+ has a SDL_WINDOW_SKIP_TASKBAR flag which is supported under Windows but the way it create the window breaks our seamless transition.
+ if (viewport->Flags & ImGuiViewportFlags_NoTaskBarIcon)
+ {
+ LONG ex_style = ::GetWindowLong(hwnd, GWL_EXSTYLE);
+ ex_style &= ~WS_EX_APPWINDOW;
+ ex_style |= WS_EX_TOOLWINDOW;
+ ::SetWindowLong(hwnd, GWL_EXSTYLE, ex_style);
+ }
+
+ // SDL hack: SDL always activate/focus windows :/
+ if (viewport->Flags & ImGuiViewportFlags_NoFocusOnAppearing)
+ {
+ ::ShowWindow(hwnd, SW_SHOWNA);
+ return;
+ }
+#endif
+
+ SDL_ShowWindow(vd->Window);
+}
+
+static ImVec2 ImGui_ImplSDL2_GetWindowPos(ImGuiViewport* viewport)
+{
+ ImGui_ImplSDL2_ViewportData* vd = (ImGui_ImplSDL2_ViewportData*)viewport->PlatformUserData;
+ int x = 0, y = 0;
+ SDL_GetWindowPosition(vd->Window, &x, &y);
+ return ImVec2((float)x, (float)y);
+}
+
+static void ImGui_ImplSDL2_SetWindowPos(ImGuiViewport* viewport, ImVec2 pos)
+{
+ ImGui_ImplSDL2_ViewportData* vd = (ImGui_ImplSDL2_ViewportData*)viewport->PlatformUserData;
+ SDL_SetWindowPosition(vd->Window, (int)pos.x, (int)pos.y);
+}
+
+static ImVec2 ImGui_ImplSDL2_GetWindowSize(ImGuiViewport* viewport)
+{
+ ImGui_ImplSDL2_ViewportData* vd = (ImGui_ImplSDL2_ViewportData*)viewport->PlatformUserData;
+ int w = 0, h = 0;
+ SDL_GetWindowSize(vd->Window, &w, &h);
+ return ImVec2((float)w, (float)h);
+}
+
+static void ImGui_ImplSDL2_SetWindowSize(ImGuiViewport* viewport, ImVec2 size)
+{
+ ImGui_ImplSDL2_ViewportData* vd = (ImGui_ImplSDL2_ViewportData*)viewport->PlatformUserData;
+ SDL_SetWindowSize(vd->Window, (int)size.x, (int)size.y);
+}
+
+static void ImGui_ImplSDL2_SetWindowTitle(ImGuiViewport* viewport, const char* title)
+{
+ ImGui_ImplSDL2_ViewportData* vd = (ImGui_ImplSDL2_ViewportData*)viewport->PlatformUserData;
+ SDL_SetWindowTitle(vd->Window, title);
+}
+
+#if SDL_HAS_WINDOW_ALPHA
+static void ImGui_ImplSDL2_SetWindowAlpha(ImGuiViewport* viewport, float alpha)
+{
+ ImGui_ImplSDL2_ViewportData* vd = (ImGui_ImplSDL2_ViewportData*)viewport->PlatformUserData;
+ SDL_SetWindowOpacity(vd->Window, alpha);
+}
+#endif
+
+static void ImGui_ImplSDL2_SetWindowFocus(ImGuiViewport* viewport)
+{
+ ImGui_ImplSDL2_ViewportData* vd = (ImGui_ImplSDL2_ViewportData*)viewport->PlatformUserData;
+ SDL_RaiseWindow(vd->Window);
+}
+
+static bool ImGui_ImplSDL2_GetWindowFocus(ImGuiViewport* viewport)
+{
+ ImGui_ImplSDL2_ViewportData* vd = (ImGui_ImplSDL2_ViewportData*)viewport->PlatformUserData;
+ return (SDL_GetWindowFlags(vd->Window) & SDL_WINDOW_INPUT_FOCUS) != 0;
+}
+
+static bool ImGui_ImplSDL2_GetWindowMinimized(ImGuiViewport* viewport)
+{
+ ImGui_ImplSDL2_ViewportData* vd = (ImGui_ImplSDL2_ViewportData*)viewport->PlatformUserData;
+ return (SDL_GetWindowFlags(vd->Window) & SDL_WINDOW_MINIMIZED) != 0;
+}
+
+static void ImGui_ImplSDL2_RenderWindow(ImGuiViewport* viewport, void*)
+{
+ ImGui_ImplSDL2_ViewportData* vd = (ImGui_ImplSDL2_ViewportData*)viewport->PlatformUserData;
+ if (vd->GLContext)
+ SDL_GL_MakeCurrent(vd->Window, vd->GLContext);
+}
+
+static void ImGui_ImplSDL2_SwapBuffers(ImGuiViewport* viewport, void*)
+{
+ ImGui_ImplSDL2_ViewportData* vd = (ImGui_ImplSDL2_ViewportData*)viewport->PlatformUserData;
+ if (vd->GLContext)
+ {
+ SDL_GL_MakeCurrent(vd->Window, vd->GLContext);
+ SDL_GL_SwapWindow(vd->Window);
+ }
+}
+
+// Vulkan support (the Vulkan renderer needs to call a platform-side support function to create the surface)
+// SDL is graceful enough to _not_ need <vulkan/vulkan.h> so we can safely include this.
+#if SDL_HAS_VULKAN
+#include <SDL_vulkan.h>
+static int ImGui_ImplSDL2_CreateVkSurface(ImGuiViewport* viewport, ImU64 vk_instance, const void* vk_allocator, ImU64* out_vk_surface)
+{
+ ImGui_ImplSDL2_ViewportData* vd = (ImGui_ImplSDL2_ViewportData*)viewport->PlatformUserData;
+ (void)vk_allocator;
+ SDL_bool ret = SDL_Vulkan_CreateSurface(vd->Window, (VkInstance)vk_instance, (VkSurfaceKHR*)out_vk_surface);
+ return ret ? 0 : 1; // ret ? VK_SUCCESS : VK_NOT_READY
+}
+#endif // SDL_HAS_VULKAN
+
+static void ImGui_ImplSDL2_InitPlatformInterface(SDL_Window* window, void* sdl_gl_context)
+{
+ // Register platform interface (will be coupled with a renderer interface)
+ ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
+ platform_io.Platform_CreateWindow = ImGui_ImplSDL2_CreateWindow;
+ platform_io.Platform_DestroyWindow = ImGui_ImplSDL2_DestroyWindow;
+ platform_io.Platform_ShowWindow = ImGui_ImplSDL2_ShowWindow;
+ platform_io.Platform_SetWindowPos = ImGui_ImplSDL2_SetWindowPos;
+ platform_io.Platform_GetWindowPos = ImGui_ImplSDL2_GetWindowPos;
+ platform_io.Platform_SetWindowSize = ImGui_ImplSDL2_SetWindowSize;
+ platform_io.Platform_GetWindowSize = ImGui_ImplSDL2_GetWindowSize;
+ platform_io.Platform_SetWindowFocus = ImGui_ImplSDL2_SetWindowFocus;
+ platform_io.Platform_GetWindowFocus = ImGui_ImplSDL2_GetWindowFocus;
+ platform_io.Platform_GetWindowMinimized = ImGui_ImplSDL2_GetWindowMinimized;
+ platform_io.Platform_SetWindowTitle = ImGui_ImplSDL2_SetWindowTitle;
+ platform_io.Platform_RenderWindow = ImGui_ImplSDL2_RenderWindow;
+ platform_io.Platform_SwapBuffers = ImGui_ImplSDL2_SwapBuffers;
+#if SDL_HAS_WINDOW_ALPHA
+ platform_io.Platform_SetWindowAlpha = ImGui_ImplSDL2_SetWindowAlpha;
+#endif
+#if SDL_HAS_VULKAN
+ platform_io.Platform_CreateVkSurface = ImGui_ImplSDL2_CreateVkSurface;
+#endif
+
+ // Register main window handle (which is owned by the main application, not by us)
+ // This is mostly for simplicity and consistency, so that our code (e.g. mouse handling etc.) can use same logic for main and secondary viewports.
+ ImGuiViewport* main_viewport = ImGui::GetMainViewport();
+ ImGui_ImplSDL2_ViewportData* vd = IM_NEW(ImGui_ImplSDL2_ViewportData)();
+ vd->Window = window;
+ vd->WindowID = SDL_GetWindowID(window);
+ vd->WindowOwned = false;
+ vd->GLContext = sdl_gl_context;
+ main_viewport->PlatformUserData = vd;
+ main_viewport->PlatformHandle = vd->Window;
+}
+
+static void ImGui_ImplSDL2_ShutdownPlatformInterface()
+{
+ ImGui::DestroyPlatformWindows();
+}
+
//-----------------------------------------------------------------------------
#if defined(__clang__)
diff --git a/backends/imgui/backends/imgui_impl_sdl2.h b/backends/imgui/backends/imgui_impl_sdl2.h
index 6f15a1ae8a2..2b6d04393d8 100644
--- a/backends/imgui/backends/imgui_impl_sdl2.h
+++ b/backends/imgui/backends/imgui_impl_sdl2.h
@@ -8,7 +8,11 @@
// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy SDL_SCANCODE_* values will also be supported unless IMGUI_DISABLE_OBSOLETE_KEYIO is set]
// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
-// [X] Platform: Basic IME support. App needs to call 'SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");' before SDL_CreateWindow()!.
+// [X] Platform: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
+// Issues:
+// [ ] Platform: Multi-viewport: Minimized windows seems to break mouse wheel events (at least under Windows).
+// [ ] Platform: Multi-viewport: ParentViewportID not honored, and so io.ConfigViewportsNoDefaultParent has no effect (minor).
+// [x] Platform: Basic IME support. App needs to call 'SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");' before SDL_CreateWindow()!.
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
diff --git a/backends/imgui/imgui.cpp b/backends/imgui/imgui.cpp
index 4e3d2ddf753..f1ef235a021 100644
--- a/backends/imgui/imgui.cpp
+++ b/backends/imgui/imgui.cpp
@@ -1,4 +1,4 @@
-// dear imgui, v1.90.6
+// dear imgui, v1.90.7 WIP
// (main code and documentation)
// Help:
@@ -91,6 +91,7 @@ CODE
// [SECTION] SETTINGS
// [SECTION] LOCALIZATION
// [SECTION] VIEWPORTS, PLATFORM WINDOWS
+// [SECTION] DOCKING
// [SECTION] PLATFORM DEPENDENT HELPERS
// [SECTION] METRICS/DEBUGGER WINDOW
// [SECTION] DEBUG LOG WINDOW
@@ -143,7 +144,7 @@ CODE
- CTRL+X, CTRL+C, CTRL+V: Use OS clipboard.
- CTRL+Z, CTRL+Y: Undo, Redo.
- ESCAPE: Revert text to its original value.
- - On OSX, controls are automatically adjusted to match standard OSX text editing shortcuts and behaviors.
+ - On OSX, controls are automatically adjusted to match standard OSX text editing 2ts and behaviors.
- KEYBOARD CONTROLS
- Basic:
@@ -430,6 +431,32 @@ CODE
When you are not sure about an old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all imgui files.
You can read releases logs https://github.com/ocornut/imgui/releases for more details.
+(Docking/Viewport Branch)
+ - 2024/XX/XX (1.XXXX) - when multi-viewports are enabled, all positions will be in your natural OS coordinates space. It means that:
+ - reference to hard-coded positions such as in SetNextWindowPos(ImVec2(0,0)) are probably not what you want anymore.
+ you may use GetMainViewport()->Pos to offset hard-coded positions, e.g. SetNextWindowPos(GetMainViewport()->Pos)
+ - likewise io.MousePos and GetMousePos() will use OS coordinates.
+ If you query mouse positions to interact with non-imgui coordinates you will need to offset them, e.g. subtract GetWindowViewport()->Pos.
+
+ - 2024/05/22 (1.90.7) - inputs (internals): renamed ImGuiKeyOwner_None to ImGuiKeyOwner_NoOwner, to make use more explicit and reduce confusion with the default it is a non-zero value and cannot be the default value (never made public, but disclosing as I expect a few users caught on owner-aware inputs).
+ - inputs (internals): renamed ImGuiInputFlags_RouteGlobalLow -> ImGuiInputFlags_RouteGlobal, ImGuiInputFlags_RouteGlobal -> ImGuiInputFlags_RouteGlobalOverFocused, ImGuiInputFlags_RouteGlobalHigh -> ImGuiInputFlags_RouteGlobalHighest.
+ - inputs (internals): Shortcut(), SetShortcutRouting(): swapped last two parameters order in function signatures:
+ - old: Shortcut(ImGuiKeyChord key_chord, ImGuiID owner_id = 0, ImGuiInputFlags flags = 0);
+ - new: Shortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags = 0, ImGuiID owner_id = 0);
+ - inputs (internals): owner-aware versions of IsKeyPressed(), IsKeyChordPressed(), IsMouseClicked(): swapped last two parameters order in function signatures.
+ - old: IsKeyPressed(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags = 0);
+ - new: IsKeyPressed(ImGuiKey key, ImGuiInputFlags flags, ImGuiID owner_id = 0);
+ - old: IsMouseClicked(ImGuiMouseButton button, ImGuiID owner_id, ImGuiInputFlags flags = 0);
+ - new: IsMouseClicked(ImGuiMouseButton button, ImGuiInputFlags flags, ImGuiID owner_id = 0);
+ for various reasons those changes makes sense. They are being made because making some of those API public.
+ only past users of imgui_internal.h with the extra parameters will be affected. Added asserts for valid flags in various functions to detect _some_ misuses, BUT NOT ALL.
+ - 2024/05/21 (1.90.7) - docking: changed signature of DockSpaceOverViewport() to add explicit dockspace id if desired. pass 0 to use old behavior. (#7611)
+ - old: DockSpaceOverViewport(const ImGuiViewport* viewport = NULL, ImGuiDockNodeFlags flags = 0, ...);
+ - new: DockSpaceOverViewport(ImGuiID dockspace_id = 0, const ImGuiViewport* viewport = NULL, ImGuiDockNodeFlags flags = 0, ...);
+ - 2024/05/16 (1.90.7) - inputs: on macOS X, Cmd and Ctrl keys are now automatically swapped by io.AddKeyEvent() as this naturally align with how macOS X uses those keys.
+ - it shouldn't really affect you unless you had custom shortcut swapping in place for macOS X apps.
+ - removed ImGuiMod_Shortcut which was previously dynamically remapping to Ctrl or Cmd/Super. It is now unnecessary to specific cross-platform idiomatic shortcuts. (#2343, #4084, #5923, #456)
+ - 2024/05/14 (1.90.7) - backends: SDL_Renderer2 and SDL_Renderer3 backend now take a SDL_Renderer* in their RenderDrawData() functions.
- 2024/04/18 (1.90.6) - TreeNode: Fixed a layout inconsistency when using an empty/hidden label followed by a SameLine() call. (#7505, #282)
- old: TreeNode("##Hidden"); SameLine(); Text("Hello"); // <-- This was actually incorrect! BUT appeared to look ok with the default style where ItemSpacing.x == FramePadding.x * 2 (it didn't look aligned otherwise).
- new: TreeNode("##Hidden"); SameLine(0, 0); Text("Hello"); // <-- This is correct for all styles values.
@@ -551,7 +578,7 @@ CODE
- Backend writing to io.MouseHoveredViewport -> backend should call io.AddMouseViewportEvent() [Docking branch w/ multi-viewports only]
note: for all calls to IO new functions, the Dear ImGui context should be bound/current.
read https://github.com/ocornut/imgui/issues/4921 for details.
- - 2022/01/10 (1.87) - inputs: reworked keyboard IO. Removed io.KeyMap[], io.KeysDown[] in favor of calling io.AddKeyEvent(). Removed GetKeyIndex(), now unecessary. All IsKeyXXX() functions now take ImGuiKey values. All features are still functional until IMGUI_DISABLE_OBSOLETE_KEYIO is defined. Read Changelog and Release Notes for details.
+ - 2022/01/10 (1.87) - inputs: reworked keyboard IO. Removed io.KeyMap[], io.KeysDown[] in favor of calling io.AddKeyEvent(). Removed GetKeyIndex(), now unnecessary. All IsKeyXXX() functions now take ImGuiKey values. All features are still functional until IMGUI_DISABLE_OBSOLETE_KEYIO is defined. Read Changelog and Release Notes for details.
- IsKeyPressed(MY_NATIVE_KEY_XXX) -> use IsKeyPressed(ImGuiKey_XXX)
- IsKeyPressed(GetKeyIndex(ImGuiKey_XXX)) -> use IsKeyPressed(ImGuiKey_XXX)
- Backend writing to io.KeyMap[],io.KeysDown[] -> backend should call io.AddKeyEvent() (+ call io.SetKeyEventNativeData() if you want legacy user code to stil function with legacy key codes).
@@ -1000,7 +1027,8 @@ CODE
#else
#include <windows.h>
#endif
-#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) // UWP doesn't have all Win32 functions
+#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP || WINAPI_FAMILY == WINAPI_FAMILY_GAMES)
+// The UWP and GDK Win32 API subsets don't support clipboard nor IME functions
#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS
#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS
#endif
@@ -1072,12 +1100,14 @@ static const float WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER = 0.70f; // Lock
// Tooltip offset
static const ImVec2 TOOLTIP_DEFAULT_OFFSET = ImVec2(16, 10); // Multiplied by g.Style.MouseCursorScale
+// Docking
+static const float DOCKING_TRANSPARENT_PAYLOAD_ALPHA = 0.50f; // For use with io.ConfigDockingTransparentPayload. Apply to Viewport _or_ WindowBg in host viewport.
+
//-------------------------------------------------------------------------
// [SECTION] FORWARD DECLARATIONS
//-------------------------------------------------------------------------
static void SetCurrentWindow(ImGuiWindow* window);
-static void FindHoveredWindow();
static ImGuiWindow* CreateNewWindow(const char* name, ImGuiWindowFlags flags);
static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window);
@@ -1097,6 +1127,9 @@ static void SetPlatformImeDataFn_DefaultImpl(ImGuiViewport* viewport
namespace ImGui
{
+// Item
+static void ItemHandleShortcut(ImGuiID id);
+
// Navigation
static void NavUpdate();
static void NavUpdateWindowing();
@@ -1143,7 +1176,18 @@ static void SetLastItemDataForWindow(ImGuiWindow* window, const ImRe
// Viewports
const ImGuiID IMGUI_VIEWPORT_DEFAULT_ID = 0x11111111; // Using an arbitrary constant instead of e.g. ImHashStr("ViewportDefault", 0); so it's easier to spot in the debugger. The exact value doesn't matter.
+static ImGuiViewportP* AddUpdateViewport(ImGuiWindow* window, ImGuiID id, const ImVec2& platform_pos, const ImVec2& size, ImGuiViewportFlags flags);
+static void DestroyViewport(ImGuiViewportP* viewport);
static void UpdateViewportsNewFrame();
+static void UpdateViewportsEndFrame();
+static void WindowSelectViewport(ImGuiWindow* window);
+static void WindowSyncOwnedViewport(ImGuiWindow* window, ImGuiWindow* parent_window_in_stack);
+static bool UpdateTryMergeWindowIntoHostViewport(ImGuiWindow* window, ImGuiViewportP* host_viewport);
+static bool UpdateTryMergeWindowIntoHostViewports(ImGuiWindow* window);
+static bool GetWindowAlwaysWantOwnViewport(ImGuiWindow* window);
+static int FindPlatformMonitorForPos(const ImVec2& pos);
+static int FindPlatformMonitorForRect(const ImRect& r);
+static void UpdateViewportPlatformMonitor(ImGuiViewportP* viewport);
}
@@ -1235,6 +1279,7 @@ ImGuiStyle::ImGuiStyle()
SeparatorTextPadding = ImVec2(20.0f,3.f);// Horizontal offset of text from each edge of the separator + spacing on other axis. Generally small values. .y is recommended to be == FramePadding.y.
DisplayWindowPadding = ImVec2(19,19); // Window position are clamped to be visible within the display area or monitors by at least this amount. Only applies to regular windows.
DisplaySafeAreaPadding = ImVec2(3,3); // If you cannot see the edge of your screen (e.g. on a TV) increase the safe area padding. Covers popups/tooltips as well regular windows.
+ DockingSeparatorSize = 2.0f; // Thickness of resizing border between docked windows
MouseCursorScale = 1.0f; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later.
AntiAliasedLines = true; // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU.
AntiAliasedLinesUseTex = true; // Enable anti-aliased lines/borders using textures where possible. Require backend to render with bilinear filtering (NOT point/nearest filtering).
@@ -1278,6 +1323,7 @@ void ImGuiStyle::ScaleAllSizes(float scale_factor)
TabRounding = ImTrunc(TabRounding * scale_factor);
TabMinWidthForCloseButton = (TabMinWidthForCloseButton != FLT_MAX) ? ImTrunc(TabMinWidthForCloseButton * scale_factor) : FLT_MAX;
SeparatorTextPadding = ImTrunc(SeparatorTextPadding * scale_factor);
+ DockingSeparatorSize = ImTrunc(DockingSeparatorSize * scale_factor);
DisplayWindowPadding = ImTrunc(DisplayWindowPadding * scale_factor);
DisplaySafeAreaPadding = ImTrunc(DisplaySafeAreaPadding * scale_factor);
MouseCursorScale = ImTrunc(MouseCursorScale * scale_factor);
@@ -1309,11 +1355,17 @@ ImGuiIO::ImGuiIO()
FontAllowUserScaling = false;
DisplayFramebufferScale = ImVec2(1.0f, 1.0f);
- MouseDoubleClickTime = 0.30f;
- MouseDoubleClickMaxDist = 6.0f;
- MouseDragThreshold = 6.0f;
- KeyRepeatDelay = 0.275f;
- KeyRepeatRate = 0.050f;
+ // Docking options (when ImGuiConfigFlags_DockingEnable is set)
+ ConfigDockingNoSplit = false;
+ ConfigDockingWithShift = false;
+ ConfigDockingAlwaysTabBar = false;
+ ConfigDockingTransparentPayload = false;
+
+ // Viewport options (when ImGuiConfigFlags_ViewportsEnable is set)
+ ConfigViewportsNoAutoMerge = false;
+ ConfigViewportsNoTaskBarIcon = false;
+ ConfigViewportsNoDecoration = true;
+ ConfigViewportsNoDefaultParent = false;
// Miscellaneous options
MouseDrawCursor = false;
@@ -1332,6 +1384,13 @@ ImGuiIO::ImGuiIO()
ConfigDebugBeginReturnValueOnce = false;
ConfigDebugBeginReturnValueLoop = false;
+ // Inputs Behaviors
+ MouseDoubleClickTime = 0.30f;
+ MouseDoubleClickMaxDist = 6.0f;
+ MouseDragThreshold = 6.0f;
+ KeyRepeatDelay = 0.275f;
+ KeyRepeatRate = 0.050f;
+
// Platform Functions
// Note: Initialize() will setup default clipboard/ime handlers.
BackendPlatformName = BackendRendererName = NULL;
@@ -1479,7 +1538,7 @@ static ImGuiInputEvent* FindLatestInputEvent(ImGuiContext* ctx, ImGuiInputEventT
// - bool down: Is the key down? use false to signify a key release.
// - float analog_value: 0.0f..1.0f
// IMPORTANT: THIS FUNCTION AND OTHER "ADD" GRABS THE CONTEXT FROM OUR INSTANCE.
-// WE NEED TO ENSURE THAT ALL FUNCTION CALLS ARE FULLFILLING THIS, WHICH IS WHY GetKeyData() HAS AN EXPLICIT CONTEXT.
+// WE NEED TO ENSURE THAT ALL FUNCTION CALLS ARE FULFILLING THIS, WHICH IS WHY GetKeyData() HAS AN EXPLICIT CONTEXT.
void ImGuiIO::AddKeyAnalogEvent(ImGuiKey key, bool down, float analog_value)
{
//if (e->Down) { IMGUI_DEBUG_LOG_IO("AddKeyEvent() Key='%s' %d, NativeKeycode = %d, NativeScancode = %d\n", ImGui::GetKeyName(e->Key), e->Down, e->NativeKeycode, e->NativeScancode); }
@@ -1487,9 +1546,19 @@ void ImGuiIO::AddKeyAnalogEvent(ImGuiKey key, bool down, float analog_value)
if (key == ImGuiKey_None || !AppAcceptingEvents)
return;
ImGuiContext& g = *Ctx;
- IM_ASSERT(ImGui::IsNamedKeyOrModKey(key)); // Backend needs to pass a valid ImGuiKey_ constant. 0..511 values are legacy native key codes which are not accepted by this API.
+ IM_ASSERT(ImGui::IsNamedKeyOrMod(key)); // Backend needs to pass a valid ImGuiKey_ constant. 0..511 values are legacy native key codes which are not accepted by this API.
IM_ASSERT(ImGui::IsAliasKey(key) == false); // Backend cannot submit ImGuiKey_MouseXXX values they are automatically inferred from AddMouseXXX() events.
- IM_ASSERT(key != ImGuiMod_Shortcut); // We could easily support the translation here but it seems saner to not accept it (TestEngine perform a translation itself)
+
+ // MacOS: swap Cmd(Super) and Ctrl
+ if (g.IO.ConfigMacOSXBehaviors)
+ {
+ if (key == ImGuiMod_Super) { key = ImGuiMod_Ctrl; }
+ else if (key == ImGuiMod_Ctrl) { key = ImGuiMod_Super; }
+ else if (key == ImGuiKey_LeftSuper) { key = ImGuiKey_LeftCtrl; }
+ else if (key == ImGuiKey_RightSuper){ key = ImGuiKey_RightCtrl; }
+ else if (key == ImGuiKey_LeftCtrl) { key = ImGuiKey_LeftSuper; }
+ else if (key == ImGuiKey_RightCtrl) { key = ImGuiKey_RightSuper; }
+ }
// Verify that backend isn't mixing up using new io.AddKeyEvent() api and old io.KeysDown[] + io.KeyMap[] data.
#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO
@@ -1594,12 +1663,36 @@ void ImGuiIO::AddMouseButtonEvent(int mouse_button, bool down)
if (!AppAcceptingEvents)
return;
+ // On MacOS X: Convert Ctrl(Super)+Left click into Right-click: handle held button.
+ if (ConfigMacOSXBehaviors && mouse_button == 0 && MouseCtrlLeftAsRightClick)
+ {
+ // Order of both statements matterns: this event will still release mouse button 1
+ mouse_button = 1;
+ if (!down)
+ MouseCtrlLeftAsRightClick = false;
+ }
+
// Filter duplicate
const ImGuiInputEvent* latest_event = FindLatestInputEvent(&g, ImGuiInputEventType_MouseButton, (int)mouse_button);
const bool latest_button_down = latest_event ? latest_event->MouseButton.Down : g.IO.MouseDown[mouse_button];
if (latest_button_down == down)
return;
+ // On MacOS X: Convert Ctrl(Super)+Left click into Right-click.
+ // - Note that this is actual physical Ctrl which is ImGuiMod_Super for us.
+ // - At this point we want from !down to down, so this is handling the initial press.
+ if (ConfigMacOSXBehaviors && mouse_button == 0 && down)
+ {
+ const ImGuiInputEvent* latest_super_event = FindLatestInputEvent(&g, ImGuiInputEventType_Key, (int)ImGuiMod_Super);
+ if (latest_super_event ? latest_super_event->Key.Down : g.IO.KeySuper)
+ {
+ IMGUI_DEBUG_LOG_IO("[io] Super+Left Click aliased into Right Click\n");
+ MouseCtrlLeftAsRightClick = true;
+ AddMouseButtonEvent(1, true); // This is just quicker to write that passing through, as we need to filter duplicate again.
+ return;
+ }
+ }
+
ImGuiInputEvent e;
e.Type = ImGuiInputEventType_MouseButton;
e.Source = ImGuiInputSource_Mouse;
@@ -1639,6 +1732,27 @@ void ImGuiIO::AddMouseSourceEvent(ImGuiMouseSource source)
g.InputEventsNextMouseSource = source;
}
+void ImGuiIO::AddMouseViewportEvent(ImGuiID viewport_id)
+{
+ IM_ASSERT(Ctx != NULL);
+ ImGuiContext& g = *Ctx;
+ //IM_ASSERT(g.IO.BackendFlags & ImGuiBackendFlags_HasMouseHoveredViewport);
+ if (!AppAcceptingEvents)
+ return;
+
+ // Filter duplicate
+ const ImGuiInputEvent* latest_event = FindLatestInputEvent(&g, ImGuiInputEventType_MouseViewport);
+ const ImGuiID latest_viewport_id = latest_event ? latest_event->MouseViewport.HoveredViewportID : g.IO.MouseHoveredViewport;
+ if (latest_viewport_id == viewport_id)
+ return;
+
+ ImGuiInputEvent e;
+ e.Type = ImGuiInputEventType_MouseViewport;
+ e.Source = ImGuiInputSource_Mouse;
+ e.MouseViewport.HoveredViewportID = viewport_id;
+ g.InputEventsQueue.push_back(e);
+}
+
void ImGuiIO::AddFocusEvent(bool focused)
{
IM_ASSERT(Ctx != NULL);
@@ -3129,6 +3243,11 @@ void ImGui::PopStyleColor(int count)
}
}
+static const ImGuiCol GWindowDockStyleColors[ImGuiWindowDockStyleCol_COUNT] =
+{
+ ImGuiCol_Text, ImGuiCol_Tab, ImGuiCol_TabHovered, ImGuiCol_TabActive, ImGuiCol_TabUnfocused, ImGuiCol_TabUnfocusedActive
+};
+
static const ImGuiDataVarInfo GStyleVarInfo[] =
{
{ ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, Alpha) }, // ImGuiStyleVar_Alpha
@@ -3163,6 +3282,7 @@ static const ImGuiDataVarInfo GStyleVarInfo[] =
{ ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, SeparatorTextBorderSize)}, // ImGuiStyleVar_SeparatorTextBorderSize
{ ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, SeparatorTextAlign) }, // ImGuiStyleVar_SeparatorTextAlign
{ ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, SeparatorTextPadding) }, // ImGuiStyleVar_SeparatorTextPadding
+ { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, DockingSeparatorSize) }, // ImGuiStyleVar_DockingSeparatorSize
};
const ImGuiDataVarInfo* ImGui::GetStyleVarInfo(ImGuiStyleVar idx)
@@ -3264,6 +3384,8 @@ const char* ImGui::GetStyleColorName(ImGuiCol idx)
case ImGuiCol_TabActive: return "TabActive";
case ImGuiCol_TabUnfocused: return "TabUnfocused";
case ImGuiCol_TabUnfocusedActive: return "TabUnfocusedActive";
+ case ImGuiCol_DockingPreview: return "DockingPreview";
+ case ImGuiCol_DockingEmptyBg: return "DockingEmptyBg";
case ImGuiCol_PlotLines: return "PlotLines";
case ImGuiCol_PlotLinesHovered: return "PlotLinesHovered";
case ImGuiCol_PlotHistogram: return "PlotHistogram";
@@ -3524,7 +3646,7 @@ void ImGui::RenderMouseCursor(ImVec2 base_pos, float base_scale, ImGuiMouseCurso
if (!font_atlas->GetMouseCursorTexData(mouse_cursor, &offset, &size, &uv[0], &uv[2]))
continue;
const ImVec2 pos = base_pos - offset;
- const float scale = base_scale;
+ const float scale = base_scale * viewport->DpiScale;
if (!viewport->GetMainRect().Overlaps(ImRect(pos, pos + ImVec2(size.x + 2, size.y + 2) * scale)))
continue;
ImDrawList* draw_list = GetForegroundDrawList(viewport);
@@ -3606,6 +3728,9 @@ static const ImGuiLocEntry GLocalizationEntriesEnUS[] =
{ ImGuiLocKey_WindowingMainMenuBar, "(Main menu bar)" },
{ ImGuiLocKey_WindowingPopup, "(Popup)" },
{ ImGuiLocKey_WindowingUntitled, "(Untitled)" },
+ { ImGuiLocKey_DockingHideTabBar, "Hide tab bar###HideTabBar" },
+ { ImGuiLocKey_DockingHoldShiftToDock, "Hold SHIFT to enable Docking window." },
+ { ImGuiLocKey_DockingDragToUndockOrMoveNode,"Click and drag to move or undock whole node." },
};
void ImGui::Initialize()
@@ -3639,8 +3764,13 @@ void ImGui::Initialize()
// Create default viewport
ImGuiViewportP* viewport = IM_NEW(ImGuiViewportP)();
viewport->ID = IMGUI_VIEWPORT_DEFAULT_ID;
+ viewport->Idx = 0;
+ viewport->PlatformWindowCreated = true;
+ viewport->Flags = ImGuiViewportFlags_OwnedByApp;
g.Viewports.push_back(viewport);
g.TempBuffer.resize(1024 * 3 + 1, 0);
+ g.ViewportCreatedCount++;
+ g.PlatformIO.Viewports.push_back(g.Viewports[0]);
// Build KeysMayBeCharInput[] lookup table (1 bool per named key)
for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1))
@@ -3651,6 +3781,8 @@ void ImGui::Initialize()
g.KeysMayBeCharInput.SetBit(key);
#ifdef IMGUI_HAS_DOCK
+ // Initialize Docking
+ DockContextInitialize(&g);
#endif
g.Initialized = true;
@@ -3680,6 +3812,12 @@ void ImGui::Shutdown()
if (g.SettingsLoaded && g.IO.IniFilename != NULL)
SaveIniSettingsToDisk(g.IO.IniFilename);
+ // Destroy platform windows
+ DestroyPlatformWindows();
+
+ // Shutdown extensions
+ DockContextShutdown(&g);
+
CallContextHooks(&g, ImGuiContextHookType_Shutdown);
// Clear everything else
@@ -3703,6 +3841,7 @@ void ImGui::Shutdown()
g.BeginPopupStack.clear();
g.NavTreeNodeStack.clear();
+ g.CurrentViewport = g.MouseViewport = g.MouseLastHoveredViewport = NULL;
g.Viewports.clear_delete();
g.TabBars.Clear();
@@ -3782,21 +3921,27 @@ ImGuiWindow::ImGuiWindow(ImGuiContext* ctx, const char* name) : DrawListInst(NUL
NameBufLen = (int)strlen(name) + 1;
ID = ImHashStr(name);
IDStack.push_back(ID);
+ ViewportAllowPlatformMonitorExtend = -1;
+ ViewportPos = ImVec2(FLT_MAX, FLT_MAX);
MoveId = GetID("#MOVE");
+ TabId = GetID("#TAB");
ScrollTarget = ImVec2(FLT_MAX, FLT_MAX);
ScrollTargetCenterRatio = ImVec2(0.5f, 0.5f);
AutoFitFramesX = AutoFitFramesY = -1;
AutoPosLastDirection = ImGuiDir_None;
- SetWindowPosAllowFlags = SetWindowSizeAllowFlags = SetWindowCollapsedAllowFlags = 0;
+ SetWindowPosAllowFlags = SetWindowSizeAllowFlags = SetWindowCollapsedAllowFlags = SetWindowDockAllowFlags = 0;
SetWindowPosVal = SetWindowPosPivot = ImVec2(FLT_MAX, FLT_MAX);
LastFrameActive = -1;
+ LastFrameJustFocused = -1;
LastTimeActive = -1.0f;
- FontWindowScale = 1.0f;
+ FontWindowScale = FontDpiScale = 1.0f;
SettingsOffset = -1;
+ DockOrder = -1;
DrawList = &DrawListInst;
DrawList->_Data = &Ctx->DrawListSharedData;
DrawList->_OwnerName = Name;
NavPreferredScoringPosRel[0] = NavPreferredScoringPosRel[1] = ImVec2(FLT_MAX, FLT_MAX);
+ IM_PLACEMENT_NEW(&WindowClass) ImGuiWindowClass();
}
ImGuiWindow::~ImGuiWindow()
@@ -3959,8 +4104,8 @@ bool ImGui::IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flag
// FIXME-OPT: This could be cached/stored within the window.
ImGuiContext& g = *GImGui;
if (g.NavWindow)
- if (ImGuiWindow* focused_root_window = g.NavWindow->RootWindow)
- if (focused_root_window->WasActive && focused_root_window != window->RootWindow)
+ if (ImGuiWindow* focused_root_window = g.NavWindow->RootWindowDockTree)
+ if (focused_root_window->WasActive && focused_root_window != window->RootWindowDockTree)
{
// For the purpose of those flags we differentiate "standard popup" from "modal popup"
// NB: The 'else' is important because Modal windows are also Popups.
@@ -3975,6 +4120,12 @@ bool ImGui::IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flag
if (!IsWindowWithinBeginStackOf(window->RootWindow, focused_root_window))
return false;
}
+
+ // Filter by viewport
+ if (window->Viewport != g.MouseViewport)
+ if (g.MovingWindow == NULL || window->RootWindowDockTree != g.MovingWindow->RootWindowDockTree)
+ return false;
+
return true;
}
@@ -4025,7 +4176,7 @@ bool ImGui::IsItemHovered(ImGuiHoveredFlags flags)
if (flags & ImGuiHoveredFlags_ForTooltip)
flags = ApplyHoverFlagsForTooltip(flags, g.Style.HoverFlagsForTooltipMouse);
- IM_ASSERT((flags & (ImGuiHoveredFlags_AnyWindow | ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_NoPopupHierarchy)) == 0); // Flags not supported by this function
+ IM_ASSERT((flags & (ImGuiHoveredFlags_AnyWindow | ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_NoPopupHierarchy | ImGuiHoveredFlags_DockHierarchy)) == 0); // Flags not supported by this function
// Done with rectangle culling so we can perform heavier checks now
// Test if we are hovering the right window (our window could be behind another window)
@@ -4040,8 +4191,9 @@ bool ImGui::IsItemHovered(ImGuiHoveredFlags flags)
// Test if another item is active (e.g. being dragged)
const ImGuiID id = g.LastItemData.ID;
if ((flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem) == 0)
- if (g.ActiveId != 0 && g.ActiveId != id && !g.ActiveIdAllowOverlap && g.ActiveId != window->MoveId)
- return false;
+ if (g.ActiveId != 0 && g.ActiveId != id && !g.ActiveIdAllowOverlap)
+ if (g.ActiveId != window->MoveId && g.ActiveId != window->TabId)
+ return false;
// Test if interactions on this window are blocked by an active popup or modal.
// The ImGuiHoveredFlags_AllowWhenBlockedByPopup flag will be tested here.
@@ -4053,7 +4205,8 @@ bool ImGui::IsItemHovered(ImGuiHoveredFlags flags)
return false;
// Special handling for calling after Begin() which represent the title bar or tab.
- // When the window is skipped/collapsed (SkipItems==true) that last item will never be overwritten so we need to detect the case.
+ // When the window is skipped/collapsed (SkipItems==true) that last item (always ->MoveId submitted by Begin)
+ // will never be overwritten so we need to detect the case.
if (id == window->MoveId && window->WriteAccessed)
return false;
@@ -4133,6 +4286,11 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id, ImGuiItemFlags item_flag
if (g.HoveredIdPreviousFrame != id)
return false;
}
+
+ // Display shortcut (only works with mouse)
+ if (id == g.LastItemData.ID && (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HasShortcut))
+ if (IsItemHovered(ImGuiHoveredFlags_ForTooltip | ImGuiHoveredFlags_DelayNormal))
+ SetTooltip("%s", GetKeyChordName(g.LastItemData.Shortcut));
}
// When disabled we'll return false but still set HoveredId
@@ -4285,6 +4443,12 @@ ImGuiIO& ImGui::GetIO()
return GImGui->IO;
}
+ImGuiPlatformIO& ImGui::GetPlatformIO()
+{
+ IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() or ImGui::SetCurrentContext()?");
+ return GImGui->PlatformIO;
+}
+
// Pass this to your backend rendering function! Valid after Render() and until the next call to NewFrame()
ImDrawData* ImGui::GetDrawData()
{
@@ -4335,7 +4499,7 @@ ImDrawList* ImGui::GetBackgroundDrawList(ImGuiViewport* viewport)
ImDrawList* ImGui::GetBackgroundDrawList()
{
ImGuiContext& g = *GImGui;
- return GetBackgroundDrawList(g.Viewports[0]);
+ return GetBackgroundDrawList(g.CurrentWindow->Viewport);
}
ImDrawList* ImGui::GetForegroundDrawList(ImGuiViewport* viewport)
@@ -4346,7 +4510,7 @@ ImDrawList* ImGui::GetForegroundDrawList(ImGuiViewport* viewport)
ImDrawList* ImGui::GetForegroundDrawList()
{
ImGuiContext& g = *GImGui;
- return GetForegroundDrawList(g.Viewports[0]);
+ return GetForegroundDrawList(g.CurrentWindow->Viewport);
}
ImDrawListSharedData* ImGui::GetDrawListSharedData()
@@ -4363,17 +4527,43 @@ void ImGui::StartMouseMovingWindow(ImGuiWindow* window)
FocusWindow(window);
SetActiveID(window->MoveId, window);
g.NavDisableHighlight = true;
- g.ActiveIdClickOffset = g.IO.MouseClickedPos[0] - window->RootWindow->Pos;
+ g.ActiveIdClickOffset = g.IO.MouseClickedPos[0] - window->RootWindowDockTree->Pos;
g.ActiveIdNoClearOnFocusLoss = true;
SetActiveIdUsingAllKeyboardKeys();
bool can_move_window = true;
- if ((window->Flags & ImGuiWindowFlags_NoMove) || (window->RootWindow->Flags & ImGuiWindowFlags_NoMove))
+ if ((window->Flags & ImGuiWindowFlags_NoMove) || (window->RootWindowDockTree->Flags & ImGuiWindowFlags_NoMove))
can_move_window = false;
+ if (ImGuiDockNode* node = window->DockNodeAsHost)
+ if (node->VisibleWindow && (node->VisibleWindow->Flags & ImGuiWindowFlags_NoMove))
+ can_move_window = false;
if (can_move_window)
g.MovingWindow = window;
}
+// We use 'undock == false' when dragging from title bar to allow moving groups of floating nodes without undocking them.
+void ImGui::StartMouseMovingWindowOrNode(ImGuiWindow* window, ImGuiDockNode* node, bool undock)
+{
+ ImGuiContext& g = *GImGui;
+ bool can_undock_node = false;
+ if (undock && node != NULL && node->VisibleWindow && (node->VisibleWindow->Flags & ImGuiWindowFlags_NoMove) == 0 && (node->MergedFlags & ImGuiDockNodeFlags_NoUndocking) == 0)
+ {
+ // Can undock if:
+ // - part of a hierarchy with more than one visible node (if only one is visible, we'll just move the root window)
+ // - part of a dockspace node hierarchy: so we can undock the last single visible node too (trivia: undocking from a fixed/central node will create a new node and copy windows)
+ ImGuiDockNode* root_node = DockNodeGetRootNode(node);
+ if (root_node->OnlyNodeWithWindows != node || root_node->CentralNode != NULL) // -V1051 PVS-Studio thinks node should be root_node and is wrong about that.
+ can_undock_node = true;
+ }
+
+ const bool clicked = IsMouseClicked(0);
+ const bool dragging = IsMouseDragging(0);
+ if (can_undock_node && dragging)
+ DockContextQueueUndockNode(&g, node); // Will lead to DockNodeStartMouseMovingWindow() -> StartMouseMovingWindow() being called next frame
+ else if (!can_undock_node && (clicked || dragging) && g.MovingWindow != window)
+ StartMouseMovingWindow(window);
+}
+
// Handle mouse moving window
// Note: moving window with the navigation keys (Square + d-pad / CTRL+TAB + Arrows) are processed in NavUpdateWindowing()
// FIXME: We don't have strong guarantee that g.MovingWindow stay synched with g.ActiveId == g.MovingWindow->MoveId.
@@ -4387,16 +4577,43 @@ void ImGui::UpdateMouseMovingWindowNewFrame()
// We actually want to move the root window. g.MovingWindow == window we clicked on (could be a child window).
// We track it to preserve Focus and so that generally ActiveIdWindow == MovingWindow and ActiveId == MovingWindow->MoveId for consistency.
KeepAliveID(g.ActiveId);
- IM_ASSERT(g.MovingWindow && g.MovingWindow->RootWindow);
- ImGuiWindow* moving_window = g.MovingWindow->RootWindow;
- if (g.IO.MouseDown[0] && IsMousePosValid(&g.IO.MousePos))
+ IM_ASSERT(g.MovingWindow && g.MovingWindow->RootWindowDockTree);
+ ImGuiWindow* moving_window = g.MovingWindow->RootWindowDockTree;
+
+ // When a window stop being submitted while being dragged, it may will its viewport until next Begin()
+ const bool window_disappared = (!moving_window->WasActive && !moving_window->Active);
+ if (g.IO.MouseDown[0] && IsMousePosValid(&g.IO.MousePos) && !window_disappared)
{
ImVec2 pos = g.IO.MousePos - g.ActiveIdClickOffset;
- SetWindowPos(moving_window, pos, ImGuiCond_Always);
+ if (moving_window->Pos.x != pos.x || moving_window->Pos.y != pos.y)
+ {
+ SetWindowPos(moving_window, pos, ImGuiCond_Always);
+ if (moving_window->Viewport && moving_window->ViewportOwned) // Synchronize viewport immediately because some overlays may relies on clipping rectangle before we Begin() into the window.
+ {
+ moving_window->Viewport->Pos = pos;
+ moving_window->Viewport->UpdateWorkRect();
+ }
+ }
FocusWindow(g.MovingWindow);
}
else
{
+ if (!window_disappared)
+ {
+ // Try to merge the window back into the main viewport.
+ // This works because MouseViewport should be != MovingWindow->Viewport on release (as per code in UpdateViewports)
+ if (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable)
+ UpdateTryMergeWindowIntoHostViewport(moving_window, g.MouseViewport);
+
+ // Restore the mouse viewport so that we don't hover the viewport _under_ the moved window during the frame we released the mouse button.
+ if (moving_window->Viewport && !IsDragDropPayloadBeingAccepted())
+ g.MouseViewport = moving_window->Viewport;
+
+ // Clear the NoInput window flag set by the Viewport system
+ if (moving_window->Viewport)
+ moving_window->Viewport->Flags &= ~ImGuiViewportFlags_NoInputs;
+ }
+
g.MovingWindow = NULL;
ClearActiveID();
}
@@ -4426,7 +4643,7 @@ void ImGui::UpdateMouseMovingWindowEndFrame()
return;
// Click on empty space to focus window and start moving
- // (after we're done with all our widgets)
+ // (after we're done with all our widgets, so e.g. clicking on docking tab-bar which have set HoveredId already and not get us here!)
if (g.IO.MouseClicked[0])
{
// Handle the edge case of a popup being closed while clicking in its empty space.
@@ -4439,9 +4656,10 @@ void ImGui::UpdateMouseMovingWindowEndFrame()
StartMouseMovingWindow(g.HoveredWindow); //-V595
// Cancel moving if clicked outside of title bar
- if (g.IO.ConfigWindowsMoveFromTitleBarOnly && !(root_window->Flags & ImGuiWindowFlags_NoTitleBar))
- if (!root_window->TitleBarRect().Contains(g.IO.MouseClickedPos[0]))
- g.MovingWindow = NULL;
+ if (g.IO.ConfigWindowsMoveFromTitleBarOnly)
+ if (!(root_window->Flags & ImGuiWindowFlags_NoTitleBar) || root_window->DockIsActive)
+ if (!root_window->TitleBarRect().Contains(g.IO.MouseClickedPos[0]))
+ g.MovingWindow = NULL;
// Cancel moving if clicked over an item which was disabled or inhibited by popups (note that we know HoveredId == 0 already)
if (g.HoveredIdDisabled)
@@ -4467,6 +4685,29 @@ void ImGui::UpdateMouseMovingWindowEndFrame()
}
}
+// This is called during NewFrame()->UpdateViewportsNewFrame() only.
+// Need to keep in sync with SetWindowPos()
+static void TranslateWindow(ImGuiWindow* window, const ImVec2& delta)
+{
+ window->Pos += delta;
+ window->ClipRect.Translate(delta);
+ window->OuterRectClipped.Translate(delta);
+ window->InnerRect.Translate(delta);
+ window->DC.CursorPos += delta;
+ window->DC.CursorStartPos += delta;
+ window->DC.CursorMaxPos += delta;
+ window->DC.IdealMaxPos += delta;
+}
+
+static void ScaleWindow(ImGuiWindow* window, float scale)
+{
+ ImVec2 origin = window->Viewport->Pos;
+ window->Pos = ImFloor((window->Pos - origin) * scale + origin);
+ window->Size = ImTrunc(window->Size * scale);
+ window->SizeFull = ImTrunc(window->SizeFull * scale);
+ window->ContentSize = ImTrunc(window->ContentSize * scale);
+}
+
static bool IsWindowActiveAndVisible(ImGuiWindow* window)
{
return (window->Active) && (!window->Hidden);
@@ -4477,6 +4718,9 @@ void ImGui::UpdateHoveredWindowAndCaptureFlags()
{
ImGuiContext& g = *GImGui;
ImGuiIO& io = g.IO;
+
+ // FIXME-DPI: This storage was added on 2021/03/31 for test engine, but if we want to multiply WINDOWS_HOVER_PADDING
+ // by DpiScale, we need to make this window-agnostic anyhow, maybe need storing inside ImGuiWindow.
g.WindowsHoverPadding = ImMax(g.Style.TouchExtraPadding, ImVec2(WINDOWS_HOVER_PADDING, WINDOWS_HOVER_PADDING));
// Find the window hovered by mouse:
@@ -4484,11 +4728,12 @@ void ImGui::UpdateHoveredWindowAndCaptureFlags()
// - When moving a window we can skip the search, which also conveniently bypasses the fact that window->WindowRectClipped is lagging as this point of the frame.
// - We also support the moved window toggling the NoInputs flag after moving has started in order to be able to detect windows below it, which is useful for e.g. docking mechanisms.
bool clear_hovered_windows = false;
- FindHoveredWindow();
+ FindHoveredWindowEx(g.IO.MousePos, false, &g.HoveredWindow, &g.HoveredWindowUnderMovingWindow);
+ IM_ASSERT(g.HoveredWindow == NULL || g.HoveredWindow == g.MovingWindow || g.HoveredWindow->Viewport == g.MouseViewport);
// Modal windows prevents mouse from hovering behind them.
ImGuiWindow* modal_window = GetTopMostPopupModal();
- if (modal_window && g.HoveredWindow && !IsWindowWithinBeginStackOf(g.HoveredWindow->RootWindow, modal_window))
+ if (modal_window && g.HoveredWindow && !IsWindowWithinBeginStackOf(g.HoveredWindow->RootWindow, modal_window)) // FIXME-MERGE: RootWindowDockTree ?
clear_hovered_windows = true;
// Disabled mouse?
@@ -4583,7 +4828,9 @@ void ImGui::NewFrame()
CallContextHooks(&g, ImGuiContextHookType_NewFramePre);
// Check and assert for various common IO and Configuration mistakes
+ g.ConfigFlagsLastFrame = g.ConfigFlagsCurrFrame;
ErrorCheckNewFrameSanityChecks();
+ g.ConfigFlagsCurrFrame = g.IO.ConfigFlags;
// Load settings on first frame, save settings when modified (after a delay)
UpdateSettings();
@@ -4610,6 +4857,7 @@ void ImGui::NewFrame()
UpdateViewportsNewFrame();
// Setup current font and draw list shared data
+ // FIXME-VIEWPORT: the concept of a single ClipRectFullscreen is not ideal!
g.IO.Fonts->Locked = true;
SetupDrawListSharedData();
SetCurrentFont(GetDefaultFont());
@@ -4617,7 +4865,10 @@ void ImGui::NewFrame()
// Mark rendering data as invalid to prevent user who may have a handle on it to use it.
for (ImGuiViewportP* viewport : g.Viewports)
+ {
+ viewport->DrawData = NULL;
viewport->DrawDataP.Valid = false;
+ }
// Drag and drop keep the source ID alive so even if the source disappear our state is consistent
if (g.DragDropActive && g.DragDropPayload.SourceId == g.ActiveId)
@@ -4737,6 +4988,10 @@ void ImGui::NewFrame()
// Update mouse input state
UpdateMouseInputs();
+ // Undocking
+ // (needs to be before UpdateMouseMovingWindowNewFrame so the window is already offset and following the mouse on the detaching frame)
+ DockContextNewFrameUpdateUndocking(&g);
+
// Find hovered window
// (needs to be before UpdateMouseMovingWindowNewFrame so we fill g.HoveredWindowUnderMovingWindow on the mouse release frame)
UpdateHoveredWindowAndCaptureFlags();
@@ -4799,6 +5054,9 @@ void ImGui::NewFrame()
g.ItemFlagsStack.push_back(ImGuiItemFlags_None);
g.GroupStack.resize(0);
+ // Docking
+ DockContextNewFrameUpdateDocking(&g);
+
// [DEBUG] Update debug features
#ifndef IMGUI_DISABLE_DEBUG_TOOLS
UpdateDebugToolItemPicker();
@@ -4868,7 +5126,8 @@ static void AddWindowToSortBuffer(ImVector<ImGuiWindow*>* out_sorted_windows, Im
static void AddWindowToDrawData(ImGuiWindow* window, int layer)
{
ImGuiContext& g = *GImGui;
- ImGuiViewportP* viewport = g.Viewports[0];
+ ImGuiViewportP* viewport = window->Viewport;
+ IM_ASSERT(viewport != NULL);
g.IO.MetricsRenderWindows++;
if (window->DrawList->_Splitter._Count > 1)
window->DrawList->ChannelsMerge(); // Merge if user forgot to merge back. Also required in Docking branch for ImGuiWindowFlags_DockNodeHost windows.
@@ -4912,17 +5171,25 @@ static void InitViewportDrawData(ImGuiViewportP* viewport)
ImGuiIO& io = ImGui::GetIO();
ImDrawData* draw_data = &viewport->DrawDataP;
+ viewport->DrawData = draw_data; // Make publicly accessible
viewport->DrawDataBuilder.Layers[0] = &draw_data->CmdLists;
viewport->DrawDataBuilder.Layers[1] = &viewport->DrawDataBuilder.LayerData1;
viewport->DrawDataBuilder.Layers[0]->resize(0);
viewport->DrawDataBuilder.Layers[1]->resize(0);
+ // When minimized, we report draw_data->DisplaySize as zero to be consistent with non-viewport mode,
+ // and to allow applications/backends to easily skip rendering.
+ // FIXME: Note that we however do NOT attempt to report "zero drawlist / vertices" into the ImDrawData structure.
+ // This is because the work has been done already, and its wasted! We should fix that and add optimizations for
+ // it earlier in the pipeline, rather than pretend to hide the data at the end of the pipeline.
+ const bool is_minimized = (viewport->Flags & ImGuiViewportFlags_IsMinimized) != 0;
+
draw_data->Valid = true;
draw_data->CmdListsCount = 0;
draw_data->TotalVtxCount = draw_data->TotalIdxCount = 0;
draw_data->DisplayPos = viewport->Pos;
- draw_data->DisplaySize = viewport->Size;
- draw_data->FramebufferScale = io.DisplayFramebufferScale;
+ draw_data->DisplaySize = is_minimized ? ImVec2(0.0f, 0.0f) : viewport->Size;
+ draw_data->FramebufferScale = io.DisplayFramebufferScale; // FIXME-VIEWPORT: This may vary on a per-monitor/viewport basis?
draw_data->OwnerViewport = viewport;
}
@@ -4946,20 +5213,28 @@ void ImGui::PopClipRect()
window->ClipRect = window->DrawList->_ClipRectStack.back();
}
+static ImGuiWindow* FindFrontMostVisibleChildWindow(ImGuiWindow* window)
+{
+ for (int n = window->DC.ChildWindows.Size - 1; n >= 0; n--)
+ if (IsWindowActiveAndVisible(window->DC.ChildWindows[n]))
+ return FindFrontMostVisibleChildWindow(window->DC.ChildWindows[n]);
+ return window;
+}
+
static void ImGui::RenderDimmedBackgroundBehindWindow(ImGuiWindow* window, ImU32 col)
{
if ((col & IM_COL32_A_MASK) == 0)
return;
- ImGuiViewportP* viewport = (ImGuiViewportP*)GetMainViewport();
+ ImGuiViewportP* viewport = window->Viewport;
ImRect viewport_rect = viewport->GetMainRect();
// Draw behind window by moving the draw command at the FRONT of the draw list
{
- // We've already called AddWindowToDrawData() which called DrawList->ChannelsMerge() on DockNodeHost windows,
- // and draw list have been trimmed already, hence the explicit recreation of a draw command if missing.
+ // Draw list have been trimmed already, hence the explicit recreation of a draw command if missing.
// FIXME: This is creating complication, might be simpler if we could inject a drawlist in drawdata at a given position and not attempt to manipulate ImDrawCmd order.
- ImDrawList* draw_list = window->RootWindow->DrawList;
+ ImDrawList* draw_list = window->RootWindowDockTree->DrawList;
+ draw_list->ChannelsMerge();
if (draw_list->CmdBuffer.Size == 0)
draw_list->AddDrawCmd();
draw_list->PushClipRect(viewport_rect.Min - ImVec2(1, 1), viewport_rect.Max + ImVec2(1, 1), false); // FIXME: Need to stricty ensure ImDrawCmd are not merged (ElemCount==6 checks below will verify that)
@@ -4971,6 +5246,18 @@ static void ImGui::RenderDimmedBackgroundBehindWindow(ImGuiWindow* window, ImU32
draw_list->AddDrawCmd(); // We need to create a command as CmdBuffer.back().IdxOffset won't be correct if we append to same command.
draw_list->PopClipRect();
}
+
+ // Draw over sibling docking nodes in a same docking tree
+ if (window->RootWindow->DockIsActive)
+ {
+ ImDrawList* draw_list = FindFrontMostVisibleChildWindow(window->RootWindowDockTree)->DrawList;
+ draw_list->ChannelsMerge();
+ if (draw_list->CmdBuffer.Size == 0)
+ draw_list->AddDrawCmd();
+ draw_list->PushClipRect(viewport_rect.Min, viewport_rect.Max, false);
+ RenderRectFilledWithHole(draw_list, window->RootWindowDockTree->Rect(), window->RootWindow->Rect(), col, 0.0f);// window->RootWindowDockTree->WindowRounding);
+ draw_list->PopClipRect();
+ }
}
ImGuiWindow* ImGui::FindBottomMostVisibleWindowWithinBeginStack(ImGuiWindow* parent_window)
@@ -4990,6 +5277,8 @@ ImGuiWindow* ImGui::FindBottomMostVisibleWindowWithinBeginStack(ImGuiWindow* par
return bottom_most_visible_window;
}
+// Important: AddWindowToDrawData() has not been called yet, meaning DockNodeHost windows needs a DrawList->ChannelsMerge() before usage.
+// We call ChannelsMerge() lazily here at it is faster that doing a full iteration of g.Windows[] prior to calling RenderDimmedBackgrounds().
static void ImGui::RenderDimmedBackgrounds()
{
ImGuiContext& g = *GImGui;
@@ -5001,31 +5290,50 @@ static void ImGui::RenderDimmedBackgrounds()
if (!dim_bg_for_modal && !dim_bg_for_window_list)
return;
+ ImGuiViewport* viewports_already_dimmed[2] = { NULL, NULL };
if (dim_bg_for_modal)
{
// Draw dimming behind modal or a begin stack child, whichever comes first in draw order.
ImGuiWindow* dim_behind_window = FindBottomMostVisibleWindowWithinBeginStack(modal_window);
RenderDimmedBackgroundBehindWindow(dim_behind_window, GetColorU32(modal_window->DC.ModalDimBgColor, g.DimBgRatio));
+ viewports_already_dimmed[0] = modal_window->Viewport;
}
else if (dim_bg_for_window_list)
{
- // Draw dimming behind CTRL+Tab target window
+ // Draw dimming behind CTRL+Tab target window and behind CTRL+Tab UI window
RenderDimmedBackgroundBehindWindow(g.NavWindowingTargetAnim, GetColorU32(ImGuiCol_NavWindowingDimBg, g.DimBgRatio));
+ if (g.NavWindowingListWindow != NULL && g.NavWindowingListWindow->Viewport && g.NavWindowingListWindow->Viewport != g.NavWindowingTargetAnim->Viewport)
+ RenderDimmedBackgroundBehindWindow(g.NavWindowingListWindow, GetColorU32(ImGuiCol_NavWindowingDimBg, g.DimBgRatio));
+ viewports_already_dimmed[0] = g.NavWindowingTargetAnim->Viewport;
+ viewports_already_dimmed[1] = g.NavWindowingListWindow ? g.NavWindowingListWindow->Viewport : NULL;
// Draw border around CTRL+Tab target window
ImGuiWindow* window = g.NavWindowingTargetAnim;
- ImGuiViewport* viewport = GetMainViewport();
+ ImGuiViewport* viewport = window->Viewport;
float distance = g.FontSize;
ImRect bb = window->Rect();
bb.Expand(distance);
if (bb.GetWidth() >= viewport->Size.x && bb.GetHeight() >= viewport->Size.y)
bb.Expand(-distance - 1.0f); // If a window fits the entire viewport, adjust its highlight inward
+ window->DrawList->ChannelsMerge();
if (window->DrawList->CmdBuffer.Size == 0)
window->DrawList->AddDrawCmd();
window->DrawList->PushClipRect(viewport->Pos, viewport->Pos + viewport->Size);
window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha), window->WindowRounding, 0, 3.0f);
window->DrawList->PopClipRect();
}
+
+ // Draw dimming background on _other_ viewports than the ones our windows are in
+ for (ImGuiViewportP* viewport : g.Viewports)
+ {
+ if (viewport == viewports_already_dimmed[0] || viewport == viewports_already_dimmed[1])
+ continue;
+ if (modal_window && viewport->Window && IsWindowAbove(viewport->Window, modal_window))
+ continue;
+ ImDrawList* draw_list = GetForegroundDrawList(viewport);
+ const ImU32 dim_bg_col = GetColorU32(dim_bg_for_modal ? ImGuiCol_ModalWindowDimBg : ImGuiCol_NavWindowingDimBg, g.DimBgRatio);
+ draw_list->AddRectFilled(viewport->Pos, viewport->Pos + viewport->Size, dim_bg_col);
+ }
}
// This is normally called by Render(). You may want to call it directly if you want to avoid calling Render() but the gain will be very minimal.
@@ -5047,8 +5355,10 @@ void ImGui::EndFrame()
ImGuiPlatformImeData* ime_data = &g.PlatformImeData;
if (g.IO.SetPlatformImeDataFn && memcmp(ime_data, &g.PlatformImeDataPrev, sizeof(ImGuiPlatformImeData)) != 0)
{
+ ImGuiViewport* viewport = FindViewportByID(g.PlatformImeViewport);
IMGUI_DEBUG_LOG_IO("[io] Calling io.SetPlatformImeDataFn(): WantVisible: %d, InputPos (%.2f,%.2f)\n", ime_data->WantVisible, ime_data->InputPos.x, ime_data->InputPos.y);
- ImGuiViewport* viewport = GetMainViewport();
+ if (viewport == NULL)
+ viewport = GetMainViewport();
g.IO.SetPlatformImeDataFn(viewport, ime_data);
}
@@ -5061,6 +5371,11 @@ void ImGui::EndFrame()
// Update navigation: CTRL+Tab, wrap-around requests
NavEndFrame();
+ // Update docking
+ DockContextEndFrame(&g);
+
+ SetCurrentViewport(NULL, NULL);
+
// Drag and Drop: Elapse payload (if delivered, or if source stops being submitted)
if (g.DragDropActive)
{
@@ -5085,6 +5400,9 @@ void ImGui::EndFrame()
// Initiate moving window + handle left-click and right-click focus
UpdateMouseMovingWindowEndFrame();
+ // Update user-facing viewport list (g.Viewports -> g.PlatformIO.Viewports after filtering out some)
+ UpdateViewportsEndFrame();
+
// Sort the window list so that all child windows are after their parent
// We cannot do that on FocusWindow() because children may not exist yet
g.WindowsTempSortBuffer.resize(0);
@@ -5130,9 +5448,6 @@ void ImGui::Render()
g.IO.MetricsRenderWindows = 0;
CallContextHooks(&g, ImGuiContextHookType_RenderPre);
- // Draw modal/window whitening backgrounds
- RenderDimmedBackgrounds();
-
// Add background ImDrawList (for each active viewport)
for (ImGuiViewportP* viewport : g.Viewports)
{
@@ -5141,9 +5456,12 @@ void ImGui::Render()
AddDrawListToDrawDataEx(&viewport->DrawDataP, viewport->DrawDataBuilder.Layers[0], GetBackgroundDrawList(viewport));
}
+ // Draw modal/window whitening backgrounds
+ RenderDimmedBackgrounds();
+
// Add ImDrawList to render
ImGuiWindow* windows_to_render_top_most[2];
- windows_to_render_top_most[0] = (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus)) ? g.NavWindowingTarget->RootWindow : NULL;
+ windows_to_render_top_most[0] = (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus)) ? g.NavWindowingTarget->RootWindowDockTree : NULL;
windows_to_render_top_most[1] = (g.NavWindowingTarget ? g.NavWindowingListWindow : NULL);
for (ImGuiWindow* window : g.Windows)
{
@@ -5211,17 +5529,26 @@ ImVec2 ImGui::CalcTextSize(const char* text, const char* text_end, bool hide_tex
}
// Find window given position, search front-to-back
-// FIXME: Note that we have an inconsequential lag here: OuterRectClipped is updated in Begin(), so windows moved programmatically
-// with SetWindowPos() and not SetNextWindowPos() will have that rectangle lagging by a frame at the time FindHoveredWindow() is
-// called, aka before the next Begin(). Moving window isn't affected.
-static void FindHoveredWindow()
+// - Typically write output back to g.HoveredWindow and g.HoveredWindowUnderMovingWindow.
+// - FIXME: Note that we have an inconsequential lag here: OuterRectClipped is updated in Begin(), so windows moved programmatically
+// with SetWindowPos() and not SetNextWindowPos() will have that rectangle lagging by a frame at the time FindHoveredWindow() is
+// called, aka before the next Begin(). Moving window isn't affected.
+// - The 'find_first_and_in_any_viewport = true' mode is only used by TestEngine. It is simpler to maintain here.
+void ImGui::FindHoveredWindowEx(const ImVec2& pos, bool find_first_and_in_any_viewport, ImGuiWindow** out_hovered_window, ImGuiWindow** out_hovered_window_under_moving_window)
{
ImGuiContext& g = *GImGui;
-
ImGuiWindow* hovered_window = NULL;
- ImGuiWindow* hovered_window_ignoring_moving_window = NULL;
- if (g.MovingWindow && !(g.MovingWindow->Flags & ImGuiWindowFlags_NoMouseInputs))
- hovered_window = g.MovingWindow;
+ ImGuiWindow* hovered_window_under_moving_window = NULL;
+
+ // Special handling for the window being moved: Ignore the mouse viewport check (because it may reset/lose its viewport during the undocking frame)
+ ImGuiViewportP* backup_moving_window_viewport = NULL;
+ if (find_first_and_in_any_viewport == false && g.MovingWindow)
+ {
+ backup_moving_window_viewport = g.MovingWindow->Viewport;
+ g.MovingWindow->Viewport = g.MouseViewport;
+ if (!(g.MovingWindow->Flags & ImGuiWindowFlags_NoMouseInputs))
+ hovered_window = g.MovingWindow;
+ }
ImVec2 padding_regular = g.Style.TouchExtraPadding;
ImVec2 padding_for_resize = g.IO.ConfigWindowsResizeFromEdges ? g.WindowsHoverPadding : padding_regular;
@@ -5233,10 +5560,13 @@ static void FindHoveredWindow()
continue;
if (window->Flags & ImGuiWindowFlags_NoMouseInputs)
continue;
+ IM_ASSERT(window->Viewport);
+ if (window->Viewport != g.MouseViewport)
+ continue;
// Using the clipped AABB, a child window will typically be clipped by its parent (not always)
ImVec2 hit_padding = (window->Flags & (ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize)) ? padding_regular : padding_for_resize;
- if (!window->OuterRectClipped.ContainsWithPad(g.IO.MousePos, hit_padding))
+ if (!window->OuterRectClipped.ContainsWithPad(pos, hit_padding))
continue;
// Support for one rectangular hole in any given window
@@ -5245,21 +5575,32 @@ static void FindHoveredWindow()
{
ImVec2 hole_pos(window->Pos.x + (float)window->HitTestHoleOffset.x, window->Pos.y + (float)window->HitTestHoleOffset.y);
ImVec2 hole_size((float)window->HitTestHoleSize.x, (float)window->HitTestHoleSize.y);
- if (ImRect(hole_pos, hole_pos + hole_size).Contains(g.IO.MousePos))
+ if (ImRect(hole_pos, hole_pos + hole_size).Contains(pos))
continue;
}
- if (hovered_window == NULL)
+ if (find_first_and_in_any_viewport)
+ {
hovered_window = window;
- IM_MSVC_WARNING_SUPPRESS(28182); // [Static Analyzer] Dereferencing NULL pointer.
- if (hovered_window_ignoring_moving_window == NULL && (!g.MovingWindow || window->RootWindow != g.MovingWindow->RootWindow))
- hovered_window_ignoring_moving_window = window;
- if (hovered_window && hovered_window_ignoring_moving_window)
break;
+ }
+ else
+ {
+ if (hovered_window == NULL)
+ hovered_window = window;
+ IM_MSVC_WARNING_SUPPRESS(28182); // [Static Analyzer] Dereferencing NULL pointer.
+ if (hovered_window_under_moving_window == NULL && (!g.MovingWindow || window->RootWindowDockTree != g.MovingWindow->RootWindowDockTree))
+ hovered_window_under_moving_window = window;
+ if (hovered_window && hovered_window_under_moving_window)
+ break;
+ }
}
- g.HoveredWindow = hovered_window;
- g.HoveredWindowUnderMovingWindow = hovered_window_ignoring_moving_window;
+ *out_hovered_window = hovered_window;
+ if (out_hovered_window_under_moving_window != NULL)
+ *out_hovered_window_under_moving_window = hovered_window_under_moving_window;
+ if (find_first_and_in_any_viewport == false && g.MovingWindow)
+ g.MovingWindow->Viewport = backup_moving_window_viewport;
}
bool ImGui::IsItemActive()
@@ -5299,6 +5640,13 @@ bool ImGui::IsItemFocused()
ImGuiContext& g = *GImGui;
if (g.NavId != g.LastItemData.ID || g.NavId == 0)
return false;
+
+ // Special handling for the dummy item after Begin() which represent the title bar or tab.
+ // When the window is collapsed (SkipItems==true) that last item will never be overwritten so we need to detect the case.
+ ImGuiWindow* window = g.CurrentWindow;
+ if (g.LastItemData.ID == window->ID && window->WriteAccessed)
+ return false;
+
return true;
}
@@ -5450,7 +5798,7 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, I
child_flags &= ~ImGuiChildFlags_ResizeY;
// Set window flags
- window_flags |= ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NoTitleBar;
+ window_flags |= ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoDocking;
window_flags |= (parent_window->Flags & ImGuiWindowFlags_NoMove); // Inherit the NoMove flag
if (child_flags & (ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_AlwaysAutoResize))
window_flags |= ImGuiWindowFlags_AlwaysAutoResize;
@@ -5559,7 +5907,9 @@ void ImGui::EndChild()
else
{
// Not navigable into
- ItemAdd(bb, 0);
+ // - This is a bit of a fringe use case, mostly useful for undecorated, non-scrolling contents childs, or empty childs.
+ // - We could later decide to not apply this path if ImGuiChildFlags_FrameStyle or ImGuiChildFlags_Borders is set.
+ ItemAdd(bb, child_window->ChildId, NULL, ImGuiItemFlags_NoNav);
// But when flattened we directly reach items, adjust active layer mask accordingly
if (child_window->Flags & ImGuiWindowFlags_NavFlattened)
@@ -5577,6 +5927,7 @@ static void SetWindowConditionAllowFlags(ImGuiWindow* window, ImGuiCond flags, b
window->SetWindowPosAllowFlags = enabled ? (window->SetWindowPosAllowFlags | flags) : (window->SetWindowPosAllowFlags & ~flags);
window->SetWindowSizeAllowFlags = enabled ? (window->SetWindowSizeAllowFlags | flags) : (window->SetWindowSizeAllowFlags & ~flags);
window->SetWindowCollapsedAllowFlags = enabled ? (window->SetWindowCollapsedAllowFlags | flags) : (window->SetWindowCollapsedAllowFlags & ~flags);
+ window->SetWindowDockAllowFlags = enabled ? (window->SetWindowDockAllowFlags | flags) : (window->SetWindowDockAllowFlags & ~flags);
}
ImGuiWindow* ImGui::FindWindowByID(ImGuiID id)
@@ -5593,10 +5944,19 @@ ImGuiWindow* ImGui::FindWindowByName(const char* name)
static void ApplyWindowSettings(ImGuiWindow* window, ImGuiWindowSettings* settings)
{
- window->Pos = ImTrunc(ImVec2(settings->Pos.x, settings->Pos.y));
+ const ImGuiViewport* main_viewport = ImGui::GetMainViewport();
+ window->ViewportPos = main_viewport->Pos;
+ if (settings->ViewportId)
+ {
+ window->ViewportId = settings->ViewportId;
+ window->ViewportPos = ImVec2(settings->ViewportPos.x, settings->ViewportPos.y);
+ }
+ window->Pos = ImTrunc(ImVec2(settings->Pos.x + window->ViewportPos.x, settings->Pos.y + window->ViewportPos.y));
if (settings->Size.x > 0 && settings->Size.y > 0)
window->Size = window->SizeFull = ImTrunc(ImVec2(settings->Size.x, settings->Size.y));
window->Collapsed = settings->Collapsed;
+ window->DockId = settings->DockId;
+ window->DockOrder = settings->DockOrder;
}
static void UpdateWindowInFocusOrderList(ImGuiWindow* window, bool just_created, ImGuiWindowFlags new_flags)
@@ -5629,7 +5989,8 @@ static void InitOrLoadWindowSettings(ImGuiWindow* window, ImGuiWindowSettings* s
const ImGuiViewport* main_viewport = ImGui::GetMainViewport();
window->Pos = main_viewport->Pos + ImVec2(60, 60);
window->Size = window->SizeFull = ImVec2(0, 0);
- window->SetWindowPosAllowFlags = window->SetWindowSizeAllowFlags = window->SetWindowCollapsedAllowFlags = ImGuiCond_Always | ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing;
+ window->ViewportPos = main_viewport->Pos;
+ window->SetWindowPosAllowFlags = window->SetWindowSizeAllowFlags = window->SetWindowCollapsedAllowFlags = window->SetWindowDockAllowFlags = ImGuiCond_Always | ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing;
if (settings != NULL)
{
@@ -5677,6 +6038,16 @@ static ImGuiWindow* CreateNewWindow(const char* name, ImGuiWindowFlags flags)
return window;
}
+static ImGuiWindow* GetWindowForTitleDisplay(ImGuiWindow* window)
+{
+ return window->DockNodeAsHost ? window->DockNodeAsHost->VisibleWindow : window;
+}
+
+static ImGuiWindow* GetWindowForTitleAndMenuHeight(ImGuiWindow* window)
+{
+ return (window->DockNodeAsHost && window->DockNodeAsHost->VisibleWindow) ? window->DockNodeAsHost->VisibleWindow : window;
+}
+
static inline ImVec2 CalcWindowMinSize(ImGuiWindow* window)
{
// We give windows non-zero minimum size to facilitate understanding problematic cases (e.g. empty popups)
@@ -5696,7 +6067,7 @@ static inline ImVec2 CalcWindowMinSize(ImGuiWindow* window)
}
// Reduce artifacts with very small windows
- ImGuiWindow* window_for_height = window;
+ ImGuiWindow* window_for_height = GetWindowForTitleAndMenuHeight(window);
size_min.y = ImMax(size_min.y, window_for_height->TitleBarHeight() + window_for_height->MenuBarHeight() + ImMax(0.0f, g.Style.WindowRounding - 1.0f));
return size_min;
}
@@ -5767,13 +6138,24 @@ static ImVec2 CalcWindowAutoFitSize(ImGuiWindow* window, const ImVec2& size_cont
{
// Maximum window size is determined by the viewport size or monitor size
ImVec2 size_min = CalcWindowMinSize(window);
- ImVec2 size_max = ((window->Flags & ImGuiWindowFlags_ChildWindow) && !(window->Flags & ImGuiWindowFlags_Popup)) ? ImVec2(FLT_MAX, FLT_MAX) : ImGui::GetMainViewport()->WorkSize - style.DisplaySafeAreaPadding * 2.0f;
- ImVec2 size_auto_fit = ImClamp(size_desired, size_min, size_max);
+ ImVec2 size_max = (window->ViewportOwned || ((window->Flags & ImGuiWindowFlags_ChildWindow) && !(window->Flags & ImGuiWindowFlags_Popup))) ? ImVec2(FLT_MAX, FLT_MAX) : ImGui::GetMainViewport()->WorkSize - style.DisplaySafeAreaPadding * 2.0f;
+ const int monitor_idx = window->ViewportAllowPlatformMonitorExtend;
+ if (monitor_idx >= 0 && monitor_idx < g.PlatformIO.Monitors.Size && (window->Flags & ImGuiWindowFlags_ChildWindow) == 0)
+ size_max = g.PlatformIO.Monitors[monitor_idx].WorkSize - style.DisplaySafeAreaPadding * 2.0f;
+ ImVec2 size_auto_fit = ImClamp(size_desired, size_min, ImMax(size_min, size_max));
+
+ // FIXME: CalcWindowAutoFitSize() doesn't take into account that only one axis may be auto-fit when calculating scrollbars,
+ // we may need to compute/store three variants of size_auto_fit, for x/y/xy.
+ // Here we implement a workaround for child windows only, but a full solution would apply to normal windows as well:
+ if ((window->ChildFlags & ImGuiChildFlags_ResizeX) && !(window->ChildFlags & ImGuiChildFlags_ResizeY))
+ size_auto_fit.y = window->SizeFull.y;
+ else if (!(window->ChildFlags & ImGuiChildFlags_ResizeX) && (window->ChildFlags & ImGuiChildFlags_ResizeY))
+ size_auto_fit.x = window->SizeFull.x;
// When the window cannot fit all contents (either because of constraints, either because screen is too small),
// we are growing the size on the other axis to compensate for expected scrollbar. FIXME: Might turn bigger than ViewportSize-WindowPadding.
ImVec2 size_auto_fit_after_constraint = CalcWindowSizeAfterConstraint(window, size_auto_fit);
- bool will_have_scrollbar_x = (size_auto_fit_after_constraint.x - size_pad.x - decoration_w_without_scrollbars < size_contents.x && !(window->Flags & ImGuiWindowFlags_NoScrollbar) && (window->Flags & ImGuiWindowFlags_HorizontalScrollbar)) || (window->Flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar);
+ bool will_have_scrollbar_x = (size_auto_fit_after_constraint.x - size_pad.x - decoration_w_without_scrollbars < size_contents.x && !(window->Flags & ImGuiWindowFlags_NoScrollbar) && (window->Flags & ImGuiWindowFlags_HorizontalScrollbar)) || (window->Flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar);
bool will_have_scrollbar_y = (size_auto_fit_after_constraint.y - size_pad.y - decoration_h_without_scrollbars < size_contents.y && !(window->Flags & ImGuiWindowFlags_NoScrollbar)) || (window->Flags & ImGuiWindowFlags_AlwaysVerticalScrollbar);
if (will_have_scrollbar_x)
size_auto_fit.y += style.ScrollbarSize;
@@ -5797,7 +6179,7 @@ static ImGuiCol GetWindowBgColorIdx(ImGuiWindow* window)
{
if (window->Flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup))
return ImGuiCol_PopupBg;
- if (window->Flags & ImGuiWindowFlags_ChildWindow)
+ if ((window->Flags & ImGuiWindowFlags_ChildWindow) && !window->DockIsActive)
return ImGuiCol_ChildBg;
return ImGuiCol_WindowBg;
}
@@ -5863,7 +6245,7 @@ static ImRect GetResizeBorderRect(ImGuiWindow* window, int border_n, float perp_
ImGuiID ImGui::GetWindowResizeCornerID(ImGuiWindow* window, int n)
{
IM_ASSERT(n >= 0 && n < 4);
- ImGuiID id = window->ID;
+ ImGuiID id = window->DockIsActive ? window->DockNode->HostWindow->ID : window->ID;
id = ImHashStr("#RESIZE", 0, id);
id = ImHashData(&n, sizeof(int), id);
return id;
@@ -5874,7 +6256,7 @@ ImGuiID ImGui::GetWindowResizeBorderID(ImGuiWindow* window, ImGuiDir dir)
{
IM_ASSERT(dir >= 0 && dir < 4);
int n = (int)dir + 4;
- ImGuiID id = window->ID;
+ ImGuiID id = window->DockIsActive ? window->DockNode->HostWindow->ID : window->ID;
id = ImHashStr("#RESIZE", 0, id);
id = ImHashData(&n, sizeof(int), id);
return id;
@@ -5905,6 +6287,16 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si
ImVec2 pos_target(FLT_MAX, FLT_MAX);
ImVec2 size_target(FLT_MAX, FLT_MAX);
+ // Clip mouse interaction rectangles within the viewport rectangle (in practice the narrowing is going to happen most of the time).
+ // - Not narrowing would mostly benefit the situation where OS windows _without_ decoration have a threshold for hovering when outside their limits.
+ // This is however not the case with current backends under Win32, but a custom borderless window implementation would benefit from it.
+ // - When decoration are enabled we typically benefit from that distance, but then our resize elements would be conflicting with OS resize elements, so we also narrow.
+ // - Note that we are unable to tell if the platform setup allows hovering with a distance threshold (on Win32, decorated window have such threshold).
+ // We only clip interaction so we overwrite window->ClipRect, cannot call PushClipRect() yet as DrawList is not yet setup.
+ const bool clip_with_viewport_rect = !(g.IO.BackendFlags & ImGuiBackendFlags_HasMouseHoveredViewport) || (g.IO.MouseHoveredViewport != window->ViewportId) || !(window->Viewport->Flags & ImGuiViewportFlags_NoDecoration);
+ if (clip_with_viewport_rect)
+ window->ClipRect = window->Viewport->GetMainRect();
+
// Resize grips and borders are on layer 1
window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;
@@ -5975,6 +6367,7 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si
if (held && g.IO.MouseDoubleClicked[0])
{
// Double-clicking bottom or right border auto-fit on this axis
+ // FIXME: CalcWindowAutoFitSize() doesn't take into account that only one side may be auto-fit when calculating scrollbars.
// FIXME: Support top and right borders: rework CalcResizePosSizeFromAnyCorner() to be reusable in both cases.
if (border_n == 1 || border_n == 3) // Right and bottom border
{
@@ -5989,7 +6382,7 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si
// Switch to relative resizing mode when border geometry moved (e.g. resizing a child altering parent scroll), in order to avoid resizing feedback loop.
// Currently only using relative mode on resizable child windows, as the problem to solve is more likely noticeable for them, but could apply for all windows eventually.
// FIXME: May want to generalize this idiom at lower-level, so more widgets can use it!
- const bool just_scrolled_manually_while_resizing = (g.WheelingWindow != NULL && g.WheelingWindowScrolledFrame == g.FrameCount && IsWindowChildOf(window, g.WheelingWindow, false));
+ const bool just_scrolled_manually_while_resizing = (g.WheelingWindow != NULL && g.WheelingWindowScrolledFrame == g.FrameCount && IsWindowChildOf(window, g.WheelingWindow, false, true));
if (g.ActiveIdIsJustActivated || just_scrolled_manually_while_resizing)
{
g.WindowResizeBorderExpectedRect = border_rect;
@@ -6046,7 +6439,7 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si
// Navigation resize (keyboard/gamepad)
// FIXME: This cannot be moved to NavUpdateWindowing() because CalcWindowSizeAfterConstraint() need to callback into user.
// Not even sure the callback works here.
- if (g.NavWindowingTarget && g.NavWindowingTarget->RootWindow == window)
+ if (g.NavWindowingTarget && g.NavWindowingTarget->RootWindowDockTree == window)
{
ImVec2 nav_resize_dir;
if (g.NavInputSource == ImGuiInputSource_Keyboard && g.IO.KeyShift)
@@ -6097,8 +6490,8 @@ static inline void ClampWindowPos(ImGuiWindow* window, const ImRect& visibility_
{
ImGuiContext& g = *GImGui;
ImVec2 size_for_clamping = window->Size;
- if (g.IO.ConfigWindowsMoveFromTitleBarOnly && !(window->Flags & ImGuiWindowFlags_NoTitleBar))
- size_for_clamping.y = window->TitleBarHeight();
+ if (g.IO.ConfigWindowsMoveFromTitleBarOnly && (!(window->Flags & ImGuiWindowFlags_NoTitleBar) || window->DockNodeAsHost))
+ size_for_clamping.y = ImGui::GetFrameHeight(); // Not using window->TitleBarHeight() as DockNodeAsHost will report 0.0f here.
window->Pos = ImClamp(window->Pos, visibility_rect.Min - size_for_clamping, visibility_rect.Max);
}
@@ -6132,7 +6525,7 @@ static void ImGui::RenderWindowOuterBorders(ImGuiWindow* window)
const ImU32 border_col_resizing = GetColorU32((window->ResizeBorderHeld != -1) ? ImGuiCol_SeparatorActive : ImGuiCol_SeparatorHovered);
RenderWindowOuterSingleBorder(window, border_n, border_col_resizing, ImMax(2.0f, window->WindowBorderSize)); // Thicker than usual
}
- if (g.Style.FrameBorderSize > 0 && !(window->Flags & ImGuiWindowFlags_NoTitleBar))
+ if (g.Style.FrameBorderSize > 0 && !(window->Flags & ImGuiWindowFlags_NoTitleBar) && !window->DockIsActive)
{
float y = window->Pos.y + window->TitleBarHeight() - 1;
window->DrawList->AddLine(ImVec2(window->Pos.x + border_size, y), ImVec2(window->Pos.x + window->Size.x - border_size, y), border_col, g.Style.FrameBorderSize);
@@ -6161,6 +6554,8 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar
const float backup_border_size = style.FrameBorderSize;
g.Style.FrameBorderSize = window->WindowBorderSize;
ImU32 title_bar_col = GetColorU32((title_bar_is_highlight && !g.NavDisableHighlight) ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBgCollapsed);
+ if (window->ViewportOwned)
+ title_bar_col |= IM_COL32_A_MASK; // No alpha (we don't support is_docking_transparent_payload here because simpler and less meaningful, but could with a bit of code shuffle/reuse)
RenderFrame(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, true, window_rounding);
g.Style.FrameBorderSize = backup_border_size;
}
@@ -6169,23 +6564,58 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar
// Window background
if (!(flags & ImGuiWindowFlags_NoBackground))
{
+ bool is_docking_transparent_payload = false;
+ if (g.DragDropActive && (g.FrameCount - g.DragDropAcceptFrameCount) <= 1 && g.IO.ConfigDockingTransparentPayload)
+ if (g.DragDropPayload.IsDataType(IMGUI_PAYLOAD_TYPE_WINDOW) && *(ImGuiWindow**)g.DragDropPayload.Data == window)
+ is_docking_transparent_payload = true;
+
ImU32 bg_col = GetColorU32(GetWindowBgColorIdx(window));
- bool override_alpha = false;
- float alpha = 1.0f;
- if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasBgAlpha)
+ if (window->ViewportOwned)
+ {
+ bg_col |= IM_COL32_A_MASK; // No alpha
+ if (is_docking_transparent_payload)
+ window->Viewport->Alpha *= DOCKING_TRANSPARENT_PAYLOAD_ALPHA;
+ }
+ else
{
- alpha = g.NextWindowData.BgAlphaVal;
- override_alpha = true;
+ // Adjust alpha. For docking
+ bool override_alpha = false;
+ float alpha = 1.0f;
+ if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasBgAlpha)
+ {
+ alpha = g.NextWindowData.BgAlphaVal;
+ override_alpha = true;
+ }
+ if (is_docking_transparent_payload)
+ {
+ alpha *= DOCKING_TRANSPARENT_PAYLOAD_ALPHA; // FIXME-DOCK: Should that be an override?
+ override_alpha = true;
+ }
+ if (override_alpha)
+ bg_col = (bg_col & ~IM_COL32_A_MASK) | (IM_F32_TO_INT8_SAT(alpha) << IM_COL32_A_SHIFT);
}
- if (override_alpha)
- bg_col = (bg_col & ~IM_COL32_A_MASK) | (IM_F32_TO_INT8_SAT(alpha) << IM_COL32_A_SHIFT);
- window->DrawList->AddRectFilled(window->Pos + ImVec2(0, window->TitleBarHeight()), window->Pos + window->Size, bg_col, window_rounding, (flags & ImGuiWindowFlags_NoTitleBar) ? 0 : ImDrawFlags_RoundCornersBottom);
+
+ // Render, for docked windows and host windows we ensure bg goes before decorations
+ if (window->DockIsActive)
+ window->DockNode->LastBgColor = bg_col;
+ ImDrawList* bg_draw_list = window->DockIsActive ? window->DockNode->HostWindow->DrawList : window->DrawList;
+ if (window->DockIsActive || (flags & ImGuiWindowFlags_DockNodeHost))
+ bg_draw_list->ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_BG);
+ bg_draw_list->AddRectFilled(window->Pos + ImVec2(0, window->TitleBarHeight()), window->Pos + window->Size, bg_col, window_rounding, (flags & ImGuiWindowFlags_NoTitleBar) ? 0 : ImDrawFlags_RoundCornersBottom);
+ if (window->DockIsActive || (flags & ImGuiWindowFlags_DockNodeHost))
+ bg_draw_list->ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_FG);
}
+ if (window->DockIsActive)
+ window->DockNode->IsBgDrawnThisFrame = true;
// Title bar
- if (!(flags & ImGuiWindowFlags_NoTitleBar))
+ // (when docked, DockNode are drawing their own title bar. Individual windows however do NOT set the _NoTitleBar flag,
+ // in order for their pos/size to be matching their undocking state.)
+ if (!(flags & ImGuiWindowFlags_NoTitleBar) && !window->DockIsActive)
{
ImU32 title_bar_col = GetColorU32(title_bar_is_highlight ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg);
+ if (window->ViewportOwned)
+ title_bar_col |= IM_COL32_A_MASK; // No alpha
window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, window_rounding, ImDrawFlags_RoundCornersTop);
}
@@ -6199,6 +6629,27 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar
window->DrawList->AddLine(menu_bar_rect.GetBL(), menu_bar_rect.GetBR(), GetColorU32(ImGuiCol_Border), style.FrameBorderSize);
}
+ // Docking: Unhide tab bar (small triangle in the corner), drag from small triangle to quickly undock
+ ImGuiDockNode* node = window->DockNode;
+ if (window->DockIsActive && node->IsHiddenTabBar() && !node->IsNoTabBar())
+ {
+ float unhide_sz_draw = ImTrunc(g.FontSize * 0.70f);
+ float unhide_sz_hit = ImTrunc(g.FontSize * 0.55f);
+ ImVec2 p = node->Pos;
+ ImRect r(p, p + ImVec2(unhide_sz_hit, unhide_sz_hit));
+ ImGuiID unhide_id = window->GetID("#UNHIDE");
+ KeepAliveID(unhide_id);
+ bool hovered, held;
+ if (ButtonBehavior(r, unhide_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren))
+ node->WantHiddenTabBarToggle = true;
+ else if (held && IsMouseDragging(0))
+ StartMouseMovingWindowOrNode(window, node, true); // Undock from tab-bar triangle = same as window/collapse menu button
+
+ // FIXME-DOCK: Ideally we'd use ImGuiCol_TitleBgActive/ImGuiCol_TitleBg here, but neither is guaranteed to be visible enough at this sort of size..
+ ImU32 col = GetColorU32(((held && hovered) || (node->IsFocused && !hovered)) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
+ window->DrawList->AddTriangleFilled(p, p + ImVec2(unhide_sz_draw, 0.0f), p + ImVec2(0.0f, unhide_sz_draw), col);
+ }
+
// Scrollbars
if (window->ScrollbarX)
Scrollbar(ImGuiAxis_X);
@@ -6222,12 +6673,13 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar
}
}
- // Borders
- if (handle_borders_and_resize_grips)
+ // Borders (for dock node host they will be rendered over after the tab bar)
+ if (handle_borders_and_resize_grips && !window->DockNodeAsHost)
RenderWindowOuterBorders(window);
}
}
+// When inside a dock node, this is handled in DockNodeCalcTabBarLayout() instead.
// Render title text, collapse button, close button
void ImGui::RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& title_bar_rect, const char* name, bool* p_open)
{
@@ -6269,7 +6721,7 @@ void ImGui::RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& titl
// Collapse button (submitting first so it gets priority when choosing a navigation init fallback)
if (has_collapse_button)
- if (CollapseButton(window->GetID("#COLLAPSE"), collapse_button_pos))
+ if (CollapseButton(window->GetID("#COLLAPSE"), collapse_button_pos, NULL))
window->WantCollapseToggle = true; // Defer actual collapsing to next frame as we are too far in the Begin() function
// Close button
@@ -6320,12 +6772,16 @@ void ImGui::RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& titl
void ImGui::UpdateWindowParentAndRootLinks(ImGuiWindow* window, ImGuiWindowFlags flags, ImGuiWindow* parent_window)
{
window->ParentWindow = parent_window;
- window->RootWindow = window->RootWindowPopupTree = window->RootWindowForTitleBarHighlight = window->RootWindowForNav = window;
+ window->RootWindow = window->RootWindowPopupTree = window->RootWindowDockTree = window->RootWindowForTitleBarHighlight = window->RootWindowForNav = window;
if (parent_window && (flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Tooltip))
- window->RootWindow = parent_window->RootWindow;
+ {
+ window->RootWindowDockTree = parent_window->RootWindowDockTree;
+ if (!window->DockIsActive && !(parent_window->Flags & ImGuiWindowFlags_DockNodeHost))
+ window->RootWindow = parent_window->RootWindow;
+ }
if (parent_window && (flags & ImGuiWindowFlags_Popup))
window->RootWindowPopupTree = parent_window->RootWindowPopupTree;
- if (parent_window && !(flags & ImGuiWindowFlags_Modal) && (flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup)))
+ if (parent_window && !(flags & ImGuiWindowFlags_Modal) && (flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup))) // FIXME: simply use _NoTitleBar ?
window->RootWindowForTitleBarHighlight = parent_window->RootWindowForTitleBarHighlight;
while (window->RootWindowForNav->Flags & ImGuiWindowFlags_NavFlattened)
{
@@ -6370,7 +6826,7 @@ void ImGui::UpdateWindowSkipRefresh(ImGuiWindow* window)
// - WindowE // .. returns NULL
// Notes:
// - FindBlockingModal(NULL) == NULL is generally equivalent to GetTopMostPopupModal() == NULL.
-// Only difference is here we check for ->Active/WasActive but it may be unecessary.
+// Only difference is here we check for ->Active/WasActive but it may be unnecessary.
ImGuiWindow* ImGui::FindBlockingModal(ImGuiWindow* window)
{
ImGuiContext& g = *GImGui;
@@ -6430,22 +6886,24 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
const bool first_begin_of_the_frame = (window->LastFrameActive != current_frame);
window->IsFallbackWindow = (g.CurrentWindowStack.Size == 0 && g.WithinFrameScopeWithImplicitWindow);
- // Update the Appearing flag
- bool window_just_activated_by_user = (window->LastFrameActive < current_frame - 1); // Not using !WasActive because the implicit "Debug" window would always toggle off->on
+ // Update the Appearing flag (note: the BeginDocked() path may also set this to true later)
+ bool window_just_activated_by_user = (window->LastFrameActive < current_frame - 1); // Not using !WasActive because the implicit "Debug" window would always toggle off->on
if (flags & ImGuiWindowFlags_Popup)
{
ImGuiPopupData& popup_ref = g.OpenPopupStack[g.BeginPopupStack.Size];
window_just_activated_by_user |= (window->PopupId != popup_ref.PopupId); // We recycle popups so treat window as activated if popup id changed
window_just_activated_by_user |= (window != popup_ref.Window);
}
- window->Appearing = window_just_activated_by_user;
- if (window->Appearing)
- SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, true);
// Update Flags, LastFrameActive, BeginOrderXXX fields
+ const bool window_was_appearing = window->Appearing;
if (first_begin_of_the_frame)
{
UpdateWindowInFocusOrderList(window, window_just_created, flags);
+ window->Appearing = window_just_activated_by_user;
+ if (window->Appearing)
+ SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, true);
+ window->FlagsPreviousFrame = window->Flags;
window->Flags = (ImGuiWindowFlags)flags;
window->ChildFlags = (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasChildFlags) ? g.NextWindowData.ChildFlags : 0;
window->LastFrameActive = current_frame;
@@ -6458,8 +6916,42 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
flags = window->Flags;
}
+ // Docking
+ // (NB: during the frame dock nodes are created, it is possible that (window->DockIsActive == false) even though (window->DockNode->Windows.Size > 1)
+ IM_ASSERT(window->DockNode == NULL || window->DockNodeAsHost == NULL); // Cannot be both
+ if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasDock)
+ SetWindowDock(window, g.NextWindowData.DockId, g.NextWindowData.DockCond);
+ if (first_begin_of_the_frame)
+ {
+ bool has_dock_node = (window->DockId != 0 || window->DockNode != NULL);
+ bool new_auto_dock_node = !has_dock_node && GetWindowAlwaysWantOwnTabBar(window);
+ bool dock_node_was_visible = window->DockNodeIsVisible;
+ bool dock_tab_was_visible = window->DockTabIsVisible;
+ if (has_dock_node || new_auto_dock_node)
+ {
+ BeginDocked(window, p_open);
+ flags = window->Flags;
+ if (window->DockIsActive)
+ {
+ IM_ASSERT(window->DockNode != NULL);
+ g.NextWindowData.Flags &= ~ImGuiNextWindowDataFlags_HasSizeConstraint; // Docking currently override constraints
+ }
+
+ // Amend the Appearing flag
+ if (window->DockTabIsVisible && !dock_tab_was_visible && dock_node_was_visible && !window->Appearing && !window_was_appearing)
+ {
+ window->Appearing = true;
+ SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, true);
+ }
+ }
+ else
+ {
+ window->DockIsActive = window->DockNodeIsVisible = window->DockTabIsVisible = false;
+ }
+ }
+
// Parent window is latched only on the first call to Begin() of the frame, so further append-calls can be done from a different window stack
- ImGuiWindow* parent_window_in_stack = g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back().Window;
+ ImGuiWindow* parent_window_in_stack = (window->DockIsActive && window->DockNode->HostWindow) ? window->DockNode->HostWindow : g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back().Window;
ImGuiWindow* parent_window = first_begin_of_the_frame ? ((flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup)) ? parent_window_in_stack : NULL) : window->ParentWindow;
IM_ASSERT(parent_window != NULL || !(flags & ImGuiWindowFlags_ChildWindow));
@@ -6483,9 +6975,20 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
UpdateWindowParentAndRootLinks(window, flags, parent_window);
window->ParentWindowInBeginStack = parent_window_in_stack;
+ // Focus route
// There's little point to expose a flag to set this: because the interesting cases won't be using parent_window_in_stack,
- // e.g. linking a tool window in a standalone viewport to a document window, regardless of their Begin() stack parenting. (#6798)
- window->ParentWindowForFocusRoute = (flags & ImGuiWindowFlags_ChildWindow) ? parent_window_in_stack : NULL;
+ // Use for e.g. linking a tool window in a standalone viewport to a document window, regardless of their Begin() stack parenting. (#6798)
+ window->ParentWindowForFocusRoute = (window->RootWindow != window) ? parent_window_in_stack : NULL;
+ if (window->ParentWindowForFocusRoute == NULL && window->DockNode != NULL)
+ if (window->DockNode->MergedFlags & ImGuiDockNodeFlags_DockedWindowsInFocusRoute)
+ window->ParentWindowForFocusRoute = window->DockNode->HostWindow;
+
+ // Override with SetNextWindowClass() field or direct call to SetWindowParentWindowForFocusRoute()
+ if (window->WindowClass.FocusRouteParentWindowId != 0)
+ {
+ window->ParentWindowForFocusRoute = FindWindowByID(window->WindowClass.FocusRouteParentWindowId);
+ IM_ASSERT(window->ParentWindowForFocusRoute != 0); // Invalid value for FocusRouteParentWindowId.
+ }
}
// Add to focus scope stack
@@ -6549,6 +7052,8 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
window->ContentSizeExplicit = g.NextWindowData.ContentSizeVal;
else if (first_begin_of_the_frame)
window->ContentSizeExplicit = ImVec2(0.0f, 0.0f);
+ if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasWindowClass)
+ window->WindowClass = g.NextWindowData.WindowClass;
if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasCollapsed)
SetWindowCollapsed(window, g.NextWindowData.CollapsedVal, g.NextWindowData.CollapsedCond);
if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasFocus)
@@ -6574,6 +7079,11 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
window->IDStack.resize(1);
window->DrawList->_ResetForNewFrame();
window->DC.CurrentTableIdx = -1;
+ if (flags & ImGuiWindowFlags_DockNodeHost)
+ {
+ window->DrawList->ChannelsSplit(2);
+ window->DrawList->ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_FG); // Render decorations on channel 1 as we will render the backgrounds manually later
+ }
// Restore buffer capacity when woken from a compacted state, to avoid
if (window->MemoryCompacted)
@@ -6582,7 +7092,9 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
// Update stored window name when it changes (which can _only_ happen with the "###" operator, so the ID would stay unchanged).
// The title bar always display the 'name' parameter, so we only update the string storage if it needs to be visible to the end-user elsewhere.
bool window_title_visible_elsewhere = false;
- if (g.NavWindowingListWindow != NULL && (window->Flags & ImGuiWindowFlags_NoNavFocus) == 0) // Window titles visible when using CTRL+TAB
+ if ((window->Viewport && window->Viewport->Window == window) || (window->DockIsActive))
+ window_title_visible_elsewhere = true;
+ else if (g.NavWindowingListWindow != NULL && (window->Flags & ImGuiWindowFlags_NoNavFocus) == 0) // Window titles visible when using CTRL+TAB
window_title_visible_elsewhere = true;
if (window_title_visible_elsewhere && !window_just_created && strcmp(name, window->Name) != 0)
{
@@ -6595,6 +7107,10 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
// Update contents size from last frame for auto-fitting (or use explicit size)
CalcWindowContentSizes(window, &window->ContentSize, &window->ContentSizeIdeal);
+
+ // FIXME: These flags are decremented before they are used. This means that in order to have these fields produce their intended behaviors
+ // for one frame we must set them to at least 2, which is counter-intuitive. HiddenFramesCannotSkipItems is a more complicated case because
+ // it has a single usage before this code block and may be set below before it is finally checked.
if (window->HiddenFramesCanSkipItems > 0)
window->HiddenFramesCanSkipItems--;
if (window->HiddenFramesCannotSkipItems > 0)
@@ -6622,20 +7138,23 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
}
// SELECT VIEWPORT
- // FIXME-VIEWPORT: In the docking/viewport branch, this is the point where we select the current viewport (which may affect the style)
+ // We need to do this before using any style/font sizes, as viewport with a different DPI may affect font sizes.
- ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)GetMainViewport();
- SetWindowViewport(window, viewport);
+ WindowSelectViewport(window);
+ SetCurrentViewport(window, window->Viewport);
+ window->FontDpiScale = (g.IO.ConfigFlags & ImGuiConfigFlags_DpiEnableScaleFonts) ? window->Viewport->DpiScale : 1.0f;
SetCurrentWindow(window);
+ flags = window->Flags;
// LOCK BORDER SIZE AND PADDING FOR THE FRAME (so that altering them doesn't cause inconsistencies)
+ // We read Style data after the call to UpdateSelectWindowViewport() which might be swapping the style.
- if (flags & ImGuiWindowFlags_ChildWindow)
+ if (!window->DockIsActive && (flags & ImGuiWindowFlags_ChildWindow))
window->WindowBorderSize = style.ChildBorderSize;
else
window->WindowBorderSize = ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupBorderSize : style.WindowBorderSize;
window->WindowPadding = style.WindowPadding;
- if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) && !(window->ChildFlags & ImGuiChildFlags_AlwaysUseWindowPadding) && window->WindowBorderSize == 0.0f)
+ if (!window->DockIsActive && (flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) && !(window->ChildFlags & ImGuiChildFlags_AlwaysUseWindowPadding) && window->WindowBorderSize == 0.0f)
window->WindowPadding = ImVec2(0.0f, (flags & ImGuiWindowFlags_MenuBar) ? style.WindowPadding.y : 0.0f);
// Lock menu offset so size calculation can use it as menu-bar windows need a minimum size.
@@ -6653,12 +7172,12 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
// Collapse window by double-clicking on title bar
// At this point we don't have a clipping rectangle setup yet, so we can use the title bar area for hit detection and drawing
- if (!(flags & ImGuiWindowFlags_NoTitleBar) && !(flags & ImGuiWindowFlags_NoCollapse))
+ if (!(flags & ImGuiWindowFlags_NoTitleBar) && !(flags & ImGuiWindowFlags_NoCollapse) && !window->DockIsActive)
{
// We don't use a regular button+id to test for double-click on title bar (mostly due to legacy reason, could be fixed), so verify that we don't have items over the title bar.
ImRect title_bar_rect = window->TitleBarRect();
if (g.HoveredWindow == window && g.HoveredId == 0 && g.HoveredIdPreviousFrame == 0 && IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max))
- if (g.IO.MouseClickedCount[0] == 2 && GetKeyOwner(ImGuiKey_MouseLeft) == ImGuiKeyOwner_None)
+ if (g.IO.MouseClickedCount[0] == 2 && GetKeyOwner(ImGuiKey_MouseLeft) == ImGuiKeyOwner_NoOwner)
window->WantCollapseToggle = true;
if (window->WantCollapseToggle)
{
@@ -6677,7 +7196,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
// SIZE
// Outer Decoration Sizes
- // (we need to clear ScrollbarSize immediatly as CalcWindowAutoFitSize() needs it and can be called from other locations).
+ // (we need to clear ScrollbarSize immediately as CalcWindowAutoFitSize() needs it and can be called from other locations).
const ImVec2 scrollbar_sizes_from_last_frame = window->ScrollbarSizes;
window->DecoOuterSizeX1 = 0.0f;
window->DecoOuterSizeX2 = 0.0f;
@@ -6753,23 +7272,66 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
else if ((flags & ImGuiWindowFlags_Tooltip) != 0 && !window_pos_set_by_api && !window_is_child_tooltip)
window->Pos = FindBestWindowPosForPopup(window);
+ // Late create viewport if we don't fit within our current host viewport.
+ if (window->ViewportAllowPlatformMonitorExtend >= 0 && !window->ViewportOwned && !(window->Viewport->Flags & ImGuiViewportFlags_IsMinimized))
+ if (!window->Viewport->GetMainRect().Contains(window->Rect()))
+ {
+ // This is based on the assumption that the DPI will be known ahead (same as the DPI of the selection done in UpdateSelectWindowViewport)
+ //ImGuiViewport* old_viewport = window->Viewport;
+ window->Viewport = AddUpdateViewport(window, window->ID, window->Pos, window->Size, ImGuiViewportFlags_NoFocusOnAppearing);
+
+ // FIXME-DPI
+ //IM_ASSERT(old_viewport->DpiScale == window->Viewport->DpiScale); // FIXME-DPI: Something went wrong
+ SetCurrentViewport(window, window->Viewport);
+ window->FontDpiScale = (g.IO.ConfigFlags & ImGuiConfigFlags_DpiEnableScaleFonts) ? window->Viewport->DpiScale : 1.0f;
+ SetCurrentWindow(window);
+ }
+
+ if (window->ViewportOwned)
+ WindowSyncOwnedViewport(window, parent_window_in_stack);
+
// Calculate the range of allowed position for that window (to be movable and visible past safe area padding)
// When clamping to stay visible, we will enforce that window->Pos stays inside of visibility_rect.
- ImRect viewport_rect(viewport->GetMainRect());
- ImRect viewport_work_rect(viewport->GetWorkRect());
+ ImRect viewport_rect(window->Viewport->GetMainRect());
+ ImRect viewport_work_rect(window->Viewport->GetWorkRect());
ImVec2 visibility_padding = ImMax(style.DisplayWindowPadding, style.DisplaySafeAreaPadding);
ImRect visibility_rect(viewport_work_rect.Min + visibility_padding, viewport_work_rect.Max - visibility_padding);
// Clamp position/size so window stays visible within its viewport or monitor
// Ignore zero-sized display explicitly to avoid losing positions if a window manager reports zero-sized window when initializing or minimizing.
+ // FIXME: Similar to code in GetWindowAllowedExtentRect()
if (!window_pos_set_by_api && !(flags & ImGuiWindowFlags_ChildWindow))
- if (viewport_rect.GetWidth() > 0.0f && viewport_rect.GetHeight() > 0.0f)
+ {
+ if (!window->ViewportOwned && viewport_rect.GetWidth() > 0 && viewport_rect.GetHeight() > 0.0f)
+ {
+ ClampWindowPos(window, visibility_rect);
+ }
+ else if (window->ViewportOwned && g.PlatformIO.Monitors.Size > 0)
+ {
+ if (g.MovingWindow != NULL && window->RootWindowDockTree == g.MovingWindow->RootWindowDockTree)
+ {
+ // While moving windows we allow them to straddle monitors (#7299, #3071)
+ visibility_rect = g.PlatformMonitorsFullWorkRect;
+ }
+ else
+ {
+ // When not moving ensure visible in its monitor
+ // Lost windows (e.g. a monitor disconnected) will naturally moved to the fallback/dummy monitor aka the main viewport.
+ const ImGuiPlatformMonitor* monitor = GetViewportPlatformMonitor(window->Viewport);
+ visibility_rect = ImRect(monitor->WorkPos, monitor->WorkPos + monitor->WorkSize);
+ }
+ visibility_rect.Expand(-visibility_padding);
ClampWindowPos(window, visibility_rect);
+ }
+ }
window->Pos = ImTrunc(window->Pos);
// Lock window rounding for the frame (so that altering them doesn't cause inconsistencies)
// Large values tend to lead to variety of artifacts and are not recommended.
- window->WindowRounding = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildRounding : ((flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupRounding : style.WindowRounding;
+ if (window->ViewportOwned || window->DockIsActive)
+ window->WindowRounding = 0.0f;
+ else
+ window->WindowRounding = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildRounding : ((flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupRounding : style.WindowRounding;
// For windows with title bar or menu bar, we clamp to FrameHeight(FontSize + FramePadding.y * 2.0f) to completely hide artifacts.
//if ((window->Flags & ImGuiWindowFlags_MenuBar) || !(window->Flags & ImGuiWindowFlags_NoTitleBar))
@@ -6781,7 +7343,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
{
if (flags & ImGuiWindowFlags_Popup)
want_focus = true;
- else if ((flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Tooltip)) == 0)
+ else if ((window->DockIsActive || (flags & ImGuiWindowFlags_ChildWindow) == 0) && !(flags & ImGuiWindowFlags_Tooltip))
want_focus = true;
}
@@ -6797,12 +7359,15 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
}
#endif
+ // Decide if we are going to handle borders and resize grips
+ const bool handle_borders_and_resize_grips = (window->DockNodeAsHost || !window->DockIsActive);
+
// Handle manual resize: Resize Grips, Borders, Gamepad
int border_hovered = -1, border_held = -1;
ImU32 resize_grip_col[4] = {};
const int resize_grip_count = ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup)) ? 0 : g.IO.ConfigWindowsResizeFromEdges ? 2 : 1; // Allow resize from lower-left if we have the mouse cursor feedback for it.
const float resize_grip_draw_size = IM_TRUNC(ImMax(g.FontSize * 1.10f, window->WindowRounding + 1.0f + g.FontSize * 0.2f));
- if (!window->Collapsed)
+ if (handle_borders_and_resize_grips && !window->Collapsed)
if (int auto_fit_mask = UpdateWindowManualResize(window, size_auto_fit, &border_hovered, &border_held, resize_grip_count, &resize_grip_col[0], visibility_rect))
{
if (auto_fit_mask & (1 << ImGuiAxis_X))
@@ -6813,6 +7378,20 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
window->ResizeBorderHovered = (signed char)border_hovered;
window->ResizeBorderHeld = (signed char)border_held;
+ // Synchronize window --> viewport again and one last time (clamping and manual resize may have affected either)
+ if (window->ViewportOwned)
+ {
+ if (!window->Viewport->PlatformRequestMove)
+ window->Viewport->Pos = window->Pos;
+ if (!window->Viewport->PlatformRequestResize)
+ window->Viewport->Size = window->Size;
+ window->Viewport->UpdateWorkRect();
+ viewport_rect = window->Viewport->GetMainRect();
+ }
+
+ // Save last known viewport position within the window itself (so it can be saved in .ini file and restored)
+ window->ViewportPos = window->Viewport->Pos;
+
// SCROLLBAR VISIBILITY
// Update scrollbar visibility (based on the Size that was effective during last frame or the auto-resized Size).
@@ -6851,6 +7430,8 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
const ImRect outer_rect = window->Rect();
const ImRect title_bar_rect = window->TitleBarRect();
window->OuterRectClipped = outer_rect;
+ if (window->DockIsActive)
+ window->OuterRectClipped.Min.y += window->TitleBarHeight();
window->OuterRectClipped.ClipWith(host_rect);
// Inner rectangle
@@ -6909,6 +7490,8 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
// Child windows can render their decoration (bg color, border, scrollbars, etc.) within their parent to save a draw call (since 1.71)
// When using overlapping child windows, this will break the assumption that child z-order is mapped to submission order.
// FIXME: User code may rely on explicit sorting of overlapping child window and would need to disable this somehow. Please get in contact if you are affected (github #4493)
+ const bool is_undocked_or_docked_visible = !window->DockIsActive || window->DockTabIsVisible;
+ if (is_undocked_or_docked_visible)
{
bool render_decorations_in_parent = false;
if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) && !window_is_child_tooltip)
@@ -6926,8 +7509,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
// Handle title bar, scrollbar, resize grips and resize borders
const ImGuiWindow* window_to_highlight = g.NavWindowingTarget ? g.NavWindowingTarget : g.NavWindow;
- const bool title_bar_is_highlight = want_focus || (window_to_highlight && window->RootWindowForTitleBarHighlight == window_to_highlight->RootWindowForTitleBarHighlight);
- const bool handle_borders_and_resize_grips = true; // This exists to facilitate merge with 'docking' branch.
+ const bool title_bar_is_highlight = want_focus || (window_to_highlight && (window->RootWindowForTitleBarHighlight == window_to_highlight->RootWindowForTitleBarHighlight || (window->DockNode && window->DockNode == window_to_highlight->DockNode)));
RenderWindowDecorations(window, title_bar_rect, title_bar_is_highlight, handle_borders_and_resize_grips, resize_grip_count, resize_grip_col, resize_grip_draw_size);
if (render_decorations_in_parent)
@@ -7010,6 +7592,9 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
if (window->AutoFitFramesY > 0)
window->AutoFitFramesY--;
+ // Clear SetNextWindowXXX data (can aim to move this higher in the function)
+ g.NextWindowData.ClearFlags();
+
// Apply focus (we need to call FocusWindow() AFTER setting DC.CursorStartPos so our initial navigation reference rectangle can start around there)
// We ImGuiFocusRequestFlags_UnlessBelowModal to:
// - Avoid focusing a window that is created outside of a modal. This will prevent active modal from being closed.
@@ -7019,8 +7604,16 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
if (want_focus && window == g.NavWindow)
NavInitWindow(window, false); // <-- this is in the way for us to be able to defer and sort reappearing FocusWindow() calls
+ // Close requested by platform window (apply to all windows in this viewport)
+ if (p_open != NULL && window->Viewport->PlatformRequestClose && window->Viewport != GetMainViewport())
+ {
+ IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Window '%s' closed by PlatformRequestClose\n", window->Name);
+ *p_open = false;
+ g.NavWindowingToggleLayer = false; // Assume user mapped PlatformRequestClose on ALT-F4 so we disable ALT for menu toggle. False positive not an issue. // FIXME-NAV: Try removing.
+ }
+
// Title bar
- if (!(flags & ImGuiWindowFlags_NoTitleBar))
+ if (!(flags & ImGuiWindowFlags_NoTitleBar) && !window->DockIsActive)
RenderWindowTitleBarContents(window, ImRect(title_bar_rect.Min.x + window->WindowBorderSize, title_bar_rect.Min.y, title_bar_rect.Max.x - window->WindowBorderSize, title_bar_rect.Max.y), name, p_open);
// Clear hit test shape every frame
@@ -7036,6 +7629,20 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
LogToClipboard();
*/
+ if (g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable)
+ {
+ // Docking: Dragging a dockable window (or any of its child) turns it into a drag and drop source.
+ // We need to do this _before_ we overwrite window->DC.LastItemId below because BeginDockableDragDropSource() also overwrites it.
+ if (g.MovingWindow == window && (window->RootWindowDockTree->Flags & ImGuiWindowFlags_NoDocking) == 0)
+ BeginDockableDragDropSource(window);
+
+ // Docking: Any dockable window can act as a target. For dock node hosts we call BeginDockableDragDropTarget() in DockNodeUpdate() instead.
+ if (g.DragDropActive && !(flags & ImGuiWindowFlags_NoDocking))
+ if (g.MovingWindow == NULL || g.MovingWindow->RootWindowDockTree != window)
+ if ((window == window->RootWindowDockTree) && !(window->Flags & ImGuiWindowFlags_DockNodeHost))
+ BeginDockableDragDropTarget(window);
+ }
+
// We fill last item data based on Title Bar/Tab, in order for IsItemHovered() and IsItemActive() to be usable after Begin().
// This is useful to allow creating context menus on title bar only, etc.
SetLastItemDataForWindow(window, title_bar_rect);
@@ -7059,26 +7666,39 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
window->Active = true;
// Append
+ SetCurrentViewport(window, window->Viewport);
SetCurrentWindow(window);
+ g.NextWindowData.ClearFlags();
SetLastItemDataForWindow(window, window->TitleBarRect());
}
- if (!window->SkipRefresh)
+ if (!(flags & ImGuiWindowFlags_DockNodeHost) && !window->SkipRefresh)
PushClipRect(window->InnerClipRect.Min, window->InnerClipRect.Max, true);
// Clear 'accessed' flag last thing (After PushClipRect which will set the flag. We want the flag to stay false when the default "Debug" window is unused)
window->WriteAccessed = false;
window->BeginCount++;
- g.NextWindowData.ClearFlags();
// Update visibility
if (first_begin_of_the_frame && !window->SkipRefresh)
{
+ // When we are about to select this tab (which will only be visible on the _next frame_), flag it with a non-zero HiddenFramesCannotSkipItems.
+ // This will have the important effect of actually returning true in Begin() and not setting SkipItems, allowing an earlier submission of the window contents.
+ // This is analogous to regular windows being hidden from one frame.
+ // It is especially important as e.g. nested TabBars would otherwise generate flicker in the form of one empty frame, or focus requests won't be processed.
+ if (window->DockIsActive && !window->DockTabIsVisible)
+ {
+ if (window->LastFrameJustFocused == g.FrameCount)
+ window->HiddenFramesCannotSkipItems = 1;
+ else
+ window->HiddenFramesCanSkipItems = 1;
+ }
+
if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_ChildMenu))
{
// Child window can be out of sight and have "negative" clip windows.
// Mark them as collapsed so commands are skipped earlier (we can't manually collapse them because they have no title bar).
- IM_ASSERT((flags & ImGuiWindowFlags_NoTitleBar) != 0);
+ IM_ASSERT((flags& ImGuiWindowFlags_NoTitleBar) != 0 || window->DockIsActive);
const bool nav_request = (flags & ImGuiWindowFlags_NavFlattened) && (g.NavAnyRequest && g.NavWindow && g.NavWindow->RootWindowForNav == window->RootWindowForNav);
if (!g.LogEnabled && !nav_request)
if (window->OuterRectClipped.Min.x >= window->OuterRectClipped.Max.x || window->OuterRectClipped.Min.y >= window->OuterRectClipped.Max.y)
@@ -7117,6 +7737,16 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
if (window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0 && window->HiddenFramesCannotSkipItems <= 0)
skip_items = true;
window->SkipItems = skip_items;
+
+ // Restore NavLayersActiveMaskNext to previous value when not visible, so a CTRL+Tab back can use a safe value.
+ if (window->SkipItems)
+ window->DC.NavLayersActiveMaskNext = window->DC.NavLayersActiveMask;
+
+ // Sanity check: there are two spots which can set Appearing = true
+ // - when 'window_just_activated_by_user' is set -> HiddenFramesCannotSkipItems is set -> SkipItems always false
+ // - in BeginDocked() path when DockNodeIsVisible == DockTabIsVisible == true -> hidden _should_ be all zero // FIXME: Not formally proven, hence the assert.
+ if (window->SkipItems && !window->Appearing)
+ IM_ASSERT(window->Appearing == false); // Please report on GitHub if this triggers: https://github.com/ocornut/imgui/issues/4177
}
else if (first_begin_of_the_frame)
{
@@ -7142,7 +7772,10 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
static void ImGui::SetLastItemDataForWindow(ImGuiWindow* window, const ImRect& rect)
{
ImGuiContext& g = *GImGui;
- SetLastItemData(window->MoveId, g.CurrentItemFlags, IsMouseHoveringRect(rect.Min, rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0, rect);
+ if (window->DockIsActive)
+ SetLastItemData(window->MoveId, g.CurrentItemFlags, window->DockTabItemStatusFlags, window->DockTabItemRect);
+ else
+ SetLastItemData(window->MoveId, g.CurrentItemFlags, IsMouseHoveringRect(rect.Min, rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0, rect);
}
void ImGui::End()
@@ -7159,14 +7792,14 @@ void ImGui::End()
IM_ASSERT(g.CurrentWindowStack.Size > 0);
// Error checking: verify that user doesn't directly call End() on a child window.
- if (window->Flags & ImGuiWindowFlags_ChildWindow)
+ if ((window->Flags & ImGuiWindowFlags_ChildWindow) && !(window->Flags & ImGuiWindowFlags_DockNodeHost) && !window->DockIsActive)
IM_ASSERT_USER_ERROR(g.WithinEndChild, "Must call EndChild() and not End()!");
// Close anything that is open
if (window->DC.CurrentColumns)
EndColumns();
- if (!window->SkipRefresh)
- PopClipRect(); // Inner window clip rectangle
+ if (!(window->Flags & ImGuiWindowFlags_DockNodeHost) && !window->SkipRefresh) // Pop inner window clip rectangle
+ PopClipRect();
PopFocusScope();
if (window->SkipRefresh)
@@ -7182,6 +7815,11 @@ void ImGui::End()
if (window->DC.IsSetPos)
ErrorCheckUsingSetCursorPosToExtendParentBoundaries();
+ // Docking: report contents sizes to parent to allow for auto-resize
+ if (window->DockNode && window->DockTabIsVisible)
+ if (ImGuiWindow* host_window = window->DockNode->HostWindow) // FIXME-DOCK
+ host_window->DC.CursorMaxPos = window->DC.CursorMaxPos + window->WindowPadding - host_window->WindowPadding;
+
// Pop from window stack
g.LastItemData = g.CurrentWindowStack.back().ParentLastItemDataBackup;
if (window->Flags & ImGuiWindowFlags_ChildMenu)
@@ -7191,6 +7829,8 @@ void ImGui::End()
g.CurrentWindowStack.back().StackSizesOnBegin.CompareWithContextState(&g);
g.CurrentWindowStack.pop_back();
SetCurrentWindow(g.CurrentWindowStack.Size == 0 ? NULL : g.CurrentWindowStack.back().Window);
+ if (g.CurrentWindow)
+ SetCurrentViewport(g.CurrentWindow, g.CurrentWindow->Viewport);
}
void ImGui::BringWindowToFocusFront(ImGuiWindow* window)
@@ -7218,7 +7858,7 @@ void ImGui::BringWindowToDisplayFront(ImGuiWindow* window)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* current_front_window = g.Windows.back();
- if (current_front_window == window || current_front_window->RootWindow == window) // Cheap early out (could be better)
+ if (current_front_window == window || current_front_window->RootWindowDockTree == window) // Cheap early out (could be better)
return;
for (int i = g.Windows.Size - 2; i >= 0; i--) // We can ignore the top-most window
if (g.Windows[i] == window)
@@ -7307,31 +7947,39 @@ void ImGui::FocusWindow(ImGuiWindow* window, ImGuiFocusRequestFlags flags)
}
// Move the root window to the top of the pile
- IM_ASSERT(window == NULL || window->RootWindow != NULL);
- ImGuiWindow* focus_front_window = window ? window->RootWindow : NULL; // NB: In docking branch this is window->RootWindowDockStop
- ImGuiWindow* display_front_window = window ? window->RootWindow : NULL;
+ IM_ASSERT(window == NULL || window->RootWindowDockTree != NULL);
+ ImGuiWindow* focus_front_window = window ? window->RootWindow : NULL;
+ ImGuiWindow* display_front_window = window ? window->RootWindowDockTree : NULL;
+ ImGuiDockNode* dock_node = window ? window->DockNode : NULL;
+ bool active_id_window_is_dock_node_host = (g.ActiveIdWindow && dock_node && dock_node->HostWindow == g.ActiveIdWindow);
// Steal active widgets. Some of the cases it triggers includes:
// - Focus a window while an InputText in another window is active, if focus happens before the old InputText can run.
// - When using Nav to activate menu items (due to timing of activating on press->new window appears->losing ActiveId)
+ // - Using dock host items (tab, collapse button) can trigger this before we redirect the ActiveIdWindow toward the child window.
if (g.ActiveId != 0 && g.ActiveIdWindow && g.ActiveIdWindow->RootWindow != focus_front_window)
- if (!g.ActiveIdNoClearOnFocusLoss)
+ if (!g.ActiveIdNoClearOnFocusLoss && !active_id_window_is_dock_node_host)
ClearActiveID();
// Passing NULL allow to disable keyboard focus
if (!window)
return;
+ window->LastFrameJustFocused = g.FrameCount;
+
+ // Select in dock node
+ // For #2304 we avoid applying focus immediately before the tabbar is visible.
+ //if (dock_node && dock_node->TabBar)
+ // dock_node->TabBar->SelectedTabId = dock_node->TabBar->NextSelectedTabId = window->TabId;
// Bring to front
BringWindowToFocusFront(focus_front_window);
- if (((window->Flags | display_front_window->Flags) & ImGuiWindowFlags_NoBringToFrontOnFocus) == 0)
+ if (((window->Flags | focus_front_window->Flags | display_front_window->Flags) & ImGuiWindowFlags_NoBringToFrontOnFocus) == 0)
BringWindowToDisplayFront(display_front_window);
}
void ImGui::FocusTopMostWindowUnderOne(ImGuiWindow* under_this_window, ImGuiWindow* ignore_window, ImGuiViewport* filter_viewport, ImGuiFocusRequestFlags flags)
{
ImGuiContext& g = *GImGui;
- IM_UNUSED(filter_viewport); // Unused in master branch.
int start_idx = g.WindowsFocusOrder.Size - 1;
if (under_this_window != NULL)
{
@@ -7350,8 +7998,15 @@ void ImGui::FocusTopMostWindowUnderOne(ImGuiWindow* under_this_window, ImGuiWind
ImGuiWindow* window = g.WindowsFocusOrder[i];
if (window == ignore_window || !window->WasActive)
continue;
+ if (filter_viewport != NULL && window->Viewport != filter_viewport)
+ continue;
if ((window->Flags & (ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs)) != (ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs))
{
+ // FIXME-DOCK: When ImGuiFocusRequestFlags_RestoreFocusedChild is set...
+ // This is failing (lagging by one frame) for docked windows.
+ // If A and B are docked into window and B disappear, at the NewFrame() call site window->NavLastChildNavWindow will still point to B.
+ // We might leverage the tab order implicitly stored in window->DockNodeAsHost->TabBar (essentially the 'most_recently_selected_tab' code in tab bar will do that but on next update)
+ // to tell which is the "previous" window. Or we may leverage 'LastFrameFocused/LastFrameJustFocused' and have this function handle child window itself?
FocusWindow(window, flags);
return;
}
@@ -7483,7 +8138,7 @@ void ImGui::PopTextWrapPos()
window->DC.TextWrapPosStack.pop_back();
}
-static ImGuiWindow* GetCombinedRootWindow(ImGuiWindow* window, bool popup_hierarchy)
+static ImGuiWindow* GetCombinedRootWindow(ImGuiWindow* window, bool popup_hierarchy, bool dock_hierarchy)
{
ImGuiWindow* last_window = NULL;
while (last_window != window)
@@ -7492,13 +8147,15 @@ static ImGuiWindow* GetCombinedRootWindow(ImGuiWindow* window, bool popup_hierar
window = window->RootWindow;
if (popup_hierarchy)
window = window->RootWindowPopupTree;
- }
+ if (dock_hierarchy)
+ window = window->RootWindowDockTree;
+ }
return window;
}
-bool ImGui::IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent, bool popup_hierarchy)
+bool ImGui::IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent, bool popup_hierarchy, bool dock_hierarchy)
{
- ImGuiWindow* window_root = GetCombinedRootWindow(window, popup_hierarchy);
+ ImGuiWindow* window_root = GetCombinedRootWindow(window, popup_hierarchy, dock_hierarchy);
if (window_root == potential_parent)
return true;
while (window != NULL)
@@ -7563,12 +8220,13 @@ bool ImGui::IsWindowHovered(ImGuiHoveredFlags flags)
{
IM_ASSERT(cur_window); // Not inside a Begin()/End()
const bool popup_hierarchy = (flags & ImGuiHoveredFlags_NoPopupHierarchy) == 0;
+ const bool dock_hierarchy = (flags & ImGuiHoveredFlags_DockHierarchy) != 0;
if (flags & ImGuiHoveredFlags_RootWindow)
- cur_window = GetCombinedRootWindow(cur_window, popup_hierarchy);
+ cur_window = GetCombinedRootWindow(cur_window, popup_hierarchy, dock_hierarchy);
bool result;
if (flags & ImGuiHoveredFlags_ChildWindows)
- result = IsWindowChildOf(ref_window, cur_window, popup_hierarchy);
+ result = IsWindowChildOf(ref_window, cur_window, popup_hierarchy, dock_hierarchy);
else
result = (ref_window == cur_window);
if (!result)
@@ -7583,7 +8241,7 @@ bool ImGui::IsWindowHovered(ImGuiHoveredFlags flags)
// When changing hovered window we requires a bit of stationary delay before activating hover timer.
// FIXME: We don't support delay other than stationary one for now, other delay would need a way
- // to fullfill the possibility that multiple IsWindowHovered() with varying flag could return true
+ // to fulfill the possibility that multiple IsWindowHovered() with varying flag could return true
// for different windows of the hierarchy. Possibly need a Hash(Current+Flags) ==> (Timer) cache.
// We can implement this for _Stationary because the data is linked to HoveredWindow rather than CurrentWindow.
if (flags & ImGuiHoveredFlags_ForTooltip)
@@ -7607,15 +8265,28 @@ bool ImGui::IsWindowFocused(ImGuiFocusedFlags flags)
IM_ASSERT(cur_window); // Not inside a Begin()/End()
const bool popup_hierarchy = (flags & ImGuiFocusedFlags_NoPopupHierarchy) == 0;
+ const bool dock_hierarchy = (flags & ImGuiFocusedFlags_DockHierarchy) != 0;
if (flags & ImGuiHoveredFlags_RootWindow)
- cur_window = GetCombinedRootWindow(cur_window, popup_hierarchy);
+ cur_window = GetCombinedRootWindow(cur_window, popup_hierarchy, dock_hierarchy);
if (flags & ImGuiHoveredFlags_ChildWindows)
- return IsWindowChildOf(ref_window, cur_window, popup_hierarchy);
+ return IsWindowChildOf(ref_window, cur_window, popup_hierarchy, dock_hierarchy);
else
return (ref_window == cur_window);
}
+ImGuiID ImGui::GetWindowDockID()
+{
+ ImGuiContext& g = *GImGui;
+ return g.CurrentWindow->DockId;
+}
+
+bool ImGui::IsWindowDocked()
+{
+ ImGuiContext& g = *GImGui;
+ return g.CurrentWindow->DockIsActive;
+}
+
// Can we focus this window with CTRL+TAB (or PadMenu + PadFocusPrev/PadFocusNext)
// Note that NoNavFocus makes the window not reachable with CTRL+TAB but it can still be focused with mouse or programmatically.
// If you want a window to never be focused, you may use the e.g. NoInputs flag.
@@ -7660,6 +8331,7 @@ void ImGui::SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond)
if (offset.x == 0.0f && offset.y == 0.0f)
return;
MarkIniSettingsDirty(window);
+ // FIXME: share code with TranslateWindow(), need to confirm whether the 3 rect modified by TranslateWindow() are desirable here.
window->DC.CursorPos += offset; // As we happen to move the window while it is being appended to (which is a bad idea - will smear) let's at least offset the cursor
window->DC.CursorMaxPos += offset; // And more importantly we need to offset CursorMaxPos/CursorStartPos this so ContentSize calculation doesn't get affected.
window->DC.IdealMaxPos += offset;
@@ -7797,6 +8469,7 @@ void ImGui::SetNextWindowPos(const ImVec2& pos, ImGuiCond cond, const ImVec2& pi
g.NextWindowData.PosVal = pos;
g.NextWindowData.PosPivotVal = pivot;
g.NextWindowData.PosCond = cond ? cond : ImGuiCond_Always;
+ g.NextWindowData.PosUndock = true;
}
void ImGui::SetNextWindowSize(const ImVec2& size, ImGuiCond cond)
@@ -7859,6 +8532,29 @@ void ImGui::SetNextWindowBgAlpha(float alpha)
g.NextWindowData.BgAlphaVal = alpha;
}
+void ImGui::SetNextWindowViewport(ImGuiID id)
+{
+ ImGuiContext& g = *GImGui;
+ g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasViewport;
+ g.NextWindowData.ViewportId = id;
+}
+
+void ImGui::SetNextWindowDockID(ImGuiID id, ImGuiCond cond)
+{
+ ImGuiContext& g = *GImGui;
+ g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasDock;
+ g.NextWindowData.DockCond = cond ? cond : ImGuiCond_Always;
+ g.NextWindowData.DockId = id;
+}
+
+void ImGui::SetNextWindowClass(const ImGuiWindowClass* window_class)
+{
+ ImGuiContext& g = *GImGui;
+ IM_ASSERT((window_class->ViewportFlagsOverrideSet & window_class->ViewportFlagsOverrideClear) == 0); // Cannot set both set and clear for the same bit
+ g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasWindowClass;
+ g.NextWindowData.WindowClass = *window_class;
+}
+
// This is experimental and meant to be a toy for exploring a future/wider range of features.
void ImGui::SetNextWindowRefreshPolicy(ImGuiWindowRefreshFlags flags)
{
@@ -7873,6 +8569,19 @@ ImDrawList* ImGui::GetWindowDrawList()
return window->DrawList;
}
+float ImGui::GetWindowDpiScale()
+{
+ ImGuiContext& g = *GImGui;
+ return g.CurrentDpiScale;
+}
+
+ImGuiViewport* ImGui::GetWindowViewport()
+{
+ ImGuiContext& g = *GImGui;
+ IM_ASSERT(g.CurrentViewport != NULL && g.CurrentViewport == g.CurrentWindow->Viewport);
+ return g.CurrentViewport;
+}
+
ImFont* ImGui::GetFont()
{
return GImGui->Font;
@@ -8197,6 +8906,8 @@ ImGuiID ImGui::GetID(const void* ptr_id)
//-----------------------------------------------------------------------------
// [SECTION] INPUTS
//-----------------------------------------------------------------------------
+// - GetModForModKey() [Internal]
+// - FixupKeyChord() [Internal]
// - GetKeyData() [Internal]
// - GetKeyIndex() [Internal]
// - GetKeyName()
@@ -8258,23 +8969,25 @@ ImGuiID ImGui::GetID(const void* ptr_id)
// - Shortcut() [Internal]
//-----------------------------------------------------------------------------
-ImGuiKeyChord ImGui::FixupKeyChord(ImGuiContext* ctx, ImGuiKeyChord key_chord)
+static ImGuiKeyChord GetModForModKey(ImGuiKey key)
+{
+ if (key == ImGuiKey_LeftCtrl || key == ImGuiKey_RightCtrl)
+ return ImGuiMod_Ctrl;
+ if (key == ImGuiKey_LeftShift || key == ImGuiKey_RightShift)
+ return ImGuiMod_Shift;
+ if (key == ImGuiKey_LeftAlt || key == ImGuiKey_RightAlt)
+ return ImGuiMod_Alt;
+ if (key == ImGuiKey_LeftSuper || key == ImGuiKey_RightSuper)
+ return ImGuiMod_Super;
+ return ImGuiMod_None;
+}
+
+ImGuiKeyChord ImGui::FixupKeyChord(ImGuiKeyChord key_chord)
{
- // Convert ImGuiMod_Shortcut and add ImGuiMod_XXXX when a corresponding ImGuiKey_LeftXXX/ImGuiKey_RightXXX is specified.
+ // Add ImGuiMod_XXXX when a corresponding ImGuiKey_LeftXXX/ImGuiKey_RightXXX is specified.
ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_);
if (IsModKey(key))
- {
- if (key == ImGuiKey_LeftCtrl || key == ImGuiKey_RightCtrl)
- key_chord |= ImGuiMod_Ctrl;
- if (key == ImGuiKey_LeftShift || key == ImGuiKey_RightShift)
- key_chord |= ImGuiMod_Shift;
- if (key == ImGuiKey_LeftAlt || key == ImGuiKey_RightAlt)
- key_chord |= ImGuiMod_Alt;
- if (key == ImGuiKey_LeftSuper || key == ImGuiKey_RightSuper)
- key_chord |= ImGuiMod_Super;
- }
- if (key_chord & ImGuiMod_Shortcut)
- return (key_chord & ~ImGuiMod_Shortcut) | (ctx->IO.ConfigMacOSXBehaviors ? ImGuiMod_Super : ImGuiMod_Ctrl);
+ key_chord |= GetModForModKey(key);
return key_chord;
}
@@ -8284,7 +8997,7 @@ ImGuiKeyData* ImGui::GetKeyData(ImGuiContext* ctx, ImGuiKey key)
// Special storage location for mods
if (key & ImGuiMod_Mask_)
- key = ConvertSingleModFlagToKey(ctx, key);
+ key = ConvertSingleModFlagToKey(key);
#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO
IM_ASSERT(key >= ImGuiKey_LegacyNativeKey_BEGIN && key < ImGuiKey_NamedKey_END);
@@ -8335,10 +9048,12 @@ IM_STATIC_ASSERT(ImGuiKey_NamedKey_COUNT == IM_ARRAYSIZE(GKeyNames));
const char* ImGui::GetKeyName(ImGuiKey key)
{
- ImGuiContext& g = *GImGui;
+ if (key == ImGuiKey_None)
+ return "None";
#ifdef IMGUI_DISABLE_OBSOLETE_KEYIO
- IM_ASSERT((IsNamedKeyOrModKey(key) || key == ImGuiKey_None) && "Support for user key indices was dropped in favor of ImGuiKey. Please update backend and user code.");
+ IM_ASSERT(IsNamedKeyOrMod(key) && "Support for user key indices was dropped in favor of ImGuiKey. Please update backend and user code.");
#else
+ ImGuiContext& g = *GImGui;
if (IsLegacyKey(key))
{
if (g.IO.KeyMap[key] == -1)
@@ -8347,27 +9062,33 @@ const char* ImGui::GetKeyName(ImGuiKey key)
key = (ImGuiKey)g.IO.KeyMap[key];
}
#endif
- if (key == ImGuiKey_None)
- return "None";
if (key & ImGuiMod_Mask_)
- key = ConvertSingleModFlagToKey(&g, key);
+ key = ConvertSingleModFlagToKey(key);
if (!IsNamedKey(key))
return "Unknown";
return GKeyNames[key - ImGuiKey_NamedKey_BEGIN];
}
-// ImGuiMod_Shortcut is translated to either Ctrl or Super.
+// Return untranslated names: on macOS, Cmd key will show as Ctrl, Ctrl key will show as super.
+// Lifetime of return value: valid until next call to same function.
const char* ImGui::GetKeyChordName(ImGuiKeyChord key_chord)
{
ImGuiContext& g = *GImGui;
- key_chord = FixupKeyChord(&g, key_chord);
- ImFormatString(g.TempKeychordName, IM_ARRAYSIZE(g.TempKeychordName), "%s%s%s%s%s",
+
+ const ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_);
+ if (IsModKey(key))
+ key_chord &= ~GetModForModKey(key); // Return "Ctrl+LeftShift" instead of "Ctrl+Shift+LeftShift"
+ ImFormatString(g.TempKeychordName, IM_ARRAYSIZE(g.TempKeychordName), "%s%s%s%s%s",
(key_chord & ImGuiMod_Ctrl) ? "Ctrl+" : "",
(key_chord & ImGuiMod_Shift) ? "Shift+" : "",
(key_chord & ImGuiMod_Alt) ? "Alt+" : "",
- (key_chord & ImGuiMod_Super) ? (g.IO.ConfigMacOSXBehaviors ? "Cmd+" : "Super+") : "",
- GetKeyName((ImGuiKey)(key_chord & ~ImGuiMod_Mask_)));
+ (key_chord & ImGuiMod_Super) ? "Super+" : "",
+ (key != ImGuiKey_None || key_chord == ImGuiKey_None) ? GetKeyName(key) : "");
+ size_t len;
+ if (key == ImGuiKey_None && key_chord != 0)
+ if ((len = strlen(g.TempKeychordName)) != 0) // Remove trailing '+'
+ g.TempKeychordName[len - 1] = 0;
return g.TempKeychordName;
}
@@ -8437,9 +9158,9 @@ static void ImGui::UpdateKeyRoutingTable(ImGuiKeyRoutingTable* rt)
routing_entry = &rt->Entries[old_routing_idx];
routing_entry->RoutingCurrScore = routing_entry->RoutingNextScore;
routing_entry->RoutingCurr = routing_entry->RoutingNext; // Update entry
- routing_entry->RoutingNext = ImGuiKeyOwner_None;
+ routing_entry->RoutingNext = ImGuiKeyOwner_NoOwner;
routing_entry->RoutingNextScore = 255;
- if (routing_entry->RoutingCurr == ImGuiKeyOwner_None)
+ if (routing_entry->RoutingCurr == ImGuiKeyOwner_NoOwner)
continue;
rt->EntriesNext.push_back(*routing_entry); // Write alive ones into new buffer
@@ -8448,7 +9169,7 @@ static void ImGui::UpdateKeyRoutingTable(ImGuiKeyRoutingTable* rt)
if (routing_entry->Mods == g.IO.KeyMods)
{
ImGuiKeyOwnerData* owner_data = GetKeyOwnerData(&g, key);
- if (owner_data->OwnerCurr == ImGuiKeyOwner_None)
+ if (owner_data->OwnerCurr == ImGuiKeyOwner_NoOwner)
{
owner_data->OwnerCurr = routing_entry->RoutingCurr;
//IMGUI_DEBUG_LOG("SetKeyOwner(%s, owner_id=0x%08X) via Routing\n", GetKeyName(key), routing_entry->RoutingCurr);
@@ -8468,7 +9189,7 @@ static void ImGui::UpdateKeyRoutingTable(ImGuiKeyRoutingTable* rt)
static inline ImGuiID GetRoutingIdFromOwnerId(ImGuiID owner_id)
{
ImGuiContext& g = *GImGui;
- return (owner_id != ImGuiKeyOwner_None && owner_id != ImGuiKeyOwner_Any) ? owner_id : g.CurrentFocusScopeId;
+ return (owner_id != ImGuiKeyOwner_NoOwner && owner_id != ImGuiKeyOwner_Any) ? owner_id : g.CurrentFocusScopeId;
}
ImGuiKeyRoutingData* ImGui::GetShortcutRoutingData(ImGuiKeyChord key_chord)
@@ -8485,8 +9206,8 @@ ImGuiKeyRoutingData* ImGui::GetShortcutRoutingData(ImGuiKeyChord key_chord)
ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_);
ImGuiKey mods = (ImGuiKey)(key_chord & ImGuiMod_Mask_);
if (key == ImGuiKey_None)
- key = ConvertSingleModFlagToKey(&g, mods);
- IM_ASSERT(IsNamedKey(key) && (key_chord & ImGuiMod_Shortcut) == 0); // Please call ConvertShortcutMod() in calling function.
+ key = ConvertSingleModFlagToKey(mods);
+ IM_ASSERT(IsNamedKey(key));
// Get (in the majority of case, the linked list will have one element so this should be 2 reads.
// Subsequent elements will be contiguous in memory as list is sorted/rebuilt in NewFrame).
@@ -8508,19 +9229,18 @@ ImGuiKeyRoutingData* ImGui::GetShortcutRoutingData(ImGuiKeyChord key_chord)
}
// Current score encoding (lower is highest priority):
-// - 0: ImGuiInputFlags_RouteGlobalHigh
-// - 1: ImGuiInputFlags_RouteFocused (if item active)
-// - 2: ImGuiInputFlags_RouteGlobal
+// - 0: ImGuiInputFlags_RouteGlobalOverActive
+// - 1: ImGuiInputFlags_ActiveItem or ImGuiInputFlags_RouteFocused (if item active)
+// - 2: ImGuiInputFlags_RouteGlobalOverFocused
// - 3+: ImGuiInputFlags_RouteFocused (if window in focus-stack)
-// - 254: ImGuiInputFlags_RouteGlobalLow
+// - 254: ImGuiInputFlags_RouteGlobal
// - 255: never route
// 'flags' should include an explicit routing policy
static int CalcRoutingScore(ImGuiID focus_scope_id, ImGuiID owner_id, ImGuiInputFlags flags)
{
+ ImGuiContext& g = *GImGui;
if (flags & ImGuiInputFlags_RouteFocused)
{
- ImGuiContext& g = *GImGui;
-
// ActiveID gets top priority
// (we don't check g.ActiveIdUsingAllKeys here. Routing is applied but if input ownership is tested later it may discard it)
if (owner_id != 0 && g.ActiveId == owner_id)
@@ -8541,12 +9261,19 @@ static int CalcRoutingScore(ImGuiID focus_scope_id, ImGuiID owner_id, ImGuiInput
return 255;
}
-
- // ImGuiInputFlags_RouteGlobalHigh is default, so calls without flags are not conditional
- if (flags & ImGuiInputFlags_RouteGlobal)
+ if (flags & ImGuiInputFlags_RouteActive)
+ {
+ if (owner_id != 0 && g.ActiveId == owner_id)
+ return 1;
+ return 255;
+ }
+ if (flags & ImGuiInputFlags_RouteOverFocused)
return 2;
- if (flags & ImGuiInputFlags_RouteGlobalLow)
+ if (flags & ImGuiInputFlags_RouteGlobal)
return 254;
+ if (flags & ImGuiInputFlags_RouteOverActive)
+ return 0;
+ IM_ASSERT(0);
return 0;
}
@@ -8559,7 +9286,7 @@ static bool IsKeyChordPotentiallyCharInput(ImGuiKeyChord key_chord)
// When the right mods are pressed it cannot be a char input so we won't filter the shortcut out.
ImGuiKey mods = (ImGuiKey)(key_chord & ImGuiMod_Mask_);
- const bool ignore_char_inputs = ((mods & ImGuiMod_Ctrl) && !(mods & ImGuiMod_Alt)) || (g.IO.ConfigMacOSXBehaviors && (mods & ImGuiMod_Super));
+ const bool ignore_char_inputs = ((mods & ImGuiMod_Ctrl) && !(mods & ImGuiMod_Alt)) || (g.IO.ConfigMacOSXBehaviors && (mods & ImGuiMod_Ctrl));
if (ignore_char_inputs)
return false;
@@ -8573,17 +9300,16 @@ static bool IsKeyChordPotentiallyCharInput(ImGuiKeyChord key_chord)
// - Routes and key ownership are attributed at the beginning of next frame based on best score and mod state.
// (Conceptually this does a "Submit for next frame" + "Test for current frame".
// As such, it could be called TrySetXXX or SubmitXXX, or the Submit and Test operations should be separate.)
-bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags flags)
+bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiInputFlags flags, ImGuiID owner_id)
{
ImGuiContext& g = *GImGui;
- if ((flags & ImGuiInputFlags_RouteMask_) == 0)
- flags |= ImGuiInputFlags_RouteGlobalHigh; // IMPORTANT: This is the default for SetShortcutRouting() but NOT Shortcut()
- else
- IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiInputFlags_RouteMask_)); // Check that only 1 routing flag is used
- IM_ASSERT(owner_id != ImGuiKeyOwner_Any && owner_id != ImGuiKeyOwner_None);
+ IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiInputFlags_RouteTypeMask_)); // Check that only 1 routing flag is used
+ IM_ASSERT(owner_id != ImGuiKeyOwner_Any && owner_id != ImGuiKeyOwner_NoOwner);
+ if (flags & (ImGuiInputFlags_RouteOverFocused | ImGuiInputFlags_RouteOverActive | ImGuiInputFlags_RouteUnlessBgFocused))
+ IM_ASSERT(flags & ImGuiInputFlags_RouteGlobal);
- // Convert ImGuiMod_Shortcut and add ImGuiMod_XXXX when a corresponding ImGuiKey_LeftXXX/ImGuiKey_RightXXX is specified.
- key_chord = FixupKeyChord(&g, key_chord);
+ // Add ImGuiMod_XXXX when a corresponding ImGuiKey_LeftXXX/ImGuiKey_RightXXX is specified.
+ key_chord = FixupKeyChord(key_chord);
// [DEBUG] Debug break requested by user
if (g.DebugBreakInShortcutRouting == key_chord)
@@ -8596,11 +9322,11 @@ bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiI
// Note how ImGuiInputFlags_RouteAlways won't set routing and thus won't set owner. May want to rework this?
if (flags & ImGuiInputFlags_RouteAlways)
{
- IMGUI_DEBUG_LOG_INPUTROUTING("SetShortcutRouting(%s, owner_id=0x%08X, flags=%04X) -> always\n", GetKeyChordName(key_chord), owner_id, flags);
+ IMGUI_DEBUG_LOG_INPUTROUTING("SetShortcutRouting(%s, flags=%04X, owner_id=0x%08X) -> always, no register\n", GetKeyChordName(key_chord), flags, owner_id);
return true;
}
- // Specific culling when there's an active.
+ // Specific culling when there's an active item.
if (g.ActiveId != 0 && g.ActiveId != owner_id)
{
// Cull shortcuts with no modifiers when it could generate a character.
@@ -8610,24 +9336,31 @@ bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiI
// (We cannot filter based on io.InputQueueCharacters[] contents because of trickling and key<>chars submission order are undefined)
if ((flags & ImGuiInputFlags_RouteFocused) && g.IO.WantTextInput && IsKeyChordPotentiallyCharInput(key_chord))
{
- IMGUI_DEBUG_LOG_INPUTROUTING("SetShortcutRouting(%s, owner_id=0x%08X, flags=%04X) -> filtered as potential char input\n", GetKeyChordName(key_chord), owner_id, flags);
+ IMGUI_DEBUG_LOG_INPUTROUTING("SetShortcutRouting(%s, flags=%04X, owner_id=0x%08X) -> filtered as potential char input\n", GetKeyChordName(key_chord), flags, owner_id);
return false;
}
+ if (flags & ImGuiInputFlags_RouteActive)
+ return false;
+
// ActiveIdUsingAllKeyboardKeys trumps all for ActiveId
- if ((flags & ImGuiInputFlags_RouteGlobalHigh) == 0 && g.ActiveIdUsingAllKeyboardKeys)
+ if ((flags & ImGuiInputFlags_RouteOverActive) == 0 && g.ActiveIdUsingAllKeyboardKeys)
{
ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_);
if (key == ImGuiKey_None)
- key = ConvertSingleModFlagToKey(&g, (ImGuiKey)(key_chord & ImGuiMod_Mask_));
+ key = ConvertSingleModFlagToKey((ImGuiKey)(key_chord & ImGuiMod_Mask_));
if (key >= ImGuiKey_Keyboard_BEGIN && key < ImGuiKey_Keyboard_END)
return false;
}
}
- // FIXME-SHORTCUT: A way to configure the location/focus-scope to test would render this more flexible.
- const int score = CalcRoutingScore(g.CurrentFocusScopeId, owner_id, flags);
- IMGUI_DEBUG_LOG_INPUTROUTING("SetShortcutRouting(%s, owner_id=0x%08X, flags=%04X) -> score %d\n", GetKeyChordName(key_chord), owner_id, flags, score);
+ // Where do we evaluate route for?
+ ImGuiID focus_scope_id = g.CurrentFocusScopeId;
+ if (flags & ImGuiInputFlags_RouteFromRootWindow)
+ focus_scope_id = g.CurrentWindow->RootWindow->ID; // See PushFocusScope() call in Begin()
+
+ const int score = CalcRoutingScore(focus_scope_id, owner_id, flags);
+ IMGUI_DEBUG_LOG_INPUTROUTING("SetShortcutRouting(%s, flags=%04X, owner_id=0x%08X) -> score %d\n", GetKeyChordName(key_chord), flags, owner_id, score);
if (score == 255)
return false;
@@ -8651,9 +9384,8 @@ bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiI
// Note: this cannot be turned into GetShortcutRouting() because we do the owner_id->routing_id translation, name would be more misleading.
bool ImGui::TestShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id)
{
- ImGuiContext& g = *GImGui;
const ImGuiID routing_id = GetRoutingIdFromOwnerId(owner_id);
- key_chord = FixupKeyChord(&g, key_chord);
+ key_chord = FixupKeyChord(key_chord);
ImGuiKeyRoutingData* routing_data = GetShortcutRoutingData(key_chord); // FIXME: Could avoid creating entry.
return routing_data->RoutingCurr == routing_id;
}
@@ -8677,11 +9409,11 @@ bool ImGui::IsKeyDown(ImGuiKey key, ImGuiID owner_id)
bool ImGui::IsKeyPressed(ImGuiKey key, bool repeat)
{
- return IsKeyPressed(key, ImGuiKeyOwner_Any, repeat ? ImGuiInputFlags_Repeat : ImGuiInputFlags_None);
+ return IsKeyPressed(key, repeat ? ImGuiInputFlags_Repeat : ImGuiInputFlags_None, ImGuiKeyOwner_Any);
}
// Important: unless legacy IsKeyPressed(ImGuiKey, bool repeat=true) which DEFAULT to repeat, this requires EXPLICIT repeat.
-bool ImGui::IsKeyPressed(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags)
+bool ImGui::IsKeyPressed(ImGuiKey key, ImGuiInputFlags flags, ImGuiID owner_id)
{
const ImGuiKeyData* key_data = GetKeyData(key);
if (!key_data->Down) // In theory this should already be encoded as (DownDuration < 0.0f), but testing this facilitates eating mechanism (until we finish work on key ownership)
@@ -8754,7 +9486,7 @@ bool ImGui::IsMouseClicked(ImGuiMouseButton button, bool repeat)
return IsMouseClicked(button, ImGuiKeyOwner_Any, repeat ? ImGuiInputFlags_Repeat : ImGuiInputFlags_None);
}
-bool ImGui::IsMouseClicked(ImGuiMouseButton button, ImGuiID owner_id, ImGuiInputFlags flags)
+bool ImGui::IsMouseClicked(ImGuiMouseButton button, ImGuiInputFlags flags, ImGuiID owner_id)
{
ImGuiContext& g = *GImGui;
IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
@@ -8826,6 +9558,8 @@ bool ImGui::IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool c
// Hit testing, expanded for touch input
if (!rect_clipped.ContainsWithPad(g.IO.MousePos, g.Style.TouchExtraPadding))
return false;
+ if (!g.MouseViewport->GetMainRect().Overlaps(rect_clipped))
+ return false;
return true;
}
@@ -9080,7 +9814,7 @@ static void ImGui::UpdateKeyboardInputs()
ImGuiKeyOwnerData* owner_data = &g.KeysOwnerData[key - ImGuiKey_NamedKey_BEGIN];
owner_data->OwnerCurr = owner_data->OwnerNext;
if (!key_data->Down) // Important: ownership is released on the frame after a release. Ensure a 'MouseDown -> CloseWindow -> MouseUp' chain doesn't lead to someone else seeing the MouseUp.
- owner_data->OwnerNext = ImGuiKeyOwner_None;
+ owner_data->OwnerNext = ImGuiKeyOwner_NoOwner;
owner_data->LockThisFrame = owner_data->LockUntilRelease = owner_data->LockUntilRelease && key_data->Down; // Clear LockUntilRelease when key is not Down anymore
}
@@ -9144,13 +9878,16 @@ static void ImGui::UpdateMouseInputs()
io.MouseClickedTime[i] = g.Time;
io.MouseClickedPos[i] = io.MousePos;
io.MouseClickedCount[i] = io.MouseClickedLastCount[i];
+ io.MouseDragMaxDistanceAbs[i] = ImVec2(0.0f, 0.0f);
io.MouseDragMaxDistanceSqr[i] = 0.0f;
}
else if (io.MouseDown[i])
{
// Maintain the maximum distance we reaching from the initial click position, which is used with dragging threshold
- float delta_sqr_click_pos = IsMousePosValid(&io.MousePos) ? ImLengthSqr(io.MousePos - io.MouseClickedPos[i]) : 0.0f;
- io.MouseDragMaxDistanceSqr[i] = ImMax(io.MouseDragMaxDistanceSqr[i], delta_sqr_click_pos);
+ ImVec2 delta_from_click_pos = IsMousePosValid(&io.MousePos) ? (io.MousePos - io.MouseClickedPos[i]) : ImVec2(0.0f, 0.0f);
+ io.MouseDragMaxDistanceSqr[i] = ImMax(io.MouseDragMaxDistanceSqr[i], ImLengthSqr(delta_from_click_pos));
+ io.MouseDragMaxDistanceAbs[i].x = ImMax(io.MouseDragMaxDistanceAbs[i].x, delta_from_click_pos.x < 0.0f ? -delta_from_click_pos.x : delta_from_click_pos.x);
+ io.MouseDragMaxDistanceAbs[i].y = ImMax(io.MouseDragMaxDistanceAbs[i].y, delta_from_click_pos.y < 0.0f ? -delta_from_click_pos.y : delta_from_click_pos.y);
}
// We provide io.MouseDoubleClicked[] as a legacy service
@@ -9236,8 +9973,8 @@ void ImGui::UpdateMouseWheel()
}
ImVec2 wheel;
- wheel.x = TestKeyOwner(ImGuiKey_MouseWheelX, ImGuiKeyOwner_None) ? g.IO.MouseWheelH : 0.0f;
- wheel.y = TestKeyOwner(ImGuiKey_MouseWheelY, ImGuiKeyOwner_None) ? g.IO.MouseWheel : 0.0f;
+ wheel.x = TestKeyOwner(ImGuiKey_MouseWheelX, ImGuiKeyOwner_NoOwner) ? g.IO.MouseWheelH : 0.0f;
+ wheel.y = TestKeyOwner(ImGuiKey_MouseWheelY, ImGuiKeyOwner_NoOwner) ? g.IO.MouseWheel : 0.0f;
//IMGUI_DEBUG_LOG("MouseWheel X:%.3f Y:%.3f\n", wheel_x, wheel_y);
ImGuiWindow* mouse_window = g.WheelingWindow ? g.WheelingWindow : g.HoveredWindow;
@@ -9340,6 +10077,7 @@ static void DebugPrintInputEvent(const char* prefix, const ImGuiInputEvent* e)
if (e->Type == ImGuiInputEventType_MousePos) { if (e->MousePos.PosX == -FLT_MAX && e->MousePos.PosY == -FLT_MAX) IMGUI_DEBUG_LOG_IO("[io] %s: MousePos (-FLT_MAX, -FLT_MAX)\n", prefix); else IMGUI_DEBUG_LOG_IO("[io] %s: MousePos (%.1f, %.1f) (%s)\n", prefix, e->MousePos.PosX, e->MousePos.PosY, GetMouseSourceName(e->MousePos.MouseSource)); return; }
if (e->Type == ImGuiInputEventType_MouseButton) { IMGUI_DEBUG_LOG_IO("[io] %s: MouseButton %d %s (%s)\n", prefix, e->MouseButton.Button, e->MouseButton.Down ? "Down" : "Up", GetMouseSourceName(e->MouseButton.MouseSource)); return; }
if (e->Type == ImGuiInputEventType_MouseWheel) { IMGUI_DEBUG_LOG_IO("[io] %s: MouseWheel (%.3f, %.3f) (%s)\n", prefix, e->MouseWheel.WheelX, e->MouseWheel.WheelY, GetMouseSourceName(e->MouseWheel.MouseSource)); return; }
+ if (e->Type == ImGuiInputEventType_MouseViewport){IMGUI_DEBUG_LOG_IO("[io] %s: MouseViewport (0x%08X)\n", prefix, e->MouseViewport.HoveredViewportID); return; }
if (e->Type == ImGuiInputEventType_Key) { IMGUI_DEBUG_LOG_IO("[io] %s: Key \"%s\" %s\n", prefix, ImGui::GetKeyName(e->Key.Key), e->Key.Down ? "Down" : "Up"); return; }
if (e->Type == ImGuiInputEventType_Text) { IMGUI_DEBUG_LOG_IO("[io] %s: Text: %c (U+%08X)\n", prefix, e->Text.Char, e->Text.Char); return; }
if (e->Type == ImGuiInputEventType_Focus) { IMGUI_DEBUG_LOG_IO("[io] %s: AppFocused %d\n", prefix, e->AppFocused.Focused); return; }
@@ -9403,6 +10141,10 @@ void ImGui::UpdateInputEvents(bool trickle_fast_inputs)
io.MouseSource = e->MouseWheel.MouseSource;
mouse_wheeled = true;
}
+ else if (e->Type == ImGuiInputEventType_MouseViewport)
+ {
+ io.MouseHoveredViewport = e->MouseViewport.HoveredViewportID;
+ }
else if (e->Type == ImGuiInputEventType_Key)
{
// Trickling Rule: Stop processing queued events if we got multiple action on the same button
@@ -9475,8 +10217,8 @@ void ImGui::UpdateInputEvents(bool trickle_fast_inputs)
ImGuiID ImGui::GetKeyOwner(ImGuiKey key)
{
- if (!IsNamedKeyOrModKey(key))
- return ImGuiKeyOwner_None;
+ if (!IsNamedKeyOrMod(key))
+ return ImGuiKeyOwner_NoOwner;
ImGuiContext& g = *GImGui;
ImGuiKeyOwnerData* owner_data = GetKeyOwnerData(&g, key);
@@ -9484,7 +10226,7 @@ ImGuiID ImGui::GetKeyOwner(ImGuiKey key)
if (g.ActiveIdUsingAllKeyboardKeys && owner_id != g.ActiveId && owner_id != ImGuiKeyOwner_Any)
if (key >= ImGuiKey_Keyboard_BEGIN && key < ImGuiKey_Keyboard_END)
- return ImGuiKeyOwner_None;
+ return ImGuiKeyOwner_NoOwner;
return owner_id;
}
@@ -9495,7 +10237,7 @@ ImGuiID ImGui::GetKeyOwner(ImGuiKey key)
// All paths are also testing for key not being locked, for the rare cases that key have been locked with using ImGuiInputFlags_LockXXX flags.
bool ImGui::TestKeyOwner(ImGuiKey key, ImGuiID owner_id)
{
- if (!IsNamedKeyOrModKey(key))
+ if (!IsNamedKeyOrMod(key))
return true;
ImGuiContext& g = *GImGui;
@@ -9514,7 +10256,7 @@ bool ImGui::TestKeyOwner(ImGuiKey key, ImGuiID owner_id)
{
if (owner_data->LockThisFrame)
return false;
- if (owner_data->OwnerCurr != ImGuiKeyOwner_None)
+ if (owner_data->OwnerCurr != ImGuiKeyOwner_NoOwner)
return false;
}
@@ -9529,7 +10271,7 @@ bool ImGui::TestKeyOwner(ImGuiKey key, ImGuiID owner_id)
void ImGui::SetKeyOwner(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags)
{
ImGuiContext& g = *GImGui;
- IM_ASSERT(IsNamedKeyOrModKey(key) && (owner_id != ImGuiKeyOwner_Any || (flags & (ImGuiInputFlags_LockThisFrame | ImGuiInputFlags_LockUntilRelease)))); // Can only use _Any with _LockXXX flags (to eat a key away without an ID to retrieve it)
+ IM_ASSERT(IsNamedKeyOrMod(key) && (owner_id != ImGuiKeyOwner_Any || (flags & (ImGuiInputFlags_LockThisFrame | ImGuiInputFlags_LockUntilRelease)))); // Can only use _Any with _LockXXX flags (to eat a key away without an ID to retrieve it)
IM_ASSERT((flags & ~ImGuiInputFlags_SupportedBySetKeyOwner) == 0); // Passing flags not supported by this function!
//IMGUI_DEBUG_LOG("SetKeyOwner(%s, owner_id=0x%08X, flags=%08X)\n", GetKeyName(key), owner_id, flags);
@@ -9549,7 +10291,6 @@ void ImGui::SetKeyOwnersForKeyChord(ImGuiKeyChord key_chord, ImGuiID owner_id, I
if (key_chord & ImGuiMod_Shift) { SetKeyOwner(ImGuiMod_Shift, owner_id, flags); }
if (key_chord & ImGuiMod_Alt) { SetKeyOwner(ImGuiMod_Alt, owner_id, flags); }
if (key_chord & ImGuiMod_Super) { SetKeyOwner(ImGuiMod_Super, owner_id, flags); }
- if (key_chord & ImGuiMod_Shortcut) { SetKeyOwner(ImGuiMod_Shortcut, owner_id, flags); }
if (key_chord & ~ImGuiMod_Mask_) { SetKeyOwner((ImGuiKey)(key_chord & ~ImGuiMod_Mask_), owner_id, flags); }
}
@@ -9581,10 +10322,10 @@ bool ImGui::IsKeyChordPressed(ImGuiKeyChord key_chord)
}
// This is equivalent to comparing KeyMods + doing a IsKeyPressed()
-bool ImGui::IsKeyChordPressed(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags flags)
+bool ImGui::IsKeyChordPressed(ImGuiKeyChord key_chord, ImGuiInputFlags flags, ImGuiID owner_id)
{
ImGuiContext& g = *GImGui;
- key_chord = FixupKeyChord(&g, key_chord);
+ key_chord = FixupKeyChord(key_chord);
ImGuiKey mods = (ImGuiKey)(key_chord & ImGuiMod_Mask_);
if (g.IO.KeyMods != mods)
return false;
@@ -9592,35 +10333,63 @@ bool ImGui::IsKeyChordPressed(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiIn
// Special storage location for mods
ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_);
if (key == ImGuiKey_None)
- key = ConvertSingleModFlagToKey(&g, mods);
- if (!IsKeyPressed(key, owner_id, (flags & ImGuiInputFlags_RepeatMask_)))
+ key = ConvertSingleModFlagToKey(mods);
+ if (!IsKeyPressed(key, (flags & ImGuiInputFlags_RepeatMask_), owner_id))
return false;
return true;
}
-void ImGui::SetNextItemShortcut(ImGuiKeyChord key_chord)
+void ImGui::SetNextItemShortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags)
{
ImGuiContext& g = *GImGui;
g.NextItemData.Flags |= ImGuiNextItemDataFlags_HasShortcut;
g.NextItemData.Shortcut = key_chord;
+ g.NextItemData.ShortcutFlags = flags;
+}
+
+void ImGui::ItemHandleShortcut(ImGuiID id)
+{
+ ImGuiContext& g = *GImGui;
+ ImGuiInputFlags flags = g.NextItemData.ShortcutFlags;
+ IM_ASSERT((flags & ~ImGuiInputFlags_SupportedBySetNextItemShortcut) == 0); // Passing flags not supported by SetNextItemShortcut()!
+
+ if (flags & ImGuiInputFlags_Tooltip)
+ {
+ g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasShortcut;
+ g.LastItemData.Shortcut = g.NextItemData.Shortcut;
+ }
+ if (!Shortcut(g.NextItemData.Shortcut, flags & ImGuiInputFlags_SupportedByShortcut, id) || g.NavActivateId != 0)
+ return;
+
+ // FIXME: Generalize Activation queue?
+ g.NavActivateId = id; // Will effectively disable clipping.
+ g.NavActivateFlags = ImGuiActivateFlags_PreferInput | ImGuiActivateFlags_FromShortcut;
+ //if (g.ActiveId == 0 || g.ActiveId == id)
+ g.NavActivateDownId = g.NavActivatePressedId = id;
+ NavHighlightActivated(id);
+}
+
+bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags)
+{
+ return Shortcut(key_chord, flags, ImGuiKeyOwner_Any);
}
-bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags flags)
+bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags, ImGuiID owner_id)
{
//ImGuiContext& g = *GImGui;
- //IMGUI_DEBUG_LOG("Shortcut(%s, owner_id=0x%08X, flags=%X)\n", GetKeyChordName(key_chord, g.TempBuffer.Data, g.TempBuffer.Size), owner_id, flags);
+ //IMGUI_DEBUG_LOG("Shortcut(%s, flags=%X, owner_id=0x%08X)\n", GetKeyChordName(key_chord, g.TempBuffer.Data, g.TempBuffer.Size), flags, owner_id);
// When using (owner_id == 0/Any): SetShortcutRouting() will use CurrentFocusScopeId and filter with this, so IsKeyPressed() is fine with he 0/Any.
- if ((flags & ImGuiInputFlags_RouteMask_) == 0)
+ if ((flags & ImGuiInputFlags_RouteTypeMask_) == 0)
flags |= ImGuiInputFlags_RouteFocused;
// Using 'owner_id == ImGuiKeyOwner_Any/0': auto-assign an owner based on current focus scope (each window has its focus scope by default)
// Effectively makes Shortcut() always input-owner aware.
- if (owner_id == ImGuiKeyOwner_Any || owner_id == ImGuiKeyOwner_None)
+ if (owner_id == ImGuiKeyOwner_Any || owner_id == ImGuiKeyOwner_NoOwner)
owner_id = GetRoutingIdFromOwnerId(owner_id);
// Submit route
- if (!SetShortcutRouting(key_chord, owner_id, flags))
+ if (!SetShortcutRouting(key_chord, flags, owner_id))
return false;
// Default repeat behavior for Shortcut()
@@ -9628,8 +10397,12 @@ bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags
if ((flags & ImGuiInputFlags_Repeat) != 0 && (flags & ImGuiInputFlags_RepeatUntilMask_) == 0)
flags |= ImGuiInputFlags_RepeatUntilKeyModsChange;
- if (!IsKeyChordPressed(key_chord, owner_id, flags))
+ if (!IsKeyChordPressed(key_chord, flags, owner_id))
return false;
+
+ // Claim mods during the press
+ SetKeyOwnersForKeyChord(key_chord & ImGuiMod_Mask_, owner_id);
+
IM_ASSERT((flags & ~ImGuiInputFlags_SupportedByShortcut) == 0); // Passing flags not supported by this function!
return true;
}
@@ -9639,8 +10412,8 @@ bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags
// [SECTION] ERROR CHECKING
//-----------------------------------------------------------------------------
-// Helper function to verify ABI compatibility between caller code and compiled version of Dear ImGui.
-// This is called by IMGUI_CHECKVERSION().
+// Verify ABI compatibility between caller code and compiled version of Dear ImGui. This helps detects some build issues.
+// Called by IMGUI_CHECKVERSION().
// Verify that the type sizes are matching between the calling file's compilation unit and imgui.cpp's compilation unit
// If this triggers you have mismatched headers and compiled code versions.
// - It could be because of a build issue (using new headers with old compiled code)
@@ -9653,12 +10426,12 @@ bool ImGui::DebugCheckVersionAndDataLayout(const char* version, size_t sz_io, si
{
bool error = false;
if (strcmp(version, IMGUI_VERSION) != 0) { error = true; IM_ASSERT(strcmp(version, IMGUI_VERSION) == 0 && "Mismatched version string!"); }
- if (sz_io != sizeof(ImGuiIO)) { error = true; IM_ASSERT(sz_io == sizeof(ImGuiIO) && "Mismatched struct layout!"); }
+ if (sz_io != sizeof(ImGuiIO)) { error = true; IM_ASSERT(sz_io == sizeof(ImGuiIO) && "Mismatched struct layout!"); }
if (sz_style != sizeof(ImGuiStyle)) { error = true; IM_ASSERT(sz_style == sizeof(ImGuiStyle) && "Mismatched struct layout!"); }
- if (sz_vec2 != sizeof(ImVec2)) { error = true; IM_ASSERT(sz_vec2 == sizeof(ImVec2) && "Mismatched struct layout!"); }
- if (sz_vec4 != sizeof(ImVec4)) { error = true; IM_ASSERT(sz_vec4 == sizeof(ImVec4) && "Mismatched struct layout!"); }
- if (sz_vert != sizeof(ImDrawVert)) { error = true; IM_ASSERT(sz_vert == sizeof(ImDrawVert) && "Mismatched struct layout!"); }
- if (sz_idx != sizeof(ImDrawIdx)) { error = true; IM_ASSERT(sz_idx == sizeof(ImDrawIdx) && "Mismatched struct layout!"); }
+ if (sz_vec2 != sizeof(ImVec2)) { error = true; IM_ASSERT(sz_vec2 == sizeof(ImVec2) && "Mismatched struct layout!"); }
+ if (sz_vec4 != sizeof(ImVec4)) { error = true; IM_ASSERT(sz_vec4 == sizeof(ImVec4) && "Mismatched struct layout!"); }
+ if (sz_vert != sizeof(ImDrawVert)) { error = true; IM_ASSERT(sz_vert == sizeof(ImDrawVert) && "Mismatched struct layout!"); }
+ if (sz_idx != sizeof(ImDrawIdx)) { error = true; IM_ASSERT(sz_idx == sizeof(ImDrawIdx) && "Mismatched struct layout!"); }
return !error;
}
@@ -9738,6 +10511,45 @@ static void ImGui::ErrorCheckNewFrameSanityChecks()
// Check: the io.ConfigWindowsResizeFromEdges option requires backend to honor mouse cursor changes and set the ImGuiBackendFlags_HasMouseCursors flag accordingly.
if (g.IO.ConfigWindowsResizeFromEdges && !(g.IO.BackendFlags & ImGuiBackendFlags_HasMouseCursors))
g.IO.ConfigWindowsResizeFromEdges = false;
+
+ // Perform simple check: error if Docking or Viewport are enabled _exactly_ on frame 1 (instead of frame 0 or later), which is a common error leading to loss of .ini data.
+ if (g.FrameCount == 1 && (g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable) && (g.ConfigFlagsLastFrame & ImGuiConfigFlags_DockingEnable) == 0)
+ IM_ASSERT(0 && "Please set DockingEnable before the first call to NewFrame()! Otherwise you will lose your .ini settings!");
+ if (g.FrameCount == 1 && (g.IO.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) && (g.ConfigFlagsLastFrame & ImGuiConfigFlags_ViewportsEnable) == 0)
+ IM_ASSERT(0 && "Please set ViewportsEnable before the first call to NewFrame()! Otherwise you will lose your .ini settings!");
+
+ // Perform simple checks: multi-viewport and platform windows support
+ if (g.IO.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
+ {
+ if ((g.IO.BackendFlags & ImGuiBackendFlags_PlatformHasViewports) && (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasViewports))
+ {
+ IM_ASSERT((g.FrameCount == 0 || g.FrameCount == g.FrameCountPlatformEnded) && "Forgot to call UpdatePlatformWindows() in main loop after EndFrame()? Check examples/ applications for reference.");
+ IM_ASSERT(g.PlatformIO.Platform_CreateWindow != NULL && "Platform init didn't install handlers?");
+ IM_ASSERT(g.PlatformIO.Platform_DestroyWindow != NULL && "Platform init didn't install handlers?");
+ IM_ASSERT(g.PlatformIO.Platform_GetWindowPos != NULL && "Platform init didn't install handlers?");
+ IM_ASSERT(g.PlatformIO.Platform_SetWindowPos != NULL && "Platform init didn't install handlers?");
+ IM_ASSERT(g.PlatformIO.Platform_GetWindowSize != NULL && "Platform init didn't install handlers?");
+ IM_ASSERT(g.PlatformIO.Platform_SetWindowSize != NULL && "Platform init didn't install handlers?");
+ IM_ASSERT(g.PlatformIO.Monitors.Size > 0 && "Platform init didn't setup Monitors list?");
+ IM_ASSERT((g.Viewports[0]->PlatformUserData != NULL || g.Viewports[0]->PlatformHandle != NULL) && "Platform init didn't setup main viewport.");
+ if (g.IO.ConfigDockingTransparentPayload && (g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable))
+ IM_ASSERT(g.PlatformIO.Platform_SetWindowAlpha != NULL && "Platform_SetWindowAlpha handler is required to use io.ConfigDockingTransparent!");
+ }
+ else
+ {
+ // Disable feature, our backends do not support it
+ g.IO.ConfigFlags &= ~ImGuiConfigFlags_ViewportsEnable;
+ }
+
+ // Perform simple checks on platform monitor data + compute a total bounding box for quick early outs
+ for (ImGuiPlatformMonitor& mon : g.PlatformIO.Monitors)
+ {
+ IM_UNUSED(mon);
+ IM_ASSERT(mon.MainSize.x > 0.0f && mon.MainSize.y > 0.0f && "Monitor main bounds not setup properly.");
+ IM_ASSERT(ImRect(mon.MainPos, mon.MainPos + mon.MainSize).Contains(ImRect(mon.WorkPos, mon.WorkPos + mon.WorkSize)) && "Monitor work bounds not setup properly. If you don't have work area information, just copy MainPos/MainSize into them.");
+ IM_ASSERT(mon.DpiScale != 0.0f);
+ }
+ }
}
static void ImGui::ErrorCheckEndFrameSanityChecks()
@@ -9917,7 +10729,6 @@ void ImGuiStackSizes::CompareWithContextState(ImGuiContext* ctx)
// [SECTION] ITEM SUBMISSION
//-----------------------------------------------------------------------------
// - KeepAliveID()
-// - ItemHandleShortcut() [Internal]
// - ItemAdd()
//-----------------------------------------------------------------------------
@@ -9931,20 +10742,6 @@ void ImGui::KeepAliveID(ImGuiID id)
g.ActiveIdPreviousFrameIsAlive = true;
}
-static void ItemHandleShortcut(ImGuiID id)
-{
- // FIXME: Generalize Activation queue?
- ImGuiContext& g = *GImGui;
- if (ImGui::Shortcut(g.NextItemData.Shortcut, id, ImGuiInputFlags_None) && g.NavActivateId == 0)
- {
- g.NavActivateId = id; // Will effectively disable clipping.
- g.NavActivateFlags = ImGuiActivateFlags_PreferInput | ImGuiActivateFlags_FromShortcut;
- if (g.ActiveId == 0 || g.ActiveId == id)
- g.NavActivateDownId = g.NavActivatePressedId = id;
- ImGui::NavHighlightActivated(id);
- }
-}
-
// Declare item bounding box for clipping and interaction.
// Note that the size can be different than the one provided to ItemSize(). Typically, widgets that spread over available surface
// declare their minimum size requirement to ItemSize() and provide a larger region to ItemAdd() which is used drawing/interaction.
@@ -10758,7 +11555,7 @@ bool ImGui::BeginTooltipEx(ImGuiTooltipFlags tooltip_flags, ImGuiWindowFlags ext
SetWindowHiddenAndSkipItemsForCurrentFrame(window);
ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", ++g.TooltipOverrideCount);
}
- ImGuiWindowFlags flags = ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize;
+ ImGuiWindowFlags flags = ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDocking;
Begin(window_name, NULL, flags | extra_window_flags);
// 2023-03-09: Added bool return value to the API, but currently always returning true.
// If this ever returns false we need to update BeginDragDropSource() accordingly.
@@ -10831,8 +11628,8 @@ bool ImGui::IsPopupOpen(ImGuiID id, ImGuiPopupFlags popup_flags)
if (popup_flags & ImGuiPopupFlags_AnyPopupLevel)
{
// Return true if the popup is open anywhere in the popup stack
- for (int n = 0; n < g.OpenPopupStack.Size; n++)
- if (g.OpenPopupStack[n].PopupId == id)
+ for (ImGuiPopupData& popup_data : g.OpenPopupStack)
+ if (popup_data.PopupId == id)
return true;
return false;
}
@@ -10971,12 +11768,13 @@ void ImGui::ClosePopupsOverWindow(ImGuiWindow* ref_window, bool restore_focus_to
// Window -> Popup1 -> Window2(Ref)
// - Clicking/focusing Popup1 will close Popup2 and Popup3:
// Window -> Popup1(Ref) -> Popup2 -> Popup3
- // - Each popups may contain child windows, which is why we compare ->RootWindow!
+ // - Each popups may contain child windows, which is why we compare ->RootWindowDockTree!
// Window -> Popup1 -> Popup1_Child -> Popup2 -> Popup2_Child
// We step through every popup from bottom to top to validate their position relative to reference window.
bool ref_window_is_descendent_of_popup = false;
for (int n = popup_count_to_keep; n < g.OpenPopupStack.Size; n++)
if (ImGuiWindow* popup_window = g.OpenPopupStack[n].Window)
+ //if (popup_window->RootWindowDockTree == ref_window->RootWindowDockTree) // FIXME-MERGE
if (IsWindowWithinBeginStackOf(ref_window, popup_window))
{
ref_window_is_descendent_of_popup = true;
@@ -11077,7 +11875,7 @@ bool ImGui::BeginPopupEx(ImGuiID id, ImGuiWindowFlags flags)
else
ImFormatString(name, IM_ARRAYSIZE(name), "##Popup_%08x", id); // Not recycling, so we can close/open during the same frame
- flags |= ImGuiWindowFlags_Popup;
+ flags |= ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoDocking;
bool is_open = Begin(name, NULL, flags);
if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display)
EndPopup();
@@ -11122,11 +11920,11 @@ bool ImGui::BeginPopupModal(const char* name, bool* p_open, ImGuiWindowFlags fla
// FIXME: Should test for (PosCond & window->SetWindowPosAllowFlags) with the upcoming window.
if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasPos) == 0)
{
- const ImGuiViewport* viewport = GetMainViewport();
+ const ImGuiViewport* viewport = window->WasActive ? window->Viewport : GetMainViewport(); // FIXME-VIEWPORT: What may be our reference viewport?
SetNextWindowPos(viewport->GetCenter(), ImGuiCond_FirstUseEver, ImVec2(0.5f, 0.5f));
}
- flags |= ImGuiWindowFlags_Popup | ImGuiWindowFlags_Modal | ImGuiWindowFlags_NoCollapse;
+ flags |= ImGuiWindowFlags_Popup | ImGuiWindowFlags_Modal | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDocking;
const bool is_open = Begin(name, p_open, flags);
if (!is_open || (p_open && !*p_open)) // NB: is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
{
@@ -11313,8 +12111,19 @@ ImVec2 ImGui::FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& s
ImRect ImGui::GetPopupAllowedExtentRect(ImGuiWindow* window)
{
ImGuiContext& g = *GImGui;
- IM_UNUSED(window);
- ImRect r_screen = ((ImGuiViewportP*)(void*)GetMainViewport())->GetMainRect();
+ ImRect r_screen;
+ if (window->ViewportAllowPlatformMonitorExtend >= 0)
+ {
+ // Extent with be in the frame of reference of the given viewport (so Min is likely to be negative here)
+ const ImGuiPlatformMonitor& monitor = g.PlatformIO.Monitors[window->ViewportAllowPlatformMonitorExtend];
+ r_screen.Min = monitor.WorkPos;
+ r_screen.Max = monitor.WorkPos + monitor.WorkSize;
+ }
+ else
+ {
+ // Use the full viewport area (not work area) for popups
+ r_screen = window->Viewport->GetMainRect();
+ }
ImVec2 padding = g.Style.DisplaySafeAreaPadding;
r_screen.Expand(ImVec2((r_screen.GetWidth() > padding.x * 2) ? -padding.x : 0.0f, (r_screen.GetHeight() > padding.y * 2) ? -padding.y : 0.0f));
return r_screen;
@@ -11329,8 +12138,7 @@ ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window)
{
// Child menus typically request _any_ position within the parent menu item, and then we move the new menu outside the parent bounds.
// This is how we end up with child menus appearing (most-commonly) on the right of the parent menu.
- IM_ASSERT(g.CurrentWindow == window);
- ImGuiWindow* parent_window = g.CurrentWindowStack[g.CurrentWindowStack.Size - 2].Window;
+ ImGuiWindow* parent_window = window->ParentWindow;
float horizontal_overlap = g.Style.ItemInnerSpacing.x; // We want some overlap to convey the relative depth of each menu (currently the amount of overlap is hard-coded to style.ItemSpacing.x).
ImRect r_avoid;
if (parent_window->DC.MenuBarAppending)
@@ -11868,6 +12676,9 @@ static ImGuiWindow* ImGui::NavRestoreLastChildNavWindow(ImGuiWindow* window)
{
if (window->NavLastChildNavWindow && window->NavLastChildNavWindow->WasActive)
return window->NavLastChildNavWindow;
+ if (window->DockNodeAsHost && window->DockNodeAsHost->TabBar)
+ if (ImGuiTabItem* tab = TabBarFindMostRecentlySelectedTabForActiveWindow(window->DockNodeAsHost->TabBar))
+ return tab->Window;
return window;
}
@@ -11912,6 +12723,7 @@ static inline void ImGui::NavUpdateAnyRequestFlag()
// This needs to be called before we submit any widget (aka in or before Begin)
void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit)
{
+ // FIXME: ChildWindow test here is wrong for docking
ImGuiContext& g = *GImGui;
IM_ASSERT(window == g.NavWindow);
@@ -11945,7 +12757,10 @@ static ImVec2 ImGui::NavCalcPreferredRefPos()
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.NavWindow;
- if (g.NavDisableHighlight || !g.NavDisableMouseHover || !window)
+ const bool activated_shortcut = g.ActiveId != 0 && g.ActiveIdFromShortcut && g.ActiveId == g.LastItemData.ID;
+
+ // Testing for !activated_shortcut here could in theory be removed if we decided that activating a remote shortcut altered one of the g.NavDisableXXX flag.
+ if ((g.NavDisableHighlight || !g.NavDisableMouseHover || !window) && !activated_shortcut)
{
// Mouse (we need a fallback in case the mouse becomes invalid after being used)
// The +1.0f offset when stored by OpenPopupEx() allows reopening this or another popup (same or another mouse button) while not moving the mouse, it is pretty standard.
@@ -11956,15 +12771,20 @@ static ImVec2 ImGui::NavCalcPreferredRefPos()
else
{
// When navigation is active and mouse is disabled, pick a position around the bottom left of the currently navigated item
+ ImRect ref_rect;
+ if (activated_shortcut)
+ ref_rect = g.LastItemData.NavRect;
+ else
+ ref_rect = WindowRectRelToAbs(window, window->NavRectRel[g.NavLayer]);
+
// Take account of upcoming scrolling (maybe set mouse pos should be done in EndFrame?)
- ImRect rect_rel = WindowRectRelToAbs(window, window->NavRectRel[g.NavLayer]);
if (window->LastFrameActive != g.FrameCount && (window->ScrollTarget.x != FLT_MAX || window->ScrollTarget.y != FLT_MAX))
{
ImVec2 next_scroll = CalcNextScrollFromScrollTargetAndClamp(window);
- rect_rel.Translate(window->Scroll - next_scroll);
+ ref_rect.Translate(window->Scroll - next_scroll);
}
- ImVec2 pos = ImVec2(rect_rel.Min.x + ImMin(g.Style.FramePadding.x * 4, rect_rel.GetWidth()), rect_rel.Max.y - ImMin(g.Style.FramePadding.y, rect_rel.GetHeight()));
- ImGuiViewport* viewport = GetMainViewport();
+ ImVec2 pos = ImVec2(ref_rect.Min.x + ImMin(g.Style.FramePadding.x * 4, ref_rect.GetWidth()), ref_rect.Max.y - ImMin(g.Style.FramePadding.y, ref_rect.GetHeight()));
+ ImGuiViewport* viewport = window->Viewport;
return ImTrunc(ImClamp(pos, viewport->Pos, viewport->Pos + viewport->Size)); // ImTrunc() is important because non-integer mouse position application in backend might be lossy and result in undesirable non-zero delta.
}
}
@@ -12058,10 +12878,10 @@ static void ImGui::NavUpdate()
g.NavActivateFlags = ImGuiActivateFlags_None;
if (g.NavId != 0 && !g.NavDisableHighlight && !g.NavWindowingTarget && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))
{
- const bool activate_down = (nav_keyboard_active && IsKeyDown(ImGuiKey_Space, ImGuiKeyOwner_None)) || (nav_gamepad_active && IsKeyDown(ImGuiKey_NavGamepadActivate, ImGuiKeyOwner_None));
- const bool activate_pressed = activate_down && ((nav_keyboard_active && IsKeyPressed(ImGuiKey_Space, ImGuiKeyOwner_None)) || (nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadActivate, ImGuiKeyOwner_None)));
- const bool input_down = (nav_keyboard_active && (IsKeyDown(ImGuiKey_Enter, ImGuiKeyOwner_None) || IsKeyDown(ImGuiKey_KeypadEnter, ImGuiKeyOwner_None))) || (nav_gamepad_active && IsKeyDown(ImGuiKey_NavGamepadInput, ImGuiKeyOwner_None));
- const bool input_pressed = input_down && ((nav_keyboard_active && (IsKeyPressed(ImGuiKey_Enter, ImGuiKeyOwner_None) || IsKeyPressed(ImGuiKey_KeypadEnter, ImGuiKeyOwner_None))) || (nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadInput, ImGuiKeyOwner_None)));
+ const bool activate_down = (nav_keyboard_active && IsKeyDown(ImGuiKey_Space, ImGuiKeyOwner_NoOwner)) || (nav_gamepad_active && IsKeyDown(ImGuiKey_NavGamepadActivate, ImGuiKeyOwner_NoOwner));
+ const bool activate_pressed = activate_down && ((nav_keyboard_active && IsKeyPressed(ImGuiKey_Space, 0, ImGuiKeyOwner_NoOwner)) || (nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadActivate, 0, ImGuiKeyOwner_NoOwner)));
+ const bool input_down = (nav_keyboard_active && (IsKeyDown(ImGuiKey_Enter, ImGuiKeyOwner_NoOwner) || IsKeyDown(ImGuiKey_KeypadEnter, ImGuiKeyOwner_NoOwner))) || (nav_gamepad_active && IsKeyDown(ImGuiKey_NavGamepadInput, ImGuiKeyOwner_NoOwner));
+ const bool input_pressed = input_down && ((nav_keyboard_active && (IsKeyPressed(ImGuiKey_Enter, 0, ImGuiKeyOwner_NoOwner) || IsKeyPressed(ImGuiKey_KeypadEnter, 0, ImGuiKeyOwner_NoOwner))) || (nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadInput, 0, ImGuiKeyOwner_NoOwner)));
if (g.ActiveId == 0 && activate_pressed)
{
g.NavActivateId = g.NavId;
@@ -12234,11 +13054,11 @@ void ImGui::NavUpdateCreateMoveRequest()
g.NavMoveScrollFlags = ImGuiScrollFlags_None;
if (window && !g.NavWindowingTarget && !(window->Flags & ImGuiWindowFlags_NoNavInputs))
{
- const ImGuiInputFlags repeat_mode = ImGuiInputFlags_Repeat | ImGuiInputFlags_RepeatRateNavMove;
- if (!IsActiveIdUsingNavDir(ImGuiDir_Left) && ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadLeft, ImGuiKeyOwner_None, repeat_mode)) || (nav_keyboard_active && IsKeyPressed(ImGuiKey_LeftArrow, ImGuiKeyOwner_None, repeat_mode)))) { g.NavMoveDir = ImGuiDir_Left; }
- if (!IsActiveIdUsingNavDir(ImGuiDir_Right) && ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadRight, ImGuiKeyOwner_None, repeat_mode)) || (nav_keyboard_active && IsKeyPressed(ImGuiKey_RightArrow, ImGuiKeyOwner_None, repeat_mode)))) { g.NavMoveDir = ImGuiDir_Right; }
- if (!IsActiveIdUsingNavDir(ImGuiDir_Up) && ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadUp, ImGuiKeyOwner_None, repeat_mode)) || (nav_keyboard_active && IsKeyPressed(ImGuiKey_UpArrow, ImGuiKeyOwner_None, repeat_mode)))) { g.NavMoveDir = ImGuiDir_Up; }
- if (!IsActiveIdUsingNavDir(ImGuiDir_Down) && ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadDown, ImGuiKeyOwner_None, repeat_mode)) || (nav_keyboard_active && IsKeyPressed(ImGuiKey_DownArrow, ImGuiKeyOwner_None, repeat_mode)))) { g.NavMoveDir = ImGuiDir_Down; }
+ const ImGuiInputFlags repeat_mode = ImGuiInputFlags_Repeat | (ImGuiInputFlags)ImGuiInputFlags_RepeatRateNavMove;
+ if (!IsActiveIdUsingNavDir(ImGuiDir_Left) && ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadLeft, repeat_mode, ImGuiKeyOwner_NoOwner)) || (nav_keyboard_active && IsKeyPressed(ImGuiKey_LeftArrow, repeat_mode, ImGuiKeyOwner_NoOwner)))) { g.NavMoveDir = ImGuiDir_Left; }
+ if (!IsActiveIdUsingNavDir(ImGuiDir_Right) && ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadRight, repeat_mode, ImGuiKeyOwner_NoOwner)) || (nav_keyboard_active && IsKeyPressed(ImGuiKey_RightArrow, repeat_mode, ImGuiKeyOwner_NoOwner)))) { g.NavMoveDir = ImGuiDir_Right; }
+ if (!IsActiveIdUsingNavDir(ImGuiDir_Up) && ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadUp, repeat_mode, ImGuiKeyOwner_NoOwner)) || (nav_keyboard_active && IsKeyPressed(ImGuiKey_UpArrow, repeat_mode, ImGuiKeyOwner_NoOwner)))) { g.NavMoveDir = ImGuiDir_Up; }
+ if (!IsActiveIdUsingNavDir(ImGuiDir_Down) && ((nav_gamepad_active && IsKeyPressed(ImGuiKey_GamepadDpadDown, repeat_mode, ImGuiKeyOwner_NoOwner)) || (nav_keyboard_active && IsKeyPressed(ImGuiKey_DownArrow, repeat_mode, ImGuiKeyOwner_NoOwner)))) { g.NavMoveDir = ImGuiDir_Down; }
}
g.NavMoveClipDir = g.NavMoveDir;
g.NavScoringNoClipRect = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX);
@@ -12334,7 +13154,7 @@ void ImGui::NavUpdateCreateTabbingRequest()
if (window == NULL || g.NavWindowingTarget != NULL || (window->Flags & ImGuiWindowFlags_NoNavInputs))
return;
- const bool tab_pressed = IsKeyPressed(ImGuiKey_Tab, ImGuiKeyOwner_None, ImGuiInputFlags_Repeat) && !g.IO.KeyCtrl && !g.IO.KeyAlt;
+ const bool tab_pressed = IsKeyPressed(ImGuiKey_Tab, ImGuiInputFlags_Repeat, ImGuiKeyOwner_NoOwner) && !g.IO.KeyCtrl && !g.IO.KeyAlt;
if (!tab_pressed)
return;
@@ -12472,7 +13292,7 @@ static void ImGui::NavUpdateCancelRequest()
ImGuiContext& g = *GImGui;
const bool nav_gamepad_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (g.IO.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
const bool nav_keyboard_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0;
- if (!(nav_keyboard_active && IsKeyPressed(ImGuiKey_Escape, ImGuiKeyOwner_None)) && !(nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadCancel, ImGuiKeyOwner_None)))
+ if (!(nav_keyboard_active && IsKeyPressed(ImGuiKey_Escape, 0, ImGuiKeyOwner_NoOwner)) && !(nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadCancel, 0, ImGuiKeyOwner_NoOwner)))
return;
IMGUI_DEBUG_LOG_NAV("[nav] NavUpdateCancelRequest()\n");
@@ -12521,10 +13341,10 @@ static float ImGui::NavUpdatePageUpPageDown()
if ((window->Flags & ImGuiWindowFlags_NoNavInputs) || g.NavWindowingTarget != NULL)
return 0.0f;
- const bool page_up_held = IsKeyDown(ImGuiKey_PageUp, ImGuiKeyOwner_None);
- const bool page_down_held = IsKeyDown(ImGuiKey_PageDown, ImGuiKeyOwner_None);
- const bool home_pressed = IsKeyPressed(ImGuiKey_Home, ImGuiKeyOwner_None, ImGuiInputFlags_Repeat);
- const bool end_pressed = IsKeyPressed(ImGuiKey_End, ImGuiKeyOwner_None, ImGuiInputFlags_Repeat);
+ const bool page_up_held = IsKeyDown(ImGuiKey_PageUp, ImGuiKeyOwner_NoOwner);
+ const bool page_down_held = IsKeyDown(ImGuiKey_PageDown, ImGuiKeyOwner_NoOwner);
+ const bool home_pressed = IsKeyPressed(ImGuiKey_Home, ImGuiInputFlags_Repeat, ImGuiKeyOwner_NoOwner);
+ const bool end_pressed = IsKeyPressed(ImGuiKey_End, ImGuiInputFlags_Repeat, ImGuiKeyOwner_NoOwner);
if (page_up_held == page_down_held && home_pressed == end_pressed) // Proceed if either (not both) are pressed, otherwise early out
return 0.0f;
@@ -12534,9 +13354,9 @@ static float ImGui::NavUpdatePageUpPageDown()
if (window->DC.NavLayersActiveMask == 0x00 && window->DC.NavWindowHasScrollY)
{
// Fallback manual-scroll when window has no navigable item
- if (IsKeyPressed(ImGuiKey_PageUp, ImGuiKeyOwner_None, ImGuiInputFlags_Repeat))
+ if (IsKeyPressed(ImGuiKey_PageUp, ImGuiInputFlags_Repeat, ImGuiKeyOwner_NoOwner))
SetScrollY(window, window->Scroll.y - window->InnerRect.GetHeight());
- else if (IsKeyPressed(ImGuiKey_PageDown, ImGuiKeyOwner_None, ImGuiInputFlags_Repeat))
+ else if (IsKeyPressed(ImGuiKey_PageDown, ImGuiInputFlags_Repeat, ImGuiKeyOwner_NoOwner))
SetScrollY(window, window->Scroll.y + window->InnerRect.GetHeight());
else if (home_pressed)
SetScrollY(window, 0.0f);
@@ -12729,9 +13549,9 @@ static void ImGui::NavUpdateWindowing()
const ImGuiID owner_id = ImHashStr("###NavUpdateWindowing");
const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
const bool nav_keyboard_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0;
- const bool keyboard_next_window = allow_windowing && g.ConfigNavWindowingKeyNext && Shortcut(g.ConfigNavWindowingKeyNext, owner_id, ImGuiInputFlags_Repeat | ImGuiInputFlags_RouteAlways);
- const bool keyboard_prev_window = allow_windowing && g.ConfigNavWindowingKeyPrev && Shortcut(g.ConfigNavWindowingKeyPrev, owner_id, ImGuiInputFlags_Repeat | ImGuiInputFlags_RouteAlways);
- const bool start_windowing_with_gamepad = allow_windowing && nav_gamepad_active && !g.NavWindowingTarget && IsKeyPressed(ImGuiKey_NavGamepadMenu, 0, ImGuiInputFlags_None);
+ const bool keyboard_next_window = allow_windowing && g.ConfigNavWindowingKeyNext && Shortcut(g.ConfigNavWindowingKeyNext, ImGuiInputFlags_Repeat | ImGuiInputFlags_RouteAlways, owner_id);
+ const bool keyboard_prev_window = allow_windowing && g.ConfigNavWindowingKeyPrev && Shortcut(g.ConfigNavWindowingKeyPrev, ImGuiInputFlags_Repeat | ImGuiInputFlags_RouteAlways, owner_id);
+ const bool start_windowing_with_gamepad = allow_windowing && nav_gamepad_active && !g.NavWindowingTarget && IsKeyPressed(ImGuiKey_NavGamepadMenu, ImGuiInputFlags_None);
const bool start_windowing_with_keyboard = allow_windowing && !g.NavWindowingTarget && (keyboard_next_window || keyboard_prev_window); // Note: enabled even without NavEnableKeyboard!
if (start_windowing_with_gamepad || start_windowing_with_keyboard)
if (ImGuiWindow* window = g.NavWindow ? g.NavWindow : FindWindowNavFocusable(g.WindowsFocusOrder.Size - 1, -INT_MAX, -1))
@@ -12742,7 +13562,7 @@ static void ImGui::NavUpdateWindowing()
g.NavWindowingToggleLayer = start_windowing_with_gamepad ? true : false; // Gamepad starts toggling layer
g.NavInputSource = start_windowing_with_keyboard ? ImGuiInputSource_Keyboard : ImGuiInputSource_Gamepad;
- // Register ownership of our mods. Using ImGuiInputFlags_RouteGlobalHigh in the Shortcut() calls instead would probably be correct but may have more side-effects.
+ // Manually register ownership of our mods. Using a global route in the Shortcut() calls instead would probably be correct but may have more side-effects.
if (keyboard_next_window || keyboard_prev_window)
SetKeyOwnersForKeyChord((g.ConfigNavWindowingKeyNext | g.ConfigNavWindowingKeyPrev) & ImGuiMod_Mask_, owner_id);
}
@@ -12790,7 +13610,7 @@ static void ImGui::NavUpdateWindowing()
// Keyboard: Press and Release ALT to toggle menu layer
const ImGuiKey windowing_toggle_keys[] = { ImGuiKey_LeftAlt, ImGuiKey_RightAlt };
for (ImGuiKey windowing_toggle_key : windowing_toggle_keys)
- if (nav_keyboard_active && IsKeyPressed(windowing_toggle_key, ImGuiKeyOwner_None))
+ if (nav_keyboard_active && IsKeyPressed(windowing_toggle_key, 0, ImGuiKeyOwner_NoOwner))
{
g.NavWindowingToggleLayer = true;
g.NavWindowingToggleKey = windowing_toggle_key;
@@ -12805,7 +13625,7 @@ static void ImGui::NavUpdateWindowing()
// We cancel toggling nav layer if an owner has claimed the key.
if (io.InputQueueCharacters.Size > 0 || io.KeyCtrl || io.KeyShift || io.KeySuper)
g.NavWindowingToggleLayer = false;
- if (TestKeyOwner(g.NavWindowingToggleKey, ImGuiKeyOwner_None) == false || TestKeyOwner(ImGuiMod_Alt, ImGuiKeyOwner_None) == false)
+ if (TestKeyOwner(g.NavWindowingToggleKey, ImGuiKeyOwner_NoOwner) == false || TestKeyOwner(ImGuiMod_Alt, ImGuiKeyOwner_NoOwner) == false)
g.NavWindowingToggleLayer = false;
// Apply layer toggle on Alt release
@@ -12835,7 +13655,7 @@ static void ImGui::NavUpdateWindowing()
ImVec2 accum_floored = ImTrunc(g.NavWindowingAccumDeltaPos);
if (accum_floored.x != 0.0f || accum_floored.y != 0.0f)
{
- ImGuiWindow* moving_window = g.NavWindowingTarget->RootWindow;
+ ImGuiWindow* moving_window = g.NavWindowingTarget->RootWindowDockTree;
SetWindowPos(moving_window, moving_window->Pos + accum_floored, ImGuiCond_Always);
g.NavWindowingAccumDeltaPos -= accum_floored;
}
@@ -12845,6 +13665,9 @@ static void ImGui::NavUpdateWindowing()
// Apply final focus
if (apply_focus_window && (g.NavWindow == NULL || apply_focus_window != g.NavWindow->RootWindow))
{
+ // FIXME: Many actions here could be part of a higher-level/reused function. Why aren't they in FocusWindow()
+ // Investigate for each of them: ClearActiveID(), NavRestoreHighlightAfterMove(), NavRestoreLastChildNavWindow(), ClosePopupsOverWindow(), NavInitWindow()
+ ImGuiViewport* previous_viewport = g.NavWindow ? g.NavWindow->Viewport : NULL;
ClearActiveID();
NavRestoreHighlightAfterMove();
ClosePopupsOverWindow(apply_focus_window, false);
@@ -12862,6 +13685,10 @@ static void ImGui::NavUpdateWindowing()
// won't be valid.
if (apply_focus_window->DC.NavLayersActiveMaskNext == (1 << ImGuiNavLayer_Menu))
g.NavLayer = ImGuiNavLayer_Menu;
+
+ // Request OS level focus
+ if (apply_focus_window->Viewport != previous_viewport && g.PlatformIO.Platform_SetWindowFocus)
+ g.PlatformIO.Platform_SetWindowFocus(apply_focus_window->Viewport);
}
if (apply_focus_window)
g.NavWindowingTarget = NULL;
@@ -12890,7 +13717,8 @@ static void ImGui::NavUpdateWindowing()
if (new_nav_layer != g.NavLayer)
{
// Reinitialize navigation when entering menu bar with the Alt key (FIXME: could be a properly of the layer?)
- if (new_nav_layer == ImGuiNavLayer_Menu)
+ const bool preserve_layer_1_nav_id = (new_nav_window->DockNodeAsHost != NULL);
+ if (new_nav_layer == ImGuiNavLayer_Menu && !preserve_layer_1_nav_id)
g.NavWindow->NavLastIds[new_nav_layer] = 0;
NavRestoreLayer(new_nav_layer);
NavRestoreHighlightAfterMove();
@@ -12905,6 +13733,8 @@ static const char* GetFallbackWindowNameForWindowingList(ImGuiWindow* window)
return ImGui::LocalizeGetMsg(ImGuiLocKey_WindowingPopup);
if ((window->Flags & ImGuiWindowFlags_MenuBar) && strcmp(window->Name, "##MainMenuBar") == 0)
return ImGui::LocalizeGetMsg(ImGuiLocKey_WindowingMainMenuBar);
+ if (window->DockNodeAsHost)
+ return "(Dock node)"; // Not normally shown to user.
return ImGui::LocalizeGetMsg(ImGuiLocKey_WindowingUntitled);
}
@@ -12919,7 +13749,7 @@ void ImGui::NavUpdateWindowingOverlay()
if (g.NavWindowingListWindow == NULL)
g.NavWindowingListWindow = FindWindowByName("###NavWindowingList");
- const ImGuiViewport* viewport = GetMainViewport();
+ const ImGuiViewport* viewport = /*g.NavWindow ? g.NavWindow->Viewport :*/ GetMainViewport();
SetNextWindowSizeConstraints(ImVec2(viewport->Size.x * 0.20f, viewport->Size.y * 0.20f), ImVec2(FLT_MAX, FLT_MAX));
SetNextWindowPos(viewport->GetCenter(), ImGuiCond_Always, ImVec2(0.5f, 0.5f));
PushStyleVar(ImGuiStyleVar_WindowPadding, g.Style.WindowPadding * 2.0f);
@@ -13160,7 +13990,7 @@ bool ImGui::BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id)
ImGuiWindow* window = g.CurrentWindow;
ImGuiWindow* hovered_window = g.HoveredWindowUnderMovingWindow;
- if (hovered_window == NULL || window->RootWindow != hovered_window->RootWindow)
+ if (hovered_window == NULL || window->RootWindowDockTree != hovered_window->RootWindowDockTree)
return false;
IM_ASSERT(id != 0);
if (!IsMouseHoveringRect(bb.Min, bb.Max) || (id == g.DragDropPayload.SourceId))
@@ -13190,7 +14020,7 @@ bool ImGui::BeginDragDropTarget()
if (!(g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect))
return false;
ImGuiWindow* hovered_window = g.HoveredWindowUnderMovingWindow;
- if (hovered_window == NULL || window->RootWindow != hovered_window->RootWindow || window->SkipItems)
+ if (hovered_window == NULL || window->RootWindowDockTree != hovered_window->RootWindowDockTree || window->SkipItems)
return false;
const ImRect& display_rect = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HasDisplayRect) ? g.LastItemData.DisplayRect : g.LastItemData.Rect;
@@ -13791,11 +14621,14 @@ ImGuiWindowSettings* ImGui::FindWindowSettingsByWindow(ImGuiWindow* window)
void ImGui::ClearWindowSettings(const char* name)
{
//IMGUI_DEBUG_LOG("ClearWindowSettings('%s')\n", name);
+ ImGuiContext& g = *GImGui;
ImGuiWindow* window = FindWindowByName(name);
if (window != NULL)
{
window->Flags |= ImGuiWindowFlags_NoSavedSettings;
InitOrLoadWindowSettings(window, NULL);
+ if (window->DockId != 0)
+ DockContextProcessUndockWindow(&g, window, true);
}
if (ImGuiWindowSettings* settings = window ? FindWindowSettingsByWindow(window) : FindWindowSettingsByID(ImHashStr(name)))
settings->WantDelete = true;
@@ -13827,10 +14660,16 @@ static void WindowSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*,
ImGuiWindowSettings* settings = (ImGuiWindowSettings*)entry;
int x, y;
int i;
- if (sscanf(line, "Pos=%i,%i", &x, &y) == 2) { settings->Pos = ImVec2ih((short)x, (short)y); }
- else if (sscanf(line, "Size=%i,%i", &x, &y) == 2) { settings->Size = ImVec2ih((short)x, (short)y); }
- else if (sscanf(line, "Collapsed=%d", &i) == 1) { settings->Collapsed = (i != 0); }
- else if (sscanf(line, "IsChild=%d", &i) == 1) { settings->IsChild = (i != 0); }
+ ImU32 u1;
+ if (sscanf(line, "Pos=%i,%i", &x, &y) == 2) { settings->Pos = ImVec2ih((short)x, (short)y); }
+ else if (sscanf(line, "Size=%i,%i", &x, &y) == 2) { settings->Size = ImVec2ih((short)x, (short)y); }
+ else if (sscanf(line, "ViewportId=0x%08X", &u1) == 1) { settings->ViewportId = u1; }
+ else if (sscanf(line, "ViewportPos=%i,%i", &x, &y) == 2){ settings->ViewportPos = ImVec2ih((short)x, (short)y); }
+ else if (sscanf(line, "Collapsed=%d", &i) == 1) { settings->Collapsed = (i != 0); }
+ else if (sscanf(line, "IsChild=%d", &i) == 1) { settings->IsChild = (i != 0); }
+ else if (sscanf(line, "DockId=0x%X,%d", &u1, &i) == 2) { settings->DockId = u1; settings->DockOrder = (short)i; }
+ else if (sscanf(line, "DockId=0x%X", &u1) == 1) { settings->DockId = u1; settings->DockOrder = -1; }
+ else if (sscanf(line, "ClassId=0x%X", &u1) == 1) { settings->ClassId = u1; }
}
// Apply to existing windows (if any)
@@ -13863,10 +14702,16 @@ static void WindowSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandl
window->SettingsOffset = g.SettingsWindows.offset_from_ptr(settings);
}
IM_ASSERT(settings->ID == window->ID);
- settings->Pos = ImVec2ih(window->Pos);
+ settings->Pos = ImVec2ih(window->Pos - window->ViewportPos);
settings->Size = ImVec2ih(window->SizeFull);
- settings->IsChild = (window->Flags & ImGuiWindowFlags_ChildWindow) != 0;
+ settings->ViewportId = window->ViewportId;
+ settings->ViewportPos = ImVec2ih(window->ViewportPos);
+ IM_ASSERT(window->DockNode == NULL || window->DockNode->ID == window->DockId);
+ settings->DockId = window->DockId;
+ settings->ClassId = window->WindowClass.ClassId;
+ settings->DockOrder = window->DockOrder;
settings->Collapsed = window->Collapsed;
+ settings->IsChild = (window->RootWindow != window); // Cannot rely on ImGuiWindowFlags_ChildWindow here as docked windows have this set.
settings->WantDelete = false;
}
@@ -13885,10 +14730,26 @@ static void WindowSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandl
}
else
{
- buf->appendf("Pos=%d,%d\n", settings->Pos.x, settings->Pos.y);
- buf->appendf("Size=%d,%d\n", settings->Size.x, settings->Size.y);
- if (settings->Collapsed)
- buf->appendf("Collapsed=1\n");
+ if (settings->ViewportId != 0 && settings->ViewportId != ImGui::IMGUI_VIEWPORT_DEFAULT_ID)
+ {
+ buf->appendf("ViewportPos=%d,%d\n", settings->ViewportPos.x, settings->ViewportPos.y);
+ buf->appendf("ViewportId=0x%08X\n", settings->ViewportId);
+ }
+ if (settings->Pos.x != 0 || settings->Pos.y != 0 || settings->ViewportId == ImGui::IMGUI_VIEWPORT_DEFAULT_ID)
+ buf->appendf("Pos=%d,%d\n", settings->Pos.x, settings->Pos.y);
+ if (settings->Size.x != 0 || settings->Size.y != 0)
+ buf->appendf("Size=%d,%d\n", settings->Size.x, settings->Size.y);
+ buf->appendf("Collapsed=%d\n", settings->Collapsed);
+ if (settings->DockId != 0)
+ {
+ //buf->appendf("TabId=0x%08X\n", ImHashStr("#TAB", 4, settings->ID)); // window->TabId: this is not read back but writing it makes "debugging" the .ini data easier.
+ if (settings->DockOrder == -1)
+ buf->appendf("DockId=0x%08X\n", settings->DockId);
+ else
+ buf->appendf("DockId=0x%08X,%d\n", settings->DockId, settings->DockOrder);
+ if (settings->ClassId != 0)
+ buf->appendf("ClassId=0x%08X\n", settings->ClassId);
+ }
}
buf->append("\n");
}
@@ -13911,9 +14772,28 @@ void ImGui::LocalizeRegisterEntries(const ImGuiLocEntry* entries, int count)
// [SECTION] VIEWPORTS, PLATFORM WINDOWS
//-----------------------------------------------------------------------------
// - GetMainViewport()
+// - FindViewportByID()
+// - FindViewportByPlatformHandle()
+// - SetCurrentViewport() [Internal]
// - SetWindowViewport() [Internal]
+// - GetWindowAlwaysWantOwnViewport() [Internal]
+// - UpdateTryMergeWindowIntoHostViewport() [Internal]
+// - UpdateTryMergeWindowIntoHostViewports() [Internal]
+// - TranslateWindowsInViewport() [Internal]
+// - ScaleWindowsInViewport() [Internal]
+// - FindHoveredViewportFromPlatformWindowStack() [Internal]
// - UpdateViewportsNewFrame() [Internal]
-// (this section is more complete in the 'docking' branch)
+// - UpdateViewportsEndFrame() [Internal]
+// - AddUpdateViewport() [Internal]
+// - WindowSelectViewport() [Internal]
+// - WindowSyncOwnedViewport() [Internal]
+// - UpdatePlatformWindows()
+// - RenderPlatformWindowsDefault()
+// - FindPlatformMonitorForPos() [Internal]
+// - FindPlatformMonitorForRect() [Internal]
+// - UpdateViewportPlatformMonitor() [Internal]
+// - DestroyPlatformWindow() [Internal]
+// - DestroyPlatformWindows()
//-----------------------------------------------------------------------------
ImGuiViewport* ImGui::GetMainViewport()
@@ -13922,172 +14802,5043 @@ ImGuiViewport* ImGui::GetMainViewport()
return g.Viewports[0];
}
-void ImGui::SetWindowViewport(ImGuiWindow* window, ImGuiViewportP* viewport)
+// FIXME: This leaks access to viewports not listed in PlatformIO.Viewports[]. Problematic? (#4236)
+ImGuiViewport* ImGui::FindViewportByID(ImGuiID id)
{
- window->Viewport = viewport;
+ ImGuiContext& g = *GImGui;
+ for (ImGuiViewportP* viewport : g.Viewports)
+ if (viewport->ID == id)
+ return viewport;
+ return NULL;
}
-// Update viewports and monitor infos
-static void ImGui::UpdateViewportsNewFrame()
+ImGuiViewport* ImGui::FindViewportByPlatformHandle(void* platform_handle)
{
ImGuiContext& g = *GImGui;
- IM_ASSERT(g.Viewports.Size == 1);
-
- // Update main viewport with current platform position.
- // FIXME-VIEWPORT: Size is driven by backend/user code for backward-compatibility but we should aim to make this more consistent.
- ImGuiViewportP* main_viewport = g.Viewports[0];
- main_viewport->Flags = ImGuiViewportFlags_IsPlatformWindow | ImGuiViewportFlags_OwnedByApp;
- main_viewport->Pos = ImVec2(0.0f, 0.0f);
- main_viewport->Size = g.IO.DisplaySize;
-
for (ImGuiViewportP* viewport : g.Viewports)
- {
- // Lock down space taken by menu bars and status bars, reset the offset for fucntions like BeginMainMenuBar() to alter them again.
- viewport->WorkOffsetMin = viewport->BuildWorkOffsetMin;
- viewport->WorkOffsetMax = viewport->BuildWorkOffsetMax;
- viewport->BuildWorkOffsetMin = viewport->BuildWorkOffsetMax = ImVec2(0.0f, 0.0f);
- viewport->UpdateWorkRect();
- }
+ if (viewport->PlatformHandle == platform_handle)
+ return viewport;
+ return NULL;
}
-//-----------------------------------------------------------------------------
-// [SECTION] DOCKING
-//-----------------------------------------------------------------------------
+void ImGui::SetCurrentViewport(ImGuiWindow* current_window, ImGuiViewportP* viewport)
+{
+ ImGuiContext& g = *GImGui;
+ (void)current_window;
-// (this section is filled in the 'docking' branch)
+ if (viewport)
+ viewport->LastFrameActive = g.FrameCount;
+ if (g.CurrentViewport == viewport)
+ return;
+ g.CurrentDpiScale = viewport ? viewport->DpiScale : 1.0f;
+ g.CurrentViewport = viewport;
+ //IMGUI_DEBUG_LOG_VIEWPORT("[viewport] SetCurrentViewport changed '%s' 0x%08X\n", current_window ? current_window->Name : NULL, viewport ? viewport->ID : 0);
+ // Notify platform layer of viewport changes
+ // FIXME-DPI: This is only currently used for experimenting with handling of multiple DPI
+ if (g.CurrentViewport && g.PlatformIO.Platform_OnChangedViewport)
+ g.PlatformIO.Platform_OnChangedViewport(g.CurrentViewport);
+}
-//-----------------------------------------------------------------------------
-// [SECTION] PLATFORM DEPENDENT HELPERS
-//-----------------------------------------------------------------------------
+void ImGui::SetWindowViewport(ImGuiWindow* window, ImGuiViewportP* viewport)
+{
+ // Abandon viewport
+ if (window->ViewportOwned && window->Viewport->Window == window)
+ window->Viewport->Size = ImVec2(0.0f, 0.0f);
-#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS)
+ window->Viewport = viewport;
+ window->ViewportId = viewport->ID;
+ window->ViewportOwned = (viewport->Window == window);
+}
-#ifdef _MSC_VER
-#pragma comment(lib, "user32")
-#pragma comment(lib, "kernel32")
-#endif
+static bool ImGui::GetWindowAlwaysWantOwnViewport(ImGuiWindow* window)
+{
+ // Tooltips and menus are not automatically forced into their own viewport when the NoMerge flag is set, however the multiplication of viewports makes them more likely to protrude and create their own.
+ ImGuiContext& g = *GImGui;
+ if (g.IO.ConfigViewportsNoAutoMerge || (window->WindowClass.ViewportFlagsOverrideSet & ImGuiViewportFlags_NoAutoMerge))
+ if (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable)
+ if (!window->DockIsActive)
+ if ((window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_Tooltip)) == 0)
+ if ((window->Flags & ImGuiWindowFlags_Popup) == 0 || (window->Flags & ImGuiWindowFlags_Modal) != 0)
+ return true;
+ return false;
+}
-// Win32 clipboard implementation
-// We use g.ClipboardHandlerData for temporary storage to ensure it is freed on Shutdown()
-static const char* GetClipboardTextFn_DefaultImpl(void* user_data_ctx)
+static bool ImGui::UpdateTryMergeWindowIntoHostViewport(ImGuiWindow* window, ImGuiViewportP* viewport)
{
- ImGuiContext& g = *(ImGuiContext*)user_data_ctx;
- g.ClipboardHandlerData.clear();
- if (!::OpenClipboard(NULL))
- return NULL;
- HANDLE wbuf_handle = ::GetClipboardData(CF_UNICODETEXT);
- if (wbuf_handle == NULL)
- {
- ::CloseClipboard();
- return NULL;
- }
- if (const WCHAR* wbuf_global = (const WCHAR*)::GlobalLock(wbuf_handle))
+ ImGuiContext& g = *GImGui;
+ if (window->Viewport == viewport)
+ return false;
+ if ((viewport->Flags & ImGuiViewportFlags_CanHostOtherWindows) == 0)
+ return false;
+ if ((viewport->Flags & ImGuiViewportFlags_IsMinimized) != 0)
+ return false;
+ if (!viewport->GetMainRect().Contains(window->Rect()))
+ return false;
+ if (GetWindowAlwaysWantOwnViewport(window))
+ return false;
+
+ // FIXME: Can't use g.WindowsFocusOrder[] for root windows only as we care about Z order. If we maintained a DisplayOrder along with FocusOrder we could..
+ for (ImGuiWindow* window_behind : g.Windows)
{
- int buf_len = ::WideCharToMultiByte(CP_UTF8, 0, wbuf_global, -1, NULL, 0, NULL, NULL);
- g.ClipboardHandlerData.resize(buf_len);
- ::WideCharToMultiByte(CP_UTF8, 0, wbuf_global, -1, g.ClipboardHandlerData.Data, buf_len, NULL, NULL);
+ if (window_behind == window)
+ break;
+ if (window_behind->WasActive && window_behind->ViewportOwned && !(window_behind->Flags & ImGuiWindowFlags_ChildWindow))
+ if (window_behind->Viewport->GetMainRect().Overlaps(window->Rect()))
+ return false;
}
- ::GlobalUnlock(wbuf_handle);
- ::CloseClipboard();
- return g.ClipboardHandlerData.Data;
+
+ // Move to the existing viewport, Move child/hosted windows as well (FIXME-OPT: iterate child)
+ ImGuiViewportP* old_viewport = window->Viewport;
+ if (window->ViewportOwned)
+ for (int n = 0; n < g.Windows.Size; n++)
+ if (g.Windows[n]->Viewport == old_viewport)
+ SetWindowViewport(g.Windows[n], viewport);
+ SetWindowViewport(window, viewport);
+ BringWindowToDisplayFront(window);
+
+ return true;
}
-static void SetClipboardTextFn_DefaultImpl(void*, const char* text)
+// FIXME: handle 0 to N host viewports
+static bool ImGui::UpdateTryMergeWindowIntoHostViewports(ImGuiWindow* window)
{
- if (!::OpenClipboard(NULL))
- return;
- const int wbuf_length = ::MultiByteToWideChar(CP_UTF8, 0, text, -1, NULL, 0);
- HGLOBAL wbuf_handle = ::GlobalAlloc(GMEM_MOVEABLE, (SIZE_T)wbuf_length * sizeof(WCHAR));
- if (wbuf_handle == NULL)
- {
- ::CloseClipboard();
- return;
- }
- WCHAR* wbuf_global = (WCHAR*)::GlobalLock(wbuf_handle);
- ::MultiByteToWideChar(CP_UTF8, 0, text, -1, wbuf_global, wbuf_length);
- ::GlobalUnlock(wbuf_handle);
- ::EmptyClipboard();
- if (::SetClipboardData(CF_UNICODETEXT, wbuf_handle) == NULL)
- ::GlobalFree(wbuf_handle);
- ::CloseClipboard();
+ ImGuiContext& g = *GImGui;
+ return UpdateTryMergeWindowIntoHostViewport(window, g.Viewports[0]);
}
-#elif defined(__APPLE__) && TARGET_OS_OSX && defined(IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS)
+// Translate Dear ImGui windows when a Host Viewport has been moved
+// (This additionally keeps windows at the same place when ImGuiConfigFlags_ViewportsEnable is toggled!)
+void ImGui::TranslateWindowsInViewport(ImGuiViewportP* viewport, const ImVec2& old_pos, const ImVec2& new_pos)
+{
+ ImGuiContext& g = *GImGui;
+ IM_ASSERT(viewport->Window == NULL && (viewport->Flags & ImGuiViewportFlags_CanHostOtherWindows));
-#include <Carbon/Carbon.h> // Use old API to avoid need for separate .mm file
-static PasteboardRef main_clipboard = 0;
+ // 1) We test if ImGuiConfigFlags_ViewportsEnable was just toggled, which allows us to conveniently
+ // translate imgui windows from OS-window-local to absolute coordinates or vice-versa.
+ // 2) If it's not going to fit into the new size, keep it at same absolute position.
+ // One problem with this is that most Win32 applications doesn't update their render while dragging,
+ // and so the window will appear to teleport when releasing the mouse.
+ const bool translate_all_windows = (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable) != (g.ConfigFlagsLastFrame & ImGuiConfigFlags_ViewportsEnable);
+ ImRect test_still_fit_rect(old_pos, old_pos + viewport->Size);
+ ImVec2 delta_pos = new_pos - old_pos;
+ for (ImGuiWindow* window : g.Windows) // FIXME-OPT
+ if (translate_all_windows || (window->Viewport == viewport && test_still_fit_rect.Contains(window->Rect())))
+ TranslateWindow(window, delta_pos);
+}
-// OSX clipboard implementation
-// If you enable this you will need to add '-framework ApplicationServices' to your linker command-line!
-static void SetClipboardTextFn_DefaultImpl(void*, const char* text)
+// Scale all windows (position, size). Use when e.g. changing DPI. (This is a lossy operation!)
+void ImGui::ScaleWindowsInViewport(ImGuiViewportP* viewport, float scale)
{
- if (!main_clipboard)
- PasteboardCreate(kPasteboardClipboard, &main_clipboard);
- PasteboardClear(main_clipboard);
- CFDataRef cf_data = CFDataCreate(kCFAllocatorDefault, (const UInt8*)text, strlen(text));
- if (cf_data)
+ ImGuiContext& g = *GImGui;
+ if (viewport->Window)
{
- PasteboardPutItemFlavor(main_clipboard, (PasteboardItemID)1, CFSTR("public.utf8-plain-text"), cf_data, 0);
- CFRelease(cf_data);
+ ScaleWindow(viewport->Window, scale);
+ }
+ else
+ {
+ for (ImGuiWindow* window : g.Windows)
+ if (window->Viewport == viewport)
+ ScaleWindow(window, scale);
}
}
-static const char* GetClipboardTextFn_DefaultImpl(void* user_data_ctx)
+// If the backend doesn't set MouseLastHoveredViewport or doesn't honor ImGuiViewportFlags_NoInputs, we do a search ourselves.
+// A) It won't take account of the possibility that non-imgui windows may be in-between our dragged window and our target window.
+// B) It requires Platform_GetWindowFocus to be implemented by backend.
+ImGuiViewportP* ImGui::FindHoveredViewportFromPlatformWindowStack(const ImVec2& mouse_platform_pos)
{
- ImGuiContext& g = *(ImGuiContext*)user_data_ctx;
- if (!main_clipboard)
- PasteboardCreate(kPasteboardClipboard, &main_clipboard);
- PasteboardSynchronize(main_clipboard);
+ ImGuiContext& g = *GImGui;
+ ImGuiViewportP* best_candidate = NULL;
+ for (ImGuiViewportP* viewport : g.Viewports)
+ if (!(viewport->Flags & (ImGuiViewportFlags_NoInputs | ImGuiViewportFlags_IsMinimized)) && viewport->GetMainRect().Contains(mouse_platform_pos))
+ if (best_candidate == NULL || best_candidate->LastFocusedStampCount < viewport->LastFocusedStampCount)
+ best_candidate = viewport;
+ return best_candidate;
+}
- ItemCount item_count = 0;
- PasteboardGetItemCount(main_clipboard, &item_count);
- for (ItemCount i = 0; i < item_count; i++)
+// Update viewports and monitor infos
+// Note that this is running even if 'ImGuiConfigFlags_ViewportsEnable' is not set, in order to clear unused viewports (if any) and update monitor info.
+static void ImGui::UpdateViewportsNewFrame()
+{
+ ImGuiContext& g = *GImGui;
+ IM_ASSERT(g.PlatformIO.Viewports.Size <= g.Viewports.Size);
+
+ // Update Minimized status (we need it first in order to decide if we'll apply Pos/Size of the main viewport)
+ // Update Focused status
+ const bool viewports_enabled = (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable) != 0;
+ if (viewports_enabled)
{
- PasteboardItemID item_id = 0;
- PasteboardGetItemIdentifier(main_clipboard, i + 1, &item_id);
- CFArrayRef flavor_type_array = 0;
- PasteboardCopyItemFlavors(main_clipboard, item_id, &flavor_type_array);
- for (CFIndex j = 0, nj = CFArrayGetCount(flavor_type_array); j < nj; j++)
+ ImGuiViewportP* focused_viewport = NULL;
+ for (ImGuiViewportP* viewport : g.Viewports)
{
- CFDataRef cf_data;
- if (PasteboardCopyItemFlavorData(main_clipboard, item_id, CFSTR("public.utf8-plain-text"), &cf_data) == noErr)
+ const bool platform_funcs_available = viewport->PlatformWindowCreated;
+ if (g.PlatformIO.Platform_GetWindowMinimized && platform_funcs_available)
{
- g.ClipboardHandlerData.clear();
- int length = (int)CFDataGetLength(cf_data);
- g.ClipboardHandlerData.resize(length + 1);
- CFDataGetBytes(cf_data, CFRangeMake(0, length), (UInt8*)g.ClipboardHandlerData.Data);
- g.ClipboardHandlerData[length] = 0;
- CFRelease(cf_data);
- return g.ClipboardHandlerData.Data;
+ bool is_minimized = g.PlatformIO.Platform_GetWindowMinimized(viewport);
+ if (is_minimized)
+ viewport->Flags |= ImGuiViewportFlags_IsMinimized;
+ else
+ viewport->Flags &= ~ImGuiViewportFlags_IsMinimized;
+ }
+
+ // Update our implicit z-order knowledge of platform windows, which is used when the backend cannot provide io.MouseHoveredViewport.
+ // When setting Platform_GetWindowFocus, it is expected that the platform backend can handle calls without crashing if it doesn't have data stored.
+ if (g.PlatformIO.Platform_GetWindowFocus && platform_funcs_available)
+ {
+ bool is_focused = g.PlatformIO.Platform_GetWindowFocus(viewport);
+ if (is_focused)
+ viewport->Flags |= ImGuiViewportFlags_IsFocused;
+ else
+ viewport->Flags &= ~ImGuiViewportFlags_IsFocused;
+ if (is_focused)
+ focused_viewport = viewport;
}
}
- }
- return NULL;
-}
-#else
+ // Focused viewport has changed?
+ if (focused_viewport && g.PlatformLastFocusedViewportId != focused_viewport->ID)
+ {
+ IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Focused viewport changed %08X -> %08X, attempting to apply our focus.\n", g.PlatformLastFocusedViewportId, focused_viewport->ID);
+ const ImGuiViewport* prev_focused_viewport = FindViewportByID(g.PlatformLastFocusedViewportId);
+ const bool prev_focused_has_been_destroyed = (prev_focused_viewport == NULL) || (prev_focused_viewport->PlatformWindowCreated == false);
+
+ // Store a tag so we can infer z-order easily from all our windows
+ // We compare PlatformLastFocusedViewportId so newly created viewports with _NoFocusOnAppearing flag
+ // will keep the front most stamp instead of losing it back to their parent viewport.
+ if (focused_viewport->LastFocusedStampCount != g.ViewportFocusedStampCount)
+ focused_viewport->LastFocusedStampCount = ++g.ViewportFocusedStampCount;
+ g.PlatformLastFocusedViewportId = focused_viewport->ID;
+
+ // Focus associated dear imgui window
+ // - if focus didn't happen with a click within imgui boundaries, e.g. Clicking platform title bar. (#6299)
+ // - if focus didn't happen because we destroyed another window (#6462)
+ // FIXME: perhaps 'FocusTopMostWindowUnderOne()' can handle the 'focused_window->Window != NULL' case as well.
+ const bool apply_imgui_focus_on_focused_viewport = !IsAnyMouseDown() && !prev_focused_has_been_destroyed;
+ if (apply_imgui_focus_on_focused_viewport)
+ {
+ focused_viewport->LastFocusedHadNavWindow |= (g.NavWindow != NULL) && (g.NavWindow->Viewport == focused_viewport); // Update so a window changing viewport won't lose focus.
+ ImGuiFocusRequestFlags focus_request_flags = ImGuiFocusRequestFlags_UnlessBelowModal | ImGuiFocusRequestFlags_RestoreFocusedChild;
+ if (focused_viewport->Window != NULL)
+ FocusWindow(focused_viewport->Window, focus_request_flags);
+ else if (focused_viewport->LastFocusedHadNavWindow)
+ FocusTopMostWindowUnderOne(NULL, NULL, focused_viewport, focus_request_flags); // Focus top most in viewport
+ else
+ FocusWindow(NULL, focus_request_flags); // No window had focus last time viewport was focused
+ }
+ }
+ if (focused_viewport)
+ focused_viewport->LastFocusedHadNavWindow = (g.NavWindow != NULL) && (g.NavWindow->Viewport == focused_viewport);
+ }
-// Local Dear ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers.
-static const char* GetClipboardTextFn_DefaultImpl(void* user_data_ctx)
-{
- ImGuiContext& g = *(ImGuiContext*)user_data_ctx;
- return g.ClipboardHandlerData.empty() ? NULL : g.ClipboardHandlerData.begin();
-}
+ // Create/update main viewport with current platform position.
+ // FIXME-VIEWPORT: Size is driven by backend/user code for backward-compatibility but we should aim to make this more consistent.
+ ImGuiViewportP* main_viewport = g.Viewports[0];
+ IM_ASSERT(main_viewport->ID == IMGUI_VIEWPORT_DEFAULT_ID);
+ IM_ASSERT(main_viewport->Window == NULL);
+ ImVec2 main_viewport_pos = viewports_enabled ? g.PlatformIO.Platform_GetWindowPos(main_viewport) : ImVec2(0.0f, 0.0f);
+ ImVec2 main_viewport_size = g.IO.DisplaySize;
+ if (viewports_enabled && (main_viewport->Flags & ImGuiViewportFlags_IsMinimized))
+ {
+ main_viewport_pos = main_viewport->Pos; // Preserve last pos/size when minimized (FIXME: We don't do the same for Size outside of the viewport path)
+ main_viewport_size = main_viewport->Size;
+ }
+ AddUpdateViewport(NULL, IMGUI_VIEWPORT_DEFAULT_ID, main_viewport_pos, main_viewport_size, ImGuiViewportFlags_OwnedByApp | ImGuiViewportFlags_CanHostOtherWindows);
-static void SetClipboardTextFn_DefaultImpl(void* user_data_ctx, const char* text)
-{
- ImGuiContext& g = *(ImGuiContext*)user_data_ctx;
- g.ClipboardHandlerData.clear();
- const char* text_end = text + strlen(text);
- g.ClipboardHandlerData.resize((int)(text_end - text) + 1);
- memcpy(&g.ClipboardHandlerData[0], text, (size_t)(text_end - text));
- g.ClipboardHandlerData[(int)(text_end - text)] = 0;
-}
+ g.CurrentDpiScale = 0.0f;
+ g.CurrentViewport = NULL;
+ g.MouseViewport = NULL;
+ for (int n = 0; n < g.Viewports.Size; n++)
+ {
+ ImGuiViewportP* viewport = g.Viewports[n];
+ viewport->Idx = n;
-#endif
+ // Erase unused viewports
+ if (n > 0 && viewport->LastFrameActive < g.FrameCount - 2)
+ {
+ DestroyViewport(viewport);
+ n--;
+ continue;
+ }
-// Win32 API IME support (for Asian languages, etc.)
+ const bool platform_funcs_available = viewport->PlatformWindowCreated;
+ if (viewports_enabled)
+ {
+ // Update Position and Size (from Platform Window to ImGui) if requested.
+ // We do it early in the frame instead of waiting for UpdatePlatformWindows() to avoid a frame of lag when moving/resizing using OS facilities.
+ if (!(viewport->Flags & ImGuiViewportFlags_IsMinimized) && platform_funcs_available)
+ {
+ // Viewport->WorkPos and WorkSize will be updated below
+ if (viewport->PlatformRequestMove)
+ viewport->Pos = viewport->LastPlatformPos = g.PlatformIO.Platform_GetWindowPos(viewport);
+ if (viewport->PlatformRequestResize)
+ viewport->Size = viewport->LastPlatformSize = g.PlatformIO.Platform_GetWindowSize(viewport);
+ }
+ }
+
+ // Update/copy monitor info
+ UpdateViewportPlatformMonitor(viewport);
+
+ // Lock down space taken by menu bars and status bars, reset the offset for functions like BeginMainMenuBar() to alter them again.
+ viewport->WorkOffsetMin = viewport->BuildWorkOffsetMin;
+ viewport->WorkOffsetMax = viewport->BuildWorkOffsetMax;
+ viewport->BuildWorkOffsetMin = viewport->BuildWorkOffsetMax = ImVec2(0.0f, 0.0f);
+ viewport->UpdateWorkRect();
+
+ // Reset alpha every frame. Users of transparency (docking) needs to request a lower alpha back.
+ viewport->Alpha = 1.0f;
+
+ // Translate Dear ImGui windows when a Host Viewport has been moved
+ // (This additionally keeps windows at the same place when ImGuiConfigFlags_ViewportsEnable is toggled!)
+ const ImVec2 viewport_delta_pos = viewport->Pos - viewport->LastPos;
+ if ((viewport->Flags & ImGuiViewportFlags_CanHostOtherWindows) && (viewport_delta_pos.x != 0.0f || viewport_delta_pos.y != 0.0f))
+ TranslateWindowsInViewport(viewport, viewport->LastPos, viewport->Pos);
+
+ // Update DPI scale
+ float new_dpi_scale;
+ if (g.PlatformIO.Platform_GetWindowDpiScale && platform_funcs_available)
+ new_dpi_scale = g.PlatformIO.Platform_GetWindowDpiScale(viewport);
+ else if (viewport->PlatformMonitor != -1)
+ new_dpi_scale = g.PlatformIO.Monitors[viewport->PlatformMonitor].DpiScale;
+ else
+ new_dpi_scale = (viewport->DpiScale != 0.0f) ? viewport->DpiScale : 1.0f;
+ if (viewport->DpiScale != 0.0f && new_dpi_scale != viewport->DpiScale)
+ {
+ float scale_factor = new_dpi_scale / viewport->DpiScale;
+ if (g.IO.ConfigFlags & ImGuiConfigFlags_DpiEnableScaleViewports)
+ ScaleWindowsInViewport(viewport, scale_factor);
+ //if (viewport == GetMainViewport())
+ // g.PlatformInterface.SetWindowSize(viewport, viewport->Size * scale_factor);
+
+ // Scale our window moving pivot so that the window will rescale roughly around the mouse position.
+ // FIXME-VIEWPORT: This currently creates a resizing feedback loop when a window is straddling a DPI transition border.
+ // (Minor: since our sizes do not perfectly linearly scale, deferring the click offset scale until we know the actual window scale ratio may get us slightly more precise mouse positioning.)
+ //if (g.MovingWindow != NULL && g.MovingWindow->Viewport == viewport)
+ // g.ActiveIdClickOffset = ImTrunc(g.ActiveIdClickOffset * scale_factor);
+ }
+ viewport->DpiScale = new_dpi_scale;
+ }
+
+ // Update fallback monitor
+ g.PlatformMonitorsFullWorkRect = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX);
+ if (g.PlatformIO.Monitors.Size == 0)
+ {
+ ImGuiPlatformMonitor* monitor = &g.FallbackMonitor;
+ monitor->MainPos = main_viewport->Pos;
+ monitor->MainSize = main_viewport->Size;
+ monitor->WorkPos = main_viewport->WorkPos;
+ monitor->WorkSize = main_viewport->WorkSize;
+ monitor->DpiScale = main_viewport->DpiScale;
+ g.PlatformMonitorsFullWorkRect.Add(monitor->WorkPos);
+ g.PlatformMonitorsFullWorkRect.Add(monitor->WorkPos + monitor->WorkSize);
+ }
+ for (ImGuiPlatformMonitor& monitor : g.PlatformIO.Monitors)
+ {
+ g.PlatformMonitorsFullWorkRect.Add(monitor.WorkPos);
+ g.PlatformMonitorsFullWorkRect.Add(monitor.WorkPos + monitor.WorkSize);
+ }
+
+ if (!viewports_enabled)
+ {
+ g.MouseViewport = main_viewport;
+ return;
+ }
+
+ // Mouse handling: decide on the actual mouse viewport for this frame between the active/focused viewport and the hovered viewport.
+ // Note that 'viewport_hovered' should skip over any viewport that has the ImGuiViewportFlags_NoInputs flags set.
+ ImGuiViewportP* viewport_hovered = NULL;
+ if (g.IO.BackendFlags & ImGuiBackendFlags_HasMouseHoveredViewport)
+ {
+ viewport_hovered = g.IO.MouseHoveredViewport ? (ImGuiViewportP*)FindViewportByID(g.IO.MouseHoveredViewport) : NULL;
+ if (viewport_hovered && (viewport_hovered->Flags & ImGuiViewportFlags_NoInputs))
+ viewport_hovered = FindHoveredViewportFromPlatformWindowStack(g.IO.MousePos); // Backend failed to handle _NoInputs viewport: revert to our fallback.
+ }
+ else
+ {
+ // If the backend doesn't know how to honor ImGuiViewportFlags_NoInputs, we do a search ourselves. Note that this search:
+ // A) won't take account of the possibility that non-imgui windows may be in-between our dragged window and our target window.
+ // B) won't take account of how the backend apply parent<>child relationship to secondary viewports, which affects their Z order.
+ // C) uses LastFrameAsRefViewport as a flawed replacement for the last time a window was focused (we could/should fix that by introducing Focus functions in PlatformIO)
+ viewport_hovered = FindHoveredViewportFromPlatformWindowStack(g.IO.MousePos);
+ }
+ if (viewport_hovered != NULL)
+ g.MouseLastHoveredViewport = viewport_hovered;
+ else if (g.MouseLastHoveredViewport == NULL)
+ g.MouseLastHoveredViewport = g.Viewports[0];
+
+ // Update mouse reference viewport
+ // (when moving a window we aim at its viewport, but this will be overwritten below if we go in drag and drop mode)
+ // (MovingViewport->Viewport will be NULL in the rare situation where the window disappared while moving, set UpdateMouseMovingWindowNewFrame() for details)
+ if (g.MovingWindow && g.MovingWindow->Viewport)
+ g.MouseViewport = g.MovingWindow->Viewport;
+ else
+ g.MouseViewport = g.MouseLastHoveredViewport;
+
+ // When dragging something, always refer to the last hovered viewport.
+ // - when releasing a moving window we will revert to aiming behind (at viewport_hovered)
+ // - when we are between viewports, our dragged preview will tend to show in the last viewport _even_ if we don't have tooltips in their viewports (when lacking monitor info)
+ // - consider the case of holding on a menu item to browse child menus: even thou a mouse button is held, there's no active id because menu items only react on mouse release.
+ // FIXME-VIEWPORT: This is essentially broken, when ImGuiBackendFlags_HasMouseHoveredViewport is set we want to trust when viewport_hovered==NULL and use that.
+ const bool is_mouse_dragging_with_an_expected_destination = g.DragDropActive;
+ if (is_mouse_dragging_with_an_expected_destination && viewport_hovered == NULL)
+ viewport_hovered = g.MouseLastHoveredViewport;
+ if (is_mouse_dragging_with_an_expected_destination || g.ActiveId == 0 || !IsAnyMouseDown())
+ if (viewport_hovered != NULL && viewport_hovered != g.MouseViewport && !(viewport_hovered->Flags & ImGuiViewportFlags_NoInputs))
+ g.MouseViewport = viewport_hovered;
+
+ IM_ASSERT(g.MouseViewport != NULL);
+}
+
+// Update user-facing viewport list (g.Viewports -> g.PlatformIO.Viewports after filtering out some)
+static void ImGui::UpdateViewportsEndFrame()
+{
+ ImGuiContext& g = *GImGui;
+ g.PlatformIO.Viewports.resize(0);
+ for (int i = 0; i < g.Viewports.Size; i++)
+ {
+ ImGuiViewportP* viewport = g.Viewports[i];
+ viewport->LastPos = viewport->Pos;
+ if (viewport->LastFrameActive < g.FrameCount || viewport->Size.x <= 0.0f || viewport->Size.y <= 0.0f)
+ if (i > 0) // Always include main viewport in the list
+ continue;
+ if (viewport->Window && !IsWindowActiveAndVisible(viewport->Window))
+ continue;
+ if (i > 0)
+ IM_ASSERT(viewport->Window != NULL);
+ g.PlatformIO.Viewports.push_back(viewport);
+ }
+ g.Viewports[0]->ClearRequestFlags(); // Clear main viewport flags because UpdatePlatformWindows() won't do it and may not even be called
+}
+
+// FIXME: We should ideally refactor the system to call this every frame (we currently don't)
+ImGuiViewportP* ImGui::AddUpdateViewport(ImGuiWindow* window, ImGuiID id, const ImVec2& pos, const ImVec2& size, ImGuiViewportFlags flags)
+{
+ ImGuiContext& g = *GImGui;
+ IM_ASSERT(id != 0);
+
+ flags |= ImGuiViewportFlags_IsPlatformWindow;
+ if (window != NULL)
+ {
+ if (g.MovingWindow && g.MovingWindow->RootWindowDockTree == window)
+ flags |= ImGuiViewportFlags_NoInputs | ImGuiViewportFlags_NoFocusOnAppearing;
+ if ((window->Flags & ImGuiWindowFlags_NoMouseInputs) && (window->Flags & ImGuiWindowFlags_NoNavInputs))
+ flags |= ImGuiViewportFlags_NoInputs;
+ if (window->Flags & ImGuiWindowFlags_NoFocusOnAppearing)
+ flags |= ImGuiViewportFlags_NoFocusOnAppearing;
+ }
+
+ ImGuiViewportP* viewport = (ImGuiViewportP*)FindViewportByID(id);
+ if (viewport)
+ {
+ // Always update for main viewport as we are already pulling correct platform pos/size (see #4900)
+ if (!viewport->PlatformRequestMove || viewport->ID == IMGUI_VIEWPORT_DEFAULT_ID)
+ viewport->Pos = pos;
+ if (!viewport->PlatformRequestResize || viewport->ID == IMGUI_VIEWPORT_DEFAULT_ID)
+ viewport->Size = size;
+ viewport->Flags = flags | (viewport->Flags & (ImGuiViewportFlags_IsMinimized | ImGuiViewportFlags_IsFocused)); // Preserve existing flags
+ }
+ else
+ {
+ // New viewport
+ viewport = IM_NEW(ImGuiViewportP)();
+ viewport->ID = id;
+ viewport->Idx = g.Viewports.Size;
+ viewport->Pos = viewport->LastPos = pos;
+ viewport->Size = size;
+ viewport->Flags = flags;
+ UpdateViewportPlatformMonitor(viewport);
+ g.Viewports.push_back(viewport);
+ g.ViewportCreatedCount++;
+ IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Add Viewport %08X '%s'\n", id, window ? window->Name : "<NULL>");
+
+ // We normally setup for all viewports in NewFrame() but here need to handle the mid-frame creation of a new viewport.
+ // We need to extend the fullscreen clip rect so the OverlayDrawList clip is correct for that the first frame
+ g.DrawListSharedData.ClipRectFullscreen.x = ImMin(g.DrawListSharedData.ClipRectFullscreen.x, viewport->Pos.x);
+ g.DrawListSharedData.ClipRectFullscreen.y = ImMin(g.DrawListSharedData.ClipRectFullscreen.y, viewport->Pos.y);
+ g.DrawListSharedData.ClipRectFullscreen.z = ImMax(g.DrawListSharedData.ClipRectFullscreen.z, viewport->Pos.x + viewport->Size.x);
+ g.DrawListSharedData.ClipRectFullscreen.w = ImMax(g.DrawListSharedData.ClipRectFullscreen.w, viewport->Pos.y + viewport->Size.y);
+
+ // Store initial DpiScale before the OS platform window creation, based on expected monitor data.
+ // This is so we can select an appropriate font size on the first frame of our window lifetime
+ if (viewport->PlatformMonitor != -1)
+ viewport->DpiScale = g.PlatformIO.Monitors[viewport->PlatformMonitor].DpiScale;
+ }
+
+ viewport->Window = window;
+ viewport->LastFrameActive = g.FrameCount;
+ viewport->UpdateWorkRect();
+ IM_ASSERT(window == NULL || viewport->ID == window->ID);
+
+ if (window != NULL)
+ window->ViewportOwned = true;
+
+ return viewport;
+}
+
+static void ImGui::DestroyViewport(ImGuiViewportP* viewport)
+{
+ // Clear references to this viewport in windows (window->ViewportId becomes the master data)
+ ImGuiContext& g = *GImGui;
+ for (ImGuiWindow* window : g.Windows)
+ {
+ if (window->Viewport != viewport)
+ continue;
+ window->Viewport = NULL;
+ window->ViewportOwned = false;
+ }
+ if (viewport == g.MouseLastHoveredViewport)
+ g.MouseLastHoveredViewport = NULL;
+
+ // Destroy
+ IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Delete Viewport %08X '%s'\n", viewport->ID, viewport->Window ? viewport->Window->Name : "n/a");
+ DestroyPlatformWindow(viewport); // In most circumstances the platform window will already be destroyed here.
+ IM_ASSERT(g.PlatformIO.Viewports.contains(viewport) == false);
+ IM_ASSERT(g.Viewports[viewport->Idx] == viewport);
+ g.Viewports.erase(g.Viewports.Data + viewport->Idx);
+ IM_DELETE(viewport);
+}
+
+// FIXME-VIEWPORT: This is all super messy and ought to be clarified or rewritten.
+static void ImGui::WindowSelectViewport(ImGuiWindow* window)
+{
+ ImGuiContext& g = *GImGui;
+ ImGuiWindowFlags flags = window->Flags;
+ window->ViewportAllowPlatformMonitorExtend = -1;
+
+ // Restore main viewport if multi-viewport is not supported by the backend
+ ImGuiViewportP* main_viewport = (ImGuiViewportP*)(void*)GetMainViewport();
+ if (!(g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable))
+ {
+ SetWindowViewport(window, main_viewport);
+ return;
+ }
+ window->ViewportOwned = false;
+
+ // Appearing popups reset their viewport so they can inherit again
+ if ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) && window->Appearing)
+ {
+ window->Viewport = NULL;
+ window->ViewportId = 0;
+ }
+
+ if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasViewport) == 0)
+ {
+ // By default inherit from parent window
+ if (window->Viewport == NULL && window->ParentWindow && (!window->ParentWindow->IsFallbackWindow || window->ParentWindow->WasActive))
+ window->Viewport = window->ParentWindow->Viewport;
+
+ // Attempt to restore saved viewport id (= window that hasn't been activated yet), try to restore the viewport based on saved 'window->ViewportPos' restored from .ini file
+ if (window->Viewport == NULL && window->ViewportId != 0)
+ {
+ window->Viewport = (ImGuiViewportP*)FindViewportByID(window->ViewportId);
+ if (window->Viewport == NULL && window->ViewportPos.x != FLT_MAX && window->ViewportPos.y != FLT_MAX)
+ window->Viewport = AddUpdateViewport(window, window->ID, window->ViewportPos, window->Size, ImGuiViewportFlags_None);
+ }
+ }
+
+ bool lock_viewport = false;
+ if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasViewport)
+ {
+ // Code explicitly request a viewport
+ window->Viewport = (ImGuiViewportP*)FindViewportByID(g.NextWindowData.ViewportId);
+ window->ViewportId = g.NextWindowData.ViewportId; // Store ID even if Viewport isn't resolved yet.
+ if (window->Viewport && (window->Flags & ImGuiWindowFlags_DockNodeHost) != 0 && window->Viewport->Window != NULL)
+ {
+ window->Viewport->Window = window;
+ window->Viewport->ID = window->ViewportId = window->ID; // Overwrite ID (always owned by node)
+ }
+ lock_viewport = true;
+ }
+ else if ((flags & ImGuiWindowFlags_ChildWindow) || (flags & ImGuiWindowFlags_ChildMenu))
+ {
+ // Always inherit viewport from parent window
+ if (window->DockNode && window->DockNode->HostWindow)
+ IM_ASSERT(window->DockNode->HostWindow->Viewport == window->ParentWindow->Viewport);
+ window->Viewport = window->ParentWindow->Viewport;
+ }
+ else if (window->DockNode && window->DockNode->HostWindow)
+ {
+ // This covers the "always inherit viewport from parent window" case for when a window reattach to a node that was just created mid-frame
+ window->Viewport = window->DockNode->HostWindow->Viewport;
+ }
+ else if (flags & ImGuiWindowFlags_Tooltip)
+ {
+ window->Viewport = g.MouseViewport;
+ }
+ else if (GetWindowAlwaysWantOwnViewport(window))
+ {
+ window->Viewport = AddUpdateViewport(window, window->ID, window->Pos, window->Size, ImGuiViewportFlags_None);
+ }
+ else if (g.MovingWindow && g.MovingWindow->RootWindowDockTree == window && IsMousePosValid())
+ {
+ if (window->Viewport != NULL && window->Viewport->Window == window)
+ window->Viewport = AddUpdateViewport(window, window->ID, window->Pos, window->Size, ImGuiViewportFlags_None);
+ }
+ else
+ {
+ // Merge into host viewport?
+ // We cannot test window->ViewportOwned as it set lower in the function.
+ // Testing (g.ActiveId == 0 || g.ActiveIdAllowOverlap) to avoid merging during a short-term widget interaction. Main intent was to avoid during resize (see #4212)
+ bool try_to_merge_into_host_viewport = (window->Viewport && window == window->Viewport->Window && (g.ActiveId == 0 || g.ActiveIdAllowOverlap));
+ if (try_to_merge_into_host_viewport)
+ UpdateTryMergeWindowIntoHostViewports(window);
+ }
+
+ // Fallback: merge in default viewport if z-order matches, otherwise create a new viewport
+ if (window->Viewport == NULL)
+ if (!UpdateTryMergeWindowIntoHostViewport(window, main_viewport))
+ window->Viewport = AddUpdateViewport(window, window->ID, window->Pos, window->Size, ImGuiViewportFlags_None);
+
+ // Mark window as allowed to protrude outside of its viewport and into the current monitor
+ if (!lock_viewport)
+ {
+ if (flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup))
+ {
+ // We need to take account of the possibility that mouse may become invalid.
+ // Popups/Tooltip always set ViewportAllowPlatformMonitorExtend so GetWindowAllowedExtentRect() will return full monitor bounds.
+ ImVec2 mouse_ref = (flags & ImGuiWindowFlags_Tooltip) ? g.IO.MousePos : g.BeginPopupStack.back().OpenMousePos;
+ bool use_mouse_ref = (g.NavDisableHighlight || !g.NavDisableMouseHover || !g.NavWindow);
+ bool mouse_valid = IsMousePosValid(&mouse_ref);
+ if ((window->Appearing || (flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_ChildMenu))) && (!use_mouse_ref || mouse_valid))
+ window->ViewportAllowPlatformMonitorExtend = FindPlatformMonitorForPos((use_mouse_ref && mouse_valid) ? mouse_ref : NavCalcPreferredRefPos());
+ else
+ window->ViewportAllowPlatformMonitorExtend = window->Viewport->PlatformMonitor;
+ }
+ else if (window->Viewport && window != window->Viewport->Window && window->Viewport->Window && !(flags & ImGuiWindowFlags_ChildWindow) && window->DockNode == NULL)
+ {
+ // When called from Begin() we don't have access to a proper version of the Hidden flag yet, so we replicate this code.
+ const bool will_be_visible = (window->DockIsActive && !window->DockTabIsVisible) ? false : true;
+ if ((window->Flags & ImGuiWindowFlags_DockNodeHost) && window->Viewport->LastFrameActive < g.FrameCount && will_be_visible)
+ {
+ // Steal/transfer ownership
+ IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Window '%s' steal Viewport %08X from Window '%s'\n", window->Name, window->Viewport->ID, window->Viewport->Window->Name);
+ window->Viewport->Window = window;
+ window->Viewport->ID = window->ID;
+ window->Viewport->LastNameHash = 0;
+ }
+ else if (!UpdateTryMergeWindowIntoHostViewports(window)) // Merge?
+ {
+ // New viewport
+ window->Viewport = AddUpdateViewport(window, window->ID, window->Pos, window->Size, ImGuiViewportFlags_NoFocusOnAppearing);
+ }
+ }
+ else if (window->ViewportAllowPlatformMonitorExtend < 0 && (flags & ImGuiWindowFlags_ChildWindow) == 0)
+ {
+ // Regular (non-child, non-popup) windows by default are also allowed to protrude
+ // Child windows are kept contained within their parent.
+ window->ViewportAllowPlatformMonitorExtend = window->Viewport->PlatformMonitor;
+ }
+ }
+
+ // Update flags
+ window->ViewportOwned = (window == window->Viewport->Window);
+ window->ViewportId = window->Viewport->ID;
+
+ // If the OS window has a title bar, hide our imgui title bar
+ //if (window->ViewportOwned && !(window->Viewport->Flags & ImGuiViewportFlags_NoDecoration))
+ // window->Flags |= ImGuiWindowFlags_NoTitleBar;
+}
+
+void ImGui::WindowSyncOwnedViewport(ImGuiWindow* window, ImGuiWindow* parent_window_in_stack)
+{
+ ImGuiContext& g = *GImGui;
+
+ bool viewport_rect_changed = false;
+
+ // Synchronize window --> viewport in most situations
+ // Synchronize viewport -> window in case the platform window has been moved or resized from the OS/WM
+ if (window->Viewport->PlatformRequestMove)
+ {
+ window->Pos = window->Viewport->Pos;
+ MarkIniSettingsDirty(window);
+ }
+ else if (memcmp(&window->Viewport->Pos, &window->Pos, sizeof(window->Pos)) != 0)
+ {
+ viewport_rect_changed = true;
+ window->Viewport->Pos = window->Pos;
+ }
+
+ if (window->Viewport->PlatformRequestResize)
+ {
+ window->Size = window->SizeFull = window->Viewport->Size;
+ MarkIniSettingsDirty(window);
+ }
+ else if (memcmp(&window->Viewport->Size, &window->Size, sizeof(window->Size)) != 0)
+ {
+ viewport_rect_changed = true;
+ window->Viewport->Size = window->Size;
+ }
+ window->Viewport->UpdateWorkRect();
+
+ // The viewport may have changed monitor since the global update in UpdateViewportsNewFrame()
+ // Either a SetNextWindowPos() call in the current frame or a SetWindowPos() call in the previous frame may have this effect.
+ if (viewport_rect_changed)
+ UpdateViewportPlatformMonitor(window->Viewport);
+
+ // Update common viewport flags
+ const ImGuiViewportFlags viewport_flags_to_clear = ImGuiViewportFlags_TopMost | ImGuiViewportFlags_NoTaskBarIcon | ImGuiViewportFlags_NoDecoration | ImGuiViewportFlags_NoRendererClear;
+ ImGuiViewportFlags viewport_flags = window->Viewport->Flags & ~viewport_flags_to_clear;
+ ImGuiWindowFlags window_flags = window->Flags;
+ const bool is_modal = (window_flags & ImGuiWindowFlags_Modal) != 0;
+ const bool is_short_lived_floating_window = (window_flags & (ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup)) != 0;
+ if (window_flags & ImGuiWindowFlags_Tooltip)
+ viewport_flags |= ImGuiViewportFlags_TopMost;
+ if ((g.IO.ConfigViewportsNoTaskBarIcon || is_short_lived_floating_window) && !is_modal)
+ viewport_flags |= ImGuiViewportFlags_NoTaskBarIcon;
+ if (g.IO.ConfigViewportsNoDecoration || is_short_lived_floating_window)
+ viewport_flags |= ImGuiViewportFlags_NoDecoration;
+
+ // Not correct to set modal as topmost because:
+ // - Because other popups can be stacked above a modal (e.g. combo box in a modal)
+ // - ImGuiViewportFlags_TopMost is currently handled different in backends: in Win32 it is "appear top most" whereas in GLFW and SDL it is "stay topmost"
+ //if (flags & ImGuiWindowFlags_Modal)
+ // viewport_flags |= ImGuiViewportFlags_TopMost;
+
+ // For popups and menus that may be protruding out of their parent viewport, we enable _NoFocusOnClick so that clicking on them
+ // won't steal the OS focus away from their parent window (which may be reflected in OS the title bar decoration).
+ // Setting _NoFocusOnClick would technically prevent us from bringing back to front in case they are being covered by an OS window from a different app,
+ // but it shouldn't be much of a problem considering those are already popups that are closed when clicking elsewhere.
+ if (is_short_lived_floating_window && !is_modal)
+ viewport_flags |= ImGuiViewportFlags_NoFocusOnAppearing | ImGuiViewportFlags_NoFocusOnClick;
+
+ // We can overwrite viewport flags using ImGuiWindowClass (advanced users)
+ if (window->WindowClass.ViewportFlagsOverrideSet)
+ viewport_flags |= window->WindowClass.ViewportFlagsOverrideSet;
+ if (window->WindowClass.ViewportFlagsOverrideClear)
+ viewport_flags &= ~window->WindowClass.ViewportFlagsOverrideClear;
+
+ // We can also tell the backend that clearing the platform window won't be necessary,
+ // as our window background is filling the viewport and we have disabled BgAlpha.
+ // FIXME: Work on support for per-viewport transparency (#2766)
+ if (!(window_flags & ImGuiWindowFlags_NoBackground))
+ viewport_flags |= ImGuiViewportFlags_NoRendererClear;
+
+ window->Viewport->Flags = viewport_flags;
+
+ // Update parent viewport ID
+ // (the !IsFallbackWindow test mimic the one done in WindowSelectViewport())
+ if (window->WindowClass.ParentViewportId != (ImGuiID)-1)
+ window->Viewport->ParentViewportId = window->WindowClass.ParentViewportId;
+ else if ((window_flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) && parent_window_in_stack && (!parent_window_in_stack->IsFallbackWindow || parent_window_in_stack->WasActive))
+ window->Viewport->ParentViewportId = parent_window_in_stack->Viewport->ID;
+ else
+ window->Viewport->ParentViewportId = g.IO.ConfigViewportsNoDefaultParent ? 0 : IMGUI_VIEWPORT_DEFAULT_ID;
+}
+
+// Called by user at the end of the main loop, after EndFrame()
+// This will handle the creation/update of all OS windows via function defined in the ImGuiPlatformIO api.
+void ImGui::UpdatePlatformWindows()
+{
+ ImGuiContext& g = *GImGui;
+ IM_ASSERT(g.FrameCountEnded == g.FrameCount && "Forgot to call Render() or EndFrame() before UpdatePlatformWindows()?");
+ IM_ASSERT(g.FrameCountPlatformEnded < g.FrameCount);
+ g.FrameCountPlatformEnded = g.FrameCount;
+ if (!(g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable))
+ return;
+
+ // Create/resize/destroy platform windows to match each active viewport.
+ // Skip the main viewport (index 0), which is always fully handled by the application!
+ for (int i = 1; i < g.Viewports.Size; i++)
+ {
+ ImGuiViewportP* viewport = g.Viewports[i];
+
+ // Destroy platform window if the viewport hasn't been submitted or if it is hosting a hidden window
+ // (the implicit/fallback Debug##Default window will be registering its viewport then be disabled, causing a dummy DestroyPlatformWindow to be made each frame)
+ bool destroy_platform_window = false;
+ destroy_platform_window |= (viewport->LastFrameActive < g.FrameCount - 1);
+ destroy_platform_window |= (viewport->Window && !IsWindowActiveAndVisible(viewport->Window));
+ if (destroy_platform_window)
+ {
+ DestroyPlatformWindow(viewport);
+ continue;
+ }
+
+ // New windows that appears directly in a new viewport won't always have a size on their first frame
+ if (viewport->LastFrameActive < g.FrameCount || viewport->Size.x <= 0 || viewport->Size.y <= 0)
+ continue;
+
+ // Create window
+ const bool is_new_platform_window = (viewport->PlatformWindowCreated == false);
+ if (is_new_platform_window)
+ {
+ IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Create Platform Window %08X '%s'\n", viewport->ID, viewport->Window ? viewport->Window->Name : "n/a");
+ g.PlatformIO.Platform_CreateWindow(viewport);
+ if (g.PlatformIO.Renderer_CreateWindow != NULL)
+ g.PlatformIO.Renderer_CreateWindow(viewport);
+ g.PlatformWindowsCreatedCount++;
+ viewport->LastNameHash = 0;
+ viewport->LastPlatformPos = viewport->LastPlatformSize = ImVec2(FLT_MAX, FLT_MAX); // By clearing those we'll enforce a call to Platform_SetWindowPos/Size below, before Platform_ShowWindow (FIXME: Is that necessary?)
+ viewport->LastRendererSize = viewport->Size; // We don't need to call Renderer_SetWindowSize() as it is expected Renderer_CreateWindow() already did it.
+ viewport->PlatformWindowCreated = true;
+ }
+
+ // Apply Position and Size (from ImGui to Platform/Renderer backends)
+ if ((viewport->LastPlatformPos.x != viewport->Pos.x || viewport->LastPlatformPos.y != viewport->Pos.y) && !viewport->PlatformRequestMove)
+ g.PlatformIO.Platform_SetWindowPos(viewport, viewport->Pos);
+ if ((viewport->LastPlatformSize.x != viewport->Size.x || viewport->LastPlatformSize.y != viewport->Size.y) && !viewport->PlatformRequestResize)
+ g.PlatformIO.Platform_SetWindowSize(viewport, viewport->Size);
+ if ((viewport->LastRendererSize.x != viewport->Size.x || viewport->LastRendererSize.y != viewport->Size.y) && g.PlatformIO.Renderer_SetWindowSize)
+ g.PlatformIO.Renderer_SetWindowSize(viewport, viewport->Size);
+ viewport->LastPlatformPos = viewport->Pos;
+ viewport->LastPlatformSize = viewport->LastRendererSize = viewport->Size;
+
+ // Update title bar (if it changed)
+ if (ImGuiWindow* window_for_title = GetWindowForTitleDisplay(viewport->Window))
+ {
+ const char* title_begin = window_for_title->Name;
+ char* title_end = (char*)(intptr_t)FindRenderedTextEnd(title_begin);
+ const ImGuiID title_hash = ImHashStr(title_begin, title_end - title_begin);
+ if (viewport->LastNameHash != title_hash)
+ {
+ char title_end_backup_c = *title_end;
+ *title_end = 0; // Cut existing buffer short instead of doing an alloc/free, no small gain.
+ g.PlatformIO.Platform_SetWindowTitle(viewport, title_begin);
+ *title_end = title_end_backup_c;
+ viewport->LastNameHash = title_hash;
+ }
+ }
+
+ // Update alpha (if it changed)
+ if (viewport->LastAlpha != viewport->Alpha && g.PlatformIO.Platform_SetWindowAlpha)
+ g.PlatformIO.Platform_SetWindowAlpha(viewport, viewport->Alpha);
+ viewport->LastAlpha = viewport->Alpha;
+
+ // Optional, general purpose call to allow the backend to perform general book-keeping even if things haven't changed.
+ if (g.PlatformIO.Platform_UpdateWindow)
+ g.PlatformIO.Platform_UpdateWindow(viewport);
+
+ if (is_new_platform_window)
+ {
+ // On startup ensure new platform window don't steal focus (give it a few frames, as nested contents may lead to viewport being created a few frames late)
+ if (g.FrameCount < 3)
+ viewport->Flags |= ImGuiViewportFlags_NoFocusOnAppearing;
+
+ // Show window
+ g.PlatformIO.Platform_ShowWindow(viewport);
+
+ // Even without focus, we assume the window becomes front-most.
+ // This is useful for our platform z-order heuristic when io.MouseHoveredViewport is not available.
+ if (viewport->LastFocusedStampCount != g.ViewportFocusedStampCount)
+ viewport->LastFocusedStampCount = ++g.ViewportFocusedStampCount;
+ }
+
+ // Clear request flags
+ viewport->ClearRequestFlags();
+ }
+}
+
+// This is a default/basic function for performing the rendering/swap of multiple Platform Windows.
+// Custom renderers may prefer to not call this function at all, and instead iterate the publicly exposed platform data and handle rendering/sync themselves.
+// The Render/Swap functions stored in ImGuiPlatformIO are merely here to allow for this helper to exist, but you can do it yourself:
+//
+// ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
+// for (int i = 1; i < platform_io.Viewports.Size; i++)
+// if ((platform_io.Viewports[i]->Flags & ImGuiViewportFlags_Minimized) == 0)
+// MyRenderFunction(platform_io.Viewports[i], my_args);
+// for (int i = 1; i < platform_io.Viewports.Size; i++)
+// if ((platform_io.Viewports[i]->Flags & ImGuiViewportFlags_Minimized) == 0)
+// MySwapBufferFunction(platform_io.Viewports[i], my_args);
+//
+void ImGui::RenderPlatformWindowsDefault(void* platform_render_arg, void* renderer_render_arg)
+{
+ // Skip the main viewport (index 0), which is always fully handled by the application!
+ ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
+ for (int i = 1; i < platform_io.Viewports.Size; i++)
+ {
+ ImGuiViewport* viewport = platform_io.Viewports[i];
+ if (viewport->Flags & ImGuiViewportFlags_IsMinimized)
+ continue;
+ if (platform_io.Platform_RenderWindow) platform_io.Platform_RenderWindow(viewport, platform_render_arg);
+ if (platform_io.Renderer_RenderWindow) platform_io.Renderer_RenderWindow(viewport, renderer_render_arg);
+ }
+ for (int i = 1; i < platform_io.Viewports.Size; i++)
+ {
+ ImGuiViewport* viewport = platform_io.Viewports[i];
+ if (viewport->Flags & ImGuiViewportFlags_IsMinimized)
+ continue;
+ if (platform_io.Platform_SwapBuffers) platform_io.Platform_SwapBuffers(viewport, platform_render_arg);
+ if (platform_io.Renderer_SwapBuffers) platform_io.Renderer_SwapBuffers(viewport, renderer_render_arg);
+ }
+}
+
+static int ImGui::FindPlatformMonitorForPos(const ImVec2& pos)
+{
+ ImGuiContext& g = *GImGui;
+ for (int monitor_n = 0; monitor_n < g.PlatformIO.Monitors.Size; monitor_n++)
+ {
+ const ImGuiPlatformMonitor& monitor = g.PlatformIO.Monitors[monitor_n];
+ if (ImRect(monitor.MainPos, monitor.MainPos + monitor.MainSize).Contains(pos))
+ return monitor_n;
+ }
+ return -1;
+}
+
+// Search for the monitor with the largest intersection area with the given rectangle
+// We generally try to avoid searching loops but the monitor count should be very small here
+// FIXME-OPT: We could test the last monitor used for that viewport first, and early
+static int ImGui::FindPlatformMonitorForRect(const ImRect& rect)
+{
+ ImGuiContext& g = *GImGui;
+
+ const int monitor_count = g.PlatformIO.Monitors.Size;
+ if (monitor_count <= 1)
+ return monitor_count - 1;
+
+ // Use a minimum threshold of 1.0f so a zero-sized rect won't false positive, and will still find the correct monitor given its position.
+ // This is necessary for tooltips which always resize down to zero at first.
+ const float surface_threshold = ImMax(rect.GetWidth() * rect.GetHeight() * 0.5f, 1.0f);
+ int best_monitor_n = -1;
+ float best_monitor_surface = 0.001f;
+
+ for (int monitor_n = 0; monitor_n < g.PlatformIO.Monitors.Size && best_monitor_surface < surface_threshold; monitor_n++)
+ {
+ const ImGuiPlatformMonitor& monitor = g.PlatformIO.Monitors[monitor_n];
+ const ImRect monitor_rect = ImRect(monitor.MainPos, monitor.MainPos + monitor.MainSize);
+ if (monitor_rect.Contains(rect))
+ return monitor_n;
+ ImRect overlapping_rect = rect;
+ overlapping_rect.ClipWithFull(monitor_rect);
+ float overlapping_surface = overlapping_rect.GetWidth() * overlapping_rect.GetHeight();
+ if (overlapping_surface < best_monitor_surface)
+ continue;
+ best_monitor_surface = overlapping_surface;
+ best_monitor_n = monitor_n;
+ }
+ return best_monitor_n;
+}
+
+// Update monitor from viewport rectangle (we'll use this info to clamp windows and save windows lost in a removed monitor)
+static void ImGui::UpdateViewportPlatformMonitor(ImGuiViewportP* viewport)
+{
+ viewport->PlatformMonitor = (short)FindPlatformMonitorForRect(viewport->GetMainRect());
+}
+
+// Return value is always != NULL, but don't hold on it across frames.
+const ImGuiPlatformMonitor* ImGui::GetViewportPlatformMonitor(ImGuiViewport* viewport_p)
+{
+ ImGuiContext& g = *GImGui;
+ ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)viewport_p;
+ int monitor_idx = viewport->PlatformMonitor;
+ if (monitor_idx >= 0 && monitor_idx < g.PlatformIO.Monitors.Size)
+ return &g.PlatformIO.Monitors[monitor_idx];
+ return &g.FallbackMonitor;
+}
+
+void ImGui::DestroyPlatformWindow(ImGuiViewportP* viewport)
+{
+ ImGuiContext& g = *GImGui;
+ if (viewport->PlatformWindowCreated)
+ {
+ IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Destroy Platform Window %08X '%s'\n", viewport->ID, viewport->Window ? viewport->Window->Name : "n/a");
+ if (g.PlatformIO.Renderer_DestroyWindow)
+ g.PlatformIO.Renderer_DestroyWindow(viewport);
+ if (g.PlatformIO.Platform_DestroyWindow)
+ g.PlatformIO.Platform_DestroyWindow(viewport);
+ IM_ASSERT(viewport->RendererUserData == NULL && viewport->PlatformUserData == NULL);
+
+ // Don't clear PlatformWindowCreated for the main viewport, as we initially set that up to true in Initialize()
+ // The righter way may be to leave it to the backend to set this flag all-together, and made the flag public.
+ if (viewport->ID != IMGUI_VIEWPORT_DEFAULT_ID)
+ viewport->PlatformWindowCreated = false;
+ }
+ else
+ {
+ IM_ASSERT(viewport->RendererUserData == NULL && viewport->PlatformUserData == NULL && viewport->PlatformHandle == NULL);
+ }
+ viewport->RendererUserData = viewport->PlatformUserData = viewport->PlatformHandle = NULL;
+ viewport->ClearRequestFlags();
+}
+
+void ImGui::DestroyPlatformWindows()
+{
+ // We call the destroy window on every viewport (including the main viewport, index 0) to give a chance to the backend
+ // to clear any data they may have stored in e.g. PlatformUserData, RendererUserData.
+ // It is convenient for the platform backend code to store something in the main viewport, in order for e.g. the mouse handling
+ // code to operator a consistent manner.
+ // It is expected that the backend can handle calls to Renderer_DestroyWindow/Platform_DestroyWindow without
+ // crashing if it doesn't have data stored.
+ ImGuiContext& g = *GImGui;
+ for (ImGuiViewportP* viewport : g.Viewports)
+ DestroyPlatformWindow(viewport);
+}
+
+
+//-----------------------------------------------------------------------------
+// [SECTION] DOCKING
+//-----------------------------------------------------------------------------
+// Docking: Internal Types
+// Docking: Forward Declarations
+// Docking: ImGuiDockContext
+// Docking: ImGuiDockContext Docking/Undocking functions
+// Docking: ImGuiDockNode
+// Docking: ImGuiDockNode Tree manipulation functions
+// Docking: Public Functions (SetWindowDock, DockSpace, DockSpaceOverViewport)
+// Docking: Builder Functions
+// Docking: Begin/End Support Functions (called from Begin/End)
+// Docking: Settings
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+// Typical Docking call flow: (root level is generally public API):
+//-----------------------------------------------------------------------------
+// - NewFrame() new dear imgui frame
+// | DockContextNewFrameUpdateUndocking() - process queued undocking requests
+// | - DockContextProcessUndockWindow() - process one window undocking request
+// | - DockContextProcessUndockNode() - process one whole node undocking request
+// | DockContextNewFrameUpdateUndocking() - process queue docking requests, create floating dock nodes
+// | - update g.HoveredDockNode - [debug] update node hovered by mouse
+// | - DockContextProcessDock() - process one docking request
+// | - DockNodeUpdate()
+// | - DockNodeUpdateForRootNode()
+// | - DockNodeUpdateFlagsAndCollapse()
+// | - DockNodeFindInfo()
+// | - destroy unused node or tab bar
+// | - create dock node host window
+// | - Begin() etc.
+// | - DockNodeStartMouseMovingWindow()
+// | - DockNodeTreeUpdatePosSize()
+// | - DockNodeTreeUpdateSplitter()
+// | - draw node background
+// | - DockNodeUpdateTabBar() - create/update tab bar for a docking node
+// | - DockNodeAddTabBar()
+// | - DockNodeWindowMenuUpdate()
+// | - DockNodeCalcTabBarLayout()
+// | - BeginTabBarEx()
+// | - TabItemEx() calls
+// | - EndTabBar()
+// | - BeginDockableDragDropTarget()
+// | - DockNodeUpdate() - recurse into child nodes...
+//-----------------------------------------------------------------------------
+// - DockSpace() user submit a dockspace into a window
+// | Begin(Child) - create a child window
+// | DockNodeUpdate() - call main dock node update function
+// | End(Child)
+// | ItemSize()
+//-----------------------------------------------------------------------------
+// - Begin()
+// | BeginDocked()
+// | BeginDockableDragDropSource()
+// | BeginDockableDragDropTarget()
+// | - DockNodePreviewDockRender()
+//-----------------------------------------------------------------------------
+// - EndFrame()
+// | DockContextEndFrame()
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+// Docking: Internal Types
+//-----------------------------------------------------------------------------
+// - ImGuiDockRequestType
+// - ImGuiDockRequest
+// - ImGuiDockPreviewData
+// - ImGuiDockNodeSettings
+// - ImGuiDockContext
+//-----------------------------------------------------------------------------
+
+enum ImGuiDockRequestType
+{
+ ImGuiDockRequestType_None = 0,
+ ImGuiDockRequestType_Dock,
+ ImGuiDockRequestType_Undock,
+ ImGuiDockRequestType_Split // Split is the same as Dock but without a DockPayload
+};
+
+struct ImGuiDockRequest
+{
+ ImGuiDockRequestType Type;
+ ImGuiWindow* DockTargetWindow; // Destination/Target Window to dock into (may be a loose window or a DockNode, might be NULL in which case DockTargetNode cannot be NULL)
+ ImGuiDockNode* DockTargetNode; // Destination/Target Node to dock into
+ ImGuiWindow* DockPayload; // Source/Payload window to dock (may be a loose window or a DockNode), [Optional]
+ ImGuiDir DockSplitDir;
+ float DockSplitRatio;
+ bool DockSplitOuter;
+ ImGuiWindow* UndockTargetWindow;
+ ImGuiDockNode* UndockTargetNode;
+
+ ImGuiDockRequest()
+ {
+ Type = ImGuiDockRequestType_None;
+ DockTargetWindow = DockPayload = UndockTargetWindow = NULL;
+ DockTargetNode = UndockTargetNode = NULL;
+ DockSplitDir = ImGuiDir_None;
+ DockSplitRatio = 0.5f;
+ DockSplitOuter = false;
+ }
+};
+
+struct ImGuiDockPreviewData
+{
+ ImGuiDockNode FutureNode;
+ bool IsDropAllowed;
+ bool IsCenterAvailable;
+ bool IsSidesAvailable; // Hold your breath, grammar freaks..
+ bool IsSplitDirExplicit; // Set when hovered the drop rect (vs. implicit SplitDir==None when hovered the window)
+ ImGuiDockNode* SplitNode;
+ ImGuiDir SplitDir;
+ float SplitRatio;
+ ImRect DropRectsDraw[ImGuiDir_COUNT + 1]; // May be slightly different from hit-testing drop rects used in DockNodeCalcDropRects()
+
+ ImGuiDockPreviewData() : FutureNode(0) { IsDropAllowed = IsCenterAvailable = IsSidesAvailable = IsSplitDirExplicit = false; SplitNode = NULL; SplitDir = ImGuiDir_None; SplitRatio = 0.f; for (int n = 0; n < IM_ARRAYSIZE(DropRectsDraw); n++) DropRectsDraw[n] = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX); }
+};
+
+// Persistent Settings data, stored contiguously in SettingsNodes (sizeof() ~32 bytes)
+struct ImGuiDockNodeSettings
+{
+ ImGuiID ID;
+ ImGuiID ParentNodeId;
+ ImGuiID ParentWindowId;
+ ImGuiID SelectedTabId;
+ signed char SplitAxis;
+ char Depth;
+ ImGuiDockNodeFlags Flags; // NB: We save individual flags one by one in ascii format (ImGuiDockNodeFlags_SavedFlagsMask_)
+ ImVec2ih Pos;
+ ImVec2ih Size;
+ ImVec2ih SizeRef;
+ ImGuiDockNodeSettings() { memset(this, 0, sizeof(*this)); SplitAxis = ImGuiAxis_None; }
+};
+
+//-----------------------------------------------------------------------------
+// Docking: Forward Declarations
+//-----------------------------------------------------------------------------
+
+namespace ImGui
+{
+ // ImGuiDockContext
+ static ImGuiDockNode* DockContextAddNode(ImGuiContext* ctx, ImGuiID id);
+ static void DockContextRemoveNode(ImGuiContext* ctx, ImGuiDockNode* node, bool merge_sibling_into_parent_node);
+ static void DockContextQueueNotifyRemovedNode(ImGuiContext* ctx, ImGuiDockNode* node);
+ static void DockContextProcessDock(ImGuiContext* ctx, ImGuiDockRequest* req);
+ static void DockContextPruneUnusedSettingsNodes(ImGuiContext* ctx);
+ static ImGuiDockNode* DockContextBindNodeToWindow(ImGuiContext* ctx, ImGuiWindow* window);
+ static void DockContextBuildNodesFromSettings(ImGuiContext* ctx, ImGuiDockNodeSettings* node_settings_array, int node_settings_count);
+ static void DockContextBuildAddWindowsToNodes(ImGuiContext* ctx, ImGuiID root_id); // Use root_id==0 to add all
+
+ // ImGuiDockNode
+ static void DockNodeAddWindow(ImGuiDockNode* node, ImGuiWindow* window, bool add_to_tab_bar);
+ static void DockNodeMoveWindows(ImGuiDockNode* dst_node, ImGuiDockNode* src_node);
+ static void DockNodeMoveChildNodes(ImGuiDockNode* dst_node, ImGuiDockNode* src_node);
+ static ImGuiWindow* DockNodeFindWindowByID(ImGuiDockNode* node, ImGuiID id);
+ static void DockNodeApplyPosSizeToWindows(ImGuiDockNode* node);
+ static void DockNodeRemoveWindow(ImGuiDockNode* node, ImGuiWindow* window, ImGuiID save_dock_id);
+ static void DockNodeHideHostWindow(ImGuiDockNode* node);
+ static void DockNodeUpdate(ImGuiDockNode* node);
+ static void DockNodeUpdateForRootNode(ImGuiDockNode* node);
+ static void DockNodeUpdateFlagsAndCollapse(ImGuiDockNode* node);
+ static void DockNodeUpdateHasCentralNodeChild(ImGuiDockNode* node);
+ static void DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_window);
+ static void DockNodeAddTabBar(ImGuiDockNode* node);
+ static void DockNodeRemoveTabBar(ImGuiDockNode* node);
+ static void DockNodeWindowMenuUpdate(ImGuiDockNode* node, ImGuiTabBar* tab_bar);
+ static void DockNodeUpdateVisibleFlag(ImGuiDockNode* node);
+ static void DockNodeStartMouseMovingWindow(ImGuiDockNode* node, ImGuiWindow* window);
+ static bool DockNodeIsDropAllowed(ImGuiWindow* host_window, ImGuiWindow* payload_window);
+ static void DockNodePreviewDockSetup(ImGuiWindow* host_window, ImGuiDockNode* host_node, ImGuiWindow* payload_window, ImGuiDockNode* payload_node, ImGuiDockPreviewData* preview_data, bool is_explicit_target, bool is_outer_docking);
+ static void DockNodePreviewDockRender(ImGuiWindow* host_window, ImGuiDockNode* host_node, ImGuiWindow* payload_window, const ImGuiDockPreviewData* preview_data);
+ static void DockNodeCalcTabBarLayout(const ImGuiDockNode* node, ImRect* out_title_rect, ImRect* out_tab_bar_rect, ImVec2* out_window_menu_button_pos, ImVec2* out_close_button_pos);
+ static void DockNodeCalcSplitRects(ImVec2& pos_old, ImVec2& size_old, ImVec2& pos_new, ImVec2& size_new, ImGuiDir dir, ImVec2 size_new_desired);
+ static bool DockNodeCalcDropRectsAndTestMousePos(const ImRect& parent, ImGuiDir dir, ImRect& out_draw, bool outer_docking, ImVec2* test_mouse_pos);
+ static const char* DockNodeGetHostWindowTitle(ImGuiDockNode* node, char* buf, int buf_size) { ImFormatString(buf, buf_size, "##DockNode_%02X", node->ID); return buf; }
+ static int DockNodeGetTabOrder(ImGuiWindow* window);
+
+ // ImGuiDockNode tree manipulations
+ static void DockNodeTreeSplit(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImGuiAxis split_axis, int split_first_child, float split_ratio, ImGuiDockNode* new_node);
+ static void DockNodeTreeMerge(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImGuiDockNode* merge_lead_child);
+ static void DockNodeTreeUpdatePosSize(ImGuiDockNode* node, ImVec2 pos, ImVec2 size, ImGuiDockNode* only_write_to_single_node = NULL);
+ static void DockNodeTreeUpdateSplitter(ImGuiDockNode* node);
+ static ImGuiDockNode* DockNodeTreeFindVisibleNodeByPos(ImGuiDockNode* node, ImVec2 pos);
+ static ImGuiDockNode* DockNodeTreeFindFallbackLeafNode(ImGuiDockNode* node);
+
+ // Settings
+ static void DockSettingsRenameNodeReferences(ImGuiID old_node_id, ImGuiID new_node_id);
+ static void DockSettingsRemoveNodeReferences(ImGuiID* node_ids, int node_ids_count);
+ static ImGuiDockNodeSettings* DockSettingsFindNodeSettings(ImGuiContext* ctx, ImGuiID node_id);
+ static void DockSettingsHandler_ClearAll(ImGuiContext*, ImGuiSettingsHandler*);
+ static void DockSettingsHandler_ApplyAll(ImGuiContext*, ImGuiSettingsHandler*);
+ static void* DockSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name);
+ static void DockSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line);
+ static void DockSettingsHandler_WriteAll(ImGuiContext* imgui_ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf);
+}
+
+//-----------------------------------------------------------------------------
+// Docking: ImGuiDockContext
+//-----------------------------------------------------------------------------
+// The lifetime model is different from the one of regular windows: we always create a ImGuiDockNode for each ImGuiDockNodeSettings,
+// or we always hold the entire docking node tree. Nodes are frequently hidden, e.g. if the window(s) or child nodes they host are not active.
+// At boot time only, we run a simple GC to remove nodes that have no references.
+// Because dock node settings (which are small, contiguous structures) are always mirrored by their corresponding dock nodes (more complete structures),
+// we can also very easily recreate the nodes from scratch given the settings data (this is what DockContextRebuild() does).
+// This is convenient as docking reconfiguration can be implemented by mostly poking at the simpler settings data.
+//-----------------------------------------------------------------------------
+// - DockContextInitialize()
+// - DockContextShutdown()
+// - DockContextClearNodes()
+// - DockContextRebuildNodes()
+// - DockContextNewFrameUpdateUndocking()
+// - DockContextNewFrameUpdateDocking()
+// - DockContextEndFrame()
+// - DockContextFindNodeByID()
+// - DockContextBindNodeToWindow()
+// - DockContextGenNodeID()
+// - DockContextAddNode()
+// - DockContextRemoveNode()
+// - ImGuiDockContextPruneNodeData
+// - DockContextPruneUnusedSettingsNodes()
+// - DockContextBuildNodesFromSettings()
+// - DockContextBuildAddWindowsToNodes()
+//-----------------------------------------------------------------------------
+
+void ImGui::DockContextInitialize(ImGuiContext* ctx)
+{
+ ImGuiContext& g = *ctx;
+
+ // Add .ini handle for persistent docking data
+ ImGuiSettingsHandler ini_handler;
+ ini_handler.TypeName = "Docking";
+ ini_handler.TypeHash = ImHashStr("Docking");
+ ini_handler.ClearAllFn = DockSettingsHandler_ClearAll;
+ ini_handler.ReadInitFn = DockSettingsHandler_ClearAll; // Also clear on read
+ ini_handler.ReadOpenFn = DockSettingsHandler_ReadOpen;
+ ini_handler.ReadLineFn = DockSettingsHandler_ReadLine;
+ ini_handler.ApplyAllFn = DockSettingsHandler_ApplyAll;
+ ini_handler.WriteAllFn = DockSettingsHandler_WriteAll;
+ g.SettingsHandlers.push_back(ini_handler);
+
+ g.DockNodeWindowMenuHandler = &DockNodeWindowMenuHandler_Default;
+}
+
+void ImGui::DockContextShutdown(ImGuiContext* ctx)
+{
+ ImGuiDockContext* dc = &ctx->DockContext;
+ for (int n = 0; n < dc->Nodes.Data.Size; n++)
+ if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p)
+ IM_DELETE(node);
+}
+
+void ImGui::DockContextClearNodes(ImGuiContext* ctx, ImGuiID root_id, bool clear_settings_refs)
+{
+ IM_UNUSED(ctx);
+ IM_ASSERT(ctx == GImGui);
+ DockBuilderRemoveNodeDockedWindows(root_id, clear_settings_refs);
+ DockBuilderRemoveNodeChildNodes(root_id);
+}
+
+// [DEBUG] This function also acts as a defacto test to make sure we can rebuild from scratch without a glitch
+// (Different from DockSettingsHandler_ClearAll() + DockSettingsHandler_ApplyAll() because this reuses current settings!)
+void ImGui::DockContextRebuildNodes(ImGuiContext* ctx)
+{
+ ImGuiContext& g = *ctx;
+ ImGuiDockContext* dc = &ctx->DockContext;
+ IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextRebuildNodes\n");
+ SaveIniSettingsToMemory();
+ ImGuiID root_id = 0; // Rebuild all
+ DockContextClearNodes(ctx, root_id, false);
+ DockContextBuildNodesFromSettings(ctx, dc->NodesSettings.Data, dc->NodesSettings.Size);
+ DockContextBuildAddWindowsToNodes(ctx, root_id);
+}
+
+// Docking context update function, called by NewFrame()
+void ImGui::DockContextNewFrameUpdateUndocking(ImGuiContext* ctx)
+{
+ ImGuiContext& g = *ctx;
+ ImGuiDockContext* dc = &ctx->DockContext;
+ if (!(g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable))
+ {
+ if (dc->Nodes.Data.Size > 0 || dc->Requests.Size > 0)
+ DockContextClearNodes(ctx, 0, true);
+ return;
+ }
+
+ // Setting NoSplit at runtime merges all nodes
+ if (g.IO.ConfigDockingNoSplit)
+ for (int n = 0; n < dc->Nodes.Data.Size; n++)
+ if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p)
+ if (node->IsRootNode() && node->IsSplitNode())
+ {
+ DockBuilderRemoveNodeChildNodes(node->ID);
+ //dc->WantFullRebuild = true;
+ }
+
+ // Process full rebuild
+#if 0
+ if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_C)))
+ dc->WantFullRebuild = true;
+#endif
+ if (dc->WantFullRebuild)
+ {
+ DockContextRebuildNodes(ctx);
+ dc->WantFullRebuild = false;
+ }
+
+ // Process Undocking requests (we need to process them _before_ the UpdateMouseMovingWindowNewFrame call in NewFrame)
+ for (ImGuiDockRequest& req : dc->Requests)
+ {
+ if (req.Type == ImGuiDockRequestType_Undock && req.UndockTargetWindow)
+ DockContextProcessUndockWindow(ctx, req.UndockTargetWindow);
+ else if (req.Type == ImGuiDockRequestType_Undock && req.UndockTargetNode)
+ DockContextProcessUndockNode(ctx, req.UndockTargetNode);
+ }
+}
+
+// Docking context update function, called by NewFrame()
+void ImGui::DockContextNewFrameUpdateDocking(ImGuiContext* ctx)
+{
+ ImGuiContext& g = *ctx;
+ ImGuiDockContext* dc = &ctx->DockContext;
+ if (!(g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable))
+ return;
+
+ // [DEBUG] Store hovered dock node.
+ // We could in theory use DockNodeTreeFindVisibleNodeByPos() on the root host dock node, but using ->DockNode is a good shortcut.
+ // Note this is mostly a debug thing and isn't actually used for docking target, because docking involve more detailed filtering.
+ g.DebugHoveredDockNode = NULL;
+ if (ImGuiWindow* hovered_window = g.HoveredWindowUnderMovingWindow)
+ {
+ if (hovered_window->DockNodeAsHost)
+ g.DebugHoveredDockNode = DockNodeTreeFindVisibleNodeByPos(hovered_window->DockNodeAsHost, g.IO.MousePos);
+ else if (hovered_window->RootWindow->DockNode)
+ g.DebugHoveredDockNode = hovered_window->RootWindow->DockNode;
+ }
+
+ // Process Docking requests
+ for (ImGuiDockRequest& req : dc->Requests)
+ if (req.Type == ImGuiDockRequestType_Dock)
+ DockContextProcessDock(ctx, &req);
+ dc->Requests.resize(0);
+
+ // Create windows for each automatic docking nodes
+ // We can have NULL pointers when we delete nodes, but because ID are recycled this should amortize nicely (and our node count will never be very high)
+ for (int n = 0; n < dc->Nodes.Data.Size; n++)
+ if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p)
+ if (node->IsFloatingNode())
+ DockNodeUpdate(node);
+}
+
+void ImGui::DockContextEndFrame(ImGuiContext* ctx)
+{
+ // Draw backgrounds of node missing their window
+ ImGuiContext& g = *ctx;
+ ImGuiDockContext* dc = &g.DockContext;
+ for (int n = 0; n < dc->Nodes.Data.Size; n++)
+ if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p)
+ if (node->LastFrameActive == g.FrameCount && node->IsVisible && node->HostWindow && node->IsLeafNode() && !node->IsBgDrawnThisFrame)
+ {
+ ImRect bg_rect(node->Pos + ImVec2(0.0f, GetFrameHeight()), node->Pos + node->Size);
+ ImDrawFlags bg_rounding_flags = CalcRoundingFlagsForRectInRect(bg_rect, node->HostWindow->Rect(), g.Style.DockingSeparatorSize);
+ node->HostWindow->DrawList->ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_BG);
+ node->HostWindow->DrawList->AddRectFilled(bg_rect.Min, bg_rect.Max, node->LastBgColor, node->HostWindow->WindowRounding, bg_rounding_flags);
+ }
+}
+
+ImGuiDockNode* ImGui::DockContextFindNodeByID(ImGuiContext* ctx, ImGuiID id)
+{
+ return (ImGuiDockNode*)ctx->DockContext.Nodes.GetVoidPtr(id);
+}
+
+ImGuiID ImGui::DockContextGenNodeID(ImGuiContext* ctx)
+{
+ // Generate an ID for new node (the exact ID value doesn't matter as long as it is not already used)
+ // FIXME-OPT FIXME-DOCK: This is suboptimal, even if the node count is small enough not to be a worry.0
+ // We should poke in ctx->Nodes to find a suitable ID faster. Even more so trivial that ctx->Nodes lookup is already sorted.
+ ImGuiID id = 0x0001;
+ while (DockContextFindNodeByID(ctx, id) != NULL)
+ id++;
+ return id;
+}
+
+static ImGuiDockNode* ImGui::DockContextAddNode(ImGuiContext* ctx, ImGuiID id)
+{
+ // Generate an ID for the new node (the exact ID value doesn't matter as long as it is not already used) and add the first window.
+ ImGuiContext& g = *ctx;
+ if (id == 0)
+ id = DockContextGenNodeID(ctx);
+ else
+ IM_ASSERT(DockContextFindNodeByID(ctx, id) == NULL);
+
+ // We don't set node->LastFrameAlive on construction. Nodes are always created at all time to reflect .ini settings!
+ IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextAddNode 0x%08X\n", id);
+ ImGuiDockNode* node = IM_NEW(ImGuiDockNode)(id);
+ ctx->DockContext.Nodes.SetVoidPtr(node->ID, node);
+ return node;
+}
+
+static void ImGui::DockContextRemoveNode(ImGuiContext* ctx, ImGuiDockNode* node, bool merge_sibling_into_parent_node)
+{
+ ImGuiContext& g = *ctx;
+ ImGuiDockContext* dc = &ctx->DockContext;
+
+ IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextRemoveNode 0x%08X\n", node->ID);
+ IM_ASSERT(DockContextFindNodeByID(ctx, node->ID) == node);
+ IM_ASSERT(node->ChildNodes[0] == NULL && node->ChildNodes[1] == NULL);
+ IM_ASSERT(node->Windows.Size == 0);
+
+ if (node->HostWindow)
+ node->HostWindow->DockNodeAsHost = NULL;
+
+ ImGuiDockNode* parent_node = node->ParentNode;
+ const bool merge = (merge_sibling_into_parent_node && parent_node != NULL);
+ if (merge)
+ {
+ IM_ASSERT(parent_node->ChildNodes[0] == node || parent_node->ChildNodes[1] == node);
+ ImGuiDockNode* sibling_node = (parent_node->ChildNodes[0] == node ? parent_node->ChildNodes[1] : parent_node->ChildNodes[0]);
+ DockNodeTreeMerge(&g, parent_node, sibling_node);
+ }
+ else
+ {
+ for (int n = 0; parent_node && n < IM_ARRAYSIZE(parent_node->ChildNodes); n++)
+ if (parent_node->ChildNodes[n] == node)
+ node->ParentNode->ChildNodes[n] = NULL;
+ dc->Nodes.SetVoidPtr(node->ID, NULL);
+ IM_DELETE(node);
+ }
+}
+
+static int IMGUI_CDECL DockNodeComparerDepthMostFirst(const void* lhs, const void* rhs)
+{
+ const ImGuiDockNode* a = *(const ImGuiDockNode* const*)lhs;
+ const ImGuiDockNode* b = *(const ImGuiDockNode* const*)rhs;
+ return ImGui::DockNodeGetDepth(b) - ImGui::DockNodeGetDepth(a);
+}
+
+// Pre C++0x doesn't allow us to use a function-local type (without linkage) as template parameter, so we moved this here.
+struct ImGuiDockContextPruneNodeData
+{
+ int CountWindows, CountChildWindows, CountChildNodes;
+ ImGuiID RootId;
+ ImGuiDockContextPruneNodeData() { CountWindows = CountChildWindows = CountChildNodes = 0; RootId = 0; }
+};
+
+// Garbage collect unused nodes (run once at init time)
+static void ImGui::DockContextPruneUnusedSettingsNodes(ImGuiContext* ctx)
+{
+ ImGuiContext& g = *ctx;
+ ImGuiDockContext* dc = &ctx->DockContext;
+ IM_ASSERT(g.Windows.Size == 0);
+
+ ImPool<ImGuiDockContextPruneNodeData> pool;
+ pool.Reserve(dc->NodesSettings.Size);
+
+ // Count child nodes and compute RootID
+ for (int settings_n = 0; settings_n < dc->NodesSettings.Size; settings_n++)
+ {
+ ImGuiDockNodeSettings* settings = &dc->NodesSettings[settings_n];
+ ImGuiDockContextPruneNodeData* parent_data = settings->ParentNodeId ? pool.GetByKey(settings->ParentNodeId) : 0;
+ pool.GetOrAddByKey(settings->ID)->RootId = parent_data ? parent_data->RootId : settings->ID;
+ if (settings->ParentNodeId)
+ pool.GetOrAddByKey(settings->ParentNodeId)->CountChildNodes++;
+ }
+
+ // Count reference to dock ids from dockspaces
+ // We track the 'auto-DockNode <- manual-Window <- manual-DockSpace' in order to avoid 'auto-DockNode' being ditched by DockContextPruneUnusedSettingsNodes()
+ for (int settings_n = 0; settings_n < dc->NodesSettings.Size; settings_n++)
+ {
+ ImGuiDockNodeSettings* settings = &dc->NodesSettings[settings_n];
+ if (settings->ParentWindowId != 0)
+ if (ImGuiWindowSettings* window_settings = FindWindowSettingsByID(settings->ParentWindowId))
+ if (window_settings->DockId)
+ if (ImGuiDockContextPruneNodeData* data = pool.GetByKey(window_settings->DockId))
+ data->CountChildNodes++;
+ }
+
+ // Count reference to dock ids from window settings
+ // We guard against the possibility of an invalid .ini file (RootID may point to a missing node)
+ for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings))
+ if (ImGuiID dock_id = settings->DockId)
+ if (ImGuiDockContextPruneNodeData* data = pool.GetByKey(dock_id))
+ {
+ data->CountWindows++;
+ if (ImGuiDockContextPruneNodeData* data_root = (data->RootId == dock_id) ? data : pool.GetByKey(data->RootId))
+ data_root->CountChildWindows++;
+ }
+
+ // Prune
+ for (int settings_n = 0; settings_n < dc->NodesSettings.Size; settings_n++)
+ {
+ ImGuiDockNodeSettings* settings = &dc->NodesSettings[settings_n];
+ ImGuiDockContextPruneNodeData* data = pool.GetByKey(settings->ID);
+ if (data->CountWindows > 1)
+ continue;
+ ImGuiDockContextPruneNodeData* data_root = (data->RootId == settings->ID) ? data : pool.GetByKey(data->RootId);
+
+ bool remove = false;
+ remove |= (data->CountWindows == 1 && settings->ParentNodeId == 0 && data->CountChildNodes == 0 && !(settings->Flags & ImGuiDockNodeFlags_CentralNode)); // Floating root node with only 1 window
+ remove |= (data->CountWindows == 0 && settings->ParentNodeId == 0 && data->CountChildNodes == 0); // Leaf nodes with 0 window
+ remove |= (data_root->CountChildWindows == 0);
+ if (remove)
+ {
+ IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextPruneUnusedSettingsNodes: Prune 0x%08X\n", settings->ID);
+ DockSettingsRemoveNodeReferences(&settings->ID, 1);
+ settings->ID = 0;
+ }
+ }
+}
+
+static void ImGui::DockContextBuildNodesFromSettings(ImGuiContext* ctx, ImGuiDockNodeSettings* node_settings_array, int node_settings_count)
+{
+ // Build nodes
+ for (int node_n = 0; node_n < node_settings_count; node_n++)
+ {
+ ImGuiDockNodeSettings* settings = &node_settings_array[node_n];
+ if (settings->ID == 0)
+ continue;
+ ImGuiDockNode* node = DockContextAddNode(ctx, settings->ID);
+ node->ParentNode = settings->ParentNodeId ? DockContextFindNodeByID(ctx, settings->ParentNodeId) : NULL;
+ node->Pos = ImVec2(settings->Pos.x, settings->Pos.y);
+ node->Size = ImVec2(settings->Size.x, settings->Size.y);
+ node->SizeRef = ImVec2(settings->SizeRef.x, settings->SizeRef.y);
+ node->AuthorityForPos = node->AuthorityForSize = node->AuthorityForViewport = ImGuiDataAuthority_DockNode;
+ if (node->ParentNode && node->ParentNode->ChildNodes[0] == NULL)
+ node->ParentNode->ChildNodes[0] = node;
+ else if (node->ParentNode && node->ParentNode->ChildNodes[1] == NULL)
+ node->ParentNode->ChildNodes[1] = node;
+ node->SelectedTabId = settings->SelectedTabId;
+ node->SplitAxis = (ImGuiAxis)settings->SplitAxis;
+ node->SetLocalFlags(settings->Flags & ImGuiDockNodeFlags_SavedFlagsMask_);
+
+ // Bind host window immediately if it already exist (in case of a rebuild)
+ // This is useful as the RootWindowForTitleBarHighlight links necessary to highlight the currently focused node requires node->HostWindow to be set.
+ char host_window_title[20];
+ ImGuiDockNode* root_node = DockNodeGetRootNode(node);
+ node->HostWindow = FindWindowByName(DockNodeGetHostWindowTitle(root_node, host_window_title, IM_ARRAYSIZE(host_window_title)));
+ }
+}
+
+void ImGui::DockContextBuildAddWindowsToNodes(ImGuiContext* ctx, ImGuiID root_id)
+{
+ // Rebind all windows to nodes (they can also lazily rebind but we'll have a visible glitch during the first frame)
+ ImGuiContext& g = *ctx;
+ for (ImGuiWindow* window : g.Windows)
+ {
+ if (window->DockId == 0 || window->LastFrameActive < g.FrameCount - 1)
+ continue;
+ if (window->DockNode != NULL)
+ continue;
+
+ ImGuiDockNode* node = DockContextFindNodeByID(ctx, window->DockId);
+ IM_ASSERT(node != NULL); // This should have been called after DockContextBuildNodesFromSettings()
+ if (root_id == 0 || DockNodeGetRootNode(node)->ID == root_id)
+ DockNodeAddWindow(node, window, true);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Docking: ImGuiDockContext Docking/Undocking functions
+//-----------------------------------------------------------------------------
+// - DockContextQueueDock()
+// - DockContextQueueUndockWindow()
+// - DockContextQueueUndockNode()
+// - DockContextQueueNotifyRemovedNode()
+// - DockContextProcessDock()
+// - DockContextProcessUndockWindow()
+// - DockContextProcessUndockNode()
+// - DockContextCalcDropPosForDocking()
+//-----------------------------------------------------------------------------
+
+void ImGui::DockContextQueueDock(ImGuiContext* ctx, ImGuiWindow* target, ImGuiDockNode* target_node, ImGuiWindow* payload, ImGuiDir split_dir, float split_ratio, bool split_outer)
+{
+ IM_ASSERT(target != payload);
+ ImGuiDockRequest req;
+ req.Type = ImGuiDockRequestType_Dock;
+ req.DockTargetWindow = target;
+ req.DockTargetNode = target_node;
+ req.DockPayload = payload;
+ req.DockSplitDir = split_dir;
+ req.DockSplitRatio = split_ratio;
+ req.DockSplitOuter = split_outer;
+ ctx->DockContext.Requests.push_back(req);
+}
+
+void ImGui::DockContextQueueUndockWindow(ImGuiContext* ctx, ImGuiWindow* window)
+{
+ ImGuiDockRequest req;
+ req.Type = ImGuiDockRequestType_Undock;
+ req.UndockTargetWindow = window;
+ ctx->DockContext.Requests.push_back(req);
+}
+
+void ImGui::DockContextQueueUndockNode(ImGuiContext* ctx, ImGuiDockNode* node)
+{
+ ImGuiDockRequest req;
+ req.Type = ImGuiDockRequestType_Undock;
+ req.UndockTargetNode = node;
+ ctx->DockContext.Requests.push_back(req);
+}
+
+void ImGui::DockContextQueueNotifyRemovedNode(ImGuiContext* ctx, ImGuiDockNode* node)
+{
+ ImGuiDockContext* dc = &ctx->DockContext;
+ for (ImGuiDockRequest& req : dc->Requests)
+ if (req.DockTargetNode == node)
+ req.Type = ImGuiDockRequestType_None;
+}
+
+void ImGui::DockContextProcessDock(ImGuiContext* ctx, ImGuiDockRequest* req)
+{
+ IM_ASSERT((req->Type == ImGuiDockRequestType_Dock && req->DockPayload != NULL) || (req->Type == ImGuiDockRequestType_Split && req->DockPayload == NULL));
+ IM_ASSERT(req->DockTargetWindow != NULL || req->DockTargetNode != NULL);
+
+ ImGuiContext& g = *ctx;
+ IM_UNUSED(g);
+
+ ImGuiWindow* payload_window = req->DockPayload; // Optional
+ ImGuiWindow* target_window = req->DockTargetWindow;
+ ImGuiDockNode* node = req->DockTargetNode;
+ if (payload_window)
+ IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextProcessDock node 0x%08X target '%s' dock window '%s', split_dir %d\n", node ? node->ID : 0, target_window ? target_window->Name : "NULL", payload_window->Name, req->DockSplitDir);
+ else
+ IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextProcessDock node 0x%08X, split_dir %d\n", node ? node->ID : 0, req->DockSplitDir);
+
+ // Decide which Tab will be selected at the end of the operation
+ ImGuiID next_selected_id = 0;
+ ImGuiDockNode* payload_node = NULL;
+ if (payload_window)
+ {
+ payload_node = payload_window->DockNodeAsHost;
+ payload_window->DockNodeAsHost = NULL; // Important to clear this as the node will have its life as a child which might be merged/deleted later.
+ if (payload_node && payload_node->IsLeafNode())
+ next_selected_id = payload_node->TabBar->NextSelectedTabId ? payload_node->TabBar->NextSelectedTabId : payload_node->TabBar->SelectedTabId;
+ if (payload_node == NULL)
+ next_selected_id = payload_window->TabId;
+ }
+
+ // FIXME-DOCK: When we are trying to dock an existing single-window node into a loose window, transfer Node ID as well
+ // When processing an interactive split, usually LastFrameAlive will be < g.FrameCount. But DockBuilder operations can make it ==.
+ if (node)
+ IM_ASSERT(node->LastFrameAlive <= g.FrameCount);
+ if (node && target_window && node == target_window->DockNodeAsHost)
+ IM_ASSERT(node->Windows.Size > 0 || node->IsSplitNode() || node->IsCentralNode());
+
+ // Create new node and add existing window to it
+ if (node == NULL)
+ {
+ node = DockContextAddNode(ctx, 0);
+ node->Pos = target_window->Pos;
+ node->Size = target_window->Size;
+ if (target_window->DockNodeAsHost == NULL)
+ {
+ DockNodeAddWindow(node, target_window, true);
+ node->TabBar->Tabs[0].Flags &= ~ImGuiTabItemFlags_Unsorted;
+ target_window->DockIsActive = true;
+ }
+ }
+
+ ImGuiDir split_dir = req->DockSplitDir;
+ if (split_dir != ImGuiDir_None)
+ {
+ // Split into two, one side will be our payload node unless we are dropping a loose window
+ const ImGuiAxis split_axis = (split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Right) ? ImGuiAxis_X : ImGuiAxis_Y;
+ const int split_inheritor_child_idx = (split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Up) ? 1 : 0; // Current contents will be moved to the opposite side
+ const float split_ratio = req->DockSplitRatio;
+ DockNodeTreeSplit(ctx, node, split_axis, split_inheritor_child_idx, split_ratio, payload_node); // payload_node may be NULL here!
+ ImGuiDockNode* new_node = node->ChildNodes[split_inheritor_child_idx ^ 1];
+ new_node->HostWindow = node->HostWindow;
+ node = new_node;
+ }
+ node->SetLocalFlags(node->LocalFlags & ~ImGuiDockNodeFlags_HiddenTabBar);
+
+ if (node != payload_node)
+ {
+ // Create tab bar before we call DockNodeMoveWindows (which would attempt to move the old tab-bar, which would lead us to payload tabs wrongly appearing before target tabs!)
+ if (node->Windows.Size > 0 && node->TabBar == NULL)
+ {
+ DockNodeAddTabBar(node);
+ for (int n = 0; n < node->Windows.Size; n++)
+ TabBarAddTab(node->TabBar, ImGuiTabItemFlags_None, node->Windows[n]);
+ }
+
+ if (payload_node != NULL)
+ {
+ // Transfer full payload node (with 1+ child windows or child nodes)
+ if (payload_node->IsSplitNode())
+ {
+ if (node->Windows.Size > 0)
+ {
+ // We can dock a split payload into a node that already has windows _only_ if our payload is a node tree with a single visible node.
+ // In this situation, we move the windows of the target node into the currently visible node of the payload.
+ // This allows us to preserve some of the underlying dock tree settings nicely.
+ IM_ASSERT(payload_node->OnlyNodeWithWindows != NULL); // The docking should have been blocked by DockNodePreviewDockSetup() early on and never submitted.
+ ImGuiDockNode* visible_node = payload_node->OnlyNodeWithWindows;
+ if (visible_node->TabBar)
+ IM_ASSERT(visible_node->TabBar->Tabs.Size > 0);
+ DockNodeMoveWindows(node, visible_node);
+ DockNodeMoveWindows(visible_node, node);
+ DockSettingsRenameNodeReferences(node->ID, visible_node->ID);
+ }
+ if (node->IsCentralNode())
+ {
+ // Central node property needs to be moved to a leaf node, pick the last focused one.
+ // FIXME-DOCK: If we had to transfer other flags here, what would the policy be?
+ ImGuiDockNode* last_focused_node = DockContextFindNodeByID(ctx, payload_node->LastFocusedNodeId);
+ IM_ASSERT(last_focused_node != NULL);
+ ImGuiDockNode* last_focused_root_node = DockNodeGetRootNode(last_focused_node);
+ IM_ASSERT(last_focused_root_node == DockNodeGetRootNode(payload_node));
+ last_focused_node->SetLocalFlags(last_focused_node->LocalFlags | ImGuiDockNodeFlags_CentralNode);
+ node->SetLocalFlags(node->LocalFlags & ~ImGuiDockNodeFlags_CentralNode);
+ last_focused_root_node->CentralNode = last_focused_node;
+ }
+
+ IM_ASSERT(node->Windows.Size == 0);
+ DockNodeMoveChildNodes(node, payload_node);
+ }
+ else
+ {
+ const ImGuiID payload_dock_id = payload_node->ID;
+ DockNodeMoveWindows(node, payload_node);
+ DockSettingsRenameNodeReferences(payload_dock_id, node->ID);
+ }
+ DockContextRemoveNode(ctx, payload_node, true);
+ }
+ else if (payload_window)
+ {
+ // Transfer single window
+ const ImGuiID payload_dock_id = payload_window->DockId;
+ node->VisibleWindow = payload_window;
+ DockNodeAddWindow(node, payload_window, true);
+ if (payload_dock_id != 0)
+ DockSettingsRenameNodeReferences(payload_dock_id, node->ID);
+ }
+ }
+ else
+ {
+ // When docking a floating single window node we want to reevaluate auto-hiding of the tab bar
+ node->WantHiddenTabBarUpdate = true;
+ }
+
+ // Update selection immediately
+ if (ImGuiTabBar* tab_bar = node->TabBar)
+ tab_bar->NextSelectedTabId = next_selected_id;
+ MarkIniSettingsDirty();
+}
+
+// Problem:
+// Undocking a large (~full screen) window would leave it so large that the bottom right sizing corner would more
+// than likely be off the screen and the window would be hard to resize to fit on screen. This can be particularly problematic
+// with 'ConfigWindowsMoveFromTitleBarOnly=true' and/or with 'ConfigWindowsResizeFromEdges=false' as well (the later can be
+// due to missing ImGuiBackendFlags_HasMouseCursors backend flag).
+// Solution:
+// When undocking a window we currently force its maximum size to 90% of the host viewport or monitor.
+// Reevaluate this when we implement preserving docked/undocked size ("docking_wip/undocked_size" branch).
+static ImVec2 FixLargeWindowsWhenUndocking(const ImVec2& size, ImGuiViewport* ref_viewport)
+{
+ if (ref_viewport == NULL)
+ return size;
+
+ ImGuiContext& g = *GImGui;
+ ImVec2 max_size = ImTrunc(ref_viewport->WorkSize * 0.90f);
+ if (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable)
+ {
+ const ImGuiPlatformMonitor* monitor = ImGui::GetViewportPlatformMonitor(ref_viewport);
+ max_size = ImTrunc(monitor->WorkSize * 0.90f);
+ }
+ return ImMin(size, max_size);
+}
+
+void ImGui::DockContextProcessUndockWindow(ImGuiContext* ctx, ImGuiWindow* window, bool clear_persistent_docking_ref)
+{
+ ImGuiContext& g = *ctx;
+ IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextProcessUndockWindow window '%s', clear_persistent_docking_ref = %d\n", window->Name, clear_persistent_docking_ref);
+ if (window->DockNode)
+ DockNodeRemoveWindow(window->DockNode, window, clear_persistent_docking_ref ? 0 : window->DockId);
+ else
+ window->DockId = 0;
+ window->Collapsed = false;
+ window->DockIsActive = false;
+ window->DockNodeIsVisible = window->DockTabIsVisible = false;
+ window->Size = window->SizeFull = FixLargeWindowsWhenUndocking(window->SizeFull, window->Viewport);
+
+ MarkIniSettingsDirty();
+}
+
+void ImGui::DockContextProcessUndockNode(ImGuiContext* ctx, ImGuiDockNode* node)
+{
+ ImGuiContext& g = *ctx;
+ IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextProcessUndockNode node %08X\n", node->ID);
+ IM_ASSERT(node->IsLeafNode());
+ IM_ASSERT(node->Windows.Size >= 1);
+
+ if (node->IsRootNode() || node->IsCentralNode())
+ {
+ // In the case of a root node or central node, the node will have to stay in place. Create a new node to receive the payload.
+ ImGuiDockNode* new_node = DockContextAddNode(ctx, 0);
+ new_node->Pos = node->Pos;
+ new_node->Size = node->Size;
+ new_node->SizeRef = node->SizeRef;
+ DockNodeMoveWindows(new_node, node);
+ DockSettingsRenameNodeReferences(node->ID, new_node->ID);
+ node = new_node;
+ }
+ else
+ {
+ // Otherwise extract our node and merge our sibling back into the parent node.
+ IM_ASSERT(node->ParentNode->ChildNodes[0] == node || node->ParentNode->ChildNodes[1] == node);
+ int index_in_parent = (node->ParentNode->ChildNodes[0] == node) ? 0 : 1;
+ node->ParentNode->ChildNodes[index_in_parent] = NULL;
+ DockNodeTreeMerge(ctx, node->ParentNode, node->ParentNode->ChildNodes[index_in_parent ^ 1]);
+ node->ParentNode->AuthorityForViewport = ImGuiDataAuthority_Window; // The node that stays in place keeps the viewport, so our newly dragged out node will create a new viewport
+ node->ParentNode = NULL;
+ }
+ for (ImGuiWindow* window : node->Windows)
+ {
+ window->Flags &= ~ImGuiWindowFlags_ChildWindow;
+ if (window->ParentWindow)
+ window->ParentWindow->DC.ChildWindows.find_erase(window);
+ UpdateWindowParentAndRootLinks(window, window->Flags, NULL);
+ }
+ node->AuthorityForPos = node->AuthorityForSize = ImGuiDataAuthority_DockNode;
+ node->Size = FixLargeWindowsWhenUndocking(node->Size, node->Windows[0]->Viewport);
+ node->WantMouseMove = true;
+ MarkIniSettingsDirty();
+}
+
+// This is mostly used for automation.
+bool ImGui::DockContextCalcDropPosForDocking(ImGuiWindow* target, ImGuiDockNode* target_node, ImGuiWindow* payload_window, ImGuiDockNode* payload_node, ImGuiDir split_dir, bool split_outer, ImVec2* out_pos)
+{
+ if (target != NULL && target_node == NULL)
+ target_node = target->DockNode;
+
+ // In DockNodePreviewDockSetup() for a root central node instead of showing both "inner" and "outer" drop rects
+ // (which would be functionally identical) we only show the outer one. Reflect this here.
+ if (target_node && target_node->ParentNode == NULL && target_node->IsCentralNode() && split_dir != ImGuiDir_None)
+ split_outer = true;
+ ImGuiDockPreviewData split_data;
+ DockNodePreviewDockSetup(target, target_node, payload_window, payload_node, &split_data, false, split_outer);
+ if (split_data.DropRectsDraw[split_dir+1].IsInverted())
+ return false;
+ *out_pos = split_data.DropRectsDraw[split_dir+1].GetCenter();
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Docking: ImGuiDockNode
+//-----------------------------------------------------------------------------
+// - DockNodeGetTabOrder()
+// - DockNodeAddWindow()
+// - DockNodeRemoveWindow()
+// - DockNodeMoveChildNodes()
+// - DockNodeMoveWindows()
+// - DockNodeApplyPosSizeToWindows()
+// - DockNodeHideHostWindow()
+// - ImGuiDockNodeFindInfoResults
+// - DockNodeFindInfo()
+// - DockNodeFindWindowByID()
+// - DockNodeUpdateFlagsAndCollapse()
+// - DockNodeUpdateHasCentralNodeFlag()
+// - DockNodeUpdateVisibleFlag()
+// - DockNodeStartMouseMovingWindow()
+// - DockNodeUpdate()
+// - DockNodeUpdateWindowMenu()
+// - DockNodeBeginAmendTabBar()
+// - DockNodeEndAmendTabBar()
+// - DockNodeUpdateTabBar()
+// - DockNodeAddTabBar()
+// - DockNodeRemoveTabBar()
+// - DockNodeIsDropAllowedOne()
+// - DockNodeIsDropAllowed()
+// - DockNodeCalcTabBarLayout()
+// - DockNodeCalcSplitRects()
+// - DockNodeCalcDropRectsAndTestMousePos()
+// - DockNodePreviewDockSetup()
+// - DockNodePreviewDockRender()
+//-----------------------------------------------------------------------------
+
+ImGuiDockNode::ImGuiDockNode(ImGuiID id)
+{
+ ID = id;
+ SharedFlags = LocalFlags = LocalFlagsInWindows = MergedFlags = ImGuiDockNodeFlags_None;
+ ParentNode = ChildNodes[0] = ChildNodes[1] = NULL;
+ TabBar = NULL;
+ SplitAxis = ImGuiAxis_None;
+
+ State = ImGuiDockNodeState_Unknown;
+ LastBgColor = IM_COL32_WHITE;
+ HostWindow = VisibleWindow = NULL;
+ CentralNode = OnlyNodeWithWindows = NULL;
+ CountNodeWithWindows = 0;
+ LastFrameAlive = LastFrameActive = LastFrameFocused = -1;
+ LastFocusedNodeId = 0;
+ SelectedTabId = 0;
+ WantCloseTabId = 0;
+ RefViewportId = 0;
+ AuthorityForPos = AuthorityForSize = ImGuiDataAuthority_DockNode;
+ AuthorityForViewport = ImGuiDataAuthority_Auto;
+ IsVisible = true;
+ IsFocused = HasCloseButton = HasWindowMenuButton = HasCentralNodeChild = false;
+ IsBgDrawnThisFrame = false;
+ WantCloseAll = WantLockSizeOnce = WantMouseMove = WantHiddenTabBarUpdate = WantHiddenTabBarToggle = false;
+}
+
+ImGuiDockNode::~ImGuiDockNode()
+{
+ IM_DELETE(TabBar);
+ TabBar = NULL;
+ ChildNodes[0] = ChildNodes[1] = NULL;
+}
+
+int ImGui::DockNodeGetTabOrder(ImGuiWindow* window)
+{
+ ImGuiTabBar* tab_bar = window->DockNode->TabBar;
+ if (tab_bar == NULL)
+ return -1;
+ ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, window->TabId);
+ return tab ? TabBarGetTabOrder(tab_bar, tab) : -1;
+}
+
+static void DockNodeHideWindowDuringHostWindowCreation(ImGuiWindow* window)
+{
+ window->Hidden = true;
+ window->HiddenFramesCanSkipItems = window->Active ? 1 : 2;
+}
+
+static void ImGui::DockNodeAddWindow(ImGuiDockNode* node, ImGuiWindow* window, bool add_to_tab_bar)
+{
+ ImGuiContext& g = *GImGui; (void)g;
+ if (window->DockNode)
+ {
+ // Can overwrite an existing window->DockNode (e.g. pointing to a disabled DockSpace node)
+ IM_ASSERT(window->DockNode->ID != node->ID);
+ DockNodeRemoveWindow(window->DockNode, window, 0);
+ }
+ IM_ASSERT(window->DockNode == NULL || window->DockNodeAsHost == NULL);
+ IMGUI_DEBUG_LOG_DOCKING("[docking] DockNodeAddWindow node 0x%08X window '%s'\n", node->ID, window->Name);
+
+ // If more than 2 windows appeared on the same frame leading to the creation of a new hosting window,
+ // we'll hide windows until the host window is ready. Hide the 1st window after its been output (so it is not visible for one frame).
+ // We will call DockNodeHideWindowDuringHostWindowCreation() on ourselves in Begin()
+ if (node->HostWindow == NULL && node->Windows.Size == 1 && node->Windows[0]->WasActive == false)
+ DockNodeHideWindowDuringHostWindowCreation(node->Windows[0]);
+
+ node->Windows.push_back(window);
+ node->WantHiddenTabBarUpdate = true;
+ window->DockNode = node;
+ window->DockId = node->ID;
+ window->DockIsActive = (node->Windows.Size > 1);
+ window->DockTabWantClose = false;
+
+ // When reactivating a node with one or two loose window, the window pos/size/viewport are authoritative over the node storage.
+ // In particular it is important we init the viewport from the first window so we don't create two viewports and drop one.
+ if (node->HostWindow == NULL && node->IsFloatingNode())
+ {
+ if (node->AuthorityForPos == ImGuiDataAuthority_Auto)
+ node->AuthorityForPos = ImGuiDataAuthority_Window;
+ if (node->AuthorityForSize == ImGuiDataAuthority_Auto)
+ node->AuthorityForSize = ImGuiDataAuthority_Window;
+ if (node->AuthorityForViewport == ImGuiDataAuthority_Auto)
+ node->AuthorityForViewport = ImGuiDataAuthority_Window;
+ }
+
+ // Add to tab bar if requested
+ if (add_to_tab_bar)
+ {
+ if (node->TabBar == NULL)
+ {
+ DockNodeAddTabBar(node);
+ node->TabBar->SelectedTabId = node->TabBar->NextSelectedTabId = node->SelectedTabId;
+
+ // Add existing windows
+ for (int n = 0; n < node->Windows.Size - 1; n++)
+ TabBarAddTab(node->TabBar, ImGuiTabItemFlags_None, node->Windows[n]);
+ }
+ TabBarAddTab(node->TabBar, ImGuiTabItemFlags_Unsorted, window);
+ }
+
+ DockNodeUpdateVisibleFlag(node);
+
+ // Update this without waiting for the next time we Begin() in the window, so our host window will have the proper title bar color on its first frame.
+ if (node->HostWindow)
+ UpdateWindowParentAndRootLinks(window, window->Flags | ImGuiWindowFlags_ChildWindow, node->HostWindow);
+}
+
+static void ImGui::DockNodeRemoveWindow(ImGuiDockNode* node, ImGuiWindow* window, ImGuiID save_dock_id)
+{
+ ImGuiContext& g = *GImGui;
+ IM_ASSERT(window->DockNode == node);
+ //IM_ASSERT(window->RootWindowDockTree == node->HostWindow);
+ //IM_ASSERT(window->LastFrameActive < g.FrameCount); // We may call this from Begin()
+ IM_ASSERT(save_dock_id == 0 || save_dock_id == node->ID);
+ IMGUI_DEBUG_LOG_DOCKING("[docking] DockNodeRemoveWindow node 0x%08X window '%s'\n", node->ID, window->Name);
+
+ window->DockNode = NULL;
+ window->DockIsActive = window->DockTabWantClose = false;
+ window->DockId = save_dock_id;
+ window->Flags &= ~ImGuiWindowFlags_ChildWindow;
+ if (window->ParentWindow)
+ window->ParentWindow->DC.ChildWindows.find_erase(window);
+ UpdateWindowParentAndRootLinks(window, window->Flags, NULL); // Update immediately
+
+ if (node->HostWindow && node->HostWindow->ViewportOwned)
+ {
+ // When undocking from a user interaction this will always run in NewFrame() and have not much effect.
+ // But mid-frame, if we clear viewport we need to mark window as hidden as well.
+ window->Viewport = NULL;
+ window->ViewportId = 0;
+ window->ViewportOwned = false;
+ window->Hidden = true;
+ }
+
+ // Remove window
+ bool erased = false;
+ for (int n = 0; n < node->Windows.Size; n++)
+ if (node->Windows[n] == window)
+ {
+ node->Windows.erase(node->Windows.Data + n);
+ erased = true;
+ break;
+ }
+ if (!erased)
+ IM_ASSERT(erased);
+ if (node->VisibleWindow == window)
+ node->VisibleWindow = NULL;
+
+ // Remove tab and possibly tab bar
+ node->WantHiddenTabBarUpdate = true;
+ if (node->TabBar)
+ {
+ TabBarRemoveTab(node->TabBar, window->TabId);
+ const int tab_count_threshold_for_tab_bar = node->IsCentralNode() ? 1 : 2;
+ if (node->Windows.Size < tab_count_threshold_for_tab_bar)
+ DockNodeRemoveTabBar(node);
+ }
+
+ if (node->Windows.Size == 0 && !node->IsCentralNode() && !node->IsDockSpace() && window->DockId != node->ID)
+ {
+ // Automatic dock node delete themselves if they are not holding at least one tab
+ DockContextRemoveNode(&g, node, true);
+ return;
+ }
+
+ if (node->Windows.Size == 1 && !node->IsCentralNode() && node->HostWindow)
+ {
+ ImGuiWindow* remaining_window = node->Windows[0];
+ // Note: we used to transport viewport ownership here.
+ remaining_window->Collapsed = node->HostWindow->Collapsed;
+ }
+
+ // Update visibility immediately is required so the DockNodeUpdateRemoveInactiveChilds() processing can reflect changes up the tree
+ DockNodeUpdateVisibleFlag(node);
+}
+
+static void ImGui::DockNodeMoveChildNodes(ImGuiDockNode* dst_node, ImGuiDockNode* src_node)
+{
+ IM_ASSERT(dst_node->Windows.Size == 0);
+ dst_node->ChildNodes[0] = src_node->ChildNodes[0];
+ dst_node->ChildNodes[1] = src_node->ChildNodes[1];
+ if (dst_node->ChildNodes[0])
+ dst_node->ChildNodes[0]->ParentNode = dst_node;
+ if (dst_node->ChildNodes[1])
+ dst_node->ChildNodes[1]->ParentNode = dst_node;
+ dst_node->SplitAxis = src_node->SplitAxis;
+ dst_node->SizeRef = src_node->SizeRef;
+ src_node->ChildNodes[0] = src_node->ChildNodes[1] = NULL;
+}
+
+static void ImGui::DockNodeMoveWindows(ImGuiDockNode* dst_node, ImGuiDockNode* src_node)
+{
+ // Insert tabs in the same orders as currently ordered (node->Windows isn't ordered)
+ IM_ASSERT(src_node && dst_node && dst_node != src_node);
+ ImGuiTabBar* src_tab_bar = src_node->TabBar;
+ if (src_tab_bar != NULL)
+ IM_ASSERT(src_node->Windows.Size <= src_node->TabBar->Tabs.Size);
+
+ // If the dst_node is empty we can just move the entire tab bar (to preserve selection, scrolling, etc.)
+ bool move_tab_bar = (src_tab_bar != NULL) && (dst_node->TabBar == NULL);
+ if (move_tab_bar)
+ {
+ dst_node->TabBar = src_node->TabBar;
+ src_node->TabBar = NULL;
+ }
+
+ // Tab order is not important here, it is preserved by sorting in DockNodeUpdateTabBar().
+ for (ImGuiWindow* window : src_node->Windows)
+ {
+ window->DockNode = NULL;
+ window->DockIsActive = false;
+ DockNodeAddWindow(dst_node, window, !move_tab_bar);
+ }
+ src_node->Windows.clear();
+
+ if (!move_tab_bar && src_node->TabBar)
+ {
+ if (dst_node->TabBar)
+ dst_node->TabBar->SelectedTabId = src_node->TabBar->SelectedTabId;
+ DockNodeRemoveTabBar(src_node);
+ }
+}
+
+static void ImGui::DockNodeApplyPosSizeToWindows(ImGuiDockNode* node)
+{
+ for (ImGuiWindow* window : node->Windows)
+ {
+ SetWindowPos(window, node->Pos, ImGuiCond_Always); // We don't assign directly to Pos because it can break the calculation of SizeContents on next frame
+ SetWindowSize(window, node->Size, ImGuiCond_Always);
+ }
+}
+
+static void ImGui::DockNodeHideHostWindow(ImGuiDockNode* node)
+{
+ if (node->HostWindow)
+ {
+ if (node->HostWindow->DockNodeAsHost == node)
+ node->HostWindow->DockNodeAsHost = NULL;
+ node->HostWindow = NULL;
+ }
+
+ if (node->Windows.Size == 1)
+ {
+ node->VisibleWindow = node->Windows[0];
+ node->Windows[0]->DockIsActive = false;
+ }
+
+ if (node->TabBar)
+ DockNodeRemoveTabBar(node);
+}
+
+// Search function called once by root node in DockNodeUpdate()
+struct ImGuiDockNodeTreeInfo
+{
+ ImGuiDockNode* CentralNode;
+ ImGuiDockNode* FirstNodeWithWindows;
+ int CountNodesWithWindows;
+ //ImGuiWindowClass WindowClassForMerges;
+
+ ImGuiDockNodeTreeInfo() { memset(this, 0, sizeof(*this)); }
+};
+
+static void DockNodeFindInfo(ImGuiDockNode* node, ImGuiDockNodeTreeInfo* info)
+{
+ if (node->Windows.Size > 0)
+ {
+ if (info->FirstNodeWithWindows == NULL)
+ info->FirstNodeWithWindows = node;
+ info->CountNodesWithWindows++;
+ }
+ if (node->IsCentralNode())
+ {
+ IM_ASSERT(info->CentralNode == NULL); // Should be only one
+ IM_ASSERT(node->IsLeafNode() && "If you get this assert: please submit .ini file + repro of actions leading to this.");
+ info->CentralNode = node;
+ }
+ if (info->CountNodesWithWindows > 1 && info->CentralNode != NULL)
+ return;
+ if (node->ChildNodes[0])
+ DockNodeFindInfo(node->ChildNodes[0], info);
+ if (node->ChildNodes[1])
+ DockNodeFindInfo(node->ChildNodes[1], info);
+}
+
+static ImGuiWindow* ImGui::DockNodeFindWindowByID(ImGuiDockNode* node, ImGuiID id)
+{
+ IM_ASSERT(id != 0);
+ for (ImGuiWindow* window : node->Windows)
+ if (window->ID == id)
+ return window;
+ return NULL;
+}
+
+// - Remove inactive windows/nodes.
+// - Update visibility flag.
+static void ImGui::DockNodeUpdateFlagsAndCollapse(ImGuiDockNode* node)
+{
+ ImGuiContext& g = *GImGui;
+ IM_ASSERT(node->ParentNode == NULL || node->ParentNode->ChildNodes[0] == node || node->ParentNode->ChildNodes[1] == node);
+
+ // Inherit most flags
+ if (node->ParentNode)
+ node->SharedFlags = node->ParentNode->SharedFlags & ImGuiDockNodeFlags_SharedFlagsInheritMask_;
+
+ // Recurse into children
+ // There is the possibility that one of our child becoming empty will delete itself and moving its sibling contents into 'node'.
+ // If 'node->ChildNode[0]' delete itself, then 'node->ChildNode[1]->Windows' will be moved into 'node'
+ // If 'node->ChildNode[1]' delete itself, then 'node->ChildNode[0]->Windows' will be moved into 'node' and the "remove inactive windows" loop will have run twice on those windows (harmless)
+ node->HasCentralNodeChild = false;
+ if (node->ChildNodes[0])
+ DockNodeUpdateFlagsAndCollapse(node->ChildNodes[0]);
+ if (node->ChildNodes[1])
+ DockNodeUpdateFlagsAndCollapse(node->ChildNodes[1]);
+
+ // Remove inactive windows, collapse nodes
+ // Merge node flags overrides stored in windows
+ node->LocalFlagsInWindows = ImGuiDockNodeFlags_None;
+ for (int window_n = 0; window_n < node->Windows.Size; window_n++)
+ {
+ ImGuiWindow* window = node->Windows[window_n];
+ IM_ASSERT(window->DockNode == node);
+
+ bool node_was_active = (node->LastFrameActive + 1 == g.FrameCount);
+ bool remove = false;
+ remove |= node_was_active && (window->LastFrameActive + 1 < g.FrameCount);
+ remove |= node_was_active && (node->WantCloseAll || node->WantCloseTabId == window->TabId) && window->HasCloseButton && !(window->Flags & ImGuiWindowFlags_UnsavedDocument); // Submit all _expected_ closure from last frame
+ remove |= (window->DockTabWantClose);
+ if (remove)
+ {
+ window->DockTabWantClose = false;
+ if (node->Windows.Size == 1 && !node->IsCentralNode())
+ {
+ DockNodeHideHostWindow(node);
+ node->State = ImGuiDockNodeState_HostWindowHiddenBecauseSingleWindow;
+ DockNodeRemoveWindow(node, window, node->ID); // Will delete the node so it'll be invalid on return
+ return;
+ }
+ DockNodeRemoveWindow(node, window, node->ID);
+ window_n--;
+ continue;
+ }
+
+ // FIXME-DOCKING: Missing policies for conflict resolution, hence the "Experimental" tag on this.
+ //node->LocalFlagsInWindow &= ~window->WindowClass.DockNodeFlagsOverrideClear;
+ node->LocalFlagsInWindows |= window->WindowClass.DockNodeFlagsOverrideSet;
+ }
+ node->UpdateMergedFlags();
+
+ // Auto-hide tab bar option
+ ImGuiDockNodeFlags node_flags = node->MergedFlags;
+ if (node->WantHiddenTabBarUpdate && node->Windows.Size == 1 && (node_flags & ImGuiDockNodeFlags_AutoHideTabBar) && !node->IsHiddenTabBar())
+ node->WantHiddenTabBarToggle = true;
+ node->WantHiddenTabBarUpdate = false;
+
+ // Cancel toggling if we know our tab bar is enforced to be hidden at all times
+ if (node->WantHiddenTabBarToggle && node->VisibleWindow && (node->VisibleWindow->WindowClass.DockNodeFlagsOverrideSet & ImGuiDockNodeFlags_HiddenTabBar))
+ node->WantHiddenTabBarToggle = false;
+
+ // Apply toggles at a single point of the frame (here!)
+ if (node->Windows.Size > 1)
+ node->SetLocalFlags(node->LocalFlags & ~ImGuiDockNodeFlags_HiddenTabBar);
+ else if (node->WantHiddenTabBarToggle)
+ node->SetLocalFlags(node->LocalFlags ^ ImGuiDockNodeFlags_HiddenTabBar);
+ node->WantHiddenTabBarToggle = false;
+
+ DockNodeUpdateVisibleFlag(node);
+}
+
+// This is rarely called as DockNodeUpdateForRootNode() generally does it most frames.
+static void ImGui::DockNodeUpdateHasCentralNodeChild(ImGuiDockNode* node)
+{
+ node->HasCentralNodeChild = false;
+ if (node->ChildNodes[0])
+ DockNodeUpdateHasCentralNodeChild(node->ChildNodes[0]);
+ if (node->ChildNodes[1])
+ DockNodeUpdateHasCentralNodeChild(node->ChildNodes[1]);
+ if (node->IsRootNode())
+ {
+ ImGuiDockNode* mark_node = node->CentralNode;
+ while (mark_node)
+ {
+ mark_node->HasCentralNodeChild = true;
+ mark_node = mark_node->ParentNode;
+ }
+ }
+}
+
+static void ImGui::DockNodeUpdateVisibleFlag(ImGuiDockNode* node)
+{
+ // Update visibility flag
+ bool is_visible = (node->ParentNode == NULL) ? node->IsDockSpace() : node->IsCentralNode();
+ is_visible |= (node->Windows.Size > 0);
+ is_visible |= (node->ChildNodes[0] && node->ChildNodes[0]->IsVisible);
+ is_visible |= (node->ChildNodes[1] && node->ChildNodes[1]->IsVisible);
+ node->IsVisible = is_visible;
+}
+
+static void ImGui::DockNodeStartMouseMovingWindow(ImGuiDockNode* node, ImGuiWindow* window)
+{
+ ImGuiContext& g = *GImGui;
+ IM_ASSERT(node->WantMouseMove == true);
+ StartMouseMovingWindow(window);
+ g.ActiveIdClickOffset = g.IO.MouseClickedPos[0] - node->Pos;
+ g.MovingWindow = window; // If we are docked into a non moveable root window, StartMouseMovingWindow() won't set g.MovingWindow. Override that decision.
+ node->WantMouseMove = false;
+}
+
+// Update CentralNode, OnlyNodeWithWindows, LastFocusedNodeID. Copy window class.
+static void ImGui::DockNodeUpdateForRootNode(ImGuiDockNode* node)
+{
+ DockNodeUpdateFlagsAndCollapse(node);
+
+ // - Setup central node pointers
+ // - Find if there's only a single visible window in the hierarchy (in which case we need to display a regular title bar -> FIXME-DOCK: that last part is not done yet!)
+ // Cannot merge this with DockNodeUpdateFlagsAndCollapse() because FirstNodeWithWindows is found after window removal and child collapsing
+ ImGuiDockNodeTreeInfo info;
+ DockNodeFindInfo(node, &info);
+ node->CentralNode = info.CentralNode;
+ node->OnlyNodeWithWindows = (info.CountNodesWithWindows == 1) ? info.FirstNodeWithWindows : NULL;
+ node->CountNodeWithWindows = info.CountNodesWithWindows;
+ if (node->LastFocusedNodeId == 0 && info.FirstNodeWithWindows != NULL)
+ node->LastFocusedNodeId = info.FirstNodeWithWindows->ID;
+
+ // Copy the window class from of our first window so it can be used for proper dock filtering.
+ // When node has mixed windows, prioritize the class with the most constraint (DockingAllowUnclassed = false) as the reference to copy.
+ // FIXME-DOCK: We don't recurse properly, this code could be reworked to work from DockNodeUpdateScanRec.
+ if (ImGuiDockNode* first_node_with_windows = info.FirstNodeWithWindows)
+ {
+ node->WindowClass = first_node_with_windows->Windows[0]->WindowClass;
+ for (int n = 1; n < first_node_with_windows->Windows.Size; n++)
+ if (first_node_with_windows->Windows[n]->WindowClass.DockingAllowUnclassed == false)
+ {
+ node->WindowClass = first_node_with_windows->Windows[n]->WindowClass;
+ break;
+ }
+ }
+
+ ImGuiDockNode* mark_node = node->CentralNode;
+ while (mark_node)
+ {
+ mark_node->HasCentralNodeChild = true;
+ mark_node = mark_node->ParentNode;
+ }
+}
+
+static void DockNodeSetupHostWindow(ImGuiDockNode* node, ImGuiWindow* host_window)
+{
+ // Remove ourselves from any previous different host window
+ // This can happen if a user mistakenly does (see #4295 for details):
+ // - N+0: DockBuilderAddNode(id, 0) // missing ImGuiDockNodeFlags_DockSpace
+ // - N+1: NewFrame() // will create floating host window for that node
+ // - N+1: DockSpace(id) // requalify node as dockspace, moving host window
+ if (node->HostWindow && node->HostWindow != host_window && node->HostWindow->DockNodeAsHost == node)
+ node->HostWindow->DockNodeAsHost = NULL;
+
+ host_window->DockNodeAsHost = node;
+ node->HostWindow = host_window;
+}
+
+static void ImGui::DockNodeUpdate(ImGuiDockNode* node)
+{
+ ImGuiContext& g = *GImGui;
+ IM_ASSERT(node->LastFrameActive != g.FrameCount);
+ node->LastFrameAlive = g.FrameCount;
+ node->IsBgDrawnThisFrame = false;
+
+ node->CentralNode = node->OnlyNodeWithWindows = NULL;
+ if (node->IsRootNode())
+ DockNodeUpdateForRootNode(node);
+
+ // Remove tab bar if not needed
+ if (node->TabBar && node->IsNoTabBar())
+ DockNodeRemoveTabBar(node);
+
+ // Early out for hidden root dock nodes (when all DockId references are in inactive windows, or there is only 1 floating window holding on the DockId)
+ bool want_to_hide_host_window = false;
+ if (node->IsFloatingNode())
+ {
+ if (node->Windows.Size <= 1 && node->IsLeafNode())
+ if (!g.IO.ConfigDockingAlwaysTabBar && (node->Windows.Size == 0 || !node->Windows[0]->WindowClass.DockingAlwaysTabBar))
+ want_to_hide_host_window = true;
+ if (node->CountNodeWithWindows == 0)
+ want_to_hide_host_window = true;
+ }
+ if (want_to_hide_host_window)
+ {
+ if (node->Windows.Size == 1)
+ {
+ // Floating window pos/size is authoritative
+ ImGuiWindow* single_window = node->Windows[0];
+ node->Pos = single_window->Pos;
+ node->Size = single_window->SizeFull;
+ node->AuthorityForPos = node->AuthorityForSize = node->AuthorityForViewport = ImGuiDataAuthority_Window;
+
+ // Transfer focus immediately so when we revert to a regular window it is immediately selected
+ if (node->HostWindow && g.NavWindow == node->HostWindow)
+ FocusWindow(single_window);
+ if (node->HostWindow)
+ {
+ IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Node %08X transfer Viewport %08X->%08X to Window '%s'\n", node->ID, node->HostWindow->Viewport->ID, single_window->ID, single_window->Name);
+ single_window->Viewport = node->HostWindow->Viewport;
+ single_window->ViewportId = node->HostWindow->ViewportId;
+ if (node->HostWindow->ViewportOwned)
+ {
+ single_window->Viewport->ID = single_window->ID;
+ single_window->Viewport->Window = single_window;
+ single_window->ViewportOwned = true;
+ }
+ }
+ node->RefViewportId = single_window->ViewportId;
+ }
+
+ DockNodeHideHostWindow(node);
+ node->State = ImGuiDockNodeState_HostWindowHiddenBecauseSingleWindow;
+ node->WantCloseAll = false;
+ node->WantCloseTabId = 0;
+ node->HasCloseButton = node->HasWindowMenuButton = false;
+ node->LastFrameActive = g.FrameCount;
+
+ if (node->WantMouseMove && node->Windows.Size == 1)
+ DockNodeStartMouseMovingWindow(node, node->Windows[0]);
+ return;
+ }
+
+ // In some circumstance we will defer creating the host window (so everything will be kept hidden),
+ // while the expected visible window is resizing itself.
+ // This is important for first-time (no ini settings restored) single window when io.ConfigDockingAlwaysTabBar is enabled,
+ // otherwise the node ends up using the minimum window size. Effectively those windows will take an extra frame to show up:
+ // N+0: Begin(): window created (with no known size), node is created
+ // N+1: DockNodeUpdate(): node skip creating host window / Begin(): window size applied, not visible
+ // N+2: DockNodeUpdate(): node can create host window / Begin(): window becomes visible
+ // We could remove this frame if we could reliably calculate the expected window size during node update, before the Begin() code.
+ // It would require a generalization of CalcWindowExpectedSize(), probably extracting code away from Begin().
+ // In reality it isn't very important as user quickly ends up with size data in .ini file.
+ if (node->IsVisible && node->HostWindow == NULL && node->IsFloatingNode() && node->IsLeafNode())
+ {
+ IM_ASSERT(node->Windows.Size > 0);
+ ImGuiWindow* ref_window = NULL;
+ if (node->SelectedTabId != 0) // Note that we prune single-window-node settings on .ini loading, so this is generally 0 for them!
+ ref_window = DockNodeFindWindowByID(node, node->SelectedTabId);
+ if (ref_window == NULL)
+ ref_window = node->Windows[0];
+ if (ref_window->AutoFitFramesX > 0 || ref_window->AutoFitFramesY > 0)
+ {
+ node->State = ImGuiDockNodeState_HostWindowHiddenBecauseWindowsAreResizing;
+ return;
+ }
+ }
+
+ const ImGuiDockNodeFlags node_flags = node->MergedFlags;
+
+ // Decide if the node will have a close button and a window menu button
+ node->HasWindowMenuButton = (node->Windows.Size > 0) && (node_flags & ImGuiDockNodeFlags_NoWindowMenuButton) == 0;
+ node->HasCloseButton = false;
+ for (ImGuiWindow* window : node->Windows)
+ {
+ // FIXME-DOCK: Setting DockIsActive here means that for single active window in a leaf node, DockIsActive will be cleared until the next Begin() call.
+ node->HasCloseButton |= window->HasCloseButton;
+ window->DockIsActive = (node->Windows.Size > 1);
+ }
+ if (node_flags & ImGuiDockNodeFlags_NoCloseButton)
+ node->HasCloseButton = false;
+
+ // Bind or create host window
+ ImGuiWindow* host_window = NULL;
+ bool beginned_into_host_window = false;
+ if (node->IsDockSpace())
+ {
+ // [Explicit root dockspace node]
+ IM_ASSERT(node->HostWindow);
+ host_window = node->HostWindow;
+ }
+ else
+ {
+ // [Automatic root or child nodes]
+ if (node->IsRootNode() && node->IsVisible)
+ {
+ ImGuiWindow* ref_window = (node->Windows.Size > 0) ? node->Windows[0] : NULL;
+
+ // Sync Pos
+ if (node->AuthorityForPos == ImGuiDataAuthority_Window && ref_window)
+ SetNextWindowPos(ref_window->Pos);
+ else if (node->AuthorityForPos == ImGuiDataAuthority_DockNode)
+ SetNextWindowPos(node->Pos);
+
+ // Sync Size
+ if (node->AuthorityForSize == ImGuiDataAuthority_Window && ref_window)
+ SetNextWindowSize(ref_window->SizeFull);
+ else if (node->AuthorityForSize == ImGuiDataAuthority_DockNode)
+ SetNextWindowSize(node->Size);
+
+ // Sync Collapsed
+ if (node->AuthorityForSize == ImGuiDataAuthority_Window && ref_window)
+ SetNextWindowCollapsed(ref_window->Collapsed);
+
+ // Sync Viewport
+ if (node->AuthorityForViewport == ImGuiDataAuthority_Window && ref_window)
+ SetNextWindowViewport(ref_window->ViewportId);
+ else if (node->AuthorityForViewport == ImGuiDataAuthority_Window && node->RefViewportId != 0)
+ SetNextWindowViewport(node->RefViewportId);
+
+ SetNextWindowClass(&node->WindowClass);
+
+ // Begin into the host window
+ char window_label[20];
+ DockNodeGetHostWindowTitle(node, window_label, IM_ARRAYSIZE(window_label));
+ ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_DockNodeHost;
+ window_flags |= ImGuiWindowFlags_NoFocusOnAppearing;
+ window_flags |= ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoCollapse;
+ window_flags |= ImGuiWindowFlags_NoTitleBar;
+
+ SetNextWindowBgAlpha(0.0f); // Don't set ImGuiWindowFlags_NoBackground because it disables borders
+ PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
+ Begin(window_label, NULL, window_flags);
+ PopStyleVar();
+ beginned_into_host_window = true;
+
+ host_window = g.CurrentWindow;
+ DockNodeSetupHostWindow(node, host_window);
+ host_window->DC.CursorPos = host_window->Pos;
+ node->Pos = host_window->Pos;
+ node->Size = host_window->Size;
+
+ // We set ImGuiWindowFlags_NoFocusOnAppearing because we don't want the host window to take full focus (e.g. steal NavWindow)
+ // But we still it bring it to the front of display. There's no way to choose this precise behavior via window flags.
+ // One simple case to ponder if: window A has a toggle to create windows B/C/D. Dock B/C/D together, clear the toggle and enable it again.
+ // When reappearing B/C/D will request focus and be moved to the top of the display pile, but they are not linked to the dock host window
+ // during the frame they appear. The dock host window would keep its old display order, and the sorting in EndFrame would move B/C/D back
+ // after the dock host window, losing their top-most status.
+ if (node->HostWindow->Appearing)
+ BringWindowToDisplayFront(node->HostWindow);
+
+ node->AuthorityForPos = node->AuthorityForSize = node->AuthorityForViewport = ImGuiDataAuthority_Auto;
+ }
+ else if (node->ParentNode)
+ {
+ node->HostWindow = host_window = node->ParentNode->HostWindow;
+ node->AuthorityForPos = node->AuthorityForSize = node->AuthorityForViewport = ImGuiDataAuthority_Auto;
+ }
+ if (node->WantMouseMove && node->HostWindow)
+ DockNodeStartMouseMovingWindow(node, node->HostWindow);
+ }
+ node->RefViewportId = 0; // Clear when we have a host window
+
+ // Update focused node (the one whose title bar is highlight) within a node tree
+ if (node->IsSplitNode())
+ IM_ASSERT(node->TabBar == NULL);
+ if (node->IsRootNode())
+ if (ImGuiWindow* p_window = g.NavWindow ? g.NavWindow->RootWindow : NULL)
+ while (p_window != NULL && p_window->DockNode != NULL)
+ {
+ ImGuiDockNode* p_node = DockNodeGetRootNode(p_window->DockNode);
+ if (p_node == node)
+ {
+ node->LastFocusedNodeId = p_window->DockNode->ID; // Note: not using root node ID!
+ break;
+ }
+ p_window = p_node->HostWindow ? p_node->HostWindow->RootWindow : NULL;
+ }
+
+ // Register a hit-test hole in the window unless we are currently dragging a window that is compatible with our dockspace
+ ImGuiDockNode* central_node = node->CentralNode;
+ const bool central_node_hole = node->IsRootNode() && host_window && (node_flags & ImGuiDockNodeFlags_PassthruCentralNode) != 0 && central_node != NULL && central_node->IsEmpty();
+ bool central_node_hole_register_hit_test_hole = central_node_hole;
+ if (central_node_hole)
+ if (const ImGuiPayload* payload = ImGui::GetDragDropPayload())
+ if (payload->IsDataType(IMGUI_PAYLOAD_TYPE_WINDOW) && DockNodeIsDropAllowed(host_window, *(ImGuiWindow**)payload->Data))
+ central_node_hole_register_hit_test_hole = false;
+ if (central_node_hole_register_hit_test_hole)
+ {
+ // We add a little padding to match the "resize from edges" behavior and allow grabbing the splitter easily.
+ // (But we only add it if there's something else on the other side of the hole, otherwise for e.g. fullscreen
+ // covering passthru node we'd have a gap on the edge not covered by the hole)
+ IM_ASSERT(node->IsDockSpace()); // We cannot pass this flag without the DockSpace() api. Testing this because we also setup the hole in host_window->ParentNode
+ ImGuiDockNode* root_node = DockNodeGetRootNode(central_node);
+ ImRect root_rect(root_node->Pos, root_node->Pos + root_node->Size);
+ ImRect hole_rect(central_node->Pos, central_node->Pos + central_node->Size);
+ if (hole_rect.Min.x > root_rect.Min.x) { hole_rect.Min.x += WINDOWS_HOVER_PADDING; }
+ if (hole_rect.Max.x < root_rect.Max.x) { hole_rect.Max.x -= WINDOWS_HOVER_PADDING; }
+ if (hole_rect.Min.y > root_rect.Min.y) { hole_rect.Min.y += WINDOWS_HOVER_PADDING; }
+ if (hole_rect.Max.y < root_rect.Max.y) { hole_rect.Max.y -= WINDOWS_HOVER_PADDING; }
+ //GetForegroundDrawList()->AddRect(hole_rect.Min, hole_rect.Max, IM_COL32(255, 0, 0, 255));
+ if (central_node_hole && !hole_rect.IsInverted())
+ {
+ SetWindowHitTestHole(host_window, hole_rect.Min, hole_rect.Max - hole_rect.Min);
+ if (host_window->ParentWindow)
+ SetWindowHitTestHole(host_window->ParentWindow, hole_rect.Min, hole_rect.Max - hole_rect.Min);
+ }
+ }
+
+ // Update position/size, process and draw resizing splitters
+ if (node->IsRootNode() && host_window)
+ {
+ DockNodeTreeUpdatePosSize(node, host_window->Pos, host_window->Size);
+ PushStyleColor(ImGuiCol_Separator, g.Style.Colors[ImGuiCol_Border]);
+ PushStyleColor(ImGuiCol_SeparatorActive, g.Style.Colors[ImGuiCol_ResizeGripActive]);
+ PushStyleColor(ImGuiCol_SeparatorHovered, g.Style.Colors[ImGuiCol_ResizeGripHovered]);
+ DockNodeTreeUpdateSplitter(node);
+ PopStyleColor(3);
+ }
+
+ // Draw empty node background (currently can only be the Central Node)
+ if (host_window && node->IsEmpty() && node->IsVisible)
+ {
+ host_window->DrawList->ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_BG);
+ node->LastBgColor = (node_flags & ImGuiDockNodeFlags_PassthruCentralNode) ? 0 : GetColorU32(ImGuiCol_DockingEmptyBg);
+ if (node->LastBgColor != 0)
+ host_window->DrawList->AddRectFilled(node->Pos, node->Pos + node->Size, node->LastBgColor);
+ node->IsBgDrawnThisFrame = true;
+ }
+
+ // Draw whole dockspace background if ImGuiDockNodeFlags_PassthruCentralNode if set.
+ // We need to draw a background at the root level if requested by ImGuiDockNodeFlags_PassthruCentralNode, but we will only know the correct pos/size
+ // _after_ processing the resizing splitters. So we are using the DrawList channel splitting facility to submit drawing primitives out of order!
+ const bool render_dockspace_bg = node->IsRootNode() && host_window && (node_flags & ImGuiDockNodeFlags_PassthruCentralNode) != 0;
+ if (render_dockspace_bg && node->IsVisible)
+ {
+ host_window->DrawList->ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_BG);
+ if (central_node_hole)
+ RenderRectFilledWithHole(host_window->DrawList, node->Rect(), central_node->Rect(), GetColorU32(ImGuiCol_WindowBg), 0.0f);
+ else
+ host_window->DrawList->AddRectFilled(node->Pos, node->Pos + node->Size, GetColorU32(ImGuiCol_WindowBg), 0.0f);
+ }
+
+ // Draw and populate Tab Bar
+ if (host_window)
+ host_window->DrawList->ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_FG);
+ if (host_window && node->Windows.Size > 0)
+ {
+ DockNodeUpdateTabBar(node, host_window);
+ }
+ else
+ {
+ node->WantCloseAll = false;
+ node->WantCloseTabId = 0;
+ node->IsFocused = false;
+ }
+ if (node->TabBar && node->TabBar->SelectedTabId)
+ node->SelectedTabId = node->TabBar->SelectedTabId;
+ else if (node->Windows.Size > 0)
+ node->SelectedTabId = node->Windows[0]->TabId;
+
+ // Draw payload drop target
+ if (host_window && node->IsVisible)
+ if (node->IsRootNode() && (g.MovingWindow == NULL || g.MovingWindow->RootWindowDockTree != host_window))
+ BeginDockableDragDropTarget(host_window);
+
+ // We update this after DockNodeUpdateTabBar()
+ node->LastFrameActive = g.FrameCount;
+
+ // Recurse into children
+ // FIXME-DOCK FIXME-OPT: Should not need to recurse into children
+ if (host_window)
+ {
+ if (node->ChildNodes[0])
+ DockNodeUpdate(node->ChildNodes[0]);
+ if (node->ChildNodes[1])
+ DockNodeUpdate(node->ChildNodes[1]);
+
+ // Render outer borders last (after the tab bar)
+ if (node->IsRootNode())
+ RenderWindowOuterBorders(host_window);
+ }
+
+ // End host window
+ if (beginned_into_host_window) //-V1020
+ End();
+}
+
+// Compare TabItem nodes given the last known DockOrder (will persist in .ini file as hint), used to sort tabs when multiple tabs are added on the same frame.
+static int IMGUI_CDECL TabItemComparerByDockOrder(const void* lhs, const void* rhs)
+{
+ ImGuiWindow* a = ((const ImGuiTabItem*)lhs)->Window;
+ ImGuiWindow* b = ((const ImGuiTabItem*)rhs)->Window;
+ if (int d = ((a->DockOrder == -1) ? INT_MAX : a->DockOrder) - ((b->DockOrder == -1) ? INT_MAX : b->DockOrder))
+ return d;
+ return (a->BeginOrderWithinContext - b->BeginOrderWithinContext);
+}
+
+// Default handler for g.DockNodeWindowMenuHandler(): display the list of windows for a given dock-node.
+// This is exceptionally stored in a function pointer to also user applications to tweak this menu (undocumented)
+// Custom overrides may want to decorate, group, sort entries.
+// Please note those are internal structures: if you copy this expect occasional breakage.
+// (if you don't need to modify the "Tabs.Size == 1" behavior/path it is recommend you call this function in your handler)
+void ImGui::DockNodeWindowMenuHandler_Default(ImGuiContext* ctx, ImGuiDockNode* node, ImGuiTabBar* tab_bar)
+{
+ IM_UNUSED(ctx);
+ if (tab_bar->Tabs.Size == 1)
+ {
+ // "Hide tab bar" option. Being one of our rare user-facing string we pull it from a table.
+ if (MenuItem(LocalizeGetMsg(ImGuiLocKey_DockingHideTabBar), NULL, node->IsHiddenTabBar()))
+ node->WantHiddenTabBarToggle = true;
+ }
+ else
+ {
+ // Display a selectable list of windows in this docking node
+ for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
+ {
+ ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
+ if (tab->Flags & ImGuiTabItemFlags_Button)
+ continue;
+ if (Selectable(TabBarGetTabName(tab_bar, tab), tab->ID == tab_bar->SelectedTabId))
+ TabBarQueueFocus(tab_bar, tab);
+ SameLine();
+ Text(" ");
+ }
+ }
+}
+
+static void ImGui::DockNodeWindowMenuUpdate(ImGuiDockNode* node, ImGuiTabBar* tab_bar)
+{
+ // Try to position the menu so it is more likely to stays within the same viewport
+ ImGuiContext& g = *GImGui;
+ if (g.Style.WindowMenuButtonPosition == ImGuiDir_Left)
+ SetNextWindowPos(ImVec2(node->Pos.x, node->Pos.y + GetFrameHeight()), ImGuiCond_Always, ImVec2(0.0f, 0.0f));
+ else
+ SetNextWindowPos(ImVec2(node->Pos.x + node->Size.x, node->Pos.y + GetFrameHeight()), ImGuiCond_Always, ImVec2(1.0f, 0.0f));
+ if (BeginPopup("#WindowMenu"))
+ {
+ node->IsFocused = true;
+ g.DockNodeWindowMenuHandler(&g, node, tab_bar);
+ EndPopup();
+ }
+}
+
+// User helper to append/amend into a dock node tab bar. Most commonly used to add e.g. a "+" button.
+bool ImGui::DockNodeBeginAmendTabBar(ImGuiDockNode* node)
+{
+ if (node->TabBar == NULL || node->HostWindow == NULL)
+ return false;
+ if (node->MergedFlags & ImGuiDockNodeFlags_KeepAliveOnly)
+ return false;
+ if (node->TabBar->ID == 0)
+ return false;
+ Begin(node->HostWindow->Name);
+ PushOverrideID(node->ID);
+ bool ret = BeginTabBarEx(node->TabBar, node->TabBar->BarRect, node->TabBar->Flags);
+ IM_UNUSED(ret);
+ IM_ASSERT(ret);
+ return true;
+}
+
+void ImGui::DockNodeEndAmendTabBar()
+{
+ EndTabBar();
+ PopID();
+ End();
+}
+
+static bool IsDockNodeTitleBarHighlighted(ImGuiDockNode* node, ImGuiDockNode* root_node)
+{
+ // CTRL+Tab highlight (only highlighting leaf node, not whole hierarchy)
+ ImGuiContext& g = *GImGui;
+ if (g.NavWindowingTarget)
+ return (g.NavWindowingTarget->DockNode == node);
+
+ // FIXME-DOCKING: May want alternative to treat central node void differently? e.g. if (g.NavWindow == host_window)
+ if (g.NavWindow && root_node->LastFocusedNodeId == node->ID)
+ {
+ // FIXME: This could all be backed in RootWindowForTitleBarHighlight? Probably need to reorganize for both dock nodes + other RootWindowForTitleBarHighlight users (not-node)
+ ImGuiWindow* parent_window = g.NavWindow->RootWindow;
+ while (parent_window->Flags & ImGuiWindowFlags_ChildMenu)
+ parent_window = parent_window->ParentWindow->RootWindow;
+ ImGuiDockNode* start_parent_node = parent_window->DockNodeAsHost ? parent_window->DockNodeAsHost : parent_window->DockNode;
+ for (ImGuiDockNode* parent_node = start_parent_node; parent_node != NULL; parent_node = parent_node->HostWindow ? parent_node->HostWindow->RootWindow->DockNode : NULL)
+ if ((parent_node = ImGui::DockNodeGetRootNode(parent_node)) == root_node)
+ return true;
+ }
+ return false;
+}
+
+// Submit the tab bar corresponding to a dock node and various housekeeping details.
+static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_window)
+{
+ ImGuiContext& g = *GImGui;
+ ImGuiStyle& style = g.Style;
+
+ const bool node_was_active = (node->LastFrameActive + 1 == g.FrameCount);
+ const bool closed_all = node->WantCloseAll && node_was_active;
+ const ImGuiID closed_one = node->WantCloseTabId && node_was_active;
+ node->WantCloseAll = false;
+ node->WantCloseTabId = 0;
+
+ // Decide if we should use a focused title bar color
+ bool is_focused = false;
+ ImGuiDockNode* root_node = DockNodeGetRootNode(node);
+ if (IsDockNodeTitleBarHighlighted(node, root_node))
+ is_focused = true;
+
+ // Hidden tab bar will show a triangle on the upper-left (in Begin)
+ if (node->IsHiddenTabBar() || node->IsNoTabBar())
+ {
+ node->VisibleWindow = (node->Windows.Size > 0) ? node->Windows[0] : NULL;
+ node->IsFocused = is_focused;
+ if (is_focused)
+ node->LastFrameFocused = g.FrameCount;
+ if (node->VisibleWindow)
+ {
+ // Notify root of visible window (used to display title in OS task bar)
+ if (is_focused || root_node->VisibleWindow == NULL)
+ root_node->VisibleWindow = node->VisibleWindow;
+ if (node->TabBar)
+ node->TabBar->VisibleTabId = node->VisibleWindow->TabId;
+ }
+ return;
+ }
+
+ // Move ourselves to the Menu layer (so we can be accessed by tapping Alt) + undo SkipItems flag in order to draw over the title bar even if the window is collapsed
+ bool backup_skip_item = host_window->SkipItems;
+ if (!node->IsDockSpace())
+ {
+ host_window->SkipItems = false;
+ host_window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;
+ }
+
+ // Use PushOverrideID() instead of PushID() to use the node id _without_ the host window ID.
+ // This is to facilitate computing those ID from the outside, and will affect more or less only the ID of the collapse button, popup and tabs,
+ // as docked windows themselves will override the stack with their own root ID.
+ PushOverrideID(node->ID);
+ ImGuiTabBar* tab_bar = node->TabBar;
+ bool tab_bar_is_recreated = (tab_bar == NULL); // Tab bar are automatically destroyed when a node gets hidden
+ if (tab_bar == NULL)
+ {
+ DockNodeAddTabBar(node);
+ tab_bar = node->TabBar;
+ }
+
+ ImGuiID focus_tab_id = 0;
+ node->IsFocused = is_focused;
+
+ const ImGuiDockNodeFlags node_flags = node->MergedFlags;
+ const bool has_window_menu_button = (node_flags & ImGuiDockNodeFlags_NoWindowMenuButton) == 0 && (style.WindowMenuButtonPosition != ImGuiDir_None);
+
+ // In a dock node, the Collapse Button turns into the Window Menu button.
+ // FIXME-DOCK FIXME-OPT: Could we recycle popups id across multiple dock nodes?
+ if (has_window_menu_button && IsPopupOpen("#WindowMenu"))
+ {
+ ImGuiID next_selected_tab_id = tab_bar->NextSelectedTabId;
+ DockNodeWindowMenuUpdate(node, tab_bar);
+ if (tab_bar->NextSelectedTabId != 0 && tab_bar->NextSelectedTabId != next_selected_tab_id)
+ focus_tab_id = tab_bar->NextSelectedTabId;
+ is_focused |= node->IsFocused;
+ }
+
+ // Layout
+ ImRect title_bar_rect, tab_bar_rect;
+ ImVec2 window_menu_button_pos;
+ ImVec2 close_button_pos;
+ DockNodeCalcTabBarLayout(node, &title_bar_rect, &tab_bar_rect, &window_menu_button_pos, &close_button_pos);
+
+ // Submit new tabs, they will be added as Unsorted and sorted below based on relative DockOrder value.
+ const int tabs_count_old = tab_bar->Tabs.Size;
+ for (int window_n = 0; window_n < node->Windows.Size; window_n++)
+ {
+ ImGuiWindow* window = node->Windows[window_n];
+ if (TabBarFindTabByID(tab_bar, window->TabId) == NULL)
+ TabBarAddTab(tab_bar, ImGuiTabItemFlags_Unsorted, window);
+ }
+
+ // Title bar
+ if (is_focused)
+ node->LastFrameFocused = g.FrameCount;
+ ImU32 title_bar_col = GetColorU32(host_window->Collapsed ? ImGuiCol_TitleBgCollapsed : is_focused ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg);
+ ImDrawFlags rounding_flags = CalcRoundingFlagsForRectInRect(title_bar_rect, host_window->Rect(), g.Style.DockingSeparatorSize);
+ host_window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, host_window->WindowRounding, rounding_flags);
+
+ // Docking/Collapse button
+ if (has_window_menu_button)
+ {
+ if (CollapseButton(host_window->GetID("#COLLAPSE"), window_menu_button_pos, node)) // == DockNodeGetWindowMenuButtonId(node)
+ OpenPopup("#WindowMenu");
+ if (IsItemActive())
+ focus_tab_id = tab_bar->SelectedTabId;
+ if (IsItemHovered(ImGuiHoveredFlags_ForTooltip | ImGuiHoveredFlags_DelayNormal) && g.HoveredIdTimer > 0.5f)
+ SetTooltip("%s", LocalizeGetMsg(ImGuiLocKey_DockingDragToUndockOrMoveNode));
+ }
+
+ // If multiple tabs are appearing on the same frame, sort them based on their persistent DockOrder value
+ int tabs_unsorted_start = tab_bar->Tabs.Size;
+ for (int tab_n = tab_bar->Tabs.Size - 1; tab_n >= 0 && (tab_bar->Tabs[tab_n].Flags & ImGuiTabItemFlags_Unsorted); tab_n--)
+ {
+ // FIXME-DOCK: Consider only clearing the flag after the tab has been alive for a few consecutive frames, allowing late comers to not break sorting?
+ tab_bar->Tabs[tab_n].Flags &= ~ImGuiTabItemFlags_Unsorted;
+ tabs_unsorted_start = tab_n;
+ }
+ if (tab_bar->Tabs.Size > tabs_unsorted_start)
+ {
+ IMGUI_DEBUG_LOG_DOCKING("[docking] In node 0x%08X: %d new appearing tabs:%s\n", node->ID, tab_bar->Tabs.Size - tabs_unsorted_start, (tab_bar->Tabs.Size > tabs_unsorted_start + 1) ? " (will sort)" : "");
+ for (int tab_n = tabs_unsorted_start; tab_n < tab_bar->Tabs.Size; tab_n++)
+ {
+ ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
+ IMGUI_DEBUG_LOG_DOCKING("[docking] - Tab 0x%08X '%s' Order %d\n", tab->ID, TabBarGetTabName(tab_bar, tab), tab->Window ? tab->Window->DockOrder : -1);
+ }
+ IMGUI_DEBUG_LOG_DOCKING("[docking] SelectedTabId = 0x%08X, NavWindow->TabId = 0x%08X\n", node->SelectedTabId, g.NavWindow ? g.NavWindow->TabId : -1);
+ if (tab_bar->Tabs.Size > tabs_unsorted_start + 1)
+ ImQsort(tab_bar->Tabs.Data + tabs_unsorted_start, tab_bar->Tabs.Size - tabs_unsorted_start, sizeof(ImGuiTabItem), TabItemComparerByDockOrder);
+ }
+
+ // Apply NavWindow focus back to the tab bar
+ if (g.NavWindow && g.NavWindow->RootWindow->DockNode == node)
+ tab_bar->SelectedTabId = g.NavWindow->RootWindow->TabId;
+
+ // Selected newly added tabs, or persistent tab ID if the tab bar was just recreated
+ if (tab_bar_is_recreated && TabBarFindTabByID(tab_bar, node->SelectedTabId) != NULL)
+ tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = node->SelectedTabId;
+ else if (tab_bar->Tabs.Size > tabs_count_old)
+ tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = tab_bar->Tabs.back().Window->TabId;
+
+ // Begin tab bar
+ ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_AutoSelectNewTabs; // | ImGuiTabBarFlags_NoTabListScrollingButtons);
+ tab_bar_flags |= ImGuiTabBarFlags_SaveSettings | ImGuiTabBarFlags_DockNode;// | ImGuiTabBarFlags_FittingPolicyScroll;
+ if (!host_window->Collapsed && is_focused)
+ tab_bar_flags |= ImGuiTabBarFlags_IsFocused;
+ tab_bar->ID = GetID("#TabBar");
+ tab_bar->SeparatorMinX = node->Pos.x + host_window->WindowBorderSize; // Separator cover the whole node width
+ tab_bar->SeparatorMaxX = node->Pos.x + node->Size.x - host_window->WindowBorderSize;
+ BeginTabBarEx(tab_bar, tab_bar_rect, tab_bar_flags);
+ //host_window->DrawList->AddRect(tab_bar_rect.Min, tab_bar_rect.Max, IM_COL32(255,0,255,255));
+
+ // Backup style colors
+ ImVec4 backup_style_cols[ImGuiWindowDockStyleCol_COUNT];
+ for (int color_n = 0; color_n < ImGuiWindowDockStyleCol_COUNT; color_n++)
+ backup_style_cols[color_n] = g.Style.Colors[GWindowDockStyleColors[color_n]];
+
+ // Submit actual tabs
+ node->VisibleWindow = NULL;
+ for (int window_n = 0; window_n < node->Windows.Size; window_n++)
+ {
+ ImGuiWindow* window = node->Windows[window_n];
+ if ((closed_all || closed_one == window->TabId) && window->HasCloseButton && !(window->Flags & ImGuiWindowFlags_UnsavedDocument))
+ continue;
+ if (window->LastFrameActive + 1 >= g.FrameCount || !node_was_active)
+ {
+ ImGuiTabItemFlags tab_item_flags = 0;
+ tab_item_flags |= window->WindowClass.TabItemFlagsOverrideSet;
+ if (window->Flags & ImGuiWindowFlags_UnsavedDocument)
+ tab_item_flags |= ImGuiTabItemFlags_UnsavedDocument;
+ if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)
+ tab_item_flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton;
+
+ // Apply stored style overrides for the window
+ for (int color_n = 0; color_n < ImGuiWindowDockStyleCol_COUNT; color_n++)
+ g.Style.Colors[GWindowDockStyleColors[color_n]] = ColorConvertU32ToFloat4(window->DockStyle.Colors[color_n]);
+
+ // Note that TabItemEx() calls TabBarCalcTabID() so our tab item ID will ignore the current ID stack (rightly so)
+ bool tab_open = true;
+ TabItemEx(tab_bar, window->Name, window->HasCloseButton ? &tab_open : NULL, tab_item_flags, window);
+ if (!tab_open)
+ node->WantCloseTabId = window->TabId;
+ if (tab_bar->VisibleTabId == window->TabId)
+ node->VisibleWindow = window;
+
+ // Store last item data so it can be queried with IsItemXXX functions after the user Begin() call
+ window->DockTabItemStatusFlags = g.LastItemData.StatusFlags;
+ window->DockTabItemRect = g.LastItemData.Rect;
+
+ // Update navigation ID on menu layer
+ if (g.NavWindow && g.NavWindow->RootWindow == window && (window->DC.NavLayersActiveMask & (1 << ImGuiNavLayer_Menu)) == 0)
+ host_window->NavLastIds[1] = window->TabId;
+ }
+ }
+
+ // Restore style colors
+ for (int color_n = 0; color_n < ImGuiWindowDockStyleCol_COUNT; color_n++)
+ g.Style.Colors[GWindowDockStyleColors[color_n]] = backup_style_cols[color_n];
+
+ // Notify root of visible window (used to display title in OS task bar)
+ if (node->VisibleWindow)
+ if (is_focused || root_node->VisibleWindow == NULL)
+ root_node->VisibleWindow = node->VisibleWindow;
+
+ // Close button (after VisibleWindow was updated)
+ // Note that VisibleWindow may have been overrided by CTRL+Tabbing, so VisibleWindow->TabId may be != from tab_bar->SelectedTabId
+ const bool close_button_is_enabled = node->HasCloseButton && node->VisibleWindow && node->VisibleWindow->HasCloseButton;
+ const bool close_button_is_visible = node->HasCloseButton;
+ //const bool close_button_is_visible = close_button_is_enabled; // Most people would expect this behavior of not even showing the button (leaving a hole since we can't claim that space as other windows in the tba bar have one)
+ if (close_button_is_visible)
+ {
+ if (!close_button_is_enabled)
+ {
+ PushItemFlag(ImGuiItemFlags_Disabled, true);
+ PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_Text] * ImVec4(1.0f,1.0f,1.0f,0.4f));
+ }
+ if (CloseButton(host_window->GetID("#CLOSE"), close_button_pos))
+ {
+ node->WantCloseAll = true;
+ for (int n = 0; n < tab_bar->Tabs.Size; n++)
+ TabBarCloseTab(tab_bar, &tab_bar->Tabs[n]);
+ }
+ //if (IsItemActive())
+ // focus_tab_id = tab_bar->SelectedTabId;
+ if (!close_button_is_enabled)
+ {
+ PopStyleColor();
+ PopItemFlag();
+ }
+ }
+
+ // When clicking on the title bar outside of tabs, we still focus the selected tab for that node
+ // FIXME: TabItems submitted earlier use AllowItemOverlap so we manually perform a more specific test for now (hovered || held) in order to not cover them.
+ ImGuiID title_bar_id = host_window->GetID("#TITLEBAR");
+ if (g.HoveredId == 0 || g.HoveredId == title_bar_id || g.ActiveId == title_bar_id)
+ {
+ // AllowOverlap mode required for appending into dock node tab bar,
+ // otherwise dragging window will steal HoveredId and amended tabs cannot get them.
+ bool held;
+ KeepAliveID(title_bar_id);
+ ButtonBehavior(title_bar_rect, title_bar_id, NULL, &held, ImGuiButtonFlags_AllowOverlap);
+ if (g.HoveredId == title_bar_id)
+ {
+ g.LastItemData.ID = title_bar_id;
+ }
+ if (held)
+ {
+ if (IsMouseClicked(0))
+ focus_tab_id = tab_bar->SelectedTabId;
+
+ // Forward moving request to selected window
+ if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_bar->SelectedTabId))
+ StartMouseMovingWindowOrNode(tab->Window ? tab->Window : node->HostWindow, node, false); // Undock from tab bar empty space
+ }
+ }
+
+ // Forward focus from host node to selected window
+ //if (is_focused && g.NavWindow == host_window && !g.NavWindowingTarget)
+ // focus_tab_id = tab_bar->SelectedTabId;
+
+ // When clicked on a tab we requested focus to the docked child
+ // This overrides the value set by "forward focus from host node to selected window".
+ if (tab_bar->NextSelectedTabId)
+ focus_tab_id = tab_bar->NextSelectedTabId;
+
+ // Apply navigation focus
+ if (focus_tab_id != 0)
+ if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, focus_tab_id))
+ if (tab->Window)
+ {
+ FocusWindow(tab->Window);
+ NavInitWindow(tab->Window, false);
+ }
+
+ EndTabBar();
+ PopID();
+
+ // Restore SkipItems flag
+ if (!node->IsDockSpace())
+ {
+ host_window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
+ host_window->SkipItems = backup_skip_item;
+ }
+}
+
+static void ImGui::DockNodeAddTabBar(ImGuiDockNode* node)
+{
+ IM_ASSERT(node->TabBar == NULL);
+ node->TabBar = IM_NEW(ImGuiTabBar);
+}
+
+static void ImGui::DockNodeRemoveTabBar(ImGuiDockNode* node)
+{
+ if (node->TabBar == NULL)
+ return;
+ IM_DELETE(node->TabBar);
+ node->TabBar = NULL;
+}
+
+static bool DockNodeIsDropAllowedOne(ImGuiWindow* payload, ImGuiWindow* host_window)
+{
+ if (host_window->DockNodeAsHost && host_window->DockNodeAsHost->IsDockSpace() && payload->BeginOrderWithinContext < host_window->BeginOrderWithinContext)
+ return false;
+
+ ImGuiWindowClass* host_class = host_window->DockNodeAsHost ? &host_window->DockNodeAsHost->WindowClass : &host_window->WindowClass;
+ ImGuiWindowClass* payload_class = &payload->WindowClass;
+ if (host_class->ClassId != payload_class->ClassId)
+ {
+ bool pass = false;
+ if (host_class->ClassId != 0 && host_class->DockingAllowUnclassed && payload_class->ClassId == 0)
+ pass = true;
+ if (payload_class->ClassId != 0 && payload_class->DockingAllowUnclassed && host_class->ClassId == 0)
+ pass = true;
+ if (!pass)
+ return false;
+ }
+
+ // Prevent docking any window created above a popup
+ // Technically we should support it (e.g. in the case of a long-lived modal window that had fancy docking features),
+ // by e.g. adding a 'if (!ImGui::IsWindowWithinBeginStackOf(host_window, popup_window))' test.
+ // But it would requires more work on our end because the dock host windows is technically created in NewFrame()
+ // and our ->ParentXXX and ->RootXXX pointers inside windows are currently mislading or lacking.
+ ImGuiContext& g = *GImGui;
+ for (int i = g.OpenPopupStack.Size - 1; i >= 0; i--)
+ if (ImGuiWindow* popup_window = g.OpenPopupStack[i].Window)
+ if (ImGui::IsWindowWithinBeginStackOf(payload, popup_window)) // Payload is created from within a popup begin stack.
+ return false;
+
+ return true;
+}
+
+static bool ImGui::DockNodeIsDropAllowed(ImGuiWindow* host_window, ImGuiWindow* root_payload)
+{
+ if (root_payload->DockNodeAsHost && root_payload->DockNodeAsHost->IsSplitNode()) // FIXME-DOCK: Missing filtering
+ return true;
+
+ const int payload_count = root_payload->DockNodeAsHost ? root_payload->DockNodeAsHost->Windows.Size : 1;
+ for (int payload_n = 0; payload_n < payload_count; payload_n++)
+ {
+ ImGuiWindow* payload = root_payload->DockNodeAsHost ? root_payload->DockNodeAsHost->Windows[payload_n] : root_payload;
+ if (DockNodeIsDropAllowedOne(payload, host_window))
+ return true;
+ }
+ return false;
+}
+
+// window menu button == collapse button when not in a dock node.
+// FIXME: This is similar to RenderWindowTitleBarContents(), may want to share code.
+static void ImGui::DockNodeCalcTabBarLayout(const ImGuiDockNode* node, ImRect* out_title_rect, ImRect* out_tab_bar_rect, ImVec2* out_window_menu_button_pos, ImVec2* out_close_button_pos)
+{
+ ImGuiContext& g = *GImGui;
+ ImGuiStyle& style = g.Style;
+
+ ImRect r = ImRect(node->Pos.x, node->Pos.y, node->Pos.x + node->Size.x, node->Pos.y + g.FontSize + g.Style.FramePadding.y * 2.0f);
+ if (out_title_rect) { *out_title_rect = r; }
+
+ r.Min.x += style.WindowBorderSize;
+ r.Max.x -= style.WindowBorderSize;
+
+ float button_sz = g.FontSize;
+ r.Min.x += style.FramePadding.x;
+ r.Max.x -= style.FramePadding.x;
+ ImVec2 window_menu_button_pos = ImVec2(r.Min.x, r.Min.y + style.FramePadding.y);
+ if (node->HasCloseButton)
+ {
+ if (out_close_button_pos) *out_close_button_pos = ImVec2(r.Max.x - button_sz, r.Min.y + style.FramePadding.y);
+ r.Max.x -= button_sz + style.ItemInnerSpacing.x;
+ }
+ if (node->HasWindowMenuButton && style.WindowMenuButtonPosition == ImGuiDir_Left)
+ {
+ r.Min.x += button_sz + style.ItemInnerSpacing.x;
+ }
+ else if (node->HasWindowMenuButton && style.WindowMenuButtonPosition == ImGuiDir_Right)
+ {
+ window_menu_button_pos = ImVec2(r.Max.x - button_sz, r.Min.y + style.FramePadding.y);
+ r.Max.x -= button_sz + style.ItemInnerSpacing.x;
+ }
+ if (out_tab_bar_rect) { *out_tab_bar_rect = r; }
+ if (out_window_menu_button_pos) { *out_window_menu_button_pos = window_menu_button_pos; }
+}
+
+void ImGui::DockNodeCalcSplitRects(ImVec2& pos_old, ImVec2& size_old, ImVec2& pos_new, ImVec2& size_new, ImGuiDir dir, ImVec2 size_new_desired)
+{
+ ImGuiContext& g = *GImGui;
+ const float dock_spacing = g.Style.ItemInnerSpacing.x;
+ const ImGuiAxis axis = (dir == ImGuiDir_Left || dir == ImGuiDir_Right) ? ImGuiAxis_X : ImGuiAxis_Y;
+ pos_new[axis ^ 1] = pos_old[axis ^ 1];
+ size_new[axis ^ 1] = size_old[axis ^ 1];
+
+ // Distribute size on given axis (with a desired size or equally)
+ const float w_avail = size_old[axis] - dock_spacing;
+ if (size_new_desired[axis] > 0.0f && size_new_desired[axis] <= w_avail * 0.5f)
+ {
+ size_new[axis] = size_new_desired[axis];
+ size_old[axis] = IM_TRUNC(w_avail - size_new[axis]);
+ }
+ else
+ {
+ size_new[axis] = IM_TRUNC(w_avail * 0.5f);
+ size_old[axis] = IM_TRUNC(w_avail - size_new[axis]);
+ }
+
+ // Position each node
+ if (dir == ImGuiDir_Right || dir == ImGuiDir_Down)
+ {
+ pos_new[axis] = pos_old[axis] + size_old[axis] + dock_spacing;
+ }
+ else if (dir == ImGuiDir_Left || dir == ImGuiDir_Up)
+ {
+ pos_new[axis] = pos_old[axis];
+ pos_old[axis] = pos_new[axis] + size_new[axis] + dock_spacing;
+ }
+}
+
+// Retrieve the drop rectangles for a given direction or for the center + perform hit testing.
+bool ImGui::DockNodeCalcDropRectsAndTestMousePos(const ImRect& parent, ImGuiDir dir, ImRect& out_r, bool outer_docking, ImVec2* test_mouse_pos)
+{
+ ImGuiContext& g = *GImGui;
+
+ const float parent_smaller_axis = ImMin(parent.GetWidth(), parent.GetHeight());
+ const float hs_for_central_nodes = ImMin(g.FontSize * 1.5f, ImMax(g.FontSize * 0.5f, parent_smaller_axis / 8.0f));
+ float hs_w; // Half-size, longer axis
+ float hs_h; // Half-size, smaller axis
+ ImVec2 off; // Distance from edge or center
+ if (outer_docking)
+ {
+ //hs_w = ImTrunc(ImClamp(parent_smaller_axis - hs_for_central_nodes * 4.0f, g.FontSize * 0.5f, g.FontSize * 8.0f));
+ //hs_h = ImTrunc(hs_w * 0.15f);
+ //off = ImVec2(ImTrunc(parent.GetWidth() * 0.5f - GetFrameHeightWithSpacing() * 1.4f - hs_h), ImTrunc(parent.GetHeight() * 0.5f - GetFrameHeightWithSpacing() * 1.4f - hs_h));
+ hs_w = ImTrunc(hs_for_central_nodes * 1.50f);
+ hs_h = ImTrunc(hs_for_central_nodes * 0.80f);
+ off = ImTrunc(ImVec2(parent.GetWidth() * 0.5f - hs_h, parent.GetHeight() * 0.5f - hs_h));
+ }
+ else
+ {
+ hs_w = ImTrunc(hs_for_central_nodes);
+ hs_h = ImTrunc(hs_for_central_nodes * 0.90f);
+ off = ImTrunc(ImVec2(hs_w * 2.40f, hs_w * 2.40f));
+ }
+
+ ImVec2 c = ImTrunc(parent.GetCenter());
+ if (dir == ImGuiDir_None) { out_r = ImRect(c.x - hs_w, c.y - hs_w, c.x + hs_w, c.y + hs_w); }
+ else if (dir == ImGuiDir_Up) { out_r = ImRect(c.x - hs_w, c.y - off.y - hs_h, c.x + hs_w, c.y - off.y + hs_h); }
+ else if (dir == ImGuiDir_Down) { out_r = ImRect(c.x - hs_w, c.y + off.y - hs_h, c.x + hs_w, c.y + off.y + hs_h); }
+ else if (dir == ImGuiDir_Left) { out_r = ImRect(c.x - off.x - hs_h, c.y - hs_w, c.x - off.x + hs_h, c.y + hs_w); }
+ else if (dir == ImGuiDir_Right) { out_r = ImRect(c.x + off.x - hs_h, c.y - hs_w, c.x + off.x + hs_h, c.y + hs_w); }
+
+ if (test_mouse_pos == NULL)
+ return false;
+
+ ImRect hit_r = out_r;
+ if (!outer_docking)
+ {
+ // Custom hit testing for the 5-way selection, designed to reduce flickering when moving diagonally between sides
+ hit_r.Expand(ImTrunc(hs_w * 0.30f));
+ ImVec2 mouse_delta = (*test_mouse_pos - c);
+ float mouse_delta_len2 = ImLengthSqr(mouse_delta);
+ float r_threshold_center = hs_w * 1.4f;
+ float r_threshold_sides = hs_w * (1.4f + 1.2f);
+ if (mouse_delta_len2 < r_threshold_center * r_threshold_center)
+ return (dir == ImGuiDir_None);
+ if (mouse_delta_len2 < r_threshold_sides * r_threshold_sides)
+ return (dir == ImGetDirQuadrantFromDelta(mouse_delta.x, mouse_delta.y));
+ }
+ return hit_r.Contains(*test_mouse_pos);
+}
+
+// host_node may be NULL if the window doesn't have a DockNode already.
+// FIXME-DOCK: This is misnamed since it's also doing the filtering.
+static void ImGui::DockNodePreviewDockSetup(ImGuiWindow* host_window, ImGuiDockNode* host_node, ImGuiWindow* payload_window, ImGuiDockNode* payload_node, ImGuiDockPreviewData* data, bool is_explicit_target, bool is_outer_docking)
+{
+ ImGuiContext& g = *GImGui;
+
+ // There is an edge case when docking into a dockspace which only has inactive nodes.
+ // In this case DockNodeTreeFindNodeByPos() will have selected a leaf node which is inactive.
+ // Because the inactive leaf node doesn't have proper pos/size yet, we'll use the root node as reference.
+ if (payload_node == NULL)
+ payload_node = payload_window->DockNodeAsHost;
+ ImGuiDockNode* ref_node_for_rect = (host_node && !host_node->IsVisible) ? DockNodeGetRootNode(host_node) : host_node;
+ if (ref_node_for_rect)
+ IM_ASSERT(ref_node_for_rect->IsVisible == true);
+
+ // Filter, figure out where we are allowed to dock
+ ImGuiDockNodeFlags src_node_flags = payload_node ? payload_node->MergedFlags : payload_window->WindowClass.DockNodeFlagsOverrideSet;
+ ImGuiDockNodeFlags dst_node_flags = host_node ? host_node->MergedFlags : host_window->WindowClass.DockNodeFlagsOverrideSet;
+ data->IsCenterAvailable = true;
+ if (is_outer_docking)
+ data->IsCenterAvailable = false;
+ else if (dst_node_flags & ImGuiDockNodeFlags_NoDockingOverMe)
+ data->IsCenterAvailable = false;
+ else if (host_node && (dst_node_flags & ImGuiDockNodeFlags_NoDockingOverCentralNode) && host_node->IsCentralNode())
+ data->IsCenterAvailable = false;
+ else if ((!host_node || !host_node->IsEmpty()) && payload_node && payload_node->IsSplitNode() && (payload_node->OnlyNodeWithWindows == NULL)) // Is _visibly_ split?
+ data->IsCenterAvailable = false;
+ else if ((src_node_flags & ImGuiDockNodeFlags_NoDockingOverOther) && (!host_node || !host_node->IsEmpty()))
+ data->IsCenterAvailable = false;
+ else if ((src_node_flags & ImGuiDockNodeFlags_NoDockingOverEmpty) && host_node && host_node->IsEmpty())
+ data->IsCenterAvailable = false;
+
+ data->IsSidesAvailable = true;
+ if ((dst_node_flags & ImGuiDockNodeFlags_NoDockingSplit) || g.IO.ConfigDockingNoSplit)
+ data->IsSidesAvailable = false;
+ else if (!is_outer_docking && host_node && host_node->ParentNode == NULL && host_node->IsCentralNode())
+ data->IsSidesAvailable = false;
+ else if (src_node_flags & ImGuiDockNodeFlags_NoDockingSplitOther)
+ data->IsSidesAvailable = false;
+
+ // Build a tentative future node (reuse same structure because it is practical. Shape will be readjusted when previewing a split)
+ data->FutureNode.HasCloseButton = (host_node ? host_node->HasCloseButton : host_window->HasCloseButton) || (payload_window->HasCloseButton);
+ data->FutureNode.HasWindowMenuButton = host_node ? true : ((host_window->Flags & ImGuiWindowFlags_NoCollapse) == 0);
+ data->FutureNode.Pos = ref_node_for_rect ? ref_node_for_rect->Pos : host_window->Pos;
+ data->FutureNode.Size = ref_node_for_rect ? ref_node_for_rect->Size : host_window->Size;
+
+ // Calculate drop shapes geometry for allowed splitting directions
+ IM_ASSERT(ImGuiDir_None == -1);
+ data->SplitNode = host_node;
+ data->SplitDir = ImGuiDir_None;
+ data->IsSplitDirExplicit = false;
+ if (!host_window->Collapsed)
+ for (int dir = ImGuiDir_None; dir < ImGuiDir_COUNT; dir++)
+ {
+ if (dir == ImGuiDir_None && !data->IsCenterAvailable)
+ continue;
+ if (dir != ImGuiDir_None && !data->IsSidesAvailable)
+ continue;
+ if (DockNodeCalcDropRectsAndTestMousePos(data->FutureNode.Rect(), (ImGuiDir)dir, data->DropRectsDraw[dir+1], is_outer_docking, &g.IO.MousePos))
+ {
+ data->SplitDir = (ImGuiDir)dir;
+ data->IsSplitDirExplicit = true;
+ }
+ }
+
+ // When docking without holding Shift, we only allow and preview docking when hovering over a drop rect or over the title bar
+ data->IsDropAllowed = (data->SplitDir != ImGuiDir_None) || (data->IsCenterAvailable);
+ if (!is_explicit_target && !data->IsSplitDirExplicit && !g.IO.ConfigDockingWithShift)
+ data->IsDropAllowed = false;
+
+ // Calculate split area
+ data->SplitRatio = 0.0f;
+ if (data->SplitDir != ImGuiDir_None)
+ {
+ ImGuiDir split_dir = data->SplitDir;
+ ImGuiAxis split_axis = (split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Right) ? ImGuiAxis_X : ImGuiAxis_Y;
+ ImVec2 pos_new, pos_old = data->FutureNode.Pos;
+ ImVec2 size_new, size_old = data->FutureNode.Size;
+ DockNodeCalcSplitRects(pos_old, size_old, pos_new, size_new, split_dir, payload_window->Size);
+
+ // Calculate split ratio so we can pass it down the docking request
+ float split_ratio = ImSaturate(size_new[split_axis] / data->FutureNode.Size[split_axis]);
+ data->FutureNode.Pos = pos_new;
+ data->FutureNode.Size = size_new;
+ data->SplitRatio = (split_dir == ImGuiDir_Right || split_dir == ImGuiDir_Down) ? (1.0f - split_ratio) : (split_ratio);
+ }
+}
+
+static void ImGui::DockNodePreviewDockRender(ImGuiWindow* host_window, ImGuiDockNode* host_node, ImGuiWindow* root_payload, const ImGuiDockPreviewData* data)
+{
+ ImGuiContext& g = *GImGui;
+ IM_ASSERT(g.CurrentWindow == host_window); // Because we rely on font size to calculate tab sizes
+
+ // With this option, we only display the preview on the target viewport, and the payload viewport is made transparent.
+ // To compensate for the single layer obstructed by the payload, we'll increase the alpha of the preview nodes.
+ const bool is_transparent_payload = g.IO.ConfigDockingTransparentPayload;
+
+ // In case the two windows involved are on different viewports, we will draw the overlay on each of them.
+ int overlay_draw_lists_count = 0;
+ ImDrawList* overlay_draw_lists[2];
+ overlay_draw_lists[overlay_draw_lists_count++] = GetForegroundDrawList(host_window->Viewport);
+ if (host_window->Viewport != root_payload->Viewport && !is_transparent_payload)
+ overlay_draw_lists[overlay_draw_lists_count++] = GetForegroundDrawList(root_payload->Viewport);
+
+ // Draw main preview rectangle
+ const ImU32 overlay_col_main = GetColorU32(ImGuiCol_DockingPreview, is_transparent_payload ? 0.60f : 0.40f);
+ const ImU32 overlay_col_drop = GetColorU32(ImGuiCol_DockingPreview, is_transparent_payload ? 0.90f : 0.70f);
+ const ImU32 overlay_col_drop_hovered = GetColorU32(ImGuiCol_DockingPreview, is_transparent_payload ? 1.20f : 1.00f);
+ const ImU32 overlay_col_lines = GetColorU32(ImGuiCol_NavWindowingHighlight, is_transparent_payload ? 0.80f : 0.60f);
+
+ // Display area preview
+ const bool can_preview_tabs = (root_payload->DockNodeAsHost == NULL || root_payload->DockNodeAsHost->Windows.Size > 0);
+ if (data->IsDropAllowed)
+ {
+ ImRect overlay_rect = data->FutureNode.Rect();
+ if (data->SplitDir == ImGuiDir_None && can_preview_tabs)
+ overlay_rect.Min.y += GetFrameHeight();
+ if (data->SplitDir != ImGuiDir_None || data->IsCenterAvailable)
+ for (int overlay_n = 0; overlay_n < overlay_draw_lists_count; overlay_n++)
+ overlay_draw_lists[overlay_n]->AddRectFilled(overlay_rect.Min, overlay_rect.Max, overlay_col_main, host_window->WindowRounding, CalcRoundingFlagsForRectInRect(overlay_rect, host_window->Rect(), g.Style.DockingSeparatorSize));
+ }
+
+ // Display tab shape/label preview unless we are splitting node (it generally makes the situation harder to read)
+ if (data->IsDropAllowed && can_preview_tabs && data->SplitDir == ImGuiDir_None && data->IsCenterAvailable)
+ {
+ // Compute target tab bar geometry so we can locate our preview tabs
+ ImRect tab_bar_rect;
+ DockNodeCalcTabBarLayout(&data->FutureNode, NULL, &tab_bar_rect, NULL, NULL);
+ ImVec2 tab_pos = tab_bar_rect.Min;
+ if (host_node && host_node->TabBar)
+ {
+ if (!host_node->IsHiddenTabBar() && !host_node->IsNoTabBar())
+ tab_pos.x += host_node->TabBar->WidthAllTabs + g.Style.ItemInnerSpacing.x; // We don't use OffsetNewTab because when using non-persistent-order tab bar it is incremented with each Tab submission.
+ else
+ tab_pos.x += g.Style.ItemInnerSpacing.x + TabItemCalcSize(host_node->Windows[0]).x;
+ }
+ else if (!(host_window->Flags & ImGuiWindowFlags_DockNodeHost))
+ {
+ tab_pos.x += g.Style.ItemInnerSpacing.x + TabItemCalcSize(host_window).x; // Account for slight offset which will be added when changing from title bar to tab bar
+ }
+
+ // Draw tab shape/label preview (payload may be a loose window or a host window carrying multiple tabbed windows)
+ if (root_payload->DockNodeAsHost)
+ IM_ASSERT(root_payload->DockNodeAsHost->Windows.Size <= root_payload->DockNodeAsHost->TabBar->Tabs.Size);
+ ImGuiTabBar* tab_bar_with_payload = root_payload->DockNodeAsHost ? root_payload->DockNodeAsHost->TabBar : NULL;
+ const int payload_count = tab_bar_with_payload ? tab_bar_with_payload->Tabs.Size : 1;
+ for (int payload_n = 0; payload_n < payload_count; payload_n++)
+ {
+ // DockNode's TabBar may have non-window Tabs manually appended by user
+ ImGuiWindow* payload_window = tab_bar_with_payload ? tab_bar_with_payload->Tabs[payload_n].Window : root_payload;
+ if (tab_bar_with_payload && payload_window == NULL)
+ continue;
+ if (!DockNodeIsDropAllowedOne(payload_window, host_window))
+ continue;
+
+ // Calculate the tab bounding box for each payload window
+ ImVec2 tab_size = TabItemCalcSize(payload_window);
+ ImRect tab_bb(tab_pos.x, tab_pos.y, tab_pos.x + tab_size.x, tab_pos.y + tab_size.y);
+ tab_pos.x += tab_size.x + g.Style.ItemInnerSpacing.x;
+ const ImU32 overlay_col_text = GetColorU32(payload_window->DockStyle.Colors[ImGuiWindowDockStyleCol_Text]);
+ const ImU32 overlay_col_tabs = GetColorU32(payload_window->DockStyle.Colors[ImGuiWindowDockStyleCol_TabActive]);
+ PushStyleColor(ImGuiCol_Text, overlay_col_text);
+ for (int overlay_n = 0; overlay_n < overlay_draw_lists_count; overlay_n++)
+ {
+ ImGuiTabItemFlags tab_flags = (payload_window->Flags & ImGuiWindowFlags_UnsavedDocument) ? ImGuiTabItemFlags_UnsavedDocument : 0;
+ if (!tab_bar_rect.Contains(tab_bb))
+ overlay_draw_lists[overlay_n]->PushClipRect(tab_bar_rect.Min, tab_bar_rect.Max);
+ TabItemBackground(overlay_draw_lists[overlay_n], tab_bb, tab_flags, overlay_col_tabs);
+ TabItemLabelAndCloseButton(overlay_draw_lists[overlay_n], tab_bb, tab_flags, g.Style.FramePadding, payload_window->Name, 0, 0, false, NULL, NULL);
+ if (!tab_bar_rect.Contains(tab_bb))
+ overlay_draw_lists[overlay_n]->PopClipRect();
+ }
+ PopStyleColor();
+ }
+ }
+
+ // Display drop boxes
+ const float overlay_rounding = ImMax(3.0f, g.Style.FrameRounding);
+ for (int dir = ImGuiDir_None; dir < ImGuiDir_COUNT; dir++)
+ {
+ if (!data->DropRectsDraw[dir + 1].IsInverted())
+ {
+ ImRect draw_r = data->DropRectsDraw[dir + 1];
+ ImRect draw_r_in = draw_r;
+ draw_r_in.Expand(-2.0f);
+ ImU32 overlay_col = (data->SplitDir == (ImGuiDir)dir && data->IsSplitDirExplicit) ? overlay_col_drop_hovered : overlay_col_drop;
+ for (int overlay_n = 0; overlay_n < overlay_draw_lists_count; overlay_n++)
+ {
+ ImVec2 center = ImFloor(draw_r_in.GetCenter());
+ overlay_draw_lists[overlay_n]->AddRectFilled(draw_r.Min, draw_r.Max, overlay_col, overlay_rounding);
+ overlay_draw_lists[overlay_n]->AddRect(draw_r_in.Min, draw_r_in.Max, overlay_col_lines, overlay_rounding);
+ if (dir == ImGuiDir_Left || dir == ImGuiDir_Right)
+ overlay_draw_lists[overlay_n]->AddLine(ImVec2(center.x, draw_r_in.Min.y), ImVec2(center.x, draw_r_in.Max.y), overlay_col_lines);
+ if (dir == ImGuiDir_Up || dir == ImGuiDir_Down)
+ overlay_draw_lists[overlay_n]->AddLine(ImVec2(draw_r_in.Min.x, center.y), ImVec2(draw_r_in.Max.x, center.y), overlay_col_lines);
+ }
+ }
+
+ // Stop after ImGuiDir_None
+ if ((host_node && (host_node->MergedFlags & ImGuiDockNodeFlags_NoDockingSplit)) || g.IO.ConfigDockingNoSplit)
+ return;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Docking: ImGuiDockNode Tree manipulation functions
+//-----------------------------------------------------------------------------
+// - DockNodeTreeSplit()
+// - DockNodeTreeMerge()
+// - DockNodeTreeUpdatePosSize()
+// - DockNodeTreeUpdateSplitterFindTouchingNode()
+// - DockNodeTreeUpdateSplitter()
+// - DockNodeTreeFindFallbackLeafNode()
+// - DockNodeTreeFindNodeByPos()
+//-----------------------------------------------------------------------------
+
+void ImGui::DockNodeTreeSplit(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImGuiAxis split_axis, int split_inheritor_child_idx, float split_ratio, ImGuiDockNode* new_node)
+{
+ ImGuiContext& g = *GImGui;
+ IM_ASSERT(split_axis != ImGuiAxis_None);
+
+ ImGuiDockNode* child_0 = (new_node && split_inheritor_child_idx != 0) ? new_node : DockContextAddNode(ctx, 0);
+ child_0->ParentNode = parent_node;
+
+ ImGuiDockNode* child_1 = (new_node && split_inheritor_child_idx != 1) ? new_node : DockContextAddNode(ctx, 0);
+ child_1->ParentNode = parent_node;
+
+ ImGuiDockNode* child_inheritor = (split_inheritor_child_idx == 0) ? child_0 : child_1;
+ DockNodeMoveChildNodes(child_inheritor, parent_node);
+ parent_node->ChildNodes[0] = child_0;
+ parent_node->ChildNodes[1] = child_1;
+ parent_node->ChildNodes[split_inheritor_child_idx]->VisibleWindow = parent_node->VisibleWindow;
+ parent_node->SplitAxis = split_axis;
+ parent_node->VisibleWindow = NULL;
+ parent_node->AuthorityForPos = parent_node->AuthorityForSize = ImGuiDataAuthority_DockNode;
+
+ float size_avail = (parent_node->Size[split_axis] - g.Style.DockingSeparatorSize);
+ size_avail = ImMax(size_avail, g.Style.WindowMinSize[split_axis] * 2.0f);
+ IM_ASSERT(size_avail > 0.0f); // If you created a node manually with DockBuilderAddNode(), you need to also call DockBuilderSetNodeSize() before splitting.
+ child_0->SizeRef = child_1->SizeRef = parent_node->Size;
+ child_0->SizeRef[split_axis] = ImTrunc(size_avail * split_ratio);
+ child_1->SizeRef[split_axis] = ImTrunc(size_avail - child_0->SizeRef[split_axis]);
+
+ DockNodeMoveWindows(parent_node->ChildNodes[split_inheritor_child_idx], parent_node);
+ DockSettingsRenameNodeReferences(parent_node->ID, parent_node->ChildNodes[split_inheritor_child_idx]->ID);
+ DockNodeUpdateHasCentralNodeChild(DockNodeGetRootNode(parent_node));
+ DockNodeTreeUpdatePosSize(parent_node, parent_node->Pos, parent_node->Size);
+
+ // Flags transfer (e.g. this is where we transfer the ImGuiDockNodeFlags_CentralNode property)
+ child_0->SharedFlags = parent_node->SharedFlags & ImGuiDockNodeFlags_SharedFlagsInheritMask_;
+ child_1->SharedFlags = parent_node->SharedFlags & ImGuiDockNodeFlags_SharedFlagsInheritMask_;
+ child_inheritor->LocalFlags = parent_node->LocalFlags & ImGuiDockNodeFlags_LocalFlagsTransferMask_;
+ parent_node->LocalFlags &= ~ImGuiDockNodeFlags_LocalFlagsTransferMask_;
+ child_0->UpdateMergedFlags();
+ child_1->UpdateMergedFlags();
+ parent_node->UpdateMergedFlags();
+ if (child_inheritor->IsCentralNode())
+ DockNodeGetRootNode(parent_node)->CentralNode = child_inheritor;
+}
+
+void ImGui::DockNodeTreeMerge(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImGuiDockNode* merge_lead_child)
+{
+ // When called from DockContextProcessUndockNode() it is possible that one of the child is NULL.
+ ImGuiContext& g = *GImGui;
+ ImGuiDockNode* child_0 = parent_node->ChildNodes[0];
+ ImGuiDockNode* child_1 = parent_node->ChildNodes[1];
+ IM_ASSERT(child_0 || child_1);
+ IM_ASSERT(merge_lead_child == child_0 || merge_lead_child == child_1);
+ if ((child_0 && child_0->Windows.Size > 0) || (child_1 && child_1->Windows.Size > 0))
+ {
+ IM_ASSERT(parent_node->TabBar == NULL);
+ IM_ASSERT(parent_node->Windows.Size == 0);
+ }
+ IMGUI_DEBUG_LOG_DOCKING("[docking] DockNodeTreeMerge: 0x%08X + 0x%08X back into parent 0x%08X\n", child_0 ? child_0->ID : 0, child_1 ? child_1->ID : 0, parent_node->ID);
+
+ ImVec2 backup_last_explicit_size = parent_node->SizeRef;
+ DockNodeMoveChildNodes(parent_node, merge_lead_child);
+ if (child_0)
+ {
+ DockNodeMoveWindows(parent_node, child_0); // Generally only 1 of the 2 child node will have windows
+ DockSettingsRenameNodeReferences(child_0->ID, parent_node->ID);
+ }
+ if (child_1)
+ {
+ DockNodeMoveWindows(parent_node, child_1);
+ DockSettingsRenameNodeReferences(child_1->ID, parent_node->ID);
+ }
+ DockNodeApplyPosSizeToWindows(parent_node);
+ parent_node->AuthorityForPos = parent_node->AuthorityForSize = parent_node->AuthorityForViewport = ImGuiDataAuthority_Auto;
+ parent_node->VisibleWindow = merge_lead_child->VisibleWindow;
+ parent_node->SizeRef = backup_last_explicit_size;
+
+ // Flags transfer
+ parent_node->LocalFlags &= ~ImGuiDockNodeFlags_LocalFlagsTransferMask_; // Preserve Dockspace flag
+ parent_node->LocalFlags |= (child_0 ? child_0->LocalFlags : 0) & ImGuiDockNodeFlags_LocalFlagsTransferMask_;
+ parent_node->LocalFlags |= (child_1 ? child_1->LocalFlags : 0) & ImGuiDockNodeFlags_LocalFlagsTransferMask_;
+ parent_node->LocalFlagsInWindows = (child_0 ? child_0->LocalFlagsInWindows : 0) | (child_1 ? child_1->LocalFlagsInWindows : 0); // FIXME: Would be more consistent to update from actual windows
+ parent_node->UpdateMergedFlags();
+
+ if (child_0)
+ {
+ ctx->DockContext.Nodes.SetVoidPtr(child_0->ID, NULL);
+ IM_DELETE(child_0);
+ }
+ if (child_1)
+ {
+ ctx->DockContext.Nodes.SetVoidPtr(child_1->ID, NULL);
+ IM_DELETE(child_1);
+ }
+}
+
+// Update Pos/Size for a node hierarchy (don't affect child Windows yet)
+// (Depth-first, Pre-Order)
+void ImGui::DockNodeTreeUpdatePosSize(ImGuiDockNode* node, ImVec2 pos, ImVec2 size, ImGuiDockNode* only_write_to_single_node)
+{
+ // During the regular dock node update we write to all nodes.
+ // 'only_write_to_single_node' is only set when turning a node visible mid-frame and we need its size right-away.
+ ImGuiContext& g = *GImGui;
+ const bool write_to_node = only_write_to_single_node == NULL || only_write_to_single_node == node;
+ if (write_to_node)
+ {
+ node->Pos = pos;
+ node->Size = size;
+ }
+
+ if (node->IsLeafNode())
+ return;
+
+ ImGuiDockNode* child_0 = node->ChildNodes[0];
+ ImGuiDockNode* child_1 = node->ChildNodes[1];
+ ImVec2 child_0_pos = pos, child_1_pos = pos;
+ ImVec2 child_0_size = size, child_1_size = size;
+
+ const bool child_0_is_toward_single_node = (only_write_to_single_node != NULL && DockNodeIsInHierarchyOf(only_write_to_single_node, child_0));
+ const bool child_1_is_toward_single_node = (only_write_to_single_node != NULL && DockNodeIsInHierarchyOf(only_write_to_single_node, child_1));
+ const bool child_0_is_or_will_be_visible = child_0->IsVisible || child_0_is_toward_single_node;
+ const bool child_1_is_or_will_be_visible = child_1->IsVisible || child_1_is_toward_single_node;
+
+ if (child_0_is_or_will_be_visible && child_1_is_or_will_be_visible)
+ {
+ const float spacing = g.Style.DockingSeparatorSize;
+ const ImGuiAxis axis = (ImGuiAxis)node->SplitAxis;
+ const float size_avail = ImMax(size[axis] - spacing, 0.0f);
+
+ // Size allocation policy
+ // 1) The first 0..WindowMinSize[axis]*2 are allocated evenly to both windows.
+ const float size_min_each = ImTrunc(ImMin(size_avail, g.Style.WindowMinSize[axis] * 2.0f) * 0.5f);
+
+ // FIXME: Blocks 2) and 3) are essentially doing nearly the same thing.
+ // Difference are: write-back to SizeRef; application of a minimum size; rounding before ImTrunc()
+ // Clarify and rework differences between Size & SizeRef and purpose of WantLockSizeOnce
+
+ // 2) Process locked absolute size (during a splitter resize we preserve the child of nodes not touching the splitter edge)
+ if (child_0->WantLockSizeOnce && !child_1->WantLockSizeOnce)
+ {
+ child_0_size[axis] = child_0->SizeRef[axis] = ImMin(size_avail - 1.0f, child_0->Size[axis]);
+ child_1_size[axis] = child_1->SizeRef[axis] = (size_avail - child_0_size[axis]);
+ IM_ASSERT(child_0->SizeRef[axis] > 0.0f && child_1->SizeRef[axis] > 0.0f);
+ }
+ else if (child_1->WantLockSizeOnce && !child_0->WantLockSizeOnce)
+ {
+ child_1_size[axis] = child_1->SizeRef[axis] = ImMin(size_avail - 1.0f, child_1->Size[axis]);
+ child_0_size[axis] = child_0->SizeRef[axis] = (size_avail - child_1_size[axis]);
+ IM_ASSERT(child_0->SizeRef[axis] > 0.0f && child_1->SizeRef[axis] > 0.0f);
+ }
+ else if (child_0->WantLockSizeOnce && child_1->WantLockSizeOnce)
+ {
+ // FIXME-DOCK: We cannot honor the requested size, so apply ratio.
+ // Currently this path will only be taken if code programmatically sets WantLockSizeOnce
+ float split_ratio = child_0_size[axis] / (child_0_size[axis] + child_1_size[axis]);
+ child_0_size[axis] = child_0->SizeRef[axis] = ImTrunc(size_avail * split_ratio);
+ child_1_size[axis] = child_1->SizeRef[axis] = (size_avail - child_0_size[axis]);
+ IM_ASSERT(child_0->SizeRef[axis] > 0.0f && child_1->SizeRef[axis] > 0.0f);
+ }
+
+ // 3) If one window is the central node (~ use remaining space, should be made explicit!), use explicit size from the other, and remainder for the central node
+ else if (child_0->SizeRef[axis] != 0.0f && child_1->HasCentralNodeChild)
+ {
+ child_0_size[axis] = ImMin(size_avail - size_min_each, child_0->SizeRef[axis]);
+ child_1_size[axis] = (size_avail - child_0_size[axis]);
+ }
+ else if (child_1->SizeRef[axis] != 0.0f && child_0->HasCentralNodeChild)
+ {
+ child_1_size[axis] = ImMin(size_avail - size_min_each, child_1->SizeRef[axis]);
+ child_0_size[axis] = (size_avail - child_1_size[axis]);
+ }
+ else
+ {
+ // 4) Otherwise distribute according to the relative ratio of each SizeRef value
+ float split_ratio = child_0->SizeRef[axis] / (child_0->SizeRef[axis] + child_1->SizeRef[axis]);
+ child_0_size[axis] = ImMax(size_min_each, ImTrunc(size_avail * split_ratio + 0.5f));
+ child_1_size[axis] = (size_avail - child_0_size[axis]);
+ }
+
+ child_1_pos[axis] += spacing + child_0_size[axis];
+ }
+
+ if (only_write_to_single_node == NULL)
+ child_0->WantLockSizeOnce = child_1->WantLockSizeOnce = false;
+
+ const bool child_0_recurse = only_write_to_single_node ? child_0_is_toward_single_node : child_0->IsVisible;
+ const bool child_1_recurse = only_write_to_single_node ? child_1_is_toward_single_node : child_1->IsVisible;
+ if (child_0_recurse)
+ DockNodeTreeUpdatePosSize(child_0, child_0_pos, child_0_size);
+ if (child_1_recurse)
+ DockNodeTreeUpdatePosSize(child_1, child_1_pos, child_1_size);
+}
+
+static void DockNodeTreeUpdateSplitterFindTouchingNode(ImGuiDockNode* node, ImGuiAxis axis, int side, ImVector<ImGuiDockNode*>* touching_nodes)
+{
+ if (node->IsLeafNode())
+ {
+ touching_nodes->push_back(node);
+ return;
+ }
+ if (node->ChildNodes[0]->IsVisible)
+ if (node->SplitAxis != axis || side == 0 || !node->ChildNodes[1]->IsVisible)
+ DockNodeTreeUpdateSplitterFindTouchingNode(node->ChildNodes[0], axis, side, touching_nodes);
+ if (node->ChildNodes[1]->IsVisible)
+ if (node->SplitAxis != axis || side == 1 || !node->ChildNodes[0]->IsVisible)
+ DockNodeTreeUpdateSplitterFindTouchingNode(node->ChildNodes[1], axis, side, touching_nodes);
+}
+
+// (Depth-First, Pre-Order)
+void ImGui::DockNodeTreeUpdateSplitter(ImGuiDockNode* node)
+{
+ if (node->IsLeafNode())
+ return;
+
+ ImGuiContext& g = *GImGui;
+
+ ImGuiDockNode* child_0 = node->ChildNodes[0];
+ ImGuiDockNode* child_1 = node->ChildNodes[1];
+ if (child_0->IsVisible && child_1->IsVisible)
+ {
+ // Bounding box of the splitter cover the space between both nodes (w = Spacing, h = Size[xy^1] for when splitting horizontally)
+ const ImGuiAxis axis = (ImGuiAxis)node->SplitAxis;
+ IM_ASSERT(axis != ImGuiAxis_None);
+ ImRect bb;
+ bb.Min = child_0->Pos;
+ bb.Max = child_1->Pos;
+ bb.Min[axis] += child_0->Size[axis];
+ bb.Max[axis ^ 1] += child_1->Size[axis ^ 1];
+ //if (g.IO.KeyCtrl) GetForegroundDrawList(g.CurrentWindow->Viewport)->AddRect(bb.Min, bb.Max, IM_COL32(255,0,255,255));
+
+ const ImGuiDockNodeFlags merged_flags = child_0->MergedFlags | child_1->MergedFlags; // Merged flags for BOTH childs
+ const ImGuiDockNodeFlags no_resize_axis_flag = (axis == ImGuiAxis_X) ? ImGuiDockNodeFlags_NoResizeX : ImGuiDockNodeFlags_NoResizeY;
+ if ((merged_flags & ImGuiDockNodeFlags_NoResize) || (merged_flags & no_resize_axis_flag))
+ {
+ ImGuiWindow* window = g.CurrentWindow;
+ window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_Separator), g.Style.FrameRounding);
+ }
+ else
+ {
+ //bb.Min[axis] += 1; // Display a little inward so highlight doesn't connect with nearby tabs on the neighbor node.
+ //bb.Max[axis] -= 1;
+ PushID(node->ID);
+
+ // Find resizing limits by gathering list of nodes that are touching the splitter line.
+ ImVector<ImGuiDockNode*> touching_nodes[2];
+ float min_size = g.Style.WindowMinSize[axis];
+ float resize_limits[2];
+ resize_limits[0] = node->ChildNodes[0]->Pos[axis] + min_size;
+ resize_limits[1] = node->ChildNodes[1]->Pos[axis] + node->ChildNodes[1]->Size[axis] - min_size;
+
+ ImGuiID splitter_id = GetID("##Splitter");
+ if (g.ActiveId == splitter_id) // Only process when splitter is active
+ {
+ DockNodeTreeUpdateSplitterFindTouchingNode(child_0, axis, 1, &touching_nodes[0]);
+ DockNodeTreeUpdateSplitterFindTouchingNode(child_1, axis, 0, &touching_nodes[1]);
+ for (int touching_node_n = 0; touching_node_n < touching_nodes[0].Size; touching_node_n++)
+ resize_limits[0] = ImMax(resize_limits[0], touching_nodes[0][touching_node_n]->Rect().Min[axis] + min_size);
+ for (int touching_node_n = 0; touching_node_n < touching_nodes[1].Size; touching_node_n++)
+ resize_limits[1] = ImMin(resize_limits[1], touching_nodes[1][touching_node_n]->Rect().Max[axis] - min_size);
+
+ // [DEBUG] Render touching nodes & limits
+ /*
+ ImDrawList* draw_list = node->HostWindow ? GetForegroundDrawList(node->HostWindow) : GetForegroundDrawList(GetMainViewport());
+ for (int n = 0; n < 2; n++)
+ {
+ for (int touching_node_n = 0; touching_node_n < touching_nodes[n].Size; touching_node_n++)
+ draw_list->AddRect(touching_nodes[n][touching_node_n]->Pos, touching_nodes[n][touching_node_n]->Pos + touching_nodes[n][touching_node_n]->Size, IM_COL32(0, 255, 0, 255));
+ if (axis == ImGuiAxis_X)
+ draw_list->AddLine(ImVec2(resize_limits[n], node->ChildNodes[n]->Pos.y), ImVec2(resize_limits[n], node->ChildNodes[n]->Pos.y + node->ChildNodes[n]->Size.y), IM_COL32(255, 0, 255, 255), 3.0f);
+ else
+ draw_list->AddLine(ImVec2(node->ChildNodes[n]->Pos.x, resize_limits[n]), ImVec2(node->ChildNodes[n]->Pos.x + node->ChildNodes[n]->Size.x, resize_limits[n]), IM_COL32(255, 0, 255, 255), 3.0f);
+ }
+ */
+ }
+
+ // Use a short delay before highlighting the splitter (and changing the mouse cursor) in order for regular mouse movement to not highlight many splitters
+ float cur_size_0 = child_0->Size[axis];
+ float cur_size_1 = child_1->Size[axis];
+ float min_size_0 = resize_limits[0] - child_0->Pos[axis];
+ float min_size_1 = child_1->Pos[axis] + child_1->Size[axis] - resize_limits[1];
+ ImU32 bg_col = GetColorU32(ImGuiCol_WindowBg);
+ if (SplitterBehavior(bb, GetID("##Splitter"), axis, &cur_size_0, &cur_size_1, min_size_0, min_size_1, WINDOWS_HOVER_PADDING, WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER, bg_col))
+ {
+ if (touching_nodes[0].Size > 0 && touching_nodes[1].Size > 0)
+ {
+ child_0->Size[axis] = child_0->SizeRef[axis] = cur_size_0;
+ child_1->Pos[axis] -= cur_size_1 - child_1->Size[axis];
+ child_1->Size[axis] = child_1->SizeRef[axis] = cur_size_1;
+
+ // Lock the size of every node that is a sibling of the node we are touching
+ // This might be less desirable if we can merge sibling of a same axis into the same parental level.
+ for (int side_n = 0; side_n < 2; side_n++)
+ for (int touching_node_n = 0; touching_node_n < touching_nodes[side_n].Size; touching_node_n++)
+ {
+ ImGuiDockNode* touching_node = touching_nodes[side_n][touching_node_n];
+ //ImDrawList* draw_list = node->HostWindow ? GetForegroundDrawList(node->HostWindow) : GetForegroundDrawList(GetMainViewport());
+ //draw_list->AddRect(touching_node->Pos, touching_node->Pos + touching_node->Size, IM_COL32(255, 128, 0, 255));
+ while (touching_node->ParentNode != node)
+ {
+ if (touching_node->ParentNode->SplitAxis == axis)
+ {
+ // Mark other node so its size will be preserved during the upcoming call to DockNodeTreeUpdatePosSize().
+ ImGuiDockNode* node_to_preserve = touching_node->ParentNode->ChildNodes[side_n];
+ node_to_preserve->WantLockSizeOnce = true;
+ //draw_list->AddRect(touching_node->Pos, touching_node->Rect().Max, IM_COL32(255, 0, 0, 255));
+ //draw_list->AddRectFilled(node_to_preserve->Pos, node_to_preserve->Rect().Max, IM_COL32(0, 255, 0, 100));
+ }
+ touching_node = touching_node->ParentNode;
+ }
+ }
+
+ DockNodeTreeUpdatePosSize(child_0, child_0->Pos, child_0->Size);
+ DockNodeTreeUpdatePosSize(child_1, child_1->Pos, child_1->Size);
+ MarkIniSettingsDirty();
+ }
+ }
+ PopID();
+ }
+ }
+
+ if (child_0->IsVisible)
+ DockNodeTreeUpdateSplitter(child_0);
+ if (child_1->IsVisible)
+ DockNodeTreeUpdateSplitter(child_1);
+}
+
+ImGuiDockNode* ImGui::DockNodeTreeFindFallbackLeafNode(ImGuiDockNode* node)
+{
+ if (node->IsLeafNode())
+ return node;
+ if (ImGuiDockNode* leaf_node = DockNodeTreeFindFallbackLeafNode(node->ChildNodes[0]))
+ return leaf_node;
+ if (ImGuiDockNode* leaf_node = DockNodeTreeFindFallbackLeafNode(node->ChildNodes[1]))
+ return leaf_node;
+ return NULL;
+}
+
+ImGuiDockNode* ImGui::DockNodeTreeFindVisibleNodeByPos(ImGuiDockNode* node, ImVec2 pos)
+{
+ if (!node->IsVisible)
+ return NULL;
+
+ const float dock_spacing = 0.0f;// g.Style.ItemInnerSpacing.x; // FIXME: Relation to DOCKING_SPLITTER_SIZE?
+ ImRect r(node->Pos, node->Pos + node->Size);
+ r.Expand(dock_spacing * 0.5f);
+ bool inside = r.Contains(pos);
+ if (!inside)
+ return NULL;
+
+ if (node->IsLeafNode())
+ return node;
+ if (ImGuiDockNode* hovered_node = DockNodeTreeFindVisibleNodeByPos(node->ChildNodes[0], pos))
+ return hovered_node;
+ if (ImGuiDockNode* hovered_node = DockNodeTreeFindVisibleNodeByPos(node->ChildNodes[1], pos))
+ return hovered_node;
+
+ // This means we are hovering over the splitter/spacing of a parent node
+ return node;
+}
+
+//-----------------------------------------------------------------------------
+// Docking: Public Functions (SetWindowDock, DockSpace, DockSpaceOverViewport)
+//-----------------------------------------------------------------------------
+// - SetWindowDock() [Internal]
+// - DockSpace()
+// - DockSpaceOverViewport()
+//-----------------------------------------------------------------------------
+
+// [Internal] Called via SetNextWindowDockID()
+void ImGui::SetWindowDock(ImGuiWindow* window, ImGuiID dock_id, ImGuiCond cond)
+{
+ // Test condition (NB: bit 0 is always true) and clear flags for next time
+ if (cond && (window->SetWindowDockAllowFlags & cond) == 0)
+ return;
+ window->SetWindowDockAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
+
+ if (window->DockId == dock_id)
+ return;
+
+ // If the user attempt to set a dock id that is a split node, we'll dig within to find a suitable docking spot
+ ImGuiContext& g = *GImGui;
+ if (ImGuiDockNode* new_node = DockContextFindNodeByID(&g, dock_id))
+ if (new_node->IsSplitNode())
+ {
+ // Policy: Find central node or latest focused node. We first move back to our root node.
+ new_node = DockNodeGetRootNode(new_node);
+ if (new_node->CentralNode)
+ {
+ IM_ASSERT(new_node->CentralNode->IsCentralNode());
+ dock_id = new_node->CentralNode->ID;
+ }
+ else
+ {
+ dock_id = new_node->LastFocusedNodeId;
+ }
+ }
+
+ if (window->DockId == dock_id)
+ return;
+
+ if (window->DockNode)
+ DockNodeRemoveWindow(window->DockNode, window, 0);
+ window->DockId = dock_id;
+}
+
+// Create an explicit dockspace node within an existing window. Also expose dock node flags and creates a CentralNode by default.
+// The Central Node is always displayed even when empty and shrink/extend according to the requested size of its neighbors.
+// DockSpace() needs to be submitted _before_ any window they can host. If you use a dockspace, submit it early in your app.
+// When ImGuiDockNodeFlags_KeepAliveOnly is set, nothing is submitted in the current window (function may be called from any location).
+ImGuiID ImGui::DockSpace(ImGuiID dockspace_id, const ImVec2& size_arg, ImGuiDockNodeFlags flags, const ImGuiWindowClass* window_class)
+{
+ ImGuiContext& g = *GImGui;
+ ImGuiWindow* window = GetCurrentWindowRead();
+ if (!(g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable))
+ return 0;
+
+ // Early out if parent window is hidden/collapsed
+ // This is faster but also DockNodeUpdateTabBar() relies on TabBarLayout() running (which won't if SkipItems=true) to set NextSelectedTabId = 0). See #2960.
+ // If for whichever reason this is causing problem we would need to ensure that DockNodeUpdateTabBar() ends up clearing NextSelectedTabId even if SkipItems=true.
+ if (window->SkipItems)
+ flags |= ImGuiDockNodeFlags_KeepAliveOnly;
+ if ((flags & ImGuiDockNodeFlags_KeepAliveOnly) == 0)
+ window = GetCurrentWindow(); // call to set window->WriteAccessed = true;
+
+ IM_ASSERT((flags & ImGuiDockNodeFlags_DockSpace) == 0);
+ IM_ASSERT(dockspace_id != 0);
+ ImGuiDockNode* node = DockContextFindNodeByID(&g, dockspace_id);
+ if (node == NULL)
+ {
+ IMGUI_DEBUG_LOG_DOCKING("[docking] DockSpace: dockspace node 0x%08X created\n", dockspace_id);
+ node = DockContextAddNode(&g, dockspace_id);
+ node->SetLocalFlags(ImGuiDockNodeFlags_CentralNode);
+ }
+ if (window_class && window_class->ClassId != node->WindowClass.ClassId)
+ IMGUI_DEBUG_LOG_DOCKING("[docking] DockSpace: dockspace node 0x%08X: setup WindowClass 0x%08X -> 0x%08X\n", dockspace_id, node->WindowClass.ClassId, window_class->ClassId);
+ node->SharedFlags = flags;
+ node->WindowClass = window_class ? *window_class : ImGuiWindowClass();
+
+ // When a DockSpace transitioned form implicit to explicit this may be called a second time
+ // It is possible that the node has already been claimed by a docked window which appeared before the DockSpace() node, so we overwrite IsDockSpace again.
+ if (node->LastFrameActive == g.FrameCount && !(flags & ImGuiDockNodeFlags_KeepAliveOnly))
+ {
+ IM_ASSERT(node->IsDockSpace() == false && "Cannot call DockSpace() twice a frame with the same ID");
+ node->SetLocalFlags(node->LocalFlags | ImGuiDockNodeFlags_DockSpace);
+ return dockspace_id;
+ }
+ node->SetLocalFlags(node->LocalFlags | ImGuiDockNodeFlags_DockSpace);
+
+ // Keep alive mode, this is allow windows docked into this node so stay docked even if they are not visible
+ if (flags & ImGuiDockNodeFlags_KeepAliveOnly)
+ {
+ node->LastFrameAlive = g.FrameCount;
+ return dockspace_id;
+ }
+
+ const ImVec2 content_avail = GetContentRegionAvail();
+ ImVec2 size = ImTrunc(size_arg);
+ if (size.x <= 0.0f)
+ size.x = ImMax(content_avail.x + size.x, 4.0f); // Arbitrary minimum child size (0.0f causing too much issues)
+ if (size.y <= 0.0f)
+ size.y = ImMax(content_avail.y + size.y, 4.0f);
+ IM_ASSERT(size.x > 0.0f && size.y > 0.0f);
+
+ node->Pos = window->DC.CursorPos;
+ node->Size = node->SizeRef = size;
+ SetNextWindowPos(node->Pos);
+ SetNextWindowSize(node->Size);
+ g.NextWindowData.PosUndock = false;
+
+ // FIXME-DOCK: Why do we need a child window to host a dockspace, could we host it in the existing window?
+ // FIXME-DOCK: What is the reason for not simply calling BeginChild()? (OK to have a reason but should be commented)
+ ImGuiWindowFlags window_flags = ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_DockNodeHost;
+ window_flags |= ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar;
+ window_flags |= ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse;
+ window_flags |= ImGuiWindowFlags_NoBackground;
+
+ char title[256];
+ ImFormatString(title, IM_ARRAYSIZE(title), "%s/DockSpace_%08X", window->Name, dockspace_id);
+
+ PushStyleVar(ImGuiStyleVar_ChildBorderSize, 0.0f);
+ Begin(title, NULL, window_flags);
+ PopStyleVar();
+
+ ImGuiWindow* host_window = g.CurrentWindow;
+ DockNodeSetupHostWindow(node, host_window);
+ host_window->ChildId = window->GetID(title);
+ node->OnlyNodeWithWindows = NULL;
+
+ IM_ASSERT(node->IsRootNode());
+
+ // We need to handle the rare case were a central node is missing.
+ // This can happen if the node was first created manually with DockBuilderAddNode() but _without_ the ImGuiDockNodeFlags_Dockspace.
+ // Doing it correctly would set the _CentralNode flags, which would then propagate according to subsequent split.
+ // It would also be ambiguous to attempt to assign a central node while there are split nodes, so we wait until there's a single node remaining.
+ // The specific sub-property of _CentralNode we are interested in recovering here is the "Don't delete when empty" property,
+ // as it doesn't make sense for an empty dockspace to not have this property.
+ if (node->IsLeafNode() && !node->IsCentralNode())
+ node->SetLocalFlags(node->LocalFlags | ImGuiDockNodeFlags_CentralNode);
+
+ // Update the node
+ DockNodeUpdate(node);
+
+ End();
+
+ ImRect bb(node->Pos, node->Pos + size);
+ ItemSize(size);
+ ItemAdd(bb, dockspace_id, NULL, ImGuiItemFlags_NoNav); // Not a nav point (could be, would need to draw the nav rect and replicate/refactor activation from BeginChild(), but seems like CTRL+Tab works better here?)
+ if ((g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect) && IsWindowChildOf(g.HoveredWindow, host_window, false, true)) // To fullfill IsItemHovered(), similar to EndChild()
+ g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow;
+
+ return dockspace_id;
+}
+
+// Tips: Use with ImGuiDockNodeFlags_PassthruCentralNode!
+// The limitation with this call is that your window won't have a local menu bar, but you can also use BeginMainMenuBar().
+// Even though we could pass window flags, it would also require the user to be able to call BeginMenuBar() somehow meaning we can't Begin/End in a single function.
+// If you really want a menu bar inside the same window as the one hosting the dockspace, you will need to copy this code somewhere and tweak it.
+ImGuiID ImGui::DockSpaceOverViewport(ImGuiID dockspace_id, const ImGuiViewport* viewport, ImGuiDockNodeFlags dockspace_flags, const ImGuiWindowClass* window_class)
+{
+ if (viewport == NULL)
+ viewport = GetMainViewport();
+
+ // Submit a window filling the entire viewport
+ SetNextWindowPos(viewport->WorkPos);
+ SetNextWindowSize(viewport->WorkSize);
+ SetNextWindowViewport(viewport->ID);
+
+ ImGuiWindowFlags host_window_flags = 0;
+ host_window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDocking;
+ host_window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;
+ if (dockspace_flags & ImGuiDockNodeFlags_PassthruCentralNode)
+ host_window_flags |= ImGuiWindowFlags_NoBackground;
+
+ char label[32];
+ ImFormatString(label, IM_ARRAYSIZE(label), "WindowOverViewport_%08X", viewport->ID);
+
+ PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
+ PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
+ PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
+ Begin(label, NULL, host_window_flags);
+ PopStyleVar(3);
+
+ // Submit the dockspace
+ if (dockspace_id == 0)
+ dockspace_id = GetID("DockSpace");
+ DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags, window_class);
+
+ End();
+
+ return dockspace_id;
+}
+
+//-----------------------------------------------------------------------------
+// Docking: Builder Functions
+//-----------------------------------------------------------------------------
+// Very early end-user API to manipulate dock nodes.
+// Only available in imgui_internal.h. Expect this API to change/break!
+// It is expected that those functions are all called _before_ the dockspace node submission.
+//-----------------------------------------------------------------------------
+// - DockBuilderDockWindow()
+// - DockBuilderGetNode()
+// - DockBuilderSetNodePos()
+// - DockBuilderSetNodeSize()
+// - DockBuilderAddNode()
+// - DockBuilderRemoveNode()
+// - DockBuilderRemoveNodeChildNodes()
+// - DockBuilderRemoveNodeDockedWindows()
+// - DockBuilderSplitNode()
+// - DockBuilderCopyNodeRec()
+// - DockBuilderCopyNode()
+// - DockBuilderCopyWindowSettings()
+// - DockBuilderCopyDockSpace()
+// - DockBuilderFinish()
+//-----------------------------------------------------------------------------
+
+void ImGui::DockBuilderDockWindow(const char* window_name, ImGuiID node_id)
+{
+ // We don't preserve relative order of multiple docked windows (by clearing DockOrder back to -1)
+ ImGuiContext& g = *GImGui; IM_UNUSED(g);
+ IMGUI_DEBUG_LOG_DOCKING("[docking] DockBuilderDockWindow '%s' to node 0x%08X\n", window_name, node_id);
+ ImGuiID window_id = ImHashStr(window_name);
+ if (ImGuiWindow* window = FindWindowByID(window_id))
+ {
+ // Apply to created window
+ ImGuiID prev_node_id = window->DockId;
+ SetWindowDock(window, node_id, ImGuiCond_Always);
+ if (window->DockId != prev_node_id)
+ window->DockOrder = -1;
+ }
+ else
+ {
+ // Apply to settings
+ ImGuiWindowSettings* settings = FindWindowSettingsByID(window_id);
+ if (settings == NULL)
+ settings = CreateNewWindowSettings(window_name);
+ if (settings->DockId != node_id)
+ settings->DockOrder = -1;
+ settings->DockId = node_id;
+ }
+}
+
+ImGuiDockNode* ImGui::DockBuilderGetNode(ImGuiID node_id)
+{
+ ImGuiContext& g = *GImGui;
+ return DockContextFindNodeByID(&g, node_id);
+}
+
+void ImGui::DockBuilderSetNodePos(ImGuiID node_id, ImVec2 pos)
+{
+ ImGuiContext& g = *GImGui;
+ ImGuiDockNode* node = DockContextFindNodeByID(&g, node_id);
+ if (node == NULL)
+ return;
+ node->Pos = pos;
+ node->AuthorityForPos = ImGuiDataAuthority_DockNode;
+}
+
+void ImGui::DockBuilderSetNodeSize(ImGuiID node_id, ImVec2 size)
+{
+ ImGuiContext& g = *GImGui;
+ ImGuiDockNode* node = DockContextFindNodeByID(&g, node_id);
+ if (node == NULL)
+ return;
+ IM_ASSERT(size.x > 0.0f && size.y > 0.0f);
+ node->Size = node->SizeRef = size;
+ node->AuthorityForSize = ImGuiDataAuthority_DockNode;
+}
+
+// Make sure to use the ImGuiDockNodeFlags_DockSpace flag to create a dockspace node! Otherwise this will create a floating node!
+// - Floating node: you can then call DockBuilderSetNodePos()/DockBuilderSetNodeSize() to position and size the floating node.
+// - Dockspace node: calling DockBuilderSetNodePos() is unnecessary.
+// - If you intend to split a node immediately after creation using DockBuilderSplitNode(), make sure to call DockBuilderSetNodeSize() beforehand!
+// For various reason, the splitting code currently needs a base size otherwise space may not be allocated as precisely as you would expect.
+// - Use (id == 0) to let the system allocate a node identifier.
+// - Existing node with a same id will be removed.
+ImGuiID ImGui::DockBuilderAddNode(ImGuiID node_id, ImGuiDockNodeFlags flags)
+{
+ ImGuiContext& g = *GImGui; IM_UNUSED(g);
+ IMGUI_DEBUG_LOG_DOCKING("[docking] DockBuilderAddNode 0x%08X flags=%08X\n", node_id, flags);
+
+ if (node_id != 0)
+ DockBuilderRemoveNode(node_id);
+
+ ImGuiDockNode* node = NULL;
+ if (flags & ImGuiDockNodeFlags_DockSpace)
+ {
+ DockSpace(node_id, ImVec2(0, 0), (flags & ~ImGuiDockNodeFlags_DockSpace) | ImGuiDockNodeFlags_KeepAliveOnly);
+ node = DockContextFindNodeByID(&g, node_id);
+ }
+ else
+ {
+ node = DockContextAddNode(&g, node_id);
+ node->SetLocalFlags(flags);
+ }
+ node->LastFrameAlive = g.FrameCount; // Set this otherwise BeginDocked will undock during the same frame.
+ return node->ID;
+}
+
+void ImGui::DockBuilderRemoveNode(ImGuiID node_id)
+{
+ ImGuiContext& g = *GImGui; IM_UNUSED(g);
+ IMGUI_DEBUG_LOG_DOCKING("[docking] DockBuilderRemoveNode 0x%08X\n", node_id);
+
+ ImGuiDockNode* node = DockContextFindNodeByID(&g, node_id);
+ if (node == NULL)
+ return;
+ DockBuilderRemoveNodeDockedWindows(node_id, true);
+ DockBuilderRemoveNodeChildNodes(node_id);
+ // Node may have moved or deleted if e.g. any merge happened
+ node = DockContextFindNodeByID(&g, node_id);
+ if (node == NULL)
+ return;
+ if (node->IsCentralNode() && node->ParentNode)
+ node->ParentNode->SetLocalFlags(node->ParentNode->LocalFlags | ImGuiDockNodeFlags_CentralNode);
+ DockContextRemoveNode(&g, node, true);
+}
+
+// root_id = 0 to remove all, root_id != 0 to remove child of given node.
+void ImGui::DockBuilderRemoveNodeChildNodes(ImGuiID root_id)
+{
+ ImGuiContext& g = *GImGui;
+ ImGuiDockContext* dc = &g.DockContext;
+
+ ImGuiDockNode* root_node = root_id ? DockContextFindNodeByID(&g, root_id) : NULL;
+ if (root_id && root_node == NULL)
+ return;
+ bool has_central_node = false;
+
+ ImGuiDataAuthority backup_root_node_authority_for_pos = root_node ? root_node->AuthorityForPos : ImGuiDataAuthority_Auto;
+ ImGuiDataAuthority backup_root_node_authority_for_size = root_node ? root_node->AuthorityForSize : ImGuiDataAuthority_Auto;
+
+ // Process active windows
+ ImVector<ImGuiDockNode*> nodes_to_remove;
+ for (int n = 0; n < dc->Nodes.Data.Size; n++)
+ if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p)
+ {
+ bool want_removal = (root_id == 0) || (node->ID != root_id && DockNodeGetRootNode(node)->ID == root_id);
+ if (want_removal)
+ {
+ if (node->IsCentralNode())
+ has_central_node = true;
+ if (root_id != 0)
+ DockContextQueueNotifyRemovedNode(&g, node);
+ if (root_node)
+ {
+ DockNodeMoveWindows(root_node, node);
+ DockSettingsRenameNodeReferences(node->ID, root_node->ID);
+ }
+ nodes_to_remove.push_back(node);
+ }
+ }
+
+ // DockNodeMoveWindows->DockNodeAddWindow will normally set those when reaching two windows (which is only adequate during interactive merge)
+ // Make sure we don't lose our current pos/size. (FIXME-DOCK: Consider tidying up that code in DockNodeAddWindow instead)
+ if (root_node)
+ {
+ root_node->AuthorityForPos = backup_root_node_authority_for_pos;
+ root_node->AuthorityForSize = backup_root_node_authority_for_size;
+ }
+
+ // Apply to settings
+ for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings))
+ if (ImGuiID window_settings_dock_id = settings->DockId)
+ for (int n = 0; n < nodes_to_remove.Size; n++)
+ if (nodes_to_remove[n]->ID == window_settings_dock_id)
+ {
+ settings->DockId = root_id;
+ break;
+ }
+
+ // Not really efficient, but easier to destroy a whole hierarchy considering DockContextRemoveNode is attempting to merge nodes
+ if (nodes_to_remove.Size > 1)
+ ImQsort(nodes_to_remove.Data, nodes_to_remove.Size, sizeof(ImGuiDockNode*), DockNodeComparerDepthMostFirst);
+ for (int n = 0; n < nodes_to_remove.Size; n++)
+ DockContextRemoveNode(&g, nodes_to_remove[n], false);
+
+ if (root_id == 0)
+ {
+ dc->Nodes.Clear();
+ dc->Requests.clear();
+ }
+ else if (has_central_node)
+ {
+ root_node->CentralNode = root_node;
+ root_node->SetLocalFlags(root_node->LocalFlags | ImGuiDockNodeFlags_CentralNode);
+ }
+}
+
+void ImGui::DockBuilderRemoveNodeDockedWindows(ImGuiID root_id, bool clear_settings_refs)
+{
+ // Clear references in settings
+ ImGuiContext& g = *GImGui;
+ if (clear_settings_refs)
+ {
+ for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings))
+ {
+ bool want_removal = (root_id == 0) || (settings->DockId == root_id);
+ if (!want_removal && settings->DockId != 0)
+ if (ImGuiDockNode* node = DockContextFindNodeByID(&g, settings->DockId))
+ if (DockNodeGetRootNode(node)->ID == root_id)
+ want_removal = true;
+ if (want_removal)
+ settings->DockId = 0;
+ }
+ }
+
+ // Clear references in windows
+ for (int n = 0; n < g.Windows.Size; n++)
+ {
+ ImGuiWindow* window = g.Windows[n];
+ bool want_removal = (root_id == 0) || (window->DockNode && DockNodeGetRootNode(window->DockNode)->ID == root_id) || (window->DockNodeAsHost && window->DockNodeAsHost->ID == root_id);
+ if (want_removal)
+ {
+ const ImGuiID backup_dock_id = window->DockId;
+ IM_UNUSED(backup_dock_id);
+ DockContextProcessUndockWindow(&g, window, clear_settings_refs);
+ if (!clear_settings_refs)
+ IM_ASSERT(window->DockId == backup_dock_id);
+ }
+ }
+}
+
+// If 'out_id_at_dir' or 'out_id_at_opposite_dir' are non NULL, the function will write out the ID of the two new nodes created.
+// Return value is ID of the node at the specified direction, so same as (*out_id_at_dir) if that pointer is set.
+// FIXME-DOCK: We are not exposing nor using split_outer.
+ImGuiID ImGui::DockBuilderSplitNode(ImGuiID id, ImGuiDir split_dir, float size_ratio_for_node_at_dir, ImGuiID* out_id_at_dir, ImGuiID* out_id_at_opposite_dir)
+{
+ ImGuiContext& g = *GImGui;
+ IM_ASSERT(split_dir != ImGuiDir_None);
+ IMGUI_DEBUG_LOG_DOCKING("[docking] DockBuilderSplitNode: node 0x%08X, split_dir %d\n", id, split_dir);
+
+ ImGuiDockNode* node = DockContextFindNodeByID(&g, id);
+ if (node == NULL)
+ {
+ IM_ASSERT(node != NULL);
+ return 0;
+ }
+
+ IM_ASSERT(!node->IsSplitNode()); // Assert if already Split
+
+ ImGuiDockRequest req;
+ req.Type = ImGuiDockRequestType_Split;
+ req.DockTargetWindow = NULL;
+ req.DockTargetNode = node;
+ req.DockPayload = NULL;
+ req.DockSplitDir = split_dir;
+ req.DockSplitRatio = ImSaturate((split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Up) ? size_ratio_for_node_at_dir : 1.0f - size_ratio_for_node_at_dir);
+ req.DockSplitOuter = false;
+ DockContextProcessDock(&g, &req);
+
+ ImGuiID id_at_dir = node->ChildNodes[(split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Up) ? 0 : 1]->ID;
+ ImGuiID id_at_opposite_dir = node->ChildNodes[(split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Up) ? 1 : 0]->ID;
+ if (out_id_at_dir)
+ *out_id_at_dir = id_at_dir;
+ if (out_id_at_opposite_dir)
+ *out_id_at_opposite_dir = id_at_opposite_dir;
+ return id_at_dir;
+}
+
+static ImGuiDockNode* DockBuilderCopyNodeRec(ImGuiDockNode* src_node, ImGuiID dst_node_id_if_known, ImVector<ImGuiID>* out_node_remap_pairs)
+{
+ ImGuiContext& g = *GImGui;
+ ImGuiDockNode* dst_node = ImGui::DockContextAddNode(&g, dst_node_id_if_known);
+ dst_node->SharedFlags = src_node->SharedFlags;
+ dst_node->LocalFlags = src_node->LocalFlags;
+ dst_node->LocalFlagsInWindows = ImGuiDockNodeFlags_None;
+ dst_node->Pos = src_node->Pos;
+ dst_node->Size = src_node->Size;
+ dst_node->SizeRef = src_node->SizeRef;
+ dst_node->SplitAxis = src_node->SplitAxis;
+ dst_node->UpdateMergedFlags();
+
+ out_node_remap_pairs->push_back(src_node->ID);
+ out_node_remap_pairs->push_back(dst_node->ID);
+
+ for (int child_n = 0; child_n < IM_ARRAYSIZE(src_node->ChildNodes); child_n++)
+ if (src_node->ChildNodes[child_n])
+ {
+ dst_node->ChildNodes[child_n] = DockBuilderCopyNodeRec(src_node->ChildNodes[child_n], 0, out_node_remap_pairs);
+ dst_node->ChildNodes[child_n]->ParentNode = dst_node;
+ }
+
+ IMGUI_DEBUG_LOG_DOCKING("[docking] Fork node %08X -> %08X (%d childs)\n", src_node->ID, dst_node->ID, dst_node->IsSplitNode() ? 2 : 0);
+ return dst_node;
+}
+
+void ImGui::DockBuilderCopyNode(ImGuiID src_node_id, ImGuiID dst_node_id, ImVector<ImGuiID>* out_node_remap_pairs)
+{
+ ImGuiContext& g = *GImGui;
+ IM_ASSERT(src_node_id != 0);
+ IM_ASSERT(dst_node_id != 0);
+ IM_ASSERT(out_node_remap_pairs != NULL);
+
+ DockBuilderRemoveNode(dst_node_id);
+
+ ImGuiDockNode* src_node = DockContextFindNodeByID(&g, src_node_id);
+ IM_ASSERT(src_node != NULL);
+
+ out_node_remap_pairs->clear();
+ DockBuilderCopyNodeRec(src_node, dst_node_id, out_node_remap_pairs);
+
+ IM_ASSERT((out_node_remap_pairs->Size % 2) == 0);
+}
+
+void ImGui::DockBuilderCopyWindowSettings(const char* src_name, const char* dst_name)
+{
+ ImGuiWindow* src_window = FindWindowByName(src_name);
+ if (src_window == NULL)
+ return;
+ if (ImGuiWindow* dst_window = FindWindowByName(dst_name))
+ {
+ dst_window->Pos = src_window->Pos;
+ dst_window->Size = src_window->Size;
+ dst_window->SizeFull = src_window->SizeFull;
+ dst_window->Collapsed = src_window->Collapsed;
+ }
+ else
+ {
+ ImGuiWindowSettings* dst_settings = FindWindowSettingsByID(ImHashStr(dst_name));
+ if (!dst_settings)
+ dst_settings = CreateNewWindowSettings(dst_name);
+ ImVec2ih window_pos_2ih = ImVec2ih(src_window->Pos);
+ if (src_window->ViewportId != 0 && src_window->ViewportId != IMGUI_VIEWPORT_DEFAULT_ID)
+ {
+ dst_settings->ViewportPos = window_pos_2ih;
+ dst_settings->ViewportId = src_window->ViewportId;
+ dst_settings->Pos = ImVec2ih(0, 0);
+ }
+ else
+ {
+ dst_settings->Pos = window_pos_2ih;
+ }
+ dst_settings->Size = ImVec2ih(src_window->SizeFull);
+ dst_settings->Collapsed = src_window->Collapsed;
+ }
+}
+
+// FIXME: Will probably want to change this signature, in particular how the window remapping pairs are passed.
+void ImGui::DockBuilderCopyDockSpace(ImGuiID src_dockspace_id, ImGuiID dst_dockspace_id, ImVector<const char*>* in_window_remap_pairs)
+{
+ ImGuiContext& g = *GImGui;
+ IM_ASSERT(src_dockspace_id != 0);
+ IM_ASSERT(dst_dockspace_id != 0);
+ IM_ASSERT(in_window_remap_pairs != NULL);
+ IM_ASSERT((in_window_remap_pairs->Size % 2) == 0);
+
+ // Duplicate entire dock
+ // FIXME: When overwriting dst_dockspace_id, windows that aren't part of our dockspace window class but that are docked in a same node will be split apart,
+ // whereas we could attempt to at least keep them together in a new, same floating node.
+ ImVector<ImGuiID> node_remap_pairs;
+ DockBuilderCopyNode(src_dockspace_id, dst_dockspace_id, &node_remap_pairs);
+
+ // Attempt to transition all the upcoming windows associated to dst_dockspace_id into the newly created hierarchy of dock nodes
+ // (The windows associated to src_dockspace_id are staying in place)
+ ImVector<ImGuiID> src_windows;
+ for (int remap_window_n = 0; remap_window_n < in_window_remap_pairs->Size; remap_window_n += 2)
+ {
+ const char* src_window_name = (*in_window_remap_pairs)[remap_window_n];
+ const char* dst_window_name = (*in_window_remap_pairs)[remap_window_n + 1];
+ ImGuiID src_window_id = ImHashStr(src_window_name);
+ src_windows.push_back(src_window_id);
+
+ // Search in the remapping tables
+ ImGuiID src_dock_id = 0;
+ if (ImGuiWindow* src_window = FindWindowByID(src_window_id))
+ src_dock_id = src_window->DockId;
+ else if (ImGuiWindowSettings* src_window_settings = FindWindowSettingsByID(src_window_id))
+ src_dock_id = src_window_settings->DockId;
+ ImGuiID dst_dock_id = 0;
+ for (int dock_remap_n = 0; dock_remap_n < node_remap_pairs.Size; dock_remap_n += 2)
+ if (node_remap_pairs[dock_remap_n] == src_dock_id)
+ {
+ dst_dock_id = node_remap_pairs[dock_remap_n + 1];
+ //node_remap_pairs[dock_remap_n] = node_remap_pairs[dock_remap_n + 1] = 0; // Clear
+ break;
+ }
+
+ if (dst_dock_id != 0)
+ {
+ // Docked windows gets redocked into the new node hierarchy.
+ IMGUI_DEBUG_LOG_DOCKING("[docking] Remap live window '%s' 0x%08X -> '%s' 0x%08X\n", src_window_name, src_dock_id, dst_window_name, dst_dock_id);
+ DockBuilderDockWindow(dst_window_name, dst_dock_id);
+ }
+ else
+ {
+ // Floating windows gets their settings transferred (regardless of whether the new window already exist or not)
+ // When this is leading to a Copy and not a Move, we would get two overlapping floating windows. Could we possibly dock them together?
+ IMGUI_DEBUG_LOG_DOCKING("[docking] Remap window settings '%s' -> '%s'\n", src_window_name, dst_window_name);
+ DockBuilderCopyWindowSettings(src_window_name, dst_window_name);
+ }
+ }
+
+ // Anything else in the source nodes of 'node_remap_pairs' are windows that are not included in the remapping list.
+ // Find those windows and move to them to the cloned dock node. This may be optional?
+ // Dock those are a second step as undocking would invalidate source dock nodes.
+ struct DockRemainingWindowTask { ImGuiWindow* Window; ImGuiID DockId; DockRemainingWindowTask(ImGuiWindow* window, ImGuiID dock_id) { Window = window; DockId = dock_id; } };
+ ImVector<DockRemainingWindowTask> dock_remaining_windows;
+ for (int dock_remap_n = 0; dock_remap_n < node_remap_pairs.Size; dock_remap_n += 2)
+ if (ImGuiID src_dock_id = node_remap_pairs[dock_remap_n])
+ {
+ ImGuiID dst_dock_id = node_remap_pairs[dock_remap_n + 1];
+ ImGuiDockNode* node = DockBuilderGetNode(src_dock_id);
+ for (int window_n = 0; window_n < node->Windows.Size; window_n++)
+ {
+ ImGuiWindow* window = node->Windows[window_n];
+ if (src_windows.contains(window->ID))
+ continue;
+
+ // Docked windows gets redocked into the new node hierarchy.
+ IMGUI_DEBUG_LOG_DOCKING("[docking] Remap window '%s' %08X -> %08X\n", window->Name, src_dock_id, dst_dock_id);
+ dock_remaining_windows.push_back(DockRemainingWindowTask(window, dst_dock_id));
+ }
+ }
+ for (const DockRemainingWindowTask& task : dock_remaining_windows)
+ DockBuilderDockWindow(task.Window->Name, task.DockId);
+}
+
+// FIXME-DOCK: This is awkward because in series of split user is likely to loose access to its root node.
+void ImGui::DockBuilderFinish(ImGuiID root_id)
+{
+ ImGuiContext& g = *GImGui;
+ //DockContextRebuild(&g);
+ DockContextBuildAddWindowsToNodes(&g, root_id);
+}
+
+//-----------------------------------------------------------------------------
+// Docking: Begin/End Support Functions (called from Begin/End)
+//-----------------------------------------------------------------------------
+// - GetWindowAlwaysWantOwnTabBar()
+// - DockContextBindNodeToWindow()
+// - BeginDocked()
+// - BeginDockableDragDropSource()
+// - BeginDockableDragDropTarget()
+//-----------------------------------------------------------------------------
+
+bool ImGui::GetWindowAlwaysWantOwnTabBar(ImGuiWindow* window)
+{
+ ImGuiContext& g = *GImGui;
+ if (g.IO.ConfigDockingAlwaysTabBar || window->WindowClass.DockingAlwaysTabBar)
+ if ((window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoDocking)) == 0)
+ if (!window->IsFallbackWindow) // We don't support AlwaysTabBar on the fallback/implicit window to avoid unused dock-node overhead/noise
+ return true;
+ return false;
+}
+
+static ImGuiDockNode* ImGui::DockContextBindNodeToWindow(ImGuiContext* ctx, ImGuiWindow* window)
+{
+ ImGuiContext& g = *ctx;
+ ImGuiDockNode* node = DockContextFindNodeByID(ctx, window->DockId);
+ IM_ASSERT(window->DockNode == NULL);
+
+ // We should not be docking into a split node (SetWindowDock should avoid this)
+ if (node && node->IsSplitNode())
+ {
+ DockContextProcessUndockWindow(ctx, window);
+ return NULL;
+ }
+
+ // Create node
+ if (node == NULL)
+ {
+ node = DockContextAddNode(ctx, window->DockId);
+ node->AuthorityForPos = node->AuthorityForSize = node->AuthorityForViewport = ImGuiDataAuthority_Window;
+ node->LastFrameAlive = g.FrameCount;
+ }
+
+ // If the node just turned visible and is part of a hierarchy, it doesn't have a Size assigned by DockNodeTreeUpdatePosSize() yet,
+ // so we're forcing a Pos/Size update from the first ancestor that is already visible (often it will be the root node).
+ // If we don't do this, the window will be assigned a zero-size on its first frame, which won't ideally warm up the layout.
+ // This is a little wonky because we don't normally update the Pos/Size of visible node mid-frame.
+ if (!node->IsVisible)
+ {
+ ImGuiDockNode* ancestor_node = node;
+ while (!ancestor_node->IsVisible && ancestor_node->ParentNode)
+ ancestor_node = ancestor_node->ParentNode;
+ IM_ASSERT(ancestor_node->Size.x > 0.0f && ancestor_node->Size.y > 0.0f);
+ DockNodeUpdateHasCentralNodeChild(DockNodeGetRootNode(ancestor_node));
+ DockNodeTreeUpdatePosSize(ancestor_node, ancestor_node->Pos, ancestor_node->Size, node);
+ }
+
+ // Add window to node
+ bool node_was_visible = node->IsVisible;
+ DockNodeAddWindow(node, window, true);
+ node->IsVisible = node_was_visible; // Don't mark visible right away (so DockContextEndFrame() doesn't render it, maybe other side effects? will see)
+ IM_ASSERT(node == window->DockNode);
+ return node;
+}
+
+static void StoreDockStyleForWindow(ImGuiWindow* window)
+{
+ ImGuiContext& g = *GImGui;
+ for (int color_n = 0; color_n < ImGuiWindowDockStyleCol_COUNT; color_n++)
+ window->DockStyle.Colors[color_n] = ImGui::ColorConvertFloat4ToU32(g.Style.Colors[GWindowDockStyleColors[color_n]]);
+}
+
+void ImGui::BeginDocked(ImGuiWindow* window, bool* p_open)
+{
+ ImGuiContext& g = *GImGui;
+
+ // Clear fields ahead so most early-out paths don't have to do it
+ window->DockIsActive = window->DockNodeIsVisible = window->DockTabIsVisible = false;
+
+ const bool auto_dock_node = GetWindowAlwaysWantOwnTabBar(window);
+ if (auto_dock_node)
+ {
+ if (window->DockId == 0)
+ {
+ IM_ASSERT(window->DockNode == NULL);
+ window->DockId = DockContextGenNodeID(&g);
+ }
+ }
+ else
+ {
+ // Calling SetNextWindowPos() undock windows by default (by setting PosUndock)
+ bool want_undock = false;
+ want_undock |= (window->Flags & ImGuiWindowFlags_NoDocking) != 0;
+ want_undock |= (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasPos) && (window->SetWindowPosAllowFlags & g.NextWindowData.PosCond) && g.NextWindowData.PosUndock;
+ if (want_undock)
+ {
+ DockContextProcessUndockWindow(&g, window);
+ return;
+ }
+ }
+
+ // Bind to our dock node
+ ImGuiDockNode* node = window->DockNode;
+ if (node != NULL)
+ IM_ASSERT(window->DockId == node->ID);
+ if (window->DockId != 0 && node == NULL)
+ {
+ node = DockContextBindNodeToWindow(&g, window);
+ if (node == NULL)
+ return;
+ }
+
+#if 0
+ // Undock if the ImGuiDockNodeFlags_NoDockingInCentralNode got set
+ if (node->IsCentralNode && (node->Flags & ImGuiDockNodeFlags_NoDockingInCentralNode))
+ {
+ DockContextProcessUndockWindow(ctx, window);
+ return;
+ }
+#endif
+
+ // Undock if our dockspace node disappeared
+ // Note how we are testing for LastFrameAlive and NOT LastFrameActive. A DockSpace node can be maintained alive while being inactive with ImGuiDockNodeFlags_KeepAliveOnly.
+ if (node->LastFrameAlive < g.FrameCount)
+ {
+ // If the window has been orphaned, transition the docknode to an implicit node processed in DockContextNewFrameUpdateDocking()
+ ImGuiDockNode* root_node = DockNodeGetRootNode(node);
+ if (root_node->LastFrameAlive < g.FrameCount)
+ DockContextProcessUndockWindow(&g, window);
+ else
+ window->DockIsActive = true;
+ return;
+ }
+
+ // Store style overrides
+ StoreDockStyleForWindow(window);
+
+ // Fast path return. It is common for windows to hold on a persistent DockId but be the only visible window,
+ // and never create neither a host window neither a tab bar.
+ // FIXME-DOCK: replace ->HostWindow NULL compare with something more explicit (~was initially intended as a first frame test)
+ if (node->HostWindow == NULL)
+ {
+ if (node->State == ImGuiDockNodeState_HostWindowHiddenBecauseWindowsAreResizing)
+ window->DockIsActive = true;
+ if (node->Windows.Size > 1 && window->Appearing) // Only hide appearing window
+ DockNodeHideWindowDuringHostWindowCreation(window);
+ return;
+ }
+
+ // We can have zero-sized nodes (e.g. children of a small-size dockspace)
+ IM_ASSERT(node->HostWindow);
+ IM_ASSERT(node->IsLeafNode());
+ IM_ASSERT(node->Size.x >= 0.0f && node->Size.y >= 0.0f);
+ node->State = ImGuiDockNodeState_HostWindowVisible;
+
+ // Undock if we are submitted earlier than the host window
+ if (!(node->MergedFlags & ImGuiDockNodeFlags_KeepAliveOnly) && window->BeginOrderWithinContext < node->HostWindow->BeginOrderWithinContext)
+ {
+ DockContextProcessUndockWindow(&g, window);
+ return;
+ }
+
+ // Position/Size window
+ SetNextWindowPos(node->Pos);
+ SetNextWindowSize(node->Size);
+ g.NextWindowData.PosUndock = false; // Cancel implicit undocking of SetNextWindowPos()
+ window->DockIsActive = true;
+ window->DockNodeIsVisible = true;
+ window->DockTabIsVisible = false;
+ if (node->MergedFlags & ImGuiDockNodeFlags_KeepAliveOnly)
+ return;
+
+ // When the window is selected we mark it as visible.
+ if (node->VisibleWindow == window)
+ window->DockTabIsVisible = true;
+
+ // Update window flag
+ IM_ASSERT((window->Flags & ImGuiWindowFlags_ChildWindow) == 0);
+ window->Flags |= ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NoResize;
+ window->ChildFlags |= ImGuiChildFlags_AlwaysUseWindowPadding;
+ if (node->IsHiddenTabBar() || node->IsNoTabBar())
+ window->Flags |= ImGuiWindowFlags_NoTitleBar;
+ else
+ window->Flags &= ~ImGuiWindowFlags_NoTitleBar; // Clear the NoTitleBar flag in case the user set it: confusingly enough we need a title bar height so we are correctly offset, but it won't be displayed!
+
+ // Save new dock order only if the window has been visible once already
+ // This allows multiple windows to be created in the same frame and have their respective dock orders preserved.
+ if (node->TabBar && window->WasActive)
+ window->DockOrder = (short)DockNodeGetTabOrder(window);
+
+ if ((node->WantCloseAll || node->WantCloseTabId == window->TabId) && p_open != NULL)
+ *p_open = false;
+
+ // Update ChildId to allow returning from Child to Parent with Escape
+ ImGuiWindow* parent_window = window->DockNode->HostWindow;
+ window->ChildId = parent_window->GetID(window->Name);
+}
+
+void ImGui::BeginDockableDragDropSource(ImGuiWindow* window)
+{
+ ImGuiContext& g = *GImGui;
+ IM_ASSERT(g.ActiveId == window->MoveId);
+ IM_ASSERT(g.MovingWindow == window);
+ IM_ASSERT(g.CurrentWindow == window);
+
+ // 0: Hold SHIFT to disable docking, 1: Hold SHIFT to enable docking.
+ if (g.IO.ConfigDockingWithShift != g.IO.KeyShift)
+ {
+ // When ConfigDockingWithShift is set, display a tooltip to increase UI affordance.
+ // We cannot set for HoveredWindowUnderMovingWindow != NULL here, as it is only valid/useful when drag and drop is already active
+ // (because of the 'is_mouse_dragging_with_an_expected_destination' logic in UpdateViewportsNewFrame() function)
+ IM_ASSERT(g.NextWindowData.Flags == 0);
+ if (g.IO.ConfigDockingWithShift && g.MouseStationaryTimer >= 1.0f && g.ActiveId >= 1.0f)
+ SetTooltip("%s", LocalizeGetMsg(ImGuiLocKey_DockingHoldShiftToDock));
+ return;
+ }
+
+ g.LastItemData.ID = window->MoveId;
+ window = window->RootWindowDockTree;
+ IM_ASSERT((window->Flags & ImGuiWindowFlags_NoDocking) == 0);
+ bool is_drag_docking = (g.IO.ConfigDockingWithShift) || ImRect(0, 0, window->SizeFull.x, GetFrameHeight()).Contains(g.ActiveIdClickOffset); // FIXME-DOCKING: Need to make this stateful and explicit
+ if (is_drag_docking && BeginDragDropSource(ImGuiDragDropFlags_SourceNoPreviewTooltip | ImGuiDragDropFlags_SourceNoHoldToOpenOthers | ImGuiDragDropFlags_SourceAutoExpirePayload))
+ {
+ SetDragDropPayload(IMGUI_PAYLOAD_TYPE_WINDOW, &window, sizeof(window));
+ EndDragDropSource();
+ StoreDockStyleForWindow(window); // Store style overrides while dragging (even when not docked) because docking preview may need it.
+ }
+}
+
+void ImGui::BeginDockableDragDropTarget(ImGuiWindow* window)
+{
+ ImGuiContext& g = *GImGui;
+
+ //IM_ASSERT(window->RootWindowDockTree == window); // May also be a DockSpace
+ IM_ASSERT((window->Flags & ImGuiWindowFlags_NoDocking) == 0);
+ if (!g.DragDropActive)
+ return;
+ //GetForegroundDrawList(window)->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 255, 0, 255));
+ if (!BeginDragDropTargetCustom(window->Rect(), window->ID))
+ return;
+
+ // Peek into the payload before calling AcceptDragDropPayload() so we can handle overlapping dock nodes with filtering
+ // (this is a little unusual pattern, normally most code would call AcceptDragDropPayload directly)
+ const ImGuiPayload* payload = &g.DragDropPayload;
+ if (!payload->IsDataType(IMGUI_PAYLOAD_TYPE_WINDOW) || !DockNodeIsDropAllowed(window, *(ImGuiWindow**)payload->Data))
+ {
+ EndDragDropTarget();
+ return;
+ }
+
+ ImGuiWindow* payload_window = *(ImGuiWindow**)payload->Data;
+ if (AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_WINDOW, ImGuiDragDropFlags_AcceptBeforeDelivery | ImGuiDragDropFlags_AcceptNoDrawDefaultRect))
+ {
+ // Select target node
+ // (Important: we cannot use g.HoveredDockNode here! Because each of our target node have filters based on payload, each candidate drop target will do its own evaluation)
+ bool dock_into_floating_window = false;
+ ImGuiDockNode* node = NULL;
+ if (window->DockNodeAsHost)
+ {
+ // Cannot assume that node will != NULL even though we passed the rectangle test: it depends on padding/spacing handled by DockNodeTreeFindVisibleNodeByPos().
+ node = DockNodeTreeFindVisibleNodeByPos(window->DockNodeAsHost, g.IO.MousePos);
+
+ // There is an edge case when docking into a dockspace which only has _inactive_ nodes (because none of the windows are active)
+ // In this case we need to fallback into any leaf mode, possibly the central node.
+ // FIXME-20181220: We should not have to test for IsLeafNode() here but we have another bug to fix first.
+ if (node && node->IsDockSpace() && node->IsRootNode())
+ node = (node->CentralNode && node->IsLeafNode()) ? node->CentralNode : DockNodeTreeFindFallbackLeafNode(node);
+ }
+ else
+ {
+ if (window->DockNode)
+ node = window->DockNode;
+ else
+ dock_into_floating_window = true; // Dock into a regular window
+ }
+
+ const ImRect explicit_target_rect = (node && node->TabBar && !node->IsHiddenTabBar() && !node->IsNoTabBar()) ? node->TabBar->BarRect : ImRect(window->Pos, window->Pos + ImVec2(window->Size.x, GetFrameHeight()));
+ const bool is_explicit_target = g.IO.ConfigDockingWithShift || IsMouseHoveringRect(explicit_target_rect.Min, explicit_target_rect.Max);
+
+ // Preview docking request and find out split direction/ratio
+ //const bool do_preview = true; // Ignore testing for payload->IsPreview() which removes one frame of delay, but breaks overlapping drop targets within the same window.
+ const bool do_preview = payload->IsPreview() || payload->IsDelivery();
+ if (do_preview && (node != NULL || dock_into_floating_window))
+ {
+ // If we have a non-leaf node it means we are hovering the border of a parent node, in which case only outer markers will appear.
+ ImGuiDockPreviewData split_inner;
+ ImGuiDockPreviewData split_outer;
+ ImGuiDockPreviewData* split_data = &split_inner;
+ if (node && (node->ParentNode || node->IsCentralNode() || !node->IsLeafNode()))
+ if (ImGuiDockNode* root_node = DockNodeGetRootNode(node))
+ {
+ DockNodePreviewDockSetup(window, root_node, payload_window, NULL, &split_outer, is_explicit_target, true);
+ if (split_outer.IsSplitDirExplicit)
+ split_data = &split_outer;
+ }
+ if (!node || node->IsLeafNode())
+ DockNodePreviewDockSetup(window, node, payload_window, NULL, &split_inner, is_explicit_target, false);
+ if (split_data == &split_outer)
+ split_inner.IsDropAllowed = false;
+
+ // Draw inner then outer, so that previewed tab (in inner data) will be behind the outer drop boxes
+ DockNodePreviewDockRender(window, node, payload_window, &split_inner);
+ DockNodePreviewDockRender(window, node, payload_window, &split_outer);
+
+ // Queue docking request
+ if (split_data->IsDropAllowed && payload->IsDelivery())
+ DockContextQueueDock(&g, window, split_data->SplitNode, payload_window, split_data->SplitDir, split_data->SplitRatio, split_data == &split_outer);
+ }
+ }
+ EndDragDropTarget();
+}
+
+//-----------------------------------------------------------------------------
+// Docking: Settings
+//-----------------------------------------------------------------------------
+// - DockSettingsRenameNodeReferences()
+// - DockSettingsRemoveNodeReferences()
+// - DockSettingsFindNodeSettings()
+// - DockSettingsHandler_ApplyAll()
+// - DockSettingsHandler_ReadOpen()
+// - DockSettingsHandler_ReadLine()
+// - DockSettingsHandler_DockNodeToSettings()
+// - DockSettingsHandler_WriteAll()
+//-----------------------------------------------------------------------------
+
+static void ImGui::DockSettingsRenameNodeReferences(ImGuiID old_node_id, ImGuiID new_node_id)
+{
+ ImGuiContext& g = *GImGui;
+ IMGUI_DEBUG_LOG_DOCKING("[docking] DockSettingsRenameNodeReferences: from 0x%08X -> to 0x%08X\n", old_node_id, new_node_id);
+ for (int window_n = 0; window_n < g.Windows.Size; window_n++)
+ {
+ ImGuiWindow* window = g.Windows[window_n];
+ if (window->DockId == old_node_id && window->DockNode == NULL)
+ window->DockId = new_node_id;
+ }
+ //// FIXME-OPT: We could remove this loop by storing the index in the map
+ for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings))
+ if (settings->DockId == old_node_id)
+ settings->DockId = new_node_id;
+}
+
+// Remove references stored in ImGuiWindowSettings to the given ImGuiDockNodeSettings
+static void ImGui::DockSettingsRemoveNodeReferences(ImGuiID* node_ids, int node_ids_count)
+{
+ ImGuiContext& g = *GImGui;
+ int found = 0;
+ //// FIXME-OPT: We could remove this loop by storing the index in the map
+ for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings))
+ for (int node_n = 0; node_n < node_ids_count; node_n++)
+ if (settings->DockId == node_ids[node_n])
+ {
+ settings->DockId = 0;
+ settings->DockOrder = -1;
+ if (++found < node_ids_count)
+ break;
+ return;
+ }
+}
+
+static ImGuiDockNodeSettings* ImGui::DockSettingsFindNodeSettings(ImGuiContext* ctx, ImGuiID id)
+{
+ // FIXME-OPT
+ ImGuiDockContext* dc = &ctx->DockContext;
+ for (int n = 0; n < dc->NodesSettings.Size; n++)
+ if (dc->NodesSettings[n].ID == id)
+ return &dc->NodesSettings[n];
+ return NULL;
+}
+
+// Clear settings data
+static void ImGui::DockSettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettingsHandler*)
+{
+ ImGuiDockContext* dc = &ctx->DockContext;
+ dc->NodesSettings.clear();
+ DockContextClearNodes(ctx, 0, true);
+}
+
+// Recreate nodes based on settings data
+static void ImGui::DockSettingsHandler_ApplyAll(ImGuiContext* ctx, ImGuiSettingsHandler*)
+{
+ // Prune settings at boot time only
+ ImGuiDockContext* dc = &ctx->DockContext;
+ if (ctx->Windows.Size == 0)
+ DockContextPruneUnusedSettingsNodes(ctx);
+ DockContextBuildNodesFromSettings(ctx, dc->NodesSettings.Data, dc->NodesSettings.Size);
+ DockContextBuildAddWindowsToNodes(ctx, 0);
+}
+
+static void* ImGui::DockSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name)
+{
+ if (strcmp(name, "Data") != 0)
+ return NULL;
+ return (void*)1;
+}
+
+static void ImGui::DockSettingsHandler_ReadLine(ImGuiContext* ctx, ImGuiSettingsHandler*, void*, const char* line)
+{
+ char c = 0;
+ int x = 0, y = 0;
+ int r = 0;
+
+ // Parsing, e.g.
+ // " DockNode ID=0x00000001 Pos=383,193 Size=201,322 Split=Y,0.506 "
+ // " DockNode ID=0x00000002 Parent=0x00000001 "
+ // Important: this code expect currently fields in a fixed order.
+ ImGuiDockNodeSettings node;
+ line = ImStrSkipBlank(line);
+ if (strncmp(line, "DockNode", 8) == 0) { line = ImStrSkipBlank(line + strlen("DockNode")); }
+ else if (strncmp(line, "DockSpace", 9) == 0) { line = ImStrSkipBlank(line + strlen("DockSpace")); node.Flags |= ImGuiDockNodeFlags_DockSpace; }
+ else return;
+ if (sscanf(line, "ID=0x%08X%n", &node.ID, &r) == 1) { line += r; } else return;
+ if (sscanf(line, " Parent=0x%08X%n", &node.ParentNodeId, &r) == 1) { line += r; if (node.ParentNodeId == 0) return; }
+ if (sscanf(line, " Window=0x%08X%n", &node.ParentWindowId, &r) ==1) { line += r; if (node.ParentWindowId == 0) return; }
+ if (node.ParentNodeId == 0)
+ {
+ if (sscanf(line, " Pos=%i,%i%n", &x, &y, &r) == 2) { line += r; node.Pos = ImVec2ih((short)x, (short)y); } else return;
+ if (sscanf(line, " Size=%i,%i%n", &x, &y, &r) == 2) { line += r; node.Size = ImVec2ih((short)x, (short)y); } else return;
+ }
+ else
+ {
+ if (sscanf(line, " SizeRef=%i,%i%n", &x, &y, &r) == 2) { line += r; node.SizeRef = ImVec2ih((short)x, (short)y); }
+ }
+ if (sscanf(line, " Split=%c%n", &c, &r) == 1) { line += r; if (c == 'X') node.SplitAxis = ImGuiAxis_X; else if (c == 'Y') node.SplitAxis = ImGuiAxis_Y; }
+ if (sscanf(line, " NoResize=%d%n", &x, &r) == 1) { line += r; if (x != 0) node.Flags |= ImGuiDockNodeFlags_NoResize; }
+ if (sscanf(line, " CentralNode=%d%n", &x, &r) == 1) { line += r; if (x != 0) node.Flags |= ImGuiDockNodeFlags_CentralNode; }
+ if (sscanf(line, " NoTabBar=%d%n", &x, &r) == 1) { line += r; if (x != 0) node.Flags |= ImGuiDockNodeFlags_NoTabBar; }
+ if (sscanf(line, " HiddenTabBar=%d%n", &x, &r) == 1) { line += r; if (x != 0) node.Flags |= ImGuiDockNodeFlags_HiddenTabBar; }
+ if (sscanf(line, " NoWindowMenuButton=%d%n", &x, &r) == 1) { line += r; if (x != 0) node.Flags |= ImGuiDockNodeFlags_NoWindowMenuButton; }
+ if (sscanf(line, " NoCloseButton=%d%n", &x, &r) == 1) { line += r; if (x != 0) node.Flags |= ImGuiDockNodeFlags_NoCloseButton; }
+ if (sscanf(line, " Selected=0x%08X%n", &node.SelectedTabId,&r) == 1) { line += r; }
+ if (node.ParentNodeId != 0)
+ if (ImGuiDockNodeSettings* parent_settings = DockSettingsFindNodeSettings(ctx, node.ParentNodeId))
+ node.Depth = parent_settings->Depth + 1;
+ ctx->DockContext.NodesSettings.push_back(node);
+}
+
+static void DockSettingsHandler_DockNodeToSettings(ImGuiDockContext* dc, ImGuiDockNode* node, int depth)
+{
+ ImGuiDockNodeSettings node_settings;
+ IM_ASSERT(depth < (1 << (sizeof(node_settings.Depth) << 3)));
+ node_settings.ID = node->ID;
+ node_settings.ParentNodeId = node->ParentNode ? node->ParentNode->ID : 0;
+ node_settings.ParentWindowId = (node->IsDockSpace() && node->HostWindow && node->HostWindow->ParentWindow) ? node->HostWindow->ParentWindow->ID : 0;
+ node_settings.SelectedTabId = node->SelectedTabId;
+ node_settings.SplitAxis = (signed char)(node->IsSplitNode() ? node->SplitAxis : ImGuiAxis_None);
+ node_settings.Depth = (char)depth;
+ node_settings.Flags = (node->LocalFlags & ImGuiDockNodeFlags_SavedFlagsMask_);
+ node_settings.Pos = ImVec2ih(node->Pos);
+ node_settings.Size = ImVec2ih(node->Size);
+ node_settings.SizeRef = ImVec2ih(node->SizeRef);
+ dc->NodesSettings.push_back(node_settings);
+ if (node->ChildNodes[0])
+ DockSettingsHandler_DockNodeToSettings(dc, node->ChildNodes[0], depth + 1);
+ if (node->ChildNodes[1])
+ DockSettingsHandler_DockNodeToSettings(dc, node->ChildNodes[1], depth + 1);
+}
+
+static void ImGui::DockSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf)
+{
+ ImGuiContext& g = *ctx;
+ ImGuiDockContext* dc = &ctx->DockContext;
+ if (!(g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable))
+ return;
+
+ // Gather settings data
+ // (unlike our windows settings, because nodes are always built we can do a full rewrite of the SettingsNode buffer)
+ dc->NodesSettings.resize(0);
+ dc->NodesSettings.reserve(dc->Nodes.Data.Size);
+ for (int n = 0; n < dc->Nodes.Data.Size; n++)
+ if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p)
+ if (node->IsRootNode())
+ DockSettingsHandler_DockNodeToSettings(dc, node, 0);
+
+ int max_depth = 0;
+ for (int node_n = 0; node_n < dc->NodesSettings.Size; node_n++)
+ max_depth = ImMax((int)dc->NodesSettings[node_n].Depth, max_depth);
+
+ // Write to text buffer
+ buf->appendf("[%s][Data]\n", handler->TypeName);
+ for (int node_n = 0; node_n < dc->NodesSettings.Size; node_n++)
+ {
+ const int line_start_pos = buf->size(); (void)line_start_pos;
+ const ImGuiDockNodeSettings* node_settings = &dc->NodesSettings[node_n];
+ buf->appendf("%*s%s%*s", node_settings->Depth * 2, "", (node_settings->Flags & ImGuiDockNodeFlags_DockSpace) ? "DockSpace" : "DockNode ", (max_depth - node_settings->Depth) * 2, ""); // Text align nodes to facilitate looking at .ini file
+ buf->appendf(" ID=0x%08X", node_settings->ID);
+ if (node_settings->ParentNodeId)
+ {
+ buf->appendf(" Parent=0x%08X SizeRef=%d,%d", node_settings->ParentNodeId, node_settings->SizeRef.x, node_settings->SizeRef.y);
+ }
+ else
+ {
+ if (node_settings->ParentWindowId)
+ buf->appendf(" Window=0x%08X", node_settings->ParentWindowId);
+ buf->appendf(" Pos=%d,%d Size=%d,%d", node_settings->Pos.x, node_settings->Pos.y, node_settings->Size.x, node_settings->Size.y);
+ }
+ if (node_settings->SplitAxis != ImGuiAxis_None)
+ buf->appendf(" Split=%c", (node_settings->SplitAxis == ImGuiAxis_X) ? 'X' : 'Y');
+ if (node_settings->Flags & ImGuiDockNodeFlags_NoResize)
+ buf->appendf(" NoResize=1");
+ if (node_settings->Flags & ImGuiDockNodeFlags_CentralNode)
+ buf->appendf(" CentralNode=1");
+ if (node_settings->Flags & ImGuiDockNodeFlags_NoTabBar)
+ buf->appendf(" NoTabBar=1");
+ if (node_settings->Flags & ImGuiDockNodeFlags_HiddenTabBar)
+ buf->appendf(" HiddenTabBar=1");
+ if (node_settings->Flags & ImGuiDockNodeFlags_NoWindowMenuButton)
+ buf->appendf(" NoWindowMenuButton=1");
+ if (node_settings->Flags & ImGuiDockNodeFlags_NoCloseButton)
+ buf->appendf(" NoCloseButton=1");
+ if (node_settings->SelectedTabId)
+ buf->appendf(" Selected=0x%08X", node_settings->SelectedTabId);
+
+ // [DEBUG] Include comments in the .ini file to ease debugging (this makes saving slower!)
+ if (g.IO.ConfigDebugIniSettings)
+ if (ImGuiDockNode* node = DockContextFindNodeByID(ctx, node_settings->ID))
+ {
+ buf->appendf("%*s", ImMax(2, (line_start_pos + 92) - buf->size()), ""); // Align everything
+ if (node->IsDockSpace() && node->HostWindow && node->HostWindow->ParentWindow)
+ buf->appendf(" ; in '%s'", node->HostWindow->ParentWindow->Name);
+ // Iterate settings so we can give info about windows that didn't exist during the session.
+ int contains_window = 0;
+ for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings))
+ if (settings->DockId == node_settings->ID)
+ {
+ if (contains_window++ == 0)
+ buf->appendf(" ; contains ");
+ buf->appendf("'%s' ", settings->GetName());
+ }
+ }
+
+ buf->appendf("\n");
+ }
+ buf->appendf("\n");
+}
+
+
+//-----------------------------------------------------------------------------
+// [SECTION] PLATFORM DEPENDENT HELPERS
+//-----------------------------------------------------------------------------
+
+#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS)
+
+#ifdef _MSC_VER
+#pragma comment(lib, "user32")
+#pragma comment(lib, "kernel32")
+#endif
+
+// Win32 clipboard implementation
+// We use g.ClipboardHandlerData for temporary storage to ensure it is freed on Shutdown()
+static const char* GetClipboardTextFn_DefaultImpl(void* user_data_ctx)
+{
+ ImGuiContext& g = *(ImGuiContext*)user_data_ctx;
+ g.ClipboardHandlerData.clear();
+ if (!::OpenClipboard(NULL))
+ return NULL;
+ HANDLE wbuf_handle = ::GetClipboardData(CF_UNICODETEXT);
+ if (wbuf_handle == NULL)
+ {
+ ::CloseClipboard();
+ return NULL;
+ }
+ if (const WCHAR* wbuf_global = (const WCHAR*)::GlobalLock(wbuf_handle))
+ {
+ int buf_len = ::WideCharToMultiByte(CP_UTF8, 0, wbuf_global, -1, NULL, 0, NULL, NULL);
+ g.ClipboardHandlerData.resize(buf_len);
+ ::WideCharToMultiByte(CP_UTF8, 0, wbuf_global, -1, g.ClipboardHandlerData.Data, buf_len, NULL, NULL);
+ }
+ ::GlobalUnlock(wbuf_handle);
+ ::CloseClipboard();
+ return g.ClipboardHandlerData.Data;
+}
+
+static void SetClipboardTextFn_DefaultImpl(void*, const char* text)
+{
+ if (!::OpenClipboard(NULL))
+ return;
+ const int wbuf_length = ::MultiByteToWideChar(CP_UTF8, 0, text, -1, NULL, 0);
+ HGLOBAL wbuf_handle = ::GlobalAlloc(GMEM_MOVEABLE, (SIZE_T)wbuf_length * sizeof(WCHAR));
+ if (wbuf_handle == NULL)
+ {
+ ::CloseClipboard();
+ return;
+ }
+ WCHAR* wbuf_global = (WCHAR*)::GlobalLock(wbuf_handle);
+ ::MultiByteToWideChar(CP_UTF8, 0, text, -1, wbuf_global, wbuf_length);
+ ::GlobalUnlock(wbuf_handle);
+ ::EmptyClipboard();
+ if (::SetClipboardData(CF_UNICODETEXT, wbuf_handle) == NULL)
+ ::GlobalFree(wbuf_handle);
+ ::CloseClipboard();
+}
+
+#elif defined(__APPLE__) && TARGET_OS_OSX && defined(IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS)
+
+#include <Carbon/Carbon.h> // Use old API to avoid need for separate .mm file
+static PasteboardRef main_clipboard = 0;
+
+// OSX clipboard implementation
+// If you enable this you will need to add '-framework ApplicationServices' to your linker command-line!
+static void SetClipboardTextFn_DefaultImpl(void*, const char* text)
+{
+ if (!main_clipboard)
+ PasteboardCreate(kPasteboardClipboard, &main_clipboard);
+ PasteboardClear(main_clipboard);
+ CFDataRef cf_data = CFDataCreate(kCFAllocatorDefault, (const UInt8*)text, strlen(text));
+ if (cf_data)
+ {
+ PasteboardPutItemFlavor(main_clipboard, (PasteboardItemID)1, CFSTR("public.utf8-plain-text"), cf_data, 0);
+ CFRelease(cf_data);
+ }
+}
+
+static const char* GetClipboardTextFn_DefaultImpl(void* user_data_ctx)
+{
+ ImGuiContext& g = *(ImGuiContext*)user_data_ctx;
+ if (!main_clipboard)
+ PasteboardCreate(kPasteboardClipboard, &main_clipboard);
+ PasteboardSynchronize(main_clipboard);
+
+ ItemCount item_count = 0;
+ PasteboardGetItemCount(main_clipboard, &item_count);
+ for (ItemCount i = 0; i < item_count; i++)
+ {
+ PasteboardItemID item_id = 0;
+ PasteboardGetItemIdentifier(main_clipboard, i + 1, &item_id);
+ CFArrayRef flavor_type_array = 0;
+ PasteboardCopyItemFlavors(main_clipboard, item_id, &flavor_type_array);
+ for (CFIndex j = 0, nj = CFArrayGetCount(flavor_type_array); j < nj; j++)
+ {
+ CFDataRef cf_data;
+ if (PasteboardCopyItemFlavorData(main_clipboard, item_id, CFSTR("public.utf8-plain-text"), &cf_data) == noErr)
+ {
+ g.ClipboardHandlerData.clear();
+ int length = (int)CFDataGetLength(cf_data);
+ g.ClipboardHandlerData.resize(length + 1);
+ CFDataGetBytes(cf_data, CFRangeMake(0, length), (UInt8*)g.ClipboardHandlerData.Data);
+ g.ClipboardHandlerData[length] = 0;
+ CFRelease(cf_data);
+ return g.ClipboardHandlerData.Data;
+ }
+ }
+ }
+ return NULL;
+}
+
+#else
+
+// Local Dear ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers.
+static const char* GetClipboardTextFn_DefaultImpl(void* user_data_ctx)
+{
+ ImGuiContext& g = *(ImGuiContext*)user_data_ctx;
+ return g.ClipboardHandlerData.empty() ? NULL : g.ClipboardHandlerData.begin();
+}
+
+static void SetClipboardTextFn_DefaultImpl(void* user_data_ctx, const char* text)
+{
+ ImGuiContext& g = *(ImGuiContext*)user_data_ctx;
+ g.ClipboardHandlerData.clear();
+ const char* text_end = text + strlen(text);
+ g.ClipboardHandlerData.resize((int)(text_end - text) + 1);
+ memcpy(&g.ClipboardHandlerData[0], text, (size_t)(text_end - text));
+ g.ClipboardHandlerData[(int)(text_end - text)] = 0;
+}
+
+#endif
+
+// Win32 API IME support (for Asian languages, etc.)
#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS)
#include <imm.h>
@@ -14106,14 +19857,14 @@ static void SetPlatformImeDataFn_DefaultImpl(ImGuiViewport* viewport, ImGuiPlatf
if (HIMC himc = ::ImmGetContext(hwnd))
{
COMPOSITIONFORM composition_form = {};
- composition_form.ptCurrentPos.x = (LONG)data->InputPos.x;
- composition_form.ptCurrentPos.y = (LONG)data->InputPos.y;
+ composition_form.ptCurrentPos.x = (LONG)(data->InputPos.x - viewport->Pos.x);
+ composition_form.ptCurrentPos.y = (LONG)(data->InputPos.y - viewport->Pos.y);
composition_form.dwStyle = CFS_FORCE_POSITION;
::ImmSetCompositionWindow(himc, &composition_form);
CANDIDATEFORM candidate_form = {};
candidate_form.dwStyle = CFS_CANDIDATEPOS;
- candidate_form.ptCurrentPos.x = (LONG)data->InputPos.x;
- candidate_form.ptCurrentPos.y = (LONG)data->InputPos.y;
+ candidate_form.ptCurrentPos.x = (LONG)(data->InputPos.x - viewport->Pos.x);
+ candidate_form.ptCurrentPos.y = (LONG)(data->InputPos.y - viewport->Pos.y);
::ImmSetCandidateWindow(himc, &candidate_form);
::ImmReleaseContext(hwnd, himc);
}
@@ -14135,6 +19886,7 @@ static void SetPlatformImeDataFn_DefaultImpl(ImGuiViewport*, ImGuiPlatformImeDat
// - ShowFontAtlas() [Internal]
// - ShowMetricsWindow()
// - DebugNodeColumns() [Internal]
+// - DebugNodeDockNode() [Internal]
// - DebugNodeDrawList() [Internal]
// - DebugNodeDrawCmdShowMeshAndBoundingBox() [Internal]
// - DebugNodeFont() [Internal]
@@ -14157,12 +19909,14 @@ void ImGui::DebugRenderViewportThumbnail(ImDrawList* draw_list, ImGuiViewportP*
ImVec2 scale = bb.GetSize() / viewport->Size;
ImVec2 off = bb.Min - viewport->Pos * scale;
- float alpha_mul = 1.0f;
+ float alpha_mul = (viewport->Flags & ImGuiViewportFlags_IsMinimized) ? 0.30f : 1.00f;
window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border, alpha_mul * 0.40f));
for (ImGuiWindow* thumb_window : g.Windows)
{
if (!thumb_window->WasActive || (thumb_window->Flags & ImGuiWindowFlags_ChildWindow))
continue;
+ if (thumb_window->Viewport != viewport)
+ continue;
ImRect thumb_r = thumb_window->Rect();
ImRect title_r = thumb_window->TitleBarRect();
@@ -14186,10 +19940,19 @@ static void RenderViewportsThumbnails()
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
+ // Draw monitor and calculate their boundaries
float SCALE = 1.0f / 8.0f;
- ImRect bb_full(g.Viewports[0]->Pos, g.Viewports[0]->Pos + g.Viewports[0]->Size);
+ ImRect bb_full(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX);
+ for (ImGuiPlatformMonitor& monitor : g.PlatformIO.Monitors)
+ bb_full.Add(ImRect(monitor.MainPos, monitor.MainPos + monitor.MainSize));
ImVec2 p = window->DC.CursorPos;
ImVec2 off = p - bb_full.Min * SCALE;
+ for (ImGuiPlatformMonitor& monitor : g.PlatformIO.Monitors)
+ {
+ ImRect monitor_draw_bb(off + (monitor.MainPos) * SCALE, off + (monitor.MainPos + monitor.MainSize) * SCALE);
+ window->DrawList->AddRect(monitor_draw_bb.Min, monitor_draw_bb.Max, (g.DebugMetricsConfig.HighlightMonitorIdx == g.PlatformIO.Monitors.index_from_ptr(&monitor)) ? IM_COL32(255, 255, 0, 255) : ImGui::GetColorU32(ImGuiCol_Border), 4.0f);
+ window->DrawList->AddRectFilled(monitor_draw_bb.Min, monitor_draw_bb.Max, ImGui::GetColorU32(ImGuiCol_Border, 0.10f), 4.0f);
+ }
// Draw viewports
for (ImGuiViewportP* viewport : g.Viewports)
@@ -14200,6 +19963,13 @@ static void RenderViewportsThumbnails()
ImGui::Dummy(bb_full.GetSize() * SCALE);
}
+static int IMGUI_CDECL ViewportComparerByLastFocusedStampCount(const void* lhs, const void* rhs)
+{
+ const ImGuiViewportP* a = *(const ImGuiViewportP* const*)lhs;
+ const ImGuiViewportP* b = *(const ImGuiViewportP* const*)rhs;
+ return b->LastFocusedStampCount - a->LastFocusedStampCount;
+}
+
// Draw an arbitrary US keyboard layout to visualize translated keys
void ImGui::DebugRenderKeyboardPreview(ImDrawList* draw_list)
{
@@ -14562,14 +20332,41 @@ void ImGui::ShowMetricsWindow(bool* p_open)
Checkbox("Show ImDrawCmd mesh when hovering", &cfg->ShowDrawCmdMesh);
Checkbox("Show ImDrawCmd bounding boxes when hovering", &cfg->ShowDrawCmdBoundingBoxes);
for (ImGuiViewportP* viewport : g.Viewports)
+ {
+ bool viewport_has_drawlist = false;
for (ImDrawList* draw_list : viewport->DrawDataP.CmdLists)
+ {
+ if (!viewport_has_drawlist)
+ Text("Active DrawLists in Viewport #%d, ID: 0x%08X", viewport->Idx, viewport->ID);
+ viewport_has_drawlist = true;
DebugNodeDrawList(NULL, viewport, draw_list, "DrawList");
+ }
+ }
TreePop();
}
// Viewports
if (TreeNode("Viewports", "Viewports (%d)", g.Viewports.Size))
{
+ cfg->HighlightMonitorIdx = -1;
+ bool open = TreeNode("Monitors", "Monitors (%d)", g.PlatformIO.Monitors.Size);
+ SameLine();
+ MetricsHelpMarker("Dear ImGui uses monitor data:\n- to query DPI settings on a per monitor basis\n- to position popup/tooltips so they don't straddle monitors.");
+ if (open)
+ {
+ for (int i = 0; i < g.PlatformIO.Monitors.Size; i++)
+ {
+ const ImGuiPlatformMonitor& mon = g.PlatformIO.Monitors[i];
+ BulletText("Monitor #%d: DPI %.0f%%\n MainMin (%.0f,%.0f), MainMax (%.0f,%.0f), MainSize (%.0f,%.0f)\n WorkMin (%.0f,%.0f), WorkMax (%.0f,%.0f), WorkSize (%.0f,%.0f)",
+ i, mon.DpiScale * 100.0f,
+ mon.MainPos.x, mon.MainPos.y, mon.MainPos.x + mon.MainSize.x, mon.MainPos.y + mon.MainSize.y, mon.MainSize.x, mon.MainSize.y,
+ mon.WorkPos.x, mon.WorkPos.y, mon.WorkPos.x + mon.WorkSize.x, mon.WorkPos.y + mon.WorkSize.y, mon.WorkSize.x, mon.WorkSize.y);
+ if (IsItemHovered())
+ cfg->HighlightMonitorIdx = i;
+ }
+ TreePop();
+ }
+
SetNextItemOpen(true, ImGuiCond_Once);
if (TreeNode("Windows Minimap"))
{
@@ -14578,6 +20375,26 @@ void ImGui::ShowMetricsWindow(bool* p_open)
}
cfg->HighlightViewportID = 0;
+ BulletText("MouseViewport: 0x%08X (UserHovered 0x%08X, LastHovered 0x%08X)", g.MouseViewport ? g.MouseViewport->ID : 0, g.IO.MouseHoveredViewport, g.MouseLastHoveredViewport ? g.MouseLastHoveredViewport->ID : 0);
+ if (TreeNode("Inferred Z order (front-to-back)"))
+ {
+ static ImVector<ImGuiViewportP*> viewports;
+ viewports.resize(g.Viewports.Size);
+ memcpy(viewports.Data, g.Viewports.Data, g.Viewports.size_in_bytes());
+ if (viewports.Size > 1)
+ ImQsort(viewports.Data, viewports.Size, sizeof(ImGuiViewport*), ViewportComparerByLastFocusedStampCount);
+ for (ImGuiViewportP* viewport : viewports)
+ {
+ BulletText("Viewport #%d, ID: 0x%08X, LastFocused = %08d, PlatformFocused = %s, Window: \"%s\"",
+ viewport->Idx, viewport->ID, viewport->LastFocusedStampCount,
+ (g.PlatformIO.Platform_GetWindowFocus && viewport->PlatformWindowCreated) ? (g.PlatformIO.Platform_GetWindowFocus(viewport) ? "1" : "0") : "N/A",
+ viewport->Window ? viewport->Window->Name : "N/A");
+ if (IsItemHovered())
+ cfg->HighlightViewportID = viewport->ID;
+ }
+ TreePop();
+ }
+
for (ImGuiViewportP* viewport : g.Viewports)
DebugNodeViewport(viewport);
TreePop();
@@ -14645,6 +20462,17 @@ void ImGui::ShowMetricsWindow(bool* p_open)
#ifdef IMGUI_HAS_DOCK
if (TreeNode("Docking"))
{
+ static bool root_nodes_only = true;
+ ImGuiDockContext* dc = &g.DockContext;
+ Checkbox("List root nodes", &root_nodes_only);
+ Checkbox("Ctrl shows window dock info", &cfg->ShowDockingNodes);
+ if (SmallButton("Clear nodes")) { DockContextClearNodes(&g, 0, true); }
+ SameLine();
+ if (SmallButton("Rebuild all")) { dc->WantFullRebuild = true; }
+ for (int n = 0; n < dc->Nodes.Data.Size; n++)
+ if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p)
+ if (!root_nodes_only || node->IsRootNode())
+ DebugNodeDockNode(node, "Node");
TreePop();
}
#endif // #ifdef IMGUI_HAS_DOCK
@@ -14688,6 +20516,29 @@ void ImGui::ShowMetricsWindow(bool* p_open)
}
#ifdef IMGUI_HAS_DOCK
+ if (TreeNode("SettingsDocking", "Settings packed data: Docking"))
+ {
+ ImGuiDockContext* dc = &g.DockContext;
+ Text("In SettingsWindows:");
+ for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings))
+ if (settings->DockId != 0)
+ BulletText("Window '%s' -> DockId %08X DockOrder=%d", settings->GetName(), settings->DockId, settings->DockOrder);
+ Text("In SettingsNodes:");
+ for (int n = 0; n < dc->NodesSettings.Size; n++)
+ {
+ ImGuiDockNodeSettings* settings = &dc->NodesSettings[n];
+ const char* selected_tab_name = NULL;
+ if (settings->SelectedTabId)
+ {
+ if (ImGuiWindow* window = FindWindowByID(settings->SelectedTabId))
+ selected_tab_name = window->Name;
+ else if (ImGuiWindowSettings* window_settings = FindWindowSettingsByID(settings->SelectedTabId))
+ selected_tab_name = window_settings->GetName();
+ }
+ BulletText("Node %08X, Parent %08X, SelectedTab %08X ('%s')", settings->ID, settings->ParentNodeId, settings->SelectedTabId, selected_tab_name ? selected_tab_name : settings->SelectedTabId ? "N/A" : "");
+ }
+ TreePop();
+ }
#endif // #ifdef IMGUI_HAS_DOCK
if (TreeNode("SettingsIniData", "Settings unpacked data (.ini): %d bytes", g.SettingsIniData.size()))
@@ -14771,7 +20622,7 @@ void ImGui::ShowMetricsWindow(bool* p_open)
for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1))
{
ImGuiKeyOwnerData* owner_data = GetKeyOwnerData(&g, key);
- if (owner_data->OwnerCurr == ImGuiKeyOwner_None)
+ if (owner_data->OwnerCurr == ImGuiKeyOwner_NoOwner)
continue;
Text("%s: 0x%08X%s", GetKeyName(key), owner_data->OwnerCurr,
owner_data->LockUntilRelease ? " LockUntilRelease" : owner_data->LockThisFrame ? " LockThisFrame" : "");
@@ -14816,9 +20667,11 @@ void ImGui::ShowMetricsWindow(bool* p_open)
Text("WINDOWING");
Indent();
Text("HoveredWindow: '%s'", g.HoveredWindow ? g.HoveredWindow->Name : "NULL");
- Text("HoveredWindow->Root: '%s'", g.HoveredWindow ? g.HoveredWindow->RootWindow->Name : "NULL");
+ Text("HoveredWindow->Root: '%s'", g.HoveredWindow ? g.HoveredWindow->RootWindowDockTree->Name : "NULL");
Text("HoveredWindowUnderMovingWindow: '%s'", g.HoveredWindowUnderMovingWindow ? g.HoveredWindowUnderMovingWindow->Name : "NULL");
+ Text("HoveredDockNode: 0x%08X", g.DebugHoveredDockNode ? g.DebugHoveredDockNode->ID : 0);
Text("MovingWindow: '%s'", g.MovingWindow ? g.MovingWindow->Name : "NULL");
+ Text("MouseViewport: 0x%08X (UserHovered 0x%08X, LastHovered 0x%08X)", g.MouseViewport->ID, g.IO.MouseHoveredViewport, g.MouseLastHoveredViewport ? g.MouseLastHoveredViewport->ID : 0);
Unindent();
Text("ITEMS");
@@ -14912,8 +20765,21 @@ void ImGui::ShowMetricsWindow(bool* p_open)
#ifdef IMGUI_HAS_DOCK
// Overlay: Display Docking info
- if (show_docking_nodes && g.IO.KeyCtrl)
- {
+ if (cfg->ShowDockingNodes && g.IO.KeyCtrl && g.DebugHoveredDockNode)
+ {
+ char buf[64] = "";
+ char* p = buf;
+ ImGuiDockNode* node = g.DebugHoveredDockNode;
+ ImDrawList* overlay_draw_list = node->HostWindow ? GetForegroundDrawList(node->HostWindow) : GetForegroundDrawList(GetMainViewport());
+ p += ImFormatString(p, buf + IM_ARRAYSIZE(buf) - p, "DockId: %X%s\n", node->ID, node->IsCentralNode() ? " *CentralNode*" : "");
+ p += ImFormatString(p, buf + IM_ARRAYSIZE(buf) - p, "WindowClass: %08X\n", node->WindowClass.ClassId);
+ p += ImFormatString(p, buf + IM_ARRAYSIZE(buf) - p, "Size: (%.0f, %.0f)\n", node->Size.x, node->Size.y);
+ p += ImFormatString(p, buf + IM_ARRAYSIZE(buf) - p, "SizeRef: (%.0f, %.0f)\n", node->SizeRef.x, node->SizeRef.y);
+ int depth = DockNodeGetDepth(node);
+ overlay_draw_list->AddRect(node->Pos + ImVec2(3, 3) * (float)depth, node->Pos + node->Size - ImVec2(3, 3) * (float)depth, IM_COL32(200, 100, 100, 255));
+ ImVec2 pos = node->Pos + ImVec2(3, 3) * (float)depth;
+ overlay_draw_list->AddRectFilled(pos - ImVec2(1, 1), pos + CalcTextSize(buf) + ImVec2(1, 1), IM_COL32(200, 100, 100, 255));
+ overlay_draw_list->AddText(NULL, 0.0f, pos, IM_COL32(255, 255, 255, 255), buf);
}
#endif // #ifdef IMGUI_HAS_DOCK
@@ -14989,6 +20855,93 @@ void ImGui::DebugNodeColumns(ImGuiOldColumns* columns)
TreePop();
}
+static void DebugNodeDockNodeFlags(ImGuiDockNodeFlags* p_flags, const char* label, bool enabled)
+{
+ using namespace ImGui;
+ PushID(label);
+ PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f));
+ Text("%s:", label);
+ if (!enabled)
+ BeginDisabled();
+ CheckboxFlags("NoResize", p_flags, ImGuiDockNodeFlags_NoResize);
+ CheckboxFlags("NoResizeX", p_flags, ImGuiDockNodeFlags_NoResizeX);
+ CheckboxFlags("NoResizeY",p_flags, ImGuiDockNodeFlags_NoResizeY);
+ CheckboxFlags("NoTabBar", p_flags, ImGuiDockNodeFlags_NoTabBar);
+ CheckboxFlags("HiddenTabBar", p_flags, ImGuiDockNodeFlags_HiddenTabBar);
+ CheckboxFlags("NoWindowMenuButton", p_flags, ImGuiDockNodeFlags_NoWindowMenuButton);
+ CheckboxFlags("NoCloseButton", p_flags, ImGuiDockNodeFlags_NoCloseButton);
+ CheckboxFlags("DockedWindowsInFocusRoute", p_flags, ImGuiDockNodeFlags_DockedWindowsInFocusRoute);
+ CheckboxFlags("NoDocking", p_flags, ImGuiDockNodeFlags_NoDocking); // Multiple flags
+ CheckboxFlags("NoDockingSplit", p_flags, ImGuiDockNodeFlags_NoDockingSplit);
+ CheckboxFlags("NoDockingSplitOther", p_flags, ImGuiDockNodeFlags_NoDockingSplitOther);
+ CheckboxFlags("NoDockingOver", p_flags, ImGuiDockNodeFlags_NoDockingOverMe);
+ CheckboxFlags("NoDockingOverOther", p_flags, ImGuiDockNodeFlags_NoDockingOverOther);
+ CheckboxFlags("NoDockingOverEmpty", p_flags, ImGuiDockNodeFlags_NoDockingOverEmpty);
+ CheckboxFlags("NoUndocking", p_flags, ImGuiDockNodeFlags_NoUndocking);
+ if (!enabled)
+ EndDisabled();
+ PopStyleVar();
+ PopID();
+}
+
+// [DEBUG] Display contents of ImDockNode
+void ImGui::DebugNodeDockNode(ImGuiDockNode* node, const char* label)
+{
+ ImGuiContext& g = *GImGui;
+ const bool is_alive = (g.FrameCount - node->LastFrameAlive < 2); // Submitted with ImGuiDockNodeFlags_KeepAliveOnly
+ const bool is_active = (g.FrameCount - node->LastFrameActive < 2); // Submitted
+ if (!is_alive) { PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled)); }
+ bool open;
+ ImGuiTreeNodeFlags tree_node_flags = node->IsFocused ? ImGuiTreeNodeFlags_Selected : ImGuiTreeNodeFlags_None;
+ if (node->Windows.Size > 0)
+ open = TreeNodeEx((void*)(intptr_t)node->ID, tree_node_flags, "%s 0x%04X%s: %d windows (vis: '%s')", label, node->ID, node->IsVisible ? "" : " (hidden)", node->Windows.Size, node->VisibleWindow ? node->VisibleWindow->Name : "NULL");
+ else
+ open = TreeNodeEx((void*)(intptr_t)node->ID, tree_node_flags, "%s 0x%04X%s: %s (vis: '%s')", label, node->ID, node->IsVisible ? "" : " (hidden)", (node->SplitAxis == ImGuiAxis_X) ? "horizontal split" : (node->SplitAxis == ImGuiAxis_Y) ? "vertical split" : "empty", node->VisibleWindow ? node->VisibleWindow->Name : "NULL");
+ if (!is_alive) { PopStyleColor(); }
+ if (is_active && IsItemHovered())
+ if (ImGuiWindow* window = node->HostWindow ? node->HostWindow : node->VisibleWindow)
+ GetForegroundDrawList(window)->AddRect(node->Pos, node->Pos + node->Size, IM_COL32(255, 255, 0, 255));
+ if (open)
+ {
+ IM_ASSERT(node->ChildNodes[0] == NULL || node->ChildNodes[0]->ParentNode == node);
+ IM_ASSERT(node->ChildNodes[1] == NULL || node->ChildNodes[1]->ParentNode == node);
+ BulletText("Pos (%.0f,%.0f), Size (%.0f, %.0f) Ref (%.0f, %.0f)",
+ node->Pos.x, node->Pos.y, node->Size.x, node->Size.y, node->SizeRef.x, node->SizeRef.y);
+ DebugNodeWindow(node->HostWindow, "HostWindow");
+ DebugNodeWindow(node->VisibleWindow, "VisibleWindow");
+ BulletText("SelectedTabID: 0x%08X, LastFocusedNodeID: 0x%08X", node->SelectedTabId, node->LastFocusedNodeId);
+ BulletText("Misc:%s%s%s%s%s%s%s",
+ node->IsDockSpace() ? " IsDockSpace" : "",
+ node->IsCentralNode() ? " IsCentralNode" : "",
+ is_alive ? " IsAlive" : "", is_active ? " IsActive" : "", node->IsFocused ? " IsFocused" : "",
+ node->WantLockSizeOnce ? " WantLockSizeOnce" : "",
+ node->HasCentralNodeChild ? " HasCentralNodeChild" : "");
+ if (TreeNode("flags", "Flags Merged: 0x%04X, Local: 0x%04X, InWindows: 0x%04X, Shared: 0x%04X", node->MergedFlags, node->LocalFlags, node->LocalFlagsInWindows, node->SharedFlags))
+ {
+ if (BeginTable("flags", 4))
+ {
+ TableNextColumn(); DebugNodeDockNodeFlags(&node->MergedFlags, "MergedFlags", false);
+ TableNextColumn(); DebugNodeDockNodeFlags(&node->LocalFlags, "LocalFlags", true);
+ TableNextColumn(); DebugNodeDockNodeFlags(&node->LocalFlagsInWindows, "LocalFlagsInWindows", false);
+ TableNextColumn(); DebugNodeDockNodeFlags(&node->SharedFlags, "SharedFlags", true);
+ EndTable();
+ }
+ TreePop();
+ }
+ if (node->ParentNode)
+ DebugNodeDockNode(node->ParentNode, "ParentNode");
+ if (node->ChildNodes[0])
+ DebugNodeDockNode(node->ChildNodes[0], "Child[0]");
+ if (node->ChildNodes[1])
+ DebugNodeDockNode(node->ChildNodes[1], "Child[1]");
+ if (node->TabBar)
+ DebugNodeTabBar(node->TabBar, "TabBar");
+ DebugNodeWindowsList(&node->Windows, "Windows");
+
+ TreePop();
+ }
+}
+
static void FormatTextureIDForDebugDisplay(char* buf, int buf_size, ImTextureID tex_id)
{
union { void* ptr; int integer; } tex_id_opaque;
@@ -15000,10 +20953,10 @@ static void FormatTextureIDForDebugDisplay(char* buf, int buf_size, ImTextureID
}
// [DEBUG] Display contents of ImDrawList
+// Note that both 'window' and 'viewport' may be NULL here. Viewport is generally null of destroyed popups which previously owned a viewport.
void ImGui::DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, const ImDrawList* draw_list, const char* label)
{
ImGuiContext& g = *GImGui;
- IM_UNUSED(viewport); // Used in docking branch
ImGuiMetricsConfig* cfg = &g.DebugMetricsConfig;
int cmd_count = draw_list->CmdBuffer.Size;
if (cmd_count > 0 && draw_list->CmdBuffer.back().ElemCount == 0 && draw_list->CmdBuffer.back().UserCallback == NULL)
@@ -15018,7 +20971,7 @@ void ImGui::DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, con
return;
}
- ImDrawList* fg_draw_list = GetForegroundDrawList(window); // Render additional visuals into the top-most draw list
+ ImDrawList* fg_draw_list = viewport ? GetForegroundDrawList(viewport) : NULL; // Render additional visuals into the top-most draw list
if (window && IsItemHovered() && fg_draw_list)
fg_draw_list->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 255, 0, 255));
if (!node_open)
@@ -15281,19 +21234,32 @@ void ImGui::DebugNodeViewport(ImGuiViewportP* viewport)
{
ImGuiContext& g = *GImGui;
SetNextItemOpen(true, ImGuiCond_Once);
- bool open = TreeNode("viewport0", "Viewport #%d", 0);
+ bool open = TreeNode((void*)(intptr_t)viewport->ID, "Viewport #%d, ID: 0x%08X, Parent: 0x%08X, Window: \"%s\"", viewport->Idx, viewport->ID, viewport->ParentViewportId, viewport->Window ? viewport->Window->Name : "N/A");
if (IsItemHovered())
g.DebugMetricsConfig.HighlightViewportID = viewport->ID;
if (open)
{
ImGuiWindowFlags flags = viewport->Flags;
- BulletText("Main Pos: (%.0f,%.0f), Size: (%.0f,%.0f)\nWorkArea Offset Left: %.0f Top: %.0f, Right: %.0f, Bottom: %.0f",
+ BulletText("Main Pos: (%.0f,%.0f), Size: (%.0f,%.0f)\nWorkArea Offset Left: %.0f Top: %.0f, Right: %.0f, Bottom: %.0f\nMonitor: %d, DpiScale: %.0f%%",
viewport->Pos.x, viewport->Pos.y, viewport->Size.x, viewport->Size.y,
- viewport->WorkOffsetMin.x, viewport->WorkOffsetMin.y, viewport->WorkOffsetMax.x, viewport->WorkOffsetMax.y);
- BulletText("Flags: 0x%04X =%s%s%s", viewport->Flags,
- (flags & ImGuiViewportFlags_IsPlatformWindow) ? " IsPlatformWindow" : "",
+ viewport->WorkOffsetMin.x, viewport->WorkOffsetMin.y, viewport->WorkOffsetMax.x, viewport->WorkOffsetMax.y,
+ viewport->PlatformMonitor, viewport->DpiScale * 100.0f);
+ if (viewport->Idx > 0) { SameLine(); if (SmallButton("Reset Pos")) { viewport->Pos = ImVec2(200, 200); viewport->UpdateWorkRect(); if (viewport->Window) viewport->Window->Pos = viewport->Pos; } }
+ BulletText("Flags: 0x%04X =%s%s%s%s%s%s%s%s%s%s%s%s%s", viewport->Flags,
+ //(flags & ImGuiViewportFlags_IsPlatformWindow) ? " IsPlatformWindow" : "", // Omitting because it is the standard
(flags & ImGuiViewportFlags_IsPlatformMonitor) ? " IsPlatformMonitor" : "",
- (flags & ImGuiViewportFlags_OwnedByApp) ? " OwnedByApp" : "");
+ (flags & ImGuiViewportFlags_IsMinimized) ? " IsMinimized" : "",
+ (flags & ImGuiViewportFlags_IsFocused) ? " IsFocused" : "",
+ (flags & ImGuiViewportFlags_OwnedByApp) ? " OwnedByApp" : "",
+ (flags & ImGuiViewportFlags_NoDecoration) ? " NoDecoration" : "",
+ (flags & ImGuiViewportFlags_NoTaskBarIcon) ? " NoTaskBarIcon" : "",
+ (flags & ImGuiViewportFlags_NoFocusOnAppearing) ? " NoFocusOnAppearing" : "",
+ (flags & ImGuiViewportFlags_NoFocusOnClick) ? " NoFocusOnClick" : "",
+ (flags & ImGuiViewportFlags_NoInputs) ? " NoInputs" : "",
+ (flags & ImGuiViewportFlags_NoRendererClear) ? " NoRendererClear" : "",
+ (flags & ImGuiViewportFlags_NoAutoMerge) ? " NoAutoMerge" : "",
+ (flags & ImGuiViewportFlags_TopMost) ? " TopMost" : "",
+ (flags & ImGuiViewportFlags_CanHostOtherWindows) ? " CanHostOtherWindows" : "");
for (ImDrawList* draw_list : viewport->DrawDataP.CmdLists)
DebugNodeDrawList(NULL, viewport, draw_list, "DrawList");
TreePop();
@@ -15332,6 +21298,7 @@ void ImGui::DebugNodeWindow(ImGuiWindow* window, const char* label)
(flags & ImGuiWindowFlags_ChildWindow) ? "Child " : "", (flags & ImGuiWindowFlags_Tooltip) ? "Tooltip " : "", (flags & ImGuiWindowFlags_Popup) ? "Popup " : "",
(flags & ImGuiWindowFlags_Modal) ? "Modal " : "", (flags & ImGuiWindowFlags_ChildMenu) ? "ChildMenu " : "", (flags & ImGuiWindowFlags_NoSavedSettings) ? "NoSavedSettings " : "",
(flags & ImGuiWindowFlags_NoMouseInputs)? "NoMouseInputs":"", (flags & ImGuiWindowFlags_NoNavInputs) ? "NoNavInputs" : "", (flags & ImGuiWindowFlags_AlwaysAutoResize) ? "AlwaysAutoResize" : "");
+ BulletText("WindowClassId: 0x%08X", window->WindowClass.ClassId);
BulletText("Scroll: (%.2f/%.2f,%.2f/%.2f) Scrollbar:%s%s", window->Scroll.x, window->ScrollMax.x, window->Scroll.y, window->ScrollMax.y, window->ScrollbarX ? "X" : "", window->ScrollbarY ? "Y" : "");
BulletText("Active: %d/%d, WriteAccessed: %d, BeginOrderWithinContext: %d", window->Active, window->WasActive, window->WriteAccessed, (window->Active || window->WasActive) ? window->BeginOrderWithinContext : -1);
BulletText("Appearing: %d, Hidden: %d (CanSkip %d Cannot %d), SkipItems: %d", window->Appearing, window->Hidden, window->HiddenFramesCanSkipItems, window->HiddenFramesCannotSkipItems, window->SkipItems);
@@ -15348,7 +21315,15 @@ void ImGui::DebugNodeWindow(ImGuiWindow* window, const char* label)
for (int layer = 0; layer < ImGuiNavLayer_COUNT; layer++)
BulletText("NavPreferredScoringPosRel[%d] = {%.1f,%.1f)", layer, (pr[layer].x == FLT_MAX ? -99999.0f : pr[layer].x), (pr[layer].y == FLT_MAX ? -99999.0f : pr[layer].y)); // Display as 99999.0f so it looks neater.
BulletText("NavLayersActiveMask: %X, NavLastChildNavWindow: %s", window->DC.NavLayersActiveMask, window->NavLastChildNavWindow ? window->NavLastChildNavWindow->Name : "NULL");
+
+ BulletText("Viewport: %d%s, ViewportId: 0x%08X, ViewportPos: (%.1f,%.1f)", window->Viewport ? window->Viewport->Idx : -1, window->ViewportOwned ? " (Owned)" : "", window->ViewportId, window->ViewportPos.x, window->ViewportPos.y);
+ BulletText("ViewportMonitor: %d", window->Viewport ? window->Viewport->PlatformMonitor : -1);
+ BulletText("DockId: 0x%04X, DockOrder: %d, Act: %d, Vis: %d", window->DockId, window->DockOrder, window->DockIsActive, window->DockTabIsVisible);
+ if (window->DockNode || window->DockNodeAsHost)
+ DebugNodeDockNode(window->DockNodeAsHost ? window->DockNodeAsHost : window->DockNode, window->DockNodeAsHost ? "DockNodeAsHost" : "DockNode");
+
if (window->RootWindow != window) { DebugNodeWindow(window->RootWindow, "RootWindow"); }
+ if (window->RootWindowDockTree != window->RootWindow) { DebugNodeWindow(window->RootWindowDockTree, "RootWindowDockTree"); }
if (window->ParentWindow != NULL) { DebugNodeWindow(window->ParentWindow, "ParentWindow"); }
if (window->ParentWindowForFocusRoute != NULL) { DebugNodeWindow(window->ParentWindowForFocusRoute, "ParentWindowForFocusRoute"); }
if (window->DC.ChildWindows.Size > 0) { DebugNodeWindowsList(&window->DC.ChildWindows, "ChildWindows"); }
@@ -15470,11 +21445,13 @@ void ImGui::ShowDebugLogWindow(bool* p_open)
ShowDebugLogFlag("ActiveId", ImGuiDebugLogFlags_EventActiveId);
ShowDebugLogFlag("Clipper", ImGuiDebugLogFlags_EventClipper);
+ ShowDebugLogFlag("Docking", ImGuiDebugLogFlags_EventDocking);
ShowDebugLogFlag("Focus", ImGuiDebugLogFlags_EventFocus);
ShowDebugLogFlag("IO", ImGuiDebugLogFlags_EventIO);
ShowDebugLogFlag("Nav", ImGuiDebugLogFlags_EventNav);
ShowDebugLogFlag("Popup", ImGuiDebugLogFlags_EventPopup);
//ShowDebugLogFlag("Selection", ImGuiDebugLogFlags_EventSelection);
+ ShowDebugLogFlag("Viewport", ImGuiDebugLogFlags_EventViewport);
ShowDebugLogFlag("InputRouting", ImGuiDebugLogFlags_EventInputRouting);
if (SmallButton("Clear"))
@@ -15778,7 +21755,7 @@ void ImGui::ShowIDStackToolWindow(bool* p_open)
Checkbox("Ctrl+C: copy path to clipboard", &tool->CopyToClipboardOnCtrlC);
SameLine();
TextColored((time_since_copy >= 0.0f && time_since_copy < 0.75f && ImFmod(time_since_copy, 0.25f) < 0.25f * 0.5f) ? ImVec4(1.f, 1.f, 0.3f, 1.f) : ImVec4(), "*COPIED*");
- if (tool->CopyToClipboardOnCtrlC && Shortcut(ImGuiMod_Ctrl | ImGuiKey_C, 0, ImGuiInputFlags_RouteGlobal))
+ if (tool->CopyToClipboardOnCtrlC && Shortcut(ImGuiMod_Ctrl | ImGuiKey_C, ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteOverFocused))
{
tool->CopyToClipboardLastTime = (float)g.Time;
char* p = g.TempBuffer.Data;
diff --git a/backends/imgui/imgui.h b/backends/imgui/imgui.h
index 52830d50174..f6af5da13c0 100644
--- a/backends/imgui/imgui.h
+++ b/backends/imgui/imgui.h
@@ -1,4 +1,4 @@
-// dear imgui, v1.90.6 WIP
+// dear imgui, v1.90.7 WIP
// (headers)
// Help:
@@ -27,9 +27,11 @@
// Library Version
// (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345')
-#define IMGUI_VERSION "1.90.6"
-#define IMGUI_VERSION_NUM 19060
+#define IMGUI_VERSION "1.90.7 WIP"
+#define IMGUI_VERSION_NUM 19067
#define IMGUI_HAS_TABLE
+#define IMGUI_HAS_VIEWPORT // Viewport WIP branch
+#define IMGUI_HAS_DOCK // Docking WIP branch
/*
@@ -42,12 +44,12 @@ Index of this file:
// [SECTION] Helpers: Memory allocations macros, ImVector<>
// [SECTION] ImGuiStyle
// [SECTION] ImGuiIO
-// [SECTION] Misc data structures (ImGuiInputTextCallbackData, ImGuiSizeCallbackData, ImGuiPayload)
+// [SECTION] Misc data structures (ImGuiInputTextCallbackData, ImGuiSizeCallbackData, ImGuiWindowClass, ImGuiPayload)
// [SECTION] Helpers (ImGuiOnceUponAFrame, ImGuiTextFilter, ImGuiTextBuffer, ImGuiStorage, ImGuiListClipper, Math Operators, ImColor)
// [SECTION] Drawing API (ImDrawCallback, ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawFlags, ImDrawListFlags, ImDrawList, ImDrawData)
// [SECTION] Font API (ImFontConfig, ImFontGlyph, ImFontGlyphRangesBuilder, ImFontAtlasFlags, ImFontAtlas, ImFont)
// [SECTION] Viewports (ImGuiViewportFlags, ImGuiViewport)
-// [SECTION] Platform Dependent Interfaces (ImGuiPlatformImeData)
+// [SECTION] Platform Dependent Interfaces (ImGuiPlatformIO, ImGuiPlatformMonitor, ImGuiPlatformImeData)
// [SECTION] Obsolete functions and types
*/
@@ -90,6 +92,8 @@ Index of this file:
#endif
#define IM_ARRAYSIZE(_ARR) ((int)(sizeof(_ARR) / sizeof(*(_ARR)))) // Size of a static C-style array. Don't use on pointers!
#define IM_UNUSED(_VAR) ((void)(_VAR)) // Used to silence "unused variable warnings". Often useful as asserts may be stripped out from final builds.
+
+// Check that version and structures layouts are matching between compiled imgui code and caller. Read comments above DebugCheckVersionAndDataLayout() for details.
#define IMGUI_CHECKVERSION() ImGui::DebugCheckVersionAndDataLayout(IMGUI_VERSION, sizeof(ImGuiIO), sizeof(ImGuiStyle), sizeof(ImVec2), sizeof(ImVec4), sizeof(ImDrawVert), sizeof(ImDrawIdx))
// Helper Macros - IM_FMTARGS, IM_FMTLIST: Apply printf-style warnings to our formatting functions.
@@ -165,6 +169,8 @@ struct ImGuiKeyData; // Storage for ImGuiIO and IsKeyDown(), IsKe
struct ImGuiListClipper; // Helper to manually clip large list of items
struct ImGuiOnceUponAFrame; // Helper for running a block of code not more than once a frame
struct ImGuiPayload; // User data payload for drag and drop operations
+struct ImGuiPlatformIO; // Multi-viewport support: interface for Platform/Renderer backends + viewports to render
+struct ImGuiPlatformMonitor; // Multi-viewport support: user-provided bounds for each connected monitor/display. Used when positioning popups and tooltips to avoid them straddling monitors
struct ImGuiPlatformImeData; // Platform IME data for io.SetPlatformImeDataFn() function.
struct ImGuiSizeCallbackData; // Callback data when using SetNextWindowSizeConstraints() (rare/advanced use)
struct ImGuiStorage; // Helper for key->value storage
@@ -173,7 +179,8 @@ struct ImGuiTableSortSpecs; // Sorting specifications for a table (often
struct ImGuiTableColumnSortSpecs; // Sorting specification for one column of a table
struct ImGuiTextBuffer; // Helper to hold and append into a text buffer (~string builder)
struct ImGuiTextFilter; // Helper to parse and apply text filters (e.g. "aaaaa[,bbbbb][,ccccc]")
-struct ImGuiViewport; // A Platform Window (always only one in 'master' branch), in the future may represent Platform Monitor
+struct ImGuiViewport; // A Platform Window (always 1 unless multi-viewport are enabled. One per platform window to output to). In the future may represent Platform Monitor
+struct ImGuiWindowClass; // Window class (rare/advanced uses: provide hints to the platform backend via altered viewport flags and parent/child info)
// Enumerations
// - We don't use strongly typed enums much because they add constraints (can't extend in private code, can't store typed in bit fields, extra casting on iteration)
@@ -207,9 +214,11 @@ typedef int ImGuiChildFlags; // -> enum ImGuiChildFlags_ // Flags: f
typedef int ImGuiColorEditFlags; // -> enum ImGuiColorEditFlags_ // Flags: for ColorEdit4(), ColorPicker4() etc.
typedef int ImGuiConfigFlags; // -> enum ImGuiConfigFlags_ // Flags: for io.ConfigFlags
typedef int ImGuiComboFlags; // -> enum ImGuiComboFlags_ // Flags: for BeginCombo()
+typedef int ImGuiDockNodeFlags; // -> enum ImGuiDockNodeFlags_ // Flags: for DockSpace()
typedef int ImGuiDragDropFlags; // -> enum ImGuiDragDropFlags_ // Flags: for BeginDragDropSource(), AcceptDragDropPayload()
typedef int ImGuiFocusedFlags; // -> enum ImGuiFocusedFlags_ // Flags: for IsWindowFocused()
typedef int ImGuiHoveredFlags; // -> enum ImGuiHoveredFlags_ // Flags: for IsItemHovered(), IsWindowHovered() etc.
+typedef int ImGuiInputFlags; // -> enum ImGuiInputFlags_ // Flags: for Shortcut(), SetNextItemShortcut()
typedef int ImGuiInputTextFlags; // -> enum ImGuiInputTextFlags_ // Flags: for InputText(), InputTextMultiline()
typedef int ImGuiKeyChord; // -> ImGuiKey | ImGuiMod_XXX // Flags: for IsKeyChordPressed(), Shortcut() etc. an ImGuiKey optionally OR-ed with one or more ImGuiMod_XXX values.
typedef int ImGuiPopupFlags; // -> enum ImGuiPopupFlags_ // Flags: for OpenPopup*(), BeginPopupContext*(), IsPopupOpen()
@@ -378,10 +387,12 @@ namespace ImGui
IMGUI_API bool IsWindowFocused(ImGuiFocusedFlags flags=0); // is current window focused? or its root/child, depending on flags. see flags for options.
IMGUI_API bool IsWindowHovered(ImGuiHoveredFlags flags=0); // is current window hovered and hoverable (e.g. not blocked by a popup/modal)? See ImGuiHoveredFlags_ for options. IMPORTANT: If you are trying to check whether your mouse should be dispatched to Dear ImGui or to your underlying app, you should not use this function! Use the 'io.WantCaptureMouse' boolean for that! Refer to FAQ entry "How can I tell whether to dispatch mouse/keyboard to Dear ImGui or my application?" for details.
IMGUI_API ImDrawList* GetWindowDrawList(); // get draw list associated to the current window, to append your own drawing primitives
+ IMGUI_API float GetWindowDpiScale(); // get DPI scale currently associated to the current window's viewport.
IMGUI_API ImVec2 GetWindowPos(); // get current window position in screen space (note: it is unlikely you need to use this. Consider using current layout pos instead, GetCursorScreenPos())
IMGUI_API ImVec2 GetWindowSize(); // get current window size (note: it is unlikely you need to use this. Consider using GetCursorScreenPos() and e.g. GetContentRegionAvail() instead)
IMGUI_API float GetWindowWidth(); // get current window width (shortcut for GetWindowSize().x)
IMGUI_API float GetWindowHeight(); // get current window height (shortcut for GetWindowSize().y)
+ IMGUI_API ImGuiViewport*GetWindowViewport(); // get viewport currently associated to the current window.
// Window manipulation
// - Prefer using SetNextXXX functions (before Begin) rather that SetXXX functions (after Begin).
@@ -393,6 +404,7 @@ namespace ImGui
IMGUI_API void SetNextWindowFocus(); // set next window to be focused / top-most. call before Begin()
IMGUI_API void SetNextWindowScroll(const ImVec2& scroll); // set next window scrolling value (use < 0.0f to not affect a given axis).
IMGUI_API void SetNextWindowBgAlpha(float alpha); // set next window background color alpha. helper to easily override the Alpha component of ImGuiCol_WindowBg/ChildBg/PopupBg. you may also use ImGuiWindowFlags_NoBackground.
+ IMGUI_API void SetNextWindowViewport(ImGuiID viewport_id); // set next window viewport
IMGUI_API void SetWindowPos(const ImVec2& pos, ImGuiCond cond = 0); // (not recommended) set current window position - call within Begin()/End(). prefer using SetNextWindowPos(), as this may incur tearing and side-effects.
IMGUI_API void SetWindowSize(const ImVec2& size, ImGuiCond cond = 0); // (not recommended) set current window size - call within Begin()/End(). set to ImVec2(0, 0) to force an auto-fit. prefer using SetNextWindowSize(), as this may incur tearing and minor side-effects.
IMGUI_API void SetWindowCollapsed(bool collapsed, ImGuiCond cond = 0); // (not recommended) set current window collapsed state. prefer using SetNextWindowCollapsed().
@@ -699,7 +711,8 @@ namespace ImGui
// Tooltips
// - Tooltips are windows following the mouse. They do not take focus away.
- // - A tooltip window can contain items of any types. SetTooltip() is a shortcut for the 'if (BeginTooltip()) { Text(...); EndTooltip(); }' idiom.
+ // - A tooltip window can contain items of any types.
+ // - SetTooltip() is more or less a shortcut for the 'if (BeginTooltip()) { Text(...); EndTooltip(); }' idiom (with a subtlety that it discard any previously submitted tooltip)
IMGUI_API bool BeginTooltip(); // begin/append a tooltip window.
IMGUI_API void EndTooltip(); // only call EndTooltip() if BeginTooltip()/BeginItemTooltip() returns true!
IMGUI_API void SetTooltip(const char* fmt, ...) IM_FMTARGS(1); // set a text-only tooltip. Often used after a ImGui::IsItemHovered() check. Override any previous call to SetTooltip().
@@ -831,6 +844,26 @@ namespace ImGui
IMGUI_API bool TabItemButton(const char* label, ImGuiTabItemFlags flags = 0); // create a Tab behaving like a button. return true when clicked. cannot be selected in the tab bar.
IMGUI_API void SetTabItemClosed(const char* tab_or_docked_window_label); // notify TabBar or Docking system of a closed tab/window ahead (useful to reduce visual flicker on reorderable tab bars). For tab-bar: call after BeginTabBar() and before Tab submissions. Otherwise call with a window name.
+ // Docking
+ // [BETA API] Enable with io.ConfigFlags |= ImGuiConfigFlags_DockingEnable.
+ // Note: You can use most Docking facilities without calling any API. You DO NOT need to call DockSpace() to use Docking!
+ // - Drag from window title bar or their tab to dock/undock. Hold SHIFT to disable docking.
+ // - Drag from window menu button (upper-left button) to undock an entire node (all windows).
+ // - When io.ConfigDockingWithShift == true, you instead need to hold SHIFT to enable docking.
+ // About dockspaces:
+ // - Use DockSpaceOverViewport() to create a window covering the screen or a specific viewport + a dockspace inside it.
+ // This is often used with ImGuiDockNodeFlags_PassthruCentralNode to make it transparent.
+ // - Use DockSpace() to create an explicit dock node _within_ an existing window. See Docking demo for details.
+ // - Important: Dockspaces need to be submitted _before_ any window they can host. Submit it early in your frame!
+ // - Important: Dockspaces need to be kept alive if hidden, otherwise windows docked into it will be undocked.
+ // e.g. if you have multiple tabs with a dockspace inside each tab: submit the non-visible dockspaces with ImGuiDockNodeFlags_KeepAliveOnly.
+ IMGUI_API ImGuiID DockSpace(ImGuiID dockspace_id, const ImVec2& size = ImVec2(0, 0), ImGuiDockNodeFlags flags = 0, const ImGuiWindowClass* window_class = NULL);
+ IMGUI_API ImGuiID DockSpaceOverViewport(ImGuiID dockspace_id = 0, const ImGuiViewport* viewport = NULL, ImGuiDockNodeFlags flags = 0, const ImGuiWindowClass* window_class = NULL);
+ IMGUI_API void SetNextWindowDockID(ImGuiID dock_id, ImGuiCond cond = 0); // set next window dock id
+ IMGUI_API void SetNextWindowClass(const ImGuiWindowClass* window_class); // set next window class (control docking compatibility + provide hints to platform backend via custom viewport flags and platform parent/child relationship)
+ IMGUI_API ImGuiID GetWindowDockID();
+ IMGUI_API bool IsWindowDocked(); // is current window docked into another window?
+
// Logging/Capture
// - All text output from the interface can be captured into tty/file/clipboard. By default, tree nodes are automatically opened during logging.
IMGUI_API void LogToTTY(int auto_open_depth = -1); // start logging to tty (stdout)
@@ -902,8 +935,10 @@ namespace ImGui
IMGUI_API ImGuiViewport* GetMainViewport(); // return primary/default viewport. This can never be NULL.
// Background/Foreground Draw Lists
- IMGUI_API ImDrawList* GetBackgroundDrawList(); // this draw list will be the first rendered one. Useful to quickly draw shapes/text behind dear imgui contents.
- IMGUI_API ImDrawList* GetForegroundDrawList(); // this draw list will be the last rendered one. Useful to quickly draw shapes/text over dear imgui contents.
+ IMGUI_API ImDrawList* GetBackgroundDrawList(); // get background draw list for the viewport associated to the current window. this draw list will be the first rendering one. Useful to quickly draw shapes/text behind dear imgui contents.
+ IMGUI_API ImDrawList* GetForegroundDrawList(); // get foreground draw list for the viewport associated to the current window. this draw list will be the last rendered one. Useful to quickly draw shapes/text over dear imgui contents.
+ IMGUI_API ImDrawList* GetBackgroundDrawList(ImGuiViewport* viewport); // get background draw list for the given viewport. this draw list will be the first rendering one. Useful to quickly draw shapes/text behind dear imgui contents.
+ IMGUI_API ImDrawList* GetForegroundDrawList(ImGuiViewport* viewport); // get foreground draw list for the given viewport. this draw list will be the last rendered one. Useful to quickly draw shapes/text over dear imgui contents.
// Miscellaneous Utilities
IMGUI_API bool IsRectVisible(const ImVec2& size); // test if rectangle (of given size, starting from cursor position) is visible / not clipped.
@@ -937,6 +972,24 @@ namespace ImGui
IMGUI_API const char* GetKeyName(ImGuiKey key); // [DEBUG] returns English name of the key. Those names a provided for debugging purpose and are not meant to be saved persistently not compared.
IMGUI_API void SetNextFrameWantCaptureKeyboard(bool want_capture_keyboard); // Override io.WantCaptureKeyboard flag next frame (said flag is left for your application to handle, typically when true it instructs your app to ignore inputs). e.g. force capture keyboard when your widget is being hovered. This is equivalent to setting "io.WantCaptureKeyboard = want_capture_keyboard"; after the next NewFrame() call.
+ // Inputs Utilities: Shortcut Testing & Routing
+ // - ImGuiKeyChord = a ImGuiKey + optional ImGuiMod_Alt/ImGuiMod_Ctrl/ImGuiMod_Shift/ImGuiMod_Super.
+ // ImGuiKey_C // Accepted by functions taking ImGuiKey or ImGuiKeyChord arguments)
+ // ImGuiMod_Ctrl | ImGuiKey_C // Accepted by functions taking ImGuiKeyChord arguments)
+ // only ImGuiMod_XXX values are legal to combine with an ImGuiKey. You CANNOT combine two ImGuiKey values.
+ // - The general idea is that several callers may register interest in a shortcut, and only one owner gets it.
+ // Parent -> call Shortcut(Ctrl+S) // When Parent is focused, Parent gets the shortcut.
+ // Child1 -> call Shortcut(Ctrl+S) // When Child1 is focused, Child1 gets the shortcut (Child1 overrides Parent shortcuts)
+ // Child2 -> no call // When Child2 is focused, Parent gets the shortcut.
+ // The whole system is order independent, so if Child1 makes its calls before Parent, results will be identical.
+ // This is an important property as it facilitate working with foreign code or larger codebase.
+ // - To understand the difference:
+ // - IsKeyChordPressed() compares mods and call IsKeyPressed() -> function has no side-effect.
+ // - Shortcut() submits a route, routes are resolved, if it currently can be routed it calls IsKeyChordPressed() -> function has (desirable) side-effects as it can prevents another call from getting the route.
+ // - Visualize registered routes in 'Metrics/Debugger->Inputs'.
+ IMGUI_API bool Shortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags = 0);
+ IMGUI_API void SetNextItemShortcut(ImGuiKeyChord key_chord, ImGuiInputFlags flags = 0);
+
// Inputs Utilities: Mouse specific
// - To refer to a mouse button, you may use named enums in your code e.g. ImGuiMouseButton_Left, ImGuiMouseButton_Right.
// - You can also use regular integer: it is forever guaranteed that 0=Left, 1=Right, 2=Middle.
@@ -988,6 +1041,16 @@ namespace ImGui
IMGUI_API void* MemAlloc(size_t size);
IMGUI_API void MemFree(void* ptr);
+ // (Optional) Platform/OS interface for multi-viewport support
+ // Read comments around the ImGuiPlatformIO structure for more details.
+ // Note: You may use GetWindowViewport() to get the current viewport of the current window.
+ IMGUI_API ImGuiPlatformIO& GetPlatformIO(); // platform/renderer functions, for backend to setup + viewports list.
+ IMGUI_API void UpdatePlatformWindows(); // call in main loop. will call CreateWindow/ResizeWindow/etc. platform functions for each secondary viewport, and DestroyWindow for each inactive viewport.
+ IMGUI_API void RenderPlatformWindowsDefault(void* platform_render_arg = NULL, void* renderer_render_arg = NULL); // call in main loop. will call RenderWindow/SwapBuffers platform functions for each secondary viewport which doesn't have the ImGuiViewportFlags_Minimized flag set. May be reimplemented by user for custom rendering needs.
+ IMGUI_API void DestroyPlatformWindows(); // call DestroyWindow platform functions for all viewports. call from backend Shutdown() if you need to close platform windows before imgui shutdown. otherwise will be called by DestroyContext().
+ IMGUI_API ImGuiViewport* FindViewportByID(ImGuiID id); // this is a helper for backends.
+ IMGUI_API ImGuiViewport* FindViewportByPlatformHandle(void* platform_handle); // this is a helper for backends. the type platform_handle is decided by the backend (e.g. HWND, MyWindow*, GLFWwindow* etc.)
+
} // namespace ImGui
//-----------------------------------------------------------------------------
@@ -1018,6 +1081,7 @@ enum ImGuiWindowFlags_
ImGuiWindowFlags_NoNavInputs = 1 << 16, // No gamepad/keyboard navigation within the window
ImGuiWindowFlags_NoNavFocus = 1 << 17, // No focusing toward this window with gamepad/keyboard navigation (e.g. skipped by CTRL+TAB)
ImGuiWindowFlags_UnsavedDocument = 1 << 18, // Display a dot next to the title. When used in a tab/docking context, tab is selected when clicking the X + closure is not assumed (will wait for user to stop submitting the tab). Otherwise closure is assumed when pressing the X, so if you keep submitting the tab may reappear at end of tab bar.
+ ImGuiWindowFlags_NoDocking = 1 << 19, // Disable docking of this window
ImGuiWindowFlags_NoNav = ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoNavFocus,
ImGuiWindowFlags_NoDecoration = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse,
ImGuiWindowFlags_NoInputs = ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoNavFocus,
@@ -1029,6 +1093,7 @@ enum ImGuiWindowFlags_
ImGuiWindowFlags_Popup = 1 << 26, // Don't use! For internal use by BeginPopup()
ImGuiWindowFlags_Modal = 1 << 27, // Don't use! For internal use by BeginPopupModal()
ImGuiWindowFlags_ChildMenu = 1 << 28, // Don't use! For internal use by BeginMenu()
+ ImGuiWindowFlags_DockNodeHost = 1 << 29, // Don't use! For internal use by Begin()/NewFrame()
// Obsolete names
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
@@ -1211,7 +1276,7 @@ enum ImGuiFocusedFlags_
ImGuiFocusedFlags_RootWindow = 1 << 1, // Test from root window (top most parent of the current hierarchy)
ImGuiFocusedFlags_AnyWindow = 1 << 2, // Return true if any window is focused. Important: If you are trying to tell how to dispatch your low-level inputs, do NOT use this. Use 'io.WantCaptureMouse' instead! Please read the FAQ!
ImGuiFocusedFlags_NoPopupHierarchy = 1 << 3, // Do not consider popup hierarchy (do not treat popup emitter as parent of popup) (when used with _ChildWindows or _RootWindow)
- //ImGuiFocusedFlags_DockHierarchy = 1 << 4, // Consider docking hierarchy (treat dockspace host as parent of docked window) (when used with _ChildWindows or _RootWindow)
+ ImGuiFocusedFlags_DockHierarchy = 1 << 4, // Consider docking hierarchy (treat dockspace host as parent of docked window) (when used with _ChildWindows or _RootWindow)
ImGuiFocusedFlags_RootAndChildWindows = ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows,
};
@@ -1225,7 +1290,7 @@ enum ImGuiHoveredFlags_
ImGuiHoveredFlags_RootWindow = 1 << 1, // IsWindowHovered() only: Test from root window (top most parent of the current hierarchy)
ImGuiHoveredFlags_AnyWindow = 1 << 2, // IsWindowHovered() only: Return true if any window is hovered
ImGuiHoveredFlags_NoPopupHierarchy = 1 << 3, // IsWindowHovered() only: Do not consider popup hierarchy (do not treat popup emitter as parent of popup) (when used with _ChildWindows or _RootWindow)
- //ImGuiHoveredFlags_DockHierarchy = 1 << 4, // IsWindowHovered() only: Consider docking hierarchy (treat dockspace host as parent of docked window) (when used with _ChildWindows or _RootWindow)
+ ImGuiHoveredFlags_DockHierarchy = 1 << 4, // IsWindowHovered() only: Consider docking hierarchy (treat dockspace host as parent of docked window) (when used with _ChildWindows or _RootWindow)
ImGuiHoveredFlags_AllowWhenBlockedByPopup = 1 << 5, // Return true even if a popup window is normally blocking access to this item/window
//ImGuiHoveredFlags_AllowWhenBlockedByModal = 1 << 6, // Return true even if a modal popup window is normally blocking access to this item/window. FIXME-TODO: Unavailable yet.
ImGuiHoveredFlags_AllowWhenBlockedByActiveItem = 1 << 7, // Return true even if an active item is blocking access to this item/window. Useful for Drag and Drop patterns.
@@ -1255,6 +1320,27 @@ enum ImGuiHoveredFlags_
ImGuiHoveredFlags_NoSharedDelay = 1 << 17, // IsItemHovered() only: Disable shared delay system where moving from one item to the next keeps the previous timer for a short time (standard for tooltips with long delays)
};
+// Flags for ImGui::DockSpace(), shared/inherited by child nodes.
+// (Some flags can be applied to individual nodes directly)
+// FIXME-DOCK: Also see ImGuiDockNodeFlagsPrivate_ which may involve using the WIP and internal DockBuilder api.
+enum ImGuiDockNodeFlags_
+{
+ ImGuiDockNodeFlags_None = 0,
+ ImGuiDockNodeFlags_KeepAliveOnly = 1 << 0, // // Don't display the dockspace node but keep it alive. Windows docked into this dockspace node won't be undocked.
+ //ImGuiDockNodeFlags_NoCentralNode = 1 << 1, // // Disable Central Node (the node which can stay empty)
+ ImGuiDockNodeFlags_NoDockingOverCentralNode = 1 << 2, // // Disable docking over the Central Node, which will be always kept empty.
+ ImGuiDockNodeFlags_PassthruCentralNode = 1 << 3, // // Enable passthru dockspace: 1) DockSpace() will render a ImGuiCol_WindowBg background covering everything excepted the Central Node when empty. Meaning the host window should probably use SetNextWindowBgAlpha(0.0f) prior to Begin() when using this. 2) When Central Node is empty: let inputs pass-through + won't display a DockingEmptyBg background. See demo for details.
+ ImGuiDockNodeFlags_NoDockingSplit = 1 << 4, // // Disable other windows/nodes from splitting this node.
+ ImGuiDockNodeFlags_NoResize = 1 << 5, // Saved // Disable resizing node using the splitter/separators. Useful with programmatically setup dockspaces.
+ ImGuiDockNodeFlags_AutoHideTabBar = 1 << 6, // // Tab bar will automatically hide when there is a single window in the dock node.
+ ImGuiDockNodeFlags_NoUndocking = 1 << 7, // // Disable undocking this node.
+
+#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
+ ImGuiDockNodeFlags_NoSplit = ImGuiDockNodeFlags_NoDockingSplit, // Renamed in 1.90
+ ImGuiDockNodeFlags_NoDockingInCentralNode = ImGuiDockNodeFlags_NoDockingOverCentralNode, // Renamed in 1.90
+#endif
+};
+
// Flags for ImGui::BeginDragDropSource(), ImGui::AcceptDragDropPayload()
enum ImGuiDragDropFlags_
{
@@ -1424,13 +1510,13 @@ enum ImGuiKey : int
// - In theory the value of keyboard modifiers should be roughly equivalent to a logical or of the equivalent left/right keys.
// In practice: it's complicated; mods are often provided from different sources. Keyboard layout, IME, sticky keys and
// backends tend to interfere and break that equivalence. The safer decision is to relay that ambiguity down to the end-user...
+ // - On macOS, we swap Cmd(Super) and Ctrl keys at the time of the io.AddKeyEvent() call.
ImGuiMod_None = 0,
- ImGuiMod_Ctrl = 1 << 12, // Ctrl
+ ImGuiMod_Ctrl = 1 << 12, // Ctrl (non-macOS), Cmd (macOS)
ImGuiMod_Shift = 1 << 13, // Shift
ImGuiMod_Alt = 1 << 14, // Option/Menu
- ImGuiMod_Super = 1 << 15, // Cmd/Super/Windows
- ImGuiMod_Shortcut = 1 << 11, // Alias for Ctrl (non-macOS) _or_ Super (macOS).
- ImGuiMod_Mask_ = 0xF800, // 5-bits
+ ImGuiMod_Super = 1 << 15, // Windows/Super (non-macOS), Ctrl (macOS)
+ ImGuiMod_Mask_ = 0xF000, // 4-bits
// [Internal] Prior to 1.87 we required user to fill io.KeysDown[512] using their own native index + the io.KeyMap[] array.
// We are ditching this method but keeping a legacy path for user code doing e.g. IsKeyPressed(MY_NATIVE_KEY_CODE)
@@ -1447,11 +1533,37 @@ enum ImGuiKey : int
#endif
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
+ ImGuiMod_Shortcut = ImGuiMod_Ctrl, // Removed in 1.90.7, you can now simply use ImGuiMod_Ctrl
ImGuiKey_ModCtrl = ImGuiMod_Ctrl, ImGuiKey_ModShift = ImGuiMod_Shift, ImGuiKey_ModAlt = ImGuiMod_Alt, ImGuiKey_ModSuper = ImGuiMod_Super, // Renamed in 1.89
//ImGuiKey_KeyPadEnter = ImGuiKey_KeypadEnter, // Renamed in 1.87
#endif
};
+// Flags for Shortcut(), SetNextItemShortcut(),
+// (and for upcoming extended versions of IsKeyPressed(), IsMouseClicked(), Shortcut(), SetKeyOwner(), SetItemKeyOwner() that are still in imgui_internal.h)
+// Don't mistake with ImGuiInputTextFlags! (which is for ImGui::InputText() function)
+enum ImGuiInputFlags_
+{
+ ImGuiInputFlags_None = 0,
+ ImGuiInputFlags_Repeat = 1 << 0, // Enable repeat. Return true on successive repeats. Default for legacy IsKeyPressed(). NOT Default for legacy IsMouseClicked(). MUST BE == 1.
+
+ // Flags for Shortcut(), SetNextItemShortcut()
+ // - Routing policies: RouteGlobalOverActive >> RouteActive or RouteFocused (if owner is active item) >> RouteGlobalOverFocused >> RouteFocused (if in focused window stack) >> RouteGlobal.
+ // - Default policy is RouteFocused. Can select only 1 policy among all available.
+ ImGuiInputFlags_RouteActive = 1 << 10, // Route to active item only.
+ ImGuiInputFlags_RouteFocused = 1 << 11, // Route to windows in the focus stack (DEFAULT). Deep-most focused window takes inputs. Active item takes inputs over deep-most focused window.
+ ImGuiInputFlags_RouteGlobal = 1 << 12, // Global route (unless a focused window or active item registered the route).
+ ImGuiInputFlags_RouteAlways = 1 << 13, // Do not register route, poll keys directly.
Commit: 9f223073536e7918ecd8f06ba5a4341a9f4062a3
https://github.com/scummvm/scummvm/commit/9f223073536e7918ecd8f06ba5a4341a9f4062a3
Author: scemino (scemino74 at gmail.com)
Date: 2024-05-24T16:37:25+02:00
Commit Message:
DIRECTOR: Use imgui docking
Changed paths:
engines/director/debugtools.cpp
diff --git a/engines/director/debugtools.cpp b/engines/director/debugtools.cpp
index 0a446ab7428..715bd113925 100644
--- a/engines/director/debugtools.cpp
+++ b/engines/director/debugtools.cpp
@@ -3275,6 +3275,8 @@ void onImGuiRender() {
ImGuiIO &io = ImGui::GetIO();
io.ConfigFlags &= ~(ImGuiConfigFlags_NoMouseCursorChange | ImGuiConfigFlags_NoMouse);
+ ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport(), ImGuiDockNodeFlags_PassthruCentralNode);
+
if (ImGui::BeginMainMenuBar()) {
if (ImGui::BeginMenu("View")) {
ImGui::SeparatorText("Windows");
More information about the Scummvm-git-logs
mailing list