From f9bb2976b1103df2df724cd9b7ccfd6d97839760 Mon Sep 17 00:00:00 2001 From: Ionut Cava Date: Tue, 2 Oct 2018 12:36:05 +0100 Subject: [PATCH] - Move to the ImGui Docking branch (https://github.com/ocornut/imgui/issues/2109) --- Source Code/Editor/Editor.cpp | 268 +- .../Editor/Widgets/Headers/PanelManager.h | 1 - Source Code/Editor/Widgets/PanelManager.cpp | 15 - .../Libs/imgui/.github/CONTRIBUTING.md | 6 +- .../Libs/imgui/.github/issue_template.md | 14 +- Source Code/Libs/imgui/addons/imgui_user.h | 16 +- Source Code/Libs/imgui/addons/imgui_user.inl | 9 - .../addons/imguibindings/imguibindings.cpp | 22 - .../Libs/imgui/addons/imguidock/imguidock.cpp | 1484 ---- .../Libs/imgui/addons/imguidock/imguidock.h | 150 - .../imguipanelmanager/imguipanelmanager.cpp | 1875 ----- .../imguipanelmanager/imguipanelmanager.h | 249 - .../addons/imguitabwindow/imguitabwindow.cpp | 3578 -------- .../addons/imguitabwindow/imguitabwindow.h | 574 -- Source Code/Libs/imgui/docs/CHANGELOG.txt | 89 +- Source Code/Libs/imgui/docs/README.md | 6 +- Source Code/Libs/imgui/docs/TODO.txt | 38 +- Source Code/Libs/imgui/imgui.cpp | 7311 +++++++++++++---- Source Code/Libs/imgui/imgui.h | 287 +- Source Code/Libs/imgui/imgui_demo.cpp | 559 +- Source Code/Libs/imgui/imgui_draw.cpp | 108 +- Source Code/Libs/imgui/imgui_internal.h | 395 +- Source Code/Libs/imgui/imgui_widgets.cpp | 981 ++- Source Code/Libs/imgui/misc/fonts/README.txt | 37 +- .../imgui/misc/freetype/imgui_freetype.cpp | 2 +- Source Code/Platform/DisplayWindow.cpp | 14 +- Source Code/Platform/Headers/DisplayWindow.h | 2 + .../Divide-Editor/Divide-Editor.vcxproj | 3 - .../Divide-Editor.vcxproj.filters | 18 - imgui.ini | 29 +- 30 files changed, 8448 insertions(+), 9692 deletions(-) delete mode 100644 Source Code/Libs/imgui/addons/imguidock/imguidock.cpp delete mode 100644 Source Code/Libs/imgui/addons/imguidock/imguidock.h delete mode 100644 Source Code/Libs/imgui/addons/imguipanelmanager/imguipanelmanager.cpp delete mode 100644 Source Code/Libs/imgui/addons/imguipanelmanager/imguipanelmanager.h delete mode 100644 Source Code/Libs/imgui/addons/imguitabwindow/imguitabwindow.cpp delete mode 100644 Source Code/Libs/imgui/addons/imguitabwindow/imguitabwindow.h diff --git a/Source Code/Editor/Editor.cpp b/Source Code/Editor/Editor.cpp index e89fc6a9b0..d45b51c5ca 100644 --- a/Source Code/Editor/Editor.cpp +++ b/Source Code/Editor/Editor.cpp @@ -27,6 +27,17 @@ #include +#include + +#define SDL_HAS_WARP_MOUSE_GLOBAL SDL_VERSION_ATLEAST(2,0,4) +#define SDL_HAS_CAPTURE_MOUSE SDL_VERSION_ATLEAST(2,0,4) +#define SDL_HAS_WINDOW_OPACITY 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_MOUSE_FOCUS_CLICKTHROUGH SDL_VERSION_ATLEAST(2,0,5) + namespace Divide { namespace { @@ -35,6 +46,31 @@ namespace { I32 previous_window_opacity = 255; bool show_test_window = true; }; + +struct ImGuiViewportDataSDL2 +{ + SDL_Window* Window; + Uint32 WindowID; + bool WindowOwned; + SDL_GLContext GLContext; + + ImGuiViewportDataSDL2() { Window = NULL; WindowID = 0; WindowOwned = false; GLContext = NULL; } + ~ImGuiViewportDataSDL2() { IM_ASSERT(Window == NULL && GLContext == NULL); } +}; + +static void ImGui_ImplSDL2_CreateWindow(ImGuiViewport* viewport); +static void ImGui_ImplSDL2_DestroyWindow(ImGuiViewport* viewport); +static void ImGui_ImplSDL2_ShowWindow(ImGuiViewport* viewport); +static ImVec2 ImGui_ImplSDL2_GetWindowPos(ImGuiViewport* viewport); +static void ImGui_ImplSDL2_SetWindowPos(ImGuiViewport* viewport, ImVec2 pos); +static ImVec2 ImGui_ImplSDL2_GetWindowSize(ImGuiViewport* viewport); +static void ImGui_ImplSDL2_SetWindowSize(ImGuiViewport* viewport, ImVec2 size); +static void ImGui_ImplSDL2_SetWindowTitle(ImGuiViewport* viewport, const char* title); +static void ImGui_ImplSDL2_SetWindowFocus(ImGuiViewport* viewport); +static bool ImGui_ImplSDL2_GetWindowFocus(ImGuiViewport* viewport); +static void ImGui_ImplSDL2_RenderWindow(ImGuiViewport* viewport, void*); +static void ImGui_ImplSDL2_SwapBuffers(ImGuiViewport* viewport, void*); +static void ImGui_ImplSDL2_UpdateMonitors(); Editor::Editor(PlatformContext& context, ImGuiStyleEnum theme, ImGuiStyleEnum lostFocusTheme, ImGuiStyleEnum dimmedTheme) : PlatformContextComponent(context), @@ -145,6 +181,11 @@ bool Editor::init(const vec2& renderResolution) { io.Fonts->ClearTexData(); } + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking + io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows + //io.ConfigFlags |= ImGuiConfigFlags_ViewportsNoTaskBarIcons; + //io.ConfigFlags |= ImGuiConfigFlags_ViewportsNoMerge; io.KeyMap[ImGuiKey_Tab] = Input::KeyCode::KC_TAB; io.KeyMap[ImGuiKey_LeftArrow] = Input::KeyCode::KC_LEFT; io.KeyMap[ImGuiKey_RightArrow] = Input::KeyCode::KC_RIGHT; @@ -168,12 +209,46 @@ bool Editor::init(const vec2& renderResolution) { io.SetClipboardTextFn = SetClipboardText; io.GetClipboardTextFn = GetClipboardText; io.ClipboardUserData = nullptr; - io.ImeWindowHandle = _mainWindow->handle()._handle; + io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; +#if SDL_HAS_WARP_MOUSE_GLOBAL + io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used) +#endif +#if SDL_HAS_CAPTURE_MOUSE + io.BackendFlags |= ImGuiBackendFlags_PlatformHasViewports; // We can create multi-viewports on the Platform side (optional) + io.BackendFlags |= ImGuiBackendFlags_HasMouseHoveredViewport; +#endif io.DisplaySize = ImVec2((float)renderResolution.width, (float)renderResolution.height); io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f); } - ImGui::SetCurrentContext(_imguiContext[to_base(Context::Editor)]); + + 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_SetWindowTitle = ImGui_ImplSDL2_SetWindowTitle; + platform_io.Platform_RenderWindow = ImGui_ImplSDL2_RenderWindow; + platform_io.Platform_SwapBuffers = ImGui_ImplSDL2_SwapBuffers; + +#if SDL_HAS_MOUSE_FOCUS_CLICKTHROUGH + SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1"); +#endif + ImGui_ImplSDL2_UpdateMonitors(); + ImGuiViewportDataSDL2* data = IM_NEW(ImGuiViewportDataSDL2)(); + ImGuiViewport* main_viewport = ImGui::GetMainViewport(); + data->Window = _mainWindow->getRawWindow(); + data->WindowID = SDL_GetWindowID(_mainWindow->getRawWindow()); + data->WindowOwned = false; + data->GLContext = SDL_GL_GetCurrentContext(); + main_viewport->PlatformUserData = data; + main_viewport->PlatformHandle = data->Window; + _panelManager->init(renderResolution); ImGui::ResetStyle(_currentTheme); @@ -392,7 +467,11 @@ bool Editor::framePostRenderStarted(const FrameEvent& evt) { ImGui::Render(); renderDrawList(ImGui::GetDrawData(), _mainWindow->getGUID(), true); - + if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + { + ImGui::UpdatePlatformWindows(); + ImGui::RenderPlatformWindowsDefault(); + } return true; } @@ -856,4 +935,187 @@ ImGuiIO& Editor::GetIO(U8 idx) { return _imguiContext[idx]->IO; } + +//-------------------------------------------------------------------------------------------------------- +// MULTI-VIEWPORT / PLATFORM INTERFACE SUPPORT +// This is an _advanced_ and _optional_ feature, allowing the back-end 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_ImplSDL2_CreateWindow(ImGuiViewport* viewport) +{ + ImGuiViewportDataSDL2* data = IM_NEW(ImGuiViewportDataSDL2)(); + viewport->PlatformUserData = data; + + ImGuiViewport* main_viewport = ImGui::GetMainViewport(); + ImGuiViewportDataSDL2* main_viewport_data = (ImGuiViewportDataSDL2*)main_viewport->PlatformUserData; + + // Share GL resources with main context + bool use_opengl = (main_viewport_data->GLContext != NULL); + SDL_GLContext backup_context = NULL; + 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); + } + + // We don't enable SDL_WINDOW_RESIZABLE because it enforce windows decorations + Uint32 sdl_flags = 0; + sdl_flags |= SDL_WINDOW_OPENGL;//use_opengl ? SDL_WINDOW_OPENGL : SDL_WINDOW_VULKAN; + 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 SDL_HAS_ALWAYS_ON_TOP + sdl_flags |= (viewport->Flags & ImGuiViewportFlags_TopMost) ? SDL_WINDOW_ALWAYS_ON_TOP : 0; +#endif + data->Window = SDL_CreateWindow("No Title Yet", (int)viewport->Pos.x, (int)viewport->Pos.y, (int)viewport->Size.x, (int)viewport->Size.y, sdl_flags); + data->WindowOwned = true; + if (use_opengl) + { + data->GLContext = SDL_GL_CreateContext(data->Window); + SDL_GL_SetSwapInterval(0); + } + if (use_opengl && backup_context) + SDL_GL_MakeCurrent(data->Window, backup_context); + viewport->PlatformHandle = (void*)data->Window; +} + +static void ImGui_ImplSDL2_DestroyWindow(ImGuiViewport* viewport) +{ + if (ImGuiViewportDataSDL2* data = (ImGuiViewportDataSDL2*)viewport->PlatformUserData) + { + if (data->GLContext && data->WindowOwned) + SDL_GL_DeleteContext(data->GLContext); + if (data->Window && data->WindowOwned) + SDL_DestroyWindow(data->Window); + data->GLContext = NULL; + data->Window = NULL; + IM_DELETE(data); + } + viewport->PlatformUserData = viewport->PlatformHandle = NULL; +} + +static void ImGui_ImplSDL2_ShowWindow(ImGuiViewport* viewport) +{ + ImGuiViewportDataSDL2* data = (ImGuiViewportDataSDL2*)viewport->PlatformUserData; +#if defined(_WIN32) + SDL_SysWMinfo info; + SDL_VERSION(&info.version); + if (SDL_GetWindowWMInfo(data->Window, &info)) + { + HWND hwnd = info.info.win.window; + + // 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(data->Window); +} + +static ImVec2 ImGui_ImplSDL2_GetWindowPos(ImGuiViewport* viewport) +{ + ImGuiViewportDataSDL2* data = (ImGuiViewportDataSDL2*)viewport->PlatformUserData; + int x = 0, y = 0; + SDL_GetWindowPosition(data->Window, &x, &y); + return ImVec2((float)x, (float)y); +} + +static void ImGui_ImplSDL2_SetWindowPos(ImGuiViewport* viewport, ImVec2 pos) +{ + ImGuiViewportDataSDL2* data = (ImGuiViewportDataSDL2*)viewport->PlatformUserData; + SDL_SetWindowPosition(data->Window, (int)pos.x, (int)pos.y); +} + +static ImVec2 ImGui_ImplSDL2_GetWindowSize(ImGuiViewport* viewport) +{ + ImGuiViewportDataSDL2* data = (ImGuiViewportDataSDL2*)viewport->PlatformUserData; + int w = 0, h = 0; + SDL_GetWindowSize(data->Window, &w, &h); + return ImVec2((float)w, (float)h); +} + +static void ImGui_ImplSDL2_SetWindowSize(ImGuiViewport* viewport, ImVec2 size) +{ + ImGuiViewportDataSDL2* data = (ImGuiViewportDataSDL2*)viewport->PlatformUserData; + SDL_SetWindowSize(data->Window, (int)size.x, (int)size.y); +} + +static void ImGui_ImplSDL2_SetWindowTitle(ImGuiViewport* viewport, const char* title) +{ + ImGuiViewportDataSDL2* data = (ImGuiViewportDataSDL2*)viewport->PlatformUserData; + SDL_SetWindowTitle(data->Window, title); +} + +static void ImGui_ImplSDL2_SetWindowFocus(ImGuiViewport* viewport) +{ + ImGuiViewportDataSDL2* data = (ImGuiViewportDataSDL2*)viewport->PlatformUserData; + SDL_RaiseWindow(data->Window); +} + +static bool ImGui_ImplSDL2_GetWindowFocus(ImGuiViewport* viewport) +{ + ImGuiViewportDataSDL2* data = (ImGuiViewportDataSDL2*)viewport->PlatformUserData; + return (SDL_GetWindowFlags(data->Window) & SDL_WINDOW_INPUT_FOCUS) != 0; +} + +static void ImGui_ImplSDL2_RenderWindow(ImGuiViewport* viewport, void*) +{ + ImGuiViewportDataSDL2* data = (ImGuiViewportDataSDL2*)viewport->PlatformUserData; + if (data->GLContext) + SDL_GL_MakeCurrent(data->Window, data->GLContext); +} + +static void ImGui_ImplSDL2_SwapBuffers(ImGuiViewport* viewport, void*) +{ + ImGuiViewportDataSDL2* data = (ImGuiViewportDataSDL2*)viewport->PlatformUserData; + if (data->GLContext) + { + SDL_GL_MakeCurrent(data->Window, data->GLContext); + SDL_GL_SwapWindow(data->Window); + } +} + +// FIXME-PLATFORM: SDL doesn't have an event to notify the application of display/monitor changes +static void ImGui_ImplSDL2_UpdateMonitors() +{ + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + platform_io.Monitors.resize(0); + 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 + float dpi = 0.0f; + if (SDL_GetDisplayDPI(n, &dpi, NULL, NULL)) + monitor.DpiScale = dpi / 96.0f; +#endif + platform_io.Monitors.push_back(monitor); + } +} }; //namespace Divide diff --git a/Source Code/Editor/Widgets/Headers/PanelManager.h b/Source Code/Editor/Widgets/Headers/PanelManager.h index 6cbcd7ac73..4eaeafaf29 100644 --- a/Source Code/Editor/Widgets/Headers/PanelManager.h +++ b/Source Code/Editor/Widgets/Headers/PanelManager.h @@ -104,7 +104,6 @@ namespace Divide { protected: F32 calcMainMenuHeight(); - void drawDockedTabWindows(ImGui::PanelManagerWindowData& wd); void setSelectedCamera(Camera* camera); Camera* getSelectedCamera() const; diff --git a/Source Code/Editor/Widgets/PanelManager.cpp b/Source Code/Editor/Widgets/PanelManager.cpp index a5616c58dc..e43157ce5f 100644 --- a/Source Code/Editor/Widgets/PanelManager.cpp +++ b/Source Code/Editor/Widgets/PanelManager.cpp @@ -232,18 +232,6 @@ namespace Divide { s_globalCache = &context().kernel().resourceCache(); - if (!ImGui::TabWindow::DockPanelIconTextureID) { - ImVector rgba_buffer;int w, h; - ImGui::TabWindow::GetDockPanelIconImageRGBA(rgba_buffer, &w, &h); - ImGui::TabWindow::DockPanelIconTextureID = - LoadTextureFromMemory(_textures[to_base(TextureUsage::Dock)], - "Docking Texture", - w, - h, - 4, - &rgba_buffer[0]); - } - # ifdef YES_IMGUIIMAGEEDITOR ImGui::ImageEditor::SetGenerateOrUpdateTextureCallback(&GenerateOrUpdateTexture); // This will be called only with channels=3 or channels=4 ImGui::ImageEditor::SetFreeTextureCallback(&FreeTextureDelegate); @@ -319,9 +307,6 @@ namespace Divide { setPanelManagerBoundsToIncludeMainMenuIfPresent(w, h); // This line is only necessary if we have a global menu bar } - void PanelManager::drawDockedTabWindows(ImGui::PanelManagerWindowData& wd) { - } - void PanelManager::setSelectedCamera(Camera* camera) { _selectedCamera = camera; } diff --git a/Source Code/Libs/imgui/.github/CONTRIBUTING.md b/Source Code/Libs/imgui/.github/CONTRIBUTING.md index c438fc2357..4fe220b911 100644 --- a/Source Code/Libs/imgui/.github/CONTRIBUTING.md +++ b/Source Code/Libs/imgui/.github/CONTRIBUTING.md @@ -18,7 +18,7 @@ You may use the Issue Tracker to submit bug reports, feature requests or suggest **Guidelines to report an issue or ask a question:** - Please provide your imgui version number. - Please state if you have made substantial modifications to your copy of imgui. -- Try to be explicit with your expectations and what you have tried. What you have in mind or in your code is not obvious to other people. +- Try to be explicit with your Goals, your Expectations and what you have Tried. What you have in mind or in your code is not obvious to other people. People frequently discuss problems without first mentioning their goal. - If you are discussing an assert or a crash, please provide a debugger callstack. Never state "it crashes" without additional information. If you don't know how to use a debugger and retrieve a callstack, learning about it will be useful. - Please make sure that your compilation settings have asserts enabled. Calls to IM_ASSERT() are scattered in the code to help catch common issues. By default IM_ASSERT() calls the standard assert() function. To verify that your asserts are enabled, add the line `IM_ASSERT(false);` in your main() function. Your application should display an error message and abort. If your application report an error, it means that your asserts are disabled. Please make sure they are enabled. - When discussing issues related to rendering or inputs, please state the OS/back-end/renderer you are using. Please state if you are using a vanilla copy of the example back-ends (imgui_impl_XXX files), or a modified one, or if you built your own. @@ -27,10 +27,10 @@ You may use the Issue Tracker to submit bug reports, feature requests or suggest - When requesting a new feature, please describe the usage context (how you intend to use it, why you need it, etc.). **Some unfortunate words of warning** -- If you are or were involved in cheating schemes (e.g. DLL injection) for competitive online multi-player games, please don't post here. We won't answer and you will be blocked. We've had too many of you. Please stop. +- If you are or were involved in cheating schemes (e.g. DLL injection) for competitive online multi-player games, please don't post here. We won't answer and you will be blocked. We've had too many of you. - Due to frequent abuse of this service from aforementioned users, if your GitHub account is anonymous and was created five minutes ago please understand that your post will receive more scrutiny and incomplete questions may be dismissed. -If you have been using dear imgui for a while or have been using C/C++ for several years or have demonstrated good behavior here, it is ok to not fullfill every item to the letter. Those are guidelines and experienced users or members of the community will know what information are useful in a given context. +If you have been using dear imgui for a while or have been using C/C++ for several years or have demonstrated good behavior here, it is ok to not fullfill every item to the letter. Those are guidelines and experienced users or members of the community will know which information are useful in a given context. ## How to create an Pull Request - When adding a feature, please describe the usage context (how you intend to use it, why you need it, etc.). diff --git a/Source Code/Libs/imgui/.github/issue_template.md b/Source Code/Libs/imgui/.github/issue_template.md index 7aedc9594b..e434a545f0 100644 --- a/Source Code/Libs/imgui/.github/issue_template.md +++ b/Source Code/Libs/imgui/.github/issue_template.md @@ -1,13 +1,13 @@ -IF YOU ARE HAVING AN ISSUE COMPILING/LINKING/RUNNING/DISPLAYING/ADDING FONTS/WIRING INPUTS, please post on the "Getting Started" Discourse forum: +(Click "Preview" to turn any http URL into a clickable link) + +1. IF YOU ARE HAVING AN ISSUE COMPILING/LINKING/RUNNING/DISPLAYING/ADDING FONTS/WIRING INPUTS, please post on the "Getting Started" Discourse forum: https://discourse.dearimgui.org/c/getting-started -Otherwise, you may use this Issue Tracker to ask for help and submit bug reports, feature requests or suggestions. PLEASE CAREFULLY READ THIS DOCUMENT before submitting any issue: -https://github.com/ocornut/imgui/blob/master/.github/CONTRIBUTING.md -(Click "Preview" to turn the URL above into a clickable link) +2. You may use this Issue Tracker to ask for help and submit bug reports, feature requests or suggestions that don't fit in any category of (1). PLEASE CAREFULLY READ THE CONTRIBUTING DOCUMENT before submitting any issue: https://github.com/ocornut/imgui/blob/master/.github/CONTRIBUTING.md -PLEASE MAKE SURE that you have: read the FAQ in imgui.cpp; explored the contents of ShowDemoWindow() including the Examples menu; searched among Issues; used your IDE to search for keywords in all sources and text files; and read the CONTRIBUTING.md file linked above. +3. PLEASE MAKE SURE that you have: read the FAQ in imgui.cpp; explored the contents of ShowDemoWindow() including the Examples menu; searched among Issues; used your IDE to search for keywords in all sources and text files; and read the CONTRIBUTING.md file linked above. -(Delete everything above this section before submitting your issue.) +4. Delete points 1-4 and PLEASE FILL THE TEMPLATE BELOW before submitting your issue. ---- @@ -23,6 +23,8 @@ Compiler: XXX _(if the question is related to building)_ **My Issue/Question:** _(please provide context)_ +XXX + **Standalone, minimal, complete and verifiable example:** _(see CONTRIBUTING.md)_ ``` ImGui::Begin("Example Bug"); diff --git a/Source Code/Libs/imgui/addons/imgui_user.h b/Source Code/Libs/imgui/addons/imgui_user.h index 648a2ea988..b0f2a35cd3 100644 --- a/Source Code/Libs/imgui/addons/imgui_user.h +++ b/Source Code/Libs/imgui/addons/imgui_user.h @@ -36,6 +36,7 @@ # define NO_IMGUI_ADDONS #endif //NO_IMGUIADDONS + #ifdef NO_IMGUI_ADDONS // This definition turns all "normal" addons into "yes_addons" # if (!defined(YES_IMGUISTYLESERIALIZER) && !defined(NO_IMGUISTYLESERIALIZER)) # define NO_IMGUISTYLESERIALIZER @@ -55,12 +56,6 @@ # if (!defined(YES_IMGUIPANELMANAGER) && !defined(NO_IMGUIPANELMANAGER)) # define NO_IMGUIPANELMANAGER # endif //YES_IMGUIPANELMANAGER -# if (!defined(YES_IMGUITABWINDOW) && !defined(NO_IMGUITABWINDOW)) -# define NO_IMGUITABWINDOW -# endif //YES_IMGUITABWINDOW -# if (!defined(YES_IMGUIDOCK) && !defined(NO_IMGUIDOCK)) -# define NO_IMGUIDOCK -# endif //YES_IMGUIDOCK # if (!defined(YES_IMGUINODEGRAPHEDITOR) && !defined(NO_IMGUINODEGRAPHEDITOR)) # define NO_IMGUINODEGRAPHEDITOR # endif //YES_IMGUINODEGRAPHEDITOR @@ -130,9 +125,6 @@ inline void operator delete(void*, ImImplPlacementNewDummy, void*) {} #ifndef NO_IMGUIHELPER #include "./imguihelper/imguihelper.h" #endif //NO_IMGUIHELPER -#ifndef NO_IMGUITABWINDOW -#include "./imguitabwindow/imguitabwindow.h" -#endif //NO_IMGUITABWINDOW #ifdef YES_IMGUISOLOUD_ALL # undef YES_IMGUISOLOUD @@ -184,9 +176,6 @@ inline void operator delete(void*, ImImplPlacementNewDummy, void*) {} #ifndef NO_IMGUITOOLBAR #include "./imguitoolbar/imguitoolbar.h" #endif //NO_IMGUITOOLBAR -#ifndef NO_IMGUIPANELMANAGER -#include "./imguipanelmanager/imguipanelmanager.h" -#endif //NO_IMGUIPANELMANAGER #ifndef NO_IMGUIVARIOUSCONTROLS #include "./imguivariouscontrols/imguivariouscontrols.h" #endif //NO_IMGUIVARIOUSCONTROLS @@ -202,9 +191,6 @@ inline void operator delete(void*, ImImplPlacementNewDummy, void*) {} #ifndef NO_IMGUINODEGRAPHEDITOR #include "./imguinodegrapheditor/imguinodegrapheditor.h" #endif //NO_IMGUINODEGRAPHEDITOR -#ifndef NO_IMGUIDOCK -#include "./imguidock/imguidock.h" -#endif //NO_IMGUIDOCK #ifdef YES_IMGUIADDONS_ALL # ifndef NO_IMGUIPDFVIEWER diff --git a/Source Code/Libs/imgui/addons/imgui_user.inl b/Source Code/Libs/imgui/addons/imgui_user.inl index 54d1c4944e..f7603e9ec3 100644 --- a/Source Code/Libs/imgui/addons/imgui_user.inl +++ b/Source Code/Libs/imgui/addons/imgui_user.inl @@ -13,9 +13,6 @@ #ifndef NO_IMGUIHELPER #include "./imguihelper/imguihelper.cpp" #endif //NO_IMGUIHELPER -#ifndef NO_IMGUITABWINDOW -#include "./imguitabwindow/imguitabwindow.cpp" -#endif //NO_IMGUITABWINDOW #ifdef IMGUI_USE_AUTO_BINDING // defined in imgui_user.h # ifdef __EMSCRIPTEN__ @@ -63,9 +60,6 @@ #ifndef NO_IMGUITOOLBAR #include "./imguitoolbar/imguitoolbar.cpp" #endif //NO_IMGUITOOLBAR -#ifndef NO_IMGUIPANELMANAGER -#include "./imguipanelmanager/imguipanelmanager.cpp" -#endif //NO_IMGUIPANELMANAGER #ifndef NO_IMGUIVARIOUSCONTROLS #include "./imguivariouscontrols/imguivariouscontrols.cpp" #endif //NO_IMGUIVARIOUSCONTROLS @@ -84,9 +78,6 @@ #ifndef NO_IMGUINODEGRAPHEDITOR #include "./imguinodegrapheditor/imguinodegrapheditor.cpp" #endif //NO_IMGUINODEGRAPHEDITOR -#ifndef NO_IMGUIDOCK -#include "./imguidock/imguidock.cpp" -#endif //NO_IMGUIDOCK #ifdef YES_IMGUIBZ2 #include "./imguiyesaddons/imguibz2.cpp" diff --git a/Source Code/Libs/imgui/addons/imguibindings/imguibindings.cpp b/Source Code/Libs/imgui/addons/imguibindings/imguibindings.cpp index aef58f89b0..0d2dad1c51 100644 --- a/Source Code/Libs/imgui/addons/imguibindings/imguibindings.cpp +++ b/Source Code/Libs/imgui/addons/imguibindings/imguibindings.cpp @@ -594,19 +594,6 @@ void InitImGuiFontTexture(const ImImpl_InitParams* pOptionalInitParams) { # endif //IMGUIBINDINGS_DONT_CLEAR_INPUT_DATA_SOON //fprintf(stderr,"Loaded font texture\n"); - -// We overuse this method to load textures from other imgui addons -# ifndef NO_IMGUITABWINDOW - if (!ImGui::TabWindow::DockPanelIconTextureID) { - ImVector rgba_buffer;int w=0,h=0; - ImGui::TabWindow::GetDockPanelIconImageRGBA(rgba_buffer,&w,&h); - ImImpl_GenerateOrUpdateTexture(ImGui::TabWindow::DockPanelIconTextureID,w,h,4,&rgba_buffer[0]); - } -# endif //NO_IMGUITABWINDOW -# ifndef NO_IMGUIDOCK - if (!gDockContent) gDockContent = ImGui::CreateDockContext(); - ImGui::SetCurrentDockContext(gDockContent); -# endif //NO_IMGUIDOCK } void DestroyImGuiFontTexture() { @@ -625,15 +612,6 @@ void DestroyImGuiFontTexture() { # ifdef YES_IMGUIIMAGEEDITOR ImGui::ImageEditor::Destroy(); # endif //YES_IMGUIIMAGEEDITOR -# ifndef NO_IMGUITABWINDOW - if (ImGui::TabWindow::DockPanelIconTextureID) { - ImImpl_FreeTexture(ImGui::TabWindow::DockPanelIconTextureID); - ImGui::TabWindow::DockPanelIconTextureID = NULL; - } -# endif //NO_IMGUITABWINDOW -# ifndef NO_IMGUIDOCK - if (gDockContent) {ImGui::DestroyDockContext(gDockContent);gDockContent=NULL;} -# endif //NO_IMGUIDOCK } #ifndef _WIN32 diff --git a/Source Code/Libs/imgui/addons/imguidock/imguidock.cpp b/Source Code/Libs/imgui/addons/imguidock/imguidock.cpp deleted file mode 100644 index 68ba4c2191..0000000000 --- a/Source Code/Libs/imgui/addons/imguidock/imguidock.cpp +++ /dev/null @@ -1,1484 +0,0 @@ -// based on https://github.com/nem0/LumixEngine/blob/master/external/imgui/imgui_dock.h -// Lumix Engine Dock. From: https://github.com/nem0/LumixEngine/blob/master/src/editor/imgui/imgui_dock.h -/* -The MIT License (MIT) - -Copyright (c) 2013-2016 Mikulas Florek - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ -// modified from https://bitbucket.org/duangle/liminal/src/tip/src/liminal/imgui_dock.h - -//- Common Code For All Addons needed just to ease inclusion as separate files in user code ---------------------- -#include -#undef IMGUI_DEFINE_PLACEMENT_NEW -#define IMGUI_DEFINE_PLACEMENT_NEW -#undef IMGUI_DEFINE_MATH_OPERATORS -#define IMGUI_DEFINE_MATH_OPERATORS -#include -//----------------------------------------------------------------------------------------------------------------- - -#include "imguidock.h" - -bool gImGuiDockReuseTabWindowTextureIfAvailable = true; - -namespace ImGui { - -struct DockContext -{ - enum EndAction_ - { - EndAction_None, - EndAction_Panel, - EndAction_End, - EndAction_EndChild - }; - - - enum Status_ - { - Status_Docked, - Status_Float, - Status_Dragged - }; - - - struct Dock - { - Dock() - : label(NULL) - , id(0) - , next_tab(NULL) - , prev_tab(NULL) - , parent(NULL) - , active(true) - , pos(0, 0) - , size(-1, -1) - , floatmode_size(0, 0) - , status(Status_Float) - , last_frame(0) - , invalid_frames(0) - , opened(false) - , first(false) - - { - location[0] = 0; - children[0] = children[1] = NULL; - } - - - ~Dock() {MemFree(label); } - - - ImVec2 getMinSize() const - { - if (!children[0]) return ImVec2(16, 16 + GetTextLineHeightWithSpacing()); - - ImVec2 s0 = children[0]->getMinSize(); - ImVec2 s1 = children[1]->getMinSize(); - return isHorizontal() ? ImVec2(s0.x + s1.x, ImMax(s0.y, s1.y)) - : ImVec2(ImMax(s0.x, s1.x), s0.y + s1.y); - } - - - bool isHorizontal() const { return children[0]->pos.x < children[1]->pos.x; } - - - void setParent(Dock* dock) - { - parent = dock; - for (Dock* tmp = prev_tab; tmp; tmp = tmp->prev_tab) tmp->parent = dock; - for (Dock* tmp = next_tab; tmp; tmp = tmp->next_tab) tmp->parent = dock; - } - - Dock& getRoot() - { - Dock *dock = this; - while (dock->parent) - dock = dock->parent; - return *dock; - } - - - Dock& getSibling() - { - IM_ASSERT(parent); - if (parent->children[0] == &getFirstTab()) return *parent->children[1]; - return *parent->children[0]; - } - - - Dock& getFirstTab() - { - Dock* tmp = this; - while (tmp->prev_tab) tmp = tmp->prev_tab; - return *tmp; - } - - - void setActive() - { - active = true; - for (Dock* tmp = prev_tab; tmp; tmp = tmp->prev_tab) tmp->active = false; - for (Dock* tmp = next_tab; tmp; tmp = tmp->next_tab) tmp->active = false; - } - - - bool hasChildren() const { return children[0] != NULL; } - - - void setChildrenPosSize(const ImVec2& _pos, const ImVec2& _size) - { - ImVec2 s = children[0]->size; - if (isHorizontal()) - { - s.y = _size.y; - s.x = (float)int( - _size.x * children[0]->size.x / (children[0]->size.x + children[1]->size.x)); - if (s.x < children[0]->getMinSize().x) - { - s.x = children[0]->getMinSize().x; - } - else if (_size.x - s.x < children[1]->getMinSize().x) - { - s.x = _size.x - children[1]->getMinSize().x; - } - children[0]->setPosSize(_pos, s); - - s.x = _size.x - children[0]->size.x; - ImVec2 p = _pos; - p.x += children[0]->size.x; - children[1]->setPosSize(p, s); - } - else - { - s.x = _size.x; - s.y = (float)int( - _size.y * children[0]->size.y / (children[0]->size.y + children[1]->size.y)); - if (s.y < children[0]->getMinSize().y) - { - s.y = children[0]->getMinSize().y; - } - else if (_size.y - s.y < children[1]->getMinSize().y) - { - s.y = _size.y - children[1]->getMinSize().y; - } - children[0]->setPosSize(_pos, s); - - s.y = _size.y - children[0]->size.y; - ImVec2 p = _pos; - p.y += children[0]->size.y; - children[1]->setPosSize(p, s); - } - } - - - void setPosSize(const ImVec2& _pos, const ImVec2& _size) - { - size = _size; - pos = _pos; - for (Dock* tmp = prev_tab; tmp; tmp = tmp->prev_tab) - { - tmp->size = _size; - tmp->pos = _pos; - } - for (Dock* tmp = next_tab; tmp; tmp = tmp->next_tab) - { - tmp->size = _size; - tmp->pos = _pos; - } - - if (!hasChildren()) return; - setChildrenPosSize(_pos, _size); - } - - - char* label; - ImU32 id; - Dock* next_tab; - Dock* prev_tab; - Dock* children[2]; - Dock* parent; - bool active; - ImVec2 pos; - ImVec2 size; - ImVec2 floatmode_size; - Status_ status; - int last_frame; - int invalid_frames; - char location[16]; - bool opened; - bool first; - }; - - - ImVector m_docks; - ImVec2 m_drag_offset; - Dock* m_current; - Dock *m_next_parent; - int m_last_frame; - EndAction_ m_end_action; - bool m_is_begin_open; - ImVec2 m_workspace_pos; - ImVec2 m_workspace_size; - ImGuiDockSlot m_next_dock_slot; - bool m_is_first_call; - - DockContext() - : m_current(NULL) - , m_next_parent(NULL) - , m_last_frame(0) - , m_is_begin_open(false) - , m_next_dock_slot(ImGuiDockSlot_Tab) - , m_is_first_call(true) - { - } - - - ~DockContext() { - Shutdown(); - } - - void Shutdown() - { - for (int i = 0; i < m_docks.size(); ++i) - { - m_docks[i]->~Dock(); - MemFree(m_docks[i]); - } - m_docks.clear(); - } - - Dock& getDock(const char* label, bool opened, const ImVec2& default_size, const ImVec2& default_pos) - { - ImU32 id = ImHash(label, 0); - for (int i = 0,iSz = m_docks.size(); i < iSz; ++i) - { - if (m_docks[i]->id == id) return *m_docks[i]; - } - - Dock* new_dock = (Dock*)MemAlloc(sizeof(Dock)); - IM_PLACEMENT_NEW(new_dock) Dock(); - m_docks.push_back(new_dock); - new_dock->label = ImStrdup(label); - IM_ASSERT(new_dock->label); - new_dock->id = id; - new_dock->setActive(); - new_dock->status = (m_docks.size() == 1)?Status_Docked:Status_Float; - new_dock->pos = default_pos; - //new_dock->size = GetIO().DisplaySize; - new_dock->size.x = default_size.x < 0 ? GetIO().DisplaySize.x : default_size.x; - new_dock->size.y = default_size.y < 0 ? GetIO().DisplaySize.y : default_size.y; - new_dock->floatmode_size.x = default_size.x; - new_dock->floatmode_size.y = default_size.y; - new_dock->opened = opened; - new_dock->first = true; - new_dock->last_frame = 0; - new_dock->invalid_frames = 0; - new_dock->location[0] = 0; - return *new_dock; - } - - - void putInBackground() - { - ImGuiWindow* win = GetCurrentWindow(); - ImGuiContext& g = *GImGui; - if (g.Windows[0] == win) return; - - for (int i = 0; i < g.Windows.Size; i++) - { - if (g.Windows[i] == win) - { - for (int j = i - 1; j >= 0; --j) - { - g.Windows[j + 1] = g.Windows[j]; - } - g.Windows[0] = win; - break; - } - } - } - - - void splits() - { - if (GetFrameCount() == m_last_frame) return; - m_last_frame = GetFrameCount(); - - putInBackground(); - - for (int i = 0; i < m_docks.size(); ++i) { - Dock& dock = *m_docks[i]; - if (!dock.parent && (dock.status == Status_Docked)) { - dock.setPosSize(m_workspace_pos, m_workspace_size); - } - } - - ImU32 color = GetColorU32(ImGuiCol_Button); - ImU32 color_hovered = GetColorU32(ImGuiCol_ButtonHovered); - ImDrawList* draw_list = GetWindowDrawList(); - ImGuiIO& io = GetIO(); - for (int i = 0; i < m_docks.size(); ++i) - { - Dock& dock = *m_docks[i]; - if (!dock.hasChildren()) continue; - - PushID(i); - if (!IsMouseDown(0)) dock.status = Status_Docked; - - ImVec2 pos0 = dock.children[0]->pos; - ImVec2 pos1 = dock.children[1]->pos; - ImVec2 size0 = dock.children[0]->size; - ImVec2 size1 = dock.children[1]->size; - - ImGuiMouseCursor cursor; - - ImVec2 dsize(0, 0); - ImVec2 min_size0 = dock.children[0]->getMinSize(); - ImVec2 min_size1 = dock.children[1]->getMinSize(); - if (dock.isHorizontal()) - { - cursor = ImGuiMouseCursor_ResizeEW; - SetCursorScreenPos(ImVec2(dock.pos.x + size0.x, dock.pos.y)); - InvisibleButton("split", ImVec2(3, dock.size.y)); - if (dock.status == Status_Dragged) dsize.x = io.MouseDelta.x; - dsize.x = -ImMin(-dsize.x, dock.children[0]->size.x - min_size0.x); - dsize.x = ImMin(dsize.x, dock.children[1]->size.x - min_size1.x); - size0 += dsize; - size1 -= dsize; - pos0 = dock.pos; - pos1.x = pos0.x + size0.x; - pos1.y = dock.pos.y; - size0.y = size1.y = dock.size.y; - size1.x = ImMax(min_size1.x, dock.size.x - size0.x); - size0.x = ImMax(min_size0.x, dock.size.x - size1.x); - } - else - { - cursor = ImGuiMouseCursor_ResizeNS; - SetCursorScreenPos(ImVec2(dock.pos.x, dock.pos.y + size0.y)); - InvisibleButton("split", ImVec2(dock.size.x, 3)); - if (dock.status == Status_Dragged) dsize.y = io.MouseDelta.y; - dsize.y = -ImMin(-dsize.y, dock.children[0]->size.y - min_size0.y); - dsize.y = ImMin(dsize.y, dock.children[1]->size.y - min_size1.y); - size0 += dsize; - size1 -= dsize; - pos0 = dock.pos; - pos1.x = dock.pos.x; - pos1.y = pos0.y + size0.y; - size0.x = size1.x = dock.size.x; - size1.y = ImMax(min_size1.y, dock.size.y - size0.y); - size0.y = ImMax(min_size0.y, dock.size.y - size1.y); - } - dock.children[0]->setPosSize(pos0, size0); - dock.children[1]->setPosSize(pos1, size1); - - if (IsItemHovered()) { - SetMouseCursor(cursor); - SetHoveredID(GImGui->CurrentWindow->DC.LastItemId); - } - - if (IsItemHovered() && IsMouseClicked(0)) - { - dock.status = Status_Dragged; - } - - draw_list->AddRectFilled( - GetItemRectMin(), GetItemRectMax(), IsItemHovered() ? color_hovered : color); - PopID(); - } - } - - - void checkNonexistent() - { - int frame_limit = ImMax(0, ImGui::GetFrameCount() - 2); - for (int i = 0; i < m_docks.size(); ++i) - { - Dock *dock = m_docks[i]; - if (dock->hasChildren()) continue; - if (dock->status == Status_Float) continue; - if (dock->last_frame < frame_limit) - { - ++dock->invalid_frames; - if (dock->invalid_frames > 2) - { - doUndock(*dock); - dock->status = Status_Float; - } - return; - } - dock->invalid_frames = 0; - } - } - - - Dock* getDockAt(const ImVec2& /*pos*/) const - { - for (int i = 0; i < m_docks.size(); ++i) - { - Dock& dock = *m_docks[i]; - if (dock.hasChildren()) continue; - if (dock.status != Status_Docked) continue; - if (IsMouseHoveringRect(dock.pos, dock.pos + dock.size, false)) - { - return &dock; - } - } - - return NULL; - } - - - static ImRect getDockedRect(const ImRect& rect, ImGuiDockSlot dock_slot) - { - ImVec2 size = rect.GetSize(); - switch (dock_slot) - { - default: return rect; - case ImGuiDockSlot_Top: return ImRect(rect.Min, rect.Min + ImVec2(size.x, size.y * 0.5f)); - case ImGuiDockSlot_Right: return ImRect(rect.Min + ImVec2(size.x * 0.5f, 0), rect.Max); - case ImGuiDockSlot_Bottom: return ImRect(rect.Min + ImVec2(0, size.y * 0.5f), rect.Max); - case ImGuiDockSlot_Left: return ImRect(rect.Min, rect.Min + ImVec2(size.x * 0.5f, rect.GetSize().y)); - } - } - - - static ImRect getSlotRect(ImRect parent_rect, ImGuiDockSlot dock_slot) - { - ImVec2 size = parent_rect.Max - parent_rect.Min; - ImVec2 center = parent_rect.Min + size * 0.5f; - switch (dock_slot) - { - default: return ImRect(center - ImVec2(20, 20), center + ImVec2(20, 20)); - case ImGuiDockSlot_Top: return ImRect(center + ImVec2(-20, -50), center + ImVec2(20, -30)); - case ImGuiDockSlot_Right: return ImRect(center + ImVec2(30, -20), center + ImVec2(50, 20)); - case ImGuiDockSlot_Bottom: return ImRect(center + ImVec2(-20, +30), center + ImVec2(20, 50)); - case ImGuiDockSlot_Left: return ImRect(center + ImVec2(-50, -20), center + ImVec2(-30, 20)); - } - } - - - static ImRect getSlotRectOnBorder(ImRect parent_rect, ImGuiDockSlot dock_slot) - { - ImVec2 size = parent_rect.Max - parent_rect.Min; - ImVec2 center = parent_rect.Min + size * 0.5f; - switch (dock_slot) - { - case ImGuiDockSlot_Top: - return ImRect(ImVec2(center.x - 20, parent_rect.Min.y + 10), - ImVec2(center.x + 20, parent_rect.Min.y + 30)); - case ImGuiDockSlot_Left: - return ImRect(ImVec2(parent_rect.Min.x + 10, center.y - 20), - ImVec2(parent_rect.Min.x + 30, center.y + 20)); - case ImGuiDockSlot_Bottom: - return ImRect(ImVec2(center.x - 20, parent_rect.Max.y - 30), - ImVec2(center.x + 20, parent_rect.Max.y - 10)); - case ImGuiDockSlot_Right: - return ImRect(ImVec2(parent_rect.Max.x - 30, center.y - 20), - ImVec2(parent_rect.Max.x - 10, center.y + 20)); - default: IM_ASSERT(false); - } - IM_ASSERT(false); - return ImRect(); - } - - - Dock* getRootDock() - { - for (int i = 0; i < m_docks.size(); ++i) - { - if (!m_docks[i]->parent && - (m_docks[i]->status == Status_Docked || m_docks[i]->children[0])) - { - return m_docks[i]; - } - } - return NULL; - } - - - bool dockSlots(Dock& dock, Dock* dest_dock, const ImRect& rect, bool on_border) - { - ImDrawList* canvas = GetWindowDrawList(); - ImU32 color = GetColorU32(ImGuiCol_Button); // Color of all the available "spots" - ImU32 color_hovered = GetColorU32(ImGuiCol_ButtonHovered); // Color of the hovered "spot" - ImU32 docked_rect_color = color; - ImVec2 mouse_pos = GetIO().MousePos; - ImTextureID texture = NULL; - if (gImGuiDockReuseTabWindowTextureIfAvailable) { -# ifdef IMGUITABWINDOW_H_ - texture = ImGui::TabWindow::DockPanelIconTextureID; // Nope. It doesn't look OK. - if (texture) { - color = 0x00FFFFFF | 0x90000000; - color_hovered = (color_hovered & 0x00FFFFFF) | 0x90000000; - docked_rect_color = (docked_rect_color & 0x00FFFFFF) | 0x80000000; - - // The whole part of channel split has been removed (because it stopped working) - //canvas->ChannelsSplit(2); // Solves overlay order. But won't it break something else ? - } -# endif ////IMGUITABWINDOW_H_ - } - for (int i = 0; i < (on_border ? 4 : 5); ++i) - { - const ImGuiDockSlot iSlot = (ImGuiDockSlot)i; - ImRect r = - on_border ? getSlotRectOnBorder(rect, iSlot) : getSlotRect(rect, iSlot); - bool hovered = r.Contains(mouse_pos); - ImU32 color_to_use = hovered ? color_hovered : color; - if (!texture) canvas->AddRectFilled(r.Min, r.Max, color_to_use); - else { -# ifdef IMGUITABWINDOW_H_ - //canvas->ChannelsSetCurrent(0); // Background - switch (iSlot) { - case ImGuiDockSlot_Left: - case ImGuiDockSlot_Right: - case ImGuiDockSlot_Top: - case ImGuiDockSlot_Bottom: - { - const int uvIndex = (i==0)?3:(i==2)?0:(i==3)?2:i; - ImVec2 uv0(0.75f,(float)uvIndex*0.25f),uv1(uv0.x+0.25f,uv0.y+0.25f); - canvas->AddImage(texture,r.Min, r.Max,uv0,uv1,color_to_use); - } - break; - case ImGuiDockSlot_Tab: - canvas->AddImage(texture,r.Min, r.Max,ImVec2(0.22916f,0.22916f),ImVec2(0.45834f,0.45834f),color_to_use); - break; - default: - canvas->AddRectFilled(r.Min, r.Max, color_to_use); - break; - } - //canvas->ChannelsSetCurrent(1); // Foreground -# endif ////IMGUITABWINDOW_H_ - } - if (!hovered) continue; - - if (!IsMouseDown(0)) - { -# ifdef IMGUITABWINDOW_H_ - //if (texture) canvas->ChannelsMerge(); -# endif ////IMGUITABWINDOW_H_ - doDock(dock, dest_dock ? dest_dock : getRootDock(), iSlot); - return true; - } - ImRect docked_rect = getDockedRect(rect, iSlot); - canvas->AddRectFilled(docked_rect.Min, docked_rect.Max, docked_rect_color); - } -# ifdef IMGUITABWINDOW_H_ - //if (texture) canvas->ChannelsMerge(); -# endif ////IMGUITABWINDOW_H_ - return false; - } - - - void handleDrag(Dock& dock) - { - Dock* dest_dock = getDockAt(GetIO().MousePos); - - SetNextWindowBgAlpha(0.0f); - Begin("##Overlay", - NULL, - ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | - ImGuiWindowFlags_AlwaysAutoResize); - ImDrawList* canvas = GetWindowDrawList(); - - canvas->PushClipRectFullScreen(); - - ImU32 docked_color = GetColorU32(ImGuiCol_FrameBg); - docked_color = (docked_color & 0x00ffFFFF) | 0x80000000; - dock.pos = GetIO().MousePos - m_drag_offset; - if (dest_dock) - { - if (dockSlots(dock, - dest_dock, - ImRect(dest_dock->pos, dest_dock->pos + dest_dock->size), - false)) - { - canvas->PopClipRect(); - End(); - return; - } - } - if (dockSlots(dock, NULL, ImRect(m_workspace_pos, m_workspace_pos + m_workspace_size), true)) - { - canvas->PopClipRect(); - End(); - return; - } - canvas->AddRectFilled(dock.pos, dock.pos + dock.size, docked_color); - canvas->PopClipRect(); - - if (!IsMouseDown(0)) - { - dock.status = Status_Float; - dock.location[0] = 0; - dock.setActive(); - } - - End(); - } - - - void fillLocation(Dock& dock) - { - if (dock.status == Status_Float) return; - char* c = dock.location; - Dock* tmp = &dock; - while (tmp->parent) - { - *c = getLocationCode(tmp); - tmp = tmp->parent; - ++c; - } - *c = 0; - } - - - void doUndock(Dock& dock) - { - if (dock.prev_tab) - dock.prev_tab->setActive(); - else if (dock.next_tab) - dock.next_tab->setActive(); - else - dock.active = false; - Dock* container = dock.parent; - - if (container) - { - Dock& sibling = dock.getSibling(); - if (container->children[0] == &dock) - { - container->children[0] = dock.next_tab; - } - else if (container->children[1] == &dock) - { - container->children[1] = dock.next_tab; - } - - bool remove_container = !container->children[0] || !container->children[1]; - if (remove_container) - { - if (container->parent) - { - Dock*& child = container->parent->children[0] == container - ? container->parent->children[0] - : container->parent->children[1]; - child = &sibling; - child->setPosSize(container->pos, container->size); - child->setParent(container->parent); - } - else - { - if (container->children[0]) - { - container->children[0]->setParent(NULL); - container->children[0]->setPosSize(container->pos, container->size); - } - if (container->children[1]) - { - container->children[1]->setParent(NULL); - container->children[1]->setPosSize(container->pos, container->size); - } - } - for (int i = 0; i < m_docks.size(); ++i) - { - if (m_docks[i] == container) - { - m_docks.erase(m_docks.begin() + i); - break; - } - } - if (container == m_next_parent) - m_next_parent = NULL; - container->~Dock(); - MemFree(container); - } - } - - if (dock.prev_tab) dock.prev_tab->next_tab = dock.next_tab; - if (dock.next_tab) dock.next_tab->prev_tab = dock.prev_tab; - dock.parent = NULL; - dock.prev_tab = dock.next_tab = NULL; - - if (m_next_parent == &dock) - m_next_parent = NULL; - } - - - void drawTabbarListButton(Dock& dock) - { - if (!dock.next_tab) return; - - ImDrawList* draw_list = GetWindowDrawList(); - if (InvisibleButton("list", ImVec2(16, 16))) - { - OpenPopup("tab_list_popup"); - } - if (BeginPopup("tab_list_popup")) - { - Dock* tmp = &dock; - while (tmp) - { - bool dummy = false; - if (Selectable(tmp->label, &dummy)) - { - tmp->setActive(); - m_next_parent = tmp; - } - tmp = tmp->next_tab; - } - EndPopup(); - } - - bool hovered = IsItemHovered(); - ImVec2 min = GetItemRectMin(); - ImVec2 max = GetItemRectMax(); - ImVec2 center = (min + max) * 0.5f; - ImU32 text_color = GetColorU32(ImGuiCol_Text); - ImU32 color_active = GetColorU32(ImGuiCol_FrameBgActive); - draw_list->AddRectFilled(ImVec2(center.x - 4, min.y + 3), - ImVec2(center.x + 4, min.y + 5), - hovered ? color_active : text_color); - draw_list->AddTriangleFilled(ImVec2(center.x - 4, min.y + 7), - ImVec2(center.x + 4, min.y + 7), - ImVec2(center.x, min.y + 12), - hovered ? color_active : text_color); - } - - - bool tabbar(Dock& dock, bool close_button) - { - float tabbar_height = 2 * GetTextLineHeightWithSpacing(); - ImVec2 size(dock.size.x, tabbar_height); - bool tab_closed = false; - - SetCursorScreenPos(dock.pos); - char tmp[20]; - ImFormatString(tmp, IM_ARRAYSIZE(tmp), "tabs%d", (int)dock.id); - if (BeginChild(tmp, size, true)) - { - Dock* dock_tab = &dock; - - ImDrawList* draw_list = GetWindowDrawList(); - ImU32 color = GetColorU32(ImGuiCol_FrameBg); - ImU32 color_active = GetColorU32(ImGuiCol_FrameBgActive); - ImU32 color_hovered = GetColorU32(ImGuiCol_FrameBgHovered); - ImU32 button_hovered = GetColorU32(ImGuiCol_ButtonHovered); - ImU32 text_color = GetColorU32(ImGuiCol_Text); - float line_height = GetTextLineHeightWithSpacing(); - float tab_base; - - drawTabbarListButton(dock); - - while (dock_tab) - { - SameLine(0, 15); - - const char* text_end = FindRenderedTextEnd(dock_tab->label); - ImVec2 size(CalcTextSize(dock_tab->label, text_end).x, line_height); - if (InvisibleButton(dock_tab->label, size)) - { - dock_tab->setActive(); - m_next_parent = dock_tab; - } - - if (IsItemActive() && IsMouseDragging()) - { - m_drag_offset = GetMousePos() - dock_tab->pos; - doUndock(*dock_tab); - dock_tab->status = Status_Dragged; - } - - if (dock_tab->active && close_button) size.x += 16 + GetStyle().ItemSpacing.x; - - bool hovered = IsItemHovered(); - ImVec2 pos = GetItemRectMin(); - tab_base = pos.y; - draw_list->PathClear(); - draw_list->PathLineTo(pos + ImVec2(-15, size.y)); - draw_list->PathBezierCurveTo( - pos + ImVec2(-10, size.y), pos + ImVec2(-5, 0), pos + ImVec2(0, 0), 10); - draw_list->PathLineTo(pos + ImVec2(size.x, 0)); - draw_list->PathBezierCurveTo(pos + ImVec2(size.x + 5, 0), - pos + ImVec2(size.x + 10, size.y), - pos + ImVec2(size.x + 15, size.y), - 10); - draw_list->PathFillConvex(hovered ? color_hovered : (dock_tab->active ? color_active : color)); - draw_list->AddText(pos + ImVec2(0, 1), text_color, dock_tab->label, text_end); - - if (dock_tab->active && close_button) { - size.x += 16 + GetStyle().ItemSpacing.x; - SameLine(); - tab_closed = InvisibleButton("close", ImVec2(16, 16)); - ImVec2 center = (GetItemRectMin() + GetItemRectMax()) * 0.5f; - if (IsItemHovered()) { - draw_list->AddRectFilled(center + ImVec2(-6.0f, -6.0f), center + ImVec2(7.0f, 7.0f), button_hovered); - } - draw_list->AddLine( - center + ImVec2(-3.5f, -3.5f), center + ImVec2(3.5f, 3.5f), text_color); - draw_list->AddLine( - center + ImVec2(3.5f, -3.5f), center + ImVec2(-3.5f, 3.5f), text_color); - } - - dock_tab = dock_tab->next_tab; - } - ImVec2 cp(dock.pos.x, tab_base + line_height); - draw_list->AddLine(cp, cp + ImVec2(dock.size.x, 0), color); - } - EndChild(); - return tab_closed; - } - - - static void setDockPosSize(Dock& dest, Dock& dock, ImGuiDockSlot dock_slot, Dock& container) - { - IM_ASSERT(!dock.prev_tab && !dock.next_tab && !dock.children[0] && !dock.children[1]); - - dest.pos = container.pos; - dest.size = container.size; - dock.pos = container.pos; - dock.size = container.size; - - switch (dock_slot) - { - case ImGuiDockSlot_Bottom: - dest.size.y *= 0.5f; - dock.size.y *= 0.5f; - dock.pos.y += dest.size.y; - break; - case ImGuiDockSlot_Right: - dest.size.x *= 0.5f; - dock.size.x *= 0.5f; - dock.pos.x += dest.size.x; - break; - case ImGuiDockSlot_Left: - dest.size.x *= 0.5f; - dock.size.x *= 0.5f; - dest.pos.x += dock.size.x; - break; - case ImGuiDockSlot_Top: - dest.size.y *= 0.5f; - dock.size.y *= 0.5f; - dest.pos.y += dock.size.y; - break; - default: IM_ASSERT(false); break; - } - dest.setPosSize(dest.pos, dest.size); - - if (container.children[1]->pos.x < container.children[0]->pos.x || - container.children[1]->pos.y < container.children[0]->pos.y) - { - Dock* tmp = container.children[0]; - container.children[0] = container.children[1]; - container.children[1] = tmp; - } - } - - Dock *findNonContainer(Dock *dock) - { - if (dock->hasChildren()) - { - Dock *dock2; - - dock2 = findNonContainer(dock->children[0]); - if (!dock2) - dock2 = findNonContainer(dock->children[1]); - - return dock2; - } - else - return dock; - } - - void doDock(Dock& dock, Dock* dest, ImGuiDockSlot dock_slot) - { - IM_ASSERT(!dock.parent); - IM_ASSERT(!dock.prev_tab); - IM_ASSERT(!dock.next_tab); - - if (!dest) - { - dock.status = Status_Docked; - dock.setPosSize(m_workspace_pos, m_workspace_size); - } - else if (dock_slot == ImGuiDockSlot_Tab) - { - dest = findNonContainer(dest); - IM_ASSERT(dest); - - Dock *tmp = dest; - while (tmp->next_tab) - { - tmp = tmp->next_tab; - } - - tmp->next_tab = &dock; - dock.prev_tab = tmp; - dock.size = tmp->size; - dock.pos = tmp->pos; - dock.parent = dest->parent; - dock.status = Status_Docked; - } - else if (dock_slot == ImGuiDockSlot_None) - { - dock.status = Status_Float; - } - else - { - Dock* container = (Dock*)MemAlloc(sizeof(Dock)); - IM_PLACEMENT_NEW(container) Dock(); - m_docks.push_back(container); - container->children[0] = &dest->getFirstTab(); - container->children[1] = &dock; - container->next_tab = NULL; - container->prev_tab = NULL; - container->parent = dest->parent; - container->size = dest->size; - container->pos = dest->pos; - container->status = Status_Docked; - container->label = ImStrdup(""); - - if (!dest->parent) - { - } - else if (&dest->getFirstTab() == dest->parent->children[0]) - { - dest->parent->children[0] = container; - } - else - { - dest->parent->children[1] = container; - } - - dest->setParent(container); - dock.parent = container; - dock.status = Status_Docked; - - setDockPosSize(*dest, dock, dock_slot, *container); - } - dock.setActive(); - } - - - void rootDock(const ImVec2& pos, const ImVec2& size) - { - Dock* root = getRootDock(); - if (!root) return; - - ImVec2 min_size = root->getMinSize(); - ImVec2 requested_size = size; - root->setPosSize(pos, ImMax(min_size, requested_size)); - - // New December 2017 [https://github.com/nem0/LumixEngine/pull/1185] - // Further modified here: https://github.com/nem0/LumixEngine/commit/dfa6598a386224bb1d25e08f4c229c62d59351ff#diff-40effb02fb4dfcd620fa88d16875bb14 - if (!m_is_first_call) { - for (ImVector::const_iterator it = m_docks.begin(); it != m_docks.end();) { - Dock* dock = *it; - if (!dock->hasChildren() && dock != root && (ImGui::GetFrameCount() - dock->last_frame) > 1) { - doUndock(*dock); - dock->~Dock(); - MemFree(dock); - it = m_docks.erase(it); - } - else ++it; - } - } - m_is_first_call = false; - - } - - - void setDockActive() - { - IM_ASSERT(m_current); - if (m_current) m_current->setActive(); - } - - - static ImGuiDockSlot getSlotFromLocationCode(char code) - { - switch (code) - { - case '1': return ImGuiDockSlot_Left; - case '2': return ImGuiDockSlot_Top; - case '3': return ImGuiDockSlot_Bottom; - default: return ImGuiDockSlot_Right; - } - } - - - static char getLocationCode(Dock* dock) - { - if (!dock) return '0'; - - if (dock->parent->isHorizontal()) - { - if (dock->pos.x < dock->parent->children[0]->pos.x) return '1'; - if (dock->pos.x < dock->parent->children[1]->pos.x) return '1'; - return '0'; - } - else - { - if (dock->pos.y < dock->parent->children[0]->pos.y) return '2'; - if (dock->pos.y < dock->parent->children[1]->pos.y) return '2'; - return '3'; - } - } - - - void tryDockToStoredLocation(Dock& dock) - { - if (dock.status == Status_Docked) return; - if (dock.location[0] == 0) return; - - Dock* tmp = getRootDock(); - if (!tmp) return; - - Dock* prev = NULL; - char* c = dock.location + strlen(dock.location) - 1; - while (c >= dock.location && tmp) - { - prev = tmp; - tmp = *c == getLocationCode(tmp->children[0]) ? tmp->children[0] : tmp->children[1]; - if(tmp) --c; - } - if (tmp && tmp->children[0]) tmp = tmp->parent; - doDock(dock, tmp ? tmp : prev, tmp && !tmp->children[0] ? ImGuiDockSlot_Tab : getSlotFromLocationCode(*c)); - } - - - void cleanDocks() { - restart: - for (int i = 0, c = m_docks.size(); i < c; ++i) - { - Dock& dock = *m_docks[i]; - if (dock.last_frame == 0 && dock.status != Status_Float && !dock.children[0]) - { - fillLocation(*m_docks[i]); - doUndock(*m_docks[i]); - m_docks[i]->status = Status_Float; - goto restart; - } - } - } - - bool begin(const char* label, bool* opened, ImGuiWindowFlags extra_flags, const ImVec2& default_size, const ImVec2& default_pos) - { - IM_ASSERT(!m_is_begin_open); - m_is_begin_open = true; - Dock& dock = getDock(label, !opened || *opened, default_size, default_pos); - if (dock.last_frame != 0 && m_last_frame != ImGui::GetFrameCount()) - { - cleanDocks(); - } - dock.last_frame = ImGui::GetFrameCount(); - ImGuiDockSlot next_slot = m_next_dock_slot; - m_next_dock_slot = ImGuiDockSlot_Tab; - if (!dock.opened && (!opened || *opened)) tryDockToStoredLocation(dock); - if (strcmp(dock.label, label) != 0) - { - MemFree(dock.label); - dock.label = ImStrdup(label); - } - - m_end_action = EndAction_None; - - bool prev_opened = dock.opened; - bool first = dock.first; - if (dock.first && opened) *opened = dock.opened; - dock.first = false; - if (opened && !*opened) - { - if (dock.status != Status_Float) - { - fillLocation(dock); - doUndock(dock); - dock.status = Status_Float; - } - dock.opened = false; - return false; - } - dock.opened = true; - - checkNonexistent(); - - if (first || (prev_opened != dock.opened)) { - Dock* root = m_next_parent ? m_next_parent : getRootDock(); - if (root && (&dock != root) && !dock.parent) { - doDock(dock, root, next_slot); - } - m_next_parent = &dock; - } - - m_current = &dock; - if (dock.status == Status_Dragged) handleDrag(dock); - - bool is_float = dock.status == Status_Float; - - if (is_float) - { - SetNextWindowPos(dock.pos); - const ImVec2 old_size(dock.floatmode_size.x>0 ? dock.floatmode_size.x : dock.size.x, - dock.floatmode_size.y>0 ? dock.floatmode_size.y : dock.size.y); - SetNextWindowSize(old_size); - dock.size = old_size; - bool ret = Begin(label, - opened, - ImGuiWindowFlags_NoCollapse | extra_flags); - m_end_action = EndAction_End; - dock.pos = GetWindowPos(); - dock.size = GetWindowSize(); - if (dock.size.x!=old_size.x && dock.floatmode_size.x>=0) dock.floatmode_size.x = dock.size.x; - if (dock.size.y!=old_size.y && dock.floatmode_size.y>=0) dock.floatmode_size.y = dock.size.y; - - // Dbg (to remove) - //if (ImGui::IsWindowHovered()) ImGui::SetTooltip("dock.size:\t(%1.f,%1.f)\\ndock.floatmode_size\t(%1.f,%1.f)\n",dock.size.x,dock.size.y,dock.floatmode_size.x,dock.floatmode_size.y); - - ImGuiContext& g = *GImGui; - - if (g.ActiveId == GetCurrentWindow()->MoveId && g.IO.MouseDown[0]) - { - m_drag_offset = GetMousePos() - dock.pos; - doUndock(dock); - dock.status = Status_Dragged; - } - return ret; - } - - if (!dock.active && dock.status != Status_Dragged) return false; - - //beginPanel(); - - m_end_action = EndAction_EndChild; - - splits(); - - PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0)); - PushStyleColor(ImGuiCol_BorderShadow, ImVec4(0, 0, 0, 0)); - float tabbar_height = GetTextLineHeightWithSpacing(); - - bool draw_tabbar = true; - Dock& firstTab = dock.getFirstTab(); - IM_ASSERT(!firstTab.prev_tab); - if (!firstTab.next_tab) - { - if (extra_flags & ImGuiWindowFlags_NoTitleBar) - { - if (!firstTab.parent && !firstTab.hasChildren()) - draw_tabbar = false; - } - } - - ImVec2 pos = dock.pos; - ImVec2 size = dock.size; - - if (draw_tabbar) - { - if (tabbar(firstTab, opened != NULL)) - { - fillLocation(dock); - *opened = false; - } - pos.y += tabbar_height + GetStyle().WindowPadding.y; - size.y -= tabbar_height + GetStyle().WindowPadding.y; - } - - SetCursorScreenPos(pos); - ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | - ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoBringToFrontOnFocus | - extra_flags; - char tmp[256]; - strcpy(tmp, label); - strcat(tmp, "_docked"); // to avoid https://github.com/ocornut/imgui/issues/713 - bool ret = BeginChild(tmp, size, true, flags); - PopStyleColor(2); - - if (dock.status == Status_Dragged) - { - // Draw this on top of everything else. - ImGuiWindow* window = GetCurrentWindow(); - window->BeginOrderWithinParent = 100000; - } - - return ret; - } - - - void end() - { - m_current = NULL; - if (m_end_action != EndAction_None) { - if (m_end_action == EndAction_End) - { - End(); - } - else if (m_end_action == EndAction_EndChild) - { - PushStyleColor(ImGuiCol_Border, ImVec4(0, 0, 0, 0)); - PushStyleColor(ImGuiCol_BorderShadow, ImVec4(0, 0, 0, 0)); - EndChild(); - PopStyleColor(2); - } - //endPanel(); - } - m_is_begin_open = false; - } - - - void debugWindow() { - //SetNextWindowSize(ImVec2(300, 300)); - if (Begin("Dock Debug Info")) { - for (int i = 0; i < m_docks.size(); ++i) { - if (TreeNode(reinterpret_cast (i), "Dock %d (%p)", i, m_docks[i])) { - Dock &dock = *m_docks[i]; - Text("pos=(%.1f %.1f) size=(%.1f %.1f)", - dock.pos.x, dock.pos.y, - dock.size.x, dock.size.y); - Text("parent = %p\n", - dock.parent); - Text("isContainer() == %s\n", - dock.hasChildren()?"true":"false"); - Text("status = %s\n", - (dock.status == Status_Docked)?"Docked": - ((dock.status == Status_Dragged)?"Dragged": - ((dock.status == Status_Float)?"Float": "?"))); - TreePop(); - } - } - - } - End(); - } - - int getDockIndex(Dock* dock) - { - if (!dock) return -1; - - for (int i = 0; i < m_docks.size(); ++i) - { - if (dock == m_docks[i]) return i; - } - - IM_ASSERT(false); - return -1; - } -}; - - - -static DockContext *g_dock = NULL; - -DockContext* CreateDockContext() -{ - //return new DockContext; // Original code - DockContext* ptr = (DockContext*) ImGui::MemAlloc(sizeof(DockContext));IM_PLACEMENT_NEW (ptr) DockContext();return ptr; // Alternative -} - -void DestroyDockContext(DockContext* dock) -{ - if (dock==g_dock) g_dock=NULL; - if (dock!=NULL) { - //delete dock; // Original code - dock->~DockContext();ImGui::MemFree(dock); // Alternative - } - -} - -void SetCurrentDockContext(DockContext* dock) -{ - g_dock = dock; -} - -DockContext* GetCurrentDockContext() -{ - return g_dock; -} - -void ShutdownDock() -{ - IM_ASSERT(g_dock != NULL && "No current context. Did you call ImGui::CreateDockContext() or ImGui::SetCurrentDockContext()?"); - g_dock->Shutdown(); -} - -void SetNextDock(ImGuiDockSlot slot) { - IM_ASSERT(g_dock != NULL && "No current context. Did you call ImGui::CreateDockContext() or ImGui::SetCurrentDockContext()?"); - g_dock->m_next_dock_slot = slot; -} - -void BeginDockspace() { - IM_ASSERT(g_dock != NULL && "No current context. Did you call ImGui::CreateDockContext() or ImGui::SetCurrentDockContext()?"); - ImGuiWindowFlags flags = ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoScrollbar; - BeginChild("###workspace", ImVec2(0,0), false, flags); - g_dock->m_workspace_pos = GetWindowPos(); - g_dock->m_workspace_size = GetWindowSize(); -} - -void EndDockspace() { - EndChild(); -} - -void SetDockActive() -{ - IM_ASSERT(g_dock != NULL && "No current context. Did you call ImGui::CreateDockContext() or ImGui::SetCurrentDockContext()?"); - g_dock->setDockActive(); -} - - -bool BeginDock(const char* label, bool* opened, ImGuiWindowFlags extra_flags, const ImVec2& default_size, const ImVec2& default_pos) -{ - IM_ASSERT(g_dock != NULL && "No current context. Did you call ImGui::CreateDockContext() or ImGui::SetCurrentDockContext()?"); - return g_dock->begin(label, opened, extra_flags, default_size, default_pos); -} - - -void EndDock() -{ - IM_ASSERT(g_dock != NULL && "No current context. Did you call ImGui::CreateDockContext() or ImGui::SetCurrentDockContext()?"); - g_dock->end(); -} - -void DockDebugWindow() -{ - IM_ASSERT(g_dock != NULL && "No current context. Did you call ImGui::CreateDockContext() or ImGui::SetCurrentDockContext()?"); - g_dock->debugWindow(); -} - - -#if (defined(IMGUIHELPER_H_) && !defined(NO_IMGUIHELPER_SERIALIZATION)) -# ifndef NO_IMGUIHELPER_SERIALIZATION_SAVE - bool SaveDock(ImGuiHelper::Serializer& s) { - if (!s.isValid()) return false; - - IM_ASSERT(g_dock != NULL && "No current context. Did you call ImGui::CreateDockContext() or ImGui::SetCurrentDockContext()?"); - - DockContext& myDock = *g_dock; - ImVector& m_docks = myDock.m_docks; - - int sz = m_docks.size();s.save(&sz,"NumDocks");int id=0; - for (int i = 0; i < m_docks.size(); ++i) { - DockContext::Dock& dock = *m_docks[i]; - s.save(&i,"index"); - if (dock.label) s.save(dock.label,"label"); - s.save(&dock.pos.x,"pos",2); - s.save(&dock.size.x,"size",2); - s.save(&dock.floatmode_size.x,"floatmode_size",2); - id = (int) dock.status;s.save(ImGui::FT_ENUM,&id,"status"); - s.save(&dock.active,"active"); - s.save(&dock.opened,"opened"); - id = myDock.getDockIndex(dock.prev_tab);s.save(&id,"prev"); - id = myDock.getDockIndex(dock.next_tab);s.save(&id,"next"); - id = myDock.getDockIndex(dock.children[0]);s.save(&id,"child0"); - id = myDock.getDockIndex(dock.children[1]);s.save(&id,"child1"); - id = myDock.getDockIndex(dock.parent);s.save(&id,"parent"); - } - return true; - } - bool SaveDock(const char* filename) {ImGuiHelper::Serializer s(filename);return SaveDock(s);} -# endif //NO_IMGUIHELPER_SERIALIZATION_SAVE -# ifndef NO_IMGUIHELPER_SERIALIZATION_LOAD - struct DockParser { - DockContext* myDock;int numDocks;int curIndex; - inline static DockContext::Dock* getDockByIndex(DockContext* myDock,int idx) { return (idx < 0) ? NULL : myDock->m_docks[idx];} - DockParser(DockContext* _myDock) : myDock(_myDock),numDocks(0),curIndex(-1) {IM_ASSERT(myDock);} - static bool Parse(ImGuiHelper::FieldType /*ft*/,int /*numArrayElements*/,void* pValue,const char* name,void* userPtr) { - DockParser& P = *((DockParser*) userPtr); - DockContext& myDock = *P.myDock; - ImVector& m_docks = myDock.m_docks; - const int* pValueInt = (const int*) pValue; - if (strcmp(name,"NumDocks")==0) { - IM_ASSERT(P.curIndex==-1); - P.numDocks = *pValueInt; - IM_ASSERT(m_docks.size()==0); - m_docks.reserve(P.numDocks); - for (int i=0;iP.numDocks) {IM_ASSERT(true);return true;} - else if (strcmp(name,"index")==0) { - P.curIndex = *pValueInt; - IM_ASSERT(P.curIndex>=0 && P.curIndexlast_frame = 0; - m_docks[P.curIndex]->invalid_frames = 0; - } - else if (strcmp(name,"label")==0) { - if (strcmp((const char*)pValue, "\n") == 0) { - // ??? - bug in the serializer, I suppose? - pValue = (void*)""; - } - m_docks[P.curIndex]->label = ImStrdup((const char*) pValue); - m_docks[P.curIndex]->id = ImHash(m_docks[P.curIndex]->label, 0); - } - else if (strcmp(name,"pos")==0) m_docks[P.curIndex]->pos = *((ImVec2*) pValue); - else if (strcmp(name,"size")==0) m_docks[P.curIndex]->size = *((ImVec2*) pValue); - else if (strcmp(name,"floatmode_size")==0) m_docks[P.curIndex]->floatmode_size = *((ImVec2*) pValue); - else if (strcmp(name,"status")==0) m_docks[P.curIndex]->status = (DockContext::Status_) (*pValueInt); - else if (strcmp(name,"active")==0) m_docks[P.curIndex]->active = (*pValueInt) ? true : false; - else if (strcmp(name,"opened")==0) m_docks[P.curIndex]->opened = (*pValueInt) ? true : false; - - else if (strcmp(name,"prev")==0) m_docks[P.curIndex]->prev_tab = getDockByIndex(&myDock,*pValueInt); - else if (strcmp(name,"next")==0) m_docks[P.curIndex]->next_tab = getDockByIndex(&myDock,*pValueInt); - else if (strcmp(name,"child0")==0) m_docks[P.curIndex]->children[0] = getDockByIndex(&myDock,*pValueInt); - else if (strcmp(name,"child1")==0) m_docks[P.curIndex]->children[1] = getDockByIndex(&myDock,*pValueInt); - else if (strcmp(name,"parent")==0) { - m_docks[P.curIndex]->parent = getDockByIndex(&myDock,*pValueInt); - if (P.curIndex+1==P.numDocks) { - return true; - } - } - - return false; - } - }; - bool LoadDock(ImGuiHelper::Deserializer& d,const char ** pOptionalBufferStart) { - if (!d.isValid()) return false; - const char* amount = pOptionalBufferStart ? (*pOptionalBufferStart) : 0; - - IM_ASSERT(g_dock != NULL && "No current context. Did you call ImGui::CreateDockContext() or ImGui::SetCurrentDockContext()?"); - - DockContext& myDock = *g_dock; - ImVector& m_docks = myDock.m_docks; - // clear - for (int i = 0; i < m_docks.size(); ++i) { - m_docks[i]->~Dock(); - ImGui::MemFree(m_docks[i]); - } - m_docks.clear(); - myDock.m_current = myDock.m_next_parent = NULL; - - // parse - DockParser parser(&myDock); - amount = d.parse(&DockParser::Parse,(void*)&parser,amount); - if (pOptionalBufferStart) *pOptionalBufferStart = amount; - return true; - } - bool LoadDock(const char* filename) {ImGuiHelper::Deserializer d(filename);return LoadDock(d);} -# endif //NO_IMGUIHELPER_SERIALIZATION_LOAD -#endif //(defined(IMGUIHELPER_H_) && !defined(NO_IMGUIHELPER_SERIALIZATION)) - - -} // namespace ImGui - - diff --git a/Source Code/Libs/imgui/addons/imguidock/imguidock.h b/Source Code/Libs/imgui/addons/imguidock/imguidock.h deleted file mode 100644 index 0918bba33d..0000000000 --- a/Source Code/Libs/imgui/addons/imguidock/imguidock.h +++ /dev/null @@ -1,150 +0,0 @@ -#ifndef IMGUIDOCK_H_ -#define IMGUIDOCK_H_ - -// based on https://github.com/nem0/LumixEngine/blob/master/external/imgui/imgui_dock.h -// Lumix Engine Dock. From: https://github.com/nem0/LumixEngine/blob/master/src/editor/imgui/imgui_dock.h -/* -The MIT License (MIT) - -Copyright (c) 2013-2016 Mikulas Florek - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ -// modified from https://bitbucket.org/duangle/liminal/src/tip/src/liminal/imgui_dock.h - -// USAGE: -/* - // Outside any ImGuiWindow: - - // Windowed: - if (ImGui::Begin("imguidock window (= lumix engine's dock system)",NULL,ImVec2(500, 500),0.95f,ImGuiWindowFlags_NoScrollbar)) { - ImGui::BeginDockspace(); - static char tmp[128]; - for (int i=0;i<10;i++) { - sprintf(tmp,"Dock %d",i); - if (i==9) ImGui::SetNextDock(ImGuiDockSlot_Bottom);// optional - if(ImGui::BeginDock(tmp)) { - ImGui::Text("Content of dock window %d goes here",i); - } - ImGui::EndDock(); - } - ImGui::EndDockspace(); - } - ImGui::End(); - - - // Fullscreen (without visual artifacts): - ImGui::SetNextWindowSize(ImGui::GetIO().DisplaySize); - const ImGuiWindowFlags flags = (ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoTitleBar); - const float oldWindowRounding = ImGui::GetStyle().WindowRounding;ImGui::GetStyle().WindowRounding = 0; - const bool visible = ImGui::Begin("imguidock window (= lumix engine's dock system)",NULL,ImVec2(0, 0),1.0f,flags); - ImGui::GetStyle().WindowRounding = oldWindowRounding; - if (visible) { - ImGui::BeginDockspace(); - static char tmp[128]; - for (int i=0;i<10;i++) { - sprintf(tmp,"Dock %d",i); - if (i==9) ImGui::SetNextDock(ImGuiDockSlot_Bottom);// optional - if(ImGui::BeginDock(tmp)) { - ImGui::Text("Content of dock window %d goes here",i); - } - ImGui::EndDock(); - } - ImGui::EndDockspace(); - } - ImGui::End(); -*/ - -#ifndef IMGUI_API -#include -#endif //IMGUI_API - -typedef enum ImGuiDockSlot { - ImGuiDockSlot_Left=0, - ImGuiDockSlot_Right, - ImGuiDockSlot_Top, - ImGuiDockSlot_Bottom, - ImGuiDockSlot_Tab, - - ImGuiDockSlot_Float, - ImGuiDockSlot_None -} ImGuiDockSlot; - -namespace ImGui{ - -struct DockContext; - -// Create, destroy and change dock contexts (*). - -// EXAMPLE USAGE: -/* ImGui::DockContext* myDockContext=NULL; // global variable - - // When you init your application: - myDockContext = ImGui::CreateDockContext(); - ImGui::SetCurrentDockContext(myDockContext); - // From now on you can use imguidock [calling BeginDockspace()/EndDockspace() and so on]. - - // When you destroy your application: - ImGui::DestroyDockContext(myDockContext);myDockContext=NULL; -*/ - -// (*) This is really mandatory only if you're not using an IMGUI_USE_XXX_BINDING, or if you don't know -// what IMGUI_USE_XXX_BINDING is (because otherwise the code above is already called for you in addons/imguibindings/imguibindings.cpp). - -// Each created context must be set current using SetCurrentDockContext and destroyed using DestroyDockContext. -IMGUI_API DockContext* CreateDockContext(); -IMGUI_API void DestroyDockContext(DockContext* dock); - -IMGUI_API void SetCurrentDockContext(DockContext* dock); -IMGUI_API DockContext* GetCurrentDockContext(); - - - -IMGUI_API void BeginDockspace(); -IMGUI_API void EndDockspace(); -IMGUI_API void ShutdownDock(); -IMGUI_API void SetNextDock(ImGuiDockSlot slot); -// 'default_size', when positive, will be used as the initial size of the Window when in floating/undocked mode. -// When the floating/undocked window is manually resized, the last modified window size is kept (and the passed argument is ignored). -// If 'default_size' is negative, any manual resizing (of the floating window) will be lost when the window is re-docked. -// Please note that if you LoadDock(...) the last saved value will be used (so 'default_size' can still be ignored). -IMGUI_API bool BeginDock(const char* label, bool* opened = NULL, ImGuiWindowFlags extra_flags = 0, const ImVec2& default_size = ImVec2(0,0), const ImVec2& default_pos = ImVec2(0,0)); -IMGUI_API void EndDock(); -IMGUI_API void SetDockActive(); -IMGUI_API void DockDebugWindow(); - -// Ported from the original "Lua binding" code -#if (defined(IMGUIHELPER_H_) && !defined(NO_IMGUIHELPER_SERIALIZATION)) -# ifndef NO_IMGUIHELPER_SERIALIZATION_SAVE - IMGUI_API bool SaveDock(ImGuiHelper::Serializer& s); - IMGUI_API bool SaveDock(const char* filename); -# endif //NO_IMGUIHELPER_SERIALIZATION_SAVE -# ifndef NO_IMGUIHELPER_SERIALIZATION_LOAD - IMGUI_API bool LoadDock(ImGuiHelper::Deserializer& d,const char ** pOptionalBufferStart=NULL); - IMGUI_API bool LoadDock(const char* filename); -# endif //NO_IMGUIHELPER_SERIALIZATION_LOAD -#endif //(defined(IMGUIHELPER_H_) && !defined(NO_IMGUIHELPER_SERIALIZATION)) - -} // namespace ImGui - -extern bool gImGuiDockReuseTabWindowTextureIfAvailable; // [true] (used only when available) - - -#endif //IMGUIDOCK_H_ - diff --git a/Source Code/Libs/imgui/addons/imguipanelmanager/imguipanelmanager.cpp b/Source Code/Libs/imgui/addons/imguipanelmanager/imguipanelmanager.cpp deleted file mode 100644 index 3a60eecafe..0000000000 --- a/Source Code/Libs/imgui/addons/imguipanelmanager/imguipanelmanager.cpp +++ /dev/null @@ -1,1875 +0,0 @@ -#include "imguipanelmanager.h" -//#include // fprintf - -#ifndef NO_IMGUITABWINDOW -#include "../imguitabwindow/imguitabwindow.h" -#endif //NO_IMGUITABWINDOW - - -bool gImGuiDockpanelManagerExtendedStyle = true; -float gImGuiDockPanelManagerActiveResizeSize = 6.f; -bool gImGuiDockPanelManagerAddExtraTitleBarResizing = false; -bool gImGuiDockPanelManagerAlwaysDrawExternalBorders = true; - -// Static Helper Methods: -namespace ImGui { - -static ImVec4 ColorDockButton(0.75f, 0.75f, 0.30f, 0.50f); -static ImVec4 ColorDockButtonHovered(0.85f, 0.85f, 0.5f, 0.60f); -static ImVec4 ColorDockButtonActive(0.70f, 0.70f, 0.60f, 1.00f); - -static ImVec4 ColorDockButtonLines(0.80f, 0.90f, 0.70f, 1.00f); -static ImVec4 ColorDockButtonLinesHovered(0.85f, 1.f, 0.65f, 1.00f); - -static ImVec4 ColorDockButtonIconBg(0.75f, 0.75f, 0.75f, 0.70f); -static ImVec4 ColorDockButtonIconBgHovered(0.85f, 0.85f, 0.85f, 0.85f); -static ImVec4 ColorDockButtonIconBgActive(0.70f, 0.70f, 0.70f, 1.00f); - -static ImVec4 ColorDockButtonIcon(0.80f, 0.90f, 0.70f, 1.00f); -static ImVec4 ColorDockButtonIconHovered(0.85f, 1.f, 0.65f, 1.00f); - - -// Upper-right button to close a window. -static bool DockWindowButton(bool* p_undocked,bool *p_open=NULL) -{ - ImGuiWindow* window = GetCurrentWindow(); - - const ImGuiID id = window->GetID("#DOCK"); - const float size = window->TitleBarHeight() - 4.0f; - const ImRect bb(window->Rect().GetTR() + ImVec2(-3.0f-size,2.0f) + (p_open ? ImVec2(-3.0f-size,0) : ImVec2(0,0)), - window->Rect().GetTR() + ImVec2(-3.0f,2.0f+size) + (p_open ? ImVec2(-3.0f-size,0) : ImVec2(0,0)) - ); - - bool hovered, held; - bool pressed = ButtonBehavior(bb, id, &hovered, &held);//, true); - - if (p_undocked) { -# ifdef IMGUITABWINDOW_H_ - if (gImGuiDockpanelManagerExtendedStyle && ImGui::TabWindow::DockPanelIconTextureID) { - // Render - ImU32 col = GetColorU32((held && hovered) ? ColorDockButtonIconBgActive : hovered ? ColorDockButtonIconBgHovered : ColorDockButtonIconBg); - window->DrawList->AddRectFilled(bb.Min, bb.Max, col,2.f); - - col = !hovered ? GetColorU32(ColorDockButtonIcon) : GetColorU32(ColorDockButtonIconHovered); - if (*p_undocked) { - window->DrawList->AddImage(ImGui::TabWindow::DockPanelIconTextureID,bb.Min,bb.Max,ImVec2(0.0f,0.75f),ImVec2(0.25f,1.f), col); - } - else { - window->DrawList->AddImage(ImGui::TabWindow::DockPanelIconTextureID,bb.Min,bb.Max,ImVec2(0.25f,0.75f),ImVec2(0.5f,1.f), col); - } - } - else -# endif //IMGUITABWINDOW_H_ - { - // Render - ImU32 col = GetColorU32((held && hovered) ? ColorDockButtonActive : hovered ? ColorDockButtonHovered : ColorDockButton); - window->DrawList->AddRectFilled(bb.Min, bb.Max, col, 0); - - col = !hovered ? GetColorU32(ColorDockButtonLines) : GetColorU32(ColorDockButtonLinesHovered); - ImRect bb2 = bb; - const ImVec2 sz = bb.GetSize(); - if (*p_undocked) { - bb2.Expand(ImVec2(-.2f*sz.x,-.7*sz.y)); - window->DrawList->AddRect(bb2.Min,bb2.Max, col, 0); - } - else { - bb2.Expand(ImVec2(-.7f*sz.x,-.2*sz.y)); - window->DrawList->AddRect(bb2.Min,bb2.Max, col, 0); - } - } - } - else { - // Render - ImU32 col = GetColorU32((held && hovered) ? ColorDockButtonActive : hovered ? ColorDockButtonHovered : ColorDockButton); - window->DrawList->AddRectFilled(bb.Min, bb.Max, col, 0); - } - - if (p_undocked != NULL && pressed) - *p_undocked = !*p_undocked; - - return pressed; -} -static bool DockWindowBegin(const char* name, bool* p_opened,bool* p_undocked, const ImVec2& size, float bg_alpha, ImGuiWindowFlags flags,bool* pDraggingStarted=NULL,ImGui::PanelManager::WindowData* wd=NULL) -{ - ImGuiContext& g = *GImGui; - const ImGuiStyle& style = g.Style; - IM_ASSERT(name != NULL); // Window name required - IM_ASSERT(g.Initialized); // Forgot to call ImGui::NewFrame() - IM_ASSERT(g.FrameScopeActive); // Forgot to call ImGui::NewFrame() - IM_ASSERT(g.FrameCountEnded != g.FrameCount); // Called ImGui::Render() or ImGui::EndFrame() and haven't called ImGui::NewFrame() again yet - - // Find or create - ImGuiWindow* window = FindWindowByName(name); - const bool window_just_created = (window == NULL); - if (window_just_created) - { - ImVec2 size_on_first_use = (g.NextWindowData.SizeCond != 0) ? g.NextWindowData.SizeVal : ImVec2(0.0f, 0.0f); // Any condition flag will do since we are creating a new window here. - window = CreateNewWindow(name, size_on_first_use, flags); - } - - // Automatically disable manual moving/resizing when NoInputs is set - if (flags & ImGuiWindowFlags_NoInputs) - flags |= ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize; - - if (flags & ImGuiWindowFlags_NavFlattened) - IM_ASSERT(flags & ImGuiWindowFlags_ChildWindow); - - const int current_frame = g.FrameCount; - const bool first_begin_of_the_frame = (window->LastFrameActive != current_frame); - if (first_begin_of_the_frame) - window->Flags = (ImGuiWindowFlags)flags; - else - flags = window->Flags; - - // 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(); - 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)); - window->HasCloseButton = (p_opened != NULL); - - // 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 - const bool window_just_appearing_after_hidden_for_resize = (window->HiddenFramesForResize > 0); - if (flags & ImGuiWindowFlags_Popup) - { - ImGuiPopupRef& popup_ref = g.OpenPopupStack[g.CurrentPopupStack.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 || window_just_appearing_after_hidden_for_resize); - if (window->Appearing) - SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, true); - - // Add to stack - g.CurrentWindowStack.push_back(window); - SetCurrentWindow(window); - CheckStacksSize(window, true); - if (flags & ImGuiWindowFlags_Popup) - { - ImGuiPopupRef& popup_ref = g.OpenPopupStack[g.CurrentPopupStack.Size]; - popup_ref.Window = window; - g.CurrentPopupStack.push_back(popup_ref); - window->PopupId = popup_ref.PopupId; - } - - if (window_just_appearing_after_hidden_for_resize && !(flags & ImGuiWindowFlags_ChildWindow)) - window->NavLastIds[0] = 0; - - // Process SetNextWindow***() calls - bool window_pos_set_by_api = false; - bool window_size_x_set_by_api = false, window_size_y_set_by_api = false; - if (g.NextWindowData.PosCond) - { - window_pos_set_by_api = (window->SetWindowPosAllowFlags & g.NextWindowData.PosCond) != 0; - if (window_pos_set_by_api && ImLengthSqr(g.NextWindowData.PosPivotVal) > 0.00001f) - { - // May be processed on the next frame if this is our first frame and we are measuring size - // FIXME: Look into removing the branch so everything can go through this same code path for consistency. - window->SetWindowPosVal = g.NextWindowData.PosVal; - window->SetWindowPosPivot = g.NextWindowData.PosPivotVal; - window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing); - } - else - { - SetWindowPos(window, g.NextWindowData.PosVal, g.NextWindowData.PosCond); - } - } - if (g.NextWindowData.SizeCond) - { - window_size_x_set_by_api = (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.x > 0.0f); - window_size_y_set_by_api = (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.y > 0.0f); - SetWindowSize(window, g.NextWindowData.SizeVal, g.NextWindowData.SizeCond); - } - if (g.NextWindowData.ContentSizeCond) - { - // Adjust passed "client size" to become a "window size" - window->SizeContentsExplicit = g.NextWindowData.SizeVal; - if (window->SizeContentsExplicit.y != 0.0f) - window->SizeContentsExplicit.y += window->TitleBarHeight() + window->MenuBarHeight(); - } - else if (first_begin_of_the_frame) - { - window->SizeContentsExplicit = ImVec2(0.0f, 0.0f); - } - if (g.NextWindowData.CollapsedCond) - SetWindowCollapsed(window, g.NextWindowData.CollapsedVal, g.NextWindowData.CollapsedCond); - if (g.NextWindowData.FocusCond) - FocusWindow(window); - if (window->Appearing) - SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, false); - - // Default alpha - //if (bg_alpha < 0.0f) bg_alpha = 0.7f; //1.0f; //It was 0.7f (e.g. style.WindowFillAlphaDefault); - const ImGuiCol bg_color_idx = GetWindowBgColorIdxFromFlags(flags); - const ImVec4 bg_color_backup = g.Style.Colors[bg_color_idx]; - if (bg_alpha >= 0.0f) g.Style.Colors[bg_color_idx].w = bg_alpha; - - // When reusing window again multiple times a frame, just append content (don't need to setup again) - if (first_begin_of_the_frame) - { - // Initialize - const bool window_is_child_tooltip = (flags & ImGuiWindowFlags_ChildWindow) && (flags & ImGuiWindowFlags_Tooltip); // FIXME-WIP: Undocumented behavior of Child+Tooltip for pinned tooltip (#1345) - UpdateWindowParentAndRootLinks(window, flags, parent_window); - - window->Active = true; - window->BeginOrderWithinParent = 0; - window->BeginOrderWithinContext = g.WindowsActiveCount++; - window->BeginCount = 0; - window->ClipRect = ImVec4(-FLT_MAX,-FLT_MAX,+FLT_MAX,+FLT_MAX); - window->LastFrameActive = current_frame; - window->IDStack.resize(1); - - // UPDATE CONTENTS SIZE, UPDATE HIDDEN STATUS - - // Update contents size from last frame for auto-fitting (or use explicit size) - window->SizeContents = CalcSizeContents(window); - if (window->HiddenFramesRegular > 0) - window->HiddenFramesRegular--; - if (window->HiddenFramesForResize > 0) - window->HiddenFramesForResize--; - - // Hide new windows for one frame until they calculate their size - if (window_just_created && (!window_size_x_set_by_api || !window_size_y_set_by_api)) - window->HiddenFramesForResize = 1; - - // Hide popup/tooltip window when re-opening while we measure size (because we recycle the windows) - // We reset Size/SizeContents for reappearing popups/tooltips early in this function, so further code won't be tempted to use the old size. - if (window_just_activated_by_user && (flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) != 0) - { - window->HiddenFramesForResize = 1; - if (flags & ImGuiWindowFlags_AlwaysAutoResize) - { - if (!window_size_x_set_by_api) - window->Size.x = window->SizeFull.x = 0.f; - if (!window_size_y_set_by_api) - window->Size.y = window->SizeFull.y = 0.f; - window->SizeContents = ImVec2(0.f, 0.f); - } - } - - SetCurrentWindow(window); - - - // Setup draw list and outer clipping rectangle - window->DrawList->Clear(); - window->DrawList->Flags = (g.Style.AntiAliasedLines ? ImDrawListFlags_AntiAliasedLines : 0) | (g.Style.AntiAliasedFill ? ImDrawListFlags_AntiAliasedFill : 0); - window->DrawList->PushTextureID(g.Font->ContainerAtlas->TexID); - ImRect viewport_rect(GetViewportRect()); - if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) && !window_is_child_tooltip) - PushClipRect(parent_window->ClipRect.Min, parent_window->ClipRect.Max, true); - else - PushClipRect(viewport_rect.Min, viewport_rect.Max, true); - - /*if (window_just_activated_by_user) - { - // Popup first latch mouse position, will position itself when it appears next frame - window->AutoPosLastDirection = ImGuiDir_None; - if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api) - window->PosFloat = g.IO.MousePos; - }*/ - - // 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 - //int border_held = -1; - if (!(flags & ImGuiWindowFlags_NoTitleBar))// && !(flags & ImGuiWindowFlags_NoCollapse)) - { - ImRect title_bar_rect = window->TitleBarRect(); - /* - if (g.HoveredWindow == window && IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max) && g.IO.MouseDoubleClicked[0]) - { - window->Collapsed = !window->Collapsed; - if (!(flags & ImGuiWindowFlags_NoSavedSettings)) - MarkSettingsDirty(); - FocusWindow(window); - } - */ - float buttonsSize = 0; - if (p_opened) buttonsSize+=window->TitleBarHeight() - 4.0f +3.0f; - if (p_undocked) buttonsSize+=window->TitleBarHeight() - 4.0f +3.0f; - - const ImRect title_bar_without_buttons_rect(title_bar_rect.Min,title_bar_rect.Max-ImVec2(buttonsSize,0)); - - // NEW PART: resize by dragging title bar (and mousewheel ?) - if (wd && pDraggingStarted && (wd->dockPos==ImGui::PanelManager::BOTTOM || gImGuiDockPanelManagerAddExtraTitleBarResizing || gImGuiDockPanelManagerActiveResizeSize==0) && (*pDraggingStarted || (g.HoveredWindow == window && IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max)))) { - const ImGuiID resize_id = window->GetID("#RESIZE"); - bool hovered = false, held=false,wheel = false;//g.IO.MouseWheel!=0.f; - if (!wheel) ButtonBehavior(title_bar_without_buttons_rect, resize_id, &hovered, &held);//, true); - if (hovered || held || wheel) { - g.MouseCursor = wd->dockPosisToggleWindow) wd->isResizing = true; - } - *pDraggingStarted = held; - if (held || wheel) { - ImVec2 newSize(0,0); - ImVec2 amount = wheel ? ImVec2(g.IO.MouseWheel*20.f,g.IO.MouseWheel*20.f) : g.IO.MouseDelta; - if (wd->dockPos==ImGui::PanelManager::LEFT || wd->dockPos==ImGui::PanelManager::TOP) newSize = ImMax(window->SizeFull + amount, style.WindowMinSize); - else newSize = ImMax(window->SizeFull - amount, style.WindowMinSize); - wd->length = wd->dockPosCollapsed = false; - - - window->WindowBorderSize = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildBorderSize : ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupBorderSize : style.WindowBorderSize; - window->WindowPadding = style.WindowPadding; - if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & (ImGuiWindowFlags_AlwaysUseWindowPadding | ImGuiWindowFlags_Popup)) && window->WindowBorderSize == 0.0f) - window->WindowPadding = ImVec2(0.0f, (flags & ImGuiWindowFlags_MenuBar) ? style.WindowPadding.y : 0.0f); - window->DC.MenuBarOffset.x = ImMax(ImMax(window->WindowPadding.x, style.ItemSpacing.x), g.NextWindowData.MenuBarOffsetMinVal.x); - window->DC.MenuBarOffset.y = g.NextWindowData.MenuBarOffsetMinVal.y; - const float window_rounding = window->WindowRounding; - const float window_border_size = window->WindowBorderSize; - - // Hide new windows for one frame until they calculate their size - if (window_just_created && (!window_size_x_set_by_api || !window_size_y_set_by_api)) - window->HiddenFramesForResize = 1; - - // Calculate auto-fit size, handle automatic resize - const ImVec2 size_auto_fit = CalcSizeAutoFit(window, window->SizeContents); - ImVec2 size_full_modified(FLT_MAX, FLT_MAX); - if ((flags & ImGuiWindowFlags_AlwaysAutoResize) && !window->Collapsed) - { - // Using SetNextWindowSize() overrides ImGuiWindowFlags_AlwaysAutoResize, so it can be used on tooltips/popups, etc. - if (!window_size_x_set_by_api) - window->SizeFull.x = size_full_modified.x = size_auto_fit.x; - if (!window_size_y_set_by_api) - window->SizeFull.y = size_full_modified.y = size_auto_fit.y; - } - else if (window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0) - { - // Auto-fit may only grow window during the first few frames - // We still process initial auto-fit on collapsed windows to get a window width, but otherwise don't honor ImGuiWindowFlags_AlwaysAutoResize when collapsed. - if (!window_size_x_set_by_api && window->AutoFitFramesX > 0) - window->SizeFull.x = size_full_modified.x = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.x, size_auto_fit.x) : size_auto_fit.x; - if (!window_size_y_set_by_api && window->AutoFitFramesY > 0) - window->SizeFull.y = size_full_modified.y = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.y, size_auto_fit.y) : size_auto_fit.y; - if (!window->Collapsed) - MarkIniSettingsDirty(window); - } - - // Apply minimum/maximum window size constraints and final size - window->SizeFull = CalcSizeAfterConstraint(window, window->SizeFull); - /*window->Size = window->Collapsed ? window->TitleBarRect().GetSize() : window->SizeFull; - if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup)) - { - IM_ASSERT(window_size_x_set_by_api && window_size_y_set_by_api); // Submitted by BeginChild() - window->Size = window->SizeFull; - }*/ - window->Size = window->Collapsed && !(flags & ImGuiWindowFlags_ChildWindow) ? window->TitleBarRect().GetSize() : window->SizeFull; - - // SCROLLBAR STATUS - - // Update scrollbar status (based on the Size that was effective during last frame or the auto-resized Size). - if (!window->Collapsed) - { - // When reading the current size we need to read it after size constraints have been applied - float size_x_for_scrollbars = size_full_modified.x != FLT_MAX ? window->SizeFull.x : window->SizeFullAtLastBegin.x; - float size_y_for_scrollbars = size_full_modified.y != FLT_MAX ? window->SizeFull.y : window->SizeFullAtLastBegin.y; - window->ScrollbarY = (flags & ImGuiWindowFlags_AlwaysVerticalScrollbar) || ((window->SizeContents.y > size_y_for_scrollbars) && !(flags & ImGuiWindowFlags_NoScrollbar)); - //window->ScrollbarX = (flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar) || ((window->SizeContents.x > size_x_for_scrollbars - (window->ScrollbarY ? style.ScrollbarSize : 0.0f) - window->WindowPadding.x) && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar)); - window->ScrollbarX = (flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar) || ((window->SizeContents.x > size_x_for_scrollbars - (window->ScrollbarY ? style.ScrollbarSize : 0.0f)) && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar)); - if (window->ScrollbarX && !window->ScrollbarY) { - //window->ScrollbarY = (window->SizeContents.y > size_y_for_scrollbars + style.ScrollbarSize) && !(flags & ImGuiWindowFlags_NoScrollbar); - window->ScrollbarY = (window->SizeContents.y > size_y_for_scrollbars - style.ScrollbarSize) && !(flags & ImGuiWindowFlags_NoScrollbar); - } - window->ScrollbarSizes = ImVec2(window->ScrollbarY ? style.ScrollbarSize : 0.0f, window->ScrollbarX ? style.ScrollbarSize : 0.0f); - } - - // POSITION - - // Popup latch its initial position, will position itself when it appears next frame - if (window_just_activated_by_user) - { - window->AutoPosLastDirection = ImGuiDir_None; - if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api) - window->Pos = g.CurrentPopupStack.back().OpenPopupPos; - } - - // Position child window - if (flags & ImGuiWindowFlags_ChildWindow) - { - window->BeginOrderWithinParent = parent_window->DC.ChildWindows.Size; - parent_window->DC.ChildWindows.push_back(window); - if (!(flags & ImGuiWindowFlags_Popup) && !window_pos_set_by_api && !window_is_child_tooltip) - window->Pos = parent_window->DC.CursorPos; - } - //if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) && !window_pos_set_by_api) - // window->Pos = window->PosFloat = parent_window->DC.CursorPos; - - const bool window_pos_with_pivot = (window->SetWindowPosVal.x != FLT_MAX && window->HiddenFramesForResize == 0); - if (window_pos_with_pivot) - SetWindowPos(window, ImMax(style.DisplaySafeAreaPadding, window->SetWindowPosVal - window->SizeFull * window->SetWindowPosPivot), 0); // Position given a pivot (e.g. for centering) - else if ((flags & ImGuiWindowFlags_ChildMenu) != 0) - window->Pos = FindBestWindowPosForPopup(window); - else if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api && window_just_appearing_after_hidden_for_resize) - window->Pos = FindBestWindowPosForPopup(window); - else if ((flags & ImGuiWindowFlags_Tooltip) != 0 && !window_pos_set_by_api && !window_is_child_tooltip) - window->Pos = FindBestWindowPosForPopup(window); - - // Clamp position so it stays visible - if (!(flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Tooltip)) - { - if (!window_pos_set_by_api && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0 && g.IO.DisplaySize.x > 0.0f && g.IO.DisplaySize.y > 0.0f) // Ignore zero-sized display explicitly to avoid losing positions if a window manager reports zero-sized window when initializing or minimizing. - { - ImVec2 padding = ImMax(style.DisplayWindowPadding, style.DisplaySafeAreaPadding); - window->Pos = ImMax(window->Pos + window->Size, padding) - window->Size; - window->Pos = ImMin(window->Pos, g.IO.DisplaySize - padding); - } - } - window->Pos = ImFloor(window->Pos); - - // Default item width. Make it proportional to window size if window manually resizes - if (window->Size.x > 0.0f && !(flags & ImGuiWindowFlags_Tooltip) && !(flags & ImGuiWindowFlags_AlwaysAutoResize)) - window->ItemWidthDefault = (float)(int)(window->Size.x * 0.65f); - else - window->ItemWidthDefault = (float)(int)(g.FontSize * 16.0f); - - // Lock window rounding for the frame (so that altering them doesn't cause inconsistencies) - window->WindowRounding = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildRounding : ((flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupRounding : style.WindowRounding; - - // Prepare for item focus requests - window->FocusIdxAllRequestCurrent = (window->FocusIdxAllRequestNext == INT_MAX || window->FocusIdxAllCounter == -1) ? INT_MAX : (window->FocusIdxAllRequestNext + (window->FocusIdxAllCounter+1)) % (window->FocusIdxAllCounter+1); - window->FocusIdxTabRequestCurrent = (window->FocusIdxTabRequestNext == INT_MAX || window->FocusIdxTabCounter == -1) ? INT_MAX : (window->FocusIdxTabRequestNext + (window->FocusIdxTabCounter+1)) % (window->FocusIdxTabCounter+1); - window->FocusIdxAllCounter = window->FocusIdxTabCounter = -1; - window->FocusIdxAllRequestNext = window->FocusIdxTabRequestNext = INT_MAX; - - // Apply scrolling - window->Scroll = CalcNextScrollFromScrollTargetAndClamp(window, true); - window->ScrollTarget = ImVec2(FLT_MAX, FLT_MAX); - - // Apply window focus (new and reactivated windows are moved to front) - bool want_focus = false; - if (window_just_activated_by_user && !(flags & ImGuiWindowFlags_NoFocusOnAppearing)) - if (!(flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Tooltip)) || (flags & ImGuiWindowFlags_Popup)) - want_focus = true; - - // Draw modal window background (darkens what is behind them, all viewports) - const bool dim_bg_for_modal = (flags & ImGuiWindowFlags_Modal) && window == GetFrontMostPopupModal() && window->HiddenFramesForResize <= 0; - const bool dim_bg_for_window_list = g.NavWindowingTargetAnim && (window == g.NavWindowingTargetAnim->RootWindow); - if (dim_bg_for_modal || dim_bg_for_window_list) - { - const ImU32 dim_bg_col = GetColorU32(dim_bg_for_modal ? ImGuiCol_ModalWindowDimBg : ImGuiCol_NavWindowingDimBg, g.DimBgRatio); - window->DrawList->AddRectFilled(viewport_rect.Min, viewport_rect.Max, dim_bg_col); - } - - // Draw navigation selection/windowing rectangle background - if (dim_bg_for_window_list && window == g.NavWindowingTargetAnim) - { - ImRect bb = window->Rect(); - bb.Expand(g.FontSize); - if (!bb.Contains(viewport_rect)) // Avoid drawing if the window covers all the viewport anyway - window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha * 0.25f), g.Style.WindowRounding); - } - - // Draw window + handle manual resize - 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 ImRect title_bar_rect = window->TitleBarRect(); - if (window->Collapsed) - { - // Title bar only - 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); - RenderFrame(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, true, window_rounding); - g.Style.FrameBorderSize = backup_border_size; - } - else - { - //ImU32 resize_col = 0; - //const float resize_corner_size = ImMax(g.FontSize * 1.35f, window_rounding + 1.0f + g.FontSize * 0.2f); - /*if (!(flags & ImGuiWindowFlags_AlwaysAutoResize) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0 && !(flags & ImGuiWindowFlags_NoResize)) - { - // Manual resize - const ImVec2 br = window->Rect().GetBR(); - const ImRect resize_rect(br - ImFloor(ImVec2(resize_corner_size * 0.75f, resize_corner_size * 0.75f)), br); - const ImGuiID resize_id = window->GetID("#RESIZE"); - bool hovered, held; - ButtonBehavior(resize_rect, resize_id, &hovered, &held, ImGuiButtonFlags_FlattenChilds); - resize_col = GetColorU32(held ? ImGuiCol_ResizeGripActive : hovered ? ImGuiCol_ResizeGripHovered : ImGuiCol_ResizeGrip); - - if (hovered || held) - g.MouseCursor = ImGuiMouseCursor_ResizeNWSE; - - if (g.HoveredWindow == window && held && g.IO.MouseDoubleClicked[0]) - { - // Manual auto-fit when double-clicking - window->SizeFull = size_auto_fit; - if (!(flags & ImGuiWindowFlags_NoSavedSettings)) - MarkSettingsDirty(); - ClearActiveID(); - } - else if (held) - { - window->SizeFull = ImMax(window->SizeFull + g.IO.MouseDelta, style.WindowMinSize); - if (!(flags & ImGuiWindowFlags_NoSavedSettings)) - MarkSettingsDirty(); - } - - window->Size = window->SizeFull; - title_bar_rect = window->TitleBarRect(); - }*/ - - // NEW PART: resize by dragging window border - if (gImGuiDockPanelManagerActiveResizeSize>0 && !(flags & ImGuiWindowFlags_AlwaysAutoResize) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0 - && pDraggingStarted && wd && (wd->dockPos != ImGui::PanelManager::BOTTOM || (flags & ImGuiWindowFlags_NoTitleBar)) - ) - { - ImVec2 rectMin(0,0),rectMax(0,0); - if (wd->dockPos == ImGui::PanelManager::LEFT) { - rectMin = window->Rect().GetTR(); rectMin.x-=gImGuiDockPanelManagerActiveResizeSize;rectMin.y+=title_bar_rect.GetHeight(); - rectMax = window->Rect().GetBR(); - } - else if (wd->dockPos == ImGui::PanelManager::RIGHT) { - rectMin = window->Rect().GetTL();rectMin.y+=title_bar_rect.GetHeight(); - rectMax = window->Rect().GetBL();rectMax.x+=gImGuiDockPanelManagerActiveResizeSize; - } - else if (wd->dockPos == ImGui::PanelManager::TOP) { - rectMin = window->Rect().GetBL();rectMin.y-=gImGuiDockPanelManagerActiveResizeSize; - rectMax = window->Rect().GetBR(); - } - else if (wd->dockPos == ImGui::PanelManager::BOTTOM) { - rectMin = window->Rect().GetTL(); - rectMax = window->Rect().GetTR();rectMax.y+=gImGuiDockPanelManagerActiveResizeSize; - } - - const ImRect resize_rect(rectMin,rectMax); - if (*pDraggingStarted || (g.HoveredWindow == window && IsMouseHoveringRect(resize_rect.Min, resize_rect.Max))) { - const ImGuiID resize_id = window->GetID("#RESIZE"); - bool hovered = false, held=false,wheel = false; - if (!wheel) ButtonBehavior(resize_rect, resize_id, &hovered, &held); - if (hovered || held || wheel) { - g.MouseCursor = wd->dockPosisToggleWindow) wd->isResizing = true; - } - *pDraggingStarted = held; - if (held || wheel) { - ImVec2 newSize(0,0); - ImVec2 amount = wheel ? ImVec2(g.IO.MouseWheel*20.f,g.IO.MouseWheel*20.f) : g.IO.MouseDelta; - if (wd->dockPos==ImGui::PanelManager::LEFT || wd->dockPos==ImGui::PanelManager::TOP) newSize = ImMax(window->SizeFull + amount, style.WindowMinSize); - else newSize = ImMax(window->SizeFull - amount, style.WindowMinSize); - wd->length = wd->dockPos= 0.0f) - bg_color.w = bg_alpha; - bg_color.w *= style.Alpha; - if (bg_color.w > 0.0f) - window->DrawList->AddRectFilled(window->Pos, window->Pos+window->Size, ColorConvertFloat4ToU32(bg_color), window_rounding); - //window->DrawList->AddRectFilled(window->Pos+ImVec2(0,window->TitleBarHeight()), window->Pos+window->Size, ColorConvertFloat4ToU32(bg_color), window_rounding, (flags & ImGuiWindowFlags_NoTitleBar) ? ImGuiCorner_All : ImGuiCorner_BottomLeft|ImGuiCorner_BottomRight); - //window->DrawList->AddRectFilled(window->Pos + ImVec2(0, window->TitleBarHeight()), window->Pos + window->Size, bg_col, window_rounding, (flags & ImGuiWindowFlags_NoTitleBar) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Bot); - - - // Title bar - ImU32 title_bar_col = GetColorU32(window->Collapsed ? ImGuiCol_TitleBgCollapsed : title_bar_is_highlight ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg); - if (!(flags & ImGuiWindowFlags_NoTitleBar)) { - //const bool window_is_focused = g.NavWindow && window->RootNonPopupWindow == g.NavWindow->RootNonPopupWindow; -# if (defined(IMGUIHELPER_H_) && !defined(NO_IMGUIHELPER_DRAW_METHODS)) - if (gImGuiDockpanelManagerExtendedStyle) { - ImGui::ImDrawListAddRectWithVerticalGradient(window->DrawList, - title_bar_rect.GetTL(), title_bar_rect.GetBR(),title_bar_col,0.15f,0, - window_rounding, ImDrawCornerFlags_TopLeft|ImDrawCornerFlags_TopRight,1.f - ); - } - else -# endif // (defined(IMGUIHELPER_H_) && !defined(NO_IMGUIHELPER_DRAW_METHODS)) - { - window->DrawList->AddRectFilled(title_bar_rect.GetTL(), title_bar_rect.GetBR(), title_bar_col, window_rounding, ImDrawCornerFlags_TopLeft|ImDrawCornerFlags_TopRight); - } - } - - // Menu bar - if (flags & ImGuiWindowFlags_MenuBar) - { - ImRect menu_bar_rect = window->MenuBarRect(); - menu_bar_rect.ClipWith(window->Rect()); // Soft clipping, in particular child window don't have minimum size covering the menu bar so this is useful for them. - window->DrawList->AddRectFilled(menu_bar_rect.Min, menu_bar_rect.Max, GetColorU32(ImGuiCol_MenuBarBg), (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f, ImDrawCornerFlags_Top); - if (style.FrameBorderSize > 0.0f && menu_bar_rect.Max.y < window->Pos.y + window->Size.y) - window->DrawList->AddLine(menu_bar_rect.GetBL(), menu_bar_rect.GetBR(), GetColorU32(ImGuiCol_Border), style.FrameBorderSize); - } - - // Scrollbars - if (window->ScrollbarX) - Scrollbar(ImGuiLayoutType_Horizontal); - if (window->ScrollbarY) - Scrollbar(ImGuiLayoutType_Vertical); - - // Render resize grips (after their input handling so we don't have a frame of latency) - /*if (!(flags & ImGuiWindowFlags_NoResize)) - { - for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++) - { - const ImGuiResizeGripDef& grip = resize_grip_def[resize_grip_n]; - const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, grip.CornerPos); - window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(window_border_size, grip_draw_size) : ImVec2(grip_draw_size, window_border_size))); - window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(grip_draw_size, window_border_size) : ImVec2(window_border_size, grip_draw_size))); - window->DrawList->PathArcToFast(ImVec2(corner.x + grip.InnerDir.x * (window_rounding + window_border_size), corner.y + grip.InnerDir.y * (window_rounding + window_border_size)), window_rounding, grip.AngleMin12, grip.AngleMax12); - window->DrawList->PathFillConvex(resize_grip_col[resize_grip_n]); - } - }*/ - - // Borders - /*if (window_border_size > 0.0f || gImGuiDockPanelManagerAlwaysDrawExternalBorders) - { - window->DrawList->AddRect(window->Pos+ImVec2(1,1), window->Pos+window->Size+ImVec2(1,1), GetColorU32(ImGuiCol_BorderShadow), window_rounding); - window->DrawList->AddRect(window->Pos, window->Pos+window->Size, GetColorU32(ImGuiCol_Border), window_rounding); - if (!(flags & ImGuiWindowFlags_NoTitleBar)) window->DrawList->AddLine(title_bar_rect.GetBL(), title_bar_rect.GetBR(), GetColorU32(ImGuiCol_Border)); - }*/ - if (window_border_size > 0.0f || gImGuiDockPanelManagerAlwaysDrawExternalBorders) - window->DrawList->AddRect(window->Pos, window->Pos+window->Size, GetColorU32(ImGuiCol_Border), window_rounding, ImDrawCornerFlags_All, window_border_size); - /*if (border_held != -1) - { - const float grip_draw_size = (float)(int)ImMax(g.FontSize * 1.35f, window_rounding + 1.0f + g.FontSize * 0.2f); - ImRect border = GetBorderRect(window, border_held, grip_draw_size, 0.0f); - window->DrawList->AddLine(border.Min, border.Max, GetColorU32(ImGuiCol_SeparatorActive), ImMax(1.0f, window_border_size)); - }*/ - if ((style.FrameBorderSize > 0 || gImGuiDockPanelManagerAlwaysDrawExternalBorders) && !(flags & ImGuiWindowFlags_NoTitleBar)) - window->DrawList->AddLine(title_bar_rect.GetBL() + ImVec2(style.WindowBorderSize, -1), title_bar_rect.GetBR() + ImVec2(-style.WindowBorderSize,-1), GetColorU32(ImGuiCol_Border), style.FrameBorderSize); - } - - // Draw navigation selection/windowing rectangle border - if (g.NavWindowingTargetAnim == window) - { - /*ImRect bb = window->Rect(); - bb.Expand(g.FontSize); - if (bb.Contains(viewport_rect)) - bb.Expand(-g.FontSize - 2.0f); - window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha), g.Style.WindowRounding, ~0, 3.0f);*/ - - float rounding = ImMax(window->WindowRounding, g.Style.WindowRounding); - ImRect bb = window->Rect(); - bb.Expand(g.FontSize); - if (bb.Contains(viewport_rect)) // If a window fits the entire viewport, adjust its highlight inward - { - bb.Expand(-g.FontSize - 1.0f); - rounding = window->WindowRounding; - } - window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha), rounding, ~0, 3.0f); - } - - // Store a backup of SizeFull which we will use next frame to decide if we need scrollbars. - window->SizeFullAtLastBegin = window->SizeFull; - - // Update ContentsRegionMax. All the variable it depends on are set above in this function. - window->ContentsRegionRect.Min.x = -window->Scroll.x + window->WindowPadding.x; - window->ContentsRegionRect.Min.y = -window->Scroll.y + window->WindowPadding.y + window->TitleBarHeight() + window->MenuBarHeight(); - window->ContentsRegionRect.Max.x = -window->Scroll.x - window->WindowPadding.x + (window->SizeContentsExplicit.x != 0.0f ? window->SizeContentsExplicit.x : (window->Size.x - window->ScrollbarSizes.x)); - window->ContentsRegionRect.Max.y = -window->Scroll.y - window->WindowPadding.y + (window->SizeContentsExplicit.y != 0.0f ? window->SizeContentsExplicit.y : (window->Size.y - window->ScrollbarSizes.y)); - - // Setup drawing context - // (NB: That term "drawing context / DC" lost its meaning a long time ago. Initially was meant to hold transient data only. Nowadays difference between window-> and window->DC-> is dubious.) - window->DC.Indent = 0.0f + window->WindowPadding.x - window->Scroll.x; - window->DC.GroupOffset = 0.0f; - window->DC.ColumnsOffset = 0.0f; - window->DC.CursorStartPos = window->Pos + ImVec2(window->DC.Indent.x + window->DC.ColumnsOffset.x, window->TitleBarHeight() + window->MenuBarHeight() + window->WindowPadding.y - window->Scroll.y); - window->DC.CursorPos = window->DC.CursorStartPos; - window->DC.CursorPosPrevLine = window->DC.CursorPos; - window->DC.CursorMaxPos = window->DC.CursorStartPos; - window->DC.CurrentLineSize.y = window->DC.PrevLineSize.y = 0.0f; - window->DC.CurrentLineTextBaseOffset = window->DC.PrevLineTextBaseOffset = 0.0f; - window->DC.NavHideHighlightOneFrame = false; - window->DC.NavHasScroll = (GetScrollMaxY() > 0.0f); - window->DC.NavLayerActiveMask = window->DC.NavLayerActiveMaskNext; - window->DC.NavLayerActiveMaskNext = 0x00; - window->DC.MenuBarAppending = false; - window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f; - window->DC.ChildWindows.resize(0); - window->DC.LayoutType = ImGuiLayoutType_Vertical; - window->DC.ParentLayoutType = parent_window ? parent_window->DC.LayoutType : ImGuiLayoutType_Vertical; - window->DC.ItemFlags = ImGuiItemFlags_Default_; - window->DC.ItemWidth = window->ItemWidthDefault; - window->DC.TextWrapPos = -1.0f; // disabled - window->DC.ItemFlagsStack.resize(0); - window->DC.ItemWidthStack.resize(0); - window->DC.TextWrapPosStack.resize(0); - window->DC.ColumnsSet = NULL; - window->DC.TreeDepth = 0; - window->DC.TreeDepthMayJumpToParentOnPop = 0x00; - window->DC.StateStorage = &window->StateStorage; - window->DC.GroupStack.resize(0); - window->MenuColumns.Update(3, style.ItemSpacing.x, window_just_activated_by_user); - - if ((flags & ImGuiWindowFlags_ChildWindow) && (window->DC.ItemFlags != parent_window->DC.ItemFlags)) - { - window->DC.ItemFlags = parent_window->DC.ItemFlags; - window->DC.ItemFlagsStack.push_back(window->DC.ItemFlags); - } - - if (window->AutoFitFramesX > 0) - window->AutoFitFramesX--; - if (window->AutoFitFramesY > 0) - window->AutoFitFramesY--; - - // Apply focus (we need to call FocusWindow() AFTER setting DC.CursorStartPos so our initial navigation reference rectangle can start around there) - if (want_focus) { - FocusWindow(window); - NavInitWindow(window, false); - } - - // Title bar - if (!(flags & ImGuiWindowFlags_NoTitleBar)) - { - - // Close & collapse button are on layer 1 (same as menus) and don't default focus - const ImGuiItemFlags item_flags_backup = window->DC.ItemFlags; - window->DC.ItemFlags |= ImGuiItemFlags_NoNavDefaultFocus; - window->DC.NavLayerCurrent++; - window->DC.NavLayerCurrentMask <<= 1; - - if (p_opened != NULL) { - //*p_opened = CloseWindowButton(p_opened); - //const float pad = 2.0f; - //const float rad = (window->TitleBarHeight() - pad*2.0f) * 0.5f; - //const bool pressed = CloseButton(window->GetID("#CLOSE"), window->Rect().GetTR() + ImVec2(-pad - rad, pad + rad), rad); - const float pad = style.FramePadding.y; - const float rad = g.FontSize * 0.5f; - const bool pressed = CloseButton(window->GetID("#CLOSE"), window->Rect().GetTR() + ImVec2(-pad - rad, pad + rad), rad + 1); - if (p_opened != NULL && pressed) *p_opened = false; - *p_opened = pressed; // Bad but safer - } - if (p_undocked != NULL) - DockWindowButton(p_undocked,p_opened); - - // Restore layer - window->DC.NavLayerCurrent--; - window->DC.NavLayerCurrentMask >>= 1; - window->DC.ItemFlags = item_flags_backup; - - // Title text (FIXME: refactor text alignment facilities along with RenderText helpers) - const ImVec2 text_size = CalcTextSize(name, NULL, true); - if (!(flags & ImGuiWindowFlags_NoCollapse)) - RenderArrow(window->Pos + style.FramePadding, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f); - - ImVec2 text_min = window->Pos; - ImVec2 text_max = window->Pos + ImVec2(window->Size.x, style.FramePadding.y*2 + text_size.y); - ImRect clip_rect; - clip_rect.Max = ImVec2(window->Pos.x + window->Size.x - (p_opened ? title_bar_rect.GetHeight() - 3 : style.FramePadding.x) - (p_undocked ? title_bar_rect.GetHeight() - 3 : style.FramePadding.x), text_max.y); // Match the size of CloseWindowButton() - - float pad_left = (flags & ImGuiWindowFlags_NoCollapse) == 0 ? (style.FramePadding.x + g.FontSize + style.ItemInnerSpacing.x) : style.FramePadding.x; - float pad_right = (p_opened != NULL) ? (style.FramePadding.x + g.FontSize + style.ItemInnerSpacing.x) : style.FramePadding.x; - - if (style.WindowTitleAlign.x > 0.0f) pad_right = ImLerp(pad_right, pad_left, style.WindowTitleAlign.x); - text_min.x += pad_left; - text_max.x -= pad_right; - clip_rect.Min = ImVec2(text_min.x, window->Pos.y); - RenderTextClipped(text_min, text_max, name, NULL, &text_size, style.WindowTitleAlign, &clip_rect); - - } - - // Save clipped aabb so we can access it in constant-time in FindHoveredWindow() - window->OuterRectClipped = window->Rect(); - window->OuterRectClipped.ClipWith(window->ClipRect); - - // Pressing CTRL+C while holding on a window copy its content to the clipboard - // This works but 1. doesn't handle multiple Begin/End pairs, 2. recursing into another Begin/End pair - so we need to work that out and add better logging scope. - // Maybe we can support CTRL+C on every element? - /* - if (g.ActiveId == move_id) - if (g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_C)) - ImGui::LogToClipboard(); - */ - - // Inner rectangle - // We set this up after processing the resize grip so that our clip rectangle doesn't lag by a frame - // Note that if our window is collapsed we will end up with a null clipping rectangle which is the correct behavior. - window->InnerMainRect.Min.x = title_bar_rect.Min.x; - window->InnerMainRect.Min.y = title_bar_rect.Max.y + window->MenuBarHeight(); - window->InnerMainRect.Max.x = window->Pos.x + window->Size.x - window->ScrollbarSizes.x; - window->InnerMainRect.Max.y = window->Pos.y + window->Size.y - window->ScrollbarSizes.y; - //window->DrawList->AddRect(window->InnerRect.Min, window->InnerRect.Max, IM_COL32_WHITE); - - // After Begin() we fill the last item / hovered data using the title bar data. Make that a standard behavior (to allow usage of context menus on title bar only, etc.). - window->DC.LastItemId = window->MoveId; - window->DC.LastItemStatusFlags = IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0; - window->DC.LastItemRect = title_bar_rect; - } - - // Inner clipping rectangle - // Force round operator last to ensure that e.g. (int)(max.x-min.x) in user's render code produce correct result. - const float border_size = window->WindowBorderSize; - ImRect clip_rect; - clip_rect.Min.x = ImFloor(0.5f + window->InnerMainRect.Min.x + ImMax(0.0f, ImFloor(window->WindowPadding.x*0.5f - border_size))); - clip_rect.Min.y = ImFloor(0.5f + window->InnerMainRect.Min.y); - clip_rect.Max.x = ImFloor(0.5f + window->InnerMainRect.Max.x - ImMax(0.0f, ImFloor(window->WindowPadding.x*0.5f - border_size))); - clip_rect.Max.y = ImFloor(0.5f + window->InnerMainRect.Max.y); - PushClipRect(clip_rect.Min, clip_rect.Max, true); - - // Clear 'accessed' flag last thing - if (first_begin_of_the_frame) - window->WriteAccessed = false; - - window->BeginCount++; - g.NextWindowData.Clear(); - - if (flags & ImGuiWindowFlags_ChildWindow) - { - // 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); - /* - window->Collapsed = parent_window && parent_window->Collapsed; - - if (!(flags & ImGuiWindowFlags_AlwaysAutoResize) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0) - window->Collapsed |= (window->ClipRect.Min.x >= window->ClipRect.Max.x || window->ClipRect.Min.y >= window->ClipRect.Max.y); - - // We also hide the window from rendering because we've already added its border to the command list. - // (we could perform the check earlier in the function but it is simpler at this point) - if (window->Collapsed) - window->Active = false; - */ - if (!(flags & ImGuiWindowFlags_AlwaysAutoResize) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0) - if (window->OuterRectClipped.Min.x >= window->OuterRectClipped.Max.x || window->OuterRectClipped.Min.y >= window->OuterRectClipped.Max.y) - window->HiddenFramesRegular = 1; - - // Completely hide along with parent or if parent is collapsed - if (parent_window && (parent_window->Collapsed || parent_window->Hidden)) - window->HiddenFramesRegular = 1; - - } - // Don't render if style alpha is 0.0 at the time of Begin(). This is arbitrary and inconsistent but has been there for a long while (may remove at some point) - if (style.Alpha <= 0.0f) - //window->Active = false; - window->HiddenFramesRegular = 1; - - // Update the Hidden flag - window->Hidden = (window->HiddenFramesRegular > 0) || (window->HiddenFramesForResize); - - - // Return false if we don't intend to display anything to allow user to perform an early out optimization - window->SkipItems = (window->Collapsed || !window->Active || window->Hidden) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0 && window->HiddenFramesForResize <= 0; - - if (bg_alpha >= 0.0f) g.Style.Colors[bg_color_idx] = bg_color_backup; - - return !window->SkipItems; -} -static void DockWindowEnd() -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - - if (window->DC.ColumnsSet != NULL) // close columns set if any is open - EndColumns(); - PopClipRect(); // inner window clip rectangle - - // Stop logging - if (!(window->Flags & ImGuiWindowFlags_ChildWindow)) // FIXME: add more options for scope of logging - ImGui::LogFinish(); - - // Pop - // NB: we don't clear 'window->RootWindow'. The pointer is allowed to live until the next call to Begin(). - g.CurrentWindowStack.pop_back(); - if (window->Flags & ImGuiWindowFlags_Popup) - g.CurrentPopupStack.pop_back(); - CheckStacksSize(window, false); - SetCurrentWindow(g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back()); -} - -} // namespace ImGui - - -void ImGui::PanelManager::Pane::AssociatedWindow::updateSizeInHoverMode(const ImGui::PanelManager &mgr, const ImGui::PanelManager::Pane &pane, size_t winAndBarIndex) const { - if (!isValid() || !dirty) return; - const Toolbutton* button = pane.bar.getButton(winAndBarIndex); - const bool togglable = button && button->isToggleButton; - if (!togglable) { - //fprintf(stderr,"ok \"%s\"\n",windowName); - if (mgr.innerBarQuadSize.x<=0 || mgr.innerBarQuadSize.y<=0) mgr.calculateInnerBarQuadPlacement(); - static const float DefaultHoverSizePortion = 0.2f; - switch (pane.pos) { - case TOP: - if (sizeHoverMode<0) sizeHoverMode = ImGui::GetIO().DisplaySize.y * DefaultHoverSizePortion; - if (sizeHoverMode<2.f * ImGui::GetStyle().WindowMinSize.x) sizeHoverMode = 2.f * ImGui::GetStyle().WindowMinSize.x;// greater than "ImGui::GetStyle().WindowMinSize.x", otherwise the title bar is not accessible - curPos.x = mgr.innerBarQuadPos.x; - curPos.y = mgr.innerBarQuadPos.y; - curSize.x = mgr.innerBarQuadSize.x; - if (sizeHoverMode > mgr.innerBarQuadSize.y) sizeHoverMode = mgr.innerBarQuadSize.y; - curSize.y = sizeHoverMode; - break; - case LEFT: - if (sizeHoverMode<0) sizeHoverMode = ImGui::GetIO().DisplaySize.x * DefaultHoverSizePortion; - if (sizeHoverMode<2.f * ImGui::GetStyle().WindowMinSize.x) sizeHoverMode = 2.f * ImGui::GetStyle().WindowMinSize.x;// greater than "ImGui::GetStyle().WindowMinSize.x", otherwise the title bar is not accessible - curPos.x = mgr.innerBarQuadPos.x; - curPos.y = mgr.innerBarQuadPos.y; - if (sizeHoverMode > mgr.innerBarQuadSize.x) sizeHoverMode = mgr.innerBarQuadSize.x; - curSize.x = sizeHoverMode; - curSize.y = mgr.innerBarQuadSize.y; - break; - case RIGHT: - if (sizeHoverMode<0) sizeHoverMode = ImGui::GetIO().DisplaySize.x * DefaultHoverSizePortion; - curPos.y = mgr.innerBarQuadPos.y; - if (sizeHoverMode > mgr.innerBarQuadSize.x) sizeHoverMode = mgr.innerBarQuadSize.x; - curSize.x = sizeHoverMode; - curPos.x = mgr.innerBarQuadPos.x + mgr.innerBarQuadSize.x - curSize.x; - curSize.y = mgr.innerBarQuadSize.y; - break; - case BOTTOM: - if (sizeHoverMode<0) sizeHoverMode = ImGui::GetIO().DisplaySize.y * DefaultHoverSizePortion; - curPos.x = mgr.innerBarQuadPos.x; - curSize.x = mgr.innerBarQuadSize.x; - if (sizeHoverMode > mgr.innerBarQuadSize.y) sizeHoverMode = mgr.innerBarQuadSize.y; - curSize.y = sizeHoverMode; - curPos.y = mgr.innerBarQuadPos.y + mgr.innerBarQuadSize.y - curSize.y; - break; - default: - IM_ASSERT("Error: unknown case\n"); - break; - } - } - dirty = false; -} - - -void ImGui::PanelManager::Pane::AssociatedWindow::draw(const ImGui::PanelManager &mgr, const ImGui::PanelManager::Pane &pane, size_t winAndBarIndex) const { - if (!isValid()) return; - - const Toolbutton* button = pane.bar.getButton(winAndBarIndex); - const bool togglable = button && button->isToggleButton; - const bool togglableAndVisible = togglable && button->isDown; - const bool selected = (this==pane.getSelectedWindow()); - const bool hovered = !selected && (pane.allowHoverOnTogglableWindows ? !togglableAndVisible : !togglable) && (this==pane.getHoverWindow() || persistHoverFocus); - if (!togglableAndVisible && !selected && !hovered) return; - - if (dirty) { - if (selected) mgr.updateSizes(); - else if (hovered) updateSizeInHoverMode(mgr,pane,winAndBarIndex); // (*) - } - - if (curSize.x<0 || curSize.y<0) return; - - float& sizeToUse = hovered ? sizeHoverMode : size; - if (selected) persistHoverFocus = false; - //else if (hovered) ImGui::SetNextWindowFocus(); // is this line necessary ? - - const float oldSize = sizeToUse;bool open = true;bool undocked = hovered; - WindowData wd(windowName,curPos,curSize,togglable,pane.pos,sizeToUse,open,persistHoverFocus,userData); - //=============================================================================================== - if (wd.isToggleWindow) windowDrawer(wd); - else if (wd.size.x>=ImGui::GetStyle().WindowMinSize.x){ - if (hovered) { - // TO FIX: Ideally updateSizeInHoverMode(...) should not be called every frame, but it's only 1 window on screen after all - dirty = true; // Needed for updateSizeXXX(...) to work. - updateSizeInHoverMode(mgr,pane,winAndBarIndex); // sets dirty to false. - - // Honestly I don't know why dirty = true; is not set in some cases... - // Note that we can comment out the line above and it's still correct, - // because next frame (*) is triggered correctly, but there's one frame delay - - // In short: forcing "dirty = true;" every frame for the hovered windows is WRONG, but it's a hack to fix. - // some cases in which the hovered window pos and size are wrong. - // We should manage to detect the very first frame a new hovered window is displayed and set it dirty only on that frame. - - // I don't think the overhead is big in any case... - } - ImGui::SetNextWindowPos(wd.pos); - ImGui::SetNextWindowSize(wd.size); - static const ImGuiWindowFlags windowFlags = - ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoCollapse | - ImGuiWindowFlags_NoSavedSettings; - ImGuiWindowFlags windowFlagsTotal = wd.isToggleWindow ? ImGuiWindowFlags_NoSavedSettings : (windowFlags|mgr.dockedWindowsExtraFlags|extraWindowFlags); - //if (selected && !wd.isToggleWindow) windowFlagsTotal|=ImGuiWindowFlags_NoBringToFrontOnFocus; // optional line (To be tested). [It doesn't seem to cover all cases] - if ((windowFlagsTotal & ImGuiWindowFlags_NoTitleBar)) open=!open; // Terrible hack to make it work (but I remind that "open" was previously called "closed": so that had a sense!). See *p_opened = CloseWindowButton(p_opened); in BeginDockWindow. - if (ImGui::DockWindowBegin(wd.name, &wd.open,&undocked, wd.size,mgr.dockedWindowsAlpha,windowFlagsTotal,&draggingStarted,&wd)) { - ImGuiContext& g = *GImGui; - const ImGuiStyle& style = g.Style; - ImGuiWindow* window = NULL; - //wd.persistFocus = ImGui::IsMouseHoveringWindow(); - //----------------------------------------------------------------------------------------------- - windowDrawer(wd); - //----------------------------------------------------------------------------------------------- - window = g.CurrentWindow; - wd.persistFocus = (g.HoveredWindow == window || g.HoveredRootWindow == window || (hovered && wd.isResizing)); - if (hovered && !wd.persistFocus) { - const ImVec2& mp = ImGui::GetIO().MousePos; - if (mp.x!=-1 && mp.y!=-1) { - const ImRect bb(wd.pos,ImVec2(wd.pos.x+wd.size.x,wd.pos.y+wd.size.y)); - wd.persistFocus = bb.Contains(mp); - } - } - - const bool allowManualResizeGrip = false;//true; // I DON'T THINK THIS WORKS ANYMORE (TODO: Remove it) - if (allowManualResizeGrip) { - ImRect resize_aabb(0,0,0,0); - const ImGuiID resize_id = window->GetID("#RESIZE"); - float rounding=0; int rounding_corners=~0; - float gripDist = 0;float gripSize = 8; - bool hovered=false, held=false;ImVec2 newSize(-1,-1); - const ImRect& aabb = window->Rect(); - const ImVec2& amin = aabb.Min; - const ImVec2& amax = aabb.Max; - switch (wd.dockPos) { - case LEFT: - resize_aabb = ImRect(amax.x-gripDist-gripSize, - amin.y+gripDist, - amax.x-gripDist, - amax.y-gripDist); - ButtonBehavior(resize_aabb, resize_id, &hovered, &held);//, true); - if (held) { - newSize = ImMax(window->SizeFull + g.IO.MouseDelta, style.WindowMinSize); - wd.length = newSize.x; - } - break; - case RIGHT: - resize_aabb = ImRect(amin.x+gripDist, - amin.y+gripDist, - amin.x+gripDist+gripSize, - amax.y-gripDist); - ButtonBehavior(resize_aabb, resize_id, &hovered, &held);//, true); - if (held) { - newSize = ImMax(window->SizeFull - g.IO.MouseDelta, style.WindowMinSize); - wd.length = newSize.x; - } - break; - case TOP: - resize_aabb = ImRect(amin.x+gripDist, - amax.y-gripDist-gripSize, - amax.x-gripDist, - amax.y-gripDist); - ButtonBehavior(resize_aabb, resize_id, &hovered, &held);//, true); - if (held) { - newSize = ImMax(window->SizeFull + g.IO.MouseDelta, style.WindowMinSize); - wd.length = newSize.y; - } - break; - case BOTTOM: - resize_aabb = ImRect(amin.x+gripDist, - amin.y+gripDist, - amax.x-gripDist, - amin.y+gripDist+gripSize); - ButtonBehavior(resize_aabb, resize_id, &hovered, &held);//, true); - if (held) { - newSize = ImMax(window->SizeFull - g.IO.MouseDelta, style.WindowMinSize); - wd.length = newSize.y; - } - break; - default: - IM_ASSERT("Error"); - break; - } - - if (hovered || held) { - const ImGuiCol resize_col = GetColorU32(held ? ImGuiCol_ResizeGripActive : hovered ? ImGuiCol_ResizeGripHovered : ImGuiCol_ResizeGrip); - window->DrawList->AddRectFilled(resize_aabb.Min, resize_aabb.Max, resize_col, rounding, rounding_corners); - } - if (held) wd.persistFocus = true; - - } - - if (sizeToUse!=oldSize) { - dirty = true; - // Better clamp size now if it's a "selected" window - if (selected) { - if (wd.dockPosdelta) sizeToUse = delta; - const float minWidth = 2.f * ImGui::GetStyle().WindowMinSize.x; // greater than "ImGui::GetStyle().WindowMinSize.x", otherwise the title bar is not accessible - if (sizeToUsedelta) sizeToUse = delta; - curSize.y = sizeToUse; - } - } - } - - } - ImGui::DockWindowEnd(); - } - //================================================================================================ - if (open) { - if (hovered) { - persistHoverFocus = false; - } - //else if (togglableAndVisible) button->isDown = false; - else if (selected) { - pane.bar.setSelectedButtonIndex(-1); - persistHoverFocus = false; // optional line (when commented out leaves a selected window in hover move: we must press the close button another time to hide it) - dirty = true;mgr.updateSizes(); // Needed to update the innerQuadSize - dirty = true; // Needed so next time the hover window sizes are set - } - } - else if (togglableAndVisible) button->isDown = false; - if (!wd.isToggleWindow) { - if (!undocked && hovered) { - pane.bar.setSelectedButtonIndex(winAndBarIndex); - dirty = true; - } - else if (undocked && selected) { - pane.bar.setSelectedButtonIndex(-1); - persistHoverFocus = true; // optional line (when commented out leaves a selected window in hover move: we must press the close button another time to hide it) - dirty = true; mgr.updateSizes(); // Needed to update the innerQuadSize - dirty = true; // Needed so next time the hover window sizes are set - } - } -} - - -size_t ImGui::PanelManager::Pane::addButtonAndWindow(const ImGui::Toolbutton &button, const ImGui::PanelManager::Pane::AssociatedWindow &window, int insertPosition) { - int numButtons = (int) bar.getNumButtons(); - if (numButtons>0 && insertPosition>=0 && insertPosition=insertPosition;i--) { - bar.buttons[i+1]=bar.buttons[i]; - windows[i+1]=windows[i]; - if (bar.selectedButtonIndex==i) bar.selectedButtonIndex=i+1; - if (bar.hoverButtonIndex==i) bar.hoverButtonIndex=i+1; - } - bar.buttons[insertPosition]=button; - windows[insertPosition]=window; - } - else { - bar.addButton(button); - windows.push_back(window); - /*const int wsz = (int) windows.size(); - windows.resize(wsz+1); - windows[wsz] = window;*/ - } - bar.updatePositionAndSize(); - return getSize(); -} - - -size_t ImGui::PanelManager::Pane::addSeparator(float pixels, int insertPosition) { - return addButtonOnly(Toolbutton("",NULL,ImVec2(0,0),ImVec2(0,0),ImVec2(pixels,pixels)),insertPosition); -} - -size_t ImGui::PanelManager::Pane::addButtonOnly(const Toolbutton& button, int insertPosition) { - return addButtonAndWindow(button,AssociatedWindow(),insertPosition); -} - - -size_t ImGui::PanelManager::Pane::addClonedButtonAndWindow(const ImGui::PanelManager::Pane &sourcePane, const size_t sourcePaneEntryIndex, bool flipButtonHorizontally) { - IM_ASSERT(&sourcePane!=this); - IM_ASSERT(sourcePane.getSize()>sourcePaneEntryIndex); - const Toolbutton& sourceButton = *sourcePane.bar.getButton(sourcePaneEntryIndex); - const Pane::AssociatedWindow& sourceWindow = sourcePane.windows[sourcePaneEntryIndex]; - IM_ASSERT(!sourceButton.isToggleButton); // cloning toggle buttons is NOT supported - if (sourceButton.isToggleButton) return getSize(); - size_t size = getSize(); - bar.addButton(sourceButton); - windows.push_back(sourceWindow); - if (flipButtonHorizontally) { - Toolbutton& b = *bar.getButton(size); - const float tmp = b.uv0.x;b.uv0.x=b.uv1.x;b.uv1.x=tmp; - } - bar.updatePositionAndSize(); - return size+1; -} - - -size_t ImGui::PanelManager::Pane::addClonedPane(const ImGui::PanelManager::Pane &sourcePane, bool flipButtonHorizontally, int sourcePaneEntryStartIndex, int sourcePaneEntryEndIndex) { - const int sourcePaneSize = (int)sourcePane.getSize(); - if (sourcePaneSize==0) return getSize(); - sourcePaneEntryEndIndex = (sourcePaneEntryEndIndex<0 || sourcePaneEntryEndIndex>=sourcePaneSize) ? (sourcePaneSize-1) : sourcePaneEntryEndIndex; - sourcePaneEntryStartIndex = sourcePaneEntryStartIndex<0 ? 0 : sourcePaneEntryStartIndex>sourcePaneEntryEndIndex ? sourcePaneEntryEndIndex : sourcePaneEntryStartIndex; - for (int i=sourcePaneEntryStartIndex;i<=sourcePaneEntryEndIndex;i++) { - if (sourcePane.bar.getButton(i)->isToggleButton) continue; // Not supported - addClonedButtonAndWindow(sourcePane,i,flipButtonHorizontally); - } - return getSize(); -} - -size_t ImGui::PanelManager::Pane::deleteButtonAt(int index) { - int numButtons = (int) bar.getNumButtons(); - if (numButtons==0 || index<0 || index>=numButtons) return getSize(); - if (bar.selectedButtonIndex==index) bar.selectedButtonIndex=-1; - if (bar.hoverButtonIndex==index) bar.hoverButtonIndex=-1; - for (int i=index;i=TOP) ? 0.5f : pos == RIGHT ? 1.0f : 0.f; - if (al.y==-1000) al.y = (posisToggleButton) return button->isDown; - const int selectedIndex = bar.getSelectedButtonIndex(); - return (selectedIndex==index); -} - -bool ImGui::PanelManager::Pane::setButtonPressed(int index, bool flag) { - Toolbutton* button = bar.getButton(index); - if (!button) return false; - AssociatedWindow& wnd = windows[index]; - if (!button->isToggleButton) { - if (!bar.keepAButtonSelected) return false; - if (bar.getSelectedButtonIndex()!=index) bar.setSelectedButtonIndex(index); - if (wnd.isValid()) wnd.dirty = true; // Must we include this in the "if" above ? - } - else { - if (button->isDown != flag) { - button->isDown = flag; - //if (wnd.isValid()) {wnd.} // Must we do something here ? - } - } - return true; -} - -const char *ImGui::PanelManager::Pane::getWindowName(int index) const { - if (index<0 || index>=(int)windows.size()) return NULL; - return windows[index].windowName; -} - -int ImGui::PanelManager::Pane::findWindowIndex(const char *windowName) const { - if (!windowName) return -1; - for (size_t i=0,isz=windows.size();i=0 && selectedButtonIndex<(int)bar.getNumButtons()) { - const Pane::AssociatedWindow& wnd = pane.windows[selectedButtonIndex]; - if (!wnd.isValid()) { - bar.selectedButtonIndex = selectedButtonIndex = oldSelectedButtonIndex; - selectedButtonIndexChanged = false; - if (selectedButtonIndex!=-1) updateSizes(); - //fprintf(stderr,"Pressed NULL window\n"); - } - } - - // Optional hack: more comfortable hovering from button to window without easily losing focus - if (hoverButtonIndexChanged && !selectedButtonIndexChanged && hoverButtonIndex==-1) { - if (oldHoverButtonIndex<(int)bar.getNumButtons()) { - const Pane::AssociatedWindow& window = pane.windows[oldHoverButtonIndex]; - if (window.isValid()) - { - if (!window.persistHoverFocus && pane.hoverReleaseIndex!=oldHoverButtonIndex) { - pane.hoverReleaseIndex=oldHoverButtonIndex; - pane.hoverReleaseTimer=ImGui::GetTime(); - //fprintf(stderr,"Starting timer\n"); - - hoverButtonIndex = oldHoverButtonIndex; - hoverButtonIndexChanged = false; - window.persistHoverFocus = true; - } - else if (pane.hoverReleaseIndex == oldHoverButtonIndex) - { - if (ImGui::GetTime()-pane.hoverReleaseTimer<0.25f) { - hoverButtonIndex = oldHoverButtonIndex; - hoverButtonIndexChanged = false; - window.persistHoverFocus = true; - //fprintf(stderr,"Keeping focus\n"); - } - else { - pane.hoverReleaseIndex = -1; - pane.hoverReleaseTimer = -1.f; - //fprintf(stderr,"Hovering timer elapsed\n"); - } - } - } - } - } - - if (pressed>=0) { - // Fill Optional arguments - if (pPanePressedOut) *pPanePressedOut=const_cast (&pane); - if (pPaneToolbuttonPressedIndexOut) *pPaneToolbuttonPressedIndexOut=(int) pressed; - } - - // hack (without this block when clicking to a selected toolbutton, the hover window does not enlarge its size (like it does when clicking on its titlebar button) - if (selectedButtonIndexChanged && selectedButtonIndex==-1 && hoverButtonIndex>=0 && hoverButtonIndex==oldSelectedButtonIndex) { - const Toolbutton* button = bar.getButton((size_t)hoverButtonIndex); - if (!button->isToggleButton) { - pane.windows[hoverButtonIndex].dirty = true; - } - } - - // Cross-pane window handling (optional): - { - if (selectedButtonIndexChanged && selectedButtonIndex>=0) { - const char* windowName = pane.windows[selectedButtonIndex].windowName; - if (!windowName) continue; - // We must turn off the newly selected windows from other panes if present: - for (size_t pi=0,panesSize=(size_t)panes.size();pi=0 && pn.windows.size() > si) { - const char* cmpName = pn.windows[si].windowName; - if (cmpName && strcmp(windowName,cmpName)==0) pn.bar.setSelectedButtonIndex(-1); - } - } - - } - else if (hoverButtonIndexChanged && hoverButtonIndex>=0 && hoverButtonIndex!=selectedButtonIndex) { - const Toolbutton* button = bar.getButton((size_t)hoverButtonIndex); - if (!button->isToggleButton) { - const char* windowName = pane.windows[hoverButtonIndex].windowName; - // we must avoid hovering if "windowName" is selected on another pane - if (windowName) { - for (size_t pi=0,panesSize=(size_t)panes.size();pi=0) { - const char* cmpName = pn.windows[si].windowName; - if (cmpName && strcmp(windowName,cmpName)==0) pane.bar.hoverButtonIndex = -1; - } - } - } - } - } - } - - } - - if (mustUpdateSizes) this->updateSizes(); - - // Draw windows: - for (size_t paneIndex=0,panesSize=(size_t)panes.size();paneIndex0 && ImGui::GetTime()-innerQuadChangedTimer>1.f) { - innerQuadChangedTimer = -1.f; - return true; - } - - return false; -} - - -size_t ImGui::PanelManager::getNumPanes() const {return panes.size();} - -const ImGui::PanelManager::Pane *ImGui::PanelManager::getPane(ImGui::PanelManager::Position pos) const { - for (size_t i=0,isz=panes.size();iinnerBarQuadPos.x+innerQuadSize.x) win.curPos.x=innerBarQuadPos.x+innerQuadSize.x-win.curSize.x; - if (win.curPos.y+win.curSize.y>innerBarQuadPos.y+innerQuadSize.y) win.curPos.y=innerBarQuadPos.y+innerQuadSize.y-win.curSize.y; - if (win.curPos.x=0 && pane.windows.size() > selectedButtonIndex) { - const Pane::AssociatedWindow& win = pane.windows[selectedButtonIndex]; - if (win.isValid()) { - // - win.curPos.x = innerBarQuadPos.x; - win.curPos.y = innerBarQuadPos.y; - win.curSize.x = innerBarQuadSize.x; - if (win.size<0) win.size = ImGui::GetIO().DisplaySize.y * DefaultDockSizePortion; - else if (win.sizeinnerBarQuadSize.y) win.size = innerBarQuadSize.y; - win.curSize.y = win.size; - // - innerQuadPos.y+=win.curSize.y; - innerQuadSize.y-=win.curSize.y; - // - } - win.dirty = false; - } - } - if (paneLeft) { - const Pane& pane = *paneLeft; - const int selectedButtonIndex = pane.bar.getSelectedButtonIndex(); - // - if (selectedButtonIndex>=0 && pane.windows.size() > selectedButtonIndex) { - const Pane::AssociatedWindow& win = pane.windows[selectedButtonIndex]; - if (win.isValid()) { - // - win.curPos.x = innerBarQuadPos.x; - win.curPos.y = innerQuadPos.y; - if (win.size<0) win.size = ImGui::GetIO().DisplaySize.x * DefaultDockSizePortion; - else if (win.sizeinnerBarQuadSize.x) win.size = innerBarQuadSize.x; - win.curSize.x = win.size; - win.curSize.y = innerQuadSize.y; - - // - innerQuadPos.x+=win.curSize.x; - innerQuadSize.x-=win.curSize.x; - // - } - win.dirty = false; - } - } - if (paneRight) { - const Pane& pane = *paneRight; - const int selectedButtonIndex = pane.bar.getSelectedButtonIndex(); - // - if (selectedButtonIndex>=0 && pane.windows.size() > selectedButtonIndex) { - const Pane::AssociatedWindow& win = pane.windows[selectedButtonIndex]; - if (win.isValid()) { - // - win.curPos.y = innerQuadPos.y; - win.curSize.y = innerQuadSize.y; - if (win.size<0) win.size = ImGui::GetIO().DisplaySize.x * DefaultDockSizePortion; - else if (win.sizeinnerQuadSize.x) win.size = innerQuadSize.x; - win.curSize.x = win.size; - win.curPos.x = innerBarQuadPos.x+innerBarQuadSize.x-win.curSize.x; - // - innerQuadSize.x-=win.curSize.x; - // - } - win.dirty = false; - } - } - if (paneBottom) { - const Pane& pane = *paneBottom; - const int selectedButtonIndex = pane.bar.getSelectedButtonIndex(); - // - if (selectedButtonIndex>=0 && pane.windows.size() > selectedButtonIndex) { - const Pane::AssociatedWindow& win = pane.windows[selectedButtonIndex]; - if (win.isValid()) { - // - win.curPos.x = innerQuadPos.x; - win.curSize.x = innerQuadSize.x; - if (win.size<0) win.size = ImGui::GetIO().DisplaySize.y * DefaultDockSizePortion; - else if (win.sizeinnerQuadSize.y) win.size = innerQuadSize.y; - win.curSize.y = win.size; - win.curPos.y = innerQuadPos.y+innerQuadSize.y-win.curSize.y; - // - innerQuadSize.y-=win.curSize.y; - // - } - win.dirty = false; - } - } - - //fprintf(stderr,"innerBarQuad (%1.1f,%1.1f,%1.1f,%1.1f)\n",innerBarQuadPos.x,innerBarQuadPos.y,innerBarQuadSize.x,innerBarQuadSize.y); - //fprintf(stderr,"innerQuad (%1.1f,%1.1f,%1.1f,%1.1f)\n",innerQuadPos.x,innerQuadPos.y,innerQuadSize.x,innerQuadSize.y); -} - -void ImGui::PanelManager::closeHoverWindow() { - for (int i=0,isz=getNumPanes();i=0 && displaySize.y>=0); - - int mwf = (int) mgr.getDockedWindowsExtraFlags();s.save(ImGui::FT_ENUM,&mwf,"managerExtraWindowFlags"); - int sz = mgr.panes.size();s.save(&sz,"numPanes"); - for (int ip=0,ipsz=mgr.panes.size();ipsize = *((float*)pValue); - if(ps.curWindow->size>0) ps.curWindow->size*=(ps.curPane->possizeHoverMode = *((float*)pValue); - if (ps.curWindow->sizeHoverMode>0) ps.curWindow->sizeHoverMode*=(ps.curPane->pospos==*((int*)pValue)) ps.curPane=mgr.getPaneFromIndex(ps.curPaneNum); - //else fprintf(stderr,"Invalid pane\n"); - break; - } - else if (strcmp(name,"extraWindowFlags")==0) { - if (ps.curWindow) { - ps.curWindow->extraWindowFlags=*((int*)pValue); - } - break; - } - else if (strcmp(name,"managerExtraWindowFlags")==0) { - ImGuiWindowFlags flags = *((int*)pValue); - //mgr.dockedWindowsExtraFlags = flags; - //mgr.setDockedWindowsBorder(flags&ImGuiWindowFlags_ShowBorders); - mgr.setDockedWindowsNoTitleBar(flags&ImGuiWindowFlags_NoTitleBar); - break; - } - } - break; - case ImGui::FT_INT: { - if (strcmp(name,"numPanes")==0) { - ps.numPanes=*((int*)pValue);ps.curPaneNum = -1;ps.curPane=NULL; - ps.numButtons = 0;ps.curButtonNum=-1;ps.curWindow=NULL; - break; - } - else if (strcmp(name,"selectedButtonIndex")==0) { - if (ps.curPane) { - ps.curPane->bar.setSelectedButtonIndex(*((int*)pValue)); - } - break; - } - else if (strcmp(name,"numPaneButtons")==0) { - ps.numButtons=*((int*)pValue);ps.curButtonNum=-1;ps.curWindow=NULL; - if (ps.curPane && (int)ps.curPane->bar.getNumButtons()!=ps.numButtons) { - ps.curPane = NULL; // Skip if number does not match - } - if (ps.curPane) { - IM_ASSERT((int)ps.curPane->windows.size()==(int)ps.curPane->bar.getNumButtons()); - } - break; - } - if (strcmp(name,"isWindowPresent")==0) { - ++ps.curButtonNum; - if (ps.curPane && ps.curButtonNumbar.getNumButtons()) ps.curWindow=&ps.curPane->windows[ps.curButtonNum]; - else {ps.curPane = NULL;ps.curWindow = NULL;} - //fprintf(stderr,"pane:%d/%d == %s button:%d/%d window == %s\n",ps.curPaneNum,ps.numPanes,ps.curPane?"OK":"NULL",ps.curButtonNum,ps.numButtons,ps.curWindow? (ps.curWindow->windowName?ps.curWindow->windowName:"OK"):"NULL"); - //bool isWindowPresent = *((bool*)pValue) ? true : false; - break; - } - else if (strcmp(name,"toggleButtonIsPressed")==0) { - bool down = *((int*)pValue) ? true : false; - if (ps.curPane && ps.curButtonNumbar.getNumButtons()>ps.curButtonNum) { - ImGui::Toolbar::Button& btn = *(ps.curPane->bar.getButton(ps.curButtonNum)); - if (btn.isToggleButton) btn.isDown = down; - } - break; - } - } - break; - case ImGui::FT_TEXTLINE: - if (strcmp(name,"endPanelManager")==0) return true; - break; - default: - break; - } - return false; -} -bool ImGui::PanelManager::Load(PanelManager &mgr,ImGuiHelper::Deserializer& d, const char ** pOptionalBufferStart) { - if (!d.isValid()) return false; - ImGuiPanelManagerParserStruct pmps; - pmps.mgr = &mgr;pmps.numButtons=pmps.curButtonNum=pmps.numPanes=pmps.curPaneNum=0;pmps.curPane=NULL;pmps.curWindow=NULL; - pmps.displaySize = ImGui::GetIO().DisplaySize; - IM_ASSERT(pmps.displaySize.x>=0 && pmps.displaySize.y>=0); - - const char* amount = pOptionalBufferStart ? (*pOptionalBufferStart) : 0; - amount = d.parse(ImGuiPanelManagerParser,(void*)&pmps,amount); - if (pOptionalBufferStart) *pOptionalBufferStart=amount; - - mgr.recalculatePositionAndSizes(); - mgr.closeHoverWindow(); - - return true; -} -#endif //NO_IMGUIHELPER_SERIALIZATION_LOAD -#endif //NO_IMGUIHELPER_SERIALIZATION diff --git a/Source Code/Libs/imgui/addons/imguipanelmanager/imguipanelmanager.h b/Source Code/Libs/imgui/addons/imguipanelmanager/imguipanelmanager.h deleted file mode 100644 index 7508379b3a..0000000000 --- a/Source Code/Libs/imgui/addons/imguipanelmanager/imguipanelmanager.h +++ /dev/null @@ -1,249 +0,0 @@ -#ifndef IMGUIPANELMANAGER_H_ -#define IMGUIPANELMANAGER_H_ - -#include "../imguitoolbar/imguitoolbar.h" - - -// STAND-ALONE COMPILATION OF THIS ADDON -/* -If you want to include this addon in your code without using the IMGUI_INCLUDE_IMGUI_USER_H / IMGUI_INCLUDE_IMGUI_USER_INL mechanism, -you can't because imguitoolbar.cpp includes some methods that are static inside imgui.cpp, and are not exposed by imgui_internal.h. -*/ - -#ifndef IMGUI_API -#include -#endif //IMGUI_API - -namespace ImGui { - - -struct PanelManager { - enum Position { - LEFT= 0, - RIGHT= 1, - TOP= 2, - BOTTOM= 3 - }; - struct WindowData { - const char* name; - const ImVec2& pos; - const ImVec2& size; - const bool isToggleWindow; // toggle windows are not "docked" (you can safely ignore "pos" and "size") - const Position dockPos; - - float& length; // can be adjusted to resize the window (usable when isToggleWindow=false) - bool& open; // can be set to "false" to close the window - bool& persistFocus; // must be set to "true" when mouse is inside window (usable when isToggleWindow=false) - - void* userData; - bool isResizing; - WindowData(const char* _name,const ImVec2& _pos,const ImVec2& _size,const bool _isToggleWindow,const Position _dockPos, - float& _length,bool& _open,bool& _persistFocus,void* _userData) - : name(_name),pos(_pos),size(_size),isToggleWindow(_isToggleWindow),dockPos(_dockPos), - length(_length),open(_open),persistFocus(_persistFocus),userData(_userData),isResizing(false) - {} - }; - typedef void (*WindowDrawerDelegate)(WindowData& wd); - - struct Pane { - Toolbar bar; - struct AssociatedWindow { - const char* windowName; // reference - mutable float size; - mutable float sizeHoverMode; - WindowDrawerDelegate windowDrawer; - void* userData; - mutable ImGuiWindowFlags extraWindowFlags; - - // protected: - mutable bool dirty; - mutable ImVec2 curPos; - mutable ImVec2 curSize; - mutable bool persistHoverFocus; - mutable bool draggingStarted; - - AssociatedWindow(const char* _windowName=NULL,float _size=-1,WindowDrawerDelegate _windowDrawer=NULL,void* _userData=NULL,ImGuiWindowFlags _extraWindowFlags=0) : windowName(_windowName),size(_size),sizeHoverMode(_size),windowDrawer(_windowDrawer),userData(_userData),extraWindowFlags(_extraWindowFlags), - dirty(true),curPos(0,0),curSize(0,0),persistHoverFocus(false),draggingStarted(false) - {} - bool isValid() const {return windowDrawer!=NULL && windowName!=NULL;} - - IMGUI_API void updateSizeInHoverMode(const PanelManager& mgr,const Pane& pane,size_t winAndBarIndex) const; - IMGUI_API void draw(const PanelManager& mgr,const Pane& pane,size_t winAndBarIndex) const; - }; - ImVector windows; // must be one per "bar" button - Position pos; - bool visible; - bool allowHoverOnTogglableWindows; - mutable float hoverReleaseTimer; - mutable int hoverReleaseIndex; - Pane(Position _pos,const char* name) - : bar(name),pos(_pos),visible(true),allowHoverOnTogglableWindows(false),hoverReleaseTimer(-1),hoverReleaseIndex(-1) - {} - ~Pane() {clear();} - void clear() { - bar.clearButtons(); - for (int i=0;i=0 ? &windows[bar.selectedButtonIndex] : NULL;} - const AssociatedWindow* getHoverWindow() const {return bar.hoverButtonIndex>=0 ? &windows[bar.hoverButtonIndex] : NULL;} - }; - protected: - - ImVector panes; - Pane *paneLeft,*paneRight,*paneTop,*paneBottom; - bool visible; - mutable ImVec2 innerBarQuadPos;mutable ImVec2 innerBarQuadSize; // placement of the blank quad contained inside the toolbars only - mutable ImVec2 innerQuadPos;mutable ImVec2 innerQuadSize; // placement of the blank quad contained inside the toolbars AND the selected windows that are docked to them - mutable float dockedWindowsAlpha; - mutable float innerQuadChangedTimer; - mutable ImGuiWindowFlags dockedWindowsExtraFlags; - public: - PanelManager(bool _visible=true,float _dockedWindowsAlpha=0.8f) : paneLeft(NULL),paneRight(NULL),paneTop(NULL),paneBottom(NULL),visible(_visible), - innerBarQuadPos(0,0),innerBarQuadSize(-1,-1),innerQuadPos(0,0),innerQuadSize(-1,-1),dockedWindowsAlpha(_dockedWindowsAlpha),innerQuadChangedTimer(-1.f),dockedWindowsExtraFlags(0) {} - ~PanelManager() {clear();} - void clear() { - for (int i=0;i=0 && index=0 && index -#undef IMGUI_DEFINE_MATH_OPERATORS -#define IMGUI_DEFINE_MATH_OPERATORS -#include -//----------------------------------------------------------------------------------------------------------------- - -#include "imguitabwindow.h" - - -// TODO: Clean this code, it's a mess! - -#if !defined(alloca) -# if defined(__GLIBC__) || defined(__sun) || defined(__CYGWIN__) -# include // alloca (glibc uses . Note that Cygwin may have _WIN32 defined, so the order matters here) -# elif defined(_WIN32) -# include // alloca -# if !defined(alloca) -# define alloca _alloca // for clang with MS Codegen -# endif //alloca -# elif defined(__GLIBC__) || defined(__sun) -# include // alloca -# else -# include // alloca -# endif -#endif //alloca - - -namespace ImGui { - -namespace DrawListHelper { - -// Two main additions: -// 1) PathFillAndStroke in the same method (so that we don't have to build the path twice) -// 2) VerticalGradient: looks good -inline static void GetVerticalGradientTopAndBottomColors(ImU32 c,float fillColorGradientDeltaIn0_05,ImU32& tc,ImU32& bc) { - if (fillColorGradientDeltaIn0_05==0) {tc=bc=c;return;} - -# define CACHE_LAST_GRADIENT // Maybe useless, but when displaying a lot of tabs we can save some calls... -# ifdef CACHE_LAST_GRADIENT - //static unsigned int dbg = 0; - static ImU32 cacheColorIn=0;static float cacheGradientIn=0.f;static ImU32 cacheTopColorOut=0;static ImU32 cacheBottomColorOut=0; - if (cacheColorIn==c && cacheGradientIn==fillColorGradientDeltaIn0_05) {tc=cacheTopColorOut;bc=cacheBottomColorOut; - //fprintf(stderr,"cached: %u\n",dbg++); - return;} - cacheColorIn=c;cacheGradientIn=fillColorGradientDeltaIn0_05; -# endif //CACHE_LAST_GRADIENT - - const bool negative = (fillColorGradientDeltaIn0_05<0); - if (negative) fillColorGradientDeltaIn0_05=-fillColorGradientDeltaIn0_05; - if (fillColorGradientDeltaIn0_05>0.5f) fillColorGradientDeltaIn0_05=0.5f; - - // New code: - //#define IM_COL32(R,G,B,A) (((ImU32)(A)<>IM_COL32_R_SHIFT); // The cast should reset upper bits (as far as I hope) - const int G = (unsigned char) (c>>IM_COL32_G_SHIFT); - const int B = (unsigned char) (c>>IM_COL32_B_SHIFT); - const int A = (unsigned char) (c>>IM_COL32_A_SHIFT); - - int r = R+fcgi, g = G+fcgi, b = B+fcgi; - if (r>255) r=255;if (g>255) g=255;if (b>255) b=255; - if (negative) bc = IM_COL32(r,g,b,A); else tc = IM_COL32(r,g,b,A); - - r = R-fcgi; g = G-fcgi; b = B-fcgi; - if (r<0) r=0;if (g<0) g=0;if (b<0) b=0; - if (negative) tc = IM_COL32(r,g,b,A); else bc = IM_COL32(r,g,b,A); - - // Old legacy code (to remove)... [However here we lerp alpha too...] - /*// Can we do it without the double conversion ImU32 -> ImVec4 -> ImU32 ? - const ImVec4 cf = ColorConvertU32ToFloat4(c); - ImVec4 tmp(cf.x+fillColorGradientDeltaIn0_05,cf.y+fillColorGradientDeltaIn0_05,cf.z+fillColorGradientDeltaIn0_05,cf.w+fillColorGradientDeltaIn0_05); - if (tmp.x>1.f) tmp.x=1.f;if (tmp.y>1.f) tmp.y=1.f;if (tmp.z>1.f) tmp.z=1.f;if (tmp.w>1.f) tmp.w=1.f; - if (negative) bc = ColorConvertFloat4ToU32(tmp); else tc = ColorConvertFloat4ToU32(tmp); - tmp=ImVec4(cf.x-fillColorGradientDeltaIn0_05,cf.y-fillColorGradientDeltaIn0_05,cf.z-fillColorGradientDeltaIn0_05,cf.w-fillColorGradientDeltaIn0_05); - if (tmp.x<0.f) tmp.x=0.f;if (tmp.y<0.f) tmp.y=0.f;if (tmp.z<0.f) tmp.z=0.f;if (tmp.w<0.f) tmp.w=0.f; - if (negative) tc = ColorConvertFloat4ToU32(tmp); else bc = ColorConvertFloat4ToU32(tmp);*/ - -# ifdef CACHE_LAST_GRADIENT - cacheTopColorOut=tc;cacheBottomColorOut=bc; - //fprintf(stderr,"uncached: %u\n",dbg++); -# undef CACHE_LAST_GRADIENT // Mandatory -# endif //CACHE_LAST_GRADIENT -} -// Can we do it from ImU32 ct and cb, without conversion to ImVec4 ? -inline static ImU32 GetVerticalGradient(const ImVec4& ct,const ImVec4& cb,float DH,float H) { - IM_ASSERT(H!=0); - const float fa = DH/H; - const float fc = (1.f-fa); - return ColorConvertFloat4ToU32(ImVec4( - ct.x * fc + cb.x * fa, - ct.y * fc + cb.y * fa, - ct.z * fc + cb.z * fa, - ct.w * fc + cb.w * fa) - ); -} -void ImDrawListAddConvexPolyFilledWithVerticalGradient(ImDrawList *dl, const ImVec2 *points, const int points_count, ImU32 colTop, ImU32 colBot,float miny=-1.f,float maxy=-1.f) -{ - if (!dl) return; - if (colTop==colBot) { - dl->AddConvexPolyFilled(points,points_count,colTop); - return; - } - const ImVec2 uv = GImGui->DrawListSharedData.TexUvWhitePixel; - const bool anti_aliased = GImGui->Style.AntiAliasedFill; - //if (ImGui::GetIO().KeyCtrl) anti_aliased = false; // Debug - - int height=0; - if (miny<=0 || maxy<=0) { - const float max_float = 999999999999999999.f; - miny=max_float;maxy=-max_float; - for (int i = 0; i < points_count; i++) { - const float h = points[i].y; - if (h < miny) miny = h; - else if (h > maxy) maxy = h; - } - } - height = maxy-miny; - const ImVec4 colTopf = ColorConvertU32ToFloat4(colTop); - const ImVec4 colBotf = ColorConvertU32ToFloat4(colBot); - - - if (anti_aliased) - { - // Anti-aliased Fill - const float AA_SIZE = 1.0f; - - const ImVec4 colTransTopf(colTopf.x,colTopf.y,colTopf.z,0.f); - const ImVec4 colTransBotf(colBotf.x,colBotf.y,colBotf.z,0.f); - const int idx_count = (points_count-2)*3 + points_count*6; - const int vtx_count = (points_count*2); - dl->PrimReserve(idx_count, vtx_count); - - // Add indexes for fill - unsigned int vtx_inner_idx = dl->_VtxCurrentIdx; - unsigned int vtx_outer_idx = dl->_VtxCurrentIdx+1; - for (int i = 2; i < points_count; i++) - { - dl->_IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx); dl->_IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx+((i-1)<<1)); dl->_IdxWritePtr[2] = (ImDrawIdx)(vtx_inner_idx+(i<<1)); - dl->_IdxWritePtr += 3; - } - - // Compute normals - ImVec2* temp_normals = (ImVec2*)alloca(points_count * sizeof(ImVec2)); - for (int i0 = points_count-1, i1 = 0; i1 < points_count; i0 = i1++) - { - const ImVec2& p0 = points[i0]; - const ImVec2& p1 = points[i1]; - ImVec2 diff = p1 - p0; - diff *= ImInvLength(diff, 1.0f); - temp_normals[i0].x = diff.y; - temp_normals[i0].y = -diff.x; - } - - for (int i0 = points_count-1, i1 = 0; i1 < points_count; i0 = i1++) - { - // Average normals - const ImVec2& n0 = temp_normals[i0]; - const ImVec2& n1 = temp_normals[i1]; - ImVec2 dm = (n0 + n1) * 0.5f; - float dmr2 = dm.x*dm.x + dm.y*dm.y; - if (dmr2 > 0.000001f) - { - float scale = 1.0f / dmr2; - if (scale > 100.0f) scale = 100.0f; - dm *= scale; - } - dm *= AA_SIZE * 0.5f; - - // Add vertices - //_VtxWritePtr[0].pos = (points[i1] - dm); _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; // Inner - //_VtxWritePtr[1].pos = (points[i1] + dm); _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col_trans; // Outer - dl->_VtxWritePtr[0].pos = (points[i1] - dm); dl->_VtxWritePtr[0].uv = uv; dl->_VtxWritePtr[0].col = GetVerticalGradient(colTopf,colBotf,points[i1].y-miny,height); // Inner - dl->_VtxWritePtr[1].pos = (points[i1] + dm); dl->_VtxWritePtr[1].uv = uv; dl->_VtxWritePtr[1].col = GetVerticalGradient(colTransTopf,colTransBotf,points[i1].y-miny,height); // Outer - dl->_VtxWritePtr += 2; - - // Add indexes for fringes - dl->_IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx+(i1<<1)); dl->_IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx+(i0<<1)); dl->_IdxWritePtr[2] = (ImDrawIdx)(vtx_outer_idx+(i0<<1)); - dl->_IdxWritePtr[3] = (ImDrawIdx)(vtx_outer_idx+(i0<<1)); dl->_IdxWritePtr[4] = (ImDrawIdx)(vtx_outer_idx+(i1<<1)); dl->_IdxWritePtr[5] = (ImDrawIdx)(vtx_inner_idx+(i1<<1)); - dl->_IdxWritePtr += 6; - } - dl->_VtxCurrentIdx += (ImDrawIdx)vtx_count; - } - else - { - // Non Anti-aliased Fill - const int idx_count = (points_count-2)*3; - const int vtx_count = points_count; - dl->PrimReserve(idx_count, vtx_count); - for (int i = 0; i < vtx_count; i++) - { - //_VtxWritePtr[0].pos = points[i]; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; - dl->_VtxWritePtr[0].pos = points[i]; dl->_VtxWritePtr[0].uv = uv; dl->_VtxWritePtr[0].col = GetVerticalGradient(colTopf,colBotf,points[i].y-miny,height); - dl->_VtxWritePtr++; - } - for (int i = 2; i < points_count; i++) - { - dl->_IdxWritePtr[0] = (ImDrawIdx)(dl->_VtxCurrentIdx); dl->_IdxWritePtr[1] = (ImDrawIdx)(dl->_VtxCurrentIdx+i-1); dl->_IdxWritePtr[2] = (ImDrawIdx)(dl->_VtxCurrentIdx+i); - dl->_IdxWritePtr += 3; - } - dl->_VtxCurrentIdx += (ImDrawIdx)vtx_count; - } -} -void ImDrawListPathFillWithVerticalGradientAndStroke(ImDrawList *dl, const ImU32 &fillColorTop, const ImU32 &fillColorBottom, const ImU32 &strokeColor, bool strokeClosed, float strokeThickness,float miny,float maxy) { - if (!dl) return; - if (fillColorTop==fillColorBottom) dl->AddConvexPolyFilled(dl->_Path.Data,dl->_Path.Size, fillColorTop); - else if ((fillColorTop & IM_COL32_A_MASK) != 0 || (fillColorBottom & IM_COL32_A_MASK) != 0) ImDrawListAddConvexPolyFilledWithVerticalGradient(dl, dl->_Path.Data, dl->_Path.Size, fillColorTop, fillColorBottom,miny,maxy); - if ((strokeColor& IM_COL32_A_MASK)!= 0 && strokeThickness>0) dl->AddPolyline(dl->_Path.Data, dl->_Path.Size, strokeColor, strokeClosed, strokeThickness); - dl->PathClear(); -} -void ImDrawListPathFillAndStroke(ImDrawList *dl, const ImU32 &fillColor, const ImU32 &strokeColor, bool strokeClosed, float strokeThickness) { - if (!dl) return; - if ((fillColor & IM_COL32_A_MASK) != 0) dl->AddConvexPolyFilled(dl->_Path.Data, dl->_Path.Size, fillColor); - if ((strokeColor& IM_COL32_A_MASK)!= 0 && strokeThickness>0) dl->AddPolyline(dl->_Path.Data, dl->_Path.Size, strokeColor, strokeClosed, strokeThickness); - dl->PathClear(); -} -void ImDrawListAddRect(ImDrawList *dl, const ImVec2 &a, const ImVec2 &b, const ImU32 &fillColor, const ImU32 &strokeColor, float rounding, int rounding_corners, float strokeThickness) { - if (!dl || (((fillColor & IM_COL32_A_MASK) == 0) && ((strokeColor & IM_COL32_A_MASK) == 0))) return; - dl->PathRect(a, b, rounding, rounding_corners); - ImDrawListPathFillAndStroke(dl,fillColor,strokeColor,true,strokeThickness); -} -void ImDrawListAddRectWithVerticalGradient(ImDrawList *dl, const ImVec2 &a, const ImVec2 &b, const ImU32 &fillColorTop, const ImU32 &fillColorBottom, const ImU32 &strokeColor, float rounding, int rounding_corners, float strokeThickness) { - if (!dl || (((fillColorTop & IM_COL32_A_MASK) == 0) && ((fillColorBottom & IM_COL32_A_MASK) == 0) && ((strokeColor & IM_COL32_A_MASK) == 0))) return; - if (rounding==0.f || rounding_corners==0) { - dl->AddRectFilledMultiColor(a,b,fillColorTop,fillColorTop,fillColorBottom,fillColorBottom); // Huge speedup! - if ((strokeColor& IM_COL32_A_MASK)!= 0 && strokeThickness>0.f) { - dl->PathRect(a, b, rounding, rounding_corners); - dl->AddPolyline(dl->_Path.Data, dl->_Path.Size, strokeColor, true, strokeThickness); - dl->PathClear(); - } - } - else { - dl->PathRect(a, b, rounding, rounding_corners); - ImDrawListPathFillWithVerticalGradientAndStroke(dl,fillColorTop,fillColorBottom,strokeColor,true,strokeThickness,a.y,b.y); - } -} -void ImDrawListAddRectWithVerticalGradient(ImDrawList *dl, const ImVec2 &a, const ImVec2 &b, const ImU32 &fillColor, float fillColorGradientDeltaIn0_05, const ImU32 &strokeColor, float rounding, int rounding_corners, float strokeThickness) -{ - ImU32 fillColorTop,fillColorBottom;GetVerticalGradientTopAndBottomColors(fillColor,fillColorGradientDeltaIn0_05,fillColorTop,fillColorBottom); - ImDrawListAddRectWithVerticalGradient(dl,a,b,fillColorTop,fillColorBottom,strokeColor,rounding,rounding_corners,strokeThickness); -} - -void ImDrawListAddConvexPolyFilledWithHorizontalGradient(ImDrawList *dl, const ImVec2 *points, const int points_count, ImU32 colLeft, ImU32 colRight, float minx, float maxx) -{ - if (!dl) return; - if (colLeft==colRight) { - dl->AddConvexPolyFilled(points,points_count,colLeft); - return; - } - const ImVec2 uv = GImGui->DrawListSharedData.TexUvWhitePixel; - const bool anti_aliased = GImGui->Style.AntiAliasedFill; - //if (ImGui::GetIO().KeyCtrl) anti_aliased = false; // Debug - - int width=0; - if (minx<=0 || maxx<=0) { - const float max_float = 999999999999999999.f; - minx=max_float;maxx=-max_float; - for (int i = 0; i < points_count; i++) { - const float w = points[i].x; - if (w < minx) minx = w; - else if (w > maxx) maxx = w; - } - } - width = maxx-minx; - const ImVec4 colLeftf = ColorConvertU32ToFloat4(colLeft); - const ImVec4 colRightf = ColorConvertU32ToFloat4(colRight); - - - if (anti_aliased) - { - // Anti-aliased Fill - const float AA_SIZE = 1.0f; - - const ImVec4 colTransLeftf(colLeftf.x,colLeftf.y,colLeftf.z,0.f); - const ImVec4 colTransRightf(colRightf.x,colRightf.y,colRightf.z,0.f); - const int idx_count = (points_count-2)*3 + points_count*6; - const int vtx_count = (points_count*2); - dl->PrimReserve(idx_count, vtx_count); - - // Add indexes for fill - unsigned int vtx_inner_idx = dl->_VtxCurrentIdx; - unsigned int vtx_outer_idx = dl->_VtxCurrentIdx+1; - for (int i = 2; i < points_count; i++) - { - dl->_IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx); dl->_IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx+((i-1)<<1)); dl->_IdxWritePtr[2] = (ImDrawIdx)(vtx_inner_idx+(i<<1)); - dl->_IdxWritePtr += 3; - } - - // Compute normals - ImVec2* temp_normals = (ImVec2*)alloca(points_count * sizeof(ImVec2)); - for (int i0 = points_count-1, i1 = 0; i1 < points_count; i0 = i1++) - { - const ImVec2& p0 = points[i0]; - const ImVec2& p1 = points[i1]; - ImVec2 diff = p1 - p0; - diff *= ImInvLength(diff, 1.0f); - temp_normals[i0].x = diff.y; - temp_normals[i0].y = -diff.x; - } - - for (int i0 = points_count-1, i1 = 0; i1 < points_count; i0 = i1++) - { - // Average normals - const ImVec2& n0 = temp_normals[i0]; - const ImVec2& n1 = temp_normals[i1]; - ImVec2 dm = (n0 + n1) * 0.5f; - float dmr2 = dm.x*dm.x + dm.y*dm.y; - if (dmr2 > 0.000001f) - { - float scale = 1.0f / dmr2; - if (scale > 100.0f) scale = 100.0f; - dm *= scale; - } - dm *= AA_SIZE * 0.5f; - - // Add vertices - //_VtxWritePtr[0].pos = (points[i1] - dm); _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; // Inner - //_VtxWritePtr[1].pos = (points[i1] + dm); _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col_trans; // Outer - dl->_VtxWritePtr[0].pos = (points[i1] - dm); dl->_VtxWritePtr[0].uv = uv; dl->_VtxWritePtr[0].col = GetVerticalGradient(colLeftf,colRightf,points[i1].x-minx,width); // Inner - dl->_VtxWritePtr[1].pos = (points[i1] + dm); dl->_VtxWritePtr[1].uv = uv; dl->_VtxWritePtr[1].col = GetVerticalGradient(colTransLeftf,colTransRightf,points[i1].x-minx,width); // Outer - dl->_VtxWritePtr += 2; - - // Add indexes for fringes - dl->_IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx+(i1<<1)); dl->_IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx+(i0<<1)); dl->_IdxWritePtr[2] = (ImDrawIdx)(vtx_outer_idx+(i0<<1)); - dl->_IdxWritePtr[3] = (ImDrawIdx)(vtx_outer_idx+(i0<<1)); dl->_IdxWritePtr[4] = (ImDrawIdx)(vtx_outer_idx+(i1<<1)); dl->_IdxWritePtr[5] = (ImDrawIdx)(vtx_inner_idx+(i1<<1)); - dl->_IdxWritePtr += 6; - } - dl->_VtxCurrentIdx += (ImDrawIdx)vtx_count; - } - else - { - // Non Anti-aliased Fill - const int idx_count = (points_count-2)*3; - const int vtx_count = points_count; - dl->PrimReserve(idx_count, vtx_count); - for (int i = 0; i < vtx_count; i++) - { - //_VtxWritePtr[0].pos = points[i]; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; - dl->_VtxWritePtr[0].pos = points[i]; dl->_VtxWritePtr[0].uv = uv; dl->_VtxWritePtr[0].col = GetVerticalGradient(colLeftf,colRightf,points[i].x-minx,width); - dl->_VtxWritePtr++; - } - for (int i = 2; i < points_count; i++) - { - dl->_IdxWritePtr[0] = (ImDrawIdx)(dl->_VtxCurrentIdx); dl->_IdxWritePtr[1] = (ImDrawIdx)(dl->_VtxCurrentIdx+i-1); dl->_IdxWritePtr[2] = (ImDrawIdx)(dl->_VtxCurrentIdx+i); - dl->_IdxWritePtr += 3; - } - dl->_VtxCurrentIdx += (ImDrawIdx)vtx_count; - } -} -void ImDrawListPathFillWithHorizontalGradientAndStroke(ImDrawList *dl, const ImU32 &fillColorLeft, const ImU32 &fillColorRight, const ImU32 &strokeColor, bool strokeClosed, float strokeThickness, float minx, float maxx) { - if (!dl) return; - if (fillColorLeft==fillColorRight) dl->AddConvexPolyFilled(dl->_Path.Data,dl->_Path.Size, fillColorLeft); - else if ((fillColorLeft & IM_COL32_A_MASK) != 0 || (fillColorRight & IM_COL32_A_MASK) != 0) ImDrawListAddConvexPolyFilledWithHorizontalGradient(dl, dl->_Path.Data, dl->_Path.Size, fillColorLeft, fillColorRight,minx,maxx); - if ((strokeColor& IM_COL32_A_MASK)!= 0 && strokeThickness>0) dl->AddPolyline(dl->_Path.Data, dl->_Path.Size, strokeColor, strokeClosed, strokeThickness); - dl->PathClear(); -} -void ImDrawListAddRectWithHorizontalGradient(ImDrawList *dl, const ImVec2 &a, const ImVec2 &b, const ImU32 &fillColorLeft, const ImU32 &fillColoRight, const ImU32 &strokeColor, float rounding, int rounding_corners, float strokeThickness) { - if (!dl || (((fillColorLeft & IM_COL32_A_MASK) == 0) && ((fillColoRight & IM_COL32_A_MASK) == 0) && ((strokeColor & IM_COL32_A_MASK) == 0))) return; - if (rounding==0.f || rounding_corners==0) { - dl->AddRectFilledMultiColor(a,b,fillColorLeft,fillColoRight,fillColoRight,fillColorLeft); // Huge speedup! - if ((strokeColor& IM_COL32_A_MASK)!= 0 && strokeThickness>0.f) { - dl->PathRect(a, b, rounding, rounding_corners); - dl->AddPolyline(dl->_Path.Data, dl->_Path.Size, strokeColor, true, strokeThickness); - dl->PathClear(); - } - } - else { - dl->PathRect(a, b, rounding, rounding_corners); - ImDrawListPathFillWithHorizontalGradientAndStroke(dl,fillColorLeft,fillColoRight,strokeColor,true,strokeThickness,a.x,b.x); - } -} -void ImDrawListAddRectWithHorizontalGradient(ImDrawList *dl, const ImVec2 &a, const ImVec2 &b, const ImU32 &fillColor, float fillColorGradientDeltaIn0_05, const ImU32 &strokeColor, float rounding, int rounding_corners, float strokeThickness) { - ImU32 fillColorTop,fillColorBottom;GetVerticalGradientTopAndBottomColors(fillColor,fillColorGradientDeltaIn0_05,fillColorTop,fillColorBottom); - ImDrawListAddRectWithHorizontalGradient(dl,a,b,fillColorTop,fillColorBottom,strokeColor,rounding,rounding_corners,strokeThickness); -} - -} //DrawListHelper - -namespace VerticalTextHelper { -ImVec2 CalcVerticalTextSize(const char* text, const char* text_end = NULL, bool hide_text_after_double_hash = false, float wrap_width = -1.0f) { - const ImVec2 rv = ImGui::CalcTextSize(text,text_end,hide_text_after_double_hash,wrap_width); - return ImVec2(rv.y,rv.x); -} -void RenderTextVertical(const ImFont* font,ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end=NULL, float wrap_width=0.0f, bool cpu_fine_clip=false, bool rotateCCW=false) { - if (!text_end) text_end = text_begin + strlen(text_begin); - - const float scale = size / font->FontSize; - const float line_height = font->FontSize * scale; - - // Align to be pixel perfect - pos.x = (float)(int)pos.x;// + (rotateCCW ? (font->FontSize-font->DisplayOffset.y) : 0); // Not sure it's correct - pos.y = (float)(int)pos.y + font->DisplayOffset.x; - - - float x = pos.x; - float y = pos.y; - if (x > clip_rect.z) - return; - - const bool word_wrap_enabled = (wrap_width > 0.0f); - const char* word_wrap_eol = NULL; - const float y_dir = rotateCCW ? -1.f : 1.f; - - // Skip non-visible lines - const char* s = text_begin; - if (!word_wrap_enabled && y + line_height < clip_rect.y) - while (s < text_end && *s != '\n') // Fast-forward to next line - s++; - - // Reserve vertices for remaining worse case (over-reserving is useful and easily amortized) - const int vtx_count_max = (int)(text_end - s) * 4; - const int idx_count_max = (int)(text_end - s) * 6; - const int idx_expected_size = draw_list->IdxBuffer.Size + idx_count_max; - draw_list->PrimReserve(idx_count_max, vtx_count_max); - - ImDrawVert* vtx_write = draw_list->_VtxWritePtr; - ImDrawIdx* idx_write = draw_list->_IdxWritePtr; - unsigned int vtx_current_idx = draw_list->_VtxCurrentIdx; - float x1=0.f,x2=0.f,y1=0.f,y2=0.f; - - while (s < text_end) - { - if (word_wrap_enabled) - { - // Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature. - if (!word_wrap_eol) - { - word_wrap_eol = font->CalcWordWrapPositionA(scale, s, text_end, wrap_width - (y - pos.y)); - if (word_wrap_eol == s) // Wrap_width is too small to fit anything. Force displaying 1 character to minimize the height discontinuity. - word_wrap_eol++; // +1 may not be a character start point in UTF-8 but it's ok because we use s >= word_wrap_eol below - } - - if (s >= word_wrap_eol) - { - y = pos.y; - x += line_height; - word_wrap_eol = NULL; - - // Wrapping skips upcoming blanks - while (s < text_end) - { - const char c = *s; - if (ImCharIsBlankA(c)) { s++; } else if (c == '\n') { s++; break; } else { break; } - } - continue; - } - } - - // Decode and advance source - unsigned int c = (unsigned int)*s; - if (c < 0x80) - { - s += 1; - } - else - { - s += ImTextCharFromUtf8(&c, s, text_end); - if (c == 0) - break; - } - - if (c < 32) - { - if (c == '\n') - { - y = pos.y; - x += line_height; - - if (x > clip_rect.z) - break; - if (!word_wrap_enabled && x + line_height < clip_rect.x) - while (s < text_end && *s != '\n') // Fast-forward to next line - s++; - continue; - } - if (c == '\r') - continue; - } - - float char_width = 0.0f; - if (const ImFontGlyph* glyph = font->FindGlyph((unsigned short)c)) - { - char_width = glyph->AdvanceX * scale; - //fprintf(stderr,"%c [%1.4f]\n",(unsigned char) glyph->Codepoint,char_width); - - // Arbitrarily assume that both space and tabs are empty glyphs as an optimization - if (c != ' ' && c != '\t') - { - // We don't do a second finer clipping test on the Y axis as we've already skipped anything before clip_rect.y and exit once we pass clip_rect.w - if (!rotateCCW) { - x1 = x + (font->FontSize-glyph->Y1) * scale; - x2 = x + (font->FontSize-glyph->Y0) * scale; - y1 = y + glyph->X0 * scale; - y2 = y + glyph->X1 * scale; - } - else { - x1 = x + glyph->Y0 * scale; - x2 = x + glyph->Y1 * scale; - y1 = y - glyph->X1 * scale; - y2 = y - glyph->X0 * scale; - } - if (y1 <= clip_rect.w && y2 >= clip_rect.y) - { - // Render a character - float u1 = glyph->U0; - float v1 = glyph->V0; - float u2 = glyph->U1; - float v2 = glyph->V1; - - // CPU side clipping used to fit text in their frame when the frame is too small. Only does clipping for axis aligned quads. - if (cpu_fine_clip) - { - if (x1 < clip_rect.x) - { - u1 = u1 + (1.0f - (x2 - clip_rect.x) / (x2 - x1)) * (u2 - u1); - x1 = clip_rect.x; - } - if (y1 < clip_rect.y) - { - v1 = v1 + (1.0f - (y2 - clip_rect.y) / (y2 - y1)) * (v2 - v1); - y1 = clip_rect.y; - } - if (x2 > clip_rect.z) - { - u2 = u1 + ((clip_rect.z - x1) / (x2 - x1)) * (u2 - u1); - x2 = clip_rect.z; - } - if (y2 > clip_rect.w) - { - v2 = v1 + ((clip_rect.w - y1) / (y2 - y1)) * (v2 - v1); - y2 = clip_rect.w; - } - if (x1 >= x2) - { - y += char_width*y_dir; - continue; - } - } - - // We are NOT calling PrimRectUV() here because non-inlined causes too much overhead in a debug build. - // Inlined here: - { - idx_write[0] = (ImDrawIdx)(vtx_current_idx); idx_write[1] = (ImDrawIdx)(vtx_current_idx+1); idx_write[2] = (ImDrawIdx)(vtx_current_idx+2); - idx_write[3] = (ImDrawIdx)(vtx_current_idx); idx_write[4] = (ImDrawIdx)(vtx_current_idx+2); idx_write[5] = (ImDrawIdx)(vtx_current_idx+3); - vtx_write[0].col = vtx_write[1].col = vtx_write[2].col = vtx_write[3].col = col; - vtx_write[0].pos.x = x1; vtx_write[0].pos.y = y1; - vtx_write[1].pos.x = x2; vtx_write[1].pos.y = y1; - vtx_write[2].pos.x = x2; vtx_write[2].pos.y = y2; - vtx_write[3].pos.x = x1; vtx_write[3].pos.y = y2; - - if (rotateCCW) { - vtx_write[0].uv.x = u2; vtx_write[0].uv.y = v1; - vtx_write[1].uv.x = u2; vtx_write[1].uv.y = v2; - vtx_write[2].uv.x = u1; vtx_write[2].uv.y = v2; - vtx_write[3].uv.x = u1; vtx_write[3].uv.y = v1; - } - else { - vtx_write[0].uv.x = u1; vtx_write[0].uv.y = v2; - vtx_write[1].uv.x = u1; vtx_write[1].uv.y = v1; - vtx_write[2].uv.x = u2; vtx_write[2].uv.y = v1; - vtx_write[3].uv.x = u2; vtx_write[3].uv.y = v2; - } - - vtx_write += 4; - vtx_current_idx += 4; - idx_write += 6; - } - } - } - } - - y += char_width*y_dir; - } - - // Give back unused vertices - draw_list->VtxBuffer.resize((int)(vtx_write - draw_list->VtxBuffer.Data)); - draw_list->IdxBuffer.resize((int)(idx_write - draw_list->IdxBuffer.Data)); - draw_list->CmdBuffer[draw_list->CmdBuffer.Size-1].ElemCount -= (idx_expected_size - draw_list->IdxBuffer.Size); - draw_list->_VtxWritePtr = vtx_write; - draw_list->_IdxWritePtr = idx_write; - draw_list->_VtxCurrentIdx = (unsigned int)draw_list->VtxBuffer.Size; -} -void AddTextVertical(ImDrawList* drawList,const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end=NULL, float wrap_width=0.0f, const ImVec4* cpu_fine_clip_rect=NULL,bool rotateCCW = false) { - if ((col & IM_COL32_A_MASK) == 0) - return; - - if (text_end == NULL) - text_end = text_begin + strlen(text_begin); - if (text_begin == text_end) - return; - - // Note: This is one of the few instance of breaking the encapsulation of ImDrawList, as we pull this from ImGui state, but it is just SO useful. - // Might just move Font/FontSize to ImDrawList? - if (font == NULL) - font = GImGui->Font; - if (font_size == 0.0f) - font_size = GImGui->FontSize; - - IM_ASSERT(drawList && font->ContainerAtlas->TexID == drawList->_TextureIdStack.back()); // Use high-level ImGui::PushFont() or low-level ImDrawList::PushTextureId() to change font. - - ImVec4 clip_rect = drawList->_ClipRectStack.back(); - if (cpu_fine_clip_rect) - { - clip_rect.x = ImMax(clip_rect.x, cpu_fine_clip_rect->x); - clip_rect.y = ImMax(clip_rect.y, cpu_fine_clip_rect->y); - clip_rect.z = ImMin(clip_rect.z, cpu_fine_clip_rect->z); - clip_rect.w = ImMin(clip_rect.w, cpu_fine_clip_rect->w); - } - RenderTextVertical(font, drawList, font_size, pos, col, clip_rect, text_begin, text_end, wrap_width, cpu_fine_clip_rect != NULL,rotateCCW); -} -void AddTextVertical(ImDrawList* drawList,const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end=NULL,bool rotateCCW=false) { - AddTextVertical(drawList,GImGui->Font, GImGui->FontSize, pos, col, text_begin, text_end,0.0f,NULL,rotateCCW); -} -void RenderTextVerticalClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align = ImVec2(0.0f,0.0f), const ImVec2* clip_min=NULL, const ImVec2* clip_max=NULL,bool rotateCCW=false) { - // Hide anything after a '##' string - const char* text_display_end = FindRenderedTextEnd(text, text_end); - const int text_len = (int)(text_display_end - text); - if (text_len == 0) - return; - - ImGuiContext& g = *GImGui; - ImGuiWindow* window = GetCurrentWindow(); - - // Perform CPU side clipping for single clipped element to avoid using scissor state - ImVec2 pos = pos_min; - const ImVec2 text_size = text_size_if_known ? *text_size_if_known : CalcVerticalTextSize(text, text_display_end, false, 0.0f); - - if (!clip_max) clip_max = &pos_max; - bool need_clipping = (pos.x + text_size.x >= clip_max->x) || (pos.y + text_size.y >= clip_max->y); - if (!clip_min) clip_min = &pos_min; else need_clipping |= (pos.x < clip_min->x) || (pos.y < clip_min->y); - - // Align - /*if (align & ImGuiAlign_Center) pos.x = ImMax(pos.x, (pos.x + pos_max.x - text_size.x) * 0.5f); - else if (align & ImGuiAlign_Right) pos.x = ImMax(pos.x, pos_max.x - text_size.x); - if (align & ImGuiAlign_VCenter) pos.y = ImMax(pos.y, (pos.y + pos_max.y - text_size.y) * 0.5f);*/ - - //if (align & ImGuiAlign_Center) pos.y = ImMax(pos.y, (pos.y + pos_max.y - text_size.y) * 0.5f); - //else if (align & ImGuiAlign_Right) pos.y = ImMax(pos.y, pos_max.y - text_size.y); - //if (align & ImGuiAlign_VCenter) pos.x = ImMax(pos.x, (pos.x + pos_max.x - text_size.x) * 0.5f); - - // Align whole block (we should defer that to the better rendering function - if (align.x > 0.0f) pos.x = ImMax(pos.x, pos.x + (pos_max.x - pos.x - text_size.x) * align.x); - if (align.y > 0.0f) pos.y = ImMax(pos.y, pos.y + (pos_max.y - pos.y - text_size.y) * align.y); - - - if (rotateCCW) pos.y+=text_size.y; - // Render - if (need_clipping) - { - ImVec4 fine_clip_rect(clip_min->x, clip_min->y, clip_max->x, clip_max->y); - AddTextVertical(window->DrawList,g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, &fine_clip_rect,rotateCCW); - } - else - { - AddTextVertical(window->DrawList,g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, NULL,rotateCCW); - } -} -} - -namespace RevertUpstreamBeginChildCommit { -// That commit [2016/11/06 (1.50)] broke everything! -static bool OldBeginChild(const char* str_id, const ImVec2& size_arg = ImVec2(0,0), ImGuiWindowFlags extra_flags = 0) { - ImGuiWindow* parent_window = ImGui::GetCurrentWindow(); - const ImGuiID id = parent_window->GetID(str_id); - ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_ChildWindow; - - const ImVec2 content_avail = ImGui::GetContentRegionAvail(); - ImVec2 size = ImFloor(size_arg); - const int auto_fit_axises = ((size.x == 0.0f) ? 0x01 : 0x00) | ((size.y == 0.0f) ? 0x02 : 0x00); - if (size.x <= 0.0f) - { - //if (size.x == 0.0f) flags |= ImGuiWindowFlags_ChildWindowAutoFitX; - size.x = ImMax(content_avail.x, 4.0f) - fabsf(size.x); // Arbitrary minimum zero-ish child size of 4.0f (0.0f causing too much issues) - } - if (size.y <= 0.0f) - { - //if (size.y == 0.0f) flags |= ImGuiWindowFlags_ChildWindowAutoFitY; - size.y = ImMax(content_avail.y, 4.0f) - fabsf(size.y); - } - flags |= extra_flags; - - char title[256]; - ImFormatString(title, IM_ARRAYSIZE(title), "%s.%s", parent_window->Name, str_id); - - ImGui::SetNextWindowSize(size); - bool ret = ImGui::Begin(title, NULL, flags); - - ImGuiWindow* child_window = ImGui::GetCurrentWindow(); - child_window->ChildId = id; - child_window->AutoFitChildAxises = auto_fit_axises; - //if (!(parent_window->Flags & ImGuiWindowFlags_ShowBorders)) child_window->Flags &= ~ImGuiWindowFlags_ShowBorders; - - // Process navigation-in immediately so NavInit can run on first frame - if (!(flags & ImGuiWindowFlags_NavFlattened) && (child_window->DC.NavLayerActiveMask != 0 || child_window->DC.NavHasScroll) && GImGui->NavActivateId == id) - { - ImGui::FocusWindow(child_window); - ImGui::NavInitWindow(child_window, false); - ImGui::SetActiveID(id+1, child_window); // Steal ActiveId with a dummy id so that key-press won't activate child item - GImGui->ActiveIdSource = ImGuiInputSource_Nav; - } - - return ret; -} -} // namespace RevertUpstreamBeginChildCommit - -// TabLabelStyle -------------------------------------------------------------------------------------------------- -TabLabelStyle TabLabelStyle::style; -const char* TabLabelStyle::ColorNames[TabLabelStyle::Col_TabLabel_Count] = { -"Col_TabLabel","Col_TabLabelHovered","Col_TabLabelActive","Col_TabLabelBorder","Col_TabLabelText", -"Col_TabLabelSelected","Col_TabLabelSelectedHovered","Col_TabLabelSelectedActive","Col_TabLabelSelectedBorder", -"Col_TabLabelSelectedText","Col_TabLabelCloseButtonHovered","Col_TabLabelCloseButtonActive","Col_TabLabelCloseButtonBorder","Col_TabLabelCloseButtonTextHovered" -}; -const char* TabLabelStyle::FontStyleNames[TabLabelStyle::FONT_STYLE_COUNT]={"FONT_STYLE_NORMAL","FONT_STYLE_BOLD","FONT_STYLE_ITALIC","FONT_STYLE_BOLD_ITALIC"}; -const char* TabLabelStyle::TabStateNames[TabLabelStyle::TAB_STATE_COUNT]={"TAB_STATE_NORMAL","TAB_STATE_SELECTED","TAB_STATE_MODIFIED","TAB_STATE_SELECTED_MODIFIED"}; -const ImFont* TabLabelStyle::ImGuiFonts[TabLabelStyle::FONT_STYLE_COUNT]={NULL,NULL,NULL,NULL}; -// These bit operations are probably non-endian independent, but ImGui uses them too and so do I. -inline static ImU32 ColorMergeWithAlpha(ImU32 c,float alphaMult) { - // I'm not sure how to convert these using IM_COL32_A_MASK... - ImU32 alpha = ((float)(c>>24))*alphaMult; - return ((c&0x00FFFFFF)|(alpha<<24)); -} -inline static ImU32 ColorDarken(ImU32 c,float value,float optionalAlphaToSet=-1.f) { - ImVec4 f = ImGui::ColorConvertU32ToFloat4(c); - f.x-=value;if (f.x<0) f.x=0;else if (f.x>1) f.x=1; - f.y-=value;if (f.y<0) f.y=0;else if (f.y>1) f.y=1; - f.z-=value;if (f.z<0) f.z=0;else if (f.z>1) f.z=1; - if (optionalAlphaToSet>=0 && optionalAlphaToSet<=1) f.w = optionalAlphaToSet; - return ImGui::ColorConvertFloat4ToU32(f); -} -inline static ImU32 ColorLighten(ImU32 c,float value,float optionalAlphaToSet=-1.f) {return ColorDarken(c,-value,optionalAlphaToSet);} -// Helper inline methods to save some code in TabLabelStyle::ctr() and ResetTabLabelStyle(...) -inline static void TabLabelStyleSetSelectedTabColors(ImGui::TabLabelStyle& style,const ImColor& tabColor,const ImColor& textColor,const ImColor& borderColor) { - style.colors[TabLabelStyle::Col_TabLabelSelectedActive] = style.colors[TabLabelStyle::Col_TabLabelSelectedHovered] = style.colors[TabLabelStyle::Col_TabLabelSelected] = tabColor; - style.colors[TabLabelStyle::Col_TabLabelSelectedText] = textColor; - style.colors[TabLabelStyle::Col_TabLabelSelectedBorder] = borderColor; -} -inline static void TabLabelStyleSetTabColors(ImGui::TabLabelStyle& style,const ImColor& tabColor,const ImColor& tabColorHovered,const ImColor& textColor,const ImColor& borderColor) { - style.colors[TabLabelStyle::Col_TabLabel] = tabColor; - style.colors[TabLabelStyle::Col_TabLabelHovered] = style.colors[TabLabelStyle::Col_TabLabelActive] = tabColorHovered; - style.colors[TabLabelStyle::Col_TabLabelText] = textColor; - style.colors[TabLabelStyle::Col_TabLabelBorder] = borderColor; -} -inline static void TabLabelStyleSetCloseButtonColors(ImGui::TabLabelStyle& style,const ImColor& hoveredBtnColor=ImColor(166,0,11,255),const ImColor& actveBtnColor=ImColor(206,40,51,255),const ImColor* pHoveredBtnTextColor=NULL,const ImColor* pHoveredBtnBorderColor=NULL) { - style.colors[TabLabelStyle::Col_TabLabelCloseButtonHovered] = hoveredBtnColor; - style.colors[TabLabelStyle::Col_TabLabelCloseButtonActive] = actveBtnColor; - style.colors[TabLabelStyle::Col_TabLabelCloseButtonTextHovered] = (pHoveredBtnTextColor!=NULL) ? (ImU32)(*pHoveredBtnTextColor) : style.colors[TabLabelStyle::Col_TabLabelSelectedText]; - style.colors[TabLabelStyle::Col_TabLabelCloseButtonBorder] = (pHoveredBtnBorderColor!=NULL) ? (ImU32)(*pHoveredBtnBorderColor) : style.colors[TabLabelStyle::Col_TabLabelSelectedBorder]; -} -// End Helper inline methods to save some code in TabLabelStyle::ctr() and ResetTabLabelStyle(...) - -TabLabelStyle::TabLabelStyle() { - - fillColorGradientDeltaIn0_05 = 0.0f;rounding = 6.f;borderWidth = 0.f; - closeButtonRounding = 0.f;closeButtonBorderWidth = 1.f;closeButtonTextWidth = 2.5f;//3.f; - - TabLabelStyleSetSelectedTabColors(*this,ImColor(0.267f,0.282f,0.396f,1.0f),ImColor(0.925f,0.945f,0.957,1.0f),ImColor(0.090f,0.106f,0.157f,0.000f)); - TabLabelStyleSetTabColors(*this,ImColor(0.161f,0.188f,0.204f,1.f),ImColor(0.239f,0.259f,0.275f,1.f),ImColor(0.549f,0.565f,0.576f,0.784f),ColorDarken(colors[Col_TabLabelSelectedBorder],.0225f)); - TabLabelStyleSetCloseButtonColors(*this); - - //for (int i=0;i=1.f && shiftHue==0.f) return; - for (int i = 0; i < TabLabelStyle::Col_TabLabel_Count; i++) { - ImVec4 col = ImGui::ColorConvertU32ToFloat4(style.colors[i]); - float H, S, V; - ImGui::ColorConvertRGBtoHSV( col.x, col.y, col.z, H, S, V ); - if( S <= satThresholdForInvertingLuminance) { V = 1.0 - V; } - if (shiftHue) {H+=shiftHue;if (H>1) H-=1.f;else if (H<0) H+=1.f;} - ImGui::ColorConvertHSVtoRGB( H, S, V, col.x, col.y, col.z ); - style.colors[i] = ImGui::ColorConvertFloat4ToU32(col); - } -} -bool TabLabelStyle::EditFast(TabLabelStyle &s) { - bool rv = false; - static bool resetCurrentStyle = false; - resetCurrentStyle = ImGui::Button("Reset Current Style To: ###TabLabelStyleResetTo"); - ImGui::SameLine(); - static int styleEnumNum = 0; - ImGui::PushItemWidth(135); - ImGui::Combo("###TabLabelStyleEnumCombo",&styleEnumNum,ImGui::GetDefaultTabLabelStyleNames(),(int) ImGuiTabLabelStyle_Count,(int) ImGuiTabLabelStyle_Count); - ImGui::PopItemWidth(); - ImGui::SameLine(); - static float hueShift = 0; - ImGui::PushItemWidth(50); - ImGui::DragFloat("HueShift##tablabelstyleShiftHue",&hueShift,.005f,0,1,"%.2f"); - ImGui::PopItemWidth(); - if (hueShift!=0) { - ImGui::SameLine(); - if (ImGui::SmallButton("reset##tablabelstyleReset")) {hueShift=0.f;} - } - const bool mustInvertColors = ImGui::Button("Invert Colors:##tablabelstyleInvertColors"); - ImGui::SameLine(); - ImGui::PushItemWidth(50); - static float invertColorThreshold = .1f; - ImGui::DragFloat("Saturation Threshold##tablabelstyleLumThres",&invertColorThreshold,.005f,0.f,0.5f,"%.2f"); - ImGui::PopItemWidth(); - if (mustInvertColors) ChangeTabLabelStyleColors(s,invertColorThreshold); - if (resetCurrentStyle) { - ImGui::ResetTabLabelStyle(styleEnumNum,s); - if (hueShift!=0) ChangeTabLabelStyleColors(s,0.f,hueShift); - rv = true; - } - if (ImGui::Button("Invert Selected Look")) {InvertSelectedLook(s);rv=true;} - ImGui::SameLine(); - if (ImGui::Button("Lighten Tab Colors")) {TabLabelStyle::LightenBackground(s,0.05f);rv=true;} - ImGui::SameLine(); - if (ImGui::Button("Darken Tab Colors")) {TabLabelStyle::DarkenBackground(s,0.05f);rv=true;} - return rv; -} -bool TabLabelStyle::Edit(TabLabelStyle &s) { - bool changed = false; - const float dragSpeed = 0.25f; - const char prec[] = "%1.1f"; - ImGui::PushID(&s); - - static bool useSimplifiedInterface = true; - if (ImGui::Button(useSimplifiedInterface ? "Use Complex Interface###TLSInter" : "Use Simple Interface###TLSInter")) useSimplifiedInterface = !useSimplifiedInterface; - ImGui::Separator(); - - if (useSimplifiedInterface) { - changed|=EditFast(s); - ImGui::Separator(); - } - - ImGui::Text("Tab Labels:"); - ImGui::PushItemWidth(50); - changed|=ImGui::DragFloat("fillColorGradientDeltaIn0_05",&s.fillColorGradientDeltaIn0_05,0.01f,0.f,.5f,"%1.3f"); - if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s","Zero gradient (render)s much faster\nwhen \"rounding\" is positive."); - changed|=ImGui::DragFloat("rounding",&s.rounding,dragSpeed,0.0f,16.f,prec); - if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s","Small values render faster\nbut to really speed up gradients\nset this to zero."); - changed|=ImGui::DragFloat("borderWidth",&s.borderWidth,.01f,0.f,5.f,"%1.2f"); - ImGui::Spacing(); - - if (!useSimplifiedInterface) { - changed|=ImGui::DragFloat("closeButtonRounding",&s.closeButtonRounding,dragSpeed,0.0f,16.f,prec); - if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s","I suggest setting this to zero..."); - changed|=ImGui::DragFloat("closeButtonBorderWidth",&s.closeButtonBorderWidth,.01f,0.f,5.f,"%1.2f"); - } - changed|=ImGui::DragFloat("closeButtonTextWidth",&s.closeButtonTextWidth,.01f,0.f,5.f,"%1.2f"); - ImGui::Spacing(); - ImGui::PopItemWidth(); - - //changed|=ImGui::Checkbox("antialiasing",&s.antialiasing); - ImGui::Spacing(); - - ImGui::Text("Colors:"); - ImGui::PushItemWidth(ImGui::GetWindowWidth()*0.5f); - for (int item = 0,itemSz=(int)TabLabelStyle::Col_TabLabel_Count;item=ImGuiTabLabelStyle_Count) return false; - style = TabLabelStyle(); - switch (tabLabelStyleEnum) { - case ImGuiTabLabelStyle_Dark: - style.fillColorGradientDeltaIn0_05 = 0.075f;style.rounding = 0.f;style.borderWidth = 1.f; - TabLabelStyleSetSelectedTabColors(style,ImColor(49,54,58,255),ImColor(210,214,217,255),ImColor(23,27,40,250)); - TabLabelStyleSetTabColors(style,ColorDarken(style.colors[TabLabelStyle::Col_TabLabelSelected],.135f,1.f),ColorLighten(style.colors[TabLabelStyle::Col_TabLabel],.1f,1.f),ImColor(140,144,147,200),ColorDarken(style.colors[TabLabelStyle::Col_TabLabelSelectedBorder],.0225f,1.f)); - TabLabelStyleSetCloseButtonColors(style); - break; - case ImGuiTabLabelStyle_Red: - case ImGuiTabLabelStyle_Green: - case ImGuiTabLabelStyle_Blue: - case ImGuiTabLabelStyle_Yellow: - case ImGuiTabLabelStyle_Orange: - style.fillColorGradientDeltaIn0_05 = 0.075f;style.rounding = 0.f;style.borderWidth = 1.f; - // Colors for ImGuiTabLabelStyle_Red here: - TabLabelStyleSetSelectedTabColors(style,ImColor(0.549f,0.108f,0.071f,1.000f),ImColor(0.863f,1.000f,0.965f,1.000f),ImColor(0.337f,0.125f,0.125f,1.000f)); - TabLabelStyleSetTabColors(style,ImColor(0.337f,0.162f,0.143f,0.981f),ImColor(0.525f,0.206f,0.163f,0.981f),ImColor(0.655f,0.745f,0.718f,0.981f),ImColor(0.537f,0.200f,0.200f,0.881f)); - // Hue shift if necessary here: - if (tabLabelStyleEnum == ImGuiTabLabelStyle_Green) TabLabelStyle::ShiftHue(style,0.32f); - else if (tabLabelStyleEnum == ImGuiTabLabelStyle_Blue) TabLabelStyle::ShiftHue(style,0.62f); - else if (tabLabelStyleEnum == ImGuiTabLabelStyle_Yellow) TabLabelStyle::ShiftHue(style,0.14f); - else if (tabLabelStyleEnum == ImGuiTabLabelStyle_Orange) TabLabelStyle::ShiftHue(style,0.08f); - // set close button colors after shifting hue - TabLabelStyleSetCloseButtonColors(style); - break; - case ImGuiTabLabelStyle_White: { - style.fillColorGradientDeltaIn0_05 = 0.0f;style.rounding = 6.f;style.borderWidth = 1.0f; - TabLabelStyleSetSelectedTabColors(style,ImColor(1.f,1.f,1.f),ImColor(0.059f,0.059f,0.059f,1.f),ImColor(0.090f,0.106f,0.157f,0.706f)); - TabLabelStyleSetTabColors(style,ImColor(0.925f,0.953f,0.969f,1.f),ImColor(1.f,1.f,1.f,1.f),ImColor(0.247f,0.286f,0.294f,0.765f),ImColor(0.067f,0.082f,0.133f,0.353f)); - style.closeButtonBorderWidth = 1.f;style.closeButtonTextWidth = 2.5f;ImColor btc(1.f,1.f,1.f,1.),bbc(0.090f,0.106f,0.157,1.f); - TabLabelStyleSetCloseButtonColors(style,ImColor(0.651f,0.000f,0.047f,0.490f),ImColor(0.949f,0.f,0.067f,1.f),&btc,&bbc); - } - break; - case ImGuiTabLabelStyle_Foxy: - case ImGuiTabLabelStyle_FoxyInverse: - style.fillColorGradientDeltaIn0_05 = 0.f;style.rounding = 6.f;style.borderWidth = 1.f;//0.f - TabLabelStyleSetSelectedTabColors(style,ImColor(213,212,211,255),ImColor(30,26,53,255),ImColor(136,137,135,255)); - TabLabelStyleSetTabColors(style,ImColor(60,59,55,255),ImColor(104,103,100,255),ImColor(223,219,210,255),ImColor(60,59,55,255)); - TabLabelStyleSetCloseButtonColors(style);style.colors[TabLabelStyle::Col_TabLabelCloseButtonBorder] = style.colors[TabLabelStyle::Col_TabLabelCloseButtonHovered]; - if (tabLabelStyleEnum == ImGuiTabLabelStyle_FoxyInverse) { - TabLabelStyle::InvertSelectedLook(style); - style.colors[TabLabelStyle::Col_TabLabelActive] = style.colors[TabLabelStyle::Col_TabLabelHovered] = ImColor(154,153,150,255); - style.colors[TabLabelStyle::Col_TabLabelSelectedActive] = style.colors[TabLabelStyle::Col_TabLabelSelectedHovered] = style.colors[TabLabelStyle::Col_TabLabelSelected] = style.colors[TabLabelStyle::Col_TabLabelSelected]; - } - break; - case ImGuiTabLabelStyle_Tidy: { - style.fillColorGradientDeltaIn0_05 = 0.0f;style.rounding = 6.f;style.borderWidth = 1.5f; - TabLabelStyleSetSelectedTabColors(style,ImColor(0.682f,0.682f,0.682f,0.941f),ImColor(0.000f,0.000f,0.000f,1.000f),ImColor(0.992f,0.992f,0.992f,1.000f)); - TabLabelStyleSetTabColors(style,ImColor(0.212f,0.212f,0.212f,1.000f),ImColor(0.392f,0.392f,0.392f,1.000f),ImColor(0.784f,0.784f,0.784f,1.000f),ImColor(0.541f,0.541f,0.541f,0.588f)); - style.closeButtonBorderWidth = 3.f;style.closeButtonTextWidth = 2.5f;ImColor btc(0.949f,0.949f,0.949f,1.000f),bbc(0.749f,0.749f,0.749f,0.549f); - TabLabelStyleSetCloseButtonColors(style,ImColor(0.651f,0.000f,0.043f,0.608f),ImColor(0.808f,0.157f,0.200f,0.608f),&btc,&bbc); - } - break; - case ImGuiTabLabelStyle_Fancy: { - style.fillColorGradientDeltaIn0_05 = 0.075f;style.rounding = 6.1f;style.borderWidth = 2.040f; - TabLabelStyleSetSelectedTabColors(style,ImColor(0.749f,0.357f,0.000f,1.000f),ImColor(0.875f,1.000f,0.776f,1.000f),ImColor(1.000f,1.000f,0.125f,1.000f)); - TabLabelStyleSetTabColors(style,ImColor(0.529f,0.325f,0.263f,1.000f),ImColor(0.690f,0.451f,0.345f,0.980f),ImColor(0.745f,0.745f,0.745f,1.000f),ImColor(0.580f,0.533f,0.000f,0.729f)); - style.closeButtonBorderWidth = 1.f;style.closeButtonTextWidth = 2.5f;ImColor btc(0.000f,0.000f,0.000f,1.000f),bbc(0.988f,1.000f,0.125f,1.000f); - TabLabelStyleSetCloseButtonColors(style,ImColor(1.000f,0.000f,0.071f,1.000f),ImColor(1.000f,0.200f,0.255f,1.000f),&btc,&bbc); - style.tabWindowLabelShowAreaSeparator=true;style.tabWindowSplitterColor=ImVec4(1.000f,1.000f,0.000f,1.000f);style.tabWindowSplitterSize=6.f; - } - break; - default: - break; - } - - return true; -} -static const char* DefaultTabLabelStyleNames[ImGuiTabLabelStyle_Count]={"Default","Dark","Red","Green","Blue","Yellow","Orange","White","Tidy","Foxy","FoxyInverse","Fancy"}; -const char** GetDefaultTabLabelStyleNames() {return &DefaultTabLabelStyleNames[0];} - - -#if (defined(IMGUIHELPER_H_) && !defined(NO_IMGUIHELPER_SERIALIZATION)) -#ifndef NO_IMGUIHELPER_SERIALIZATION_SAVE -#include "../imguihelper/imguihelper.h" -bool TabLabelStyle::Save(const TabLabelStyle &style, ImGuiHelper::Serializer& s) { - if (!s.isValid()) return false; - - s.save(ImGui::FT_FLOAT,&style.fillColorGradientDeltaIn0_05,"fillColorGradientDeltaIn0_05"); - s.save(ImGui::FT_FLOAT,&style.rounding,"rounding"); - s.save(ImGui::FT_FLOAT,&style.borderWidth,"borderWidth"); - - s.save(ImGui::FT_FLOAT,&style.closeButtonRounding,"closeButtonRounding"); - s.save(ImGui::FT_FLOAT,&style.closeButtonBorderWidth,"closeButtonBorderWidth"); - s.save(ImGui::FT_FLOAT,&style.closeButtonTextWidth,"closeButtonTextWidth"); - - //s.save(&style.antialiasing,"antialiasing"); - - ImVec4 tmpColor(1,1,1,1); - for (int i=0;i3) s.fontStyles[i]=0;break;} - } - break; - case ImGui::FT_BOOL: - //if (strcmp(name,"antialiasing")==0) s.antialiasing = *((bool*)pValue); - //else - if (strcmp(name,"tabWindowLabelShowAreaSeparator")==0) s.tabWindowLabelShowAreaSeparator = *((bool*)pValue); - break; - case ImGui::FT_COLOR: - if (strcmp(name,"tabWindowLabelBackgroundColor")==0) s.tabWindowLabelBackgroundColor = ImColor(tmp); - else if (strcmp(name,"tabWindowSplitterColor")==0) s.tabWindowSplitterColor = ImColor(tmp); - else { - for (int i=0;iStyle.Alpha. - // I guess there's some per-window alpha I have to multiply... - static TabLabelStyle S; - static int frameCnt=-1; - if (frameCnt!=ImGui::GetFrameCount()) { - frameCnt=ImGui::GetFrameCount(); - const float alpha = GImGui->Style.Alpha * GImGui->Style.Colors[ImGuiCol_WindowBg].w; - S = TabLabelStyle::style; - for (int i=0;iSkipItems && !isFakeControl) return false; - - //ImGuiContext& g = *GImGui; - const ImGuiStyle& style = ImGui::GetStyle(); - const TabLabelStyle& tabStyle = pOptionalStyleToUseIn ? *pOptionalStyleToUseIn : TabLabelStyle::Get(); - const ImGuiID id = isFakeControl ? 0 : window->GetID(label); - if (textOverrideIn) label = textOverrideIn; - - if (!fontOverride) fontOverride = (ImFont*) (selected ? TabLabelStyle::ImGuiFonts[tabStyle.fontStyles[TabLabelStyle::TAB_STATE_SELECTED]] : TabLabelStyle::ImGuiFonts[tabStyle.fontStyles[TabLabelStyle::TAB_STATE_NORMAL]]); - if (fontOverride) ImGui::PushFont(fontOverride); - static ImVec2 staticLabelSize(0,0); - ImVec2 label_size(0,0); - if (!privateReuseLastCalculatedLabelSizeDoNotUse) label_size = staticLabelSize = ImGui::CalcTextSize(label, NULL, true); - else label_size = staticLabelSize; - - ImVec2 pos = window ? window->DC.CursorPos : ImVec2(0,0); - if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrentLineTextBaseOffset) pos.y += window->DC.CurrentLineTextBaseOffset - style.FramePadding.y; - ImVec2 size(label_size.x + (style.FramePadding.x+tabStyle.borderWidth) * 2.0f, label_size.y + (style.FramePadding.y+tabStyle.borderWidth) * 2.0f); - float btnWidth = label_size.y*0.75f,btnSpacingX = label_size.y*0.25f; - float extraWidthForBtn = hasCloseButton ? (btnSpacingX*2.f+btnWidth) : 0; - if (hasCloseButton) size.x+=extraWidthForBtn; - if (pJustReturnItsSizeHereOut) {*pJustReturnItsSizeHereOut=size;if (fontOverride) ImGui::PopFont();return false;} - - const ImRect bb(pOptionalJustDrawTabButtonGraphicsUnderMouseWithThisOffset ? *pOptionalJustDrawTabButtonGraphicsUnderMouseWithThisOffset : pos, - (pOptionalJustDrawTabButtonGraphicsUnderMouseWithThisOffset ? *pOptionalJustDrawTabButtonGraphicsUnderMouseWithThisOffset : pos) + size); - if (!pOptionalJustDrawTabButtonGraphicsUnderMouseWithThisOffset) { - ItemSize(bb, style.FramePadding.y); - if (!ItemAdd(bb, id)) {if (fontOverride) ImGui::PopFont();return false;} - } - - //if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat) flags |= ImGuiButtonFlags_Repeat; // What's this ? - bool hovered=false, held=false; - bool pressed = isFakeControl ? false : ButtonBehavior(bb, id, &hovered, &held, flags); - bool btnHovered = false; - bool btnPressed = false; - ImVec2 startBtn(0,0),endBtn(0,0); - if (hasCloseButton) { - startBtn = ImVec2(bb.Max.x-extraWidthForBtn+btnSpacingX*0.5f,bb.Min.y+(size.y-btnWidth)*0.5f); - endBtn = ImVec2(startBtn.x+btnWidth,startBtn.y+btnWidth); - if (!isFakeControl) { - btnHovered = hovered && ImGui::IsMouseHoveringRect(startBtn,endBtn); - btnPressed = pressed && btnHovered; - if (btnPressed) pressed = false; - if (pCloseButtonHovered) *pCloseButtonHovered = btnHovered; - if (pCloseButtonPressedOut) * pCloseButtonPressedOut = btnPressed; - } - } - if (pHoveredOut) *pHoveredOut = hovered && !btnHovered; // We may choose not to return "hovered" when the close btn is hovered. - if (forceActiveColorLook) {hovered = held = true;} - - // Render - - const ImU32 col = (hovered && !btnHovered && held) ? tabStyle.colors[selected ? TabLabelStyle::Col_TabLabelSelectedActive : TabLabelStyle::Col_TabLabelActive] : (hovered && !btnHovered) ? tabStyle.colors[selected ? TabLabelStyle::Col_TabLabelSelectedHovered : TabLabelStyle::Col_TabLabelHovered] : tabStyle.colors[selected ? TabLabelStyle::Col_TabLabelSelected : TabLabelStyle::Col_TabLabel]; - const ImU32 colText = tabStyle.colors[selected ? TabLabelStyle::Col_TabLabelSelectedText : TabLabelStyle::Col_TabLabelText]; - - if (!drawListOverride) drawListOverride = window->DrawList; - - // Canvas - DrawListHelper::ImDrawListAddRectWithVerticalGradient(drawListOverride,bb.Min, bb.Max,col,(selected || hovered || held)?tabStyle.fillColorGradientDeltaIn0_05:(-tabStyle.fillColorGradientDeltaIn0_05),tabStyle.colors[selected ? TabLabelStyle::Col_TabLabelSelectedBorder : TabLabelStyle::Col_TabLabelBorder],tabStyle.rounding,1|2,tabStyle.borderWidth); - - // Text - ImGui::PushStyleColor(ImGuiCol_Text,ImGui::ColorConvertU32ToFloat4(colText)); - if (!pOptionalJustDrawTabButtonGraphicsUnderMouseWithThisOffset) RenderTextClipped(bb.Min,ImVec2(bb.Max.x-extraWidthForBtn,bb.Max.y), label, NULL, &label_size, ImVec2(0.5f,0.5f)); - else { - ImVec2 textPos(bb.Min.x+(bb.Max.x-bb.Min.x-label_size.x-extraWidthForBtn)*0.5f,bb.Min.y+(bb.Max.y-bb.Min.y-label_size.y)*0.5f); - drawListOverride->AddText(textPos,colText,label); - } - ImGui::PopStyleColor(); - - - - //fprintf(stderr,"bb.Min=%d,%d bb.Max=%d,%d label_size=%d,%d extraWidthForBtn=%d\n",(int)bb.Min.x,(int)bb.Min.y,(int)bb.Max.x,(int)bb.Max.y,(int)label_size.x,(int)label_size.y,(int)extraWidthForBtn); - if (hasCloseButton) { - const ImU32 col = (held && btnHovered) ? tabStyle.colors[TabLabelStyle::Col_TabLabelCloseButtonActive] : btnHovered ? tabStyle.colors[TabLabelStyle::Col_TabLabelCloseButtonHovered] : 0; - if (btnHovered) DrawListHelper::ImDrawListAddRect(drawListOverride,startBtn, endBtn, col,tabStyle.colors[TabLabelStyle::Col_TabLabelCloseButtonBorder],tabStyle.closeButtonRounding,0x0F,tabStyle.closeButtonBorderWidth); - - const float cross_extent = (btnWidth * 0.5f * 0.7071f);// - 1.0f; - const ImVec2 center((startBtn.x+endBtn.x)*0.5f,(startBtn.y+endBtn.y)*0.5f); - const ImU32 cross_col = tabStyle.colors[(btnHovered) ? TabLabelStyle::Col_TabLabelCloseButtonTextHovered : selected ? TabLabelStyle::Col_TabLabelSelectedText : TabLabelStyle::Col_TabLabelText];//btnHovered ? 0xFFFF0000 : ImGui::GetColorU32(ImGuiCol_Text); - drawListOverride->AddLine(center + ImVec2(+cross_extent,+cross_extent), center + ImVec2(-cross_extent,-cross_extent), cross_col,tabStyle.closeButtonTextWidth); - drawListOverride->AddLine(center + ImVec2(+cross_extent,-cross_extent), center + ImVec2(-cross_extent,+cross_extent), cross_col,tabStyle.closeButtonTextWidth); - - } - if (fontOverride) ImGui::PopFont(); - - return pressed; -} -//======================================================================================== -// Main code starts here - - -void TabWindow::TabLabel::DestroyTabLabel(TabWindow::TabLabel*& tab) { - if (TabWindow::TabLabelDeletingCb) TabWindow::TabLabelDeletingCb(tab); - tab->~TabLabel(); - ImGui::MemFree(tab); - tab=NULL; -} - -struct TabWindowNode { - friend class TabLabel; - friend class TabWindow; - friend struct TabWindowDragData; - - ImVector tabs; // only in leaf nodes - TabWindow::TabLabel* selectedTab; - TabWindowNode *parent; // (reference) - TabWindowNode *child[2]; // (owned) - char* name; // (owned) - float splitterPerc; // in [0,1] - bool horizontal; - ImVec2 allTabsSize; - int numClosableTabs; - TabWindowNode() {tabs.clear();selectedTab=NULL;parent=NULL;for (int i=0;i<2;i++) child[i]=NULL;name=NULL; - horizontal=false;splitterPerc=0.5f;allTabsSize.x=allTabsSize.y=0.f;numClosableTabs=0;} - ~TabWindowNode() { - clear(); - if (name) {ImGui::MemFree(name);name=NULL;} - } - inline bool isLeafNode() const {return (!child[0] && !child[1]);} - void clear() { - for (int i=0;i<2;i++) { - TabWindowNode*& ch = child[i]; - if (ch) { - ch->clear(); // delete child nodes too - ch->~TabWindowNode(); - ImGui::MemFree(ch); - ch=NULL; - } - } - for (int i=0,isz=tabs.size();iisLeafNode()); - IM_ASSERT(!parent || (!parent->isLeafNode() && parent->child[0] && parent->child[1] && parent->tabs.size()==0)); - if (childPosLTRB==-1) { - if (pos<0 || pos>tabs.size()) pos=tabs.size(); - tabs.push_back(tab); - for (int i=tabs.size()-2;i>=pos;--i) tabs[i+1] = tabs[i]; - tabs[pos] = tab; - return this; - } - IM_ASSERT(childPosLTRB>=0 && childPosLTRB<4); - horizontal = (childPosLTRB==1 || childPosLTRB==3); - splitterPerc = 0.5f; - const bool spFirst = (childPosLTRB==0 || childPosLTRB==1); - // create the two child nodes - for (int i=0;i<2;i++) { - TabWindowNode* ch = child[i]; - ch = (TabWindowNode*) ImGui::MemAlloc(sizeof(TabWindowNode)); - IM_PLACEMENT_NEW(ch) TabWindowNode(); - child[i] = ch; - ch->parent = this; - } - assignChildNames(false); - - // We must move tabs to child[]: - TabWindowNode* ch = spFirst ? child[1] : child[0]; - ch->tabs.resize(tabs.size()); - for (int i=0,isz=tabs.size();itabs[i] = tab; - } - tabs.clear(); - ch->selectedTab = selectedTab; - selectedTab = NULL; - // We must insert tab - ch = spFirst ? child[0] : child[1]; - ch->selectedTab = tab; - return ch->addTabLabel(tab,-1,pos); - } - TabWindowNode* findTabLabel(TabWindow::TabLabel* tab,bool recursive=false) { - if (!tab) return NULL; - if (recursive) { - TabWindowNode * n = NULL; - for (int i=0;i<2;i++) - if (child[i] && (n=child[i]->findTabLabel(tab,true))) return n; - } - for (int i=0,isz=tabs.size();ifindTabLabelFromUserPtr(value,pOptionalParentNodeOut))!=NULL) return tab; - for (int i=0,isz=tabs.size();iuserPtr==value) { - if (pOptionalParentNodeOut) *pOptionalParentNodeOut = this; - return tabs[i]; - } - return NULL; - } - TabWindow::TabLabel* findTabLabelFromUserText(const char* value,TabWindowNode** pOptionalParentNodeOut=NULL) { - TabWindow::TabLabel* tab = NULL; - for (int i=0;i<2;i++) - if (child[i] && (tab=child[i]->findTabLabelFromUserText(value,pOptionalParentNodeOut))!=NULL) return tab; - for (int i=0,isz=tabs.size();iuserText && strcmp(tabs[i]->userText,value)==0) { - if (pOptionalParentNodeOut) *pOptionalParentNodeOut = this; - return tabs[i]; - } - return NULL; - } - TabWindow::TabLabel* findTabLabelFromTooltip(const char* value,TabWindowNode** pOptionalParentNodeOut=NULL) { - TabWindow::TabLabel* tab = NULL; - for (int i=0;i<2;i++) - if (child[i] && (tab=child[i]->findTabLabelFromTooltip(value,pOptionalParentNodeOut))!=NULL) return tab; - for (int i=0,isz=tabs.size();itooltip,value)==0) { - if (pOptionalParentNodeOut) *pOptionalParentNodeOut = this; - return tabs[i]; - } - return NULL; - } - TabWindow::TabLabel* findTabLabelFromLabel(const char* value,TabWindowNode** pOptionalParentNodeOut=NULL) { - TabWindow::TabLabel* tab = NULL; - for (int i=0;i<2;i++) - if (child[i] && (tab=child[i]->findTabLabelFromLabel(value,pOptionalParentNodeOut))!=NULL) return tab; - for (int i=0,isz=tabs.size();imatchLabel(value)) { - if (pOptionalParentNodeOut) *pOptionalParentNodeOut = this; - return tabs[i]; - } - return NULL; - } - bool removeTabLabel(TabWindow::TabLabel* tab,bool recursive=false,TabWindowNode** pOptionalActiveTabNodeToChange=NULL,bool dontDeleteTabLabel=false) { - if (!tab) return false; - if (recursive) { - for (int i=0;i<2;i++) { - if (child[i] && child[i]->removeTabLabel(tab,true,pOptionalActiveTabNodeToChange,dontDeleteTabLabel)) return true; - } - } - IM_ASSERT(tab); - IM_ASSERT(tabs.size()>0 ? this->isLeafNode() : true); - for (int i=0;iparent; - IM_ASSERT(parent->child[0] && parent->child[1]); - IM_ASSERT(parent->child[0]==this || parent->child[1]==this); - IM_ASSERT(parent->child[0]!=parent->child[1]); - - int id = parent->child[0]==this ? 0 : 1; - // delete parent->child[id]: it's empty (Hey! that's me! Am I allowed delete myself?) - { - TabWindowNode* ch = parent->child[id]; - IM_ASSERT(ch==this); - IM_ASSERT(ch->isLeafNode()); - parent->child[id] = NULL; - if (pOptionalActiveTabNodeToChange && *pOptionalActiveTabNodeToChange==ch) *pOptionalActiveTabNodeToChange=parent; - IM_ASSERT(ch->tabs.size()==0); - // We defer deleting it at the bottom of this method for extended safety - } - // merge the other child with parent - id = (id == 1) ? 0 : 1;// other parent child - { - TabWindowNode* ch = parent->child[id]; - if (ch->isLeafNode()) { - if (pOptionalActiveTabNodeToChange && *pOptionalActiveTabNodeToChange==ch) *pOptionalActiveTabNodeToChange=parent; - IM_ASSERT(parent->tabs.size()==0); - parent->tabs.resize(ch->tabs.size()); - for (int i=0,isz=ch->tabs.size();itabs[i] = ch->tabs[i]; - } - ch->tabs.clear(); - parent->selectedTab = ch->selectedTab; - parent->splitterPerc = 0.5f; - - parent->child[id] = NULL; - } - else { - IM_ASSERT(ch->tabs.size()==0); - IM_ASSERT(parent->tabs.size()==0); - - // We must replace "parent" with "ch" and then delete "parent" - // Nope: it's better to "deep clone "ch" to "parent" and delete "ch" - - if (pOptionalActiveTabNodeToChange && *pOptionalActiveTabNodeToChange==ch) *pOptionalActiveTabNodeToChange=parent; - - if (ch->name) {ImGui::MemFree(ch->name);ch->name=NULL;} - parent->child[0] = ch->child[0];ch->child[0]=NULL; - parent->child[1] = ch->child[1];ch->child[1]=NULL;ch->parent=NULL; - parent->child[0]->parent = parent->child[1]->parent = parent; - parent->horizontal = ch->horizontal; - parent->selectedTab = ch->selectedTab; - parent->splitterPerc = ch->splitterPerc; - parent->assignChildNames(true); - - } - - // delete the other child - ch->~TabWindowNode(); - ImGui::MemFree(ch); - ch = NULL; - // delete me - ch = this; - ch->~TabWindowNode(); - ImGui::MemFree(ch); - } - - - - } - return true; - } - } - return false; - } - bool isEmpty(bool recursive=false) { - if (tabs.size()!=0) return false; - if (recursive) { - for (int i=0;i<2;i++) - if (child[i] && !child[i]->isEmpty(true)) return false; - } - return true; - } - TabWindowNode* getFirstLeaftNode() {return isLeafNode() ? this : child[0]->getFirstLeaftNode();} - TabWindowNode* getRootNode() {return parent ? parent->getRootNode() : this;} - bool mergeToParent(TabWindowNode** pOptionalActiveTabNodeToChange=NULL) { - if (!isLeafNode() || !parent) return false; - ImVector nodetabs;int sz=0; - TabWindowNode* parent = this->parent; - while ((sz=tabs.size())>0) { - TabWindow::TabLabel* tab = tabs[0]; - nodetabs.push_back(tab); - removeTabLabel(tab,false,pOptionalActiveTabNodeToChange,true); - if (sz==1) break; - } - // "this" it's invalid now: after having removed all its TabLabels the node we're in has been deleted... - parent = parent->getFirstLeaftNode(); - for (int i=0,isz = nodetabs.size();iaddTabLabel(nodetabs[i]); - } - return true; - } - void mergeEmptyLeafNodes(TabWindowNode** pOptionalActiveTabNodeToChange=NULL) { - for (int i=0;i<2;i++) { - if (child[i]) child[i]->mergeEmptyLeafNodes(pOptionalActiveTabNodeToChange); - } - if (isLeafNode() && tabs.size()==0) mergeToParent(pOptionalActiveTabNodeToChange); - } - - void setName(const char* lbl) { - if (name) {ImGui::MemFree(name);name=NULL;} - const char e = '\0';if (!lbl) lbl=&e; - const int sz = strlen(lbl)+1; - name = (char*) ImGui::MemAlloc(sz+1);strcpy(name,lbl); - } - void assignChildNames(bool recursive=false) { - const int sz = strlen(name)+8; - for (int i=0;i<2;i++) { - TabWindowNode* ch = child[i]; - if (!ch) continue; - if (ch->name) {ImGui::MemFree(ch->name);ch->name=NULL;} - ch->name = (char*) ImGui::MemAlloc(sz); - strcpy(ch->name,name); - strcat(ch->name,".child"); - sprintf(&ch->name[sz-2],"%d",i); - ch->name[sz-1]='\0'; - if (recursive) ch->assignChildNames(true); - } - } - void getTabLabels(ImVector& tabsOut,bool onlyClosableTabs=false,bool onlyModifiedTabs=false) { - for (int i=0;i<2;i++) { - if (child[i]) child[i]->getTabLabels(tabsOut,onlyClosableTabs,onlyModifiedTabs); - } - IM_ASSERT(tabs.size()>0 ? this->isLeafNode() : true); - for (int i=0,isz=tabs.size();iisClosable()) && (!onlyModifiedTabs || tab->getModified())) { - tabsOut.push_back(tab); - //fprintf(stderr,"%s\n",tab->getLabel()); - } - } - } - - void render(const ImVec2& windowSize,struct MyTabWindowHelperStruct *ptr); - -#if (defined(IMGUIHELPER_H_) && !defined(NO_IMGUIHELPER_SERIALIZATION)) -#ifndef NO_IMGUIHELPER_SERIALIZATION_SAVE - void serialize(ImGuiHelper::Serializer& s, const TabWindow* tabWindow) { - if (name) s.save(name,"name"); - s.save(&splitterPerc,"splitterPerc"); - s.save(&horizontal,"horizontal"); - if (tabWindow->activeNode==this) {bool a = true;s.save(&a,"isActiveNode");} - const bool isLeafNode = this->isLeafNode(); - s.save(&isLeafNode,"isLeafNode"); - const int tabSize = tabs.size();s.save(&tabSize,"numTabs"); - IM_ASSERT(tabSize>0 ? isLeafNode : true); - if (!isLeafNode) { - for (int i=0;i<2;i++) child[i]->serialize(s,tabWindow); - } - else { - for (int i=0;i0) ? (strlen(tl.getLabel())-1) : -1); - if (tl.tooltip && strlen(tl.tooltip)>0) s.save(tl.getTooltip(),"tooltip"); - s.save(&tl.closable,"closable"); - s.save(&tl.draggable,"draggable"); - if (selectedTab==&tl) {bool a = true;s.save(&a,"selected");} - if (tl.userText && strlen(tl.userText)>0) s.save(tl.getUserText(),"userText"); - s.save(&tl.userInt,"userInt"); - s.save(&tl.wndFlags,"wndFlags"); - } - } - } -#endif //NO_IMGUIHELPER_SERIALIZATION_SAVE -#ifndef NO_IMGUIHELPER_SERIALIZATION_LOAD - struct ParseCallbackStruct { - TabWindowNode* node; - bool isLeafNode; - int numTabs; - bool isActiveNode; - }; - static bool ParseCallback(ImGuiHelper::FieldType /*ft*/,int /*numArrayElements*/,void* pValue,const char* name,void* userPtr) { - ParseCallbackStruct& cbs = *((ParseCallbackStruct*)userPtr); - TabWindowNode* n = cbs.node; - if (strcmp(name,"name")==0) { - n->setName((const char*)pValue); - //fprintf(stderr,"\"%s\"\n",n->name); - } - else if (strcmp(name,"splitterPerc")==0) n->splitterPerc = *((float*)pValue); - else if (strcmp(name,"horizontal")==0) n->horizontal = *((bool*)pValue); - else if (strcmp(name,"isActiveNode")==0) cbs.isActiveNode = *((bool*)pValue); - else if (strcmp(name,"isLeafNode")==0) cbs.isLeafNode = *((bool*)pValue); - else if (strcmp(name,"numTabs")==0) {cbs.numTabs = *((int*)pValue);return true;} - return false; - } - struct ParseTabLabelCallbackStruct { - TabWindow::TabLabel* tab; - bool isSelected; - }; - static bool ParseTabLabelCallback(ImGuiHelper::FieldType /*ft*/,int /*numArrayElements*/,void* pValue,const char* name,void* userPtr) { - ParseTabLabelCallbackStruct& tls = *((ParseTabLabelCallbackStruct*)userPtr); - TabWindow::TabLabel& tab = *tls.tab; - if (strcmp(name,"label")==0) { - tab.setLabel((const char*)pValue); - //fprintf(stderr,"\"%s\"\n",tab.label); - } - else if (strcmp(name,"tooltip")==0) tab.setTooltip((const char*)pValue); - else if (strcmp(name,"closable")==0) tab.closable = *((bool*)pValue); - else if (strcmp(name,"draggable")==0) tab.draggable = *((bool*)pValue); - else if (strcmp(name,"selected")==0) tls.isSelected = *((bool*)pValue); - else if (strcmp(name,"userText")==0) tab.setUserText((const char*)pValue); - else if (strcmp(name,"userInt")==0) tab.userInt = *((int*)pValue); - else if (strcmp(name,"wndFlags")==0) {tab.wndFlags = *((int*)pValue);return true;} - return false; - } - void deserialize(ImGuiHelper::Deserializer& d,TabWindowNode* parent,const char*& amount,TabWindow* tabWindow) { - ParseCallbackStruct cbs;cbs.node=this;cbs.isLeafNode=true;cbs.numTabs=0;cbs.isActiveNode=false; - amount = d.parse(ParseCallback,(void*)&cbs,amount); - this->parent = parent; - if (cbs.isActiveNode && tabWindow) { - tabWindow->activeNode = this; - //IM_ASSERT(cbs.isLeafNode); // mmmh, this gets hit sometimes... - } - IM_ASSERT(cbs.numTabs>0 ? cbs.isLeafNode : true); - if (!cbs.isLeafNode) { - for (int i=0;i<2;i++) { - TabWindowNode* n = (TabWindowNode*) ImGui::MemAlloc(sizeof(TabWindowNode)); - IM_PLACEMENT_NEW(n) TabWindowNode(); - n->deserialize(d,this,amount,tabWindow); - this->child[i] = n; - } - } - else { - for (int i=0;icreateTabLabel(tl.getLabel(),tl.getTooltip(),tl.isClosable(),tl.isDraggable(),NULL,tl.userText,tl.userInt,tl.wndFlags); - if (tab) { - this->tabs.push_back(tab); - if (tls.isSelected) this->selectedTab = tab; - } - } - } - IM_ASSERT(this->tabs.size()>0 ? this->isLeafNode() : true); - IM_ASSERT(!this->isLeafNode() ? this->tabs.size()==0 : true); - } -#endif //NO_IMGUIHELPER_SERIALIZATION_LOAD -#endif //NO_IMGUIHELPER_SERIALIZATION - -}; - -namespace TabWindowDefaultCallbacks { -void TabLabelGroupPopupMenuProvider(ImVector& tabs,ImGui::TabWindow& parent,ImGui::TabWindowNode* tabNode,void*) { - const int numTabsToClose = parent.getNumClosableTabs(tabNode); // or parent.getNumTabs(tabNode) - const bool isMergeble = parent.isMergeble(tabNode); - if (numTabsToClose || isMergeble) { - ImGui::PushStyleColor(ImGuiCol_WindowBg,ImGui::ColorConvertU32ToFloat4(ImGui::TabLabelStyle::Get().colors[ImGui::TabLabelStyle::Col_TabLabel])); - ImGui::PushStyleColor(ImGuiCol_Text,ImGui::ColorConvertU32ToFloat4(ImGui::TabLabelStyle::Get().colors[ImGui::TabLabelStyle::Col_TabLabelText])); - if (ImGui::BeginPopup(ImGui::TabWindow::GetTabLabelGroupPopupMenuName())) { - //ImGui::Text("TabLabel Group Menu"); - //ImGui::Separator(); - if (isMergeble && ImGui::MenuItem("Merge")) parent.merge(tabNode); // Warning: this invalidates "tabNode" after the call - if (numTabsToClose && ImGui::MenuItem("Close all")) { - for (int i=0,isz=tabs.size();iisClosable()) // otherwise even non-closable tabs will be closed [skip if using parent.getNumTabs(tabNode) above] - { - //parent.removeTabLabel(tab); - tab->mustCloseNextFrame = true; // alternative way... this asks for closing - } - } - } - ImGui::EndPopup(); - } - ImGui::PopStyleColor(2); - } -} -void TabLabelPopupMenuProvider(ImGui::TabWindow::TabLabel* tab,ImGui::TabWindow& parent,void*) { - const bool savable = tab && /*tab->isClosable() &&*/ tab->getModified(); - if (savable && ImGui::BeginPopup(ImGui::TabWindow::GetTabLabelPopupMenuName())) { - ImGui::PushID(tab); - //ImGui::Text("\"%.*s\" Menu",(int)(strlen(tab->getLabel())-(tab->getModified()?1:0)),tab->getLabel()); - //ImGui::Separator(); - if (ImGui::MenuItem("Save")) { - bool ok = false; - if (TabWindow::TabLabelSaveCb) ok = TabWindow::TabLabelSaveCb(tab,parent,NULL); - else ok = tab->saveAs(NULL); - if (ok) tab->setModified(false); - } - //if (tab->closable && ImGui::MenuItem("Close")) tab->mustCloseNextFrame = true; - ImGui::PopID(); - ImGui::EndPopup(); - } - -} - -} // TabWindowDefaultCallbacks - -struct TabWindowDragData { - TabWindow::TabLabel* draggingTabSrc; - TabWindowNode* draggingTabNodeSrc; - ImGuiWindow* draggingTabImGuiWindowSrc; - TabWindow* draggingTabWindowSrc; - ImVec2 draggingTabSrcSize; - ImVec2 draggingTabSrcOffset; - bool draggingTabSrcIsSelected; - - TabWindow::TabLabel* draggingTabDst; - TabWindowNode* draggingTabNodeDst; - ImGuiWindow* draggingTabImGuiWindowDst; - TabWindow* draggingTabWindowDst; - - TabWindowDragData() {reset();} - void resetDraggingSrc() { - draggingTabSrc = NULL; - draggingTabNodeSrc = NULL; - draggingTabImGuiWindowSrc = NULL; - draggingTabWindowSrc = NULL; - draggingTabSrcSize = draggingTabSrcOffset = ImVec2(0,0); - draggingTabSrcIsSelected = false; - } - void resetDraggingDst() { - draggingTabDst = NULL; - draggingTabNodeDst = NULL; - draggingTabImGuiWindowDst = NULL; - draggingTabWindowDst = NULL; - } - inline void reset() {resetDraggingSrc();resetDraggingDst();} - inline bool isDraggingSrcValid() const { - return (draggingTabSrc && draggingTabNodeSrc && draggingTabImGuiWindowSrc); - } - inline bool isDraggingDstValid() const { - return (draggingTabDst && draggingTabNodeDst && draggingTabImGuiWindowDst); - } - inline int findDraggingSrcIndex(const TabWindow::TabLabel* tab=NULL) const { - if (!tab) tab = draggingTabSrc; - for (int i=0,isz=draggingTabNodeSrc->tabs.size();itabs[i] == tab) return i; - } - return -1; - } - inline int findDraggingDstIndex(const TabWindow::TabLabel* tab=NULL) const { - if (!tab) tab = draggingTabDst; - for (int i=0,isz=draggingTabNodeDst->tabs.size();itabs[i] == tab) return i; - } - return -1; - } - inline static TabWindowNode* FindTabNodeByName(TabWindowNode* firstNode,const char* name,int numCharsToMatch=-1) { - if ((numCharsToMatch==-1 && strcmp(firstNode->name,name)==0) - || (strncmp(firstNode->name,name,numCharsToMatch)==0)) return firstNode; - TabWindowNode* rv = NULL; - for (int i=0;i<2;i++) { - TabWindowNode* ch = firstNode->child[i]; - if (ch && (rv=FindTabNodeByName(ch,name,numCharsToMatch))) return rv; - } - return NULL; - } - - inline void drawDragButton(ImDrawList* drawList,const ImVec2& wp,const ImVec2& mp) { - const TabLabelStyle& tabStyle = TabLabelStyleGetMergedWithAlphaForOverlayUsage(); - ImVec2 start(wp.x+mp.x-draggingTabSrcOffset.x-draggingTabSrcSize.x*0.5f,wp.y+mp.y-draggingTabSrcOffset.y-draggingTabSrcSize.y*0.5f); - bool mustCloseTab = false; - const ImFont* fontOverride = (draggingTabSrcIsSelected ? (draggingTabSrc->getModified() ? TabLabelStyle::ImGuiFonts[tabStyle.fontStyles[TabLabelStyle::TAB_STATE_SELECTED_MODIFIED]] : TabLabelStyle::ImGuiFonts[tabStyle.fontStyles[TabLabelStyle::TAB_STATE_SELECTED]]) : - (draggingTabSrc->getModified() ? TabLabelStyle::ImGuiFonts[tabStyle.fontStyles[TabLabelStyle::TAB_STATE_MODIFIED]] : TabLabelStyle::ImGuiFonts[tabStyle.fontStyles[TabLabelStyle::TAB_STATE_NORMAL]])); - if (!fontOverride) { - if (draggingTabSrcIsSelected) fontOverride = TabLabelStyle::ImGuiFonts[tabStyle.fontStyles[TabLabelStyle::TAB_STATE_SELECTED]]; - else if (draggingTabSrc->getModified()) fontOverride = TabLabelStyle::ImGuiFonts[tabStyle.fontStyles[TabLabelStyle::TAB_STATE_MODIFIED]]; - } - ImGui::TabButton(NULL,draggingTabSrcIsSelected,draggingTabSrc->closable ? &mustCloseTab : NULL,draggingTabSrc->getLabel(),NULL,&tabStyle,(ImFont*)fontOverride,&start,drawList,false,true); - } - inline void drawProhibitionSign(ImDrawList* drawList,const ImVec2& wp,const ImVec2& pos,float size,float alpha=0.5f) { - ImVec2 start(wp.x+pos.x-size*0.5f,wp.y+pos.y-size*0.5f); - const ImVec2 end(start.x+size,start.y+size); - const ImVec4 color(1.f,1.f,1.f,alpha); - drawList->AddImage(TabWindow::DockPanelIconTextureID,start,end,ImVec2(0.5f,0.75f),ImVec2(0.75f,1.f),ImGui::ColorConvertFloat4ToU32(color)); - } - -}; -static TabWindowDragData gDragData; -struct MyTabWindowHelperStruct { - bool isRMBclicked; - static bool isMouseDragging; - static bool isMouseDraggingJustStarted; - static bool LockedDragging; // better dragging experience when trying to drag non-draggable tab labels - bool isASplitterActive; - static TabWindow::TabLabel* tabLabelPopup; - static bool tabLabelPopupChanged; - static TabWindow* tabLabelPopupTabWindow; // used by both tabLabelPopup and tabLabelGroupPopup - static ImVector tabLabelGroupPopup; - static bool tabLabelGroupPopupChanged; - static TabWindowNode* tabLabelGroupPopupNode; - - TabWindow* tabWindow; - bool allowExchangeTabLabels; - - static ImVector TabsToClose; - static ImVector TabsToCloseParents; - - static ImVector TabsToAskForClosing; - static ImVector TabsToAskForClosingParents; - static bool TabsToAskForClosingIsUsedJustToSaveTheseTabs; - static bool TabsToAskForClosingDontAllowCancel; - static bool MustOpenAskForClosingPopup; - - - ImVec2 itemSpacing; - float childBorderSize; - - ImVec4 splitterColor; - ImVec4 splitterColorHovered; - ImVec4 splitterColorActive; - - float textHeightWithSpacing; - bool isWindowHovered; - - ImGuiWindowFlags flags; - - MyTabWindowHelperStruct(TabWindow* _tabWindow) { - isMouseDragging = ImGui::IsMouseDragging(0,3.f); - isMouseDraggingJustStarted = isMouseDragging && (ImGui::GetIO().MouseDownDuration[0] < 0.35f);// ImGui::GetIO().MouseDown[0] does not work! - isRMBclicked = ImGui::IsMouseClicked(1); - isASplitterActive = false; - tabWindow = _tabWindow; - allowExchangeTabLabels = !gDragData.draggingTabSrc || (gDragData.draggingTabWindowSrc && gDragData.draggingTabWindowSrc->canExchangeTabLabelsWith(tabWindow)); - //mustOpenAskForClosingPopup = false; - - ImGuiStyle& style = ImGui::GetStyle(); - itemSpacing = style.ItemSpacing; - childBorderSize = style.ChildBorderSize; - - const TabLabelStyle& ts = TabLabelStyle::Get(); - splitterColor = ts.tabWindowSplitterColor; splitterColor.w *= 0.4f; - splitterColorHovered = ts.tabWindowSplitterColor; splitterColorHovered.w *= 0.55f; - splitterColorActive = ts.tabWindowSplitterColor; splitterColorActive.w *= 0.7f; - - storeStyleVars(); - - textHeightWithSpacing = ImGui::GetTextLineHeightWithSpacing(); - - isWindowHovered = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows); - - flags = TabWindow::ExtraWindowFlags;// | ((ImGui::GetCurrentWindow()->Flags&ImGuiWindowFlags_ShowBorders) ? ImGuiWindowFlags_ShowBorders : 0); - } - ~MyTabWindowHelperStruct() { - restoreStyleVars(); - /*if (mustOpenAskForClosingPopup) { - ImGuiContext& g = *GImGui; while (g.OpenPopupStack.size() > 0) g.OpenPopupStack.pop_back(); // Close all existing context-menus - ImGui::OpenPopup(ImGui::TabWindow::GetTabLabelAskForDeletionModalWindowName()); - }*/ - } - inline void storeStyleVars() { - ImGuiStyle& style = ImGui::GetStyle(); - style.ItemSpacing = ImVec2(1,1); - style.ChildBorderSize = 0.f; - } - inline void restoreStyleVars() { - ImGuiStyle& style = ImGui::GetStyle(); - style.ItemSpacing = itemSpacing; - style.ChildBorderSize = childBorderSize; - } - - inline static void ResetTabsToClose() { - TabsToClose.clear();TabsToCloseParents.clear(); - } - inline static void ResetTabsToAskForClosing() { - TabsToAskForClosing.clear();TabsToAskForClosingParents.clear(); - TabsToAskForClosingIsUsedJustToSaveTheseTabs=false; - TabsToAskForClosingDontAllowCancel=false; - } -}; -TabWindow::TabLabel* MyTabWindowHelperStruct::tabLabelPopup = NULL; -ImVector MyTabWindowHelperStruct::tabLabelGroupPopup; -TabWindow* MyTabWindowHelperStruct::tabLabelPopupTabWindow = NULL; -bool MyTabWindowHelperStruct::tabLabelPopupChanged = false; -bool MyTabWindowHelperStruct::tabLabelGroupPopupChanged = false; -TabWindowNode* MyTabWindowHelperStruct::tabLabelGroupPopupNode = NULL; -bool MyTabWindowHelperStruct::isMouseDragging = false; -bool MyTabWindowHelperStruct::isMouseDraggingJustStarted = false; -bool MyTabWindowHelperStruct::LockedDragging = false; -ImVector MyTabWindowHelperStruct::TabsToClose; -ImVector MyTabWindowHelperStruct::TabsToCloseParents; -ImVector MyTabWindowHelperStruct::TabsToAskForClosing; -ImVector MyTabWindowHelperStruct::TabsToAskForClosingParents; -bool MyTabWindowHelperStruct::TabsToAskForClosingIsUsedJustToSaveTheseTabs=false; -bool MyTabWindowHelperStruct::TabsToAskForClosingDontAllowCancel=false; -bool MyTabWindowHelperStruct::MustOpenAskForClosingPopup=false; -TabWindow::TabLabelCallback TabWindow::WindowContentDrawerCb=NULL; -void* TabWindow::WindowContentDrawerUserPtr=NULL; -TabWindow::TabLabelCallback TabWindow::TabLabelPopupMenuDrawerCb=&TabWindowDefaultCallbacks::TabLabelPopupMenuProvider; -void* TabWindow::TabLabelPopupMenuDrawerUserPtr=NULL; -//TabWindow::TabLabelClosingCallback TabWindow::TabLabelClosingCb=NULL; -//void* TabWindow::TabLabelClosingUserPtr=NULL; -TabWindow::TabLabelDeletingCallback TabWindow::TabLabelDeletingCb=NULL; -TabWindow::TabLabelGroupPopupMenuCallback TabWindow::TabLabelGroupPopupMenuDrawerCb=&TabWindowDefaultCallbacks::TabLabelGroupPopupMenuProvider; -void* TabWindow::TabLabelGroupPopupMenuDrawerUserPtr=NULL; -TabWindow::TabLabelFactoryCallback TabWindow::TabLabelFactoryCb=NULL; -TabWindow::TabLabelFileCallback TabWindow::TabLabelSaveCb=NULL; -ImGuiWindowFlags TabWindow::ExtraWindowFlags = 0; - - -void TabWindowNode::render(const ImVec2 &windowSize, MyTabWindowHelperStruct *ptr) -{ - MyTabWindowHelperStruct& mhs = *ptr; - const TabLabelStyle& tabStyle = TabLabelStyle::GetMergedWithWindowAlpha(); // Or just Get() ? - ImGuiStyle& style = ImGui::GetStyle(); - const ImVec4 colorChildWindowBg = style.Colors[ImGuiCol_ChildBg]; - const float splitterSize = tabStyle.tabWindowSplitterSize; - bool splitterActive = false; - static ImVec4 colorTransparent(0,0,0,0); - - IM_ASSERT(name); - if (child[0]) { - IM_ASSERT(child[1]); - IM_ASSERT(tabs.size()==0); - const float minSplitSize = 10; // If size is smaller, the child won't be displayed - style.Colors[ImGuiCol_ChildBg] = colorTransparent; - if (ImGui::RevertUpstreamBeginChildCommit::OldBeginChild(name,windowSize,ImGuiWindowFlags_NoScrollbar)) { - style.Colors[ImGuiCol_ChildBg] = colorChildWindowBg; - ImVec2 ws = windowSize; - float splitterPercToPixels = 0.f,splitterDelta = 0.f; - if (horizontal && ws.y>splitterSize && ws.x>minSplitSize) { - ws.y-=splitterSize; - splitterPercToPixels = ws.y*splitterPerc; - if (splitterPercToPixels>minSplitSize) child[0]->render(ImVec2(ws.x,splitterPercToPixels),ptr); - // Horizontal Splitter ------------------------------------------ - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0,0)); - ImGui::PushStyleColor(ImGuiCol_Button,mhs.splitterColor); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered,mhs.splitterColorHovered); - ImGui::PushStyleColor(ImGuiCol_ButtonActive,mhs.splitterColorActive); - ImGui::PushID(this); - - ImGui::Button("##splitter0", ImVec2(ws.x,splitterSize)); - splitterActive = !mhs.isASplitterActive && ImGui::IsItemActive(); - if (splitterActive || ImGui::IsItemHovered()) ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS); - - mhs.isASplitterActive |= splitterActive; - if (splitterActive) splitterDelta = ImGui::GetIO().MouseDelta.y; - else splitterDelta = 0.f; - if (splitterActive) { - float& h = splitterPercToPixels; - const float minh = splitterSize; - const float maxh = ws.y-splitterSize - mhs.textHeightWithSpacing;//20; Is this correct ? // Warning: 20.f is hard-coded! - if (h+splitterDelta>maxh) splitterDelta = (h!=maxh) ? (maxh-h) : 0.f; - else if (h+splitterDeltaminSplitSize) child[1]->render(ImVec2(ws.x,ws.y-splitterPercToPixels),ptr); - } - else if (!horizontal && ws.x>splitterSize && ws.y>minSplitSize) { - ws.x-=splitterSize; - splitterPercToPixels = ws.x*splitterPerc; - if (splitterPercToPixels>minSplitSize) child[0]->render(ImVec2(splitterPercToPixels,ws.y),ptr); - // Vertical Splitter ------------------------------------------ - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0,0)); - ImGui::PushStyleColor(ImGuiCol_Button,mhs.splitterColor); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered,mhs.splitterColorHovered); - ImGui::PushStyleColor(ImGuiCol_ButtonActive,mhs.splitterColorActive); - ImGui::PushID(this); - ImGui::SameLine(0,0); - - ImGui::Button("##splitter1", ImVec2(splitterSize,ws.y)); - splitterActive = !mhs.isASplitterActive && ImGui::IsItemActive(); - if (splitterActive || ImGui::IsItemHovered()) ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); - - mhs.isASplitterActive |= splitterActive; - if (splitterActive) splitterDelta = ImGui::GetIO().MouseDelta.x; - else splitterDelta = 0.f; - if (splitterActive) { - float& w = splitterPercToPixels; - const float minw = splitterSize; - const float maxw = ws.x-splitterSize; - if (w + splitterDelta>maxw) splitterDelta = (w!=maxw) ? (maxw-w) : 0.f; - else if (w + splitterDeltaminSplitSize) child[1]->render(ImVec2(ws.x-splitterPercToPixels,ws.y),ptr); - } - //else {/* Window too tiny: better not to draw it, otherwise the splitters overlap and may cause bad stuff */} - } - else style.Colors[ImGuiCol_ChildBg] = colorChildWindowBg; - ImGui::EndChild(); // name - return; - } - - // Leaf Node - IM_ASSERT(!child[1]); - - - //TabWindow::TabLabel* hoveredTab = NULL; - //---------------------------------------------------------------- - { - style.Colors[ImGuiCol_ChildBg] = colorTransparent; - if (ImGui::RevertUpstreamBeginChildCommit::OldBeginChild(name,windowSize,ImGuiWindowFlags_NoScrollbar)) { - if (tabStyle.tabWindowLabelBackgroundColor.w!=0) { - ImGuiWindow* window = ImGui::GetCurrentWindow(); - window->DrawList->AddRectFilled(window->Pos, window->Pos+ImVec2(windowSize.x,allTabsSize.y), GetColorU32(tabStyle.tabWindowLabelBackgroundColor), 0); - } - style.Colors[ImGuiCol_ChildBg] = colorChildWindowBg; - ImGuiContext& g = *GImGui; - TabWindowDragData& dd = gDragData; - const ImFont* fontOverride = NULL; - - - ImGui::Spacing(); // Hack to remove when I'll find out why the top border of the tab labels gets clipped out. - ImGui::BeginGroup(); - const int numTabs = tabs.size(); - if (numTabs>0 && !selectedTab) selectedTab = tabs[0]; - - float windowWidth = 0.f,sumX=0.f; - windowWidth = windowSize.x;//ImGui::GetWindowWidth();// - style.WindowPadding.x;// - (ImGui::GetScrollMaxY()>0 ? style.ScrollbarSize : 0.f); - TabWindow::TabLabel* newSelectedTab = selectedTab; - numClosableTabs = 0; - ImVec2 tabButtonSz(0,0);bool mustCloseTab = false;bool canUseSizeOptimization = false;bool isAItemHovered = false; - const bool isDraggingCorrectly = mhs.isMouseDragging && !mhs.LockedDragging && !mhs.isASplitterActive; - bool selection_changed = false; - for (int i = 0; i < numTabs; i++) - { - TabWindow::TabLabel& tab = *tabs[i]; - if (tab.closable) ++numClosableTabs; - - fontOverride = ((selectedTab == &tab) ? (tab.getModified() ? TabLabelStyle::ImGuiFonts[tabStyle.fontStyles[TabLabelStyle::TAB_STATE_SELECTED_MODIFIED]] : TabLabelStyle::ImGuiFonts[tabStyle.fontStyles[TabLabelStyle::TAB_STATE_SELECTED]]) : - (tab.getModified() ? TabLabelStyle::ImGuiFonts[tabStyle.fontStyles[TabLabelStyle::TAB_STATE_MODIFIED]] : TabLabelStyle::ImGuiFonts[tabStyle.fontStyles[TabLabelStyle::TAB_STATE_NORMAL]])); - if (!fontOverride) { - if (selectedTab == &tab) fontOverride = TabLabelStyle::ImGuiFonts[tabStyle.fontStyles[TabLabelStyle::TAB_STATE_SELECTED]]; - else if (tab.getModified()) fontOverride = TabLabelStyle::ImGuiFonts[tabStyle.fontStyles[TabLabelStyle::TAB_STATE_MODIFIED]]; - } - - if (sumX > 0.f) { - sumX+=style.ItemSpacing.x; // Maybe we can skip it if we use SameLine(0,0) below - ImGui::TabButton(NULL,selectedTab == &tab,tab.closable ? &mustCloseTab : NULL,tab.getLabel(),&tabButtonSz,&tabStyle,(ImFont*)fontOverride); - sumX+=tabButtonSz.x; - if (sumX>windowWidth) sumX = 0.f; - else ImGui::SameLine(); - canUseSizeOptimization = true; - } - else canUseSizeOptimization = false; - - // Draw the button - mustCloseTab = false; - ImGui::PushID(&tab); // otherwise two tabs with the same name would clash. - if (tab.mustSelectNextFrame || ImGui::TabButton("",(selectedTab == &tab),tab.closable ? &mustCloseTab : NULL,tab.getLabel(),NULL,&tabStyle,(ImFont*)fontOverride,NULL,NULL,canUseSizeOptimization)) { - selection_changed = (selectedTab != &tab); - newSelectedTab = &tab; - tab.mustSelectNextFrame = false; - } - ImGui::PopID(); - - if (sumX==0.f) sumX = style.WindowPadding.x + ImGui::GetItemRectSize().x; // First element of a line - - if (tab.mustCloseNextFrame || mustCloseTab) { - tab.mustCloseNextFrame = false; - if (!tab.getModified()) { - mhs.TabsToClose.push_back(&tab); - mhs.TabsToCloseParents.push_back(mhs.tabWindow); - } - else { - mhs.TabsToAskForClosing.push_back(&tab); - mhs.TabsToAskForClosingParents.push_back(mhs.tabWindow); - mhs.MustOpenAskForClosingPopup = true; - } - } - else if (ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly)) { - isAItemHovered = true; - //hoveredTab = &tab; - if (tab.tooltip && mhs.isWindowHovered && strlen(tab.tooltip)>0 && (&tab!=mhs.tabLabelPopup || GImGui->OpenPopupStack.size()==0) ) ImGui::SetTooltip("%s",tab.tooltip); - - if (isDraggingCorrectly) { - if (mhs.isMouseDraggingJustStarted) { - if (!dd.draggingTabSrc) { - if (mhs.isWindowHovered) { - if (!tab.draggable) mhs.LockedDragging = true; - else { - dd.draggingTabSrc = &tab; - dd.draggingTabNodeSrc = this; - dd.draggingTabImGuiWindowSrc = g.HoveredWindow; - dd.draggingTabWindowSrc = mhs.tabWindow; - dd.draggingTabSrcIsSelected = (selectedTab == &tab); - - dd.draggingTabSrcSize = ImGui::GetItemRectSize(); - const ImVec2& mp = ImGui::GetIO().MousePos; - const ImVec2 draggingTabCursorPos = ImGui::GetCursorPos(); - dd.draggingTabSrcOffset=ImVec2( - mp.x+dd.draggingTabSrcSize.x*0.5f-sumX+ImGui::GetScrollX(), - mp.y+dd.draggingTabSrcSize.y*0.5f-draggingTabCursorPos.y+ImGui::GetScrollY() - ); - - //fprintf(stderr,"Hovered Start Window:%s\n",g.HoveredWindow ? g.HoveredWindow->Name : "NULL"); - } - } - } - else if (dd.draggingTabSrc && (!tab.draggable || !mhs.allowExchangeTabLabels)) { - // Prohibition sign------- - const ImVec2& itemSize = ImGui::GetItemRectSize(); - const ImVec2 itemPos =ImVec2( - sumX-itemSize.x*0.5f-ImGui::GetScrollX(), - ImGui::GetCursorPos().y-itemSize.y*0.5f-ImGui::GetScrollY() - ); - ImDrawList* drawList = ImGui::GetWindowDrawList(); // main problem is that the sign is covered by the dragging tab (even if the latter is semi-transparent...) - const ImVec2 wp = g.HoveredWindow->Pos; - dd.drawProhibitionSign(drawList,wp,itemPos,dd.draggingTabSrcSize.y*1.2f); - } - } - } - else if (dd.draggingTabSrc && dd.draggingTabSrc!=&tab && g.HoveredRootWindow && g.CurrentWindow) { - // This code should execute only on a drop AFAIK - const int len1 = strlen(g.HoveredRootWindow->Name); - const int len2 = strlen(g.CurrentWindow->Name); - if (strncmp(g.HoveredRootWindow->Name,g.CurrentWindow->Name,len1)==0 && (len1<=len2 || g.CurrentWindow->Name[len1]=='.')) { - //fprintf(stderr,"g.HoveredRootWindow=%s g.CurrentWindow=%s\n",g.HoveredRootWindow?g.HoveredRootWindow->Name:"NULL",g.CurrentWindow?g.CurrentWindow->Name:"NULL"); - dd.draggingTabDst = &tab; - dd.draggingTabNodeDst = this; - dd.draggingTabImGuiWindowDst = g.HoveredWindow; - dd.draggingTabWindowDst = mhs.tabWindow; - } - } - - if (mhs.isRMBclicked && mhs.isWindowHovered && !dd.draggingTabSrc) { - // select it - selection_changed = (selectedTab != &tab); - newSelectedTab = &tab; - tab.mustSelectNextFrame = false; - // see if we need popup menu - if (TabWindow::TabLabelPopupMenuDrawerCb) { - mhs.tabLabelPopup = &tab; - mhs.tabLabelPopupTabWindow = mhs.tabWindow; - mhs.tabLabelPopupChanged = true; - // fprintf(stderr,"open popup\n"); // This gets actually called... - } - } - - } - - } - - selectedTab = newSelectedTab; - if (selection_changed) mhs.tabWindow->activeNode = this; - - if (tabStyle.tabWindowLabelShowAreaSeparator) { - const TabLabelStyle::Colors separatorColor = TabLabelStyle::Col_TabLabelText;//TabLabelStyle::Col_TabLabelSelectedBorder - ImGui::PushStyleColor(ImGuiCol_Separator,ImGui::ColorConvertU32ToFloat4(tabStyle.colors[separatorColor])); - ImGui::Separator(); // We'd need it higher than just one pixel... - ImGui::PopStyleColor(); - } - ImGui::EndGroup();allTabsSize = ImGui::GetItemRectSize(); - - // tab label group popup menu trigger - if (TabWindow::TabLabelGroupPopupMenuDrawerCb && mhs.isRMBclicked && !isAItemHovered) { - ImGuiWindow* window = ImGui::GetCurrentWindow(); - if (ImGui::IsMouseHoveringRect(window->Pos, window->Pos+ImVec2(windowSize.x,allTabsSize.y))) { - mhs.tabLabelGroupPopupChanged = true; - mhs.tabLabelGroupPopup.clear(); - mhs.tabLabelPopupTabWindow = mhs.tabWindow; - mhs.tabLabelGroupPopupNode = this; - for (int i = 0; i < numTabs; i++) { - TabWindow::TabLabel* tab = tabs[i]; - mhs.tabLabelGroupPopup.push_back(tab); // should I check against mhs.TabsToClose ? - } - } - } - - //---------------------------------------------------------------- - mhs.restoreStyleVars(); // needs matching - const ImGuiWindowFlags childFlags = mhs.flags | (selectedTab ? selectedTab->wndFlags : 0); - if (ImGui::RevertUpstreamBeginChildCommit::OldBeginChild("user",ImVec2(0,0),childFlags)) { - /*if (childFlags&ImGuiWindowFlags_ShowBorders) { - // This kind of handling the ImGuiWindowFlags_ShowBorders flag on its own is necessary to achieve what we want - GImGui->CurrentWindow->Flags|=childFlags;//Changed from ImGui::GetCurrentWindow()-> (faster) - }*/ - if (/*selectedTab &&*/ TabWindow::WindowContentDrawerCb) { - TabWindow::WindowContentDrawerCb(selectedTab,*mhs.tabWindow,TabWindow::WindowContentDrawerUserPtr); - } - else { - if (selectedTab) selectedTab->render(); - else { - ImGui::Text("EMPTY TAB LABEL DOCKING SPACE."); - ImGui::Text("PLEASE DRAG AND DROP TAB LABELS HERE!"); - ImGui::Separator(); - ImGui::Spacing(); - ImGui::TextWrapped("And please use ImGui::TabWindow::SetWindowContentDrawerCallback(...) to set a callback to modify this text.");} - } - } - ImGui::EndChild(); // user - mhs.storeStyleVars(); - } - else style.Colors[ImGuiCol_ChildBg] = colorChildWindowBg; - ImGui::EndChild(); // "name" - } - //---------------------------------------------------------------- - - - -} - - -bool TabWindow::ModalDialogSaveDisplay(const char* dialogName,ImVector& TabsToAskFor,ImVector& TabsToAskForParents, -bool closeTabsAfterSaving,bool allowCancel,bool * pMustCloseDialogOut,const char* btnDoNotSaveName,const char* btnSaveName,const char* btnCancelName, -const char* dialogTitleLine1,const char* dialogTitleLine2) { - IM_ASSERT(dialogName && TabsToAskFor.size()>0); - bool mustCloseDialog = false; - const bool open = ImGui::BeginPopupModal(dialogName, NULL, ImGuiWindowFlags_AlwaysAutoResize); - if (open) { - if (dialogTitleLine1) ImGui::Text("%s",dialogTitleLine1); - if (dialogTitleLine2) ImGui::Text("%s",dialogTitleLine2); - ImGui::Separator(); - const int sz = TabsToAskFor.size(); - ImGui::BeginChild("List",ImVec2(0,150),true); - for (int i=0;igetLabel())-(tabLabel->getModified() ? 1 : 0),tabLabel->getLabel()); - } - ImGui::EndChild(); - ImGui::Separator(); - //const ImGuiStyle& style = ImGui::GetStyle(); - //ImGui::PushItemWidth(ImGui::GetWindowWidth()-(allowCancel ? ImGui::CalcTextSize(btnCancelName).x : 0)-ImGui::CalcTextSize(btnDoNotSaveName).x-ImGui::CalcTextSize(btnSaveName).x-2.f*style.ItemSpacing.x-6.f*style.FramePadding.x); - bool cancel = false; - if (allowCancel) {cancel = ImGui::Button(btnCancelName);ImGui::SameLine();} - const bool doNotSave = ImGui::Button(btnDoNotSaveName);ImGui::SameLine(); - const bool save = ImGui::Button(btnSaveName); - //ImGui::PopItemWidth(); - if (cancel) mustCloseDialog = true; - else if (doNotSave || save) { - for (int i=0;isaveAs(NULL); - } - - if (closeTabsAfterSaving) { - if (MyTabWindowHelperStruct::tabLabelPopup == tabLabel) MyTabWindowHelperStruct::tabLabelPopup = NULL; - if (gDragData.draggingTabSrc == tabLabel) gDragData.resetDraggingSrc(); - if (gDragData.draggingTabDst == tabLabel) gDragData.resetDraggingDst(); - for (int j=0;jmainNode->removeTabLabel(tabLabel,true,&tabWindow->activeNode)) { - fprintf(stderr,"Error: Can't delete TabLabel: \"%s\"\n",tabLabel->getLabel()); - } - } - } - mustCloseDialog = true; - } - ImGui::EndPopup(); - if (mustCloseDialog) {ImGuiContext& g = *GImGui; while (g.OpenPopupStack.size() > 0) g.OpenPopupStack.pop_back();} // Close all existing context-menus - //if (cancel) mustCloseDialog = false; - } - if (pMustCloseDialogOut) *pMustCloseDialogOut = mustCloseDialog; - return open; -} - -void TabWindow::render() -{ - IM_ASSERT(ImGui::GetCurrentWindow()); // Call me inside a window - - if (!init) {init=true;} - if (!activeNode) activeNode = mainNode->getFirstLeaftNode(); - - ImVec2 windowSize = ImGui::GetWindowSize(); - windowSize.x-=/*2.f**/ImGui::GetStyle().WindowPadding.x; // It should be 2.f*ImGui::GetStyle().WindowPadding.x, but ImGui::GetStyle().WindowPadding.x seems to work better... - windowSize.y-=(2.f*ImGui::GetStyle().WindowPadding.y+ImGui::GetCurrentWindow()->TitleBarHeight()); - TabWindowDragData& dd = gDragData; - - static int frameCnt = -1; - static bool lastFrameNoDragTabLabelHasBeenDrawn = true; - ImGuiContext& g = *GImGui; - if (frameCnt!=g.FrameCount) { - frameCnt=g.FrameCount; - const bool mustDrawDraggedTabLabel = (!g.HoveredWindow || lastFrameNoDragTabLabelHasBeenDrawn) && dd.draggingTabSrc; - lastFrameNoDragTabLabelHasBeenDrawn = true; - //-------------------------------------------------------------- - // Some "static" actions here: - //-------------------------------------------------------------- - // 1) Close Tabs - //-------------------------------------------------------------- - if (MyTabWindowHelperStruct::TabsToClose.size()>0) { - const int sz = MyTabWindowHelperStruct::TabsToClose.size(); - for (int i=0;imainNode->removeTabLabel(tabLabel,true,&tabWindow->activeNode)) { - fprintf(stderr,"Error: Can't delete TabLabel: \"%s\"\n",tabLabel->getLabel()); - } - } - MyTabWindowHelperStruct::ResetTabsToClose(); - } - - if (MyTabWindowHelperStruct::TabsToAskForClosing.size()>0) { - //fprintf(stderr,"Ok: %d\n",MyTabWindowHelperStruct::TabsToAskForClosing.size()); - if (MyTabWindowHelperStruct::MustOpenAskForClosingPopup) { - ImGuiContext& g = *GImGui; while (g.OpenPopupStack.size() > 0) g.OpenPopupStack.pop_back(); // Close all existing context-menus - ImGui::OpenPopup(TabWindow::GetTabLabelAskForDeletionModalWindowName()); - MyTabWindowHelperStruct::MustOpenAskForClosingPopup = false; - } - bool mustCloseDialog = false; - if (!ModalDialogSaveDisplay(GetTabLabelAskForDeletionModalWindowName(),MyTabWindowHelperStruct::TabsToAskForClosing,MyTabWindowHelperStruct::TabsToAskForClosingParents, - !MyTabWindowHelperStruct::TabsToAskForClosingIsUsedJustToSaveTheseTabs,!MyTabWindowHelperStruct::TabsToAskForClosingDontAllowCancel,&mustCloseDialog)) - MyTabWindowHelperStruct::ResetTabsToAskForClosing(); - if (mustCloseDialog) MyTabWindowHelperStruct::ResetTabsToAskForClosing(); - - } - // 2) Display Tab Menu ------------------------------------------ - if (TabLabelPopupMenuDrawerCb && MyTabWindowHelperStruct::tabLabelPopup) { - if (MyTabWindowHelperStruct::tabLabelPopupChanged) { - MyTabWindowHelperStruct::tabLabelPopupChanged = false; - ImGuiContext& g = *GImGui; while (g.OpenPopupStack.size() > 0) g.OpenPopupStack.pop_back(); // Close all existing context-menus - ImGui::OpenPopup(TabWindow::GetTabLabelPopupMenuName()); - } - TabLabelPopupMenuDrawerCb(MyTabWindowHelperStruct::tabLabelPopup,*MyTabWindowHelperStruct::tabLabelPopupTabWindow,TabLabelPopupMenuDrawerUserPtr); - } - if (TabLabelGroupPopupMenuDrawerCb && MyTabWindowHelperStruct::tabLabelGroupPopup.size()>0) { - if (MyTabWindowHelperStruct::tabLabelGroupPopupChanged) { - MyTabWindowHelperStruct::tabLabelGroupPopupChanged = false; - ImGuiContext& g = *GImGui; while (g.OpenPopupStack.size() > 0) g.OpenPopupStack.pop_back(); // Close all existing context-menus - ImGui::OpenPopup(TabWindow::GetTabLabelGroupPopupMenuName()); - } - TabLabelGroupPopupMenuDrawerCb(MyTabWindowHelperStruct::tabLabelGroupPopup,*MyTabWindowHelperStruct::tabLabelPopupTabWindow,MyTabWindowHelperStruct::tabLabelGroupPopupNode,TabLabelGroupPopupMenuDrawerUserPtr); - } - // 3) Display dragging button only if no hover window is present (otherwise we need to draw something under it before, see below) - if (mustDrawDraggedTabLabel) { - if (dd.draggingTabWindowSrc->isIsolated()) { - dd.resetDraggingSrc(); - MyTabWindowHelperStruct::LockedDragging = true; // consume one click before restrt dragging again - } - else { - const ImVec2& mp = ImGui::GetIO().MousePos; - const ImVec2 wp = dd.draggingTabImGuiWindowSrc->Pos; - ImDrawList* drawList = ImGui::GetWindowDrawList(); - drawList->PushClipRectFullScreen(); // New - dd.drawDragButton(drawList,wp,mp); - } - } - //---------------------------------------------------------------- - gDragData.resetDraggingDst(); - //---------------------------------------------------------------- - } - - MyTabWindowHelperStruct mhs(this); - mainNode->render(windowSize,&mhs); - static const ImGuiWindow* HoveredCorrectChildWindow = NULL; - - // Draw dragging stuff and Apply drag logic ------------------------------------------- - if (g.HoveredRootWindow==ImGui::GetCurrentWindow()) - { - ImGuiStyle& style = ImGui::GetStyle(); - int hoversInt = 0; // 1 = center, 3 = center-top, 4 = center-right, 5 = center-bottom, 2 = center-left, - - // Draw tab label while mouse drags it - if (dd.draggingTabSrc) { - IM_ASSERT(dd.draggingTabImGuiWindowSrc); - const ImVec2& mp = ImGui::GetIO().MousePos; - const ImVec2 wp = dd.draggingTabImGuiWindowSrc->Pos; - ImDrawList* drawList = //ImGui::GetWindowDrawList(); // This draws the dragging tab under the other tabs, and has OTHER problems with: e.g.: ImGui::GetStyle().Colors[ImGuiCol_ChildWindowBg]=ImVec4(0.4,0.4,0.4,1);ImGui::GetStyle().Alpha = 1.f; - &g.OverlayDrawList; // wrong, but it works as expected! [Maybe we can use ChannelsSplit(),ChannelsSetCurrent(),ChannelsMerge(), but that would require modifying code in various spots and it's more error prone] - - - const ImGuiWindow* hoveredWindow = g.HoveredWindow; - //const ImGuiWindow* hoveredRootWindow = g.HoveredRootWindow; - int hoveredWindowNameSz = 0; - //------------------------- - const char* match = NULL; - // Window ----------------- - if (hoveredWindow && hoveredWindow!=dd.draggingTabImGuiWindowSrc - && (hoveredWindowNameSz=strlen(hoveredWindow->Name))>4 && - //strcmp(&hoveredWindow->Name[hoveredWindowNameSz-4],"user")==0 - (match=strstr(hoveredWindow->Name,"user")) - //&& strncmp(g.ActiveIdWindow->Name,hoveredWindow->Name,hoveredWindowNameSz-5)!=0 // works for g.ActiveIdWindow or g.FocusedWindow - ) - { - const int matchLen = match-hoveredWindow->Name+4; - if (matchLen==hoveredWindowNameSz) { - HoveredCorrectChildWindow = hoveredWindow; - //ImGui::SetTooltip("good: \"%s\"",hoveredWindow->Name); - } - else { - if (HoveredCorrectChildWindow && strncmp(HoveredCorrectChildWindow->Name,hoveredWindow->Name,matchLen)==0 && (int)strlen(HoveredCorrectChildWindow->Name)==matchLen) { - //ImGui::SetTooltip("good (reused): \"%s\" for \"%s\"",HoveredCorrectChildWindow->Name,hoveredWindow->Name); - } - else { - HoveredCorrectChildWindow = NULL; - for (int i=0,isz=g.Windows.size();iName,hoveredWindow->Name,matchLen)==0 && (int)strlen(wnd->Name)==matchLen) { - HoveredCorrectChildWindow = wnd; - break; - } - } - //if (HoveredCorrectChildWindow) ImGui::SetTooltip("recalculated: \"%s\" for \"%s\"",HoveredCorrectChildWindow->Name,hoveredWindow->Name); - //else ImGui::SetTooltip("bad: \"%s\"",hoveredWindow->Name); - } - } - if (HoveredCorrectChildWindow) { - hoveredWindow = HoveredCorrectChildWindow; // Mandatory - //--------------------------------------------- - - // Background - const ImVec2 wp = hoveredWindow->Pos; - const ImVec2 ws = hoveredWindow->Size; - ImVec2 start(wp.x,wp.y); - ImVec2 end(start.x+ws.x,start.y+ws.y); - const float draggedBtnAlpha = 0.35f; - const ImVec4& bgColor = style.Colors[ImGuiCol_TitleBg]; - drawList->AddRectFilled(start,end,ImColor(bgColor.x,bgColor.y,bgColor.z,bgColor.w*draggedBtnAlpha),style.FrameRounding); - - // central quad - const float defaultQuadAlpha = 0.75f; - const ImTextureID tid = DockPanelIconTextureID; - ImU32 quadCol = ImColor(1.f,1.f,1.f,defaultQuadAlpha); - ImU32 quadColHovered = ImColor(0.5f,0.5f,1.f,1.f); - const float minDim = ws.x < ws.y ? ws.x : ws.y; - const float MIN_SIZE = 87.5f; - const float MAX_SIZE = 200.5f; - float centralQuadDim = minDim*0.45f; - if (MIN_SIZE>0 && centralQuadDim0 && centralQuadDim>MAX_SIZE) centralQuadDim = MAX_SIZE; - ImVec2 uv0,uv1;bool hovers; - - if (dd.draggingTabWindowSrc->canExchangeTabLabelsWith(this)) { - const float singleQuadDim = centralQuadDim*0.3333333334f; - // central quad top - uv0=ImVec2(0.22916f,0.f);uv1=ImVec2(0.45834f,0.22916f); - start.x = wp.x + (ws.x-singleQuadDim)*0.5f; - start.y = wp.y + (ws.y-singleQuadDim)*0.5f-singleQuadDim; - end.x = start.x+singleQuadDim; - end.y = start.y+singleQuadDim; - hovers = ImGui::IsMouseHoveringRect(start,end,false); - if (hovers) hoversInt = 3; - drawList->AddImage(tid,start,end,uv0,uv1,hovers ? quadColHovered : quadCol); - // central quad right - uv0=ImVec2(0.45834f,0.22916f);uv1=ImVec2(0.6875f,0.45834f); - start.x = wp.x + (ws.x-singleQuadDim)*0.5f + singleQuadDim; - start.y = wp.y + (ws.y-singleQuadDim)*0.5f; - end.x = start.x+singleQuadDim; - end.y = start.y+singleQuadDim; - hovers = ImGui::IsMouseHoveringRect(start,end,false); - if (hovers) hoversInt = 4; - drawList->AddImage(tid,start,end,uv0,uv1,hovers ? quadColHovered : quadCol); - // central quad bottom - uv0=ImVec2(0.22916f,0.45834f);uv1=ImVec2(0.45834f,0.6875f); - start.x = wp.x + (ws.x-singleQuadDim)*0.5f; - start.y = wp.y + (ws.y-singleQuadDim)*0.5f+singleQuadDim; - end.x = start.x+singleQuadDim; - end.y = start.y+singleQuadDim; - hovers = ImGui::IsMouseHoveringRect(start,end,false); - if (hovers) hoversInt = 5; - drawList->AddImage(tid,start,end,uv0,uv1,hovers ? quadColHovered : quadCol); - // central quad left - uv0=ImVec2(0.0f,0.22916f);uv1=ImVec2(0.22916f,0.45834f); - start.x = wp.x + (ws.x-singleQuadDim)*0.5f - singleQuadDim; - start.y = wp.y + (ws.y-singleQuadDim)*0.5f; - end.x = start.x+singleQuadDim; - end.y = start.y+singleQuadDim; - hovers = ImGui::IsMouseHoveringRect(start,end,false); - if (hovers) hoversInt = 2; - drawList->AddImage(tid,start,end,uv0,uv1,hovers ? quadColHovered : quadCol); - // central quad center - uv0=ImVec2(0.22916f,0.22916f);uv1=ImVec2(0.45834f,0.45834f); - start.x = wp.x + (ws.x-singleQuadDim)*0.5f; - start.y = wp.y + (ws.y-singleQuadDim)*0.5f; - end.x = start.x+singleQuadDim; - end.y = start.y+singleQuadDim; - hovers = //hoversInt==0; - ImGui::IsMouseHoveringRect(start,end,false); - if (hovers) hoversInt = 1; - drawList->AddImage(tid,start,end,uv0,uv1,hovers ? quadColHovered : quadCol); - // Refinement: draw remaining 4 inert quads - uv0=ImVec2(0.f,0.f);uv1=ImVec2(0.22916f,0.22916f); - start.x = wp.x + (ws.x-singleQuadDim)*0.5f - singleQuadDim; - start.y = wp.y + (ws.y-singleQuadDim)*0.5f - singleQuadDim; - end.x = start.x+singleQuadDim;end.y = start.y+singleQuadDim; - drawList->AddImage(tid,start,end,uv0,uv1,quadCol); - uv0=ImVec2(0.45834f,0.f);uv1=ImVec2(0.6875f,0.22916f); - start.x = wp.x + (ws.x-singleQuadDim)*0.5f + singleQuadDim; - start.y = wp.y + (ws.y-singleQuadDim)*0.5f - singleQuadDim; - end.x = start.x+singleQuadDim;end.y = start.y+singleQuadDim; - drawList->AddImage(tid,start,end,uv0,uv1,quadCol); - uv0=ImVec2(0.f,0.45834f);uv1=ImVec2(0.22916f,0.6875f); - start.x = wp.x + (ws.x-singleQuadDim)*0.5f - singleQuadDim; - start.y = wp.y + (ws.y-singleQuadDim)*0.5f + singleQuadDim; - end.x = start.x+singleQuadDim;end.y = start.y+singleQuadDim; - drawList->AddImage(tid,start,end,uv0,uv1,quadCol); - uv0=ImVec2(0.45834f,0.45834f);uv1=ImVec2(0.6875f,0.6875f); - start.x = wp.x + (ws.x-singleQuadDim)*0.5f + singleQuadDim; - start.y = wp.y + (ws.y-singleQuadDim)*0.5f + singleQuadDim; - end.x = start.x+singleQuadDim;end.y = start.y+singleQuadDim; - drawList->AddImage(tid,start,end,uv0,uv1,quadCol); - } - else { - hoversInt = 0; - uv0=ImVec2(0.5f,0.75f);uv1=ImVec2(0.75f,1.f); - start.x = wp.x + (ws.x-centralQuadDim)*0.5f; - start.y = wp.y + (ws.y-centralQuadDim)*0.5f; - end.x = start.x+centralQuadDim;end.y = start.y+centralQuadDim; - drawList->AddImage(tid,start,end,uv0,uv1,quadCol); - } - - } - } - else HoveredCorrectChildWindow = NULL; - // Button ----------------- - dd.drawDragButton(drawList,wp,mp); - lastFrameNoDragTabLabelHasBeenDrawn = false; - // ------------------------------------------------------------------- - ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); - } - - // Drop tab label onto another - if (dd.draggingTabDst && dd.draggingTabDst->draggable) { - // swap draggingTabSrc and draggingTabDst - IM_ASSERT(dd.isDraggingSrcValid()); - IM_ASSERT(dd.isDraggingDstValid()); - IM_ASSERT(dd.draggingTabSrc!=dd.draggingTabDst); - - if (dd.draggingTabWindowSrc->canExchangeTabLabelsWith(this)) { - - if (dd.draggingTabNodeSrc!=dd.draggingTabNodeDst) { - bool srcWasSelected = dd.draggingTabNodeSrc->selectedTab == dd.draggingTabSrc; - bool dstWasSelected = dd.draggingTabNodeDst->selectedTab == dd.draggingTabDst; - if (srcWasSelected) dd.draggingTabNodeSrc->selectedTab = dd.draggingTabDst; - if (dstWasSelected) dd.draggingTabNodeDst->selectedTab = dd.draggingTabSrc; - } - - const int iSrc = dd.findDraggingSrcIndex(); - IM_ASSERT(iSrc>=0); - const int iDst = dd.findDraggingDstIndex(); - IM_ASSERT(iDst>=0); - dd.draggingTabNodeDst->tabs[iDst] = dd.draggingTabSrc; - dd.draggingTabNodeSrc->tabs[iSrc] = dd.draggingTabDst; - - dd.reset(); - //fprintf(stderr,"Drop tab label onto another\n"); - } - } - - // Reset draggingTabIndex if necessary - if (!MyTabWindowHelperStruct::isMouseDragging) { - if (hoversInt && HoveredCorrectChildWindow && dd.draggingTabSrc && dd.draggingTabImGuiWindowSrc && dd.draggingTabImGuiWindowSrc!=g.HoveredWindow && dd.draggingTabImGuiWindowSrc!=HoveredCorrectChildWindow) - { - // Drop tab label onto a window portion - int nameSz = strlen(HoveredCorrectChildWindow->Name); - static const char trailString[] = ".user"; - static const int trailStringSz = (int) strlen(trailString); - IM_ASSERT(nameSz>=trailStringSz); - IM_ASSERT(strcmp(&HoveredCorrectChildWindow->Name[nameSz-trailStringSz],trailString)==0); - const char* startMatchCh = strstr(HoveredCorrectChildWindow->Name,".##main"),*startMatchCh2 = NULL; - if (startMatchCh) { - while ((startMatchCh2 = strstr(&HoveredCorrectChildWindow->Name[(int)(startMatchCh-HoveredCorrectChildWindow->Name)+7],".##main"))) { - startMatchCh = startMatchCh2; - } - } - const int startMatchIndex = startMatchCh ? ((int)(startMatchCh-HoveredCorrectChildWindow->Name)+1) : 0; - IM_ASSERT(nameSz>=trailStringSz-startMatchIndex); - - ImVector tmp;tmp.resize(nameSz); - strncpy(&tmp[0],&HoveredCorrectChildWindow->Name[startMatchIndex],nameSz-trailStringSz-startMatchIndex); - tmp[nameSz-trailStringSz-startMatchIndex]='\0'; - //fprintf(stderr,"\"%s\"\n",&tmp[0]); - dd.draggingTabNodeDst = TabWindowDragData::FindTabNodeByName(mainNode,&tmp[0]); - - //fprintf(stderr,"Item: \"%s\" dragged to window:\"%s\" at pos: %d\n",dd.draggingTabSrc->label,HoveredCorrectChildWindow ? HoveredCorrectChildWindow->Name : "NULL",hoversInt); - //if (dd.draggingTabNodeDst) fprintf(stderr,"dd.draggingTabNodeDst->tabs.size()=%d\n",(int)dd.draggingTabNodeDst->tabs.size()); - //else fprintf(stderr,"No dd.draggingTabNodeDst.\n"); - //TODO: move dd.draggingTabSrc and delete the src node if empty------------ - // How can I find dd.draggingTabNodeDst from HoveredCorrectChildWindow? - // I must strip ".HorizontalStrip.content.user" and then seek TabNode::Name - //------------------------------------------------------------------------- - if (dd.draggingTabNodeDst) { - if (hoversInt!=1 && dd.draggingTabNodeDst->tabs.size()==0) hoversInt=1; - if (!(dd.draggingTabNodeDst==dd.draggingTabNodeSrc && (dd.draggingTabNodeDst->tabs.size()==0 || hoversInt==1))) { - // We must: - - // 1) remove dd.draggingTabSrc from dd.draggingTabNodeSrc - if (!dd.draggingTabNodeSrc->removeTabLabel(dd.draggingTabSrc,false,&dd.draggingTabNodeDst,true)) { - //fprintf(stderr,"Error: !dd.draggingTabNodeSrc->removeTabLabel(dd.draggingTabSrc,false,&activeNode,true): \"%s\"\n",dd.draggingTabSrc->getLabel()); - } - // 2) append if to dd.draggingTabNodeDst - activeNode = dd.draggingTabNodeDst->addTabLabel(dd.draggingTabSrc,hoversInt==1 ? -1 : hoversInt-2); - //---------------------------------------------------- - } - //else fprintf(stderr,"Do nothing.\n"); - } - - dd.resetDraggingDst(); - } - if (dd.draggingTabSrc) dd.resetDraggingSrc(); - MyTabWindowHelperStruct::LockedDragging = false; - } - } - -} - -void TabWindow::clearNodes() { - if (mainNode) { - mainNode->~TabWindowNode(); - ImGui::MemFree(mainNode); - mainNode=NULL; - } - activeNode = NULL; -} -void TabWindow::clear() {mainNode->clear();activeNode=mainNode;} - - -TabWindow::TabWindow() { - mainNode = (TabWindowNode*) ImGui::MemAlloc(sizeof(TabWindowNode)); - IM_PLACEMENT_NEW(mainNode) TabWindowNode(); - mainNode->name = (char*) ImGui::MemAlloc(7);strcpy(mainNode->name,"##main"); - activeNode=mainNode; - init=false; - userPtr=NULL; - isolatedMode=false; - -} -TabWindow::~TabWindow() {clearNodes();} - -void TabWindow::excludeTabWindow(TabWindow &tw) { - for (int i=0,isz=tabWindowsToExclude.size();iisIsolated())) || !tw) return false; - for (int i=0,isz=tabWindowsToExclude.size();iisLeafNode() && node->parent); -} -int TabWindow::getNumTabs(TabWindowNode *node) { - return node ? node->tabs.size() : 0; -} -int TabWindow::getNumClosableTabs(TabWindowNode *node) { - return (node && node->tabs.size()>0) ? node->numClosableTabs : 0; -} -bool TabWindow::merge(TabWindowNode *node) { - if (!node || !isMergeble(node)) return false; - {TabWindowNode* n = node;while (n->parent) n=n->parent;if (n!=mainNode) return false;} // checks if tabNode belongs to this TabWindow - - if (!node->mergeToParent(&activeNode)) return false; - node = NULL; // it's invalid now - - /*TabWindowNode* parent = node->parent; - ImVector tabs;int sz=0; - while ((sz=node->tabs.size())>0) { - TabLabel* tab = node->tabs[0]; - tabs.push_back(tab); - node->removeTabLabel(tab,false,&activeNode,true); - if (sz==1) break; - } - node = NULL; // it's invalid now - parent = parent->getFirstLeaftNode(); - for (int i=0,isz = tabs.size();iaddTabLabel(tabs[i]); - }*/ - - return true; -} - -void TabWindow::GetAllTabLabels(TabWindow *pTabWindowsIn, int numTabWindowsIn, ImVector &tabsOut, ImVector &parentsOut,bool onlyClosableTabs,bool onlyModifiedTabs) { - IM_ASSERT(pTabWindowsIn && numTabWindowsIn>0); - tabsOut.clear();parentsOut.clear(); - for (int i=0;igetTabLabels(tabsOut,onlyClosableTabs,onlyModifiedTabs); - if (startTabs!=tabsOut.size()) { - parentsOut.resize(tabsOut.size()); - for (int j=startTabs,jsz=tabsOut.size();j &tabs, ImVector &parents,bool saveAll, bool askForSaving, bool allowCancelDialog,bool dontCloseTabs) { - bool mustStartDialog = false; - if (tabs.size()==0) return false; - IM_ASSERT(tabs.size()==parents.size()); - for (int i=0,isz=tabs.size();imodified) { - if (askForSaving) { - if (!mustStartDialog) { - MyTabWindowHelperStruct::ResetTabsToAskForClosing(); - MyTabWindowHelperStruct::TabsToAskForClosingDontAllowCancel = !allowCancelDialog; - MyTabWindowHelperStruct::TabsToAskForClosingIsUsedJustToSaveTheseTabs = dontCloseTabs; - mustStartDialog = true; - } - MyTabWindowHelperStruct::TabsToAskForClosing.push_back(tab); - MyTabWindowHelperStruct::TabsToAskForClosingParents.push_back(tw); - } - else { - bool ok = false; - if (TabWindow::TabLabelSaveCb) ok = TabWindow::TabLabelSaveCb(tab,*tw,NULL); // can't we check return value ? - else ok = tab->saveAs(NULL); - } - } - } - if (!dontCloseTabs) { - if (!mustStartDialog || (tab!=MyTabWindowHelperStruct::TabsToAskForClosing[MyTabWindowHelperStruct::TabsToAskForClosing.size()-1])) { - // delete tab - //fprintf(stderr,"Deleting tab: %s\n",tab->getLabel()); - tw->mainNode->removeTabLabel(tab,true,&tw->activeNode,false); - } - } - } - - //---------------------------------------------------------------------------------------------------------------------------------- - if (mustStartDialog) { - MyTabWindowHelperStruct::MustOpenAskForClosingPopup = true; - //fprintf(stderr,"Ok: %d\n",MyTabWindowHelperStruct::TabsToAskForClosing.size()); - } - return mustStartDialog; -} -bool TabWindow::AreSomeDialogsOpen() {return (GImGui->OpenPopupStack.size() > 0);} - -void TabWindow::getAllTabLabels(ImVector &tabsOut, bool onlyClosableTabs, bool onlyModifiedTabs) {tabsOut.clear();mainNode->getTabLabels(tabsOut,onlyClosableTabs,onlyModifiedTabs);} -void TabWindow::saveAll(ImVector *ptabs) { - ImVector tabs; - if (!ptabs) { - getAllTabLabels(tabs,false,true); - ptabs = &tabs; - } - for (int i=0,isz=ptabs->size();isaveAs(NULL); - } -} -bool TabWindow::startCloseAllDialog(ImVector *ptabs, bool allowCancelDialog) { - ImVector tabs; - ImVector tws; - if (!ptabs) { - getAllTabLabels(tabs,false,false); - ptabs = &tabs; - } - tws.resize(ptabs->size()); - for (int i=0,isz=ptabs->size();iserialize(s,this); - return true; -} -bool TabWindow::save(const char* filename) const { - ImGuiHelper::Serializer s(filename); - return save(s); -} -bool TabWindow::Save(const char *filename, const TabWindow *pTabWindows, int numTabWindows) { - IM_ASSERT(pTabWindows && numTabWindows>0); - ImGuiHelper::Serializer s(filename); - bool ok = true; - for (int i=0;iclear(); // Well, shouldn't we ask for modified unclosed tab labels here ? - - const char* amount = pOptionalBufferStart ? (*pOptionalBufferStart) : 0; - mainNode->deserialize(d,NULL,amount,this); - if (pOptionalBufferStart) *pOptionalBufferStart = amount; - - mainNode->mergeEmptyLeafNodes(&activeNode); // optional, but should fix hierarchy problems - if (!activeNode) activeNode=mainNode; - if (!activeNode->isLeafNode()) activeNode = activeNode->getFirstLeaftNode(); - IM_ASSERT(activeNode && activeNode->isLeafNode()); - - return true; -} -bool TabWindow::load(const char* filename) { - ImGuiHelper::Deserializer d(filename); - return load(d); -} -bool TabWindow::Load(const char *filename, TabWindow *pTabWindows, int numTabWindows) { - IM_ASSERT(pTabWindows && numTabWindows>0); - for (int i=0;iuserPtr = userPtr; - tab->setUserText(userText); - tab->userInt =userInt; - tab->wndFlags = ImGuiWindowFlagsForContent; - } - return tab; -} -TabWindow::TabLabel *TabWindow::addTabLabel(const char *label, const char *tooltip,bool closable, bool draggable, void *userPtr, const char *userText,int userInt,int ImGuiWindowFlagsForContent) { - TabLabel* tab = createTabLabel(label,tooltip,closable,draggable,userPtr,userText,userInt,ImGuiWindowFlagsForContent); - if (!activeNode) activeNode = mainNode->getFirstLeaftNode(); - if (tab) activeNode = activeNode->addTabLabel(tab); - return tab; -} -TabWindow::TabLabel *TabWindow::addTabLabel(TabLabel *tabLabel, bool checkIfAlreadyPresent) { - if (!tabLabel) return NULL; - if (checkIfAlreadyPresent && mainNode->findTabLabel(tabLabel,true)) return tabLabel; - if (!activeNode) activeNode = mainNode->getFirstLeaftNode(); - activeNode = activeNode->addTabLabel(tabLabel); - return tabLabel; -} -bool TabWindow::removeTabLabel(TabWindow::TabLabel *tab) { - if (!tab) return false; - if (!mainNode->removeTabLabel(tab,true,&activeNode)) { - fprintf(stderr,"Error: cannot remove TabLabel: \"%s\"\n",tab->getLabel()); - return false; - } - return true; -} - -TabWindow::TabLabel *TabWindow::findTabLabelFromLabel(const char *label) const { - return mainNode->findTabLabelFromLabel(label); -} -TabWindow::TabLabel *TabWindow::findTabLabelFromTooltip(const char *tooltip) const { - return mainNode->findTabLabelFromTooltip(tooltip); -} -TabWindow::TabLabel *TabWindow::findTabLabelFromUserPtr(void *userPtr) const { - return mainNode->findTabLabelFromUserPtr(userPtr); -} -TabWindow::TabLabel *TabWindow::findTabLabelFromUserText(const char *userText) const { - return mainNode->findTabLabelFromUserText(userText); -} -TabWindow::TabLabel *TabWindow::FindTabLabelFromLabel(const char *label, const TabWindow *pTabWindows, int numTabWindows, int *pOptionalTabWindowIndexOut) { - IM_ASSERT(pTabWindows && numTabWindows>0); - TabLabel* rv = NULL; - for (int i=0;i0); - TabLabel* rv = NULL; - for (int i=0;i0); - TabLabel* rv = NULL; - for (int i=0;i0); - TabLabel* rv = NULL; - for (int i=0;i0 && (selectedIndex<0 || selectedIndex>=numTabs)) { - if (!pOptionalItemOrdering) selectedIndex = 0; - else selectedIndex = -1; - } - if (pOptionalHoveredIndex) *pOptionalHoveredIndex = -1; - if (pOptionalClosedTabIndex) *pOptionalClosedTabIndex = -1; - if (pOptionalClosedTabIndexInsideItemOrdering) *pOptionalClosedTabIndexInsideItemOrdering = -1; - - float windowWidth = 0.f,sumX=0.f; - if (wrapMode) windowWidth = ImGui::GetWindowWidth() - style.WindowPadding.x - (ImGui::GetScrollMaxY()>0 ? style.ScrollbarSize : 0.f); - - static int draggingTabIndex = -1;int draggingTabTargetIndex = -1; // These are indices inside pOptionalItemOrdering - static bool draggingTabWasSelected = false; - static ImVec2 draggingTabSize(0,0); - static ImVec2 draggingTabOffset(0,0); - static bool draggingLocked = false; - - const bool isRMBclicked = ImGui::IsMouseClicked(1); - const bool isMouseDragging = ImGui::IsMouseDragging(0,3.f); - const bool isMouseDraggingJustStarted = isMouseDragging && (ImGui::GetIO().MouseDownDuration[0] < 0.35f);// ImGui::GetIO().MouseDown[0] does not work! - int justClosedTabIndex = -1,newSelectedIndex = selectedIndex; - - ImVec2 startGroupCursorPos = ImGui::GetCursorPos(); - ImGui::BeginGroup(); - ImVec2 tabButtonSz(0,0);bool mustCloseTab = false;bool canUseSizeOptimization = false; - const bool isWindowHovered = ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem); - bool selection_changed = false;bool noButtonDrawn = true; - for (int j = 0,i; j < numTabs; j++) - { - i = pOptionalItemOrdering ? pOptionalItemOrdering[j] : j; - if (i==-1) continue; - - if (!wrapMode) {if (!noButtonDrawn) ImGui::SameLine();canUseSizeOptimization=false;} - else if (sumX > 0.f) { - sumX+=style.ItemSpacing.x; // Maybe we can skip it if we use SameLine(0,0) below - ImGui::TabButton(tabLabels[i],(i == selectedIndex),allowTabClosing ? &mustCloseTab : NULL,NULL,&tabButtonSz,&tabStyle); - sumX+=tabButtonSz.x; - if (sumX>windowWidth) sumX = 0.f; - else ImGui::SameLine(); - canUseSizeOptimization = true; - } - else canUseSizeOptimization = false; - - // Draw the button - ImGui::PushID(i); // otherwise two tabs with the same name would clash. - if (ImGui::TabButton(tabLabels[i],i == selectedIndex,allowTabClosing ? &mustCloseTab : NULL,NULL,NULL,&tabStyle,NULL,NULL,NULL,canUseSizeOptimization)) { - selection_changed = (selectedIndex!=i); - newSelectedIndex = i; - } - ImGui::PopID(); - noButtonDrawn = false; - - if (wrapMode) { - if (sumX==0.f) sumX = style.WindowPadding.x + ImGui::GetItemRectSize().x; // First element of a line - } - else if (isMouseDragging && allowTabReorder && pOptionalItemOrdering) { - // We still need sumX - if (sumX==0.f) sumX = style.WindowPadding.x + ImGui::GetItemRectSize().x; // First element of a line - else sumX+=style.ItemSpacing.x + ImGui::GetItemRectSize().x; - - } - - if (isWindowHovered && ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly) && !mustCloseTab) { - if (pOptionalHoveredIndex) *pOptionalHoveredIndex = i; - if (tabLabelTooltips && !isRMBclicked && tabLabelTooltips[i] && strlen(tabLabelTooltips[i])>0) ImGui::SetTooltip("%s",tabLabelTooltips[i]); - - if (pOptionalItemOrdering) { - if (allowTabReorder) { - if (isMouseDragging) { - if (draggingTabIndex==-1 && !draggingLocked && isMouseDraggingJustStarted) { - draggingTabIndex = j; - draggingTabWasSelected = (i == selectedIndex); - draggingTabSize = ImGui::GetItemRectSize(); - const ImVec2& mp = ImGui::GetIO().MousePos; - const ImVec2 draggingTabCursorPos = ImGui::GetCursorPos(); - draggingTabOffset=ImVec2( - mp.x+draggingTabSize.x*0.5f-sumX+ImGui::GetScrollX(), - mp.y+draggingTabSize.y*0.5f-draggingTabCursorPos.y+ImGui::GetScrollY() - ); - - } - } - else if (draggingTabIndex>=0 && draggingTabIndex=0 && draggingTabIndexOverlayDrawList; - const TabLabelStyle& tabStyle = TabLabelStyleGetMergedWithAlphaForOverlayUsage(); - ImFont* fontOverride = (ImFont*) (draggingTabWasSelected ? TabLabelStyle::ImGuiFonts[tabStyle.fontStyles[TabLabelStyle::TAB_STATE_SELECTED]] : TabLabelStyle::ImGuiFonts[tabStyle.fontStyles[TabLabelStyle::TAB_STATE_NORMAL]]); - ImGui::TabButton(tabLabels[pOptionalItemOrdering[draggingTabIndex]],draggingTabWasSelected,allowTabClosing ? &mustCloseTab : NULL,NULL,NULL,&tabStyle,fontOverride,&start,drawList,false,true); - ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); - - if (TabWindow::DockPanelIconTextureID) { - // Optional: draw prohibition sign when dragging too far (you can remove this if you want) - startGroupCursorPos.y+=deltaY*.5f; - groupSize.y-=deltaY; - if (!ImGui::IsMouseHoveringRect(startGroupCursorPos,startGroupCursorPos+groupSize)) { - const float signWidth = draggingTabSize.y*1.25f; - start.x+=(draggingTabSize.x-signWidth)*0.5f; - start.y+=(draggingTabSize.y-signWidth)*0.5f; - const ImVec2 end(start.x+signWidth,start.y+signWidth); - const ImVec4 color(1.f,1.f,1.f,0.85f); - drawList->AddImage(TabWindow::DockPanelIconTextureID,start,end,ImVec2(0.5f,0.75f),ImVec2(0.75f,1.f),ImGui::ColorConvertFloat4ToU32(color)); - } - } - } - else { - draggingTabIndex = -1;draggingTabTargetIndex=-1; - draggingLocked = true;// consume one mouse release - } - } - - // Drop tab label - if (draggingTabTargetIndex!=-1) { - // swap draggingTabIndex and draggingTabTargetIndex in pOptionalItemOrdering - const int tmp = pOptionalItemOrdering[draggingTabTargetIndex]; - pOptionalItemOrdering[draggingTabTargetIndex] = pOptionalItemOrdering[draggingTabIndex]; - pOptionalItemOrdering[draggingTabIndex] = tmp; - //fprintf(stderr,"%d %d\n",draggingTabIndex,draggingTabTargetIndex); - draggingTabTargetIndex = draggingTabIndex = -1; - } - - // Reset draggingTabIndex if necessary - if (!isMouseDragging) {draggingTabIndex = -1;draggingLocked=false;} - - // Change selected tab when user closes the selected tab - if (selectedIndex == justClosedTabIndex && selectedIndex>=0) { - selectedIndex = -1; - for (int j = 0,i; j < numTabs; j++) { - i = pOptionalItemOrdering ? pOptionalItemOrdering[j] : j; - if (i==-1) continue; - selectedIndex = i; - break; - } - } - - // Restore the style - style.ItemSpacing = itemSpacing; - - return selection_changed; -} - -//------------------------------------------------------------------------------- -# if (defined(IMGUIHELPER_H_) && !defined(NO_IMGUIHELPER_SERIALIZATION)) -# ifndef NO_IMGUIHELPER_SERIALIZATION_SAVE - bool TabLabelsSave(ImGuiHelper::Serializer& s,int selectedIndex,const int* pOptionalItemOrdering,int numTabs) { - if (!s.isValid()) return false; - if (numTabs<0 || !pOptionalItemOrdering) numTabs=0; - s.save(&numTabs,"TabLabelsNumTabs"); - s.save(&selectedIndex,"TabLabelsSelectedIndex"); - for (int i=0;i4) num=4; - s.save(&pOptionalItemOrdering[i],"TabLabelsOrdering",num); - } - return true; - } - bool TabLabelsSave(const char* filename,int selectedIndex,const int* pOptionalItemOrdering,int numTabs) { - ImGuiHelper::Serializer s(filename); - return TabLabelsSave(s,selectedIndex,pOptionalItemOrdering,numTabs); - } -# endif //NO_IMGUIHELPER_SERIALIZATION_SAVE -# ifndef NO_IMGUIHELPER_SERIALIZATION_LOAD - struct TabLabelsParser { - int* pSelectedIndex;int* pOptionalItemOrdering;int numTabs,numSavedTabs,cnt; - TabLabelsParser(int* _pSelectedIndex,int* _pOptionalItemOrdering,int _numTabs) : pSelectedIndex(_pSelectedIndex),pOptionalItemOrdering(_pOptionalItemOrdering),numTabs(_numTabs),numSavedTabs(0),cnt(0) {} - static bool Parse(ImGuiHelper::FieldType /*ft*/,int numArrayElements,void* pValue,const char* name,void* userPtr) { - TabLabelsParser& P = *((TabLabelsParser*) userPtr); - const int* pValueInt = (const int*) pValue; - if (strcmp(name,"TabLabelsNumTabs")==0) P.numSavedTabs = *pValueInt; - else if (strcmp(name,"TabLabelsSelectedIndex")==0) { - if (P.pSelectedIndex) *P.pSelectedIndex = *pValueInt; - if (P.numSavedTabs==0) return true; - } - else if (strcmp(name,"TabLabelsOrdering")==0) { - for (int i=0;i0 && P.pOptionalItemOrdering && P.cnt& rgba_buffer_out, int* w_out, int* h_out) { - // I have drawn all the icons that compose this image myself. - // I took inspiration from the icons bundled with: https://github.com/dockpanelsuite/dockpanelsuite (that is MIT licensed). - // So no copyright issues for this AFAIK. - - const int w = 128;const int h = 128; - if (w_out) *w_out = w; - if (h_out) *h_out = h; - - const unsigned int palette[54] = { // each is a 4 bytes (RGBA) unsigned integer in little-endian encoding - 0U,4290098613U,4291413446U,4291282887U,4290624957U,4291743181U,4292138196U,4292467161U,4292598747U,4289686099U,4289625722U,4288176475U,4292203989U,4292664540U,4292795869U,4293624876U,4293232449U,4293302112U,4294626468U,4293256419U,4291201680U,4294760628U,4294760113U,4294827453U,4290678164U,4294828744U,4294503133U,4294901244U,4290815144U,4294306530U, - 4293518821U,4292729306U,4292596434U,4294967295U,4290295221U,4292203732U,4290097844U,4294829260U,4291652670U,4294696642U,16777216U,620954113U,1996883201U,2667906306U,1409614848U,3422816012U,3741452830U,4227862605U,4278196610U,4278198180U,4278265018U,4285032038U,4281484145U,4278199226U}; - - unsigned char indices[w*h+1] = // [width x height (+ end string char)]: each (char-'0'), [skipping (char)92], points to a palette entry - "00000000000000000000000000000111111111111111111111111111110000000000000000000000000000000000000001111111111111111111111111111100000000000000000000000000000001222333333333333333333333333100000000000000000000000000000000000000014233333333333333333333333331000000000000000000000000000000012335555555555555555555555551000000000000000000000000000000000000000125566666666666666666666666610000000000000000000000000000000123577777777777777777777777710000000000000000000000000000000000000001357888888888888888888888888100000000000000000000000000000001357999999999999999999::;<=>10000000000000000000000000000000000000001368999999999999999999::;<8>1000000000000000000000000000000013579?""?""?""?""?@@@@AAAAAAAAAB:47C100000000000000000000000000000000000000013689?""?""?""?""?@@@@AAAAAAAAAB:2<>10000000000000000000000000000000135" \ - "79?""?""?""?""?@@@@AAAAAAAAAB:16C100000000000000000000000000000000000000013689?""?""?""?""?@@@@AAAAAAAAAB:46>100000000000000000000000000000001357DBBBBBBBBBBBBBBBBBBBD16C10000000000000000000000000000000000000001368DBBBBBBBBBBBBBBBBBBBD46>100000000000000000000000000000001357DEEEEEEFFFFFFFFFFFFFD16C10000000000000000000000000000000000000001368DEEEEEEFFFFFFFFFFFFFD46>100000000000000000000000000000001357DGGGGGGGGGGGGGGGGGGGH16C10000000000000000000000000000000000000001368DGGGGGGGGGGGGGGGGGGGH46>100000000000000000000000000000001357DIIIIIIIIIIIIIIIIIIIH16C10000000000000000000000000000000000000001368DIIIIIIIIIIIIIIIIIIIH46>100000000000000000000000000000001357DJJJJJJJJJJJJJJJJJJJD16C10000000000000000000000000000000000000001368DJJJJJJJJJJJJJJJJJJJD46>100000000000000000000000000000001357DJJJJJJJJJJJJJJJJJJJD16C1000000" \ - "0000000000000000000000000000000001368DJJJJJJJJJJJJJJJJJJJD46>100000000000000000000000000000001357HKKKKKKKKKKKKKKKKKKKL16C10000000000000000000000000000000000000001368HKKKKKKKKKKKKKKKKKKKL46>100000000000000000000000000000001357DMNMJNMNMJOMNNMONMNMD16C10000000000000000000000000000000000000001368DMNMJNMNMJOMNNMONMNMD46>100000000000000000000000000000001357:O5ON5OPON5OOONPONPND16C10000000000000000000000000000000000000001368:O5ON5OPON5OOONPONPND46>100000000000000000000000000000001357HQQQQQQQQQQQQQQQQQQQL16C10000000000000000000000000000000000000001368HQQQQQQQQQQQQQQQQQQQL46>100000000000000000000000000000001357HQQQQQQQQQQQQQQQQQQQL16C10000000000000000000000000000000000000001368HQQQQQQQQQQQQQQQQQQQL46>100000000000000000000000000000001357HQQQQQQQQKLKQQQQQQQQL16C100000000000000000000000000000000000000" \ - "01368HQQQQQQQQKLKQQQQQQQQL46>100000000000000000000000000000001357HQQQQQQQKD;LKQQQQQQQL16C10000000000000000000000000000000000000001368HQQQQQQQKD;LKQQQQQQQL46>100000000000000000000000000000001357HQQQQQQKL;;;LKQQQQQQL16C10000000000000000000000000000000000000001368HQQQQQQKL;;;LKQQQQQQL46>100000000000000000000000000000001357HQQQQQKL99999RKQQQQQL16C10000000000000000000000000000000000000001368HQQQQQKL99999RKQQQQQL46>100000000000000000000000000000001357HQQQQK2:::::::OQQQQQL16C10000000000000000000000000000000000000001368HQQQQK2:::::::OQQQQQL46>100000000000000000000000000000011357HQQQQKOPPPPPPPNQQQQQL16C11000000000000000000000000000000000000001368HQQQQKOPPPPPPPNQQQQQL46>100000000000000000000000000000122357HQQQQQQQQQQQQQQQQQQQL16C>=100000000000000000000000000000000000001368HQQQQQQQQQQQQQQQQQQQL46>100" \ - "0000000000000000000000000012223S=HQQQQQQQQQQQQQQQQQQQL16CC>710000000000000000000000000000000000001368HQQQQQQQQQQQQQQQQQQQL46>10000000000000000000000000001223357>HQQQQQQQQQQQQQQQQQQQL16CCC>71000000000000000000000000000000000001368HQQQQQQQQQQQQQQQQQQQL46>1000000000000000000000000001223557>C;DDDDDDDDDDDDDDDDDDD916CCCC>7100000000000000000000000000000000001368;DDDDDDDDDDDDDDDDDDD946>100000000000000000000000001223577>CC74111111111111111111147CCCCC>710000000000000000000000000000000001368<244444444444444444442<>10011111111111111111111111122357>CCC>766666666666666666667>CCCCCC>711111111111111111111111100000000013688<6666666666666666666<8>1001222333333333333333333333357>CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC>7533333333333333333333310000000001368>>>>>>>>>>>>>>>>>>>>>>>>1001233555555555555555555555S7>CCCC" \ - "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC>7S555555555555555555551000000000TTTTTTTTTTTTTTTTTTTTTTTTTTTTT001235777777777777777777777=>CCCCC;;;;99999999999999::::;7>CCCCCCCC>=77777777777777777777100000000000000000000000000000000000000001357999DDDDDDD:DDDDDDDDDDDD97>CC9?""?""?""?""?""?@@@@AAAAAAAAABB:47CCC9DDDDDDDDDDD:DDDDDDD:999<=>100000000T111111111111111111111111111111113579?""?BFGIJJJPKQQQQQQQQQQQD47CC9?""?""?""?""?""?@@@@AAAAAAAAABB:16CCCDQQQQQQQQQQQOMKJJIGEB?""?947C100000000T333333333333333333333333333324113579?""?BFGIJJJ2KQQQQQQQQQQQD16CC:IJPOJOMOOJOJOOJOOMOMOD16CCCDQQQQQQQQQQQ2OKJJUGEB?""?916C100000000T666666666666666666666666666552113579?""?BFGIJJJPKQQQQQQQQQQQD16CC:BBLLBLBLLBLBBLBLBBBBL:16CCCDQQQQQQQQQQQOMKJJIGEB?""?916C100000000T888888888888888888888888888753113579?""?BFGIJJMNKQQQQQQQQQQQD16CC:LBFFFFFFFFFFFFFFFFFFBD16CCCDQQQ" \ - "QQQQQQQQNJKJJIGEB?""?916C100000000T>8<9DDDDDDDDDDD:DDDDDDD:999863113579?""?BFGIJJJ2KQQQQQQKKQQQD16CC:BFFFFFFFFFFFFFFFFFFFFD16CCCDQQQKKQQQQQQ5OKJJIGEB?""?916C100000000T><2DQQQQQQQQQQQOMKJJIGEB?""?9863113579@@BFGIJJJPKQQQQQKLMQQQD16CC:BFFFFFFFFFFFFFFFFFFEL:16CCCDQQQN2QQQQQQOMKJJIGEB@@916C100000000T>64DQQQQQQQQQQQ2OKJJUGEB?""?986311357V@@BFGIJJJPKQQQQKH:MQQQD16CC:BEEEEEEEEEEEEEEEEEEEBD16CCCDQQQP:LKQQQQPNKJJIGEB@@916C100000000T>64DQQQQQQQQQQQOMKJJIGEB?""?986311357V@@BFGIJJJPKQQQMH9:MQQQD16CCDJEEGGEGGGGGGGGGGGGGGBD16CCCDQQQP:9LKQQQONKJJIGEB@@916C100000000T>64DQQQQQQQQQQQNJKJJIGEB?""?986311357V@@BFGIJJMNKQQM:;9:MQQQD16CC:BGGGGGGGGGGGGGGGGGGGBD16CCCDQQQP:9;DKQQMJKJJIGEB@@916C100000000T>64DQQQKKQQQQQQ5OKJJIGEB?""?9863113579AABFGIJJJ2KQKD;;9:MQQQD16CC:BWWWWWWWWWWWWWWWWWWWFD16CCCDQQQP:9;;LQQ5OKJJIGFBAA916C100000000" \ - "T>64DQQQN2QQQQQQOMKJJIGEB@@9863113579AABFGIJJJPKQQN:;9:MQQQD16CC:PIIIIIIIIIIIIIIIIIIUL:16CCCDQQQP:9;LKQQONKJJIGFBAA916C100000000T>64DQQQP:LKQQQQPNKJJIGEB@@9863113579AABFGIJJJPKQQQKH9:NQQQD16CC:2IUUUUUUUUUUUUUUUUUJ2D16CCCDQQQP:9LKQQQONKJJIGFBAA916C100000000T>64DQQQP:9LKQQQONKJJIGEB@@9863113579AABFGIJJJPKQQQQKL:NQQQD16CC:UJJJJJJJJJJJJJJJJJJJID16CCCDQQQP:LKQQQQPNKJJIGFBAA916C100000000T>64DQQQP:9;DKQQMJKJJIGEB@@9863113579AABFGIJJMNKQQQQQK2MQQQD16CC:PJJJJJJJJJJJJJJJJJJJLD16CCCDQQQNOKQQQQQNJKJJIGFBAA916C100000000T>64DQQQP:9;;LQQ5OKJJIGFBAA9863113579AABFGIJJJ2KQQQQQQQQQQQD16CC:2JJJJJJJJJJJJJJJJJJJ5D16CCCDQQQQQQQQQQQPOKJJIGFBAA916C100000000T>64DQQQP:9;LKQQONKJJIGFBAA9863113579AABFGIJJJPKQQQQQQQQQQQD16CC:MJJJJJ2UPUIPJIWUPJUW2D16CCCDQQQQQQQQQQQONKJJIGFBAA916C100000000T>64DQQQP:9LKQQQONKJJIGFBAA98631" \ - "1357:AABFGIJJMOKQQQQQQQQQQQD16CC:PJJJJLO2R22L5R22L52LP:16CCCDQQQQQQQQQQQNMKJJIGFBAA:16C100000000T>64DQQQP:LKQQQQPNKJJIGFBAA986311357:AABFGIJJJPKQQQQQQQQQQQD16CC:PJJJJJN:2224R2;2RLLLR:16CCCDQQQQQQQQQQQPNKJJIGFBAA:16C100000000T>64DQQQNOKQQQQQNJKJJIGFBAA986311357:BBBEGIJJMOKQQQQQQQQQQQD16CC:PMJJJ2NLQQQQQQ:QQQQQQD16CCCDQQQQQQQQQQQNMKJJIGFBBB:16C100000000T>64DQQQQQQQQQQQPOKJJIGFBAA986311357;::DDDDDLL:LLLLLLLLLLLL:16CC:5J4LOLKHQQQQQQ:QQQQQQD16CCC:LLLLLLLLLLLDDLLDDDDDDD916C100000000T>64DQQQQQQQQQQQONKJJIGFBAA986311357<4111111111111111111111147CC9LLLLLLL:LLLLLL;LLLLLL947CCC74111111111111111111111147C100000000T>64DQQQQQQQQQQQNMKJJIGFBAA:86311357=766666666666666666666667>CC>54111111142411142411145>CCC>766666666666666666666667>C100000000T>64DQQQQQQQQQQQPNKJJIGFBAA:86311357>CCCCCCCCCCCCCCCCCCCCCCCCCCC" \ - "C>766666667=76667=76667>CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC100000000T>64DQQQQQQQQQQQNMKJJIGFBBB:8631111111111111111111111111>CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC11111111111111111111111100000000T>64:LLLLLLLLLLLDDLLDDDDDDD98631000000000000000000000001=>CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC100000000000000000000000000000000T><244444444444444444444442<863100000000000000000000000017>CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC1000000000000000000000000000000000T>8<6666666666666666666666<88631000000000000000000000000017>CCCCC9DDDDDDDDDDDDDDDDDDD:7>CCCCC10000000000000000000000000000000000T>>>>>>>>>>>>>>>>>>>>>>>>>>>86310000000000000000000000000017>CCCCDQQQQQQQQQQQQQQQQQQQL47CCCC100000000000000000000000000000000000T111111111111111111111111111111100000000000000000000000000017>CCCDQQQQQQQQQQQQQQQQQQQL16CCC10000" \ - "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000017>CCDQQQQQQQQQQQQQQQQQQQL16CC10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000017>CDQQQQKMNNNNNNNMQQQQQL16C1000000000000000000000000000000000000000TTTTTTTTTTTTTTTTTTTTTTTTTTTTT0000000000000000000000000000000157>DQQQQK2:::::::5QQQQQL16C10000000000000000000000000000000000000001>>>>>>>>>>>>>>>>>>>>>>>>8631000000000000000000000000000000013S=DQQQQQKH99999HKQQQQQL16C10000000000000000000000000000000000000001>8<6666666666666666666<8863100000000000000000000000000000001357DQQQQQQKH;;;HMQQQQQQL16C10000000000000000000000000000000000000001><244444444444444444442<863100000000000000000000000000000001357DQQQQQQQM:;:KQQQQQQQL16C100000000000000000000000000000000000000" \ - "01>649DDDDDDDDDDDDDDDDDDD:863100000000000000000000000000000001357DQQQQQQQQKDNQQQQQQQQL16C10000000000000000000000000000000000000001>64DQQQQQQQQQQQQQQQQQQQL863100000000000000000000000000000001357DQQQQQQQQQQQQQQQQQQQL16C10000000000000000000000000000000000000001>64DQQQQQQQQQQQQQQQQQQQL863100000000000000000000000000000001357DQQQQQQQQQQQQQQQQQQQL16C10000000000000000000000000000000000000001>64DQQQQQQQQQQQQQQQQQQQL863100000000000000000000000000000001357DKKKKKKKKKKKKKKKKKKKL16C10000000000000000000000000000000000000001>64DQQQQKMNNNNNNNMQQQQQL863100000000000000000000000000000001357:P2ON2P5PN2PPPN2POPOD16C10000000000000000000000000000000000000001>64DQQQQK2:::::::5QQQQQL863100000000000000000000000000000001357DJJJJJJJJJJJJJMJJJJML16C10000000000000000000000000000000000000001>64DQQQQQKH99999HKQQQQQL863100" \ - "000000000000000000000000000001357DJJJJJJJJJJJJJJJJJJJL16C10000000000000000000000000000000000000001>64DQQQQQQKH;;;HMQQQQQQL863100000000000000000000000000000001357DJJJJJJJJJJJJJJJJJJJL16C10000000000000000000000000000000000000001>64DQQQQQQQM:;:KQQQQQQQL863100000000000000000000000000000001357DIIIIIIIIIIIIIIIIIIIH16C10000000000000000000000000000000000000001>64DQQQQQQQQKDNQQQQQQQQL863100000000000000000000000000000001357DGGGGGGGGGGGGGGGGGGGD16C10000000000000000000000000000000000000001>64DQQQQQQQQQQQQQQQQQQQL863100000000000000000000000000000001357DFFFFFFFFFFFFFFFFFFED16C10000000000000000000000000000000000000001>64DQQQQQQQQQQQQQQQQQQQL863100000000000000000000000000000001357DBBBBBBBBBBBBBBBBBBBD16C10000000000000000000000000000000000000001>64DKKKKKKKKKKKKKKKKKKKL86310000000000000000000000000000000135" \ - "79?""?""?""?""?@@@@AAAAAAAAAB:16C10000000000000000000000000000000000000001>64:P2ON2P5PN2PPPN2POPOD8631000000000000000000000000000000013579?""?""?""?""?@@@@AAAAAAAAAB:16C10000000000000000000000000000000000000001>64DJJJJJJJJJJJJJMJJJJML8631000000000000000000000000000000013579999999VVV9999999:::;16C10000000000000000000000000000000000000001>64DJJJJJJJJJJJJJJJJJJJL863100000000000000000000000000000001357<4111111111111111111147C10000000000000000000000000000000000000001>64DJJJJJJJJJJJJJJJJJJJL863100000000000000000000000000000001357=766666666666666666667>C10000000000000000000000000000000000000001>64DIIIIIIIIIIIIIIIIIIIH863100000000000000000000000000000001357>CCCCCCCCCCCCCCCCCCCCCCC10000000000000000000000000000000000000001>64DGGGGGGGGGGGGGGGGGGGD8631000000000000000000000000000000011111111111111111111111111111000000" \ - "0000000000000000000000000000000001>64DFFFFFFFFFFFFFFFFFFED86310000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001>64DBBBBBBBBBBBBBBBBBBBD86310000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001>649?""?""?""?""?@@@@AAAAAAAAAB:86310000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001><29?""?""?""?""?@@@@AAAAAAAAAB:86310000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001>8<9999999VVV9999999:::;86310000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001888888888888888888888888753100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" \ - "016666666666666666666666665521000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000133333333333333333333333332410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001111111111111111111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000XYZ[[[[Z]X00000000000001111111111111111111111111111111T00000000000000000000000000000000000000000000000000000000000000000000000YZ^_`aaaa`_^[Y000000000001368>>>>>>>>>>>>>>>>>>>>>>>>>>>T0000000000000000000000000000000000000000000000000000000000000000000000Z^`bccccccccb`_ZX00000000013688<6666666666666666666666<8>T" \ - "00000000000000000000000000000000000000000000000000000000000000000000Y[`bccccccccccccb`^Y000000001368<244444444444444444444442<>T0000000000000000000000000000000000000000000000000000000000000000000Y^`cccccccccccccccca^Y00000001368999DDDDDDD:DDDDDDDDDDDD946>T0000000000000000000000000000000000000000LH:::dddddddeee00000000000X^`ccccccccfccccccccca^Y00000013689?""?BFGIJJJPKQQQQQQQQQQQD46>T000000000000000000000000000000000000000072TT::ddddddddd00000000000[`cccccca`___``bcccccca^X0000013689?""?BFGIJJJ2KQQQQQQQQQQQD46>T0000000000000000000000000000000000000000L3TT::dddddddde0000000000]_ccccca_^]YYY][^bcccccc`Z0000013689?""?BFGIJJJPKQQQQQQQQQQQD46>T000000000000000LT000000000000000H22000000032H0000" \ - "00000000000L::dddddd000000000000]_cccca^Y00000X[`ccccccccc`Z000013689?""?BFGIJJJ2KQQQQQQKKQQQD46>T000000000000000:TTT00000TTT:000000000000000L::dddddd000000000000[acccc_]00000X[`cccccacccca^000013689@@BFGIJJJPKQQQQQKLMQQQD46>T000000000000000:LLLLLLLLTTT:000000000000000L::dddddd00000000000X^bccc`[00000X[`ccccc`^bcccf_Y0001368V@@BFGIJJJPKQQQQKH:MQQQD46>T000000000000000:::::::::::::000000000000000L::dddddd00000000000Y_cccc_Y0000X[`ccccc`^[`cccc`]0001368V@@BFGIJJJPKQQQMH9:MQQQD46>T000000HHHHHHHHHd:::::::::::d000000000000000L::dddddd00000000000]`cccb^X000X[`ccccc`^Y]`cccc`Z0001368V@@BFGIJJMNKQQM:;9:MQQQD46>T0000d4444442222ddddddddddddd00000000000000TL::dddddddd000000000]`ccca[000X[`ccccc`^Y0Y_cccc`Z00013689AABFGIJJJ2KQKD;;9:MQQQD46>T00e::::HHHHHHHHddddddddddddd000000000000L2TL::dddddddde00000000]" \ - "`ccca[00X[`ccccc`^Y00Y_cccc`Z00013689AABFGIJJJPKQQN:;9:MQQQD46>T000e:::::::::::ddddddddddddd000000000000<2TL::ddddddddd00000000]`ccca^0X[`ccccc`^Y000Y_cccc`Z00013689AABFGIJJJPKQQQKH9:NQQQD46>T00000e:::::::::ddddddddddddd000000000000LH:::dddddddeee00000000Y_cccb_Y[`ccccc`^Y0000]`cccc`]00013689AABFGIJJJPKQQQQKL:NQQQD46>T000000000000000ddddddddddddd00000000000000000H2H::0000000000000X^bccc`^`ccccc`^Y00000[acccc_Y00013689AABFGIJJMNKQQQQQK2MQQQD46>T000000000000000ddddddddddddd00000000000000000H2H::00000000000000[accca`ccccc`^Y00000]_ccccb^X00013689AABFGIJJJ2KQQQQQQQQQQQD46>T000000000000000eddd00000ddde00000000000000000H2H::00000000000000]`ccccccccc`^Y00000Y^acccc`Z000013689AABFGIJJJPKQQQQQQQQQQQD46>T000000000000000eddd000006dde00000000000000000H2H::00000000000000X^accccccc`^Y00000Y^accccb_Y0000" \ - "1368:AABFGIJJMOKQQQQQQQQQQQD46>T000000000000000ede0000000ede00000000000000000H4H::000000000000000Z`cccccc`^Y0000YZ_accccc`[000001368:AABFGIJJJPKQQQQQQQQQQQD46>T000000000000000000000000000000000000000000000H4H::0000000000000000[accccca`^[[[^_`bccccca^Y000001368:BBBEGIJJMOKQQQQQQQQQQQD2<>T000000000000000000000000000000000000000000000H4H::0000000000000000Y^accccccbaaaabccccccb_]0000001368;::DDDDDLL:LLLLLLLLLLLL:<8>T000000000000000000000000000000000000000000000H4H::00000000000000000Y^accccccccccccccccb_]00000001357888888888888888888888888888T000000000000000000000000000000000000000000000H4:::000000000000000000Y^`cccccccccccccca_]000000001255666666666666666666666666666T00000000000000000000000000000000000000000000004::e0000000000000000000Y[_acccccccccca`[Y0000000001423333333333333333333333333333T" \ - "0000000000000000000000000000000000000000000000d::0000000000000000000000][_`aabbba`_^]000000000001111111111111111111111111111111T00000000000000000000000000000000000000000000000:e000000000000000000000000Y][^^^^[ZY00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000XX0000000000000000000000000000000000000000000000000"; - - // Decode 'indices': - const unsigned num_indices = sizeof(indices)/sizeof(indices[0])-1; // or strlen(indices); - IM_ASSERT(w*h==num_indices); - for (unsigned i=0;i=']') c-='1'; // This skips the backslash char - else c-='0'; - } - - // Fill 'rgba_buffer_out' - rgba_buffer_out.resize(w*h*4); - int idx;unsigned int pal;const unsigned char *pPal;unsigned char *pRGBA=(unsigned char *) &rgba_buffer_out[0]; - const int maxPaletteIdx = sizeof(palette)/sizeof(palette[0]); - for (unsigned i=0,isz=w*h;i=0;--c) *pRGBA++=pPal[c]; // This works only on big-endian machines (maybe) -# endif - //} - } - - // TODO: Add optional code guarded by the definition IMGUITABWINDOW_RGBA_FLIP_Y or something like that is somebody request it -} - - -} // namespace ImGui - - - -namespace ImGui { - -//======================================================================================= -// Main method to draw the tab label -// The TabLabelStyle used by this method won't be merged with the Window Alpha (please provide a pOptionalStyleToUseIn using TabLabelStyle::GetMergedWithWindowAlpha() if needed). -static bool TabButtonVertical(bool rotateCCW,const char *label, bool selected, bool *pCloseButtonPressedOut=NULL, const char* textOverrideIn=NULL, ImVec2 *pJustReturnItsSizeHereOut=NULL, const TabLabelStyle* pOptionalStyleToUseIn=NULL,ImFont *fontOverride=NULL, ImVec2 *pOptionalJustDrawTabButtonGraphicsUnderMouseWithThisOffset=NULL, ImDrawList *drawListOverride=NULL,bool privateReuseLastCalculatedLabelSizeDoNotUse = false,bool forceActiveColorLook = false,bool invertRounding=false) { - // Based on ImGui::ButtonEx(...) - bool *pHoveredOut = NULL; // removed from args (can be queried from outside) - bool *pCloseButtonHovered = NULL; // removed from args (who cares if the close button is hovered?) - const int flags = 0; // what's this ? - const bool hasCloseButton = pCloseButtonHovered || pCloseButtonPressedOut; - - const bool isFakeControl = pOptionalJustDrawTabButtonGraphicsUnderMouseWithThisOffset || pJustReturnItsSizeHereOut; - - ImGuiWindow* window = GetCurrentWindow(); - if (window && window->SkipItems && !isFakeControl) return false; - - //ImGuiContext& g = *GImGui; - const ImGuiStyle& style = ImGui::GetStyle(); - const TabLabelStyle& tabStyle = pOptionalStyleToUseIn ? *pOptionalStyleToUseIn : TabLabelStyle::Get(); - const ImGuiID id = isFakeControl ? 0 : window->GetID(label); - if (textOverrideIn) label = textOverrideIn; - - if (!fontOverride) fontOverride = (ImFont*) (selected ? TabLabelStyle::ImGuiFonts[tabStyle.fontStyles[TabLabelStyle::TAB_STATE_SELECTED]] : TabLabelStyle::ImGuiFonts[tabStyle.fontStyles[TabLabelStyle::TAB_STATE_NORMAL]]); - if (fontOverride) ImGui::PushFont(fontOverride); - static ImVec2 staticLabelSize(0,0); - ImVec2 label_size(0,0); - if (!privateReuseLastCalculatedLabelSizeDoNotUse) label_size = staticLabelSize = ImGui::VerticalTextHelper::CalcVerticalTextSize(label, NULL, true); - else label_size = staticLabelSize; - - ImVec2 pos = window ? window->DC.CursorPos : ImVec2(0,0); - if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrentLineTextBaseOffset) pos.y += window->DC.CurrentLineTextBaseOffset - style.FramePadding.y; - ImVec2 size(label_size.x + (style.FramePadding.x+tabStyle.borderWidth) * 2.0f, label_size.y + (style.FramePadding.y+tabStyle.borderWidth) * 2.0f); - - float btnSize = label_size.x*0.75f,btnSpacingY = label_size.x*0.25f; - float extraWidthForBtn = hasCloseButton ? (btnSpacingY*2.f+btnSize) : 0; - if (hasCloseButton) size.y+=extraWidthForBtn; - if (pJustReturnItsSizeHereOut) {*pJustReturnItsSizeHereOut=size;if (fontOverride) ImGui::PopFont();return false;} - - const ImRect bb(pOptionalJustDrawTabButtonGraphicsUnderMouseWithThisOffset ? *pOptionalJustDrawTabButtonGraphicsUnderMouseWithThisOffset : pos, - (pOptionalJustDrawTabButtonGraphicsUnderMouseWithThisOffset ? *pOptionalJustDrawTabButtonGraphicsUnderMouseWithThisOffset : pos) + size); - if (!pOptionalJustDrawTabButtonGraphicsUnderMouseWithThisOffset) { - ItemSize(bb, 0.f);//style.FramePadding.y); - if (!ItemAdd(bb, id)) {if (fontOverride) ImGui::PopFont();return false;} - } - - //if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat) flags |= ImGuiButtonFlags_Repeat; // What's this ? - bool hovered=false, held=false; - bool pressed = isFakeControl ? false : ButtonBehavior(bb, id, &hovered, &held, flags); - bool btnHovered = false; - bool btnPressed = false; - ImVec2 startBtn(0,0),endBtn(0,0); - if (hasCloseButton) { - //startBtn = ImVec2(bb.Max.x-extraWidthForBtn+btnSpacingY*0.5f,bb.Min.y+(size.y-btnSize)*0.5f); - //endBtn = ImVec2(startBtn.x+btnSize,startBtn.y+btnSize); - startBtn = ImVec2(bb.Min.x+(size.x-btnSize)*0.5f,rotateCCW ? (bb.Min.y+btnSpacingY*1.5f/*extraWidthForBtn-btnSpacingY*0.5f*/) : (bb.Max.y-extraWidthForBtn+btnSpacingY*0.5f)); - endBtn = ImVec2(startBtn.x+btnSize,startBtn.y+btnSize); - if (!isFakeControl) { - btnHovered = hovered && ImGui::IsMouseHoveringRect(startBtn,endBtn); - btnPressed = pressed && btnHovered; - if (btnPressed) pressed = false; - if (pCloseButtonHovered) *pCloseButtonHovered = btnHovered; - if (pCloseButtonPressedOut) * pCloseButtonPressedOut = btnPressed; - } - } - if (pHoveredOut) *pHoveredOut = hovered && !btnHovered; // We may choose not to return "hovered" when the close btn is hovered. - if (forceActiveColorLook) {hovered = held = true;} - - // Render - - const ImU32 col = (hovered && !btnHovered && held) ? tabStyle.colors[selected ? TabLabelStyle::Col_TabLabelSelectedActive : TabLabelStyle::Col_TabLabelActive] : (hovered && !btnHovered) ? tabStyle.colors[selected ? TabLabelStyle::Col_TabLabelSelectedHovered : TabLabelStyle::Col_TabLabelHovered] : tabStyle.colors[selected ? TabLabelStyle::Col_TabLabelSelected : TabLabelStyle::Col_TabLabel]; - const ImU32 colText = tabStyle.colors[selected ? TabLabelStyle::Col_TabLabelSelectedText : TabLabelStyle::Col_TabLabelText]; - - if (!drawListOverride) drawListOverride = window->DrawList; - - // Canvas - /* - ImDrawCornerFlags_TopLeft = 1 << 0, // 0x1 - ImDrawCornerFlags_TopRight = 1 << 1, // 0x2 - ImDrawCornerFlags_BotLeft = 1 << 2, // 0x4 - ImDrawCornerFlags_BotRight = 1 << 3, // 0x8 - */ - if (rotateCCW) ImGui::DrawListHelper::ImDrawListAddRectWithHorizontalGradient(drawListOverride,bb.Min, bb.Max,col,(selected || hovered || held)?tabStyle.fillColorGradientDeltaIn0_05:(-tabStyle.fillColorGradientDeltaIn0_05),tabStyle.colors[selected ? TabLabelStyle::Col_TabLabelSelectedBorder : TabLabelStyle::Col_TabLabelBorder],tabStyle.rounding,invertRounding ? (2|8) : (1|4),tabStyle.borderWidth); - else ImGui::DrawListHelper::ImDrawListAddRectWithHorizontalGradient(drawListOverride,bb.Min, bb.Max,col,(selected || hovered || held)?(-tabStyle.fillColorGradientDeltaIn0_05):tabStyle.fillColorGradientDeltaIn0_05,tabStyle.colors[selected ? TabLabelStyle::Col_TabLabelSelectedBorder : TabLabelStyle::Col_TabLabelBorder],tabStyle.rounding,invertRounding ? (1|4) : (2|8),tabStyle.borderWidth); - - // Text - ImGui::PushStyleColor(ImGuiCol_Text,ImGui::ColorConvertU32ToFloat4(colText)); - if (!pOptionalJustDrawTabButtonGraphicsUnderMouseWithThisOffset) { - if (!rotateCCW) - VerticalTextHelper::RenderTextVerticalClipped( - bb.Min, - ImVec2(bb.Max.x,bb.Max.y-extraWidthForBtn),//ImVec2(bb.Max.x-extraHeightForBtn,bb.Max.y), - label, NULL, &label_size, ImVec2(0.5f,0.5f),NULL,NULL,rotateCCW); - else - VerticalTextHelper::RenderTextVerticalClipped( - ImVec2(bb.Min.x,bb.Min.y+extraWidthForBtn), - bb.Max, - label, NULL, &label_size, ImVec2(0.5f,0.5f),NULL,NULL,rotateCCW); - } - else { - //ImVec2 textPos(bb.Min.x+(bb.Max.x-bb.Min.x-label_size.x-extraHeightForBtn)*0.5f,bb.Min.y+(bb.Max.y-bb.Min.y-label_size.y)*0.5f); - ImVec2 textPos(bb.Min.x+(bb.Max.x-bb.Min.x-label_size.x)*0.5f, - rotateCCW ? - (bb.Max.y-(bb.Max.y-bb.Min.y-label_size.y-extraWidthForBtn)*0.5f) - : - (bb.Min.y+(bb.Max.y-bb.Min.y-label_size.y-extraWidthForBtn)*0.5f) - ); - VerticalTextHelper::AddTextVertical(drawListOverride,textPos,colText,label,NULL,rotateCCW); - } - ImGui::PopStyleColor(); - - - - //fprintf(stderr,"bb.Min=%d,%d bb.Max=%d,%d label_size=%d,%d extraWidthForBtn=%d\n",(int)bb.Min.x,(int)bb.Min.y,(int)bb.Max.x,(int)bb.Max.y,(int)label_size.x,(int)label_size.y,(int)extraWidthForBtn); - if (hasCloseButton) { - const ImU32 col = (held && btnHovered) ? tabStyle.colors[TabLabelStyle::Col_TabLabelCloseButtonActive] : btnHovered ? tabStyle.colors[TabLabelStyle::Col_TabLabelCloseButtonHovered] : 0; - if (btnHovered) DrawListHelper::ImDrawListAddRect(drawListOverride,startBtn, endBtn, col,tabStyle.colors[TabLabelStyle::Col_TabLabelCloseButtonBorder],tabStyle.closeButtonRounding,0x0F,tabStyle.closeButtonBorderWidth); - - const float cross_extent = (btnSize * 0.5f * 0.7071f);// - 1.0f; - const ImVec2 center((startBtn.x+endBtn.x)*0.5f,(startBtn.y+endBtn.y)*0.5f); - const ImU32 cross_col = tabStyle.colors[(btnHovered) ? TabLabelStyle::Col_TabLabelCloseButtonTextHovered : selected ? TabLabelStyle::Col_TabLabelSelectedText : TabLabelStyle::Col_TabLabelText]; - drawListOverride->AddLine(center + ImVec2(+cross_extent,+cross_extent), center + ImVec2(-cross_extent,-cross_extent), cross_col,tabStyle.closeButtonTextWidth); - drawListOverride->AddLine(center + ImVec2(+cross_extent,-cross_extent), center + ImVec2(-cross_extent,+cross_extent), cross_col,tabStyle.closeButtonTextWidth); - - } - if (fontOverride) ImGui::PopFont(); - - return pressed; -} -//======================================================================================== - - -bool TabLabelsVertical(bool textIsRotatedCCW, int numTabs, const char** tabLabels, int& selectedIndex, const char** tabLabelTooltips, int* pOptionalHoveredIndex, int* pOptionalItemOrdering, bool allowTabReorder, bool allowTabClosing, int* pOptionalClosedTabIndex, int * pOptionalClosedTabIndexInsideItemOrdering, bool invertRounding) { - ImGuiStyle& style = ImGui::GetStyle(); - const TabLabelStyle& tabStyle = TabLabelStyle::GetMergedWithWindowAlpha(); - - const ImVec2 itemSpacing = style.ItemSpacing; - style.ItemSpacing.x = 1; - style.ItemSpacing.y = 1; - - if (numTabs>0 && (selectedIndex<0 || selectedIndex>=numTabs)) { - if (!pOptionalItemOrdering) selectedIndex = 0; - else selectedIndex = -1; - } - if (pOptionalHoveredIndex) *pOptionalHoveredIndex = -1; - if (pOptionalClosedTabIndex) *pOptionalClosedTabIndex = -1; - if (pOptionalClosedTabIndexInsideItemOrdering) *pOptionalClosedTabIndexInsideItemOrdering = -1; - - //float sumY=0.f; - //float windowWidth = 0.f; - //if (wrapMode) windowWidth = ImGui::GetWindowWidth() - style.WindowPadding.x - (ImGui::GetScrollMaxY()>0 ? style.ScrollbarSize : 0.f); - - static int draggingTabIndex = -1;int draggingTabTargetIndex = -1; // These are indices inside pOptionalItemOrdering - static bool draggingTabWasSelected = false; - static ImVec2 draggingTabSize(0,0); - static ImVec2 draggingTabOffset(0,0); - static bool draggingLocked = false; - - const bool isRMBclicked = ImGui::IsMouseClicked(1); - const bool isMouseDragging = ImGui::IsMouseDragging(0,3.f); - const bool isMouseDraggingJustStarted = isMouseDragging && (ImGui::GetIO().MouseDownDuration[0] < 0.35f);// ImGui::GetIO().MouseDown[0] does not work! - int justClosedTabIndex = -1,newSelectedIndex = selectedIndex; - - ImVec2 startGroupCursorPos = ImGui::GetCursorPos(); - ImGui::BeginGroup(); - //ImVec2 tabButtonSz(0,0); - bool mustCloseTab = false;bool canUseSizeOptimization = false; - const bool isWindowHovered = ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem); - bool selection_changed = false;//bool noButtonDrawn = true; - for (int j = 0,i; j < numTabs; j++) - { - i = pOptionalItemOrdering ? pOptionalItemOrdering[j] : j; - if (i==-1) continue; - - //if (!wrapMode) - { - //if (!noButtonDrawn) ImGui::SameLine(); - canUseSizeOptimization=false; - } - /*else if (sumX > 0.f) { - sumX+=style.ItemSpacing.x; // Maybe we can skip it if we use SameLine(0,0) below - ImGui::TabButtonVertical(tabLabels[i],(i == selectedIndex),allowTabClosing ? &mustCloseTab : NULL,NULL,&tabButtonSz,&tabStyle); - sumX+=tabButtonSz.x; - if (sumX>windowWidth) sumX = 0.f; - //else ImGui::SameLine(); - canUseSizeOptimization = true; - } - else canUseSizeOptimization = false;*/ - - // Draw the button - ImGui::PushID(i); // otherwise two tabs with the same name would clash. - if (ImGui::TabButtonVertical(textIsRotatedCCW,tabLabels[i],i == selectedIndex,allowTabClosing ? &mustCloseTab : NULL,NULL,NULL,&tabStyle,NULL,NULL,NULL,canUseSizeOptimization,false,invertRounding)) { - selection_changed = (selectedIndex!=i); - newSelectedIndex = i; - } - ImGui::PopID(); - //noButtonDrawn = false; - - /*if (wrapMode) { - if (sumX==0.f) sumX = style.WindowPadding.x + ImGui::GetItemRectSize().x; // First element of a line - } - else if (isMouseDragging && allowTabReorder && pOptionalItemOrdering) { - // We still need sumX - if (sumY==0.f) sumY = style.WindowPadding.y + ImGui::GetItemRectSize().y; // First element of a line - else sumY+=style.ItemSpacing.y + ImGui::GetItemRectSize().y; - - }*/ - - if (isWindowHovered && ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly) && !mustCloseTab) { - if (pOptionalHoveredIndex) *pOptionalHoveredIndex = i; - if (tabLabelTooltips && !isRMBclicked && tabLabelTooltips[i] && strlen(tabLabelTooltips[i])>0) ImGui::SetTooltip("%s",tabLabelTooltips[i]); - - if (pOptionalItemOrdering) { - if (allowTabReorder) { - if (isMouseDragging) { - if (draggingTabIndex==-1 && !draggingLocked && isMouseDraggingJustStarted) { - draggingTabIndex = j; - draggingTabWasSelected = (i == selectedIndex); - draggingTabSize = ImGui::GetItemRectSize(); - const ImVec2& mp = ImGui::GetIO().MousePos; - const ImVec2 draggingTabCursorPos = ImGui::GetCursorPos(); - draggingTabOffset=ImVec2( - mp.x-draggingTabSize.x*0.5f-draggingTabCursorPos.x+ImGui::GetScrollX(), - mp.y+draggingTabSize.y*0.5f-draggingTabCursorPos.y+ImGui::GetScrollY() - ); - - } - } - else if (draggingTabIndex>=0 && draggingTabIndex=0 && draggingTabIndexOverlayDrawList; - const TabLabelStyle& tabStyle = TabLabelStyleGetMergedWithAlphaForOverlayUsage(); - ImFont* fontOverride = (ImFont*) (draggingTabWasSelected ? TabLabelStyle::ImGuiFonts[tabStyle.fontStyles[TabLabelStyle::TAB_STATE_SELECTED]] : TabLabelStyle::ImGuiFonts[tabStyle.fontStyles[TabLabelStyle::TAB_STATE_NORMAL]]); - ImGui::TabButtonVertical(textIsRotatedCCW,tabLabels[pOptionalItemOrdering[draggingTabIndex]],draggingTabWasSelected,allowTabClosing ? &mustCloseTab : NULL,NULL,NULL,&tabStyle,fontOverride,&start,drawList,false,true,invertRounding); - ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); - - if (TabWindow::DockPanelIconTextureID) { - // Optional: draw prohibition sign when dragging too far (you can remove this if you want) - startGroupCursorPos.x+=deltaX*.5f; - groupSize.x-=deltaX; - if (!ImGui::IsMouseHoveringRect(startGroupCursorPos,startGroupCursorPos+groupSize)) { - const float signSize = draggingTabSize.x*1.25f; - start.x+=(draggingTabSize.x-signSize)*0.5f; - start.y+=(draggingTabSize.y-signSize)*0.5f; - const ImVec2 end(start.x+signSize,start.y+signSize); - const ImVec4 color(1.f,1.f,1.f,0.85f); - drawList->AddImage(TabWindow::DockPanelIconTextureID,start,end,ImVec2(0.5f,0.75f),ImVec2(0.75f,1.f),ImGui::ColorConvertFloat4ToU32(color)); - } - } - } - else { - draggingTabIndex = -1;draggingTabTargetIndex=-1; - draggingLocked = true;// consume one mouse release - } - } - - // Drop tab label - if (draggingTabTargetIndex!=-1) { - // swap draggingTabIndex and draggingTabTargetIndex in pOptionalItemOrdering - const int tmp = pOptionalItemOrdering[draggingTabTargetIndex]; - pOptionalItemOrdering[draggingTabTargetIndex] = pOptionalItemOrdering[draggingTabIndex]; - pOptionalItemOrdering[draggingTabIndex] = tmp; - //fprintf(stderr,"%d %d\n",draggingTabIndex,draggingTabTargetIndex); - draggingTabTargetIndex = draggingTabIndex = -1; - } - - // Reset draggingTabIndex if necessary - if (!isMouseDragging) {draggingTabIndex = -1;draggingLocked=false;} - - // Change selected tab when user closes the selected tab - if (selectedIndex == justClosedTabIndex && selectedIndex>=0) { - selectedIndex = -1; - for (int j = 0,i; j < numTabs; j++) { - i = pOptionalItemOrdering ? pOptionalItemOrdering[j] : j; - if (i==-1) continue; - selectedIndex = i; - break; - } - } - - // Restore the style - style.ItemSpacing = itemSpacing; - - return selection_changed; -} - -float CalcVerticalTabLabelsWidth() { - return ImGui::GetFontSize() + (ImGui::GetStyle().FramePadding.y+ImGui::TabLabelStyle::Get().borderWidth) * 2.0f; -} - -} //namespace ImGui - diff --git a/Source Code/Libs/imgui/addons/imguitabwindow/imguitabwindow.h b/Source Code/Libs/imgui/addons/imguitabwindow/imguitabwindow.h deleted file mode 100644 index 5e617d145d..0000000000 --- a/Source Code/Libs/imgui/addons/imguitabwindow/imguitabwindow.h +++ /dev/null @@ -1,574 +0,0 @@ -#ifndef IMGUITABWINDOW_H_ -#define IMGUITABWINDOW_H_ - -#ifndef IMGUI_API -#include -#endif //IMGUI_API - -// This addon is available here: https://gist.github.com/Flix01/2cdf1db8d936100628c0 -// and is bundled in the "ImGui Addons Branch" here: https://github.com/Flix01/imgui/tree/2015-10-Addons -// Wiki about the "ImGui Addons Branch" is here: https://github.com/Flix01/imgui/wiki/ImGui-Addons-Branch-Home - - -// USAGE: -/* -1) In the main "ImGui loop": ----------------------------- - static bool open = true; - if (ImGui::Begin("Main", &open, ImVec2(400,600),-1.f,ImGuiWindowFlags_NoScrollbar)) { - - static ImGui::TabWindow tabWindow; - if (!tabWindow.isInited()) { - static const char* tabNames[] = {"Render","Layers","Scene","World","Object","Constraints","Modifiers","Data","Material","Texture","Particle","Physics"}; - static const int numTabs = sizeof(tabNames)/sizeof(tabNames[0]); - static const char* tabTooltips[numTabs] = {"Render Tab Tooltip","Layers Tab Tooltip","Scene Tab Tooltip","","Object Tab Tooltip","","","","","Tired to add tooltips...",""}; - for (int i=0;i rgba_buffer;int w,h; - ImGui::TabWindow::GetDockPanelIconImageRGBA(rgba_buffer,&w,&h); // 4 channels, no additional stride between lines, => rgba_buffer.size() = 4*w*h - ImGui::TabWindow::DockPanelIconTextureID = reinterpret_cast(MyTextureFromMemoryRGBAMethod(&rgba_buffer[0],w,h)); // User must turn raw RBGA to texture (using GetDockPanelIconImageRGBA). - } - - // Optional Style - //ImGui::TabLabelStyle& tabStyle = ImGui::TabLabelStyle::Get(); - // ... modify tabStyle ... - -3) At deinit time: -------------------- - Free: ImGui::TabWindow::DockPanelIconTextureID if not NULL. (*) - -4) Finally, in the global scope (at the top of you code), define the callbacks: -------------------------------------------------------------------------------- -void TabContentProvider(ImGui::TabWindow::TabLabel* tab,ImGui::TabWindow& parent,void* userPtr) { - // Users will use tab->userPtr here most of the time - ImGui::Spacing();ImGui::Separator(); - if (tab) ImGui::Text("Here is the content of tab label: \"%s\"\n",tab->getLabel()); - else {ImGui::Text("EMPTY TAB LABEL DOCKING SPACE.");ImGui::Text("PLEASE DRAG AND DROP TAB LABELS HERE!");} - ImGui::Separator();ImGui::Spacing(); -} -// Optional (tab label context-menu) -void TabLabelPopupMenuProvider(ImGui::TabWindow::TabLabel* tab,ImGui::TabWindow& parent,void* userPtr) { - if (ImGui::BeginPopup(ImGui::TabWindow::GetTabLabelPopupMenuName())) { - ImGui::PushID(tab); - ImGui::Text("\"%.*s\" Menu",(int)(strlen(tab->getLabel())-(tab->getModified()?1:0)),tab->getLabel()); - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - ImGui::MenuItem("Entry 1"); - ImGui::MenuItem("Entry 2"); - ImGui::MenuItem("Entry 3"); - ImGui::MenuItem("Entry 4"); - ImGui::MenuItem("Entry 5"); - ImGui::PopID(); - ImGui::EndPopup(); - } - -} -// Optional (delete TabLabel::userPtr when TabLabel gets deleted) -void OnTabLabelDeleting(ImGui::TabWindow::TabLabel* tab) { - // Use this callback to delete tab->userPtr if you used it -} -// Optional (this is fired when RMB is clicked on an empty spot in the tab area) -void TabLabelGroupPopupMenuProvider(ImVector& tabs,ImGui::TabWindow& parent,ImGui::TabWindowNode* tabNode,void* userPtr) { - ImGui::PushStyleColor(ImGuiCol_WindowBg,ImGui::ColorConvertU32ToFloat4(ImGui::TabLabelStyle::Get().colors[ImGui::TabLabelStyle::Col_TabLabel])); - ImGui::PushStyleColor(ImGuiCol_Text,ImGui::ColorConvertU32ToFloat4(ImGui::TabLabelStyle::Get().colors[ImGui::TabLabelStyle::Col_TabLabelText])); - if (ImGui::BeginPopup(ImGui::TabWindow::GetTabLabelGroupPopupMenuName())) { - ImGui::Text("TabLabel Group Menu"); - ImGui::Separator(); - if (parent.isTabNodeMergeble(tabNode) && ImGui::MenuItem("Merge with parent group")) parent.mergeTabNode(tabNode); // Warning: this invalidates "tabNode" after the call - if (ImGui::MenuItem("Close all tabs in this group")) { - for (int i=0,isz=tabs.size();iisClosable()) // otherwise even non-closable tabs will be closed - { - parent.removeTabLabel(tab); - //tab->mustCloseNextFrame = true; // alternative way... this asks for saving if file is modified - } - } - } - ImGui::EndPopup(); - } - ImGui::PopStyleColor(2); -} - -TIPS ABOUT TEXTURE LOADING; -(*): -> Texture loading/freeing is mandatory only if you're not using an IMGUI_USE_XXX_BINDING, or if you don't know - what IMGUI_USE_XXX_BINDING is. - -> If you prefer loading the texture from an external image, I'll provide it here: https://gist.github.com/Flix01/2cdf1db8d936100628c0 - -> Since internally we use texcoords, we had to choose a single convention for them. That means that it might be necessary for - some people to flip the image RGBA upside down (we can provide some definitions for that if needed). - -ADDITIONAL (OPTIONAL) CALLBACKS: -static void TabWindow::SetTabLabelFactoryCallback(TabLabelFactoryCallback _tabLabelFactoryCb) {TabLabelFactoryCb=_tabLabelFactoryCb;} - -> If present, fired when calling: TabLabel* addTabLabel(const char* label,const char* tooltip=NULL,bool closable=true,bool draggable=true,void* userPtr=NULL,const char* userText=NULL,int userInt=0,int ImGuiWindowFlagsForContent=0); - User can use it to append a TabLabel::userPtr or to provide a proper extension from TabWindowLabel. - In addition, that method is called on layout deserialization, so that the user can load the content after deserialization (label,tooltip,userInt and userText are the TabLabel fields that are serialized/deserialized). -static void TabWindow::SetTabLabelSaveCallback(TabLabelFileCallback _tabLabelSaveCb) {TabLabelSaveCb=_tabLabelSaveCb;} - -> When a modified file needs to be saved, if set, that method will be called (with NULL argument). - Otherwise TabLabel::saveAs(NULL) will be called. - If you don't need to save anything, simply never use TabLabel::setModified(true). -*/ - - - -// KNOWN BUGS: -/* --> If you scale the tab labels (e.g. with CTRL+mouse wheel), the dragged tab is not scaled. (I'm not sure this will be ever fixed). -*/ - - -// BETTER ALTERNATIVES: -/* -There are better alternatives to Imgui::TabWindow: --> https://github.com/thennequin/ImWindow [for Window OS] --> https://github.com/nem0/LumixEngine/blob/master/src/studio_lib/imgui/imgui_dock.inl [lumixengine's Dock] => NOW AVAILABLE as imguidock addon. -Please see: https://github.com/ocornut/imgui/issues for further info -*/ - - -#include -#ifdef _MSC_VER -# ifndef strcasecmp -# define strcasecmp _stricmp -# endif // strcasecmp -# ifndef strncasecmp -# define strncasecmp _strnicmp -# endif // strncasecmp -#endif // _MSC_VER - - -namespace ImGui { - -enum ImGuiTabLabelStyleEnum { - ImGuiTabLabelStyle_Default=0, - ImGuiTabLabelStyle_Dark, - ImGuiTabLabelStyle_Red, - ImGuiTabLabelStyle_Green, - ImGuiTabLabelStyle_Blue, - ImGuiTabLabelStyle_Yellow, - ImGuiTabLabelStyle_Orange, - ImGuiTabLabelStyle_White, - ImGuiTabLabelStyle_Tidy, - ImGuiTabLabelStyle_Foxy, - ImGuiTabLabelStyle_FoxyInverse, - ImGuiTabLabelStyle_Fancy, - - ImGuiTabLabelStyle_Count -}; -struct TabLabelStyle { -enum Colors { - Col_TabLabel = 0, - Col_TabLabelHovered, - Col_TabLabelActive, - Col_TabLabelBorder, - Col_TabLabelText, - - Col_TabLabelSelected, - Col_TabLabelSelectedHovered, - Col_TabLabelSelectedActive, - Col_TabLabelSelectedBorder, - Col_TabLabelSelectedText, - - Col_TabLabelCloseButtonHovered, - Col_TabLabelCloseButtonActive, - Col_TabLabelCloseButtonBorder, - Col_TabLabelCloseButtonTextHovered, - - Col_TabLabel_Count -}; -ImU32 colors[Col_TabLabel_Count]; - -float fillColorGradientDeltaIn0_05; // vertical gradient if > 0 (looks nice but it's very slow) -float rounding; -float borderWidth; - -float closeButtonRounding; -float closeButtonBorderWidth; -float closeButtonTextWidth; - -enum FontStyle { - FONT_STYLE_NORMAL=0, - FONT_STYLE_BOLD, - FONT_STYLE_ITALIC, - FONT_STYLE_BOLD_ITALIC, - FONT_STYLE_COUNT -}; -enum TabState { - TAB_STATE_NORMAL, - TAB_STATE_SELECTED, - TAB_STATE_MODIFIED, - TAB_STATE_SELECTED_MODIFIED, - TAB_STATE_COUNT -}; -int fontStyles[TAB_STATE_COUNT]; // Users should add TabLabelStyle::ImGuiFonts to map them for these to work - -ImVec4 tabWindowLabelBackgroundColor; -bool tabWindowLabelShowAreaSeparator; -ImVec4 tabWindowSplitterColor; -float tabWindowSplitterSize; - -IMGUI_API TabLabelStyle(); - -void reset() {Reset(*this);} -IMGUI_API static bool Edit(TabLabelStyle& style=TabLabelStyle::Get()); -IMGUI_API static bool EditFast(TabLabelStyle &s=TabLabelStyle::Get()); -static void Reset(TabLabelStyle& style=TabLabelStyle::Get()) {style = TabLabelStyle();} - -// These modify the style: some operation are not loseless! -IMGUI_API static void InvertSelectedLook(TabLabelStyle& style=TabLabelStyle::Get()); -IMGUI_API static void ShiftHue(TabLabelStyle& style,float amountIn0_1); -IMGUI_API static void InvertColors(TabLabelStyle& style=TabLabelStyle::Get(),float saturationThreshould=0.1f); // in [0.f,0.5f] AFAIR -IMGUI_API static void LightenBackground(TabLabelStyle& style=TabLabelStyle::Get(),float amount=0.15f); -IMGUI_API static void DarkenBackground(TabLabelStyle& style=TabLabelStyle::Get(),float amount=0.15f); - - -#if (defined(IMGUIHELPER_H_) && !defined(NO_IMGUIHELPER_SERIALIZATION)) -#ifndef NO_IMGUIHELPER_SERIALIZATION_SAVE -IMGUI_API static bool Save(const TabLabelStyle& style,ImGuiHelper::Serializer& s); -static inline bool Save(const TabLabelStyle &style, const char *filename) { - ImGuiHelper::Serializer s(filename); - return Save(style,s); -} -#endif //NO_IMGUIHELPER_SERIALIZATION_SAVE -#ifndef NO_IMGUIHELPER_SERIALIZATION_LOAD -IMGUI_API static bool Load(TabLabelStyle& style, ImGuiHelper::Deserializer& d, const char ** pOptionalBufferStart=NULL); -static inline bool Load(TabLabelStyle& style,const char* filename) { - ImGuiHelper::Deserializer d(filename); - return Load(style,d); -} -#endif //NO_IMGUIHELPER_SERIALIZATION_LOAD -#endif //NO_IMGUIHELPER_SERIALIZATION - - -// Gets the default style instance (same as TabLabelStyle::style) -inline static TabLabelStyle& Get() {return style;} -// Gets a new instance = default style instance blended with ImGui::GetStyle().Alpha -IMGUI_API static const TabLabelStyle& GetMergedWithWindowAlpha(); -static TabLabelStyle style; -static const char* ColorNames[Col_TabLabel_Count]; -static const char* FontStyleNames[FONT_STYLE_COUNT]; -static const char* TabStateNames[TAB_STATE_COUNT]; -static const ImFont* ImGuiFonts[FONT_STYLE_COUNT]; -}; -IMGUI_API bool ResetTabLabelStyle(int tabLabelStyleEnum, TabLabelStyle& style); -IMGUI_API const char** GetDefaultTabLabelStyleNames(); // ImGuiTabLabelStyle_Count names re returned -// satThresholdForInvertingLuminance: in [0,1] if == 0.f luminance is not inverted at all -// shiftHue: in [0,1] if == 0.f hue is not changed at all -IMGUI_API void ChangeTabLabelStyleColors(TabLabelStyle& style,float satThresholdForInvertingLuminance=.1f,float shiftHue=0.f); - - -class TabWindow { -public: -struct TabLabel { - friend struct TabWindowNode; - friend class TabWindow; - friend struct TabWindowDragData; -private: - // [Nothing is used as ImGui ID: the ImGui ID is the address of the TabLabel] - char* label; // [owned] text displayed by the TabLabel (one more char is allocated to optimize appending an asterisk to it) - char* tooltip; // [owned] tooltip displayed by the TabLabel - char* userText; // [owned] user text - bool modified; -protected: - bool closable; - bool draggable; - TabLabel(const char* _label=NULL,const char* _tooltip=NULL,bool _closable=true,bool _draggable=true) { - label = tooltip = NULL; - userPtr = NULL;userText=NULL;userInt=0; - setLabel(_label); - setTooltip(_tooltip); - closable = _closable; - draggable = _draggable; - mustCloseNextFrame = false; - mustSelectNextFrame = false; - wndFlags = 0; - modified = false; - } - virtual ~TabLabel() { - if (label) {ImGui::MemFree(label);label=NULL;} - if (tooltip) {ImGui::MemFree(tooltip);tooltip=NULL;} - if (userText) {ImGui::MemFree(userText);userText=NULL;} - } - IMGUI_API static void DestroyTabLabel(TabLabel*& tab); -public: - inline const char* getLabel() const {return label;} - inline bool matchLabel(const char* match) const {return modified ? (strncmp(match,label,strlen(label)-1)==0) : (strcmp(match,label)==0);} - inline bool matchLabelExtension(const char* matchExtension) const { - const char* dot = strrchr(label,(int) '.');if (!dot) return false; - return modified ? (strncasecmp(matchExtension,dot,strlen(dot)-1)==0) : (strcasecmp(matchExtension,dot)==0); - } - void setLabel(const char* lbl,bool appendAnAsteriskAndMarkAsModified=false) { - if (label) {ImGui::MemFree(label);label=NULL;} - const char e = '\0';if (!lbl) lbl=&e; - const int sz = strlen(lbl)+1; // we allocate one char more (optimization for appending an asterisk) - label = (char*) ImGui::MemAlloc(sz+1);strcpy(label,lbl); - if (appendAnAsteriskAndMarkAsModified) { - modified = true;strcat(label,"*"); - } - else modified = false; - } - inline bool copyLabelTo(char* buffer, int buffer_size) const { - int len = modified ? ((int)strlen(label)-1) : (int)strlen(label); - if (len<0) len=0;bool ok = true; - if (buffer_size<=len) {len = buffer_size-1;ok=false;} - strncpy(buffer,label,len); - buffer[len]='\0'; - return ok; - } - inline bool matchTooltip(const char* match) const {return (strcmp(match,tooltip)==0);} - inline bool matchUserText(const char* match) const {return (strcmp(match,userText)==0);} - - inline bool getModified() const {return modified;} - inline void setModified(bool flag) { - if (modified == flag) return; - modified = flag;int sz = strlen(label); - if (modified) {if (sz==0 || label[sz-1]!='*') strcat(label,"*");} - else {if (sz>0 && label[sz-1]=='*') label[sz-1]='\0';} - } - inline const char* getTooltip() const {return tooltip;} - void setTooltip(const char* tt) { - if (tooltip) {ImGui::MemFree(tooltip);tooltip=NULL;} - const char e = '\0';if (!tt) tt=&e; - const int sz = strlen(tt); - tooltip = (char*) ImGui::MemAlloc(sz+1);strcpy(tooltip,tt); - } - void setUserText(const char* _userText) { - if (userText) {ImGui::MemFree(userText);userText=NULL;} - if (_userText) { - const int sz = strlen(_userText); - userText = (char*) ImGui::MemAlloc(sz+1);strcpy(userText,_userText); - } - } - inline const char* getUserText() const {return userText;} - inline bool isClosable() const {return closable;} - inline bool isDraggable() const {return draggable;} - mutable void* userPtr; - mutable int userInt; - mutable bool mustCloseNextFrame; - mutable bool mustSelectNextFrame; - mutable int wndFlags; // used for the imgui child window that hosts the tab content - - // This method will be used ONLY if you don't set TabWindow::SetWindowContentDrawerCallback(...), - // (and you prefer extending from this class) - virtual void render() { - ImGui::Text("Here is the content of tab label: \"%s\".",getLabel()); - ImGui::Separator(); - ImGui::Spacing(); - ImGui::Text("Please consider using:"); - ImGui::Text("ImGui::TabWindow::SetWindowContentDrawerCallback(...)"); - ImGui::TextWrapped("%s","to set a content for it, or extending TabWindowLabel and implement its render() method."); - } - - virtual bool saveAs(const char* savePath=NULL) { - setModified(false); - return true; - } -}; -protected: -public: -typedef void (*TabLabelCallback)(TabLabel* tabLabel,TabWindow& parent,void* userPtr); -//typedef void (*TabLabelClosingCallback)(const ImVector& tabLabels,const ImVector& parents,ImVector& forbidClosingOut,void* userPtr); -typedef void (*TabLabelDeletingCallback)(TabLabel* tabLabel); -typedef void (*TabLabelGroupPopupMenuCallback)(ImVector& tabLabels,TabWindow& parent,struct TabWindowNode* node,void* userPtr); -typedef TabLabel* (*TabLabelFactoryCallback)(TabWindow& parent,const char* label,const char* tooltip,bool closable,bool draggable,void* userPtr,const char* userText,int userInt,int ImGuiWindowFlagsForContent); -typedef bool (*TabLabelFileCallback)(TabLabel* tabLabel,TabWindow& parent,const char* filePath); - - -protected: -struct TabWindowNode* mainNode; // owned -struct TabWindowNode* activeNode; // reference -ImVector tabWindowsToExclude;// can't exchange tab labels with these -bool isolatedMode; // can't exchange tab labels outside -bool init; -IMGUI_API void clearNodes(); - -public: -static TabLabelCallback WindowContentDrawerCb; -static void* WindowContentDrawerUserPtr; -static TabLabelCallback TabLabelPopupMenuDrawerCb; -static void* TabLabelPopupMenuDrawerUserPtr; -//static TabLabelClosingCallback TabLabelClosingCb; -//static void* TabLabelClosingUserPtr; -static TabLabelDeletingCallback TabLabelDeletingCb; -static TabLabelGroupPopupMenuCallback TabLabelGroupPopupMenuDrawerCb; -static void* TabLabelGroupPopupMenuDrawerUserPtr; -static TabLabelFactoryCallback TabLabelFactoryCb; -static TabLabelFileCallback TabLabelSaveCb; - - -public: -IMGUI_API TabWindow(); -IMGUI_API ~TabWindow(); - -// Handy for initialization before calling render() the firsat time -bool isInited() const {return init;} - -// Here "label" is NOT used as ImGui ID (you shouldn't worry about it): it's just the text you want to display -// If "TabLabelFactoryCb" is present, it will be used in the following method: -IMGUI_API TabLabel* addTabLabel(const char* label,const char* tooltip=NULL,bool closable=true,bool draggable=true,void* userPtr=NULL,const char* userText=NULL,int userInt=0,int ImGuiWindowFlagsForContent=0); -IMGUI_API TabLabel* addTabLabel(TabLabel* tabLabel,bool checkIfAlreadyPresent=true); // use it only if you extend TabLabel -IMGUI_API bool removeTabLabel(TabLabel* tab); -IMGUI_API void clear(); - -// Find methods (untested) -IMGUI_API TabLabel* findTabLabelFromLabel(const char* label) const; // trimming the last trailing asterisk -IMGUI_API TabLabel* findTabLabelFromTooltip(const char* tooltip) const; -IMGUI_API TabLabel* findTabLabelFromUserPtr(void* userPtr) const; -IMGUI_API TabLabel* findTabLabelFromUserText(const char* userText) const; -IMGUI_API static TabLabel* FindTabLabelFromLabel(const char* label,const TabWindow* pTabWindows,int numTabWindows,int* pOptionalTabWindowIndexOut); // trimming the last trailing asterisk -IMGUI_API static TabLabel* FindTabLabelFromTooltip(const char* tooltip,const TabWindow* pTabWindows,int numTabWindows,int* pOptionalTabWindowIndexOut); -IMGUI_API static TabLabel* FindTabLabelFromUserPtr(void* userPtr,const TabWindow* pTabWindows,int numTabWindows,int* pOptionalTabWindowIndexOut); -IMGUI_API static TabLabel* FindTabLabelFromUserText(const char* userText,const TabWindow* pTabWindows,int numTabWindows,int* pOptionalTabWindowIndexOut); - -// Callbacks -static void SetWindowContentDrawerCallback(TabLabelCallback _windowContentDrawer,void* userPtr=NULL) { - WindowContentDrawerCb=_windowContentDrawer; - WindowContentDrawerUserPtr=userPtr; -} -static void SetTabLabelPopupMenuDrawerCallback(TabLabelCallback _tabLabelPopupMenuDrawer,void* userPtr=NULL) { - TabLabelPopupMenuDrawerCb=_tabLabelPopupMenuDrawer; - TabLabelPopupMenuDrawerUserPtr=userPtr; -} -inline static const char* GetTabLabelPopupMenuName() {return "TabWindowTabLabelPopupMenu";} -/*static void SetTabLabelClosingCallback(TabLabelClosingCallback _tabLabelClosing,void* userPtr=NULL) { - TabLabelClosingCb=_tabLabelClosing; - TabLabelClosingUserPtr=userPtr; -}*/ -static void SetTabLabelDeletingCallback(TabLabelDeletingCallback _tabLabelDeleting) {TabLabelDeletingCb=_tabLabelDeleting;} -static void SetTabLabelGroupPopupMenuDrawerCallback(TabLabelGroupPopupMenuCallback _tabLabelGroupPopupMenuDrawer,void* userPtr=NULL) { - TabLabelGroupPopupMenuDrawerCb=_tabLabelGroupPopupMenuDrawer; - TabLabelGroupPopupMenuDrawerUserPtr=userPtr; -} -inline static const char* GetTabLabelGroupPopupMenuName() {return "TabWindowTabLabelGroupPopupMenu";} -static void SetTabLabelFactoryCallback(TabLabelFactoryCallback _tabLabelFactoryCb) {TabLabelFactoryCb=_tabLabelFactoryCb;} -static void SetTabLabelSaveCallback(TabLabelFileCallback _tabLabelSaveCb) {TabLabelSaveCb=_tabLabelSaveCb;} -inline static const char* GetTabLabelAskForDeletionModalWindowName() {return "Save modified file/s ?##TabWindowTabLabelAskForDeletionModalWindowName";} - - -// Main method -IMGUI_API void render(); - -// Texture And Memory Data (4 channels, no additional stride between lines, => rgba_buffer_out.size() = 4*(*w_out)*(*h_out) ) -static void GetDockPanelIconImageRGBA(ImVector& rgba_buffer_out,int* w_out=NULL,int* h_out=NULL); // Manually redrawn based on the ones in https://github.com/dockpanelsuite/dockpanelsuite (that is MIT licensed). So no copyright issues for this AFAIK, but I'm not a lawyer and I cannot guarantee it. -static ImTextureID DockPanelIconTextureID; // User must load it (using GetDockPanelIconImageRGBA) and free it when no IMGUI_USE_XXX_BINDING is used. - -// These are just optional "filtering" methods -bool isIsolated() const {return isolatedMode;} // can't exchange tab labels outside -void setIsolatedMode(bool flag) {isolatedMode=flag;} -IMGUI_API void excludeTabWindow(TabWindow& tw); // can't exchange tab labels with... -IMGUI_API void includeTabWindow(TabWindow& tw); // removes from the "exclude list"... -const ImVector& getTabWindowsToExclude() const {return tabWindowsToExclude;} -IMGUI_API bool canExchangeTabLabelsWith(TabWindow* tw); - -IMGUI_API bool isMergeble(struct TabWindowNode* node); -IMGUI_API int getNumTabs(struct TabWindowNode* node); -IMGUI_API int getNumClosableTabs(struct TabWindowNode* node); -IMGUI_API bool merge(struct TabWindowNode* node); // Warning: it invalidates "node" after the call - -IMGUI_API static void GetAllTabLabels(TabWindow* pTabWindowsIn, int numTabWindowsIn, ImVector& tabsOut, ImVector& parentsOut, bool onlyClosableTabs=false, bool onlyModifiedTabs=false); -inline static void SaveAll(ImVector& tabs, ImVector& parents) {CloseTabLabelsHelper(tabs,parents,true,false,false,true);} -inline static bool StartCloseAllDialog(ImVector& tabs, ImVector& parents,bool allowCancelDialog=true) {return CloseTabLabelsHelper(tabs,parents,true,true,allowCancelDialog,false);} -IMGUI_API static bool AreSomeDialogsOpen(); - -IMGUI_API void getAllTabLabels(ImVector& tabsOut,bool onlyClosableTabs=false, bool onlyModifiedTabs=false); -IMGUI_API void saveAll(ImVector* ptabs=NULL); -IMGUI_API bool startCloseAllDialog(ImVector* ptabs=NULL,bool allowCancelDialog=true); - -mutable void* userPtr; - -static ImGuiWindowFlags ExtraWindowFlags; - -//------------------------------------------------------------------------------- -# if (defined(IMGUIHELPER_H_) && !defined(NO_IMGUIHELPER_SERIALIZATION)) -# ifndef NO_IMGUIHELPER_SERIALIZATION_SAVE -public: - IMGUI_API bool save(ImGuiHelper::Serializer& s) const; - IMGUI_API bool save(const char* filename) const; - IMGUI_API static bool Save(const char* filename,const TabWindow* pTabWindows,int numTabWindows); -# endif //NO_IMGUIHELPER_SERIALIZATION_SAVE -# ifndef NO_IMGUIHELPER_SERIALIZATION_LOAD -public: - IMGUI_API bool load(ImGuiHelper::Deserializer& d,const char ** pOptionalBufferStart=NULL); - IMGUI_API bool load(const char* filename); - IMGUI_API static bool Load(const char* filename,TabWindow* pTabWindows,int numTabWindows); -# endif //NO_IMGUIHELPER_SERIALIZATION_LOAD -# endif //NO_IMGUIHELPER_SERIALIZATION -//-------------------------------------------------------------------------------- - -protected: - -IMGUI_API TabLabel* createTabLabel(const char* label,const char* tooltip=NULL,bool closable=true,bool draggable=true,void* userPtr=NULL,const char* userText=NULL,int userInt=0,int ImGuiWindowFlagsForContent=0); -IMGUI_API static bool CloseTabLabelsHelper(ImVector& tabs, ImVector& parents, bool saveAll, bool askForSaving, bool allowCancelDialog, bool dontCloseTabs); - - -friend struct TabLabel; -friend struct TabWindowNode; -friend struct TabWindowDragData; - -private: -IMGUI_API static bool ModalDialogSaveDisplay(const char* dialogName,ImVector& TabsToAskFor,ImVector& TabsToAskForParents, -IMGUI_API bool closeTabsAfterSaving,bool allowCancel,bool * pMustCloseDialogOut=NULL,const char* btnDoNotSaveName="Do not save",const char* btnSaveName="Save",const char* btnCancelName="Cancel", -IMGUI_API const char* dialogTitleLine1="The following tab labels have unsaved changes.",const char* dialogTitleLine2="Do you want to save them ?"); - -}; -typedef TabWindow::TabLabel TabWindowLabel; - - -// This has nothing to do with TabWindow; it's just a port of this gist: https://gist.github.com/Flix01/3bc3d7b3d996582e034e sharing "TabLabelStyle". -// Please see: https://github.com/ocornut/imgui/issues/261 for further info -// Based on the code by krys-spectralpixel (https://github.com/krys-spectralpixel), posted here: https://github.com/ocornut/imgui/issues/261 -/* pOptionalHoveredIndex: a ptr to an optional int that is set to -1 if no tab label is hovered by the mouse. - * pOptionalItemOrdering: an optional static array of unique integers from 0 to numTabs-1 that maps the tab label order. If one of the numbers is replaced by -1 the tab label is not visible (closed). It can be read/modified at runtime. - * allowTabReorder (requires pOptionalItemOrdering): allows tab reordering through drag and drop (it modifies pOptionalItemOrdering). - * However it seems to work only when dragging tabs from the left (top) to the right (bottom) and not vice-versa (this is bad, but can't lock the tab order in any way). - * allowTabClosing (requires pOptionalItemOrdering): adds a close button to the tab. When the close button is clicked, the tab value in pOptionalItemOrdering is set to -1. - * pOptionalClosedTabIndex (requires allowTabClosing): out variable (int pointer) that returns the index of the closed tab in last call or -1. - * pOptionalClosedTabIndexInsideItemOrdering (requires allowTabClosing): same as above, but index inside the pOptionalItemOrdering array. Users can use this value to prevent single tabs from closing when their close button is clicked (since we can't mix closable and non-closable tabs here). -*/ -IMGUI_API bool TabLabels(int numTabs, const char** tabLabels, int& selectedIndex, const char** tabLabelTooltips=NULL , bool wrapMode=true, int* pOptionalHoveredIndex=NULL, int* pOptionalItemOrdering=NULL, bool allowTabReorder=true, bool allowTabClosing=false, int* pOptionalClosedTabIndex=NULL,int * pOptionalClosedTabIndexInsideItemOrdering=NULL); - -// ImGui::TabLabelsVertical() are similiar to ImGui::TabLabels(), but they do not support WrapMode. -IMGUI_API bool TabLabelsVertical(bool textIsRotatedCCW,int numTabs, const char** tabLabels, int& selectedIndex, const char** tabLabelTooltips=NULL, int* pOptionalHoveredIndex=NULL, int* pOptionalItemOrdering=NULL, bool allowTabReorder=false, bool allowTabClosing=false, int* pOptionalClosedTabIndex=NULL,int * pOptionalClosedTabIndexInsideItemOrdering=NULL,bool invertRounding=false); -IMGUI_API float CalcVerticalTabLabelsWidth(); - - -// Untested attempt to provide serialization for ImGui::TabLabels(...) or ImGui::TabLabelsVertical(...): only "selectedIndex" and "pOptionalItemOrdering" are serialized. -//------------------------------------------------------------------------------- -# if (defined(IMGUIHELPER_H_) && !defined(NO_IMGUIHELPER_SERIALIZATION)) -# ifndef NO_IMGUIHELPER_SERIALIZATION_SAVE - IMGUI_API bool TabLabelsSave(ImGuiHelper::Serializer& s,int selectedIndex,const int* pOptionalItemOrdering=NULL,int numTabs=0); - IMGUI_API bool TabLabelsSave(const char* filename,int selectedIndex,const int* pOptionalItemOrdering=NULL,int numTabs=0); -# endif //NO_IMGUIHELPER_SERIALIZATION_SAVE -# ifndef NO_IMGUIHELPER_SERIALIZATION_LOAD - IMGUI_API bool TabLabelsLoad(ImGuiHelper::Deserializer& d,int* pSelectedIndex,int* pOptionalItemOrdering=NULL,int numTabs=0,const char ** pOptionalBufferStart=NULL); - IMGUI_API bool TabLabelsLoad(const char* filename,int* pSelectedIndex,int* pOptionalItemOrdering=NULL,int numTabs=0); -# endif //NO_IMGUIHELPER_SERIALIZATION_LOAD -# endif //NO_IMGUIHELPER_SERIALIZATION -//-------------------------------------------------------------------------------- - - - -} // namespace ImGui - - -#endif //IMGUITABWINDOW_H_ diff --git a/Source Code/Libs/imgui/docs/CHANGELOG.txt b/Source Code/Libs/imgui/docs/CHANGELOG.txt index 1767302247..0f1670fb56 100644 --- a/Source Code/Libs/imgui/docs/CHANGELOG.txt +++ b/Source Code/Libs/imgui/docs/CHANGELOG.txt @@ -29,6 +29,60 @@ HOW TO UPDATE? - Please report any issue! +----------------------------------------------------------------------- + DOCKING BRANCH (In Progress) +----------------------------------------------------------------------- + +- Added ImGuiConfigFlags_DockingEnable flag to enable Docking. [BETA] + Set with `io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;`. +- Added BeginTabBar(), EndTabBar(), BeginTabItem(), EndTabItem(), SetTabItemClosed() API. (#261, #351) +- Added ImGuiTabBarFlags flags for BeginTabBar(). +- Added ImGuiTabItemFlags flags for BeginTabItem(). +- Added DockSpace() API. (#351) +- Added ImGuiDockNodeFlags flags for DockSpace(). +- Added SetNextWindowDock(), SetNextWindowDockFamily() API. (#351) +- Added GetWindowDockId(), IsWindowDocked() API. (#351) +- Added ImGuiWindowFlags_NoDocking window flag to disable the possibility for a window to be docked. + Popup, Menu and Child windows always have the ImGuiWindowFlags_NoDocking flag set. (#351) +- Added ImGuiWindowFlags_UnsavedDocument window flag to append '*' to title without altering the ID, + as a convenience to avoid using the ### operator. +- Added io.ConfigDockingWithShift option to configure docking mode. +- Style: Added ImGuiCol_DockingPreview, ImGuiCol_DockingBg colors. (#351) +- Style: Added ImGuiCol_Tab, ImGuiCol_TabHovered, ImGuiCol_TabActive, ImGuiCol_TabUnfocused, ImGuiCol_TabUnfocusedActive colors. (#261, #351) +- Demo: Added Layout->Tabs demo code. (#261, #351) +- Demo: Added "Documents" example app showcasing possible use for tabs. (#261, #351) +- Demo: Added "DockSpace" example app showcasing use of explicit dockspace nodes. (#351) + + +----------------------------------------------------------------------- + VERSION 1.66 (In Progress) +----------------------------------------------------------------------- + +Breaking Changes: + +- Renamed SetScrollHere() to SetScrollHereY(). Kept redirection function (will obsolete). + +Other Changes: + +- Fixed calling DestroyContext() always saving .ini data with the current context instead + of the supplied context pointer. (#2066) +- Fixed calling SetNextWindowSize()/SetWindowSize() with non-integer values leading to + accidental alteration of window position. We now round the provided size. (#2067) +- Nav, Focus: Fixed ImGuiWindowFlags_NoBringToFrontOnFocus windows not being restoring focus + properly after the main menu bar or last focused window is deactivated. +- DragFloat: Fixed a situation where dragging with value rounding enabled or with a power curve + erroneously wrapped the value to one of the min/max edge. (#2024, #708, #320, #2075). +- DragFloat: Disabled using power curve when one edge is FLT_MAX (broken in 1.61). (#2024) +- DragFloat: Disabled setting a default drag speed when one edge is FLT_MAX. (#2024) +- BeginChild(): Fixed BeginChild(const char*, ...) variation erroneously not applying the ID stack + to the provided string to uniquely identify the child window. This was undoing an intentional change + introduced in 1.50 and broken in 1.60. (#1698, #894, #713). +- BeginMenu(): Fixed menu popup horizontal offset being off the item in the menu bar when WindowPadding=0.0f. +- ArrowButton(): Fixed arrow shape being horizontally misaligned by (FramePadding.y-FramePadding.x) if they are different. +- ImDrawList: Fixed AddConvexPolyFilled() undefined behavior when passing points_count smaller than 3, + in particular, points_count==0 could lead to a memory stomp if the draw list was previously empty. + + ----------------------------------------------------------------------- VERSION 1.65 (Released 2018-09-06) ----------------------------------------------------------------------- @@ -293,6 +347,37 @@ The gamepad/keyboard navigation branch (which has been in the work since July 20 Gamepad/keyboard navigation is still marked as Beta and has to be enabled explicitly. Various internal refactoring have also been done, as part of the navigation work and as part of the upcoming viewport/docking work. +VIEWPORT BRANCH +(IN PROGRESS, WILL MERGE INTO THE MAIN LISTS WHEN WE MERGE THE BRANCH) + + - Viewport: Added support for multi-viewport [...] blah blah + - Viewport: Rendering: the ImDrawData structure now contains 'DisplayPos' and 'DisplaySize' fields. To support multi-viewport, you need to use those values when + creating your orthographic projection matrix. Use 'draw_data->DisplaySize' instead of 'io.DisplaySize', and 'draw_data->DisplayPos' instead of (0,0) as the upper-left point. + You also need to subtract 'draw_data->DisplayPos' from your scissor rectangles, as scissor rectangles are specified in the space of your target viewport. + - Examples: Back-ends have been refactored to separate the platform code (e.g. Win32, Glfw, SDL2) from the renderer code (e.g. DirectX11, OpenGL3, Vulkan). + before: imgui_impl_dx11.cpp --> after: imgui_impl_win32.cpp + imgui_impl_dx11.cpp + before: imgui_impl_dx12.cpp --> after: imgui_impl_win32.cpp + imgui_impl_dx12.cpp + before: imgui_impl_glfw_gl3.cpp --> after: imgui_impl_glfw.cpp + imgui_impl_opengl2.cpp + before: imgui_impl_glfw_vulkan.cpp --> after: imgui_impl_glfw.cpp + imgui_impl_vulkan.cpp + before: imgui_impl_sdl_gl3.cpp --> after: imgui_impl_sdl2.cpp + imgui_impl_opengl2.cpp + before: imgui_impl_sdl_gl3.cpp --> after: imgui_impl_sdl2.cpp + imgui_impl_opengl3.cpp + etc. + - The idea is what we can now easily combine and maintain back-ends and reduce code redundancy. Integration of imgui into a new/custom engine may also + be easier as there is less overlap between "windowing / inputs" and "rendering" code, so you may study or grab one half of the code and not the other. + - This change was motivated by the fact that adding support for multi-viewport requires more work from the platform and renderer back-ends, and the + amount of redundancy accross files was becoming too difficult to maintain. + - Some frameworks (such as the Allegro, Marmalade) handle both the "platform" and "rendering" part, and your custom engine may as well. + - Each example still has its own main.cpp which you may refer you to understand how to initialize and glue everything together. + - Examples: Win32: Added DPI-related helpers to access DPI features _without_ requiring the latest Windows SDK at compile time, and _without_ requiring Windows 10 at runtime. + - Examples: Platforms currently supporting multi-viewport: Win32, Glfw, SDL2. + - Examples: Renderers currently supporting multi-viewport: DirectX10, DirectX11, OpenGL2, OpenGL3, Vulkan (WIP). + - Examples: All imgui_impl_xxx files now have an individual Changelog at the top of the file, making it easier to follow how back-ends are evolving. + - Examples: Vulkan: Added various optional helpers in imgui_impl_vulkan.h (they are used for multi-viewport support) to make the examples main.cpp easier to read. + - Examples: Allegro: Renamed imgui_impl_a5.xxx files to imgui_impl_allegro5.xxx, ImGui_ImplA5_** symbols to ImGui_ImplAllegro5_xxx. + - Examples: Vulkan+SDL: Added a Vulkan+SDL example. (#1367) [@gmueckl] + - Metrics: Added a "Show window begin order" checkbox to visualize the order windows are submitted. + - Internal: Settings: Added ReadCloseFn handler to be able to patch/alter a loaded object after all the fields are known. + Breaking Changes: - Obsoleted the io.RenderDrawListsFn callback, you can call your graphics engine render function after ImGui::Render(). @@ -314,6 +399,9 @@ Breaking Changes: - Removed ImGuiCol_CloseButton, ImGuiCol_CloseButtonActive, ImGuiCol_CloseButtonHovered style colors as the closing cross uses regular button colors now. - Renamed ImGuiSizeConstraintCallback to ImGuiSizeCallback, ImGuiSizeConstraintCallbackData to ImGuiSizeCallbackData. - Removed CalcItemRectClosestPoint() which was weird and not really used by anyone except demo code. If you need it should be easy to replicate on your side (you can find the code in 1.53). +- [EDITED] Window: BeginChild() with an explicit name doesn't include the hash within the internal window name. (#1698) + This change was erroneously introduced, undoing the change done for #894, #713, and not documented properly in the original + 1.60 release Changelog. It was fixed on 2018-09-28 (1.66) and I wrote this paragraph the same day. Other Changes: @@ -522,7 +610,6 @@ Other Changes: - Window: Using the ImGuiWindowFlags_NoScrollWithMouse flag on a child window forwards the mouse wheel event to the parent window, unless either ImGuiWindowFlags_NoInputs or ImGuiWindowFlags_NoScrollbar are also set. (#1380, #1502) - Window: Active Modal window always set the WantCaptureKeyboard flag. (#744) - Window: Moving window doesn't use accumulating MouseDelta so straying out of imgui boundaries keeps moved imgui window at the same cursor-relative position. -- Window: BeginChild() which an explicit name doesn't include the hash within the internal window name. (#1698) - IsWindowFocused(): Added ImGuiFocusedFlags_ChildWindows flag to include child windows in the focused test. (#1382). - IsWindowFocused(): Added ImGuiFocusedFlags_RootWindow flag to start focused test from the root (top-most) window. Obsolete IsRootWindowFocused(). (#1382) - IsWindowHovered(): Added ImGuiHoveredFlags_ChildWindows flag to include child windows in the hovered test. (#1382). diff --git a/Source Code/Libs/imgui/docs/README.md b/Source Code/Libs/imgui/docs/README.md index 06b8cb409d..8dd9eed6f1 100644 --- a/Source Code/Libs/imgui/docs/README.md +++ b/Source Code/Libs/imgui/docs/README.md @@ -25,9 +25,9 @@ Dear ImGui is self-contained within a few files that you can easily copy and com - imgui_widgets.cpp - imgui_internal.h - imconfig.h (empty by default, user-editable) -- stb_rect_pack.h -- stb_textedit.h -- stb_truetype.h +- imstb_rect_pack.h +- imstb_textedit.h +- imstb_truetype.h No specific build process is required. You can add the .cpp files to your project or #include them from an existing file. diff --git a/Source Code/Libs/imgui/docs/TODO.txt b/Source Code/Libs/imgui/docs/TODO.txt index 2b76024393..31536e21ae 100644 --- a/Source Code/Libs/imgui/docs/TODO.txt +++ b/Source Code/Libs/imgui/docs/TODO.txt @@ -123,8 +123,32 @@ It's mostly a bunch of personal notes, probably incomplete. Feel free to query i - splitter/separator: formalize the splitter idiom into an official api (we want to handle n-way split) (#319) - - dock: docking extension - - dock: dock out from a collapsing header? would work nicely but need emitting window to keep submitting the code. + - dock: A~ Unreal style document system (requires low-level controls of dockspace serialization fork/copy/delete). this is mostly working but the DockBuilderXXX api are not exposed/finished. + - dock: A- implicit, invisible per-viewport dockspace to dock to. + - dock: B: when docking outer, perform size locking on neighbors nodes the same way we do it with splitters, so other nodes are not resized. + - dock: B~ document root node resizing behavior incorrect. + - dock: B~ document root node retrieval of ID ? + - dock: B- debug full rebuild loses viewport of floating dock nodes. + - dock: B- dock node inside its own viewports creates 1 temporary viewport per window on startup before ditching them (doesn't affect the user nor request platform windows to be created, but unnecessary) + - dock: B- resize sibling locking behavior may be less desirable if we merged same-axis sibling in a same node level? + - dock: B- single visible node part of a hidden split hierarchy (OnlyNodeWithWindows != NULL) should show a normal title bar (not a tab bar) + - dock: B~ SetNextWindowDock() calls (with conditional) -> defer everything to DockContextUpdate (repro: Documents->[X]Windows->Dock 1 elsewhere->Click Redock All + - dock: B~ tidy up tab list popup buttons features (available with manual tab-bar, see ImGuiTabBarFlags_NoTabListPopupButton code, not used by docking nodes) + - dock: B- DockSpace() border issues + - dock: B- inconsistent clipping/border 1-pixel issue (#2) + - dock: B- fix/disable auto-resize grip on split host nodes (~#2) + - dock: B- SetNextWindowFocus() doesn't seem to apply if the window is hidden this frame, need repro (#4) + - dock: B- resizing a dock tree small currently has glitches (overlapping collapse and close button, etc.) + - dock: B- dpi: look at interaction with the hi-dpi and multi-dpi stuff. + - dock: B- tab bar: appearing on first frame with a dumb layout would do less harm that not appearing? (when behind dynamic branch) or store titles + render in EndTabBar() + - dock: B- tab bar: make selected tab always shows its full title? + - dock: B- tab bar: the order/focus restoring code could be part of TabBar and not DockNode? (#8) + - dock: B- nav: design interactions so nav controls can dock/undock + - dock: B- dockspace: flag to lock the dock tree and/or sizes + - dock: B- reintroduce collapsing a floating dock node. also collapsing a docked dock node! + - dock: B- allow dragging a non-floating dock node by clicking on the title-bar-looking section (not just the collapse/menu button) + - dock: C- nav: CTRL+TAB highlighting tabs shows the mismatch between focus-stack and tab-order (not visible in VS because it doesn't highlight the tabs) + - dock: C- after a dock/undock, the Scrollbar Status update in Begin() should use an updated e.g. size_y_for_scrollbars to avoid a 1 frame scrollbar flicker. - tabs: re-ordering, close buttons, context menu, persistent order (#261, #351) @@ -279,6 +303,16 @@ It's mostly a bunch of personal notes, probably incomplete. Feel free to query i - focus: SetKeyboardFocusHere() on with >= 0 offset could be done on same frame (else latch and modulate on beginning of next frame) - focus: unable to use SetKeyboardFocusHere() on clipped widgets. (#787) + - examples: move ImGui::NewFrame() out of the backend _NewFrame() ? + - viewport: make it possible to have no main/hosting viewport + - viewport: use getfocus/setfocus api to synchronize imgui<>platform focus better (e.g imgui-side ctrl-tab can focus os window, OS initial setup and alt-tab can focus imgui window etc.) + - viewport: store per-viewport/monitor DPI in .ini file so an application reload or main window changing DPI on reload can be properly patched for. + - viewport: vulkan renderer implementation. + - viewport: need to clarify how to use GetMousePos() from a user point of view. + - platform: glfw: no support for ImGuiBackendFlags_HasMouseHoveredViewport. + - platform: sdl: no support for ImGuiBackendFlags_HasMouseHoveredViewport. maybe we could use SDL_GetMouseFocus() / SDL_WINDOW_MOUSE_FOCUS if imgui could fallback on its heuristic when NoInputs is set + - platform: sdl: no refresh of monitor/display (SDL doesn't seem to have an event for it). + - inputs: we need an explicit flag about whether the imgui window is focused, to be able to distinguish focused key releases vs alt-tabbing all release behaviors. - inputs: rework IO system to be able to pass actual ordered/timestamped events. use an event queue? (~#335, #71) - inputs: support track pad style scrolling & slider edit. diff --git a/Source Code/Libs/imgui/imgui.cpp b/Source Code/Libs/imgui/imgui.cpp index 73667447ed..b60928102c 100644 --- a/Source Code/Libs/imgui/imgui.cpp +++ b/Source Code/Libs/imgui/imgui.cpp @@ -1,6 +1,6 @@ #include "stdafx.h" -// dear imgui, v1.65 +// dear imgui, v1.66 WIP // (main code and documentation) // Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp for demo code. @@ -24,6 +24,7 @@ Index of this file: DOCUMENTATION + - MISSION STATEMENT - END-USER GUIDE - PROGRAMMER GUIDE (read me!) @@ -65,9 +66,11 @@ CODE // [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!) // [SECTION] TOOLTIPS // [SECTION] POPUPS +// [SECTION] VIEWPORTS, PLATFORM WINDOWS // [SECTION] KEYBOARD/GAMEPAD NAVIGATION // [SECTION] COLUMNS // [SECTION] DRAG AND DROP +// [SECTION] DOCKING // [SECTION] LOGGING/CAPTURING // [SECTION] SETTINGS // [SECTION] PLATFORM DEPENDENT HELPERS @@ -343,6 +346,18 @@ CODE When you are not sure about a 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. + (Viewport Branch) + - 2018/XX/XX (1.XX) - examples: the examples imgui_impl_xxx files have been split to separate platform (Win32, Glfw, SDL2, etc.) from renderer (DX11, OpenGL, Vulkan, etc.) + when adopting new bindings follow the code in examples/ to know which functions to call. + - 2018/XX/XX (1.XX) - 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 coordinates. + If you query mouse positions to interact with non-imgui coordinates you will need to offset them, e.g. subtract GetWindowViewport()->Pos. + - 2018/XX/XX (1.XX) - Moved IME support functions from io.ImeSetInputScreenPosFn, io.ImeWindowHandle to the PlatformIO api. + - 2018/XX/XX (1.XX) - removed io.DisplayVisibleMin, io.DisplayVisibleMax settings (it was used to clip within the DisplayMin..DisplayMax range, I don't know of anyone using it) + + - 2018/09/28 (1.66) - renamed SetScrollHere() to SetScrollHereY(). Kept redirection function (will obsolete). - 2018/09/06 (1.65) - renamed stb_truetype.h to imstb_truetype.h, stb_textedit.h to imstb_textedit.h, and stb_rect_pack.h to imstb_rectpack.h. If you were conveniently using the imgui copy of those STB headers in your project you will have to update your include paths. - 2018/09/05 (1.65) - renamed io.OptCursorBlink/io.ConfigCursorBlink to io.ConfigInputTextCursorBlink. (#1427) @@ -808,7 +823,7 @@ CODE Q: How can I use the drawing facilities without an ImGui window? (using ImDrawList API) A: - You can create a dummy window. Call SetNextWindowBgAlpha(0.0f), call Begin() with NoTitleBar|NoResize|NoMove|NoScrollbar|NoSavedSettings|NoInputs flags. Then you can retrieve the ImDrawList* via GetWindowDrawList() and draw to it in any way you like. - - You can call ImGui::GetOverlayDrawList() and use this draw list to display contents over every other imgui windows. + - You can call ImGui::GetOverlayDrawList() and use this draw list to display contents over every other imgui windows (1 overlay per viewport). - You can create your own ImDrawList instance. You'll need to initialize them ImGui::GetDrawListSharedData(), or create your own ImDrawListSharedData. Q: I integrated Dear ImGui in my engine and the text or lines are blurry.. @@ -855,8 +870,9 @@ CODE #include // intptr_t #endif -#define IMGUI_DEBUG_NAV_SCORING 0 -#define IMGUI_DEBUG_NAV_RECTS 0 +#define IMGUI_DEBUG_NAV_SCORING 0 // Display navigation scoring preview when hovering items. Display last moving direction matches when holding CTRL +#define IMGUI_DEBUG_NAV_RECTS 0 // Display the reference navigation rectangle for each window +#define IMGUI_DEBUG_DOCKING_INI 0 // Save additional comments in .ini file (makes saving slower) // Visual Studio warnings #ifdef _MSC_VER @@ -906,10 +922,7 @@ static void CheckStacksSize(ImGuiWindow* window, bool write); static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window, bool snap_on_edges); static void AddDrawListToDrawData(ImVector* out_list, ImDrawList* draw_list); -static void AddWindowToDrawData(ImVector* out_list, ImGuiWindow* window); -static void AddWindowToSortedBuffer(ImVector* out_sorted_windows, ImGuiWindow* window); - -static ImRect GetViewportRect(); +static void AddWindowToSortBuffer(ImVector* out_sorted_windows, ImGuiWindow* window); // Settings static void* SettingsHandlerWindow_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name); @@ -919,7 +932,6 @@ static void SettingsHandlerWindow_WriteAll(ImGuiContext* imgui_ctx, // Platform Dependents default implementation for IO functions static const char* GetClipboardTextFn_DefaultImpl(void* user_data); static void SetClipboardTextFn_DefaultImpl(void* user_data, const char* text); -static void ImeSetInputScreenPosFn_DefaultImpl(int x, int y); namespace ImGui { @@ -941,6 +953,18 @@ static ImGuiWindow* NavRestoreLastChildNavWindow(ImGuiWindow* window); static void UpdateMouseInputs(); static void UpdateMouseWheel(); static void UpdateManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4]); + +// Viewports +const ImGuiID IMGUI_VIEWPORT_DEFAULT_ID = 0x11111111; // Using an arbitrary constant instead of e.g. ImHash("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 UpdateViewports(); +static void UpdateSelectWindowViewport(ImGuiWindow* window); +static bool UpdateTryMergeWindowIntoHostViewport(ImGuiWindow* window, ImGuiViewportP* host_viewport); +static void SetCurrentViewport(ImGuiWindow* window, ImGuiViewportP* viewport); +static bool GetWindowAlwaysWantOwnViewport(ImGuiWindow* window); +static int FindPlatformMonitorForPos(const ImVec2& pos); +static int FindPlatformMonitorForRect(const ImRect& r); + } //----------------------------------------------------------------------------- @@ -1000,8 +1024,10 @@ ImGuiStyle::ImGuiStyle() ScrollbarRounding = 9.0f; // Radius of grab corners rounding for scrollbar GrabMinSize = 10.0f; // Minimum width/height of a grab box for slider/scrollbar GrabRounding = 0.0f; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs. + TabRounding = 4.0f; // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs. + TabBorderSize = 0.0f; // Thickness of border around tabs. ButtonTextAlign = ImVec2(0.5f,0.5f);// Alignment of button text when button is larger than text. - DisplayWindowPadding = ImVec2(20,20); // Window position are clamped to be visible within the display area by at least this amount. Only applies to regular windows. + DisplayWindowPadding = ImVec2(20,20); // 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. MouseCursorScale = 1.0f; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. AntiAliasedLines = true; // Enable anti-aliasing on lines/borders. Disable if you are really short on CPU/GPU. @@ -1023,6 +1049,7 @@ void ImGuiStyle::ScaleAllSizes(float scale_factor) PopupRounding = ImFloor(PopupRounding * scale_factor); FramePadding = ImFloor(FramePadding * scale_factor); FrameRounding = ImFloor(FrameRounding * scale_factor); + TabRounding = ImFloor(TabRounding * scale_factor); ItemSpacing = ImFloor(ItemSpacing * scale_factor); ItemInnerSpacing = ImFloor(ItemInnerSpacing * scale_factor); TouchExtraPadding = ImFloor(TouchExtraPadding * scale_factor); @@ -1063,23 +1090,21 @@ ImGuiIO::ImGuiIO() FontDefault = NULL; FontAllowUserScaling = false; DisplayFramebufferScale = ImVec2(1.0f, 1.0f); - DisplayVisibleMin = DisplayVisibleMax = ImVec2(0.0f, 0.0f); // Miscellaneous configuration options + ConfigDockingWithShift = false; #ifdef __APPLE__ ConfigMacOSXBehaviors = true; // Set Mac OS X style defaults based on __APPLE__ compile time flag #else ConfigMacOSXBehaviors = false; #endif ConfigInputTextCursorBlink = true; - ConfigResizeWindowsFromEdges = false; + ConfigResizeWindowsFromEdges = true; // Settings (User Functions) GetClipboardTextFn = GetClipboardTextFn_DefaultImpl; // Platform dependent default implementations SetClipboardTextFn = SetClipboardTextFn_DefaultImpl; ClipboardUserData = NULL; - ImeSetInputScreenPosFn = ImeSetInputScreenPosFn_DefaultImpl; - ImeWindowHandle = NULL; #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS RenderDrawListsFn = NULL; @@ -1257,6 +1282,13 @@ void ImStrTrimBlanks(char* buf) buf[p - p_start] = 0; // Zero terminate } +const char* ImStrSkipBlank(const char* str) +{ + while (str[0] == ' ' || str[0] == '\t') + str++; + return str; +} + // A) MSVC version appears to return -1 on overflow, whereas glibc appears to return total count (which may be >= buf_size). // Ideally we would test for only one of those limits at runtime depending on the behavior the vsnprintf(), but trying to deduct it at compile time sounds like a pandora can of worm. // B) When buf==NULL vsnprintf() will return the output size. @@ -1993,12 +2025,12 @@ void ImGuiTextBuffer::appendf(const char* fmt, ...) static void SetCursorPosYAndSetupDummyPrevLine(float pos_y, float line_height) { - // Set cursor position and a few other things so that SetScrollHere() and Columns() can work when seeking cursor. + // Set cursor position and a few other things so that SetScrollHereY() and Columns() can work when seeking cursor. // FIXME: It is problematic that we have to do that here, because custom/equivalent end-user code would stumble on the same issue. // The clipper should probably have a 4th step to display the last item in a regular manner. ImGui::SetCursorPosY(pos_y); ImGuiWindow* window = ImGui::GetCurrentWindow(); - window->DC.CursorPosPrevLine.y = window->DC.CursorPos.y - line_height; // Setting those fields so that SetScrollHere() can properly function after the end of our clipper usage. + window->DC.CursorPosPrevLine.y = window->DC.CursorPos.y - line_height; // Setting those fields so that SetScrollHereY() can properly function after the end of our clipper usage. window->DC.PrevLineSize.y = (line_height - GImGui->Style.ItemSpacing.y); // If we end up needing more accurate data (to e.g. use SameLine) we may as well make the clipper have a fourth step to let user process and display the last item in their list. if (window->DC.ColumnsSet) window->DC.ColumnsSet->LineMinY = window->DC.CursorPos.y; // Setting this so that cell Y position are set properly @@ -2134,17 +2166,8 @@ void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end // Default clip_rect uses (pos_min,pos_max) // Handle clipping on CPU immediately (vs typically let the GPU clip the triangles that are overlapping the clipping rectangle edges) -void ImGui::RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align, const ImRect* clip_rect) +void ImGui::RenderTextClippedEx(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_display_end, const ImVec2* text_size_if_known, const ImVec2& align, const ImRect* clip_rect) { - // Hide anything after a '##' string - const char* text_display_end = FindRenderedTextEnd(text, text_end); - const int text_len = (int)(text_display_end - text); - if (text_len == 0) - return; - - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - // Perform CPU side clipping for single clipped element to avoid using scissor state ImVec2 pos = pos_min; const ImVec2 text_size = text_size_if_known ? *text_size_if_known : CalcTextSize(text, text_display_end, false, 0.0f); @@ -2163,14 +2186,27 @@ void ImGui::RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, cons if (need_clipping) { ImVec4 fine_clip_rect(clip_min->x, clip_min->y, clip_max->x, clip_max->y); - window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, &fine_clip_rect); + draw_list->AddText(NULL, 0.0f, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, &fine_clip_rect); } else { - window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, NULL); + draw_list->AddText(NULL, 0.0f, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, NULL); + } } + +void ImGui::RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align, const ImRect* clip_rect) +{ + // Hide anything after a '##' string + const char* text_display_end = FindRenderedTextEnd(text, text_end); + const int text_len = (int)(text_display_end - text); + if (text_len == 0) + return; + + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + RenderTextClippedEx(window->DrawList, pos_min, pos_max, text, text_display_end, text_size_if_known, align, clip_rect); if (g.LogEnabled) - LogRenderedText(&pos, text, text_display_end); + LogRenderedText(&pos_min, text, text_display_end); } // Render a rectangle shaped with optional rounding and borders @@ -2302,7 +2338,11 @@ ImGuiWindow::ImGuiWindow(ImGuiContext* context, const char* name) Name = ImStrdup(name); ID = ImHash(name, 0); IDStack.push_back(ID); - Flags = 0; + Flags = FlagsPreviousFrame = 0; + Viewport = NULL; + ViewportId = 0; + ViewportAllowPlatformMonitorExtend = -1; + ViewportPos = ImVec2(FLT_MAX, FLT_MAX); Pos = ImVec2(0.0f, 0.0f); Size = SizeFull = ImVec2(0.0f, 0.0f); SizeContents = SizeContentsExplicit = ImVec2(0.0f, 0.0f); @@ -2316,6 +2356,8 @@ ImGuiWindow::ImGuiWindow(ImGuiContext* context, const char* name) ScrollTargetCenterRatio = ImVec2(0.5f, 0.5f); ScrollbarSizes = ImVec2(0.0f, 0.0f); ScrollbarX = ScrollbarY = false; + ViewportOwned = false; + ViewportTryMerge = ViewportTrySplit = false; Active = WasActive = false; WriteAccessed = false; Collapsed = false; @@ -2333,18 +2375,19 @@ ImGuiWindow::ImGuiWindow(ImGuiContext* context, const char* name) AutoFitChildAxises = 0x00; AutoPosLastDirection = ImGuiDir_None; HiddenFramesRegular = HiddenFramesForResize = 0; - SetWindowPosAllowFlags = SetWindowSizeAllowFlags = SetWindowCollapsedAllowFlags = ImGuiCond_Always | ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing; + SetWindowPosAllowFlags = SetWindowSizeAllowFlags = SetWindowCollapsedAllowFlags = SetWindowDockAllowFlags = ImGuiCond_Always | ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing; SetWindowPosVal = SetWindowPosPivot = ImVec2(FLT_MAX, FLT_MAX); LastFrameActive = -1; ItemWidthDefault = 0.0f; - FontWindowScale = 1.0f; + FontWindowScale = FontDpiScale = 1.0f; SettingsIdx = -1; DrawList = &DrawListInst; DrawList->_OwnerName = Name; ParentWindow = NULL; RootWindow = NULL; + RootWindowDockStop = NULL; RootWindowForTitleBarHighlight = NULL; RootWindowForNav = NULL; @@ -2355,6 +2398,12 @@ ImGuiWindow::ImGuiWindow(ImGuiContext* context, const char* name) FocusIdxAllCounter = FocusIdxTabCounter = -1; FocusIdxAllRequestCurrent = FocusIdxTabRequestCurrent = INT_MAX; FocusIdxAllRequestNext = FocusIdxTabRequestNext = INT_MAX; + + DockNode = DockNodeAsHost = NULL; + DockId = 0; + DockTabItemStatusFlags = 0; + DockOrder = -1; + DockIsActive = DockTabIsVisible = DockTabWantClose = false; } ImGuiWindow::~ImGuiWindow() @@ -2535,6 +2584,11 @@ static inline bool IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFla return false; } + // Filter by viewport + if (window->Viewport != g.MouseViewport) + if (g.MovingWindow == NULL || window->RootWindow != g.MovingWindow->RootWindow) + return false; + return true; } @@ -2644,8 +2698,9 @@ bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) if ((window->DC.ItemFlags & ImGuiItemFlags_Disabled) && !(flags & ImGuiHoveredFlags_AllowWhenDisabled)) return false; - // Special handling for the 1st item after Begin() which represent the title bar. When the window is collapsed (SkipItems==true) that last item will never be overwritten so we need to detect tht case. - if (window->DC.LastItemId == window->MoveId && window->WriteAccessed) + // 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. + if ((window->DC.LastItemId == window->ID || window->DC.LastItemId == window->MoveId) && window->WriteAccessed) return false; return true; } @@ -2835,6 +2890,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; +} + ImGuiStyle& ImGui::GetStyle() { IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() or ImGui::SetCurrentContext()?"); @@ -2845,7 +2906,7 @@ ImGuiStyle& ImGui::GetStyle() ImDrawData* ImGui::GetDrawData() { ImGuiContext& g = *GImGui; - return g.DrawData.Valid ? &g.DrawData : NULL; + return g.Viewports[0]->DrawDataP.Valid ? &g.Viewports[0]->DrawDataP : NULL; } double ImGui::GetTime() @@ -2858,9 +2919,32 @@ int ImGui::GetFrameCount() return GImGui->FrameCount; } +ImDrawList* ImGui::GetOverlayDrawList(ImGuiViewportP* viewport) +{ + // Create the draw list on demand, because it is not frequently used for all viewports + ImGuiContext& g = *GImGui; + if (viewport->OverlayDrawList == NULL) + { + viewport->OverlayDrawList = IM_NEW(ImDrawList)(&g.DrawListSharedData); + viewport->OverlayDrawList->_OwnerName = "##Overlay"; + } + + // Our ImDrawList system requires that there is always a command + if (viewport->LastFrameOverlayDrawList != g.FrameCount) + { + viewport->OverlayDrawList->Clear(); + viewport->OverlayDrawList->PushTextureID(g.IO.Fonts->TexID); + viewport->OverlayDrawList->PushClipRect(viewport->Pos, viewport->Pos + viewport->Size, false); + viewport->OverlayDrawList->Flags = (g.Style.AntiAliasedLines ? ImDrawListFlags_AntiAliasedLines : 0) | (g.Style.AntiAliasedFill ? ImDrawListFlags_AntiAliasedFill : 0); + viewport->LastFrameOverlayDrawList = g.FrameCount; + } + return viewport->OverlayDrawList; +} + ImDrawList* ImGui::GetOverlayDrawList() { - return &GImGui->OverlayDrawList; + ImGuiWindow* window = GImGui->CurrentWindow; + return GetOverlayDrawList(window->Viewport); } ImDrawListSharedData* ImGui::GetDrawListSharedData() @@ -2899,11 +2983,24 @@ void ImGui::UpdateMouseMovingWindow() { MarkIniSettingsDirty(moving_window); SetWindowPos(moving_window, pos, ImGuiCond_Always); + if (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; } FocusWindow(g.MovingWindow); } else { + // 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) + 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 (!IsDragDropPayloadBeingAccepted()) + g.MouseViewport = moving_window->Viewport; + + // Clear the NoInput window flag set by the Viewport system + moving_window->Viewport->Flags &= ~ImGuiViewportFlags_NoInputs; + ClearActiveID(); g.MovingWindow = NULL; } @@ -2920,6 +3017,28 @@ void ImGui::UpdateMouseMovingWindow() } } +static void TranslateWindow(ImGuiWindow* window, const ImVec2& delta) +{ + window->Pos += delta; + window->ClipRect.Translate(delta); + window->OuterRectClipped.Translate(delta); + window->InnerMainRect.Translate(delta); + window->DC.CursorPos += delta; + window->DC.CursorStartPos += delta; + window->DC.CursorMaxPos += delta; + window->DC.LastItemRect.Translate(delta); + window->DC.LastItemDisplayRect.Translate(delta); +} + +static void ScaleWindow(ImGuiWindow* window, float scale) +{ + ImVec2 origin = window->Viewport->Pos; + window->Pos = ImFloor((window->Pos - origin) * scale + origin); + window->Size = ImFloor(window->Size * scale); + window->SizeFull = ImFloor(window->SizeFull * scale); + window->SizeContents = ImFloor(window->SizeContents * scale); +} + static bool IsWindowActiveAndVisible(ImGuiWindow* window) { return (window->Active) && (!window->Hidden); @@ -3030,16 +3149,17 @@ 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. FindHoveredWindow(); + IM_ASSERT(g.HoveredWindow == NULL || g.HoveredWindow == g.MovingWindow || g.HoveredWindow->Viewport == g.MouseViewport); // Modal windows prevents cursor from hovering behind them. ImGuiWindow* modal_window = GetFrontMostPopupModal(); if (modal_window) if (g.HoveredRootWindow && !IsWindowChildOf(g.HoveredRootWindow, modal_window)) - g.HoveredRootWindow = g.HoveredWindow = NULL; + g.HoveredWindow = g.HoveredRootWindow = g.HoveredWindowUnderMovingWindow = NULL; // Disabled mouse? if (g.IO.ConfigFlags & ImGuiConfigFlags_NoMouse) - g.HoveredWindow = g.HoveredRootWindow = NULL; + g.HoveredWindow = g.HoveredRootWindow = g.HoveredWindowUnderMovingWindow = NULL; // We track click ownership. When clicked outside of a window the click is owned by the application and won't report hovering nor request capture even while dragging over our windows afterward. int mouse_earliest_button_down = -1; @@ -3059,7 +3179,7 @@ void ImGui::UpdateHoveredWindowAndCaptureFlags() // FIXME: For patterns of drag and drop across OS windows, we may need to rework/remove this test (first committed 311c0ca9 on 2015/02) const bool mouse_dragging_extern_payload = g.DragDropActive && (g.DragDropSourceFlags & ImGuiDragDropFlags_SourceExtern) != 0; if (!mouse_avail_to_imgui && !mouse_dragging_extern_payload) - g.HoveredWindow = g.HoveredRootWindow = NULL; + g.HoveredWindow = g.HoveredRootWindow = g.HoveredWindowUnderMovingWindow = NULL; // Update io.WantCaptureMouse for the user application (true = dispatch mouse info to imgui, false = dispatch mouse info to imgui + app) if (g.WantCaptureMouseNextFrame != -1) @@ -3105,6 +3225,36 @@ void ImGui::NewFrame() if (g.IO.ConfigResizeWindowsFromEdges && !(g.IO.BackendFlags & ImGuiBackendFlags_HasMouseCursors)) g.IO.ConfigResizeWindowsFromEdges = false; + // 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() at the end of the previous frame?"); + 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.Viewports[0]->PlatformUserData != NULL || g.Viewports[0]->PlatformHandle != NULL) && "Platform init didn't setup main viewport."); +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + IM_ASSERT(g.IO.RenderDrawListsFn == NULL); // Call ImGui::Render() then pass ImGui::GetDrawData() yourself to your render function! +#endif + } + else + { + // Disable feature, our back-ends 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 (int monitor_n = 0; monitor_n < g.PlatformIO.Monitors.Size; monitor_n++) + { + ImGuiPlatformMonitor& mon = g.PlatformIO.Monitors[monitor_n]; + IM_ASSERT(mon.MainSize.x > 0.0f && mon.MainSize.y > 0.0f && "Monitor bounds not setup properly."); + IM_ASSERT(mon.WorkSize.x > 0.0f && mon.WorkSize.y > 0.0f && "Monitor bounds not setup properly. If you don't have work area information, just copy Min/Max into them."); + IM_ASSERT(mon.DpiScale != 0.0f); + } + } + // Load settings on first frame (if not explicitly loaded manually before) if (!g.SettingsLoaded) { @@ -3134,20 +3284,26 @@ void ImGui::NewFrame() g.TooltipOverrideCount = 0; g.WindowsActiveCount = 0; - // Setup current font and draw list + UpdateViewports(); + + // Setup current font, and draw list shared data + // FIXME-VIEWPORT: the concept of a single ClipRectFullscreen is not ideal! g.IO.Fonts->Locked = true; SetCurrentFont(GetDefaultFont()); IM_ASSERT(g.Font->IsLoaded()); - g.DrawListSharedData.ClipRectFullscreen = ImVec4(0.0f, 0.0f, g.IO.DisplaySize.x, g.IO.DisplaySize.y); + ImVec2 virtual_space_max(0,0); + for (int n = 0; n < g.Viewports.Size; n++) + virtual_space_max = ImMax(virtual_space_max, g.Viewports[n]->Pos + g.Viewports[n]->Size); + g.DrawListSharedData.ClipRectFullscreen = ImVec4(0.0f, 0.0f, virtual_space_max.x, virtual_space_max.y); g.DrawListSharedData.CurveTessellationTol = g.Style.CurveTessellationTol; - g.OverlayDrawList.Clear(); - g.OverlayDrawList.PushTextureID(g.IO.Fonts->TexID); - g.OverlayDrawList.PushClipRectFullScreen(); - g.OverlayDrawList.Flags = (g.Style.AntiAliasedLines ? ImDrawListFlags_AntiAliasedLines : 0) | (g.Style.AntiAliasedFill ? ImDrawListFlags_AntiAliasedFill : 0); - - // Mark rendering data as invalid to prevent user who may have a handle on it to use it - g.DrawData.Clear(); + // Mark rendering data as invalid to prevent user who may have a handle on it to use it. Setup Overlay draw list for the viewport. + for (int n = 0; n < g.Viewports.Size; n++) + { + ImGuiViewportP* viewport = g.Viewports[n]; + viewport->DrawData = NULL; + viewport->DrawDataP.Clear(); + } // 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) @@ -3198,9 +3354,16 @@ void ImGui::NewFrame() g.FramerateSecPerFrameIdx = (g.FramerateSecPerFrameIdx + 1) % IM_ARRAYSIZE(g.FramerateSecPerFrame); g.IO.Framerate = (g.FramerateSecPerFrameAccum > 0.0f) ? (1.0f / (g.FramerateSecPerFrameAccum / (float)IM_ARRAYSIZE(g.FramerateSecPerFrame))) : FLT_MAX; + // Undocking + // (needs to be before UpdateMouseMovingWindow so the window is already offset and following the mouse on the detaching frame) + DockContextNewFrameUpdateUndocking(&g); + + // Find hovered window + // (needs to be before UpdateMovingWindow so we fill HoveredWindowUnderMovingWindow on the mouse release frame) + UpdateHoveredWindowAndCaptureFlags(); + // Handle user moving window with mouse (at the beginning of the frame to avoid input lag or sheering) UpdateMouseMovingWindow(); - UpdateHoveredWindowAndCaptureFlags(); // Background darkening/whitening if (GetFrontMostPopupModal() != NULL || (g.NavWindowingTarget != NULL && g.NavWindowingHighlightAlpha > 0.0f)) @@ -3211,6 +3374,7 @@ void ImGui::NewFrame() g.MouseCursor = ImGuiMouseCursor_Arrow; g.WantCaptureMouseNextFrame = g.WantCaptureKeyboardNextFrame = g.WantTextInputNextFrame = -1; g.PlatformImePos = ImVec2(1.0f, 1.0f); // OS Input Method Editor showing on top-left of our window by default + g.PlatformImePosViewport = NULL; // Mouse wheel scrolling, scale UpdateMouseWheel(); @@ -3226,17 +3390,19 @@ void ImGui::NewFrame() g.NavIdTabCounter = INT_MAX; // Mark all windows as not visible + IM_ASSERT(g.WindowsFocusOrder.Size == g.Windows.Size); for (int i = 0; i != g.Windows.Size; i++) { ImGuiWindow* window = g.Windows[i]; window->WasActive = window->Active; + window->BeginCount = 0; window->Active = false; window->WriteAccessed = false; } // Closing the focused window restore focus to the first active root window in descending z-order if (g.NavWindow && !g.NavWindow->WasActive) - FocusFrontMostActiveWindowIgnoringOne(NULL); + FocusPreviousWindowIgnoringOne(NULL); // No window should be open at the beginning of the frame. // But in order to allow the user to call NewFrame() multiple times without calling Render(), we are doing an explicit clear. @@ -3244,6 +3410,9 @@ void ImGui::NewFrame() g.CurrentPopupStack.resize(0); ClosePopupsOverWindow(g.NavWindow); + // Docking + DockContextNewFrameUpdateDocking(&g); + // Create implicit window - we will only render it if the user has added something to it. // We don't use "Debug" to avoid colliding with user trying to create a "Debug" window with custom flags. SetNextWindowSize(ImVec2(400,400), ImGuiCond_FirstUseEver); @@ -3262,7 +3431,20 @@ void ImGui::Initialize(ImGuiContext* context) ini_handler.ReadOpenFn = SettingsHandlerWindow_ReadOpen; ini_handler.ReadLineFn = SettingsHandlerWindow_ReadLine; ini_handler.WriteAllFn = SettingsHandlerWindow_WriteAll; - g.SettingsHandlers.push_front(ini_handler); + g.SettingsHandlers.push_back(ini_handler); + + // Create default viewport + ImGuiViewportP* viewport = IM_NEW(ImGuiViewportP)(); + viewport->ID = IMGUI_VIEWPORT_DEFAULT_ID; + viewport->Idx = 0; + viewport->CreatedPlatformWindow = true; // Set this flag so DestroyPlatformWindows() gives a chance for backend to receive DestroyWindow calls for the main viewport. + g.Viewports.push_back(viewport); + g.PlatformIO.MainViewport = g.Viewports[0]; // Make it accessible in public-facing GetPlatformIO() immediately (before the first call to EndFrame) + g.PlatformIO.Viewports.push_back(g.Viewports[0]); + + // Extensions + IM_ASSERT(g.DockContext == NULL); + DockContextInitialize(&g); g.Initialized = true; } @@ -3282,19 +3464,34 @@ void ImGui::Shutdown(ImGuiContext* context) // Save settings (unless we haven't attempted to load them: CreateContext/DestroyContext without a call to NewFrame shouldn't save an empty file) if (g.SettingsLoaded && g.IO.IniFilename != NULL) + { + ImGuiContext* backup_context = GImGui; + SetCurrentContext(context); SaveIniSettingsToDisk(g.IO.IniFilename); + SetCurrentContext(backup_context); + } + + // Destroy platform windows + ImGuiContext* backup_context = ImGui::GetCurrentContext(); + SetCurrentContext(context); + DestroyPlatformWindows(); + SetCurrentContext(backup_context); + + // Shutdown extensions + IM_ASSERT(g.DockContext != NULL); + DockContextShutdown(&g); // Clear everything else for (int i = 0; i < g.Windows.Size; i++) IM_DELETE(g.Windows[i]); g.Windows.clear(); + g.WindowsFocusOrder.clear(); g.WindowsSortBuffer.clear(); g.CurrentWindow = NULL; g.CurrentWindowStack.clear(); g.WindowsById.Clear(); g.NavWindow = NULL; - g.HoveredWindow = NULL; - g.HoveredRootWindow = NULL; + g.HoveredWindow = g.HoveredRootWindow = g.HoveredWindowUnderMovingWindow = NULL; g.ActiveIdWindow = g.ActiveIdPreviousFrameWindow = NULL; g.MovingWindow = NULL; g.ColorModifiers.clear(); @@ -3302,8 +3499,10 @@ void ImGui::Shutdown(ImGuiContext* context) g.FontStack.clear(); g.OpenPopupStack.clear(); g.CurrentPopupStack.clear(); - g.DrawDataBuilder.ClearFreeMemory(); - g.OverlayDrawList.ClearFreeMemory(); + g.CurrentViewport = g.MouseViewport = g.MouseLastHoveredViewport = NULL; + for (int i = 0; i < g.Viewports.Size; i++) + IM_DELETE(g.Viewports[i]); + g.Viewports.clear(); g.PrivateClipboard.clear(); g.InputTextState.TextW.clear(); g.InputTextState.InitialText.clear(); @@ -3336,7 +3535,7 @@ static int IMGUI_CDECL ChildWindowComparer(const void* lhs, const void* rhs) return (a->BeginOrderWithinParent - b->BeginOrderWithinParent); } -static void AddWindowToSortedBuffer(ImVector* out_sorted_windows, ImGuiWindow* window) +static void AddWindowToSortBuffer(ImVector* out_sorted_windows, ImGuiWindow* window) { out_sorted_windows->push_back(window); if (window->Active) @@ -3348,7 +3547,7 @@ static void AddWindowToSortedBuffer(ImVector* out_sorted_windows, { ImGuiWindow* child = window->DC.ChildWindows[i]; if (child->Active) - AddWindowToSortedBuffer(out_sorted_windows, child); + AddWindowToSortBuffer(out_sorted_windows, child); } } } @@ -3386,26 +3585,24 @@ static void AddDrawListToDrawData(ImVector* out_list, ImDrawList* d out_list->push_back(draw_list); } -static void AddWindowToDrawData(ImVector* out_render_list, ImGuiWindow* window) +static void AddWindowToDrawData(ImGuiWindow* window, int layer) { ImGuiContext& g = *GImGui; g.IO.MetricsRenderWindows++; - AddDrawListToDrawData(out_render_list, window->DrawList); + AddDrawListToDrawData(&window->Viewport->DrawDataBuilder.Layers[layer], window->DrawList); for (int i = 0; i < window->DC.ChildWindows.Size; i++) { ImGuiWindow* child = window->DC.ChildWindows[i]; - if (IsWindowActiveAndVisible(child)) // clipped children may have been marked not active - AddWindowToDrawData(out_render_list, child); + if (IsWindowActiveAndVisible(child)) // Clipped children may have been marked not active + AddWindowToDrawData(child, layer); } } -static void AddWindowToDrawDataSelectLayer(ImGuiWindow* window) +// Layer is locked for the root window, however child windows may use a different viewport (e.g. extruding menu) +static void AddRootWindowToDrawData(ImGuiWindow* window) { - ImGuiContext& g = *GImGui; - if (window->Flags & ImGuiWindowFlags_Tooltip) - AddWindowToDrawData(&g.DrawDataBuilder.Layers[1], window); - else - AddWindowToDrawData(&g.DrawDataBuilder.Layers[0], window); + int layer = (window->Flags & ImGuiWindowFlags_Tooltip) ? 1 : 0; + AddWindowToDrawData(window, layer); } void ImDrawDataBuilder::FlattenIntoSingleLayer() @@ -3426,15 +3623,16 @@ void ImDrawDataBuilder::FlattenIntoSingleLayer() } } -static void SetupDrawData(ImVector* draw_lists, ImDrawData* draw_data) +static void SetupViewportDrawData(ImGuiViewportP* viewport, ImVector* draw_lists) { - ImGuiIO& io = ImGui::GetIO(); + ImDrawData* draw_data = &viewport->DrawDataP; + viewport->DrawData = draw_data; // Make publicly accessible draw_data->Valid = true; draw_data->CmdLists = (draw_lists->Size > 0) ? draw_lists->Data : NULL; draw_data->CmdListsCount = draw_lists->Size; draw_data->TotalVtxCount = draw_data->TotalIdxCount = 0; - draw_data->DisplayPos = ImVec2(0.0f, 0.0f); - draw_data->DisplaySize = io.DisplaySize; + draw_data->DisplayPos = viewport->Pos; + draw_data->DisplaySize = viewport->Size; for (int n = 0; n < draw_lists->Size; n++) { draw_data->TotalVtxCount += draw_lists->Data[n]->VtxBuffer.Size; @@ -3457,6 +3655,14 @@ void ImGui::PopClipRect() window->ClipRect = window->DrawList->_ClipRectStack.back(); } +static ImGuiWindow* FindFromMostVisibleChildWindow(ImGuiWindow* window) +{ + for (int n = window->DC.ChildWindows.Size - 1; n >= 0; n--) + if (IsWindowActiveAndVisible(window->DC.ChildWindows[n])) + return FindFromMostVisibleChildWindow(window->DC.ChildWindows[n]); + return window; +} + // 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. void ImGui::EndFrame() { @@ -3467,10 +3673,11 @@ void ImGui::EndFrame() IM_ASSERT(g.FrameScopeActive && "Forgot to call ImGui::NewFrame()"); // Notify OS when our Input Method Editor cursor has moved (e.g. CJK inputs using Microsoft IME) - if (g.IO.ImeSetInputScreenPosFn && ImLengthSqr(g.PlatformImeLastPos - g.PlatformImePos) > 0.0001f) + if (g.PlatformIO.Platform_SetImeInputPos && g.PlatformImePosViewport != NULL && ImLengthSqr(g.PlatformImePos - g.PlatformImeLastPos) > 0.0001f) { - g.IO.ImeSetInputScreenPosFn((int)g.PlatformImePos.x, (int)g.PlatformImePos.y); + g.PlatformIO.Platform_SetImeInputPos(g.PlatformImePosViewport, g.PlatformImePos); g.PlatformImeLastPos = g.PlatformImePos; + g.PlatformImePosViewport = NULL; } // Hide implicit "Debug" window if it hasn't been used @@ -3479,10 +3686,60 @@ void ImGui::EndFrame() g.CurrentWindow->Active = false; End(); + // Docking + DockContextEndFrame(&g); + + // Draw modal whitening background on _other_ viewports than the one the modal or target are on + ImGuiWindow* modal_window = GetFrontMostPopupModal(); + const bool dim_bg_for_modal = (modal_window != NULL); + const bool dim_bg_for_window_list = (g.NavWindowingTargetAnim != NULL); + if (dim_bg_for_modal || dim_bg_for_window_list) + for (int viewport_n = 0; viewport_n < g.Viewports.Size; viewport_n++) + { + ImGuiViewportP* viewport = g.Viewports[viewport_n]; + if (modal_window && viewport == modal_window->Viewport) + continue; + if (g.NavWindowingList && viewport == g.NavWindowingList->Viewport) + continue; + if (g.NavWindowingTargetAnim && viewport == g.NavWindowingTargetAnim->Viewport) + continue; + ImDrawList* draw_list = GetOverlayDrawList(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); + } + + // CTRL-TAB + if (dim_bg_for_window_list) + { + // Choose a draw list that will be front-most across all our children + ImGuiWindow* window = g.NavWindowingTargetAnim; + ImDrawList* draw_list = FindFromMostVisibleChildWindow(window->RootWindow)->DrawList; + draw_list->PushClipRectFullScreen(); + + // Docking: draw modal whitening background on other nodes of a same dock tree + if (window->RootWindowDockStop->DockIsActive) + if (window->RootWindow != window->RootWindowDockStop) + RenderRectFilledWithHole(draw_list, window->RootWindow->Rect(), window->RootWindowDockStop->Rect(), GetColorU32(ImGuiCol_NavWindowingDimBg, g.DimBgRatio), g.Style.WindowRounding); + + // Draw navigation selection/windowing rectangle border + float rounding = ImMax(window->WindowRounding, g.Style.WindowRounding); + ImRect bb = window->Rect(); + bb.Expand(g.FontSize); + if (bb.Contains(window->Viewport->GetRect())) // If a window fits the entire viewport, adjust its highlight inward + { + bb.Expand(-g.FontSize - 1.0f); + rounding = window->WindowRounding; + } + draw_list->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha), rounding, ~0, 3.0f); + draw_list->PopClipRect(); + } + // Show CTRL+TAB list if (g.NavWindowingTarget) NavUpdateWindowingList(); + SetCurrentViewport(NULL, NULL); + // Drag and Drop: Elapse payload (if delivered, or if source stops being submitted) if (g.DragDropActive) { @@ -3537,6 +3794,21 @@ void ImGui::EndFrame() } } + // Update user-facing viewport list + g.PlatformIO.MainViewport = g.Viewports[0]; + g.PlatformIO.Viewports.resize(0); + for (int i = 0; i < g.Viewports.Size; i++) + { + ImGuiViewportP* viewport = g.Viewports[i]; + if (viewport->LastFrameActive < g.FrameCount || viewport->Size.x <= 0.0f || viewport->Size.y <= 0.0f) + continue; + if (viewport->Window && !IsWindowActiveAndVisible(viewport->Window)) + continue; + if (i > 0) + IM_ASSERT(viewport->Window != NULL); + g.PlatformIO.Viewports.push_back(viewport); + } + // Sort the window list so that all child windows are after their parent // We cannot do that on FocusWindow() because childs may not exist yet g.WindowsSortBuffer.resize(0); @@ -3546,10 +3818,11 @@ void ImGui::EndFrame() ImGuiWindow* window = g.Windows[i]; if (window->Active && (window->Flags & ImGuiWindowFlags_ChildWindow)) // if a child is active its parent will add it continue; - AddWindowToSortedBuffer(&g.WindowsSortBuffer, window); + AddWindowToSortBuffer(&g.WindowsSortBuffer, window); } - IM_ASSERT(g.Windows.Size == g.WindowsSortBuffer.Size); // we done something wrong + // This usually assert if there is a mismatch between the ImGuiWindowFlags_ChildWindow / ParentWindow values and DC.ChildWindows[] in parents, aka we've done something wrong. + IM_ASSERT(g.Windows.Size == g.WindowsSortBuffer.Size); g.Windows.swap(g.WindowsSortBuffer); g.IO.MetricsActiveWindows = g.WindowsActiveCount; @@ -3576,7 +3849,8 @@ void ImGui::Render() // Gather ImDrawList to render (for each active window) g.IO.MetricsRenderVertices = g.IO.MetricsRenderIndices = g.IO.MetricsRenderWindows = 0; - g.DrawDataBuilder.Clear(); + for (int n = 0; n != g.Viewports.Size; n++) + g.Viewports[n]->DrawDataBuilder.Clear(); ImGuiWindow* windows_to_render_front_most[2]; windows_to_render_front_most[0] = (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus)) ? g.NavWindowingTarget->RootWindow : NULL; windows_to_render_front_most[1] = g.NavWindowingTarget ? g.NavWindowingList : NULL; @@ -3584,29 +3858,33 @@ void ImGui::Render() { ImGuiWindow* window = g.Windows[n]; if (IsWindowActiveAndVisible(window) && (window->Flags & ImGuiWindowFlags_ChildWindow) == 0 && window != windows_to_render_front_most[0] && window != windows_to_render_front_most[1]) - AddWindowToDrawDataSelectLayer(window); + AddRootWindowToDrawData(window); } for (int n = 0; n < IM_ARRAYSIZE(windows_to_render_front_most); n++) if (windows_to_render_front_most[n] && IsWindowActiveAndVisible(windows_to_render_front_most[n])) // NavWindowingTarget is always temporarily displayed as the front-most window - AddWindowToDrawDataSelectLayer(windows_to_render_front_most[n]); - g.DrawDataBuilder.FlattenIntoSingleLayer(); + AddRootWindowToDrawData(windows_to_render_front_most[n]); // Draw software mouse cursor if requested if (g.IO.MouseDrawCursor) - RenderMouseCursor(&g.OverlayDrawList, g.IO.MousePos, g.Style.MouseCursorScale, g.MouseCursor); + RenderMouseCursor(g.IO.MousePos, g.Style.MouseCursorScale, g.MouseCursor); - if (!g.OverlayDrawList.VtxBuffer.empty()) - AddDrawListToDrawData(&g.DrawDataBuilder.Layers[0], &g.OverlayDrawList); - - // Setup ImDrawData structure for end-user - SetupDrawData(&g.DrawDataBuilder.Layers[0], &g.DrawData); - g.IO.MetricsRenderVertices = g.DrawData.TotalVtxCount; - g.IO.MetricsRenderIndices = g.DrawData.TotalIdxCount; + // Setup ImDrawData structures for end-user + g.IO.MetricsRenderVertices = g.IO.MetricsRenderIndices = 0; + for (int n = 0; n < g.Viewports.Size; n++) + { + ImGuiViewportP* viewport = g.Viewports[n]; + viewport->DrawDataBuilder.FlattenIntoSingleLayer(); + if (viewport->OverlayDrawList != NULL) + AddDrawListToDrawData(&viewport->DrawDataBuilder.Layers[0], GetOverlayDrawList(viewport)); + SetupViewportDrawData(viewport, &viewport->DrawDataBuilder.Layers[0]); + g.IO.MetricsRenderVertices += viewport->DrawData->TotalVtxCount; + g.IO.MetricsRenderIndices += viewport->DrawData->TotalIdxCount; + } // Render. If user hasn't set a callback then they may retrieve the draw data via GetDrawData() #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - if (g.DrawData.CmdListsCount > 0 && g.IO.RenderDrawListsFn != NULL) - g.IO.RenderDrawListsFn(&g.DrawData); + if (g.Viewports[0]->DrawData->CmdListsCount > 0 && g.IO.RenderDrawListsFn != NULL) + g.IO.RenderDrawListsFn(g.Viewports[0]->DrawData); #endif } @@ -3687,17 +3965,26 @@ static void FindHoveredWindow() { ImGuiContext& g = *GImGui; + // Special handling for the window being moved: Ignore the mouse viewport check (because it may reset/lose its viewport during the undocking frame) + ImGuiViewportP* moving_window_viewport = g.MovingWindow ? g.MovingWindow->Viewport : NULL; + if (g.MovingWindow) + g.MovingWindow->Viewport = g.MouseViewport; + ImGuiWindow* hovered_window = NULL; + ImGuiWindow* hovered_window_ignoring_moving_window = NULL; if (g.MovingWindow && !(g.MovingWindow->Flags & ImGuiWindowFlags_NoInputs)) hovered_window = g.MovingWindow; - for (int i = g.Windows.Size - 1; i >= 0 && hovered_window == NULL; i--) + for (int i = g.Windows.Size - 1; i >= 0; i--) { ImGuiWindow* window = g.Windows[i]; if (!window->Active || window->Hidden) continue; if (window->Flags & ImGuiWindowFlags_NoInputs) 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) ImRect bb(window->OuterRectClipped.Min - g.Style.TouchExtraPadding, window->OuterRectClipped.Max + g.Style.TouchExtraPadding); @@ -3705,14 +3992,19 @@ static void FindHoveredWindow() { if (hovered_window == NULL) hovered_window = window; - if (hovered_window) + 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; } } g.HoveredWindow = hovered_window; g.HoveredRootWindow = g.HoveredWindow ? g.HoveredWindow->RootWindow : NULL; + g.HoveredWindowUnderMovingWindow = hovered_window_ignoring_moving_window; + if (g.MovingWindow) + g.MovingWindow->Viewport = moving_window_viewport; } // Test if mouse cursor is hovering given rectangle @@ -3731,6 +4023,8 @@ bool ImGui::IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool c const ImRect rect_for_touch(rect_clipped.Min - g.Style.TouchExtraPadding, rect_clipped.Max + g.Style.TouchExtraPadding); if (!rect_for_touch.Contains(g.IO.MousePos)) return false; + if (!g.MouseViewport->GetRect().Overlaps(rect_clipped)) + return false; return true; } @@ -3938,7 +4232,17 @@ bool ImGui::IsItemDeactivatedAfterEdit() bool ImGui::IsItemFocused() { ImGuiContext& g = *GImGui; - return g.NavId && !g.NavDisableHighlight && g.NavId == g.CurrentWindow->DC.LastItemId; + ImGuiWindow* window = g.CurrentWindow; + + if (g.NavId == 0 || g.NavDisableHighlight || g.NavId != window->DC.LastItemId) + 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. + if (window->DC.LastItemId == window->ID && window->WriteAccessed) + return false; + + return true; } bool ImGui::IsItemClicked(int mouse_button) @@ -4004,20 +4308,12 @@ ImVec2 ImGui::GetItemRectSize() return window->DC.LastItemRect.GetSize(); } -static ImRect GetViewportRect() -{ - ImGuiContext& g = *GImGui; - if (g.IO.DisplayVisibleMin.x != g.IO.DisplayVisibleMax.x && g.IO.DisplayVisibleMin.y != g.IO.DisplayVisibleMax.y) - return ImRect(g.IO.DisplayVisibleMin, g.IO.DisplayVisibleMax); - return ImRect(0.0f, 0.0f, g.IO.DisplaySize.x, g.IO.DisplaySize.y); -} - static bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags flags) { ImGuiContext& g = *GImGui; ImGuiWindow* parent_window = g.CurrentWindow; - flags |= ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_ChildWindow; + flags |= ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_ChildWindow|ImGuiWindowFlags_NoDocking; flags |= (parent_window->Flags & ImGuiWindowFlags_NoMove); // Inherit the NoMove flag // Size @@ -4030,10 +4326,10 @@ static bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size size.y = ImMax(content_avail.y + size.y, 4.0f); SetNextWindowSize(size); - // Name + // Build up name. If you need to append to a same child from multiple location in the ID stack, use BeginChild(ImGuiID id) with a stable value. char title[256]; if (name) - ImFormatString(title, IM_ARRAYSIZE(title), "%s/%s", parent_window->Name, name); + ImFormatString(title, IM_ARRAYSIZE(title), "%s/%s_%08X", parent_window->Name, name, id); else ImFormatString(title, IM_ARRAYSIZE(title), "%s/%08X", parent_window->Name, id); @@ -4150,6 +4446,13 @@ 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) +{ + ImGuiContext& g = *GImGui; + return (ImGuiWindow*)g.WindowsById.GetVoidPtr(id); } ImGuiWindow* ImGui::FindWindowByName(const char* name) @@ -4169,7 +4472,8 @@ static ImGuiWindow* CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFl g.WindowsById.SetVoidPtr(window->ID, window); // Default/arbitrary window position. Use SetNextWindowPos() with the appropriate condition flag to change the initial position of a window. - window->Pos = ImVec2(60, 60); + ImGuiViewport* main_viewport = ImGui::GetMainViewport(); + window->Pos = main_viewport->Pos + ImVec2(60, 60); // User can disable loading and saving of settings. Tooltip and child windows also don't store settings. if (!(flags & ImGuiWindowFlags_NoSavedSettings)) @@ -4178,12 +4482,23 @@ static ImGuiWindow* CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFl // Retrieve settings from .ini file window->SettingsIdx = g.SettingsWindows.index_from_pointer(settings); SetWindowConditionAllowFlags(window, ImGuiCond_FirstUseEver, false); - window->Pos = ImFloor(settings->Pos); + if (settings->ViewportId) + { + window->ViewportId = settings->ViewportId; + window->ViewportPos = settings->ViewportPos; + } + else + { + window->ViewportPos = main_viewport->Pos; + } + window->Pos = ImFloor(settings->Pos + window->ViewportPos); window->Collapsed = settings->Collapsed; if (ImLengthSqr(settings->Size) > 0.00001f) size = ImFloor(settings->Size); + window->DockId = settings->DockId; + window->DockOrder = settings->DockOrder; } - window->Size = window->SizeFull = window->SizeFullAtLastBegin = size; + window->Size = window->SizeFull = window->SizeFullAtLastBegin = ImFloor(size); window->DC.CursorMaxPos = window->Pos; // So first call to CalcSizeContents() doesn't return crazy values if ((flags & ImGuiWindowFlags_AlwaysAutoResize) != 0) @@ -4200,13 +4515,24 @@ static ImGuiWindow* CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFl window->AutoFitOnlyGrows = (window->AutoFitFramesX > 0) || (window->AutoFitFramesY > 0); } + g.WindowsFocusOrder.push_back(window); if (flags & ImGuiWindowFlags_NoBringToFrontOnFocus) - g.Windows.insert(g.Windows.begin(), window); // Quite slow but rare and only once + g.Windows.push_front(window); // Quite slow but rare and only once else g.Windows.push_back(window); 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 ImVec2 CalcSizeAfterConstraint(ImGuiWindow* window, ImVec2 new_size) { ImGuiContext& g = *GImGui; @@ -4231,14 +4557,18 @@ static ImVec2 CalcSizeAfterConstraint(ImGuiWindow* window, ImVec2 new_size) // Minimum size if (!(window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_AlwaysAutoResize))) { + ImGuiWindow* window_for_height = GetWindowForTitleAndMenuHeight(window); new_size = ImMax(new_size, g.Style.WindowMinSize); - new_size.y = ImMax(new_size.y, window->TitleBarHeight() + window->MenuBarHeight() + ImMax(0.0f, g.Style.WindowRounding - 1.0f)); // Reduce artifacts with very small windows + new_size.y = ImMax(new_size.y, window_for_height->TitleBarHeight() + window_for_height->MenuBarHeight() + ImMax(0.0f, g.Style.WindowRounding - 1.0f)); // Reduce artifacts with very small windows } return new_size; } static ImVec2 CalcSizeContents(ImGuiWindow* window) { + if (window->Hidden && window->HiddenFramesForResize == 0 && window->HiddenFramesRegular > 0) + return window->SizeContents; + ImVec2 sz; sz.x = (float)(int)((window->SizeContentsExplicit.x != 0.0f) ? window->SizeContentsExplicit.x : (window->DC.CursorMaxPos.x - window->Pos.x + window->Scroll.x)); sz.y = (float)(int)((window->SizeContentsExplicit.y != 0.0f) ? window->SizeContentsExplicit.y : (window->DC.CursorMaxPos.y - window->Pos.y + window->Scroll.y)); @@ -4256,13 +4586,23 @@ static ImVec2 CalcSizeAutoFit(ImGuiWindow* window, const ImVec2& size_contents) } else { - // 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 DisplaySize-WindowPadding. + // Maximum window size is determined by the viewport size or monitor size const bool is_popup = (window->Flags & ImGuiWindowFlags_Popup) != 0; const bool is_menu = (window->Flags & ImGuiWindowFlags_ChildMenu) != 0; ImVec2 size_min = style.WindowMinSize; if (is_popup || is_menu) // Popups and menus bypass style.WindowMinSize by default, but we give then a non-zero minimum size to facilitate understanding problematic cases (e.g. empty popups) size_min = ImMin(size_min, ImVec2(4.0f, 4.0f)); - ImVec2 size_auto_fit = ImClamp(size_contents, size_min, ImMax(size_min, g.IO.DisplaySize - style.DisplaySafeAreaPadding * 2.0f)); + + ImVec2 avail_size = window->Viewport->Size; + if (window->ViewportOwned) + avail_size = ImVec2(FLT_MAX, FLT_MAX); + const int monitor_idx = window->ViewportAllowPlatformMonitorExtend; + if (monitor_idx >= 0 && monitor_idx < g.PlatformIO.Monitors.Size) + avail_size = g.PlatformIO.Monitors[monitor_idx].WorkSize; + ImVec2 size_auto_fit = ImClamp(size_contents, size_min, ImMax(size_min, avail_size - g.Style.DisplaySafeAreaPadding * 2.0f)); + + // 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 = CalcSizeAfterConstraint(window, size_auto_fit); if (size_auto_fit_after_constraint.x < size_contents.x && !(window->Flags & ImGuiWindowFlags_NoScrollbar) && (window->Flags & ImGuiWindowFlags_HorizontalScrollbar)) size_auto_fit.y += style.ScrollbarSize; @@ -4460,9 +4800,13 @@ static void ImGui::UpdateManualResize(ImGuiWindow* window, const ImVec2& size_au } // Apply back modified position/size to window - if (size_target.x != FLT_MAX) + if (size_target.x != FLT_MAX && (size_target.x != window->SizeFull.x || size_target.y != window->SizeFull.y)) { window->SizeFull = size_target; + if (window->ViewportOwned) + window->ViewportTryMerge = true; + else + window->ViewportTrySplit = true; MarkIniSettingsDirty(window); } if (pos_target.x != FLT_MAX) @@ -4474,12 +4818,20 @@ static void ImGui::UpdateManualResize(ImGuiWindow* window, const ImVec2& size_au window->Size = window->SizeFull; } +static inline void ClampWindowRect(ImGuiWindow* window, const ImRect& rect, const ImVec2& padding) +{ + window->Pos = ImMin(rect.Max - padding, ImMax(window->Pos + window->Size, rect.Min + padding) - window->Size); +} + void ImGui::UpdateWindowParentAndRootLinks(ImGuiWindow* window, ImGuiWindowFlags flags, ImGuiWindow* parent_window) { window->ParentWindow = parent_window; - window->RootWindow = window->RootWindowForTitleBarHighlight = window->RootWindowForNav = window; + window->RootWindow = window->RootWindowDockStop = window->RootWindowForTitleBarHighlight = window->RootWindowForNav = window; if (parent_window && (flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Tooltip)) window->RootWindow = parent_window->RootWindow; + if (parent_window && (flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Tooltip)) + if (!window->DockIsActive && !(parent_window->Flags & ImGuiWindowFlags_DockNodeHost)) + window->RootWindowDockStop = parent_window->RootWindowDockStop; if (parent_window && !(flags & ImGuiWindowFlags_Modal) && (flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup))) window->RootWindowForTitleBarHighlight = parent_window->RootWindowForTitleBarHighlight; while (window->RootWindowForNav->Flags & ImGuiWindowFlags_NavFlattened) @@ -4510,6 +4862,15 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window = CreateNewWindow(name, size_on_first_use, flags); } + // Update stored window name when it changes (which can only happen with the "###" operator). + // Only if it is meant to be displayed to the end user in a different place than the title bar (which already always display the 'name' parameter) + bool window_title_visible_elsewhere = (window->Viewport && window->Viewport->Window == window) || (window->DockIsActive); + if (!window_just_created && window_title_visible_elsewhere && strcmp(name, window->Name) != 0) + { + IM_DELETE(window->Name); + window->Name = ImStrdup(name); + } + // Automatically disable manual moving/resizing when NoInputs is set if (flags & ImGuiWindowFlags_NoInputs) flags |= ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize; @@ -4519,16 +4880,6 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) const int current_frame = g.FrameCount; const bool first_begin_of_the_frame = (window->LastFrameActive != current_frame); - if (first_begin_of_the_frame) - window->Flags = (ImGuiWindowFlags)flags; - else - flags = window->Flags; - - // 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(); - 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)); - window->HasCloseButton = (p_open != NULL); // 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 @@ -4543,9 +4894,40 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) if (window->Appearing) SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, true); + // Update Flags, LastFrameActive, BeginOrderXXX fields + if (first_begin_of_the_frame) + { + window->FlagsPreviousFrame = window->Flags; + window->Flags = (ImGuiWindowFlags)flags; + window->LastFrameActive = current_frame; + window->BeginOrderWithinParent = 0; + window->BeginOrderWithinContext = g.WindowsActiveCount++; + } + else + { + 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.DockCond) + SetWindowDock(window, g.NextWindowData.DockId, g.NextWindowData.DockCond); + if (first_begin_of_the_frame && (window->DockId != 0 || window->DockNode != NULL)) + { + BeginDocked(window, p_open); + flags = window->Flags; + } + + // 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 = window->DockIsActive ? window->DockNode->HostWindow : g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back(); + 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)); + // Add to stack + // We intentionally set g.CurrentWindow to NULL to prevent usage until when the viewport is set, then will call SetCurrentWindow() g.CurrentWindowStack.push_back(window); - SetCurrentWindow(window); + g.CurrentWindow = NULL; CheckStacksSize(window, true); if (flags & ImGuiWindowFlags_Popup) { @@ -4594,6 +4976,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) { window->SizeContentsExplicit = ImVec2(0.0f, 0.0f); } + window->DockFamily = g.NextWindowData.DockFamily; if (g.NextWindowData.CollapsedCond) SetWindowCollapsed(window, g.NextWindowData.CollapsedVal, g.NextWindowData.CollapsedCond); if (g.NextWindowData.FocusCond) @@ -4609,11 +4992,8 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) UpdateWindowParentAndRootLinks(window, flags, parent_window); window->Active = true; - window->BeginOrderWithinParent = 0; - window->BeginOrderWithinContext = g.WindowsActiveCount++; - window->BeginCount = 0; + window->HasCloseButton = (p_open != NULL); window->ClipRect = ImVec4(-FLT_MAX,-FLT_MAX,+FLT_MAX,+FLT_MAX); - window->LastFrameActive = current_frame; window->IDStack.resize(1); // UPDATE CONTENTS SIZE, UPDATE HIDDEN STATUS @@ -4644,19 +5024,32 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) } } + // SELECT VIEWPORT + // We need to do this before using any style/font sizes, as viewport with a different DPI may affect font sizes. + + UpdateSelectWindowViewport(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) - window->WindowBorderSize = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildBorderSize : ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupBorderSize : style.WindowBorderSize; - window->WindowPadding = style.WindowPadding; - if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & (ImGuiWindowFlags_AlwaysUseWindowPadding | ImGuiWindowFlags_Popup)) && window->WindowBorderSize == 0.0f) + if (window->DockIsActive) + window->WindowBorderSize = 0.0f; + else if (flags & ImGuiWindowFlags_ChildWindow) + window->WindowBorderSize = style.ChildBorderSize; + else + window->WindowBorderSize = ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupBorderSize : style.WindowBorderSize; + if (!window->DockIsActive && (flags & ImGuiWindowFlags_ChildWindow) && !(flags & (ImGuiWindowFlags_AlwaysUseWindowPadding | ImGuiWindowFlags_Popup)) && window->WindowBorderSize == 0.0f) window->WindowPadding = ImVec2(0.0f, (flags & ImGuiWindowFlags_MenuBar) ? style.WindowPadding.y : 0.0f); + else + window->WindowPadding = style.WindowPadding; window->DC.MenuBarOffset.x = ImMax(ImMax(window->WindowPadding.x, style.ItemSpacing.x), g.NextWindowData.MenuBarOffsetMinVal.x); window->DC.MenuBarOffset.y = g.NextWindowData.MenuBarOffsetMinVal.y; // 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(); @@ -4700,6 +5093,9 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) MarkIniSettingsDirty(window); } + //if (window->DockNode && window->DockIsActive) + // size_full_modified = window->SizeFull; + // Apply minimum/maximum window size constraints and final size window->SizeFull = CalcSizeAfterConstraint(window, window->SizeFull); window->Size = window->Collapsed && !(flags & ImGuiWindowFlags_ChildWindow) ? window->TitleBarRect().GetSize() : window->SizeFull; @@ -4748,20 +5144,71 @@ 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); - // Clamp position so it stays visible - if (!(flags & ImGuiWindowFlags_ChildWindow)) + if (window->ViewportAllowPlatformMonitorExtend >= 0 && !window->ViewportOwned) + { + if (!window->Viewport->GetRect().Contains(window->Rect())) + { + // Late create viewport, based on the assumption that with our calculations, the DPI will be known ahead (same as the DPI of the selection done in UpdateSelectWindowViewport) + //ImGuiViewport* old_viewport = window->Viewport; + ImGuiViewportFlags viewport_flags = ImGuiViewportFlags_NoDecoration | ImGuiViewportFlags_NoFocusOnAppearing | ((window->Flags & ImGuiWindowFlags_NoInputs) ? ImGuiViewportFlags_NoInputs : 0); + window->Viewport = AddUpdateViewport(window, window->ID, window->Pos, window->Size, viewport_flags); + + // 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); + } + } + + // Synchronize viewport --> window in case the platform window has been moved or resized from the OS/WM + if (window->ViewportOwned) + { + if (window->Viewport->PlatformRequestMove) + { + window->Pos = window->Viewport->Pos; + window->ViewportTryMerge = true; + } + if (window->Viewport->PlatformRequestResize) + { + window->Size = window->SizeFull = window->Viewport->Size; + window->ViewportTryMerge = true; + } + + // We also tell the back-end that clearing the platform window won't be necessary, as our window is filling the viewport and we have disabled BgAlpha + window->Viewport->Flags |= ImGuiViewportFlags_NoRendererClear; + } + + // Clamp position 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. + ImRect viewport_rect = window->Viewport->GetRect(); + if (!window_pos_set_by_api && !(flags & ImGuiWindowFlags_ChildWindow) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0) + { + ImVec2 clamp_padding = ImMax(style.DisplayWindowPadding, style.DisplaySafeAreaPadding); + if (!window->ViewportOwned && viewport_rect.GetWidth() > 0 && viewport_rect.GetHeight() > 0.0f) + ClampWindowRect(window, viewport_rect, clamp_padding); + else if (window->ViewportOwned && g.PlatformIO.Monitors.Size > 0) + { + IM_ASSERT(window->Viewport->PlatformMonitor != INT_MIN); + if (window->Viewport->PlatformMonitor == -1) { - if (!window_pos_set_by_api && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0 && g.IO.DisplaySize.x > 0.0f && g.IO.DisplaySize.y > 0.0f) // Ignore zero-sized display explicitly to avoid losing positions if a window manager reports zero-sized window when initializing or minimizing. + // Fallback for "lost" window (e.g. a monitor disconnected): we move the window back over the main viewport + SetWindowPos(window, g.Viewports[0]->Pos + style.DisplayWindowPadding, ImGuiCond_Always); + window->ViewportTryMerge = true; + } + else { - ImVec2 padding = ImMax(style.DisplayWindowPadding, style.DisplaySafeAreaPadding); - window->Pos = ImMax(window->Pos + window->Size, padding) - window->Size; - window->Pos = ImMin(window->Pos, g.IO.DisplaySize - padding); + ImGuiPlatformMonitor& monitor = g.PlatformIO.Monitors[window->Viewport->PlatformMonitor]; + ClampWindowRect(window, ImRect(monitor.WorkPos, monitor.WorkPos + monitor.WorkSize), clamp_padding); + } } } window->Pos = ImFloor(window->Pos); // Lock window rounding for the frame (so that altering them doesn't cause inconsistencies) window->WindowRounding = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildRounding : ((flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupRounding : style.WindowRounding; + if (window->ViewportOwned) + window->WindowRounding = 0.0f; // Prepare for item focus requests window->FocusIdxAllRequestCurrent = (window->FocusIdxAllRequestNext == INT_MAX || window->FocusIdxAllCounter == -1) ? INT_MAX : (window->FocusIdxAllRequestNext + (window->FocusIdxAllCounter+1)) % (window->FocusIdxAllCounter+1); @@ -4776,8 +5223,12 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Apply window focus (new and reactivated windows are moved to front) bool want_focus = false; if (window_just_activated_by_user && !(flags & ImGuiWindowFlags_NoFocusOnAppearing)) - if (!(flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Tooltip)) || (flags & ImGuiWindowFlags_Popup)) + { + if (flags & ImGuiWindowFlags_Popup) want_focus = true; + else if ((window->DockIsActive || !(flags & ImGuiWindowFlags_ChildWindow)) && !(flags & ImGuiWindowFlags_Tooltip)) + want_focus = true; + } // Handle manual resize: Resize Grips, Borders, Gamepad int border_held = -1; @@ -4787,6 +5238,19 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) if (!window->Collapsed) UpdateManualResize(window, size_auto_fit, &border_held, resize_grip_count, &resize_grip_col[0]); + // Synchronize window --> viewport + if (window->ViewportOwned) + { + if (!window->Viewport->PlatformRequestMove) + window->Viewport->Pos = window->Pos; + if (!window->Viewport->PlatformRequestResize) + window->Viewport->Size = window->Size; + viewport_rect = window->Viewport->GetRect(); + } + + // Save last known viewport position within the window itself (so it can be saved in .ini file and restored) + window->ViewportPos = window->Viewport->Pos; + // Default item width. Make it proportional to window size if window manually resizes if (window->Size.x > 0.0f && !(flags & ImGuiWindowFlags_Tooltip) && !(flags & ImGuiWindowFlags_AlwaysAutoResize)) window->ItemWidthDefault = (float)(int)(window->Size.x * 0.65f); @@ -4799,15 +5263,14 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->DrawList->Clear(); window->DrawList->Flags = (g.Style.AntiAliasedLines ? ImDrawListFlags_AntiAliasedLines : 0) | (g.Style.AntiAliasedFill ? ImDrawListFlags_AntiAliasedFill : 0); window->DrawList->PushTextureID(g.Font->ContainerAtlas->TexID); - ImRect viewport_rect(GetViewportRect()); if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) && !window_is_child_tooltip) PushClipRect(parent_window->ClipRect.Min, parent_window->ClipRect.Max, true); else PushClipRect(viewport_rect.Min, viewport_rect.Max, true); - // Draw modal window background (darkens what is behind them, all viewports) + // Draw modal or window list full viewport dimming background (for other viewports we'll render them in EndFrame) const bool dim_bg_for_modal = (flags & ImGuiWindowFlags_Modal) && window == GetFrontMostPopupModal() && window->HiddenFramesForResize <= 0; - const bool dim_bg_for_window_list = g.NavWindowingTargetAnim && (window == g.NavWindowingTargetAnim->RootWindow); + const bool dim_bg_for_window_list = g.NavWindowingTargetAnim && ((window == g.NavWindowingTargetAnim->RootWindow) || (g.NavWindowingList && (window == g.NavWindowingList) && g.NavWindowingList->Viewport != g.NavWindowingTargetAnim->Viewport)); if (dim_bg_for_modal || dim_bg_for_window_list) { const ImU32 dim_bg_col = GetColorU32(dim_bg_for_modal ? ImGuiCol_ModalWindowDimBg : ImGuiCol_NavWindowingDimBg, g.DimBgRatio); @@ -4827,7 +5290,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) const float window_rounding = window->WindowRounding; const float window_border_size = window->WindowBorderSize; 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 title_bar_is_highlight = want_focus || (window_to_highlight && (window->RootWindowForTitleBarHighlight == window_to_highlight->RootWindowForTitleBarHighlight || (window->DockNode && window->DockNode == window_to_highlight->DockNode))); const ImRect title_bar_rect = window->TitleBarRect(); if (window->Collapsed) { @@ -4843,16 +5306,22 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Window background ImU32 bg_col = GetColorU32(GetWindowBgColorIdxFromFlags(flags)); if (g.NextWindowData.BgAlphaCond != 0) - { bg_col = (bg_col & ~IM_COL32_A_MASK) | (IM_F32_TO_INT8_SAT(g.NextWindowData.BgAlphaVal) << IM_COL32_A_SHIFT); - g.NextWindowData.BgAlphaCond = 0; + if (window->ViewportOwned) + { + //window->Viewport->Alpha = ((bg_col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT) / 255.0f; + bg_col = (bg_col | IM_COL32_A_MASK); } window->DrawList->AddRectFilled(window->Pos + ImVec2(0, window->TitleBarHeight()), window->Pos + window->Size, bg_col, window_rounding, (flags & ImGuiWindowFlags_NoTitleBar) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Bot); // Title bar + // (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(window->Collapsed ? ImGuiCol_TitleBgCollapsed : title_bar_is_highlight ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg); - if (!(flags & ImGuiWindowFlags_NoTitleBar)) window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, window_rounding, ImDrawCornerFlags_Top); + } // Menu bar if (flags & ImGuiWindowFlags_MenuBar) @@ -4892,24 +5361,10 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) ImRect border = GetResizeBorderRect(window, border_held, grip_draw_size, 0.0f); window->DrawList->AddLine(border.Min, border.Max, GetColorU32(ImGuiCol_SeparatorActive), ImMax(1.0f, window_border_size)); } - if (style.FrameBorderSize > 0 && !(flags & ImGuiWindowFlags_NoTitleBar)) + if (style.FrameBorderSize > 0 && !(flags & ImGuiWindowFlags_NoTitleBar) && !window->DockIsActive) window->DrawList->AddLine(title_bar_rect.GetBL() + ImVec2(style.WindowBorderSize, -1), title_bar_rect.GetBR() + ImVec2(-style.WindowBorderSize, -1), GetColorU32(ImGuiCol_Border), style.FrameBorderSize); } - // Draw navigation selection/windowing rectangle border - if (g.NavWindowingTargetAnim == window) - { - float rounding = ImMax(window->WindowRounding, g.Style.WindowRounding); - ImRect bb = window->Rect(); - bb.Expand(g.FontSize); - if (bb.Contains(viewport_rect)) // If a window fits the entire viewport, adjust its highlight inward - { - bb.Expand(-g.FontSize - 1.0f); - rounding = window->WindowRounding; - } - window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha), rounding, ~0, 3.0f); - } - // Store a backup of SizeFull which we will use next frame to decide if we need scrollbars. window->SizeFullAtLastBegin = window->SizeFull; @@ -4971,8 +5426,19 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) NavInitWindow(window, false); } + // Close from platform window + if (p_open != NULL && window->Viewport->PlatformRequestClose && window->Viewport != GetMainViewport()) + { + if (!window->DockIsActive || window->DockTabIsVisible) + { + window->Viewport->PlatformRequestClose = false; + g.NavWindowingToggleLayer = false; // Assume user mapped PlatformRequestClose on ALT-F4 so we disable ALT for menu toggle. False positive not an issue. + *p_open = false; + } + } + // Title bar - if (!(flags & ImGuiWindowFlags_NoTitleBar)) + if (!(flags & ImGuiWindowFlags_NoTitleBar) && !window->DockIsActive) { // Close & collapse button are on layer 1 (same as menus) and don't default focus const ImGuiItemFlags item_flags_backup = window->DC.ItemFlags; @@ -4982,15 +5448,14 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Collapse button if (!(flags & ImGuiWindowFlags_NoCollapse)) - if (CollapseButton(window->GetID("#COLLAPSE"), window->Pos)) + if (CollapseButton(window->GetID("#COLLAPSE"), window->Pos, NULL)) window->WantCollapseToggle = true; // Defer collapsing to next frame as we are too far in the Begin() function // Close button if (p_open != NULL) { - const float pad = style.FramePadding.y; const float rad = g.FontSize * 0.5f; - if (CloseButton(window->GetID("#CLOSE"), window->Rect().GetTR() + ImVec2(-pad - rad, pad + rad), rad + 1)) + if (CloseButton(window->GetID("#CLOSE"), ImVec2(window->Pos.x + window->Size.x - style.FramePadding.x - rad, window->Pos.y + style.FramePadding.y + rad), rad + 1)) *p_open = false; } @@ -4998,8 +5463,11 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->DC.NavLayerCurrentMask >>= 1; window->DC.ItemFlags = item_flags_backup; - // Title text (FIXME: refactor text alignment facilities along with RenderText helpers, this is too much code for what it does.) - ImVec2 text_size = CalcTextSize(name, NULL, true); + // Title bar text (with: horizontal alignment, avoiding collapse/close button, optional "unsaved document" marker) + // FIXME: Refactor text alignment facilities along with RenderText helpers, this is too much code.. + const char* UNSAVED_DOCUMENT_MARKER = "*"; + float marker_size_x = (flags & ImGuiWindowFlags_UnsavedDocument) ? CalcTextSize(UNSAVED_DOCUMENT_MARKER, NULL, false).x : 0.0f; + ImVec2 text_size = CalcTextSize(name, NULL, true) + ImVec2(marker_size_x, 0.0f); ImRect text_r = title_bar_rect; float pad_left = (flags & ImGuiWindowFlags_NoCollapse) ? style.FramePadding.x : (style.FramePadding.x + g.FontSize + style.ItemInnerSpacing.x); float pad_right = (p_open == NULL) ? style.FramePadding.x : (style.FramePadding.x + g.FontSize + style.ItemInnerSpacing.x); @@ -5010,10 +5478,18 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) ImRect clip_rect = text_r; clip_rect.Max.x = window->Pos.x + window->Size.x - (p_open ? title_bar_rect.GetHeight() - 3 : style.FramePadding.x); // Match the size of CloseButton() RenderTextClipped(text_r.Min, text_r.Max, name, NULL, &text_size, style.WindowTitleAlign, &clip_rect); + if (flags & ImGuiWindowFlags_UnsavedDocument) + { + ImVec2 marker_pos = ImVec2(ImMax(text_r.Min.x, text_r.Min.x + (text_r.GetWidth() - text_size.x) * style.WindowTitleAlign.x) + text_size.x, text_r.Min.y) + ImVec2(2 - marker_size_x, 0.0f); + ImVec2 off = ImVec2(0.0f, (float)(int)(-g.FontSize * 0.25f)); + RenderTextClipped(marker_pos + off, text_r.Max + off, UNSAVED_DOCUMENT_MARKER, NULL, NULL, ImVec2(0, style.WindowTitleAlign.y), &clip_rect); + } } // Save clipped aabb so we can access it in constant-time in FindHoveredWindow() window->OuterRectClipped = window->Rect(); + if (window->DockIsActive) + window->OuterRectClipped.Min.y += window->TitleBarHeight(); window->OuterRectClipped.ClipWith(window->ClipRect); // Pressing CTRL+C while holding on a window copy its content to the clipboard @@ -5041,27 +5517,61 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->InnerClipRect.Max.x = ImFloor(0.5f + window->InnerMainRect.Max.x - ImMax(0.0f, ImFloor(window->WindowPadding.x*0.5f - window->WindowBorderSize))); window->InnerClipRect.Max.y = ImFloor(0.5f + window->InnerMainRect.Max.y); - // After Begin() we fill the last item / hovered data based on title bar data. It is a standard behavior (to allow creation of context menus on title bar only, etc.). + 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 BeginAsDockableDragDropSource() also overwrites it. + if ((g.ActiveId == window->MoveId) && ((g.IO.ConfigDockingWithShift && g.IO.KeyShift) || (!g.IO.ConfigDockingWithShift))) + if ((window->RootWindow->Flags & (ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDocking)) == 0) + BeginAsDockableDragDropSource(window); + + // Docking: Any dockable window can act as a target. For dock node hosts we call BeginAsDockableDragDropTarget() in DockNodeUpdate() instead. + if (g.DragDropActive && !(flags & ImGuiWindowFlags_NoDocking)) + if (g.MovingWindow == NULL || g.MovingWindow->RootWindow != window) + if ((window == window->RootWindow) && !(window->Flags & ImGuiWindowFlags_DockNodeHost)) + BeginAsDockableDragDropTarget(window); + } + + // We fill last item data based on Title Bar or 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. + if (window->DockIsActive) + { + window->DC.LastItemId = window->ID; + window->DC.LastItemStatusFlags = window->DockTabItemStatusFlags; + window->DC.LastItemRect = window->DockTabItemRect; + } + else + { window->DC.LastItemId = window->MoveId; window->DC.LastItemStatusFlags = IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0; window->DC.LastItemRect = title_bar_rect; } - - 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) + } + else + { + // Append + SetCurrentViewport(window, window->Viewport); + SetCurrentWindow(window); + } + + if (!(flags & ImGuiWindowFlags_DockNodeHost)) + 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) if (first_begin_of_the_frame) window->WriteAccessed = false; window->BeginCount++; g.NextWindowData.Clear(); + if (window->DockIsActive && !window->DockTabIsVisible) + window->HiddenFramesRegular = 1; + if (flags & ImGuiWindowFlags_ChildWindow) { // 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)); if (!(flags & ImGuiWindowFlags_AlwaysAutoResize) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0) if (window->OuterRectClipped.Min.x >= window->OuterRectClipped.Max.x || window->OuterRectClipped.Min.y >= window->OuterRectClipped.Max.y) window->HiddenFramesRegular = 1; @@ -5076,7 +5586,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->HiddenFramesRegular = 1; // Update the Hidden flag - window->Hidden = (window->HiddenFramesRegular > 0) || (window->HiddenFramesForResize); + window->Hidden = (window->HiddenFramesRegular > 0) || (window->HiddenFramesForResize > 0); // Return false if we don't intend to display anything to allow user to perform an early out optimization window->SkipItems = (window->Collapsed || !window->Active || window->Hidden) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0 && window->HiddenFramesForResize <= 0; @@ -5107,21 +5617,43 @@ void ImGui::End() if (window->DC.ColumnsSet != NULL) EndColumns(); - PopClipRect(); // Inner window clip rectangle + if (!(window->Flags & ImGuiWindowFlags_DockNodeHost)) // Pop inner window clip rectangle + PopClipRect(); // Stop logging if (!(window->Flags & ImGuiWindowFlags_ChildWindow)) // FIXME: add more options for scope of logging LogFinish(); + // 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.CurrentWindowStack.pop_back(); if (window->Flags & ImGuiWindowFlags_Popup) g.CurrentPopupStack.pop_back(); CheckStacksSize(window, false); SetCurrentWindow(g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back()); + if (g.CurrentWindow) + SetCurrentViewport(g.CurrentWindow, g.CurrentWindow->Viewport); +} + +void ImGui::BringWindowToFocusFront(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + if (g.WindowsFocusOrder.back() == window) + return; + for (int i = g.WindowsFocusOrder.Size - 2; i >= 0; i--) // We can ignore the front most window + if (g.WindowsFocusOrder[i] == window) + { + memmove(&g.WindowsFocusOrder[i], &g.WindowsFocusOrder[i + 1], (size_t)(g.WindowsFocusOrder.Size - i - 1) * sizeof(ImGuiWindow*)); + g.WindowsFocusOrder[g.WindowsFocusOrder.Size - 1] = window; + break; + } } -void ImGui::BringWindowToFront(ImGuiWindow* window) +void ImGui::BringWindowToDisplayFront(ImGuiWindow* window) { ImGuiContext& g = *GImGui; ImGuiWindow* current_front_window = g.Windows.back(); @@ -5130,13 +5662,13 @@ void ImGui::BringWindowToFront(ImGuiWindow* window) for (int i = g.Windows.Size - 2; i >= 0; i--) // We can ignore the front most window if (g.Windows[i] == window) { - g.Windows.erase(g.Windows.Data + i); - g.Windows.push_back(window); + memmove(&g.Windows[i], &g.Windows[i + 1], (size_t)(g.Windows.Size - i - 1) * sizeof(ImGuiWindow*)); + g.Windows[g.Windows.Size - 1] = window; break; } } -void ImGui::BringWindowToBack(ImGuiWindow* window) +void ImGui::BringWindowToDisplayBack(ImGuiWindow* window) { ImGuiContext& g = *GImGui; if (g.Windows[0] == window) @@ -5164,13 +5696,17 @@ void ImGui::FocusWindow(ImGuiWindow* window) g.NavId = window ? window->NavLastIds[0] : 0; // Restore NavId g.NavIdIsAlive = false; g.NavLayer = 0; - //printf("[%05d] FocusWindow(\"%s\")\n", g.FrameCount, window ? window->Name : NULL); + //IMGUI_DEBUG_LOG("FocusWindow(\"%s\")\n", window ? window->Name : NULL); } // Passing NULL allow to disable keyboard focus if (!window) return; + // Select in dock node + if (window->DockNode && window->DockNode->TabBar) + window->DockNode->TabBar->SelectedTabId = window->ID; + // Move the root window to the top of the pile if (window->RootWindow) window = window->RootWindow; @@ -5181,21 +5717,25 @@ void ImGui::FocusWindow(ImGuiWindow* window) ClearActiveID(); // Bring to front + BringWindowToFocusFront(window); if (!(window->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus)) - BringWindowToFront(window); + BringWindowToDisplayFront(window); } -void ImGui::FocusFrontMostActiveWindowIgnoringOne(ImGuiWindow* ignore_window) +void ImGui::FocusPreviousWindowIgnoringOne(ImGuiWindow* ignore_window) { ImGuiContext& g = *GImGui; - for (int i = g.Windows.Size - 1; i >= 0; i--) - if (g.Windows[i] != ignore_window && g.Windows[i]->WasActive && !(g.Windows[i]->Flags & ImGuiWindowFlags_ChildWindow)) + for (int i = g.WindowsFocusOrder.Size - 1; i >= 0; i--) + { + ImGuiWindow* window = g.WindowsFocusOrder[i]; + if (window != ignore_window && window->WasActive && !(window->Flags & ImGuiWindowFlags_ChildWindow)) { - ImGuiWindow* focus_window = NavRestoreLastChildNavWindow(g.Windows[i]); + ImGuiWindow* focus_window = NavRestoreLastChildNavWindow(window); FocusWindow(focus_window); return; } } +} void ImGui::PushItemWidth(float item_width) { @@ -5386,6 +5926,7 @@ static const ImGuiStyleVarInfo GStyleVarInfo[] = { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ScrollbarRounding) }, // ImGuiStyleVar_ScrollbarRounding { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, GrabMinSize) }, // ImGuiStyleVar_GrabMinSize { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, GrabRounding) }, // ImGuiStyleVar_GrabRounding + { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, TabRounding) }, // ImGuiStyleVar_TabRounding { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, ButtonTextAlign) }, // ImGuiStyleVar_ButtonTextAlign }; @@ -5478,6 +6019,13 @@ const char* ImGui::GetStyleColorName(ImGuiCol idx) case ImGuiCol_ResizeGrip: return "ResizeGrip"; case ImGuiCol_ResizeGripHovered: return "ResizeGripHovered"; case ImGuiCol_ResizeGripActive: return "ResizeGripActive"; + case ImGuiCol_Tab: return "Tab"; + case ImGuiCol_TabHovered: return "TabHovered"; + case ImGuiCol_TabActive: return "TabActive"; + case ImGuiCol_TabUnfocused: return "TabUnfocused"; + case ImGuiCol_TabUnfocusedActive: return "TabUnfocusedActive"; + case ImGuiCol_DockingPreview: return "DockingPreview"; + case ImGuiCol_DockingBg: return "DockingBg"; case ImGuiCol_PlotLines: return "PlotLines"; case ImGuiCol_PlotLinesHovered: return "PlotLinesHovered"; case ImGuiCol_PlotHistogram: return "PlotHistogram"; @@ -5521,11 +6069,11 @@ bool ImGui::IsWindowHovered(ImGuiHoveredFlags flags) switch (flags & (ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows)) { case ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows: - if (g.HoveredRootWindow != g.CurrentWindow->RootWindow) + if (g.HoveredWindow == NULL || g.HoveredWindow->RootWindowDockStop != g.CurrentWindow->RootWindowDockStop) return false; break; case ImGuiHoveredFlags_RootWindow: - if (g.HoveredWindow != g.CurrentWindow->RootWindow) + if (g.HoveredWindow != g.CurrentWindow->RootWindowDockStop) return false; break; case ImGuiHoveredFlags_ChildWindows: @@ -5558,9 +6106,9 @@ bool ImGui::IsWindowFocused(ImGuiFocusedFlags flags) switch (flags & (ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows)) { case ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows: - return g.NavWindow && g.NavWindow->RootWindow == g.CurrentWindow->RootWindow; + return g.NavWindow && g.NavWindow->RootWindowDockStop == g.CurrentWindow->RootWindowDockStop; case ImGuiFocusedFlags_RootWindow: - return g.NavWindow == g.CurrentWindow->RootWindow; + return g.NavWindow == g.CurrentWindow->RootWindowDockStop; case ImGuiFocusedFlags_ChildWindows: return g.NavWindow && IsWindowChildOf(g.NavWindow, g.CurrentWindow); default: @@ -5568,10 +6116,22 @@ bool ImGui::IsWindowFocused(ImGuiFocusedFlags flags) } } +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) bool ImGui::IsWindowNavFocusable(ImGuiWindow* window) { - return window->Active && window == window->RootWindow && !(window->Flags & ImGuiWindowFlags_NoNavFocus); + return window->Active && window == window->RootWindowDockStop && !(window->Flags & ImGuiWindowFlags_NoNavFocus); } float ImGui::GetWindowWidth() @@ -5655,7 +6215,7 @@ static void SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond con if (size.x > 0.0f) { window->AutoFitFramesX = 0; - window->SizeFull.x = size.x; + window->SizeFull.x = ImFloor(size.x); } else { @@ -5665,7 +6225,7 @@ static void SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond con if (size.y > 0.0f) { window->AutoFitFramesY = 0; - window->SizeFull.y = size.y; + window->SizeFull.y = ImFloor(size.y); } else { @@ -5744,7 +6304,17 @@ 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; +} + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS +void ImGui::SetNextWindowPosCenter(ImGuiCond cond) +{ + ImGuiViewport* viewport = ImGui::GetMainViewport(); + SetNextWindowPos(viewport->Pos + viewport->Size * 0.5f, cond, ImVec2(0.5f, 0.5f)); + SetNextWindowViewport(viewport->ID); } +#endif void ImGui::SetNextWindowSize(const ImVec2& size, ImGuiCond cond) { @@ -5791,6 +6361,26 @@ void ImGui::SetNextWindowBgAlpha(float alpha) g.NextWindowData.BgAlphaCond = ImGuiCond_Always; // Using a Cond member for consistency (may transition all of them to single flag set for fast Clear() op) } +void ImGui::SetNextWindowViewport(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.ViewportCond = ImGuiCond_Always; + g.NextWindowData.ViewportId = id; +} + +void ImGui::SetNextWindowDockId(ImGuiID id, ImGuiCond cond) +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.DockCond = cond ? cond : ImGuiCond_Always; + g.NextWindowData.DockId = id; +} + +void ImGui::SetNextWindowDockFamily(const ImGuiDockFamily* family) +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.DockFamily = *family; +} + // In window space (not screen space!) ImVec2 ImGui::GetContentRegionMax() { @@ -5861,6 +6451,20 @@ ImDrawList* ImGui::GetWindowDrawList() return window->DrawList; } +float ImGui::GetWindowDpiScale() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.CurrentViewport != NULL); + return g.CurrentViewport->DpiScale; +} + +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; @@ -5988,7 +6592,7 @@ void ImGui::SetScrollFromPosY(float pos_y, float center_y_ratio) } // center_y_ratio: 0.0f top of last item, 0.5f vertical center of last item, 1.0f bottom of last item. -void ImGui::SetScrollHere(float center_y_ratio) +void ImGui::SetScrollHereY(float center_y_ratio) { ImGuiWindow* window = GetCurrentWindow(); float target_y = window->DC.CursorPosPrevLine.y - window->Pos.y; // Top of last item, in window space @@ -6023,7 +6627,7 @@ void ImGui::SetItemDefaultFocus() g.NavInitResultRectRel = ImRect(g.NavWindow->DC.LastItemRect.Min - g.NavWindow->Pos, g.NavWindow->DC.LastItemRect.Max - g.NavWindow->Pos); NavUpdateAnyRequestFlag(); if (!IsItemVisible()) - SetScrollHere(); + SetScrollHereY(); } } @@ -6247,7 +6851,7 @@ void ImGui::BeginTooltipEx(ImGuiWindowFlags extra_flags, bool override_previous_ window->HiddenFramesRegular = 1; 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_NoNav; + ImGuiWindowFlags flags = ImGuiWindowFlags_Tooltip|ImGuiWindowFlags_NoInputs|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoNav|ImGuiWindowFlags_NoDocking; Begin(window_name, NULL, flags | extra_flags); } @@ -6461,7 +7065,7 @@ bool ImGui::BeginPopup(const char* str_id, ImGuiWindowFlags flags) g.NextWindowData.Clear(); // We behave like Begin() and need to consume those values return false; } - return BeginPopupEx(g.CurrentWindow->GetID(str_id), flags|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings); + return BeginPopupEx(g.CurrentWindow->GetID(str_id), flags|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_NoDocking); } bool ImGui::BeginPopupModal(const char* name, bool* p_open, ImGuiWindowFlags flags) @@ -6478,7 +7082,7 @@ bool ImGui::BeginPopupModal(const char* name, bool* p_open, ImGuiWindowFlags fla // Center modal windows by default // FIXME: Should test for (PosCond & window->SetWindowPosAllowFlags) with the upcoming window. if (g.NextWindowData.PosCond == 0) - SetNextWindowPos(g.IO.DisplaySize * 0.5f, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); + SetNextWindowPos(window->Viewport->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5f, 0.5f)); bool is_open = Begin(name, p_open, flags | ImGuiWindowFlags_Popup | ImGuiWindowFlags_Modal | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings); if (!is_open || (p_open && !*p_open)) // NB: is_open can be 'false' when the popup is completely clipped (e.g. zero size display) @@ -6537,16 +7141,11 @@ bool ImGui::BeginPopupContextVoid(const char* str_id, int mouse_button) return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings); } -ImRect ImGui::GetWindowAllowedExtentRect(ImGuiWindow*) -{ - ImVec2 padding = GImGui->Style.DisplaySafeAreaPadding; - ImRect r_screen = GetViewportRect(); - 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; -} - // r_avoid = the rectangle to avoid (e.g. for tooltip it is a rectangle around the mouse cursor which we want to avoid. for popups it's a small point around the cursor.) // r_outer = the visible area rectangle, minus safe area padding. If our popup size won't fit because of safe area padding we ignore it. +// (r_outer is usually equivalent to the viewport rectangle minus padding, but when multi-viewports are enabled and monitor +// information are available, it may represent the entire platform monitor from the frame of reference of the current viewport. +// this allows us to have tooltips/popups displayed out of the parent viewport.) ImVec2 ImGui::FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& size, ImGuiDir* last_dir, const ImRect& r_outer, const ImRect& r_avoid, ImGuiPopupPositionPolicy policy) { ImVec2 base_pos_clamped = ImClamp(ref_pos, r_outer.Min, r_outer.Max - size); @@ -6600,18 +7199,37 @@ ImVec2 ImGui::FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& s return pos; } -ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window) +ImRect ImGui::GetWindowAllowedExtentRect(ImGuiWindow* window) { ImGuiContext& g = *GImGui; + 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 + { + r_screen.Min = window->Viewport->Pos; + r_screen.Max = window->Viewport->Pos + window->Viewport->Size; + } + 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; +} - ImRect r_outer = GetWindowAllowedExtentRect(window); +ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; if (window->Flags & ImGuiWindowFlags_ChildMenu) { // Child menus typically request _any_ position within the parent menu item, and then our FindBestWindowPosForPopup() function will 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]; + ImGuiWindow* parent_window = window->ParentWindow; float horizontal_overlap = g.Style.ItemSpacing.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_outer = GetWindowAllowedExtentRect(window); ImRect r_avoid; if (parent_window->DC.MenuBarAppending) r_avoid = ImRect(-FLT_MAX, parent_window->Pos.y + parent_window->TitleBarHeight(), FLT_MAX, parent_window->Pos.y + parent_window->TitleBarHeight() + parent_window->MenuBarHeight()); @@ -6621,6 +7239,7 @@ ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window) } if (window->Flags & ImGuiWindowFlags_Popup) { + ImRect r_outer = GetWindowAllowedExtentRect(window); ImRect r_avoid = ImRect(window->Pos.x - 1, window->Pos.y - 1, window->Pos.x + 1, window->Pos.y + 1); return FindBestWindowPosForPopupEx(window->Pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid); } @@ -6629,6 +7248,7 @@ ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window) // Position tooltip (always follows mouse) float sc = g.Style.MouseCursorScale; ImVec2 ref_pos = NavCalcPreferredRefPos(); + ImRect r_outer = GetWindowAllowedExtentRect(window); ImRect r_avoid; if (!g.NavDisableHighlight && g.NavDisableMouseHover && !(g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos)) r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 16, ref_pos.y + 8); @@ -6644,382 +7264,1033 @@ ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window) } //----------------------------------------------------------------------------- -// [SECTION] KEYBOARD/GAMEPAD NAVIGATION +// [SECTION] VIEWPORTS, PLATFORM WINDOWS //----------------------------------------------------------------------------- -ImGuiDir ImGetDirQuadrantFromDelta(float dx, float dy) +ImGuiViewport* ImGui::GetMainViewport() { - if (ImFabs(dx) > ImFabs(dy)) - return (dx > 0.0f) ? ImGuiDir_Right : ImGuiDir_Left; - return (dy > 0.0f) ? ImGuiDir_Down : ImGuiDir_Up; + ImGuiContext& g = *GImGui; + return g.Viewports[0]; } -static float inline NavScoreItemDistInterval(float a0, float a1, float b0, float b1) +ImGuiViewportP* ImGui::FindViewportByID(ImGuiID id) { - if (a1 < b0) - return a1 - b0; - if (b1 < a0) - return a0 - b1; - return 0.0f; + ImGuiContext& g = *GImGui; + for (int n = 0; n < g.Viewports.Size; n++) + if (g.Viewports[n]->ID == id) + return g.Viewports[n]; + return NULL; } -static void inline NavClampRectToVisibleAreaForMoveDir(ImGuiDir move_dir, ImRect& r, const ImRect& clip_rect) +ImGuiViewport* ImGui::FindViewportByPlatformHandle(void* platform_handle) { - if (move_dir == ImGuiDir_Left || move_dir == ImGuiDir_Right) - { - r.Min.y = ImClamp(r.Min.y, clip_rect.Min.y, clip_rect.Max.y); - r.Max.y = ImClamp(r.Max.y, clip_rect.Min.y, clip_rect.Max.y); - } - else - { - r.Min.x = ImClamp(r.Min.x, clip_rect.Min.x, clip_rect.Max.x); - r.Max.x = ImClamp(r.Max.x, clip_rect.Min.x, clip_rect.Max.x); - } + ImGuiContext& g = *GImGui; + for (int i = 0; i != g.Viewports.Size; i++) + if (g.Viewports[i]->PlatformHandle == platform_handle) + return g.Viewports[i]; + return NULL; } -// Scoring function for directional navigation. Based on https://gist.github.com/rygorous/6981057 -static bool NavScoreItem(ImGuiNavMoveResult* result, ImRect cand) -{ +void ImGui::SetCurrentViewport(ImGuiWindow* current_window, ImGuiViewportP* viewport) + { ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - if (g.NavLayer != window->DC.NavLayerCurrent) - return false; + (void)current_window; - const ImRect& curr = g.NavScoringRectScreen; // Current modified source rect (NB: we've applied Max.x = Min.x in NavUpdate() to inhibit the effect of having varied item width) - g.NavScoringCount++; + if (viewport) + viewport->LastFrameActive = g.FrameCount; + if (g.CurrentViewport == viewport) + return; + g.CurrentViewport = viewport; - // When entering through a NavFlattened border, we consider child window items as fully clipped for scoring - if (window->ParentWindow == g.NavWindow) + // 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); + } + +static void SetWindowViewport(ImGuiWindow* window, ImGuiViewportP* viewport) { - IM_ASSERT((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened); - if (!window->ClipRect.Contains(cand)) - return false; - cand.ClipWithFull(window->ClipRect); // This allows the scored item to not overlap other candidates in the parent window + window->Viewport = viewport; + window->ViewportId = viewport->ID; + window->ViewportOwned = (viewport->Window == window); } - // We perform scoring on items bounding box clipped by the current clipping rectangle on the other axis (clipping on our movement axis would give us equal scores for all clipped items) - // For example, this ensure that items in one column are not reached when moving vertically from items in another column. - NavClampRectToVisibleAreaForMoveDir(g.NavMoveClipDir, cand, window->ClipRect); +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 protude and create their own. + ImGuiContext& g = *GImGui; + if ((g.IO.ConfigFlags & ImGuiConfigFlags_ViewportsNoMerge) && (g.IO.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)) + if (!window->DockIsActive) + if ((window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_Tooltip)) == 0) + return true; + return false; +} - // Compute distance between boxes - // FIXME-NAV: Introducing biases for vertical navigation, needs to be removed. - float dbx = NavScoreItemDistInterval(cand.Min.x, cand.Max.x, curr.Min.x, curr.Max.x); - float dby = NavScoreItemDistInterval(ImLerp(cand.Min.y, cand.Max.y, 0.2f), ImLerp(cand.Min.y, cand.Max.y, 0.8f), ImLerp(curr.Min.y, curr.Max.y, 0.2f), ImLerp(curr.Min.y, curr.Max.y, 0.8f)); // Scale down on Y to keep using box-distance for vertically touching items - if (dby != 0.0f && dbx != 0.0f) - dbx = (dbx/1000.0f) + ((dbx > 0.0f) ? +1.0f : -1.0f); - float dist_box = ImFabs(dbx) + ImFabs(dby); +static bool ImGui::UpdateTryMergeWindowIntoHostViewport(ImGuiWindow* window, ImGuiViewportP* viewport) +{ + ImGuiContext& g = *GImGui; + if (!(g.IO.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)) + return false; + if (!(viewport->Flags & ImGuiViewportFlags_CanHostOtherWindows) || window->Viewport == viewport) + return false; + if (!viewport->GetRect().Contains(window->Rect())) + return false; + if (GetWindowAlwaysWantOwnViewport(window)) + return false; - // Compute distance between centers (this is off by a factor of 2, but we only compare center distances with each other so it doesn't matter) - float dcx = (cand.Min.x + cand.Max.x) - (curr.Min.x + curr.Max.x); - float dcy = (cand.Min.y + cand.Max.y) - (curr.Min.y + curr.Max.y); - float dist_center = ImFabs(dcx) + ImFabs(dcy); // L1 metric (need this for our connectedness guarantee) + // 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); + return true; +} - // Determine which quadrant of 'curr' our candidate item 'cand' lies in based on distance - ImGuiDir quadrant; - float dax = 0.0f, day = 0.0f, dist_axial = 0.0f; - if (dbx != 0.0f || dby != 0.0f) - { - // For non-overlapping boxes, use distance between boxes - dax = dbx; - day = dby; - dist_axial = dist_box; - quadrant = ImGetDirQuadrantFromDelta(dbx, dby); - } - else if (dcx != 0.0f || dcy != 0.0f) +// Scale all windows (position, size). Use when e.g. changing DPI. (This is a lossy operation!) +void ImGui::ScaleWindowsInViewport(ImGuiViewportP* viewport, float scale) +{ + ImGuiContext& g = *GImGui; + if (viewport->Window) { - // For overlapping boxes with different centers, use distance between centers - dax = dcx; - day = dcy; - dist_axial = dist_center; - quadrant = ImGetDirQuadrantFromDelta(dcx, dcy); + ScaleWindow(viewport->Window, scale); } else { - // Degenerate case: two overlapping buttons with same center, break ties arbitrarily (note that LastItemId here is really the _previous_ item order, but it doesn't matter) - quadrant = (window->DC.LastItemId < g.NavId) ? ImGuiDir_Left : ImGuiDir_Right; + for (int i = 0; i != g.Windows.Size; i++) + if (g.Windows[i]->Viewport == viewport) + ScaleWindow(g.Windows[i], scale); } +} -#if IMGUI_DEBUG_NAV_SCORING - char buf[128]; - if (ImGui::IsMouseHoveringRect(cand.Min, cand.Max)) +// If the back-end 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 back-end. +static ImGuiViewportP* FindViewportHoveredFromPlatformWindowStack(const ImVec2 mouse_platform_pos) +{ + ImGuiContext& g = *GImGui; + ImGuiViewportP* best_candidate = NULL; + for (int n = 0; n < g.Viewports.Size; n++) { - ImFormatString(buf, IM_ARRAYSIZE(buf), "dbox (%.2f,%.2f->%.4f)\ndcen (%.2f,%.2f->%.4f)\nd (%.2f,%.2f->%.4f)\nnav %c, quadrant %c", dbx, dby, dist_box, dcx, dcy, dist_center, dax, day, dist_axial, "WENS"[g.NavMoveDir], "WENS"[quadrant]); - ImDrawList* draw_list = ImGui::GetOverlayDrawList(); - draw_list->AddRect(curr.Min, curr.Max, IM_COL32(255,200,0,100)); - draw_list->AddRect(cand.Min, cand.Max, IM_COL32(255,255,0,200)); - draw_list->AddRectFilled(cand.Max-ImVec2(4,4), cand.Max+ImGui::CalcTextSize(buf)+ImVec2(4,4), IM_COL32(40,0,0,150)); - draw_list->AddText(g.IO.FontDefault, 13.0f, cand.Max, ~0U, buf); + ImGuiViewportP* viewport = g.Viewports[n]; + if (!(viewport->Flags & ImGuiViewportFlags_NoInputs) && viewport->GetRect().Contains(mouse_platform_pos)) + if (best_candidate == NULL || best_candidate->LastFrontMostStampCount < viewport->LastFrontMostStampCount) + best_candidate = viewport; } - else if (g.IO.KeyCtrl) // Hold to preview score in matching quadrant. Press C to rotate. + return best_candidate; +} + +static void ImGui::UpdateViewports() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.PlatformIO.Viewports.Size <= g.Viewports.Size); + + // Update main viewport with current platform position and size + ImGuiViewportP* main_viewport = g.Viewports[0]; + IM_ASSERT(main_viewport->ID == IMGUI_VIEWPORT_DEFAULT_ID); + IM_ASSERT(main_viewport->Window == NULL); + ImVec2 main_viewport_platform_pos = ImVec2(0.0f, 0.0f); + if ((g.IO.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)) + main_viewport_platform_pos = g.PlatformIO.Platform_GetWindowPos(main_viewport); + AddUpdateViewport(NULL, IMGUI_VIEWPORT_DEFAULT_ID, main_viewport_platform_pos, g.IO.DisplaySize, ImGuiViewportFlags_CanHostOtherWindows); + + g.CurrentViewport = NULL; + g.MouseViewport = NULL; + for (int n = 0; n < g.Viewports.Size; n++) { - if (IsKeyPressedMap(ImGuiKey_C)) { g.NavMoveDirLast = (ImGuiDir)((g.NavMoveDirLast + 1) & 3); g.IO.KeysDownDuration[g.IO.KeyMap[ImGuiKey_C]] = 0.01f; } - if (quadrant == g.NavMoveDir) + // Erase unused viewports + ImGuiViewportP* viewport = g.Viewports[n]; + viewport->Idx = n; + + if (n > 0 && viewport->LastFrameActive < g.FrameCount - 2) { - ImFormatString(buf, IM_ARRAYSIZE(buf), "%.0f/%.0f", dist_box, dist_center); - ImDrawList* draw_list = ImGui::GetOverlayDrawList(); - draw_list->AddRectFilled(cand.Min, cand.Max, IM_COL32(255, 0, 0, 200)); - draw_list->AddText(g.IO.FontDefault, 13.0f, cand.Min, IM_COL32(255, 255, 255, 255), buf); + // Clear references to this viewport in windows (window->ViewportId becomes the master data) + for (int window_n = 0; window_n < g.Windows.Size; window_n++) + if (g.Windows[window_n]->Viewport == viewport) + g.Windows[window_n]->Viewport = NULL; + if (viewport == g.MouseLastHoveredViewport) + g.MouseLastHoveredViewport = NULL; + g.Viewports.erase(g.Viewports.Data + n); + + // Destroy + //IMGUI_DEBUG_LOG("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_DELETE(viewport); + n--; + continue; } - } - #endif - // Is it in the quadrant we're interesting in moving to? - bool new_best = false; - if (quadrant == g.NavMoveDir) - { - // Does it beat the current best candidate? - if (dist_box < result->DistBox) + if ((g.IO.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)) { - result->DistBox = dist_box; - result->DistCenter = dist_center; - return true; + // Apply 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->PlatformRequestMove) + viewport->Pos = g.PlatformIO.Platform_GetWindowPos(viewport); + if (viewport->PlatformRequestResize) + viewport->Size = g.PlatformIO.Platform_GetWindowSize(viewport); + + // Translate imgui windows when a Host Viewport has been moved + ImVec2 delta = viewport->Pos - viewport->LastPos; + if ((viewport->Flags & ImGuiViewportFlags_CanHostOtherWindows) && (delta.x != 0.0f || delta.y != 0.0f)) + for (int window_n = 0; window_n < g.Windows.Size; window_n++) + if (g.Windows[window_n]->Viewport == viewport) + TranslateWindow(g.Windows[window_n], delta); + + // Update monitor (we'll use this info to clamp windows and save windows lost in a removed monitor) + viewport->PlatformMonitor = FindPlatformMonitorForRect(viewport->GetRect()); } - if (dist_box == result->DistBox) + + // Update DPI scale + float new_dpi_scale; + if (g.PlatformIO.Platform_GetWindowDpiScale) + new_dpi_scale = g.PlatformIO.Platform_GetWindowDpiScale(viewport); + else + new_dpi_scale = (viewport->DpiScale != 0.0f) ? viewport->DpiScale : 1.0f; + if (viewport->DpiScale != 0.0f && new_dpi_scale != viewport->DpiScale) { - // Try using distance between center points to break ties - if (dist_center < result->DistCenter) - { - result->DistCenter = dist_center; - new_best = true; - } - else if (dist_center == result->DistCenter) - { - // Still tied! we need to be extra-careful to make sure everything gets linked properly. We consistently break ties by symbolically moving "later" items - // (with higher index) to the right/downwards by an infinitesimal amount since we the current "best" button already (so it must have a lower index), - // this is fairly easy. This rule ensures that all buttons with dx==dy==0 will end up being linked in order of appearance along the x axis. - if (((g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) ? dby : dbx) < 0.0f) // moving bj to the right/down decreases distance - new_best = true; - } + 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 = ImFloor(g.ActiveIdClickOffset * scale_factor); } + viewport->DpiScale = new_dpi_scale; } - // Axial check: if 'curr' has no link at all in some direction and 'cand' lies roughly in that direction, add a tentative link. This will only be kept if no "real" matches - // are found, so it only augments the graph produced by the above method using extra links. (important, since it doesn't guarantee strong connectedness) - // This is just to avoid buttons having no links in a particular direction when there's a suitable neighbor. you get good graphs without this too. - // 2017/09/29: FIXME: This now currently only enabled inside menu bars, ideally we'd disable it everywhere. Menus in particular need to catch failure. For general navigation it feels awkward. - // Disabling it may lead to disconnected graphs when nodes are very spaced out on different axis. Perhaps consider offering this as an option? - if (result->DistBox == FLT_MAX && dist_axial < result->DistAxial) // Check axial match - if (g.NavLayer == 1 && !(g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu)) - if ((g.NavMoveDir == ImGuiDir_Left && dax < 0.0f) || (g.NavMoveDir == ImGuiDir_Right && dax > 0.0f) || (g.NavMoveDir == ImGuiDir_Up && day < 0.0f) || (g.NavMoveDir == ImGuiDir_Down && day > 0.0f)) - { - result->DistAxial = dist_axial; - new_best = true; - } + if (!(g.IO.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)) + { + g.MouseViewport = main_viewport; + return; + } - return new_best; + // 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 ? FindViewportByID(g.IO.MouseHoveredViewport) : NULL; + if (viewport_hovered && (viewport_hovered->Flags & ImGuiViewportFlags_NoInputs)) + { + // Back-end failed at honoring its contract if it returned a viewport with the _NoInputs flag. + IM_ASSERT(0); + viewport_hovered = FindViewportHoveredFromPlatformWindowStack(g.IO.MousePos); + } + } + else + { + // If the back-end 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) 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 = FindViewportHoveredFromPlatformWindowStack(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) + if (g.MovingWindow) + 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. + 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 || !ImGui::IsAnyMouseDown()) + if (viewport_hovered != NULL && viewport_hovered != g.MouseViewport && !(viewport_hovered->Flags & ImGuiViewportFlags_NoInputs)) + g.MouseViewport = viewport_hovered; + + IM_ASSERT(g.MouseViewport != NULL); } -// We get there when either NavId == id, or when g.NavAnyRequest is set (which is updated by NavUpdateAnyRequestFlag above) -static void ImGui::NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, const ImGuiID id) +ImGuiViewportP* ImGui::AddUpdateViewport(ImGuiWindow* window, ImGuiID id, const ImVec2& pos, const ImVec2& size, ImGuiViewportFlags flags) { ImGuiContext& g = *GImGui; - //if (!g.IO.NavActive) // [2017/10/06] Removed this possibly redundant test but I am not sure of all the side-effects yet. Some of the feature here will need to work regardless of using a _NoNavInputs flag. - // return; - - const ImGuiItemFlags item_flags = window->DC.ItemFlags; - const ImRect nav_bb_rel(nav_bb.Min - window->Pos, nav_bb.Max - window->Pos); + IM_ASSERT(id != 0); - // Process Init Request - if (g.NavInitRequest && g.NavLayer == window->DC.NavLayerCurrent) + ImGuiViewportP* viewport = (ImGuiViewportP*)FindViewportByID(id); + if (viewport) { - // Even if 'ImGuiItemFlags_NoNavDefaultFocus' is on (typically collapse/close button) we record the first ResultId so they can be used as a fallback - if (!(item_flags & ImGuiItemFlags_NoNavDefaultFocus) || g.NavInitResultId == 0) - { - g.NavInitResultId = id; - g.NavInitResultRectRel = nav_bb_rel; - } - if (!(item_flags & ImGuiItemFlags_NoNavDefaultFocus)) - { - g.NavInitRequest = false; // Found a match, clear request - NavUpdateAnyRequestFlag(); - } + viewport->Pos = pos; + viewport->Size = size; } + else + { + // New viewport + viewport = IM_NEW(ImGuiViewportP)(); + viewport->ID = id; + viewport->Idx = g.Viewports.Size; + viewport->Pos = viewport->LastPos = pos; + viewport->Size = size; + viewport->PlatformMonitor = FindPlatformMonitorForRect(viewport->GetRect()); + g.Viewports.push_back(viewport); + //IMGUI_DEBUG_LOG("Add Viewport %08X (%s)\n", id, window->Name); - // Process Move Request (scoring for navigation) - // FIXME-NAV: Consider policy for double scoring (scoring from NavScoringRectScreen + scoring from a rect wrapped according to current wrapping policy) - if ((g.NavId != id || (g.NavMoveRequestFlags & ImGuiNavMoveFlags_AllowCurrentNavId)) && !(item_flags & ImGuiItemFlags_NoNav)) + if (window && (window->Flags & ImGuiWindowFlags_NoFocusOnAppearing)) + flags |= ImGuiViewportFlags_NoFocusOnAppearing; + + // 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.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); + + // Request an initial DpiScale before the OS platform window creation + // This is so we can select an appropriate font size on the first frame of our window lifetime + if (g.PlatformIO.Platform_GetWindowDpiScale) + viewport->DpiScale = g.PlatformIO.Platform_GetWindowDpiScale(viewport); + } + + viewport->Window = window; + viewport->Flags = flags; + viewport->LastFrameActive = g.FrameCount; + IM_ASSERT(window == NULL || viewport->ID == window->ID); + + if (window != NULL) + window->ViewportOwned = true; + + return viewport; +} + +// FIXME-VIEWPORT: This is all super messy and ought to be clarified or rewritten. +static void ImGui::UpdateSelectWindowViewport(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + ImGuiWindowFlags flags = window->Flags; + window->ViewportAllowPlatformMonitorExtend = -1; + + // Restore main viewport if multi-viewport is not supported by the back-end + ImGuiViewportP* main_viewport = g.Viewports[0]; + if (!(g.IO.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)) { - ImGuiNavMoveResult* result = (window == g.NavWindow) ? &g.NavMoveResultLocal : &g.NavMoveResultOther; -#if IMGUI_DEBUG_NAV_SCORING - // [DEBUG] Score all items in NavWindow at all times - if (!g.NavMoveRequest) - g.NavMoveDir = g.NavMoveDirLast; - bool new_best = NavScoreItem(result, nav_bb) && g.NavMoveRequest; -#else - bool new_best = g.NavMoveRequest && NavScoreItem(result, nav_bb); -#endif - if (new_best) + SetWindowViewport(window, main_viewport); + return; + } + + // Merge into host viewports (after moving, resizing) + if (window->ViewportOwned && window->ViewportTryMerge && g.ActiveId == 0) + { + UpdateTryMergeWindowIntoHostViewport(window, g.Viewports[0]); + window->ViewportTryMerge = false; + } + 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.ViewportCond) + { + // By default inherit from parent window + if (window->Viewport == NULL && window->ParentWindow) + 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) { - result->ID = id; - result->Window = window; - result->RectRel = nav_bb_rel; + window->Viewport = 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_NoDecoration); } + } - const float VISIBLE_RATIO = 0.70f; - if ((g.NavMoveRequestFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet) && window->ClipRect.Overlaps(nav_bb)) - if (ImClamp(nav_bb.Max.y, window->ClipRect.Min.y, window->ClipRect.Max.y) - ImClamp(nav_bb.Min.y, window->ClipRect.Min.y, window->ClipRect.Max.y) >= (nav_bb.Max.y - nav_bb.Min.y) * VISIBLE_RATIO) - if (NavScoreItem(&g.NavMoveResultLocalVisibleSet, nav_bb)) - { - result = &g.NavMoveResultLocalVisibleSet; - result->ID = id; - result->Window = window; - result->RectRel = nav_bb_rel; - } + if (g.NextWindowData.ViewportCond) + { + // Code explicitly request a viewport + window->Viewport = FindViewportByID(g.NextWindowData.ViewportId); + window->ViewportId = g.NextWindowData.ViewportId; // Store ID even if Viewport isn't resolved yet. + } + else if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_ChildMenu)) + { + // Always inherit viewport from parent window + window->Viewport = window->ParentWindow->Viewport; + } + else if (flags & ImGuiWindowFlags_Tooltip) + { + window->Viewport = g.MouseViewport; + } + else if (g.MovingWindow && g.MovingWindow->RootWindow == window && IsMousePosValid()) + { + // Transition to our own viewport when leaving our host boundaries + set the NoInputs flag (which will be cleared in UpdateMovingWindow when releasing the mouse) + // If we are already in our own viewport, if need to set the NoInputs flag. + // If we have no viewport (which happens when detaching a docked node) immediately create one. + // We test for 'window->Viewport->Window == window' instead of 'window->ViewportOwned' because ViewportOwned is not valid during this function. + bool has_viewport = (window->Viewport != NULL); + bool own_viewport = has_viewport && (window->Viewport->Window == window); + bool leave_host_viewport = has_viewport && !own_viewport && !window->Viewport->GetRect().Contains(window->Rect()); + bool move_from_own_viewport = has_viewport && own_viewport && !(window->Viewport->Flags & ImGuiViewportFlags_NoInputs); + if (!has_viewport || leave_host_viewport || move_from_own_viewport) + window->Viewport = AddUpdateViewport(window, window->ID, window->Pos, window->Size, ImGuiViewportFlags_NoDecoration | ImGuiViewportFlags_NoFocusOnAppearing | ImGuiViewportFlags_NoInputs); + } + else if (GetWindowAlwaysWantOwnViewport(window)) + { + window->Viewport = AddUpdateViewport(window, window->ID, window->Pos, window->Size, ImGuiViewportFlags_NoDecoration); } - // Update window-relative bounding box of navigated item - if (g.NavId == id) + // Mark window as allowed to protrude outside of its viewport and into the current monitor + // We need to take account of the possibility that mouse may become invalid. + const bool use_mouse_ref = (g.NavDisableHighlight || !g.NavDisableMouseHover || !g.NavWindow); + if (flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup)) { - g.NavWindow = window; // Always refresh g.NavWindow, because some operations such as FocusItem() don't have a window. - g.NavLayer = window->DC.NavLayerCurrent; - g.NavIdIsAlive = true; - g.NavIdTabCounter = window->FocusIdxTabCounter; - window->NavRectRel[window->DC.NavLayerCurrent] = nav_bb_rel; // Store item bounding box (relative to window position) + ImVec2 mouse_ref = (flags & ImGuiWindowFlags_Tooltip) ? g.IO.MousePos : g.CurrentPopupStack.back().OpenMousePos; + bool mouse_valid = IsMousePosValid(&mouse_ref); + if ((window->Appearing || (flags & ImGuiWindowFlags_Tooltip)) && (!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)) + { + // When called from Begin() we don't have access to a proper version of the Hidden flag yet. + 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 + //printf("[%05d] Window '%s' steal Viewport %08X from Window '%s'\n", g.FrameCount, window->Name, window->Viewport->ID, window->Viewport->Window->Name); + window->Viewport->Window = window; + window->Viewport->ID = window->ID; + window->Viewport->LastNameHash = 0; + } + else if (!UpdateTryMergeWindowIntoHostViewport(window, g.Viewports[0])) // Merge? + { + // New viewport + window->Viewport = AddUpdateViewport(window, window->ID, window->Pos, window->Size, ImGuiViewportFlags_NoDecoration | ImGuiViewportFlags_NoFocusOnAppearing); + } } + else if ((flags & ImGuiWindowFlags_DockNodeHost) && (window->Appearing)) + { + // Mark so the dock host can be on its own viewport + window->ViewportAllowPlatformMonitorExtend = FindPlatformMonitorForRect(window->Rect()); + } + if (window->ViewportTrySplit && window->ViewportAllowPlatformMonitorExtend < 0) + window->ViewportAllowPlatformMonitorExtend = window->Viewport->PlatformMonitor; + window->ViewportTrySplit = false; + + // Fallback to default viewport + if (window->Viewport == NULL) + window->Viewport = main_viewport; + + // Update flags + window->ViewportOwned = (window == window->Viewport->Window); + if (window->ViewportOwned) + window->Viewport->Flags |= ImGuiViewportFlags_NoDecoration; + + // 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; + + window->ViewportId = window->Viewport->ID; } -bool ImGui::NavMoveRequestButNoResultYet() +void ImGui::UpdatePlatformWindows() { ImGuiContext& g = *GImGui; - return g.NavMoveRequest && g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0; + IM_ASSERT(g.FrameCountEnded == g.FrameCount && "Forgot to call Render() or EndFrame() before UpdatePlatformWindows()?"); + IM_ASSERT(g.FrameCountPlatformEnded < g.FrameCount); + g.FrameCountPlatformEnded = g.FrameCount; + g.Viewports[0]->LastPos = g.Viewports[0]->Pos; + if (!(g.IO.ConfigFlags & 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]; + viewport->LastPos = viewport->Pos; + + // Destroy platform window if the viewport hasn't been submitted or if it is hosting a hidden window (the implicit Debug window will be registered its viewport then be disabled) + 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; + } + if (viewport->LastFrameActive < g.FrameCount) + continue; + + // New windows that appears directly in a new viewport won't always have a size on their frame + if (viewport->Size.x <= 0 || viewport->Size.y <= 0) + continue; + + // Update viewport flags + if (viewport->Window != NULL) + { + bool topmost = (viewport->Window->Flags & ImGuiWindowFlags_Tooltip) != 0; + bool no_task_bar_icon = (g.IO.ConfigFlags & ImGuiConfigFlags_ViewportsNoTaskBarIcons) != 0 || (viewport->Window->Flags & (ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup)) != 0; + viewport->Flags = topmost ? (viewport->Flags | ImGuiViewportFlags_TopMost) : (viewport->Flags & ~ImGuiViewportFlags_TopMost); + viewport->Flags = no_task_bar_icon ? (viewport->Flags | ImGuiViewportFlags_NoTaskBarIcon) : (viewport->Flags & ~ImGuiViewportFlags_NoTaskBarIcon); + } + + // Create window + bool is_new_window = (viewport->CreatedPlatformWindow == false); + if (is_new_window) + { + //IMGUI_DEBUG_LOG("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); + viewport->LastNameHash = 0; + viewport->RendererLastSize = viewport->Size; + viewport->CreatedPlatformWindow = true; + } + + // Apply Position and Size (from ImGui to Platform/Renderer back-ends) + if (!viewport->PlatformRequestMove) + g.PlatformIO.Platform_SetWindowPos(viewport, viewport->Pos); + if (!viewport->PlatformRequestResize) + g.PlatformIO.Platform_SetWindowSize(viewport, viewport->Size); + if (g.PlatformIO.Renderer_SetWindowSize && (viewport->RendererLastSize.x != viewport->Size.x || viewport->RendererLastSize.y != viewport->Size.y)) + g.PlatformIO.Renderer_SetWindowSize(viewport, viewport->Size); + viewport->RendererLastSize = 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 = ImHash(title_begin, (int)(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 + g.PlatformIO.Platform_SetWindowTitle(viewport, title_begin); + *title_end = title_end_backup_c; + viewport->LastNameHash = title_hash; + } + } + + // Update alpha + if (viewport->LastAlpha != viewport->Alpha && g.PlatformIO.Platform_SetWindowAlpha) + g.PlatformIO.Platform_SetWindowAlpha(viewport, viewport->Alpha); + viewport->LastAlpha = viewport->Alpha; + + // Show window. On startup ensure platform window don't get focus + if (is_new_window) + { + if (g.FrameCount < 3) // Give a few frames for the application to stabilize (nested contents may lead to viewport being created a few frames late) + viewport->Flags |= ImGuiViewportFlags_NoFocusOnAppearing; + g.PlatformIO.Platform_ShowWindow(viewport); + } + + // Even without focus, we assume the window becomes front-most. This is used by our platform z-order heuristic when io.MouseHoveredViewport is not available. + if (is_new_window && viewport->LastFrontMostStampCount != g.WindowsFrontMostStampCount) + viewport->LastFrontMostStampCount = ++g.WindowsFrontMostStampCount; + + // Clear request flags + viewport->PlatformRequestClose = viewport->PlatformRequestMove = viewport->PlatformRequestResize = false; + } + + // Update our implicit z-order knowledge of platform windows, which is used when the back-end cannot provide io.MouseHoveredViewport. + if (g.PlatformIO.Platform_GetWindowFocus != NULL) + { + ImGuiViewportP* focused_viewport = NULL; + for (int i = 0; i < g.Viewports.Size && focused_viewport == NULL; i++) + if (g.Viewports[i]->PlatformUserData != NULL || g.Viewports[i]->PlatformHandle != NULL || g.Viewports[i]->CreatedPlatformWindow) + if (g.PlatformIO.Platform_GetWindowFocus(g.Viewports[i])) + focused_viewport = g.Viewports[i]; + if (focused_viewport && g.PlatformLastFocusedViewport != focused_viewport->ID) + { + if (focused_viewport->LastFrontMostStampCount != g.WindowsFrontMostStampCount) + focused_viewport->LastFrontMostStampCount = ++g.WindowsFrontMostStampCount; + g.PlatformLastFocusedViewport = focused_viewport->ID; + } + } } -void ImGui::NavMoveRequestCancel() +static int ImGui::FindPlatformMonitorForPos(const ImVec2& pos) { ImGuiContext& g = *GImGui; - g.NavMoveRequest = false; - NavUpdateAnyRequestFlag(); + 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; } -void ImGui::NavMoveRequestForward(ImGuiDir move_dir, ImGuiDir clip_dir, const ImRect& bb_rel, ImGuiNavMoveFlags move_flags) +// 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 +static int ImGui::FindPlatformMonitorForRect(const ImRect& rect) { ImGuiContext& g = *GImGui; - IM_ASSERT(g.NavMoveRequestForward == ImGuiNavForward_None); - ImGui::NavMoveRequestCancel(); - g.NavMoveDir = move_dir; - g.NavMoveClipDir = clip_dir; - g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued; - g.NavMoveRequestFlags = move_flags; - g.NavWindow->NavRectRel[g.NavLayer] = bb_rel; + float surface_threshold = rect.GetWidth() * rect.GetHeight() * 0.5f; + 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; } -void ImGui::NavMoveRequestTryWrapping(ImGuiWindow* window, ImGuiNavMoveFlags move_flags) +// 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++) +// MyRenderFunction(platform_io.Viewports[i], my_args); +// for (int i = 1; i < platform_io.Viewports.Size; i++) +// MySwapBufferFunction(platform_io.Viewports[i], my_args); +// +void ImGui::RenderPlatformWindowsDefault(void* platform_render_arg, void* renderer_render_arg) { - ImGuiContext& g = *GImGui; - if (g.NavWindow != window || !NavMoveRequestButNoResultYet() || g.NavMoveRequestForward != ImGuiNavForward_None || g.NavLayer != 0) + if (!(ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable)) return; - IM_ASSERT(move_flags != 0); // No points calling this with no wrapping - ImRect bb_rel = window->NavRectRel[0]; - ImGuiDir clip_dir = g.NavMoveDir; - if (g.NavMoveDir == ImGuiDir_Left && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX))) - { - bb_rel.Min.x = bb_rel.Max.x = ImMax(window->SizeFull.x, window->SizeContents.x) - window->Scroll.x; - if (move_flags & ImGuiNavMoveFlags_WrapX) { bb_rel.TranslateY(-bb_rel.GetHeight()); clip_dir = ImGuiDir_Up; } - NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags); - } - if (g.NavMoveDir == ImGuiDir_Right && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX))) - { - bb_rel.Min.x = bb_rel.Max.x = -window->Scroll.x; - if (move_flags & ImGuiNavMoveFlags_WrapX) { bb_rel.TranslateY(+bb_rel.GetHeight()); clip_dir = ImGuiDir_Down; } - NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags); - } - if (g.NavMoveDir == ImGuiDir_Up && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY))) + // 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++) { - bb_rel.Min.y = bb_rel.Max.y = ImMax(window->SizeFull.y, window->SizeContents.y) - window->Scroll.y; - if (move_flags & ImGuiNavMoveFlags_WrapY) { bb_rel.TranslateX(-bb_rel.GetWidth()); clip_dir = ImGuiDir_Left; } - NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags); + ImGuiViewport* viewport = platform_io.Viewports[i]; + 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); } - if (g.NavMoveDir == ImGuiDir_Down && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY))) + for (int i = 1; i < platform_io.Viewports.Size; i++) { - bb_rel.Min.y = bb_rel.Max.y = -window->Scroll.y; - if (move_flags & ImGuiNavMoveFlags_WrapY) { bb_rel.TranslateX(+bb_rel.GetWidth()); clip_dir = ImGuiDir_Right; } - NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags); + ImGuiViewport* viewport = platform_io.Viewports[i]; + 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 void ImGui::NavSaveLastChildNavWindow(ImGuiWindow* nav_window) +void ImGui::DestroyPlatformWindow(ImGuiViewportP* viewport) { - ImGuiWindow* parent_window = nav_window; - while (parent_window && (parent_window->Flags & ImGuiWindowFlags_ChildWindow) != 0 && (parent_window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0) - parent_window = parent_window->ParentWindow; - if (parent_window && parent_window != nav_window) - parent_window->NavLastChildNavWindow = nav_window; + ImGuiContext& g = *GImGui; + if (viewport->CreatedPlatformWindow && g.PlatformIO.Renderer_DestroyWindow) + g.PlatformIO.Renderer_DestroyWindow(viewport); + if (viewport->CreatedPlatformWindow && g.PlatformIO.Platform_DestroyWindow) + g.PlatformIO.Platform_DestroyWindow(viewport); + IM_ASSERT(viewport->RendererUserData == NULL); + IM_ASSERT(viewport->PlatformUserData == NULL); + viewport->PlatformHandle = NULL; + viewport->RendererUserData = viewport->PlatformHandle = NULL; + viewport->CreatedPlatformWindow = false; } -// Call when we are expected to land on Layer 0 after FocusWindow() -static ImGuiWindow* ImGui::NavRestoreLastChildNavWindow(ImGuiWindow* window) +void ImGui::DestroyPlatformWindows() { - return window->NavLastChildNavWindow ? window->NavLastChildNavWindow : window; + // We call the destroy window on the main viewport (index 0) to give a chance to the back-end to clear any data + // have stored in e.g. PlatformUserData, RendererUserData. It can be convenient for the platform back-end code to + // store something in the main viewport, in order for e.g. the mouse handling code to work in a more generic manner. + // It is expected that the back-end can handle calls to Renderer_DestroyWindow/Platform_DestroyWindow without + // crashing if it doesn't have data stored. + ImGuiContext& g = *GImGui; + for (int i = 0; i < g.Viewports.Size; i++) + if (g.Viewports[i]->CreatedPlatformWindow) + DestroyPlatformWindow(g.Viewports[i]); } -static void NavRestoreLayer(int layer) +//----------------------------------------------------------------------------- +// [SECTION] KEYBOARD/GAMEPAD NAVIGATION +//----------------------------------------------------------------------------- + +ImGuiDir ImGetDirQuadrantFromDelta(float dx, float dy) { - ImGuiContext& g = *GImGui; - g.NavLayer = layer; - if (layer == 0) - g.NavWindow = ImGui::NavRestoreLastChildNavWindow(g.NavWindow); - if (layer == 0 && g.NavWindow->NavLastIds[0] != 0) - ImGui::SetNavIDWithRectRel(g.NavWindow->NavLastIds[0], layer, g.NavWindow->NavRectRel[0]); - else - ImGui::NavInitWindow(g.NavWindow, true); + if (ImFabs(dx) > ImFabs(dy)) + return (dx > 0.0f) ? ImGuiDir_Right : ImGuiDir_Left; + return (dy > 0.0f) ? ImGuiDir_Down : ImGuiDir_Up; } -static inline void ImGui::NavUpdateAnyRequestFlag() +static float inline NavScoreItemDistInterval(float a0, float a1, float b0, float b1) { - ImGuiContext& g = *GImGui; - g.NavAnyRequest = g.NavMoveRequest || g.NavInitRequest || (IMGUI_DEBUG_NAV_SCORING && g.NavWindow != NULL); - if (g.NavAnyRequest) - IM_ASSERT(g.NavWindow != NULL); + if (a1 < b0) + return a1 - b0; + if (b1 < a0) + return a0 - b1; + return 0.0f; } -// This needs to be called before we submit any widget (aka in or before Begin) -void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit) +static void inline NavClampRectToVisibleAreaForMoveDir(ImGuiDir move_dir, ImRect& r, const ImRect& clip_rect) { - ImGuiContext& g = *GImGui; - IM_ASSERT(window == g.NavWindow); - bool init_for_nav = false; - if (!(window->Flags & ImGuiWindowFlags_NoNavInputs)) - if (!(window->Flags & ImGuiWindowFlags_ChildWindow) || (window->Flags & ImGuiWindowFlags_Popup) || (window->NavLastIds[0] == 0) || force_reinit) - init_for_nav = true; - if (init_for_nav) + if (move_dir == ImGuiDir_Left || move_dir == ImGuiDir_Right) { - SetNavID(0, g.NavLayer); - g.NavInitRequest = true; - g.NavInitRequestFromMove = false; - g.NavInitResultId = 0; - g.NavInitResultRectRel = ImRect(); - NavUpdateAnyRequestFlag(); + r.Min.y = ImClamp(r.Min.y, clip_rect.Min.y, clip_rect.Max.y); + r.Max.y = ImClamp(r.Max.y, clip_rect.Min.y, clip_rect.Max.y); } else { - g.NavId = window->NavLastIds[0]; + r.Min.x = ImClamp(r.Min.x, clip_rect.Min.x, clip_rect.Max.x); + r.Max.x = ImClamp(r.Max.x, clip_rect.Min.x, clip_rect.Max.x); } } -static ImVec2 ImGui::NavCalcPreferredRefPos() +// Scoring function for directional navigation. Based on https://gist.github.com/rygorous/6981057 +static bool NavScoreItem(ImGuiNavMoveResult* result, ImRect cand) { ImGuiContext& g = *GImGui; - if (g.NavDisableHighlight || !g.NavDisableMouseHover || !g.NavWindow) - return ImFloor(g.IO.MousePos); - - // When navigation is active and mouse is disabled, decide on an arbitrary position around the bottom left of the currently navigated item - const ImRect& rect_rel = g.NavWindow->NavRectRel[g.NavLayer]; - ImVec2 pos = g.NavWindow->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())); - ImRect visible_rect = GetViewportRect(); - return ImFloor(ImClamp(pos, visible_rect.Min, visible_rect.Max)); // ImFloor() is important because non-integer mouse position application in back-end might be lossy and result in undesirable non-zero delta. -} + ImGuiWindow* window = g.CurrentWindow; + if (g.NavLayer != window->DC.NavLayerCurrent) + return false; -float ImGui::GetNavInputAmount(ImGuiNavInput n, ImGuiInputReadMode mode) -{ - ImGuiContext& g = *GImGui; - if (mode == ImGuiInputReadMode_Down) - return g.IO.NavInputs[n]; // Instant, read analog input (0.0f..1.0f, as provided by user) + const ImRect& curr = g.NavScoringRectScreen; // Current modified source rect (NB: we've applied Max.x = Min.x in NavUpdate() to inhibit the effect of having varied item width) + g.NavScoringCount++; - const float t = g.IO.NavInputsDownDuration[n]; - if (t < 0.0f && mode == ImGuiInputReadMode_Released) // Return 1.0f when just released, no repeat, ignore analog input. - return (g.IO.NavInputsDownDurationPrev[n] >= 0.0f ? 1.0f : 0.0f); - if (t < 0.0f) + // When entering through a NavFlattened border, we consider child window items as fully clipped for scoring + if (window->ParentWindow == g.NavWindow) + { + IM_ASSERT((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened); + if (!window->ClipRect.Contains(cand)) + return false; + cand.ClipWithFull(window->ClipRect); // This allows the scored item to not overlap other candidates in the parent window + } + + // We perform scoring on items bounding box clipped by the current clipping rectangle on the other axis (clipping on our movement axis would give us equal scores for all clipped items) + // For example, this ensure that items in one column are not reached when moving vertically from items in another column. + NavClampRectToVisibleAreaForMoveDir(g.NavMoveClipDir, cand, window->ClipRect); + + // Compute distance between boxes + // FIXME-NAV: Introducing biases for vertical navigation, needs to be removed. + float dbx = NavScoreItemDistInterval(cand.Min.x, cand.Max.x, curr.Min.x, curr.Max.x); + float dby = NavScoreItemDistInterval(ImLerp(cand.Min.y, cand.Max.y, 0.2f), ImLerp(cand.Min.y, cand.Max.y, 0.8f), ImLerp(curr.Min.y, curr.Max.y, 0.2f), ImLerp(curr.Min.y, curr.Max.y, 0.8f)); // Scale down on Y to keep using box-distance for vertically touching items + if (dby != 0.0f && dbx != 0.0f) + dbx = (dbx/1000.0f) + ((dbx > 0.0f) ? +1.0f : -1.0f); + float dist_box = ImFabs(dbx) + ImFabs(dby); + + // Compute distance between centers (this is off by a factor of 2, but we only compare center distances with each other so it doesn't matter) + float dcx = (cand.Min.x + cand.Max.x) - (curr.Min.x + curr.Max.x); + float dcy = (cand.Min.y + cand.Max.y) - (curr.Min.y + curr.Max.y); + float dist_center = ImFabs(dcx) + ImFabs(dcy); // L1 metric (need this for our connectedness guarantee) + + // Determine which quadrant of 'curr' our candidate item 'cand' lies in based on distance + ImGuiDir quadrant; + float dax = 0.0f, day = 0.0f, dist_axial = 0.0f; + if (dbx != 0.0f || dby != 0.0f) + { + // For non-overlapping boxes, use distance between boxes + dax = dbx; + day = dby; + dist_axial = dist_box; + quadrant = ImGetDirQuadrantFromDelta(dbx, dby); + } + else if (dcx != 0.0f || dcy != 0.0f) + { + // For overlapping boxes with different centers, use distance between centers + dax = dcx; + day = dcy; + dist_axial = dist_center; + quadrant = ImGetDirQuadrantFromDelta(dcx, dcy); + } + else + { + // Degenerate case: two overlapping buttons with same center, break ties arbitrarily (note that LastItemId here is really the _previous_ item order, but it doesn't matter) + quadrant = (window->DC.LastItemId < g.NavId) ? ImGuiDir_Left : ImGuiDir_Right; + } + +#if IMGUI_DEBUG_NAV_SCORING + char buf[128]; + if (ImGui::IsMouseHoveringRect(cand.Min, cand.Max)) + { + ImFormatString(buf, IM_ARRAYSIZE(buf), "dbox (%.2f,%.2f->%.4f)\ndcen (%.2f,%.2f->%.4f)\nd (%.2f,%.2f->%.4f)\nnav %c, quadrant %c", dbx, dby, dist_box, dcx, dcy, dist_center, dax, day, dist_axial, "WENS"[g.NavMoveDir], "WENS"[quadrant]); + ImDrawList* draw_list = ImGui::GetOverlayDrawList(window); + draw_list->AddRect(curr.Min, curr.Max, IM_COL32(255,200,0,100)); + draw_list->AddRect(cand.Min, cand.Max, IM_COL32(255,255,0,200)); + draw_list->AddRectFilled(cand.Max-ImVec2(4,4), cand.Max+ImGui::CalcTextSize(buf)+ImVec2(4,4), IM_COL32(40,0,0,150)); + draw_list->AddText(g.IO.FontDefault, 13.0f, cand.Max, ~0U, buf); + } + else if (g.IO.KeyCtrl) // Hold to preview score in matching quadrant. Press C to rotate. + { + if (IsKeyPressedMap(ImGuiKey_C)) { g.NavMoveDirLast = (ImGuiDir)((g.NavMoveDirLast + 1) & 3); g.IO.KeysDownDuration[g.IO.KeyMap[ImGuiKey_C]] = 0.01f; } + if (quadrant == g.NavMoveDir) + { + ImFormatString(buf, IM_ARRAYSIZE(buf), "%.0f/%.0f", dist_box, dist_center); + ImDrawList* draw_list = ImGui::GetOverlayDrawList(window); + draw_list->AddRectFilled(cand.Min, cand.Max, IM_COL32(255, 0, 0, 200)); + draw_list->AddText(g.IO.FontDefault, 13.0f, cand.Min, IM_COL32(255, 255, 255, 255), buf); + } + } + #endif + + // Is it in the quadrant we're interesting in moving to? + bool new_best = false; + if (quadrant == g.NavMoveDir) + { + // Does it beat the current best candidate? + if (dist_box < result->DistBox) + { + result->DistBox = dist_box; + result->DistCenter = dist_center; + return true; + } + if (dist_box == result->DistBox) + { + // Try using distance between center points to break ties + if (dist_center < result->DistCenter) + { + result->DistCenter = dist_center; + new_best = true; + } + else if (dist_center == result->DistCenter) + { + // Still tied! we need to be extra-careful to make sure everything gets linked properly. We consistently break ties by symbolically moving "later" items + // (with higher index) to the right/downwards by an infinitesimal amount since we the current "best" button already (so it must have a lower index), + // this is fairly easy. This rule ensures that all buttons with dx==dy==0 will end up being linked in order of appearance along the x axis. + if (((g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) ? dby : dbx) < 0.0f) // moving bj to the right/down decreases distance + new_best = true; + } + } + } + + // Axial check: if 'curr' has no link at all in some direction and 'cand' lies roughly in that direction, add a tentative link. This will only be kept if no "real" matches + // are found, so it only augments the graph produced by the above method using extra links. (important, since it doesn't guarantee strong connectedness) + // This is just to avoid buttons having no links in a particular direction when there's a suitable neighbor. you get good graphs without this too. + // 2017/09/29: FIXME: This now currently only enabled inside menu bars, ideally we'd disable it everywhere. Menus in particular need to catch failure. For general navigation it feels awkward. + // Disabling it may lead to disconnected graphs when nodes are very spaced out on different axis. Perhaps consider offering this as an option? + if (result->DistBox == FLT_MAX && dist_axial < result->DistAxial) // Check axial match + if (g.NavLayer == 1 && !(g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu)) + if ((g.NavMoveDir == ImGuiDir_Left && dax < 0.0f) || (g.NavMoveDir == ImGuiDir_Right && dax > 0.0f) || (g.NavMoveDir == ImGuiDir_Up && day < 0.0f) || (g.NavMoveDir == ImGuiDir_Down && day > 0.0f)) + { + result->DistAxial = dist_axial; + new_best = true; + } + + return new_best; +} + +// We get there when either NavId == id, or when g.NavAnyRequest is set (which is updated by NavUpdateAnyRequestFlag above) +static void ImGui::NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, const ImGuiID id) +{ + ImGuiContext& g = *GImGui; + //if (!g.IO.NavActive) // [2017/10/06] Removed this possibly redundant test but I am not sure of all the side-effects yet. Some of the feature here will need to work regardless of using a _NoNavInputs flag. + // return; + + const ImGuiItemFlags item_flags = window->DC.ItemFlags; + const ImRect nav_bb_rel(nav_bb.Min - window->Pos, nav_bb.Max - window->Pos); + + // Process Init Request + if (g.NavInitRequest && g.NavLayer == window->DC.NavLayerCurrent) + { + // Even if 'ImGuiItemFlags_NoNavDefaultFocus' is on (typically collapse/close button) we record the first ResultId so they can be used as a fallback + if (!(item_flags & ImGuiItemFlags_NoNavDefaultFocus) || g.NavInitResultId == 0) + { + g.NavInitResultId = id; + g.NavInitResultRectRel = nav_bb_rel; + } + if (!(item_flags & ImGuiItemFlags_NoNavDefaultFocus)) + { + g.NavInitRequest = false; // Found a match, clear request + NavUpdateAnyRequestFlag(); + } + } + + // Process Move Request (scoring for navigation) + // FIXME-NAV: Consider policy for double scoring (scoring from NavScoringRectScreen + scoring from a rect wrapped according to current wrapping policy) + if ((g.NavId != id || (g.NavMoveRequestFlags & ImGuiNavMoveFlags_AllowCurrentNavId)) && !(item_flags & ImGuiItemFlags_NoNav)) + { + ImGuiNavMoveResult* result = (window == g.NavWindow) ? &g.NavMoveResultLocal : &g.NavMoveResultOther; +#if IMGUI_DEBUG_NAV_SCORING + // [DEBUG] Score all items in NavWindow at all times + if (!g.NavMoveRequest) + g.NavMoveDir = g.NavMoveDirLast; + bool new_best = NavScoreItem(result, nav_bb) && g.NavMoveRequest; +#else + bool new_best = g.NavMoveRequest && NavScoreItem(result, nav_bb); +#endif + if (new_best) + { + result->ID = id; + result->Window = window; + result->RectRel = nav_bb_rel; + } + + const float VISIBLE_RATIO = 0.70f; + if ((g.NavMoveRequestFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet) && window->ClipRect.Overlaps(nav_bb)) + if (ImClamp(nav_bb.Max.y, window->ClipRect.Min.y, window->ClipRect.Max.y) - ImClamp(nav_bb.Min.y, window->ClipRect.Min.y, window->ClipRect.Max.y) >= (nav_bb.Max.y - nav_bb.Min.y) * VISIBLE_RATIO) + if (NavScoreItem(&g.NavMoveResultLocalVisibleSet, nav_bb)) + { + result = &g.NavMoveResultLocalVisibleSet; + result->ID = id; + result->Window = window; + result->RectRel = nav_bb_rel; + } + } + + // Update window-relative bounding box of navigated item + if (g.NavId == id) + { + g.NavWindow = window; // Always refresh g.NavWindow, because some operations such as FocusItem() don't have a window. + g.NavLayer = window->DC.NavLayerCurrent; + g.NavIdIsAlive = true; + g.NavIdTabCounter = window->FocusIdxTabCounter; + window->NavRectRel[window->DC.NavLayerCurrent] = nav_bb_rel; // Store item bounding box (relative to window position) + } +} + +bool ImGui::NavMoveRequestButNoResultYet() +{ + ImGuiContext& g = *GImGui; + return g.NavMoveRequest && g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0; +} + +void ImGui::NavMoveRequestCancel() +{ + ImGuiContext& g = *GImGui; + g.NavMoveRequest = false; + NavUpdateAnyRequestFlag(); +} + +void ImGui::NavMoveRequestForward(ImGuiDir move_dir, ImGuiDir clip_dir, const ImRect& bb_rel, ImGuiNavMoveFlags move_flags) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.NavMoveRequestForward == ImGuiNavForward_None); + ImGui::NavMoveRequestCancel(); + g.NavMoveDir = move_dir; + g.NavMoveClipDir = clip_dir; + g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued; + g.NavMoveRequestFlags = move_flags; + g.NavWindow->NavRectRel[g.NavLayer] = bb_rel; +} + +void ImGui::NavMoveRequestTryWrapping(ImGuiWindow* window, ImGuiNavMoveFlags move_flags) +{ + ImGuiContext& g = *GImGui; + if (g.NavWindow != window || !NavMoveRequestButNoResultYet() || g.NavMoveRequestForward != ImGuiNavForward_None || g.NavLayer != 0) + return; + IM_ASSERT(move_flags != 0); // No points calling this with no wrapping + ImRect bb_rel = window->NavRectRel[0]; + + ImGuiDir clip_dir = g.NavMoveDir; + if (g.NavMoveDir == ImGuiDir_Left && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX))) + { + bb_rel.Min.x = bb_rel.Max.x = ImMax(window->SizeFull.x, window->SizeContents.x) - window->Scroll.x; + if (move_flags & ImGuiNavMoveFlags_WrapX) { bb_rel.TranslateY(-bb_rel.GetHeight()); clip_dir = ImGuiDir_Up; } + NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags); + } + if (g.NavMoveDir == ImGuiDir_Right && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX))) + { + bb_rel.Min.x = bb_rel.Max.x = -window->Scroll.x; + if (move_flags & ImGuiNavMoveFlags_WrapX) { bb_rel.TranslateY(+bb_rel.GetHeight()); clip_dir = ImGuiDir_Down; } + NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags); + } + if (g.NavMoveDir == ImGuiDir_Up && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY))) + { + bb_rel.Min.y = bb_rel.Max.y = ImMax(window->SizeFull.y, window->SizeContents.y) - window->Scroll.y; + if (move_flags & ImGuiNavMoveFlags_WrapY) { bb_rel.TranslateX(-bb_rel.GetWidth()); clip_dir = ImGuiDir_Left; } + NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags); + } + if (g.NavMoveDir == ImGuiDir_Down && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY))) + { + bb_rel.Min.y = bb_rel.Max.y = -window->Scroll.y; + if (move_flags & ImGuiNavMoveFlags_WrapY) { bb_rel.TranslateX(+bb_rel.GetWidth()); clip_dir = ImGuiDir_Right; } + NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags); + } +} + +static void ImGui::NavSaveLastChildNavWindow(ImGuiWindow* nav_window) +{ + ImGuiWindow* parent_window = nav_window; + while (parent_window && (parent_window->Flags & ImGuiWindowFlags_ChildWindow) != 0 && (parent_window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0) + parent_window = parent_window->ParentWindow; + if (parent_window && parent_window != nav_window) + parent_window->NavLastChildNavWindow = nav_window; +} + +// Call when we are expected to land on Layer 0 after FocusWindow() +static ImGuiWindow* ImGui::NavRestoreLastChildNavWindow(ImGuiWindow* window) +{ + return window->NavLastChildNavWindow ? window->NavLastChildNavWindow : window; +} + +static void NavRestoreLayer(int layer) +{ + ImGuiContext& g = *GImGui; + g.NavLayer = layer; + if (layer == 0) + g.NavWindow = ImGui::NavRestoreLastChildNavWindow(g.NavWindow); + if (g.NavWindow->NavLastIds[layer] != 0) + ImGui::SetNavIDWithRectRel(g.NavWindow->NavLastIds[layer], layer, g.NavWindow->NavRectRel[layer]); + else + ImGui::NavInitWindow(g.NavWindow, true); +} + +static inline void ImGui::NavUpdateAnyRequestFlag() +{ + ImGuiContext& g = *GImGui; + g.NavAnyRequest = g.NavMoveRequest || g.NavInitRequest || (IMGUI_DEBUG_NAV_SCORING && g.NavWindow != NULL); + if (g.NavAnyRequest) + IM_ASSERT(g.NavWindow != NULL); +} + +// This needs to be called before we submit any widget (aka in or before Begin) +void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(window == g.NavWindow); + bool init_for_nav = false; + if (!(window->Flags & ImGuiWindowFlags_NoNavInputs)) + if (!(window->Flags & ImGuiWindowFlags_ChildWindow) || (window->Flags & ImGuiWindowFlags_Popup) || (window->NavLastIds[0] == 0) || force_reinit) + init_for_nav = true; + if (init_for_nav) + { + SetNavID(0, g.NavLayer); + g.NavInitRequest = true; + g.NavInitRequestFromMove = false; + g.NavInitResultId = 0; + g.NavInitResultRectRel = ImRect(); + NavUpdateAnyRequestFlag(); + } + else + { + g.NavId = window->NavLastIds[0]; + } +} + +static ImVec2 ImGui::NavCalcPreferredRefPos() +{ + ImGuiContext& g = *GImGui; + if (g.NavDisableHighlight || !g.NavDisableMouseHover || !g.NavWindow) + { + IM_ASSERT(ImGui::IsMousePosValid()); // This will probably trigger at some point, please share your repro! + return ImFloor(g.IO.MousePos); + } + + // When navigation is active and mouse is disabled, decide on an arbitrary position around the bottom left of the currently navigated item + const ImRect& rect_rel = g.NavWindow->NavRectRel[g.NavLayer]; + ImVec2 pos = g.NavWindow->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())); + ImRect visible_rect = g.NavWindow->Viewport->GetRect(); + return ImFloor(ImClamp(pos, visible_rect.Min, visible_rect.Max)); // ImFloor() is important because non-integer mouse position application in back-end might be lossy and result in undesirable non-zero delta. +} + +float ImGui::GetNavInputAmount(ImGuiNavInput n, ImGuiInputReadMode mode) +{ + ImGuiContext& g = *GImGui; + if (mode == ImGuiInputReadMode_Down) + return g.IO.NavInputs[n]; // Instant, read analog input (0.0f..1.0f, as provided by user) + + const float t = g.IO.NavInputsDownDuration[n]; + if (t < 0.0f && mode == ImGuiInputReadMode_Released) // Return 1.0f when just released, no repeat, ignore analog input. + return (g.IO.NavInputsDownDurationPrev[n] >= 0.0f ? 1.0f : 0.0f); + if (t < 0.0f) return 0.0f; if (mode == ImGuiInputReadMode_Pressed) // Return 1.0f when just pressed, no repeat, ignore analog input. return (t == 0.0f) ? 1.0f : 0.0f; @@ -7032,1220 +8303,3951 @@ float ImGui::GetNavInputAmount(ImGuiNavInput n, ImGuiInputReadMode mode) return 0.0f; } -ImVec2 ImGui::GetNavInputAmount2d(ImGuiNavDirSourceFlags dir_sources, ImGuiInputReadMode mode, float slow_factor, float fast_factor) +ImVec2 ImGui::GetNavInputAmount2d(ImGuiNavDirSourceFlags dir_sources, ImGuiInputReadMode mode, float slow_factor, float fast_factor) +{ + ImVec2 delta(0.0f, 0.0f); + if (dir_sources & ImGuiNavDirSourceFlags_Keyboard) + delta += ImVec2(GetNavInputAmount(ImGuiNavInput_KeyRight_, mode) - GetNavInputAmount(ImGuiNavInput_KeyLeft_, mode), GetNavInputAmount(ImGuiNavInput_KeyDown_, mode) - GetNavInputAmount(ImGuiNavInput_KeyUp_, mode)); + if (dir_sources & ImGuiNavDirSourceFlags_PadDPad) + delta += ImVec2(GetNavInputAmount(ImGuiNavInput_DpadRight, mode) - GetNavInputAmount(ImGuiNavInput_DpadLeft, mode), GetNavInputAmount(ImGuiNavInput_DpadDown, mode) - GetNavInputAmount(ImGuiNavInput_DpadUp, mode)); + if (dir_sources & ImGuiNavDirSourceFlags_PadLStick) + delta += ImVec2(GetNavInputAmount(ImGuiNavInput_LStickRight, mode) - GetNavInputAmount(ImGuiNavInput_LStickLeft, mode), GetNavInputAmount(ImGuiNavInput_LStickDown, mode) - GetNavInputAmount(ImGuiNavInput_LStickUp, mode)); + if (slow_factor != 0.0f && IsNavInputDown(ImGuiNavInput_TweakSlow)) + delta *= slow_factor; + if (fast_factor != 0.0f && IsNavInputDown(ImGuiNavInput_TweakFast)) + delta *= fast_factor; + return delta; +} + +// Scroll to keep newly navigated item fully into view +// NB: We modify rect_rel by the amount we scrolled for, so it is immediately updated. +static void NavScrollToBringItemIntoView(ImGuiWindow* window, const ImRect& item_rect) +{ + ImRect window_rect(window->InnerMainRect.Min - ImVec2(1, 1), window->InnerMainRect.Max + ImVec2(1, 1)); + //GetOverlayDrawList(window)->AddRect(window_rect.Min, window_rect.Max, IM_COL32_WHITE); // [DEBUG] + if (window_rect.Contains(item_rect)) + return; + + ImGuiContext& g = *GImGui; + if (window->ScrollbarX && item_rect.Min.x < window_rect.Min.x) + { + window->ScrollTarget.x = item_rect.Min.x - window->Pos.x + window->Scroll.x - g.Style.ItemSpacing.x; + window->ScrollTargetCenterRatio.x = 0.0f; + } + else if (window->ScrollbarX && item_rect.Max.x >= window_rect.Max.x) + { + window->ScrollTarget.x = item_rect.Max.x - window->Pos.x + window->Scroll.x + g.Style.ItemSpacing.x; + window->ScrollTargetCenterRatio.x = 1.0f; + } + if (item_rect.Min.y < window_rect.Min.y) + { + window->ScrollTarget.y = item_rect.Min.y - window->Pos.y + window->Scroll.y - g.Style.ItemSpacing.y; + window->ScrollTargetCenterRatio.y = 0.0f; + } + else if (item_rect.Max.y >= window_rect.Max.y) + { + window->ScrollTarget.y = item_rect.Max.y - window->Pos.y + window->Scroll.y + g.Style.ItemSpacing.y; + window->ScrollTargetCenterRatio.y = 1.0f; + } +} + +static void ImGui::NavUpdate() +{ + ImGuiContext& g = *GImGui; + g.IO.WantSetMousePos = false; +#if 0 + if (g.NavScoringCount > 0) printf("[%05d] NavScoringCount %d for '%s' layer %d (Init:%d, Move:%d)\n", g.FrameCount, g.NavScoringCount, g.NavWindow ? g.NavWindow->Name : "NULL", g.NavLayer, g.NavInitRequest || g.NavInitResultId != 0, g.NavMoveRequest); +#endif + + // Set input source as Gamepad when buttons are pressed before we map Keyboard (some features differs when used with Gamepad vs Keyboard) + bool nav_keyboard_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0; + bool nav_gamepad_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (g.IO.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; + if (nav_gamepad_active) + if (g.IO.NavInputs[ImGuiNavInput_Activate] > 0.0f || g.IO.NavInputs[ImGuiNavInput_Input] > 0.0f || g.IO.NavInputs[ImGuiNavInput_Cancel] > 0.0f || g.IO.NavInputs[ImGuiNavInput_Menu] > 0.0f) + g.NavInputSource = ImGuiInputSource_NavGamepad; + + // Update Keyboard->Nav inputs mapping + if (nav_keyboard_active) + { + #define NAV_MAP_KEY(_KEY, _NAV_INPUT) if (IsKeyDown(g.IO.KeyMap[_KEY])) { g.IO.NavInputs[_NAV_INPUT] = 1.0f; g.NavInputSource = ImGuiInputSource_NavKeyboard; } + NAV_MAP_KEY(ImGuiKey_Space, ImGuiNavInput_Activate ); + NAV_MAP_KEY(ImGuiKey_Enter, ImGuiNavInput_Input ); + NAV_MAP_KEY(ImGuiKey_Escape, ImGuiNavInput_Cancel ); + NAV_MAP_KEY(ImGuiKey_LeftArrow, ImGuiNavInput_KeyLeft_ ); + NAV_MAP_KEY(ImGuiKey_RightArrow,ImGuiNavInput_KeyRight_); + NAV_MAP_KEY(ImGuiKey_UpArrow, ImGuiNavInput_KeyUp_ ); + NAV_MAP_KEY(ImGuiKey_DownArrow, ImGuiNavInput_KeyDown_ ); + if (g.IO.KeyCtrl) g.IO.NavInputs[ImGuiNavInput_TweakSlow] = 1.0f; + if (g.IO.KeyShift) g.IO.NavInputs[ImGuiNavInput_TweakFast] = 1.0f; + if (g.IO.KeyAlt) g.IO.NavInputs[ImGuiNavInput_KeyMenu_] = 1.0f; + #undef NAV_MAP_KEY + } + memcpy(g.IO.NavInputsDownDurationPrev, g.IO.NavInputsDownDuration, sizeof(g.IO.NavInputsDownDuration)); + for (int i = 0; i < IM_ARRAYSIZE(g.IO.NavInputs); i++) + g.IO.NavInputsDownDuration[i] = (g.IO.NavInputs[i] > 0.0f) ? (g.IO.NavInputsDownDuration[i] < 0.0f ? 0.0f : g.IO.NavInputsDownDuration[i] + g.IO.DeltaTime) : -1.0f; + + // Process navigation init request (select first/default focus) + if (g.NavInitResultId != 0 && (!g.NavDisableHighlight || g.NavInitRequestFromMove)) + { + // Apply result from previous navigation init request (will typically select the first item, unless SetItemDefaultFocus() has been called) + IM_ASSERT(g.NavWindow); + if (g.NavInitRequestFromMove) + SetNavIDWithRectRel(g.NavInitResultId, g.NavLayer, g.NavInitResultRectRel); + else + SetNavID(g.NavInitResultId, g.NavLayer); + g.NavWindow->NavRectRel[g.NavLayer] = g.NavInitResultRectRel; + } + g.NavInitRequest = false; + g.NavInitRequestFromMove = false; + g.NavInitResultId = 0; + g.NavJustMovedToId = 0; + + // Process navigation move request + if (g.NavMoveRequest && (g.NavMoveResultLocal.ID != 0 || g.NavMoveResultOther.ID != 0)) + NavUpdateMoveResult(); + + // When a forwarded move request failed, we restore the highlight that we disabled during the forward frame + if (g.NavMoveRequestForward == ImGuiNavForward_ForwardActive) + { + IM_ASSERT(g.NavMoveRequest); + if (g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0) + g.NavDisableHighlight = false; + g.NavMoveRequestForward = ImGuiNavForward_None; + } + + // Apply application mouse position movement, after we had a chance to process move request result. + if (g.NavMousePosDirty && g.NavIdIsAlive) + { + // Set mouse position given our knowledge of the navigated item position from last frame + if ((g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos) && (g.IO.BackendFlags & ImGuiBackendFlags_HasSetMousePos)) + { + if (!g.NavDisableHighlight && g.NavDisableMouseHover && g.NavWindow) + { + g.IO.MousePos = g.IO.MousePosPrev = NavCalcPreferredRefPos(); + g.IO.WantSetMousePos = true; + } + } + g.NavMousePosDirty = false; + } + g.NavIdIsAlive = false; + g.NavJustTabbedId = 0; + IM_ASSERT(g.NavLayer == 0 || g.NavLayer == 1); + + // Store our return window (for returning from Layer 1 to Layer 0) and clear it as soon as we step back in our own Layer 0 + if (g.NavWindow) + NavSaveLastChildNavWindow(g.NavWindow); + if (g.NavWindow && g.NavWindow->NavLastChildNavWindow != NULL && g.NavLayer == 0) + g.NavWindow->NavLastChildNavWindow = NULL; + + // Update CTRL+TAB and Windowing features (hold Square to move/resize/etc.) + NavUpdateWindowing(); + + // Set output flags for user application + g.IO.NavActive = (nav_keyboard_active || nav_gamepad_active) && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs); + g.IO.NavVisible = (g.IO.NavActive && g.NavId != 0 && !g.NavDisableHighlight) || (g.NavWindowingTarget != NULL) || g.NavInitRequest; + + // Process NavCancel input (to close a popup, get back to parent, clear focus) + if (IsNavInputPressed(ImGuiNavInput_Cancel, ImGuiInputReadMode_Pressed)) + { + if (g.ActiveId != 0) + { + ClearActiveID(); + } + else if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow) && !(g.NavWindow->Flags & ImGuiWindowFlags_Popup) && g.NavWindow->ParentWindow && g.NavWindow != g.NavWindow->RootWindowDockStop) + { + // Exit child window + ImGuiWindow* child_window = g.NavWindow; + ImGuiWindow* parent_window = g.NavWindow->ParentWindow; + IM_ASSERT(child_window->ChildId != 0); + FocusWindow(parent_window); + SetNavID(child_window->ChildId, 0); + g.NavIdIsAlive = false; + if (g.NavDisableMouseHover) + g.NavMousePosDirty = true; + } + else if (g.OpenPopupStack.Size > 0) + { + // Close open popup/menu + if (!(g.OpenPopupStack.back().Window->Flags & ImGuiWindowFlags_Modal)) + ClosePopupToLevel(g.OpenPopupStack.Size - 1); + } + else if (g.NavLayer != 0) + { + // Leave the "menu" layer + NavRestoreLayer(0); + } + else + { + // Clear NavLastId for popups but keep it for regular child window so we can leave one and come back where we were + if (g.NavWindow && ((g.NavWindow->Flags & ImGuiWindowFlags_Popup) || !(g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow))) + g.NavWindow->NavLastIds[0] = 0; + g.NavId = 0; + } + } + + // Process manual activation request + g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavInputId = 0; + if (g.NavId != 0 && !g.NavDisableHighlight && !g.NavWindowingTarget && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) + { + bool activate_down = IsNavInputDown(ImGuiNavInput_Activate); + bool activate_pressed = activate_down && IsNavInputPressed(ImGuiNavInput_Activate, ImGuiInputReadMode_Pressed); + if (g.ActiveId == 0 && activate_pressed) + g.NavActivateId = g.NavId; + if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_down) + g.NavActivateDownId = g.NavId; + if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_pressed) + g.NavActivatePressedId = g.NavId; + if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && IsNavInputPressed(ImGuiNavInput_Input, ImGuiInputReadMode_Pressed)) + g.NavInputId = g.NavId; + } + if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) + g.NavDisableHighlight = true; + if (g.NavActivateId != 0) + IM_ASSERT(g.NavActivateDownId == g.NavActivateId); + g.NavMoveRequest = false; + + // Process programmatic activation request + if (g.NavNextActivateId != 0) + g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavInputId = g.NavNextActivateId; + g.NavNextActivateId = 0; + + // Initiate directional inputs request + const int allowed_dir_flags = (g.ActiveId == 0) ? ~0 : g.ActiveIdAllowNavDirFlags; + if (g.NavMoveRequestForward == ImGuiNavForward_None) + { + g.NavMoveDir = ImGuiDir_None; + g.NavMoveRequestFlags = 0; + if (g.NavWindow && !g.NavWindowingTarget && allowed_dir_flags && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) + { + if ((allowed_dir_flags & (1<Flags & ImGuiWindowFlags_NoNavInputs) && !g.NavWindowingTarget) + { + // *Fallback* manual-scroll with Nav directional keys when window has no navigable item + ImGuiWindow* window = g.NavWindow; + const float scroll_speed = ImFloor(window->CalcFontSize() * 100 * g.IO.DeltaTime + 0.5f); // We need round the scrolling speed because sub-pixel scroll isn't reliably supported. + if (window->DC.NavLayerActiveMask == 0x00 && window->DC.NavHasScroll && g.NavMoveRequest) + { + if (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) + SetWindowScrollX(window, ImFloor(window->Scroll.x + ((g.NavMoveDir == ImGuiDir_Left) ? -1.0f : +1.0f) * scroll_speed)); + if (g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) + SetWindowScrollY(window, ImFloor(window->Scroll.y + ((g.NavMoveDir == ImGuiDir_Up) ? -1.0f : +1.0f) * scroll_speed)); + } + + // *Normal* Manual scroll with NavScrollXXX keys + // Next movement request will clamp the NavId reference rectangle to the visible area, so navigation will resume within those bounds. + ImVec2 scroll_dir = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadLStick, ImGuiInputReadMode_Down, 1.0f/10.0f, 10.0f); + if (scroll_dir.x != 0.0f && window->ScrollbarX) + { + SetWindowScrollX(window, ImFloor(window->Scroll.x + scroll_dir.x * scroll_speed)); + g.NavMoveFromClampedRefRect = true; + } + if (scroll_dir.y != 0.0f) + { + SetWindowScrollY(window, ImFloor(window->Scroll.y + scroll_dir.y * scroll_speed)); + g.NavMoveFromClampedRefRect = true; + } + } + + // Reset search results + g.NavMoveResultLocal.Clear(); + g.NavMoveResultLocalVisibleSet.Clear(); + g.NavMoveResultOther.Clear(); + + // When we have manually scrolled (without using navigation) and NavId becomes out of bounds, we project its bounding box to the visible area to restart navigation within visible items + if (g.NavMoveRequest && g.NavMoveFromClampedRefRect && g.NavLayer == 0) + { + ImGuiWindow* window = g.NavWindow; + ImRect window_rect_rel(window->InnerMainRect.Min - window->Pos - ImVec2(1,1), window->InnerMainRect.Max - window->Pos + ImVec2(1,1)); + if (!window_rect_rel.Contains(window->NavRectRel[g.NavLayer])) + { + float pad = window->CalcFontSize() * 0.5f; + window_rect_rel.Expand(ImVec2(-ImMin(window_rect_rel.GetWidth(), pad), -ImMin(window_rect_rel.GetHeight(), pad))); // Terrible approximation for the intent of starting navigation from first fully visible item + window->NavRectRel[g.NavLayer].ClipWith(window_rect_rel); + g.NavId = 0; + } + g.NavMoveFromClampedRefRect = false; + } + + // For scoring we use a single segment on the left side our current item bounding box (not touching the edge to avoid box overlap with zero-spaced items) + ImRect nav_rect_rel = (g.NavWindow && !g.NavWindow->NavRectRel[g.NavLayer].IsInverted()) ? g.NavWindow->NavRectRel[g.NavLayer] : ImRect(0,0,0,0); + g.NavScoringRectScreen = g.NavWindow ? ImRect(g.NavWindow->Pos + nav_rect_rel.Min, g.NavWindow->Pos + nav_rect_rel.Max) : ImRect(0,0,0,0); + g.NavScoringRectScreen.TranslateY(nav_scoring_rect_offset_y); + g.NavScoringRectScreen.Min.x = ImMin(g.NavScoringRectScreen.Min.x + 1.0f, g.NavScoringRectScreen.Max.x); + g.NavScoringRectScreen.Max.x = g.NavScoringRectScreen.Min.x; + IM_ASSERT(!g.NavScoringRectScreen.IsInverted()); // Ensure if we have a finite, non-inverted bounding box here will allows us to remove extraneous ImFabs() calls in NavScoreItem(). + //g.OverlayDrawList.AddRect(g.NavScoringRectScreen.Min, g.NavScoringRectScreen.Max, IM_COL32(255,200,0,255)); // [DEBUG] + g.NavScoringCount = 0; +#if IMGUI_DEBUG_NAV_RECTS + if (g.NavWindow) { for (int layer = 0; layer < 2; layer++) GetOverlayDrawList(g.NavWindow)->AddRect(g.NavWindow->Pos + g.NavWindow->NavRectRel[layer].Min, g.NavWindow->Pos + g.NavWindow->NavRectRel[layer].Max, IM_COL32(255,200,0,255)); } // [DEBUG] + if (g.NavWindow) { ImU32 col = (g.NavWindow->HiddenFrames == 0) ? IM_COL32(255,0,255,255) : IM_COL32(255,0,0,255); ImVec2 p = NavCalcPreferredRefPos(NULL); char buf[32]; ImFormatString(buf, 32, "%d", g.NavLayer); GetOverlayDrawList(g.NavWindow)->AddCircleFilled(p, 3.0f, col); GetOverlayDrawList(g.NavWindow)->AddText(NULL, 13.0f, p + ImVec2(8,-4), col, buf); } +#endif +} + +static void ImGui::NavUpdateMoveResult() +{ + // Select which result to use + ImGuiContext& g = *GImGui; + ImGuiNavMoveResult* result = (g.NavMoveResultLocal.ID != 0) ? &g.NavMoveResultLocal : &g.NavMoveResultOther; + + // PageUp/PageDown behavior first jumps to the bottom/top mostly visible item, _otherwise_ use the result from the previous/next page. + if (g.NavMoveRequestFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet) + if (g.NavMoveResultLocalVisibleSet.ID != 0 && g.NavMoveResultLocalVisibleSet.ID != g.NavId) + result = &g.NavMoveResultLocalVisibleSet; + + // Maybe entering a flattened child from the outside? In this case solve the tie using the regular scoring rules. + if (result != &g.NavMoveResultOther && g.NavMoveResultOther.ID != 0 && g.NavMoveResultOther.Window->ParentWindow == g.NavWindow) + if ((g.NavMoveResultOther.DistBox < result->DistBox) || (g.NavMoveResultOther.DistBox == result->DistBox && g.NavMoveResultOther.DistCenter < result->DistCenter)) + result = &g.NavMoveResultOther; + IM_ASSERT(g.NavWindow && result->Window); + + // Scroll to keep newly navigated item fully into view. + if (g.NavLayer == 0) + { + ImRect rect_abs = ImRect(result->RectRel.Min + result->Window->Pos, result->RectRel.Max + result->Window->Pos); + NavScrollToBringItemIntoView(result->Window, rect_abs); + + // Estimate upcoming scroll so we can offset our result position so mouse position can be applied immediately after in NavUpdate() + ImVec2 next_scroll = CalcNextScrollFromScrollTargetAndClamp(result->Window, false); + ImVec2 delta_scroll = result->Window->Scroll - next_scroll; + result->RectRel.Translate(delta_scroll); + + // Also scroll parent window to keep us into view if necessary (we could/should technically recurse back the whole the parent hierarchy). + if (result->Window->Flags & ImGuiWindowFlags_ChildWindow) + NavScrollToBringItemIntoView(result->Window->ParentWindow, ImRect(rect_abs.Min + delta_scroll, rect_abs.Max + delta_scroll)); + } + + // Apply result from previous frame navigation directional move request + ClearActiveID(); + g.NavWindow = result->Window; + SetNavIDWithRectRel(result->ID, g.NavLayer, result->RectRel); + g.NavJustMovedToId = result->ID; + g.NavMoveFromClampedRefRect = false; +} + +static float ImGui::NavUpdatePageUpPageDown(int allowed_dir_flags) +{ + ImGuiContext& g = *GImGui; + if (g.NavMoveDir == ImGuiDir_None && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.NavWindowingTarget && g.NavLayer == 0) + { + ImGuiWindow* window = g.NavWindow; + bool page_up_held = IsKeyDown(g.IO.KeyMap[ImGuiKey_PageUp]) && (allowed_dir_flags & (1 << ImGuiDir_Up)); + bool page_down_held = IsKeyDown(g.IO.KeyMap[ImGuiKey_PageDown]) && (allowed_dir_flags & (1 << ImGuiDir_Down)); + if ((page_up_held && !page_down_held) || (page_down_held && !page_up_held)) + { + if (window->DC.NavLayerActiveMask == 0x00 && window->DC.NavHasScroll) + { + // Fallback manual-scroll when window has no navigable item + if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageUp], true)) + SetWindowScrollY(window, window->Scroll.y - window->InnerClipRect.GetHeight()); + else if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageDown], true)) + SetWindowScrollY(window, window->Scroll.y + window->InnerClipRect.GetHeight()); + } + else + { + const ImRect& nav_rect_rel = window->NavRectRel[g.NavLayer]; + const float page_offset_y = ImMax(0.0f, window->InnerClipRect.GetHeight() - window->CalcFontSize() * 1.0f + nav_rect_rel.GetHeight()); + float nav_scoring_rect_offset_y = 0.0f; + if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageUp], true)) + { + nav_scoring_rect_offset_y = -page_offset_y; + g.NavMoveDir = ImGuiDir_Down; // Because our scoring rect is offset, we intentionally request the opposite direction (so we can always land on the last item) + g.NavMoveClipDir = ImGuiDir_Up; + g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet; + } + else if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageDown], true)) + { + nav_scoring_rect_offset_y = +page_offset_y; + g.NavMoveDir = ImGuiDir_Up; // Because our scoring rect is offset, we intentionally request the opposite direction (so we can always land on the last item) + g.NavMoveClipDir = ImGuiDir_Down; + g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet; + } + return nav_scoring_rect_offset_y; + } + } + } + return 0.0f; +} + +static int FindWindowFocusIndex(ImGuiWindow* window) // FIXME-OPT O(N) +{ + ImGuiContext& g = *GImGui; + for (int i = g.WindowsFocusOrder.Size-1; i >= 0; i--) + if (g.WindowsFocusOrder[i] == window) + return i; + return -1; +} + +static ImGuiWindow* FindWindowNavFocusable(int i_start, int i_stop, int dir) // FIXME-OPT O(N) +{ + ImGuiContext& g = *GImGui; + for (int i = i_start; i >= 0 && i < g.WindowsFocusOrder.Size && i != i_stop; i += dir) + if (ImGui::IsWindowNavFocusable(g.WindowsFocusOrder[i])) + return g.WindowsFocusOrder[i]; + return NULL; +} + +static void NavUpdateWindowingHighlightWindow(int focus_change_dir) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.NavWindowingTarget); + if (g.NavWindowingTarget->Flags & ImGuiWindowFlags_Modal) + return; + + const int i_current = FindWindowFocusIndex(g.NavWindowingTarget); + ImGuiWindow* window_target = FindWindowNavFocusable(i_current + focus_change_dir, -INT_MAX, focus_change_dir); + if (!window_target) + window_target = FindWindowNavFocusable((focus_change_dir < 0) ? (g.WindowsFocusOrder.Size - 1) : 0, i_current, focus_change_dir); + if (window_target) // Don't reset windowing target if there's a single window in the list + g.NavWindowingTarget = g.NavWindowingTargetAnim = window_target; + g.NavWindowingToggleLayer = false; +} + +// Window management mode (hold to: change focus/move/resize, tap to: toggle menu layer) +static void ImGui::NavUpdateWindowing() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* apply_focus_window = NULL; + bool apply_toggle_layer = false; + + ImGuiWindow* modal_window = GetFrontMostPopupModal(); + if (modal_window != NULL) + { + g.NavWindowingTarget = NULL; + return; + } + + // Fade out + if (g.NavWindowingTargetAnim && g.NavWindowingTarget == NULL) + { + g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha - g.IO.DeltaTime * 10.0f, 0.0f); + if (g.DimBgRatio <= 0.0f && g.NavWindowingHighlightAlpha <= 0.0f) + g.NavWindowingTargetAnim = NULL; + } + + // Start CTRL-TAB or Square+L/R window selection + bool start_windowing_with_gamepad = !g.NavWindowingTarget && IsNavInputPressed(ImGuiNavInput_Menu, ImGuiInputReadMode_Pressed); + bool start_windowing_with_keyboard = !g.NavWindowingTarget && g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab) && (g.IO.ConfigFlags & ImGuiConfigFlags_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)) + { + g.NavWindowingTarget = g.NavWindowingTargetAnim = window; + g.NavWindowingTimer = g.NavWindowingHighlightAlpha = 0.0f; + g.NavWindowingToggleLayer = start_windowing_with_keyboard ? false : true; + g.NavInputSource = start_windowing_with_keyboard ? ImGuiInputSource_NavKeyboard : ImGuiInputSource_NavGamepad; + } + + // Gamepad update + g.NavWindowingTimer += g.IO.DeltaTime; + if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_NavGamepad) + { + // Highlight only appears after a brief time holding the button, so that a fast tap on PadMenu (to toggle NavLayer) doesn't add visual noise + g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f)); + + // Select window to focus + const int focus_change_dir = (int)IsNavInputPressed(ImGuiNavInput_FocusPrev, ImGuiInputReadMode_RepeatSlow) - (int)IsNavInputPressed(ImGuiNavInput_FocusNext, ImGuiInputReadMode_RepeatSlow); + if (focus_change_dir != 0) + { + NavUpdateWindowingHighlightWindow(focus_change_dir); + g.NavWindowingHighlightAlpha = 1.0f; + } + + // Single press toggles NavLayer, long press with L/R apply actual focus on release (until then the window was merely rendered front-most) + if (!IsNavInputDown(ImGuiNavInput_Menu)) + { + g.NavWindowingToggleLayer &= (g.NavWindowingHighlightAlpha < 1.0f); // Once button was held long enough we don't consider it a tap-to-toggle-layer press anymore. + if (g.NavWindowingToggleLayer && g.NavWindow) + apply_toggle_layer = true; + else if (!g.NavWindowingToggleLayer) + apply_focus_window = g.NavWindowingTarget; + g.NavWindowingTarget = NULL; + } + } + + // Keyboard: Focus + if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_NavKeyboard) + { + // Visuals only appears after a brief time after pressing TAB the first time, so that a fast CTRL+TAB doesn't add visual noise + g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f)); // 1.0f + if (IsKeyPressedMap(ImGuiKey_Tab, true)) + NavUpdateWindowingHighlightWindow(g.IO.KeyShift ? +1 : -1); + if (!g.IO.KeyCtrl) + apply_focus_window = g.NavWindowingTarget; + } + + // Keyboard: Press and Release ALT to toggle menu layer + // FIXME: We lack an explicit IO variable for "is the imgui window focused", so compare mouse validity to detect the common case of back-end clearing releases all keys on ALT-TAB + if (IsNavInputPressed(ImGuiNavInput_KeyMenu_, ImGuiInputReadMode_Pressed)) + g.NavWindowingToggleLayer = true; + if ((g.ActiveId == 0 || g.ActiveIdAllowOverlap) && g.NavWindowingToggleLayer && IsNavInputPressed(ImGuiNavInput_KeyMenu_, ImGuiInputReadMode_Released)) + if (IsMousePosValid(&g.IO.MousePos) == IsMousePosValid(&g.IO.MousePosPrev)) + apply_toggle_layer = true; + + // Move window + if (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoMove)) + { + ImVec2 move_delta; + if (g.NavInputSource == ImGuiInputSource_NavKeyboard && !g.IO.KeyShift) + move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard, ImGuiInputReadMode_Down); + if (g.NavInputSource == ImGuiInputSource_NavGamepad) + move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadLStick, ImGuiInputReadMode_Down); + if (move_delta.x != 0.0f || move_delta.y != 0.0f) + { + const float NAV_MOVE_SPEED = 800.0f; + const float move_speed = ImFloor(NAV_MOVE_SPEED * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y)); // FIXME: Doesn't code variable framerate very well + g.NavWindowingTarget->RootWindow->Pos += move_delta * move_speed; + g.NavDisableMouseHover = true; + MarkIniSettingsDirty(g.NavWindowingTarget); + } + } + + // Apply final focus + if (apply_focus_window && (g.NavWindow == NULL || apply_focus_window != g.NavWindow->RootWindowDockStop)) + { + ImGuiViewport* previous_viewport = g.NavWindow ? g.NavWindow->Viewport : NULL; + g.NavDisableHighlight = false; + g.NavDisableMouseHover = true; + apply_focus_window = NavRestoreLastChildNavWindow(apply_focus_window); + ClosePopupsOverWindow(apply_focus_window); + FocusWindow(apply_focus_window); + if (apply_focus_window->NavLastIds[0] == 0) + NavInitWindow(apply_focus_window, false); + + // If the window only has a menu layer, select it directly + if (apply_focus_window->DC.NavLayerActiveMask == (1 << 1)) + g.NavLayer = 1; + + // 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; + + // Apply menu/layer toggle + if (apply_toggle_layer && g.NavWindow) + { + // Move to parent menu if necessary + ImGuiWindow* new_nav_window = g.NavWindow; + while (new_nav_window->ParentWindow + && (new_nav_window->DC.NavLayerActiveMask & (1 << 1)) == 0 + && (new_nav_window->Flags & ImGuiWindowFlags_ChildWindow) != 0 + && (new_nav_window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0) + new_nav_window = new_nav_window->ParentWindow; + if (new_nav_window != g.NavWindow) + { + ImGuiWindow* old_nav_window = g.NavWindow; + FocusWindow(new_nav_window); + new_nav_window->NavLastChildNavWindow = old_nav_window; + } + g.NavDisableHighlight = false; + g.NavDisableMouseHover = true; + + // When entering a regular menu bar with the Alt key, we always reinitialize the navigation ID. It however persist on docking tab tabs. + const int new_nav_layer = (g.NavWindow->DC.NavLayerActiveMask & (1 << 1)) ? (g.NavLayer ^ 1) : 0; + const bool preserve_layer_1_nav_id = (new_nav_window->DockNodeAsHost != NULL); + if (new_nav_layer == 1 && !preserve_layer_1_nav_id) + g.NavWindow->NavLastIds[1] = 0; + NavRestoreLayer(new_nav_layer); + } +} + +// Window has already passed the IsWindowNavFocusable() +static const char* GetFallbackWindowNameForWindowingList(ImGuiWindow* window) +{ + if (window->Flags & ImGuiWindowFlags_Popup) + return "(Popup)"; + if ((window->Flags & ImGuiWindowFlags_MenuBar) && strcmp(window->Name, "##MainMenuBar") == 0) + return "(Main menu bar)"; + if (window->DockNodeAsHost) + return "(Dock node)"; + return "(Untitled)"; +} + +// Overlay displayed when using CTRL+TAB. Called by EndFrame(). +void ImGui::NavUpdateWindowingList() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.NavWindowingTarget != NULL); + + if (g.NavWindowingTimer < NAV_WINDOWING_LIST_APPEAR_DELAY) + return; + + if (g.NavWindowingList == NULL) + g.NavWindowingList = FindWindowByName("###NavWindowingList"); + ImGuiViewportP* viewport = /*g.NavWindow ? g.NavWindow->Viewport :*/ (ImGuiViewportP*)GetMainViewport(); + SetNextWindowSizeConstraints(ImVec2(viewport->Size.x * 0.20f, viewport->Size.y * 0.20f), ImVec2(FLT_MAX, FLT_MAX)); + SetNextWindowPos(viewport->Pos + viewport->Size * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); + PushStyleVar(ImGuiStyleVar_WindowPadding, g.Style.WindowPadding * 2.0f); + Begin("###NavWindowingList", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings); + for (int n = g.WindowsFocusOrder.Size - 1; n >= 0; n--) + { + ImGuiWindow* window = g.WindowsFocusOrder[n]; + if (!IsWindowNavFocusable(window)) + continue; + const char* label = window->Name; + if (label == FindRenderedTextEnd(label)) + label = GetFallbackWindowNameForWindowingList(window); + Selectable(label, g.NavWindowingTarget == window); + } + End(); + PopStyleVar(); +} + +//----------------------------------------------------------------------------- +// [SECTION] COLUMNS +// In the current version, Columns are very weak. Needs to be replaced with a more full-featured system. +//----------------------------------------------------------------------------- + +void ImGui::NextColumn() +{ + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems || window->DC.ColumnsSet == NULL) + return; + + ImGuiContext& g = *GImGui; + PopItemWidth(); + PopClipRect(); + + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + columns->LineMaxY = ImMax(columns->LineMaxY, window->DC.CursorPos.y); + if (++columns->Current < columns->Count) + { + // Columns 1+ cancel out IndentX + window->DC.ColumnsOffset.x = GetColumnOffset(columns->Current) - window->DC.Indent.x + g.Style.ItemSpacing.x; + window->DrawList->ChannelsSetCurrent(columns->Current); + } + else + { + window->DC.ColumnsOffset.x = 0.0f; + window->DrawList->ChannelsSetCurrent(0); + columns->Current = 0; + columns->LineMinY = columns->LineMaxY; + } + window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); + window->DC.CursorPos.y = columns->LineMinY; + window->DC.CurrentLineSize = ImVec2(0.0f, 0.0f); + window->DC.CurrentLineTextBaseOffset = 0.0f; + + PushColumnClipRect(); + PushItemWidth(GetColumnWidth() * 0.65f); // FIXME: Move on columns setup +} + +int ImGui::GetColumnIndex() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.ColumnsSet ? window->DC.ColumnsSet->Current : 0; +} + +int ImGui::GetColumnsCount() +{ + ImGuiWindow* window = GetCurrentWindowRead(); + return window->DC.ColumnsSet ? window->DC.ColumnsSet->Count : 1; +} + +static float OffsetNormToPixels(const ImGuiColumnsSet* columns, float offset_norm) +{ + return offset_norm * (columns->MaxX - columns->MinX); +} + +static float PixelsToOffsetNorm(const ImGuiColumnsSet* columns, float offset) +{ + return offset / (columns->MaxX - columns->MinX); +} + +static inline float GetColumnsRectHalfWidth() { return 4.0f; } + +static float GetDraggedColumnOffset(ImGuiColumnsSet* columns, int column_index) +{ + // Active (dragged) column always follow mouse. The reason we need this is that dragging a column to the right edge of an auto-resizing + // window creates a feedback loop because we store normalized positions. So while dragging we enforce absolute positioning. + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + IM_ASSERT(column_index > 0); // We are not supposed to drag column 0. + IM_ASSERT(g.ActiveId == columns->ID + ImGuiID(column_index)); + + float x = g.IO.MousePos.x - g.ActiveIdClickOffset.x + GetColumnsRectHalfWidth() - window->Pos.x; + x = ImMax(x, ImGui::GetColumnOffset(column_index - 1) + g.Style.ColumnsMinSpacing); + if ((columns->Flags & ImGuiColumnsFlags_NoPreserveWidths)) + x = ImMin(x, ImGui::GetColumnOffset(column_index + 1) - g.Style.ColumnsMinSpacing); + + return x; +} + +float ImGui::GetColumnOffset(int column_index) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + IM_ASSERT(columns != NULL); + + if (column_index < 0) + column_index = columns->Current; + IM_ASSERT(column_index < columns->Columns.Size); + + const float t = columns->Columns[column_index].OffsetNorm; + const float x_offset = ImLerp(columns->MinX, columns->MaxX, t); + return x_offset; +} + +static float GetColumnWidthEx(ImGuiColumnsSet* columns, int column_index, bool before_resize = false) +{ + if (column_index < 0) + column_index = columns->Current; + + float offset_norm; + if (before_resize) + offset_norm = columns->Columns[column_index + 1].OffsetNormBeforeResize - columns->Columns[column_index].OffsetNormBeforeResize; + else + offset_norm = columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm; + return OffsetNormToPixels(columns, offset_norm); +} + +float ImGui::GetColumnWidth(int column_index) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + IM_ASSERT(columns != NULL); + + if (column_index < 0) + column_index = columns->Current; + return OffsetNormToPixels(columns, columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm); +} + +void ImGui::SetColumnOffset(int column_index, float offset) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + IM_ASSERT(columns != NULL); + + if (column_index < 0) + column_index = columns->Current; + IM_ASSERT(column_index < columns->Columns.Size); + + const bool preserve_width = !(columns->Flags & ImGuiColumnsFlags_NoPreserveWidths) && (column_index < columns->Count-1); + const float width = preserve_width ? GetColumnWidthEx(columns, column_index, columns->IsBeingResized) : 0.0f; + + if (!(columns->Flags & ImGuiColumnsFlags_NoForceWithinWindow)) + offset = ImMin(offset, columns->MaxX - g.Style.ColumnsMinSpacing * (columns->Count - column_index)); + columns->Columns[column_index].OffsetNorm = PixelsToOffsetNorm(columns, offset - columns->MinX); + + if (preserve_width) + SetColumnOffset(column_index + 1, offset + ImMax(g.Style.ColumnsMinSpacing, width)); +} + +void ImGui::SetColumnWidth(int column_index, float width) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + IM_ASSERT(columns != NULL); + + if (column_index < 0) + column_index = columns->Current; + SetColumnOffset(column_index + 1, GetColumnOffset(column_index) + width); +} + +void ImGui::PushColumnClipRect(int column_index) +{ + ImGuiWindow* window = GetCurrentWindowRead(); + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + if (column_index < 0) + column_index = columns->Current; + + PushClipRect(columns->Columns[column_index].ClipRect.Min, columns->Columns[column_index].ClipRect.Max, false); +} + +static ImGuiColumnsSet* FindOrAddColumnsSet(ImGuiWindow* window, ImGuiID id) +{ + for (int n = 0; n < window->ColumnsStorage.Size; n++) + if (window->ColumnsStorage[n].ID == id) + return &window->ColumnsStorage[n]; + + window->ColumnsStorage.push_back(ImGuiColumnsSet()); + ImGuiColumnsSet* columns = &window->ColumnsStorage.back(); + columns->ID = id; + return columns; +} + +void ImGui::BeginColumns(const char* str_id, int columns_count, ImGuiColumnsFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + + IM_ASSERT(columns_count > 1); + IM_ASSERT(window->DC.ColumnsSet == NULL); // Nested columns are currently not supported + + // Differentiate column ID with an arbitrary prefix for cases where users name their columns set the same as another widget. + // In addition, when an identifier isn't explicitly provided we include the number of columns in the hash to make it uniquer. + PushID(0x11223347 + (str_id ? 0 : columns_count)); + ImGuiID id = window->GetID(str_id ? str_id : "columns"); + PopID(); + + // Acquire storage for the columns set + ImGuiColumnsSet* columns = FindOrAddColumnsSet(window, id); + IM_ASSERT(columns->ID == id); + columns->Current = 0; + columns->Count = columns_count; + columns->Flags = flags; + window->DC.ColumnsSet = columns; + + // Set state for first column + const float content_region_width = (window->SizeContentsExplicit.x != 0.0f) ? (window->SizeContentsExplicit.x) : (window->InnerClipRect.Max.x - window->Pos.x); + columns->MinX = window->DC.Indent.x - g.Style.ItemSpacing.x; // Lock our horizontal range + columns->MaxX = ImMax(content_region_width - window->Scroll.x, columns->MinX + 1.0f); + columns->StartPosY = window->DC.CursorPos.y; + columns->StartMaxPosX = window->DC.CursorMaxPos.x; + columns->LineMinY = columns->LineMaxY = window->DC.CursorPos.y; + window->DC.ColumnsOffset.x = 0.0f; + window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); + + // Clear data if columns count changed + if (columns->Columns.Size != 0 && columns->Columns.Size != columns_count + 1) + columns->Columns.resize(0); + + // Initialize defaults + columns->IsFirstFrame = (columns->Columns.Size == 0); + if (columns->Columns.Size == 0) + { + columns->Columns.reserve(columns_count + 1); + for (int n = 0; n < columns_count + 1; n++) + { + ImGuiColumnData column; + column.OffsetNorm = n / (float)columns_count; + columns->Columns.push_back(column); + } + } + + for (int n = 0; n < columns_count; n++) + { + // Compute clipping rectangle + ImGuiColumnData* column = &columns->Columns[n]; + float clip_x1 = ImFloor(0.5f + window->Pos.x + GetColumnOffset(n) - 1.0f); + float clip_x2 = ImFloor(0.5f + window->Pos.x + GetColumnOffset(n + 1) - 1.0f); + column->ClipRect = ImRect(clip_x1, -FLT_MAX, clip_x2, +FLT_MAX); + column->ClipRect.ClipWith(window->ClipRect); + } + + window->DrawList->ChannelsSplit(columns->Count); + PushColumnClipRect(); + PushItemWidth(GetColumnWidth() * 0.65f); +} + +void ImGui::EndColumns() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + ImGuiColumnsSet* columns = window->DC.ColumnsSet; + IM_ASSERT(columns != NULL); + + PopItemWidth(); + PopClipRect(); + window->DrawList->ChannelsMerge(); + + columns->LineMaxY = ImMax(columns->LineMaxY, window->DC.CursorPos.y); + window->DC.CursorPos.y = columns->LineMaxY; + if (!(columns->Flags & ImGuiColumnsFlags_GrowParentContentsSize)) + window->DC.CursorMaxPos.x = columns->StartMaxPosX; // Restore cursor max pos, as columns don't grow parent + + // Draw columns borders and handle resize + bool is_being_resized = false; + if (!(columns->Flags & ImGuiColumnsFlags_NoBorder) && !window->SkipItems) + { + const float y1 = columns->StartPosY; + const float y2 = window->DC.CursorPos.y; + int dragging_column = -1; + for (int n = 1; n < columns->Count; n++) + { + float x = window->Pos.x + GetColumnOffset(n); + const ImGuiID column_id = columns->ID + ImGuiID(n); + const float column_hw = GetColumnsRectHalfWidth(); // Half-width for interaction + const ImRect column_rect(ImVec2(x - column_hw, y1), ImVec2(x + column_hw, y2)); + KeepAliveID(column_id); + if (IsClippedEx(column_rect, column_id, false)) + continue; + + bool hovered = false, held = false; + if (!(columns->Flags & ImGuiColumnsFlags_NoResize)) + { + ButtonBehavior(column_rect, column_id, &hovered, &held); + if (hovered || held) + g.MouseCursor = ImGuiMouseCursor_ResizeEW; + if (held && !(columns->Columns[n].Flags & ImGuiColumnsFlags_NoResize)) + dragging_column = n; + } + + // Draw column (we clip the Y boundaries CPU side because very long triangles are mishandled by some GPU drivers.) + const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : hovered ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator); + const float xi = (float)(int)x; + window->DrawList->AddLine(ImVec2(xi, ImMax(y1 + 1.0f, window->ClipRect.Min.y)), ImVec2(xi, ImMin(y2, window->ClipRect.Max.y)), col); + } + + // Apply dragging after drawing the column lines, so our rendered lines are in sync with how items were displayed during the frame. + if (dragging_column != -1) + { + if (!columns->IsBeingResized) + for (int n = 0; n < columns->Count + 1; n++) + columns->Columns[n].OffsetNormBeforeResize = columns->Columns[n].OffsetNorm; + columns->IsBeingResized = is_being_resized = true; + float x = GetDraggedColumnOffset(columns, dragging_column); + SetColumnOffset(dragging_column, x); + } + } + columns->IsBeingResized = is_being_resized; + + window->DC.ColumnsSet = NULL; + window->DC.ColumnsOffset.x = 0.0f; + window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); +} + +// [2018-03: This is currently the only public API, while we are working on making BeginColumns/EndColumns user-facing] +void ImGui::Columns(int columns_count, const char* id, bool border) +{ + ImGuiWindow* window = GetCurrentWindow(); + IM_ASSERT(columns_count >= 1); + + ImGuiColumnsFlags flags = (border ? 0 : ImGuiColumnsFlags_NoBorder); + //flags |= ImGuiColumnsFlags_NoPreserveWidths; // NB: Legacy behavior + if (window->DC.ColumnsSet != NULL && window->DC.ColumnsSet->Count == columns_count && window->DC.ColumnsSet->Flags == flags) + return; + + if (window->DC.ColumnsSet != NULL) + EndColumns(); + + if (columns_count != 1) + BeginColumns(id, columns_count, flags); +} + +//----------------------------------------------------------------------------- +// [SECTION] DRAG AND DROP +//----------------------------------------------------------------------------- + +void ImGui::ClearDragDrop() +{ + ImGuiContext& g = *GImGui; + g.DragDropActive = false; + g.DragDropPayload.Clear(); + g.DragDropAcceptFlags = 0; + g.DragDropAcceptIdCurr = g.DragDropAcceptIdPrev = 0; + g.DragDropAcceptIdCurrRectSurface = FLT_MAX; + g.DragDropAcceptFrameCount = -1; + + g.DragDropPayloadBufHeap.clear(); + memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal)); +} + +// Call when current ID is active. +// When this returns true you need to: a) call SetDragDropPayload() exactly once, b) you may render the payload visual/description, c) call EndDragDropSource() +bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + bool source_drag_active = false; + ImGuiID source_id = 0; + ImGuiID source_parent_id = 0; + int mouse_button = 0; + if (!(flags & ImGuiDragDropFlags_SourceExtern)) + { + source_id = window->DC.LastItemId; + if (source_id != 0 && g.ActiveId != source_id) // Early out for most common case + return false; + if (g.IO.MouseDown[mouse_button] == false) + return false; + + if (source_id == 0) + { + // If you want to use BeginDragDropSource() on an item with no unique identifier for interaction, such as Text() or Image(), you need to: + // A) Read the explanation below, B) Use the ImGuiDragDropFlags_SourceAllowNullID flag, C) Swallow your programmer pride. + if (!(flags & ImGuiDragDropFlags_SourceAllowNullID)) + { + IM_ASSERT(0); + return false; + } + + // Magic fallback (=somehow reprehensible) to handle items with no assigned ID, e.g. Text(), Image() + // We build a throwaway ID based on current ID stack + relative AABB of items in window. + // THE IDENTIFIER WON'T SURVIVE ANY REPOSITIONING OF THE WIDGET, so if your widget moves your dragging operation will be canceled. + // We don't need to maintain/call ClearActiveID() as releasing the button will early out this function and trigger !ActiveIdIsAlive. + bool is_hovered = (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) != 0; + if (!is_hovered && (g.ActiveId == 0 || g.ActiveIdWindow != window)) + return false; + source_id = window->DC.LastItemId = window->GetIDFromRectangle(window->DC.LastItemRect); + if (is_hovered) + SetHoveredID(source_id); + if (is_hovered && g.IO.MouseClicked[mouse_button]) + { + SetActiveID(source_id, window); + FocusWindow(window); + } + if (g.ActiveId == source_id) // Allow the underlying widget to display/return hovered during the mouse release frame, else we would get a flicker. + g.ActiveIdAllowOverlap = is_hovered; + } + else + { + g.ActiveIdAllowOverlap = false; + } + if (g.ActiveId != source_id) + return false; + source_parent_id = window->IDStack.back(); + source_drag_active = IsMouseDragging(mouse_button); + } + else + { + window = NULL; + source_id = ImHash("#SourceExtern", 0); + source_drag_active = true; + } + + if (source_drag_active) + { + if (!g.DragDropActive) + { + IM_ASSERT(source_id != 0); + ClearDragDrop(); + ImGuiPayload& payload = g.DragDropPayload; + payload.SourceId = source_id; + payload.SourceParentId = source_parent_id; + g.DragDropActive = true; + g.DragDropSourceFlags = flags; + g.DragDropMouseButton = mouse_button; + } + g.DragDropSourceFrameCount = g.FrameCount; + g.DragDropWithinSourceOrTarget = true; + + if (!(flags & ImGuiDragDropFlags_SourceNoPreviewTooltip)) + { + // Target can request the Source to not display its tooltip (we use a dedicated flag to make this request explicit) + // We unfortunately can't just modify the source flags and skip the call to BeginTooltip, as caller may be emitting contents. + BeginTooltip(); + if (g.DragDropActive && g.DragDropAcceptIdPrev && (g.DragDropAcceptFlags & ImGuiDragDropFlags_AcceptNoPreviewTooltip)) + { + ImGuiWindow* tooltip_window = g.CurrentWindow; + tooltip_window->SkipItems = true; + tooltip_window->HiddenFramesRegular = 1; + } + } + + if (!(flags & ImGuiDragDropFlags_SourceNoDisableHover) && !(flags & ImGuiDragDropFlags_SourceExtern)) + window->DC.LastItemStatusFlags &= ~ImGuiItemStatusFlags_HoveredRect; + + return true; + } + return false; +} + +void ImGui::EndDragDropSource() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.DragDropActive); + IM_ASSERT(g.DragDropWithinSourceOrTarget && "Not after a BeginDragDropSource()?"); + + if (!(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoPreviewTooltip)) + EndTooltip(); + + // Discard the drag if have not called SetDragDropPayload() + if (g.DragDropPayload.DataFrameCount == -1) + ClearDragDrop(); + g.DragDropWithinSourceOrTarget = false; +} + +// Use 'cond' to choose to submit payload on drag start or every frame +bool ImGui::SetDragDropPayload(const char* type, const void* data, size_t data_size, ImGuiCond cond) { - ImVec2 delta(0.0f, 0.0f); - if (dir_sources & ImGuiNavDirSourceFlags_Keyboard) - delta += ImVec2(GetNavInputAmount(ImGuiNavInput_KeyRight_, mode) - GetNavInputAmount(ImGuiNavInput_KeyLeft_, mode), GetNavInputAmount(ImGuiNavInput_KeyDown_, mode) - GetNavInputAmount(ImGuiNavInput_KeyUp_, mode)); - if (dir_sources & ImGuiNavDirSourceFlags_PadDPad) - delta += ImVec2(GetNavInputAmount(ImGuiNavInput_DpadRight, mode) - GetNavInputAmount(ImGuiNavInput_DpadLeft, mode), GetNavInputAmount(ImGuiNavInput_DpadDown, mode) - GetNavInputAmount(ImGuiNavInput_DpadUp, mode)); - if (dir_sources & ImGuiNavDirSourceFlags_PadLStick) - delta += ImVec2(GetNavInputAmount(ImGuiNavInput_LStickRight, mode) - GetNavInputAmount(ImGuiNavInput_LStickLeft, mode), GetNavInputAmount(ImGuiNavInput_LStickDown, mode) - GetNavInputAmount(ImGuiNavInput_LStickUp, mode)); - if (slow_factor != 0.0f && IsNavInputDown(ImGuiNavInput_TweakSlow)) - delta *= slow_factor; - if (fast_factor != 0.0f && IsNavInputDown(ImGuiNavInput_TweakFast)) - delta *= fast_factor; - return delta; + ImGuiContext& g = *GImGui; + ImGuiPayload& payload = g.DragDropPayload; + if (cond == 0) + cond = ImGuiCond_Always; + + IM_ASSERT(type != NULL); + IM_ASSERT(strlen(type) < IM_ARRAYSIZE(payload.DataType) && "Payload type can be at most 32 characters long"); + IM_ASSERT((data != NULL && data_size > 0) || (data == NULL && data_size == 0)); + IM_ASSERT(cond == ImGuiCond_Always || cond == ImGuiCond_Once); + IM_ASSERT(payload.SourceId != 0); // Not called between BeginDragDropSource() and EndDragDropSource() + + if (cond == ImGuiCond_Always || payload.DataFrameCount == -1) + { + // Copy payload + ImStrncpy(payload.DataType, type, IM_ARRAYSIZE(payload.DataType)); + g.DragDropPayloadBufHeap.resize(0); + if (data_size > sizeof(g.DragDropPayloadBufLocal)) + { + // Store in heap + g.DragDropPayloadBufHeap.resize((int)data_size); + payload.Data = g.DragDropPayloadBufHeap.Data; + memcpy(payload.Data, data, data_size); + } + else if (data_size > 0) + { + // Store locally + memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal)); + payload.Data = g.DragDropPayloadBufLocal; + memcpy(payload.Data, data, data_size); + } + else + { + payload.Data = NULL; + } + payload.DataSize = (int)data_size; + } + payload.DataFrameCount = g.FrameCount; + + return (g.DragDropAcceptFrameCount == g.FrameCount) || (g.DragDropAcceptFrameCount == g.FrameCount - 1); +} + +bool ImGui::BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id) +{ + ImGuiContext& g = *GImGui; + if (!g.DragDropActive) + return false; + + ImGuiWindow* window = g.CurrentWindow; + if (g.HoveredWindowUnderMovingWindow == NULL || window->RootWindow != g.HoveredWindowUnderMovingWindow->RootWindow) + return false; + IM_ASSERT(id != 0); + if (!IsMouseHoveringRect(bb.Min, bb.Max) || (id == g.DragDropPayload.SourceId)) + return false; + if (window->SkipItems) + return false; + + IM_ASSERT(g.DragDropWithinSourceOrTarget == false); + g.DragDropTargetRect = bb; + g.DragDropTargetId = id; + g.DragDropWithinSourceOrTarget = true; + return true; +} + +// We don't use BeginDragDropTargetCustom() and duplicate its code because: +// 1) we use LastItemRectHoveredRect which handles items that pushes a temporarily clip rectangle in their code. Calling BeginDragDropTargetCustom(LastItemRect) would not handle them. +// 2) and it's faster. as this code may be very frequently called, we want to early out as fast as we can. +// Also note how the HoveredWindow test is positioned differently in both functions (in both functions we optimize for the cheapest early out case) +bool ImGui::BeginDragDropTarget() +{ + ImGuiContext& g = *GImGui; + if (!g.DragDropActive) + return false; + + ImGuiWindow* window = g.CurrentWindow; + if (!(window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect)) + return false; + if (g.HoveredWindowUnderMovingWindow == NULL || window->RootWindow != g.HoveredWindowUnderMovingWindow->RootWindow) + return false; + + const ImRect& display_rect = (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HasDisplayRect) ? window->DC.LastItemDisplayRect : window->DC.LastItemRect; + ImGuiID id = window->DC.LastItemId; + if (id == 0) + id = window->GetIDFromRectangle(display_rect); + if (g.DragDropPayload.SourceId == id) + return false; + + IM_ASSERT(g.DragDropWithinSourceOrTarget == false); + g.DragDropTargetRect = display_rect; + g.DragDropTargetId = id; + g.DragDropWithinSourceOrTarget = true; + return true; +} + +bool ImGui::IsDragDropPayloadBeingAccepted() +{ + ImGuiContext& g = *GImGui; + return g.DragDropActive && g.DragDropAcceptIdPrev != 0; +} + +const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDropFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + ImGuiPayload& payload = g.DragDropPayload; + IM_ASSERT(g.DragDropActive); // Not called between BeginDragDropTarget() and EndDragDropTarget() ? + IM_ASSERT(payload.DataFrameCount != -1); // Forgot to call EndDragDropTarget() ? + if (type != NULL && !payload.IsDataType(type)) + return NULL; + + // Accept smallest drag target bounding box, this allows us to nest drag targets conveniently without ordering constraints. + // NB: We currently accept NULL id as target. However, overlapping targets requires a unique ID to function! + const bool was_accepted_previously = (g.DragDropAcceptIdPrev == g.DragDropTargetId); + ImRect r = g.DragDropTargetRect; + float r_surface = r.GetWidth() * r.GetHeight(); + if (r_surface < g.DragDropAcceptIdCurrRectSurface) + { + g.DragDropAcceptFlags = flags; + g.DragDropAcceptIdCurr = g.DragDropTargetId; + g.DragDropAcceptIdCurrRectSurface = r_surface; + } + + // Render default drop visuals + payload.Preview = was_accepted_previously; + flags |= (g.DragDropSourceFlags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect); // Source can also inhibit the preview (useful for external sources that lives for 1 frame) + if (!(flags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect) && payload.Preview) + { + // FIXME-DRAG: Settle on a proper default visuals for drop target. + r.Expand(3.5f); + bool push_clip_rect = !window->ClipRect.Contains(r); + if (push_clip_rect) window->DrawList->PushClipRect(r.Min-ImVec2(1,1), r.Max+ImVec2(1,1)); + window->DrawList->AddRect(r.Min, r.Max, GetColorU32(ImGuiCol_DragDropTarget), 0.0f, ~0, 2.0f); + if (push_clip_rect) window->DrawList->PopClipRect(); + } + + g.DragDropAcceptFrameCount = g.FrameCount; + payload.Delivery = was_accepted_previously && !IsMouseDown(g.DragDropMouseButton); // For extern drag sources affecting os window focus, it's easier to just test !IsMouseDown() instead of IsMouseReleased() + if (!payload.Delivery && !(flags & ImGuiDragDropFlags_AcceptBeforeDelivery)) + return NULL; + + return &payload; +} + +// We don't really use/need this now, but added it for the sake of consistency and because we might need it later. +void ImGui::EndDragDropTarget() +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(g.DragDropActive); + IM_ASSERT(g.DragDropWithinSourceOrTarget); + g.DragDropWithinSourceOrTarget = false; +} + +//----------------------------------------------------------------------------- +// [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) +// Docking: Public Builder Functions +// Docking: Begin/End Functions (called from Begin/End) +// Docking: Settings +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Docking: Internal Types +//----------------------------------------------------------------------------- + +static float IMGUI_DOCK_SPLITTER_SIZE = 4.0f; + +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; } +}; + +// Persistent Settings data, stored contiguously in SettingsNodes (sizeof() ~32 bytes) +struct ImGuiDockNodeSettings +{ + ImGuiID ID; + ImGuiID ParentID; + ImGuiID SelectedTabID; + char SplitAxis; + char Depth; + char IsDockSpace; + char IsDocumentRoot; + ImVec2ih Pos; + ImVec2ih Size; + ImVec2ih SizeRef; + ImGuiDockNodeSettings() { ID = ParentID = SelectedTabID = 0; SplitAxis = ImGuiAxis_None; Depth = 0; IsDockSpace = IsDocumentRoot = 0; } +}; + +struct ImGuiDockContext + { + ImGuiStorage Nodes; // Map ID -> ImGuiDockNode*: Active nodes + ImVector Requests; + ImVector SettingsNodes; + bool WantFullRebuild; + ImGuiDockContext() { WantFullRebuild = false; } +}; + +//----------------------------------------------------------------------------- +// 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 DockContextQueueDock(ImGuiContext* ctx, ImGuiWindow* target, ImGuiDockNode* target_node, ImGuiWindow* payload, ImGuiDir split_dir, float split_ratio, bool split_outer); + static void DockContextQueueNotifyRemovedNode(ImGuiContext* ctx, ImGuiDockNode* node); + static void DockContextProcessDock(ImGuiContext* ctx, ImGuiDockRequest* req); + static void DockContextProcessUndockWindow(ImGuiContext* ctx, ImGuiWindow* window, bool clear_persistent_docking_ref = true); + static void DockContextProcessUndockNode(ImGuiContext* ctx, ImGuiDockNode* node); + static void DockContextPruneUnusedSettingsNodes(ImGuiContext* ctx); + static ImGuiDockNode* DockContextFindNodeByID(ImGuiContext* ctx, ImGuiID id); + static void DockContextClearNodes(ImGuiContext* ctx, ImGuiID root_id, bool clear_persistent_docking_refs); // Set root_id==0 to clear all + 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 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 DockNodeUpdateVisibleFlagAndInactiveChilds(ImGuiDockNode* node); + static void DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_window); + static void DockNodeUpdateVisibleFlag(ImGuiDockNode* node); + static void DockNodeStartMouseMovingWindow(ImGuiDockNode* node, ImGuiWindow* window); + static bool DockNodeIsDropAllowed(ImGuiWindow* host_window, ImGuiWindow* payload_window); + static bool DockNodePreviewDockCalc(ImGuiWindow* host_window, ImGuiDockNode* host_node, ImGuiWindow* payload_window, 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 ImRect DockNodeCalcTabBarRect(const ImGuiDockNode* node); + static void DockNodeCalcSplitRects(ImVec2& pos_old, ImVec2& size_old, ImVec2& pos_new, ImVec2& size_new, ImGuiDir dir, ImVec2 size_new_desired); + static bool DockNodeCalcDropRects(const ImRect& parent, ImGuiDir dir, ImRect& out_draw, bool outer_docking); + static const char* DockNodeGetHostWindowTitle(ImGuiDockNode* node, char* buf, int buf_size) { ImFormatString(buf, buf_size, "##DockNode_%02X", node->ID); return buf; } + static int DockNodeGetDepth(const ImGuiDockNode* node) { int depth = 0; while (node->ParentNode) { node = node->ParentNode; depth++; } return depth; } + 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); + static void DockNodeTreeUpdateSplitter(ImGuiDockNode* node); + static ImGuiDockNode* DockNodeTreeFindNodeByPos(ImGuiDockNode* node, ImVec2 pos); + + // Settings + static void DockSettingsMoveDockReferencesInInactiveWindow(ImGuiID old_dock_id, ImGuiID new_dock_id); + static void DockSettingsRemoveReferencesToNodes(ImGuiID* node_ids, int node_ids_count); + static ImGuiDockNodeSettings* DockSettingsFindNodeSettings(ImGuiContext* ctx, ImGuiID node_id); + 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 setttings data. +//----------------------------------------------------------------------------- + +void ImGui::DockContextInitialize(ImGuiContext* ctx) + { + ImGuiContext& g = *ctx; + IM_ASSERT(g.DockContext == NULL); + g.DockContext = IM_NEW(ImGuiDockContext)(); + + // Add .ini handle for persistent docking data + ImGuiSettingsHandler ini_handler; + ini_handler.TypeName = "Docking"; + ini_handler.TypeHash = ImHash("Docking", 0, 0); + ini_handler.ReadOpenFn = DockSettingsHandler_ReadOpen; + ini_handler.ReadLineFn = DockSettingsHandler_ReadLine; + ini_handler.WriteAllFn = DockSettingsHandler_WriteAll; + g.SettingsHandlers.push_back(ini_handler); + } + +void ImGui::DockContextShutdown(ImGuiContext* ctx) +{ + ImGuiContext& g = *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); + IM_DELETE(g.DockContext); + g.DockContext = NULL; + } + +void ImGui::DockContextOnLoadSettings(ImGuiContext* ctx) +{ + ImGuiDockContext* dc = ctx->DockContext; + DockContextPruneUnusedSettingsNodes(ctx); + DockContextBuildNodesFromSettings(ctx, dc->SettingsNodes.Data, dc->SettingsNodes.Size); +} + +void ImGui::DockContextClearNodes(ImGuiContext* ctx, ImGuiID root_id, bool clear_persistent_docking_references) +{ + DockBuilderRemoveNodeDockedWindows(ctx, root_id, clear_persistent_docking_references); + DockBuilderRemoveNodeChildNodes(ctx, root_id); +} + +// This function also acts as a defacto test to make sure we can rebuild from scratch without a glitch +void ImGui::DockContextRebuild(ImGuiContext* ctx) +{ + //IMGUI_DEBUG_LOG("[docking] full rebuild\n"); + ImGuiDockContext* dc = ctx->DockContext; + SaveIniSettingsToMemory(); + ImGuiID root_id = 0; // Rebuild all + DockContextClearNodes(ctx, root_id, false); + DockContextBuildNodesFromSettings(ctx, dc->SettingsNodes.Data, dc->SettingsNodes.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; + } + +#if 0 + if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_C))) + dc->WantFullRebuild = true; +#endif + if (dc->WantFullRebuild) + { + DockContextRebuild(ctx); + dc->WantFullRebuild = false; + } + + // Process Undocking requests (we need to process them _before_ the UpdateMouseMovingWindow call in NewFrame) + for (int n = 0; n < dc->Requests.Size; n++) + { + ImGuiDockRequest* req = &dc->Requests[n]; + 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; + + // Process Docking requests + for (int n = 0; n < dc->Requests.Size; n++) + if (dc->Requests[n].Type == ImGuiDockRequestType_Dock) + DockContextProcessDock(ctx, &dc->Requests[n]); + 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->IsDockSpace && node->IsRootNode()) + DockNodeUpdate(node); + } + +// Docking context update function, called by EndFrame() +void ImGui::DockContextEndFrame(ImGuiContext* ctx) +{ + (void)ctx; +} + +static ImGuiDockNode* ImGui::DockContextFindNodeByID(ImGuiContext* ctx, ImGuiID id) +{ + return (ImGuiDockNode*)ctx->DockContext->Nodes.GetVoidPtr(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. + if (id == 0) + { + // FIXME-OPT: This is suboptimal, even if the node count is small enough not to be a worry. We could poke in ctx->Nodes to find a suitable ID faster. + id = 0x0001; + while (DockContextFindNodeByID(ctx, id) != NULL) + id++; + } + IM_ASSERT(DockContextFindNodeByID(ctx, id) == NULL); + ImGuiDockNode* node = IM_NEW(ImGuiDockNode)(id); + node->InitFromFirstWindowPosSize = node->InitFromFirstWindowViewport = true; + ctx->DockContext->Nodes.SetVoidPtr(node->ID, node); + return node; } -// Scroll to keep newly navigated item fully into view -// NB: We modify rect_rel by the amount we scrolled for, so it is immediately updated. -static void NavScrollToBringItemIntoView(ImGuiWindow* window, const ImRect& item_rect) +static void ImGui::DockContextRemoveNode(ImGuiContext* ctx, ImGuiDockNode* node, bool merge_sibling_into_parent_node) { - ImRect window_rect(window->InnerMainRect.Min - ImVec2(1, 1), window->InnerMainRect.Max + ImVec2(1, 1)); - //g.OverlayDrawList.AddRect(window_rect.Min, window_rect.Max, IM_COL32_WHITE); // [DEBUG] - if (window_rect.Contains(item_rect)) - return; + ImGuiContext& g = *ctx; + ImGuiDockContext* dc = ctx->DockContext; - ImGuiContext& g = *GImGui; - if (window->ScrollbarX && item_rect.Min.x < window_rect.Min.x) - { - window->ScrollTarget.x = item_rect.Min.x - window->Pos.x + window->Scroll.x - g.Style.ItemSpacing.x; - window->ScrollTargetCenterRatio.x = 0.0f; - } - else if (window->ScrollbarX && item_rect.Max.x >= window_rect.Max.x) - { - window->ScrollTarget.x = item_rect.Max.x - window->Pos.x + window->Scroll.x + g.Style.ItemSpacing.x; - window->ScrollTargetCenterRatio.x = 1.0f; - } - if (item_rect.Min.y < window_rect.Min.y) + //printf("[%05d] RemoveNode 0x%04X\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) { - window->ScrollTarget.y = item_rect.Min.y - window->Pos.y + window->Scroll.y - g.Style.ItemSpacing.y; - window->ScrollTargetCenterRatio.y = 0.0f; + 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 if (item_rect.Max.y >= window_rect.Max.y) + else { - window->ScrollTarget.y = item_rect.Max.y - window->Pos.y + window->Scroll.y + g.Style.ItemSpacing.y; - window->ScrollTargetCenterRatio.y = 1.0f; + 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 void ImGui::NavUpdate() +static int IMGUI_CDECL DockNodeComparerDepthMostFirst(const void* lhs, const void* rhs) { - ImGuiContext& g = *GImGui; - g.IO.WantSetMousePos = false; -#if 0 - if (g.NavScoringCount > 0) printf("[%05d] NavScoringCount %d for '%s' layer %d (Init:%d, Move:%d)\n", g.FrameCount, g.NavScoringCount, g.NavWindow ? g.NavWindow->Name : "NULL", g.NavLayer, g.NavInitRequest || g.NavInitResultId != 0, g.NavMoveRequest); -#endif + const ImGuiDockNode* a = *(const ImGuiDockNode* const*)lhs; + const ImGuiDockNode* b = *(const ImGuiDockNode* const*)rhs; + return ImGui::DockNodeGetDepth(b) - ImGui::DockNodeGetDepth(a); +} - // Set input source as Gamepad when buttons are pressed before we map Keyboard (some features differs when used with Gamepad vs Keyboard) - bool nav_keyboard_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0; - bool nav_gamepad_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (g.IO.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; - if (nav_gamepad_active) - if (g.IO.NavInputs[ImGuiNavInput_Activate] > 0.0f || g.IO.NavInputs[ImGuiNavInput_Input] > 0.0f || g.IO.NavInputs[ImGuiNavInput_Cancel] > 0.0f || g.IO.NavInputs[ImGuiNavInput_Menu] > 0.0f) - g.NavInputSource = ImGuiInputSource_NavGamepad; +// Pre C++0x doesn't allow us to use a 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; } +}; - // Update Keyboard->Nav inputs mapping - if (nav_keyboard_active) +// 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 pool; + pool.Reserve(dc->SettingsNodes.Size); + + // Count child nodes and compute RootID + for (int settings_n = 0; settings_n < dc->SettingsNodes.Size; settings_n++) { - #define NAV_MAP_KEY(_KEY, _NAV_INPUT) if (IsKeyDown(g.IO.KeyMap[_KEY])) { g.IO.NavInputs[_NAV_INPUT] = 1.0f; g.NavInputSource = ImGuiInputSource_NavKeyboard; } - NAV_MAP_KEY(ImGuiKey_Space, ImGuiNavInput_Activate ); - NAV_MAP_KEY(ImGuiKey_Enter, ImGuiNavInput_Input ); - NAV_MAP_KEY(ImGuiKey_Escape, ImGuiNavInput_Cancel ); - NAV_MAP_KEY(ImGuiKey_LeftArrow, ImGuiNavInput_KeyLeft_ ); - NAV_MAP_KEY(ImGuiKey_RightArrow,ImGuiNavInput_KeyRight_); - NAV_MAP_KEY(ImGuiKey_UpArrow, ImGuiNavInput_KeyUp_ ); - NAV_MAP_KEY(ImGuiKey_DownArrow, ImGuiNavInput_KeyDown_ ); - if (g.IO.KeyCtrl) g.IO.NavInputs[ImGuiNavInput_TweakSlow] = 1.0f; - if (g.IO.KeyShift) g.IO.NavInputs[ImGuiNavInput_TweakFast] = 1.0f; - if (g.IO.KeyAlt) g.IO.NavInputs[ImGuiNavInput_KeyMenu_] = 1.0f; - #undef NAV_MAP_KEY + ImGuiDockNodeSettings* settings = &dc->SettingsNodes[settings_n]; + ImGuiDockContextPruneNodeData* parent_data = settings->ParentID ? pool.GetByKey(settings->ParentID) : 0; + pool.GetOrAddByKey(settings->ID)->RootID = parent_data ? parent_data->RootID : settings->ID; + if (settings->ParentID) + pool.GetOrAddByKey(settings->ParentID)->CountChildNodes++; } - memcpy(g.IO.NavInputsDownDurationPrev, g.IO.NavInputsDownDuration, sizeof(g.IO.NavInputsDownDuration)); - for (int i = 0; i < IM_ARRAYSIZE(g.IO.NavInputs); i++) - g.IO.NavInputsDownDuration[i] = (g.IO.NavInputs[i] > 0.0f) ? (g.IO.NavInputsDownDuration[i] < 0.0f ? 0.0f : g.IO.NavInputsDownDuration[i] + g.IO.DeltaTime) : -1.0f; - // Process navigation init request (select first/default focus) - if (g.NavInitResultId != 0 && (!g.NavDisableHighlight || g.NavInitRequestFromMove)) - { - // Apply result from previous navigation init request (will typically select the first item, unless SetItemDefaultFocus() has been called) - IM_ASSERT(g.NavWindow); - if (g.NavInitRequestFromMove) - SetNavIDWithRectRel(g.NavInitResultId, g.NavLayer, g.NavInitResultRectRel); - else - SetNavID(g.NavInitResultId, g.NavLayer); - g.NavWindow->NavRectRel[g.NavLayer] = g.NavInitResultRectRel; + // Count reference to dock ids from window settings + for (int settings_n = 0; settings_n < g.SettingsWindows.Size; settings_n++) + if (ImGuiID dock_id = g.SettingsWindows[settings_n].DockId) + if (ImGuiDockContextPruneNodeData* data = pool.GetByKey(dock_id)) + { + ImGuiDockContextPruneNodeData* data_root = (data->RootID == dock_id) ? data : pool.GetByKey(data->RootID); + data->CountWindows++; + data_root->CountChildWindows++; } - g.NavInitRequest = false; - g.NavInitRequestFromMove = false; - g.NavInitResultId = 0; - g.NavJustMovedToId = 0; - // Process navigation move request - if (g.NavMoveRequest && (g.NavMoveResultLocal.ID != 0 || g.NavMoveResultOther.ID != 0)) - NavUpdateMoveResult(); + // Prune + for (int settings_n = 0; settings_n < dc->SettingsNodes.Size; settings_n++) + { + ImGuiDockNodeSettings* settings = &dc->SettingsNodes[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); - // When a forwarded move request failed, we restore the highlight that we disabled during the forward frame - if (g.NavMoveRequestForward == ImGuiNavForward_ForwardActive) + bool remove = false; + remove |= (data->CountWindows == 1 && settings->ParentID == 0 && data->CountChildNodes == 0 && !settings->IsDocumentRoot); // Floating root node with only 1 window + remove |= (data->CountWindows == 0 && settings->ParentID == 0 && data->CountChildNodes == 0); // Leaf nodes with 0 window + remove |= (data_root->CountChildWindows == 0); + if (remove) { - IM_ASSERT(g.NavMoveRequest); - if (g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0) - g.NavDisableHighlight = false; - g.NavMoveRequestForward = ImGuiNavForward_None; + DockSettingsRemoveReferencesToNodes(&settings->ID, 1); + settings->ID = 0; + } } +} - // Apply application mouse position movement, after we had a chance to process move request result. - if (g.NavMousePosDirty && g.NavIdIsAlive) +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* node_settings = &node_settings_array[node_n]; + if (node_settings->ID == 0) + continue; + ImGuiDockNode* node = DockContextAddNode(ctx, node_settings->ID); + node->ParentNode = node_settings->ParentID ? DockContextFindNodeByID(ctx, node_settings->ParentID) : NULL; + node->Pos = ImVec2(node_settings->Pos.x, node_settings->Pos.y); + node->Size = ImVec2(node_settings->Size.x, node_settings->Size.y); + node->SizeRef = ImVec2(node_settings->SizeRef.x, node_settings->SizeRef.y); + 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 = node_settings->SelectedTabID; + node->SplitAxis = node_settings->SplitAxis; + node->IsDockSpace = node_settings->IsDockSpace != 0; + node->IsDocumentRoot = node_settings->IsDocumentRoot != 0; + + // 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 (int n = 0; n < g.Windows.Size; n++) { - // Set mouse position given our knowledge of the navigated item position from last frame - if ((g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos) && (g.IO.BackendFlags & ImGuiBackendFlags_HasSetMousePos)) - { - if (!g.NavDisableHighlight && g.NavDisableMouseHover && g.NavWindow) - { - g.IO.MousePos = g.IO.MousePosPrev = NavCalcPreferredRefPos(); - g.IO.WantSetMousePos = true; - } - } - g.NavMousePosDirty = false; + ImGuiWindow* window = g.Windows[n]; + if (window->DockId == 0 || window->LastFrameActive < g.FrameCount - 1) + continue; + if (window->DockNode != NULL) + continue; + + ImGuiDockNode* dock_node = DockContextFindNodeByID(ctx, window->DockId); + IM_ASSERT(dock_node != NULL); // This should have been called after DockContextBuildNodesFromSettings() + if (root_id == 0 || DockNodeGetRootNode(dock_node)->ID == root_id) + DockNodeAddWindow(dock_node, window, true); } - g.NavIdIsAlive = false; - g.NavJustTabbedId = 0; - IM_ASSERT(g.NavLayer == 0 || g.NavLayer == 1); +} - // Store our return window (for returning from Layer 1 to Layer 0) and clear it as soon as we step back in our own Layer 0 - if (g.NavWindow) - NavSaveLastChildNavWindow(g.NavWindow); - if (g.NavWindow && g.NavWindow->NavLastChildNavWindow != NULL && g.NavLayer == 0) - g.NavWindow->NavLastChildNavWindow = NULL; +//----------------------------------------------------------------------------- +// Docking: ImGuiDockContext Docking/Undocking functions +//----------------------------------------------------------------------------- - // Update CTRL+TAB and Windowing features (hold Square to move/resize/etc.) - NavUpdateWindowing(); +void ImGui::DockContextQueueDock(ImGuiContext* ctx, ImGuiWindow* target, ImGuiDockNode* target_node, ImGuiWindow* payload, ImGuiDir split_dir, float split_ratio, bool split_outer) +{ + 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); +} - // Set output flags for user application - g.IO.NavActive = (nav_keyboard_active || nav_gamepad_active) && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs); - g.IO.NavVisible = (g.IO.NavActive && g.NavId != 0 && !g.NavDisableHighlight) || (g.NavWindowingTarget != NULL) || g.NavInitRequest; +void ImGui::DockContextQueueUndockWindow(ImGuiContext* ctx, ImGuiWindow* window) +{ + ImGuiDockRequest req; + req.Type = ImGuiDockRequestType_Undock; + req.UndockTargetWindow = window; + ctx->DockContext->Requests.push_back(req); +} - // Process NavCancel input (to close a popup, get back to parent, clear focus) - if (IsNavInputPressed(ImGuiNavInput_Cancel, ImGuiInputReadMode_Pressed)) +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 (int n = 0; n < dc->Requests.Size; n++) + if (dc->Requests[n].DockTargetNode == node) + dc->Requests[n].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; + ImGuiWindow* payload_window = req->DockPayload; // Optional + ImGuiWindow* target_window = req->DockTargetWindow; + ImGuiDockNode* target_node = req->DockTargetNode; + + // 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->IsSplitNode()) + next_selected_id = payload_node->TabBar->NextSelectedTabId ? payload_node->TabBar->NextSelectedTabId : payload_node->TabBar->SelectedTabId; + if (payload_node == NULL) + next_selected_id = payload_window->ID; +} + + // FIXME-DOCK: When we are trying to dock an existing single-window node into a loose window, transfer Node ID as well + + if (target_node) + IM_ASSERT(target_node->LastFrameAlive < g.FrameCount); + if (target_node && target_window && target_node == target_window->DockNodeAsHost) + IM_ASSERT(target_node->Windows.Size > 1 || target_node->IsSplitNode() || target_node->IsDocumentRoot); + + // Create new node and add existing window to it + if (target_node == NULL) { - if (g.ActiveId != 0) - { - ClearActiveID(); + target_node = DockContextAddNode(ctx, 0); + target_node->Pos = target_window->Pos; + target_node->Size = target_window->Size; + if (target_window->DockNodeAsHost == NULL) +{ + DockNodeAddWindow(target_node, target_window, true); + target_node->TabBar->Tabs[0].Flags &= ~ImGuiTabItemFlags_Unsorted; + target_window->DockIsActive = true; } - else if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow) && !(g.NavWindow->Flags & ImGuiWindowFlags_Popup) && g.NavWindow->ParentWindow) - { - // Exit child window - ImGuiWindow* child_window = g.NavWindow; - ImGuiWindow* parent_window = g.NavWindow->ParentWindow; - IM_ASSERT(child_window->ChildId != 0); - FocusWindow(parent_window); - SetNavID(child_window->ChildId, 0); - g.NavIdIsAlive = false; - if (g.NavDisableMouseHover) - g.NavMousePosDirty = true; +} + + ImGuiDir split_dir = req->DockSplitDir; + if (split_dir != ImGuiDir_None) + { + // Split into one, 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; + const float split_ratio = req->DockSplitRatio; + DockNodeTreeSplit(ctx, target_node, split_axis, split_inheritor_child_idx, split_ratio, payload_node); // payload_node may be NULL here! + ImGuiDockNode* inheritor_node = target_node->ChildNodes[split_inheritor_child_idx]; + ImGuiDockNode* new_node = target_node->ChildNodes[split_inheritor_child_idx ^ 1]; + new_node->HostWindow = target_node->HostWindow; + if (target_node) +{ + inheritor_node->IsDocumentRoot = target_node->IsDocumentRoot; + target_node->IsDocumentRoot = false; } - else if (g.OpenPopupStack.Size > 0) + target_node = new_node; + } + + if (target_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 (target_node->Windows.Size > 0 && target_node->TabBar == NULL) { - // Close open popup/menu - if (!(g.OpenPopupStack.back().Window->Flags & ImGuiWindowFlags_Modal)) - ClosePopupToLevel(g.OpenPopupStack.Size - 1); + target_node->TabBar = IM_NEW(ImGuiTabBar)(); + for (int n = 0; n < target_node->Windows.Size; n++) + TabBarAddTab(target_node->TabBar, target_node->Windows[n], ImGuiTabItemFlags_None); } - else if (g.NavLayer != 0) + + if (payload_node != NULL) + { + // Transfer full payload node (with 1+ child windows or child nodes) + if (payload_node->IsSplitNode()) { - // Leave the "menu" layer - NavRestoreLayer(0); + if (target_node->Windows.Size > 0) + { + // We can dock 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 DockNodePreviewDockCalc() early on and never submitted. + ImGuiDockNode* visible_node = payload_node->OnlyNodeWithWindows; + if (visible_node->TabBar) + IM_ASSERT(visible_node->TabBar->Tabs.Size > 0); + DockNodeMoveWindows(target_node, visible_node); + DockNodeMoveWindows(visible_node, target_node); + DockSettingsMoveDockReferencesInInactiveWindow(target_node->ID, visible_node->ID); + } + IM_ASSERT(target_node->Windows.Size == 0); + DockNodeMoveChildNodes(target_node, payload_node); + } + else + { + const ImGuiID payload_dock_id = payload_node->ID; + DockNodeMoveWindows(target_node, payload_node); + DockSettingsMoveDockReferencesInInactiveWindow(payload_dock_id, target_node->ID); + } + DockContextRemoveNode(ctx, payload_node, true); } - else + else if (payload_window) { - // Clear NavLastId for popups but keep it for regular child window so we can leave one and come back where we were - if (g.NavWindow && ((g.NavWindow->Flags & ImGuiWindowFlags_Popup) || !(g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow))) - g.NavWindow->NavLastIds[0] = 0; - g.NavId = 0; + // Transfer single window + const ImGuiID payload_dock_id = payload_window->DockId; + target_node->VisibleWindow = payload_window; + DockNodeAddWindow(target_node, payload_window, true); + if (payload_dock_id != 0) + DockSettingsMoveDockReferencesInInactiveWindow(payload_dock_id, target_node->ID); } } - // Process manual activation request - g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavInputId = 0; - if (g.NavId != 0 && !g.NavDisableHighlight && !g.NavWindowingTarget && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) + // Update selection immediately + if (ImGuiTabBar* tab_bar = target_node->TabBar) + tab_bar->NextSelectedTabId = tab_bar->WantFocusTabId = next_selected_id; + MarkIniSettingsDirty(); +} + +void ImGui::DockContextProcessUndockWindow(ImGuiContext* ctx, ImGuiWindow* window, bool clear_persistent_docking_ref) +{ + (void)ctx; + 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->DockTabIsVisible = false; + MarkIniSettingsDirty(); +} + +void ImGui::DockContextProcessUndockNode(ImGuiContext* ctx, ImGuiDockNode* node) +{ + (void)ctx; + IM_ASSERT(!node->IsSplitNode()); + IM_ASSERT(node->Windows.Size >= 1); + + // In the case of a root node or document root, the node will have to stay in place. Create a new node to receive the payload. + // Otherwise delete the previous node by merging the other sibling back into the parent node. + if (node->IsRootNode() || node->IsDocumentRoot) { - bool activate_down = IsNavInputDown(ImGuiNavInput_Activate); - bool activate_pressed = activate_down && IsNavInputPressed(ImGuiNavInput_Activate, ImGuiInputReadMode_Pressed); - if (g.ActiveId == 0 && activate_pressed) - g.NavActivateId = g.NavId; - if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_down) - g.NavActivateDownId = g.NavId; - if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_pressed) - g.NavActivatePressedId = g.NavId; - if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && IsNavInputPressed(ImGuiNavInput_Input, ImGuiInputReadMode_Pressed)) - g.NavInputId = g.NavId; + ImGuiDockNode* new_node = DockContextAddNode(ctx, 0); + DockNodeMoveWindows(new_node, node); + DockSettingsMoveDockReferencesInInactiveWindow(node->ID, new_node->ID); + for (int n = 0; n < new_node->Windows.Size; n++) + UpdateWindowParentAndRootLinks(new_node->Windows[n], new_node->Windows[n]->Flags, NULL); + new_node->WantMouseMove = true; } - if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) - g.NavDisableHighlight = true; - if (g.NavActivateId != 0) - IM_ASSERT(g.NavActivateDownId == g.NavActivateId); - g.NavMoveRequest = false; + else + { + 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->InitFromFirstWindowViewport = true; // The node that stays in place keeps the viewport, so our newly dragged out node will create a new viewport + node->ParentNode = NULL; + node->InitFromFirstWindowPosSize = true; + node->WantMouseMove = true; + } + MarkIniSettingsDirty(); + } + +//----------------------------------------------------------------------------- +// Docking: ImGuiDockNode +//----------------------------------------------------------------------------- + +ImGuiDockNode::ImGuiDockNode(ImGuiID id) +{ + ID = id; + Flags = 0; + ParentNode = ChildNodes[0] = ChildNodes[1] = NULL; + TabBar = NULL; + SplitAxis = ImGuiAxis_None; + + HostWindow = VisibleWindow = NULL; + OnlyNodeWithWindows = NULL; + LastFrameAlive = LastFrameActive = -1; + LastFocusedNodeID = 0; + SelectedTabID = 0; + WantCloseTabID = 0; + InitFromFirstWindowPosSize = InitFromFirstWindowViewport = false; + IsVisible = true; + IsDockSpace = IsDocumentRoot = HasCloseButton = HasCollapseButton = WantCloseAll = WantLockSizeOnce = WantMouseMove = 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->ID); + return tab ? tab_bar->GetTabOrder(tab) : -1; +} + +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); + + node->Windows.push_back(window); + window->DockNode = node; + window->DockId = node->ID; + window->DockIsActive = (node->Windows.Size > 1); + window->DockTabWantClose = false; + + // If 2+ windows appeared on the same frame, creating a new DockNode+TabBar from the second window, + // then we need to hide the first one after the fact otherwise it would be visible as a standalone window for one frame. + if (node->Windows.Size == 2 && node->HostWindow == NULL && node->Windows[0]->WasActive == false) + { + node->Windows[0]->Hidden = true; + node->Windows[0]->HiddenFramesRegular = 1; + } + + // When reactivating a node from two loose window, the target window pos/size are authoritative + if (node->Windows.Size == 2 && !node->IsDockSpace) + node->InitFromFirstWindowPosSize = node->InitFromFirstWindowViewport = true; + + // Add to tab bar if requested + if (add_to_tab_bar) + { + if (node->TabBar == NULL) + { + node->TabBar = IM_NEW(ImGuiTabBar)(); + node->TabBar->SelectedTabId = node->TabBar->NextSelectedTabId = node->SelectedTabID; + } + TabBarAddTab(node->TabBar, window, ImGuiTabItemFlags_Unsorted); +} + + 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->RootWindow == 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); + + window->DockNode = NULL; + window->DockIsActive = window->DockTabWantClose = false; + window->DockId = save_dock_id; + UpdateWindowParentAndRootLinks(window, window->Flags & ~ImGuiWindowFlags_ChildWindow, NULL); // Update immediately + + // 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; + } + IM_ASSERT(erased); + + // Remove tab and possibly tab bar + if (node->TabBar) + { + TabBarRemoveTab(node->TabBar, window->ID); + const int tab_count_threshold_for_tab_bar = node->IsDocumentRoot ? 1 : 2; + if (node->Windows.Size < tab_count_threshold_for_tab_bar) + { + IM_DELETE(node->TabBar); + node->TabBar = NULL; + } +} - // Process programmatic activation request - if (g.NavNextActivateId != 0) - g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavInputId = g.NavNextActivateId; - g.NavNextActivateId = 0; + if (node->Windows.Size == 0 && !node->IsDocumentRoot && window->DockId != node->ID) +{ + // Automatic dock node delete themselves if they are not holding at least one tab + DockContextRemoveNode(&g, node, true); + return; + } - // Initiate directional inputs request - const int allowed_dir_flags = (g.ActiveId == 0) ? ~0 : g.ActiveIdAllowNavDirFlags; - if (g.NavMoveRequestForward == ImGuiNavForward_None) + if (node->Windows.Size == 1 && !node->IsDocumentRoot && node->HostWindow) { - g.NavMoveDir = ImGuiDir_None; - g.NavMoveRequestFlags = 0; - if (g.NavWindow && !g.NavWindowingTarget && allowed_dir_flags && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) - { - if ((allowed_dir_flags & (1<Windows[0]; + if (node->HostWindow->ViewportOwned && node->IsRootNode()) + { + // Transfer viewport back to the remaining loose window + IM_ASSERT(node->HostWindow->Viewport->Window == node->HostWindow); + node->HostWindow->Viewport->Window = remaining_window; + node->HostWindow->Viewport->ID = remaining_window->ID; } - g.NavMoveClipDir = g.NavMoveDir; + remaining_window->Collapsed = node->HostWindow->Collapsed; } - else - { - // Forwarding previous request (which has been modified, e.g. wrap around menus rewrite the requests with a starting rectangle at the other side of the window) - // (Preserve g.NavMoveRequestFlags, g.NavMoveClipDir which were set by the NavMoveRequestForward() function) - IM_ASSERT(g.NavMoveDir != ImGuiDir_None && g.NavMoveClipDir != ImGuiDir_None); - IM_ASSERT(g.NavMoveRequestForward == ImGuiNavForward_ForwardQueued); - g.NavMoveRequestForward = ImGuiNavForward_ForwardActive; + + // Update visibility immediately is required so the DockNodeUpdateRemoveInactiveChilds() processing can reflect changes up the tree + DockNodeUpdateVisibleFlag(node); } - // Update PageUp/PageDown scroll - float nav_scoring_rect_offset_y = 0.0f; - if (nav_keyboard_active) - nav_scoring_rect_offset_y = NavUpdatePageUpPageDown(allowed_dir_flags); +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; +} - // If we initiate a movement request and have no current NavId, we initiate a InitDefautRequest that will be used as a fallback if the direction fails to find a match - if (g.NavMoveDir != ImGuiDir_None) +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) { - g.NavMoveRequest = true; - g.NavMoveDirLast = g.NavMoveDir; + dst_node->TabBar = src_node->TabBar; + src_node->TabBar = NULL; } - if (g.NavMoveRequest && g.NavId == 0) + + for (int n = 0; n < src_node->Windows.Size; n++) +{ + ImGuiWindow* window = src_tab_bar ? src_tab_bar->Tabs[n].Window : src_node->Windows[n]; + window->DockNode = NULL; + window->DockIsActive = false; + DockNodeAddWindow(dst_node, window, move_tab_bar ? false : true); +} + src_node->Windows.clear(); + + if (!move_tab_bar && src_node->TabBar) +{ + if (dst_node->TabBar) + dst_node->TabBar->SelectedTabId = src_node->TabBar->SelectedTabId; + IM_DELETE(src_node->TabBar); + src_node->TabBar = NULL; + } +} + +static void ImGui::DockNodeApplyPosSizeToWindows(ImGuiDockNode* node) +{ + for (int n = 0; n < node->Windows.Size; n++) { - g.NavInitRequest = g.NavInitRequestFromMove = true; - g.NavInitResultId = 0; - g.NavDisableHighlight = false; + node->Windows[n]->Pos = node->Pos; + node->Windows[n]->SizeFull = node->Size; } - NavUpdateAnyRequestFlag(); +} - // Scrolling - if (g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.NavWindowingTarget) +static void ImGui::DockNodeHideHostWindow(ImGuiDockNode* node) +{ + if (node->HostWindow) { - // *Fallback* manual-scroll with Nav directional keys when window has no navigable item - ImGuiWindow* window = g.NavWindow; - const float scroll_speed = ImFloor(window->CalcFontSize() * 100 * g.IO.DeltaTime + 0.5f); // We need round the scrolling speed because sub-pixel scroll isn't reliably supported. - if (window->DC.NavLayerActiveMask == 0x00 && window->DC.NavHasScroll && g.NavMoveRequest) - { - if (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) - SetWindowScrollX(window, ImFloor(window->Scroll.x + ((g.NavMoveDir == ImGuiDir_Left) ? -1.0f : +1.0f) * scroll_speed)); - if (g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) - SetWindowScrollY(window, ImFloor(window->Scroll.y + ((g.NavMoveDir == ImGuiDir_Up) ? -1.0f : +1.0f) * scroll_speed)); - } + if (node->HostWindow->DockNodeAsHost == node) + node->HostWindow->DockNodeAsHost = NULL; + node->HostWindow = NULL; + } - // *Normal* Manual scroll with NavScrollXXX keys - // Next movement request will clamp the NavId reference rectangle to the visible area, so navigation will resume within those bounds. - ImVec2 scroll_dir = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadLStick, ImGuiInputReadMode_Down, 1.0f/10.0f, 10.0f); - if (scroll_dir.x != 0.0f && window->ScrollbarX) - { - SetWindowScrollX(window, ImFloor(window->Scroll.x + scroll_dir.x * scroll_speed)); - g.NavMoveFromClampedRefRect = true; - } - if (scroll_dir.y != 0.0f) - { - SetWindowScrollY(window, ImFloor(window->Scroll.y + scroll_dir.y * scroll_speed)); - g.NavMoveFromClampedRefRect = true; - } + if (node->Windows.Size == 1) + { + node->VisibleWindow = node->Windows[0]; + node->Windows[0]->DockIsActive = false; } - // Reset search results - g.NavMoveResultLocal.Clear(); - g.NavMoveResultLocalVisibleSet.Clear(); - g.NavMoveResultOther.Clear(); + if (node->TabBar) +{ + IM_DELETE(node->TabBar); + node->TabBar = NULL; + } +} - // When we have manually scrolled (without using navigation) and NavId becomes out of bounds, we project its bounding box to the visible area to restart navigation within visible items - if (g.NavMoveRequest && g.NavMoveFromClampedRefRect && g.NavLayer == 0) +static void DockNodeUpdateFindOnlyNodeWithWindowsRec(ImGuiDockNode* node, int* p_count, ImGuiDockNode** p_first_node_with_windows) +{ + if (node->Windows.Size > 0) { - ImGuiWindow* window = g.NavWindow; - ImRect window_rect_rel(window->InnerMainRect.Min - window->Pos - ImVec2(1,1), window->InnerMainRect.Max - window->Pos + ImVec2(1,1)); - if (!window_rect_rel.Contains(window->NavRectRel[g.NavLayer])) - { - float pad = window->CalcFontSize() * 0.5f; - window_rect_rel.Expand(ImVec2(-ImMin(window_rect_rel.GetWidth(), pad), -ImMin(window_rect_rel.GetHeight(), pad))); // Terrible approximation for the intent of starting navigation from first fully visible item - window->NavRectRel[g.NavLayer].ClipWith(window_rect_rel); - g.NavId = 0; - } - g.NavMoveFromClampedRefRect = false; + if (*p_first_node_with_windows == NULL) + *p_first_node_with_windows = node; + (*p_count)++; } - - // For scoring we use a single segment on the left side our current item bounding box (not touching the edge to avoid box overlap with zero-spaced items) - ImRect nav_rect_rel = (g.NavWindow && !g.NavWindow->NavRectRel[g.NavLayer].IsInverted()) ? g.NavWindow->NavRectRel[g.NavLayer] : ImRect(0,0,0,0); - g.NavScoringRectScreen = g.NavWindow ? ImRect(g.NavWindow->Pos + nav_rect_rel.Min, g.NavWindow->Pos + nav_rect_rel.Max) : GetViewportRect(); - g.NavScoringRectScreen.TranslateY(nav_scoring_rect_offset_y); - g.NavScoringRectScreen.Min.x = ImMin(g.NavScoringRectScreen.Min.x + 1.0f, g.NavScoringRectScreen.Max.x); - g.NavScoringRectScreen.Max.x = g.NavScoringRectScreen.Min.x; - IM_ASSERT(!g.NavScoringRectScreen.IsInverted()); // Ensure if we have a finite, non-inverted bounding box here will allows us to remove extraneous ImFabs() calls in NavScoreItem(). - //g.OverlayDrawList.AddRect(g.NavScoringRectScreen.Min, g.NavScoringRectScreen.Max, IM_COL32(255,200,0,255)); // [DEBUG] - g.NavScoringCount = 0; -#if IMGUI_DEBUG_NAV_RECTS - if (g.NavWindow) { for (int layer = 0; layer < 2; layer++) GetOverlayDrawList()->AddRect(g.NavWindow->Pos + g.NavWindow->NavRectRel[layer].Min, g.NavWindow->Pos + g.NavWindow->NavRectRel[layer].Max, IM_COL32(255,200,0,255)); } // [DEBUG] - if (g.NavWindow) { ImU32 col = (g.NavWindow->HiddenFrames == 0) ? IM_COL32(255,0,255,255) : IM_COL32(255,0,0,255); ImVec2 p = NavCalcPreferredRefPos(); char buf[32]; ImFormatString(buf, 32, "%d", g.NavLayer); g.OverlayDrawList.AddCircleFilled(p, 3.0f, col); g.OverlayDrawList.AddText(NULL, 13.0f, p + ImVec2(8,-4), col, buf); } -#endif + if (*p_count > 1) + return; + if (node->ChildNodes[0]) + DockNodeUpdateFindOnlyNodeWithWindowsRec(node->ChildNodes[0], p_count, p_first_node_with_windows); + if (node->ChildNodes[1]) + DockNodeUpdateFindOnlyNodeWithWindowsRec(node->ChildNodes[1], p_count, p_first_node_with_windows); } -static void ImGui::NavUpdateMoveResult() +static void ImGui::DockNodeUpdateVisibleFlagAndInactiveChilds(ImGuiDockNode* node) { - // Select which result to use ImGuiContext& g = *GImGui; - ImGuiNavMoveResult* result = (g.NavMoveResultLocal.ID != 0) ? &g.NavMoveResultLocal : &g.NavMoveResultOther; - // PageUp/PageDown behavior first jumps to the bottom/top mostly visible item, _otherwise_ use the result from the previous/next page. - if (g.NavMoveRequestFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet) - if (g.NavMoveResultLocalVisibleSet.ID != 0 && g.NavMoveResultLocalVisibleSet.ID != g.NavId) - result = &g.NavMoveResultLocalVisibleSet; + IM_ASSERT(node->ParentNode == NULL || node->ParentNode->ChildNodes[0] == node || node->ParentNode->ChildNodes[1] == node); - // Maybe entering a flattened child from the outside? In this case solve the tie using the regular scoring rules. - if (result != &g.NavMoveResultOther && g.NavMoveResultOther.ID != 0 && g.NavMoveResultOther.Window->ParentWindow == g.NavWindow) - if ((g.NavMoveResultOther.DistBox < result->DistBox) || (g.NavMoveResultOther.DistBox == result->DistBox && g.NavMoveResultOther.DistCenter < result->DistCenter)) - result = &g.NavMoveResultOther; - IM_ASSERT(g.NavWindow && result->Window); + // 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) + if (node->ChildNodes[0]) + DockNodeUpdateVisibleFlagAndInactiveChilds(node->ChildNodes[0]); + if (node->ChildNodes[1]) + DockNodeUpdateVisibleFlagAndInactiveChilds(node->ChildNodes[1]); - // Scroll to keep newly navigated item fully into view. - if (g.NavLayer == 0) + // Remove inactive windows + 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->ID) && window->HasCloseButton && !(window->Flags & ImGuiWindowFlags_UnsavedDocument); // Submit all _expected_ closure from last frame + remove |= (window->DockTabWantClose); + if (!remove) + continue; + window->DockTabWantClose = false; + if (node->Windows.Size == 1 && !node->IsDocumentRoot) { - ImRect rect_abs = ImRect(result->RectRel.Min + result->Window->Pos, result->RectRel.Max + result->Window->Pos); - NavScrollToBringItemIntoView(result->Window, rect_abs); + DockNodeHideHostWindow(node); + DockNodeRemoveWindow(node, window, node->ID); // Will delete the node so it'll be invalid on return + return; + } + DockNodeRemoveWindow(node, window, node->ID); + window_n--; + } - // Estimate upcoming scroll so we can offset our result position so mouse position can be applied immediately after in NavUpdate() - ImVec2 next_scroll = CalcNextScrollFromScrollTargetAndClamp(result->Window, false); - ImVec2 delta_scroll = result->Window->Scroll - next_scroll; - result->RectRel.Translate(delta_scroll); + DockNodeUpdateVisibleFlag(node); +} - // Also scroll parent window to keep us into view if necessary (we could/should technically recurse back the whole the parent hierarchy). - if (result->Window->Flags & ImGuiWindowFlags_ChildWindow) - NavScrollToBringItemIntoView(result->Window->ParentWindow, ImRect(rect_abs.Min + delta_scroll, rect_abs.Max + delta_scroll)); - } +static void ImGui::DockNodeUpdateVisibleFlag(ImGuiDockNode* node) +{ + // Update visibility flag + bool is_visible = (node->ParentNode == 0) ? node->IsDockSpace : node->IsDocumentRoot; + 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; +} - // Apply result from previous frame navigation directional move request - ClearActiveID(); - g.NavWindow = result->Window; - SetNavIDWithRectRel(result->ID, g.NavLayer, result->RectRel); - g.NavJustMovedToId = result->ID; - g.NavMoveFromClampedRefRect = false; +static void ImGui::DockNodeStartMouseMovingWindow(ImGuiDockNode* node, ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(node->WantMouseMove == true); + ImVec2 backup_active_click_offset = g.ActiveIdClickOffset; + StartMouseMovingWindow(window); + g.MovingWindow = window; // If we are docked into a non moveable root widnow, StartMouseMovingWindow() won't set g.MovingWindow. OVerride that decision. + node->WantMouseMove = false; + g.ActiveIdClickOffset = backup_active_click_offset; } -static float ImGui::NavUpdatePageUpPageDown(int allowed_dir_flags) +static void ImGui::DockNodeUpdate(ImGuiDockNode* node) { ImGuiContext& g = *GImGui; - if (g.NavMoveDir == ImGuiDir_None && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.NavWindowingTarget && g.NavLayer == 0) + IM_ASSERT(node->LastFrameActive != g.FrameCount); + node->LastFrameAlive = g.FrameCount; + + if (node->IsRootNode()) { - ImGuiWindow* window = g.NavWindow; - bool page_up_held = IsKeyDown(g.IO.KeyMap[ImGuiKey_PageUp]) && (allowed_dir_flags & (1 << ImGuiDir_Up)); - bool page_down_held = IsKeyDown(g.IO.KeyMap[ImGuiKey_PageDown]) && (allowed_dir_flags & (1 << ImGuiDir_Down)); - if ((page_up_held && !page_down_held) || (page_down_held && !page_up_held)) + DockNodeUpdateVisibleFlagAndInactiveChilds(node); + + // 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: Not done yet!) + if (!node->IsDockSpace) { - if (window->DC.NavLayerActiveMask == 0x00 && window->DC.NavHasScroll) + int count = 0; + ImGuiDockNode* first_node_with_windows = NULL; + DockNodeUpdateFindOnlyNodeWithWindowsRec(node, &count, &first_node_with_windows); + node->OnlyNodeWithWindows = (count == 1 ? first_node_with_windows : NULL); + if (node->LastFocusedNodeID == 0 && first_node_with_windows != NULL) + node->LastFocusedNodeID = first_node_with_windows->ID; + + // Copy the dock family from of our window so it can be used for proper dock filtering. + // When node has mixed windows, prioritize the family with the most constraint (CompatibleWithNeutral = false) as the reference to copy. + if (first_node_with_windows) { - // Fallback manual-scroll when window has no navigable item - if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageUp], true)) - SetWindowScrollY(window, window->Scroll.y - window->InnerClipRect.GetHeight()); - else if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageDown], true)) - SetWindowScrollY(window, window->Scroll.y + window->InnerClipRect.GetHeight()); + node->DockFamily = first_node_with_windows->Windows[0]->DockFamily; + for (int n = 1; n < first_node_with_windows->Windows.Size; n++) + if (first_node_with_windows->Windows[n]->DockFamily.CompatibleWithFamilyZero == false) + { + node->DockFamily = first_node_with_windows->Windows[n]->DockFamily; + break; + } } - else + } + } + + // 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) + if (node->IsRootNode() && !node->IsSplitNode() && node->Windows.Size <= 1 && !node->IsDockSpace) + { + 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; + + // 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) { - const ImRect& nav_rect_rel = window->NavRectRel[g.NavLayer]; - const float page_offset_y = ImMax(0.0f, window->InnerClipRect.GetHeight() - window->CalcFontSize() * 1.0f + nav_rect_rel.GetHeight()); - float nav_scoring_rect_offset_y = 0.0f; - if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageUp], true)) + single_window->Viewport = node->HostWindow->Viewport; + single_window->ViewportId = node->HostWindow->ViewportId; + if (node->HostWindow->ViewportOwned) { - nav_scoring_rect_offset_y = -page_offset_y; - g.NavMoveDir = ImGuiDir_Down; // Because our scoring rect is offset, we intentionally request the opposite direction (so we can always land on the last item) - g.NavMoveClipDir = ImGuiDir_Up; - g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet; + single_window->Viewport->Window = single_window; + single_window->ViewportOwned = true; } - else if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageDown], true)) + } + } + + DockNodeHideHostWindow(node); + node->InitFromFirstWindowPosSize = node->InitFromFirstWindowViewport = false; + node->WantCloseAll = false; + node->WantCloseTabID = 0; + node->HasCloseButton = node->HasCollapseButton = false; + node->LastFrameActive = g.FrameCount; + + if (node->WantMouseMove && node->Windows.Size == 1) + DockNodeStartMouseMovingWindow(node, node->Windows[0]); + return; + } + + ImGuiWindow* host_window = NULL; + bool beginned_into_host_window = false; + if (node->IsDockSpace) { - nav_scoring_rect_offset_y = +page_offset_y; - g.NavMoveDir = ImGuiDir_Up; // Because our scoring rect is offset, we intentionally request the opposite direction (so we can always land on the last item) - g.NavMoveClipDir = ImGuiDir_Down; - g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet; + // [Explicit root dockspace node] + IM_ASSERT(node->HostWindow); + node->HasCloseButton = false; + node->HasCollapseButton = true; + host_window = node->HostWindow; + } + else + { + // [Automatic root or child nodes] + node->HasCloseButton = false; + node->HasCollapseButton = (node->Windows.Size > 0); + for (int window_n = 0; window_n < node->Windows.Size; window_n++) + { + ImGuiWindow* window = node->Windows[window_n]; + window->DockIsActive = (node->Windows.Size > 1); + node->HasCloseButton |= window->HasCloseButton; + } + + if (node->IsRootNode() && node->IsVisible) + { + if (node->InitFromFirstWindowPosSize && node->Windows.Size > 0) + { + ImGuiWindow* init_window = node->Windows[0]; + SetNextWindowPos(init_window->Pos); + SetNextWindowSize(init_window->SizeFull); + SetNextWindowCollapsed(init_window->Collapsed); + } + else if (node->HostWindow == NULL) + { + // Leaf + SetNextWindowPos(node->Pos); + SetNextWindowSize(node->Size); + } + if (node->InitFromFirstWindowViewport && node->Windows.Size > 0) + SetNextWindowViewport(node->Windows[0]->ViewportId); + + // 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; + window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse; + + PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + Begin(window_label, NULL, window_flags); + PopStyleVar(); + beginned_into_host_window = true; + + node->HostWindow = host_window = g.CurrentWindow; + host_window->DockNodeAsHost = node; + host_window->DC.CursorPos = host_window->Pos; + node->Pos = host_window->Pos; + node->Size = host_window->Size; + } + else if (node->ParentNode) + { + node->HostWindow = host_window = node->ParentNode->HostWindow; } - return nav_scoring_rect_offset_y; + node->InitFromFirstWindowPosSize = node->InitFromFirstWindowViewport = false; + if (node->WantMouseMove && node->HostWindow) + DockNodeStartMouseMovingWindow(node, node->HostWindow); } - } + + // Update active node (the one whose title bar is highlight) within a node tree + if (node->IsSplitNode()) + IM_ASSERT(node->TabBar == NULL); + if (node->IsRootNode()) + { + //if (!node->IsSplitNode()) + // node->LastFocusedNodeID = node->ID; // This also ensure on our creation frame we will receive the title screen highlight + //else + if (g.NavWindow && g.NavWindow->RootWindowDockStop->DockNode && g.NavWindow->RootWindowDockStop->ParentWindow == host_window) + node->LastFocusedNodeID = g.NavWindow->RootWindowDockStop->DockNode->ID; } - return 0.0f; -} -static int FindWindowIndex(ImGuiWindow* window) // FIXME-OPT O(N) -{ - ImGuiContext& g = *GImGui; - for (int i = g.Windows.Size-1; i >= 0; i--) - if (g.Windows[i] == window) - return i; - return -1; -} + // Draw and populate Tab Bar + if (host_window && node->Windows.Size > 0) + { + DockNodeUpdateTabBar(node, host_window); + if (node->TabBar->SelectedTabId) + node->SelectedTabID = node->TabBar->SelectedTabId; + } + else + { + node->WantCloseAll = false; + node->WantCloseTabID = 0; + if (node->Windows.Size > 0) + node->SelectedTabID = node->Windows[0]->ID; + } + if (host_window && node->IsVisible) + { + // Background for empty nodes + if (node->Windows.Size == 0 && !node->IsSplitNode()) + host_window->DrawList->AddRectFilled(node->Pos, node->Pos + node->Size, GetColorU32(ImGuiCol_DockingBg)); -static ImGuiWindow* FindWindowNavFocusable(int i_start, int i_stop, int dir) // FIXME-OPT O(N) -{ - ImGuiContext& g = *GImGui; - for (int i = i_start; i >= 0 && i < g.Windows.Size && i != i_stop; i += dir) - if (ImGui::IsWindowNavFocusable(g.Windows[i])) - return g.Windows[i]; - return NULL; -} + // Drop target + if (node->IsRootNode() && (g.MovingWindow == NULL || g.MovingWindow->RootWindow != host_window)) + BeginAsDockableDragDropTarget(host_window); + } + node->LastFrameActive = g.FrameCount; -static void NavUpdateWindowingHighlightWindow(int focus_change_dir) -{ - ImGuiContext& g = *GImGui; - IM_ASSERT(g.NavWindowingTarget); - if (g.NavWindowingTarget->Flags & ImGuiWindowFlags_Modal) - return; + // Update pos/size and recurse into children + if (host_window) + { + if (node->IsRootNode()) + { + DockNodeTreeUpdatePosSize(node, host_window->Pos, host_window->Size); + DockNodeTreeUpdateSplitter(node); + } + if (node->ChildNodes[0]) + DockNodeUpdate(node->ChildNodes[0]); + if (node->ChildNodes[1]) + DockNodeUpdate(node->ChildNodes[1]); - const int i_current = FindWindowIndex(g.NavWindowingTarget); - ImGuiWindow* window_target = FindWindowNavFocusable(i_current + focus_change_dir, -INT_MAX, focus_change_dir); - if (!window_target) - window_target = FindWindowNavFocusable((focus_change_dir < 0) ? (g.Windows.Size - 1) : 0, i_current, focus_change_dir); - if (window_target) // Don't reset windowing target if there's a single window in the list - g.NavWindowingTarget = g.NavWindowingTargetAnim = window_target; - g.NavWindowingToggleLayer = false; + // End host window + if (beginned_into_host_window) + 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); } -// Window management mode (hold to: change focus/move/resize, tap to: toggle menu layer) -static void ImGui::NavUpdateWindowing() +static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_window) { ImGuiContext& g = *GImGui; - ImGuiWindow* apply_focus_window = NULL; - bool apply_toggle_layer = false; + ImGuiStyle& style = g.Style; - ImGuiWindow* modal_window = GetFrontMostPopupModal(); - if (modal_window != NULL) + 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; + + // 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) { - g.NavWindowingTarget = NULL; - return; + host_window->SkipItems = false; + host_window->DC.NavLayerCurrent++; + host_window->DC.NavLayerCurrentMask <<= 1; } - // Fade out - if (g.NavWindowingTargetAnim && g.NavWindowingTarget == NULL) + PushID(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) + tab_bar = node->TabBar = IM_NEW(ImGuiTabBar)(); + + // Decide if we should use a focused title bar color + bool is_focused = false; + ImGuiDockNode* root_node = DockNodeGetRootNode(node); + if (g.NavWindowingTarget) + is_focused = (g.NavWindowingTarget->DockNode == node); + else if (g.NavWindow && g.NavWindow->RootWindowForTitleBarHighlight == host_window->RootWindow && root_node->LastFocusedNodeID == node->ID) + is_focused = true; + + // Collapse button changes shape and display a list + if (IsPopupOpen("#TabListMenu")) { - g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha - g.IO.DeltaTime * 10.0f, 0.0f); - if (g.DimBgRatio <= 0.0f && g.NavWindowingHighlightAlpha <= 0.0f) - g.NavWindowingTargetAnim = NULL; + SetNextWindowPos(ImVec2(node->Pos.x - style.FramePadding.x, node->Pos.y + GetFrameHeight())); + if (BeginPopup("#TabListMenu")) + { + is_focused = true; + for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++) + { + ImGuiTabItem* tab = &tab_bar->Tabs[tab_n]; + IM_ASSERT(tab->Window != NULL); + if (Selectable(tab->Window->Name, tab->ID == tab_bar->SelectedTabId)) + tab_bar->NextSelectedTabId = tab_bar->WantFocusTabId = tab->ID; + SameLine(); + Text(" "); + } + EndPopup(); + } } - // Start CTRL-TAB or Square+L/R window selection - bool start_windowing_with_gamepad = !g.NavWindowingTarget && IsNavInputPressed(ImGuiNavInput_Menu, ImGuiInputReadMode_Pressed); - bool start_windowing_with_keyboard = !g.NavWindowingTarget && g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab) && (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard); - if (start_windowing_with_gamepad || start_windowing_with_keyboard) - if (ImGuiWindow* window = g.NavWindow ? g.NavWindow : FindWindowNavFocusable(g.Windows.Size - 1, -INT_MAX, -1)) + ImGuiID focus_tab_id = 0; + + // Title bar + ImRect title_bar_rect = ImRect(node->Pos, node->Pos + ImVec2(node->Size.x, g.FontSize + style.FramePadding.y * 2.0f)); + ImU32 title_bar_col = GetColorU32(host_window->Collapsed ? ImGuiCol_TitleBgCollapsed : is_focused ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg); + host_window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, host_window->WindowRounding, ImDrawCornerFlags_Top); + host_window->DrawList->AddLine(title_bar_rect.GetBL(), title_bar_rect.GetBR(), GetColorU32(ImGuiCol_Border), style.WindowBorderSize); + + // Collapse button + if (CollapseButton(host_window->GetID("#COLLAPSE"), title_bar_rect.Min, node)) + OpenPopup("#TabListMenu"); + if (IsItemActive()) + focus_tab_id = tab_bar->SelectedTabId; + + // Submit new tabs + const int tabs_count_old = tab_bar->Tabs.Size; + for (int window_n = 0; window_n < node->Windows.Size; window_n++) + if (TabBarFindTabByID(tab_bar, node->Windows[window_n]->ID) == NULL) + TabBarAddTab(tab_bar, node->Windows[window_n], ImGuiTabItemFlags_Unsorted); + + // 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--) + { + tab_bar->Tabs[tab_n].Flags &= ~ImGuiTabItemFlags_Unsorted; + tabs_unsorted_start = tab_n; + } + //printf("[%05d] Sorting %d new appearing tabs\n", g.FrameCount, tab_bar->Tabs.Size - tabs_unsorted_start); + 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); + + // 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->ID; + + // Begin tab bar + const ImRect tab_bar_rect = DockNodeCalcTabBarRect(node); + ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_AutoSelectNewTabs | ImGuiTabBarFlags_NoTabListPopupButton;// | ImGuiTabBarFlags_NoTabListScrollingButtons); + tab_bar_flags |= ImGuiTabBarFlags_SaveSettings; + tab_bar_flags |= ImGuiTabBarFlags_DockNode | (node->IsDockSpace ? ImGuiTabBarFlags_DockNodeIsDockSpace : 0); + if (!host_window->Collapsed && is_focused) + tab_bar_flags |= ImGuiTabBarFlags_IsFocused; + BeginTabBarEx(tab_bar, tab_bar_rect, tab_bar_flags, node); + //host_window->DrawList->AddRect(tab_bar_rect.Min, tab_bar_rect.Max, IM_COL32(255,0,255,255)); + + // 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->ID) && window->HasCloseButton && !(window->Flags & ImGuiWindowFlags_UnsavedDocument)) + continue; + if (window->LastFrameActive + 1 >= g.FrameCount || !node_was_active) { - g.NavWindowingTarget = g.NavWindowingTargetAnim = window; - g.NavWindowingTimer = g.NavWindowingHighlightAlpha = 0.0f; - g.NavWindowingToggleLayer = start_windowing_with_keyboard ? false : true; - g.NavInputSource = start_windowing_with_keyboard ? ImGuiInputSource_NavKeyboard : ImGuiInputSource_NavGamepad; + ImGuiTabItemFlags tab_item_flags = ImGuiTabItemFlags_DockedWindow; + if (window->Flags & ImGuiWindowFlags_UnsavedDocument) + tab_item_flags |= ImGuiTabItemFlags_UnsavedDocument; + if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton) + tab_item_flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton; + + bool tab_open = true; + TabItemEx(tab_bar, window->Name, window->HasCloseButton ? &tab_open : NULL, tab_item_flags, window); + if (!tab_open) + node->WantCloseTabID = window->ID; + if (tab_bar->VisibleTabId == window->ID) + node->VisibleWindow = window; + + // Store last item data so it can be queried with IsItemXXX functions after the user Begin() call + window->DockTabItemStatusFlags = host_window->DC.LastItemStatusFlags; + window->DockTabItemRect = host_window->DC.LastItemRect; + + // Update navigation ID on menu layer + if (g.NavWindow && g.NavWindow->RootWindowDockStop == window && (window->DC.NavLayerActiveMask & (1 << 1)) == 0) + host_window->NavLastIds[1] = window->ID; } + } - // Gamepad update - g.NavWindowingTimer += g.IO.DeltaTime; - if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_NavGamepad) - { - // Highlight only appears after a brief time holding the button, so that a fast tap on PadMenu (to toggle NavLayer) doesn't add visual noise - g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f)); + // 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; - // Select window to focus - const int focus_change_dir = (int)IsNavInputPressed(ImGuiNavInput_FocusPrev, ImGuiInputReadMode_RepeatSlow) - (int)IsNavInputPressed(ImGuiNavInput_FocusNext, ImGuiInputReadMode_RepeatSlow); - if (focus_change_dir != 0) + // Close button (after VisibleWindow was updated) + // Note that VisibleWindow may have been overrided by CTRL+Tabbing, so VisibleWindow->ID may be != from tab_bar->SelectedTabId + if (node->VisibleWindow) + { + if (!node->VisibleWindow->HasCloseButton) { - NavUpdateWindowingHighlightWindow(focus_change_dir); - g.NavWindowingHighlightAlpha = 1.0f; + PushItemFlag(ImGuiItemFlags_Disabled, true); + PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_Text] * ImVec4(1.0f,1.0f,1.0f,0.5f)); } - - // Single press toggles NavLayer, long press with L/R apply actual focus on release (until then the window was merely rendered front-most) - if (!IsNavInputDown(ImGuiNavInput_Menu)) + const float rad = g.FontSize * 0.5f; + if (CloseButton(host_window->GetID("#CLOSE"), title_bar_rect.GetTR() + ImVec2(-style.FramePadding.x - rad, style.FramePadding.y + rad), rad + 1)) + if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_bar->VisibleTabId)) + { + node->WantCloseTabID = tab->ID; + TabBarCloseTab(tab_bar, tab); + } + //if (IsItemActive()) + // focus_tab_id = tab_bar->SelectedTabId; + if (!node->VisibleWindow->HasCloseButton) { - g.NavWindowingToggleLayer &= (g.NavWindowingHighlightAlpha < 1.0f); // Once button was held long enough we don't consider it a tap-to-toggle-layer press anymore. - if (g.NavWindowingToggleLayer && g.NavWindow) - apply_toggle_layer = true; - else if (!g.NavWindowingToggleLayer) - apply_focus_window = g.NavWindowingTarget; - g.NavWindowingTarget = NULL; + PopStyleColor(); + PopItemFlag(); } } - // Keyboard: Focus - if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_NavKeyboard) + // When clicked on a tab we requested focus to the docked child + if (tab_bar->WantFocusTabId) + focus_tab_id = tab_bar->WantFocusTabId; + + // When clicking on the title bar outside of tabs, we still focus the selected tab for that node + if (g.HoveredWindow == host_window && g.HoveredId == 0 && IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max) && IsMouseClicked(0)) { - // Visuals only appears after a brief time after pressing TAB the first time, so that a fast CTRL+TAB doesn't add visual noise - g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f)); // 1.0f - if (IsKeyPressedMap(ImGuiKey_Tab, true)) - NavUpdateWindowingHighlightWindow(g.IO.KeyShift ? +1 : -1); - if (!g.IO.KeyCtrl) - apply_focus_window = g.NavWindowingTarget; + focus_tab_id = tab_bar->SelectedTabId; + if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, focus_tab_id)) + StartMouseMovingWindow(tab->Window); } - // Keyboard: Press and Release ALT to toggle menu layer - // FIXME: We lack an explicit IO variable for "is the imgui window focused", so compare mouse validity to detect the common case of back-end clearing releases all keys on ALT-TAB - if ((g.ActiveId == 0 || g.ActiveIdAllowOverlap) && IsNavInputPressed(ImGuiNavInput_KeyMenu_, ImGuiInputReadMode_Released)) - if (IsMousePosValid(&g.IO.MousePos) == IsMousePosValid(&g.IO.MousePosPrev)) - apply_toggle_layer = true; + // Forward focus from host node to selected window + if (is_focused && g.NavWindow == host_window && !g.NavWindowingTarget) + focus_tab_id = tab_bar->SelectedTabId; - // Move window - if (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoMove)) - { - ImVec2 move_delta; - if (g.NavInputSource == ImGuiInputSource_NavKeyboard && !g.IO.KeyShift) - move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard, ImGuiInputReadMode_Down); - if (g.NavInputSource == ImGuiInputSource_NavGamepad) - move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadLStick, ImGuiInputReadMode_Down); - if (move_delta.x != 0.0f || move_delta.y != 0.0f) + // Apply navigation focus + if (focus_tab_id != 0) + if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, focus_tab_id)) { - const float NAV_MOVE_SPEED = 800.0f; - const float move_speed = ImFloor(NAV_MOVE_SPEED * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y)); // FIXME: Doesn't code variable framerate very well - g.NavWindowingTarget->RootWindow->Pos += move_delta * move_speed; - g.NavDisableMouseHover = true; - MarkIniSettingsDirty(g.NavWindowingTarget); + FocusWindow(tab->Window); + NavInitWindow(tab->Window, false); } - } - // Apply final focus - if (apply_focus_window && (g.NavWindow == NULL || apply_focus_window != g.NavWindow->RootWindow)) + EndTabBar(); + PopID(); + + // Restore SkipItems flag + if (!node->IsDockSpace) { - g.NavDisableHighlight = false; - g.NavDisableMouseHover = true; - apply_focus_window = NavRestoreLastChildNavWindow(apply_focus_window); - ClosePopupsOverWindow(apply_focus_window); - FocusWindow(apply_focus_window); - if (apply_focus_window->NavLastIds[0] == 0) - NavInitWindow(apply_focus_window, false); + host_window->DC.NavLayerCurrent--; + host_window->DC.NavLayerCurrentMask >>= 1; + host_window->SkipItems = backup_skip_item; + } +} - // If the window only has a menu layer, select it directly - if (apply_focus_window->DC.NavLayerActiveMask == (1 << 1)) - g.NavLayer = 1; +static bool DockNodeIsDropAllowedOne(ImGuiWindow* payload, ImGuiWindow* host_window) +{ + if ((host_window->Flags & ImGuiWindowFlags_DockNodeHost) && host_window->DockNodeAsHost->IsDockSpace && payload->BeginOrderWithinContext < host_window->BeginOrderWithinContext) + return false; + + ImGuiDockFamily* host_family = host_window->DockNodeAsHost ? &host_window->DockNodeAsHost->DockFamily : &host_window->DockFamily; + ImGuiDockFamily* payload_family = &payload->DockFamily; + if (host_family->ID != payload_family->ID) + { + if (host_family->ID != 0 && host_family->CompatibleWithFamilyZero && payload_family->ID == 0) + return true; + if (payload_family->ID != 0 && payload_family->CompatibleWithFamilyZero && host_family->ID == 0) + return true; + return false; } - if (apply_focus_window) - g.NavWindowingTarget = NULL; - // Apply menu/layer toggle - if (apply_toggle_layer && g.NavWindow) + return true; +} + +static bool ImGui::DockNodeIsDropAllowed(ImGuiWindow* host_window, ImGuiWindow* root_payload) +{ + if (root_payload->DockNodeAsHost && root_payload->DockNodeAsHost->IsSplitNode()) + 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++) { - // Move to parent menu if necessary - ImGuiWindow* new_nav_window = g.NavWindow; - while ((new_nav_window->DC.NavLayerActiveMask & (1 << 1)) == 0 && (new_nav_window->Flags & ImGuiWindowFlags_ChildWindow) != 0 && (new_nav_window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0) - new_nav_window = new_nav_window->ParentWindow; - if (new_nav_window != g.NavWindow) - { - ImGuiWindow* old_nav_window = g.NavWindow; - FocusWindow(new_nav_window); - new_nav_window->NavLastChildNavWindow = old_nav_window; - } - g.NavDisableHighlight = false; - g.NavDisableMouseHover = true; - NavRestoreLayer((g.NavWindow->DC.NavLayerActiveMask & (1 << 1)) ? (g.NavLayer ^ 1) : 0); + ImGuiWindow* payload = root_payload->DockNodeAsHost ? root_payload->DockNodeAsHost->Windows[payload_n] : root_payload; + if (DockNodeIsDropAllowedOne(payload, host_window)) + return true; } + return false; } -// Window has already passed the IsWindowNavFocusable() -static const char* GetFallbackWindowNameForWindowingList(ImGuiWindow* window) +static ImRect ImGui::DockNodeCalcTabBarRect(const ImGuiDockNode* node) { - if (window->Flags & ImGuiWindowFlags_Popup) - return "(Popup)"; - if ((window->Flags & ImGuiWindowFlags_MenuBar) && strcmp(window->Name, "##MainMenuBar") == 0) - return "(Main menu bar)"; - return "(Untitled)"; + ImGuiContext& g = *GImGui; + 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 (node->HasCollapseButton) + r.Min.x += g.Style.FramePadding.x + g.FontSize; // + g.Style.ItemInnerSpacing.x; // <-- Adding ItemInnerSpacing makes the title text moves slightly when in a tab bar. Instead we adjusted RenderArrowDockMenu() + if (node->HasCloseButton) + r.Max.x -= g.Style.FramePadding.x + g.FontSize + 1.0f; + return r; } -// Overlay displayed when using CTRL+TAB. Called by EndFrame(). -void ImGui::NavUpdateWindowingList() +void ImGui::DockNodeCalcSplitRects(ImVec2& pos_old, ImVec2& size_old, ImVec2& pos_new, ImVec2& size_new, ImGuiDir dir, ImVec2 size_new_desired) { ImGuiContext& g = *GImGui; - IM_ASSERT(g.NavWindowingTarget != NULL); + 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]; - if (g.NavWindowingTimer < NAV_WINDOWING_LIST_APPEAR_DELAY) - return; + // 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] = (float)(int)(w_avail - size_new[axis]); + } + else + { + size_new[axis] = (float)(int)(w_avail * 0.5f); + size_old[axis] = (float)(int)(w_avail - size_new[axis]); + } - if (g.NavWindowingList == NULL) - g.NavWindowingList = FindWindowByName("###NavWindowingList"); - SetNextWindowSizeConstraints(ImVec2(g.IO.DisplaySize.x * 0.20f, g.IO.DisplaySize.y * 0.20f), ImVec2(FLT_MAX, FLT_MAX)); - SetNextWindowPos(g.IO.DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); - PushStyleVar(ImGuiStyleVar_WindowPadding, g.Style.WindowPadding * 2.0f); - Begin("###NavWindowingList", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings); - for (int n = g.Windows.Size - 1; n >= 0; n--) + // Position each node + if (dir == ImGuiDir_Right || dir == ImGuiDir_Down) { - ImGuiWindow* window = g.Windows[n]; - if (!IsWindowNavFocusable(window)) - continue; - const char* label = window->Name; - if (label == FindRenderedTextEnd(label)) - label = GetFallbackWindowNameForWindowingList(window); - Selectable(label, g.NavWindowingTarget == window); + 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; } - End(); - PopStyleVar(); } -//----------------------------------------------------------------------------- -// [SECTION] COLUMNS -// In the current version, Columns are very weak. Needs to be replaced with a more full-featured system. -//----------------------------------------------------------------------------- - -void ImGui::NextColumn() +// Retrieve the drop rectangles for a given direction or for the center + perform hit testing. +bool ImGui::DockNodeCalcDropRects(const ImRect& parent, ImGuiDir dir, ImRect& out_r, bool outer_docking) { - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems || window->DC.ColumnsSet == NULL) - return; - ImGuiContext& g = *GImGui; - PopItemWidth(); - PopClipRect(); - ImGuiColumnsSet* columns = window->DC.ColumnsSet; - columns->LineMaxY = ImMax(columns->LineMaxY, window->DC.CursorPos.y); - if (++columns->Current < columns->Count) + 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) { - // Columns 1+ cancel out IndentX - window->DC.ColumnsOffset.x = GetColumnOffset(columns->Current) - window->DC.Indent.x + g.Style.ItemSpacing.x; - window->DrawList->ChannelsSetCurrent(columns->Current); + //hs_w = ImFloor(ImClamp(parent_smaller_axis - hs_for_central_nodes * 4.0f, g.FontSize * 0.5f, g.FontSize * 8.0f)); + //hs_h = ImFloor(hs_w * 0.15f); + //off = ImVec2(ImFloor(parent.GetWidth() * 0.5f - GetFrameHeightWithSpacing() * 1.4f - hs_h), ImFloor(parent.GetHeight() * 0.5f - GetFrameHeightWithSpacing() * 1.4f - hs_h)); + hs_w = ImFloor(hs_for_central_nodes * 1.50f); + hs_h = ImFloor(hs_for_central_nodes * 0.80f); + off = ImVec2(ImFloor(parent.GetWidth() * 0.5f - GetFrameHeightWithSpacing() * 0.0f - hs_h), ImFloor(parent.GetHeight() * 0.5f - GetFrameHeightWithSpacing() * 0.0f - hs_h)); } else { - window->DC.ColumnsOffset.x = 0.0f; - window->DrawList->ChannelsSetCurrent(0); - columns->Current = 0; - columns->LineMinY = columns->LineMaxY; + hs_w = ImFloor(hs_for_central_nodes); + hs_h = ImFloor(hs_for_central_nodes * 0.90f); + off = ImVec2(ImFloor(hs_w * 2.40f), ImFloor(hs_w * 2.40f)); } - window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); - window->DC.CursorPos.y = columns->LineMinY; - window->DC.CurrentLineSize = ImVec2(0.0f, 0.0f); - window->DC.CurrentLineTextBaseOffset = 0.0f; - PushColumnClipRect(); - PushItemWidth(GetColumnWidth() * 0.65f); // FIXME: Move on columns setup -} + ImVec2 c = ImFloor(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); } -int ImGui::GetColumnIndex() -{ - ImGuiWindow* window = GetCurrentWindowRead(); - return window->DC.ColumnsSet ? window->DC.ColumnsSet->Current : 0; + 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(ImFloor(hs_w * 0.30f)); + ImVec2 mouse_delta = (g.IO.MousePos - 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(g.IO.MousePos); } -int ImGui::GetColumnsCount() +// host_node may be NULL if the window doesn't have a DockNode already. +static bool ImGui::DockNodePreviewDockCalc(ImGuiWindow* host_window, ImGuiDockNode* host_node, ImGuiWindow* root_payload, ImGuiDockPreviewData* data, bool is_explicit_target, bool is_outer_docking) { - ImGuiWindow* window = GetCurrentWindowRead(); - return window->DC.ColumnsSet ? window->DC.ColumnsSet->Count : 1; -} + ImGuiContext& g = *GImGui; + IM_ASSERT(g.CurrentWindow == host_window); // Because we rely on font size to calculate tab sizes -static float OffsetNormToPixels(const ImGuiColumnsSet* columns, float offset_norm) -{ - return offset_norm * (columns->MaxX - columns->MinX); -} + data->FutureNode.HasCloseButton = (host_node ? host_node->HasCloseButton : host_window->HasCloseButton) || (root_payload->HasCloseButton); + data->FutureNode.HasCollapseButton = host_node ? true : ((host_window->Flags & ImGuiWindowFlags_NoCollapse) == 0); + data->FutureNode.Pos = host_node ? host_node->Pos : host_window->Pos; + data->FutureNode.Size = host_node ? host_node->Size : host_window->Size; -static float PixelsToOffsetNorm(const ImGuiColumnsSet* columns, float offset) -{ - return offset / (columns->MaxX - columns->MinX); -} + const bool src_is_visibly_splitted = root_payload->DockNodeAsHost && root_payload->DockNodeAsHost->IsSplitNode() && (root_payload->DockNodeAsHost->OnlyNodeWithWindows == NULL); + data->IsCenterAvailable = !is_outer_docking; + if (src_is_visibly_splitted && (!host_node || !host_node->IsEmpty())) + data->IsCenterAvailable = false; -static inline float GetColumnsRectHalfWidth() { return 4.0f; } + data->IsSidesAvailable = true; + if (host_node && (host_node->Flags & ImGuiDockNodeFlags_NoSplit)) + data->IsSidesAvailable = false; + if (!is_outer_docking && host_node && host_node->ParentNode == NULL && host_node->IsDocumentRoot) + data->IsSidesAvailable = false; -static float GetDraggedColumnOffset(ImGuiColumnsSet* columns, int column_index) + // 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 (DockNodeCalcDropRects(data->FutureNode.Rect(), (ImGuiDir)dir, data->DropRectsDraw[dir+1], is_outer_docking)) + { + 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_old = data->FutureNode.Pos, pos_new; + ImVec2 size_old = data->FutureNode.Size, size_new; + DockNodeCalcSplitRects(pos_old, size_old, pos_new, size_new, split_dir, root_payload->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); + } + + return data->IsSplitDirExplicit; +} + +static void ImGui::DockNodePreviewDockRender(ImGuiWindow* host_window, ImGuiDockNode* host_node, ImGuiWindow* root_payload, const ImGuiDockPreviewData* data) { - // Active (dragged) column always follow mouse. The reason we need this is that dragging a column to the right edge of an auto-resizing - // window creates a feedback loop because we store normalized positions. So while dragging we enforce absolute positioning. ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - IM_ASSERT(column_index > 0); // We are not supposed to drag column 0. - IM_ASSERT(g.ActiveId == columns->ID + ImGuiID(column_index)); - float x = g.IO.MousePos.x - g.ActiveIdClickOffset.x + GetColumnsRectHalfWidth() - window->Pos.x; - x = ImMax(x, ImGui::GetColumnOffset(column_index - 1) + g.Style.ColumnsMinSpacing); - if ((columns->Flags & ImGuiColumnsFlags_NoPreserveWidths)) - x = ImMin(x, ImGui::GetColumnOffset(column_index + 1) - g.Style.ColumnsMinSpacing); + // 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++] = GetOverlayDrawList(host_window->Viewport); + if (host_window->Viewport != root_payload->Viewport) + overlay_draw_lists[overlay_draw_lists_count++] = GetOverlayDrawList(root_payload->Viewport); - return x; + // Draw main preview rectangle + const ImU32 overlay_col_tabs = GetColorU32(ImGuiCol_TabActive); + const ImU32 overlay_col_main = GetColorU32(ImGuiCol_DockingPreview, 0.40f); + const ImU32 overlay_col_drop = GetColorU32(ImGuiCol_DockingPreview, 0.70f); + const ImU32 overlay_col_drop_hovered = GetColorU32(ImGuiCol_DockingPreview); + const ImU32 overlay_col_lines = GetColorU32(ImGuiCol_NavWindowingHighlight, 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); + } + + // 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 = DockNodeCalcTabBarRect(&data->FutureNode); + ImVec2 tab_pos = tab_bar_rect.Min; + if (host_node && host_node->TabBar) + tab_pos.x += host_node->TabBar->OffsetMax + 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 if (!(host_window->Flags & ImGuiWindowFlags_DockNodeHost)) + tab_pos.x += g.Style.ItemInnerSpacing.x + TabItemCalcSize(host_window->Name, host_window->HasCloseButton).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); + const int payload_count = root_payload->DockNodeAsHost ? root_payload->DockNodeAsHost->TabBar->Tabs.Size : 1; + for (int payload_n = 0; payload_n < payload_count; payload_n++) + { + // Calculate the tab bounding box for each payload window + ImGuiWindow* payload = root_payload->DockNodeAsHost ? root_payload->DockNodeAsHost->TabBar->Tabs[payload_n].Window : root_payload; + if (!DockNodeIsDropAllowedOne(payload, host_window)) + continue; + + ImVec2 tab_size = TabItemCalcSize(payload->Name, payload->HasCloseButton); + 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; + for (int overlay_n = 0; overlay_n < overlay_draw_lists_count; overlay_n++) + { + ImGuiTabItemFlags tab_flags = ImGuiTabItemFlags_Preview | ((payload->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, payload->Name, 0, 0); + if (!tab_bar_rect.Contains(tab_bb)) + overlay_draw_lists[overlay_n]->PopClipRect(); + } + } + } + + // 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->Flags & ImGuiDockNodeFlags_NoSplit)) + return; + } } -float ImGui::GetColumnOffset(int column_index) +//----------------------------------------------------------------------------- +// Docking: ImGuiDockNode Tree manipulation functions +//----------------------------------------------------------------------------- + +void ImGui::DockNodeTreeSplit(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImGuiAxis split_axis, int split_inheritor_child_idx, float split_ratio, ImGuiDockNode* new_node) { - ImGuiWindow* window = GetCurrentWindowRead(); - ImGuiColumnsSet* columns = window->DC.ColumnsSet; - IM_ASSERT(columns != NULL); + IM_ASSERT(split_axis != ImGuiAxis_None); - if (column_index < 0) - column_index = columns->Current; - IM_ASSERT(column_index < columns->Columns.Size); + ImGuiDockNode* child_0 = (new_node && split_inheritor_child_idx != 0) ? new_node : DockContextAddNode(ctx, 0); + child_0->ParentNode = parent_node; - const float t = columns->Columns[column_index].OffsetNorm; - const float x_offset = ImLerp(columns->MinX, columns->MaxX, t); - return x_offset; -} + ImGuiDockNode* child_1 = (new_node && split_inheritor_child_idx != 1) ? new_node : DockContextAddNode(ctx, 0); + child_1->ParentNode = parent_node; -static float GetColumnWidthEx(ImGuiColumnsSet* columns, int column_index, bool before_resize = false) -{ - if (column_index < 0) - column_index = columns->Current; + 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; - float offset_norm; - if (before_resize) - offset_norm = columns->Columns[column_index + 1].OffsetNormBeforeResize - columns->Columns[column_index].OffsetNormBeforeResize; - else - offset_norm = columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm; - return OffsetNormToPixels(columns, offset_norm); + float size_avail = (parent_node->Size[split_axis] - IMGUI_DOCK_SPLITTER_SIZE); + IM_ASSERT(size_avail > 0.0f); + child_0->SizeRef = child_1->SizeRef = parent_node->Size; + child_0->SizeRef[split_axis] = ImFloor(size_avail * split_ratio); + child_1->SizeRef[split_axis] = ImFloor(size_avail - child_0->SizeRef[split_axis]); + + DockNodeMoveWindows(parent_node->ChildNodes[split_inheritor_child_idx], parent_node); + DockNodeTreeUpdatePosSize(parent_node, parent_node->Pos, parent_node->Size); } -float ImGui::GetColumnWidth(int column_index) +void ImGui::DockNodeTreeMerge(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImGuiDockNode* merge_lead_child) { - ImGuiWindow* window = GetCurrentWindowRead(); - ImGuiColumnsSet* columns = window->DC.ColumnsSet; - IM_ASSERT(columns != NULL); + // When called from DockContextProcessUndockNode() it is possible that one of the child is NULL. + 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); + } - if (column_index < 0) - column_index = columns->Current; - return OffsetNormToPixels(columns, columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm); + 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 + DockSettingsMoveDockReferencesInInactiveWindow(child_0->ID, parent_node->ID); + } + if (child_1) + { + DockNodeMoveWindows(parent_node, child_1); + DockSettingsMoveDockReferencesInInactiveWindow(child_1->ID, parent_node->ID); + } + DockNodeApplyPosSizeToWindows(parent_node); + parent_node->InitFromFirstWindowPosSize = parent_node->InitFromFirstWindowViewport = false; + parent_node->VisibleWindow = merge_lead_child->VisibleWindow; + parent_node->IsDocumentRoot = (child_0 && child_0->IsDocumentRoot) || (child_1 && child_1->IsDocumentRoot); + parent_node->SizeRef = backup_last_explicit_size; + + 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); + } } -void ImGui::SetColumnOffset(int column_index, float offset) +// Update Pos/Size for a node hierarchy (don't affect child Windows yet) +void ImGui::DockNodeTreeUpdatePosSize(ImGuiDockNode* node, ImVec2 pos, ImVec2 size) { - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - ImGuiColumnsSet* columns = window->DC.ColumnsSet; - IM_ASSERT(columns != NULL); - - if (column_index < 0) - column_index = columns->Current; - IM_ASSERT(column_index < columns->Columns.Size); + node->Pos = pos; + node->Size = size; + if (!node->IsSplitNode()) + return; - const bool preserve_width = !(columns->Flags & ImGuiColumnsFlags_NoPreserveWidths) && (column_index < columns->Count-1); - const float width = preserve_width ? GetColumnWidthEx(columns, column_index, columns->IsBeingResized) : 0.0f; + 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; + if (child_0->IsVisible && child_1->IsVisible) + { + const float spacing = IMGUI_DOCK_SPLITTER_SIZE; + const ImGuiAxis axis = (ImGuiAxis)node->SplitAxis; + const float size_avail = ImMax(size[axis] - spacing, 0.0f); - if (!(columns->Flags & ImGuiColumnsFlags_NoForceWithinWindow)) - offset = ImMin(offset, columns->MaxX - g.Style.ColumnsMinSpacing * (columns->Count - column_index)); - columns->Columns[column_index].OffsetNorm = PixelsToOffsetNorm(columns, offset - columns->MinX); + // Size allocation policy + // 1) The first 0..WindowMinSize[axis]*2 are allocated evenly to both windows. + ImGuiContext& g = *GImGui; + const float size_min_each = ImFloor(ImMin(size_avail, g.Style.WindowMinSize[axis] * 2.0f) * 0.5f); - if (preserve_width) - SetColumnOffset(column_index + 1, offset + ImMax(g.Style.ColumnsMinSpacing, width)); -} + // 2) Process locked absolute size (during a splitter resize we preserve the child of nodes not touching the splitter edge) + IM_ASSERT(!(child_0->WantLockSizeOnce && child_1->WantLockSizeOnce)); + if (child_0->WantLockSizeOnce) + { + child_0->WantLockSizeOnce = false; + child_0_size[axis] = child_0->SizeRef[axis] = child_0->Size[axis]; + child_1_size[axis] = child_1->SizeRef[axis] = (size_avail - child_0_size[axis]); -void ImGui::SetColumnWidth(int column_index, float width) -{ - ImGuiWindow* window = GetCurrentWindowRead(); - ImGuiColumnsSet* columns = window->DC.ColumnsSet; - IM_ASSERT(columns != NULL); + } + else if (child_1->WantLockSizeOnce) + { + child_1->WantLockSizeOnce = false; + child_1_size[axis] = child_1->SizeRef[axis] = child_1->Size[axis]; + child_0_size[axis] = child_0->SizeRef[axis] = (size_avail - child_1_size[axis]); + } - if (column_index < 0) - column_index = columns->Current; - SetColumnOffset(column_index + 1, GetColumnOffset(column_index) + width); + // 3) If one window is the document root (~ use remaining space, should be made explicit!), use explicit size from the other, and remainder for the document root + else if (child_1->IsDocumentRoot && child_0->SizeRef[axis] != 0.0f) + { + 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_0->IsDocumentRoot && child_1->SizeRef[axis] != 0.0f) + { + 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, ImFloor(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 (child_0->IsVisible) + DockNodeTreeUpdatePosSize(child_0, child_0_pos, child_0_size); + if (child_1->IsVisible) + DockNodeTreeUpdatePosSize(child_1, child_1_pos, child_1_size); } -void ImGui::PushColumnClipRect(int column_index) +static void DockNodeTreeUpdateSplitterFindTouchingNode(ImGuiDockNode* node, ImGuiAxis axis, int side, ImVector* touching_nodes) { - ImGuiWindow* window = GetCurrentWindowRead(); - ImGuiColumnsSet* columns = window->DC.ColumnsSet; - if (column_index < 0) - column_index = columns->Current; - - PushClipRect(columns->Columns[column_index].ClipRect.Min, columns->Columns[column_index].ClipRect.Max, false); + if (!node->IsSplitNode()) + { + 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); } -static ImGuiColumnsSet* FindOrAddColumnsSet(ImGuiWindow* window, ImGuiID id) +void ImGui::DockNodeTreeUpdateSplitter(ImGuiDockNode* node) { - for (int n = 0; n < window->ColumnsStorage.Size; n++) - if (window->ColumnsStorage[n].ID == id) - return &window->ColumnsStorage[n]; - - window->ColumnsStorage.push_back(ImGuiColumnsSet()); - ImGuiColumnsSet* columns = &window->ColumnsStorage.back(); - columns->ID = id; - return columns; -} + if (!node->IsSplitNode()) + return; -void ImGui::BeginColumns(const char* str_id, int columns_count, ImGuiColumnsFlags flags) -{ ImGuiContext& g = *GImGui; - ImGuiWindow* window = GetCurrentWindow(); - IM_ASSERT(columns_count > 1); - IM_ASSERT(window->DC.ColumnsSet == NULL); // Nested columns are currently not supported + ImGuiDockNode* child_0 = node->ChildNodes[0]; + ImGuiDockNode* child_1 = node->ChildNodes[1]; + if (child_0->IsVisible && child_1->IsVisible) + { + // Extend hovering range past the displayed border + float HOVER_EXTEND = 4.0f; - // Differentiate column ID with an arbitrary prefix for cases where users name their columns set the same as another widget. - // In addition, when an identifier isn't explicitly provided we include the number of columns in the hash to make it uniquer. - PushID(0x11223347 + (str_id ? 0 : columns_count)); - ImGuiID id = window->GetID(str_id ? str_id : "columns"); - PopID(); + // 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 HOVER_VISIBILITY_DELAY = 0.040f; - // Acquire storage for the columns set - ImGuiColumnsSet* columns = FindOrAddColumnsSet(window, id); - IM_ASSERT(columns->ID == id); - columns->Current = 0; - columns->Count = columns_count; - columns->Flags = flags; - window->DC.ColumnsSet = columns; + // 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]; + //GetOverlayDrawList(g.CurrentWindow->Viewport)->AddRect(bb.Min, bb.Max, IM_COL32(255,0,255,255)); - // Set state for first column - const float content_region_width = (window->SizeContentsExplicit.x != 0.0f) ? (window->SizeContentsExplicit.x) : (window->InnerClipRect.Max.x - window->Pos.x); - columns->MinX = window->DC.Indent.x - g.Style.ItemSpacing.x; // Lock our horizontal range - columns->MaxX = ImMax(content_region_width - window->Scroll.x, columns->MinX + 1.0f); - columns->StartPosY = window->DC.CursorPos.y; - columns->StartMaxPosX = window->DC.CursorMaxPos.x; - columns->LineMinY = columns->LineMaxY = window->DC.CursorPos.y; - window->DC.ColumnsOffset.x = 0.0f; - window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); + float w1 = child_0->Size[axis]; + float w2 = child_1->Size[axis]; + 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); - // Clear data if columns count changed - if (columns->Columns.Size != 0 && columns->Columns.Size != columns_count + 1) - columns->Columns.resize(0); + // Gather list of nodes that are touching the splitter line. Find resizing limits based on those nodes. + ImVector 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; - // Initialize defaults - columns->IsFirstFrame = (columns->Columns.Size == 0); - if (columns->Columns.Size == 0) - { - columns->Columns.reserve(columns_count + 1); - for (int n = 0; n < columns_count + 1; n++) + ImGuiID splitter_id = GetID("##Splitter"); + if (g.ActiveId == splitter_id) { - ImGuiColumnData column; - column.OffsetNorm = n / (float)columns_count; - columns->Columns.push_back(column); + // 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 limits + ImDrawList* draw_list = node->HostWindow ? GetOverlayDrawList(node->HostWindow) : GetOverlayDrawList((ImGuiViewportP*)GetMainViewport()); + for (int n = 0; n < 2; n++) + 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); + */ } - } - for (int n = 0; n < columns_count; n++) - { - // Compute clipping rectangle - ImGuiColumnData* column = &columns->Columns[n]; - float clip_x1 = ImFloor(0.5f + window->Pos.x + GetColumnOffset(n) - 1.0f); - float clip_x2 = ImFloor(0.5f + window->Pos.x + GetColumnOffset(n + 1) - 1.0f); - column->ClipRect = ImRect(clip_x1, -FLT_MAX, clip_x2, +FLT_MAX); - column->ClipRect.ClipWith(window->ClipRect); + 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]; + if (SplitterBehavior(bb, GetID("##Splitter"), axis, &w1, &w2, min_size_0, min_size_1, HOVER_EXTEND, HOVER_VISIBILITY_DELAY)) + { + if (touching_nodes[0].Size > 0 && touching_nodes[1].Size > 0) + { + child_0->Size[axis] = child_0->SizeRef[axis] = w1; + child_1->Pos[axis] -= w2 - child_1->Size[axis]; + child_1->Size[axis] = child_1->SizeRef[axis] = w2; + + // 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. +#if 1 + 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 ? GetOverlayDrawList(node->HostWindow) : GetOverlayDrawList((ImGuiViewportP*)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; + } + } +#endif + + DockNodeTreeUpdatePosSize(child_0, child_0->Pos, child_0->Size); + DockNodeTreeUpdatePosSize(child_1, child_1->Pos, child_1->Size); + MarkIniSettingsDirty(); + } + } + PopID(); } - window->DrawList->ChannelsSplit(columns->Count); - PushColumnClipRect(); - PushItemWidth(GetColumnWidth() * 0.65f); + if (child_0->IsVisible) + DockNodeTreeUpdateSplitter(child_0); + if (child_1->IsVisible) + DockNodeTreeUpdateSplitter(child_1); } -void ImGui::EndColumns() +ImGuiDockNode* ImGui::DockNodeTreeFindNodeByPos(ImGuiDockNode* node, ImVec2 pos) { + if (!node->IsVisible) + return NULL; + ImGuiContext& g = *GImGui; - ImGuiWindow* window = GetCurrentWindow(); - ImGuiColumnsSet* columns = window->DC.ColumnsSet; - IM_ASSERT(columns != NULL); + const float dock_spacing = g.Style.ItemInnerSpacing.x; + ImRect r(node->Pos, node->Pos + node->Size); + r.Expand(dock_spacing * 0.5f); + bool inside = r.Contains(pos); + if (!inside) + return NULL; - PopItemWidth(); - PopClipRect(); - window->DrawList->ChannelsMerge(); + if (!node->IsSplitNode()) + return node; + if (ImGuiDockNode* hovered_node = DockNodeTreeFindNodeByPos(node->ChildNodes[0], pos)) + return hovered_node; + if (ImGuiDockNode* hovered_node = DockNodeTreeFindNodeByPos(node->ChildNodes[1], pos)) + return hovered_node; + return NULL; +} - columns->LineMaxY = ImMax(columns->LineMaxY, window->DC.CursorPos.y); - window->DC.CursorPos.y = columns->LineMaxY; - if (!(columns->Flags & ImGuiColumnsFlags_GrowParentContentsSize)) - window->DC.CursorMaxPos.x = columns->StartMaxPosX; // Restore cursor max pos, as columns don't grow parent +//----------------------------------------------------------------------------- +// Docking: Public Functions (SetWindowDock, DockSpace) +//----------------------------------------------------------------------------- - // Draw columns borders and handle resize - bool is_being_resized = false; - if (!(columns->Flags & ImGuiColumnsFlags_NoBorder) && !window->SkipItems) - { - const float y1 = columns->StartPosY; - const float y2 = window->DC.CursorPos.y; - int dragging_column = -1; - for (int n = 1; n < columns->Count; n++) - { - float x = window->Pos.x + GetColumnOffset(n); - const ImGuiID column_id = columns->ID + ImGuiID(n); - const float column_hw = GetColumnsRectHalfWidth(); // Half-width for interaction - const ImRect column_rect(ImVec2(x - column_hw, y1), ImVec2(x + column_hw, y2)); - KeepAliveID(column_id); - if (IsClippedEx(column_rect, column_id, false)) - continue; +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); - bool hovered = false, held = false; - if (!(columns->Flags & ImGuiColumnsFlags_NoResize)) - { - ButtonBehavior(column_rect, column_id, &hovered, &held); - if (hovered || held) - g.MouseCursor = ImGuiMouseCursor_ResizeEW; - if (held && !(columns->Columns[n].Flags & ImGuiColumnsFlags_NoResize)) - dragging_column = n; - } + // Set + if (window->DockId == dock_id) + return; + if (window->DockNode) + DockNodeRemoveWindow(window->DockNode, window, 0); + window->DockId = dock_id; +} - // Draw column (we clip the Y boundaries CPU side because very long triangles are mishandled by some GPU drivers.) - const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : hovered ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator); - const float xi = (float)(int)x; - window->DrawList->AddLine(ImVec2(xi, ImMax(y1 + 1.0f, window->ClipRect.Min.y)), ImVec2(xi, ImMin(y2, window->ClipRect.Max.y)), col); - } +// Create an explicit dockspace node within an existing window. Also expose dock node flags and creates a DocumentRoot node by default. +// The DocumentRoot node is always displayed even when empty and shrink/extend according to the requested size of its neighbors. +void ImGui::DockSpace(ImGuiID id, const ImVec2& size_arg, ImGuiDockNodeFlags dock_space_flags, const ImGuiDockFamily* dock_family) +{ + ImGuiContext* ctx = GImGui; + ImGuiContext& g = *ctx; + ImGuiWindow* window = GetCurrentWindow(); + if (!(g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable)) + return; - // Apply dragging after drawing the column lines, so our rendered lines are in sync with how items were displayed during the frame. - if (dragging_column != -1) - { - if (!columns->IsBeingResized) - for (int n = 0; n < columns->Count + 1; n++) - columns->Columns[n].OffsetNormBeforeResize = columns->Columns[n].OffsetNorm; - columns->IsBeingResized = is_being_resized = true; - float x = GetDraggedColumnOffset(columns, dragging_column); - SetColumnOffset(dragging_column, x); - } + ImGuiDockNode* node = DockContextFindNodeByID(ctx, id); + if (!node) + { + node = DockContextAddNode(ctx, id); + node->IsDocumentRoot = true; } - columns->IsBeingResized = is_being_resized; + node->Flags = dock_space_flags; + node->DockFamily = dock_family ? *dock_family : ImGuiDockFamily(); + + // 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 && !(dock_space_flags & ImGuiDockNodeFlags_KeepAliveOnly)) + { + IM_ASSERT(node->IsDockSpace == false && "Cannot call DockSpace() twice a frame with the same ID"); + node->IsDockSpace = true; + return; + } + node->IsDockSpace = true; + + // Keep alive mode, this is allow windows docked into this node so stay docked even if they are not visible + if (dock_space_flags & ImGuiDockNodeFlags_KeepAliveOnly) + { + node->LastFrameAlive = g.FrameCount; + return; + } + + const ImVec2 content_avail = GetContentRegionAvail(); + ImVec2 size = ImFloor(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); + + node->Pos = window->DC.CursorPos; + node->Size = node->SizeRef = size; + SetNextWindowPos(node->Pos); + SetNextWindowSize(node->Size); + g.NextWindowData.PosUndock = false; - window->DC.ColumnsSet = NULL; - window->DC.ColumnsOffset.x = 0.0f; - window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); -} + ImGuiWindowFlags window_flags = ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_DockNodeHost; + window_flags |= ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar; -// [2018-03: This is currently the only public API, while we are working on making BeginColumns/EndColumns user-facing] -void ImGui::Columns(int columns_count, const char* id, bool border) -{ - ImGuiWindow* window = GetCurrentWindow(); - IM_ASSERT(columns_count >= 1); + char title[256]; + ImFormatString(title, IM_ARRAYSIZE(title), "%s/DockSpace_%08X", window->Name, id); - ImGuiColumnsFlags flags = (border ? 0 : ImGuiColumnsFlags_NoBorder); - //flags |= ImGuiColumnsFlags_NoPreserveWidths; // NB: Legacy behavior - if (window->DC.ColumnsSet != NULL && window->DC.ColumnsSet->Count == columns_count && window->DC.ColumnsSet->Flags == flags) - return; + if (node->Windows.Size > 0 || node->IsSplitNode()) + PushStyleColor(ImGuiCol_ChildBg, IM_COL32(0, 0, 0, 0)); + Begin(title, NULL, window_flags); + if (node->Windows.Size > 0 || node->IsSplitNode()) + PopStyleColor(); - if (window->DC.ColumnsSet != NULL) - EndColumns(); + ImGuiWindow* host_window = g.CurrentWindow; + host_window->DockNodeAsHost = node; + host_window->ChildId = window->GetID(title); + node->HostWindow = host_window; + node->OnlyNodeWithWindows = NULL; - if (columns_count != 1) - BeginColumns(id, columns_count, flags); + IM_ASSERT(node->IsRootNode()); + DockNodeUpdate(node); + + End(); } //----------------------------------------------------------------------------- -// [SECTION] DRAG AND DROP +// Docking: Builder Functions +//----------------------------------------------------------------------------- +// Very early end-user API to manipulate dock nodes. +// It is expected that those functions are all called _before_ the dockspace node submission. //----------------------------------------------------------------------------- -void ImGui::ClearDragDrop() +void ImGui::DockBuilderDockWindow(ImGuiContext*, const char* window_name, ImGuiID node_id) { - ImGuiContext& g = *GImGui; - g.DragDropActive = false; - g.DragDropPayload.Clear(); - g.DragDropAcceptFlags = 0; - g.DragDropAcceptIdCurr = g.DragDropAcceptIdPrev = 0; - g.DragDropAcceptIdCurrRectSurface = FLT_MAX; - g.DragDropAcceptFrameCount = -1; + ImGuiID window_id = ImHash(window_name, 0); + if (ImGuiWindow* window = FindWindowByID(window_id)) + { + // Apply to created window + SetWindowDock(window, node_id, ImGuiCond_Always); + } + else + { + // Apply to settings + ImGuiWindowSettings* settings = FindWindowSettings(window_id); + if (settings == NULL) + settings = CreateNewWindowSettings(window_name); + settings->DockId = node_id; + } +} - g.DragDropPayloadBufHeap.clear(); - memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal)); +ImGuiDockNode* ImGui::DockBuilderGetNode(ImGuiContext* ctx, ImGuiID node_id) +{ + return DockContextFindNodeByID(ctx, node_id); } -// Call when current ID is active. -// When this returns true you need to: a) call SetDragDropPayload() exactly once, b) you may render the payload visual/description, c) call EndDragDropSource() -bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags) +void ImGui::DockBuilderAddNode(ImGuiContext* ctx, ImGuiID id, ImVec2 ref_size, ImGuiDockNodeFlags flags) { - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; + DockSpace(id, ImVec2(0,0), flags | ImGuiDockNodeFlags_KeepAliveOnly); + ImGuiDockNode* node = DockContextFindNodeByID(ctx, id); + node->SizeRef = node->Size = ref_size; + node->LastFrameAlive = -1; +} - bool source_drag_active = false; - ImGuiID source_id = 0; - ImGuiID source_parent_id = 0; - int mouse_button = 0; - if (!(flags & ImGuiDragDropFlags_SourceExtern)) - { - source_id = window->DC.LastItemId; - if (source_id != 0 && g.ActiveId != source_id) // Early out for most common case - return false; - if (g.IO.MouseDown[mouse_button] == false) - return false; +void ImGui::DockBuilderRemoveNode(ImGuiContext* ctx, ImGuiID node_id) +{ + ImGuiDockNode* node = DockContextFindNodeByID(ctx, node_id); + if (node == NULL) + return; + DockBuilderRemoveNodeDockedWindows(ctx, node_id, true); + DockBuilderRemoveNodeChildNodes(ctx, node_id); + if (node->IsDocumentRoot && node->ParentNode) + node->ParentNode->IsDocumentRoot = true; + DockContextRemoveNode(ctx, node, true); +} - if (source_id == 0) - { - // If you want to use BeginDragDropSource() on an item with no unique identifier for interaction, such as Text() or Image(), you need to: - // A) Read the explanation below, B) Use the ImGuiDragDropFlags_SourceAllowNullID flag, C) Swallow your programmer pride. - if (!(flags & ImGuiDragDropFlags_SourceAllowNullID)) - { - IM_ASSERT(0); - return false; - } +void ImGui::DockBuilderRemoveNodeChildNodes(ImGuiContext* ctx, ImGuiID root_id) +{ + ImGuiDockContext* dc = ctx->DockContext; - // Magic fallback (=somehow reprehensible) to handle items with no assigned ID, e.g. Text(), Image() - // We build a throwaway ID based on current ID stack + relative AABB of items in window. - // THE IDENTIFIER WON'T SURVIVE ANY REPOSITIONING OF THE WIDGET, so if your widget moves your dragging operation will be canceled. - // We don't need to maintain/call ClearActiveID() as releasing the button will early out this function and trigger !ActiveIdIsAlive. - bool is_hovered = (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) != 0; - if (!is_hovered && (g.ActiveId == 0 || g.ActiveIdWindow != window)) - return false; - source_id = window->DC.LastItemId = window->GetIDFromRectangle(window->DC.LastItemRect); - if (is_hovered) - SetHoveredID(source_id); - if (is_hovered && g.IO.MouseClicked[mouse_button]) + ImGuiDockNode* root_node = root_id ? DockContextFindNodeByID(ctx, root_id) : NULL; + if (root_id && root_node == NULL) + return; + bool has_document_root = false; + + // Process active windows + ImVector 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) { - SetActiveID(source_id, window); - FocusWindow(window); + if (node->IsDocumentRoot) + has_document_root = true; + if (root_id != 0) + DockContextQueueNotifyRemovedNode(ctx, node); + if (root_node) + DockNodeMoveWindows(root_node, node); + nodes_to_remove.push_back(node); } - if (g.ActiveId == source_id) // Allow the underlying widget to display/return hovered during the mouse release frame, else we would get a flicker. - g.ActiveIdAllowOverlap = is_hovered; - } - else - { - g.ActiveIdAllowOverlap = false; } - if (g.ActiveId != source_id) - return false; - source_parent_id = window->IDStack.back(); - source_drag_active = IsMouseDragging(mouse_button); + + // Apply to settings + for (int settings_n = 0; settings_n < ctx->SettingsWindows.Size; settings_n++) + if (ImGuiID window_settings_dock_id = ctx->SettingsWindows[settings_n].DockId) + for (int n = 0; n < nodes_to_remove.Size; n++) + if (nodes_to_remove[n]->ID == window_settings_dock_id) + { + ctx->SettingsWindows[settings_n].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(ctx, nodes_to_remove[n], false); + + if (root_id == 0) + { + dc->Nodes.Clear(); + dc->Requests.clear(); } - else + else if (has_document_root) { - window = NULL; - source_id = ImHash("#SourceExtern", 0); - source_drag_active = true; + root_node->IsDocumentRoot = true; } +} - if (source_drag_active) +void ImGui::DockBuilderRemoveNodeDockedWindows(ImGuiContext* ctx, ImGuiID root_id, bool clear_persistent_docking_references) +{ + // Clear references in settings + ImGuiContext& g = *ctx; + if (clear_persistent_docking_references) { - if (!g.DragDropActive) + for (int n = 0; n < g.SettingsWindows.Size; n++) { - IM_ASSERT(source_id != 0); - ClearDragDrop(); - ImGuiPayload& payload = g.DragDropPayload; - payload.SourceId = source_id; - payload.SourceParentId = source_parent_id; - g.DragDropActive = true; - g.DragDropSourceFlags = flags; - g.DragDropMouseButton = mouse_button; + ImGuiWindowSettings* settings = &g.SettingsWindows[n]; + bool want_removal = (root_id == 0) || (settings->DockId == root_id); + if (!want_removal && settings->DockId != 0) + if (ImGuiDockNode* node = DockContextFindNodeByID(ctx, settings->DockId)) + if (DockNodeGetRootNode(node)->ID == root_id) + want_removal = true; + if (want_removal) + settings->DockId = 0; } - g.DragDropSourceFrameCount = g.FrameCount; - g.DragDropWithinSourceOrTarget = true; + } - if (!(flags & ImGuiDragDropFlags_SourceNoPreviewTooltip)) + // 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) { - // Target can request the Source to not display its tooltip (we use a dedicated flag to make this request explicit) - // We unfortunately can't just modify the source flags and skip the call to BeginTooltip, as caller may be emitting contents. - BeginTooltip(); - if (g.DragDropActive && g.DragDropAcceptIdPrev && (g.DragDropAcceptFlags & ImGuiDragDropFlags_AcceptNoPreviewTooltip)) - { - ImGuiWindow* tooltip_window = g.CurrentWindow; - tooltip_window->SkipItems = true; - tooltip_window->HiddenFramesRegular = 1; - } + const ImGuiID backup_dock_id = window->DockId; + DockContextProcessUndockWindow(ctx, window, clear_persistent_docking_references); + if (!clear_persistent_docking_references) + IM_ASSERT(window->DockId == backup_dock_id); } + } +} - if (!(flags & ImGuiDragDropFlags_SourceNoDisableHover) && !(flags & ImGuiDragDropFlags_SourceExtern)) - window->DC.LastItemStatusFlags &= ~ImGuiItemStatusFlags_HoveredRect; +ImGuiID ImGui::DockBuilderSplitNode(ImGuiContext* ctx, ImGuiID id, ImGuiDir split_dir, float size_ratio_for_node_at_dir, ImGuiID* out_id_at_dir, ImGuiID* out_id_other) +{ + IM_ASSERT(split_dir != ImGuiDir_None); - return true; + ImGuiDockNode* node = DockContextFindNodeByID(ctx, id); + if (node == NULL) + { + IM_ASSERT(node != NULL); + return 0; } - return false; + + IM_ASSERT(!node->IsSplitNode()); // 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(ctx, &req); + + ImGuiID id_at_dir = node->ChildNodes[(split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Up) ? 0 : 1]->ID; + ImGuiID id_other = 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_other) + *out_id_other = id_other; + return id_at_dir; } -void ImGui::EndDragDropSource() +static ImGuiDockNode* DockBuilderCopyNodeRec(ImGuiContext* ctx, ImGuiDockNode* src_node, ImGuiID dst_node_id_if_known, ImVector* out_node_remap_pairs) { - ImGuiContext& g = *GImGui; - IM_ASSERT(g.DragDropActive); - IM_ASSERT(g.DragDropWithinSourceOrTarget && "Not after a BeginDragDropSource()?"); + ImGuiDockNode* dst_node = ImGui::DockContextAddNode(ctx, dst_node_id_if_known); + dst_node->Flags = src_node->Flags; + 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->IsDockSpace = src_node->IsDockSpace; + dst_node->IsDocumentRoot = src_node->IsDocumentRoot; - if (!(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoPreviewTooltip)) - EndTooltip(); + out_node_remap_pairs->push_back(src_node->ID); + out_node_remap_pairs->push_back(dst_node->ID); - // Discard the drag if have not called SetDragDropPayload() - if (g.DragDropPayload.DataFrameCount == -1) - ClearDragDrop(); - g.DragDropWithinSourceOrTarget = false; + 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(ctx, src_node->ChildNodes[child_n], 0, out_node_remap_pairs); + dst_node->ChildNodes[child_n]->ParentNode = dst_node; + } + + //IMGUI_DEBUG_LOG("Fork node %08X -> %08X (%d childs)\n", src_node->ID, dst_node->ID, dst_node->IsSplitNode() ? 2 : 0); + return dst_node; } -// Use 'cond' to choose to submit payload on drag start or every frame -bool ImGui::SetDragDropPayload(const char* type, const void* data, size_t data_size, ImGuiCond cond) +void ImGui::DockBuilderCopyNode(ImGuiContext* ctx, ImGuiID src_node_id, ImGuiID dst_node_id, ImVector* out_node_remap_pairs) { - ImGuiContext& g = *GImGui; - ImGuiPayload& payload = g.DragDropPayload; - if (cond == 0) - cond = ImGuiCond_Always; + IM_ASSERT(src_node_id != 0); + IM_ASSERT(dst_node_id != 0); + IM_ASSERT(out_node_remap_pairs != NULL); - IM_ASSERT(type != NULL); - IM_ASSERT(strlen(type) < IM_ARRAYSIZE(payload.DataType) && "Payload type can be at most 32 characters long"); - IM_ASSERT((data != NULL && data_size > 0) || (data == NULL && data_size == 0)); - IM_ASSERT(cond == ImGuiCond_Always || cond == ImGuiCond_Once); - IM_ASSERT(payload.SourceId != 0); // Not called between BeginDragDropSource() and EndDragDropSource() + ImGuiDockNode* src_node = DockContextFindNodeByID(ctx, src_node_id); + IM_ASSERT(src_node != NULL); - if (cond == ImGuiCond_Always || payload.DataFrameCount == -1) + out_node_remap_pairs->clear(); + DockBuilderRemoveNode(ctx, dst_node_id); + DockBuilderCopyNodeRec(ctx, src_node, dst_node_id, out_node_remap_pairs); + + IM_ASSERT((out_node_remap_pairs->Size % 2) == 0); +} + +void ImGui::DockBuilderCopyWindowSettings(ImGuiContext* ctx, const char* src_name, const char* dst_name) +{ + (void)ctx; + ImGuiWindow* src_window = FindWindowByName(src_name); + if (src_window == NULL) + return; + if (ImGuiWindow* dst_window = FindWindowByName(dst_name)) { - // Copy payload - ImStrncpy(payload.DataType, type, IM_ARRAYSIZE(payload.DataType)); - g.DragDropPayloadBufHeap.resize(0); - if (data_size > sizeof(g.DragDropPayloadBufLocal)) + 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 if (ImGuiWindowSettings* dst_settings = FindOrCreateWindowSettings(dst_name)) + { + if (src_window->ViewportId != 0 && src_window->ViewportId != IMGUI_VIEWPORT_DEFAULT_ID) { - // Store in heap - g.DragDropPayloadBufHeap.resize((int)data_size); - payload.Data = g.DragDropPayloadBufHeap.Data; - memcpy(payload.Data, data, data_size); + dst_settings->ViewportPos = src_window->Pos; + dst_settings->ViewportId = src_window->ViewportId; + dst_settings->Pos = ImVec2(0.0f, 0.0f); } - else if (data_size > 0) + else { - // Store locally - memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal)); - payload.Data = g.DragDropPayloadBufLocal; - memcpy(payload.Data, data, data_size); + dst_settings->Pos = src_window->Pos; + } + dst_settings->Size = 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(ImGuiContext* ctx, ImGuiID src_dockspace_id, ImGuiID dst_dockspace_id, ImVector* in_window_remap_pairs) +{ + 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 family 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 node_remap_pairs; + ImGui::DockBuilderCopyNode(ctx, 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 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 = ImHash(src_window_name, 0); + 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 = FindWindowSettings(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("Remap window '%s' %08X -> %08X\n", dst_window_name, src_dock_id, dst_dock_id); + ImGui::DockBuilderDockWindow(ctx, dst_window_name, dst_dock_id); } else { - payload.Data = NULL; + // 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::DockBuilderCopyWindowSettings(ctx, src_window_name, dst_window_name); } - payload.DataSize = (int)data_size; } - payload.DataFrameCount = g.FrameCount; - return (g.DragDropAcceptFrameCount == g.FrameCount) || (g.DragDropAcceptFrameCount == g.FrameCount - 1); + // Anything else in the source nodes of 'node_remap_pairs' are windows that were docked in src_dockspace_id but are not owned by it (unaffiliated windows, e.g. "ImGui Demo") + // Find those windows and move to them to the cloned dock node. This may be optional? + 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* dock_node = ImGui::DockBuilderGetNode(ctx, src_dock_id); + for (int window_n = 0; window_n < dock_node->Windows.Size; window_n++) + { + ImGuiWindow* window = dock_node->Windows[window_n]; + if (src_windows.contains(window->ID)) + continue; + + // Docked windows gets redocked into the new node hierarchy. + IMGUI_DEBUG_LOG("Remap window '%s' %08X -> %08X\n", window->Name, src_dock_id, dst_dock_id); + ImGui::DockBuilderDockWindow(ctx, window->Name, dst_dock_id); + } + } } -bool ImGui::BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id) +void ImGui::DockBuilderFinish(ImGuiContext* ctx, ImGuiID root_id) { - ImGuiContext& g = *GImGui; - if (!g.DragDropActive) - return false; + //DockContextRebuild(ctx); + DockContextBuildAddWindowsToNodes(ctx, root_id); +} - ImGuiWindow* window = g.CurrentWindow; - if (g.HoveredWindow == NULL || window->RootWindow != g.HoveredWindow->RootWindow) - return false; - IM_ASSERT(id != 0); - if (!IsMouseHoveringRect(bb.Min, bb.Max) || (id == g.DragDropPayload.SourceId)) - return false; - if (window->SkipItems) - return false; +//----------------------------------------------------------------------------- +// Docking: Begin/End Functions (called from Begin/End) +//----------------------------------------------------------------------------- + +void ImGui::BeginDocked(ImGuiWindow* window, bool* p_open) +{ + ImGuiContext* ctx = GImGui; + ImGuiContext& g = *ctx; + + // Calling SetNextWindowPos() undock windows by default (by setting PosUndock) + bool want_undock = false; + want_undock |= (window->Flags & ImGuiWindowFlags_NoDocking) != 0; + want_undock |= (g.NextWindowData.PosCond && (window->SetWindowPosAllowFlags & g.NextWindowData.PosCond) && g.NextWindowData.PosUndock); + g.NextWindowData.PosUndock = false; + if (want_undock) + { + DockContextProcessUndockWindow(ctx, window); + return; + } + + // Bind to our dock node + ImGuiDockNode* dock_node = window->DockNode; + if (window->DockId != 0 && dock_node == NULL) + { + dock_node = DockContextFindNodeByID(ctx, window->DockId); + if (dock_node == NULL) + dock_node = DockContextAddNode(ctx, window->DockId); + + if (dock_node->IsSplitNode()) + { + DockContextProcessUndockWindow(ctx, window); + return; + } + + DockNodeAddWindow(dock_node, window, true); + IM_ASSERT(dock_node == window->DockNode); + + // Fix an edge case with auto-resizing windows: if they are created on the same frame they are creating their dock node, + // we don't want their initial zero-size to spread to the DockNode. We preserve their size. + SetNextWindowPos(window->Pos); + SetNextWindowSize(window->SizeFull); + g.NextWindowData.PosUndock = false; + } + + // 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 (dock_node->LastFrameAlive < g.FrameCount) + { + // If the window has been orphaned, transition the docknode to an implicit node processed in DockContextUpdateDocking() + ImGuiDockNode* root_node = DockNodeGetRootNode(dock_node); + if (root_node->LastFrameAlive < g.FrameCount) + { + DockContextProcessUndockWindow(ctx, window); + } + else + { + window->DockIsActive = true; + window->DockTabIsVisible = false; + } + return; + } + + // Undock if we are submitted earlier than the host window + if (dock_node->HostWindow && window->BeginOrderWithinContext < dock_node->HostWindow->BeginOrderWithinContext) + { + DockContextProcessUndockWindow(ctx, window); + return; + } + + // FIXME-DOCK: replace ->HostWindow NULL compare with something more explicit (~was initially intended as a first frame test) + if (dock_node->HostWindow == NULL) + { + window->DockTabIsVisible = false; + return; + } + + IM_ASSERT(dock_node->HostWindow); + IM_ASSERT(!dock_node->IsSplitNode()); + + // Position window + SetNextWindowPos(dock_node->Pos); + SetNextWindowSize(dock_node->Size); + g.NextWindowData.PosUndock = false; // Cancel implicit undocking of SetNextWindowPos() + + window->DockIsActive = true; + window->DockTabIsVisible = false; + if (dock_node->Flags & ImGuiDockNodeFlags_KeepAliveOnly) + return; - IM_ASSERT(g.DragDropWithinSourceOrTarget == false); - g.DragDropTargetRect = bb; - g.DragDropTargetId = id; - g.DragDropWithinSourceOrTarget = true; - return true; + if (dock_node->TabBar && dock_node->TabBar->VisibleTabId == window->ID) + window->DockTabIsVisible = true; + + // When we are about to select this tab (which will only be visible on the _next frame_), flag it with a non-zero HiddenFramesForResize. + // 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 nested TabBars would otherwise generate flicker in the form + // of one empty frame. + // Note that we set HiddenFramesForResize=2 because BeginDocked() is called just before Begin() has a chance to decrement the value. Effectively it'll be a 1 frame thing. + if (!window->DockTabIsVisible && dock_node->TabBar && dock_node->TabBar->NextSelectedTabId == window->ID) + window->HiddenFramesForResize = 2; + + // Update window flag + IM_ASSERT((window->Flags & ImGuiWindowFlags_ChildWindow) == 0); + window->Flags |= ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_AlwaysUseWindowPadding | ImGuiWindowFlags_NoResize; + 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 tab bar is active + if (dock_node->TabBar) + window->DockOrder = (short)DockNodeGetTabOrder(window); + + if ((dock_node->WantCloseAll || dock_node->WantCloseTabID == window->ID) && 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); } -// We don't use BeginDragDropTargetCustom() and duplicate its code because: -// 1) we use LastItemRectHoveredRect which handles items that pushes a temporarily clip rectangle in their code. Calling BeginDragDropTargetCustom(LastItemRect) would not handle them. -// 2) and it's faster. as this code may be very frequently called, we want to early out as fast as we can. -// Also note how the HoveredWindow test is positioned differently in both functions (in both functions we optimize for the cheapest early out case) -bool ImGui::BeginDragDropTarget() +void ImGui::BeginAsDockableDragDropSource(ImGuiWindow* window) { ImGuiContext& g = *GImGui; + IM_ASSERT(g.ActiveId == window->MoveId); + + window->DC.LastItemId = window->MoveId; + window = window->RootWindow; + IM_ASSERT((window->Flags & ImGuiWindowFlags_NoDocking) == 0); + bool is_drag_docking = (g.IO.ConfigDockingWithShift) || ImRect(0, 0, window->SizeFull.x, GetFrameHeight()).Contains(g.ActiveIdClickOffset); + if (is_drag_docking && BeginDragDropSource(ImGuiDragDropFlags_SourceNoPreviewTooltip | ImGuiDragDropFlags_SourceNoHoldToOpenOthers | ImGuiDragDropFlags_SourceAutoExpirePayload)) + { + SetDragDropPayload(IMGUI_PAYLOAD_TYPE_WINDOW, &window, sizeof(window)); + EndDragDropSource(); + } +} + +void ImGui::BeginAsDockableDragDropTarget(ImGuiWindow* window) +{ + ImGuiContext* ctx = GImGui; + ImGuiContext& g = *ctx; + + IM_ASSERT((window->Flags & ImGuiWindowFlags_NoDocking) == 0); if (!g.DragDropActive) - return false; + return; - ImGuiWindow* window = g.CurrentWindow; - if (!(window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect)) - return false; - if (g.HoveredWindow == NULL || window->RootWindow != g.HoveredWindow->RootWindow) - return false; + if (!BeginDragDropTargetCustom(window->Rect(), window->ID)) + return; - const ImRect& display_rect = (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HasDisplayRect) ? window->DC.LastItemDisplayRect : window->DC.LastItemRect; - ImGuiID id = window->DC.LastItemId; - if (id == 0) - id = window->GetIDFromRectangle(display_rect); - if (g.DragDropPayload.SourceId == id) - return false; + // 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; + } - IM_ASSERT(g.DragDropWithinSourceOrTarget == false); - g.DragDropTargetRect = display_rect; - g.DragDropTargetId = id; - g.DragDropWithinSourceOrTarget = true; - return true; + ImGuiWindow* payload_window = *(ImGuiWindow**)payload->Data; + if (AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_WINDOW, ImGuiDragDropFlags_AcceptBeforeDelivery | ImGuiDragDropFlags_AcceptNoDrawDefaultRect)) + { + bool allow_null_target_node = false; + ImGuiDockNode* target_node = NULL; + if (window->DockNodeAsHost) + target_node = DockNodeTreeFindNodeByPos(window->DockNodeAsHost, g.IO.MousePos); + else if (window->DockNode) // && window->DockIsActive) + target_node = window->DockNode; + else + allow_null_target_node = true; // Dock into a regular window + + const ImRect explicit_target_rect = (target_node && target_node->TabBar) ? target_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 && (target_node != NULL || allow_null_target_node)) + { + ImGuiDockPreviewData split_inner, split_outer; + ImGuiDockPreviewData* split_data = &split_inner; + if (target_node && (target_node->ParentNode || target_node->IsDocumentRoot)) + if (ImGuiDockNode* root_node = DockNodeGetRootNode(target_node)) + if (DockNodePreviewDockCalc(window, root_node, payload_window, &split_outer, is_explicit_target, true)) + split_data = &split_outer; + DockNodePreviewDockCalc(window, target_node, payload_window, &split_inner, is_explicit_target, false); + + // Draw inner then outer, so that previewed tab (in inner data) will be behind the outer drop boxes + DockNodePreviewDockRender(window, target_node, payload_window, &split_inner); + DockNodePreviewDockRender(window, target_node, payload_window, &split_outer); + + // Queue docking request + if (split_data && split_data->IsDropAllowed && payload->IsDelivery()) + DockContextQueueDock(ctx, window, split_data->SplitNode, payload_window, split_data->SplitDir, split_data->SplitRatio, split_data == &split_outer); + } + } + EndDragDropTarget(); } -bool ImGui::IsDragDropPayloadBeingAccepted() +//----------------------------------------------------------------------------- +// Docking: Settings +//----------------------------------------------------------------------------- + +static void ImGui::DockSettingsMoveDockReferencesInInactiveWindow(ImGuiID old_dock_id, ImGuiID new_dock_id) { ImGuiContext& g = *GImGui; - return g.DragDropActive && g.DragDropAcceptIdPrev != 0; + for (int window_n = 0; window_n < g.Windows.Size; window_n++) + { + ImGuiWindow* window = g.Windows[window_n]; + if (window->DockId == old_dock_id && window->DockNode == NULL) + window->DockId = new_dock_id; + } + for (int settings_n = 0; settings_n < g.SettingsWindows.Size; settings_n++) // FIXME-OPT: We could remove this loop by storing the index in the map + { + ImGuiWindowSettings* window_settings = &g.SettingsWindows[settings_n]; + if (window_settings->DockId == old_dock_id) + window_settings->DockId = new_dock_id; + } } -const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDropFlags flags) +// Remove references stored in ImGuiWindowSettings to the given ImGuiDockNodeSettings +static void ImGui::DockSettingsRemoveReferencesToNodes(ImGuiID* node_ids, int node_ids_count) { ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - ImGuiPayload& payload = g.DragDropPayload; - IM_ASSERT(g.DragDropActive); // Not called between BeginDragDropTarget() and EndDragDropTarget() ? - IM_ASSERT(payload.DataFrameCount != -1); // Forgot to call EndDragDropTarget() ? - if (type != NULL && !payload.IsDataType(type)) - return NULL; - - // Accept smallest drag target bounding box, this allows us to nest drag targets conveniently without ordering constraints. - // NB: We currently accept NULL id as target. However, overlapping targets requires a unique ID to function! - const bool was_accepted_previously = (g.DragDropAcceptIdPrev == g.DragDropTargetId); - ImRect r = g.DragDropTargetRect; - float r_surface = r.GetWidth() * r.GetHeight(); - if (r_surface < g.DragDropAcceptIdCurrRectSurface) + int found = 0; + for (int settings_n = 0; settings_n < g.SettingsWindows.Size; settings_n++) // FIXME-OPT: We could remove this loop by storing the index in the map { - g.DragDropAcceptFlags = flags; - g.DragDropAcceptIdCurr = g.DragDropTargetId; - g.DragDropAcceptIdCurrRectSurface = r_surface; + ImGuiWindowSettings* window_settings = &g.SettingsWindows[settings_n]; + for (int node_n = 0; node_n < node_ids_count; node_n++) + if (window_settings->DockId == node_ids[node_n]) + { + window_settings->DockId = 0; + window_settings->DockOrder = -1; + if (++found < node_ids_count) + break; + return; + } } +} - // Render default drop visuals - payload.Preview = was_accepted_previously; - flags |= (g.DragDropSourceFlags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect); // Source can also inhibit the preview (useful for external sources that lives for 1 frame) - if (!(flags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect) && payload.Preview) - { - // FIXME-DRAG: Settle on a proper default visuals for drop target. - r.Expand(3.5f); - bool push_clip_rect = !window->ClipRect.Contains(r); - if (push_clip_rect) window->DrawList->PushClipRect(r.Min-ImVec2(1,1), r.Max+ImVec2(1,1)); - window->DrawList->AddRect(r.Min, r.Max, GetColorU32(ImGuiCol_DragDropTarget), 0.0f, ~0, 2.0f); - if (push_clip_rect) window->DrawList->PopClipRect(); - } +static ImGuiDockNodeSettings* ImGui::DockSettingsFindNodeSettings(ImGuiContext* ctx, ImGuiID id) +{ + // FIXME-OPT + ImGuiDockContext* dc = ctx->DockContext; + for (int n = 0; n < dc->SettingsNodes.Size; n++) + if (dc->SettingsNodes[n].ID == id) + return &dc->SettingsNodes[n]; + return NULL; +} - g.DragDropAcceptFrameCount = g.FrameCount; - payload.Delivery = was_accepted_previously && !IsMouseDown(g.DragDropMouseButton); // For extern drag sources affecting os window focus, it's easier to just test !IsMouseDown() instead of IsMouseReleased() - if (!payload.Delivery && !(flags & ImGuiDragDropFlags_AcceptBeforeDelivery)) +static void* ImGui::DockSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name) +{ + if (strcmp(name, "Data") != 0) return NULL; - - return &payload; + return (void*)1; } -// We don't really use/need this now, but added it for the sake of consistency and because we might need it later. -void ImGui::EndDragDropTarget() +static void ImGui::DockSettingsHandler_ReadLine(ImGuiContext* ctx, ImGuiSettingsHandler*, void*, const char* line) { - ImGuiContext& g = *GImGui; - IM_ASSERT(g.DragDropActive); - IM_ASSERT(g.DragDropWithinSourceOrTarget); - g.DragDropWithinSourceOrTarget = false; + 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 " + 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.IsDockSpace = true; } + 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.ParentID, &r) == 1) { line += r; if (node.ParentID == 0) return; } + if (node.ParentID == 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, " DocRoot=%d%n", &x, &r) == 1) { line += r; node.IsDocumentRoot = (x != 0); } + if (sscanf(line, " SelectedTab=0x%08X%n", &node.SelectedTabID,&r) == 1) { line += r; } + ImGuiDockContext* dc = ctx->DockContext; + if (node.ParentID != 0) + if (ImGuiDockNodeSettings* parent_settings = DockSettingsFindNodeSettings(ctx, node.ParentID)) + node.Depth = parent_settings->Depth + 1; + dc->SettingsNodes.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.ParentID = node->ParentNode ? node->ParentNode->ID : 0; + node_settings.SelectedTabID = node->SelectedTabID; + node_settings.SplitAxis = node->IsSplitNode() ? (char)node->SplitAxis : ImGuiAxis_None; + node_settings.Depth = (char)depth; + node_settings.IsDockSpace = (char)node->IsDockSpace; + node_settings.IsDocumentRoot = (char)node->IsDocumentRoot; + node_settings.Pos = ImVec2ih((short)node->Pos.x, (short)node->Pos.y); + node_settings.Size = ImVec2ih((short)node->Size.x, (short)node->Size.y); + node_settings.SizeRef = ImVec2ih((short)node->SizeRef.x, (short)node->SizeRef.y); + dc->SettingsNodes.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) +{ + // Gather settings data + // (unlike our windows settings, because nodes are always built we can do a full rewrite of the SettingsNode buffer) + ImGuiDockContext* dc = ctx->DockContext; + dc->SettingsNodes.resize(0); + dc->SettingsNodes.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->SettingsNodes.Size; node_n++) + max_depth = ImMax((int)dc->SettingsNodes[node_n].Depth, max_depth); + + // Write to text buffer + buf->appendf("[%s][Data]\n", handler->TypeName); + for (int node_n = 0; node_n < dc->SettingsNodes.Size; node_n++) + { +#if IMGUI_DEBUG_DOCKING_INI + const int line_start_pos = buf->size(); +#endif + const ImGuiDockNodeSettings* node_settings = &dc->SettingsNodes[node_n]; + buf->appendf("%*s%s%*s", node_settings->Depth * 2, "", node_settings->IsDockSpace ? "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->ParentID) + buf->appendf(" Parent=0x%08X SizeRef=%d,%d", node_settings->ParentID, node_settings->SizeRef.x, node_settings->SizeRef.y); + else + 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->IsDocumentRoot) + buf->appendf(" DocRoot=%d", node_settings->IsDocumentRoot); + if (node_settings->SelectedTabID) + buf->appendf(" SelectedTab=0x%08X", node_settings->SelectedTabID); + +#if IMGUI_DEBUG_DOCKING_INI + // [DEBUG] Include comments in the .ini file to ease debugging + 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); + int contains_window = 0; + for (int window_n = 0; window_n < ctx->SettingsWindows.Size; window_n++) + if (ctx->SettingsWindows[window_n].DockId == node_settings->ID) + { + if (contains_window++ == 0) + buf->appendf(" ; contains "); + buf->appendf("'%s' ", ctx->SettingsWindows[window_n].Name); + } + } +#endif + buf->appendf("\n"); + } + buf->appendf("\n"); } //----------------------------------------------------------------------------- @@ -8447,7 +12449,7 @@ void ImGui::MarkIniSettingsDirty(ImGuiWindow* window) g.SettingsDirtyTimer = g.IO.IniSavingRate; } -static ImGuiWindowSettings* CreateNewWindowSettings(const char* name) +ImGuiWindowSettings* ImGui::CreateNewWindowSettings(const char* name) { ImGuiContext& g = *GImGui; g.SettingsWindows.push_back(ImGuiWindowSettings()); @@ -8466,6 +12468,13 @@ ImGuiWindowSettings* ImGui::FindWindowSettings(ImGuiID id) return NULL; } +ImGuiWindowSettings* ImGui::FindOrCreateWindowSettings(const char* name) +{ + if (ImGuiWindowSettings* settings = FindWindowSettings(ImHash(name, 0))) + return settings; + return CreateNewWindowSettings(name); +} + void ImGui::LoadIniSettingsFromDisk(const char* ini_filename) { size_t file_data_size = 0; @@ -8546,6 +12555,7 @@ void ImGui::LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size) } ImGui::MemFree(buf); g.SettingsLoaded = true; + DockContextOnLoadSettings(&g); } void ImGui::SaveIniSettingsToDisk(const char* ini_filename) @@ -8585,7 +12595,7 @@ static void* SettingsHandlerWindow_ReadOpen(ImGuiContext*, ImGuiSettingsHandler* { ImGuiWindowSettings* settings = ImGui::FindWindowSettings(ImHash(name, 0)); if (!settings) - settings = CreateNewWindowSettings(name); + settings = ImGui::CreateNewWindowSettings(name); return (void*)settings; } @@ -8594,9 +12604,15 @@ static void SettingsHandlerWindow_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, ImGuiWindowSettings* settings = (ImGuiWindowSettings*)entry; float x, y; int i; - if (sscanf(line, "Pos=%f,%f", &x, &y) == 2) settings->Pos = ImVec2(x, y); - else if (sscanf(line, "Size=%f,%f", &x, &y) == 2) settings->Size = ImMax(ImVec2(x, y), GImGui->Style.WindowMinSize); - else if (sscanf(line, "Collapsed=%d", &i) == 1) settings->Collapsed = (i != 0); + ImU32 u1; + if (sscanf(line, "Pos=%f,%f", &x, &y) == 2) { settings->Pos = ImVec2(x, y); } + else if (sscanf(line, "Size=%f,%f", &x, &y) == 2) { settings->Size = ImMax(ImVec2(x, y), GImGui->Style.WindowMinSize); } + else if (sscanf(line, "ViewportId=0x%08X", &u1) == 1) { settings->ViewportId = u1; } + else if (sscanf(line, "ViewportPos=%f,%f", &x, &y) == 2) { settings->ViewportPos = ImVec2(x, y); } + else if (sscanf(line, "Collapsed=%d", &i) == 1) { settings->Collapsed = (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, "DockFamilyId=0x%X", &u1) == 1) { settings->DockFamilyId = u1; } } static void SettingsHandlerWindow_WriteAll(ImGuiContext* imgui_ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf) @@ -8604,6 +12620,8 @@ static void SettingsHandlerWindow_WriteAll(ImGuiContext* imgui_ctx, ImGuiSetting // Gather data from windows that were active during this session // (if a window wasn't opened in this session we preserve its settings) ImGuiContext& g = *imgui_ctx; + if (!(g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable)) + return; for (int i = 0; i != g.Windows.Size; i++) { ImGuiWindow* window = g.Windows[i]; @@ -8613,12 +12631,18 @@ static void SettingsHandlerWindow_WriteAll(ImGuiContext* imgui_ctx, ImGuiSetting ImGuiWindowSettings* settings = (window->SettingsIdx != -1) ? &g.SettingsWindows[window->SettingsIdx] : ImGui::FindWindowSettings(window->ID); if (!settings) { - settings = CreateNewWindowSettings(window->Name); + settings = ImGui::CreateNewWindowSettings(window->Name); window->SettingsIdx = g.SettingsWindows.index_from_pointer(settings); } IM_ASSERT(settings->ID == window->ID); - settings->Pos = window->Pos; + settings->Pos = window->Pos - window->ViewportPos; settings->Size = window->SizeFull; + settings->ViewportId = window->ViewportId; + settings->ViewportPos = window->ViewportPos; + IM_ASSERT(window->DockNode == NULL || window->DockNode->ID == window->DockId); + settings->DockId = window->DockId; + settings->DockFamilyId = window->DockFamily.ID; + settings->DockOrder = window->DockOrder; settings->Collapsed = window->Collapsed; } @@ -8627,15 +12651,30 @@ static void SettingsHandlerWindow_WriteAll(ImGuiContext* imgui_ctx, ImGuiSetting for (int i = 0; i != g.SettingsWindows.Size; i++) { const ImGuiWindowSettings* settings = &g.SettingsWindows[i]; - if (settings->Pos.x == FLT_MAX) - continue; const char* name = settings->Name; if (const char* p = strstr(name, "###")) // Skip to the "###" marker if any. We don't skip past to match the behavior of GetID() name = p; buf->appendf("[%s][%s]\n", handler->TypeName, name); - buf->appendf("Pos=%d,%d\n", (int)settings->Pos.x, (int)settings->Pos.y); - buf->appendf("Size=%d,%d\n", (int)settings->Size.x, (int)settings->Size.y); + if (settings->ViewportId != 0 && settings->ViewportId != ImGui::IMGUI_VIEWPORT_DEFAULT_ID) + { + buf->appendf("ViewportPos=%d,%d\n", (int)settings->ViewportPos.x, (int)settings->ViewportPos.y); + buf->appendf("ViewportId=0x%08X\n", settings->ViewportId); + } + if (settings->Pos.x != 0.0f || settings->Pos.y != 0.0f || settings->ViewportId == ImGui::IMGUI_VIEWPORT_DEFAULT_ID) + buf->appendf("Pos=%d,%d\n", (int)settings->Pos.x, (int)settings->Pos.y); + if (settings->Size.x != 0.0f || settings->Size.y != 0.0f) + buf->appendf("Size=%d,%d\n", (int)settings->Size.x, (int)settings->Size.y); buf->appendf("Collapsed=%d\n", settings->Collapsed); + if (settings->DockId != 0) + { + // Write DockId as 4 digits if possible. Automatic DockId are small numbers, but full explicit DockSpace() are full ImGuiID range. + 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->DockFamilyId != 0) + buf->appendf("DockFamilyId=0x%08X\n", settings->DockFamilyId); + } buf->appendf("\n"); } } @@ -8727,38 +12766,68 @@ static void SetClipboardTextFn_DefaultImpl(void*, const char* text) #endif -// Win32 API IME support (for Asian languages, etc.) -#if defined(_WIN32) && !defined(__GNUC__) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS) - -#include -#ifdef _MSC_VER -#pragma comment(lib, "imm32") -#endif +//----------------------------------------------------------------------------- +// [SECTION] METRICS/DEBUG WINDOW +//----------------------------------------------------------------------------- -static void ImeSetInputScreenPosFn_DefaultImpl(int x, int y) +static void RenderViewportThumbnail(ImDrawList* draw_list, ImGuiViewportP* viewport, const ImRect& bb) { - // Notify OS Input Method Editor of text input position - if (HWND hwnd = (HWND)GImGui->IO.ImeWindowHandle) - if (HIMC himc = ::ImmGetContext(hwnd)) - { - COMPOSITIONFORM cf; - cf.ptCurrentPos.x = x; - cf.ptCurrentPos.y = y; - cf.dwStyle = CFS_FORCE_POSITION; - ::ImmSetCompositionWindow(himc, &cf); - ::ImmReleaseContext(hwnd, himc); - } -} + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; -#else + ImVec2 scale = bb.GetSize() / viewport->Size; + ImVec2 off = bb.Min - viewport->Pos * scale; + window->DrawList->AddRectFilled(bb.Min, bb.Max, ImGui::GetColorU32(ImGuiCol_Border, 0.40f)); + for (int i = 0; i != g.Windows.Size; i++) + { + ImGuiWindow* thumb_window = g.Windows[i]; + if (!thumb_window->WasActive || ((thumb_window->Flags & ImGuiWindowFlags_ChildWindow))) + continue; + if (thumb_window->SkipItems && (thumb_window->Flags & ImGuiWindowFlags_ChildWindow)) // FIXME-DOCK: Skip hidden docked windows. Identify those betters. + continue; + if (thumb_window->Viewport != viewport) + continue; -static void ImeSetInputScreenPosFn_DefaultImpl(int, int) {} + ImRect thumb_r = thumb_window->Rect(); + ImRect title_r = thumb_window->TitleBarRect(); + ImRect thumb_r_scaled = ImRect(ImFloor(off + thumb_r.Min * scale), ImFloor(off + thumb_r.Max * scale)); + ImRect title_r_scaled = ImRect(ImFloor(off + title_r.Min * scale), ImFloor(off + ImVec2(title_r.Max.x, title_r.Min.y) * scale) + ImVec2(0,5)); // Exaggerate title bar height + thumb_r_scaled.ClipWithFull(bb); + title_r_scaled.ClipWithFull(bb); + const bool window_is_focused = (g.NavWindow && thumb_window->RootWindowForTitleBarHighlight == g.NavWindow->RootWindowForTitleBarHighlight); + window->DrawList->AddRectFilled(thumb_r_scaled.Min, thumb_r_scaled.Max, ImGui::GetColorU32(ImGuiCol_WindowBg)); + window->DrawList->AddRectFilled(title_r_scaled.Min, title_r_scaled.Max, ImGui::GetColorU32(window_is_focused ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg)); + window->DrawList->AddRect(thumb_r_scaled.Min, thumb_r_scaled.Max, ImGui::GetColorU32(ImGuiCol_Border)); + if (ImGuiWindow* window_for_title = GetWindowForTitleDisplay(thumb_window)) + window->DrawList->AddText(g.Font, g.FontSize * 1.0f, title_r_scaled.Min, ImGui::GetColorU32(ImGuiCol_Text), window_for_title->Name, ImGui::FindRenderedTextEnd(window_for_title->Name)); + } + draw_list->AddRect(bb.Min, bb.Max, ImGui::GetColorU32(ImGuiCol_Border)); +} -#endif +void ImGui::ShowViewportThumbnails() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; -//----------------------------------------------------------------------------- -// [SECTION] METRICS/DEBUG WINDOW -//----------------------------------------------------------------------------- + // We don't display full monitor bounds (we could, but it often looks awkward), instead we display just enough to cover all of our viewports. + float SCALE = 1.0f / 8.0f; + ImRect bb_full; + //for (int n = 0; n < g.PlatformIO.Monitors.Size; n++) + // bb_full.Add(GetPlatformMonitorMainRect(g.PlatformIO.Monitors[n])); + for (int n = 0; n < g.Viewports.Size; n++) + bb_full.Add(g.Viewports[n]->GetRect()); + ImVec2 p = window->DC.CursorPos; + ImVec2 off = p - bb_full.Min * SCALE; + //for (int n = 0; n < g.PlatformIO.Monitors.Size; n++) + // window->DrawList->AddRect(off + g.PlatformIO.Monitors[n].MainPos * SCALE, off + (g.PlatformIO.Monitors[n].MainPos + g.PlatformIO.Monitors[n].MainSize) * SCALE, ImGui::GetColorU32(ImGuiCol_Border)); + for (int n = 0; n < g.Viewports.Size; n++) + { + ImGuiViewportP* viewport = g.Viewports[n]; + ImRect viewport_draw_bb(off + (viewport->Pos) * SCALE, off + (viewport->Pos + viewport->Size) * SCALE); + RenderViewportThumbnail(window->DrawList, viewport, viewport_draw_bb); + } + ImGui::Dummy(bb_full.GetSize() * SCALE); +} void ImGui::ShowMetricsWindow(bool* p_open) { @@ -8767,91 +12836,91 @@ void ImGui::ShowMetricsWindow(bool* p_open) ImGui::End(); return; } - static bool show_draw_cmd_clip_rects = true; - static bool show_window_begin_order = false; - ImGuiIO& io = ImGui::GetIO(); - ImGui::Text("Dear ImGui %s", ImGui::GetVersion()); - ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); - ImGui::Text("%d vertices, %d indices (%d triangles)", io.MetricsRenderVertices, io.MetricsRenderIndices, io.MetricsRenderIndices / 3); - ImGui::Text("%d active windows (%d visible)", io.MetricsActiveWindows, io.MetricsRenderWindows); - ImGui::Text("%d allocations", io.MetricsActiveAllocations); - ImGui::Checkbox("Show clipping rectangles when hovering draw commands", &show_draw_cmd_clip_rects); - ImGui::Checkbox("Ctrl shows window begin order", &show_window_begin_order); - ImGui::Separator(); - - struct Funcs + static bool show_draw_cmd_clip_rects = true; + static bool show_window_begin_order = false; + ImGuiIO& io = ImGui::GetIO(); + ImGui::Text("Dear ImGui %s", ImGui::GetVersion()); + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); + ImGui::Text("%d vertices, %d indices (%d triangles)", io.MetricsRenderVertices, io.MetricsRenderIndices, io.MetricsRenderIndices / 3); + ImGui::Text("%d active windows (%d visible)", io.MetricsActiveWindows, io.MetricsRenderWindows); + ImGui::Text("%d allocations", io.MetricsActiveAllocations); + ImGui::Checkbox("Show clipping rectangles when hovering draw commands", &show_draw_cmd_clip_rects); + ImGui::Checkbox("Ctrl shows window begin order", &show_window_begin_order); + ImGui::Separator(); + + struct Funcs + { + static void NodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, ImDrawList* draw_list, const char* label) { - static void NodeDrawList(ImGuiWindow* window, ImDrawList* draw_list, const char* label) + bool node_open = ImGui::TreeNode(draw_list, "%s: '%s' %d vtx, %d indices, %d cmds", label, draw_list->_OwnerName ? draw_list->_OwnerName : "", draw_list->VtxBuffer.Size, draw_list->IdxBuffer.Size, draw_list->CmdBuffer.Size); + if (draw_list == ImGui::GetWindowDrawList()) { - bool node_open = ImGui::TreeNode(draw_list, "%s: '%s' %d vtx, %d indices, %d cmds", label, draw_list->_OwnerName ? draw_list->_OwnerName : "", draw_list->VtxBuffer.Size, draw_list->IdxBuffer.Size, draw_list->CmdBuffer.Size); - if (draw_list == ImGui::GetWindowDrawList()) - { - ImGui::SameLine(); - ImGui::TextColored(ImColor(255,100,100), "CURRENTLY APPENDING"); // Can't display stats for active draw list! (we don't have the data double-buffered) - if (node_open) ImGui::TreePop(); - return; - } + ImGui::SameLine(); + ImGui::TextColored(ImColor(255,100,100), "CURRENTLY APPENDING"); // Can't display stats for active draw list! (we don't have the data double-buffered) + if (node_open) ImGui::TreePop(); + return; + } - ImDrawList* overlay_draw_list = GetOverlayDrawList(); // Render additional visuals into the top-most draw list - if (window && IsItemHovered()) - overlay_draw_list->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 255, 0, 255)); - if (!node_open) - return; + ImDrawList* overlay_draw_list = viewport ? GetOverlayDrawList(viewport) : NULL; // Render additional visuals into the top-most draw list + if (window && overlay_draw_list && ImGui::IsItemHovered()) + overlay_draw_list->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 255, 0, 255)); + if (!node_open) + return; - int elem_offset = 0; - for (const ImDrawCmd* pcmd = draw_list->CmdBuffer.begin(); pcmd < draw_list->CmdBuffer.end(); elem_offset += pcmd->ElemCount, pcmd++) + int elem_offset = 0; + for (const ImDrawCmd* pcmd = draw_list->CmdBuffer.begin(); pcmd < draw_list->CmdBuffer.end(); elem_offset += pcmd->ElemCount, pcmd++) + { + if (pcmd->UserCallback == NULL && pcmd->ElemCount == 0) + continue; + if (pcmd->UserCallback) { - if (pcmd->UserCallback == NULL && pcmd->ElemCount == 0) - continue; - if (pcmd->UserCallback) - { - ImGui::BulletText("Callback %p, user_data %p", pcmd->UserCallback, pcmd->UserCallbackData); - continue; - } - ImDrawIdx* idx_buffer = (draw_list->IdxBuffer.Size > 0) ? draw_list->IdxBuffer.Data : NULL; - bool pcmd_node_open = ImGui::TreeNode((void*)(pcmd - draw_list->CmdBuffer.begin()), "Draw %4d %s vtx, tex 0x%p, clip_rect (%4.0f,%4.0f)-(%4.0f,%4.0f)", pcmd->ElemCount, draw_list->IdxBuffer.Size > 0 ? "indexed" : "non-indexed", pcmd->TextureId, pcmd->ClipRect.x, pcmd->ClipRect.y, pcmd->ClipRect.z, pcmd->ClipRect.w); - if (show_draw_cmd_clip_rects && ImGui::IsItemHovered()) - { - ImRect clip_rect = pcmd->ClipRect; - ImRect vtxs_rect; - for (int i = elem_offset; i < elem_offset + (int)pcmd->ElemCount; i++) - vtxs_rect.Add(draw_list->VtxBuffer[idx_buffer ? idx_buffer[i] : i].pos); - clip_rect.Floor(); overlay_draw_list->AddRect(clip_rect.Min, clip_rect.Max, IM_COL32(255,255,0,255)); - vtxs_rect.Floor(); overlay_draw_list->AddRect(vtxs_rect.Min, vtxs_rect.Max, IM_COL32(255,0,255,255)); - } - if (!pcmd_node_open) - continue; + ImGui::BulletText("Callback %p, user_data %p", pcmd->UserCallback, pcmd->UserCallbackData); + continue; + } + ImDrawIdx* idx_buffer = (draw_list->IdxBuffer.Size > 0) ? draw_list->IdxBuffer.Data : NULL; + bool pcmd_node_open = ImGui::TreeNode((void*)(pcmd - draw_list->CmdBuffer.begin()), "Draw %4d %s vtx, tex 0x%p, clip_rect (%4.0f,%4.0f)-(%4.0f,%4.0f)", pcmd->ElemCount, draw_list->IdxBuffer.Size > 0 ? "indexed" : "non-indexed", pcmd->TextureId, pcmd->ClipRect.x, pcmd->ClipRect.y, pcmd->ClipRect.z, pcmd->ClipRect.w); + if (show_draw_cmd_clip_rects && overlay_draw_list && ImGui::IsItemHovered()) + { + ImRect clip_rect = pcmd->ClipRect; + ImRect vtxs_rect; + for (int i = elem_offset; i < elem_offset + (int)pcmd->ElemCount; i++) + vtxs_rect.Add(draw_list->VtxBuffer[idx_buffer ? idx_buffer[i] : i].pos); + clip_rect.Floor(); overlay_draw_list->AddRect(clip_rect.Min, clip_rect.Max, IM_COL32(255,255,0,255)); + vtxs_rect.Floor(); overlay_draw_list->AddRect(vtxs_rect.Min, vtxs_rect.Max, IM_COL32(255,0,255,255)); + } + if (!pcmd_node_open) + continue; - // Display individual triangles/vertices. Hover on to get the corresponding triangle highlighted. - ImGuiListClipper clipper(pcmd->ElemCount/3); // Manually coarse clip our print out of individual vertices to save CPU, only items that may be visible. - while (clipper.Step()) - for (int prim = clipper.DisplayStart, vtx_i = elem_offset + clipper.DisplayStart*3; prim < clipper.DisplayEnd; prim++) + // Display individual triangles/vertices. Hover on to get the corresponding triangle highlighted. + ImGuiListClipper clipper(pcmd->ElemCount/3); // Manually coarse clip our print out of individual vertices to save CPU, only items that may be visible. + while (clipper.Step()) + for (int prim = clipper.DisplayStart, vtx_i = elem_offset + clipper.DisplayStart*3; prim < clipper.DisplayEnd; prim++) + { + char buf[300]; + char *buf_p = buf, *buf_end = buf + IM_ARRAYSIZE(buf); + ImVec2 triangles_pos[3]; + for (int n = 0; n < 3; n++, vtx_i++) { - char buf[300]; - char *buf_p = buf, *buf_end = buf + IM_ARRAYSIZE(buf); - ImVec2 triangles_pos[3]; - for (int n = 0; n < 3; n++, vtx_i++) - { - ImDrawVert& v = draw_list->VtxBuffer[idx_buffer ? idx_buffer[vtx_i] : vtx_i]; - triangles_pos[n] = v.pos; - buf_p += ImFormatString(buf_p, (int)(buf_end - buf_p), "%s %04d: pos (%8.2f,%8.2f), uv (%.6f,%.6f), col %08X\n", (n == 0) ? "vtx" : " ", vtx_i, v.pos.x, v.pos.y, v.uv.x, v.uv.y, v.col); - } - ImGui::Selectable(buf, false); - if (ImGui::IsItemHovered()) - { - ImDrawListFlags backup_flags = overlay_draw_list->Flags; - overlay_draw_list->Flags &= ~ImDrawListFlags_AntiAliasedLines; // Disable AA on triangle outlines at is more readable for very large and thin triangles. - overlay_draw_list->AddPolyline(triangles_pos, 3, IM_COL32(255,255,0,255), true, 1.0f); - overlay_draw_list->Flags = backup_flags; - } + ImDrawVert& v = draw_list->VtxBuffer[idx_buffer ? idx_buffer[vtx_i] : vtx_i]; + triangles_pos[n] = v.pos; + buf_p += ImFormatString(buf_p, (int)(buf_end - buf_p), "%s %04d: pos (%8.2f,%8.2f), uv (%.6f,%.6f), col %08X\n", (n == 0) ? "vtx" : " ", vtx_i, v.pos.x, v.pos.y, v.uv.x, v.uv.y, v.col); } - ImGui::TreePop(); - } + ImGui::Selectable(buf, false); + if (overlay_draw_list && ImGui::IsItemHovered()) + { + ImDrawListFlags backup_flags = overlay_draw_list->Flags; + overlay_draw_list->Flags &= ~ImDrawListFlags_AntiAliasedLines; // Disable AA on triangle outlines at is more readable for very large and thin triangles. + overlay_draw_list->AddPolyline(triangles_pos, 3, IM_COL32(255,255,0,255), true, 1.0f); + overlay_draw_list->Flags = backup_flags; + } + } ImGui::TreePop(); } + ImGui::TreePop(); + } - static void NodeWindows(ImVector& windows, const char* label) + static void NodeWindows(ImVector& windows, const char* label) { if (!ImGui::TreeNode(label, "%s (%d)", label, windows.Size)) return; @@ -8865,7 +12934,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) if (!ImGui::TreeNode(window, "%s '%s', %d @ 0x%p", label, window->Name, window->Active || window->WasActive, window)) return; ImGuiWindowFlags flags = window->Flags; - NodeDrawList(window, window->DrawList, "DrawList"); + NodeDrawList(window, window->Viewport, window->DrawList, "DrawList"); ImGui::BulletText("Pos: (%.1f,%.1f), Size: (%.1f,%.1f), SizeContents (%.1f,%.1f)", window->Pos.x, window->Pos.y, window->Size.x, window->Size.y, window->SizeContents.x, window->SizeContents.y); ImGui::BulletText("Flags: 0x%08X (%s%s%s%s%s%s%s%s..)", flags, (flags & ImGuiWindowFlags_ChildWindow) ? "Child " : "", (flags & ImGuiWindowFlags_Tooltip) ? "Tooltip " : "", (flags & ImGuiWindowFlags_Popup) ? "Popup " : "", @@ -8880,7 +12949,11 @@ void ImGui::ShowMetricsWindow(bool* p_open) ImGui::BulletText("NavRectRel[0]: (%.1f,%.1f)(%.1f,%.1f)", window->NavRectRel[0].Min.x, window->NavRectRel[0].Min.y, window->NavRectRel[0].Max.x, window->NavRectRel[0].Max.y); else ImGui::BulletText("NavRectRel[0]: "); + ImGui::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); + ImGui::BulletText("ViewportMonitor: %d", window->Viewport ? window->Viewport->PlatformMonitor : -1); + ImGui::BulletText("DockId: 0x%04X, DockOrder: %d, %s: 0x%p", window->DockId, window->DockOrder, window->DockNodeAsHost ? "DockNodeAsHost" : "DockNode", window->DockNodeAsHost ? window->DockNodeAsHost : window->DockNode); if (window->RootWindow != window) NodeWindow(window->RootWindow, "RootWindow"); + if (window->RootWindowDockStop != window->RootWindow) NodeWindow(window->RootWindowDockStop, "RootWindowDockStop"); if (window->ParentWindow != NULL) NodeWindow(window->ParentWindow, "ParentWindow"); if (window->DC.ChildWindows.Size > 0) NodeWindows(window->DC.ChildWindows, "ChildWindows"); if (window->ColumnsStorage.Size > 0 && ImGui::TreeNode("Columns", "Columns sets (%d)", window->ColumnsStorage.Size)) @@ -8901,15 +12974,50 @@ void ImGui::ShowMetricsWindow(bool* p_open) ImGui::BulletText("Storage: %d bytes", window->StateStorage.Data.Size * (int)sizeof(ImGuiStorage::Pair)); ImGui::TreePop(); } + + static void NodeViewport(ImGuiViewportP* viewport) + { + ImGui::SetNextTreeNodeOpen(true, ImGuiCond_Once); + if (ImGui::TreeNode((void*)(intptr_t)viewport->ID, "Viewport #%d, ID: 0x%08X, Window: \"%s\"", viewport->Idx, viewport->ID, viewport->Window ? viewport->Window->Name : "N/A")) + { + ImGuiWindowFlags flags = viewport->Flags; + ImGui::BulletText("Pos: (%.0f,%.0f), Size: (%.0f,%.0f), Monitor: %d, DpiScale: %.0f%%", viewport->Pos.x, viewport->Pos.y, viewport->Size.x, viewport->Size.y, viewport->PlatformMonitor, viewport->DpiScale * 100.0f); + if (viewport->Idx > 0) { ImGui::SameLine(); if (ImGui::SmallButton("Reset Pos")) { viewport->Pos = ImVec2(200,200); if (viewport->Window) viewport->Window->Pos = ImVec2(200,200); } } + ImGui::BulletText("Flags: 0x%04X =%s%s%s%s%s", viewport->Flags, + (flags & ImGuiViewportFlags_CanHostOtherWindows) ? " CanHostOtherWindows" : "", (flags & ImGuiViewportFlags_NoDecoration) ? " NoDecoration" : "", + (flags & ImGuiViewportFlags_NoFocusOnAppearing) ? " NoFocusOnAppearing" : "", (flags & ImGuiViewportFlags_NoInputs) ? " NoInputs" : "", + (flags & ImGuiViewportFlags_NoRendererClear) ? " NoRendererClear" : ""); + for (int layer_i = 0; layer_i < IM_ARRAYSIZE(viewport->DrawDataBuilder.Layers); layer_i++) + for (int draw_list_i = 0; draw_list_i < viewport->DrawDataBuilder.Layers[layer_i].Size; draw_list_i++) + Funcs::NodeDrawList(NULL, viewport, viewport->DrawDataBuilder.Layers[layer_i][draw_list_i], "DrawList"); + ImGui::TreePop(); + } + } }; // Access private state, we are going to display the draw lists from last frame ImGuiContext& g = *GImGui; Funcs::NodeWindows(g.Windows, "Windows"); - if (ImGui::TreeNode("DrawList", "Active DrawLists (%d)", g.DrawDataBuilder.Layers[0].Size)) + if (ImGui::TreeNode("Viewport", "Viewports (%d)", g.Viewports.Size)) { - for (int i = 0; i < g.DrawDataBuilder.Layers[0].Size; i++) - Funcs::NodeDrawList(NULL, g.DrawDataBuilder.Layers[0][i], "DrawList"); + ImGui::Indent(ImGui::GetTreeNodeToLabelSpacing()); + ImGui::ShowViewportThumbnails(); + ImGui::Unindent(ImGui::GetTreeNodeToLabelSpacing()); + if (g.PlatformIO.Monitors.Size > 0 && ImGui::TreeNode("Monitors", "Monitors (%d)", g.PlatformIO.Monitors.Size)) + { + ImGui::TextWrapped("(When viewports are enabled, imgui optionally uses monitor data to position popup/tooltips so they don't straddle monitors.)"); + for (int i = 0; i < g.PlatformIO.Monitors.Size; i++) + { + const ImGuiPlatformMonitor& mon = g.PlatformIO.Monitors[i]; + ImGui::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); + } + ImGui::TreePop(); + } + for (int i = 0; i < g.Viewports.Size; i++) + Funcs::NodeViewport(g.Viewports[i]); ImGui::TreePop(); } if (ImGui::TreeNode("Popups", "Popups (%d)", g.OpenPopupStack.Size)) @@ -8921,11 +13029,17 @@ void ImGui::ShowMetricsWindow(bool* p_open) } ImGui::TreePop(); } + if (ImGui::TreeNode("Docking & Tabs")) + { + ShowDockingDebug(); + ImGui::TreePop(); + } if (ImGui::TreeNode("Internal state")) { const char* input_source_names[] = { "None", "Mouse", "Nav", "NavKeyboard", "NavGamepad" }; IM_ASSERT(IM_ARRAYSIZE(input_source_names) == ImGuiInputSource_COUNT); ImGui::Text("HoveredWindow: '%s'", g.HoveredWindow ? g.HoveredWindow->Name : "NULL"); ImGui::Text("HoveredRootWindow: '%s'", g.HoveredRootWindow ? g.HoveredRootWindow->Name : "NULL"); + ImGui::Text("HoveredWindowUnderMovingWindow: '%s'", g.HoveredWindowUnderMovingWindow ? g.HoveredWindowUnderMovingWindow->Name : "NULL"); ImGui::Text("HoveredId: 0x%08X/0x%08X (%.2f sec), AllowOverlap: %d", g.HoveredId, g.HoveredIdPreviousFrame, g.HoveredIdTimer, g.HoveredIdAllowOverlap); // Data is "in-flight" so depending on when the Metrics window is called we may see current frame information or not ImGui::Text("ActiveId: 0x%08X/0x%08X (%.2f sec), AllowOverlap: %d, Source: %s", g.ActiveId, g.ActiveIdPreviousFrame, g.ActiveIdTimer, g.ActiveIdAllowOverlap, input_source_names[g.ActiveIdSource]); ImGui::Text("ActiveIdWindow: '%s'", g.ActiveIdWindow ? g.ActiveIdWindow->Name : "NULL"); @@ -8938,28 +13052,171 @@ void ImGui::ShowMetricsWindow(bool* p_open) ImGui::Text("NavDisableHighlight: %d, NavDisableMouseHover: %d", g.NavDisableHighlight, g.NavDisableMouseHover); ImGui::Text("NavWindowingTarget: '%s'", g.NavWindowingTarget ? g.NavWindowingTarget->Name : "NULL"); ImGui::Text("DragDrop: %d, SourceId = 0x%08X, Payload \"%s\" (%d bytes)", g.DragDropActive, g.DragDropPayload.SourceId, g.DragDropPayload.DataType, g.DragDropPayload.DataSize); + ImGui::Text("MouseViewport: 0x%08X (UserHovered 0x%08X, LastHovered 0x%08X)", g.MouseViewport->ID, g.IO.MouseHoveredViewport, g.MouseLastHoveredViewport->ID); ImGui::TreePop(); } - if (g.IO.KeyCtrl && show_window_begin_order) { for (int n = 0; n < g.Windows.Size; n++) { ImGuiWindow* window = g.Windows[n]; - if ((window->Flags & ImGuiWindowFlags_ChildWindow) || !window->WasActive) + if (!window->WasActive || ((window->Flags & ImGuiWindowFlags_ChildWindow) && window->DockNode == NULL)) continue; - char buf[32]; - ImFormatString(buf, IM_ARRAYSIZE(buf), "%d", window->BeginOrderWithinContext); - float font_size = ImGui::GetFontSize() * 2; - ImDrawList* overlay_draw_list = GetOverlayDrawList(); - overlay_draw_list->AddRectFilled(window->Pos, window->Pos + ImVec2(font_size, font_size), IM_COL32(200, 100, 100, 255)); - overlay_draw_list->AddText(NULL, font_size, window->Pos, IM_COL32(255, 255, 255, 255), buf); + + char buf[64] = ""; + char* p = buf; + p += ImFormatString(p, buf + IM_ARRAYSIZE(buf) - p, "Order: %d\n", window->BeginOrderWithinContext); + ImDrawList* overlay_draw_list = GetOverlayDrawList(window->Viewport); + overlay_draw_list->AddRectFilled(window->Pos - ImVec2(1, 1), window->Pos + CalcTextSize(buf) + ImVec2(1, 1), IM_COL32(200, 100, 100, 255)); + overlay_draw_list->AddText(NULL, 0.0f, window->Pos, IM_COL32(255, 255, 255, 255), buf); } } ImGui::End(); } +void ImGui::ShowDockingDebug() +{ + ImGuiContext* ctx = GImGui; + ImGuiContext& g = *ctx; + ImGuiDockContext* dc = ctx->DockContext; + + struct Funcs + { + static void NodeDockNode(ImGuiDockNode* node, const char* label) + { + ImGui::SetNextTreeNodeOpen(true, ImGuiCond_Once); + bool open; + if (node->Windows.Size > 0) + open = ImGui::TreeNode((void*)(intptr_t)node->ID, "%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 = ImGui::TreeNode((void*)(intptr_t)node->ID, "%s 0x%04X%s: split %s (act: '%s')", label, node->ID, node->IsVisible ? "" : " (hidden)", (node->SplitAxis == ImGuiAxis_X) ? "horizontal" : (node->SplitAxis == ImGuiAxis_Y) ? "vertical" : "n/a", node->VisibleWindow ? node->VisibleWindow->Name : "NULL"); + if (open) + { + IM_ASSERT(node->ChildNodes[0] == NULL || node->ChildNodes[0]->ParentNode == node); + IM_ASSERT(node->ChildNodes[1] == NULL || node->ChildNodes[1]->ParentNode == node); + ImGui::BulletText("Pos (%.0f,%.0f), Size (%.0f, %.0f), LastExplicit (%.0f, %.0f)", + node->Pos.x, node->Pos.y, node->Size.x, node->Size.y, + node->SizeRef.x, node->SizeRef.y); + ImGui::BulletText("Flags %02X%s%s%s%s", + node->Flags, node->IsDockSpace ? ", IsDockSpace" : "", node->IsDocumentRoot ? ", IsDocumentRoot" : "", + (GImGui->FrameCount - node->LastFrameAlive < 2) ? ", IsAlive" : "", (GImGui->FrameCount - node->LastFrameActive < 2) ? ", IsActive" : ""); + if (node->ChildNodes[0]) + NodeDockNode(node->ChildNodes[0], "Child[0]"); + if (node->ChildNodes[1]) + NodeDockNode(node->ChildNodes[1], "Child[1]"); + if (node->TabBar) + NodeTabBar(node->TabBar); + ImGui::TreePop(); + } + } + static void NodeTabBar(ImGuiTabBar* tab_bar) + { + // Previous window list + char buf[256]; + char* p = buf; + const char* buf_end = buf + IM_ARRAYSIZE(buf); + p += ImFormatString(p, buf_end - p, "TabBar (%d tabs)%s", + tab_bar->Tabs.Size, (tab_bar->PrevFrameVisible < ImGui::GetFrameCount() - 2) ? " *Inactive*" : ""); + if (tab_bar->Flags & ImGuiTabBarFlags_DockNode) + { + p += ImFormatString(p, buf_end - p, " { "); + for (int tab_n = 0; tab_n < ImMin(tab_bar->Tabs.Size, 3); tab_n++) + p += ImFormatString(p, buf_end - p, "%s'%s'", tab_n > 0 ? ", " : "", tab_bar->Tabs[tab_n].Window->Name); + p += ImFormatString(p, buf_end - p, (tab_bar->Tabs.Size > 3) ? " ... }" : " } "); + } + if (ImGui::TreeNode(tab_bar, "%s", buf)) + { + for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++) + { + const ImGuiTabItem* tab = &tab_bar->Tabs[tab_n]; + ImGui::PushID(tab); + if (ImGui::SmallButton("<")) { TabBarQueueChangeTabOrder(tab_bar, tab, -1); } ImGui::SameLine(0, 2); + if (ImGui::SmallButton(">")) { TabBarQueueChangeTabOrder(tab_bar, tab, +1); } ImGui::SameLine(); + ImGui::Text("%02d%c Tab 0x%08X '%s'", tab_n, (tab->ID == tab_bar->SelectedTabId) ? '*' : ' ', tab->ID, tab->Window ? tab->Window->Name : "N/A"); + ImGui::PopID(); + } + ImGui::TreePop(); + } + } + }; + + static bool show_window_dock_info = false; + ImGui::Checkbox("Ctrl shows window dock info", &show_window_dock_info); + + if (ImGui::TreeNode("Dock nodes")) + { + if (ImGui::SmallButton("Clear settings")) { DockContextClearNodes(&g, 0, true); } + ImGui::SameLine(); + if (ImGui::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 (node->IsRootNode()) + Funcs::NodeDockNode(node, "Node"); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("TabBars", "Tab Bars (%d)", g.TabBars.Data.Size)) + { + for (int n = 0; n < g.TabBars.Data.Size; n++) + Funcs::NodeTabBar(g.TabBars.GetByIndex(n)); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Settings")) + { + if (ImGui::SmallButton("Refresh")) + SaveIniSettingsToMemory(); + ImGui::SameLine(); + if (ImGui::SmallButton("Save to disk")) + SaveIniSettingsToDisk(g.IO.IniFilename); + ImGui::Separator(); + ImGui::Text("Docked Windows:"); + for (int n = 0; n < g.SettingsWindows.Size; n++) + if (g.SettingsWindows[n].DockId != 0) + ImGui::BulletText("Window '%s' -> DockId %08X", g.SettingsWindows[n].Name, g.SettingsWindows[n].DockId); + ImGui::Separator(); + ImGui::Text("Dock Nodes:"); + for (int n = 0; n < dc->SettingsNodes.Size; n++) + { + ImGuiDockNodeSettings* settings = &dc->SettingsNodes[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 = FindWindowSettings(settings->SelectedTabID)) + selected_tab_name = window_settings->Name; + } + ImGui::BulletText("Node %08X, Parent %08X, SelectedTab %08X ('%s')", settings->ID, settings->ParentID, settings->SelectedTabID, selected_tab_name ? selected_tab_name : settings->SelectedTabID ? "N/A" : ""); + } + ImGui::TreePop(); + } + + if (g.IO.KeyCtrl && show_window_dock_info) + { + for (int n = 0; n < dc->Nodes.Data.Size; n++) + if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p) + { + ImGuiDockNode* root_node = DockNodeGetRootNode(node); + if (ImGuiDockNode* hovered_node = DockNodeTreeFindNodeByPos(root_node, g.IO.MousePos)) + if (hovered_node != node) + continue; + char buf[64] = ""; + char* p = buf; + ImDrawList* overlay_draw_list = node->HostWindow ? GetOverlayDrawList(node->HostWindow) : GetOverlayDrawList((ImGuiViewportP*)GetMainViewport()); + p += ImFormatString(p, buf + IM_ARRAYSIZE(buf) - p, "DockId: %X%s\n", node->ID, node->IsDocumentRoot ? " *DocRoot*" : ""); + 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); + } + } +} + //----------------------------------------------------------------------------- // Include imgui_user.inl at the end of imgui.cpp to access private data/functions that aren't exposed. diff --git a/Source Code/Libs/imgui/imgui.h b/Source Code/Libs/imgui/imgui.h index 26c8887c1b..c89aca776c 100644 --- a/Source Code/Libs/imgui/imgui.h +++ b/Source Code/Libs/imgui/imgui.h @@ -1,4 +1,4 @@ -// dear imgui, v1.65 +// dear imgui, v1.66 WIP // (headers) // See imgui.cpp file for documentation. @@ -23,9 +23,12 @@ // Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals. Work in progress versions typically starts at XYY00 then bounced up to XYY01 when release tagging happens) -#define IMGUI_VERSION "1.65" -#define IMGUI_VERSION_NUM 16501 +#define IMGUI_VERSION "1.66 WIP" +#define IMGUI_VERSION_NUM 16600 #define IMGUI_CHECKVERSION() ImGui::DebugCheckVersionAndDataLayout(IMGUI_VERSION, sizeof(ImGuiIO), sizeof(ImGuiStyle), sizeof(ImVec2), sizeof(ImVec4), sizeof(ImDrawVert)) +#define IMGUI_HAS_VIEWPORT 1 // Viewport WIP branch +#define IMGUI_HAS_DOCK 1 // Docking WIP branch +#define IMGUI_HAS_TABS 1 // Docking WIP branch // Define attributes of all API symbols declarations (e.g. for DLL under Windows) // IMGUI_API is used for core imgui functions, IMGUI_IMPL_API is used for the default bindings files (imgui_impl_xxx.h) @@ -74,16 +77,20 @@ struct ImColor; // Helper functions to create a color that c typedef void* ImTextureID; // User data to identify a texture (this is whatever to you want it to be! read the FAQ about ImTextureID in imgui.cpp) #endif struct ImGuiContext; // ImGui context (opaque) +struct ImGuiDockFamily; // Docking family for dock filtering struct ImGuiIO; // Main configuration and I/O between your application and ImGui struct ImGuiInputTextCallbackData; // Shared state of InputText() when using custom ImGuiInputTextCallback (rare/advanced use) 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, used by IMGUI_ONCE_UPON_A_FRAME macro struct ImGuiPayload; // User data payload for drag and drop operations +struct ImGuiPlatformIO; // Multi-viewport support: interface for Platform/Renderer back-ends + 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 ImGuiSizeCallbackData; // Callback data when using SetNextWindowSizeConstraints() (rare/advanced use) struct ImGuiStorage; // Helper for key->value storage struct ImGuiStyle; // Runtime data for styling/colors struct ImGuiTextFilter; // Helper to parse and apply text filters (e.g. "aaaaa[,bbbb][,ccccc]") struct ImGuiTextBuffer; // Helper to hold and append into a text buffer (~string builder) +struct ImGuiViewport; // Viewport (generally ~1 per window to output to at the OS level. Need per-platform support to use multiple viewports) // Typedefs and Enums/Flags (declared as int for compatibility with old C++, to allow using as flags and to not pollute the top of this file) // Use your programming IDE "Go to definition" facility on the names of the center columns to find the actual flags/enum lists. @@ -105,12 +112,16 @@ typedef int ImGuiColorEditFlags; // -> enum ImGuiColorEditFlags_ // Flags: f typedef int ImGuiColumnsFlags; // -> enum ImGuiColumnsFlags_ // Flags: for Columns(), BeginColumns() 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 *DragDrop*() typedef int ImGuiFocusedFlags; // -> enum ImGuiFocusedFlags_ // Flags: for IsWindowFocused() typedef int ImGuiHoveredFlags; // -> enum ImGuiHoveredFlags_ // Flags: for IsItemHovered(), IsWindowHovered() etc. typedef int ImGuiInputTextFlags; // -> enum ImGuiInputTextFlags_ // Flags: for InputText*() typedef int ImGuiSelectableFlags; // -> enum ImGuiSelectableFlags_ // Flags: for Selectable() +typedef int ImGuiTabBarFlags; // -> enum ImGuiTabBarFlags_ // Flags: for BeginTabBar() +typedef int ImGuiTabItemFlags; // -> enum ImGuiTabItemFlags_ // Flags: for BeginTabItem() typedef int ImGuiTreeNodeFlags; // -> enum ImGuiTreeNodeFlags_ // Flags: for TreeNode*(),CollapsingHeader() +typedef int ImGuiViewportFlags; // -> enum ImGuiViewportFlags_ // Flags: for ImGuiViewport typedef int ImGuiWindowFlags; // -> enum ImGuiWindowFlags_ // Flags: for Begin*() typedef int (*ImGuiInputTextCallback)(ImGuiInputTextCallbackData *data); typedef void (*ImGuiSizeCallback)(ImGuiSizeCallbackData* data); @@ -136,7 +147,8 @@ struct ImVec2 float x, y; ImVec2() { x = y = 0.0f; } ImVec2(float _x, float _y) { x = _x; y = _y; } - float operator[] (size_t i) const { IM_ASSERT(i <= 1); return (&x)[i]; } // We very rarely use this [] operator, the assert overhead is fine. + float operator[] (size_t idx) const { IM_ASSERT(idx <= 1); return (&x)[idx]; } // We very rarely use this [] operator, the assert overhead is fine. + float& operator[] (size_t idx) { IM_ASSERT(idx <= 1); return (&x)[idx]; } // We very rarely use this [] operator, the assert overhead is fine. #ifdef IM_VEC2_CLASS_EXTRA IM_VEC2_CLASS_EXTRA // Define additional constructors and implicit cast operators in imconfig.h to convert back and forth between your math types and ImVec2. #endif @@ -189,14 +201,20 @@ namespace ImGui IMGUI_API void StyleColorsLight(ImGuiStyle* dst = NULL); // best used with borders and a custom, thicker font // Windows - // (Begin = push window to the stack and start appending to it. End = pop window from the stack. You may append multiple times to the same window during the same frame) - // Begin()/BeginChild() return false to indicate the window being collapsed or fully clipped, so you may early out and omit submitting anything to the window. - // You need to always call a matching End()/EndChild() for a Begin()/BeginChild() call, regardless of its return value (this is due to legacy reason and is inconsistent with BeginMenu/EndMenu, BeginPopup/EndPopup and other functions where the End call should only be called if the corresponding Begin function returned true.) - // Passing 'bool* p_open != NULL' shows a close widget in the upper-right corner of the window, which when clicking will set the boolean to false. - // Use child windows to introduce independent scrolling/clipping regions within a host window. Child windows can embed their own child. + // - Begin() = push window to the stack and start appending to it. End() = pop window from the stack. + // - You may append multiple times to the same window during the same frame. + // - Passing 'bool* p_open != NULL' shows a window-closing widget in the upper-right corner of the window, which clicking will set the boolean to false when clicked. + // - Begin() return false to indicate the window is collapsed or fully clipped, so you may early out and omit submitting anything to the window. + // Always call a matching End() for each Begin() call, regardless of its return value [this is due to legacy reason and is inconsistent with most other functions such as BeginMenu/EndMenu, BeginPopup/EndPopup, etc. where the EndXXX call should only be called if the corresponding BeginXXX function returned true.] IMGUI_API bool Begin(const char* name, bool* p_open = NULL, ImGuiWindowFlags flags = 0); IMGUI_API void End(); - IMGUI_API bool BeginChild(const char* str_id, const ImVec2& size = ImVec2(0,0), bool border = false, ImGuiWindowFlags flags = 0); // Begin a scrolling region. size==0.0f: use remaining window size, size<0.0f: use remaining window size minus abs(size). size>0.0f: fixed size. each axis can use a different mode, e.g. ImVec2(0,400). + + // Child Windows + // - Use child windows to begin into a self-contained independent scrolling/clipping regions within a host window. Child windows can embed their own child. + // - For each independent axis of 'size': ==0.0f: use remaining host window size / >0.0f: fixed size / <0.0f: use remaining window size minus abs(size) / Each axis can use a different mode, e.g. ImVec2(0,400). + // - BeginChild() returns false to indicate the window is collapsed or fully clipped, so you may early out and omit submitting anything to the window. + // Always call a matching EndChild() for each BeginChild() call, regardless of its return value [this is due to legacy reason and is inconsistent with most other functions such as BeginMenu/EndMenu, BeginPopup/EndPopup, etc. where the EndXXX call should only be called if the corresponding BeginXXX function returned true.] + IMGUI_API bool BeginChild(const char* str_id, const ImVec2& size = ImVec2(0,0), bool border = false, ImGuiWindowFlags flags = 0); IMGUI_API bool BeginChild(ImGuiID id, const ImVec2& size = ImVec2(0,0), bool border = false, ImGuiWindowFlags flags = 0); IMGUI_API void EndChild(); @@ -205,7 +223,9 @@ namespace ImGui IMGUI_API bool IsWindowCollapsed(); 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 typically: not blocked by a popup/modal)? see flags for options. NB: If you are trying to check whether your mouse should be dispatched to imgui or to your app, you should use the 'io.WantCaptureMouse' boolean for that! Please read the FAQ! - IMGUI_API ImDrawList* GetWindowDrawList(); // get draw list associated to the window, to append your own drawing primitives + 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 ImGuiViewport*GetWindowViewport(); // get viewport currently associated to the current window. IMGUI_API ImVec2 GetWindowPos(); // get current window position in screen space (useful if you want to do your own drawing via the DrawList API) IMGUI_API ImVec2 GetWindowSize(); // get current window size IMGUI_API float GetWindowWidth(); // get current window width (shortcut for GetWindowSize().x) @@ -224,6 +244,7 @@ namespace ImGui IMGUI_API void SetNextWindowCollapsed(bool collapsed, ImGuiCond cond = 0); // set next window collapsed state. call before Begin() IMGUI_API void SetNextWindowFocus(); // set next window to be focused / front-most. call before Begin() IMGUI_API void SetNextWindowBgAlpha(float alpha); // set next window background color alpha. helper to easily modify ImGuiCol_WindowBg/ChildBg/PopupBg. + 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(). @@ -241,7 +262,7 @@ namespace ImGui IMGUI_API float GetScrollMaxY(); // get maximum scrolling amount ~~ ContentSize.Y - WindowSize.Y IMGUI_API void SetScrollX(float scroll_x); // set scrolling amount [0..GetScrollMaxX()] IMGUI_API void SetScrollY(float scroll_y); // set scrolling amount [0..GetScrollMaxY()] - IMGUI_API void SetScrollHere(float center_y_ratio = 0.5f); // adjust scrolling amount to make current cursor position visible. center_y_ratio=0.0: top, 0.5: center, 1.0: bottom. When using to make a "default/current item" visible, consider using SetItemDefaultFocus() instead. + IMGUI_API void SetScrollHereY(float center_y_ratio = 0.5f); // adjust scrolling amount to make current cursor position visible. center_y_ratio=0.0: top, 0.5: center, 1.0: bottom. When using to make a "default/current item" visible, consider using SetItemDefaultFocus() instead. IMGUI_API void SetScrollFromPosY(float pos_y, float center_y_ratio = 0.5f); // adjust scrolling amount to make given position valid. use GetCursorPos() or GetCursorStartPos()+offset to get valid positions. // Parameters stacks (shared) @@ -289,8 +310,8 @@ namespace ImGui IMGUI_API void SetCursorPosX(float x); // " IMGUI_API void SetCursorPosY(float y); // " IMGUI_API ImVec2 GetCursorStartPos(); // initial cursor position - IMGUI_API ImVec2 GetCursorScreenPos(); // cursor position in absolute screen coordinates [0..io.DisplaySize] (useful to work with ImDrawList API) - IMGUI_API void SetCursorScreenPos(const ImVec2& screen_pos); // cursor position in absolute screen coordinates [0..io.DisplaySize] + IMGUI_API ImVec2 GetCursorScreenPos(); // cursor position in screen coordinates [0..io.DisplaySize] (or [io.ViewportPos..io.ViewportPos + io.ViewportSize] when using multiple viewport). useful to work with ImDrawList API. + IMGUI_API void SetCursorScreenPos(const ImVec2& pos); // cursor position in screen coordinates [0..io.DisplaySize] (or [io.ViewportPos..io.ViewportPos + io.ViewportSize] when using multiple viewport) IMGUI_API void AlignTextToFramePadding(); // vertically align upcoming text baseline to FramePadding.y so that it will align properly to regularly framed items (call if you have text on a line before a framed item) IMGUI_API float GetTextLineHeight(); // ~ FontSize IMGUI_API float GetTextLineHeightWithSpacing(); // ~ FontSize + style.ItemSpacing.y (distance in pixels between 2 consecutive lines of text) @@ -493,6 +514,25 @@ namespace ImGui IMGUI_API void SetColumnOffset(int column_index, float offset_x); // set position of column line (in pixels, from the left side of the contents region). pass -1 to use current column IMGUI_API int GetColumnsCount(); + // Tabs + // Note: Tabs are automatically created by the docking system. Use this to create tab bars/tabs yourself without docking being involved. + IMGUI_API bool BeginTabBar(const char* str_id, ImGuiTabBarFlags flags = 0); // create and append into a TabBar + IMGUI_API void EndTabBar(); + IMGUI_API bool BeginTabItem(const char* label, bool* p_open = NULL, ImGuiTabItemFlags = 0);// create a Tab. Returns true if the Tab is selected. + IMGUI_API void EndTabItem(); // only call EndTabItem() if BeginTabItem() returns true! + 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 DO NOT need to call DockSpace() to use most Docking facilities! + // To dock windows: hold SHIFT anywhere while moving windows (if io.ConfigDockingWithShift == true) or drag windows from their title bar (if io.ConfigDockingWithShift = false) + // Use DockSpace() to create an explicit dock node _within_ an existing window. See Docking demo for details. + IMGUI_API void DockSpace(ImGuiID id, const ImVec2& size = ImVec2(0, 0), ImGuiDockNodeFlags flags = 0, const ImGuiDockFamily* dock_family = NULL); + IMGUI_API void SetNextWindowDockId(ImGuiID dock_id, ImGuiCond cond = 0); // set next window dock id (FIXME-DOCK) + IMGUI_API void SetNextWindowDockFamily(const ImGuiDockFamily* dock_family); // set next window user type (docking filters by same user_type) + IMGUI_API ImGuiID GetWindowDockId(); + IMGUI_API bool IsWindowDocked(); // is current window docked into another window? + // Logging/Capture: all text output from interface is captured to tty/file/clipboard. By default, tree nodes are automatically opened during logging. IMGUI_API void LogToTTY(int max_depth = -1); // start logging to tty IMGUI_API void LogToFile(int max_depth = -1, const char* filename = NULL); // start logging to file @@ -515,8 +555,8 @@ namespace ImGui IMGUI_API void PopClipRect(); // Focus, Activation - // (Prefer using "SetItemDefaultFocus()" over "if (IsWindowAppearing()) SetScrollHere()" when applicable, to make your code more forward compatible when navigation branch is merged) - IMGUI_API void SetItemDefaultFocus(); // make last item the default focused item of a window. Please use instead of "if (IsWindowAppearing()) SetScrollHere()" to signify "default item". + // (Prefer using "SetItemDefaultFocus()" over "if (IsWindowAppearing()) SetScrollHereY()" when applicable to signify "this is the default item") + IMGUI_API void SetItemDefaultFocus(); // make last item the default focused item of a window. IMGUI_API void SetKeyboardFocusHere(int offset = 0); // focus keyboard on the next widget. Use positive 'offset' to access sub components of a multiple component widget. Use -1 to access previous widget. // Utilities @@ -540,7 +580,7 @@ namespace ImGui IMGUI_API bool IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max); // test if rectangle (in screen space) is visible / not clipped. to perform coarse clipping on user's side. IMGUI_API double GetTime(); IMGUI_API int GetFrameCount(); - IMGUI_API ImDrawList* GetOverlayDrawList(); // this draw list will be the last rendered one, useful to quickly draw overlays shapes/text + IMGUI_API ImDrawList* GetOverlayDrawList(); // this draw list will be the last rendered. it covers the entire current viewport. useful to quickly draw overlays shapes/text IMGUI_API ImDrawListSharedData* GetDrawListSharedData(); // you may use this when creating your own ImDrawList instances IMGUI_API const char* GetStyleColorName(ImGuiCol idx); IMGUI_API void SetStateStorage(ImGuiStorage* storage); // replace current window storage with our own (if you want to manipulate it yourself, typically clear subsection of it) @@ -598,6 +638,15 @@ namespace ImGui IMGUI_API void* MemAlloc(size_t size); IMGUI_API void MemFree(void* ptr); + // (Optional) Platform/OS interface for multi-viewport support + // Note: You may use GetWindowViewport() to get the current viewport of the current window. + IMGUI_API ImGuiPlatformIO& GetPlatformIO(); // platform/renderer functions, for back-end to setup + viewports list. + IMGUI_API ImGuiViewport* GetMainViewport(); // main viewport. same as GetPlatformIO().MainViewport == GetPlatformIO().Viewports[0]. + 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_arg = NULL, void* renderer_arg = NULL); // call in main loop. will call RenderWindow/SwapBuffers platform functions for each secondary viewport. may be reimplemented by user for custom rendering needs. + IMGUI_API void DestroyPlatformWindows(); // call DestroyWindow platform functions for all viewports. call from back-end Shutdown() if you need to close platform windows before imgui shutdown. otherwise will be called by DestroyContext(). + IMGUI_API ImGuiViewport* FindViewportByPlatformHandle(void* platform_handle); // this is a helper for back-ends. the type platform_handle is decided by the back-end (e.g. HWND, MyWindow*, GLFWwindow* etc.) + } // namespace ImGui // Flags for ImGui::Begin() @@ -623,6 +672,8 @@ enum ImGuiWindowFlags_ ImGuiWindowFlags_NoNavInputs = 1 << 18, // No gamepad/keyboard navigation within the window ImGuiWindowFlags_NoNavFocus = 1 << 19, // No focusing toward this window with gamepad/keyboard navigation (e.g. skipped by CTRL+TAB) ImGuiWindowFlags_NoNav = ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoNavFocus, + ImGuiWindowFlags_UnsavedDocument = 1 << 20, // Append '*' to title without affecting the ID, as a convenience to avoid using the ### operator. When used in a tab/docking context, tab is selected on closure and closure is deferred by one frame to allow code to cancel the closure (with a confirmation popup, etc.) without flicker. + ImGuiWindowFlags_NoDocking = 1 << 21, // Disable docking of this window // [Internal] ImGuiWindowFlags_NavFlattened = 1 << 23, // [BETA] Allow gamepad/keyboard navigation to cross over parent border to this child (only use on child that have no scrolling!) @@ -630,7 +681,8 @@ enum ImGuiWindowFlags_ ImGuiWindowFlags_Tooltip = 1 << 25, // Don't use! For internal use by BeginTooltip() 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_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] //ImGuiWindowFlags_ShowBorders = 1 << 7, // --> Set style.FrameBorderSize=1.0f / style.WindowBorderSize=1.0f to enable borders around windows and items @@ -714,6 +766,39 @@ enum ImGuiComboFlags_ ImGuiComboFlags_HeightMask_ = ImGuiComboFlags_HeightSmall | ImGuiComboFlags_HeightRegular | ImGuiComboFlags_HeightLarge | ImGuiComboFlags_HeightLargest }; +// Flags for ImGui::BeginTabBar() +enum ImGuiTabBarFlags_ +{ + ImGuiTabBarFlags_None = 0, + ImGuiTabBarFlags_Reorderable = 1 << 0, // Allow manually dragging tabs to re-order them + New tabs are appended at the end of list + ImGuiTabBarFlags_AutoSelectNewTabs = 1 << 1, // Automatically select new tabs when they appear + ImGuiTabBarFlags_NoCloseWithMiddleMouseButton = 1 << 2, // Disable behavior of closing tabs (that are submitted with p_open != NULL) with middle mouse button. You can still repro this behavior on user's side with if (IsItemHovered() && IsMouseClicked(2)) *p_open = false. + ImGuiTabBarFlags_NoTabListPopupButton = 1 << 3, + ImGuiTabBarFlags_NoTabListScrollingButtons = 1 << 4, + ImGuiTabBarFlags_FittingPolicyResizeDown = 1 << 5, // Resize tabs when they don't fit + ImGuiTabBarFlags_FittingPolicyScroll = 1 << 6, // Add scroll buttons when tabs don't fit + ImGuiTabBarFlags_FittingPolicyMask_ = ImGuiTabBarFlags_FittingPolicyResizeDown | ImGuiTabBarFlags_FittingPolicyScroll, + ImGuiTabBarFlags_FittingPolicyDefault_ = ImGuiTabBarFlags_FittingPolicyResizeDown +}; + +// Flags for ImGui::BeginTabItem() +enum ImGuiTabItemFlags_ +{ + ImGuiTabItemFlags_None = 0, + ImGuiTabItemFlags_UnsavedDocument = 1 << 0, // Append '*' to title without affecting the ID, as a convenience to avoid using the ### operator. Also: tab is selected on closure and closure is deferred by one frame to allow code to undo it without flicker. + ImGuiTabItemFlags_SetSelected = 1 << 1, // Trigger flag to programatically make the tab selected when calling BeginTabItem() + ImGuiTabItemFlags_NoCloseWithMiddleMouseButton = 1 << 2, // Disable behavior of closing tabs (that are submitted with p_open != NULL) with middle mouse button. You can still repro this behavior on user's side with if (IsItemHovered() && IsMouseClicked(2)) *p_open = false. + ImGuiTabItemFlags_NoPushId = 1 << 3 // Don't call PushID(tab->ID)/PopID() on BeginTabItem()/EndTabItem() +}; + +// Flags for ImGui::DockSpace() +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_NoSplit = 1 << 1 // Disable splitting the node into smaller nodes. Useful e.g. when embedding dockspaces into a main root one (the root one may have splitting disableed to reduce confusion) +}; + // Flags for ImGui::IsWindowFocused() enum ImGuiFocusedFlags_ { @@ -859,6 +944,16 @@ enum ImGuiConfigFlags_ ImGuiConfigFlags_NoMouse = 1 << 4, // Instruct imgui to clear mouse position/buttons in NewFrame(). This allows ignoring the mouse information set by the back-end. ImGuiConfigFlags_NoMouseCursorChange = 1 << 5, // Instruct back-end to not alter mouse cursor shape and visibility. Use if the back-end cursor changes are interfering with yours and you don't want to use SetMouseCursor() to change mouse cursor. You may want to honor requests from imgui by reading GetMouseCursor() yourself instead. + // [BETA] Docking + ImGuiConfigFlags_DockingEnable = 1 << 6, // Docking enable flags. Use SHIFT to dock window into another (or without SHIFT if io.ConfigDockingWithShift = false). + + // [BETA] Viewports + ImGuiConfigFlags_ViewportsEnable = 1 << 10, // Viewport enable flags (require both ImGuiConfigFlags_PlatformHasViewports + ImGuiConfigFlags_RendererHasViewports set by the respective back-ends) + ImGuiConfigFlags_ViewportsNoTaskBarIcons= 1 << 11, // Disable task bars icons for all secondary viewports (will set ImGuiViewportFlags_NoTaskBarIcon on them) + ImGuiConfigFlags_ViewportsNoMerge = 1 << 12, // All floating windows will always create their own viewport and platform window. + ImGuiConfigFlags_DpiEnableScaleViewports= 1 << 13, // FIXME-DPI: Reposition and resize imgui windows when the DpiScale of a viewport changed (mostly useful for the main viewport hosting other window). Note that resizing the main window itself is up to your application. + ImGuiConfigFlags_DpiEnableScaleFonts = 1 << 14, // FIXME-DPI: Request bitmap-scaled fonts to match DpiScale. This is a very low-quality workaround. The correct way to handle DPI is _currently_ to replace the atlas and/or fonts in the Platform_OnChangedViewport callback, but this is all early work in progress. + // User storage (to allow your back-end/engine to communicate to code that may be shared between multiple projects. Those flags are not used by core ImGui) ImGuiConfigFlags_IsSRGB = 1 << 20, // Application is SRGB-aware. ImGuiConfigFlags_IsTouchScreen = 1 << 21 // Application is using a touch screen instead of a mouse. @@ -869,7 +964,12 @@ enum ImGuiBackendFlags_ { ImGuiBackendFlags_HasGamepad = 1 << 0, // Back-end supports gamepad and currently has one connected. ImGuiBackendFlags_HasMouseCursors = 1 << 1, // Back-end supports honoring GetMouseCursor() value to change the OS cursor shape. - ImGuiBackendFlags_HasSetMousePos = 1 << 2 // Back-end supports io.WantSetMousePos requests to reposition the OS mouse position (only used if ImGuiConfigFlags_NavEnableSetMousePos is set). + ImGuiBackendFlags_HasSetMousePos = 1 << 2, // Back-end supports io.WantSetMousePos requests to reposition the OS mouse position (only used if ImGuiConfigFlags_NavEnableSetMousePos is set). + + // [BETA] Viewports + ImGuiBackendFlags_PlatformHasViewports = 1 << 10, // Back-end Platform supports multiple viewports. + ImGuiBackendFlags_HasMouseHoveredViewport=1 << 11, // Back-end Platform supports setting io.MouseHoveredViewport to the viewport directly under the mouse _IGNORING_ viewports with the ImGuiViewportFlags_NoInputs flag and _REGARDLESS_ of whether another viewport is focused and may be capturing the mouse. This information is _NOT EASY_ to provide correctly with most high-level engines! Don't set this without studying how the examples/ back-end handle it! + ImGuiBackendFlags_RendererHasViewports = 1 << 12 // Back-end Renderer supports multiple viewports. }; // Enumeration for PushStyleColor() / PopStyleColor() @@ -908,6 +1008,13 @@ enum ImGuiCol_ ImGuiCol_ResizeGrip, ImGuiCol_ResizeGripHovered, ImGuiCol_ResizeGripActive, + ImGuiCol_Tab, + ImGuiCol_TabHovered, + ImGuiCol_TabActive, + ImGuiCol_TabUnfocused, + ImGuiCol_TabUnfocusedActive, + ImGuiCol_DockingPreview, + ImGuiCol_DockingBg, // Empty node ImGuiCol_PlotLines, ImGuiCol_PlotLinesHovered, ImGuiCol_PlotHistogram, @@ -955,6 +1062,7 @@ enum ImGuiStyleVar_ ImGuiStyleVar_ScrollbarRounding, // float ScrollbarRounding ImGuiStyleVar_GrabMinSize, // float GrabMinSize ImGuiStyleVar_GrabRounding, // float GrabRounding + ImGuiStyleVar_TabRounding, // float TabRounding ImGuiStyleVar_ButtonTextAlign, // ImVec2 ButtonTextAlign ImGuiStyleVar_COUNT @@ -1060,8 +1168,10 @@ struct ImGuiStyle float ScrollbarRounding; // Radius of grab corners for scrollbar. float GrabMinSize; // Minimum width/height of a grab box for slider/scrollbar. float GrabRounding; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs. + float TabRounding; // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs. + float TabBorderSize; // Thickness of border around tabs. ImVec2 ButtonTextAlign; // Alignment of button text when button is larger than text. Defaults to (0.5f,0.5f) for horizontally+vertically centered. - ImVec2 DisplayWindowPadding; // Window position are clamped to be visible within the display area by at least this amount. Only applies to regular windows. + ImVec2 DisplayWindowPadding; // Window position are clamped to be visible within the display area or monitors by at least this amount. Only applies to regular windows. ImVec2 DisplaySafeAreaPadding; // If you cannot see the edges of your screen (e.g. on a TV) increase the safe area padding. Apply to popups/tooltips as well regular windows. NB: Prefer configuring your TV sets correctly! float MouseCursorScale; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. bool AntiAliasedLines; // Enable anti-aliasing on lines/borders. Disable if you are really tight on CPU/GPU. @@ -1083,7 +1193,7 @@ struct ImGuiIO ImGuiConfigFlags ConfigFlags; // = 0 // See ImGuiConfigFlags_ enum. Set by user/application. Gamepad/keyboard navigation options, etc. ImGuiBackendFlags BackendFlags; // = 0 // Set ImGuiBackendFlags_ enum. Set by imgui_impl_xxx files or custom back-end to communicate features supported by the back-end. - ImVec2 DisplaySize; // // Main display size, in pixels. For clamping windows positions. + ImVec2 DisplaySize; // // Main display size, in pixels. Used e.g. to clamp windows positions. This is the default viewport. Use BeginViewport() for other viewports. float DeltaTime; // = 1.0f/60.0f // Time elapsed since last frame, in seconds. float IniSavingRate; // = 5.0f // Minimum time between saving positions/sizes to .ini file, in seconds. const char* IniFilename; // = "imgui.ini" // Path to .ini file. Set NULL to disable automatic .ini loading/saving, if e.g. you want to manually load/save from memory. @@ -1101,14 +1211,13 @@ struct ImGuiIO bool FontAllowUserScaling; // = false // Allow user scaling text of individual window with CTRL+Wheel. ImFont* FontDefault; // = NULL // Font to use on NewFrame(). Use NULL to uses Fonts->Fonts[0]. ImVec2 DisplayFramebufferScale; // = (1.0f,1.0f) // For retina display or other situations where window coordinates are different from framebuffer coordinates. User storage only, presently not used by ImGui. - ImVec2 DisplayVisibleMin; // (0.0f,0.0f) // [obsolete] If you use DisplaySize as a virtual space larger than your screen, set DisplayVisibleMin/Max to the visible area. - ImVec2 DisplayVisibleMax; // (0.0f,0.0f) // [obsolete: just use io.DisplaySize] If the values are the same, we defaults to Min=(0.0f) and Max=DisplaySize // Miscellaneous configuration options bool MouseDrawCursor; // = false // Request ImGui to draw a mouse cursor for you (if you are on a platform without a mouse cursor). Cannot be easily renamed to 'io.ConfigXXX' because this is frequently used by back-end implementations. + bool ConfigDockingWithShift; // = true // Enable docking with holding Shift key (reduce visual noise, allows dropping in wider space) bool ConfigMacOSXBehaviors; // = defined(__APPLE__) // OS X style: Text editing cursor movement using Alt instead of Ctrl, Shortcuts using Cmd/Super instead of Ctrl, Line/Text Start and End using Cmd+Arrows instead of Home/End, Double click selects by word instead of selecting whole text, Multi-selection in lists uses Cmd/Super instead of Ctrl (was called io.OptMacOSXBehaviors prior to 1.63) bool ConfigInputTextCursorBlink; // = true // Set to false to disable blinking cursor, for users who consider it distracting. (was called: io.OptCursorBlink prior to 1.63) - bool ConfigResizeWindowsFromEdges; // = false // [BETA] Enable resizing of windows from their edges and from the lower-left corner. This requires (io.BackendFlags & ImGuiBackendFlags_HasMouseCursors) because it needs mouse cursor feedback. (This used to be the ImGuiWindowFlags_ResizeFromAnySide flag) + bool ConfigResizeWindowsFromEdges; // = true // [BETA] Enable resizing of windows from their edges and from the lower-left corner. This requires (io.BackendFlags & ImGuiBackendFlags_HasMouseCursors) because it needs mouse cursor feedback. (This used to be the ImGuiWindowFlags_ResizeFromAnySide flag) //------------------------------------------------------------------ // Settings (User Functions) @@ -1120,11 +1229,6 @@ struct ImGuiIO void (*SetClipboardTextFn)(void* user_data, const char* text); void* ClipboardUserData; - // Optional: notify OS Input Method Editor of the screen position of your cursor for text input position (e.g. when using Japanese/Chinese IME in Windows) - // (default to use native imm32 api on Windows) - void (*ImeSetInputScreenPosFn)(int x, int y); - void* ImeWindowHandle; // (Windows) Set this to your HWND to get automatic IME cursor positioning. - #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS // [OBSOLETE since 1.60+] Rendering function, will be automatically called in Render(). Please call your rendering function yourself now! // You can obtain the ImDrawData* by calling ImGui::GetDrawData() after Render(). See example applications if you are unsure of how to implement this. @@ -1142,6 +1246,7 @@ struct ImGuiIO bool MouseDown[5]; // Mouse buttons: 0=left, 1=right, 2=middle + extras. ImGui itself mostly only uses left button (BeginPopupContext** are using right button). Others buttons allows us to track if the mouse is being used by your application + available to user as a convenience via IsMouse** API. float MouseWheel; // Mouse wheel Vertical: 1 unit scrolls about 5 lines text. float MouseWheelH; // Mouse wheel Horizontal. Most users don't have a mouse with an horizontal wheel, may not be filled by all back-ends. + ImGuiID MouseHoveredViewport; // (Optional) When using multiple viewports: viewport the OS mouse cursor is hovering _IGNORING_ viewports with the ImGuiViewportFlags_NoInputs flag, and _REGARDLESS_ of whether another viewport is focused. Set io.BackendFlags |= ImGuiBackendFlags_HasMouseHoveredViewport if you can provide this info. If you don't imgui will use a decent heuristic instead. bool KeyCtrl; // Keyboard modifier pressed: Control bool KeyShift; // Keyboard modifier pressed: Shift bool KeyAlt; // Keyboard modifier pressed: Alt @@ -1204,6 +1309,8 @@ struct ImGuiIO #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS namespace ImGui { + // OBSOLETED in 1.66 (from Sep 2018) + static inline void SetScrollHere(float center_ratio=0.5f){ SetScrollHereY(center_ratio); } // OBSOLETED in 1.63 (from Aug 2018) static inline bool IsItemDeactivatedAfterChange() { return IsItemDeactivatedAfterEdit(); } // OBSOLETED in 1.61 (from Apr 2018) @@ -1225,7 +1332,7 @@ namespace ImGui IMGUI_API bool Begin(const char* name, bool* p_open, const ImVec2& size_on_first_use, float bg_alpha_override = -1.0f, ImGuiWindowFlags flags = 0); // Use SetNextWindowSize(size, ImGuiCond_FirstUseEver) + SetNextWindowBgAlpha() instead. static inline bool IsRootWindowOrAnyChildHovered() { return IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows); } static inline void AlignFirstTextHeightToWidgets() { AlignTextToFramePadding(); } - static inline void SetNextWindowPosCenter(ImGuiCond c=0) { ImGuiIO& io = GetIO(); SetNextWindowPos(ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f), c, ImVec2(0.5f, 0.5f)); } + void SetNextWindowPosCenter(ImGuiCond cond); // OBSOLETED in 1.51 (between Jun 2017 and Aug 2017) static inline bool IsItemHoveredRect() { return IsItemHovered(ImGuiHoveredFlags_RectOnly); } static inline bool IsPosHoveringAnyWindow(const ImVec2&) { IM_ASSERT(0); return false; } // This was misleading and partly broken. You probably want to use the ImGui::GetIO().WantCaptureMouse flag instead. @@ -1255,7 +1362,7 @@ class ImVector inline ImVector() { Size = Capacity = 0; Data = NULL; } inline ~ImVector() { if (Data) ImGui::MemFree(Data); } inline ImVector(const ImVector& src) { Size = Capacity = 0; Data = NULL; operator=(src); } - inline ImVector& operator=(const ImVector& src) { clear(); resize(src.Size); memcpy(Data, src.Data, (size_t)Size * sizeof(value_type)); return *this; } + inline ImVector& operator=(const ImVector& src) { clear(); resize(src.Size); memcpy(Data, src.Data, (size_t)Size * sizeof(value_type)); return *this; } inline bool empty() const { return Size == 0; } inline int size() const { return Size; } @@ -1469,6 +1576,16 @@ struct ImGuiSizeCallbackData ImVec2 DesiredSize; // Read-write. Desired size, based on user's mouse position. Write to this field to restrain resizing. }; +// [BETA] For SetNextWindowDockFamily() and DockSpace() function +struct ImGuiDockFamily +{ + ImGuiID ID; // 0 = unaffiliated + bool CompatibleWithFamilyZero; // true = can be docked/merged with an unaffiliated window + + ImGuiDockFamily() { ID = 0; CompatibleWithFamilyZero = true; } + ImGuiDockFamily(ImGuiID id, bool compatible_with_family_zero = true) { ID = id; CompatibleWithFamilyZero = compatible_with_family_zero; } +}; + // Data payload for Drag and Drop operations struct ImGuiPayload { @@ -1636,7 +1753,7 @@ enum ImDrawListFlags_ // This is the low-level list of polygons that ImGui functions are filling. At the end of the frame, all command lists are passed to your ImGuiIO::RenderDrawListFn function for rendering. // Each ImGui window contains its own ImDrawList. You can use ImGui::GetWindowDrawList() to access the current window draw list and draw custom primitives. // You can interleave normal ImGui:: calls and adding primitives to the current draw list. -// All positions are generally in pixel coordinates (top-left at (0,0), bottom-right at io.DisplaySize), but you are totally free to apply whatever transformation matrix to want to the data (if you apply such transformation you'll want to apply it to ClipRect as well) +// All positions are generally in pixel coordinates (generally top-left at 0,0, bottom-right at io.DisplaySize, unless multiple viewports are used), but you are totally free to apply whatever transformation matrix to want to the data (if you apply such transformation you'll want to apply it to ClipRect as well) // Important: Primitives are always added to the list and not culled (culling is done at higher-level by ImGui:: functions), if you use this API a lot consider coarse culling your drawn objects. struct ImDrawList { @@ -1920,7 +2037,7 @@ struct ImFont ImVec2 DisplayOffset; // = (0.f,0.f) // Offset font rendering by xx pixels ImVector Glyphs; // // All glyphs. ImVector IndexAdvanceX; // // Sparse. Glyphs->AdvanceX in a directly indexable way (more cache-friendly, for CalcTextSize functions which are often bottleneck in large UI). - ImVector IndexLookup; // // Sparse. Index glyphs by Unicode code-point. + ImVector IndexLookup; // // Sparse. Index glyphs by Unicode code-point. const ImFontGlyph* FallbackGlyph; // == FindGlyph(FontFallbackChar) float FallbackAdvanceX; // == FallbackGlyph->AdvanceX ImWchar FallbackChar; // = '?' // Replacement glyph if one isn't found. Only set via SetFallbackChar() @@ -1949,7 +2066,7 @@ struct ImFont // 'wrap_width' enable automatic word-wrapping across multiple lines to fit into given width. 0.0f to disable. IMGUI_API ImVec2 CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end = NULL, const char** remaining = NULL) const; // utf8 IMGUI_API const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const; - IMGUI_API void RenderChar(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, unsigned short c) const; + IMGUI_API void RenderChar(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, ImWchar c) const; IMGUI_API void RenderText(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, float wrap_width = 0.0f, bool cpu_fine_clip = false) const; // [Internal] @@ -1962,6 +2079,108 @@ struct ImFont #endif }; +//----------------------------------------------------------------------------- +// [BETA] Platform interface for multi-viewport support +// - completely optional, for advanced users! +// - this is used for back-ends aiming to support the seamless creation of multiple viewport (= multiple Platform/OS windows) +// dear imgui manages the viewports, and the back-end create one Platform/OS windows for each secondary viewport. +// - if you are new to dear imgui and trying to integrate it into your engine, you should probably ignore this for now. +//----------------------------------------------------------------------------- + +// (Optional) Represent the bounds of each connected monitor/display +// Dear ImGui only uses this to clamp the position of popups and tooltips so they don't straddle multiple monitors +struct ImGuiPlatformMonitor +{ + ImVec2 MainPos, MainSize; // Coordinates of the area displayed on this monitor (Min = upper left, Max = bottom right) + ImVec2 WorkPos, WorkSize; // (Optional) Coordinates without task bars / side bars / menu bars. imgui uses this to avoid positioning popups/tooltips inside this region. + float DpiScale; + ImGuiPlatformMonitor() { MainPos = MainSize = WorkPos = WorkSize = ImVec2(0,0); DpiScale = 1.0f; } +}; + +// (Optional) Setup required only if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) is enabled. Access via ImGui::GetPlatformIO(). +// This is designed so we can mix and match two imgui_impl_xxxx files, one for the Platform (~window handling), one for Renderer. +// Custom engine back-ends will often provide both Platform and Renderer interfaces and thus may not need to use all functions. +// Platform functions are typically called before their Renderer counterpart, apart from Destroy which are called the other way. +// RenderPlatformWindowsDefault() basically iterate secondary viewports and call Platform+Renderer's RenderWindow then Platform+Renderer's SwapBuffers, +// You may skip using RenderPlatformWindowsDefault() and call your draw/swap functions yourself if you need specific behavior for your multi-window rendering. +struct ImGuiPlatformIO +{ + //------------------------------------------------------------------ + // Input - Back-end interface/functions + Monitor List + //------------------------------------------------------------------ + + // (Optional) Platform functions (e.g. Win32, GLFW, SDL2) + void (*Platform_CreateWindow)(ImGuiViewport* vp); // Create a new platform window for the given viewport + void (*Platform_DestroyWindow)(ImGuiViewport* vp); + void (*Platform_ShowWindow)(ImGuiViewport* vp); // Newly created windows are initially hidden so SetWindowPos/Size/Title can be called on them first + void (*Platform_SetWindowPos)(ImGuiViewport* vp, ImVec2 pos); + ImVec2 (*Platform_GetWindowPos)(ImGuiViewport* vp); + void (*Platform_SetWindowSize)(ImGuiViewport* vp, ImVec2 size); + ImVec2 (*Platform_GetWindowSize)(ImGuiViewport* vp); + void (*Platform_SetWindowFocus)(ImGuiViewport* vp); // Move window to front and set input focus + bool (*Platform_GetWindowFocus)(ImGuiViewport* vp); + void (*Platform_SetWindowTitle)(ImGuiViewport* vp, const char* title); + void (*Platform_SetWindowAlpha)(ImGuiViewport* vp, float alpha); // (Optional) Setup window transparency + void (*Platform_RenderWindow)(ImGuiViewport* vp, void* render_arg); // (Optional) Setup for render (platform side) + void (*Platform_SwapBuffers)(ImGuiViewport* vp, void* render_arg); // (Optional) Call Present/SwapBuffers (platform side) + float (*Platform_GetWindowDpiScale)(ImGuiViewport* vp); // (Optional) DPI handling: Return DPI scale for this viewport. 1.0f = 96 DPI. (FIXME-DPI) + void (*Platform_OnChangedViewport)(ImGuiViewport* vp); // (Optional) DPI handling: Called during Begin() every time the viewport we are outputting into changes, so back-end has a chance to swap fonts to adjust style. + void (*Platform_SetImeInputPos)(ImGuiViewport* vp, ImVec2 pos); // (Optional) Set IME (Input Method Editor, e.g. for Asian languages) input position, so text preview appears over the imgui input box. + int (*Platform_CreateVkSurface)(ImGuiViewport* vp, ImU64 vk_inst, const void* vk_allocators, ImU64* out_vk_surface); // (Optional) For Renderer to call into Platform code + + // (Optional) Renderer functions (e.g. DirectX, OpenGL3, Vulkan) + void (*Renderer_CreateWindow)(ImGuiViewport* vp); // Create swap chains, frame buffers etc. + void (*Renderer_DestroyWindow)(ImGuiViewport* vp); + void (*Renderer_SetWindowSize)(ImGuiViewport* vp, ImVec2 size); // Resize swap chain, frame buffers etc. + void (*Renderer_RenderWindow)(ImGuiViewport* vp, void* render_arg); // (Optional) Clear targets, Render viewport->DrawData + void (*Renderer_SwapBuffers)(ImGuiViewport* vp, void* render_arg); // (Optional) Call Present/SwapBuffers (renderer side) + + // (Optional) List of monitors (updated by: app/back-end, used by: imgui to clamp popups/tooltips within same monitor and not have them straddle monitors) + ImVector Monitors; + + //------------------------------------------------------------------ + // Output - List of viewports to render into platform windows + //------------------------------------------------------------------ + + // List of viewports (the list is updated by calling ImGui::EndFrame or ImGui::Render) + ImGuiViewport* MainViewport; // Guaranteed to be == Viewports[0] + ImVector Viewports; // Main viewports, followed by all secondary viewports. + + ImGuiPlatformIO() { memset(this, 0, sizeof(*this)); } // Zero clear +}; + +// Flags stored in ImGuiViewport::Flags, giving indications to the platform back-ends +enum ImGuiViewportFlags_ +{ + ImGuiViewportFlags_NoDecoration = 1 << 0, // Platform Window: Disable platform title bar, borders, etc. + ImGuiViewportFlags_NoFocusOnAppearing = 1 << 1, // Platform Window: Don't take focus when created. + ImGuiViewportFlags_NoInputs = 1 << 2, // Platform Window: Make mouse pass through so we can drag this window while peaking behind it. + ImGuiViewportFlags_NoTaskBarIcon = 1 << 3, // Platform Window: Disable platform task bar icon (for popups, menus, or all windows if ImGuiConfigFlags_ViewportsNoTaskBarIcons if set) + ImGuiViewportFlags_NoRendererClear = 1 << 4, // Platform Window: Renderer doesn't need to clear the framebuffer ahead. + ImGuiViewportFlags_TopMost = 1 << 5 // Platform Window: Display on top (for tooltips only) +}; + +// The viewports created and managed by imgui. The role of the platform back-end is to create the platform/OS windows corresponding to each viewport. +struct ImGuiViewport +{ + ImGuiID ID; + ImGuiViewportFlags Flags; + ImVec2 Pos; // Position of viewport both in imgui space and in OS desktop/native space + ImVec2 Size; // Size of viewport in pixel + float DpiScale; // 1.0f = 96 DPI = No extra scale + ImDrawData* DrawData; // The ImDrawData corresponding to this viewport. Valid after Render() and until the next call to NewFrame(). + + void* PlatformUserData; // void* to hold custom data structure for the platform (e.g. windowing info, render context) + void* PlatformHandle; // void* for FindViewportByPlatformHandle(). (e.g. suggested to use natural platform handle such as HWND, GlfwWindow*, SDL_Window*) + bool PlatformRequestClose; // Platform window requested closure + bool PlatformRequestMove; // Platform window requested move (e.g. window was moved by the OS / host window manager) + bool PlatformRequestResize; // Platform window requested resize (e.g. window was resize by the OS / host window manager) + void* RendererUserData; // void* to hold custom data structure for the renderer (e.g. swap chain, frame-buffers etc.) + + ImGuiViewport() { ID = 0; Flags = 0; DpiScale = 0.0f; DrawData = NULL; PlatformUserData = PlatformHandle = NULL; PlatformRequestClose = PlatformRequestMove = PlatformRequestResize = false; RendererUserData = NULL; } + ~ImGuiViewport() { IM_ASSERT(PlatformUserData == NULL && RendererUserData == NULL); } +}; + #if defined(__clang__) #pragma clang diagnostic pop #elif defined(__GNUC__) && __GNUC__ >= 8 diff --git a/Source Code/Libs/imgui/imgui_demo.cpp b/Source Code/Libs/imgui/imgui_demo.cpp index 30cd6cafae..5434cd0f67 100644 --- a/Source Code/Libs/imgui/imgui_demo.cpp +++ b/Source Code/Libs/imgui/imgui_demo.cpp @@ -1,6 +1,6 @@ #include "stdafx.h" -// dear imgui, v1.65 +// dear imgui, v1.66 WIP // (demo code) // Message to the person tempted to delete this file when integrating ImGui into their code base: @@ -38,6 +38,8 @@ Index of this file: // [SECTION] Example App: Simple Overlay / ShowExampleAppSimpleOverlay() // [SECTION] Example App: Manipulating Window Titles / ShowExampleAppWindowTitles() // [SECTION] Example App: Custom Rendering using ImDrawList API / ShowExampleAppCustomRendering() +// [SECTION] Example App: Docking, DockSpace / ShowExampleAppDockSpace() +// [SECTION] Example App: Documents Handling / ShowExampleAppDocuments() */ @@ -100,6 +102,8 @@ Index of this file: #if !defined(IMGUI_DISABLE_DEMO_WINDOWS) // Forward Declarations +static void ShowExampleAppDockSpace(bool* p_open); +static void ShowExampleAppDocuments(bool* p_open); static void ShowExampleAppMainMenuBar(); static void ShowExampleAppConsole(bool* p_open); static void ShowExampleAppLog(bool* p_open); @@ -127,6 +131,16 @@ static void ShowHelpMarker(const char* desc) } } +static void ShowDockingDisabledMessage() +{ + ImGuiIO& io = ImGui::GetIO(); + ImGui::Text("ERROR: Docking is not enabled! See Demo > Configuration."); + ImGui::Text("Set io.ConfigFlags |= ImGuiConfigFlags_DockingEnable in your code, or "); + ImGui::SameLine(0.0f, 0.0f); + if (ImGui::SmallButton("click here")) + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; +} + // Helper to display basic user controls. void ImGui::ShowUserGuide() { @@ -159,6 +173,8 @@ void ImGui::ShowUserGuide() void ImGui::ShowDemoWindow(bool* p_open) { // Examples Apps (accessible from the "Examples" menu) + static bool show_app_dockspace = false; + static bool show_app_documents = false; static bool show_app_main_menu_bar = false; static bool show_app_console = false; static bool show_app_log = false; @@ -171,6 +187,8 @@ void ImGui::ShowDemoWindow(bool* p_open) static bool show_app_window_titles = false; static bool show_app_custom_rendering = false; + if (show_app_dockspace) ShowExampleAppDockSpace(&show_app_dockspace); // Process the Docking app first, as explicit DockSpace() nodes needs to be submitted early (read comments near the DockSpace function) + if (show_app_documents) ShowExampleAppDocuments(&show_app_documents); // Process the Document app next, as it may also use a DockSpace() if (show_app_main_menu_bar) ShowExampleAppMainMenuBar(); if (show_app_console) ShowExampleAppConsole(&show_app_console); if (show_app_log) ShowExampleAppLog(&show_app_log); @@ -209,6 +227,8 @@ void ImGui::ShowDemoWindow(bool* p_open) static bool no_collapse = false; static bool no_close = false; static bool no_nav = false; + static bool no_bring_to_front = false; + static bool no_docking = false; ImGuiWindowFlags window_flags = 0; if (no_titlebar) window_flags |= ImGuiWindowFlags_NoTitleBar; @@ -218,10 +238,13 @@ void ImGui::ShowDemoWindow(bool* p_open) if (no_resize) window_flags |= ImGuiWindowFlags_NoResize; if (no_collapse) window_flags |= ImGuiWindowFlags_NoCollapse; if (no_nav) window_flags |= ImGuiWindowFlags_NoNav; + if (no_bring_to_front) window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus; + if (no_docking) window_flags |= ImGuiWindowFlags_NoDocking; if (no_close) p_open = NULL; // Don't pass our bool* to Begin // We specify a default position/size in case there's no data in the .ini file. Typically this isn't required! We only do it to make the Demo applications a little more welcoming. - ImGui::SetNextWindowPos(ImVec2(650, 20), ImGuiCond_FirstUseEver); + ImVec2 main_viewport_pos = ImGui::GetMainViewport()->Pos; + ImGui::SetNextWindowPos(ImVec2(main_viewport_pos.x + 650, main_viewport_pos.y + 20), ImGuiCond_FirstUseEver); ImGui::SetNextWindowSize(ImVec2(550, 680), ImGuiCond_FirstUseEver); // Main body of the Demo window starts here. @@ -258,6 +281,8 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::MenuItem("Simple overlay", NULL, &show_app_simple_overlay); ImGui::MenuItem("Manipulating window titles", NULL, &show_app_window_titles); ImGui::MenuItem("Custom rendering", NULL, &show_app_custom_rendering); + ImGui::MenuItem("Dockspace", NULL, &show_app_dockspace); + ImGui::MenuItem("Documents", NULL, &show_app_documents); ImGui::EndMenu(); } if (ImGui::BeginMenu("Help")) @@ -309,6 +334,19 @@ void ImGui::ShowDemoWindow(bool* p_open) } ImGui::CheckboxFlags("io.ConfigFlags: NoMouseCursorChange", (unsigned int *)&io.ConfigFlags, ImGuiConfigFlags_NoMouseCursorChange); ImGui::SameLine(); ShowHelpMarker("Instruct back-end to not alter mouse cursor shape and visibility."); + + ImGui::CheckboxFlags("io.ConfigFlags: DockingEnable", (unsigned int *)&io.ConfigFlags, ImGuiConfigFlags_DockingEnable); + ImGui::SameLine(); ShowHelpMarker("Use SHIFT to dock window into another (or without SHIFT if io.ConfigDockingWithShift == false)"); + + ImGui::CheckboxFlags("io.ConfigFlags: ViewportsEnable", (unsigned int *)&io.ConfigFlags, ImGuiConfigFlags_ViewportsEnable); + ImGui::SameLine(); ShowHelpMarker("Toggling this at runtime is normally unsupported (it will offset your windows)."); + ImGui::CheckboxFlags("io.ConfigFlags: ViewportsNoTaskBarIcons", (unsigned int *)&io.ConfigFlags, ImGuiConfigFlags_ViewportsNoTaskBarIcons); + ImGui::SameLine(); ShowHelpMarker("Toggling this at runtime is normally unsupported (most platform back-ends won't refresh the task bar icon state right away)."); + ImGui::CheckboxFlags("io.ConfigFlags: ViewportsNoMerge", (unsigned int *)&io.ConfigFlags, ImGuiConfigFlags_ViewportsNoMerge); + ImGui::SameLine(); ShowHelpMarker("All floating windows will always create their own viewport and platform window."); + + ImGui::Checkbox("io.ConfigDockingWithShift", &io.ConfigDockingWithShift); + ImGui::SameLine(); ShowHelpMarker("Enable docking when holding Shift only (allows to drop in wider space, reduce visual noise)"); ImGui::Checkbox("io.ConfigInputTextCursorBlink", &io.ConfigInputTextCursorBlink); ImGui::SameLine(); ShowHelpMarker("Set to false to disable blinking cursor, for users who consider it distracting"); ImGui::Checkbox("io.ConfigResizeWindowsFromEdges [beta]", &io.ConfigResizeWindowsFromEdges); @@ -325,6 +363,9 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::CheckboxFlags("io.BackendFlags: HasGamepad", (unsigned int *)&backend_flags, ImGuiBackendFlags_HasGamepad); ImGui::CheckboxFlags("io.BackendFlags: HasMouseCursors", (unsigned int *)&backend_flags, ImGuiBackendFlags_HasMouseCursors); ImGui::CheckboxFlags("io.BackendFlags: HasSetMousePos", (unsigned int *)&backend_flags, ImGuiBackendFlags_HasSetMousePos); + ImGui::CheckboxFlags("io.BackendFlags: PlatformHasViewports", (unsigned int *)&backend_flags, ImGuiBackendFlags_PlatformHasViewports); + ImGui::CheckboxFlags("io.BackendFlags: HasMouseHoveredViewport", (unsigned int *)&backend_flags, ImGuiBackendFlags_HasMouseHoveredViewport); + ImGui::CheckboxFlags("io.BackendFlags: RendererHasViewports", (unsigned int *)&backend_flags, ImGuiBackendFlags_RendererHasViewports); ImGui::TreePop(); ImGui::Separator(); } @@ -361,7 +402,9 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::Checkbox("No resize", &no_resize); ImGui::SameLine(300); ImGui::Checkbox("No collapse", &no_collapse); ImGui::Checkbox("No close", &no_close); ImGui::SameLine(150); - ImGui::Checkbox("No nav", &no_nav); + ImGui::Checkbox("No nav", &no_nav); ImGui::SameLine(300); + ImGui::Checkbox("No bring to front", &no_bring_to_front); + ImGui::Checkbox("No docking", &no_docking); } if (ImGui::CollapsingHeader("Widgets")) @@ -388,7 +431,8 @@ void ImGui::ShowDemoWindow(bool* p_open) // Color buttons, demonstrate using PushID() to add unique identifier in the ID stack, and changing style. for (int i = 0; i < 7; i++) { - if (i > 0) ImGui::SameLine(); + if (i > 0) + ImGui::SameLine(); ImGui::PushID(i); ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(i/7.0f, 0.6f, 0.6f)); ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(i/7.0f, 0.7f, 0.7f)); @@ -398,7 +442,12 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::PopID(); } - // Arrow buttons + // Use AlignTextToFramePadding() to align text baseline to the baseline of framed elements (otherwise a Text+SameLine+Button sequence will have the text a little too high by default) + ImGui::AlignTextToFramePadding(); + ImGui::Text("Hold to repeat:"); + ImGui::SameLine(); + + // Arrow buttons with Repeater static int counter = 0; float spacing = ImGui::GetStyle().ItemInnerSpacing.x; ImGui::PushButtonRepeat(true); @@ -1460,6 +1509,7 @@ void ImGui::ShowDemoWindow(bool* p_open) // Calling IsItemHovered() after begin returns the hovered status of the title bar. // This is useful in particular if you want to create a context menu (with BeginPopupContextItem) associated to the title bar of a window. + // This will also work when docked into a Tab (the Tab replace the Title Bar and guarantee the same properties). static bool test_window = false; ImGui::Checkbox("Hovered/Active tests after Begin() for title bar testing", &test_window); if (test_window) @@ -1470,6 +1520,7 @@ void ImGui::ShowDemoWindow(bool* p_open) if (ImGui::MenuItem("Close")) { test_window = false; } ImGui::EndPopup(); } + //if (IsItemHovered() || IsItemActive()) { printf("[%05d] Hovered %d Active %d\n", GetFrameCount(), IsItemHovered(), IsItemActive()); } // [DEBUG] ImGui::Text( "IsItemHovered() after begin = %d (== is title bar hovered)\n" "IsItemActive() after begin = %d (== is window being clicked/moved)\n", @@ -1504,10 +1555,10 @@ void ImGui::ShowDemoWindow(bool* p_open) { ImGui::Text("%04d: scrollable region", i); if (goto_line && line == i) - ImGui::SetScrollHere(); + ImGui::SetScrollHereY(); } if (goto_line && line >= 100) - ImGui::SetScrollHere(); + ImGui::SetScrollHereY(); ImGui::EndChild(); } @@ -1665,6 +1716,72 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::TreePop(); } + if (ImGui::TreeNode("Tabs")) + { + if (ImGui::TreeNode("Basic")) + { + ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_None; + ImGui::BeginTabBar("MyTabBar", tab_bar_flags); + if (ImGui::BeginTabItem("Avocado")) + { + ImGui::Text("This is the Avocado tab!\nblah blah blah blah blah"); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Broccoli")) + { + ImGui::Text("This is the Broccoli tab!\nblah blah blah blah blah"); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Cucumber")) + { + ImGui::Text("This is the Cucumber tab!\nblah blah blah blah blah"); + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); + ImGui::Separator(); + ImGui::TreePop(); + } + + if (ImGui::TreeNode("Advanced & Close Button")) + { + // Expose a couple of the available flags. In most cases you may just call BeginTabBar() with no flags (0). + static ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_Reorderable; + ImGui::CheckboxFlags("ImGuiTabBarFlags_Reorderable", (unsigned int*)&tab_bar_flags, ImGuiTabBarFlags_Reorderable); + ImGui::CheckboxFlags("ImGuiTabBarFlags_AutoSelectNewTabs", (unsigned int*)&tab_bar_flags, ImGuiTabBarFlags_AutoSelectNewTabs); + ImGui::CheckboxFlags("ImGuiTabBarFlags_NoCloseWithMiddleMouseButton", (unsigned int*)&tab_bar_flags, ImGuiTabBarFlags_NoCloseWithMiddleMouseButton); + if ((tab_bar_flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0) + tab_bar_flags |= ImGuiTabBarFlags_FittingPolicyDefault_; + if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyResizeDown", (unsigned int*)&tab_bar_flags, ImGuiTabBarFlags_FittingPolicyResizeDown)) + tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyResizeDown); + if (ImGui::CheckboxFlags("ImGuiTabBarFlags_FittingPolicyScroll", (unsigned int*)&tab_bar_flags, ImGuiTabBarFlags_FittingPolicyScroll)) + tab_bar_flags &= ~(ImGuiTabBarFlags_FittingPolicyMask_ ^ ImGuiTabBarFlags_FittingPolicyScroll); + + // Tab Bar + const char* names[4] = { "Artichoke", "Beetroot", "Celery", "Daikon" }; + static bool opened[4] = { true, true, true, true }; // Persistent user state + for (int n = 0; n < IM_ARRAYSIZE(opened); n++) + { + if (n > 0) { ImGui::SameLine(); } + ImGui::Checkbox(names[n], &opened[n]); + } + + // Passing a bool* to BeginTabItem() is similar to passing one to Begin(): the underlying bool will be set to false when the tab is closed. + ImGui::BeginTabBar("MyTabBar", tab_bar_flags); + for (int n = 0; n < IM_ARRAYSIZE(opened); n++) + if (opened[n] && ImGui::BeginTabItem(names[n], &opened[n])) + { + ImGui::Text("This is the %s tab!", names[n]); + if (n & 1) + ImGui::Text("I am an odd tab."); + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); + ImGui::Separator(); + ImGui::TreePop(); + } + ImGui::TreePop(); + } + if (ImGui::TreeNode("Groups")) { ImGui::TextWrapped("(Using ImGui::BeginGroup()/EndGroup() to layout items. BeginGroup() basically locks the horizontal position. EndGroup() bundles the whole group so that you can use functions such as IsItemHovered() on it.)"); @@ -1766,7 +1883,7 @@ void ImGui::ShowDemoWindow(bool* p_open) if (ImGui::TreeNode("Scrolling")) { - ImGui::TextWrapped("(Use SetScrollHere() or SetScrollFromPosY() to scroll to a given position.)"); + ImGui::TextWrapped("(Use SetScrollHereY() or SetScrollFromPosY() to scroll to a given position.)"); static bool track = true; static int track_line = 50, scroll_to_px = 200; ImGui::Checkbox("Track", &track); @@ -1790,7 +1907,7 @@ void ImGui::ShowDemoWindow(bool* p_open) if (track && line == track_line) { ImGui::TextColored(ImColor(255,255,0), "Line %d", line); - ImGui::SetScrollHere(i * 0.25f); // 0.0f:top, 0.5f:center, 1.0f:bottom + ImGui::SetScrollHereY(i * 0.25f); // 0.0f:top, 0.5f:center, 1.0f:bottom } else { @@ -2476,8 +2593,8 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) ImGui::Checkbox("Anti-aliased lines", &style.AntiAliasedLines); ImGui::SameLine(); ShowHelpMarker("When disabling anti-aliasing lines, you'll probably want to disable borders in your style as well."); ImGui::Checkbox("Anti-aliased fill", &style.AntiAliasedFill); ImGui::PushItemWidth(100); - ImGui::DragFloat("Curve Tessellation Tolerance", &style.CurveTessellationTol, 0.02f, 0.10f, FLT_MAX, NULL, 2.0f); - if (style.CurveTessellationTol < 0.0f) style.CurveTessellationTol = 0.10f; + ImGui::DragFloat("Curve Tessellation Tolerance", &style.CurveTessellationTol, 0.02f, 0.10f, FLT_MAX, "%.2f", 2.0f); + if (style.CurveTessellationTol < 0.10f) style.CurveTessellationTol = 0.10f; ImGui::DragFloat("Global Alpha", &style.Alpha, 0.005f, 0.20f, 1.0f, "%.2f"); // Not exposing zero here so user doesn't "lose" the UI (zero alpha clips all widgets). But application code could have a toggle to switch between zero and non-zero. ImGui::PopItemWidth(); ImGui::TreePop(); @@ -2499,12 +2616,14 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) ImGui::SliderFloat("ChildBorderSize", &style.ChildBorderSize, 0.0f, 1.0f, "%.0f"); ImGui::SliderFloat("PopupBorderSize", &style.PopupBorderSize, 0.0f, 1.0f, "%.0f"); ImGui::SliderFloat("FrameBorderSize", &style.FrameBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui::SliderFloat("TabBorderSize", &style.TabBorderSize, 0.0f, 1.0f, "%.0f"); ImGui::Text("Rounding"); - ImGui::SliderFloat("WindowRounding", &style.WindowRounding, 0.0f, 14.0f, "%.0f"); - ImGui::SliderFloat("ChildRounding", &style.ChildRounding, 0.0f, 16.0f, "%.0f"); + ImGui::SliderFloat("WindowRounding", &style.WindowRounding, 0.0f, 12.0f, "%.0f"); + ImGui::SliderFloat("ChildRounding", &style.ChildRounding, 0.0f, 12.0f, "%.0f"); ImGui::SliderFloat("FrameRounding", &style.FrameRounding, 0.0f, 12.0f, "%.0f"); ImGui::SliderFloat("ScrollbarRounding", &style.ScrollbarRounding, 0.0f, 12.0f, "%.0f"); ImGui::SliderFloat("GrabRounding", &style.GrabRounding, 0.0f, 12.0f, "%.0f"); + ImGui::SliderFloat("TabRounding", &style.TabRounding, 0.0f, 12.0f, "%.0f"); ImGui::Text("Alignment"); ImGui::SliderFloat2("WindowTitleAlign", (float*)&style.WindowTitleAlign, 0.0f, 1.0f, "%.2f"); ImGui::SliderFloat2("ButtonTextAlign", (float*)&style.ButtonTextAlign, 0.0f, 1.0f, "%.2f"); ImGui::SameLine(); ShowHelpMarker("Alignment applies when a button is larger than its text content."); @@ -2885,7 +3004,7 @@ struct ExampleAppConsole if (copy_to_clipboard) ImGui::LogFinish(); if (ScrollToBottom) - ImGui::SetScrollHere(1.0f); + ImGui::SetScrollHereY(1.0f); ScrollToBottom = false; ImGui::PopStyleVar(); ImGui::EndChild(); @@ -3127,7 +3246,7 @@ struct ExampleAppLog } if (ScrollToBottom) - ImGui::SetScrollHere(1.0f); + ImGui::SetScrollHereY(1.0f); ScrollToBottom = false; ImGui::EndChild(); ImGui::End(); @@ -3389,6 +3508,8 @@ static void ShowExampleAppConstrainedResize(bool* p_open) "Custom: Always Square", "Custom: Fixed Steps (100)", }; + if (ImGui::IsWindowDocked()) + ImGui::Text("Warning: Sizing Constraints won't work if the window is docked!"); if (ImGui::Button("200x200")) { ImGui::SetWindowSize(ImVec2(200, 200)); } ImGui::SameLine(); if (ImGui::Button("500x500")) { ImGui::SetWindowSize(ImVec2(500, 500)); } ImGui::SameLine(); if (ImGui::Button("800x200")) { ImGui::SetWindowSize(ImVec2(800, 200)); } @@ -3410,14 +3531,19 @@ static void ShowExampleAppConstrainedResize(bool* p_open) // Demonstrate creating a simple static window with no decoration + a context-menu to choose which corner of the screen to use. static void ShowExampleAppSimpleOverlay(bool* p_open) { + // FIXME-VIEWPORT-ABS: Select a default viewport const float DISTANCE = 10.0f; static int corner = 0; - ImVec2 window_pos = ImVec2((corner & 1) ? ImGui::GetIO().DisplaySize.x - DISTANCE : DISTANCE, (corner & 2) ? ImGui::GetIO().DisplaySize.y - DISTANCE : DISTANCE); - ImVec2 window_pos_pivot = ImVec2((corner & 1) ? 1.0f : 0.0f, (corner & 2) ? 1.0f : 0.0f); if (corner != -1) + { + ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImVec2 window_pos = ImVec2((corner & 1) ? (viewport->Pos.x + viewport->Size.x - DISTANCE) : (viewport->Pos.x + DISTANCE), (corner & 2) ? (viewport->Pos.y + viewport->Size.y - DISTANCE) : (viewport->Pos.y + DISTANCE)); + ImVec2 window_pos_pivot = ImVec2((corner & 1) ? 1.0f : 0.0f, (corner & 2) ? 1.0f : 0.0f); ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot); + ImGui::SetNextWindowViewport(viewport->ID); + } ImGui::SetNextWindowBgAlpha(0.3f); // Transparent background - if (ImGui::Begin("Example: Simple Overlay", p_open, (corner != -1 ? ImGuiWindowFlags_NoMove : 0) | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav)) + if (ImGui::Begin("Example: Simple Overlay", p_open, (corner != -1 ? ImGuiWindowFlags_NoMove : 0) | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav)) { ImGui::Text("Simple overlay\n" "in the corner of the screen.\n" "(right-click to change position)"); ImGui::Separator(); @@ -3581,6 +3707,403 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImGui::End(); } +//----------------------------------------------------------------------------- +// [SECTION] Example App: Docking, DockSpace / ShowExampleAppDockSpace() +//----------------------------------------------------------------------------- + +// Demonstrate using DockSpace() to create an explicit docking node within an existing window. +// Note that you already dock windows into each others _without_ a DockSpace() by just holding SHIFT when moving a window. +// DockSpace() is only useful to construct to a central location for your application. +void ShowExampleAppDockSpace(bool* p_open) +{ + static bool opt_fullscreen_persistant = true; + bool opt_fullscreen = opt_fullscreen_persistant; + + // We are using the ImGuiWindowFlags_NoDocking flag to make the parent window not dockable into. + // Because 1) it would be confusing to have two docking targets within each others. + // and 2) we want our main DockSpace node to always be visible (never hidden within a tab bar): if the DockSpace node disappear its child windows will be orphaned. + ImGuiWindowFlags flags = ImGuiWindowFlags_MenuBar; + flags |= ImGuiWindowFlags_NoDocking; + if (opt_fullscreen) + { + ImGuiViewport* viewport = ImGui::GetMainViewport(); + ImGui::SetNextWindowPos(viewport->Pos); + ImGui::SetNextWindowSize(viewport->Size); + ImGui::SetNextWindowViewport(viewport->ID); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove; + flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus; + } + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + ImGui::Begin("DockSpace Demo", p_open, flags); + ImGui::PopStyleVar(); + + if (ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("Docking")) + { + if (ImGui::MenuItem("Remove DockSpace", NULL, false, p_open != NULL)) + *p_open = false; + ImGui::EndMenu(); + } + // Disabling fullscreen would allow the window to be moved to the front of other windows, + // which we can't undo at the moment without finer window depth/z control. + /*if (ImGui::BeginMenu("Options")) + { + ImGui::MenuItem("Fullscreen", NULL, &opt_fullscreen_persistant); + ImGui::EndMenu(); + }*/ + ShowHelpMarker( + "You can _always_ dock _any_ window into another by holding the SHIFT key while moving a window. Try it now!" "\n" + "This demo app has nothing to do with it!" "\n\n" + "This demo app only demonstrate the use of ImGui::DockSpace() which allows you to manually create a docking node _within_ another window. This is useful so you can decorate your main application window (e.g. with a menu bar)." "\n\n" + "ImGui::DockSpace() comes with one hard constraint: it needs to be submitted _before_ any window which may be docked into it. Therefore, if you use a dock spot as the central point of your application, you'll probably want it to be part of the very first window you are submitting to imgui every frame." "\n\n" + "(NB: because of this constraint, the implicit \"Debug\" window can not be docked into an explicit DockSpace() node, because that window is submitted as part of the NewFrame() call. An easy workaround is that you can create your own implicit \"Debug##2\" window after calling DockSpace() and leave it in the window stack for anyone to use.)" + ); + + ImGui::EndMenuBar(); + } + + ImGuiIO& io = ImGui::GetIO(); + if (io.ConfigFlags & ImGuiConfigFlags_DockingEnable) + { + //ImGui::PushStyleColor(ImGuiCol_DockingBg, ImVec4(0.2f, 0.2f, 0.2f, 1.0f)); + ImGuiID dockspace_id = ImGui::GetID("MyDockspace"); + ImGui::DockSpace(dockspace_id); + //ImGui::PopStyleColor(); + } + else + { + ShowDockingDisabledMessage(); + } + + ImGui::End(); + if (opt_fullscreen) + ImGui::PopStyleVar(); +} + +//----------------------------------------------------------------------------- +// [SECTION] Example App: Documents Handling / ShowExampleAppDocuments() +//----------------------------------------------------------------------------- + +// Simplified structure to mimic a Document model +struct MyDocument +{ + const char* Name; // Document title + bool Open; // Set when the document is open (in this demo, we keep an array of all available documents to simplify the demo) + bool OpenPrev; // Copy of Open from last update. + bool Dirty; // Set when the document has been modified + bool WantClose; // Set when the document + ImVec4 Color; // An arbitrary variable associated to the document + + MyDocument(const char* name, bool open = true, const ImVec4& color = ImVec4(1.0f,1.0f,1.0f,1.0f)) + { + Name = name; + Open = OpenPrev = open; + Dirty = false; + WantClose = false; + Color = color; + } + void DoOpen() { Open = true; } + void DoQueueClose() { WantClose = true; } + void DoForceClose() { Open = false; Dirty = false; } + void DoSave() { Dirty = false; } + + // Display dummy contents for the Document + static void DisplayContents(MyDocument* doc) + { + ImGui::PushID(doc); + ImGui::Text("Document \"%s\"", doc->Name); + ImGui::PushStyleColor(ImGuiCol_Text, doc->Color); + ImGui::TextWrapped("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."); + ImGui::PopStyleColor(); + if (ImGui::Button("Modify", ImVec2(100, 0))) + doc->Dirty = true; + ImGui::SameLine(); + if (ImGui::Button("Save", ImVec2(100, 0))) + doc->DoSave(); + ImGui::ColorEdit3("color", &doc->Color.x); // Useful to test drag and drop and hold-dragged-to-open-tab behavior. + ImGui::PopID(); + } + + // Display context menu for the Document + static void DisplayContextMenu(MyDocument* doc) + { + if (!ImGui::BeginPopupContextItem()) + return; + + char buf[256]; + sprintf(buf, "Save %s", doc->Name); + if (ImGui::MenuItem(buf, "CTRL+S", false, doc->Open)) + doc->DoSave(); + if (ImGui::MenuItem("Close", "CTRL+W", false, doc->Open)) + doc->DoQueueClose(); + ImGui::EndPopup(); + } +}; + +struct ExampleAppDocuments +{ + ImVector Documents; + + ExampleAppDocuments() + { + Documents.push_back(MyDocument("Lettuce", true, ImVec4(0.4f, 0.8f, 0.4f, 1.0f))); + Documents.push_back(MyDocument("Eggplant", true, ImVec4(0.8f, 0.5f, 1.0f, 1.0f))); + Documents.push_back(MyDocument("Carrot", true, ImVec4(1.0f, 0.8f, 0.5f, 1.0f))); + Documents.push_back(MyDocument("Tomato", false, ImVec4(1.0f, 0.3f, 0.4f, 1.0f))); + Documents.push_back(MyDocument("A Rather Long Title", false)); + Documents.push_back(MyDocument("Some Document", false)); + } +}; + +// [Optional] Notify the system of Tabs/Windows of closure that happened outside the regular tab interface +// If a tab has been closed programmatically (aka closed from another source such as the Checkbox() in the demo, as opposed +// to clicking on the regular tab closing button) and stops being submitted, it will take a frame for the tab bar to notice its absence. +// During this frame there will be a gap in the tab bar, and if the tab that has disappeared was the selected one, the tab bar +// will report no selected tab during the frame. This will effectively give the impression of a flicker for one frame. +// We call SetTabItemClosed() to manually notify the Tab Bar or Docking system of removed tabs to avoid this glitch. +// Note that this completely optional, and only affect tab bars with the ImGuiTabBarFlags_Reorderable flag. +static void NotifyOfDocumentsClosedElsewhere(ExampleAppDocuments& app) +{ + for (int doc_n = 0; doc_n < app.Documents.Size; doc_n++) + { + MyDocument* doc = &app.Documents[doc_n]; + if (!doc->Open && doc->OpenPrev) + ImGui::SetTabItemClosed(doc->Name); + doc->OpenPrev = doc->Open; + } +} + +void ShowExampleAppDocuments(bool* p_open) +{ + static ExampleAppDocuments app; + + ImGui::Begin("Examples: Documents", p_open, ImGuiWindowFlags_MenuBar); + + // Options + enum Target + { + Target_None, + Target_Tab, // Create document as a local tab into a local tab bar + Target_Window // Create document as a regular window + }; + static Target opt_target = Target_Tab; + static bool opt_reorderable = true; + static ImGuiTabBarFlags opt_fitting_flags = ImGuiTabBarFlags_FittingPolicyDefault_; + + // Menu + if (ImGui::BeginMenuBar()) + { + if (ImGui::BeginMenu("File")) + { + int open_count = 0; + for (int doc_n = 0; doc_n < app.Documents.Size; doc_n++) + open_count += app.Documents[doc_n].Open ? 1 : 0; + + if (ImGui::BeginMenu("Open", open_count < app.Documents.Size)) + { + for (int doc_n = 0; doc_n < app.Documents.Size; doc_n++) + { + MyDocument* doc = &app.Documents[doc_n]; + if (!doc->Open) + if (ImGui::MenuItem(doc->Name)) + doc->DoOpen(); + } + ImGui::EndMenu(); + } + if (ImGui::MenuItem("Close All Documents", NULL, false, open_count > 0)) + for (int doc_n = 0; doc_n < app.Documents.Size; doc_n++) + app.Documents[doc_n].DoQueueClose(); + if (ImGui::MenuItem("Exit", "Alt+F4")) {} + ImGui::EndMenu(); + } + ImGui::EndMenuBar(); + } + + // [Debug] List documents with one checkbox for each + for (int doc_n = 0; doc_n < app.Documents.Size; doc_n++) + { + MyDocument* doc = &app.Documents[doc_n]; + if (doc_n > 0) + ImGui::SameLine(); + ImGui::PushID(doc); + if (ImGui::Checkbox(doc->Name, &doc->Open)) + if (!doc->Open) + doc->DoForceClose(); + ImGui::PopID(); + } + ImGui::PushItemWidth(ImGui::GetFontSize() * 12); + ImGui::Combo("Output", (int*)&opt_target, "None\0TabBar+Tabs\0DockSpace+Window\0"); + ImGui::PopItemWidth(); + bool redock_all = false; + if (opt_target == Target_Tab) { ImGui::SameLine(); ImGui::Checkbox("Reorderable Tabs", &opt_reorderable); } + if (opt_target == Target_Window) { ImGui::SameLine(); redock_all = ImGui::Button("Redock all"); } + + ImGui::Separator(); + + // Tabs + if (opt_target == Target_Tab) + { + ImGuiTabBarFlags tab_bar_flags = (opt_fitting_flags) | (opt_reorderable ? ImGuiTabBarFlags_Reorderable : 0); + ImGui::BeginTabBar("##tabs", tab_bar_flags); + + if (opt_reorderable) + NotifyOfDocumentsClosedElsewhere(app); + + // [DEBUG] Stress tests + //if ((ImGui::GetFrameCount() % 30) == 0) docs[1].Open ^= 1; // [DEBUG] Automatically show/hide a tab. Test various interactions e.g. dragging with this on. + //if (ImGui::GetIO().KeyCtrl) ImGui::SetTabItemSelected(docs[1].Name); // [DEBUG] Test SetTabItemSelected(), probably not very useful as-is anyway.. + + // Submit Tabs + for (int doc_n = 0; doc_n < app.Documents.Size; doc_n++) + { + MyDocument* doc = &app.Documents[doc_n]; + if (!doc->Open) + continue; + + ImGuiTabItemFlags tab_flags = (doc->Dirty ? ImGuiTabItemFlags_UnsavedDocument : 0); + bool visible = ImGui::BeginTabItem(doc->Name, &doc->Open, tab_flags); + + // Cancel attempt to close when unsaved add to save queue so we can display a popup. + if (!doc->Open && doc->Dirty) + { + doc->Open = true; + doc->DoQueueClose(); + } + + MyDocument::DisplayContextMenu(doc); + if (visible) + { + MyDocument::DisplayContents(doc); + ImGui::EndTabItem(); + } + } + + ImGui::EndTabBar(); + } + else if (opt_target == Target_Window) + { + if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_DockingEnable) + { + NotifyOfDocumentsClosedElsewhere(app); + + // Create a DockSpace node where any window can be docked + ImGuiID dockspace_id = ImGui::GetID("MyDockSpace"); + ImGui::DockSpace(dockspace_id); + + // Create Windows + for (int doc_n = 0; doc_n < app.Documents.Size; doc_n++) + { + MyDocument* doc = &app.Documents[doc_n]; + if (!doc->Open) + continue; + + // FIXME-DOCK: SetNextWindowDock() + //ImGuiID default_dock_id = GetDockspaceRootDocumentDockID(); + //ImGuiID default_dock_id = GetDockspacePreferedDocumentDockID(); + ImGui::SetNextWindowDockId(dockspace_id, redock_all ? ImGuiCond_Always : ImGuiCond_FirstUseEver); + ImGuiWindowFlags window_flags = (doc->Dirty ? ImGuiWindowFlags_UnsavedDocument : 0); + bool visible = ImGui::Begin(doc->Name, &doc->Open, window_flags); + + // Cancel attempt to close when unsaved add to save queue so we can display a popup. + if (!doc->Open && doc->Dirty) + { + doc->Open = true; + doc->DoQueueClose(); + } + + MyDocument::DisplayContextMenu(doc); + if (visible) + MyDocument::DisplayContents(doc); + + ImGui::End(); + } + } + else + { + ShowDockingDisabledMessage(); + } + } + + // Update closing queue + static ImVector close_queue; + if (close_queue.empty()) + { + // Close queue is locked once we started a popup + for (int doc_n = 0; doc_n < app.Documents.Size; doc_n++) + { + MyDocument* doc = &app.Documents[doc_n]; + if (doc->WantClose) + { + doc->WantClose = false; + close_queue.push_back(doc); + } + } + } + + // Display closing confirmation UI + if (!close_queue.empty()) + { + int close_queue_unsaved_documents = 0; + for (int n = 0; n < close_queue.Size; n++) + if (close_queue[n]->Dirty) + close_queue_unsaved_documents++; + + if (close_queue_unsaved_documents == 0) + { + // Close documents when all are unsaved + for (int n = 0; n < close_queue.Size; n++) + close_queue[n]->DoForceClose(); + close_queue.clear(); + } + else + { + if (!ImGui::IsPopupOpen("Save?")) + ImGui::OpenPopup("Save?"); + if (ImGui::BeginPopupModal("Save?")) + { + ImGui::Text("Save change to the following items?"); + ImGui::PushItemWidth(-1.0f); + ImGui::ListBoxHeader("##", close_queue_unsaved_documents, 6); + for (int n = 0; n < close_queue.Size; n++) + if (close_queue[n]->Dirty) + ImGui::Text("%s", close_queue[n]->Name); + ImGui::ListBoxFooter(); + + if (ImGui::Button("Yes", ImVec2(80, 0))) + { + for (int n = 0; n < close_queue.Size; n++) + { + if (close_queue[n]->Dirty) + close_queue[n]->DoSave(); + close_queue[n]->DoForceClose(); + } + close_queue.clear(); + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("No", ImVec2(80, 0))) + { + for (int n = 0; n < close_queue.Size; n++) + close_queue[n]->DoForceClose(); + close_queue.clear(); + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("Cancel", ImVec2(80, 0))) + { + close_queue.clear(); + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + } + } + + ImGui::End(); +} + // End of Demo code #else diff --git a/Source Code/Libs/imgui/imgui_draw.cpp b/Source Code/Libs/imgui/imgui_draw.cpp index 705b0b7014..dde01fd6fb 100644 --- a/Source Code/Libs/imgui/imgui_draw.cpp +++ b/Source Code/Libs/imgui/imgui_draw.cpp @@ -1,7 +1,6 @@ #include "stdafx.h" -// dear imgui, v1.65 - +// dear imgui, v1.66 WIP // (drawing and font code) /* @@ -208,6 +207,13 @@ void ImGui::StyleColorsDark(ImGuiStyle* dst) colors[ImGuiCol_ResizeGrip] = ImVec4(0.26f, 0.59f, 0.98f, 0.25f); colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); colors[ImGuiCol_ResizeGripActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); + colors[ImGuiCol_Tab] = ImLerp(colors[ImGuiCol_Header], colors[ImGuiCol_TitleBgActive], 0.80f); + colors[ImGuiCol_TabActive] = ImLerp(colors[ImGuiCol_HeaderActive], colors[ImGuiCol_TitleBgActive], 0.60f); + colors[ImGuiCol_TabUnfocused] = ImLerp(colors[ImGuiCol_Tab], colors[ImGuiCol_TitleBg], 0.80f); + colors[ImGuiCol_TabUnfocusedActive] = ImLerp(colors[ImGuiCol_TabActive], colors[ImGuiCol_TitleBg], 0.40f); + colors[ImGuiCol_TabHovered] = colors[ImGuiCol_HeaderHovered]; + colors[ImGuiCol_DockingPreview] = colors[ImGuiCol_HeaderActive] * ImVec4(1.0f, 1.0f, 1.0f, 0.7f); + colors[ImGuiCol_DockingBg] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f);; colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f); colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); @@ -258,6 +264,13 @@ void ImGui::StyleColorsClassic(ImGuiStyle* dst) colors[ImGuiCol_ResizeGrip] = ImVec4(1.00f, 1.00f, 1.00f, 0.16f); colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.78f, 0.82f, 1.00f, 0.60f); colors[ImGuiCol_ResizeGripActive] = ImVec4(0.78f, 0.82f, 1.00f, 0.90f); + colors[ImGuiCol_Tab] = ImLerp(colors[ImGuiCol_Header], colors[ImGuiCol_TitleBgActive], 0.80f); + colors[ImGuiCol_TabActive] = ImLerp(colors[ImGuiCol_HeaderActive], colors[ImGuiCol_TitleBgActive], 0.60f); + colors[ImGuiCol_TabUnfocused] = ImLerp(colors[ImGuiCol_Tab], colors[ImGuiCol_TitleBg], 0.80f); + colors[ImGuiCol_TabUnfocusedActive] = ImLerp(colors[ImGuiCol_TabActive], colors[ImGuiCol_TitleBg], 0.40f); + colors[ImGuiCol_TabHovered] = colors[ImGuiCol_HeaderHovered]; + colors[ImGuiCol_DockingPreview] = colors[ImGuiCol_Header] * ImVec4(1.0f, 1.0f, 1.0f, 0.7f); + colors[ImGuiCol_DockingBg] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f);; colors[ImGuiCol_PlotLines] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); @@ -309,6 +322,13 @@ void ImGui::StyleColorsLight(ImGuiStyle* dst) colors[ImGuiCol_ResizeGrip] = ImVec4(0.80f, 0.80f, 0.80f, 0.56f); colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); colors[ImGuiCol_ResizeGripActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); + colors[ImGuiCol_Tab] = ImLerp(colors[ImGuiCol_Header], colors[ImGuiCol_TitleBgActive], 0.90f); + colors[ImGuiCol_TabActive] = ImLerp(colors[ImGuiCol_HeaderActive], colors[ImGuiCol_TitleBgActive], 0.60f); + colors[ImGuiCol_TabUnfocused] = ImLerp(colors[ImGuiCol_Tab], colors[ImGuiCol_TitleBg], 0.80f); + colors[ImGuiCol_TabUnfocusedActive] = ImLerp(colors[ImGuiCol_TabActive], colors[ImGuiCol_TitleBg], 0.40f); + colors[ImGuiCol_TabHovered] = colors[ImGuiCol_HeaderHovered]; + colors[ImGuiCol_DockingPreview] = colors[ImGuiCol_Header] * ImVec4(1.0f, 1.0f, 1.0f, 0.7f); + colors[ImGuiCol_DockingBg] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f);; colors[ImGuiCol_PlotLines] = ImVec4(0.39f, 0.39f, 0.39f, 1.00f); colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); @@ -825,6 +845,9 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_count, ImU32 col) { + if (points_count < 3) + return; + const ImVec2 uv = _Data->TexUvWhitePixel; if (Flags & ImDrawListFlags_AntiAliasedFill) @@ -1897,7 +1920,7 @@ bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) continue; const int codepoint = range.first_unicode_codepoint_in_range + char_idx; - if (cfg.MergeMode && dst_font->FindGlyphNoFallback((unsigned short)codepoint)) + if (cfg.MergeMode && dst_font->FindGlyphNoFallback((ImWchar)codepoint)) continue; float char_advance_x_org = pc.xadvance; @@ -2319,21 +2342,21 @@ void ImFont::BuildLookupTable() { int codepoint = (int)Glyphs[i].Codepoint; IndexAdvanceX[codepoint] = Glyphs[i].AdvanceX; - IndexLookup[codepoint] = (unsigned short)i; + IndexLookup[codepoint] = (ImWchar)i; } // Create a glyph to handle TAB // FIXME: Needs proper TAB handling but it needs to be contextualized (or we could arbitrary say that each string starts at "column 0" ?) - if (FindGlyph((unsigned short)' ')) + if (FindGlyph((ImWchar)' ')) { if (Glyphs.back().Codepoint != '\t') // So we can call this function multiple times Glyphs.resize(Glyphs.Size + 1); ImFontGlyph& tab_glyph = Glyphs.back(); - tab_glyph = *FindGlyph((unsigned short)' '); + tab_glyph = *FindGlyph((ImWchar)' '); tab_glyph.Codepoint = '\t'; tab_glyph.AdvanceX *= 4; IndexAdvanceX[(int)tab_glyph.Codepoint] = (float)tab_glyph.AdvanceX; - IndexLookup[(int)tab_glyph.Codepoint] = (unsigned short)(Glyphs.Size-1); + IndexLookup[(int)tab_glyph.Codepoint] = (ImWchar)(Glyphs.Size-1); } FallbackGlyph = FindGlyphNoFallback(FallbackChar); @@ -2355,7 +2378,7 @@ void ImFont::GrowIndex(int new_size) if (new_size <= IndexLookup.Size) return; IndexAdvanceX.resize(new_size, -1.0f); - IndexLookup.resize(new_size, (unsigned short)-1); + IndexLookup.resize(new_size, (ImWchar)-1); } // x0/y0/x1/y1 are offset from the character upper-left layout position, in pixels. Therefore x0/y0 are often fairly close to zero. @@ -2388,13 +2411,13 @@ void ImFont::AddRemapChar(ImWchar dst, ImWchar src, bool overwrite_dst) IM_ASSERT(IndexLookup.Size > 0); // Currently this can only be called AFTER the font has been built, aka after calling ImFontAtlas::GetTexDataAs*() function. int index_size = IndexLookup.Size; - if (dst < index_size && IndexLookup.Data[dst] == (unsigned short)-1 && !overwrite_dst) // 'dst' already exists + if (dst < index_size && IndexLookup.Data[dst] == (ImWchar)-1 && !overwrite_dst) // 'dst' already exists return; if (src >= index_size && dst >= index_size) // both 'dst' and 'src' don't exist -> no-op return; GrowIndex(dst + 1); - IndexLookup[dst] = (src < index_size) ? IndexLookup.Data[src] : (unsigned short)-1; + IndexLookup[dst] = (src < index_size) ? IndexLookup.Data[src] : (ImWchar)-1; IndexAdvanceX[dst] = (src < index_size) ? IndexAdvanceX.Data[src] : 1.0f; } @@ -2402,8 +2425,8 @@ const ImFontGlyph* ImFont::FindGlyph(ImWchar c) const { if (c >= IndexLookup.Size) return FallbackGlyph; - const unsigned short i = IndexLookup[c]; - if (i == (unsigned short)-1) + const ImWchar i = IndexLookup[c]; + if (i == (ImWchar)-1) return FallbackGlyph; return &Glyphs.Data[i]; } @@ -2412,8 +2435,8 @@ const ImFontGlyph* ImFont::FindGlyphNoFallback(ImWchar c) const { if (c >= IndexLookup.Size) return NULL; - const unsigned short i = IndexLookup[c]; - if (i == (unsigned short)-1) + const ImWchar i = IndexLookup[c]; + if (i == (ImWchar)-1) return NULL; return &Glyphs.Data[i]; } @@ -2611,7 +2634,7 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons return text_size; } -void ImFont::RenderChar(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, unsigned short c) const +void ImFont::RenderChar(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col, ImWchar c) const { if (c == ' ' || c == '\t' || c == '\n' || c == '\r') // Match behavior of RenderText(), those 4 codepoints are hard-coded. return; @@ -2736,7 +2759,7 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col } float char_width = 0.0f; - if (const ImFontGlyph* glyph = FindGlyph((unsigned short)c)) + if (const ImFontGlyph* glyph = FindGlyph((ImWchar)c)) { char_width = glyph->AdvanceX * scale; @@ -2819,11 +2842,14 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, ImVec2 pos, ImU32 col // (progressively moved from imgui.cpp to here when they are redesigned to stop accessing ImGui global state) //----------------------------------------------------------------------------- // - RenderMouseCursor() +// - RenderArrowDockMenu() // - RenderArrowPointingAt() // - RenderRectFilledRangeH() +// - RenderRectFilledWithHole() +// - RenderPixelEllipsis() //----------------------------------------------------------------------------- -void ImGui::RenderMouseCursor(ImDrawList* draw_list, ImVec2 pos, float scale, ImGuiMouseCursor mouse_cursor) +void ImGui::RenderMouseCursor(ImVec2 pos, float scale, ImGuiMouseCursor mouse_cursor) { if (mouse_cursor == ImGuiMouseCursor_None) return; @@ -2833,12 +2859,22 @@ void ImGui::RenderMouseCursor(ImDrawList* draw_list, ImVec2 pos, float scale, Im const ImU32 col_border = IM_COL32(0, 0, 0, 255); // Black const ImU32 col_fill = IM_COL32(255, 255, 255, 255); // White - ImFontAtlas* font_atlas = draw_list->_Data->Font->ContainerAtlas; + ImGuiContext& g = *GImGui; + ImFontAtlas* font_atlas = g.IO.Fonts; ImVec2 offset, size, uv[4]; if (font_atlas->GetMouseCursorTexData(mouse_cursor, &offset, &size, &uv[0], &uv[2])) { pos -= offset; const ImTextureID tex_id = font_atlas->TexID; + + // We need to account for the possibility of the mouse cursor straddling multiple viewports... + for (int viewport_n = 0; viewport_n < g.Viewports.Size; viewport_n++) + { + ImGuiViewportP* viewport = g.Viewports[viewport_n]; + if (!viewport->GetRect().Overlaps(ImRect(pos, pos + ImVec2(size.x + 2, size.y + 2) * scale))) + continue; + + ImDrawList* draw_list = GetOverlayDrawList(viewport); draw_list->PushTextureID(tex_id); draw_list->AddImage(tex_id, pos + ImVec2(1,0)*scale, pos + ImVec2(1,0)*scale + size*scale, uv[2], uv[3], col_shadow); draw_list->AddImage(tex_id, pos + ImVec2(2,0)*scale, pos + ImVec2(2,0)*scale + size*scale, uv[2], uv[3], col_shadow); @@ -2847,6 +2883,7 @@ void ImGui::RenderMouseCursor(ImDrawList* draw_list, ImVec2 pos, float scale, Im draw_list->PopTextureID(); } } +} // Render an arrow. 'pos' is position of the arrow tip. half_sz.x is length from base to tip. half_sz.y is length on each side. void ImGui::RenderArrowPointingAt(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, ImGuiDir direction, ImU32 col) @@ -2861,6 +2898,14 @@ void ImGui::RenderArrowPointingAt(ImDrawList* draw_list, ImVec2 pos, ImVec2 half } } +// This is less wide than RenderArrow() and we use in dock nodes instead of the regular RenderArrow() to denote a change of functionality, +// and because the saved space means that the left-most tab label can stay at exactly the same position as the label of a loose window. +void ImGui::RenderArrowDockMenu(ImDrawList* draw_list, ImVec2 p_min, float sz, ImU32 col) +{ + draw_list->AddRectFilled(p_min + ImVec2(sz * 0.10f, sz * 0.15f), p_min + ImVec2(sz * 0.70f, sz * 0.30f), col); + RenderArrowPointingAt(draw_list, p_min + ImVec2(sz * 0.40f, sz * 0.85f), ImVec2(sz * 0.30f, sz * 0.40f), ImGuiDir_Down, col); +} + static inline float ImAcos01(float x) { if (x <= 0.0f) return IM_PI * 0.5f; @@ -2929,6 +2974,33 @@ void ImGui::RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, Im draw_list->PathFillConvex(col); } +// For CTRL+TAB within a docking node we need to render the dimming background in 8 steps +// (Because the root node renders the background in one shot, in order to avoid flickering when a child dock node is not submitted) +void ImGui::RenderRectFilledWithHole(ImDrawList* draw_list, ImRect outer, ImRect inner, ImU32 col, float rounding) +{ + const bool fill_L = (inner.Min.x > outer.Min.x); + const bool fill_R = (inner.Max.x < outer.Max.x); + const bool fill_U = (inner.Min.y > outer.Min.y); + const bool fill_D = (inner.Max.y < outer.Max.y); + if (fill_L) draw_list->AddRectFilled(ImVec2(outer.Min.x, inner.Min.y), ImVec2(inner.Min.x, inner.Max.y), col, rounding, (fill_U ? 0 : ImDrawCornerFlags_TopLeft) | (fill_D ? 0 : ImDrawCornerFlags_BotLeft)); + if (fill_R) draw_list->AddRectFilled(ImVec2(inner.Max.x, inner.Min.y), ImVec2(outer.Max.x, inner.Max.y), col, rounding, (fill_U ? 0 : ImDrawCornerFlags_TopRight) | (fill_D ? 0 : ImDrawCornerFlags_BotRight)); + if (fill_U) draw_list->AddRectFilled(ImVec2(inner.Min.x, outer.Min.y), ImVec2(inner.Max.x, inner.Min.y), col, rounding, (fill_L ? 0 : ImDrawCornerFlags_TopLeft) | (fill_R ? 0 : ImDrawCornerFlags_TopRight)); + if (fill_D) draw_list->AddRectFilled(ImVec2(inner.Min.x, inner.Max.y), ImVec2(inner.Max.x, outer.Max.y), col, rounding, (fill_L ? 0 : ImDrawCornerFlags_BotLeft) | (fill_R ? 0 : ImDrawCornerFlags_BotRight)); + if (fill_L && fill_U) draw_list->AddRectFilled(ImVec2(outer.Min.x, outer.Min.y), ImVec2(inner.Min.x, inner.Min.y), col, rounding, ImDrawCornerFlags_TopLeft); + if (fill_R && fill_U) draw_list->AddRectFilled(ImVec2(inner.Max.x, outer.Min.y), ImVec2(outer.Max.x, inner.Min.y), col, rounding, ImDrawCornerFlags_TopRight); + if (fill_L && fill_D) draw_list->AddRectFilled(ImVec2(outer.Min.x, inner.Max.y), ImVec2(inner.Min.x, outer.Max.y), col, rounding, ImDrawCornerFlags_BotLeft); + if (fill_R && fill_D) draw_list->AddRectFilled(ImVec2(inner.Max.x, inner.Max.y), ImVec2(outer.Max.x, outer.Max.y), col, rounding, ImDrawCornerFlags_BotRight); +} + +// FIXME: Rendering an ellipsis "..." is a surprisingly tricky problem... can't rely on font glyph having it, and regular dot are typically too wide. +// If we render a dot/shape ourselves it comes with the risk that it wouldn't match the boldness or positioning of what the font uses... +void ImGui::RenderPixelEllipsis(ImDrawList* draw_list, ImFont* font, ImVec2 pos, int count, ImU32 col) +{ + pos.y += (float)(int)(font->DisplayOffset.y + font->Ascent + 0.5f - 1.0f); + for (int dot_n = 0; dot_n < count; dot_n++) + draw_list->AddRectFilled(ImVec2(pos.x + dot_n * 2.0f, pos.y), ImVec2(pos.x + dot_n * 2.0f + 1.0f, pos.y + 1.0f), col); +} + //----------------------------------------------------------------------------- // [SECTION] Decompression code //----------------------------------------------------------------------------- diff --git a/Source Code/Libs/imgui/imgui_internal.h b/Source Code/Libs/imgui/imgui_internal.h index 050f2acf24..04d86b3876 100644 --- a/Source Code/Libs/imgui/imgui_internal.h +++ b/Source Code/Libs/imgui/imgui_internal.h @@ -1,4 +1,4 @@ -// dear imgui, v1.65 +// dear imgui, v1.66 WIP // (internal structures/api) // You may use this file to debug, understand or extend ImGui features but we don't provide any guarantee of forward compatibility! @@ -40,6 +40,9 @@ struct ImGuiColorMod; // Stacked color modifier, backup of modifie struct ImGuiColumnData; // Storage data for a single column struct ImGuiColumnsSet; // Storage data for a columns set struct ImGuiContext; // Main imgui context +struct ImGuiDockContext; // Docking system context +struct ImGuiDockNode; // Docking system node (hold a list of Windows OR two child dock nodes) +struct ImGuiDockNodeSettings; // Storage for a dock node in .ini file (we preserve those even if the associated dock node isn't active during the session) struct ImGuiGroupData; // Stacked storage data for BeginGroup()/EndGroup() struct ImGuiInputTextState; // Internal state of the currently focused/edited text input box struct ImGuiItemHoveredDataBackup; // Backup and restore IsItemHovered() internal data @@ -49,6 +52,8 @@ struct ImGuiNextWindowData; // Storage for SetNexWindow** functions struct ImGuiPopupRef; // Storage for current popup stack struct ImGuiSettingsHandler; // Storage for one type registered in the .ini file struct ImGuiStyleMod; // Stacked style modifier, backup of modified data so we can restore it +struct ImGuiTabBar; // Storage for a tab bar +struct ImGuiTabItem; // Storage for a tab item (within a tab bar) struct ImGuiWindow; // Storage for one window struct ImGuiWindowTempData; // Temporary storage for one window (that's the data which in theory we could ditch at the end of the frame) struct ImGuiWindowSettings; // Storage for window settings stored in .ini file (we keep one of those even if the actual window wasn't instanced during this session) @@ -88,6 +93,9 @@ namespace ImGuiStb extern IMGUI_API ImGuiContext* GImGui; // Current implicit ImGui context pointer #endif +// Internal Drag and Drop payload types. String starting with '_' are reserved for Dear ImGui. +#define IMGUI_PAYLOAD_TYPE_WINDOW "_IMWINDOW" // Payload == ImGuiWindow* + //----------------------------------------------------------------------------- // Helpers //----------------------------------------------------------------------------- @@ -98,6 +106,8 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit ImGui context pointe #else #define IM_NEWLINE "\n" #endif + +#define IMGUI_DEBUG_LOG(FMT,...) printf("[%05d] " FMT, GImGui->FrameCount, __VA_ARGS__) #define IM_STATIC_ASSERT(_COND) typedef char static_assertion_##__line__[(_COND)?1:-1] #define IM_F32_TO_INT8_UNBOUND(_VAL) ((int)((_VAL) * 255.0f + ((_VAL)>=0 ? 0.5f : -0.5f))) // Unsaturated, for display purpose #define IM_F32_TO_INT8_SAT(_VAL) ((int)(ImSaturate(_VAL) * 255.0f + 0.5f)) // Saturated, always output 0..255 @@ -144,6 +154,7 @@ IMGUI_API int ImStrlenW(const ImWchar* str); IMGUI_API const ImWchar*ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* buf_begin); // Find beginning-of-line IMGUI_API const char* ImStristr(const char* haystack, const char* haystack_end, const char* needle, const char* needle_end); IMGUI_API void ImStrTrimBlanks(char* str); +IMGUI_API const char* ImStrSkipBlank(const char* str); IMGUI_API int ImFormatString(char* buf, size_t buf_size, const char* fmt, ...) IM_FMTARGS(3); IMGUI_API int ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args) IM_FMTLIST(3); IMGUI_API const char* ImParseFormatFindStart(const char* format); @@ -211,18 +222,33 @@ static inline ImVec2 ImRotate(const ImVec2& v, float cos_a, float sin_a) static inline float ImLinearSweep(float current, float target, float speed) { if (current < target) return ImMin(current + speed, target); if (current > target) return ImMax(current - speed, target); return current; } static inline ImVec2 ImMul(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } +// Helper: ImPool<>. Basic keyed storage for contiguous instances, slow/amortized insertion, O(1) indexable, O(Log N) queries by ID over a dense/hot buffer +// Creation/Erasure invalidate all pointers, but indexes are valid as long as the object lifetime. +template +struct IMGUI_API ImPool +{ + ImVector Data; // Contiguous data + ImGuiStorage Map; // ID->Index + int FreeIdx; // Next free idx to use + + ImPool() { FreeIdx = 0; } + ~ImPool() { Clear(); } + T* GetByKey(ImGuiID key) { int idx = Map.GetInt(key, -1); return (idx != -1) ? &Data[idx] : NULL; } + T* GetByIndex(int n) { return &Data[n]; } + int GetIndex(const T* p) const { IM_ASSERT(p >= Data.Data && p < Data.Data + Data.Size); return (int)(p - Data.Data); } + T* GetOrAddByKey(ImGuiID key) { int* p_idx = Map.GetIntRef(key, -1); if (*p_idx != -1) return &Data[*p_idx]; *p_idx = FreeIdx; return Add(); } + void Clear() { for (int n = 0; n < Map.Data.Size; n++) { int idx = Map.Data[n].val_i; if (idx != -1) Data[idx].~T(); } Map.Clear(); Data.clear(); FreeIdx = 0; } + T* Add() { int idx = FreeIdx; if (idx == Data.Size) { Data.resize(Data.Size + 1); FreeIdx++; } else { FreeIdx = *(int*)&Data[idx]; } IM_PLACEMENT_NEW(&Data[idx]) T(); return &Data[idx]; } + void Remove(ImGuiID key, const T* p) { Remove(key, GetIndex(p)); } + void Remove(ImGuiID key, int idx) { Data[idx].~T(); *(int*)&Data[idx] = FreeIdx; FreeIdx = idx; Map.SetInt(key, -1); } + void Reserve(int capacity) { Data.reserve(capacity); Map.Data.reserve(capacity); } +}; +typedef int ImPoolIdx; + //----------------------------------------------------------------------------- // Types //----------------------------------------------------------------------------- -// 1D vector (this odd construct is used to facilitate the transition between 1D and 2D and maintenance of some patches) -struct ImVec1 -{ - float x; - ImVec1() { x = 0.0f; } - ImVec1(float _x) { x = _x; } -}; - enum ImGuiButtonFlags_ { ImGuiButtonFlags_None = 0, @@ -275,6 +301,19 @@ enum ImGuiSeparatorFlags_ ImGuiSeparatorFlags_Vertical = 1 << 1 }; +// Transient per-window ItemFlags, reset at the beginning of the frame. For child windows: inherited from parent on first Begin(). +// This is going to be exposed in imgui.h when stabilized enough. +enum ImGuiItemFlags_ +{ + ImGuiItemFlags_AllowKeyboardFocus = 1 << 0, // true + ImGuiItemFlags_ButtonRepeat = 1 << 1, // false // Button() will return true multiple times based on io.KeyRepeatDelay and io.KeyRepeatRate settings. + ImGuiItemFlags_Disabled = 1 << 2, // false // [BETA] Disable interactions but doesn't affect visuals yet. See github.com/ocornut/imgui/issues/211 + ImGuiItemFlags_NoNav = 1 << 3, // false + ImGuiItemFlags_NoNavDefaultFocus = 1 << 4, // false + ImGuiItemFlags_SelectableDontClosePopup = 1 << 5, // false // MenuItem/Selectable() automatically closes current Popup window + ImGuiItemFlags_Default_ = ImGuiItemFlags_AllowKeyboardFocus +}; + // Storage for LastItem data enum ImGuiItemStatusFlags_ { @@ -366,6 +405,22 @@ enum ImGuiPopupPositionPolicy ImGuiPopupPositionPolicy_ComboBox }; +// 1D vector (this odd construct is used to facilitate the transition between 1D and 2D and maintenance of some patches) +struct ImVec1 +{ + float x; + ImVec1() { x = 0.0f; } + ImVec1(float _x) { x = _x; } +}; + +// 2D vector (half-size integer) +struct ImVec2ih +{ + short x, y; + ImVec2ih() { x = y = 0; } + ImVec2ih(short _x, short _y) { x = _x; y = _y; } +}; + // 2D axis aligned bounding-box // NB: we can't rely on ImVec2 math operators being available here struct IMGUI_API ImRect @@ -482,11 +537,16 @@ struct ImGuiWindowSettings { char* Name; ImGuiID ID; - ImVec2 Pos; + ImVec2 Pos; // NB: Settings position are stored RELATIVE to the viewport! Whereas runtime ones are absolute positions. ImVec2 Size; + ImVec2 ViewportPos; + ImGuiID ViewportId; + ImGuiID DockId; // ID of last known DockNode (even if the DockNode is invisible because it has only 1 active window), or 0 if none. + ImGuiID DockFamilyId; // ID of dock family if specified + short DockOrder; // Order of the last time the window was visible within its DockNode. This is used to reorder windows that are reappearing on the same frame. Same value between windows that were active and windows that were none are possible. bool Collapsed; - ImGuiWindowSettings() { Name = NULL; ID = 0; Pos = Size = ImVec2(0,0); Collapsed = false; } + ImGuiWindowSettings() { Name = NULL; ID = 0; Pos = Size = ViewportPos = ImVec2(0, 0); ViewportId = DockId = DockFamilyId = 0; DockOrder = -1; Collapsed = false; } }; struct ImGuiSettingsHandler @@ -579,6 +639,36 @@ struct ImDrawDataBuilder IMGUI_API void FlattenIntoSingleLayer(); }; +enum ImGuiViewportFlagsPrivate_ +{ + ImGuiViewportFlags_CanHostOtherWindows = 1 << 10 // Normal viewports are associated to a single window. The main viewport can host multiple windows. +}; + +// ImGuiViewport Private/Internals fields (cardinal sin: we are using inheritance!) +struct ImGuiViewportP : public ImGuiViewport +{ + int Idx; + int LastFrameActive; // Last frame number this viewport was activated by a window + int LastFrameOverlayDrawList; + int LastFrontMostStampCount; // Last stamp number from when a window hosted by this viewport was made front-most (by comparing this value between two viewport we have an implicit viewport z-order + ImGuiID LastNameHash; + ImVec2 LastPos; + bool CreatedPlatformWindow; + float Alpha; // Window opacity (when dragging dockable windows/viewports we make them transparent) + float LastAlpha; + int PlatformMonitor; + ImGuiWindow* Window; // Set when the viewport is owned by a window + ImDrawList* OverlayDrawList; // For convenience, a draw list we can render to that's always rendered last (we use it to draw software mouse cursor when io.MouseDrawCursor is set) + ImDrawData DrawDataP; + ImDrawDataBuilder DrawDataBuilder; + ImVec2 RendererLastSize; + + ImGuiViewportP() { Idx = -1; LastFrameActive = LastFrameOverlayDrawList = LastFrontMostStampCount = -1; LastNameHash = 0; CreatedPlatformWindow = false; Alpha = LastAlpha = 1.0f; PlatformMonitor = INT_MIN; Window = NULL; OverlayDrawList = NULL; RendererLastSize = ImVec2(-1.0f,-1.0f); } + ~ImGuiViewportP() { if (OverlayDrawList) IM_DELETE(OverlayDrawList); } + ImRect GetRect() const { return ImRect(Pos.x, Pos.y, Pos.x + Size.x, Pos.y + Size.y); } + ImVec2 GetCenter() const{ return ImVec2(Pos.x + Size.x * 0.5f, Pos.y + Size.y * 0.5f); } +}; + struct ImGuiNavMoveResult { ImGuiID ID; // Best candidate @@ -602,43 +692,107 @@ struct ImGuiNextWindowData ImGuiCond SizeConstraintCond; ImGuiCond FocusCond; ImGuiCond BgAlphaCond; + ImGuiCond ViewportCond; + ImGuiCond DockCond; ImVec2 PosVal; ImVec2 PosPivotVal; ImVec2 SizeVal; ImVec2 ContentSizeVal; + bool PosUndock; bool CollapsedVal; ImRect SizeConstraintRect; ImGuiSizeCallback SizeCallback; void* SizeCallbackUserData; float BgAlphaVal; + ImGuiID ViewportId; + ImGuiID DockId; + ImGuiDockFamily DockFamily; ImVec2 MenuBarOffsetMinVal; // This is not exposed publicly, so we don't clear it. ImGuiNextWindowData() { - PosCond = SizeCond = ContentSizeCond = CollapsedCond = SizeConstraintCond = FocusCond = BgAlphaCond = 0; + PosCond = SizeCond = ContentSizeCond = CollapsedCond = SizeConstraintCond = FocusCond = BgAlphaCond = ViewportCond = DockCond = 0; PosVal = PosPivotVal = SizeVal = ImVec2(0.0f, 0.0f); ContentSizeVal = ImVec2(0.0f, 0.0f); - CollapsedVal = false; + PosUndock = CollapsedVal = false; SizeConstraintRect = ImRect(); SizeCallback = NULL; SizeCallbackUserData = NULL; BgAlphaVal = FLT_MAX; + ViewportId = DockId = 0; MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f); } void Clear() { - PosCond = SizeCond = ContentSizeCond = CollapsedCond = SizeConstraintCond = FocusCond = BgAlphaCond = 0; + PosCond = SizeCond = ContentSizeCond = CollapsedCond = SizeConstraintCond = FocusCond = BgAlphaCond = ViewportCond = DockCond = 0; + DockFamily = ImGuiDockFamily(); } }; +//----------------------------------------------------------------------------- +// Docking, Tabs +//----------------------------------------------------------------------------- + +struct ImGuiTabBarSortItem +{ + int Index; + float Width; +}; + +// sizeof() 108~144 +struct ImGuiDockNode +{ + ImGuiID ID; + ImGuiDockNodeFlags Flags; + ImGuiDockNode* ParentNode; + ImGuiDockNode* ChildNodes[2]; // [Split node only] Child nodes (left/right or top/bottom). Consider switching to an array. + ImVector Windows; // Note: unordered list! Iterate TabBar->Tabs for user-order. + ImGuiTabBar* TabBar; + ImVec2 Pos; // Current position + ImVec2 Size; // Current size + ImVec2 SizeRef; // [Split node only] Last explicitly written-to size (overridden when using a splitter affecting the node), used to calculate Size. + int SplitAxis; // [Split node only] Split axis (X or Y) + ImGuiDockFamily DockFamily; + + ImGuiWindow* HostWindow; + ImGuiWindow* VisibleWindow; + ImGuiDockNode* OnlyNodeWithWindows; // [Root node only] Set when there is a single visible node within the hierarchy + int LastFrameAlive; // Last frame number the node was updated or kept alive explicitly with DockSpace() + ImGuiDockNodeFlags_KeepAliveOnly + int LastFrameActive; // Last frame number the node was updated. + ImGuiID LastFocusedNodeID; // [Root node only] Which of our child node (any ancestor in the hierarchy) was last focused. + ImGuiID SelectedTabID; // [Tab node only] Which of our tab is selected. + ImGuiID WantCloseTabID; // [Tab node only] Set when closing a specific tab. + bool InitFromFirstWindowPosSize :1; + bool InitFromFirstWindowViewport :1; + bool IsVisible :1; // Set to false when the node is hidden (usually disabled as it has no active window) + bool IsDockSpace :1; // Root node was created by a DockSpace() call. + bool IsDocumentRoot :1; + bool HasCloseButton :1; + bool HasCollapseButton :1; + bool WantCloseAll :1; // Set when closing all tabs at once. + bool WantLockSizeOnce :1; + bool WantMouseMove :1; // After a node extraction we need to transition toward moving the newly created host window + + ImGuiDockNode(ImGuiID id); + ~ImGuiDockNode(); + bool IsRootNode() const { return ParentNode == NULL; } + bool IsSplitNode() const { return ChildNodes[0] != NULL; } + bool IsEmpty() const { return ChildNodes[0] == NULL && Windows.Size == 0; } + ImRect Rect() const { return ImRect(Pos.x, Pos.y, Pos.x + Size.x, Pos.y + Size.y); } +}; + +//----------------------------------------------------------------------------- // Main imgui context +//----------------------------------------------------------------------------- + struct ImGuiContext { bool Initialized; bool FrameScopeActive; // Set by NewFrame(), cleared by EndFrame()/Render() bool FontAtlasOwnedByContext; // Io.Fonts-> is owned by the ImGuiContext and will be destructed along with it. ImGuiIO IO; + ImGuiPlatformIO PlatformIO; ImGuiStyle Style; ImFont* Font; // (Shortcut) == FontStack.empty() ? IO.Font : FontStack.back() float FontSize; // (Shortcut) == FontBaseSize * g.CurrentWindow->FontWindowScale == window->FontSize(). Text height for current window. @@ -648,15 +802,19 @@ struct ImGuiContext double Time; int FrameCount; int FrameCountEnded; + int FrameCountPlatformEnded; int FrameCountRendered; - ImVector Windows; + ImVector Windows; // Windows, sorted in display order, back to front + ImVector WindowsFocusOrder; // Windows, sorted in focus order, back to front ImVector WindowsSortBuffer; ImVector CurrentWindowStack; ImGuiStorage WindowsById; int WindowsActiveCount; + int WindowsFrontMostStampCount; // Every time the front-most window changes, we stamp its viewport with an incrementing counter ImGuiWindow* CurrentWindow; // Being drawn into ImGuiWindow* HoveredWindow; // Will catch mouse inputs ImGuiWindow* HoveredRootWindow; // Will catch mouse inputs (for focus/move only) + ImGuiWindow* HoveredWindowUnderMovingWindow; ImGuiID HoveredId; // Hovered widget bool HoveredIdAllowOverlap; ImGuiID HoveredIdPreviousFrame; @@ -687,6 +845,13 @@ struct ImGuiContext bool NextTreeNodeOpenVal; // Storage for SetNextTreeNode** functions ImGuiCond NextTreeNodeOpenCond; + // Viewports + ImVector Viewports; // Active viewports (always 1+, and generally 1 unless multi-viewports are enabled). Each viewports hold their copy of ImDrawData. + ImGuiViewportP* CurrentViewport; // We track changes of viewport (happening in Begin) so we can call Platform_OnChangedViewport() + ImGuiViewportP* MouseViewport; + ImGuiViewportP* MouseLastHoveredViewport; // Last known viewport that was hovered by mouse (even if we are not hovering any viewport any more) + honoring the _NoInputs flag. + ImGuiID PlatformLastFocusedViewport; // Record of last focused platform window/viewport, when this changes we stamp the viewport as front-most + // Navigation data (for gamepad/keyboard) ImGuiWindow* NavWindow; // Focused window for navigation. Could be called 'FocusWindow' ImGuiID NavId; // Focused item for navigation @@ -728,10 +893,7 @@ struct ImGuiContext ImGuiNavMoveResult NavMoveResultOther; // Best move request candidate within NavWindow's flattened hierarchy (when using ImGuiWindowFlags_NavFlattened flag) // Render - ImDrawData DrawData; // Main ImDrawData instance to pass render information to the user - ImDrawDataBuilder DrawDataBuilder; float DimBgRatio; // 0.0..1.0 animation when fading in a dimming background (for modal window and CTRL+TAB list) - ImDrawList OverlayDrawList; // Optional software render of mouse cursors, if io.MouseDrawCursor is set + a few debug overlays ImGuiMouseCursor MouseCursor; // Drag and Drop @@ -751,6 +913,11 @@ struct ImGuiContext ImVector DragDropPayloadBufHeap; // We don't expose the ImVector<> directly unsigned char DragDropPayloadBufLocal[8]; // Local buffer for small payloads + // Tab bars + ImPool TabBars; + ImVector CurrentTabBar; + ImVector TabSortByWidthBuffer; + // Widget state ImGuiInputTextState InputTextState; ImFont InputTextPasswordFont; @@ -763,7 +930,14 @@ struct ImGuiContext ImVec2 ScrollbarClickDeltaToGrabCenter; // Distance between mouse and center of grab box, normalized in parent space. Use storage? int TooltipOverrideCount; ImVector PrivateClipboard; // If no custom clipboard handler is defined + + // Platform support ImVec2 PlatformImePos, PlatformImeLastPos; // Cursor position request & last passed to the OS Input Method Editor + ImGuiViewport* PlatformImePosViewport; + + // Extensions + // FIXME: We could provide an API to register one slot in an array held in ImGuiContext? + ImGuiDockContext* DockContext; // Settings bool SettingsLoaded; @@ -788,7 +962,7 @@ struct ImGuiContext int WantTextInputNextFrame; char TempBuffer[1024*3+1]; // Temporary text buffer - ImGuiContext(ImFontAtlas* shared_font_atlas) : OverlayDrawList(NULL) + ImGuiContext(ImFontAtlas* shared_font_atlas) { Initialized = false; FrameScopeActive = false; @@ -799,8 +973,9 @@ struct ImGuiContext Time = 0.0f; FrameCount = 0; - FrameCountEnded = FrameCountRendered = -1; + FrameCountEnded = FrameCountPlatformEnded = FrameCountRendered = -1; WindowsActiveCount = 0; + WindowsFrontMostStampCount = 0; CurrentWindow = NULL; HoveredWindow = NULL; HoveredRootWindow = NULL; @@ -827,6 +1002,10 @@ struct ImGuiContext NextTreeNodeOpenVal = false; NextTreeNodeOpenCond = 0; + CurrentViewport = NULL; + MouseViewport = MouseLastHoveredViewport = NULL; + PlatformLastFocusedViewport = 0; + NavWindow = NULL; NavId = NavActivateId = NavActivateDownId = NavActivatePressedId = NavInputId = 0; NavJustTabbedId = NavJustMovedToId = NavNextActivateId = 0; @@ -853,8 +1032,6 @@ struct ImGuiContext NavMoveDir = NavMoveDirLast = NavMoveClipDir = ImGuiDir_None; DimBgRatio = 0.0f; - OverlayDrawList._Data = &DrawListSharedData; - OverlayDrawList._OwnerName = "##Overlay"; // Give it a name for debugging MouseCursor = ImGuiMouseCursor_Arrow; DragDropActive = DragDropWithinSourceOrTarget = false; @@ -876,6 +1053,9 @@ struct ImGuiContext ScrollbarClickDeltaToGrabCenter = ImVec2(0.0f, 0.0f); TooltipOverrideCount = 0; PlatformImePos = PlatformImeLastPos = ImVec2(FLT_MAX, FLT_MAX); + PlatformImePosViewport = 0; + + DockContext = NULL; SettingsLoaded = false; SettingsDirtyTimer = 0.0f; @@ -893,18 +1073,9 @@ struct ImGuiContext } }; -// Transient per-window flags, reset at the beginning of the frame. For child window, inherited from parent on first Begin(). -// This is going to be exposed in imgui.h when stabilized enough. -enum ImGuiItemFlags_ -{ - ImGuiItemFlags_AllowKeyboardFocus = 1 << 0, // true - ImGuiItemFlags_ButtonRepeat = 1 << 1, // false // Button() will return true multiple times based on io.KeyRepeatDelay and io.KeyRepeatRate settings. - ImGuiItemFlags_Disabled = 1 << 2, // false // [BETA] Disable interactions but doesn't affect visuals yet. See github.com/ocornut/imgui/issues/211 - ImGuiItemFlags_NoNav = 1 << 3, // false - ImGuiItemFlags_NoNavDefaultFocus = 1 << 4, // false - ImGuiItemFlags_SelectableDontClosePopup = 1 << 5, // false // MenuItem/Selectable() automatically closes current Popup window - ImGuiItemFlags_Default_ = ImGuiItemFlags_AllowKeyboardFocus -}; +//----------------------------------------------------------------------------- +// ImGuiWindow +//----------------------------------------------------------------------------- // Transient per-window data, reset at the beginning of the frame. This used to be called ImGuiDrawContext, hence the DC variable name in ImGuiWindow. // FIXME: That's theory, in practice the delimitation between ImGuiWindow and ImGuiWindowTempData is quite tenuous and could be reconsidered. @@ -990,7 +1161,11 @@ struct IMGUI_API ImGuiWindow { char* Name; ImGuiID ID; // == ImHash(Name) - ImGuiWindowFlags Flags; // See enum ImGuiWindowFlags_ + ImGuiWindowFlags Flags, FlagsPreviousFrame; // See enum ImGuiWindowFlags_ + ImGuiViewportP* Viewport; // Always set in Begin(), only inactive windows may have a NULL value here + ImGuiID ViewportId; // We backup the viewport id (since the viewport may disappear or never be created if the window is inactive) + ImVec2 ViewportPos; // We backup the viewport position (since the viewport may disappear or never be created if the window is inactive) + int ViewportAllowPlatformMonitorExtend; // Reset to -1 every frame (index is guaranteed to be valid between NewFrame..EndFrame), only used in the Appearing frame of a tooltip/popup to enforce clamping to a given monitor ImVec2 Pos; // Position (always rounded-up to nearest pixel) ImVec2 Size; // Current size (==SizeFull or collapsed title bar size) ImVec2 SizeFull; // Size when non collapsed @@ -1007,6 +1182,9 @@ struct IMGUI_API ImGuiWindow ImVec2 ScrollTargetCenterRatio; // 0.0f = scroll so that target position is at top, 0.5f = scroll so that target position is centered ImVec2 ScrollbarSizes; // Size taken by scrollbars on each axis bool ScrollbarX, ScrollbarY; + bool ViewportOwned; + bool ViewportTryMerge; // Request attempt to merge into a host viewport and destroy our owned viewport + bool ViewportTrySplit; // Request attempt to split out of a host viewport and create our owned viewport bool Active; // Set to true on Begin(), unless Collapsed bool WasActive; bool WriteAccessed; // Set to true when any widget access the current window @@ -1029,8 +1207,10 @@ struct IMGUI_API ImGuiWindow ImGuiCond SetWindowPosAllowFlags; // store acceptable condition flags for SetNextWindowPos() use. ImGuiCond SetWindowSizeAllowFlags; // store acceptable condition flags for SetNextWindowSize() use. ImGuiCond SetWindowCollapsedAllowFlags; // store acceptable condition flags for SetNextWindowCollapsed() use. + ImGuiCond SetWindowDockAllowFlags; // store acceptable condition flags for SetNextWindowDock() use. ImVec2 SetWindowPosVal; // store window position when using a non-zero Pivot (position set needs to be processed when we know the window size) ImVec2 SetWindowPosPivot; // store window pivot for positioning. ImVec2(0,0) when positioning from top-left corner; ImVec2(0.5f,0.5f) for centering; ImVec2(1,1) for bottom right. + ImGuiDockFamily DockFamily; // set with SetNextWindowDockFamily() ImGuiWindowTempData DC; // Temporary per-window data, reset at the beginning of the frame. This used to be called ImGuiDrawContext, hence the "DC" variable name. ImVector IDStack; // ID stack. ID are hashes seeded with the value at the top of the stack @@ -1044,12 +1224,14 @@ struct IMGUI_API ImGuiWindow ImGuiStorage StateStorage; ImVector ColumnsStorage; float FontWindowScale; // User scale multiplier per-window + float FontDpiScale; int SettingsIdx; // Index into SettingsWindow[] (indices are always valid as we only grow the array from the back) ImDrawList* DrawList; // == &DrawListInst (for backward compatibility reason with code using imgui_internal.h we keep this a pointer) ImDrawList DrawListInst; ImGuiWindow* ParentWindow; // If we are a child _or_ popup window, this is pointing to our parent. Otherwise NULL. ImGuiWindow* RootWindow; // Point to ourself or first ancestor that is not a child window. + ImGuiWindow* RootWindowDockStop; // Point to ourself or first ancestor that is not a child window. Doesn't cross through dock nodes. We use this so IsWindowFocused() can behave consistently regardless of docking state. ImGuiWindow* RootWindowForTitleBarHighlight; // Point to ourself or first ancestor which will display TitleBgActive color when this window is active. ImGuiWindow* RootWindowForNav; // Point to ourself or first ancestor which doesn't have the NavFlattened flag. @@ -1066,6 +1248,17 @@ struct IMGUI_API ImGuiWindow int FocusIdxAllRequestNext; // Item being requested for focus, for next update (relies on layout to be stable between the frame pressing TAB and the next frame) int FocusIdxTabRequestNext; // " + // Docking + ImGuiDockNode* DockNode; // Which node are we docked into + ImGuiDockNode* DockNodeAsHost; // Which node are we owning (for parent windows) + ImGuiID DockId; // Backup of last valid DockNode->Id, so single value remember their dock node id + ImGuiItemStatusFlags DockTabItemStatusFlags; + ImRect DockTabItemRect; + short DockOrder; // Order of the last time the window was visible within its DockNode. This is used to reorder windows that are reappearing on the same frame. Same value between windows that were active and windows that were none are possible. + bool DockIsActive; // =~ (DockNode != NULL) && (DockNode->Windows.Size > 1) + bool DockTabIsVisible; // Is the window visible this frame? =~ is the corresponding tab selected? + bool DockTabWantClose; + public: ImGuiWindow(ImGuiContext* context, const char* name); ~ImGuiWindow(); @@ -1078,7 +1271,7 @@ struct IMGUI_API ImGuiWindow // We don't use g.FontSize because the window may be != g.CurrentWidow. ImRect Rect() const { return ImRect(Pos.x, Pos.y, Pos.x+Size.x, Pos.y+Size.y); } - float CalcFontSize() const { return GImGui->FontBaseSize * FontWindowScale; } + float CalcFontSize() const { return GImGui->FontBaseSize * FontWindowScale * FontDpiScale; } float TitleBarHeight() const { return (Flags & ImGuiWindowFlags_NoTitleBar) ? 0.0f : CalcFontSize() + GImGui->Style.FramePadding.y * 2.0f; } ImRect TitleBarRect() const { return ImRect(Pos, ImVec2(Pos.x + SizeFull.x, Pos.y + TitleBarHeight())); } float MenuBarHeight() const { return (Flags & ImGuiWindowFlags_MenuBar) ? DC.MenuBarOffset.y + CalcFontSize() + GImGui->Style.FramePadding.y * 2.0f : 0.0f; } @@ -1098,6 +1291,68 @@ struct ImGuiItemHoveredDataBackup void Restore() const { ImGuiWindow* window = GImGui->CurrentWindow; window->DC.LastItemId = LastItemId; window->DC.LastItemStatusFlags = LastItemStatusFlags; window->DC.LastItemRect = LastItemRect; window->DC.LastItemDisplayRect = LastItemDisplayRect; } }; +//----------------------------------------------------------------------------- +// Tab Bar, Tab Item +//----------------------------------------------------------------------------- + +enum ImGuiTabBarFlagsPrivate_ +{ + ImGuiTabBarFlags_DockNode = 1 << 20, // Part of a dock node + ImGuiTabBarFlags_DockNodeIsDockSpace = 1 << 21, // Part of an explicit dockspace node node + ImGuiTabBarFlags_IsFocused = 1 << 22, + ImGuiTabBarFlags_SaveSettings = 1 << 23 // FIXME: Settings are handled by the docking system, this only request the tab bar to mark settings dirty when reordering tabs +}; + +enum ImGuiTabItemFlagsPrivate_ +{ + ImGuiTabItemFlags_DockedWindow = 1 << 20, // [Docking] + ImGuiTabItemFlags_Unsorted = 1 << 22, // [Docking] Trailing tabs with the _Unsorted flag will be sorted based on the DockOrder of their Window. + ImGuiTabItemFlags_Preview = 1 << 21 // [Docking] Display tab shape for docking preview (height is adjusted slightly to compensate for the yet missing tab bar) +}; + +// Storage for one active tab item (sizeof() 32~40 bytes) +struct ImGuiTabItem +{ + ImGuiID ID; + ImGuiTabItemFlags Flags; + ImGuiWindow* Window; // When TabItem is part of a DockNode's TabBar, we hold on to a window. + int LastFrameVisible; + int LastFrameSelected; // This allows us to infer an ordered list of the last activated tabs with little maintenance + float Offset; // Position relative to beginning of tab + float Width; // Width currently displayed + float WidthContents; // Width of actual contents, stored during BeginTabItem() call + + ImGuiTabItem() { ID = Flags = 0; Window = NULL; LastFrameVisible = LastFrameSelected = -1; Offset = Width = WidthContents = 0.0f; } +}; + +// Storage for a tab bar (sizeof() 92~96 bytes) +struct ImGuiTabBar +{ + ImVector Tabs; + ImGuiID ID; // Zero for tab-bars used by docking + ImGuiID SelectedTabId; // Selected tab + ImGuiID NextSelectedTabId; + ImGuiID VisibleTabId; // Can occasionally be != SelectedTabId (e.g. when previewing contents for CTRL+TAB preview) + ImGuiID WantFocusTabId; // Request focus for the window associated to this tab. Used and only honored by DockNode (meaningless for standalone tab bars) + int CurrFrameVisible; + int PrevFrameVisible; + ImRect BarRect; + float ContentsHeight; + float OffsetMax; // Distance from BarRect.Min.x, locked during layout + float OffsetNextTab; // Distance from BarRect.Min.x, incremented with each BeginTabItem() call, not used if ImGuiTabBarFlags_Reorderable if set. + float ScrollingAnim; + float ScrollingTarget; + ImGuiTabBarFlags Flags; + ImGuiID ReorderRequestTabId; + int ReorderRequestDir; + bool WantLayout; + bool VisibleTabWasSubmitted; + short LastTabItemIdx; // For BeginTabItem()/EndTabItem() + + ImGuiTabBar(); + int GetTabOrder(const ImGuiTabItem* tab) const { return Tabs.index_from_pointer(tab); } +}; + //----------------------------------------------------------------------------- // Internal API // No guarantee of forward compatibility here. @@ -1111,11 +1366,13 @@ namespace ImGui // - You are calling ImGui functions after ImGui::EndFrame()/ImGui::Render() and before the next ImGui::NewFrame(), which is also illegal. inline ImGuiWindow* GetCurrentWindowRead() { ImGuiContext& g = *GImGui; return g.CurrentWindow; } inline ImGuiWindow* GetCurrentWindow() { ImGuiContext& g = *GImGui; g.CurrentWindow->WriteAccessed = true; return g.CurrentWindow; } + IMGUI_API ImGuiWindow* FindWindowByID(ImGuiID id); IMGUI_API ImGuiWindow* FindWindowByName(const char* name); IMGUI_API void FocusWindow(ImGuiWindow* window); - IMGUI_API void FocusFrontMostActiveWindowIgnoringOne(ImGuiWindow* ignore_window); - IMGUI_API void BringWindowToFront(ImGuiWindow* window); - IMGUI_API void BringWindowToBack(ImGuiWindow* window); + IMGUI_API void FocusPreviousWindowIgnoringOne(ImGuiWindow* ignore_window); + IMGUI_API void BringWindowToFocusFront(ImGuiWindow* window); + IMGUI_API void BringWindowToDisplayFront(ImGuiWindow* window); + IMGUI_API void BringWindowToDisplayBack(ImGuiWindow* window); IMGUI_API void UpdateWindowParentAndRootLinks(ImGuiWindow* window, ImGuiWindowFlags flags, ImGuiWindow* parent_window); IMGUI_API ImVec2 CalcWindowExpectedSize(ImGuiWindow* window); IMGUI_API bool IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent); @@ -1123,8 +1380,11 @@ namespace ImGui IMGUI_API void SetWindowScrollX(ImGuiWindow* window, float new_scroll_x); IMGUI_API void SetWindowScrollY(ImGuiWindow* window, float new_scroll_y); IMGUI_API ImRect GetWindowAllowedExtentRect(ImGuiWindow* window); + IMGUI_API void SetCurrentFont(ImFont* font); inline ImFont* GetDefaultFont() { ImGuiContext& g = *GImGui; return g.IO.FontDefault ? g.IO.FontDefault : g.IO.Fonts->Fonts[0]; } + IMGUI_API ImDrawList* GetOverlayDrawList(ImGuiViewportP* viewport); + inline ImDrawList* GetOverlayDrawList(ImGuiWindow* window) { return GetOverlayDrawList(window->Viewport); } // Init IMGUI_API void Initialize(ImGuiContext* context); @@ -1135,11 +1395,18 @@ namespace ImGui IMGUI_API void StartMouseMovingWindow(ImGuiWindow* window); IMGUI_API void UpdateMouseMovingWindow(); + // Viewports + IMGUI_API ImGuiViewportP* FindViewportByID(ImGuiID id); + IMGUI_API void ScaleWindowsInViewport(ImGuiViewportP* viewport, float scale); + IMGUI_API void DestroyPlatformWindow(ImGuiViewportP* viewport); + IMGUI_API void ShowViewportThumbnails(); + // Settings IMGUI_API void MarkIniSettingsDirty(); IMGUI_API void MarkIniSettingsDirty(ImGuiWindow* window); IMGUI_API ImGuiWindowSettings* CreateNewWindowSettings(const char* name); IMGUI_API ImGuiWindowSettings* FindWindowSettings(ImGuiID id); + IMGUI_API ImGuiWindowSettings* FindOrCreateWindowSettings(const char* name); IMGUI_API ImGuiSettingsHandler* FindSettingsHandler(const char* type_name); // Basic Accessors @@ -1199,6 +1466,36 @@ namespace ImGui inline bool IsNavInputPressed(ImGuiNavInput n, ImGuiInputReadMode mode) { return GetNavInputAmount(n, mode) > 0.0f; } inline bool IsNavInputPressedAnyOfTwo(ImGuiNavInput n1, ImGuiNavInput n2, ImGuiInputReadMode mode) { return (GetNavInputAmount(n1, mode) + GetNavInputAmount(n2, mode)) > 0.0f; } + // Docking + IMGUI_API void DockContextInitialize(ImGuiContext* ctx); + IMGUI_API void DockContextShutdown(ImGuiContext* ctx); + IMGUI_API void DockContextOnLoadSettings(ImGuiContext* ctx); + IMGUI_API void DockContextRebuild(ImGuiContext* ctx); + IMGUI_API void DockContextNewFrameUpdateUndocking(ImGuiContext* ctx); + IMGUI_API void DockContextNewFrameUpdateDocking(ImGuiContext* ctx); + IMGUI_API void DockContextEndFrame(ImGuiContext* ctx); + IMGUI_API void DockContextQueueUndockWindow(ImGuiContext* ctx, ImGuiWindow* window); + IMGUI_API void DockContextQueueUndockNode(ImGuiContext* ctx, ImGuiDockNode* node); + inline ImGuiDockNode* DockNodeGetRootNode(ImGuiDockNode* node) { while (node->ParentNode) node = node->ParentNode; return node; } + IMGUI_API void BeginDocked(ImGuiWindow* window, bool* p_open); + IMGUI_API void BeginAsDockableDragDropSource(ImGuiWindow* window); + IMGUI_API void BeginAsDockableDragDropTarget(ImGuiWindow* window); + IMGUI_API void SetWindowDock(ImGuiWindow* window, ImGuiID dock_id, ImGuiCond cond); + IMGUI_API void ShowDockingDebug(); + + // Docking - Builder function needs to be generally called before the DockSpace() node is submitted. + IMGUI_API void DockBuilderDockWindow(ImGuiContext* ctx, const char* window_name, ImGuiID node_id); + IMGUI_API ImGuiDockNode*DockBuilderGetNode(ImGuiContext* ctx, ImGuiID node_id); + IMGUI_API void DockBuilderAddNode(ImGuiContext* ctx, ImGuiID node_id, ImVec2 ref_size, ImGuiDockNodeFlags flags = 0); + IMGUI_API void DockBuilderRemoveNode(ImGuiContext* ctx, ImGuiID node_id); // Remove node and all its child, undock all windows + IMGUI_API void DockBuilderRemoveNodeDockedWindows(ImGuiContext* ctx, ImGuiID node_id, bool clear_persistent_docking_references = true); + IMGUI_API void DockBuilderRemoveNodeChildNodes(ImGuiContext* ctx, ImGuiID node_id); // Remove all split/hierarchy. All remaining docked windows will be re-docked to the root. + IMGUI_API ImGuiID DockBuilderSplitNode(ImGuiContext* ctx, ImGuiID node_id, ImGuiDir split_dir, float size_ratio_for_node_at_dir, ImGuiID* out_id_dir, ImGuiID* out_id_other); + IMGUI_API void DockBuilderCopyDockspace(ImGuiContext* ctx, ImGuiID src_dockspace_id, ImGuiID dst_dockspace_id, ImVector* in_window_remap_pairs); + IMGUI_API void DockBuilderCopyNode(ImGuiContext* ctx, ImGuiID src_node_id, ImGuiID dst_node_id, ImVector* out_node_remap_pairs); + IMGUI_API void DockBuilderCopyWindowSettings(ImGuiContext* ctx, const char* src_name, const char* dst_name); + IMGUI_API void DockBuilderFinish(ImGuiContext* ctx, ImGuiID node_id); + // Drag and Drop IMGUI_API bool BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id); IMGUI_API void ClearDragDrop(); @@ -1209,12 +1506,25 @@ namespace ImGui IMGUI_API void EndColumns(); // close columns IMGUI_API void PushColumnClipRect(int column_index = -1); + // Tab Bars + IMGUI_API bool BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& bb, ImGuiTabBarFlags flags, ImGuiDockNode* dock_node); + IMGUI_API ImGuiTabItem* TabBarFindTabByID(ImGuiTabBar* tab_bar, ImGuiID tab_id); + IMGUI_API void TabBarAddTab(ImGuiTabBar* tab_bar, ImGuiWindow* window, ImGuiTabItemFlags tab_flags = ImGuiTabItemFlags_None); + IMGUI_API void TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id); + IMGUI_API void TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab); + IMGUI_API void TabBarQueueChangeTabOrder(ImGuiTabBar* tab_bar, const ImGuiTabItem* tab, int dir); + IMGUI_API bool TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags, ImGuiWindow* docked_window); + IMGUI_API ImVec2 TabItemCalcSize(const char* label, bool has_close_button); + IMGUI_API void TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImU32 col); + IMGUI_API bool TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, const char* label, ImGuiID tab_id, ImGuiID close_button_id); + // Render helpers // AVOID USING OUTSIDE OF IMGUI.CPP! NOT FOR PUBLIC CONSUMPTION. THOSE FUNCTIONS ARE A MESS. THEIR SIGNATURE AND BEHAVIOR WILL CHANGE, THEY NEED TO BE REFACTORED INTO SOMETHING DECENT. // NB: All position are in absolute pixels coordinates (we are never using window coordinates internally) IMGUI_API void RenderText(ImVec2 pos, const char* text, const char* text_end = NULL, bool hide_text_after_hash = true); IMGUI_API void RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width); IMGUI_API void RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align = ImVec2(0,0), const ImRect* clip_rect = NULL); + IMGUI_API void RenderTextClippedEx(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align = ImVec2(0, 0), const ImRect* clip_rect = NULL); IMGUI_API void RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border = true, float rounding = 0.0f); IMGUI_API void RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding = 0.0f); IMGUI_API void RenderColorRectWithAlphaCheckerboard(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, float grid_step, ImVec2 grid_off, float rounding = 0.0f, int rounding_corners_flags = ~0); @@ -1222,18 +1532,21 @@ namespace ImGui IMGUI_API void RenderBullet(ImVec2 pos); IMGUI_API void RenderCheckMark(ImVec2 pos, ImU32 col, float sz); IMGUI_API void RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFlags flags = ImGuiNavHighlightFlags_TypeDefault); // Navigation highlight + IMGUI_API void RenderMouseCursor(ImVec2 pos, float scale, ImGuiMouseCursor mouse_cursor = ImGuiMouseCursor_Arrow); IMGUI_API const char* FindRenderedTextEnd(const char* text, const char* text_end = NULL); // Find the optional ## from which we stop displaying text. IMGUI_API void LogRenderedText(const ImVec2* ref_pos, const char* text, const char* text_end = NULL); // Render helpers (those functions don't access any ImGui state!) - IMGUI_API void RenderMouseCursor(ImDrawList* draw_list, ImVec2 pos, float scale, ImGuiMouseCursor mouse_cursor = ImGuiMouseCursor_Arrow); IMGUI_API void RenderArrowPointingAt(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, ImGuiDir direction, ImU32 col); + IMGUI_API void RenderArrowDockMenu(ImDrawList* draw_list, ImVec2 p_min, float sz, ImU32 col); IMGUI_API void RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, ImU32 col, float x_start_norm, float x_end_norm, float rounding); + IMGUI_API void RenderRectFilledWithHole(ImDrawList* draw_list, ImRect outer, ImRect inner, ImU32 col, float rounding); + IMGUI_API void RenderPixelEllipsis(ImDrawList* draw_list, ImFont* font, ImVec2 pos, int count, ImU32 col); // Widgets IMGUI_API bool ButtonEx(const char* label, const ImVec2& size_arg = ImVec2(0,0), ImGuiButtonFlags flags = 0); IMGUI_API bool CloseButton(ImGuiID id, const ImVec2& pos, float radius); - IMGUI_API bool CollapseButton(ImGuiID id, const ImVec2& pos); + IMGUI_API bool CollapseButton(ImGuiID id, const ImVec2& pos, ImGuiDockNode* dock_node); IMGUI_API bool ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size_arg, ImGuiButtonFlags flags); IMGUI_API void Scrollbar(ImGuiLayoutType direction); IMGUI_API void VerticalSeparator(); // Vertical separator, for menu bars (use current line height). Not exposed because it is misleading and it doesn't have an effect on regular layout. diff --git a/Source Code/Libs/imgui/imgui_widgets.cpp b/Source Code/Libs/imgui/imgui_widgets.cpp index de04a4f2b4..1adda5e987 100644 --- a/Source Code/Libs/imgui/imgui_widgets.cpp +++ b/Source Code/Libs/imgui/imgui_widgets.cpp @@ -24,6 +24,8 @@ Index of this file: // [SECTION] Widgets: PlotLines, PlotHistogram // [SECTION] Widgets: Value helpers // [SECTION] Widgets: MenuItem, BeginMenu, EndMenu, etc. +// [SECTION] Widgets: BeginTabBar, EndTabBar, etc. +// [SECTION] Widgets: BeginTabItem, EndTabItem, etc. */ @@ -628,7 +630,7 @@ bool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiBu const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); RenderNavHighlight(bb, id); RenderFrame(bb.Min, bb.Max, col, true, g.Style.FrameRounding); - RenderArrow(bb.Min + ImVec2(ImMax(0.0f, size.x - g.FontSize - g.Style.FramePadding.x), ImMax(0.0f, size.y - g.FontSize - g.Style.FramePadding.y)), dir); + RenderArrow(bb.Min + ImVec2(ImMax(0.0f, (size.x - g.FontSize) * 0.5f), ImMax(0.0f, (size.y - g.FontSize) * 0.5f)), dir); return pressed; } @@ -669,7 +671,7 @@ bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos, float radius) return pressed; } -bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos) +bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos, ImGuiDockNode* dock_node) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; @@ -679,14 +681,36 @@ bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos) bool hovered, held; bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None); + //bool is_dock_menu = (window->DockNodeAsHost && !window->Collapsed); + ImVec2 off = dock_node ? ImVec2((float)(int)(-g.Style.ItemInnerSpacing.x * 0.5f) + 0.5f, 0.0f) : ImVec2(0.0f, 0.0f); ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); if (hovered || held) - window->DrawList->AddCircleFilled(bb.GetCenter() + ImVec2(0.0f, -0.5f), g.FontSize * 0.5f + 1.0f, col, 9); + window->DrawList->AddCircleFilled(bb.GetCenter() + off + ImVec2(0,-0.5f), g.FontSize * 0.5f + 1.0f, col, 9); + + if (dock_node) + RenderArrowDockMenu(window->DrawList, bb.Min + g.Style.FramePadding, g.FontSize, GetColorU32(ImGuiCol_Text)); + else RenderArrow(bb.Min + g.Style.FramePadding, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f); // Switch to moving the window after mouse is moved beyond the initial drag threshold - if (IsItemActive() && IsMouseDragging()) + if (IsItemActive() && IsMouseDragging(0)) + { + if (dock_node != NULL && DockNodeGetRootNode(dock_node)->OnlyNodeWithWindows != dock_node) + { + float threshold_base = g.FontSize; + float threshold_x = (threshold_base * 2.2f); + float threshold_y = (threshold_base * 1.5f); + IM_ASSERT(window->DockNodeAsHost != NULL); + if (g.IO.MouseDragMaxDistanceAbs[0].x > threshold_x || g.IO.MouseDragMaxDistanceAbs[0].y > threshold_y) + DockContextQueueUndockNode(&g, dock_node); + } + else + { + ImVec2 backup_active_click_offset = g.ActiveIdClickOffset; StartMouseMovingWindow(window); + g.ActiveIdClickOffset = backup_active_click_offset; + } + } return pressed; } @@ -840,7 +864,7 @@ bool ImGui::ImageButton(ImTextureID user_texture_id, const ImVec2& size, const I // Default to using texture ID as ID. User can still push string/integer prefixes. // We could hash the size/uv to create a unique ID but that would prevent the user from animating UV. - PushID((void*)user_texture_id); + PushID((void*)(intptr_t)user_texture_id); const ImGuiID id = window->GetID("#image"); PopID(); @@ -1240,7 +1264,6 @@ bool ImGui::SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float return held; } - //------------------------------------------------------------------------- // [SECTION] Widgets: Combo Box //------------------------------------------------------------------------- @@ -1710,10 +1733,11 @@ template bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const TYPE v_min, const TYPE v_max, const char* format, float power) { ImGuiContext& g = *GImGui; + const bool is_decimal = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double); + const bool has_min_max = (v_min != v_max); // Default tweak speed - bool has_min_max = (v_min != v_max) && (v_max - v_max < FLT_MAX); - if (v_speed == 0.0f && has_min_max) + if (v_speed == 0.0f && has_min_max && (v_max - v_min < FLT_MAX)) v_speed = (float)((v_max - v_min) * g.DragSpeedDefaultRatio); // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings @@ -1728,7 +1752,7 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const } else if (g.ActiveIdSource == ImGuiInputSource_Nav) { - int decimal_precision = (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) ? ImParseFormatPrecision(format, 3) : 0; + int decimal_precision = is_decimal ? ImParseFormatPrecision(format, 3) : 0; adjust_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard|ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 1.0f/10.0f, 10.0f).x; v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision)); } @@ -1755,7 +1779,7 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const TYPE v_cur = *v; FLOATTYPE v_old_ref_for_accum_remainder = (FLOATTYPE)0.0f; - const bool is_power = (power != 1.0f && (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) && has_min_max); + const bool is_power = (power != 1.0f && is_decimal && has_min_max && (v_max - v_min < FLT_MAX)); if (is_power) { // Offset + round to user desired precision, with a curve on the v_min..v_max range to get more precision on one side of the range @@ -1788,12 +1812,12 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const if (v_cur == (TYPE)-0) v_cur = (TYPE)0; - // Clamp values (handle overflow/wrap-around) + // Clamp values (+ handle overflow/wrap-around for integer types) if (*v != v_cur && has_min_max) { - if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f)) + if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f && !is_decimal)) v_cur = v_min; - if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f)) + if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f && !is_decimal)) v_cur = v_max; } @@ -2614,7 +2638,6 @@ bool ImGui::InputScalarAsWidgetReplacement(const ImRect& bb, ImGuiID id, const c return false; } -// NB: format here must be a simple "%xx" format string with no prefix/suffix (unlike the Drag/Slider functions "format" argument) bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* data_ptr, const void* step, const void* step_fast, const char* format, ImGuiInputTextFlags extra_flags) { ImGuiWindow* window = GetCurrentWindow(); @@ -2845,7 +2868,7 @@ static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* t if (c == '\r') continue; - const float char_width = font->GetCharAdvance((unsigned short)c) * scale; + const float char_width = font->GetCharAdvance((ImWchar)c) * scale; line_width += char_width; } @@ -3698,7 +3721,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 else { ImVec2 rect_size = InputTextCalcTextSizeW(p, text_selected_end, &p, NULL, true); - if (rect_size.x <= 0.0f) rect_size.x = (float)(int)(g.Font->GetCharAdvance((unsigned short)' ') * 0.50f); // So we can see selected empty lines + if (rect_size.x <= 0.0f) rect_size.x = (float)(int)(g.Font->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos +ImVec2(rect_size.x, bg_offy_dn)); rect.ClipWith(clip_rect); if (rect.Overlaps(clip_rect)) @@ -3722,7 +3745,10 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 // Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.) if (is_editable) + { g.PlatformImePos = ImVec2(cursor_screen_pos.x - 1, cursor_screen_pos.y - g.FontSize); + g.PlatformImePosViewport = window->Viewport; + } } else { @@ -3923,7 +3949,7 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag if (label != label_display_end) { TextUnformatted(label, label_display_end); - Separator(); + Spacing(); } ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar; ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf; @@ -5427,11 +5453,11 @@ bool ImGui::BeginMainMenuBar() { ImGuiContext& g = *GImGui; g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f)); - SetNextWindowPos(ImVec2(0.0f, 0.0f)); - SetNextWindowSize(ImVec2(g.IO.DisplaySize.x, g.NextWindowData.MenuBarOffsetMinVal.y + g.FontBaseSize + g.Style.FramePadding.y)); + SetNextWindowPos(g.Viewports[0]->Pos); + SetNextWindowSize(ImVec2(g.Viewports[0]->Size.x, g.NextWindowData.MenuBarOffsetMinVal.y + g.FontBaseSize + g.Style.FramePadding.y)); PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0,0)); - ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar; + ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar; bool is_open = Begin("##MainMenuBar", NULL, window_flags) && BeginMenuBar(); PopStyleVar(2); g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f); @@ -5450,7 +5476,7 @@ void ImGui::EndMainMenuBar() // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window ImGuiContext& g = *GImGui; if (g.CurrentWindow == g.NavWindow && g.NavLayer == 0) - FocusFrontMostActiveWindowIgnoringOne(g.NavWindow); + FocusPreviousWindowIgnoringOne(g.NavWindow); End(); } @@ -5549,7 +5575,7 @@ bool ImGui::BeginMenu(const char* label, bool enabled) // Menu inside an horizontal menu bar // Selectable extend their highlight by half ItemSpacing in each direction. // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin() - popup_pos = ImVec2(pos.x - window->WindowPadding.x, pos.y - style.FramePadding.y + window->MenuBarHeight()); + popup_pos = ImVec2(pos.x - 1.0f - (float)(int)(style.ItemSpacing.x * 0.5f), pos.y - style.FramePadding.y + window->MenuBarHeight()); window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f); PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f); float w = label_size.x; @@ -5726,3 +5752,914 @@ bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, return false; } +//------------------------------------------------------------------------- +// [SECTION] Widgets: BeginTabBar, EndTabBar, etc. +//------------------------------------------------------------------------- +// - BeginTabBar() +// - BeginTabBarEx() [Internal] +// - EndTabBar() +// - TabBarLayout() [Internal] +// - TabBarCalcTabID() [Internal] +// - TabBarCalcMaxTabWidth() [Internal] +// - TabBarFindTabById() [Internal] +// - TabBarAddTab() [Internal] +// - TabBarRemoveTab() [Internal] +// - TabBarCloseTab() [Internal] +// - TabBarScrollClamp()v +// - TabBarScrollToTab() [Internal] +// - TabBarQueueChangeTabOrder() [Internal] +// - TabBarScrollingButtons() [Internal] +// - TabBarTabListPopupButton() [Internal] +//------------------------------------------------------------------------- + +namespace ImGui +{ + static void TabBarLayout(ImGuiTabBar* tab_bar); + static ImU32 TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label); + static float TabBarCalcMaxTabWidth(); + static float TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling); + static void TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab); + static ImGuiTabItem* TabBarScrollingButtons(ImGuiTabBar* tab_bar); + static ImGuiTabItem* TabBarTabListPopupButton(ImGuiTabBar* tab_bar); +} + +ImGuiTabBar::ImGuiTabBar() +{ + ID = 0; + SelectedTabId = NextSelectedTabId = VisibleTabId = WantFocusTabId = 0; + CurrFrameVisible = PrevFrameVisible = -1; + OffsetMax = OffsetNextTab = 0.0f; + ScrollingAnim = ScrollingTarget = 0.0f; + Flags = ImGuiTabBarFlags_None; + ReorderRequestTabId = 0; + ReorderRequestDir = 0; + WantLayout = VisibleTabWasSubmitted = false; + LastTabItemIdx = -1; +} + +static int IMGUI_CDECL TabItemComparerByVisibleOffset(const void* lhs, const void* rhs) +{ + const ImGuiTabItem* a = (const ImGuiTabItem*)lhs; + const ImGuiTabItem* b = (const ImGuiTabItem*)rhs; + return (int)(a->Offset - b->Offset); +} + +static int IMGUI_CDECL TabBarSortItemComparer(const void* lhs, const void* rhs) +{ + const ImGuiTabBarSortItem* a = (const ImGuiTabBarSortItem*)lhs; + const ImGuiTabBarSortItem* b = (const ImGuiTabBarSortItem*)rhs; + if (int d = (int)(b->Width - a->Width)) + return d; + return (b->Index - a->Index); +} + +bool ImGui::BeginTabBar(const char* str_id, ImGuiTabBarFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return false; + + ImGuiID id = window->GetID(str_id); + ImGuiTabBar* tab_bar = g.TabBars.GetOrAddByKey(id); + ImRect tab_bar_bb = ImRect(window->DC.CursorPos.x, window->DC.CursorPos.y, window->InnerClipRect.Max.x, window->DC.CursorPos.y + g.FontSize + g.Style.FramePadding.y * 2); + tab_bar->ID = id; + return BeginTabBarEx(tab_bar, tab_bar_bb, flags | ImGuiTabBarFlags_IsFocused, NULL); +} + +bool ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImGuiTabBarFlags flags, ImGuiDockNode* dock_node) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return false; + + if ((flags & ImGuiTabBarFlags_DockNode) == 0) + window->IDStack.push_back(tab_bar->ID); + + g.CurrentTabBar.push_back(tab_bar); + if (tab_bar->CurrFrameVisible == g.FrameCount) + { + printf("[%05d] BeginTabBarEx already called this frame\n", g.FrameCount); + //IM_ASSERT(0); + return true; + } + + // When toggling back from ordered to manually-reorderable, shuffle tabs to enforce the last visible order. + // Otherwise, the most recently inserted tabs would move at the end of visible list which can be a little too confusing or magic for the user. + if ((flags & ImGuiTabBarFlags_Reorderable) && !(tab_bar->Flags & ImGuiTabBarFlags_Reorderable) && tab_bar->Tabs.Size > 1 && tab_bar->PrevFrameVisible != -1) + ImQsort(tab_bar->Tabs.Data, tab_bar->Tabs.Size, sizeof(ImGuiTabItem), TabItemComparerByVisibleOffset); + + // Flags + if ((flags & ImGuiTabBarFlags_FittingPolicyMask_) == 0) + flags |= ImGuiTabBarFlags_FittingPolicyDefault_; + + tab_bar->Flags = flags; + tab_bar->BarRect = tab_bar_bb; + tab_bar->WantLayout = true; // Layout will be done on the first call to ItemTab() + tab_bar->PrevFrameVisible = tab_bar->CurrFrameVisible; + tab_bar->CurrFrameVisible = g.FrameCount; + + // Layout + ItemSize(ImVec2(tab_bar->OffsetMax, tab_bar->BarRect.GetHeight())); + window->DC.CursorPos.x = tab_bar->BarRect.Min.x; + + // Draw separator + const ImU32 col = GetColorU32((flags & ImGuiTabBarFlags_IsFocused) ? ImGuiCol_TabActive : ImGuiCol_Tab); + const float y = tab_bar->BarRect.Max.y - 1.0f; + if (dock_node != NULL) + { + const float separator_min_x = dock_node->Pos.x; + const float separator_max_x = dock_node->Pos.x + dock_node->Size.x; + window->DrawList->AddLine(ImVec2(separator_min_x, y), ImVec2(separator_max_x, y), col, 1.0f); + } + else + { + const float separator_min_x = tab_bar->BarRect.Min.x - ((flags & ImGuiTabBarFlags_DockNodeIsDockSpace) ? 0.0f : window->WindowPadding.x); + const float separator_max_x = tab_bar->BarRect.Max.x + ((flags & ImGuiTabBarFlags_DockNodeIsDockSpace) ? 0.0f : window->WindowPadding.x); + window->DrawList->AddLine(ImVec2(separator_min_x, y), ImVec2(separator_max_x, y), col, 1.0f); + } + return true; +} + +void ImGui::EndTabBar() +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return; + + IM_ASSERT(!g.CurrentTabBar.empty()); // Mismatched BeginTabBar/EndTabBar + ImGuiTabBar* tab_bar = g.CurrentTabBar.back(); + if (tab_bar->WantLayout) + TabBarLayout(tab_bar); + + // Restore the last visible height if no tab is visible, this reduce vertical flicker/movement when a tabs gets removed without calling SetTabItemClosed(). + const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount); + if (tab_bar->VisibleTabWasSubmitted || tab_bar->VisibleTabId == 0 || tab_bar_appearing) + tab_bar->ContentsHeight = ImMax(window->DC.CursorPos.y - tab_bar->BarRect.Max.y, 0.0f); + else + window->DC.CursorPos.y = tab_bar->BarRect.Max.y + tab_bar->ContentsHeight; + + if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0) + PopID(); + g.CurrentTabBar.pop_back(); +} + +// This is called only once a frame before by the first call to ItemTab() +// The reason we're not calling it in BeginTabBar() is to leave a chance to the user to call the SetTabItemClosed() functions. +static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar) +{ + ImGuiContext& g = *GImGui; + tab_bar->WantLayout = false; + + // Garbage collect + int tab_dst_n = 0; + for (int tab_src_n = 0; tab_src_n < tab_bar->Tabs.Size; tab_src_n++) + { + ImGuiTabItem* tab = &tab_bar->Tabs[tab_src_n]; + if (tab->LastFrameVisible < tab_bar->PrevFrameVisible) + { + if (tab->ID == tab_bar->SelectedTabId) + tab_bar->SelectedTabId = 0; + continue; + } + if (tab_dst_n != tab_src_n) + tab_bar->Tabs[tab_dst_n] = tab_bar->Tabs[tab_src_n]; + tab_dst_n++; + } + if (tab_bar->Tabs.Size != tab_dst_n) + tab_bar->Tabs.resize(tab_dst_n); + + // Setup next selected tab + ImGuiID scroll_track_selected_tab_id = 0; + tab_bar->WantFocusTabId = 0; + if (tab_bar->NextSelectedTabId) + { + tab_bar->SelectedTabId = tab_bar->NextSelectedTabId; + tab_bar->NextSelectedTabId = 0; + scroll_track_selected_tab_id = tab_bar->SelectedTabId; + } + + // Process order change request (we could probably process it when requested but it's just saner to do it in a single spot). + if (tab_bar->ReorderRequestTabId != 0) + { + if (ImGuiTabItem* tab1 = TabBarFindTabByID(tab_bar, tab_bar->ReorderRequestTabId)) + { + IM_ASSERT(tab_bar->Flags & ImGuiTabBarFlags_Reorderable); + int tab2_order = tab_bar->GetTabOrder(tab1) + tab_bar->ReorderRequestDir; + if (tab2_order >= 0 && tab2_order < tab_bar->Tabs.Size) + { + ImGuiTabItem* tab2 = &tab_bar->Tabs[tab2_order]; + ImGuiTabItem item_tmp = *tab1; + *tab1 = *tab2; + *tab2 = item_tmp; + if (tab2->ID == tab_bar->SelectedTabId) + scroll_track_selected_tab_id = tab2->ID; + tab1 = tab2 = NULL; + } + if (tab_bar->Flags & ImGuiTabBarFlags_SaveSettings) + MarkIniSettingsDirty(); + } + tab_bar->ReorderRequestTabId = 0; + } + + ImVector& width_sort_buffer = g.TabSortByWidthBuffer; + width_sort_buffer.resize(tab_bar->Tabs.Size); + + // Compute ideal widths + float width_total_contents = 0.0f; + ImGuiTabItem* most_recently_selected_tab = NULL; + bool found_selected_tab_id = false; + for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++) + { + ImGuiTabItem* tab = &tab_bar->Tabs[tab_n]; + IM_ASSERT(tab->LastFrameVisible >= tab_bar->PrevFrameVisible); + + if (most_recently_selected_tab == NULL || most_recently_selected_tab->LastFrameSelected < tab->LastFrameSelected) + most_recently_selected_tab = tab; + if (tab->ID == tab_bar->SelectedTabId) + found_selected_tab_id = true; + + // Refresh tab width immediately if we can (for manual tab bar, WidthContent will lag by one frame which is mostly noticeable when changing style.FramePadding.x) + // Additionally, when using TabBarAddTab() to manipulate tab bar order we occasionally insert new tabs that don't have a width yet, + // and we cannot wait for the next BeginTabItem() call. We cannot compute this width within TabBarAddTab() because font size depends on the active window. + if (tab->Window) + tab->WidthContents = TabItemCalcSize(tab->Window->Name, tab->Window->HasCloseButton).x; + width_total_contents += (tab_n > 0 ? g.Style.ItemInnerSpacing.x : 0.0f) + tab->WidthContents; + + // Store data so we can build an array sorted by width if we need to shrink tabs down + width_sort_buffer[tab_n].Index = tab_n; + width_sort_buffer[tab_n].Width = tab->WidthContents; + } + + // Compute width + const float width_avail = tab_bar->BarRect.GetWidth(); + float width_excess = (width_avail < width_total_contents) ? (width_total_contents - width_avail) : 0.0f; + if (width_excess > 0.0f && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown)) + { + // If we don't have enough room, resize down the largest tabs first + if (tab_bar->Tabs.Size > 1) + ImQsort(width_sort_buffer.Data, (size_t)width_sort_buffer.Size, sizeof(ImGuiTabBarSortItem), TabBarSortItemComparer); + int tab_count_same_width = 1; + while (width_excess > 0.0f && tab_count_same_width < tab_bar->Tabs.Size) + { + while (tab_count_same_width < tab_bar->Tabs.Size && width_sort_buffer[0].Width == width_sort_buffer[tab_count_same_width].Width) + tab_count_same_width++; + float width_to_remove_per_tab_max = (tab_count_same_width < tab_bar->Tabs.Size) ? (width_sort_buffer[0].Width - width_sort_buffer[tab_count_same_width].Width) : (width_sort_buffer[0].Width - 1.0f); + float width_to_remove_per_tab = ImMin(width_excess / tab_count_same_width, width_to_remove_per_tab_max); + for (int tab_n = 0; tab_n < tab_count_same_width; tab_n++) + width_sort_buffer[tab_n].Width -= width_to_remove_per_tab; + width_excess -= width_to_remove_per_tab * tab_count_same_width; + } + for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++) + tab_bar->Tabs[width_sort_buffer[tab_n].Index].Width = (float)(int)width_sort_buffer[tab_n].Width; + } + else + { + const float tab_max_width = TabBarCalcMaxTabWidth(); + for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++) + { + ImGuiTabItem* tab = &tab_bar->Tabs[tab_n]; + tab->Width = ImMin(tab->WidthContents, tab_max_width); + } + } + + // Layout all active tabs + float offset_x = 0.0f; + for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++) + { + ImGuiTabItem* tab = &tab_bar->Tabs[tab_n]; + tab->Offset = offset_x; + if (scroll_track_selected_tab_id == 0 && g.NavJustMovedToId == tab->ID) + scroll_track_selected_tab_id = tab->ID; + offset_x += tab->Width + g.Style.ItemInnerSpacing.x; + } + tab_bar->OffsetMax = ImMax(offset_x - g.Style.ItemInnerSpacing.x, 0.0f); + tab_bar->OffsetNextTab = 0.0f; + + // Tab List Popup + const bool tab_list_popup_button = (tab_bar->Flags & ImGuiTabBarFlags_DockNode) != 0 && (tab_bar->Flags & ImGuiTabBarFlags_NoTabListPopupButton) == 0; + if (tab_list_popup_button) + if (ImGuiTabItem* tab_to_select = TabBarTabListPopupButton(tab_bar)) // NB: Will alter BarRect.Max.x! + scroll_track_selected_tab_id = tab_bar->SelectedTabId = tab_to_select->ID; + + // Horizontal scrolling buttons + const bool scrolling_buttons = (tab_bar->OffsetMax > tab_bar->BarRect.GetWidth() && tab_bar->Tabs.Size > 1) && !(tab_bar->Flags & ImGuiTabBarFlags_NoTabListScrollingButtons) && (tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyScroll); + if (scrolling_buttons) + if (ImGuiTabItem* tab_to_select = TabBarScrollingButtons(tab_bar)) // NB: Will alter BarRect.Max.x! + scroll_track_selected_tab_id = tab_bar->SelectedTabId = tab_to_select->ID; + + // If we have lost the selected tab, select the next most recently active one + if (found_selected_tab_id == false) + tab_bar->SelectedTabId = 0; + if (tab_bar->SelectedTabId == 0 && tab_bar->NextSelectedTabId == 0 && most_recently_selected_tab != NULL) + scroll_track_selected_tab_id = tab_bar->SelectedTabId = most_recently_selected_tab->ID; + + // Lock in visible tab + tab_bar->VisibleTabId = tab_bar->SelectedTabId; + tab_bar->VisibleTabWasSubmitted = false; + + // CTRL+TAB can override visible tab temporarily + if (g.NavWindowingTarget != NULL && g.NavWindowingTarget->DockNode && g.NavWindowingTarget->DockNode->TabBar == tab_bar) + tab_bar->VisibleTabId = scroll_track_selected_tab_id = g.NavWindowingTarget->ID; + + // Update scrolling + if (scroll_track_selected_tab_id) + if (ImGuiTabItem* scroll_track_selected_tab = TabBarFindTabByID(tab_bar, scroll_track_selected_tab_id)) + TabBarScrollToTab(tab_bar, scroll_track_selected_tab); + tab_bar->ScrollingAnim = TabBarScrollClamp(tab_bar, tab_bar->ScrollingAnim); + tab_bar->ScrollingTarget = TabBarScrollClamp(tab_bar, tab_bar->ScrollingTarget); + const float scrolling_speed = (tab_bar->PrevFrameVisible + 1 < g.FrameCount) ? FLT_MAX : (g.IO.DeltaTime * g.FontSize * 70.0f); + if (tab_bar->ScrollingAnim != tab_bar->ScrollingTarget) + tab_bar->ScrollingAnim = ImLinearSweep(tab_bar->ScrollingAnim, tab_bar->ScrollingTarget, scrolling_speed); +} + +// Dockables uses Name/ID in the global namespace. Non-dockable items use the ID stack. +static ImU32 ImGui::TabBarCalcTabID(ImGuiTabBar* tab_bar, const char* label) +{ + if (tab_bar->Flags & ImGuiTabBarFlags_DockNode) + { + ImGuiID id = ImHash(label, 0); + KeepAliveID(id); + return id; + } + else + { + ImGuiWindow* window = GImGui->CurrentWindow; + return window->GetID(label); + } +} + +static float ImGui::TabBarCalcMaxTabWidth() +{ + ImGuiContext& g = *GImGui; + return g.FontSize * 20.0f; +} + +ImGuiTabItem* ImGui::TabBarFindTabByID(ImGuiTabBar* tab_bar, ImGuiID tab_id) +{ + if (tab_id != 0) + for (int n = 0; n < tab_bar->Tabs.Size; n++) + if (tab_bar->Tabs[n].ID == tab_id) + return &tab_bar->Tabs[n]; + return NULL; +} + +// The purpose of this call is to register tab in advance so we can control their order at the time they appear. +// Otherwise calling this is unnecessary as tabs are appending as needed by the BeginTabItem() function. +void ImGui::TabBarAddTab(ImGuiTabBar* tab_bar, ImGuiWindow* window, ImGuiTabItemFlags tab_flags) +{ + ImGuiContext& g = *GImGui; + IM_ASSERT(TabBarFindTabByID(tab_bar, window->ID) == NULL); + IM_ASSERT(g.CurrentTabBar.empty()); // Can't work while the tab bar is active as our tab doesn't have an X offset yet + + ImGuiTabItem new_tab; + new_tab.ID = window->ID; + new_tab.Flags = tab_flags; + new_tab.LastFrameVisible = tab_bar->CurrFrameVisible; // Required so BeginTabBar() doesn't ditch the tab + if (new_tab.LastFrameVisible == -1) + new_tab.LastFrameVisible = g.FrameCount - 1; + new_tab.Window = window; // Required so tab bar layout can compute the tab width before tab submission + tab_bar->Tabs.push_back(new_tab); +} + +// The *TabId fields be already set by the docking system _before_ the actual TabItem was created, so we clear them regardless. +void ImGui::TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id) +{ + if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id)) + tab_bar->Tabs.erase(tab); + if (tab_bar->VisibleTabId == tab_id) { tab_bar->VisibleTabId = 0; } + if (tab_bar->WantFocusTabId == tab_id) { tab_bar->WantFocusTabId = 0; } + if (tab_bar->SelectedTabId == tab_id) { tab_bar->SelectedTabId = 0; } + if (tab_bar->NextSelectedTabId == tab_id) { tab_bar->NextSelectedTabId = 0; } +} + +// Called on manual closure attempt +void ImGui::TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) +{ + if ((tab_bar->VisibleTabId == tab->ID) && !(tab->Flags & ImGuiTabItemFlags_UnsavedDocument)) + { + // This will remove a frame of lag for selecting another tab on closure. + // However we don't run it in the case where the 'Unsaved' flag is set, so user gets a chance to fully undo the closure + tab->LastFrameVisible = -1; + tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = 0; + } + else if ((tab_bar->VisibleTabId != tab->ID) && (tab->Flags & ImGuiTabItemFlags_UnsavedDocument)) + { + // Actually select before expecting closure + tab_bar->NextSelectedTabId = tab->ID; + } +} + +static float ImGui::TabBarScrollClamp(ImGuiTabBar* tab_bar, float scrolling) +{ + scrolling = ImMin(scrolling, tab_bar->OffsetMax - tab_bar->BarRect.GetWidth()); + return ImMax(scrolling, 0.0f); +} + +static void ImGui::TabBarScrollToTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab) +{ + ImGuiContext& g = *GImGui; + float margin = g.FontSize * 1.0f; // When to scroll to make Tab N+1 visible always make a bit of N visible to suggest more scrolling area (since we don't have a scrollbar) + int order = tab_bar->GetTabOrder(tab); + float tab_x1 = tab->Offset + (order > 0 ? -margin : 0.0f); + float tab_x2 = tab->Offset + tab->Width + (order + 1 < tab_bar->Tabs.Size ? margin : 1.0f); + if (tab_bar->ScrollingTarget > tab_x1) + tab_bar->ScrollingTarget = tab_x1; + if (tab_bar->ScrollingTarget + tab_bar->BarRect.GetWidth() < tab_x2) + tab_bar->ScrollingTarget = tab_x2 - tab_bar->BarRect.GetWidth(); +} + +void ImGui::TabBarQueueChangeTabOrder(ImGuiTabBar* tab_bar, const ImGuiTabItem* tab, int dir) +{ + IM_ASSERT(dir == -1 || dir == +1); + IM_ASSERT(tab_bar->ReorderRequestTabId == 0); + tab_bar->ReorderRequestTabId = tab->ID; + tab_bar->ReorderRequestDir = dir; +} + +static ImGuiTabItem* ImGui::TabBarScrollingButtons(ImGuiTabBar* tab_bar) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + const ImVec2 arrow_button_size(g.FontSize - 2.0f, g.FontSize + g.Style.FramePadding.y * 2.0f); + const float scrolling_buttons_width = arrow_button_size.x * 2.0f; + + const ImVec2 backup_cursor_pos = window->DC.CursorPos; + //window->DrawList->AddRect(ImVec2(tab_bar->BarRect.Max.x - scrolling_buttons_width, tab_bar->BarRect.Min.y), ImVec2(tab_bar->BarRect.Max.x, tab_bar->BarRect.Max.y), IM_COL32(255,0,0,255)); + + const ImRect avail_bar_rect = tab_bar->BarRect; + bool want_clip_rect = !avail_bar_rect.Contains(ImRect(window->DC.CursorPos, window->DC.CursorPos + ImVec2(scrolling_buttons_width, 0.0f))); + if (want_clip_rect) + PushClipRect(tab_bar->BarRect.Min, tab_bar->BarRect.Max + ImVec2(g.Style.ItemInnerSpacing.x, 0.0f), true); + + ImGuiTabItem* tab_to_select = NULL; + + int select_dir = 0; + ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text]; + arrow_col.w *= 0.5f; + + PushStyleColor(ImGuiCol_Text, arrow_col); + PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); + const float backup_repeat_delay = g.IO.KeyRepeatDelay; + const float backup_repeat_rate = g.IO.KeyRepeatRate; + g.IO.KeyRepeatDelay = 0.250f; + g.IO.KeyRepeatRate = 0.200f; + window->DC.CursorPos = ImVec2(tab_bar->BarRect.Max.x - scrolling_buttons_width, tab_bar->BarRect.Min.y); + if (ArrowButtonEx("##<", ImGuiDir_Left, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat)) + select_dir = -1; + window->DC.CursorPos = ImVec2(tab_bar->BarRect.Max.x - scrolling_buttons_width + arrow_button_size.x, tab_bar->BarRect.Min.y); + if (ArrowButtonEx("##>", ImGuiDir_Right, arrow_button_size, ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_Repeat)) + select_dir = +1; + PopStyleColor(2); + g.IO.KeyRepeatRate = backup_repeat_rate; + g.IO.KeyRepeatDelay = backup_repeat_delay; + + if (want_clip_rect) + PopClipRect(); + + if (select_dir != 0) + if (ImGuiTabItem* tab_item = TabBarFindTabByID(tab_bar, tab_bar->SelectedTabId)) + { + int selected_order = tab_bar->GetTabOrder(tab_item); + int target_order = selected_order + select_dir; + tab_to_select = &tab_bar->Tabs[(target_order >= 0 && target_order < tab_bar->Tabs.Size) ? target_order : selected_order]; // If we are at the end of the list, still scroll to make our tab visible + } + window->DC.CursorPos = backup_cursor_pos; + tab_bar->BarRect.Max.x -= scrolling_buttons_width + 1.0f; + + return tab_to_select; +} + +// FIXME-DOCK: Unused by Docking system +static ImGuiTabItem* ImGui::TabBarTabListPopupButton(ImGuiTabBar* tab_bar) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + const float tab_list_popup_button_width = g.FontSize + g.Style.FramePadding.y * 2.0f; + const ImVec2 backup_cursor_pos = window->DC.CursorPos; + tab_bar->BarRect.Max.x -= tab_list_popup_button_width; + if (window->HasCloseButton) + tab_bar->BarRect.Max.x += g.Style.ItemInnerSpacing.x; + window->DC.CursorPos = ImVec2(tab_bar->BarRect.Max.x, tab_bar->BarRect.Min.y); + + ImVec4 arrow_col = g.Style.Colors[ImGuiCol_Text]; + arrow_col.w *= 0.5f; + PushStyleColor(ImGuiCol_Text, arrow_col); + PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0)); + bool open = BeginCombo("##v", NULL, ImGuiComboFlags_NoPreview | ImGuiComboFlags_PopupAlignLeft); + PopStyleColor(2); + + ImGuiTabItem* tab_to_select = NULL; + if (open) + { + for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++) + { + ImGuiTabItem* tab = &tab_bar->Tabs[tab_n]; + IM_ASSERT(tab->Window != NULL); + if (MenuItem(tab->Window->Name)) + tab_to_select = tab; + } + EndCombo(); + } + + window->DC.CursorPos = backup_cursor_pos; + return tab_to_select; +} + +//------------------------------------------------------------------------- +// [SECTION] Widgets: BeginTabItem, EndTabItem, etc. +//------------------------------------------------------------------------- +// - BeginTabItem() +// - EndTabItem() +// - TabItemEx() [Internal] +// - SetTabItemClosed() +// - TabItemCalcSize() [Internal] +// - TabItemRenderBackground() [Internal] +// - TabItemLabelAndCloseButton() [Internal] +//------------------------------------------------------------------------- + +bool ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags flags) +{ + ImGuiContext& g = *GImGui; + if (g.CurrentWindow->SkipItems) + return false; + + IM_ASSERT(g.CurrentTabBar.Size > 0 && "Needs to be called between BeginTabBar() and EndTabBar()!"); + ImGuiTabBar* tab_bar = g.CurrentTabBar.back(); + bool ret = TabItemEx(tab_bar, label, p_open, flags, NULL); + if (ret && !(flags & ImGuiTabItemFlags_NoPushId)) + PushID(tab_bar->Tabs[tab_bar->LastTabItemIdx].ID); + return ret; +} + +void ImGui::EndTabItem() +{ + ImGuiContext& g = *GImGui; + if (g.CurrentWindow->SkipItems) + return; + + IM_ASSERT(g.CurrentTabBar.Size > 0 && "Needs to be called between BeginTabBar() and EndTabBar()!"); + ImGuiTabBar* tab_bar = g.CurrentTabBar.back(); + ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx]; + if (!(tab->Flags & ImGuiTabItemFlags_NoPushId)) + PopID(); +} + +bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, ImGuiTabItemFlags flags, ImGuiWindow* docked_window) +{ + // Layout whole tab bar if not already done + if (tab_bar->WantLayout) + TabBarLayout(tab_bar); + + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return false; + + const ImGuiStyle& style = g.Style; + const ImGuiID id = TabBarCalcTabID(tab_bar, label); + + // If the user called us with *p_open == false, we early out and don't render. We make a dummy call to ItemAdd() so that attempts to use a contextual popup menu with an implicit ID won't use an older ID. + if (p_open && !*p_open) + { + PushItemFlag(ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus, true); + ItemAdd(ImRect(), id); + PopItemFlag(); + return false; + } + + // Calculate tab contents size + ImVec2 size = TabItemCalcSize(label, p_open != NULL); + + // Acquire tab data + ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, id); + bool tab_is_new = false; + if (tab == NULL) + { + tab_bar->Tabs.push_back(ImGuiTabItem()); + tab = &tab_bar->Tabs.back(); + tab->ID = id; + tab->Width = size.x; + tab_is_new = true; + } + tab_bar->LastTabItemIdx = (short)tab_bar->Tabs.index_from_pointer(tab); + tab->WidthContents = size.x; + + const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount); + const bool tab_bar_focused = (tab_bar->Flags & ImGuiTabBarFlags_IsFocused) != 0; + const bool tab_appearing = (tab->LastFrameVisible + 1 < g.FrameCount); + tab->LastFrameVisible = g.FrameCount; + tab->Flags = flags; + tab->Window = docked_window; + + // If we are not reorderable, always reset offset based on submission order. + // (We already handled layout and sizing using the previous known order, but sizing is not affected by order!) + if (!tab_appearing && !(tab_bar->Flags & ImGuiTabBarFlags_Reorderable)) + { + tab->Offset = tab_bar->OffsetNextTab; + tab_bar->OffsetNextTab += tab->Width + g.Style.ItemInnerSpacing.x; + } + + // Update selected tab + if (tab_appearing && (tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs) && tab_bar->NextSelectedTabId == 0) + if (!tab_bar_appearing || tab_bar->SelectedTabId == 0) + tab_bar->NextSelectedTabId = id; // New tabs gets activated + + // Lock visibility + bool tab_contents_visible = (tab_bar->VisibleTabId == id); + if (tab_contents_visible) + tab_bar->VisibleTabWasSubmitted = true; + + // On the very first frame of a tab bar we let first tab contents be visible to minimize appearing glitches + if (!tab_contents_visible && tab_bar->SelectedTabId == 0 && tab_bar_appearing && docked_window == NULL) + if (tab_bar->Tabs.Size == 1 && !(tab_bar->Flags & ImGuiTabBarFlags_AutoSelectNewTabs)) + tab_contents_visible = true; + + if (tab_appearing && !(tab_bar_appearing && !tab_is_new)) + { + PushItemFlag(ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus, true); + ItemAdd(ImRect(), id); + PopItemFlag(); + return tab_contents_visible; + } + + if (tab_bar->SelectedTabId == id) + tab->LastFrameSelected = g.FrameCount; + + // Backup current layout position + const ImVec2 backup_main_cursor_pos = window->DC.CursorPos; + + // Layout + size.x = tab->Width; + window->DC.CursorPos = tab_bar->BarRect.Min + ImVec2((float)(int)tab->Offset - tab_bar->ScrollingAnim, 0.0f); + ImVec2 pos = window->DC.CursorPos; + ImRect bb(pos, pos + size); + + // We don't have CPU clipping primitives to clip the CloseButton (until it becomes a texture), so need to add an extra draw call (temporary in the case of vertical animation) + bool want_clip_rect = (bb.Min.x < tab_bar->BarRect.Min.x) || (bb.Max.x >= tab_bar->BarRect.Max.x); + if (want_clip_rect) + PushClipRect(ImVec2(ImMax(bb.Min.x, tab_bar->BarRect.Min.x), bb.Min.y - 1), ImVec2(tab_bar->BarRect.Max.x, bb.Max.y), true); + + ItemSize(bb, style.FramePadding.y); + if (!ItemAdd(bb, id)) + { + if (want_clip_rect) + PopClipRect(); + window->DC.CursorPos = backup_main_cursor_pos; + return tab_contents_visible; + } + + // Click to Select a tab + ImGuiButtonFlags button_flags = (ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_AllowItemOverlap); + if (g.DragDropActive && !g.DragDropPayload.IsDataType(IMGUI_PAYLOAD_TYPE_WINDOW)) + button_flags |= ImGuiButtonFlags_PressedOnDragDropHold; + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags); + hovered |= (g.HoveredId == id); + if (pressed || ((flags & ImGuiTabItemFlags_SetSelected) && !tab_contents_visible)) // SetSelected can only be passed on explicit tab bar, so we don't need to set WantFocusTabId + tab_bar->NextSelectedTabId = tab_bar->WantFocusTabId = id; + + // Allow the close button to overlap unless we are dragging (in which case we don't want any overlapping tabs to be hovered) + if (!held) + SetItemAllowOverlap(); + + // Drag and drop + if (held && !tab_appearing && IsMouseDragging()) + { + // Re-order local or dockable tabs + float drag_distance_from_edge_x = 0.0f; + if (!g.DragDropActive && ((tab_bar->Flags & ImGuiTabBarFlags_Reorderable) || (flags & ImGuiTabItemFlags_DockedWindow))) + { + // While moving a tab it will jump on the other side of the mouse, so we also test for MouseDelta.x + if (g.IO.MouseDelta.x < 0.0f && g.IO.MousePos.x < bb.Min.x) + { + drag_distance_from_edge_x = bb.Min.x - g.IO.MousePos.x; + if (tab_bar->Flags & ImGuiTabBarFlags_Reorderable) + TabBarQueueChangeTabOrder(tab_bar, tab, -1); + } + else if (g.IO.MouseDelta.x > 0.0f && g.IO.MousePos.x > bb.Max.x) + { + drag_distance_from_edge_x = g.IO.MousePos.x - bb.Max.x; + if (tab_bar->Flags & ImGuiTabBarFlags_Reorderable) + TabBarQueueChangeTabOrder(tab_bar, tab, +1); + } + } + + // Extract a Dockable window out of it's tab bar + if (flags & ImGuiTabItemFlags_DockedWindow) + { + // We use a variable threshold to distinguish dragging tabs within a tab bar and extracting them out of the tab bar + //ImVec2 drag_delta = GetMouseDragDelta(); + bool undocking_tab = (g.DragDropActive && g.DragDropPayload.SourceId == id); + if (!undocking_tab && held)// && (drag_delta.x != 0.0f || drag_delta.y != 0.0f)) + { + //if (!g.IO.ConfigDockingWithShift || g.IO.KeyShift) + { + float threshold_base = g.FontSize; + //float threshold_base = g.IO.ConfigDockingWithShift ? g.FontSize * 0.5f : g.FontSize; + float threshold_x = (threshold_base * 2.2f); + float threshold_y = (threshold_base * 1.5f) + ImClamp((ImFabs(g.IO.MouseDragMaxDistanceAbs[0].x) - threshold_base * 2.0f) * 0.20f, 0.0f, threshold_base * 4.0f); + //GetOverlayDrawList(window)->AddRect(ImVec2(bb.Min.x - threshold_x, bb.Min.y - threshold_y), ImVec2(bb.Max.x + threshold_x, bb.Max.y + threshold_y), IM_COL32_WHITE); // [DEBUG] + + float distance_from_edge_y = ImMax(bb.Min.y - g.IO.MousePos.y, g.IO.MousePos.y - bb.Max.y); + if (distance_from_edge_y >= threshold_y) + undocking_tab = true; + else if (drag_distance_from_edge_x > threshold_x) + if ((tab_bar->ReorderRequestDir < 0 && tab_bar->GetTabOrder(tab) == 0) || (tab_bar->ReorderRequestDir > 0 && tab_bar->GetTabOrder(tab) == tab_bar->Tabs.Size - 1)) + undocking_tab = true; + } + } + + // Undock + if (undocking_tab && g.ActiveId == id && IsMouseDragging()) + { + DockContextQueueUndockWindow(&g, docked_window); + g.MovingWindow = docked_window; + g.ActiveId = g.MovingWindow->MoveId; + g.ActiveIdClickOffset -= g.MovingWindow->Pos - bb.Min; + } + } + } + +#if 0 + if (hovered && g.HoveredIdTimer > 0.40f && bb.GetWidth() < tab->WidthContents) + { + // Enlarge tab display when hovering + bb.Max.x = bb.Min.x + (float)(int)ImLerp(bb.GetWidth(), tab->WidthContents, ImSaturate((g.HoveredIdTimer - 0.40f) * 6.0f)); + display_draw_list = GetOverlayDrawList(window); + TabItemRenderBackground(display_draw_list, bb, flags, GetColorU32(ImGuiCol_TitleBgActive)); + } +#endif + + // Render tab shape + ImDrawList* display_draw_list = window->DrawList; + const ImU32 tab_col = GetColorU32((held || hovered) ? ImGuiCol_TabHovered : tab_contents_visible ? (tab_bar_focused ? ImGuiCol_TabActive : ImGuiCol_TabUnfocusedActive) : (tab_bar_focused ? ImGuiCol_Tab : ImGuiCol_TabUnfocused)); + TabItemBackground(display_draw_list, bb, flags, tab_col); + RenderNavHighlight(bb, id); + + // Select with right mouse button. This is so the common idiom for context menu automatically highlight the current widget. + const bool hovered_unblocked = IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup); + if (hovered_unblocked && (IsMouseClicked(1) || IsMouseReleased(1))) + tab_bar->NextSelectedTabId = tab_bar->WantFocusTabId = id; + + if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton) + flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton; + + // Render tab label, process close button + const ImGuiID close_button_id = p_open ? window->GetID((void*)(intptr_t)(id + 1)) : 0; + bool just_closed = TabItemLabelAndCloseButton(display_draw_list, bb, flags, label, id, close_button_id); + if (just_closed) + { + *p_open = false; + TabBarCloseTab(tab_bar, tab); + } + + // Restore main window position so user can draw there + if (want_clip_rect) + PopClipRect(); + window->DC.CursorPos = backup_main_cursor_pos; + + // Tooltip (FIXME: Won't work over the close button because ItemOverlap systems messes up with HoveredIdTimer) + if (g.HoveredId == id && !held && g.HoveredIdTimer > 0.50f) + SetTooltip("%.*s", (int)(FindRenderedTextEnd(label) - label), label); + + return tab_contents_visible; +} + +// [Public] This is call is 100% optional but it allows to remove some one-frame glitches when a tab has been unexpectedly removed. +// To use it to need to call the function SetTabItemClosed() after BeginTabBar() and before any call to BeginTabItem() +void ImGui::SetTabItemClosed(const char* label) +{ + ImGuiContext& g = *GImGui; + bool is_within_manual_tab_bar = (g.CurrentTabBar.Size > 0) && !(g.CurrentTabBar.back()->Flags & ImGuiTabBarFlags_DockNode); + if (is_within_manual_tab_bar) + { + ImGuiTabBar* tab_bar = g.CurrentTabBar.back(); + IM_ASSERT(tab_bar->WantLayout); // Needs to be called AFTER BeginTabBar() and BEFORE the first call to BeginTabItem() + ImGuiID tab_id = TabBarCalcTabID(tab_bar, label); + TabBarRemoveTab(tab_bar, tab_id); + } + else if (ImGuiWindow* window = FindWindowByName(label)) + { + if (window->DockIsActive) + if (ImGuiDockNode* node = window->DockNode) + { + ImGuiID tab_id = TabBarCalcTabID(node->TabBar, label); + TabBarRemoveTab(node->TabBar, tab_id); + window->DockTabWantClose = true; + } + } +} + +ImVec2 ImGui::TabItemCalcSize(const char* label, bool has_close_button) +{ + ImGuiContext& g = *GImGui; + ImVec2 label_size = CalcTextSize(label, NULL, true); + ImVec2 size = ImVec2(label_size.x + g.Style.FramePadding.x, label_size.y + g.Style.FramePadding.y * 2.0f); + if (has_close_button) + size.x += g.Style.FramePadding.x + (g.Style.ItemInnerSpacing.x + g.FontSize); // We use Y intentionally to fit the close button circle. + else + size.x += g.Style.FramePadding.x + 1.0f; + return ImVec2(ImMin(size.x, TabBarCalcMaxTabWidth()), size.y); +} + +void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImU32 col) +{ + // While rendering tabs, we trim 1 pixel off the top of our bounding box so they can fit within a regular frame height while looking "detached" from it. + ImGuiContext& g = *GImGui; + const float width = bb.GetWidth(); + IM_ASSERT(width > 0.0f); + const float rounding = ImMax(0.0f, ImMin(g.Style.TabRounding, width * 0.5f - 1.0f)); + float y1 = bb.Min.y + 1.0f; + float y2 = bb.Max.y + ((flags & ImGuiTabItemFlags_Preview) ? 0.0f : -1.0f); + draw_list->PathLineTo(ImVec2(bb.Min.x, y2)); + draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding, y1 + rounding), rounding, 6, 9); + draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding, y1 + rounding), rounding, 9, 12); + draw_list->PathLineTo(ImVec2(bb.Max.x, y2)); + draw_list->AddConvexPolyFilled(draw_list->_Path.Data, draw_list->_Path.Size, col); + if (g.Style.TabBorderSize > 0.0f) + draw_list->AddPolyline(draw_list->_Path.Data, draw_list->_Path.Size, GetColorU32(ImGuiCol_Border), false, g.Style.TabBorderSize); + draw_list->PathClear(); +} + +// Render text label (with clipping + alpha gradient) + unsaved marker + Close Button logic +bool ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, const char* label, ImGuiID tab_id, ImGuiID close_button_id) +{ + ImGuiContext& g = *GImGui; + ImGuiStyle& style = g.Style; + ImVec2 label_size = CalcTextSize(label, NULL, true); + if (bb.GetWidth() <= 1.0f) + return false; + + // Render text label (with clipping + alpha gradient) + unsaved marker + const char* TAB_UNSAVED_MARKER = "*"; + ImRect text_pixel_clip_bb(bb.Min.x + style.FramePadding.x, bb.Min.y + style.FramePadding.y, bb.Max.x - style.FramePadding.x, bb.Max.y); + if (flags & ImGuiTabItemFlags_UnsavedDocument) + { + text_pixel_clip_bb.Max.x -= CalcTextSize(TAB_UNSAVED_MARKER, NULL, false).x; + ImVec2 unsaved_marker_pos(ImMin(bb.Min.x + style.FramePadding.x + label_size.x + 2, text_pixel_clip_bb.Max.x), bb.Min.y + style.FramePadding.y + (float)(int)(-g.FontSize * 0.25f)); + RenderTextClippedEx(draw_list, unsaved_marker_pos, bb.Max - style.FramePadding, TAB_UNSAVED_MARKER, NULL, NULL); + } + ImRect text_ellipsis_clip_bb = text_pixel_clip_bb; + + // Close Button + // We are relying on a subtle and confusing distinction between 'hovered' and 'g.HoveredId' which happens because we are using ImGuiButtonFlags_AllowOverlapMode + SetItemAllowOverlap() + // 'hovered' will be true when hovering the Tab but NOT when hovering the close button + // 'g.HoveredId==id' will be true when hovering the Tab including when hovering the close button + // 'g.ActiveId==close_button_id' will be true when we are holding on the close button, in which case both hovered booleans are false + bool close_button_pressed = false; + bool close_button_visible = false; + if (close_button_id != 0) + if (g.HoveredId == tab_id || g.HoveredId == close_button_id || g.ActiveId == close_button_id) + close_button_visible = true; + if (close_button_visible) + { + ImGuiItemHoveredDataBackup last_item_backup; + const float close_button_sz = g.FontSize * 0.5f; + if (CloseButton(close_button_id, ImVec2(bb.Max.x - style.FramePadding.x - close_button_sz, bb.Min.y + style.FramePadding.y + close_button_sz), close_button_sz)) + close_button_pressed = true; + last_item_backup.Restore(); + + // Close with middle mouse button + if (!(flags & ImGuiTabItemFlags_NoCloseWithMiddleMouseButton) && IsMouseClicked(2)) + close_button_pressed = true; + + text_pixel_clip_bb.Max.x -= close_button_sz * 2.0f; + } + + // Label with ellipsis + // FIXME: This could be extracted into a helper but or use of text_pixel_clip_bb and !close_button_visible makes it tricky to abstract at the moment + const char* label_display_end = FindRenderedTextEnd(label); + if (label_size.x > text_ellipsis_clip_bb.GetWidth()) + { + const int ellipsis_dot_count = 3; + const float ellipsis_width = (1.0f + 1.0f) * ellipsis_dot_count - 1.0f; + const char* label_end = NULL; + float label_size_clipped_x = g.Font->CalcTextSizeA(g.FontSize, text_ellipsis_clip_bb.GetWidth() - ellipsis_width + 1.0f, 0.0f, label, label_display_end, &label_end).x; + if (label_end == label && label_end < label_display_end) // Always display at least 1 character if there's no room for character + ellipsis + { + label_end = label + ImTextCountUtf8BytesFromChar(label, label_display_end); + label_size_clipped_x = g.Font->CalcTextSizeA(g.FontSize, FLT_MAX, 0.0f, label, label_end).x; + } + while (label_end > label && ImCharIsBlankA(label_end[-1])) // Trim trailing space + { + label_end--; + label_size_clipped_x -= g.Font->CalcTextSizeA(g.FontSize, FLT_MAX, 0.0f, label_end, label_end + 1).x; // Ascii blanks are always 1 byte + } + RenderTextClippedEx(draw_list, text_pixel_clip_bb.Min, text_pixel_clip_bb.Max, label, label_end, &label_size, ImVec2(0.0f, 0.0f)); + + const float ellipsis_x = text_pixel_clip_bb.Min.x + label_size_clipped_x + 1.0f; + if (!close_button_visible && ellipsis_x + ellipsis_width <= bb.Max.x) + RenderPixelEllipsis(draw_list, g.Font, ImVec2(ellipsis_x, text_pixel_clip_bb.Min.y), ellipsis_dot_count, GetColorU32(ImGuiCol_Text)); + } + else + { + RenderTextClippedEx(draw_list, text_pixel_clip_bb.Min, text_pixel_clip_bb.Max, label, label_display_end, &label_size, ImVec2(0.0f, 0.0f)); + } + + return close_button_pressed; +} diff --git a/Source Code/Libs/imgui/misc/fonts/README.txt b/Source Code/Libs/imgui/misc/fonts/README.txt index 06e74e9b9a..a40bb3d1af 100644 --- a/Source Code/Libs/imgui/misc/fonts/README.txt +++ b/Source Code/Libs/imgui/misc/fonts/README.txt @@ -1,9 +1,11 @@ -The code in imgui.cpp embeds a copy of 'ProggyClean.ttf' (by Tristan Grimmer) that is used by default. -We embed the font in source code so you can use Dear ImGui without any file system access. +The code in imgui.cpp embeds a copy of 'ProggyClean.ttf' (by Tristan Grimmer), +a 13 pixels high, pixel-perfect font used by default. +We embed it font in source code so you can use Dear ImGui without any file system access. + You may also load external .TTF/.OTF files. The files in this folder are suggested fonts, provided as a convenience. -(Note: .OTF support in stb_truetype.h currently doesn't appear to load every font) +(Note: .OTF support in imstb_truetype.h currently doesn't appear to load every font) Fonts are rasterized in a single texture at the time of calling either of io.Fonts->GetTexDataAsAlpha8()/GetTexDataAsRGBA32()/Build(). Also read dear imgui FAQ in imgui.cpp! @@ -11,6 +13,7 @@ Also read dear imgui FAQ in imgui.cpp! If you have other loading/merging/adding fonts, you can post on the Dear ImGui "Getting Started" forum: https://discourse.dearimgui.org/c/getting-started + --------------------------------------- INDEX: --------------------------------------- @@ -35,7 +38,7 @@ If you have other loading/merging/adding fonts, you can post on the Dear ImGui " u8"hello" u8"こんにちは" // this will be encoded as UTF-8 - If you want to include a backslash \ character in your string literal, you need to double them e.g. "folder\\filename". - - Please use the Discourse forum (https://discourse.dearimgui.org) and not the Github issue tracker. + - Please use the Discourse forum (https://discourse.dearimgui.org) and not the Github issue tracker for basic font loading questions. --------------------------------------- @@ -68,7 +71,11 @@ If you have other loading/merging/adding fonts, you can post on the Dear ImGui " io.Fonts->AddFontFromFileTTF("fonts/fontawesome-webfont.ttf", 13.0f, &config, icon_ranges); // Usage, e.g. - ImGui::Button(ICON_FA_SEARCH " Search"); // C string literals can be concatenated at compilation time, this is the same as "A" "B" becoming "AB" + ImGui::Button(ICON_FA_SEARCH " Search"); + // C string _literals_ can be concatenated at compilation time, e.g. "hello" " world" + // ICON_FA_SEARCH is defined as a string literal so this is the same as "A" "B" becoming "AB" + + // Usage, e.g. ImGui::Text("%s among %d items", ICON_FA_SEARCH, count); See Links below for other icons fonts and related tools. @@ -124,12 +131,13 @@ If you have other loading/merging/adding fonts, you can post on the Dear ImGui " // Add character ranges and merge into the previous font // The ranges array is not copied by the AddFont* functions and is used lazily - // so ensure it is available for duration of font usage - static const ImWchar icons_ranges[] = { 0xf000, 0xf3ff, 0 }; // will not be copied by AddFont* so keep in scope. + // so ensure it is available at the time of building or calling GetTexDataAsRGBA32(). + static const ImWchar icons_ranges[] = { 0xf000, 0xf3ff, 0 }; // Will not be copied by AddFont* so keep in scope. ImFontConfig config; config.MergeMode = true; io.Fonts->AddFontFromFileTTF("DroidSans.ttf", 18.0f, &config, io.Fonts->GetGlyphRangesJapanese()); io.Fonts->AddFontFromFileTTF("fontawesome-webfont.ttf", 18.0f, &config, icons_ranges); + io.Fonts->Build(); Add a fourth parameter to bake specific font ranges only: @@ -153,12 +161,15 @@ If you have other loading/merging/adding fonts, you can post on the Dear ImGui " FREETYPE RASTERIZER, SMALL FONT SIZES --------------------------------------- - Dear Imgui uses stb_truetype.h to rasterize fonts (with optional oversampling). - This technique and implementation are not ideal for fonts rendered at _small sizes_, which may appear a little blurry. + Dear ImGui uses imstb_truetype.h to rasterize fonts (with optional oversampling). + This technique and its implementation are not ideal for fonts rendered at _small sizes_, which may appear a + little blurry or hard to read. + There is an implementation of the ImFontAtlas builder using FreeType that you can use in the misc/freetype/ folder. FreeType supports auto-hinting which tends to improve the readability of small fonts. - Note that this code currently creates textures that are unoptimally too large (could be fixed with some work) + Note that this code currently creates textures that are unoptimally too large (could be fixed with some work). + Also note that correct sRGB space blending will have an important effect on your font rendering quality. --------------------------------------- @@ -174,7 +185,9 @@ If you have other loading/merging/adding fonts, you can post on the Dear ImGui " builder.AddChar(0x7262); // Add a specific character builder.AddRanges(io.Fonts->GetGlyphRangesJapanese()); // Add one of the default ranges builder.BuildRanges(&ranges); // Build the final result (ordered ranges with all the unique characters submitted) + io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels, NULL, ranges.Data); + io.Fonts->Build(); // Build the atlas while 'ranges' is still in scope and not deleted. --------------------------------------- @@ -242,6 +255,10 @@ If you have other loading/merging/adding fonts, you can post on the Dear ImGui " (Icons) IcoMoon - Custom Icon font builder https://icomoon.io/app + (Pixel perfect) Sweet16, Sweet16 Mono, by Martin Sedlak (Latin + Supplemental + Extended A) + https://github.com/kmar/Sweet16Font + Also include .inl file to use directly in dear imgui. + (Regular) Open Sans Fonts https://fonts.google.com/specimen/Open+Sans diff --git a/Source Code/Libs/imgui/misc/freetype/imgui_freetype.cpp b/Source Code/Libs/imgui/misc/freetype/imgui_freetype.cpp index 89aa23f5cf..33bdb14044 100644 --- a/Source Code/Libs/imgui/misc/freetype/imgui_freetype.cpp +++ b/Source Code/Libs/imgui/misc/freetype/imgui_freetype.cpp @@ -337,7 +337,7 @@ bool ImGuiFreeType::BuildFontAtlas(ImFontAtlas* atlas, unsigned int extra_flags) { for (uint32_t codepoint = in_range[0]; codepoint <= in_range[1]; ++codepoint) { - if (cfg.MergeMode && dst_font->FindGlyphNoFallback((unsigned short)codepoint)) + if (cfg.MergeMode && dst_font->FindGlyphNoFallback((ImWchar)codepoint)) continue; FT_Glyph ft_glyph = NULL; diff --git a/Source Code/Platform/DisplayWindow.cpp b/Source Code/Platform/DisplayWindow.cpp index d18eecf724..e90e488c7d 100644 --- a/Source Code/Platform/DisplayWindow.cpp +++ b/Source Code/Platform/DisplayWindow.cpp @@ -60,11 +60,21 @@ DisplayWindow::DisplayWindow(WindowManager& parent, PlatformContext& context) _windowPosition.set(0); _prevDimensions.set(1); _windowDimensions.set(1); + + for (U8 i = 0; i < to_U8(CursorStyle::COUNT); ++i) { + CursorStyle style = static_cast(i); + _cursors[style] = SDL_CreateSystemCursor(CursorToSDL(style)); + } } DisplayWindow::~DisplayWindow() { - destroyWindow(); + for (auto it : _cursors) { + SDL_FreeCursor(it.second); + } + _cursors.clear(); + + destroyWindow(); } ErrorCode DisplayWindow::destroyWindow() { @@ -307,7 +317,7 @@ void DisplayWindow::setCursorPosition(I32 x, I32 y) { } void DisplayWindow::setCursorStyle(CursorStyle style) { - SDL_SetCursor(SDL_CreateSystemCursor(CursorToSDL(style))); + SDL_SetCursor(_cursors[style]); } vec2 DisplayWindow::getCursorPosition() const { diff --git a/Source Code/Platform/Headers/DisplayWindow.h b/Source Code/Platform/Headers/DisplayWindow.h index cdefedee50..0dc2360061 100644 --- a/Source Code/Platform/Headers/DisplayWindow.h +++ b/Source Code/Platform/Headers/DisplayWindow.h @@ -271,6 +271,8 @@ class DisplayWindow : public GUIDWrapper, DELEGATE_CBK _destroyCbk; + hashMap _cursors; + // Varies from OS to OS WindowHandle _handle; diff --git a/VCData/Projects/Divide-Editor/Divide-Editor.vcxproj b/VCData/Projects/Divide-Editor/Divide-Editor.vcxproj index affbde98cb..ffd91f3808 100644 --- a/VCData/Projects/Divide-Editor/Divide-Editor.vcxproj +++ b/VCData/Projects/Divide-Editor/Divide-Editor.vcxproj @@ -95,7 +95,6 @@ - @@ -107,10 +106,8 @@ - - diff --git a/VCData/Projects/Divide-Editor/Divide-Editor.vcxproj.filters b/VCData/Projects/Divide-Editor/Divide-Editor.vcxproj.filters index d1284904d9..92036520d1 100644 --- a/VCData/Projects/Divide-Editor/Divide-Editor.vcxproj.filters +++ b/VCData/Projects/Divide-Editor/Divide-Editor.vcxproj.filters @@ -38,9 +38,6 @@ {1f7b44a6-23e1-4043-9591-26aac1776cd8} - - {ffbaf51d-2b0d-4ddb-ba38-dc85697dc9cd} - {46b80529-a517-4822-bb2c-379b689b27e7} @@ -53,18 +50,12 @@ {c519a381-4f71-4c84-8804-a016e988e4b9} - - {043dbaf5-e702-4dfd-a19f-aac495adfb9c} - {15390a71-95f0-4af3-8531-2cbb2c2128d0} {91075a97-cb6d-4d13-b1f3-43d302a84969} - - {ba129140-a309-43d7-8ac1-99f496121ce8} - {ad7a9b23-3ff0-4c9e-8fac-2af5d9b47f76} @@ -235,9 +226,6 @@ 3rdParty/ImGUI\addons\imguidatechooser - - 3rdParty/ImGUI\addons\imguidock - 3rdParty/ImGUI\addons\imguifilesystem @@ -265,18 +253,12 @@ 3rdParty/ImGUI\addons\imguinodegrapheditor - - 3rdParty/ImGUI\addons\imguipanelmanager - 3rdParty/ImGUI\addons\imguistring 3rdParty/ImGUI\addons\imguistyleserializer - - 3rdParty/ImGUI\addons\imguitabwindow - 3rdParty/ImGUI\addons\imguitoolbar diff --git a/imgui.ini b/imgui.ini index 0f3b572fa8..c67157247d 100644 --- a/imgui.ini +++ b/imgui.ini @@ -4,22 +4,35 @@ Size=400,400 Collapsed=0 [Window][Solution Explorer] -Pos=0,20 -Size=300,550 +Pos=0,18 +Size=319,483 Collapsed=0 +DockId=0x00000002,0 [Window][Properties] -Pos=980,20 -Size=300,550 +Pos=1003,18 +Size=276,483 Collapsed=0 +DockId=0x00000005,0 [Window][Output] -Pos=0,573 -Size=1280,147 +Pos=0,505 +Size=1279,214 Collapsed=0 +DockId=0x00000007,0 [Window][SceneView] -Pos=309,46 -Size=640,480 +Pos=323,18 +Size=676,483 Collapsed=0 +DockId=0x00000003,0 + +[Docking][Data] +DockNode ID=0x00000001 Pos=0,18 Size=1279,701 Split=Y + DockNode ID=0x00000006 Parent=0x00000001 SizeRef=1279,483 Split=X + DockNode ID=0x00000004 Parent=0x00000006 SizeRef=232,550 Split=X + DockNode ID=0x00000002 Parent=0x00000004 SizeRef=73,550 SelectedTab=0x342008E6 + DockNode ID=0x00000003 Parent=0x00000004 SizeRef=155,550 SelectedTab=0x369BE0BC + DockNode ID=0x00000005 Parent=0x00000006 SizeRef=64,550 SelectedTab=0xC89E3217 + DockNode ID=0x00000007 Parent=0x00000001 SizeRef=1279,214 SelectedTab=0xCB7211A8