mirror of https://github.com/axmolengine/axmol.git
19652 lines
888 KiB
C++
19652 lines
888 KiB
C++
// dear imgui, v1.84
|
|
// (main code and documentation)
|
|
|
|
// Help:
|
|
// - Read FAQ at http://dearimgui.org/faq
|
|
// - Newcomers, read 'Programmer guide' below for notes on how to setup Dear ImGui in your codebase.
|
|
// - Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp. All applications in examples/ are doing that.
|
|
// Read imgui.cpp for details, links and comments.
|
|
|
|
// Resources:
|
|
// - FAQ http://dearimgui.org/faq
|
|
// - Homepage & latest https://github.com/ocornut/imgui
|
|
// - Releases & changelog https://github.com/ocornut/imgui/releases
|
|
// - Gallery https://github.com/ocornut/imgui/issues/4451 (please post your screenshots/video there!)
|
|
// - Wiki https://github.com/ocornut/imgui/wiki (lots of good stuff there)
|
|
// - Glossary https://github.com/ocornut/imgui/wiki/Glossary
|
|
// - Issues & support https://github.com/ocornut/imgui/issues
|
|
// - Discussions https://github.com/ocornut/imgui/discussions
|
|
|
|
// Developed by Omar Cornut and every direct or indirect contributors to the GitHub.
|
|
// See LICENSE.txt for copyright and licensing details (standard MIT License).
|
|
// This library is free but needs your support to sustain development and maintenance.
|
|
// Businesses: you can support continued development via invoiced technical support, maintenance and sponsoring
|
|
// contracts. Please reach out to "contact AT dearimgui.com". Individuals: you can support continued development via
|
|
// donations. See docs/README or web page.
|
|
|
|
// It is recommended that you don't modify imgui.cpp! It will become difficult for you to update the library.
|
|
// Note that 'ImGui::' being a namespace, you can add functions into the namespace from your own source files, without
|
|
// modifying imgui.h or imgui.cpp. You may include imgui_internal.h to access internal data structures, but it doesn't
|
|
// come with any guarantee of forward compatibility. Discussing your changes on the GitHub Issue Tracker may lead you
|
|
// to a better solution or official support for them.
|
|
|
|
/*
|
|
|
|
Index of this file:
|
|
|
|
DOCUMENTATION
|
|
|
|
- MISSION STATEMENT
|
|
- END-USER GUIDE
|
|
- PROGRAMMER GUIDE
|
|
- READ FIRST
|
|
- HOW TO UPDATE TO A NEWER VERSION OF DEAR IMGUI
|
|
- GETTING STARTED WITH INTEGRATING DEAR IMGUI IN YOUR CODE/ENGINE
|
|
- HOW A SIMPLE APPLICATION MAY LOOK LIKE
|
|
- HOW A SIMPLE RENDERING FUNCTION MAY LOOK LIKE
|
|
- USING GAMEPAD/KEYBOARD NAVIGATION CONTROLS
|
|
- API BREAKING CHANGES (read me when you update!)
|
|
- FREQUENTLY ASKED QUESTIONS (FAQ)
|
|
- Read all answers online: https://www.dearimgui.org/faq, or in docs/FAQ.md (with a Markdown viewer)
|
|
|
|
CODE
|
|
(search for "[SECTION]" in the code to find them)
|
|
|
|
// [SECTION] INCLUDES
|
|
// [SECTION] FORWARD DECLARATIONS
|
|
// [SECTION] CONTEXT AND MEMORY ALLOCATORS
|
|
// [SECTION] USER FACING STRUCTURES (ImGuiStyle, ImGuiIO)
|
|
// [SECTION] MISC HELPERS/UTILITIES (Geometry functions)
|
|
// [SECTION] MISC HELPERS/UTILITIES (String, Format, Hash functions)
|
|
// [SECTION] MISC HELPERS/UTILITIES (File functions)
|
|
// [SECTION] MISC HELPERS/UTILITIES (ImText* functions)
|
|
// [SECTION] MISC HELPERS/UTILITIES (Color functions)
|
|
// [SECTION] ImGuiStorage
|
|
// [SECTION] ImGuiTextFilter
|
|
// [SECTION] ImGuiTextBuffer
|
|
// [SECTION] ImGuiListClipper
|
|
// [SECTION] STYLING
|
|
// [SECTION] RENDER HELPERS
|
|
// [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!)
|
|
// [SECTION] ERROR CHECKING
|
|
// [SECTION] LAYOUT
|
|
// [SECTION] SCROLLING
|
|
// [SECTION] TOOLTIPS
|
|
// [SECTION] POPUPS
|
|
// [SECTION] KEYBOARD/GAMEPAD NAVIGATION
|
|
// [SECTION] DRAG AND DROP
|
|
// [SECTION] LOGGING/CAPTURING
|
|
// [SECTION] SETTINGS
|
|
// [SECTION] VIEWPORTS, PLATFORM WINDOWS
|
|
// [SECTION] DOCKING
|
|
// [SECTION] PLATFORM DEPENDENT HELPERS
|
|
// [SECTION] METRICS/DEBUGGER WINDOW
|
|
|
|
*/
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// DOCUMENTATION
|
|
//-----------------------------------------------------------------------------
|
|
|
|
/*
|
|
|
|
MISSION STATEMENT
|
|
=================
|
|
|
|
- Easy to use to create code-driven and data-driven tools.
|
|
- Easy to use to create ad hoc short-lived tools and long-lived, more elaborate tools.
|
|
- Easy to hack and improve.
|
|
- Minimize setup and maintenance.
|
|
- Minimize state storage on user side.
|
|
- Portable, minimize dependencies, run on target (consoles, phones, etc.).
|
|
- Efficient runtime and memory consumption.
|
|
|
|
Designed for developers and content-creators, not the typical end-user! Some of the current weaknesses includes:
|
|
|
|
- Doesn't look fancy, doesn't animate.
|
|
- Limited layout features, intricate layouts are typically crafted in code.
|
|
|
|
|
|
END-USER GUIDE
|
|
==============
|
|
|
|
- Double-click on title bar to collapse window.
|
|
- Click upper right corner to close a window, available when 'bool* p_open' is passed to ImGui::Begin().
|
|
- Click and drag on lower right corner to resize window (double-click to auto fit window to its contents).
|
|
- Click and drag on any empty space to move window.
|
|
- TAB/SHIFT+TAB to cycle through keyboard editable fields.
|
|
- CTRL+Click on a slider or drag box to input value as text.
|
|
- Use mouse wheel to scroll.
|
|
- Text editor:
|
|
- Hold SHIFT or use mouse to select text.
|
|
- CTRL+Left/Right to word jump.
|
|
- CTRL+Shift+Left/Right to select words.
|
|
- CTRL+A our Double-Click to select all.
|
|
- CTRL+X,CTRL+C,CTRL+V to use OS clipboard/
|
|
- CTRL+Z,CTRL+Y to undo/redo.
|
|
- ESCAPE to revert text to its original value.
|
|
- You can apply arithmetic operators +,*,/ on numerical values. Use +- to subtract (because - would set a negative
|
|
value!)
|
|
- Controls are automatically adjusted for OSX to match standard OSX text editing operations.
|
|
- General Keyboard controls: enable with ImGuiConfigFlags_NavEnableKeyboard.
|
|
- General Gamepad controls: enable with ImGuiConfigFlags_NavEnableGamepad. See suggested mappings in imgui.h
|
|
ImGuiNavInput_ + download PNG/PSD at http://dearimgui.org/controls_sheets
|
|
|
|
|
|
PROGRAMMER GUIDE
|
|
================
|
|
|
|
READ FIRST
|
|
----------
|
|
- Remember to check the wonderful Wiki (https://github.com/ocornut/imgui/wiki)
|
|
- Your code creates the UI, if your code doesn't run the UI is gone! The UI can be highly dynamic, there are no
|
|
construction or destruction steps, less superfluous data retention on your side, less state duplication, less state
|
|
synchronization, fewer bugs.
|
|
- Call and read ImGui::ShowDemoWindow() for demo code demonstrating most features.
|
|
- The library is designed to be built from sources. Avoid pre-compiled binaries and packaged versions. See imconfig.h
|
|
to configure your build.
|
|
- Dear ImGui is an implementation of the IMGUI paradigm (immediate-mode graphical user interface, a term coined by
|
|
Casey Muratori). You can learn about IMGUI principles at http://www.johno.se/book/imgui.html, http://mollyrocket.com/861
|
|
& more links in Wiki.
|
|
- Dear ImGui is a "single pass" rasterizing implementation of the IMGUI paradigm, aimed at ease of use and
|
|
high-performances. For every application frame, your UI code will be called only once. This is in contrast to e.g.
|
|
Unity's implementation of an IMGUI, where the UI code is called multiple times ("multiple passes") from a single entry
|
|
point. There are pros and cons to both approaches.
|
|
- Our origin is on the top-left. In axis aligned bounding boxes, Min = top-left, Max = bottom-right.
|
|
- This codebase is also optimized to yield decent performances with typical "Debug" builds settings.
|
|
- Please make sure you have asserts enabled (IM_ASSERT redirects to assert() by default, but can be redirected).
|
|
If you get an assert, read the messages and comments around the assert.
|
|
- C++: this is a very C-ish codebase: we don't rely on C++11, we don't include any C++ headers, and ImGui:: is a
|
|
namespace.
|
|
- C++: ImVec2/ImVec4 do not expose math operators by default, because it is expected that you use your own math types.
|
|
See FAQ "How can I use my own math types instead of ImVec2/ImVec4?" for details about setting up imconfig.h for that.
|
|
However, imgui_internal.h can optionally export math operators for ImVec2/ImVec4, which we use in this codebase.
|
|
- C++: pay attention that ImVector<> manipulates plain-old-data and does not honor construction/destruction (avoid
|
|
using it in your code!).
|
|
|
|
|
|
HOW TO UPDATE TO A NEWER VERSION OF DEAR IMGUI
|
|
----------------------------------------------
|
|
- Overwrite all the sources files except for imconfig.h (if you have modified your copy of imconfig.h)
|
|
- Or maintain your own branch where you have imconfig.h modified as a top-most commit which you can regularly rebase
|
|
over "master".
|
|
- You can also use '#define IMGUI_USER_CONFIG "my_config_file.h" to redirect configuration to your own file.
|
|
- Read the "API BREAKING CHANGES" section (below). This is where we list occasional API breaking changes.
|
|
If a function/type has been renamed / or marked obsolete, try to fix the name in your code before it is permanently
|
|
removed from the public API. If you have a problem with a missing function/symbols, search for its name in the code,
|
|
there will likely be a comment about it. Please report any issue to the GitHub page!
|
|
- To find out usage of old API, you can add '#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS' in your configuration file.
|
|
- Try to keep your copy of Dear ImGui reasonably up to date.
|
|
|
|
|
|
GETTING STARTED WITH INTEGRATING DEAR IMGUI IN YOUR CODE/ENGINE
|
|
---------------------------------------------------------------
|
|
- Run and study the examples and demo in imgui_demo.cpp to get acquainted with the library.
|
|
- In the majority of cases you should be able to use unmodified backends files available in the backends/ folder.
|
|
- Add the Dear ImGui source files + selected backend source files to your projects or using your preferred build
|
|
system. It is recommended you build and statically link the .cpp files as part of your project and NOT as a shared
|
|
library (DLL).
|
|
- You can later customize the imconfig.h file to tweak some compile-time behavior, such as integrating Dear ImGui types
|
|
with your own maths types.
|
|
- When using Dear ImGui, your programming IDE is your friend: follow the declaration of variables, functions and types
|
|
to find comments about them.
|
|
- Dear ImGui never touches or knows about your GPU state. The only function that knows about GPU is the draw function
|
|
that you provide. Effectively it means you can create widgets at any time in your code, regardless of considerations of
|
|
being in "update" vs "render" phases of your own application. All rendering information is stored into command-lists
|
|
that you will retrieve after calling ImGui::Render().
|
|
- Refer to the backends and demo applications in the examples/ folder for instruction on how to setup your code.
|
|
- If you are running over a standard OS with a common graphics API, you should be able to use unmodified imgui_impl_***
|
|
files from the examples/ folder.
|
|
|
|
|
|
HOW A SIMPLE APPLICATION MAY LOOK LIKE
|
|
--------------------------------------
|
|
EXHIBIT 1: USING THE EXAMPLE BACKENDS (= imgui_impl_XXX.cpp files from the backends/ folder).
|
|
The sub-folders in examples/ contain examples applications following this structure.
|
|
|
|
// Application init: create a dear imgui context, setup some options, load fonts
|
|
ImGui::CreateContext();
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
// TODO: Set optional io.ConfigFlags values, e.g. 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard' to enable
|
|
keyboard controls.
|
|
// TODO: Fill optional fields of the io structure later.
|
|
// TODO: Load TTF/OTF fonts if you don't want to use the default font.
|
|
|
|
// Initialize helper Platform and Renderer backends (here we are using imgui_impl_win32.cpp and
|
|
imgui_impl_dx11.cpp) ImGui_ImplWin32_Init(hwnd); ImGui_ImplDX11_Init(g_pd3dDevice, g_pd3dDeviceContext);
|
|
|
|
// Application main loop
|
|
while (true)
|
|
{
|
|
// Feed inputs to dear imgui, start new frame
|
|
ImGui_ImplDX11_NewFrame();
|
|
ImGui_ImplWin32_NewFrame();
|
|
ImGui::NewFrame();
|
|
|
|
// Any application code here
|
|
ImGui::Text("Hello, world!");
|
|
|
|
// Render dear imgui into screen
|
|
ImGui::Render();
|
|
ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
|
|
g_pSwapChain->Present(1, 0);
|
|
}
|
|
|
|
// Shutdown
|
|
ImGui_ImplDX11_Shutdown();
|
|
ImGui_ImplWin32_Shutdown();
|
|
ImGui::DestroyContext();
|
|
|
|
EXHIBIT 2: IMPLEMENTING CUSTOM BACKEND / CUSTOM ENGINE
|
|
|
|
// Application init: create a dear imgui context, setup some options, load fonts
|
|
ImGui::CreateContext();
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
// TODO: Set optional io.ConfigFlags values, e.g. 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard' to enable
|
|
keyboard controls.
|
|
// TODO: Fill optional fields of the io structure later.
|
|
// TODO: Load TTF/OTF fonts if you don't want to use the default font.
|
|
|
|
// Build and load the texture atlas into a texture
|
|
// (In the examples/ app this is usually done within the ImGui_ImplXXX_Init() function from one of the demo
|
|
Renderer) int width, height; unsigned char* pixels = NULL; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
|
|
|
|
// At this point you've got the texture data and you need to upload that to your graphic system:
|
|
// After we have created the texture, store its pointer/identifier (_in whichever format your engine uses_) in
|
|
'io.Fonts->TexID'.
|
|
// This will be passed back to your via the renderer. Basically ImTextureID == void*. Read FAQ for details about
|
|
ImTextureID. MyTexture* texture = MyEngine::CreateTextureFromMemoryPixels(pixels, width, height, TEXTURE_TYPE_RGBA32)
|
|
io.Fonts->SetTexID((void*)texture);
|
|
|
|
// Application main loop
|
|
while (true)
|
|
{
|
|
// Setup low-level inputs, e.g. on Win32: calling GetKeyboardState(), or write to those fields from your Windows
|
|
message handlers, etc.
|
|
// (In the examples/ app this is usually done within the ImGui_ImplXXX_NewFrame() function from one of the demo
|
|
Platform Backends) io.DeltaTime = 1.0f/60.0f; // set the time elapsed since the previous frame (in seconds)
|
|
io.DisplaySize.x = 1920.0f; // set the current display width
|
|
io.DisplaySize.y = 1280.0f; // set the current display height here
|
|
io.MousePos = my_mouse_pos; // set the mouse position
|
|
io.MouseDown[0] = my_mouse_buttons[0]; // set the mouse button states
|
|
io.MouseDown[1] = my_mouse_buttons[1];
|
|
|
|
// Call NewFrame(), after this point you can use ImGui::* functions anytime
|
|
// (So you want to try calling NewFrame() as early as you can in your main loop to be able to use Dear ImGui
|
|
everywhere) ImGui::NewFrame();
|
|
|
|
// Most of your application code here
|
|
ImGui::Text("Hello, world!");
|
|
MyGameUpdate(); // may use any Dear ImGui functions, e.g. ImGui::Begin("My window"); ImGui::Text("Hello,
|
|
world!"); ImGui::End(); MyGameRender(); // may use any Dear ImGui functions as well!
|
|
|
|
// Render dear imgui, swap buffers
|
|
// (You want to try calling EndFrame/Render as late as you can, to be able to use Dear ImGui in your own game
|
|
rendering code) ImGui::EndFrame(); ImGui::Render(); ImDrawData* draw_data = ImGui::GetDrawData();
|
|
MyImGuiRenderFunction(draw_data);
|
|
SwapBuffers();
|
|
}
|
|
|
|
// Shutdown
|
|
ImGui::DestroyContext();
|
|
|
|
To decide whether to dispatch mouse/keyboard inputs to Dear ImGui to the rest of your application,
|
|
you should read the 'io.WantCaptureMouse', 'io.WantCaptureKeyboard' and 'io.WantTextInput' flags!
|
|
Please read the FAQ and example applications for details about this!
|
|
|
|
|
|
HOW A SIMPLE RENDERING FUNCTION MAY LOOK LIKE
|
|
---------------------------------------------
|
|
The backends in impl_impl_XXX.cpp files contain many working implementations of a rendering function.
|
|
|
|
void void MyImGuiRenderFunction(ImDrawData* draw_data)
|
|
{
|
|
// TODO: Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled
|
|
// TODO: Setup viewport covering draw_data->DisplayPos to draw_data->DisplayPos + draw_data->DisplaySize
|
|
// TODO: Setup orthographic projection matrix cover draw_data->DisplayPos to draw_data->DisplayPos +
|
|
draw_data->DisplaySize
|
|
// TODO: Setup shader: vertex { float2 pos, float2 uv, u32 color }, fragment shader sample color from 1 texture,
|
|
multiply by vertex color. for (int n = 0; n < draw_data->CmdListsCount; n++)
|
|
{
|
|
const ImDrawList* cmd_list = draw_data->CmdLists[n];
|
|
const ImDrawVert* vtx_buffer = cmd_list->VtxBuffer.Data; // vertex buffer generated by Dear ImGui
|
|
const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data; // index buffer generated by Dear ImGui
|
|
for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
|
|
{
|
|
const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
|
|
if (pcmd->UserCallback)
|
|
{
|
|
pcmd->UserCallback(cmd_list, pcmd);
|
|
}
|
|
else
|
|
{
|
|
// The texture for the draw call is specified by pcmd->GetTexID().
|
|
// The vast majority of draw calls will use the Dear ImGui texture atlas, which value you have set
|
|
yourself during initialization. MyEngineBindTexture((MyTexture*)pcmd->GetTexID());
|
|
|
|
// We are using scissoring to clip some objects. All low-level graphics API should support it.
|
|
// - If your engine doesn't support scissoring yet, you may ignore this at first. You will get some
|
|
small glitches
|
|
// (some elements visible outside their bounds) but you can fix that once everything else works!
|
|
// - Clipping coordinates are provided in imgui coordinates space:
|
|
// - For a given viewport, draw_data->DisplayPos == viewport->Pos and draw_data->DisplaySize ==
|
|
viewport->Size
|
|
// - In a single viewport application, draw_data->DisplayPos == (0,0) and draw_data->DisplaySize ==
|
|
io.DisplaySize, but always use GetMainViewport()->Pos/Size instead of hardcoding those values.
|
|
// - In the interest of supporting multi-viewport applications (see 'docking' branch on github),
|
|
// always subtract draw_data->DisplayPos from clipping bounds to convert them to your viewport
|
|
space.
|
|
// - Note that pcmd->ClipRect contains Min+Max bounds. Some graphics API may use Min+Max, other may use
|
|
Min+Size (size being Max-Min) ImVec2 pos = draw_data->DisplayPos; MyEngineScissor((int)(pcmd->ClipRect.x - pos.x),
|
|
(int)(pcmd->ClipRect.y - pos.y), (int)(pcmd->ClipRect.z - pos.x), (int)(pcmd->ClipRect.w - pos.y));
|
|
|
|
// Render 'pcmd->ElemCount/3' indexed triangles.
|
|
// By default the indices ImDrawIdx are 16-bit, you can change them to 32-bit in imconfig.h if your
|
|
engine doesn't support 16-bit indices. MyEngineDrawIndexedTriangles(pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ?
|
|
GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer, vtx_buffer);
|
|
}
|
|
idx_buffer += pcmd->ElemCount;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
USING GAMEPAD/KEYBOARD NAVIGATION CONTROLS
|
|
------------------------------------------
|
|
- The gamepad/keyboard navigation is fairly functional and keeps being improved.
|
|
- Gamepad support is particularly useful to use Dear ImGui on a console system (e.g. PS4, Switch, XB1) without a mouse!
|
|
- You can ask questions and report issues at https://github.com/ocornut/imgui/issues/787
|
|
- The initial focus was to support game controllers, but keyboard is becoming increasingly and decently usable.
|
|
- Keyboard:
|
|
- Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard to enable.
|
|
NewFrame() will automatically fill io.NavInputs[] based on your io.KeysDown[] + io.KeyMap[] arrays.
|
|
- When keyboard navigation is active (io.NavActive + ImGuiConfigFlags_NavEnableKeyboard), the io.WantCaptureKeyboard
|
|
flag will be set. For more advanced uses, you may want to read from:
|
|
- io.NavActive: true when a window is focused and it doesn't have the ImGuiWindowFlags_NoNavInputs flag set.
|
|
- io.NavVisible: true when the navigation cursor is visible (and usually goes false when mouse is used).
|
|
- or query focus information with e.g. IsWindowFocused(ImGuiFocusedFlags_AnyWindow), IsItemFocused() etc.
|
|
functions. Please reach out if you think the game vs navigation input sharing could be improved.
|
|
- Gamepad:
|
|
- Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad to enable.
|
|
- Backend: Set io.BackendFlags |= ImGuiBackendFlags_HasGamepad + fill the io.NavInputs[] fields before calling
|
|
NewFrame(). Note that io.NavInputs[] is cleared by EndFrame().
|
|
- See 'enum ImGuiNavInput_' in imgui.h for a description of inputs. For each entry of io.NavInputs[], set the
|
|
following values: 0.0f= not held. 1.0f= fully held. Pass intermediate 0.0f..1.0f values for analog triggers/sticks.
|
|
- We use a simple >0.0f test for activation testing, and won't attempt to test for a dead-zone.
|
|
Your code will probably need to transform your raw inputs (such as e.g. remapping your 0.2..0.9 raw input range to
|
|
0.0..1.0 imgui range, etc.).
|
|
- You can download PNG/PSD files depicting the gamepad controls for common controllers at:
|
|
http://dearimgui.org/controls_sheets
|
|
- If you need to share inputs between your game and the imgui parts, the easiest approach is to go all-or-nothing,
|
|
with a buttons combo to toggle the target. Please reach out if you think the game vs navigation input sharing could be
|
|
improved.
|
|
- Mouse:
|
|
- PS4 users: Consider emulating a mouse cursor with DualShock4 touch pad or a spare analog stick as a
|
|
mouse-emulation fallback.
|
|
- Consoles/Tablet/Phone users: Consider using a Synergy 1.x server (on your PC) + uSynergy.c (on your
|
|
console/tablet/phone app) to share your PC mouse/keyboard.
|
|
- On a TV/console system where readability may be lower or mouse inputs may be awkward, you may want to set the
|
|
ImGuiConfigFlags_NavEnableSetMousePos flag. Enabling ImGuiConfigFlags_NavEnableSetMousePos +
|
|
ImGuiBackendFlags_HasSetMousePos instructs dear imgui to move your mouse cursor along with navigation movements. When
|
|
enabled, the NewFrame() function may alter 'io.MousePos' and set 'io.WantSetMousePos' to notify you that it wants the
|
|
mouse cursor to be moved. When that happens your backend NEEDS to move the OS or underlying mouse cursor on the next
|
|
frame. Some of the backends in examples/ do that. (If you set the NavEnableSetMousePos flag but don't honor
|
|
'io.WantSetMousePos' properly, imgui will misbehave as it will see your mouse moving back and forth!) (In a setup when
|
|
you may not have easy control over the mouse cursor, e.g. uSynergy.c doesn't expose moving remote mouse cursor, you may
|
|
want to set a boolean to ignore your other external mouse positions until the external source is moved again.)
|
|
|
|
|
|
API BREAKING CHANGES
|
|
====================
|
|
|
|
Occasionally introducing changes that are breaking the API. We try to make the breakage minor and easy to fix.
|
|
Below is a change-log of API breaking changes only. If you are using one of the functions listed, expect to have to fix
|
|
some code. When you are not sure about an old symbol or function name, try using the Search/Find function of your IDE to
|
|
look for comments or references in all imgui files. You can read releases logs https://github.com/ocornut/imgui/releases
|
|
for more details.
|
|
|
|
(Docking/Viewport Branch)
|
|
- 2021/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.
|
|
If you query mouse positions to interact with non-imgui coordinates you will need to offset
|
|
them, e.g. subtract GetWindowViewport()->Pos.
|
|
- 2021/XX/XX (1.XX) - Moved IME support functions from io.ImeSetInputScreenPosFn, io.ImeWindowHandle to the PlatformIO
|
|
api.
|
|
|
|
|
|
- 2021/07/26 (1.84) - commented out redirecting functions/enums names that were marked obsolete in 1.67 and 1.69 (March
|
|
2019):
|
|
- ImGui::GetOverlayDrawList() -> use ImGui::GetForegroundDrawList()
|
|
- ImFont::GlyphRangesBuilder -> use ImFontGlyphRangesBuilder
|
|
- 2021/05/19 (1.83) - backends: obsoleted direct access to ImDrawCmd::TextureId in favor of calling
|
|
ImDrawCmd::GetTexID().
|
|
- if you are using official backends from the source tree: you have nothing to do.
|
|
- if you have copied old backend code or using your own: change access to draw_cmd->TextureId to
|
|
draw_cmd->GetTexID().
|
|
- 2021/03/12 (1.82) - upgraded ImDrawList::AddRect(), AddRectFilled(), PathRect() to use ImDrawFlags instead of
|
|
ImDrawCornersFlags.
|
|
- ImDrawCornerFlags_TopLeft -> use ImDrawFlags_RoundCornersTopLeft
|
|
- ImDrawCornerFlags_BotRight -> use ImDrawFlags_RoundCornersBottomRight
|
|
- ImDrawCornerFlags_None -> use ImDrawFlags_RoundCornersNone etc.
|
|
flags now sanely defaults to 0 instead of 0x0F, consistent with all other flags in the API.
|
|
breaking: the default with rounding > 0.0f is now "round all corners" vs old implicit "round no
|
|
corners":
|
|
- rounding == 0.0f + flags == 0 --> meant no rounding --> unchanged (common use)
|
|
- rounding > 0.0f + flags != 0 --> meant rounding --> unchanged (common use)
|
|
- rounding == 0.0f + flags != 0 --> meant no rounding --> unchanged (unlikely use)
|
|
- rounding > 0.0f + flags == 0 --> meant no rounding --> BREAKING (unlikely use): will now
|
|
round all corners --> use ImDrawFlags_RoundCornersNone or rounding == 0.0f. this ONLY matters for hard coded use of 0 +
|
|
rounding > 0.0f. Use of named ImDrawFlags_RoundCornersNone (new) or ImDrawCornerFlags_None (old) are ok. the old
|
|
ImDrawCornersFlags used awkward default values of ~0 or 0xF (4 lower bits set) to signify "round all corners" and we
|
|
sometimes encouraged using them as shortcuts. legacy path still support use of hard coded ~0 or any value from 0x1 or
|
|
0xF. They will behave the same with legacy paths enabled (will assert otherwise).
|
|
- 2021/03/11 (1.82) - removed redirecting functions/enums names that were marked obsolete in 1.66 (September 2018):
|
|
- ImGui::SetScrollHere() -> use ImGui::SetScrollHereY()
|
|
- 2021/03/11 (1.82) - clarified that ImDrawList::PathArcTo(), ImDrawList::PathArcToFast() won't render with radius <
|
|
0.0f. Previously it sorts of accidentally worked but would generally lead to counter-clockwise paths and have an effect
|
|
on anti-aliasing.
|
|
- 2021/03/10 (1.82) - upgraded ImDrawList::AddPolyline() and PathStroke() "bool closed" parameter to "ImDrawFlags
|
|
flags". The matching ImDrawFlags_Closed value is guaranteed to always stay == 1 in the future.
|
|
- 2021/02/22 (1.82) - (*undone in 1.84*) win32+mingw: Re-enabled IME functions by default even under MinGW. In July
|
|
2016, issue #738 had me incorrectly disable those default functions for MinGW. MinGW users should: either link with
|
|
-limm32, either set their imconfig file with '#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS'.
|
|
- 2021/02/17 (1.82) - renamed rarely used style.CircleSegmentMaxError (old default = 1.60f) to
|
|
style.CircleTessellationMaxError (new default = 0.30f) as the meaning of the value changed.
|
|
- 2021/02/03 (1.81) - renamed ListBoxHeader(const char* label, ImVec2 size) to BeginListBox(). Kept inline redirection
|
|
function (will obsolete).
|
|
- removed ListBoxHeader(const char* label, int items_count, int height_in_items = -1) in favor of
|
|
specifying size. Kept inline redirection function (will obsolete).
|
|
- renamed ListBoxFooter() to EndListBox(). Kept inline redirection function (will obsolete).
|
|
- 2021/01/26 (1.81) - removed ImGuiFreeType::BuildFontAtlas(). Kept inline redirection function. Prefer using '#define
|
|
IMGUI_ENABLE_FREETYPE', but there's a runtime selection path available too. The shared extra flags parameters (very
|
|
rarely used) are now stored in ImFontAtlas::FontBuilderFlags.
|
|
- renamed ImFontConfig::RasterizerFlags (used by FreeType) to ImFontConfig::FontBuilderFlags.
|
|
- renamed ImGuiFreeType::XXX flags to ImGuiFreeTypeBuilderFlags_XXX for consistency with other API.
|
|
- 2020/10/12 (1.80) - removed redirecting functions/enums that were marked obsolete in 1.63 (August 2018):
|
|
- ImGui::IsItemDeactivatedAfterChange() -> use ImGui::IsItemDeactivatedAfterEdit().
|
|
- ImGuiCol_ModalWindowDarkening -> use ImGuiCol_ModalWindowDimBg
|
|
- ImGuiInputTextCallback -> use ImGuiTextEditCallback
|
|
- ImGuiInputTextCallbackData -> use ImGuiTextEditCallbackData
|
|
- 2020/12/21 (1.80) - renamed ImDrawList::AddBezierCurve() to AddBezierCubic(), and PathBezierCurveTo() to
|
|
PathBezierCubicCurveTo(). Kept inline redirection function (will obsolete).
|
|
- 2020/12/04 (1.80) - added imgui_tables.cpp file! Manually constructed project files will need the new file added!
|
|
- 2020/11/18 (1.80) - renamed undocumented/internals ImGuiColumnsFlags_* to ImGuiOldColumnFlags_* in prevision of
|
|
incoming Tables API.
|
|
- 2020/11/03 (1.80) - renamed io.ConfigWindowsMemoryCompactTimer to io.ConfigMemoryCompactTimer as the feature will
|
|
apply to other data structures
|
|
- 2020/10/14 (1.80) - backends: moved all backends files (imgui_impl_XXXX.cpp, imgui_impl_XXXX.h) from examples/ to
|
|
backends/.
|
|
- 2020/10/12 (1.80) - removed redirecting functions/enums that were marked obsolete in 1.60 (April 2018):
|
|
- io.RenderDrawListsFn pointer -> use ImGui::GetDrawData() value and call the render
|
|
function of your backend
|
|
- ImGui::IsAnyWindowFocused() -> use ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow)
|
|
- ImGui::IsAnyWindowHovered() -> use ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow)
|
|
- ImGuiStyleVar_Count_ -> use ImGuiStyleVar_COUNT
|
|
- ImGuiMouseCursor_Count_ -> use ImGuiMouseCursor_COUNT
|
|
- removed redirecting functions names that were marked obsolete in 1.61 (May 2018):
|
|
- InputFloat (... int decimal_precision ...) -> use InputFloat (... const char* format ...) with
|
|
format = "%.Xf" where X is your value for decimal_precision.
|
|
- same for InputFloat2()/InputFloat3()/InputFloat4() variants taking a `int decimal_precision`
|
|
parameter.
|
|
- 2020/10/05 (1.79) - removed ImGuiListClipper: Renamed constructor parameters which created an ambiguous alternative
|
|
to using the ImGuiListClipper::Begin() function, with misleading edge cases (note: imgui_memory_editor <0.40 from
|
|
imgui_club/ used this old clipper API. Update your copy if needed).
|
|
- 2020/09/25 (1.79) - renamed ImGuiSliderFlags_ClampOnInput to ImGuiSliderFlags_AlwaysClamp. Kept redirection enum
|
|
(will obsolete sooner because previous name was added recently).
|
|
- 2020/09/25 (1.79) - renamed style.TabMinWidthForUnselectedCloseButton to style.TabMinWidthForCloseButton.
|
|
- 2020/09/21 (1.79) - renamed OpenPopupContextItem() back to OpenPopupOnItemClick(), reverting the change from 1.77.
|
|
For varieties of reason this is more self-explanatory.
|
|
- 2020/09/21 (1.79) - removed return value from OpenPopupOnItemClick() - returned true on mouse release on an item -
|
|
because it is inconsistent with other popup APIs and makes others misleading. It's also and unnecessary: you can use
|
|
IsWindowAppearing() after BeginPopup() for a similar result.
|
|
- 2020/09/17 (1.79) - removed ImFont::DisplayOffset in favor of ImFontConfig::GlyphOffset. DisplayOffset was applied
|
|
after scaling and not very meaningful/useful outside of being needed by the default ProggyClean font. If you scaled this
|
|
value after calling AddFontDefault(), this is now done automatically. It was also getting in the way of better font
|
|
scaling, so let's get rid of it now!
|
|
- 2020/08/17 (1.78) - obsoleted use of the trailing 'float power=1.0f' parameter for DragFloat(), DragFloat2(),
|
|
DragFloat3(), DragFloat4(), DragFloatRange2(), DragScalar(), DragScalarN(), SliderFloat(), SliderFloat2(),
|
|
SliderFloat3(), SliderFloat4(), SliderScalar(), SliderScalarN(), VSliderFloat() and VSliderScalar(). replaced the 'float
|
|
power=1.0f' argument with integer-based flags defaulting to 0 (as with all our flags). worked out a
|
|
backward-compatibility scheme so hopefully most C++ codebase should not be affected. in short, when calling those
|
|
functions:
|
|
- if you omitted the 'power' parameter (likely!), you are not affected.
|
|
- if you set the 'power' parameter to 1.0f (same as previous default value): 1/ your compiler may
|
|
warn on float>int conversion, 2/ everything else will work. 3/ you can replace the 1.0f value with 0 to fix the warning,
|
|
and be technically correct.
|
|
- if you set the 'power' parameter to >1.0f (to enable non-linear editing): 1/ your compiler may
|
|
warn on float>int conversion, 2/ code will assert at runtime, 3/ in case asserts are disabled, the code will not crash
|
|
and enable the _Logarithmic flag. 4/ you can replace the >1.0f value with ImGuiSliderFlags_Logarithmic to fix the
|
|
warning/assert and get a _similar_ effect as previous uses of power >1.0f. see
|
|
https://github.com/ocornut/imgui/issues/3361 for all details. kept inline redirection functions (will obsolete) apart
|
|
for: DragFloatRange2(), VSliderFloat(), VSliderScalar(). For those three the 'float power=1.0f' version was removed
|
|
directly as they were most unlikely ever used. for shared code, you can version check at compile-time with `#if
|
|
IMGUI_VERSION_NUM >= 17704`.
|
|
- obsoleted use of v_min > v_max in DragInt, DragFloat, DragScalar to lock edits (introduced
|
|
in 1.73, was not demoed nor documented very), will be replaced by a more generic ReadOnly feature. You may use the
|
|
ImGuiSliderFlags_ReadOnly internal flag in the meantime.
|
|
- 2020/06/23 (1.77) - removed BeginPopupContextWindow(const char*, int mouse_button, bool also_over_items) in favor of
|
|
BeginPopupContextWindow(const char*, ImGuiPopupFlags flags) with ImGuiPopupFlags_NoOverItems.
|
|
- 2020/06/15 (1.77) - renamed OpenPopupOnItemClick() to OpenPopupContextItem(). Kept inline redirection function (will
|
|
obsolete). [NOTE: THIS WAS REVERTED IN 1.79]
|
|
- 2020/06/15 (1.77) - removed CalcItemRectClosestPoint() entry point which was made obsolete and asserting in December
|
|
2017.
|
|
- 2020/04/23 (1.77) - removed unnecessary ID (first arg) of ImFontAtlas::AddCustomRectRegular().
|
|
- 2020/01/22 (1.75) - ImDrawList::AddCircle()/AddCircleFilled() functions don't accept negative radius any more.
|
|
- 2019/12/17 (1.75) - [undid this change in 1.76] made Columns() limited to 64 columns by asserting above that limit.
|
|
While the current code technically supports it, future code may not so we're putting the restriction ahead.
|
|
- 2019/12/13 (1.75) - [imgui_internal.h] changed ImRect() default constructor initializes all fields to 0.0f instead of
|
|
(FLT_MAX,FLT_MAX,-FLT_MAX,-FLT_MAX). If you used ImRect::Add() to create bounding boxes by adding multiple points into
|
|
it, you may need to fix your initial value.
|
|
- 2019/12/08 (1.75) - removed redirecting functions/enums that were marked obsolete in 1.53 (December 2017):
|
|
- ShowTestWindow() -> use ShowDemoWindow()
|
|
- IsRootWindowFocused() -> use IsWindowFocused(ImGuiFocusedFlags_RootWindow)
|
|
- IsRootWindowOrAnyChildFocused() -> use
|
|
IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)
|
|
- SetNextWindowContentWidth(w) -> use SetNextWindowContentSize(ImVec2(w, 0.0f)
|
|
- GetItemsLineHeightWithSpacing() -> use GetFrameHeightWithSpacing()
|
|
- ImGuiCol_ChildWindowBg -> use ImGuiCol_ChildBg
|
|
- ImGuiStyleVar_ChildWindowRounding -> use ImGuiStyleVar_ChildRounding
|
|
- ImGuiTreeNodeFlags_AllowOverlapMode -> use ImGuiTreeNodeFlags_AllowItemOverlap
|
|
- IMGUI_DISABLE_TEST_WINDOWS -> use IMGUI_DISABLE_DEMO_WINDOWS
|
|
- 2019/12/08 (1.75) - obsoleted calling ImDrawList::PrimReserve() with a negative count (which was vaguely documented
|
|
and rarely if ever used). Instead, we added an explicit PrimUnreserve() API.
|
|
- 2019/12/06 (1.75) - removed implicit default parameter to IsMouseDragging(int button = 0) to be consistent with other
|
|
mouse functions (none of the other functions have it).
|
|
- 2019/11/21 (1.74) - ImFontAtlas::AddCustomRectRegular() now requires an ID larger than 0x110000 (instead of 0x10000)
|
|
to conform with supporting Unicode planes 1-16 in a future update. ID below 0x110000 will now assert.
|
|
- 2019/11/19 (1.74) - renamed IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS to IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS for
|
|
consistency.
|
|
- 2019/11/19 (1.74) - renamed IMGUI_DISABLE_MATH_FUNCTIONS to IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS for consistency.
|
|
- 2019/10/22 (1.74) - removed redirecting functions/enums that were marked obsolete in 1.52 (October 2017):
|
|
- Begin() [old 5 args version] -> use Begin() [3 args], use SetNextWindowSize()
|
|
SetNextWindowBgAlpha() if needed
|
|
- IsRootWindowOrAnyChildHovered() -> use
|
|
IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows)
|
|
- AlignFirstTextHeightToWidgets() -> use AlignTextToFramePadding()
|
|
- SetNextWindowPosCenter() -> use SetNextWindowPos() with a pivot of (0.5f, 0.5f)
|
|
- ImFont::Glyph -> use ImFontGlyph
|
|
- 2019/10/14 (1.74) - inputs: Fixed a miscalculation in the keyboard/mouse "typematic" repeat delay/rate calculation,
|
|
used by keys and e.g. repeating mouse buttons as well as the GetKeyPressedAmount() function. if you were using a
|
|
non-default value for io.KeyRepeatRate (previous default was 0.250), you can add +io.KeyRepeatDelay to it to compensate
|
|
for the fix. The function was triggering on: 0.0 and (delay+rate*N) where (N>=1). Fixed formula responds to (N>=0).
|
|
Effectively it made io.KeyRepeatRate behave like it was set to (io.KeyRepeatRate + io.KeyRepeatDelay). If you never
|
|
altered io.KeyRepeatRate nor used GetKeyPressedAmount() this won't affect you.
|
|
- 2019/07/15 (1.72) - removed TreeAdvanceToLabelPos() which is rarely used and only does SetCursorPosX(GetCursorPosX()
|
|
+ GetTreeNodeToLabelSpacing()). Kept redirection function (will obsolete).
|
|
- 2019/07/12 (1.72) - renamed ImFontAtlas::CustomRect to ImFontAtlasCustomRect. Kept redirection typedef (will
|
|
obsolete).
|
|
- 2019/06/14 (1.72) - removed redirecting functions/enums names that were marked obsolete in 1.51 (June 2017):
|
|
ImGuiCol_Column*, ImGuiSetCond_*, IsItemHoveredRect(), IsPosHoveringAnyWindow(), IsMouseHoveringAnyWindow(),
|
|
IsMouseHoveringWindow(), IMGUI_ONCE_UPON_A_FRAME. Grep this log for details and new names, or see how they were
|
|
implemented until 1.71.
|
|
- 2019/06/07 (1.71) - rendering of child window outer decorations (bg color, border, scrollbars) is now performed as
|
|
part of the parent window. If you have overlapping child windows in a same parent, and relied on their relative z-order
|
|
to be mapped to their submission order, this will affect your rendering. This optimization is disabled if the parent
|
|
window has no visual output, because it appears to be the most common situation leading to the creation of overlapping
|
|
child windows. Please reach out if you are affected.
|
|
- 2019/05/13 (1.71) - renamed SetNextTreeNodeOpen() to SetNextItemOpen(). Kept inline redirection function (will
|
|
obsolete).
|
|
- 2019/05/11 (1.71) - changed io.AddInputCharacter(unsigned short c) signature to io.AddInputCharacter(unsigned int c).
|
|
- 2019/04/29 (1.70) - improved ImDrawList thick strokes (>1.0f) preserving correct thickness up to 90 degrees angles
|
|
(e.g. rectangles). If you have custom rendering using thick lines, they will appear thicker now.
|
|
- 2019/04/29 (1.70) - removed GetContentRegionAvailWidth(), use GetContentRegionAvail().x instead. Kept inline
|
|
redirection function (will obsolete).
|
|
- 2019/03/04 (1.69) - renamed GetOverlayDrawList() to GetForegroundDrawList(). Kept redirection function (will
|
|
obsolete).
|
|
- 2019/02/26 (1.69) - renamed ImGuiColorEditFlags_RGB/ImGuiColorEditFlags_HSV/ImGuiColorEditFlags_HEX to
|
|
ImGuiColorEditFlags_DisplayRGB/ImGuiColorEditFlags_DisplayHSV/ImGuiColorEditFlags_DisplayHex. Kept redirection enums
|
|
(will obsolete).
|
|
- 2019/02/14 (1.68) - made it illegal/assert when io.DisplayTime == 0.0f (with an exception for the first frame). If
|
|
for some reason your time step calculation gives you a zero value, replace it with an arbitrarily small value!
|
|
- 2019/02/01 (1.68) - removed io.DisplayVisibleMin/DisplayVisibleMax (which were marked obsolete and removed from
|
|
viewport/docking branch already).
|
|
- 2019/01/06 (1.67) - renamed io.InputCharacters[], marked internal as was always intended. Please don't access
|
|
directly, and use AddInputCharacter() instead!
|
|
- 2019/01/06 (1.67) - renamed ImFontAtlas::GlyphRangesBuilder to ImFontGlyphRangesBuilder. Kept redirection typedef
|
|
(will obsolete).
|
|
- 2018/12/20 (1.67) - made it illegal to call Begin("") with an empty string. This somehow half-worked before but had
|
|
various undesirable side-effects.
|
|
- 2018/12/10 (1.67) - renamed io.ConfigResizeWindowsFromEdges to io.ConfigWindowsResizeFromEdges as we are doing a
|
|
large pass on configuration flags.
|
|
- 2018/10/12 (1.66) - renamed misc/stl/imgui_stl.* to misc/cpp/imgui_stdlib.* in prevision for other C++ helper files.
|
|
- 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)
|
|
- 2018/08/31 (1.64) - added imgui_widgets.cpp file, extracted and moved widgets code out of imgui.cpp into
|
|
imgui_widgets.cpp. Re-ordered some of the code remaining in imgui.cpp. NONE OF THE FUNCTIONS HAVE CHANGED. THE CODE IS
|
|
SEMANTICALLY 100% IDENTICAL, BUT _EVERY_ FUNCTION HAS BEEN MOVED. Because of this, any local modifications to imgui.cpp
|
|
will likely conflict when you update. Read docs/CHANGELOG.txt for suggestions.
|
|
- 2018/08/22 (1.63) - renamed IsItemDeactivatedAfterChange() to IsItemDeactivatedAfterEdit() for consistency with new
|
|
IsItemEdited() API. Kept redirection function (will obsolete soonish as IsItemDeactivatedAfterChange() is very recent).
|
|
- 2018/08/21 (1.63) - renamed ImGuiTextEditCallback to ImGuiInputTextCallback, ImGuiTextEditCallbackData to
|
|
ImGuiInputTextCallbackData for consistency. Kept redirection types (will obsolete).
|
|
- 2018/08/21 (1.63) - removed ImGuiInputTextCallbackData::ReadOnly since it is a duplication of
|
|
(ImGuiInputTextCallbackData::Flags & ImGuiInputTextFlags_ReadOnly).
|
|
- 2018/08/01 (1.63) - removed per-window ImGuiWindowFlags_ResizeFromAnySide beta flag in favor of a global
|
|
io.ConfigResizeWindowsFromEdges [update 1.67 renamed to ConfigWindowsResizeFromEdges] to enable the feature.
|
|
- 2018/08/01 (1.63) - renamed io.OptCursorBlink to io.ConfigCursorBlink [-> io.ConfigInputTextCursorBlink in 1.65],
|
|
io.OptMacOSXBehaviors to ConfigMacOSXBehaviors for consistency.
|
|
- 2018/07/22 (1.63) - changed ImGui::GetTime() return value from float to double to avoid accumulating floating point
|
|
imprecisions over time.
|
|
- 2018/07/08 (1.63) - style: renamed ImGuiCol_ModalWindowDarkening to ImGuiCol_ModalWindowDimBg for consistency with
|
|
other features. Kept redirection enum (will obsolete).
|
|
- 2018/06/08 (1.62) - examples: the imgui_impl_XXX files have been split to separate platform (Win32, GLFW, SDL2, etc.)
|
|
from renderer (DX11, OpenGL, Vulkan, etc.). old backends will still work as is, however prefer using the separated
|
|
backends as they will be updated to support multi-viewports. when adopting new backends follow the main.cpp code of your
|
|
preferred examples/ folder to know which functions to call. in particular, note that old backends called
|
|
ImGui::NewFrame() at the end of their ImGui_ImplXXXX_NewFrame() function.
|
|
- 2018/06/06 (1.62) - renamed GetGlyphRangesChinese() to GetGlyphRangesChineseFull() to distinguish other variants and
|
|
discourage using the full set.
|
|
- 2018/06/06 (1.62) - TreeNodeEx()/TreeNodeBehavior(): the ImGuiTreeNodeFlags_CollapsingHeader helper now include the
|
|
ImGuiTreeNodeFlags_NoTreePushOnOpen flag. See Changelog for details.
|
|
- 2018/05/03 (1.61) - DragInt(): the default compile-time format string has been changed from "%.0f" to "%d", as we are
|
|
not using integers internally any more. If you used DragInt() with custom format strings, make sure you change them to
|
|
use %d or an integer-compatible format. To honor backward-compatibility, the DragInt() code will currently parse and
|
|
modify format strings to replace %*f with %d, giving time to users to upgrade their code. If you have
|
|
IMGUI_DISABLE_OBSOLETE_FUNCTIONS enabled, the code will instead assert! You may run a reg-exp search on your codebase
|
|
for e.g. "DragInt.*%f" to help you find them.
|
|
- 2018/04/28 (1.61) - obsoleted InputFloat() functions taking an optional "int decimal_precision" in favor of an
|
|
equivalent and more flexible "const char* format", consistent with other functions. Kept redirection functions (will
|
|
obsolete).
|
|
- 2018/04/09 (1.61) - IM_DELETE() helper function added in 1.60 doesn't clear the input _pointer_ reference, more
|
|
consistent with expectation and allows passing r-value.
|
|
- 2018/03/20 (1.60) - renamed io.WantMoveMouse to io.WantSetMousePos for consistency and ease of understanding (was
|
|
added in 1.52, _not_ used by core and only honored by some backend ahead of merging the Nav branch).
|
|
- 2018/03/12 (1.60) - removed ImGuiCol_CloseButton, ImGuiCol_CloseButtonActive, ImGuiCol_CloseButtonHovered as the
|
|
closing cross uses regular button colors now.
|
|
- 2018/03/08 (1.60) - changed ImFont::DisplayOffset.y to default to 0 instead of +1. Fixed rounding of Ascent/Descent
|
|
to match TrueType renderer. If you were adding or subtracting to ImFont::DisplayOffset check if your fonts are correctly
|
|
aligned vertically.
|
|
- 2018/03/03 (1.60) - renamed ImGuiStyleVar_Count_ to ImGuiStyleVar_COUNT and ImGuiMouseCursor_Count_ to
|
|
ImGuiMouseCursor_COUNT for consistency with other public enums.
|
|
- 2018/02/18 (1.60) - BeginDragDropSource(): temporarily removed the optional mouse_button=0 parameter because it is
|
|
not really usable in many situations at the moment.
|
|
- 2018/02/16 (1.60) - obsoleted the io.RenderDrawListsFn callback, you can call your graphics engine render function
|
|
after ImGui::Render(). Use ImGui::GetDrawData() to retrieve the ImDrawData* to display.
|
|
- 2018/02/07 (1.60) - reorganized context handling to be more explicit,
|
|
- YOU NOW NEED TO CALL ImGui::CreateContext() AT THE BEGINNING OF YOUR APP, AND CALL
|
|
ImGui::DestroyContext() AT THE END.
|
|
- removed Shutdown() function, as DestroyContext() serve this purpose.
|
|
- you may pass a ImFontAtlas* pointer to CreateContext() to share a font atlas between contexts.
|
|
Otherwise CreateContext() will create its own font atlas instance.
|
|
- removed allocator parameters from CreateContext(), they are now setup with
|
|
SetAllocatorFunctions(), and shared by all contexts.
|
|
- removed the default global context and font atlas instance, which were confusing for users of
|
|
DLL reloading and users of multiple contexts.
|
|
- 2018/01/31 (1.60) - moved sample TTF files from extra_fonts/ to misc/fonts/. If you loaded files directly from the
|
|
imgui repo you may need to update your paths.
|
|
- 2018/01/11 (1.60) - obsoleted IsAnyWindowHovered() in favor of IsWindowHovered(ImGuiHoveredFlags_AnyWindow). Kept
|
|
redirection function (will obsolete).
|
|
- 2018/01/11 (1.60) - obsoleted IsAnyWindowFocused() in favor of IsWindowFocused(ImGuiFocusedFlags_AnyWindow). Kept
|
|
redirection function (will obsolete).
|
|
- 2018/01/03 (1.60) - renamed ImGuiSizeConstraintCallback to ImGuiSizeCallback, ImGuiSizeConstraintCallbackData to
|
|
ImGuiSizeCallbackData.
|
|
- 2017/12/29 (1.60) - removed CalcItemRectClosestPoint() which was weird and not really used by anyone except demo
|
|
code. If you need it it's easy to replicate on your side.
|
|
- 2017/12/24 (1.53) - renamed the emblematic ShowTestWindow() function to ShowDemoWindow(). Kept redirection function
|
|
(will obsolete).
|
|
- 2017/12/21 (1.53) - ImDrawList: renamed style.AntiAliasedShapes to style.AntiAliasedFill for consistency and as a way
|
|
to explicitly break code that manipulate those flag at runtime. You can now manipulate ImDrawList::Flags
|
|
- 2017/12/21 (1.53) - ImDrawList: removed 'bool anti_aliased = true' final parameter of ImDrawList::AddPolyline() and
|
|
ImDrawList::AddConvexPolyFilled(). Prefer manipulating ImDrawList::Flags if you need to toggle them during the frame.
|
|
- 2017/12/14 (1.53) - 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.
|
|
- 2017/12/13 (1.53) - renamed GetItemsLineHeightWithSpacing() to GetFrameHeightWithSpacing(). Kept redirection function
|
|
(will obsolete).
|
|
- 2017/12/13 (1.53) - obsoleted IsRootWindowFocused() in favor of using IsWindowFocused(ImGuiFocusedFlags_RootWindow).
|
|
Kept redirection function (will obsolete).
|
|
- obsoleted IsRootWindowOrAnyChildFocused() in favor of using
|
|
IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows). Kept redirection function (will obsolete).
|
|
- 2017/12/12 (1.53) - renamed ImGuiTreeNodeFlags_AllowOverlapMode to ImGuiTreeNodeFlags_AllowItemOverlap. Kept
|
|
redirection enum (will obsolete).
|
|
- 2017/12/10 (1.53) - removed SetNextWindowContentWidth(), prefer using SetNextWindowContentSize(). Kept redirection
|
|
function (will obsolete).
|
|
- 2017/11/27 (1.53) - renamed ImGuiTextBuffer::append() helper to appendf(), appendv() to appendfv(). If you copied the
|
|
'Log' demo in your code, it uses appendv() so that needs to be renamed.
|
|
- 2017/11/18 (1.53) - Style, Begin: removed ImGuiWindowFlags_ShowBorders window flag. Borders are now fully set up in
|
|
the ImGuiStyle structure (see e.g. style.FrameBorderSize, style.WindowBorderSize). Use ImGui::ShowStyleEditor() to look
|
|
them up. Please note that the style system will keep evolving (hopefully stabilizing in Q1 2018), and so custom styles
|
|
will probably subtly break over time. It is recommended you use the StyleColorsClassic(), StyleColorsDark(),
|
|
StyleColorsLight() functions.
|
|
- 2017/11/18 (1.53) - Style: removed ImGuiCol_ComboBg in favor of combo boxes using ImGuiCol_PopupBg for consistency.
|
|
- 2017/11/18 (1.53) - Style: renamed ImGuiCol_ChildWindowBg to ImGuiCol_ChildBg.
|
|
- 2017/11/18 (1.53) - Style: renamed style.ChildWindowRounding to style.ChildRounding,
|
|
ImGuiStyleVar_ChildWindowRounding to ImGuiStyleVar_ChildRounding.
|
|
- 2017/11/02 (1.53) - obsoleted IsRootWindowOrAnyChildHovered() in favor of using
|
|
IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows);
|
|
- 2017/10/24 (1.52) - renamed IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCS/IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCS to
|
|
IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS/IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS for consistency.
|
|
- 2017/10/20 (1.52) - changed IsWindowHovered() default parameters behavior to return false if an item is active in
|
|
another window (e.g. click-dragging item from another window to this window). You can use the newly introduced
|
|
IsWindowHovered() flags to requests this specific behavior if you need it.
|
|
- 2017/10/20 (1.52) - marked IsItemHoveredRect()/IsMouseHoveringWindow() as obsolete, in favor of using the newly
|
|
introduced flags for IsItemHovered() and IsWindowHovered(). See https://github.com/ocornut/imgui/issues/1382 for
|
|
details. removed the IsItemRectHovered()/IsWindowRectHovered() names introduced in 1.51 since they were merely more
|
|
consistent names for the two functions we are now obsoleting. IsItemHoveredRect() -->
|
|
IsItemHovered(ImGuiHoveredFlags_RectOnly) IsMouseHoveringAnyWindow() --> IsWindowHovered(ImGuiHoveredFlags_AnyWindow)
|
|
IsMouseHoveringWindow() --> IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup |
|
|
ImGuiHoveredFlags_AllowWhenBlockedByActiveItem) [weird, old behavior]
|
|
- 2017/10/17 (1.52) - marked the old 5-parameters version of Begin() as obsolete (still available). Use
|
|
SetNextWindowSize()+Begin() instead!
|
|
- 2017/10/11 (1.52) - renamed AlignFirstTextHeightToWidgets() to AlignTextToFramePadding(). Kept inline redirection
|
|
function (will obsolete).
|
|
- 2017/09/26 (1.52) - renamed ImFont::Glyph to ImFontGlyph. Kept redirection typedef (will obsolete).
|
|
- 2017/09/25 (1.52) - removed SetNextWindowPosCenter() because SetNextWindowPos() now has the optional pivot
|
|
information to do the same and more. Kept redirection function (will obsolete).
|
|
- 2017/08/25 (1.52) - io.MousePos needs to be set to ImVec2(-FLT_MAX,-FLT_MAX) when mouse is unavailable/missing.
|
|
Previously ImVec2(-1,-1) was enough but we now accept negative mouse coordinates. In your backend if you need to support
|
|
unavailable mouse, make sure to replace "io.MousePos = ImVec2(-1,-1)" with "io.MousePos = ImVec2(-FLT_MAX,-FLT_MAX)".
|
|
- 2017/08/22 (1.51) - renamed IsItemHoveredRect() to IsItemRectHovered(). Kept inline redirection function (will
|
|
obsolete). -> (1.52) use IsItemHovered(ImGuiHoveredFlags_RectOnly)!
|
|
- renamed IsMouseHoveringAnyWindow() to IsAnyWindowHovered() for consistency. Kept inline
|
|
redirection function (will obsolete).
|
|
- renamed IsMouseHoveringWindow() to IsWindowRectHovered() for consistency. Kept inline redirection
|
|
function (will obsolete).
|
|
- 2017/08/20 (1.51) - renamed GetStyleColName() to GetStyleColorName() for consistency.
|
|
- 2017/08/20 (1.51) - added PushStyleColor(ImGuiCol idx, ImU32 col) overload, which _might_ cause an "ambiguous call"
|
|
compilation error if you are using ImColor() with implicit cast. Cast to ImU32 or ImVec4 explicily to fix.
|
|
- 2017/08/15 (1.51) - marked the weird IMGUI_ONCE_UPON_A_FRAME helper macro as obsolete. prefer using the more explicit
|
|
ImGuiOnceUponAFrame type.
|
|
- 2017/08/15 (1.51) - changed parameter order for BeginPopupContextWindow() from (const char*,int buttons,bool
|
|
also_over_items) to (const char*,int buttons,bool also_over_items). Note that most calls relied on default parameters
|
|
completely.
|
|
- 2017/08/13 (1.51) - renamed ImGuiCol_Column to ImGuiCol_Separator, ImGuiCol_ColumnHovered to
|
|
ImGuiCol_SeparatorHovered, ImGuiCol_ColumnActive to ImGuiCol_SeparatorActive. Kept redirection enums (will obsolete).
|
|
- 2017/08/11 (1.51) - renamed ImGuiSetCond_Always to ImGuiCond_Always, ImGuiSetCond_Once to ImGuiCond_Once,
|
|
ImGuiSetCond_FirstUseEver to ImGuiCond_FirstUseEver, ImGuiSetCond_Appearing to ImGuiCond_Appearing. Kept redirection
|
|
enums (will obsolete).
|
|
- 2017/08/09 (1.51) - removed ValueColor() helpers, they are equivalent to calling Text(label) + SameLine() +
|
|
ColorButton().
|
|
- 2017/08/08 (1.51) - removed ColorEditMode() and ImGuiColorEditMode in favor of ImGuiColorEditFlags and parameters to
|
|
the various Color*() functions. The SetColorEditOptions() allows to initialize default but the user can still change
|
|
them with right-click context menu.
|
|
- changed prototype of 'ColorEdit4(const char* label, float col[4], bool show_alpha = true)' to
|
|
'ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags = 0)', where passing flags = 0x01 is a safe no-op
|
|
(hello dodgy backward compatibility!). - check and run the demo window, under "Color/Picker Widgets", to understand the
|
|
various new options.
|
|
- changed prototype of rarely used 'ColorButton(ImVec4 col, bool small_height = false, bool
|
|
outline_border = true)' to 'ColorButton(const char* desc_id, ImVec4 col, ImGuiColorEditFlags flags = 0, ImVec2 size =
|
|
ImVec2(0, 0))'
|
|
- 2017/07/20 (1.51) - removed IsPosHoveringAnyWindow(ImVec2), which was partly broken and misleading. ASSERT + redirect
|
|
user to io.WantCaptureMouse
|
|
- 2017/05/26 (1.50) - removed ImFontConfig::MergeGlyphCenterV in favor of a more multipurpose
|
|
ImFontConfig::GlyphOffset.
|
|
- 2017/05/01 (1.50) - renamed ImDrawList::PathFill() (rarely used directly) to ImDrawList::PathFillConvex() for
|
|
clarity.
|
|
- 2016/11/06 (1.50) - BeginChild(const char*) now applies the stack id to the provided label, consistently with other
|
|
functions as it should always have been. It shouldn't affect you unless (extremely unlikely) you were appending multiple
|
|
times to a same child from different locations of the stack id. If that's the case, generate an id with GetID() and use
|
|
it instead of passing string to BeginChild().
|
|
- 2016/10/15 (1.50) - avoid 'void* user_data' parameter to io.SetClipboardTextFn/io.GetClipboardTextFn pointers. We
|
|
pass io.ClipboardUserData to it.
|
|
- 2016/09/25 (1.50) - style.WindowTitleAlign is now a ImVec2 (ImGuiAlign enum was removed). set to (0.5f,0.5f) for
|
|
horizontal+vertical centering, (0.0f,0.0f) for upper-left, etc.
|
|
- 2016/07/30 (1.50) - SameLine(x) with x>0.0f is now relative to left of column/group if any, and not always to left of
|
|
window. This was sort of always the intent and hopefully, breakage should be minimal.
|
|
- 2016/05/12 (1.49) - title bar (using ImGuiCol_TitleBg/ImGuiCol_TitleBgActive colors) isn't rendered over a window
|
|
background (ImGuiCol_WindowBg color) anymore. If your TitleBg/TitleBgActive alpha was 1.0f or you are using the default
|
|
theme it will not affect you, otherwise if <1.0f you need to tweak your custom theme to readjust for the fact that we
|
|
don't draw a WindowBg background behind the title bar. This helper function will convert an old TitleBg/TitleBgActive
|
|
color into a new one with the same visual output, given the OLD color and the OLD WindowBg color: ImVec4
|
|
ConvertTitleBgCol(const ImVec4& win_bg_col, const ImVec4& title_bg_col) { float new_a = 1.0f - ((1.0f - win_bg_col.w) *
|
|
(1.0f - title_bg_col.w)), k = title_bg_col.w / new_a; return ImVec4((win_bg_col.x * win_bg_col.w + title_bg_col.x) * k,
|
|
(win_bg_col.y * win_bg_col.w + title_bg_col.y) * k, (win_bg_col.z * win_bg_col.w + title_bg_col.z) * k, new_a); } If
|
|
this is confusing, pick the RGB value from title bar from an old screenshot and apply this as TitleBg/TitleBgActive. Or
|
|
you may just create TitleBgActive from a tweaked TitleBg color.
|
|
- 2016/05/07 (1.49) - removed confusing set of GetInternalState(), GetInternalStateSize(), SetInternalState()
|
|
functions. Now using CreateContext(), DestroyContext(), GetCurrentContext(), SetCurrentContext().
|
|
- 2016/05/02 (1.49) - renamed SetNextTreeNodeOpened() to SetNextTreeNodeOpen(), no redirection.
|
|
- 2016/05/01 (1.49) - obsoleted old signature of CollapsingHeader(const char* label, const char* str_id = NULL, bool
|
|
display_frame = true, bool default_open = false) as extra parameters were badly designed and rarely used. You can
|
|
replace the "default_open = true" flag in new API with CollapsingHeader(label, ImGuiTreeNodeFlags_DefaultOpen).
|
|
- 2016/04/26 (1.49) - changed ImDrawList::PushClipRect(ImVec4 rect) to ImDrawList::PushClipRect(Imvec2 min,ImVec2
|
|
max,bool intersect_with_current_clip_rect=false). Note that higher-level ImGui::PushClipRect() is preferable because it
|
|
will clip at logic/widget level, whereas ImDrawList::PushClipRect() only affect your renderer.
|
|
- 2016/04/03 (1.48) - removed style.WindowFillAlphaDefault setting which was redundant. Bake default BG alpha inside
|
|
style.Colors[ImGuiCol_WindowBg] and all other Bg color values. (ref GitHub issue #337).
|
|
- 2016/04/03 (1.48) - renamed ImGuiCol_TooltipBg to ImGuiCol_PopupBg, used by popups/menus and tooltips. popups/menus
|
|
were previously using ImGuiCol_WindowBg. (ref github issue #337)
|
|
- 2016/03/21 (1.48) - renamed GetWindowFont() to GetFont(), GetWindowFontSize() to GetFontSize(). Kept inline
|
|
redirection function (will obsolete).
|
|
- 2016/03/02 (1.48) - InputText() completion/history/always callbacks: if you modify the text buffer manually (without
|
|
using DeleteChars()/InsertChars() helper) you need to maintain the BufTextLen field. added an assert.
|
|
- 2016/01/23 (1.48) - fixed not honoring exact width passed to PushItemWidth(), previously it would add extra
|
|
FramePadding.x*2 over that width. if you had manual pixel-perfect alignment in place it might affect you.
|
|
- 2015/12/27 (1.48) - fixed ImDrawList::AddRect() which used to render a rectangle 1 px too large on each axis.
|
|
- 2015/12/04 (1.47) - renamed Color() helpers to ValueColor() - dangerously named, rarely used and probably to be made
|
|
obsolete.
|
|
- 2015/08/29 (1.45) - with the addition of horizontal scrollbar we made various fixes to inconsistencies with dealing
|
|
with cursor position. GetCursorPos()/SetCursorPos() functions now include the scrolled amount. It shouldn't affect the
|
|
majority of users, but take note that SetCursorPosX(100.0f) puts you at +100 from the starting x position which may
|
|
include scrolling, not at +100 from the window left side.
|
|
GetContentRegionMax()/GetWindowContentRegionMin()/GetWindowContentRegionMax() functions allow
|
|
include the scrolled amount. Typically those were used in cases where no scrolling would happen so it may not be a
|
|
problem, but watch out!
|
|
- 2015/08/29 (1.45) - renamed style.ScrollbarWidth to style.ScrollbarSize
|
|
- 2015/08/05 (1.44) - split imgui.cpp into extra files: imgui_demo.cpp imgui_draw.cpp imgui_internal.h that you need to
|
|
add to your project.
|
|
- 2015/07/18 (1.44) - fixed angles in ImDrawList::PathArcTo(), PathArcToFast() (introduced in 1.43) being off by an
|
|
extra PI for no justifiable reason
|
|
- 2015/07/14 (1.43) - add new ImFontAtlas::AddFont() API. For the old AddFont***, moved the 'font_no' parameter of
|
|
ImFontAtlas::AddFont** functions to the ImFontConfig structure. you need to render your textured triangles with bilinear
|
|
filtering to benefit from sub-pixel positioning of text.
|
|
- 2015/07/08 (1.43) - switched rendering data to use indexed rendering. this is saving a fair amount of CPU/GPU and
|
|
enables us to get anti-aliasing for a marginal cost. this necessary change will break your rendering function! the fix
|
|
should be very easy. sorry for that :(
|
|
- if you are using a vanilla copy of one of the imgui_impl_XXX.cpp provided in the example, you
|
|
just need to update your copy and you can ignore the rest.
|
|
- the signature of the io.RenderDrawListsFn handler has changed!
|
|
old: ImGui_XXXX_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count)
|
|
new: ImGui_XXXX_RenderDrawLists(ImDrawData* draw_data).
|
|
parameters: 'cmd_lists' becomes 'draw_data->CmdLists', 'cmd_lists_count' becomes
|
|
'draw_data->CmdListsCount' ImDrawList: 'commands' becomes 'CmdBuffer', 'vtx_buffer' becomes 'VtxBuffer', 'IdxBuffer' is
|
|
new. ImDrawCmd: 'vtx_count' becomes 'ElemCount', 'clip_rect' becomes 'ClipRect', 'user_callback' becomes
|
|
'UserCallback', 'texture_id' becomes 'TextureId'.
|
|
- each ImDrawList now contains both a vertex buffer and an index buffer. For each command, render
|
|
ElemCount/3 triangles using indices from the index buffer.
|
|
- if you REALLY cannot render indexed primitives, you can call the draw_data->DeIndexAllBuffers()
|
|
method to de-index the buffers. This is slow and a waste of CPU/GPU. Prefer using indexed rendering!
|
|
- refer to code in the examples/ folder or ask on the GitHub if you are unsure of how to upgrade.
|
|
please upgrade!
|
|
- 2015/07/10 (1.43) - changed SameLine() parameters from int to float.
|
|
- 2015/07/02 (1.42) - renamed SetScrollPosHere() to SetScrollFromCursorPos(). Kept inline redirection function (will
|
|
obsolete).
|
|
- 2015/07/02 (1.42) - renamed GetScrollPosY() to GetScrollY(). Necessary to reduce confusion along with other scrolling
|
|
functions, because positions (e.g. cursor position) are not equivalent to scrolling amount.
|
|
- 2015/06/14 (1.41) - changed ImageButton() default bg_col parameter from (0,0,0,1) (black) to (0,0,0,0) (transparent)
|
|
- makes a difference when texture have transparence
|
|
- 2015/06/14 (1.41) - changed Selectable() API from (label, selected, size) to (label, selected, flags, size). Size
|
|
override should have been rarely used. Sorry!
|
|
- 2015/05/31 (1.40) - renamed GetWindowCollapsed() to IsWindowCollapsed() for consistency. Kept inline redirection
|
|
function (will obsolete).
|
|
- 2015/05/31 (1.40) - renamed IsRectClipped() to IsRectVisible() for consistency. Note that return value is opposite!
|
|
Kept inline redirection function (will obsolete).
|
|
- 2015/05/27 (1.40) - removed the third 'repeat_if_held' parameter from Button() - sorry! it was rarely used and
|
|
inconsistent. Use PushButtonRepeat(true) / PopButtonRepeat() to enable repeat on desired buttons.
|
|
- 2015/05/11 (1.40) - changed BeginPopup() API, takes a string identifier instead of a bool. ImGui needs to manage the
|
|
open/closed state of popups. Call OpenPopup() to actually set the "open" state of a popup. BeginPopup() returns true if
|
|
the popup is opened.
|
|
- 2015/05/03 (1.40) - removed style.AutoFitPadding, using style.WindowPadding makes more sense (the default values were
|
|
already the same).
|
|
- 2015/04/13 (1.38) - renamed IsClipped() to IsRectClipped(). Kept inline redirection function until 1.50.
|
|
- 2015/04/09 (1.38) - renamed ImDrawList::AddArc() to ImDrawList::AddArcFast() for compatibility with future API
|
|
- 2015/04/03 (1.38) - removed ImGuiCol_CheckHovered, ImGuiCol_CheckActive, replaced with the more general
|
|
ImGuiCol_FrameBgHovered, ImGuiCol_FrameBgActive.
|
|
- 2014/04/03 (1.38) - removed support for passing -FLT_MAX..+FLT_MAX as the range for a SliderFloat(). Use DragFloat()
|
|
or Inputfloat() instead.
|
|
- 2015/03/17 (1.36) - renamed GetItemBoxMin()/GetItemBoxMax()/IsMouseHoveringBox() to
|
|
GetItemRectMin()/GetItemRectMax()/IsMouseHoveringRect(). Kept inline redirection function until 1.50.
|
|
- 2015/03/15 (1.36) - renamed style.TreeNodeSpacing to style.IndentSpacing, ImGuiStyleVar_TreeNodeSpacing to
|
|
ImGuiStyleVar_IndentSpacing
|
|
- 2015/03/13 (1.36) - renamed GetWindowIsFocused() to IsWindowFocused(). Kept inline redirection function until 1.50.
|
|
- 2015/03/08 (1.35) - renamed style.ScrollBarWidth to style.ScrollbarWidth (casing)
|
|
- 2015/02/27 (1.34) - renamed OpenNextNode(bool) to SetNextTreeNodeOpened(bool, ImGuiSetCond). Kept inline redirection
|
|
function until 1.50.
|
|
- 2015/02/27 (1.34) - renamed ImGuiSetCondition_*** to ImGuiSetCond_***, and _FirstUseThisSession becomes _Once.
|
|
- 2015/02/11 (1.32) - changed text input callback ImGuiTextEditCallback return type from void-->int. reserved for
|
|
future use, return 0 for now.
|
|
- 2015/02/10 (1.32) - renamed GetItemWidth() to CalcItemWidth() to clarify its evolving behavior
|
|
- 2015/02/08 (1.31) - renamed GetTextLineSpacing() to GetTextLineHeightWithSpacing()
|
|
- 2015/02/01 (1.31) - removed IO.MemReallocFn (unused)
|
|
- 2015/01/19 (1.30) - renamed ImGuiStorage::GetIntPtr()/GetFloatPtr() to GetIntRef()/GetIntRef() because Ptr was
|
|
conflicting with actual pointer storage functions.
|
|
- 2015/01/11 (1.30) - big font/image API change! now loads TTF file. allow for multiple fonts. no need for a PNG
|
|
loader.
|
|
- 2015/01/11 (1.30) - removed GetDefaultFontData(). uses io.Fonts->GetTextureData*() API to retrieve uncompressed
|
|
pixels.
|
|
- old: const void* png_data; unsigned int png_size; ImGui::GetDefaultFontData(NULL, NULL,
|
|
&png_data, &png_size); [..Upload texture to GPU..];
|
|
- new: unsigned char* pixels; int width, height; io.Fonts->GetTexDataAsRGBA32(&pixels, &width,
|
|
&height); [..Upload texture to GPU..]; io.Fonts->SetTexID(YourTexIdentifier); you now have more flexibility to load
|
|
multiple TTF fonts and manage the texture buffer for internal needs. It is now recommended that you sample the font
|
|
texture with bilinear interpolation.
|
|
- 2015/01/11 (1.30) - added texture identifier in ImDrawCmd passed to your render function (we can now render images).
|
|
make sure to call io.Fonts->SetTexID()
|
|
- 2015/01/11 (1.30) - removed IO.PixelCenterOffset (unnecessary, can be handled in user projection matrix)
|
|
- 2015/01/11 (1.30) - removed ImGui::IsItemFocused() in favor of ImGui::IsItemActive() which handles all widgets
|
|
- 2014/12/10 (1.18) - removed SetNewWindowDefaultPos() in favor of new generic API SetNextWindowPos(pos,
|
|
ImGuiSetCondition_FirstUseEver)
|
|
- 2014/11/28 (1.17) - moved IO.Font*** options to inside the IO.Font-> structure (FontYOffset, FontTexUvForWhite,
|
|
FontBaseScale, FontFallbackGlyph)
|
|
- 2014/11/26 (1.17) - reworked syntax of IMGUI_ONCE_UPON_A_FRAME helper macro to increase compiler compatibility
|
|
- 2014/11/07 (1.15) - renamed IsHovered() to IsItemHovered()
|
|
- 2014/10/02 (1.14) - renamed IMGUI_INCLUDE_IMGUI_USER_CPP to IMGUI_INCLUDE_IMGUI_USER_INL and imgui_user.cpp to
|
|
imgui_user.inl (more IDE friendly)
|
|
- 2014/09/25 (1.13) - removed 'text_end' parameter from IO.SetClipboardTextFn (the string is now always zero-terminated
|
|
for simplicity)
|
|
- 2014/09/24 (1.12) - renamed SetFontScale() to SetWindowFontScale()
|
|
- 2014/09/24 (1.12) - moved IM_MALLOC/IM_REALLOC/IM_FREE preprocessor defines to
|
|
IO.MemAllocFn/IO.MemReallocFn/IO.MemFreeFn
|
|
- 2014/08/30 (1.09) - removed IO.FontHeight (now computed automatically)
|
|
- 2014/08/30 (1.09) - moved IMGUI_FONT_TEX_UV_FOR_WHITE preprocessor define to IO.FontTexUvForWhite
|
|
- 2014/08/28 (1.09) - changed the behavior of IO.PixelCenterOffset following various rendering fixes
|
|
|
|
|
|
FREQUENTLY ASKED QUESTIONS (FAQ)
|
|
================================
|
|
|
|
Read all answers online:
|
|
https://www.dearimgui.org/faq or https://github.com/ocornut/imgui/blob/master/docs/FAQ.md (same url)
|
|
Read all answers locally (with a text editor or ideally a Markdown viewer):
|
|
docs/FAQ.md
|
|
Some answers are copied down here to facilitate searching in code.
|
|
|
|
Q&A: Basics
|
|
===========
|
|
|
|
Q: Where is the documentation?
|
|
A: This library is poorly documented at the moment and expects the user to be acquainted with C/C++.
|
|
- Run the examples/ and explore them.
|
|
- See demo code in imgui_demo.cpp and particularly the ImGui::ShowDemoWindow() function.
|
|
- The demo covers most features of Dear ImGui, so you can read the code and see its output.
|
|
- See documentation and comments at the top of imgui.cpp + effectively imgui.h.
|
|
- Dozens of standalone example applications using e.g. OpenGL/DirectX are provided in the
|
|
examples/ folder to explain how to integrate Dear ImGui with your own engine/application.
|
|
- The Wiki (https://github.com/ocornut/imgui/wiki) has many resources and links.
|
|
- The Glossary (https://github.com/ocornut/imgui/wiki/Glossary) page also may be useful.
|
|
- Your programming IDE is your friend, find the type or function declaration to find comments
|
|
associated with it.
|
|
|
|
Q: What is this library called?
|
|
Q: Which version should I get?
|
|
>> This library is called "Dear ImGui", please don't call it "ImGui" :)
|
|
>> See https://www.dearimgui.org/faq for details.
|
|
|
|
Q&A: Integration
|
|
================
|
|
|
|
Q: How to get started?
|
|
A: Read 'PROGRAMMER GUIDE' above. Read examples/README.txt.
|
|
|
|
Q: How can I tell whether to dispatch mouse/keyboard to Dear ImGui or my application?
|
|
A: You should read the 'io.WantCaptureMouse', 'io.WantCaptureKeyboard' and 'io.WantTextInput' flags!
|
|
>> See https://www.dearimgui.org/faq for a fully detailed answer. You really want to read this.
|
|
|
|
Q. How can I enable keyboard controls?
|
|
Q: How can I use this without a mouse, without a keyboard or without a screen? (gamepad, input share, remote display)
|
|
Q: I integrated Dear ImGui in my engine and little squares are showing instead of text...
|
|
Q: I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around...
|
|
Q: I integrated Dear ImGui in my engine and some elements are displaying outside their expected windows boundaries...
|
|
>> See https://www.dearimgui.org/faq
|
|
|
|
Q&A: Usage
|
|
----------
|
|
|
|
Q: Why is my widget not reacting when I click on it?
|
|
Q: How can I have widgets with an empty label?
|
|
Q: How can I have multiple widgets with the same label?
|
|
Q: How can I display an image? What is ImTextureID, how does it works?
|
|
Q: How can I use my own math types instead of ImVec2/ImVec4?
|
|
Q: How can I interact with standard C++ types (such as std::string and std::vector)?
|
|
Q: How can I display custom shapes? (using low-level ImDrawList API)
|
|
>> See https://www.dearimgui.org/faq
|
|
|
|
Q&A: Fonts, Text
|
|
================
|
|
|
|
Q: How should I handle DPI in my application?
|
|
Q: How can I load a different font than the default?
|
|
Q: How can I easily use icons in my application?
|
|
Q: How can I load multiple fonts?
|
|
Q: How can I display and input non-Latin characters such as Chinese, Japanese, Korean, Cyrillic?
|
|
>> See https://www.dearimgui.org/faq and https://github.com/ocornut/imgui/edit/master/docs/FONTS.md
|
|
|
|
Q&A: Concerns
|
|
=============
|
|
|
|
Q: Who uses Dear ImGui?
|
|
Q: Can you create elaborate/serious tools with Dear ImGui?
|
|
Q: Can you reskin the look of Dear ImGui?
|
|
Q: Why using C++ (as opposed to C)?
|
|
>> See https://www.dearimgui.org/faq
|
|
|
|
Q&A: Community
|
|
==============
|
|
|
|
Q: How can I help?
|
|
A: - Businesses: please reach out to "contact AT dearimgui.com" if you work in a place using Dear ImGui!
|
|
We can discuss ways for your company to fund development via invoiced technical support, maintenance or sponsoring
|
|
contacts. This is among the most useful thing you can do for Dear ImGui. With increased funding, we can hire more people
|
|
working on this project.
|
|
- Individuals: you can support continued development via PayPal donations. See README.
|
|
- If you are experienced with Dear ImGui and C++, look at the GitHub issues, look at the Wiki, read docs/TODO.txt
|
|
and see how you want to help and can help!
|
|
- Disclose your usage of Dear ImGui via a dev blog post, a tweet, a screenshot, a mention somewhere etc.
|
|
You may post screenshot or links in the gallery threads. Visuals are ideal as they inspire other programmers.
|
|
But even without visuals, disclosing your use of dear imgui helps the library grow credibility, and help other
|
|
teams and programmers with taking decisions.
|
|
- If you have issues or if you need to hack into the library, even if you don't expect any support it is useful that
|
|
you share your issues (on GitHub or privately).
|
|
|
|
*/
|
|
|
|
//-------------------------------------------------------------------------
|
|
// [SECTION] INCLUDES
|
|
//-------------------------------------------------------------------------
|
|
|
|
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
|
|
# define _CRT_SECURE_NO_WARNINGS
|
|
#endif
|
|
|
|
#include "imgui.h"
|
|
#ifndef IMGUI_DISABLE
|
|
|
|
# ifndef IMGUI_DEFINE_MATH_OPERATORS
|
|
# define IMGUI_DEFINE_MATH_OPERATORS
|
|
# endif
|
|
# include "imgui_internal.h"
|
|
|
|
// System includes
|
|
# include <ctype.h> // toupper
|
|
# include <stdio.h> // vsnprintf, sscanf, printf
|
|
# if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier
|
|
# include <stddef.h> // intptr_t
|
|
# else
|
|
# include <stdint.h> // intptr_t
|
|
# endif
|
|
|
|
// [Windows] On non-Visual Studio compilers, we default to IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS unless explicitly
|
|
// enabled
|
|
# if defined(_WIN32) && !defined(_MSC_VER) && !defined(IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS) && \
|
|
!defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS)
|
|
# define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS
|
|
# endif
|
|
|
|
// [Windows] OS specific includes (optional)
|
|
# if defined(_WIN32) && defined(IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS) && \
|
|
defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS) && \
|
|
defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS)
|
|
# define IMGUI_DISABLE_WIN32_FUNCTIONS
|
|
# endif
|
|
# if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS)
|
|
# ifndef WIN32_LEAN_AND_MEAN
|
|
# define WIN32_LEAN_AND_MEAN
|
|
# endif
|
|
# ifndef NOMINMAX
|
|
# define NOMINMAX
|
|
# endif
|
|
# ifndef __MINGW32__
|
|
# include <Windows.h> // _wfopen, OpenClipboard
|
|
# else
|
|
# include <windows.h>
|
|
# endif
|
|
# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) // UWP doesn't have all Win32 functions
|
|
# define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS
|
|
# define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS
|
|
# endif
|
|
# endif
|
|
|
|
// [Apple] OS specific includes
|
|
# if defined(__APPLE__)
|
|
# include <TargetConditionals.h>
|
|
# endif
|
|
|
|
// Visual Studio warnings
|
|
# ifdef _MSC_VER
|
|
# pragma warning(disable : 4127) // condition expression is constant
|
|
# pragma warning(disable : 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf,
|
|
// vsnprintf, sscanf, fopen
|
|
# if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later
|
|
# pragma warning(disable : 5054) // operator '|': deprecated between enumerations of different types
|
|
# endif
|
|
# pragma warning(disable : 26451) // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte
|
|
// value and then casting the result to a 8 byte value. Cast the value to the
|
|
// wider type before calling operator 'xxx' to avoid overflow(io.2).
|
|
# pragma warning(disable : 26495) // [Static Analyzer] Variable 'XXX' is uninitialized. Always initialize a
|
|
// member variable (type.6).
|
|
# pragma warning(disable : 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over
|
|
// 'enum' (Enum.3).
|
|
# endif
|
|
|
|
// Clang/GCC warnings with -Weverything
|
|
# if defined(__clang__)
|
|
# if __has_warning("-Wunknown-warning-option")
|
|
# pragma clang diagnostic ignored \
|
|
"-Wunknown-warning-option" // warning: unknown warning group 'xxx' // not all
|
|
// warnings are known by all Clang versions and they tend to be
|
|
// rename-happy.. so ignoring warnings triggers new warnings on some
|
|
// configuration. Great!
|
|
# endif
|
|
# pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx'
|
|
# pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more
|
|
// terse.
|
|
# pragma clang diagnostic ignored \
|
|
"-Wfloat-equal" // warning: comparing floating point with == or != is unsafe // storing and comparing
|
|
// against same constants (typically 0.0f) is ok.
|
|
# pragma clang diagnostic ignored \
|
|
"-Wformat-nonliteral" // warning: format string is not a string literal // passing non-literal
|
|
// to vsnformat(). yes, user passing incorrect format strings can crash the code.
|
|
# pragma clang diagnostic ignored \
|
|
"-Wexit-time-destructors" // warning: declaration requires an exit-time destructor // exit-time
|
|
// destruction order is undefined. if MemFree() leads to users code that has been
|
|
// disabled before exit it might cause problems. ImGui coding style welcomes
|
|
// static/globals.
|
|
# pragma clang diagnostic ignored \
|
|
"-Wglobal-constructors" // warning: declaration requires a global destructor // similar to above,
|
|
// not sure what the exact difference is.
|
|
# pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness
|
|
# pragma clang diagnostic ignored \
|
|
"-Wformat-pedantic" // warning: format specifies type 'void *' but the argument has type 'xxxx *' //
|
|
// unreasonable, would lead to casting every %p arg to void*. probably enabled by
|
|
// -pedantic.
|
|
# pragma clang diagnostic ignored \
|
|
"-Wint-to-void-pointer-cast" // warning: cast to 'void *' from smaller integer type 'int'
|
|
# pragma clang diagnostic ignored \
|
|
"-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant // some
|
|
// standard header variations use #define NULL 0
|
|
# pragma clang diagnostic ignored \
|
|
"-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to
|
|
// function // using printf() is a misery with this as C++ va_arg ellipsis changes
|
|
// float to double.
|
|
# pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx'
|
|
// to 'float' may lose precision
|
|
# elif defined(__GNUC__)
|
|
// We disable -Wpragmas because GCC doesn't provide an has_warning equivalent and some forks/patches may not following
|
|
// the warning/version association.
|
|
# pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind
|
|
# pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used
|
|
# pragma GCC diagnostic ignored \
|
|
"-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size
|
|
# pragma GCC diagnostic ignored "-Wformat" // warning: format '%p' expects argument of type 'void*', but
|
|
// argument 6 has type 'ImGuiWindow*'
|
|
# pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double'
|
|
// when passing argument to function
|
|
# pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value
|
|
# pragma GCC diagnostic ignored \
|
|
"-Wformat-nonliteral" // warning: format not a string literal, format string not checked
|
|
# pragma GCC diagnostic ignored "-Wstrict-overflow" // warning: assuming signed overflow does not occur when
|
|
// assuming that (X - c) > X is always false
|
|
# pragma GCC diagnostic ignored \
|
|
"-Wclass-memaccess" // [__GNUC__ >= 8] warning: 'memset/memcpy' clearing/writing an object of type 'xxxx'
|
|
// with no trivial copy-assignment; use assignment or value-initialization instead
|
|
# endif
|
|
|
|
// Debug options
|
|
# 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_INI_SETTINGS \
|
|
0 // Save additional comments in .ini file (particularly helps for Docking, but makes saving slower)
|
|
|
|
// When using CTRL+TAB (or Gamepad Square+L/R) we delay the visual a little in order to reduce visual noise doing a fast
|
|
// switch.
|
|
static const float NAV_WINDOWING_HIGHLIGHT_DELAY =
|
|
0.20f; // Time before the highlight and screen dimming starts fading in
|
|
static const float NAV_WINDOWING_LIST_APPEAR_DELAY = 0.15f; // Time before the window list starts to appear
|
|
|
|
// Window resizing from edges (when io.ConfigWindowsResizeFromEdges = true and ImGuiBackendFlags_HasMouseCursors is set
|
|
// in io.BackendFlags by backend)
|
|
static const float WINDOWS_HOVER_PADDING =
|
|
4.0f; // Extend outside window for hovering/resizing (maxxed with TouchPadding) and inside windows for borders.
|
|
// Affect FindHoveredWindow().
|
|
static const float WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER =
|
|
0.04f; // Reduce visual noise by only highlighting the border after a certain time.
|
|
static const float WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER =
|
|
2.00f; // Lock scrolled window (so it doesn't pick child windows that are scrolling through) for a certain time,
|
|
// unless mouse moved.
|
|
|
|
// Docking
|
|
static const float DOCKING_TRANSPARENT_PAYLOAD_ALPHA =
|
|
0.50f; // For use with io.ConfigDockingTransparentPayload. Apply to Viewport _or_ WindowBg in host viewport.
|
|
static const float DOCKING_SPLITTER_SIZE = 2.0f;
|
|
|
|
//-------------------------------------------------------------------------
|
|
// [SECTION] FORWARD DECLARATIONS
|
|
//-------------------------------------------------------------------------
|
|
|
|
static void SetCurrentWindow(ImGuiWindow* window);
|
|
static void FindHoveredWindow();
|
|
static ImGuiWindow* CreateNewWindow(const char* name, ImGuiWindowFlags flags);
|
|
static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window);
|
|
|
|
static void AddDrawListToDrawData(ImVector<ImDrawList*>* out_list, ImDrawList* draw_list);
|
|
static void AddWindowToSortBuffer(ImVector<ImGuiWindow*>* out_sorted_windows, ImGuiWindow* window);
|
|
|
|
// Settings
|
|
static void WindowSettingsHandler_ClearAll(ImGuiContext*, ImGuiSettingsHandler*);
|
|
static void* WindowSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name);
|
|
static void WindowSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line);
|
|
static void WindowSettingsHandler_ApplyAll(ImGuiContext*, ImGuiSettingsHandler*);
|
|
static void WindowSettingsHandler_WriteAll(ImGuiContext*, ImGuiSettingsHandler*, ImGuiTextBuffer* buf);
|
|
|
|
// 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);
|
|
|
|
namespace ImGui
|
|
{
|
|
// Navigation
|
|
static void NavUpdate();
|
|
static void NavUpdateWindowing();
|
|
static void NavUpdateWindowingOverlay();
|
|
static void NavUpdateMoveResult();
|
|
static void NavUpdateInitResult();
|
|
static float NavUpdatePageUpPageDown();
|
|
static inline void NavUpdateAnyRequestFlag();
|
|
static void NavEndFrame();
|
|
static bool NavScoreItem(ImGuiNavItemData* result, ImRect cand);
|
|
static void NavApplyItemToResult(ImGuiNavItemData* result, ImGuiWindow* window, ImGuiID id, const ImRect& nav_bb_rel);
|
|
static void NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, ImGuiID id);
|
|
static ImVec2 NavCalcPreferredRefPos();
|
|
static void NavSaveLastChildNavWindowIntoParent(ImGuiWindow* nav_window);
|
|
static ImGuiWindow* NavRestoreLastChildNavWindow(ImGuiWindow* window);
|
|
static void NavRestoreLayer(ImGuiNavLayer layer);
|
|
static int FindWindowFocusIndex(ImGuiWindow* window);
|
|
|
|
// Error Checking
|
|
static void ErrorCheckNewFrameSanityChecks();
|
|
static void ErrorCheckEndFrameSanityChecks();
|
|
|
|
// Misc
|
|
static void UpdateSettings();
|
|
static void UpdateMouseInputs();
|
|
static void UpdateMouseWheel();
|
|
static void UpdateTabFocus();
|
|
static void UpdateDebugToolItemPicker();
|
|
static bool UpdateWindowManualResize(ImGuiWindow* window,
|
|
const ImVec2& size_auto_fit,
|
|
int* border_held,
|
|
int resize_grip_count,
|
|
ImU32 resize_grip_col[4],
|
|
const ImRect& visibility_rect);
|
|
static void RenderWindowOuterBorders(ImGuiWindow* window);
|
|
static void RenderWindowDecorations(ImGuiWindow* window,
|
|
const ImRect& title_bar_rect,
|
|
bool title_bar_is_highlight,
|
|
bool handle_borders_and_resize_grips,
|
|
int resize_grip_count,
|
|
const ImU32 resize_grip_col[4],
|
|
float resize_grip_draw_size);
|
|
static void RenderWindowTitleBarContents(ImGuiWindow* window,
|
|
const ImRect& title_bar_rect,
|
|
const char* name,
|
|
bool* p_open);
|
|
static void EndFrameDrawDimmedBackgrounds();
|
|
|
|
// Viewports
|
|
const ImGuiID IMGUI_VIEWPORT_DEFAULT_ID =
|
|
0x11111111; // Using an arbitrary constant instead of e.g. ImHashStr("ViewportDefault", 0); so it's easier to spot
|
|
// in the debugger. The exact value doesn't matter.
|
|
static ImGuiViewportP* AddUpdateViewport(ImGuiWindow* window,
|
|
ImGuiID id,
|
|
const ImVec2& platform_pos,
|
|
const ImVec2& size,
|
|
ImGuiViewportFlags flags);
|
|
static void UpdateViewportsNewFrame();
|
|
static void UpdateViewportsEndFrame();
|
|
static void WindowSelectViewport(ImGuiWindow* window);
|
|
static void WindowSyncOwnedViewport(ImGuiWindow* window, ImGuiWindow* parent_window_in_stack);
|
|
static bool UpdateTryMergeWindowIntoHostViewport(ImGuiWindow* window, ImGuiViewportP* host_viewport);
|
|
static bool UpdateTryMergeWindowIntoHostViewports(ImGuiWindow* window);
|
|
static bool GetWindowAlwaysWantOwnViewport(ImGuiWindow* window);
|
|
static int FindPlatformMonitorForPos(const ImVec2& pos);
|
|
static int FindPlatformMonitorForRect(const ImRect& r);
|
|
static void UpdateViewportPlatformMonitor(ImGuiViewportP* viewport);
|
|
|
|
} // namespace ImGui
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// [SECTION] CONTEXT AND MEMORY ALLOCATORS
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// DLL users:
|
|
// - Heaps and globals are not shared across DLL boundaries!
|
|
// - You will need to call SetCurrentContext() + SetAllocatorFunctions() for each static/DLL boundary you are calling
|
|
// from.
|
|
// - Same applies for hot-reloading mechanisms that are reliant on reloading DLL (note that many hot-reloading
|
|
// mechanisms work without DLL).
|
|
// - Using Dear ImGui via a shared library is not recommended, because of function call overhead and because we don't
|
|
// guarantee backward nor forward ABI compatibility.
|
|
// - Confused? In a debugger: add GImGui to your watch window and notice how its value changes depending on your current
|
|
// location (which DLL boundary you are in).
|
|
|
|
// Current context pointer. Implicitly used by all Dear ImGui functions. Always assumed to be != NULL.
|
|
// - ImGui::CreateContext() will automatically set this pointer if it is NULL.
|
|
// Change to a different context by calling ImGui::SetCurrentContext().
|
|
// - Important: Dear ImGui functions are not thread-safe because of this pointer.
|
|
// If you want thread-safety to allow N threads to access N different contexts:
|
|
// - Change this variable to use thread local storage so each thread can refer to a different context, in your
|
|
// imconfig.h:
|
|
// struct ImGuiContext;
|
|
// extern thread_local ImGuiContext* MyImGuiTLS;
|
|
// #define GImGui MyImGuiTLS
|
|
// And then define MyImGuiTLS in one of your cpp files. Note that thread_local is a C++11 keyword, earlier C++ uses
|
|
// compiler-specific keyword.
|
|
// - Future development aims to make this context pointer explicit to all calls. Also read
|
|
// https://github.com/ocornut/imgui/issues/586
|
|
// - If you need a finite number of contexts, you may compile and use multiple instances of the ImGui code from a
|
|
// different namespace.
|
|
// - DLL users: read comments above.
|
|
# ifndef GImGui
|
|
ImGuiContext* GImGui = NULL;
|
|
# endif
|
|
|
|
// Memory Allocator functions. Use SetAllocatorFunctions() to change them.
|
|
// - You probably don't want to modify that mid-program, and if you use global/static e.g. ImVector<> instances you may
|
|
// need to keep them accessible during program destruction.
|
|
// - DLL users: read comments above.
|
|
# ifndef IMGUI_DISABLE_DEFAULT_ALLOCATORS
|
|
static void* MallocWrapper(size_t size, void* user_data)
|
|
{
|
|
IM_UNUSED(user_data);
|
|
return malloc(size);
|
|
}
|
|
static void FreeWrapper(void* ptr, void* user_data)
|
|
{
|
|
IM_UNUSED(user_data);
|
|
free(ptr);
|
|
}
|
|
# else
|
|
static void* MallocWrapper(size_t size, void* user_data)
|
|
{
|
|
IM_UNUSED(user_data);
|
|
IM_UNUSED(size);
|
|
IM_ASSERT(0);
|
|
return NULL;
|
|
}
|
|
static void FreeWrapper(void* ptr, void* user_data)
|
|
{
|
|
IM_UNUSED(user_data);
|
|
IM_UNUSED(ptr);
|
|
IM_ASSERT(0);
|
|
}
|
|
# endif
|
|
static ImGuiMemAllocFunc GImAllocatorAllocFunc = MallocWrapper;
|
|
static ImGuiMemFreeFunc GImAllocatorFreeFunc = FreeWrapper;
|
|
static void* GImAllocatorUserData = NULL;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// [SECTION] USER FACING STRUCTURES (ImGuiStyle, ImGuiIO)
|
|
//-----------------------------------------------------------------------------
|
|
|
|
ImGuiStyle::ImGuiStyle()
|
|
{
|
|
Alpha = 1.0f; // Global alpha applies to everything in Dear ImGui.
|
|
DisabledAlpha =
|
|
0.60f; // Additional alpha multiplier applied by BeginDisabled(). Multiply over current value of Alpha.
|
|
WindowPadding = ImVec2(8, 8); // Padding within a window
|
|
WindowRounding = 0.0f; // Radius of window corners rounding. Set to 0.0f to have rectangular windows. Large values
|
|
// tend to lead to variety of artifacts and are not recommended.
|
|
WindowBorderSize =
|
|
1.0f; // Thickness of border around windows. Generally set to 0.0f or 1.0f. Other values not well tested.
|
|
WindowMinSize = ImVec2(32, 32); // Minimum window size
|
|
WindowTitleAlign = ImVec2(0.0f, 0.5f); // Alignment for title bar text
|
|
WindowMenuButtonPosition = ImGuiDir_Left; // Position of the collapsing/docking button in the title bar
|
|
// (left/right). Defaults to ImGuiDir_Left.
|
|
ChildRounding = 0.0f; // Radius of child window corners rounding. Set to 0.0f to have rectangular child windows
|
|
ChildBorderSize =
|
|
1.0f; // Thickness of border around child windows. Generally set to 0.0f or 1.0f. Other values not well tested.
|
|
PopupRounding = 0.0f; // Radius of popup window corners rounding. Set to 0.0f to have rectangular child windows
|
|
PopupBorderSize = 1.0f; // Thickness of border around popup or tooltip windows. Generally set to 0.0f or 1.0f.
|
|
// Other values not well tested.
|
|
FramePadding = ImVec2(4, 3); // Padding within a framed rectangle (used by most widgets)
|
|
FrameRounding =
|
|
0.0f; // Radius of frame corners rounding. Set to 0.0f to have rectangular frames (used by most widgets).
|
|
FrameBorderSize =
|
|
0.0f; // Thickness of border around frames. Generally set to 0.0f or 1.0f. Other values not well tested.
|
|
ItemSpacing = ImVec2(8, 4); // Horizontal and vertical spacing between widgets/lines
|
|
ItemInnerSpacing = ImVec2(4, 4); // Horizontal and vertical spacing between within elements of a composed widget
|
|
// (e.g. a slider and its label)
|
|
CellPadding = ImVec2(4, 2); // Padding within a table cell
|
|
TouchExtraPadding = ImVec2(0, 0); // Expand reactive bounding box for touch-based system where touch position is
|
|
// not accurate enough. Unfortunately we don't sort widgets so priority on
|
|
// overlap will always be given to the first widget. So don't grow this too much!
|
|
IndentSpacing =
|
|
21.0f; // Horizontal spacing when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2).
|
|
ColumnsMinSpacing = 6.0f; // Minimum horizontal spacing between two columns. Preferably > (FramePadding.x + 1).
|
|
ScrollbarSize = 14.0f; // Width of the vertical scrollbar, Height of the horizontal scrollbar
|
|
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.
|
|
LogSliderDeadzone =
|
|
4.0f; // The size in pixels of the dead-zone around zero on logarithmic sliders that cross zero.
|
|
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.
|
|
TabMinWidthForCloseButton =
|
|
0.0f; // Minimum width for close button to appears on an unselected tab when hovered. Set to 0.0f to always
|
|
// show when hovering, set to FLT_MAX to never show close button unless selected.
|
|
ColorButtonPosition =
|
|
ImGuiDir_Right; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right.
|
|
ButtonTextAlign = ImVec2(0.5f, 0.5f); // Alignment of button text when button is larger than text.
|
|
SelectableTextAlign = ImVec2(
|
|
0.0f, 0.0f); // Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally
|
|
// important to keep this left-aligned if you want to lay multiple items on a same line.
|
|
DisplayWindowPadding = ImVec2(19, 19); // Window position are clamped to be visible within the display area or
|
|
// monitors by at least this amount. Only applies to regular windows.
|
|
DisplaySafeAreaPadding = ImVec2(3, 3); // If you cannot see the edge of your screen (e.g. on a TV) increase the
|
|
// safe area padding. Covers popups/tooltips as well regular windows.
|
|
MouseCursorScale =
|
|
1.0f; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later.
|
|
AntiAliasedLines = true; // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU.
|
|
AntiAliasedLinesUseTex = true; // Enable anti-aliased lines/borders using textures where possible. Require backend
|
|
// to render with bilinear filtering.
|
|
AntiAliasedFill = true; // Enable anti-aliased filled shapes (rounded rectangles, circles, etc.).
|
|
CurveTessellationTol =
|
|
1.25f; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease
|
|
// for highly tessellated curves (higher quality, more polygons), increase to reduce quality.
|
|
CircleTessellationMaxError =
|
|
0.30f; // Maximum error (in pixels) allowed when using AddCircle()/AddCircleFilled() or drawing rounded corner
|
|
// rectangles with no explicit segment count specified. Decrease for higher quality but more geometry.
|
|
|
|
// Default theme
|
|
ImGui::StyleColorsDark(this);
|
|
}
|
|
|
|
// To scale your entire UI (e.g. if you want your app to use High DPI or generally be DPI aware) you may use this helper
|
|
// function. Scaling the fonts is done separately and is up to you. Important: This operation is lossy because we round
|
|
// all sizes to integer. If you need to change your scale multiples, call this over a freshly initialized ImGuiStyle
|
|
// structure rather than scaling multiple times.
|
|
void ImGuiStyle::ScaleAllSizes(float scale_factor)
|
|
{
|
|
WindowPadding = ImFloor(WindowPadding * scale_factor);
|
|
WindowRounding = ImFloor(WindowRounding * scale_factor);
|
|
WindowMinSize = ImFloor(WindowMinSize * scale_factor);
|
|
ChildRounding = ImFloor(ChildRounding * scale_factor);
|
|
PopupRounding = ImFloor(PopupRounding * scale_factor);
|
|
FramePadding = ImFloor(FramePadding * scale_factor);
|
|
FrameRounding = ImFloor(FrameRounding * scale_factor);
|
|
ItemSpacing = ImFloor(ItemSpacing * scale_factor);
|
|
ItemInnerSpacing = ImFloor(ItemInnerSpacing * scale_factor);
|
|
CellPadding = ImFloor(CellPadding * scale_factor);
|
|
TouchExtraPadding = ImFloor(TouchExtraPadding * scale_factor);
|
|
IndentSpacing = ImFloor(IndentSpacing * scale_factor);
|
|
ColumnsMinSpacing = ImFloor(ColumnsMinSpacing * scale_factor);
|
|
ScrollbarSize = ImFloor(ScrollbarSize * scale_factor);
|
|
ScrollbarRounding = ImFloor(ScrollbarRounding * scale_factor);
|
|
GrabMinSize = ImFloor(GrabMinSize * scale_factor);
|
|
GrabRounding = ImFloor(GrabRounding * scale_factor);
|
|
LogSliderDeadzone = ImFloor(LogSliderDeadzone * scale_factor);
|
|
TabRounding = ImFloor(TabRounding * scale_factor);
|
|
TabMinWidthForCloseButton =
|
|
(TabMinWidthForCloseButton != FLT_MAX) ? ImFloor(TabMinWidthForCloseButton * scale_factor) : FLT_MAX;
|
|
DisplayWindowPadding = ImFloor(DisplayWindowPadding * scale_factor);
|
|
DisplaySafeAreaPadding = ImFloor(DisplaySafeAreaPadding * scale_factor);
|
|
MouseCursorScale = ImFloor(MouseCursorScale * scale_factor);
|
|
}
|
|
|
|
ImGuiIO::ImGuiIO()
|
|
{
|
|
// Most fields are initialized with zero
|
|
memset(this, 0, sizeof(*this));
|
|
IM_ASSERT(IM_ARRAYSIZE(ImGuiIO::MouseDown) == ImGuiMouseButton_COUNT &&
|
|
IM_ARRAYSIZE(ImGuiIO::MouseClicked) ==
|
|
ImGuiMouseButton_COUNT); // Our pre-C++11 IM_STATIC_ASSERT() macros triggers warning on modern
|
|
// compilers so we don't use it here.
|
|
|
|
// Settings
|
|
ConfigFlags = ImGuiConfigFlags_None;
|
|
BackendFlags = ImGuiBackendFlags_None;
|
|
DisplaySize = ImVec2(-1.0f, -1.0f);
|
|
DeltaTime = 1.0f / 60.0f;
|
|
IniSavingRate = 5.0f;
|
|
IniFilename = "imgui.ini"; // Important: "imgui.ini" is relative to current working dir, most apps will want to
|
|
// lock this to an absolute path (e.g. same path as executables).
|
|
LogFilename = "imgui_log.txt";
|
|
MouseDoubleClickTime = 0.30f;
|
|
MouseDoubleClickMaxDist = 6.0f;
|
|
for (int i = 0; i < ImGuiKey_COUNT; i++)
|
|
KeyMap[i] = -1;
|
|
KeyRepeatDelay = 0.275f;
|
|
KeyRepeatRate = 0.050f;
|
|
UserData = NULL;
|
|
|
|
Fonts = NULL;
|
|
FontGlobalScale = 1.0f;
|
|
FontDefault = NULL;
|
|
FontAllowUserScaling = false;
|
|
DisplayFramebufferScale = ImVec2(1.0f, 1.0f);
|
|
|
|
// Docking options (when ImGuiConfigFlags_DockingEnable is set)
|
|
ConfigDockingNoSplit = false;
|
|
ConfigDockingAlwaysTabBar = false;
|
|
ConfigDockingTransparentPayload = false;
|
|
|
|
// Viewport options (when ImGuiConfigFlags_ViewportsEnable is set)
|
|
ConfigViewportsNoAutoMerge = false;
|
|
ConfigViewportsNoTaskBarIcon = false;
|
|
ConfigViewportsNoDecoration = true;
|
|
ConfigViewportsNoDefaultParent = false;
|
|
|
|
// Miscellaneous options
|
|
MouseDrawCursor = false;
|
|
# ifdef __APPLE__
|
|
ConfigMacOSXBehaviors = true; // Set Mac OS X style defaults based on __APPLE__ compile time flag
|
|
# else
|
|
ConfigMacOSXBehaviors = false;
|
|
# endif
|
|
ConfigInputTextCursorBlink = true;
|
|
ConfigWindowsResizeFromEdges = true;
|
|
ConfigWindowsMoveFromTitleBarOnly = false;
|
|
ConfigMemoryCompactTimer = 60.0f;
|
|
|
|
// Platform Functions
|
|
BackendPlatformName = BackendRendererName = NULL;
|
|
BackendPlatformUserData = BackendRendererUserData = BackendLanguageUserData = NULL;
|
|
GetClipboardTextFn = GetClipboardTextFn_DefaultImpl; // Platform dependent default implementations
|
|
SetClipboardTextFn = SetClipboardTextFn_DefaultImpl;
|
|
ClipboardUserData = NULL;
|
|
|
|
// Input (NB: we already have memset zero the entire structure!)
|
|
MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
|
|
MousePosPrev = ImVec2(-FLT_MAX, -FLT_MAX);
|
|
MouseDragThreshold = 6.0f;
|
|
for (int i = 0; i < IM_ARRAYSIZE(MouseDownDuration); i++)
|
|
MouseDownDuration[i] = MouseDownDurationPrev[i] = -1.0f;
|
|
for (int i = 0; i < IM_ARRAYSIZE(KeysDownDuration); i++)
|
|
KeysDownDuration[i] = KeysDownDurationPrev[i] = -1.0f;
|
|
for (int i = 0; i < IM_ARRAYSIZE(NavInputsDownDuration); i++)
|
|
NavInputsDownDuration[i] = -1.0f;
|
|
}
|
|
|
|
// Pass in translated ASCII characters for text input.
|
|
// - with glfw you can get those from the callback set in glfwSetCharCallback()
|
|
// - on Windows you can get those using ToAscii+keyboard state, or via the WM_CHAR message
|
|
void ImGuiIO::AddInputCharacter(unsigned int c)
|
|
{
|
|
if (c != 0)
|
|
InputQueueCharacters.push_back(c <= IM_UNICODE_CODEPOINT_MAX ? (ImWchar)c : IM_UNICODE_CODEPOINT_INVALID);
|
|
}
|
|
|
|
// UTF16 strings use surrogate pairs to encode codepoints >= 0x10000, so
|
|
// we should save the high surrogate.
|
|
void ImGuiIO::AddInputCharacterUTF16(ImWchar16 c)
|
|
{
|
|
if (c == 0 && InputQueueSurrogate == 0)
|
|
return;
|
|
|
|
if ((c & 0xFC00) == 0xD800) // High surrogate, must save
|
|
{
|
|
if (InputQueueSurrogate != 0)
|
|
InputQueueCharacters.push_back(IM_UNICODE_CODEPOINT_INVALID);
|
|
InputQueueSurrogate = c;
|
|
return;
|
|
}
|
|
|
|
ImWchar cp = c;
|
|
if (InputQueueSurrogate != 0)
|
|
{
|
|
if ((c & 0xFC00) != 0xDC00) // Invalid low surrogate
|
|
{
|
|
InputQueueCharacters.push_back(IM_UNICODE_CODEPOINT_INVALID);
|
|
}
|
|
else
|
|
{
|
|
# if IM_UNICODE_CODEPOINT_MAX == 0xFFFF
|
|
cp = IM_UNICODE_CODEPOINT_INVALID; // Codepoint will not fit in ImWchar
|
|
# else
|
|
cp = (ImWchar)(((InputQueueSurrogate - 0xD800) << 10) + (c - 0xDC00) + 0x10000);
|
|
# endif
|
|
}
|
|
|
|
InputQueueSurrogate = 0;
|
|
}
|
|
InputQueueCharacters.push_back(cp);
|
|
}
|
|
|
|
void ImGuiIO::AddInputCharactersUTF8(const char* utf8_chars)
|
|
{
|
|
while (*utf8_chars != 0)
|
|
{
|
|
unsigned int c = 0;
|
|
utf8_chars += ImTextCharFromUtf8(&c, utf8_chars, NULL);
|
|
if (c != 0)
|
|
InputQueueCharacters.push_back((ImWchar)c);
|
|
}
|
|
}
|
|
|
|
void ImGuiIO::ClearInputCharacters()
|
|
{
|
|
InputQueueCharacters.resize(0);
|
|
}
|
|
|
|
void ImGuiIO::ClearInputKeys()
|
|
{
|
|
memset(KeysDown, 0, sizeof(KeysDown));
|
|
for (int n = 0; n < IM_ARRAYSIZE(KeysDownDuration); n++)
|
|
KeysDownDuration[n] = KeysDownDurationPrev[n] = -1.0f;
|
|
KeyCtrl = KeyShift = KeyAlt = KeySuper = false;
|
|
KeyMods = KeyModsPrev = ImGuiKeyModFlags_None;
|
|
for (int n = 0; n < IM_ARRAYSIZE(NavInputsDownDuration); n++)
|
|
NavInputsDownDuration[n] = NavInputsDownDurationPrev[n] = -1.0f;
|
|
}
|
|
|
|
void ImGuiIO::AddFocusEvent(bool focused)
|
|
{
|
|
// We intentionally overwrite this and process in NewFrame(), in order to give a chance
|
|
// to multi-viewports backends to queue AddFocusEvent(false),AddFocusEvent(true) in same frame.
|
|
AppFocusLost = !focused;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// [SECTION] MISC HELPERS/UTILITIES (Geometry functions)
|
|
//-----------------------------------------------------------------------------
|
|
|
|
ImVec2 ImBezierCubicClosestPoint(const ImVec2& p1,
|
|
const ImVec2& p2,
|
|
const ImVec2& p3,
|
|
const ImVec2& p4,
|
|
const ImVec2& p,
|
|
int num_segments)
|
|
{
|
|
IM_ASSERT(num_segments > 0); // Use ImBezierCubicClosestPointCasteljau()
|
|
ImVec2 p_last = p1;
|
|
ImVec2 p_closest;
|
|
float p_closest_dist2 = FLT_MAX;
|
|
float t_step = 1.0f / (float)num_segments;
|
|
for (int i_step = 1; i_step <= num_segments; i_step++)
|
|
{
|
|
ImVec2 p_current = ImBezierCubicCalc(p1, p2, p3, p4, t_step * i_step);
|
|
ImVec2 p_line = ImLineClosestPoint(p_last, p_current, p);
|
|
float dist2 = ImLengthSqr(p - p_line);
|
|
if (dist2 < p_closest_dist2)
|
|
{
|
|
p_closest = p_line;
|
|
p_closest_dist2 = dist2;
|
|
}
|
|
p_last = p_current;
|
|
}
|
|
return p_closest;
|
|
}
|
|
|
|
// Closely mimics PathBezierToCasteljau() in imgui_draw.cpp
|
|
static void ImBezierCubicClosestPointCasteljauStep(const ImVec2& p,
|
|
ImVec2& p_closest,
|
|
ImVec2& p_last,
|
|
float& p_closest_dist2,
|
|
float x1,
|
|
float y1,
|
|
float x2,
|
|
float y2,
|
|
float x3,
|
|
float y3,
|
|
float x4,
|
|
float y4,
|
|
float tess_tol,
|
|
int level)
|
|
{
|
|
float dx = x4 - x1;
|
|
float dy = y4 - y1;
|
|
float d2 = ((x2 - x4) * dy - (y2 - y4) * dx);
|
|
float d3 = ((x3 - x4) * dy - (y3 - y4) * dx);
|
|
d2 = (d2 >= 0) ? d2 : -d2;
|
|
d3 = (d3 >= 0) ? d3 : -d3;
|
|
if ((d2 + d3) * (d2 + d3) < tess_tol * (dx * dx + dy * dy))
|
|
{
|
|
ImVec2 p_current(x4, y4);
|
|
ImVec2 p_line = ImLineClosestPoint(p_last, p_current, p);
|
|
float dist2 = ImLengthSqr(p - p_line);
|
|
if (dist2 < p_closest_dist2)
|
|
{
|
|
p_closest = p_line;
|
|
p_closest_dist2 = dist2;
|
|
}
|
|
p_last = p_current;
|
|
}
|
|
else if (level < 10)
|
|
{
|
|
float x12 = (x1 + x2) * 0.5f, y12 = (y1 + y2) * 0.5f;
|
|
float x23 = (x2 + x3) * 0.5f, y23 = (y2 + y3) * 0.5f;
|
|
float x34 = (x3 + x4) * 0.5f, y34 = (y3 + y4) * 0.5f;
|
|
float x123 = (x12 + x23) * 0.5f, y123 = (y12 + y23) * 0.5f;
|
|
float x234 = (x23 + x34) * 0.5f, y234 = (y23 + y34) * 0.5f;
|
|
float x1234 = (x123 + x234) * 0.5f, y1234 = (y123 + y234) * 0.5f;
|
|
ImBezierCubicClosestPointCasteljauStep(p, p_closest, p_last, p_closest_dist2, x1, y1, x12, y12, x123, y123,
|
|
x1234, y1234, tess_tol, level + 1);
|
|
ImBezierCubicClosestPointCasteljauStep(p, p_closest, p_last, p_closest_dist2, x1234, y1234, x234, y234, x34,
|
|
y34, x4, y4, tess_tol, level + 1);
|
|
}
|
|
}
|
|
|
|
// tess_tol is generally the same value you would find in ImGui::GetStyle().CurveTessellationTol
|
|
// Because those ImXXX functions are lower-level than ImGui:: we cannot access this value automatically.
|
|
ImVec2 ImBezierCubicClosestPointCasteljau(const ImVec2& p1,
|
|
const ImVec2& p2,
|
|
const ImVec2& p3,
|
|
const ImVec2& p4,
|
|
const ImVec2& p,
|
|
float tess_tol)
|
|
{
|
|
IM_ASSERT(tess_tol > 0.0f);
|
|
ImVec2 p_last = p1;
|
|
ImVec2 p_closest;
|
|
float p_closest_dist2 = FLT_MAX;
|
|
ImBezierCubicClosestPointCasteljauStep(p, p_closest, p_last, p_closest_dist2, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y,
|
|
p4.x, p4.y, tess_tol, 0);
|
|
return p_closest;
|
|
}
|
|
|
|
ImVec2 ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& p)
|
|
{
|
|
ImVec2 ap = p - a;
|
|
ImVec2 ab_dir = b - a;
|
|
float dot = ap.x * ab_dir.x + ap.y * ab_dir.y;
|
|
if (dot < 0.0f)
|
|
return a;
|
|
float ab_len_sqr = ab_dir.x * ab_dir.x + ab_dir.y * ab_dir.y;
|
|
if (dot > ab_len_sqr)
|
|
return b;
|
|
return a + ab_dir * dot / ab_len_sqr;
|
|
}
|
|
|
|
bool ImTriangleContainsPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p)
|
|
{
|
|
bool b1 = ((p.x - b.x) * (a.y - b.y) - (p.y - b.y) * (a.x - b.x)) < 0.0f;
|
|
bool b2 = ((p.x - c.x) * (b.y - c.y) - (p.y - c.y) * (b.x - c.x)) < 0.0f;
|
|
bool b3 = ((p.x - a.x) * (c.y - a.y) - (p.y - a.y) * (c.x - a.x)) < 0.0f;
|
|
return ((b1 == b2) && (b2 == b3));
|
|
}
|
|
|
|
void ImTriangleBarycentricCoords(const ImVec2& a,
|
|
const ImVec2& b,
|
|
const ImVec2& c,
|
|
const ImVec2& p,
|
|
float& out_u,
|
|
float& out_v,
|
|
float& out_w)
|
|
{
|
|
ImVec2 v0 = b - a;
|
|
ImVec2 v1 = c - a;
|
|
ImVec2 v2 = p - a;
|
|
const float denom = v0.x * v1.y - v1.x * v0.y;
|
|
out_v = (v2.x * v1.y - v1.x * v2.y) / denom;
|
|
out_w = (v0.x * v2.y - v2.x * v0.y) / denom;
|
|
out_u = 1.0f - out_v - out_w;
|
|
}
|
|
|
|
ImVec2 ImTriangleClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p)
|
|
{
|
|
ImVec2 proj_ab = ImLineClosestPoint(a, b, p);
|
|
ImVec2 proj_bc = ImLineClosestPoint(b, c, p);
|
|
ImVec2 proj_ca = ImLineClosestPoint(c, a, p);
|
|
float dist2_ab = ImLengthSqr(p - proj_ab);
|
|
float dist2_bc = ImLengthSqr(p - proj_bc);
|
|
float dist2_ca = ImLengthSqr(p - proj_ca);
|
|
float m = ImMin(dist2_ab, ImMin(dist2_bc, dist2_ca));
|
|
if (m == dist2_ab)
|
|
return proj_ab;
|
|
if (m == dist2_bc)
|
|
return proj_bc;
|
|
return proj_ca;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// [SECTION] MISC HELPERS/UTILITIES (String, Format, Hash functions)
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Consider using _stricmp/_strnicmp under Windows or strcasecmp/strncasecmp. We don't actually use either
|
|
// ImStricmp/ImStrnicmp in the codebase any more.
|
|
int ImStricmp(const char* str1, const char* str2)
|
|
{
|
|
int d;
|
|
while ((d = toupper(*str2) - toupper(*str1)) == 0 && *str1)
|
|
{
|
|
str1++;
|
|
str2++;
|
|
}
|
|
return d;
|
|
}
|
|
|
|
int ImStrnicmp(const char* str1, const char* str2, size_t count)
|
|
{
|
|
int d = 0;
|
|
while (count > 0 && (d = toupper(*str2) - toupper(*str1)) == 0 && *str1)
|
|
{
|
|
str1++;
|
|
str2++;
|
|
count--;
|
|
}
|
|
return d;
|
|
}
|
|
|
|
void ImStrncpy(char* dst, const char* src, size_t count)
|
|
{
|
|
if (count < 1)
|
|
return;
|
|
if (count > 1)
|
|
strncpy(dst, src, count - 1);
|
|
dst[count - 1] = 0;
|
|
}
|
|
|
|
char* ImStrdup(const char* str)
|
|
{
|
|
size_t len = strlen(str);
|
|
void* buf = IM_ALLOC(len + 1);
|
|
return (char*)memcpy(buf, (const void*)str, len + 1);
|
|
}
|
|
|
|
char* ImStrdupcpy(char* dst, size_t* p_dst_size, const char* src)
|
|
{
|
|
size_t dst_buf_size = p_dst_size ? *p_dst_size : strlen(dst) + 1;
|
|
size_t src_size = strlen(src) + 1;
|
|
if (dst_buf_size < src_size)
|
|
{
|
|
IM_FREE(dst);
|
|
dst = (char*)IM_ALLOC(src_size);
|
|
if (p_dst_size)
|
|
*p_dst_size = src_size;
|
|
}
|
|
return (char*)memcpy(dst, (const void*)src, src_size);
|
|
}
|
|
|
|
const char* ImStrchrRange(const char* str, const char* str_end, char c)
|
|
{
|
|
const char* p = (const char*)memchr(str, (int)c, str_end - str);
|
|
return p;
|
|
}
|
|
|
|
int ImStrlenW(const ImWchar* str)
|
|
{
|
|
// return (int)wcslen((const wchar_t*)str); // FIXME-OPT: Could use this when wchar_t are 16-bit
|
|
int n = 0;
|
|
while (*str++)
|
|
n++;
|
|
return n;
|
|
}
|
|
|
|
// Find end-of-line. Return pointer will point to either first \n, either str_end.
|
|
const char* ImStreolRange(const char* str, const char* str_end)
|
|
{
|
|
const char* p = (const char*)memchr(str, '\n', str_end - str);
|
|
return p ? p : str_end;
|
|
}
|
|
|
|
const ImWchar* ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* buf_begin) // find beginning-of-line
|
|
{
|
|
while (buf_mid_line > buf_begin && buf_mid_line[-1] != '\n')
|
|
buf_mid_line--;
|
|
return buf_mid_line;
|
|
}
|
|
|
|
const char* ImStristr(const char* haystack, const char* haystack_end, const char* needle, const char* needle_end)
|
|
{
|
|
if (!needle_end)
|
|
needle_end = needle + strlen(needle);
|
|
|
|
const char un0 = (char)toupper(*needle);
|
|
while ((!haystack_end && *haystack) || (haystack_end && haystack < haystack_end))
|
|
{
|
|
if (toupper(*haystack) == un0)
|
|
{
|
|
const char* b = needle + 1;
|
|
for (const char* a = haystack + 1; b < needle_end; a++, b++)
|
|
if (toupper(*a) != toupper(*b))
|
|
break;
|
|
if (b == needle_end)
|
|
return haystack;
|
|
}
|
|
haystack++;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// Trim str by offsetting contents when there's leading data + writing a \0 at the trailing position. We use this in
|
|
// situation where the cost is negligible.
|
|
void ImStrTrimBlanks(char* buf)
|
|
{
|
|
char* p = buf;
|
|
while (p[0] == ' ' || p[0] == '\t') // Leading blanks
|
|
p++;
|
|
char* p_start = p;
|
|
while (*p != 0) // Find end of string
|
|
p++;
|
|
while (p > p_start && (p[-1] == ' ' || p[-1] == '\t')) // Trailing blanks
|
|
p--;
|
|
if (p_start != buf) // Copy memory if we had leading blanks
|
|
memmove(buf, p_start, p - p_start);
|
|
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.
|
|
# ifndef IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS
|
|
|
|
// We support stb_sprintf which is much faster (see: https://github.com/nothings/stb/blob/master/stb_sprintf.h)
|
|
// You may set IMGUI_USE_STB_SPRINTF to use our default wrapper, or set IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS
|
|
// and setup the wrapper yourself. (FIXME-OPT: Some of our high-level operations such as ImGuiTextBuffer::appendfv() are
|
|
// designed using two-passes worst case, which probably could be improved using the stbsp_vsprintfcb() function.)
|
|
# ifdef IMGUI_USE_STB_SPRINTF
|
|
# define STB_SPRINTF_IMPLEMENTATION
|
|
# include "stb_sprintf.h"
|
|
# endif
|
|
|
|
# if defined(_MSC_VER) && !defined(vsnprintf)
|
|
# define vsnprintf _vsnprintf
|
|
# endif
|
|
|
|
int ImFormatString(char* buf, size_t buf_size, const char* fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
# ifdef IMGUI_USE_STB_SPRINTF
|
|
int w = stbsp_vsnprintf(buf, (int)buf_size, fmt, args);
|
|
# else
|
|
int w = vsnprintf(buf, buf_size, fmt, args);
|
|
# endif
|
|
va_end(args);
|
|
if (buf == NULL)
|
|
return w;
|
|
if (w == -1 || w >= (int)buf_size)
|
|
w = (int)buf_size - 1;
|
|
buf[w] = 0;
|
|
return w;
|
|
}
|
|
|
|
int ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args)
|
|
{
|
|
# ifdef IMGUI_USE_STB_SPRINTF
|
|
int w = stbsp_vsnprintf(buf, (int)buf_size, fmt, args);
|
|
# else
|
|
int w = vsnprintf(buf, buf_size, fmt, args);
|
|
# endif
|
|
if (buf == NULL)
|
|
return w;
|
|
if (w == -1 || w >= (int)buf_size)
|
|
w = (int)buf_size - 1;
|
|
buf[w] = 0;
|
|
return w;
|
|
}
|
|
# endif // #ifdef IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS
|
|
|
|
// CRC32 needs a 1KB lookup table (not cache friendly)
|
|
// Although the code to generate the table is simple and shorter than the table itself, using a const table allows us to
|
|
// easily:
|
|
// - avoid an unnecessary branch/memory tap, - keep the ImHashXXX functions usable by static constructors, - make it
|
|
// thread-safe.
|
|
static const ImU32 GCrc32LookupTable[256] = {
|
|
0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832,
|
|
0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2,
|
|
0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A,
|
|
0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
|
|
0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3,
|
|
0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423,
|
|
0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB,
|
|
0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
|
|
0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4,
|
|
0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950,
|
|
0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074,
|
|
0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
|
|
0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525,
|
|
0x206F85B3, 0xB966D409, 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81,
|
|
0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615,
|
|
0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
|
|
0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76,
|
|
0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E,
|
|
0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6,
|
|
0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
|
|
0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7,
|
|
0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F,
|
|
0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7,
|
|
0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
|
|
0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278,
|
|
0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC,
|
|
0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330,
|
|
0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
|
|
0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D,
|
|
};
|
|
|
|
// Known size hash
|
|
// It is ok to call ImHashData on a string with known length but the ### operator won't be supported.
|
|
// FIXME-OPT: Replace with e.g. FNV1a hash? CRC32 pretty much randomly access 1KB. Need to do proper measurements.
|
|
ImGuiID ImHashData(const void* data_p, size_t data_size, ImU32 seed)
|
|
{
|
|
ImU32 crc = ~seed;
|
|
const unsigned char* data = (const unsigned char*)data_p;
|
|
const ImU32* crc32_lut = GCrc32LookupTable;
|
|
while (data_size-- != 0)
|
|
crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ *data++];
|
|
return ~crc;
|
|
}
|
|
|
|
// Zero-terminated string hash, with support for ### to reset back to seed value
|
|
// We support a syntax of "label###id" where only "###id" is included in the hash, and only "label" gets displayed.
|
|
// Because this syntax is rarely used we are optimizing for the common case.
|
|
// - If we reach ### in the string we discard the hash so far and reset to the seed.
|
|
// - We don't do 'current += 2; continue;' after handling ### to keep the code smaller/faster (measured ~10% diff in
|
|
// Debug build)
|
|
// FIXME-OPT: Replace with e.g. FNV1a hash? CRC32 pretty much randomly access 1KB. Need to do proper measurements.
|
|
ImGuiID ImHashStr(const char* data_p, size_t data_size, ImU32 seed)
|
|
{
|
|
seed = ~seed;
|
|
ImU32 crc = seed;
|
|
const unsigned char* data = (const unsigned char*)data_p;
|
|
const ImU32* crc32_lut = GCrc32LookupTable;
|
|
if (data_size != 0)
|
|
{
|
|
while (data_size-- != 0)
|
|
{
|
|
unsigned char c = *data++;
|
|
if (c == '#' && data_size >= 2 && data[0] == '#' && data[1] == '#')
|
|
crc = seed;
|
|
crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while (unsigned char c = *data++)
|
|
{
|
|
if (c == '#' && data[0] == '#' && data[1] == '#')
|
|
crc = seed;
|
|
crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c];
|
|
}
|
|
}
|
|
return ~crc;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// [SECTION] MISC HELPERS/UTILITIES (File functions)
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Default file functions
|
|
# ifndef IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS
|
|
|
|
ImFileHandle ImFileOpen(const char* filename, const char* mode)
|
|
{
|
|
# if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && !defined(__CYGWIN__) && !defined(__GNUC__)
|
|
// We need a fopen() wrapper because MSVC/Windows fopen doesn't handle UTF-8 filenames.
|
|
// Previously we used ImTextCountCharsFromUtf8/ImTextStrFromUtf8 here but we now need to support ImWchar16 and
|
|
// ImWchar32!
|
|
const int filename_wsize = ::MultiByteToWideChar(CP_UTF8, 0, filename, -1, NULL, 0);
|
|
const int mode_wsize = ::MultiByteToWideChar(CP_UTF8, 0, mode, -1, NULL, 0);
|
|
ImVector<ImWchar> buf;
|
|
buf.resize(filename_wsize + mode_wsize);
|
|
::MultiByteToWideChar(CP_UTF8, 0, filename, -1, (wchar_t*)&buf[0], filename_wsize);
|
|
::MultiByteToWideChar(CP_UTF8, 0, mode, -1, (wchar_t*)&buf[filename_wsize], mode_wsize);
|
|
return ::_wfopen((const wchar_t*)&buf[0], (const wchar_t*)&buf[filename_wsize]);
|
|
# else
|
|
return fopen(filename, mode);
|
|
# endif
|
|
}
|
|
|
|
// We should in theory be using fseeko()/ftello() with off_t and _fseeki64()/_ftelli64() with __int64, waiting for the
|
|
// PR that does that in a very portable pre-C++11 zero-warnings way.
|
|
bool ImFileClose(ImFileHandle f)
|
|
{
|
|
return fclose(f) == 0;
|
|
}
|
|
ImU64 ImFileGetSize(ImFileHandle f)
|
|
{
|
|
long off = 0, sz = 0;
|
|
return ((off = ftell(f)) != -1 && !fseek(f, 0, SEEK_END) && (sz = ftell(f)) != -1 && !fseek(f, off, SEEK_SET))
|
|
? (ImU64)sz
|
|
: (ImU64)-1;
|
|
}
|
|
ImU64 ImFileRead(void* data, ImU64 sz, ImU64 count, ImFileHandle f)
|
|
{
|
|
return fread(data, (size_t)sz, (size_t)count, f);
|
|
}
|
|
ImU64 ImFileWrite(const void* data, ImU64 sz, ImU64 count, ImFileHandle f)
|
|
{
|
|
return fwrite(data, (size_t)sz, (size_t)count, f);
|
|
}
|
|
# endif // #ifndef IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS
|
|
|
|
// Helper: Load file content into memory
|
|
// Memory allocated with IM_ALLOC(), must be freed by user using IM_FREE() == ImGui::MemFree()
|
|
// This can't really be used with "rt" because fseek size won't match read size.
|
|
void* ImFileLoadToMemory(const char* filename, const char* mode, size_t* out_file_size, int padding_bytes)
|
|
{
|
|
IM_ASSERT(filename && mode);
|
|
if (out_file_size)
|
|
*out_file_size = 0;
|
|
|
|
ImFileHandle f;
|
|
if ((f = ImFileOpen(filename, mode)) == NULL)
|
|
return NULL;
|
|
|
|
size_t file_size = (size_t)ImFileGetSize(f);
|
|
if (file_size == (size_t)-1)
|
|
{
|
|
ImFileClose(f);
|
|
return NULL;
|
|
}
|
|
|
|
void* file_data = IM_ALLOC(file_size + padding_bytes);
|
|
if (file_data == NULL)
|
|
{
|
|
ImFileClose(f);
|
|
return NULL;
|
|
}
|
|
if (ImFileRead(file_data, 1, file_size, f) != file_size)
|
|
{
|
|
ImFileClose(f);
|
|
IM_FREE(file_data);
|
|
return NULL;
|
|
}
|
|
if (padding_bytes > 0)
|
|
memset((void*)(((char*)file_data) + file_size), 0, (size_t)padding_bytes);
|
|
|
|
ImFileClose(f);
|
|
if (out_file_size)
|
|
*out_file_size = file_size;
|
|
|
|
return file_data;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// [SECTION] MISC HELPERS/UTILITIES (ImText* functions)
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Convert UTF-8 to 32-bit character, process single character input.
|
|
// A nearly-branchless UTF-8 decoder, based on work of Christopher Wellons (https://github.com/skeeto/branchless-utf8).
|
|
// We handle UTF-8 decoding error by skipping forward.
|
|
int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* in_text_end)
|
|
{
|
|
static const char lengths[32] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 3, 3, 4, 0};
|
|
static const int masks[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07};
|
|
static const uint32_t mins[] = {0x400000, 0, 0x80, 0x800, 0x10000};
|
|
static const int shiftc[] = {0, 18, 12, 6, 0};
|
|
static const int shifte[] = {0, 6, 4, 2, 0};
|
|
int len = lengths[*(const unsigned char*)in_text >> 3];
|
|
int wanted = len + !len;
|
|
|
|
if (in_text_end == NULL)
|
|
in_text_end = in_text + wanted; // Max length, nulls will be taken into account.
|
|
|
|
// Copy at most 'len' bytes, stop copying at 0 or past in_text_end. Branch predictor does a good job here,
|
|
// so it is fast even with excessive branching.
|
|
unsigned char s[4];
|
|
s[0] = in_text + 0 < in_text_end ? in_text[0] : 0;
|
|
s[1] = in_text + 1 < in_text_end ? in_text[1] : 0;
|
|
s[2] = in_text + 2 < in_text_end ? in_text[2] : 0;
|
|
s[3] = in_text + 3 < in_text_end ? in_text[3] : 0;
|
|
|
|
// Assume a four-byte character and load four bytes. Unused bits are shifted out.
|
|
*out_char = (uint32_t)(s[0] & masks[len]) << 18;
|
|
*out_char |= (uint32_t)(s[1] & 0x3f) << 12;
|
|
*out_char |= (uint32_t)(s[2] & 0x3f) << 6;
|
|
*out_char |= (uint32_t)(s[3] & 0x3f) << 0;
|
|
*out_char >>= shiftc[len];
|
|
|
|
// Accumulate the various error conditions.
|
|
int e = 0;
|
|
e = (*out_char < mins[len]) << 6; // non-canonical encoding
|
|
e |= ((*out_char >> 11) == 0x1b) << 7; // surrogate half?
|
|
e |= (*out_char > IM_UNICODE_CODEPOINT_MAX) << 8; // out of range?
|
|
e |= (s[1] & 0xc0) >> 2;
|
|
e |= (s[2] & 0xc0) >> 4;
|
|
e |= (s[3]) >> 6;
|
|
e ^= 0x2a; // top two bits of each tail byte correct?
|
|
e >>= shifte[len];
|
|
|
|
if (e)
|
|
{
|
|
// No bytes are consumed when *in_text == 0 || in_text == in_text_end.
|
|
// One byte is consumed in case of invalid first byte of in_text.
|
|
// All available bytes (at most `len` bytes) are consumed on incomplete/invalid second to last bytes.
|
|
// Invalid or incomplete input may consume less bytes than wanted, therefore every byte has to be inspected in
|
|
// s.
|
|
wanted = ImMin(wanted, !!s[0] + !!s[1] + !!s[2] + !!s[3]);
|
|
*out_char = IM_UNICODE_CODEPOINT_INVALID;
|
|
}
|
|
|
|
return wanted;
|
|
}
|
|
|
|
int ImTextStrFromUtf8(ImWchar* buf,
|
|
int buf_size,
|
|
const char* in_text,
|
|
const char* in_text_end,
|
|
const char** in_text_remaining)
|
|
{
|
|
ImWchar* buf_out = buf;
|
|
ImWchar* buf_end = buf + buf_size;
|
|
while (buf_out < buf_end - 1 && (!in_text_end || in_text < in_text_end) && *in_text)
|
|
{
|
|
unsigned int c;
|
|
in_text += ImTextCharFromUtf8(&c, in_text, in_text_end);
|
|
if (c == 0)
|
|
break;
|
|
*buf_out++ = (ImWchar)c;
|
|
}
|
|
*buf_out = 0;
|
|
if (in_text_remaining)
|
|
*in_text_remaining = in_text;
|
|
return (int)(buf_out - buf);
|
|
}
|
|
|
|
int ImTextCountCharsFromUtf8(const char* in_text, const char* in_text_end)
|
|
{
|
|
int char_count = 0;
|
|
while ((!in_text_end || in_text < in_text_end) && *in_text)
|
|
{
|
|
unsigned int c;
|
|
in_text += ImTextCharFromUtf8(&c, in_text, in_text_end);
|
|
if (c == 0)
|
|
break;
|
|
char_count++;
|
|
}
|
|
return char_count;
|
|
}
|
|
|
|
// Based on stb_to_utf8() from github.com/nothings/stb/
|
|
static inline int ImTextCharToUtf8_inline(char* buf, int buf_size, unsigned int c)
|
|
{
|
|
if (c < 0x80)
|
|
{
|
|
buf[0] = (char)c;
|
|
return 1;
|
|
}
|
|
if (c < 0x800)
|
|
{
|
|
if (buf_size < 2)
|
|
return 0;
|
|
buf[0] = (char)(0xc0 + (c >> 6));
|
|
buf[1] = (char)(0x80 + (c & 0x3f));
|
|
return 2;
|
|
}
|
|
if (c < 0x10000)
|
|
{
|
|
if (buf_size < 3)
|
|
return 0;
|
|
buf[0] = (char)(0xe0 + (c >> 12));
|
|
buf[1] = (char)(0x80 + ((c >> 6) & 0x3f));
|
|
buf[2] = (char)(0x80 + ((c)&0x3f));
|
|
return 3;
|
|
}
|
|
if (c <= 0x10FFFF)
|
|
{
|
|
if (buf_size < 4)
|
|
return 0;
|
|
buf[0] = (char)(0xf0 + (c >> 18));
|
|
buf[1] = (char)(0x80 + ((c >> 12) & 0x3f));
|
|
buf[2] = (char)(0x80 + ((c >> 6) & 0x3f));
|
|
buf[3] = (char)(0x80 + ((c)&0x3f));
|
|
return 4;
|
|
}
|
|
// Invalid code point, the max unicode is 0x10FFFF
|
|
return 0;
|
|
}
|
|
|
|
const char* ImTextCharToUtf8(char out_buf[5], unsigned int c)
|
|
{
|
|
int count = ImTextCharToUtf8_inline(out_buf, 5, c);
|
|
out_buf[count] = 0;
|
|
return out_buf;
|
|
}
|
|
|
|
// Not optimal but we very rarely use this function.
|
|
int ImTextCountUtf8BytesFromChar(const char* in_text, const char* in_text_end)
|
|
{
|
|
unsigned int unused = 0;
|
|
return ImTextCharFromUtf8(&unused, in_text, in_text_end);
|
|
}
|
|
|
|
static inline int ImTextCountUtf8BytesFromChar(unsigned int c)
|
|
{
|
|
if (c < 0x80)
|
|
return 1;
|
|
if (c < 0x800)
|
|
return 2;
|
|
if (c < 0x10000)
|
|
return 3;
|
|
if (c <= 0x10FFFF)
|
|
return 4;
|
|
return 3;
|
|
}
|
|
|
|
int ImTextStrToUtf8(char* out_buf, int out_buf_size, const ImWchar* in_text, const ImWchar* in_text_end)
|
|
{
|
|
char* buf_p = out_buf;
|
|
const char* buf_end = out_buf + out_buf_size;
|
|
while (buf_p < buf_end - 1 && (!in_text_end || in_text < in_text_end) && *in_text)
|
|
{
|
|
unsigned int c = (unsigned int)(*in_text++);
|
|
if (c < 0x80)
|
|
*buf_p++ = (char)c;
|
|
else
|
|
buf_p += ImTextCharToUtf8_inline(buf_p, (int)(buf_end - buf_p - 1), c);
|
|
}
|
|
*buf_p = 0;
|
|
return (int)(buf_p - out_buf);
|
|
}
|
|
|
|
int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_end)
|
|
{
|
|
int bytes_count = 0;
|
|
while ((!in_text_end || in_text < in_text_end) && *in_text)
|
|
{
|
|
unsigned int c = (unsigned int)(*in_text++);
|
|
if (c < 0x80)
|
|
bytes_count++;
|
|
else
|
|
bytes_count += ImTextCountUtf8BytesFromChar(c);
|
|
}
|
|
return bytes_count;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// [SECTION] MISC HELPERS/UTILITIES (Color functions)
|
|
// Note: The Convert functions are early design which are not consistent with other API.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
IMGUI_API ImU32 ImAlphaBlendColors(ImU32 col_a, ImU32 col_b)
|
|
{
|
|
float t = ((col_b >> IM_COL32_A_SHIFT) & 0xFF) / 255.f;
|
|
int r = ImLerp((int)(col_a >> IM_COL32_R_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_R_SHIFT) & 0xFF, t);
|
|
int g = ImLerp((int)(col_a >> IM_COL32_G_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_G_SHIFT) & 0xFF, t);
|
|
int b = ImLerp((int)(col_a >> IM_COL32_B_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_B_SHIFT) & 0xFF, t);
|
|
return IM_COL32(r, g, b, 0xFF);
|
|
}
|
|
|
|
ImVec4 ImGui::ColorConvertU32ToFloat4(ImU32 in)
|
|
{
|
|
float s = 1.0f / 255.0f;
|
|
return ImVec4(((in >> IM_COL32_R_SHIFT) & 0xFF) * s, ((in >> IM_COL32_G_SHIFT) & 0xFF) * s,
|
|
((in >> IM_COL32_B_SHIFT) & 0xFF) * s, ((in >> IM_COL32_A_SHIFT) & 0xFF) * s);
|
|
}
|
|
|
|
ImU32 ImGui::ColorConvertFloat4ToU32(const ImVec4& in)
|
|
{
|
|
ImU32 out;
|
|
out = ((ImU32)IM_F32_TO_INT8_SAT(in.x)) << IM_COL32_R_SHIFT;
|
|
out |= ((ImU32)IM_F32_TO_INT8_SAT(in.y)) << IM_COL32_G_SHIFT;
|
|
out |= ((ImU32)IM_F32_TO_INT8_SAT(in.z)) << IM_COL32_B_SHIFT;
|
|
out |= ((ImU32)IM_F32_TO_INT8_SAT(in.w)) << IM_COL32_A_SHIFT;
|
|
return out;
|
|
}
|
|
|
|
// Convert rgb floats ([0-1],[0-1],[0-1]) to hsv floats ([0-1],[0-1],[0-1]), from Foley & van Dam p592
|
|
// Optimized http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv
|
|
void ImGui::ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& out_s, float& out_v)
|
|
{
|
|
float K = 0.f;
|
|
if (g < b)
|
|
{
|
|
ImSwap(g, b);
|
|
K = -1.f;
|
|
}
|
|
if (r < g)
|
|
{
|
|
ImSwap(r, g);
|
|
K = -2.f / 6.f - K;
|
|
}
|
|
|
|
const float chroma = r - (g < b ? g : b);
|
|
out_h = ImFabs(K + (g - b) / (6.f * chroma + 1e-20f));
|
|
out_s = chroma / (r + 1e-20f);
|
|
out_v = r;
|
|
}
|
|
|
|
// Convert hsv floats ([0-1],[0-1],[0-1]) to rgb floats ([0-1],[0-1],[0-1]), from Foley & van Dam p593
|
|
// also http://en.wikipedia.org/wiki/HSL_and_HSV
|
|
void ImGui::ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& out_g, float& out_b)
|
|
{
|
|
if (s == 0.0f)
|
|
{
|
|
// gray
|
|
out_r = out_g = out_b = v;
|
|
return;
|
|
}
|
|
|
|
h = ImFmod(h, 1.0f) / (60.0f / 360.0f);
|
|
int i = (int)h;
|
|
float f = h - (float)i;
|
|
float p = v * (1.0f - s);
|
|
float q = v * (1.0f - s * f);
|
|
float t = v * (1.0f - s * (1.0f - f));
|
|
|
|
switch (i)
|
|
{
|
|
case 0:
|
|
out_r = v;
|
|
out_g = t;
|
|
out_b = p;
|
|
break;
|
|
case 1:
|
|
out_r = q;
|
|
out_g = v;
|
|
out_b = p;
|
|
break;
|
|
case 2:
|
|
out_r = p;
|
|
out_g = v;
|
|
out_b = t;
|
|
break;
|
|
case 3:
|
|
out_r = p;
|
|
out_g = q;
|
|
out_b = v;
|
|
break;
|
|
case 4:
|
|
out_r = t;
|
|
out_g = p;
|
|
out_b = v;
|
|
break;
|
|
case 5:
|
|
default:
|
|
out_r = v;
|
|
out_g = p;
|
|
out_b = q;
|
|
break;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// [SECTION] ImGuiStorage
|
|
// Helper: Key->value storage
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// std::lower_bound but without the bullshit
|
|
static ImGuiStorage::ImGuiStoragePair* LowerBound(ImVector<ImGuiStorage::ImGuiStoragePair>& data, ImGuiID key)
|
|
{
|
|
ImGuiStorage::ImGuiStoragePair* first = data.Data;
|
|
ImGuiStorage::ImGuiStoragePair* last = data.Data + data.Size;
|
|
size_t count = (size_t)(last - first);
|
|
while (count > 0)
|
|
{
|
|
size_t count2 = count >> 1;
|
|
ImGuiStorage::ImGuiStoragePair* mid = first + count2;
|
|
if (mid->key < key)
|
|
{
|
|
first = ++mid;
|
|
count -= count2 + 1;
|
|
}
|
|
else
|
|
{
|
|
count = count2;
|
|
}
|
|
}
|
|
return first;
|
|
}
|
|
|
|
// For quicker full rebuild of a storage (instead of an incremental one), you may add all your contents and then sort
|
|
// once.
|
|
void ImGuiStorage::BuildSortByKey()
|
|
{
|
|
struct StaticFunc
|
|
{
|
|
static int IMGUI_CDECL PairCompareByID(const void* lhs, const void* rhs)
|
|
{
|
|
// We can't just do a subtraction because qsort uses signed integers and subtracting our ID doesn't play
|
|
// well with that.
|
|
if (((const ImGuiStoragePair*)lhs)->key > ((const ImGuiStoragePair*)rhs)->key)
|
|
return +1;
|
|
if (((const ImGuiStoragePair*)lhs)->key < ((const ImGuiStoragePair*)rhs)->key)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
};
|
|
if (Data.Size > 1)
|
|
ImQsort(Data.Data, (size_t)Data.Size, sizeof(ImGuiStoragePair), StaticFunc::PairCompareByID);
|
|
}
|
|
|
|
int ImGuiStorage::GetInt(ImGuiID key, int default_val) const
|
|
{
|
|
ImGuiStoragePair* it = LowerBound(const_cast<ImVector<ImGuiStoragePair>&>(Data), key);
|
|
if (it == Data.end() || it->key != key)
|
|
return default_val;
|
|
return it->val_i;
|
|
}
|
|
|
|
bool ImGuiStorage::GetBool(ImGuiID key, bool default_val) const
|
|
{
|
|
return GetInt(key, default_val ? 1 : 0) != 0;
|
|
}
|
|
|
|
float ImGuiStorage::GetFloat(ImGuiID key, float default_val) const
|
|
{
|
|
ImGuiStoragePair* it = LowerBound(const_cast<ImVector<ImGuiStoragePair>&>(Data), key);
|
|
if (it == Data.end() || it->key != key)
|
|
return default_val;
|
|
return it->val_f;
|
|
}
|
|
|
|
void* ImGuiStorage::GetVoidPtr(ImGuiID key) const
|
|
{
|
|
ImGuiStoragePair* it = LowerBound(const_cast<ImVector<ImGuiStoragePair>&>(Data), key);
|
|
if (it == Data.end() || it->key != key)
|
|
return NULL;
|
|
return it->val_p;
|
|
}
|
|
|
|
// References are only valid until a new value is added to the storage. Calling a Set***() function or a Get***Ref()
|
|
// function invalidates the pointer.
|
|
int* ImGuiStorage::GetIntRef(ImGuiID key, int default_val)
|
|
{
|
|
ImGuiStoragePair* it = LowerBound(Data, key);
|
|
if (it == Data.end() || it->key != key)
|
|
it = Data.insert(it, ImGuiStoragePair(key, default_val));
|
|
return &it->val_i;
|
|
}
|
|
|
|
bool* ImGuiStorage::GetBoolRef(ImGuiID key, bool default_val)
|
|
{
|
|
return (bool*)GetIntRef(key, default_val ? 1 : 0);
|
|
}
|
|
|
|
float* ImGuiStorage::GetFloatRef(ImGuiID key, float default_val)
|
|
{
|
|
ImGuiStoragePair* it = LowerBound(Data, key);
|
|
if (it == Data.end() || it->key != key)
|
|
it = Data.insert(it, ImGuiStoragePair(key, default_val));
|
|
return &it->val_f;
|
|
}
|
|
|
|
void** ImGuiStorage::GetVoidPtrRef(ImGuiID key, void* default_val)
|
|
{
|
|
ImGuiStoragePair* it = LowerBound(Data, key);
|
|
if (it == Data.end() || it->key != key)
|
|
it = Data.insert(it, ImGuiStoragePair(key, default_val));
|
|
return &it->val_p;
|
|
}
|
|
|
|
// FIXME-OPT: Need a way to reuse the result of lower_bound when doing GetInt()/SetInt() - not too bad because it only
|
|
// happens on explicit interaction (maximum one a frame)
|
|
void ImGuiStorage::SetInt(ImGuiID key, int val)
|
|
{
|
|
ImGuiStoragePair* it = LowerBound(Data, key);
|
|
if (it == Data.end() || it->key != key)
|
|
{
|
|
Data.insert(it, ImGuiStoragePair(key, val));
|
|
return;
|
|
}
|
|
it->val_i = val;
|
|
}
|
|
|
|
void ImGuiStorage::SetBool(ImGuiID key, bool val)
|
|
{
|
|
SetInt(key, val ? 1 : 0);
|
|
}
|
|
|
|
void ImGuiStorage::SetFloat(ImGuiID key, float val)
|
|
{
|
|
ImGuiStoragePair* it = LowerBound(Data, key);
|
|
if (it == Data.end() || it->key != key)
|
|
{
|
|
Data.insert(it, ImGuiStoragePair(key, val));
|
|
return;
|
|
}
|
|
it->val_f = val;
|
|
}
|
|
|
|
void ImGuiStorage::SetVoidPtr(ImGuiID key, void* val)
|
|
{
|
|
ImGuiStoragePair* it = LowerBound(Data, key);
|
|
if (it == Data.end() || it->key != key)
|
|
{
|
|
Data.insert(it, ImGuiStoragePair(key, val));
|
|
return;
|
|
}
|
|
it->val_p = val;
|
|
}
|
|
|
|
void ImGuiStorage::SetAllInt(int v)
|
|
{
|
|
for (int i = 0; i < Data.Size; i++)
|
|
Data[i].val_i = v;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// [SECTION] ImGuiTextFilter
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]"
|
|
ImGuiTextFilter::ImGuiTextFilter(const char* default_filter)
|
|
{
|
|
if (default_filter)
|
|
{
|
|
ImStrncpy(InputBuf, default_filter, IM_ARRAYSIZE(InputBuf));
|
|
Build();
|
|
}
|
|
else
|
|
{
|
|
InputBuf[0] = 0;
|
|
CountGrep = 0;
|
|
}
|
|
}
|
|
|
|
bool ImGuiTextFilter::Draw(const char* label, float width)
|
|
{
|
|
if (width != 0.0f)
|
|
ImGui::SetNextItemWidth(width);
|
|
bool value_changed = ImGui::InputText(label, InputBuf, IM_ARRAYSIZE(InputBuf));
|
|
if (value_changed)
|
|
Build();
|
|
return value_changed;
|
|
}
|
|
|
|
void ImGuiTextFilter::ImGuiTextRange::split(char separator, ImVector<ImGuiTextRange>* out) const
|
|
{
|
|
out->resize(0);
|
|
const char* wb = b;
|
|
const char* we = wb;
|
|
while (we < e)
|
|
{
|
|
if (*we == separator)
|
|
{
|
|
out->push_back(ImGuiTextRange(wb, we));
|
|
wb = we + 1;
|
|
}
|
|
we++;
|
|
}
|
|
if (wb != we)
|
|
out->push_back(ImGuiTextRange(wb, we));
|
|
}
|
|
|
|
void ImGuiTextFilter::Build()
|
|
{
|
|
Filters.resize(0);
|
|
ImGuiTextRange input_range(InputBuf, InputBuf + strlen(InputBuf));
|
|
input_range.split(',', &Filters);
|
|
|
|
CountGrep = 0;
|
|
for (int i = 0; i != Filters.Size; i++)
|
|
{
|
|
ImGuiTextRange& f = Filters[i];
|
|
while (f.b < f.e && ImCharIsBlankA(f.b[0]))
|
|
f.b++;
|
|
while (f.e > f.b && ImCharIsBlankA(f.e[-1]))
|
|
f.e--;
|
|
if (f.empty())
|
|
continue;
|
|
if (Filters[i].b[0] != '-')
|
|
CountGrep += 1;
|
|
}
|
|
}
|
|
|
|
bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const
|
|
{
|
|
if (Filters.empty())
|
|
return true;
|
|
|
|
if (text == NULL)
|
|
text = "";
|
|
|
|
for (int i = 0; i != Filters.Size; i++)
|
|
{
|
|
const ImGuiTextRange& f = Filters[i];
|
|
if (f.empty())
|
|
continue;
|
|
if (f.b[0] == '-')
|
|
{
|
|
// Subtract
|
|
if (ImStristr(text, text_end, f.b + 1, f.e) != NULL)
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// Grep
|
|
if (ImStristr(text, text_end, f.b, f.e) != NULL)
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Implicit * grep
|
|
if (CountGrep == 0)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// [SECTION] ImGuiTextBuffer
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// On some platform vsnprintf() takes va_list by reference and modifies it.
|
|
// va_copy is the 'correct' way to copy a va_list but Visual Studio prior to 2013 doesn't have it.
|
|
# ifndef va_copy
|
|
# if defined(__GNUC__) || defined(__clang__)
|
|
# define va_copy(dest, src) __builtin_va_copy(dest, src)
|
|
# else
|
|
# define va_copy(dest, src) (dest = src)
|
|
# endif
|
|
# endif
|
|
|
|
char ImGuiTextBuffer::EmptyString[1] = {0};
|
|
|
|
void ImGuiTextBuffer::append(const char* str, const char* str_end)
|
|
{
|
|
int len = str_end ? (int)(str_end - str) : (int)strlen(str);
|
|
|
|
// Add zero-terminator the first time
|
|
const int write_off = (Buf.Size != 0) ? Buf.Size : 1;
|
|
const int needed_sz = write_off + len;
|
|
if (write_off + len >= Buf.Capacity)
|
|
{
|
|
int new_capacity = Buf.Capacity * 2;
|
|
Buf.reserve(needed_sz > new_capacity ? needed_sz : new_capacity);
|
|
}
|
|
|
|
Buf.resize(needed_sz);
|
|
memcpy(&Buf[write_off - 1], str, (size_t)len);
|
|
Buf[write_off - 1 + len] = 0;
|
|
}
|
|
|
|
void ImGuiTextBuffer::appendf(const char* fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
appendfv(fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
// Helper: Text buffer for logging/accumulating text
|
|
void ImGuiTextBuffer::appendfv(const char* fmt, va_list args)
|
|
{
|
|
va_list args_copy;
|
|
va_copy(args_copy, args);
|
|
|
|
int len = ImFormatStringV(
|
|
NULL, 0, fmt, args); // FIXME-OPT: could do a first pass write attempt, likely successful on first pass.
|
|
if (len <= 0)
|
|
{
|
|
va_end(args_copy);
|
|
return;
|
|
}
|
|
|
|
// Add zero-terminator the first time
|
|
const int write_off = (Buf.Size != 0) ? Buf.Size : 1;
|
|
const int needed_sz = write_off + len;
|
|
if (write_off + len >= Buf.Capacity)
|
|
{
|
|
int new_capacity = Buf.Capacity * 2;
|
|
Buf.reserve(needed_sz > new_capacity ? needed_sz : new_capacity);
|
|
}
|
|
|
|
Buf.resize(needed_sz);
|
|
ImFormatStringV(&Buf[write_off - 1], (size_t)len + 1, fmt, args_copy);
|
|
va_end(args_copy);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// [SECTION] ImGuiListClipper
|
|
// This is currently not as flexible/powerful as it should be and really confusing/spaghetti, mostly because we changed
|
|
// the API mid-way through development and support two ways to using the clipper, needs some rework (see TODO)
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// FIXME-TABLE: This prevents us from using ImGuiListClipper _inside_ a table cell.
|
|
// The problem we have is that without a Begin/End scheme for rows using the clipper is ambiguous.
|
|
static bool GetSkipItemForListClipping()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
return (g.CurrentTable ? g.CurrentTable->HostSkipItems : g.CurrentWindow->SkipItems);
|
|
}
|
|
|
|
// Helper to calculate coarse clipping of large list of evenly sized items.
|
|
// NB: Prefer using the ImGuiListClipper higher-level helper if you can! Read comments and instructions there on how
|
|
// those use this sort of pattern. NB: 'items_count' is only used to clamp the result, if you don't know your count you
|
|
// can use INT_MAX
|
|
void ImGui::CalcListClipping(int items_count,
|
|
float items_height,
|
|
int* out_items_display_start,
|
|
int* out_items_display_end)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
if (g.LogEnabled)
|
|
{
|
|
// If logging is active, do not perform any clipping
|
|
*out_items_display_start = 0;
|
|
*out_items_display_end = items_count;
|
|
return;
|
|
}
|
|
if (GetSkipItemForListClipping())
|
|
{
|
|
*out_items_display_start = *out_items_display_end = 0;
|
|
return;
|
|
}
|
|
|
|
// We create the union of the ClipRect and the NavScoringRect which at worst should be 1 page away from ClipRect
|
|
ImRect unclipped_rect = window->ClipRect;
|
|
if (g.NavMoveRequest)
|
|
unclipped_rect.Add(g.NavScoringRect);
|
|
if (g.NavJustMovedToId && window->NavLastIds[0] == g.NavJustMovedToId)
|
|
unclipped_rect.Add(ImRect(window->Pos + window->NavRectRel[0].Min, window->Pos + window->NavRectRel[0].Max));
|
|
|
|
const ImVec2 pos = window->DC.CursorPos;
|
|
int start = (int)((unclipped_rect.Min.y - pos.y) / items_height);
|
|
int end = (int)((unclipped_rect.Max.y - pos.y) / items_height);
|
|
|
|
// When performing a navigation request, ensure we have one item extra in the direction we are moving to
|
|
if (g.NavMoveRequest && g.NavMoveClipDir == ImGuiDir_Up)
|
|
start--;
|
|
if (g.NavMoveRequest && g.NavMoveClipDir == ImGuiDir_Down)
|
|
end++;
|
|
|
|
start = ImClamp(start, 0, items_count);
|
|
end = ImClamp(end + 1, start, items_count);
|
|
*out_items_display_start = start;
|
|
*out_items_display_end = end;
|
|
}
|
|
|
|
static void SetCursorPosYAndSetupForPrevLine(float pos_y, float line_height)
|
|
{
|
|
// 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.
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
float off_y = pos_y - window->DC.CursorPos.y;
|
|
window->DC.CursorPos.y = pos_y;
|
|
window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, pos_y);
|
|
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 -
|
|
g.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 (ImGuiOldColumns* columns = window->DC.CurrentColumns)
|
|
columns->LineMinY = window->DC.CursorPos.y; // Setting this so that cell Y position are set properly
|
|
if (ImGuiTable* table = g.CurrentTable)
|
|
{
|
|
if (table->IsInsideRow)
|
|
ImGui::TableEndRow(table);
|
|
table->RowPosY2 = window->DC.CursorPos.y;
|
|
const int row_increase = (int)((off_y / line_height) + 0.5f);
|
|
// table->CurrentRow += row_increase; // Can't do without fixing TableEndRow()
|
|
table->RowBgColorCounter += row_increase;
|
|
}
|
|
}
|
|
|
|
ImGuiListClipper::ImGuiListClipper()
|
|
{
|
|
memset(this, 0, sizeof(*this));
|
|
ItemsCount = -1;
|
|
}
|
|
|
|
ImGuiListClipper::~ImGuiListClipper()
|
|
{
|
|
IM_ASSERT(ItemsCount == -1 && "Forgot to call End(), or to Step() until false?");
|
|
}
|
|
|
|
// Use case A: Begin() called from constructor with items_height<0, then called again from Step() in StepNo 1
|
|
// Use case B: Begin() called from constructor with items_height>0
|
|
// FIXME-LEGACY: Ideally we should remove the Begin/End functions but they are part of the legacy API we still support.
|
|
// This is why some of the code in Step() calling Begin() and reassign some fields, spaghetti style.
|
|
void ImGuiListClipper::Begin(int items_count, float items_height)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
|
|
if (ImGuiTable* table = g.CurrentTable)
|
|
if (table->IsInsideRow)
|
|
ImGui::TableEndRow(table);
|
|
|
|
StartPosY = window->DC.CursorPos.y;
|
|
ItemsHeight = items_height;
|
|
ItemsCount = items_count;
|
|
ItemsFrozen = 0;
|
|
StepNo = 0;
|
|
DisplayStart = -1;
|
|
DisplayEnd = 0;
|
|
}
|
|
|
|
void ImGuiListClipper::End()
|
|
{
|
|
if (ItemsCount < 0) // Already ended
|
|
return;
|
|
|
|
// In theory here we should assert that ImGui::GetCursorPosY() == StartPosY + DisplayEnd * ItemsHeight, but it feels
|
|
// saner to just seek at the end and not assert/crash the user.
|
|
if (ItemsCount < INT_MAX && DisplayStart >= 0)
|
|
SetCursorPosYAndSetupForPrevLine(StartPosY + (ItemsCount - ItemsFrozen) * ItemsHeight, ItemsHeight);
|
|
ItemsCount = -1;
|
|
StepNo = 3;
|
|
}
|
|
|
|
bool ImGuiListClipper::Step()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
|
|
ImGuiTable* table = g.CurrentTable;
|
|
if (table && table->IsInsideRow)
|
|
ImGui::TableEndRow(table);
|
|
|
|
// No items
|
|
if (ItemsCount == 0 || GetSkipItemForListClipping())
|
|
{
|
|
End();
|
|
return false;
|
|
}
|
|
|
|
// Step 0: Let you process the first element (regardless of it being visible or not, so we can measure the element
|
|
// height)
|
|
if (StepNo == 0)
|
|
{
|
|
// While we are in frozen row state, keep displaying items one by one, unclipped
|
|
// FIXME: Could be stored as a table-agnostic state.
|
|
if (table != NULL && !table->IsUnfrozenRows)
|
|
{
|
|
DisplayStart = ItemsFrozen;
|
|
DisplayEnd = ItemsFrozen + 1;
|
|
ItemsFrozen++;
|
|
return true;
|
|
}
|
|
|
|
StartPosY = window->DC.CursorPos.y;
|
|
if (ItemsHeight <= 0.0f)
|
|
{
|
|
// Submit the first item so we can measure its height (generally it is 0..1)
|
|
DisplayStart = ItemsFrozen;
|
|
DisplayEnd = ItemsFrozen + 1;
|
|
StepNo = 1;
|
|
return true;
|
|
}
|
|
|
|
// Already has item height (given by user in Begin): skip to calculating step
|
|
DisplayStart = DisplayEnd;
|
|
StepNo = 2;
|
|
}
|
|
|
|
// Step 1: the clipper infer height from first element
|
|
if (StepNo == 1)
|
|
{
|
|
IM_ASSERT(ItemsHeight <= 0.0f);
|
|
if (table)
|
|
{
|
|
const float pos_y1 =
|
|
table->RowPosY1; // Using this instead of StartPosY to handle clipper straddling the frozen row
|
|
const float pos_y2 = table->RowPosY2; // Using this instead of CursorPos.y to take account of tallest cell.
|
|
ItemsHeight = pos_y2 - pos_y1;
|
|
window->DC.CursorPos.y = pos_y2;
|
|
}
|
|
else
|
|
{
|
|
ItemsHeight = window->DC.CursorPos.y - StartPosY;
|
|
}
|
|
IM_ASSERT(ItemsHeight > 0.0f &&
|
|
"Unable to calculate item height! First item hasn't moved the cursor vertically!");
|
|
StepNo = 2;
|
|
}
|
|
|
|
// Reached end of list
|
|
if (DisplayEnd >= ItemsCount)
|
|
{
|
|
End();
|
|
return false;
|
|
}
|
|
|
|
// Step 2: calculate the actual range of elements to display, and position the cursor before the first element
|
|
if (StepNo == 2)
|
|
{
|
|
IM_ASSERT(ItemsHeight > 0.0f);
|
|
|
|
int already_submitted = DisplayEnd;
|
|
ImGui::CalcListClipping(ItemsCount - already_submitted, ItemsHeight, &DisplayStart, &DisplayEnd);
|
|
DisplayStart += already_submitted;
|
|
DisplayEnd += already_submitted;
|
|
|
|
// Seek cursor
|
|
if (DisplayStart > already_submitted)
|
|
SetCursorPosYAndSetupForPrevLine(StartPosY + (DisplayStart - ItemsFrozen) * ItemsHeight, ItemsHeight);
|
|
|
|
StepNo = 3;
|
|
return true;
|
|
}
|
|
|
|
// Step 3: the clipper validate that we have reached the expected Y position (corresponding to element DisplayEnd),
|
|
// Advance the cursor to the end of the list and then returns 'false' to end the loop.
|
|
if (StepNo == 3)
|
|
{
|
|
// Seek cursor
|
|
if (ItemsCount < INT_MAX)
|
|
SetCursorPosYAndSetupForPrevLine(StartPosY + (ItemsCount - ItemsFrozen) * ItemsHeight,
|
|
ItemsHeight); // advance cursor
|
|
ItemsCount = -1;
|
|
return false;
|
|
}
|
|
|
|
IM_ASSERT(0);
|
|
return false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// [SECTION] STYLING
|
|
//-----------------------------------------------------------------------------
|
|
|
|
ImGuiStyle& ImGui::GetStyle()
|
|
{
|
|
IM_ASSERT(GImGui != NULL &&
|
|
"No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext() ?");
|
|
return GImGui->Style;
|
|
}
|
|
|
|
ImU32 ImGui::GetColorU32(ImGuiCol idx, float alpha_mul)
|
|
{
|
|
ImGuiStyle& style = GImGui->Style;
|
|
ImVec4 c = style.Colors[idx];
|
|
c.w *= style.Alpha * alpha_mul;
|
|
return ColorConvertFloat4ToU32(c);
|
|
}
|
|
|
|
ImU32 ImGui::GetColorU32(const ImVec4& col)
|
|
{
|
|
ImGuiStyle& style = GImGui->Style;
|
|
ImVec4 c = col;
|
|
c.w *= style.Alpha;
|
|
return ColorConvertFloat4ToU32(c);
|
|
}
|
|
|
|
const ImVec4& ImGui::GetStyleColorVec4(ImGuiCol idx)
|
|
{
|
|
ImGuiStyle& style = GImGui->Style;
|
|
return style.Colors[idx];
|
|
}
|
|
|
|
ImU32 ImGui::GetColorU32(ImU32 col)
|
|
{
|
|
ImGuiStyle& style = GImGui->Style;
|
|
if (style.Alpha >= 1.0f)
|
|
return col;
|
|
ImU32 a = (col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT;
|
|
a = (ImU32)(a * style.Alpha); // We don't need to clamp 0..255 because Style.Alpha is in 0..1 range.
|
|
return (col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT);
|
|
}
|
|
|
|
// FIXME: This may incur a round-trip (if the end user got their data from a float4) but eventually we aim to store the
|
|
// in-flight colors as ImU32
|
|
void ImGui::PushStyleColor(ImGuiCol idx, ImU32 col)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiColorMod backup;
|
|
backup.Col = idx;
|
|
backup.BackupValue = g.Style.Colors[idx];
|
|
g.ColorStack.push_back(backup);
|
|
g.Style.Colors[idx] = ColorConvertU32ToFloat4(col);
|
|
}
|
|
|
|
void ImGui::PushStyleColor(ImGuiCol idx, const ImVec4& col)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiColorMod backup;
|
|
backup.Col = idx;
|
|
backup.BackupValue = g.Style.Colors[idx];
|
|
g.ColorStack.push_back(backup);
|
|
g.Style.Colors[idx] = col;
|
|
}
|
|
|
|
void ImGui::PopStyleColor(int count)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
while (count > 0)
|
|
{
|
|
ImGuiColorMod& backup = g.ColorStack.back();
|
|
g.Style.Colors[backup.Col] = backup.BackupValue;
|
|
g.ColorStack.pop_back();
|
|
count--;
|
|
}
|
|
}
|
|
|
|
struct ImGuiStyleVarInfo
|
|
{
|
|
ImGuiDataType Type;
|
|
ImU32 Count;
|
|
ImU32 Offset;
|
|
void* GetVarPtr(ImGuiStyle* style) const { return (void*)((unsigned char*)style + Offset); }
|
|
};
|
|
|
|
static const ImGuiCol GWindowDockStyleColors[ImGuiWindowDockStyleCol_COUNT] = {
|
|
ImGuiCol_Text, ImGuiCol_Tab, ImGuiCol_TabHovered,
|
|
ImGuiCol_TabActive, ImGuiCol_TabUnfocused, ImGuiCol_TabUnfocusedActive};
|
|
|
|
static const ImGuiStyleVarInfo GStyleVarInfo[] = {
|
|
{ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, Alpha)}, // ImGuiStyleVar_Alpha
|
|
{ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, DisabledAlpha)}, // ImGuiStyleVar_DisabledAlpha
|
|
{ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowPadding)}, // ImGuiStyleVar_WindowPadding
|
|
{ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowRounding)}, // ImGuiStyleVar_WindowRounding
|
|
{ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowBorderSize)}, // ImGuiStyleVar_WindowBorderSize
|
|
{ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowMinSize)}, // ImGuiStyleVar_WindowMinSize
|
|
{ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowTitleAlign)}, // ImGuiStyleVar_WindowTitleAlign
|
|
{ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ChildRounding)}, // ImGuiStyleVar_ChildRounding
|
|
{ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ChildBorderSize)}, // ImGuiStyleVar_ChildBorderSize
|
|
{ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, PopupRounding)}, // ImGuiStyleVar_PopupRounding
|
|
{ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, PopupBorderSize)}, // ImGuiStyleVar_PopupBorderSize
|
|
{ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, FramePadding)}, // ImGuiStyleVar_FramePadding
|
|
{ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, FrameRounding)}, // ImGuiStyleVar_FrameRounding
|
|
{ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, FrameBorderSize)}, // ImGuiStyleVar_FrameBorderSize
|
|
{ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemSpacing)}, // ImGuiStyleVar_ItemSpacing
|
|
{ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemInnerSpacing)}, // ImGuiStyleVar_ItemInnerSpacing
|
|
{ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, IndentSpacing)}, // ImGuiStyleVar_IndentSpacing
|
|
{ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, CellPadding)}, // ImGuiStyleVar_CellPadding
|
|
{ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ScrollbarSize)}, // ImGuiStyleVar_ScrollbarSize
|
|
{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
|
|
{ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, SelectableTextAlign)}, // ImGuiStyleVar_SelectableTextAlign
|
|
};
|
|
|
|
static const ImGuiStyleVarInfo* GetStyleVarInfo(ImGuiStyleVar idx)
|
|
{
|
|
IM_ASSERT(idx >= 0 && idx < ImGuiStyleVar_COUNT);
|
|
IM_ASSERT(IM_ARRAYSIZE(GStyleVarInfo) == ImGuiStyleVar_COUNT);
|
|
return &GStyleVarInfo[idx];
|
|
}
|
|
|
|
void ImGui::PushStyleVar(ImGuiStyleVar idx, float val)
|
|
{
|
|
const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx);
|
|
if (var_info->Type == ImGuiDataType_Float && var_info->Count == 1)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
float* pvar = (float*)var_info->GetVarPtr(&g.Style);
|
|
g.StyleVarStack.push_back(ImGuiStyleMod(idx, *pvar));
|
|
*pvar = val;
|
|
return;
|
|
}
|
|
IM_ASSERT(0 && "Called PushStyleVar() float variant but variable is not a float!");
|
|
}
|
|
|
|
void ImGui::PushStyleVar(ImGuiStyleVar idx, const ImVec2& val)
|
|
{
|
|
const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx);
|
|
if (var_info->Type == ImGuiDataType_Float && var_info->Count == 2)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(&g.Style);
|
|
g.StyleVarStack.push_back(ImGuiStyleMod(idx, *pvar));
|
|
*pvar = val;
|
|
return;
|
|
}
|
|
IM_ASSERT(0 && "Called PushStyleVar() ImVec2 variant but variable is not a ImVec2!");
|
|
}
|
|
|
|
void ImGui::PopStyleVar(int count)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
while (count > 0)
|
|
{
|
|
// We avoid a generic memcpy(data, &backup.Backup.., GDataTypeSize[info->Type] * info->Count), the overhead in
|
|
// Debug is not worth it.
|
|
ImGuiStyleMod& backup = g.StyleVarStack.back();
|
|
const ImGuiStyleVarInfo* info = GetStyleVarInfo(backup.VarIdx);
|
|
void* data = info->GetVarPtr(&g.Style);
|
|
if (info->Type == ImGuiDataType_Float && info->Count == 1)
|
|
{
|
|
((float*)data)[0] = backup.BackupFloat[0];
|
|
}
|
|
else if (info->Type == ImGuiDataType_Float && info->Count == 2)
|
|
{
|
|
((float*)data)[0] = backup.BackupFloat[0];
|
|
((float*)data)[1] = backup.BackupFloat[1];
|
|
}
|
|
g.StyleVarStack.pop_back();
|
|
count--;
|
|
}
|
|
}
|
|
|
|
const char* ImGui::GetStyleColorName(ImGuiCol idx)
|
|
{
|
|
// Create switch-case from enum with regexp: ImGuiCol_{.*}, --> case ImGuiCol_\1: return "\1";
|
|
switch (idx)
|
|
{
|
|
case ImGuiCol_Text:
|
|
return "Text";
|
|
case ImGuiCol_TextDisabled:
|
|
return "TextDisabled";
|
|
case ImGuiCol_WindowBg:
|
|
return "WindowBg";
|
|
case ImGuiCol_ChildBg:
|
|
return "ChildBg";
|
|
case ImGuiCol_PopupBg:
|
|
return "PopupBg";
|
|
case ImGuiCol_Border:
|
|
return "Border";
|
|
case ImGuiCol_BorderShadow:
|
|
return "BorderShadow";
|
|
case ImGuiCol_FrameBg:
|
|
return "FrameBg";
|
|
case ImGuiCol_FrameBgHovered:
|
|
return "FrameBgHovered";
|
|
case ImGuiCol_FrameBgActive:
|
|
return "FrameBgActive";
|
|
case ImGuiCol_TitleBg:
|
|
return "TitleBg";
|
|
case ImGuiCol_TitleBgActive:
|
|
return "TitleBgActive";
|
|
case ImGuiCol_TitleBgCollapsed:
|
|
return "TitleBgCollapsed";
|
|
case ImGuiCol_MenuBarBg:
|
|
return "MenuBarBg";
|
|
case ImGuiCol_ScrollbarBg:
|
|
return "ScrollbarBg";
|
|
case ImGuiCol_ScrollbarGrab:
|
|
return "ScrollbarGrab";
|
|
case ImGuiCol_ScrollbarGrabHovered:
|
|
return "ScrollbarGrabHovered";
|
|
case ImGuiCol_ScrollbarGrabActive:
|
|
return "ScrollbarGrabActive";
|
|
case ImGuiCol_CheckMark:
|
|
return "CheckMark";
|
|
case ImGuiCol_SliderGrab:
|
|
return "SliderGrab";
|
|
case ImGuiCol_SliderGrabActive:
|
|
return "SliderGrabActive";
|
|
case ImGuiCol_Button:
|
|
return "Button";
|
|
case ImGuiCol_ButtonHovered:
|
|
return "ButtonHovered";
|
|
case ImGuiCol_ButtonActive:
|
|
return "ButtonActive";
|
|
case ImGuiCol_Header:
|
|
return "Header";
|
|
case ImGuiCol_HeaderHovered:
|
|
return "HeaderHovered";
|
|
case ImGuiCol_HeaderActive:
|
|
return "HeaderActive";
|
|
case ImGuiCol_Separator:
|
|
return "Separator";
|
|
case ImGuiCol_SeparatorHovered:
|
|
return "SeparatorHovered";
|
|
case ImGuiCol_SeparatorActive:
|
|
return "SeparatorActive";
|
|
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_DockingEmptyBg:
|
|
return "DockingEmptyBg";
|
|
case ImGuiCol_PlotLines:
|
|
return "PlotLines";
|
|
case ImGuiCol_PlotLinesHovered:
|
|
return "PlotLinesHovered";
|
|
case ImGuiCol_PlotHistogram:
|
|
return "PlotHistogram";
|
|
case ImGuiCol_PlotHistogramHovered:
|
|
return "PlotHistogramHovered";
|
|
case ImGuiCol_TableHeaderBg:
|
|
return "TableHeaderBg";
|
|
case ImGuiCol_TableBorderStrong:
|
|
return "TableBorderStrong";
|
|
case ImGuiCol_TableBorderLight:
|
|
return "TableBorderLight";
|
|
case ImGuiCol_TableRowBg:
|
|
return "TableRowBg";
|
|
case ImGuiCol_TableRowBgAlt:
|
|
return "TableRowBgAlt";
|
|
case ImGuiCol_TextSelectedBg:
|
|
return "TextSelectedBg";
|
|
case ImGuiCol_DragDropTarget:
|
|
return "DragDropTarget";
|
|
case ImGuiCol_NavHighlight:
|
|
return "NavHighlight";
|
|
case ImGuiCol_NavWindowingHighlight:
|
|
return "NavWindowingHighlight";
|
|
case ImGuiCol_NavWindowingDimBg:
|
|
return "NavWindowingDimBg";
|
|
case ImGuiCol_ModalWindowDimBg:
|
|
return "ModalWindowDimBg";
|
|
}
|
|
IM_ASSERT(0);
|
|
return "Unknown";
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// [SECTION] RENDER HELPERS
|
|
// Some of those (internal) functions are currently quite a legacy mess - their signature and behavior will change,
|
|
// we need a nicer separation between low-level functions and high-level functions relying on the ImGui context.
|
|
// Also see imgui_draw.cpp for some more which have been reworked to not rely on ImGui:: context.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
const char* ImGui::FindRenderedTextEnd(const char* text, const char* text_end)
|
|
{
|
|
const char* text_display_end = text;
|
|
if (!text_end)
|
|
text_end = (const char*)-1;
|
|
|
|
while (text_display_end < text_end && *text_display_end != '\0' &&
|
|
(text_display_end[0] != '#' || text_display_end[1] != '#'))
|
|
text_display_end++;
|
|
return text_display_end;
|
|
}
|
|
|
|
// Internal ImGui functions to render text
|
|
// RenderText***() functions calls ImDrawList::AddText() calls ImBitmapFont::RenderText()
|
|
void ImGui::RenderText(ImVec2 pos, const char* text, const char* text_end, bool hide_text_after_hash)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
|
|
// Hide anything after a '##' string
|
|
const char* text_display_end;
|
|
if (hide_text_after_hash)
|
|
{
|
|
text_display_end = FindRenderedTextEnd(text, text_end);
|
|
}
|
|
else
|
|
{
|
|
if (!text_end)
|
|
text_end = text + strlen(text); // FIXME-OPT
|
|
text_display_end = text_end;
|
|
}
|
|
|
|
if (text != text_display_end)
|
|
{
|
|
window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end);
|
|
if (g.LogEnabled)
|
|
LogRenderedText(&pos, text, text_display_end);
|
|
}
|
|
}
|
|
|
|
void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
|
|
if (!text_end)
|
|
text_end = text + strlen(text); // FIXME-OPT
|
|
|
|
if (text != text_end)
|
|
{
|
|
window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_end, wrap_width);
|
|
if (g.LogEnabled)
|
|
LogRenderedText(&pos, text, 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::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)
|
|
{
|
|
// 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);
|
|
|
|
const ImVec2* clip_min = clip_rect ? &clip_rect->Min : &pos_min;
|
|
const ImVec2* clip_max = clip_rect ? &clip_rect->Max : &pos_max;
|
|
bool need_clipping = (pos.x + text_size.x >= clip_max->x) || (pos.y + text_size.y >= clip_max->y);
|
|
if (clip_rect) // If we had no explicit clipping rectangle then pos==clip_min
|
|
need_clipping |= (pos.x < clip_min->x) || (pos.y < clip_min->y);
|
|
|
|
// Align whole block. We should defer that to the better rendering function when we'll have support for individual
|
|
// line alignment.
|
|
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);
|
|
|
|
// Render
|
|
if (need_clipping)
|
|
{
|
|
ImVec4 fine_clip_rect(clip_min->x, clip_min->y, clip_max->x, clip_max->y);
|
|
draw_list->AddText(NULL, 0.0f, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, &fine_clip_rect);
|
|
}
|
|
else
|
|
{
|
|
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_min, text, text_display_end);
|
|
}
|
|
|
|
// Another overly complex function until we reorganize everything into a nice all-in-one helper.
|
|
// This is made more complex because we have dissociated the layout rectangle (pos_min..pos_max) which define _where_
|
|
// the ellipsis is, from actual clipping of text and limit of the ellipsis display. This is because in the context of
|
|
// tabs we selectively hide part of the text when the Close Button appears, but we don't want the ellipsis to move.
|
|
void ImGui::RenderTextEllipsis(ImDrawList* draw_list,
|
|
const ImVec2& pos_min,
|
|
const ImVec2& pos_max,
|
|
float clip_max_x,
|
|
float ellipsis_max_x,
|
|
const char* text,
|
|
const char* text_end_full,
|
|
const ImVec2* text_size_if_known)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (text_end_full == NULL)
|
|
text_end_full = FindRenderedTextEnd(text);
|
|
const ImVec2 text_size = text_size_if_known ? *text_size_if_known : CalcTextSize(text, text_end_full, false, 0.0f);
|
|
|
|
// draw_list->AddLine(ImVec2(pos_max.x, pos_min.y - 4), ImVec2(pos_max.x, pos_max.y + 4), IM_COL32(0, 0, 255, 255));
|
|
// draw_list->AddLine(ImVec2(ellipsis_max_x, pos_min.y-2), ImVec2(ellipsis_max_x, pos_max.y+2), IM_COL32(0, 255, 0,
|
|
// 255)); draw_list->AddLine(ImVec2(clip_max_x, pos_min.y), ImVec2(clip_max_x, pos_max.y), IM_COL32(255, 0, 0,
|
|
// 255));
|
|
// FIXME: We could technically remove (last_glyph->AdvanceX - last_glyph->X1) from text_size.x here and save a few
|
|
// pixels.
|
|
if (text_size.x > pos_max.x - pos_min.x)
|
|
{
|
|
// Hello wo...
|
|
// | | |
|
|
// min max ellipsis_max
|
|
// <-> this is generally some padding value
|
|
|
|
const ImFont* font = draw_list->_Data->Font;
|
|
const float font_size = draw_list->_Data->FontSize;
|
|
const char* text_end_ellipsis = NULL;
|
|
|
|
ImWchar ellipsis_char = font->EllipsisChar;
|
|
int ellipsis_char_count = 1;
|
|
if (ellipsis_char == (ImWchar)-1)
|
|
{
|
|
ellipsis_char = font->DotChar;
|
|
ellipsis_char_count = 3;
|
|
}
|
|
const ImFontGlyph* glyph = font->FindGlyph(ellipsis_char);
|
|
|
|
float ellipsis_glyph_width = glyph->X1; // Width of the glyph with no padding on either side
|
|
float ellipsis_total_width = ellipsis_glyph_width; // Full width of entire ellipsis
|
|
|
|
if (ellipsis_char_count > 1)
|
|
{
|
|
// Full ellipsis size without free spacing after it.
|
|
const float spacing_between_dots = 1.0f * (draw_list->_Data->FontSize / font->FontSize);
|
|
ellipsis_glyph_width = glyph->X1 - glyph->X0 + spacing_between_dots;
|
|
ellipsis_total_width = ellipsis_glyph_width * (float)ellipsis_char_count - spacing_between_dots;
|
|
}
|
|
|
|
// We can now claim the space between pos_max.x and ellipsis_max.x
|
|
const float text_avail_width =
|
|
ImMax((ImMax(pos_max.x, ellipsis_max_x) - ellipsis_total_width) - pos_min.x, 1.0f);
|
|
float text_size_clipped_x =
|
|
font->CalcTextSizeA(font_size, text_avail_width, 0.0f, text, text_end_full, &text_end_ellipsis).x;
|
|
if (text == text_end_ellipsis && text_end_ellipsis < text_end_full)
|
|
{
|
|
// Always display at least 1 character if there's no room for character + ellipsis
|
|
text_end_ellipsis = text + ImTextCountUtf8BytesFromChar(text, text_end_full);
|
|
text_size_clipped_x = font->CalcTextSizeA(font_size, FLT_MAX, 0.0f, text, text_end_ellipsis).x;
|
|
}
|
|
while (text_end_ellipsis > text && ImCharIsBlankA(text_end_ellipsis[-1]))
|
|
{
|
|
// Trim trailing space before ellipsis (FIXME: Supporting non-ascii blanks would be nice, for this we need a
|
|
// function to backtrack in UTF-8 text)
|
|
text_end_ellipsis--;
|
|
text_size_clipped_x -=
|
|
font->CalcTextSizeA(font_size, FLT_MAX, 0.0f, text_end_ellipsis, text_end_ellipsis + 1)
|
|
.x; // Ascii blanks are always 1 byte
|
|
}
|
|
|
|
// Render text, render ellipsis
|
|
RenderTextClippedEx(draw_list, pos_min, ImVec2(clip_max_x, pos_max.y), text, text_end_ellipsis, &text_size,
|
|
ImVec2(0.0f, 0.0f));
|
|
float ellipsis_x = pos_min.x + text_size_clipped_x;
|
|
if (ellipsis_x + ellipsis_total_width <= ellipsis_max_x)
|
|
for (int i = 0; i < ellipsis_char_count; i++)
|
|
{
|
|
font->RenderChar(draw_list, font_size, ImVec2(ellipsis_x, pos_min.y), GetColorU32(ImGuiCol_Text),
|
|
ellipsis_char);
|
|
ellipsis_x += ellipsis_glyph_width;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RenderTextClippedEx(draw_list, pos_min, ImVec2(clip_max_x, pos_max.y), text, text_end_full, &text_size,
|
|
ImVec2(0.0f, 0.0f));
|
|
}
|
|
|
|
if (g.LogEnabled)
|
|
LogRenderedText(&pos_min, text, text_end_full);
|
|
}
|
|
|
|
// Render a rectangle shaped with optional rounding and borders
|
|
void ImGui::RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border, float rounding)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
window->DrawList->AddRectFilled(p_min, p_max, fill_col, rounding);
|
|
const float border_size = g.Style.FrameBorderSize;
|
|
if (border && border_size > 0.0f)
|
|
{
|
|
window->DrawList->AddRect(p_min + ImVec2(1, 1), p_max + ImVec2(1, 1), GetColorU32(ImGuiCol_BorderShadow),
|
|
rounding, 0, border_size);
|
|
window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, 0, border_size);
|
|
}
|
|
}
|
|
|
|
void ImGui::RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
const float border_size = g.Style.FrameBorderSize;
|
|
if (border_size > 0.0f)
|
|
{
|
|
window->DrawList->AddRect(p_min + ImVec2(1, 1), p_max + ImVec2(1, 1), GetColorU32(ImGuiCol_BorderShadow),
|
|
rounding, 0, border_size);
|
|
window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, 0, border_size);
|
|
}
|
|
}
|
|
|
|
void ImGui::RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFlags flags)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (id != g.NavId)
|
|
return;
|
|
if (g.NavDisableHighlight && !(flags & ImGuiNavHighlightFlags_AlwaysDraw))
|
|
return;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
if (window->DC.NavHideHighlightOneFrame)
|
|
return;
|
|
|
|
float rounding = (flags & ImGuiNavHighlightFlags_NoRounding) ? 0.0f : g.Style.FrameRounding;
|
|
ImRect display_rect = bb;
|
|
display_rect.ClipWith(window->ClipRect);
|
|
if (flags & ImGuiNavHighlightFlags_TypeDefault)
|
|
{
|
|
const float THICKNESS = 2.0f;
|
|
const float DISTANCE = 3.0f + THICKNESS * 0.5f;
|
|
display_rect.Expand(ImVec2(DISTANCE, DISTANCE));
|
|
bool fully_visible = window->ClipRect.Contains(display_rect);
|
|
if (!fully_visible)
|
|
window->DrawList->PushClipRect(display_rect.Min, display_rect.Max);
|
|
window->DrawList->AddRect(display_rect.Min + ImVec2(THICKNESS * 0.5f, THICKNESS * 0.5f),
|
|
display_rect.Max - ImVec2(THICKNESS * 0.5f, THICKNESS * 0.5f),
|
|
GetColorU32(ImGuiCol_NavHighlight), rounding, 0, THICKNESS);
|
|
if (!fully_visible)
|
|
window->DrawList->PopClipRect();
|
|
}
|
|
if (flags & ImGuiNavHighlightFlags_TypeThin)
|
|
{
|
|
window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavHighlight), rounding, 0,
|
|
1.0f);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!)
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// ImGuiWindow is mostly a dumb struct. It merely has a constructor and a few helper methods
|
|
ImGuiWindow::ImGuiWindow(ImGuiContext* context, const char* name) : DrawListInst(NULL)
|
|
{
|
|
memset(this, 0, sizeof(*this));
|
|
Name = ImStrdup(name);
|
|
NameBufLen = (int)strlen(name) + 1;
|
|
ID = ImHashStr(name);
|
|
IDStack.push_back(ID);
|
|
ViewportAllowPlatformMonitorExtend = -1;
|
|
ViewportPos = ImVec2(FLT_MAX, FLT_MAX);
|
|
MoveId = GetID("#MOVE");
|
|
ScrollTarget = ImVec2(FLT_MAX, FLT_MAX);
|
|
ScrollTargetCenterRatio = ImVec2(0.5f, 0.5f);
|
|
AutoFitFramesX = AutoFitFramesY = -1;
|
|
AutoPosLastDirection = ImGuiDir_None;
|
|
SetWindowPosAllowFlags = SetWindowSizeAllowFlags = SetWindowCollapsedAllowFlags = SetWindowDockAllowFlags =
|
|
ImGuiCond_Always | ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing;
|
|
SetWindowPosVal = SetWindowPosPivot = ImVec2(FLT_MAX, FLT_MAX);
|
|
LastFrameActive = -1;
|
|
LastFrameJustFocused = -1;
|
|
LastTimeActive = -1.0f;
|
|
FontWindowScale = FontDpiScale = 1.0f;
|
|
SettingsOffset = -1;
|
|
DockOrder = -1;
|
|
DrawList = &DrawListInst;
|
|
DrawList->_Data = &context->DrawListSharedData;
|
|
DrawList->_OwnerName = Name;
|
|
}
|
|
|
|
ImGuiWindow::~ImGuiWindow()
|
|
{
|
|
IM_ASSERT(DrawList == &DrawListInst);
|
|
IM_DELETE(Name);
|
|
ColumnsStorage.clear_destruct();
|
|
}
|
|
|
|
ImGuiID ImGuiWindow::GetID(const char* str, const char* str_end)
|
|
{
|
|
ImGuiID seed = IDStack.back();
|
|
ImGuiID id = ImHashStr(str, str_end ? (str_end - str) : 0, seed);
|
|
ImGui::KeepAliveID(id);
|
|
# ifdef IMGUI_ENABLE_TEST_ENGINE
|
|
ImGuiContext& g = *GImGui;
|
|
IMGUI_TEST_ENGINE_ID_INFO2(id, ImGuiDataType_String, str, str_end);
|
|
# endif
|
|
return id;
|
|
}
|
|
|
|
ImGuiID ImGuiWindow::GetID(const void* ptr)
|
|
{
|
|
ImGuiID seed = IDStack.back();
|
|
ImGuiID id = ImHashData(&ptr, sizeof(void*), seed);
|
|
ImGui::KeepAliveID(id);
|
|
# ifdef IMGUI_ENABLE_TEST_ENGINE
|
|
ImGuiContext& g = *GImGui;
|
|
IMGUI_TEST_ENGINE_ID_INFO(id, ImGuiDataType_Pointer, ptr);
|
|
# endif
|
|
return id;
|
|
}
|
|
|
|
ImGuiID ImGuiWindow::GetID(int n)
|
|
{
|
|
ImGuiID seed = IDStack.back();
|
|
ImGuiID id = ImHashData(&n, sizeof(n), seed);
|
|
ImGui::KeepAliveID(id);
|
|
# ifdef IMGUI_ENABLE_TEST_ENGINE
|
|
ImGuiContext& g = *GImGui;
|
|
IMGUI_TEST_ENGINE_ID_INFO(id, ImGuiDataType_S32, (intptr_t)n);
|
|
# endif
|
|
return id;
|
|
}
|
|
|
|
ImGuiID ImGuiWindow::GetIDNoKeepAlive(const char* str, const char* str_end)
|
|
{
|
|
ImGuiID seed = IDStack.back();
|
|
ImGuiID id = ImHashStr(str, str_end ? (str_end - str) : 0, seed);
|
|
# ifdef IMGUI_ENABLE_TEST_ENGINE
|
|
ImGuiContext& g = *GImGui;
|
|
IMGUI_TEST_ENGINE_ID_INFO2(id, ImGuiDataType_String, str, str_end);
|
|
# endif
|
|
return id;
|
|
}
|
|
|
|
ImGuiID ImGuiWindow::GetIDNoKeepAlive(const void* ptr)
|
|
{
|
|
ImGuiID seed = IDStack.back();
|
|
ImGuiID id = ImHashData(&ptr, sizeof(void*), seed);
|
|
# ifdef IMGUI_ENABLE_TEST_ENGINE
|
|
ImGuiContext& g = *GImGui;
|
|
IMGUI_TEST_ENGINE_ID_INFO(id, ImGuiDataType_Pointer, ptr);
|
|
# endif
|
|
return id;
|
|
}
|
|
|
|
ImGuiID ImGuiWindow::GetIDNoKeepAlive(int n)
|
|
{
|
|
ImGuiID seed = IDStack.back();
|
|
ImGuiID id = ImHashData(&n, sizeof(n), seed);
|
|
# ifdef IMGUI_ENABLE_TEST_ENGINE
|
|
ImGuiContext& g = *GImGui;
|
|
IMGUI_TEST_ENGINE_ID_INFO(id, ImGuiDataType_S32, (intptr_t)n);
|
|
# endif
|
|
return id;
|
|
}
|
|
|
|
// This is only used in rare/specific situations to manufacture an ID out of nowhere.
|
|
ImGuiID ImGuiWindow::GetIDFromRectangle(const ImRect& r_abs)
|
|
{
|
|
ImGuiID seed = IDStack.back();
|
|
const int r_rel[4] = {(int)(r_abs.Min.x - Pos.x), (int)(r_abs.Min.y - Pos.y), (int)(r_abs.Max.x - Pos.x),
|
|
(int)(r_abs.Max.y - Pos.y)};
|
|
ImGuiID id = ImHashData(&r_rel, sizeof(r_rel), seed);
|
|
ImGui::KeepAliveID(id);
|
|
return id;
|
|
}
|
|
|
|
static void SetCurrentWindow(ImGuiWindow* window)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
g.CurrentWindow = window;
|
|
g.CurrentTable =
|
|
window && window->DC.CurrentTableIdx != -1 ? g.Tables.GetByIndex(window->DC.CurrentTableIdx) : NULL;
|
|
if (window)
|
|
g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize();
|
|
}
|
|
|
|
void ImGui::GcCompactTransientMiscBuffers()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
g.ItemFlagsStack.clear();
|
|
g.GroupStack.clear();
|
|
TableGcCompactSettings();
|
|
}
|
|
|
|
// Free up/compact internal window buffers, we can use this when a window becomes unused.
|
|
// Not freed:
|
|
// - ImGuiWindow, ImGuiWindowSettings, Name, StateStorage, ColumnsStorage (may hold useful data)
|
|
// This should have no noticeable visual effect. When the window reappear however, expect new allocation/buffer
|
|
// growth/copy cost.
|
|
void ImGui::GcCompactTransientWindowBuffers(ImGuiWindow* window)
|
|
{
|
|
window->MemoryCompacted = true;
|
|
window->MemoryDrawListIdxCapacity = window->DrawList->IdxBuffer.Capacity;
|
|
window->MemoryDrawListVtxCapacity = window->DrawList->VtxBuffer.Capacity;
|
|
window->IDStack.clear();
|
|
window->DrawList->_ClearFreeMemory();
|
|
window->DC.ChildWindows.clear();
|
|
window->DC.ItemWidthStack.clear();
|
|
window->DC.TextWrapPosStack.clear();
|
|
}
|
|
|
|
void ImGui::GcAwakeTransientWindowBuffers(ImGuiWindow* window)
|
|
{
|
|
// We stored capacity of the ImDrawList buffer to reduce growth-caused allocation/copy when awakening.
|
|
// The other buffers tends to amortize much faster.
|
|
window->MemoryCompacted = false;
|
|
window->DrawList->IdxBuffer.reserve(window->MemoryDrawListIdxCapacity);
|
|
window->DrawList->VtxBuffer.reserve(window->MemoryDrawListVtxCapacity);
|
|
window->MemoryDrawListIdxCapacity = window->MemoryDrawListVtxCapacity = 0;
|
|
}
|
|
|
|
void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
g.ActiveIdIsJustActivated = (g.ActiveId != id);
|
|
if (g.ActiveIdIsJustActivated)
|
|
{
|
|
g.ActiveIdTimer = 0.0f;
|
|
g.ActiveIdHasBeenPressedBefore = false;
|
|
g.ActiveIdHasBeenEditedBefore = false;
|
|
g.ActiveIdMouseButton = -1;
|
|
if (id != 0)
|
|
{
|
|
g.LastActiveId = id;
|
|
g.LastActiveIdTimer = 0.0f;
|
|
}
|
|
}
|
|
g.ActiveId = id;
|
|
g.ActiveIdAllowOverlap = false;
|
|
g.ActiveIdNoClearOnFocusLoss = false;
|
|
g.ActiveIdWindow = window;
|
|
g.ActiveIdHasBeenEditedThisFrame = false;
|
|
if (id)
|
|
{
|
|
g.ActiveIdIsAlive = id;
|
|
g.ActiveIdSource =
|
|
(g.NavActivateId == id || g.NavInputId == id || g.NavJustTabbedId == id || g.NavJustMovedToId == id)
|
|
? ImGuiInputSource_Nav
|
|
: ImGuiInputSource_Mouse;
|
|
}
|
|
|
|
// Clear declaration of inputs claimed by the widget
|
|
// (Please note that this is WIP and not all keys/inputs are thoroughly declared by all widgets yet)
|
|
g.ActiveIdUsingMouseWheel = false;
|
|
g.ActiveIdUsingNavDirMask = 0x00;
|
|
g.ActiveIdUsingNavInputMask = 0x00;
|
|
g.ActiveIdUsingKeyInputMask = 0x00;
|
|
}
|
|
|
|
void ImGui::ClearActiveID()
|
|
{
|
|
SetActiveID(0, NULL); // g.ActiveId = 0;
|
|
}
|
|
|
|
void ImGui::SetHoveredID(ImGuiID id)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
g.HoveredId = id;
|
|
g.HoveredIdAllowOverlap = false;
|
|
g.HoveredIdUsingMouseWheel = false;
|
|
if (id != 0 && g.HoveredIdPreviousFrame != id)
|
|
g.HoveredIdTimer = g.HoveredIdNotActiveTimer = 0.0f;
|
|
}
|
|
|
|
ImGuiID ImGui::GetHoveredID()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
return g.HoveredId ? g.HoveredId : g.HoveredIdPreviousFrame;
|
|
}
|
|
|
|
void ImGui::KeepAliveID(ImGuiID id)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (g.ActiveId == id)
|
|
g.ActiveIdIsAlive = id;
|
|
if (g.ActiveIdPreviousFrame == id)
|
|
g.ActiveIdPreviousFrameIsAlive = true;
|
|
}
|
|
|
|
void ImGui::MarkItemEdited(ImGuiID id)
|
|
{
|
|
// This marking is solely to be able to provide info for IsItemDeactivatedAfterEdit().
|
|
// ActiveId might have been released by the time we call this (as in the typical press/release button behavior) but
|
|
// still need need to fill the data.
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(g.ActiveId == id || g.ActiveId == 0 || g.DragDropActive);
|
|
IM_UNUSED(id); // Avoid unused variable warnings when asserts are compiled out.
|
|
// IM_ASSERT(g.CurrentWindow->DC.LastItemId == id);
|
|
g.ActiveIdHasBeenEditedThisFrame = true;
|
|
g.ActiveIdHasBeenEditedBefore = true;
|
|
g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Edited;
|
|
}
|
|
|
|
static inline bool IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flags)
|
|
{
|
|
// An active popup disable hovering on other windows (apart from its own children)
|
|
// FIXME-OPT: This could be cached/stored within the window.
|
|
ImGuiContext& g = *GImGui;
|
|
if (g.NavWindow)
|
|
if (ImGuiWindow* focused_root_window = g.NavWindow->RootWindowDockTree)
|
|
if (focused_root_window->WasActive && focused_root_window != window->RootWindowDockTree)
|
|
{
|
|
// For the purpose of those flags we differentiate "standard popup" from "modal popup"
|
|
// NB: The order of those two tests is important because Modal windows are also Popups.
|
|
if (focused_root_window->Flags & ImGuiWindowFlags_Modal)
|
|
return false;
|
|
if ((focused_root_window->Flags & ImGuiWindowFlags_Popup) &&
|
|
!(flags & ImGuiHoveredFlags_AllowWhenBlockedByPopup))
|
|
return false;
|
|
}
|
|
|
|
// Filter by viewport
|
|
if (window->Viewport != g.MouseViewport)
|
|
if (g.MovingWindow == NULL || window->RootWindowDockTree != g.MovingWindow->RootWindowDockTree)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
// This is roughly matching the behavior of internal-facing ItemHoverable()
|
|
// - we allow hovering to be true when ActiveId==window->MoveID, so that clicking on non-interactive items such as a
|
|
// Text() item still returns true with IsItemHovered()
|
|
// - this should work even for non-interactive items that have no ID, so we cannot use LastItemId
|
|
bool ImGui::IsItemHovered(ImGuiHoveredFlags flags)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
if (g.NavDisableMouseHover && !g.NavDisableHighlight)
|
|
{
|
|
if ((g.LastItemData.InFlags & ImGuiItemFlags_Disabled) && !(flags & ImGuiHoveredFlags_AllowWhenDisabled))
|
|
return false;
|
|
return IsItemFocused();
|
|
}
|
|
|
|
// Test for bounding box overlap, as updated as ItemAdd()
|
|
ImGuiItemStatusFlags status_flags = g.LastItemData.StatusFlags;
|
|
if (!(status_flags & ImGuiItemStatusFlags_HoveredRect))
|
|
return false;
|
|
IM_ASSERT((flags & (ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows)) ==
|
|
0); // Flags not supported by this function
|
|
|
|
// Test if we are hovering the right window (our window could be behind another window)
|
|
// [2021/03/02] Reworked / reverted the revert, finally. Note we want e.g. BeginGroup/ItemAdd/EndGroup to work as
|
|
// well. (#3851) [2017/10/16] Reverted commit 344d48be3 and testing RootWindow instead. I believe it is correct to
|
|
// NOT test for RootWindow but this leaves us unable to use IsItemHovered() after EndChild() itself. Until a
|
|
// solution is found I believe reverting to the test from 2017/09/27 is safe since this was the test that has been
|
|
// running for a long while.
|
|
if (g.HoveredWindow != window && (status_flags & ImGuiItemStatusFlags_HoveredWindow) == 0)
|
|
if ((flags & ImGuiHoveredFlags_AllowWhenOverlapped) == 0)
|
|
return false;
|
|
|
|
// Test if another item is active (e.g. being dragged)
|
|
if ((flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem) == 0)
|
|
if (g.ActiveId != 0 && g.ActiveId != g.LastItemData.ID && !g.ActiveIdAllowOverlap &&
|
|
g.ActiveId != window->MoveId)
|
|
return false;
|
|
|
|
// Test if interactions on this window are blocked by an active popup or modal.
|
|
// The ImGuiHoveredFlags_AllowWhenBlockedByPopup flag will be tested here.
|
|
if (!IsWindowContentHoverable(window, flags))
|
|
return false;
|
|
|
|
// Test if the item is disabled
|
|
if ((g.LastItemData.InFlags & ImGuiItemFlags_Disabled) && !(flags & ImGuiHoveredFlags_AllowWhenDisabled))
|
|
return false;
|
|
|
|
// Special handling for calling 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 ((g.LastItemData.ID == window->ID || g.LastItemData.ID == window->MoveId) && window->WriteAccessed)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
// Internal facing ItemHoverable() used when submitting widgets. Differs slightly from IsItemHovered().
|
|
bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (g.HoveredId != 0 && g.HoveredId != id && !g.HoveredIdAllowOverlap)
|
|
return false;
|
|
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
if (g.HoveredWindow != window)
|
|
return false;
|
|
if (g.ActiveId != 0 && g.ActiveId != id && !g.ActiveIdAllowOverlap)
|
|
return false;
|
|
if (!IsMouseHoveringRect(bb.Min, bb.Max))
|
|
return false;
|
|
if (g.NavDisableMouseHover)
|
|
return false;
|
|
if (!IsWindowContentHoverable(window, ImGuiHoveredFlags_None))
|
|
{
|
|
g.HoveredIdDisabled = true;
|
|
return false;
|
|
}
|
|
|
|
// We exceptionally allow this function to be called with id==0 to allow using it for easy high-level
|
|
// hover test in widgets code. We could also decide to split this function is two.
|
|
if (id != 0)
|
|
SetHoveredID(id);
|
|
|
|
// When disabled we'll return false but still set HoveredId
|
|
ImGuiItemFlags item_flags = (g.LastItemData.ID == id ? g.LastItemData.InFlags : g.CurrentItemFlags);
|
|
if (item_flags & ImGuiItemFlags_Disabled)
|
|
{
|
|
// Release active id if turning disabled
|
|
if (g.ActiveId == id)
|
|
ClearActiveID();
|
|
g.HoveredIdDisabled = true;
|
|
return false;
|
|
}
|
|
|
|
if (id != 0)
|
|
{
|
|
// [DEBUG] Item Picker tool!
|
|
// We perform the check here because SetHoveredID() is not frequently called (1~ time a frame), making
|
|
// the cost of this tool near-zero. We can get slightly better call-stack and support picking non-hovered
|
|
// items if we perform the test in ItemAdd(), but that would incur a small runtime cost.
|
|
// #define IMGUI_DEBUG_TOOL_ITEM_PICKER_EX in imconfig.h if you want this check to also be performed in
|
|
// ItemAdd().
|
|
if (g.DebugItemPickerActive && g.HoveredIdPreviousFrame == id)
|
|
GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(255, 255, 0, 255));
|
|
if (g.DebugItemPickerBreakId == id)
|
|
IM_DEBUG_BREAK();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ImGui::IsClippedEx(const ImRect& bb, ImGuiID id, bool clip_even_when_logged)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
if (!bb.Overlaps(window->ClipRect))
|
|
if (id == 0 || (id != g.ActiveId && id != g.NavId))
|
|
if (clip_even_when_logged || !g.LogEnabled)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
// This is also inlined in ItemAdd()
|
|
// Note: if ImGuiItemStatusFlags_HasDisplayRect is set, user needs to set window->DC.LastItemDisplayRect!
|
|
void ImGui::SetLastItemData(ImGuiID item_id,
|
|
ImGuiItemFlags in_flags,
|
|
ImGuiItemStatusFlags item_flags,
|
|
const ImRect& item_rect)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
g.LastItemData.ID = item_id;
|
|
g.LastItemData.InFlags = in_flags;
|
|
g.LastItemData.StatusFlags = item_flags;
|
|
g.LastItemData.Rect = item_rect;
|
|
}
|
|
|
|
// Called by ItemAdd()
|
|
// Process TAB/Shift+TAB. Be mindful that this function may _clear_ the ActiveID when tabbing out.
|
|
void ImGui::ItemFocusable(ImGuiWindow* window, ImGuiID id)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(id != 0 && id == g.LastItemData.ID);
|
|
|
|
// Increment counters
|
|
// FIXME: ImGuiItemFlags_Disabled should disable more.
|
|
const bool is_tab_stop = (g.LastItemData.InFlags & (ImGuiItemFlags_NoTabStop | ImGuiItemFlags_Disabled)) == 0;
|
|
window->DC.FocusCounterRegular++;
|
|
if (is_tab_stop)
|
|
{
|
|
window->DC.FocusCounterTabStop++;
|
|
if (g.NavId == id)
|
|
g.NavIdTabCounter = window->DC.FocusCounterTabStop;
|
|
}
|
|
|
|
// Process TAB/Shift-TAB to tab *OUT* of the currently focused item.
|
|
// (Note that we can always TAB out of a widget that doesn't allow tabbing in)
|
|
if (g.ActiveId == id && g.TabFocusPressed && !IsActiveIdUsingKey(ImGuiKey_Tab) &&
|
|
g.TabFocusRequestNextWindow == NULL)
|
|
{
|
|
g.TabFocusRequestNextWindow = window;
|
|
g.TabFocusRequestNextCounterTabStop =
|
|
window->DC.FocusCounterTabStop +
|
|
(g.IO.KeyShift ? (is_tab_stop ? -1 : 0) : +1); // Modulo on index will be applied at the end of frame once
|
|
// we've got the total counter of items.
|
|
}
|
|
|
|
// Handle focus requests
|
|
if (g.TabFocusRequestCurrWindow == window)
|
|
{
|
|
if (window->DC.FocusCounterRegular == g.TabFocusRequestCurrCounterRegular)
|
|
{
|
|
g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_FocusedByCode;
|
|
return;
|
|
}
|
|
if (is_tab_stop && window->DC.FocusCounterTabStop == g.TabFocusRequestCurrCounterTabStop)
|
|
{
|
|
g.NavJustTabbedId = id;
|
|
g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_FocusedByTabbing;
|
|
return;
|
|
}
|
|
|
|
// If another item is about to be focused, we clear our own active id
|
|
if (g.ActiveId == id)
|
|
ClearActiveID();
|
|
}
|
|
}
|
|
|
|
float ImGui::CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x)
|
|
{
|
|
if (wrap_pos_x < 0.0f)
|
|
return 0.0f;
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
if (wrap_pos_x == 0.0f)
|
|
{
|
|
// We could decide to setup a default wrapping max point for auto-resizing windows,
|
|
// or have auto-wrap (with unspecified wrapping pos) behave as a ContentSize extending function?
|
|
// if (window->Hidden && (window->Flags & ImGuiWindowFlags_AlwaysAutoResize))
|
|
// wrap_pos_x = ImMax(window->WorkRect.Min.x + g.FontSize * 10.0f, window->WorkRect.Max.x);
|
|
// else
|
|
wrap_pos_x = window->WorkRect.Max.x;
|
|
}
|
|
else if (wrap_pos_x > 0.0f)
|
|
{
|
|
wrap_pos_x += window->Pos.x - window->Scroll.x; // wrap_pos_x is provided is window local space
|
|
}
|
|
|
|
return ImMax(wrap_pos_x - pos.x, 1.0f);
|
|
}
|
|
|
|
// IM_ALLOC() == ImGui::MemAlloc()
|
|
void* ImGui::MemAlloc(size_t size)
|
|
{
|
|
if (ImGuiContext* ctx = GImGui)
|
|
ctx->IO.MetricsActiveAllocations++;
|
|
return (*GImAllocatorAllocFunc)(size, GImAllocatorUserData);
|
|
}
|
|
|
|
// IM_FREE() == ImGui::MemFree()
|
|
void ImGui::MemFree(void* ptr)
|
|
{
|
|
if (ptr)
|
|
if (ImGuiContext* ctx = GImGui)
|
|
ctx->IO.MetricsActiveAllocations--;
|
|
return (*GImAllocatorFreeFunc)(ptr, GImAllocatorUserData);
|
|
}
|
|
|
|
const char* ImGui::GetClipboardText()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
return g.IO.GetClipboardTextFn ? g.IO.GetClipboardTextFn(g.IO.ClipboardUserData) : "";
|
|
}
|
|
|
|
void ImGui::SetClipboardText(const char* text)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (g.IO.SetClipboardTextFn)
|
|
g.IO.SetClipboardTextFn(g.IO.ClipboardUserData, text);
|
|
}
|
|
|
|
const char* ImGui::GetVersion()
|
|
{
|
|
return IMGUI_VERSION;
|
|
}
|
|
|
|
// Internal state access - if you want to share Dear ImGui state between modules (e.g. DLL) or allocate it yourself
|
|
// Note that we still point to some static data and members (such as GFontAtlas), so the state instance you end up using
|
|
// will point to the static data within its module
|
|
ImGuiContext* ImGui::GetCurrentContext()
|
|
{
|
|
return GImGui;
|
|
}
|
|
|
|
void ImGui::SetCurrentContext(ImGuiContext* ctx)
|
|
{
|
|
# ifdef IMGUI_SET_CURRENT_CONTEXT_FUNC
|
|
IMGUI_SET_CURRENT_CONTEXT_FUNC(ctx); // For custom thread-based hackery you may want to have control over this.
|
|
# else
|
|
GImGui = ctx;
|
|
# endif
|
|
}
|
|
|
|
void ImGui::SetAllocatorFunctions(ImGuiMemAllocFunc alloc_func, ImGuiMemFreeFunc free_func, void* user_data)
|
|
{
|
|
GImAllocatorAllocFunc = alloc_func;
|
|
GImAllocatorFreeFunc = free_func;
|
|
GImAllocatorUserData = user_data;
|
|
}
|
|
|
|
// This is provided to facilitate copying allocators from one static/DLL boundary to another (e.g. retrieve default
|
|
// allocator of your executable address space)
|
|
void ImGui::GetAllocatorFunctions(ImGuiMemAllocFunc* p_alloc_func, ImGuiMemFreeFunc* p_free_func, void** p_user_data)
|
|
{
|
|
*p_alloc_func = GImAllocatorAllocFunc;
|
|
*p_free_func = GImAllocatorFreeFunc;
|
|
*p_user_data = GImAllocatorUserData;
|
|
}
|
|
|
|
ImGuiContext* ImGui::CreateContext(ImFontAtlas* shared_font_atlas)
|
|
{
|
|
ImGuiContext* ctx = IM_NEW(ImGuiContext)(shared_font_atlas);
|
|
if (GImGui == NULL)
|
|
SetCurrentContext(ctx);
|
|
Initialize(ctx);
|
|
return ctx;
|
|
}
|
|
|
|
void ImGui::DestroyContext(ImGuiContext* ctx)
|
|
{
|
|
if (ctx == NULL)
|
|
ctx = GImGui;
|
|
Shutdown(ctx);
|
|
if (GImGui == ctx)
|
|
SetCurrentContext(NULL);
|
|
IM_DELETE(ctx);
|
|
}
|
|
|
|
// No specific ordering/dependency support, will see as needed
|
|
ImGuiID ImGui::AddContextHook(ImGuiContext* ctx, const ImGuiContextHook* hook)
|
|
{
|
|
ImGuiContext& g = *ctx;
|
|
IM_ASSERT(hook->Callback != NULL && hook->HookId == 0 && hook->Type != ImGuiContextHookType_PendingRemoval_);
|
|
g.Hooks.push_back(*hook);
|
|
g.Hooks.back().HookId = ++g.HookIdNext;
|
|
return g.HookIdNext;
|
|
}
|
|
|
|
// Deferred removal, avoiding issue with changing vector while iterating it
|
|
void ImGui::RemoveContextHook(ImGuiContext* ctx, ImGuiID hook_id)
|
|
{
|
|
ImGuiContext& g = *ctx;
|
|
IM_ASSERT(hook_id != 0);
|
|
for (int n = 0; n < g.Hooks.Size; n++)
|
|
if (g.Hooks[n].HookId == hook_id)
|
|
g.Hooks[n].Type = ImGuiContextHookType_PendingRemoval_;
|
|
}
|
|
|
|
// Call context hooks (used by e.g. test engine)
|
|
// We assume a small number of hooks so all stored in same array
|
|
void ImGui::CallContextHooks(ImGuiContext* ctx, ImGuiContextHookType hook_type)
|
|
{
|
|
ImGuiContext& g = *ctx;
|
|
for (int n = 0; n < g.Hooks.Size; n++)
|
|
if (g.Hooks[n].Type == hook_type)
|
|
g.Hooks[n].Callback(&g, &g.Hooks[n]);
|
|
}
|
|
|
|
ImGuiIO& ImGui::GetIO()
|
|
{
|
|
IM_ASSERT(GImGui != NULL &&
|
|
"No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext() ?");
|
|
return GImGui->IO;
|
|
}
|
|
|
|
ImGuiPlatformIO& ImGui::GetPlatformIO()
|
|
{
|
|
IM_ASSERT(GImGui != NULL &&
|
|
"No current context. Did you call ImGui::CreateContext() or ImGui::SetCurrentContext()?");
|
|
return GImGui->PlatformIO;
|
|
}
|
|
|
|
// Pass this to your backend rendering function! Valid after Render() and until the next call to NewFrame()
|
|
ImDrawData* ImGui::GetDrawData()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiViewportP* viewport = g.Viewports[0];
|
|
return viewport->DrawDataP.Valid ? &viewport->DrawDataP : NULL;
|
|
}
|
|
|
|
double ImGui::GetTime()
|
|
{
|
|
return GImGui->Time;
|
|
}
|
|
|
|
int ImGui::GetFrameCount()
|
|
{
|
|
return GImGui->FrameCount;
|
|
}
|
|
|
|
static ImDrawList* GetViewportDrawList(ImGuiViewportP* viewport, size_t drawlist_no, const char* drawlist_name)
|
|
{
|
|
// Create the draw list on demand, because they are not frequently used for all viewports
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(drawlist_no < IM_ARRAYSIZE(viewport->DrawLists));
|
|
ImDrawList* draw_list = viewport->DrawLists[drawlist_no];
|
|
if (draw_list == NULL)
|
|
{
|
|
draw_list = IM_NEW(ImDrawList)(&g.DrawListSharedData);
|
|
draw_list->_OwnerName = drawlist_name;
|
|
viewport->DrawLists[drawlist_no] = draw_list;
|
|
}
|
|
|
|
// Our ImDrawList system requires that there is always a command
|
|
if (viewport->DrawListsLastFrame[drawlist_no] != g.FrameCount)
|
|
{
|
|
draw_list->_ResetForNewFrame();
|
|
draw_list->PushTextureID(g.IO.Fonts->TexID);
|
|
draw_list->PushClipRect(viewport->Pos, viewport->Pos + viewport->Size, false);
|
|
viewport->DrawListsLastFrame[drawlist_no] = g.FrameCount;
|
|
}
|
|
return draw_list;
|
|
}
|
|
|
|
ImDrawList* ImGui::GetBackgroundDrawList(ImGuiViewport* viewport)
|
|
{
|
|
return GetViewportDrawList((ImGuiViewportP*)viewport, 0, "##Background");
|
|
}
|
|
|
|
ImDrawList* ImGui::GetBackgroundDrawList()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
return GetBackgroundDrawList(g.CurrentWindow->Viewport);
|
|
}
|
|
|
|
ImDrawList* ImGui::GetForegroundDrawList(ImGuiViewport* viewport)
|
|
{
|
|
return GetViewportDrawList((ImGuiViewportP*)viewport, 1, "##Foreground");
|
|
}
|
|
|
|
ImDrawList* ImGui::GetForegroundDrawList()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
return GetForegroundDrawList(g.CurrentWindow->Viewport);
|
|
}
|
|
|
|
ImDrawListSharedData* ImGui::GetDrawListSharedData()
|
|
{
|
|
return &GImGui->DrawListSharedData;
|
|
}
|
|
|
|
void ImGui::StartMouseMovingWindow(ImGuiWindow* window)
|
|
{
|
|
// Set ActiveId even if the _NoMove flag is set. Without it, dragging away from a window with _NoMove would activate
|
|
// hover on other windows. We _also_ call this when clicking in a window empty space when
|
|
// io.ConfigWindowsMoveFromTitleBarOnly is set, but clear g.MovingWindow afterward. This is because we want ActiveId
|
|
// to be set even when the window is not permitted to move.
|
|
ImGuiContext& g = *GImGui;
|
|
FocusWindow(window);
|
|
SetActiveID(window->MoveId, window);
|
|
g.NavDisableHighlight = true;
|
|
g.ActiveIdClickOffset = g.IO.MouseClickedPos[0] - window->RootWindowDockTree->Pos;
|
|
g.ActiveIdNoClearOnFocusLoss = true;
|
|
SetActiveIdUsingNavAndKeys();
|
|
|
|
bool can_move_window = true;
|
|
if ((window->Flags & ImGuiWindowFlags_NoMove) || (window->RootWindowDockTree->Flags & ImGuiWindowFlags_NoMove))
|
|
can_move_window = false;
|
|
if (ImGuiDockNode* node = window->DockNodeAsHost)
|
|
if (node->VisibleWindow && (node->VisibleWindow->Flags & ImGuiWindowFlags_NoMove))
|
|
can_move_window = false;
|
|
if (can_move_window)
|
|
g.MovingWindow = window;
|
|
}
|
|
|
|
// We use 'undock_floating_node == false' when dragging from title bar to allow moving groups of floating nodes without
|
|
// undocking them.
|
|
// - undock_floating_node == true: when dragging from a floating node within a hierarchy, always undock the node.
|
|
// - undock_floating_node == false: when dragging from a floating node within a hierarchy, move root window.
|
|
void ImGui::StartMouseMovingWindowOrNode(ImGuiWindow* window, ImGuiDockNode* node, bool undock_floating_node)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
bool can_undock_node = false;
|
|
if (node != NULL && node->VisibleWindow && (node->VisibleWindow->Flags & ImGuiWindowFlags_NoMove) == 0)
|
|
{
|
|
// Can undock if:
|
|
// - part of a floating node hierarchy with more than one visible node (if only one is visible, we'll just move
|
|
// the whole hierarchy)
|
|
// - part of a dockspace node hierarchy (trivia: undocking from a fixed/central node will create a new node and
|
|
// copy windows)
|
|
ImGuiDockNode* root_node = DockNodeGetRootNode(node);
|
|
if (root_node->OnlyNodeWithWindows != node ||
|
|
root_node->CentralNode !=
|
|
NULL) // -V1051 PVS-Studio thinks node should be root_node and is wrong about that.
|
|
if (undock_floating_node || root_node->IsDockSpace())
|
|
can_undock_node = true;
|
|
}
|
|
|
|
const bool clicked = IsMouseClicked(0);
|
|
const bool dragging = IsMouseDragging(0, g.IO.MouseDragThreshold * 1.70f);
|
|
if (can_undock_node && dragging)
|
|
DockContextQueueUndockNode(
|
|
&g,
|
|
node); // Will lead to DockNodeStartMouseMovingWindow() -> StartMouseMovingWindow() being called next frame
|
|
else if (!can_undock_node && (clicked || dragging) && g.MovingWindow != window)
|
|
StartMouseMovingWindow(window);
|
|
}
|
|
|
|
// Handle mouse moving window
|
|
// Note: moving window with the navigation keys (Square + d-pad / CTRL+TAB + Arrows) are processed in
|
|
// NavUpdateWindowing()
|
|
// FIXME: We don't have strong guarantee that g.MovingWindow stay synched with g.ActiveId == g.MovingWindow->MoveId.
|
|
// This is currently enforced by the fact that BeginDragDropSource() is setting all g.ActiveIdUsingXXXX flags to inhibit
|
|
// navigation inputs, but if we should more thoroughly test cases where g.ActiveId or g.MovingWindow gets changed and
|
|
// not the other.
|
|
void ImGui::UpdateMouseMovingWindowNewFrame()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (g.MovingWindow != NULL)
|
|
{
|
|
// We actually want to move the root window. g.MovingWindow == window we clicked on (could be a child window).
|
|
// We track it to preserve Focus and so that generally ActiveIdWindow == MovingWindow and ActiveId ==
|
|
// MovingWindow->MoveId for consistency.
|
|
KeepAliveID(g.ActiveId);
|
|
IM_ASSERT(g.MovingWindow && g.MovingWindow->RootWindowDockTree);
|
|
ImGuiWindow* moving_window = g.MovingWindow->RootWindowDockTree;
|
|
if (g.IO.MouseDown[0] && IsMousePosValid(&g.IO.MousePos))
|
|
{
|
|
ImVec2 pos = g.IO.MousePos - g.ActiveIdClickOffset;
|
|
if (moving_window->Pos.x != pos.x || moving_window->Pos.y != pos.y)
|
|
{
|
|
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;
|
|
moving_window->Viewport->UpdateWorkRect();
|
|
}
|
|
}
|
|
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)
|
|
if (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable)
|
|
UpdateTryMergeWindowIntoHostViewport(moving_window, g.MouseViewport);
|
|
|
|
// Restore the mouse viewport so that we don't hover the viewport _under_ the moved window during the frame
|
|
// we released the mouse button.
|
|
if (!IsDragDropPayloadBeingAccepted())
|
|
g.MouseViewport = moving_window->Viewport;
|
|
|
|
// Clear the NoInput window flag set by the Viewport system
|
|
moving_window->Viewport->Flags &= ~ImGuiViewportFlags_NoInputs; // FIXME-VIEWPORT: Test engine managed to
|
|
// crash here because Viewport was NULL.
|
|
|
|
g.MovingWindow = NULL;
|
|
ClearActiveID();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// When clicking/dragging from a window that has the _NoMove flag, we still set the ActiveId in order to prevent
|
|
// hovering others.
|
|
if (g.ActiveIdWindow && g.ActiveIdWindow->MoveId == g.ActiveId)
|
|
{
|
|
KeepAliveID(g.ActiveId);
|
|
if (!g.IO.MouseDown[0])
|
|
ClearActiveID();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initiate moving window when clicking on empty space or title bar.
|
|
// Handle left-click and right-click focus.
|
|
void ImGui::UpdateMouseMovingWindowEndFrame()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (g.ActiveId != 0 || g.HoveredId != 0)
|
|
return;
|
|
|
|
// Unless we just made a window/popup appear
|
|
if (g.NavWindow && g.NavWindow->Appearing)
|
|
return;
|
|
|
|
// Click on empty space to focus window and start moving
|
|
// (after we're done with all our widgets, so e.g. clicking on docking tab-bar which have set HoveredId already and
|
|
// not get us here!)
|
|
if (g.IO.MouseClicked[0])
|
|
{
|
|
// Handle the edge case of a popup being closed while clicking in its empty space.
|
|
// If we try to focus it, FocusWindow() > ClosePopupsOverWindow() will accidentally close any parent popups
|
|
// because they are not linked together any more.
|
|
ImGuiWindow* root_window = g.HoveredWindow ? g.HoveredWindow->RootWindow : NULL;
|
|
const bool is_closed_popup = root_window && (root_window->Flags & ImGuiWindowFlags_Popup) &&
|
|
!IsPopupOpen(root_window->PopupId, ImGuiPopupFlags_AnyPopupLevel);
|
|
|
|
if (root_window != NULL && !is_closed_popup)
|
|
{
|
|
StartMouseMovingWindow(g.HoveredWindow); //-V595
|
|
|
|
// Cancel moving if clicked outside of title bar
|
|
if (g.IO.ConfigWindowsMoveFromTitleBarOnly)
|
|
if (!(root_window->Flags & ImGuiWindowFlags_NoTitleBar) || root_window->DockIsActive)
|
|
if (!root_window->TitleBarRect().Contains(g.IO.MouseClickedPos[0]))
|
|
g.MovingWindow = NULL;
|
|
|
|
// Cancel moving if clicked over an item which was disabled or inhibited by popups (note that we know
|
|
// HoveredId == 0 already)
|
|
if (g.HoveredIdDisabled)
|
|
g.MovingWindow = NULL;
|
|
}
|
|
else if (root_window == NULL && g.NavWindow != NULL && GetTopMostPopupModal() == NULL)
|
|
{
|
|
// Clicking on void disable focus
|
|
FocusWindow(NULL);
|
|
}
|
|
}
|
|
|
|
// With right mouse button we close popups without changing focus based on where the mouse is aimed
|
|
// Instead, focus will be restored to the window under the bottom-most closed popup.
|
|
// (The left mouse button path calls FocusWindow on the hovered window, which will lead
|
|
// NewFrame->ClosePopupsOverWindow to trigger)
|
|
if (g.IO.MouseClicked[1])
|
|
{
|
|
// Find the top-most window between HoveredWindow and the top-most Modal Window.
|
|
// This is where we can trim the popup stack.
|
|
ImGuiWindow* modal = GetTopMostPopupModal();
|
|
bool hovered_window_above_modal = g.HoveredWindow && IsWindowAbove(g.HoveredWindow, modal);
|
|
ClosePopupsOverWindow(hovered_window_above_modal ? g.HoveredWindow : modal, true);
|
|
}
|
|
}
|
|
|
|
static void TranslateWindow(ImGuiWindow* window, const ImVec2& delta)
|
|
{
|
|
window->Pos += delta;
|
|
window->ClipRect.Translate(delta);
|
|
window->OuterRectClipped.Translate(delta);
|
|
window->InnerRect.Translate(delta);
|
|
window->DC.CursorPos += delta;
|
|
window->DC.CursorStartPos += delta;
|
|
window->DC.CursorMaxPos += delta;
|
|
}
|
|
|
|
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->ContentSize = ImFloor(window->ContentSize * scale);
|
|
}
|
|
|
|
static bool IsWindowActiveAndVisible(ImGuiWindow* window)
|
|
{
|
|
return (window->Active) && (!window->Hidden);
|
|
}
|
|
|
|
static void ImGui::UpdateMouseInputs()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
// Round mouse position to avoid spreading non-rounded position (e.g. UpdateManualResize doesn't support them well)
|
|
if (IsMousePosValid(&g.IO.MousePos))
|
|
g.IO.MousePos = g.LastValidMousePos = ImFloor(g.IO.MousePos);
|
|
|
|
// If mouse just appeared or disappeared (usually denoted by -FLT_MAX components) we cancel out movement in
|
|
// MouseDelta
|
|
if (IsMousePosValid(&g.IO.MousePos) && IsMousePosValid(&g.IO.MousePosPrev))
|
|
g.IO.MouseDelta = g.IO.MousePos - g.IO.MousePosPrev;
|
|
else
|
|
g.IO.MouseDelta = ImVec2(0.0f, 0.0f);
|
|
if (g.IO.MouseDelta.x != 0.0f || g.IO.MouseDelta.y != 0.0f)
|
|
g.NavDisableMouseHover = false;
|
|
|
|
g.IO.MousePosPrev = g.IO.MousePos;
|
|
for (int i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++)
|
|
{
|
|
g.IO.MouseClicked[i] = g.IO.MouseDown[i] && g.IO.MouseDownDuration[i] < 0.0f;
|
|
g.IO.MouseReleased[i] = !g.IO.MouseDown[i] && g.IO.MouseDownDuration[i] >= 0.0f;
|
|
g.IO.MouseDownDurationPrev[i] = g.IO.MouseDownDuration[i];
|
|
g.IO.MouseDownDuration[i] =
|
|
g.IO.MouseDown[i] ? (g.IO.MouseDownDuration[i] < 0.0f ? 0.0f : g.IO.MouseDownDuration[i] + g.IO.DeltaTime)
|
|
: -1.0f;
|
|
g.IO.MouseDoubleClicked[i] = false;
|
|
if (g.IO.MouseClicked[i])
|
|
{
|
|
if ((float)(g.Time - g.IO.MouseClickedTime[i]) < g.IO.MouseDoubleClickTime)
|
|
{
|
|
ImVec2 delta_from_click_pos =
|
|
IsMousePosValid(&g.IO.MousePos) ? (g.IO.MousePos - g.IO.MouseClickedPos[i]) : ImVec2(0.0f, 0.0f);
|
|
if (ImLengthSqr(delta_from_click_pos) < g.IO.MouseDoubleClickMaxDist * g.IO.MouseDoubleClickMaxDist)
|
|
g.IO.MouseDoubleClicked[i] = true;
|
|
g.IO.MouseClickedTime[i] =
|
|
-g.IO.MouseDoubleClickTime *
|
|
2.0f; // Mark as "old enough" so the third click isn't turned into a double-click
|
|
}
|
|
else
|
|
{
|
|
g.IO.MouseClickedTime[i] = g.Time;
|
|
}
|
|
g.IO.MouseClickedPos[i] = g.IO.MousePos;
|
|
g.IO.MouseDownWasDoubleClick[i] = g.IO.MouseDoubleClicked[i];
|
|
g.IO.MouseDragMaxDistanceAbs[i] = ImVec2(0.0f, 0.0f);
|
|
g.IO.MouseDragMaxDistanceSqr[i] = 0.0f;
|
|
}
|
|
else if (g.IO.MouseDown[i])
|
|
{
|
|
// Maintain the maximum distance we reaching from the initial click position, which is used with dragging
|
|
// threshold
|
|
ImVec2 delta_from_click_pos =
|
|
IsMousePosValid(&g.IO.MousePos) ? (g.IO.MousePos - g.IO.MouseClickedPos[i]) : ImVec2(0.0f, 0.0f);
|
|
g.IO.MouseDragMaxDistanceSqr[i] = ImMax(g.IO.MouseDragMaxDistanceSqr[i], ImLengthSqr(delta_from_click_pos));
|
|
g.IO.MouseDragMaxDistanceAbs[i].x =
|
|
ImMax(g.IO.MouseDragMaxDistanceAbs[i].x,
|
|
delta_from_click_pos.x < 0.0f ? -delta_from_click_pos.x : delta_from_click_pos.x);
|
|
g.IO.MouseDragMaxDistanceAbs[i].y =
|
|
ImMax(g.IO.MouseDragMaxDistanceAbs[i].y,
|
|
delta_from_click_pos.y < 0.0f ? -delta_from_click_pos.y : delta_from_click_pos.y);
|
|
}
|
|
if (!g.IO.MouseDown[i] && !g.IO.MouseReleased[i])
|
|
g.IO.MouseDownWasDoubleClick[i] = false;
|
|
if (g.IO.MouseClicked[i]) // Clicking any mouse button reactivate mouse hovering which may have been
|
|
// deactivated by gamepad/keyboard navigation
|
|
g.NavDisableMouseHover = false;
|
|
}
|
|
}
|
|
|
|
static void StartLockWheelingWindow(ImGuiWindow* window)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (g.WheelingWindow == window)
|
|
return;
|
|
g.WheelingWindow = window;
|
|
g.WheelingWindowRefMousePos = g.IO.MousePos;
|
|
g.WheelingWindowTimer = WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER;
|
|
}
|
|
|
|
void ImGui::UpdateMouseWheel()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
// Reset the locked window if we move the mouse or after the timer elapses
|
|
if (g.WheelingWindow != NULL)
|
|
{
|
|
g.WheelingWindowTimer -= g.IO.DeltaTime;
|
|
if (IsMousePosValid() && ImLengthSqr(g.IO.MousePos - g.WheelingWindowRefMousePos) >
|
|
g.IO.MouseDragThreshold * g.IO.MouseDragThreshold)
|
|
g.WheelingWindowTimer = 0.0f;
|
|
if (g.WheelingWindowTimer <= 0.0f)
|
|
{
|
|
g.WheelingWindow = NULL;
|
|
g.WheelingWindowTimer = 0.0f;
|
|
}
|
|
}
|
|
|
|
if (g.IO.MouseWheel == 0.0f && g.IO.MouseWheelH == 0.0f)
|
|
return;
|
|
|
|
if ((g.ActiveId != 0 && g.ActiveIdUsingMouseWheel) ||
|
|
(g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrameUsingMouseWheel))
|
|
return;
|
|
|
|
ImGuiWindow* window = g.WheelingWindow ? g.WheelingWindow : g.HoveredWindow;
|
|
if (!window || window->Collapsed)
|
|
return;
|
|
|
|
// Zoom / Scale window
|
|
// FIXME-OBSOLETE: This is an old feature, it still works but pretty much nobody is using it and may be best
|
|
// redesigned.
|
|
if (g.IO.MouseWheel != 0.0f && g.IO.KeyCtrl && g.IO.FontAllowUserScaling)
|
|
{
|
|
StartLockWheelingWindow(window);
|
|
const float new_font_scale = ImClamp(window->FontWindowScale + g.IO.MouseWheel * 0.10f, 0.50f, 2.50f);
|
|
const float scale = new_font_scale / window->FontWindowScale;
|
|
window->FontWindowScale = new_font_scale;
|
|
if (window == window->RootWindow)
|
|
{
|
|
const ImVec2 offset = window->Size * (1.0f - scale) * (g.IO.MousePos - window->Pos) / window->Size;
|
|
SetWindowPos(window, window->Pos + offset, 0);
|
|
window->Size = ImFloor(window->Size * scale);
|
|
window->SizeFull = ImFloor(window->SizeFull * scale);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Mouse wheel scrolling
|
|
// If a child window has the ImGuiWindowFlags_NoScrollWithMouse flag, we give a chance to scroll its parent
|
|
if (g.IO.KeyCtrl)
|
|
return;
|
|
|
|
// As a standard behavior holding SHIFT while using Vertical Mouse Wheel triggers Horizontal scroll instead
|
|
// (we avoid doing it on OSX as it the OS input layer handles this already)
|
|
const bool swap_axis = g.IO.KeyShift && !g.IO.ConfigMacOSXBehaviors;
|
|
const float wheel_y = swap_axis ? 0.0f : g.IO.MouseWheel;
|
|
const float wheel_x = swap_axis ? g.IO.MouseWheel : g.IO.MouseWheelH;
|
|
|
|
// Vertical Mouse Wheel scrolling
|
|
if (wheel_y != 0.0f)
|
|
{
|
|
StartLockWheelingWindow(window);
|
|
while ((window->Flags & ImGuiWindowFlags_ChildWindow) &&
|
|
((window->ScrollMax.y == 0.0f) || ((window->Flags & ImGuiWindowFlags_NoScrollWithMouse) &&
|
|
!(window->Flags & ImGuiWindowFlags_NoMouseInputs))))
|
|
window = window->ParentWindow;
|
|
if (!(window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs))
|
|
{
|
|
float max_step = window->InnerRect.GetHeight() * 0.67f;
|
|
float scroll_step = ImFloor(ImMin(5 * window->CalcFontSize(), max_step));
|
|
SetScrollY(window, window->Scroll.y - wheel_y * scroll_step);
|
|
}
|
|
}
|
|
|
|
// Horizontal Mouse Wheel scrolling, or Vertical Mouse Wheel w/ Shift held
|
|
if (wheel_x != 0.0f)
|
|
{
|
|
StartLockWheelingWindow(window);
|
|
while ((window->Flags & ImGuiWindowFlags_ChildWindow) &&
|
|
((window->ScrollMax.x == 0.0f) || ((window->Flags & ImGuiWindowFlags_NoScrollWithMouse) &&
|
|
!(window->Flags & ImGuiWindowFlags_NoMouseInputs))))
|
|
window = window->ParentWindow;
|
|
if (!(window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs))
|
|
{
|
|
float max_step = window->InnerRect.GetWidth() * 0.67f;
|
|
float scroll_step = ImFloor(ImMin(2 * window->CalcFontSize(), max_step));
|
|
SetScrollX(window, window->Scroll.x - wheel_x * scroll_step);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ImGui::UpdateTabFocus()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
// Pressing TAB activate widget focus
|
|
g.TabFocusPressed = (g.NavWindow && g.NavWindow->Active && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) &&
|
|
!g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab));
|
|
if (g.ActiveId == 0 && g.TabFocusPressed)
|
|
{
|
|
// - This path is only taken when no widget are active/tabbed-into yet.
|
|
// Subsequent tabbing will be processed by FocusableItemRegister()
|
|
// - Note that SetKeyboardFocusHere() sets the Next fields mid-frame. To be consistent we also
|
|
// manipulate the Next fields here even though they will be turned into Curr fields below.
|
|
g.TabFocusRequestNextWindow = g.NavWindow;
|
|
g.TabFocusRequestNextCounterRegular = INT_MAX;
|
|
if (g.NavId != 0 && g.NavIdTabCounter != INT_MAX)
|
|
g.TabFocusRequestNextCounterTabStop = g.NavIdTabCounter + (g.IO.KeyShift ? -1 : 0);
|
|
else
|
|
g.TabFocusRequestNextCounterTabStop = g.IO.KeyShift ? -1 : 0;
|
|
}
|
|
|
|
// Turn queued focus request into current one
|
|
g.TabFocusRequestCurrWindow = NULL;
|
|
g.TabFocusRequestCurrCounterRegular = g.TabFocusRequestCurrCounterTabStop = INT_MAX;
|
|
if (g.TabFocusRequestNextWindow != NULL)
|
|
{
|
|
ImGuiWindow* window = g.TabFocusRequestNextWindow;
|
|
g.TabFocusRequestCurrWindow = window;
|
|
if (g.TabFocusRequestNextCounterRegular != INT_MAX && window->DC.FocusCounterRegular != -1)
|
|
g.TabFocusRequestCurrCounterRegular =
|
|
ImModPositive(g.TabFocusRequestNextCounterRegular, window->DC.FocusCounterRegular + 1);
|
|
if (g.TabFocusRequestNextCounterTabStop != INT_MAX && window->DC.FocusCounterTabStop != -1)
|
|
g.TabFocusRequestCurrCounterTabStop =
|
|
ImModPositive(g.TabFocusRequestNextCounterTabStop, window->DC.FocusCounterTabStop + 1);
|
|
g.TabFocusRequestNextWindow = NULL;
|
|
g.TabFocusRequestNextCounterRegular = g.TabFocusRequestNextCounterTabStop = INT_MAX;
|
|
}
|
|
|
|
g.NavIdTabCounter = INT_MAX;
|
|
}
|
|
|
|
// The reason this is exposed in imgui_internal.h is: on touch-based system that don't have hovering, we want to
|
|
// dispatch inputs to the right target (imgui vs imgui+app)
|
|
void ImGui::UpdateHoveredWindowAndCaptureFlags()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
g.WindowsHoverPadding = ImMax(g.Style.TouchExtraPadding, ImVec2(WINDOWS_HOVER_PADDING, WINDOWS_HOVER_PADDING));
|
|
|
|
// Find the window hovered by mouse:
|
|
// - Child windows can extend beyond the limit of their parent so we need to derive HoveredRootWindow from
|
|
// HoveredWindow.
|
|
// - When moving a window we can skip the search, which also conveniently bypasses the fact that
|
|
// window->WindowRectClipped is lagging as this point of the frame.
|
|
// - We also support the moved window toggling the NoInputs flag after moving has started in order to be able to
|
|
// detect windows below it, which is useful for e.g. docking mechanisms.
|
|
bool clear_hovered_windows = false;
|
|
FindHoveredWindow();
|
|
IM_ASSERT(g.HoveredWindow == NULL || g.HoveredWindow == g.MovingWindow ||
|
|
g.HoveredWindow->Viewport == g.MouseViewport);
|
|
|
|
// Modal windows prevents mouse from hovering behind them.
|
|
ImGuiWindow* modal_window = GetTopMostPopupModal();
|
|
if (modal_window && g.HoveredWindow && !IsWindowChildOf(g.HoveredWindow->RootWindowDockTree, modal_window))
|
|
clear_hovered_windows = true;
|
|
|
|
// Disabled mouse?
|
|
if (g.IO.ConfigFlags & ImGuiConfigFlags_NoMouse)
|
|
clear_hovered_windows = true;
|
|
|
|
// 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;
|
|
bool mouse_any_down = false;
|
|
for (int i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++)
|
|
{
|
|
if (g.IO.MouseClicked[i])
|
|
g.IO.MouseDownOwned[i] = (g.HoveredWindow != NULL) || (g.OpenPopupStack.Size > 0);
|
|
mouse_any_down |= g.IO.MouseDown[i];
|
|
if (g.IO.MouseDown[i])
|
|
if (mouse_earliest_button_down == -1 ||
|
|
g.IO.MouseClickedTime[i] < g.IO.MouseClickedTime[mouse_earliest_button_down])
|
|
mouse_earliest_button_down = i;
|
|
}
|
|
const bool mouse_avail_to_imgui =
|
|
(mouse_earliest_button_down == -1) || g.IO.MouseDownOwned[mouse_earliest_button_down];
|
|
|
|
// If mouse was first clicked outside of ImGui bounds we also cancel out hovering.
|
|
// 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)
|
|
clear_hovered_windows = true;
|
|
|
|
if (clear_hovered_windows)
|
|
g.HoveredWindow = g.HoveredWindowUnderMovingWindow = NULL;
|
|
|
|
// Update io.WantCaptureMouse for the user application (true = dispatch mouse info to imgui, false = dispatch mouse
|
|
// info to Dear ImGui + app)
|
|
if (g.WantCaptureMouseNextFrame != -1)
|
|
g.IO.WantCaptureMouse = (g.WantCaptureMouseNextFrame != 0);
|
|
else
|
|
g.IO.WantCaptureMouse =
|
|
(mouse_avail_to_imgui && (g.HoveredWindow != NULL || mouse_any_down)) || (g.OpenPopupStack.Size > 0);
|
|
|
|
// Update io.WantCaptureKeyboard for the user application (true = dispatch keyboard info to imgui, false = dispatch
|
|
// keyboard info to Dear ImGui + app)
|
|
if (g.WantCaptureKeyboardNextFrame != -1)
|
|
g.IO.WantCaptureKeyboard = (g.WantCaptureKeyboardNextFrame != 0);
|
|
else
|
|
g.IO.WantCaptureKeyboard = (g.ActiveId != 0) || (modal_window != NULL);
|
|
if (g.IO.NavActive && (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) &&
|
|
!(g.IO.ConfigFlags & ImGuiConfigFlags_NavNoCaptureKeyboard))
|
|
g.IO.WantCaptureKeyboard = true;
|
|
|
|
// Update io.WantTextInput flag, this is to allow systems without a keyboard (e.g. mobile, hand-held) to show a
|
|
// software keyboard if possible
|
|
g.IO.WantTextInput = (g.WantTextInputNextFrame != -1) ? (g.WantTextInputNextFrame != 0) : false;
|
|
}
|
|
|
|
ImGuiKeyModFlags ImGui::GetMergedKeyModFlags()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiKeyModFlags key_mod_flags = ImGuiKeyModFlags_None;
|
|
if (g.IO.KeyCtrl)
|
|
{
|
|
key_mod_flags |= ImGuiKeyModFlags_Ctrl;
|
|
}
|
|
if (g.IO.KeyShift)
|
|
{
|
|
key_mod_flags |= ImGuiKeyModFlags_Shift;
|
|
}
|
|
if (g.IO.KeyAlt)
|
|
{
|
|
key_mod_flags |= ImGuiKeyModFlags_Alt;
|
|
}
|
|
if (g.IO.KeySuper)
|
|
{
|
|
key_mod_flags |= ImGuiKeyModFlags_Super;
|
|
}
|
|
return key_mod_flags;
|
|
}
|
|
|
|
void ImGui::NewFrame()
|
|
{
|
|
IM_ASSERT(GImGui != NULL &&
|
|
"No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext() ?");
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
// Remove pending delete hooks before frame start.
|
|
// This deferred removal avoid issues of removal while iterating the hook vector
|
|
for (int n = g.Hooks.Size - 1; n >= 0; n--)
|
|
if (g.Hooks[n].Type == ImGuiContextHookType_PendingRemoval_)
|
|
g.Hooks.erase(&g.Hooks[n]);
|
|
|
|
CallContextHooks(&g, ImGuiContextHookType_NewFramePre);
|
|
|
|
// Check and assert for various common IO and Configuration mistakes
|
|
g.ConfigFlagsLastFrame = g.ConfigFlagsCurrFrame;
|
|
ErrorCheckNewFrameSanityChecks();
|
|
g.ConfigFlagsCurrFrame = g.IO.ConfigFlags;
|
|
|
|
// Load settings on first frame, save settings when modified (after a delay)
|
|
UpdateSettings();
|
|
|
|
g.Time += g.IO.DeltaTime;
|
|
g.WithinFrameScope = true;
|
|
g.FrameCount += 1;
|
|
g.TooltipOverrideCount = 0;
|
|
g.WindowsActiveCount = 0;
|
|
g.MenusIdSubmittedThisFrame.resize(0);
|
|
|
|
// Calculate frame-rate for the user, as a purely luxurious feature
|
|
g.FramerateSecPerFrameAccum += g.IO.DeltaTime - g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx];
|
|
g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx] = g.IO.DeltaTime;
|
|
g.FramerateSecPerFrameIdx = (g.FramerateSecPerFrameIdx + 1) % IM_ARRAYSIZE(g.FramerateSecPerFrame);
|
|
g.FramerateSecPerFrameCount = ImMin(g.FramerateSecPerFrameCount + 1, IM_ARRAYSIZE(g.FramerateSecPerFrame));
|
|
g.IO.Framerate = (g.FramerateSecPerFrameAccum > 0.0f)
|
|
? (1.0f / (g.FramerateSecPerFrameAccum / (float)g.FramerateSecPerFrameCount))
|
|
: FLT_MAX;
|
|
|
|
UpdateViewportsNewFrame();
|
|
|
|
// Setup current font and draw list shared data
|
|
// FIXME-VIEWPORT: the concept of a single ClipRectFullscreen is not ideal!
|
|
g.IO.Fonts->Locked = true;
|
|
SetCurrentFont(GetDefaultFont());
|
|
IM_ASSERT(g.Font->IsLoaded());
|
|
ImRect virtual_space(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX);
|
|
for (int n = 0; n < g.Viewports.Size; n++)
|
|
virtual_space.Add(g.Viewports[n]->GetMainRect());
|
|
g.DrawListSharedData.ClipRectFullscreen = virtual_space.ToVec4();
|
|
g.DrawListSharedData.CurveTessellationTol = g.Style.CurveTessellationTol;
|
|
g.DrawListSharedData.SetCircleTessellationMaxError(g.Style.CircleTessellationMaxError);
|
|
g.DrawListSharedData.InitialFlags = ImDrawListFlags_None;
|
|
if (g.Style.AntiAliasedLines)
|
|
g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLines;
|
|
if (g.Style.AntiAliasedLinesUseTex && !(g.Font->ContainerAtlas->Flags & ImFontAtlasFlags_NoBakedLines))
|
|
g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLinesUseTex;
|
|
if (g.Style.AntiAliasedFill)
|
|
g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedFill;
|
|
if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset)
|
|
g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AllowVtxOffset;
|
|
|
|
// Mark rendering data as invalid to prevent user who may have a handle on it to use it.
|
|
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)
|
|
KeepAliveID(g.DragDropPayload.SourceId);
|
|
|
|
// Update HoveredId data
|
|
if (!g.HoveredIdPreviousFrame)
|
|
g.HoveredIdTimer = 0.0f;
|
|
if (!g.HoveredIdPreviousFrame || (g.HoveredId && g.ActiveId == g.HoveredId))
|
|
g.HoveredIdNotActiveTimer = 0.0f;
|
|
if (g.HoveredId)
|
|
g.HoveredIdTimer += g.IO.DeltaTime;
|
|
if (g.HoveredId && g.ActiveId != g.HoveredId)
|
|
g.HoveredIdNotActiveTimer += g.IO.DeltaTime;
|
|
g.HoveredIdPreviousFrame = g.HoveredId;
|
|
g.HoveredIdPreviousFrameUsingMouseWheel = g.HoveredIdUsingMouseWheel;
|
|
g.HoveredId = 0;
|
|
g.HoveredIdAllowOverlap = false;
|
|
g.HoveredIdUsingMouseWheel = false;
|
|
g.HoveredIdDisabled = false;
|
|
|
|
// Update ActiveId data (clear reference to active widget if the widget isn't alive anymore)
|
|
if (g.ActiveIdIsAlive != g.ActiveId && g.ActiveIdPreviousFrame == g.ActiveId && g.ActiveId != 0)
|
|
ClearActiveID();
|
|
if (g.ActiveId)
|
|
g.ActiveIdTimer += g.IO.DeltaTime;
|
|
g.LastActiveIdTimer += g.IO.DeltaTime;
|
|
g.ActiveIdPreviousFrame = g.ActiveId;
|
|
g.ActiveIdPreviousFrameWindow = g.ActiveIdWindow;
|
|
g.ActiveIdPreviousFrameHasBeenEditedBefore = g.ActiveIdHasBeenEditedBefore;
|
|
g.ActiveIdIsAlive = 0;
|
|
g.ActiveIdHasBeenEditedThisFrame = false;
|
|
g.ActiveIdPreviousFrameIsAlive = false;
|
|
g.ActiveIdIsJustActivated = false;
|
|
if (g.TempInputId != 0 && g.ActiveId != g.TempInputId)
|
|
g.TempInputId = 0;
|
|
if (g.ActiveId == 0)
|
|
{
|
|
g.ActiveIdUsingNavDirMask = 0x00;
|
|
g.ActiveIdUsingNavInputMask = 0x00;
|
|
g.ActiveIdUsingKeyInputMask = 0x00;
|
|
}
|
|
|
|
// Drag and drop
|
|
g.DragDropAcceptIdPrev = g.DragDropAcceptIdCurr;
|
|
g.DragDropAcceptIdCurr = 0;
|
|
g.DragDropAcceptIdCurrRectSurface = FLT_MAX;
|
|
g.DragDropWithinSource = false;
|
|
g.DragDropWithinTarget = false;
|
|
g.DragDropHoldJustPressedId = 0;
|
|
|
|
// Clear buttons state when focus is lost
|
|
// (this is useful so e.g. releasing Alt after focus loss on Alt-Tab doesn't trigger the Alt menu toggle)
|
|
if (g.IO.AppFocusLost)
|
|
{
|
|
g.IO.ClearInputKeys();
|
|
g.IO.AppFocusLost = false;
|
|
}
|
|
|
|
// Update keyboard input state
|
|
// Synchronize io.KeyMods with individual modifiers io.KeyXXX bools
|
|
g.IO.KeyMods = GetMergedKeyModFlags();
|
|
memcpy(g.IO.KeysDownDurationPrev, g.IO.KeysDownDuration, sizeof(g.IO.KeysDownDuration));
|
|
for (int i = 0; i < IM_ARRAYSIZE(g.IO.KeysDown); i++)
|
|
g.IO.KeysDownDuration[i] =
|
|
g.IO.KeysDown[i] ? (g.IO.KeysDownDuration[i] < 0.0f ? 0.0f : g.IO.KeysDownDuration[i] + g.IO.DeltaTime)
|
|
: -1.0f;
|
|
|
|
// Update gamepad/keyboard navigation
|
|
NavUpdate();
|
|
|
|
// Update mouse input state
|
|
UpdateMouseInputs();
|
|
|
|
// Undocking
|
|
// (needs to be before UpdateMouseMovingWindowNewFrame so the window is already offset and following the mouse on
|
|
// the detaching frame)
|
|
DockContextNewFrameUpdateUndocking(&g);
|
|
|
|
// Find hovered window
|
|
// (needs to be before UpdateMouseMovingWindowNewFrame so we fill g.HoveredWindowUnderMovingWindow on the mouse
|
|
// release frame)
|
|
UpdateHoveredWindowAndCaptureFlags();
|
|
|
|
// Handle user moving window with mouse (at the beginning of the frame to avoid input lag or sheering)
|
|
UpdateMouseMovingWindowNewFrame();
|
|
|
|
// Background darkening/whitening
|
|
if (GetTopMostPopupModal() != NULL || (g.NavWindowingTarget != NULL && g.NavWindowingHighlightAlpha > 0.0f))
|
|
g.DimBgRatio = ImMin(g.DimBgRatio + g.IO.DeltaTime * 6.0f, 1.0f);
|
|
else
|
|
g.DimBgRatio = ImMax(g.DimBgRatio - g.IO.DeltaTime * 10.0f, 0.0f);
|
|
|
|
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();
|
|
|
|
// Update legacy TAB focus
|
|
UpdateTabFocus();
|
|
|
|
// Mark all windows as not visible and compact unused memory.
|
|
IM_ASSERT(g.WindowsFocusOrder.Size <= g.Windows.Size);
|
|
const float memory_compact_start_time = (g.GcCompactAll || g.IO.ConfigMemoryCompactTimer < 0.0f)
|
|
? FLT_MAX
|
|
: (float)g.Time - g.IO.ConfigMemoryCompactTimer;
|
|
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;
|
|
|
|
// Garbage collect transient buffers of recently unused windows
|
|
if (!window->WasActive && !window->MemoryCompacted && window->LastTimeActive < memory_compact_start_time)
|
|
GcCompactTransientWindowBuffers(window);
|
|
}
|
|
|
|
// Garbage collect transient buffers of recently unused tables
|
|
for (int i = 0; i < g.TablesLastTimeActive.Size; i++)
|
|
if (g.TablesLastTimeActive[i] >= 0.0f && g.TablesLastTimeActive[i] < memory_compact_start_time)
|
|
TableGcCompactTransientBuffers(g.Tables.GetByIndex(i));
|
|
for (int i = 0; i < g.TablesTempDataStack.Size; i++)
|
|
if (g.TablesTempDataStack[i].LastTimeActive >= 0.0f &&
|
|
g.TablesTempDataStack[i].LastTimeActive < memory_compact_start_time)
|
|
TableGcCompactTransientBuffers(&g.TablesTempDataStack[i]);
|
|
if (g.GcCompactAll)
|
|
GcCompactTransientMiscBuffers();
|
|
g.GcCompactAll = false;
|
|
|
|
// Closing the focused window restore focus to the first active root window in descending z-order
|
|
if (g.NavWindow && !g.NavWindow->WasActive)
|
|
FocusTopMostWindowUnderOne(NULL, 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.
|
|
g.CurrentWindowStack.resize(0);
|
|
g.BeginPopupStack.resize(0);
|
|
g.ItemFlagsStack.resize(0);
|
|
g.ItemFlagsStack.push_back(ImGuiItemFlags_None);
|
|
g.GroupStack.resize(0);
|
|
|
|
// Docking
|
|
DockContextNewFrameUpdateDocking(&g);
|
|
|
|
// [DEBUG] Item picker tool - start with DebugStartItemPicker() - useful to visually select an item and break into
|
|
// its call-stack.
|
|
UpdateDebugToolItemPicker();
|
|
|
|
// Create implicit/fallback window - which 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.
|
|
// This fallback is particularly important as it avoid ImGui:: calls from crashing.
|
|
g.WithinFrameScopeWithImplicitWindow = true;
|
|
SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver);
|
|
Begin("Debug##Default");
|
|
IM_ASSERT(g.CurrentWindow->IsFallbackWindow == true);
|
|
|
|
CallContextHooks(&g, ImGuiContextHookType_NewFramePost);
|
|
}
|
|
|
|
// [DEBUG] Item picker tool - start with DebugStartItemPicker() - useful to visually select an item and break into its
|
|
// call-stack.
|
|
void ImGui::UpdateDebugToolItemPicker()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
g.DebugItemPickerBreakId = 0;
|
|
if (g.DebugItemPickerActive)
|
|
{
|
|
const ImGuiID hovered_id = g.HoveredIdPreviousFrame;
|
|
SetMouseCursor(ImGuiMouseCursor_Hand);
|
|
if (IsKeyPressedMap(ImGuiKey_Escape))
|
|
g.DebugItemPickerActive = false;
|
|
if (IsMouseClicked(0) && hovered_id)
|
|
{
|
|
g.DebugItemPickerBreakId = hovered_id;
|
|
g.DebugItemPickerActive = false;
|
|
}
|
|
SetNextWindowBgAlpha(0.60f);
|
|
BeginTooltip();
|
|
Text("HoveredId: 0x%08X", hovered_id);
|
|
Text("Press ESC to abort picking.");
|
|
TextColored(GetStyleColorVec4(hovered_id ? ImGuiCol_Text : ImGuiCol_TextDisabled),
|
|
"Click to break in debugger!");
|
|
EndTooltip();
|
|
}
|
|
}
|
|
|
|
void ImGui::Initialize(ImGuiContext* context)
|
|
{
|
|
ImGuiContext& g = *context;
|
|
IM_ASSERT(!g.Initialized && !g.SettingsLoaded);
|
|
|
|
// Add .ini handle for ImGuiWindow type
|
|
{
|
|
ImGuiSettingsHandler ini_handler;
|
|
ini_handler.TypeName = "Window";
|
|
ini_handler.TypeHash = ImHashStr("Window");
|
|
ini_handler.ClearAllFn = WindowSettingsHandler_ClearAll;
|
|
ini_handler.ReadOpenFn = WindowSettingsHandler_ReadOpen;
|
|
ini_handler.ReadLineFn = WindowSettingsHandler_ReadLine;
|
|
ini_handler.ApplyAllFn = WindowSettingsHandler_ApplyAll;
|
|
ini_handler.WriteAllFn = WindowSettingsHandler_WriteAll;
|
|
g.SettingsHandlers.push_back(ini_handler);
|
|
}
|
|
|
|
// Add .ini handle for ImGuiTable type
|
|
TableSettingsInstallHandler(context);
|
|
|
|
// Create default viewport
|
|
ImGuiViewportP* viewport = IM_NEW(ImGuiViewportP)();
|
|
viewport->ID = IMGUI_VIEWPORT_DEFAULT_ID;
|
|
viewport->Idx = 0;
|
|
viewport->PlatformWindowCreated = true;
|
|
viewport->Flags = ImGuiViewportFlags_OwnedByApp;
|
|
g.Viewports.push_back(viewport);
|
|
g.PlatformIO.Viewports.push_back(g.Viewports[0]);
|
|
|
|
# ifdef IMGUI_HAS_DOCK
|
|
// Initialize Docking
|
|
DockContextInitialize(&g);
|
|
# endif
|
|
|
|
g.Initialized = true;
|
|
}
|
|
|
|
// This function is merely here to free heap allocations.
|
|
void ImGui::Shutdown(ImGuiContext* context)
|
|
{
|
|
// The fonts atlas can be used prior to calling NewFrame(), so we clear it even if g.Initialized is FALSE (which
|
|
// would happen if we never called NewFrame)
|
|
ImGuiContext& g = *context;
|
|
if (g.IO.Fonts && g.FontAtlasOwnedByContext)
|
|
{
|
|
g.IO.Fonts->Locked = false;
|
|
IM_DELETE(g.IO.Fonts);
|
|
}
|
|
g.IO.Fonts = NULL;
|
|
|
|
// Cleanup of other data are conditional on actually having initialized Dear ImGui.
|
|
if (!g.Initialized)
|
|
return;
|
|
|
|
// 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(&g);
|
|
SaveIniSettingsToDisk(g.IO.IniFilename);
|
|
SetCurrentContext(backup_context);
|
|
}
|
|
|
|
// Destroy platform windows
|
|
ImGuiContext* backup_context = ImGui::GetCurrentContext();
|
|
SetCurrentContext(context);
|
|
DestroyPlatformWindows();
|
|
SetCurrentContext(backup_context);
|
|
|
|
// Shutdown extensions
|
|
DockContextShutdown(&g);
|
|
|
|
CallContextHooks(&g, ImGuiContextHookType_Shutdown);
|
|
|
|
// Clear everything else
|
|
g.Windows.clear_delete();
|
|
g.WindowsFocusOrder.clear();
|
|
g.WindowsTempSortBuffer.clear();
|
|
g.CurrentWindow = NULL;
|
|
g.CurrentWindowStack.clear();
|
|
g.WindowsById.Clear();
|
|
g.NavWindow = NULL;
|
|
g.HoveredWindow = g.HoveredWindowUnderMovingWindow = NULL;
|
|
g.ActiveIdWindow = g.ActiveIdPreviousFrameWindow = NULL;
|
|
g.MovingWindow = NULL;
|
|
g.ColorStack.clear();
|
|
g.StyleVarStack.clear();
|
|
g.FontStack.clear();
|
|
g.OpenPopupStack.clear();
|
|
g.BeginPopupStack.clear();
|
|
|
|
g.CurrentViewport = g.MouseViewport = g.MouseLastHoveredViewport = NULL;
|
|
g.Viewports.clear_delete();
|
|
|
|
g.TabBars.Clear();
|
|
g.CurrentTabBarStack.clear();
|
|
g.ShrinkWidthBuffer.clear();
|
|
|
|
g.Tables.Clear();
|
|
g.TablesTempDataStack.clear_destruct();
|
|
g.DrawChannelsTempMergeBuffer.clear();
|
|
|
|
g.ClipboardHandlerData.clear();
|
|
g.MenusIdSubmittedThisFrame.clear();
|
|
g.InputTextState.ClearFreeMemory();
|
|
|
|
g.SettingsWindows.clear();
|
|
g.SettingsHandlers.clear();
|
|
|
|
if (g.LogFile)
|
|
{
|
|
# ifndef IMGUI_DISABLE_TTY_FUNCTIONS
|
|
if (g.LogFile != stdout)
|
|
# endif
|
|
ImFileClose(g.LogFile);
|
|
g.LogFile = NULL;
|
|
}
|
|
g.LogBuffer.clear();
|
|
|
|
g.Initialized = false;
|
|
}
|
|
|
|
// FIXME: Add a more explicit sort order in the window structure.
|
|
static int IMGUI_CDECL ChildWindowComparer(const void* lhs, const void* rhs)
|
|
{
|
|
const ImGuiWindow* const a = *(const ImGuiWindow* const*)lhs;
|
|
const ImGuiWindow* const b = *(const ImGuiWindow* const*)rhs;
|
|
if (int d = (a->Flags & ImGuiWindowFlags_Popup) - (b->Flags & ImGuiWindowFlags_Popup))
|
|
return d;
|
|
if (int d = (a->Flags & ImGuiWindowFlags_Tooltip) - (b->Flags & ImGuiWindowFlags_Tooltip))
|
|
return d;
|
|
return (a->BeginOrderWithinParent - b->BeginOrderWithinParent);
|
|
}
|
|
|
|
static void AddWindowToSortBuffer(ImVector<ImGuiWindow*>* out_sorted_windows, ImGuiWindow* window)
|
|
{
|
|
out_sorted_windows->push_back(window);
|
|
if (window->Active)
|
|
{
|
|
int count = window->DC.ChildWindows.Size;
|
|
if (count > 1)
|
|
ImQsort(window->DC.ChildWindows.Data, (size_t)count, sizeof(ImGuiWindow*), ChildWindowComparer);
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
ImGuiWindow* child = window->DC.ChildWindows[i];
|
|
if (child->Active)
|
|
AddWindowToSortBuffer(out_sorted_windows, child);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void AddDrawListToDrawData(ImVector<ImDrawList*>* out_list, ImDrawList* draw_list)
|
|
{
|
|
// Remove trailing command if unused.
|
|
// Technically we could return directly instead of popping, but this make things looks neat in Metrics/Debugger
|
|
// window as well.
|
|
draw_list->_PopUnusedDrawCmd();
|
|
if (draw_list->CmdBuffer.Size == 0)
|
|
return;
|
|
|
|
// Draw list sanity check. Detect mismatch between PrimReserve() calls and incrementing _VtxCurrentIdx, _VtxWritePtr
|
|
// etc. May trigger for you if you are using PrimXXX functions incorrectly.
|
|
IM_ASSERT(draw_list->VtxBuffer.Size == 0 ||
|
|
draw_list->_VtxWritePtr == draw_list->VtxBuffer.Data + draw_list->VtxBuffer.Size);
|
|
IM_ASSERT(draw_list->IdxBuffer.Size == 0 ||
|
|
draw_list->_IdxWritePtr == draw_list->IdxBuffer.Data + draw_list->IdxBuffer.Size);
|
|
if (!(draw_list->Flags & ImDrawListFlags_AllowVtxOffset))
|
|
IM_ASSERT((int)draw_list->_VtxCurrentIdx == draw_list->VtxBuffer.Size);
|
|
|
|
// Check that draw_list doesn't use more vertices than indexable (default ImDrawIdx = unsigned short = 2 bytes = 64K
|
|
// vertices per ImDrawList = per window) If this assert triggers because you are drawing lots of stuff manually:
|
|
// - First, make sure you are coarse clipping yourself and not trying to draw many things outside visible bounds.
|
|
// Be mindful that the ImDrawList API doesn't filter vertices. Use the Metrics/Debugger window to inspect draw
|
|
// list contents.
|
|
// - If you want large meshes with more than 64K vertices, you can either:
|
|
// (A) Handle the ImDrawCmd::VtxOffset value in your renderer backend, and set 'io.BackendFlags |=
|
|
// ImGuiBackendFlags_RendererHasVtxOffset'.
|
|
// Most example backends already support this from 1.71. Pre-1.71 backends won't.
|
|
// Some graphics API such as GL ES 1/2 don't have a way to offset the starting vertex so it is not supported
|
|
// for them.
|
|
// (B) Or handle 32-bit indices in your renderer backend, and uncomment '#define ImDrawIdx unsigned int' line in
|
|
// imconfig.h.
|
|
// Most example backends already support this. For example, the OpenGL example code detect index size at
|
|
// compile-time:
|
|
// glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT :
|
|
// GL_UNSIGNED_INT, idx_buffer_offset);
|
|
// Your own engine or render API may use different parameters or function calls to specify index sizes.
|
|
// 2 and 4 bytes indices are generally supported by most graphics API.
|
|
// - If for some reason neither of those solutions works for you, a workaround is to call BeginChild()/EndChild()
|
|
// before reaching
|
|
// the 64K limit to split your draw commands in multiple draw lists.
|
|
if (sizeof(ImDrawIdx) == 2)
|
|
IM_ASSERT(draw_list->_VtxCurrentIdx < (1 << 16) &&
|
|
"Too many vertices in ImDrawList using 16-bit indices. Read comment above");
|
|
|
|
out_list->push_back(draw_list);
|
|
}
|
|
|
|
static void AddWindowToDrawData(ImGuiWindow* window, int layer)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiViewportP* viewport = window->Viewport;
|
|
g.IO.MetricsRenderWindows++;
|
|
AddDrawListToDrawData(&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(child, layer);
|
|
}
|
|
}
|
|
|
|
// Layer is locked for the root window, however child windows may use a different viewport (e.g. extruding menu)
|
|
static void AddRootWindowToDrawData(ImGuiWindow* window)
|
|
{
|
|
int layer = (window->Flags & ImGuiWindowFlags_Tooltip) ? 1 : 0;
|
|
AddWindowToDrawData(window, layer);
|
|
}
|
|
|
|
void ImDrawDataBuilder::FlattenIntoSingleLayer()
|
|
{
|
|
int n = Layers[0].Size;
|
|
int size = n;
|
|
for (int i = 1; i < IM_ARRAYSIZE(Layers); i++)
|
|
size += Layers[i].Size;
|
|
Layers[0].resize(size);
|
|
for (int layer_n = 1; layer_n < IM_ARRAYSIZE(Layers); layer_n++)
|
|
{
|
|
ImVector<ImDrawList*>& layer = Layers[layer_n];
|
|
if (layer.empty())
|
|
continue;
|
|
memcpy(&Layers[0][n], &layer[0], layer.Size * sizeof(ImDrawList*));
|
|
n += layer.Size;
|
|
layer.resize(0);
|
|
}
|
|
}
|
|
|
|
static void SetupViewportDrawData(ImGuiViewportP* viewport, ImVector<ImDrawList*>* draw_lists)
|
|
{
|
|
// When minimized, we report draw_data->DisplaySize as zero to be consistent with non-viewport mode,
|
|
// and to allow applications/backends to easily skip rendering.
|
|
// FIXME: Note that we however do NOT attempt to report "zero drawlist / vertices" into the ImDrawData structure.
|
|
// This is because the work has been done already, and its wasted! We should fix that and add optimizations for
|
|
// it earlier in the pipeline, rather than pretend to hide the data at the end of the pipeline.
|
|
const bool is_minimized = (viewport->Flags & ImGuiViewportFlags_Minimized) != 0;
|
|
|
|
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 = viewport->Pos;
|
|
draw_data->DisplaySize = is_minimized ? ImVec2(0.0f, 0.0f) : viewport->Size;
|
|
draw_data->FramebufferScale =
|
|
io.DisplayFramebufferScale; // FIXME-VIEWPORT: This may vary on a per-monitor/viewport basis?
|
|
draw_data->OwnerViewport = viewport;
|
|
for (int n = 0; n < draw_lists->Size; n++)
|
|
{
|
|
draw_data->TotalVtxCount += draw_lists->Data[n]->VtxBuffer.Size;
|
|
draw_data->TotalIdxCount += draw_lists->Data[n]->IdxBuffer.Size;
|
|
}
|
|
}
|
|
|
|
// Push a clipping rectangle for both ImGui logic (hit-testing etc.) and low-level ImDrawList rendering.
|
|
// - When using this function it is sane to ensure that float are perfectly rounded to integer values,
|
|
// so that e.g. (int)(max.x-min.x) in user's render produce correct result.
|
|
// - If the code here changes, may need to update code of functions like NextColumn() and PushColumnClipRect():
|
|
// some frequently called functions which to modify both channels and clipping simultaneously tend to use the
|
|
// more specialized SetWindowClipRectBeforeSetChannel() to avoid extraneous updates of underlying ImDrawCmds.
|
|
void ImGui::PushClipRect(const ImVec2& clip_rect_min,
|
|
const ImVec2& clip_rect_max,
|
|
bool intersect_with_current_clip_rect)
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
window->DrawList->PushClipRect(clip_rect_min, clip_rect_max, intersect_with_current_clip_rect);
|
|
window->ClipRect = window->DrawList->_ClipRectStack.back();
|
|
}
|
|
|
|
void ImGui::PopClipRect()
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
window->DrawList->PopClipRect();
|
|
window->ClipRect = window->DrawList->_ClipRectStack.back();
|
|
}
|
|
|
|
static ImGuiWindow* FindFrontMostVisibleChildWindow(ImGuiWindow* window)
|
|
{
|
|
for (int n = window->DC.ChildWindows.Size - 1; n >= 0; n--)
|
|
if (IsWindowActiveAndVisible(window->DC.ChildWindows[n]))
|
|
return FindFrontMostVisibleChildWindow(window->DC.ChildWindows[n]);
|
|
return window;
|
|
}
|
|
|
|
static void ImGui::EndFrameDrawDimmedBackgrounds()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
// Draw modal whitening background on _other_ viewports than the one the modal is one
|
|
ImGuiWindow* modal_window = GetTopMostPopupModal();
|
|
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.NavWindowingListWindow && viewport == g.NavWindowingListWindow->Viewport)
|
|
continue;
|
|
if (g.NavWindowingTargetAnim && viewport == g.NavWindowingTargetAnim->Viewport)
|
|
continue;
|
|
if (viewport->Window && modal_window && IsWindowAbove(viewport->Window, modal_window))
|
|
continue;
|
|
ImDrawList* draw_list = GetForegroundDrawList(viewport);
|
|
const ImU32 dim_bg_col =
|
|
GetColorU32(dim_bg_for_modal ? ImGuiCol_ModalWindowDimBg : ImGuiCol_NavWindowingDimBg, g.DimBgRatio);
|
|
draw_list->AddRectFilled(viewport->Pos, viewport->Pos + viewport->Size, dim_bg_col);
|
|
}
|
|
|
|
// Draw modal whitening background between CTRL-TAB list
|
|
if (dim_bg_for_window_list && g.NavWindowingTargetAnim->Active)
|
|
{
|
|
// Choose a draw list that will be front-most across all our children
|
|
// In the unlikely case that the window wasn't made active we can't rely on its drawlist and skip rendering
|
|
// all-together.
|
|
ImGuiWindow* window = g.NavWindowingTargetAnim;
|
|
ImDrawList* draw_list = FindFrontMostVisibleChildWindow(window->RootWindowDockTree)->DrawList;
|
|
draw_list->PushClipRectFullScreen();
|
|
|
|
// Docking: draw modal whitening background on other nodes of a same dock tree
|
|
// 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)
|
|
if (window->RootWindow->DockIsActive)
|
|
if (window->RootWindowDockTree != window->RootWindow)
|
|
RenderRectFilledWithHole(draw_list, window->RootWindowDockTree->Rect(), window->RootWindow->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->GetMainRect())) // 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();
|
|
}
|
|
}
|
|
|
|
// 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()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(g.Initialized);
|
|
|
|
// Don't process EndFrame() multiple times.
|
|
if (g.FrameCountEnded == g.FrameCount)
|
|
return;
|
|
IM_ASSERT(g.WithinFrameScope && "Forgot to call ImGui::NewFrame()?");
|
|
|
|
CallContextHooks(&g, ImGuiContextHookType_EndFramePre);
|
|
|
|
ErrorCheckEndFrameSanityChecks();
|
|
|
|
// Notify OS when our Input Method Editor cursor has moved (e.g. CJK inputs using Microsoft IME)
|
|
if (g.PlatformIO.Platform_SetImeInputPos &&
|
|
(g.PlatformImeLastPos.x == FLT_MAX || ImLengthSqr(g.PlatformImePos - g.PlatformImeLastPos) > 0.0001f))
|
|
if (g.PlatformImePosViewport && g.PlatformImePosViewport->PlatformWindowCreated)
|
|
{
|
|
g.PlatformIO.Platform_SetImeInputPos(g.PlatformImePosViewport, g.PlatformImePos);
|
|
g.PlatformImeLastPos = g.PlatformImePos;
|
|
g.PlatformImePosViewport = NULL;
|
|
}
|
|
|
|
// Hide implicit/fallback "Debug" window if it hasn't been used
|
|
g.WithinFrameScopeWithImplicitWindow = false;
|
|
if (g.CurrentWindow && !g.CurrentWindow->WriteAccessed)
|
|
g.CurrentWindow->Active = false;
|
|
End();
|
|
|
|
// Draw modal whitening background on _other_ viewports than the one the modal is one
|
|
EndFrameDrawDimmedBackgrounds();
|
|
|
|
// Update navigation: CTRL+Tab, wrap-around requests
|
|
NavEndFrame();
|
|
|
|
SetCurrentViewport(NULL, NULL);
|
|
|
|
// Drag and Drop: Elapse payload (if delivered, or if source stops being submitted)
|
|
if (g.DragDropActive)
|
|
{
|
|
bool is_delivered = g.DragDropPayload.Delivery;
|
|
bool is_elapsed = (g.DragDropPayload.DataFrameCount + 1 < g.FrameCount) &&
|
|
((g.DragDropSourceFlags & ImGuiDragDropFlags_SourceAutoExpirePayload) ||
|
|
!IsMouseDown(g.DragDropMouseButton));
|
|
if (is_delivered || is_elapsed)
|
|
ClearDragDrop();
|
|
}
|
|
|
|
// Drag and Drop: Fallback for source tooltip. This is not ideal but better than nothing.
|
|
if (g.DragDropActive && g.DragDropSourceFrameCount < g.FrameCount &&
|
|
!(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoPreviewTooltip))
|
|
{
|
|
g.DragDropWithinSource = true;
|
|
SetTooltip("...");
|
|
g.DragDropWithinSource = false;
|
|
}
|
|
|
|
// End frame
|
|
g.WithinFrameScope = false;
|
|
g.FrameCountEnded = g.FrameCount;
|
|
|
|
// Initiate moving window + handle left-click and right-click focus
|
|
UpdateMouseMovingWindowEndFrame();
|
|
|
|
// Update user-facing viewport list (g.Viewports -> g.PlatformIO.Viewports after filtering out some)
|
|
UpdateViewportsEndFrame();
|
|
|
|
// Sort the window list so that all child windows are after their parent
|
|
// We cannot do that on FocusWindow() because children may not exist yet
|
|
g.WindowsTempSortBuffer.resize(0);
|
|
g.WindowsTempSortBuffer.reserve(g.Windows.Size);
|
|
for (int i = 0; i != g.Windows.Size; i++)
|
|
{
|
|
ImGuiWindow* window = g.Windows[i];
|
|
if (window->Active &&
|
|
(window->Flags & ImGuiWindowFlags_ChildWindow)) // if a child is active its parent will add it
|
|
continue;
|
|
AddWindowToSortBuffer(&g.WindowsTempSortBuffer, window);
|
|
}
|
|
|
|
// 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.WindowsTempSortBuffer.Size);
|
|
g.Windows.swap(g.WindowsTempSortBuffer);
|
|
g.IO.MetricsActiveWindows = g.WindowsActiveCount;
|
|
|
|
// Unlock font atlas
|
|
g.IO.Fonts->Locked = false;
|
|
|
|
// Clear Input data for next frame
|
|
g.IO.MouseWheel = g.IO.MouseWheelH = 0.0f;
|
|
g.IO.InputQueueCharacters.resize(0);
|
|
g.IO.KeyModsPrev = g.IO.KeyMods; // doing it here is better than in NewFrame() as we'll tolerate backend writing to
|
|
// KeyMods. If we want to firmly disallow it we should detect it.
|
|
memset(g.IO.NavInputs, 0, sizeof(g.IO.NavInputs));
|
|
|
|
CallContextHooks(&g, ImGuiContextHookType_EndFramePost);
|
|
}
|
|
|
|
// Prepare the data for rendering so you can call GetDrawData()
|
|
// (As with anything within the ImGui:: namspace this doesn't touch your GPU or graphics API at all:
|
|
// it is the role of the ImGui_ImplXXXX_RenderDrawData() function provided by the renderer backend)
|
|
void ImGui::Render()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(g.Initialized);
|
|
|
|
if (g.FrameCountEnded != g.FrameCount)
|
|
EndFrame();
|
|
g.FrameCountRendered = g.FrameCount;
|
|
g.IO.MetricsRenderWindows = 0;
|
|
|
|
CallContextHooks(&g, ImGuiContextHookType_RenderPre);
|
|
|
|
// Add background ImDrawList (for each active viewport)
|
|
for (int n = 0; n != g.Viewports.Size; n++)
|
|
{
|
|
ImGuiViewportP* viewport = g.Viewports[n];
|
|
viewport->DrawDataBuilder.Clear();
|
|
if (viewport->DrawLists[0] != NULL)
|
|
AddDrawListToDrawData(&viewport->DrawDataBuilder.Layers[0], GetBackgroundDrawList(viewport));
|
|
}
|
|
|
|
// Add ImDrawList to render
|
|
ImGuiWindow* windows_to_render_top_most[2];
|
|
windows_to_render_top_most[0] =
|
|
(g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus))
|
|
? g.NavWindowingTarget->RootWindowDockTree
|
|
: NULL;
|
|
windows_to_render_top_most[1] = (g.NavWindowingTarget ? g.NavWindowingListWindow : NULL);
|
|
for (int n = 0; n != g.Windows.Size; n++)
|
|
{
|
|
ImGuiWindow* window = g.Windows[n];
|
|
IM_MSVC_WARNING_SUPPRESS(
|
|
6011); // Static Analysis false positive "warning C6011: Dereferencing NULL pointer 'window'"
|
|
if (IsWindowActiveAndVisible(window) && (window->Flags & ImGuiWindowFlags_ChildWindow) == 0 &&
|
|
window != windows_to_render_top_most[0] && window != windows_to_render_top_most[1])
|
|
AddRootWindowToDrawData(window);
|
|
}
|
|
for (int n = 0; n < IM_ARRAYSIZE(windows_to_render_top_most); n++)
|
|
if (windows_to_render_top_most[n] &&
|
|
IsWindowActiveAndVisible(windows_to_render_top_most[n])) // NavWindowingTarget is always temporarily
|
|
// displayed as the top-most window
|
|
AddRootWindowToDrawData(windows_to_render_top_most[n]);
|
|
|
|
ImVec2 mouse_cursor_offset, mouse_cursor_size, mouse_cursor_uv[4];
|
|
if (g.IO.MouseDrawCursor && g.MouseCursor != ImGuiMouseCursor_None)
|
|
g.IO.Fonts->GetMouseCursorTexData(g.MouseCursor, &mouse_cursor_offset, &mouse_cursor_size, &mouse_cursor_uv[0],
|
|
&mouse_cursor_uv[2]);
|
|
|
|
// 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();
|
|
|
|
// Draw software mouse cursor if requested by io.MouseDrawCursor flag
|
|
// (note we scale cursor by current viewport/monitor, however Windows 10 for its own hardware cursor seems to be
|
|
// using a different scale factor)
|
|
if (mouse_cursor_size.x > 0.0f && mouse_cursor_size.y > 0.0f)
|
|
{
|
|
float scale = g.Style.MouseCursorScale * viewport->DpiScale;
|
|
if (viewport->GetMainRect().Overlaps(ImRect(
|
|
g.IO.MousePos, g.IO.MousePos + ImVec2(mouse_cursor_size.x + 2, mouse_cursor_size.y + 2) * scale)))
|
|
RenderMouseCursor(GetForegroundDrawList(viewport), g.IO.MousePos, scale, g.MouseCursor, IM_COL32_WHITE,
|
|
IM_COL32_BLACK, IM_COL32(0, 0, 0, 48));
|
|
}
|
|
|
|
// Add foreground ImDrawList (for each active viewport)
|
|
if (viewport->DrawLists[1] != NULL)
|
|
AddDrawListToDrawData(&viewport->DrawDataBuilder.Layers[0], GetForegroundDrawList(viewport));
|
|
|
|
SetupViewportDrawData(viewport, &viewport->DrawDataBuilder.Layers[0]);
|
|
ImDrawData* draw_data = viewport->DrawData;
|
|
g.IO.MetricsRenderVertices += draw_data->TotalVtxCount;
|
|
g.IO.MetricsRenderIndices += draw_data->TotalIdxCount;
|
|
}
|
|
|
|
CallContextHooks(&g, ImGuiContextHookType_RenderPost);
|
|
}
|
|
|
|
// Calculate text size. Text can be multi-line. Optionally ignore text after a ## marker.
|
|
// CalcTextSize("") should return ImVec2(0.0f, g.FontSize)
|
|
ImVec2 ImGui::CalcTextSize(const char* text, const char* text_end, bool hide_text_after_double_hash, float wrap_width)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
const char* text_display_end;
|
|
if (hide_text_after_double_hash)
|
|
text_display_end = FindRenderedTextEnd(text, text_end); // Hide anything after a '##' string
|
|
else
|
|
text_display_end = text_end;
|
|
|
|
ImFont* font = g.Font;
|
|
const float font_size = g.FontSize;
|
|
if (text == text_display_end)
|
|
return ImVec2(0.0f, font_size);
|
|
ImVec2 text_size = font->CalcTextSizeA(font_size, FLT_MAX, wrap_width, text, text_display_end, NULL);
|
|
|
|
// Round
|
|
// FIXME: This has been here since Dec 2015 (7b0bf230) but down the line we want this out.
|
|
// FIXME: Investigate using ceilf or e.g.
|
|
// - https://git.musl-libc.org/cgit/musl/tree/src/math/ceilf.c
|
|
// - https://embarkstudios.github.io/rust-gpu/api/src/libm/math/ceilf.rs.html
|
|
text_size.x = IM_FLOOR(text_size.x + 0.99999f);
|
|
|
|
return text_size;
|
|
}
|
|
|
|
// Find window given position, search front-to-back
|
|
// FIXME: Note that we have an inconsequential lag here: OuterRectClipped is updated in Begin(), so windows moved
|
|
// programmatically with SetWindowPos() and not SetNextWindowPos() will have that rectangle lagging by a frame at the
|
|
// time FindHoveredWindow() is called, aka before the next Begin(). Moving window isn't affected.
|
|
static void FindHoveredWindow()
|
|
{
|
|
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_NoMouseInputs))
|
|
hovered_window = g.MovingWindow;
|
|
|
|
ImVec2 padding_regular = g.Style.TouchExtraPadding;
|
|
ImVec2 padding_for_resize = g.IO.ConfigWindowsResizeFromEdges ? g.WindowsHoverPadding : padding_regular;
|
|
for (int i = g.Windows.Size - 1; i >= 0; i--)
|
|
{
|
|
ImGuiWindow* window = g.Windows[i];
|
|
IM_MSVC_WARNING_SUPPRESS(28182); // [Static Analyzer] Dereferencing NULL pointer.
|
|
if (!window->Active || window->Hidden)
|
|
continue;
|
|
if (window->Flags & ImGuiWindowFlags_NoMouseInputs)
|
|
continue;
|
|
IM_ASSERT(window->Viewport);
|
|
if (window->Viewport != g.MouseViewport)
|
|
continue;
|
|
|
|
// Using the clipped AABB, a child window will typically be clipped by its parent (not always)
|
|
ImRect bb(window->OuterRectClipped);
|
|
if (window->Flags &
|
|
(ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize))
|
|
bb.Expand(padding_regular);
|
|
else
|
|
bb.Expand(padding_for_resize);
|
|
if (!bb.Contains(g.IO.MousePos))
|
|
continue;
|
|
|
|
// Support for one rectangular hole in any given window
|
|
// FIXME: Consider generalizing hit-testing override (with more generic data, callback, etc.) (#1512)
|
|
if (window->HitTestHoleSize.x != 0)
|
|
{
|
|
ImVec2 hole_pos(window->Pos.x + (float)window->HitTestHoleOffset.x,
|
|
window->Pos.y + (float)window->HitTestHoleOffset.y);
|
|
ImVec2 hole_size((float)window->HitTestHoleSize.x, (float)window->HitTestHoleSize.y);
|
|
if (ImRect(hole_pos, hole_pos + hole_size).Contains(g.IO.MousePos))
|
|
continue;
|
|
}
|
|
|
|
if (hovered_window == NULL)
|
|
hovered_window = window;
|
|
IM_MSVC_WARNING_SUPPRESS(28182); // [Static Analyzer] Dereferencing NULL pointer.
|
|
if (hovered_window_ignoring_moving_window == NULL &&
|
|
(!g.MovingWindow || window->RootWindowDockTree != g.MovingWindow->RootWindowDockTree))
|
|
hovered_window_ignoring_moving_window = window;
|
|
if (hovered_window && hovered_window_ignoring_moving_window)
|
|
break;
|
|
}
|
|
|
|
g.HoveredWindow = hovered_window;
|
|
g.HoveredWindowUnderMovingWindow = hovered_window_ignoring_moving_window;
|
|
|
|
if (g.MovingWindow)
|
|
g.MovingWindow->Viewport = moving_window_viewport;
|
|
}
|
|
|
|
// Test if mouse cursor is hovering given rectangle
|
|
// NB- Rectangle is clipped by our current clip setting
|
|
// NB- Expand the rectangle to be generous on imprecise inputs systems (g.Style.TouchExtraPadding)
|
|
bool ImGui::IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool clip)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
// Clip
|
|
ImRect rect_clipped(r_min, r_max);
|
|
if (clip)
|
|
rect_clipped.ClipWith(g.CurrentWindow->ClipRect);
|
|
|
|
// Expand for touch input
|
|
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->GetMainRect().Overlaps(rect_clipped))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
int ImGui::GetKeyIndex(ImGuiKey imgui_key)
|
|
{
|
|
IM_ASSERT(imgui_key >= 0 && imgui_key < ImGuiKey_COUNT);
|
|
ImGuiContext& g = *GImGui;
|
|
return g.IO.KeyMap[imgui_key];
|
|
}
|
|
|
|
// Note that dear imgui doesn't know the semantic of each entry of io.KeysDown[]!
|
|
// Use your own indices/enums according to how your backend/engine stored them into io.KeysDown[]!
|
|
bool ImGui::IsKeyDown(int user_key_index)
|
|
{
|
|
if (user_key_index < 0)
|
|
return false;
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(g.IO.KeysDown));
|
|
return g.IO.KeysDown[user_key_index];
|
|
}
|
|
|
|
// t0 = previous time (e.g.: g.Time - g.IO.DeltaTime)
|
|
// t1 = current time (e.g.: g.Time)
|
|
// An event is triggered at:
|
|
// t = 0.0f t = repeat_delay, t = repeat_delay + repeat_rate*N
|
|
int ImGui::CalcTypematicRepeatAmount(float t0, float t1, float repeat_delay, float repeat_rate)
|
|
{
|
|
if (t1 == 0.0f)
|
|
return 1;
|
|
if (t0 >= t1)
|
|
return 0;
|
|
if (repeat_rate <= 0.0f)
|
|
return (t0 < repeat_delay) && (t1 >= repeat_delay);
|
|
const int count_t0 = (t0 < repeat_delay) ? -1 : (int)((t0 - repeat_delay) / repeat_rate);
|
|
const int count_t1 = (t1 < repeat_delay) ? -1 : (int)((t1 - repeat_delay) / repeat_rate);
|
|
const int count = count_t1 - count_t0;
|
|
return count;
|
|
}
|
|
|
|
int ImGui::GetKeyPressedAmount(int key_index, float repeat_delay, float repeat_rate)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (key_index < 0)
|
|
return 0;
|
|
IM_ASSERT(key_index >= 0 && key_index < IM_ARRAYSIZE(g.IO.KeysDown));
|
|
const float t = g.IO.KeysDownDuration[key_index];
|
|
return CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, repeat_delay, repeat_rate);
|
|
}
|
|
|
|
bool ImGui::IsKeyPressed(int user_key_index, bool repeat)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (user_key_index < 0)
|
|
return false;
|
|
IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(g.IO.KeysDown));
|
|
const float t = g.IO.KeysDownDuration[user_key_index];
|
|
if (t == 0.0f)
|
|
return true;
|
|
if (repeat && t > g.IO.KeyRepeatDelay)
|
|
return GetKeyPressedAmount(user_key_index, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0;
|
|
return false;
|
|
}
|
|
|
|
bool ImGui::IsKeyReleased(int user_key_index)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (user_key_index < 0)
|
|
return false;
|
|
IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(g.IO.KeysDown));
|
|
return g.IO.KeysDownDurationPrev[user_key_index] >= 0.0f && !g.IO.KeysDown[user_key_index];
|
|
}
|
|
|
|
bool ImGui::IsMouseDown(ImGuiMouseButton button)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
|
|
return g.IO.MouseDown[button];
|
|
}
|
|
|
|
bool ImGui::IsMouseClicked(ImGuiMouseButton button, bool repeat)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
|
|
const float t = g.IO.MouseDownDuration[button];
|
|
if (t == 0.0f)
|
|
return true;
|
|
|
|
if (repeat && t > g.IO.KeyRepeatDelay)
|
|
{
|
|
// FIXME: 2019/05/03: Our old repeat code was wrong here and led to doubling the repeat rate, which made it an
|
|
// ok rate for repeat on mouse hold.
|
|
int amount = CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate * 0.50f);
|
|
if (amount > 0)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ImGui::IsMouseReleased(ImGuiMouseButton button)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
|
|
return g.IO.MouseReleased[button];
|
|
}
|
|
|
|
bool ImGui::IsMouseDoubleClicked(ImGuiMouseButton button)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
|
|
return g.IO.MouseDoubleClicked[button];
|
|
}
|
|
|
|
// Return if a mouse click/drag went past the given threshold. Valid to call during the MouseReleased frame.
|
|
// [Internal] This doesn't test if the button is pressed
|
|
bool ImGui::IsMouseDragPastThreshold(ImGuiMouseButton button, float lock_threshold)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
|
|
if (lock_threshold < 0.0f)
|
|
lock_threshold = g.IO.MouseDragThreshold;
|
|
return g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold;
|
|
}
|
|
|
|
bool ImGui::IsMouseDragging(ImGuiMouseButton button, float lock_threshold)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
|
|
if (!g.IO.MouseDown[button])
|
|
return false;
|
|
return IsMouseDragPastThreshold(button, lock_threshold);
|
|
}
|
|
|
|
ImVec2 ImGui::GetMousePos()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
return g.IO.MousePos;
|
|
}
|
|
|
|
// NB: prefer to call right after BeginPopup(). At the time Selectable/MenuItem is activated, the popup is already
|
|
// closed!
|
|
ImVec2 ImGui::GetMousePosOnOpeningCurrentPopup()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (g.BeginPopupStack.Size > 0)
|
|
return g.OpenPopupStack[g.BeginPopupStack.Size - 1].OpenMousePos;
|
|
return g.IO.MousePos;
|
|
}
|
|
|
|
// We typically use ImVec2(-FLT_MAX,-FLT_MAX) to denote an invalid mouse position.
|
|
bool ImGui::IsMousePosValid(const ImVec2* mouse_pos)
|
|
{
|
|
// The assert is only to silence a false-positive in XCode Static Analysis.
|
|
// Because GImGui is not dereferenced in every code path, the static analyzer assume that it may be NULL (which it
|
|
// doesn't for other functions).
|
|
IM_ASSERT(GImGui != NULL);
|
|
const float MOUSE_INVALID = -256000.0f;
|
|
ImVec2 p = mouse_pos ? *mouse_pos : GImGui->IO.MousePos;
|
|
return p.x >= MOUSE_INVALID && p.y >= MOUSE_INVALID;
|
|
}
|
|
|
|
bool ImGui::IsAnyMouseDown()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
for (int n = 0; n < IM_ARRAYSIZE(g.IO.MouseDown); n++)
|
|
if (g.IO.MouseDown[n])
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
// Return the delta from the initial clicking position while the mouse button is clicked or was just released.
|
|
// This is locked and return 0.0f until the mouse moves past a distance threshold at least once.
|
|
// NB: This is only valid if IsMousePosValid(). backends in theory should always keep mouse position valid when dragging
|
|
// even outside the client window.
|
|
ImVec2 ImGui::GetMouseDragDelta(ImGuiMouseButton button, float lock_threshold)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
|
|
if (lock_threshold < 0.0f)
|
|
lock_threshold = g.IO.MouseDragThreshold;
|
|
if (g.IO.MouseDown[button] || g.IO.MouseReleased[button])
|
|
if (g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold)
|
|
if (IsMousePosValid(&g.IO.MousePos) && IsMousePosValid(&g.IO.MouseClickedPos[button]))
|
|
return g.IO.MousePos - g.IO.MouseClickedPos[button];
|
|
return ImVec2(0.0f, 0.0f);
|
|
}
|
|
|
|
void ImGui::ResetMouseDragDelta(ImGuiMouseButton button)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
|
|
// NB: We don't need to reset g.IO.MouseDragMaxDistanceSqr
|
|
g.IO.MouseClickedPos[button] = g.IO.MousePos;
|
|
}
|
|
|
|
ImGuiMouseCursor ImGui::GetMouseCursor()
|
|
{
|
|
return GImGui->MouseCursor;
|
|
}
|
|
|
|
void ImGui::SetMouseCursor(ImGuiMouseCursor cursor_type)
|
|
{
|
|
GImGui->MouseCursor = cursor_type;
|
|
}
|
|
|
|
void ImGui::CaptureKeyboardFromApp(bool capture)
|
|
{
|
|
GImGui->WantCaptureKeyboardNextFrame = capture ? 1 : 0;
|
|
}
|
|
|
|
void ImGui::CaptureMouseFromApp(bool capture)
|
|
{
|
|
GImGui->WantCaptureMouseNextFrame = capture ? 1 : 0;
|
|
}
|
|
|
|
bool ImGui::IsItemActive()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (g.ActiveId)
|
|
return g.ActiveId == g.LastItemData.ID;
|
|
return false;
|
|
}
|
|
|
|
bool ImGui::IsItemActivated()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (g.ActiveId)
|
|
if (g.ActiveId == g.LastItemData.ID && g.ActiveIdPreviousFrame != g.LastItemData.ID)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool ImGui::IsItemDeactivated()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HasDeactivated)
|
|
return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Deactivated) != 0;
|
|
return (g.ActiveIdPreviousFrame == g.LastItemData.ID && g.ActiveIdPreviousFrame != 0 &&
|
|
g.ActiveId != g.LastItemData.ID);
|
|
}
|
|
|
|
bool ImGui::IsItemDeactivatedAfterEdit()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
return IsItemDeactivated() &&
|
|
(g.ActiveIdPreviousFrameHasBeenEditedBefore || (g.ActiveId == 0 && g.ActiveIdHasBeenEditedBefore));
|
|
}
|
|
|
|
// == GetItemID() == GetFocusID()
|
|
bool ImGui::IsItemFocused()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (g.NavId != g.LastItemData.ID || g.NavId == 0)
|
|
return false;
|
|
|
|
// Special handling for the dummy item after Begin() which represent the title bar or tab.
|
|
// When the window is collapsed (SkipItems==true) that last item will never be overwritten so we need to detect the
|
|
// case.
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
if (g.LastItemData.ID == window->ID && window->WriteAccessed)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
// Important: this can be useful but it is NOT equivalent to the behavior of e.g.Button()!
|
|
// Most widgets have specific reactions based on mouse-up/down state, mouse position etc.
|
|
bool ImGui::IsItemClicked(ImGuiMouseButton mouse_button)
|
|
{
|
|
return IsMouseClicked(mouse_button) && IsItemHovered(ImGuiHoveredFlags_None);
|
|
}
|
|
|
|
bool ImGui::IsItemToggledOpen()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_ToggledOpen) ? true : false;
|
|
}
|
|
|
|
bool ImGui::IsItemToggledSelection()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_ToggledSelection) ? true : false;
|
|
}
|
|
|
|
bool ImGui::IsAnyItemHovered()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
return g.HoveredId != 0 || g.HoveredIdPreviousFrame != 0;
|
|
}
|
|
|
|
bool ImGui::IsAnyItemActive()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
return g.ActiveId != 0;
|
|
}
|
|
|
|
bool ImGui::IsAnyItemFocused()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
return g.NavId != 0 && !g.NavDisableHighlight;
|
|
}
|
|
|
|
bool ImGui::IsItemVisible()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
return g.CurrentWindow->ClipRect.Overlaps(g.LastItemData.Rect);
|
|
}
|
|
|
|
bool ImGui::IsItemEdited()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Edited) != 0;
|
|
}
|
|
|
|
// Allow last item to be overlapped by a subsequent item. Both may be activated during the same frame before the later
|
|
// one takes priority.
|
|
// FIXME: Although this is exposed, its interaction and ideal idiom with using ImGuiButtonFlags_AllowItemOverlap flag
|
|
// are extremely confusing, need rework.
|
|
void ImGui::SetItemAllowOverlap()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiID id = g.LastItemData.ID;
|
|
if (g.HoveredId == id)
|
|
g.HoveredIdAllowOverlap = true;
|
|
if (g.ActiveId == id)
|
|
g.ActiveIdAllowOverlap = true;
|
|
}
|
|
|
|
void ImGui::SetItemUsingMouseWheel()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiID id = g.LastItemData.ID;
|
|
if (g.HoveredId == id)
|
|
g.HoveredIdUsingMouseWheel = true;
|
|
if (g.ActiveId == id)
|
|
g.ActiveIdUsingMouseWheel = true;
|
|
}
|
|
|
|
void ImGui::SetActiveIdUsingNavAndKeys()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(g.ActiveId != 0);
|
|
g.ActiveIdUsingNavDirMask = ~(ImU32)0;
|
|
g.ActiveIdUsingNavInputMask = ~(ImU32)0;
|
|
g.ActiveIdUsingKeyInputMask = ~(ImU64)0;
|
|
NavMoveRequestCancel();
|
|
}
|
|
|
|
ImVec2 ImGui::GetItemRectMin()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
return g.LastItemData.Rect.Min;
|
|
}
|
|
|
|
ImVec2 ImGui::GetItemRectMax()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
return g.LastItemData.Rect.Max;
|
|
}
|
|
|
|
ImVec2 ImGui::GetItemRectSize()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
return g.LastItemData.Rect.GetSize();
|
|
}
|
|
|
|
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 | ImGuiWindowFlags_NoDocking;
|
|
flags |= (parent_window->Flags & ImGuiWindowFlags_NoMove); // Inherit the NoMove flag
|
|
|
|
// Size
|
|
const ImVec2 content_avail = GetContentRegionAvail();
|
|
ImVec2 size = ImFloor(size_arg);
|
|
const int auto_fit_axises =
|
|
((size.x == 0.0f) ? (1 << ImGuiAxis_X) : 0x00) | ((size.y == 0.0f) ? (1 << ImGuiAxis_Y) : 0x00);
|
|
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);
|
|
SetNextWindowSize(size);
|
|
|
|
// 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.
|
|
if (name)
|
|
ImFormatString(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), "%s/%s_%08X", parent_window->Name, name, id);
|
|
else
|
|
ImFormatString(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), "%s/%08X", parent_window->Name, id);
|
|
|
|
const float backup_border_size = g.Style.ChildBorderSize;
|
|
if (!border)
|
|
g.Style.ChildBorderSize = 0.0f;
|
|
bool ret = Begin(g.TempBuffer, NULL, flags);
|
|
g.Style.ChildBorderSize = backup_border_size;
|
|
|
|
ImGuiWindow* child_window = g.CurrentWindow;
|
|
child_window->ChildId = id;
|
|
child_window->AutoFitChildAxises = (ImS8)auto_fit_axises;
|
|
|
|
// Set the cursor to handle case where the user called SetNextWindowPos()+BeginChild() manually.
|
|
// While this is not really documented/defined, it seems that the expected thing to do.
|
|
if (child_window->BeginCount == 1)
|
|
parent_window->DC.CursorPos = child_window->Pos;
|
|
|
|
// Process navigation-in immediately so NavInit can run on first frame
|
|
if (g.NavActivateId == id && !(flags & ImGuiWindowFlags_NavFlattened) &&
|
|
(child_window->DC.NavLayersActiveMask != 0 || child_window->DC.NavHasScroll))
|
|
{
|
|
FocusWindow(child_window);
|
|
NavInitWindow(child_window, false);
|
|
SetActiveID(
|
|
id + 1,
|
|
child_window); // Steal ActiveId with another arbitrary id so that key-press won't activate child item
|
|
g.ActiveIdSource = ImGuiInputSource_Nav;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool ImGui::BeginChild(const char* str_id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags)
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
return BeginChildEx(str_id, window->GetID(str_id), size_arg, border, extra_flags);
|
|
}
|
|
|
|
bool ImGui::BeginChild(ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags)
|
|
{
|
|
IM_ASSERT(id != 0);
|
|
return BeginChildEx(NULL, id, size_arg, border, extra_flags);
|
|
}
|
|
|
|
void ImGui::EndChild()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
|
|
IM_ASSERT(g.WithinEndChild == false);
|
|
IM_ASSERT(window->Flags & ImGuiWindowFlags_ChildWindow); // Mismatched BeginChild()/EndChild() calls
|
|
|
|
g.WithinEndChild = true;
|
|
if (window->BeginCount > 1)
|
|
{
|
|
End();
|
|
}
|
|
else
|
|
{
|
|
ImVec2 sz = window->Size;
|
|
if (window->AutoFitChildAxises &
|
|
(1 << ImGuiAxis_X)) // Arbitrary minimum zero-ish child size of 4.0f causes less trouble than a 0.0f
|
|
sz.x = ImMax(4.0f, sz.x);
|
|
if (window->AutoFitChildAxises & (1 << ImGuiAxis_Y))
|
|
sz.y = ImMax(4.0f, sz.y);
|
|
End();
|
|
|
|
ImGuiWindow* parent_window = g.CurrentWindow;
|
|
ImRect bb(parent_window->DC.CursorPos, parent_window->DC.CursorPos + sz);
|
|
ItemSize(sz);
|
|
if ((window->DC.NavLayersActiveMask != 0 || window->DC.NavHasScroll) &&
|
|
!(window->Flags & ImGuiWindowFlags_NavFlattened))
|
|
{
|
|
ItemAdd(bb, window->ChildId);
|
|
RenderNavHighlight(bb, window->ChildId);
|
|
|
|
// When browsing a window that has no activable items (scroll only) we keep a highlight on the child
|
|
if (window->DC.NavLayersActiveMask == 0 && window == g.NavWindow)
|
|
RenderNavHighlight(ImRect(bb.Min - ImVec2(2, 2), bb.Max + ImVec2(2, 2)), g.NavId,
|
|
ImGuiNavHighlightFlags_TypeThin);
|
|
}
|
|
else
|
|
{
|
|
// Not navigable into
|
|
ItemAdd(bb, 0);
|
|
}
|
|
if (g.HoveredWindow == window)
|
|
g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow;
|
|
}
|
|
g.WithinEndChild = false;
|
|
g.LogLinePosY = -FLT_MAX; // To enforce a carriage return
|
|
}
|
|
|
|
// Helper to create a child window / scrolling region that looks like a normal widget frame.
|
|
bool ImGui::BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags extra_flags)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
const ImGuiStyle& style = g.Style;
|
|
PushStyleColor(ImGuiCol_ChildBg, style.Colors[ImGuiCol_FrameBg]);
|
|
PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding);
|
|
PushStyleVar(ImGuiStyleVar_ChildBorderSize, style.FrameBorderSize);
|
|
PushStyleVar(ImGuiStyleVar_WindowPadding, style.FramePadding);
|
|
bool ret =
|
|
BeginChild(id, size, true, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysUseWindowPadding | extra_flags);
|
|
PopStyleVar(3);
|
|
PopStyleColor();
|
|
return ret;
|
|
}
|
|
|
|
void ImGui::EndChildFrame()
|
|
{
|
|
EndChild();
|
|
}
|
|
|
|
static void SetWindowConditionAllowFlags(ImGuiWindow* window, ImGuiCond flags, bool enabled)
|
|
{
|
|
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)
|
|
{
|
|
ImGuiID id = ImHashStr(name);
|
|
return FindWindowByID(id);
|
|
}
|
|
|
|
static void ApplyWindowSettings(ImGuiWindow* window, ImGuiWindowSettings* settings)
|
|
{
|
|
const ImGuiViewport* main_viewport = ImGui::GetMainViewport();
|
|
window->ViewportPos = main_viewport->Pos;
|
|
if (settings->ViewportId)
|
|
{
|
|
window->ViewportId = settings->ViewportId;
|
|
window->ViewportPos = ImVec2(settings->ViewportPos.x, settings->ViewportPos.y);
|
|
}
|
|
window->Pos = ImFloor(ImVec2(settings->Pos.x + window->ViewportPos.x, settings->Pos.y + window->ViewportPos.y));
|
|
if (settings->Size.x > 0 && settings->Size.y > 0)
|
|
window->Size = window->SizeFull = ImFloor(ImVec2(settings->Size.x, settings->Size.y));
|
|
window->Collapsed = settings->Collapsed;
|
|
window->DockId = settings->DockId;
|
|
window->DockOrder = settings->DockOrder;
|
|
}
|
|
|
|
static ImGuiWindow* CreateNewWindow(const char* name, ImGuiWindowFlags flags)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
// IMGUI_DEBUG_LOG("CreateNewWindow '%s', flags = 0x%08X\n", name, flags);
|
|
|
|
// Create window the first time
|
|
ImGuiWindow* window = IM_NEW(ImGuiWindow)(&g, name);
|
|
window->Flags = flags;
|
|
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.
|
|
const ImGuiViewport* main_viewport = ImGui::GetMainViewport();
|
|
window->Pos = main_viewport->Pos + ImVec2(60, 60);
|
|
window->ViewportPos = main_viewport->Pos;
|
|
|
|
// User can disable loading and saving of settings. Tooltip and child windows also don't store settings.
|
|
if (!(flags & ImGuiWindowFlags_NoSavedSettings))
|
|
if (ImGuiWindowSettings* settings = ImGui::FindWindowSettings(window->ID))
|
|
{
|
|
// Retrieve settings from .ini file
|
|
window->SettingsOffset = g.SettingsWindows.offset_from_ptr(settings);
|
|
SetWindowConditionAllowFlags(window, ImGuiCond_FirstUseEver, false);
|
|
ApplyWindowSettings(window, settings);
|
|
}
|
|
window->DC.CursorStartPos = window->DC.CursorMaxPos =
|
|
window->Pos; // So first call to CalcContentSize() doesn't return crazy values
|
|
|
|
if ((flags & ImGuiWindowFlags_AlwaysAutoResize) != 0)
|
|
{
|
|
window->AutoFitFramesX = window->AutoFitFramesY = 2;
|
|
window->AutoFitOnlyGrows = false;
|
|
}
|
|
else
|
|
{
|
|
if (window->Size.x <= 0.0f)
|
|
window->AutoFitFramesX = 2;
|
|
if (window->Size.y <= 0.0f)
|
|
window->AutoFitFramesY = 2;
|
|
window->AutoFitOnlyGrows = (window->AutoFitFramesX > 0) || (window->AutoFitFramesY > 0);
|
|
}
|
|
|
|
if (!(flags & ImGuiWindowFlags_ChildWindow))
|
|
{
|
|
g.WindowsFocusOrder.push_back(window);
|
|
window->FocusOrder = (short)(g.WindowsFocusOrder.Size - 1);
|
|
}
|
|
|
|
if (flags & ImGuiWindowFlags_NoBringToFrontOnFocus)
|
|
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 CalcWindowSizeAfterConstraint(ImGuiWindow* window, const ImVec2& size_desired)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImVec2 new_size = size_desired;
|
|
if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint)
|
|
{
|
|
// Using -1,-1 on either X/Y axis to preserve the current size.
|
|
ImRect cr = g.NextWindowData.SizeConstraintRect;
|
|
new_size.x = (cr.Min.x >= 0 && cr.Max.x >= 0) ? ImClamp(new_size.x, cr.Min.x, cr.Max.x) : window->SizeFull.x;
|
|
new_size.y = (cr.Min.y >= 0 && cr.Max.y >= 0) ? ImClamp(new_size.y, cr.Min.y, cr.Max.y) : window->SizeFull.y;
|
|
if (g.NextWindowData.SizeCallback)
|
|
{
|
|
ImGuiSizeCallbackData data;
|
|
data.UserData = g.NextWindowData.SizeCallbackUserData;
|
|
data.Pos = window->Pos;
|
|
data.CurrentSize = window->SizeFull;
|
|
data.DesiredSize = new_size;
|
|
g.NextWindowData.SizeCallback(&data);
|
|
new_size = data.DesiredSize;
|
|
}
|
|
new_size.x = IM_FLOOR(new_size.x);
|
|
new_size.y = IM_FLOOR(new_size.y);
|
|
}
|
|
|
|
// Minimum size
|
|
if (!(window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_AlwaysAutoResize)))
|
|
{
|
|
ImGuiWindow* window_for_height = GetWindowForTitleAndMenuHeight(window);
|
|
const float decoration_up_height = window_for_height->TitleBarHeight() + window_for_height->MenuBarHeight();
|
|
new_size = ImMax(new_size, g.Style.WindowMinSize);
|
|
new_size.y = ImMax(new_size.y,
|
|
decoration_up_height +
|
|
ImMax(0.0f, g.Style.WindowRounding - 1.0f)); // Reduce artifacts with very small windows
|
|
}
|
|
return new_size;
|
|
}
|
|
|
|
static void CalcWindowContentSizes(ImGuiWindow* window, ImVec2* content_size_current, ImVec2* content_size_ideal)
|
|
{
|
|
bool preserve_old_content_sizes = false;
|
|
if (window->Collapsed && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0)
|
|
preserve_old_content_sizes = true;
|
|
else if (window->Hidden && window->HiddenFramesCannotSkipItems == 0 && window->HiddenFramesCanSkipItems > 0)
|
|
preserve_old_content_sizes = true;
|
|
if (preserve_old_content_sizes)
|
|
{
|
|
*content_size_current = window->ContentSize;
|
|
*content_size_ideal = window->ContentSizeIdeal;
|
|
return;
|
|
}
|
|
|
|
content_size_current->x = (window->ContentSizeExplicit.x != 0.0f)
|
|
? window->ContentSizeExplicit.x
|
|
: IM_FLOOR(window->DC.CursorMaxPos.x - window->DC.CursorStartPos.x);
|
|
content_size_current->y = (window->ContentSizeExplicit.y != 0.0f)
|
|
? window->ContentSizeExplicit.y
|
|
: IM_FLOOR(window->DC.CursorMaxPos.y - window->DC.CursorStartPos.y);
|
|
content_size_ideal->x =
|
|
(window->ContentSizeExplicit.x != 0.0f)
|
|
? window->ContentSizeExplicit.x
|
|
: IM_FLOOR(ImMax(window->DC.CursorMaxPos.x, window->DC.IdealMaxPos.x) - window->DC.CursorStartPos.x);
|
|
content_size_ideal->y =
|
|
(window->ContentSizeExplicit.y != 0.0f)
|
|
? window->ContentSizeExplicit.y
|
|
: IM_FLOOR(ImMax(window->DC.CursorMaxPos.y, window->DC.IdealMaxPos.y) - window->DC.CursorStartPos.y);
|
|
}
|
|
|
|
static ImVec2 CalcWindowAutoFitSize(ImGuiWindow* window, const ImVec2& size_contents)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiStyle& style = g.Style;
|
|
const float decoration_up_height = window->TitleBarHeight() + window->MenuBarHeight();
|
|
ImVec2 size_pad = window->WindowPadding * 2.0f;
|
|
ImVec2 size_desired = size_contents + size_pad + ImVec2(0.0f, decoration_up_height);
|
|
if (window->Flags & ImGuiWindowFlags_Tooltip)
|
|
{
|
|
// Tooltip always resize
|
|
return size_desired;
|
|
}
|
|
else
|
|
{
|
|
// 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));
|
|
|
|
// FIXME-VIEWPORT-WORKAREA: May want to use GetWorkSize() instead of Size depending on the type of windows?
|
|
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_desired, size_min, ImMax(size_min, avail_size - 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 = CalcWindowSizeAfterConstraint(window, size_auto_fit);
|
|
bool will_have_scrollbar_x = (size_auto_fit_after_constraint.x - size_pad.x - 0.0f < size_contents.x &&
|
|
!(window->Flags & ImGuiWindowFlags_NoScrollbar) &&
|
|
(window->Flags & ImGuiWindowFlags_HorizontalScrollbar)) ||
|
|
(window->Flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar);
|
|
bool will_have_scrollbar_y =
|
|
(size_auto_fit_after_constraint.y - size_pad.y - decoration_up_height < size_contents.y &&
|
|
!(window->Flags & ImGuiWindowFlags_NoScrollbar)) ||
|
|
(window->Flags & ImGuiWindowFlags_AlwaysVerticalScrollbar);
|
|
if (will_have_scrollbar_x)
|
|
size_auto_fit.y += style.ScrollbarSize;
|
|
if (will_have_scrollbar_y)
|
|
size_auto_fit.x += style.ScrollbarSize;
|
|
return size_auto_fit;
|
|
}
|
|
}
|
|
|
|
ImVec2 ImGui::CalcWindowNextAutoFitSize(ImGuiWindow* window)
|
|
{
|
|
ImVec2 size_contents_current;
|
|
ImVec2 size_contents_ideal;
|
|
CalcWindowContentSizes(window, &size_contents_current, &size_contents_ideal);
|
|
ImVec2 size_auto_fit = CalcWindowAutoFitSize(window, size_contents_ideal);
|
|
ImVec2 size_final = CalcWindowSizeAfterConstraint(window, size_auto_fit);
|
|
return size_final;
|
|
}
|
|
|
|
static ImGuiCol GetWindowBgColorIdxFromFlags(ImGuiWindowFlags flags)
|
|
{
|
|
if (flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup))
|
|
return ImGuiCol_PopupBg;
|
|
if (flags & ImGuiWindowFlags_ChildWindow)
|
|
return ImGuiCol_ChildBg;
|
|
return ImGuiCol_WindowBg;
|
|
}
|
|
|
|
static void CalcResizePosSizeFromAnyCorner(ImGuiWindow* window,
|
|
const ImVec2& corner_target,
|
|
const ImVec2& corner_norm,
|
|
ImVec2* out_pos,
|
|
ImVec2* out_size)
|
|
{
|
|
ImVec2 pos_min = ImLerp(corner_target, window->Pos, corner_norm); // Expected window upper-left
|
|
ImVec2 pos_max = ImLerp(window->Pos + window->Size, corner_target, corner_norm); // Expected window lower-right
|
|
ImVec2 size_expected = pos_max - pos_min;
|
|
ImVec2 size_constrained = CalcWindowSizeAfterConstraint(window, size_expected);
|
|
*out_pos = pos_min;
|
|
if (corner_norm.x == 0.0f)
|
|
out_pos->x -= (size_constrained.x - size_expected.x);
|
|
if (corner_norm.y == 0.0f)
|
|
out_pos->y -= (size_constrained.y - size_expected.y);
|
|
*out_size = size_constrained;
|
|
}
|
|
|
|
// Data for resizing from corner
|
|
struct ImGuiResizeGripDef
|
|
{
|
|
ImVec2 CornerPosN;
|
|
ImVec2 InnerDir;
|
|
int AngleMin12, AngleMax12;
|
|
};
|
|
static const ImGuiResizeGripDef resize_grip_def[4] = {
|
|
{ImVec2(1, 1), ImVec2(-1, -1), 0, 3}, // Lower-right
|
|
{ImVec2(0, 1), ImVec2(+1, -1), 3, 6}, // Lower-left
|
|
{ImVec2(0, 0), ImVec2(+1, +1), 6, 9}, // Upper-left (Unused)
|
|
{ImVec2(1, 0), ImVec2(-1, +1), 9, 12} // Upper-right (Unused)
|
|
};
|
|
|
|
// Data for resizing from borders
|
|
struct ImGuiResizeBorderDef
|
|
{
|
|
ImVec2 InnerDir;
|
|
ImVec2 SegmentN1, SegmentN2;
|
|
float OuterAngle;
|
|
};
|
|
static const ImGuiResizeBorderDef resize_border_def[4] = {
|
|
{ImVec2(+1, 0), ImVec2(0, 1), ImVec2(0, 0), IM_PI * 1.00f}, // Left
|
|
{ImVec2(-1, 0), ImVec2(1, 0), ImVec2(1, 1), IM_PI * 0.00f}, // Right
|
|
{ImVec2(0, +1), ImVec2(0, 0), ImVec2(1, 0), IM_PI * 1.50f}, // Up
|
|
{ImVec2(0, -1), ImVec2(1, 1), ImVec2(0, 1), IM_PI * 0.50f} // Down
|
|
};
|
|
|
|
static ImRect GetResizeBorderRect(ImGuiWindow* window, int border_n, float perp_padding, float thickness)
|
|
{
|
|
ImRect rect = window->Rect();
|
|
if (thickness == 0.0f)
|
|
rect.Max -= ImVec2(1, 1);
|
|
if (border_n == ImGuiDir_Left)
|
|
{
|
|
return ImRect(rect.Min.x - thickness, rect.Min.y + perp_padding, rect.Min.x + thickness,
|
|
rect.Max.y - perp_padding);
|
|
}
|
|
if (border_n == ImGuiDir_Right)
|
|
{
|
|
return ImRect(rect.Max.x - thickness, rect.Min.y + perp_padding, rect.Max.x + thickness,
|
|
rect.Max.y - perp_padding);
|
|
}
|
|
if (border_n == ImGuiDir_Up)
|
|
{
|
|
return ImRect(rect.Min.x + perp_padding, rect.Min.y - thickness, rect.Max.x - perp_padding,
|
|
rect.Min.y + thickness);
|
|
}
|
|
if (border_n == ImGuiDir_Down)
|
|
{
|
|
return ImRect(rect.Min.x + perp_padding, rect.Max.y - thickness, rect.Max.x - perp_padding,
|
|
rect.Max.y + thickness);
|
|
}
|
|
IM_ASSERT(0);
|
|
return ImRect();
|
|
}
|
|
|
|
// 0..3: corners (Lower-right, Lower-left, Unused, Unused)
|
|
ImGuiID ImGui::GetWindowResizeCornerID(ImGuiWindow* window, int n)
|
|
{
|
|
IM_ASSERT(n >= 0 && n < 4);
|
|
ImGuiID id = window->DockIsActive ? window->DockNode->HostWindow->ID : window->ID;
|
|
id = ImHashStr("#RESIZE", 0, id);
|
|
id = ImHashData(&n, sizeof(int), id);
|
|
return id;
|
|
}
|
|
|
|
// Borders (Left, Right, Up, Down)
|
|
ImGuiID ImGui::GetWindowResizeBorderID(ImGuiWindow* window, ImGuiDir dir)
|
|
{
|
|
IM_ASSERT(dir >= 0 && dir < 4);
|
|
int n = (int)dir + 4;
|
|
ImGuiID id = window->DockIsActive ? window->DockNode->HostWindow->ID : window->ID;
|
|
id = ImHashStr("#RESIZE", 0, id);
|
|
id = ImHashData(&n, sizeof(int), id);
|
|
return id;
|
|
}
|
|
|
|
// Handle resize for: Resize Grips, Borders, Gamepad
|
|
// Return true when using auto-fit (double click on resize grip)
|
|
static bool ImGui::UpdateWindowManualResize(ImGuiWindow* window,
|
|
const ImVec2& size_auto_fit,
|
|
int* border_held,
|
|
int resize_grip_count,
|
|
ImU32 resize_grip_col[4],
|
|
const ImRect& visibility_rect)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindowFlags flags = window->Flags;
|
|
|
|
if ((flags & ImGuiWindowFlags_NoResize) || (flags & ImGuiWindowFlags_AlwaysAutoResize) ||
|
|
window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0)
|
|
return false;
|
|
if (window->WasActive ==
|
|
false) // Early out to avoid running this code for e.g. an hidden implicit/fallback Debug window.
|
|
return false;
|
|
|
|
bool ret_auto_fit = false;
|
|
const int resize_border_count = g.IO.ConfigWindowsResizeFromEdges ? 4 : 0;
|
|
const float grip_draw_size = IM_FLOOR(ImMax(g.FontSize * 1.35f, window->WindowRounding + 1.0f + g.FontSize * 0.2f));
|
|
const float grip_hover_inner_size = IM_FLOOR(grip_draw_size * 0.75f);
|
|
const float grip_hover_outer_size = g.IO.ConfigWindowsResizeFromEdges ? WINDOWS_HOVER_PADDING : 0.0f;
|
|
|
|
ImVec2 pos_target(FLT_MAX, FLT_MAX);
|
|
ImVec2 size_target(FLT_MAX, FLT_MAX);
|
|
|
|
// Clip mouse interaction rectangles within the viewport rectangle (in practice the narrowing is going to happen
|
|
// most of the time).
|
|
// - Not narrowing would mostly benefit the situation where OS windows _without_ decoration have a threshold for
|
|
// hovering when outside their limits.
|
|
// This is however not the case with current backends under Win32, but a custom borderless window implementation
|
|
// would benefit from it.
|
|
// - When decoration are enabled we typically benefit from that distance, but then our resize elements would be
|
|
// conflicting with OS resize elements, so we also narrow.
|
|
// - Note that we are unable to tell if the platform setup allows hovering with a distance threshold (on Win32,
|
|
// decorated window have such threshold). We only clip interaction so we overwrite window->ClipRect, cannot call
|
|
// PushClipRect() yet as DrawList is not yet setup.
|
|
const bool clip_with_viewport_rect = !(g.IO.BackendFlags & ImGuiBackendFlags_HasMouseHoveredViewport) ||
|
|
(g.IO.MouseHoveredViewport != window->ViewportId) ||
|
|
!(window->Viewport->Flags & ImGuiViewportFlags_NoDecoration);
|
|
if (clip_with_viewport_rect)
|
|
window->ClipRect = window->Viewport->GetMainRect();
|
|
|
|
// Resize grips and borders are on layer 1
|
|
window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;
|
|
|
|
// Manual resize grips
|
|
PushID("#RESIZE");
|
|
for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++)
|
|
{
|
|
const ImGuiResizeGripDef& def = resize_grip_def[resize_grip_n];
|
|
const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, def.CornerPosN);
|
|
|
|
// Using the FlattenChilds button flag we make the resize button accessible even if we are hovering over a child
|
|
// window
|
|
bool hovered, held;
|
|
ImRect resize_rect(corner - def.InnerDir * grip_hover_outer_size,
|
|
corner + def.InnerDir * grip_hover_inner_size);
|
|
if (resize_rect.Min.x > resize_rect.Max.x)
|
|
ImSwap(resize_rect.Min.x, resize_rect.Max.x);
|
|
if (resize_rect.Min.y > resize_rect.Max.y)
|
|
ImSwap(resize_rect.Min.y, resize_rect.Max.y);
|
|
ImGuiID resize_grip_id = window->GetID(resize_grip_n); // == GetWindowResizeCornerID()
|
|
ButtonBehavior(resize_rect, resize_grip_id, &hovered, &held,
|
|
ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_NoNavFocus);
|
|
// GetForegroundDrawList(window)->AddRect(resize_rect.Min, resize_rect.Max, IM_COL32(255, 255, 0, 255));
|
|
if (hovered || held)
|
|
g.MouseCursor = (resize_grip_n & 1) ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE;
|
|
|
|
if (held && g.IO.MouseDoubleClicked[0] && resize_grip_n == 0)
|
|
{
|
|
// Manual auto-fit when double-clicking
|
|
size_target = CalcWindowSizeAfterConstraint(window, size_auto_fit);
|
|
ret_auto_fit = true;
|
|
ClearActiveID();
|
|
}
|
|
else if (held)
|
|
{
|
|
// Resize from any of the four corners
|
|
// We don't use an incremental MouseDelta but rather compute an absolute target size based on mouse position
|
|
ImVec2 clamp_min = ImVec2(def.CornerPosN.x == 1.0f ? visibility_rect.Min.x : -FLT_MAX,
|
|
def.CornerPosN.y == 1.0f ? visibility_rect.Min.y : -FLT_MAX);
|
|
ImVec2 clamp_max = ImVec2(def.CornerPosN.x == 0.0f ? visibility_rect.Max.x : +FLT_MAX,
|
|
def.CornerPosN.y == 0.0f ? visibility_rect.Max.y : +FLT_MAX);
|
|
ImVec2 corner_target = g.IO.MousePos - g.ActiveIdClickOffset +
|
|
ImLerp(def.InnerDir * grip_hover_outer_size, def.InnerDir * -grip_hover_inner_size,
|
|
def.CornerPosN); // Corner of the window corresponding to our corner grip
|
|
corner_target = ImClamp(corner_target, clamp_min, clamp_max);
|
|
CalcResizePosSizeFromAnyCorner(window, corner_target, def.CornerPosN, &pos_target, &size_target);
|
|
}
|
|
|
|
// Only lower-left grip is visible before hovering/activating
|
|
if (resize_grip_n == 0 || held || hovered)
|
|
resize_grip_col[resize_grip_n] = GetColorU32(held ? ImGuiCol_ResizeGripActive
|
|
: hovered ? ImGuiCol_ResizeGripHovered
|
|
: ImGuiCol_ResizeGrip);
|
|
}
|
|
for (int border_n = 0; border_n < resize_border_count; border_n++)
|
|
{
|
|
const ImGuiResizeBorderDef& def = resize_border_def[border_n];
|
|
const ImGuiAxis axis = (border_n == ImGuiDir_Left || border_n == ImGuiDir_Right) ? ImGuiAxis_X : ImGuiAxis_Y;
|
|
|
|
bool hovered, held;
|
|
ImRect border_rect = GetResizeBorderRect(window, border_n, grip_hover_inner_size, WINDOWS_HOVER_PADDING);
|
|
ImGuiID border_id = window->GetID(border_n + 4); // == GetWindowResizeBorderID()
|
|
ButtonBehavior(border_rect, border_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren);
|
|
// GetForegroundDrawLists(window)->AddRect(border_rect.Min, border_rect.Max, IM_COL32(255, 255, 0, 255));
|
|
if ((hovered && g.HoveredIdTimer > WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER) || held)
|
|
{
|
|
g.MouseCursor = (axis == ImGuiAxis_X) ? ImGuiMouseCursor_ResizeEW : ImGuiMouseCursor_ResizeNS;
|
|
if (held)
|
|
*border_held = border_n;
|
|
}
|
|
if (held)
|
|
{
|
|
ImVec2 clamp_min(border_n == ImGuiDir_Right ? visibility_rect.Min.x : -FLT_MAX,
|
|
border_n == ImGuiDir_Down ? visibility_rect.Min.y : -FLT_MAX);
|
|
ImVec2 clamp_max(border_n == ImGuiDir_Left ? visibility_rect.Max.x : +FLT_MAX,
|
|
border_n == ImGuiDir_Up ? visibility_rect.Max.y : +FLT_MAX);
|
|
ImVec2 border_target = window->Pos;
|
|
border_target[axis] = g.IO.MousePos[axis] - g.ActiveIdClickOffset[axis] + WINDOWS_HOVER_PADDING;
|
|
border_target = ImClamp(border_target, clamp_min, clamp_max);
|
|
CalcResizePosSizeFromAnyCorner(window, border_target, ImMin(def.SegmentN1, def.SegmentN2), &pos_target,
|
|
&size_target);
|
|
}
|
|
}
|
|
PopID();
|
|
|
|
// Restore nav layer
|
|
window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
|
|
|
|
// Navigation resize (keyboard/gamepad)
|
|
if (g.NavWindowingTarget && g.NavWindowingTarget->RootWindowDockTree == window)
|
|
{
|
|
ImVec2 nav_resize_delta;
|
|
if (g.NavInputSource == ImGuiInputSource_Keyboard && g.IO.KeyShift)
|
|
nav_resize_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard, ImGuiInputReadMode_Down);
|
|
if (g.NavInputSource == ImGuiInputSource_Gamepad)
|
|
nav_resize_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_Down);
|
|
if (nav_resize_delta.x != 0.0f || nav_resize_delta.y != 0.0f)
|
|
{
|
|
const float NAV_RESIZE_SPEED = 600.0f;
|
|
nav_resize_delta *= ImFloor(NAV_RESIZE_SPEED * g.IO.DeltaTime *
|
|
ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y));
|
|
nav_resize_delta = ImMax(nav_resize_delta, visibility_rect.Min - window->Pos - window->Size);
|
|
g.NavWindowingToggleLayer = false;
|
|
g.NavDisableMouseHover = true;
|
|
resize_grip_col[0] = GetColorU32(ImGuiCol_ResizeGripActive);
|
|
// FIXME-NAV: Should store and accumulate into a separate size buffer to handle sizing constraints properly,
|
|
// right now a constraint will make us stuck.
|
|
size_target = CalcWindowSizeAfterConstraint(window, window->SizeFull + nav_resize_delta);
|
|
}
|
|
}
|
|
|
|
// Apply back modified position/size to window
|
|
if (size_target.x != FLT_MAX)
|
|
{
|
|
window->SizeFull = size_target;
|
|
MarkIniSettingsDirty(window);
|
|
}
|
|
if (pos_target.x != FLT_MAX)
|
|
{
|
|
window->Pos = ImFloor(pos_target);
|
|
MarkIniSettingsDirty(window);
|
|
}
|
|
|
|
window->Size = window->SizeFull;
|
|
return ret_auto_fit;
|
|
}
|
|
|
|
static inline void ClampWindowRect(ImGuiWindow* window, const ImRect& visibility_rect)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImVec2 size_for_clamping = window->Size;
|
|
if (g.IO.ConfigWindowsMoveFromTitleBarOnly && !(window->Flags & ImGuiWindowFlags_NoTitleBar))
|
|
size_for_clamping.y = window->TitleBarHeight();
|
|
window->Pos = ImClamp(window->Pos, visibility_rect.Min - size_for_clamping, visibility_rect.Max);
|
|
}
|
|
|
|
static void ImGui::RenderWindowOuterBorders(ImGuiWindow* window)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
float rounding = window->WindowRounding;
|
|
float border_size = window->WindowBorderSize;
|
|
if (border_size > 0.0f && !(window->Flags & ImGuiWindowFlags_NoBackground))
|
|
window->DrawList->AddRect(window->Pos, window->Pos + window->Size, GetColorU32(ImGuiCol_Border), rounding, 0,
|
|
border_size);
|
|
|
|
int border_held = window->ResizeBorderHeld;
|
|
if (border_held != -1)
|
|
{
|
|
const ImGuiResizeBorderDef& def = resize_border_def[border_held];
|
|
ImRect border_r = GetResizeBorderRect(window, border_held, rounding, 0.0f);
|
|
window->DrawList->PathArcTo(
|
|
ImLerp(border_r.Min, border_r.Max, def.SegmentN1) + ImVec2(0.5f, 0.5f) + def.InnerDir * rounding, rounding,
|
|
def.OuterAngle - IM_PI * 0.25f, def.OuterAngle);
|
|
window->DrawList->PathArcTo(
|
|
ImLerp(border_r.Min, border_r.Max, def.SegmentN2) + ImVec2(0.5f, 0.5f) + def.InnerDir * rounding, rounding,
|
|
def.OuterAngle, def.OuterAngle + IM_PI * 0.25f);
|
|
window->DrawList->PathStroke(GetColorU32(ImGuiCol_SeparatorActive), 0,
|
|
ImMax(2.0f, border_size)); // Thicker than usual
|
|
}
|
|
if (g.Style.FrameBorderSize > 0 && !(window->Flags & ImGuiWindowFlags_NoTitleBar) && !window->DockIsActive)
|
|
{
|
|
float y = window->Pos.y + window->TitleBarHeight() - 1;
|
|
window->DrawList->AddLine(ImVec2(window->Pos.x + border_size, y),
|
|
ImVec2(window->Pos.x + window->Size.x - border_size, y), GetColorU32(ImGuiCol_Border),
|
|
g.Style.FrameBorderSize);
|
|
}
|
|
}
|
|
|
|
// Draw background and borders
|
|
// Draw and handle scrollbars
|
|
void ImGui::RenderWindowDecorations(ImGuiWindow* window,
|
|
const ImRect& title_bar_rect,
|
|
bool title_bar_is_highlight,
|
|
bool handle_borders_and_resize_grips,
|
|
int resize_grip_count,
|
|
const ImU32 resize_grip_col[4],
|
|
float resize_grip_draw_size)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiStyle& style = g.Style;
|
|
ImGuiWindowFlags flags = window->Flags;
|
|
|
|
// Ensure that ScrollBar doesn't read last frame's SkipItems
|
|
IM_ASSERT(window->BeginCount == 0);
|
|
window->SkipItems = false;
|
|
|
|
// Draw window + handle manual resize
|
|
// As we highlight the title bar when want_focus is set, multiple reappearing windows will have have their title bar
|
|
// highlighted on their reappearing frame.
|
|
const float window_rounding = window->WindowRounding;
|
|
const float window_border_size = window->WindowBorderSize;
|
|
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
|
|
{
|
|
// Window background
|
|
if (!(flags & ImGuiWindowFlags_NoBackground))
|
|
{
|
|
bool is_docking_transparent_payload = false;
|
|
if (g.DragDropActive && (g.FrameCount - g.DragDropAcceptFrameCount) <= 1 &&
|
|
g.IO.ConfigDockingTransparentPayload)
|
|
if (g.DragDropPayload.IsDataType(IMGUI_PAYLOAD_TYPE_WINDOW) &&
|
|
*(ImGuiWindow**)g.DragDropPayload.Data == window)
|
|
is_docking_transparent_payload = true;
|
|
|
|
ImU32 bg_col = GetColorU32(GetWindowBgColorIdxFromFlags(flags));
|
|
if (window->ViewportOwned)
|
|
{
|
|
// No alpha
|
|
bg_col = (bg_col | IM_COL32_A_MASK);
|
|
if (is_docking_transparent_payload)
|
|
window->Viewport->Alpha *= DOCKING_TRANSPARENT_PAYLOAD_ALPHA;
|
|
}
|
|
else
|
|
{
|
|
// Adjust alpha. For docking
|
|
bool override_alpha = false;
|
|
float alpha = 1.0f;
|
|
if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasBgAlpha)
|
|
{
|
|
alpha = g.NextWindowData.BgAlphaVal;
|
|
override_alpha = true;
|
|
}
|
|
if (is_docking_transparent_payload)
|
|
{
|
|
alpha *= DOCKING_TRANSPARENT_PAYLOAD_ALPHA; // FIXME-DOCK: Should that be an override?
|
|
override_alpha = true;
|
|
}
|
|
if (override_alpha)
|
|
bg_col = (bg_col & ~IM_COL32_A_MASK) | (IM_F32_TO_INT8_SAT(alpha) << IM_COL32_A_SHIFT);
|
|
}
|
|
window->DrawList->AddRectFilled(window->Pos + ImVec2(0, window->TitleBarHeight()),
|
|
window->Pos + window->Size, bg_col, window_rounding,
|
|
(flags & ImGuiWindowFlags_NoTitleBar) ? 0 : ImDrawFlags_RoundCornersBottom);
|
|
}
|
|
|
|
// 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(title_bar_is_highlight ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg);
|
|
window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, window_rounding,
|
|
ImDrawFlags_RoundCornersTop);
|
|
}
|
|
|
|
// 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 + ImVec2(window_border_size, 0), menu_bar_rect.Max - ImVec2(window_border_size, 0),
|
|
GetColorU32(ImGuiCol_MenuBarBg), (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f,
|
|
ImDrawFlags_RoundCornersTop);
|
|
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);
|
|
}
|
|
|
|
// Docking: Unhide tab bar (small triangle in the corner), drag from small triangle to quickly undock
|
|
ImGuiDockNode* node = window->DockNode;
|
|
if (window->DockIsActive && node->IsHiddenTabBar() && !node->IsNoTabBar())
|
|
{
|
|
float unhide_sz_draw = ImFloor(g.FontSize * 0.70f);
|
|
float unhide_sz_hit = ImFloor(g.FontSize * 0.55f);
|
|
ImVec2 p = node->Pos;
|
|
ImRect r(p, p + ImVec2(unhide_sz_hit, unhide_sz_hit));
|
|
bool hovered, held;
|
|
if (ButtonBehavior(r, window->GetID("#UNHIDE"), &hovered, &held, ImGuiButtonFlags_FlattenChildren))
|
|
node->WantHiddenTabBarToggle = true;
|
|
else if (held && IsMouseDragging(0))
|
|
StartMouseMovingWindowOrNode(window, node, true);
|
|
|
|
// FIXME-DOCK: Ideally we'd use ImGuiCol_TitleBgActive/ImGuiCol_TitleBg here, but neither is guaranteed to
|
|
// be visible enough at this sort of size..
|
|
ImU32 col = GetColorU32(((held && hovered) || (node->IsFocused && !hovered)) ? ImGuiCol_ButtonActive
|
|
: hovered ? ImGuiCol_ButtonHovered
|
|
: ImGuiCol_Button);
|
|
window->DrawList->AddTriangleFilled(p, p + ImVec2(unhide_sz_draw, 0.0f), p + ImVec2(0.0f, unhide_sz_draw),
|
|
col);
|
|
}
|
|
|
|
// Scrollbars
|
|
if (window->ScrollbarX)
|
|
Scrollbar(ImGuiAxis_X);
|
|
if (window->ScrollbarY)
|
|
Scrollbar(ImGuiAxis_Y);
|
|
|
|
// Render resize grips (after their input handling so we don't have a frame of latency)
|
|
if (handle_borders_and_resize_grips && !(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.CornerPosN);
|
|
window->DrawList->PathLineTo(
|
|
corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(window_border_size, resize_grip_draw_size)
|
|
: ImVec2(resize_grip_draw_size, window_border_size)));
|
|
window->DrawList->PathLineTo(
|
|
corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(resize_grip_draw_size, window_border_size)
|
|
: ImVec2(window_border_size, resize_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 (for dock node host they will be rendered over after the tab bar)
|
|
if (handle_borders_and_resize_grips && !window->DockNodeAsHost)
|
|
RenderWindowOuterBorders(window);
|
|
}
|
|
}
|
|
|
|
// Render title text, collapse button, close button
|
|
// When inside a dock node, this is handled in DockNodeCalcTabBarLayout() instead.
|
|
void ImGui::RenderWindowTitleBarContents(ImGuiWindow* window,
|
|
const ImRect& title_bar_rect,
|
|
const char* name,
|
|
bool* p_open)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiStyle& style = g.Style;
|
|
ImGuiWindowFlags flags = window->Flags;
|
|
|
|
const bool has_close_button = (p_open != NULL);
|
|
const bool has_collapse_button =
|
|
!(flags & ImGuiWindowFlags_NoCollapse) && (style.WindowMenuButtonPosition != ImGuiDir_None);
|
|
|
|
// Close & Collapse button are on the Menu NavLayer and don't default focus (unless there's nothing else on that
|
|
// layer)
|
|
const ImGuiItemFlags item_flags_backup = g.CurrentItemFlags;
|
|
g.CurrentItemFlags |= ImGuiItemFlags_NoNavDefaultFocus;
|
|
window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;
|
|
|
|
// Layout buttons
|
|
// FIXME: Would be nice to generalize the subtleties expressed here into reusable code.
|
|
float pad_l = style.FramePadding.x;
|
|
float pad_r = style.FramePadding.x;
|
|
float button_sz = g.FontSize;
|
|
ImVec2 close_button_pos;
|
|
ImVec2 collapse_button_pos;
|
|
if (has_close_button)
|
|
{
|
|
pad_r += button_sz;
|
|
close_button_pos = ImVec2(title_bar_rect.Max.x - pad_r - style.FramePadding.x, title_bar_rect.Min.y);
|
|
}
|
|
if (has_collapse_button && style.WindowMenuButtonPosition == ImGuiDir_Right)
|
|
{
|
|
pad_r += button_sz;
|
|
collapse_button_pos = ImVec2(title_bar_rect.Max.x - pad_r - style.FramePadding.x, title_bar_rect.Min.y);
|
|
}
|
|
if (has_collapse_button && style.WindowMenuButtonPosition == ImGuiDir_Left)
|
|
{
|
|
collapse_button_pos = ImVec2(title_bar_rect.Min.x + pad_l - style.FramePadding.x, title_bar_rect.Min.y);
|
|
pad_l += button_sz;
|
|
}
|
|
|
|
// Collapse button (submitting first so it gets priority when choosing a navigation init fallback)
|
|
if (has_collapse_button)
|
|
if (CollapseButton(window->GetID("#COLLAPSE"), collapse_button_pos, NULL))
|
|
window->WantCollapseToggle =
|
|
true; // Defer actual collapsing to next frame as we are too far in the Begin() function
|
|
|
|
// Close button
|
|
if (has_close_button)
|
|
if (CloseButton(window->GetID("#CLOSE"), close_button_pos))
|
|
*p_open = false;
|
|
|
|
window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
|
|
g.CurrentItemFlags = item_flags_backup;
|
|
|
|
// 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 WAY too much messy code..
|
|
const float marker_size_x = (flags & ImGuiWindowFlags_UnsavedDocument) ? button_sz * 0.80f : 0.0f;
|
|
const ImVec2 text_size = CalcTextSize(name, NULL, true) + ImVec2(marker_size_x, 0.0f);
|
|
|
|
// As a nice touch we try to ensure that centered title text doesn't get affected by visibility of Close/Collapse
|
|
// button, while uncentered title text will still reach edges correctly.
|
|
if (pad_l > style.FramePadding.x)
|
|
pad_l += g.Style.ItemInnerSpacing.x;
|
|
if (pad_r > style.FramePadding.x)
|
|
pad_r += g.Style.ItemInnerSpacing.x;
|
|
if (style.WindowTitleAlign.x > 0.0f && style.WindowTitleAlign.x < 1.0f)
|
|
{
|
|
float centerness =
|
|
ImSaturate(1.0f - ImFabs(style.WindowTitleAlign.x - 0.5f) * 2.0f); // 0.0f on either edges, 1.0f on center
|
|
float pad_extend = ImMin(ImMax(pad_l, pad_r), title_bar_rect.GetWidth() - pad_l - pad_r - text_size.x);
|
|
pad_l = ImMax(pad_l, pad_extend * centerness);
|
|
pad_r = ImMax(pad_r, pad_extend * centerness);
|
|
}
|
|
|
|
ImRect layout_r(title_bar_rect.Min.x + pad_l, title_bar_rect.Min.y, title_bar_rect.Max.x - pad_r,
|
|
title_bar_rect.Max.y);
|
|
ImRect clip_r(layout_r.Min.x, layout_r.Min.y,
|
|
ImMin(layout_r.Max.x + g.Style.ItemInnerSpacing.x, title_bar_rect.Max.x), layout_r.Max.y);
|
|
if (flags & ImGuiWindowFlags_UnsavedDocument)
|
|
{
|
|
ImVec2 marker_pos;
|
|
marker_pos.x =
|
|
ImClamp(layout_r.Min.x + (layout_r.GetWidth() - text_size.x) * style.WindowTitleAlign.x + text_size.x,
|
|
layout_r.Min.x, layout_r.Max.x);
|
|
marker_pos.y = (layout_r.Min.y + layout_r.Max.y) * 0.5f;
|
|
if (marker_pos.x > layout_r.Min.x)
|
|
{
|
|
RenderBullet(window->DrawList, marker_pos, GetColorU32(ImGuiCol_Text));
|
|
clip_r.Max.x = ImMin(clip_r.Max.x, marker_pos.x - (int)(marker_size_x * 0.5f));
|
|
}
|
|
}
|
|
// if (g.IO.KeyShift) window->DrawList->AddRect(layout_r.Min, layout_r.Max, IM_COL32(255, 128, 0, 255)); // [DEBUG]
|
|
// if (g.IO.KeyCtrl) window->DrawList->AddRect(clip_r.Min, clip_r.Max, IM_COL32(255, 128, 0, 255)); // [DEBUG]
|
|
RenderTextClipped(layout_r.Min, layout_r.Max, name, NULL, &text_size, style.WindowTitleAlign, &clip_r);
|
|
}
|
|
|
|
void ImGui::UpdateWindowParentAndRootLinks(ImGuiWindow* window, ImGuiWindowFlags flags, ImGuiWindow* parent_window)
|
|
{
|
|
window->ParentWindow = parent_window;
|
|
window->RootWindow = window->RootWindowDockTree = window->RootWindowForTitleBarHighlight =
|
|
window->RootWindowForNav = window;
|
|
if (parent_window && (flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Tooltip))
|
|
{
|
|
window->RootWindowDockTree = parent_window->RootWindowDockTree;
|
|
if (!window->DockIsActive && !(parent_window->Flags & ImGuiWindowFlags_DockNodeHost))
|
|
window->RootWindow = parent_window->RootWindow;
|
|
}
|
|
if (parent_window && !(flags & ImGuiWindowFlags_Modal) &&
|
|
(flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup)))
|
|
window->RootWindowForTitleBarHighlight = parent_window->RootWindowForTitleBarHighlight;
|
|
while (window->RootWindowForNav->Flags & ImGuiWindowFlags_NavFlattened)
|
|
{
|
|
IM_ASSERT(window->RootWindowForNav->ParentWindow != NULL);
|
|
window->RootWindowForNav = window->RootWindowForNav->ParentWindow;
|
|
}
|
|
}
|
|
|
|
// Push a new Dear ImGui window to add widgets to.
|
|
// - A default window called "Debug" is automatically stacked at the beginning of every frame so you can use widgets
|
|
// without explicitly calling a Begin/End pair.
|
|
// - Begin/End can be called multiple times during the frame with the same window name to append content.
|
|
// - The window name is used as a unique identifier to preserve window information across frames (and save rudimentary
|
|
// information to the .ini file).
|
|
// You can use the "##" or "###" markers to use the same label with different id, or same id with different label. See
|
|
// documentation at the top of this file.
|
|
// - Return false when window is collapsed, so you can early out in your code. You always need to call ImGui::End() even
|
|
// if false is returned.
|
|
// - Passing 'bool* p_open' displays a Close button on the upper-right corner of the window, the pointed value will be
|
|
// set to false when the button is pressed.
|
|
bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
const ImGuiStyle& style = g.Style;
|
|
IM_ASSERT(name != NULL && name[0] != '\0'); // Window name required
|
|
IM_ASSERT(g.WithinFrameScope); // 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)
|
|
window = CreateNewWindow(name, flags);
|
|
|
|
// Automatically disable manual moving/resizing when NoInputs is set
|
|
if ((flags & ImGuiWindowFlags_NoInputs) == 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);
|
|
window->IsFallbackWindow = (g.CurrentWindowStack.Size == 0 && g.WithinFrameScopeWithImplicitWindow);
|
|
|
|
// Update the Appearing flag (note: the BeginDocked() path may also set this to true later)
|
|
bool window_just_activated_by_user =
|
|
(window->LastFrameActive <
|
|
current_frame - 1); // Not using !WasActive because the implicit "Debug" window would always toggle off->on
|
|
if (flags & ImGuiWindowFlags_Popup)
|
|
{
|
|
ImGuiPopupData& popup_ref = g.OpenPopupStack[g.BeginPopupStack.Size];
|
|
window_just_activated_by_user |=
|
|
(window->PopupId !=
|
|
popup_ref.PopupId); // We recycle popups so treat window as activated if popup id changed
|
|
window_just_activated_by_user |= (window != popup_ref.Window);
|
|
}
|
|
|
|
// Update Flags, LastFrameActive, BeginOrderXXX fields
|
|
const bool window_was_appearing = window->Appearing;
|
|
if (first_begin_of_the_frame)
|
|
{
|
|
window->Appearing = window_just_activated_by_user;
|
|
if (window->Appearing)
|
|
SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, true);
|
|
|
|
window->FlagsPreviousFrame = window->Flags;
|
|
window->Flags = (ImGuiWindowFlags)flags;
|
|
window->LastFrameActive = current_frame;
|
|
window->LastTimeActive = (float)g.Time;
|
|
window->BeginOrderWithinParent = 0;
|
|
window->BeginOrderWithinContext = (short)(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.Flags & ImGuiNextWindowDataFlags_HasDock)
|
|
SetWindowDock(window, g.NextWindowData.DockId, g.NextWindowData.DockCond);
|
|
if (first_begin_of_the_frame)
|
|
{
|
|
bool has_dock_node = (window->DockId != 0 || window->DockNode != NULL);
|
|
bool new_auto_dock_node = !has_dock_node && GetWindowAlwaysWantOwnTabBar(window);
|
|
bool dock_node_was_visible = window->DockNodeIsVisible;
|
|
bool dock_tab_was_visible = window->DockTabIsVisible;
|
|
if (has_dock_node || new_auto_dock_node)
|
|
{
|
|
BeginDocked(window, p_open);
|
|
flags = window->Flags;
|
|
if (window->DockIsActive)
|
|
IM_ASSERT(window->DockNode != NULL);
|
|
|
|
// Docking currently override constraints
|
|
g.NextWindowData.Flags &= ~ImGuiNextWindowDataFlags_HasSizeConstraint;
|
|
|
|
// Amend the Appearing flag
|
|
if (window->DockTabIsVisible && !dock_tab_was_visible && dock_node_was_visible && !window->Appearing &&
|
|
!window_was_appearing)
|
|
{
|
|
window->Appearing = true;
|
|
SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
window->DockIsActive = window->DockNodeIsVisible = window->DockTabIsVisible = false;
|
|
}
|
|
}
|
|
|
|
// Parent window is latched only on the first call to Begin() of the frame, so further append-calls can be done from
|
|
// a different window stack
|
|
ImGuiWindow* parent_window_in_stack = window->DockIsActive ? window->DockNode->HostWindow
|
|
: g.CurrentWindowStack.empty() ? NULL
|
|
: g.CurrentWindowStack.back().Window;
|
|
ImGuiWindow* parent_window =
|
|
first_begin_of_the_frame
|
|
? ((flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup)) ? parent_window_in_stack : NULL)
|
|
: window->ParentWindow;
|
|
IM_ASSERT(parent_window != NULL || !(flags & ImGuiWindowFlags_ChildWindow));
|
|
|
|
// We allow window memory to be compacted so recreate the base stack when needed.
|
|
if (window->IDStack.Size == 0)
|
|
window->IDStack.push_back(window->ID);
|
|
|
|
// Add to stack
|
|
// We intentionally set g.CurrentWindow to NULL to prevent usage until when the viewport is set, then will call
|
|
// SetCurrentWindow()
|
|
ImGuiWindowStackData window_stack_data;
|
|
window_stack_data.Window = window;
|
|
window_stack_data.ParentLastItemDataBackup = g.LastItemData;
|
|
g.CurrentWindowStack.push_back(window_stack_data);
|
|
g.CurrentWindow = window;
|
|
window->DC.StackSizesOnBegin.SetToCurrentState();
|
|
g.CurrentWindow = NULL;
|
|
|
|
if (flags & ImGuiWindowFlags_Popup)
|
|
{
|
|
ImGuiPopupData& popup_ref = g.OpenPopupStack[g.BeginPopupStack.Size];
|
|
popup_ref.Window = window;
|
|
g.BeginPopupStack.push_back(popup_ref);
|
|
window->PopupId = popup_ref.PopupId;
|
|
}
|
|
|
|
// Update ->RootWindow and others pointers (before any possible call to FocusWindow)
|
|
if (first_begin_of_the_frame)
|
|
UpdateWindowParentAndRootLinks(window, flags, parent_window);
|
|
|
|
// Process SetNextWindow***() calls
|
|
// (FIXME: Consider splitting the HasXXX flags into X/Y components
|
|
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.Flags & ImGuiNextWindowDataFlags_HasPos)
|
|
{
|
|
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.Flags & ImGuiNextWindowDataFlags_HasSize)
|
|
{
|
|
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.Flags & ImGuiNextWindowDataFlags_HasScroll)
|
|
{
|
|
if (g.NextWindowData.ScrollVal.x >= 0.0f)
|
|
{
|
|
window->ScrollTarget.x = g.NextWindowData.ScrollVal.x;
|
|
window->ScrollTargetCenterRatio.x = 0.0f;
|
|
}
|
|
if (g.NextWindowData.ScrollVal.y >= 0.0f)
|
|
{
|
|
window->ScrollTarget.y = g.NextWindowData.ScrollVal.y;
|
|
window->ScrollTargetCenterRatio.y = 0.0f;
|
|
}
|
|
}
|
|
if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasContentSize)
|
|
window->ContentSizeExplicit = g.NextWindowData.ContentSizeVal;
|
|
else if (first_begin_of_the_frame)
|
|
window->ContentSizeExplicit = ImVec2(0.0f, 0.0f);
|
|
if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasWindowClass)
|
|
window->WindowClass = g.NextWindowData.WindowClass;
|
|
if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasCollapsed)
|
|
SetWindowCollapsed(window, g.NextWindowData.CollapsedVal, g.NextWindowData.CollapsedCond);
|
|
if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasFocus)
|
|
FocusWindow(window);
|
|
if (window->Appearing)
|
|
SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, false);
|
|
|
|
// 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)
|
|
window->Active = true;
|
|
window->HasCloseButton = (p_open != NULL);
|
|
window->ClipRect = ImVec4(-FLT_MAX, -FLT_MAX, +FLT_MAX, +FLT_MAX);
|
|
window->IDStack.resize(1);
|
|
window->DrawList->_ResetForNewFrame();
|
|
window->DC.CurrentTableIdx = -1;
|
|
|
|
// Restore buffer capacity when woken from a compacted state, to avoid
|
|
if (window->MemoryCompacted)
|
|
GcAwakeTransientWindowBuffers(window);
|
|
|
|
// Update stored window name when it changes (which can _only_ happen with the "###" operator, so the ID would
|
|
// stay unchanged). The title bar always display the 'name' parameter, so we only update the string storage if
|
|
// it needs to be visible to the end-user elsewhere.
|
|
bool window_title_visible_elsewhere = false;
|
|
if ((window->Viewport && window->Viewport->Window == window) || (window->DockIsActive))
|
|
window_title_visible_elsewhere = true;
|
|
else if (g.NavWindowingListWindow != NULL &&
|
|
(window->Flags & ImGuiWindowFlags_NoNavFocus) == 0) // Window titles visible when using CTRL+TAB
|
|
window_title_visible_elsewhere = true;
|
|
if (window_title_visible_elsewhere && !window_just_created && strcmp(name, window->Name) != 0)
|
|
{
|
|
size_t buf_len = (size_t)window->NameBufLen;
|
|
window->Name = ImStrdupcpy(window->Name, &buf_len, name);
|
|
window->NameBufLen = (int)buf_len;
|
|
}
|
|
|
|
// UPDATE CONTENTS SIZE, UPDATE HIDDEN STATUS
|
|
|
|
// Update contents size from last frame for auto-fitting (or use explicit size)
|
|
const bool window_just_appearing_after_hidden_for_resize = (window->HiddenFramesCannotSkipItems > 0);
|
|
CalcWindowContentSizes(window, &window->ContentSize, &window->ContentSizeIdeal);
|
|
|
|
// FIXME: These flags are decremented before they are used. This means that in order to have these fields
|
|
// produce their intended behaviors for one frame we must set them to at least 2, which is counter-intuitive.
|
|
// HiddenFramesCannotSkipItems is a more complicated case because it has a single usage before this code block
|
|
// and may be set below before it is finally checked.
|
|
if (window->HiddenFramesCanSkipItems > 0)
|
|
window->HiddenFramesCanSkipItems--;
|
|
if (window->HiddenFramesCannotSkipItems > 0)
|
|
window->HiddenFramesCannotSkipItems--;
|
|
if (window->HiddenFramesForRenderOnly > 0)
|
|
window->HiddenFramesForRenderOnly--;
|
|
|
|
// 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->HiddenFramesCannotSkipItems = 1;
|
|
|
|
// Hide popup/tooltip window when re-opening while we measure size (because we recycle the windows)
|
|
// We reset Size/ContentSize 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->HiddenFramesCannotSkipItems = 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->ContentSize = window->ContentSizeIdeal = ImVec2(0.f, 0.f);
|
|
}
|
|
}
|
|
|
|
// SELECT VIEWPORT
|
|
// We need to do this before using any style/font sizes, as viewport with a different DPI may affect font sizes.
|
|
|
|
WindowSelectViewport(window);
|
|
SetCurrentViewport(window, window->Viewport);
|
|
window->FontDpiScale =
|
|
(g.IO.ConfigFlags & ImGuiConfigFlags_DpiEnableScaleFonts) ? window->Viewport->DpiScale : 1.0f;
|
|
SetCurrentWindow(window);
|
|
flags = window->Flags;
|
|
|
|
// LOCK BORDER SIZE AND PADDING FOR THE FRAME (so that altering them doesn't cause inconsistencies)
|
|
// We read Style data after the call to UpdateSelectWindowViewport() which might be swapping the style.
|
|
|
|
if (flags & ImGuiWindowFlags_ChildWindow)
|
|
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;
|
|
|
|
// Lock menu offset so size calculation can use it as menu-bar windows need a minimum size.
|
|
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) && !window->DockIsActive)
|
|
{
|
|
// We don't use a regular button+id to test for double-click on title bar (mostly due to legacy reason,
|
|
// could be fixed), so verify that we don't have items over the title bar.
|
|
ImRect title_bar_rect = window->TitleBarRect();
|
|
if (g.HoveredWindow == window && g.HoveredId == 0 && g.HoveredIdPreviousFrame == 0 &&
|
|
IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max) && g.IO.MouseDoubleClicked[0])
|
|
window->WantCollapseToggle = true;
|
|
if (window->WantCollapseToggle)
|
|
{
|
|
window->Collapsed = !window->Collapsed;
|
|
MarkIniSettingsDirty(window);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
window->Collapsed = false;
|
|
}
|
|
window->WantCollapseToggle = false;
|
|
|
|
// SIZE
|
|
|
|
// Calculate auto-fit size, handle automatic resize
|
|
const ImVec2 size_auto_fit = CalcWindowAutoFitSize(window, window->ContentSizeIdeal);
|
|
bool use_current_size_for_scrollbar_x = window_just_created;
|
|
bool use_current_size_for_scrollbar_y = window_just_created;
|
|
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_auto_fit.x;
|
|
use_current_size_for_scrollbar_x = true;
|
|
}
|
|
if (!window_size_y_set_by_api)
|
|
{
|
|
window->SizeFull.y = size_auto_fit.y;
|
|
use_current_size_for_scrollbar_y = true;
|
|
}
|
|
}
|
|
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 =
|
|
window->AutoFitOnlyGrows ? ImMax(window->SizeFull.x, size_auto_fit.x) : size_auto_fit.x;
|
|
use_current_size_for_scrollbar_x = true;
|
|
}
|
|
if (!window_size_y_set_by_api && window->AutoFitFramesY > 0)
|
|
{
|
|
window->SizeFull.y =
|
|
window->AutoFitOnlyGrows ? ImMax(window->SizeFull.y, size_auto_fit.y) : size_auto_fit.y;
|
|
use_current_size_for_scrollbar_y = true;
|
|
}
|
|
if (!window->Collapsed)
|
|
MarkIniSettingsDirty(window);
|
|
}
|
|
|
|
// Apply minimum/maximum window size constraints and final size
|
|
window->SizeFull = CalcWindowSizeAfterConstraint(window, window->SizeFull);
|
|
window->Size = window->Collapsed && !(flags & ImGuiWindowFlags_ChildWindow) ? window->TitleBarRect().GetSize()
|
|
: window->SizeFull;
|
|
|
|
// Decoration size
|
|
const float decoration_up_height = window->TitleBarHeight() + window->MenuBarHeight();
|
|
|
|
// 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 && !(flags & ImGuiWindowFlags_Modal) &&
|
|
!window_pos_set_by_api) // FIXME: BeginPopup() could use SetNextWindowPos()
|
|
window->Pos = g.BeginPopupStack.back().OpenPopupPos;
|
|
}
|
|
|
|
// Position child window
|
|
if (flags & ImGuiWindowFlags_ChildWindow)
|
|
{
|
|
IM_ASSERT(parent_window && parent_window->Active);
|
|
window->BeginOrderWithinParent = (short)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;
|
|
}
|
|
|
|
const bool window_pos_with_pivot =
|
|
(window->SetWindowPosVal.x != FLT_MAX && window->HiddenFramesCannotSkipItems == 0);
|
|
if (window_pos_with_pivot)
|
|
SetWindowPos(window, window->SetWindowPosVal - window->Size * 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);
|
|
|
|
// Late create viewport if we don't fit within our current host viewport.
|
|
if (window->ViewportAllowPlatformMonitorExtend >= 0 && !window->ViewportOwned &&
|
|
!(window->Viewport->Flags & ImGuiViewportFlags_Minimized))
|
|
if (!window->Viewport->GetMainRect().Contains(window->Rect()))
|
|
{
|
|
// This is based on the assumption that the DPI will be known ahead (same as the DPI of the selection
|
|
// done in UpdateSelectWindowViewport)
|
|
// ImGuiViewport* old_viewport = window->Viewport;
|
|
window->Viewport = AddUpdateViewport(window, window->ID, window->Pos, window->Size,
|
|
ImGuiViewportFlags_NoFocusOnAppearing);
|
|
|
|
// FIXME-DPI
|
|
// IM_ASSERT(old_viewport->DpiScale == window->Viewport->DpiScale); // FIXME-DPI: Something went wrong
|
|
SetCurrentViewport(window, window->Viewport);
|
|
window->FontDpiScale =
|
|
(g.IO.ConfigFlags & ImGuiConfigFlags_DpiEnableScaleFonts) ? window->Viewport->DpiScale : 1.0f;
|
|
SetCurrentWindow(window);
|
|
}
|
|
|
|
if (window->ViewportOwned)
|
|
WindowSyncOwnedViewport(window, parent_window_in_stack);
|
|
|
|
// Calculate the range of allowed position for that window (to be movable and visible past safe area padding)
|
|
// When clamping to stay visible, we will enforce that window->Pos stays inside of visibility_rect.
|
|
ImRect viewport_rect(window->Viewport->GetMainRect());
|
|
ImRect viewport_work_rect(window->Viewport->GetWorkRect());
|
|
ImVec2 visibility_padding = ImMax(style.DisplayWindowPadding, style.DisplaySafeAreaPadding);
|
|
ImRect visibility_rect(viewport_work_rect.Min + visibility_padding,
|
|
viewport_work_rect.Max - visibility_padding);
|
|
|
|
// Clamp position/size so window stays visible within its viewport or monitor
|
|
// Ignore zero-sized display explicitly to avoid losing positions if a window manager reports zero-sized window
|
|
// when initializing or minimizing.
|
|
// FIXME: Similar to code in GetWindowAllowedExtentRect()
|
|
if (!window_pos_set_by_api && !(flags & ImGuiWindowFlags_ChildWindow) && window->AutoFitFramesX <= 0 &&
|
|
window->AutoFitFramesY <= 0)
|
|
{
|
|
if (!window->ViewportOwned && viewport_rect.GetWidth() > 0 && viewport_rect.GetHeight() > 0.0f)
|
|
{
|
|
ClampWindowRect(window, visibility_rect);
|
|
}
|
|
else if (window->ViewportOwned && g.PlatformIO.Monitors.Size > 0)
|
|
{
|
|
// Lost windows (e.g. a monitor disconnected) will naturally moved to the fallback/dummy monitor aka the
|
|
// main viewport.
|
|
const ImGuiPlatformMonitor* monitor = GetViewportPlatformMonitor(window->Viewport);
|
|
visibility_rect.Min = monitor->WorkPos + visibility_padding;
|
|
visibility_rect.Max = monitor->WorkPos + monitor->WorkSize - visibility_padding;
|
|
ClampWindowRect(window, visibility_rect);
|
|
}
|
|
}
|
|
window->Pos = ImFloor(window->Pos);
|
|
|
|
// Lock window rounding for the frame (so that altering them doesn't cause inconsistencies)
|
|
// Large values tend to lead to variety of artifacts and are not recommended.
|
|
if (window->ViewportOwned || window->DockIsActive)
|
|
window->WindowRounding = 0.0f;
|
|
else
|
|
window->WindowRounding = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildRounding
|
|
: ((flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiWindowFlags_Modal))
|
|
? style.PopupRounding
|
|
: style.WindowRounding;
|
|
|
|
// For windows with title bar or menu bar, we clamp to FrameHeight(FontSize + FramePadding.y * 2.0f) to
|
|
// completely hide artifacts.
|
|
// if ((window->Flags & ImGuiWindowFlags_MenuBar) || !(window->Flags & ImGuiWindowFlags_NoTitleBar))
|
|
// window->WindowRounding = ImMin(window->WindowRounding, g.FontSize + style.FramePadding.y * 2.0f);
|
|
|
|
// 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_Popup)
|
|
want_focus = true;
|
|
else if ((window->DockIsActive || (flags & ImGuiWindowFlags_ChildWindow) == 0) &&
|
|
!(flags & ImGuiWindowFlags_Tooltip))
|
|
want_focus = true;
|
|
}
|
|
|
|
// Decide if we are going to handle borders and resize grips
|
|
const bool handle_borders_and_resize_grips = (window->DockNodeAsHost || !window->DockIsActive);
|
|
|
|
// Handle manual resize: Resize Grips, Borders, Gamepad
|
|
int border_held = -1;
|
|
ImU32 resize_grip_col[4] = {};
|
|
const int resize_grip_count =
|
|
g.IO.ConfigWindowsResizeFromEdges
|
|
? 2
|
|
: 1; // Allow resize from lower-left if we have the mouse cursor feedback for it.
|
|
const float resize_grip_draw_size =
|
|
IM_FLOOR(ImMax(g.FontSize * 1.10f, window->WindowRounding + 1.0f + g.FontSize * 0.2f));
|
|
if (handle_borders_and_resize_grips && !window->Collapsed)
|
|
if (UpdateWindowManualResize(window, size_auto_fit, &border_held, resize_grip_count, &resize_grip_col[0],
|
|
visibility_rect))
|
|
use_current_size_for_scrollbar_x = use_current_size_for_scrollbar_y = true;
|
|
window->ResizeBorderHeld = (signed char)border_held;
|
|
|
|
// Synchronize window --> viewport again and one last time (clamping and manual resize may have affected either)
|
|
if (window->ViewportOwned)
|
|
{
|
|
if (!window->Viewport->PlatformRequestMove)
|
|
window->Viewport->Pos = window->Pos;
|
|
if (!window->Viewport->PlatformRequestResize)
|
|
window->Viewport->Size = window->Size;
|
|
window->Viewport->UpdateWorkRect();
|
|
viewport_rect = window->Viewport->GetMainRect();
|
|
}
|
|
|
|
// Save last known viewport position within the window itself (so it can be saved in .ini file and restored)
|
|
window->ViewportPos = window->Viewport->Pos;
|
|
|
|
// SCROLLBAR VISIBILITY
|
|
|
|
// Update scrollbar visibility (based on the Size that was effective during last frame or the auto-resized
|
|
// Size).
|
|
if (!window->Collapsed)
|
|
{
|
|
// When reading the current size we need to read it after size constraints have been applied.
|
|
// When we use InnerRect here we are intentionally reading last frame size, same for ScrollbarSizes values
|
|
// before we set them again.
|
|
ImVec2 avail_size_from_current_frame =
|
|
ImVec2(window->SizeFull.x, window->SizeFull.y - decoration_up_height);
|
|
ImVec2 avail_size_from_last_frame = window->InnerRect.GetSize() + window->ScrollbarSizes;
|
|
ImVec2 needed_size_from_last_frame =
|
|
window_just_created ? ImVec2(0, 0) : window->ContentSize + window->WindowPadding * 2.0f;
|
|
float size_x_for_scrollbars =
|
|
use_current_size_for_scrollbar_x ? avail_size_from_current_frame.x : avail_size_from_last_frame.x;
|
|
float size_y_for_scrollbars =
|
|
use_current_size_for_scrollbar_y ? avail_size_from_current_frame.y : avail_size_from_last_frame.y;
|
|
// bool scrollbar_y_from_last_frame = window->ScrollbarY; // FIXME: May want to use that in the ScrollbarX
|
|
// expression? How many pros vs cons?
|
|
window->ScrollbarY =
|
|
(flags & ImGuiWindowFlags_AlwaysVerticalScrollbar) ||
|
|
((needed_size_from_last_frame.y > size_y_for_scrollbars) && !(flags & ImGuiWindowFlags_NoScrollbar));
|
|
window->ScrollbarX =
|
|
(flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar) ||
|
|
((needed_size_from_last_frame.x >
|
|
size_x_for_scrollbars - (window->ScrollbarY ? style.ScrollbarSize : 0.0f)) &&
|
|
!(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar));
|
|
if (window->ScrollbarX && !window->ScrollbarY)
|
|
window->ScrollbarY =
|
|
(needed_size_from_last_frame.y > size_y_for_scrollbars) && !(flags & ImGuiWindowFlags_NoScrollbar);
|
|
window->ScrollbarSizes = ImVec2(window->ScrollbarY ? style.ScrollbarSize : 0.0f,
|
|
window->ScrollbarX ? style.ScrollbarSize : 0.0f);
|
|
}
|
|
|
|
// UPDATE RECTANGLES (1- THOSE NOT AFFECTED BY SCROLLING)
|
|
// Update various regions. Variables they depends on should be set above in this function.
|
|
// We set this up after processing the resize grip so that our rectangles doesn't lag by a frame.
|
|
|
|
// Outer rectangle
|
|
// Not affected by window border size. Used by:
|
|
// - FindHoveredWindow() (w/ extra padding when border resize is enabled)
|
|
// - Begin() initial clipping rect for drawing window background and borders.
|
|
// - Begin() clipping whole child
|
|
const ImRect host_rect =
|
|
((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) && !window_is_child_tooltip)
|
|
? parent_window->ClipRect
|
|
: viewport_rect;
|
|
const ImRect outer_rect = window->Rect();
|
|
const ImRect title_bar_rect = window->TitleBarRect();
|
|
window->OuterRectClipped = outer_rect;
|
|
if (window->DockIsActive)
|
|
window->OuterRectClipped.Min.y += window->TitleBarHeight();
|
|
window->OuterRectClipped.ClipWith(host_rect);
|
|
|
|
// Inner rectangle
|
|
// Not affected by window border size. Used by:
|
|
// - InnerClipRect
|
|
// - ScrollToBringRectIntoView()
|
|
// - NavUpdatePageUpPageDown()
|
|
// - Scrollbar()
|
|
window->InnerRect.Min.x = window->Pos.x;
|
|
window->InnerRect.Min.y = window->Pos.y + decoration_up_height;
|
|
window->InnerRect.Max.x = window->Pos.x + window->Size.x - window->ScrollbarSizes.x;
|
|
window->InnerRect.Max.y = window->Pos.y + window->Size.y - window->ScrollbarSizes.y;
|
|
|
|
// Inner clipping rectangle.
|
|
// Will extend a little bit outside the normal work region.
|
|
// This is to allow e.g. Selectable or CollapsingHeader or some separators to cover that space.
|
|
// Force round operator last to ensure that e.g. (int)(max.x-min.x) in user's render code produce correct
|
|
// result. Note that if our window is collapsed we will end up with an inverted (~null) clipping rectangle which
|
|
// is the correct behavior. Affected by window/frame border size. Used by:
|
|
// - Begin() initial clip rect
|
|
float top_border_size =
|
|
(((flags & ImGuiWindowFlags_MenuBar) || !(flags & ImGuiWindowFlags_NoTitleBar)) ? style.FrameBorderSize
|
|
: window->WindowBorderSize);
|
|
window->InnerClipRect.Min.x = ImFloor(0.5f + window->InnerRect.Min.x +
|
|
ImMax(ImFloor(window->WindowPadding.x * 0.5f), window->WindowBorderSize));
|
|
window->InnerClipRect.Min.y = ImFloor(0.5f + window->InnerRect.Min.y + top_border_size);
|
|
window->InnerClipRect.Max.x = ImFloor(0.5f + window->InnerRect.Max.x -
|
|
ImMax(ImFloor(window->WindowPadding.x * 0.5f), window->WindowBorderSize));
|
|
window->InnerClipRect.Max.y = ImFloor(0.5f + window->InnerRect.Max.y - window->WindowBorderSize);
|
|
window->InnerClipRect.ClipWithFull(host_rect);
|
|
|
|
// 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 = ImFloor(window->Size.x * 0.65f);
|
|
else
|
|
window->ItemWidthDefault = ImFloor(g.FontSize * 16.0f);
|
|
|
|
// SCROLLING
|
|
|
|
// Lock down maximum scrolling
|
|
// The value of ScrollMax are ahead from ScrollbarX/ScrollbarY which is intentionally using InnerRect from
|
|
// previous rect in order to accommodate for right/bottom aligned items without creating a scrollbar.
|
|
window->ScrollMax.x =
|
|
ImMax(0.0f, window->ContentSize.x + window->WindowPadding.x * 2.0f - window->InnerRect.GetWidth());
|
|
window->ScrollMax.y =
|
|
ImMax(0.0f, window->ContentSize.y + window->WindowPadding.y * 2.0f - window->InnerRect.GetHeight());
|
|
|
|
// Apply scrolling
|
|
window->Scroll = CalcNextScrollFromScrollTargetAndClamp(window);
|
|
window->ScrollTarget = ImVec2(FLT_MAX, FLT_MAX);
|
|
|
|
// DRAWING
|
|
|
|
// Setup draw list and outer clipping rectangle
|
|
IM_ASSERT(window->DrawList->CmdBuffer.Size == 1 && window->DrawList->CmdBuffer[0].ElemCount == 0);
|
|
window->DrawList->PushTextureID(g.Font->ContainerAtlas->TexID);
|
|
PushClipRect(host_rect.Min, host_rect.Max, false);
|
|
|
|
// Draw modal or window list full viewport dimming background (for other viewports we'll render them in
|
|
// EndFrame)
|
|
ImGuiWindow* window_window_list = g.NavWindowingListWindow;
|
|
const bool dim_bg_for_modal = (flags & ImGuiWindowFlags_Modal) && window == GetTopMostPopupModal() &&
|
|
window->HiddenFramesCannotSkipItems <= 0;
|
|
const bool dim_bg_for_window_list =
|
|
g.NavWindowingTargetAnim &&
|
|
((window == g.NavWindowingTargetAnim->RootWindowDockTree) ||
|
|
(window == window_window_list && window_window_list->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);
|
|
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);
|
|
}
|
|
|
|
// Since 1.71, child window can render their decoration (bg color, border, scrollbars, etc.) within their parent
|
|
// to save a draw call. When using overlapping child windows, this will break the assumption that child z-order
|
|
// is mapped to submission order. We disable this when the parent window has zero vertices, which is a common
|
|
// pattern leading to laying out multiple overlapping child. We also disabled this when we have dimming overlay
|
|
// behind this specific one child.
|
|
// FIXME: More code may rely on explicit sorting of overlapping child window and would need to disable this
|
|
// somehow. Please get in contact if you are affected.
|
|
const bool is_undocked_or_docked_visible = !window->DockIsActive || window->DockTabIsVisible;
|
|
if (is_undocked_or_docked_visible)
|
|
{
|
|
bool render_decorations_in_parent = false;
|
|
if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) && !window_is_child_tooltip)
|
|
if (window->DrawList->CmdBuffer.back().ElemCount == 0 && parent_window->DrawList->VtxBuffer.Size > 0)
|
|
render_decorations_in_parent = true;
|
|
if (render_decorations_in_parent)
|
|
window->DrawList = parent_window->DrawList;
|
|
|
|
// Handle title bar, scrollbar, resize grips and resize borders
|
|
const ImGuiWindow* window_to_highlight = g.NavWindowingTarget ? g.NavWindowingTarget : g.NavWindow;
|
|
const bool title_bar_is_highlight =
|
|
want_focus ||
|
|
(window_to_highlight &&
|
|
(window->RootWindowForTitleBarHighlight == window_to_highlight->RootWindowForTitleBarHighlight ||
|
|
(window->DockNode && window->DockNode == window_to_highlight->DockNode)));
|
|
RenderWindowDecorations(window, title_bar_rect, title_bar_is_highlight, handle_borders_and_resize_grips,
|
|
resize_grip_count, resize_grip_col, resize_grip_draw_size);
|
|
|
|
if (render_decorations_in_parent)
|
|
window->DrawList = &window->DrawListInst;
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
// UPDATE RECTANGLES (2- THOSE AFFECTED BY SCROLLING)
|
|
|
|
// Work rectangle.
|
|
// Affected by window padding and border size. Used by:
|
|
// - Columns() for right-most edge
|
|
// - TreeNode(), CollapsingHeader() for right-most edge
|
|
// - BeginTabBar() for right-most edge
|
|
const bool allow_scrollbar_x =
|
|
!(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar);
|
|
const bool allow_scrollbar_y = !(flags & ImGuiWindowFlags_NoScrollbar);
|
|
const float work_rect_size_x =
|
|
(window->ContentSizeExplicit.x != 0.0f
|
|
? window->ContentSizeExplicit.x
|
|
: ImMax(allow_scrollbar_x ? window->ContentSize.x : 0.0f,
|
|
window->Size.x - window->WindowPadding.x * 2.0f - window->ScrollbarSizes.x));
|
|
const float work_rect_size_y =
|
|
(window->ContentSizeExplicit.y != 0.0f ? window->ContentSizeExplicit.y
|
|
: ImMax(allow_scrollbar_y ? window->ContentSize.y : 0.0f,
|
|
window->Size.y - window->WindowPadding.y * 2.0f -
|
|
decoration_up_height - window->ScrollbarSizes.y));
|
|
window->WorkRect.Min.x = ImFloor(window->InnerRect.Min.x - window->Scroll.x +
|
|
ImMax(window->WindowPadding.x, window->WindowBorderSize));
|
|
window->WorkRect.Min.y = ImFloor(window->InnerRect.Min.y - window->Scroll.y +
|
|
ImMax(window->WindowPadding.y, window->WindowBorderSize));
|
|
window->WorkRect.Max.x = window->WorkRect.Min.x + work_rect_size_x;
|
|
window->WorkRect.Max.y = window->WorkRect.Min.y + work_rect_size_y;
|
|
window->ParentWorkRect = window->WorkRect;
|
|
|
|
// [LEGACY] Content Region
|
|
// FIXME-OBSOLETE: window->ContentRegionRect.Max is currently very misleading / partly faulty, but some
|
|
// BeginChild() patterns relies on it. Used by:
|
|
// - Mouse wheel scrolling + many other things
|
|
window->ContentRegionRect.Min.x = window->Pos.x - window->Scroll.x + window->WindowPadding.x;
|
|
window->ContentRegionRect.Min.y =
|
|
window->Pos.y - window->Scroll.y + window->WindowPadding.y + decoration_up_height;
|
|
window->ContentRegionRect.Max.x =
|
|
window->ContentRegionRect.Min.x +
|
|
(window->ContentSizeExplicit.x != 0.0f
|
|
? window->ContentSizeExplicit.x
|
|
: (window->Size.x - window->WindowPadding.x * 2.0f - window->ScrollbarSizes.x));
|
|
window->ContentRegionRect.Max.y =
|
|
window->ContentRegionRect.Min.y +
|
|
(window->ContentSizeExplicit.y != 0.0f
|
|
? window->ContentSizeExplicit.y
|
|
: (window->Size.y - window->WindowPadding.y * 2.0f - decoration_up_height - 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.x = 0.0f + window->WindowPadding.x - window->Scroll.x;
|
|
window->DC.GroupOffset.x = 0.0f;
|
|
window->DC.ColumnsOffset.x = 0.0f;
|
|
window->DC.CursorStartPos =
|
|
window->Pos + ImVec2(window->DC.Indent.x + window->DC.ColumnsOffset.x,
|
|
decoration_up_height + 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.IdealMaxPos = window->DC.CursorStartPos;
|
|
window->DC.CurrLineSize = window->DC.PrevLineSize = ImVec2(0.0f, 0.0f);
|
|
window->DC.CurrLineTextBaseOffset = window->DC.PrevLineTextBaseOffset = 0.0f;
|
|
|
|
window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
|
|
window->DC.NavLayersActiveMask = window->DC.NavLayersActiveMaskNext;
|
|
window->DC.NavLayersActiveMaskNext = 0x00;
|
|
window->DC.NavHideHighlightOneFrame = false;
|
|
window->DC.NavHasScroll = (window->ScrollMax.y > 0.0f);
|
|
|
|
window->DC.MenuBarAppending = false;
|
|
window->DC.MenuColumns.Update(style.ItemSpacing.x, window_just_activated_by_user);
|
|
window->DC.TreeDepth = 0;
|
|
window->DC.TreeJumpToParentOnPopMask = 0x00;
|
|
window->DC.ChildWindows.resize(0);
|
|
window->DC.StateStorage = &window->StateStorage;
|
|
window->DC.CurrentColumns = NULL;
|
|
window->DC.LayoutType = ImGuiLayoutType_Vertical;
|
|
window->DC.ParentLayoutType = parent_window ? parent_window->DC.LayoutType : ImGuiLayoutType_Vertical;
|
|
window->DC.FocusCounterRegular = window->DC.FocusCounterTabStop = -1;
|
|
|
|
window->DC.ItemWidth = window->ItemWidthDefault;
|
|
window->DC.TextWrapPos = -1.0f; // disabled
|
|
window->DC.ItemWidthStack.resize(0);
|
|
window->DC.TextWrapPosStack.resize(0);
|
|
|
|
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); // <-- this is in the way for us to be able to defer and sort reappearing FocusWindow() calls
|
|
}
|
|
|
|
// Close requested by 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.
|
|
IMGUI_DEBUG_LOG_VIEWPORT("Window '%s' PlatformRequestClose\n", window->Name);
|
|
*p_open = false;
|
|
}
|
|
}
|
|
|
|
// Title bar
|
|
if (!(flags & ImGuiWindowFlags_NoTitleBar) && !window->DockIsActive)
|
|
RenderWindowTitleBarContents(window,
|
|
ImRect(title_bar_rect.Min.x + window->WindowBorderSize, title_bar_rect.Min.y,
|
|
title_bar_rect.Max.x - window->WindowBorderSize, title_bar_rect.Max.y),
|
|
name, p_open);
|
|
|
|
// Clear hit test shape every frame
|
|
window->HitTestHoleSize.x = window->HitTestHoleSize.y = 0;
|
|
|
|
// 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.NavWindow == window && g.ActiveId == 0)
|
|
if (g.ActiveId == window->MoveId)
|
|
if (g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_C))
|
|
LogToClipboard();
|
|
*/
|
|
|
|
if (g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable)
|
|
{
|
|
// Docking: Dragging a dockable window (or any of its child) turns it into a drag and drop source.
|
|
// We need to do this _before_ we overwrite window->DC.LastItemId below because
|
|
// BeginDockableDragDropSource() also overwrites it.
|
|
if (g.MovingWindow == window && g.IO.KeyShift == false)
|
|
if ((window->RootWindowDockTree->Flags & ImGuiWindowFlags_NoDocking) == 0)
|
|
BeginDockableDragDropSource(window);
|
|
|
|
// Docking: Any dockable window can act as a target. For dock node hosts we call
|
|
// BeginDockableDragDropTarget() in DockNodeUpdate() instead.
|
|
if (g.DragDropActive && !(flags & ImGuiWindowFlags_NoDocking))
|
|
if (g.MovingWindow == NULL || g.MovingWindow->RootWindowDockTree != window)
|
|
if ((window == window->RootWindowDockTree) && !(window->Flags & ImGuiWindowFlags_DockNodeHost))
|
|
BeginDockableDragDropTarget(window);
|
|
}
|
|
|
|
// We fill last item data based on Title Bar/Tab, in order for IsItemHovered() and IsItemActive() to be usable
|
|
// after Begin(). This is useful to allow creating context menus on title bar only, etc.
|
|
if (window->DockIsActive)
|
|
SetLastItemData(window->ID, g.CurrentItemFlags, window->DockTabItemStatusFlags, window->DockTabItemRect);
|
|
else
|
|
SetLastItemData(window->MoveId, g.CurrentItemFlags,
|
|
IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max, false)
|
|
? ImGuiItemStatusFlags_HoveredRect
|
|
: 0,
|
|
title_bar_rect);
|
|
|
|
# ifdef IMGUI_ENABLE_TEST_ENGINE
|
|
if (!(window->Flags & ImGuiWindowFlags_NoTitleBar))
|
|
IMGUI_TEST_ENGINE_ITEM_ADD(g.LastItemData.Rect, g.LastItemData.ID);
|
|
# endif
|
|
}
|
|
else
|
|
{
|
|
// Append
|
|
SetCurrentViewport(window, window->Viewport);
|
|
SetCurrentWindow(window);
|
|
}
|
|
|
|
// Pull/inherit current state
|
|
window->DC.NavFocusScopeIdCurrent = (flags & ImGuiWindowFlags_ChildWindow)
|
|
? parent_window->DC.NavFocusScopeIdCurrent
|
|
: window->GetID("#FOCUSSCOPE"); // Inherit from parent only // -V595
|
|
|
|
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)
|
|
window->WriteAccessed = false;
|
|
window->BeginCount++;
|
|
g.NextWindowData.ClearFlags();
|
|
|
|
// Update visibility
|
|
if (first_begin_of_the_frame)
|
|
{
|
|
// When we are about to select this tab (which will only be visible on the _next frame_), flag it with a
|
|
// non-zero HiddenFramesCannotSkipItems. This will have the important effect of actually returning true in
|
|
// Begin() and not setting SkipItems, allowing an earlier submission of the window contents. This is analogous
|
|
// to regular windows being hidden from one frame. It is especially important as e.g. nested TabBars would
|
|
// otherwise generate flicker in the form of one empty frame, or focus requests won't be processed.
|
|
if (window->DockIsActive && !window->DockTabIsVisible)
|
|
{
|
|
if (window->LastFrameJustFocused == g.FrameCount)
|
|
window->HiddenFramesCannotSkipItems = 1;
|
|
else
|
|
window->HiddenFramesCanSkipItems = 1;
|
|
}
|
|
|
|
if (flags & ImGuiWindowFlags_ChildWindow)
|
|
{
|
|
// 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->DockIsActive));
|
|
if (!(flags & ImGuiWindowFlags_AlwaysAutoResize) && window->AutoFitFramesX <= 0 &&
|
|
window->AutoFitFramesY <= 0) // FIXME: Doesn't make sense for ChildWindow??
|
|
if (!g.LogEnabled)
|
|
if (window->OuterRectClipped.Min.x >= window->OuterRectClipped.Max.x ||
|
|
window->OuterRectClipped.Min.y >= window->OuterRectClipped.Max.y)
|
|
window->HiddenFramesCanSkipItems = 1;
|
|
|
|
// Hide along with parent or if parent is collapsed
|
|
if (parent_window && (parent_window->Collapsed || parent_window->HiddenFramesCanSkipItems > 0))
|
|
window->HiddenFramesCanSkipItems = 1;
|
|
if (parent_window && (parent_window->Collapsed || parent_window->HiddenFramesCannotSkipItems > 0))
|
|
window->HiddenFramesCannotSkipItems = 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->HiddenFramesCanSkipItems = 1;
|
|
|
|
// Update the Hidden flag
|
|
window->Hidden = (window->HiddenFramesCanSkipItems > 0) || (window->HiddenFramesCannotSkipItems > 0) ||
|
|
(window->HiddenFramesForRenderOnly > 0);
|
|
|
|
// Disable inputs for requested number of frames
|
|
if (window->DisableInputsFrames > 0)
|
|
{
|
|
window->DisableInputsFrames--;
|
|
window->Flags |= ImGuiWindowFlags_NoInputs;
|
|
}
|
|
|
|
// Update the SkipItems flag, used to early out of all items functions (no layout required)
|
|
bool skip_items = false;
|
|
if (window->Collapsed || !window->Active || window->Hidden)
|
|
if (window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0 && window->HiddenFramesCannotSkipItems <= 0)
|
|
skip_items = true;
|
|
window->SkipItems = skip_items;
|
|
|
|
// Sanity check: there are two spots which can set Appearing = true
|
|
// - when 'window_just_activated_by_user' is set -> HiddenFramesCannotSkipItems is set -> SkipItems always false
|
|
// - in BeginDocked() path when DockNodeIsVisible == DockTabIsVisible == true -> hidden _should_ be all zero //
|
|
// FIXME: Not formally proven, hence the assert.
|
|
if (window->SkipItems && !window->Appearing)
|
|
IM_ASSERT(window->Appearing ==
|
|
false); // Please report on GitHub if this triggers: https://github.com/ocornut/imgui/issues/4177
|
|
}
|
|
|
|
return !window->SkipItems;
|
|
}
|
|
|
|
void ImGui::End()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
|
|
// Error checking: verify that user hasn't called End() too many times!
|
|
if (g.CurrentWindowStack.Size <= 1 && g.WithinFrameScopeWithImplicitWindow)
|
|
{
|
|
IM_ASSERT_USER_ERROR(g.CurrentWindowStack.Size > 1, "Calling End() too many times!");
|
|
return;
|
|
}
|
|
IM_ASSERT(g.CurrentWindowStack.Size > 0);
|
|
|
|
// Error checking: verify that user doesn't directly call End() on a child window.
|
|
if ((window->Flags & ImGuiWindowFlags_ChildWindow) && !(window->Flags & ImGuiWindowFlags_DockNodeHost) &&
|
|
!window->DockIsActive)
|
|
IM_ASSERT_USER_ERROR(g.WithinEndChild, "Must call EndChild() and not End()!");
|
|
|
|
// Close anything that is open
|
|
if (window->DC.CurrentColumns)
|
|
EndColumns();
|
|
if (!(window->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.LastItemData = g.CurrentWindowStack.back().ParentLastItemDataBackup;
|
|
g.CurrentWindowStack.pop_back();
|
|
if (window->Flags & ImGuiWindowFlags_Popup)
|
|
g.BeginPopupStack.pop_back();
|
|
window->DC.StackSizesOnBegin.CompareWithCurrentState();
|
|
SetCurrentWindow(g.CurrentWindowStack.Size == 0 ? NULL : g.CurrentWindowStack.back().Window);
|
|
if (g.CurrentWindow)
|
|
SetCurrentViewport(g.CurrentWindow, g.CurrentWindow->Viewport);
|
|
}
|
|
|
|
void ImGui::BringWindowToFocusFront(ImGuiWindow* window)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(window == window->RootWindow);
|
|
|
|
const int cur_order = window->FocusOrder;
|
|
IM_ASSERT(g.WindowsFocusOrder[cur_order] == window);
|
|
if (g.WindowsFocusOrder.back() == window)
|
|
return;
|
|
|
|
const int new_order = g.WindowsFocusOrder.Size - 1;
|
|
for (int n = cur_order; n < new_order; n++)
|
|
{
|
|
g.WindowsFocusOrder[n] = g.WindowsFocusOrder[n + 1];
|
|
g.WindowsFocusOrder[n]->FocusOrder--;
|
|
IM_ASSERT(g.WindowsFocusOrder[n]->FocusOrder == n);
|
|
}
|
|
g.WindowsFocusOrder[new_order] = window;
|
|
window->FocusOrder = (short)new_order;
|
|
}
|
|
|
|
void ImGui::BringWindowToDisplayFront(ImGuiWindow* window)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* current_front_window = g.Windows.back();
|
|
if (current_front_window == window ||
|
|
current_front_window->RootWindowDockTree == window) // Cheap early out (could be better)
|
|
return;
|
|
for (int i = g.Windows.Size - 2; i >= 0; i--) // We can ignore the top-most window
|
|
if (g.Windows[i] == window)
|
|
{
|
|
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::BringWindowToDisplayBack(ImGuiWindow* window)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (g.Windows[0] == window)
|
|
return;
|
|
for (int i = 0; i < g.Windows.Size; i++)
|
|
if (g.Windows[i] == window)
|
|
{
|
|
memmove(&g.Windows[1], &g.Windows[0], (size_t)i * sizeof(ImGuiWindow*));
|
|
g.Windows[0] = window;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Moving window to front of display and set focus (which happens to be back of our sorted list)
|
|
void ImGui::FocusWindow(ImGuiWindow* window)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
if (g.NavWindow != window)
|
|
{
|
|
g.NavWindow = window;
|
|
if (window && g.NavDisableMouseHover)
|
|
g.NavMousePosDirty = true;
|
|
g.NavId = window ? window->NavLastIds[0] : 0; // Restore NavId
|
|
g.NavFocusScopeId = 0;
|
|
g.NavIdIsAlive = false;
|
|
g.NavLayer = ImGuiNavLayer_Main;
|
|
g.NavInitRequest = g.NavMoveRequest = false;
|
|
NavUpdateAnyRequestFlag();
|
|
// IMGUI_DEBUG_LOG("FocusWindow(\"%s\")\n", window ? window->Name : NULL);
|
|
}
|
|
|
|
// Close popups if any
|
|
ClosePopupsOverWindow(window, false);
|
|
|
|
// Move the root window to the top of the pile
|
|
IM_ASSERT(window == NULL || window->RootWindowDockTree != NULL);
|
|
ImGuiWindow* focus_front_window = window ? window->RootWindow : NULL;
|
|
ImGuiWindow* display_front_window = window ? window->RootWindowDockTree : NULL;
|
|
ImGuiDockNode* dock_node = window ? window->DockNode : NULL;
|
|
bool active_id_window_is_dock_node_host =
|
|
(g.ActiveIdWindow && dock_node && dock_node->HostWindow == g.ActiveIdWindow);
|
|
|
|
// Steal active widgets. Some of the cases it triggers includes:
|
|
// - Focus a window while an InputText in another window is active, if focus happens before the old InputText can
|
|
// run.
|
|
// - When using Nav to activate menu items (due to timing of activating on press->new window appears->losing
|
|
// ActiveId)
|
|
// - Using dock host items (tab, collapse button) can trigger this before we redirect the ActiveIdWindow toward the
|
|
// child window.
|
|
if (g.ActiveId != 0 && g.ActiveIdWindow && g.ActiveIdWindow->RootWindow != focus_front_window)
|
|
if (!g.ActiveIdNoClearOnFocusLoss && !active_id_window_is_dock_node_host)
|
|
ClearActiveID();
|
|
|
|
// Passing NULL allow to disable keyboard focus
|
|
if (!window)
|
|
return;
|
|
window->LastFrameJustFocused = g.FrameCount;
|
|
|
|
// Select in dock node
|
|
if (dock_node && dock_node->TabBar)
|
|
dock_node->TabBar->SelectedTabId = dock_node->TabBar->NextSelectedTabId = window->ID;
|
|
|
|
// Bring to front
|
|
BringWindowToFocusFront(focus_front_window);
|
|
if (((window->Flags | focus_front_window->Flags | display_front_window->Flags) &
|
|
ImGuiWindowFlags_NoBringToFrontOnFocus) == 0)
|
|
BringWindowToDisplayFront(display_front_window);
|
|
}
|
|
|
|
void ImGui::FocusTopMostWindowUnderOne(ImGuiWindow* under_this_window, ImGuiWindow* ignore_window)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
const int start_idx =
|
|
((under_this_window != NULL) ? FindWindowFocusIndex(under_this_window) : g.WindowsFocusOrder.Size) - 1;
|
|
for (int i = start_idx; i >= 0; i--)
|
|
{
|
|
// We may later decide to test for different NoXXXInputs based on the active navigation input (mouse vs nav) but
|
|
// that may feel more confusing to the user.
|
|
ImGuiWindow* window = g.WindowsFocusOrder[i];
|
|
IM_ASSERT(window == window->RootWindow);
|
|
if (window != ignore_window && window->WasActive)
|
|
if ((window->Flags & (ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs)) !=
|
|
(ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs))
|
|
{
|
|
// FIXME-DOCK: This is failing (lagging by one frame) for docked windows.
|
|
// If A and B are docked into window and B disappear, at the NewFrame() call site
|
|
// window->NavLastChildNavWindow will still point to B. We might leverage the tab order implicitly
|
|
// stored in window->DockNodeAsHost->TabBar (essentially the 'most_recently_selected_tab' code in tab
|
|
// bar will do that but on next update) to tell which is the "previous" window. Or we may leverage
|
|
// 'LastFrameFocused/LastFrameJustFocused' and have this function handle child window itself?
|
|
ImGuiWindow* focus_window = NavRestoreLastChildNavWindow(window);
|
|
FocusWindow(focus_window);
|
|
return;
|
|
}
|
|
}
|
|
FocusWindow(NULL);
|
|
}
|
|
|
|
// Important: this alone doesn't alter current ImDrawList state. This is called by PushFont/PopFont only.
|
|
void ImGui::SetCurrentFont(ImFont* font)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(
|
|
font &&
|
|
font->IsLoaded()); // Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ?
|
|
IM_ASSERT(font->Scale > 0.0f);
|
|
g.Font = font;
|
|
g.FontBaseSize = ImMax(1.0f, g.IO.FontGlobalScale * g.Font->FontSize * g.Font->Scale);
|
|
g.FontSize = g.CurrentWindow ? g.CurrentWindow->CalcFontSize() : 0.0f;
|
|
|
|
ImFontAtlas* atlas = g.Font->ContainerAtlas;
|
|
g.DrawListSharedData.TexUvWhitePixel = atlas->TexUvWhitePixel;
|
|
g.DrawListSharedData.TexUvLines = atlas->TexUvLines;
|
|
g.DrawListSharedData.Font = g.Font;
|
|
g.DrawListSharedData.FontSize = g.FontSize;
|
|
}
|
|
|
|
void ImGui::PushFont(ImFont* font)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (!font)
|
|
font = GetDefaultFont();
|
|
SetCurrentFont(font);
|
|
g.FontStack.push_back(font);
|
|
g.CurrentWindow->DrawList->PushTextureID(font->ContainerAtlas->TexID);
|
|
}
|
|
|
|
void ImGui::PopFont()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
g.CurrentWindow->DrawList->PopTextureID();
|
|
g.FontStack.pop_back();
|
|
SetCurrentFont(g.FontStack.empty() ? GetDefaultFont() : g.FontStack.back());
|
|
}
|
|
|
|
void ImGui::PushItemFlag(ImGuiItemFlags option, bool enabled)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiItemFlags item_flags = g.CurrentItemFlags;
|
|
IM_ASSERT(item_flags == g.ItemFlagsStack.back());
|
|
if (enabled)
|
|
item_flags |= option;
|
|
else
|
|
item_flags &= ~option;
|
|
g.CurrentItemFlags = item_flags;
|
|
g.ItemFlagsStack.push_back(item_flags);
|
|
}
|
|
|
|
void ImGui::PopItemFlag()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(g.ItemFlagsStack.Size >
|
|
1); // Too many calls to PopItemFlag() - we always leave a 0 at the bottom of the stack.
|
|
g.ItemFlagsStack.pop_back();
|
|
g.CurrentItemFlags = g.ItemFlagsStack.back();
|
|
}
|
|
|
|
// BeginDisabled()/EndDisabled()
|
|
// - Those can be nested but this cannot be used to enable an already disabled section (a single BeginDisabled(true) in
|
|
// the stack is enough to keep things disabled)
|
|
// - Visually this is currently altering alpha, but it is expected that in a future styling system this would work
|
|
// differently.
|
|
// - Feedback welcome at https://github.com/ocornut/imgui/issues/211
|
|
// - BeginDisabled(false) essentially does nothing useful but is provided to facilitate use of boolean expressions. If
|
|
// you can avoid calling BeginDisabled(False)/EndDisabled() best to avoid it.
|
|
// - Optimized shortcuts instead of PushStyleVar() + PushItemFlag()
|
|
void ImGui::BeginDisabled(bool disabled)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
bool was_disabled = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0;
|
|
g.DisabledAlphaBackup = g.Style.Alpha;
|
|
if (!was_disabled && disabled)
|
|
g.Style.Alpha *=
|
|
g.Style.DisabledAlpha; // PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * g.Style.DisabledAlpha);
|
|
if (was_disabled || disabled)
|
|
g.CurrentItemFlags |= ImGuiItemFlags_Disabled;
|
|
g.ItemFlagsStack.push_back(g.CurrentItemFlags);
|
|
}
|
|
|
|
void ImGui::EndDisabled()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
bool was_disabled = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0;
|
|
// PopItemFlag();
|
|
g.ItemFlagsStack.pop_back();
|
|
g.CurrentItemFlags &= ~ImGuiItemFlags_Disabled;
|
|
if (was_disabled && (g.CurrentItemFlags & ImGuiItemFlags_Disabled) == 0)
|
|
g.Style.Alpha = g.DisabledAlphaBackup; // PopStyleVar();
|
|
}
|
|
|
|
// FIXME: Look into renaming this once we have settled the new Focus/Activation/TabStop system.
|
|
void ImGui::PushAllowKeyboardFocus(bool allow_keyboard_focus)
|
|
{
|
|
PushItemFlag(ImGuiItemFlags_NoTabStop, !allow_keyboard_focus);
|
|
}
|
|
|
|
void ImGui::PopAllowKeyboardFocus()
|
|
{
|
|
PopItemFlag();
|
|
}
|
|
|
|
void ImGui::PushButtonRepeat(bool repeat)
|
|
{
|
|
PushItemFlag(ImGuiItemFlags_ButtonRepeat, repeat);
|
|
}
|
|
|
|
void ImGui::PopButtonRepeat()
|
|
{
|
|
PopItemFlag();
|
|
}
|
|
|
|
void ImGui::PushTextWrapPos(float wrap_pos_x)
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
window->DC.TextWrapPosStack.push_back(window->DC.TextWrapPos);
|
|
window->DC.TextWrapPos = wrap_pos_x;
|
|
}
|
|
|
|
void ImGui::PopTextWrapPos()
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
window->DC.TextWrapPos = window->DC.TextWrapPosStack.back();
|
|
window->DC.TextWrapPosStack.pop_back();
|
|
}
|
|
|
|
// FIXME: We are exposing the docking hierarchy to end-user here (via IsWindowHovered, IsWindowFocused) which is
|
|
// unusual.
|
|
bool ImGui::IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent)
|
|
{
|
|
if (window->RootWindowDockTree == potential_parent)
|
|
return true;
|
|
while (window != NULL)
|
|
{
|
|
if (window == potential_parent)
|
|
return true;
|
|
window = window->ParentWindow;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ImGui::IsWindowAbove(ImGuiWindow* potential_above, ImGuiWindow* potential_below)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
for (int i = g.Windows.Size - 1; i >= 0; i--)
|
|
{
|
|
ImGuiWindow* candidate_window = g.Windows[i];
|
|
if (candidate_window == potential_above)
|
|
return true;
|
|
if (candidate_window == potential_below)
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ImGui::IsWindowHovered(ImGuiHoveredFlags flags)
|
|
{
|
|
IM_ASSERT((flags & ImGuiHoveredFlags_AllowWhenOverlapped) == 0); // Flags not supported by this function
|
|
ImGuiContext& g = *GImGui;
|
|
if (g.HoveredWindow == NULL)
|
|
return false;
|
|
|
|
if ((flags & ImGuiHoveredFlags_AnyWindow) == 0)
|
|
{
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
switch (flags & (ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows))
|
|
{
|
|
case ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows:
|
|
if (g.HoveredWindow->RootWindow != window->RootWindow)
|
|
return false;
|
|
break;
|
|
case ImGuiHoveredFlags_RootWindow:
|
|
if (g.HoveredWindow != window->RootWindow)
|
|
return false;
|
|
break;
|
|
case ImGuiHoveredFlags_ChildWindows:
|
|
if (!IsWindowChildOf(g.HoveredWindow, window))
|
|
return false;
|
|
break;
|
|
default:
|
|
if (g.HoveredWindow != window)
|
|
return false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!IsWindowContentHoverable(g.HoveredWindow, flags))
|
|
return false;
|
|
if (!(flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
|
|
if (g.ActiveId != 0 && !g.ActiveIdAllowOverlap && g.ActiveId != g.HoveredWindow->MoveId)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool ImGui::IsWindowFocused(ImGuiFocusedFlags flags)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
if (flags & ImGuiFocusedFlags_AnyWindow)
|
|
return g.NavWindow != NULL;
|
|
|
|
IM_ASSERT(g.CurrentWindow); // Not inside a Begin()/End()
|
|
switch (flags & (ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows))
|
|
{
|
|
case ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows:
|
|
return g.NavWindow && g.NavWindow->RootWindow == g.CurrentWindow->RootWindow;
|
|
case ImGuiFocusedFlags_RootWindow:
|
|
return g.NavWindow == g.CurrentWindow->RootWindow;
|
|
case ImGuiFocusedFlags_ChildWindows:
|
|
return g.NavWindow && IsWindowChildOf(g.NavWindow, g.CurrentWindow);
|
|
default:
|
|
return g.NavWindow == g.CurrentWindow;
|
|
}
|
|
}
|
|
|
|
ImGuiID ImGui::GetWindowDockID()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
return g.CurrentWindow->DockId;
|
|
}
|
|
|
|
bool ImGui::IsWindowDocked()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
return g.CurrentWindow->DockIsActive;
|
|
}
|
|
|
|
// Can we focus this window with CTRL+TAB (or PadMenu + PadFocusPrev/PadFocusNext)
|
|
// Note that NoNavFocus makes the window not reachable with CTRL+TAB but it can still be focused with mouse or
|
|
// programmatically. If you want a window to never be focused, you may use the e.g. NoInputs flag.
|
|
bool ImGui::IsWindowNavFocusable(ImGuiWindow* window)
|
|
{
|
|
return window->WasActive && window == window->RootWindow && !(window->Flags & ImGuiWindowFlags_NoNavFocus);
|
|
}
|
|
|
|
float ImGui::GetWindowWidth()
|
|
{
|
|
ImGuiWindow* window = GImGui->CurrentWindow;
|
|
return window->Size.x;
|
|
}
|
|
|
|
float ImGui::GetWindowHeight()
|
|
{
|
|
ImGuiWindow* window = GImGui->CurrentWindow;
|
|
return window->Size.y;
|
|
}
|
|
|
|
ImVec2 ImGui::GetWindowPos()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
return window->Pos;
|
|
}
|
|
|
|
void ImGui::SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond)
|
|
{
|
|
// Test condition (NB: bit 0 is always true) and clear flags for next time
|
|
if (cond && (window->SetWindowPosAllowFlags & cond) == 0)
|
|
return;
|
|
|
|
IM_ASSERT(cond == 0 ||
|
|
ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
|
|
window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
|
|
window->SetWindowPosVal = ImVec2(FLT_MAX, FLT_MAX);
|
|
|
|
// Set
|
|
const ImVec2 old_pos = window->Pos;
|
|
window->Pos = ImFloor(pos);
|
|
ImVec2 offset = window->Pos - old_pos;
|
|
window->DC.CursorPos += offset; // As we happen to move the window while it is being appended to (which is a bad
|
|
// idea - will smear) let's at least offset the cursor
|
|
window->DC.CursorMaxPos += offset; // And more importantly we need to offset CursorMaxPos/CursorStartPos this so
|
|
// ContentSize calculation doesn't get affected.
|
|
window->DC.IdealMaxPos += offset;
|
|
window->DC.CursorStartPos += offset;
|
|
}
|
|
|
|
void ImGui::SetWindowPos(const ImVec2& pos, ImGuiCond cond)
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindowRead();
|
|
SetWindowPos(window, pos, cond);
|
|
}
|
|
|
|
void ImGui::SetWindowPos(const char* name, const ImVec2& pos, ImGuiCond cond)
|
|
{
|
|
if (ImGuiWindow* window = FindWindowByName(name))
|
|
SetWindowPos(window, pos, cond);
|
|
}
|
|
|
|
ImVec2 ImGui::GetWindowSize()
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindowRead();
|
|
return window->Size;
|
|
}
|
|
|
|
void ImGui::SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond)
|
|
{
|
|
// Test condition (NB: bit 0 is always true) and clear flags for next time
|
|
if (cond && (window->SetWindowSizeAllowFlags & cond) == 0)
|
|
return;
|
|
|
|
IM_ASSERT(cond == 0 ||
|
|
ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
|
|
window->SetWindowSizeAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
|
|
|
|
// Set
|
|
if (size.x > 0.0f)
|
|
{
|
|
window->AutoFitFramesX = 0;
|
|
window->SizeFull.x = IM_FLOOR(size.x);
|
|
}
|
|
else
|
|
{
|
|
window->AutoFitFramesX = 2;
|
|
window->AutoFitOnlyGrows = false;
|
|
}
|
|
if (size.y > 0.0f)
|
|
{
|
|
window->AutoFitFramesY = 0;
|
|
window->SizeFull.y = IM_FLOOR(size.y);
|
|
}
|
|
else
|
|
{
|
|
window->AutoFitFramesY = 2;
|
|
window->AutoFitOnlyGrows = false;
|
|
}
|
|
}
|
|
|
|
void ImGui::SetWindowSize(const ImVec2& size, ImGuiCond cond)
|
|
{
|
|
SetWindowSize(GImGui->CurrentWindow, size, cond);
|
|
}
|
|
|
|
void ImGui::SetWindowSize(const char* name, const ImVec2& size, ImGuiCond cond)
|
|
{
|
|
if (ImGuiWindow* window = FindWindowByName(name))
|
|
SetWindowSize(window, size, cond);
|
|
}
|
|
|
|
void ImGui::SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond)
|
|
{
|
|
// Test condition (NB: bit 0 is always true) and clear flags for next time
|
|
if (cond && (window->SetWindowCollapsedAllowFlags & cond) == 0)
|
|
return;
|
|
window->SetWindowCollapsedAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
|
|
|
|
// Set
|
|
window->Collapsed = collapsed;
|
|
}
|
|
|
|
void ImGui::SetWindowHitTestHole(ImGuiWindow* window, const ImVec2& pos, const ImVec2& size)
|
|
{
|
|
IM_ASSERT(window->HitTestHoleSize.x == 0); // We don't support multiple holes/hit test filters
|
|
window->HitTestHoleSize = ImVec2ih(size);
|
|
window->HitTestHoleOffset = ImVec2ih(pos - window->Pos);
|
|
}
|
|
|
|
void ImGui::SetWindowCollapsed(bool collapsed, ImGuiCond cond)
|
|
{
|
|
SetWindowCollapsed(GImGui->CurrentWindow, collapsed, cond);
|
|
}
|
|
|
|
bool ImGui::IsWindowCollapsed()
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindowRead();
|
|
return window->Collapsed;
|
|
}
|
|
|
|
bool ImGui::IsWindowAppearing()
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindowRead();
|
|
return window->Appearing;
|
|
}
|
|
|
|
void ImGui::SetWindowCollapsed(const char* name, bool collapsed, ImGuiCond cond)
|
|
{
|
|
if (ImGuiWindow* window = FindWindowByName(name))
|
|
SetWindowCollapsed(window, collapsed, cond);
|
|
}
|
|
|
|
void ImGui::SetWindowFocus()
|
|
{
|
|
FocusWindow(GImGui->CurrentWindow);
|
|
}
|
|
|
|
void ImGui::SetWindowFocus(const char* name)
|
|
{
|
|
if (name)
|
|
{
|
|
if (ImGuiWindow* window = FindWindowByName(name))
|
|
FocusWindow(window);
|
|
}
|
|
else
|
|
{
|
|
FocusWindow(NULL);
|
|
}
|
|
}
|
|
|
|
void ImGui::SetNextWindowPos(const ImVec2& pos, ImGuiCond cond, const ImVec2& pivot)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(cond == 0 ||
|
|
ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
|
|
g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasPos;
|
|
g.NextWindowData.PosVal = pos;
|
|
g.NextWindowData.PosPivotVal = pivot;
|
|
g.NextWindowData.PosCond = cond ? cond : ImGuiCond_Always;
|
|
g.NextWindowData.PosUndock = true;
|
|
}
|
|
|
|
void ImGui::SetNextWindowSize(const ImVec2& size, ImGuiCond cond)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(cond == 0 ||
|
|
ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
|
|
g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasSize;
|
|
g.NextWindowData.SizeVal = size;
|
|
g.NextWindowData.SizeCond = cond ? cond : ImGuiCond_Always;
|
|
}
|
|
|
|
void ImGui::SetNextWindowSizeConstraints(const ImVec2& size_min,
|
|
const ImVec2& size_max,
|
|
ImGuiSizeCallback custom_callback,
|
|
void* custom_callback_user_data)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasSizeConstraint;
|
|
g.NextWindowData.SizeConstraintRect = ImRect(size_min, size_max);
|
|
g.NextWindowData.SizeCallback = custom_callback;
|
|
g.NextWindowData.SizeCallbackUserData = custom_callback_user_data;
|
|
}
|
|
|
|
// Content size = inner scrollable rectangle, padded with WindowPadding.
|
|
// SetNextWindowContentSize(ImVec2(100,100) + ImGuiWindowFlags_AlwaysAutoResize will always allow submitting a 100x100
|
|
// item.
|
|
void ImGui::SetNextWindowContentSize(const ImVec2& size)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasContentSize;
|
|
g.NextWindowData.ContentSizeVal = ImFloor(size);
|
|
}
|
|
|
|
void ImGui::SetNextWindowScroll(const ImVec2& scroll)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasScroll;
|
|
g.NextWindowData.ScrollVal = scroll;
|
|
}
|
|
|
|
void ImGui::SetNextWindowCollapsed(bool collapsed, ImGuiCond cond)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(cond == 0 ||
|
|
ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
|
|
g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasCollapsed;
|
|
g.NextWindowData.CollapsedVal = collapsed;
|
|
g.NextWindowData.CollapsedCond = cond ? cond : ImGuiCond_Always;
|
|
}
|
|
|
|
void ImGui::SetNextWindowFocus()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasFocus;
|
|
}
|
|
|
|
void ImGui::SetNextWindowBgAlpha(float alpha)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasBgAlpha;
|
|
g.NextWindowData.BgAlphaVal = alpha;
|
|
}
|
|
|
|
void ImGui::SetNextWindowViewport(ImGuiID id)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasViewport;
|
|
g.NextWindowData.ViewportId = id;
|
|
}
|
|
|
|
void ImGui::SetNextWindowDockID(ImGuiID id, ImGuiCond cond)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasDock;
|
|
g.NextWindowData.DockCond = cond ? cond : ImGuiCond_Always;
|
|
g.NextWindowData.DockId = id;
|
|
}
|
|
|
|
void ImGui::SetNextWindowClass(const ImGuiWindowClass* window_class)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT((window_class->ViewportFlagsOverrideSet & window_class->ViewportFlagsOverrideClear) ==
|
|
0); // Cannot set both set and clear for the same bit
|
|
g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasWindowClass;
|
|
g.NextWindowData.WindowClass = *window_class;
|
|
}
|
|
|
|
ImDrawList* ImGui::GetWindowDrawList()
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
return window->DrawList;
|
|
}
|
|
|
|
float ImGui::GetWindowDpiScale()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
return g.CurrentDpiScale;
|
|
}
|
|
|
|
ImGuiViewport* ImGui::GetWindowViewport()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(g.CurrentViewport != NULL && g.CurrentViewport == g.CurrentWindow->Viewport);
|
|
return g.CurrentViewport;
|
|
}
|
|
|
|
ImFont* ImGui::GetFont()
|
|
{
|
|
return GImGui->Font;
|
|
}
|
|
|
|
float ImGui::GetFontSize()
|
|
{
|
|
return GImGui->FontSize;
|
|
}
|
|
|
|
ImVec2 ImGui::GetFontTexUvWhitePixel()
|
|
{
|
|
return GImGui->DrawListSharedData.TexUvWhitePixel;
|
|
}
|
|
|
|
void ImGui::SetWindowFontScale(float scale)
|
|
{
|
|
IM_ASSERT(scale > 0.0f);
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
window->FontWindowScale = scale;
|
|
g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize();
|
|
}
|
|
|
|
void ImGui::ActivateItem(ImGuiID id)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
g.NavNextActivateId = id;
|
|
}
|
|
|
|
void ImGui::PushFocusScope(ImGuiID id)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
g.FocusScopeStack.push_back(window->DC.NavFocusScopeIdCurrent);
|
|
window->DC.NavFocusScopeIdCurrent = id;
|
|
}
|
|
|
|
void ImGui::PopFocusScope()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
IM_ASSERT(g.FocusScopeStack.Size > 0); // Too many PopFocusScope() ?
|
|
window->DC.NavFocusScopeIdCurrent = g.FocusScopeStack.back();
|
|
g.FocusScopeStack.pop_back();
|
|
}
|
|
|
|
void ImGui::SetKeyboardFocusHere(int offset)
|
|
{
|
|
IM_ASSERT(offset >= -1); // -1 is allowed but not below
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
g.TabFocusRequestNextWindow = window;
|
|
g.TabFocusRequestNextCounterRegular = window->DC.FocusCounterRegular + 1 + offset;
|
|
g.TabFocusRequestNextCounterTabStop = INT_MAX;
|
|
}
|
|
|
|
void ImGui::SetItemDefaultFocus()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
if (!window->Appearing)
|
|
return;
|
|
if (g.NavWindow == window->RootWindowForNav && (g.NavInitRequest || g.NavInitResultId != 0) &&
|
|
g.NavLayer == window->DC.NavLayerCurrent)
|
|
{
|
|
g.NavInitRequest = false;
|
|
g.NavInitResultId = g.LastItemData.ID;
|
|
g.NavInitResultRectRel = ImRect(g.LastItemData.Rect.Min - window->Pos, g.LastItemData.Rect.Max - window->Pos);
|
|
NavUpdateAnyRequestFlag();
|
|
if (!IsItemVisible())
|
|
SetScrollHereY();
|
|
}
|
|
}
|
|
|
|
void ImGui::SetStateStorage(ImGuiStorage* tree)
|
|
{
|
|
ImGuiWindow* window = GImGui->CurrentWindow;
|
|
window->DC.StateStorage = tree ? tree : &window->StateStorage;
|
|
}
|
|
|
|
ImGuiStorage* ImGui::GetStateStorage()
|
|
{
|
|
ImGuiWindow* window = GImGui->CurrentWindow;
|
|
return window->DC.StateStorage;
|
|
}
|
|
|
|
void ImGui::PushID(const char* str_id)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
ImGuiID id = window->GetIDNoKeepAlive(str_id);
|
|
window->IDStack.push_back(id);
|
|
}
|
|
|
|
void ImGui::PushID(const char* str_id_begin, const char* str_id_end)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
ImGuiID id = window->GetIDNoKeepAlive(str_id_begin, str_id_end);
|
|
window->IDStack.push_back(id);
|
|
}
|
|
|
|
void ImGui::PushID(const void* ptr_id)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
ImGuiID id = window->GetIDNoKeepAlive(ptr_id);
|
|
window->IDStack.push_back(id);
|
|
}
|
|
|
|
void ImGui::PushID(int int_id)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
ImGuiID id = window->GetIDNoKeepAlive(int_id);
|
|
window->IDStack.push_back(id);
|
|
}
|
|
|
|
// Push a given id value ignoring the ID stack as a seed.
|
|
void ImGui::PushOverrideID(ImGuiID id)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
window->IDStack.push_back(id);
|
|
}
|
|
|
|
// Helper to avoid a common series of PushOverrideID -> GetID() -> PopID() call
|
|
// (note that when using this pattern, TestEngine's "Stack Tool" will tend to not display the intermediate stack level.
|
|
// for that to work we would need to do PushOverrideID() -> ItemAdd() -> PopID() which would alter widget code a little
|
|
// more)
|
|
ImGuiID ImGui::GetIDWithSeed(const char* str, const char* str_end, ImGuiID seed)
|
|
{
|
|
ImGuiID id = ImHashStr(str, str_end ? (str_end - str) : 0, seed);
|
|
ImGui::KeepAliveID(id);
|
|
# ifdef IMGUI_ENABLE_TEST_ENGINE
|
|
ImGuiContext& g = *GImGui;
|
|
IMGUI_TEST_ENGINE_ID_INFO2(id, ImGuiDataType_String, str, str_end);
|
|
# endif
|
|
return id;
|
|
}
|
|
|
|
void ImGui::PopID()
|
|
{
|
|
ImGuiWindow* window = GImGui->CurrentWindow;
|
|
IM_ASSERT(window->IDStack.Size > 1); // Too many PopID(), or could be popping in a wrong/different window?
|
|
window->IDStack.pop_back();
|
|
}
|
|
|
|
ImGuiID ImGui::GetID(const char* str_id)
|
|
{
|
|
ImGuiWindow* window = GImGui->CurrentWindow;
|
|
return window->GetID(str_id);
|
|
}
|
|
|
|
ImGuiID ImGui::GetID(const char* str_id_begin, const char* str_id_end)
|
|
{
|
|
ImGuiWindow* window = GImGui->CurrentWindow;
|
|
return window->GetID(str_id_begin, str_id_end);
|
|
}
|
|
|
|
ImGuiID ImGui::GetID(const void* ptr_id)
|
|
{
|
|
ImGuiWindow* window = GImGui->CurrentWindow;
|
|
return window->GetID(ptr_id);
|
|
}
|
|
|
|
bool ImGui::IsRectVisible(const ImVec2& size)
|
|
{
|
|
ImGuiWindow* window = GImGui->CurrentWindow;
|
|
return window->ClipRect.Overlaps(ImRect(window->DC.CursorPos, window->DC.CursorPos + size));
|
|
}
|
|
|
|
bool ImGui::IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max)
|
|
{
|
|
ImGuiWindow* window = GImGui->CurrentWindow;
|
|
return window->ClipRect.Overlaps(ImRect(rect_min, rect_max));
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// [SECTION] ERROR CHECKING
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Helper function to verify ABI compatibility between caller code and compiled version of Dear ImGui.
|
|
// Verify that the type sizes are matching between the calling file's compilation unit and imgui.cpp's compilation unit
|
|
// If the user has inconsistent compilation settings, imgui configuration #define, packing pragma, etc. your user code
|
|
// may see different structures than what imgui.cpp sees, which is problematic.
|
|
// We usually require settings to be in imconfig.h to make sure that they are accessible to all compilation units
|
|
// involved with Dear ImGui.
|
|
bool ImGui::DebugCheckVersionAndDataLayout(const char* version,
|
|
size_t sz_io,
|
|
size_t sz_style,
|
|
size_t sz_vec2,
|
|
size_t sz_vec4,
|
|
size_t sz_vert,
|
|
size_t sz_idx)
|
|
{
|
|
bool error = false;
|
|
if (strcmp(version, IMGUI_VERSION) != 0)
|
|
{
|
|
error = true;
|
|
IM_ASSERT(strcmp(version, IMGUI_VERSION) == 0 && "Mismatched version string!");
|
|
}
|
|
if (sz_io != sizeof(ImGuiIO))
|
|
{
|
|
error = true;
|
|
IM_ASSERT(sz_io == sizeof(ImGuiIO) && "Mismatched struct layout!");
|
|
}
|
|
if (sz_style != sizeof(ImGuiStyle))
|
|
{
|
|
error = true;
|
|
IM_ASSERT(sz_style == sizeof(ImGuiStyle) && "Mismatched struct layout!");
|
|
}
|
|
if (sz_vec2 != sizeof(ImVec2))
|
|
{
|
|
error = true;
|
|
IM_ASSERT(sz_vec2 == sizeof(ImVec2) && "Mismatched struct layout!");
|
|
}
|
|
if (sz_vec4 != sizeof(ImVec4))
|
|
{
|
|
error = true;
|
|
IM_ASSERT(sz_vec4 == sizeof(ImVec4) && "Mismatched struct layout!");
|
|
}
|
|
if (sz_vert != sizeof(ImDrawVert))
|
|
{
|
|
error = true;
|
|
IM_ASSERT(sz_vert == sizeof(ImDrawVert) && "Mismatched struct layout!");
|
|
}
|
|
if (sz_idx != sizeof(ImDrawIdx))
|
|
{
|
|
error = true;
|
|
IM_ASSERT(sz_idx == sizeof(ImDrawIdx) && "Mismatched struct layout!");
|
|
}
|
|
return !error;
|
|
}
|
|
|
|
static void ImGui::ErrorCheckNewFrameSanityChecks()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
// Check user IM_ASSERT macro
|
|
// (IF YOU GET A WARNING OR COMPILE ERROR HERE: it means your assert macro is incorrectly defined!
|
|
// If your macro uses multiple statements, it NEEDS to be surrounded by a 'do { ... } while (0)' block.
|
|
// This is a common C/C++ idiom to allow multiple statements macros to be used in control flow blocks.)
|
|
// #define IM_ASSERT(EXPR) if (SomeCode(EXPR)) SomeMoreCode(); // Wrong!
|
|
// #define IM_ASSERT(EXPR) do { if (SomeCode(EXPR)) SomeMoreCode(); } while (0) // Correct!
|
|
if (true)
|
|
IM_ASSERT(1);
|
|
else
|
|
IM_ASSERT(0);
|
|
|
|
// Check user data
|
|
// (We pass an error message in the assert expression to make it visible to programmers who are not using a
|
|
// debugger, as most assert handlers display their argument)
|
|
IM_ASSERT(g.Initialized);
|
|
IM_ASSERT((g.IO.DeltaTime > 0.0f || g.FrameCount == 0) && "Need a positive DeltaTime!");
|
|
IM_ASSERT((g.FrameCount == 0 || g.FrameCountEnded == g.FrameCount) &&
|
|
"Forgot to call Render() or EndFrame() at the end of the previous frame?");
|
|
IM_ASSERT(g.IO.DisplaySize.x >= 0.0f && g.IO.DisplaySize.y >= 0.0f && "Invalid DisplaySize value!");
|
|
IM_ASSERT(g.IO.Fonts->IsBuilt() &&
|
|
"Font Atlas not built! Make sure you called ImGui_ImplXXXX_NewFrame() function for renderer backend, "
|
|
"which should call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8()");
|
|
IM_ASSERT(g.Style.CurveTessellationTol > 0.0f && "Invalid style setting!");
|
|
IM_ASSERT(g.Style.CircleTessellationMaxError > 0.0f && "Invalid style setting!");
|
|
IM_ASSERT(g.Style.Alpha >= 0.0f && g.Style.Alpha <= 1.0f &&
|
|
"Invalid style setting!"); // Allows us to avoid a few clamps in color computations
|
|
IM_ASSERT(g.Style.WindowMinSize.x >= 1.0f && g.Style.WindowMinSize.y >= 1.0f && "Invalid style setting.");
|
|
IM_ASSERT(g.Style.WindowMenuButtonPosition == ImGuiDir_None || g.Style.WindowMenuButtonPosition == ImGuiDir_Left ||
|
|
g.Style.WindowMenuButtonPosition == ImGuiDir_Right);
|
|
for (int n = 0; n < ImGuiKey_COUNT; n++)
|
|
IM_ASSERT(g.IO.KeyMap[n] >= -1 && g.IO.KeyMap[n] < IM_ARRAYSIZE(g.IO.KeysDown) &&
|
|
"io.KeyMap[] contains an out of bound value (need to be 0..512, or -1 for unmapped key)");
|
|
|
|
// Check: required key mapping (we intentionally do NOT check all keys to not pressure user into setting up
|
|
// everything, but Space is required and was only added in 1.60 WIP)
|
|
if (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard)
|
|
IM_ASSERT(g.IO.KeyMap[ImGuiKey_Space] != -1 &&
|
|
"ImGuiKey_Space is not mapped, required for keyboard navigation.");
|
|
|
|
// Check: the io.ConfigWindowsResizeFromEdges option requires backend to honor mouse cursor changes and set the
|
|
// ImGuiBackendFlags_HasMouseCursors flag accordingly.
|
|
if (g.IO.ConfigWindowsResizeFromEdges && !(g.IO.BackendFlags & ImGuiBackendFlags_HasMouseCursors))
|
|
g.IO.ConfigWindowsResizeFromEdges = false;
|
|
|
|
// Perform simple check: error if Docking or Viewport are enabled _exactly_ on frame 1 (instead of frame 0 or
|
|
// later), which is a common error leading to loss of .ini data.
|
|
if (g.FrameCount == 1 && (g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable) &&
|
|
(g.ConfigFlagsLastFrame & ImGuiConfigFlags_DockingEnable) == 0)
|
|
IM_ASSERT(0 &&
|
|
"Please set DockingEnable before the first call to NewFrame()! Otherwise you will lose your .ini "
|
|
"settings!");
|
|
if (g.FrameCount == 1 && (g.IO.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) &&
|
|
(g.ConfigFlagsLastFrame & ImGuiConfigFlags_ViewportsEnable) == 0)
|
|
IM_ASSERT(0 &&
|
|
"Please set ViewportsEnable before the first call to NewFrame()! Otherwise you will lose your .ini "
|
|
"settings!");
|
|
|
|
// Perform simple checks: multi-viewport and platform windows support
|
|
if (g.IO.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
|
|
{
|
|
if ((g.IO.BackendFlags & ImGuiBackendFlags_PlatformHasViewports) &&
|
|
(g.IO.BackendFlags & ImGuiBackendFlags_RendererHasViewports))
|
|
{
|
|
IM_ASSERT((g.FrameCount == 0 || g.FrameCount == g.FrameCountPlatformEnded) &&
|
|
"Forgot to call UpdatePlatformWindows() in main loop after EndFrame()? Check examples/ "
|
|
"applications for reference.");
|
|
IM_ASSERT(g.PlatformIO.Platform_CreateWindow != NULL && "Platform init didn't install handlers?");
|
|
IM_ASSERT(g.PlatformIO.Platform_DestroyWindow != NULL && "Platform init didn't install handlers?");
|
|
IM_ASSERT(g.PlatformIO.Platform_GetWindowPos != NULL && "Platform init didn't install handlers?");
|
|
IM_ASSERT(g.PlatformIO.Platform_SetWindowPos != NULL && "Platform init didn't install handlers?");
|
|
IM_ASSERT(g.PlatformIO.Platform_GetWindowSize != NULL && "Platform init didn't install handlers?");
|
|
IM_ASSERT(g.PlatformIO.Platform_SetWindowSize != NULL && "Platform init didn't install handlers?");
|
|
IM_ASSERT(g.PlatformIO.Monitors.Size > 0 && "Platform init didn't setup Monitors list?");
|
|
IM_ASSERT((g.Viewports[0]->PlatformUserData != NULL || g.Viewports[0]->PlatformHandle != NULL) &&
|
|
"Platform init didn't setup main viewport.");
|
|
if (g.IO.ConfigDockingTransparentPayload && (g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable))
|
|
IM_ASSERT(g.PlatformIO.Platform_SetWindowAlpha != NULL &&
|
|
"Platform_SetWindowAlpha handler is required to use io.ConfigDockingTransparent!");
|
|
}
|
|
else
|
|
{
|
|
// Disable feature, our backends do not support it
|
|
g.IO.ConfigFlags &= ~ImGuiConfigFlags_ViewportsEnable;
|
|
}
|
|
|
|
// Perform simple checks on platform monitor data + compute a total bounding box for quick early outs
|
|
for (int monitor_n = 0; monitor_n < g.PlatformIO.Monitors.Size; monitor_n++)
|
|
{
|
|
ImGuiPlatformMonitor& mon = g.PlatformIO.Monitors[monitor_n];
|
|
IM_UNUSED(mon);
|
|
IM_ASSERT(mon.MainSize.x > 0.0f && mon.MainSize.y > 0.0f && "Monitor main bounds not setup properly.");
|
|
IM_ASSERT(ImRect(mon.MainPos, mon.MainPos + mon.MainSize)
|
|
.Contains(ImRect(mon.WorkPos, mon.WorkPos + mon.WorkSize)) &&
|
|
"Monitor work bounds not setup properly. If you don't have work area information, just copy "
|
|
"MainPos/MainSize into them.");
|
|
IM_ASSERT(mon.DpiScale != 0.0f);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ImGui::ErrorCheckEndFrameSanityChecks()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
// Verify that io.KeyXXX fields haven't been tampered with. Key mods should not be modified between NewFrame() and
|
|
// EndFrame() One possible reason leading to this assert is that your backends update inputs _AFTER_ NewFrame(). It
|
|
// is known that when some modal native windows called mid-frame takes focus away, some backends such as GLFW will
|
|
// send key release events mid-frame. This would normally trigger this assertion and lead to sheared inputs.
|
|
// We silently accommodate for this case by ignoring/ the case where all io.KeyXXX modifiers were released (aka
|
|
// key_mod_flags == 0), while still correctly asserting on mid-frame key press events.
|
|
const ImGuiKeyModFlags key_mod_flags = GetMergedKeyModFlags();
|
|
IM_ASSERT((key_mod_flags == 0 || g.IO.KeyMods == key_mod_flags) &&
|
|
"Mismatching io.KeyCtrl/io.KeyShift/io.KeyAlt/io.KeySuper vs io.KeyMods");
|
|
IM_UNUSED(key_mod_flags);
|
|
|
|
// Recover from errors
|
|
// ErrorCheckEndFrameRecover();
|
|
|
|
// Report when there is a mismatch of Begin/BeginChild vs End/EndChild calls. Important: Remember that the
|
|
// Begin/BeginChild API requires you to always call End/EndChild even if Begin/BeginChild returns false! (this is
|
|
// unfortunately inconsistent with most other Begin* API).
|
|
if (g.CurrentWindowStack.Size != 1)
|
|
{
|
|
if (g.CurrentWindowStack.Size > 1)
|
|
{
|
|
IM_ASSERT_USER_ERROR(
|
|
g.CurrentWindowStack.Size == 1,
|
|
"Mismatched Begin/BeginChild vs End/EndChild calls: did you forget to call End/EndChild?");
|
|
while (g.CurrentWindowStack.Size > 1)
|
|
End();
|
|
}
|
|
else
|
|
{
|
|
IM_ASSERT_USER_ERROR(
|
|
g.CurrentWindowStack.Size == 1,
|
|
"Mismatched Begin/BeginChild vs End/EndChild calls: did you call End/EndChild too much?");
|
|
}
|
|
}
|
|
|
|
IM_ASSERT_USER_ERROR(g.GroupStack.Size == 0, "Missing EndGroup call!");
|
|
}
|
|
|
|
// Experimental recovery from incorrect usage of BeginXXX/EndXXX/PushXXX/PopXXX calls.
|
|
// Must be called during or before EndFrame().
|
|
// This is generally flawed as we are not necessarily End/Popping things in the right order.
|
|
// FIXME: Can't recover from inside BeginTabItem/EndTabItem yet.
|
|
// FIXME: Can't recover from interleaved BeginTabBar/Begin
|
|
void ImGui::ErrorCheckEndFrameRecover(ImGuiErrorLogCallback log_callback, void* user_data)
|
|
{
|
|
// PVS-Studio V1044 is "Loop break conditions do not depend on the number of iterations"
|
|
ImGuiContext& g = *GImGui;
|
|
while (g.CurrentWindowStack.Size > 0)
|
|
{
|
|
while (g.CurrentTable &&
|
|
(g.CurrentTable->OuterWindow == g.CurrentWindow || g.CurrentTable->InnerWindow == g.CurrentWindow))
|
|
{
|
|
if (log_callback)
|
|
log_callback(user_data, "Recovered from missing EndTable() in '%s'", g.CurrentTable->OuterWindow->Name);
|
|
EndTable();
|
|
}
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
IM_ASSERT(window != NULL);
|
|
while (g.CurrentTabBar != NULL) //-V1044
|
|
{
|
|
if (log_callback)
|
|
log_callback(user_data, "Recovered from missing EndTabBar() in '%s'", window->Name);
|
|
EndTabBar();
|
|
}
|
|
while (window->DC.TreeDepth > 0)
|
|
{
|
|
if (log_callback)
|
|
log_callback(user_data, "Recovered from missing TreePop() in '%s'", window->Name);
|
|
TreePop();
|
|
}
|
|
while (g.GroupStack.Size > window->DC.StackSizesOnBegin.SizeOfGroupStack)
|
|
{
|
|
if (log_callback)
|
|
log_callback(user_data, "Recovered from missing EndGroup() in '%s'", window->Name);
|
|
EndGroup();
|
|
}
|
|
while (window->IDStack.Size > 1)
|
|
{
|
|
if (log_callback)
|
|
log_callback(user_data, "Recovered from missing PopID() in '%s'", window->Name);
|
|
PopID();
|
|
}
|
|
while (g.ColorStack.Size > window->DC.StackSizesOnBegin.SizeOfColorStack)
|
|
{
|
|
if (log_callback)
|
|
log_callback(user_data, "Recovered from missing PopStyleColor() in '%s' for ImGuiCol_%s", window->Name,
|
|
GetStyleColorName(g.ColorStack.back().Col));
|
|
PopStyleColor();
|
|
}
|
|
while (g.StyleVarStack.Size > window->DC.StackSizesOnBegin.SizeOfStyleVarStack)
|
|
{
|
|
if (log_callback)
|
|
log_callback(user_data, "Recovered from missing PopStyleVar() in '%s'", window->Name);
|
|
PopStyleVar();
|
|
}
|
|
while (g.FocusScopeStack.Size > window->DC.StackSizesOnBegin.SizeOfFocusScopeStack)
|
|
{
|
|
if (log_callback)
|
|
log_callback(user_data, "Recovered from missing PopFocusScope() in '%s'", window->Name);
|
|
PopFocusScope();
|
|
}
|
|
if (g.CurrentWindowStack.Size == 1)
|
|
{
|
|
IM_ASSERT(g.CurrentWindow->IsFallbackWindow);
|
|
break;
|
|
}
|
|
IM_ASSERT(window == g.CurrentWindow);
|
|
if (window->Flags & ImGuiWindowFlags_ChildWindow)
|
|
{
|
|
if (log_callback)
|
|
log_callback(user_data, "Recovered from missing EndChild() for '%s'", window->Name);
|
|
EndChild();
|
|
}
|
|
else
|
|
{
|
|
if (log_callback)
|
|
log_callback(user_data, "Recovered from missing End() for '%s'", window->Name);
|
|
End();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Save current stack sizes for later compare
|
|
void ImGuiStackSizes::SetToCurrentState()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
SizeOfIDStack = (short)window->IDStack.Size;
|
|
SizeOfColorStack = (short)g.ColorStack.Size;
|
|
SizeOfStyleVarStack = (short)g.StyleVarStack.Size;
|
|
SizeOfFontStack = (short)g.FontStack.Size;
|
|
SizeOfFocusScopeStack = (short)g.FocusScopeStack.Size;
|
|
SizeOfGroupStack = (short)g.GroupStack.Size;
|
|
SizeOfBeginPopupStack = (short)g.BeginPopupStack.Size;
|
|
}
|
|
|
|
// Compare to detect usage errors
|
|
void ImGuiStackSizes::CompareWithCurrentState()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
IM_UNUSED(window);
|
|
|
|
// Window stacks
|
|
// NOT checking: DC.ItemWidth, DC.TextWrapPos (per window) to allow user to conveniently push once and not pop (they
|
|
// are cleared on Begin)
|
|
IM_ASSERT(SizeOfIDStack == window->IDStack.Size && "PushID/PopID or TreeNode/TreePop Mismatch!");
|
|
|
|
// Global stacks
|
|
// For color, style and font stacks there is an incentive to use Push/Begin/Pop/.../End patterns, so we relax our
|
|
// checks a little to allow them.
|
|
IM_ASSERT(SizeOfGroupStack == g.GroupStack.Size && "BeginGroup/EndGroup Mismatch!");
|
|
IM_ASSERT(SizeOfBeginPopupStack == g.BeginPopupStack.Size && "BeginPopup/EndPopup or BeginMenu/EndMenu Mismatch!");
|
|
IM_ASSERT(SizeOfColorStack >= g.ColorStack.Size && "PushStyleColor/PopStyleColor Mismatch!");
|
|
IM_ASSERT(SizeOfStyleVarStack >= g.StyleVarStack.Size && "PushStyleVar/PopStyleVar Mismatch!");
|
|
IM_ASSERT(SizeOfFontStack >= g.FontStack.Size && "PushFont/PopFont Mismatch!");
|
|
IM_ASSERT(SizeOfFocusScopeStack == g.FocusScopeStack.Size && "PushFocusScope/PopFocusScope Mismatch!");
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// [SECTION] LAYOUT
|
|
//-----------------------------------------------------------------------------
|
|
// - ItemSize()
|
|
// - ItemAdd()
|
|
// - SameLine()
|
|
// - GetCursorScreenPos()
|
|
// - SetCursorScreenPos()
|
|
// - GetCursorPos(), GetCursorPosX(), GetCursorPosY()
|
|
// - SetCursorPos(), SetCursorPosX(), SetCursorPosY()
|
|
// - GetCursorStartPos()
|
|
// - Indent()
|
|
// - Unindent()
|
|
// - SetNextItemWidth()
|
|
// - PushItemWidth()
|
|
// - PushMultiItemsWidths()
|
|
// - PopItemWidth()
|
|
// - CalcItemWidth()
|
|
// - CalcItemSize()
|
|
// - GetTextLineHeight()
|
|
// - GetTextLineHeightWithSpacing()
|
|
// - GetFrameHeight()
|
|
// - GetFrameHeightWithSpacing()
|
|
// - GetContentRegionMax()
|
|
// - GetContentRegionMaxAbs() [Internal]
|
|
// - GetContentRegionAvail(),
|
|
// - GetWindowContentRegionMin(), GetWindowContentRegionMax()
|
|
// - GetWindowContentRegionWidth()
|
|
// - BeginGroup()
|
|
// - EndGroup()
|
|
// Also see in imgui_widgets: tab bars, columns.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Advance cursor given item size for layout.
|
|
// Register minimum needed size so it can extend the bounding box used for auto-fit calculation.
|
|
// See comments in ItemAdd() about how/why the size provided to ItemSize() vs ItemAdd() may often different.
|
|
void ImGui::ItemSize(const ImVec2& size, float text_baseline_y)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
if (window->SkipItems)
|
|
return;
|
|
|
|
// We increase the height in this function to accommodate for baseline offset.
|
|
// In theory we should be offsetting the starting position (window->DC.CursorPos), that will be the topic of a
|
|
// larger refactor, but since ItemSize() is not yet an API that moves the cursor (to handle e.g. wrapping) enlarging
|
|
// the height has the same effect.
|
|
const float offset_to_match_baseline_y =
|
|
(text_baseline_y >= 0) ? ImMax(0.0f, window->DC.CurrLineTextBaseOffset - text_baseline_y) : 0.0f;
|
|
const float line_height = ImMax(window->DC.CurrLineSize.y, size.y + offset_to_match_baseline_y);
|
|
|
|
// Always align ourselves on pixel boundaries
|
|
// if (g.IO.KeyAlt) window->DrawList->AddRect(window->DC.CursorPos, window->DC.CursorPos + ImVec2(size.x,
|
|
// line_height), IM_COL32(255,0,0,200)); // [DEBUG]
|
|
window->DC.CursorPosPrevLine.x = window->DC.CursorPos.x + size.x;
|
|
window->DC.CursorPosPrevLine.y = window->DC.CursorPos.y;
|
|
window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); // Next line
|
|
window->DC.CursorPos.y = IM_FLOOR(window->DC.CursorPos.y + line_height + g.Style.ItemSpacing.y); // Next line
|
|
window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPosPrevLine.x);
|
|
window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y - g.Style.ItemSpacing.y);
|
|
// if (g.IO.KeyAlt) window->DrawList->AddCircle(window->DC.CursorMaxPos, 3.0f, IM_COL32(255,0,0,255), 4); // [DEBUG]
|
|
|
|
window->DC.PrevLineSize.y = line_height;
|
|
window->DC.CurrLineSize.y = 0.0f;
|
|
window->DC.PrevLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, text_baseline_y);
|
|
window->DC.CurrLineTextBaseOffset = 0.0f;
|
|
|
|
// Horizontal layout mode
|
|
if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
|
|
SameLine();
|
|
}
|
|
|
|
void ImGui::ItemSize(const ImRect& bb, float text_baseline_y)
|
|
{
|
|
ItemSize(bb.GetSize(), text_baseline_y);
|
|
}
|
|
|
|
// Declare item bounding box for clipping and interaction.
|
|
// Note that the size can be different than the one provided to ItemSize(). Typically, widgets that spread over
|
|
// available surface declare their minimum size requirement to ItemSize() and provide a larger region to ItemAdd() which
|
|
// is used drawing/interaction.
|
|
bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGuiItemAddFlags flags)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
|
|
// Set item data
|
|
g.LastItemData.ID = id;
|
|
g.LastItemData.Rect = bb;
|
|
g.LastItemData.InFlags = g.CurrentItemFlags;
|
|
g.LastItemData.StatusFlags = ImGuiItemStatusFlags_None;
|
|
|
|
// Directional navigation processing
|
|
if (id != 0)
|
|
{
|
|
// Runs prior to clipping early-out
|
|
// (a) So that NavInitRequest can be honored, for newly opened windows to select a default widget
|
|
// (b) So that we can scroll up/down past clipped items. This adds a small O(N) cost to regular navigation
|
|
// requests
|
|
// unfortunately, but it is still limited to one window. It may not scale very well for windows with ten of
|
|
// thousands of item, but at least NavMoveRequest is only set on user interaction, aka maximum once a
|
|
// frame. We could early out with "if (is_clipped && !g.NavInitRequest) return false;" but when we wouldn't
|
|
// be able to reach unclipped widgets. This would work if user had explicit scrolling control (e.g. mapped
|
|
// on a stick).
|
|
// We intentionally don't check if g.NavWindow != NULL because g.NavAnyRequest should only be set when it is non
|
|
// null. If we crash on a NULL g.NavWindow we need to fix the bug elsewhere.
|
|
window->DC.NavLayersActiveMaskNext |= (1 << window->DC.NavLayerCurrent);
|
|
if (g.NavId == id || g.NavAnyRequest)
|
|
if (g.NavWindow->RootWindowForNav == window->RootWindowForNav)
|
|
if (window == g.NavWindow || ((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened))
|
|
NavProcessItem(window, nav_bb_arg ? *nav_bb_arg : bb, id);
|
|
|
|
// [DEBUG] Item Picker tool, when enabling the "extended" version we perform the check in ItemAdd()
|
|
# ifdef IMGUI_DEBUG_TOOL_ITEM_PICKER_EX
|
|
if (id == g.DebugItemPickerBreakId)
|
|
{
|
|
IM_DEBUG_BREAK();
|
|
g.DebugItemPickerBreakId = 0;
|
|
}
|
|
# endif
|
|
}
|
|
g.NextItemData.Flags = ImGuiNextItemDataFlags_None;
|
|
|
|
# ifdef IMGUI_ENABLE_TEST_ENGINE
|
|
if (id != 0)
|
|
IMGUI_TEST_ENGINE_ITEM_ADD(nav_bb_arg ? *nav_bb_arg : bb, id);
|
|
# endif
|
|
|
|
// Clipping test
|
|
const bool is_clipped = IsClippedEx(bb, id, false);
|
|
if (is_clipped)
|
|
return false;
|
|
// if (g.IO.KeyAlt) window->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(255,255,0,120)); // [DEBUG]
|
|
|
|
// Tab stop handling (previously was using internal ItemFocusable() api)
|
|
// FIXME-NAV: We would now want to move this above the clipping test, but this would require being able to scroll
|
|
// and currently this would mean an extra frame. (#4079, #343)
|
|
if (flags & ImGuiItemAddFlags_Focusable)
|
|
ItemFocusable(window, id);
|
|
|
|
// We need to calculate this now to take account of the current clipping rectangle (as items like Selectable may
|
|
// change them)
|
|
if (IsMouseHoveringRect(bb.Min, bb.Max))
|
|
g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredRect;
|
|
return true;
|
|
}
|
|
|
|
// Gets back to previous line and continue with horizontal layout
|
|
// offset_from_start_x == 0 : follow right after previous item
|
|
// offset_from_start_x != 0 : align to specified x position (relative to window/group left)
|
|
// spacing_w < 0 : use default spacing if pos_x == 0, no spacing if pos_x != 0
|
|
// spacing_w >= 0 : enforce spacing amount
|
|
void ImGui::SameLine(float offset_from_start_x, float spacing_w)
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
if (window->SkipItems)
|
|
return;
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
if (offset_from_start_x != 0.0f)
|
|
{
|
|
if (spacing_w < 0.0f)
|
|
spacing_w = 0.0f;
|
|
window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + offset_from_start_x + spacing_w +
|
|
window->DC.GroupOffset.x + window->DC.ColumnsOffset.x;
|
|
window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y;
|
|
}
|
|
else
|
|
{
|
|
if (spacing_w < 0.0f)
|
|
spacing_w = g.Style.ItemSpacing.x;
|
|
window->DC.CursorPos.x = window->DC.CursorPosPrevLine.x + spacing_w;
|
|
window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y;
|
|
}
|
|
window->DC.CurrLineSize = window->DC.PrevLineSize;
|
|
window->DC.CurrLineTextBaseOffset = window->DC.PrevLineTextBaseOffset;
|
|
}
|
|
|
|
ImVec2 ImGui::GetCursorScreenPos()
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindowRead();
|
|
return window->DC.CursorPos;
|
|
}
|
|
|
|
void ImGui::SetCursorScreenPos(const ImVec2& pos)
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
window->DC.CursorPos = pos;
|
|
window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos);
|
|
}
|
|
|
|
// User generally sees positions in window coordinates. Internally we store CursorPos in absolute screen coordinates
|
|
// because it is more convenient. Conversion happens as we pass the value to user, but it makes our naming convention
|
|
// confusing because GetCursorPos() == (DC.CursorPos - window.Pos). May want to rename 'DC.CursorPos'.
|
|
ImVec2 ImGui::GetCursorPos()
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindowRead();
|
|
return window->DC.CursorPos - window->Pos + window->Scroll;
|
|
}
|
|
|
|
float ImGui::GetCursorPosX()
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindowRead();
|
|
return window->DC.CursorPos.x - window->Pos.x + window->Scroll.x;
|
|
}
|
|
|
|
float ImGui::GetCursorPosY()
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindowRead();
|
|
return window->DC.CursorPos.y - window->Pos.y + window->Scroll.y;
|
|
}
|
|
|
|
void ImGui::SetCursorPos(const ImVec2& local_pos)
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
window->DC.CursorPos = window->Pos - window->Scroll + local_pos;
|
|
window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos);
|
|
}
|
|
|
|
void ImGui::SetCursorPosX(float x)
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + x;
|
|
window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPos.x);
|
|
}
|
|
|
|
void ImGui::SetCursorPosY(float y)
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
window->DC.CursorPos.y = window->Pos.y - window->Scroll.y + y;
|
|
window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y);
|
|
}
|
|
|
|
ImVec2 ImGui::GetCursorStartPos()
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindowRead();
|
|
return window->DC.CursorStartPos - window->Pos;
|
|
}
|
|
|
|
void ImGui::Indent(float indent_w)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
window->DC.Indent.x += (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing;
|
|
window->DC.CursorPos.x = window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x;
|
|
}
|
|
|
|
void ImGui::Unindent(float indent_w)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
window->DC.Indent.x -= (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing;
|
|
window->DC.CursorPos.x = window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x;
|
|
}
|
|
|
|
// Affect large frame+labels widgets only.
|
|
void ImGui::SetNextItemWidth(float item_width)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
g.NextItemData.Flags |= ImGuiNextItemDataFlags_HasWidth;
|
|
g.NextItemData.Width = item_width;
|
|
}
|
|
|
|
// FIXME: Remove the == 0.0f behavior?
|
|
void ImGui::PushItemWidth(float item_width)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
window->DC.ItemWidthStack.push_back(window->DC.ItemWidth); // Backup current width
|
|
window->DC.ItemWidth = (item_width == 0.0f ? window->ItemWidthDefault : item_width);
|
|
g.NextItemData.Flags &= ~ImGuiNextItemDataFlags_HasWidth;
|
|
}
|
|
|
|
void ImGui::PushMultiItemsWidths(int components, float w_full)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
const ImGuiStyle& style = g.Style;
|
|
const float w_item_one =
|
|
ImMax(1.0f, IM_FLOOR((w_full - (style.ItemInnerSpacing.x) * (components - 1)) / (float)components));
|
|
const float w_item_last =
|
|
ImMax(1.0f, IM_FLOOR(w_full - (w_item_one + style.ItemInnerSpacing.x) * (components - 1)));
|
|
window->DC.ItemWidthStack.push_back(window->DC.ItemWidth); // Backup current width
|
|
window->DC.ItemWidthStack.push_back(w_item_last);
|
|
for (int i = 0; i < components - 2; i++)
|
|
window->DC.ItemWidthStack.push_back(w_item_one);
|
|
window->DC.ItemWidth = (components == 1) ? w_item_last : w_item_one;
|
|
g.NextItemData.Flags &= ~ImGuiNextItemDataFlags_HasWidth;
|
|
}
|
|
|
|
void ImGui::PopItemWidth()
|
|
{
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
window->DC.ItemWidth = window->DC.ItemWidthStack.back();
|
|
window->DC.ItemWidthStack.pop_back();
|
|
}
|
|
|
|
// Calculate default item width given value passed to PushItemWidth() or SetNextItemWidth().
|
|
// The SetNextItemWidth() data is generally cleared/consumed by ItemAdd() or NextItemData.ClearFlags()
|
|
float ImGui::CalcItemWidth()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
float w;
|
|
if (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasWidth)
|
|
w = g.NextItemData.Width;
|
|
else
|
|
w = window->DC.ItemWidth;
|
|
if (w < 0.0f)
|
|
{
|
|
float region_max_x = GetContentRegionMaxAbs().x;
|
|
w = ImMax(1.0f, region_max_x - window->DC.CursorPos.x + w);
|
|
}
|
|
w = IM_FLOOR(w);
|
|
return w;
|
|
}
|
|
|
|
// [Internal] Calculate full item size given user provided 'size' parameter and default width/height. Default width is
|
|
// often == CalcItemWidth(). Those two functions CalcItemWidth vs CalcItemSize are awkwardly named because they are not
|
|
// fully symmetrical. Note that only CalcItemWidth() is publicly exposed. The 4.0f here may be changed to match
|
|
// CalcItemWidth() and/or BeginChild() (right now we have a mismatch which is harmless but undesirable)
|
|
ImVec2 ImGui::CalcItemSize(ImVec2 size, float default_w, float default_h)
|
|
{
|
|
ImGuiWindow* window = GImGui->CurrentWindow;
|
|
|
|
ImVec2 region_max;
|
|
if (size.x < 0.0f || size.y < 0.0f)
|
|
region_max = GetContentRegionMaxAbs();
|
|
|
|
if (size.x == 0.0f)
|
|
size.x = default_w;
|
|
else if (size.x < 0.0f)
|
|
size.x = ImMax(4.0f, region_max.x - window->DC.CursorPos.x + size.x);
|
|
|
|
if (size.y == 0.0f)
|
|
size.y = default_h;
|
|
else if (size.y < 0.0f)
|
|
size.y = ImMax(4.0f, region_max.y - window->DC.CursorPos.y + size.y);
|
|
|
|
return size;
|
|
}
|
|
|
|
float ImGui::GetTextLineHeight()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
return g.FontSize;
|
|
}
|
|
|
|
float ImGui::GetTextLineHeightWithSpacing()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
return g.FontSize + g.Style.ItemSpacing.y;
|
|
}
|
|
|
|
float ImGui::GetFrameHeight()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
return g.FontSize + g.Style.FramePadding.y * 2.0f;
|
|
}
|
|
|
|
float ImGui::GetFrameHeightWithSpacing()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
return g.FontSize + g.Style.FramePadding.y * 2.0f + g.Style.ItemSpacing.y;
|
|
}
|
|
|
|
// FIXME: All the Contents Region function are messy or misleading. WE WILL AIM TO OBSOLETE ALL OF THEM WITH A NEW "WORK
|
|
// RECT" API. Thanks for your patience!
|
|
|
|
// FIXME: This is in window space (not screen space!).
|
|
ImVec2 ImGui::GetContentRegionMax()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
ImVec2 mx = window->ContentRegionRect.Max - window->Pos;
|
|
if (window->DC.CurrentColumns || g.CurrentTable)
|
|
mx.x = window->WorkRect.Max.x - window->Pos.x;
|
|
return mx;
|
|
}
|
|
|
|
// [Internal] Absolute coordinate. Saner. This is not exposed until we finishing refactoring work rect features.
|
|
ImVec2 ImGui::GetContentRegionMaxAbs()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
ImVec2 mx = window->ContentRegionRect.Max;
|
|
if (window->DC.CurrentColumns || g.CurrentTable)
|
|
mx.x = window->WorkRect.Max.x;
|
|
return mx;
|
|
}
|
|
|
|
ImVec2 ImGui::GetContentRegionAvail()
|
|
{
|
|
ImGuiWindow* window = GImGui->CurrentWindow;
|
|
return GetContentRegionMaxAbs() - window->DC.CursorPos;
|
|
}
|
|
|
|
// In window space (not screen space!)
|
|
ImVec2 ImGui::GetWindowContentRegionMin()
|
|
{
|
|
ImGuiWindow* window = GImGui->CurrentWindow;
|
|
return window->ContentRegionRect.Min - window->Pos;
|
|
}
|
|
|
|
ImVec2 ImGui::GetWindowContentRegionMax()
|
|
{
|
|
ImGuiWindow* window = GImGui->CurrentWindow;
|
|
return window->ContentRegionRect.Max - window->Pos;
|
|
}
|
|
|
|
float ImGui::GetWindowContentRegionWidth()
|
|
{
|
|
ImGuiWindow* window = GImGui->CurrentWindow;
|
|
return window->ContentRegionRect.GetWidth();
|
|
}
|
|
|
|
// Lock horizontal starting position + capture group bounding box into one "item" (so you can use IsItemHovered() or
|
|
// layout primitives such as SameLine() on whole group, etc.) Groups are currently a mishmash of functionalities which
|
|
// should perhaps be clarified and separated.
|
|
void ImGui::BeginGroup()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
|
|
g.GroupStack.resize(g.GroupStack.Size + 1);
|
|
ImGuiGroupData& group_data = g.GroupStack.back();
|
|
group_data.WindowID = window->ID;
|
|
group_data.BackupCursorPos = window->DC.CursorPos;
|
|
group_data.BackupCursorMaxPos = window->DC.CursorMaxPos;
|
|
group_data.BackupIndent = window->DC.Indent;
|
|
group_data.BackupGroupOffset = window->DC.GroupOffset;
|
|
group_data.BackupCurrLineSize = window->DC.CurrLineSize;
|
|
group_data.BackupCurrLineTextBaseOffset = window->DC.CurrLineTextBaseOffset;
|
|
group_data.BackupActiveIdIsAlive = g.ActiveIdIsAlive;
|
|
group_data.BackupHoveredIdIsAlive = g.HoveredId != 0;
|
|
group_data.BackupActiveIdPreviousFrameIsAlive = g.ActiveIdPreviousFrameIsAlive;
|
|
group_data.EmitItem = true;
|
|
|
|
window->DC.GroupOffset.x = window->DC.CursorPos.x - window->Pos.x - window->DC.ColumnsOffset.x;
|
|
window->DC.Indent = window->DC.GroupOffset;
|
|
window->DC.CursorMaxPos = window->DC.CursorPos;
|
|
window->DC.CurrLineSize = ImVec2(0.0f, 0.0f);
|
|
if (g.LogEnabled)
|
|
g.LogLinePosY = -FLT_MAX; // To enforce a carriage return
|
|
}
|
|
|
|
void ImGui::EndGroup()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
IM_ASSERT(g.GroupStack.Size > 0); // Mismatched BeginGroup()/EndGroup() calls
|
|
|
|
ImGuiGroupData& group_data = g.GroupStack.back();
|
|
IM_ASSERT(group_data.WindowID == window->ID); // EndGroup() in wrong window?
|
|
|
|
ImRect group_bb(group_data.BackupCursorPos, ImMax(window->DC.CursorMaxPos, group_data.BackupCursorPos));
|
|
|
|
window->DC.CursorPos = group_data.BackupCursorPos;
|
|
window->DC.CursorMaxPos = ImMax(group_data.BackupCursorMaxPos, window->DC.CursorMaxPos);
|
|
window->DC.Indent = group_data.BackupIndent;
|
|
window->DC.GroupOffset = group_data.BackupGroupOffset;
|
|
window->DC.CurrLineSize = group_data.BackupCurrLineSize;
|
|
window->DC.CurrLineTextBaseOffset = group_data.BackupCurrLineTextBaseOffset;
|
|
if (g.LogEnabled)
|
|
g.LogLinePosY = -FLT_MAX; // To enforce a carriage return
|
|
|
|
if (!group_data.EmitItem)
|
|
{
|
|
g.GroupStack.pop_back();
|
|
return;
|
|
}
|
|
|
|
window->DC.CurrLineTextBaseOffset =
|
|
ImMax(window->DC.PrevLineTextBaseOffset,
|
|
group_data.BackupCurrLineTextBaseOffset); // FIXME: Incorrect, we should grab the base offset from the
|
|
// *first line* of the group but it is hard to obtain now.
|
|
ItemSize(group_bb.GetSize());
|
|
ItemAdd(group_bb, 0);
|
|
|
|
// If the current ActiveId was declared within the boundary of our group, we copy it to LastItemId so
|
|
// IsItemActive(), IsItemDeactivated() etc. will be functional on the entire group. It would be be neater if we
|
|
// replaced window.DC.LastItemId by e.g. 'bool LastItemIsActive', but would put a little more burden on individual
|
|
// widgets. Also if you grep for LastItemId you'll notice it is only used in that context. (The two tests not the
|
|
// same because ActiveIdIsAlive is an ID itself, in order to be able to handle ActiveId being overwritten during the
|
|
// frame.)
|
|
const bool group_contains_curr_active_id =
|
|
(group_data.BackupActiveIdIsAlive != g.ActiveId) && (g.ActiveIdIsAlive == g.ActiveId) && g.ActiveId;
|
|
const bool group_contains_prev_active_id =
|
|
(group_data.BackupActiveIdPreviousFrameIsAlive == false) && (g.ActiveIdPreviousFrameIsAlive == true);
|
|
if (group_contains_curr_active_id)
|
|
g.LastItemData.ID = g.ActiveId;
|
|
else if (group_contains_prev_active_id)
|
|
g.LastItemData.ID = g.ActiveIdPreviousFrame;
|
|
g.LastItemData.Rect = group_bb;
|
|
|
|
// Forward Hovered flag
|
|
const bool group_contains_curr_hovered_id = (group_data.BackupHoveredIdIsAlive == false) && g.HoveredId != 0;
|
|
if (group_contains_curr_hovered_id)
|
|
g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow;
|
|
|
|
// Forward Edited flag
|
|
if (group_contains_curr_active_id && g.ActiveIdHasBeenEditedThisFrame)
|
|
g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Edited;
|
|
|
|
// Forward Deactivated flag
|
|
g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDeactivated;
|
|
if (group_contains_prev_active_id && g.ActiveId != g.ActiveIdPreviousFrame)
|
|
g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Deactivated;
|
|
|
|
g.GroupStack.pop_back();
|
|
// window->DrawList->AddRect(group_bb.Min, group_bb.Max, IM_COL32(255,0,255,255)); // [Debug]
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// [SECTION] SCROLLING
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Helper to snap on edges when aiming at an item very close to the edge,
|
|
// So the difference between WindowPadding and ItemSpacing will be in the visible area after scrolling.
|
|
// When we refactor the scrolling API this may be configurable with a flag?
|
|
// Note that the effect for this won't be visible on X axis with default Style settings as WindowPadding.x ==
|
|
// ItemSpacing.x by default.
|
|
static float CalcScrollEdgeSnap(float target, float snap_min, float snap_max, float snap_threshold, float center_ratio)
|
|
{
|
|
if (target <= snap_min + snap_threshold)
|
|
return ImLerp(snap_min, target, center_ratio);
|
|
if (target >= snap_max - snap_threshold)
|
|
return ImLerp(target, snap_max, center_ratio);
|
|
return target;
|
|
}
|
|
|
|
static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window)
|
|
{
|
|
ImVec2 scroll = window->Scroll;
|
|
if (window->ScrollTarget.x < FLT_MAX)
|
|
{
|
|
float decoration_total_width = window->ScrollbarSizes.x;
|
|
float center_x_ratio = window->ScrollTargetCenterRatio.x;
|
|
float scroll_target_x = window->ScrollTarget.x;
|
|
if (window->ScrollTargetEdgeSnapDist.x > 0.0f)
|
|
{
|
|
float snap_x_min = 0.0f;
|
|
float snap_x_max = window->ScrollMax.x + window->SizeFull.x - decoration_total_width;
|
|
scroll_target_x = CalcScrollEdgeSnap(scroll_target_x, snap_x_min, snap_x_max,
|
|
window->ScrollTargetEdgeSnapDist.x, center_x_ratio);
|
|
}
|
|
scroll.x = scroll_target_x - center_x_ratio * (window->SizeFull.x - decoration_total_width);
|
|
}
|
|
if (window->ScrollTarget.y < FLT_MAX)
|
|
{
|
|
float decoration_total_height = window->TitleBarHeight() + window->MenuBarHeight() + window->ScrollbarSizes.y;
|
|
float center_y_ratio = window->ScrollTargetCenterRatio.y;
|
|
float scroll_target_y = window->ScrollTarget.y;
|
|
if (window->ScrollTargetEdgeSnapDist.y > 0.0f)
|
|
{
|
|
float snap_y_min = 0.0f;
|
|
float snap_y_max = window->ScrollMax.y + window->SizeFull.y - decoration_total_height;
|
|
scroll_target_y = CalcScrollEdgeSnap(scroll_target_y, snap_y_min, snap_y_max,
|
|
window->ScrollTargetEdgeSnapDist.y, center_y_ratio);
|
|
}
|
|
scroll.y = scroll_target_y - center_y_ratio * (window->SizeFull.y - decoration_total_height);
|
|
}
|
|
scroll.x = IM_FLOOR(ImMax(scroll.x, 0.0f));
|
|
scroll.y = IM_FLOOR(ImMax(scroll.y, 0.0f));
|
|
if (!window->Collapsed && !window->SkipItems)
|
|
{
|
|
scroll.x = ImMin(scroll.x, window->ScrollMax.x);
|
|
scroll.y = ImMin(scroll.y, window->ScrollMax.y);
|
|
}
|
|
return scroll;
|
|
}
|
|
|
|
// Scroll to keep newly navigated item fully into view
|
|
ImVec2 ImGui::ScrollToBringRectIntoView(ImGuiWindow* window, const ImRect& item_rect)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImRect window_rect(window->InnerRect.Min - ImVec2(1, 1), window->InnerRect.Max + ImVec2(1, 1));
|
|
// GetForegroundDrawList(window)->AddRect(window_rect.Min, window_rect.Max, IM_COL32_WHITE); // [DEBUG]
|
|
|
|
ImVec2 delta_scroll;
|
|
if (!window_rect.Contains(item_rect))
|
|
{
|
|
if (window->ScrollbarX && item_rect.Min.x < window_rect.Min.x)
|
|
SetScrollFromPosX(window, item_rect.Min.x - window->Pos.x - g.Style.ItemSpacing.x, 0.0f);
|
|
else if (window->ScrollbarX && item_rect.Max.x >= window_rect.Max.x)
|
|
SetScrollFromPosX(window, item_rect.Max.x - window->Pos.x + g.Style.ItemSpacing.x, 1.0f);
|
|
if (item_rect.Min.y < window_rect.Min.y)
|
|
SetScrollFromPosY(window, item_rect.Min.y - window->Pos.y - g.Style.ItemSpacing.y, 0.0f);
|
|
else if (item_rect.Max.y >= window_rect.Max.y)
|
|
SetScrollFromPosY(window, item_rect.Max.y - window->Pos.y + g.Style.ItemSpacing.y, 1.0f);
|
|
|
|
ImVec2 next_scroll = CalcNextScrollFromScrollTargetAndClamp(window);
|
|
delta_scroll = next_scroll - window->Scroll;
|
|
}
|
|
|
|
// Also scroll parent window to keep us into view if necessary
|
|
if (window->Flags & ImGuiWindowFlags_ChildWindow)
|
|
delta_scroll += ScrollToBringRectIntoView(window->ParentWindow,
|
|
ImRect(item_rect.Min - delta_scroll, item_rect.Max - delta_scroll));
|
|
|
|
return delta_scroll;
|
|
}
|
|
|
|
float ImGui::GetScrollX()
|
|
{
|
|
ImGuiWindow* window = GImGui->CurrentWindow;
|
|
return window->Scroll.x;
|
|
}
|
|
|
|
float ImGui::GetScrollY()
|
|
{
|
|
ImGuiWindow* window = GImGui->CurrentWindow;
|
|
return window->Scroll.y;
|
|
}
|
|
|
|
float ImGui::GetScrollMaxX()
|
|
{
|
|
ImGuiWindow* window = GImGui->CurrentWindow;
|
|
return window->ScrollMax.x;
|
|
}
|
|
|
|
float ImGui::GetScrollMaxY()
|
|
{
|
|
ImGuiWindow* window = GImGui->CurrentWindow;
|
|
return window->ScrollMax.y;
|
|
}
|
|
|
|
void ImGui::SetScrollX(ImGuiWindow* window, float scroll_x)
|
|
{
|
|
window->ScrollTarget.x = scroll_x;
|
|
window->ScrollTargetCenterRatio.x = 0.0f;
|
|
window->ScrollTargetEdgeSnapDist.x = 0.0f;
|
|
}
|
|
|
|
void ImGui::SetScrollY(ImGuiWindow* window, float scroll_y)
|
|
{
|
|
window->ScrollTarget.y = scroll_y;
|
|
window->ScrollTargetCenterRatio.y = 0.0f;
|
|
window->ScrollTargetEdgeSnapDist.y = 0.0f;
|
|
}
|
|
|
|
void ImGui::SetScrollX(float scroll_x)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
SetScrollX(g.CurrentWindow, scroll_x);
|
|
}
|
|
|
|
void ImGui::SetScrollY(float scroll_y)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
SetScrollY(g.CurrentWindow, scroll_y);
|
|
}
|
|
|
|
// Note that a local position will vary depending on initial scroll value,
|
|
// This is a little bit confusing so bear with us:
|
|
// - local_pos = (absolution_pos - window->Pos)
|
|
// - So local_x/local_y are 0.0f for a position at the upper-left corner of a window,
|
|
// and generally local_x/local_y are >(padding+decoration) && <(size-padding-decoration) when in the visible area.
|
|
// - They mostly exists because of legacy API.
|
|
// Following the rules above, when trying to work with scrolling code, consider that:
|
|
// - SetScrollFromPosY(0.0f) == SetScrollY(0.0f + scroll.y) == has no effect!
|
|
// - SetScrollFromPosY(-scroll.y) == SetScrollY(-scroll.y + scroll.y) == SetScrollY(0.0f) == reset scroll. Of course
|
|
// writing SetScrollY(0.0f) directly then makes more sense
|
|
// We store a target position so centering and clamping can occur on the next frame when we are guaranteed to have a
|
|
// known window size
|
|
void ImGui::SetScrollFromPosX(ImGuiWindow* window, float local_x, float center_x_ratio)
|
|
{
|
|
IM_ASSERT(center_x_ratio >= 0.0f && center_x_ratio <= 1.0f);
|
|
window->ScrollTarget.x = IM_FLOOR(local_x + window->Scroll.x); // Convert local position to scroll offset
|
|
window->ScrollTargetCenterRatio.x = center_x_ratio;
|
|
window->ScrollTargetEdgeSnapDist.x = 0.0f;
|
|
}
|
|
|
|
void ImGui::SetScrollFromPosY(ImGuiWindow* window, float local_y, float center_y_ratio)
|
|
{
|
|
IM_ASSERT(center_y_ratio >= 0.0f && center_y_ratio <= 1.0f);
|
|
const float decoration_up_height =
|
|
window->TitleBarHeight() + window->MenuBarHeight(); // FIXME: Would be nice to have a more standardized access
|
|
// to our scrollable/client rect;
|
|
local_y -= decoration_up_height;
|
|
window->ScrollTarget.y = IM_FLOOR(local_y + window->Scroll.y); // Convert local position to scroll offset
|
|
window->ScrollTargetCenterRatio.y = center_y_ratio;
|
|
window->ScrollTargetEdgeSnapDist.y = 0.0f;
|
|
}
|
|
|
|
void ImGui::SetScrollFromPosX(float local_x, float center_x_ratio)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
SetScrollFromPosX(g.CurrentWindow, local_x, center_x_ratio);
|
|
}
|
|
|
|
void ImGui::SetScrollFromPosY(float local_y, float center_y_ratio)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
SetScrollFromPosY(g.CurrentWindow, local_y, center_y_ratio);
|
|
}
|
|
|
|
// center_x_ratio: 0.0f left of last item, 0.5f horizontal center of last item, 1.0f right of last item.
|
|
void ImGui::SetScrollHereX(float center_x_ratio)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
float spacing_x = ImMax(window->WindowPadding.x, g.Style.ItemSpacing.x);
|
|
float target_pos_x =
|
|
ImLerp(g.LastItemData.Rect.Min.x - spacing_x, g.LastItemData.Rect.Max.x + spacing_x, center_x_ratio);
|
|
SetScrollFromPosX(window, target_pos_x - window->Pos.x, center_x_ratio); // Convert from absolute to local pos
|
|
|
|
// Tweak: snap on edges when aiming at an item very close to the edge
|
|
window->ScrollTargetEdgeSnapDist.x = ImMax(0.0f, window->WindowPadding.x - spacing_x);
|
|
}
|
|
|
|
// center_y_ratio: 0.0f top of last item, 0.5f vertical center of last item, 1.0f bottom of last item.
|
|
void ImGui::SetScrollHereY(float center_y_ratio)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
float spacing_y = ImMax(window->WindowPadding.y, g.Style.ItemSpacing.y);
|
|
float target_pos_y = ImLerp(window->DC.CursorPosPrevLine.y - spacing_y,
|
|
window->DC.CursorPosPrevLine.y + window->DC.PrevLineSize.y + spacing_y, center_y_ratio);
|
|
SetScrollFromPosY(window, target_pos_y - window->Pos.y, center_y_ratio); // Convert from absolute to local pos
|
|
|
|
// Tweak: snap on edges when aiming at an item very close to the edge
|
|
window->ScrollTargetEdgeSnapDist.y = ImMax(0.0f, window->WindowPadding.y - spacing_y);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// [SECTION] TOOLTIPS
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void ImGui::BeginTooltip()
|
|
{
|
|
BeginTooltipEx(ImGuiWindowFlags_None, ImGuiTooltipFlags_None);
|
|
}
|
|
|
|
void ImGui::BeginTooltipEx(ImGuiWindowFlags extra_flags, ImGuiTooltipFlags tooltip_flags)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
if (g.DragDropWithinSource || g.DragDropWithinTarget)
|
|
{
|
|
// The default tooltip position is a little offset to give space to see the context menu (it's also clamped
|
|
// within the current viewport/monitor) In the context of a dragging tooltip we try to reduce that offset and we
|
|
// enforce following the cursor. Whatever we do we want to call SetNextWindowPos() to enforce a tooltip position
|
|
// and disable clipping the tooltip without our display area, like regular tooltip do.
|
|
// ImVec2 tooltip_pos = g.IO.MousePos - g.ActiveIdClickOffset - g.Style.WindowPadding;
|
|
ImVec2 tooltip_pos = g.IO.MousePos + ImVec2(16 * g.Style.MouseCursorScale, 8 * g.Style.MouseCursorScale);
|
|
SetNextWindowPos(tooltip_pos);
|
|
SetNextWindowBgAlpha(g.Style.Colors[ImGuiCol_PopupBg].w * 0.60f);
|
|
// PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * 0.60f); // This would be nice but e.g ColorButton with
|
|
// checkboard has issue with transparent colors :(
|
|
tooltip_flags |= ImGuiTooltipFlags_OverridePreviousTooltip;
|
|
}
|
|
|
|
char window_name[16];
|
|
ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", g.TooltipOverrideCount);
|
|
if (tooltip_flags & ImGuiTooltipFlags_OverridePreviousTooltip)
|
|
if (ImGuiWindow* window = FindWindowByName(window_name))
|
|
if (window->Active)
|
|
{
|
|
// Hide previous tooltip from being displayed. We can't easily "reset" the content of a window so we
|
|
// create a new one.
|
|
window->Hidden = true;
|
|
window->HiddenFramesCanSkipItems = 1; // FIXME: This may not be necessary?
|
|
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_NoDocking;
|
|
Begin(window_name, NULL, flags | extra_flags);
|
|
}
|
|
|
|
void ImGui::EndTooltip()
|
|
{
|
|
IM_ASSERT(GetCurrentWindowRead()->Flags &
|
|
ImGuiWindowFlags_Tooltip); // Mismatched BeginTooltip()/EndTooltip() calls
|
|
End();
|
|
}
|
|
|
|
void ImGui::SetTooltipV(const char* fmt, va_list args)
|
|
{
|
|
BeginTooltipEx(0, ImGuiTooltipFlags_OverridePreviousTooltip);
|
|
TextV(fmt, args);
|
|
EndTooltip();
|
|
}
|
|
|
|
void ImGui::SetTooltip(const char* fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
SetTooltipV(fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// [SECTION] POPUPS
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Supported flags: ImGuiPopupFlags_AnyPopupId, ImGuiPopupFlags_AnyPopupLevel
|
|
bool ImGui::IsPopupOpen(ImGuiID id, ImGuiPopupFlags popup_flags)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (popup_flags & ImGuiPopupFlags_AnyPopupId)
|
|
{
|
|
// Return true if any popup is open at the current BeginPopup() level of the popup stack
|
|
// This may be used to e.g. test for another popups already opened to handle popups priorities at the same
|
|
// level.
|
|
IM_ASSERT(id == 0);
|
|
if (popup_flags & ImGuiPopupFlags_AnyPopupLevel)
|
|
return g.OpenPopupStack.Size > 0;
|
|
else
|
|
return g.OpenPopupStack.Size > g.BeginPopupStack.Size;
|
|
}
|
|
else
|
|
{
|
|
if (popup_flags & ImGuiPopupFlags_AnyPopupLevel)
|
|
{
|
|
// Return true if the popup is open anywhere in the popup stack
|
|
for (int n = 0; n < g.OpenPopupStack.Size; n++)
|
|
if (g.OpenPopupStack[n].PopupId == id)
|
|
return true;
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
// Return true if the popup is open at the current BeginPopup() level of the popup stack (this is the
|
|
// most-common query)
|
|
return g.OpenPopupStack.Size > g.BeginPopupStack.Size &&
|
|
g.OpenPopupStack[g.BeginPopupStack.Size].PopupId == id;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ImGui::IsPopupOpen(const char* str_id, ImGuiPopupFlags popup_flags)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiID id = (popup_flags & ImGuiPopupFlags_AnyPopupId) ? 0 : g.CurrentWindow->GetID(str_id);
|
|
if ((popup_flags & ImGuiPopupFlags_AnyPopupLevel) && id != 0)
|
|
IM_ASSERT(
|
|
0 &&
|
|
"Cannot use IsPopupOpen() with a string id and ImGuiPopupFlags_AnyPopupLevel."); // But non-string version
|
|
// is legal and used
|
|
// internally
|
|
return IsPopupOpen(id, popup_flags);
|
|
}
|
|
|
|
ImGuiWindow* ImGui::GetTopMostPopupModal()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
for (int n = g.OpenPopupStack.Size - 1; n >= 0; n--)
|
|
if (ImGuiWindow* popup = g.OpenPopupStack.Data[n].Window)
|
|
if (popup->Flags & ImGuiWindowFlags_Modal)
|
|
return popup;
|
|
return NULL;
|
|
}
|
|
|
|
void ImGui::OpenPopup(const char* str_id, ImGuiPopupFlags popup_flags)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
OpenPopupEx(g.CurrentWindow->GetID(str_id), popup_flags);
|
|
}
|
|
|
|
void ImGui::OpenPopup(ImGuiID id, ImGuiPopupFlags popup_flags)
|
|
{
|
|
OpenPopupEx(id, popup_flags);
|
|
}
|
|
|
|
// Mark popup as open (toggle toward open state).
|
|
// Popups are closed when user click outside, or activate a pressable item, or CloseCurrentPopup() is called within a
|
|
// BeginPopup()/EndPopup() block. Popup identifiers are relative to the current ID-stack (so OpenPopup and BeginPopup
|
|
// needs to be at the same level). One open popup per level of the popup hierarchy (NB: when assigning we reset the
|
|
// Window member of ImGuiPopupRef to NULL)
|
|
void ImGui::OpenPopupEx(ImGuiID id, ImGuiPopupFlags popup_flags)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* parent_window = g.CurrentWindow;
|
|
const int current_stack_size = g.BeginPopupStack.Size;
|
|
|
|
if (popup_flags & ImGuiPopupFlags_NoOpenOverExistingPopup)
|
|
if (IsPopupOpen(0u, ImGuiPopupFlags_AnyPopupId))
|
|
return;
|
|
|
|
ImGuiPopupData
|
|
popup_ref; // Tagged as new ref as Window will be set back to NULL if we write this into OpenPopupStack.
|
|
popup_ref.PopupId = id;
|
|
popup_ref.Window = NULL;
|
|
popup_ref.SourceWindow = g.NavWindow;
|
|
popup_ref.OpenFrameCount = g.FrameCount;
|
|
popup_ref.OpenParentId = parent_window->IDStack.back();
|
|
popup_ref.OpenPopupPos = NavCalcPreferredRefPos();
|
|
popup_ref.OpenMousePos = IsMousePosValid(&g.IO.MousePos) ? g.IO.MousePos : popup_ref.OpenPopupPos;
|
|
|
|
IMGUI_DEBUG_LOG_POPUP("OpenPopupEx(0x%08X)\n", id);
|
|
if (g.OpenPopupStack.Size < current_stack_size + 1)
|
|
{
|
|
g.OpenPopupStack.push_back(popup_ref);
|
|
}
|
|
else
|
|
{
|
|
// Gently handle the user mistakenly calling OpenPopup() every frame. It is a programming mistake! However, if
|
|
// we were to run the regular code path, the ui would become completely unusable because the popup will always
|
|
// be in hidden-while-calculating-size state _while_ claiming focus. Which would be a very confusing situation
|
|
// for the programmer. Instead, we silently allow the popup to proceed, it will keep reappearing and the
|
|
// programming error will be more obvious to understand.
|
|
if (g.OpenPopupStack[current_stack_size].PopupId == id &&
|
|
g.OpenPopupStack[current_stack_size].OpenFrameCount == g.FrameCount - 1)
|
|
{
|
|
g.OpenPopupStack[current_stack_size].OpenFrameCount = popup_ref.OpenFrameCount;
|
|
}
|
|
else
|
|
{
|
|
// Close child popups if any, then flag popup for open/reopen
|
|
ClosePopupToLevel(current_stack_size, false);
|
|
g.OpenPopupStack.push_back(popup_ref);
|
|
}
|
|
|
|
// When reopening a popup we first refocus its parent, otherwise if its parent is itself a popup it would get
|
|
// closed by ClosePopupsOverWindow(). This is equivalent to what ClosePopupToLevel() does.
|
|
// if (g.OpenPopupStack[current_stack_size].PopupId == id)
|
|
// FocusWindow(parent_window);
|
|
}
|
|
}
|
|
|
|
// When popups are stacked, clicking on a lower level popups puts focus back to it and close popups above it.
|
|
// This function closes any popups that are over 'ref_window'.
|
|
void ImGui::ClosePopupsOverWindow(ImGuiWindow* ref_window, bool restore_focus_to_window_under_popup)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (g.OpenPopupStack.Size == 0)
|
|
return;
|
|
|
|
// Don't close our own child popup windows.
|
|
int popup_count_to_keep = 0;
|
|
if (ref_window)
|
|
{
|
|
// Find the highest popup which is a descendant of the reference window (generally reference window = NavWindow)
|
|
for (; popup_count_to_keep < g.OpenPopupStack.Size; popup_count_to_keep++)
|
|
{
|
|
ImGuiPopupData& popup = g.OpenPopupStack[popup_count_to_keep];
|
|
if (!popup.Window)
|
|
continue;
|
|
IM_ASSERT((popup.Window->Flags & ImGuiWindowFlags_Popup) != 0);
|
|
if (popup.Window->Flags & ImGuiWindowFlags_ChildWindow)
|
|
continue;
|
|
|
|
// Trim the stack unless the popup is a direct parent of the reference window (the reference window is often
|
|
// the NavWindow)
|
|
// - With this stack of window, clicking/focusing Popup1 will close Popup2 and Popup3:
|
|
// Window -> Popup1 -> Popup2 -> Popup3
|
|
// - Each popups may contain child windows, which is why we compare ->RootWindowDockTree!
|
|
// Window -> Popup1 -> Popup1_Child -> Popup2 -> Popup2_Child
|
|
bool ref_window_is_descendent_of_popup = false;
|
|
for (int n = popup_count_to_keep; n < g.OpenPopupStack.Size; n++)
|
|
if (ImGuiWindow* popup_window = g.OpenPopupStack[n].Window)
|
|
if (popup_window->RootWindowDockTree == ref_window->RootWindowDockTree)
|
|
{
|
|
ref_window_is_descendent_of_popup = true;
|
|
break;
|
|
}
|
|
if (!ref_window_is_descendent_of_popup)
|
|
break;
|
|
}
|
|
}
|
|
if (popup_count_to_keep <
|
|
g.OpenPopupStack
|
|
.Size) // This test is not required but it allows to set a convenient breakpoint on the statement below
|
|
{
|
|
IMGUI_DEBUG_LOG_POPUP("ClosePopupsOverWindow(\"%s\") -> ClosePopupToLevel(%d)\n", ref_window->Name,
|
|
popup_count_to_keep);
|
|
ClosePopupToLevel(popup_count_to_keep, restore_focus_to_window_under_popup);
|
|
}
|
|
}
|
|
|
|
void ImGui::ClosePopupToLevel(int remaining, bool restore_focus_to_window_under_popup)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IMGUI_DEBUG_LOG_POPUP("ClosePopupToLevel(%d), restore_focus_to_window_under_popup=%d\n", remaining,
|
|
restore_focus_to_window_under_popup);
|
|
IM_ASSERT(remaining >= 0 && remaining < g.OpenPopupStack.Size);
|
|
|
|
// Trim open popup stack
|
|
ImGuiWindow* focus_window = g.OpenPopupStack[remaining].SourceWindow;
|
|
ImGuiWindow* popup_window = g.OpenPopupStack[remaining].Window;
|
|
g.OpenPopupStack.resize(remaining);
|
|
|
|
if (restore_focus_to_window_under_popup)
|
|
{
|
|
if (focus_window && !focus_window->WasActive && popup_window)
|
|
{
|
|
// Fallback
|
|
FocusTopMostWindowUnderOne(popup_window, NULL);
|
|
}
|
|
else
|
|
{
|
|
if (g.NavLayer == ImGuiNavLayer_Main && focus_window)
|
|
focus_window = NavRestoreLastChildNavWindow(focus_window);
|
|
FocusWindow(focus_window);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Close the popup we have begin-ed into.
|
|
void ImGui::CloseCurrentPopup()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
int popup_idx = g.BeginPopupStack.Size - 1;
|
|
if (popup_idx < 0 || popup_idx >= g.OpenPopupStack.Size ||
|
|
g.BeginPopupStack[popup_idx].PopupId != g.OpenPopupStack[popup_idx].PopupId)
|
|
return;
|
|
|
|
// Closing a menu closes its top-most parent popup (unless a modal)
|
|
while (popup_idx > 0)
|
|
{
|
|
ImGuiWindow* popup_window = g.OpenPopupStack[popup_idx].Window;
|
|
ImGuiWindow* parent_popup_window = g.OpenPopupStack[popup_idx - 1].Window;
|
|
bool close_parent = false;
|
|
if (popup_window && (popup_window->Flags & ImGuiWindowFlags_ChildMenu))
|
|
if (parent_popup_window == NULL || !(parent_popup_window->Flags & ImGuiWindowFlags_Modal))
|
|
close_parent = true;
|
|
if (!close_parent)
|
|
break;
|
|
popup_idx--;
|
|
}
|
|
IMGUI_DEBUG_LOG_POPUP("CloseCurrentPopup %d -> %d\n", g.BeginPopupStack.Size - 1, popup_idx);
|
|
ClosePopupToLevel(popup_idx, true);
|
|
|
|
// A common pattern is to close a popup when selecting a menu item/selectable that will open another window.
|
|
// To improve this usage pattern, we avoid nav highlight for a single frame in the parent window.
|
|
// Similarly, we could avoid mouse hover highlight in this window but it is less visually problematic.
|
|
if (ImGuiWindow* window = g.NavWindow)
|
|
window->DC.NavHideHighlightOneFrame = true;
|
|
}
|
|
|
|
// Attention! BeginPopup() adds default flags which BeginPopupEx()!
|
|
bool ImGui::BeginPopupEx(ImGuiID id, ImGuiWindowFlags flags)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (!IsPopupOpen(id, ImGuiPopupFlags_None))
|
|
{
|
|
g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
|
|
return false;
|
|
}
|
|
|
|
char name[20];
|
|
if (flags & ImGuiWindowFlags_ChildMenu)
|
|
ImFormatString(name, IM_ARRAYSIZE(name), "##Menu_%02d",
|
|
g.BeginPopupStack.Size); // Recycle windows based on depth
|
|
else
|
|
ImFormatString(name, IM_ARRAYSIZE(name), "##Popup_%08x",
|
|
id); // Not recycling, so we can close/open during the same frame
|
|
|
|
flags |= ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoDocking;
|
|
bool is_open = Begin(name, NULL, flags);
|
|
if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display)
|
|
EndPopup();
|
|
|
|
return is_open;
|
|
}
|
|
|
|
bool ImGui::BeginPopup(const char* str_id, ImGuiWindowFlags flags)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (g.OpenPopupStack.Size <= g.BeginPopupStack.Size) // Early out for performance
|
|
{
|
|
g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
|
|
return false;
|
|
}
|
|
flags |= ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings;
|
|
return BeginPopupEx(g.CurrentWindow->GetID(str_id), flags);
|
|
}
|
|
|
|
// If 'p_open' is specified for a modal popup window, the popup will have a regular close button which will close the
|
|
// popup. Note that popup visibility status is owned by Dear ImGui (and manipulated with e.g. OpenPopup) so the actual
|
|
// value of *p_open is meaningless here.
|
|
bool ImGui::BeginPopupModal(const char* name, bool* p_open, ImGuiWindowFlags flags)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
const ImGuiID id = window->GetID(name);
|
|
if (!IsPopupOpen(id, ImGuiPopupFlags_None))
|
|
{
|
|
g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
|
|
return false;
|
|
}
|
|
|
|
// Center modal windows by default for increased visibility
|
|
// (this won't really last as settings will kick in, and is mostly for backward compatibility. user may do the same
|
|
// themselves)
|
|
// FIXME: Should test for (PosCond & window->SetWindowPosAllowFlags) with the upcoming window.
|
|
if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasPos) == 0)
|
|
{
|
|
const ImGuiViewport* viewport = window->WasActive
|
|
? window->Viewport
|
|
: GetMainViewport(); // FIXME-VIEWPORT: What may be our reference viewport?
|
|
SetNextWindowPos(viewport->GetCenter(), ImGuiCond_FirstUseEver, ImVec2(0.5f, 0.5f));
|
|
}
|
|
|
|
flags |= ImGuiWindowFlags_Popup | ImGuiWindowFlags_Modal | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDocking;
|
|
const bool is_open = Begin(name, p_open, flags);
|
|
if (!is_open ||
|
|
(p_open &&
|
|
!*p_open)) // NB: is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
|
|
{
|
|
EndPopup();
|
|
if (is_open)
|
|
ClosePopupToLevel(g.BeginPopupStack.Size, true);
|
|
return false;
|
|
}
|
|
return is_open;
|
|
}
|
|
|
|
void ImGui::EndPopup()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
IM_ASSERT(window->Flags & ImGuiWindowFlags_Popup); // Mismatched BeginPopup()/EndPopup() calls
|
|
IM_ASSERT(g.BeginPopupStack.Size > 0);
|
|
|
|
// Make all menus and popups wrap around for now, may need to expose that policy.
|
|
if (g.NavWindow == window)
|
|
NavMoveRequestTryWrapping(window, ImGuiNavMoveFlags_LoopY);
|
|
|
|
// Child-popups don't need to be laid out
|
|
IM_ASSERT(g.WithinEndChild == false);
|
|
if (window->Flags & ImGuiWindowFlags_ChildWindow)
|
|
g.WithinEndChild = true;
|
|
End();
|
|
g.WithinEndChild = false;
|
|
}
|
|
|
|
// Helper to open a popup if mouse button is released over the item
|
|
// - This is essentially the same as BeginPopupContextItem() but without the trailing BeginPopup()
|
|
void ImGui::OpenPopupOnItemClick(const char* str_id, ImGuiPopupFlags popup_flags)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_);
|
|
if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup))
|
|
{
|
|
ImGuiID id = str_id ? window->GetID(str_id)
|
|
: g.LastItemData.ID; // If user hasn't passed an ID, we can use the LastItemID. Using
|
|
// LastItemID as a Popup ID won't conflict!
|
|
IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item)
|
|
OpenPopupEx(id, popup_flags);
|
|
}
|
|
}
|
|
|
|
// This is a helper to handle the simplest case of associating one named popup to one given widget.
|
|
// - To create a popup associated to the last item, you generally want to pass a NULL value to str_id.
|
|
// - To create a popup with a specific identifier, pass it in str_id.
|
|
// - This is useful when using using BeginPopupContextItem() on an item which doesn't have an identifier, e.g. a
|
|
// Text() call.
|
|
// - This is useful when multiple code locations may want to manipulate/open the same popup, given an explicit id.
|
|
// - You may want to handle the whole on user side if you have specific needs (e.g. tweaking IsItemHovered()
|
|
// parameters).
|
|
// This is essentially the same as:
|
|
// id = str_id ? GetID(str_id) : GetItemID();
|
|
// OpenPopupOnItemClick(str_id);
|
|
// return BeginPopup(id);
|
|
// Which is essentially the same as:
|
|
// id = str_id ? GetID(str_id) : GetItemID();
|
|
// if (IsItemHovered() && IsMouseReleased(ImGuiMouseButton_Right))
|
|
// OpenPopup(id);
|
|
// return BeginPopup(id);
|
|
// The main difference being that this is tweaked to avoid computing the ID twice.
|
|
bool ImGui::BeginPopupContextItem(const char* str_id, ImGuiPopupFlags popup_flags)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
if (window->SkipItems)
|
|
return false;
|
|
ImGuiID id =
|
|
str_id ? window->GetID(str_id) : g.LastItemData.ID; // If user hasn't passed an ID, we can use the LastItemID.
|
|
// Using LastItemID as a Popup ID won't conflict!
|
|
IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item)
|
|
int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_);
|
|
if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup))
|
|
OpenPopupEx(id, popup_flags);
|
|
return BeginPopupEx(
|
|
id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings);
|
|
}
|
|
|
|
bool ImGui::BeginPopupContextWindow(const char* str_id, ImGuiPopupFlags popup_flags)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
if (!str_id)
|
|
str_id = "window_context";
|
|
ImGuiID id = window->GetID(str_id);
|
|
int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_);
|
|
if (IsMouseReleased(mouse_button) && IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup))
|
|
if (!(popup_flags & ImGuiPopupFlags_NoOpenOverItems) || !IsAnyItemHovered())
|
|
OpenPopupEx(id, popup_flags);
|
|
return BeginPopupEx(
|
|
id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings);
|
|
}
|
|
|
|
bool ImGui::BeginPopupContextVoid(const char* str_id, ImGuiPopupFlags popup_flags)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
if (!str_id)
|
|
str_id = "void_context";
|
|
ImGuiID id = window->GetID(str_id);
|
|
int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_);
|
|
if (IsMouseReleased(mouse_button) && !IsWindowHovered(ImGuiHoveredFlags_AnyWindow))
|
|
if (GetTopMostPopupModal() == NULL)
|
|
OpenPopupEx(id, popup_flags);
|
|
return BeginPopupEx(
|
|
id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings);
|
|
}
|
|
|
|
// 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);
|
|
// GetForegroundDrawList()->AddRect(r_avoid.Min, r_avoid.Max, IM_COL32(255,0,0,255));
|
|
// GetForegroundDrawList()->AddRect(r_outer.Min, r_outer.Max, IM_COL32(0,255,0,255));
|
|
|
|
// Combo Box policy (we want a connecting edge)
|
|
if (policy == ImGuiPopupPositionPolicy_ComboBox)
|
|
{
|
|
const ImGuiDir dir_prefered_order[ImGuiDir_COUNT] = {ImGuiDir_Down, ImGuiDir_Right, ImGuiDir_Left, ImGuiDir_Up};
|
|
for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_COUNT; n++)
|
|
{
|
|
const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n];
|
|
if (n != -1 && dir == *last_dir) // Already tried this direction?
|
|
continue;
|
|
ImVec2 pos;
|
|
if (dir == ImGuiDir_Down)
|
|
pos = ImVec2(r_avoid.Min.x, r_avoid.Max.y); // Below, Toward Right (default)
|
|
if (dir == ImGuiDir_Right)
|
|
pos = ImVec2(r_avoid.Min.x, r_avoid.Min.y - size.y); // Above, Toward Right
|
|
if (dir == ImGuiDir_Left)
|
|
pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Max.y); // Below, Toward Left
|
|
if (dir == ImGuiDir_Up)
|
|
pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Min.y - size.y); // Above, Toward Left
|
|
if (!r_outer.Contains(ImRect(pos, pos + size)))
|
|
continue;
|
|
*last_dir = dir;
|
|
return pos;
|
|
}
|
|
}
|
|
|
|
// Tooltip and Default popup policy
|
|
// (Always first try the direction we used on the last frame, if any)
|
|
if (policy == ImGuiPopupPositionPolicy_Tooltip || policy == ImGuiPopupPositionPolicy_Default)
|
|
{
|
|
const ImGuiDir dir_prefered_order[ImGuiDir_COUNT] = {ImGuiDir_Right, ImGuiDir_Down, ImGuiDir_Up, ImGuiDir_Left};
|
|
for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_COUNT; n++)
|
|
{
|
|
const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n];
|
|
if (n != -1 && dir == *last_dir) // Already tried this direction?
|
|
continue;
|
|
|
|
const float avail_w = (dir == ImGuiDir_Left ? r_avoid.Min.x : r_outer.Max.x) -
|
|
(dir == ImGuiDir_Right ? r_avoid.Max.x : r_outer.Min.x);
|
|
const float avail_h = (dir == ImGuiDir_Up ? r_avoid.Min.y : r_outer.Max.y) -
|
|
(dir == ImGuiDir_Down ? r_avoid.Max.y : r_outer.Min.y);
|
|
|
|
// If there not enough room on one axis, there's no point in positioning on a side on this axis (e.g. when
|
|
// not enough width, use a top/bottom position to maximize available width)
|
|
if (avail_w < size.x && (dir == ImGuiDir_Left || dir == ImGuiDir_Right))
|
|
continue;
|
|
if (avail_h < size.y && (dir == ImGuiDir_Up || dir == ImGuiDir_Down))
|
|
continue;
|
|
|
|
ImVec2 pos;
|
|
pos.x = (dir == ImGuiDir_Left) ? r_avoid.Min.x - size.x
|
|
: (dir == ImGuiDir_Right) ? r_avoid.Max.x
|
|
: base_pos_clamped.x;
|
|
pos.y = (dir == ImGuiDir_Up) ? r_avoid.Min.y - size.y
|
|
: (dir == ImGuiDir_Down) ? r_avoid.Max.y
|
|
: base_pos_clamped.y;
|
|
|
|
// Clamp top-left corner of popup
|
|
pos.x = ImMax(pos.x, r_outer.Min.x);
|
|
pos.y = ImMax(pos.y, r_outer.Min.y);
|
|
|
|
*last_dir = dir;
|
|
return pos;
|
|
}
|
|
}
|
|
|
|
// Fallback when not enough room:
|
|
*last_dir = ImGuiDir_None;
|
|
|
|
// For tooltip we prefer avoiding the cursor at all cost even if it means that part of the tooltip won't be visible.
|
|
if (policy == ImGuiPopupPositionPolicy_Tooltip)
|
|
return ref_pos + ImVec2(2, 2);
|
|
|
|
// Otherwise try to keep within display
|
|
ImVec2 pos = ref_pos;
|
|
pos.x = ImMax(ImMin(pos.x + size.x, r_outer.Max.x) - size.x, r_outer.Min.x);
|
|
pos.y = ImMax(ImMin(pos.y + size.y, r_outer.Max.y) - size.y, r_outer.Min.y);
|
|
return pos;
|
|
}
|
|
|
|
// Note that this is used for popups, which can overlap the non work-area of individual viewports.
|
|
ImRect ImGui::GetPopupAllowedExtentRect(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
|
|
{
|
|
// Use the full viewport area (not work area) for popups
|
|
r_screen = window->Viewport->GetMainRect();
|
|
}
|
|
ImVec2 padding = g.Style.DisplaySafeAreaPadding;
|
|
r_screen.Expand(ImVec2((r_screen.GetWidth() > padding.x * 2) ? -padding.x : 0.0f,
|
|
(r_screen.GetHeight() > padding.y * 2) ? -padding.y : 0.0f));
|
|
return r_screen;
|
|
}
|
|
|
|
ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
ImRect r_outer = GetPopupAllowedExtentRect(window);
|
|
if (window->Flags & ImGuiWindowFlags_ChildMenu)
|
|
{
|
|
// Child menus typically request _any_ position within the parent menu item, and then we move the new menu
|
|
// outside the parent bounds. This is how we end up with child menus appearing (most-commonly) on the right of
|
|
// the parent menu.
|
|
ImGuiWindow* parent_window = window->ParentWindow;
|
|
float horizontal_overlap =
|
|
g.Style.ItemInnerSpacing.x; // We want some overlap to convey the relative depth of each menu (currently
|
|
// the amount of overlap is hard-coded to style.ItemSpacing.x).
|
|
ImRect r_avoid;
|
|
if (parent_window->DC.MenuBarAppending)
|
|
r_avoid =
|
|
ImRect(-FLT_MAX, parent_window->ClipRect.Min.y, FLT_MAX,
|
|
parent_window->ClipRect.Max
|
|
.y); // Avoid parent menu-bar. If we wanted multi-line menu-bar, we may instead want to have
|
|
// the calling window setup e.g. a NextWindowData.PosConstraintAvoidRect field
|
|
else
|
|
r_avoid = ImRect(
|
|
parent_window->Pos.x + horizontal_overlap, -FLT_MAX,
|
|
parent_window->Pos.x + parent_window->Size.x - horizontal_overlap - parent_window->ScrollbarSizes.x,
|
|
FLT_MAX);
|
|
return FindBestWindowPosForPopupEx(window->Pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid,
|
|
ImGuiPopupPositionPolicy_Default);
|
|
}
|
|
if (window->Flags & ImGuiWindowFlags_Popup)
|
|
{
|
|
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,
|
|
ImGuiPopupPositionPolicy_Default);
|
|
}
|
|
if (window->Flags & ImGuiWindowFlags_Tooltip)
|
|
{
|
|
// Position tooltip (always follows mouse)
|
|
float sc = g.Style.MouseCursorScale;
|
|
ImVec2 ref_pos = NavCalcPreferredRefPos();
|
|
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);
|
|
else
|
|
r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 24 * sc,
|
|
ref_pos.y + 24 * sc); // FIXME: Hard-coded based on mouse cursor shape expectation. Exact
|
|
// dimension not very important.
|
|
return FindBestWindowPosForPopupEx(ref_pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid,
|
|
ImGuiPopupPositionPolicy_Tooltip);
|
|
}
|
|
IM_ASSERT(0);
|
|
return window->Pos;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// [SECTION] KEYBOARD/GAMEPAD NAVIGATION
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// FIXME-NAV: The existence of SetNavID vs SetFocusID properly needs to be clarified/reworked.
|
|
void ImGui::SetNavID(ImGuiID id, ImGuiNavLayer nav_layer, ImGuiID focus_scope_id, const ImRect& rect_rel)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(g.NavWindow != NULL);
|
|
IM_ASSERT(nav_layer == ImGuiNavLayer_Main || nav_layer == ImGuiNavLayer_Menu);
|
|
g.NavId = id;
|
|
g.NavLayer = nav_layer;
|
|
g.NavFocusScopeId = focus_scope_id;
|
|
g.NavWindow->NavLastIds[nav_layer] = id;
|
|
g.NavWindow->NavRectRel[nav_layer] = rect_rel;
|
|
// g.NavDisableHighlight = false;
|
|
// g.NavDisableMouseHover = g.NavMousePosDirty = true;
|
|
}
|
|
|
|
void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(id != 0);
|
|
|
|
// Assume that SetFocusID() is called in the context where its window->DC.NavLayerCurrent and
|
|
// window->DC.NavFocusScopeIdCurrent are valid. Note that window may be != g.CurrentWindow (e.g. SetFocusID call in
|
|
// InputTextEx for multi-line text)
|
|
const ImGuiNavLayer nav_layer = window->DC.NavLayerCurrent;
|
|
if (g.NavWindow != window)
|
|
g.NavInitRequest = false;
|
|
g.NavWindow = window;
|
|
g.NavId = id;
|
|
g.NavLayer = nav_layer;
|
|
g.NavFocusScopeId = window->DC.NavFocusScopeIdCurrent;
|
|
window->NavLastIds[nav_layer] = id;
|
|
if (g.LastItemData.ID == id)
|
|
window->NavRectRel[nav_layer] =
|
|
ImRect(g.LastItemData.Rect.Min - window->Pos, g.LastItemData.Rect.Max - window->Pos);
|
|
|
|
if (g.ActiveIdSource == ImGuiInputSource_Nav)
|
|
g.NavDisableMouseHover = true;
|
|
else
|
|
g.NavDisableHighlight = true;
|
|
}
|
|
|
|
ImGuiDir ImGetDirQuadrantFromDelta(float dx, float dy)
|
|
{
|
|
if (ImFabs(dx) > ImFabs(dy))
|
|
return (dx > 0.0f) ? ImGuiDir_Right : ImGuiDir_Left;
|
|
return (dy > 0.0f) ? ImGuiDir_Down : ImGuiDir_Up;
|
|
}
|
|
|
|
static float inline NavScoreItemDistInterval(float a0, float a1, float b0, float b1)
|
|
{
|
|
if (a1 < b0)
|
|
return a1 - b0;
|
|
if (b1 < a0)
|
|
return a0 - b1;
|
|
return 0.0f;
|
|
}
|
|
|
|
static void inline NavClampRectToVisibleAreaForMoveDir(ImGuiDir move_dir, ImRect& r, const ImRect& clip_rect)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
|
|
// Scoring function for gamepad/keyboard directional navigation. Based on https://gist.github.com/rygorous/6981057
|
|
static bool ImGui::NavScoreItem(ImGuiNavItemData* result, ImRect cand)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
if (g.NavLayer != window->DC.NavLayerCurrent)
|
|
return false;
|
|
|
|
const ImRect& curr = g.NavScoringRect; // 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++;
|
|
|
|
// 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.Overlaps(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 = (g.LastItemData.ID < g.NavId) ? ImGuiDir_Left : ImGuiDir_Right;
|
|
}
|
|
|
|
# if IMGUI_DEBUG_NAV_SCORING
|
|
char buf[128];
|
|
if (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 = GetForegroundDrawList(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 + 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 = GetForegroundDrawList(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 == ImGuiNavLayer_Menu && !(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;
|
|
}
|
|
|
|
static void ImGui::NavApplyItemToResult(ImGuiNavItemData* result,
|
|
ImGuiWindow* window,
|
|
ImGuiID id,
|
|
const ImRect& nav_bb_rel)
|
|
{
|
|
result->Window = window;
|
|
result->ID = id;
|
|
result->FocusScopeId = window->DC.NavFocusScopeIdCurrent;
|
|
result->RectRel = nav_bb_rel;
|
|
}
|
|
|
|
// 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 = g.LastItemData.InFlags;
|
|
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
|
|
const bool candidate_for_nav_default_focus =
|
|
(item_flags & (ImGuiItemFlags_NoNavDefaultFocus | ImGuiItemFlags_Disabled)) == 0;
|
|
if (candidate_for_nav_default_focus || g.NavInitResultId == 0)
|
|
{
|
|
g.NavInitResultId = id;
|
|
g.NavInitResultRectRel = nav_bb_rel;
|
|
}
|
|
if (candidate_for_nav_default_focus)
|
|
{
|
|
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_Disabled | ImGuiItemFlags_NoNav)))
|
|
{
|
|
ImGuiNavItemData* 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)
|
|
NavApplyItemToResult(result, window, id, nav_bb_rel);
|
|
|
|
// Features like PageUp/PageDown need to maintain a separate score for the visible set of items.
|
|
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))
|
|
NavApplyItemToResult(&g.NavMoveResultLocalVisibleSet, window, id, 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.NavFocusScopeId = window->DC.NavFocusScopeIdCurrent;
|
|
g.NavIdIsAlive = true;
|
|
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);
|
|
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;
|
|
|
|
// Navigation wrap-around logic is delayed to the end of the frame because this operation is only valid after entire
|
|
// popup is assembled and in case of appended popups it is not clear which EndPopup() call is final.
|
|
g.NavWrapRequestWindow = window;
|
|
g.NavWrapRequestFlags = move_flags;
|
|
}
|
|
|
|
// FIXME: This could be replaced by updating a frame number in each window when (window == NavWindow) and (NavLayer ==
|
|
// 0). This way we could find the last focused window among our children. It would be much less confusing this way?
|
|
static void ImGui::NavSaveLastChildNavWindowIntoParent(ImGuiWindow* nav_window)
|
|
{
|
|
ImGuiWindow* parent = nav_window;
|
|
while (parent && parent->RootWindow != parent &&
|
|
(parent->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0)
|
|
parent = parent->ParentWindow;
|
|
if (parent && parent != nav_window)
|
|
parent->NavLastChildNavWindow = nav_window;
|
|
}
|
|
|
|
// Restore the last focused child.
|
|
// Call when we are expected to land on the Main Layer (0) after FocusWindow()
|
|
static ImGuiWindow* ImGui::NavRestoreLastChildNavWindow(ImGuiWindow* window)
|
|
{
|
|
if (window->NavLastChildNavWindow && window->NavLastChildNavWindow->WasActive)
|
|
return window->NavLastChildNavWindow;
|
|
if (window->DockNodeAsHost && window->DockNodeAsHost->TabBar)
|
|
if (ImGuiTabItem* tab = TabBarFindMostRecentlySelectedTabForActiveWindow(window->DockNodeAsHost->TabBar))
|
|
return tab->Window;
|
|
return window;
|
|
}
|
|
|
|
void ImGui::NavRestoreLayer(ImGuiNavLayer layer)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (layer == ImGuiNavLayer_Main)
|
|
g.NavWindow = NavRestoreLastChildNavWindow(g.NavWindow);
|
|
ImGuiWindow* window = g.NavWindow;
|
|
if (window->NavLastIds[layer] != 0)
|
|
{
|
|
SetNavID(window->NavLastIds[layer], layer, 0, window->NavRectRel[layer]);
|
|
g.NavDisableHighlight = false;
|
|
g.NavDisableMouseHover = g.NavMousePosDirty = true;
|
|
}
|
|
else
|
|
{
|
|
g.NavLayer = layer;
|
|
NavInitWindow(window, 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)
|
|
{
|
|
// FIXME: ChildWindow test here is wrong for docking
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(window == g.NavWindow);
|
|
|
|
if (window->Flags & ImGuiWindowFlags_NoNavInputs)
|
|
{
|
|
g.NavId = g.NavFocusScopeId = 0;
|
|
return;
|
|
}
|
|
|
|
bool init_for_nav = false;
|
|
if (window == window->RootWindow || (window->Flags & ImGuiWindowFlags_Popup) || (window->NavLastIds[0] == 0) ||
|
|
force_reinit)
|
|
init_for_nav = true;
|
|
IMGUI_DEBUG_LOG_NAV("[nav] NavInitRequest: from NavInitWindow(), init_for_nav=%d, window=\"%s\", layer=%d\n",
|
|
init_for_nav, window->Name, g.NavLayer);
|
|
if (init_for_nav)
|
|
{
|
|
SetNavID(0, g.NavLayer, 0, ImRect());
|
|
g.NavInitRequest = true;
|
|
g.NavInitRequestFromMove = false;
|
|
g.NavInitResultId = 0;
|
|
g.NavInitResultRectRel = ImRect();
|
|
NavUpdateAnyRequestFlag();
|
|
}
|
|
else
|
|
{
|
|
g.NavId = window->NavLastIds[0];
|
|
g.NavFocusScopeId = 0;
|
|
}
|
|
}
|
|
|
|
static ImVec2 ImGui::NavCalcPreferredRefPos()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (g.NavDisableHighlight || !g.NavDisableMouseHover || !g.NavWindow)
|
|
{
|
|
// Mouse (we need a fallback in case the mouse becomes invalid after being used)
|
|
if (IsMousePosValid(&g.IO.MousePos))
|
|
return g.IO.MousePos;
|
|
return g.LastValidMousePos;
|
|
}
|
|
else
|
|
{
|
|
// 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()));
|
|
ImGuiViewport* viewport = g.NavWindow->Viewport;
|
|
return ImFloor(ImClamp(
|
|
pos, viewport->Pos,
|
|
viewport->Pos + viewport->Size)); // ImFloor() is important because non-integer mouse position application
|
|
// in backend 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;
|
|
if (mode == ImGuiInputReadMode_Repeat)
|
|
return (float)CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, g.IO.KeyRepeatDelay * 0.72f,
|
|
g.IO.KeyRepeatRate * 0.80f);
|
|
if (mode == ImGuiInputReadMode_RepeatSlow)
|
|
return (float)CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, g.IO.KeyRepeatDelay * 1.25f,
|
|
g.IO.KeyRepeatRate * 2.00f);
|
|
if (mode == ImGuiInputReadMode_RepeatFast)
|
|
return (float)CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, g.IO.KeyRepeatDelay * 0.72f,
|
|
g.IO.KeyRepeatRate * 0.30f);
|
|
return 0.0f;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static void ImGui::NavUpdate()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiIO& io = g.IO;
|
|
|
|
io.WantSetMousePos = false;
|
|
g.NavWrapRequestWindow = NULL;
|
|
g.NavWrapRequestFlags = ImGuiNavMoveFlags_None;
|
|
# if 0
|
|
if (g.NavScoringCount > 0) IMGUI_DEBUG_LOG("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 (as some features differs when used with Gamepad vs
|
|
// Keyboard) (do it before we map Keyboard input!)
|
|
bool nav_keyboard_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0;
|
|
bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 &&
|
|
(io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
|
|
if (nav_gamepad_active && g.NavInputSource != ImGuiInputSource_Gamepad)
|
|
{
|
|
if (io.NavInputs[ImGuiNavInput_Activate] > 0.0f || io.NavInputs[ImGuiNavInput_Input] > 0.0f ||
|
|
io.NavInputs[ImGuiNavInput_Cancel] > 0.0f || io.NavInputs[ImGuiNavInput_Menu] > 0.0f ||
|
|
io.NavInputs[ImGuiNavInput_DpadLeft] > 0.0f || io.NavInputs[ImGuiNavInput_DpadRight] > 0.0f ||
|
|
io.NavInputs[ImGuiNavInput_DpadUp] > 0.0f || io.NavInputs[ImGuiNavInput_DpadDown] > 0.0f)
|
|
g.NavInputSource = ImGuiInputSource_Gamepad;
|
|
}
|
|
|
|
// Update Keyboard->Nav inputs mapping
|
|
if (nav_keyboard_active)
|
|
{
|
|
# define NAV_MAP_KEY(_KEY, _NAV_INPUT) \
|
|
do \
|
|
{ \
|
|
if (IsKeyDown(io.KeyMap[_KEY])) \
|
|
{ \
|
|
io.NavInputs[_NAV_INPUT] = 1.0f; \
|
|
g.NavInputSource = ImGuiInputSource_Keyboard; \
|
|
} \
|
|
} while (0)
|
|
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 (io.KeyCtrl)
|
|
io.NavInputs[ImGuiNavInput_TweakSlow] = 1.0f;
|
|
if (io.KeyShift)
|
|
io.NavInputs[ImGuiNavInput_TweakFast] = 1.0f;
|
|
# undef NAV_MAP_KEY
|
|
}
|
|
memcpy(io.NavInputsDownDurationPrev, io.NavInputsDownDuration, sizeof(io.NavInputsDownDuration));
|
|
for (int i = 0; i < IM_ARRAYSIZE(io.NavInputs); i++)
|
|
io.NavInputsDownDuration[i] =
|
|
(io.NavInputs[i] > 0.0f)
|
|
? (io.NavInputsDownDuration[i] < 0.0f ? 0.0f : io.NavInputsDownDuration[i] + io.DeltaTime)
|
|
: -1.0f;
|
|
|
|
// Process navigation init request (select first/default focus)
|
|
if (g.NavInitResultId != 0)
|
|
NavUpdateInitResult();
|
|
g.NavInitRequest = false;
|
|
g.NavInitRequestFromMove = false;
|
|
g.NavInitResultId = 0;
|
|
g.NavJustMovedToId = 0;
|
|
|
|
// Process navigation move request
|
|
if (g.NavMoveRequest)
|
|
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 ((io.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos) &&
|
|
(io.BackendFlags & ImGuiBackendFlags_HasSetMousePos))
|
|
if (!g.NavDisableHighlight && g.NavDisableMouseHover && g.NavWindow)
|
|
{
|
|
io.MousePos = io.MousePosPrev = NavCalcPreferredRefPos();
|
|
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)
|
|
NavSaveLastChildNavWindowIntoParent(g.NavWindow);
|
|
if (g.NavWindow && g.NavWindow->NavLastChildNavWindow != NULL && g.NavLayer == ImGuiNavLayer_Main)
|
|
g.NavWindow->NavLastChildNavWindow = NULL;
|
|
|
|
// Update CTRL+TAB and Windowing features (hold Square to move/resize/etc.)
|
|
NavUpdateWindowing();
|
|
|
|
// Set output flags for user application
|
|
io.NavActive = (nav_keyboard_active || nav_gamepad_active) && g.NavWindow &&
|
|
!(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs);
|
|
io.NavVisible = (io.NavActive && g.NavId != 0 && !g.NavDisableHighlight) || (g.NavWindowingTarget != NULL);
|
|
|
|
// Process NavCancel input (to close a popup, get back to parent, clear focus)
|
|
if (IsNavInputTest(ImGuiNavInput_Cancel, ImGuiInputReadMode_Pressed))
|
|
{
|
|
IMGUI_DEBUG_LOG_NAV("[nav] ImGuiNavInput_Cancel\n");
|
|
if (g.ActiveId != 0)
|
|
{
|
|
if (!IsActiveIdUsingNavInput(ImGuiNavInput_Cancel))
|
|
ClearActiveID();
|
|
}
|
|
else if (g.NavLayer != ImGuiNavLayer_Main)
|
|
{
|
|
// Leave the "menu" layer
|
|
NavRestoreLayer(ImGuiNavLayer_Main);
|
|
}
|
|
else if (g.NavWindow && g.NavWindow != g.NavWindow->RootWindow &&
|
|
!(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);
|
|
ImRect child_rect = child_window->Rect();
|
|
FocusWindow(parent_window);
|
|
SetNavID(child_window->ChildId, ImGuiNavLayer_Main, 0,
|
|
ImRect(child_rect.Min - parent_window->Pos, child_rect.Max - parent_window->Pos));
|
|
}
|
|
else if (g.OpenPopupStack.Size > 0)
|
|
{
|
|
// Close open popup/menu
|
|
if (!(g.OpenPopupStack.back().Window->Flags & ImGuiWindowFlags_Modal))
|
|
ClosePopupToLevel(g.OpenPopupStack.Size - 1, true);
|
|
}
|
|
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 = g.NavFocusScopeId = 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 && IsNavInputTest(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) &&
|
|
IsNavInputTest(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
|
|
if (g.NavMoveRequestForward == ImGuiNavForward_None)
|
|
{
|
|
g.NavMoveDir = ImGuiDir_None;
|
|
g.NavMoveRequestFlags = ImGuiNavMoveFlags_None;
|
|
if (g.NavWindow && !g.NavWindowingTarget && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))
|
|
{
|
|
const ImGuiInputReadMode read_mode = ImGuiInputReadMode_Repeat;
|
|
if (!IsActiveIdUsingNavDir(ImGuiDir_Left) && (IsNavInputTest(ImGuiNavInput_DpadLeft, read_mode) ||
|
|
IsNavInputTest(ImGuiNavInput_KeyLeft_, read_mode)))
|
|
{
|
|
g.NavMoveDir = ImGuiDir_Left;
|
|
}
|
|
if (!IsActiveIdUsingNavDir(ImGuiDir_Right) && (IsNavInputTest(ImGuiNavInput_DpadRight, read_mode) ||
|
|
IsNavInputTest(ImGuiNavInput_KeyRight_, read_mode)))
|
|
{
|
|
g.NavMoveDir = ImGuiDir_Right;
|
|
}
|
|
if (!IsActiveIdUsingNavDir(ImGuiDir_Up) &&
|
|
(IsNavInputTest(ImGuiNavInput_DpadUp, read_mode) || IsNavInputTest(ImGuiNavInput_KeyUp_, read_mode)))
|
|
{
|
|
g.NavMoveDir = ImGuiDir_Up;
|
|
}
|
|
if (!IsActiveIdUsingNavDir(ImGuiDir_Down) && (IsNavInputTest(ImGuiNavInput_DpadDown, read_mode) ||
|
|
IsNavInputTest(ImGuiNavInput_KeyDown_, read_mode)))
|
|
{
|
|
g.NavMoveDir = ImGuiDir_Down;
|
|
}
|
|
}
|
|
g.NavMoveClipDir = g.NavMoveDir;
|
|
}
|
|
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);
|
|
IMGUI_DEBUG_LOG_NAV("[nav] NavMoveRequestForward %d\n", g.NavMoveDir);
|
|
g.NavMoveRequestForward = ImGuiNavForward_ForwardActive;
|
|
}
|
|
|
|
// Update PageUp/PageDown/Home/End scroll
|
|
// FIXME-NAV: Consider enabling those keys even without the master ImGuiConfigFlags_NavEnableKeyboard flag?
|
|
float nav_scoring_rect_offset_y = 0.0f;
|
|
if (nav_keyboard_active)
|
|
nav_scoring_rect_offset_y = NavUpdatePageUpPageDown();
|
|
|
|
// 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)
|
|
{
|
|
g.NavMoveRequest = true;
|
|
g.NavMoveRequestKeyMods = io.KeyMods;
|
|
g.NavMoveDirLast = g.NavMoveDir;
|
|
}
|
|
if (g.NavMoveRequest && g.NavId == 0)
|
|
{
|
|
IMGUI_DEBUG_LOG_NAV("[nav] NavInitRequest: from move, window \"%s\", layer=%d\n", g.NavWindow->Name,
|
|
g.NavLayer);
|
|
g.NavInitRequest = g.NavInitRequestFromMove = true;
|
|
// Reassigning with same value, we're being explicit here.
|
|
g.NavInitResultId = 0; // -V1048
|
|
g.NavDisableHighlight = false;
|
|
}
|
|
NavUpdateAnyRequestFlag();
|
|
|
|
// Scrolling
|
|
if (g.NavWindow && !(g.NavWindow->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 = IM_ROUND(
|
|
window->CalcFontSize() * 100 *
|
|
io.DeltaTime); // We need round the scrolling speed because sub-pixel scroll isn't reliably supported.
|
|
if (window->DC.NavLayersActiveMask == 0x00 && window->DC.NavHasScroll && g.NavMoveRequest)
|
|
{
|
|
if (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right)
|
|
SetScrollX(window, ImFloor(window->Scroll.x +
|
|
((g.NavMoveDir == ImGuiDir_Left) ? -1.0f : +1.0f) * scroll_speed));
|
|
if (g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down)
|
|
SetScrollY(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)
|
|
SetScrollX(window, ImFloor(window->Scroll.x + scroll_dir.x * scroll_speed));
|
|
if (scroll_dir.y != 0.0f)
|
|
SetScrollY(window, ImFloor(window->Scroll.y + scroll_dir.y * scroll_speed));
|
|
}
|
|
|
|
// Reset search results
|
|
g.NavMoveResultLocal.Clear();
|
|
g.NavMoveResultLocalVisibleSet.Clear();
|
|
g.NavMoveResultOther.Clear();
|
|
|
|
// When using gamepad, we project the reference nav bounding box into window visible area.
|
|
// This is to allow resuming navigation inside the visible area after doing a large amount of scrolling, since with
|
|
// gamepad every movements are relative (can't focus a visible object like we can with the mouse).
|
|
if (g.NavMoveRequest && g.NavInputSource == ImGuiInputSource_Gamepad && g.NavLayer == ImGuiNavLayer_Main)
|
|
{
|
|
ImGuiWindow* window = g.NavWindow;
|
|
ImRect window_rect_rel(window->InnerRect.Min - window->Pos - ImVec2(1, 1),
|
|
window->InnerRect.Max - window->Pos + ImVec2(1, 1));
|
|
if (!window_rect_rel.Contains(window->NavRectRel[g.NavLayer]))
|
|
{
|
|
IMGUI_DEBUG_LOG_NAV("[nav] NavMoveRequest: clamp NavRectRel\n");
|
|
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].ClipWithFull(window_rect_rel);
|
|
g.NavId = g.NavFocusScopeId = 0;
|
|
}
|
|
}
|
|
|
|
// 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.NavScoringRect = g.NavWindow ? ImRect(g.NavWindow->Pos + nav_rect_rel.Min, g.NavWindow->Pos + nav_rect_rel.Max)
|
|
: ImRect(0, 0, 0, 0);
|
|
g.NavScoringRect.TranslateY(nav_scoring_rect_offset_y);
|
|
g.NavScoringRect.Min.x = ImMin(g.NavScoringRect.Min.x + 1.0f, g.NavScoringRect.Max.x);
|
|
g.NavScoringRect.Max.x = g.NavScoringRect.Min.x;
|
|
IM_ASSERT(!g.NavScoringRect.IsInverted()); // Ensure if we have a finite, non-inverted bounding box here will
|
|
// allows us to remove extraneous ImFabs() calls in NavScoreItem().
|
|
// GetForegroundDrawList()->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)
|
|
{
|
|
ImDrawList* draw_list = GetForegroundDrawList(g.NavWindow);
|
|
if (1)
|
|
{
|
|
for (int layer = 0; layer < 2; layer++)
|
|
draw_list->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 (1)
|
|
{
|
|
ImU32 col = (!g.NavWindow->Hidden) ? IM_COL32(255, 0, 255, 255) : IM_COL32(255, 0, 0, 255);
|
|
ImVec2 p = NavCalcPreferredRefPos();
|
|
char buf[32];
|
|
ImFormatString(buf, 32, "%d", g.NavLayer);
|
|
draw_list->AddCircleFilled(p, 3.0f, col);
|
|
draw_list->AddText(NULL, 13.0f, p + ImVec2(8, -4), col, buf);
|
|
}
|
|
}
|
|
# endif
|
|
}
|
|
|
|
static void ImGui::NavUpdateInitResult()
|
|
{
|
|
// In very rare cases g.NavWindow may be null (e.g. clearing focus after requesting an init request, which does
|
|
// happen when releasing Alt while clicking on void)
|
|
ImGuiContext& g = *GImGui;
|
|
if (!g.NavWindow)
|
|
return;
|
|
|
|
// Apply result from previous navigation init request (will typically select the first item, unless
|
|
// SetItemDefaultFocus() has been called)
|
|
// FIXME-NAV: On _NavFlattened windows, g.NavWindow will only be updated during subsequent frame. Not a problem
|
|
// currently.
|
|
IMGUI_DEBUG_LOG_NAV("[nav] NavInitRequest: result NavID 0x%08X in Layer %d Window \"%s\"\n", g.NavInitResultId,
|
|
g.NavLayer, g.NavWindow->Name);
|
|
SetNavID(g.NavInitResultId, g.NavLayer, 0, g.NavInitResultRectRel);
|
|
if (g.NavInitRequestFromMove)
|
|
{
|
|
g.NavDisableHighlight = false;
|
|
g.NavDisableMouseHover = g.NavMousePosDirty = true;
|
|
}
|
|
}
|
|
|
|
// Apply result from previous frame navigation directional move request
|
|
static void ImGui::NavUpdateMoveResult()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0)
|
|
{
|
|
// In a situation when there is no results but NavId != 0, re-enable the Navigation highlight (because g.NavId
|
|
// is not considered as a possible result)
|
|
if (g.NavId != 0)
|
|
{
|
|
g.NavDisableHighlight = false;
|
|
g.NavDisableMouseHover = true;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Select which result to use
|
|
ImGuiNavItemData* 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 == ImGuiNavLayer_Main)
|
|
{
|
|
ImVec2 delta_scroll;
|
|
if (g.NavMoveRequestFlags & ImGuiNavMoveFlags_ScrollToEdge)
|
|
{
|
|
float scroll_target = (g.NavMoveDir == ImGuiDir_Up) ? result->Window->ScrollMax.y : 0.0f;
|
|
delta_scroll.y = result->Window->Scroll.y - scroll_target;
|
|
SetScrollY(result->Window, scroll_target);
|
|
}
|
|
else
|
|
{
|
|
ImRect rect_abs =
|
|
ImRect(result->RectRel.Min + result->Window->Pos, result->RectRel.Max + result->Window->Pos);
|
|
delta_scroll = ScrollToBringRectIntoView(result->Window, rect_abs);
|
|
}
|
|
|
|
// Offset our result position so mouse position can be applied immediately after in NavUpdate()
|
|
result->RectRel.TranslateX(-delta_scroll.x);
|
|
result->RectRel.TranslateY(-delta_scroll.y);
|
|
}
|
|
|
|
ClearActiveID();
|
|
g.NavWindow = result->Window;
|
|
if (g.NavId != result->ID)
|
|
{
|
|
// Don't set NavJustMovedToId if just landed on the same spot (which may happen with
|
|
// ImGuiNavMoveFlags_AllowCurrentNavId)
|
|
g.NavJustMovedToId = result->ID;
|
|
g.NavJustMovedToFocusScopeId = result->FocusScopeId;
|
|
g.NavJustMovedToKeyMods = g.NavMoveRequestKeyMods;
|
|
}
|
|
IMGUI_DEBUG_LOG_NAV("[nav] NavMoveRequest: result NavID 0x%08X in Layer %d Window \"%s\"\n", result->ID, g.NavLayer,
|
|
g.NavWindow->Name);
|
|
SetNavID(result->ID, g.NavLayer, result->FocusScopeId, result->RectRel);
|
|
g.NavDisableHighlight = false;
|
|
g.NavDisableMouseHover = g.NavMousePosDirty = true;
|
|
}
|
|
|
|
// Handle PageUp/PageDown/Home/End keys
|
|
static float ImGui::NavUpdatePageUpPageDown()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiIO& io = g.IO;
|
|
|
|
if (g.NavMoveDir != ImGuiDir_None || g.NavWindow == NULL)
|
|
return 0.0f;
|
|
if ((g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) || g.NavWindowingTarget != NULL ||
|
|
g.NavLayer != ImGuiNavLayer_Main)
|
|
return 0.0f;
|
|
|
|
ImGuiWindow* window = g.NavWindow;
|
|
const bool page_up_held = IsKeyDown(io.KeyMap[ImGuiKey_PageUp]) && !IsActiveIdUsingKey(ImGuiKey_PageUp);
|
|
const bool page_down_held = IsKeyDown(io.KeyMap[ImGuiKey_PageDown]) && !IsActiveIdUsingKey(ImGuiKey_PageDown);
|
|
const bool home_pressed = IsKeyPressed(io.KeyMap[ImGuiKey_Home]) && !IsActiveIdUsingKey(ImGuiKey_Home);
|
|
const bool end_pressed = IsKeyPressed(io.KeyMap[ImGuiKey_End]) && !IsActiveIdUsingKey(ImGuiKey_End);
|
|
if (page_up_held != page_down_held || home_pressed != end_pressed) // If either (not both) are pressed
|
|
{
|
|
if (window->DC.NavLayersActiveMask == 0x00 && window->DC.NavHasScroll)
|
|
{
|
|
// Fallback manual-scroll when window has no navigable item
|
|
if (IsKeyPressed(io.KeyMap[ImGuiKey_PageUp], true))
|
|
SetScrollY(window, window->Scroll.y - window->InnerRect.GetHeight());
|
|
else if (IsKeyPressed(io.KeyMap[ImGuiKey_PageDown], true))
|
|
SetScrollY(window, window->Scroll.y + window->InnerRect.GetHeight());
|
|
else if (home_pressed)
|
|
SetScrollY(window, 0.0f);
|
|
else if (end_pressed)
|
|
SetScrollY(window, window->ScrollMax.y);
|
|
}
|
|
else
|
|
{
|
|
ImRect& nav_rect_rel = window->NavRectRel[g.NavLayer];
|
|
const float page_offset_y =
|
|
ImMax(0.0f, window->InnerRect.GetHeight() - window->CalcFontSize() * 1.0f + nav_rect_rel.GetHeight());
|
|
float nav_scoring_rect_offset_y = 0.0f;
|
|
if (IsKeyPressed(io.KeyMap[ImGuiKey_PageUp], true))
|
|
{
|
|
nav_scoring_rect_offset_y = -page_offset_y;
|
|
g.NavMoveDir = ImGuiDir_Down; // Because our scoring rect is offset up, we request the down direction
|
|
// (so we can always land on the last item)
|
|
g.NavMoveClipDir = ImGuiDir_Up;
|
|
g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet;
|
|
}
|
|
else if (IsKeyPressed(io.KeyMap[ImGuiKey_PageDown], true))
|
|
{
|
|
nav_scoring_rect_offset_y = +page_offset_y;
|
|
g.NavMoveDir = ImGuiDir_Up; // Because our scoring rect is offset down, we request the up direction (so
|
|
// we can always land on the last item)
|
|
g.NavMoveClipDir = ImGuiDir_Down;
|
|
g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet;
|
|
}
|
|
else if (home_pressed)
|
|
{
|
|
// FIXME-NAV: handling of Home/End is assuming that the top/bottom most item will be visible with
|
|
// Scroll.y == 0/ScrollMax.y Scrolling will be handled via the ImGuiNavMoveFlags_ScrollToEdge flag, we
|
|
// don't scroll immediately to avoid scrolling happening before nav result. Preserve current horizontal
|
|
// position if we have any.
|
|
nav_rect_rel.Min.y = nav_rect_rel.Max.y = -window->Scroll.y;
|
|
if (nav_rect_rel.IsInverted())
|
|
nav_rect_rel.Min.x = nav_rect_rel.Max.x = 0.0f;
|
|
g.NavMoveDir = ImGuiDir_Down;
|
|
g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_ScrollToEdge;
|
|
}
|
|
else if (end_pressed)
|
|
{
|
|
nav_rect_rel.Min.y = nav_rect_rel.Max.y = window->ScrollMax.y + window->SizeFull.y - window->Scroll.y;
|
|
if (nav_rect_rel.IsInverted())
|
|
nav_rect_rel.Min.x = nav_rect_rel.Max.x = 0.0f;
|
|
g.NavMoveDir = ImGuiDir_Up;
|
|
g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_ScrollToEdge;
|
|
}
|
|
return nav_scoring_rect_offset_y;
|
|
}
|
|
}
|
|
return 0.0f;
|
|
}
|
|
|
|
static void ImGui::NavEndFrame()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
// Show CTRL+TAB list window
|
|
if (g.NavWindowingTarget != NULL)
|
|
NavUpdateWindowingOverlay();
|
|
|
|
// Perform wrap-around in menus
|
|
ImGuiWindow* window = g.NavWrapRequestWindow;
|
|
ImGuiNavMoveFlags move_flags = g.NavWrapRequestFlags;
|
|
if (window != NULL && g.NavWindow == window && NavMoveRequestButNoResultYet() &&
|
|
g.NavMoveRequestForward == ImGuiNavForward_None && g.NavLayer == ImGuiNavLayer_Main)
|
|
{
|
|
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->ContentSize.x + window->WindowPadding.x * 2.0f) - 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->ContentSize.y + window->WindowPadding.y * 2.0f) - 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 int ImGui::FindWindowFocusIndex(ImGuiWindow* window)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_UNUSED(g);
|
|
int order = window->FocusOrder;
|
|
IM_ASSERT(g.WindowsFocusOrder[order] == window);
|
|
return order;
|
|
}
|
|
|
|
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 = ImGui::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;
|
|
}
|
|
|
|
// Windowing management mode
|
|
// Keyboard: CTRL+Tab (change focus/move/resize), Alt (toggle menu layer)
|
|
// Gamepad: Hold Menu/Square (change focus/move/resize), Tap Menu/Square (toggle menu layer)
|
|
static void ImGui::NavUpdateWindowing()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiIO& io = g.IO;
|
|
|
|
ImGuiWindow* apply_focus_window = NULL;
|
|
bool apply_toggle_layer = false;
|
|
|
|
ImGuiWindow* modal_window = GetTopMostPopupModal();
|
|
bool allow_windowing = (modal_window == NULL);
|
|
if (!allow_windowing)
|
|
g.NavWindowingTarget = NULL;
|
|
|
|
// Fade out
|
|
if (g.NavWindowingTargetAnim && g.NavWindowingTarget == NULL)
|
|
{
|
|
g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha - 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 =
|
|
allow_windowing && !g.NavWindowingTarget && IsNavInputTest(ImGuiNavInput_Menu, ImGuiInputReadMode_Pressed);
|
|
bool start_windowing_with_keyboard = allow_windowing && !g.NavWindowingTarget && io.KeyCtrl &&
|
|
IsKeyPressedMap(ImGuiKey_Tab) &&
|
|
(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->RootWindow;
|
|
g.NavWindowingTimer = g.NavWindowingHighlightAlpha = 0.0f;
|
|
g.NavWindowingToggleLayer = start_windowing_with_gamepad ? true : false; // Gamepad starts toggling layer
|
|
g.NavInputSource = start_windowing_with_keyboard ? ImGuiInputSource_Keyboard : ImGuiInputSource_Gamepad;
|
|
}
|
|
|
|
// Gamepad update
|
|
g.NavWindowingTimer += io.DeltaTime;
|
|
if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_Gamepad)
|
|
{
|
|
// 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)IsNavInputTest(ImGuiNavInput_FocusPrev, ImGuiInputReadMode_RepeatSlow) -
|
|
(int)IsNavInputTest(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 top-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_Keyboard)
|
|
{
|
|
// 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(io.KeyShift ? +1 : -1);
|
|
if (!io.KeyCtrl)
|
|
apply_focus_window = g.NavWindowingTarget;
|
|
}
|
|
|
|
// Keyboard: Press and Release ALT to toggle menu layer
|
|
// - Testing that only Alt is tested prevents Alt+Shift or AltGR from toggling menu layer.
|
|
// - AltGR is normally Alt+Ctrl but we can't reliably detect it (not all backends/systems/layout emit it as
|
|
// Alt+Ctrl). But even on keyboards without AltGR we don't want Alt+Ctrl to open menu anyway.
|
|
if (io.KeyMods == ImGuiKeyModFlags_Alt && (io.KeyModsPrev & ImGuiKeyModFlags_Alt) == 0)
|
|
{
|
|
g.NavWindowingToggleLayer = true;
|
|
g.NavInputSource = ImGuiInputSource_Keyboard;
|
|
}
|
|
if (g.NavWindowingToggleLayer && g.NavInputSource == ImGuiInputSource_Keyboard)
|
|
{
|
|
// We cancel toggling nav layer when any text has been typed (generally while holding Alt). (See #370)
|
|
// We cancel toggling nav layer when other modifiers are pressed. (See #4439)
|
|
if (io.InputQueueCharacters.Size > 0 || io.KeyCtrl || io.KeyShift || io.KeySuper)
|
|
g.NavWindowingToggleLayer = false;
|
|
|
|
// Apply layer toggle on release
|
|
// Important: we don't assume that Alt was previously held in order to handle loss of focus when backend calls
|
|
// io.AddFocusEvent(false) Important: as before version <18314 we lacked an explicit IO event for focus
|
|
// gain/loss, we also compare mouse validity to detect old backends clearing mouse pos on focus loss.
|
|
if (!(io.KeyMods & ImGuiKeyModFlags_Alt) && (io.KeyModsPrev & ImGuiKeyModFlags_Alt) &&
|
|
g.NavWindowingToggleLayer)
|
|
if (g.ActiveId == 0 || g.ActiveIdAllowOverlap)
|
|
if (IsMousePosValid(&io.MousePos) == IsMousePosValid(&io.MousePosPrev))
|
|
apply_toggle_layer = true;
|
|
if (!io.KeyAlt)
|
|
g.NavWindowingToggleLayer = false;
|
|
}
|
|
|
|
// Move window
|
|
if (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoMove))
|
|
{
|
|
ImVec2 move_delta;
|
|
if (g.NavInputSource == ImGuiInputSource_Keyboard && !io.KeyShift)
|
|
move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard, ImGuiInputReadMode_Down);
|
|
if (g.NavInputSource == ImGuiInputSource_Gamepad)
|
|
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 * io.DeltaTime *
|
|
ImMin(io.DisplayFramebufferScale.x,
|
|
io.DisplayFramebufferScale.y)); // FIXME: Doesn't handle variable framerate very well
|
|
ImGuiWindow* moving_window = g.NavWindowingTarget->RootWindowDockTree;
|
|
SetWindowPos(moving_window, moving_window->Pos + move_delta * move_speed, ImGuiCond_Always);
|
|
MarkIniSettingsDirty(moving_window);
|
|
g.NavDisableMouseHover = true;
|
|
}
|
|
}
|
|
|
|
// Apply final focus
|
|
if (apply_focus_window && (g.NavWindow == NULL || apply_focus_window != g.NavWindow->RootWindow))
|
|
{
|
|
ImGuiViewport* previous_viewport = g.NavWindow ? g.NavWindow->Viewport : NULL;
|
|
ClearActiveID();
|
|
g.NavDisableHighlight = false;
|
|
g.NavDisableMouseHover = true;
|
|
apply_focus_window = NavRestoreLastChildNavWindow(apply_focus_window);
|
|
ClosePopupsOverWindow(apply_focus_window, false);
|
|
FocusWindow(apply_focus_window);
|
|
if (apply_focus_window->NavLastIds[0] == 0)
|
|
NavInitWindow(apply_focus_window, false);
|
|
|
|
// If the window has ONLY a menu layer (no main layer), select it directly
|
|
// Use NavLayersActiveMaskNext since windows didn't have a chance to be Begin()-ed on this frame,
|
|
// so CTRL+Tab where the keys are only held for 1 frame will be able to use correct layers mask since
|
|
// the target window as already been previewed once.
|
|
// FIXME-NAV: This should be done in NavInit.. or in FocusWindow... However in both of those cases,
|
|
// we won't have a guarantee that windows has been visible before and therefore NavLayersActiveMask*
|
|
// won't be valid.
|
|
if (apply_focus_window->DC.NavLayersActiveMaskNext == (1 << ImGuiNavLayer_Menu))
|
|
g.NavLayer = ImGuiNavLayer_Menu;
|
|
|
|
// Request OS level focus
|
|
if (apply_focus_window->Viewport != previous_viewport && g.PlatformIO.Platform_SetWindowFocus)
|
|
g.PlatformIO.Platform_SetWindowFocus(apply_focus_window->Viewport);
|
|
}
|
|
if (apply_focus_window)
|
|
g.NavWindowingTarget = NULL;
|
|
|
|
// Apply menu/layer toggle
|
|
if (apply_toggle_layer && g.NavWindow)
|
|
{
|
|
ClearActiveID();
|
|
|
|
// Move to parent menu if necessary
|
|
ImGuiWindow* new_nav_window = g.NavWindow;
|
|
while (new_nav_window->ParentWindow &&
|
|
(new_nav_window->DC.NavLayersActiveMask & (1 << ImGuiNavLayer_Menu)) == 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;
|
|
|
|
// Reinitialize navigation when entering menu bar with the Alt key.
|
|
const ImGuiNavLayer new_nav_layer = (g.NavWindow->DC.NavLayersActiveMask & (1 << ImGuiNavLayer_Menu))
|
|
? (ImGuiNavLayer)((int)g.NavLayer ^ 1)
|
|
: ImGuiNavLayer_Main;
|
|
const bool preserve_layer_1_nav_id = (new_nav_window->DockNodeAsHost != NULL);
|
|
if (new_nav_layer == ImGuiNavLayer_Menu && !preserve_layer_1_nav_id)
|
|
g.NavWindow->NavLastIds[new_nav_layer] = 0;
|
|
NavRestoreLayer(new_nav_layer);
|
|
}
|
|
}
|
|
|
|
// 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::NavUpdateWindowingOverlay()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(g.NavWindowingTarget != NULL);
|
|
|
|
if (g.NavWindowingTimer < NAV_WINDOWING_LIST_APPEAR_DELAY)
|
|
return;
|
|
|
|
if (g.NavWindowingListWindow == NULL)
|
|
g.NavWindowingListWindow = FindWindowByName("###NavWindowingList");
|
|
const ImGuiViewport* viewport = /*g.NavWindow ? g.NavWindow->Viewport :*/ GetMainViewport();
|
|
SetNextWindowSizeConstraints(ImVec2(viewport->Size.x * 0.20f, viewport->Size.y * 0.20f), ImVec2(FLT_MAX, FLT_MAX));
|
|
SetNextWindowPos(viewport->GetCenter(), ImGuiCond_Always, ImVec2(0.5f, 0.5f));
|
|
PushStyleVar(ImGuiStyleVar_WindowPadding, g.Style.WindowPadding * 2.0f);
|
|
Begin("###NavWindowingList", NULL,
|
|
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoFocusOnAppearing | 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];
|
|
IM_ASSERT(window != NULL); // Fix static analyzers
|
|
if (!IsWindowNavFocusable(window))
|
|
continue;
|
|
const char* label = window->Name;
|
|
if (label == FindRenderedTextEnd(label))
|
|
label = GetFallbackWindowNameForWindowingList(window);
|
|
Selectable(label, g.NavWindowingTarget == window);
|
|
}
|
|
End();
|
|
PopStyleVar();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// [SECTION] DRAG AND DROP
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void ImGui::ClearDragDrop()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
g.DragDropActive = false;
|
|
g.DragDropPayload.Clear();
|
|
g.DragDropAcceptFlags = ImGuiDragDropFlags_None;
|
|
g.DragDropAcceptIdCurr = g.DragDropAcceptIdPrev = 0;
|
|
g.DragDropAcceptIdCurrRectSurface = FLT_MAX;
|
|
g.DragDropAcceptFrameCount = -1;
|
|
|
|
g.DragDropPayloadBufHeap.clear();
|
|
memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal));
|
|
}
|
|
|
|
// When this returns true you need to: a) call SetDragDropPayload() exactly once, b) you may render the payload
|
|
// visual/description, c) call EndDragDropSource() If the item has an identifier:
|
|
// - This assume/require the item to be activated (typically via ButtonBehavior).
|
|
// - Therefore if you want to use this with a mouse button other than left mouse button, it is up to the item itself to
|
|
// activate with another button.
|
|
// - We then pull and use the mouse button that was used to activate the item and use it to carry on the drag.
|
|
// If the item has no identifier:
|
|
// - Currently always assume left mouse button.
|
|
bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
|
|
// FIXME-DRAGDROP: While in the common-most "drag from non-zero active id" case we can tell the mouse button,
|
|
// in both SourceExtern and id==0 cases we may requires something else (explicit flags or some heuristic).
|
|
ImGuiMouseButton mouse_button = ImGuiMouseButton_Left;
|
|
|
|
bool source_drag_active = false;
|
|
ImGuiID source_id = 0;
|
|
ImGuiID source_parent_id = 0;
|
|
if (!(flags & ImGuiDragDropFlags_SourceExtern))
|
|
{
|
|
source_id = g.LastItemData.ID;
|
|
if (source_id != 0)
|
|
{
|
|
// Common path: items with ID
|
|
if (g.ActiveId != source_id)
|
|
return false;
|
|
if (g.ActiveIdMouseButton != -1)
|
|
mouse_button = g.ActiveIdMouseButton;
|
|
if (g.IO.MouseDown[mouse_button] == false)
|
|
return false;
|
|
g.ActiveIdAllowOverlap = false;
|
|
}
|
|
else
|
|
{
|
|
// Uncommon path: items without ID
|
|
if (g.IO.MouseDown[mouse_button] == false)
|
|
return false;
|
|
|
|
// 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;
|
|
}
|
|
|
|
// Early out
|
|
if ((g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect) == 0 &&
|
|
(g.ActiveId == 0 || g.ActiveIdWindow != window))
|
|
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. Rely on keeping other window->LastItemXXX fields
|
|
// intact.
|
|
source_id = g.LastItemData.ID = window->GetIDFromRectangle(g.LastItemData.Rect);
|
|
bool is_hovered = ItemHoverable(g.LastItemData.Rect, 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;
|
|
}
|
|
if (g.ActiveId != source_id)
|
|
return false;
|
|
source_parent_id = window->IDStack.back();
|
|
source_drag_active = IsMouseDragging(mouse_button);
|
|
|
|
// Disable navigation and key inputs while dragging + cancel existing request if any
|
|
SetActiveIdUsingNavAndKeys();
|
|
}
|
|
else
|
|
{
|
|
window = NULL;
|
|
source_id = ImHashStr("#SourceExtern");
|
|
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;
|
|
if (payload.SourceId == g.ActiveId)
|
|
g.ActiveIdNoClearOnFocusLoss = true;
|
|
}
|
|
g.DragDropSourceFrameCount = g.FrameCount;
|
|
g.DragDropWithinSource = 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.DragDropAcceptIdPrev && (g.DragDropAcceptFlags & ImGuiDragDropFlags_AcceptNoPreviewTooltip))
|
|
{
|
|
ImGuiWindow* tooltip_window = g.CurrentWindow;
|
|
tooltip_window->Hidden = tooltip_window->SkipItems = true;
|
|
tooltip_window->HiddenFramesCanSkipItems = 1;
|
|
}
|
|
}
|
|
|
|
if (!(flags & ImGuiDragDropFlags_SourceNoDisableHover) && !(flags & ImGuiDragDropFlags_SourceExtern))
|
|
g.LastItemData.StatusFlags &= ~ImGuiItemStatusFlags_HoveredRect;
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ImGui::EndDragDropSource()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(g.DragDropActive);
|
|
IM_ASSERT(g.DragDropWithinSource && "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.DragDropWithinSource = 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)
|
|
{
|
|
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;
|
|
ImGuiWindow* hovered_window = g.HoveredWindowUnderMovingWindow;
|
|
if (hovered_window == NULL || window->RootWindowDockTree != hovered_window->RootWindowDockTree)
|
|
return false;
|
|
IM_ASSERT(id != 0);
|
|
if (!IsMouseHoveringRect(bb.Min, bb.Max) || (id == g.DragDropPayload.SourceId))
|
|
return false;
|
|
if (window->SkipItems)
|
|
return false;
|
|
|
|
IM_ASSERT(g.DragDropWithinTarget == false);
|
|
g.DragDropTargetRect = bb;
|
|
g.DragDropTargetId = id;
|
|
g.DragDropWithinTarget = 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 (!(g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect))
|
|
return false;
|
|
ImGuiWindow* hovered_window = g.HoveredWindowUnderMovingWindow;
|
|
if (hovered_window == NULL || window->RootWindowDockTree != hovered_window->RootWindowDockTree)
|
|
return false;
|
|
|
|
const ImRect& display_rect = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HasDisplayRect)
|
|
? g.LastItemData.DisplayRect
|
|
: g.LastItemData.Rect;
|
|
ImGuiID id = g.LastItemData.ID;
|
|
if (id == 0)
|
|
id = window->GetIDFromRectangle(display_rect);
|
|
if (g.DragDropPayload.SourceId == id)
|
|
return false;
|
|
|
|
IM_ASSERT(g.DragDropWithinTarget == false);
|
|
g.DragDropTargetRect = display_rect;
|
|
g.DragDropTargetId = id;
|
|
g.DragDropWithinTarget = 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
|
|
// FIXME-DRAGDROP: Settle on a proper default visuals for drop target.
|
|
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)
|
|
window->DrawList->AddRect(r.Min - ImVec2(3.5f, 3.5f), r.Max + ImVec2(3.5f, 3.5f),
|
|
GetColorU32(ImGuiCol_DragDropTarget), 0.0f, 0, 2.0f);
|
|
|
|
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;
|
|
}
|
|
|
|
const ImGuiPayload* ImGui::GetDragDropPayload()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
return g.DragDropActive ? &g.DragDropPayload : NULL;
|
|
}
|
|
|
|
// 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.DragDropWithinTarget);
|
|
g.DragDropWithinTarget = false;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// [SECTION] LOGGING/CAPTURING
|
|
//-----------------------------------------------------------------------------
|
|
// All text output from the interface can be captured into tty/file/clipboard.
|
|
// By default, tree nodes are automatically opened during logging.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Pass text data straight to log (without being displayed)
|
|
static inline void LogTextV(ImGuiContext& g, const char* fmt, va_list args)
|
|
{
|
|
if (g.LogFile)
|
|
{
|
|
g.LogBuffer.Buf.resize(0);
|
|
g.LogBuffer.appendfv(fmt, args);
|
|
ImFileWrite(g.LogBuffer.c_str(), sizeof(char), (ImU64)g.LogBuffer.size(), g.LogFile);
|
|
}
|
|
else
|
|
{
|
|
g.LogBuffer.appendfv(fmt, args);
|
|
}
|
|
}
|
|
|
|
void ImGui::LogText(const char* fmt, ...)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (!g.LogEnabled)
|
|
return;
|
|
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
LogTextV(g, fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
void ImGui::LogTextV(const char* fmt, va_list args)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (!g.LogEnabled)
|
|
return;
|
|
|
|
LogTextV(g, fmt, args);
|
|
}
|
|
|
|
// Internal version that takes a position to decide on newline placement and pad items according to their depth.
|
|
// We split text into individual lines to add current tree level padding
|
|
// FIXME: This code is a little complicated perhaps, considering simplifying the whole system.
|
|
void ImGui::LogRenderedText(const ImVec2* ref_pos, const char* text, const char* text_end)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
|
|
const char* prefix = g.LogNextPrefix;
|
|
const char* suffix = g.LogNextSuffix;
|
|
g.LogNextPrefix = g.LogNextSuffix = NULL;
|
|
|
|
if (!text_end)
|
|
text_end = FindRenderedTextEnd(text, text_end);
|
|
|
|
const bool log_new_line = ref_pos && (ref_pos->y > g.LogLinePosY + g.Style.FramePadding.y + 1);
|
|
if (ref_pos)
|
|
g.LogLinePosY = ref_pos->y;
|
|
if (log_new_line)
|
|
{
|
|
LogText(IM_NEWLINE);
|
|
g.LogLineFirstItem = true;
|
|
}
|
|
|
|
if (prefix)
|
|
LogRenderedText(ref_pos, prefix,
|
|
prefix + strlen(prefix)); // Calculate end ourself to ensure "##" are included here.
|
|
|
|
// Re-adjust padding if we have popped out of our starting depth
|
|
if (g.LogDepthRef > window->DC.TreeDepth)
|
|
g.LogDepthRef = window->DC.TreeDepth;
|
|
const int tree_depth = (window->DC.TreeDepth - g.LogDepthRef);
|
|
|
|
const char* text_remaining = text;
|
|
for (;;)
|
|
{
|
|
// Split the string. Each new line (after a '\n') is followed by indentation corresponding to the current depth
|
|
// of our log entry. We don't add a trailing \n yet to allow a subsequent item on the same line to be captured.
|
|
const char* line_start = text_remaining;
|
|
const char* line_end = ImStreolRange(line_start, text_end);
|
|
const bool is_last_line = (line_end == text_end);
|
|
if (line_start != line_end || !is_last_line)
|
|
{
|
|
const int line_length = (int)(line_end - line_start);
|
|
const int indentation = g.LogLineFirstItem ? tree_depth * 4 : 1;
|
|
LogText("%*s%.*s", indentation, "", line_length, line_start);
|
|
g.LogLineFirstItem = false;
|
|
if (*line_end == '\n')
|
|
{
|
|
LogText(IM_NEWLINE);
|
|
g.LogLineFirstItem = true;
|
|
}
|
|
}
|
|
if (is_last_line)
|
|
break;
|
|
text_remaining = line_end + 1;
|
|
}
|
|
|
|
if (suffix)
|
|
LogRenderedText(ref_pos, suffix, suffix + strlen(suffix));
|
|
}
|
|
|
|
// Start logging/capturing text output
|
|
void ImGui::LogBegin(ImGuiLogType type, int auto_open_depth)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
IM_ASSERT(g.LogEnabled == false);
|
|
IM_ASSERT(g.LogFile == NULL);
|
|
IM_ASSERT(g.LogBuffer.empty());
|
|
g.LogEnabled = true;
|
|
g.LogType = type;
|
|
g.LogNextPrefix = g.LogNextSuffix = NULL;
|
|
g.LogDepthRef = window->DC.TreeDepth;
|
|
g.LogDepthToExpand = ((auto_open_depth >= 0) ? auto_open_depth : g.LogDepthToExpandDefault);
|
|
g.LogLinePosY = FLT_MAX;
|
|
g.LogLineFirstItem = true;
|
|
}
|
|
|
|
// Important: doesn't copy underlying data, use carefully (prefix/suffix must be in scope at the time of the next
|
|
// LogRenderedText)
|
|
void ImGui::LogSetNextTextDecoration(const char* prefix, const char* suffix)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
g.LogNextPrefix = prefix;
|
|
g.LogNextSuffix = suffix;
|
|
}
|
|
|
|
void ImGui::LogToTTY(int auto_open_depth)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (g.LogEnabled)
|
|
return;
|
|
IM_UNUSED(auto_open_depth);
|
|
# ifndef IMGUI_DISABLE_TTY_FUNCTIONS
|
|
LogBegin(ImGuiLogType_TTY, auto_open_depth);
|
|
g.LogFile = stdout;
|
|
# endif
|
|
}
|
|
|
|
// Start logging/capturing text output to given file
|
|
void ImGui::LogToFile(int auto_open_depth, const char* filename)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (g.LogEnabled)
|
|
return;
|
|
|
|
// FIXME: We could probably open the file in text mode "at", however note that clipboard/buffer logging will still
|
|
// be subject to outputting OS-incompatible carriage return if within strings the user doesn't use IM_NEWLINE.
|
|
// By opening the file in binary mode "ab" we have consistent output everywhere.
|
|
if (!filename)
|
|
filename = g.IO.LogFilename;
|
|
if (!filename || !filename[0])
|
|
return;
|
|
ImFileHandle f = ImFileOpen(filename, "ab");
|
|
if (!f)
|
|
{
|
|
IM_ASSERT(0);
|
|
return;
|
|
}
|
|
|
|
LogBegin(ImGuiLogType_File, auto_open_depth);
|
|
g.LogFile = f;
|
|
}
|
|
|
|
// Start logging/capturing text output to clipboard
|
|
void ImGui::LogToClipboard(int auto_open_depth)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (g.LogEnabled)
|
|
return;
|
|
LogBegin(ImGuiLogType_Clipboard, auto_open_depth);
|
|
}
|
|
|
|
void ImGui::LogToBuffer(int auto_open_depth)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (g.LogEnabled)
|
|
return;
|
|
LogBegin(ImGuiLogType_Buffer, auto_open_depth);
|
|
}
|
|
|
|
void ImGui::LogFinish()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (!g.LogEnabled)
|
|
return;
|
|
|
|
LogText(IM_NEWLINE);
|
|
switch (g.LogType)
|
|
{
|
|
case ImGuiLogType_TTY:
|
|
# ifndef IMGUI_DISABLE_TTY_FUNCTIONS
|
|
fflush(g.LogFile);
|
|
# endif
|
|
break;
|
|
case ImGuiLogType_File:
|
|
ImFileClose(g.LogFile);
|
|
break;
|
|
case ImGuiLogType_Buffer:
|
|
break;
|
|
case ImGuiLogType_Clipboard:
|
|
if (!g.LogBuffer.empty())
|
|
SetClipboardText(g.LogBuffer.begin());
|
|
break;
|
|
case ImGuiLogType_None:
|
|
IM_ASSERT(0);
|
|
break;
|
|
}
|
|
|
|
g.LogEnabled = false;
|
|
g.LogType = ImGuiLogType_None;
|
|
g.LogFile = NULL;
|
|
g.LogBuffer.clear();
|
|
}
|
|
|
|
// Helper to display logging buttons
|
|
// FIXME-OBSOLETE: We should probably obsolete this and let the user have their own helper (this is one of the oldest
|
|
// function alive!)
|
|
void ImGui::LogButtons()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
PushID("LogButtons");
|
|
# ifndef IMGUI_DISABLE_TTY_FUNCTIONS
|
|
const bool log_to_tty = Button("Log To TTY");
|
|
SameLine();
|
|
# else
|
|
const bool log_to_tty = false;
|
|
# endif
|
|
const bool log_to_file = Button("Log To File");
|
|
SameLine();
|
|
const bool log_to_clipboard = Button("Log To Clipboard");
|
|
SameLine();
|
|
PushAllowKeyboardFocus(false);
|
|
SetNextItemWidth(80.0f);
|
|
SliderInt("Default Depth", &g.LogDepthToExpandDefault, 0, 9, NULL);
|
|
PopAllowKeyboardFocus();
|
|
PopID();
|
|
|
|
// Start logging at the end of the function so that the buttons don't appear in the log
|
|
if (log_to_tty)
|
|
LogToTTY();
|
|
if (log_to_file)
|
|
LogToFile();
|
|
if (log_to_clipboard)
|
|
LogToClipboard();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// [SECTION] SETTINGS
|
|
//-----------------------------------------------------------------------------
|
|
// - UpdateSettings() [Internal]
|
|
// - MarkIniSettingsDirty() [Internal]
|
|
// - CreateNewWindowSettings() [Internal]
|
|
// - FindWindowSettings() [Internal]
|
|
// - FindOrCreateWindowSettings() [Internal]
|
|
// - FindSettingsHandler() [Internal]
|
|
// - ClearIniSettings() [Internal]
|
|
// - LoadIniSettingsFromDisk()
|
|
// - LoadIniSettingsFromMemory()
|
|
// - SaveIniSettingsToDisk()
|
|
// - SaveIniSettingsToMemory()
|
|
// - WindowSettingsHandler_***() [Internal]
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Called by NewFrame()
|
|
void ImGui::UpdateSettings()
|
|
{
|
|
// Load settings on first frame (if not explicitly loaded manually before)
|
|
ImGuiContext& g = *GImGui;
|
|
if (!g.SettingsLoaded)
|
|
{
|
|
IM_ASSERT(g.SettingsWindows.empty());
|
|
if (g.IO.IniFilename)
|
|
LoadIniSettingsFromDisk(g.IO.IniFilename);
|
|
g.SettingsLoaded = true;
|
|
}
|
|
|
|
// Save settings (with a delay after the last modification, so we don't spam disk too much)
|
|
if (g.SettingsDirtyTimer > 0.0f)
|
|
{
|
|
g.SettingsDirtyTimer -= g.IO.DeltaTime;
|
|
if (g.SettingsDirtyTimer <= 0.0f)
|
|
{
|
|
if (g.IO.IniFilename != NULL)
|
|
SaveIniSettingsToDisk(g.IO.IniFilename);
|
|
else
|
|
g.IO.WantSaveIniSettings = true; // Let user know they can call SaveIniSettingsToMemory(). user will
|
|
// need to clear io.WantSaveIniSettings themselves.
|
|
g.SettingsDirtyTimer = 0.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ImGui::MarkIniSettingsDirty()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (g.SettingsDirtyTimer <= 0.0f)
|
|
g.SettingsDirtyTimer = g.IO.IniSavingRate;
|
|
}
|
|
|
|
void ImGui::MarkIniSettingsDirty(ImGuiWindow* window)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (!(window->Flags & ImGuiWindowFlags_NoSavedSettings))
|
|
if (g.SettingsDirtyTimer <= 0.0f)
|
|
g.SettingsDirtyTimer = g.IO.IniSavingRate;
|
|
}
|
|
|
|
ImGuiWindowSettings* ImGui::CreateNewWindowSettings(const char* name)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
# if !IMGUI_DEBUG_INI_SETTINGS
|
|
// Skip to the "###" marker if any. We don't skip past to match the behavior of GetID()
|
|
// Preserve the full string when IMGUI_DEBUG_INI_SETTINGS is set to make .ini inspection easier.
|
|
if (const char* p = strstr(name, "###"))
|
|
name = p;
|
|
# endif
|
|
const size_t name_len = strlen(name);
|
|
|
|
// Allocate chunk
|
|
const size_t chunk_size = sizeof(ImGuiWindowSettings) + name_len + 1;
|
|
ImGuiWindowSettings* settings = g.SettingsWindows.alloc_chunk(chunk_size);
|
|
IM_PLACEMENT_NEW(settings) ImGuiWindowSettings();
|
|
settings->ID = ImHashStr(name, name_len);
|
|
memcpy(settings->GetName(), name, name_len + 1); // Store with zero terminator
|
|
|
|
return settings;
|
|
}
|
|
|
|
ImGuiWindowSettings* ImGui::FindWindowSettings(ImGuiID id)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL;
|
|
settings = g.SettingsWindows.next_chunk(settings))
|
|
if (settings->ID == id)
|
|
return settings;
|
|
return NULL;
|
|
}
|
|
|
|
ImGuiWindowSettings* ImGui::FindOrCreateWindowSettings(const char* name)
|
|
{
|
|
if (ImGuiWindowSettings* settings = FindWindowSettings(ImHashStr(name)))
|
|
return settings;
|
|
return CreateNewWindowSettings(name);
|
|
}
|
|
|
|
ImGuiSettingsHandler* ImGui::FindSettingsHandler(const char* type_name)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
const ImGuiID type_hash = ImHashStr(type_name);
|
|
for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++)
|
|
if (g.SettingsHandlers[handler_n].TypeHash == type_hash)
|
|
return &g.SettingsHandlers[handler_n];
|
|
return NULL;
|
|
}
|
|
|
|
void ImGui::ClearIniSettings()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
g.SettingsIniData.clear();
|
|
for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++)
|
|
if (g.SettingsHandlers[handler_n].ClearAllFn)
|
|
g.SettingsHandlers[handler_n].ClearAllFn(&g, &g.SettingsHandlers[handler_n]);
|
|
}
|
|
|
|
void ImGui::LoadIniSettingsFromDisk(const char* ini_filename)
|
|
{
|
|
size_t file_data_size = 0;
|
|
char* file_data = (char*)ImFileLoadToMemory(ini_filename, "rb", &file_data_size);
|
|
if (!file_data)
|
|
return;
|
|
LoadIniSettingsFromMemory(file_data, (size_t)file_data_size);
|
|
IM_FREE(file_data);
|
|
}
|
|
|
|
// Zero-tolerance, no error reporting, cheap .ini parsing
|
|
void ImGui::LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(g.Initialized);
|
|
// IM_ASSERT(!g.WithinFrameScope && "Cannot be called between NewFrame() and EndFrame()");
|
|
// IM_ASSERT(g.SettingsLoaded == false && g.FrameCount == 0);
|
|
|
|
// For user convenience, we allow passing a non zero-terminated string (hence the ini_size parameter).
|
|
// For our convenience and to make the code simpler, we'll also write zero-terminators within the buffer. So let's
|
|
// create a writable copy..
|
|
if (ini_size == 0)
|
|
ini_size = strlen(ini_data);
|
|
g.SettingsIniData.Buf.resize((int)ini_size + 1);
|
|
char* const buf = g.SettingsIniData.Buf.Data;
|
|
char* const buf_end = buf + ini_size;
|
|
memcpy(buf, ini_data, ini_size);
|
|
buf_end[0] = 0;
|
|
|
|
// Call pre-read handlers
|
|
// Some types will clear their data (e.g. dock information) some types will allow merge/override (window)
|
|
for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++)
|
|
if (g.SettingsHandlers[handler_n].ReadInitFn)
|
|
g.SettingsHandlers[handler_n].ReadInitFn(&g, &g.SettingsHandlers[handler_n]);
|
|
|
|
void* entry_data = NULL;
|
|
ImGuiSettingsHandler* entry_handler = NULL;
|
|
|
|
char* line_end = NULL;
|
|
for (char* line = buf; line < buf_end; line = line_end + 1)
|
|
{
|
|
// Skip new lines markers, then find end of the line
|
|
while (*line == '\n' || *line == '\r')
|
|
line++;
|
|
line_end = line;
|
|
while (line_end < buf_end && *line_end != '\n' && *line_end != '\r')
|
|
line_end++;
|
|
line_end[0] = 0;
|
|
if (line[0] == ';')
|
|
continue;
|
|
if (line[0] == '[' && line_end > line && line_end[-1] == ']')
|
|
{
|
|
// Parse "[Type][Name]". Note that 'Name' can itself contains [] characters, which is acceptable with the
|
|
// current format and parsing code.
|
|
line_end[-1] = 0;
|
|
const char* name_end = line_end - 1;
|
|
const char* type_start = line + 1;
|
|
char* type_end = (char*)(void*)ImStrchrRange(type_start, name_end, ']');
|
|
const char* name_start = type_end ? ImStrchrRange(type_end + 1, name_end, '[') : NULL;
|
|
if (!type_end || !name_start)
|
|
continue;
|
|
*type_end = 0; // Overwrite first ']'
|
|
name_start++; // Skip second '['
|
|
entry_handler = FindSettingsHandler(type_start);
|
|
entry_data = entry_handler ? entry_handler->ReadOpenFn(&g, entry_handler, name_start) : NULL;
|
|
}
|
|
else if (entry_handler != NULL && entry_data != NULL)
|
|
{
|
|
// Let type handler parse the line
|
|
entry_handler->ReadLineFn(&g, entry_handler, entry_data, line);
|
|
}
|
|
}
|
|
g.SettingsLoaded = true;
|
|
|
|
// [DEBUG] Restore untouched copy so it can be browsed in Metrics (not strictly necessary)
|
|
memcpy(buf, ini_data, ini_size);
|
|
|
|
// Call post-read handlers
|
|
for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++)
|
|
if (g.SettingsHandlers[handler_n].ApplyAllFn)
|
|
g.SettingsHandlers[handler_n].ApplyAllFn(&g, &g.SettingsHandlers[handler_n]);
|
|
}
|
|
|
|
void ImGui::SaveIniSettingsToDisk(const char* ini_filename)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
g.SettingsDirtyTimer = 0.0f;
|
|
if (!ini_filename)
|
|
return;
|
|
|
|
size_t ini_data_size = 0;
|
|
const char* ini_data = SaveIniSettingsToMemory(&ini_data_size);
|
|
ImFileHandle f = ImFileOpen(ini_filename, "wt");
|
|
if (!f)
|
|
return;
|
|
ImFileWrite(ini_data, sizeof(char), ini_data_size, f);
|
|
ImFileClose(f);
|
|
}
|
|
|
|
// Call registered handlers (e.g. SettingsHandlerWindow_WriteAll() + custom handlers) to write their stuff into a text
|
|
// buffer
|
|
const char* ImGui::SaveIniSettingsToMemory(size_t* out_size)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
g.SettingsDirtyTimer = 0.0f;
|
|
g.SettingsIniData.Buf.resize(0);
|
|
g.SettingsIniData.Buf.push_back(0);
|
|
for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++)
|
|
{
|
|
ImGuiSettingsHandler* handler = &g.SettingsHandlers[handler_n];
|
|
handler->WriteAllFn(&g, handler, &g.SettingsIniData);
|
|
}
|
|
if (out_size)
|
|
*out_size = (size_t)g.SettingsIniData.size();
|
|
return g.SettingsIniData.c_str();
|
|
}
|
|
|
|
static void WindowSettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettingsHandler*)
|
|
{
|
|
ImGuiContext& g = *ctx;
|
|
for (int i = 0; i != g.Windows.Size; i++)
|
|
g.Windows[i]->SettingsOffset = -1;
|
|
g.SettingsWindows.clear();
|
|
}
|
|
|
|
static void* WindowSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name)
|
|
{
|
|
ImGuiWindowSettings* settings = ImGui::FindOrCreateWindowSettings(name);
|
|
ImGuiID id = settings->ID;
|
|
*settings = ImGuiWindowSettings(); // Clear existing if recycling previous entry
|
|
settings->ID = id;
|
|
settings->WantApply = true;
|
|
return (void*)settings;
|
|
}
|
|
|
|
static void WindowSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line)
|
|
{
|
|
ImGuiWindowSettings* settings = (ImGuiWindowSettings*)entry;
|
|
int x, y;
|
|
int i;
|
|
ImU32 u1;
|
|
if (sscanf(line, "Pos=%i,%i", &x, &y) == 2)
|
|
{
|
|
settings->Pos = ImVec2ih((short)x, (short)y);
|
|
}
|
|
else if (sscanf(line, "Size=%i,%i", &x, &y) == 2)
|
|
{
|
|
settings->Size = ImVec2ih((short)x, (short)y);
|
|
}
|
|
else if (sscanf(line, "ViewportId=0x%08X", &u1) == 1)
|
|
{
|
|
settings->ViewportId = u1;
|
|
}
|
|
else if (sscanf(line, "ViewportPos=%i,%i", &x, &y) == 2)
|
|
{
|
|
settings->ViewportPos = ImVec2ih((short)x, (short)y);
|
|
}
|
|
else if (sscanf(line, "Collapsed=%d", &i) == 1)
|
|
{
|
|
settings->Collapsed = (i != 0);
|
|
}
|
|
else if (sscanf(line, "DockId=0x%X,%d", &u1, &i) == 2)
|
|
{
|
|
settings->DockId = u1;
|
|
settings->DockOrder = (short)i;
|
|
}
|
|
else if (sscanf(line, "DockId=0x%X", &u1) == 1)
|
|
{
|
|
settings->DockId = u1;
|
|
settings->DockOrder = -1;
|
|
}
|
|
else if (sscanf(line, "ClassId=0x%X", &u1) == 1)
|
|
{
|
|
settings->ClassId = u1;
|
|
}
|
|
}
|
|
|
|
// Apply to existing windows (if any)
|
|
static void WindowSettingsHandler_ApplyAll(ImGuiContext* ctx, ImGuiSettingsHandler*)
|
|
{
|
|
ImGuiContext& g = *ctx;
|
|
for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL;
|
|
settings = g.SettingsWindows.next_chunk(settings))
|
|
if (settings->WantApply)
|
|
{
|
|
if (ImGuiWindow* window = ImGui::FindWindowByID(settings->ID))
|
|
ApplyWindowSettings(window, settings);
|
|
settings->WantApply = false;
|
|
}
|
|
}
|
|
|
|
static void WindowSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf)
|
|
{
|
|
// 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 = *ctx;
|
|
for (int i = 0; i != g.Windows.Size; i++)
|
|
{
|
|
ImGuiWindow* window = g.Windows[i];
|
|
if (window->Flags & ImGuiWindowFlags_NoSavedSettings)
|
|
continue;
|
|
|
|
ImGuiWindowSettings* settings = (window->SettingsOffset != -1)
|
|
? g.SettingsWindows.ptr_from_offset(window->SettingsOffset)
|
|
: ImGui::FindWindowSettings(window->ID);
|
|
if (!settings)
|
|
{
|
|
settings = ImGui::CreateNewWindowSettings(window->Name);
|
|
window->SettingsOffset = g.SettingsWindows.offset_from_ptr(settings);
|
|
}
|
|
IM_ASSERT(settings->ID == window->ID);
|
|
settings->Pos = ImVec2ih(window->Pos - window->ViewportPos);
|
|
settings->Size = ImVec2ih(window->SizeFull);
|
|
settings->ViewportId = window->ViewportId;
|
|
settings->ViewportPos = ImVec2ih(window->ViewportPos);
|
|
IM_ASSERT(window->DockNode == NULL || window->DockNode->ID == window->DockId);
|
|
settings->DockId = window->DockId;
|
|
settings->ClassId = window->WindowClass.ClassId;
|
|
settings->DockOrder = window->DockOrder;
|
|
settings->Collapsed = window->Collapsed;
|
|
}
|
|
|
|
// Write to text buffer
|
|
buf->reserve(buf->size() + g.SettingsWindows.size() * 6); // ballpark reserve
|
|
for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL;
|
|
settings = g.SettingsWindows.next_chunk(settings))
|
|
{
|
|
const char* settings_name = settings->GetName();
|
|
buf->appendf("[%s][%s]\n", handler->TypeName, settings_name);
|
|
if (settings->ViewportId != 0 && settings->ViewportId != ImGui::IMGUI_VIEWPORT_DEFAULT_ID)
|
|
{
|
|
buf->appendf("ViewportPos=%d,%d\n", settings->ViewportPos.x, settings->ViewportPos.y);
|
|
buf->appendf("ViewportId=0x%08X\n", settings->ViewportId);
|
|
}
|
|
if (settings->Pos.x != 0 || settings->Pos.y != 0 || settings->ViewportId == ImGui::IMGUI_VIEWPORT_DEFAULT_ID)
|
|
buf->appendf("Pos=%d,%d\n", settings->Pos.x, settings->Pos.y);
|
|
if (settings->Size.x != 0 || settings->Size.y != 0)
|
|
buf->appendf("Size=%d,%d\n", settings->Size.x, settings->Size.y);
|
|
buf->appendf("Collapsed=%d\n", settings->Collapsed);
|
|
if (settings->DockId != 0)
|
|
{
|
|
if (settings->DockOrder == -1)
|
|
buf->appendf("DockId=0x%08X\n", settings->DockId);
|
|
else
|
|
buf->appendf("DockId=0x%08X,%d\n", settings->DockId, settings->DockOrder);
|
|
if (settings->ClassId != 0)
|
|
buf->appendf("ClassId=0x%08X\n", settings->ClassId);
|
|
}
|
|
buf->append("\n");
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// [SECTION] VIEWPORTS, PLATFORM WINDOWS
|
|
//-----------------------------------------------------------------------------
|
|
// - GetMainViewport()
|
|
// - FindViewportByID()
|
|
// - FindViewportByPlatformHandle()
|
|
// - SetCurrentViewport() [Internal]
|
|
// - SetWindowViewport() [Internal]
|
|
// - GetWindowAlwaysWantOwnViewport() [Internal]
|
|
// - UpdateTryMergeWindowIntoHostViewport() [Internal]
|
|
// - UpdateTryMergeWindowIntoHostViewports() [Internal]
|
|
// - TranslateWindowsInViewport() [Internal]
|
|
// - ScaleWindowsInViewport() [Internal]
|
|
// - FindHoveredViewportFromPlatformWindowStack() [Internal]
|
|
// - UpdateViewportsNewFrame() [Internal]
|
|
// - UpdateViewportsEndFrame() [Internal]
|
|
// - AddUpdateViewport() [Internal]
|
|
// - WindowSelectViewport() [Internal]
|
|
// - WindowSyncOwnedViewport() [Internal]
|
|
// - UpdatePlatformWindows()
|
|
// - RenderPlatformWindowsDefault()
|
|
// - FindPlatformMonitorForPos() [Internal]
|
|
// - FindPlatformMonitorForRect() [Internal]
|
|
// - UpdateViewportPlatformMonitor() [Internal]
|
|
// - DestroyPlatformWindow() [Internal]
|
|
// - DestroyPlatformWindows()
|
|
//-----------------------------------------------------------------------------
|
|
|
|
ImGuiViewport* ImGui::GetMainViewport()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
return g.Viewports[0];
|
|
}
|
|
|
|
// FIXME: This leaks access to viewports not listed in PlatformIO.Viewports[]. Problematic? (#4236)
|
|
ImGuiViewport* ImGui::FindViewportByID(ImGuiID id)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
for (int n = 0; n < g.Viewports.Size; n++)
|
|
if (g.Viewports[n]->ID == id)
|
|
return g.Viewports[n];
|
|
return NULL;
|
|
}
|
|
|
|
ImGuiViewport* ImGui::FindViewportByPlatformHandle(void* platform_handle)
|
|
{
|
|
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;
|
|
}
|
|
|
|
void ImGui::SetCurrentViewport(ImGuiWindow* current_window, ImGuiViewportP* viewport)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
(void)current_window;
|
|
|
|
if (viewport)
|
|
viewport->LastFrameActive = g.FrameCount;
|
|
if (g.CurrentViewport == viewport)
|
|
return;
|
|
g.CurrentDpiScale = viewport ? viewport->DpiScale : 1.0f;
|
|
g.CurrentViewport = viewport;
|
|
// IMGUI_DEBUG_LOG_VIEWPORT("SetCurrentViewport changed '%s' 0x%08X\n", current_window ? current_window->Name :
|
|
// NULL, viewport ? viewport->ID : 0);
|
|
|
|
// Notify platform layer of viewport changes
|
|
// FIXME-DPI: This is only currently used for experimenting with handling of multiple DPI
|
|
if (g.CurrentViewport && g.PlatformIO.Platform_OnChangedViewport)
|
|
g.PlatformIO.Platform_OnChangedViewport(g.CurrentViewport);
|
|
}
|
|
|
|
static void SetWindowViewport(ImGuiWindow* window, ImGuiViewportP* viewport)
|
|
{
|
|
window->Viewport = viewport;
|
|
window->ViewportId = viewport->ID;
|
|
window->ViewportOwned = (viewport->Window == window);
|
|
}
|
|
|
|
static bool ImGui::GetWindowAlwaysWantOwnViewport(ImGuiWindow* window)
|
|
{
|
|
// Tooltips and menus are not automatically forced into their own viewport when the NoMerge flag is set, however the
|
|
// multiplication of viewports makes them more likely to protrude and create their own.
|
|
ImGuiContext& g = *GImGui;
|
|
if (g.IO.ConfigViewportsNoAutoMerge ||
|
|
(window->WindowClass.ViewportFlagsOverrideSet & ImGuiViewportFlags_NoAutoMerge))
|
|
if (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable)
|
|
if (!window->DockIsActive)
|
|
if ((window->Flags &
|
|
(ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_Tooltip)) == 0)
|
|
if ((window->Flags & ImGuiWindowFlags_Popup) == 0 || (window->Flags & ImGuiWindowFlags_Modal) != 0)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static bool ImGui::UpdateTryMergeWindowIntoHostViewport(ImGuiWindow* window, ImGuiViewportP* viewport)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (window->Viewport == viewport)
|
|
return false;
|
|
if ((viewport->Flags & ImGuiViewportFlags_CanHostOtherWindows) == 0)
|
|
return false;
|
|
if ((viewport->Flags & ImGuiViewportFlags_Minimized) != 0)
|
|
return false;
|
|
if (!viewport->GetMainRect().Contains(window->Rect()))
|
|
return false;
|
|
if (GetWindowAlwaysWantOwnViewport(window))
|
|
return false;
|
|
|
|
for (int n = 0; n < g.Windows.Size; n++)
|
|
{
|
|
ImGuiWindow* window_behind = g.Windows[n];
|
|
if (window_behind == window)
|
|
break;
|
|
if (window_behind->WasActive && window_behind->ViewportOwned &&
|
|
!(window_behind->Flags & ImGuiWindowFlags_ChildWindow))
|
|
if (window_behind->Viewport->GetMainRect().Overlaps(window->Rect()))
|
|
return false;
|
|
}
|
|
|
|
// Move to the existing viewport, Move child/hosted windows as well (FIXME-OPT: iterate child)
|
|
ImGuiViewportP* old_viewport = window->Viewport;
|
|
if (window->ViewportOwned)
|
|
for (int n = 0; n < g.Windows.Size; n++)
|
|
if (g.Windows[n]->Viewport == old_viewport)
|
|
SetWindowViewport(g.Windows[n], viewport);
|
|
SetWindowViewport(window, viewport);
|
|
BringWindowToDisplayFront(window);
|
|
|
|
return true;
|
|
}
|
|
|
|
// FIXME: handle 0 to N host viewports
|
|
static bool ImGui::UpdateTryMergeWindowIntoHostViewports(ImGuiWindow* window)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
return UpdateTryMergeWindowIntoHostViewport(window, g.Viewports[0]);
|
|
}
|
|
|
|
// Translate Dear ImGui windows when a Host Viewport has been moved
|
|
// (This additionally keeps windows at the same place when ImGuiConfigFlags_ViewportsEnable is toggled!)
|
|
void ImGui::TranslateWindowsInViewport(ImGuiViewportP* viewport, const ImVec2& old_pos, const ImVec2& new_pos)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(viewport->Window == NULL && (viewport->Flags & ImGuiViewportFlags_CanHostOtherWindows));
|
|
|
|
// 1) We test if ImGuiConfigFlags_ViewportsEnable was just toggled, which allows us to conveniently
|
|
// translate imgui windows from OS-window-local to absolute coordinates or vice-versa.
|
|
// 2) If it's not going to fit into the new size, keep it at same absolute position.
|
|
// One problem with this is that most Win32 applications doesn't update their render while dragging,
|
|
// and so the window will appear to teleport when releasing the mouse.
|
|
const bool translate_all_windows = (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable) !=
|
|
(g.ConfigFlagsLastFrame & ImGuiConfigFlags_ViewportsEnable);
|
|
ImRect test_still_fit_rect(old_pos, old_pos + viewport->Size);
|
|
ImVec2 delta_pos = new_pos - old_pos;
|
|
for (int window_n = 0; window_n < g.Windows.Size; window_n++) // FIXME-OPT
|
|
if (translate_all_windows ||
|
|
(g.Windows[window_n]->Viewport == viewport && test_still_fit_rect.Contains(g.Windows[window_n]->Rect())))
|
|
TranslateWindow(g.Windows[window_n], delta_pos);
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
ScaleWindow(viewport->Window, scale);
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i != g.Windows.Size; i++)
|
|
if (g.Windows[i]->Viewport == viewport)
|
|
ScaleWindow(g.Windows[i], scale);
|
|
}
|
|
}
|
|
|
|
// If the backend doesn't set MouseLastHoveredViewport or doesn't honor ImGuiViewportFlags_NoInputs, we do a search
|
|
// ourselves. A) It won't take account of the possibility that non-imgui windows may be in-between our dragged window
|
|
// and our target window. B) It requires Platform_GetWindowFocus to be implemented by backend.
|
|
static ImGuiViewportP* FindHoveredViewportFromPlatformWindowStack(const ImVec2 mouse_platform_pos)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiViewportP* best_candidate = NULL;
|
|
for (int n = 0; n < g.Viewports.Size; n++)
|
|
{
|
|
ImGuiViewportP* viewport = g.Viewports[n];
|
|
if (!(viewport->Flags & (ImGuiViewportFlags_NoInputs | ImGuiViewportFlags_Minimized)) &&
|
|
viewport->GetMainRect().Contains(mouse_platform_pos))
|
|
if (best_candidate == NULL || best_candidate->LastFrontMostStampCount < viewport->LastFrontMostStampCount)
|
|
best_candidate = viewport;
|
|
}
|
|
return best_candidate;
|
|
}
|
|
|
|
// Update viewports and monitor infos
|
|
// Note that this is running even if 'ImGuiConfigFlags_ViewportsEnable' is not set, in order to clear unused viewports
|
|
// (if any) and update monitor info.
|
|
static void ImGui::UpdateViewportsNewFrame()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(g.PlatformIO.Viewports.Size <= g.Viewports.Size);
|
|
|
|
// Update Minimized status (we need it first in order to decide if we'll apply Pos/Size of the main viewport)
|
|
const bool viewports_enabled = (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable) != 0;
|
|
if (viewports_enabled)
|
|
{
|
|
for (int n = 0; n < g.Viewports.Size; n++)
|
|
{
|
|
ImGuiViewportP* viewport = g.Viewports[n];
|
|
const bool platform_funcs_available = viewport->PlatformWindowCreated;
|
|
if (g.PlatformIO.Platform_GetWindowMinimized && platform_funcs_available)
|
|
{
|
|
bool minimized = g.PlatformIO.Platform_GetWindowMinimized(viewport);
|
|
if (minimized)
|
|
viewport->Flags |= ImGuiViewportFlags_Minimized;
|
|
else
|
|
viewport->Flags &= ~ImGuiViewportFlags_Minimized;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create/update main viewport with current platform position.
|
|
// FIXME-VIEWPORT: Size is driven by backend/user code for backward-compatibility but we should aim to make this
|
|
// more consistent.
|
|
ImGuiViewportP* main_viewport = g.Viewports[0];
|
|
IM_ASSERT(main_viewport->ID == IMGUI_VIEWPORT_DEFAULT_ID);
|
|
IM_ASSERT(main_viewport->Window == NULL);
|
|
ImVec2 main_viewport_pos =
|
|
viewports_enabled ? g.PlatformIO.Platform_GetWindowPos(main_viewport) : ImVec2(0.0f, 0.0f);
|
|
ImVec2 main_viewport_size = g.IO.DisplaySize;
|
|
if (viewports_enabled && (main_viewport->Flags & ImGuiViewportFlags_Minimized))
|
|
{
|
|
main_viewport_pos = main_viewport->Pos; // Preserve last pos/size when minimized (FIXME: We don't do the same
|
|
// for Size outside of the viewport path)
|
|
main_viewport_size = main_viewport->Size;
|
|
}
|
|
AddUpdateViewport(NULL, IMGUI_VIEWPORT_DEFAULT_ID, main_viewport_pos, main_viewport_size,
|
|
ImGuiViewportFlags_OwnedByApp | ImGuiViewportFlags_CanHostOtherWindows);
|
|
|
|
g.CurrentDpiScale = 0.0f;
|
|
g.CurrentViewport = NULL;
|
|
g.MouseViewport = NULL;
|
|
for (int n = 0; n < g.Viewports.Size; n++)
|
|
{
|
|
ImGuiViewportP* viewport = g.Viewports[n];
|
|
viewport->Idx = n;
|
|
|
|
// Erase unused viewports
|
|
if (n > 0 && viewport->LastFrameActive < g.FrameCount - 2)
|
|
{
|
|
// 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;
|
|
g.Windows[window_n]->ViewportOwned = false;
|
|
}
|
|
if (viewport == g.MouseLastHoveredViewport)
|
|
g.MouseLastHoveredViewport = NULL;
|
|
g.Viewports.erase(g.Viewports.Data + n);
|
|
|
|
// Destroy
|
|
IMGUI_DEBUG_LOG_VIEWPORT("Delete Viewport %08X (%s)\n", viewport->ID,
|
|
viewport->Window ? viewport->Window->Name : "n/a");
|
|
DestroyPlatformWindow(
|
|
viewport); // In most circumstances the platform window will already be destroyed here.
|
|
IM_ASSERT(g.PlatformIO.Viewports.contains(viewport) == false);
|
|
IM_DELETE(viewport);
|
|
n--;
|
|
continue;
|
|
}
|
|
|
|
const bool platform_funcs_available = viewport->PlatformWindowCreated;
|
|
if (viewports_enabled)
|
|
{
|
|
// Update Position and Size (from Platform Window to ImGui) if requested.
|
|
// We do it early in the frame instead of waiting for UpdatePlatformWindows() to avoid a frame of lag when
|
|
// moving/resizing using OS facilities.
|
|
if (!(viewport->Flags & ImGuiViewportFlags_Minimized) && platform_funcs_available)
|
|
{
|
|
// Viewport->WorkPos and WorkSize will be updated below
|
|
if (viewport->PlatformRequestMove)
|
|
viewport->Pos = viewport->LastPlatformPos = g.PlatformIO.Platform_GetWindowPos(viewport);
|
|
if (viewport->PlatformRequestResize)
|
|
viewport->Size = viewport->LastPlatformSize = g.PlatformIO.Platform_GetWindowSize(viewport);
|
|
}
|
|
}
|
|
|
|
// Update/copy monitor info
|
|
UpdateViewportPlatformMonitor(viewport);
|
|
|
|
// Lock down space taken by menu bars and status bars, reset the offset for functions like BeginMainMenuBar() to
|
|
// alter them again.
|
|
viewport->WorkOffsetMin = viewport->BuildWorkOffsetMin;
|
|
viewport->WorkOffsetMax = viewport->BuildWorkOffsetMax;
|
|
viewport->BuildWorkOffsetMin = viewport->BuildWorkOffsetMax = ImVec2(0.0f, 0.0f);
|
|
viewport->UpdateWorkRect();
|
|
|
|
// Reset alpha every frame. Users of transparency (docking) needs to request a lower alpha back.
|
|
viewport->Alpha = 1.0f;
|
|
|
|
// Translate imgui windows when a Host Viewport has been moved
|
|
// (This additionally keeps windows at the same place when ImGuiConfigFlags_ViewportsEnable is toggled!)
|
|
const ImVec2 viewport_delta_pos = viewport->Pos - viewport->LastPos;
|
|
if ((viewport->Flags & ImGuiViewportFlags_CanHostOtherWindows) &&
|
|
(viewport_delta_pos.x != 0.0f || viewport_delta_pos.y != 0.0f))
|
|
TranslateWindowsInViewport(viewport, viewport->LastPos, viewport->Pos);
|
|
|
|
// Update DPI scale
|
|
float new_dpi_scale;
|
|
if (g.PlatformIO.Platform_GetWindowDpiScale && platform_funcs_available)
|
|
new_dpi_scale = g.PlatformIO.Platform_GetWindowDpiScale(viewport);
|
|
else if (viewport->PlatformMonitor != -1)
|
|
new_dpi_scale = g.PlatformIO.Monitors[viewport->PlatformMonitor].DpiScale;
|
|
else
|
|
new_dpi_scale = (viewport->DpiScale != 0.0f) ? viewport->DpiScale : 1.0f;
|
|
if (viewport->DpiScale != 0.0f && new_dpi_scale != viewport->DpiScale)
|
|
{
|
|
float scale_factor = new_dpi_scale / viewport->DpiScale;
|
|
if (g.IO.ConfigFlags & ImGuiConfigFlags_DpiEnableScaleViewports)
|
|
ScaleWindowsInViewport(viewport, scale_factor);
|
|
// if (viewport == GetMainViewport())
|
|
// g.PlatformInterface.SetWindowSize(viewport, viewport->Size * scale_factor);
|
|
|
|
// Scale our window moving pivot so that the window will rescale roughly around the mouse position.
|
|
// FIXME-VIEWPORT: This currently creates a resizing feedback loop when a window is straddling a DPI
|
|
// transition border. (Minor: since our sizes do not perfectly linearly scale, deferring the click offset
|
|
// scale until we know the actual window scale ratio may get us slightly more precise mouse positioning.)
|
|
// if (g.MovingWindow != NULL && g.MovingWindow->Viewport == viewport)
|
|
// g.ActiveIdClickOffset = ImFloor(g.ActiveIdClickOffset * scale_factor);
|
|
}
|
|
viewport->DpiScale = new_dpi_scale;
|
|
}
|
|
|
|
// Update fallback monitor
|
|
if (g.PlatformIO.Monitors.Size == 0)
|
|
{
|
|
ImGuiPlatformMonitor* monitor = &g.FallbackMonitor;
|
|
monitor->MainPos = main_viewport->Pos;
|
|
monitor->MainSize = main_viewport->Size;
|
|
monitor->WorkPos = main_viewport->WorkPos;
|
|
monitor->WorkSize = main_viewport->WorkSize;
|
|
monitor->DpiScale = main_viewport->DpiScale;
|
|
}
|
|
|
|
if (!viewports_enabled)
|
|
{
|
|
g.MouseViewport = main_viewport;
|
|
return;
|
|
}
|
|
|
|
// Mouse handling: decide on the actual mouse viewport for this frame between the active/focused viewport and the
|
|
// hovered viewport. Note that 'viewport_hovered' should skip over any viewport that has the
|
|
// ImGuiViewportFlags_NoInputs flags set.
|
|
ImGuiViewportP* viewport_hovered = NULL;
|
|
if (g.IO.BackendFlags & ImGuiBackendFlags_HasMouseHoveredViewport)
|
|
{
|
|
viewport_hovered =
|
|
g.IO.MouseHoveredViewport ? (ImGuiViewportP*)FindViewportByID(g.IO.MouseHoveredViewport) : NULL;
|
|
if (viewport_hovered && (viewport_hovered->Flags & ImGuiViewportFlags_NoInputs))
|
|
{
|
|
// Backend failed at honoring its contract if it returned a viewport with the _NoInputs flag.
|
|
IM_ASSERT(0);
|
|
viewport_hovered = FindHoveredViewportFromPlatformWindowStack(g.IO.MousePos);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If the backend doesn't know how to honor ImGuiViewportFlags_NoInputs, we do a search ourselves. Note that
|
|
// this search: A) won't take account of the possibility that non-imgui windows may be in-between our dragged
|
|
// window and our target window. B) uses LastFrameAsRefViewport as a flawed replacement for the last time a
|
|
// window was focused (we could/should fix that by introducing Focus functions in PlatformIO)
|
|
viewport_hovered = FindHoveredViewportFromPlatformWindowStack(g.IO.MousePos);
|
|
}
|
|
if (viewport_hovered != NULL)
|
|
g.MouseLastHoveredViewport = viewport_hovered;
|
|
else if (g.MouseLastHoveredViewport == NULL)
|
|
g.MouseLastHoveredViewport = g.Viewports[0];
|
|
|
|
// Update mouse reference viewport
|
|
// (when moving a window we aim at its viewport, but this will be overwritten below if we go in drag and drop mode)
|
|
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.
|
|
// FIXME-VIEWPORT: This is essentially broken, when ImGuiBackendFlags_HasMouseHoveredViewport is set we want to
|
|
// trust when viewport_hovered==NULL and use that.
|
|
const bool is_mouse_dragging_with_an_expected_destination = g.DragDropActive;
|
|
if (is_mouse_dragging_with_an_expected_destination && viewport_hovered == NULL)
|
|
viewport_hovered = g.MouseLastHoveredViewport;
|
|
if (is_mouse_dragging_with_an_expected_destination || g.ActiveId == 0 || !IsAnyMouseDown())
|
|
if (viewport_hovered != NULL && viewport_hovered != g.MouseViewport &&
|
|
!(viewport_hovered->Flags & ImGuiViewportFlags_NoInputs))
|
|
g.MouseViewport = viewport_hovered;
|
|
|
|
IM_ASSERT(g.MouseViewport != NULL);
|
|
}
|
|
|
|
// Update user-facing viewport list (g.Viewports -> g.PlatformIO.Viewports after filtering out some)
|
|
static void ImGui::UpdateViewportsEndFrame()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
g.PlatformIO.Viewports.resize(0);
|
|
for (int i = 0; i < g.Viewports.Size; i++)
|
|
{
|
|
ImGuiViewportP* viewport = g.Viewports[i];
|
|
viewport->LastPos = viewport->Pos;
|
|
if (viewport->LastFrameActive < g.FrameCount || viewport->Size.x <= 0.0f || viewport->Size.y <= 0.0f)
|
|
if (i > 0) // Always include main viewport in the list
|
|
continue;
|
|
if (viewport->Window && !IsWindowActiveAndVisible(viewport->Window))
|
|
continue;
|
|
if (i > 0)
|
|
IM_ASSERT(viewport->Window != NULL);
|
|
g.PlatformIO.Viewports.push_back(viewport);
|
|
}
|
|
g.Viewports[0]->ClearRequestFlags(); // Clear main viewport flags because UpdatePlatformWindows() won't do it and
|
|
// may not even be called
|
|
}
|
|
|
|
// FIXME: We should ideally refactor the system to call this every frame (we currently don't)
|
|
ImGuiViewportP* ImGui::AddUpdateViewport(ImGuiWindow* window,
|
|
ImGuiID id,
|
|
const ImVec2& pos,
|
|
const ImVec2& size,
|
|
ImGuiViewportFlags flags)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(id != 0);
|
|
|
|
flags |= ImGuiViewportFlags_IsPlatformWindow;
|
|
if (window != NULL)
|
|
{
|
|
if (g.MovingWindow && g.MovingWindow->RootWindowDockTree == window)
|
|
flags |= ImGuiViewportFlags_NoInputs | ImGuiViewportFlags_NoFocusOnAppearing;
|
|
if ((window->Flags & ImGuiWindowFlags_NoMouseInputs) && (window->Flags & ImGuiWindowFlags_NoNavInputs))
|
|
flags |= ImGuiViewportFlags_NoInputs;
|
|
if (window->Flags & ImGuiWindowFlags_NoFocusOnAppearing)
|
|
flags |= ImGuiViewportFlags_NoFocusOnAppearing;
|
|
}
|
|
|
|
ImGuiViewportP* viewport = (ImGuiViewportP*)FindViewportByID(id);
|
|
if (viewport)
|
|
{
|
|
if (!viewport->PlatformRequestMove)
|
|
viewport->Pos = pos;
|
|
if (!viewport->PlatformRequestResize)
|
|
viewport->Size = size;
|
|
viewport->Flags = flags | (viewport->Flags & ImGuiViewportFlags_Minimized); // Preserve existing flags
|
|
}
|
|
else
|
|
{
|
|
// New viewport
|
|
viewport = IM_NEW(ImGuiViewportP)();
|
|
viewport->ID = id;
|
|
viewport->Idx = g.Viewports.Size;
|
|
viewport->Pos = viewport->LastPos = pos;
|
|
viewport->Size = size;
|
|
viewport->Flags = flags;
|
|
UpdateViewportPlatformMonitor(viewport);
|
|
g.Viewports.push_back(viewport);
|
|
IMGUI_DEBUG_LOG_VIEWPORT("Add Viewport %08X (%s)\n", id, window->Name);
|
|
|
|
// We normally setup for all viewports in NewFrame() but here need to handle the mid-frame creation of a new
|
|
// viewport. We need to extend the fullscreen clip rect so the OverlayDrawList clip is correct for that the
|
|
// first frame
|
|
g.DrawListSharedData.ClipRectFullscreen.x = ImMin(g.DrawListSharedData.ClipRectFullscreen.x, viewport->Pos.x);
|
|
g.DrawListSharedData.ClipRectFullscreen.y = ImMin(g.DrawListSharedData.ClipRectFullscreen.y, viewport->Pos.y);
|
|
g.DrawListSharedData.ClipRectFullscreen.z =
|
|
ImMax(g.DrawListSharedData.ClipRectFullscreen.z, viewport->Pos.x + viewport->Size.x);
|
|
g.DrawListSharedData.ClipRectFullscreen.w =
|
|
ImMax(g.DrawListSharedData.ClipRectFullscreen.w, viewport->Pos.y + viewport->Size.y);
|
|
|
|
// Store initial DpiScale before the OS platform window creation, based on expected monitor data.
|
|
// This is so we can select an appropriate font size on the first frame of our window lifetime
|
|
if (viewport->PlatformMonitor != -1)
|
|
viewport->DpiScale = g.PlatformIO.Monitors[viewport->PlatformMonitor].DpiScale;
|
|
}
|
|
|
|
viewport->Window = window;
|
|
viewport->LastFrameActive = g.FrameCount;
|
|
viewport->UpdateWorkRect();
|
|
IM_ASSERT(window == NULL || viewport->ID == window->ID);
|
|
|
|
if (window != NULL)
|
|
window->ViewportOwned = true;
|
|
|
|
return viewport;
|
|
}
|
|
|
|
// FIXME-VIEWPORT: This is all super messy and ought to be clarified or rewritten.
|
|
static void ImGui::WindowSelectViewport(ImGuiWindow* window)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindowFlags flags = window->Flags;
|
|
window->ViewportAllowPlatformMonitorExtend = -1;
|
|
|
|
// Restore main viewport if multi-viewport is not supported by the backend
|
|
ImGuiViewportP* main_viewport = (ImGuiViewportP*)(void*)GetMainViewport();
|
|
if (!(g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable))
|
|
{
|
|
SetWindowViewport(window, main_viewport);
|
|
return;
|
|
}
|
|
window->ViewportOwned = false;
|
|
|
|
// Appearing popups reset their viewport so they can inherit again
|
|
if ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) && window->Appearing)
|
|
{
|
|
window->Viewport = NULL;
|
|
window->ViewportId = 0;
|
|
}
|
|
|
|
if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasViewport) == 0)
|
|
{
|
|
// By default inherit from parent window
|
|
if (window->Viewport == NULL && window->ParentWindow &&
|
|
(!window->ParentWindow->IsFallbackWindow || window->ParentWindow->WasActive))
|
|
window->Viewport = window->ParentWindow->Viewport;
|
|
|
|
// Attempt to restore saved viewport id (= window that hasn't been activated yet), try to restore the viewport
|
|
// based on saved 'window->ViewportPos' restored from .ini file
|
|
if (window->Viewport == NULL && window->ViewportId != 0)
|
|
{
|
|
window->Viewport = (ImGuiViewportP*)FindViewportByID(window->ViewportId);
|
|
if (window->Viewport == NULL && window->ViewportPos.x != FLT_MAX && window->ViewportPos.y != FLT_MAX)
|
|
window->Viewport =
|
|
AddUpdateViewport(window, window->ID, window->ViewportPos, window->Size, ImGuiViewportFlags_None);
|
|
}
|
|
}
|
|
|
|
bool lock_viewport = false;
|
|
if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasViewport)
|
|
{
|
|
// Code explicitly request a viewport
|
|
window->Viewport = (ImGuiViewportP*)FindViewportByID(g.NextWindowData.ViewportId);
|
|
window->ViewportId = g.NextWindowData.ViewportId; // Store ID even if Viewport isn't resolved yet.
|
|
lock_viewport = true;
|
|
}
|
|
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 (GetWindowAlwaysWantOwnViewport(window))
|
|
{
|
|
window->Viewport = AddUpdateViewport(window, window->ID, window->Pos, window->Size, ImGuiViewportFlags_None);
|
|
}
|
|
else if (g.MovingWindow && g.MovingWindow->RootWindowDockTree == window && IsMousePosValid())
|
|
{
|
|
if (window->Viewport != NULL && window->Viewport->Window == window)
|
|
window->Viewport =
|
|
AddUpdateViewport(window, window->ID, window->Pos, window->Size, ImGuiViewportFlags_None);
|
|
}
|
|
else
|
|
{
|
|
// Merge into host viewport?
|
|
// We cannot test window->ViewportOwned as it set lower in the function.
|
|
bool try_to_merge_into_host_viewport =
|
|
(window->Viewport && window == window->Viewport->Window && g.ActiveId == 0);
|
|
if (try_to_merge_into_host_viewport)
|
|
UpdateTryMergeWindowIntoHostViewports(window);
|
|
}
|
|
|
|
// Fallback: merge in default viewport if z-order matches, otherwise create a new viewport
|
|
if (window->Viewport == NULL)
|
|
if (!UpdateTryMergeWindowIntoHostViewport(window, main_viewport))
|
|
window->Viewport =
|
|
AddUpdateViewport(window, window->ID, window->Pos, window->Size, ImGuiViewportFlags_None);
|
|
|
|
// Mark window as allowed to protrude outside of its viewport and into the current monitor
|
|
if (!lock_viewport)
|
|
{
|
|
if (flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup))
|
|
{
|
|
// We need to take account of the possibility that mouse may become invalid.
|
|
// Popups/Tooltip always set ViewportAllowPlatformMonitorExtend so GetWindowAllowedExtentRect() will return
|
|
// full monitor bounds.
|
|
ImVec2 mouse_ref =
|
|
(flags & ImGuiWindowFlags_Tooltip) ? g.IO.MousePos : g.BeginPopupStack.back().OpenMousePos;
|
|
bool use_mouse_ref = (g.NavDisableHighlight || !g.NavDisableMouseHover || !g.NavWindow);
|
|
bool mouse_valid = IsMousePosValid(&mouse_ref);
|
|
if ((window->Appearing || (flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_ChildMenu))) &&
|
|
(!use_mouse_ref || mouse_valid))
|
|
window->ViewportAllowPlatformMonitorExtend =
|
|
FindPlatformMonitorForPos((use_mouse_ref && mouse_valid) ? mouse_ref : NavCalcPreferredRefPos());
|
|
else
|
|
window->ViewportAllowPlatformMonitorExtend = window->Viewport->PlatformMonitor;
|
|
}
|
|
else if (window->Viewport && window != window->Viewport->Window && window->Viewport->Window &&
|
|
!(flags & ImGuiWindowFlags_ChildWindow))
|
|
{
|
|
// When called from Begin() we don't have access to a proper version of the Hidden flag yet, so we replicate
|
|
// this code.
|
|
const bool will_be_visible = (window->DockIsActive && !window->DockTabIsVisible) ? false : true;
|
|
if ((window->Flags & ImGuiWindowFlags_DockNodeHost) && window->Viewport->LastFrameActive < g.FrameCount &&
|
|
will_be_visible)
|
|
{
|
|
// Steal/transfer ownership
|
|
IMGUI_DEBUG_LOG_VIEWPORT("Window '%s' steal Viewport %08X from Window '%s'\n", window->Name,
|
|
window->Viewport->ID, window->Viewport->Window->Name);
|
|
window->Viewport->Window = window;
|
|
window->Viewport->ID = window->ID;
|
|
window->Viewport->LastNameHash = 0;
|
|
}
|
|
else if (!UpdateTryMergeWindowIntoHostViewports(window)) // Merge?
|
|
{
|
|
// New viewport
|
|
window->Viewport = AddUpdateViewport(window, window->ID, window->Pos, window->Size,
|
|
ImGuiViewportFlags_NoFocusOnAppearing);
|
|
}
|
|
}
|
|
else if (window->ViewportAllowPlatformMonitorExtend < 0 && (flags & ImGuiWindowFlags_ChildWindow) == 0)
|
|
{
|
|
// Regular (non-child, non-popup) windows by default are also allowed to protrude
|
|
// Child windows are kept contained within their parent.
|
|
window->ViewportAllowPlatformMonitorExtend = window->Viewport->PlatformMonitor;
|
|
}
|
|
}
|
|
|
|
// Update flags
|
|
window->ViewportOwned = (window == window->Viewport->Window);
|
|
window->ViewportId = window->Viewport->ID;
|
|
|
|
// If the OS window has a title bar, hide our imgui title bar
|
|
// if (window->ViewportOwned && !(window->Viewport->Flags & ImGuiViewportFlags_NoDecoration))
|
|
// window->Flags |= ImGuiWindowFlags_NoTitleBar;
|
|
}
|
|
|
|
void ImGui::WindowSyncOwnedViewport(ImGuiWindow* window, ImGuiWindow* parent_window_in_stack)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
bool viewport_rect_changed = false;
|
|
|
|
// Synchronize window --> viewport in most situations
|
|
// Synchronize viewport -> window in case the platform window has been moved or resized from the OS/WM
|
|
if (window->Viewport->PlatformRequestMove)
|
|
{
|
|
window->Pos = window->Viewport->Pos;
|
|
MarkIniSettingsDirty(window);
|
|
}
|
|
else if (memcmp(&window->Viewport->Pos, &window->Pos, sizeof(window->Pos)) != 0)
|
|
{
|
|
viewport_rect_changed = true;
|
|
window->Viewport->Pos = window->Pos;
|
|
}
|
|
|
|
if (window->Viewport->PlatformRequestResize)
|
|
{
|
|
window->Size = window->SizeFull = window->Viewport->Size;
|
|
MarkIniSettingsDirty(window);
|
|
}
|
|
else if (memcmp(&window->Viewport->Size, &window->Size, sizeof(window->Size)) != 0)
|
|
{
|
|
viewport_rect_changed = true;
|
|
window->Viewport->Size = window->Size;
|
|
}
|
|
window->Viewport->UpdateWorkRect();
|
|
|
|
// The viewport may have changed monitor since the global update in UpdateViewportsNewFrame()
|
|
// Either a SetNextWindowPos() call in the current frame or a SetWindowPos() call in the previous frame may have
|
|
// this effect.
|
|
if (viewport_rect_changed)
|
|
UpdateViewportPlatformMonitor(window->Viewport);
|
|
|
|
// Update common viewport flags
|
|
const ImGuiViewportFlags viewport_flags_to_clear = ImGuiViewportFlags_TopMost | ImGuiViewportFlags_NoTaskBarIcon |
|
|
ImGuiViewportFlags_NoDecoration |
|
|
ImGuiViewportFlags_NoRendererClear;
|
|
ImGuiViewportFlags viewport_flags = window->Viewport->Flags & ~viewport_flags_to_clear;
|
|
ImGuiWindowFlags window_flags = window->Flags;
|
|
const bool is_modal = (window_flags & ImGuiWindowFlags_Modal) != 0;
|
|
const bool is_short_lived_floating_window =
|
|
(window_flags & (ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup)) != 0;
|
|
if (window_flags & ImGuiWindowFlags_Tooltip)
|
|
viewport_flags |= ImGuiViewportFlags_TopMost;
|
|
if ((g.IO.ConfigViewportsNoTaskBarIcon || is_short_lived_floating_window) && !is_modal)
|
|
viewport_flags |= ImGuiViewportFlags_NoTaskBarIcon;
|
|
if (g.IO.ConfigViewportsNoDecoration || is_short_lived_floating_window)
|
|
viewport_flags |= ImGuiViewportFlags_NoDecoration;
|
|
|
|
// Not correct to set modal as topmost because:
|
|
// - Because other popups can be stacked above a modal (e.g. combo box in a modal)
|
|
// - ImGuiViewportFlags_TopMost is currently handled different in backends: in Win32 it is "appear top most" whereas
|
|
// in GLFW and SDL it is "stay topmost"
|
|
// if (flags & ImGuiWindowFlags_Modal)
|
|
// viewport_flags |= ImGuiViewportFlags_TopMost;
|
|
|
|
// For popups and menus that may be protruding out of their parent viewport, we enable _NoFocusOnClick so that
|
|
// clicking on them won't steal the OS focus away from their parent window (which may be reflected in OS the title
|
|
// bar decoration). Setting _NoFocusOnClick would technically prevent us from bringing back to front in case they
|
|
// are being covered by an OS window from a different app, but it shouldn't be much of a problem considering those
|
|
// are already popups that are closed when clicking elsewhere.
|
|
if (is_short_lived_floating_window && !is_modal)
|
|
viewport_flags |= ImGuiViewportFlags_NoFocusOnAppearing | ImGuiViewportFlags_NoFocusOnClick;
|
|
|
|
// We can overwrite viewport flags using ImGuiWindowClass (advanced users)
|
|
if (window->WindowClass.ViewportFlagsOverrideSet)
|
|
viewport_flags |= window->WindowClass.ViewportFlagsOverrideSet;
|
|
if (window->WindowClass.ViewportFlagsOverrideClear)
|
|
viewport_flags &= ~window->WindowClass.ViewportFlagsOverrideClear;
|
|
|
|
// We can also tell the backend that clearing the platform window won't be necessary,
|
|
// as our window background is filling the viewport and we have disabled BgAlpha.
|
|
// FIXME: Work on support for per-viewport transparency (#2766)
|
|
if (!(window_flags & ImGuiWindowFlags_NoBackground))
|
|
viewport_flags |= ImGuiViewportFlags_NoRendererClear;
|
|
|
|
window->Viewport->Flags = viewport_flags;
|
|
|
|
// Update parent viewport ID
|
|
// (the !IsFallbackWindow test mimic the one done in WindowSelectViewport())
|
|
if (window->WindowClass.ParentViewportId)
|
|
window->Viewport->ParentViewportId = window->WindowClass.ParentViewportId;
|
|
else if ((window_flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) && parent_window_in_stack &&
|
|
(!parent_window_in_stack->IsFallbackWindow || parent_window_in_stack->WasActive))
|
|
window->Viewport->ParentViewportId = parent_window_in_stack->Viewport->ID;
|
|
else
|
|
window->Viewport->ParentViewportId = g.IO.ConfigViewportsNoDefaultParent ? 0 : IMGUI_VIEWPORT_DEFAULT_ID;
|
|
}
|
|
|
|
// Called by user at the end of the main loop, after EndFrame()
|
|
// This will handle the creation/update of all OS windows via function defined in the ImGuiPlatformIO api.
|
|
void ImGui::UpdatePlatformWindows()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(g.FrameCountEnded == g.FrameCount &&
|
|
"Forgot to call Render() or EndFrame() before UpdatePlatformWindows()?");
|
|
IM_ASSERT(g.FrameCountPlatformEnded < g.FrameCount);
|
|
g.FrameCountPlatformEnded = g.FrameCount;
|
|
if (!(g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable))
|
|
return;
|
|
|
|
// Create/resize/destroy platform windows to match each active viewport.
|
|
// Skip the main viewport (index 0), which is always fully handled by the application!
|
|
for (int i = 1; i < g.Viewports.Size; i++)
|
|
{
|
|
ImGuiViewportP* viewport = g.Viewports[i];
|
|
|
|
// Destroy platform window if the viewport hasn't been submitted or if it is hosting a hidden window
|
|
// (the implicit/fallback Debug##Default window will be registering its viewport then be disabled, causing a
|
|
// dummy DestroyPlatformWindow to be made each frame)
|
|
bool destroy_platform_window = false;
|
|
destroy_platform_window |= (viewport->LastFrameActive < g.FrameCount - 1);
|
|
destroy_platform_window |= (viewport->Window && !IsWindowActiveAndVisible(viewport->Window));
|
|
if (destroy_platform_window)
|
|
{
|
|
DestroyPlatformWindow(viewport);
|
|
continue;
|
|
}
|
|
|
|
// New windows that appears directly in a new viewport won't always have a size on their first frame
|
|
if (viewport->LastFrameActive < g.FrameCount || viewport->Size.x <= 0 || viewport->Size.y <= 0)
|
|
continue;
|
|
|
|
// Create window
|
|
bool is_new_platform_window = (viewport->PlatformWindowCreated == false);
|
|
if (is_new_platform_window)
|
|
{
|
|
IMGUI_DEBUG_LOG_VIEWPORT("Create Platform Window %08X (%s)\n", viewport->ID,
|
|
viewport->Window ? viewport->Window->Name : "n/a");
|
|
g.PlatformIO.Platform_CreateWindow(viewport);
|
|
if (g.PlatformIO.Renderer_CreateWindow != NULL)
|
|
g.PlatformIO.Renderer_CreateWindow(viewport);
|
|
viewport->LastNameHash = 0;
|
|
viewport->LastPlatformPos = viewport->LastPlatformSize =
|
|
ImVec2(FLT_MAX, FLT_MAX); // By clearing those we'll enforce a call to Platform_SetWindowPos/Size
|
|
// below, before Platform_ShowWindow (FIXME: Is that necessary?)
|
|
viewport->LastRendererSize = viewport->Size; // We don't need to call Renderer_SetWindowSize() as it is
|
|
// expected Renderer_CreateWindow() already did it.
|
|
viewport->PlatformWindowCreated = true;
|
|
}
|
|
|
|
// Apply Position and Size (from ImGui to Platform/Renderer backends)
|
|
if ((viewport->LastPlatformPos.x != viewport->Pos.x || viewport->LastPlatformPos.y != viewport->Pos.y) &&
|
|
!viewport->PlatformRequestMove)
|
|
g.PlatformIO.Platform_SetWindowPos(viewport, viewport->Pos);
|
|
if ((viewport->LastPlatformSize.x != viewport->Size.x || viewport->LastPlatformSize.y != viewport->Size.y) &&
|
|
!viewport->PlatformRequestResize)
|
|
g.PlatformIO.Platform_SetWindowSize(viewport, viewport->Size);
|
|
if ((viewport->LastRendererSize.x != viewport->Size.x || viewport->LastRendererSize.y != viewport->Size.y) &&
|
|
g.PlatformIO.Renderer_SetWindowSize)
|
|
g.PlatformIO.Renderer_SetWindowSize(viewport, viewport->Size);
|
|
viewport->LastPlatformPos = viewport->Pos;
|
|
viewport->LastPlatformSize = viewport->LastRendererSize = viewport->Size;
|
|
|
|
// Update title bar (if it changed)
|
|
if (ImGuiWindow* window_for_title = GetWindowForTitleDisplay(viewport->Window))
|
|
{
|
|
const char* title_begin = window_for_title->Name;
|
|
char* title_end = (char*)(intptr_t)FindRenderedTextEnd(title_begin);
|
|
const ImGuiID title_hash = ImHashStr(title_begin, title_end - title_begin);
|
|
if (viewport->LastNameHash != title_hash)
|
|
{
|
|
char title_end_backup_c = *title_end;
|
|
*title_end = 0; // Cut existing buffer short instead of doing an alloc/free, no small gain.
|
|
g.PlatformIO.Platform_SetWindowTitle(viewport, title_begin);
|
|
*title_end = title_end_backup_c;
|
|
viewport->LastNameHash = title_hash;
|
|
}
|
|
}
|
|
|
|
// Update alpha (if it changed)
|
|
if (viewport->LastAlpha != viewport->Alpha && g.PlatformIO.Platform_SetWindowAlpha)
|
|
g.PlatformIO.Platform_SetWindowAlpha(viewport, viewport->Alpha);
|
|
viewport->LastAlpha = viewport->Alpha;
|
|
|
|
// Optional, general purpose call to allow the backend to perform general book-keeping even if things haven't
|
|
// changed.
|
|
if (g.PlatformIO.Platform_UpdateWindow)
|
|
g.PlatformIO.Platform_UpdateWindow(viewport);
|
|
|
|
if (is_new_platform_window)
|
|
{
|
|
// On startup ensure new platform window don't steal focus (give it a few frames, as nested contents may
|
|
// lead to viewport being created a few frames late)
|
|
if (g.FrameCount < 3)
|
|
viewport->Flags |= ImGuiViewportFlags_NoFocusOnAppearing;
|
|
|
|
// Show window
|
|
g.PlatformIO.Platform_ShowWindow(viewport);
|
|
|
|
// Even without focus, we assume the window becomes front-most.
|
|
// This is useful for our platform z-order heuristic when io.MouseHoveredViewport is not available.
|
|
if (viewport->LastFrontMostStampCount != g.ViewportFrontMostStampCount)
|
|
viewport->LastFrontMostStampCount = ++g.ViewportFrontMostStampCount;
|
|
}
|
|
|
|
// Clear request flags
|
|
viewport->ClearRequestFlags();
|
|
}
|
|
|
|
// Update our implicit z-order knowledge of platform windows, which is used when the backend cannot provide
|
|
// io.MouseHoveredViewport. When setting Platform_GetWindowFocus, it is expected that the platform backend can
|
|
// handle calls without crashing if it doesn't have data stored.
|
|
// FIXME-VIEWPORT: We should use this information to also set dear imgui-side focus, allowing us to handle os-level
|
|
// alt+tab.
|
|
if (g.PlatformIO.Platform_GetWindowFocus != NULL)
|
|
{
|
|
ImGuiViewportP* focused_viewport = NULL;
|
|
for (int n = 0; n < g.Viewports.Size && focused_viewport == NULL; n++)
|
|
{
|
|
ImGuiViewportP* viewport = g.Viewports[n];
|
|
if (viewport->PlatformWindowCreated)
|
|
if (g.PlatformIO.Platform_GetWindowFocus(viewport))
|
|
focused_viewport = viewport;
|
|
}
|
|
|
|
// Store a tag so we can infer z-order easily from all our windows
|
|
// We compare PlatformLastFocusedViewportId so newly created viewports with _NoFocusOnAppearing flag
|
|
// will keep the front most stamp instead of losing it back to their parent viewport.
|
|
if (focused_viewport && g.PlatformLastFocusedViewportId != focused_viewport->ID)
|
|
{
|
|
if (focused_viewport->LastFrontMostStampCount != g.ViewportFrontMostStampCount)
|
|
focused_viewport->LastFrontMostStampCount = ++g.ViewportFrontMostStampCount;
|
|
g.PlatformLastFocusedViewportId = focused_viewport->ID;
|
|
}
|
|
}
|
|
}
|
|
|
|
// This is a default/basic function for performing the rendering/swap of multiple Platform Windows.
|
|
// Custom renderers may prefer to not call this function at all, and instead iterate the publicly exposed platform data
|
|
// and handle rendering/sync themselves. The Render/Swap functions stored in ImGuiPlatformIO are merely here to allow
|
|
// for this helper to exist, but you can do it yourself:
|
|
//
|
|
// ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
|
|
// for (int i = 1; i < platform_io.Viewports.Size; i++)
|
|
// if ((platform_io.Viewports[i]->Flags & ImGuiViewportFlags_Minimized) == 0)
|
|
// MyRenderFunction(platform_io.Viewports[i], my_args);
|
|
// for (int i = 1; i < platform_io.Viewports.Size; i++)
|
|
// if ((platform_io.Viewports[i]->Flags & ImGuiViewportFlags_Minimized) == 0)
|
|
// MySwapBufferFunction(platform_io.Viewports[i], my_args);
|
|
//
|
|
void ImGui::RenderPlatformWindowsDefault(void* platform_render_arg, void* renderer_render_arg)
|
|
{
|
|
// Skip the main viewport (index 0), which is always fully handled by the application!
|
|
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
|
|
for (int i = 1; i < platform_io.Viewports.Size; i++)
|
|
{
|
|
ImGuiViewport* viewport = platform_io.Viewports[i];
|
|
if (viewport->Flags & ImGuiViewportFlags_Minimized)
|
|
continue;
|
|
if (platform_io.Platform_RenderWindow)
|
|
platform_io.Platform_RenderWindow(viewport, platform_render_arg);
|
|
if (platform_io.Renderer_RenderWindow)
|
|
platform_io.Renderer_RenderWindow(viewport, renderer_render_arg);
|
|
}
|
|
for (int i = 1; i < platform_io.Viewports.Size; i++)
|
|
{
|
|
ImGuiViewport* viewport = platform_io.Viewports[i];
|
|
if (viewport->Flags & ImGuiViewportFlags_Minimized)
|
|
continue;
|
|
if (platform_io.Platform_SwapBuffers)
|
|
platform_io.Platform_SwapBuffers(viewport, platform_render_arg);
|
|
if (platform_io.Renderer_SwapBuffers)
|
|
platform_io.Renderer_SwapBuffers(viewport, renderer_render_arg);
|
|
}
|
|
}
|
|
|
|
static int ImGui::FindPlatformMonitorForPos(const ImVec2& pos)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
for (int monitor_n = 0; monitor_n < g.PlatformIO.Monitors.Size; monitor_n++)
|
|
{
|
|
const ImGuiPlatformMonitor& monitor = g.PlatformIO.Monitors[monitor_n];
|
|
if (ImRect(monitor.MainPos, monitor.MainPos + monitor.MainSize).Contains(pos))
|
|
return monitor_n;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
// Search for the monitor with the largest intersection area with the given rectangle
|
|
// We generally try to avoid searching loops but the monitor count should be very small here
|
|
// FIXME-OPT: We could test the last monitor used for that viewport first, and early
|
|
static int ImGui::FindPlatformMonitorForRect(const ImRect& rect)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
const int monitor_count = g.PlatformIO.Monitors.Size;
|
|
if (monitor_count <= 1)
|
|
return monitor_count - 1;
|
|
|
|
// Use a minimum threshold of 1.0f so a zero-sized rect won't false positive, and will still find the correct
|
|
// monitor given its position. This is necessary for tooltips which always resize down to zero at first.
|
|
const float surface_threshold = ImMax(rect.GetWidth() * rect.GetHeight() * 0.5f, 1.0f);
|
|
int best_monitor_n = -1;
|
|
float best_monitor_surface = 0.001f;
|
|
|
|
for (int monitor_n = 0; monitor_n < g.PlatformIO.Monitors.Size && best_monitor_surface < surface_threshold;
|
|
monitor_n++)
|
|
{
|
|
const ImGuiPlatformMonitor& monitor = g.PlatformIO.Monitors[monitor_n];
|
|
const ImRect monitor_rect = ImRect(monitor.MainPos, monitor.MainPos + monitor.MainSize);
|
|
if (monitor_rect.Contains(rect))
|
|
return monitor_n;
|
|
ImRect overlapping_rect = rect;
|
|
overlapping_rect.ClipWithFull(monitor_rect);
|
|
float overlapping_surface = overlapping_rect.GetWidth() * overlapping_rect.GetHeight();
|
|
if (overlapping_surface < best_monitor_surface)
|
|
continue;
|
|
best_monitor_surface = overlapping_surface;
|
|
best_monitor_n = monitor_n;
|
|
}
|
|
return best_monitor_n;
|
|
}
|
|
|
|
// Update monitor from viewport rectangle (we'll use this info to clamp windows and save windows lost in a removed
|
|
// monitor)
|
|
static void ImGui::UpdateViewportPlatformMonitor(ImGuiViewportP* viewport)
|
|
{
|
|
viewport->PlatformMonitor = (short)FindPlatformMonitorForRect(viewport->GetMainRect());
|
|
}
|
|
|
|
// Return value is always != NULL, but don't hold on it across frames.
|
|
const ImGuiPlatformMonitor* ImGui::GetViewportPlatformMonitor(ImGuiViewport* viewport_p)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)viewport_p;
|
|
int monitor_idx = viewport->PlatformMonitor;
|
|
if (monitor_idx >= 0 && monitor_idx < g.PlatformIO.Monitors.Size)
|
|
return &g.PlatformIO.Monitors[monitor_idx];
|
|
return &g.FallbackMonitor;
|
|
}
|
|
|
|
void ImGui::DestroyPlatformWindow(ImGuiViewportP* viewport)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (viewport->PlatformWindowCreated)
|
|
{
|
|
if (g.PlatformIO.Renderer_DestroyWindow)
|
|
g.PlatformIO.Renderer_DestroyWindow(viewport);
|
|
if (g.PlatformIO.Platform_DestroyWindow)
|
|
g.PlatformIO.Platform_DestroyWindow(viewport);
|
|
IM_ASSERT(viewport->RendererUserData == NULL && viewport->PlatformUserData == NULL);
|
|
|
|
// Don't clear PlatformWindowCreated for the main viewport, as we initially set that up to true in Initialize()
|
|
// The righter way may be to leave it to the backend to set this flag all-together, and made the flag public.
|
|
if (viewport->ID != IMGUI_VIEWPORT_DEFAULT_ID)
|
|
viewport->PlatformWindowCreated = false;
|
|
}
|
|
else
|
|
{
|
|
IM_ASSERT(viewport->RendererUserData == NULL && viewport->PlatformUserData == NULL &&
|
|
viewport->PlatformHandle == NULL);
|
|
}
|
|
viewport->RendererUserData = viewport->PlatformUserData = viewport->PlatformHandle = NULL;
|
|
viewport->ClearRequestFlags();
|
|
}
|
|
|
|
void ImGui::DestroyPlatformWindows()
|
|
{
|
|
// We call the destroy window on every viewport (including the main viewport, index 0) to give a chance to the
|
|
// backend to clear any data they may have stored in e.g. PlatformUserData, RendererUserData. It is convenient for
|
|
// the platform backend code to store something in the main viewport, in order for e.g. the mouse handling code to
|
|
// operator a consistent manner. It is expected that the backend can handle calls to
|
|
// Renderer_DestroyWindow/Platform_DestroyWindow without crashing if it doesn't have data stored.
|
|
ImGuiContext& g = *GImGui;
|
|
for (int i = 0; i < g.Viewports.Size; i++)
|
|
DestroyPlatformWindow(g.Viewports[i]);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// [SECTION] DOCKING
|
|
//-----------------------------------------------------------------------------
|
|
// Docking: Internal Types
|
|
// Docking: Forward Declarations
|
|
// Docking: ImGuiDockContext
|
|
// Docking: ImGuiDockContext Docking/Undocking functions
|
|
// Docking: ImGuiDockNode
|
|
// Docking: ImGuiDockNode Tree manipulation functions
|
|
// Docking: Public Functions (SetWindowDock, DockSpace, DockSpaceOverViewport)
|
|
// Docking: Builder Functions
|
|
// Docking: Begin/End Support Functions (called from Begin/End)
|
|
// Docking: Settings
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Typical Docking call flow: (root level is generally public API):
|
|
//-----------------------------------------------------------------------------
|
|
// - NewFrame() new dear imgui frame
|
|
// | DockContextNewFrameUpdateUndocking() - process queued undocking requests
|
|
// | - DockContextProcessUndockWindow() - process one window undocking request
|
|
// | - DockContextProcessUndockNode() - process one whole node undocking request
|
|
// | DockContextNewFrameUpdateUndocking() - process queue docking requests, create floating dock nodes
|
|
// | - update g.HoveredDockNode - [debug] update node hovered by mouse
|
|
// | - DockContextProcessDock() - process one docking request
|
|
// | - DockNodeUpdate()
|
|
// | - DockNodeUpdateForRootNode()
|
|
// | - DockNodeUpdateVisibleFlagAndInactiveChilds()
|
|
// | - DockNodeFindInfo()
|
|
// | - destroy unused node or tab bar
|
|
// | - create dock node host window
|
|
// | - Begin() etc.
|
|
// | - DockNodeStartMouseMovingWindow()
|
|
// | - DockNodeTreeUpdatePosSize()
|
|
// | - DockNodeTreeUpdateSplitter()
|
|
// | - draw node background
|
|
// | - DockNodeUpdateTabBar() - create/update tab bar for a docking node
|
|
// | - DockNodeAddTabBar()
|
|
// | - DockNodeUpdateWindowMenu()
|
|
// | - DockNodeCalcTabBarLayout()
|
|
// | - BeginTabBarEx()
|
|
// | - TabItemEx() calls
|
|
// | - EndTabBar()
|
|
// | - BeginDockableDragDropTarget()
|
|
// | - DockNodeUpdate() - recurse into child nodes...
|
|
//-----------------------------------------------------------------------------
|
|
// - DockSpace() user submit a dockspace into a window
|
|
// | Begin(Child) - create a child window
|
|
// | DockNodeUpdate() - call main dock node update function
|
|
// | End(Child)
|
|
// | ItemSize()
|
|
//-----------------------------------------------------------------------------
|
|
// - Begin()
|
|
// | BeginDocked()
|
|
// | BeginDockableDragDropSource()
|
|
// | BeginDockableDragDropTarget()
|
|
// | - DockNodePreviewDockRender()
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Docking: Internal Types
|
|
//-----------------------------------------------------------------------------
|
|
// - ImGuiDockRequestType
|
|
// - ImGuiDockRequest
|
|
// - ImGuiDockPreviewData
|
|
// - ImGuiDockNodeSettings
|
|
// - ImGuiDockContext
|
|
//-----------------------------------------------------------------------------
|
|
|
|
enum ImGuiDockRequestType
|
|
{
|
|
ImGuiDockRequestType_None = 0,
|
|
ImGuiDockRequestType_Dock,
|
|
ImGuiDockRequestType_Undock,
|
|
ImGuiDockRequestType_Split // Split is the same as Dock but without a DockPayload
|
|
};
|
|
|
|
struct ImGuiDockRequest
|
|
{
|
|
ImGuiDockRequestType Type;
|
|
ImGuiWindow* DockTargetWindow; // Destination/Target Window to dock into (may be a loose window or a DockNode,
|
|
// might be NULL in which case DockTargetNode cannot be NULL)
|
|
ImGuiDockNode* DockTargetNode; // Destination/Target Node to dock into
|
|
ImGuiWindow* DockPayload; // Source/Payload window to dock (may be a loose window or a DockNode), [Optional]
|
|
ImGuiDir DockSplitDir;
|
|
float DockSplitRatio;
|
|
bool DockSplitOuter;
|
|
ImGuiWindow* UndockTargetWindow;
|
|
ImGuiDockNode* UndockTargetNode;
|
|
|
|
ImGuiDockRequest()
|
|
{
|
|
Type = ImGuiDockRequestType_None;
|
|
DockTargetWindow = DockPayload = UndockTargetWindow = NULL;
|
|
DockTargetNode = UndockTargetNode = NULL;
|
|
DockSplitDir = ImGuiDir_None;
|
|
DockSplitRatio = 0.5f;
|
|
DockSplitOuter = false;
|
|
}
|
|
};
|
|
|
|
struct ImGuiDockPreviewData
|
|
{
|
|
ImGuiDockNode FutureNode;
|
|
bool IsDropAllowed;
|
|
bool IsCenterAvailable;
|
|
bool IsSidesAvailable; // Hold your breath, grammar freaks..
|
|
bool IsSplitDirExplicit; // Set when hovered the drop rect (vs. implicit SplitDir==None when hovered the window)
|
|
ImGuiDockNode* SplitNode;
|
|
ImGuiDir SplitDir;
|
|
float SplitRatio;
|
|
ImRect DropRectsDraw[ImGuiDir_COUNT +
|
|
1]; // May be slightly different from hit-testing drop rects used in DockNodeCalcDropRects()
|
|
|
|
ImGuiDockPreviewData() : FutureNode(0)
|
|
{
|
|
IsDropAllowed = IsCenterAvailable = IsSidesAvailable = IsSplitDirExplicit = false;
|
|
SplitNode = NULL;
|
|
SplitDir = ImGuiDir_None;
|
|
SplitRatio = 0.f;
|
|
for (int n = 0; n < IM_ARRAYSIZE(DropRectsDraw); n++)
|
|
DropRectsDraw[n] = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX);
|
|
}
|
|
};
|
|
|
|
// Persistent Settings data, stored contiguously in SettingsNodes (sizeof() ~32 bytes)
|
|
struct ImGuiDockNodeSettings
|
|
{
|
|
ImGuiID ID;
|
|
ImGuiID ParentNodeId;
|
|
ImGuiID ParentWindowId;
|
|
ImGuiID SelectedTabId;
|
|
signed char SplitAxis;
|
|
char Depth;
|
|
ImGuiDockNodeFlags
|
|
Flags; // NB: We save individual flags one by one in ascii format (ImGuiDockNodeFlags_SavedFlagsMask_)
|
|
ImVec2ih Pos;
|
|
ImVec2ih Size;
|
|
ImVec2ih SizeRef;
|
|
ImGuiDockNodeSettings()
|
|
{
|
|
memset(this, 0, sizeof(*this));
|
|
SplitAxis = ImGuiAxis_None;
|
|
}
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Docking: Forward Declarations
|
|
//-----------------------------------------------------------------------------
|
|
|
|
namespace ImGui
|
|
{
|
|
// ImGuiDockContext
|
|
static ImGuiDockNode* DockContextAddNode(ImGuiContext* ctx, ImGuiID id);
|
|
static void DockContextRemoveNode(ImGuiContext* ctx, ImGuiDockNode* node, bool merge_sibling_into_parent_node);
|
|
static void DockContextQueueNotifyRemovedNode(ImGuiContext* ctx, ImGuiDockNode* node);
|
|
static void DockContextProcessDock(ImGuiContext* ctx, ImGuiDockRequest* req);
|
|
static void 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 ImGuiDockNode* DockContextBindNodeToWindow(ImGuiContext* ctx, ImGuiWindow* window);
|
|
static void DockContextBuildNodesFromSettings(ImGuiContext* ctx,
|
|
ImGuiDockNodeSettings* node_settings_array,
|
|
int node_settings_count);
|
|
static void DockContextBuildAddWindowsToNodes(ImGuiContext* ctx, ImGuiID root_id); // Use root_id==0 to add all
|
|
|
|
// ImGuiDockNode
|
|
static void DockNodeAddWindow(ImGuiDockNode* node, ImGuiWindow* window, bool add_to_tab_bar);
|
|
static void DockNodeMoveWindows(ImGuiDockNode* dst_node, ImGuiDockNode* src_node);
|
|
static void DockNodeMoveChildNodes(ImGuiDockNode* dst_node, ImGuiDockNode* src_node);
|
|
static ImGuiWindow* DockNodeFindWindowByID(ImGuiDockNode* node, ImGuiID id);
|
|
static void DockNodeApplyPosSizeToWindows(ImGuiDockNode* node);
|
|
static void DockNodeRemoveWindow(ImGuiDockNode* node, ImGuiWindow* window, ImGuiID save_dock_id);
|
|
static void DockNodeHideHostWindow(ImGuiDockNode* node);
|
|
static void DockNodeUpdate(ImGuiDockNode* node);
|
|
static void DockNodeUpdateForRootNode(ImGuiDockNode* node);
|
|
static void DockNodeUpdateVisibleFlagAndInactiveChilds(ImGuiDockNode* node);
|
|
static void DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_window);
|
|
static void DockNodeAddTabBar(ImGuiDockNode* node);
|
|
static void DockNodeRemoveTabBar(ImGuiDockNode* node);
|
|
static ImGuiID DockNodeUpdateWindowMenu(ImGuiDockNode* node, ImGuiTabBar* tab_bar);
|
|
static void DockNodeUpdateVisibleFlag(ImGuiDockNode* node);
|
|
static void DockNodeStartMouseMovingWindow(ImGuiDockNode* node, ImGuiWindow* window);
|
|
static bool DockNodeIsDropAllowed(ImGuiWindow* host_window, ImGuiWindow* payload_window);
|
|
static void DockNodePreviewDockSetup(ImGuiWindow* host_window,
|
|
ImGuiDockNode* host_node,
|
|
ImGuiWindow* payload_window,
|
|
ImGuiDockPreviewData* preview_data,
|
|
bool is_explicit_target,
|
|
bool is_outer_docking);
|
|
static void DockNodePreviewDockRender(ImGuiWindow* host_window,
|
|
ImGuiDockNode* host_node,
|
|
ImGuiWindow* payload_window,
|
|
const ImGuiDockPreviewData* preview_data);
|
|
static void DockNodeCalcTabBarLayout(const ImGuiDockNode* node,
|
|
ImRect* out_title_rect,
|
|
ImRect* out_tab_bar_rect,
|
|
ImVec2* out_window_menu_button_pos,
|
|
ImVec2* out_close_button_pos);
|
|
static void DockNodeCalcSplitRects(ImVec2& pos_old,
|
|
ImVec2& size_old,
|
|
ImVec2& pos_new,
|
|
ImVec2& size_new,
|
|
ImGuiDir dir,
|
|
ImVec2 size_new_desired);
|
|
static bool DockNodeCalcDropRectsAndTestMousePos(const ImRect& parent,
|
|
ImGuiDir dir,
|
|
ImRect& out_draw,
|
|
bool outer_docking,
|
|
ImVec2* test_mouse_pos);
|
|
static const char* DockNodeGetHostWindowTitle(ImGuiDockNode* node, char* buf, int buf_size)
|
|
{
|
|
ImFormatString(buf, buf_size, "##DockNode_%02X", node->ID);
|
|
return buf;
|
|
}
|
|
static int DockNodeGetTabOrder(ImGuiWindow* window);
|
|
|
|
// ImGuiDockNode tree manipulations
|
|
static void DockNodeTreeSplit(ImGuiContext* ctx,
|
|
ImGuiDockNode* parent_node,
|
|
ImGuiAxis split_axis,
|
|
int split_first_child,
|
|
float split_ratio,
|
|
ImGuiDockNode* new_node);
|
|
static void DockNodeTreeMerge(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImGuiDockNode* merge_lead_child);
|
|
static void DockNodeTreeUpdatePosSize(ImGuiDockNode* node,
|
|
ImVec2 pos,
|
|
ImVec2 size,
|
|
bool only_write_to_marked_nodes = false);
|
|
static void DockNodeTreeUpdateSplitter(ImGuiDockNode* node);
|
|
static ImGuiDockNode* DockNodeTreeFindVisibleNodeByPos(ImGuiDockNode* node, ImVec2 pos);
|
|
static ImGuiDockNode* DockNodeTreeFindFallbackLeafNode(ImGuiDockNode* node);
|
|
|
|
// Settings
|
|
static void DockSettingsRenameNodeReferences(ImGuiID old_node_id, ImGuiID new_node_id);
|
|
static void DockSettingsRemoveNodeReferences(ImGuiID* node_ids, int node_ids_count);
|
|
static ImGuiDockNodeSettings* DockSettingsFindNodeSettings(ImGuiContext* ctx, ImGuiID node_id);
|
|
static void DockSettingsHandler_ClearAll(ImGuiContext*, ImGuiSettingsHandler*);
|
|
static void DockSettingsHandler_ApplyAll(ImGuiContext*, ImGuiSettingsHandler*);
|
|
static void* DockSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name);
|
|
static void DockSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line);
|
|
static void DockSettingsHandler_WriteAll(ImGuiContext* imgui_ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf);
|
|
} // namespace ImGui
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Docking: ImGuiDockContext
|
|
//-----------------------------------------------------------------------------
|
|
// The lifetime model is different from the one of regular windows: we always create a ImGuiDockNode for each
|
|
// ImGuiDockNodeSettings, or we always hold the entire docking node tree. Nodes are frequently hidden, e.g. if the
|
|
// window(s) or child nodes they host are not active. At boot time only, we run a simple GC to remove nodes that have no
|
|
// references. Because dock node settings (which are small, contiguous structures) are always mirrored by their
|
|
// corresponding dock nodes (more complete structures), we can also very easily recreate the nodes from scratch given
|
|
// the settings data (this is what DockContextRebuild() does). This is convenient as docking reconfiguration can be
|
|
// implemented by mostly poking at the simpler settings data.
|
|
//-----------------------------------------------------------------------------
|
|
// - DockContextInitialize()
|
|
// - DockContextShutdown()
|
|
// - DockContextClearNodes()
|
|
// - DockContextRebuildNodes()
|
|
// - DockContextNewFrameUpdateUndocking()
|
|
// - DockContextNewFrameUpdateDocking()
|
|
// - DockContextFindNodeByID()
|
|
// - DockContextBindNodeToWindow()
|
|
// - DockContextGenNodeID()
|
|
// - DockContextAddNode()
|
|
// - DockContextRemoveNode()
|
|
// - ImGuiDockContextPruneNodeData
|
|
// - DockContextPruneUnusedSettingsNodes()
|
|
// - DockContextBuildNodesFromSettings()
|
|
// - DockContextBuildAddWindowsToNodes()
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void ImGui::DockContextInitialize(ImGuiContext* ctx)
|
|
{
|
|
ImGuiContext& g = *ctx;
|
|
|
|
// Add .ini handle for persistent docking data
|
|
ImGuiSettingsHandler ini_handler;
|
|
ini_handler.TypeName = "Docking";
|
|
ini_handler.TypeHash = ImHashStr("Docking");
|
|
ini_handler.ClearAllFn = DockSettingsHandler_ClearAll;
|
|
ini_handler.ReadInitFn = DockSettingsHandler_ClearAll; // Also clear on read
|
|
ini_handler.ReadOpenFn = DockSettingsHandler_ReadOpen;
|
|
ini_handler.ReadLineFn = DockSettingsHandler_ReadLine;
|
|
ini_handler.ApplyAllFn = DockSettingsHandler_ApplyAll;
|
|
ini_handler.WriteAllFn = DockSettingsHandler_WriteAll;
|
|
g.SettingsHandlers.push_back(ini_handler);
|
|
}
|
|
|
|
void ImGui::DockContextShutdown(ImGuiContext* ctx)
|
|
{
|
|
ImGuiDockContext* dc = &ctx->DockContext;
|
|
for (int n = 0; n < dc->Nodes.Data.Size; n++)
|
|
if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p)
|
|
IM_DELETE(node);
|
|
}
|
|
|
|
void ImGui::DockContextClearNodes(ImGuiContext* ctx, ImGuiID root_id, bool clear_settings_refs)
|
|
{
|
|
IM_UNUSED(ctx);
|
|
IM_ASSERT(ctx == GImGui);
|
|
DockBuilderRemoveNodeDockedWindows(root_id, clear_settings_refs);
|
|
DockBuilderRemoveNodeChildNodes(root_id);
|
|
}
|
|
|
|
// [DEBUG] This function also acts as a defacto test to make sure we can rebuild from scratch without a glitch
|
|
// (Different from DockSettingsHandler_ClearAll() + DockSettingsHandler_ApplyAll() because this reuses current
|
|
// settings!)
|
|
void ImGui::DockContextRebuildNodes(ImGuiContext* ctx)
|
|
{
|
|
IMGUI_DEBUG_LOG_DOCKING("DockContextRebuild()\n");
|
|
ImGuiDockContext* dc = &ctx->DockContext;
|
|
SaveIniSettingsToMemory();
|
|
ImGuiID root_id = 0; // Rebuild all
|
|
DockContextClearNodes(ctx, root_id, false);
|
|
DockContextBuildNodesFromSettings(ctx, dc->NodesSettings.Data, dc->NodesSettings.Size);
|
|
DockContextBuildAddWindowsToNodes(ctx, root_id);
|
|
}
|
|
|
|
// Docking context update function, called by NewFrame()
|
|
void ImGui::DockContextNewFrameUpdateUndocking(ImGuiContext* ctx)
|
|
{
|
|
ImGuiContext& g = *ctx;
|
|
ImGuiDockContext* dc = &ctx->DockContext;
|
|
if (!(g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable))
|
|
{
|
|
if (dc->Nodes.Data.Size > 0 || dc->Requests.Size > 0)
|
|
DockContextClearNodes(ctx, 0, true);
|
|
return;
|
|
}
|
|
|
|
// Setting NoSplit at runtime merges all nodes
|
|
if (g.IO.ConfigDockingNoSplit)
|
|
for (int n = 0; n < dc->Nodes.Data.Size; n++)
|
|
if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p)
|
|
if (node->IsRootNode() && node->IsSplitNode())
|
|
{
|
|
DockBuilderRemoveNodeChildNodes(node->ID);
|
|
// dc->WantFullRebuild = true;
|
|
}
|
|
|
|
// Process full rebuild
|
|
# if 0
|
|
if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_C)))
|
|
dc->WantFullRebuild = true;
|
|
# endif
|
|
if (dc->WantFullRebuild)
|
|
{
|
|
DockContextRebuildNodes(ctx);
|
|
dc->WantFullRebuild = false;
|
|
}
|
|
|
|
// Process Undocking requests (we need to process them _before_ the UpdateMouseMovingWindowNewFrame call in
|
|
// NewFrame)
|
|
for (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;
|
|
|
|
// [DEBUG] Store hovered dock node.
|
|
// We could in theory use DockNodeTreeFindVisibleNodeByPos() on the root host dock node, but using ->DockNode is a
|
|
// good shortcut. Note this is mostly a debug thing and isn't actually used for docking target, because docking
|
|
// involve more detailed filtering.
|
|
g.HoveredDockNode = NULL;
|
|
if (ImGuiWindow* hovered_window = g.HoveredWindowUnderMovingWindow)
|
|
{
|
|
if (hovered_window->DockNodeAsHost)
|
|
g.HoveredDockNode = DockNodeTreeFindVisibleNodeByPos(hovered_window->DockNodeAsHost, g.IO.MousePos);
|
|
else if (hovered_window->RootWindow->DockNode)
|
|
g.HoveredDockNode = hovered_window->RootWindow->DockNode;
|
|
}
|
|
|
|
// 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->IsFloatingNode())
|
|
DockNodeUpdate(node);
|
|
}
|
|
|
|
static ImGuiDockNode* ImGui::DockContextFindNodeByID(ImGuiContext* ctx, ImGuiID id)
|
|
{
|
|
return (ImGuiDockNode*)ctx->DockContext.Nodes.GetVoidPtr(id);
|
|
}
|
|
|
|
ImGuiID ImGui::DockContextGenNodeID(ImGuiContext* ctx)
|
|
{
|
|
// Generate an ID for new node (the exact ID value doesn't matter as long as it is not already used)
|
|
// FIXME-OPT FIXME-DOCK: This is suboptimal, even if the node count is small enough not to be a worry.0
|
|
// We should poke in ctx->Nodes to find a suitable ID faster. Even more so trivial that ctx->Nodes lookup is already
|
|
// sorted.
|
|
ImGuiID id = 0x0001;
|
|
while (DockContextFindNodeByID(ctx, id) != NULL)
|
|
id++;
|
|
return id;
|
|
}
|
|
|
|
static ImGuiDockNode* ImGui::DockContextAddNode(ImGuiContext* ctx, ImGuiID id)
|
|
{
|
|
// Generate an ID for the new node (the exact ID value doesn't matter as long as it is not already used) and add the
|
|
// first window.
|
|
if (id == 0)
|
|
id = DockContextGenNodeID(ctx);
|
|
else
|
|
IM_ASSERT(DockContextFindNodeByID(ctx, id) == NULL);
|
|
|
|
// We don't set node->LastFrameAlive on construction. Nodes are always created at all time to reflect .ini settings!
|
|
IMGUI_DEBUG_LOG_DOCKING("DockContextAddNode 0x%08X\n", id);
|
|
ImGuiDockNode* node = IM_NEW(ImGuiDockNode)(id);
|
|
ctx->DockContext.Nodes.SetVoidPtr(node->ID, node);
|
|
return node;
|
|
}
|
|
|
|
static void ImGui::DockContextRemoveNode(ImGuiContext* ctx, ImGuiDockNode* node, bool merge_sibling_into_parent_node)
|
|
{
|
|
ImGuiContext& g = *ctx;
|
|
ImGuiDockContext* dc = &ctx->DockContext;
|
|
|
|
IMGUI_DEBUG_LOG_DOCKING("DockContextRemoveNode 0x%08X\n", node->ID);
|
|
IM_ASSERT(DockContextFindNodeByID(ctx, node->ID) == node);
|
|
IM_ASSERT(node->ChildNodes[0] == NULL && node->ChildNodes[1] == NULL);
|
|
IM_ASSERT(node->Windows.Size == 0);
|
|
|
|
if (node->HostWindow)
|
|
node->HostWindow->DockNodeAsHost = NULL;
|
|
|
|
ImGuiDockNode* parent_node = node->ParentNode;
|
|
const bool merge = (merge_sibling_into_parent_node && parent_node != NULL);
|
|
if (merge)
|
|
{
|
|
IM_ASSERT(parent_node->ChildNodes[0] == node || parent_node->ChildNodes[1] == node);
|
|
ImGuiDockNode* sibling_node =
|
|
(parent_node->ChildNodes[0] == node ? parent_node->ChildNodes[1] : parent_node->ChildNodes[0]);
|
|
DockNodeTreeMerge(&g, parent_node, sibling_node);
|
|
}
|
|
else
|
|
{
|
|
for (int n = 0; parent_node && n < IM_ARRAYSIZE(parent_node->ChildNodes); n++)
|
|
if (parent_node->ChildNodes[n] == node)
|
|
node->ParentNode->ChildNodes[n] = NULL;
|
|
dc->Nodes.SetVoidPtr(node->ID, NULL);
|
|
IM_DELETE(node);
|
|
}
|
|
}
|
|
|
|
static int IMGUI_CDECL DockNodeComparerDepthMostFirst(const void* lhs, const void* rhs)
|
|
{
|
|
const ImGuiDockNode* a = *(const ImGuiDockNode* const*)lhs;
|
|
const ImGuiDockNode* b = *(const ImGuiDockNode* const*)rhs;
|
|
return ImGui::DockNodeGetDepth(b) - ImGui::DockNodeGetDepth(a);
|
|
}
|
|
|
|
// Pre C++0x doesn't allow us to use a function-local type (without linkage) as template parameter, so we moved this
|
|
// here.
|
|
struct ImGuiDockContextPruneNodeData
|
|
{
|
|
int CountWindows, CountChildWindows, CountChildNodes;
|
|
ImGuiID RootId;
|
|
ImGuiDockContextPruneNodeData()
|
|
{
|
|
CountWindows = CountChildWindows = CountChildNodes = 0;
|
|
RootId = 0;
|
|
}
|
|
};
|
|
|
|
// Garbage collect unused nodes (run once at init time)
|
|
static void ImGui::DockContextPruneUnusedSettingsNodes(ImGuiContext* ctx)
|
|
{
|
|
ImGuiContext& g = *ctx;
|
|
ImGuiDockContext* dc = &ctx->DockContext;
|
|
IM_ASSERT(g.Windows.Size == 0);
|
|
|
|
ImPool<ImGuiDockContextPruneNodeData> pool;
|
|
pool.Reserve(dc->NodesSettings.Size);
|
|
|
|
// Count child nodes and compute RootID
|
|
for (int settings_n = 0; settings_n < dc->NodesSettings.Size; settings_n++)
|
|
{
|
|
ImGuiDockNodeSettings* settings = &dc->NodesSettings[settings_n];
|
|
ImGuiDockContextPruneNodeData* parent_data = settings->ParentNodeId ? pool.GetByKey(settings->ParentNodeId) : 0;
|
|
pool.GetOrAddByKey(settings->ID)->RootId = parent_data ? parent_data->RootId : settings->ID;
|
|
if (settings->ParentNodeId)
|
|
pool.GetOrAddByKey(settings->ParentNodeId)->CountChildNodes++;
|
|
}
|
|
|
|
// Count reference to dock ids from dockspaces
|
|
// We track the 'auto-DockNode <- manual-Window <- manual-DockSpace' in order to avoid 'auto-DockNode' being ditched
|
|
// by DockContextPruneUnusedSettingsNodes()
|
|
for (int settings_n = 0; settings_n < dc->NodesSettings.Size; settings_n++)
|
|
{
|
|
ImGuiDockNodeSettings* settings = &dc->NodesSettings[settings_n];
|
|
if (settings->ParentWindowId != 0)
|
|
if (ImGuiWindowSettings* window_settings = FindWindowSettings(settings->ParentWindowId))
|
|
if (window_settings->DockId)
|
|
if (ImGuiDockContextPruneNodeData* data = pool.GetByKey(window_settings->DockId))
|
|
data->CountChildNodes++;
|
|
}
|
|
|
|
// Count reference to dock ids from window settings
|
|
// We guard against the possibility of an invalid .ini file (RootID may point to a missing node)
|
|
for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL;
|
|
settings = g.SettingsWindows.next_chunk(settings))
|
|
if (ImGuiID dock_id = settings->DockId)
|
|
if (ImGuiDockContextPruneNodeData* data = pool.GetByKey(dock_id))
|
|
{
|
|
data->CountWindows++;
|
|
if (ImGuiDockContextPruneNodeData* data_root =
|
|
(data->RootId == dock_id) ? data : pool.GetByKey(data->RootId))
|
|
data_root->CountChildWindows++;
|
|
}
|
|
|
|
// Prune
|
|
for (int settings_n = 0; settings_n < dc->NodesSettings.Size; settings_n++)
|
|
{
|
|
ImGuiDockNodeSettings* settings = &dc->NodesSettings[settings_n];
|
|
ImGuiDockContextPruneNodeData* data = pool.GetByKey(settings->ID);
|
|
if (data->CountWindows > 1)
|
|
continue;
|
|
ImGuiDockContextPruneNodeData* data_root = (data->RootId == settings->ID) ? data : pool.GetByKey(data->RootId);
|
|
|
|
bool remove = false;
|
|
remove |= (data->CountWindows == 1 && settings->ParentNodeId == 0 && data->CountChildNodes == 0 &&
|
|
!(settings->Flags & ImGuiDockNodeFlags_CentralNode)); // Floating root node with only 1 window
|
|
remove |= (data->CountWindows == 0 && settings->ParentNodeId == 0 &&
|
|
data->CountChildNodes == 0); // Leaf nodes with 0 window
|
|
remove |= (data_root->CountChildWindows == 0);
|
|
if (remove)
|
|
{
|
|
IMGUI_DEBUG_LOG_DOCKING("DockContextPruneUnusedSettingsNodes: Prune 0x%08X\n", settings->ID);
|
|
DockSettingsRemoveNodeReferences(&settings->ID, 1);
|
|
settings->ID = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ImGui::DockContextBuildNodesFromSettings(ImGuiContext* ctx,
|
|
ImGuiDockNodeSettings* node_settings_array,
|
|
int node_settings_count)
|
|
{
|
|
// Build nodes
|
|
for (int node_n = 0; node_n < node_settings_count; node_n++)
|
|
{
|
|
ImGuiDockNodeSettings* settings = &node_settings_array[node_n];
|
|
if (settings->ID == 0)
|
|
continue;
|
|
ImGuiDockNode* node = DockContextAddNode(ctx, settings->ID);
|
|
node->ParentNode = settings->ParentNodeId ? DockContextFindNodeByID(ctx, settings->ParentNodeId) : NULL;
|
|
node->Pos = ImVec2(settings->Pos.x, settings->Pos.y);
|
|
node->Size = ImVec2(settings->Size.x, settings->Size.y);
|
|
node->SizeRef = ImVec2(settings->SizeRef.x, settings->SizeRef.y);
|
|
node->AuthorityForPos = node->AuthorityForSize = node->AuthorityForViewport = ImGuiDataAuthority_DockNode;
|
|
if (node->ParentNode && node->ParentNode->ChildNodes[0] == NULL)
|
|
node->ParentNode->ChildNodes[0] = node;
|
|
else if (node->ParentNode && node->ParentNode->ChildNodes[1] == NULL)
|
|
node->ParentNode->ChildNodes[1] = node;
|
|
node->SelectedTabId = settings->SelectedTabId;
|
|
node->SplitAxis = (ImGuiAxis)settings->SplitAxis;
|
|
node->SetLocalFlags(settings->Flags & ImGuiDockNodeFlags_SavedFlagsMask_);
|
|
|
|
// Bind host window immediately if it already exist (in case of a rebuild)
|
|
// This is useful as the RootWindowForTitleBarHighlight links necessary to highlight the currently focused node
|
|
// requires node->HostWindow to be set.
|
|
char host_window_title[20];
|
|
ImGuiDockNode* root_node = DockNodeGetRootNode(node);
|
|
node->HostWindow =
|
|
FindWindowByName(DockNodeGetHostWindowTitle(root_node, host_window_title, IM_ARRAYSIZE(host_window_title)));
|
|
}
|
|
}
|
|
|
|
void ImGui::DockContextBuildAddWindowsToNodes(ImGuiContext* ctx, ImGuiID root_id)
|
|
{
|
|
// Rebind all windows to nodes (they can also lazily rebind but we'll have a visible glitch during the first frame)
|
|
ImGuiContext& g = *ctx;
|
|
for (int n = 0; n < g.Windows.Size; n++)
|
|
{
|
|
ImGuiWindow* window = g.Windows[n];
|
|
if (window->DockId == 0 || window->LastFrameActive < g.FrameCount - 1)
|
|
continue;
|
|
if (window->DockNode != NULL)
|
|
continue;
|
|
|
|
ImGuiDockNode* node = DockContextFindNodeByID(ctx, window->DockId);
|
|
IM_ASSERT(node != NULL); // This should have been called after DockContextBuildNodesFromSettings()
|
|
if (root_id == 0 || DockNodeGetRootNode(node)->ID == root_id)
|
|
DockNodeAddWindow(node, window, true);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Docking: ImGuiDockContext Docking/Undocking functions
|
|
//-----------------------------------------------------------------------------
|
|
// - DockContextQueueDock()
|
|
// - DockContextQueueUndockWindow()
|
|
// - DockContextQueueUndockNode()
|
|
// - DockContextQueueNotifyRemovedNode()
|
|
// - DockContextProcessDock()
|
|
// - DockContextProcessUndockWindow()
|
|
// - DockContextProcessUndockNode()
|
|
// - DockContextCalcDropPosForDocking()
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void ImGui::DockContextQueueDock(ImGuiContext* ctx,
|
|
ImGuiWindow* target,
|
|
ImGuiDockNode* target_node,
|
|
ImGuiWindow* payload,
|
|
ImGuiDir split_dir,
|
|
float split_ratio,
|
|
bool split_outer)
|
|
{
|
|
IM_ASSERT(target != payload);
|
|
ImGuiDockRequest req;
|
|
req.Type = ImGuiDockRequestType_Dock;
|
|
req.DockTargetWindow = target;
|
|
req.DockTargetNode = target_node;
|
|
req.DockPayload = payload;
|
|
req.DockSplitDir = split_dir;
|
|
req.DockSplitRatio = split_ratio;
|
|
req.DockSplitOuter = split_outer;
|
|
ctx->DockContext.Requests.push_back(req);
|
|
}
|
|
|
|
void ImGui::DockContextQueueUndockWindow(ImGuiContext* ctx, ImGuiWindow* window)
|
|
{
|
|
ImGuiDockRequest req;
|
|
req.Type = ImGuiDockRequestType_Undock;
|
|
req.UndockTargetWindow = window;
|
|
ctx->DockContext.Requests.push_back(req);
|
|
}
|
|
|
|
void ImGui::DockContextQueueUndockNode(ImGuiContext* ctx, ImGuiDockNode* node)
|
|
{
|
|
ImGuiDockRequest req;
|
|
req.Type = ImGuiDockRequestType_Undock;
|
|
req.UndockTargetNode = node;
|
|
ctx->DockContext.Requests.push_back(req);
|
|
}
|
|
|
|
void ImGui::DockContextQueueNotifyRemovedNode(ImGuiContext* ctx, ImGuiDockNode* node)
|
|
{
|
|
ImGuiDockContext* dc = &ctx->DockContext;
|
|
for (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;
|
|
IM_UNUSED(g);
|
|
|
|
ImGuiWindow* payload_window = req->DockPayload; // Optional
|
|
ImGuiWindow* target_window = req->DockTargetWindow;
|
|
ImGuiDockNode* node = req->DockTargetNode;
|
|
if (payload_window)
|
|
IMGUI_DEBUG_LOG_DOCKING("DockContextProcessDock node 0x%08X target '%s' dock window '%s', split_dir %d\n",
|
|
node ? node->ID : 0, target_window ? target_window->Name : "NULL",
|
|
payload_window ? payload_window->Name : "NULL", req->DockSplitDir);
|
|
else
|
|
IMGUI_DEBUG_LOG_DOCKING("DockContextProcessDock node 0x%08X, split_dir %d\n", node ? node->ID : 0,
|
|
req->DockSplitDir);
|
|
|
|
// Decide which Tab will be selected at the end of the operation
|
|
ImGuiID next_selected_id = 0;
|
|
ImGuiDockNode* payload_node = NULL;
|
|
if (payload_window)
|
|
{
|
|
payload_node = payload_window->DockNodeAsHost;
|
|
payload_window->DockNodeAsHost = NULL; // Important to clear this as the node will have its life as a child
|
|
// which might be merged/deleted later.
|
|
if (payload_node && payload_node->IsLeafNode())
|
|
next_selected_id = payload_node->TabBar->NextSelectedTabId ? payload_node->TabBar->NextSelectedTabId
|
|
: payload_node->TabBar->SelectedTabId;
|
|
if (payload_node == NULL)
|
|
next_selected_id = payload_window->ID;
|
|
}
|
|
|
|
// FIXME-DOCK: When we are trying to dock an existing single-window node into a loose window, transfer Node ID as
|
|
// well When processing an interactive split, usually LastFrameAlive will be < g.FrameCount. But DockBuilder
|
|
// operations can make it ==.
|
|
if (node)
|
|
IM_ASSERT(node->LastFrameAlive <= g.FrameCount);
|
|
if (node && target_window && node == target_window->DockNodeAsHost)
|
|
IM_ASSERT(node->Windows.Size > 0 || node->IsSplitNode() || node->IsCentralNode());
|
|
|
|
// Create new node and add existing window to it
|
|
if (node == NULL)
|
|
{
|
|
node = DockContextAddNode(ctx, 0);
|
|
node->Pos = target_window->Pos;
|
|
node->Size = target_window->Size;
|
|
if (target_window->DockNodeAsHost == NULL)
|
|
{
|
|
DockNodeAddWindow(node, target_window, true);
|
|
node->TabBar->Tabs[0].Flags &= ~ImGuiTabItemFlags_Unsorted;
|
|
target_window->DockIsActive = true;
|
|
}
|
|
}
|
|
|
|
ImGuiDir split_dir = req->DockSplitDir;
|
|
if (split_dir != ImGuiDir_None)
|
|
{
|
|
// Split into two, one side will be our payload node unless we are dropping a loose window
|
|
const ImGuiAxis split_axis =
|
|
(split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Right) ? ImGuiAxis_X : ImGuiAxis_Y;
|
|
const int split_inheritor_child_idx = (split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Up)
|
|
? 1
|
|
: 0; // Current contents will be moved to the opposite side
|
|
const float split_ratio = req->DockSplitRatio;
|
|
DockNodeTreeSplit(ctx, node, split_axis, split_inheritor_child_idx, split_ratio,
|
|
payload_node); // payload_node may be NULL here!
|
|
ImGuiDockNode* new_node = node->ChildNodes[split_inheritor_child_idx ^ 1];
|
|
new_node->HostWindow = node->HostWindow;
|
|
node = new_node;
|
|
}
|
|
node->SetLocalFlags(node->LocalFlags & ~ImGuiDockNodeFlags_HiddenTabBar);
|
|
|
|
if (node != payload_node)
|
|
{
|
|
// Create tab bar before we call DockNodeMoveWindows (which would attempt to move the old tab-bar, which would
|
|
// lead us to payload tabs wrongly appearing before target tabs!)
|
|
if (node->Windows.Size > 0 && node->TabBar == NULL)
|
|
{
|
|
DockNodeAddTabBar(node);
|
|
for (int n = 0; n < node->Windows.Size; n++)
|
|
TabBarAddTab(node->TabBar, ImGuiTabItemFlags_None, node->Windows[n]);
|
|
}
|
|
|
|
if (payload_node != NULL)
|
|
{
|
|
// Transfer full payload node (with 1+ child windows or child nodes)
|
|
if (payload_node->IsSplitNode())
|
|
{
|
|
if (node->Windows.Size > 0)
|
|
{
|
|
// We can dock a split payload into a node that already has windows _only_ if our payload is a node
|
|
// tree with a single visible node. In this situation, we move the windows of the target node into
|
|
// the currently visible node of the payload. This allows us to preserve some of the underlying dock
|
|
// tree settings nicely.
|
|
IM_ASSERT(payload_node->OnlyNodeWithWindows !=
|
|
NULL); // The docking should have been blocked by DockNodePreviewDockSetup() early on and
|
|
// never submitted.
|
|
ImGuiDockNode* visible_node = payload_node->OnlyNodeWithWindows;
|
|
if (visible_node->TabBar)
|
|
IM_ASSERT(visible_node->TabBar->Tabs.Size > 0);
|
|
DockNodeMoveWindows(node, visible_node);
|
|
DockNodeMoveWindows(visible_node, node);
|
|
DockSettingsRenameNodeReferences(node->ID, visible_node->ID);
|
|
}
|
|
if (node->IsCentralNode())
|
|
{
|
|
// Central node property needs to be moved to a leaf node, pick the last focused one.
|
|
// FIXME-DOCK: If we had to transfer other flags here, what would the policy be?
|
|
ImGuiDockNode* last_focused_node = DockContextFindNodeByID(ctx, payload_node->LastFocusedNodeId);
|
|
IM_ASSERT(last_focused_node != NULL);
|
|
ImGuiDockNode* last_focused_root_node = DockNodeGetRootNode(last_focused_node);
|
|
IM_ASSERT(last_focused_root_node == DockNodeGetRootNode(payload_node));
|
|
last_focused_node->SetLocalFlags(last_focused_node->LocalFlags |= ImGuiDockNodeFlags_CentralNode);
|
|
node->SetLocalFlags(node->LocalFlags & ~ImGuiDockNodeFlags_CentralNode);
|
|
last_focused_root_node->CentralNode = last_focused_node;
|
|
}
|
|
|
|
IM_ASSERT(node->Windows.Size == 0);
|
|
DockNodeMoveChildNodes(node, payload_node);
|
|
}
|
|
else
|
|
{
|
|
const ImGuiID payload_dock_id = payload_node->ID;
|
|
DockNodeMoveWindows(node, payload_node);
|
|
DockSettingsRenameNodeReferences(payload_dock_id, node->ID);
|
|
}
|
|
DockContextRemoveNode(ctx, payload_node, true);
|
|
}
|
|
else if (payload_window)
|
|
{
|
|
// Transfer single window
|
|
const ImGuiID payload_dock_id = payload_window->DockId;
|
|
node->VisibleWindow = payload_window;
|
|
DockNodeAddWindow(node, payload_window, true);
|
|
if (payload_dock_id != 0)
|
|
DockSettingsRenameNodeReferences(payload_dock_id, node->ID);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// When docking a floating single window node we want to reevaluate auto-hiding of the tab bar
|
|
node->WantHiddenTabBarUpdate = true;
|
|
}
|
|
|
|
// Update selection immediately
|
|
if (ImGuiTabBar* tab_bar = node->TabBar)
|
|
tab_bar->NextSelectedTabId = next_selected_id;
|
|
MarkIniSettingsDirty();
|
|
}
|
|
|
|
// Problem:
|
|
// Undocking a large (~full screen) window would leave it so large that the bottom right sizing corner would more
|
|
// than likely be off the screen and the window would be hard to resize to fit on screen. This can be particularly
|
|
// problematic with 'ConfigWindowsMoveFromTitleBarOnly=true' and/or with 'ConfigWindowsResizeFromEdges=false' as well
|
|
// (the later can be due to missing ImGuiBackendFlags_HasMouseCursors backend flag).
|
|
// Solution:
|
|
// When undocking a window we currently force its maximum size to 90% of the host viewport or monitor.
|
|
// Reevaluate this when we implement preserving docked/undocked size ("docking_wip/undocked_size" branch).
|
|
static ImVec2 FixLargeWindowsWhenUndocking(const ImVec2& size, ImGuiViewport* ref_viewport)
|
|
{
|
|
if (ref_viewport == NULL)
|
|
return size;
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
ImVec2 max_size = ImFloor(ref_viewport->WorkSize * 0.90f);
|
|
if (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable)
|
|
{
|
|
const ImGuiPlatformMonitor* monitor = ImGui::GetViewportPlatformMonitor(ref_viewport);
|
|
max_size = ImFloor(monitor->WorkSize * 0.90f);
|
|
}
|
|
return ImMin(size, max_size);
|
|
}
|
|
|
|
void ImGui::DockContextProcessUndockWindow(ImGuiContext* ctx, ImGuiWindow* window, bool clear_persistent_docking_ref)
|
|
{
|
|
IMGUI_DEBUG_LOG_DOCKING("DockContextProcessUndockWindow window '%s', clear_persistent_docking_ref = %d\n",
|
|
window->Name, clear_persistent_docking_ref);
|
|
IM_UNUSED(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->DockNodeIsVisible = window->DockTabIsVisible = false;
|
|
window->Size = window->SizeFull = FixLargeWindowsWhenUndocking(window->SizeFull, window->Viewport);
|
|
|
|
MarkIniSettingsDirty();
|
|
}
|
|
|
|
void ImGui::DockContextProcessUndockNode(ImGuiContext* ctx, ImGuiDockNode* node)
|
|
{
|
|
IMGUI_DEBUG_LOG_DOCKING("DockContextProcessUndockNode node %08X\n", node->ID);
|
|
IM_ASSERT(node->IsLeafNode());
|
|
IM_ASSERT(node->Windows.Size >= 1);
|
|
|
|
if (node->IsRootNode() || node->IsCentralNode())
|
|
{
|
|
// In the case of a root node or central node, the node will have to stay in place. Create a new node to receive
|
|
// the payload.
|
|
ImGuiDockNode* new_node = DockContextAddNode(ctx, 0);
|
|
new_node->Pos = node->Pos;
|
|
new_node->Size = node->Size;
|
|
new_node->SizeRef = node->SizeRef;
|
|
DockNodeMoveWindows(new_node, node);
|
|
DockSettingsRenameNodeReferences(node->ID, new_node->ID);
|
|
for (int n = 0; n < new_node->Windows.Size; n++)
|
|
UpdateWindowParentAndRootLinks(new_node->Windows[n], new_node->Windows[n]->Flags, NULL);
|
|
node = new_node;
|
|
}
|
|
else
|
|
{
|
|
// Otherwise extract our node and merge our sibling back into the parent node.
|
|
IM_ASSERT(node->ParentNode->ChildNodes[0] == node || node->ParentNode->ChildNodes[1] == node);
|
|
int index_in_parent = (node->ParentNode->ChildNodes[0] == node) ? 0 : 1;
|
|
node->ParentNode->ChildNodes[index_in_parent] = NULL;
|
|
DockNodeTreeMerge(ctx, node->ParentNode, node->ParentNode->ChildNodes[index_in_parent ^ 1]);
|
|
node->ParentNode->AuthorityForViewport =
|
|
ImGuiDataAuthority_Window; // The node that stays in place keeps the viewport, so our newly dragged out
|
|
// node will create a new viewport
|
|
node->ParentNode = NULL;
|
|
}
|
|
node->AuthorityForPos = node->AuthorityForSize = ImGuiDataAuthority_DockNode;
|
|
node->Size = FixLargeWindowsWhenUndocking(node->Size, node->Windows[0]->Viewport);
|
|
node->WantMouseMove = true;
|
|
MarkIniSettingsDirty();
|
|
}
|
|
|
|
// This is mostly used for automation.
|
|
bool ImGui::DockContextCalcDropPosForDocking(ImGuiWindow* target,
|
|
ImGuiDockNode* target_node,
|
|
ImGuiWindow* payload,
|
|
ImGuiDir split_dir,
|
|
bool split_outer,
|
|
ImVec2* out_pos)
|
|
{
|
|
// In DockNodePreviewDockSetup() for a root central node instead of showing both "inner" and "outer" drop rects
|
|
// (which would be functionally identical) we only show the outer one. Reflect this here.
|
|
if (target_node && target_node->ParentNode == NULL && target_node->IsCentralNode() && split_dir != ImGuiDir_None)
|
|
split_outer = true;
|
|
ImGuiDockPreviewData split_data;
|
|
DockNodePreviewDockSetup(target, target_node, payload, &split_data, false, split_outer);
|
|
if (split_data.DropRectsDraw[split_dir + 1].IsInverted())
|
|
return false;
|
|
*out_pos = split_data.DropRectsDraw[split_dir + 1].GetCenter();
|
|
return true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Docking: ImGuiDockNode
|
|
//-----------------------------------------------------------------------------
|
|
// - DockNodeGetTabOrder()
|
|
// - DockNodeAddWindow()
|
|
// - DockNodeRemoveWindow()
|
|
// - DockNodeMoveChildNodes()
|
|
// - DockNodeMoveWindows()
|
|
// - DockNodeApplyPosSizeToWindows()
|
|
// - DockNodeHideHostWindow()
|
|
// - ImGuiDockNodeFindInfoResults
|
|
// - DockNodeFindInfo()
|
|
// - DockNodeFindWindowByID()
|
|
// - DockNodeUpdateVisibleFlagAndInactiveChilds()
|
|
// - DockNodeUpdateVisibleFlag()
|
|
// - DockNodeStartMouseMovingWindow()
|
|
// - DockNodeUpdate()
|
|
// - DockNodeUpdateWindowMenu()
|
|
// - DockNodeBeginAmendTabBar()
|
|
// - DockNodeEndAmendTabBar()
|
|
// - DockNodeUpdateTabBar()
|
|
// - DockNodeAddTabBar()
|
|
// - DockNodeRemoveTabBar()
|
|
// - DockNodeIsDropAllowedOne()
|
|
// - DockNodeIsDropAllowed()
|
|
// - DockNodeCalcTabBarLayout()
|
|
// - DockNodeCalcSplitRects()
|
|
// - DockNodeCalcDropRectsAndTestMousePos()
|
|
// - DockNodePreviewDockSetup()
|
|
// - DockNodePreviewDockRender()
|
|
//-----------------------------------------------------------------------------
|
|
|
|
ImGuiDockNode::ImGuiDockNode(ImGuiID id)
|
|
{
|
|
ID = id;
|
|
SharedFlags = LocalFlags = LocalFlagsInWindows = MergedFlags = ImGuiDockNodeFlags_None;
|
|
ParentNode = ChildNodes[0] = ChildNodes[1] = NULL;
|
|
TabBar = NULL;
|
|
SplitAxis = ImGuiAxis_None;
|
|
|
|
State = ImGuiDockNodeState_Unknown;
|
|
HostWindow = VisibleWindow = NULL;
|
|
CentralNode = OnlyNodeWithWindows = NULL;
|
|
LastFrameAlive = LastFrameActive = LastFrameFocused = -1;
|
|
LastFocusedNodeId = 0;
|
|
SelectedTabId = 0;
|
|
WantCloseTabId = 0;
|
|
AuthorityForPos = AuthorityForSize = ImGuiDataAuthority_DockNode;
|
|
AuthorityForViewport = ImGuiDataAuthority_Auto;
|
|
IsVisible = true;
|
|
IsFocused = HasCloseButton = HasWindowMenuButton = false;
|
|
WantCloseAll = WantLockSizeOnce = WantMouseMove = WantHiddenTabBarUpdate = WantHiddenTabBarToggle = false;
|
|
MarkedForPosSizeWrite = 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 DockNodeHideWindowDuringHostWindowCreation(ImGuiWindow* window)
|
|
{
|
|
window->Hidden = true;
|
|
window->HiddenFramesCanSkipItems = window->Active ? 1 : 2;
|
|
}
|
|
|
|
static void ImGui::DockNodeAddWindow(ImGuiDockNode* node, ImGuiWindow* window, bool add_to_tab_bar)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
(void)g;
|
|
if (window->DockNode)
|
|
{
|
|
// Can overwrite an existing window->DockNode (e.g. pointing to a disabled DockSpace node)
|
|
IM_ASSERT(window->DockNode->ID != node->ID);
|
|
DockNodeRemoveWindow(window->DockNode, window, 0);
|
|
}
|
|
IM_ASSERT(window->DockNode == NULL || window->DockNodeAsHost == NULL);
|
|
IMGUI_DEBUG_LOG_DOCKING("DockNodeAddWindow node 0x%08X window '%s'\n", node->ID, window->Name);
|
|
|
|
// If more than 2 windows appeared on the same frame leading to the creation of a new hosting window,
|
|
// we'll hide windows until the host window is ready. Hide the 1st window after its been output (so it is not
|
|
// visible for one frame). We will call DockNodeHideWindowDuringHostWindowCreation() on ourselves in Begin()
|
|
if (node->HostWindow == NULL && node->Windows.Size == 1 && node->Windows[0]->WasActive == false)
|
|
DockNodeHideWindowDuringHostWindowCreation(node->Windows[0]);
|
|
|
|
node->Windows.push_back(window);
|
|
node->WantHiddenTabBarUpdate = true;
|
|
window->DockNode = node;
|
|
window->DockId = node->ID;
|
|
window->DockIsActive = (node->Windows.Size > 1);
|
|
window->DockTabWantClose = false;
|
|
|
|
// When reactivating a node with one or two loose window, the window pos/size/viewport are authoritative over the
|
|
// node storage. In particular it is important we init the viewport from the first window so we don't create two
|
|
// viewports and drop one.
|
|
if (node->HostWindow == NULL && node->IsFloatingNode())
|
|
{
|
|
if (node->AuthorityForPos == ImGuiDataAuthority_Auto)
|
|
node->AuthorityForPos = ImGuiDataAuthority_Window;
|
|
if (node->AuthorityForSize == ImGuiDataAuthority_Auto)
|
|
node->AuthorityForSize = ImGuiDataAuthority_Window;
|
|
if (node->AuthorityForViewport == ImGuiDataAuthority_Auto)
|
|
node->AuthorityForViewport = ImGuiDataAuthority_Window;
|
|
}
|
|
|
|
// Add to tab bar if requested
|
|
if (add_to_tab_bar)
|
|
{
|
|
if (node->TabBar == NULL)
|
|
{
|
|
DockNodeAddTabBar(node);
|
|
node->TabBar->SelectedTabId = node->TabBar->NextSelectedTabId = node->SelectedTabId;
|
|
|
|
// Add existing windows
|
|
for (int n = 0; n < node->Windows.Size - 1; n++)
|
|
TabBarAddTab(node->TabBar, ImGuiTabItemFlags_None, node->Windows[n]);
|
|
}
|
|
TabBarAddTab(node->TabBar, ImGuiTabItemFlags_Unsorted, window);
|
|
}
|
|
|
|
DockNodeUpdateVisibleFlag(node);
|
|
|
|
// Update this without waiting for the next time we Begin() in the window, so our host window will have the proper
|
|
// title bar color on its first frame.
|
|
if (node->HostWindow)
|
|
UpdateWindowParentAndRootLinks(window, window->Flags | ImGuiWindowFlags_ChildWindow, node->HostWindow);
|
|
}
|
|
|
|
static void ImGui::DockNodeRemoveWindow(ImGuiDockNode* node, ImGuiWindow* window, ImGuiID save_dock_id)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(window->DockNode == node);
|
|
// IM_ASSERT(window->RootWindowDockTree == node->HostWindow);
|
|
// IM_ASSERT(window->LastFrameActive < g.FrameCount); // We may call this from Begin()
|
|
IM_ASSERT(save_dock_id == 0 || save_dock_id == node->ID);
|
|
IMGUI_DEBUG_LOG_DOCKING("DockNodeRemoveWindow node 0x%08X window '%s'\n", node->ID, window->Name);
|
|
|
|
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);
|
|
if (node->VisibleWindow == window)
|
|
node->VisibleWindow = NULL;
|
|
|
|
// Remove tab and possibly tab bar
|
|
node->WantHiddenTabBarUpdate = true;
|
|
if (node->TabBar)
|
|
{
|
|
TabBarRemoveTab(node->TabBar, window->ID);
|
|
const int tab_count_threshold_for_tab_bar = node->IsCentralNode() ? 1 : 2;
|
|
if (node->Windows.Size < tab_count_threshold_for_tab_bar)
|
|
DockNodeRemoveTabBar(node);
|
|
}
|
|
|
|
if (node->Windows.Size == 0 && !node->IsCentralNode() && !node->IsDockSpace() && window->DockId != node->ID)
|
|
{
|
|
// Automatic dock node delete themselves if they are not holding at least one tab
|
|
DockContextRemoveNode(&g, node, true);
|
|
return;
|
|
}
|
|
|
|
if (node->Windows.Size == 1 && !node->IsCentralNode() && node->HostWindow)
|
|
{
|
|
ImGuiWindow* remaining_window = node->Windows[0];
|
|
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;
|
|
}
|
|
remaining_window->Collapsed = node->HostWindow->Collapsed;
|
|
}
|
|
|
|
// Update visibility immediately is required so the DockNodeUpdateRemoveInactiveChilds() processing can reflect
|
|
// changes up the tree
|
|
DockNodeUpdateVisibleFlag(node);
|
|
}
|
|
|
|
static void ImGui::DockNodeMoveChildNodes(ImGuiDockNode* dst_node, ImGuiDockNode* src_node)
|
|
{
|
|
IM_ASSERT(dst_node->Windows.Size == 0);
|
|
dst_node->ChildNodes[0] = src_node->ChildNodes[0];
|
|
dst_node->ChildNodes[1] = src_node->ChildNodes[1];
|
|
if (dst_node->ChildNodes[0])
|
|
dst_node->ChildNodes[0]->ParentNode = dst_node;
|
|
if (dst_node->ChildNodes[1])
|
|
dst_node->ChildNodes[1]->ParentNode = dst_node;
|
|
dst_node->SplitAxis = src_node->SplitAxis;
|
|
dst_node->SizeRef = src_node->SizeRef;
|
|
src_node->ChildNodes[0] = src_node->ChildNodes[1] = NULL;
|
|
}
|
|
|
|
static void ImGui::DockNodeMoveWindows(ImGuiDockNode* dst_node, ImGuiDockNode* src_node)
|
|
{
|
|
// Insert tabs in the same orders as currently ordered (node->Windows isn't ordered)
|
|
IM_ASSERT(src_node && dst_node && dst_node != src_node);
|
|
ImGuiTabBar* src_tab_bar = src_node->TabBar;
|
|
if (src_tab_bar != NULL)
|
|
IM_ASSERT(src_node->Windows.Size <= src_node->TabBar->Tabs.Size);
|
|
|
|
// If the dst_node is empty we can just move the entire tab bar (to preserve selection, scrolling, etc.)
|
|
bool move_tab_bar = (src_tab_bar != NULL) && (dst_node->TabBar == NULL);
|
|
if (move_tab_bar)
|
|
{
|
|
dst_node->TabBar = src_node->TabBar;
|
|
src_node->TabBar = NULL;
|
|
}
|
|
|
|
for (int n = 0; n < src_node->Windows.Size; n++)
|
|
{
|
|
// DockNode's TabBar may have non-window Tabs manually appended by user
|
|
if (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;
|
|
DockNodeRemoveTabBar(src_node);
|
|
}
|
|
}
|
|
|
|
static void ImGui::DockNodeApplyPosSizeToWindows(ImGuiDockNode* node)
|
|
{
|
|
for (int n = 0; n < node->Windows.Size; n++)
|
|
{
|
|
SetWindowPos(node->Windows[n], node->Pos,
|
|
ImGuiCond_Always); // We don't assign directly to Pos because it can break the calculation of
|
|
// SizeContents on next frame
|
|
SetWindowSize(node->Windows[n], node->Size, ImGuiCond_Always);
|
|
}
|
|
}
|
|
|
|
static void ImGui::DockNodeHideHostWindow(ImGuiDockNode* node)
|
|
{
|
|
if (node->HostWindow)
|
|
{
|
|
if (node->HostWindow->DockNodeAsHost == node)
|
|
node->HostWindow->DockNodeAsHost = NULL;
|
|
node->HostWindow = NULL;
|
|
}
|
|
|
|
if (node->Windows.Size == 1)
|
|
{
|
|
node->VisibleWindow = node->Windows[0];
|
|
node->Windows[0]->DockIsActive = false;
|
|
}
|
|
|
|
if (node->TabBar)
|
|
DockNodeRemoveTabBar(node);
|
|
}
|
|
|
|
// Search function called once by root node in DockNodeUpdate()
|
|
struct ImGuiDockNodeFindInfoResults
|
|
{
|
|
ImGuiDockNode* CentralNode;
|
|
ImGuiDockNode* FirstNodeWithWindows;
|
|
int CountNodesWithWindows;
|
|
// ImGuiWindowClass WindowClassForMerges;
|
|
|
|
ImGuiDockNodeFindInfoResults()
|
|
{
|
|
CentralNode = FirstNodeWithWindows = NULL;
|
|
CountNodesWithWindows = 0;
|
|
}
|
|
};
|
|
|
|
static void DockNodeFindInfo(ImGuiDockNode* node, ImGuiDockNodeFindInfoResults* results)
|
|
{
|
|
if (node->Windows.Size > 0)
|
|
{
|
|
if (results->FirstNodeWithWindows == NULL)
|
|
results->FirstNodeWithWindows = node;
|
|
results->CountNodesWithWindows++;
|
|
}
|
|
if (node->IsCentralNode())
|
|
{
|
|
IM_ASSERT(results->CentralNode == NULL); // Should be only one
|
|
IM_ASSERT(node->IsLeafNode() &&
|
|
"If you get this assert: please submit .ini file + repro of actions leading to this.");
|
|
results->CentralNode = node;
|
|
}
|
|
if (results->CountNodesWithWindows > 1 && results->CentralNode != NULL)
|
|
return;
|
|
if (node->ChildNodes[0])
|
|
DockNodeFindInfo(node->ChildNodes[0], results);
|
|
if (node->ChildNodes[1])
|
|
DockNodeFindInfo(node->ChildNodes[1], results);
|
|
}
|
|
|
|
static ImGuiWindow* ImGui::DockNodeFindWindowByID(ImGuiDockNode* node, ImGuiID id)
|
|
{
|
|
IM_ASSERT(id != 0);
|
|
for (int n = 0; n < node->Windows.Size; n++)
|
|
if (node->Windows[n]->ID == id)
|
|
return node->Windows[n];
|
|
return NULL;
|
|
}
|
|
|
|
// - Remove inactive windows/nodes.
|
|
// - Update visibility flag.
|
|
static void ImGui::DockNodeUpdateVisibleFlagAndInactiveChilds(ImGuiDockNode* node)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(node->ParentNode == NULL || node->ParentNode->ChildNodes[0] == node ||
|
|
node->ParentNode->ChildNodes[1] == node);
|
|
|
|
// Inherit most flags
|
|
if (node->ParentNode)
|
|
node->SharedFlags = node->ParentNode->SharedFlags & ImGuiDockNodeFlags_SharedFlagsInheritMask_;
|
|
|
|
// Recurse into children
|
|
// There is the possibility that one of our child becoming empty will delete itself and moving its sibling contents
|
|
// into 'node'. If 'node->ChildNode[0]' delete itself, then 'node->ChildNode[1]->Windows' will be moved into 'node'
|
|
// If 'node->ChildNode[1]' delete itself, then 'node->ChildNode[0]->Windows' will be moved into 'node' and the
|
|
// "remove inactive windows" loop will have run twice on those windows (harmless)
|
|
if (node->ChildNodes[0])
|
|
DockNodeUpdateVisibleFlagAndInactiveChilds(node->ChildNodes[0]);
|
|
if (node->ChildNodes[1])
|
|
DockNodeUpdateVisibleFlagAndInactiveChilds(node->ChildNodes[1]);
|
|
|
|
// Remove inactive windows
|
|
// Merge node flags overrides stored in windows
|
|
node->LocalFlagsInWindows = ImGuiDockNodeFlags_None;
|
|
for (int window_n = 0; window_n < node->Windows.Size; window_n++)
|
|
{
|
|
ImGuiWindow* window = node->Windows[window_n];
|
|
IM_ASSERT(window->DockNode == node);
|
|
|
|
bool node_was_active = (node->LastFrameActive + 1 == g.FrameCount);
|
|
bool remove = false;
|
|
remove |= node_was_active && (window->LastFrameActive + 1 < g.FrameCount);
|
|
remove |= node_was_active && (node->WantCloseAll || node->WantCloseTabId == window->ID) &&
|
|
window->HasCloseButton &&
|
|
!(window->Flags & ImGuiWindowFlags_UnsavedDocument); // Submit all _expected_ closure from last frame
|
|
remove |= (window->DockTabWantClose);
|
|
if (remove)
|
|
{
|
|
window->DockTabWantClose = false;
|
|
if (node->Windows.Size == 1 && !node->IsCentralNode())
|
|
{
|
|
DockNodeHideHostWindow(node);
|
|
node->State = ImGuiDockNodeState_HostWindowHiddenBecauseSingleWindow;
|
|
DockNodeRemoveWindow(node, window, node->ID); // Will delete the node so it'll be invalid on return
|
|
return;
|
|
}
|
|
DockNodeRemoveWindow(node, window, node->ID);
|
|
window_n--;
|
|
}
|
|
else
|
|
{
|
|
// FIXME-DOCKING: Missing policies for conflict resolution, hence the "Experimental" tag on this.
|
|
// node->LocalFlagsInWindow &= ~window->WindowClass.DockNodeFlagsOverrideClear;
|
|
node->LocalFlagsInWindows |= window->WindowClass.DockNodeFlagsOverrideSet;
|
|
}
|
|
}
|
|
node->UpdateMergedFlags();
|
|
|
|
// Auto-hide tab bar option
|
|
ImGuiDockNodeFlags node_flags = node->MergedFlags;
|
|
if (node->WantHiddenTabBarUpdate && node->Windows.Size == 1 && (node_flags & ImGuiDockNodeFlags_AutoHideTabBar) &&
|
|
!node->IsHiddenTabBar())
|
|
node->WantHiddenTabBarToggle = true;
|
|
node->WantHiddenTabBarUpdate = false;
|
|
|
|
// Cancel toggling if we know our tab bar is enforced to be hidden at all times
|
|
if (node->WantHiddenTabBarToggle && node->VisibleWindow &&
|
|
(node->VisibleWindow->WindowClass.DockNodeFlagsOverrideSet & ImGuiDockNodeFlags_HiddenTabBar))
|
|
node->WantHiddenTabBarToggle = false;
|
|
|
|
// Apply toggles at a single point of the frame (here!)
|
|
if (node->Windows.Size > 1)
|
|
node->SetLocalFlags(node->LocalFlags & ~ImGuiDockNodeFlags_HiddenTabBar);
|
|
else if (node->WantHiddenTabBarToggle)
|
|
node->SetLocalFlags(node->LocalFlags ^ ImGuiDockNodeFlags_HiddenTabBar);
|
|
node->WantHiddenTabBarToggle = false;
|
|
|
|
DockNodeUpdateVisibleFlag(node);
|
|
}
|
|
|
|
static void ImGui::DockNodeUpdateVisibleFlag(ImGuiDockNode* node)
|
|
{
|
|
// Update visibility flag
|
|
bool is_visible = (node->ParentNode == NULL) ? node->IsDockSpace() : node->IsCentralNode();
|
|
is_visible |= (node->Windows.Size > 0);
|
|
is_visible |= (node->ChildNodes[0] && node->ChildNodes[0]->IsVisible);
|
|
is_visible |= (node->ChildNodes[1] && node->ChildNodes[1]->IsVisible);
|
|
node->IsVisible = is_visible;
|
|
}
|
|
|
|
static void ImGui::DockNodeStartMouseMovingWindow(ImGuiDockNode* node, ImGuiWindow* window)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(node->WantMouseMove == true);
|
|
StartMouseMovingWindow(window);
|
|
g.ActiveIdClickOffset = g.IO.MouseClickedPos[0] - node->Pos;
|
|
g.MovingWindow = window; // If we are docked into a non moveable root window, StartMouseMovingWindow() won't set
|
|
// g.MovingWindow. Override that decision.
|
|
node->WantMouseMove = false;
|
|
}
|
|
|
|
// Update CentralNode, OnlyNodeWithWindows, LastFocusedNodeID. Copy window class.
|
|
static void ImGui::DockNodeUpdateForRootNode(ImGuiDockNode* node)
|
|
{
|
|
DockNodeUpdateVisibleFlagAndInactiveChilds(node);
|
|
|
|
// FIXME-DOCK: Merge this scan into the one above.
|
|
// - Setup central node pointers
|
|
// - Find if there's only a single visible window in the hierarchy (in which case we need to display a regular title
|
|
// bar -> FIXME-DOCK: that last part is not done yet!)
|
|
ImGuiDockNodeFindInfoResults results;
|
|
DockNodeFindInfo(node, &results);
|
|
node->CentralNode = results.CentralNode;
|
|
node->OnlyNodeWithWindows = (results.CountNodesWithWindows == 1) ? results.FirstNodeWithWindows : NULL;
|
|
if (node->LastFocusedNodeId == 0 && results.FirstNodeWithWindows != NULL)
|
|
node->LastFocusedNodeId = results.FirstNodeWithWindows->ID;
|
|
|
|
// Copy the window class from of our first window so it can be used for proper dock filtering.
|
|
// When node has mixed windows, prioritize the class with the most constraint (DockingAllowUnclassed = false) as the
|
|
// reference to copy.
|
|
// FIXME-DOCK: We don't recurse properly, this code could be reworked to work from DockNodeUpdateScanRec.
|
|
if (ImGuiDockNode* first_node_with_windows = results.FirstNodeWithWindows)
|
|
{
|
|
node->WindowClass = first_node_with_windows->Windows[0]->WindowClass;
|
|
for (int n = 1; n < first_node_with_windows->Windows.Size; n++)
|
|
if (first_node_with_windows->Windows[n]->WindowClass.DockingAllowUnclassed == false)
|
|
{
|
|
node->WindowClass = first_node_with_windows->Windows[n]->WindowClass;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void DockNodeSetupHostWindow(ImGuiDockNode* node, ImGuiWindow* host_window)
|
|
{
|
|
// Remove ourselves from any previous different host window
|
|
// This can happen if a user mistakenly does (see #4295 for details):
|
|
// - N+0: DockBuilderAddNode(id, 0) // missing ImGuiDockNodeFlags_DockSpace
|
|
// - N+1: NewFrame() // will create floating host window for that node
|
|
// - N+1: DockSpace(id) // requalify node as dockspace, moving host window
|
|
if (node->HostWindow && node->HostWindow != host_window && node->HostWindow->DockNodeAsHost == node)
|
|
node->HostWindow->DockNodeAsHost = NULL;
|
|
|
|
host_window->DockNodeAsHost = node;
|
|
node->HostWindow = host_window;
|
|
}
|
|
|
|
static void ImGui::DockNodeUpdate(ImGuiDockNode* node)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(node->LastFrameActive != g.FrameCount);
|
|
node->LastFrameAlive = g.FrameCount;
|
|
node->MarkedForPosSizeWrite = false;
|
|
|
|
node->CentralNode = node->OnlyNodeWithWindows = NULL;
|
|
if (node->IsRootNode())
|
|
DockNodeUpdateForRootNode(node);
|
|
|
|
// Remove tab bar if not needed
|
|
if (node->TabBar && node->IsNoTabBar())
|
|
DockNodeRemoveTabBar(node);
|
|
|
|
// Early out for hidden root dock nodes (when all DockId references are in inactive windows, or there is only 1
|
|
// floating window holding on the DockId)
|
|
bool want_to_hide_host_window = false;
|
|
if (node->Windows.Size <= 1 && node->IsFloatingNode() && node->IsLeafNode())
|
|
if (!g.IO.ConfigDockingAlwaysTabBar &&
|
|
(node->Windows.Size == 0 || !node->Windows[0]->WindowClass.DockingAlwaysTabBar))
|
|
want_to_hide_host_window = true;
|
|
if (want_to_hide_host_window)
|
|
{
|
|
if (node->Windows.Size == 1)
|
|
{
|
|
// Floating window pos/size is authoritative
|
|
ImGuiWindow* single_window = node->Windows[0];
|
|
node->Pos = single_window->Pos;
|
|
node->Size = single_window->SizeFull;
|
|
node->AuthorityForPos = node->AuthorityForSize = node->AuthorityForViewport = ImGuiDataAuthority_Window;
|
|
|
|
// Transfer focus immediately so when we revert to a regular window it is immediately selected
|
|
if (node->HostWindow && g.NavWindow == node->HostWindow)
|
|
FocusWindow(single_window);
|
|
if (node->HostWindow)
|
|
{
|
|
single_window->Viewport = node->HostWindow->Viewport;
|
|
single_window->ViewportId = node->HostWindow->ViewportId;
|
|
if (node->HostWindow->ViewportOwned)
|
|
{
|
|
single_window->Viewport->Window = single_window;
|
|
single_window->ViewportOwned = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
DockNodeHideHostWindow(node);
|
|
node->State = ImGuiDockNodeState_HostWindowHiddenBecauseSingleWindow;
|
|
node->WantCloseAll = false;
|
|
node->WantCloseTabId = 0;
|
|
node->HasCloseButton = node->HasWindowMenuButton = false;
|
|
node->LastFrameActive = g.FrameCount;
|
|
|
|
if (node->WantMouseMove && node->Windows.Size == 1)
|
|
DockNodeStartMouseMovingWindow(node, node->Windows[0]);
|
|
return;
|
|
}
|
|
|
|
// In some circumstance we will defer creating the host window (so everything will be kept hidden),
|
|
// while the expected visible window is resizing itself.
|
|
// This is important for first-time (no ini settings restored) single window when io.ConfigDockingAlwaysTabBar is
|
|
// enabled, otherwise the node ends up using the minimum window size. Effectively those windows will take an extra
|
|
// frame to show up:
|
|
// N+0: Begin(): window created (with no known size), node is created
|
|
// N+1: DockNodeUpdate(): node skip creating host window / Begin(): window size applied, not visible
|
|
// N+2: DockNodeUpdate(): node can create host window / Begin(): window becomes visible
|
|
// We could remove this frame if we could reliably calculate the expected window size during node update, before the
|
|
// Begin() code. It would require a generalization of CalcWindowExpectedSize(), probably extracting code away from
|
|
// Begin(). In reality it isn't very important as user quickly ends up with size data in .ini file.
|
|
if (node->IsVisible && node->HostWindow == NULL && node->IsFloatingNode() && node->IsLeafNode())
|
|
{
|
|
IM_ASSERT(node->Windows.Size > 0);
|
|
ImGuiWindow* ref_window = NULL;
|
|
if (node->SelectedTabId !=
|
|
0) // Note that we prune single-window-node settings on .ini loading, so this is generally 0 for them!
|
|
ref_window = DockNodeFindWindowByID(node, node->SelectedTabId);
|
|
if (ref_window == NULL)
|
|
ref_window = node->Windows[0];
|
|
if (ref_window->AutoFitFramesX > 0 || ref_window->AutoFitFramesY > 0)
|
|
{
|
|
node->State = ImGuiDockNodeState_HostWindowHiddenBecauseWindowsAreResizing;
|
|
return;
|
|
}
|
|
}
|
|
|
|
const ImGuiDockNodeFlags node_flags = node->MergedFlags;
|
|
|
|
// Decide if the node will have a close button and a window menu button
|
|
node->HasWindowMenuButton = (node->Windows.Size > 0) && (node_flags & ImGuiDockNodeFlags_NoWindowMenuButton) == 0;
|
|
node->HasCloseButton = false;
|
|
for (int window_n = 0; window_n < node->Windows.Size; window_n++)
|
|
{
|
|
// FIXME-DOCK: Setting DockIsActive here means that for single active window in a leaf node, DockIsActive will
|
|
// be cleared until the next Begin() call.
|
|
ImGuiWindow* window = node->Windows[window_n];
|
|
node->HasCloseButton |= window->HasCloseButton;
|
|
window->DockIsActive = (node->Windows.Size > 1);
|
|
}
|
|
if (node_flags & ImGuiDockNodeFlags_NoCloseButton)
|
|
node->HasCloseButton = false;
|
|
|
|
// Bind or create host window
|
|
ImGuiWindow* host_window = NULL;
|
|
bool beginned_into_host_window = false;
|
|
if (node->IsDockSpace())
|
|
{
|
|
// [Explicit root dockspace node]
|
|
IM_ASSERT(node->HostWindow);
|
|
host_window = node->HostWindow;
|
|
}
|
|
else
|
|
{
|
|
// [Automatic root or child nodes]
|
|
if (node->IsRootNode() && node->IsVisible)
|
|
{
|
|
ImGuiWindow* ref_window = (node->Windows.Size > 0) ? node->Windows[0] : NULL;
|
|
|
|
// Sync Pos
|
|
if (node->AuthorityForPos == ImGuiDataAuthority_Window && ref_window)
|
|
SetNextWindowPos(ref_window->Pos);
|
|
else if (node->AuthorityForPos == ImGuiDataAuthority_DockNode)
|
|
SetNextWindowPos(node->Pos);
|
|
|
|
// Sync Size
|
|
if (node->AuthorityForSize == ImGuiDataAuthority_Window && ref_window)
|
|
SetNextWindowSize(ref_window->SizeFull);
|
|
else if (node->AuthorityForSize == ImGuiDataAuthority_DockNode)
|
|
SetNextWindowSize(node->Size);
|
|
|
|
// Sync Collapsed
|
|
if (node->AuthorityForSize == ImGuiDataAuthority_Window && ref_window)
|
|
SetNextWindowCollapsed(ref_window->Collapsed);
|
|
|
|
// Sync Viewport
|
|
if (node->AuthorityForViewport == ImGuiDataAuthority_Window && ref_window)
|
|
SetNextWindowViewport(ref_window->ViewportId);
|
|
|
|
SetNextWindowClass(&node->WindowClass);
|
|
|
|
// Begin into the host window
|
|
char window_label[20];
|
|
DockNodeGetHostWindowTitle(node, window_label, IM_ARRAYSIZE(window_label));
|
|
ImGuiWindowFlags window_flags =
|
|
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_DockNodeHost;
|
|
window_flags |= ImGuiWindowFlags_NoFocusOnAppearing;
|
|
window_flags |=
|
|
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoCollapse;
|
|
window_flags |= ImGuiWindowFlags_NoTitleBar;
|
|
|
|
PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
|
Begin(window_label, NULL, window_flags);
|
|
PopStyleVar();
|
|
beginned_into_host_window = true;
|
|
|
|
host_window = g.CurrentWindow;
|
|
DockNodeSetupHostWindow(node, host_window);
|
|
host_window->DC.CursorPos = host_window->Pos;
|
|
node->Pos = host_window->Pos;
|
|
node->Size = host_window->Size;
|
|
|
|
// We set ImGuiWindowFlags_NoFocusOnAppearing because we don't want the host window to take full focus (e.g.
|
|
// steal NavWindow) But we still it bring it to the front of display. There's no way to choose this precise
|
|
// behavior via window flags. One simple case to ponder if: window A has a toggle to create windows B/C/D.
|
|
// Dock B/C/D together, clear the toggle and enable it again. When reappearing B/C/D will request focus and
|
|
// be moved to the top of the display pile, but they are not linked to the dock host window during the frame
|
|
// they appear. The dock host window would keep its old display order, and the sorting in EndFrame would
|
|
// move B/C/D back after the dock host window, losing their top-most status.
|
|
if (node->HostWindow->Appearing)
|
|
BringWindowToDisplayFront(node->HostWindow);
|
|
|
|
node->AuthorityForPos = node->AuthorityForSize = node->AuthorityForViewport = ImGuiDataAuthority_Auto;
|
|
}
|
|
else if (node->ParentNode)
|
|
{
|
|
node->HostWindow = host_window = node->ParentNode->HostWindow;
|
|
node->AuthorityForPos = node->AuthorityForSize = node->AuthorityForViewport = ImGuiDataAuthority_Auto;
|
|
}
|
|
if (node->WantMouseMove && node->HostWindow)
|
|
DockNodeStartMouseMovingWindow(node, node->HostWindow);
|
|
}
|
|
|
|
// Update focused node (the one whose title bar is highlight) within a node tree
|
|
if (node->IsSplitNode())
|
|
IM_ASSERT(node->TabBar == NULL);
|
|
if (node->IsRootNode())
|
|
if (g.NavWindow && g.NavWindow->RootWindow->DockNode && g.NavWindow->RootWindow->ParentWindow == host_window)
|
|
node->LastFocusedNodeId = g.NavWindow->RootWindow->DockNode->ID;
|
|
|
|
// We need to draw a background at the root level if requested by ImGuiDockNodeFlags_PassthruCentralNode, but we
|
|
// will only know the correct pos/size _after_ processing the resizing splitters. So we are using the DrawList
|
|
// channel splitting facility to submit drawing primitives out of order!
|
|
const bool render_dockspace_bg =
|
|
node->IsRootNode() && host_window && (node_flags & ImGuiDockNodeFlags_PassthruCentralNode) != 0;
|
|
if (render_dockspace_bg)
|
|
{
|
|
host_window->DrawList->ChannelsSplit(2);
|
|
host_window->DrawList->ChannelsSetCurrent(1);
|
|
}
|
|
|
|
// Register a hit-test hole in the window unless we are currently dragging a window that is compatible with our
|
|
// dockspace
|
|
ImGuiDockNode* central_node = node->CentralNode;
|
|
const bool central_node_hole = node->IsRootNode() && host_window &&
|
|
(node_flags & ImGuiDockNodeFlags_PassthruCentralNode) != 0 && central_node != NULL &&
|
|
central_node->IsEmpty();
|
|
bool central_node_hole_register_hit_test_hole = central_node_hole;
|
|
if (central_node_hole)
|
|
if (const ImGuiPayload* payload = ImGui::GetDragDropPayload())
|
|
if (payload->IsDataType(IMGUI_PAYLOAD_TYPE_WINDOW) &&
|
|
DockNodeIsDropAllowed(host_window, *(ImGuiWindow**)payload->Data))
|
|
central_node_hole_register_hit_test_hole = false;
|
|
if (central_node_hole_register_hit_test_hole)
|
|
{
|
|
// We add a little padding to match the "resize from edges" behavior and allow grabbing the splitter easily.
|
|
// (But we only add it if there's something else on the other side of the hole, otherwise for e.g. fullscreen
|
|
// covering passthru node we'd have a gap on the edge not covered by the hole)
|
|
IM_ASSERT(node->IsDockSpace()); // We cannot pass this flag without the DockSpace() api. Testing this because
|
|
// we also setup the hole in host_window->ParentNode
|
|
ImGuiDockNode* root_node = DockNodeGetRootNode(central_node);
|
|
ImRect root_rect(root_node->Pos, root_node->Pos + root_node->Size);
|
|
ImRect hole_rect(central_node->Pos, central_node->Pos + central_node->Size);
|
|
if (hole_rect.Min.x > root_rect.Min.x)
|
|
{
|
|
hole_rect.Min.x += WINDOWS_HOVER_PADDING;
|
|
}
|
|
if (hole_rect.Max.x < root_rect.Max.x)
|
|
{
|
|
hole_rect.Max.x -= WINDOWS_HOVER_PADDING;
|
|
}
|
|
if (hole_rect.Min.y > root_rect.Min.y)
|
|
{
|
|
hole_rect.Min.y += WINDOWS_HOVER_PADDING;
|
|
}
|
|
if (hole_rect.Max.y < root_rect.Max.y)
|
|
{
|
|
hole_rect.Max.y -= WINDOWS_HOVER_PADDING;
|
|
}
|
|
// GetForegroundDrawList()->AddRect(hole_rect.Min, hole_rect.Max, IM_COL32(255, 0, 0, 255));
|
|
if (central_node_hole && !hole_rect.IsInverted())
|
|
{
|
|
SetWindowHitTestHole(host_window, hole_rect.Min, hole_rect.Max - hole_rect.Min);
|
|
SetWindowHitTestHole(host_window->ParentWindow, hole_rect.Min, hole_rect.Max - hole_rect.Min);
|
|
}
|
|
}
|
|
|
|
// Update position/size, process and draw resizing splitters
|
|
if (node->IsRootNode() && host_window)
|
|
{
|
|
DockNodeTreeUpdatePosSize(node, host_window->Pos, host_window->Size);
|
|
DockNodeTreeUpdateSplitter(node);
|
|
}
|
|
|
|
// Draw empty node background (currently can only be the Central Node)
|
|
if (host_window && node->IsEmpty() && node->IsVisible && !(node_flags & ImGuiDockNodeFlags_PassthruCentralNode))
|
|
host_window->DrawList->AddRectFilled(node->Pos, node->Pos + node->Size, GetColorU32(ImGuiCol_DockingEmptyBg));
|
|
|
|
// Draw whole dockspace background if ImGuiDockNodeFlags_PassthruCentralNode if set.
|
|
if (render_dockspace_bg && node->IsVisible)
|
|
{
|
|
host_window->DrawList->ChannelsSetCurrent(0);
|
|
if (central_node_hole)
|
|
RenderRectFilledWithHole(host_window->DrawList, node->Rect(), central_node->Rect(),
|
|
GetColorU32(ImGuiCol_WindowBg), 0.0f);
|
|
else
|
|
host_window->DrawList->AddRectFilled(node->Pos, node->Pos + node->Size, GetColorU32(ImGuiCol_WindowBg),
|
|
0.0f);
|
|
host_window->DrawList->ChannelsMerge();
|
|
}
|
|
|
|
// Draw and populate Tab Bar
|
|
if (host_window && node->Windows.Size > 0)
|
|
{
|
|
DockNodeUpdateTabBar(node, host_window);
|
|
}
|
|
else
|
|
{
|
|
node->WantCloseAll = false;
|
|
node->WantCloseTabId = 0;
|
|
node->IsFocused = false;
|
|
}
|
|
if (node->TabBar && node->TabBar->SelectedTabId)
|
|
node->SelectedTabId = node->TabBar->SelectedTabId;
|
|
else if (node->Windows.Size > 0)
|
|
node->SelectedTabId = node->Windows[0]->ID;
|
|
|
|
// Draw payload drop target
|
|
if (host_window && node->IsVisible)
|
|
if (node->IsRootNode() && (g.MovingWindow == NULL || g.MovingWindow->RootWindowDockTree != host_window))
|
|
BeginDockableDragDropTarget(host_window);
|
|
|
|
// We update this after DockNodeUpdateTabBar()
|
|
node->LastFrameActive = g.FrameCount;
|
|
|
|
// Recurse into children
|
|
// FIXME-DOCK FIXME-OPT: Should not need to recurse into children
|
|
if (host_window)
|
|
{
|
|
if (node->ChildNodes[0])
|
|
DockNodeUpdate(node->ChildNodes[0]);
|
|
if (node->ChildNodes[1])
|
|
DockNodeUpdate(node->ChildNodes[1]);
|
|
|
|
// Render outer borders last (after the tab bar)
|
|
if (node->IsRootNode())
|
|
RenderWindowOuterBorders(host_window);
|
|
}
|
|
|
|
// End host window
|
|
if (beginned_into_host_window) //-V1020
|
|
End();
|
|
}
|
|
|
|
// Compare TabItem nodes given the last known DockOrder (will persist in .ini file as hint), used to sort tabs when
|
|
// multiple tabs are added on the same frame.
|
|
static int IMGUI_CDECL TabItemComparerByDockOrder(const void* lhs, const void* rhs)
|
|
{
|
|
ImGuiWindow* a = ((const ImGuiTabItem*)lhs)->Window;
|
|
ImGuiWindow* b = ((const ImGuiTabItem*)rhs)->Window;
|
|
if (int d = ((a->DockOrder == -1) ? INT_MAX : a->DockOrder) - ((b->DockOrder == -1) ? INT_MAX : b->DockOrder))
|
|
return d;
|
|
return (a->BeginOrderWithinContext - b->BeginOrderWithinContext);
|
|
}
|
|
|
|
static ImGuiID ImGui::DockNodeUpdateWindowMenu(ImGuiDockNode* node, ImGuiTabBar* tab_bar)
|
|
{
|
|
// Try to position the menu so it is more likely to stays within the same viewport
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiID ret_tab_id = 0;
|
|
if (g.Style.WindowMenuButtonPosition == ImGuiDir_Left)
|
|
SetNextWindowPos(ImVec2(node->Pos.x, node->Pos.y + GetFrameHeight()), ImGuiCond_Always, ImVec2(0.0f, 0.0f));
|
|
else
|
|
SetNextWindowPos(ImVec2(node->Pos.x + node->Size.x, node->Pos.y + GetFrameHeight()), ImGuiCond_Always,
|
|
ImVec2(1.0f, 0.0f));
|
|
if (BeginPopup("#WindowMenu"))
|
|
{
|
|
node->IsFocused = true;
|
|
if (tab_bar->Tabs.Size == 1)
|
|
{
|
|
if (MenuItem("Hide tab bar", NULL, node->IsHiddenTabBar()))
|
|
node->WantHiddenTabBarToggle = true;
|
|
}
|
|
else
|
|
{
|
|
for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
|
|
{
|
|
ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
|
|
if (tab->Flags & ImGuiTabItemFlags_Button)
|
|
continue;
|
|
if (Selectable(tab_bar->GetTabName(tab), tab->ID == tab_bar->SelectedTabId))
|
|
ret_tab_id = tab->ID;
|
|
SameLine();
|
|
Text(" ");
|
|
}
|
|
}
|
|
EndPopup();
|
|
}
|
|
return ret_tab_id;
|
|
}
|
|
|
|
// User helper to append/amend into a dock node tab bar. Most commonly used to add e.g. a "+" button.
|
|
bool ImGui::DockNodeBeginAmendTabBar(ImGuiDockNode* node)
|
|
{
|
|
if (node->TabBar == NULL || node->HostWindow == NULL)
|
|
return false;
|
|
if (node->MergedFlags & ImGuiDockNodeFlags_KeepAliveOnly)
|
|
return false;
|
|
Begin(node->HostWindow->Name);
|
|
PushOverrideID(node->ID);
|
|
bool ret = BeginTabBarEx(node->TabBar, node->TabBar->BarRect, node->TabBar->Flags, node);
|
|
IM_UNUSED(ret);
|
|
IM_ASSERT(ret);
|
|
return true;
|
|
}
|
|
|
|
void ImGui::DockNodeEndAmendTabBar()
|
|
{
|
|
EndTabBar();
|
|
PopID();
|
|
End();
|
|
}
|
|
|
|
// Submit the tab bar corresponding to a dock node and various housekeeping details.
|
|
static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_window)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiStyle& style = g.Style;
|
|
|
|
const bool node_was_active = (node->LastFrameActive + 1 == g.FrameCount);
|
|
const bool closed_all = node->WantCloseAll && node_was_active;
|
|
const ImGuiID closed_one = node->WantCloseTabId && node_was_active;
|
|
node->WantCloseAll = false;
|
|
node->WantCloseTabId = 0;
|
|
|
|
// Decide if we should use a focused title bar color
|
|
bool is_focused = false;
|
|
ImGuiDockNode* root_node = DockNodeGetRootNode(node);
|
|
if (g.NavWindowingTarget)
|
|
is_focused = (g.NavWindowingTarget->DockNode == node);
|
|
else if (g.NavWindow && g.NavWindow->RootWindowForTitleBarHighlight == host_window->RootWindowDockTree &&
|
|
root_node->LastFocusedNodeId == node->ID)
|
|
is_focused = true;
|
|
|
|
// Hidden tab bar will show a triangle on the upper-left (in Begin)
|
|
if (node->IsHiddenTabBar() || node->IsNoTabBar())
|
|
{
|
|
node->VisibleWindow = (node->Windows.Size > 0) ? node->Windows[0] : NULL;
|
|
node->IsFocused = is_focused;
|
|
if (is_focused)
|
|
node->LastFrameFocused = g.FrameCount;
|
|
if (node->VisibleWindow)
|
|
{
|
|
// Notify root of visible window (used to display title in OS task bar)
|
|
if (is_focused || root_node->VisibleWindow == NULL)
|
|
root_node->VisibleWindow = node->VisibleWindow;
|
|
if (node->TabBar)
|
|
node->TabBar->VisibleTabId = node->VisibleWindow->ID;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Move ourselves to the Menu layer (so we can be accessed by tapping Alt) + undo SkipItems flag in order to draw
|
|
// over the title bar even if the window is collapsed
|
|
bool backup_skip_item = host_window->SkipItems;
|
|
if (!node->IsDockSpace())
|
|
{
|
|
host_window->SkipItems = false;
|
|
host_window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;
|
|
}
|
|
|
|
// Use PushOverrideID() instead of PushID() to use the node id _without_ the host window ID.
|
|
// This is to facilitate computing those ID from the outside, and will affect more or less only the ID of the
|
|
// collapse button, popup and tabs, as docked windows themselves will override the stack with their own root ID.
|
|
PushOverrideID(node->ID);
|
|
ImGuiTabBar* tab_bar = node->TabBar;
|
|
bool tab_bar_is_recreated = (tab_bar == NULL); // Tab bar are automatically destroyed when a node gets hidden
|
|
if (tab_bar == NULL)
|
|
{
|
|
DockNodeAddTabBar(node);
|
|
tab_bar = node->TabBar;
|
|
}
|
|
|
|
ImGuiID focus_tab_id = 0;
|
|
node->IsFocused = is_focused;
|
|
|
|
const ImGuiDockNodeFlags node_flags = node->MergedFlags;
|
|
const bool has_window_menu_button =
|
|
(node_flags & ImGuiDockNodeFlags_NoWindowMenuButton) == 0 && (style.WindowMenuButtonPosition != ImGuiDir_None);
|
|
|
|
// In a dock node, the Collapse Button turns into the Window Menu button.
|
|
// FIXME-DOCK FIXME-OPT: Could we recycle popups id across multiple dock nodes?
|
|
if (has_window_menu_button && IsPopupOpen("#WindowMenu"))
|
|
{
|
|
if (ImGuiID tab_id = DockNodeUpdateWindowMenu(node, tab_bar))
|
|
focus_tab_id = tab_bar->NextSelectedTabId = tab_id;
|
|
is_focused |= node->IsFocused;
|
|
}
|
|
|
|
// Layout
|
|
ImRect title_bar_rect, tab_bar_rect;
|
|
ImVec2 window_menu_button_pos;
|
|
ImVec2 close_button_pos;
|
|
DockNodeCalcTabBarLayout(node, &title_bar_rect, &tab_bar_rect, &window_menu_button_pos, &close_button_pos);
|
|
|
|
// Submit new tabs, they will be added as Unsorted and sorted below based on relative DockOrder value.
|
|
const int tabs_count_old = tab_bar->Tabs.Size;
|
|
for (int window_n = 0; window_n < node->Windows.Size; window_n++)
|
|
{
|
|
ImGuiWindow* window = node->Windows[window_n];
|
|
if (TabBarFindTabByID(tab_bar, window->ID) == NULL)
|
|
TabBarAddTab(tab_bar, ImGuiTabItemFlags_Unsorted, window);
|
|
}
|
|
|
|
// Title bar
|
|
if (is_focused)
|
|
node->LastFrameFocused = g.FrameCount;
|
|
ImU32 title_bar_col = GetColorU32(host_window->Collapsed ? ImGuiCol_TitleBgCollapsed
|
|
: is_focused ? ImGuiCol_TitleBgActive
|
|
: ImGuiCol_TitleBg);
|
|
host_window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col,
|
|
host_window->WindowRounding, ImDrawFlags_RoundCornersTop);
|
|
|
|
// Docking/Collapse button
|
|
if (has_window_menu_button)
|
|
{
|
|
if (CollapseButton(host_window->GetID("#COLLAPSE"), window_menu_button_pos,
|
|
node)) // == DockNodeGetWindowMenuButtonId(node)
|
|
OpenPopup("#WindowMenu");
|
|
if (IsItemActive())
|
|
focus_tab_id = tab_bar->SelectedTabId;
|
|
}
|
|
|
|
// If multiple tabs are appearing on the same frame, sort them based on their persistent DockOrder value
|
|
int tabs_unsorted_start = tab_bar->Tabs.Size;
|
|
for (int tab_n = tab_bar->Tabs.Size - 1; tab_n >= 0 && (tab_bar->Tabs[tab_n].Flags & ImGuiTabItemFlags_Unsorted);
|
|
tab_n--)
|
|
{
|
|
// FIXME-DOCK: Consider only clearing the flag after the tab has been alive for a few consecutive frames,
|
|
// allowing late comers to not break sorting?
|
|
tab_bar->Tabs[tab_n].Flags &= ~ImGuiTabItemFlags_Unsorted;
|
|
tabs_unsorted_start = tab_n;
|
|
}
|
|
if (tab_bar->Tabs.Size > tabs_unsorted_start)
|
|
{
|
|
IMGUI_DEBUG_LOG_DOCKING("In node 0x%08X: %d new appearing tabs:%s\n", node->ID,
|
|
tab_bar->Tabs.Size - tabs_unsorted_start,
|
|
(tab_bar->Tabs.Size > tabs_unsorted_start + 1) ? " (will sort)" : "");
|
|
for (int tab_n = tabs_unsorted_start; tab_n < tab_bar->Tabs.Size; tab_n++)
|
|
IMGUI_DEBUG_LOG_DOCKING(" - Tab '%s' Order %d\n", tab_bar->Tabs[tab_n].Window->Name,
|
|
tab_bar->Tabs[tab_n].Window->DockOrder);
|
|
if (tab_bar->Tabs.Size > tabs_unsorted_start + 1)
|
|
ImQsort(tab_bar->Tabs.Data + tabs_unsorted_start, tab_bar->Tabs.Size - tabs_unsorted_start,
|
|
sizeof(ImGuiTabItem), TabItemComparerByDockOrder);
|
|
}
|
|
|
|
// Apply NavWindow focus back to the tab bar
|
|
if (g.NavWindow && g.NavWindow->RootWindow->DockNode == node)
|
|
tab_bar->SelectedTabId = g.NavWindow->RootWindow->ID;
|
|
|
|
// 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
|
|
ImGuiTabBarFlags tab_bar_flags =
|
|
ImGuiTabBarFlags_Reorderable |
|
|
ImGuiTabBarFlags_AutoSelectNewTabs; // | ImGuiTabBarFlags_NoTabListScrollingButtons);
|
|
tab_bar_flags |= ImGuiTabBarFlags_SaveSettings | ImGuiTabBarFlags_DockNode;
|
|
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));
|
|
|
|
// Backup style colors
|
|
ImVec4 backup_style_cols[ImGuiWindowDockStyleCol_COUNT];
|
|
for (int color_n = 0; color_n < ImGuiWindowDockStyleCol_COUNT; color_n++)
|
|
backup_style_cols[color_n] = g.Style.Colors[GWindowDockStyleColors[color_n]];
|
|
|
|
// Submit actual tabs
|
|
node->VisibleWindow = NULL;
|
|
for (int window_n = 0; window_n < node->Windows.Size; window_n++)
|
|
{
|
|
ImGuiWindow* window = node->Windows[window_n];
|
|
if ((closed_all || closed_one == window->ID) && window->HasCloseButton &&
|
|
!(window->Flags & ImGuiWindowFlags_UnsavedDocument))
|
|
continue;
|
|
if (window->LastFrameActive + 1 >= g.FrameCount || !node_was_active)
|
|
{
|
|
ImGuiTabItemFlags tab_item_flags = 0;
|
|
tab_item_flags |= window->WindowClass.TabItemFlagsOverrideSet;
|
|
if (window->Flags & ImGuiWindowFlags_UnsavedDocument)
|
|
tab_item_flags |= ImGuiTabItemFlags_UnsavedDocument;
|
|
if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)
|
|
tab_item_flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton;
|
|
|
|
// Apply stored style overrides for the window
|
|
for (int color_n = 0; color_n < ImGuiWindowDockStyleCol_COUNT; color_n++)
|
|
g.Style.Colors[GWindowDockStyleColors[color_n]] =
|
|
ColorConvertU32ToFloat4(window->DockStyle.Colors[color_n]);
|
|
|
|
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 = g.LastItemData.StatusFlags;
|
|
window->DockTabItemRect = g.LastItemData.Rect;
|
|
|
|
// Update navigation ID on menu layer
|
|
if (g.NavWindow && g.NavWindow->RootWindow == window &&
|
|
(window->DC.NavLayersActiveMask & (1 << ImGuiNavLayer_Menu)) == 0)
|
|
host_window->NavLastIds[1] = window->ID;
|
|
}
|
|
}
|
|
|
|
// Restore style colors
|
|
for (int color_n = 0; color_n < ImGuiWindowDockStyleCol_COUNT; color_n++)
|
|
g.Style.Colors[GWindowDockStyleColors[color_n]] = backup_style_cols[color_n];
|
|
|
|
// Notify root of visible window (used to display title in OS task bar)
|
|
if (node->VisibleWindow)
|
|
if (is_focused || root_node->VisibleWindow == NULL)
|
|
root_node->VisibleWindow = node->VisibleWindow;
|
|
|
|
// Close button (after VisibleWindow was updated)
|
|
// Note that VisibleWindow may have been overrided by CTRL+Tabbing, so VisibleWindow->ID may be != from
|
|
// tab_bar->SelectedTabId
|
|
const bool close_button_is_enabled =
|
|
node->HasCloseButton && node->VisibleWindow && node->VisibleWindow->HasCloseButton;
|
|
const bool close_button_is_visible = node->HasCloseButton;
|
|
// const bool close_button_is_visible = close_button_is_enabled; // Most people would expect this behavior of not
|
|
// even showing the button (leaving a hole since we can't claim that space as other windows in the tba bar have one)
|
|
if (close_button_is_visible)
|
|
{
|
|
if (!close_button_is_enabled)
|
|
{
|
|
PushItemFlag(ImGuiItemFlags_Disabled, true);
|
|
PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_Text] * ImVec4(1.0f, 1.0f, 1.0f, 0.4f));
|
|
}
|
|
if (CloseButton(host_window->GetID("#CLOSE"), close_button_pos))
|
|
{
|
|
node->WantCloseAll = true;
|
|
for (int n = 0; n < tab_bar->Tabs.Size; n++)
|
|
TabBarCloseTab(tab_bar, &tab_bar->Tabs[n]);
|
|
}
|
|
// if (IsItemActive())
|
|
// focus_tab_id = tab_bar->SelectedTabId;
|
|
if (!close_button_is_enabled)
|
|
{
|
|
PopStyleColor();
|
|
PopItemFlag();
|
|
}
|
|
}
|
|
|
|
// When clicking on the title bar outside of tabs, we still focus the selected tab for that node
|
|
// FIXME: TabItem use AllowItemOverlap so we manually perform a more specific test for now (hovered || held)
|
|
ImGuiID title_bar_id = host_window->GetID("#TITLEBAR");
|
|
if (g.HoveredId == 0 || g.HoveredId == title_bar_id || g.ActiveId == title_bar_id)
|
|
{
|
|
bool held;
|
|
ButtonBehavior(title_bar_rect, title_bar_id, NULL, &held, ImGuiButtonFlags_AllowItemOverlap);
|
|
if (g.HoveredId == title_bar_id)
|
|
{
|
|
// ImGuiButtonFlags_AllowItemOverlap + SetItemAllowOverlap() required for appending into dock node tab bar,
|
|
// otherwise dragging window will steal HoveredId and amended tabs cannot get them.
|
|
g.LastItemData.ID = title_bar_id;
|
|
SetItemAllowOverlap();
|
|
}
|
|
if (held)
|
|
{
|
|
if (IsMouseClicked(0))
|
|
focus_tab_id = tab_bar->SelectedTabId;
|
|
|
|
// Forward moving request to selected window
|
|
if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_bar->SelectedTabId))
|
|
StartMouseMovingWindowOrNode(tab->Window ? tab->Window : node->HostWindow, node, false);
|
|
}
|
|
}
|
|
|
|
// Forward focus from host node to selected window
|
|
// if (is_focused && g.NavWindow == host_window && !g.NavWindowingTarget)
|
|
// focus_tab_id = tab_bar->SelectedTabId;
|
|
|
|
// When clicked on a tab we requested focus to the docked child
|
|
// This overrides the value set by "forward focus from host node to selected window".
|
|
if (tab_bar->NextSelectedTabId)
|
|
focus_tab_id = tab_bar->NextSelectedTabId;
|
|
|
|
// Apply navigation focus
|
|
if (focus_tab_id != 0)
|
|
if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, focus_tab_id))
|
|
if (tab->Window)
|
|
{
|
|
FocusWindow(tab->Window);
|
|
NavInitWindow(tab->Window, false);
|
|
}
|
|
|
|
EndTabBar();
|
|
PopID();
|
|
|
|
// Restore SkipItems flag
|
|
if (!node->IsDockSpace())
|
|
{
|
|
host_window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
|
|
host_window->SkipItems = backup_skip_item;
|
|
}
|
|
}
|
|
|
|
static void ImGui::DockNodeAddTabBar(ImGuiDockNode* node)
|
|
{
|
|
IM_ASSERT(node->TabBar == NULL);
|
|
node->TabBar = IM_NEW(ImGuiTabBar);
|
|
}
|
|
|
|
static void ImGui::DockNodeRemoveTabBar(ImGuiDockNode* node)
|
|
{
|
|
if (node->TabBar == NULL)
|
|
return;
|
|
IM_DELETE(node->TabBar);
|
|
node->TabBar = NULL;
|
|
}
|
|
|
|
static bool DockNodeIsDropAllowedOne(ImGuiWindow* payload, ImGuiWindow* host_window)
|
|
{
|
|
if (host_window->DockNodeAsHost && host_window->DockNodeAsHost->IsDockSpace() &&
|
|
payload->BeginOrderWithinContext < host_window->BeginOrderWithinContext)
|
|
return false;
|
|
|
|
ImGuiWindowClass* host_class =
|
|
host_window->DockNodeAsHost ? &host_window->DockNodeAsHost->WindowClass : &host_window->WindowClass;
|
|
ImGuiWindowClass* payload_class = &payload->WindowClass;
|
|
if (host_class->ClassId != payload_class->ClassId)
|
|
{
|
|
if (host_class->ClassId != 0 && host_class->DockingAllowUnclassed && payload_class->ClassId == 0)
|
|
return true;
|
|
if (payload_class->ClassId != 0 && payload_class->DockingAllowUnclassed && host_class->ClassId == 0)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
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++)
|
|
{
|
|
ImGuiWindow* payload =
|
|
root_payload->DockNodeAsHost ? root_payload->DockNodeAsHost->Windows[payload_n] : root_payload;
|
|
if (DockNodeIsDropAllowedOne(payload, host_window))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// window menu button == collapse button when not in a dock node.
|
|
// FIXME: This is similar to RenderWindowTitleBarContents(), may want to share code.
|
|
static void ImGui::DockNodeCalcTabBarLayout(const ImGuiDockNode* node,
|
|
ImRect* out_title_rect,
|
|
ImRect* out_tab_bar_rect,
|
|
ImVec2* out_window_menu_button_pos,
|
|
ImVec2* out_close_button_pos)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiStyle& style = g.Style;
|
|
|
|
ImRect r = ImRect(node->Pos.x, node->Pos.y, node->Pos.x + node->Size.x,
|
|
node->Pos.y + g.FontSize + g.Style.FramePadding.y * 2.0f);
|
|
if (out_title_rect)
|
|
{
|
|
*out_title_rect = r;
|
|
}
|
|
|
|
r.Min.x += style.WindowBorderSize;
|
|
r.Max.x -= style.WindowBorderSize;
|
|
|
|
float button_sz = g.FontSize;
|
|
|
|
ImVec2 window_menu_button_pos = r.Min;
|
|
r.Min.x += style.FramePadding.x;
|
|
r.Max.x -= style.FramePadding.x;
|
|
if (node->HasCloseButton)
|
|
{
|
|
r.Max.x -= button_sz;
|
|
if (out_close_button_pos)
|
|
*out_close_button_pos = ImVec2(r.Max.x - style.FramePadding.x, r.Min.y);
|
|
}
|
|
if (node->HasWindowMenuButton && style.WindowMenuButtonPosition == ImGuiDir_Left)
|
|
{
|
|
r.Min.x += button_sz + style.ItemInnerSpacing.x;
|
|
}
|
|
else if (node->HasWindowMenuButton && style.WindowMenuButtonPosition == ImGuiDir_Right)
|
|
{
|
|
r.Max.x -= button_sz + style.FramePadding.x;
|
|
window_menu_button_pos = ImVec2(r.Max.x, r.Min.y);
|
|
}
|
|
if (out_tab_bar_rect)
|
|
{
|
|
*out_tab_bar_rect = r;
|
|
}
|
|
if (out_window_menu_button_pos)
|
|
{
|
|
*out_window_menu_button_pos = window_menu_button_pos;
|
|
}
|
|
}
|
|
|
|
void ImGui::DockNodeCalcSplitRects(ImVec2& pos_old,
|
|
ImVec2& size_old,
|
|
ImVec2& pos_new,
|
|
ImVec2& size_new,
|
|
ImGuiDir dir,
|
|
ImVec2 size_new_desired)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
const float dock_spacing = g.Style.ItemInnerSpacing.x;
|
|
const ImGuiAxis axis = (dir == ImGuiDir_Left || dir == ImGuiDir_Right) ? ImGuiAxis_X : ImGuiAxis_Y;
|
|
pos_new[axis ^ 1] = pos_old[axis ^ 1];
|
|
size_new[axis ^ 1] = size_old[axis ^ 1];
|
|
|
|
// Distribute size on given axis (with a desired size or equally)
|
|
const float w_avail = size_old[axis] - dock_spacing;
|
|
if (size_new_desired[axis] > 0.0f && size_new_desired[axis] <= w_avail * 0.5f)
|
|
{
|
|
size_new[axis] = size_new_desired[axis];
|
|
size_old[axis] = IM_FLOOR(w_avail - size_new[axis]);
|
|
}
|
|
else
|
|
{
|
|
size_new[axis] = IM_FLOOR(w_avail * 0.5f);
|
|
size_old[axis] = IM_FLOOR(w_avail - size_new[axis]);
|
|
}
|
|
|
|
// Position each node
|
|
if (dir == ImGuiDir_Right || dir == ImGuiDir_Down)
|
|
{
|
|
pos_new[axis] = pos_old[axis] + size_old[axis] + dock_spacing;
|
|
}
|
|
else if (dir == ImGuiDir_Left || dir == ImGuiDir_Up)
|
|
{
|
|
pos_new[axis] = pos_old[axis];
|
|
pos_old[axis] = pos_new[axis] + size_new[axis] + dock_spacing;
|
|
}
|
|
}
|
|
|
|
// Retrieve the drop rectangles for a given direction or for the center + perform hit testing.
|
|
bool ImGui::DockNodeCalcDropRectsAndTestMousePos(const ImRect& parent,
|
|
ImGuiDir dir,
|
|
ImRect& out_r,
|
|
bool outer_docking,
|
|
ImVec2* test_mouse_pos)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
const float parent_smaller_axis = ImMin(parent.GetWidth(), parent.GetHeight());
|
|
const float hs_for_central_nodes = ImMin(g.FontSize * 1.5f, ImMax(g.FontSize * 0.5f, parent_smaller_axis / 8.0f));
|
|
float hs_w; // Half-size, longer axis
|
|
float hs_h; // Half-size, smaller axis
|
|
ImVec2 off; // Distance from edge or center
|
|
if (outer_docking)
|
|
{
|
|
// hs_w = 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 - hs_h), ImFloor(parent.GetHeight() * 0.5f - hs_h));
|
|
}
|
|
else
|
|
{
|
|
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));
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
if (test_mouse_pos == NULL)
|
|
return false;
|
|
|
|
ImRect hit_r = out_r;
|
|
if (!outer_docking)
|
|
{
|
|
// Custom hit testing for the 5-way selection, designed to reduce flickering when moving diagonally between
|
|
// sides
|
|
hit_r.Expand(ImFloor(hs_w * 0.30f));
|
|
ImVec2 mouse_delta = (*test_mouse_pos - c);
|
|
float mouse_delta_len2 = ImLengthSqr(mouse_delta);
|
|
float r_threshold_center = hs_w * 1.4f;
|
|
float r_threshold_sides = hs_w * (1.4f + 1.2f);
|
|
if (mouse_delta_len2 < r_threshold_center * r_threshold_center)
|
|
return (dir == ImGuiDir_None);
|
|
if (mouse_delta_len2 < r_threshold_sides * r_threshold_sides)
|
|
return (dir == ImGetDirQuadrantFromDelta(mouse_delta.x, mouse_delta.y));
|
|
}
|
|
return hit_r.Contains(*test_mouse_pos);
|
|
}
|
|
|
|
// host_node may be NULL if the window doesn't have a DockNode already.
|
|
// FIXME-DOCK: This is misnamed since it's also doing the filtering.
|
|
static void ImGui::DockNodePreviewDockSetup(ImGuiWindow* host_window,
|
|
ImGuiDockNode* host_node,
|
|
ImGuiWindow* root_payload,
|
|
ImGuiDockPreviewData* data,
|
|
bool is_explicit_target,
|
|
bool is_outer_docking)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
// There is an edge case when docking into a dockspace which only has inactive nodes.
|
|
// In this case DockNodeTreeFindNodeByPos() will have selected a leaf node which is inactive.
|
|
// Because the inactive leaf node doesn't have proper pos/size yet, we'll use the root node as reference.
|
|
ImGuiDockNode* root_payload_as_host = root_payload->DockNodeAsHost;
|
|
ImGuiDockNode* ref_node_for_rect =
|
|
(host_node && !host_node->IsVisible) ? DockNodeGetRootNode(host_node) : host_node;
|
|
if (ref_node_for_rect)
|
|
IM_ASSERT(ref_node_for_rect->IsVisible);
|
|
|
|
// Filter, figure out where we are allowed to dock
|
|
ImGuiDockNodeFlags src_node_flags =
|
|
root_payload_as_host ? root_payload_as_host->MergedFlags : root_payload->WindowClass.DockNodeFlagsOverrideSet;
|
|
ImGuiDockNodeFlags dst_node_flags =
|
|
host_node ? host_node->MergedFlags : host_window->WindowClass.DockNodeFlagsOverrideSet;
|
|
data->IsCenterAvailable = true;
|
|
if (is_outer_docking)
|
|
data->IsCenterAvailable = false;
|
|
else if (dst_node_flags & ImGuiDockNodeFlags_NoDocking)
|
|
data->IsCenterAvailable = false;
|
|
else if (host_node && (dst_node_flags & ImGuiDockNodeFlags_NoDockingInCentralNode) && host_node->IsCentralNode())
|
|
data->IsCenterAvailable = false;
|
|
else if ((!host_node || !host_node->IsEmpty()) && root_payload_as_host && root_payload_as_host->IsSplitNode() &&
|
|
(root_payload_as_host->OnlyNodeWithWindows == NULL)) // Is _visibly_ split?
|
|
data->IsCenterAvailable = false;
|
|
else if (dst_node_flags & ImGuiDockNodeFlags_NoDockingOverMe)
|
|
data->IsCenterAvailable = false;
|
|
else if ((src_node_flags & ImGuiDockNodeFlags_NoDockingOverOther) && (!host_node || !host_node->IsEmpty()))
|
|
data->IsCenterAvailable = false;
|
|
else if ((src_node_flags & ImGuiDockNodeFlags_NoDockingOverEmpty) && host_node && host_node->IsEmpty())
|
|
data->IsCenterAvailable = false;
|
|
|
|
data->IsSidesAvailable = true;
|
|
if ((dst_node_flags & ImGuiDockNodeFlags_NoSplit) || g.IO.ConfigDockingNoSplit)
|
|
data->IsSidesAvailable = false;
|
|
else if (!is_outer_docking && host_node && host_node->ParentNode == NULL && host_node->IsCentralNode())
|
|
data->IsSidesAvailable = false;
|
|
else if ((dst_node_flags & ImGuiDockNodeFlags_NoDockingSplitMe) ||
|
|
(src_node_flags & ImGuiDockNodeFlags_NoDockingSplitOther))
|
|
data->IsSidesAvailable = false;
|
|
|
|
// Build a tentative future node (reuse same structure because it is practical. Shape will be readjusted when
|
|
// previewing a split)
|
|
data->FutureNode.HasCloseButton =
|
|
(host_node ? host_node->HasCloseButton : host_window->HasCloseButton) || (root_payload->HasCloseButton);
|
|
data->FutureNode.HasWindowMenuButton = host_node ? true : ((host_window->Flags & ImGuiWindowFlags_NoCollapse) == 0);
|
|
data->FutureNode.Pos = ref_node_for_rect ? ref_node_for_rect->Pos : host_window->Pos;
|
|
data->FutureNode.Size = ref_node_for_rect ? ref_node_for_rect->Size : host_window->Size;
|
|
|
|
// Calculate drop shapes geometry for allowed splitting directions
|
|
IM_ASSERT(ImGuiDir_None == -1);
|
|
data->SplitNode = host_node;
|
|
data->SplitDir = ImGuiDir_None;
|
|
data->IsSplitDirExplicit = false;
|
|
if (!host_window->Collapsed)
|
|
for (int dir = ImGuiDir_None; dir < ImGuiDir_COUNT; dir++)
|
|
{
|
|
if (dir == ImGuiDir_None && !data->IsCenterAvailable)
|
|
continue;
|
|
if (dir != ImGuiDir_None && !data->IsSidesAvailable)
|
|
continue;
|
|
if (DockNodeCalcDropRectsAndTestMousePos(data->FutureNode.Rect(), (ImGuiDir)dir,
|
|
data->DropRectsDraw[dir + 1], is_outer_docking, &g.IO.MousePos))
|
|
{
|
|
data->SplitDir = (ImGuiDir)dir;
|
|
data->IsSplitDirExplicit = true;
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
data->IsDropAllowed = false;
|
|
|
|
// Calculate split area
|
|
data->SplitRatio = 0.0f;
|
|
if (data->SplitDir != ImGuiDir_None)
|
|
{
|
|
ImGuiDir split_dir = data->SplitDir;
|
|
ImGuiAxis split_axis = (split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Right) ? ImGuiAxis_X : ImGuiAxis_Y;
|
|
ImVec2 pos_new, pos_old = data->FutureNode.Pos;
|
|
ImVec2 size_new, size_old = data->FutureNode.Size;
|
|
DockNodeCalcSplitRects(pos_old, size_old, pos_new, size_new, split_dir, 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);
|
|
}
|
|
}
|
|
|
|
static void ImGui::DockNodePreviewDockRender(ImGuiWindow* host_window,
|
|
ImGuiDockNode* host_node,
|
|
ImGuiWindow* root_payload,
|
|
const ImGuiDockPreviewData* data)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(g.CurrentWindow == host_window); // Because we rely on font size to calculate tab sizes
|
|
|
|
// With this option, we only display the preview on the target viewport, and the payload viewport is made
|
|
// transparent. To compensate for the single layer obstructed by the payload, we'll increase the alpha of the
|
|
// preview nodes.
|
|
const bool is_transparent_payload = g.IO.ConfigDockingTransparentPayload;
|
|
|
|
// In case the two windows involved are on different viewports, we will draw the overlay on each of them.
|
|
int overlay_draw_lists_count = 0;
|
|
ImDrawList* overlay_draw_lists[2];
|
|
overlay_draw_lists[overlay_draw_lists_count++] = GetForegroundDrawList(host_window->Viewport);
|
|
if (host_window->Viewport != root_payload->Viewport && !is_transparent_payload)
|
|
overlay_draw_lists[overlay_draw_lists_count++] = GetForegroundDrawList(root_payload->Viewport);
|
|
|
|
// Draw main preview rectangle
|
|
const ImU32 overlay_col_main = GetColorU32(ImGuiCol_DockingPreview, is_transparent_payload ? 0.60f : 0.40f);
|
|
const ImU32 overlay_col_drop = GetColorU32(ImGuiCol_DockingPreview, is_transparent_payload ? 0.90f : 0.70f);
|
|
const ImU32 overlay_col_drop_hovered = GetColorU32(ImGuiCol_DockingPreview, is_transparent_payload ? 1.20f : 1.00f);
|
|
const ImU32 overlay_col_lines = GetColorU32(ImGuiCol_NavWindowingHighlight, is_transparent_payload ? 0.80f : 0.60f);
|
|
|
|
// Display area preview
|
|
const bool can_preview_tabs =
|
|
(root_payload->DockNodeAsHost == NULL || root_payload->DockNodeAsHost->Windows.Size > 0);
|
|
if (data->IsDropAllowed)
|
|
{
|
|
ImRect overlay_rect = data->FutureNode.Rect();
|
|
if (data->SplitDir == ImGuiDir_None && can_preview_tabs)
|
|
overlay_rect.Min.y += GetFrameHeight();
|
|
if (data->SplitDir != ImGuiDir_None || data->IsCenterAvailable)
|
|
for (int overlay_n = 0; overlay_n < overlay_draw_lists_count; overlay_n++)
|
|
overlay_draw_lists[overlay_n]->AddRectFilled(overlay_rect.Min, overlay_rect.Max, overlay_col_main,
|
|
host_window->WindowRounding);
|
|
}
|
|
|
|
// Display tab shape/label preview unless we are splitting node (it generally makes the situation harder to read)
|
|
if (data->IsDropAllowed && can_preview_tabs && data->SplitDir == ImGuiDir_None && data->IsCenterAvailable)
|
|
{
|
|
// Compute target tab bar geometry so we can locate our preview tabs
|
|
ImRect tab_bar_rect;
|
|
DockNodeCalcTabBarLayout(&data->FutureNode, NULL, &tab_bar_rect, NULL, NULL);
|
|
ImVec2 tab_pos = tab_bar_rect.Min;
|
|
if (host_node && host_node->TabBar)
|
|
{
|
|
if (!host_node->IsHiddenTabBar() && !host_node->IsNoTabBar())
|
|
tab_pos.x +=
|
|
host_node->TabBar->WidthAllTabs +
|
|
g.Style.ItemInnerSpacing.x; // We don't use OffsetNewTab because when using non-persistent-order
|
|
// tab bar it is incremented with each Tab submission.
|
|
else
|
|
tab_pos.x += g.Style.ItemInnerSpacing.x +
|
|
TabItemCalcSize(host_node->Windows[0]->Name, host_node->Windows[0]->HasCloseButton).x;
|
|
}
|
|
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);
|
|
ImGuiTabBar* tab_bar_with_payload = root_payload->DockNodeAsHost ? root_payload->DockNodeAsHost->TabBar : NULL;
|
|
const int payload_count = tab_bar_with_payload ? tab_bar_with_payload->Tabs.Size : 1;
|
|
for (int payload_n = 0; payload_n < payload_count; payload_n++)
|
|
{
|
|
// DockNode's TabBar may have non-window Tabs manually appended by user
|
|
ImGuiWindow* payload_window =
|
|
tab_bar_with_payload ? tab_bar_with_payload->Tabs[payload_n].Window : root_payload;
|
|
if (tab_bar_with_payload && payload_window == NULL)
|
|
continue;
|
|
if (!DockNodeIsDropAllowedOne(payload_window, host_window))
|
|
continue;
|
|
|
|
// Calculate the tab bounding box for each payload window
|
|
ImVec2 tab_size = TabItemCalcSize(payload_window->Name, payload_window->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;
|
|
const ImU32 overlay_col_text = GetColorU32(payload_window->DockStyle.Colors[ImGuiWindowDockStyleCol_Text]);
|
|
const ImU32 overlay_col_tabs =
|
|
GetColorU32(payload_window->DockStyle.Colors[ImGuiWindowDockStyleCol_TabActive]);
|
|
PushStyleColor(ImGuiCol_Text, overlay_col_text);
|
|
for (int overlay_n = 0; overlay_n < overlay_draw_lists_count; overlay_n++)
|
|
{
|
|
ImGuiTabItemFlags tab_flags =
|
|
ImGuiTabItemFlags_Preview |
|
|
((payload_window->Flags & ImGuiWindowFlags_UnsavedDocument) ? ImGuiTabItemFlags_UnsavedDocument
|
|
: 0);
|
|
if (!tab_bar_rect.Contains(tab_bb))
|
|
overlay_draw_lists[overlay_n]->PushClipRect(tab_bar_rect.Min, tab_bar_rect.Max);
|
|
TabItemBackground(overlay_draw_lists[overlay_n], tab_bb, tab_flags, overlay_col_tabs);
|
|
TabItemLabelAndCloseButton(overlay_draw_lists[overlay_n], tab_bb, tab_flags, g.Style.FramePadding,
|
|
payload_window->Name, 0, 0, false, NULL, NULL);
|
|
if (!tab_bar_rect.Contains(tab_bb))
|
|
overlay_draw_lists[overlay_n]->PopClipRect();
|
|
}
|
|
PopStyleColor();
|
|
}
|
|
}
|
|
|
|
// Display drop boxes
|
|
const float overlay_rounding = ImMax(3.0f, g.Style.FrameRounding);
|
|
for (int dir = ImGuiDir_None; dir < ImGuiDir_COUNT; dir++)
|
|
{
|
|
if (!data->DropRectsDraw[dir + 1].IsInverted())
|
|
{
|
|
ImRect draw_r = data->DropRectsDraw[dir + 1];
|
|
ImRect draw_r_in = draw_r;
|
|
draw_r_in.Expand(-2.0f);
|
|
ImU32 overlay_col = (data->SplitDir == (ImGuiDir)dir && data->IsSplitDirExplicit) ? overlay_col_drop_hovered
|
|
: overlay_col_drop;
|
|
for (int overlay_n = 0; overlay_n < overlay_draw_lists_count; overlay_n++)
|
|
{
|
|
ImVec2 center = ImFloor(draw_r_in.GetCenter());
|
|
overlay_draw_lists[overlay_n]->AddRectFilled(draw_r.Min, draw_r.Max, overlay_col, overlay_rounding);
|
|
overlay_draw_lists[overlay_n]->AddRect(draw_r_in.Min, draw_r_in.Max, overlay_col_lines,
|
|
overlay_rounding);
|
|
if (dir == ImGuiDir_Left || dir == ImGuiDir_Right)
|
|
overlay_draw_lists[overlay_n]->AddLine(ImVec2(center.x, draw_r_in.Min.y),
|
|
ImVec2(center.x, draw_r_in.Max.y), overlay_col_lines);
|
|
if (dir == ImGuiDir_Up || dir == ImGuiDir_Down)
|
|
overlay_draw_lists[overlay_n]->AddLine(ImVec2(draw_r_in.Min.x, center.y),
|
|
ImVec2(draw_r_in.Max.x, center.y), overlay_col_lines);
|
|
}
|
|
}
|
|
|
|
// Stop after ImGuiDir_None
|
|
if ((host_node && (host_node->MergedFlags & ImGuiDockNodeFlags_NoSplit)) || g.IO.ConfigDockingNoSplit)
|
|
return;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Docking: ImGuiDockNode Tree manipulation functions
|
|
//-----------------------------------------------------------------------------
|
|
// - DockNodeTreeSplit()
|
|
// - DockNodeTreeMerge()
|
|
// - DockNodeTreeUpdatePosSize()
|
|
// - DockNodeTreeUpdateSplitterFindTouchingNode()
|
|
// - DockNodeTreeUpdateSplitter()
|
|
// - DockNodeTreeFindFallbackLeafNode()
|
|
// - DockNodeTreeFindNodeByPos()
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void ImGui::DockNodeTreeSplit(ImGuiContext* ctx,
|
|
ImGuiDockNode* parent_node,
|
|
ImGuiAxis split_axis,
|
|
int split_inheritor_child_idx,
|
|
float split_ratio,
|
|
ImGuiDockNode* new_node)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(split_axis != ImGuiAxis_None);
|
|
|
|
ImGuiDockNode* child_0 = (new_node && split_inheritor_child_idx != 0) ? new_node : DockContextAddNode(ctx, 0);
|
|
child_0->ParentNode = parent_node;
|
|
|
|
ImGuiDockNode* child_1 = (new_node && split_inheritor_child_idx != 1) ? new_node : DockContextAddNode(ctx, 0);
|
|
child_1->ParentNode = parent_node;
|
|
|
|
ImGuiDockNode* child_inheritor = (split_inheritor_child_idx == 0) ? child_0 : child_1;
|
|
DockNodeMoveChildNodes(child_inheritor, parent_node);
|
|
parent_node->ChildNodes[0] = child_0;
|
|
parent_node->ChildNodes[1] = child_1;
|
|
parent_node->ChildNodes[split_inheritor_child_idx]->VisibleWindow = parent_node->VisibleWindow;
|
|
parent_node->SplitAxis = split_axis;
|
|
parent_node->VisibleWindow = NULL;
|
|
parent_node->AuthorityForPos = parent_node->AuthorityForSize = ImGuiDataAuthority_DockNode;
|
|
|
|
float size_avail = (parent_node->Size[split_axis] - DOCKING_SPLITTER_SIZE);
|
|
size_avail = ImMax(size_avail, g.Style.WindowMinSize[split_axis] * 2.0f);
|
|
IM_ASSERT(size_avail > 0.0f); // If you created a node manually with DockBuilderAddNode(), you need to also call
|
|
// DockBuilderSetNodeSize() before splitting.
|
|
child_0->SizeRef = child_1->SizeRef = parent_node->Size;
|
|
child_0->SizeRef[split_axis] = 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);
|
|
DockSettingsRenameNodeReferences(parent_node->ID, parent_node->ChildNodes[split_inheritor_child_idx]->ID);
|
|
DockNodeTreeUpdatePosSize(parent_node, parent_node->Pos, parent_node->Size);
|
|
|
|
// Flags transfer (e.g. this is where we transfer the ImGuiDockNodeFlags_CentralNode property)
|
|
child_0->SharedFlags = parent_node->SharedFlags & ImGuiDockNodeFlags_SharedFlagsInheritMask_;
|
|
child_1->SharedFlags = parent_node->SharedFlags & ImGuiDockNodeFlags_SharedFlagsInheritMask_;
|
|
child_inheritor->LocalFlags = parent_node->LocalFlags & ImGuiDockNodeFlags_LocalFlagsTransferMask_;
|
|
parent_node->LocalFlags &= ~ImGuiDockNodeFlags_LocalFlagsTransferMask_;
|
|
child_0->UpdateMergedFlags();
|
|
child_1->UpdateMergedFlags();
|
|
parent_node->UpdateMergedFlags();
|
|
if (child_inheritor->IsCentralNode())
|
|
DockNodeGetRootNode(parent_node)->CentralNode = child_inheritor;
|
|
}
|
|
|
|
void ImGui::DockNodeTreeMerge(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImGuiDockNode* merge_lead_child)
|
|
{
|
|
// When called from DockContextProcessUndockNode() it is possible that one of the child is NULL.
|
|
ImGuiDockNode* child_0 = parent_node->ChildNodes[0];
|
|
ImGuiDockNode* child_1 = parent_node->ChildNodes[1];
|
|
IM_ASSERT(child_0 || child_1);
|
|
IM_ASSERT(merge_lead_child == child_0 || merge_lead_child == child_1);
|
|
if ((child_0 && child_0->Windows.Size > 0) || (child_1 && child_1->Windows.Size > 0))
|
|
{
|
|
IM_ASSERT(parent_node->TabBar == NULL);
|
|
IM_ASSERT(parent_node->Windows.Size == 0);
|
|
}
|
|
IMGUI_DEBUG_LOG_DOCKING("DockNodeTreeMerge 0x%08X & 0x%08X back into parent 0x%08X\n", child_0 ? child_0->ID : 0,
|
|
child_1 ? child_1->ID : 0, parent_node->ID);
|
|
|
|
ImVec2 backup_last_explicit_size = parent_node->SizeRef;
|
|
DockNodeMoveChildNodes(parent_node, merge_lead_child);
|
|
if (child_0)
|
|
{
|
|
DockNodeMoveWindows(parent_node, child_0); // Generally only 1 of the 2 child node will have windows
|
|
DockSettingsRenameNodeReferences(child_0->ID, parent_node->ID);
|
|
}
|
|
if (child_1)
|
|
{
|
|
DockNodeMoveWindows(parent_node, child_1);
|
|
DockSettingsRenameNodeReferences(child_1->ID, parent_node->ID);
|
|
}
|
|
DockNodeApplyPosSizeToWindows(parent_node);
|
|
parent_node->AuthorityForPos = parent_node->AuthorityForSize = parent_node->AuthorityForViewport =
|
|
ImGuiDataAuthority_Auto;
|
|
parent_node->VisibleWindow = merge_lead_child->VisibleWindow;
|
|
parent_node->SizeRef = backup_last_explicit_size;
|
|
|
|
// Flags transfer
|
|
parent_node->LocalFlags &= ~ImGuiDockNodeFlags_LocalFlagsTransferMask_; // Preserve Dockspace flag
|
|
parent_node->LocalFlags |= (child_0 ? child_0->LocalFlags : 0) & ImGuiDockNodeFlags_LocalFlagsTransferMask_;
|
|
parent_node->LocalFlags |= (child_1 ? child_1->LocalFlags : 0) & ImGuiDockNodeFlags_LocalFlagsTransferMask_;
|
|
parent_node->LocalFlagsInWindows =
|
|
(child_0 ? child_0->LocalFlagsInWindows : 0) |
|
|
(child_1 ? child_1->LocalFlagsInWindows : 0); // FIXME: Would be more consistent to update from actual windows
|
|
parent_node->UpdateMergedFlags();
|
|
|
|
if (child_0)
|
|
{
|
|
ctx->DockContext.Nodes.SetVoidPtr(child_0->ID, NULL);
|
|
IM_DELETE(child_0);
|
|
}
|
|
if (child_1)
|
|
{
|
|
ctx->DockContext.Nodes.SetVoidPtr(child_1->ID, NULL);
|
|
IM_DELETE(child_1);
|
|
}
|
|
}
|
|
|
|
// Update Pos/Size for a node hierarchy (don't affect child Windows yet)
|
|
void ImGui::DockNodeTreeUpdatePosSize(ImGuiDockNode* node, ImVec2 pos, ImVec2 size, bool only_write_to_marked_nodes)
|
|
{
|
|
// During the regular dock node update we write to all nodes.
|
|
// 'only_write_to_marked_nodes' is only set when turning a node visible mid-frame and we need its size right-away.
|
|
const bool write_to_node = (only_write_to_marked_nodes == false) || (node->MarkedForPosSizeWrite);
|
|
if (write_to_node)
|
|
{
|
|
node->Pos = pos;
|
|
node->Size = size;
|
|
}
|
|
|
|
if (node->IsLeafNode())
|
|
return;
|
|
|
|
ImGuiDockNode* child_0 = node->ChildNodes[0];
|
|
ImGuiDockNode* child_1 = node->ChildNodes[1];
|
|
ImVec2 child_0_pos = pos, child_1_pos = pos;
|
|
ImVec2 child_0_size = size, child_1_size = size;
|
|
if (child_0->IsVisible && child_1->IsVisible)
|
|
{
|
|
const float spacing = DOCKING_SPLITTER_SIZE;
|
|
const ImGuiAxis axis = (ImGuiAxis)node->SplitAxis;
|
|
const float size_avail = ImMax(size[axis] - spacing, 0.0f);
|
|
|
|
// Size allocation policy
|
|
// 1) The first 0..WindowMinSize[axis]*2 are allocated evenly to both windows.
|
|
ImGuiContext& g = *GImGui;
|
|
const float size_min_each = ImFloor(ImMin(size_avail, g.Style.WindowMinSize[axis] * 2.0f) * 0.5f);
|
|
|
|
// 2) Process locked absolute size (during a splitter resize we preserve the child of nodes not touching the
|
|
// splitter edge)
|
|
if (child_0->WantLockSizeOnce && !child_1->WantLockSizeOnce)
|
|
{
|
|
child_0_size[axis] = child_0->SizeRef[axis] = ImMin(size_avail - 1.0f, child_0->Size[axis]);
|
|
child_1_size[axis] = child_1->SizeRef[axis] = (size_avail - child_0_size[axis]);
|
|
IM_ASSERT(child_0->SizeRef[axis] > 0.0f && child_1->SizeRef[axis] > 0.0f);
|
|
}
|
|
else if (child_1->WantLockSizeOnce && !child_0->WantLockSizeOnce)
|
|
{
|
|
child_1_size[axis] = child_1->SizeRef[axis] = ImMin(size_avail - 1.0f, child_1->Size[axis]);
|
|
child_0_size[axis] = child_0->SizeRef[axis] = (size_avail - child_1_size[axis]);
|
|
IM_ASSERT(child_0->SizeRef[axis] > 0.0f && child_1->SizeRef[axis] > 0.0f);
|
|
}
|
|
else if (child_0->WantLockSizeOnce && child_1->WantLockSizeOnce)
|
|
{
|
|
// FIXME-DOCK: We cannot honor the requested size, so apply ratio.
|
|
// Currently this path will only be taken if code programmatically sets WantLockSizeOnce
|
|
float ratio_0 = child_0_size[axis] / (child_0_size[axis] + child_1_size[axis]);
|
|
child_0_size[axis] = child_0->SizeRef[axis] = ImFloor(size_avail * ratio_0);
|
|
child_1_size[axis] = child_1->SizeRef[axis] = (size_avail - child_0_size[axis]);
|
|
IM_ASSERT(child_0->SizeRef[axis] > 0.0f && child_1->SizeRef[axis] > 0.0f);
|
|
}
|
|
|
|
// 3) If one window is the central node (~ use remaining space, should be made explicit!), use explicit size
|
|
// from the other, and remainder for the central node
|
|
else if (child_1->IsCentralNode() && 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->IsCentralNode() && 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];
|
|
}
|
|
child_0->WantLockSizeOnce = child_1->WantLockSizeOnce = false;
|
|
|
|
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);
|
|
}
|
|
|
|
static void DockNodeTreeUpdateSplitterFindTouchingNode(ImGuiDockNode* node,
|
|
ImGuiAxis axis,
|
|
int side,
|
|
ImVector<ImGuiDockNode*>* touching_nodes)
|
|
{
|
|
if (node->IsLeafNode())
|
|
{
|
|
touching_nodes->push_back(node);
|
|
return;
|
|
}
|
|
if (node->ChildNodes[0]->IsVisible)
|
|
if (node->SplitAxis != axis || side == 0 || !node->ChildNodes[1]->IsVisible)
|
|
DockNodeTreeUpdateSplitterFindTouchingNode(node->ChildNodes[0], axis, side, touching_nodes);
|
|
if (node->ChildNodes[1]->IsVisible)
|
|
if (node->SplitAxis != axis || side == 1 || !node->ChildNodes[0]->IsVisible)
|
|
DockNodeTreeUpdateSplitterFindTouchingNode(node->ChildNodes[1], axis, side, touching_nodes);
|
|
}
|
|
|
|
void ImGui::DockNodeTreeUpdateSplitter(ImGuiDockNode* node)
|
|
{
|
|
if (node->IsLeafNode())
|
|
return;
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
|
|
ImGuiDockNode* child_0 = node->ChildNodes[0];
|
|
ImGuiDockNode* child_1 = node->ChildNodes[1];
|
|
if (child_0->IsVisible && child_1->IsVisible)
|
|
{
|
|
// Bounding box of the splitter cover the space between both nodes (w = Spacing, h = Size[xy^1] for when
|
|
// splitting horizontally)
|
|
const ImGuiAxis axis = (ImGuiAxis)node->SplitAxis;
|
|
IM_ASSERT(axis != ImGuiAxis_None);
|
|
ImRect bb;
|
|
bb.Min = child_0->Pos;
|
|
bb.Max = child_1->Pos;
|
|
bb.Min[axis] += child_0->Size[axis];
|
|
bb.Max[axis ^ 1] += child_1->Size[axis ^ 1];
|
|
// if (g.IO.KeyCtrl) GetForegroundDrawList(g.CurrentWindow->Viewport)->AddRect(bb.Min, bb.Max,
|
|
// IM_COL32(255,0,255,255));
|
|
|
|
const ImGuiDockNodeFlags merged_flags =
|
|
child_0->MergedFlags | child_1->MergedFlags; // Merged flags for BOTH childs
|
|
const ImGuiDockNodeFlags no_resize_axis_flag =
|
|
(axis == ImGuiAxis_X) ? ImGuiDockNodeFlags_NoResizeX : ImGuiDockNodeFlags_NoResizeY;
|
|
if ((merged_flags & ImGuiDockNodeFlags_NoResize) || (merged_flags & no_resize_axis_flag))
|
|
{
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_Separator), g.Style.FrameRounding);
|
|
}
|
|
else
|
|
{
|
|
// bb.Min[axis] += 1; // Display a little inward so highlight doesn't connect with nearby tabs on the
|
|
// neighbor node. bb.Max[axis] -= 1;
|
|
PushID(node->ID);
|
|
|
|
// Gather list of nodes that are touching the splitter line. Find resizing limits based on those nodes.
|
|
ImVector<ImGuiDockNode*> touching_nodes[2];
|
|
float min_size = g.Style.WindowMinSize[axis];
|
|
float resize_limits[2];
|
|
resize_limits[0] = node->ChildNodes[0]->Pos[axis] + min_size;
|
|
resize_limits[1] = node->ChildNodes[1]->Pos[axis] + node->ChildNodes[1]->Size[axis] - min_size;
|
|
|
|
ImGuiID splitter_id = GetID("##Splitter");
|
|
if (g.ActiveId == splitter_id)
|
|
{
|
|
// Only process when splitter is active
|
|
DockNodeTreeUpdateSplitterFindTouchingNode(child_0, axis, 1, &touching_nodes[0]);
|
|
DockNodeTreeUpdateSplitterFindTouchingNode(child_1, axis, 0, &touching_nodes[1]);
|
|
for (int touching_node_n = 0; touching_node_n < touching_nodes[0].Size; touching_node_n++)
|
|
resize_limits[0] =
|
|
ImMax(resize_limits[0], touching_nodes[0][touching_node_n]->Rect().Min[axis] + min_size);
|
|
for (int touching_node_n = 0; touching_node_n < touching_nodes[1].Size; touching_node_n++)
|
|
resize_limits[1] =
|
|
ImMin(resize_limits[1], touching_nodes[1][touching_node_n]->Rect().Max[axis] - min_size);
|
|
|
|
/*
|
|
// [DEBUG] Render limits
|
|
ImDrawList* draw_list = node->HostWindow ? GetForegroundDrawList(node->HostWindow) :
|
|
GetForegroundDrawList(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);
|
|
*/
|
|
}
|
|
|
|
// Use a short delay before highlighting the splitter (and changing the mouse cursor) in order for regular
|
|
// mouse movement to not highlight many splitters
|
|
float cur_size_0 = child_0->Size[axis];
|
|
float cur_size_1 = child_1->Size[axis];
|
|
float min_size_0 = resize_limits[0] - child_0->Pos[axis];
|
|
float min_size_1 = child_1->Pos[axis] + child_1->Size[axis] - resize_limits[1];
|
|
if (SplitterBehavior(bb, GetID("##Splitter"), axis, &cur_size_0, &cur_size_1, min_size_0, min_size_1,
|
|
WINDOWS_HOVER_PADDING, WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER))
|
|
{
|
|
if (touching_nodes[0].Size > 0 && touching_nodes[1].Size > 0)
|
|
{
|
|
child_0->Size[axis] = child_0->SizeRef[axis] = cur_size_0;
|
|
child_1->Pos[axis] -= cur_size_1 - child_1->Size[axis];
|
|
child_1->Size[axis] = child_1->SizeRef[axis] = cur_size_1;
|
|
|
|
// Lock the size of every node that is a sibling of the node we are touching
|
|
// This might be less desirable if we can merge sibling of a same axis into the same parental level.
|
|
for (int side_n = 0; side_n < 2; side_n++)
|
|
for (int touching_node_n = 0; touching_node_n < touching_nodes[side_n].Size; touching_node_n++)
|
|
{
|
|
ImGuiDockNode* touching_node = touching_nodes[side_n][touching_node_n];
|
|
// ImDrawList* draw_list = node->HostWindow ? GetForegroundDrawList(node->HostWindow) :
|
|
// GetForegroundDrawList(GetMainViewport()); draw_list->AddRect(touching_node->Pos,
|
|
// touching_node->Pos + touching_node->Size, IM_COL32(255, 128, 0, 255));
|
|
while (touching_node->ParentNode != node)
|
|
{
|
|
if (touching_node->ParentNode->SplitAxis == axis)
|
|
{
|
|
// Mark other node so its size will be preserved during the upcoming call to
|
|
// DockNodeTreeUpdatePosSize().
|
|
ImGuiDockNode* node_to_preserve = touching_node->ParentNode->ChildNodes[side_n];
|
|
node_to_preserve->WantLockSizeOnce = true;
|
|
// draw_list->AddRect(touching_node->Pos, touching_node->Rect().Max, IM_COL32(255,
|
|
// 0, 0, 255)); draw_list->AddRectFilled(node_to_preserve->Pos,
|
|
// node_to_preserve->Rect().Max, IM_COL32(0, 255, 0, 100));
|
|
}
|
|
touching_node = touching_node->ParentNode;
|
|
}
|
|
}
|
|
|
|
DockNodeTreeUpdatePosSize(child_0, child_0->Pos, child_0->Size);
|
|
DockNodeTreeUpdatePosSize(child_1, child_1->Pos, child_1->Size);
|
|
MarkIniSettingsDirty();
|
|
}
|
|
}
|
|
PopID();
|
|
}
|
|
}
|
|
|
|
if (child_0->IsVisible)
|
|
DockNodeTreeUpdateSplitter(child_0);
|
|
if (child_1->IsVisible)
|
|
DockNodeTreeUpdateSplitter(child_1);
|
|
}
|
|
|
|
ImGuiDockNode* ImGui::DockNodeTreeFindFallbackLeafNode(ImGuiDockNode* node)
|
|
{
|
|
if (node->IsLeafNode())
|
|
return node;
|
|
if (ImGuiDockNode* leaf_node = DockNodeTreeFindFallbackLeafNode(node->ChildNodes[0]))
|
|
return leaf_node;
|
|
if (ImGuiDockNode* leaf_node = DockNodeTreeFindFallbackLeafNode(node->ChildNodes[1]))
|
|
return leaf_node;
|
|
return NULL;
|
|
}
|
|
|
|
ImGuiDockNode* ImGui::DockNodeTreeFindVisibleNodeByPos(ImGuiDockNode* node, ImVec2 pos)
|
|
{
|
|
if (!node->IsVisible)
|
|
return NULL;
|
|
|
|
const float dock_spacing = 0.0f; // g.Style.ItemInnerSpacing.x; // FIXME: Relation to DOCKING_SPLITTER_SIZE?
|
|
ImRect r(node->Pos, node->Pos + node->Size);
|
|
r.Expand(dock_spacing * 0.5f);
|
|
bool inside = r.Contains(pos);
|
|
if (!inside)
|
|
return NULL;
|
|
|
|
if (node->IsLeafNode())
|
|
return node;
|
|
if (ImGuiDockNode* hovered_node = DockNodeTreeFindVisibleNodeByPos(node->ChildNodes[0], pos))
|
|
return hovered_node;
|
|
if (ImGuiDockNode* hovered_node = DockNodeTreeFindVisibleNodeByPos(node->ChildNodes[1], pos))
|
|
return hovered_node;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Docking: Public Functions (SetWindowDock, DockSpace, DockSpaceOverViewport)
|
|
//-----------------------------------------------------------------------------
|
|
// - SetWindowDock() [Internal]
|
|
// - DockSpace()
|
|
// - DockSpaceOverViewport()
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// [Internal] Called via SetNextWindowDockID()
|
|
void ImGui::SetWindowDock(ImGuiWindow* window, ImGuiID dock_id, ImGuiCond cond)
|
|
{
|
|
// Test condition (NB: bit 0 is always true) and clear flags for next time
|
|
if (cond && (window->SetWindowDockAllowFlags & cond) == 0)
|
|
return;
|
|
window->SetWindowDockAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
|
|
|
|
if (window->DockId == dock_id)
|
|
return;
|
|
|
|
// If the user attempt to set a dock id that is a split node, we'll dig within to find a suitable docking spot
|
|
ImGuiContext* ctx = GImGui;
|
|
if (ImGuiDockNode* new_node = DockContextFindNodeByID(ctx, dock_id))
|
|
if (new_node->IsSplitNode())
|
|
{
|
|
// Policy: Find central node or latest focused node. We first move back to our root node.
|
|
new_node = DockNodeGetRootNode(new_node);
|
|
if (new_node->CentralNode)
|
|
{
|
|
IM_ASSERT(new_node->CentralNode->IsCentralNode());
|
|
dock_id = new_node->CentralNode->ID;
|
|
}
|
|
else
|
|
{
|
|
dock_id = new_node->LastFocusedNodeId;
|
|
}
|
|
}
|
|
|
|
if (window->DockId == dock_id)
|
|
return;
|
|
|
|
if (window->DockNode)
|
|
DockNodeRemoveWindow(window->DockNode, window, 0);
|
|
window->DockId = dock_id;
|
|
}
|
|
|
|
// Create an explicit dockspace node within an existing window. Also expose dock node flags and creates a CentralNode by
|
|
// default. The Central Node is always displayed even when empty and shrink/extend according to the requested size of
|
|
// its neighbors. DockSpace() needs to be submitted _before_ any window they can host. If you use a dockspace, submit it
|
|
// early in your app.
|
|
ImGuiID ImGui::DockSpace(ImGuiID id,
|
|
const ImVec2& size_arg,
|
|
ImGuiDockNodeFlags flags,
|
|
const ImGuiWindowClass* window_class)
|
|
{
|
|
ImGuiContext* ctx = GImGui;
|
|
ImGuiContext& g = *ctx;
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
if (!(g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable))
|
|
return 0;
|
|
|
|
// Early out if parent window is hidden/collapsed
|
|
// This is faster but also DockNodeUpdateTabBar() relies on TabBarLayout() running (which won't if SkipItems=true)
|
|
// to set NextSelectedTabId = 0). See #2960. If for whichever reason this is causing problem we would need to ensure
|
|
// that DockNodeUpdateTabBar() ends up clearing NextSelectedTabId even if SkipItems=true.
|
|
if (window->SkipItems)
|
|
flags |= ImGuiDockNodeFlags_KeepAliveOnly;
|
|
|
|
IM_ASSERT((flags & ImGuiDockNodeFlags_DockSpace) == 0);
|
|
IM_ASSERT(id != 0);
|
|
ImGuiDockNode* node = DockContextFindNodeByID(ctx, id);
|
|
if (!node)
|
|
{
|
|
IMGUI_DEBUG_LOG_DOCKING("DockSpace: dockspace node 0x%08X created\n", id);
|
|
node = DockContextAddNode(ctx, id);
|
|
node->SetLocalFlags(ImGuiDockNodeFlags_CentralNode);
|
|
}
|
|
if (window_class && window_class->ClassId != node->WindowClass.ClassId)
|
|
IMGUI_DEBUG_LOG_DOCKING("DockSpace: dockspace node 0x%08X: setup WindowClass 0x%08X -> 0x%08X\n", id,
|
|
node->WindowClass.ClassId, window_class->ClassId);
|
|
node->SharedFlags = flags;
|
|
node->WindowClass = window_class ? *window_class : ImGuiWindowClass();
|
|
|
|
// When a DockSpace transitioned form implicit to explicit this may be called a second time
|
|
// It is possible that the node has already been claimed by a docked window which appeared before the DockSpace()
|
|
// node, so we overwrite IsDockSpace again.
|
|
if (node->LastFrameActive == g.FrameCount && !(flags & ImGuiDockNodeFlags_KeepAliveOnly))
|
|
{
|
|
IM_ASSERT(node->IsDockSpace() == false && "Cannot call DockSpace() twice a frame with the same ID");
|
|
node->SetLocalFlags(node->LocalFlags | ImGuiDockNodeFlags_DockSpace);
|
|
return id;
|
|
}
|
|
node->SetLocalFlags(node->LocalFlags | ImGuiDockNodeFlags_DockSpace);
|
|
|
|
// Keep alive mode, this is allow windows docked into this node so stay docked even if they are not visible
|
|
if (flags & ImGuiDockNodeFlags_KeepAliveOnly)
|
|
{
|
|
node->LastFrameAlive = g.FrameCount;
|
|
return id;
|
|
}
|
|
|
|
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);
|
|
IM_ASSERT(size.x > 0.0f && size.y > 0.0f);
|
|
|
|
node->Pos = window->DC.CursorPos;
|
|
node->Size = node->SizeRef = size;
|
|
SetNextWindowPos(node->Pos);
|
|
SetNextWindowSize(node->Size);
|
|
g.NextWindowData.PosUndock = false;
|
|
|
|
// FIXME-DOCK: Why do we need a child window to host a dockspace, could we host it in the existing window?
|
|
// FIXME-DOCK: What is the reason for not simply calling BeginChild()? (OK to have a reason but should be commented)
|
|
ImGuiWindowFlags window_flags = ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_DockNodeHost;
|
|
window_flags |= ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse |
|
|
ImGuiWindowFlags_NoTitleBar;
|
|
window_flags |= ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse;
|
|
window_flags |= ImGuiWindowFlags_NoBackground;
|
|
|
|
char title[256];
|
|
ImFormatString(title, IM_ARRAYSIZE(title), "%s/DockSpace_%08X", window->Name, id);
|
|
|
|
PushStyleVar(ImGuiStyleVar_ChildBorderSize, 0.0f);
|
|
Begin(title, NULL, window_flags);
|
|
PopStyleVar();
|
|
|
|
ImGuiWindow* host_window = g.CurrentWindow;
|
|
DockNodeSetupHostWindow(node, host_window);
|
|
host_window->ChildId = window->GetID(title);
|
|
node->OnlyNodeWithWindows = NULL;
|
|
|
|
IM_ASSERT(node->IsRootNode());
|
|
|
|
// We need to handle the rare case were a central node is missing.
|
|
// This can happen if the node was first created manually with DockBuilderAddNode() but _without_ the
|
|
// ImGuiDockNodeFlags_Dockspace. Doing it correctly would set the _CentralNode flags, which would then propagate
|
|
// according to subsequent split. It would also be ambiguous to attempt to assign a central node while there are
|
|
// split nodes, so we wait until there's a single node remaining. The specific sub-property of _CentralNode we are
|
|
// interested in recovering here is the "Don't delete when empty" property, as it doesn't make sense for an empty
|
|
// dockspace to not have this property.
|
|
if (node->IsLeafNode() && !node->IsCentralNode())
|
|
node->SetLocalFlags(node->LocalFlags | ImGuiDockNodeFlags_CentralNode);
|
|
|
|
// Update the node
|
|
DockNodeUpdate(node);
|
|
|
|
End();
|
|
ItemSize(size);
|
|
return id;
|
|
}
|
|
|
|
// Tips: Use with ImGuiDockNodeFlags_PassthruCentralNode!
|
|
// The limitation with this call is that your window won't have a menu bar.
|
|
// Even though we could pass window flags, it would also require the user to be able to call BeginMenuBar() somehow
|
|
// meaning we can't Begin/End in a single function. But you can also use BeginMainMenuBar(). If you really want a menu
|
|
// bar inside the same window as the one hosting the dockspace, you will need to copy this code somewhere and tweak it.
|
|
ImGuiID ImGui::DockSpaceOverViewport(const ImGuiViewport* viewport,
|
|
ImGuiDockNodeFlags dockspace_flags,
|
|
const ImGuiWindowClass* window_class)
|
|
{
|
|
if (viewport == NULL)
|
|
viewport = GetMainViewport();
|
|
|
|
SetNextWindowPos(viewport->WorkPos);
|
|
SetNextWindowSize(viewport->WorkSize);
|
|
SetNextWindowViewport(viewport->ID);
|
|
|
|
ImGuiWindowFlags host_window_flags = 0;
|
|
host_window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize |
|
|
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDocking;
|
|
host_window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;
|
|
if (dockspace_flags & ImGuiDockNodeFlags_PassthruCentralNode)
|
|
host_window_flags |= ImGuiWindowFlags_NoBackground;
|
|
|
|
char label[32];
|
|
ImFormatString(label, IM_ARRAYSIZE(label), "DockSpaceViewport_%08X", viewport->ID);
|
|
|
|
PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
|
|
PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
|
|
PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
|
|
Begin(label, NULL, host_window_flags);
|
|
PopStyleVar(3);
|
|
|
|
ImGuiID dockspace_id = GetID("DockSpace");
|
|
DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags, window_class);
|
|
End();
|
|
|
|
return dockspace_id;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Docking: Builder Functions
|
|
//-----------------------------------------------------------------------------
|
|
// Very early end-user API to manipulate dock nodes.
|
|
// Only available in imgui_internal.h. Expect this API to change/break!
|
|
// It is expected that those functions are all called _before_ the dockspace node submission.
|
|
//-----------------------------------------------------------------------------
|
|
// - DockBuilderDockWindow()
|
|
// - DockBuilderGetNode()
|
|
// - DockBuilderSetNodePos()
|
|
// - DockBuilderSetNodeSize()
|
|
// - DockBuilderAddNode()
|
|
// - DockBuilderRemoveNode()
|
|
// - DockBuilderRemoveNodeChildNodes()
|
|
// - DockBuilderRemoveNodeDockedWindows()
|
|
// - DockBuilderSplitNode()
|
|
// - DockBuilderCopyNodeRec()
|
|
// - DockBuilderCopyNode()
|
|
// - DockBuilderCopyWindowSettings()
|
|
// - DockBuilderCopyDockSpace()
|
|
// - DockBuilderFinish()
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void ImGui::DockBuilderDockWindow(const char* window_name, ImGuiID node_id)
|
|
{
|
|
// We don't preserve relative order of multiple docked windows (by clearing DockOrder back to -1)
|
|
ImGuiID window_id = ImHashStr(window_name);
|
|
if (ImGuiWindow* window = FindWindowByID(window_id))
|
|
{
|
|
// Apply to created window
|
|
SetWindowDock(window, node_id, ImGuiCond_Always);
|
|
window->DockOrder = -1;
|
|
}
|
|
else
|
|
{
|
|
// Apply to settings
|
|
ImGuiWindowSettings* settings = FindWindowSettings(window_id);
|
|
if (settings == NULL)
|
|
settings = CreateNewWindowSettings(window_name);
|
|
settings->DockId = node_id;
|
|
settings->DockOrder = -1;
|
|
}
|
|
}
|
|
|
|
ImGuiDockNode* ImGui::DockBuilderGetNode(ImGuiID node_id)
|
|
{
|
|
ImGuiContext* ctx = GImGui;
|
|
return DockContextFindNodeByID(ctx, node_id);
|
|
}
|
|
|
|
void ImGui::DockBuilderSetNodePos(ImGuiID node_id, ImVec2 pos)
|
|
{
|
|
ImGuiContext* ctx = GImGui;
|
|
ImGuiDockNode* node = DockContextFindNodeByID(ctx, node_id);
|
|
if (node == NULL)
|
|
return;
|
|
node->Pos = pos;
|
|
node->AuthorityForPos = ImGuiDataAuthority_DockNode;
|
|
}
|
|
|
|
void ImGui::DockBuilderSetNodeSize(ImGuiID node_id, ImVec2 size)
|
|
{
|
|
ImGuiContext* ctx = GImGui;
|
|
ImGuiDockNode* node = DockContextFindNodeByID(ctx, node_id);
|
|
if (node == NULL)
|
|
return;
|
|
IM_ASSERT(size.x > 0.0f && size.y > 0.0f);
|
|
node->Size = node->SizeRef = size;
|
|
node->AuthorityForSize = ImGuiDataAuthority_DockNode;
|
|
}
|
|
|
|
// Make sure to use the ImGuiDockNodeFlags_DockSpace flag to create a dockspace node! Otherwise this will create a
|
|
// floating node!
|
|
// - Floating node: you can then call DockBuilderSetNodePos()/DockBuilderSetNodeSize() to position and size the floating
|
|
// node.
|
|
// - Dockspace node: calling DockBuilderSetNodePos() is unnecessary.
|
|
// - If you intend to split a node immediately after creation using DockBuilderSplitNode(), make sure to call
|
|
// DockBuilderSetNodeSize() beforehand!
|
|
// For various reason, the splitting code currently needs a base size otherwise space may not be allocated as
|
|
// precisely as you would expect.
|
|
// - Use (id == 0) to let the system allocate a node identifier.
|
|
// - Existing node with a same id will be removed.
|
|
ImGuiID ImGui::DockBuilderAddNode(ImGuiID id, ImGuiDockNodeFlags flags)
|
|
{
|
|
ImGuiContext* ctx = GImGui;
|
|
|
|
if (id != 0)
|
|
DockBuilderRemoveNode(id);
|
|
|
|
ImGuiDockNode* node = NULL;
|
|
if (flags & ImGuiDockNodeFlags_DockSpace)
|
|
{
|
|
DockSpace(id, ImVec2(0, 0), (flags & ~ImGuiDockNodeFlags_DockSpace) | ImGuiDockNodeFlags_KeepAliveOnly);
|
|
node = DockContextFindNodeByID(ctx, id);
|
|
}
|
|
else
|
|
{
|
|
node = DockContextAddNode(ctx, id);
|
|
node->SetLocalFlags(flags);
|
|
}
|
|
node->LastFrameAlive = ctx->FrameCount; // Set this otherwise BeginDocked will undock during the same frame.
|
|
return node->ID;
|
|
}
|
|
|
|
void ImGui::DockBuilderRemoveNode(ImGuiID node_id)
|
|
{
|
|
ImGuiContext* ctx = GImGui;
|
|
ImGuiDockNode* node = DockContextFindNodeByID(ctx, node_id);
|
|
if (node == NULL)
|
|
return;
|
|
DockBuilderRemoveNodeDockedWindows(node_id, true);
|
|
DockBuilderRemoveNodeChildNodes(node_id);
|
|
// Node may have moved or deleted if e.g. any merge happened
|
|
node = DockContextFindNodeByID(ctx, node_id);
|
|
if (node == NULL)
|
|
return;
|
|
if (node->IsCentralNode() && node->ParentNode)
|
|
node->ParentNode->SetLocalFlags(node->ParentNode->LocalFlags | ImGuiDockNodeFlags_CentralNode);
|
|
DockContextRemoveNode(ctx, node, true);
|
|
}
|
|
|
|
// root_id = 0 to remove all, root_id != 0 to remove child of given node.
|
|
void ImGui::DockBuilderRemoveNodeChildNodes(ImGuiID root_id)
|
|
{
|
|
ImGuiContext* ctx = GImGui;
|
|
ImGuiDockContext* dc = &ctx->DockContext;
|
|
|
|
ImGuiDockNode* root_node = root_id ? DockContextFindNodeByID(ctx, root_id) : NULL;
|
|
if (root_id && root_node == NULL)
|
|
return;
|
|
bool has_central_node = false;
|
|
|
|
ImGuiDataAuthority backup_root_node_authority_for_pos =
|
|
root_node ? root_node->AuthorityForPos : ImGuiDataAuthority_Auto;
|
|
ImGuiDataAuthority backup_root_node_authority_for_size =
|
|
root_node ? root_node->AuthorityForSize : ImGuiDataAuthority_Auto;
|
|
|
|
// Process active windows
|
|
ImVector<ImGuiDockNode*> nodes_to_remove;
|
|
for (int n = 0; n < dc->Nodes.Data.Size; n++)
|
|
if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p)
|
|
{
|
|
bool want_removal = (root_id == 0) || (node->ID != root_id && DockNodeGetRootNode(node)->ID == root_id);
|
|
if (want_removal)
|
|
{
|
|
if (node->IsCentralNode())
|
|
has_central_node = true;
|
|
if (root_id != 0)
|
|
DockContextQueueNotifyRemovedNode(ctx, node);
|
|
if (root_node)
|
|
{
|
|
DockNodeMoveWindows(root_node, node);
|
|
DockSettingsRenameNodeReferences(node->ID, root_node->ID);
|
|
}
|
|
nodes_to_remove.push_back(node);
|
|
}
|
|
}
|
|
|
|
// DockNodeMoveWindows->DockNodeAddWindow will normally set those when reaching two windows (which is only adequate
|
|
// during interactive merge) Make sure we don't lose our current pos/size. (FIXME-DOCK: Consider tidying up that
|
|
// code in DockNodeAddWindow instead)
|
|
if (root_node)
|
|
{
|
|
root_node->AuthorityForPos = backup_root_node_authority_for_pos;
|
|
root_node->AuthorityForSize = backup_root_node_authority_for_size;
|
|
}
|
|
|
|
// Apply to settings
|
|
for (ImGuiWindowSettings* settings = ctx->SettingsWindows.begin(); settings != NULL;
|
|
settings = ctx->SettingsWindows.next_chunk(settings))
|
|
if (ImGuiID window_settings_dock_id = settings->DockId)
|
|
for (int n = 0; n < nodes_to_remove.Size; n++)
|
|
if (nodes_to_remove[n]->ID == window_settings_dock_id)
|
|
{
|
|
settings->DockId = root_id;
|
|
break;
|
|
}
|
|
|
|
// Not really efficient, but easier to destroy a whole hierarchy considering DockContextRemoveNode is attempting to
|
|
// merge nodes
|
|
if (nodes_to_remove.Size > 1)
|
|
ImQsort(nodes_to_remove.Data, nodes_to_remove.Size, sizeof(ImGuiDockNode*), DockNodeComparerDepthMostFirst);
|
|
for (int n = 0; n < nodes_to_remove.Size; n++)
|
|
DockContextRemoveNode(ctx, nodes_to_remove[n], false);
|
|
|
|
if (root_id == 0)
|
|
{
|
|
dc->Nodes.Clear();
|
|
dc->Requests.clear();
|
|
}
|
|
else if (has_central_node)
|
|
{
|
|
root_node->CentralNode = root_node;
|
|
root_node->SetLocalFlags(root_node->LocalFlags | ImGuiDockNodeFlags_CentralNode);
|
|
}
|
|
}
|
|
|
|
void ImGui::DockBuilderRemoveNodeDockedWindows(ImGuiID root_id, bool clear_settings_refs)
|
|
{
|
|
// Clear references in settings
|
|
ImGuiContext* ctx = GImGui;
|
|
ImGuiContext& g = *ctx;
|
|
if (clear_settings_refs)
|
|
{
|
|
for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL;
|
|
settings = g.SettingsWindows.next_chunk(settings))
|
|
{
|
|
bool want_removal = (root_id == 0) || (settings->DockId == root_id);
|
|
if (!want_removal && settings->DockId != 0)
|
|
if (ImGuiDockNode* node = DockContextFindNodeByID(ctx, settings->DockId))
|
|
if (DockNodeGetRootNode(node)->ID == root_id)
|
|
want_removal = true;
|
|
if (want_removal)
|
|
settings->DockId = 0;
|
|
}
|
|
}
|
|
|
|
// Clear references in windows
|
|
for (int n = 0; n < g.Windows.Size; n++)
|
|
{
|
|
ImGuiWindow* window = g.Windows[n];
|
|
bool want_removal = (root_id == 0) ||
|
|
(window->DockNode && DockNodeGetRootNode(window->DockNode)->ID == root_id) ||
|
|
(window->DockNodeAsHost && window->DockNodeAsHost->ID == root_id);
|
|
if (want_removal)
|
|
{
|
|
const ImGuiID backup_dock_id = window->DockId;
|
|
IM_UNUSED(backup_dock_id);
|
|
DockContextProcessUndockWindow(ctx, window, clear_settings_refs);
|
|
if (!clear_settings_refs)
|
|
IM_ASSERT(window->DockId == backup_dock_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If 'out_id_at_dir' or 'out_id_at_opposite_dir' are non NULL, the function will write out the ID of the two new nodes
|
|
// created. Return value is ID of the node at the specified direction, so same as (*out_id_at_dir) if that pointer is
|
|
// set.
|
|
// FIXME-DOCK: We are not exposing nor using split_outer.
|
|
ImGuiID ImGui::DockBuilderSplitNode(ImGuiID id,
|
|
ImGuiDir split_dir,
|
|
float size_ratio_for_node_at_dir,
|
|
ImGuiID* out_id_at_dir,
|
|
ImGuiID* out_id_at_opposite_dir)
|
|
{
|
|
ImGuiContext* ctx = GImGui;
|
|
IM_ASSERT(split_dir != ImGuiDir_None);
|
|
IMGUI_DEBUG_LOG_DOCKING("DockBuilderSplitNode node 0x%08X, split_dir %d\n", id, split_dir);
|
|
|
|
ImGuiDockNode* node = DockContextFindNodeByID(ctx, id);
|
|
if (node == NULL)
|
|
{
|
|
IM_ASSERT(node != NULL);
|
|
return 0;
|
|
}
|
|
|
|
IM_ASSERT(!node->IsSplitNode()); // Assert if already Split
|
|
|
|
ImGuiDockRequest req;
|
|
req.Type = ImGuiDockRequestType_Split;
|
|
req.DockTargetWindow = NULL;
|
|
req.DockTargetNode = node;
|
|
req.DockPayload = NULL;
|
|
req.DockSplitDir = split_dir;
|
|
req.DockSplitRatio =
|
|
ImSaturate((split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Up) ? size_ratio_for_node_at_dir
|
|
: 1.0f - size_ratio_for_node_at_dir);
|
|
req.DockSplitOuter = false;
|
|
DockContextProcessDock(ctx, &req);
|
|
|
|
ImGuiID id_at_dir = node->ChildNodes[(split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Up) ? 0 : 1]->ID;
|
|
ImGuiID id_at_opposite_dir = node->ChildNodes[(split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Up) ? 1 : 0]->ID;
|
|
if (out_id_at_dir)
|
|
*out_id_at_dir = id_at_dir;
|
|
if (out_id_at_opposite_dir)
|
|
*out_id_at_opposite_dir = id_at_opposite_dir;
|
|
return id_at_dir;
|
|
}
|
|
|
|
static ImGuiDockNode* DockBuilderCopyNodeRec(ImGuiDockNode* src_node,
|
|
ImGuiID dst_node_id_if_known,
|
|
ImVector<ImGuiID>* out_node_remap_pairs)
|
|
{
|
|
ImGuiContext* ctx = GImGui;
|
|
ImGuiDockNode* dst_node = ImGui::DockContextAddNode(ctx, dst_node_id_if_known);
|
|
dst_node->SharedFlags = src_node->SharedFlags;
|
|
dst_node->LocalFlags = src_node->LocalFlags;
|
|
dst_node->LocalFlagsInWindows = ImGuiDockNodeFlags_None;
|
|
dst_node->Pos = src_node->Pos;
|
|
dst_node->Size = src_node->Size;
|
|
dst_node->SizeRef = src_node->SizeRef;
|
|
dst_node->SplitAxis = src_node->SplitAxis;
|
|
dst_node->UpdateMergedFlags();
|
|
|
|
out_node_remap_pairs->push_back(src_node->ID);
|
|
out_node_remap_pairs->push_back(dst_node->ID);
|
|
|
|
for (int child_n = 0; child_n < IM_ARRAYSIZE(src_node->ChildNodes); child_n++)
|
|
if (src_node->ChildNodes[child_n])
|
|
{
|
|
dst_node->ChildNodes[child_n] =
|
|
DockBuilderCopyNodeRec(src_node->ChildNodes[child_n], 0, out_node_remap_pairs);
|
|
dst_node->ChildNodes[child_n]->ParentNode = dst_node;
|
|
}
|
|
|
|
IMGUI_DEBUG_LOG_DOCKING("Fork node %08X -> %08X (%d childs)\n", src_node->ID, dst_node->ID,
|
|
dst_node->IsSplitNode() ? 2 : 0);
|
|
return dst_node;
|
|
}
|
|
|
|
void ImGui::DockBuilderCopyNode(ImGuiID src_node_id, ImGuiID dst_node_id, ImVector<ImGuiID>* out_node_remap_pairs)
|
|
{
|
|
ImGuiContext* ctx = GImGui;
|
|
IM_ASSERT(src_node_id != 0);
|
|
IM_ASSERT(dst_node_id != 0);
|
|
IM_ASSERT(out_node_remap_pairs != NULL);
|
|
|
|
DockBuilderRemoveNode(dst_node_id);
|
|
|
|
ImGuiDockNode* src_node = DockContextFindNodeByID(ctx, src_node_id);
|
|
IM_ASSERT(src_node != NULL);
|
|
|
|
out_node_remap_pairs->clear();
|
|
DockBuilderCopyNodeRec(src_node, dst_node_id, out_node_remap_pairs);
|
|
|
|
IM_ASSERT((out_node_remap_pairs->Size % 2) == 0);
|
|
}
|
|
|
|
void ImGui::DockBuilderCopyWindowSettings(const char* src_name, const char* dst_name)
|
|
{
|
|
ImGuiWindow* src_window = FindWindowByName(src_name);
|
|
if (src_window == NULL)
|
|
return;
|
|
if (ImGuiWindow* dst_window = FindWindowByName(dst_name))
|
|
{
|
|
dst_window->Pos = src_window->Pos;
|
|
dst_window->Size = src_window->Size;
|
|
dst_window->SizeFull = src_window->SizeFull;
|
|
dst_window->Collapsed = src_window->Collapsed;
|
|
}
|
|
else if (ImGuiWindowSettings* dst_settings = FindOrCreateWindowSettings(dst_name))
|
|
{
|
|
ImVec2ih window_pos_2ih = ImVec2ih(src_window->Pos);
|
|
if (src_window->ViewportId != 0 && src_window->ViewportId != IMGUI_VIEWPORT_DEFAULT_ID)
|
|
{
|
|
dst_settings->ViewportPos = window_pos_2ih;
|
|
dst_settings->ViewportId = src_window->ViewportId;
|
|
dst_settings->Pos = ImVec2ih(0, 0);
|
|
}
|
|
else
|
|
{
|
|
dst_settings->Pos = window_pos_2ih;
|
|
}
|
|
dst_settings->Size = ImVec2ih(src_window->SizeFull);
|
|
dst_settings->Collapsed = src_window->Collapsed;
|
|
}
|
|
}
|
|
|
|
// FIXME: Will probably want to change this signature, in particular how the window remapping pairs are passed.
|
|
void ImGui::DockBuilderCopyDockSpace(ImGuiID src_dockspace_id,
|
|
ImGuiID dst_dockspace_id,
|
|
ImVector<const char*>* in_window_remap_pairs)
|
|
{
|
|
IM_ASSERT(src_dockspace_id != 0);
|
|
IM_ASSERT(dst_dockspace_id != 0);
|
|
IM_ASSERT(in_window_remap_pairs != NULL);
|
|
IM_ASSERT((in_window_remap_pairs->Size % 2) == 0);
|
|
|
|
// Duplicate entire dock
|
|
// FIXME: When overwriting dst_dockspace_id, windows that aren't part of our dockspace window class but that are
|
|
// docked in a same node will be split apart, whereas we could attempt to at least keep them together in a new, same
|
|
// floating node.
|
|
ImVector<ImGuiID> node_remap_pairs;
|
|
DockBuilderCopyNode(src_dockspace_id, dst_dockspace_id, &node_remap_pairs);
|
|
|
|
// Attempt to transition all the upcoming windows associated to dst_dockspace_id into the newly created hierarchy of
|
|
// dock nodes (The windows associated to src_dockspace_id are staying in place)
|
|
ImVector<ImGuiID> src_windows;
|
|
for (int remap_window_n = 0; remap_window_n < in_window_remap_pairs->Size; remap_window_n += 2)
|
|
{
|
|
const char* src_window_name = (*in_window_remap_pairs)[remap_window_n];
|
|
const char* dst_window_name = (*in_window_remap_pairs)[remap_window_n + 1];
|
|
ImGuiID src_window_id = ImHashStr(src_window_name);
|
|
src_windows.push_back(src_window_id);
|
|
|
|
// Search in the remapping tables
|
|
ImGuiID src_dock_id = 0;
|
|
if (ImGuiWindow* src_window = FindWindowByID(src_window_id))
|
|
src_dock_id = src_window->DockId;
|
|
else if (ImGuiWindowSettings* src_window_settings = 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_DOCKING("Remap live window '%s' 0x%08X -> '%s' 0x%08X\n", src_window_name, src_dock_id,
|
|
dst_window_name, dst_dock_id);
|
|
DockBuilderDockWindow(dst_window_name, dst_dock_id);
|
|
}
|
|
else
|
|
{
|
|
// Floating windows gets their settings transferred (regardless of whether the new window already exist or
|
|
// not) When this is leading to a Copy and not a Move, we would get two overlapping floating windows. Could
|
|
// we possibly dock them together?
|
|
IMGUI_DEBUG_LOG_DOCKING("Remap window settings '%s' -> '%s'\n", src_window_name, dst_window_name);
|
|
DockBuilderCopyWindowSettings(src_window_name, dst_window_name);
|
|
}
|
|
}
|
|
|
|
// Anything else in the source nodes of 'node_remap_pairs' are windows that 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* node = DockBuilderGetNode(src_dock_id);
|
|
for (int window_n = 0; window_n < node->Windows.Size; window_n++)
|
|
{
|
|
ImGuiWindow* window = node->Windows[window_n];
|
|
if (src_windows.contains(window->ID))
|
|
continue;
|
|
|
|
// Docked windows gets redocked into the new node hierarchy.
|
|
IMGUI_DEBUG_LOG_DOCKING("Remap window '%s' %08X -> %08X\n", window->Name, src_dock_id, dst_dock_id);
|
|
DockBuilderDockWindow(window->Name, dst_dock_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ImGui::DockBuilderFinish(ImGuiID root_id)
|
|
{
|
|
ImGuiContext* ctx = GImGui;
|
|
// DockContextRebuild(ctx);
|
|
DockContextBuildAddWindowsToNodes(ctx, root_id);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Docking: Begin/End Support Functions (called from Begin/End)
|
|
//-----------------------------------------------------------------------------
|
|
// - GetWindowAlwaysWantOwnTabBar()
|
|
// - DockContextBindNodeToWindow()
|
|
// - BeginDocked()
|
|
// - BeginDockableDragDropSource()
|
|
// - BeginDockableDragDropTarget()
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool ImGui::GetWindowAlwaysWantOwnTabBar(ImGuiWindow* window)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
if (g.IO.ConfigDockingAlwaysTabBar || window->WindowClass.DockingAlwaysTabBar)
|
|
if ((window->Flags &
|
|
(ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoDocking)) == 0)
|
|
if (!window->IsFallbackWindow) // We don't support AlwaysTabBar on the fallback/implicit window to avoid
|
|
// unused dock-node overhead/noise
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static ImGuiDockNode* ImGui::DockContextBindNodeToWindow(ImGuiContext* ctx, ImGuiWindow* window)
|
|
{
|
|
ImGuiContext& g = *ctx;
|
|
ImGuiDockNode* node = DockContextFindNodeByID(ctx, window->DockId);
|
|
IM_ASSERT(window->DockNode == NULL);
|
|
|
|
// We should not be docking into a split node (SetWindowDock should avoid this)
|
|
if (node && node->IsSplitNode())
|
|
{
|
|
DockContextProcessUndockWindow(ctx, window);
|
|
return NULL;
|
|
}
|
|
|
|
// Create node
|
|
if (node == NULL)
|
|
{
|
|
node = DockContextAddNode(ctx, window->DockId);
|
|
node->AuthorityForPos = node->AuthorityForSize = node->AuthorityForViewport = ImGuiDataAuthority_Window;
|
|
node->LastFrameAlive = g.FrameCount;
|
|
}
|
|
|
|
// If the node just turned visible and is part of a hierarchy, it doesn't have a Size assigned by
|
|
// DockNodeTreeUpdatePosSize() yet, so we're forcing a Pos/Size update from the first ancestor that is already
|
|
// visible (often it will be the root node). If we don't do this, the window will be assigned a zero-size on its
|
|
// first frame, which won't ideally warm up the layout. This is a little wonky because we don't normally update the
|
|
// Pos/Size of visible node mid-frame.
|
|
if (!node->IsVisible)
|
|
{
|
|
ImGuiDockNode* ancestor_node = node;
|
|
while (!ancestor_node->IsVisible)
|
|
{
|
|
ancestor_node->IsVisible = true;
|
|
ancestor_node->MarkedForPosSizeWrite = true;
|
|
if (ancestor_node->ParentNode)
|
|
ancestor_node = ancestor_node->ParentNode;
|
|
}
|
|
IM_ASSERT(ancestor_node->Size.x > 0.0f && ancestor_node->Size.y > 0.0f);
|
|
DockNodeTreeUpdatePosSize(ancestor_node, ancestor_node->Pos, ancestor_node->Size, true);
|
|
}
|
|
|
|
// Add window to node
|
|
DockNodeAddWindow(node, window, true);
|
|
IM_ASSERT(node == window->DockNode);
|
|
return node;
|
|
}
|
|
|
|
void ImGui::BeginDocked(ImGuiWindow* window, bool* p_open)
|
|
{
|
|
ImGuiContext* ctx = GImGui;
|
|
ImGuiContext& g = *ctx;
|
|
|
|
// Clear fields ahead so most early-out paths don't have to do it
|
|
window->DockIsActive = window->DockNodeIsVisible = window->DockTabIsVisible = false;
|
|
|
|
const bool auto_dock_node = GetWindowAlwaysWantOwnTabBar(window);
|
|
if (auto_dock_node)
|
|
{
|
|
if (window->DockId == 0)
|
|
{
|
|
IM_ASSERT(window->DockNode == NULL);
|
|
window->DockId = DockContextGenNodeID(ctx);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Calling SetNextWindowPos() undock windows by default (by setting PosUndock)
|
|
bool want_undock = false;
|
|
want_undock |= (window->Flags & ImGuiWindowFlags_NoDocking) != 0;
|
|
want_undock |= (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasPos) &&
|
|
(window->SetWindowPosAllowFlags & g.NextWindowData.PosCond) && g.NextWindowData.PosUndock;
|
|
if (want_undock)
|
|
{
|
|
DockContextProcessUndockWindow(ctx, window);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Bind to our dock node
|
|
ImGuiDockNode* node = window->DockNode;
|
|
if (node != NULL)
|
|
IM_ASSERT(window->DockId == node->ID);
|
|
if (window->DockId != 0 && node == NULL)
|
|
{
|
|
node = DockContextBindNodeToWindow(ctx, window);
|
|
if (node == NULL)
|
|
return;
|
|
}
|
|
|
|
# if 0
|
|
// Undock if the ImGuiDockNodeFlags_NoDockingInCentralNode got set
|
|
if (node->IsCentralNode && (node->Flags & ImGuiDockNodeFlags_NoDockingInCentralNode))
|
|
{
|
|
DockContextProcessUndockWindow(ctx, window);
|
|
return;
|
|
}
|
|
# endif
|
|
|
|
// Undock if our dockspace node disappeared
|
|
// Note how we are testing for LastFrameAlive and NOT LastFrameActive. A DockSpace node can be maintained alive
|
|
// while being inactive with ImGuiDockNodeFlags_KeepAliveOnly.
|
|
if (node->LastFrameAlive < g.FrameCount)
|
|
{
|
|
// If the window has been orphaned, transition the docknode to an implicit node processed in
|
|
// DockContextNewFrameUpdateDocking()
|
|
ImGuiDockNode* root_node = DockNodeGetRootNode(node);
|
|
if (root_node->LastFrameAlive < g.FrameCount)
|
|
DockContextProcessUndockWindow(ctx, window);
|
|
else
|
|
window->DockIsActive = true;
|
|
return;
|
|
}
|
|
|
|
// Store style overrides
|
|
for (int color_n = 0; color_n < ImGuiWindowDockStyleCol_COUNT; color_n++)
|
|
window->DockStyle.Colors[color_n] = ColorConvertFloat4ToU32(g.Style.Colors[GWindowDockStyleColors[color_n]]);
|
|
|
|
// Fast path return. It is common for windows to hold on a persistent DockId but be the only visible window,
|
|
// and never create neither a host window neither a tab bar.
|
|
// FIXME-DOCK: replace ->HostWindow NULL compare with something more explicit (~was initially intended as a first
|
|
// frame test)
|
|
if (node->HostWindow == NULL)
|
|
{
|
|
if (node->State == ImGuiDockNodeState_HostWindowHiddenBecauseWindowsAreResizing)
|
|
window->DockIsActive = true;
|
|
if (node->Windows.Size > 1)
|
|
DockNodeHideWindowDuringHostWindowCreation(window);
|
|
return;
|
|
}
|
|
|
|
// We can have zero-sized nodes (e.g. children of a small-size dockspace)
|
|
IM_ASSERT(node->HostWindow);
|
|
IM_ASSERT(node->IsLeafNode());
|
|
IM_ASSERT(node->Size.x >= 0.0f && node->Size.y >= 0.0f);
|
|
node->State = ImGuiDockNodeState_HostWindowVisible;
|
|
|
|
// Undock if we are submitted earlier than the host window
|
|
if (window->BeginOrderWithinContext < node->HostWindow->BeginOrderWithinContext)
|
|
{
|
|
DockContextProcessUndockWindow(ctx, window);
|
|
return;
|
|
}
|
|
|
|
// Position/Size window
|
|
SetNextWindowPos(node->Pos);
|
|
SetNextWindowSize(node->Size);
|
|
g.NextWindowData.PosUndock = false; // Cancel implicit undocking of SetNextWindowPos()
|
|
window->DockIsActive = true;
|
|
window->DockNodeIsVisible = true;
|
|
window->DockTabIsVisible = false;
|
|
if (node->MergedFlags & ImGuiDockNodeFlags_KeepAliveOnly)
|
|
return;
|
|
|
|
// When the window is selected we mark it as visible.
|
|
if (node->VisibleWindow == window)
|
|
window->DockTabIsVisible = true;
|
|
|
|
// Update window flag
|
|
IM_ASSERT((window->Flags & ImGuiWindowFlags_ChildWindow) == 0);
|
|
window->Flags |= ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_AlwaysUseWindowPadding | ImGuiWindowFlags_NoResize;
|
|
if (node->IsHiddenTabBar() || node->IsNoTabBar())
|
|
window->Flags |= ImGuiWindowFlags_NoTitleBar;
|
|
else
|
|
window->Flags &= ~ImGuiWindowFlags_NoTitleBar; // Clear the NoTitleBar flag in case the user set it:
|
|
// confusingly enough we need a title bar height so we are
|
|
// correctly offset, but it won't be displayed!
|
|
|
|
// Save new dock order only if the window has been visible once already
|
|
// This allows multiple windows to be created in the same frame and have their respective dock orders preserved.
|
|
if (node->TabBar && window->WasActive)
|
|
window->DockOrder = (short)DockNodeGetTabOrder(window);
|
|
|
|
if ((node->WantCloseAll || node->WantCloseTabId == window->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);
|
|
}
|
|
|
|
void ImGui::BeginDockableDragDropSource(ImGuiWindow* window)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IM_ASSERT(g.ActiveId == window->MoveId);
|
|
IM_ASSERT(g.MovingWindow == window);
|
|
IM_ASSERT(g.CurrentWindow == window);
|
|
|
|
g.LastItemData.ID = window->MoveId;
|
|
window = window->RootWindowDockTree;
|
|
IM_ASSERT((window->Flags & ImGuiWindowFlags_NoDocking) == 0);
|
|
bool is_drag_docking =
|
|
ImRect(0, 0, window->SizeFull.x, GetFrameHeight())
|
|
.Contains(g.ActiveIdClickOffset); // FIXME-DOCKING: Need to make this stateful and explicit
|
|
if (is_drag_docking &&
|
|
BeginDragDropSource(ImGuiDragDropFlags_SourceNoPreviewTooltip | ImGuiDragDropFlags_SourceNoHoldToOpenOthers |
|
|
ImGuiDragDropFlags_SourceAutoExpirePayload))
|
|
{
|
|
SetDragDropPayload(IMGUI_PAYLOAD_TYPE_WINDOW, &window, sizeof(window));
|
|
EndDragDropSource();
|
|
|
|
// Store style overrides
|
|
for (int color_n = 0; color_n < ImGuiWindowDockStyleCol_COUNT; color_n++)
|
|
window->DockStyle.Colors[color_n] =
|
|
ColorConvertFloat4ToU32(g.Style.Colors[GWindowDockStyleColors[color_n]]);
|
|
}
|
|
}
|
|
|
|
void ImGui::BeginDockableDragDropTarget(ImGuiWindow* window)
|
|
{
|
|
ImGuiContext* ctx = GImGui;
|
|
ImGuiContext& g = *ctx;
|
|
|
|
// IM_ASSERT(window->RootWindowDockTree == window); // May also be a DockSpace
|
|
IM_ASSERT((window->Flags & ImGuiWindowFlags_NoDocking) == 0);
|
|
if (!g.DragDropActive)
|
|
return;
|
|
// GetForegroundDrawList(window)->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 255, 0, 255));
|
|
if (!BeginDragDropTargetCustom(window->Rect(), window->ID))
|
|
return;
|
|
|
|
// Peek into the payload before calling AcceptDragDropPayload() so we can handle overlapping dock nodes with
|
|
// filtering (this is a little unusual pattern, normally most code would call AcceptDragDropPayload directly)
|
|
const ImGuiPayload* payload = &g.DragDropPayload;
|
|
if (!payload->IsDataType(IMGUI_PAYLOAD_TYPE_WINDOW) ||
|
|
!DockNodeIsDropAllowed(window, *(ImGuiWindow**)payload->Data))
|
|
{
|
|
EndDragDropTarget();
|
|
return;
|
|
}
|
|
|
|
ImGuiWindow* payload_window = *(ImGuiWindow**)payload->Data;
|
|
if (AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_WINDOW,
|
|
ImGuiDragDropFlags_AcceptBeforeDelivery | ImGuiDragDropFlags_AcceptNoDrawDefaultRect))
|
|
{
|
|
// Select target node
|
|
// (Important: we cannot use g.HoveredDockNode here! Because each of our target node have filters based on
|
|
// payload, each candidate drop target will do its own evaluation)
|
|
bool dock_into_floating_window = false;
|
|
ImGuiDockNode* node = NULL;
|
|
if (window->DockNodeAsHost)
|
|
{
|
|
// Cannot assume that node will != NULL even though we passed the rectangle test: it depends on
|
|
// padding/spacing handled by DockNodeTreeFindVisibleNodeByPos().
|
|
node = DockNodeTreeFindVisibleNodeByPos(window->DockNodeAsHost, g.IO.MousePos);
|
|
|
|
// There is an edge case when docking into a dockspace which only has _inactive_ nodes (because none of the
|
|
// windows are active) In this case we need to fallback into any leaf mode, possibly the central node.
|
|
// FIXME-20181220: We should not have to test for IsLeafNode() here but we have another bug to fix first.
|
|
if (node && node->IsDockSpace() && node->IsRootNode())
|
|
node = (node->CentralNode && node->IsLeafNode()) ? node->CentralNode
|
|
: DockNodeTreeFindFallbackLeafNode(node);
|
|
}
|
|
else
|
|
{
|
|
if (window->DockNode)
|
|
node = window->DockNode;
|
|
else
|
|
dock_into_floating_window = true; // Dock into a regular window
|
|
}
|
|
|
|
const ImRect explicit_target_rect =
|
|
(node && node->TabBar && !node->IsHiddenTabBar() && !node->IsNoTabBar())
|
|
? node->TabBar->BarRect
|
|
: ImRect(window->Pos, window->Pos + ImVec2(window->Size.x, GetFrameHeight()));
|
|
const bool is_explicit_target = IsMouseHoveringRect(explicit_target_rect.Min, explicit_target_rect.Max);
|
|
|
|
// Preview docking request and find out split direction/ratio
|
|
// const bool do_preview = true; // Ignore testing for payload->IsPreview() which removes one frame of
|
|
// delay, but breaks overlapping drop targets within the same window.
|
|
const bool do_preview = payload->IsPreview() || payload->IsDelivery();
|
|
if (do_preview && (node != NULL || dock_into_floating_window))
|
|
{
|
|
ImGuiDockPreviewData split_inner;
|
|
ImGuiDockPreviewData split_outer;
|
|
ImGuiDockPreviewData* split_data = &split_inner;
|
|
if (node && (node->ParentNode || node->IsCentralNode()))
|
|
if (ImGuiDockNode* root_node = DockNodeGetRootNode(node))
|
|
{
|
|
DockNodePreviewDockSetup(window, root_node, payload_window, &split_outer, is_explicit_target, true);
|
|
if (split_outer.IsSplitDirExplicit)
|
|
split_data = &split_outer;
|
|
}
|
|
DockNodePreviewDockSetup(window, node, payload_window, &split_inner, is_explicit_target, false);
|
|
if (split_data == &split_outer)
|
|
split_inner.IsDropAllowed = false;
|
|
|
|
// Draw inner then outer, so that previewed tab (in inner data) will be behind the outer drop boxes
|
|
DockNodePreviewDockRender(window, node, payload_window, &split_inner);
|
|
DockNodePreviewDockRender(window, node, payload_window, &split_outer);
|
|
|
|
// Queue docking request
|
|
if (split_data->IsDropAllowed && payload->IsDelivery())
|
|
DockContextQueueDock(ctx, window, split_data->SplitNode, payload_window, split_data->SplitDir,
|
|
split_data->SplitRatio, split_data == &split_outer);
|
|
}
|
|
}
|
|
EndDragDropTarget();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Docking: Settings
|
|
//-----------------------------------------------------------------------------
|
|
// - DockSettingsRenameNodeReferences()
|
|
// - DockSettingsRemoveNodeReferences()
|
|
// - DockSettingsFindNodeSettings()
|
|
// - DockSettingsHandler_ApplyAll()
|
|
// - DockSettingsHandler_ReadOpen()
|
|
// - DockSettingsHandler_ReadLine()
|
|
// - DockSettingsHandler_DockNodeToSettings()
|
|
// - DockSettingsHandler_WriteAll()
|
|
//-----------------------------------------------------------------------------
|
|
|
|
static void ImGui::DockSettingsRenameNodeReferences(ImGuiID old_node_id, ImGuiID new_node_id)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
IMGUI_DEBUG_LOG_DOCKING("DockSettingsRenameNodeReferences: from 0x%08X -> to 0x%08X\n", old_node_id, new_node_id);
|
|
for (int window_n = 0; window_n < g.Windows.Size; window_n++)
|
|
{
|
|
ImGuiWindow* window = g.Windows[window_n];
|
|
if (window->DockId == old_node_id && window->DockNode == NULL)
|
|
window->DockId = new_node_id;
|
|
}
|
|
//// FIXME-OPT: We could remove this loop by storing the index in the map
|
|
for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL;
|
|
settings = g.SettingsWindows.next_chunk(settings))
|
|
if (settings->DockId == old_node_id)
|
|
settings->DockId = new_node_id;
|
|
}
|
|
|
|
// Remove references stored in ImGuiWindowSettings to the given ImGuiDockNodeSettings
|
|
static void ImGui::DockSettingsRemoveNodeReferences(ImGuiID* node_ids, int node_ids_count)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
int found = 0;
|
|
//// FIXME-OPT: We could remove this loop by storing the index in the map
|
|
for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL;
|
|
settings = g.SettingsWindows.next_chunk(settings))
|
|
for (int node_n = 0; node_n < node_ids_count; node_n++)
|
|
if (settings->DockId == node_ids[node_n])
|
|
{
|
|
settings->DockId = 0;
|
|
settings->DockOrder = -1;
|
|
if (++found < node_ids_count)
|
|
break;
|
|
return;
|
|
}
|
|
}
|
|
|
|
static ImGuiDockNodeSettings* ImGui::DockSettingsFindNodeSettings(ImGuiContext* ctx, ImGuiID id)
|
|
{
|
|
// FIXME-OPT
|
|
ImGuiDockContext* dc = &ctx->DockContext;
|
|
for (int n = 0; n < dc->NodesSettings.Size; n++)
|
|
if (dc->NodesSettings[n].ID == id)
|
|
return &dc->NodesSettings[n];
|
|
return NULL;
|
|
}
|
|
|
|
// Clear settings data
|
|
static void ImGui::DockSettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettingsHandler*)
|
|
{
|
|
ImGuiDockContext* dc = &ctx->DockContext;
|
|
dc->NodesSettings.clear();
|
|
DockContextClearNodes(ctx, 0, true);
|
|
}
|
|
|
|
// Recreate nodes based on settings data
|
|
static void ImGui::DockSettingsHandler_ApplyAll(ImGuiContext* ctx, ImGuiSettingsHandler*)
|
|
{
|
|
// Prune settings at boot time only
|
|
ImGuiDockContext* dc = &ctx->DockContext;
|
|
if (ctx->Windows.Size == 0)
|
|
DockContextPruneUnusedSettingsNodes(ctx);
|
|
DockContextBuildNodesFromSettings(ctx, dc->NodesSettings.Data, dc->NodesSettings.Size);
|
|
DockContextBuildAddWindowsToNodes(ctx, 0);
|
|
}
|
|
|
|
static void* ImGui::DockSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name)
|
|
{
|
|
if (strcmp(name, "Data") != 0)
|
|
return NULL;
|
|
return (void*)1;
|
|
}
|
|
|
|
static void ImGui::DockSettingsHandler_ReadLine(ImGuiContext* ctx, ImGuiSettingsHandler*, void*, const char* line)
|
|
{
|
|
char c = 0;
|
|
int x = 0, y = 0;
|
|
int r = 0;
|
|
|
|
// Parsing, e.g.
|
|
// " DockNode ID=0x00000001 Pos=383,193 Size=201,322 Split=Y,0.506 "
|
|
// " DockNode ID=0x00000002 Parent=0x00000001 "
|
|
// Important: this code expect currently fields in a fixed order.
|
|
ImGuiDockNodeSettings node;
|
|
line = ImStrSkipBlank(line);
|
|
if (strncmp(line, "DockNode", 8) == 0)
|
|
{
|
|
line = ImStrSkipBlank(line + strlen("DockNode"));
|
|
}
|
|
else if (strncmp(line, "DockSpace", 9) == 0)
|
|
{
|
|
line = ImStrSkipBlank(line + strlen("DockSpace"));
|
|
node.Flags |= ImGuiDockNodeFlags_DockSpace;
|
|
}
|
|
else
|
|
return;
|
|
if (sscanf(line, "ID=0x%08X%n", &node.ID, &r) == 1)
|
|
{
|
|
line += r;
|
|
}
|
|
else
|
|
return;
|
|
if (sscanf(line, " Parent=0x%08X%n", &node.ParentNodeId, &r) == 1)
|
|
{
|
|
line += r;
|
|
if (node.ParentNodeId == 0)
|
|
return;
|
|
}
|
|
if (sscanf(line, " Window=0x%08X%n", &node.ParentWindowId, &r) == 1)
|
|
{
|
|
line += r;
|
|
if (node.ParentWindowId == 0)
|
|
return;
|
|
}
|
|
if (node.ParentNodeId == 0)
|
|
{
|
|
if (sscanf(line, " Pos=%i,%i%n", &x, &y, &r) == 2)
|
|
{
|
|
line += r;
|
|
node.Pos = ImVec2ih((short)x, (short)y);
|
|
}
|
|
else
|
|
return;
|
|
if (sscanf(line, " Size=%i,%i%n", &x, &y, &r) == 2)
|
|
{
|
|
line += r;
|
|
node.Size = ImVec2ih((short)x, (short)y);
|
|
}
|
|
else
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if (sscanf(line, " SizeRef=%i,%i%n", &x, &y, &r) == 2)
|
|
{
|
|
line += r;
|
|
node.SizeRef = ImVec2ih((short)x, (short)y);
|
|
}
|
|
}
|
|
if (sscanf(line, " Split=%c%n", &c, &r) == 1)
|
|
{
|
|
line += r;
|
|
if (c == 'X')
|
|
node.SplitAxis = ImGuiAxis_X;
|
|
else if (c == 'Y')
|
|
node.SplitAxis = ImGuiAxis_Y;
|
|
}
|
|
if (sscanf(line, " NoResize=%d%n", &x, &r) == 1)
|
|
{
|
|
line += r;
|
|
if (x != 0)
|
|
node.Flags |= ImGuiDockNodeFlags_NoResize;
|
|
}
|
|
if (sscanf(line, " CentralNode=%d%n", &x, &r) == 1)
|
|
{
|
|
line += r;
|
|
if (x != 0)
|
|
node.Flags |= ImGuiDockNodeFlags_CentralNode;
|
|
}
|
|
if (sscanf(line, " NoTabBar=%d%n", &x, &r) == 1)
|
|
{
|
|
line += r;
|
|
if (x != 0)
|
|
node.Flags |= ImGuiDockNodeFlags_NoTabBar;
|
|
}
|
|
if (sscanf(line, " HiddenTabBar=%d%n", &x, &r) == 1)
|
|
{
|
|
line += r;
|
|
if (x != 0)
|
|
node.Flags |= ImGuiDockNodeFlags_HiddenTabBar;
|
|
}
|
|
if (sscanf(line, " NoWindowMenuButton=%d%n", &x, &r) == 1)
|
|
{
|
|
line += r;
|
|
if (x != 0)
|
|
node.Flags |= ImGuiDockNodeFlags_NoWindowMenuButton;
|
|
}
|
|
if (sscanf(line, " NoCloseButton=%d%n", &x, &r) == 1)
|
|
{
|
|
line += r;
|
|
if (x != 0)
|
|
node.Flags |= ImGuiDockNodeFlags_NoCloseButton;
|
|
}
|
|
if (sscanf(line, " Selected=0x%08X%n", &node.SelectedTabId, &r) == 1)
|
|
{
|
|
line += r;
|
|
}
|
|
if (node.ParentNodeId != 0)
|
|
if (ImGuiDockNodeSettings* parent_settings = DockSettingsFindNodeSettings(ctx, node.ParentNodeId))
|
|
node.Depth = parent_settings->Depth + 1;
|
|
ctx->DockContext.NodesSettings.push_back(node);
|
|
}
|
|
|
|
static void DockSettingsHandler_DockNodeToSettings(ImGuiDockContext* dc, ImGuiDockNode* node, int depth)
|
|
{
|
|
ImGuiDockNodeSettings node_settings;
|
|
IM_ASSERT(depth < (1 << (sizeof(node_settings.Depth) << 3)));
|
|
node_settings.ID = node->ID;
|
|
node_settings.ParentNodeId = node->ParentNode ? node->ParentNode->ID : 0;
|
|
node_settings.ParentWindowId = (node->IsDockSpace() && node->HostWindow && node->HostWindow->ParentWindow)
|
|
? node->HostWindow->ParentWindow->ID
|
|
: 0;
|
|
node_settings.SelectedTabId = node->SelectedTabId;
|
|
node_settings.SplitAxis = (signed char)(node->IsSplitNode() ? node->SplitAxis : ImGuiAxis_None);
|
|
node_settings.Depth = (char)depth;
|
|
node_settings.Flags = (node->LocalFlags & ImGuiDockNodeFlags_SavedFlagsMask_);
|
|
node_settings.Pos = ImVec2ih(node->Pos);
|
|
node_settings.Size = ImVec2ih(node->Size);
|
|
node_settings.SizeRef = ImVec2ih(node->SizeRef);
|
|
dc->NodesSettings.push_back(node_settings);
|
|
if (node->ChildNodes[0])
|
|
DockSettingsHandler_DockNodeToSettings(dc, node->ChildNodes[0], depth + 1);
|
|
if (node->ChildNodes[1])
|
|
DockSettingsHandler_DockNodeToSettings(dc, node->ChildNodes[1], depth + 1);
|
|
}
|
|
|
|
static void ImGui::DockSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf)
|
|
{
|
|
ImGuiContext& g = *ctx;
|
|
ImGuiDockContext* dc = &ctx->DockContext;
|
|
if (!(g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable))
|
|
return;
|
|
|
|
// Gather settings data
|
|
// (unlike our windows settings, because nodes are always built we can do a full rewrite of the SettingsNode buffer)
|
|
dc->NodesSettings.resize(0);
|
|
dc->NodesSettings.reserve(dc->Nodes.Data.Size);
|
|
for (int n = 0; n < dc->Nodes.Data.Size; n++)
|
|
if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p)
|
|
if (node->IsRootNode())
|
|
DockSettingsHandler_DockNodeToSettings(dc, node, 0);
|
|
|
|
int max_depth = 0;
|
|
for (int node_n = 0; node_n < dc->NodesSettings.Size; node_n++)
|
|
max_depth = ImMax((int)dc->NodesSettings[node_n].Depth, max_depth);
|
|
|
|
// Write to text buffer
|
|
buf->appendf("[%s][Data]\n", handler->TypeName);
|
|
for (int node_n = 0; node_n < dc->NodesSettings.Size; node_n++)
|
|
{
|
|
const int line_start_pos = buf->size();
|
|
(void)line_start_pos;
|
|
const ImGuiDockNodeSettings* node_settings = &dc->NodesSettings[node_n];
|
|
buf->appendf("%*s%s%*s", node_settings->Depth * 2, "",
|
|
(node_settings->Flags & ImGuiDockNodeFlags_DockSpace) ? "DockSpace" : "DockNode ",
|
|
(max_depth - node_settings->Depth) * 2,
|
|
""); // Text align nodes to facilitate looking at .ini file
|
|
buf->appendf(" ID=0x%08X", node_settings->ID);
|
|
if (node_settings->ParentNodeId)
|
|
{
|
|
buf->appendf(" Parent=0x%08X SizeRef=%d,%d", node_settings->ParentNodeId, node_settings->SizeRef.x,
|
|
node_settings->SizeRef.y);
|
|
}
|
|
else
|
|
{
|
|
if (node_settings->ParentWindowId)
|
|
buf->appendf(" Window=0x%08X", node_settings->ParentWindowId);
|
|
buf->appendf(" Pos=%d,%d Size=%d,%d", node_settings->Pos.x, node_settings->Pos.y, node_settings->Size.x,
|
|
node_settings->Size.y);
|
|
}
|
|
if (node_settings->SplitAxis != ImGuiAxis_None)
|
|
buf->appendf(" Split=%c", (node_settings->SplitAxis == ImGuiAxis_X) ? 'X' : 'Y');
|
|
if (node_settings->Flags & ImGuiDockNodeFlags_NoResize)
|
|
buf->appendf(" NoResize=1");
|
|
if (node_settings->Flags & ImGuiDockNodeFlags_CentralNode)
|
|
buf->appendf(" CentralNode=1");
|
|
if (node_settings->Flags & ImGuiDockNodeFlags_NoTabBar)
|
|
buf->appendf(" NoTabBar=1");
|
|
if (node_settings->Flags & ImGuiDockNodeFlags_HiddenTabBar)
|
|
buf->appendf(" HiddenTabBar=1");
|
|
if (node_settings->Flags & ImGuiDockNodeFlags_NoWindowMenuButton)
|
|
buf->appendf(" NoWindowMenuButton=1");
|
|
if (node_settings->Flags & ImGuiDockNodeFlags_NoCloseButton)
|
|
buf->appendf(" NoCloseButton=1");
|
|
if (node_settings->SelectedTabId)
|
|
buf->appendf(" Selected=0x%08X", node_settings->SelectedTabId);
|
|
|
|
# if IMGUI_DEBUG_INI_SETTINGS
|
|
// [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);
|
|
// Iterate settings so we can give info about windows that didn't exist during the session.
|
|
int contains_window = 0;
|
|
for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL;
|
|
settings = g.SettingsWindows.next_chunk(settings))
|
|
if (settings->DockId == node_settings->ID)
|
|
{
|
|
if (contains_window++ == 0)
|
|
buf->appendf(" ; contains ");
|
|
buf->appendf("'%s' ", settings->GetName());
|
|
}
|
|
}
|
|
# endif
|
|
buf->appendf("\n");
|
|
}
|
|
buf->appendf("\n");
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// [SECTION] PLATFORM DEPENDENT HELPERS
|
|
//-----------------------------------------------------------------------------
|
|
|
|
# if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && \
|
|
!defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS)
|
|
|
|
# ifdef _MSC_VER
|
|
# pragma comment(lib, "user32")
|
|
# pragma comment(lib, "kernel32")
|
|
# endif
|
|
|
|
// Win32 clipboard implementation
|
|
// We use g.ClipboardHandlerData for temporary storage to ensure it is freed on Shutdown()
|
|
static const char* GetClipboardTextFn_DefaultImpl(void*)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
g.ClipboardHandlerData.clear();
|
|
if (!::OpenClipboard(NULL))
|
|
return NULL;
|
|
HANDLE wbuf_handle = ::GetClipboardData(CF_UNICODETEXT);
|
|
if (wbuf_handle == NULL)
|
|
{
|
|
::CloseClipboard();
|
|
return NULL;
|
|
}
|
|
if (const WCHAR* wbuf_global = (const WCHAR*)::GlobalLock(wbuf_handle))
|
|
{
|
|
int buf_len = ::WideCharToMultiByte(CP_UTF8, 0, wbuf_global, -1, NULL, 0, NULL, NULL);
|
|
g.ClipboardHandlerData.resize(buf_len);
|
|
::WideCharToMultiByte(CP_UTF8, 0, wbuf_global, -1, g.ClipboardHandlerData.Data, buf_len, NULL, NULL);
|
|
}
|
|
::GlobalUnlock(wbuf_handle);
|
|
::CloseClipboard();
|
|
return g.ClipboardHandlerData.Data;
|
|
}
|
|
|
|
static void SetClipboardTextFn_DefaultImpl(void*, const char* text)
|
|
{
|
|
if (!::OpenClipboard(NULL))
|
|
return;
|
|
const int wbuf_length = ::MultiByteToWideChar(CP_UTF8, 0, text, -1, NULL, 0);
|
|
HGLOBAL wbuf_handle = ::GlobalAlloc(GMEM_MOVEABLE, (SIZE_T)wbuf_length * sizeof(WCHAR));
|
|
if (wbuf_handle == NULL)
|
|
{
|
|
::CloseClipboard();
|
|
return;
|
|
}
|
|
WCHAR* wbuf_global = (WCHAR*)::GlobalLock(wbuf_handle);
|
|
::MultiByteToWideChar(CP_UTF8, 0, text, -1, wbuf_global, wbuf_length);
|
|
::GlobalUnlock(wbuf_handle);
|
|
::EmptyClipboard();
|
|
if (::SetClipboardData(CF_UNICODETEXT, wbuf_handle) == NULL)
|
|
::GlobalFree(wbuf_handle);
|
|
::CloseClipboard();
|
|
}
|
|
|
|
# elif defined(__APPLE__) && TARGET_OS_OSX && defined(IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS)
|
|
|
|
# include <Carbon/Carbon.h> // Use old API to avoid need for separate .mm file
|
|
static PasteboardRef main_clipboard = 0;
|
|
|
|
// OSX clipboard implementation
|
|
// If you enable this you will need to add '-framework ApplicationServices' to your linker command-line!
|
|
static void SetClipboardTextFn_DefaultImpl(void*, const char* text)
|
|
{
|
|
if (!main_clipboard)
|
|
PasteboardCreate(kPasteboardClipboard, &main_clipboard);
|
|
PasteboardClear(main_clipboard);
|
|
CFDataRef cf_data = CFDataCreate(kCFAllocatorDefault, (const UInt8*)text, strlen(text));
|
|
if (cf_data)
|
|
{
|
|
PasteboardPutItemFlavor(main_clipboard, (PasteboardItemID)1, CFSTR("public.utf8-plain-text"), cf_data, 0);
|
|
CFRelease(cf_data);
|
|
}
|
|
}
|
|
|
|
static const char* GetClipboardTextFn_DefaultImpl(void*)
|
|
{
|
|
if (!main_clipboard)
|
|
PasteboardCreate(kPasteboardClipboard, &main_clipboard);
|
|
PasteboardSynchronize(main_clipboard);
|
|
|
|
ItemCount item_count = 0;
|
|
PasteboardGetItemCount(main_clipboard, &item_count);
|
|
for (ItemCount i = 0; i < item_count; i++)
|
|
{
|
|
PasteboardItemID item_id = 0;
|
|
PasteboardGetItemIdentifier(main_clipboard, i + 1, &item_id);
|
|
CFArrayRef flavor_type_array = 0;
|
|
PasteboardCopyItemFlavors(main_clipboard, item_id, &flavor_type_array);
|
|
for (CFIndex j = 0, nj = CFArrayGetCount(flavor_type_array); j < nj; j++)
|
|
{
|
|
CFDataRef cf_data;
|
|
if (PasteboardCopyItemFlavorData(main_clipboard, item_id, CFSTR("public.utf8-plain-text"), &cf_data) ==
|
|
noErr)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
g.ClipboardHandlerData.clear();
|
|
int length = (int)CFDataGetLength(cf_data);
|
|
g.ClipboardHandlerData.resize(length + 1);
|
|
CFDataGetBytes(cf_data, CFRangeMake(0, length), (UInt8*)g.ClipboardHandlerData.Data);
|
|
g.ClipboardHandlerData[length] = 0;
|
|
CFRelease(cf_data);
|
|
return g.ClipboardHandlerData.Data;
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
# else
|
|
|
|
// Local Dear ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers.
|
|
static const char* GetClipboardTextFn_DefaultImpl(void*)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
return g.ClipboardHandlerData.empty() ? NULL : g.ClipboardHandlerData.begin();
|
|
}
|
|
|
|
static void SetClipboardTextFn_DefaultImpl(void*, const char* text)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
g.ClipboardHandlerData.clear();
|
|
const char* text_end = text + strlen(text);
|
|
g.ClipboardHandlerData.resize((int)(text_end - text) + 1);
|
|
memcpy(&g.ClipboardHandlerData[0], text, (size_t)(text_end - text));
|
|
g.ClipboardHandlerData[(int)(text_end - text)] = 0;
|
|
}
|
|
|
|
# endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// [SECTION] METRICS/DEBUGGER WINDOW
|
|
//-----------------------------------------------------------------------------
|
|
// - RenderViewportThumbnail() [Internal]
|
|
// - RenderViewportsThumbnails() [Internal]
|
|
// - MetricsHelpMarker() [Internal]
|
|
// - ShowMetricsWindow()
|
|
// - DebugNodeColumns() [Internal]
|
|
// - DebugNodeDockNode() [Internal]
|
|
// - DebugNodeDrawList() [Internal]
|
|
// - DebugNodeDrawCmdShowMeshAndBoundingBox() [Internal]
|
|
// - DebugNodeStorage() [Internal]
|
|
// - DebugNodeTabBar() [Internal]
|
|
// - DebugNodeViewport() [Internal]
|
|
// - DebugNodeWindow() [Internal]
|
|
// - DebugNodeWindowSettings() [Internal]
|
|
// - DebugNodeWindowsList() [Internal]
|
|
//-----------------------------------------------------------------------------
|
|
|
|
# ifndef IMGUI_DISABLE_METRICS_WINDOW
|
|
|
|
void ImGui::DebugRenderViewportThumbnail(ImDrawList* draw_list, ImGuiViewportP* viewport, const ImRect& bb)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
|
|
ImVec2 scale = bb.GetSize() / viewport->Size;
|
|
ImVec2 off = bb.Min - viewport->Pos * scale;
|
|
float alpha_mul = (viewport->Flags & ImGuiViewportFlags_Minimized) ? 0.30f : 1.00f;
|
|
window->DrawList->AddRectFilled(bb.Min, bb.Max, ImGui::GetColorU32(ImGuiCol_Border, alpha_mul * 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->Viewport != viewport)
|
|
continue;
|
|
|
|
ImRect thumb_r = thumb_window->Rect();
|
|
ImRect title_r = thumb_window->TitleBarRect();
|
|
thumb_r = ImRect(ImFloor(off + thumb_r.Min * scale), ImFloor(off + thumb_r.Max * scale));
|
|
title_r =
|
|
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.ClipWithFull(bb);
|
|
title_r.ClipWithFull(bb);
|
|
const bool window_is_focused = (g.NavWindow && thumb_window->RootWindowForTitleBarHighlight ==
|
|
g.NavWindow->RootWindowForTitleBarHighlight);
|
|
window->DrawList->AddRectFilled(thumb_r.Min, thumb_r.Max, GetColorU32(ImGuiCol_WindowBg, alpha_mul));
|
|
window->DrawList->AddRectFilled(
|
|
title_r.Min, title_r.Max,
|
|
GetColorU32(window_is_focused ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg, alpha_mul));
|
|
window->DrawList->AddRect(thumb_r.Min, thumb_r.Max, GetColorU32(ImGuiCol_Border, alpha_mul));
|
|
window->DrawList->AddText(g.Font, g.FontSize * 1.0f, title_r.Min, GetColorU32(ImGuiCol_Text, alpha_mul),
|
|
thumb_window->Name, FindRenderedTextEnd(thumb_window->Name));
|
|
}
|
|
draw_list->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border, alpha_mul));
|
|
}
|
|
|
|
static void RenderViewportsThumbnails()
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiWindow* window = g.CurrentWindow;
|
|
|
|
// 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(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX);
|
|
for (int n = 0; n < g.Viewports.Size; n++)
|
|
bb_full.Add(g.Viewports[n]->GetMainRect());
|
|
ImVec2 p = window->DC.CursorPos;
|
|
ImVec2 off = p - bb_full.Min * SCALE;
|
|
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);
|
|
ImGui::DebugRenderViewportThumbnail(window->DrawList, viewport, viewport_draw_bb);
|
|
}
|
|
ImGui::Dummy(bb_full.GetSize() * SCALE);
|
|
}
|
|
|
|
static int IMGUI_CDECL ViewportComparerByFrontMostStampCount(const void* lhs, const void* rhs)
|
|
{
|
|
const ImGuiViewportP* a = *(const ImGuiViewportP* const*)lhs;
|
|
const ImGuiViewportP* b = *(const ImGuiViewportP* const*)rhs;
|
|
return b->LastFrontMostStampCount - a->LastFrontMostStampCount;
|
|
}
|
|
|
|
// Avoid naming collision with imgui_demo.cpp's HelpMarker() for unity builds.
|
|
static void MetricsHelpMarker(const char* desc)
|
|
{
|
|
ImGui::TextDisabled("(?)");
|
|
if (ImGui::IsItemHovered())
|
|
{
|
|
ImGui::BeginTooltip();
|
|
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
|
|
ImGui::TextUnformatted(desc);
|
|
ImGui::PopTextWrapPos();
|
|
ImGui::EndTooltip();
|
|
}
|
|
}
|
|
|
|
# ifndef IMGUI_DISABLE_DEMO_WINDOWS
|
|
namespace ImGui
|
|
{
|
|
void ShowFontAtlas(ImFontAtlas* atlas);
|
|
}
|
|
# endif
|
|
|
|
void ImGui::ShowMetricsWindow(bool* p_open)
|
|
{
|
|
if (!Begin("Dear ImGui Metrics/Debugger", p_open))
|
|
{
|
|
End();
|
|
return;
|
|
}
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiIO& io = g.IO;
|
|
ImGuiMetricsConfig* cfg = &g.DebugMetricsConfig;
|
|
|
|
// Basic info
|
|
Text("Dear ImGui %s", GetVersion());
|
|
Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
|
|
Text("%d vertices, %d indices (%d triangles)", io.MetricsRenderVertices, io.MetricsRenderIndices,
|
|
io.MetricsRenderIndices / 3);
|
|
Text("%d active windows (%d visible)", io.MetricsActiveWindows, io.MetricsRenderWindows);
|
|
Text("%d active allocations", io.MetricsActiveAllocations);
|
|
// SameLine(); if (SmallButton("GC")) { g.GcCompactAll = true; }
|
|
|
|
Separator();
|
|
|
|
// Debugging enums
|
|
enum
|
|
{
|
|
WRT_OuterRect,
|
|
WRT_OuterRectClipped,
|
|
WRT_InnerRect,
|
|
WRT_InnerClipRect,
|
|
WRT_WorkRect,
|
|
WRT_Content,
|
|
WRT_ContentIdeal,
|
|
WRT_ContentRegionRect,
|
|
WRT_Count
|
|
}; // Windows Rect Type
|
|
const char* wrt_rects_names[WRT_Count] = {"OuterRect", "OuterRectClipped", "InnerRect", "InnerClipRect",
|
|
"WorkRect", "Content", "ContentIdeal", "ContentRegionRect"};
|
|
enum
|
|
{
|
|
TRT_OuterRect,
|
|
TRT_InnerRect,
|
|
TRT_WorkRect,
|
|
TRT_HostClipRect,
|
|
TRT_InnerClipRect,
|
|
TRT_BackgroundClipRect,
|
|
TRT_ColumnsRect,
|
|
TRT_ColumnsWorkRect,
|
|
TRT_ColumnsClipRect,
|
|
TRT_ColumnsContentHeadersUsed,
|
|
TRT_ColumnsContentHeadersIdeal,
|
|
TRT_ColumnsContentFrozen,
|
|
TRT_ColumnsContentUnfrozen,
|
|
TRT_Count
|
|
}; // Tables Rect Type
|
|
const char* trt_rects_names[TRT_Count] = {"OuterRect",
|
|
"InnerRect",
|
|
"WorkRect",
|
|
"HostClipRect",
|
|
"InnerClipRect",
|
|
"BackgroundClipRect",
|
|
"ColumnsRect",
|
|
"ColumnsWorkRect",
|
|
"ColumnsClipRect",
|
|
"ColumnsContentHeadersUsed",
|
|
"ColumnsContentHeadersIdeal",
|
|
"ColumnsContentFrozen",
|
|
"ColumnsContentUnfrozen"};
|
|
if (cfg->ShowWindowsRectsType < 0)
|
|
cfg->ShowWindowsRectsType = WRT_WorkRect;
|
|
if (cfg->ShowTablesRectsType < 0)
|
|
cfg->ShowTablesRectsType = TRT_WorkRect;
|
|
|
|
struct Funcs
|
|
{
|
|
static ImRect GetTableRect(ImGuiTable* table, int rect_type, int n)
|
|
{
|
|
if (rect_type == TRT_OuterRect)
|
|
{
|
|
return table->OuterRect;
|
|
}
|
|
else if (rect_type == TRT_InnerRect)
|
|
{
|
|
return table->InnerRect;
|
|
}
|
|
else if (rect_type == TRT_WorkRect)
|
|
{
|
|
return table->WorkRect;
|
|
}
|
|
else if (rect_type == TRT_HostClipRect)
|
|
{
|
|
return table->HostClipRect;
|
|
}
|
|
else if (rect_type == TRT_InnerClipRect)
|
|
{
|
|
return table->InnerClipRect;
|
|
}
|
|
else if (rect_type == TRT_BackgroundClipRect)
|
|
{
|
|
return table->BgClipRect;
|
|
}
|
|
else if (rect_type == TRT_ColumnsRect)
|
|
{
|
|
ImGuiTableColumn* c = &table->Columns[n];
|
|
return ImRect(c->MinX, table->InnerClipRect.Min.y, c->MaxX,
|
|
table->InnerClipRect.Min.y + table->LastOuterHeight);
|
|
}
|
|
else if (rect_type == TRT_ColumnsWorkRect)
|
|
{
|
|
ImGuiTableColumn* c = &table->Columns[n];
|
|
return ImRect(c->WorkMinX, table->WorkRect.Min.y, c->WorkMaxX, table->WorkRect.Max.y);
|
|
}
|
|
else if (rect_type == TRT_ColumnsClipRect)
|
|
{
|
|
ImGuiTableColumn* c = &table->Columns[n];
|
|
return c->ClipRect;
|
|
}
|
|
else if (rect_type == TRT_ColumnsContentHeadersUsed)
|
|
{
|
|
ImGuiTableColumn* c = &table->Columns[n];
|
|
return ImRect(c->WorkMinX, table->InnerClipRect.Min.y, c->ContentMaxXHeadersUsed,
|
|
table->InnerClipRect.Min.y + table->LastFirstRowHeight);
|
|
} // Note: y1/y2 not always accurate
|
|
else if (rect_type == TRT_ColumnsContentHeadersIdeal)
|
|
{
|
|
ImGuiTableColumn* c = &table->Columns[n];
|
|
return ImRect(c->WorkMinX, table->InnerClipRect.Min.y, c->ContentMaxXHeadersIdeal,
|
|
table->InnerClipRect.Min.y + table->LastFirstRowHeight);
|
|
}
|
|
else if (rect_type == TRT_ColumnsContentFrozen)
|
|
{
|
|
ImGuiTableColumn* c = &table->Columns[n];
|
|
return ImRect(c->WorkMinX, table->InnerClipRect.Min.y, c->ContentMaxXFrozen,
|
|
table->InnerClipRect.Min.y + table->LastFirstRowHeight);
|
|
}
|
|
else if (rect_type == TRT_ColumnsContentUnfrozen)
|
|
{
|
|
ImGuiTableColumn* c = &table->Columns[n];
|
|
return ImRect(c->WorkMinX, table->InnerClipRect.Min.y + table->LastFirstRowHeight,
|
|
c->ContentMaxXUnfrozen, table->InnerClipRect.Max.y);
|
|
}
|
|
IM_ASSERT(0);
|
|
return ImRect();
|
|
}
|
|
|
|
static ImRect GetWindowRect(ImGuiWindow* window, int rect_type)
|
|
{
|
|
if (rect_type == WRT_OuterRect)
|
|
{
|
|
return window->Rect();
|
|
}
|
|
else if (rect_type == WRT_OuterRectClipped)
|
|
{
|
|
return window->OuterRectClipped;
|
|
}
|
|
else if (rect_type == WRT_InnerRect)
|
|
{
|
|
return window->InnerRect;
|
|
}
|
|
else if (rect_type == WRT_InnerClipRect)
|
|
{
|
|
return window->InnerClipRect;
|
|
}
|
|
else if (rect_type == WRT_WorkRect)
|
|
{
|
|
return window->WorkRect;
|
|
}
|
|
else if (rect_type == WRT_Content)
|
|
{
|
|
ImVec2 min = window->InnerRect.Min - window->Scroll + window->WindowPadding;
|
|
return ImRect(min, min + window->ContentSize);
|
|
}
|
|
else if (rect_type == WRT_ContentIdeal)
|
|
{
|
|
ImVec2 min = window->InnerRect.Min - window->Scroll + window->WindowPadding;
|
|
return ImRect(min, min + window->ContentSizeIdeal);
|
|
}
|
|
else if (rect_type == WRT_ContentRegionRect)
|
|
{
|
|
return window->ContentRegionRect;
|
|
}
|
|
IM_ASSERT(0);
|
|
return ImRect();
|
|
}
|
|
};
|
|
|
|
// Tools
|
|
if (TreeNode("Tools"))
|
|
{
|
|
// The Item Picker tool is super useful to visually select an item and break into the call-stack of where it was
|
|
// submitted.
|
|
if (Button("Item Picker.."))
|
|
DebugStartItemPicker();
|
|
SameLine();
|
|
MetricsHelpMarker(
|
|
"Will call the IM_DEBUG_BREAK() macro to break in debugger.\nWarning: If you don't have a debugger "
|
|
"attached, this will probably crash.");
|
|
|
|
Checkbox("Show windows begin order", &cfg->ShowWindowsBeginOrder);
|
|
Checkbox("Show windows rectangles", &cfg->ShowWindowsRects);
|
|
SameLine();
|
|
SetNextItemWidth(GetFontSize() * 12);
|
|
cfg->ShowWindowsRects |=
|
|
Combo("##show_windows_rect_type", &cfg->ShowWindowsRectsType, wrt_rects_names, WRT_Count, WRT_Count);
|
|
if (cfg->ShowWindowsRects && g.NavWindow != NULL)
|
|
{
|
|
BulletText("'%s':", g.NavWindow->Name);
|
|
Indent();
|
|
for (int rect_n = 0; rect_n < WRT_Count; rect_n++)
|
|
{
|
|
ImRect r = Funcs::GetWindowRect(g.NavWindow, rect_n);
|
|
Text("(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) %s", r.Min.x, r.Min.y, r.Max.x, r.Max.y,
|
|
r.GetWidth(), r.GetHeight(), wrt_rects_names[rect_n]);
|
|
}
|
|
Unindent();
|
|
}
|
|
Checkbox("Show ImDrawCmd mesh when hovering", &cfg->ShowDrawCmdMesh);
|
|
Checkbox("Show ImDrawCmd bounding boxes when hovering", &cfg->ShowDrawCmdBoundingBoxes);
|
|
|
|
Checkbox("Show tables rectangles", &cfg->ShowTablesRects);
|
|
SameLine();
|
|
SetNextItemWidth(GetFontSize() * 12);
|
|
cfg->ShowTablesRects |=
|
|
Combo("##show_table_rects_type", &cfg->ShowTablesRectsType, trt_rects_names, TRT_Count, TRT_Count);
|
|
if (cfg->ShowTablesRects && g.NavWindow != NULL)
|
|
{
|
|
for (int table_n = 0; table_n < g.Tables.GetMapSize(); table_n++)
|
|
{
|
|
ImGuiTable* table = g.Tables.TryGetMapData(table_n);
|
|
if (table == NULL || table->LastFrameActive < g.FrameCount - 1 ||
|
|
(table->OuterWindow != g.NavWindow && table->InnerWindow != g.NavWindow))
|
|
continue;
|
|
|
|
BulletText("Table 0x%08X (%d columns, in '%s')", table->ID, table->ColumnsCount,
|
|
table->OuterWindow->Name);
|
|
if (IsItemHovered())
|
|
GetForegroundDrawList()->AddRect(table->OuterRect.Min - ImVec2(1, 1),
|
|
table->OuterRect.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255),
|
|
0.0f, 0, 2.0f);
|
|
Indent();
|
|
char buf[128];
|
|
for (int rect_n = 0; rect_n < TRT_Count; rect_n++)
|
|
{
|
|
if (rect_n >= TRT_ColumnsRect)
|
|
{
|
|
if (rect_n != TRT_ColumnsRect && rect_n != TRT_ColumnsClipRect)
|
|
continue;
|
|
for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
|
|
{
|
|
ImRect r = Funcs::GetTableRect(table, rect_n, column_n);
|
|
ImFormatString(buf, IM_ARRAYSIZE(buf),
|
|
"(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) Col %d %s", r.Min.x, r.Min.y,
|
|
r.Max.x, r.Max.y, r.GetWidth(), r.GetHeight(), column_n,
|
|
trt_rects_names[rect_n]);
|
|
Selectable(buf);
|
|
if (IsItemHovered())
|
|
GetForegroundDrawList()->AddRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1),
|
|
IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ImRect r = Funcs::GetTableRect(table, rect_n, -1);
|
|
ImFormatString(buf, IM_ARRAYSIZE(buf), "(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) %s",
|
|
r.Min.x, r.Min.y, r.Max.x, r.Max.y, r.GetWidth(), r.GetHeight(),
|
|
trt_rects_names[rect_n]);
|
|
Selectable(buf);
|
|
if (IsItemHovered())
|
|
GetForegroundDrawList()->AddRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1),
|
|
IM_COL32(255, 255, 0, 255), 0.0f, 0, 2.0f);
|
|
}
|
|
}
|
|
Unindent();
|
|
}
|
|
}
|
|
|
|
TreePop();
|
|
}
|
|
|
|
// Windows
|
|
DebugNodeWindowsList(&g.Windows, "Windows");
|
|
// DebugNodeWindowsList(&g.WindowsFocusOrder, "WindowsFocusOrder");
|
|
|
|
// DrawLists
|
|
int drawlist_count = 0;
|
|
for (int viewport_i = 0; viewport_i < g.Viewports.Size; viewport_i++)
|
|
drawlist_count += g.Viewports[viewport_i]->DrawDataBuilder.GetDrawListCount();
|
|
if (TreeNode("DrawLists", "DrawLists (%d)", drawlist_count))
|
|
{
|
|
for (int viewport_i = 0; viewport_i < g.Viewports.Size; viewport_i++)
|
|
{
|
|
ImGuiViewportP* viewport = g.Viewports[viewport_i];
|
|
bool viewport_has_drawlist = false;
|
|
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++)
|
|
{
|
|
if (!viewport_has_drawlist)
|
|
Text("Active DrawLists in Viewport #%d, ID: 0x%08X", viewport->Idx, viewport->ID);
|
|
viewport_has_drawlist = true;
|
|
DebugNodeDrawList(NULL, viewport, viewport->DrawDataBuilder.Layers[layer_i][draw_list_i],
|
|
"DrawList");
|
|
}
|
|
}
|
|
TreePop();
|
|
}
|
|
|
|
// Viewports
|
|
if (TreeNode("Viewports", "Viewports (%d)", g.Viewports.Size))
|
|
{
|
|
Indent(GetTreeNodeToLabelSpacing());
|
|
RenderViewportsThumbnails();
|
|
Unindent(GetTreeNodeToLabelSpacing());
|
|
|
|
bool open = TreeNode("Monitors", "Monitors (%d)", g.PlatformIO.Monitors.Size);
|
|
SameLine();
|
|
MetricsHelpMarker(
|
|
"Dear ImGui uses monitor data:\n- to query DPI settings on a per monitor basis\n- to position "
|
|
"popup/tooltips so they don't straddle monitors.");
|
|
if (open)
|
|
{
|
|
for (int i = 0; i < g.PlatformIO.Monitors.Size; i++)
|
|
{
|
|
const ImGuiPlatformMonitor& mon = g.PlatformIO.Monitors[i];
|
|
BulletText(
|
|
"Monitor #%d: DPI %.0f%%\n MainMin (%.0f,%.0f), MainMax (%.0f,%.0f), MainSize (%.0f,%.0f)\n "
|
|
"WorkMin (%.0f,%.0f), WorkMax (%.0f,%.0f), WorkSize (%.0f,%.0f)",
|
|
i, mon.DpiScale * 100.0f, mon.MainPos.x, mon.MainPos.y, mon.MainPos.x + mon.MainSize.x,
|
|
mon.MainPos.y + mon.MainSize.y, mon.MainSize.x, mon.MainSize.y, mon.WorkPos.x, mon.WorkPos.y,
|
|
mon.WorkPos.x + mon.WorkSize.x, mon.WorkPos.y + mon.WorkSize.y, mon.WorkSize.x, mon.WorkSize.y);
|
|
}
|
|
TreePop();
|
|
}
|
|
|
|
if (TreeNode("Inferred order (front-to-back)"))
|
|
{
|
|
static ImVector<ImGuiViewportP*> viewports;
|
|
viewports.resize(g.Viewports.Size);
|
|
memcpy(viewports.Data, g.Viewports.Data, g.Viewports.size_in_bytes());
|
|
if (viewports.Size > 1)
|
|
ImQsort(viewports.Data, viewports.Size, sizeof(ImGuiViewport*), ViewportComparerByFrontMostStampCount);
|
|
for (int i = 0; i < viewports.Size; i++)
|
|
BulletText("Viewport #%d, ID: 0x%08X, FrontMostStampCount = %08d, Window: \"%s\"", viewports[i]->Idx,
|
|
viewports[i]->ID, viewports[i]->LastFrontMostStampCount,
|
|
viewports[i]->Window ? viewports[i]->Window->Name : "N/A");
|
|
TreePop();
|
|
}
|
|
|
|
for (int i = 0; i < g.Viewports.Size; i++)
|
|
DebugNodeViewport(g.Viewports[i]);
|
|
TreePop();
|
|
}
|
|
|
|
// Details for Popups
|
|
if (TreeNode("Popups", "Popups (%d)", g.OpenPopupStack.Size))
|
|
{
|
|
for (int i = 0; i < g.OpenPopupStack.Size; i++)
|
|
{
|
|
ImGuiWindow* window = g.OpenPopupStack[i].Window;
|
|
BulletText("PopupID: %08x, Window: '%s'%s%s", g.OpenPopupStack[i].PopupId, window ? window->Name : "NULL",
|
|
window && (window->Flags & ImGuiWindowFlags_ChildWindow) ? " ChildWindow" : "",
|
|
window && (window->Flags & ImGuiWindowFlags_ChildMenu) ? " ChildMenu" : "");
|
|
}
|
|
TreePop();
|
|
}
|
|
|
|
// Details for TabBars
|
|
if (TreeNode("TabBars", "Tab Bars (%d)", g.TabBars.GetAliveCount()))
|
|
{
|
|
for (int n = 0; n < g.TabBars.GetMapSize(); n++)
|
|
if (ImGuiTabBar* tab_bar = g.TabBars.TryGetMapData(n))
|
|
{
|
|
PushID(tab_bar);
|
|
DebugNodeTabBar(tab_bar, "TabBar");
|
|
PopID();
|
|
}
|
|
TreePop();
|
|
}
|
|
|
|
// Details for Tables
|
|
if (TreeNode("Tables", "Tables (%d)", g.Tables.GetAliveCount()))
|
|
{
|
|
for (int n = 0; n < g.Tables.GetMapSize(); n++)
|
|
if (ImGuiTable* table = g.Tables.TryGetMapData(n))
|
|
DebugNodeTable(table);
|
|
TreePop();
|
|
}
|
|
|
|
// Details for Fonts
|
|
# ifndef IMGUI_DISABLE_DEMO_WINDOWS
|
|
ImFontAtlas* atlas = g.IO.Fonts;
|
|
if (TreeNode("Fonts", "Fonts (%d)", atlas->Fonts.Size))
|
|
{
|
|
ShowFontAtlas(atlas);
|
|
TreePop();
|
|
}
|
|
# endif
|
|
|
|
// Details for Docking
|
|
# ifdef IMGUI_HAS_DOCK
|
|
if (TreeNode("Docking"))
|
|
{
|
|
static bool root_nodes_only = true;
|
|
ImGuiDockContext* dc = &g.DockContext;
|
|
Checkbox("List root nodes", &root_nodes_only);
|
|
Checkbox("Ctrl shows window dock info", &cfg->ShowDockingNodes);
|
|
if (SmallButton("Clear nodes"))
|
|
{
|
|
DockContextClearNodes(&g, 0, true);
|
|
}
|
|
SameLine();
|
|
if (SmallButton("Rebuild all"))
|
|
{
|
|
dc->WantFullRebuild = true;
|
|
}
|
|
for (int n = 0; n < dc->Nodes.Data.Size; n++)
|
|
if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p)
|
|
if (!root_nodes_only || node->IsRootNode())
|
|
DebugNodeDockNode(node, "Node");
|
|
TreePop();
|
|
}
|
|
# endif // #ifdef IMGUI_HAS_DOCK
|
|
|
|
// Settings
|
|
if (TreeNode("Settings"))
|
|
{
|
|
if (SmallButton("Clear"))
|
|
ClearIniSettings();
|
|
SameLine();
|
|
if (SmallButton("Save to memory"))
|
|
SaveIniSettingsToMemory();
|
|
SameLine();
|
|
if (SmallButton("Save to disk"))
|
|
SaveIniSettingsToDisk(g.IO.IniFilename);
|
|
SameLine();
|
|
if (g.IO.IniFilename)
|
|
Text("\"%s\"", g.IO.IniFilename);
|
|
else
|
|
TextUnformatted("<NULL>");
|
|
Text("SettingsDirtyTimer %.2f", g.SettingsDirtyTimer);
|
|
if (TreeNode("SettingsHandlers", "Settings handlers: (%d)", g.SettingsHandlers.Size))
|
|
{
|
|
for (int n = 0; n < g.SettingsHandlers.Size; n++)
|
|
BulletText("%s", g.SettingsHandlers[n].TypeName);
|
|
TreePop();
|
|
}
|
|
if (TreeNode("SettingsWindows", "Settings packed data: Windows: %d bytes", g.SettingsWindows.size()))
|
|
{
|
|
for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL;
|
|
settings = g.SettingsWindows.next_chunk(settings))
|
|
DebugNodeWindowSettings(settings);
|
|
TreePop();
|
|
}
|
|
|
|
if (TreeNode("SettingsTables", "Settings packed data: Tables: %d bytes", g.SettingsTables.size()))
|
|
{
|
|
for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL;
|
|
settings = g.SettingsTables.next_chunk(settings))
|
|
DebugNodeTableSettings(settings);
|
|
TreePop();
|
|
}
|
|
|
|
# ifdef IMGUI_HAS_DOCK
|
|
if (TreeNode("SettingsDocking", "Settings packed data: Docking"))
|
|
{
|
|
ImGuiDockContext* dc = &g.DockContext;
|
|
Text("In SettingsWindows:");
|
|
for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL;
|
|
settings = g.SettingsWindows.next_chunk(settings))
|
|
if (settings->DockId != 0)
|
|
BulletText("Window '%s' -> DockId %08X", settings->GetName(), settings->DockId);
|
|
Text("In SettingsNodes:");
|
|
for (int n = 0; n < dc->NodesSettings.Size; n++)
|
|
{
|
|
ImGuiDockNodeSettings* settings = &dc->NodesSettings[n];
|
|
const char* selected_tab_name = NULL;
|
|
if (settings->SelectedTabId)
|
|
{
|
|
if (ImGuiWindow* window = FindWindowByID(settings->SelectedTabId))
|
|
selected_tab_name = window->Name;
|
|
else if (ImGuiWindowSettings* window_settings = FindWindowSettings(settings->SelectedTabId))
|
|
selected_tab_name = window_settings->GetName();
|
|
}
|
|
BulletText("Node %08X, Parent %08X, SelectedTab %08X ('%s')", settings->ID, settings->ParentNodeId,
|
|
settings->SelectedTabId,
|
|
selected_tab_name ? selected_tab_name
|
|
: settings->SelectedTabId ? "N/A"
|
|
: "");
|
|
}
|
|
TreePop();
|
|
}
|
|
# endif // #ifdef IMGUI_HAS_DOCK
|
|
|
|
if (TreeNode("SettingsIniData", "Settings unpacked data (.ini): %d bytes", g.SettingsIniData.size()))
|
|
{
|
|
InputTextMultiline("##Ini", (char*)(void*)g.SettingsIniData.c_str(), g.SettingsIniData.Buf.Size,
|
|
ImVec2(-FLT_MIN, GetTextLineHeight() * 20), ImGuiInputTextFlags_ReadOnly);
|
|
TreePop();
|
|
}
|
|
TreePop();
|
|
}
|
|
|
|
// Misc Details
|
|
if (TreeNode("Internal state"))
|
|
{
|
|
const char* input_source_names[] = {"None", "Mouse", "Keyboard", "Gamepad", "Nav", "Clipboard"};
|
|
IM_ASSERT(IM_ARRAYSIZE(input_source_names) == ImGuiInputSource_COUNT);
|
|
|
|
Text("WINDOWING");
|
|
Indent();
|
|
Text("HoveredWindow: '%s'", g.HoveredWindow ? g.HoveredWindow->Name : "NULL");
|
|
Text("HoveredWindow->Root: '%s'", g.HoveredWindow ? g.HoveredWindow->RootWindowDockTree->Name : "NULL");
|
|
Text("HoveredWindowUnderMovingWindow: '%s'",
|
|
g.HoveredWindowUnderMovingWindow ? g.HoveredWindowUnderMovingWindow->Name : "NULL");
|
|
Text("HoveredDockNode: 0x%08X", g.HoveredDockNode ? g.HoveredDockNode->ID : 0);
|
|
Text("MovingWindow: '%s'", g.MovingWindow ? g.MovingWindow->Name : "NULL");
|
|
Text("MouseViewport: 0x%08X (UserHovered 0x%08X, LastHovered 0x%08X)", g.MouseViewport->ID,
|
|
g.IO.MouseHoveredViewport, g.MouseLastHoveredViewport ? g.MouseLastHoveredViewport->ID : 0);
|
|
Unindent();
|
|
|
|
Text("ITEMS");
|
|
Indent();
|
|
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]);
|
|
Text("ActiveIdWindow: '%s'", g.ActiveIdWindow ? g.ActiveIdWindow->Name : "NULL");
|
|
Text("ActiveIdUsing: Wheel: %d, NavDirMask: %X, NavInputMask: %X, KeyInputMask: %llX",
|
|
g.ActiveIdUsingMouseWheel, g.ActiveIdUsingNavDirMask, g.ActiveIdUsingNavInputMask,
|
|
g.ActiveIdUsingKeyInputMask);
|
|
Text("HoveredId: 0x%08X (%.2f sec), AllowOverlap: %d", g.HoveredIdPreviousFrame, g.HoveredIdTimer,
|
|
g.HoveredIdAllowOverlap); // Not displaying g.HoveredId as it is update mid-frame
|
|
Text("DragDrop: %d, SourceId = 0x%08X, Payload \"%s\" (%d bytes)", g.DragDropActive, g.DragDropPayload.SourceId,
|
|
g.DragDropPayload.DataType, g.DragDropPayload.DataSize);
|
|
Unindent();
|
|
|
|
Text("NAV,FOCUS");
|
|
Indent();
|
|
Text("NavWindow: '%s'", g.NavWindow ? g.NavWindow->Name : "NULL");
|
|
Text("NavId: 0x%08X, NavLayer: %d", g.NavId, g.NavLayer);
|
|
Text("NavInputSource: %s", input_source_names[g.NavInputSource]);
|
|
Text("NavActive: %d, NavVisible: %d", g.IO.NavActive, g.IO.NavVisible);
|
|
Text("NavActivateId: 0x%08X, NavInputId: 0x%08X", g.NavActivateId, g.NavInputId);
|
|
Text("NavDisableHighlight: %d, NavDisableMouseHover: %d", g.NavDisableHighlight, g.NavDisableMouseHover);
|
|
Text("NavFocusScopeId = 0x%08X", g.NavFocusScopeId);
|
|
Text("NavWindowingTarget: '%s'", g.NavWindowingTarget ? g.NavWindowingTarget->Name : "NULL");
|
|
Unindent();
|
|
|
|
TreePop();
|
|
}
|
|
|
|
// Overlay: Display windows Rectangles and Begin Order
|
|
if (cfg->ShowWindowsRects || cfg->ShowWindowsBeginOrder)
|
|
{
|
|
for (int n = 0; n < g.Windows.Size; n++)
|
|
{
|
|
ImGuiWindow* window = g.Windows[n];
|
|
if (!window->WasActive)
|
|
continue;
|
|
ImDrawList* draw_list = GetForegroundDrawList(window);
|
|
if (cfg->ShowWindowsRects)
|
|
{
|
|
ImRect r = Funcs::GetWindowRect(window, cfg->ShowWindowsRectsType);
|
|
draw_list->AddRect(r.Min, r.Max, IM_COL32(255, 0, 128, 255));
|
|
}
|
|
if (cfg->ShowWindowsBeginOrder && !(window->Flags & ImGuiWindowFlags_ChildWindow))
|
|
{
|
|
char buf[32];
|
|
ImFormatString(buf, IM_ARRAYSIZE(buf), "%d", window->BeginOrderWithinContext);
|
|
float font_size = GetFontSize();
|
|
draw_list->AddRectFilled(window->Pos, window->Pos + ImVec2(font_size, font_size),
|
|
IM_COL32(200, 100, 100, 255));
|
|
draw_list->AddText(window->Pos, IM_COL32(255, 255, 255, 255), buf);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Overlay: Display Tables Rectangles
|
|
if (cfg->ShowTablesRects)
|
|
{
|
|
for (int table_n = 0; table_n < g.Tables.GetMapSize(); table_n++)
|
|
{
|
|
ImGuiTable* table = g.Tables.TryGetMapData(table_n);
|
|
if (table == NULL || table->LastFrameActive < g.FrameCount - 1)
|
|
continue;
|
|
ImDrawList* draw_list = GetForegroundDrawList(table->OuterWindow);
|
|
if (cfg->ShowTablesRectsType >= TRT_ColumnsRect)
|
|
{
|
|
for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
|
|
{
|
|
ImRect r = Funcs::GetTableRect(table, cfg->ShowTablesRectsType, column_n);
|
|
ImU32 col = (table->HoveredColumnBody == column_n) ? IM_COL32(255, 255, 128, 255)
|
|
: IM_COL32(255, 0, 128, 255);
|
|
float thickness = (table->HoveredColumnBody == column_n) ? 3.0f : 1.0f;
|
|
draw_list->AddRect(r.Min, r.Max, col, 0.0f, 0, thickness);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ImRect r = Funcs::GetTableRect(table, cfg->ShowTablesRectsType, -1);
|
|
draw_list->AddRect(r.Min, r.Max, IM_COL32(255, 0, 128, 255));
|
|
}
|
|
}
|
|
}
|
|
|
|
# ifdef IMGUI_HAS_DOCK
|
|
// Overlay: Display Docking info
|
|
if (cfg->ShowDockingNodes && g.IO.KeyCtrl && g.HoveredDockNode)
|
|
{
|
|
char buf[64] = "";
|
|
char* p = buf;
|
|
ImGuiDockNode* node = g.HoveredDockNode;
|
|
ImDrawList* overlay_draw_list =
|
|
node->HostWindow ? GetForegroundDrawList(node->HostWindow) : GetForegroundDrawList(GetMainViewport());
|
|
p += ImFormatString(p, buf + IM_ARRAYSIZE(buf) - p, "DockId: %X%s\n", node->ID,
|
|
node->IsCentralNode() ? " *CentralNode*" : "");
|
|
p += ImFormatString(p, buf + IM_ARRAYSIZE(buf) - p, "WindowClass: %08X\n", node->WindowClass.ClassId);
|
|
p += ImFormatString(p, buf + IM_ARRAYSIZE(buf) - p, "Size: (%.0f, %.0f)\n", node->Size.x, node->Size.y);
|
|
p +=
|
|
ImFormatString(p, buf + IM_ARRAYSIZE(buf) - p, "SizeRef: (%.0f, %.0f)\n", node->SizeRef.x, node->SizeRef.y);
|
|
int depth = DockNodeGetDepth(node);
|
|
overlay_draw_list->AddRect(node->Pos + ImVec2(3, 3) * (float)depth,
|
|
node->Pos + node->Size - ImVec2(3, 3) * (float)depth, IM_COL32(200, 100, 100, 255));
|
|
ImVec2 pos = node->Pos + ImVec2(3, 3) * (float)depth;
|
|
overlay_draw_list->AddRectFilled(pos - ImVec2(1, 1), pos + CalcTextSize(buf) + ImVec2(1, 1),
|
|
IM_COL32(200, 100, 100, 255));
|
|
overlay_draw_list->AddText(NULL, 0.0f, pos, IM_COL32(255, 255, 255, 255), buf);
|
|
}
|
|
# endif // #ifdef IMGUI_HAS_DOCK
|
|
|
|
End();
|
|
}
|
|
|
|
// [DEBUG] List fonts in a font atlas and display its texture
|
|
void ImGui::ShowFontAtlas(ImFontAtlas* atlas)
|
|
{
|
|
for (int i = 0; i < atlas->Fonts.Size; i++)
|
|
{
|
|
ImFont* font = atlas->Fonts[i];
|
|
PushID(font);
|
|
DebugNodeFont(font);
|
|
PopID();
|
|
}
|
|
if (TreeNode("Atlas texture", "Atlas texture (%dx%d pixels)", atlas->TexWidth, atlas->TexHeight))
|
|
{
|
|
ImVec4 tint_col = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
|
|
ImVec4 border_col = ImVec4(1.0f, 1.0f, 1.0f, 0.5f);
|
|
Image(atlas->TexID, ImVec2((float)atlas->TexWidth, (float)atlas->TexHeight), ImVec2(0.0f, 0.0f),
|
|
ImVec2(1.0f, 1.0f), tint_col, border_col);
|
|
TreePop();
|
|
}
|
|
}
|
|
|
|
// [DEBUG] Display contents of Columns
|
|
void ImGui::DebugNodeColumns(ImGuiOldColumns* columns)
|
|
{
|
|
if (!TreeNode((void*)(uintptr_t)columns->ID, "Columns Id: 0x%08X, Count: %d, Flags: 0x%04X", columns->ID,
|
|
columns->Count, columns->Flags))
|
|
return;
|
|
BulletText("Width: %.1f (MinX: %.1f, MaxX: %.1f)", columns->OffMaxX - columns->OffMinX, columns->OffMinX,
|
|
columns->OffMaxX);
|
|
for (int column_n = 0; column_n < columns->Columns.Size; column_n++)
|
|
BulletText("Column %02d: OffsetNorm %.3f (= %.1f px)", column_n, columns->Columns[column_n].OffsetNorm,
|
|
GetColumnOffsetFromNorm(columns, columns->Columns[column_n].OffsetNorm));
|
|
TreePop();
|
|
}
|
|
|
|
static void DebugNodeDockNodeFlags(ImGuiDockNodeFlags* p_flags, const char* label, bool enabled)
|
|
{
|
|
using namespace ImGui;
|
|
PushID(label);
|
|
PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 0.0f));
|
|
Text("%s:", label);
|
|
if (!enabled)
|
|
BeginDisabled();
|
|
CheckboxFlags("NoSplit", p_flags, ImGuiDockNodeFlags_NoSplit);
|
|
CheckboxFlags("NoResize", p_flags, ImGuiDockNodeFlags_NoResize);
|
|
CheckboxFlags("NoResizeX", p_flags, ImGuiDockNodeFlags_NoResizeX);
|
|
CheckboxFlags("NoResizeY", p_flags, ImGuiDockNodeFlags_NoResizeY);
|
|
CheckboxFlags("NoTabBar", p_flags, ImGuiDockNodeFlags_NoTabBar);
|
|
CheckboxFlags("HiddenTabBar", p_flags, ImGuiDockNodeFlags_HiddenTabBar);
|
|
CheckboxFlags("NoWindowMenuButton", p_flags, ImGuiDockNodeFlags_NoWindowMenuButton);
|
|
CheckboxFlags("NoCloseButton", p_flags, ImGuiDockNodeFlags_NoCloseButton);
|
|
CheckboxFlags("NoDocking", p_flags, ImGuiDockNodeFlags_NoDocking);
|
|
CheckboxFlags("NoDockingSplitMe", p_flags, ImGuiDockNodeFlags_NoDockingSplitMe);
|
|
CheckboxFlags("NoDockingSplitOther", p_flags, ImGuiDockNodeFlags_NoDockingSplitOther);
|
|
CheckboxFlags("NoDockingOverMe", p_flags, ImGuiDockNodeFlags_NoDockingOverMe);
|
|
CheckboxFlags("NoDockingOverOther", p_flags, ImGuiDockNodeFlags_NoDockingOverOther);
|
|
CheckboxFlags("NoDockingOverEmpty", p_flags, ImGuiDockNodeFlags_NoDockingOverEmpty);
|
|
if (!enabled)
|
|
EndDisabled();
|
|
PopStyleVar();
|
|
PopID();
|
|
}
|
|
|
|
// [DEBUG] Display contents of ImDockNode
|
|
void ImGui::DebugNodeDockNode(ImGuiDockNode* node, const char* label)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
const bool is_alive = (g.FrameCount - node->LastFrameAlive < 2); // Submitted with ImGuiDockNodeFlags_KeepAliveOnly
|
|
const bool is_active = (g.FrameCount - node->LastFrameActive < 2); // Submitted
|
|
if (!is_alive)
|
|
{
|
|
PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled));
|
|
}
|
|
bool open;
|
|
if (node->Windows.Size > 0)
|
|
open = 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 = TreeNode((void*)(intptr_t)node->ID, "%s 0x%04X%s: %s split (vis: '%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 (!is_alive)
|
|
{
|
|
PopStyleColor();
|
|
}
|
|
if (is_active && IsItemHovered())
|
|
if (ImGuiWindow* window = node->HostWindow ? node->HostWindow : node->VisibleWindow)
|
|
GetForegroundDrawList(window)->AddRect(node->Pos, node->Pos + node->Size, IM_COL32(255, 255, 0, 255));
|
|
if (open)
|
|
{
|
|
IM_ASSERT(node->ChildNodes[0] == NULL || node->ChildNodes[0]->ParentNode == node);
|
|
IM_ASSERT(node->ChildNodes[1] == NULL || node->ChildNodes[1]->ParentNode == node);
|
|
BulletText("Pos (%.0f,%.0f), Size (%.0f, %.0f) Ref (%.0f, %.0f)", node->Pos.x, node->Pos.y, node->Size.x,
|
|
node->Size.y, node->SizeRef.x, node->SizeRef.y);
|
|
DebugNodeWindow(node->HostWindow, "HostWindow");
|
|
DebugNodeWindow(node->VisibleWindow, "VisibleWindow");
|
|
BulletText("SelectedTabID: 0x%08X, LastFocusedNodeID: 0x%08X", node->SelectedTabId, node->LastFocusedNodeId);
|
|
BulletText("Misc:%s%s%s%s%s", node->IsDockSpace() ? " IsDockSpace" : "",
|
|
node->IsCentralNode() ? " IsCentralNode" : "", is_alive ? " IsAlive" : "",
|
|
is_active ? " IsActive" : "", node->WantLockSizeOnce ? " WantLockSizeOnce" : "");
|
|
if (TreeNode("flags", "Flags Merged: 0x%04X, Local: 0x%04X, InWindows: 0x%04X, Shared: 0x%04X",
|
|
node->MergedFlags, node->LocalFlags, node->LocalFlagsInWindows, node->SharedFlags))
|
|
{
|
|
if (BeginTable("flags", 4))
|
|
{
|
|
TableNextColumn();
|
|
DebugNodeDockNodeFlags(&node->MergedFlags, "MergedFlags", false);
|
|
TableNextColumn();
|
|
DebugNodeDockNodeFlags(&node->LocalFlags, "LocalFlags", true);
|
|
TableNextColumn();
|
|
DebugNodeDockNodeFlags(&node->LocalFlagsInWindows, "LocalFlagsInWindows", false);
|
|
TableNextColumn();
|
|
DebugNodeDockNodeFlags(&node->SharedFlags, "SharedFlags", true);
|
|
EndTable();
|
|
}
|
|
TreePop();
|
|
}
|
|
if (node->ParentNode)
|
|
DebugNodeDockNode(node->ParentNode, "ParentNode");
|
|
if (node->ChildNodes[0])
|
|
DebugNodeDockNode(node->ChildNodes[0], "Child[0]");
|
|
if (node->ChildNodes[1])
|
|
DebugNodeDockNode(node->ChildNodes[1], "Child[1]");
|
|
if (node->TabBar)
|
|
DebugNodeTabBar(node->TabBar, "TabBar");
|
|
TreePop();
|
|
}
|
|
}
|
|
|
|
// [DEBUG] Display contents of ImDrawList
|
|
// Note that both 'window' and 'viewport' may be NULL here. Viewport is generally null of destroyed popups which
|
|
// previously owned a viewport.
|
|
void ImGui::DebugNodeDrawList(ImGuiWindow* window,
|
|
ImGuiViewportP* viewport,
|
|
const ImDrawList* draw_list,
|
|
const char* label)
|
|
{
|
|
ImGuiContext& g = *GImGui;
|
|
ImGuiMetricsConfig* cfg = &g.DebugMetricsConfig;
|
|
int cmd_count = draw_list->CmdBuffer.Size;
|
|
if (cmd_count > 0 && draw_list->CmdBuffer.back().ElemCount == 0 && draw_list->CmdBuffer.back().UserCallback == NULL)
|
|
cmd_count--;
|
|
bool node_open = 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, cmd_count);
|
|
if (draw_list == GetWindowDrawList())
|
|
{
|
|
SameLine();
|
|
TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f),
|
|
"CURRENTLY APPENDING"); // Can't display stats for active draw list! (we don't have the data
|
|
// double-buffered)
|
|
if (node_open)
|
|
TreePop();
|
|
return;
|
|
}
|
|
|
|
ImDrawList* fg_draw_list =
|
|
viewport ? GetForegroundDrawList(viewport) : NULL; // Render additional visuals into the top-most draw list
|
|
if (window && fg_draw_list && IsItemHovered())
|
|
fg_draw_list->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 255, 0, 255));
|
|
if (!node_open)
|
|
return;
|
|
|
|
if (window && !window->WasActive)
|
|
TextDisabled("Warning: owning Window is inactive. This DrawList is not being rendered!");
|
|
|
|
for (const ImDrawCmd* pcmd = draw_list->CmdBuffer.Data; pcmd < draw_list->CmdBuffer.Data + cmd_count; pcmd++)
|
|
{
|
|
if (pcmd->UserCallback)
|
|
{
|
|
BulletText("Callback %p, user_data %p", pcmd->UserCallback, pcmd->UserCallbackData);
|
|
continue;
|
|
}
|
|
|
|
char buf[300];
|
|
ImFormatString(buf, IM_ARRAYSIZE(buf), "DrawCmd:%5d tris, Tex 0x%p, ClipRect (%4.0f,%4.0f)-(%4.0f,%4.0f)",
|
|
pcmd->ElemCount / 3, (void*)(intptr_t)pcmd->TextureId, pcmd->ClipRect.x, pcmd->ClipRect.y,
|
|
pcmd->ClipRect.z, pcmd->ClipRect.w);
|
|
bool pcmd_node_open = TreeNode((void*)(pcmd - draw_list->CmdBuffer.begin()), "%s", buf);
|
|
if (IsItemHovered() && (cfg->ShowDrawCmdMesh || cfg->ShowDrawCmdBoundingBoxes) && fg_draw_list)
|
|
DebugNodeDrawCmdShowMeshAndBoundingBox(fg_draw_list, draw_list, pcmd, cfg->ShowDrawCmdMesh,
|
|
cfg->ShowDrawCmdBoundingBoxes);
|
|
if (!pcmd_node_open)
|
|
continue;
|
|
|
|
// Calculate approximate coverage area (touched pixel count)
|
|
// This will be in pixels squared as long there's no post-scaling happening to the renderer output.
|
|
const ImDrawIdx* idx_buffer = (draw_list->IdxBuffer.Size > 0) ? draw_list->IdxBuffer.Data : NULL;
|
|
const ImDrawVert* vtx_buffer = draw_list->VtxBuffer.Data + pcmd->VtxOffset;
|
|
float total_area = 0.0f;
|
|
for (unsigned int idx_n = pcmd->IdxOffset; idx_n < pcmd->IdxOffset + pcmd->ElemCount;)
|
|
{
|
|
ImVec2 triangle[3];
|
|
for (int n = 0; n < 3; n++, idx_n++)
|
|
triangle[n] = vtx_buffer[idx_buffer ? idx_buffer[idx_n] : idx_n].pos;
|
|
total_area += ImTriangleArea(triangle[0], triangle[1], triangle[2]);
|
|
}
|
|
|
|
// Display vertex information summary. Hover to get all triangles drawn in wire-frame
|
|
ImFormatString(buf, IM_ARRAYSIZE(buf), "Mesh: ElemCount: %d, VtxOffset: +%d, IdxOffset: +%d, Area: ~%0.f px",
|
|
pcmd->ElemCount, pcmd->VtxOffset, pcmd->IdxOffset, total_area);
|
|
Selectable(buf);
|
|
if (IsItemHovered() && fg_draw_list)
|
|
DebugNodeDrawCmdShowMeshAndBoundingBox(fg_draw_list, draw_list, pcmd, true, false);
|
|
|
|
// Display individual triangles/vertices. Hover on to get the corresponding triangle highlighted.
|
|
ImGuiListClipper clipper;
|
|
clipper.Begin(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, idx_i = pcmd->IdxOffset + clipper.DisplayStart * 3;
|
|
prim < clipper.DisplayEnd; prim++)
|
|
{
|
|
char *buf_p = buf, *buf_end = buf + IM_ARRAYSIZE(buf);
|
|
ImVec2 triangle[3];
|
|
for (int n = 0; n < 3; n++, idx_i++)
|
|
{
|
|
const ImDrawVert& v = vtx_buffer[idx_buffer ? idx_buffer[idx_i] : idx_i];
|
|
triangle[n] = v.pos;
|
|
buf_p +=
|
|
ImFormatString(buf_p, buf_end - buf_p, "%s %04d: pos (%8.2f,%8.2f), uv (%.6f,%.6f), col %08X\n",
|
|
(n == 0) ? "Vert:" : " ", idx_i, v.pos.x, v.pos.y, v.uv.x, v.uv.y, v.col);
|
|
}
|
|
|
|
Selectable(buf, false);
|
|
if (fg_draw_list && IsItemHovered())
|
|
{
|
|
ImDrawListFlags backup_flags = fg_draw_list->Flags;
|
|
fg_draw_list->Flags &=
|
|
~ImDrawListFlags_AntiAliasedLines; // Disable AA on triangle outlines is more readable for very
|
|
// large and thin triangles.
|
|
fg_draw_list->AddPolyline(triangle, 3, IM_COL32(255, 255, 0, 255), ImDrawFlags_Closed, 1.0f);
|
|
fg_draw_list->Flags = backup_flags;
|
|
}
|
|
}
|
|
TreePop();
|
|
}
|
|
TreePop();
|
|
}
|
|
|
|
// [DEBUG] Display mesh/aabb of a ImDrawCmd
|
|
void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list,
|
|
const ImDrawList* draw_list,
|
|
const ImDrawCmd* draw_cmd,
|
|
bool show_mesh,
|
|
bool show_aabb)
|
|
{
|
|
IM_ASSERT(show_mesh || show_aabb);
|
|
|
|
// Draw wire-frame version of all triangles
|
|
ImRect clip_rect = draw_cmd->ClipRect;
|
|
ImRect vtxs_rect(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX);
|
|
ImDrawListFlags backup_flags = out_draw_list->Flags;
|
|
out_draw_list->Flags &= ~ImDrawListFlags_AntiAliasedLines; // Disable AA on triangle outlines is more readable for
|
|
// very large and thin triangles.
|
|
for (unsigned int idx_n = draw_cmd->IdxOffset, idx_end = draw_cmd->IdxOffset + draw_cmd->ElemCount;
|
|
idx_n < idx_end;)
|
|
{
|
|
ImDrawIdx* idx_buffer = (draw_list->IdxBuffer.Size > 0)
|
|
? draw_list->IdxBuffer.Data
|
|
: NULL; // We don't hold on those pointers past iterations as ->AddPolyline() may
|
|
// invalidate them if out_draw_list==draw_list
|
|
ImDrawVert* vtx_buffer = draw_list->VtxBuffer.Data + draw_cmd->VtxOffset;
|
|
|
|
ImVec2 triangle[3];
|
|
for (int n = 0; n < 3; n++, idx_n++)
|
|
vtxs_rect.Add((triangle[n] = vtx_buffer[idx_buffer ? idx_buffer[idx_n] : idx_n].pos));
|
|
if (show_mesh)
|
|
out_draw_list->AddPolyline(triangle, 3, IM_COL32(255, 255, 0, 255), ImDrawFlags_Closed,
|
|
1.0f); // In yellow: mesh triangles
|
|
}
|
|
// Draw bounding boxes
|
|
if (show_aabb)
|
|
{
|
|
out_draw_list->AddRect(ImFloor(clip_rect.Min), ImFloor(clip_rect.Max),
|
|
IM_COL32(255, 0, 255, 255)); // In pink: clipping rectangle submitted to GPU
|
|
out_draw_list->AddRect(ImFloor(vtxs_rect.Min), ImFloor(vtxs_rect.Max),
|
|
IM_COL32(0, 255, 255, 255)); // In cyan: bounding box of triangles
|
|
}
|
|
out_draw_list->Flags = backup_flags;
|
|
}
|
|
|
|
// [DEBUG] Display details for a single font, called by ShowStyleEditor().
|
|
void ImGui::DebugNodeFont(ImFont* font)
|
|
{
|
|
bool opened =
|
|
TreeNode(font, "Font: \"%s\"\n%.2f px, %d glyphs, %d file(s)", font->ConfigData ? font->ConfigData[0].Name : "",
|
|
font->FontSize, font->Glyphs.Size, font->ConfigDataCount);
|
|
SameLine();
|
|
if (SmallButton("Set as default"))
|
|
GetIO().FontDefault = font;
|
|
if (!opened)
|
|
return;
|
|
|
|
// Display preview text
|
|
PushFont(font);
|
|
Text("The quick brown fox jumps over the lazy dog");
|
|
PopFont();
|
|
|
|
// Display details
|
|
SetNextItemWidth(GetFontSize() * 8);
|
|
DragFloat("Font scale", &font->Scale, 0.005f, 0.3f, 2.0f, "%.1f");
|
|
SameLine();
|
|
MetricsHelpMarker(
|
|
"Note than the default embedded font is NOT meant to be scaled.\n\n"
|
|
"Font are currently rendered into bitmaps at a given size at the time of building the atlas. "
|
|
"You may oversample them to get some flexibility with scaling. "
|
|
"You can also render at multiple sizes and select which one to use at runtime.\n\n"
|
|
"(Glimmer of hope: the atlas system will be rewritten in the future to make scaling more flexible.)");
|
|
Text("Ascent: %f, Descent: %f, Height: %f", font->Ascent, font->Descent, font->Ascent - font->Descent);
|
|
char c_str[5];
|
|
Text("Fallback character: '%s' (U+%04X)", ImTextCharToUtf8(c_str, font->FallbackChar), font->FallbackChar);
|
|
Text("Ellipsis character: '%s' (U+%04X)", ImTextCharToUtf8(c_str, font->EllipsisChar), font->EllipsisChar);
|
|
const int surface_sqrt = (int)ImSqrt((float)font->MetricsTotalSurface);
|
|
Text("Texture Area: about %d px ~%dx%d px", font->MetricsTotalSurface, surface_sqrt, surface_sqrt);
|
|
for (int config_i = 0; config_i < font->ConfigDataCount; config_i++)
|
|
if (font->ConfigData)
|
|
if (const ImFontConfig* cfg = &font->ConfigData[config_i])
|
|
BulletText("Input %d: \'%s\', Oversample: (%d,%d), PixelSnapH: %d, Offset: (%.1f,%.1f)", config_i,
|
|
cfg->Name, cfg->OversampleH, cfg->OversampleV, cfg->PixelSnapH, cfg->GlyphOffset.x,
|
|
cfg->GlyphOffset.y);
|
|
|
|
// Display all glyphs of the fonts in separate pages of 256 characters
|
|
if (TreeNode("Glyphs", "Glyphs (%d)", font->Glyphs.Size))
|
|
{
|
|
ImDrawList* draw_list = GetWindowDrawList();
|
|
const ImU32 glyph_col = GetColorU32(ImGuiCol_Text);
|
|
const float cell_size = font->FontSize * 1;
|
|
const float cell_spacing = GetStyle().ItemSpacing.y;
|
|
for (unsigned int base = 0; base <= IM_UNICODE_CODEPOINT_MAX; base += 256)
|
|
{
|
|
// Skip ahead if a large bunch of glyphs are not present in the font (test in chunks of 4k)
|
|
// This is only a small optimization to reduce the number of iterations when IM_UNICODE_MAX_CODEPOINT
|
|
// is large // (if ImWchar==ImWchar32 we will do at least about 272 queries here)
|
|
if (!(base & 4095) && font->IsGlyphRangeUnused(base, base + 4095))
|
|
{
|
|
base += 4096 - 256;
|
|
continue;
|
|
}
|
|
|
|
int count = 0;
|
|
for (unsigned int n = 0; n < 256; n++)
|
|
if (font->FindGlyphNoFallback((ImWchar)(base + n)))
|
|
count++;
|
|
if (count <= 0)
|
|
continue;
|
|
if (!TreeNode((void*)(intptr_t)base, "U+%04X..U+%04X (%d %s)", base, base + 255, count,
|
|
count > 1 ? "glyphs" : "glyph"))
|
|
continue;
|
|
|
|
// Draw a 16x16 grid of glyphs
|
|
ImVec2 base_pos = GetCursorScreenPos();
|
|
for (unsigned int n = 0; n < 256; n++)
|
|
{
|
|
// We use ImFont::RenderChar as a shortcut because we don't have UTF-8 conversion functions
|
|
// available here and thus cannot easily generate a zero-terminated UTF-8 encoded string.
|
|
ImVec2 cell_p1(base_pos.x + (n % 16) * (cell_size + cell_spacing),
|
|
base_pos.y + (n / 16) * (cell_size + cell_spacing));
|
|
ImVec2 cell_p2(cell_p1.x + cell_size, cell_p1.y + cell_size);
|
|
const ImFontGlyph* glyph = font->FindGlyphNoFallback((ImWchar)(base + n));
|
|
draw_list->AddRect(cell_p1, cell_p2,
|
|
glyph ? IM_COL32(255, 255, 255, 100) : IM_COL32(255, 255, 255, 50));
|
|
if (glyph)
|
|
font->RenderChar(draw_list, cell_size, cell_p1, glyph_col, (ImWchar)(base + n));
|
|
if (glyph && IsMouseHoveringRect(cell_p1, cell_p2))
|
|
{
|
|
BeginTooltip();
|
|
Text("Codepoint: U+%04X", base + n);
|
|
Separator();
|
|
Text("Visible: %d", glyph->Visible);
|
|
Text("AdvanceX: %.1f", glyph->AdvanceX);
|
|
Text("Pos: (%.2f,%.2f)->(%.2f,%.2f)", glyph->X0, glyph->Y0, glyph->X1, glyph->Y1);
|
|
Text("UV: (%.3f,%.3f)->(%.3f,%.3f)", glyph->U0, glyph->V0, glyph->U1, glyph->V1);
|
|
EndTooltip();
|
|
}
|
|
}
|
|
Dummy(ImVec2((cell_size + cell_spacing) * 16, (cell_size + cell_spacing) * 16));
|
|
TreePop();
|
|
}
|
|
TreePop();
|
|
}
|
|
TreePop();
|
|
}
|
|
|
|
// [DEBUG] Display contents of ImGuiStorage
|
|
void ImGui::DebugNodeStorage(ImGuiStorage* storage, const char* label)
|
|
{
|
|
if (!TreeNode(label, "%s: %d entries, %d bytes", label, storage->Data.Size, storage->Data.size_in_bytes()))
|
|
return;
|
|
for (int n = 0; n < storage->Data.Size; n++)
|
|
{
|
|
const ImGuiStorage::ImGuiStoragePair& p = storage->Data[n];
|
|
BulletText("Key 0x%08X Value { i: %d }", p.key,
|
|
p.val_i); // Important: we currently don't store a type, real value may not be integer.
|
|
}
|
|
TreePop();
|
|
}
|
|
|
|
// [DEBUG] Display contents of ImGuiTabBar
|
|
void ImGui::DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label)
|
|
{
|
|
// Standalone tab bars (not associated to docking/windows functionality) currently hold no discernible strings.
|
|
char buf[256];
|
|
char* p = buf;
|
|
const char* buf_end = buf + IM_ARRAYSIZE(buf);
|
|
const bool is_active = (tab_bar->PrevFrameVisible >= GetFrameCount() - 2);
|
|
p += ImFormatString(p, buf_end - p, "%s 0x%08X (%d tabs)%s", label, tab_bar->ID, tab_bar->Tabs.Size,
|
|
is_active ? "" : " *Inactive*");
|
|
p += ImFormatString(p, buf_end - p, " { ");
|
|
for (int tab_n = 0; tab_n < ImMin(tab_bar->Tabs.Size, 3); tab_n++)
|
|
{
|
|
ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
|
|
p += ImFormatString(p, buf_end - p, "%s'%s'", tab_n > 0 ? ", " : "",
|
|
(tab->Window || tab->NameOffset != -1) ? tab_bar->GetTabName(tab) : "???");
|
|
}
|
|
p += ImFormatString(p, buf_end - p, (tab_bar->Tabs.Size > 3) ? " ... }" : " } ");
|
|
if (!is_active)
|
|
{
|
|
PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled));
|
|
}
|
|
bool open = TreeNode(label, "%s", buf);
|
|
if (!is_active)
|
|
{
|
|
PopStyleColor();
|
|
}
|
|
if (is_active && IsItemHovered())
|
|
{
|
|
ImDrawList* draw_list = GetForegroundDrawList();
|
|
draw_list->AddRect(tab_bar->BarRect.Min, tab_bar->BarRect.Max, IM_COL32(255, 255, 0, 255));
|
|
draw_list->AddLine(ImVec2(tab_bar->ScrollingRectMinX, tab_bar->BarRect.Min.y),
|
|
ImVec2(tab_bar->ScrollingRectMinX, tab_bar->BarRect.Max.y), IM_COL32(0, 255, 0, 255));
|
|
draw_list->AddLine(ImVec2(tab_bar->ScrollingRectMaxX, tab_bar->BarRect.Min.y),
|
|
ImVec2(tab_bar->ScrollingRectMaxX, tab_bar->BarRect.Max.y), IM_COL32(0, 255, 0, 255));
|
|
}
|
|
if (open)
|
|
{
|
|
for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
|
|
{
|
|
const ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
|
|
PushID(tab);
|
|
if (SmallButton("<"))
|
|
{
|
|
TabBarQueueReorder(tab_bar, tab, -1);
|
|
}
|
|
SameLine(0, 2);
|
|
if (SmallButton(">"))
|
|
{
|
|
TabBarQueueReorder(tab_bar, tab, +1);
|
|
}
|
|
SameLine();
|
|
Text("%02d%c Tab 0x%08X '%s' Offset: %.1f, Width: %.1f/%.1f", tab_n,
|
|
(tab->ID == tab_bar->SelectedTabId) ? '*' : ' ', tab->ID,
|
|
(tab->Window || tab->NameOffset != -1) ? tab_bar->GetTabName(tab) : "???", tab->Offset, tab->Width,
|
|
tab->ContentWidth);
|
|
PopID();
|
|
}
|
|
TreePop();
|
|
}
|
|
}
|
|
|
|
void ImGui::DebugNodeViewport(ImGuiViewportP* viewport)
|
|
{
|
|
SetNextItemOpen(true, ImGuiCond_Once);
|
|
if (TreeNode((void*)(intptr_t)viewport->ID, "Viewport #%d, ID: 0x%08X, Parent: 0x%08X, Window: \"%s\"",
|
|
viewport->Idx, viewport->ID, viewport->ParentViewportId,
|
|
viewport->Window ? viewport->Window->Name : "N/A"))
|
|
{
|
|
ImGuiWindowFlags flags = viewport->Flags;
|
|
BulletText(
|
|
"Main Pos: (%.0f,%.0f), Size: (%.0f,%.0f)\nWorkArea Offset Left: %.0f Top: %.0f, Right: %.0f, Bottom: "
|
|
"%.0f\nMonitor: %d, DpiScale: %.0f%%",
|
|
viewport->Pos.x, viewport->Pos.y, viewport->Size.x, viewport->Size.y, viewport->WorkOffsetMin.x,
|
|
viewport->WorkOffsetMin.y, viewport->WorkOffsetMax.x, viewport->WorkOffsetMax.y, viewport->PlatformMonitor,
|
|
viewport->DpiScale * 100.0f);
|
|
if (viewport->Idx > 0)
|
|
{
|
|
SameLine();
|
|
if (SmallButton("Reset Pos"))
|
|
{
|
|
viewport->Pos = ImVec2(200, 200);
|
|
viewport->UpdateWorkRect();
|
|
if (viewport->Window)
|
|
viewport->Window->Pos = viewport->Pos;
|
|
}
|
|
}
|
|
BulletText("Flags: 0x%04X =%s%s%s%s%s%s%s%s%s%s%s%s", viewport->Flags,
|
|
//(flags & ImGuiViewportFlags_IsPlatformWindow) ? " IsPlatformWindow" : "", // Omitting because it is
|
|
// the standard
|
|
(flags & ImGuiViewportFlags_IsPlatformMonitor) ? " IsPlatformMonitor" : "",
|
|
(flags & ImGuiViewportFlags_OwnedByApp) ? " OwnedByApp" : "",
|
|
(flags & ImGuiViewportFlags_NoDecoration) ? " NoDecoration" : "",
|
|
(flags & ImGuiViewportFlags_NoTaskBarIcon) ? " NoTaskBarIcon" : "",
|
|
(flags & ImGuiViewportFlags_NoFocusOnAppearing) ? " NoFocusOnAppearing" : "",
|
|
(flags & ImGuiViewportFlags_NoFocusOnClick) ? " NoFocusOnClick" : "",
|
|
(flags & ImGuiViewportFlags_NoInputs) ? " NoInputs" : "",
|
|
(flags & ImGuiViewportFlags_NoRendererClear) ? " NoRendererClear" : "",
|
|
(flags & ImGuiViewportFlags_TopMost) ? " TopMost" : "",
|
|
(flags & ImGuiViewportFlags_Minimized) ? " Minimized" : "",
|
|
(flags & ImGuiViewportFlags_NoAutoMerge) ? " NoAutoMerge" : "",
|
|
(flags & ImGuiViewportFlags_CanHostOtherWindows) ? " CanHostOtherWindows" : "");
|
|
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++)
|
|
DebugNodeDrawList(NULL, viewport, viewport->DrawDataBuilder.Layers[layer_i][draw_list_i], "DrawList");
|
|
TreePop();
|
|
}
|
|
}
|
|
|
|
void ImGui::DebugNodeWindow(ImGuiWindow* window, const char* label)
|
|
{
|
|
if (window == NULL)
|
|
{
|
|
BulletText("%s: NULL", label);
|
|
return;
|
|
}
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
const bool is_active = window->WasActive;
|
|
ImGuiTreeNodeFlags tree_node_flags =
|
|
(window == g.NavWindow) ? ImGuiTreeNodeFlags_Selected : ImGuiTreeNodeFlags_None;
|
|
if (!is_active)
|
|
{
|
|
PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled));
|
|
}
|
|
const bool open =
|
|
TreeNodeEx(label, tree_node_flags, "%s '%s'%s", label, window->Name, is_active ? "" : " *Inactive*");
|
|
if (!is_active)
|
|
{
|
|
PopStyleColor();
|
|
}
|
|
if (IsItemHovered() && is_active)
|
|
GetForegroundDrawList(window)->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 255, 0, 255));
|
|
if (!open)
|
|
return;
|
|
|
|
if (window->MemoryCompacted)
|
|
TextDisabled("Note: some memory buffers have been compacted/freed.");
|
|
|
|
ImGuiWindowFlags flags = window->Flags;
|
|
DebugNodeDrawList(window, window->Viewport, window->DrawList, "DrawList");
|
|
BulletText("Pos: (%.1f,%.1f), Size: (%.1f,%.1f), ContentSize (%.1f,%.1f) Ideal (%.1f,%.1f)", window->Pos.x,
|
|
window->Pos.y, window->Size.x, window->Size.y, window->ContentSize.x, window->ContentSize.y,
|
|
window->ContentSizeIdeal.x, window->ContentSizeIdeal.y);
|
|
BulletText("Flags: 0x%08X (%s%s%s%s%s%s%s%s%s..)", flags, (flags & ImGuiWindowFlags_ChildWindow) ? "Child " : "",
|
|
(flags & ImGuiWindowFlags_Tooltip) ? "Tooltip " : "", (flags & ImGuiWindowFlags_Popup) ? "Popup " : "",
|
|
(flags & ImGuiWindowFlags_Modal) ? "Modal " : "",
|
|
(flags & ImGuiWindowFlags_ChildMenu) ? "ChildMenu " : "",
|
|
(flags & ImGuiWindowFlags_NoSavedSettings) ? "NoSavedSettings " : "",
|
|
(flags & ImGuiWindowFlags_NoMouseInputs) ? "NoMouseInputs" : "",
|
|
(flags & ImGuiWindowFlags_NoNavInputs) ? "NoNavInputs" : "",
|
|
(flags & ImGuiWindowFlags_AlwaysAutoResize) ? "AlwaysAutoResize" : "");
|
|
BulletText("WindowClassId: 0x%08X", window->WindowClass.ClassId);
|
|
BulletText("Scroll: (%.2f/%.2f,%.2f/%.2f) Scrollbar:%s%s", window->Scroll.x, window->ScrollMax.x, window->Scroll.y,
|
|
window->ScrollMax.y, window->ScrollbarX ? "X" : "", window->ScrollbarY ? "Y" : "");
|
|
BulletText("Active: %d/%d, WriteAccessed: %d, BeginOrderWithinContext: %d", window->Active, window->WasActive,
|
|
window->WriteAccessed, (window->Active || window->WasActive) ? window->BeginOrderWithinContext : -1);
|
|
BulletText("Appearing: %d, Hidden: %d (CanSkip %d Cannot %d), SkipItems: %d", window->Appearing, window->Hidden,
|
|
window->HiddenFramesCanSkipItems, window->HiddenFramesCannotSkipItems, window->SkipItems);
|
|
for (int layer = 0; layer < ImGuiNavLayer_COUNT; layer++)
|
|
{
|
|
ImRect r = window->NavRectRel[layer];
|
|
if (r.Min.x >= r.Max.y && r.Min.y >= r.Max.y)
|
|
{
|
|
BulletText("NavLastIds[%d]: 0x%08X", layer, window->NavLastIds[layer]);
|
|
continue;
|
|
}
|
|
BulletText("NavLastIds[%d]: 0x%08X at +(%.1f,%.1f)(%.1f,%.1f)", layer, window->NavLastIds[layer], r.Min.x,
|
|
r.Min.y, r.Max.x, r.Max.y);
|
|
if (IsItemHovered())
|
|
GetForegroundDrawList(window)->AddRect(r.Min + window->Pos, r.Max + window->Pos,
|
|
IM_COL32(255, 255, 0, 255));
|
|
}
|
|
BulletText("NavLayersActiveMask: %X, NavLastChildNavWindow: %s", window->DC.NavLayersActiveMask,
|
|
window->NavLastChildNavWindow ? window->NavLastChildNavWindow->Name : "NULL");
|
|
|
|
BulletText("Viewport: %d%s, ViewportId: 0x%08X, ViewportPos: (%.1f,%.1f)",
|
|
window->Viewport ? window->Viewport->Idx : -1, window->ViewportOwned ? " (Owned)" : "",
|
|
window->ViewportId, window->ViewportPos.x, window->ViewportPos.y);
|
|
BulletText("ViewportMonitor: %d", window->Viewport ? window->Viewport->PlatformMonitor : -1);
|
|
BulletText("DockId: 0x%04X, DockOrder: %d, Act: %d, Vis: %d", window->DockId, window->DockOrder,
|
|
window->DockIsActive, window->DockTabIsVisible);
|
|
if (window->DockNode || window->DockNodeAsHost)
|
|
DebugNodeDockNode(window->DockNodeAsHost ? window->DockNodeAsHost : window->DockNode,
|
|
window->DockNodeAsHost ? "DockNodeAsHost" : "DockNode");
|
|
|
|
if (window->RootWindow != window)
|
|
{
|
|
DebugNodeWindow(window->RootWindow, "RootWindow");
|
|
}
|
|
if (window->RootWindowDockTree != window->RootWindow)
|
|
{
|
|
DebugNodeWindow(window->RootWindowDockTree, "RootWindowDockTree");
|
|
}
|
|
if (window->ParentWindow != NULL)
|
|
{
|
|
DebugNodeWindow(window->ParentWindow, "ParentWindow");
|
|
}
|
|
if (window->DC.ChildWindows.Size > 0)
|
|
{
|
|
DebugNodeWindowsList(&window->DC.ChildWindows, "ChildWindows");
|
|
}
|
|
if (window->ColumnsStorage.Size > 0 && TreeNode("Columns", "Columns sets (%d)", window->ColumnsStorage.Size))
|
|
{
|
|
for (int n = 0; n < window->ColumnsStorage.Size; n++)
|
|
DebugNodeColumns(&window->ColumnsStorage[n]);
|
|
TreePop();
|
|
}
|
|
DebugNodeStorage(&window->StateStorage, "Storage");
|
|
TreePop();
|
|
}
|
|
|
|
void ImGui::DebugNodeWindowSettings(ImGuiWindowSettings* settings)
|
|
{
|
|
Text("0x%08X \"%s\" Pos (%d,%d) Size (%d,%d) Collapsed=%d", settings->ID, settings->GetName(), settings->Pos.x,
|
|
settings->Pos.y, settings->Size.x, settings->Size.y, settings->Collapsed);
|
|
}
|
|
|
|
void ImGui::DebugNodeWindowsList(ImVector<ImGuiWindow*>* windows, const char* label)
|
|
{
|
|
if (!TreeNode(label, "%s (%d)", label, windows->Size))
|
|
return;
|
|
Text("(In front-to-back order:)");
|
|
for (int i = windows->Size - 1; i >= 0; i--) // Iterate front to back
|
|
{
|
|
PushID((*windows)[i]);
|
|
DebugNodeWindow((*windows)[i], "Window");
|
|
PopID();
|
|
}
|
|
TreePop();
|
|
}
|
|
|
|
# else
|
|
|
|
void ImGui::ShowMetricsWindow(bool*) {}
|
|
void ImGui::ShowFontAtlas(ImFontAtlas*) {}
|
|
void ImGui::DebugNodeColumns(ImGuiOldColumns*) {}
|
|
void ImGui::DebugNodeDrawList(ImGuiWindow*, ImGuiViewportP*, const ImDrawList*, const char*) {}
|
|
void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList*, const ImDrawList*, const ImDrawCmd*, bool, bool) {}
|
|
void ImGui::DebugNodeFont(ImFont*) {}
|
|
void ImGui::DebugNodeStorage(ImGuiStorage*, const char*) {}
|
|
void ImGui::DebugNodeTabBar(ImGuiTabBar*, const char*) {}
|
|
void ImGui::DebugNodeWindow(ImGuiWindow*, const char*) {}
|
|
void ImGui::DebugNodeWindowSettings(ImGuiWindowSettings*) {}
|
|
void ImGui::DebugNodeWindowsList(ImVector<ImGuiWindow*>*, const char*) {}
|
|
void ImGui::DebugNodeViewport(ImGuiViewportP*) {}
|
|
|
|
# endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// Include imgui_user.inl at the end of imgui.cpp to access private data/functions that aren't exposed.
|
|
// Prefer just including imgui_internal.h from your code rather than using this define. If a declaration is missing from
|
|
// imgui_internal.h add it or request it on the github.
|
|
# ifdef IMGUI_INCLUDE_IMGUI_USER_INL
|
|
# include "imgui_user.inl"
|
|
# endif
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#endif // #ifndef IMGUI_DISABLE
|