1// dear imgui, v1.89.2 WIP
2// (main code and documentation)
3
4// Help:
5// - Read FAQ at http://dearimgui.org/faq
6// - Newcomers, read 'Programmer guide' below for notes on how to setup Dear ImGui in your codebase.
7// - Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp. All applications in examples/ are doing that.
8// Read imgui.cpp for details, links and comments.
9
10// Resources:
11// - FAQ http://dearimgui.org/faq
12// - Homepage & latest https://github.com/ocornut/imgui
13// - Releases & changelog https://github.com/ocornut/imgui/releases
14// - Gallery https://github.com/ocornut/imgui/issues/5243 (please post your screenshots/video there!)
15// - Wiki https://github.com/ocornut/imgui/wiki (lots of good stuff there)
16// - Glossary https://github.com/ocornut/imgui/wiki/Glossary
17// - Issues & support https://github.com/ocornut/imgui/issues
18
19// Getting Started?
20// - For first-time users having issues compiling/linking/running or issues loading fonts:
21// please post in https://github.com/ocornut/imgui/discussions if you cannot find a solution in resources above.
22
23// Developed by Omar Cornut and every direct or indirect contributors to the GitHub.
24// See LICENSE.txt for copyright and licensing details (standard MIT License).
25// This library is free but needs your support to sustain development and maintenance.
26// Businesses: you can support continued development via invoiced technical support, maintenance and sponsoring contracts. Please reach out to "contact AT dearimgui.com".
27// Individuals: you can support continued development via donations. See docs/README or web page.
28
29// It is recommended that you don't modify imgui.cpp! It will become difficult for you to update the library.
30// Note that 'ImGui::' being a namespace, you can add functions into the namespace from your own source files, without
31// modifying imgui.h or imgui.cpp. You may include imgui_internal.h to access internal data structures, but it doesn't
32// come with any guarantee of forward compatibility. Discussing your changes on the GitHub Issue Tracker may lead you
33// to a better solution or official support for them.
34
35/*
36
37Index of this file:
38
39DOCUMENTATION
40
41- MISSION STATEMENT
42- END-USER GUIDE
43- PROGRAMMER GUIDE
44 - READ FIRST
45 - HOW TO UPDATE TO A NEWER VERSION OF DEAR IMGUI
46 - GETTING STARTED WITH INTEGRATING DEAR IMGUI IN YOUR CODE/ENGINE
47 - HOW A SIMPLE APPLICATION MAY LOOK LIKE
48 - HOW A SIMPLE RENDERING FUNCTION MAY LOOK LIKE
49 - USING GAMEPAD/KEYBOARD NAVIGATION CONTROLS
50- API BREAKING CHANGES (read me when you update!)
51- FREQUENTLY ASKED QUESTIONS (FAQ)
52 - Read all answers online: https://www.dearimgui.org/faq, or in docs/FAQ.md (with a Markdown viewer)
53
54CODE
55(search for "[SECTION]" in the code to find them)
56
57// [SECTION] INCLUDES
58// [SECTION] FORWARD DECLARATIONS
59// [SECTION] CONTEXT AND MEMORY ALLOCATORS
60// [SECTION] USER FACING STRUCTURES (ImGuiStyle, ImGuiIO)
61// [SECTION] MISC HELPERS/UTILITIES (Geometry functions)
62// [SECTION] MISC HELPERS/UTILITIES (String, Format, Hash functions)
63// [SECTION] MISC HELPERS/UTILITIES (File functions)
64// [SECTION] MISC HELPERS/UTILITIES (ImText* functions)
65// [SECTION] MISC HELPERS/UTILITIES (Color functions)
66// [SECTION] ImGuiStorage
67// [SECTION] ImGuiTextFilter
68// [SECTION] ImGuiTextBuffer, ImGuiTextIndex
69// [SECTION] ImGuiListClipper
70// [SECTION] STYLING
71// [SECTION] RENDER HELPERS
72// [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!)
73// [SECTION] INPUTS
74// [SECTION] ERROR CHECKING
75// [SECTION] LAYOUT
76// [SECTION] SCROLLING
77// [SECTION] TOOLTIPS
78// [SECTION] POPUPS
79// [SECTION] KEYBOARD/GAMEPAD NAVIGATION
80// [SECTION] DRAG AND DROP
81// [SECTION] LOGGING/CAPTURING
82// [SECTION] SETTINGS
83// [SECTION] LOCALIZATION
84// [SECTION] VIEWPORTS, PLATFORM WINDOWS
85// [SECTION] DOCKING
86// [SECTION] PLATFORM DEPENDENT HELPERS
87// [SECTION] METRICS/DEBUGGER WINDOW
88// [SECTION] DEBUG LOG WINDOW
89// [SECTION] OTHER DEBUG TOOLS (ITEM PICKER, STACK TOOL)
90
91*/
92
93//-----------------------------------------------------------------------------
94// DOCUMENTATION
95//-----------------------------------------------------------------------------
96
97/*
98
99 MISSION STATEMENT
100 =================
101
102 - Easy to use to create code-driven and data-driven tools.
103 - Easy to use to create ad hoc short-lived tools and long-lived, more elaborate tools.
104 - Easy to hack and improve.
105 - Minimize setup and maintenance.
106 - Minimize state storage on user side.
107 - Minimize state synchronization.
108 - Portable, minimize dependencies, run on target (consoles, phones, etc.).
109 - Efficient runtime and memory consumption.
110
111 Designed for developers and content-creators, not the typical end-user! Some of the current weaknesses includes:
112
113 - Doesn't look fancy, doesn't animate.
114 - Limited layout features, intricate layouts are typically crafted in code.
115
116
117 END-USER GUIDE
118 ==============
119
120 - Double-click on title bar to collapse window.
121 - Click upper right corner to close a window, available when 'bool* p_open' is passed to ImGui::Begin().
122 - Click and drag on lower right corner to resize window (double-click to auto fit window to its contents).
123 - Click and drag on any empty space to move window.
124 - TAB/SHIFT+TAB to cycle through keyboard editable fields.
125 - CTRL+Click on a slider or drag box to input value as text.
126 - Use mouse wheel to scroll.
127 - Text editor:
128 - Hold SHIFT or use mouse to select text.
129 - CTRL+Left/Right to word jump.
130 - CTRL+Shift+Left/Right to select words.
131 - CTRL+A or Double-Click to select all.
132 - CTRL+X,CTRL+C,CTRL+V to use OS clipboard/
133 - CTRL+Z,CTRL+Y to undo/redo.
134 - ESCAPE to revert text to its original value.
135 - Controls are automatically adjusted for OSX to match standard OSX text editing operations.
136 - General Keyboard controls: enable with ImGuiConfigFlags_NavEnableKeyboard.
137 - General Gamepad controls: enable with ImGuiConfigFlags_NavEnableGamepad. Download controller mapping PNG/PSD at http://dearimgui.org/controls_sheets
138
139
140 PROGRAMMER GUIDE
141 ================
142
143 READ FIRST
144 ----------
145 - Remember to check the wonderful Wiki (https://github.com/ocornut/imgui/wiki)
146 - 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
147 destruction steps, less superfluous data retention on your side, less state duplication, less state synchronization, fewer bugs.
148 - Call and read ImGui::ShowDemoWindow() for demo code demonstrating most features.
149 - The library is designed to be built from sources. Avoid pre-compiled binaries and packaged versions. See imconfig.h to configure your build.
150 - Dear ImGui is an implementation of the IMGUI paradigm (immediate-mode graphical user interface, a term coined by Casey Muratori).
151 You can learn about IMGUI principles at http://www.johno.se/book/imgui.html, http://mollyrocket.com/861 & more links in Wiki.
152 - Dear ImGui is a "single pass" rasterizing implementation of the IMGUI paradigm, aimed at ease of use and high-performances.
153 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,
154 where the UI code is called multiple times ("multiple passes") from a single entry point. There are pros and cons to both approaches.
155 - Our origin is on the top-left. In axis aligned bounding boxes, Min = top-left, Max = bottom-right.
156 - This codebase is also optimized to yield decent performances with typical "Debug" builds settings.
157 - Please make sure you have asserts enabled (IM_ASSERT redirects to assert() by default, but can be redirected).
158 If you get an assert, read the messages and comments around the assert.
159 - 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.
160 - C++: ImVec2/ImVec4 do not expose math operators by default, because it is expected that you use your own math types.
161 See FAQ "How can I use my own math types instead of ImVec2/ImVec4?" for details about setting up imconfig.h for that.
162 However, imgui_internal.h can optionally export math operators for ImVec2/ImVec4, which we use in this codebase.
163 - C++: pay attention that ImVector<> manipulates plain-old-data and does not honor construction/destruction (avoid using it in your code!).
164
165
166 HOW TO UPDATE TO A NEWER VERSION OF DEAR IMGUI
167 ----------------------------------------------
168 - Overwrite all the sources files except for imconfig.h (if you have modified your copy of imconfig.h)
169 - Or maintain your own branch where you have imconfig.h modified as a top-most commit which you can regularly rebase over "master".
170 - You can also use '#define IMGUI_USER_CONFIG "my_config_file.h" to redirect configuration to your own file.
171 - Read the "API BREAKING CHANGES" section (below). This is where we list occasional API breaking changes.
172 If a function/type has been renamed / or marked obsolete, try to fix the name in your code before it is permanently removed
173 from the public API. If you have a problem with a missing function/symbols, search for its name in the code, there will
174 likely be a comment about it. Please report any issue to the GitHub page!
175 - To find out usage of old API, you can add '#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS' in your configuration file.
176 - Try to keep your copy of Dear ImGui reasonably up to date.
177
178
179 GETTING STARTED WITH INTEGRATING DEAR IMGUI IN YOUR CODE/ENGINE
180 ---------------------------------------------------------------
181 - Run and study the examples and demo in imgui_demo.cpp to get acquainted with the library.
182 - In the majority of cases you should be able to use unmodified backends files available in the backends/ folder.
183 - Add the Dear ImGui source files + selected backend source files to your projects or using your preferred build system.
184 It is recommended you build and statically link the .cpp files as part of your project and NOT as a shared library (DLL).
185 - 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.
186 - When using Dear ImGui, your programming IDE is your friend: follow the declaration of variables, functions and types to find comments about them.
187 - Dear ImGui never touches or knows about your GPU state. The only function that knows about GPU is the draw function that you provide.
188 Effectively it means you can create widgets at any time in your code, regardless of considerations of being in "update" vs "render"
189 phases of your own application. All rendering information is stored into command-lists that you will retrieve after calling ImGui::Render().
190 - Refer to the backends and demo applications in the examples/ folder for instruction on how to setup your code.
191 - 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.
192
193
194 HOW A SIMPLE APPLICATION MAY LOOK LIKE
195 --------------------------------------
196 EXHIBIT 1: USING THE EXAMPLE BACKENDS (= imgui_impl_XXX.cpp files from the backends/ folder).
197 The sub-folders in examples/ contain examples applications following this structure.
198
199 // Application init: create a dear imgui context, setup some options, load fonts
200 ImGui::CreateContext();
201 ImGuiIO& io = ImGui::GetIO();
202 // TODO: Set optional io.ConfigFlags values, e.g. 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard' to enable keyboard controls.
203 // TODO: Fill optional fields of the io structure later.
204 // TODO: Load TTF/OTF fonts if you don't want to use the default font.
205
206 // Initialize helper Platform and Renderer backends (here we are using imgui_impl_win32.cpp and imgui_impl_dx11.cpp)
207 ImGui_ImplWin32_Init(hwnd);
208 ImGui_ImplDX11_Init(g_pd3dDevice, g_pd3dDeviceContext);
209
210 // Application main loop
211 while (true)
212 {
213 // Feed inputs to dear imgui, start new frame
214 ImGui_ImplDX11_NewFrame();
215 ImGui_ImplWin32_NewFrame();
216 ImGui::NewFrame();
217
218 // Any application code here
219 ImGui::Text("Hello, world!");
220
221 // Render dear imgui into screen
222 ImGui::Render();
223 ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
224 g_pSwapChain->Present(1, 0);
225 }
226
227 // Shutdown
228 ImGui_ImplDX11_Shutdown();
229 ImGui_ImplWin32_Shutdown();
230 ImGui::DestroyContext();
231
232 EXHIBIT 2: IMPLEMENTING CUSTOM BACKEND / CUSTOM ENGINE
233
234 // Application init: create a dear imgui context, setup some options, load fonts
235 ImGui::CreateContext();
236 ImGuiIO& io = ImGui::GetIO();
237 // TODO: Set optional io.ConfigFlags values, e.g. 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard' to enable keyboard controls.
238 // TODO: Fill optional fields of the io structure later.
239 // TODO: Load TTF/OTF fonts if you don't want to use the default font.
240
241 // Build and load the texture atlas into a texture
242 // (In the examples/ app this is usually done within the ImGui_ImplXXX_Init() function from one of the demo Renderer)
243 int width, height;
244 unsigned char* pixels = NULL;
245 io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
246
247 // At this point you've got the texture data and you need to upload that to your graphic system:
248 // After we have created the texture, store its pointer/identifier (_in whichever format your engine uses_) in 'io.Fonts->TexID'.
249 // This will be passed back to your via the renderer. Basically ImTextureID == void*. Read FAQ for details about ImTextureID.
250 MyTexture* texture = MyEngine::CreateTextureFromMemoryPixels(pixels, width, height, TEXTURE_TYPE_RGBA32)
251 io.Fonts->SetTexID((void*)texture);
252
253 // Application main loop
254 while (true)
255 {
256 // Setup low-level inputs, e.g. on Win32: calling GetKeyboardState(), or write to those fields from your Windows message handlers, etc.
257 // (In the examples/ app this is usually done within the ImGui_ImplXXX_NewFrame() function from one of the demo Platform Backends)
258 io.DeltaTime = 1.0f/60.0f; // set the time elapsed since the previous frame (in seconds)
259 io.DisplaySize.x = 1920.0f; // set the current display width
260 io.DisplaySize.y = 1280.0f; // set the current display height here
261 io.AddMousePosEvent(mouse_x, mouse_y); // update mouse position
262 io.AddMouseButtonEvent(0, mouse_b[0]); // update mouse button states
263 io.AddMouseButtonEvent(1, mouse_b[1]); // update mouse button states
264
265 // Call NewFrame(), after this point you can use ImGui::* functions anytime
266 // (So you want to try calling NewFrame() as early as you can in your main loop to be able to use Dear ImGui everywhere)
267 ImGui::NewFrame();
268
269 // Most of your application code here
270 ImGui::Text("Hello, world!");
271 MyGameUpdate(); // may use any Dear ImGui functions, e.g. ImGui::Begin("My window"); ImGui::Text("Hello, world!"); ImGui::End();
272 MyGameRender(); // may use any Dear ImGui functions as well!
273
274 // Render dear imgui, swap buffers
275 // (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)
276 ImGui::EndFrame();
277 ImGui::Render();
278 ImDrawData* draw_data = ImGui::GetDrawData();
279 MyImGuiRenderFunction(draw_data);
280 SwapBuffers();
281 }
282
283 // Shutdown
284 ImGui::DestroyContext();
285
286 To decide whether to dispatch mouse/keyboard inputs to Dear ImGui to the rest of your application,
287 you should read the 'io.WantCaptureMouse', 'io.WantCaptureKeyboard' and 'io.WantTextInput' flags!
288 Please read the FAQ and example applications for details about this!
289
290
291 HOW A SIMPLE RENDERING FUNCTION MAY LOOK LIKE
292 ---------------------------------------------
293 The backends in impl_impl_XXX.cpp files contain many working implementations of a rendering function.
294
295 void MyImGuiRenderFunction(ImDrawData* draw_data)
296 {
297 // TODO: Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled
298 // TODO: Setup texture sampling state: sample with bilinear filtering (NOT point/nearest filtering). Use 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines;' to allow point/nearest filtering.
299 // TODO: Setup viewport covering draw_data->DisplayPos to draw_data->DisplayPos + draw_data->DisplaySize
300 // TODO: Setup orthographic projection matrix cover draw_data->DisplayPos to draw_data->DisplayPos + draw_data->DisplaySize
301 // TODO: Setup shader: vertex { float2 pos, float2 uv, u32 color }, fragment shader sample color from 1 texture, multiply by vertex color.
302 ImVec2 clip_off = draw_data->DisplayPos;
303 for (int n = 0; n < draw_data->CmdListsCount; n++)
304 {
305 const ImDrawList* cmd_list = draw_data->CmdLists[n];
306 const ImDrawVert* vtx_buffer = cmd_list->VtxBuffer.Data; // vertex buffer generated by Dear ImGui
307 const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data; // index buffer generated by Dear ImGui
308 for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
309 {
310 const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
311 if (pcmd->UserCallback)
312 {
313 pcmd->UserCallback(cmd_list, pcmd);
314 }
315 else
316 {
317 // Project scissor/clipping rectangles into framebuffer space
318 ImVec2 clip_min(pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y);
319 ImVec2 clip_max(pcmd->ClipRect.z - clip_off.x, pcmd->ClipRect.w - clip_off.y);
320 if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
321 continue;
322
323 // We are using scissoring to clip some objects. All low-level graphics API should support it.
324 // - If your engine doesn't support scissoring yet, you may ignore this at first. You will get some small glitches
325 // (some elements visible outside their bounds) but you can fix that once everything else works!
326 // - Clipping coordinates are provided in imgui coordinates space:
327 // - For a given viewport, draw_data->DisplayPos == viewport->Pos and draw_data->DisplaySize == viewport->Size
328 // - 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.
329 // - In the interest of supporting multi-viewport applications (see 'docking' branch on github),
330 // always subtract draw_data->DisplayPos from clipping bounds to convert them to your viewport space.
331 // - Note that pcmd->ClipRect contains Min+Max bounds. Some graphics API may use Min+Max, other may use Min+Size (size being Max-Min)
332 MyEngineSetScissor(clip_min.x, clip_min.y, clip_max.x, clip_max.y);
333
334 // The texture for the draw call is specified by pcmd->GetTexID().
335 // The vast majority of draw calls will use the Dear ImGui texture atlas, which value you have set yourself during initialization.
336 MyEngineBindTexture((MyTexture*)pcmd->GetTexID());
337
338 // Render 'pcmd->ElemCount/3' indexed triangles.
339 // 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.
340 MyEngineDrawIndexedTriangles(pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer + pcmd->IdxOffset, vtx_buffer, pcmd->VtxOffset);
341 }
342 }
343 }
344 }
345
346
347 USING GAMEPAD/KEYBOARD NAVIGATION CONTROLS
348 ------------------------------------------
349 - The gamepad/keyboard navigation is fairly functional and keeps being improved.
350 - Gamepad support is particularly useful to use Dear ImGui on a console system (e.g. PlayStation, Switch, Xbox) without a mouse!
351 - The initial focus was to support game controllers, but keyboard is becoming increasingly and decently usable.
352 - Keyboard:
353 - Application: Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard to enable.
354 - When keyboard navigation is active (io.NavActive + ImGuiConfigFlags_NavEnableKeyboard),
355 the io.WantCaptureKeyboard flag will be set. For more advanced uses, you may want to read from:
356 - io.NavActive: true when a window is focused and it doesn't have the ImGuiWindowFlags_NoNavInputs flag set.
357 - io.NavVisible: true when the navigation cursor is visible (and usually goes false when mouse is used).
358 - or query focus information with e.g. IsWindowFocused(ImGuiFocusedFlags_AnyWindow), IsItemFocused() etc. functions.
359 Please reach out if you think the game vs navigation input sharing could be improved.
360 - Gamepad:
361 - Application: Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad to enable.
362 - Backend: Set io.BackendFlags |= ImGuiBackendFlags_HasGamepad + call io.AddKeyEvent/AddKeyAnalogEvent() with ImGuiKey_Gamepad_XXX keys.
363 For analog values (0.0f to 1.0f), backend is responsible to handling a dead-zone and rescaling inputs accordingly.
364 Backend 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.).
365 - BEFORE 1.87, BACKENDS USED TO WRITE TO io.NavInputs[]. This is now obsolete. Please call io functions instead!
366 - You can download PNG/PSD files depicting the gamepad controls for common controllers at: http://dearimgui.org/controls_sheets
367 - If you need to share inputs between your game and the Dear ImGui interface, the easiest approach is to go all-or-nothing,
368 with a buttons combo to toggle the target. Please reach out if you think the game vs navigation input sharing could be improved.
369 - Mouse:
370 - PS4/PS5 users: Consider emulating a mouse cursor with DualShock4 touch pad or a spare analog stick as a mouse-emulation fallback.
371 - 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.
372 - 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.
373 Enabling ImGuiConfigFlags_NavEnableSetMousePos + ImGuiBackendFlags_HasSetMousePos instructs dear imgui to move your mouse cursor along with navigation movements.
374 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.
375 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.
376 (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!)
377 (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
378 to set a boolean to ignore your other external mouse positions until the external source is moved again.)
379
380
381 API BREAKING CHANGES
382 ====================
383
384 Occasionally introducing changes that are breaking the API. We try to make the breakage minor and easy to fix.
385 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.
386 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.
387 You can read releases logs https://github.com/ocornut/imgui/releases for more details.
388
389(Docking/Viewport Branch)
390 - 2022/XX/XX (1.XX) - when multi-viewports are enabled, all positions will be in your natural OS coordinates space. It means that:
391 - reference to hard-coded positions such as in SetNextWindowPos(ImVec2(0,0)) are probably not what you want anymore.
392 you may use GetMainViewport()->Pos to offset hard-coded positions, e.g. SetNextWindowPos(GetMainViewport()->Pos)
393 - likewise io.MousePos and GetMousePos() will use OS coordinates.
394 If you query mouse positions to interact with non-imgui coordinates you will need to offset them, e.g. subtract GetWindowViewport()->Pos.
395
396 - 2022/10/26 (1.89) - commented out redirecting OpenPopupContextItem() which was briefly the name of OpenPopupOnItemClick() from 1.77 to 1.79.
397 - 2022/10/12 (1.89) - removed runtime patching of invalid "%f"/"%0.f" format strings for DragInt()/SliderInt(). This was obsoleted in 1.61 (May 2018). See 1.61 changelog for details.
398 - 2022/09/26 (1.89) - renamed and merged keyboard modifiers key enums and flags into a same set. Kept inline redirection enums (will obsolete).
399 - ImGuiKey_ModCtrl and ImGuiModFlags_Ctrl -> ImGuiMod_Ctrl
400 - ImGuiKey_ModShift and ImGuiModFlags_Shift -> ImGuiMod_Shift
401 - ImGuiKey_ModAlt and ImGuiModFlags_Alt -> ImGuiMod_Alt
402 - ImGuiKey_ModSuper and ImGuiModFlags_Super -> ImGuiMod_Super
403 the ImGuiKey_ModXXX were introduced in 1.87 and mostly used by backends.
404 the ImGuiModFlags_XXX have been exposed in imgui.h but not really used by any public api only by third-party extensions.
405 exceptionally commenting out the older ImGuiKeyModFlags_XXX names ahead of obsolescence schedule to reduce confusion and because they were not meant to be used anyway.
406 - 2022/09/20 (1.89) - ImGuiKey is now a typed enum, allowing ImGuiKey_XXX symbols to be named in debuggers.
407 this will require uses of legacy backend-dependent indices to be casted, e.g.
408 - with imgui_impl_glfw: IsKeyPressed(GLFW_KEY_A) -> IsKeyPressed((ImGuiKey)GLFW_KEY_A);
409 - with imgui_impl_win32: IsKeyPressed('A') -> IsKeyPressed((ImGuiKey)'A')
410 - etc. However if you are upgrading code you might well use the better, backend-agnostic IsKeyPressed(ImGuiKey_A) now!
411 - 2022/09/12 (1.89) - removed the bizarre legacy default argument for 'TreePush(const void* ptr = NULL)', always pass a pointer value explicitly. NULL/nullptr is ok but require cast, e.g. TreePush((void*)nullptr);
412 - 2022/09/05 (1.89) - commented out redirecting functions/enums names that were marked obsolete in 1.77 and 1.78 (June 2020):
413 - DragScalar(), DragScalarN(), DragFloat(), DragFloat2(), DragFloat3(), DragFloat4(): For old signatures ending with (..., const char* format, float power = 1.0f) -> use (..., format ImGuiSliderFlags_Logarithmic) if power != 1.0f.
414 - SliderScalar(), SliderScalarN(), SliderFloat(), SliderFloat2(), SliderFloat3(), SliderFloat4(): For old signatures ending with (..., const char* format, float power = 1.0f) -> use (..., format ImGuiSliderFlags_Logarithmic) if power != 1.0f.
415 - BeginPopupContextWindow(const char*, ImGuiMouseButton, bool) -> use BeginPopupContextWindow(const char*, ImGuiPopupFlags)
416 - 2022/09/02 (1.89) - obsoleted using SetCursorPos()/SetCursorScreenPos() to extend parent window/cell boundaries.
417 this relates to when moving the cursor position beyond current boundaries WITHOUT submitting an item.
418 - previously this would make the window content size ~200x200:
419 Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + End();
420 - instead, please submit an item:
421 Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + Dummy(ImVec2(0,0)) + End();
422 - alternative:
423 Begin(...) + Dummy(ImVec2(200,200)) + End();
424 - content size is now only extended when submitting an item!
425 - with '#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS' this will now be detected and assert.
426 - without '#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS' this will silently be fixed until we obsolete it.
427 - 2022/08/03 (1.89) - changed signature of ImageButton() function. Kept redirection function (will obsolete).
428 - added 'const char* str_id' parameter + removed 'int frame_padding = -1' parameter.
429 - old signature: bool ImageButton(ImTextureID tex_id, ImVec2 size, ImVec2 uv0 = ImVec2(0,0), ImVec2 uv1 = ImVec2(1,1), int frame_padding = -1, ImVec4 bg_col = ImVec4(0,0,0,0), ImVec4 tint_col = ImVec4(1,1,1,1));
430 - used the ImTextureID value to create an ID. This was inconsistent with other functions, led to ID conflicts, and caused problems with engines using transient ImTextureID values.
431 - had a FramePadding override which was inconsistent with other functions and made the already-long signature even longer.
432 - new signature: bool ImageButton(const char* str_id, ImTextureID tex_id, ImVec2 size, ImVec2 uv0 = ImVec2(0,0), ImVec2 uv1 = ImVec2(1,1), ImVec4 bg_col = ImVec4(0,0,0,0), ImVec4 tint_col = ImVec4(1,1,1,1));
433 - requires an explicit identifier. You may still use e.g. PushID() calls and then pass an empty identifier.
434 - always uses style.FramePadding for padding, to be consistent with other buttons. You may use PushStyleVar() to alter this.
435 - 2022/07/08 (1.89) - inputs: removed io.NavInputs[] and ImGuiNavInput enum (following 1.87 changes).
436 - Official backends from 1.87+ -> no issue.
437 - Official backends from 1.60 to 1.86 -> will build and convert gamepad inputs, unless IMGUI_DISABLE_OBSOLETE_KEYIO is defined. Need updating!
438 - Custom backends not writing to io.NavInputs[] -> no issue.
439 - Custom backends writing to io.NavInputs[] -> will build and convert gamepad inputs, unless IMGUI_DISABLE_OBSOLETE_KEYIO is defined. Need fixing!
440 - TL;DR: Backends should call io.AddKeyEvent()/io.AddKeyAnalogEvent() with ImGuiKey_GamepadXXX values instead of filling io.NavInput[].
441 - 2022/06/15 (1.88) - renamed IMGUI_DISABLE_METRICS_WINDOW to IMGUI_DISABLE_DEBUG_TOOLS for correctness. kept support for old define (will obsolete).
442 - 2022/05/03 (1.88) - backends: osx: removed ImGui_ImplOSX_HandleEvent() from backend API in favor of backend automatically handling event capture. All ImGui_ImplOSX_HandleEvent() calls should be removed as they are now unnecessary.
443 - 2022/04/05 (1.88) - inputs: renamed ImGuiKeyModFlags to ImGuiModFlags. Kept inline redirection enums (will obsolete). This was never used in public API functions but technically present in imgui.h and ImGuiIO.
444 - 2022/01/20 (1.87) - inputs: reworded gamepad IO.
445 - Backend writing to io.NavInputs[] -> backend should call io.AddKeyEvent()/io.AddKeyAnalogEvent() with ImGuiKey_GamepadXXX values.
446 - 2022/01/19 (1.87) - sliders, drags: removed support for legacy arithmetic operators (+,+-,*,/) when inputing text. This doesn't break any api/code but a feature that used to be accessible by end-users (which seemingly no one used).
447 - 2022/01/17 (1.87) - inputs: reworked mouse IO.
448 - Backend writing to io.MousePos -> backend should call io.AddMousePosEvent()
449 - Backend writing to io.MouseDown[] -> backend should call io.AddMouseButtonEvent()
450 - Backend writing to io.MouseWheel -> backend should call io.AddMouseWheelEvent()
451 - Backend writing to io.MouseHoveredViewport -> backend should call io.AddMouseViewportEvent() [Docking branch w/ multi-viewports only]
452 note: for all calls to IO new functions, the Dear ImGui context should be bound/current.
453 read https://github.com/ocornut/imgui/issues/4921 for details.
454 - 2022/01/10 (1.87) - inputs: reworked keyboard IO. Removed io.KeyMap[], io.KeysDown[] in favor of calling io.AddKeyEvent(). Removed GetKeyIndex(), now unecessary. All IsKeyXXX() functions now take ImGuiKey values. All features are still functional until IMGUI_DISABLE_OBSOLETE_KEYIO is defined. Read Changelog and Release Notes for details.
455 - IsKeyPressed(MY_NATIVE_KEY_XXX) -> use IsKeyPressed(ImGuiKey_XXX)
456 - IsKeyPressed(GetKeyIndex(ImGuiKey_XXX)) -> use IsKeyPressed(ImGuiKey_XXX)
457 - Backend writing to io.KeyMap[],io.KeysDown[] -> backend should call io.AddKeyEvent() (+ call io.SetKeyEventNativeData() if you want legacy user code to stil function with legacy key codes).
458 - Backend writing to io.KeyCtrl, io.KeyShift.. -> backend should call io.AddKeyEvent() with ImGuiMod_XXX values. *IF YOU PULLED CODE BETWEEN 2021/01/10 and 2021/01/27: We used to have a io.AddKeyModsEvent() function which was now replaced by io.AddKeyEvent() with ImGuiMod_XXX values.*
459 - one case won't work with backward compatibility: if your custom backend used ImGuiKey as mock native indices (e.g. "io.KeyMap[ImGuiKey_A] = ImGuiKey_A") because those values are now larger than the legacy KeyDown[] array. Will assert.
460 - inputs: added ImGuiKey_ModCtrl/ImGuiKey_ModShift/ImGuiKey_ModAlt/ImGuiKey_ModSuper values to submit keyboard modifiers using io.AddKeyEvent(), instead of writing directly to io.KeyCtrl, io.KeyShift, io.KeyAlt, io.KeySuper.
461 - 2022/01/05 (1.87) - inputs: renamed ImGuiKey_KeyPadEnter to ImGuiKey_KeypadEnter to align with new symbols. Kept redirection enum.
462 - 2022/01/05 (1.87) - removed io.ImeSetInputScreenPosFn() in favor of more flexible io.SetPlatformImeDataFn(). Removed 'void* io.ImeWindowHandle' in favor of writing to 'void* ImGuiViewport::PlatformHandleRaw'.
463 - 2022/01/01 (1.87) - commented out redirecting functions/enums names that were marked obsolete in 1.69, 1.70, 1.71, 1.72 (March-July 2019)
464 - ImGui::SetNextTreeNodeOpen() -> use ImGui::SetNextItemOpen()
465 - ImGui::GetContentRegionAvailWidth() -> use ImGui::GetContentRegionAvail().x
466 - ImGui::TreeAdvanceToLabelPos() -> use ImGui::SetCursorPosX(ImGui::GetCursorPosX() + ImGui::GetTreeNodeToLabelSpacing());
467 - ImFontAtlas::CustomRect -> use ImFontAtlasCustomRect
468 - ImGuiColorEditFlags_RGB/HSV/HEX -> use ImGuiColorEditFlags_DisplayRGB/HSV/Hex
469 - 2021/12/20 (1.86) - backends: removed obsolete Marmalade backend (imgui_impl_marmalade.cpp) + example. Find last supported version at https://github.com/ocornut/imgui/wiki/Bindings
470 - 2021/11/04 (1.86) - removed CalcListClipping() function. Prefer using ImGuiListClipper which can return non-contiguous ranges. Please open an issue if you think you really need this function.
471 - 2021/08/23 (1.85) - removed GetWindowContentRegionWidth() function. keep inline redirection helper. can use 'GetWindowContentRegionMax().x - GetWindowContentRegionMin().x' instead for generally 'GetContentRegionAvail().x' is more useful.
472 - 2021/07/26 (1.84) - commented out redirecting functions/enums names that were marked obsolete in 1.67 and 1.69 (March 2019):
473 - ImGui::GetOverlayDrawList() -> use ImGui::GetForegroundDrawList()
474 - ImFont::GlyphRangesBuilder -> use ImFontGlyphRangesBuilder
475 - 2021/05/19 (1.83) - backends: obsoleted direct access to ImDrawCmd::TextureId in favor of calling ImDrawCmd::GetTexID().
476 - if you are using official backends from the source tree: you have nothing to do.
477 - if you have copied old backend code or using your own: change access to draw_cmd->TextureId to draw_cmd->GetTexID().
478 - 2021/03/12 (1.82) - upgraded ImDrawList::AddRect(), AddRectFilled(), PathRect() to use ImDrawFlags instead of ImDrawCornersFlags.
479 - ImDrawCornerFlags_TopLeft -> use ImDrawFlags_RoundCornersTopLeft
480 - ImDrawCornerFlags_BotRight -> use ImDrawFlags_RoundCornersBottomRight
481 - ImDrawCornerFlags_None -> use ImDrawFlags_RoundCornersNone etc.
482 flags now sanely defaults to 0 instead of 0x0F, consistent with all other flags in the API.
483 breaking: the default with rounding > 0.0f is now "round all corners" vs old implicit "round no corners":
484 - rounding == 0.0f + flags == 0 --> meant no rounding --> unchanged (common use)
485 - rounding > 0.0f + flags != 0 --> meant rounding --> unchanged (common use)
486 - rounding == 0.0f + flags != 0 --> meant no rounding --> unchanged (unlikely use)
487 - rounding > 0.0f + flags == 0 --> meant no rounding --> BREAKING (unlikely use): will now round all corners --> use ImDrawFlags_RoundCornersNone or rounding == 0.0f.
488 this ONLY matters for hard coded use of 0 + rounding > 0.0f. Use of named ImDrawFlags_RoundCornersNone (new) or ImDrawCornerFlags_None (old) are ok.
489 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.
490 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).
491 - 2021/03/11 (1.82) - removed redirecting functions/enums names that were marked obsolete in 1.66 (September 2018):
492 - ImGui::SetScrollHere() -> use ImGui::SetScrollHereY()
493 - 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.
494 - 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.
495 - 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'.
496 - 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.
497 - 2021/02/03 (1.81) - renamed ListBoxHeader(const char* label, ImVec2 size) to BeginListBox(). Kept inline redirection function (will obsolete).
498 - removed ListBoxHeader(const char* label, int items_count, int height_in_items = -1) in favor of specifying size. Kept inline redirection function (will obsolete).
499 - renamed ListBoxFooter() to EndListBox(). Kept inline redirection function (will obsolete).
500 - 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.
501 - renamed ImFontConfig::RasterizerFlags (used by FreeType) to ImFontConfig::FontBuilderFlags.
502 - renamed ImGuiFreeType::XXX flags to ImGuiFreeTypeBuilderFlags_XXX for consistency with other API.
503 - 2020/10/12 (1.80) - removed redirecting functions/enums that were marked obsolete in 1.63 (August 2018):
504 - ImGui::IsItemDeactivatedAfterChange() -> use ImGui::IsItemDeactivatedAfterEdit().
505 - ImGuiCol_ModalWindowDarkening -> use ImGuiCol_ModalWindowDimBg
506 - ImGuiInputTextCallback -> use ImGuiTextEditCallback
507 - ImGuiInputTextCallbackData -> use ImGuiTextEditCallbackData
508 - 2020/12/21 (1.80) - renamed ImDrawList::AddBezierCurve() to AddBezierCubic(), and PathBezierCurveTo() to PathBezierCubicCurveTo(). Kept inline redirection function (will obsolete).
509 - 2020/12/04 (1.80) - added imgui_tables.cpp file! Manually constructed project files will need the new file added!
510 - 2020/11/18 (1.80) - renamed undocumented/internals ImGuiColumnsFlags_* to ImGuiOldColumnFlags_* in prevision of incoming Tables API.
511 - 2020/11/03 (1.80) - renamed io.ConfigWindowsMemoryCompactTimer to io.ConfigMemoryCompactTimer as the feature will apply to other data structures
512 - 2020/10/14 (1.80) - backends: moved all backends files (imgui_impl_XXXX.cpp, imgui_impl_XXXX.h) from examples/ to backends/.
513 - 2020/10/12 (1.80) - removed redirecting functions/enums that were marked obsolete in 1.60 (April 2018):
514 - io.RenderDrawListsFn pointer -> use ImGui::GetDrawData() value and call the render function of your backend
515 - ImGui::IsAnyWindowFocused() -> use ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow)
516 - ImGui::IsAnyWindowHovered() -> use ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow)
517 - ImGuiStyleVar_Count_ -> use ImGuiStyleVar_COUNT
518 - ImGuiMouseCursor_Count_ -> use ImGuiMouseCursor_COUNT
519 - removed redirecting functions names that were marked obsolete in 1.61 (May 2018):
520 - InputFloat (... int decimal_precision ...) -> use InputFloat (... const char* format ...) with format = "%.Xf" where X is your value for decimal_precision.
521 - same for InputFloat2()/InputFloat3()/InputFloat4() variants taking a `int decimal_precision` parameter.
522 - 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).
523 - 2020/09/25 (1.79) - renamed ImGuiSliderFlags_ClampOnInput to ImGuiSliderFlags_AlwaysClamp. Kept redirection enum (will obsolete sooner because previous name was added recently).
524 - 2020/09/25 (1.79) - renamed style.TabMinWidthForUnselectedCloseButton to style.TabMinWidthForCloseButton.
525 - 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.
526 - 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.
527 - 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!
528 - 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().
529 replaced the 'float power=1.0f' argument with integer-based flags defaulting to 0 (as with all our flags).
530 worked out a backward-compatibility scheme so hopefully most C++ codebase should not be affected. in short, when calling those functions:
531 - if you omitted the 'power' parameter (likely!), you are not affected.
532 - 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.
533 - 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.
534 see https://github.com/ocornut/imgui/issues/3361 for all details.
535 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.
536 for shared code, you can version check at compile-time with `#if IMGUI_VERSION_NUM >= 17704`.
537 - 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.
538 - 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.
539 - 2020/06/15 (1.77) - renamed OpenPopupOnItemClick() to OpenPopupContextItem(). Kept inline redirection function (will obsolete). [NOTE: THIS WAS REVERTED IN 1.79]
540 - 2020/06/15 (1.77) - removed CalcItemRectClosestPoint() entry point which was made obsolete and asserting in December 2017.
541 - 2020/04/23 (1.77) - removed unnecessary ID (first arg) of ImFontAtlas::AddCustomRectRegular().
542 - 2020/01/22 (1.75) - ImDrawList::AddCircle()/AddCircleFilled() functions don't accept negative radius any more.
543 - 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.
544 - 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.
545 - 2019/12/08 (1.75) - removed redirecting functions/enums that were marked obsolete in 1.53 (December 2017):
546 - ShowTestWindow() -> use ShowDemoWindow()
547 - IsRootWindowFocused() -> use IsWindowFocused(ImGuiFocusedFlags_RootWindow)
548 - IsRootWindowOrAnyChildFocused() -> use IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)
549 - SetNextWindowContentWidth(w) -> use SetNextWindowContentSize(ImVec2(w, 0.0f)
550 - GetItemsLineHeightWithSpacing() -> use GetFrameHeightWithSpacing()
551 - ImGuiCol_ChildWindowBg -> use ImGuiCol_ChildBg
552 - ImGuiStyleVar_ChildWindowRounding -> use ImGuiStyleVar_ChildRounding
553 - ImGuiTreeNodeFlags_AllowOverlapMode -> use ImGuiTreeNodeFlags_AllowItemOverlap
554 - IMGUI_DISABLE_TEST_WINDOWS -> use IMGUI_DISABLE_DEMO_WINDOWS
555 - 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.
556 - 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).
557 - 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.
558 - 2019/11/19 (1.74) - renamed IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS to IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS for consistency.
559 - 2019/11/19 (1.74) - renamed IMGUI_DISABLE_MATH_FUNCTIONS to IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS for consistency.
560 - 2019/10/22 (1.74) - removed redirecting functions/enums that were marked obsolete in 1.52 (October 2017):
561 - Begin() [old 5 args version] -> use Begin() [3 args], use SetNextWindowSize() SetNextWindowBgAlpha() if needed
562 - IsRootWindowOrAnyChildHovered() -> use IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows)
563 - AlignFirstTextHeightToWidgets() -> use AlignTextToFramePadding()
564 - SetNextWindowPosCenter() -> use SetNextWindowPos() with a pivot of (0.5f, 0.5f)
565 - ImFont::Glyph -> use ImFontGlyph
566 - 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.
567 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.
568 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).
569 If you never altered io.KeyRepeatRate nor used GetKeyPressedAmount() this won't affect you.
570 - 2019/07/15 (1.72) - removed TreeAdvanceToLabelPos() which is rarely used and only does SetCursorPosX(GetCursorPosX() + GetTreeNodeToLabelSpacing()). Kept redirection function (will obsolete).
571 - 2019/07/12 (1.72) - renamed ImFontAtlas::CustomRect to ImFontAtlasCustomRect. Kept redirection typedef (will obsolete).
572 - 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.
573 - 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
574 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.
575 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.
576 Please reach out if you are affected.
577 - 2019/05/13 (1.71) - renamed SetNextTreeNodeOpen() to SetNextItemOpen(). Kept inline redirection function (will obsolete).
578 - 2019/05/11 (1.71) - changed io.AddInputCharacter(unsigned short c) signature to io.AddInputCharacter(unsigned int c).
579 - 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.
580 - 2019/04/29 (1.70) - removed GetContentRegionAvailWidth(), use GetContentRegionAvail().x instead. Kept inline redirection function (will obsolete).
581 - 2019/03/04 (1.69) - renamed GetOverlayDrawList() to GetForegroundDrawList(). Kept redirection function (will obsolete).
582 - 2019/02/26 (1.69) - renamed ImGuiColorEditFlags_RGB/ImGuiColorEditFlags_HSV/ImGuiColorEditFlags_HEX to ImGuiColorEditFlags_DisplayRGB/ImGuiColorEditFlags_DisplayHSV/ImGuiColorEditFlags_DisplayHex. Kept redirection enums (will obsolete).
583 - 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!
584 - 2019/02/01 (1.68) - removed io.DisplayVisibleMin/DisplayVisibleMax (which were marked obsolete and removed from viewport/docking branch already).
585 - 2019/01/06 (1.67) - renamed io.InputCharacters[], marked internal as was always intended. Please don't access directly, and use AddInputCharacter() instead!
586 - 2019/01/06 (1.67) - renamed ImFontAtlas::GlyphRangesBuilder to ImFontGlyphRangesBuilder. Kept redirection typedef (will obsolete).
587 - 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.
588 - 2018/12/10 (1.67) - renamed io.ConfigResizeWindowsFromEdges to io.ConfigWindowsResizeFromEdges as we are doing a large pass on configuration flags.
589 - 2018/10/12 (1.66) - renamed misc/stl/imgui_stl.* to misc/cpp/imgui_stdlib.* in prevision for other C++ helper files.
590 - 2018/09/28 (1.66) - renamed SetScrollHere() to SetScrollHereY(). Kept redirection function (will obsolete).
591 - 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.
592 If you were conveniently using the imgui copy of those STB headers in your project you will have to update your include paths.
593 - 2018/09/05 (1.65) - renamed io.OptCursorBlink/io.ConfigCursorBlink to io.ConfigInputTextCursorBlink. (#1427)
594 - 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.
595 NONE OF THE FUNCTIONS HAVE CHANGED. THE CODE IS SEMANTICALLY 100% IDENTICAL, BUT _EVERY_ FUNCTION HAS BEEN MOVED.
596 Because of this, any local modifications to imgui.cpp will likely conflict when you update. Read docs/CHANGELOG.txt for suggestions.
597 - 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).
598 - 2018/08/21 (1.63) - renamed ImGuiTextEditCallback to ImGuiInputTextCallback, ImGuiTextEditCallbackData to ImGuiInputTextCallbackData for consistency. Kept redirection types (will obsolete).
599 - 2018/08/21 (1.63) - removed ImGuiInputTextCallbackData::ReadOnly since it is a duplication of (ImGuiInputTextCallbackData::Flags & ImGuiInputTextFlags_ReadOnly).
600 - 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.
601 - 2018/08/01 (1.63) - renamed io.OptCursorBlink to io.ConfigCursorBlink [-> io.ConfigInputTextCursorBlink in 1.65], io.OptMacOSXBehaviors to ConfigMacOSXBehaviors for consistency.
602 - 2018/07/22 (1.63) - changed ImGui::GetTime() return value from float to double to avoid accumulating floating point imprecisions over time.
603 - 2018/07/08 (1.63) - style: renamed ImGuiCol_ModalWindowDarkening to ImGuiCol_ModalWindowDimBg for consistency with other features. Kept redirection enum (will obsolete).
604 - 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.).
605 old backends will still work as is, however prefer using the separated backends as they will be updated to support multi-viewports.
606 when adopting new backends follow the main.cpp code of your preferred examples/ folder to know which functions to call.
607 in particular, note that old backends called ImGui::NewFrame() at the end of their ImGui_ImplXXXX_NewFrame() function.
608 - 2018/06/06 (1.62) - renamed GetGlyphRangesChinese() to GetGlyphRangesChineseFull() to distinguish other variants and discourage using the full set.
609 - 2018/06/06 (1.62) - TreeNodeEx()/TreeNodeBehavior(): the ImGuiTreeNodeFlags_CollapsingHeader helper now include the ImGuiTreeNodeFlags_NoTreePushOnOpen flag. See Changelog for details.
610 - 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.
611 If you used DragInt() with custom format strings, make sure you change them to use %d or an integer-compatible format.
612 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.
613 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.
614 - 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",
615 consistent with other functions. Kept redirection functions (will obsolete).
616 - 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.
617 - 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).
618 - 2018/03/12 (1.60) - removed ImGuiCol_CloseButton, ImGuiCol_CloseButtonActive, ImGuiCol_CloseButtonHovered as the closing cross uses regular button colors now.
619 - 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.
620 - 2018/03/03 (1.60) - renamed ImGuiStyleVar_Count_ to ImGuiStyleVar_COUNT and ImGuiMouseCursor_Count_ to ImGuiMouseCursor_COUNT for consistency with other public enums.
621 - 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.
622 - 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.
623 - 2018/02/07 (1.60) - reorganized context handling to be more explicit,
624 - YOU NOW NEED TO CALL ImGui::CreateContext() AT THE BEGINNING OF YOUR APP, AND CALL ImGui::DestroyContext() AT THE END.
625 - removed Shutdown() function, as DestroyContext() serve this purpose.
626 - you may pass a ImFontAtlas* pointer to CreateContext() to share a font atlas between contexts. Otherwise CreateContext() will create its own font atlas instance.
627 - removed allocator parameters from CreateContext(), they are now setup with SetAllocatorFunctions(), and shared by all contexts.
628 - removed the default global context and font atlas instance, which were confusing for users of DLL reloading and users of multiple contexts.
629 - 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.
630 - 2018/01/11 (1.60) - obsoleted IsAnyWindowHovered() in favor of IsWindowHovered(ImGuiHoveredFlags_AnyWindow). Kept redirection function (will obsolete).
631 - 2018/01/11 (1.60) - obsoleted IsAnyWindowFocused() in favor of IsWindowFocused(ImGuiFocusedFlags_AnyWindow). Kept redirection function (will obsolete).
632 - 2018/01/03 (1.60) - renamed ImGuiSizeConstraintCallback to ImGuiSizeCallback, ImGuiSizeConstraintCallbackData to ImGuiSizeCallbackData.
633 - 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.
634 - 2017/12/24 (1.53) - renamed the emblematic ShowTestWindow() function to ShowDemoWindow(). Kept redirection function (will obsolete).
635 - 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
636 - 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.
637 - 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.
638 - 2017/12/13 (1.53) - renamed GetItemsLineHeightWithSpacing() to GetFrameHeightWithSpacing(). Kept redirection function (will obsolete).
639 - 2017/12/13 (1.53) - obsoleted IsRootWindowFocused() in favor of using IsWindowFocused(ImGuiFocusedFlags_RootWindow). Kept redirection function (will obsolete).
640 - obsoleted IsRootWindowOrAnyChildFocused() in favor of using IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows). Kept redirection function (will obsolete).
641 - 2017/12/12 (1.53) - renamed ImGuiTreeNodeFlags_AllowOverlapMode to ImGuiTreeNodeFlags_AllowItemOverlap. Kept redirection enum (will obsolete).
642 - 2017/12/10 (1.53) - removed SetNextWindowContentWidth(), prefer using SetNextWindowContentSize(). Kept redirection function (will obsolete).
643 - 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.
644 - 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.
645 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.
646 - 2017/11/18 (1.53) - Style: removed ImGuiCol_ComboBg in favor of combo boxes using ImGuiCol_PopupBg for consistency.
647 - 2017/11/18 (1.53) - Style: renamed ImGuiCol_ChildWindowBg to ImGuiCol_ChildBg.
648 - 2017/11/18 (1.53) - Style: renamed style.ChildWindowRounding to style.ChildRounding, ImGuiStyleVar_ChildWindowRounding to ImGuiStyleVar_ChildRounding.
649 - 2017/11/02 (1.53) - obsoleted IsRootWindowOrAnyChildHovered() in favor of using IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows);
650 - 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.
651 - 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.
652 - 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.
653 removed the IsItemRectHovered()/IsWindowRectHovered() names introduced in 1.51 since they were merely more consistent names for the two functions we are now obsoleting.
654 IsItemHoveredRect() --> IsItemHovered(ImGuiHoveredFlags_RectOnly)
655 IsMouseHoveringAnyWindow() --> IsWindowHovered(ImGuiHoveredFlags_AnyWindow)
656 IsMouseHoveringWindow() --> IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem) [weird, old behavior]
657 - 2017/10/17 (1.52) - marked the old 5-parameters version of Begin() as obsolete (still available). Use SetNextWindowSize()+Begin() instead!
658 - 2017/10/11 (1.52) - renamed AlignFirstTextHeightToWidgets() to AlignTextToFramePadding(). Kept inline redirection function (will obsolete).
659 - 2017/09/26 (1.52) - renamed ImFont::Glyph to ImFontGlyph. Kept redirection typedef (will obsolete).
660 - 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).
661 - 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)".
662 - 2017/08/22 (1.51) - renamed IsItemHoveredRect() to IsItemRectHovered(). Kept inline redirection function (will obsolete). -> (1.52) use IsItemHovered(ImGuiHoveredFlags_RectOnly)!
663 - renamed IsMouseHoveringAnyWindow() to IsAnyWindowHovered() for consistency. Kept inline redirection function (will obsolete).
664 - renamed IsMouseHoveringWindow() to IsWindowRectHovered() for consistency. Kept inline redirection function (will obsolete).
665 - 2017/08/20 (1.51) - renamed GetStyleColName() to GetStyleColorName() for consistency.
666 - 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.
667 - 2017/08/15 (1.51) - marked the weird IMGUI_ONCE_UPON_A_FRAME helper macro as obsolete. prefer using the more explicit ImGuiOnceUponAFrame type.
668 - 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.
669 - 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).
670 - 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).
671 - 2017/08/09 (1.51) - removed ValueColor() helpers, they are equivalent to calling Text(label) + SameLine() + ColorButton().
672 - 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.
673 - 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.
674 - 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))'
675 - 2017/07/20 (1.51) - removed IsPosHoveringAnyWindow(ImVec2), which was partly broken and misleading. ASSERT + redirect user to io.WantCaptureMouse
676 - 2017/05/26 (1.50) - removed ImFontConfig::MergeGlyphCenterV in favor of a more multipurpose ImFontConfig::GlyphOffset.
677 - 2017/05/01 (1.50) - renamed ImDrawList::PathFill() (rarely used directly) to ImDrawList::PathFillConvex() for clarity.
678 - 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().
679 - 2016/10/15 (1.50) - avoid 'void* user_data' parameter to io.SetClipboardTextFn/io.GetClipboardTextFn pointers. We pass io.ClipboardUserData to it.
680 - 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.
681 - 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.
682 - 2016/05/12 (1.49) - title bar (using ImGuiCol_TitleBg/ImGuiCol_TitleBgActive colors) isn't rendered over a window background (ImGuiCol_WindowBg color) anymore.
683 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.
684 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:
685 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); }
686 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.
687 - 2016/05/07 (1.49) - removed confusing set of GetInternalState(), GetInternalStateSize(), SetInternalState() functions. Now using CreateContext(), DestroyContext(), GetCurrentContext(), SetCurrentContext().
688 - 2016/05/02 (1.49) - renamed SetNextTreeNodeOpened() to SetNextTreeNodeOpen(), no redirection.
689 - 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).
690 - 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.
691 - 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).
692 - 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)
693 - 2016/03/21 (1.48) - renamed GetWindowFont() to GetFont(), GetWindowFontSize() to GetFontSize(). Kept inline redirection function (will obsolete).
694 - 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.
695 - 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.
696 - 2015/12/27 (1.48) - fixed ImDrawList::AddRect() which used to render a rectangle 1 px too large on each axis.
697 - 2015/12/04 (1.47) - renamed Color() helpers to ValueColor() - dangerously named, rarely used and probably to be made obsolete.
698 - 2015/08/29 (1.45) - with the addition of horizontal scrollbar we made various fixes to inconsistencies with dealing with cursor position.
699 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.
700 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!
701 - 2015/08/29 (1.45) - renamed style.ScrollbarWidth to style.ScrollbarSize
702 - 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.
703 - 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
704 - 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.
705 you need to render your textured triangles with bilinear filtering to benefit from sub-pixel positioning of text.
706 - 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.
707 this necessary change will break your rendering function! the fix should be very easy. sorry for that :(
708 - 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.
709 - the signature of the io.RenderDrawListsFn handler has changed!
710 old: ImGui_XXXX_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count)
711 new: ImGui_XXXX_RenderDrawLists(ImDrawData* draw_data).
712 parameters: 'cmd_lists' becomes 'draw_data->CmdLists', 'cmd_lists_count' becomes 'draw_data->CmdListsCount'
713 ImDrawList: 'commands' becomes 'CmdBuffer', 'vtx_buffer' becomes 'VtxBuffer', 'IdxBuffer' is new.
714 ImDrawCmd: 'vtx_count' becomes 'ElemCount', 'clip_rect' becomes 'ClipRect', 'user_callback' becomes 'UserCallback', 'texture_id' becomes 'TextureId'.
715 - 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.
716 - 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!
717 - refer to code in the examples/ folder or ask on the GitHub if you are unsure of how to upgrade. please upgrade!
718 - 2015/07/10 (1.43) - changed SameLine() parameters from int to float.
719 - 2015/07/02 (1.42) - renamed SetScrollPosHere() to SetScrollFromCursorPos(). Kept inline redirection function (will obsolete).
720 - 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.
721 - 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
722 - 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!
723 - 2015/05/31 (1.40) - renamed GetWindowCollapsed() to IsWindowCollapsed() for consistency. Kept inline redirection function (will obsolete).
724 - 2015/05/31 (1.40) - renamed IsRectClipped() to IsRectVisible() for consistency. Note that return value is opposite! Kept inline redirection function (will obsolete).
725 - 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.
726 - 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.
727 - 2015/05/03 (1.40) - removed style.AutoFitPadding, using style.WindowPadding makes more sense (the default values were already the same).
728 - 2015/04/13 (1.38) - renamed IsClipped() to IsRectClipped(). Kept inline redirection function until 1.50.
729 - 2015/04/09 (1.38) - renamed ImDrawList::AddArc() to ImDrawList::AddArcFast() for compatibility with future API
730 - 2015/04/03 (1.38) - removed ImGuiCol_CheckHovered, ImGuiCol_CheckActive, replaced with the more general ImGuiCol_FrameBgHovered, ImGuiCol_FrameBgActive.
731 - 2014/04/03 (1.38) - removed support for passing -FLT_MAX..+FLT_MAX as the range for a SliderFloat(). Use DragFloat() or Inputfloat() instead.
732 - 2015/03/17 (1.36) - renamed GetItemBoxMin()/GetItemBoxMax()/IsMouseHoveringBox() to GetItemRectMin()/GetItemRectMax()/IsMouseHoveringRect(). Kept inline redirection function until 1.50.
733 - 2015/03/15 (1.36) - renamed style.TreeNodeSpacing to style.IndentSpacing, ImGuiStyleVar_TreeNodeSpacing to ImGuiStyleVar_IndentSpacing
734 - 2015/03/13 (1.36) - renamed GetWindowIsFocused() to IsWindowFocused(). Kept inline redirection function until 1.50.
735 - 2015/03/08 (1.35) - renamed style.ScrollBarWidth to style.ScrollbarWidth (casing)
736 - 2015/02/27 (1.34) - renamed OpenNextNode(bool) to SetNextTreeNodeOpened(bool, ImGuiSetCond). Kept inline redirection function until 1.50.
737 - 2015/02/27 (1.34) - renamed ImGuiSetCondition_*** to ImGuiSetCond_***, and _FirstUseThisSession becomes _Once.
738 - 2015/02/11 (1.32) - changed text input callback ImGuiTextEditCallback return type from void-->int. reserved for future use, return 0 for now.
739 - 2015/02/10 (1.32) - renamed GetItemWidth() to CalcItemWidth() to clarify its evolving behavior
740 - 2015/02/08 (1.31) - renamed GetTextLineSpacing() to GetTextLineHeightWithSpacing()
741 - 2015/02/01 (1.31) - removed IO.MemReallocFn (unused)
742 - 2015/01/19 (1.30) - renamed ImGuiStorage::GetIntPtr()/GetFloatPtr() to GetIntRef()/GetIntRef() because Ptr was conflicting with actual pointer storage functions.
743 - 2015/01/11 (1.30) - big font/image API change! now loads TTF file. allow for multiple fonts. no need for a PNG loader.
744 - 2015/01/11 (1.30) - removed GetDefaultFontData(). uses io.Fonts->GetTextureData*() API to retrieve uncompressed pixels.
745 - old: const void* png_data; unsigned int png_size; ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); [..Upload texture to GPU..];
746 - new: unsigned char* pixels; int width, height; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); [..Upload texture to GPU..]; io.Fonts->SetTexID(YourTexIdentifier);
747 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.
748 - 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()
749 - 2015/01/11 (1.30) - removed IO.PixelCenterOffset (unnecessary, can be handled in user projection matrix)
750 - 2015/01/11 (1.30) - removed ImGui::IsItemFocused() in favor of ImGui::IsItemActive() which handles all widgets
751 - 2014/12/10 (1.18) - removed SetNewWindowDefaultPos() in favor of new generic API SetNextWindowPos(pos, ImGuiSetCondition_FirstUseEver)
752 - 2014/11/28 (1.17) - moved IO.Font*** options to inside the IO.Font-> structure (FontYOffset, FontTexUvForWhite, FontBaseScale, FontFallbackGlyph)
753 - 2014/11/26 (1.17) - reworked syntax of IMGUI_ONCE_UPON_A_FRAME helper macro to increase compiler compatibility
754 - 2014/11/07 (1.15) - renamed IsHovered() to IsItemHovered()
755 - 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)
756 - 2014/09/25 (1.13) - removed 'text_end' parameter from IO.SetClipboardTextFn (the string is now always zero-terminated for simplicity)
757 - 2014/09/24 (1.12) - renamed SetFontScale() to SetWindowFontScale()
758 - 2014/09/24 (1.12) - moved IM_MALLOC/IM_REALLOC/IM_FREE preprocessor defines to IO.MemAllocFn/IO.MemReallocFn/IO.MemFreeFn
759 - 2014/08/30 (1.09) - removed IO.FontHeight (now computed automatically)
760 - 2014/08/30 (1.09) - moved IMGUI_FONT_TEX_UV_FOR_WHITE preprocessor define to IO.FontTexUvForWhite
761 - 2014/08/28 (1.09) - changed the behavior of IO.PixelCenterOffset following various rendering fixes
762
763
764 FREQUENTLY ASKED QUESTIONS (FAQ)
765 ================================
766
767 Read all answers online:
768 https://www.dearimgui.org/faq or https://github.com/ocornut/imgui/blob/master/docs/FAQ.md (same url)
769 Read all answers locally (with a text editor or ideally a Markdown viewer):
770 docs/FAQ.md
771 Some answers are copied down here to facilitate searching in code.
772
773 Q&A: Basics
774 ===========
775
776 Q: Where is the documentation?
777 A: This library is poorly documented at the moment and expects the user to be acquainted with C/C++.
778 - Run the examples/ and explore them.
779 - See demo code in imgui_demo.cpp and particularly the ImGui::ShowDemoWindow() function.
780 - The demo covers most features of Dear ImGui, so you can read the code and see its output.
781 - See documentation and comments at the top of imgui.cpp + effectively imgui.h.
782 - Dozens of standalone example applications using e.g. OpenGL/DirectX are provided in the
783 examples/ folder to explain how to integrate Dear ImGui with your own engine/application.
784 - The Wiki (https://github.com/ocornut/imgui/wiki) has many resources and links.
785 - The Glossary (https://github.com/ocornut/imgui/wiki/Glossary) page also may be useful.
786 - Your programming IDE is your friend, find the type or function declaration to find comments
787 associated with it.
788
789 Q: What is this library called?
790 Q: Which version should I get?
791 >> This library is called "Dear ImGui", please don't call it "ImGui" :)
792 >> See https://www.dearimgui.org/faq for details.
793
794 Q&A: Integration
795 ================
796
797 Q: How to get started?
798 A: Read 'PROGRAMMER GUIDE' above. Read examples/README.txt.
799
800 Q: How can I tell whether to dispatch mouse/keyboard to Dear ImGui or my application?
801 A: You should read the 'io.WantCaptureMouse', 'io.WantCaptureKeyboard' and 'io.WantTextInput' flags!
802 >> See https://www.dearimgui.org/faq for a fully detailed answer. You really want to read this.
803
804 Q. How can I enable keyboard controls?
805 Q: How can I use this without a mouse, without a keyboard or without a screen? (gamepad, input share, remote display)
806 Q: I integrated Dear ImGui in my engine and little squares are showing instead of text...
807 Q: I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around...
808 Q: I integrated Dear ImGui in my engine and some elements are displaying outside their expected windows boundaries...
809 >> See https://www.dearimgui.org/faq
810
811 Q&A: Usage
812 ----------
813
814 Q: About the ID Stack system..
815 - Why is my widget not reacting when I click on it?
816 - How can I have widgets with an empty label?
817 - How can I have multiple widgets with the same label?
818 - How can I have multiple windows with the same label?
819 Q: How can I display an image? What is ImTextureID, how does it work?
820 Q: How can I use my own math types instead of ImVec2/ImVec4?
821 Q: How can I interact with standard C++ types (such as std::string and std::vector)?
822 Q: How can I display custom shapes? (using low-level ImDrawList API)
823 >> See https://www.dearimgui.org/faq
824
825 Q&A: Fonts, Text
826 ================
827
828 Q: How should I handle DPI in my application?
829 Q: How can I load a different font than the default?
830 Q: How can I easily use icons in my application?
831 Q: How can I load multiple fonts?
832 Q: How can I display and input non-Latin characters such as Chinese, Japanese, Korean, Cyrillic?
833 >> See https://www.dearimgui.org/faq and https://github.com/ocornut/imgui/edit/master/docs/FONTS.md
834
835 Q&A: Concerns
836 =============
837
838 Q: Who uses Dear ImGui?
839 Q: Can you create elaborate/serious tools with Dear ImGui?
840 Q: Can you reskin the look of Dear ImGui?
841 Q: Why using C++ (as opposed to C)?
842 >> See https://www.dearimgui.org/faq
843
844 Q&A: Community
845 ==============
846
847 Q: How can I help?
848 A: - Businesses: please reach out to "contact AT dearimgui.com" if you work in a place using Dear ImGui!
849 We can discuss ways for your company to fund development via invoiced technical support, maintenance or sponsoring contacts.
850 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.
851 - Individuals: you can support continued development via PayPal donations. See README.
852 - If you are experienced with Dear ImGui and C++, look at the GitHub issues, look at the Wiki, read docs/TODO.txt
853 and see how you want to help and can help!
854 - Disclose your usage of Dear ImGui via a dev blog post, a tweet, a screenshot, a mention somewhere etc.
855 You may post screenshot or links in the gallery threads. Visuals are ideal as they inspire other programmers.
856 But even without visuals, disclosing your use of dear imgui helps the library grow credibility, and help other teams and programmers with taking decisions.
857 - 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).
858
859*/
860
861//-------------------------------------------------------------------------
862// [SECTION] INCLUDES
863//-------------------------------------------------------------------------
864
865#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
866#define _CRT_SECURE_NO_WARNINGS
867#endif
868
869#include "imgui.h"
870#ifndef IMGUI_DISABLE
871
872#ifndef IMGUI_DEFINE_MATH_OPERATORS
873#define IMGUI_DEFINE_MATH_OPERATORS
874#endif
875#include "imgui_internal.h"
876
877// System includes
878#include <stdio.h> // vsnprintf, sscanf, printf
879#if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier
880#include <stddef.h> // intptr_t
881#else
882#include <stdint.h> // intptr_t
883#endif
884
885// [Windows] On non-Visual Studio compilers, we default to IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS unless explicitly enabled
886#if defined(_WIN32) && !defined(_MSC_VER) && !defined(IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS)
887#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS
888#endif
889
890// [Windows] OS specific includes (optional)
891#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)
892#define IMGUI_DISABLE_WIN32_FUNCTIONS
893#endif
894#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS)
895#ifndef WIN32_LEAN_AND_MEAN
896#define WIN32_LEAN_AND_MEAN
897#endif
898#ifndef NOMINMAX
899#define NOMINMAX
900#endif
901#ifndef __MINGW32__
902#include <Windows.h> // _wfopen, OpenClipboard
903#else
904#include <windows.h>
905#endif
906#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) // UWP doesn't have all Win32 functions
907#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS
908#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS
909#endif
910#endif
911
912// [Apple] OS specific includes
913#if defined(__APPLE__)
914#include <TargetConditionals.h>
915#endif
916
917// Visual Studio warnings
918#ifdef _MSC_VER
919#pragma warning (disable: 4127) // condition expression is constant
920#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen
921#if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later
922#pragma warning (disable: 5054) // operator '|': deprecated between enumerations of different types
923#endif
924#pragma warning (disable: 26451) // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to an 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2).
925#pragma warning (disable: 26495) // [Static Analyzer] Variable 'XXX' is uninitialized. Always initialize a member variable (type.6).
926#pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3).
927#endif
928
929// Clang/GCC warnings with -Weverything
930#if defined(__clang__)
931#if __has_warning("-Wunknown-warning-option")
932#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!
933#endif
934#pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx'
935#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse.
936#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.
937#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.
938#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.
939#pragma clang diagnostic ignored "-Wglobal-constructors" // warning: declaration requires a global destructor // similar to above, not sure what the exact difference is.
940#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness
941#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.
942#pragma clang diagnostic ignored "-Wint-to-void-pointer-cast" // warning: cast to 'void *' from smaller integer type 'int'
943#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant // some standard header variations use #define NULL 0
944#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.
945#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision
946#elif defined(__GNUC__)
947// We disable -Wpragmas because GCC doesn't provide a has_warning equivalent and some forks/patches may not follow the warning/version association.
948#pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind
949#pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used
950#pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size
951#pragma GCC diagnostic ignored "-Wformat" // warning: format '%p' expects argument of type 'void*', but argument 6 has type 'ImGuiWindow*'
952#pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function
953#pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value
954#pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked
955#pragma GCC diagnostic ignored "-Wstrict-overflow" // warning: assuming signed overflow does not occur when assuming that (X - c) > X is always false
956#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
957#endif
958
959// Debug options
960#define IMGUI_DEBUG_NAV_SCORING 0 // Display navigation scoring preview when hovering items. Display last moving direction matches when holding CTRL
961#define IMGUI_DEBUG_NAV_RECTS 0 // Display the reference navigation rectangle for each window
962#define IMGUI_DEBUG_INI_SETTINGS 0 // Save additional comments in .ini file (particularly helps for Docking, but makes saving slower)
963
964// 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.
965static const float NAV_WINDOWING_HIGHLIGHT_DELAY = 0.20f; // Time before the highlight and screen dimming starts fading in
966static const float NAV_WINDOWING_LIST_APPEAR_DELAY = 0.15f; // Time before the window list starts to appear
967
968// Window resizing from edges (when io.ConfigWindowsResizeFromEdges = true and ImGuiBackendFlags_HasMouseCursors is set in io.BackendFlags by backend)
969static const float WINDOWS_HOVER_PADDING = 4.0f; // Extend outside window for hovering/resizing (maxxed with TouchPadding) and inside windows for borders. Affect FindHoveredWindow().
970static const float WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER = 0.04f; // Reduce visual noise by only highlighting the border after a certain time.
971static const float WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER = 0.70f; // Lock scrolled window (so it doesn't pick child windows that are scrolling through) for a certain time, unless mouse moved.
972
973// Docking
974static const float DOCKING_TRANSPARENT_PAYLOAD_ALPHA = 0.50f; // For use with io.ConfigDockingTransparentPayload. Apply to Viewport _or_ WindowBg in host viewport.
975static const float DOCKING_SPLITTER_SIZE = 2.0f;
976
977//-------------------------------------------------------------------------
978// [SECTION] FORWARD DECLARATIONS
979//-------------------------------------------------------------------------
980
981static void SetCurrentWindow(ImGuiWindow* window);
982static void FindHoveredWindow();
983static ImGuiWindow* CreateNewWindow(const char* name, ImGuiWindowFlags flags);
984static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window);
985
986static void AddDrawListToDrawData(ImVector<ImDrawList*>* out_list, ImDrawList* draw_list);
987static void AddWindowToSortBuffer(ImVector<ImGuiWindow*>* out_sorted_windows, ImGuiWindow* window);
988
989// Settings
990static void WindowSettingsHandler_ClearAll(ImGuiContext*, ImGuiSettingsHandler*);
991static void* WindowSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name);
992static void WindowSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line);
993static void WindowSettingsHandler_ApplyAll(ImGuiContext*, ImGuiSettingsHandler*);
994static void WindowSettingsHandler_WriteAll(ImGuiContext*, ImGuiSettingsHandler*, ImGuiTextBuffer* buf);
995
996// Platform Dependents default implementation for IO functions
997static const char* GetClipboardTextFn_DefaultImpl(void* user_data);
998static void SetClipboardTextFn_DefaultImpl(void* user_data, const char* text);
999static void SetPlatformImeDataFn_DefaultImpl(ImGuiViewport* viewport, ImGuiPlatformImeData* data);
1000
1001namespace ImGui
1002{
1003// Navigation
1004static void NavUpdate();
1005static void NavUpdateWindowing();
1006static void NavUpdateWindowingOverlay();
1007static void NavUpdateCancelRequest();
1008static void NavUpdateCreateMoveRequest();
1009static void NavUpdateCreateTabbingRequest();
1010static float NavUpdatePageUpPageDown();
1011static inline void NavUpdateAnyRequestFlag();
1012static void NavUpdateCreateWrappingRequest();
1013static void NavEndFrame();
1014static bool NavScoreItem(ImGuiNavItemData* result);
1015static void NavApplyItemToResult(ImGuiNavItemData* result);
1016static void NavProcessItem();
1017static void NavProcessItemForTabbingRequest(ImGuiID id);
1018static ImVec2 NavCalcPreferredRefPos();
1019static void NavSaveLastChildNavWindowIntoParent(ImGuiWindow* nav_window);
1020static ImGuiWindow* NavRestoreLastChildNavWindow(ImGuiWindow* window);
1021static void NavRestoreLayer(ImGuiNavLayer layer);
1022static void NavRestoreHighlightAfterMove();
1023static int FindWindowFocusIndex(ImGuiWindow* window);
1024
1025// Error Checking and Debug Tools
1026static void ErrorCheckNewFrameSanityChecks();
1027static void ErrorCheckEndFrameSanityChecks();
1028static void UpdateDebugToolItemPicker();
1029static void UpdateDebugToolStackQueries();
1030
1031// Misc
1032static void UpdateSettings();
1033static void UpdateKeyboardInputs();
1034static void UpdateMouseInputs();
1035static void UpdateMouseWheel();
1036static 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);
1037static void RenderWindowOuterBorders(ImGuiWindow* window);
1038static 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);
1039static void RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& title_bar_rect, const char* name, bool* p_open);
1040static void RenderDimmedBackgroundBehindWindow(ImGuiWindow* window, ImU32 col);
1041static void RenderDimmedBackgrounds();
1042static ImGuiWindow* FindBlockingModal(ImGuiWindow* window);
1043
1044// Viewports
1045const 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.
1046static ImGuiViewportP* AddUpdateViewport(ImGuiWindow* window, ImGuiID id, const ImVec2& platform_pos, const ImVec2& size, ImGuiViewportFlags flags);
1047static void DestroyViewport(ImGuiViewportP* viewport);
1048static void UpdateViewportsNewFrame();
1049static void UpdateViewportsEndFrame();
1050static void WindowSelectViewport(ImGuiWindow* window);
1051static void WindowSyncOwnedViewport(ImGuiWindow* window, ImGuiWindow* parent_window_in_stack);
1052static bool UpdateTryMergeWindowIntoHostViewport(ImGuiWindow* window, ImGuiViewportP* host_viewport);
1053static bool UpdateTryMergeWindowIntoHostViewports(ImGuiWindow* window);
1054static bool GetWindowAlwaysWantOwnViewport(ImGuiWindow* window);
1055static int FindPlatformMonitorForPos(const ImVec2& pos);
1056static int FindPlatformMonitorForRect(const ImRect& r);
1057static void UpdateViewportPlatformMonitor(ImGuiViewportP* viewport);
1058
1059}
1060
1061//-----------------------------------------------------------------------------
1062// [SECTION] CONTEXT AND MEMORY ALLOCATORS
1063//-----------------------------------------------------------------------------
1064
1065// DLL users:
1066// - Heaps and globals are not shared across DLL boundaries!
1067// - You will need to call SetCurrentContext() + SetAllocatorFunctions() for each static/DLL boundary you are calling from.
1068// - Same applies for hot-reloading mechanisms that are reliant on reloading DLL (note that many hot-reloading mechanisms work without DLL).
1069// - 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.
1070// - 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).
1071
1072// Current context pointer. Implicitly used by all Dear ImGui functions. Always assumed to be != NULL.
1073// - ImGui::CreateContext() will automatically set this pointer if it is NULL.
1074// Change to a different context by calling ImGui::SetCurrentContext().
1075// - Important: Dear ImGui functions are not thread-safe because of this pointer.
1076// If you want thread-safety to allow N threads to access N different contexts:
1077// - Change this variable to use thread local storage so each thread can refer to a different context, in your imconfig.h:
1078// struct ImGuiContext;
1079// extern thread_local ImGuiContext* MyImGuiTLS;
1080// #define GImGui MyImGuiTLS
1081// 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.
1082// - Future development aims to make this context pointer explicit to all calls. Also read https://github.com/ocornut/imgui/issues/586
1083// - If you need a finite number of contexts, you may compile and use multiple instances of the ImGui code from a different namespace.
1084// - DLL users: read comments above.
1085#ifndef GImGui
1086ImGuiContext* GImGui = NULL;
1087#endif
1088
1089// Memory Allocator functions. Use SetAllocatorFunctions() to change them.
1090// - 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.
1091// - DLL users: read comments above.
1092#ifndef IMGUI_DISABLE_DEFAULT_ALLOCATORS
1093static void* MallocWrapper(size_t size, void* user_data) { IM_UNUSED(user_data); return malloc(size: size); }
1094static void FreeWrapper(void* ptr, void* user_data) { IM_UNUSED(user_data); free(ptr: ptr); }
1095#else
1096static void* MallocWrapper(size_t size, void* user_data) { IM_UNUSED(user_data); IM_UNUSED(size); IM_ASSERT(0); return NULL; }
1097static void FreeWrapper(void* ptr, void* user_data) { IM_UNUSED(user_data); IM_UNUSED(ptr); IM_ASSERT(0); }
1098#endif
1099static ImGuiMemAllocFunc GImAllocatorAllocFunc = MallocWrapper;
1100static ImGuiMemFreeFunc GImAllocatorFreeFunc = FreeWrapper;
1101static void* GImAllocatorUserData = NULL;
1102
1103//-----------------------------------------------------------------------------
1104// [SECTION] USER FACING STRUCTURES (ImGuiStyle, ImGuiIO)
1105//-----------------------------------------------------------------------------
1106
1107ImGuiStyle::ImGuiStyle()
1108{
1109 Alpha = 1.0f; // Global alpha applies to everything in Dear ImGui.
1110 DisabledAlpha = 0.60f; // Additional alpha multiplier applied by BeginDisabled(). Multiply over current value of Alpha.
1111 WindowPadding = ImVec2(8,8); // Padding within a window
1112 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.
1113 WindowBorderSize = 1.0f; // Thickness of border around windows. Generally set to 0.0f or 1.0f. Other values not well tested.
1114 WindowMinSize = ImVec2(32,32); // Minimum window size
1115 WindowTitleAlign = ImVec2(0.0f,0.5f);// Alignment for title bar text
1116 WindowMenuButtonPosition= ImGuiDir_Left; // Position of the collapsing/docking button in the title bar (left/right). Defaults to ImGuiDir_Left.
1117 ChildRounding = 0.0f; // Radius of child window corners rounding. Set to 0.0f to have rectangular child windows
1118 ChildBorderSize = 1.0f; // Thickness of border around child windows. Generally set to 0.0f or 1.0f. Other values not well tested.
1119 PopupRounding = 0.0f; // Radius of popup window corners rounding. Set to 0.0f to have rectangular child windows
1120 PopupBorderSize = 1.0f; // Thickness of border around popup or tooltip windows. Generally set to 0.0f or 1.0f. Other values not well tested.
1121 FramePadding = ImVec2(4,3); // Padding within a framed rectangle (used by most widgets)
1122 FrameRounding = 0.0f; // Radius of frame corners rounding. Set to 0.0f to have rectangular frames (used by most widgets).
1123 FrameBorderSize = 0.0f; // Thickness of border around frames. Generally set to 0.0f or 1.0f. Other values not well tested.
1124 ItemSpacing = ImVec2(8,4); // Horizontal and vertical spacing between widgets/lines
1125 ItemInnerSpacing = ImVec2(4,4); // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label)
1126 CellPadding = ImVec2(4,2); // Padding within a table cell
1127 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!
1128 IndentSpacing = 21.0f; // Horizontal spacing when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2).
1129 ColumnsMinSpacing = 6.0f; // Minimum horizontal spacing between two columns. Preferably > (FramePadding.x + 1).
1130 ScrollbarSize = 14.0f; // Width of the vertical scrollbar, Height of the horizontal scrollbar
1131 ScrollbarRounding = 9.0f; // Radius of grab corners rounding for scrollbar
1132 GrabMinSize = 12.0f; // Minimum width/height of a grab box for slider/scrollbar
1133 GrabRounding = 0.0f; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs.
1134 LogSliderDeadzone = 4.0f; // The size in pixels of the dead-zone around zero on logarithmic sliders that cross zero.
1135 TabRounding = 4.0f; // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs.
1136 TabBorderSize = 0.0f; // Thickness of border around tabs.
1137 TabMinWidthForCloseButton = 0.0f; // Minimum width for close button to appear 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.
1138 ColorButtonPosition = ImGuiDir_Right; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right.
1139 ButtonTextAlign = ImVec2(0.5f,0.5f);// Alignment of button text when button is larger than text.
1140 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.
1141 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.
1142 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.
1143 MouseCursorScale = 1.0f; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later.
1144 AntiAliasedLines = true; // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU.
1145 AntiAliasedLinesUseTex = true; // Enable anti-aliased lines/borders using textures where possible. Require backend to render with bilinear filtering (NOT point/nearest filtering).
1146 AntiAliasedFill = true; // Enable anti-aliased filled shapes (rounded rectangles, circles, etc.).
1147 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.
1148 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.
1149
1150 // Default theme
1151 ImGui::StyleColorsDark(dst: this);
1152}
1153
1154// 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.
1155// 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.
1156void ImGuiStyle::ScaleAllSizes(float scale_factor)
1157{
1158 WindowPadding = ImFloor(v: WindowPadding * scale_factor);
1159 WindowRounding = ImFloor(f: WindowRounding * scale_factor);
1160 WindowMinSize = ImFloor(v: WindowMinSize * scale_factor);
1161 ChildRounding = ImFloor(f: ChildRounding * scale_factor);
1162 PopupRounding = ImFloor(f: PopupRounding * scale_factor);
1163 FramePadding = ImFloor(v: FramePadding * scale_factor);
1164 FrameRounding = ImFloor(f: FrameRounding * scale_factor);
1165 ItemSpacing = ImFloor(v: ItemSpacing * scale_factor);
1166 ItemInnerSpacing = ImFloor(v: ItemInnerSpacing * scale_factor);
1167 CellPadding = ImFloor(v: CellPadding * scale_factor);
1168 TouchExtraPadding = ImFloor(v: TouchExtraPadding * scale_factor);
1169 IndentSpacing = ImFloor(f: IndentSpacing * scale_factor);
1170 ColumnsMinSpacing = ImFloor(f: ColumnsMinSpacing * scale_factor);
1171 ScrollbarSize = ImFloor(f: ScrollbarSize * scale_factor);
1172 ScrollbarRounding = ImFloor(f: ScrollbarRounding * scale_factor);
1173 GrabMinSize = ImFloor(f: GrabMinSize * scale_factor);
1174 GrabRounding = ImFloor(f: GrabRounding * scale_factor);
1175 LogSliderDeadzone = ImFloor(f: LogSliderDeadzone * scale_factor);
1176 TabRounding = ImFloor(f: TabRounding * scale_factor);
1177 TabMinWidthForCloseButton = (TabMinWidthForCloseButton != FLT_MAX) ? ImFloor(f: TabMinWidthForCloseButton * scale_factor) : FLT_MAX;
1178 DisplayWindowPadding = ImFloor(v: DisplayWindowPadding * scale_factor);
1179 DisplaySafeAreaPadding = ImFloor(v: DisplaySafeAreaPadding * scale_factor);
1180 MouseCursorScale = ImFloor(f: MouseCursorScale * scale_factor);
1181}
1182
1183ImGuiIO::ImGuiIO()
1184{
1185 // Most fields are initialized with zero
1186 memset(s: this, c: 0, n: sizeof(*this));
1187 IM_STATIC_ASSERT(IM_ARRAYSIZE(ImGuiIO::MouseDown) == ImGuiMouseButton_COUNT && IM_ARRAYSIZE(ImGuiIO::MouseClicked) == ImGuiMouseButton_COUNT);
1188
1189 // Settings
1190 ConfigFlags = ImGuiConfigFlags_None;
1191 BackendFlags = ImGuiBackendFlags_None;
1192 DisplaySize = ImVec2(-1.0f, -1.0f);
1193 DeltaTime = 1.0f / 60.0f;
1194 IniSavingRate = 5.0f;
1195 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).
1196 LogFilename = "imgui_log.txt";
1197 MouseDoubleClickTime = 0.30f;
1198 MouseDoubleClickMaxDist = 6.0f;
1199#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO
1200 for (int i = 0; i < ImGuiKey_COUNT; i++)
1201 KeyMap[i] = -1;
1202#endif
1203 KeyRepeatDelay = 0.275f;
1204 KeyRepeatRate = 0.050f;
1205 HoverDelayNormal = 0.30f;
1206 HoverDelayShort = 0.10f;
1207 UserData = NULL;
1208
1209 Fonts = NULL;
1210 FontGlobalScale = 1.0f;
1211 FontDefault = NULL;
1212 FontAllowUserScaling = false;
1213 DisplayFramebufferScale = ImVec2(1.0f, 1.0f);
1214
1215 // Docking options (when ImGuiConfigFlags_DockingEnable is set)
1216 ConfigDockingNoSplit = false;
1217 ConfigDockingWithShift = false;
1218 ConfigDockingAlwaysTabBar = false;
1219 ConfigDockingTransparentPayload = false;
1220
1221 // Viewport options (when ImGuiConfigFlags_ViewportsEnable is set)
1222 ConfigViewportsNoAutoMerge = false;
1223 ConfigViewportsNoTaskBarIcon = false;
1224 ConfigViewportsNoDecoration = true;
1225 ConfigViewportsNoDefaultParent = false;
1226
1227 // Miscellaneous options
1228 MouseDrawCursor = false;
1229#ifdef __APPLE__
1230 ConfigMacOSXBehaviors = true; // Set Mac OS X style defaults based on __APPLE__ compile time flag
1231#else
1232 ConfigMacOSXBehaviors = false;
1233#endif
1234 ConfigInputTrickleEventQueue = true;
1235 ConfigInputTextCursorBlink = true;
1236 ConfigInputTextEnterKeepActive = false;
1237 ConfigDragClickToInputText = false;
1238 ConfigWindowsResizeFromEdges = true;
1239 ConfigWindowsMoveFromTitleBarOnly = false;
1240 ConfigMemoryCompactTimer = 60.0f;
1241
1242 // Platform Functions
1243 BackendPlatformName = BackendRendererName = NULL;
1244 BackendPlatformUserData = BackendRendererUserData = BackendLanguageUserData = NULL;
1245 GetClipboardTextFn = GetClipboardTextFn_DefaultImpl; // Platform dependent default implementations
1246 SetClipboardTextFn = SetClipboardTextFn_DefaultImpl;
1247 ClipboardUserData = NULL;
1248 SetPlatformImeDataFn = SetPlatformImeDataFn_DefaultImpl;
1249
1250 // Input (NB: we already have memset zero the entire structure!)
1251 MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
1252 MousePosPrev = ImVec2(-FLT_MAX, -FLT_MAX);
1253 MouseDragThreshold = 6.0f;
1254 for (int i = 0; i < IM_ARRAYSIZE(MouseDownDuration); i++) MouseDownDuration[i] = MouseDownDurationPrev[i] = -1.0f;
1255 for (int i = 0; i < IM_ARRAYSIZE(KeysData); i++) { KeysData[i].DownDuration = KeysData[i].DownDurationPrev = -1.0f; }
1256 AppAcceptingEvents = true;
1257 BackendUsingLegacyKeyArrays = (ImS8)-1;
1258 BackendUsingLegacyNavInputArray = true; // assume using legacy array until proven wrong
1259}
1260
1261// Pass in translated ASCII characters for text input.
1262// - with glfw you can get those from the callback set in glfwSetCharCallback()
1263// - on Windows you can get those using ToAscii+keyboard state, or via the WM_CHAR message
1264// FIXME: Should in theory be called "AddCharacterEvent()" to be consistent with new API
1265void ImGuiIO::AddInputCharacter(unsigned int c)
1266{
1267 ImGuiContext& g = *GImGui;
1268 IM_ASSERT(&g.IO == this && "Can only add events to current context.");
1269 if (c == 0 || !AppAcceptingEvents)
1270 return;
1271
1272 ImGuiInputEvent e;
1273 e.Type = ImGuiInputEventType_Text;
1274 e.Source = ImGuiInputSource_Keyboard;
1275 e.Text.Char = c;
1276 g.InputEventsQueue.push_back(v: e);
1277}
1278
1279// UTF16 strings use surrogate pairs to encode codepoints >= 0x10000, so
1280// we should save the high surrogate.
1281void ImGuiIO::AddInputCharacterUTF16(ImWchar16 c)
1282{
1283 if ((c == 0 && InputQueueSurrogate == 0) || !AppAcceptingEvents)
1284 return;
1285
1286 if ((c & 0xFC00) == 0xD800) // High surrogate, must save
1287 {
1288 if (InputQueueSurrogate != 0)
1289 AddInputCharacter(IM_UNICODE_CODEPOINT_INVALID);
1290 InputQueueSurrogate = c;
1291 return;
1292 }
1293
1294 ImWchar cp = c;
1295 if (InputQueueSurrogate != 0)
1296 {
1297 if ((c & 0xFC00) != 0xDC00) // Invalid low surrogate
1298 {
1299 AddInputCharacter(IM_UNICODE_CODEPOINT_INVALID);
1300 }
1301 else
1302 {
1303#if IM_UNICODE_CODEPOINT_MAX == 0xFFFF
1304 cp = IM_UNICODE_CODEPOINT_INVALID; // Codepoint will not fit in ImWchar
1305#else
1306 cp = (ImWchar)(((InputQueueSurrogate - 0xD800) << 10) + (c - 0xDC00) + 0x10000);
1307#endif
1308 }
1309
1310 InputQueueSurrogate = 0;
1311 }
1312 AddInputCharacter(c: (unsigned)cp);
1313}
1314
1315void ImGuiIO::AddInputCharactersUTF8(const char* utf8_chars)
1316{
1317 if (!AppAcceptingEvents)
1318 return;
1319 while (*utf8_chars != 0)
1320 {
1321 unsigned int c = 0;
1322 utf8_chars += ImTextCharFromUtf8(out_char: &c, in_text: utf8_chars, NULL);
1323 if (c != 0)
1324 AddInputCharacter(c);
1325 }
1326}
1327
1328// FIXME: Perhaps we could clear queued events as well?
1329void ImGuiIO::ClearInputCharacters()
1330{
1331 InputQueueCharacters.resize(new_size: 0);
1332}
1333
1334// FIXME: Perhaps we could clear queued events as well?
1335void ImGuiIO::ClearInputKeys()
1336{
1337#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO
1338 memset(s: KeysDown, c: 0, n: sizeof(KeysDown));
1339#endif
1340 for (int n = 0; n < IM_ARRAYSIZE(KeysData); n++)
1341 {
1342 KeysData[n].Down = false;
1343 KeysData[n].DownDuration = -1.0f;
1344 KeysData[n].DownDurationPrev = -1.0f;
1345 }
1346 KeyCtrl = KeyShift = KeyAlt = KeySuper = false;
1347 KeyMods = ImGuiMod_None;
1348 MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
1349 for (int n = 0; n < IM_ARRAYSIZE(MouseDown); n++)
1350 {
1351 MouseDown[n] = false;
1352 MouseDownDuration[n] = MouseDownDurationPrev[n] = -1.0f;
1353 }
1354 MouseWheel = MouseWheelH = 0.0f;
1355}
1356
1357static ImGuiInputEvent* FindLatestInputEvent(ImGuiInputEventType type, int arg = -1)
1358{
1359 ImGuiContext& g = *GImGui;
1360 for (int n = g.InputEventsQueue.Size - 1; n >= 0; n--)
1361 {
1362 ImGuiInputEvent* e = &g.InputEventsQueue[n];
1363 if (e->Type != type)
1364 continue;
1365 if (type == ImGuiInputEventType_Key && e->Key.Key != arg)
1366 continue;
1367 if (type == ImGuiInputEventType_MouseButton && e->MouseButton.Button != arg)
1368 continue;
1369 return e;
1370 }
1371 return NULL;
1372}
1373
1374// Queue a new key down/up event.
1375// - ImGuiKey key: Translated key (as in, generally ImGuiKey_A matches the key end-user would use to emit an 'A' character)
1376// - bool down: Is the key down? use false to signify a key release.
1377// - float analog_value: 0.0f..1.0f
1378void ImGuiIO::AddKeyAnalogEvent(ImGuiKey key, bool down, float analog_value)
1379{
1380 //if (e->Down) { IMGUI_DEBUG_LOG_IO("AddKeyEvent() Key='%s' %d, NativeKeycode = %d, NativeScancode = %d\n", ImGui::GetKeyName(e->Key), e->Down, e->NativeKeycode, e->NativeScancode); }
1381 if (key == ImGuiKey_None || !AppAcceptingEvents)
1382 return;
1383 ImGuiContext& g = *GImGui;
1384 IM_ASSERT(&g.IO == this && "Can only add events to current context.");
1385 IM_ASSERT(ImGui::IsNamedKeyOrModKey(key)); // Backend needs to pass a valid ImGuiKey_ constant. 0..511 values are legacy native key codes which are not accepted by this API.
1386 IM_ASSERT(!ImGui::IsAliasKey(key)); // Backend cannot submit ImGuiKey_MouseXXX values they are automatically inferred from AddMouseXXX() events.
1387 IM_ASSERT(key != ImGuiMod_Shortcut); // We could easily support the translation here but it seems saner to not accept it (TestEngine perform a translation itself)
1388
1389 // Verify that backend isn't mixing up using new io.AddKeyEvent() api and old io.KeysDown[] + io.KeyMap[] data.
1390#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO
1391 IM_ASSERT((BackendUsingLegacyKeyArrays == -1 || BackendUsingLegacyKeyArrays == 0) && "Backend needs to either only use io.AddKeyEvent(), either only fill legacy io.KeysDown[] + io.KeyMap[]. Not both!");
1392 if (BackendUsingLegacyKeyArrays == -1)
1393 for (int n = ImGuiKey_NamedKey_BEGIN; n < ImGuiKey_NamedKey_END; n++)
1394 IM_ASSERT(KeyMap[n] == -1 && "Backend needs to either only use io.AddKeyEvent(), either only fill legacy io.KeysDown[] + io.KeyMap[]. Not both!");
1395 BackendUsingLegacyKeyArrays = 0;
1396#endif
1397 if (ImGui::IsGamepadKey(key))
1398 BackendUsingLegacyNavInputArray = false;
1399
1400 // Filter duplicate (in particular: key mods and gamepad analog values are commonly spammed)
1401 const ImGuiInputEvent* latest_event = FindLatestInputEvent(type: ImGuiInputEventType_Key, arg: (int)key);
1402 const ImGuiKeyData* key_data = ImGui::GetKeyData(key);
1403 const bool latest_key_down = latest_event ? latest_event->Key.Down : key_data->Down;
1404 const float latest_key_analog = latest_event ? latest_event->Key.AnalogValue : key_data->AnalogValue;
1405 if (latest_key_down == down && latest_key_analog == analog_value)
1406 return;
1407
1408 // Add event
1409 ImGuiInputEvent e;
1410 e.Type = ImGuiInputEventType_Key;
1411 e.Source = ImGui::IsGamepadKey(key) ? ImGuiInputSource_Gamepad : ImGuiInputSource_Keyboard;
1412 e.Key.Key = key;
1413 e.Key.Down = down;
1414 e.Key.AnalogValue = analog_value;
1415 g.InputEventsQueue.push_back(v: e);
1416}
1417
1418void ImGuiIO::AddKeyEvent(ImGuiKey key, bool down)
1419{
1420 if (!AppAcceptingEvents)
1421 return;
1422 AddKeyAnalogEvent(key, down, analog_value: down ? 1.0f : 0.0f);
1423}
1424
1425// [Optional] Call after AddKeyEvent().
1426// Specify native keycode, scancode + Specify index for legacy <1.87 IsKeyXXX() functions with native indices.
1427// If you are writing a backend in 2022 or don't use IsKeyXXX() with native values that are not ImGuiKey values, you can avoid calling this.
1428void ImGuiIO::SetKeyEventNativeData(ImGuiKey key, int native_keycode, int native_scancode, int native_legacy_index)
1429{
1430 if (key == ImGuiKey_None)
1431 return;
1432 IM_ASSERT(ImGui::IsNamedKey(key)); // >= 512
1433 IM_ASSERT(native_legacy_index == -1 || ImGui::IsLegacyKey((ImGuiKey)native_legacy_index)); // >= 0 && <= 511
1434 IM_UNUSED(native_keycode); // Yet unused
1435 IM_UNUSED(native_scancode); // Yet unused
1436
1437 // Build native->imgui map so old user code can still call key functions with native 0..511 values.
1438#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO
1439 const int legacy_key = (native_legacy_index != -1) ? native_legacy_index : native_keycode;
1440 if (!ImGui::IsLegacyKey(key: (ImGuiKey)legacy_key))
1441 return;
1442 KeyMap[legacy_key] = key;
1443 KeyMap[key] = legacy_key;
1444#else
1445 IM_UNUSED(key);
1446 IM_UNUSED(native_legacy_index);
1447#endif
1448}
1449
1450// Set master flag for accepting key/mouse/text events (default to true). Useful if you have native dialog boxes that are interrupting your application loop/refresh, and you want to disable events being queued while your app is frozen.
1451void ImGuiIO::SetAppAcceptingEvents(bool accepting_events)
1452{
1453 AppAcceptingEvents = accepting_events;
1454}
1455
1456// Queue a mouse move event
1457void ImGuiIO::AddMousePosEvent(float x, float y)
1458{
1459 ImGuiContext& g = *GImGui;
1460 IM_ASSERT(&g.IO == this && "Can only add events to current context.");
1461 if (!AppAcceptingEvents)
1462 return;
1463
1464 // Apply same flooring as UpdateMouseInputs()
1465 ImVec2 pos((x > -FLT_MAX) ? ImFloorSigned(f: x) : x, (y > -FLT_MAX) ? ImFloorSigned(f: y) : y);
1466
1467 // Filter duplicate
1468 const ImGuiInputEvent* latest_event = FindLatestInputEvent(type: ImGuiInputEventType_MousePos);
1469 const ImVec2 latest_pos = latest_event ? ImVec2(latest_event->MousePos.PosX, latest_event->MousePos.PosY) : g.IO.MousePos;
1470 if (latest_pos.x == pos.x && latest_pos.y == pos.y)
1471 return;
1472
1473 ImGuiInputEvent e;
1474 e.Type = ImGuiInputEventType_MousePos;
1475 e.Source = ImGuiInputSource_Mouse;
1476 e.MousePos.PosX = pos.x;
1477 e.MousePos.PosY = pos.y;
1478 g.InputEventsQueue.push_back(v: e);
1479}
1480
1481void ImGuiIO::AddMouseButtonEvent(int mouse_button, bool down)
1482{
1483 ImGuiContext& g = *GImGui;
1484 IM_ASSERT(&g.IO == this && "Can only add events to current context.");
1485 IM_ASSERT(mouse_button >= 0 && mouse_button < ImGuiMouseButton_COUNT);
1486 if (!AppAcceptingEvents)
1487 return;
1488
1489 // Filter duplicate
1490 const ImGuiInputEvent* latest_event = FindLatestInputEvent(type: ImGuiInputEventType_MouseButton, arg: (int)mouse_button);
1491 const bool latest_button_down = latest_event ? latest_event->MouseButton.Down : g.IO.MouseDown[mouse_button];
1492 if (latest_button_down == down)
1493 return;
1494
1495 ImGuiInputEvent e;
1496 e.Type = ImGuiInputEventType_MouseButton;
1497 e.Source = ImGuiInputSource_Mouse;
1498 e.MouseButton.Button = mouse_button;
1499 e.MouseButton.Down = down;
1500 g.InputEventsQueue.push_back(v: e);
1501}
1502
1503// Queue a mouse wheel event (most mouse/API will only have a Y component)
1504void ImGuiIO::AddMouseWheelEvent(float wheel_x, float wheel_y)
1505{
1506 ImGuiContext& g = *GImGui;
1507 IM_ASSERT(&g.IO == this && "Can only add events to current context.");
1508
1509 // Filter duplicate (unlike most events, wheel values are relative and easy to filter)
1510 if (!AppAcceptingEvents || (wheel_x == 0.0f && wheel_y == 0.0f))
1511 return;
1512
1513 ImGuiInputEvent e;
1514 e.Type = ImGuiInputEventType_MouseWheel;
1515 e.Source = ImGuiInputSource_Mouse;
1516 e.MouseWheel.WheelX = wheel_x;
1517 e.MouseWheel.WheelY = wheel_y;
1518 g.InputEventsQueue.push_back(v: e);
1519}
1520
1521void ImGuiIO::AddMouseViewportEvent(ImGuiID viewport_id)
1522{
1523 ImGuiContext& g = *GImGui;
1524 IM_ASSERT(&g.IO == this && "Can only add events to current context.");
1525 IM_ASSERT(g.IO.BackendFlags & ImGuiBackendFlags_HasMouseHoveredViewport);
1526 if (!AppAcceptingEvents)
1527 return;
1528
1529 // Filter duplicate
1530 const ImGuiInputEvent* latest_event = FindLatestInputEvent(type: ImGuiInputEventType_MouseViewport);
1531 const ImGuiID latest_viewport_id = latest_event ? latest_event->MouseViewport.HoveredViewportID : g.IO.MouseHoveredViewport;
1532 if (latest_viewport_id == viewport_id)
1533 return;
1534
1535 ImGuiInputEvent e;
1536 e.Type = ImGuiInputEventType_MouseViewport;
1537 e.Source = ImGuiInputSource_Mouse;
1538 e.MouseViewport.HoveredViewportID = viewport_id;
1539 g.InputEventsQueue.push_back(v: e);
1540}
1541
1542void ImGuiIO::AddFocusEvent(bool focused)
1543{
1544 ImGuiContext& g = *GImGui;
1545 IM_ASSERT(&g.IO == this && "Can only add events to current context.");
1546
1547 // Filter duplicate
1548 const ImGuiInputEvent* latest_event = FindLatestInputEvent(type: ImGuiInputEventType_Focus);
1549 const bool latest_focused = latest_event ? latest_event->AppFocused.Focused : !g.IO.AppFocusLost;
1550 if (latest_focused == focused)
1551 return;
1552
1553 ImGuiInputEvent e;
1554 e.Type = ImGuiInputEventType_Focus;
1555 e.AppFocused.Focused = focused;
1556 g.InputEventsQueue.push_back(v: e);
1557}
1558
1559//-----------------------------------------------------------------------------
1560// [SECTION] MISC HELPERS/UTILITIES (Geometry functions)
1561//-----------------------------------------------------------------------------
1562
1563ImVec2 ImBezierCubicClosestPoint(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& p, int num_segments)
1564{
1565 IM_ASSERT(num_segments > 0); // Use ImBezierCubicClosestPointCasteljau()
1566 ImVec2 p_last = p1;
1567 ImVec2 p_closest;
1568 float p_closest_dist2 = FLT_MAX;
1569 float t_step = 1.0f / (float)num_segments;
1570 for (int i_step = 1; i_step <= num_segments; i_step++)
1571 {
1572 ImVec2 p_current = ImBezierCubicCalc(p1, p2, p3, p4, t: t_step * i_step);
1573 ImVec2 p_line = ImLineClosestPoint(a: p_last, b: p_current, p);
1574 float dist2 = ImLengthSqr(lhs: p - p_line);
1575 if (dist2 < p_closest_dist2)
1576 {
1577 p_closest = p_line;
1578 p_closest_dist2 = dist2;
1579 }
1580 p_last = p_current;
1581 }
1582 return p_closest;
1583}
1584
1585// Closely mimics PathBezierToCasteljau() in imgui_draw.cpp
1586static 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)
1587{
1588 float dx = x4 - x1;
1589 float dy = y4 - y1;
1590 float d2 = ((x2 - x4) * dy - (y2 - y4) * dx);
1591 float d3 = ((x3 - x4) * dy - (y3 - y4) * dx);
1592 d2 = (d2 >= 0) ? d2 : -d2;
1593 d3 = (d3 >= 0) ? d3 : -d3;
1594 if ((d2 + d3) * (d2 + d3) < tess_tol * (dx * dx + dy * dy))
1595 {
1596 ImVec2 p_current(x4, y4);
1597 ImVec2 p_line = ImLineClosestPoint(a: p_last, b: p_current, p);
1598 float dist2 = ImLengthSqr(lhs: p - p_line);
1599 if (dist2 < p_closest_dist2)
1600 {
1601 p_closest = p_line;
1602 p_closest_dist2 = dist2;
1603 }
1604 p_last = p_current;
1605 }
1606 else if (level < 10)
1607 {
1608 float x12 = (x1 + x2)*0.5f, y12 = (y1 + y2)*0.5f;
1609 float x23 = (x2 + x3)*0.5f, y23 = (y2 + y3)*0.5f;
1610 float x34 = (x3 + x4)*0.5f, y34 = (y3 + y4)*0.5f;
1611 float x123 = (x12 + x23)*0.5f, y123 = (y12 + y23)*0.5f;
1612 float x234 = (x23 + x34)*0.5f, y234 = (y23 + y34)*0.5f;
1613 float x1234 = (x123 + x234)*0.5f, y1234 = (y123 + y234)*0.5f;
1614 ImBezierCubicClosestPointCasteljauStep(p, p_closest, p_last, p_closest_dist2, x1, y1, x2: x12, y2: y12, x3: x123, y3: y123, x4: x1234, y4: y1234, tess_tol, level: level + 1);
1615 ImBezierCubicClosestPointCasteljauStep(p, p_closest, p_last, p_closest_dist2, x1: x1234, y1: y1234, x2: x234, y2: y234, x3: x34, y3: y34, x4, y4, tess_tol, level: level + 1);
1616 }
1617}
1618
1619// tess_tol is generally the same value you would find in ImGui::GetStyle().CurveTessellationTol
1620// Because those ImXXX functions are lower-level than ImGui:: we cannot access this value automatically.
1621ImVec2 ImBezierCubicClosestPointCasteljau(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& p, float tess_tol)
1622{
1623 IM_ASSERT(tess_tol > 0.0f);
1624 ImVec2 p_last = p1;
1625 ImVec2 p_closest;
1626 float p_closest_dist2 = FLT_MAX;
1627 ImBezierCubicClosestPointCasteljauStep(p, p_closest, p_last, p_closest_dist2, x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y, x3: p3.x, y3: p3.y, x4: p4.x, y4: p4.y, tess_tol, level: 0);
1628 return p_closest;
1629}
1630
1631ImVec2 ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& p)
1632{
1633 ImVec2 ap = p - a;
1634 ImVec2 ab_dir = b - a;
1635 float dot = ap.x * ab_dir.x + ap.y * ab_dir.y;
1636 if (dot < 0.0f)
1637 return a;
1638 float ab_len_sqr = ab_dir.x * ab_dir.x + ab_dir.y * ab_dir.y;
1639 if (dot > ab_len_sqr)
1640 return b;
1641 return a + ab_dir * dot / ab_len_sqr;
1642}
1643
1644bool ImTriangleContainsPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p)
1645{
1646 bool b1 = ((p.x - b.x) * (a.y - b.y) - (p.y - b.y) * (a.x - b.x)) < 0.0f;
1647 bool b2 = ((p.x - c.x) * (b.y - c.y) - (p.y - c.y) * (b.x - c.x)) < 0.0f;
1648 bool b3 = ((p.x - a.x) * (c.y - a.y) - (p.y - a.y) * (c.x - a.x)) < 0.0f;
1649 return ((b1 == b2) && (b2 == b3));
1650}
1651
1652void ImTriangleBarycentricCoords(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p, float& out_u, float& out_v, float& out_w)
1653{
1654 ImVec2 v0 = b - a;
1655 ImVec2 v1 = c - a;
1656 ImVec2 v2 = p - a;
1657 const float denom = v0.x * v1.y - v1.x * v0.y;
1658 out_v = (v2.x * v1.y - v1.x * v2.y) / denom;
1659 out_w = (v0.x * v2.y - v2.x * v0.y) / denom;
1660 out_u = 1.0f - out_v - out_w;
1661}
1662
1663ImVec2 ImTriangleClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p)
1664{
1665 ImVec2 proj_ab = ImLineClosestPoint(a, b, p);
1666 ImVec2 proj_bc = ImLineClosestPoint(a: b, b: c, p);
1667 ImVec2 proj_ca = ImLineClosestPoint(a: c, b: a, p);
1668 float dist2_ab = ImLengthSqr(lhs: p - proj_ab);
1669 float dist2_bc = ImLengthSqr(lhs: p - proj_bc);
1670 float dist2_ca = ImLengthSqr(lhs: p - proj_ca);
1671 float m = ImMin(lhs: dist2_ab, rhs: ImMin(lhs: dist2_bc, rhs: dist2_ca));
1672 if (m == dist2_ab)
1673 return proj_ab;
1674 if (m == dist2_bc)
1675 return proj_bc;
1676 return proj_ca;
1677}
1678
1679//-----------------------------------------------------------------------------
1680// [SECTION] MISC HELPERS/UTILITIES (String, Format, Hash functions)
1681//-----------------------------------------------------------------------------
1682
1683// Consider using _stricmp/_strnicmp under Windows or strcasecmp/strncasecmp. We don't actually use either ImStricmp/ImStrnicmp in the codebase any more.
1684int ImStricmp(const char* str1, const char* str2)
1685{
1686 int d;
1687 while ((d = ImToUpper(c: *str2) - ImToUpper(c: *str1)) == 0 && *str1) { str1++; str2++; }
1688 return d;
1689}
1690
1691int ImStrnicmp(const char* str1, const char* str2, size_t count)
1692{
1693 int d = 0;
1694 while (count > 0 && (d = ImToUpper(c: *str2) - ImToUpper(c: *str1)) == 0 && *str1) { str1++; str2++; count--; }
1695 return d;
1696}
1697
1698void ImStrncpy(char* dst, const char* src, size_t count)
1699{
1700 if (count < 1)
1701 return;
1702 if (count > 1)
1703 strncpy(dest: dst, src: src, n: count - 1);
1704 dst[count - 1] = 0;
1705}
1706
1707char* ImStrdup(const char* str)
1708{
1709 size_t len = strlen(s: str);
1710 void* buf = IM_ALLOC(len + 1);
1711 return (char*)memcpy(dest: buf, src: (const void*)str, n: len + 1);
1712}
1713
1714char* ImStrdupcpy(char* dst, size_t* p_dst_size, const char* src)
1715{
1716 size_t dst_buf_size = p_dst_size ? *p_dst_size : strlen(s: dst) + 1;
1717 size_t src_size = strlen(s: src) + 1;
1718 if (dst_buf_size < src_size)
1719 {
1720 IM_FREE(dst);
1721 dst = (char*)IM_ALLOC(src_size);
1722 if (p_dst_size)
1723 *p_dst_size = src_size;
1724 }
1725 return (char*)memcpy(dest: dst, src: (const void*)src, n: src_size);
1726}
1727
1728const char* ImStrchrRange(const char* str, const char* str_end, char c)
1729{
1730 const char* p = (const char*)memchr(s: str, c: (int)c, n: str_end - str);
1731 return p;
1732}
1733
1734int ImStrlenW(const ImWchar* str)
1735{
1736 //return (int)wcslen((const wchar_t*)str); // FIXME-OPT: Could use this when wchar_t are 16-bit
1737 int n = 0;
1738 while (*str++) n++;
1739 return n;
1740}
1741
1742// Find end-of-line. Return pointer will point to either first \n, either str_end.
1743const char* ImStreolRange(const char* str, const char* str_end)
1744{
1745 const char* p = (const char*)memchr(s: str, c: '\n', n: str_end - str);
1746 return p ? p : str_end;
1747}
1748
1749const ImWchar* ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* buf_begin) // find beginning-of-line
1750{
1751 while (buf_mid_line > buf_begin && buf_mid_line[-1] != '\n')
1752 buf_mid_line--;
1753 return buf_mid_line;
1754}
1755
1756const char* ImStristr(const char* haystack, const char* haystack_end, const char* needle, const char* needle_end)
1757{
1758 if (!needle_end)
1759 needle_end = needle + strlen(s: needle);
1760
1761 const char un0 = (char)ImToUpper(c: *needle);
1762 while ((!haystack_end && *haystack) || (haystack_end && haystack < haystack_end))
1763 {
1764 if (ImToUpper(c: *haystack) == un0)
1765 {
1766 const char* b = needle + 1;
1767 for (const char* a = haystack + 1; b < needle_end; a++, b++)
1768 if (ImToUpper(c: *a) != ImToUpper(c: *b))
1769 break;
1770 if (b == needle_end)
1771 return haystack;
1772 }
1773 haystack++;
1774 }
1775 return NULL;
1776}
1777
1778// 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.
1779void ImStrTrimBlanks(char* buf)
1780{
1781 char* p = buf;
1782 while (p[0] == ' ' || p[0] == '\t') // Leading blanks
1783 p++;
1784 char* p_start = p;
1785 while (*p != 0) // Find end of string
1786 p++;
1787 while (p > p_start && (p[-1] == ' ' || p[-1] == '\t')) // Trailing blanks
1788 p--;
1789 if (p_start != buf) // Copy memory if we had leading blanks
1790 memmove(dest: buf, src: p_start, n: p - p_start);
1791 buf[p - p_start] = 0; // Zero terminate
1792}
1793
1794const char* ImStrSkipBlank(const char* str)
1795{
1796 while (str[0] == ' ' || str[0] == '\t')
1797 str++;
1798 return str;
1799}
1800
1801// A) MSVC version appears to return -1 on overflow, whereas glibc appears to return total count (which may be >= buf_size).
1802// 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.
1803// B) When buf==NULL vsnprintf() will return the output size.
1804#ifndef IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS
1805
1806// We support stb_sprintf which is much faster (see: https://github.com/nothings/stb/blob/master/stb_sprintf.h)
1807// You may set IMGUI_USE_STB_SPRINTF to use our default wrapper, or set IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS
1808// and setup the wrapper yourself. (FIXME-OPT: Some of our high-level operations such as ImGuiTextBuffer::appendfv() are
1809// designed using two-passes worst case, which probably could be improved using the stbsp_vsprintfcb() function.)
1810#ifdef IMGUI_USE_STB_SPRINTF
1811#define STB_SPRINTF_IMPLEMENTATION
1812#ifdef IMGUI_STB_SPRINTF_FILENAME
1813#include IMGUI_STB_SPRINTF_FILENAME
1814#else
1815#include "stb_sprintf.h"
1816#endif
1817#endif
1818
1819#if defined(_MSC_VER) && !defined(vsnprintf)
1820#define vsnprintf _vsnprintf
1821#endif
1822
1823int ImFormatString(char* buf, size_t buf_size, const char* fmt, ...)
1824{
1825 va_list args;
1826 va_start(args, fmt);
1827#ifdef IMGUI_USE_STB_SPRINTF
1828 int w = stbsp_vsnprintf(buf, (int)buf_size, fmt, args);
1829#else
1830 int w = vsnprintf(s: buf, maxlen: buf_size, format: fmt, arg: args);
1831#endif
1832 va_end(args);
1833 if (buf == NULL)
1834 return w;
1835 if (w == -1 || w >= (int)buf_size)
1836 w = (int)buf_size - 1;
1837 buf[w] = 0;
1838 return w;
1839}
1840
1841int ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args)
1842{
1843#ifdef IMGUI_USE_STB_SPRINTF
1844 int w = stbsp_vsnprintf(buf, (int)buf_size, fmt, args);
1845#else
1846 int w = vsnprintf(s: buf, maxlen: buf_size, format: fmt, arg: args);
1847#endif
1848 if (buf == NULL)
1849 return w;
1850 if (w == -1 || w >= (int)buf_size)
1851 w = (int)buf_size - 1;
1852 buf[w] = 0;
1853 return w;
1854}
1855#endif // #ifdef IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS
1856
1857void ImFormatStringToTempBuffer(const char** out_buf, const char** out_buf_end, const char* fmt, ...)
1858{
1859 ImGuiContext& g = *GImGui;
1860 va_list args;
1861 va_start(args, fmt);
1862 int buf_len = ImFormatStringV(buf: g.TempBuffer.Data, buf_size: g.TempBuffer.Size, fmt, args);
1863 *out_buf = g.TempBuffer.Data;
1864 if (out_buf_end) { *out_buf_end = g.TempBuffer.Data + buf_len; }
1865 va_end(args);
1866}
1867
1868void ImFormatStringToTempBufferV(const char** out_buf, const char** out_buf_end, const char* fmt, va_list args)
1869{
1870 ImGuiContext& g = *GImGui;
1871 int buf_len = ImFormatStringV(buf: g.TempBuffer.Data, buf_size: g.TempBuffer.Size, fmt, args);
1872 *out_buf = g.TempBuffer.Data;
1873 if (out_buf_end) { *out_buf_end = g.TempBuffer.Data + buf_len; }
1874}
1875
1876// CRC32 needs a 1KB lookup table (not cache friendly)
1877// Although the code to generate the table is simple and shorter than the table itself, using a const table allows us to easily:
1878// - avoid an unnecessary branch/memory tap, - keep the ImHashXXX functions usable by static constructors, - make it thread-safe.
1879static const ImU32 GCrc32LookupTable[256] =
1880{
1881 0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F,0xE963A535,0x9E6495A3,0x0EDB8832,0x79DCB8A4,0xE0D5E91E,0x97D2D988,0x09B64C2B,0x7EB17CBD,0xE7B82D07,0x90BF1D91,
1882 0x1DB71064,0x6AB020F2,0xF3B97148,0x84BE41DE,0x1ADAD47D,0x6DDDE4EB,0xF4D4B551,0x83D385C7,0x136C9856,0x646BA8C0,0xFD62F97A,0x8A65C9EC,0x14015C4F,0x63066CD9,0xFA0F3D63,0x8D080DF5,
1883 0x3B6E20C8,0x4C69105E,0xD56041E4,0xA2677172,0x3C03E4D1,0x4B04D447,0xD20D85FD,0xA50AB56B,0x35B5A8FA,0x42B2986C,0xDBBBC9D6,0xACBCF940,0x32D86CE3,0x45DF5C75,0xDCD60DCF,0xABD13D59,
1884 0x26D930AC,0x51DE003A,0xC8D75180,0xBFD06116,0x21B4F4B5,0x56B3C423,0xCFBA9599,0xB8BDA50F,0x2802B89E,0x5F058808,0xC60CD9B2,0xB10BE924,0x2F6F7C87,0x58684C11,0xC1611DAB,0xB6662D3D,
1885 0x76DC4190,0x01DB7106,0x98D220BC,0xEFD5102A,0x71B18589,0x06B6B51F,0x9FBFE4A5,0xE8B8D433,0x7807C9A2,0x0F00F934,0x9609A88E,0xE10E9818,0x7F6A0DBB,0x086D3D2D,0x91646C97,0xE6635C01,
1886 0x6B6B51F4,0x1C6C6162,0x856530D8,0xF262004E,0x6C0695ED,0x1B01A57B,0x8208F4C1,0xF50FC457,0x65B0D9C6,0x12B7E950,0x8BBEB8EA,0xFCB9887C,0x62DD1DDF,0x15DA2D49,0x8CD37CF3,0xFBD44C65,
1887 0x4DB26158,0x3AB551CE,0xA3BC0074,0xD4BB30E2,0x4ADFA541,0x3DD895D7,0xA4D1C46D,0xD3D6F4FB,0x4369E96A,0x346ED9FC,0xAD678846,0xDA60B8D0,0x44042D73,0x33031DE5,0xAA0A4C5F,0xDD0D7CC9,
1888 0x5005713C,0x270241AA,0xBE0B1010,0xC90C2086,0x5768B525,0x206F85B3,0xB966D409,0xCE61E49F,0x5EDEF90E,0x29D9C998,0xB0D09822,0xC7D7A8B4,0x59B33D17,0x2EB40D81,0xB7BD5C3B,0xC0BA6CAD,
1889 0xEDB88320,0x9ABFB3B6,0x03B6E20C,0x74B1D29A,0xEAD54739,0x9DD277AF,0x04DB2615,0x73DC1683,0xE3630B12,0x94643B84,0x0D6D6A3E,0x7A6A5AA8,0xE40ECF0B,0x9309FF9D,0x0A00AE27,0x7D079EB1,
1890 0xF00F9344,0x8708A3D2,0x1E01F268,0x6906C2FE,0xF762575D,0x806567CB,0x196C3671,0x6E6B06E7,0xFED41B76,0x89D32BE0,0x10DA7A5A,0x67DD4ACC,0xF9B9DF6F,0x8EBEEFF9,0x17B7BE43,0x60B08ED5,
1891 0xD6D6A3E8,0xA1D1937E,0x38D8C2C4,0x4FDFF252,0xD1BB67F1,0xA6BC5767,0x3FB506DD,0x48B2364B,0xD80D2BDA,0xAF0A1B4C,0x36034AF6,0x41047A60,0xDF60EFC3,0xA867DF55,0x316E8EEF,0x4669BE79,
1892 0xCB61B38C,0xBC66831A,0x256FD2A0,0x5268E236,0xCC0C7795,0xBB0B4703,0x220216B9,0x5505262F,0xC5BA3BBE,0xB2BD0B28,0x2BB45A92,0x5CB36A04,0xC2D7FFA7,0xB5D0CF31,0x2CD99E8B,0x5BDEAE1D,
1893 0x9B64C2B0,0xEC63F226,0x756AA39C,0x026D930A,0x9C0906A9,0xEB0E363F,0x72076785,0x05005713,0x95BF4A82,0xE2B87A14,0x7BB12BAE,0x0CB61B38,0x92D28E9B,0xE5D5BE0D,0x7CDCEFB7,0x0BDBDF21,
1894 0x86D3D2D4,0xF1D4E242,0x68DDB3F8,0x1FDA836E,0x81BE16CD,0xF6B9265B,0x6FB077E1,0x18B74777,0x88085AE6,0xFF0F6A70,0x66063BCA,0x11010B5C,0x8F659EFF,0xF862AE69,0x616BFFD3,0x166CCF45,
1895 0xA00AE278,0xD70DD2EE,0x4E048354,0x3903B3C2,0xA7672661,0xD06016F7,0x4969474D,0x3E6E77DB,0xAED16A4A,0xD9D65ADC,0x40DF0B66,0x37D83BF0,0xA9BCAE53,0xDEBB9EC5,0x47B2CF7F,0x30B5FFE9,
1896 0xBDBDF21C,0xCABAC28A,0x53B39330,0x24B4A3A6,0xBAD03605,0xCDD70693,0x54DE5729,0x23D967BF,0xB3667A2E,0xC4614AB8,0x5D681B02,0x2A6F2B94,0xB40BBE37,0xC30C8EA1,0x5A05DF1B,0x2D02EF8D,
1897};
1898
1899// Known size hash
1900// It is ok to call ImHashData on a string with known length but the ### operator won't be supported.
1901// FIXME-OPT: Replace with e.g. FNV1a hash? CRC32 pretty much randomly access 1KB. Need to do proper measurements.
1902ImGuiID ImHashData(const void* data_p, size_t data_size, ImU32 seed)
1903{
1904 ImU32 crc = ~seed;
1905 const unsigned char* data = (const unsigned char*)data_p;
1906 const ImU32* crc32_lut = GCrc32LookupTable;
1907 while (data_size-- != 0)
1908 crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ *data++];
1909 return ~crc;
1910}
1911
1912// Zero-terminated string hash, with support for ### to reset back to seed value
1913// We support a syntax of "label###id" where only "###id" is included in the hash, and only "label" gets displayed.
1914// Because this syntax is rarely used we are optimizing for the common case.
1915// - If we reach ### in the string we discard the hash so far and reset to the seed.
1916// - We don't do 'current += 2; continue;' after handling ### to keep the code smaller/faster (measured ~10% diff in Debug build)
1917// FIXME-OPT: Replace with e.g. FNV1a hash? CRC32 pretty much randomly access 1KB. Need to do proper measurements.
1918ImGuiID ImHashStr(const char* data_p, size_t data_size, ImU32 seed)
1919{
1920 seed = ~seed;
1921 ImU32 crc = seed;
1922 const unsigned char* data = (const unsigned char*)data_p;
1923 const ImU32* crc32_lut = GCrc32LookupTable;
1924 if (data_size != 0)
1925 {
1926 while (data_size-- != 0)
1927 {
1928 unsigned char c = *data++;
1929 if (c == '#' && data_size >= 2 && data[0] == '#' && data[1] == '#')
1930 crc = seed;
1931 crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c];
1932 }
1933 }
1934 else
1935 {
1936 while (unsigned char c = *data++)
1937 {
1938 if (c == '#' && data[0] == '#' && data[1] == '#')
1939 crc = seed;
1940 crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c];
1941 }
1942 }
1943 return ~crc;
1944}
1945
1946//-----------------------------------------------------------------------------
1947// [SECTION] MISC HELPERS/UTILITIES (File functions)
1948//-----------------------------------------------------------------------------
1949
1950// Default file functions
1951#ifndef IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS
1952
1953ImFileHandle ImFileOpen(const char* filename, const char* mode)
1954{
1955#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && !defined(__CYGWIN__) && !defined(__GNUC__)
1956 // We need a fopen() wrapper because MSVC/Windows fopen doesn't handle UTF-8 filenames.
1957 // Previously we used ImTextCountCharsFromUtf8/ImTextStrFromUtf8 here but we now need to support ImWchar16 and ImWchar32!
1958 const int filename_wsize = ::MultiByteToWideChar(CP_UTF8, 0, filename, -1, NULL, 0);
1959 const int mode_wsize = ::MultiByteToWideChar(CP_UTF8, 0, mode, -1, NULL, 0);
1960 ImVector<ImWchar> buf;
1961 buf.resize(filename_wsize + mode_wsize);
1962 ::MultiByteToWideChar(CP_UTF8, 0, filename, -1, (wchar_t*)&buf[0], filename_wsize);
1963 ::MultiByteToWideChar(CP_UTF8, 0, mode, -1, (wchar_t*)&buf[filename_wsize], mode_wsize);
1964 return ::_wfopen((const wchar_t*)&buf[0], (const wchar_t*)&buf[filename_wsize]);
1965#else
1966 return fopen(filename: filename, modes: mode);
1967#endif
1968}
1969
1970// 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.
1971bool ImFileClose(ImFileHandle f) { return fclose(stream: f) == 0; }
1972ImU64 ImFileGetSize(ImFileHandle f) { long off = 0, sz = 0; return ((off = ftell(stream: f)) != -1 && !fseek(stream: f, off: 0, SEEK_END) && (sz = ftell(stream: f)) != -1 && !fseek(stream: f, off: off, SEEK_SET)) ? (ImU64)sz : (ImU64)-1; }
1973ImU64 ImFileRead(void* data, ImU64 sz, ImU64 count, ImFileHandle f) { return fread(ptr: data, size: (size_t)sz, n: (size_t)count, stream: f); }
1974ImU64 ImFileWrite(const void* data, ImU64 sz, ImU64 count, ImFileHandle f) { return fwrite(ptr: data, size: (size_t)sz, n: (size_t)count, s: f); }
1975#endif // #ifndef IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS
1976
1977// Helper: Load file content into memory
1978// Memory allocated with IM_ALLOC(), must be freed by user using IM_FREE() == ImGui::MemFree()
1979// This can't really be used with "rt" because fseek size won't match read size.
1980void* ImFileLoadToMemory(const char* filename, const char* mode, size_t* out_file_size, int padding_bytes)
1981{
1982 IM_ASSERT(filename && mode);
1983 if (out_file_size)
1984 *out_file_size = 0;
1985
1986 ImFileHandle f;
1987 if ((f = ImFileOpen(filename, mode)) == NULL)
1988 return NULL;
1989
1990 size_t file_size = (size_t)ImFileGetSize(f);
1991 if (file_size == (size_t)-1)
1992 {
1993 ImFileClose(f);
1994 return NULL;
1995 }
1996
1997 void* file_data = IM_ALLOC(file_size + padding_bytes);
1998 if (file_data == NULL)
1999 {
2000 ImFileClose(f);
2001 return NULL;
2002 }
2003 if (ImFileRead(data: file_data, sz: 1, count: file_size, f) != file_size)
2004 {
2005 ImFileClose(f);
2006 IM_FREE(file_data);
2007 return NULL;
2008 }
2009 if (padding_bytes > 0)
2010 memset(s: (void*)(((char*)file_data) + file_size), c: 0, n: (size_t)padding_bytes);
2011
2012 ImFileClose(f);
2013 if (out_file_size)
2014 *out_file_size = file_size;
2015
2016 return file_data;
2017}
2018
2019//-----------------------------------------------------------------------------
2020// [SECTION] MISC HELPERS/UTILITIES (ImText* functions)
2021//-----------------------------------------------------------------------------
2022
2023// Convert UTF-8 to 32-bit character, process single character input.
2024// A nearly-branchless UTF-8 decoder, based on work of Christopher Wellons (https://github.com/skeeto/branchless-utf8).
2025// We handle UTF-8 decoding error by skipping forward.
2026int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* in_text_end)
2027{
2028 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 };
2029 static const int masks[] = { 0x00, 0x7f, 0x1f, 0x0f, 0x07 };
2030 static const uint32_t mins[] = { 0x400000, 0, 0x80, 0x800, 0x10000 };
2031 static const int shiftc[] = { 0, 18, 12, 6, 0 };
2032 static const int shifte[] = { 0, 6, 4, 2, 0 };
2033 int len = lengths[*(const unsigned char*)in_text >> 3];
2034 int wanted = len + !len;
2035
2036 if (in_text_end == NULL)
2037 in_text_end = in_text + wanted; // Max length, nulls will be taken into account.
2038
2039 // Copy at most 'len' bytes, stop copying at 0 or past in_text_end. Branch predictor does a good job here,
2040 // so it is fast even with excessive branching.
2041 unsigned char s[4];
2042 s[0] = in_text + 0 < in_text_end ? in_text[0] : 0;
2043 s[1] = in_text + 1 < in_text_end ? in_text[1] : 0;
2044 s[2] = in_text + 2 < in_text_end ? in_text[2] : 0;
2045 s[3] = in_text + 3 < in_text_end ? in_text[3] : 0;
2046
2047 // Assume a four-byte character and load four bytes. Unused bits are shifted out.
2048 *out_char = (uint32_t)(s[0] & masks[len]) << 18;
2049 *out_char |= (uint32_t)(s[1] & 0x3f) << 12;
2050 *out_char |= (uint32_t)(s[2] & 0x3f) << 6;
2051 *out_char |= (uint32_t)(s[3] & 0x3f) << 0;
2052 *out_char >>= shiftc[len];
2053
2054 // Accumulate the various error conditions.
2055 int e = 0;
2056 e = (*out_char < mins[len]) << 6; // non-canonical encoding
2057 e |= ((*out_char >> 11) == 0x1b) << 7; // surrogate half?
2058 e |= (*out_char > IM_UNICODE_CODEPOINT_MAX) << 8; // out of range?
2059 e |= (s[1] & 0xc0) >> 2;
2060 e |= (s[2] & 0xc0) >> 4;
2061 e |= (s[3] ) >> 6;
2062 e ^= 0x2a; // top two bits of each tail byte correct?
2063 e >>= shifte[len];
2064
2065 if (e)
2066 {
2067 // No bytes are consumed when *in_text == 0 || in_text == in_text_end.
2068 // One byte is consumed in case of invalid first byte of in_text.
2069 // All available bytes (at most `len` bytes) are consumed on incomplete/invalid second to last bytes.
2070 // Invalid or incomplete input may consume less bytes than wanted, therefore every byte has to be inspected in s.
2071 wanted = ImMin(lhs: wanted, rhs: !!s[0] + !!s[1] + !!s[2] + !!s[3]);
2072 *out_char = IM_UNICODE_CODEPOINT_INVALID;
2073 }
2074
2075 return wanted;
2076}
2077
2078int ImTextStrFromUtf8(ImWchar* buf, int buf_size, const char* in_text, const char* in_text_end, const char** in_text_remaining)
2079{
2080 ImWchar* buf_out = buf;
2081 ImWchar* buf_end = buf + buf_size;
2082 while (buf_out < buf_end - 1 && (!in_text_end || in_text < in_text_end) && *in_text)
2083 {
2084 unsigned int c;
2085 in_text += ImTextCharFromUtf8(out_char: &c, in_text, in_text_end);
2086 if (c == 0)
2087 break;
2088 *buf_out++ = (ImWchar)c;
2089 }
2090 *buf_out = 0;
2091 if (in_text_remaining)
2092 *in_text_remaining = in_text;
2093 return (int)(buf_out - buf);
2094}
2095
2096int ImTextCountCharsFromUtf8(const char* in_text, const char* in_text_end)
2097{
2098 int char_count = 0;
2099 while ((!in_text_end || in_text < in_text_end) && *in_text)
2100 {
2101 unsigned int c;
2102 in_text += ImTextCharFromUtf8(out_char: &c, in_text, in_text_end);
2103 if (c == 0)
2104 break;
2105 char_count++;
2106 }
2107 return char_count;
2108}
2109
2110// Based on stb_to_utf8() from github.com/nothings/stb/
2111static inline int ImTextCharToUtf8_inline(char* buf, int buf_size, unsigned int c)
2112{
2113 if (c < 0x80)
2114 {
2115 buf[0] = (char)c;
2116 return 1;
2117 }
2118 if (c < 0x800)
2119 {
2120 if (buf_size < 2) return 0;
2121 buf[0] = (char)(0xc0 + (c >> 6));
2122 buf[1] = (char)(0x80 + (c & 0x3f));
2123 return 2;
2124 }
2125 if (c < 0x10000)
2126 {
2127 if (buf_size < 3) return 0;
2128 buf[0] = (char)(0xe0 + (c >> 12));
2129 buf[1] = (char)(0x80 + ((c >> 6) & 0x3f));
2130 buf[2] = (char)(0x80 + ((c ) & 0x3f));
2131 return 3;
2132 }
2133 if (c <= 0x10FFFF)
2134 {
2135 if (buf_size < 4) return 0;
2136 buf[0] = (char)(0xf0 + (c >> 18));
2137 buf[1] = (char)(0x80 + ((c >> 12) & 0x3f));
2138 buf[2] = (char)(0x80 + ((c >> 6) & 0x3f));
2139 buf[3] = (char)(0x80 + ((c ) & 0x3f));
2140 return 4;
2141 }
2142 // Invalid code point, the max unicode is 0x10FFFF
2143 return 0;
2144}
2145
2146const char* ImTextCharToUtf8(char out_buf[5], unsigned int c)
2147{
2148 int count = ImTextCharToUtf8_inline(buf: out_buf, buf_size: 5, c);
2149 out_buf[count] = 0;
2150 return out_buf;
2151}
2152
2153// Not optimal but we very rarely use this function.
2154int ImTextCountUtf8BytesFromChar(const char* in_text, const char* in_text_end)
2155{
2156 unsigned int unused = 0;
2157 return ImTextCharFromUtf8(out_char: &unused, in_text, in_text_end);
2158}
2159
2160static inline int ImTextCountUtf8BytesFromChar(unsigned int c)
2161{
2162 if (c < 0x80) return 1;
2163 if (c < 0x800) return 2;
2164 if (c < 0x10000) return 3;
2165 if (c <= 0x10FFFF) return 4;
2166 return 3;
2167}
2168
2169int ImTextStrToUtf8(char* out_buf, int out_buf_size, const ImWchar* in_text, const ImWchar* in_text_end)
2170{
2171 char* buf_p = out_buf;
2172 const char* buf_end = out_buf + out_buf_size;
2173 while (buf_p < buf_end - 1 && (!in_text_end || in_text < in_text_end) && *in_text)
2174 {
2175 unsigned int c = (unsigned int)(*in_text++);
2176 if (c < 0x80)
2177 *buf_p++ = (char)c;
2178 else
2179 buf_p += ImTextCharToUtf8_inline(buf: buf_p, buf_size: (int)(buf_end - buf_p - 1), c);
2180 }
2181 *buf_p = 0;
2182 return (int)(buf_p - out_buf);
2183}
2184
2185int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_end)
2186{
2187 int bytes_count = 0;
2188 while ((!in_text_end || in_text < in_text_end) && *in_text)
2189 {
2190 unsigned int c = (unsigned int)(*in_text++);
2191 if (c < 0x80)
2192 bytes_count++;
2193 else
2194 bytes_count += ImTextCountUtf8BytesFromChar(c);
2195 }
2196 return bytes_count;
2197}
2198
2199//-----------------------------------------------------------------------------
2200// [SECTION] MISC HELPERS/UTILITIES (Color functions)
2201// Note: The Convert functions are early design which are not consistent with other API.
2202//-----------------------------------------------------------------------------
2203
2204IMGUI_API ImU32 ImAlphaBlendColors(ImU32 col_a, ImU32 col_b)
2205{
2206 float t = ((col_b >> IM_COL32_A_SHIFT) & 0xFF) / 255.f;
2207 int r = ImLerp(a: (int)(col_a >> IM_COL32_R_SHIFT) & 0xFF, b: (int)(col_b >> IM_COL32_R_SHIFT) & 0xFF, t);
2208 int g = ImLerp(a: (int)(col_a >> IM_COL32_G_SHIFT) & 0xFF, b: (int)(col_b >> IM_COL32_G_SHIFT) & 0xFF, t);
2209 int b = ImLerp(a: (int)(col_a >> IM_COL32_B_SHIFT) & 0xFF, b: (int)(col_b >> IM_COL32_B_SHIFT) & 0xFF, t);
2210 return IM_COL32(r, g, b, 0xFF);
2211}
2212
2213ImVec4 ImGui::ColorConvertU32ToFloat4(ImU32 in)
2214{
2215 float s = 1.0f / 255.0f;
2216 return ImVec4(
2217 ((in >> IM_COL32_R_SHIFT) & 0xFF) * s,
2218 ((in >> IM_COL32_G_SHIFT) & 0xFF) * s,
2219 ((in >> IM_COL32_B_SHIFT) & 0xFF) * s,
2220 ((in >> IM_COL32_A_SHIFT) & 0xFF) * s);
2221}
2222
2223ImU32 ImGui::ColorConvertFloat4ToU32(const ImVec4& in)
2224{
2225 ImU32 out;
2226 out = ((ImU32)IM_F32_TO_INT8_SAT(in.x)) << IM_COL32_R_SHIFT;
2227 out |= ((ImU32)IM_F32_TO_INT8_SAT(in.y)) << IM_COL32_G_SHIFT;
2228 out |= ((ImU32)IM_F32_TO_INT8_SAT(in.z)) << IM_COL32_B_SHIFT;
2229 out |= ((ImU32)IM_F32_TO_INT8_SAT(in.w)) << IM_COL32_A_SHIFT;
2230 return out;
2231}
2232
2233// Convert rgb floats ([0-1],[0-1],[0-1]) to hsv floats ([0-1],[0-1],[0-1]), from Foley & van Dam p592
2234// Optimized http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv
2235void ImGui::ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& out_s, float& out_v)
2236{
2237 float K = 0.f;
2238 if (g < b)
2239 {
2240 ImSwap(a&: g, b);
2241 K = -1.f;
2242 }
2243 if (r < g)
2244 {
2245 ImSwap(a&: r, b&: g);
2246 K = -2.f / 6.f - K;
2247 }
2248
2249 const float chroma = r - (g < b ? g : b);
2250 out_h = ImFabs(K + (g - b) / (6.f * chroma + 1e-20f));
2251 out_s = chroma / (r + 1e-20f);
2252 out_v = r;
2253}
2254
2255// Convert hsv floats ([0-1],[0-1],[0-1]) to rgb floats ([0-1],[0-1],[0-1]), from Foley & van Dam p593
2256// also http://en.wikipedia.org/wiki/HSL_and_HSV
2257void ImGui::ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& out_g, float& out_b)
2258{
2259 if (s == 0.0f)
2260 {
2261 // gray
2262 out_r = out_g = out_b = v;
2263 return;
2264 }
2265
2266 h = ImFmod(h, 1.0f) / (60.0f / 360.0f);
2267 int i = (int)h;
2268 float f = h - (float)i;
2269 float p = v * (1.0f - s);
2270 float q = v * (1.0f - s * f);
2271 float t = v * (1.0f - s * (1.0f - f));
2272
2273 switch (i)
2274 {
2275 case 0: out_r = v; out_g = t; out_b = p; break;
2276 case 1: out_r = q; out_g = v; out_b = p; break;
2277 case 2: out_r = p; out_g = v; out_b = t; break;
2278 case 3: out_r = p; out_g = q; out_b = v; break;
2279 case 4: out_r = t; out_g = p; out_b = v; break;
2280 case 5: default: out_r = v; out_g = p; out_b = q; break;
2281 }
2282}
2283
2284//-----------------------------------------------------------------------------
2285// [SECTION] ImGuiStorage
2286// Helper: Key->value storage
2287//-----------------------------------------------------------------------------
2288
2289// std::lower_bound but without the bullshit
2290static ImGuiStorage::ImGuiStoragePair* LowerBound(ImVector<ImGuiStorage::ImGuiStoragePair>& data, ImGuiID key)
2291{
2292 ImGuiStorage::ImGuiStoragePair* first = data.Data;
2293 ImGuiStorage::ImGuiStoragePair* last = data.Data + data.Size;
2294 size_t count = (size_t)(last - first);
2295 while (count > 0)
2296 {
2297 size_t count2 = count >> 1;
2298 ImGuiStorage::ImGuiStoragePair* mid = first + count2;
2299 if (mid->key < key)
2300 {
2301 first = ++mid;
2302 count -= count2 + 1;
2303 }
2304 else
2305 {
2306 count = count2;
2307 }
2308 }
2309 return first;
2310}
2311
2312// For quicker full rebuild of a storage (instead of an incremental one), you may add all your contents and then sort once.
2313void ImGuiStorage::BuildSortByKey()
2314{
2315 struct StaticFunc
2316 {
2317 static int IMGUI_CDECL PairComparerByID(const void* lhs, const void* rhs)
2318 {
2319 // We can't just do a subtraction because qsort uses signed integers and subtracting our ID doesn't play well with that.
2320 if (((const ImGuiStoragePair*)lhs)->key > ((const ImGuiStoragePair*)rhs)->key) return +1;
2321 if (((const ImGuiStoragePair*)lhs)->key < ((const ImGuiStoragePair*)rhs)->key) return -1;
2322 return 0;
2323 }
2324 };
2325 ImQsort(base: Data.Data, count: (size_t)Data.Size, size_of_element: sizeof(ImGuiStoragePair), compare_func: StaticFunc::PairComparerByID);
2326}
2327
2328int ImGuiStorage::GetInt(ImGuiID key, int default_val) const
2329{
2330 ImGuiStoragePair* it = LowerBound(data&: const_cast<ImVector<ImGuiStoragePair>&>(Data), key);
2331 if (it == Data.end() || it->key != key)
2332 return default_val;
2333 return it->val_i;
2334}
2335
2336bool ImGuiStorage::GetBool(ImGuiID key, bool default_val) const
2337{
2338 return GetInt(key, default_val: default_val ? 1 : 0) != 0;
2339}
2340
2341float ImGuiStorage::GetFloat(ImGuiID key, float default_val) const
2342{
2343 ImGuiStoragePair* it = LowerBound(data&: const_cast<ImVector<ImGuiStoragePair>&>(Data), key);
2344 if (it == Data.end() || it->key != key)
2345 return default_val;
2346 return it->val_f;
2347}
2348
2349void* ImGuiStorage::GetVoidPtr(ImGuiID key) const
2350{
2351 ImGuiStoragePair* it = LowerBound(data&: const_cast<ImVector<ImGuiStoragePair>&>(Data), key);
2352 if (it == Data.end() || it->key != key)
2353 return NULL;
2354 return it->val_p;
2355}
2356
2357// 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.
2358int* ImGuiStorage::GetIntRef(ImGuiID key, int default_val)
2359{
2360 ImGuiStoragePair* it = LowerBound(data&: Data, key);
2361 if (it == Data.end() || it->key != key)
2362 it = Data.insert(it, v: ImGuiStoragePair(key, default_val));
2363 return &it->val_i;
2364}
2365
2366bool* ImGuiStorage::GetBoolRef(ImGuiID key, bool default_val)
2367{
2368 return (bool*)GetIntRef(key, default_val: default_val ? 1 : 0);
2369}
2370
2371float* ImGuiStorage::GetFloatRef(ImGuiID key, float default_val)
2372{
2373 ImGuiStoragePair* it = LowerBound(data&: Data, key);
2374 if (it == Data.end() || it->key != key)
2375 it = Data.insert(it, v: ImGuiStoragePair(key, default_val));
2376 return &it->val_f;
2377}
2378
2379void** ImGuiStorage::GetVoidPtrRef(ImGuiID key, void* default_val)
2380{
2381 ImGuiStoragePair* it = LowerBound(data&: Data, key);
2382 if (it == Data.end() || it->key != key)
2383 it = Data.insert(it, v: ImGuiStoragePair(key, default_val));
2384 return &it->val_p;
2385}
2386
2387// 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)
2388void ImGuiStorage::SetInt(ImGuiID key, int val)
2389{
2390 ImGuiStoragePair* it = LowerBound(data&: Data, key);
2391 if (it == Data.end() || it->key != key)
2392 {
2393 Data.insert(it, v: ImGuiStoragePair(key, val));
2394 return;
2395 }
2396 it->val_i = val;
2397}
2398
2399void ImGuiStorage::SetBool(ImGuiID key, bool val)
2400{
2401 SetInt(key, val: val ? 1 : 0);
2402}
2403
2404void ImGuiStorage::SetFloat(ImGuiID key, float val)
2405{
2406 ImGuiStoragePair* it = LowerBound(data&: Data, key);
2407 if (it == Data.end() || it->key != key)
2408 {
2409 Data.insert(it, v: ImGuiStoragePair(key, val));
2410 return;
2411 }
2412 it->val_f = val;
2413}
2414
2415void ImGuiStorage::SetVoidPtr(ImGuiID key, void* val)
2416{
2417 ImGuiStoragePair* it = LowerBound(data&: Data, key);
2418 if (it == Data.end() || it->key != key)
2419 {
2420 Data.insert(it, v: ImGuiStoragePair(key, val));
2421 return;
2422 }
2423 it->val_p = val;
2424}
2425
2426void ImGuiStorage::SetAllInt(int v)
2427{
2428 for (int i = 0; i < Data.Size; i++)
2429 Data[i].val_i = v;
2430}
2431
2432//-----------------------------------------------------------------------------
2433// [SECTION] ImGuiTextFilter
2434//-----------------------------------------------------------------------------
2435
2436// Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]"
2437ImGuiTextFilter::ImGuiTextFilter(const char* default_filter) //-V1077
2438{
2439 InputBuf[0] = 0;
2440 CountGrep = 0;
2441 if (default_filter)
2442 {
2443 ImStrncpy(dst: InputBuf, src: default_filter, IM_ARRAYSIZE(InputBuf));
2444 Build();
2445 }
2446}
2447
2448bool ImGuiTextFilter::Draw(const char* label, float width)
2449{
2450 if (width != 0.0f)
2451 ImGui::SetNextItemWidth(width);
2452 bool value_changed = ImGui::InputText(label, buf: InputBuf, IM_ARRAYSIZE(InputBuf));
2453 if (value_changed)
2454 Build();
2455 return value_changed;
2456}
2457
2458void ImGuiTextFilter::ImGuiTextRange::split(char separator, ImVector<ImGuiTextRange>* out) const
2459{
2460 out->resize(new_size: 0);
2461 const char* wb = b;
2462 const char* we = wb;
2463 while (we < e)
2464 {
2465 if (*we == separator)
2466 {
2467 out->push_back(v: ImGuiTextRange(wb, we));
2468 wb = we + 1;
2469 }
2470 we++;
2471 }
2472 if (wb != we)
2473 out->push_back(v: ImGuiTextRange(wb, we));
2474}
2475
2476void ImGuiTextFilter::Build()
2477{
2478 Filters.resize(new_size: 0);
2479 ImGuiTextRange input_range(InputBuf, InputBuf + strlen(s: InputBuf));
2480 input_range.split(separator: ',', out: &Filters);
2481
2482 CountGrep = 0;
2483 for (int i = 0; i != Filters.Size; i++)
2484 {
2485 ImGuiTextRange& f = Filters[i];
2486 while (f.b < f.e && ImCharIsBlankA(c: f.b[0]))
2487 f.b++;
2488 while (f.e > f.b && ImCharIsBlankA(c: f.e[-1]))
2489 f.e--;
2490 if (f.empty())
2491 continue;
2492 if (Filters[i].b[0] != '-')
2493 CountGrep += 1;
2494 }
2495}
2496
2497bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const
2498{
2499 if (Filters.empty())
2500 return true;
2501
2502 if (text == NULL)
2503 text = "";
2504
2505 for (int i = 0; i != Filters.Size; i++)
2506 {
2507 const ImGuiTextRange& f = Filters[i];
2508 if (f.empty())
2509 continue;
2510 if (f.b[0] == '-')
2511 {
2512 // Subtract
2513 if (ImStristr(haystack: text, haystack_end: text_end, needle: f.b + 1, needle_end: f.e) != NULL)
2514 return false;
2515 }
2516 else
2517 {
2518 // Grep
2519 if (ImStristr(haystack: text, haystack_end: text_end, needle: f.b, needle_end: f.e) != NULL)
2520 return true;
2521 }
2522 }
2523
2524 // Implicit * grep
2525 if (CountGrep == 0)
2526 return true;
2527
2528 return false;
2529}
2530
2531//-----------------------------------------------------------------------------
2532// [SECTION] ImGuiTextBuffer, ImGuiTextIndex
2533//-----------------------------------------------------------------------------
2534
2535// On some platform vsnprintf() takes va_list by reference and modifies it.
2536// va_copy is the 'correct' way to copy a va_list but Visual Studio prior to 2013 doesn't have it.
2537#ifndef va_copy
2538#if defined(__GNUC__) || defined(__clang__)
2539#define va_copy(dest, src) __builtin_va_copy(dest, src)
2540#else
2541#define va_copy(dest, src) (dest = src)
2542#endif
2543#endif
2544
2545char ImGuiTextBuffer::EmptyString[1] = { 0 };
2546
2547void ImGuiTextBuffer::append(const char* str, const char* str_end)
2548{
2549 int len = str_end ? (int)(str_end - str) : (int)strlen(s: str);
2550
2551 // Add zero-terminator the first time
2552 const int write_off = (Buf.Size != 0) ? Buf.Size : 1;
2553 const int needed_sz = write_off + len;
2554 if (write_off + len >= Buf.Capacity)
2555 {
2556 int new_capacity = Buf.Capacity * 2;
2557 Buf.reserve(new_capacity: needed_sz > new_capacity ? needed_sz : new_capacity);
2558 }
2559
2560 Buf.resize(new_size: needed_sz);
2561 memcpy(dest: &Buf[write_off - 1], src: str, n: (size_t)len);
2562 Buf[write_off - 1 + len] = 0;
2563}
2564
2565void ImGuiTextBuffer::appendf(const char* fmt, ...)
2566{
2567 va_list args;
2568 va_start(args, fmt);
2569 appendfv(fmt, args);
2570 va_end(args);
2571}
2572
2573// Helper: Text buffer for logging/accumulating text
2574void ImGuiTextBuffer::appendfv(const char* fmt, va_list args)
2575{
2576 va_list args_copy;
2577 va_copy(args_copy, args);
2578
2579 int len = ImFormatStringV(NULL, buf_size: 0, fmt, args); // FIXME-OPT: could do a first pass write attempt, likely successful on first pass.
2580 if (len <= 0)
2581 {
2582 va_end(args_copy);
2583 return;
2584 }
2585
2586 // Add zero-terminator the first time
2587 const int write_off = (Buf.Size != 0) ? Buf.Size : 1;
2588 const int needed_sz = write_off + len;
2589 if (write_off + len >= Buf.Capacity)
2590 {
2591 int new_capacity = Buf.Capacity * 2;
2592 Buf.reserve(new_capacity: needed_sz > new_capacity ? needed_sz : new_capacity);
2593 }
2594
2595 Buf.resize(new_size: needed_sz);
2596 ImFormatStringV(buf: &Buf[write_off - 1], buf_size: (size_t)len + 1, fmt, args: args_copy);
2597 va_end(args_copy);
2598}
2599
2600void ImGuiTextIndex::append(const char* base, int old_size, int new_size)
2601{
2602 IM_ASSERT(old_size >= 0 && new_size >= old_size && new_size >= EndOffset);
2603 if (old_size == new_size)
2604 return;
2605 if (EndOffset == 0 || base[EndOffset - 1] == '\n')
2606 LineOffsets.push_back(v: EndOffset);
2607 const char* base_end = base + new_size;
2608 for (const char* p = base + old_size; (p = (const char*)memchr(s: p, c: '\n', n: base_end - p)) != 0; )
2609 if (++p < base_end) // Don't push a trailing offset on last \n
2610 LineOffsets.push_back(v: (int)(intptr_t)(p - base));
2611 EndOffset = ImMax(lhs: EndOffset, rhs: new_size);
2612}
2613
2614//-----------------------------------------------------------------------------
2615// [SECTION] ImGuiListClipper
2616// This is currently not as flexible/powerful as it should be and really confusing/spaghetti, mostly because we changed
2617// the API mid-way through development and support two ways to using the clipper, needs some rework (see TODO)
2618//-----------------------------------------------------------------------------
2619
2620// FIXME-TABLE: This prevents us from using ImGuiListClipper _inside_ a table cell.
2621// The problem we have is that without a Begin/End scheme for rows using the clipper is ambiguous.
2622static bool GetSkipItemForListClipping()
2623{
2624 ImGuiContext& g = *GImGui;
2625 return (g.CurrentTable ? g.CurrentTable->HostSkipItems : g.CurrentWindow->SkipItems);
2626}
2627
2628#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
2629// Legacy helper to calculate coarse clipping of large list of evenly sized items.
2630// This legacy API is not ideal because it assumes we will return a single contiguous rectangle.
2631// Prefer using ImGuiListClipper which can returns non-contiguous ranges.
2632void ImGui::CalcListClipping(int items_count, float items_height, int* out_items_display_start, int* out_items_display_end)
2633{
2634 ImGuiContext& g = *GImGui;
2635 ImGuiWindow* window = g.CurrentWindow;
2636 if (g.LogEnabled)
2637 {
2638 // If logging is active, do not perform any clipping
2639 *out_items_display_start = 0;
2640 *out_items_display_end = items_count;
2641 return;
2642 }
2643 if (GetSkipItemForListClipping())
2644 {
2645 *out_items_display_start = *out_items_display_end = 0;
2646 return;
2647 }
2648
2649 // We create the union of the ClipRect and the scoring rect which at worst should be 1 page away from ClipRect
2650 // We don't include g.NavId's rectangle in there (unless g.NavJustMovedToId is set) because the rectangle enlargement can get costly.
2651 ImRect rect = window->ClipRect;
2652 if (g.NavMoveScoringItems)
2653 rect.Add(r: g.NavScoringNoClipRect);
2654 if (g.NavJustMovedToId && window->NavLastIds[0] == g.NavJustMovedToId)
2655 rect.Add(r: WindowRectRelToAbs(window, r: window->NavRectRel[0])); // Could store and use NavJustMovedToRectRel
2656
2657 const ImVec2 pos = window->DC.CursorPos;
2658 int start = (int)((rect.Min.y - pos.y) / items_height);
2659 int end = (int)((rect.Max.y - pos.y) / items_height);
2660
2661 // When performing a navigation request, ensure we have one item extra in the direction we are moving to
2662 // FIXME: Verify this works with tabbing
2663 const bool is_nav_request = (g.NavMoveScoringItems && g.NavWindow && g.NavWindow->RootWindowForNav == window->RootWindowForNav);
2664 if (is_nav_request && g.NavMoveClipDir == ImGuiDir_Up)
2665 start--;
2666 if (is_nav_request && g.NavMoveClipDir == ImGuiDir_Down)
2667 end++;
2668
2669 start = ImClamp(v: start, mn: 0, mx: items_count);
2670 end = ImClamp(v: end + 1, mn: start, mx: items_count);
2671 *out_items_display_start = start;
2672 *out_items_display_end = end;
2673}
2674#endif
2675
2676static void ImGuiListClipper_SortAndFuseRanges(ImVector<ImGuiListClipperRange>& ranges, int offset = 0)
2677{
2678 if (ranges.Size - offset <= 1)
2679 return;
2680
2681 // Helper to order ranges and fuse them together if possible (bubble sort is fine as we are only sorting 2-3 entries)
2682 for (int sort_end = ranges.Size - offset - 1; sort_end > 0; --sort_end)
2683 for (int i = offset; i < sort_end + offset; ++i)
2684 if (ranges[i].Min > ranges[i + 1].Min)
2685 ImSwap(a&: ranges[i], b&: ranges[i + 1]);
2686
2687 // Now fuse ranges together as much as possible.
2688 for (int i = 1 + offset; i < ranges.Size; i++)
2689 {
2690 IM_ASSERT(!ranges[i].PosToIndexConvert && !ranges[i - 1].PosToIndexConvert);
2691 if (ranges[i - 1].Max < ranges[i].Min)
2692 continue;
2693 ranges[i - 1].Min = ImMin(lhs: ranges[i - 1].Min, rhs: ranges[i].Min);
2694 ranges[i - 1].Max = ImMax(lhs: ranges[i - 1].Max, rhs: ranges[i].Max);
2695 ranges.erase(it: ranges.Data + i);
2696 i--;
2697 }
2698}
2699
2700static void ImGuiListClipper_SeekCursorAndSetupPrevLine(float pos_y, float line_height)
2701{
2702 // Set cursor position and a few other things so that SetScrollHereY() and Columns() can work when seeking cursor.
2703 // FIXME: It is problematic that we have to do that here, because custom/equivalent end-user code would stumble on the same issue.
2704 // The clipper should probably have a final step to display the last item in a regular manner, maybe with an opt-out flag for data sets which may have costly seek?
2705 ImGuiContext& g = *GImGui;
2706 ImGuiWindow* window = g.CurrentWindow;
2707 float off_y = pos_y - window->DC.CursorPos.y;
2708 window->DC.CursorPos.y = pos_y;
2709 window->DC.CursorMaxPos.y = ImMax(lhs: window->DC.CursorMaxPos.y, rhs: pos_y - g.Style.ItemSpacing.y);
2710 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.
2711 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.
2712 if (ImGuiOldColumns* columns = window->DC.CurrentColumns)
2713 columns->LineMinY = window->DC.CursorPos.y; // Setting this so that cell Y position are set properly
2714 if (ImGuiTable* table = g.CurrentTable)
2715 {
2716 if (table->IsInsideRow)
2717 ImGui::TableEndRow(table);
2718 table->RowPosY2 = window->DC.CursorPos.y;
2719 const int row_increase = (int)((off_y / line_height) + 0.5f);
2720 //table->CurrentRow += row_increase; // Can't do without fixing TableEndRow()
2721 table->RowBgColorCounter += row_increase;
2722 }
2723}
2724
2725static void ImGuiListClipper_SeekCursorForItem(ImGuiListClipper* clipper, int item_n)
2726{
2727 // StartPosY starts from ItemsFrozen hence the subtraction
2728 // Perform the add and multiply with double to allow seeking through larger ranges
2729 ImGuiListClipperData* data = (ImGuiListClipperData*)clipper->TempData;
2730 float pos_y = (float)((double)clipper->StartPosY + data->LossynessOffset + (double)(item_n - data->ItemsFrozen) * clipper->ItemsHeight);
2731 ImGuiListClipper_SeekCursorAndSetupPrevLine(pos_y, line_height: clipper->ItemsHeight);
2732}
2733
2734ImGuiListClipper::ImGuiListClipper()
2735{
2736 memset(s: this, c: 0, n: sizeof(*this));
2737 ItemsCount = -1;
2738}
2739
2740ImGuiListClipper::~ImGuiListClipper()
2741{
2742 End();
2743}
2744
2745void ImGuiListClipper::Begin(int items_count, float items_height)
2746{
2747 ImGuiContext& g = *GImGui;
2748 ImGuiWindow* window = g.CurrentWindow;
2749 IMGUI_DEBUG_LOG_CLIPPER("Clipper: Begin(%d,%.2f) in '%s'\n", items_count, items_height, window->Name);
2750
2751 if (ImGuiTable* table = g.CurrentTable)
2752 if (table->IsInsideRow)
2753 ImGui::TableEndRow(table);
2754
2755 StartPosY = window->DC.CursorPos.y;
2756 ItemsHeight = items_height;
2757 ItemsCount = items_count;
2758 DisplayStart = -1;
2759 DisplayEnd = 0;
2760
2761 // Acquire temporary buffer
2762 if (++g.ClipperTempDataStacked > g.ClipperTempData.Size)
2763 g.ClipperTempData.resize(new_size: g.ClipperTempDataStacked, v: ImGuiListClipperData());
2764 ImGuiListClipperData* data = &g.ClipperTempData[g.ClipperTempDataStacked - 1];
2765 data->Reset(clipper: this);
2766 data->LossynessOffset = window->DC.CursorStartPosLossyness.y;
2767 TempData = data;
2768}
2769
2770void ImGuiListClipper::End()
2771{
2772 ImGuiContext& g = *GImGui;
2773 if (ImGuiListClipperData* data = (ImGuiListClipperData*)TempData)
2774 {
2775 // In theory here we should assert that we are already at the right position, but it seems saner to just seek at the end and not assert/crash the user.
2776 IMGUI_DEBUG_LOG_CLIPPER("Clipper: End() in '%s'\n", g.CurrentWindow->Name);
2777 if (ItemsCount >= 0 && ItemsCount < INT_MAX && DisplayStart >= 0)
2778 ImGuiListClipper_SeekCursorForItem(clipper: this, item_n: ItemsCount);
2779
2780 // Restore temporary buffer and fix back pointers which may be invalidated when nesting
2781 IM_ASSERT(data->ListClipper == this);
2782 data->StepNo = data->Ranges.Size;
2783 if (--g.ClipperTempDataStacked > 0)
2784 {
2785 data = &g.ClipperTempData[g.ClipperTempDataStacked - 1];
2786 data->ListClipper->TempData = data;
2787 }
2788 TempData = NULL;
2789 }
2790 ItemsCount = -1;
2791}
2792
2793void ImGuiListClipper::ForceDisplayRangeByIndices(int item_min, int item_max)
2794{
2795 ImGuiListClipperData* data = (ImGuiListClipperData*)TempData;
2796 IM_ASSERT(DisplayStart < 0); // Only allowed after Begin() and if there has not been a specified range yet.
2797 IM_ASSERT(item_min <= item_max);
2798 if (item_min < item_max)
2799 data->Ranges.push_back(v: ImGuiListClipperRange::FromIndices(min: item_min, max: item_max));
2800}
2801
2802static bool ImGuiListClipper_StepInternal(ImGuiListClipper* clipper)
2803{
2804 ImGuiContext& g = *GImGui;
2805 ImGuiWindow* window = g.CurrentWindow;
2806 ImGuiListClipperData* data = (ImGuiListClipperData*)clipper->TempData;
2807 IM_ASSERT(data != NULL && "Called ImGuiListClipper::Step() too many times, or before ImGuiListClipper::Begin() ?");
2808
2809 ImGuiTable* table = g.CurrentTable;
2810 if (table && table->IsInsideRow)
2811 ImGui::TableEndRow(table);
2812
2813 // No items
2814 if (clipper->ItemsCount == 0 || GetSkipItemForListClipping())
2815 return false;
2816
2817 // While we are in frozen row state, keep displaying items one by one, unclipped
2818 // FIXME: Could be stored as a table-agnostic state.
2819 if (data->StepNo == 0 && table != NULL && !table->IsUnfrozenRows)
2820 {
2821 clipper->DisplayStart = data->ItemsFrozen;
2822 clipper->DisplayEnd = ImMin(lhs: data->ItemsFrozen + 1, rhs: clipper->ItemsCount);
2823 if (clipper->DisplayStart < clipper->DisplayEnd)
2824 data->ItemsFrozen++;
2825 return true;
2826 }
2827
2828 // Step 0: Let you process the first element (regardless of it being visible or not, so we can measure the element height)
2829 bool calc_clipping = false;
2830 if (data->StepNo == 0)
2831 {
2832 clipper->StartPosY = window->DC.CursorPos.y;
2833 if (clipper->ItemsHeight <= 0.0f)
2834 {
2835 // Submit the first item (or range) so we can measure its height (generally the first range is 0..1)
2836 data->Ranges.push_front(v: ImGuiListClipperRange::FromIndices(min: data->ItemsFrozen, max: data->ItemsFrozen + 1));
2837 clipper->DisplayStart = ImMax(lhs: data->Ranges[0].Min, rhs: data->ItemsFrozen);
2838 clipper->DisplayEnd = ImMin(lhs: data->Ranges[0].Max, rhs: clipper->ItemsCount);
2839 data->StepNo = 1;
2840 return true;
2841 }
2842 calc_clipping = true; // If on the first step with known item height, calculate clipping.
2843 }
2844
2845 // Step 1: Let the clipper infer height from first range
2846 if (clipper->ItemsHeight <= 0.0f)
2847 {
2848 IM_ASSERT(data->StepNo == 1);
2849 if (table)
2850 IM_ASSERT(table->RowPosY1 == clipper->StartPosY && table->RowPosY2 == window->DC.CursorPos.y);
2851
2852 clipper->ItemsHeight = (window->DC.CursorPos.y - clipper->StartPosY) / (float)(clipper->DisplayEnd - clipper->DisplayStart);
2853 bool affected_by_floating_point_precision = ImIsFloatAboveGuaranteedIntegerPrecision(f: clipper->StartPosY) || ImIsFloatAboveGuaranteedIntegerPrecision(f: window->DC.CursorPos.y);
2854 if (affected_by_floating_point_precision)
2855 clipper->ItemsHeight = window->DC.PrevLineSize.y + g.Style.ItemSpacing.y; // FIXME: Technically wouldn't allow multi-line entries.
2856
2857 IM_ASSERT(clipper->ItemsHeight > 0.0f && "Unable to calculate item height! First item hasn't moved the cursor vertically!");
2858 calc_clipping = true; // If item height had to be calculated, calculate clipping afterwards.
2859 }
2860
2861 // Step 0 or 1: Calculate the actual ranges of visible elements.
2862 const int already_submitted = clipper->DisplayEnd;
2863 if (calc_clipping)
2864 {
2865 if (g.LogEnabled)
2866 {
2867 // If logging is active, do not perform any clipping
2868 data->Ranges.push_back(v: ImGuiListClipperRange::FromIndices(min: 0, max: clipper->ItemsCount));
2869 }
2870 else
2871 {
2872 // Add range selected to be included for navigation
2873 const bool is_nav_request = (g.NavMoveScoringItems && g.NavWindow && g.NavWindow->RootWindowForNav == window->RootWindowForNav);
2874 if (is_nav_request)
2875 data->Ranges.push_back(v: ImGuiListClipperRange::FromPositions(y1: g.NavScoringNoClipRect.Min.y, y2: g.NavScoringNoClipRect.Max.y, off_min: 0, off_max: 0));
2876 if (is_nav_request && (g.NavMoveFlags & ImGuiNavMoveFlags_Tabbing) && g.NavTabbingDir == -1)
2877 data->Ranges.push_back(v: ImGuiListClipperRange::FromIndices(min: clipper->ItemsCount - 1, max: clipper->ItemsCount));
2878
2879 // Add focused/active item
2880 ImRect nav_rect_abs = ImGui::WindowRectRelToAbs(window, r: window->NavRectRel[0]);
2881 if (g.NavId != 0 && window->NavLastIds[0] == g.NavId)
2882 data->Ranges.push_back(v: ImGuiListClipperRange::FromPositions(y1: nav_rect_abs.Min.y, y2: nav_rect_abs.Max.y, off_min: 0, off_max: 0));
2883
2884 // Add visible range
2885 const int off_min = (is_nav_request && g.NavMoveClipDir == ImGuiDir_Up) ? -1 : 0;
2886 const int off_max = (is_nav_request && g.NavMoveClipDir == ImGuiDir_Down) ? 1 : 0;
2887 data->Ranges.push_back(v: ImGuiListClipperRange::FromPositions(y1: window->ClipRect.Min.y, y2: window->ClipRect.Max.y, off_min, off_max));
2888 }
2889
2890 // Convert position ranges to item index ranges
2891 // - Very important: when a starting position is after our maximum item, we set Min to (ItemsCount - 1). This allows us to handle most forms of wrapping.
2892 // - Due to how Selectable extra padding they tend to be "unaligned" with exact unit in the item list,
2893 // which with the flooring/ceiling tend to lead to 2 items instead of one being submitted.
2894 for (int i = 0; i < data->Ranges.Size; i++)
2895 if (data->Ranges[i].PosToIndexConvert)
2896 {
2897 int m1 = (int)(((double)data->Ranges[i].Min - window->DC.CursorPos.y - data->LossynessOffset) / clipper->ItemsHeight);
2898 int m2 = (int)((((double)data->Ranges[i].Max - window->DC.CursorPos.y - data->LossynessOffset) / clipper->ItemsHeight) + 0.999999f);
2899 data->Ranges[i].Min = ImClamp(v: already_submitted + m1 + data->Ranges[i].PosToIndexOffsetMin, mn: already_submitted, mx: clipper->ItemsCount - 1);
2900 data->Ranges[i].Max = ImClamp(v: already_submitted + m2 + data->Ranges[i].PosToIndexOffsetMax, mn: data->Ranges[i].Min + 1, mx: clipper->ItemsCount);
2901 data->Ranges[i].PosToIndexConvert = false;
2902 }
2903 ImGuiListClipper_SortAndFuseRanges(ranges&: data->Ranges, offset: data->StepNo);
2904 }
2905
2906 // Step 0+ (if item height is given in advance) or 1+: Display the next range in line.
2907 if (data->StepNo < data->Ranges.Size)
2908 {
2909 clipper->DisplayStart = ImMax(lhs: data->Ranges[data->StepNo].Min, rhs: already_submitted);
2910 clipper->DisplayEnd = ImMin(lhs: data->Ranges[data->StepNo].Max, rhs: clipper->ItemsCount);
2911 if (clipper->DisplayStart > already_submitted) //-V1051
2912 ImGuiListClipper_SeekCursorForItem(clipper, item_n: clipper->DisplayStart);
2913 data->StepNo++;
2914 return true;
2915 }
2916
2917 // After the last step: Let the clipper validate that we have reached the expected Y position (corresponding to element DisplayEnd),
2918 // Advance the cursor to the end of the list and then returns 'false' to end the loop.
2919 if (clipper->ItemsCount < INT_MAX)
2920 ImGuiListClipper_SeekCursorForItem(clipper, item_n: clipper->ItemsCount);
2921
2922 return false;
2923}
2924
2925bool ImGuiListClipper::Step()
2926{
2927 ImGuiContext& g = *GImGui;
2928 bool need_items_height = (ItemsHeight <= 0.0f);
2929 bool ret = ImGuiListClipper_StepInternal(clipper: this);
2930 if (ret && (DisplayStart == DisplayEnd))
2931 ret = false;
2932 if (g.CurrentTable && g.CurrentTable->IsUnfrozenRows == false)
2933 IMGUI_DEBUG_LOG_CLIPPER("Clipper: Step(): inside frozen table row.\n");
2934 if (need_items_height && ItemsHeight > 0.0f)
2935 IMGUI_DEBUG_LOG_CLIPPER("Clipper: Step(): computed ItemsHeight: %.2f.\n", ItemsHeight);
2936 if (ret)
2937 {
2938 IMGUI_DEBUG_LOG_CLIPPER("Clipper: Step(): display %d to %d.\n", DisplayStart, DisplayEnd);
2939 }
2940 else
2941 {
2942 IMGUI_DEBUG_LOG_CLIPPER("Clipper: Step(): End.\n");
2943 End();
2944 }
2945 return ret;
2946}
2947
2948//-----------------------------------------------------------------------------
2949// [SECTION] STYLING
2950//-----------------------------------------------------------------------------
2951
2952ImGuiStyle& ImGui::GetStyle()
2953{
2954 IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext() ?");
2955 return GImGui->Style;
2956}
2957
2958ImU32 ImGui::GetColorU32(ImGuiCol idx, float alpha_mul)
2959{
2960 ImGuiStyle& style = GImGui->Style;
2961 ImVec4 c = style.Colors[idx];
2962 c.w *= style.Alpha * alpha_mul;
2963 return ColorConvertFloat4ToU32(in: c);
2964}
2965
2966ImU32 ImGui::GetColorU32(const ImVec4& col)
2967{
2968 ImGuiStyle& style = GImGui->Style;
2969 ImVec4 c = col;
2970 c.w *= style.Alpha;
2971 return ColorConvertFloat4ToU32(in: c);
2972}
2973
2974const ImVec4& ImGui::GetStyleColorVec4(ImGuiCol idx)
2975{
2976 ImGuiStyle& style = GImGui->Style;
2977 return style.Colors[idx];
2978}
2979
2980ImU32 ImGui::GetColorU32(ImU32 col)
2981{
2982 ImGuiStyle& style = GImGui->Style;
2983 if (style.Alpha >= 1.0f)
2984 return col;
2985 ImU32 a = (col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT;
2986 a = (ImU32)(a * style.Alpha); // We don't need to clamp 0..255 because Style.Alpha is in 0..1 range.
2987 return (col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT);
2988}
2989
2990// 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
2991void ImGui::PushStyleColor(ImGuiCol idx, ImU32 col)
2992{
2993 ImGuiContext& g = *GImGui;
2994 ImGuiColorMod backup;
2995 backup.Col = idx;
2996 backup.BackupValue = g.Style.Colors[idx];
2997 g.ColorStack.push_back(v: backup);
2998 g.Style.Colors[idx] = ColorConvertU32ToFloat4(in: col);
2999}
3000
3001void ImGui::PushStyleColor(ImGuiCol idx, const ImVec4& col)
3002{
3003 ImGuiContext& g = *GImGui;
3004 ImGuiColorMod backup;
3005 backup.Col = idx;
3006 backup.BackupValue = g.Style.Colors[idx];
3007 g.ColorStack.push_back(v: backup);
3008 g.Style.Colors[idx] = col;
3009}
3010
3011void ImGui::PopStyleColor(int count)
3012{
3013 ImGuiContext& g = *GImGui;
3014 if (g.ColorStack.Size < count)
3015 {
3016 IM_ASSERT_USER_ERROR(g.ColorStack.Size > count, "Calling PopStyleColor() too many times: stack underflow.");
3017 count = g.ColorStack.Size;
3018 }
3019 while (count > 0)
3020 {
3021 ImGuiColorMod& backup = g.ColorStack.back();
3022 g.Style.Colors[backup.Col] = backup.BackupValue;
3023 g.ColorStack.pop_back();
3024 count--;
3025 }
3026}
3027
3028struct ImGuiStyleVarInfo
3029{
3030 ImGuiDataType Type;
3031 ImU32 Count;
3032 ImU32 Offset;
3033 void* GetVarPtr(ImGuiStyle* style) const { return (void*)((unsigned char*)style + Offset); }
3034};
3035
3036static const ImGuiCol GWindowDockStyleColors[ImGuiWindowDockStyleCol_COUNT] =
3037{
3038 ImGuiCol_Text, ImGuiCol_Tab, ImGuiCol_TabHovered, ImGuiCol_TabActive, ImGuiCol_TabUnfocused, ImGuiCol_TabUnfocusedActive
3039};
3040
3041static const ImGuiStyleVarInfo GStyleVarInfo[] =
3042{
3043 { .Type: ImGuiDataType_Float, .Count: 1, .Offset: (ImU32)IM_OFFSETOF(ImGuiStyle, Alpha) }, // ImGuiStyleVar_Alpha
3044 { .Type: ImGuiDataType_Float, .Count: 1, .Offset: (ImU32)IM_OFFSETOF(ImGuiStyle, DisabledAlpha) }, // ImGuiStyleVar_DisabledAlpha
3045 { .Type: ImGuiDataType_Float, .Count: 2, .Offset: (ImU32)IM_OFFSETOF(ImGuiStyle, WindowPadding) }, // ImGuiStyleVar_WindowPadding
3046 { .Type: ImGuiDataType_Float, .Count: 1, .Offset: (ImU32)IM_OFFSETOF(ImGuiStyle, WindowRounding) }, // ImGuiStyleVar_WindowRounding
3047 { .Type: ImGuiDataType_Float, .Count: 1, .Offset: (ImU32)IM_OFFSETOF(ImGuiStyle, WindowBorderSize) }, // ImGuiStyleVar_WindowBorderSize
3048 { .Type: ImGuiDataType_Float, .Count: 2, .Offset: (ImU32)IM_OFFSETOF(ImGuiStyle, WindowMinSize) }, // ImGuiStyleVar_WindowMinSize
3049 { .Type: ImGuiDataType_Float, .Count: 2, .Offset: (ImU32)IM_OFFSETOF(ImGuiStyle, WindowTitleAlign) }, // ImGuiStyleVar_WindowTitleAlign
3050 { .Type: ImGuiDataType_Float, .Count: 1, .Offset: (ImU32)IM_OFFSETOF(ImGuiStyle, ChildRounding) }, // ImGuiStyleVar_ChildRounding
3051 { .Type: ImGuiDataType_Float, .Count: 1, .Offset: (ImU32)IM_OFFSETOF(ImGuiStyle, ChildBorderSize) }, // ImGuiStyleVar_ChildBorderSize
3052 { .Type: ImGuiDataType_Float, .Count: 1, .Offset: (ImU32)IM_OFFSETOF(ImGuiStyle, PopupRounding) }, // ImGuiStyleVar_PopupRounding
3053 { .Type: ImGuiDataType_Float, .Count: 1, .Offset: (ImU32)IM_OFFSETOF(ImGuiStyle, PopupBorderSize) }, // ImGuiStyleVar_PopupBorderSize
3054 { .Type: ImGuiDataType_Float, .Count: 2, .Offset: (ImU32)IM_OFFSETOF(ImGuiStyle, FramePadding) }, // ImGuiStyleVar_FramePadding
3055 { .Type: ImGuiDataType_Float, .Count: 1, .Offset: (ImU32)IM_OFFSETOF(ImGuiStyle, FrameRounding) }, // ImGuiStyleVar_FrameRounding
3056 { .Type: ImGuiDataType_Float, .Count: 1, .Offset: (ImU32)IM_OFFSETOF(ImGuiStyle, FrameBorderSize) }, // ImGuiStyleVar_FrameBorderSize
3057 { .Type: ImGuiDataType_Float, .Count: 2, .Offset: (ImU32)IM_OFFSETOF(ImGuiStyle, ItemSpacing) }, // ImGuiStyleVar_ItemSpacing
3058 { .Type: ImGuiDataType_Float, .Count: 2, .Offset: (ImU32)IM_OFFSETOF(ImGuiStyle, ItemInnerSpacing) }, // ImGuiStyleVar_ItemInnerSpacing
3059 { .Type: ImGuiDataType_Float, .Count: 1, .Offset: (ImU32)IM_OFFSETOF(ImGuiStyle, IndentSpacing) }, // ImGuiStyleVar_IndentSpacing
3060 { .Type: ImGuiDataType_Float, .Count: 2, .Offset: (ImU32)IM_OFFSETOF(ImGuiStyle, CellPadding) }, // ImGuiStyleVar_CellPadding
3061 { .Type: ImGuiDataType_Float, .Count: 1, .Offset: (ImU32)IM_OFFSETOF(ImGuiStyle, ScrollbarSize) }, // ImGuiStyleVar_ScrollbarSize
3062 { .Type: ImGuiDataType_Float, .Count: 1, .Offset: (ImU32)IM_OFFSETOF(ImGuiStyle, ScrollbarRounding) }, // ImGuiStyleVar_ScrollbarRounding
3063 { .Type: ImGuiDataType_Float, .Count: 1, .Offset: (ImU32)IM_OFFSETOF(ImGuiStyle, GrabMinSize) }, // ImGuiStyleVar_GrabMinSize
3064 { .Type: ImGuiDataType_Float, .Count: 1, .Offset: (ImU32)IM_OFFSETOF(ImGuiStyle, GrabRounding) }, // ImGuiStyleVar_GrabRounding
3065 { .Type: ImGuiDataType_Float, .Count: 1, .Offset: (ImU32)IM_OFFSETOF(ImGuiStyle, TabRounding) }, // ImGuiStyleVar_TabRounding
3066 { .Type: ImGuiDataType_Float, .Count: 2, .Offset: (ImU32)IM_OFFSETOF(ImGuiStyle, ButtonTextAlign) }, // ImGuiStyleVar_ButtonTextAlign
3067 { .Type: ImGuiDataType_Float, .Count: 2, .Offset: (ImU32)IM_OFFSETOF(ImGuiStyle, SelectableTextAlign) }, // ImGuiStyleVar_SelectableTextAlign
3068};
3069
3070static const ImGuiStyleVarInfo* GetStyleVarInfo(ImGuiStyleVar idx)
3071{
3072 IM_ASSERT(idx >= 0 && idx < ImGuiStyleVar_COUNT);
3073 IM_ASSERT(IM_ARRAYSIZE(GStyleVarInfo) == ImGuiStyleVar_COUNT);
3074 return &GStyleVarInfo[idx];
3075}
3076
3077void ImGui::PushStyleVar(ImGuiStyleVar idx, float val)
3078{
3079 const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx);
3080 if (var_info->Type == ImGuiDataType_Float && var_info->Count == 1)
3081 {
3082 ImGuiContext& g = *GImGui;
3083 float* pvar = (float*)var_info->GetVarPtr(style: &g.Style);
3084 g.StyleVarStack.push_back(v: ImGuiStyleMod(idx, *pvar));
3085 *pvar = val;
3086 return;
3087 }
3088 IM_ASSERT(0 && "Called PushStyleVar() float variant but variable is not a float!");
3089}
3090
3091void ImGui::PushStyleVar(ImGuiStyleVar idx, const ImVec2& val)
3092{
3093 const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx);
3094 if (var_info->Type == ImGuiDataType_Float && var_info->Count == 2)
3095 {
3096 ImGuiContext& g = *GImGui;
3097 ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(style: &g.Style);
3098 g.StyleVarStack.push_back(v: ImGuiStyleMod(idx, *pvar));
3099 *pvar = val;
3100 return;
3101 }
3102 IM_ASSERT(0 && "Called PushStyleVar() ImVec2 variant but variable is not a ImVec2!");
3103}
3104
3105void ImGui::PopStyleVar(int count)
3106{
3107 ImGuiContext& g = *GImGui;
3108 if (g.StyleVarStack.Size < count)
3109 {
3110 IM_ASSERT_USER_ERROR(g.StyleVarStack.Size > count, "Calling PopStyleVar() too many times: stack underflow.");
3111 count = g.StyleVarStack.Size;
3112 }
3113 while (count > 0)
3114 {
3115 // We avoid a generic memcpy(data, &backup.Backup.., GDataTypeSize[info->Type] * info->Count), the overhead in Debug is not worth it.
3116 ImGuiStyleMod& backup = g.StyleVarStack.back();
3117 const ImGuiStyleVarInfo* info = GetStyleVarInfo(idx: backup.VarIdx);
3118 void* data = info->GetVarPtr(style: &g.Style);
3119 if (info->Type == ImGuiDataType_Float && info->Count == 1) { ((float*)data)[0] = backup.BackupFloat[0]; }
3120 else if (info->Type == ImGuiDataType_Float && info->Count == 2) { ((float*)data)[0] = backup.BackupFloat[0]; ((float*)data)[1] = backup.BackupFloat[1]; }
3121 g.StyleVarStack.pop_back();
3122 count--;
3123 }
3124}
3125
3126const char* ImGui::GetStyleColorName(ImGuiCol idx)
3127{
3128 // Create switch-case from enum with regexp: ImGuiCol_{.*}, --> case ImGuiCol_\1: return "\1";
3129 switch (idx)
3130 {
3131 case ImGuiCol_Text: return "Text";
3132 case ImGuiCol_TextDisabled: return "TextDisabled";
3133 case ImGuiCol_WindowBg: return "WindowBg";
3134 case ImGuiCol_ChildBg: return "ChildBg";
3135 case ImGuiCol_PopupBg: return "PopupBg";
3136 case ImGuiCol_Border: return "Border";
3137 case ImGuiCol_BorderShadow: return "BorderShadow";
3138 case ImGuiCol_FrameBg: return "FrameBg";
3139 case ImGuiCol_FrameBgHovered: return "FrameBgHovered";
3140 case ImGuiCol_FrameBgActive: return "FrameBgActive";
3141 case ImGuiCol_TitleBg: return "TitleBg";
3142 case ImGuiCol_TitleBgActive: return "TitleBgActive";
3143 case ImGuiCol_TitleBgCollapsed: return "TitleBgCollapsed";
3144 case ImGuiCol_MenuBarBg: return "MenuBarBg";
3145 case ImGuiCol_ScrollbarBg: return "ScrollbarBg";
3146 case ImGuiCol_ScrollbarGrab: return "ScrollbarGrab";
3147 case ImGuiCol_ScrollbarGrabHovered: return "ScrollbarGrabHovered";
3148 case ImGuiCol_ScrollbarGrabActive: return "ScrollbarGrabActive";
3149 case ImGuiCol_CheckMark: return "CheckMark";
3150 case ImGuiCol_SliderGrab: return "SliderGrab";
3151 case ImGuiCol_SliderGrabActive: return "SliderGrabActive";
3152 case ImGuiCol_Button: return "Button";
3153 case ImGuiCol_ButtonHovered: return "ButtonHovered";
3154 case ImGuiCol_ButtonActive: return "ButtonActive";
3155 case ImGuiCol_Header: return "Header";
3156 case ImGuiCol_HeaderHovered: return "HeaderHovered";
3157 case ImGuiCol_HeaderActive: return "HeaderActive";
3158 case ImGuiCol_Separator: return "Separator";
3159 case ImGuiCol_SeparatorHovered: return "SeparatorHovered";
3160 case ImGuiCol_SeparatorActive: return "SeparatorActive";
3161 case ImGuiCol_ResizeGrip: return "ResizeGrip";
3162 case ImGuiCol_ResizeGripHovered: return "ResizeGripHovered";
3163 case ImGuiCol_ResizeGripActive: return "ResizeGripActive";
3164 case ImGuiCol_Tab: return "Tab";
3165 case ImGuiCol_TabHovered: return "TabHovered";
3166 case ImGuiCol_TabActive: return "TabActive";
3167 case ImGuiCol_TabUnfocused: return "TabUnfocused";
3168 case ImGuiCol_TabUnfocusedActive: return "TabUnfocusedActive";
3169 case ImGuiCol_DockingPreview: return "DockingPreview";
3170 case ImGuiCol_DockingEmptyBg: return "DockingEmptyBg";
3171 case ImGuiCol_PlotLines: return "PlotLines";
3172 case ImGuiCol_PlotLinesHovered: return "PlotLinesHovered";
3173 case ImGuiCol_PlotHistogram: return "PlotHistogram";
3174 case ImGuiCol_PlotHistogramHovered: return "PlotHistogramHovered";
3175 case ImGuiCol_TableHeaderBg: return "TableHeaderBg";
3176 case ImGuiCol_TableBorderStrong: return "TableBorderStrong";
3177 case ImGuiCol_TableBorderLight: return "TableBorderLight";
3178 case ImGuiCol_TableRowBg: return "TableRowBg";
3179 case ImGuiCol_TableRowBgAlt: return "TableRowBgAlt";
3180 case ImGuiCol_TextSelectedBg: return "TextSelectedBg";
3181 case ImGuiCol_DragDropTarget: return "DragDropTarget";
3182 case ImGuiCol_NavHighlight: return "NavHighlight";
3183 case ImGuiCol_NavWindowingHighlight: return "NavWindowingHighlight";
3184 case ImGuiCol_NavWindowingDimBg: return "NavWindowingDimBg";
3185 case ImGuiCol_ModalWindowDimBg: return "ModalWindowDimBg";
3186 }
3187 IM_ASSERT(0);
3188 return "Unknown";
3189}
3190
3191
3192//-----------------------------------------------------------------------------
3193// [SECTION] RENDER HELPERS
3194// Some of those (internal) functions are currently quite a legacy mess - their signature and behavior will change,
3195// we need a nicer separation between low-level functions and high-level functions relying on the ImGui context.
3196// Also see imgui_draw.cpp for some more which have been reworked to not rely on ImGui:: context.
3197//-----------------------------------------------------------------------------
3198
3199const char* ImGui::FindRenderedTextEnd(const char* text, const char* text_end)
3200{
3201 const char* text_display_end = text;
3202 if (!text_end)
3203 text_end = (const char*)-1;
3204
3205 while (text_display_end < text_end && *text_display_end != '\0' && (text_display_end[0] != '#' || text_display_end[1] != '#'))
3206 text_display_end++;
3207 return text_display_end;
3208}
3209
3210// Internal ImGui functions to render text
3211// RenderText***() functions calls ImDrawList::AddText() calls ImBitmapFont::RenderText()
3212void ImGui::RenderText(ImVec2 pos, const char* text, const char* text_end, bool hide_text_after_hash)
3213{
3214 ImGuiContext& g = *GImGui;
3215 ImGuiWindow* window = g.CurrentWindow;
3216
3217 // Hide anything after a '##' string
3218 const char* text_display_end;
3219 if (hide_text_after_hash)
3220 {
3221 text_display_end = FindRenderedTextEnd(text, text_end);
3222 }
3223 else
3224 {
3225 if (!text_end)
3226 text_end = text + strlen(s: text); // FIXME-OPT
3227 text_display_end = text_end;
3228 }
3229
3230 if (text != text_display_end)
3231 {
3232 window->DrawList->AddText(font: g.Font, font_size: g.FontSize, pos, col: GetColorU32(idx: ImGuiCol_Text), text_begin: text, text_end: text_display_end);
3233 if (g.LogEnabled)
3234 LogRenderedText(ref_pos: &pos, text, text_end: text_display_end);
3235 }
3236}
3237
3238void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width)
3239{
3240 ImGuiContext& g = *GImGui;
3241 ImGuiWindow* window = g.CurrentWindow;
3242
3243 if (!text_end)
3244 text_end = text + strlen(s: text); // FIXME-OPT
3245
3246 if (text != text_end)
3247 {
3248 window->DrawList->AddText(font: g.Font, font_size: g.FontSize, pos, col: GetColorU32(idx: ImGuiCol_Text), text_begin: text, text_end, wrap_width);
3249 if (g.LogEnabled)
3250 LogRenderedText(ref_pos: &pos, text, text_end);
3251 }
3252}
3253
3254// Default clip_rect uses (pos_min,pos_max)
3255// Handle clipping on CPU immediately (vs typically let the GPU clip the triangles that are overlapping the clipping rectangle edges)
3256void 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)
3257{
3258 // Perform CPU side clipping for single clipped element to avoid using scissor state
3259 ImVec2 pos = pos_min;
3260 const ImVec2 text_size = text_size_if_known ? *text_size_if_known : CalcTextSize(text, text_end: text_display_end, hide_text_after_double_hash: false, wrap_width: 0.0f);
3261
3262 const ImVec2* clip_min = clip_rect ? &clip_rect->Min : &pos_min;
3263 const ImVec2* clip_max = clip_rect ? &clip_rect->Max : &pos_max;
3264 bool need_clipping = (pos.x + text_size.x >= clip_max->x) || (pos.y + text_size.y >= clip_max->y);
3265 if (clip_rect) // If we had no explicit clipping rectangle then pos==clip_min
3266 need_clipping |= (pos.x < clip_min->x) || (pos.y < clip_min->y);
3267
3268 // Align whole block. We should defer that to the better rendering function when we'll have support for individual line alignment.
3269 if (align.x > 0.0f) pos.x = ImMax(lhs: pos.x, rhs: pos.x + (pos_max.x - pos.x - text_size.x) * align.x);
3270 if (align.y > 0.0f) pos.y = ImMax(lhs: pos.y, rhs: pos.y + (pos_max.y - pos.y - text_size.y) * align.y);
3271
3272 // Render
3273 if (need_clipping)
3274 {
3275 ImVec4 fine_clip_rect(clip_min->x, clip_min->y, clip_max->x, clip_max->y);
3276 draw_list->AddText(NULL, font_size: 0.0f, pos, col: GetColorU32(idx: ImGuiCol_Text), text_begin: text, text_end: text_display_end, wrap_width: 0.0f, cpu_fine_clip_rect: &fine_clip_rect);
3277 }
3278 else
3279 {
3280 draw_list->AddText(NULL, font_size: 0.0f, pos, col: GetColorU32(idx: ImGuiCol_Text), text_begin: text, text_end: text_display_end, wrap_width: 0.0f, NULL);
3281 }
3282}
3283
3284void 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)
3285{
3286 // Hide anything after a '##' string
3287 const char* text_display_end = FindRenderedTextEnd(text, text_end);
3288 const int text_len = (int)(text_display_end - text);
3289 if (text_len == 0)
3290 return;
3291
3292 ImGuiContext& g = *GImGui;
3293 ImGuiWindow* window = g.CurrentWindow;
3294 RenderTextClippedEx(draw_list: window->DrawList, pos_min, pos_max, text, text_display_end, text_size_if_known, align, clip_rect);
3295 if (g.LogEnabled)
3296 LogRenderedText(ref_pos: &pos_min, text, text_end: text_display_end);
3297}
3298
3299
3300// Another overly complex function until we reorganize everything into a nice all-in-one helper.
3301// 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.
3302// 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.
3303void 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)
3304{
3305 ImGuiContext& g = *GImGui;
3306 if (text_end_full == NULL)
3307 text_end_full = FindRenderedTextEnd(text);
3308 const ImVec2 text_size = text_size_if_known ? *text_size_if_known : CalcTextSize(text, text_end: text_end_full, hide_text_after_double_hash: false, wrap_width: 0.0f);
3309
3310 //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));
3311 //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));
3312 //draw_list->AddLine(ImVec2(clip_max_x, pos_min.y), ImVec2(clip_max_x, pos_max.y), IM_COL32(255, 0, 0, 255));
3313 // FIXME: We could technically remove (last_glyph->AdvanceX - last_glyph->X1) from text_size.x here and save a few pixels.
3314 if (text_size.x > pos_max.x - pos_min.x)
3315 {
3316 // Hello wo...
3317 // | | |
3318 // min max ellipsis_max
3319 // <-> this is generally some padding value
3320
3321 const ImFont* font = draw_list->_Data->Font;
3322 const float font_size = draw_list->_Data->FontSize;
3323 const char* text_end_ellipsis = NULL;
3324
3325 ImWchar ellipsis_char = font->EllipsisChar;
3326 int ellipsis_char_count = 1;
3327 if (ellipsis_char == (ImWchar)-1)
3328 {
3329 ellipsis_char = font->DotChar;
3330 ellipsis_char_count = 3;
3331 }
3332 const ImFontGlyph* glyph = font->FindGlyph(c: ellipsis_char);
3333
3334 float ellipsis_glyph_width = glyph->X1; // Width of the glyph with no padding on either side
3335 float ellipsis_total_width = ellipsis_glyph_width; // Full width of entire ellipsis
3336
3337 if (ellipsis_char_count > 1)
3338 {
3339 // Full ellipsis size without free spacing after it.
3340 const float spacing_between_dots = 1.0f * (draw_list->_Data->FontSize / font->FontSize);
3341 ellipsis_glyph_width = glyph->X1 - glyph->X0 + spacing_between_dots;
3342 ellipsis_total_width = ellipsis_glyph_width * (float)ellipsis_char_count - spacing_between_dots;
3343 }
3344
3345 // We can now claim the space between pos_max.x and ellipsis_max.x
3346 const float text_avail_width = ImMax(lhs: (ImMax(lhs: pos_max.x, rhs: ellipsis_max_x) - ellipsis_total_width) - pos_min.x, rhs: 1.0f);
3347 float text_size_clipped_x = font->CalcTextSizeA(size: font_size, max_width: text_avail_width, wrap_width: 0.0f, text_begin: text, text_end: text_end_full, remaining: &text_end_ellipsis).x;
3348 if (text == text_end_ellipsis && text_end_ellipsis < text_end_full)
3349 {
3350 // Always display at least 1 character if there's no room for character + ellipsis
3351 text_end_ellipsis = text + ImTextCountUtf8BytesFromChar(in_text: text, in_text_end: text_end_full);
3352 text_size_clipped_x = font->CalcTextSizeA(size: font_size, FLT_MAX, wrap_width: 0.0f, text_begin: text, text_end: text_end_ellipsis).x;
3353 }
3354 while (text_end_ellipsis > text && ImCharIsBlankA(c: text_end_ellipsis[-1]))
3355 {
3356 // 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)
3357 text_end_ellipsis--;
3358 text_size_clipped_x -= font->CalcTextSizeA(size: font_size, FLT_MAX, wrap_width: 0.0f, text_begin: text_end_ellipsis, text_end: text_end_ellipsis + 1).x; // Ascii blanks are always 1 byte
3359 }
3360
3361 // Render text, render ellipsis
3362 RenderTextClippedEx(draw_list, pos_min, pos_max: ImVec2(clip_max_x, pos_max.y), text, text_display_end: text_end_ellipsis, text_size_if_known: &text_size, align: ImVec2(0.0f, 0.0f));
3363 float ellipsis_x = pos_min.x + text_size_clipped_x;
3364 if (ellipsis_x + ellipsis_total_width <= ellipsis_max_x)
3365 for (int i = 0; i < ellipsis_char_count; i++)
3366 {
3367 font->RenderChar(draw_list, size: font_size, pos: ImVec2(ellipsis_x, pos_min.y), col: GetColorU32(idx: ImGuiCol_Text), c: ellipsis_char);
3368 ellipsis_x += ellipsis_glyph_width;
3369 }
3370 }
3371 else
3372 {
3373 RenderTextClippedEx(draw_list, pos_min, pos_max: ImVec2(clip_max_x, pos_max.y), text, text_display_end: text_end_full, text_size_if_known: &text_size, align: ImVec2(0.0f, 0.0f));
3374 }
3375
3376 if (g.LogEnabled)
3377 LogRenderedText(ref_pos: &pos_min, text, text_end: text_end_full);
3378}
3379
3380// Render a rectangle shaped with optional rounding and borders
3381void ImGui::RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border, float rounding)
3382{
3383 ImGuiContext& g = *GImGui;
3384 ImGuiWindow* window = g.CurrentWindow;
3385 window->DrawList->AddRectFilled(p_min, p_max, col: fill_col, rounding);
3386 const float border_size = g.Style.FrameBorderSize;
3387 if (border && border_size > 0.0f)
3388 {
3389 window->DrawList->AddRect(p_min: p_min + ImVec2(1, 1), p_max: p_max + ImVec2(1, 1), col: GetColorU32(idx: ImGuiCol_BorderShadow), rounding, flags: 0, thickness: border_size);
3390 window->DrawList->AddRect(p_min, p_max, col: GetColorU32(idx: ImGuiCol_Border), rounding, flags: 0, thickness: border_size);
3391 }
3392}
3393
3394void ImGui::RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding)
3395{
3396 ImGuiContext& g = *GImGui;
3397 ImGuiWindow* window = g.CurrentWindow;
3398 const float border_size = g.Style.FrameBorderSize;
3399 if (border_size > 0.0f)
3400 {
3401 window->DrawList->AddRect(p_min: p_min + ImVec2(1, 1), p_max: p_max + ImVec2(1, 1), col: GetColorU32(idx: ImGuiCol_BorderShadow), rounding, flags: 0, thickness: border_size);
3402 window->DrawList->AddRect(p_min, p_max, col: GetColorU32(idx: ImGuiCol_Border), rounding, flags: 0, thickness: border_size);
3403 }
3404}
3405
3406void ImGui::RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFlags flags)
3407{
3408 ImGuiContext& g = *GImGui;
3409 if (id != g.NavId)
3410 return;
3411 if (g.NavDisableHighlight && !(flags & ImGuiNavHighlightFlags_AlwaysDraw))
3412 return;
3413 ImGuiWindow* window = g.CurrentWindow;
3414 if (window->DC.NavHideHighlightOneFrame)
3415 return;
3416
3417 float rounding = (flags & ImGuiNavHighlightFlags_NoRounding) ? 0.0f : g.Style.FrameRounding;
3418 ImRect display_rect = bb;
3419 display_rect.ClipWith(r: window->ClipRect);
3420 if (flags & ImGuiNavHighlightFlags_TypeDefault)
3421 {
3422 const float THICKNESS = 2.0f;
3423 const float DISTANCE = 3.0f + THICKNESS * 0.5f;
3424 display_rect.Expand(amount: ImVec2(DISTANCE, DISTANCE));
3425 bool fully_visible = window->ClipRect.Contains(r: display_rect);
3426 if (!fully_visible)
3427 window->DrawList->PushClipRect(clip_rect_min: display_rect.Min, clip_rect_max: display_rect.Max);
3428 window->DrawList->AddRect(p_min: display_rect.Min + ImVec2(THICKNESS * 0.5f, THICKNESS * 0.5f), p_max: display_rect.Max - ImVec2(THICKNESS * 0.5f, THICKNESS * 0.5f), col: GetColorU32(idx: ImGuiCol_NavHighlight), rounding, flags: 0, thickness: THICKNESS);
3429 if (!fully_visible)
3430 window->DrawList->PopClipRect();
3431 }
3432 if (flags & ImGuiNavHighlightFlags_TypeThin)
3433 {
3434 window->DrawList->AddRect(p_min: display_rect.Min, p_max: display_rect.Max, col: GetColorU32(idx: ImGuiCol_NavHighlight), rounding, flags: 0, thickness: 1.0f);
3435 }
3436}
3437
3438void ImGui::RenderMouseCursor(ImVec2 base_pos, float base_scale, ImGuiMouseCursor mouse_cursor, ImU32 col_fill, ImU32 col_border, ImU32 col_shadow)
3439{
3440 ImGuiContext& g = *GImGui;
3441 IM_ASSERT(mouse_cursor > ImGuiMouseCursor_None && mouse_cursor < ImGuiMouseCursor_COUNT);
3442 ImFontAtlas* font_atlas = g.DrawListSharedData.Font->ContainerAtlas;
3443 for (int n = 0; n < g.Viewports.Size; n++)
3444 {
3445 // We scale cursor with current viewport/monitor, however Windows 10 for its own hardware cursor seems to be using a different scale factor.
3446 ImVec2 offset, size, uv[4];
3447 if (!font_atlas->GetMouseCursorTexData(cursor: mouse_cursor, out_offset: &offset, out_size: &size, out_uv_border: &uv[0], out_uv_fill: &uv[2]))
3448 continue;
3449 ImGuiViewportP* viewport = g.Viewports[n];
3450 const ImVec2 pos = base_pos - offset;
3451 const float scale = base_scale * viewport->DpiScale;
3452 if (!viewport->GetMainRect().Overlaps(r: ImRect(pos, pos + ImVec2(size.x + 2, size.y + 2) * scale)))
3453 continue;
3454 ImDrawList* draw_list = GetForegroundDrawList(viewport);
3455 ImTextureID tex_id = font_atlas->TexID;
3456 draw_list->PushTextureID(texture_id: tex_id);
3457 draw_list->AddImage(user_texture_id: tex_id, p_min: pos + ImVec2(1, 0) * scale, p_max: pos + (ImVec2(1, 0) + size) * scale, uv_min: uv[2], uv_max: uv[3], col: col_shadow);
3458 draw_list->AddImage(user_texture_id: tex_id, p_min: pos + ImVec2(2, 0) * scale, p_max: pos + (ImVec2(2, 0) + size) * scale, uv_min: uv[2], uv_max: uv[3], col: col_shadow);
3459 draw_list->AddImage(user_texture_id: tex_id, p_min: pos, p_max: pos + size * scale, uv_min: uv[2], uv_max: uv[3], col: col_border);
3460 draw_list->AddImage(user_texture_id: tex_id, p_min: pos, p_max: pos + size * scale, uv_min: uv[0], uv_max: uv[1], col: col_fill);
3461 draw_list->PopTextureID();
3462 }
3463}
3464
3465
3466//-----------------------------------------------------------------------------
3467// [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!)
3468//-----------------------------------------------------------------------------
3469
3470// ImGuiWindow is mostly a dumb struct. It merely has a constructor and a few helper methods
3471ImGuiWindow::ImGuiWindow(ImGuiContext* context, const char* name) : DrawListInst(NULL)
3472{
3473 memset(s: this, c: 0, n: sizeof(*this));
3474 Name = ImStrdup(str: name);
3475 NameBufLen = (int)strlen(s: name) + 1;
3476 ID = ImHashStr(data_p: name);
3477 IDStack.push_back(v: ID);
3478 ViewportAllowPlatformMonitorExtend = -1;
3479 ViewportPos = ImVec2(FLT_MAX, FLT_MAX);
3480 MoveId = GetID(str: "#MOVE");
3481 TabId = GetID(str: "#TAB");
3482 ScrollTarget = ImVec2(FLT_MAX, FLT_MAX);
3483 ScrollTargetCenterRatio = ImVec2(0.5f, 0.5f);
3484 AutoFitFramesX = AutoFitFramesY = -1;
3485 AutoPosLastDirection = ImGuiDir_None;
3486 SetWindowPosAllowFlags = SetWindowSizeAllowFlags = SetWindowCollapsedAllowFlags = SetWindowDockAllowFlags = ImGuiCond_Always | ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing;
3487 SetWindowPosVal = SetWindowPosPivot = ImVec2(FLT_MAX, FLT_MAX);
3488 LastFrameActive = -1;
3489 LastFrameJustFocused = -1;
3490 LastTimeActive = -1.0f;
3491 FontWindowScale = FontDpiScale = 1.0f;
3492 SettingsOffset = -1;
3493 DockOrder = -1;
3494 DrawList = &DrawListInst;
3495 DrawList->_Data = &context->DrawListSharedData;
3496 DrawList->_OwnerName = Name;
3497 IM_PLACEMENT_NEW(&WindowClass) ImGuiWindowClass();
3498}
3499
3500ImGuiWindow::~ImGuiWindow()
3501{
3502 IM_ASSERT(DrawList == &DrawListInst);
3503 IM_DELETE(p: Name);
3504 ColumnsStorage.clear_destruct();
3505}
3506
3507ImGuiID ImGuiWindow::GetID(const char* str, const char* str_end)
3508{
3509 ImGuiID seed = IDStack.back();
3510 ImGuiID id = ImHashStr(data_p: str, data_size: str_end ? (str_end - str) : 0, seed);
3511 ImGuiContext& g = *GImGui;
3512 if (g.DebugHookIdInfo == id)
3513 ImGui::DebugHookIdInfo(id, data_type: ImGuiDataType_String, data_id: str, data_id_end: str_end);
3514 return id;
3515}
3516
3517ImGuiID ImGuiWindow::GetID(const void* ptr)
3518{
3519 ImGuiID seed = IDStack.back();
3520 ImGuiID id = ImHashData(data_p: &ptr, data_size: sizeof(void*), seed);
3521 ImGuiContext& g = *GImGui;
3522 if (g.DebugHookIdInfo == id)
3523 ImGui::DebugHookIdInfo(id, data_type: ImGuiDataType_Pointer, data_id: ptr, NULL);
3524 return id;
3525}
3526
3527ImGuiID ImGuiWindow::GetID(int n)
3528{
3529 ImGuiID seed = IDStack.back();
3530 ImGuiID id = ImHashData(data_p: &n, data_size: sizeof(n), seed);
3531 ImGuiContext& g = *GImGui;
3532 if (g.DebugHookIdInfo == id)
3533 ImGui::DebugHookIdInfo(id, data_type: ImGuiDataType_S32, data_id: (void*)(intptr_t)n, NULL);
3534 return id;
3535}
3536
3537// This is only used in rare/specific situations to manufacture an ID out of nowhere.
3538ImGuiID ImGuiWindow::GetIDFromRectangle(const ImRect& r_abs)
3539{
3540 ImGuiID seed = IDStack.back();
3541 ImRect r_rel = ImGui::WindowRectAbsToRel(window: this, r: r_abs);
3542 ImGuiID id = ImHashData(data_p: &r_rel, data_size: sizeof(r_rel), seed);
3543 return id;
3544}
3545
3546static void SetCurrentWindow(ImGuiWindow* window)
3547{
3548 ImGuiContext& g = *GImGui;
3549 g.CurrentWindow = window;
3550 g.CurrentTable = window && window->DC.CurrentTableIdx != -1 ? g.Tables.GetByIndex(n: window->DC.CurrentTableIdx) : NULL;
3551 if (window)
3552 g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize();
3553}
3554
3555void ImGui::GcCompactTransientMiscBuffers()
3556{
3557 ImGuiContext& g = *GImGui;
3558 g.ItemFlagsStack.clear();
3559 g.GroupStack.clear();
3560 TableGcCompactSettings();
3561}
3562
3563// Free up/compact internal window buffers, we can use this when a window becomes unused.
3564// Not freed:
3565// - ImGuiWindow, ImGuiWindowSettings, Name, StateStorage, ColumnsStorage (may hold useful data)
3566// This should have no noticeable visual effect. When the window reappear however, expect new allocation/buffer growth/copy cost.
3567void ImGui::GcCompactTransientWindowBuffers(ImGuiWindow* window)
3568{
3569 window->MemoryCompacted = true;
3570 window->MemoryDrawListIdxCapacity = window->DrawList->IdxBuffer.Capacity;
3571 window->MemoryDrawListVtxCapacity = window->DrawList->VtxBuffer.Capacity;
3572 window->IDStack.clear();
3573 window->DrawList->_ClearFreeMemory();
3574 window->DC.ChildWindows.clear();
3575 window->DC.ItemWidthStack.clear();
3576 window->DC.TextWrapPosStack.clear();
3577}
3578
3579void ImGui::GcAwakeTransientWindowBuffers(ImGuiWindow* window)
3580{
3581 // We stored capacity of the ImDrawList buffer to reduce growth-caused allocation/copy when awakening.
3582 // The other buffers tends to amortize much faster.
3583 window->MemoryCompacted = false;
3584 window->DrawList->IdxBuffer.reserve(new_capacity: window->MemoryDrawListIdxCapacity);
3585 window->DrawList->VtxBuffer.reserve(new_capacity: window->MemoryDrawListVtxCapacity);
3586 window->MemoryDrawListIdxCapacity = window->MemoryDrawListVtxCapacity = 0;
3587}
3588
3589void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window)
3590{
3591 ImGuiContext& g = *GImGui;
3592
3593 // While most behaved code would make an effort to not steal active id during window move/drag operations,
3594 // we at least need to be resilient to it. Cancelling the move is rather aggressive and users of 'master' branch
3595 // may prefer the weird ill-defined half working situation ('docking' did assert), so may need to rework that.
3596 if (g.MovingWindow != NULL && g.ActiveId == g.MovingWindow->MoveId)
3597 {
3598 IMGUI_DEBUG_LOG_ACTIVEID("SetActiveID() cancel MovingWindow\n");
3599 g.MovingWindow = NULL;
3600 }
3601
3602 // Set active id
3603 g.ActiveIdIsJustActivated = (g.ActiveId != id);
3604 if (g.ActiveIdIsJustActivated)
3605 {
3606 IMGUI_DEBUG_LOG_ACTIVEID("SetActiveID() old:0x%08X (window \"%s\") -> new:0x%08X (window \"%s\")\n", g.ActiveId, g.ActiveIdWindow ? g.ActiveIdWindow->Name : "", id, window ? window->Name : "");
3607 g.ActiveIdTimer = 0.0f;
3608 g.ActiveIdHasBeenPressedBefore = false;
3609 g.ActiveIdHasBeenEditedBefore = false;
3610 g.ActiveIdMouseButton = -1;
3611 if (id != 0)
3612 {
3613 g.LastActiveId = id;
3614 g.LastActiveIdTimer = 0.0f;
3615 }
3616 }
3617 g.ActiveId = id;
3618 g.ActiveIdAllowOverlap = false;
3619 g.ActiveIdNoClearOnFocusLoss = false;
3620 g.ActiveIdWindow = window;
3621 g.ActiveIdHasBeenEditedThisFrame = false;
3622 if (id)
3623 {
3624 g.ActiveIdIsAlive = id;
3625 g.ActiveIdSource = (g.NavActivateId == id || g.NavActivateInputId == id || g.NavJustMovedToId == id) ? (ImGuiInputSource)ImGuiInputSource_Nav : ImGuiInputSource_Mouse;
3626 }
3627
3628 // Clear declaration of inputs claimed by the widget
3629 // (Please note that this is WIP and not all keys/inputs are thoroughly declared by all widgets yet)
3630 g.ActiveIdUsingNavDirMask = 0x00;
3631 g.ActiveIdUsingAllKeyboardKeys = false;
3632#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO
3633 g.ActiveIdUsingNavInputMask = 0x00;
3634#endif
3635}
3636
3637void ImGui::ClearActiveID()
3638{
3639 SetActiveID(id: 0, NULL); // g.ActiveId = 0;
3640}
3641
3642void ImGui::SetHoveredID(ImGuiID id)
3643{
3644 ImGuiContext& g = *GImGui;
3645 g.HoveredId = id;
3646 g.HoveredIdAllowOverlap = false;
3647 if (id != 0 && g.HoveredIdPreviousFrame != id)
3648 g.HoveredIdTimer = g.HoveredIdNotActiveTimer = 0.0f;
3649}
3650
3651ImGuiID ImGui::GetHoveredID()
3652{
3653 ImGuiContext& g = *GImGui;
3654 return g.HoveredId ? g.HoveredId : g.HoveredIdPreviousFrame;
3655}
3656
3657// This is called by ItemAdd().
3658// Code not using ItemAdd() may need to call this manually otherwise ActiveId will be cleared. In IMGUI_VERSION_NUM < 18717 this was called by GetID().
3659void ImGui::KeepAliveID(ImGuiID id)
3660{
3661 ImGuiContext& g = *GImGui;
3662 if (g.ActiveId == id)
3663 g.ActiveIdIsAlive = id;
3664 if (g.ActiveIdPreviousFrame == id)
3665 g.ActiveIdPreviousFrameIsAlive = true;
3666}
3667
3668void ImGui::MarkItemEdited(ImGuiID id)
3669{
3670 // This marking is solely to be able to provide info for IsItemDeactivatedAfterEdit().
3671 // ActiveId might have been released by the time we call this (as in the typical press/release button behavior) but still need to fill the data.
3672 ImGuiContext& g = *GImGui;
3673 IM_ASSERT(g.ActiveId == id || g.ActiveId == 0 || g.DragDropActive);
3674 IM_UNUSED(id); // Avoid unused variable warnings when asserts are compiled out.
3675 //IM_ASSERT(g.CurrentWindow->DC.LastItemId == id);
3676 g.ActiveIdHasBeenEditedThisFrame = true;
3677 g.ActiveIdHasBeenEditedBefore = true;
3678 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Edited;
3679}
3680
3681static inline bool IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flags)
3682{
3683 // An active popup disable hovering on other windows (apart from its own children)
3684 // FIXME-OPT: This could be cached/stored within the window.
3685 ImGuiContext& g = *GImGui;
3686 if (g.NavWindow)
3687 if (ImGuiWindow* focused_root_window = g.NavWindow->RootWindowDockTree)
3688 if (focused_root_window->WasActive && focused_root_window != window->RootWindowDockTree)
3689 {
3690 // For the purpose of those flags we differentiate "standard popup" from "modal popup"
3691 // NB: The 'else' is important because Modal windows are also Popups.
3692 bool want_inhibit = false;
3693 if (focused_root_window->Flags & ImGuiWindowFlags_Modal)
3694 want_inhibit = true;
3695 else if ((focused_root_window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiHoveredFlags_AllowWhenBlockedByPopup))
3696 want_inhibit = true;
3697
3698 // Inhibit hover unless the window is within the stack of our modal/popup
3699 if (want_inhibit)
3700 if (!ImGui::IsWindowWithinBeginStackOf(window: window->RootWindow, potential_parent: focused_root_window))
3701 return false;
3702 }
3703
3704 // Filter by viewport
3705 if (window->Viewport != g.MouseViewport)
3706 if (g.MovingWindow == NULL || window->RootWindowDockTree != g.MovingWindow->RootWindowDockTree)
3707 return false;
3708
3709 return true;
3710}
3711
3712// This is roughly matching the behavior of internal-facing ItemHoverable()
3713// - 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()
3714// - this should work even for non-interactive items that have no ID, so we cannot use LastItemId
3715bool ImGui::IsItemHovered(ImGuiHoveredFlags flags)
3716{
3717 ImGuiContext& g = *GImGui;
3718 ImGuiWindow* window = g.CurrentWindow;
3719 if (g.NavDisableMouseHover && !g.NavDisableHighlight && !(flags & ImGuiHoveredFlags_NoNavOverride))
3720 {
3721 if ((g.LastItemData.InFlags & ImGuiItemFlags_Disabled) && !(flags & ImGuiHoveredFlags_AllowWhenDisabled))
3722 return false;
3723 if (!IsItemFocused())
3724 return false;
3725 }
3726 else
3727 {
3728 // Test for bounding box overlap, as updated as ItemAdd()
3729 ImGuiItemStatusFlags status_flags = g.LastItemData.StatusFlags;
3730 if (!(status_flags & ImGuiItemStatusFlags_HoveredRect))
3731 return false;
3732 IM_ASSERT((flags & (ImGuiHoveredFlags_AnyWindow | ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_NoPopupHierarchy | ImGuiHoveredFlags_DockHierarchy)) == 0); // Flags not supported by this function
3733
3734 // Done with rectangle culling so we can perform heavier checks now
3735 // Test if we are hovering the right window (our window could be behind another window)
3736 // [2021/03/02] Reworked / reverted the revert, finally. Note we want e.g. BeginGroup/ItemAdd/EndGroup to work as well. (#3851)
3737 // [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
3738 // 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
3739 // the test that has been running for a long while.
3740 if (g.HoveredWindow != window && (status_flags & ImGuiItemStatusFlags_HoveredWindow) == 0)
3741 if ((flags & ImGuiHoveredFlags_AllowWhenOverlapped) == 0)
3742 return false;
3743
3744 // Test if another item is active (e.g. being dragged)
3745 if ((flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem) == 0)
3746 if (g.ActiveId != 0 && g.ActiveId != g.LastItemData.ID && !g.ActiveIdAllowOverlap)
3747 if (g.ActiveId != window->MoveId && g.ActiveId != window->TabId)
3748 return false;
3749
3750 // Test if interactions on this window are blocked by an active popup or modal.
3751 // The ImGuiHoveredFlags_AllowWhenBlockedByPopup flag will be tested here.
3752 if (!IsWindowContentHoverable(window, flags) && !(g.LastItemData.InFlags & ImGuiItemFlags_NoWindowHoverableCheck))
3753 return false;
3754
3755 // Test if the item is disabled
3756 if ((g.LastItemData.InFlags & ImGuiItemFlags_Disabled) && !(flags & ImGuiHoveredFlags_AllowWhenDisabled))
3757 return false;
3758
3759 // Special handling for calling after Begin() which represent the title bar or tab.
3760 // When the window is skipped/collapsed (SkipItems==true) that last item (always ->MoveId submitted by Begin)
3761 // will never be overwritten so we need to detect the case.
3762 if (g.LastItemData.ID == window->MoveId && window->WriteAccessed)
3763 return false;
3764 }
3765
3766 // Handle hover delay
3767 // (some ideas: https://www.nngroup.com/articles/timing-exposing-content)
3768 float delay;
3769 if (flags & ImGuiHoveredFlags_DelayNormal)
3770 delay = g.IO.HoverDelayNormal;
3771 else if (flags & ImGuiHoveredFlags_DelayShort)
3772 delay = g.IO.HoverDelayShort;
3773 else
3774 delay = 0.0f;
3775 if (delay > 0.0f)
3776 {
3777 ImGuiID hover_delay_id = (g.LastItemData.ID != 0) ? g.LastItemData.ID : window->GetIDFromRectangle(r_abs: g.LastItemData.Rect);
3778 if ((flags & ImGuiHoveredFlags_NoSharedDelay) && (g.HoverDelayIdPreviousFrame != hover_delay_id))
3779 g.HoverDelayTimer = 0.0f;
3780 g.HoverDelayId = hover_delay_id;
3781 return g.HoverDelayTimer >= delay;
3782 }
3783
3784 return true;
3785}
3786
3787// Internal facing ItemHoverable() used when submitting widgets. Differs slightly from IsItemHovered().
3788bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id)
3789{
3790 ImGuiContext& g = *GImGui;
3791 if (g.HoveredId != 0 && g.HoveredId != id && !g.HoveredIdAllowOverlap)
3792 return false;
3793
3794 ImGuiWindow* window = g.CurrentWindow;
3795 if (g.HoveredWindow != window)
3796 return false;
3797 if (g.ActiveId != 0 && g.ActiveId != id && !g.ActiveIdAllowOverlap)
3798 return false;
3799 if (!IsMouseHoveringRect(r_min: bb.Min, r_max: bb.Max))
3800 return false;
3801
3802 // Done with rectangle culling so we can perform heavier checks now.
3803 ImGuiItemFlags item_flags = (g.LastItemData.ID == id ? g.LastItemData.InFlags : g.CurrentItemFlags);
3804 if (!(item_flags & ImGuiItemFlags_NoWindowHoverableCheck) && !IsWindowContentHoverable(window, flags: ImGuiHoveredFlags_None))
3805 {
3806 g.HoveredIdDisabled = true;
3807 return false;
3808 }
3809
3810 // We exceptionally allow this function to be called with id==0 to allow using it for easy high-level
3811 // hover test in widgets code. We could also decide to split this function is two.
3812 if (id != 0)
3813 SetHoveredID(id);
3814
3815 // When disabled we'll return false but still set HoveredId
3816 if (item_flags & ImGuiItemFlags_Disabled)
3817 {
3818 // Release active id if turning disabled
3819 if (g.ActiveId == id)
3820 ClearActiveID();
3821 g.HoveredIdDisabled = true;
3822 return false;
3823 }
3824
3825 if (id != 0)
3826 {
3827 // [DEBUG] Item Picker tool!
3828 // We perform the check here because SetHoveredID() is not frequently called (1~ time a frame), making
3829 // the cost of this tool near-zero. We can get slightly better call-stack and support picking non-hovered
3830 // items if we performed the test in ItemAdd(), but that would incur a small runtime cost.
3831 if (g.DebugItemPickerActive && g.HoveredIdPreviousFrame == id)
3832 GetForegroundDrawList()->AddRect(p_min: bb.Min, p_max: bb.Max, IM_COL32(255, 255, 0, 255));
3833 if (g.DebugItemPickerBreakId == id)
3834 IM_DEBUG_BREAK();
3835 }
3836
3837 if (g.NavDisableMouseHover)
3838 return false;
3839
3840 return true;
3841}
3842
3843// FIXME: This is inlined/duplicated in ItemAdd()
3844bool ImGui::IsClippedEx(const ImRect& bb, ImGuiID id)
3845{
3846 ImGuiContext& g = *GImGui;
3847 ImGuiWindow* window = g.CurrentWindow;
3848 if (!bb.Overlaps(r: window->ClipRect))
3849 if (id == 0 || (id != g.ActiveId && id != g.NavId))
3850 if (!g.LogEnabled)
3851 return true;
3852 return false;
3853}
3854
3855// This is also inlined in ItemAdd()
3856// Note: if ImGuiItemStatusFlags_HasDisplayRect is set, user needs to set window->DC.LastItemDisplayRect!
3857void ImGui::SetLastItemData(ImGuiID item_id, ImGuiItemFlags in_flags, ImGuiItemStatusFlags item_flags, const ImRect& item_rect)
3858{
3859 ImGuiContext& g = *GImGui;
3860 g.LastItemData.ID = item_id;
3861 g.LastItemData.InFlags = in_flags;
3862 g.LastItemData.StatusFlags = item_flags;
3863 g.LastItemData.Rect = item_rect;
3864}
3865
3866float ImGui::CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x)
3867{
3868 if (wrap_pos_x < 0.0f)
3869 return 0.0f;
3870
3871 ImGuiContext& g = *GImGui;
3872 ImGuiWindow* window = g.CurrentWindow;
3873 if (wrap_pos_x == 0.0f)
3874 {
3875 // We could decide to setup a default wrapping max point for auto-resizing windows,
3876 // or have auto-wrap (with unspecified wrapping pos) behave as a ContentSize extending function?
3877 //if (window->Hidden && (window->Flags & ImGuiWindowFlags_AlwaysAutoResize))
3878 // wrap_pos_x = ImMax(window->WorkRect.Min.x + g.FontSize * 10.0f, window->WorkRect.Max.x);
3879 //else
3880 wrap_pos_x = window->WorkRect.Max.x;
3881 }
3882 else if (wrap_pos_x > 0.0f)
3883 {
3884 wrap_pos_x += window->Pos.x - window->Scroll.x; // wrap_pos_x is provided is window local space
3885 }
3886
3887 return ImMax(lhs: wrap_pos_x - pos.x, rhs: 1.0f);
3888}
3889
3890// IM_ALLOC() == ImGui::MemAlloc()
3891void* ImGui::MemAlloc(size_t size)
3892{
3893 if (ImGuiContext* ctx = GImGui)
3894 ctx->IO.MetricsActiveAllocations++;
3895 return (*GImAllocatorAllocFunc)(size, GImAllocatorUserData);
3896}
3897
3898// IM_FREE() == ImGui::MemFree()
3899void ImGui::MemFree(void* ptr)
3900{
3901 if (ptr)
3902 if (ImGuiContext* ctx = GImGui)
3903 ctx->IO.MetricsActiveAllocations--;
3904 return (*GImAllocatorFreeFunc)(ptr, GImAllocatorUserData);
3905}
3906
3907const char* ImGui::GetClipboardText()
3908{
3909 ImGuiContext& g = *GImGui;
3910 return g.IO.GetClipboardTextFn ? g.IO.GetClipboardTextFn(g.IO.ClipboardUserData) : "";
3911}
3912
3913void ImGui::SetClipboardText(const char* text)
3914{
3915 ImGuiContext& g = *GImGui;
3916 if (g.IO.SetClipboardTextFn)
3917 g.IO.SetClipboardTextFn(g.IO.ClipboardUserData, text);
3918}
3919
3920const char* ImGui::GetVersion()
3921{
3922 return IMGUI_VERSION;
3923}
3924
3925// Internal state access - if you want to share Dear ImGui state between modules (e.g. DLL) or allocate it yourself
3926// 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
3927ImGuiContext* ImGui::GetCurrentContext()
3928{
3929 return GImGui;
3930}
3931
3932void ImGui::SetCurrentContext(ImGuiContext* ctx)
3933{
3934#ifdef IMGUI_SET_CURRENT_CONTEXT_FUNC
3935 IMGUI_SET_CURRENT_CONTEXT_FUNC(ctx); // For custom thread-based hackery you may want to have control over this.
3936#else
3937 GImGui = ctx;
3938#endif
3939}
3940
3941void ImGui::SetAllocatorFunctions(ImGuiMemAllocFunc alloc_func, ImGuiMemFreeFunc free_func, void* user_data)
3942{
3943 GImAllocatorAllocFunc = alloc_func;
3944 GImAllocatorFreeFunc = free_func;
3945 GImAllocatorUserData = user_data;
3946}
3947
3948// This is provided to facilitate copying allocators from one static/DLL boundary to another (e.g. retrieve default allocator of your executable address space)
3949void ImGui::GetAllocatorFunctions(ImGuiMemAllocFunc* p_alloc_func, ImGuiMemFreeFunc* p_free_func, void** p_user_data)
3950{
3951 *p_alloc_func = GImAllocatorAllocFunc;
3952 *p_free_func = GImAllocatorFreeFunc;
3953 *p_user_data = GImAllocatorUserData;
3954}
3955
3956ImGuiContext* ImGui::CreateContext(ImFontAtlas* shared_font_atlas)
3957{
3958 ImGuiContext* prev_ctx = GetCurrentContext();
3959 ImGuiContext* ctx = IM_NEW(ImGuiContext)(shared_font_atlas);
3960 SetCurrentContext(ctx);
3961 Initialize();
3962 if (prev_ctx != NULL)
3963 SetCurrentContext(prev_ctx); // Restore previous context if any, else keep new one.
3964 return ctx;
3965}
3966
3967void ImGui::DestroyContext(ImGuiContext* ctx)
3968{
3969 ImGuiContext* prev_ctx = GetCurrentContext();
3970 if (ctx == NULL) //-V1051
3971 ctx = prev_ctx;
3972 SetCurrentContext(ctx);
3973 Shutdown();
3974 SetCurrentContext((prev_ctx != ctx) ? prev_ctx : NULL);
3975 IM_DELETE(p: ctx);
3976}
3977
3978// No specific ordering/dependency support, will see as needed
3979ImGuiID ImGui::AddContextHook(ImGuiContext* ctx, const ImGuiContextHook* hook)
3980{
3981 ImGuiContext& g = *ctx;
3982 IM_ASSERT(hook->Callback != NULL && hook->HookId == 0 && hook->Type != ImGuiContextHookType_PendingRemoval_);
3983 g.Hooks.push_back(v: *hook);
3984 g.Hooks.back().HookId = ++g.HookIdNext;
3985 return g.HookIdNext;
3986}
3987
3988// Deferred removal, avoiding issue with changing vector while iterating it
3989void ImGui::RemoveContextHook(ImGuiContext* ctx, ImGuiID hook_id)
3990{
3991 ImGuiContext& g = *ctx;
3992 IM_ASSERT(hook_id != 0);
3993 for (int n = 0; n < g.Hooks.Size; n++)
3994 if (g.Hooks[n].HookId == hook_id)
3995 g.Hooks[n].Type = ImGuiContextHookType_PendingRemoval_;
3996}
3997
3998// Call context hooks (used by e.g. test engine)
3999// We assume a small number of hooks so all stored in same array
4000void ImGui::CallContextHooks(ImGuiContext* ctx, ImGuiContextHookType hook_type)
4001{
4002 ImGuiContext& g = *ctx;
4003 for (int n = 0; n < g.Hooks.Size; n++)
4004 if (g.Hooks[n].Type == hook_type)
4005 g.Hooks[n].Callback(&g, &g.Hooks[n]);
4006}
4007
4008ImGuiIO& ImGui::GetIO()
4009{
4010 IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext() ?");
4011 return GImGui->IO;
4012}
4013
4014ImGuiPlatformIO& ImGui::GetPlatformIO()
4015{
4016 IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() or ImGui::SetCurrentContext()?");
4017 return GImGui->PlatformIO;
4018}
4019
4020// Pass this to your backend rendering function! Valid after Render() and until the next call to NewFrame()
4021ImDrawData* ImGui::GetDrawData()
4022{
4023 ImGuiContext& g = *GImGui;
4024 ImGuiViewportP* viewport = g.Viewports[0];
4025 return viewport->DrawDataP.Valid ? &viewport->DrawDataP : NULL;
4026}
4027
4028double ImGui::GetTime()
4029{
4030 return GImGui->Time;
4031}
4032
4033int ImGui::GetFrameCount()
4034{
4035 return GImGui->FrameCount;
4036}
4037
4038static ImDrawList* GetViewportDrawList(ImGuiViewportP* viewport, size_t drawlist_no, const char* drawlist_name)
4039{
4040 // Create the draw list on demand, because they are not frequently used for all viewports
4041 ImGuiContext& g = *GImGui;
4042 IM_ASSERT(drawlist_no < IM_ARRAYSIZE(viewport->DrawLists));
4043 ImDrawList* draw_list = viewport->DrawLists[drawlist_no];
4044 if (draw_list == NULL)
4045 {
4046 draw_list = IM_NEW(ImDrawList)(&g.DrawListSharedData);
4047 draw_list->_OwnerName = drawlist_name;
4048 viewport->DrawLists[drawlist_no] = draw_list;
4049 }
4050
4051 // Our ImDrawList system requires that there is always a command
4052 if (viewport->DrawListsLastFrame[drawlist_no] != g.FrameCount)
4053 {
4054 draw_list->_ResetForNewFrame();
4055 draw_list->PushTextureID(texture_id: g.IO.Fonts->TexID);
4056 draw_list->PushClipRect(clip_rect_min: viewport->Pos, clip_rect_max: viewport->Pos + viewport->Size, intersect_with_current_clip_rect: false);
4057 viewport->DrawListsLastFrame[drawlist_no] = g.FrameCount;
4058 }
4059 return draw_list;
4060}
4061
4062ImDrawList* ImGui::GetBackgroundDrawList(ImGuiViewport* viewport)
4063{
4064 return GetViewportDrawList(viewport: (ImGuiViewportP*)viewport, drawlist_no: 0, drawlist_name: "##Background");
4065}
4066
4067ImDrawList* ImGui::GetBackgroundDrawList()
4068{
4069 ImGuiContext& g = *GImGui;
4070 return GetBackgroundDrawList(viewport: g.CurrentWindow->Viewport);
4071}
4072
4073ImDrawList* ImGui::GetForegroundDrawList(ImGuiViewport* viewport)
4074{
4075 return GetViewportDrawList(viewport: (ImGuiViewportP*)viewport, drawlist_no: 1, drawlist_name: "##Foreground");
4076}
4077
4078ImDrawList* ImGui::GetForegroundDrawList()
4079{
4080 ImGuiContext& g = *GImGui;
4081 return GetForegroundDrawList(viewport: g.CurrentWindow->Viewport);
4082}
4083
4084ImDrawListSharedData* ImGui::GetDrawListSharedData()
4085{
4086 return &GImGui->DrawListSharedData;
4087}
4088
4089void ImGui::StartMouseMovingWindow(ImGuiWindow* window)
4090{
4091 // Set ActiveId even if the _NoMove flag is set. Without it, dragging away from a window with _NoMove would activate hover on other windows.
4092 // We _also_ call this when clicking in a window empty space when io.ConfigWindowsMoveFromTitleBarOnly is set, but clear g.MovingWindow afterward.
4093 // This is because we want ActiveId to be set even when the window is not permitted to move.
4094 ImGuiContext& g = *GImGui;
4095 FocusWindow(window);
4096 SetActiveID(id: window->MoveId, window);
4097 g.NavDisableHighlight = true;
4098 g.ActiveIdClickOffset = g.IO.MouseClickedPos[0] - window->RootWindowDockTree->Pos;
4099 g.ActiveIdNoClearOnFocusLoss = true;
4100 SetActiveIdUsingAllKeyboardKeys();
4101
4102 bool can_move_window = true;
4103 if ((window->Flags & ImGuiWindowFlags_NoMove) || (window->RootWindowDockTree->Flags & ImGuiWindowFlags_NoMove))
4104 can_move_window = false;
4105 if (ImGuiDockNode* node = window->DockNodeAsHost)
4106 if (node->VisibleWindow && (node->VisibleWindow->Flags & ImGuiWindowFlags_NoMove))
4107 can_move_window = false;
4108 if (can_move_window)
4109 g.MovingWindow = window;
4110}
4111
4112// We use 'undock_floating_node == false' when dragging from title bar to allow moving groups of floating nodes without undocking them.
4113// - undock_floating_node == true: when dragging from a floating node within a hierarchy, always undock the node.
4114// - undock_floating_node == false: when dragging from a floating node within a hierarchy, move root window.
4115void ImGui::StartMouseMovingWindowOrNode(ImGuiWindow* window, ImGuiDockNode* node, bool undock_floating_node)
4116{
4117 ImGuiContext& g = *GImGui;
4118 bool can_undock_node = false;
4119 if (node != NULL && node->VisibleWindow && (node->VisibleWindow->Flags & ImGuiWindowFlags_NoMove) == 0)
4120 {
4121 // Can undock if:
4122 // - part of a floating node hierarchy with more than one visible node (if only one is visible, we'll just move the whole hierarchy)
4123 // - part of a dockspace node hierarchy (trivia: undocking from a fixed/central node will create a new node and copy windows)
4124 ImGuiDockNode* root_node = DockNodeGetRootNode(node);
4125 if (root_node->OnlyNodeWithWindows != node || root_node->CentralNode != NULL) // -V1051 PVS-Studio thinks node should be root_node and is wrong about that.
4126 if (undock_floating_node || root_node->IsDockSpace())
4127 can_undock_node = true;
4128 }
4129
4130 const bool clicked = IsMouseClicked(button: 0);
4131 const bool dragging = IsMouseDragging(button: 0, lock_threshold: g.IO.MouseDragThreshold * 1.70f);
4132 if (can_undock_node && dragging)
4133 DockContextQueueUndockNode(ctx: &g, node); // Will lead to DockNodeStartMouseMovingWindow() -> StartMouseMovingWindow() being called next frame
4134 else if (!can_undock_node && (clicked || dragging) && g.MovingWindow != window)
4135 StartMouseMovingWindow(window);
4136}
4137
4138// Handle mouse moving window
4139// Note: moving window with the navigation keys (Square + d-pad / CTRL+TAB + Arrows) are processed in NavUpdateWindowing()
4140// FIXME: We don't have strong guarantee that g.MovingWindow stay synched with g.ActiveId == g.MovingWindow->MoveId.
4141// This is currently enforced by the fact that BeginDragDropSource() is setting all g.ActiveIdUsingXXXX flags to inhibit navigation inputs,
4142// but if we should more thoroughly test cases where g.ActiveId or g.MovingWindow gets changed and not the other.
4143void ImGui::UpdateMouseMovingWindowNewFrame()
4144{
4145 ImGuiContext& g = *GImGui;
4146 if (g.MovingWindow != NULL)
4147 {
4148 // We actually want to move the root window. g.MovingWindow == window we clicked on (could be a child window).
4149 // We track it to preserve Focus and so that generally ActiveIdWindow == MovingWindow and ActiveId == MovingWindow->MoveId for consistency.
4150 KeepAliveID(id: g.ActiveId);
4151 IM_ASSERT(g.MovingWindow && g.MovingWindow->RootWindowDockTree);
4152 ImGuiWindow* moving_window = g.MovingWindow->RootWindowDockTree;
4153
4154 // When a window stop being submitted while being dragged, it may will its viewport until next Begin()
4155 const bool window_disappared = ((!moving_window->WasActive && !moving_window->Active) || moving_window->Viewport == NULL);
4156 if (g.IO.MouseDown[0] && IsMousePosValid(mouse_pos: &g.IO.MousePos) && !window_disappared)
4157 {
4158 ImVec2 pos = g.IO.MousePos - g.ActiveIdClickOffset;
4159 if (moving_window->Pos.x != pos.x || moving_window->Pos.y != pos.y)
4160 {
4161 SetWindowPos(window: moving_window, pos, cond: ImGuiCond_Always);
4162 if (moving_window->ViewportOwned) // Synchronize viewport immediately because some overlays may relies on clipping rectangle before we Begin() into the window.
4163 {
4164 moving_window->Viewport->Pos = pos;
4165 moving_window->Viewport->UpdateWorkRect();
4166 }
4167 }
4168 FocusWindow(window: g.MovingWindow);
4169 }
4170 else
4171 {
4172 if (!window_disappared)
4173 {
4174 // Try to merge the window back into the main viewport.
4175 // This works because MouseViewport should be != MovingWindow->Viewport on release (as per code in UpdateViewports)
4176 if (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable)
4177 UpdateTryMergeWindowIntoHostViewport(window: moving_window, host_viewport: g.MouseViewport);
4178
4179 // Restore the mouse viewport so that we don't hover the viewport _under_ the moved window during the frame we released the mouse button.
4180 if (!IsDragDropPayloadBeingAccepted())
4181 g.MouseViewport = moving_window->Viewport;
4182
4183 // Clear the NoInput window flag set by the Viewport system
4184 moving_window->Viewport->Flags &= ~ImGuiViewportFlags_NoInputs; // FIXME-VIEWPORT: Test engine managed to crash here because Viewport was NULL.
4185 }
4186
4187 g.MovingWindow = NULL;
4188 ClearActiveID();
4189 }
4190 }
4191 else
4192 {
4193 // When clicking/dragging from a window that has the _NoMove flag, we still set the ActiveId in order to prevent hovering others.
4194 if (g.ActiveIdWindow && g.ActiveIdWindow->MoveId == g.ActiveId)
4195 {
4196 KeepAliveID(id: g.ActiveId);
4197 if (!g.IO.MouseDown[0])
4198 ClearActiveID();
4199 }
4200 }
4201}
4202
4203// Initiate moving window when clicking on empty space or title bar.
4204// Handle left-click and right-click focus.
4205void ImGui::UpdateMouseMovingWindowEndFrame()
4206{
4207 ImGuiContext& g = *GImGui;
4208 if (g.ActiveId != 0 || g.HoveredId != 0)
4209 return;
4210
4211 // Unless we just made a window/popup appear
4212 if (g.NavWindow && g.NavWindow->Appearing)
4213 return;
4214
4215 // Click on empty space to focus window and start moving
4216 // (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!)
4217 if (g.IO.MouseClicked[0])
4218 {
4219 // Handle the edge case of a popup being closed while clicking in its empty space.
4220 // If we try to focus it, FocusWindow() > ClosePopupsOverWindow() will accidentally close any parent popups because they are not linked together any more.
4221 ImGuiWindow* root_window = g.HoveredWindow ? g.HoveredWindow->RootWindow : NULL;
4222 const bool is_closed_popup = root_window && (root_window->Flags & ImGuiWindowFlags_Popup) && !IsPopupOpen(id: root_window->PopupId, popup_flags: ImGuiPopupFlags_AnyPopupLevel);
4223
4224 if (root_window != NULL && !is_closed_popup)
4225 {
4226 StartMouseMovingWindow(window: g.HoveredWindow); //-V595
4227
4228 // Cancel moving if clicked outside of title bar
4229 if (g.IO.ConfigWindowsMoveFromTitleBarOnly)
4230 if (!(root_window->Flags & ImGuiWindowFlags_NoTitleBar) || root_window->DockIsActive)
4231 if (!root_window->TitleBarRect().Contains(p: g.IO.MouseClickedPos[0]))
4232 g.MovingWindow = NULL;
4233
4234 // Cancel moving if clicked over an item which was disabled or inhibited by popups (note that we know HoveredId == 0 already)
4235 if (g.HoveredIdDisabled)
4236 g.MovingWindow = NULL;
4237 }
4238 else if (root_window == NULL && g.NavWindow != NULL && GetTopMostPopupModal() == NULL)
4239 {
4240 // Clicking on void disable focus
4241 FocusWindow(NULL);
4242 }
4243 }
4244
4245 // With right mouse button we close popups without changing focus based on where the mouse is aimed
4246 // Instead, focus will be restored to the window under the bottom-most closed popup.
4247 // (The left mouse button path calls FocusWindow on the hovered window, which will lead NewFrame->ClosePopupsOverWindow to trigger)
4248 if (g.IO.MouseClicked[1])
4249 {
4250 // Find the top-most window between HoveredWindow and the top-most Modal Window.
4251 // This is where we can trim the popup stack.
4252 ImGuiWindow* modal = GetTopMostPopupModal();
4253 bool hovered_window_above_modal = g.HoveredWindow && (modal == NULL || IsWindowAbove(potential_above: g.HoveredWindow, potential_below: modal));
4254 ClosePopupsOverWindow(ref_window: hovered_window_above_modal ? g.HoveredWindow : modal, restore_focus_to_window_under_popup: true);
4255 }
4256}
4257
4258// This is called during NewFrame()->UpdateViewportsNewFrame() only.
4259// Need to keep in sync with SetWindowPos()
4260static void TranslateWindow(ImGuiWindow* window, const ImVec2& delta)
4261{
4262 window->Pos += delta;
4263 window->ClipRect.Translate(d: delta);
4264 window->OuterRectClipped.Translate(d: delta);
4265 window->InnerRect.Translate(d: delta);
4266 window->DC.CursorPos += delta;
4267 window->DC.CursorStartPos += delta;
4268 window->DC.CursorMaxPos += delta;
4269 window->DC.IdealMaxPos += delta;
4270}
4271
4272static void ScaleWindow(ImGuiWindow* window, float scale)
4273{
4274 ImVec2 origin = window->Viewport->Pos;
4275 window->Pos = ImFloor(v: (window->Pos - origin) * scale + origin);
4276 window->Size = ImFloor(v: window->Size * scale);
4277 window->SizeFull = ImFloor(v: window->SizeFull * scale);
4278 window->ContentSize = ImFloor(v: window->ContentSize * scale);
4279}
4280
4281static bool IsWindowActiveAndVisible(ImGuiWindow* window)
4282{
4283 return (window->Active) && (!window->Hidden);
4284}
4285
4286static void UpdateAliasKey(ImGuiKey key, bool v, float analog_value)
4287{
4288 IM_ASSERT(ImGui::IsAliasKey(key));
4289 ImGuiKeyData* key_data = ImGui::GetKeyData(key);
4290 key_data->Down = v;
4291 key_data->AnalogValue = analog_value;
4292}
4293
4294// Rewrite routing data buffers to strip old entries + sort by key to make queries not touch scattered data.
4295// Entries D,A,B,B,A,C,B --> A,A,B,B,B,C,D
4296// Index A:1 B:2 C:5 D:0 --> A:0 B:2 C:5 D:6
4297// See 'Metrics->Key Owners & Shortcut Routing' to visualize the result of that operation.
4298static void UpdateKeyRoutingTable(ImGuiKeyRoutingTable* rt)
4299{
4300 ImGuiContext& g = *GImGui;
4301 rt->EntriesNext.resize(new_size: 0);
4302 for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1))
4303 {
4304 const int new_routing_start_idx = rt->EntriesNext.Size;
4305 ImGuiKeyRoutingData* routing_entry;
4306 for (int old_routing_idx = rt->Index[key - ImGuiKey_NamedKey_BEGIN]; old_routing_idx != -1; old_routing_idx = routing_entry->NextEntryIndex)
4307 {
4308 routing_entry = &rt->Entries[old_routing_idx];
4309 routing_entry->RoutingCurr = routing_entry->RoutingNext; // Update entry
4310 routing_entry->RoutingNext = ImGuiKeyOwner_None;
4311 routing_entry->RoutingNextScore = 255;
4312 if (routing_entry->RoutingCurr == ImGuiKeyOwner_None)
4313 continue;
4314 rt->EntriesNext.push_back(v: *routing_entry); // Write alive ones into new buffer
4315
4316 // Apply routing to owner if there's no owner already (RoutingCurr == None at this point)
4317 if (routing_entry->Mods == g.IO.KeyMods)
4318 {
4319 ImGuiKeyOwnerData* owner_data = ImGui::GetKeyOwnerData(key);
4320 if (owner_data->OwnerCurr == ImGuiKeyOwner_None)
4321 owner_data->OwnerCurr = routing_entry->RoutingCurr;
4322 }
4323 }
4324
4325 // Rewrite linked-list
4326 rt->Index[key - ImGuiKey_NamedKey_BEGIN] = (ImGuiKeyRoutingIndex)(new_routing_start_idx < rt->EntriesNext.Size ? new_routing_start_idx : -1);
4327 for (int n = new_routing_start_idx; n < rt->EntriesNext.Size; n++)
4328 rt->EntriesNext[n].NextEntryIndex = (ImGuiKeyRoutingIndex)((n + 1 < rt->EntriesNext.Size) ? n + 1 : -1);
4329 }
4330 rt->Entries.swap(rhs&: rt->EntriesNext); // Swap new and old indexes
4331}
4332
4333// [Internal] Do not use directly
4334static ImGuiKeyChord GetMergedModsFromKeys()
4335{
4336 ImGuiKeyChord mods = 0;
4337 if (ImGui::IsKeyDown(key: ImGuiMod_Ctrl)) { mods |= ImGuiMod_Ctrl; }
4338 if (ImGui::IsKeyDown(key: ImGuiMod_Shift)) { mods |= ImGuiMod_Shift; }
4339 if (ImGui::IsKeyDown(key: ImGuiMod_Alt)) { mods |= ImGuiMod_Alt; }
4340 if (ImGui::IsKeyDown(key: ImGuiMod_Super)) { mods |= ImGuiMod_Super; }
4341 return mods;
4342}
4343
4344static void ImGui::UpdateKeyboardInputs()
4345{
4346 ImGuiContext& g = *GImGui;
4347 ImGuiIO& io = g.IO;
4348
4349 // Import legacy keys or verify they are not used
4350#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO
4351 if (io.BackendUsingLegacyKeyArrays == 0)
4352 {
4353 // Backend used new io.AddKeyEvent() API: Good! Verify that old arrays are never written to externally.
4354 for (int n = 0; n < ImGuiKey_LegacyNativeKey_END; n++)
4355 IM_ASSERT((io.KeysDown[n] == false || IsKeyDown((ImGuiKey)n)) && "Backend needs to either only use io.AddKeyEvent(), either only fill legacy io.KeysDown[] + io.KeyMap[]. Not both!");
4356 }
4357 else
4358 {
4359 if (g.FrameCount == 0)
4360 for (int n = ImGuiKey_LegacyNativeKey_BEGIN; n < ImGuiKey_LegacyNativeKey_END; n++)
4361 IM_ASSERT(g.IO.KeyMap[n] == -1 && "Backend is not allowed to write to io.KeyMap[0..511]!");
4362
4363 // Build reverse KeyMap (Named -> Legacy)
4364 for (int n = ImGuiKey_NamedKey_BEGIN; n < ImGuiKey_NamedKey_END; n++)
4365 if (io.KeyMap[n] != -1)
4366 {
4367 IM_ASSERT(IsLegacyKey((ImGuiKey)io.KeyMap[n]));
4368 io.KeyMap[io.KeyMap[n]] = n;
4369 }
4370
4371 // Import legacy keys into new ones
4372 for (int n = ImGuiKey_LegacyNativeKey_BEGIN; n < ImGuiKey_LegacyNativeKey_END; n++)
4373 if (io.KeysDown[n] || io.BackendUsingLegacyKeyArrays == 1)
4374 {
4375 const ImGuiKey key = (ImGuiKey)(io.KeyMap[n] != -1 ? io.KeyMap[n] : n);
4376 IM_ASSERT(io.KeyMap[n] == -1 || IsNamedKey(key));
4377 io.KeysData[key].Down = io.KeysDown[n];
4378 if (key != n)
4379 io.KeysDown[key] = io.KeysDown[n]; // Allow legacy code using io.KeysDown[GetKeyIndex()] with old backends
4380 io.BackendUsingLegacyKeyArrays = 1;
4381 }
4382 if (io.BackendUsingLegacyKeyArrays == 1)
4383 {
4384 GetKeyData(key: ImGuiMod_Ctrl)->Down = io.KeyCtrl;
4385 GetKeyData(key: ImGuiMod_Shift)->Down = io.KeyShift;
4386 GetKeyData(key: ImGuiMod_Alt)->Down = io.KeyAlt;
4387 GetKeyData(key: ImGuiMod_Super)->Down = io.KeySuper;
4388 }
4389 }
4390
4391#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO
4392 const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
4393 if (io.BackendUsingLegacyNavInputArray && nav_gamepad_active)
4394 {
4395 #define MAP_LEGACY_NAV_INPUT_TO_KEY1(_KEY, _NAV1) do { io.KeysData[_KEY].Down = (io.NavInputs[_NAV1] > 0.0f); io.KeysData[_KEY].AnalogValue = io.NavInputs[_NAV1]; } while (0)
4396 #define MAP_LEGACY_NAV_INPUT_TO_KEY2(_KEY, _NAV1, _NAV2) do { io.KeysData[_KEY].Down = (io.NavInputs[_NAV1] > 0.0f) || (io.NavInputs[_NAV2] > 0.0f); io.KeysData[_KEY].AnalogValue = ImMax(io.NavInputs[_NAV1], io.NavInputs[_NAV2]); } while (0)
4397 MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadFaceDown, ImGuiNavInput_Activate);
4398 MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadFaceRight, ImGuiNavInput_Cancel);
4399 MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadFaceLeft, ImGuiNavInput_Menu);
4400 MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadFaceUp, ImGuiNavInput_Input);
4401 MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadDpadLeft, ImGuiNavInput_DpadLeft);
4402 MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadDpadRight, ImGuiNavInput_DpadRight);
4403 MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadDpadUp, ImGuiNavInput_DpadUp);
4404 MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadDpadDown, ImGuiNavInput_DpadDown);
4405 MAP_LEGACY_NAV_INPUT_TO_KEY2(ImGuiKey_GamepadL1, ImGuiNavInput_FocusPrev, ImGuiNavInput_TweakSlow);
4406 MAP_LEGACY_NAV_INPUT_TO_KEY2(ImGuiKey_GamepadR1, ImGuiNavInput_FocusNext, ImGuiNavInput_TweakFast);
4407 MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadLStickLeft, ImGuiNavInput_LStickLeft);
4408 MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadLStickRight, ImGuiNavInput_LStickRight);
4409 MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadLStickUp, ImGuiNavInput_LStickUp);
4410 MAP_LEGACY_NAV_INPUT_TO_KEY1(ImGuiKey_GamepadLStickDown, ImGuiNavInput_LStickDown);
4411 #undef NAV_MAP_KEY
4412 }
4413#endif
4414#endif
4415
4416 // Update aliases
4417 for (int n = 0; n < ImGuiMouseButton_COUNT; n++)
4418 UpdateAliasKey(key: MouseButtonToKey(button: n), v: io.MouseDown[n], analog_value: io.MouseDown[n] ? 1.0f : 0.0f);
4419 UpdateAliasKey(key: ImGuiKey_MouseWheelX, v: io.MouseWheelH != 0.0f, analog_value: io.MouseWheelH);
4420 UpdateAliasKey(key: ImGuiKey_MouseWheelY, v: io.MouseWheel != 0.0f, analog_value: io.MouseWheel);
4421
4422 // Synchronize io.KeyMods and io.KeyXXX values.
4423 // - New backends (1.87+): send io.AddKeyEvent(ImGuiMod_XXX) -> -> (here) deriving io.KeyMods + io.KeyXXX from key array.
4424 // - Legacy backends: set io.KeyXXX bools -> (above) set key array from io.KeyXXX -> (here) deriving io.KeyMods + io.KeyXXX from key array.
4425 // So with legacy backends the 4 values will do a unnecessary back-and-forth but it makes the code simpler and future facing.
4426 io.KeyMods = GetMergedModsFromKeys();
4427 io.KeyCtrl = (io.KeyMods & ImGuiMod_Ctrl) != 0;
4428 io.KeyShift = (io.KeyMods & ImGuiMod_Shift) != 0;
4429 io.KeyAlt = (io.KeyMods & ImGuiMod_Alt) != 0;
4430 io.KeySuper = (io.KeyMods & ImGuiMod_Super) != 0;
4431
4432 // Clear gamepad data if disabled
4433 if ((io.BackendFlags & ImGuiBackendFlags_HasGamepad) == 0)
4434 for (int i = ImGuiKey_Gamepad_BEGIN; i < ImGuiKey_Gamepad_END; i++)
4435 {
4436 io.KeysData[i - ImGuiKey_KeysData_OFFSET].Down = false;
4437 io.KeysData[i - ImGuiKey_KeysData_OFFSET].AnalogValue = 0.0f;
4438 }
4439
4440 // Update keys
4441 for (int i = 0; i < ImGuiKey_KeysData_SIZE; i++)
4442 {
4443 ImGuiKeyData* key_data = &io.KeysData[i];
4444 key_data->DownDurationPrev = key_data->DownDuration;
4445 key_data->DownDuration = key_data->Down ? (key_data->DownDuration < 0.0f ? 0.0f : key_data->DownDuration + io.DeltaTime) : -1.0f;
4446 }
4447
4448 // Update keys/input owner (named keys only): one entry per key
4449 for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1))
4450 {
4451 ImGuiKeyData* key_data = &io.KeysData[key - ImGuiKey_KeysData_OFFSET];
4452 ImGuiKeyOwnerData* owner_data = &g.KeysOwnerData[key - ImGuiKey_NamedKey_BEGIN];
4453 owner_data->OwnerCurr = owner_data->OwnerNext;
4454 if (!key_data->Down) // Important: ownership is released on the frame after a release. Ensure a 'MouseDown -> CloseWindow -> MouseUp' chain doesn't lead to someone else seeing the MouseUp.
4455 owner_data->OwnerNext = ImGuiKeyOwner_None;
4456 owner_data->LockThisFrame = owner_data->LockUntilRelease = owner_data->LockUntilRelease && key_data->Down; // Clear LockUntilRelease when key is not Down anymore
4457 }
4458
4459 UpdateKeyRoutingTable(rt: &g.KeysRoutingTable);
4460}
4461
4462static void ImGui::UpdateMouseInputs()
4463{
4464 ImGuiContext& g = *GImGui;
4465 ImGuiIO& io = g.IO;
4466
4467 // Round mouse position to avoid spreading non-rounded position (e.g. UpdateManualResize doesn't support them well)
4468 if (IsMousePosValid(mouse_pos: &io.MousePos))
4469 io.MousePos = g.MouseLastValidPos = ImFloorSigned(v: io.MousePos);
4470
4471 // If mouse just appeared or disappeared (usually denoted by -FLT_MAX components) we cancel out movement in MouseDelta
4472 if (IsMousePosValid(mouse_pos: &io.MousePos) && IsMousePosValid(mouse_pos: &io.MousePosPrev))
4473 io.MouseDelta = io.MousePos - io.MousePosPrev;
4474 else
4475 io.MouseDelta = ImVec2(0.0f, 0.0f);
4476
4477 // If mouse moved we re-enable mouse hovering in case it was disabled by gamepad/keyboard. In theory should use a >0.0f threshold but would need to reset in everywhere we set this to true.
4478 if (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f)
4479 g.NavDisableMouseHover = false;
4480
4481 io.MousePosPrev = io.MousePos;
4482 for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++)
4483 {
4484 io.MouseClicked[i] = io.MouseDown[i] && io.MouseDownDuration[i] < 0.0f;
4485 io.MouseClickedCount[i] = 0; // Will be filled below
4486 io.MouseReleased[i] = !io.MouseDown[i] && io.MouseDownDuration[i] >= 0.0f;
4487 io.MouseDownDurationPrev[i] = io.MouseDownDuration[i];
4488 io.MouseDownDuration[i] = io.MouseDown[i] ? (io.MouseDownDuration[i] < 0.0f ? 0.0f : io.MouseDownDuration[i] + io.DeltaTime) : -1.0f;
4489 if (io.MouseClicked[i])
4490 {
4491 bool is_repeated_click = false;
4492 if ((float)(g.Time - io.MouseClickedTime[i]) < io.MouseDoubleClickTime)
4493 {
4494 ImVec2 delta_from_click_pos = IsMousePosValid(mouse_pos: &io.MousePos) ? (io.MousePos - io.MouseClickedPos[i]) : ImVec2(0.0f, 0.0f);
4495 if (ImLengthSqr(lhs: delta_from_click_pos) < io.MouseDoubleClickMaxDist * io.MouseDoubleClickMaxDist)
4496 is_repeated_click = true;
4497 }
4498 if (is_repeated_click)
4499 io.MouseClickedLastCount[i]++;
4500 else
4501 io.MouseClickedLastCount[i] = 1;
4502 io.MouseClickedTime[i] = g.Time;
4503 io.MouseClickedPos[i] = io.MousePos;
4504 io.MouseClickedCount[i] = io.MouseClickedLastCount[i];
4505 io.MouseDragMaxDistanceAbs[i] = ImVec2(0.0f, 0.0f);
4506 io.MouseDragMaxDistanceSqr[i] = 0.0f;
4507 }
4508 else if (io.MouseDown[i])
4509 {
4510 // Maintain the maximum distance we reaching from the initial click position, which is used with dragging threshold
4511 ImVec2 delta_from_click_pos = IsMousePosValid(mouse_pos: &io.MousePos) ? (io.MousePos - io.MouseClickedPos[i]) : ImVec2(0.0f, 0.0f);
4512 io.MouseDragMaxDistanceSqr[i] = ImMax(lhs: io.MouseDragMaxDistanceSqr[i], rhs: ImLengthSqr(lhs: delta_from_click_pos));
4513 io.MouseDragMaxDistanceAbs[i].x = ImMax(lhs: io.MouseDragMaxDistanceAbs[i].x, rhs: delta_from_click_pos.x < 0.0f ? -delta_from_click_pos.x : delta_from_click_pos.x);
4514 io.MouseDragMaxDistanceAbs[i].y = ImMax(lhs: io.MouseDragMaxDistanceAbs[i].y, rhs: delta_from_click_pos.y < 0.0f ? -delta_from_click_pos.y : delta_from_click_pos.y);
4515 }
4516
4517 // We provide io.MouseDoubleClicked[] as a legacy service
4518 io.MouseDoubleClicked[i] = (io.MouseClickedCount[i] == 2);
4519
4520 // Clicking any mouse button reactivate mouse hovering which may have been deactivated by gamepad/keyboard navigation
4521 if (io.MouseClicked[i])
4522 g.NavDisableMouseHover = false;
4523 }
4524}
4525
4526static void LockWheelingWindow(ImGuiWindow* window, float wheel_amount)
4527{
4528 ImGuiContext& g = *GImGui;
4529 if (window)
4530 g.WheelingWindowReleaseTimer = ImMin(lhs: g.WheelingWindowReleaseTimer + ImAbs(x: wheel_amount) * WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER, rhs: WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER);
4531 else
4532 g.WheelingWindowReleaseTimer = 0.0f;
4533 if (g.WheelingWindow == window)
4534 return;
4535 IMGUI_DEBUG_LOG_IO("LockWheelingWindow() \"%s\"\n", window ? window->Name : "NULL");
4536 g.WheelingWindow = window;
4537 g.WheelingWindowRefMousePos = g.IO.MousePos;
4538 if (window == NULL)
4539 {
4540 g.WheelingWindowStartFrame = -1;
4541 g.WheelingAxisAvg = ImVec2(0.0f, 0.0f);
4542 }
4543}
4544
4545static ImGuiWindow* FindBestWheelingWindow(const ImVec2& wheel)
4546{
4547 // For each axis, find window in the hierarchy that may want to use scrolling
4548 ImGuiContext& g = *GImGui;
4549 ImGuiWindow* windows[2] = { NULL, NULL };
4550 for (int axis = 0; axis < 2; axis++)
4551 if (wheel[axis] != 0.0f)
4552 for (ImGuiWindow* window = windows[axis] = g.HoveredWindow; window->Flags & ImGuiWindowFlags_ChildWindow; window = windows[axis] = window->ParentWindow)
4553 {
4554 // Bubble up into parent window if:
4555 // - a child window doesn't allow any scrolling.
4556 // - a child window has the ImGuiWindowFlags_NoScrollWithMouse flag.
4557 //// - a child window doesn't need scrolling because it is already at the edge for the direction we are going in (FIXME-WIP)
4558 const bool has_scrolling = (window->ScrollMax[axis] != 0.0f);
4559 const bool inputs_disabled = (window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs);
4560 //const bool scrolling_past_limits = (wheel_v < 0.0f) ? (window->Scroll[axis] <= 0.0f) : (window->Scroll[axis] >= window->ScrollMax[axis]);
4561 if (has_scrolling && !inputs_disabled) // && !scrolling_past_limits)
4562 break; // select this window
4563 }
4564 if (windows[0] == NULL && windows[1] == NULL)
4565 return NULL;
4566
4567 // If there's only one window or only one axis then there's no ambiguity
4568 if (windows[0] == windows[1] || windows[0] == NULL || windows[1] == NULL)
4569 return windows[1] ? windows[1] : windows[0];
4570
4571 // If candidate are different windows we need to decide which one to prioritize
4572 // - First frame: only find a winner if one axis is zero.
4573 // - Subsequent frames: only find a winner when one is more than the other.
4574 if (g.WheelingWindowStartFrame == -1)
4575 g.WheelingWindowStartFrame = g.FrameCount;
4576 if ((g.WheelingWindowStartFrame == g.FrameCount && wheel.x != 0.0f && wheel.y != 0.0f) || (g.WheelingAxisAvg.x == g.WheelingAxisAvg.y))
4577 {
4578 g.WheelingWindowWheelRemainder = wheel;
4579 return NULL;
4580 }
4581 return (g.WheelingAxisAvg.x > g.WheelingAxisAvg.y) ? windows[0] : windows[1];
4582}
4583
4584void ImGui::UpdateMouseWheel()
4585{
4586 ImGuiContext& g = *GImGui;
4587
4588 // Reset the locked window if we move the mouse or after the timer elapses.
4589 // FIXME: Ideally we could refactor to have one timer for "changing window w/ same axis" and a shorter timer for "changing window or axis w/ other axis" (#3795)
4590 if (g.WheelingWindow != NULL)
4591 {
4592 g.WheelingWindowReleaseTimer -= g.IO.DeltaTime;
4593 if (IsMousePosValid() && ImLengthSqr(lhs: g.IO.MousePos - g.WheelingWindowRefMousePos) > g.IO.MouseDragThreshold * g.IO.MouseDragThreshold)
4594 g.WheelingWindowReleaseTimer = 0.0f;
4595 if (g.WheelingWindowReleaseTimer <= 0.0f)
4596 LockWheelingWindow(NULL, wheel_amount: 0.0f);
4597 }
4598
4599 ImVec2 wheel;
4600 wheel.x = TestKeyOwner(key: ImGuiKey_MouseWheelX, ImGuiKeyOwner_None) ? g.IO.MouseWheelH : 0.0f;
4601 wheel.y = TestKeyOwner(key: ImGuiKey_MouseWheelY, ImGuiKeyOwner_None) ? g.IO.MouseWheel : 0.0f;
4602
4603 //IMGUI_DEBUG_LOG("MouseWheel X:%.3f Y:%.3f\n", wheel_x, wheel_y);
4604 ImGuiWindow* mouse_window = g.WheelingWindow ? g.WheelingWindow : g.HoveredWindow;
4605 if (!mouse_window || mouse_window->Collapsed)
4606 return;
4607
4608 // Zoom / Scale window
4609 // FIXME-OBSOLETE: This is an old feature, it still works but pretty much nobody is using it and may be best redesigned.
4610 if (wheel.y != 0.0f && g.IO.KeyCtrl && g.IO.FontAllowUserScaling)
4611 {
4612 LockWheelingWindow(window: mouse_window, wheel_amount: wheel.y);
4613 ImGuiWindow* window = mouse_window;
4614 const float new_font_scale = ImClamp(v: window->FontWindowScale + g.IO.MouseWheel * 0.10f, mn: 0.50f, mx: 2.50f);
4615 const float scale = new_font_scale / window->FontWindowScale;
4616 window->FontWindowScale = new_font_scale;
4617 if (window == window->RootWindow)
4618 {
4619 const ImVec2 offset = window->Size * (1.0f - scale) * (g.IO.MousePos - window->Pos) / window->Size;
4620 SetWindowPos(window, pos: window->Pos + offset, cond: 0);
4621 window->Size = ImFloor(v: window->Size * scale);
4622 window->SizeFull = ImFloor(v: window->SizeFull * scale);
4623 }
4624 return;
4625 }
4626 if (g.IO.KeyCtrl)
4627 return;
4628
4629 // Mouse wheel scrolling
4630 // As a standard behavior holding SHIFT while using Vertical Mouse Wheel triggers Horizontal scroll instead
4631 // (we avoid doing it on OSX as it the OS input layer handles this already)
4632 const bool swap_axis = g.IO.KeyShift && !g.IO.ConfigMacOSXBehaviors;
4633 if (swap_axis)
4634 {
4635 wheel.x = wheel.y;
4636 wheel.y = 0.0f;
4637 }
4638
4639 // Maintain a rough average of moving magnitude on both axises
4640 // FIXME: should by based on wall clock time rather than frame-counter
4641 g.WheelingAxisAvg.x = ImExponentialMovingAverage(avg: g.WheelingAxisAvg.x, sample: ImAbs(x: wheel.x), n: 30);
4642 g.WheelingAxisAvg.y = ImExponentialMovingAverage(avg: g.WheelingAxisAvg.y, sample: ImAbs(x: wheel.y), n: 30);
4643
4644 // In the rare situation where FindBestWheelingWindow() had to defer first frame of wheeling due to ambiguous main axis, reinject it now.
4645 wheel += g.WheelingWindowWheelRemainder;
4646 g.WheelingWindowWheelRemainder = ImVec2(0.0f, 0.0f);
4647 if (wheel.x == 0.0f && wheel.y == 0.0f)
4648 return;
4649
4650 // Mouse wheel scrolling: find target and apply
4651 // - don't renew lock if axis doesn't apply on the window.
4652 // - select a main axis when both axises are being moved.
4653 if (ImGuiWindow* window = (g.WheelingWindow ? g.WheelingWindow : FindBestWheelingWindow(wheel)))
4654 if (!(window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs))
4655 {
4656 bool do_scroll[2] = { wheel.x != 0.0f && window->ScrollMax.x != 0.0f, wheel.y != 0.0f && window->ScrollMax.y != 0.0f };
4657 if (do_scroll[ImGuiAxis_X] && do_scroll[ImGuiAxis_Y])
4658 do_scroll[(g.WheelingAxisAvg.x > g.WheelingAxisAvg.y) ? ImGuiAxis_Y : ImGuiAxis_X] = false;
4659 if (do_scroll[ImGuiAxis_X])
4660 {
4661 LockWheelingWindow(window, wheel_amount: wheel.x);
4662 float max_step = window->InnerRect.GetWidth() * 0.67f;
4663 float scroll_step = ImFloor(f: ImMin(lhs: 2 * window->CalcFontSize(), rhs: max_step));
4664 SetScrollX(window, scroll_x: window->Scroll.x - wheel.x * scroll_step);
4665 }
4666 if (do_scroll[ImGuiAxis_Y])
4667 {
4668 LockWheelingWindow(window, wheel_amount: wheel.y);
4669 float max_step = window->InnerRect.GetHeight() * 0.67f;
4670 float scroll_step = ImFloor(f: ImMin(lhs: 5 * window->CalcFontSize(), rhs: max_step));
4671 SetScrollY(window, scroll_y: window->Scroll.y - wheel.y * scroll_step);
4672 }
4673 }
4674}
4675
4676// 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)
4677void ImGui::UpdateHoveredWindowAndCaptureFlags()
4678{
4679 ImGuiContext& g = *GImGui;
4680 ImGuiIO& io = g.IO;
4681 g.WindowsHoverPadding = ImMax(lhs: g.Style.TouchExtraPadding, rhs: ImVec2(WINDOWS_HOVER_PADDING, WINDOWS_HOVER_PADDING));
4682
4683 // Find the window hovered by mouse:
4684 // - Child windows can extend beyond the limit of their parent so we need to derive HoveredRootWindow from HoveredWindow.
4685 // - 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.
4686 // - 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.
4687 bool clear_hovered_windows = false;
4688 FindHoveredWindow();
4689 IM_ASSERT(g.HoveredWindow == NULL || g.HoveredWindow == g.MovingWindow || g.HoveredWindow->Viewport == g.MouseViewport);
4690
4691 // Modal windows prevents mouse from hovering behind them.
4692 ImGuiWindow* modal_window = GetTopMostPopupModal();
4693 if (modal_window && g.HoveredWindow && !IsWindowWithinBeginStackOf(window: g.HoveredWindow->RootWindow, potential_parent: modal_window)) // FIXME-MERGE: RootWindowDockTree ?
4694 clear_hovered_windows = true;
4695
4696 // Disabled mouse?
4697 if (io.ConfigFlags & ImGuiConfigFlags_NoMouse)
4698 clear_hovered_windows = true;
4699
4700 // We track click ownership. When clicked outside of a window the click is owned by the application and
4701 // won't report hovering nor request capture even while dragging over our windows afterward.
4702 const bool has_open_popup = (g.OpenPopupStack.Size > 0);
4703 const bool has_open_modal = (modal_window != NULL);
4704 int mouse_earliest_down = -1;
4705 bool mouse_any_down = false;
4706 for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++)
4707 {
4708 if (io.MouseClicked[i])
4709 {
4710 io.MouseDownOwned[i] = (g.HoveredWindow != NULL) || has_open_popup;
4711 io.MouseDownOwnedUnlessPopupClose[i] = (g.HoveredWindow != NULL) || has_open_modal;
4712 }
4713 mouse_any_down |= io.MouseDown[i];
4714 if (io.MouseDown[i])
4715 if (mouse_earliest_down == -1 || io.MouseClickedTime[i] < io.MouseClickedTime[mouse_earliest_down])
4716 mouse_earliest_down = i;
4717 }
4718 const bool mouse_avail = (mouse_earliest_down == -1) || io.MouseDownOwned[mouse_earliest_down];
4719 const bool mouse_avail_unless_popup_close = (mouse_earliest_down == -1) || io.MouseDownOwnedUnlessPopupClose[mouse_earliest_down];
4720
4721 // If mouse was first clicked outside of ImGui bounds we also cancel out hovering.
4722 // FIXME: For patterns of drag and drop across OS windows, we may need to rework/remove this test (first committed 311c0ca9 on 2015/02)
4723 const bool mouse_dragging_extern_payload = g.DragDropActive && (g.DragDropSourceFlags & ImGuiDragDropFlags_SourceExtern) != 0;
4724 if (!mouse_avail && !mouse_dragging_extern_payload)
4725 clear_hovered_windows = true;
4726
4727 if (clear_hovered_windows)
4728 g.HoveredWindow = g.HoveredWindowUnderMovingWindow = NULL;
4729
4730 // Update io.WantCaptureMouse for the user application (true = dispatch mouse info to Dear ImGui only, false = dispatch mouse to Dear ImGui + underlying app)
4731 // Update io.WantCaptureMouseAllowPopupClose (experimental) to give a chance for app to react to popup closure with a drag
4732 if (g.WantCaptureMouseNextFrame != -1)
4733 {
4734 io.WantCaptureMouse = io.WantCaptureMouseUnlessPopupClose = (g.WantCaptureMouseNextFrame != 0);
4735 }
4736 else
4737 {
4738 io.WantCaptureMouse = (mouse_avail && (g.HoveredWindow != NULL || mouse_any_down)) || has_open_popup;
4739 io.WantCaptureMouseUnlessPopupClose = (mouse_avail_unless_popup_close && (g.HoveredWindow != NULL || mouse_any_down)) || has_open_modal;
4740 }
4741
4742 // Update io.WantCaptureKeyboard for the user application (true = dispatch keyboard info to Dear ImGui only, false = dispatch keyboard info to Dear ImGui + underlying app)
4743 if (g.WantCaptureKeyboardNextFrame != -1)
4744 io.WantCaptureKeyboard = (g.WantCaptureKeyboardNextFrame != 0);
4745 else
4746 io.WantCaptureKeyboard = (g.ActiveId != 0) || (modal_window != NULL);
4747 if (io.NavActive && (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) && !(io.ConfigFlags & ImGuiConfigFlags_NavNoCaptureKeyboard))
4748 io.WantCaptureKeyboard = true;
4749
4750 // Update io.WantTextInput flag, this is to allow systems without a keyboard (e.g. mobile, hand-held) to show a software keyboard if possible
4751 io.WantTextInput = (g.WantTextInputNextFrame != -1) ? (g.WantTextInputNextFrame != 0) : false;
4752}
4753
4754void ImGui::NewFrame()
4755{
4756 IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext() ?");
4757 ImGuiContext& g = *GImGui;
4758
4759 // Remove pending delete hooks before frame start.
4760 // This deferred removal avoid issues of removal while iterating the hook vector
4761 for (int n = g.Hooks.Size - 1; n >= 0; n--)
4762 if (g.Hooks[n].Type == ImGuiContextHookType_PendingRemoval_)
4763 g.Hooks.erase(it: &g.Hooks[n]);
4764
4765 CallContextHooks(ctx: &g, hook_type: ImGuiContextHookType_NewFramePre);
4766
4767 // Check and assert for various common IO and Configuration mistakes
4768 g.ConfigFlagsLastFrame = g.ConfigFlagsCurrFrame;
4769 ErrorCheckNewFrameSanityChecks();
4770 g.ConfigFlagsCurrFrame = g.IO.ConfigFlags;
4771
4772 // Load settings on first frame, save settings when modified (after a delay)
4773 UpdateSettings();
4774
4775 g.Time += g.IO.DeltaTime;
4776 g.WithinFrameScope = true;
4777 g.FrameCount += 1;
4778 g.TooltipOverrideCount = 0;
4779 g.WindowsActiveCount = 0;
4780 g.MenusIdSubmittedThisFrame.resize(new_size: 0);
4781
4782 // Calculate frame-rate for the user, as a purely luxurious feature
4783 g.FramerateSecPerFrameAccum += g.IO.DeltaTime - g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx];
4784 g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx] = g.IO.DeltaTime;
4785 g.FramerateSecPerFrameIdx = (g.FramerateSecPerFrameIdx + 1) % IM_ARRAYSIZE(g.FramerateSecPerFrame);
4786 g.FramerateSecPerFrameCount = ImMin(lhs: g.FramerateSecPerFrameCount + 1, IM_ARRAYSIZE(g.FramerateSecPerFrame));
4787 g.IO.Framerate = (g.FramerateSecPerFrameAccum > 0.0f) ? (1.0f / (g.FramerateSecPerFrameAccum / (float)g.FramerateSecPerFrameCount)) : FLT_MAX;
4788
4789 // Process input queue (trickle as many events as possible), turn events into writes to IO structure
4790 g.InputEventsTrail.resize(new_size: 0);
4791 UpdateInputEvents(trickle_fast_inputs: g.IO.ConfigInputTrickleEventQueue);
4792
4793 // Update viewports (after processing input queue, so io.MouseHoveredViewport is set)
4794 UpdateViewportsNewFrame();
4795
4796 // Setup current font and draw list shared data
4797 // FIXME-VIEWPORT: the concept of a single ClipRectFullscreen is not ideal!
4798 g.IO.Fonts->Locked = true;
4799 SetCurrentFont(GetDefaultFont());
4800 IM_ASSERT(g.Font->IsLoaded());
4801 ImRect virtual_space(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX);
4802 for (int n = 0; n < g.Viewports.Size; n++)
4803 virtual_space.Add(r: g.Viewports[n]->GetMainRect());
4804 g.DrawListSharedData.ClipRectFullscreen = virtual_space.ToVec4();
4805 g.DrawListSharedData.CurveTessellationTol = g.Style.CurveTessellationTol;
4806 g.DrawListSharedData.SetCircleTessellationMaxError(g.Style.CircleTessellationMaxError);
4807 g.DrawListSharedData.InitialFlags = ImDrawListFlags_None;
4808 if (g.Style.AntiAliasedLines)
4809 g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLines;
4810 if (g.Style.AntiAliasedLinesUseTex && !(g.Font->ContainerAtlas->Flags & ImFontAtlasFlags_NoBakedLines))
4811 g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLinesUseTex;
4812 if (g.Style.AntiAliasedFill)
4813 g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedFill;
4814 if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset)
4815 g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AllowVtxOffset;
4816
4817 // Mark rendering data as invalid to prevent user who may have a handle on it to use it.
4818 for (int n = 0; n < g.Viewports.Size; n++)
4819 {
4820 ImGuiViewportP* viewport = g.Viewports[n];
4821 viewport->DrawData = NULL;
4822 viewport->DrawDataP.Clear();
4823 }
4824
4825 // Drag and drop keep the source ID alive so even if the source disappear our state is consistent
4826 if (g.DragDropActive && g.DragDropPayload.SourceId == g.ActiveId)
4827 KeepAliveID(id: g.DragDropPayload.SourceId);
4828
4829 // Update HoveredId data
4830 if (!g.HoveredIdPreviousFrame)
4831 g.HoveredIdTimer = 0.0f;
4832 if (!g.HoveredIdPreviousFrame || (g.HoveredId && g.ActiveId == g.HoveredId))
4833 g.HoveredIdNotActiveTimer = 0.0f;
4834 if (g.HoveredId)
4835 g.HoveredIdTimer += g.IO.DeltaTime;
4836 if (g.HoveredId && g.ActiveId != g.HoveredId)
4837 g.HoveredIdNotActiveTimer += g.IO.DeltaTime;
4838 g.HoveredIdPreviousFrame = g.HoveredId;
4839 g.HoveredId = 0;
4840 g.HoveredIdAllowOverlap = false;
4841 g.HoveredIdDisabled = false;
4842
4843 // Clear ActiveID if the item is not alive anymore.
4844 // In 1.87, the common most call to KeepAliveID() was moved from GetID() to ItemAdd().
4845 // As a result, custom widget using ButtonBehavior() _without_ ItemAdd() need to call KeepAliveID() themselves.
4846 if (g.ActiveId != 0 && g.ActiveIdIsAlive != g.ActiveId && g.ActiveIdPreviousFrame == g.ActiveId)
4847 {
4848 IMGUI_DEBUG_LOG_ACTIVEID("NewFrame(): ClearActiveID() because it isn't marked alive anymore!\n");
4849 ClearActiveID();
4850 }
4851
4852 // Update ActiveId data (clear reference to active widget if the widget isn't alive anymore)
4853 if (g.ActiveId)
4854 g.ActiveIdTimer += g.IO.DeltaTime;
4855 g.LastActiveIdTimer += g.IO.DeltaTime;
4856 g.ActiveIdPreviousFrame = g.ActiveId;
4857 g.ActiveIdPreviousFrameWindow = g.ActiveIdWindow;
4858 g.ActiveIdPreviousFrameHasBeenEditedBefore = g.ActiveIdHasBeenEditedBefore;
4859 g.ActiveIdIsAlive = 0;
4860 g.ActiveIdHasBeenEditedThisFrame = false;
4861 g.ActiveIdPreviousFrameIsAlive = false;
4862 g.ActiveIdIsJustActivated = false;
4863 if (g.TempInputId != 0 && g.ActiveId != g.TempInputId)
4864 g.TempInputId = 0;
4865 if (g.ActiveId == 0)
4866 {
4867 g.ActiveIdUsingNavDirMask = 0x00;
4868 g.ActiveIdUsingAllKeyboardKeys = false;
4869#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO
4870 g.ActiveIdUsingNavInputMask = 0x00;
4871#endif
4872 }
4873
4874#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO
4875 if (g.ActiveId == 0)
4876 g.ActiveIdUsingNavInputMask = 0;
4877 else if (g.ActiveIdUsingNavInputMask != 0)
4878 {
4879 // If your custom widget code used: { g.ActiveIdUsingNavInputMask |= (1 << ImGuiNavInput_Cancel); }
4880 // Since IMGUI_VERSION_NUM >= 18804 it should be: { SetKeyOwner(ImGuiKey_Escape, g.ActiveId); SetKeyOwner(ImGuiKey_NavGamepadCancel, g.ActiveId); }
4881 if (g.ActiveIdUsingNavInputMask & (1 << ImGuiNavInput_Cancel))
4882 SetKeyOwner(key: ImGuiKey_Escape, owner_id: g.ActiveId);
4883 if (g.ActiveIdUsingNavInputMask & ~(1 << ImGuiNavInput_Cancel))
4884 IM_ASSERT(0); // Other values unsupported
4885 }
4886#endif
4887
4888 // Update hover delay for IsItemHovered() with delays and tooltips
4889 g.HoverDelayIdPreviousFrame = g.HoverDelayId;
4890 if (g.HoverDelayId != 0)
4891 {
4892 //if (g.IO.MouseDelta.x == 0.0f && g.IO.MouseDelta.y == 0.0f) // Need design/flags
4893 g.HoverDelayTimer += g.IO.DeltaTime;
4894 g.HoverDelayClearTimer = 0.0f;
4895 g.HoverDelayId = 0;
4896 }
4897 else if (g.HoverDelayTimer > 0.0f)
4898 {
4899 // This gives a little bit of leeway before clearing the hover timer, allowing mouse to cross gaps
4900 g.HoverDelayClearTimer += g.IO.DeltaTime;
4901 if (g.HoverDelayClearTimer >= ImMax(lhs: 0.20f, rhs: g.IO.DeltaTime * 2.0f)) // ~6 frames at 30 Hz + allow for low framerate
4902 g.HoverDelayTimer = g.HoverDelayClearTimer = 0.0f; // May want a decaying timer, in which case need to clamp at max first, based on max of caller last requested timer.
4903 }
4904
4905 // Drag and drop
4906 g.DragDropAcceptIdPrev = g.DragDropAcceptIdCurr;
4907 g.DragDropAcceptIdCurr = 0;
4908 g.DragDropAcceptIdCurrRectSurface = FLT_MAX;
4909 g.DragDropWithinSource = false;
4910 g.DragDropWithinTarget = false;
4911 g.DragDropHoldJustPressedId = 0;
4912
4913 // Close popups on focus lost (currently wip/opt-in)
4914 //if (g.IO.AppFocusLost)
4915 // ClosePopupsExceptModals();
4916
4917 // Update keyboard input state
4918 UpdateKeyboardInputs();
4919
4920 //IM_ASSERT(g.IO.KeyCtrl == IsKeyDown(ImGuiKey_LeftCtrl) || IsKeyDown(ImGuiKey_RightCtrl));
4921 //IM_ASSERT(g.IO.KeyShift == IsKeyDown(ImGuiKey_LeftShift) || IsKeyDown(ImGuiKey_RightShift));
4922 //IM_ASSERT(g.IO.KeyAlt == IsKeyDown(ImGuiKey_LeftAlt) || IsKeyDown(ImGuiKey_RightAlt));
4923 //IM_ASSERT(g.IO.KeySuper == IsKeyDown(ImGuiKey_LeftSuper) || IsKeyDown(ImGuiKey_RightSuper));
4924
4925 // Update gamepad/keyboard navigation
4926 NavUpdate();
4927
4928 // Update mouse input state
4929 UpdateMouseInputs();
4930
4931 // Undocking
4932 // (needs to be before UpdateMouseMovingWindowNewFrame so the window is already offset and following the mouse on the detaching frame)
4933 DockContextNewFrameUpdateUndocking(ctx: &g);
4934
4935 // Find hovered window
4936 // (needs to be before UpdateMouseMovingWindowNewFrame so we fill g.HoveredWindowUnderMovingWindow on the mouse release frame)
4937 UpdateHoveredWindowAndCaptureFlags();
4938
4939 // Handle user moving window with mouse (at the beginning of the frame to avoid input lag or sheering)
4940 UpdateMouseMovingWindowNewFrame();
4941
4942 // Background darkening/whitening
4943 if (GetTopMostPopupModal() != NULL || (g.NavWindowingTarget != NULL && g.NavWindowingHighlightAlpha > 0.0f))
4944 g.DimBgRatio = ImMin(lhs: g.DimBgRatio + g.IO.DeltaTime * 6.0f, rhs: 1.0f);
4945 else
4946 g.DimBgRatio = ImMax(lhs: g.DimBgRatio - g.IO.DeltaTime * 10.0f, rhs: 0.0f);
4947
4948 g.MouseCursor = ImGuiMouseCursor_Arrow;
4949 g.WantCaptureMouseNextFrame = g.WantCaptureKeyboardNextFrame = g.WantTextInputNextFrame = -1;
4950
4951 // Platform IME data: reset for the frame
4952 g.PlatformImeDataPrev = g.PlatformImeData;
4953 g.PlatformImeData.WantVisible = false;
4954
4955 // Mouse wheel scrolling, scale
4956 UpdateMouseWheel();
4957
4958 // Mark all windows as not visible and compact unused memory.
4959 IM_ASSERT(g.WindowsFocusOrder.Size <= g.Windows.Size);
4960 const float memory_compact_start_time = (g.GcCompactAll || g.IO.ConfigMemoryCompactTimer < 0.0f) ? FLT_MAX : (float)g.Time - g.IO.ConfigMemoryCompactTimer;
4961 for (int i = 0; i != g.Windows.Size; i++)
4962 {
4963 ImGuiWindow* window = g.Windows[i];
4964 window->WasActive = window->Active;
4965 window->Active = false;
4966 window->WriteAccessed = false;
4967 window->BeginCountPreviousFrame = window->BeginCount;
4968 window->BeginCount = 0;
4969
4970 // Garbage collect transient buffers of recently unused windows
4971 if (!window->WasActive && !window->MemoryCompacted && window->LastTimeActive < memory_compact_start_time)
4972 GcCompactTransientWindowBuffers(window);
4973 }
4974
4975 // Garbage collect transient buffers of recently unused tables
4976 for (int i = 0; i < g.TablesLastTimeActive.Size; i++)
4977 if (g.TablesLastTimeActive[i] >= 0.0f && g.TablesLastTimeActive[i] < memory_compact_start_time)
4978 TableGcCompactTransientBuffers(table: g.Tables.GetByIndex(n: i));
4979 for (int i = 0; i < g.TablesTempData.Size; i++)
4980 if (g.TablesTempData[i].LastTimeActive >= 0.0f && g.TablesTempData[i].LastTimeActive < memory_compact_start_time)
4981 TableGcCompactTransientBuffers(table: &g.TablesTempData[i]);
4982 if (g.GcCompactAll)
4983 GcCompactTransientMiscBuffers();
4984 g.GcCompactAll = false;
4985
4986 // Closing the focused window restore focus to the first active root window in descending z-order
4987 if (g.NavWindow && !g.NavWindow->WasActive)
4988 FocusTopMostWindowUnderOne(NULL, NULL);
4989
4990 // No window should be open at the beginning of the frame.
4991 // But in order to allow the user to call NewFrame() multiple times without calling Render(), we are doing an explicit clear.
4992 g.CurrentWindowStack.resize(new_size: 0);
4993 g.BeginPopupStack.resize(new_size: 0);
4994 g.ItemFlagsStack.resize(new_size: 0);
4995 g.ItemFlagsStack.push_back(v: ImGuiItemFlags_None);
4996 g.GroupStack.resize(new_size: 0);
4997
4998 // Docking
4999 DockContextNewFrameUpdateDocking(ctx: &g);
5000
5001 // [DEBUG] Update debug features
5002 UpdateDebugToolItemPicker();
5003 UpdateDebugToolStackQueries();
5004 if (g.DebugLocateFrames > 0 && --g.DebugLocateFrames == 0)
5005 g.DebugLocateId = 0;
5006
5007 // Create implicit/fallback window - which we will only render it if the user has added something to it.
5008 // We don't use "Debug" to avoid colliding with user trying to create a "Debug" window with custom flags.
5009 // This fallback is particularly important as it prevents ImGui:: calls from crashing.
5010 g.WithinFrameScopeWithImplicitWindow = true;
5011 SetNextWindowSize(size: ImVec2(400, 400), cond: ImGuiCond_FirstUseEver);
5012 Begin(name: "Debug##Default");
5013 IM_ASSERT(g.CurrentWindow->IsFallbackWindow == true);
5014
5015 CallContextHooks(ctx: &g, hook_type: ImGuiContextHookType_NewFramePost);
5016}
5017
5018// IMPORTANT: ###xxx suffixes must be same in ALL languages
5019static const ImGuiLocEntry GLocalizationEntriesEnUS[] =
5020{
5021 { .Key: ImGuiLocKey_TableSizeOne, .Text: "Size column to fit###SizeOne" },
5022 { .Key: ImGuiLocKey_TableSizeAllFit, .Text: "Size all columns to fit###SizeAll" },
5023 { .Key: ImGuiLocKey_TableSizeAllDefault, .Text: "Size all columns to default###SizeAll" },
5024 { .Key: ImGuiLocKey_TableResetOrder, .Text: "Reset order###ResetOrder" },
5025 { .Key: ImGuiLocKey_WindowingMainMenuBar, .Text: "(Main menu bar)" },
5026 { .Key: ImGuiLocKey_WindowingPopup, .Text: "(Popup)" },
5027 { .Key: ImGuiLocKey_WindowingUntitled, .Text: "(Untitled)" },
5028 { .Key: ImGuiLocKey_DockingHideTabBar, .Text: "Hide tab bar###HideTabBar" },
5029};
5030
5031void ImGui::Initialize()
5032{
5033 ImGuiContext& g = *GImGui;
5034 IM_ASSERT(!g.Initialized && !g.SettingsLoaded);
5035
5036 // Add .ini handle for ImGuiWindow and ImGuiTable types
5037 {
5038 ImGuiSettingsHandler ini_handler;
5039 ini_handler.TypeName = "Window";
5040 ini_handler.TypeHash = ImHashStr(data_p: "Window");
5041 ini_handler.ClearAllFn = WindowSettingsHandler_ClearAll;
5042 ini_handler.ReadOpenFn = WindowSettingsHandler_ReadOpen;
5043 ini_handler.ReadLineFn = WindowSettingsHandler_ReadLine;
5044 ini_handler.ApplyAllFn = WindowSettingsHandler_ApplyAll;
5045 ini_handler.WriteAllFn = WindowSettingsHandler_WriteAll;
5046 AddSettingsHandler(handler: &ini_handler);
5047 }
5048 TableSettingsAddSettingsHandler();
5049
5050 // Setup default localization table
5051 LocalizeRegisterEntries(entries: GLocalizationEntriesEnUS, IM_ARRAYSIZE(GLocalizationEntriesEnUS));
5052
5053 // Create default viewport
5054 ImGuiViewportP* viewport = IM_NEW(ImGuiViewportP)();
5055 viewport->ID = IMGUI_VIEWPORT_DEFAULT_ID;
5056 viewport->Idx = 0;
5057 viewport->PlatformWindowCreated = true;
5058 viewport->Flags = ImGuiViewportFlags_OwnedByApp;
5059 g.Viewports.push_back(v: viewport);
5060 g.TempBuffer.resize(new_size: 1024 * 3 + 1, v: 0);
5061 g.PlatformIO.Viewports.push_back(v: g.Viewports[0]);
5062
5063#ifdef IMGUI_HAS_DOCK
5064 // Initialize Docking
5065 DockContextInitialize(ctx: &g);
5066#endif
5067
5068 g.Initialized = true;
5069}
5070
5071// This function is merely here to free heap allocations.
5072void ImGui::Shutdown()
5073{
5074 // 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)
5075 ImGuiContext& g = *GImGui;
5076 if (g.IO.Fonts && g.FontAtlasOwnedByContext)
5077 {
5078 g.IO.Fonts->Locked = false;
5079 IM_DELETE(p: g.IO.Fonts);
5080 }
5081 g.IO.Fonts = NULL;
5082 g.DrawListSharedData.TempBuffer.clear();
5083
5084 // Cleanup of other data are conditional on actually having initialized Dear ImGui.
5085 if (!g.Initialized)
5086 return;
5087
5088 // Save settings (unless we haven't attempted to load them: CreateContext/DestroyContext without a call to NewFrame shouldn't save an empty file)
5089 if (g.SettingsLoaded && g.IO.IniFilename != NULL)
5090 SaveIniSettingsToDisk(ini_filename: g.IO.IniFilename);
5091
5092 // Destroy platform windows
5093 DestroyPlatformWindows();
5094
5095 // Shutdown extensions
5096 DockContextShutdown(ctx: &g);
5097
5098 CallContextHooks(ctx: &g, hook_type: ImGuiContextHookType_Shutdown);
5099
5100 // Clear everything else
5101 g.Windows.clear_delete();
5102 g.WindowsFocusOrder.clear();
5103 g.WindowsTempSortBuffer.clear();
5104 g.CurrentWindow = NULL;
5105 g.CurrentWindowStack.clear();
5106 g.WindowsById.Clear();
5107 g.NavWindow = NULL;
5108 g.HoveredWindow = g.HoveredWindowUnderMovingWindow = NULL;
5109 g.ActiveIdWindow = g.ActiveIdPreviousFrameWindow = NULL;
5110 g.MovingWindow = NULL;
5111
5112 g.KeysRoutingTable.Clear();
5113
5114 g.ColorStack.clear();
5115 g.StyleVarStack.clear();
5116 g.FontStack.clear();
5117 g.OpenPopupStack.clear();
5118 g.BeginPopupStack.clear();
5119
5120 g.CurrentViewport = g.MouseViewport = g.MouseLastHoveredViewport = NULL;
5121 g.Viewports.clear_delete();
5122
5123 g.TabBars.Clear();
5124 g.CurrentTabBarStack.clear();
5125 g.ShrinkWidthBuffer.clear();
5126
5127 g.ClipperTempData.clear_destruct();
5128
5129 g.Tables.Clear();
5130 g.TablesTempData.clear_destruct();
5131 g.DrawChannelsTempMergeBuffer.clear();
5132
5133 g.ClipboardHandlerData.clear();
5134 g.MenusIdSubmittedThisFrame.clear();
5135 g.InputTextState.ClearFreeMemory();
5136
5137 g.SettingsWindows.clear();
5138 g.SettingsHandlers.clear();
5139
5140 if (g.LogFile)
5141 {
5142#ifndef IMGUI_DISABLE_TTY_FUNCTIONS
5143 if (g.LogFile != stdout)
5144#endif
5145 ImFileClose(f: g.LogFile);
5146 g.LogFile = NULL;
5147 }
5148 g.LogBuffer.clear();
5149 g.DebugLogBuf.clear();
5150 g.DebugLogIndex.clear();
5151
5152 g.Initialized = false;
5153}
5154
5155// FIXME: Add a more explicit sort order in the window structure.
5156static int IMGUI_CDECL ChildWindowComparer(const void* lhs, const void* rhs)
5157{
5158 const ImGuiWindow* const a = *(const ImGuiWindow* const *)lhs;
5159 const ImGuiWindow* const b = *(const ImGuiWindow* const *)rhs;
5160 if (int d = (a->Flags & ImGuiWindowFlags_Popup) - (b->Flags & ImGuiWindowFlags_Popup))
5161 return d;
5162 if (int d = (a->Flags & ImGuiWindowFlags_Tooltip) - (b->Flags & ImGuiWindowFlags_Tooltip))
5163 return d;
5164 return (a->BeginOrderWithinParent - b->BeginOrderWithinParent);
5165}
5166
5167static void AddWindowToSortBuffer(ImVector<ImGuiWindow*>* out_sorted_windows, ImGuiWindow* window)
5168{
5169 out_sorted_windows->push_back(v: window);
5170 if (window->Active)
5171 {
5172 int count = window->DC.ChildWindows.Size;
5173 ImQsort(base: window->DC.ChildWindows.Data, count: (size_t)count, size_of_element: sizeof(ImGuiWindow*), compare_func: ChildWindowComparer);
5174 for (int i = 0; i < count; i++)
5175 {
5176 ImGuiWindow* child = window->DC.ChildWindows[i];
5177 if (child->Active)
5178 AddWindowToSortBuffer(out_sorted_windows, window: child);
5179 }
5180 }
5181}
5182
5183static void AddDrawListToDrawData(ImVector<ImDrawList*>* out_list, ImDrawList* draw_list)
5184{
5185 if (draw_list->CmdBuffer.Size == 0)
5186 return;
5187 if (draw_list->CmdBuffer.Size == 1 && draw_list->CmdBuffer[0].ElemCount == 0 && draw_list->CmdBuffer[0].UserCallback == NULL)
5188 return;
5189
5190 // Draw list sanity check. Detect mismatch between PrimReserve() calls and incrementing _VtxCurrentIdx, _VtxWritePtr etc.
5191 // May trigger for you if you are using PrimXXX functions incorrectly.
5192 IM_ASSERT(draw_list->VtxBuffer.Size == 0 || draw_list->_VtxWritePtr == draw_list->VtxBuffer.Data + draw_list->VtxBuffer.Size);
5193 IM_ASSERT(draw_list->IdxBuffer.Size == 0 || draw_list->_IdxWritePtr == draw_list->IdxBuffer.Data + draw_list->IdxBuffer.Size);
5194 if (!(draw_list->Flags & ImDrawListFlags_AllowVtxOffset))
5195 IM_ASSERT((int)draw_list->_VtxCurrentIdx == draw_list->VtxBuffer.Size);
5196
5197 // Check that draw_list doesn't use more vertices than indexable (default ImDrawIdx = unsigned short = 2 bytes = 64K vertices per ImDrawList = per window)
5198 // If this assert triggers because you are drawing lots of stuff manually:
5199 // - First, make sure you are coarse clipping yourself and not trying to draw many things outside visible bounds.
5200 // Be mindful that the ImDrawList API doesn't filter vertices. Use the Metrics/Debugger window to inspect draw list contents.
5201 // - If you want large meshes with more than 64K vertices, you can either:
5202 // (A) Handle the ImDrawCmd::VtxOffset value in your renderer backend, and set 'io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset'.
5203 // Most example backends already support this from 1.71. Pre-1.71 backends won't.
5204 // 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.
5205 // (B) Or handle 32-bit indices in your renderer backend, and uncomment '#define ImDrawIdx unsigned int' line in imconfig.h.
5206 // Most example backends already support this. For example, the OpenGL example code detect index size at compile-time:
5207 // glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer_offset);
5208 // Your own engine or render API may use different parameters or function calls to specify index sizes.
5209 // 2 and 4 bytes indices are generally supported by most graphics API.
5210 // - If for some reason neither of those solutions works for you, a workaround is to call BeginChild()/EndChild() before reaching
5211 // the 64K limit to split your draw commands in multiple draw lists.
5212 if (sizeof(ImDrawIdx) == 2)
5213 IM_ASSERT(draw_list->_VtxCurrentIdx < (1 << 16) && "Too many vertices in ImDrawList using 16-bit indices. Read comment above");
5214
5215 out_list->push_back(v: draw_list);
5216}
5217
5218static void AddWindowToDrawData(ImGuiWindow* window, int layer)
5219{
5220 ImGuiContext& g = *GImGui;
5221 ImGuiViewportP* viewport = window->Viewport;
5222 g.IO.MetricsRenderWindows++;
5223 if (window->Flags & ImGuiWindowFlags_DockNodeHost)
5224 window->DrawList->ChannelsMerge();
5225 AddDrawListToDrawData(out_list: &viewport->DrawDataBuilder.Layers[layer], draw_list: window->DrawList);
5226 for (int i = 0; i < window->DC.ChildWindows.Size; i++)
5227 {
5228 ImGuiWindow* child = window->DC.ChildWindows[i];
5229 if (IsWindowActiveAndVisible(window: child)) // Clipped children may have been marked not active
5230 AddWindowToDrawData(window: child, layer);
5231 }
5232}
5233
5234static inline int GetWindowDisplayLayer(ImGuiWindow* window)
5235{
5236 return (window->Flags & ImGuiWindowFlags_Tooltip) ? 1 : 0;
5237}
5238
5239// Layer is locked for the root window, however child windows may use a different viewport (e.g. extruding menu)
5240static inline void AddRootWindowToDrawData(ImGuiWindow* window)
5241{
5242 AddWindowToDrawData(window, layer: GetWindowDisplayLayer(window));
5243}
5244
5245void ImDrawDataBuilder::FlattenIntoSingleLayer()
5246{
5247 int n = Layers[0].Size;
5248 int size = n;
5249 for (int i = 1; i < IM_ARRAYSIZE(Layers); i++)
5250 size += Layers[i].Size;
5251 Layers[0].resize(new_size: size);
5252 for (int layer_n = 1; layer_n < IM_ARRAYSIZE(Layers); layer_n++)
5253 {
5254 ImVector<ImDrawList*>& layer = Layers[layer_n];
5255 if (layer.empty())
5256 continue;
5257 memcpy(dest: &Layers[0][n], src: &layer[0], n: layer.Size * sizeof(ImDrawList*));
5258 n += layer.Size;
5259 layer.resize(new_size: 0);
5260 }
5261}
5262
5263static void SetupViewportDrawData(ImGuiViewportP* viewport, ImVector<ImDrawList*>* draw_lists)
5264{
5265 // When minimized, we report draw_data->DisplaySize as zero to be consistent with non-viewport mode,
5266 // and to allow applications/backends to easily skip rendering.
5267 // FIXME: Note that we however do NOT attempt to report "zero drawlist / vertices" into the ImDrawData structure.
5268 // This is because the work has been done already, and its wasted! We should fix that and add optimizations for
5269 // it earlier in the pipeline, rather than pretend to hide the data at the end of the pipeline.
5270 const bool is_minimized = (viewport->Flags & ImGuiViewportFlags_Minimized) != 0;
5271
5272 ImGuiIO& io = ImGui::GetIO();
5273 ImDrawData* draw_data = &viewport->DrawDataP;
5274 viewport->DrawData = draw_data; // Make publicly accessible
5275 draw_data->Valid = true;
5276 draw_data->CmdLists = (draw_lists->Size > 0) ? draw_lists->Data : NULL;
5277 draw_data->CmdListsCount = draw_lists->Size;
5278 draw_data->TotalVtxCount = draw_data->TotalIdxCount = 0;
5279 draw_data->DisplayPos = viewport->Pos;
5280 draw_data->DisplaySize = is_minimized ? ImVec2(0.0f, 0.0f) : viewport->Size;
5281 draw_data->FramebufferScale = io.DisplayFramebufferScale; // FIXME-VIEWPORT: This may vary on a per-monitor/viewport basis?
5282 draw_data->OwnerViewport = viewport;
5283 for (int n = 0; n < draw_lists->Size; n++)
5284 {
5285 ImDrawList* draw_list = draw_lists->Data[n];
5286 draw_list->_PopUnusedDrawCmd();
5287 draw_data->TotalVtxCount += draw_list->VtxBuffer.Size;
5288 draw_data->TotalIdxCount += draw_list->IdxBuffer.Size;
5289 }
5290}
5291
5292// Push a clipping rectangle for both ImGui logic (hit-testing etc.) and low-level ImDrawList rendering.
5293// - When using this function it is sane to ensure that float are perfectly rounded to integer values,
5294// so that e.g. (int)(max.x-min.x) in user's render produce correct result.
5295// - If the code here changes, may need to update code of functions like NextColumn() and PushColumnClipRect():
5296// some frequently called functions which to modify both channels and clipping simultaneously tend to use the
5297// more specialized SetWindowClipRectBeforeSetChannel() to avoid extraneous updates of underlying ImDrawCmds.
5298void ImGui::PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect)
5299{
5300 ImGuiWindow* window = GetCurrentWindow();
5301 window->DrawList->PushClipRect(clip_rect_min, clip_rect_max, intersect_with_current_clip_rect);
5302 window->ClipRect = window->DrawList->_ClipRectStack.back();
5303}
5304
5305void ImGui::PopClipRect()
5306{
5307 ImGuiWindow* window = GetCurrentWindow();
5308 window->DrawList->PopClipRect();
5309 window->ClipRect = window->DrawList->_ClipRectStack.back();
5310}
5311
5312static ImGuiWindow* FindFrontMostVisibleChildWindow(ImGuiWindow* window)
5313{
5314 for (int n = window->DC.ChildWindows.Size - 1; n >= 0; n--)
5315 if (IsWindowActiveAndVisible(window: window->DC.ChildWindows[n]))
5316 return FindFrontMostVisibleChildWindow(window: window->DC.ChildWindows[n]);
5317 return window;
5318}
5319
5320static void ImGui::RenderDimmedBackgroundBehindWindow(ImGuiWindow* window, ImU32 col)
5321{
5322 if ((col & IM_COL32_A_MASK) == 0)
5323 return;
5324
5325 ImGuiViewportP* viewport = window->Viewport;
5326 ImRect viewport_rect = viewport->GetMainRect();
5327
5328 // Draw behind window by moving the draw command at the FRONT of the draw list
5329 {
5330 // We've already called AddWindowToDrawData() which called DrawList->ChannelsMerge() on DockNodeHost windows,
5331 // and draw list have been trimmed already, hence the explicit recreation of a draw command if missing.
5332 // FIXME: This is creating complication, might be simpler if we could inject a drawlist in drawdata at a given position and not attempt to manipulate ImDrawCmd order.
5333 ImDrawList* draw_list = window->RootWindowDockTree->DrawList;
5334 if (draw_list->CmdBuffer.Size == 0)
5335 draw_list->AddDrawCmd();
5336 draw_list->PushClipRect(clip_rect_min: viewport_rect.Min - ImVec2(1, 1), clip_rect_max: viewport_rect.Max + ImVec2(1, 1), intersect_with_current_clip_rect: false); // Ensure ImDrawCmd are not merged
5337 draw_list->AddRectFilled(p_min: viewport_rect.Min, p_max: viewport_rect.Max, col);
5338 ImDrawCmd cmd = draw_list->CmdBuffer.back();
5339 IM_ASSERT(cmd.ElemCount == 6);
5340 draw_list->CmdBuffer.pop_back();
5341 draw_list->CmdBuffer.push_front(v: cmd);
5342 draw_list->PopClipRect();
5343 draw_list->AddDrawCmd(); // We need to create a command as CmdBuffer.back().IdxOffset won't be correct if we append to same command.
5344 }
5345
5346 // Draw over sibling docking nodes in a same docking tree
5347 if (window->RootWindow->DockIsActive)
5348 {
5349 ImDrawList* draw_list = FindFrontMostVisibleChildWindow(window: window->RootWindowDockTree)->DrawList;
5350 if (draw_list->CmdBuffer.Size == 0)
5351 draw_list->AddDrawCmd();
5352 draw_list->PushClipRect(clip_rect_min: viewport_rect.Min, clip_rect_max: viewport_rect.Max, intersect_with_current_clip_rect: false);
5353 RenderRectFilledWithHole(draw_list, outer: window->RootWindowDockTree->Rect(), inner: window->RootWindow->Rect(), col, rounding: 0.0f);// window->RootWindowDockTree->WindowRounding);
5354 draw_list->PopClipRect();
5355 }
5356}
5357
5358ImGuiWindow* ImGui::FindBottomMostVisibleWindowWithinBeginStack(ImGuiWindow* parent_window)
5359{
5360 ImGuiContext& g = *GImGui;
5361 ImGuiWindow* bottom_most_visible_window = parent_window;
5362 for (int i = FindWindowDisplayIndex(window: parent_window); i >= 0; i--)
5363 {
5364 ImGuiWindow* window = g.Windows[i];
5365 if (window->Flags & ImGuiWindowFlags_ChildWindow)
5366 continue;
5367 if (!IsWindowWithinBeginStackOf(window, potential_parent: parent_window))
5368 break;
5369 if (IsWindowActiveAndVisible(window) && GetWindowDisplayLayer(window) <= GetWindowDisplayLayer(window: parent_window))
5370 bottom_most_visible_window = window;
5371 }
5372 return bottom_most_visible_window;
5373}
5374
5375static void ImGui::RenderDimmedBackgrounds()
5376{
5377 ImGuiContext& g = *GImGui;
5378 ImGuiWindow* modal_window = GetTopMostAndVisiblePopupModal();
5379 if (g.DimBgRatio <= 0.0f && g.NavWindowingHighlightAlpha <= 0.0f)
5380 return;
5381 const bool dim_bg_for_modal = (modal_window != NULL);
5382 const bool dim_bg_for_window_list = (g.NavWindowingTargetAnim != NULL && g.NavWindowingTargetAnim->Active);
5383 if (!dim_bg_for_modal && !dim_bg_for_window_list)
5384 return;
5385
5386 ImGuiViewport* viewports_already_dimmed[2] = { NULL, NULL };
5387 if (dim_bg_for_modal)
5388 {
5389 // Draw dimming behind modal or a begin stack child, whichever comes first in draw order.
5390 ImGuiWindow* dim_behind_window = FindBottomMostVisibleWindowWithinBeginStack(parent_window: modal_window);
5391 RenderDimmedBackgroundBehindWindow(window: dim_behind_window, col: GetColorU32(idx: ImGuiCol_ModalWindowDimBg, alpha_mul: g.DimBgRatio));
5392 viewports_already_dimmed[0] = modal_window->Viewport;
5393 }
5394 else if (dim_bg_for_window_list)
5395 {
5396 // Draw dimming behind CTRL+Tab target window and behind CTRL+Tab UI window
5397 RenderDimmedBackgroundBehindWindow(window: g.NavWindowingTargetAnim, col: GetColorU32(idx: ImGuiCol_NavWindowingDimBg, alpha_mul: g.DimBgRatio));
5398 if (g.NavWindowingListWindow != NULL && g.NavWindowingListWindow->Viewport && g.NavWindowingListWindow->Viewport != g.NavWindowingTargetAnim->Viewport)
5399 RenderDimmedBackgroundBehindWindow(window: g.NavWindowingListWindow, col: GetColorU32(idx: ImGuiCol_NavWindowingDimBg, alpha_mul: g.DimBgRatio));
5400 viewports_already_dimmed[0] = g.NavWindowingTargetAnim->Viewport;
5401 viewports_already_dimmed[1] = g.NavWindowingListWindow ? g.NavWindowingListWindow->Viewport : NULL;
5402
5403 // Draw border around CTRL+Tab target window
5404 ImGuiWindow* window = g.NavWindowingTargetAnim;
5405 ImGuiViewport* viewport = window->Viewport;
5406 float distance = g.FontSize;
5407 ImRect bb = window->Rect();
5408 bb.Expand(amount: distance);
5409 if (bb.GetWidth() >= viewport->Size.x && bb.GetHeight() >= viewport->Size.y)
5410 bb.Expand(amount: -distance - 1.0f); // If a window fits the entire viewport, adjust its highlight inward
5411 if (window->DrawList->CmdBuffer.Size == 0)
5412 window->DrawList->AddDrawCmd();
5413 window->DrawList->PushClipRect(clip_rect_min: viewport->Pos, clip_rect_max: viewport->Pos + viewport->Size);
5414 window->DrawList->AddRect(p_min: bb.Min, p_max: bb.Max, col: GetColorU32(idx: ImGuiCol_NavWindowingHighlight, alpha_mul: g.NavWindowingHighlightAlpha), rounding: window->WindowRounding, flags: 0, thickness: 3.0f);
5415 window->DrawList->PopClipRect();
5416 }
5417
5418 // Draw dimming background on _other_ viewports than the ones our windows are in
5419 for (int viewport_n = 0; viewport_n < g.Viewports.Size; viewport_n++)
5420 {
5421 ImGuiViewportP* viewport = g.Viewports[viewport_n];
5422 if (viewport == viewports_already_dimmed[0] || viewport == viewports_already_dimmed[1])
5423 continue;
5424 if (modal_window && viewport->Window && IsWindowAbove(potential_above: viewport->Window, potential_below: modal_window))
5425 continue;
5426 ImDrawList* draw_list = GetForegroundDrawList(viewport);
5427 const ImU32 dim_bg_col = GetColorU32(idx: dim_bg_for_modal ? ImGuiCol_ModalWindowDimBg : ImGuiCol_NavWindowingDimBg, alpha_mul: g.DimBgRatio);
5428 draw_list->AddRectFilled(p_min: viewport->Pos, p_max: viewport->Pos + viewport->Size, col: dim_bg_col);
5429 }
5430}
5431
5432// 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.
5433void ImGui::EndFrame()
5434{
5435 ImGuiContext& g = *GImGui;
5436 IM_ASSERT(g.Initialized);
5437
5438 // Don't process EndFrame() multiple times.
5439 if (g.FrameCountEnded == g.FrameCount)
5440 return;
5441 IM_ASSERT(g.WithinFrameScope && "Forgot to call ImGui::NewFrame()?");
5442
5443 CallContextHooks(ctx: &g, hook_type: ImGuiContextHookType_EndFramePre);
5444
5445 ErrorCheckEndFrameSanityChecks();
5446
5447 // Notify Platform/OS when our Input Method Editor cursor has moved (e.g. CJK inputs using Microsoft IME)
5448 if (g.IO.SetPlatformImeDataFn && memcmp(s1: &g.PlatformImeData, s2: &g.PlatformImeDataPrev, n: sizeof(ImGuiPlatformImeData)) != 0)
5449 {
5450 ImGuiViewport* viewport = FindViewportByID(id: g.PlatformImeViewport);
5451 g.IO.SetPlatformImeDataFn(viewport ? viewport : GetMainViewport(), &g.PlatformImeData);
5452 }
5453
5454 // Hide implicit/fallback "Debug" window if it hasn't been used
5455 g.WithinFrameScopeWithImplicitWindow = false;
5456 if (g.CurrentWindow && !g.CurrentWindow->WriteAccessed)
5457 g.CurrentWindow->Active = false;
5458 End();
5459
5460 // Update navigation: CTRL+Tab, wrap-around requests
5461 NavEndFrame();
5462
5463 // Update docking
5464 DockContextEndFrame(ctx: &g);
5465
5466 SetCurrentViewport(NULL, NULL);
5467
5468 // Drag and Drop: Elapse payload (if delivered, or if source stops being submitted)
5469 if (g.DragDropActive)
5470 {
5471 bool is_delivered = g.DragDropPayload.Delivery;
5472 bool is_elapsed = (g.DragDropPayload.DataFrameCount + 1 < g.FrameCount) && ((g.DragDropSourceFlags & ImGuiDragDropFlags_SourceAutoExpirePayload) || !IsMouseDown(button: g.DragDropMouseButton));
5473 if (is_delivered || is_elapsed)
5474 ClearDragDrop();
5475 }
5476
5477 // Drag and Drop: Fallback for source tooltip. This is not ideal but better than nothing.
5478 if (g.DragDropActive && g.DragDropSourceFrameCount < g.FrameCount && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoPreviewTooltip))
5479 {
5480 g.DragDropWithinSource = true;
5481 SetTooltip("...");
5482 g.DragDropWithinSource = false;
5483 }
5484
5485 // End frame
5486 g.WithinFrameScope = false;
5487 g.FrameCountEnded = g.FrameCount;
5488
5489 // Initiate moving window + handle left-click and right-click focus
5490 UpdateMouseMovingWindowEndFrame();
5491
5492 // Update user-facing viewport list (g.Viewports -> g.PlatformIO.Viewports after filtering out some)
5493 UpdateViewportsEndFrame();
5494
5495 // Sort the window list so that all child windows are after their parent
5496 // We cannot do that on FocusWindow() because children may not exist yet
5497 g.WindowsTempSortBuffer.resize(new_size: 0);
5498 g.WindowsTempSortBuffer.reserve(new_capacity: g.Windows.Size);
5499 for (int i = 0; i != g.Windows.Size; i++)
5500 {
5501 ImGuiWindow* window = g.Windows[i];
5502 if (window->Active && (window->Flags & ImGuiWindowFlags_ChildWindow)) // if a child is active its parent will add it
5503 continue;
5504 AddWindowToSortBuffer(out_sorted_windows: &g.WindowsTempSortBuffer, window);
5505 }
5506
5507 // 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.
5508 IM_ASSERT(g.Windows.Size == g.WindowsTempSortBuffer.Size);
5509 g.Windows.swap(rhs&: g.WindowsTempSortBuffer);
5510 g.IO.MetricsActiveWindows = g.WindowsActiveCount;
5511
5512 // Unlock font atlas
5513 g.IO.Fonts->Locked = false;
5514
5515 // Clear Input data for next frame
5516 g.IO.AppFocusLost = false;
5517 g.IO.MouseWheel = g.IO.MouseWheelH = 0.0f;
5518 g.IO.InputQueueCharacters.resize(new_size: 0);
5519
5520 CallContextHooks(ctx: &g, hook_type: ImGuiContextHookType_EndFramePost);
5521}
5522
5523// Prepare the data for rendering so you can call GetDrawData()
5524// (As with anything within the ImGui:: namspace this doesn't touch your GPU or graphics API at all:
5525// it is the role of the ImGui_ImplXXXX_RenderDrawData() function provided by the renderer backend)
5526void ImGui::Render()
5527{
5528 ImGuiContext& g = *GImGui;
5529 IM_ASSERT(g.Initialized);
5530
5531 if (g.FrameCountEnded != g.FrameCount)
5532 EndFrame();
5533 const bool first_render_of_frame = (g.FrameCountRendered != g.FrameCount);
5534 g.FrameCountRendered = g.FrameCount;
5535 g.IO.MetricsRenderWindows = 0;
5536
5537 CallContextHooks(ctx: &g, hook_type: ImGuiContextHookType_RenderPre);
5538
5539 // Add background ImDrawList (for each active viewport)
5540 for (int n = 0; n != g.Viewports.Size; n++)
5541 {
5542 ImGuiViewportP* viewport = g.Viewports[n];
5543 viewport->DrawDataBuilder.Clear();
5544 if (viewport->DrawLists[0] != NULL)
5545 AddDrawListToDrawData(out_list: &viewport->DrawDataBuilder.Layers[0], draw_list: GetBackgroundDrawList(viewport));
5546 }
5547
5548 // Add ImDrawList to render
5549 ImGuiWindow* windows_to_render_top_most[2];
5550 windows_to_render_top_most[0] = (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus)) ? g.NavWindowingTarget->RootWindowDockTree : NULL;
5551 windows_to_render_top_most[1] = (g.NavWindowingTarget ? g.NavWindowingListWindow : NULL);
5552 for (int n = 0; n != g.Windows.Size; n++)
5553 {
5554 ImGuiWindow* window = g.Windows[n];
5555 IM_MSVC_WARNING_SUPPRESS(6011); // Static Analysis false positive "warning C6011: Dereferencing NULL pointer 'window'"
5556 if (IsWindowActiveAndVisible(window) && (window->Flags & ImGuiWindowFlags_ChildWindow) == 0 && window != windows_to_render_top_most[0] && window != windows_to_render_top_most[1])
5557 AddRootWindowToDrawData(window);
5558 }
5559 for (int n = 0; n < IM_ARRAYSIZE(windows_to_render_top_most); n++)
5560 if (windows_to_render_top_most[n] && IsWindowActiveAndVisible(window: windows_to_render_top_most[n])) // NavWindowingTarget is always temporarily displayed as the top-most window
5561 AddRootWindowToDrawData(window: windows_to_render_top_most[n]);
5562
5563 // Draw modal/window whitening backgrounds
5564 if (first_render_of_frame)
5565 RenderDimmedBackgrounds();
5566
5567 // Draw software mouse cursor if requested by io.MouseDrawCursor flag
5568 if (g.IO.MouseDrawCursor && first_render_of_frame && g.MouseCursor != ImGuiMouseCursor_None)
5569 RenderMouseCursor(base_pos: g.IO.MousePos, base_scale: g.Style.MouseCursorScale, mouse_cursor: g.MouseCursor, IM_COL32_WHITE, IM_COL32_BLACK, IM_COL32(0, 0, 0, 48));
5570
5571 // Setup ImDrawData structures for end-user
5572 g.IO.MetricsRenderVertices = g.IO.MetricsRenderIndices = 0;
5573 for (int n = 0; n < g.Viewports.Size; n++)
5574 {
5575 ImGuiViewportP* viewport = g.Viewports[n];
5576 viewport->DrawDataBuilder.FlattenIntoSingleLayer();
5577
5578 // Add foreground ImDrawList (for each active viewport)
5579 if (viewport->DrawLists[1] != NULL)
5580 AddDrawListToDrawData(out_list: &viewport->DrawDataBuilder.Layers[0], draw_list: GetForegroundDrawList(viewport));
5581
5582 SetupViewportDrawData(viewport, draw_lists: &viewport->DrawDataBuilder.Layers[0]);
5583 ImDrawData* draw_data = viewport->DrawData;
5584 g.IO.MetricsRenderVertices += draw_data->TotalVtxCount;
5585 g.IO.MetricsRenderIndices += draw_data->TotalIdxCount;
5586 }
5587
5588 CallContextHooks(ctx: &g, hook_type: ImGuiContextHookType_RenderPost);
5589}
5590
5591// Calculate text size. Text can be multi-line. Optionally ignore text after a ## marker.
5592// CalcTextSize("") should return ImVec2(0.0f, g.FontSize)
5593ImVec2 ImGui::CalcTextSize(const char* text, const char* text_end, bool hide_text_after_double_hash, float wrap_width)
5594{
5595 ImGuiContext& g = *GImGui;
5596
5597 const char* text_display_end;
5598 if (hide_text_after_double_hash)
5599 text_display_end = FindRenderedTextEnd(text, text_end); // Hide anything after a '##' string
5600 else
5601 text_display_end = text_end;
5602
5603 ImFont* font = g.Font;
5604 const float font_size = g.FontSize;
5605 if (text == text_display_end)
5606 return ImVec2(0.0f, font_size);
5607 ImVec2 text_size = font->CalcTextSizeA(size: font_size, FLT_MAX, wrap_width, text_begin: text, text_end: text_display_end, NULL);
5608
5609 // Round
5610 // FIXME: This has been here since Dec 2015 (7b0bf230) but down the line we want this out.
5611 // FIXME: Investigate using ceilf or e.g.
5612 // - https://git.musl-libc.org/cgit/musl/tree/src/math/ceilf.c
5613 // - https://embarkstudios.github.io/rust-gpu/api/src/libm/math/ceilf.rs.html
5614 text_size.x = IM_FLOOR(text_size.x + 0.99999f);
5615
5616 return text_size;
5617}
5618
5619// Find window given position, search front-to-back
5620// FIXME: Note that we have an inconsequential lag here: OuterRectClipped is updated in Begin(), so windows moved programmatically
5621// with SetWindowPos() and not SetNextWindowPos() will have that rectangle lagging by a frame at the time FindHoveredWindow() is
5622// called, aka before the next Begin(). Moving window isn't affected.
5623static void FindHoveredWindow()
5624{
5625 ImGuiContext& g = *GImGui;
5626
5627 // Special handling for the window being moved: Ignore the mouse viewport check (because it may reset/lose its viewport during the undocking frame)
5628 ImGuiViewportP* moving_window_viewport = g.MovingWindow ? g.MovingWindow->Viewport : NULL;
5629 if (g.MovingWindow)
5630 g.MovingWindow->Viewport = g.MouseViewport;
5631
5632 ImGuiWindow* hovered_window = NULL;
5633 ImGuiWindow* hovered_window_ignoring_moving_window = NULL;
5634 if (g.MovingWindow && !(g.MovingWindow->Flags & ImGuiWindowFlags_NoMouseInputs))
5635 hovered_window = g.MovingWindow;
5636
5637 ImVec2 padding_regular = g.Style.TouchExtraPadding;
5638 ImVec2 padding_for_resize = g.IO.ConfigWindowsResizeFromEdges ? g.WindowsHoverPadding : padding_regular;
5639 for (int i = g.Windows.Size - 1; i >= 0; i--)
5640 {
5641 ImGuiWindow* window = g.Windows[i];
5642 IM_MSVC_WARNING_SUPPRESS(28182); // [Static Analyzer] Dereferencing NULL pointer.
5643 if (!window->Active || window->Hidden)
5644 continue;
5645 if (window->Flags & ImGuiWindowFlags_NoMouseInputs)
5646 continue;
5647 IM_ASSERT(window->Viewport);
5648 if (window->Viewport != g.MouseViewport)
5649 continue;
5650
5651 // Using the clipped AABB, a child window will typically be clipped by its parent (not always)
5652 ImRect bb(window->OuterRectClipped);
5653 if (window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize))
5654 bb.Expand(amount: padding_regular);
5655 else
5656 bb.Expand(amount: padding_for_resize);
5657 if (!bb.Contains(p: g.IO.MousePos))
5658 continue;
5659
5660 // Support for one rectangular hole in any given window
5661 // FIXME: Consider generalizing hit-testing override (with more generic data, callback, etc.) (#1512)
5662 if (window->HitTestHoleSize.x != 0)
5663 {
5664 ImVec2 hole_pos(window->Pos.x + (float)window->HitTestHoleOffset.x, window->Pos.y + (float)window->HitTestHoleOffset.y);
5665 ImVec2 hole_size((float)window->HitTestHoleSize.x, (float)window->HitTestHoleSize.y);
5666 if (ImRect(hole_pos, hole_pos + hole_size).Contains(p: g.IO.MousePos))
5667 continue;
5668 }
5669
5670 if (hovered_window == NULL)
5671 hovered_window = window;
5672 IM_MSVC_WARNING_SUPPRESS(28182); // [Static Analyzer] Dereferencing NULL pointer.
5673 if (hovered_window_ignoring_moving_window == NULL && (!g.MovingWindow || window->RootWindowDockTree != g.MovingWindow->RootWindowDockTree))
5674 hovered_window_ignoring_moving_window = window;
5675 if (hovered_window && hovered_window_ignoring_moving_window)
5676 break;
5677 }
5678
5679 g.HoveredWindow = hovered_window;
5680 g.HoveredWindowUnderMovingWindow = hovered_window_ignoring_moving_window;
5681
5682 if (g.MovingWindow)
5683 g.MovingWindow->Viewport = moving_window_viewport;
5684}
5685
5686bool ImGui::IsItemActive()
5687{
5688 ImGuiContext& g = *GImGui;
5689 if (g.ActiveId)
5690 return g.ActiveId == g.LastItemData.ID;
5691 return false;
5692}
5693
5694bool ImGui::IsItemActivated()
5695{
5696 ImGuiContext& g = *GImGui;
5697 if (g.ActiveId)
5698 if (g.ActiveId == g.LastItemData.ID && g.ActiveIdPreviousFrame != g.LastItemData.ID)
5699 return true;
5700 return false;
5701}
5702
5703bool ImGui::IsItemDeactivated()
5704{
5705 ImGuiContext& g = *GImGui;
5706 if (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HasDeactivated)
5707 return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Deactivated) != 0;
5708 return (g.ActiveIdPreviousFrame == g.LastItemData.ID && g.ActiveIdPreviousFrame != 0 && g.ActiveId != g.LastItemData.ID);
5709}
5710
5711bool ImGui::IsItemDeactivatedAfterEdit()
5712{
5713 ImGuiContext& g = *GImGui;
5714 return IsItemDeactivated() && (g.ActiveIdPreviousFrameHasBeenEditedBefore || (g.ActiveId == 0 && g.ActiveIdHasBeenEditedBefore));
5715}
5716
5717// == GetItemID() == GetFocusID()
5718bool ImGui::IsItemFocused()
5719{
5720 ImGuiContext& g = *GImGui;
5721 if (g.NavId != g.LastItemData.ID || g.NavId == 0)
5722 return false;
5723
5724 // Special handling for the dummy item after Begin() which represent the title bar or tab.
5725 // When the window is collapsed (SkipItems==true) that last item will never be overwritten so we need to detect the case.
5726 ImGuiWindow* window = g.CurrentWindow;
5727 if (g.LastItemData.ID == window->ID && window->WriteAccessed)
5728 return false;
5729
5730 return true;
5731}
5732
5733// Important: this can be useful but it is NOT equivalent to the behavior of e.g.Button()!
5734// Most widgets have specific reactions based on mouse-up/down state, mouse position etc.
5735bool ImGui::IsItemClicked(ImGuiMouseButton mouse_button)
5736{
5737 return IsMouseClicked(button: mouse_button) && IsItemHovered(flags: ImGuiHoveredFlags_None);
5738}
5739
5740bool ImGui::IsItemToggledOpen()
5741{
5742 ImGuiContext& g = *GImGui;
5743 return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_ToggledOpen) ? true : false;
5744}
5745
5746bool ImGui::IsItemToggledSelection()
5747{
5748 ImGuiContext& g = *GImGui;
5749 return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_ToggledSelection) ? true : false;
5750}
5751
5752bool ImGui::IsAnyItemHovered()
5753{
5754 ImGuiContext& g = *GImGui;
5755 return g.HoveredId != 0 || g.HoveredIdPreviousFrame != 0;
5756}
5757
5758bool ImGui::IsAnyItemActive()
5759{
5760 ImGuiContext& g = *GImGui;
5761 return g.ActiveId != 0;
5762}
5763
5764bool ImGui::IsAnyItemFocused()
5765{
5766 ImGuiContext& g = *GImGui;
5767 return g.NavId != 0 && !g.NavDisableHighlight;
5768}
5769
5770bool ImGui::IsItemVisible()
5771{
5772 ImGuiContext& g = *GImGui;
5773 return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Visible) != 0;
5774}
5775
5776bool ImGui::IsItemEdited()
5777{
5778 ImGuiContext& g = *GImGui;
5779 return (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Edited) != 0;
5780}
5781
5782// Allow last item to be overlapped by a subsequent item. Both may be activated during the same frame before the later one takes priority.
5783// FIXME: Although this is exposed, its interaction and ideal idiom with using ImGuiButtonFlags_AllowItemOverlap flag are extremely confusing, need rework.
5784void ImGui::SetItemAllowOverlap()
5785{
5786 ImGuiContext& g = *GImGui;
5787 ImGuiID id = g.LastItemData.ID;
5788 if (g.HoveredId == id)
5789 g.HoveredIdAllowOverlap = true;
5790 if (g.ActiveId == id)
5791 g.ActiveIdAllowOverlap = true;
5792}
5793
5794// FIXME: It might be undesirable that this will likely disable KeyOwner-aware shortcuts systems. Consider a more fine-tuned version for the two users of this function.
5795void ImGui::SetActiveIdUsingAllKeyboardKeys()
5796{
5797 ImGuiContext& g = *GImGui;
5798 IM_ASSERT(g.ActiveId != 0);
5799 g.ActiveIdUsingNavDirMask = (1 << ImGuiDir_COUNT) - 1;
5800 g.ActiveIdUsingAllKeyboardKeys = true;
5801 NavMoveRequestCancel();
5802}
5803
5804ImVec2 ImGui::GetItemRectMin()
5805{
5806 ImGuiContext& g = *GImGui;
5807 return g.LastItemData.Rect.Min;
5808}
5809
5810ImVec2 ImGui::GetItemRectMax()
5811{
5812 ImGuiContext& g = *GImGui;
5813 return g.LastItemData.Rect.Max;
5814}
5815
5816ImVec2 ImGui::GetItemRectSize()
5817{
5818 ImGuiContext& g = *GImGui;
5819 return g.LastItemData.Rect.GetSize();
5820}
5821
5822bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags flags)
5823{
5824 ImGuiContext& g = *GImGui;
5825 ImGuiWindow* parent_window = g.CurrentWindow;
5826
5827 flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NoDocking;
5828 flags |= (parent_window->Flags & ImGuiWindowFlags_NoMove); // Inherit the NoMove flag
5829
5830 // Size
5831 const ImVec2 content_avail = GetContentRegionAvail();
5832 ImVec2 size = ImFloor(v: size_arg);
5833 const int auto_fit_axises = ((size.x == 0.0f) ? (1 << ImGuiAxis_X) : 0x00) | ((size.y == 0.0f) ? (1 << ImGuiAxis_Y) : 0x00);
5834 if (size.x <= 0.0f)
5835 size.x = ImMax(lhs: content_avail.x + size.x, rhs: 4.0f); // Arbitrary minimum child size (0.0f causing too many issues)
5836 if (size.y <= 0.0f)
5837 size.y = ImMax(lhs: content_avail.y + size.y, rhs: 4.0f);
5838 SetNextWindowSize(size);
5839
5840 // 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.
5841 const char* temp_window_name;
5842 if (name)
5843 ImFormatStringToTempBuffer(out_buf: &temp_window_name, NULL, fmt: "%s/%s_%08X", parent_window->Name, name, id);
5844 else
5845 ImFormatStringToTempBuffer(out_buf: &temp_window_name, NULL, fmt: "%s/%08X", parent_window->Name, id);
5846
5847 const float backup_border_size = g.Style.ChildBorderSize;
5848 if (!border)
5849 g.Style.ChildBorderSize = 0.0f;
5850 bool ret = Begin(name: temp_window_name, NULL, flags);
5851 g.Style.ChildBorderSize = backup_border_size;
5852
5853 ImGuiWindow* child_window = g.CurrentWindow;
5854 child_window->ChildId = id;
5855 child_window->AutoFitChildAxises = (ImS8)auto_fit_axises;
5856
5857 // Set the cursor to handle case where the user called SetNextWindowPos()+BeginChild() manually.
5858 // While this is not really documented/defined, it seems that the expected thing to do.
5859 if (child_window->BeginCount == 1)
5860 parent_window->DC.CursorPos = child_window->Pos;
5861
5862 // Process navigation-in immediately so NavInit can run on first frame
5863 if (g.NavActivateId == id && !(flags & ImGuiWindowFlags_NavFlattened) && (child_window->DC.NavLayersActiveMask != 0 || child_window->DC.NavHasScroll))
5864 {
5865 FocusWindow(window: child_window);
5866 NavInitWindow(window: child_window, force_reinit: false);
5867 SetActiveID(id: id + 1, window: child_window); // Steal ActiveId with another arbitrary id so that key-press won't activate child item
5868 g.ActiveIdSource = ImGuiInputSource_Nav;
5869 }
5870 return ret;
5871}
5872
5873bool ImGui::BeginChild(const char* str_id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags)
5874{
5875 ImGuiWindow* window = GetCurrentWindow();
5876 return BeginChildEx(name: str_id, id: window->GetID(str: str_id), size_arg, border, flags: extra_flags);
5877}
5878
5879bool ImGui::BeginChild(ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags)
5880{
5881 IM_ASSERT(id != 0);
5882 return BeginChildEx(NULL, id, size_arg, border, flags: extra_flags);
5883}
5884
5885void ImGui::EndChild()
5886{
5887 ImGuiContext& g = *GImGui;
5888 ImGuiWindow* window = g.CurrentWindow;
5889
5890 IM_ASSERT(g.WithinEndChild == false);
5891 IM_ASSERT(window->Flags & ImGuiWindowFlags_ChildWindow); // Mismatched BeginChild()/EndChild() calls
5892
5893 g.WithinEndChild = true;
5894 if (window->BeginCount > 1)
5895 {
5896 End();
5897 }
5898 else
5899 {
5900 ImVec2 sz = window->Size;
5901 if (window->AutoFitChildAxises & (1 << ImGuiAxis_X)) // Arbitrary minimum zero-ish child size of 4.0f causes less trouble than a 0.0f
5902 sz.x = ImMax(lhs: 4.0f, rhs: sz.x);
5903 if (window->AutoFitChildAxises & (1 << ImGuiAxis_Y))
5904 sz.y = ImMax(lhs: 4.0f, rhs: sz.y);
5905 End();
5906
5907 ImGuiWindow* parent_window = g.CurrentWindow;
5908 ImRect bb(parent_window->DC.CursorPos, parent_window->DC.CursorPos + sz);
5909 ItemSize(size: sz);
5910 if ((window->DC.NavLayersActiveMask != 0 || window->DC.NavHasScroll) && !(window->Flags & ImGuiWindowFlags_NavFlattened))
5911 {
5912 ItemAdd(bb, id: window->ChildId);
5913 RenderNavHighlight(bb, id: window->ChildId);
5914
5915 // When browsing a window that has no activable items (scroll only) we keep a highlight on the child (pass g.NavId to trick into always displaying)
5916 if (window->DC.NavLayersActiveMask == 0 && window == g.NavWindow)
5917 RenderNavHighlight(bb: ImRect(bb.Min - ImVec2(2, 2), bb.Max + ImVec2(2, 2)), id: g.NavId, flags: ImGuiNavHighlightFlags_TypeThin);
5918 }
5919 else
5920 {
5921 // Not navigable into
5922 ItemAdd(bb, id: 0);
5923 }
5924 if (g.HoveredWindow == window)
5925 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow;
5926 }
5927 g.WithinEndChild = false;
5928 g.LogLinePosY = -FLT_MAX; // To enforce a carriage return
5929}
5930
5931// Helper to create a child window / scrolling region that looks like a normal widget frame.
5932bool ImGui::BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags extra_flags)
5933{
5934 ImGuiContext& g = *GImGui;
5935 const ImGuiStyle& style = g.Style;
5936 PushStyleColor(idx: ImGuiCol_ChildBg, col: style.Colors[ImGuiCol_FrameBg]);
5937 PushStyleVar(idx: ImGuiStyleVar_ChildRounding, val: style.FrameRounding);
5938 PushStyleVar(idx: ImGuiStyleVar_ChildBorderSize, val: style.FrameBorderSize);
5939 PushStyleVar(idx: ImGuiStyleVar_WindowPadding, val: style.FramePadding);
5940 bool ret = BeginChild(id, size_arg: size, border: true, extra_flags: ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysUseWindowPadding | extra_flags);
5941 PopStyleVar(count: 3);
5942 PopStyleColor();
5943 return ret;
5944}
5945
5946void ImGui::EndChildFrame()
5947{
5948 EndChild();
5949}
5950
5951static void SetWindowConditionAllowFlags(ImGuiWindow* window, ImGuiCond flags, bool enabled)
5952{
5953 window->SetWindowPosAllowFlags = enabled ? (window->SetWindowPosAllowFlags | flags) : (window->SetWindowPosAllowFlags & ~flags);
5954 window->SetWindowSizeAllowFlags = enabled ? (window->SetWindowSizeAllowFlags | flags) : (window->SetWindowSizeAllowFlags & ~flags);
5955 window->SetWindowCollapsedAllowFlags = enabled ? (window->SetWindowCollapsedAllowFlags | flags) : (window->SetWindowCollapsedAllowFlags & ~flags);
5956 window->SetWindowDockAllowFlags = enabled ? (window->SetWindowDockAllowFlags | flags) : (window->SetWindowDockAllowFlags & ~flags);
5957}
5958
5959ImGuiWindow* ImGui::FindWindowByID(ImGuiID id)
5960{
5961 ImGuiContext& g = *GImGui;
5962 return (ImGuiWindow*)g.WindowsById.GetVoidPtr(key: id);
5963}
5964
5965ImGuiWindow* ImGui::FindWindowByName(const char* name)
5966{
5967 ImGuiID id = ImHashStr(data_p: name);
5968 return FindWindowByID(id);
5969}
5970
5971static void ApplyWindowSettings(ImGuiWindow* window, ImGuiWindowSettings* settings)
5972{
5973 const ImGuiViewport* main_viewport = ImGui::GetMainViewport();
5974 window->ViewportPos = main_viewport->Pos;
5975 if (settings->ViewportId)
5976 {
5977 window->ViewportId = settings->ViewportId;
5978 window->ViewportPos = ImVec2(settings->ViewportPos.x, settings->ViewportPos.y);
5979 }
5980 window->Pos = ImFloor(v: ImVec2(settings->Pos.x + window->ViewportPos.x, settings->Pos.y + window->ViewportPos.y));
5981 if (settings->Size.x > 0 && settings->Size.y > 0)
5982 window->Size = window->SizeFull = ImFloor(v: ImVec2(settings->Size.x, settings->Size.y));
5983 window->Collapsed = settings->Collapsed;
5984 window->DockId = settings->DockId;
5985 window->DockOrder = settings->DockOrder;
5986}
5987
5988static void UpdateWindowInFocusOrderList(ImGuiWindow* window, bool just_created, ImGuiWindowFlags new_flags)
5989{
5990 ImGuiContext& g = *GImGui;
5991
5992 const bool new_is_explicit_child = (new_flags & ImGuiWindowFlags_ChildWindow) != 0 && ((new_flags & ImGuiWindowFlags_Popup) == 0 || (new_flags & ImGuiWindowFlags_ChildMenu) != 0);
5993 const bool child_flag_changed = new_is_explicit_child != window->IsExplicitChild;
5994 if ((just_created || child_flag_changed) && !new_is_explicit_child)
5995 {
5996 IM_ASSERT(!g.WindowsFocusOrder.contains(window));
5997 g.WindowsFocusOrder.push_back(v: window);
5998 window->FocusOrder = (short)(g.WindowsFocusOrder.Size - 1);
5999 }
6000 else if (!just_created && child_flag_changed && new_is_explicit_child)
6001 {
6002 IM_ASSERT(g.WindowsFocusOrder[window->FocusOrder] == window);
6003 for (int n = window->FocusOrder + 1; n < g.WindowsFocusOrder.Size; n++)
6004 g.WindowsFocusOrder[n]->FocusOrder--;
6005 g.WindowsFocusOrder.erase(it: g.WindowsFocusOrder.Data + window->FocusOrder);
6006 window->FocusOrder = -1;
6007 }
6008 window->IsExplicitChild = new_is_explicit_child;
6009}
6010
6011static ImGuiWindow* CreateNewWindow(const char* name, ImGuiWindowFlags flags)
6012{
6013 ImGuiContext& g = *GImGui;
6014 //IMGUI_DEBUG_LOG("CreateNewWindow '%s', flags = 0x%08X\n", name, flags);
6015
6016 // Create window the first time
6017 ImGuiWindow* window = IM_NEW(ImGuiWindow)(&g, name);
6018 window->Flags = flags;
6019 g.WindowsById.SetVoidPtr(key: window->ID, val: window);
6020
6021 // Default/arbitrary window position. Use SetNextWindowPos() with the appropriate condition flag to change the initial position of a window.
6022 const ImGuiViewport* main_viewport = ImGui::GetMainViewport();
6023 window->Pos = main_viewport->Pos + ImVec2(60, 60);
6024 window->ViewportPos = main_viewport->Pos;
6025
6026 // User can disable loading and saving of settings. Tooltip and child windows also don't store settings.
6027 if (!(flags & ImGuiWindowFlags_NoSavedSettings))
6028 if (ImGuiWindowSettings* settings = ImGui::FindWindowSettings(id: window->ID))
6029 {
6030 // Retrieve settings from .ini file
6031 window->SettingsOffset = g.SettingsWindows.offset_from_ptr(p: settings);
6032 SetWindowConditionAllowFlags(window, flags: ImGuiCond_FirstUseEver, enabled: false);
6033 ApplyWindowSettings(window, settings);
6034 }
6035 window->DC.CursorStartPos = window->DC.CursorMaxPos = window->DC.IdealMaxPos = window->Pos; // So first call to CalcWindowContentSizes() doesn't return crazy values
6036
6037 if ((flags & ImGuiWindowFlags_AlwaysAutoResize) != 0)
6038 {
6039 window->AutoFitFramesX = window->AutoFitFramesY = 2;
6040 window->AutoFitOnlyGrows = false;
6041 }
6042 else
6043 {
6044 if (window->Size.x <= 0.0f)
6045 window->AutoFitFramesX = 2;
6046 if (window->Size.y <= 0.0f)
6047 window->AutoFitFramesY = 2;
6048 window->AutoFitOnlyGrows = (window->AutoFitFramesX > 0) || (window->AutoFitFramesY > 0);
6049 }
6050
6051 if (flags & ImGuiWindowFlags_NoBringToFrontOnFocus)
6052 g.Windows.push_front(v: window); // Quite slow but rare and only once
6053 else
6054 g.Windows.push_back(v: window);
6055
6056 return window;
6057}
6058
6059static ImGuiWindow* GetWindowForTitleDisplay(ImGuiWindow* window)
6060{
6061 return window->DockNodeAsHost ? window->DockNodeAsHost->VisibleWindow : window;
6062}
6063
6064static ImGuiWindow* GetWindowForTitleAndMenuHeight(ImGuiWindow* window)
6065{
6066 return (window->DockNodeAsHost && window->DockNodeAsHost->VisibleWindow) ? window->DockNodeAsHost->VisibleWindow : window;
6067}
6068
6069static ImVec2 CalcWindowSizeAfterConstraint(ImGuiWindow* window, const ImVec2& size_desired)
6070{
6071 ImGuiContext& g = *GImGui;
6072 ImVec2 new_size = size_desired;
6073 if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint)
6074 {
6075 // Using -1,-1 on either X/Y axis to preserve the current size.
6076 ImRect cr = g.NextWindowData.SizeConstraintRect;
6077 new_size.x = (cr.Min.x >= 0 && cr.Max.x >= 0) ? ImClamp(v: new_size.x, mn: cr.Min.x, mx: cr.Max.x) : window->SizeFull.x;
6078 new_size.y = (cr.Min.y >= 0 && cr.Max.y >= 0) ? ImClamp(v: new_size.y, mn: cr.Min.y, mx: cr.Max.y) : window->SizeFull.y;
6079 if (g.NextWindowData.SizeCallback)
6080 {
6081 ImGuiSizeCallbackData data;
6082 data.UserData = g.NextWindowData.SizeCallbackUserData;
6083 data.Pos = window->Pos;
6084 data.CurrentSize = window->SizeFull;
6085 data.DesiredSize = new_size;
6086 g.NextWindowData.SizeCallback(&data);
6087 new_size = data.DesiredSize;
6088 }
6089 new_size.x = IM_FLOOR(new_size.x);
6090 new_size.y = IM_FLOOR(new_size.y);
6091 }
6092
6093 // Minimum size
6094 if (!(window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_AlwaysAutoResize)))
6095 {
6096 ImGuiWindow* window_for_height = GetWindowForTitleAndMenuHeight(window);
6097 const float decoration_up_height = window_for_height->TitleBarHeight() + window_for_height->MenuBarHeight();
6098 new_size = ImMax(lhs: new_size, rhs: g.Style.WindowMinSize);
6099 new_size.y = ImMax(lhs: new_size.y, rhs: decoration_up_height + ImMax(lhs: 0.0f, rhs: g.Style.WindowRounding - 1.0f)); // Reduce artifacts with very small windows
6100 }
6101 return new_size;
6102}
6103
6104static void CalcWindowContentSizes(ImGuiWindow* window, ImVec2* content_size_current, ImVec2* content_size_ideal)
6105{
6106 bool preserve_old_content_sizes = false;
6107 if (window->Collapsed && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0)
6108 preserve_old_content_sizes = true;
6109 else if (window->Hidden && window->HiddenFramesCannotSkipItems == 0 && window->HiddenFramesCanSkipItems > 0)
6110 preserve_old_content_sizes = true;
6111 if (preserve_old_content_sizes)
6112 {
6113 *content_size_current = window->ContentSize;
6114 *content_size_ideal = window->ContentSizeIdeal;
6115 return;
6116 }
6117
6118 content_size_current->x = (window->ContentSizeExplicit.x != 0.0f) ? window->ContentSizeExplicit.x : IM_FLOOR(window->DC.CursorMaxPos.x - window->DC.CursorStartPos.x);
6119 content_size_current->y = (window->ContentSizeExplicit.y != 0.0f) ? window->ContentSizeExplicit.y : IM_FLOOR(window->DC.CursorMaxPos.y - window->DC.CursorStartPos.y);
6120 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);
6121 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);
6122}
6123
6124static ImVec2 CalcWindowAutoFitSize(ImGuiWindow* window, const ImVec2& size_contents)
6125{
6126 ImGuiContext& g = *GImGui;
6127 ImGuiStyle& style = g.Style;
6128 const float decoration_up_height = window->TitleBarHeight() + window->MenuBarHeight();
6129 ImVec2 size_pad = window->WindowPadding * 2.0f;
6130 ImVec2 size_desired = size_contents + size_pad + ImVec2(0.0f, decoration_up_height);
6131 if (window->Flags & ImGuiWindowFlags_Tooltip)
6132 {
6133 // Tooltip always resize
6134 return size_desired;
6135 }
6136 else
6137 {
6138 // Maximum window size is determined by the viewport size or monitor size
6139 const bool is_popup = (window->Flags & ImGuiWindowFlags_Popup) != 0;
6140 const bool is_menu = (window->Flags & ImGuiWindowFlags_ChildMenu) != 0;
6141 ImVec2 size_min = style.WindowMinSize;
6142 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)
6143 size_min = ImMin(lhs: size_min, rhs: ImVec2(4.0f, 4.0f));
6144
6145 ImVec2 avail_size = window->Viewport->WorkSize;
6146 if (window->ViewportOwned)
6147 avail_size = ImVec2(FLT_MAX, FLT_MAX);
6148 const int monitor_idx = window->ViewportAllowPlatformMonitorExtend;
6149 if (monitor_idx >= 0 && monitor_idx < g.PlatformIO.Monitors.Size)
6150 avail_size = g.PlatformIO.Monitors[monitor_idx].WorkSize;
6151 ImVec2 size_auto_fit = ImClamp(v: size_desired, mn: size_min, mx: ImMax(lhs: size_min, rhs: avail_size - style.DisplaySafeAreaPadding * 2.0f));
6152
6153 // When the window cannot fit all contents (either because of constraints, either because screen is too small),
6154 // we are growing the size on the other axis to compensate for expected scrollbar. FIXME: Might turn bigger than ViewportSize-WindowPadding.
6155 ImVec2 size_auto_fit_after_constraint = CalcWindowSizeAfterConstraint(window, size_desired: size_auto_fit);
6156 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);
6157 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);
6158 if (will_have_scrollbar_x)
6159 size_auto_fit.y += style.ScrollbarSize;
6160 if (will_have_scrollbar_y)
6161 size_auto_fit.x += style.ScrollbarSize;
6162 return size_auto_fit;
6163 }
6164}
6165
6166ImVec2 ImGui::CalcWindowNextAutoFitSize(ImGuiWindow* window)
6167{
6168 ImVec2 size_contents_current;
6169 ImVec2 size_contents_ideal;
6170 CalcWindowContentSizes(window, content_size_current: &size_contents_current, content_size_ideal: &size_contents_ideal);
6171 ImVec2 size_auto_fit = CalcWindowAutoFitSize(window, size_contents: size_contents_ideal);
6172 ImVec2 size_final = CalcWindowSizeAfterConstraint(window, size_desired: size_auto_fit);
6173 return size_final;
6174}
6175
6176static ImGuiCol GetWindowBgColorIdx(ImGuiWindow* window)
6177{
6178 if (window->Flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup))
6179 return ImGuiCol_PopupBg;
6180 if ((window->Flags & ImGuiWindowFlags_ChildWindow) && !window->DockIsActive)
6181 return ImGuiCol_ChildBg;
6182 return ImGuiCol_WindowBg;
6183}
6184
6185static void CalcResizePosSizeFromAnyCorner(ImGuiWindow* window, const ImVec2& corner_target, const ImVec2& corner_norm, ImVec2* out_pos, ImVec2* out_size)
6186{
6187 ImVec2 pos_min = ImLerp(a: corner_target, b: window->Pos, t: corner_norm); // Expected window upper-left
6188 ImVec2 pos_max = ImLerp(a: window->Pos + window->Size, b: corner_target, t: corner_norm); // Expected window lower-right
6189 ImVec2 size_expected = pos_max - pos_min;
6190 ImVec2 size_constrained = CalcWindowSizeAfterConstraint(window, size_desired: size_expected);
6191 *out_pos = pos_min;
6192 if (corner_norm.x == 0.0f)
6193 out_pos->x -= (size_constrained.x - size_expected.x);
6194 if (corner_norm.y == 0.0f)
6195 out_pos->y -= (size_constrained.y - size_expected.y);
6196 *out_size = size_constrained;
6197}
6198
6199// Data for resizing from corner
6200struct ImGuiResizeGripDef
6201{
6202 ImVec2 CornerPosN;
6203 ImVec2 InnerDir;
6204 int AngleMin12, AngleMax12;
6205};
6206static const ImGuiResizeGripDef resize_grip_def[4] =
6207{
6208 { .CornerPosN: ImVec2(1, 1), .InnerDir: ImVec2(-1, -1), .AngleMin12: 0, .AngleMax12: 3 }, // Lower-right
6209 { .CornerPosN: ImVec2(0, 1), .InnerDir: ImVec2(+1, -1), .AngleMin12: 3, .AngleMax12: 6 }, // Lower-left
6210 { .CornerPosN: ImVec2(0, 0), .InnerDir: ImVec2(+1, +1), .AngleMin12: 6, .AngleMax12: 9 }, // Upper-left (Unused)
6211 { .CornerPosN: ImVec2(1, 0), .InnerDir: ImVec2(-1, +1), .AngleMin12: 9, .AngleMax12: 12 } // Upper-right (Unused)
6212};
6213
6214// Data for resizing from borders
6215struct ImGuiResizeBorderDef
6216{
6217 ImVec2 InnerDir;
6218 ImVec2 SegmentN1, SegmentN2;
6219 float OuterAngle;
6220};
6221static const ImGuiResizeBorderDef resize_border_def[4] =
6222{
6223 { .InnerDir: ImVec2(+1, 0), .SegmentN1: ImVec2(0, 1), .SegmentN2: ImVec2(0, 0), IM_PI * 1.00f }, // Left
6224 { .InnerDir: ImVec2(-1, 0), .SegmentN1: ImVec2(1, 0), .SegmentN2: ImVec2(1, 1), IM_PI * 0.00f }, // Right
6225 { .InnerDir: ImVec2(0, +1), .SegmentN1: ImVec2(0, 0), .SegmentN2: ImVec2(1, 0), IM_PI * 1.50f }, // Up
6226 { .InnerDir: ImVec2(0, -1), .SegmentN1: ImVec2(1, 1), .SegmentN2: ImVec2(0, 1), IM_PI * 0.50f } // Down
6227};
6228
6229static ImRect GetResizeBorderRect(ImGuiWindow* window, int border_n, float perp_padding, float thickness)
6230{
6231 ImRect rect = window->Rect();
6232 if (thickness == 0.0f)
6233 rect.Max -= ImVec2(1, 1);
6234 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); }
6235 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); }
6236 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); }
6237 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); }
6238 IM_ASSERT(0);
6239 return ImRect();
6240}
6241
6242// 0..3: corners (Lower-right, Lower-left, Unused, Unused)
6243ImGuiID ImGui::GetWindowResizeCornerID(ImGuiWindow* window, int n)
6244{
6245 IM_ASSERT(n >= 0 && n < 4);
6246 ImGuiID id = window->DockIsActive ? window->DockNode->HostWindow->ID : window->ID;
6247 id = ImHashStr(data_p: "#RESIZE", data_size: 0, seed: id);
6248 id = ImHashData(data_p: &n, data_size: sizeof(int), seed: id);
6249 return id;
6250}
6251
6252// Borders (Left, Right, Up, Down)
6253ImGuiID ImGui::GetWindowResizeBorderID(ImGuiWindow* window, ImGuiDir dir)
6254{
6255 IM_ASSERT(dir >= 0 && dir < 4);
6256 int n = (int)dir + 4;
6257 ImGuiID id = window->DockIsActive ? window->DockNode->HostWindow->ID : window->ID;
6258 id = ImHashStr(data_p: "#RESIZE", data_size: 0, seed: id);
6259 id = ImHashData(data_p: &n, data_size: sizeof(int), seed: id);
6260 return id;
6261}
6262
6263// Handle resize for: Resize Grips, Borders, Gamepad
6264// Return true when using auto-fit (double-click on resize grip)
6265static 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)
6266{
6267 ImGuiContext& g = *GImGui;
6268 ImGuiWindowFlags flags = window->Flags;
6269
6270 if ((flags & ImGuiWindowFlags_NoResize) || (flags & ImGuiWindowFlags_AlwaysAutoResize) || window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0)
6271 return false;
6272 if (window->WasActive == false) // Early out to avoid running this code for e.g. a hidden implicit/fallback Debug window.
6273 return false;
6274
6275 bool ret_auto_fit = false;
6276 const int resize_border_count = g.IO.ConfigWindowsResizeFromEdges ? 4 : 0;
6277 const float grip_draw_size = IM_FLOOR(ImMax(g.FontSize * 1.35f, window->WindowRounding + 1.0f + g.FontSize * 0.2f));
6278 const float grip_hover_inner_size = IM_FLOOR(grip_draw_size * 0.75f);
6279 const float grip_hover_outer_size = g.IO.ConfigWindowsResizeFromEdges ? WINDOWS_HOVER_PADDING : 0.0f;
6280
6281 ImVec2 pos_target(FLT_MAX, FLT_MAX);
6282 ImVec2 size_target(FLT_MAX, FLT_MAX);
6283
6284 // Clip mouse interaction rectangles within the viewport rectangle (in practice the narrowing is going to happen most of the time).
6285 // - Not narrowing would mostly benefit the situation where OS windows _without_ decoration have a threshold for hovering when outside their limits.
6286 // This is however not the case with current backends under Win32, but a custom borderless window implementation would benefit from it.
6287 // - 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.
6288 // - Note that we are unable to tell if the platform setup allows hovering with a distance threshold (on Win32, decorated window have such threshold).
6289 // We only clip interaction so we overwrite window->ClipRect, cannot call PushClipRect() yet as DrawList is not yet setup.
6290 const bool clip_with_viewport_rect = !(g.IO.BackendFlags & ImGuiBackendFlags_HasMouseHoveredViewport) || (g.IO.MouseHoveredViewport != window->ViewportId) || !(window->Viewport->Flags & ImGuiViewportFlags_NoDecoration);
6291 if (clip_with_viewport_rect)
6292 window->ClipRect = window->Viewport->GetMainRect();
6293
6294 // Resize grips and borders are on layer 1
6295 window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;
6296
6297 // Manual resize grips
6298 PushID(str_id: "#RESIZE");
6299 for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++)
6300 {
6301 const ImGuiResizeGripDef& def = resize_grip_def[resize_grip_n];
6302 const ImVec2 corner = ImLerp(a: window->Pos, b: window->Pos + window->Size, t: def.CornerPosN);
6303
6304 // Using the FlattenChilds button flag we make the resize button accessible even if we are hovering over a child window
6305 bool hovered, held;
6306 ImRect resize_rect(corner - def.InnerDir * grip_hover_outer_size, corner + def.InnerDir * grip_hover_inner_size);
6307 if (resize_rect.Min.x > resize_rect.Max.x) ImSwap(a&: resize_rect.Min.x, b&: resize_rect.Max.x);
6308 if (resize_rect.Min.y > resize_rect.Max.y) ImSwap(a&: resize_rect.Min.y, b&: resize_rect.Max.y);
6309 ImGuiID resize_grip_id = window->GetID(n: resize_grip_n); // == GetWindowResizeCornerID()
6310 ItemAdd(bb: resize_rect, id: resize_grip_id, NULL, extra_flags: ImGuiItemFlags_NoNav);
6311 ButtonBehavior(bb: resize_rect, id: resize_grip_id, out_hovered: &hovered, out_held: &held, flags: ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_NoNavFocus);
6312 //GetForegroundDrawList(window)->AddRect(resize_rect.Min, resize_rect.Max, IM_COL32(255, 255, 0, 255));
6313 if (hovered || held)
6314 g.MouseCursor = (resize_grip_n & 1) ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE;
6315
6316 if (held && g.IO.MouseClickedCount[0] == 2 && resize_grip_n == 0)
6317 {
6318 // Manual auto-fit when double-clicking
6319 size_target = CalcWindowSizeAfterConstraint(window, size_desired: size_auto_fit);
6320 ret_auto_fit = true;
6321 ClearActiveID();
6322 }
6323 else if (held)
6324 {
6325 // Resize from any of the four corners
6326 // We don't use an incremental MouseDelta but rather compute an absolute target size based on mouse position
6327 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);
6328 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);
6329 ImVec2 corner_target = g.IO.MousePos - g.ActiveIdClickOffset + ImLerp(a: def.InnerDir * grip_hover_outer_size, b: def.InnerDir * -grip_hover_inner_size, t: def.CornerPosN); // Corner of the window corresponding to our corner grip
6330 corner_target = ImClamp(v: corner_target, mn: clamp_min, mx: clamp_max);
6331 CalcResizePosSizeFromAnyCorner(window, corner_target, corner_norm: def.CornerPosN, out_pos: &pos_target, out_size: &size_target);
6332 }
6333
6334 // Only lower-left grip is visible before hovering/activating
6335 if (resize_grip_n == 0 || held || hovered)
6336 resize_grip_col[resize_grip_n] = GetColorU32(idx: held ? ImGuiCol_ResizeGripActive : hovered ? ImGuiCol_ResizeGripHovered : ImGuiCol_ResizeGrip);
6337 }
6338 for (int border_n = 0; border_n < resize_border_count; border_n++)
6339 {
6340 const ImGuiResizeBorderDef& def = resize_border_def[border_n];
6341 const ImGuiAxis axis = (border_n == ImGuiDir_Left || border_n == ImGuiDir_Right) ? ImGuiAxis_X : ImGuiAxis_Y;
6342
6343 bool hovered, held;
6344 ImRect border_rect = GetResizeBorderRect(window, border_n, perp_padding: grip_hover_inner_size, thickness: WINDOWS_HOVER_PADDING);
6345 ImGuiID border_id = window->GetID(n: border_n + 4); // == GetWindowResizeBorderID()
6346 ItemAdd(bb: border_rect, id: border_id, NULL, extra_flags: ImGuiItemFlags_NoNav);
6347 ButtonBehavior(bb: border_rect, id: border_id, out_hovered: &hovered, out_held: &held, flags: ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_NoNavFocus);
6348 //GetForegroundDrawLists(window)->AddRect(border_rect.Min, border_rect.Max, IM_COL32(255, 255, 0, 255));
6349 if ((hovered && g.HoveredIdTimer > WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER) || held)
6350 {
6351 g.MouseCursor = (axis == ImGuiAxis_X) ? ImGuiMouseCursor_ResizeEW : ImGuiMouseCursor_ResizeNS;
6352 if (held)
6353 *border_held = border_n;
6354 }
6355 if (held)
6356 {
6357 ImVec2 clamp_min(border_n == ImGuiDir_Right ? visibility_rect.Min.x : -FLT_MAX, border_n == ImGuiDir_Down ? visibility_rect.Min.y : -FLT_MAX);
6358 ImVec2 clamp_max(border_n == ImGuiDir_Left ? visibility_rect.Max.x : +FLT_MAX, border_n == ImGuiDir_Up ? visibility_rect.Max.y : +FLT_MAX);
6359 ImVec2 border_target = window->Pos;
6360 border_target[axis] = g.IO.MousePos[axis] - g.ActiveIdClickOffset[axis] + WINDOWS_HOVER_PADDING;
6361 border_target = ImClamp(v: border_target, mn: clamp_min, mx: clamp_max);
6362 CalcResizePosSizeFromAnyCorner(window, corner_target: border_target, corner_norm: ImMin(lhs: def.SegmentN1, rhs: def.SegmentN2), out_pos: &pos_target, out_size: &size_target);
6363 }
6364 }
6365 PopID();
6366
6367 // Restore nav layer
6368 window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
6369
6370 // Navigation resize (keyboard/gamepad)
6371 // FIXME: This cannot be moved to NavUpdateWindowing() because CalcWindowSizeAfterConstraint() need to callback into user.
6372 // Not even sure the callback works here.
6373 if (g.NavWindowingTarget && g.NavWindowingTarget->RootWindowDockTree == window)
6374 {
6375 ImVec2 nav_resize_dir;
6376 if (g.NavInputSource == ImGuiInputSource_Keyboard && g.IO.KeyShift)
6377 nav_resize_dir = GetKeyVector2d(key_left: ImGuiKey_LeftArrow, key_right: ImGuiKey_RightArrow, key_up: ImGuiKey_UpArrow, key_down: ImGuiKey_DownArrow);
6378 if (g.NavInputSource == ImGuiInputSource_Gamepad)
6379 nav_resize_dir = GetKeyVector2d(key_left: ImGuiKey_GamepadDpadLeft, key_right: ImGuiKey_GamepadDpadRight, key_up: ImGuiKey_GamepadDpadUp, key_down: ImGuiKey_GamepadDpadDown);
6380 if (nav_resize_dir.x != 0.0f || nav_resize_dir.y != 0.0f)
6381 {
6382 const float NAV_RESIZE_SPEED = 600.0f;
6383 const float resize_step = NAV_RESIZE_SPEED * g.IO.DeltaTime * ImMin(lhs: g.IO.DisplayFramebufferScale.x, rhs: g.IO.DisplayFramebufferScale.y);
6384 g.NavWindowingAccumDeltaSize += nav_resize_dir * resize_step;
6385 g.NavWindowingAccumDeltaSize = ImMax(lhs: g.NavWindowingAccumDeltaSize, rhs: visibility_rect.Min - window->Pos - window->Size); // We need Pos+Size >= visibility_rect.Min, so Size >= visibility_rect.Min - Pos, so size_delta >= visibility_rect.Min - window->Pos - window->Size
6386 g.NavWindowingToggleLayer = false;
6387 g.NavDisableMouseHover = true;
6388 resize_grip_col[0] = GetColorU32(idx: ImGuiCol_ResizeGripActive);
6389 ImVec2 accum_floored = ImFloor(v: g.NavWindowingAccumDeltaSize);
6390 if (accum_floored.x != 0.0f || accum_floored.y != 0.0f)
6391 {
6392 // FIXME-NAV: Should store and accumulate into a separate size buffer to handle sizing constraints properly, right now a constraint will make us stuck.
6393 size_target = CalcWindowSizeAfterConstraint(window, size_desired: window->SizeFull + accum_floored);
6394 g.NavWindowingAccumDeltaSize -= accum_floored;
6395 }
6396 }
6397 }
6398
6399 // Apply back modified position/size to window
6400 if (size_target.x != FLT_MAX)
6401 {
6402 window->SizeFull = size_target;
6403 MarkIniSettingsDirty(window);
6404 }
6405 if (pos_target.x != FLT_MAX)
6406 {
6407 window->Pos = ImFloor(v: pos_target);
6408 MarkIniSettingsDirty(window);
6409 }
6410
6411 window->Size = window->SizeFull;
6412 return ret_auto_fit;
6413}
6414
6415static inline void ClampWindowPos(ImGuiWindow* window, const ImRect& visibility_rect)
6416{
6417 ImGuiContext& g = *GImGui;
6418 ImVec2 size_for_clamping = window->Size;
6419 if (g.IO.ConfigWindowsMoveFromTitleBarOnly && (!(window->Flags & ImGuiWindowFlags_NoTitleBar) || window->DockNodeAsHost))
6420 size_for_clamping.y = ImGui::GetFrameHeight(); // Not using window->TitleBarHeight() as DockNodeAsHost will report 0.0f here.
6421 window->Pos = ImClamp(v: window->Pos, mn: visibility_rect.Min - size_for_clamping, mx: visibility_rect.Max);
6422}
6423
6424static void ImGui::RenderWindowOuterBorders(ImGuiWindow* window)
6425{
6426 ImGuiContext& g = *GImGui;
6427 float rounding = window->WindowRounding;
6428 float border_size = window->WindowBorderSize;
6429 if (border_size > 0.0f && !(window->Flags & ImGuiWindowFlags_NoBackground))
6430 window->DrawList->AddRect(p_min: window->Pos, p_max: window->Pos + window->Size, col: GetColorU32(idx: ImGuiCol_Border), rounding, flags: 0, thickness: border_size);
6431
6432 int border_held = window->ResizeBorderHeld;
6433 if (border_held != -1)
6434 {
6435 const ImGuiResizeBorderDef& def = resize_border_def[border_held];
6436 ImRect border_r = GetResizeBorderRect(window, border_n: border_held, perp_padding: rounding, thickness: 0.0f);
6437 window->DrawList->PathArcTo(center: ImLerp(a: border_r.Min, b: border_r.Max, t: def.SegmentN1) + ImVec2(0.5f, 0.5f) + def.InnerDir * rounding, radius: rounding, a_min: def.OuterAngle - IM_PI * 0.25f, a_max: def.OuterAngle);
6438 window->DrawList->PathArcTo(center: ImLerp(a: border_r.Min, b: border_r.Max, t: def.SegmentN2) + ImVec2(0.5f, 0.5f) + def.InnerDir * rounding, radius: rounding, a_min: def.OuterAngle, a_max: def.OuterAngle + IM_PI * 0.25f);
6439 window->DrawList->PathStroke(col: GetColorU32(idx: ImGuiCol_SeparatorActive), flags: 0, thickness: ImMax(lhs: 2.0f, rhs: border_size)); // Thicker than usual
6440 }
6441 if (g.Style.FrameBorderSize > 0 && !(window->Flags & ImGuiWindowFlags_NoTitleBar) && !window->DockIsActive)
6442 {
6443 float y = window->Pos.y + window->TitleBarHeight() - 1;
6444 window->DrawList->AddLine(p1: ImVec2(window->Pos.x + border_size, y), p2: ImVec2(window->Pos.x + window->Size.x - border_size, y), col: GetColorU32(idx: ImGuiCol_Border), thickness: g.Style.FrameBorderSize);
6445 }
6446}
6447
6448// Draw background and borders
6449// Draw and handle scrollbars
6450void 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)
6451{
6452 ImGuiContext& g = *GImGui;
6453 ImGuiStyle& style = g.Style;
6454 ImGuiWindowFlags flags = window->Flags;
6455
6456 // Ensure that ScrollBar doesn't read last frame's SkipItems
6457 IM_ASSERT(window->BeginCount == 0);
6458 window->SkipItems = false;
6459
6460 // Draw window + handle manual resize
6461 // As we highlight the title bar when want_focus is set, multiple reappearing windows will have their title bar highlighted on their reappearing frame.
6462 const float window_rounding = window->WindowRounding;
6463 const float window_border_size = window->WindowBorderSize;
6464 if (window->Collapsed)
6465 {
6466 // Title bar only
6467 const float backup_border_size = style.FrameBorderSize;
6468 g.Style.FrameBorderSize = window->WindowBorderSize;
6469 ImU32 title_bar_col = GetColorU32(idx: (title_bar_is_highlight && !g.NavDisableHighlight) ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBgCollapsed);
6470 if (window->ViewportOwned)
6471 title_bar_col |= IM_COL32_A_MASK; // No alpha (we don't support is_docking_transparent_payload here because simpler and less meaningful, but could with a bit of code shuffle/reuse)
6472 RenderFrame(p_min: title_bar_rect.Min, p_max: title_bar_rect.Max, fill_col: title_bar_col, border: true, rounding: window_rounding);
6473 g.Style.FrameBorderSize = backup_border_size;
6474 }
6475 else
6476 {
6477 // Window background
6478 if (!(flags & ImGuiWindowFlags_NoBackground))
6479 {
6480 bool is_docking_transparent_payload = false;
6481 if (g.DragDropActive && (g.FrameCount - g.DragDropAcceptFrameCount) <= 1 && g.IO.ConfigDockingTransparentPayload)
6482 if (g.DragDropPayload.IsDataType(IMGUI_PAYLOAD_TYPE_WINDOW) && *(ImGuiWindow**)g.DragDropPayload.Data == window)
6483 is_docking_transparent_payload = true;
6484
6485 ImU32 bg_col = GetColorU32(idx: GetWindowBgColorIdx(window));
6486 if (window->ViewportOwned)
6487 {
6488 bg_col |= IM_COL32_A_MASK; // No alpha
6489 if (is_docking_transparent_payload)
6490 window->Viewport->Alpha *= DOCKING_TRANSPARENT_PAYLOAD_ALPHA;
6491 }
6492 else
6493 {
6494 // Adjust alpha. For docking
6495 bool override_alpha = false;
6496 float alpha = 1.0f;
6497 if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasBgAlpha)
6498 {
6499 alpha = g.NextWindowData.BgAlphaVal;
6500 override_alpha = true;
6501 }
6502 if (is_docking_transparent_payload)
6503 {
6504 alpha *= DOCKING_TRANSPARENT_PAYLOAD_ALPHA; // FIXME-DOCK: Should that be an override?
6505 override_alpha = true;
6506 }
6507 if (override_alpha)
6508 bg_col = (bg_col & ~IM_COL32_A_MASK) | (IM_F32_TO_INT8_SAT(alpha) << IM_COL32_A_SHIFT);
6509 }
6510
6511 // Render, for docked windows and host windows we ensure bg goes before decorations
6512 if (window->DockIsActive)
6513 window->DockNode->LastBgColor = bg_col;
6514 ImDrawList* bg_draw_list = window->DockIsActive ? window->DockNode->HostWindow->DrawList : window->DrawList;
6515 if (window->DockIsActive || (flags & ImGuiWindowFlags_DockNodeHost))
6516 bg_draw_list->ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_BG);
6517 bg_draw_list->AddRectFilled(p_min: window->Pos + ImVec2(0, window->TitleBarHeight()), p_max: window->Pos + window->Size, col: bg_col, rounding: window_rounding, flags: (flags & ImGuiWindowFlags_NoTitleBar) ? 0 : ImDrawFlags_RoundCornersBottom);
6518 if (window->DockIsActive || (flags & ImGuiWindowFlags_DockNodeHost))
6519 bg_draw_list->ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_FG);
6520 }
6521 if (window->DockIsActive)
6522 window->DockNode->IsBgDrawnThisFrame = true;
6523
6524 // Title bar
6525 // (when docked, DockNode are drawing their own title bar. Individual windows however do NOT set the _NoTitleBar flag,
6526 // in order for their pos/size to be matching their undocking state.)
6527 if (!(flags & ImGuiWindowFlags_NoTitleBar) && !window->DockIsActive)
6528 {
6529 ImU32 title_bar_col = GetColorU32(idx: title_bar_is_highlight ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg);
6530 window->DrawList->AddRectFilled(p_min: title_bar_rect.Min, p_max: title_bar_rect.Max, col: title_bar_col, rounding: window_rounding, flags: ImDrawFlags_RoundCornersTop);
6531 }
6532
6533 // Menu bar
6534 if (flags & ImGuiWindowFlags_MenuBar)
6535 {
6536 ImRect menu_bar_rect = window->MenuBarRect();
6537 menu_bar_rect.ClipWith(r: window->Rect()); // Soft clipping, in particular child window don't have minimum size covering the menu bar so this is useful for them.
6538 window->DrawList->AddRectFilled(p_min: menu_bar_rect.Min + ImVec2(window_border_size, 0), p_max: menu_bar_rect.Max - ImVec2(window_border_size, 0), col: GetColorU32(idx: ImGuiCol_MenuBarBg), rounding: (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f, flags: ImDrawFlags_RoundCornersTop);
6539 if (style.FrameBorderSize > 0.0f && menu_bar_rect.Max.y < window->Pos.y + window->Size.y)
6540 window->DrawList->AddLine(p1: menu_bar_rect.GetBL(), p2: menu_bar_rect.GetBR(), col: GetColorU32(idx: ImGuiCol_Border), thickness: style.FrameBorderSize);
6541 }
6542
6543 // Docking: Unhide tab bar (small triangle in the corner), drag from small triangle to quickly undock
6544 ImGuiDockNode* node = window->DockNode;
6545 if (window->DockIsActive && node->IsHiddenTabBar() && !node->IsNoTabBar())
6546 {
6547 float unhide_sz_draw = ImFloor(f: g.FontSize * 0.70f);
6548 float unhide_sz_hit = ImFloor(f: g.FontSize * 0.55f);
6549 ImVec2 p = node->Pos;
6550 ImRect r(p, p + ImVec2(unhide_sz_hit, unhide_sz_hit));
6551 ImGuiID unhide_id = window->GetID(str: "#UNHIDE");
6552 KeepAliveID(id: unhide_id);
6553 bool hovered, held;
6554 if (ButtonBehavior(bb: r, id: unhide_id, out_hovered: &hovered, out_held: &held, flags: ImGuiButtonFlags_FlattenChildren))
6555 node->WantHiddenTabBarToggle = true;
6556 else if (held && IsMouseDragging(button: 0))
6557 StartMouseMovingWindowOrNode(window, node, undock_floating_node: true);
6558
6559 // FIXME-DOCK: Ideally we'd use ImGuiCol_TitleBgActive/ImGuiCol_TitleBg here, but neither is guaranteed to be visible enough at this sort of size..
6560 ImU32 col = GetColorU32(idx: ((held && hovered) || (node->IsFocused && !hovered)) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
6561 window->DrawList->AddTriangleFilled(p1: p, p2: p + ImVec2(unhide_sz_draw, 0.0f), p3: p + ImVec2(0.0f, unhide_sz_draw), col);
6562 }
6563
6564 // Scrollbars
6565 if (window->ScrollbarX)
6566 Scrollbar(axis: ImGuiAxis_X);
6567 if (window->ScrollbarY)
6568 Scrollbar(axis: ImGuiAxis_Y);
6569
6570 // Render resize grips (after their input handling so we don't have a frame of latency)
6571 if (handle_borders_and_resize_grips && !(flags & ImGuiWindowFlags_NoResize))
6572 {
6573 for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++)
6574 {
6575 const ImGuiResizeGripDef& grip = resize_grip_def[resize_grip_n];
6576 const ImVec2 corner = ImLerp(a: window->Pos, b: window->Pos + window->Size, t: grip.CornerPosN);
6577 window->DrawList->PathLineTo(pos: corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(window_border_size, resize_grip_draw_size) : ImVec2(resize_grip_draw_size, window_border_size)));
6578 window->DrawList->PathLineTo(pos: corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(resize_grip_draw_size, window_border_size) : ImVec2(window_border_size, resize_grip_draw_size)));
6579 window->DrawList->PathArcToFast(center: ImVec2(corner.x + grip.InnerDir.x * (window_rounding + window_border_size), corner.y + grip.InnerDir.y * (window_rounding + window_border_size)), radius: window_rounding, a_min_of_12: grip.AngleMin12, a_max_of_12: grip.AngleMax12);
6580 window->DrawList->PathFillConvex(col: resize_grip_col[resize_grip_n]);
6581 }
6582 }
6583
6584 // Borders (for dock node host they will be rendered over after the tab bar)
6585 if (handle_borders_and_resize_grips && !window->DockNodeAsHost)
6586 RenderWindowOuterBorders(window);
6587 }
6588}
6589
6590// Render title text, collapse button, close button
6591// When inside a dock node, this is handled in DockNodeCalcTabBarLayout() instead.
6592void ImGui::RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& title_bar_rect, const char* name, bool* p_open)
6593{
6594 ImGuiContext& g = *GImGui;
6595 ImGuiStyle& style = g.Style;
6596 ImGuiWindowFlags flags = window->Flags;
6597
6598 const bool has_close_button = (p_open != NULL);
6599 const bool has_collapse_button = !(flags & ImGuiWindowFlags_NoCollapse) && (style.WindowMenuButtonPosition != ImGuiDir_None);
6600
6601 // Close & Collapse button are on the Menu NavLayer and don't default focus (unless there's nothing else on that layer)
6602 // FIXME-NAV: Might want (or not?) to set the equivalent of ImGuiButtonFlags_NoNavFocus so that mouse clicks on standard title bar items don't necessarily set nav/keyboard ref?
6603 const ImGuiItemFlags item_flags_backup = g.CurrentItemFlags;
6604 g.CurrentItemFlags |= ImGuiItemFlags_NoNavDefaultFocus;
6605 window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;
6606
6607 // Layout buttons
6608 // FIXME: Would be nice to generalize the subtleties expressed here into reusable code.
6609 float pad_l = style.FramePadding.x;
6610 float pad_r = style.FramePadding.x;
6611 float button_sz = g.FontSize;
6612 ImVec2 close_button_pos;
6613 ImVec2 collapse_button_pos;
6614 if (has_close_button)
6615 {
6616 pad_r += button_sz;
6617 close_button_pos = ImVec2(title_bar_rect.Max.x - pad_r - style.FramePadding.x, title_bar_rect.Min.y);
6618 }
6619 if (has_collapse_button && style.WindowMenuButtonPosition == ImGuiDir_Right)
6620 {
6621 pad_r += button_sz;
6622 collapse_button_pos = ImVec2(title_bar_rect.Max.x - pad_r - style.FramePadding.x, title_bar_rect.Min.y);
6623 }
6624 if (has_collapse_button && style.WindowMenuButtonPosition == ImGuiDir_Left)
6625 {
6626 collapse_button_pos = ImVec2(title_bar_rect.Min.x + pad_l - style.FramePadding.x, title_bar_rect.Min.y);
6627 pad_l += button_sz;
6628 }
6629
6630 // Collapse button (submitting first so it gets priority when choosing a navigation init fallback)
6631 if (has_collapse_button)
6632 if (CollapseButton(id: window->GetID(str: "#COLLAPSE"), pos: collapse_button_pos, NULL))
6633 window->WantCollapseToggle = true; // Defer actual collapsing to next frame as we are too far in the Begin() function
6634
6635 // Close button
6636 if (has_close_button)
6637 if (CloseButton(id: window->GetID(str: "#CLOSE"), pos: close_button_pos))
6638 *p_open = false;
6639
6640 window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
6641 g.CurrentItemFlags = item_flags_backup;
6642
6643 // Title bar text (with: horizontal alignment, avoiding collapse/close button, optional "unsaved document" marker)
6644 // FIXME: Refactor text alignment facilities along with RenderText helpers, this is WAY too much messy code..
6645 const float marker_size_x = (flags & ImGuiWindowFlags_UnsavedDocument) ? button_sz * 0.80f : 0.0f;
6646 const ImVec2 text_size = CalcTextSize(text: name, NULL, hide_text_after_double_hash: true) + ImVec2(marker_size_x, 0.0f);
6647
6648 // As a nice touch we try to ensure that centered title text doesn't get affected by visibility of Close/Collapse button,
6649 // while uncentered title text will still reach edges correctly.
6650 if (pad_l > style.FramePadding.x)
6651 pad_l += g.Style.ItemInnerSpacing.x;
6652 if (pad_r > style.FramePadding.x)
6653 pad_r += g.Style.ItemInnerSpacing.x;
6654 if (style.WindowTitleAlign.x > 0.0f && style.WindowTitleAlign.x < 1.0f)
6655 {
6656 float centerness = ImSaturate(f: 1.0f - ImFabs(style.WindowTitleAlign.x - 0.5f) * 2.0f); // 0.0f on either edges, 1.0f on center
6657 float pad_extend = ImMin(lhs: ImMax(lhs: pad_l, rhs: pad_r), rhs: title_bar_rect.GetWidth() - pad_l - pad_r - text_size.x);
6658 pad_l = ImMax(lhs: pad_l, rhs: pad_extend * centerness);
6659 pad_r = ImMax(lhs: pad_r, rhs: pad_extend * centerness);
6660 }
6661
6662 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);
6663 ImRect clip_r(layout_r.Min.x, layout_r.Min.y, ImMin(lhs: layout_r.Max.x + g.Style.ItemInnerSpacing.x, rhs: title_bar_rect.Max.x), layout_r.Max.y);
6664 if (flags & ImGuiWindowFlags_UnsavedDocument)
6665 {
6666 ImVec2 marker_pos;
6667 marker_pos.x = ImClamp(v: layout_r.Min.x + (layout_r.GetWidth() - text_size.x) * style.WindowTitleAlign.x + text_size.x, mn: layout_r.Min.x, mx: layout_r.Max.x);
6668 marker_pos.y = (layout_r.Min.y + layout_r.Max.y) * 0.5f;
6669 if (marker_pos.x > layout_r.Min.x)
6670 {
6671 RenderBullet(draw_list: window->DrawList, pos: marker_pos, col: GetColorU32(idx: ImGuiCol_Text));
6672 clip_r.Max.x = ImMin(lhs: clip_r.Max.x, rhs: marker_pos.x - (int)(marker_size_x * 0.5f));
6673 }
6674 }
6675 //if (g.IO.KeyShift) window->DrawList->AddRect(layout_r.Min, layout_r.Max, IM_COL32(255, 128, 0, 255)); // [DEBUG]
6676 //if (g.IO.KeyCtrl) window->DrawList->AddRect(clip_r.Min, clip_r.Max, IM_COL32(255, 128, 0, 255)); // [DEBUG]
6677 RenderTextClipped(pos_min: layout_r.Min, pos_max: layout_r.Max, text: name, NULL, text_size_if_known: &text_size, align: style.WindowTitleAlign, clip_rect: &clip_r);
6678}
6679
6680void ImGui::UpdateWindowParentAndRootLinks(ImGuiWindow* window, ImGuiWindowFlags flags, ImGuiWindow* parent_window)
6681{
6682 window->ParentWindow = parent_window;
6683 window->RootWindow = window->RootWindowPopupTree = window->RootWindowDockTree = window->RootWindowForTitleBarHighlight = window->RootWindowForNav = window;
6684 if (parent_window && (flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Tooltip))
6685 {
6686 window->RootWindowDockTree = parent_window->RootWindowDockTree;
6687 if (!window->DockIsActive && !(parent_window->Flags & ImGuiWindowFlags_DockNodeHost))
6688 window->RootWindow = parent_window->RootWindow;
6689 }
6690 if (parent_window && (flags & ImGuiWindowFlags_Popup))
6691 window->RootWindowPopupTree = parent_window->RootWindowPopupTree;
6692 if (parent_window && !(flags & ImGuiWindowFlags_Modal) && (flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup))) // FIXME: simply use _NoTitleBar ?
6693 window->RootWindowForTitleBarHighlight = parent_window->RootWindowForTitleBarHighlight;
6694 while (window->RootWindowForNav->Flags & ImGuiWindowFlags_NavFlattened)
6695 {
6696 IM_ASSERT(window->RootWindowForNav->ParentWindow != NULL);
6697 window->RootWindowForNav = window->RootWindowForNav->ParentWindow;
6698 }
6699}
6700
6701// When a modal popup is open, newly created windows that want focus (i.e. are not popups and do not specify ImGuiWindowFlags_NoFocusOnAppearing)
6702// should be positioned behind that modal window, unless the window was created inside the modal begin-stack.
6703// In case of multiple stacked modals newly created window honors begin stack order and does not go below its own modal parent.
6704// - Window // FindBlockingModal() returns Modal1
6705// - Window // .. returns Modal1
6706// - Modal1 // .. returns Modal2
6707// - Window // .. returns Modal2
6708// - Window // .. returns Modal2
6709// - Modal2 // .. returns Modal2
6710static ImGuiWindow* ImGui::FindBlockingModal(ImGuiWindow* window)
6711{
6712 ImGuiContext& g = *GImGui;
6713 if (g.OpenPopupStack.Size <= 0)
6714 return NULL;
6715
6716 // Find a modal that has common parent with specified window. Specified window should be positioned behind that modal.
6717 for (int i = g.OpenPopupStack.Size - 1; i >= 0; i--)
6718 {
6719 ImGuiWindow* popup_window = g.OpenPopupStack.Data[i].Window;
6720 if (popup_window == NULL || !(popup_window->Flags & ImGuiWindowFlags_Modal))
6721 continue;
6722 if (!popup_window->Active && !popup_window->WasActive) // Check WasActive, because this code may run before popup renders on current frame, also check Active to handle newly created windows.
6723 continue;
6724 if (IsWindowWithinBeginStackOf(window, potential_parent: popup_window)) // Window is rendered over last modal, no render order change needed.
6725 break;
6726 for (ImGuiWindow* parent = popup_window->ParentWindowInBeginStack->RootWindow; parent != NULL; parent = parent->ParentWindowInBeginStack->RootWindow)
6727 if (IsWindowWithinBeginStackOf(window, potential_parent: parent))
6728 return popup_window; // Place window above its begin stack parent.
6729 }
6730 return NULL;
6731}
6732
6733// Push a new Dear ImGui window to add widgets to.
6734// - 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.
6735// - Begin/End can be called multiple times during the frame with the same window name to append content.
6736// - The window name is used as a unique identifier to preserve window information across frames (and save rudimentary information to the .ini file).
6737// 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.
6738// - 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.
6739// - 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.
6740bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
6741{
6742 ImGuiContext& g = *GImGui;
6743 const ImGuiStyle& style = g.Style;
6744 IM_ASSERT(name != NULL && name[0] != '\0'); // Window name required
6745 IM_ASSERT(g.WithinFrameScope); // Forgot to call ImGui::NewFrame()
6746 IM_ASSERT(g.FrameCountEnded != g.FrameCount); // Called ImGui::Render() or ImGui::EndFrame() and haven't called ImGui::NewFrame() again yet
6747
6748 // Find or create
6749 ImGuiWindow* window = FindWindowByName(name);
6750 const bool window_just_created = (window == NULL);
6751 if (window_just_created)
6752 window = CreateNewWindow(name, flags);
6753
6754 // Automatically disable manual moving/resizing when NoInputs is set
6755 if ((flags & ImGuiWindowFlags_NoInputs) == ImGuiWindowFlags_NoInputs)
6756 flags |= ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
6757
6758 if (flags & ImGuiWindowFlags_NavFlattened)
6759 IM_ASSERT(flags & ImGuiWindowFlags_ChildWindow);
6760
6761 const int current_frame = g.FrameCount;
6762 const bool first_begin_of_the_frame = (window->LastFrameActive != current_frame);
6763 window->IsFallbackWindow = (g.CurrentWindowStack.Size == 0 && g.WithinFrameScopeWithImplicitWindow);
6764
6765 // Update the Appearing flag (note: the BeginDocked() path may also set this to true later)
6766 bool window_just_activated_by_user = (window->LastFrameActive < current_frame - 1); // Not using !WasActive because the implicit "Debug" window would always toggle off->on
6767 if (flags & ImGuiWindowFlags_Popup)
6768 {
6769 ImGuiPopupData& popup_ref = g.OpenPopupStack[g.BeginPopupStack.Size];
6770 window_just_activated_by_user |= (window->PopupId != popup_ref.PopupId); // We recycle popups so treat window as activated if popup id changed
6771 window_just_activated_by_user |= (window != popup_ref.Window);
6772 }
6773
6774 // Update Flags, LastFrameActive, BeginOrderXXX fields
6775 const bool window_was_appearing = window->Appearing;
6776 if (first_begin_of_the_frame)
6777 {
6778 UpdateWindowInFocusOrderList(window, just_created: window_just_created, new_flags: flags);
6779 window->Appearing = window_just_activated_by_user;
6780 if (window->Appearing)
6781 SetWindowConditionAllowFlags(window, flags: ImGuiCond_Appearing, enabled: true);
6782 window->FlagsPreviousFrame = window->Flags;
6783 window->Flags = (ImGuiWindowFlags)flags;
6784 window->LastFrameActive = current_frame;
6785 window->LastTimeActive = (float)g.Time;
6786 window->BeginOrderWithinParent = 0;
6787 window->BeginOrderWithinContext = (short)(g.WindowsActiveCount++);
6788 }
6789 else
6790 {
6791 flags = window->Flags;
6792 }
6793
6794 // Docking
6795 // (NB: during the frame dock nodes are created, it is possible that (window->DockIsActive == false) even though (window->DockNode->Windows.Size > 1)
6796 IM_ASSERT(window->DockNode == NULL || window->DockNodeAsHost == NULL); // Cannot be both
6797 if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasDock)
6798 SetWindowDock(window, dock_id: g.NextWindowData.DockId, cond: g.NextWindowData.DockCond);
6799 if (first_begin_of_the_frame)
6800 {
6801 bool has_dock_node = (window->DockId != 0 || window->DockNode != NULL);
6802 bool new_auto_dock_node = !has_dock_node && GetWindowAlwaysWantOwnTabBar(window);
6803 bool dock_node_was_visible = window->DockNodeIsVisible;
6804 bool dock_tab_was_visible = window->DockTabIsVisible;
6805 if (has_dock_node || new_auto_dock_node)
6806 {
6807 BeginDocked(window, p_open);
6808 flags = window->Flags;
6809 if (window->DockIsActive)
6810 {
6811 IM_ASSERT(window->DockNode != NULL);
6812 g.NextWindowData.Flags &= ~ImGuiNextWindowDataFlags_HasSizeConstraint; // Docking currently override constraints
6813 }
6814
6815 // Amend the Appearing flag
6816 if (window->DockTabIsVisible && !dock_tab_was_visible && dock_node_was_visible && !window->Appearing && !window_was_appearing)
6817 {
6818 window->Appearing = true;
6819 SetWindowConditionAllowFlags(window, flags: ImGuiCond_Appearing, enabled: true);
6820 }
6821 }
6822 else
6823 {
6824 window->DockIsActive = window->DockNodeIsVisible = window->DockTabIsVisible = false;
6825 }
6826 }
6827
6828 // 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
6829 ImGuiWindow* parent_window_in_stack = (window->DockIsActive && window->DockNode->HostWindow) ? window->DockNode->HostWindow : g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back().Window;
6830 ImGuiWindow* parent_window = first_begin_of_the_frame ? ((flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup)) ? parent_window_in_stack : NULL) : window->ParentWindow;
6831 IM_ASSERT(parent_window != NULL || !(flags & ImGuiWindowFlags_ChildWindow));
6832
6833 // We allow window memory to be compacted so recreate the base stack when needed.
6834 if (window->IDStack.Size == 0)
6835 window->IDStack.push_back(v: window->ID);
6836
6837 // Add to stack
6838 // We intentionally set g.CurrentWindow to NULL to prevent usage until when the viewport is set, then will call SetCurrentWindow()
6839 g.CurrentWindow = window;
6840 ImGuiWindowStackData window_stack_data;
6841 window_stack_data.Window = window;
6842 window_stack_data.ParentLastItemDataBackup = g.LastItemData;
6843 window_stack_data.StackSizesOnBegin.SetToCurrentState();
6844 g.CurrentWindowStack.push_back(v: window_stack_data);
6845 if (flags & ImGuiWindowFlags_ChildMenu)
6846 g.BeginMenuCount++;
6847
6848 // Update ->RootWindow and others pointers (before any possible call to FocusWindow)
6849 if (first_begin_of_the_frame)
6850 {
6851 UpdateWindowParentAndRootLinks(window, flags, parent_window);
6852 window->ParentWindowInBeginStack = parent_window_in_stack;
6853 }
6854
6855 // Add to focus scope stack
6856 PushFocusScope(id: window->ID);
6857 window->NavRootFocusScopeId = g.CurrentFocusScopeId;
6858 g.CurrentWindow = NULL;
6859
6860 // Add to popup stack
6861 if (flags & ImGuiWindowFlags_Popup)
6862 {
6863 ImGuiPopupData& popup_ref = g.OpenPopupStack[g.BeginPopupStack.Size];
6864 popup_ref.Window = window;
6865 popup_ref.ParentNavLayer = parent_window_in_stack->DC.NavLayerCurrent;
6866 g.BeginPopupStack.push_back(v: popup_ref);
6867 window->PopupId = popup_ref.PopupId;
6868 }
6869
6870 // Process SetNextWindow***() calls
6871 // (FIXME: Consider splitting the HasXXX flags into X/Y components
6872 bool window_pos_set_by_api = false;
6873 bool window_size_x_set_by_api = false, window_size_y_set_by_api = false;
6874 if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasPos)
6875 {
6876 window_pos_set_by_api = (window->SetWindowPosAllowFlags & g.NextWindowData.PosCond) != 0;
6877 if (window_pos_set_by_api && ImLengthSqr(lhs: g.NextWindowData.PosPivotVal) > 0.00001f)
6878 {
6879 // May be processed on the next frame if this is our first frame and we are measuring size
6880 // FIXME: Look into removing the branch so everything can go through this same code path for consistency.
6881 window->SetWindowPosVal = g.NextWindowData.PosVal;
6882 window->SetWindowPosPivot = g.NextWindowData.PosPivotVal;
6883 window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
6884 }
6885 else
6886 {
6887 SetWindowPos(window, pos: g.NextWindowData.PosVal, cond: g.NextWindowData.PosCond);
6888 }
6889 }
6890 if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize)
6891 {
6892 window_size_x_set_by_api = (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.x > 0.0f);
6893 window_size_y_set_by_api = (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.y > 0.0f);
6894 SetWindowSize(window, size: g.NextWindowData.SizeVal, cond: g.NextWindowData.SizeCond);
6895 }
6896 if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasScroll)
6897 {
6898 if (g.NextWindowData.ScrollVal.x >= 0.0f)
6899 {
6900 window->ScrollTarget.x = g.NextWindowData.ScrollVal.x;
6901 window->ScrollTargetCenterRatio.x = 0.0f;
6902 }
6903 if (g.NextWindowData.ScrollVal.y >= 0.0f)
6904 {
6905 window->ScrollTarget.y = g.NextWindowData.ScrollVal.y;
6906 window->ScrollTargetCenterRatio.y = 0.0f;
6907 }
6908 }
6909 if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasContentSize)
6910 window->ContentSizeExplicit = g.NextWindowData.ContentSizeVal;
6911 else if (first_begin_of_the_frame)
6912 window->ContentSizeExplicit = ImVec2(0.0f, 0.0f);
6913 if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasWindowClass)
6914 window->WindowClass = g.NextWindowData.WindowClass;
6915 if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasCollapsed)
6916 SetWindowCollapsed(window, collapsed: g.NextWindowData.CollapsedVal, cond: g.NextWindowData.CollapsedCond);
6917 if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasFocus)
6918 FocusWindow(window);
6919 if (window->Appearing)
6920 SetWindowConditionAllowFlags(window, flags: ImGuiCond_Appearing, enabled: false);
6921
6922 // When reusing window again multiple times a frame, just append content (don't need to setup again)
6923 if (first_begin_of_the_frame)
6924 {
6925 // Initialize
6926 const bool window_is_child_tooltip = (flags & ImGuiWindowFlags_ChildWindow) && (flags & ImGuiWindowFlags_Tooltip); // FIXME-WIP: Undocumented behavior of Child+Tooltip for pinned tooltip (#1345)
6927 const bool window_just_appearing_after_hidden_for_resize = (window->HiddenFramesCannotSkipItems > 0);
6928 window->Active = true;
6929 window->HasCloseButton = (p_open != NULL);
6930 window->ClipRect = ImVec4(-FLT_MAX, -FLT_MAX, +FLT_MAX, +FLT_MAX);
6931 window->IDStack.resize(new_size: 1);
6932 window->DrawList->_ResetForNewFrame();
6933 window->DC.CurrentTableIdx = -1;
6934 if (flags & ImGuiWindowFlags_DockNodeHost)
6935 {
6936 window->DrawList->ChannelsSplit(count: 2);
6937 window->DrawList->ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_FG); // Render decorations on channel 1 as we will render the backgrounds manually later
6938 }
6939
6940 // Restore buffer capacity when woken from a compacted state, to avoid
6941 if (window->MemoryCompacted)
6942 GcAwakeTransientWindowBuffers(window);
6943
6944 // Update stored window name when it changes (which can _only_ happen with the "###" operator, so the ID would stay unchanged).
6945 // 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.
6946 bool window_title_visible_elsewhere = false;
6947 if ((window->Viewport && window->Viewport->Window == window) || (window->DockIsActive))
6948 window_title_visible_elsewhere = true;
6949 else if (g.NavWindowingListWindow != NULL && (window->Flags & ImGuiWindowFlags_NoNavFocus) == 0) // Window titles visible when using CTRL+TAB
6950 window_title_visible_elsewhere = true;
6951 if (window_title_visible_elsewhere && !window_just_created && strcmp(s1: name, s2: window->Name) != 0)
6952 {
6953 size_t buf_len = (size_t)window->NameBufLen;
6954 window->Name = ImStrdupcpy(dst: window->Name, p_dst_size: &buf_len, src: name);
6955 window->NameBufLen = (int)buf_len;
6956 }
6957
6958 // UPDATE CONTENTS SIZE, UPDATE HIDDEN STATUS
6959
6960 // Update contents size from last frame for auto-fitting (or use explicit size)
6961 CalcWindowContentSizes(window, content_size_current: &window->ContentSize, content_size_ideal: &window->ContentSizeIdeal);
6962
6963 // FIXME: These flags are decremented before they are used. This means that in order to have these fields produce their intended behaviors
6964 // for one frame we must set them to at least 2, which is counter-intuitive. HiddenFramesCannotSkipItems is a more complicated case because
6965 // it has a single usage before this code block and may be set below before it is finally checked.
6966 if (window->HiddenFramesCanSkipItems > 0)
6967 window->HiddenFramesCanSkipItems--;
6968 if (window->HiddenFramesCannotSkipItems > 0)
6969 window->HiddenFramesCannotSkipItems--;
6970 if (window->HiddenFramesForRenderOnly > 0)
6971 window->HiddenFramesForRenderOnly--;
6972
6973 // Hide new windows for one frame until they calculate their size
6974 if (window_just_created && (!window_size_x_set_by_api || !window_size_y_set_by_api))
6975 window->HiddenFramesCannotSkipItems = 1;
6976
6977 // Hide popup/tooltip window when re-opening while we measure size (because we recycle the windows)
6978 // We reset Size/ContentSize for reappearing popups/tooltips early in this function, so further code won't be tempted to use the old size.
6979 if (window_just_activated_by_user && (flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) != 0)
6980 {
6981 window->HiddenFramesCannotSkipItems = 1;
6982 if (flags & ImGuiWindowFlags_AlwaysAutoResize)
6983 {
6984 if (!window_size_x_set_by_api)
6985 window->Size.x = window->SizeFull.x = 0.f;
6986 if (!window_size_y_set_by_api)
6987 window->Size.y = window->SizeFull.y = 0.f;
6988 window->ContentSize = window->ContentSizeIdeal = ImVec2(0.f, 0.f);
6989 }
6990 }
6991
6992 // SELECT VIEWPORT
6993 // We need to do this before using any style/font sizes, as viewport with a different DPI may affect font sizes.
6994
6995 WindowSelectViewport(window);
6996 SetCurrentViewport(window, viewport: window->Viewport);
6997 window->FontDpiScale = (g.IO.ConfigFlags & ImGuiConfigFlags_DpiEnableScaleFonts) ? window->Viewport->DpiScale : 1.0f;
6998 SetCurrentWindow(window);
6999 flags = window->Flags;
7000
7001 // LOCK BORDER SIZE AND PADDING FOR THE FRAME (so that altering them doesn't cause inconsistencies)
7002 // We read Style data after the call to UpdateSelectWindowViewport() which might be swapping the style.
7003
7004 if (flags & ImGuiWindowFlags_ChildWindow)
7005 window->WindowBorderSize = style.ChildBorderSize;
7006 else
7007 window->WindowBorderSize = ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupBorderSize : style.WindowBorderSize;
7008 if (!window->DockIsActive && (flags & ImGuiWindowFlags_ChildWindow) && !(flags & (ImGuiWindowFlags_AlwaysUseWindowPadding | ImGuiWindowFlags_Popup)) && window->WindowBorderSize == 0.0f)
7009 window->WindowPadding = ImVec2(0.0f, (flags & ImGuiWindowFlags_MenuBar) ? style.WindowPadding.y : 0.0f);
7010 else
7011 window->WindowPadding = style.WindowPadding;
7012
7013 // Lock menu offset so size calculation can use it as menu-bar windows need a minimum size.
7014 window->DC.MenuBarOffset.x = ImMax(lhs: ImMax(lhs: window->WindowPadding.x, rhs: style.ItemSpacing.x), rhs: g.NextWindowData.MenuBarOffsetMinVal.x);
7015 window->DC.MenuBarOffset.y = g.NextWindowData.MenuBarOffsetMinVal.y;
7016
7017 bool use_current_size_for_scrollbar_x = window_just_created;
7018 bool use_current_size_for_scrollbar_y = window_just_created;
7019
7020 // Collapse window by double-clicking on title bar
7021 // 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
7022 if (!(flags & ImGuiWindowFlags_NoTitleBar) && !(flags & ImGuiWindowFlags_NoCollapse) && !window->DockIsActive)
7023 {
7024 // 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.
7025 ImRect title_bar_rect = window->TitleBarRect();
7026 if (g.HoveredWindow == window && g.HoveredId == 0 && g.HoveredIdPreviousFrame == 0 && IsMouseHoveringRect(r_min: title_bar_rect.Min, r_max: title_bar_rect.Max) && g.IO.MouseClickedCount[0] == 2)
7027 window->WantCollapseToggle = true;
7028 if (window->WantCollapseToggle)
7029 {
7030 window->Collapsed = !window->Collapsed;
7031 if (!window->Collapsed)
7032 use_current_size_for_scrollbar_y = true;
7033 MarkIniSettingsDirty(window);
7034 }
7035 }
7036 else
7037 {
7038 window->Collapsed = false;
7039 }
7040 window->WantCollapseToggle = false;
7041
7042 // SIZE
7043
7044 // Calculate auto-fit size, handle automatic resize
7045 const ImVec2 size_auto_fit = CalcWindowAutoFitSize(window, size_contents: window->ContentSizeIdeal);
7046 if ((flags & ImGuiWindowFlags_AlwaysAutoResize) && !window->Collapsed)
7047 {
7048 // Using SetNextWindowSize() overrides ImGuiWindowFlags_AlwaysAutoResize, so it can be used on tooltips/popups, etc.
7049 if (!window_size_x_set_by_api)
7050 {
7051 window->SizeFull.x = size_auto_fit.x;
7052 use_current_size_for_scrollbar_x = true;
7053 }
7054 if (!window_size_y_set_by_api)
7055 {
7056 window->SizeFull.y = size_auto_fit.y;
7057 use_current_size_for_scrollbar_y = true;
7058 }
7059 }
7060 else if (window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0)
7061 {
7062 // Auto-fit may only grow window during the first few frames
7063 // We still process initial auto-fit on collapsed windows to get a window width, but otherwise don't honor ImGuiWindowFlags_AlwaysAutoResize when collapsed.
7064 if (!window_size_x_set_by_api && window->AutoFitFramesX > 0)
7065 {
7066 window->SizeFull.x = window->AutoFitOnlyGrows ? ImMax(lhs: window->SizeFull.x, rhs: size_auto_fit.x) : size_auto_fit.x;
7067 use_current_size_for_scrollbar_x = true;
7068 }
7069 if (!window_size_y_set_by_api && window->AutoFitFramesY > 0)
7070 {
7071 window->SizeFull.y = window->AutoFitOnlyGrows ? ImMax(lhs: window->SizeFull.y, rhs: size_auto_fit.y) : size_auto_fit.y;
7072 use_current_size_for_scrollbar_y = true;
7073 }
7074 if (!window->Collapsed)
7075 MarkIniSettingsDirty(window);
7076 }
7077
7078 // Apply minimum/maximum window size constraints and final size
7079 window->SizeFull = CalcWindowSizeAfterConstraint(window, size_desired: window->SizeFull);
7080 window->Size = window->Collapsed && !(flags & ImGuiWindowFlags_ChildWindow) ? window->TitleBarRect().GetSize() : window->SizeFull;
7081
7082 // Decoration size
7083 const float decoration_up_height = window->TitleBarHeight() + window->MenuBarHeight();
7084
7085 // POSITION
7086
7087 // Popup latch its initial position, will position itself when it appears next frame
7088 if (window_just_activated_by_user)
7089 {
7090 window->AutoPosLastDirection = ImGuiDir_None;
7091 if ((flags & ImGuiWindowFlags_Popup) != 0 && !(flags & ImGuiWindowFlags_Modal) && !window_pos_set_by_api) // FIXME: BeginPopup() could use SetNextWindowPos()
7092 window->Pos = g.BeginPopupStack.back().OpenPopupPos;
7093 }
7094
7095 // Position child window
7096 if (flags & ImGuiWindowFlags_ChildWindow)
7097 {
7098 IM_ASSERT(parent_window && parent_window->Active);
7099 window->BeginOrderWithinParent = (short)parent_window->DC.ChildWindows.Size;
7100 parent_window->DC.ChildWindows.push_back(v: window);
7101 if (!(flags & ImGuiWindowFlags_Popup) && !window_pos_set_by_api && !window_is_child_tooltip)
7102 window->Pos = parent_window->DC.CursorPos;
7103 }
7104
7105 const bool window_pos_with_pivot = (window->SetWindowPosVal.x != FLT_MAX && window->HiddenFramesCannotSkipItems == 0);
7106 if (window_pos_with_pivot)
7107 SetWindowPos(window, pos: window->SetWindowPosVal - window->Size * window->SetWindowPosPivot, cond: 0); // Position given a pivot (e.g. for centering)
7108 else if ((flags & ImGuiWindowFlags_ChildMenu) != 0)
7109 window->Pos = FindBestWindowPosForPopup(window);
7110 else if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api && window_just_appearing_after_hidden_for_resize)
7111 window->Pos = FindBestWindowPosForPopup(window);
7112 else if ((flags & ImGuiWindowFlags_Tooltip) != 0 && !window_pos_set_by_api && !window_is_child_tooltip)
7113 window->Pos = FindBestWindowPosForPopup(window);
7114
7115 // Late create viewport if we don't fit within our current host viewport.
7116 if (window->ViewportAllowPlatformMonitorExtend >= 0 && !window->ViewportOwned && !(window->Viewport->Flags & ImGuiViewportFlags_Minimized))
7117 if (!window->Viewport->GetMainRect().Contains(r: window->Rect()))
7118 {
7119 // This is based on the assumption that the DPI will be known ahead (same as the DPI of the selection done in UpdateSelectWindowViewport)
7120 //ImGuiViewport* old_viewport = window->Viewport;
7121 window->Viewport = AddUpdateViewport(window, id: window->ID, platform_pos: window->Pos, size: window->Size, flags: ImGuiViewportFlags_NoFocusOnAppearing);
7122
7123 // FIXME-DPI
7124 //IM_ASSERT(old_viewport->DpiScale == window->Viewport->DpiScale); // FIXME-DPI: Something went wrong
7125 SetCurrentViewport(window, viewport: window->Viewport);
7126 window->FontDpiScale = (g.IO.ConfigFlags & ImGuiConfigFlags_DpiEnableScaleFonts) ? window->Viewport->DpiScale : 1.0f;
7127 SetCurrentWindow(window);
7128 }
7129
7130 if (window->ViewportOwned)
7131 WindowSyncOwnedViewport(window, parent_window_in_stack);
7132
7133 // Calculate the range of allowed position for that window (to be movable and visible past safe area padding)
7134 // When clamping to stay visible, we will enforce that window->Pos stays inside of visibility_rect.
7135 ImRect viewport_rect(window->Viewport->GetMainRect());
7136 ImRect viewport_work_rect(window->Viewport->GetWorkRect());
7137 ImVec2 visibility_padding = ImMax(lhs: style.DisplayWindowPadding, rhs: style.DisplaySafeAreaPadding);
7138 ImRect visibility_rect(viewport_work_rect.Min + visibility_padding, viewport_work_rect.Max - visibility_padding);
7139
7140 // Clamp position/size so window stays visible within its viewport or monitor
7141 // Ignore zero-sized display explicitly to avoid losing positions if a window manager reports zero-sized window when initializing or minimizing.
7142 // FIXME: Similar to code in GetWindowAllowedExtentRect()
7143 if (!window_pos_set_by_api && !(flags & ImGuiWindowFlags_ChildWindow))
7144 {
7145 if (!window->ViewportOwned && viewport_rect.GetWidth() > 0 && viewport_rect.GetHeight() > 0.0f)
7146 {
7147 ClampWindowPos(window, visibility_rect);
7148 }
7149 else if (window->ViewportOwned && g.PlatformIO.Monitors.Size > 0)
7150 {
7151 // Lost windows (e.g. a monitor disconnected) will naturally moved to the fallback/dummy monitor aka the main viewport.
7152 const ImGuiPlatformMonitor* monitor = GetViewportPlatformMonitor(viewport: window->Viewport);
7153 visibility_rect.Min = monitor->WorkPos + visibility_padding;
7154 visibility_rect.Max = monitor->WorkPos + monitor->WorkSize - visibility_padding;
7155 ClampWindowPos(window, visibility_rect);
7156 }
7157 }
7158 window->Pos = ImFloor(v: window->Pos);
7159
7160 // Lock window rounding for the frame (so that altering them doesn't cause inconsistencies)
7161 // Large values tend to lead to variety of artifacts and are not recommended.
7162 if (window->ViewportOwned || window->DockIsActive)
7163 window->WindowRounding = 0.0f;
7164 else
7165 window->WindowRounding = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildRounding : ((flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupRounding : style.WindowRounding;
7166
7167 // For windows with title bar or menu bar, we clamp to FrameHeight(FontSize + FramePadding.y * 2.0f) to completely hide artifacts.
7168 //if ((window->Flags & ImGuiWindowFlags_MenuBar) || !(window->Flags & ImGuiWindowFlags_NoTitleBar))
7169 // window->WindowRounding = ImMin(window->WindowRounding, g.FontSize + style.FramePadding.y * 2.0f);
7170
7171 // Apply window focus (new and reactivated windows are moved to front)
7172 bool want_focus = false;
7173 if (window_just_activated_by_user && !(flags & ImGuiWindowFlags_NoFocusOnAppearing))
7174 {
7175 if (flags & ImGuiWindowFlags_Popup)
7176 want_focus = true;
7177 else if ((window->DockIsActive || (flags & ImGuiWindowFlags_ChildWindow) == 0) && !(flags & ImGuiWindowFlags_Tooltip))
7178 want_focus = true;
7179
7180 ImGuiWindow* modal = GetTopMostPopupModal();
7181 if (modal != NULL && !IsWindowWithinBeginStackOf(window, potential_parent: modal))
7182 {
7183 // Avoid focusing a window that is created outside of active modal. This will prevent active modal from being closed.
7184 // Since window is not focused it would reappear at the same display position like the last time it was visible.
7185 // In case of completely new windows it would go to the top (over current modal), but input to such window would still be blocked by modal.
7186 // Position window behind a modal that is not a begin-parent of this window.
7187 want_focus = false;
7188 if (window == window->RootWindow)
7189 {
7190 ImGuiWindow* blocking_modal = FindBlockingModal(window);
7191 IM_ASSERT(blocking_modal != NULL);
7192 BringWindowToDisplayBehind(window, above_window: blocking_modal);
7193 }
7194 }
7195 }
7196
7197 // [Test Engine] Register whole window in the item system
7198#ifdef IMGUI_ENABLE_TEST_ENGINE
7199 if (g.TestEngineHookItems)
7200 {
7201 IM_ASSERT(window->IDStack.Size == 1);
7202 window->IDStack.Size = 0; // As window->IDStack[0] == window->ID here, make sure TestEngine doesn't erroneously see window as parent of itself.
7203 IMGUI_TEST_ENGINE_ITEM_ADD(window->Rect(), window->ID);
7204 IMGUI_TEST_ENGINE_ITEM_INFO(window->ID, window->Name, (g.HoveredWindow == window) ? ImGuiItemStatusFlags_HoveredRect : 0);
7205 window->IDStack.Size = 1;
7206 }
7207#endif
7208
7209 // Decide if we are going to handle borders and resize grips
7210 const bool handle_borders_and_resize_grips = (window->DockNodeAsHost || !window->DockIsActive);
7211
7212 // Handle manual resize: Resize Grips, Borders, Gamepad
7213 int border_held = -1;
7214 ImU32 resize_grip_col[4] = {};
7215 const int resize_grip_count = g.IO.ConfigWindowsResizeFromEdges ? 2 : 1; // Allow resize from lower-left if we have the mouse cursor feedback for it.
7216 const float resize_grip_draw_size = IM_FLOOR(ImMax(g.FontSize * 1.10f, window->WindowRounding + 1.0f + g.FontSize * 0.2f));
7217 if (handle_borders_and_resize_grips && !window->Collapsed)
7218 if (UpdateWindowManualResize(window, size_auto_fit, border_held: &border_held, resize_grip_count, resize_grip_col: &resize_grip_col[0], visibility_rect))
7219 use_current_size_for_scrollbar_x = use_current_size_for_scrollbar_y = true;
7220 window->ResizeBorderHeld = (signed char)border_held;
7221
7222 // Synchronize window --> viewport again and one last time (clamping and manual resize may have affected either)
7223 if (window->ViewportOwned)
7224 {
7225 if (!window->Viewport->PlatformRequestMove)
7226 window->Viewport->Pos = window->Pos;
7227 if (!window->Viewport->PlatformRequestResize)
7228 window->Viewport->Size = window->Size;
7229 window->Viewport->UpdateWorkRect();
7230 viewport_rect = window->Viewport->GetMainRect();
7231 }
7232
7233 // Save last known viewport position within the window itself (so it can be saved in .ini file and restored)
7234 window->ViewportPos = window->Viewport->Pos;
7235
7236 // SCROLLBAR VISIBILITY
7237
7238 // Update scrollbar visibility (based on the Size that was effective during last frame or the auto-resized Size).
7239 if (!window->Collapsed)
7240 {
7241 // When reading the current size we need to read it after size constraints have been applied.
7242 // When we use InnerRect here we are intentionally reading last frame size, same for ScrollbarSizes values before we set them again.
7243 ImVec2 avail_size_from_current_frame = ImVec2(window->SizeFull.x, window->SizeFull.y - decoration_up_height);
7244 ImVec2 avail_size_from_last_frame = window->InnerRect.GetSize() + window->ScrollbarSizes;
7245 ImVec2 needed_size_from_last_frame = window_just_created ? ImVec2(0, 0) : window->ContentSize + window->WindowPadding * 2.0f;
7246 float size_x_for_scrollbars = use_current_size_for_scrollbar_x ? avail_size_from_current_frame.x : avail_size_from_last_frame.x;
7247 float size_y_for_scrollbars = use_current_size_for_scrollbar_y ? avail_size_from_current_frame.y : avail_size_from_last_frame.y;
7248 //bool scrollbar_y_from_last_frame = window->ScrollbarY; // FIXME: May want to use that in the ScrollbarX expression? How many pros vs cons?
7249 window->ScrollbarY = (flags & ImGuiWindowFlags_AlwaysVerticalScrollbar) || ((needed_size_from_last_frame.y > size_y_for_scrollbars) && !(flags & ImGuiWindowFlags_NoScrollbar));
7250 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));
7251 if (window->ScrollbarX && !window->ScrollbarY)
7252 window->ScrollbarY = (needed_size_from_last_frame.y > size_y_for_scrollbars) && !(flags & ImGuiWindowFlags_NoScrollbar);
7253 window->ScrollbarSizes = ImVec2(window->ScrollbarY ? style.ScrollbarSize : 0.0f, window->ScrollbarX ? style.ScrollbarSize : 0.0f);
7254 }
7255
7256 // UPDATE RECTANGLES (1- THOSE NOT AFFECTED BY SCROLLING)
7257 // Update various regions. Variables they depend on should be set above in this function.
7258 // We set this up after processing the resize grip so that our rectangles doesn't lag by a frame.
7259
7260 // Outer rectangle
7261 // Not affected by window border size. Used by:
7262 // - FindHoveredWindow() (w/ extra padding when border resize is enabled)
7263 // - Begin() initial clipping rect for drawing window background and borders.
7264 // - Begin() clipping whole child
7265 const ImRect host_rect = ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) && !window_is_child_tooltip) ? parent_window->ClipRect : viewport_rect;
7266 const ImRect outer_rect = window->Rect();
7267 const ImRect title_bar_rect = window->TitleBarRect();
7268 window->OuterRectClipped = outer_rect;
7269 if (window->DockIsActive)
7270 window->OuterRectClipped.Min.y += window->TitleBarHeight();
7271 window->OuterRectClipped.ClipWith(r: host_rect);
7272
7273 // Inner rectangle
7274 // Not affected by window border size. Used by:
7275 // - InnerClipRect
7276 // - ScrollToRectEx()
7277 // - NavUpdatePageUpPageDown()
7278 // - Scrollbar()
7279 window->InnerRect.Min.x = window->Pos.x;
7280 window->InnerRect.Min.y = window->Pos.y + decoration_up_height;
7281 window->InnerRect.Max.x = window->Pos.x + window->Size.x - window->ScrollbarSizes.x;
7282 window->InnerRect.Max.y = window->Pos.y + window->Size.y - window->ScrollbarSizes.y;
7283
7284 // Inner clipping rectangle.
7285 // Will extend a little bit outside the normal work region.
7286 // This is to allow e.g. Selectable or CollapsingHeader or some separators to cover that space.
7287 // Force round operator last to ensure that e.g. (int)(max.x-min.x) in user's render code produce correct result.
7288 // Note that if our window is collapsed we will end up with an inverted (~null) clipping rectangle which is the correct behavior.
7289 // Affected by window/frame border size. Used by:
7290 // - Begin() initial clip rect
7291 float top_border_size = (((flags & ImGuiWindowFlags_MenuBar) || !(flags & ImGuiWindowFlags_NoTitleBar)) ? style.FrameBorderSize : window->WindowBorderSize);
7292 window->InnerClipRect.Min.x = ImFloor(f: 0.5f + window->InnerRect.Min.x + ImMax(lhs: ImFloor(f: window->WindowPadding.x * 0.5f), rhs: window->WindowBorderSize));
7293 window->InnerClipRect.Min.y = ImFloor(f: 0.5f + window->InnerRect.Min.y + top_border_size);
7294 window->InnerClipRect.Max.x = ImFloor(f: 0.5f + window->InnerRect.Max.x - ImMax(lhs: ImFloor(f: window->WindowPadding.x * 0.5f), rhs: window->WindowBorderSize));
7295 window->InnerClipRect.Max.y = ImFloor(f: 0.5f + window->InnerRect.Max.y - window->WindowBorderSize);
7296 window->InnerClipRect.ClipWithFull(r: host_rect);
7297
7298 // Default item width. Make it proportional to window size if window manually resizes
7299 if (window->Size.x > 0.0f && !(flags & ImGuiWindowFlags_Tooltip) && !(flags & ImGuiWindowFlags_AlwaysAutoResize))
7300 window->ItemWidthDefault = ImFloor(f: window->Size.x * 0.65f);
7301 else
7302 window->ItemWidthDefault = ImFloor(f: g.FontSize * 16.0f);
7303
7304 // SCROLLING
7305
7306 // Lock down maximum scrolling
7307 // The value of ScrollMax are ahead from ScrollbarX/ScrollbarY which is intentionally using InnerRect from previous rect in order to accommodate
7308 // for right/bottom aligned items without creating a scrollbar.
7309 window->ScrollMax.x = ImMax(lhs: 0.0f, rhs: window->ContentSize.x + window->WindowPadding.x * 2.0f - window->InnerRect.GetWidth());
7310 window->ScrollMax.y = ImMax(lhs: 0.0f, rhs: window->ContentSize.y + window->WindowPadding.y * 2.0f - window->InnerRect.GetHeight());
7311
7312 // Apply scrolling
7313 window->Scroll = CalcNextScrollFromScrollTargetAndClamp(window);
7314 window->ScrollTarget = ImVec2(FLT_MAX, FLT_MAX);
7315
7316 // DRAWING
7317
7318 // Setup draw list and outer clipping rectangle
7319 IM_ASSERT(window->DrawList->CmdBuffer.Size == 1 && window->DrawList->CmdBuffer[0].ElemCount == 0);
7320 window->DrawList->PushTextureID(texture_id: g.Font->ContainerAtlas->TexID);
7321 PushClipRect(clip_rect_min: host_rect.Min, clip_rect_max: host_rect.Max, intersect_with_current_clip_rect: false);
7322
7323 // Child windows can render their decoration (bg color, border, scrollbars, etc.) within their parent to save a draw call (since 1.71)
7324 // When using overlapping child windows, this will break the assumption that child z-order is mapped to submission order.
7325 // FIXME: User code may rely on explicit sorting of overlapping child window and would need to disable this somehow. Please get in contact if you are affected (github #4493)
7326 const bool is_undocked_or_docked_visible = !window->DockIsActive || window->DockTabIsVisible;
7327 if (is_undocked_or_docked_visible)
7328 {
7329 bool render_decorations_in_parent = false;
7330 if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) && !window_is_child_tooltip)
7331 {
7332 // - We test overlap with the previous child window only (testing all would end up being O(log N) not a good investment here)
7333 // - We disable this when the parent window has zero vertices, which is a common pattern leading to laying out multiple overlapping childs
7334 ImGuiWindow* previous_child = parent_window->DC.ChildWindows.Size >= 2 ? parent_window->DC.ChildWindows[parent_window->DC.ChildWindows.Size - 2] : NULL;
7335 bool previous_child_overlapping = previous_child ? previous_child->Rect().Overlaps(r: window->Rect()) : false;
7336 bool parent_is_empty = parent_window->DrawList->VtxBuffer.Size > 0;
7337 if (window->DrawList->CmdBuffer.back().ElemCount == 0 && parent_is_empty && !previous_child_overlapping)
7338 render_decorations_in_parent = true;
7339 }
7340 if (render_decorations_in_parent)
7341 window->DrawList = parent_window->DrawList;
7342
7343 // Handle title bar, scrollbar, resize grips and resize borders
7344 const ImGuiWindow* window_to_highlight = g.NavWindowingTarget ? g.NavWindowingTarget : g.NavWindow;
7345 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)));
7346 RenderWindowDecorations(window, title_bar_rect, title_bar_is_highlight, handle_borders_and_resize_grips, resize_grip_count, resize_grip_col, resize_grip_draw_size);
7347
7348 if (render_decorations_in_parent)
7349 window->DrawList = &window->DrawListInst;
7350 }
7351
7352 // UPDATE RECTANGLES (2- THOSE AFFECTED BY SCROLLING)
7353
7354 // Work rectangle.
7355 // Affected by window padding and border size. Used by:
7356 // - Columns() for right-most edge
7357 // - TreeNode(), CollapsingHeader() for right-most edge
7358 // - BeginTabBar() for right-most edge
7359 const bool allow_scrollbar_x = !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar);
7360 const bool allow_scrollbar_y = !(flags & ImGuiWindowFlags_NoScrollbar);
7361 const float work_rect_size_x = (window->ContentSizeExplicit.x != 0.0f ? window->ContentSizeExplicit.x : ImMax(lhs: allow_scrollbar_x ? window->ContentSize.x : 0.0f, rhs: window->Size.x - window->WindowPadding.x * 2.0f - window->ScrollbarSizes.x));
7362 const float work_rect_size_y = (window->ContentSizeExplicit.y != 0.0f ? window->ContentSizeExplicit.y : ImMax(lhs: allow_scrollbar_y ? window->ContentSize.y : 0.0f, rhs: window->Size.y - window->WindowPadding.y * 2.0f - decoration_up_height - window->ScrollbarSizes.y));
7363 window->WorkRect.Min.x = ImFloor(f: window->InnerRect.Min.x - window->Scroll.x + ImMax(lhs: window->WindowPadding.x, rhs: window->WindowBorderSize));
7364 window->WorkRect.Min.y = ImFloor(f: window->InnerRect.Min.y - window->Scroll.y + ImMax(lhs: window->WindowPadding.y, rhs: window->WindowBorderSize));
7365 window->WorkRect.Max.x = window->WorkRect.Min.x + work_rect_size_x;
7366 window->WorkRect.Max.y = window->WorkRect.Min.y + work_rect_size_y;
7367 window->ParentWorkRect = window->WorkRect;
7368
7369 // [LEGACY] Content Region
7370 // FIXME-OBSOLETE: window->ContentRegionRect.Max is currently very misleading / partly faulty, but some BeginChild() patterns relies on it.
7371 // Used by:
7372 // - Mouse wheel scrolling + many other things
7373 window->ContentRegionRect.Min.x = window->Pos.x - window->Scroll.x + window->WindowPadding.x;
7374 window->ContentRegionRect.Min.y = window->Pos.y - window->Scroll.y + window->WindowPadding.y + decoration_up_height;
7375 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));
7376 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));
7377
7378 // Setup drawing context
7379 // (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.)
7380 window->DC.Indent.x = 0.0f + window->WindowPadding.x - window->Scroll.x;
7381 window->DC.GroupOffset.x = 0.0f;
7382 window->DC.ColumnsOffset.x = 0.0f;
7383
7384 // Record the loss of precision of CursorStartPos which can happen due to really large scrolling amount.
7385 // This is used by clipper to compensate and fix the most common use case of large scroll area. Easy and cheap, next best thing compared to switching everything to double or ImU64.
7386 double start_pos_highp_x = (double)window->Pos.x + window->WindowPadding.x - (double)window->Scroll.x + window->DC.ColumnsOffset.x;
7387 double start_pos_highp_y = (double)window->Pos.y + window->WindowPadding.y - (double)window->Scroll.y + decoration_up_height;
7388 window->DC.CursorStartPos = ImVec2((float)start_pos_highp_x, (float)start_pos_highp_y);
7389 window->DC.CursorStartPosLossyness = ImVec2((float)(start_pos_highp_x - window->DC.CursorStartPos.x), (float)(start_pos_highp_y - window->DC.CursorStartPos.y));
7390 window->DC.CursorPos = window->DC.CursorStartPos;
7391 window->DC.CursorPosPrevLine = window->DC.CursorPos;
7392 window->DC.CursorMaxPos = window->DC.CursorStartPos;
7393 window->DC.IdealMaxPos = window->DC.CursorStartPos;
7394 window->DC.CurrLineSize = window->DC.PrevLineSize = ImVec2(0.0f, 0.0f);
7395 window->DC.CurrLineTextBaseOffset = window->DC.PrevLineTextBaseOffset = 0.0f;
7396 window->DC.IsSameLine = window->DC.IsSetPos = false;
7397
7398 window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
7399 window->DC.NavLayersActiveMask = window->DC.NavLayersActiveMaskNext;
7400 window->DC.NavLayersActiveMaskNext = 0x00;
7401 window->DC.NavHideHighlightOneFrame = false;
7402 window->DC.NavHasScroll = (window->ScrollMax.y > 0.0f);
7403
7404 window->DC.MenuBarAppending = false;
7405 window->DC.MenuColumns.Update(spacing: style.ItemSpacing.x, window_reappearing: window_just_activated_by_user);
7406 window->DC.TreeDepth = 0;
7407 window->DC.TreeJumpToParentOnPopMask = 0x00;
7408 window->DC.ChildWindows.resize(new_size: 0);
7409 window->DC.StateStorage = &window->StateStorage;
7410 window->DC.CurrentColumns = NULL;
7411 window->DC.LayoutType = ImGuiLayoutType_Vertical;
7412 window->DC.ParentLayoutType = parent_window ? parent_window->DC.LayoutType : ImGuiLayoutType_Vertical;
7413
7414 window->DC.ItemWidth = window->ItemWidthDefault;
7415 window->DC.TextWrapPos = -1.0f; // disabled
7416 window->DC.ItemWidthStack.resize(new_size: 0);
7417 window->DC.TextWrapPosStack.resize(new_size: 0);
7418
7419 if (window->AutoFitFramesX > 0)
7420 window->AutoFitFramesX--;
7421 if (window->AutoFitFramesY > 0)
7422 window->AutoFitFramesY--;
7423
7424 // Apply focus (we need to call FocusWindow() AFTER setting DC.CursorStartPos so our initial navigation reference rectangle can start around there)
7425 if (want_focus)
7426 {
7427 FocusWindow(window);
7428 NavInitWindow(window, force_reinit: false); // <-- this is in the way for us to be able to defer and sort reappearing FocusWindow() calls
7429 }
7430
7431 // Close requested by platform window
7432 if (p_open != NULL && window->Viewport->PlatformRequestClose && window->Viewport != GetMainViewport())
7433 {
7434 if (!window->DockIsActive || window->DockTabIsVisible)
7435 {
7436 window->Viewport->PlatformRequestClose = false;
7437 g.NavWindowingToggleLayer = false; // Assume user mapped PlatformRequestClose on ALT-F4 so we disable ALT for menu toggle. False positive not an issue.
7438 IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Window '%s' PlatformRequestClose\n", window->Name);
7439 *p_open = false;
7440 }
7441 }
7442
7443 // Title bar
7444 if (!(flags & ImGuiWindowFlags_NoTitleBar) && !window->DockIsActive)
7445 RenderWindowTitleBarContents(window, title_bar_rect: 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);
7446
7447 // Clear hit test shape every frame
7448 window->HitTestHoleSize.x = window->HitTestHoleSize.y = 0;
7449
7450 // Pressing CTRL+C while holding on a window copy its content to the clipboard
7451 // 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.
7452 // Maybe we can support CTRL+C on every element?
7453 /*
7454 //if (g.NavWindow == window && g.ActiveId == 0)
7455 if (g.ActiveId == window->MoveId)
7456 if (g.IO.KeyCtrl && IsKeyPressed(ImGuiKey_C))
7457 LogToClipboard();
7458 */
7459
7460 if (g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable)
7461 {
7462 // Docking: Dragging a dockable window (or any of its child) turns it into a drag and drop source.
7463 // We need to do this _before_ we overwrite window->DC.LastItemId below because BeginDockableDragDropSource() also overwrites it.
7464 if ((g.MovingWindow == window) && (g.IO.ConfigDockingWithShift == g.IO.KeyShift))
7465 if ((window->RootWindowDockTree->Flags & ImGuiWindowFlags_NoDocking) == 0)
7466 BeginDockableDragDropSource(window);
7467
7468 // Docking: Any dockable window can act as a target. For dock node hosts we call BeginDockableDragDropTarget() in DockNodeUpdate() instead.
7469 if (g.DragDropActive && !(flags & ImGuiWindowFlags_NoDocking))
7470 if (g.MovingWindow == NULL || g.MovingWindow->RootWindowDockTree != window)
7471 if ((window == window->RootWindowDockTree) && !(window->Flags & ImGuiWindowFlags_DockNodeHost))
7472 BeginDockableDragDropTarget(window);
7473 }
7474
7475 // We fill last item data based on Title Bar/Tab, in order for IsItemHovered() and IsItemActive() to be usable after Begin().
7476 // This is useful to allow creating context menus on title bar only, etc.
7477 if (window->DockIsActive)
7478 SetLastItemData(item_id: window->MoveId, in_flags: g.CurrentItemFlags, item_flags: window->DockTabItemStatusFlags, item_rect: window->DockTabItemRect);
7479 else
7480 SetLastItemData(item_id: window->MoveId, in_flags: g.CurrentItemFlags, item_flags: IsMouseHoveringRect(r_min: title_bar_rect.Min, r_max: title_bar_rect.Max, clip: false) ? ImGuiItemStatusFlags_HoveredRect : 0, item_rect: title_bar_rect);
7481
7482 // [DEBUG]
7483#ifndef IMGUI_DISABLE_DEBUG_TOOLS
7484 if (g.DebugLocateId != 0 && (window->ID == g.DebugLocateId || window->MoveId == g.DebugLocateId))
7485 DebugLocateItemResolveWithLastItem();
7486#endif
7487
7488 // [Test Engine] Register title bar / tab
7489#ifdef IMGUI_ENABLE_TEST_ENGINE
7490 if (!(window->Flags & ImGuiWindowFlags_NoTitleBar))
7491 IMGUI_TEST_ENGINE_ITEM_ADD(g.LastItemData.Rect, g.LastItemData.ID);
7492#endif
7493 }
7494 else
7495 {
7496 // Append
7497 SetCurrentViewport(window, viewport: window->Viewport);
7498 SetCurrentWindow(window);
7499 }
7500
7501 if (!(flags & ImGuiWindowFlags_DockNodeHost))
7502 PushClipRect(clip_rect_min: window->InnerClipRect.Min, clip_rect_max: window->InnerClipRect.Max, intersect_with_current_clip_rect: true);
7503
7504 // 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)
7505 window->WriteAccessed = false;
7506 window->BeginCount++;
7507 g.NextWindowData.ClearFlags();
7508
7509 // Update visibility
7510 if (first_begin_of_the_frame)
7511 {
7512 // When we are about to select this tab (which will only be visible on the _next frame_), flag it with a non-zero HiddenFramesCannotSkipItems.
7513 // This will have the important effect of actually returning true in Begin() and not setting SkipItems, allowing an earlier submission of the window contents.
7514 // This is analogous to regular windows being hidden from one frame.
7515 // 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.
7516 if (window->DockIsActive && !window->DockTabIsVisible)
7517 {
7518 if (window->LastFrameJustFocused == g.FrameCount)
7519 window->HiddenFramesCannotSkipItems = 1;
7520 else
7521 window->HiddenFramesCanSkipItems = 1;
7522 }
7523
7524 if (flags & ImGuiWindowFlags_ChildWindow)
7525 {
7526 // Child window can be out of sight and have "negative" clip windows.
7527 // Mark them as collapsed so commands are skipped earlier (we can't manually collapse them because they have no title bar).
7528 IM_ASSERT((flags& ImGuiWindowFlags_NoTitleBar) != 0 || (window->DockIsActive));
7529 if (!(flags & ImGuiWindowFlags_AlwaysAutoResize) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0) // FIXME: Doesn't make sense for ChildWindow??
7530 {
7531 const bool nav_request = (flags & ImGuiWindowFlags_NavFlattened) && (g.NavAnyRequest && g.NavWindow && g.NavWindow->RootWindowForNav == window->RootWindowForNav);
7532 if (!g.LogEnabled && !nav_request)
7533 if (window->OuterRectClipped.Min.x >= window->OuterRectClipped.Max.x || window->OuterRectClipped.Min.y >= window->OuterRectClipped.Max.y)
7534 window->HiddenFramesCanSkipItems = 1;
7535 }
7536
7537 // Hide along with parent or if parent is collapsed
7538 if (parent_window && (parent_window->Collapsed || parent_window->HiddenFramesCanSkipItems > 0))
7539 window->HiddenFramesCanSkipItems = 1;
7540 if (parent_window && (parent_window->Collapsed || parent_window->HiddenFramesCannotSkipItems > 0))
7541 window->HiddenFramesCannotSkipItems = 1;
7542 }
7543
7544 // 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)
7545 if (style.Alpha <= 0.0f)
7546 window->HiddenFramesCanSkipItems = 1;
7547
7548 // Update the Hidden flag
7549 bool hidden_regular = (window->HiddenFramesCanSkipItems > 0) || (window->HiddenFramesCannotSkipItems > 0);
7550 window->Hidden = hidden_regular || (window->HiddenFramesForRenderOnly > 0);
7551
7552 // Disable inputs for requested number of frames
7553 if (window->DisableInputsFrames > 0)
7554 {
7555 window->DisableInputsFrames--;
7556 window->Flags |= ImGuiWindowFlags_NoInputs;
7557 }
7558
7559 // Update the SkipItems flag, used to early out of all items functions (no layout required)
7560 bool skip_items = false;
7561 if (window->Collapsed || !window->Active || hidden_regular)
7562 if (window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0 && window->HiddenFramesCannotSkipItems <= 0)
7563 skip_items = true;
7564 window->SkipItems = skip_items;
7565
7566 // Restore NavLayersActiveMaskNext to previous value when not visible, so a CTRL+Tab back can use a safe value.
7567 if (window->SkipItems)
7568 window->DC.NavLayersActiveMaskNext = window->DC.NavLayersActiveMask;
7569
7570 // Sanity check: there are two spots which can set Appearing = true
7571 // - when 'window_just_activated_by_user' is set -> HiddenFramesCannotSkipItems is set -> SkipItems always false
7572 // - in BeginDocked() path when DockNodeIsVisible == DockTabIsVisible == true -> hidden _should_ be all zero // FIXME: Not formally proven, hence the assert.
7573 if (window->SkipItems && !window->Appearing)
7574 IM_ASSERT(window->Appearing == false); // Please report on GitHub if this triggers: https://github.com/ocornut/imgui/issues/4177
7575 }
7576
7577 return !window->SkipItems;
7578}
7579
7580void ImGui::End()
7581{
7582 ImGuiContext& g = *GImGui;
7583 ImGuiWindow* window = g.CurrentWindow;
7584
7585 // Error checking: verify that user hasn't called End() too many times!
7586 if (g.CurrentWindowStack.Size <= 1 && g.WithinFrameScopeWithImplicitWindow)
7587 {
7588 IM_ASSERT_USER_ERROR(g.CurrentWindowStack.Size > 1, "Calling End() too many times!");
7589 return;
7590 }
7591 IM_ASSERT(g.CurrentWindowStack.Size > 0);
7592
7593 // Error checking: verify that user doesn't directly call End() on a child window.
7594 if ((window->Flags & ImGuiWindowFlags_ChildWindow) && !(window->Flags & ImGuiWindowFlags_DockNodeHost) && !window->DockIsActive)
7595 IM_ASSERT_USER_ERROR(g.WithinEndChild, "Must call EndChild() and not End()!");
7596
7597 // Close anything that is open
7598 if (window->DC.CurrentColumns)
7599 EndColumns();
7600 if (!(window->Flags & ImGuiWindowFlags_DockNodeHost)) // Pop inner window clip rectangle
7601 PopClipRect();
7602 PopFocusScope();
7603
7604 // Stop logging
7605 if (!(window->Flags & ImGuiWindowFlags_ChildWindow)) // FIXME: add more options for scope of logging
7606 LogFinish();
7607
7608 if (window->DC.IsSetPos)
7609 ErrorCheckUsingSetCursorPosToExtendParentBoundaries();
7610
7611 // Docking: report contents sizes to parent to allow for auto-resize
7612 if (window->DockNode && window->DockTabIsVisible)
7613 if (ImGuiWindow* host_window = window->DockNode->HostWindow) // FIXME-DOCK
7614 host_window->DC.CursorMaxPos = window->DC.CursorMaxPos + window->WindowPadding - host_window->WindowPadding;
7615
7616 // Pop from window stack
7617 g.LastItemData = g.CurrentWindowStack.back().ParentLastItemDataBackup;
7618 if (window->Flags & ImGuiWindowFlags_ChildMenu)
7619 g.BeginMenuCount--;
7620 if (window->Flags & ImGuiWindowFlags_Popup)
7621 g.BeginPopupStack.pop_back();
7622 g.CurrentWindowStack.back().StackSizesOnBegin.CompareWithCurrentState();
7623 g.CurrentWindowStack.pop_back();
7624 SetCurrentWindow(g.CurrentWindowStack.Size == 0 ? NULL : g.CurrentWindowStack.back().Window);
7625 if (g.CurrentWindow)
7626 SetCurrentViewport(window: g.CurrentWindow, viewport: g.CurrentWindow->Viewport);
7627}
7628
7629void ImGui::BringWindowToFocusFront(ImGuiWindow* window)
7630{
7631 ImGuiContext& g = *GImGui;
7632 IM_ASSERT(window == window->RootWindow);
7633
7634 const int cur_order = window->FocusOrder;
7635 IM_ASSERT(g.WindowsFocusOrder[cur_order] == window);
7636 if (g.WindowsFocusOrder.back() == window)
7637 return;
7638
7639 const int new_order = g.WindowsFocusOrder.Size - 1;
7640 for (int n = cur_order; n < new_order; n++)
7641 {
7642 g.WindowsFocusOrder[n] = g.WindowsFocusOrder[n + 1];
7643 g.WindowsFocusOrder[n]->FocusOrder--;
7644 IM_ASSERT(g.WindowsFocusOrder[n]->FocusOrder == n);
7645 }
7646 g.WindowsFocusOrder[new_order] = window;
7647 window->FocusOrder = (short)new_order;
7648}
7649
7650void ImGui::BringWindowToDisplayFront(ImGuiWindow* window)
7651{
7652 ImGuiContext& g = *GImGui;
7653 ImGuiWindow* current_front_window = g.Windows.back();
7654 if (current_front_window == window || current_front_window->RootWindowDockTree == window) // Cheap early out (could be better)
7655 return;
7656 for (int i = g.Windows.Size - 2; i >= 0; i--) // We can ignore the top-most window
7657 if (g.Windows[i] == window)
7658 {
7659 memmove(dest: &g.Windows[i], src: &g.Windows[i + 1], n: (size_t)(g.Windows.Size - i - 1) * sizeof(ImGuiWindow*));
7660 g.Windows[g.Windows.Size - 1] = window;
7661 break;
7662 }
7663}
7664
7665void ImGui::BringWindowToDisplayBack(ImGuiWindow* window)
7666{
7667 ImGuiContext& g = *GImGui;
7668 if (g.Windows[0] == window)
7669 return;
7670 for (int i = 0; i < g.Windows.Size; i++)
7671 if (g.Windows[i] == window)
7672 {
7673 memmove(dest: &g.Windows[1], src: &g.Windows[0], n: (size_t)i * sizeof(ImGuiWindow*));
7674 g.Windows[0] = window;
7675 break;
7676 }
7677}
7678
7679void ImGui::BringWindowToDisplayBehind(ImGuiWindow* window, ImGuiWindow* behind_window)
7680{
7681 IM_ASSERT(window != NULL && behind_window != NULL);
7682 ImGuiContext& g = *GImGui;
7683 window = window->RootWindow;
7684 behind_window = behind_window->RootWindow;
7685 int pos_wnd = FindWindowDisplayIndex(window);
7686 int pos_beh = FindWindowDisplayIndex(window: behind_window);
7687 if (pos_wnd < pos_beh)
7688 {
7689 size_t copy_bytes = (pos_beh - pos_wnd - 1) * sizeof(ImGuiWindow*);
7690 memmove(dest: &g.Windows.Data[pos_wnd], src: &g.Windows.Data[pos_wnd + 1], n: copy_bytes);
7691 g.Windows[pos_beh - 1] = window;
7692 }
7693 else
7694 {
7695 size_t copy_bytes = (pos_wnd - pos_beh) * sizeof(ImGuiWindow*);
7696 memmove(dest: &g.Windows.Data[pos_beh + 1], src: &g.Windows.Data[pos_beh], n: copy_bytes);
7697 g.Windows[pos_beh] = window;
7698 }
7699}
7700
7701int ImGui::FindWindowDisplayIndex(ImGuiWindow* window)
7702{
7703 ImGuiContext& g = *GImGui;
7704 return g.Windows.index_from_ptr(it: g.Windows.find(v: window));
7705}
7706
7707// Moving window to front of display and set focus (which happens to be back of our sorted list)
7708void ImGui::FocusWindow(ImGuiWindow* window)
7709{
7710 ImGuiContext& g = *GImGui;
7711
7712 if (g.NavWindow != window)
7713 {
7714 SetNavWindow(window);
7715 if (window && g.NavDisableMouseHover)
7716 g.NavMousePosDirty = true;
7717 g.NavId = window ? window->NavLastIds[0] : 0; // Restore NavId
7718 g.NavLayer = ImGuiNavLayer_Main;
7719 g.NavFocusScopeId = window ? window->NavRootFocusScopeId : 0;
7720 g.NavIdIsAlive = false;
7721
7722 // Close popups if any
7723 ClosePopupsOverWindow(ref_window: window, restore_focus_to_window_under_popup: false);
7724 }
7725
7726 // Move the root window to the top of the pile
7727 IM_ASSERT(window == NULL || window->RootWindowDockTree != NULL);
7728 ImGuiWindow* focus_front_window = window ? window->RootWindow : NULL;
7729 ImGuiWindow* display_front_window = window ? window->RootWindowDockTree : NULL;
7730 ImGuiDockNode* dock_node = window ? window->DockNode : NULL;
7731 bool active_id_window_is_dock_node_host = (g.ActiveIdWindow && dock_node && dock_node->HostWindow == g.ActiveIdWindow);
7732
7733 // Steal active widgets. Some of the cases it triggers includes:
7734 // - Focus a window while an InputText in another window is active, if focus happens before the old InputText can run.
7735 // - When using Nav to activate menu items (due to timing of activating on press->new window appears->losing ActiveId)
7736 // - Using dock host items (tab, collapse button) can trigger this before we redirect the ActiveIdWindow toward the child window.
7737 if (g.ActiveId != 0 && g.ActiveIdWindow && g.ActiveIdWindow->RootWindow != focus_front_window)
7738 if (!g.ActiveIdNoClearOnFocusLoss && !active_id_window_is_dock_node_host)
7739 ClearActiveID();
7740
7741 // Passing NULL allow to disable keyboard focus
7742 if (!window)
7743 return;
7744 window->LastFrameJustFocused = g.FrameCount;
7745
7746 // Select in dock node
7747 if (dock_node && dock_node->TabBar)
7748 dock_node->TabBar->SelectedTabId = dock_node->TabBar->NextSelectedTabId = window->TabId;
7749
7750 // Bring to front
7751 BringWindowToFocusFront(window: focus_front_window);
7752 if (((window->Flags | focus_front_window->Flags | display_front_window->Flags) & ImGuiWindowFlags_NoBringToFrontOnFocus) == 0)
7753 BringWindowToDisplayFront(window: display_front_window);
7754}
7755
7756void ImGui::FocusTopMostWindowUnderOne(ImGuiWindow* under_this_window, ImGuiWindow* ignore_window)
7757{
7758 ImGuiContext& g = *GImGui;
7759 int start_idx = g.WindowsFocusOrder.Size - 1;
7760 if (under_this_window != NULL)
7761 {
7762 // Aim at root window behind us, if we are in a child window that's our own root (see #4640)
7763 int offset = -1;
7764 while (under_this_window->Flags & ImGuiWindowFlags_ChildWindow)
7765 {
7766 under_this_window = under_this_window->ParentWindow;
7767 offset = 0;
7768 }
7769 start_idx = FindWindowFocusIndex(window: under_this_window) + offset;
7770 }
7771 for (int i = start_idx; i >= 0; i--)
7772 {
7773 // 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.
7774 ImGuiWindow* window = g.WindowsFocusOrder[i];
7775 IM_ASSERT(window == window->RootWindow);
7776 if (window != ignore_window && window->WasActive)
7777 if ((window->Flags & (ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs)) != (ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs))
7778 {
7779 // FIXME-DOCK: This is failing (lagging by one frame) for docked windows.
7780 // If A and B are docked into window and B disappear, at the NewFrame() call site window->NavLastChildNavWindow will still point to B.
7781 // 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)
7782 // to tell which is the "previous" window. Or we may leverage 'LastFrameFocused/LastFrameJustFocused' and have this function handle child window itself?
7783 ImGuiWindow* focus_window = NavRestoreLastChildNavWindow(window);
7784 FocusWindow(window: focus_window);
7785 return;
7786 }
7787 }
7788 FocusWindow(NULL);
7789}
7790
7791// Important: this alone doesn't alter current ImDrawList state. This is called by PushFont/PopFont only.
7792void ImGui::SetCurrentFont(ImFont* font)
7793{
7794 ImGuiContext& g = *GImGui;
7795 IM_ASSERT(font && font->IsLoaded()); // Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ?
7796 IM_ASSERT(font->Scale > 0.0f);
7797 g.Font = font;
7798 g.FontBaseSize = ImMax(lhs: 1.0f, rhs: g.IO.FontGlobalScale * g.Font->FontSize * g.Font->Scale);
7799 g.FontSize = g.CurrentWindow ? g.CurrentWindow->CalcFontSize() : 0.0f;
7800
7801 ImFontAtlas* atlas = g.Font->ContainerAtlas;
7802 g.DrawListSharedData.TexUvWhitePixel = atlas->TexUvWhitePixel;
7803 g.DrawListSharedData.TexUvLines = atlas->TexUvLines;
7804 g.DrawListSharedData.Font = g.Font;
7805 g.DrawListSharedData.FontSize = g.FontSize;
7806}
7807
7808void ImGui::PushFont(ImFont* font)
7809{
7810 ImGuiContext& g = *GImGui;
7811 if (!font)
7812 font = GetDefaultFont();
7813 SetCurrentFont(font);
7814 g.FontStack.push_back(v: font);
7815 g.CurrentWindow->DrawList->PushTextureID(texture_id: font->ContainerAtlas->TexID);
7816}
7817
7818void ImGui::PopFont()
7819{
7820 ImGuiContext& g = *GImGui;
7821 g.CurrentWindow->DrawList->PopTextureID();
7822 g.FontStack.pop_back();
7823 SetCurrentFont(g.FontStack.empty() ? GetDefaultFont() : g.FontStack.back());
7824}
7825
7826void ImGui::PushItemFlag(ImGuiItemFlags option, bool enabled)
7827{
7828 ImGuiContext& g = *GImGui;
7829 ImGuiItemFlags item_flags = g.CurrentItemFlags;
7830 IM_ASSERT(item_flags == g.ItemFlagsStack.back());
7831 if (enabled)
7832 item_flags |= option;
7833 else
7834 item_flags &= ~option;
7835 g.CurrentItemFlags = item_flags;
7836 g.ItemFlagsStack.push_back(v: item_flags);
7837}
7838
7839void ImGui::PopItemFlag()
7840{
7841 ImGuiContext& g = *GImGui;
7842 IM_ASSERT(g.ItemFlagsStack.Size > 1); // Too many calls to PopItemFlag() - we always leave a 0 at the bottom of the stack.
7843 g.ItemFlagsStack.pop_back();
7844 g.CurrentItemFlags = g.ItemFlagsStack.back();
7845}
7846
7847// BeginDisabled()/EndDisabled()
7848// - Those can be nested but it cannot be used to enable an already disabled section (a single BeginDisabled(true) in the stack is enough to keep everything disabled)
7849// - Visually this is currently altering alpha, but it is expected that in a future styling system this would work differently.
7850// - Feedback welcome at https://github.com/ocornut/imgui/issues/211
7851// - 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.
7852// - Optimized shortcuts instead of PushStyleVar() + PushItemFlag()
7853void ImGui::BeginDisabled(bool disabled)
7854{
7855 ImGuiContext& g = *GImGui;
7856 bool was_disabled = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0;
7857 if (!was_disabled && disabled)
7858 {
7859 g.DisabledAlphaBackup = g.Style.Alpha;
7860 g.Style.Alpha *= g.Style.DisabledAlpha; // PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * g.Style.DisabledAlpha);
7861 }
7862 if (was_disabled || disabled)
7863 g.CurrentItemFlags |= ImGuiItemFlags_Disabled;
7864 g.ItemFlagsStack.push_back(v: g.CurrentItemFlags);
7865 g.DisabledStackSize++;
7866}
7867
7868void ImGui::EndDisabled()
7869{
7870 ImGuiContext& g = *GImGui;
7871 IM_ASSERT(g.DisabledStackSize > 0);
7872 g.DisabledStackSize--;
7873 bool was_disabled = (g.CurrentItemFlags & ImGuiItemFlags_Disabled) != 0;
7874 //PopItemFlag();
7875 g.ItemFlagsStack.pop_back();
7876 g.CurrentItemFlags = g.ItemFlagsStack.back();
7877 if (was_disabled && (g.CurrentItemFlags & ImGuiItemFlags_Disabled) == 0)
7878 g.Style.Alpha = g.DisabledAlphaBackup; //PopStyleVar();
7879}
7880
7881// FIXME: Look into renaming this once we have settled the new Focus/Activation/TabStop system.
7882void ImGui::PushAllowKeyboardFocus(bool allow_keyboard_focus)
7883{
7884 PushItemFlag(option: ImGuiItemFlags_NoTabStop, enabled: !allow_keyboard_focus);
7885}
7886
7887void ImGui::PopAllowKeyboardFocus()
7888{
7889 PopItemFlag();
7890}
7891
7892void ImGui::PushButtonRepeat(bool repeat)
7893{
7894 PushItemFlag(option: ImGuiItemFlags_ButtonRepeat, enabled: repeat);
7895}
7896
7897void ImGui::PopButtonRepeat()
7898{
7899 PopItemFlag();
7900}
7901
7902void ImGui::PushTextWrapPos(float wrap_pos_x)
7903{
7904 ImGuiWindow* window = GetCurrentWindow();
7905 window->DC.TextWrapPosStack.push_back(v: window->DC.TextWrapPos);
7906 window->DC.TextWrapPos = wrap_pos_x;
7907}
7908
7909void ImGui::PopTextWrapPos()
7910{
7911 ImGuiWindow* window = GetCurrentWindow();
7912 window->DC.TextWrapPos = window->DC.TextWrapPosStack.back();
7913 window->DC.TextWrapPosStack.pop_back();
7914}
7915
7916static ImGuiWindow* GetCombinedRootWindow(ImGuiWindow* window, bool popup_hierarchy, bool dock_hierarchy)
7917{
7918 ImGuiWindow* last_window = NULL;
7919 while (last_window != window)
7920 {
7921 last_window = window;
7922 window = window->RootWindow;
7923 if (popup_hierarchy)
7924 window = window->RootWindowPopupTree;
7925 if (dock_hierarchy)
7926 window = window->RootWindowDockTree;
7927 }
7928 return window;
7929}
7930
7931bool ImGui::IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent, bool popup_hierarchy, bool dock_hierarchy)
7932{
7933 ImGuiWindow* window_root = GetCombinedRootWindow(window, popup_hierarchy, dock_hierarchy);
7934 if (window_root == potential_parent)
7935 return true;
7936 while (window != NULL)
7937 {
7938 if (window == potential_parent)
7939 return true;
7940 if (window == window_root) // end of chain
7941 return false;
7942 window = window->ParentWindow;
7943 }
7944 return false;
7945}
7946
7947bool ImGui::IsWindowWithinBeginStackOf(ImGuiWindow* window, ImGuiWindow* potential_parent)
7948{
7949 if (window->RootWindow == potential_parent)
7950 return true;
7951 while (window != NULL)
7952 {
7953 if (window == potential_parent)
7954 return true;
7955 window = window->ParentWindowInBeginStack;
7956 }
7957 return false;
7958}
7959
7960bool ImGui::IsWindowAbove(ImGuiWindow* potential_above, ImGuiWindow* potential_below)
7961{
7962 ImGuiContext& g = *GImGui;
7963
7964 // It would be saner to ensure that display layer is always reflected in the g.Windows[] order, which would likely requires altering all manipulations of that array
7965 const int display_layer_delta = GetWindowDisplayLayer(window: potential_above) - GetWindowDisplayLayer(window: potential_below);
7966 if (display_layer_delta != 0)
7967 return display_layer_delta > 0;
7968
7969 for (int i = g.Windows.Size - 1; i >= 0; i--)
7970 {
7971 ImGuiWindow* candidate_window = g.Windows[i];
7972 if (candidate_window == potential_above)
7973 return true;
7974 if (candidate_window == potential_below)
7975 return false;
7976 }
7977 return false;
7978}
7979
7980bool ImGui::IsWindowHovered(ImGuiHoveredFlags flags)
7981{
7982 IM_ASSERT((flags & (ImGuiHoveredFlags_AllowWhenOverlapped | ImGuiHoveredFlags_AllowWhenDisabled)) == 0); // Flags not supported by this function
7983 ImGuiContext& g = *GImGui;
7984 ImGuiWindow* ref_window = g.HoveredWindow;
7985 ImGuiWindow* cur_window = g.CurrentWindow;
7986 if (ref_window == NULL)
7987 return false;
7988
7989 if ((flags & ImGuiHoveredFlags_AnyWindow) == 0)
7990 {
7991 IM_ASSERT(cur_window); // Not inside a Begin()/End()
7992 const bool popup_hierarchy = (flags & ImGuiHoveredFlags_NoPopupHierarchy) == 0;
7993 const bool dock_hierarchy = (flags & ImGuiHoveredFlags_DockHierarchy) != 0;
7994 if (flags & ImGuiHoveredFlags_RootWindow)
7995 cur_window = GetCombinedRootWindow(window: cur_window, popup_hierarchy, dock_hierarchy);
7996
7997 bool result;
7998 if (flags & ImGuiHoveredFlags_ChildWindows)
7999 result = IsWindowChildOf(window: ref_window, potential_parent: cur_window, popup_hierarchy, dock_hierarchy);
8000 else
8001 result = (ref_window == cur_window);
8002 if (!result)
8003 return false;
8004 }
8005
8006 if (!IsWindowContentHoverable(window: ref_window, flags))
8007 return false;
8008 if (!(flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
8009 if (g.ActiveId != 0 && !g.ActiveIdAllowOverlap && g.ActiveId != ref_window->MoveId)
8010 return false;
8011 return true;
8012}
8013
8014bool ImGui::IsWindowFocused(ImGuiFocusedFlags flags)
8015{
8016 ImGuiContext& g = *GImGui;
8017 ImGuiWindow* ref_window = g.NavWindow;
8018 ImGuiWindow* cur_window = g.CurrentWindow;
8019
8020 if (ref_window == NULL)
8021 return false;
8022 if (flags & ImGuiFocusedFlags_AnyWindow)
8023 return true;
8024
8025 IM_ASSERT(cur_window); // Not inside a Begin()/End()
8026 const bool popup_hierarchy = (flags & ImGuiFocusedFlags_NoPopupHierarchy) == 0;
8027 const bool dock_hierarchy = (flags & ImGuiFocusedFlags_DockHierarchy) != 0;
8028 if (flags & ImGuiHoveredFlags_RootWindow)
8029 cur_window = GetCombinedRootWindow(window: cur_window, popup_hierarchy, dock_hierarchy);
8030
8031 if (flags & ImGuiHoveredFlags_ChildWindows)
8032 return IsWindowChildOf(window: ref_window, potential_parent: cur_window, popup_hierarchy, dock_hierarchy);
8033 else
8034 return (ref_window == cur_window);
8035}
8036
8037ImGuiID ImGui::GetWindowDockID()
8038{
8039 ImGuiContext& g = *GImGui;
8040 return g.CurrentWindow->DockId;
8041}
8042
8043bool ImGui::IsWindowDocked()
8044{
8045 ImGuiContext& g = *GImGui;
8046 return g.CurrentWindow->DockIsActive;
8047}
8048
8049// Can we focus this window with CTRL+TAB (or PadMenu + PadFocusPrev/PadFocusNext)
8050// Note that NoNavFocus makes the window not reachable with CTRL+TAB but it can still be focused with mouse or programmatically.
8051// If you want a window to never be focused, you may use the e.g. NoInputs flag.
8052bool ImGui::IsWindowNavFocusable(ImGuiWindow* window)
8053{
8054 return window->WasActive && window == window->RootWindow && !(window->Flags & ImGuiWindowFlags_NoNavFocus);
8055}
8056
8057float ImGui::GetWindowWidth()
8058{
8059 ImGuiWindow* window = GImGui->CurrentWindow;
8060 return window->Size.x;
8061}
8062
8063float ImGui::GetWindowHeight()
8064{
8065 ImGuiWindow* window = GImGui->CurrentWindow;
8066 return window->Size.y;
8067}
8068
8069ImVec2 ImGui::GetWindowPos()
8070{
8071 ImGuiContext& g = *GImGui;
8072 ImGuiWindow* window = g.CurrentWindow;
8073 return window->Pos;
8074}
8075
8076void ImGui::SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond)
8077{
8078 // Test condition (NB: bit 0 is always true) and clear flags for next time
8079 if (cond && (window->SetWindowPosAllowFlags & cond) == 0)
8080 return;
8081
8082 IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
8083 window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
8084 window->SetWindowPosVal = ImVec2(FLT_MAX, FLT_MAX);
8085
8086 // Set
8087 const ImVec2 old_pos = window->Pos;
8088 window->Pos = ImFloor(v: pos);
8089 ImVec2 offset = window->Pos - old_pos;
8090 if (offset.x == 0.0f && offset.y == 0.0f)
8091 return;
8092 MarkIniSettingsDirty(window);
8093 // FIXME: share code with TranslateWindow(), need to confirm whether the 3 rect modified by TranslateWindow() are desirable here.
8094 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
8095 window->DC.CursorMaxPos += offset; // And more importantly we need to offset CursorMaxPos/CursorStartPos this so ContentSize calculation doesn't get affected.
8096 window->DC.IdealMaxPos += offset;
8097 window->DC.CursorStartPos += offset;
8098}
8099
8100void ImGui::SetWindowPos(const ImVec2& pos, ImGuiCond cond)
8101{
8102 ImGuiWindow* window = GetCurrentWindowRead();
8103 SetWindowPos(window, pos, cond);
8104}
8105
8106void ImGui::SetWindowPos(const char* name, const ImVec2& pos, ImGuiCond cond)
8107{
8108 if (ImGuiWindow* window = FindWindowByName(name))
8109 SetWindowPos(window, pos, cond);
8110}
8111
8112ImVec2 ImGui::GetWindowSize()
8113{
8114 ImGuiWindow* window = GetCurrentWindowRead();
8115 return window->Size;
8116}
8117
8118void ImGui::SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond)
8119{
8120 // Test condition (NB: bit 0 is always true) and clear flags for next time
8121 if (cond && (window->SetWindowSizeAllowFlags & cond) == 0)
8122 return;
8123
8124 IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
8125 window->SetWindowSizeAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
8126
8127 // Set
8128 ImVec2 old_size = window->SizeFull;
8129 window->AutoFitFramesX = (size.x <= 0.0f) ? 2 : 0;
8130 window->AutoFitFramesY = (size.y <= 0.0f) ? 2 : 0;
8131 if (size.x <= 0.0f)
8132 window->AutoFitOnlyGrows = false;
8133 else
8134 window->SizeFull.x = IM_FLOOR(size.x);
8135 if (size.y <= 0.0f)
8136 window->AutoFitOnlyGrows = false;
8137 else
8138 window->SizeFull.y = IM_FLOOR(size.y);
8139 if (old_size.x != window->SizeFull.x || old_size.y != window->SizeFull.y)
8140 MarkIniSettingsDirty(window);
8141}
8142
8143void ImGui::SetWindowSize(const ImVec2& size, ImGuiCond cond)
8144{
8145 SetWindowSize(window: GImGui->CurrentWindow, size, cond);
8146}
8147
8148void ImGui::SetWindowSize(const char* name, const ImVec2& size, ImGuiCond cond)
8149{
8150 if (ImGuiWindow* window = FindWindowByName(name))
8151 SetWindowSize(window, size, cond);
8152}
8153
8154void ImGui::SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond)
8155{
8156 // Test condition (NB: bit 0 is always true) and clear flags for next time
8157 if (cond && (window->SetWindowCollapsedAllowFlags & cond) == 0)
8158 return;
8159 window->SetWindowCollapsedAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
8160
8161 // Set
8162 window->Collapsed = collapsed;
8163}
8164
8165void ImGui::SetWindowHitTestHole(ImGuiWindow* window, const ImVec2& pos, const ImVec2& size)
8166{
8167 IM_ASSERT(window->HitTestHoleSize.x == 0); // We don't support multiple holes/hit test filters
8168 window->HitTestHoleSize = ImVec2ih(size);
8169 window->HitTestHoleOffset = ImVec2ih(pos - window->Pos);
8170}
8171
8172void ImGui::SetWindowCollapsed(bool collapsed, ImGuiCond cond)
8173{
8174 SetWindowCollapsed(window: GImGui->CurrentWindow, collapsed, cond);
8175}
8176
8177bool ImGui::IsWindowCollapsed()
8178{
8179 ImGuiWindow* window = GetCurrentWindowRead();
8180 return window->Collapsed;
8181}
8182
8183bool ImGui::IsWindowAppearing()
8184{
8185 ImGuiWindow* window = GetCurrentWindowRead();
8186 return window->Appearing;
8187}
8188
8189void ImGui::SetWindowCollapsed(const char* name, bool collapsed, ImGuiCond cond)
8190{
8191 if (ImGuiWindow* window = FindWindowByName(name))
8192 SetWindowCollapsed(window, collapsed, cond);
8193}
8194
8195void ImGui::SetWindowFocus()
8196{
8197 FocusWindow(window: GImGui->CurrentWindow);
8198}
8199
8200void ImGui::SetWindowFocus(const char* name)
8201{
8202 if (name)
8203 {
8204 if (ImGuiWindow* window = FindWindowByName(name))
8205 FocusWindow(window);
8206 }
8207 else
8208 {
8209 FocusWindow(NULL);
8210 }
8211}
8212
8213void ImGui::SetNextWindowPos(const ImVec2& pos, ImGuiCond cond, const ImVec2& pivot)
8214{
8215 ImGuiContext& g = *GImGui;
8216 IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
8217 g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasPos;
8218 g.NextWindowData.PosVal = pos;
8219 g.NextWindowData.PosPivotVal = pivot;
8220 g.NextWindowData.PosCond = cond ? cond : ImGuiCond_Always;
8221 g.NextWindowData.PosUndock = true;
8222}
8223
8224void ImGui::SetNextWindowSize(const ImVec2& size, ImGuiCond cond)
8225{
8226 ImGuiContext& g = *GImGui;
8227 IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
8228 g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasSize;
8229 g.NextWindowData.SizeVal = size;
8230 g.NextWindowData.SizeCond = cond ? cond : ImGuiCond_Always;
8231}
8232
8233void ImGui::SetNextWindowSizeConstraints(const ImVec2& size_min, const ImVec2& size_max, ImGuiSizeCallback custom_callback, void* custom_callback_user_data)
8234{
8235 ImGuiContext& g = *GImGui;
8236 g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasSizeConstraint;
8237 g.NextWindowData.SizeConstraintRect = ImRect(size_min, size_max);
8238 g.NextWindowData.SizeCallback = custom_callback;
8239 g.NextWindowData.SizeCallbackUserData = custom_callback_user_data;
8240}
8241
8242// Content size = inner scrollable rectangle, padded with WindowPadding.
8243// SetNextWindowContentSize(ImVec2(100,100) + ImGuiWindowFlags_AlwaysAutoResize will always allow submitting a 100x100 item.
8244void ImGui::SetNextWindowContentSize(const ImVec2& size)
8245{
8246 ImGuiContext& g = *GImGui;
8247 g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasContentSize;
8248 g.NextWindowData.ContentSizeVal = ImFloor(v: size);
8249}
8250
8251void ImGui::SetNextWindowScroll(const ImVec2& scroll)
8252{
8253 ImGuiContext& g = *GImGui;
8254 g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasScroll;
8255 g.NextWindowData.ScrollVal = scroll;
8256}
8257
8258void ImGui::SetNextWindowCollapsed(bool collapsed, ImGuiCond cond)
8259{
8260 ImGuiContext& g = *GImGui;
8261 IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
8262 g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasCollapsed;
8263 g.NextWindowData.CollapsedVal = collapsed;
8264 g.NextWindowData.CollapsedCond = cond ? cond : ImGuiCond_Always;
8265}
8266
8267void ImGui::SetNextWindowFocus()
8268{
8269 ImGuiContext& g = *GImGui;
8270 g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasFocus;
8271}
8272
8273void ImGui::SetNextWindowBgAlpha(float alpha)
8274{
8275 ImGuiContext& g = *GImGui;
8276 g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasBgAlpha;
8277 g.NextWindowData.BgAlphaVal = alpha;
8278}
8279
8280void ImGui::SetNextWindowViewport(ImGuiID id)
8281{
8282 ImGuiContext& g = *GImGui;
8283 g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasViewport;
8284 g.NextWindowData.ViewportId = id;
8285}
8286
8287void ImGui::SetNextWindowDockID(ImGuiID id, ImGuiCond cond)
8288{
8289 ImGuiContext& g = *GImGui;
8290 g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasDock;
8291 g.NextWindowData.DockCond = cond ? cond : ImGuiCond_Always;
8292 g.NextWindowData.DockId = id;
8293}
8294
8295void ImGui::SetNextWindowClass(const ImGuiWindowClass* window_class)
8296{
8297 ImGuiContext& g = *GImGui;
8298 IM_ASSERT((window_class->ViewportFlagsOverrideSet & window_class->ViewportFlagsOverrideClear) == 0); // Cannot set both set and clear for the same bit
8299 g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasWindowClass;
8300 g.NextWindowData.WindowClass = *window_class;
8301}
8302
8303ImDrawList* ImGui::GetWindowDrawList()
8304{
8305 ImGuiWindow* window = GetCurrentWindow();
8306 return window->DrawList;
8307}
8308
8309float ImGui::GetWindowDpiScale()
8310{
8311 ImGuiContext& g = *GImGui;
8312 return g.CurrentDpiScale;
8313}
8314
8315ImGuiViewport* ImGui::GetWindowViewport()
8316{
8317 ImGuiContext& g = *GImGui;
8318 IM_ASSERT(g.CurrentViewport != NULL && g.CurrentViewport == g.CurrentWindow->Viewport);
8319 return g.CurrentViewport;
8320}
8321
8322ImFont* ImGui::GetFont()
8323{
8324 return GImGui->Font;
8325}
8326
8327float ImGui::GetFontSize()
8328{
8329 return GImGui->FontSize;
8330}
8331
8332ImVec2 ImGui::GetFontTexUvWhitePixel()
8333{
8334 return GImGui->DrawListSharedData.TexUvWhitePixel;
8335}
8336
8337void ImGui::SetWindowFontScale(float scale)
8338{
8339 IM_ASSERT(scale > 0.0f);
8340 ImGuiContext& g = *GImGui;
8341 ImGuiWindow* window = GetCurrentWindow();
8342 window->FontWindowScale = scale;
8343 g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize();
8344}
8345
8346void ImGui::ActivateItem(ImGuiID id)
8347{
8348 ImGuiContext& g = *GImGui;
8349 g.NavNextActivateId = id;
8350 g.NavNextActivateFlags = ImGuiActivateFlags_None;
8351}
8352
8353void ImGui::PushFocusScope(ImGuiID id)
8354{
8355 ImGuiContext& g = *GImGui;
8356 g.FocusScopeStack.push_back(v: id);
8357 g.CurrentFocusScopeId = id;
8358}
8359
8360void ImGui::PopFocusScope()
8361{
8362 ImGuiContext& g = *GImGui;
8363 IM_ASSERT(g.FocusScopeStack.Size > 0); // Too many PopFocusScope() ?
8364 g.FocusScopeStack.pop_back();
8365 g.CurrentFocusScopeId = g.FocusScopeStack.Size ? g.FocusScopeStack.back() : 0;
8366}
8367
8368// Note: this will likely be called ActivateItem() once we rework our Focus/Activation system!
8369void ImGui::SetKeyboardFocusHere(int offset)
8370{
8371 ImGuiContext& g = *GImGui;
8372 ImGuiWindow* window = g.CurrentWindow;
8373 IM_ASSERT(offset >= -1); // -1 is allowed but not below
8374 IMGUI_DEBUG_LOG_ACTIVEID("SetKeyboardFocusHere(%d) in window \"%s\"\n", offset, window->Name);
8375
8376 // It makes sense in the vast majority of cases to never interrupt a drag and drop.
8377 // When we refactor this function into ActivateItem() we may want to make this an option.
8378 // MovingWindow is protected from most user inputs using SetActiveIdUsingNavAndKeys(), but
8379 // is also automatically dropped in the event g.ActiveId is stolen.
8380 if (g.DragDropActive || g.MovingWindow != NULL)
8381 {
8382 IMGUI_DEBUG_LOG_ACTIVEID("SetKeyboardFocusHere() ignored while DragDropActive!\n");
8383 return;
8384 }
8385
8386 SetNavWindow(window);
8387
8388 ImGuiScrollFlags scroll_flags = window->Appearing ? ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_AlwaysCenterY : ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_KeepVisibleEdgeY;
8389 NavMoveRequestSubmit(move_dir: ImGuiDir_None, clip_dir: offset < 0 ? ImGuiDir_Up : ImGuiDir_Down, move_flags: ImGuiNavMoveFlags_Tabbing | ImGuiNavMoveFlags_FocusApi, scroll_flags); // FIXME-NAV: Once we refactor tabbing, add LegacyApi flag to not activate non-inputable.
8390 if (offset == -1)
8391 {
8392 NavMoveRequestResolveWithLastItem(result: &g.NavMoveResultLocal);
8393 }
8394 else
8395 {
8396 g.NavTabbingDir = 1;
8397 g.NavTabbingCounter = offset + 1;
8398 }
8399}
8400
8401void ImGui::SetItemDefaultFocus()
8402{
8403 ImGuiContext& g = *GImGui;
8404 ImGuiWindow* window = g.CurrentWindow;
8405 if (!window->Appearing)
8406 return;
8407 if (g.NavWindow != window->RootWindowForNav || (!g.NavInitRequest && g.NavInitResultId == 0) || g.NavLayer != window->DC.NavLayerCurrent)
8408 return;
8409
8410 g.NavInitRequest = false;
8411 g.NavInitResultId = g.LastItemData.ID;
8412 g.NavInitResultRectRel = WindowRectAbsToRel(window, r: g.LastItemData.Rect);
8413 NavUpdateAnyRequestFlag();
8414
8415 // Scroll could be done in NavInitRequestApplyResult() via an opt-in flag (we however don't want regular init requests to scroll)
8416 if (!IsItemVisible())
8417 ScrollToRectEx(window, rect: g.LastItemData.Rect, flags: ImGuiScrollFlags_None);
8418}
8419
8420void ImGui::SetStateStorage(ImGuiStorage* tree)
8421{
8422 ImGuiWindow* window = GImGui->CurrentWindow;
8423 window->DC.StateStorage = tree ? tree : &window->StateStorage;
8424}
8425
8426ImGuiStorage* ImGui::GetStateStorage()
8427{
8428 ImGuiWindow* window = GImGui->CurrentWindow;
8429 return window->DC.StateStorage;
8430}
8431
8432void ImGui::PushID(const char* str_id)
8433{
8434 ImGuiContext& g = *GImGui;
8435 ImGuiWindow* window = g.CurrentWindow;
8436 ImGuiID id = window->GetID(str: str_id);
8437 window->IDStack.push_back(v: id);
8438}
8439
8440void ImGui::PushID(const char* str_id_begin, const char* str_id_end)
8441{
8442 ImGuiContext& g = *GImGui;
8443 ImGuiWindow* window = g.CurrentWindow;
8444 ImGuiID id = window->GetID(str: str_id_begin, str_end: str_id_end);
8445 window->IDStack.push_back(v: id);
8446}
8447
8448void ImGui::PushID(const void* ptr_id)
8449{
8450 ImGuiContext& g = *GImGui;
8451 ImGuiWindow* window = g.CurrentWindow;
8452 ImGuiID id = window->GetID(ptr: ptr_id);
8453 window->IDStack.push_back(v: id);
8454}
8455
8456void ImGui::PushID(int int_id)
8457{
8458 ImGuiContext& g = *GImGui;
8459 ImGuiWindow* window = g.CurrentWindow;
8460 ImGuiID id = window->GetID(n: int_id);
8461 window->IDStack.push_back(v: id);
8462}
8463
8464// Push a given id value ignoring the ID stack as a seed.
8465void ImGui::PushOverrideID(ImGuiID id)
8466{
8467 ImGuiContext& g = *GImGui;
8468 ImGuiWindow* window = g.CurrentWindow;
8469 if (g.DebugHookIdInfo == id)
8470 DebugHookIdInfo(id, data_type: ImGuiDataType_ID, NULL, NULL);
8471 window->IDStack.push_back(v: id);
8472}
8473
8474// Helper to avoid a common series of PushOverrideID -> GetID() -> PopID() call
8475// (note that when using this pattern, TestEngine's "Stack Tool" will tend to not display the intermediate stack level.
8476// for that to work we would need to do PushOverrideID() -> ItemAdd() -> PopID() which would alter widget code a little more)
8477ImGuiID ImGui::GetIDWithSeed(const char* str, const char* str_end, ImGuiID seed)
8478{
8479 ImGuiID id = ImHashStr(data_p: str, data_size: str_end ? (str_end - str) : 0, seed);
8480 ImGuiContext& g = *GImGui;
8481 if (g.DebugHookIdInfo == id)
8482 DebugHookIdInfo(id, data_type: ImGuiDataType_String, data_id: str, data_id_end: str_end);
8483 return id;
8484}
8485
8486void ImGui::PopID()
8487{
8488 ImGuiWindow* window = GImGui->CurrentWindow;
8489 IM_ASSERT(window->IDStack.Size > 1); // Too many PopID(), or could be popping in a wrong/different window?
8490 window->IDStack.pop_back();
8491}
8492
8493ImGuiID ImGui::GetID(const char* str_id)
8494{
8495 ImGuiWindow* window = GImGui->CurrentWindow;
8496 return window->GetID(str: str_id);
8497}
8498
8499ImGuiID ImGui::GetID(const char* str_id_begin, const char* str_id_end)
8500{
8501 ImGuiWindow* window = GImGui->CurrentWindow;
8502 return window->GetID(str: str_id_begin, str_end: str_id_end);
8503}
8504
8505ImGuiID ImGui::GetID(const void* ptr_id)
8506{
8507 ImGuiWindow* window = GImGui->CurrentWindow;
8508 return window->GetID(ptr: ptr_id);
8509}
8510
8511bool ImGui::IsRectVisible(const ImVec2& size)
8512{
8513 ImGuiWindow* window = GImGui->CurrentWindow;
8514 return window->ClipRect.Overlaps(r: ImRect(window->DC.CursorPos, window->DC.CursorPos + size));
8515}
8516
8517bool ImGui::IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max)
8518{
8519 ImGuiWindow* window = GImGui->CurrentWindow;
8520 return window->ClipRect.Overlaps(r: ImRect(rect_min, rect_max));
8521}
8522
8523
8524//-----------------------------------------------------------------------------
8525// [SECTION] INPUTS
8526//-----------------------------------------------------------------------------
8527
8528// Test if mouse cursor is hovering given rectangle
8529// NB- Rectangle is clipped by our current clip setting
8530// NB- Expand the rectangle to be generous on imprecise inputs systems (g.Style.TouchExtraPadding)
8531bool ImGui::IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool clip)
8532{
8533 ImGuiContext& g = *GImGui;
8534
8535 // Clip
8536 ImRect rect_clipped(r_min, r_max);
8537 if (clip)
8538 rect_clipped.ClipWith(r: g.CurrentWindow->ClipRect);
8539
8540 // Expand for touch input
8541 const ImRect rect_for_touch(rect_clipped.Min - g.Style.TouchExtraPadding, rect_clipped.Max + g.Style.TouchExtraPadding);
8542 if (!rect_for_touch.Contains(p: g.IO.MousePos))
8543 return false;
8544 if (!g.MouseViewport->GetMainRect().Overlaps(r: rect_clipped))
8545 return false;
8546 return true;
8547}
8548
8549ImGuiKeyData* ImGui::GetKeyData(ImGuiKey key)
8550{
8551 ImGuiContext& g = *GImGui;
8552
8553 // Special storage location for mods
8554 if (key & ImGuiMod_Mask_)
8555 key = ConvertSingleModFlagToKey(key);
8556
8557 int index;
8558#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO
8559 IM_ASSERT(key >= ImGuiKey_LegacyNativeKey_BEGIN && key < ImGuiKey_NamedKey_END);
8560 if (IsLegacyKey(key))
8561 index = (g.IO.KeyMap[key] != -1) ? g.IO.KeyMap[key] : key; // Remap native->imgui or imgui->native
8562 else
8563 index = key;
8564#else
8565 IM_ASSERT(IsNamedKey(key) && "Support for user key indices was dropped in favor of ImGuiKey. Please update backend & user code.");
8566 index = key - ImGuiKey_NamedKey_BEGIN;
8567#endif
8568 return &g.IO.KeysData[index];
8569}
8570
8571#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO
8572ImGuiKey ImGui::GetKeyIndex(ImGuiKey key)
8573{
8574 ImGuiContext& g = *GImGui;
8575 IM_ASSERT(IsNamedKey(key));
8576 const ImGuiKeyData* key_data = GetKeyData(key);
8577 return (ImGuiKey)(key_data - g.IO.KeysData);
8578}
8579#endif
8580
8581// Those names a provided for debugging purpose and are not meant to be saved persistently not compared.
8582static const char* const GKeyNames[] =
8583{
8584 "Tab", "LeftArrow", "RightArrow", "UpArrow", "DownArrow", "PageUp", "PageDown",
8585 "Home", "End", "Insert", "Delete", "Backspace", "Space", "Enter", "Escape",
8586 "LeftCtrl", "LeftShift", "LeftAlt", "LeftSuper", "RightCtrl", "RightShift", "RightAlt", "RightSuper", "Menu",
8587 "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H",
8588 "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
8589 "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12",
8590 "Apostrophe", "Comma", "Minus", "Period", "Slash", "Semicolon", "Equal", "LeftBracket",
8591 "Backslash", "RightBracket", "GraveAccent", "CapsLock", "ScrollLock", "NumLock", "PrintScreen",
8592 "Pause", "Keypad0", "Keypad1", "Keypad2", "Keypad3", "Keypad4", "Keypad5", "Keypad6",
8593 "Keypad7", "Keypad8", "Keypad9", "KeypadDecimal", "KeypadDivide", "KeypadMultiply",
8594 "KeypadSubtract", "KeypadAdd", "KeypadEnter", "KeypadEqual",
8595 "GamepadStart", "GamepadBack",
8596 "GamepadFaceLeft", "GamepadFaceRight", "GamepadFaceUp", "GamepadFaceDown",
8597 "GamepadDpadLeft", "GamepadDpadRight", "GamepadDpadUp", "GamepadDpadDown",
8598 "GamepadL1", "GamepadR1", "GamepadL2", "GamepadR2", "GamepadL3", "GamepadR3",
8599 "GamepadLStickLeft", "GamepadLStickRight", "GamepadLStickUp", "GamepadLStickDown",
8600 "GamepadRStickLeft", "GamepadRStickRight", "GamepadRStickUp", "GamepadRStickDown",
8601 "MouseLeft", "MouseRight", "MouseMiddle", "MouseX1", "MouseX2", "MouseWheelX", "MouseWheelY",
8602 "ModCtrl", "ModShift", "ModAlt", "ModSuper", // ReservedForModXXX are showing the ModXXX names.
8603};
8604IM_STATIC_ASSERT(ImGuiKey_NamedKey_COUNT == IM_ARRAYSIZE(GKeyNames));
8605
8606const char* ImGui::GetKeyName(ImGuiKey key)
8607{
8608#ifdef IMGUI_DISABLE_OBSOLETE_KEYIO
8609 IM_ASSERT((IsNamedKey(key) || key == ImGuiKey_None) && "Support for user key indices was dropped in favor of ImGuiKey. Please update backend and user code.");
8610#else
8611 if (IsLegacyKey(key))
8612 {
8613 ImGuiIO& io = GetIO();
8614 if (io.KeyMap[key] == -1)
8615 return "N/A";
8616 IM_ASSERT(IsNamedKey((ImGuiKey)io.KeyMap[key]));
8617 key = (ImGuiKey)io.KeyMap[key];
8618 }
8619#endif
8620 if (key == ImGuiKey_None)
8621 return "None";
8622 if (key & ImGuiMod_Mask_)
8623 key = ConvertSingleModFlagToKey(key);
8624 if (!IsNamedKey(key))
8625 return "Unknown";
8626
8627 return GKeyNames[key - ImGuiKey_NamedKey_BEGIN];
8628}
8629
8630// ImGuiMod_Shortcut is translated to either Ctrl or Super.
8631void ImGui::GetKeyChordName(ImGuiKeyChord key_chord, char* out_buf, int out_buf_size)
8632{
8633 ImGuiContext& g = *GImGui;
8634 if (key_chord & ImGuiMod_Shortcut)
8635 key_chord = ConvertShortcutMod(key_chord);
8636 ImFormatString(buf: out_buf, buf_size: (size_t)out_buf_size, fmt: "%s%s%s%s%s",
8637 (key_chord & ImGuiMod_Ctrl) ? "Ctrl+" : "",
8638 (key_chord & ImGuiMod_Shift) ? "Shift+" : "",
8639 (key_chord & ImGuiMod_Alt) ? "Alt+" : "",
8640 (key_chord & ImGuiMod_Super) ? (g.IO.ConfigMacOSXBehaviors ? "Cmd+" : "Super+") : "",
8641 GetKeyName(key: (ImGuiKey)(key_chord & ~ImGuiMod_Mask_)));
8642}
8643
8644// t0 = previous time (e.g.: g.Time - g.IO.DeltaTime)
8645// t1 = current time (e.g.: g.Time)
8646// An event is triggered at:
8647// t = 0.0f t = repeat_delay, t = repeat_delay + repeat_rate*N
8648int ImGui::CalcTypematicRepeatAmount(float t0, float t1, float repeat_delay, float repeat_rate)
8649{
8650 if (t1 == 0.0f)
8651 return 1;
8652 if (t0 >= t1)
8653 return 0;
8654 if (repeat_rate <= 0.0f)
8655 return (t0 < repeat_delay) && (t1 >= repeat_delay);
8656 const int count_t0 = (t0 < repeat_delay) ? -1 : (int)((t0 - repeat_delay) / repeat_rate);
8657 const int count_t1 = (t1 < repeat_delay) ? -1 : (int)((t1 - repeat_delay) / repeat_rate);
8658 const int count = count_t1 - count_t0;
8659 return count;
8660}
8661
8662void ImGui::GetTypematicRepeatRate(ImGuiInputFlags flags, float* repeat_delay, float* repeat_rate)
8663{
8664 ImGuiContext& g = *GImGui;
8665 switch (flags & ImGuiInputFlags_RepeatRateMask_)
8666 {
8667 case ImGuiInputFlags_RepeatRateNavMove: *repeat_delay = g.IO.KeyRepeatDelay * 0.72f; *repeat_rate = g.IO.KeyRepeatRate * 0.80f; return;
8668 case ImGuiInputFlags_RepeatRateNavTweak: *repeat_delay = g.IO.KeyRepeatDelay * 0.72f; *repeat_rate = g.IO.KeyRepeatRate * 0.30f; return;
8669 case ImGuiInputFlags_RepeatRateDefault: default: *repeat_delay = g.IO.KeyRepeatDelay * 1.00f; *repeat_rate = g.IO.KeyRepeatRate * 1.00f; return;
8670 }
8671}
8672
8673// Return value representing the number of presses in the last time period, for the given repeat rate
8674// (most often returns 0 or 1. The result is generally only >1 when RepeatRate is smaller than DeltaTime, aka large DeltaTime or fast RepeatRate)
8675int ImGui::GetKeyPressedAmount(ImGuiKey key, float repeat_delay, float repeat_rate)
8676{
8677 ImGuiContext& g = *GImGui;
8678 const ImGuiKeyData* key_data = GetKeyData(key);
8679 if (!key_data->Down) // In theory this should already be encoded as (DownDuration < 0.0f), but testing this facilitates eating mechanism (until we finish work on key ownership)
8680 return 0;
8681 const float t = key_data->DownDuration;
8682 return CalcTypematicRepeatAmount(t0: t - g.IO.DeltaTime, t1: t, repeat_delay, repeat_rate);
8683}
8684
8685// Return 2D vector representing the combination of four cardinal direction, with analog value support (for e.g. ImGuiKey_GamepadLStick* values).
8686ImVec2 ImGui::GetKeyVector2d(ImGuiKey key_left, ImGuiKey key_right, ImGuiKey key_up, ImGuiKey key_down)
8687{
8688 return ImVec2(
8689 GetKeyData(key: key_right)->AnalogValue - GetKeyData(key: key_left)->AnalogValue,
8690 GetKeyData(key: key_down)->AnalogValue - GetKeyData(key: key_up)->AnalogValue);
8691}
8692
8693// owner_id may be None/Any, but routing_id needs to be always be set, so we default to GetCurrentFocusScope().
8694static inline ImGuiID GetRoutingIdFromOwnerId(ImGuiID owner_id)
8695{
8696 ImGuiContext& g = *GImGui;
8697 return (owner_id != ImGuiKeyOwner_None && owner_id != ImGuiKeyOwner_Any) ? owner_id : g.CurrentFocusScopeId;
8698}
8699
8700ImGuiKeyRoutingData* ImGui::GetShortcutRoutingData(ImGuiKeyChord key_chord)
8701{
8702 // Majority of shortcuts will be Key + any number of Mods
8703 // We accept _Single_ mod with ImGuiKey_None.
8704 // - Shortcut(ImGuiKey_S | ImGuiMod_Ctrl); // Legal
8705 // - Shortcut(ImGuiKey_S | ImGuiMod_Ctrl | ImGuiMod_Shift); // Legal
8706 // - Shortcut(ImGuiMod_Ctrl); // Legal
8707 // - Shortcut(ImGuiMod_Ctrl | ImGuiMod_Shift); // Not legal
8708 ImGuiContext& g = *GImGui;
8709 ImGuiKeyRoutingTable* rt = &g.KeysRoutingTable;
8710 ImGuiKeyRoutingData* routing_data;
8711 if (key_chord & ImGuiMod_Shortcut)
8712 key_chord = ConvertShortcutMod(key_chord);
8713 ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_);
8714 ImGuiKey mods = (ImGuiKey)(key_chord & ImGuiMod_Mask_);
8715 if (key == ImGuiKey_None)
8716 key = ConvertSingleModFlagToKey(key: mods);
8717 IM_ASSERT(IsNamedKey(key));
8718
8719 // Get (in the majority of case, the linked list will have one element so this should be 2 reads.
8720 // Subsequent elements will be contiguous in memory as list is sorted/rebuilt in NewFrame).
8721 for (ImGuiKeyRoutingIndex idx = rt->Index[key - ImGuiKey_NamedKey_BEGIN]; idx != -1; idx = routing_data->NextEntryIndex)
8722 {
8723 routing_data = &rt->Entries[idx];
8724 if (routing_data->Mods == mods)
8725 return routing_data;
8726 }
8727
8728 // Add to linked-list
8729 ImGuiKeyRoutingIndex routing_data_idx = (ImGuiKeyRoutingIndex)rt->Entries.Size;
8730 rt->Entries.push_back(v: ImGuiKeyRoutingData());
8731 routing_data = &rt->Entries[routing_data_idx];
8732 routing_data->Mods = (ImU16)mods;
8733 routing_data->NextEntryIndex = rt->Index[key - ImGuiKey_NamedKey_BEGIN]; // Setup linked list
8734 rt->Index[key - ImGuiKey_NamedKey_BEGIN] = routing_data_idx;
8735 return routing_data;
8736}
8737
8738// Current score encoding (lower is highest priority):
8739// - 0: ImGuiInputFlags_RouteGlobalHigh
8740// - 1: ImGuiInputFlags_RouteFocused (if item active)
8741// - 2: ImGuiInputFlags_RouteGlobal
8742// - 3+: ImGuiInputFlags_RouteFocused (if window in focus-stack)
8743// - 254: ImGuiInputFlags_RouteGlobalLow
8744// - 255: never route
8745// 'flags' should include an explicit routing policy
8746static int CalcRoutingScore(ImGuiWindow* location, ImGuiID owner_id, ImGuiInputFlags flags)
8747{
8748 if (flags & ImGuiInputFlags_RouteFocused)
8749 {
8750 ImGuiContext& g = *GImGui;
8751 ImGuiWindow* focused = g.NavWindow;
8752
8753 // ActiveID gets top priority
8754 // (we don't check g.ActiveIdUsingAllKeys here. Routing is applied but if input ownership is tested later it may discard it)
8755 if (owner_id != 0 && g.ActiveId == owner_id)
8756 return 1;
8757
8758 // Score based on distance to focused window (lower is better)
8759 // Assuming both windows are submitting a routing request,
8760 // - When Window....... is focused -> Window scores 3 (best), Window/ChildB scores 255 (no match)
8761 // - When Window/ChildB is focused -> Window scores 4, Window/ChildB scores 3 (best)
8762 // Assuming only WindowA is submitting a routing request,
8763 // - When Window/ChildB is focused -> Window scores 4 (best), Window/ChildB doesn't have a score.
8764 if (focused != NULL && focused->RootWindow == location->RootWindow)
8765 for (int next_score = 3; focused != NULL; next_score++)
8766 {
8767 if (focused == location)
8768 {
8769 IM_ASSERT(next_score < 255);
8770 return next_score;
8771 }
8772 focused = (focused->RootWindow != focused) ? focused->ParentWindow : NULL; // FIXME: This could be later abstracted as a focus path
8773 }
8774 return 255;
8775 }
8776
8777 // ImGuiInputFlags_RouteGlobalHigh is default, so calls without flags are not conditional
8778 if (flags & ImGuiInputFlags_RouteGlobal)
8779 return 2;
8780 if (flags & ImGuiInputFlags_RouteGlobalLow)
8781 return 254;
8782 return 0;
8783}
8784
8785// Request a desired route for an input chord (key + mods).
8786// Return true if the route is available this frame.
8787// - Routes and key ownership are attributed at the beginning of next frame based on best score and mod state.
8788// (Conceptually this does a "Submit for next frame" + "Test for current frame".
8789// As such, it could be called TrySetXXX or SubmitXXX, or the Submit and Test operations should be separate.)
8790// - Using 'owner_id == ImGuiKeyOwner_Any/0': auto-assign an owner based on current focus scope (each window has its focus scope by default)
8791// - Using 'owner_id == ImGuiKeyOwner_None': allows disabling/locking a shortcut.
8792bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags flags)
8793{
8794 ImGuiContext& g = *GImGui;
8795 if ((flags & ImGuiInputFlags_RouteMask_) == 0)
8796 flags |= ImGuiInputFlags_RouteGlobalHigh; // IMPORTANT: This is the default for SetShortcutRouting() but NOT Shortcut()
8797 else
8798 IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiInputFlags_RouteMask_)); // Check that only 1 routing flag is used
8799
8800 if (flags & ImGuiInputFlags_RouteUnlessBgFocused)
8801 if (g.NavWindow == NULL)
8802 return false;
8803 if (flags & ImGuiInputFlags_RouteAlways)
8804 return true;
8805
8806 const int score = CalcRoutingScore(location: g.CurrentWindow, owner_id, flags);
8807 if (score == 255)
8808 return false;
8809
8810 // Submit routing for NEXT frame (assuming score is sufficient)
8811 // FIXME: Could expose a way to use a "serve last" policy for same score resolution (using <= instead of <).
8812 ImGuiKeyRoutingData* routing_data = GetShortcutRoutingData(key_chord);
8813 const ImGuiID routing_id = GetRoutingIdFromOwnerId(owner_id);
8814 //const bool set_route = (flags & ImGuiInputFlags_ServeLast) ? (score <= routing_data->RoutingNextScore) : (score < routing_data->RoutingNextScore);
8815 if (score < routing_data->RoutingNextScore)
8816 {
8817 routing_data->RoutingNext = routing_id;
8818 routing_data->RoutingNextScore = (ImU8)score;
8819 }
8820
8821 // Return routing state for CURRENT frame
8822 return routing_data->RoutingCurr == routing_id;
8823}
8824
8825// Currently unused by core (but used by tests)
8826// Note: this cannot be turned into GetShortcutRouting() because we do the owner_id->routing_id translation, name would be more misleading.
8827bool ImGui::TestShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id)
8828{
8829 const ImGuiID routing_id = GetRoutingIdFromOwnerId(owner_id);
8830 ImGuiKeyRoutingData* routing_data = GetShortcutRoutingData(key_chord); // FIXME: Could avoid creating entry.
8831 return routing_data->RoutingCurr == routing_id;
8832}
8833
8834// Note that Dear ImGui doesn't know the meaning/semantic of ImGuiKey from 0..511: they are legacy native keycodes.
8835// Consider transitioning from 'IsKeyDown(MY_ENGINE_KEY_A)' (<1.87) to IsKeyDown(ImGuiKey_A) (>= 1.87)
8836bool ImGui::IsKeyDown(ImGuiKey key)
8837{
8838 return IsKeyDown(key, ImGuiKeyOwner_Any);
8839}
8840
8841bool ImGui::IsKeyDown(ImGuiKey key, ImGuiID owner_id)
8842{
8843 const ImGuiKeyData* key_data = GetKeyData(key);
8844 if (!key_data->Down)
8845 return false;
8846 if (!TestKeyOwner(key, owner_id))
8847 return false;
8848 return true;
8849}
8850
8851bool ImGui::IsKeyPressed(ImGuiKey key, bool repeat)
8852{
8853 return IsKeyPressed(key, ImGuiKeyOwner_Any, flags: repeat ? ImGuiInputFlags_Repeat : ImGuiInputFlags_None);
8854}
8855
8856// Important: unless legacy IsKeyPressed(ImGuiKey, bool repeat=true) which DEFAULT to repeat, this requires EXPLICIT repeat.
8857bool ImGui::IsKeyPressed(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags)
8858{
8859 const ImGuiKeyData* key_data = GetKeyData(key);
8860 if (!key_data->Down) // In theory this should already be encoded as (DownDuration < 0.0f), but testing this facilitates eating mechanism (until we finish work on key ownership)
8861 return false;
8862 const float t = key_data->DownDuration;
8863 if (t < 0.0f)
8864 return false;
8865 IM_ASSERT((flags & ~ImGuiInputFlags_SupportedByIsKeyPressed) == 0); // Passing flags not supported by this function!
8866
8867 bool pressed = (t == 0.0f);
8868 if (!pressed && ((flags & ImGuiInputFlags_Repeat) != 0))
8869 {
8870 float repeat_delay, repeat_rate;
8871 GetTypematicRepeatRate(flags, repeat_delay: &repeat_delay, repeat_rate: &repeat_rate);
8872 pressed = (t > repeat_delay) && GetKeyPressedAmount(key, repeat_delay, repeat_rate) > 0;
8873 }
8874 if (!pressed)
8875 return false;
8876 if (!TestKeyOwner(key, owner_id))
8877 return false;
8878 return true;
8879}
8880
8881bool ImGui::IsKeyReleased(ImGuiKey key)
8882{
8883 return IsKeyReleased(key, ImGuiKeyOwner_Any);
8884}
8885
8886bool ImGui::IsKeyReleased(ImGuiKey key, ImGuiID owner_id)
8887{
8888 const ImGuiKeyData* key_data = GetKeyData(key);
8889 if (key_data->DownDurationPrev < 0.0f || key_data->Down)
8890 return false;
8891 if (!TestKeyOwner(key, owner_id))
8892 return false;
8893 return true;
8894}
8895
8896bool ImGui::IsMouseDown(ImGuiMouseButton button)
8897{
8898 ImGuiContext& g = *GImGui;
8899 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
8900 return g.IO.MouseDown[button] && TestKeyOwner(key: MouseButtonToKey(button), ImGuiKeyOwner_Any); // should be same as IsKeyDown(MouseButtonToKey(button), ImGuiKeyOwner_Any), but this allows legacy code hijacking the io.Mousedown[] array.
8901}
8902
8903bool ImGui::IsMouseDown(ImGuiMouseButton button, ImGuiID owner_id)
8904{
8905 ImGuiContext& g = *GImGui;
8906 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
8907 return g.IO.MouseDown[button] && TestKeyOwner(key: MouseButtonToKey(button), owner_id); // Should be same as IsKeyDown(MouseButtonToKey(button), owner_id), but this allows legacy code hijacking the io.Mousedown[] array.
8908}
8909
8910bool ImGui::IsMouseClicked(ImGuiMouseButton button, bool repeat)
8911{
8912 return IsMouseClicked(button, ImGuiKeyOwner_Any, flags: repeat ? ImGuiInputFlags_Repeat : ImGuiInputFlags_None);
8913}
8914
8915bool ImGui::IsMouseClicked(ImGuiMouseButton button, ImGuiID owner_id, ImGuiInputFlags flags)
8916{
8917 ImGuiContext& g = *GImGui;
8918 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
8919 if (!g.IO.MouseDown[button]) // In theory this should already be encoded as (DownDuration < 0.0f), but testing this facilitates eating mechanism (until we finish work on key ownership)
8920 return false;
8921 const float t = g.IO.MouseDownDuration[button];
8922 if (t < 0.0f)
8923 return false;
8924 IM_ASSERT((flags & ~ImGuiInputFlags_SupportedByIsKeyPressed) == 0); // Passing flags not supported by this function!
8925
8926 const bool repeat = (flags & ImGuiInputFlags_Repeat) != 0;
8927 const bool pressed = (t == 0.0f) || (repeat && t > g.IO.KeyRepeatDelay && CalcTypematicRepeatAmount(t0: t - g.IO.DeltaTime, t1: t, repeat_delay: g.IO.KeyRepeatDelay, repeat_rate: g.IO.KeyRepeatRate) > 0);
8928 if (!pressed)
8929 return false;
8930
8931 if (!TestKeyOwner(key: MouseButtonToKey(button), owner_id))
8932 return false;
8933
8934 return true;
8935}
8936
8937bool ImGui::IsMouseReleased(ImGuiMouseButton button)
8938{
8939 ImGuiContext& g = *GImGui;
8940 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
8941 return g.IO.MouseReleased[button] && TestKeyOwner(key: MouseButtonToKey(button), ImGuiKeyOwner_Any); // Should be same as IsKeyReleased(MouseButtonToKey(button), ImGuiKeyOwner_Any)
8942}
8943
8944bool ImGui::IsMouseReleased(ImGuiMouseButton button, ImGuiID owner_id)
8945{
8946 ImGuiContext& g = *GImGui;
8947 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
8948 return g.IO.MouseReleased[button] && TestKeyOwner(key: MouseButtonToKey(button), owner_id); // Should be same as IsKeyReleased(MouseButtonToKey(button), owner_id)
8949}
8950
8951bool ImGui::IsMouseDoubleClicked(ImGuiMouseButton button)
8952{
8953 ImGuiContext& g = *GImGui;
8954 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
8955 return g.IO.MouseClickedCount[button] == 2 && TestKeyOwner(key: MouseButtonToKey(button), ImGuiKeyOwner_Any);
8956}
8957
8958int ImGui::GetMouseClickedCount(ImGuiMouseButton button)
8959{
8960 ImGuiContext& g = *GImGui;
8961 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
8962 return g.IO.MouseClickedCount[button];
8963}
8964
8965// Return if a mouse click/drag went past the given threshold. Valid to call during the MouseReleased frame.
8966// [Internal] This doesn't test if the button is pressed
8967bool ImGui::IsMouseDragPastThreshold(ImGuiMouseButton button, float lock_threshold)
8968{
8969 ImGuiContext& g = *GImGui;
8970 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
8971 if (lock_threshold < 0.0f)
8972 lock_threshold = g.IO.MouseDragThreshold;
8973 return g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold;
8974}
8975
8976bool ImGui::IsMouseDragging(ImGuiMouseButton button, float lock_threshold)
8977{
8978 ImGuiContext& g = *GImGui;
8979 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
8980 if (!g.IO.MouseDown[button])
8981 return false;
8982 return IsMouseDragPastThreshold(button, lock_threshold);
8983}
8984
8985ImVec2 ImGui::GetMousePos()
8986{
8987 ImGuiContext& g = *GImGui;
8988 return g.IO.MousePos;
8989}
8990
8991// NB: prefer to call right after BeginPopup(). At the time Selectable/MenuItem is activated, the popup is already closed!
8992ImVec2 ImGui::GetMousePosOnOpeningCurrentPopup()
8993{
8994 ImGuiContext& g = *GImGui;
8995 if (g.BeginPopupStack.Size > 0)
8996 return g.OpenPopupStack[g.BeginPopupStack.Size - 1].OpenMousePos;
8997 return g.IO.MousePos;
8998}
8999
9000// We typically use ImVec2(-FLT_MAX,-FLT_MAX) to denote an invalid mouse position.
9001bool ImGui::IsMousePosValid(const ImVec2* mouse_pos)
9002{
9003 // The assert is only to silence a false-positive in XCode Static Analysis.
9004 // 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).
9005 IM_ASSERT(GImGui != NULL);
9006 const float MOUSE_INVALID = -256000.0f;
9007 ImVec2 p = mouse_pos ? *mouse_pos : GImGui->IO.MousePos;
9008 return p.x >= MOUSE_INVALID && p.y >= MOUSE_INVALID;
9009}
9010
9011// [WILL OBSOLETE] This was designed for backends, but prefer having backend maintain a mask of held mouse buttons, because upcoming input queue system will make this invalid.
9012bool ImGui::IsAnyMouseDown()
9013{
9014 ImGuiContext& g = *GImGui;
9015 for (int n = 0; n < IM_ARRAYSIZE(g.IO.MouseDown); n++)
9016 if (g.IO.MouseDown[n])
9017 return true;
9018 return false;
9019}
9020
9021// Return the delta from the initial clicking position while the mouse button is clicked or was just released.
9022// This is locked and return 0.0f until the mouse moves past a distance threshold at least once.
9023// NB: This is only valid if IsMousePosValid(). backends in theory should always keep mouse position valid when dragging even outside the client window.
9024ImVec2 ImGui::GetMouseDragDelta(ImGuiMouseButton button, float lock_threshold)
9025{
9026 ImGuiContext& g = *GImGui;
9027 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
9028 if (lock_threshold < 0.0f)
9029 lock_threshold = g.IO.MouseDragThreshold;
9030 if (g.IO.MouseDown[button] || g.IO.MouseReleased[button])
9031 if (g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold)
9032 if (IsMousePosValid(mouse_pos: &g.IO.MousePos) && IsMousePosValid(mouse_pos: &g.IO.MouseClickedPos[button]))
9033 return g.IO.MousePos - g.IO.MouseClickedPos[button];
9034 return ImVec2(0.0f, 0.0f);
9035}
9036
9037void ImGui::ResetMouseDragDelta(ImGuiMouseButton button)
9038{
9039 ImGuiContext& g = *GImGui;
9040 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
9041 // NB: We don't need to reset g.IO.MouseDragMaxDistanceSqr
9042 g.IO.MouseClickedPos[button] = g.IO.MousePos;
9043}
9044
9045// Get desired mouse cursor shape.
9046// Important: this is meant to be used by a platform backend, it is reset in ImGui::NewFrame(),
9047// updated during the frame, and locked in EndFrame()/Render().
9048// If you use software rendering by setting io.MouseDrawCursor then Dear ImGui will render those for you
9049ImGuiMouseCursor ImGui::GetMouseCursor()
9050{
9051 ImGuiContext& g = *GImGui;
9052 return g.MouseCursor;
9053}
9054
9055void ImGui::SetMouseCursor(ImGuiMouseCursor cursor_type)
9056{
9057 ImGuiContext& g = *GImGui;
9058 g.MouseCursor = cursor_type;
9059}
9060
9061void ImGui::SetNextFrameWantCaptureKeyboard(bool want_capture_keyboard)
9062{
9063 ImGuiContext& g = *GImGui;
9064 g.WantCaptureKeyboardNextFrame = want_capture_keyboard ? 1 : 0;
9065}
9066
9067void ImGui::SetNextFrameWantCaptureMouse(bool want_capture_mouse)
9068{
9069 ImGuiContext& g = *GImGui;
9070 g.WantCaptureMouseNextFrame = want_capture_mouse ? 1 : 0;
9071}
9072
9073#ifndef IMGUI_DISABLE_DEBUG_TOOLS
9074static const char* GetInputSourceName(ImGuiInputSource source)
9075{
9076 const char* input_source_names[] = { "None", "Mouse", "Keyboard", "Gamepad", "Nav", "Clipboard" };
9077 IM_ASSERT(IM_ARRAYSIZE(input_source_names) == ImGuiInputSource_COUNT && source >= 0 && source < ImGuiInputSource_COUNT);
9078 return input_source_names[source];
9079}
9080static void DebugPrintInputEvent(const char* prefix, const ImGuiInputEvent* e)
9081{
9082 ImGuiContext& g = *GImGui;
9083 if (e->Type == ImGuiInputEventType_MousePos) { if (e->MousePos.PosX == -FLT_MAX && e->MousePos.PosY == -FLT_MAX) IMGUI_DEBUG_LOG_IO("%s: MousePos (-FLT_MAX, -FLT_MAX)\n", prefix); else IMGUI_DEBUG_LOG_IO("%s: MousePos (%.1f, %.1f)\n", prefix, e->MousePos.PosX, e->MousePos.PosY); return; }
9084 if (e->Type == ImGuiInputEventType_MouseButton) { IMGUI_DEBUG_LOG_IO("%s: MouseButton %d %s\n", prefix, e->MouseButton.Button, e->MouseButton.Down ? "Down" : "Up"); return; }
9085 if (e->Type == ImGuiInputEventType_MouseWheel) { IMGUI_DEBUG_LOG_IO("%s: MouseWheel (%.3f, %.3f)\n", prefix, e->MouseWheel.WheelX, e->MouseWheel.WheelY); return; }
9086 if (e->Type == ImGuiInputEventType_MouseViewport){IMGUI_DEBUG_LOG_IO("%s: MouseViewport (0x%08X)\n", prefix, e->MouseViewport.HoveredViewportID); return; }
9087 if (e->Type == ImGuiInputEventType_Key) { IMGUI_DEBUG_LOG_IO("%s: Key \"%s\" %s\n", prefix, ImGui::GetKeyName(e->Key.Key), e->Key.Down ? "Down" : "Up"); return; }
9088 if (e->Type == ImGuiInputEventType_Text) { IMGUI_DEBUG_LOG_IO("%s: Text: %c (U+%08X)\n", prefix, e->Text.Char, e->Text.Char); return; }
9089 if (e->Type == ImGuiInputEventType_Focus) { IMGUI_DEBUG_LOG_IO("%s: AppFocused %d\n", prefix, e->AppFocused.Focused); return; }
9090}
9091#endif
9092
9093// Process input queue
9094// We always call this with the value of 'bool g.IO.ConfigInputTrickleEventQueue'.
9095// - trickle_fast_inputs = false : process all events, turn into flattened input state (e.g. successive down/up/down/up will be lost)
9096// - trickle_fast_inputs = true : process as many events as possible (successive down/up/down/up will be trickled over several frames so nothing is lost) (new feature in 1.87)
9097void ImGui::UpdateInputEvents(bool trickle_fast_inputs)
9098{
9099 ImGuiContext& g = *GImGui;
9100 ImGuiIO& io = g.IO;
9101
9102 // Only trickle chars<>key when working with InputText()
9103 // FIXME: InputText() could parse event trail?
9104 // FIXME: Could specialize chars<>keys trickling rules for control keys (those not typically associated to characters)
9105 const bool trickle_interleaved_keys_and_text = (trickle_fast_inputs && g.WantTextInputNextFrame == 1);
9106
9107 bool mouse_moved = false, mouse_wheeled = false, key_changed = false, text_inputted = false;
9108 int mouse_button_changed = 0x00;
9109 ImBitArray<ImGuiKey_KeysData_SIZE> key_changed_mask;
9110
9111 int event_n = 0;
9112 for (; event_n < g.InputEventsQueue.Size; event_n++)
9113 {
9114 ImGuiInputEvent* e = &g.InputEventsQueue[event_n];
9115 if (e->Type == ImGuiInputEventType_MousePos)
9116 {
9117 // Trickling Rule: Stop processing queued events if we already handled a mouse button change
9118 ImVec2 event_pos(e->MousePos.PosX, e->MousePos.PosY);
9119 if (trickle_fast_inputs && (mouse_button_changed != 0 || mouse_wheeled || key_changed || text_inputted))
9120 break;
9121 io.MousePos = event_pos;
9122 mouse_moved = true;
9123 }
9124 else if (e->Type == ImGuiInputEventType_MouseButton)
9125 {
9126 // Trickling Rule: Stop processing queued events if we got multiple action on the same button
9127 const ImGuiMouseButton button = e->MouseButton.Button;
9128 IM_ASSERT(button >= 0 && button < ImGuiMouseButton_COUNT);
9129 if (trickle_fast_inputs && ((mouse_button_changed & (1 << button)) || mouse_wheeled))
9130 break;
9131 io.MouseDown[button] = e->MouseButton.Down;
9132 mouse_button_changed |= (1 << button);
9133 }
9134 else if (e->Type == ImGuiInputEventType_MouseWheel)
9135 {
9136 // Trickling Rule: Stop processing queued events if we got multiple action on the event
9137 if (trickle_fast_inputs && (mouse_moved || mouse_button_changed != 0))
9138 break;
9139 io.MouseWheelH += e->MouseWheel.WheelX;
9140 io.MouseWheel += e->MouseWheel.WheelY;
9141 mouse_wheeled = true;
9142 }
9143 else if (e->Type == ImGuiInputEventType_MouseViewport)
9144 {
9145 io.MouseHoveredViewport = e->MouseViewport.HoveredViewportID;
9146 }
9147 else if (e->Type == ImGuiInputEventType_Key)
9148 {
9149 // Trickling Rule: Stop processing queued events if we got multiple action on the same button
9150 ImGuiKey key = e->Key.Key;
9151 IM_ASSERT(key != ImGuiKey_None);
9152 ImGuiKeyData* key_data = GetKeyData(key);
9153 const int key_data_index = (int)(key_data - g.IO.KeysData);
9154 if (trickle_fast_inputs && key_data->Down != e->Key.Down && (key_changed_mask.TestBit(n: key_data_index) || text_inputted || mouse_button_changed != 0))
9155 break;
9156 key_data->Down = e->Key.Down;
9157 key_data->AnalogValue = e->Key.AnalogValue;
9158 key_changed = true;
9159 key_changed_mask.SetBit(key_data_index);
9160
9161 // Allow legacy code using io.KeysDown[GetKeyIndex()] with new backends
9162#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO
9163 io.KeysDown[key_data_index] = key_data->Down;
9164 if (io.KeyMap[key_data_index] != -1)
9165 io.KeysDown[io.KeyMap[key_data_index]] = key_data->Down;
9166#endif
9167 }
9168 else if (e->Type == ImGuiInputEventType_Text)
9169 {
9170 // Trickling Rule: Stop processing queued events if keys/mouse have been interacted with
9171 if (trickle_fast_inputs && ((key_changed && trickle_interleaved_keys_and_text) || mouse_button_changed != 0 || mouse_moved || mouse_wheeled))
9172 break;
9173 unsigned int c = e->Text.Char;
9174 io.InputQueueCharacters.push_back(v: c <= IM_UNICODE_CODEPOINT_MAX ? (ImWchar)c : IM_UNICODE_CODEPOINT_INVALID);
9175 if (trickle_interleaved_keys_and_text)
9176 text_inputted = true;
9177 }
9178 else if (e->Type == ImGuiInputEventType_Focus)
9179 {
9180 // We intentionally overwrite this and process in NewFrame(), in order to give a chance
9181 // to multi-viewports backends to queue AddFocusEvent(false) + AddFocusEvent(true) in same frame.
9182 const bool focus_lost = !e->AppFocused.Focused;
9183 io.AppFocusLost = focus_lost;
9184 }
9185 else
9186 {
9187 IM_ASSERT(0 && "Unknown event!");
9188 }
9189 }
9190
9191 // Record trail (for domain-specific applications wanting to access a precise trail)
9192 //if (event_n != 0) IMGUI_DEBUG_LOG_IO("Processed: %d / Remaining: %d\n", event_n, g.InputEventsQueue.Size - event_n);
9193 for (int n = 0; n < event_n; n++)
9194 g.InputEventsTrail.push_back(v: g.InputEventsQueue[n]);
9195
9196 // [DEBUG]
9197#ifndef IMGUI_DISABLE_DEBUG_TOOLS
9198 if (event_n != 0 && (g.DebugLogFlags & ImGuiDebugLogFlags_EventIO))
9199 for (int n = 0; n < g.InputEventsQueue.Size; n++)
9200 DebugPrintInputEvent(prefix: n < event_n ? "Processed" : "Remaining", e: &g.InputEventsQueue[n]);
9201#endif
9202
9203 // Remaining events will be processed on the next frame
9204 if (event_n == g.InputEventsQueue.Size)
9205 g.InputEventsQueue.resize(new_size: 0);
9206 else
9207 g.InputEventsQueue.erase(it: g.InputEventsQueue.Data, it_last: g.InputEventsQueue.Data + event_n);
9208
9209 // Clear buttons state when focus is lost
9210 // - this is useful so e.g. releasing Alt after focus loss on Alt-Tab doesn't trigger the Alt menu toggle.
9211 // - we clear in EndFrame() and not now in order allow application/user code polling this flag
9212 // (e.g. custom backend may want to clear additional data, custom widgets may want to react with a "canceling" event).
9213 if (g.IO.AppFocusLost)
9214 g.IO.ClearInputKeys();
9215}
9216
9217ImGuiID ImGui::GetKeyOwner(ImGuiKey key)
9218{
9219 if (!IsNamedKeyOrModKey(key))
9220 return ImGuiKeyOwner_None;
9221
9222 ImGuiContext& g = *GImGui;
9223 ImGuiKeyOwnerData* owner_data = GetKeyOwnerData(key);
9224 ImGuiID owner_id = owner_data->OwnerCurr;
9225
9226 if (g.ActiveIdUsingAllKeyboardKeys && owner_id != g.ActiveId && owner_id != ImGuiKeyOwner_Any)
9227 if (key >= ImGuiKey_Keyboard_BEGIN && key < ImGuiKey_Keyboard_END)
9228 return ImGuiKeyOwner_None;
9229
9230 return owner_id;
9231}
9232
9233// TestKeyOwner(..., ID) : (owner == None || owner == ID)
9234// TestKeyOwner(..., None) : (owner == None)
9235// TestKeyOwner(..., Any) : no owner test
9236// All paths are also testing for key not being locked, for the rare cases that key have been locked with using ImGuiInputFlags_LockXXX flags.
9237bool ImGui::TestKeyOwner(ImGuiKey key, ImGuiID owner_id)
9238{
9239 if (!IsNamedKeyOrModKey(key))
9240 return true;
9241
9242 ImGuiContext& g = *GImGui;
9243 if (g.ActiveIdUsingAllKeyboardKeys && owner_id != g.ActiveId && owner_id != ImGuiKeyOwner_Any)
9244 if (key >= ImGuiKey_Keyboard_BEGIN && key < ImGuiKey_Keyboard_END)
9245 return false;
9246
9247 ImGuiKeyOwnerData* owner_data = GetKeyOwnerData(key);
9248 if (owner_id == ImGuiKeyOwner_Any)
9249 return (owner_data->LockThisFrame == false);
9250
9251 // Note: SetKeyOwner() sets OwnerCurr. It is not strictly required for most mouse routing overlap (because of ActiveId/HoveredId
9252 // are acting as filter before this has a chance to filter), but sane as soon as user tries to look into things.
9253 // Setting OwnerCurr in SetKeyOwner() is more consistent than testing OwnerNext here: would be inconsistent with getter and other functions.
9254 if (owner_data->OwnerCurr != owner_id)
9255 {
9256 if (owner_data->LockThisFrame)
9257 return false;
9258 if (owner_data->OwnerCurr != ImGuiKeyOwner_None)
9259 return false;
9260 }
9261
9262 return true;
9263}
9264
9265// _LockXXX flags are useful to lock keys away from code which is not input-owner aware.
9266// When using _LockXXX flags, you can use ImGuiKeyOwner_Any to lock keys from everyone.
9267// - SetKeyOwner(..., None) : clears owner
9268// - SetKeyOwner(..., Any, !Lock) : illegal (assert)
9269// - SetKeyOwner(..., Any or None, Lock) : set lock
9270void ImGui::SetKeyOwner(ImGuiKey key, ImGuiID owner_id, ImGuiInputFlags flags)
9271{
9272 IM_ASSERT(IsNamedKeyOrModKey(key) && (owner_id != ImGuiKeyOwner_Any || (flags & (ImGuiInputFlags_LockThisFrame | ImGuiInputFlags_LockUntilRelease)))); // Can only use _Any with _LockXXX flags (to eat a key away without an ID to retrieve it)
9273 IM_ASSERT((flags & ~ImGuiInputFlags_SupportedBySetKeyOwner) == 0); // Passing flags not supported by this function!
9274
9275 ImGuiKeyOwnerData* owner_data = GetKeyOwnerData(key);
9276 owner_data->OwnerCurr = owner_data->OwnerNext = owner_id;
9277
9278 // We cannot lock by default as it would likely break lots of legacy code.
9279 // In the case of using LockUntilRelease while key is not down we still lock during the frame (no key_data->Down test)
9280 owner_data->LockUntilRelease = (flags & ImGuiInputFlags_LockUntilRelease) != 0;
9281 owner_data->LockThisFrame = (flags & ImGuiInputFlags_LockThisFrame) != 0 || (owner_data->LockUntilRelease);
9282}
9283
9284// This is more or less equivalent to:
9285// if (IsItemHovered() || IsItemActive())
9286// SetKeyOwner(key, GetItemID());
9287// Extensive uses of that (e.g. many calls for a single item) may want to manually perform the tests once and then call SetKeyOwner() multiple times.
9288// More advanced usage scenarios may want to call SetKeyOwner() manually based on different condition.
9289// Worth noting is that only one item can be hovered and only one item can be active, therefore this usage pattern doesn't need to bother with routing and priority.
9290void ImGui::SetItemKeyOwner(ImGuiKey key, ImGuiInputFlags flags)
9291{
9292 ImGuiContext& g = *GImGui;
9293 ImGuiID id = g.LastItemData.ID;
9294 if (id == 0 || (g.HoveredId != id && g.ActiveId != id))
9295 return;
9296 if ((flags & ImGuiInputFlags_CondMask_) == 0)
9297 flags |= ImGuiInputFlags_CondDefault_;
9298 if ((g.HoveredId == id && (flags & ImGuiInputFlags_CondHovered)) || (g.ActiveId == id && (flags & ImGuiInputFlags_CondActive)))
9299 {
9300 IM_ASSERT((flags & ~ImGuiInputFlags_SupportedBySetItemKeyOwner) == 0); // Passing flags not supported by this function!
9301 SetKeyOwner(key, owner_id: id, flags: flags & ~ImGuiInputFlags_CondMask_);
9302 }
9303}
9304
9305// - Need to decide how to handle shortcut translations for Non-Mac <> Mac
9306// - Ideas: https://github.com/ocornut/imgui/issues/456#issuecomment-264390864
9307bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags flags)
9308{
9309 ImGuiContext& g = *GImGui;
9310
9311 // When using (owner_id == 0/Any): SetShortcutRouting() will use CurrentFocusScopeId and filter with this, so IsKeyPressed() is fine with he 0/Any.
9312 if ((flags & ImGuiInputFlags_RouteMask_) == 0)
9313 flags |= ImGuiInputFlags_RouteFocused;
9314 if (!SetShortcutRouting(key_chord, owner_id, flags))
9315 return false;
9316
9317 if (key_chord & ImGuiMod_Shortcut)
9318 key_chord = ConvertShortcutMod(key_chord);
9319 ImGuiKey mods = (ImGuiKey)(key_chord & ImGuiMod_Mask_);
9320 if (g.IO.KeyMods != mods)
9321 return false;
9322
9323 // Special storage location for mods
9324 ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_);
9325 if (key == ImGuiKey_None)
9326 key = ConvertSingleModFlagToKey(key: mods);
9327
9328 if (!IsKeyPressed(key, owner_id, flags: (flags & (ImGuiInputFlags_Repeat | ImGuiInputFlags_RepeatRateMask_))))
9329 return false;
9330 IM_ASSERT((flags & ~ImGuiInputFlags_SupportedByShortcut) == 0); // Passing flags not supported by this function!
9331
9332 return true;
9333}
9334
9335
9336//-----------------------------------------------------------------------------
9337// [SECTION] ERROR CHECKING
9338//-----------------------------------------------------------------------------
9339
9340// Helper function to verify ABI compatibility between caller code and compiled version of Dear ImGui.
9341// Verify that the type sizes are matching between the calling file's compilation unit and imgui.cpp's compilation unit
9342// If this triggers you have an issue:
9343// - Most commonly: mismatched headers and compiled code version.
9344// - Or: mismatched configuration #define, compilation settings, packing pragma etc.
9345// The configuration settings mentioned in imconfig.h must be set for all compilation units involved with Dear ImGui,
9346// which is way it is required you put them in your imconfig file (and not just before including imgui.h).
9347// Otherwise it is possible that different compilation units would see different structure layout
9348bool 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)
9349{
9350 bool error = false;
9351 if (strcmp(s1: version, IMGUI_VERSION) != 0) { error = true; IM_ASSERT(strcmp(version, IMGUI_VERSION) == 0 && "Mismatched version string!"); }
9352 if (sz_io != sizeof(ImGuiIO)) { error = true; IM_ASSERT(sz_io == sizeof(ImGuiIO) && "Mismatched struct layout!"); }
9353 if (sz_style != sizeof(ImGuiStyle)) { error = true; IM_ASSERT(sz_style == sizeof(ImGuiStyle) && "Mismatched struct layout!"); }
9354 if (sz_vec2 != sizeof(ImVec2)) { error = true; IM_ASSERT(sz_vec2 == sizeof(ImVec2) && "Mismatched struct layout!"); }
9355 if (sz_vec4 != sizeof(ImVec4)) { error = true; IM_ASSERT(sz_vec4 == sizeof(ImVec4) && "Mismatched struct layout!"); }
9356 if (sz_vert != sizeof(ImDrawVert)) { error = true; IM_ASSERT(sz_vert == sizeof(ImDrawVert) && "Mismatched struct layout!"); }
9357 if (sz_idx != sizeof(ImDrawIdx)) { error = true; IM_ASSERT(sz_idx == sizeof(ImDrawIdx) && "Mismatched struct layout!"); }
9358 return !error;
9359}
9360
9361// Until 1.89 (IMGUI_VERSION_NUM < 18814) it was legal to use SetCursorPos() to extend the boundary of a parent (e.g. window or table cell)
9362// This is causing issues and ambiguity and we need to retire that.
9363// See https://github.com/ocornut/imgui/issues/5548 for more details.
9364// [Scenario 1]
9365// Previously this would make the window content size ~200x200:
9366// Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + End(); // NOT OK
9367// Instead, please submit an item:
9368// Begin(...) + SetCursorScreenPos(GetCursorScreenPos() + ImVec2(200,200)) + Dummy(ImVec2(0,0)) + End(); // OK
9369// Alternative:
9370// Begin(...) + Dummy(ImVec2(200,200)) + End(); // OK
9371// [Scenario 2]
9372// For reference this is one of the issue what we aim to fix with this change:
9373// BeginGroup() + SomeItem("foobar") + SetCursorScreenPos(GetCursorScreenPos()) + EndGroup()
9374// The previous logic made SetCursorScreenPos(GetCursorScreenPos()) have a side-effect! It would erroneously incorporate ItemSpacing.y after the item into content size, making the group taller!
9375// While this code is a little twisted, no-one would expect SetXXX(GetXXX()) to have a side-effect. Using vertical alignment patterns could trigger this issue.
9376void ImGui::ErrorCheckUsingSetCursorPosToExtendParentBoundaries()
9377{
9378 ImGuiContext& g = *GImGui;
9379 ImGuiWindow* window = g.CurrentWindow;
9380 IM_ASSERT(window->DC.IsSetPos);
9381 window->DC.IsSetPos = false;
9382#ifdef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
9383 if (window->DC.CursorPos.x <= window->DC.CursorMaxPos.x && window->DC.CursorPos.y <= window->DC.CursorMaxPos.y)
9384 return;
9385 if (window->SkipItems)
9386 return;
9387 IM_ASSERT(0 && "Code uses SetCursorPos()/SetCursorScreenPos() to extend window/parent boundaries. Please submit an item e.g. Dummy() to validate extent.");
9388#else
9389 window->DC.CursorMaxPos = ImMax(lhs: window->DC.CursorMaxPos, rhs: window->DC.CursorPos);
9390#endif
9391}
9392
9393static void ImGui::ErrorCheckNewFrameSanityChecks()
9394{
9395 ImGuiContext& g = *GImGui;
9396
9397 // Check user IM_ASSERT macro
9398 // (IF YOU GET A WARNING OR COMPILE ERROR HERE: it means your assert macro is incorrectly defined!
9399 // If your macro uses multiple statements, it NEEDS to be surrounded by a 'do { ... } while (0)' block.
9400 // This is a common C/C++ idiom to allow multiple statements macros to be used in control flow blocks.)
9401 // #define IM_ASSERT(EXPR) if (SomeCode(EXPR)) SomeMoreCode(); // Wrong!
9402 // #define IM_ASSERT(EXPR) do { if (SomeCode(EXPR)) SomeMoreCode(); } while (0) // Correct!
9403 if (true) IM_ASSERT(1); else IM_ASSERT(0);
9404
9405 // Check user data
9406 // (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)
9407 IM_ASSERT(g.Initialized);
9408 IM_ASSERT((g.IO.DeltaTime > 0.0f || g.FrameCount == 0) && "Need a positive DeltaTime!");
9409 IM_ASSERT((g.FrameCount == 0 || g.FrameCountEnded == g.FrameCount) && "Forgot to call Render() or EndFrame() at the end of the previous frame?");
9410 IM_ASSERT(g.IO.DisplaySize.x >= 0.0f && g.IO.DisplaySize.y >= 0.0f && "Invalid DisplaySize value!");
9411 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()");
9412 IM_ASSERT(g.Style.CurveTessellationTol > 0.0f && "Invalid style setting!");
9413 IM_ASSERT(g.Style.CircleTessellationMaxError > 0.0f && "Invalid style setting!");
9414 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
9415 IM_ASSERT(g.Style.WindowMinSize.x >= 1.0f && g.Style.WindowMinSize.y >= 1.0f && "Invalid style setting.");
9416 IM_ASSERT(g.Style.WindowMenuButtonPosition == ImGuiDir_None || g.Style.WindowMenuButtonPosition == ImGuiDir_Left || g.Style.WindowMenuButtonPosition == ImGuiDir_Right);
9417 IM_ASSERT(g.Style.ColorButtonPosition == ImGuiDir_Left || g.Style.ColorButtonPosition == ImGuiDir_Right);
9418#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO
9419 for (int n = ImGuiKey_NamedKey_BEGIN; n < ImGuiKey_COUNT; n++)
9420 IM_ASSERT(g.IO.KeyMap[n] >= -1 && g.IO.KeyMap[n] < ImGuiKey_LegacyNativeKey_END && "io.KeyMap[] contains an out of bound value (need to be 0..511, or -1 for unmapped key)");
9421
9422 // 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)
9423 if ((g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) && g.IO.BackendUsingLegacyKeyArrays == 1)
9424 IM_ASSERT(g.IO.KeyMap[ImGuiKey_Space] != -1 && "ImGuiKey_Space is not mapped, required for keyboard navigation.");
9425#endif
9426
9427 // Check: the io.ConfigWindowsResizeFromEdges option requires backend to honor mouse cursor changes and set the ImGuiBackendFlags_HasMouseCursors flag accordingly.
9428 if (g.IO.ConfigWindowsResizeFromEdges && !(g.IO.BackendFlags & ImGuiBackendFlags_HasMouseCursors))
9429 g.IO.ConfigWindowsResizeFromEdges = false;
9430
9431 // 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.
9432 if (g.FrameCount == 1 && (g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable) && (g.ConfigFlagsLastFrame & ImGuiConfigFlags_DockingEnable) == 0)
9433 IM_ASSERT(0 && "Please set DockingEnable before the first call to NewFrame()! Otherwise you will lose your .ini settings!");
9434 if (g.FrameCount == 1 && (g.IO.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) && (g.ConfigFlagsLastFrame & ImGuiConfigFlags_ViewportsEnable) == 0)
9435 IM_ASSERT(0 && "Please set ViewportsEnable before the first call to NewFrame()! Otherwise you will lose your .ini settings!");
9436
9437 // Perform simple checks: multi-viewport and platform windows support
9438 if (g.IO.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
9439 {
9440 if ((g.IO.BackendFlags & ImGuiBackendFlags_PlatformHasViewports) && (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasViewports))
9441 {
9442 IM_ASSERT((g.FrameCount == 0 || g.FrameCount == g.FrameCountPlatformEnded) && "Forgot to call UpdatePlatformWindows() in main loop after EndFrame()? Check examples/ applications for reference.");
9443 IM_ASSERT(g.PlatformIO.Platform_CreateWindow != NULL && "Platform init didn't install handlers?");
9444 IM_ASSERT(g.PlatformIO.Platform_DestroyWindow != NULL && "Platform init didn't install handlers?");
9445 IM_ASSERT(g.PlatformIO.Platform_GetWindowPos != NULL && "Platform init didn't install handlers?");
9446 IM_ASSERT(g.PlatformIO.Platform_SetWindowPos != NULL && "Platform init didn't install handlers?");
9447 IM_ASSERT(g.PlatformIO.Platform_GetWindowSize != NULL && "Platform init didn't install handlers?");
9448 IM_ASSERT(g.PlatformIO.Platform_SetWindowSize != NULL && "Platform init didn't install handlers?");
9449 IM_ASSERT(g.PlatformIO.Monitors.Size > 0 && "Platform init didn't setup Monitors list?");
9450 IM_ASSERT((g.Viewports[0]->PlatformUserData != NULL || g.Viewports[0]->PlatformHandle != NULL) && "Platform init didn't setup main viewport.");
9451 if (g.IO.ConfigDockingTransparentPayload && (g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable))
9452 IM_ASSERT(g.PlatformIO.Platform_SetWindowAlpha != NULL && "Platform_SetWindowAlpha handler is required to use io.ConfigDockingTransparent!");
9453 }
9454 else
9455 {
9456 // Disable feature, our backends do not support it
9457 g.IO.ConfigFlags &= ~ImGuiConfigFlags_ViewportsEnable;
9458 }
9459
9460 // Perform simple checks on platform monitor data + compute a total bounding box for quick early outs
9461 for (int monitor_n = 0; monitor_n < g.PlatformIO.Monitors.Size; monitor_n++)
9462 {
9463 ImGuiPlatformMonitor& mon = g.PlatformIO.Monitors[monitor_n];
9464 IM_UNUSED(mon);
9465 IM_ASSERT(mon.MainSize.x > 0.0f && mon.MainSize.y > 0.0f && "Monitor main bounds not setup properly.");
9466 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.");
9467 IM_ASSERT(mon.DpiScale != 0.0f);
9468 }
9469 }
9470}
9471
9472static void ImGui::ErrorCheckEndFrameSanityChecks()
9473{
9474 ImGuiContext& g = *GImGui;
9475
9476 // Verify that io.KeyXXX fields haven't been tampered with. Key mods should not be modified between NewFrame() and EndFrame()
9477 // One possible reason leading to this assert is that your backends update inputs _AFTER_ NewFrame().
9478 // It is known that when some modal native windows called mid-frame takes focus away, some backends such as GLFW will
9479 // send key release events mid-frame. This would normally trigger this assertion and lead to sheared inputs.
9480 // We silently accommodate for this case by ignoring the case where all io.KeyXXX modifiers were released (aka key_mod_flags == 0),
9481 // while still correctly asserting on mid-frame key press events.
9482 const ImGuiKeyChord key_mods = GetMergedModsFromKeys();
9483 IM_ASSERT((key_mods == 0 || g.IO.KeyMods == key_mods) && "Mismatching io.KeyCtrl/io.KeyShift/io.KeyAlt/io.KeySuper vs io.KeyMods");
9484 IM_UNUSED(key_mods);
9485
9486 // [EXPERIMENTAL] Recover from errors: You may call this yourself before EndFrame().
9487 //ErrorCheckEndFrameRecover();
9488
9489 // Report when there is a mismatch of Begin/BeginChild vs End/EndChild calls. Important: Remember that the Begin/BeginChild API requires you
9490 // to always call End/EndChild even if Begin/BeginChild returns false! (this is unfortunately inconsistent with most other Begin* API).
9491 if (g.CurrentWindowStack.Size != 1)
9492 {
9493 if (g.CurrentWindowStack.Size > 1)
9494 {
9495 IM_ASSERT_USER_ERROR(g.CurrentWindowStack.Size == 1, "Mismatched Begin/BeginChild vs End/EndChild calls: did you forget to call End/EndChild?");
9496 while (g.CurrentWindowStack.Size > 1)
9497 End();
9498 }
9499 else
9500 {
9501 IM_ASSERT_USER_ERROR(g.CurrentWindowStack.Size == 1, "Mismatched Begin/BeginChild vs End/EndChild calls: did you call End/EndChild too much?");
9502 }
9503 }
9504
9505 IM_ASSERT_USER_ERROR(g.GroupStack.Size == 0, "Missing EndGroup call!");
9506}
9507
9508// Experimental recovery from incorrect usage of BeginXXX/EndXXX/PushXXX/PopXXX calls.
9509// Must be called during or before EndFrame().
9510// This is generally flawed as we are not necessarily End/Popping things in the right order.
9511// FIXME: Can't recover from inside BeginTabItem/EndTabItem yet.
9512// FIXME: Can't recover from interleaved BeginTabBar/Begin
9513void ImGui::ErrorCheckEndFrameRecover(ImGuiErrorLogCallback log_callback, void* user_data)
9514{
9515 // PVS-Studio V1044 is "Loop break conditions do not depend on the number of iterations"
9516 ImGuiContext& g = *GImGui;
9517 while (g.CurrentWindowStack.Size > 0) //-V1044
9518 {
9519 ErrorCheckEndWindowRecover(log_callback, user_data);
9520 ImGuiWindow* window = g.CurrentWindow;
9521 if (g.CurrentWindowStack.Size == 1)
9522 {
9523 IM_ASSERT(window->IsFallbackWindow);
9524 break;
9525 }
9526 if (window->Flags & ImGuiWindowFlags_ChildWindow)
9527 {
9528 if (log_callback) log_callback(user_data, "Recovered from missing EndChild() for '%s'", window->Name);
9529 EndChild();
9530 }
9531 else
9532 {
9533 if (log_callback) log_callback(user_data, "Recovered from missing End() for '%s'", window->Name);
9534 End();
9535 }
9536 }
9537}
9538
9539// Must be called before End()/EndChild()
9540void ImGui::ErrorCheckEndWindowRecover(ImGuiErrorLogCallback log_callback, void* user_data)
9541{
9542 ImGuiContext& g = *GImGui;
9543 while (g.CurrentTable && (g.CurrentTable->OuterWindow == g.CurrentWindow || g.CurrentTable->InnerWindow == g.CurrentWindow))
9544 {
9545 if (log_callback) log_callback(user_data, "Recovered from missing EndTable() in '%s'", g.CurrentTable->OuterWindow->Name);
9546 EndTable();
9547 }
9548
9549 ImGuiWindow* window = g.CurrentWindow;
9550 ImGuiStackSizes* stack_sizes = &g.CurrentWindowStack.back().StackSizesOnBegin;
9551 IM_ASSERT(window != NULL);
9552 while (g.CurrentTabBar != NULL) //-V1044
9553 {
9554 if (log_callback) log_callback(user_data, "Recovered from missing EndTabBar() in '%s'", window->Name);
9555 EndTabBar();
9556 }
9557 while (window->DC.TreeDepth > 0)
9558 {
9559 if (log_callback) log_callback(user_data, "Recovered from missing TreePop() in '%s'", window->Name);
9560 TreePop();
9561 }
9562 while (g.GroupStack.Size > stack_sizes->SizeOfGroupStack) //-V1044
9563 {
9564 if (log_callback) log_callback(user_data, "Recovered from missing EndGroup() in '%s'", window->Name);
9565 EndGroup();
9566 }
9567 while (window->IDStack.Size > 1)
9568 {
9569 if (log_callback) log_callback(user_data, "Recovered from missing PopID() in '%s'", window->Name);
9570 PopID();
9571 }
9572 while (g.DisabledStackSize > stack_sizes->SizeOfDisabledStack) //-V1044
9573 {
9574 if (log_callback) log_callback(user_data, "Recovered from missing EndDisabled() in '%s'", window->Name);
9575 EndDisabled();
9576 }
9577 while (g.ColorStack.Size > stack_sizes->SizeOfColorStack)
9578 {
9579 if (log_callback) log_callback(user_data, "Recovered from missing PopStyleColor() in '%s' for ImGuiCol_%s", window->Name, GetStyleColorName(idx: g.ColorStack.back().Col));
9580 PopStyleColor();
9581 }
9582 while (g.ItemFlagsStack.Size > stack_sizes->SizeOfItemFlagsStack) //-V1044
9583 {
9584 if (log_callback) log_callback(user_data, "Recovered from missing PopItemFlag() in '%s'", window->Name);
9585 PopItemFlag();
9586 }
9587 while (g.StyleVarStack.Size > stack_sizes->SizeOfStyleVarStack) //-V1044
9588 {
9589 if (log_callback) log_callback(user_data, "Recovered from missing PopStyleVar() in '%s'", window->Name);
9590 PopStyleVar();
9591 }
9592 while (g.FocusScopeStack.Size > stack_sizes->SizeOfFocusScopeStack + 1) //-V1044
9593 {
9594 if (log_callback) log_callback(user_data, "Recovered from missing PopFocusScope() in '%s'", window->Name);
9595 PopFocusScope();
9596 }
9597}
9598
9599// Save current stack sizes for later compare
9600void ImGuiStackSizes::SetToCurrentState()
9601{
9602 ImGuiContext& g = *GImGui;
9603 ImGuiWindow* window = g.CurrentWindow;
9604 SizeOfIDStack = (short)window->IDStack.Size;
9605 SizeOfColorStack = (short)g.ColorStack.Size;
9606 SizeOfStyleVarStack = (short)g.StyleVarStack.Size;
9607 SizeOfFontStack = (short)g.FontStack.Size;
9608 SizeOfFocusScopeStack = (short)g.FocusScopeStack.Size;
9609 SizeOfGroupStack = (short)g.GroupStack.Size;
9610 SizeOfItemFlagsStack = (short)g.ItemFlagsStack.Size;
9611 SizeOfBeginPopupStack = (short)g.BeginPopupStack.Size;
9612 SizeOfDisabledStack = (short)g.DisabledStackSize;
9613}
9614
9615// Compare to detect usage errors
9616void ImGuiStackSizes::CompareWithCurrentState()
9617{
9618 ImGuiContext& g = *GImGui;
9619 ImGuiWindow* window = g.CurrentWindow;
9620 IM_UNUSED(window);
9621
9622 // Window stacks
9623 // NOT checking: DC.ItemWidth, DC.TextWrapPos (per window) to allow user to conveniently push once and not pop (they are cleared on Begin)
9624 IM_ASSERT(SizeOfIDStack == window->IDStack.Size && "PushID/PopID or TreeNode/TreePop Mismatch!");
9625
9626 // Global stacks
9627 // 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.
9628 IM_ASSERT(SizeOfGroupStack == g.GroupStack.Size && "BeginGroup/EndGroup Mismatch!");
9629 IM_ASSERT(SizeOfBeginPopupStack == g.BeginPopupStack.Size && "BeginPopup/EndPopup or BeginMenu/EndMenu Mismatch!");
9630 IM_ASSERT(SizeOfDisabledStack == g.DisabledStackSize && "BeginDisabled/EndDisabled Mismatch!");
9631 IM_ASSERT(SizeOfItemFlagsStack >= g.ItemFlagsStack.Size && "PushItemFlag/PopItemFlag Mismatch!");
9632 IM_ASSERT(SizeOfColorStack >= g.ColorStack.Size && "PushStyleColor/PopStyleColor Mismatch!");
9633 IM_ASSERT(SizeOfStyleVarStack >= g.StyleVarStack.Size && "PushStyleVar/PopStyleVar Mismatch!");
9634 IM_ASSERT(SizeOfFontStack >= g.FontStack.Size && "PushFont/PopFont Mismatch!");
9635 IM_ASSERT(SizeOfFocusScopeStack == g.FocusScopeStack.Size && "PushFocusScope/PopFocusScope Mismatch!");
9636}
9637
9638
9639//-----------------------------------------------------------------------------
9640// [SECTION] LAYOUT
9641//-----------------------------------------------------------------------------
9642// - ItemSize()
9643// - ItemAdd()
9644// - SameLine()
9645// - GetCursorScreenPos()
9646// - SetCursorScreenPos()
9647// - GetCursorPos(), GetCursorPosX(), GetCursorPosY()
9648// - SetCursorPos(), SetCursorPosX(), SetCursorPosY()
9649// - GetCursorStartPos()
9650// - Indent()
9651// - Unindent()
9652// - SetNextItemWidth()
9653// - PushItemWidth()
9654// - PushMultiItemsWidths()
9655// - PopItemWidth()
9656// - CalcItemWidth()
9657// - CalcItemSize()
9658// - GetTextLineHeight()
9659// - GetTextLineHeightWithSpacing()
9660// - GetFrameHeight()
9661// - GetFrameHeightWithSpacing()
9662// - GetContentRegionMax()
9663// - GetContentRegionMaxAbs() [Internal]
9664// - GetContentRegionAvail(),
9665// - GetWindowContentRegionMin(), GetWindowContentRegionMax()
9666// - BeginGroup()
9667// - EndGroup()
9668// Also see in imgui_widgets: tab bars, and in imgui_tables: tables, columns.
9669//-----------------------------------------------------------------------------
9670
9671// Advance cursor given item size for layout.
9672// Register minimum needed size so it can extend the bounding box used for auto-fit calculation.
9673// See comments in ItemAdd() about how/why the size provided to ItemSize() vs ItemAdd() may often different.
9674void ImGui::ItemSize(const ImVec2& size, float text_baseline_y)
9675{
9676 ImGuiContext& g = *GImGui;
9677 ImGuiWindow* window = g.CurrentWindow;
9678 if (window->SkipItems)
9679 return;
9680
9681 // We increase the height in this function to accommodate for baseline offset.
9682 // In theory we should be offsetting the starting position (window->DC.CursorPos), that will be the topic of a larger refactor,
9683 // but since ItemSize() is not yet an API that moves the cursor (to handle e.g. wrapping) enlarging the height has the same effect.
9684 const float offset_to_match_baseline_y = (text_baseline_y >= 0) ? ImMax(lhs: 0.0f, rhs: window->DC.CurrLineTextBaseOffset - text_baseline_y) : 0.0f;
9685
9686 const float line_y1 = window->DC.IsSameLine ? window->DC.CursorPosPrevLine.y : window->DC.CursorPos.y;
9687 const float line_height = ImMax(lhs: window->DC.CurrLineSize.y, /*ImMax(*/rhs: window->DC.CursorPos.y - line_y1/*, 0.0f)*/ + size.y + offset_to_match_baseline_y);
9688
9689 // Always align ourselves on pixel boundaries
9690 //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]
9691 window->DC.CursorPosPrevLine.x = window->DC.CursorPos.x + size.x;
9692 window->DC.CursorPosPrevLine.y = line_y1;
9693 window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); // Next line
9694 window->DC.CursorPos.y = IM_FLOOR(line_y1 + line_height + g.Style.ItemSpacing.y); // Next line
9695 window->DC.CursorMaxPos.x = ImMax(lhs: window->DC.CursorMaxPos.x, rhs: window->DC.CursorPosPrevLine.x);
9696 window->DC.CursorMaxPos.y = ImMax(lhs: window->DC.CursorMaxPos.y, rhs: window->DC.CursorPos.y - g.Style.ItemSpacing.y);
9697 //if (g.IO.KeyAlt) window->DrawList->AddCircle(window->DC.CursorMaxPos, 3.0f, IM_COL32(255,0,0,255), 4); // [DEBUG]
9698
9699 window->DC.PrevLineSize.y = line_height;
9700 window->DC.CurrLineSize.y = 0.0f;
9701 window->DC.PrevLineTextBaseOffset = ImMax(lhs: window->DC.CurrLineTextBaseOffset, rhs: text_baseline_y);
9702 window->DC.CurrLineTextBaseOffset = 0.0f;
9703 window->DC.IsSameLine = window->DC.IsSetPos = false;
9704
9705 // Horizontal layout mode
9706 if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
9707 SameLine();
9708}
9709
9710// Declare item bounding box for clipping and interaction.
9711// Note that the size can be different than the one provided to ItemSize(). Typically, widgets that spread over available surface
9712// declare their minimum size requirement to ItemSize() and provide a larger region to ItemAdd() which is used drawing/interaction.
9713bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGuiItemFlags extra_flags)
9714{
9715 ImGuiContext& g = *GImGui;
9716 ImGuiWindow* window = g.CurrentWindow;
9717
9718 // Set item data
9719 // (DisplayRect is left untouched, made valid when ImGuiItemStatusFlags_HasDisplayRect is set)
9720 g.LastItemData.ID = id;
9721 g.LastItemData.Rect = bb;
9722 g.LastItemData.NavRect = nav_bb_arg ? *nav_bb_arg : bb;
9723 g.LastItemData.InFlags = g.CurrentItemFlags | extra_flags;
9724 g.LastItemData.StatusFlags = ImGuiItemStatusFlags_None;
9725
9726 // Directional navigation processing
9727 if (id != 0)
9728 {
9729 KeepAliveID(id);
9730
9731 // Runs prior to clipping early-out
9732 // (a) So that NavInitRequest can be honored, for newly opened windows to select a default widget
9733 // (b) So that we can scroll up/down past clipped items. This adds a small O(N) cost to regular navigation requests
9734 // unfortunately, but it is still limited to one window. It may not scale very well for windows with ten of
9735 // thousands of item, but at least NavMoveRequest is only set on user interaction, aka maximum once a frame.
9736 // We could early out with "if (is_clipped && !g.NavInitRequest) return false;" but when we wouldn't be able
9737 // to reach unclipped widgets. This would work if user had explicit scrolling control (e.g. mapped on a stick).
9738 // We intentionally don't check if g.NavWindow != NULL because g.NavAnyRequest should only be set when it is non null.
9739 // If we crash on a NULL g.NavWindow we need to fix the bug elsewhere.
9740 if (!(g.LastItemData.InFlags & ImGuiItemFlags_NoNav))
9741 {
9742 window->DC.NavLayersActiveMaskNext |= (1 << window->DC.NavLayerCurrent);
9743 if (g.NavId == id || g.NavAnyRequest)
9744 if (g.NavWindow->RootWindowForNav == window->RootWindowForNav)
9745 if (window == g.NavWindow || ((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened))
9746 NavProcessItem();
9747 }
9748
9749 // [DEBUG] People keep stumbling on this problem and using "" as identifier in the root of a window instead of "##something".
9750 // Empty identifier are valid and useful in a small amount of cases, but 99.9% of the time you want to use "##something".
9751 // READ THE FAQ: https://dearimgui.org/faq
9752 IM_ASSERT(id != window->ID && "Cannot have an empty ID at the root of a window. If you need an empty label, use ## and read the FAQ about how the ID Stack works!");
9753 }
9754 g.NextItemData.Flags = ImGuiNextItemDataFlags_None;
9755
9756#ifdef IMGUI_ENABLE_TEST_ENGINE
9757 if (id != 0)
9758 IMGUI_TEST_ENGINE_ITEM_ADD(nav_bb_arg ? *nav_bb_arg : bb, id);
9759#endif
9760
9761 // Clipping test
9762 // (FIXME: This is a modified copy of IsClippedEx() so we can reuse the is_rect_visible value)
9763 //const bool is_clipped = IsClippedEx(bb, id);
9764 //if (is_clipped)
9765 // return false;
9766 const bool is_rect_visible = bb.Overlaps(r: window->ClipRect);
9767 if (!is_rect_visible)
9768 if (id == 0 || (id != g.ActiveId && id != g.NavId))
9769 if (!g.LogEnabled)
9770 return false;
9771
9772 // [DEBUG]
9773#ifndef IMGUI_DISABLE_DEBUG_TOOLS
9774 if (id != 0 && id == g.DebugLocateId)
9775 DebugLocateItemResolveWithLastItem();
9776#endif
9777 //if (g.IO.KeyAlt) window->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(255,255,0,120)); // [DEBUG]
9778
9779 // We need to calculate this now to take account of the current clipping rectangle (as items like Selectable may change them)
9780 if (is_rect_visible)
9781 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Visible;
9782 if (IsMouseHoveringRect(r_min: bb.Min, r_max: bb.Max))
9783 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredRect;
9784 return true;
9785}
9786
9787// Gets back to previous line and continue with horizontal layout
9788// offset_from_start_x == 0 : follow right after previous item
9789// offset_from_start_x != 0 : align to specified x position (relative to window/group left)
9790// spacing_w < 0 : use default spacing if pos_x == 0, no spacing if pos_x != 0
9791// spacing_w >= 0 : enforce spacing amount
9792void ImGui::SameLine(float offset_from_start_x, float spacing_w)
9793{
9794 ImGuiContext& g = *GImGui;
9795 ImGuiWindow* window = g.CurrentWindow;
9796 if (window->SkipItems)
9797 return;
9798
9799 if (offset_from_start_x != 0.0f)
9800 {
9801 if (spacing_w < 0.0f)
9802 spacing_w = 0.0f;
9803 window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + offset_from_start_x + spacing_w + window->DC.GroupOffset.x + window->DC.ColumnsOffset.x;
9804 window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y;
9805 }
9806 else
9807 {
9808 if (spacing_w < 0.0f)
9809 spacing_w = g.Style.ItemSpacing.x;
9810 window->DC.CursorPos.x = window->DC.CursorPosPrevLine.x + spacing_w;
9811 window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y;
9812 }
9813 window->DC.CurrLineSize = window->DC.PrevLineSize;
9814 window->DC.CurrLineTextBaseOffset = window->DC.PrevLineTextBaseOffset;
9815 window->DC.IsSameLine = true;
9816}
9817
9818ImVec2 ImGui::GetCursorScreenPos()
9819{
9820 ImGuiWindow* window = GetCurrentWindowRead();
9821 return window->DC.CursorPos;
9822}
9823
9824// 2022/08/05: Setting cursor position also extend boundaries (via modifying CursorMaxPos) used to compute window size, group size etc.
9825// I believe this was is a judicious choice but it's probably being relied upon (it has been the case since 1.31 and 1.50)
9826// It would be sane if we requested user to use SetCursorPos() + Dummy(ImVec2(0,0)) to extend CursorMaxPos...
9827void ImGui::SetCursorScreenPos(const ImVec2& pos)
9828{
9829 ImGuiWindow* window = GetCurrentWindow();
9830 window->DC.CursorPos = pos;
9831 //window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos);
9832 window->DC.IsSetPos = true;
9833}
9834
9835// User generally sees positions in window coordinates. Internally we store CursorPos in absolute screen coordinates because it is more convenient.
9836// 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'.
9837ImVec2 ImGui::GetCursorPos()
9838{
9839 ImGuiWindow* window = GetCurrentWindowRead();
9840 return window->DC.CursorPos - window->Pos + window->Scroll;
9841}
9842
9843float ImGui::GetCursorPosX()
9844{
9845 ImGuiWindow* window = GetCurrentWindowRead();
9846 return window->DC.CursorPos.x - window->Pos.x + window->Scroll.x;
9847}
9848
9849float ImGui::GetCursorPosY()
9850{
9851 ImGuiWindow* window = GetCurrentWindowRead();
9852 return window->DC.CursorPos.y - window->Pos.y + window->Scroll.y;
9853}
9854
9855void ImGui::SetCursorPos(const ImVec2& local_pos)
9856{
9857 ImGuiWindow* window = GetCurrentWindow();
9858 window->DC.CursorPos = window->Pos - window->Scroll + local_pos;
9859 //window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos);
9860 window->DC.IsSetPos = true;
9861}
9862
9863void ImGui::SetCursorPosX(float x)
9864{
9865 ImGuiWindow* window = GetCurrentWindow();
9866 window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + x;
9867 //window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPos.x);
9868 window->DC.IsSetPos = true;
9869}
9870
9871void ImGui::SetCursorPosY(float y)
9872{
9873 ImGuiWindow* window = GetCurrentWindow();
9874 window->DC.CursorPos.y = window->Pos.y - window->Scroll.y + y;
9875 //window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y);
9876 window->DC.IsSetPos = true;
9877}
9878
9879ImVec2 ImGui::GetCursorStartPos()
9880{
9881 ImGuiWindow* window = GetCurrentWindowRead();
9882 return window->DC.CursorStartPos - window->Pos;
9883}
9884
9885void ImGui::Indent(float indent_w)
9886{
9887 ImGuiContext& g = *GImGui;
9888 ImGuiWindow* window = GetCurrentWindow();
9889 window->DC.Indent.x += (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing;
9890 window->DC.CursorPos.x = window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x;
9891}
9892
9893void ImGui::Unindent(float indent_w)
9894{
9895 ImGuiContext& g = *GImGui;
9896 ImGuiWindow* window = GetCurrentWindow();
9897 window->DC.Indent.x -= (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing;
9898 window->DC.CursorPos.x = window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x;
9899}
9900
9901// Affect large frame+labels widgets only.
9902void ImGui::SetNextItemWidth(float item_width)
9903{
9904 ImGuiContext& g = *GImGui;
9905 g.NextItemData.Flags |= ImGuiNextItemDataFlags_HasWidth;
9906 g.NextItemData.Width = item_width;
9907}
9908
9909// FIXME: Remove the == 0.0f behavior?
9910void ImGui::PushItemWidth(float item_width)
9911{
9912 ImGuiContext& g = *GImGui;
9913 ImGuiWindow* window = g.CurrentWindow;
9914 window->DC.ItemWidthStack.push_back(v: window->DC.ItemWidth); // Backup current width
9915 window->DC.ItemWidth = (item_width == 0.0f ? window->ItemWidthDefault : item_width);
9916 g.NextItemData.Flags &= ~ImGuiNextItemDataFlags_HasWidth;
9917}
9918
9919void ImGui::PushMultiItemsWidths(int components, float w_full)
9920{
9921 ImGuiContext& g = *GImGui;
9922 ImGuiWindow* window = g.CurrentWindow;
9923 const ImGuiStyle& style = g.Style;
9924 const float w_item_one = ImMax(lhs: 1.0f, IM_FLOOR((w_full - (style.ItemInnerSpacing.x) * (components - 1)) / (float)components));
9925 const float w_item_last = ImMax(lhs: 1.0f, IM_FLOOR(w_full - (w_item_one + style.ItemInnerSpacing.x) * (components - 1)));
9926 window->DC.ItemWidthStack.push_back(v: window->DC.ItemWidth); // Backup current width
9927 window->DC.ItemWidthStack.push_back(v: w_item_last);
9928 for (int i = 0; i < components - 2; i++)
9929 window->DC.ItemWidthStack.push_back(v: w_item_one);
9930 window->DC.ItemWidth = (components == 1) ? w_item_last : w_item_one;
9931 g.NextItemData.Flags &= ~ImGuiNextItemDataFlags_HasWidth;
9932}
9933
9934void ImGui::PopItemWidth()
9935{
9936 ImGuiWindow* window = GetCurrentWindow();
9937 window->DC.ItemWidth = window->DC.ItemWidthStack.back();
9938 window->DC.ItemWidthStack.pop_back();
9939}
9940
9941// Calculate default item width given value passed to PushItemWidth() or SetNextItemWidth().
9942// The SetNextItemWidth() data is generally cleared/consumed by ItemAdd() or NextItemData.ClearFlags()
9943float ImGui::CalcItemWidth()
9944{
9945 ImGuiContext& g = *GImGui;
9946 ImGuiWindow* window = g.CurrentWindow;
9947 float w;
9948 if (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasWidth)
9949 w = g.NextItemData.Width;
9950 else
9951 w = window->DC.ItemWidth;
9952 if (w < 0.0f)
9953 {
9954 float region_max_x = GetContentRegionMaxAbs().x;
9955 w = ImMax(lhs: 1.0f, rhs: region_max_x - window->DC.CursorPos.x + w);
9956 }
9957 w = IM_FLOOR(w);
9958 return w;
9959}
9960
9961// [Internal] Calculate full item size given user provided 'size' parameter and default width/height. Default width is often == CalcItemWidth().
9962// Those two functions CalcItemWidth vs CalcItemSize are awkwardly named because they are not fully symmetrical.
9963// Note that only CalcItemWidth() is publicly exposed.
9964// The 4.0f here may be changed to match CalcItemWidth() and/or BeginChild() (right now we have a mismatch which is harmless but undesirable)
9965ImVec2 ImGui::CalcItemSize(ImVec2 size, float default_w, float default_h)
9966{
9967 ImGuiContext& g = *GImGui;
9968 ImGuiWindow* window = g.CurrentWindow;
9969
9970 ImVec2 region_max;
9971 if (size.x < 0.0f || size.y < 0.0f)
9972 region_max = GetContentRegionMaxAbs();
9973
9974 if (size.x == 0.0f)
9975 size.x = default_w;
9976 else if (size.x < 0.0f)
9977 size.x = ImMax(lhs: 4.0f, rhs: region_max.x - window->DC.CursorPos.x + size.x);
9978
9979 if (size.y == 0.0f)
9980 size.y = default_h;
9981 else if (size.y < 0.0f)
9982 size.y = ImMax(lhs: 4.0f, rhs: region_max.y - window->DC.CursorPos.y + size.y);
9983
9984 return size;
9985}
9986
9987float ImGui::GetTextLineHeight()
9988{
9989 ImGuiContext& g = *GImGui;
9990 return g.FontSize;
9991}
9992
9993float ImGui::GetTextLineHeightWithSpacing()
9994{
9995 ImGuiContext& g = *GImGui;
9996 return g.FontSize + g.Style.ItemSpacing.y;
9997}
9998
9999float ImGui::GetFrameHeight()
10000{
10001 ImGuiContext& g = *GImGui;
10002 return g.FontSize + g.Style.FramePadding.y * 2.0f;
10003}
10004
10005float ImGui::GetFrameHeightWithSpacing()
10006{
10007 ImGuiContext& g = *GImGui;
10008 return g.FontSize + g.Style.FramePadding.y * 2.0f + g.Style.ItemSpacing.y;
10009}
10010
10011// 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!
10012
10013// FIXME: This is in window space (not screen space!).
10014ImVec2 ImGui::GetContentRegionMax()
10015{
10016 ImGuiContext& g = *GImGui;
10017 ImGuiWindow* window = g.CurrentWindow;
10018 ImVec2 mx = window->ContentRegionRect.Max - window->Pos;
10019 if (window->DC.CurrentColumns || g.CurrentTable)
10020 mx.x = window->WorkRect.Max.x - window->Pos.x;
10021 return mx;
10022}
10023
10024// [Internal] Absolute coordinate. Saner. This is not exposed until we finishing refactoring work rect features.
10025ImVec2 ImGui::GetContentRegionMaxAbs()
10026{
10027 ImGuiContext& g = *GImGui;
10028 ImGuiWindow* window = g.CurrentWindow;
10029 ImVec2 mx = window->ContentRegionRect.Max;
10030 if (window->DC.CurrentColumns || g.CurrentTable)
10031 mx.x = window->WorkRect.Max.x;
10032 return mx;
10033}
10034
10035ImVec2 ImGui::GetContentRegionAvail()
10036{
10037 ImGuiWindow* window = GImGui->CurrentWindow;
10038 return GetContentRegionMaxAbs() - window->DC.CursorPos;
10039}
10040
10041// In window space (not screen space!)
10042ImVec2 ImGui::GetWindowContentRegionMin()
10043{
10044 ImGuiWindow* window = GImGui->CurrentWindow;
10045 return window->ContentRegionRect.Min - window->Pos;
10046}
10047
10048ImVec2 ImGui::GetWindowContentRegionMax()
10049{
10050 ImGuiWindow* window = GImGui->CurrentWindow;
10051 return window->ContentRegionRect.Max - window->Pos;
10052}
10053
10054// 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.)
10055// Groups are currently a mishmash of functionalities which should perhaps be clarified and separated.
10056// FIXME-OPT: Could we safely early out on ->SkipItems?
10057void ImGui::BeginGroup()
10058{
10059 ImGuiContext& g = *GImGui;
10060 ImGuiWindow* window = g.CurrentWindow;
10061
10062 g.GroupStack.resize(new_size: g.GroupStack.Size + 1);
10063 ImGuiGroupData& group_data = g.GroupStack.back();
10064 group_data.WindowID = window->ID;
10065 group_data.BackupCursorPos = window->DC.CursorPos;
10066 group_data.BackupCursorMaxPos = window->DC.CursorMaxPos;
10067 group_data.BackupIndent = window->DC.Indent;
10068 group_data.BackupGroupOffset = window->DC.GroupOffset;
10069 group_data.BackupCurrLineSize = window->DC.CurrLineSize;
10070 group_data.BackupCurrLineTextBaseOffset = window->DC.CurrLineTextBaseOffset;
10071 group_data.BackupActiveIdIsAlive = g.ActiveIdIsAlive;
10072 group_data.BackupHoveredIdIsAlive = g.HoveredId != 0;
10073 group_data.BackupActiveIdPreviousFrameIsAlive = g.ActiveIdPreviousFrameIsAlive;
10074 group_data.EmitItem = true;
10075
10076 window->DC.GroupOffset.x = window->DC.CursorPos.x - window->Pos.x - window->DC.ColumnsOffset.x;
10077 window->DC.Indent = window->DC.GroupOffset;
10078 window->DC.CursorMaxPos = window->DC.CursorPos;
10079 window->DC.CurrLineSize = ImVec2(0.0f, 0.0f);
10080 if (g.LogEnabled)
10081 g.LogLinePosY = -FLT_MAX; // To enforce a carriage return
10082}
10083
10084void ImGui::EndGroup()
10085{
10086 ImGuiContext& g = *GImGui;
10087 ImGuiWindow* window = g.CurrentWindow;
10088 IM_ASSERT(g.GroupStack.Size > 0); // Mismatched BeginGroup()/EndGroup() calls
10089
10090 ImGuiGroupData& group_data = g.GroupStack.back();
10091 IM_ASSERT(group_data.WindowID == window->ID); // EndGroup() in wrong window?
10092
10093 if (window->DC.IsSetPos)
10094 ErrorCheckUsingSetCursorPosToExtendParentBoundaries();
10095
10096 ImRect group_bb(group_data.BackupCursorPos, ImMax(lhs: window->DC.CursorMaxPos, rhs: group_data.BackupCursorPos));
10097
10098 window->DC.CursorPos = group_data.BackupCursorPos;
10099 window->DC.CursorMaxPos = ImMax(lhs: group_data.BackupCursorMaxPos, rhs: window->DC.CursorMaxPos);
10100 window->DC.Indent = group_data.BackupIndent;
10101 window->DC.GroupOffset = group_data.BackupGroupOffset;
10102 window->DC.CurrLineSize = group_data.BackupCurrLineSize;
10103 window->DC.CurrLineTextBaseOffset = group_data.BackupCurrLineTextBaseOffset;
10104 if (g.LogEnabled)
10105 g.LogLinePosY = -FLT_MAX; // To enforce a carriage return
10106
10107 if (!group_data.EmitItem)
10108 {
10109 g.GroupStack.pop_back();
10110 return;
10111 }
10112
10113 window->DC.CurrLineTextBaseOffset = ImMax(lhs: window->DC.PrevLineTextBaseOffset, rhs: 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.
10114 ItemSize(size: group_bb.GetSize());
10115 ItemAdd(bb: group_bb, id: 0, NULL, extra_flags: ImGuiItemFlags_NoTabStop);
10116
10117 // 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.
10118 // It would be neater if we replaced window.DC.LastItemId by e.g. 'bool LastItemIsActive', but would put a little more burden on individual widgets.
10119 // Also if you grep for LastItemId you'll notice it is only used in that context.
10120 // (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.)
10121 const bool group_contains_curr_active_id = (group_data.BackupActiveIdIsAlive != g.ActiveId) && (g.ActiveIdIsAlive == g.ActiveId) && g.ActiveId;
10122 const bool group_contains_prev_active_id = (group_data.BackupActiveIdPreviousFrameIsAlive == false) && (g.ActiveIdPreviousFrameIsAlive == true);
10123 if (group_contains_curr_active_id)
10124 g.LastItemData.ID = g.ActiveId;
10125 else if (group_contains_prev_active_id)
10126 g.LastItemData.ID = g.ActiveIdPreviousFrame;
10127 g.LastItemData.Rect = group_bb;
10128
10129 // Forward Hovered flag
10130 const bool group_contains_curr_hovered_id = (group_data.BackupHoveredIdIsAlive == false) && g.HoveredId != 0;
10131 if (group_contains_curr_hovered_id)
10132 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow;
10133
10134 // Forward Edited flag
10135 if (group_contains_curr_active_id && g.ActiveIdHasBeenEditedThisFrame)
10136 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Edited;
10137
10138 // Forward Deactivated flag
10139 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDeactivated;
10140 if (group_contains_prev_active_id && g.ActiveId != g.ActiveIdPreviousFrame)
10141 g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Deactivated;
10142
10143 g.GroupStack.pop_back();
10144 //window->DrawList->AddRect(group_bb.Min, group_bb.Max, IM_COL32(255,0,255,255)); // [Debug]
10145}
10146
10147
10148//-----------------------------------------------------------------------------
10149// [SECTION] SCROLLING
10150//-----------------------------------------------------------------------------
10151
10152// Helper to snap on edges when aiming at an item very close to the edge,
10153// So the difference between WindowPadding and ItemSpacing will be in the visible area after scrolling.
10154// When we refactor the scrolling API this may be configurable with a flag?
10155// Note that the effect for this won't be visible on X axis with default Style settings as WindowPadding.x == ItemSpacing.x by default.
10156static float CalcScrollEdgeSnap(float target, float snap_min, float snap_max, float snap_threshold, float center_ratio)
10157{
10158 if (target <= snap_min + snap_threshold)
10159 return ImLerp(a: snap_min, b: target, t: center_ratio);
10160 if (target >= snap_max - snap_threshold)
10161 return ImLerp(a: target, b: snap_max, t: center_ratio);
10162 return target;
10163}
10164
10165static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window)
10166{
10167 ImVec2 scroll = window->Scroll;
10168 if (window->ScrollTarget.x < FLT_MAX)
10169 {
10170 float decoration_total_width = window->ScrollbarSizes.x;
10171 float center_x_ratio = window->ScrollTargetCenterRatio.x;
10172 float scroll_target_x = window->ScrollTarget.x;
10173 if (window->ScrollTargetEdgeSnapDist.x > 0.0f)
10174 {
10175 float snap_x_min = 0.0f;
10176 float snap_x_max = window->ScrollMax.x + window->SizeFull.x - decoration_total_width;
10177 scroll_target_x = CalcScrollEdgeSnap(target: scroll_target_x, snap_min: snap_x_min, snap_max: snap_x_max, snap_threshold: window->ScrollTargetEdgeSnapDist.x, center_ratio: center_x_ratio);
10178 }
10179 scroll.x = scroll_target_x - center_x_ratio * (window->SizeFull.x - decoration_total_width);
10180 }
10181 if (window->ScrollTarget.y < FLT_MAX)
10182 {
10183 float decoration_total_height = window->TitleBarHeight() + window->MenuBarHeight() + window->ScrollbarSizes.y;
10184 float center_y_ratio = window->ScrollTargetCenterRatio.y;
10185 float scroll_target_y = window->ScrollTarget.y;
10186 if (window->ScrollTargetEdgeSnapDist.y > 0.0f)
10187 {
10188 float snap_y_min = 0.0f;
10189 float snap_y_max = window->ScrollMax.y + window->SizeFull.y - decoration_total_height;
10190 scroll_target_y = CalcScrollEdgeSnap(target: scroll_target_y, snap_min: snap_y_min, snap_max: snap_y_max, snap_threshold: window->ScrollTargetEdgeSnapDist.y, center_ratio: center_y_ratio);
10191 }
10192 scroll.y = scroll_target_y - center_y_ratio * (window->SizeFull.y - decoration_total_height);
10193 }
10194 scroll.x = IM_FLOOR(ImMax(scroll.x, 0.0f));
10195 scroll.y = IM_FLOOR(ImMax(scroll.y, 0.0f));
10196 if (!window->Collapsed && !window->SkipItems)
10197 {
10198 scroll.x = ImMin(lhs: scroll.x, rhs: window->ScrollMax.x);
10199 scroll.y = ImMin(lhs: scroll.y, rhs: window->ScrollMax.y);
10200 }
10201 return scroll;
10202}
10203
10204void ImGui::ScrollToItem(ImGuiScrollFlags flags)
10205{
10206 ImGuiContext& g = *GImGui;
10207 ImGuiWindow* window = g.CurrentWindow;
10208 ScrollToRectEx(window, rect: g.LastItemData.NavRect, flags);
10209}
10210
10211void ImGui::ScrollToRect(ImGuiWindow* window, const ImRect& item_rect, ImGuiScrollFlags flags)
10212{
10213 ScrollToRectEx(window, rect: item_rect, flags);
10214}
10215
10216// Scroll to keep newly navigated item fully into view
10217ImVec2 ImGui::ScrollToRectEx(ImGuiWindow* window, const ImRect& item_rect, ImGuiScrollFlags flags)
10218{
10219 ImGuiContext& g = *GImGui;
10220 ImRect window_rect(window->InnerRect.Min - ImVec2(1, 1), window->InnerRect.Max + ImVec2(1, 1));
10221 //GetForegroundDrawList(window)->AddRect(window_rect.Min, window_rect.Max, IM_COL32_WHITE); // [DEBUG]
10222
10223 // Check that only one behavior is selected per axis
10224 IM_ASSERT((flags & ImGuiScrollFlags_MaskX_) == 0 || ImIsPowerOfTwo(flags & ImGuiScrollFlags_MaskX_));
10225 IM_ASSERT((flags & ImGuiScrollFlags_MaskY_) == 0 || ImIsPowerOfTwo(flags & ImGuiScrollFlags_MaskY_));
10226
10227 // Defaults
10228 ImGuiScrollFlags in_flags = flags;
10229 if ((flags & ImGuiScrollFlags_MaskX_) == 0 && window->ScrollbarX)
10230 flags |= ImGuiScrollFlags_KeepVisibleEdgeX;
10231 if ((flags & ImGuiScrollFlags_MaskY_) == 0)
10232 flags |= window->Appearing ? ImGuiScrollFlags_AlwaysCenterY : ImGuiScrollFlags_KeepVisibleEdgeY;
10233
10234 const bool fully_visible_x = item_rect.Min.x >= window_rect.Min.x && item_rect.Max.x <= window_rect.Max.x;
10235 const bool fully_visible_y = item_rect.Min.y >= window_rect.Min.y && item_rect.Max.y <= window_rect.Max.y;
10236 const bool can_be_fully_visible_x = (item_rect.GetWidth() + g.Style.ItemSpacing.x * 2.0f) <= window_rect.GetWidth() || (window->AutoFitFramesX > 0) || (window->Flags & ImGuiWindowFlags_AlwaysAutoResize) != 0;
10237 const bool can_be_fully_visible_y = (item_rect.GetHeight() + g.Style.ItemSpacing.y * 2.0f) <= window_rect.GetHeight() || (window->AutoFitFramesY > 0) || (window->Flags & ImGuiWindowFlags_AlwaysAutoResize) != 0;
10238
10239 if ((flags & ImGuiScrollFlags_KeepVisibleEdgeX) && !fully_visible_x)
10240 {
10241 if (item_rect.Min.x < window_rect.Min.x || !can_be_fully_visible_x)
10242 SetScrollFromPosX(window, local_x: item_rect.Min.x - g.Style.ItemSpacing.x - window->Pos.x, center_x_ratio: 0.0f);
10243 else if (item_rect.Max.x >= window_rect.Max.x)
10244 SetScrollFromPosX(window, local_x: item_rect.Max.x + g.Style.ItemSpacing.x - window->Pos.x, center_x_ratio: 1.0f);
10245 }
10246 else if (((flags & ImGuiScrollFlags_KeepVisibleCenterX) && !fully_visible_x) || (flags & ImGuiScrollFlags_AlwaysCenterX))
10247 {
10248 if (can_be_fully_visible_x)
10249 SetScrollFromPosX(window, local_x: ImFloor(f: (item_rect.Min.x + item_rect.Max.y) * 0.5f) - window->Pos.x, center_x_ratio: 0.5f);
10250 else
10251 SetScrollFromPosX(window, local_x: item_rect.Min.x - window->Pos.x, center_x_ratio: 0.0f);
10252 }
10253
10254 if ((flags & ImGuiScrollFlags_KeepVisibleEdgeY) && !fully_visible_y)
10255 {
10256 if (item_rect.Min.y < window_rect.Min.y || !can_be_fully_visible_y)
10257 SetScrollFromPosY(window, local_y: item_rect.Min.y - g.Style.ItemSpacing.y - window->Pos.y, center_y_ratio: 0.0f);
10258 else if (item_rect.Max.y >= window_rect.Max.y)
10259 SetScrollFromPosY(window, local_y: item_rect.Max.y + g.Style.ItemSpacing.y - window->Pos.y, center_y_ratio: 1.0f);
10260 }
10261 else if (((flags & ImGuiScrollFlags_KeepVisibleCenterY) && !fully_visible_y) || (flags & ImGuiScrollFlags_AlwaysCenterY))
10262 {
10263 if (can_be_fully_visible_y)
10264 SetScrollFromPosY(window, local_y: ImFloor(f: (item_rect.Min.y + item_rect.Max.y) * 0.5f) - window->Pos.y, center_y_ratio: 0.5f);
10265 else
10266 SetScrollFromPosY(window, local_y: item_rect.Min.y - window->Pos.y, center_y_ratio: 0.0f);
10267 }
10268
10269 ImVec2 next_scroll = CalcNextScrollFromScrollTargetAndClamp(window);
10270 ImVec2 delta_scroll = next_scroll - window->Scroll;
10271
10272 // Also scroll parent window to keep us into view if necessary
10273 if (!(flags & ImGuiScrollFlags_NoScrollParent) && (window->Flags & ImGuiWindowFlags_ChildWindow))
10274 {
10275 // FIXME-SCROLL: May be an option?
10276 if ((in_flags & (ImGuiScrollFlags_AlwaysCenterX | ImGuiScrollFlags_KeepVisibleCenterX)) != 0)
10277 in_flags = (in_flags & ~ImGuiScrollFlags_MaskX_) | ImGuiScrollFlags_KeepVisibleEdgeX;
10278 if ((in_flags & (ImGuiScrollFlags_AlwaysCenterY | ImGuiScrollFlags_KeepVisibleCenterY)) != 0)
10279 in_flags = (in_flags & ~ImGuiScrollFlags_MaskY_) | ImGuiScrollFlags_KeepVisibleEdgeY;
10280 delta_scroll += ScrollToRectEx(window: window->ParentWindow, item_rect: ImRect(item_rect.Min - delta_scroll, item_rect.Max - delta_scroll), flags: in_flags);
10281 }
10282
10283 return delta_scroll;
10284}
10285
10286float ImGui::GetScrollX()
10287{
10288 ImGuiWindow* window = GImGui->CurrentWindow;
10289 return window->Scroll.x;
10290}
10291
10292float ImGui::GetScrollY()
10293{
10294 ImGuiWindow* window = GImGui->CurrentWindow;
10295 return window->Scroll.y;
10296}
10297
10298float ImGui::GetScrollMaxX()
10299{
10300 ImGuiWindow* window = GImGui->CurrentWindow;
10301 return window->ScrollMax.x;
10302}
10303
10304float ImGui::GetScrollMaxY()
10305{
10306 ImGuiWindow* window = GImGui->CurrentWindow;
10307 return window->ScrollMax.y;
10308}
10309
10310void ImGui::SetScrollX(ImGuiWindow* window, float scroll_x)
10311{
10312 window->ScrollTarget.x = scroll_x;
10313 window->ScrollTargetCenterRatio.x = 0.0f;
10314 window->ScrollTargetEdgeSnapDist.x = 0.0f;
10315}
10316
10317void ImGui::SetScrollY(ImGuiWindow* window, float scroll_y)
10318{
10319 window->ScrollTarget.y = scroll_y;
10320 window->ScrollTargetCenterRatio.y = 0.0f;
10321 window->ScrollTargetEdgeSnapDist.y = 0.0f;
10322}
10323
10324void ImGui::SetScrollX(float scroll_x)
10325{
10326 ImGuiContext& g = *GImGui;
10327 SetScrollX(window: g.CurrentWindow, scroll_x);
10328}
10329
10330void ImGui::SetScrollY(float scroll_y)
10331{
10332 ImGuiContext& g = *GImGui;
10333 SetScrollY(window: g.CurrentWindow, scroll_y);
10334}
10335
10336// Note that a local position will vary depending on initial scroll value,
10337// This is a little bit confusing so bear with us:
10338// - local_pos = (absolution_pos - window->Pos)
10339// - So local_x/local_y are 0.0f for a position at the upper-left corner of a window,
10340// and generally local_x/local_y are >(padding+decoration) && <(size-padding-decoration) when in the visible area.
10341// - They mostly exist because of legacy API.
10342// Following the rules above, when trying to work with scrolling code, consider that:
10343// - SetScrollFromPosY(0.0f) == SetScrollY(0.0f + scroll.y) == has no effect!
10344// - SetScrollFromPosY(-scroll.y) == SetScrollY(-scroll.y + scroll.y) == SetScrollY(0.0f) == reset scroll. Of course writing SetScrollY(0.0f) directly then makes more sense
10345// 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
10346void ImGui::SetScrollFromPosX(ImGuiWindow* window, float local_x, float center_x_ratio)
10347{
10348 IM_ASSERT(center_x_ratio >= 0.0f && center_x_ratio <= 1.0f);
10349 window->ScrollTarget.x = IM_FLOOR(local_x + window->Scroll.x); // Convert local position to scroll offset
10350 window->ScrollTargetCenterRatio.x = center_x_ratio;
10351 window->ScrollTargetEdgeSnapDist.x = 0.0f;
10352}
10353
10354void ImGui::SetScrollFromPosY(ImGuiWindow* window, float local_y, float center_y_ratio)
10355{
10356 IM_ASSERT(center_y_ratio >= 0.0f && center_y_ratio <= 1.0f);
10357 const float decoration_up_height = window->TitleBarHeight() + window->MenuBarHeight(); // FIXME: Would be nice to have a more standardized access to our scrollable/client rect;
10358 local_y -= decoration_up_height;
10359 window->ScrollTarget.y = IM_FLOOR(local_y + window->Scroll.y); // Convert local position to scroll offset
10360 window->ScrollTargetCenterRatio.y = center_y_ratio;
10361 window->ScrollTargetEdgeSnapDist.y = 0.0f;
10362}
10363
10364void ImGui::SetScrollFromPosX(float local_x, float center_x_ratio)
10365{
10366 ImGuiContext& g = *GImGui;
10367 SetScrollFromPosX(window: g.CurrentWindow, local_x, center_x_ratio);
10368}
10369
10370void ImGui::SetScrollFromPosY(float local_y, float center_y_ratio)
10371{
10372 ImGuiContext& g = *GImGui;
10373 SetScrollFromPosY(window: g.CurrentWindow, local_y, center_y_ratio);
10374}
10375
10376// center_x_ratio: 0.0f left of last item, 0.5f horizontal center of last item, 1.0f right of last item.
10377void ImGui::SetScrollHereX(float center_x_ratio)
10378{
10379 ImGuiContext& g = *GImGui;
10380 ImGuiWindow* window = g.CurrentWindow;
10381 float spacing_x = ImMax(lhs: window->WindowPadding.x, rhs: g.Style.ItemSpacing.x);
10382 float target_pos_x = ImLerp(a: g.LastItemData.Rect.Min.x - spacing_x, b: g.LastItemData.Rect.Max.x + spacing_x, t: center_x_ratio);
10383 SetScrollFromPosX(window, local_x: target_pos_x - window->Pos.x, center_x_ratio); // Convert from absolute to local pos
10384
10385 // Tweak: snap on edges when aiming at an item very close to the edge
10386 window->ScrollTargetEdgeSnapDist.x = ImMax(lhs: 0.0f, rhs: window->WindowPadding.x - spacing_x);
10387}
10388
10389// center_y_ratio: 0.0f top of last item, 0.5f vertical center of last item, 1.0f bottom of last item.
10390void ImGui::SetScrollHereY(float center_y_ratio)
10391{
10392 ImGuiContext& g = *GImGui;
10393 ImGuiWindow* window = g.CurrentWindow;
10394 float spacing_y = ImMax(lhs: window->WindowPadding.y, rhs: g.Style.ItemSpacing.y);
10395 float target_pos_y = ImLerp(a: window->DC.CursorPosPrevLine.y - spacing_y, b: window->DC.CursorPosPrevLine.y + window->DC.PrevLineSize.y + spacing_y, t: center_y_ratio);
10396 SetScrollFromPosY(window, local_y: target_pos_y - window->Pos.y, center_y_ratio); // Convert from absolute to local pos
10397
10398 // Tweak: snap on edges when aiming at an item very close to the edge
10399 window->ScrollTargetEdgeSnapDist.y = ImMax(lhs: 0.0f, rhs: window->WindowPadding.y - spacing_y);
10400}
10401
10402//-----------------------------------------------------------------------------
10403// [SECTION] TOOLTIPS
10404//-----------------------------------------------------------------------------
10405
10406void ImGui::BeginTooltip()
10407{
10408 BeginTooltipEx(tooltip_flags: ImGuiTooltipFlags_None, extra_window_flags: ImGuiWindowFlags_None);
10409}
10410
10411void ImGui::BeginTooltipEx(ImGuiTooltipFlags tooltip_flags, ImGuiWindowFlags extra_window_flags)
10412{
10413 ImGuiContext& g = *GImGui;
10414
10415 if (g.DragDropWithinSource || g.DragDropWithinTarget)
10416 {
10417 // 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)
10418 // In the context of a dragging tooltip we try to reduce that offset and we enforce following the cursor.
10419 // 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.
10420 //ImVec2 tooltip_pos = g.IO.MousePos - g.ActiveIdClickOffset - g.Style.WindowPadding;
10421 ImVec2 tooltip_pos = g.IO.MousePos + ImVec2(16 * g.Style.MouseCursorScale, 8 * g.Style.MouseCursorScale);
10422 SetNextWindowPos(pos: tooltip_pos);
10423 SetNextWindowBgAlpha(g.Style.Colors[ImGuiCol_PopupBg].w * 0.60f);
10424 //PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * 0.60f); // This would be nice but e.g ColorButton with checkboard has issue with transparent colors :(
10425 tooltip_flags |= ImGuiTooltipFlags_OverridePreviousTooltip;
10426 }
10427
10428 char window_name[16];
10429 ImFormatString(buf: window_name, IM_ARRAYSIZE(window_name), fmt: "##Tooltip_%02d", g.TooltipOverrideCount);
10430 if (tooltip_flags & ImGuiTooltipFlags_OverridePreviousTooltip)
10431 if (ImGuiWindow* window = FindWindowByName(name: window_name))
10432 if (window->Active)
10433 {
10434 // Hide previous tooltip from being displayed. We can't easily "reset" the content of a window so we create a new one.
10435 window->Hidden = true;
10436 window->HiddenFramesCanSkipItems = 1; // FIXME: This may not be necessary?
10437 ImFormatString(buf: window_name, IM_ARRAYSIZE(window_name), fmt: "##Tooltip_%02d", ++g.TooltipOverrideCount);
10438 }
10439 ImGuiWindowFlags flags = ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDocking;
10440 Begin(name: window_name, NULL, flags: flags | extra_window_flags);
10441}
10442
10443void ImGui::EndTooltip()
10444{
10445 IM_ASSERT(GetCurrentWindowRead()->Flags & ImGuiWindowFlags_Tooltip); // Mismatched BeginTooltip()/EndTooltip() calls
10446 End();
10447}
10448
10449void ImGui::SetTooltipV(const char* fmt, va_list args)
10450{
10451 BeginTooltipEx(tooltip_flags: ImGuiTooltipFlags_OverridePreviousTooltip, extra_window_flags: ImGuiWindowFlags_None);
10452 TextV(fmt, args);
10453 EndTooltip();
10454}
10455
10456void ImGui::SetTooltip(const char* fmt, ...)
10457{
10458 va_list args;
10459 va_start(args, fmt);
10460 SetTooltipV(fmt, args);
10461 va_end(args);
10462}
10463
10464//-----------------------------------------------------------------------------
10465// [SECTION] POPUPS
10466//-----------------------------------------------------------------------------
10467
10468// Supported flags: ImGuiPopupFlags_AnyPopupId, ImGuiPopupFlags_AnyPopupLevel
10469bool ImGui::IsPopupOpen(ImGuiID id, ImGuiPopupFlags popup_flags)
10470{
10471 ImGuiContext& g = *GImGui;
10472 if (popup_flags & ImGuiPopupFlags_AnyPopupId)
10473 {
10474 // Return true if any popup is open at the current BeginPopup() level of the popup stack
10475 // This may be used to e.g. test for another popups already opened to handle popups priorities at the same level.
10476 IM_ASSERT(id == 0);
10477 if (popup_flags & ImGuiPopupFlags_AnyPopupLevel)
10478 return g.OpenPopupStack.Size > 0;
10479 else
10480 return g.OpenPopupStack.Size > g.BeginPopupStack.Size;
10481 }
10482 else
10483 {
10484 if (popup_flags & ImGuiPopupFlags_AnyPopupLevel)
10485 {
10486 // Return true if the popup is open anywhere in the popup stack
10487 for (int n = 0; n < g.OpenPopupStack.Size; n++)
10488 if (g.OpenPopupStack[n].PopupId == id)
10489 return true;
10490 return false;
10491 }
10492 else
10493 {
10494 // Return true if the popup is open at the current BeginPopup() level of the popup stack (this is the most-common query)
10495 return g.OpenPopupStack.Size > g.BeginPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].PopupId == id;
10496 }
10497 }
10498}
10499
10500bool ImGui::IsPopupOpen(const char* str_id, ImGuiPopupFlags popup_flags)
10501{
10502 ImGuiContext& g = *GImGui;
10503 ImGuiID id = (popup_flags & ImGuiPopupFlags_AnyPopupId) ? 0 : g.CurrentWindow->GetID(str: str_id);
10504 if ((popup_flags & ImGuiPopupFlags_AnyPopupLevel) && id != 0)
10505 IM_ASSERT(0 && "Cannot use IsPopupOpen() with a string id and ImGuiPopupFlags_AnyPopupLevel."); // But non-string version is legal and used internally
10506 return IsPopupOpen(id, popup_flags);
10507}
10508
10509ImGuiWindow* ImGui::GetTopMostPopupModal()
10510{
10511 ImGuiContext& g = *GImGui;
10512 for (int n = g.OpenPopupStack.Size - 1; n >= 0; n--)
10513 if (ImGuiWindow* popup = g.OpenPopupStack.Data[n].Window)
10514 if (popup->Flags & ImGuiWindowFlags_Modal)
10515 return popup;
10516 return NULL;
10517}
10518
10519ImGuiWindow* ImGui::GetTopMostAndVisiblePopupModal()
10520{
10521 ImGuiContext& g = *GImGui;
10522 for (int n = g.OpenPopupStack.Size - 1; n >= 0; n--)
10523 if (ImGuiWindow* popup = g.OpenPopupStack.Data[n].Window)
10524 if ((popup->Flags & ImGuiWindowFlags_Modal) && IsWindowActiveAndVisible(window: popup))
10525 return popup;
10526 return NULL;
10527}
10528
10529void ImGui::OpenPopup(const char* str_id, ImGuiPopupFlags popup_flags)
10530{
10531 ImGuiContext& g = *GImGui;
10532 ImGuiID id = g.CurrentWindow->GetID(str: str_id);
10533 IMGUI_DEBUG_LOG_POPUP("[popup] OpenPopup(\"%s\" -> 0x%08X\n", str_id, id);
10534 OpenPopupEx(id, popup_flags);
10535}
10536
10537void ImGui::OpenPopup(ImGuiID id, ImGuiPopupFlags popup_flags)
10538{
10539 OpenPopupEx(id, popup_flags);
10540}
10541
10542// Mark popup as open (toggle toward open state).
10543// Popups are closed when user click outside, or activate a pressable item, or CloseCurrentPopup() is called within a BeginPopup()/EndPopup() block.
10544// Popup identifiers are relative to the current ID-stack (so OpenPopup and BeginPopup needs to be at the same level).
10545// One open popup per level of the popup hierarchy (NB: when assigning we reset the Window member of ImGuiPopupRef to NULL)
10546void ImGui::OpenPopupEx(ImGuiID id, ImGuiPopupFlags popup_flags)
10547{
10548 ImGuiContext& g = *GImGui;
10549 ImGuiWindow* parent_window = g.CurrentWindow;
10550 const int current_stack_size = g.BeginPopupStack.Size;
10551
10552 if (popup_flags & ImGuiPopupFlags_NoOpenOverExistingPopup)
10553 if (IsPopupOpen(id: 0u, popup_flags: ImGuiPopupFlags_AnyPopupId))
10554 return;
10555
10556 ImGuiPopupData popup_ref; // Tagged as new ref as Window will be set back to NULL if we write this into OpenPopupStack.
10557 popup_ref.PopupId = id;
10558 popup_ref.Window = NULL;
10559 popup_ref.BackupNavWindow = g.NavWindow; // When popup closes focus may be restored to NavWindow (depend on window type).
10560 popup_ref.OpenFrameCount = g.FrameCount;
10561 popup_ref.OpenParentId = parent_window->IDStack.back();
10562 popup_ref.OpenPopupPos = NavCalcPreferredRefPos();
10563 popup_ref.OpenMousePos = IsMousePosValid(mouse_pos: &g.IO.MousePos) ? g.IO.MousePos : popup_ref.OpenPopupPos;
10564
10565 IMGUI_DEBUG_LOG_POPUP("[popup] OpenPopupEx(0x%08X)\n", id);
10566 if (g.OpenPopupStack.Size < current_stack_size + 1)
10567 {
10568 g.OpenPopupStack.push_back(v: popup_ref);
10569 }
10570 else
10571 {
10572 // 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
10573 // 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
10574 // 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.
10575 if (g.OpenPopupStack[current_stack_size].PopupId == id && g.OpenPopupStack[current_stack_size].OpenFrameCount == g.FrameCount - 1)
10576 {
10577 g.OpenPopupStack[current_stack_size].OpenFrameCount = popup_ref.OpenFrameCount;
10578 }
10579 else
10580 {
10581 // Close child popups if any, then flag popup for open/reopen
10582 ClosePopupToLevel(remaining: current_stack_size, restore_focus_to_window_under_popup: false);
10583 g.OpenPopupStack.push_back(v: popup_ref);
10584 }
10585
10586 // When reopening a popup we first refocus its parent, otherwise if its parent is itself a popup it would get closed by ClosePopupsOverWindow().
10587 // This is equivalent to what ClosePopupToLevel() does.
10588 //if (g.OpenPopupStack[current_stack_size].PopupId == id)
10589 // FocusWindow(parent_window);
10590 }
10591}
10592
10593// When popups are stacked, clicking on a lower level popups puts focus back to it and close popups above it.
10594// This function closes any popups that are over 'ref_window'.
10595void ImGui::ClosePopupsOverWindow(ImGuiWindow* ref_window, bool restore_focus_to_window_under_popup)
10596{
10597 ImGuiContext& g = *GImGui;
10598 if (g.OpenPopupStack.Size == 0)
10599 return;
10600
10601 // Don't close our own child popup windows.
10602 int popup_count_to_keep = 0;
10603 if (ref_window)
10604 {
10605 // Find the highest popup which is a descendant of the reference window (generally reference window = NavWindow)
10606 for (; popup_count_to_keep < g.OpenPopupStack.Size; popup_count_to_keep++)
10607 {
10608 ImGuiPopupData& popup = g.OpenPopupStack[popup_count_to_keep];
10609 if (!popup.Window)
10610 continue;
10611 IM_ASSERT((popup.Window->Flags & ImGuiWindowFlags_Popup) != 0);
10612 if (popup.Window->Flags & ImGuiWindowFlags_ChildWindow)
10613 continue;
10614
10615 // Trim the stack unless the popup is a direct parent of the reference window (the reference window is often the NavWindow)
10616 // - With this stack of window, clicking/focusing Popup1 will close Popup2 and Popup3:
10617 // Window -> Popup1 -> Popup2 -> Popup3
10618 // - Each popups may contain child windows, which is why we compare ->RootWindowDockTree!
10619 // Window -> Popup1 -> Popup1_Child -> Popup2 -> Popup2_Child
10620 bool ref_window_is_descendent_of_popup = false;
10621 for (int n = popup_count_to_keep; n < g.OpenPopupStack.Size; n++)
10622 if (ImGuiWindow* popup_window = g.OpenPopupStack[n].Window)
10623 //if (popup_window->RootWindowDockTree == ref_window->RootWindowDockTree) // FIXME-MERGE
10624 if (IsWindowWithinBeginStackOf(window: ref_window, potential_parent: popup_window))
10625 {
10626 ref_window_is_descendent_of_popup = true;
10627 break;
10628 }
10629 if (!ref_window_is_descendent_of_popup)
10630 break;
10631 }
10632 }
10633 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
10634 {
10635 IMGUI_DEBUG_LOG_POPUP("[popup] ClosePopupsOverWindow(\"%s\")\n", ref_window ? ref_window->Name : "<NULL>");
10636 ClosePopupToLevel(remaining: popup_count_to_keep, restore_focus_to_window_under_popup);
10637 }
10638}
10639
10640void ImGui::ClosePopupsExceptModals()
10641{
10642 ImGuiContext& g = *GImGui;
10643
10644 int popup_count_to_keep;
10645 for (popup_count_to_keep = g.OpenPopupStack.Size; popup_count_to_keep > 0; popup_count_to_keep--)
10646 {
10647 ImGuiWindow* window = g.OpenPopupStack[popup_count_to_keep - 1].Window;
10648 if (!window || window->Flags & ImGuiWindowFlags_Modal)
10649 break;
10650 }
10651 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
10652 ClosePopupToLevel(remaining: popup_count_to_keep, restore_focus_to_window_under_popup: true);
10653}
10654
10655void ImGui::ClosePopupToLevel(int remaining, bool restore_focus_to_window_under_popup)
10656{
10657 ImGuiContext& g = *GImGui;
10658 IMGUI_DEBUG_LOG_POPUP("[popup] ClosePopupToLevel(%d), restore_focus_to_window_under_popup=%d\n", remaining, restore_focus_to_window_under_popup);
10659 IM_ASSERT(remaining >= 0 && remaining < g.OpenPopupStack.Size);
10660
10661 // Trim open popup stack
10662 ImGuiWindow* popup_window = g.OpenPopupStack[remaining].Window;
10663 ImGuiWindow* popup_backup_nav_window = g.OpenPopupStack[remaining].BackupNavWindow;
10664 g.OpenPopupStack.resize(new_size: remaining);
10665
10666 if (restore_focus_to_window_under_popup)
10667 {
10668 ImGuiWindow* focus_window = (popup_window && popup_window->Flags & ImGuiWindowFlags_ChildMenu) ? popup_window->ParentWindow : popup_backup_nav_window;
10669 if (focus_window && !focus_window->WasActive && popup_window)
10670 {
10671 // Fallback
10672 FocusTopMostWindowUnderOne(under_this_window: popup_window, NULL);
10673 }
10674 else
10675 {
10676 if (g.NavLayer == ImGuiNavLayer_Main && focus_window)
10677 focus_window = NavRestoreLastChildNavWindow(window: focus_window);
10678 FocusWindow(window: focus_window);
10679 }
10680 }
10681}
10682
10683// Close the popup we have begin-ed into.
10684void ImGui::CloseCurrentPopup()
10685{
10686 ImGuiContext& g = *GImGui;
10687 int popup_idx = g.BeginPopupStack.Size - 1;
10688 if (popup_idx < 0 || popup_idx >= g.OpenPopupStack.Size || g.BeginPopupStack[popup_idx].PopupId != g.OpenPopupStack[popup_idx].PopupId)
10689 return;
10690
10691 // Closing a menu closes its top-most parent popup (unless a modal)
10692 while (popup_idx > 0)
10693 {
10694 ImGuiWindow* popup_window = g.OpenPopupStack[popup_idx].Window;
10695 ImGuiWindow* parent_popup_window = g.OpenPopupStack[popup_idx - 1].Window;
10696 bool close_parent = false;
10697 if (popup_window && (popup_window->Flags & ImGuiWindowFlags_ChildMenu))
10698 if (parent_popup_window && !(parent_popup_window->Flags & ImGuiWindowFlags_MenuBar))
10699 close_parent = true;
10700 if (!close_parent)
10701 break;
10702 popup_idx--;
10703 }
10704 IMGUI_DEBUG_LOG_POPUP("[popup] CloseCurrentPopup %d -> %d\n", g.BeginPopupStack.Size - 1, popup_idx);
10705 ClosePopupToLevel(remaining: popup_idx, restore_focus_to_window_under_popup: true);
10706
10707 // A common pattern is to close a popup when selecting a menu item/selectable that will open another window.
10708 // To improve this usage pattern, we avoid nav highlight for a single frame in the parent window.
10709 // Similarly, we could avoid mouse hover highlight in this window but it is less visually problematic.
10710 if (ImGuiWindow* window = g.NavWindow)
10711 window->DC.NavHideHighlightOneFrame = true;
10712}
10713
10714// Attention! BeginPopup() adds default flags which BeginPopupEx()!
10715bool ImGui::BeginPopupEx(ImGuiID id, ImGuiWindowFlags flags)
10716{
10717 ImGuiContext& g = *GImGui;
10718 if (!IsPopupOpen(id, popup_flags: ImGuiPopupFlags_None))
10719 {
10720 g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
10721 return false;
10722 }
10723
10724 char name[20];
10725 if (flags & ImGuiWindowFlags_ChildMenu)
10726 ImFormatString(buf: name, IM_ARRAYSIZE(name), fmt: "##Menu_%02d", g.BeginMenuCount); // Recycle windows based on depth
10727 else
10728 ImFormatString(buf: name, IM_ARRAYSIZE(name), fmt: "##Popup_%08x", id); // Not recycling, so we can close/open during the same frame
10729
10730 flags |= ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoDocking;
10731 bool is_open = Begin(name, NULL, flags);
10732 if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display)
10733 EndPopup();
10734
10735 return is_open;
10736}
10737
10738bool ImGui::BeginPopup(const char* str_id, ImGuiWindowFlags flags)
10739{
10740 ImGuiContext& g = *GImGui;
10741 if (g.OpenPopupStack.Size <= g.BeginPopupStack.Size) // Early out for performance
10742 {
10743 g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
10744 return false;
10745 }
10746 flags |= ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings;
10747 ImGuiID id = g.CurrentWindow->GetID(str: str_id);
10748 return BeginPopupEx(id, flags);
10749}
10750
10751// If 'p_open' is specified for a modal popup window, the popup will have a regular close button which will close the popup.
10752// 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.
10753bool ImGui::BeginPopupModal(const char* name, bool* p_open, ImGuiWindowFlags flags)
10754{
10755 ImGuiContext& g = *GImGui;
10756 ImGuiWindow* window = g.CurrentWindow;
10757 const ImGuiID id = window->GetID(str: name);
10758 if (!IsPopupOpen(id, popup_flags: ImGuiPopupFlags_None))
10759 {
10760 g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
10761 return false;
10762 }
10763
10764 // Center modal windows by default for increased visibility
10765 // (this won't really last as settings will kick in, and is mostly for backward compatibility. user may do the same themselves)
10766 // FIXME: Should test for (PosCond & window->SetWindowPosAllowFlags) with the upcoming window.
10767 if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasPos) == 0)
10768 {
10769 const ImGuiViewport* viewport = window->WasActive ? window->Viewport : GetMainViewport(); // FIXME-VIEWPORT: What may be our reference viewport?
10770 SetNextWindowPos(pos: viewport->GetCenter(), cond: ImGuiCond_FirstUseEver, pivot: ImVec2(0.5f, 0.5f));
10771 }
10772
10773 flags |= ImGuiWindowFlags_Popup | ImGuiWindowFlags_Modal | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDocking;
10774 const bool is_open = Begin(name, p_open, flags);
10775 if (!is_open || (p_open && !*p_open)) // NB: is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
10776 {
10777 EndPopup();
10778 if (is_open)
10779 ClosePopupToLevel(remaining: g.BeginPopupStack.Size, restore_focus_to_window_under_popup: true);
10780 return false;
10781 }
10782 return is_open;
10783}
10784
10785void ImGui::EndPopup()
10786{
10787 ImGuiContext& g = *GImGui;
10788 ImGuiWindow* window = g.CurrentWindow;
10789 IM_ASSERT(window->Flags & ImGuiWindowFlags_Popup); // Mismatched BeginPopup()/EndPopup() calls
10790 IM_ASSERT(g.BeginPopupStack.Size > 0);
10791
10792 // Make all menus and popups wrap around for now, may need to expose that policy (e.g. focus scope could include wrap/loop policy flags used by new move requests)
10793 if (g.NavWindow == window)
10794 NavMoveRequestTryWrapping(window, move_flags: ImGuiNavMoveFlags_LoopY);
10795
10796 // Child-popups don't need to be laid out
10797 IM_ASSERT(g.WithinEndChild == false);
10798 if (window->Flags & ImGuiWindowFlags_ChildWindow)
10799 g.WithinEndChild = true;
10800 End();
10801 g.WithinEndChild = false;
10802}
10803
10804// Helper to open a popup if mouse button is released over the item
10805// - This is essentially the same as BeginPopupContextItem() but without the trailing BeginPopup()
10806void ImGui::OpenPopupOnItemClick(const char* str_id, ImGuiPopupFlags popup_flags)
10807{
10808 ImGuiContext& g = *GImGui;
10809 ImGuiWindow* window = g.CurrentWindow;
10810 int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_);
10811 if (IsMouseReleased(button: mouse_button) && IsItemHovered(flags: ImGuiHoveredFlags_AllowWhenBlockedByPopup))
10812 {
10813 ImGuiID id = str_id ? window->GetID(str: 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!
10814 IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item)
10815 OpenPopupEx(id, popup_flags);
10816 }
10817}
10818
10819// This is a helper to handle the simplest case of associating one named popup to one given widget.
10820// - To create a popup associated to the last item, you generally want to pass a NULL value to str_id.
10821// - To create a popup with a specific identifier, pass it in str_id.
10822// - This is useful when using using BeginPopupContextItem() on an item which doesn't have an identifier, e.g. a Text() call.
10823// - This is useful when multiple code locations may want to manipulate/open the same popup, given an explicit id.
10824// - You may want to handle the whole on user side if you have specific needs (e.g. tweaking IsItemHovered() parameters).
10825// This is essentially the same as:
10826// id = str_id ? GetID(str_id) : GetItemID();
10827// OpenPopupOnItemClick(str_id, ImGuiPopupFlags_MouseButtonRight);
10828// return BeginPopup(id);
10829// Which is essentially the same as:
10830// id = str_id ? GetID(str_id) : GetItemID();
10831// if (IsItemHovered() && IsMouseReleased(ImGuiMouseButton_Right))
10832// OpenPopup(id);
10833// return BeginPopup(id);
10834// The main difference being that this is tweaked to avoid computing the ID twice.
10835bool ImGui::BeginPopupContextItem(const char* str_id, ImGuiPopupFlags popup_flags)
10836{
10837 ImGuiContext& g = *GImGui;
10838 ImGuiWindow* window = g.CurrentWindow;
10839 if (window->SkipItems)
10840 return false;
10841 ImGuiID id = str_id ? window->GetID(str: 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!
10842 IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item)
10843 int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_);
10844 if (IsMouseReleased(button: mouse_button) && IsItemHovered(flags: ImGuiHoveredFlags_AllowWhenBlockedByPopup))
10845 OpenPopupEx(id, popup_flags);
10846 return BeginPopupEx(id, flags: ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings);
10847}
10848
10849bool ImGui::BeginPopupContextWindow(const char* str_id, ImGuiPopupFlags popup_flags)
10850{
10851 ImGuiContext& g = *GImGui;
10852 ImGuiWindow* window = g.CurrentWindow;
10853 if (!str_id)
10854 str_id = "window_context";
10855 ImGuiID id = window->GetID(str: str_id);
10856 int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_);
10857 if (IsMouseReleased(button: mouse_button) && IsWindowHovered(flags: ImGuiHoveredFlags_AllowWhenBlockedByPopup))
10858 if (!(popup_flags & ImGuiPopupFlags_NoOpenOverItems) || !IsAnyItemHovered())
10859 OpenPopupEx(id, popup_flags);
10860 return BeginPopupEx(id, flags: ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings);
10861}
10862
10863bool ImGui::BeginPopupContextVoid(const char* str_id, ImGuiPopupFlags popup_flags)
10864{
10865 ImGuiContext& g = *GImGui;
10866 ImGuiWindow* window = g.CurrentWindow;
10867 if (!str_id)
10868 str_id = "void_context";
10869 ImGuiID id = window->GetID(str: str_id);
10870 int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_);
10871 if (IsMouseReleased(button: mouse_button) && !IsWindowHovered(flags: ImGuiHoveredFlags_AnyWindow))
10872 if (GetTopMostPopupModal() == NULL)
10873 OpenPopupEx(id, popup_flags);
10874 return BeginPopupEx(id, flags: ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings);
10875}
10876
10877// 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.)
10878// 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.
10879// (r_outer is usually equivalent to the viewport rectangle minus padding, but when multi-viewports are enabled and monitor
10880// information are available, it may represent the entire platform monitor from the frame of reference of the current viewport.
10881// this allows us to have tooltips/popups displayed out of the parent viewport.)
10882ImVec2 ImGui::FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& size, ImGuiDir* last_dir, const ImRect& r_outer, const ImRect& r_avoid, ImGuiPopupPositionPolicy policy)
10883{
10884 ImVec2 base_pos_clamped = ImClamp(v: ref_pos, mn: r_outer.Min, mx: r_outer.Max - size);
10885 //GetForegroundDrawList()->AddRect(r_avoid.Min, r_avoid.Max, IM_COL32(255,0,0,255));
10886 //GetForegroundDrawList()->AddRect(r_outer.Min, r_outer.Max, IM_COL32(0,255,0,255));
10887
10888 // Combo Box policy (we want a connecting edge)
10889 if (policy == ImGuiPopupPositionPolicy_ComboBox)
10890 {
10891 const ImGuiDir dir_prefered_order[ImGuiDir_COUNT] = { ImGuiDir_Down, ImGuiDir_Right, ImGuiDir_Left, ImGuiDir_Up };
10892 for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_COUNT; n++)
10893 {
10894 const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n];
10895 if (n != -1 && dir == *last_dir) // Already tried this direction?
10896 continue;
10897 ImVec2 pos;
10898 if (dir == ImGuiDir_Down) pos = ImVec2(r_avoid.Min.x, r_avoid.Max.y); // Below, Toward Right (default)
10899 if (dir == ImGuiDir_Right) pos = ImVec2(r_avoid.Min.x, r_avoid.Min.y - size.y); // Above, Toward Right
10900 if (dir == ImGuiDir_Left) pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Max.y); // Below, Toward Left
10901 if (dir == ImGuiDir_Up) pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Min.y - size.y); // Above, Toward Left
10902 if (!r_outer.Contains(r: ImRect(pos, pos + size)))
10903 continue;
10904 *last_dir = dir;
10905 return pos;
10906 }
10907 }
10908
10909 // Tooltip and Default popup policy
10910 // (Always first try the direction we used on the last frame, if any)
10911 if (policy == ImGuiPopupPositionPolicy_Tooltip || policy == ImGuiPopupPositionPolicy_Default)
10912 {
10913 const ImGuiDir dir_prefered_order[ImGuiDir_COUNT] = { ImGuiDir_Right, ImGuiDir_Down, ImGuiDir_Up, ImGuiDir_Left };
10914 for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_COUNT; n++)
10915 {
10916 const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n];
10917 if (n != -1 && dir == *last_dir) // Already tried this direction?
10918 continue;
10919
10920 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);
10921 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);
10922
10923 // If there's 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)
10924 if (avail_w < size.x && (dir == ImGuiDir_Left || dir == ImGuiDir_Right))
10925 continue;
10926 if (avail_h < size.y && (dir == ImGuiDir_Up || dir == ImGuiDir_Down))
10927 continue;
10928
10929 ImVec2 pos;
10930 pos.x = (dir == ImGuiDir_Left) ? r_avoid.Min.x - size.x : (dir == ImGuiDir_Right) ? r_avoid.Max.x : base_pos_clamped.x;
10931 pos.y = (dir == ImGuiDir_Up) ? r_avoid.Min.y - size.y : (dir == ImGuiDir_Down) ? r_avoid.Max.y : base_pos_clamped.y;
10932
10933 // Clamp top-left corner of popup
10934 pos.x = ImMax(lhs: pos.x, rhs: r_outer.Min.x);
10935 pos.y = ImMax(lhs: pos.y, rhs: r_outer.Min.y);
10936
10937 *last_dir = dir;
10938 return pos;
10939 }
10940 }
10941
10942 // Fallback when not enough room:
10943 *last_dir = ImGuiDir_None;
10944
10945 // For tooltip we prefer avoiding the cursor at all cost even if it means that part of the tooltip won't be visible.
10946 if (policy == ImGuiPopupPositionPolicy_Tooltip)
10947 return ref_pos + ImVec2(2, 2);
10948
10949 // Otherwise try to keep within display
10950 ImVec2 pos = ref_pos;
10951 pos.x = ImMax(lhs: ImMin(lhs: pos.x + size.x, rhs: r_outer.Max.x) - size.x, rhs: r_outer.Min.x);
10952 pos.y = ImMax(lhs: ImMin(lhs: pos.y + size.y, rhs: r_outer.Max.y) - size.y, rhs: r_outer.Min.y);
10953 return pos;
10954}
10955
10956// Note that this is used for popups, which can overlap the non work-area of individual viewports.
10957ImRect ImGui::GetPopupAllowedExtentRect(ImGuiWindow* window)
10958{
10959 ImGuiContext& g = *GImGui;
10960 ImRect r_screen;
10961 if (window->ViewportAllowPlatformMonitorExtend >= 0)
10962 {
10963 // Extent with be in the frame of reference of the given viewport (so Min is likely to be negative here)
10964 const ImGuiPlatformMonitor& monitor = g.PlatformIO.Monitors[window->ViewportAllowPlatformMonitorExtend];
10965 r_screen.Min = monitor.WorkPos;
10966 r_screen.Max = monitor.WorkPos + monitor.WorkSize;
10967 }
10968 else
10969 {
10970 // Use the full viewport area (not work area) for popups
10971 r_screen = window->Viewport->GetMainRect();
10972 }
10973 ImVec2 padding = g.Style.DisplaySafeAreaPadding;
10974 r_screen.Expand(amount: ImVec2((r_screen.GetWidth() > padding.x * 2) ? -padding.x : 0.0f, (r_screen.GetHeight() > padding.y * 2) ? -padding.y : 0.0f));
10975 return r_screen;
10976}
10977
10978ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window)
10979{
10980 ImGuiContext& g = *GImGui;
10981
10982 ImRect r_outer = GetPopupAllowedExtentRect(window);
10983 if (window->Flags & ImGuiWindowFlags_ChildMenu)
10984 {
10985 // Child menus typically request _any_ position within the parent menu item, and then we move the new menu outside the parent bounds.
10986 // This is how we end up with child menus appearing (most-commonly) on the right of the parent menu.
10987 ImGuiWindow* parent_window = window->ParentWindow;
10988 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).
10989 ImRect r_avoid;
10990 if (parent_window->DC.MenuBarAppending)
10991 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
10992 else
10993 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);
10994 return FindBestWindowPosForPopupEx(ref_pos: window->Pos, size: window->Size, last_dir: &window->AutoPosLastDirection, r_outer, r_avoid, policy: ImGuiPopupPositionPolicy_Default);
10995 }
10996 if (window->Flags & ImGuiWindowFlags_Popup)
10997 {
10998 return FindBestWindowPosForPopupEx(ref_pos: window->Pos, size: window->Size, last_dir: &window->AutoPosLastDirection, r_outer, r_avoid: ImRect(window->Pos, window->Pos), policy: ImGuiPopupPositionPolicy_Default); // Ideally we'd disable r_avoid here
10999 }
11000 if (window->Flags & ImGuiWindowFlags_Tooltip)
11001 {
11002 // Position tooltip (always follows mouse)
11003 float sc = g.Style.MouseCursorScale;
11004 ImVec2 ref_pos = NavCalcPreferredRefPos();
11005 ImRect r_avoid;
11006 if (!g.NavDisableHighlight && g.NavDisableMouseHover && !(g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos))
11007 r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 16, ref_pos.y + 8);
11008 else
11009 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.
11010 return FindBestWindowPosForPopupEx(ref_pos, size: window->Size, last_dir: &window->AutoPosLastDirection, r_outer, r_avoid, policy: ImGuiPopupPositionPolicy_Tooltip);
11011 }
11012 IM_ASSERT(0);
11013 return window->Pos;
11014}
11015
11016//-----------------------------------------------------------------------------
11017// [SECTION] KEYBOARD/GAMEPAD NAVIGATION
11018//-----------------------------------------------------------------------------
11019
11020// FIXME-NAV: The existence of SetNavID vs SetFocusID vs FocusWindow() needs to be clarified/reworked.
11021// In our terminology those should be interchangeable, yet right now this is super confusing.
11022// Those two functions are merely a legacy artifact, so at minimum naming should be clarified.
11023
11024void ImGui::SetNavWindow(ImGuiWindow* window)
11025{
11026 ImGuiContext& g = *GImGui;
11027 if (g.NavWindow != window)
11028 {
11029 IMGUI_DEBUG_LOG_FOCUS("[focus] SetNavWindow(\"%s\")\n", window ? window->Name : "<NULL>");
11030 g.NavWindow = window;
11031 }
11032 g.NavInitRequest = g.NavMoveSubmitted = g.NavMoveScoringItems = false;
11033 NavUpdateAnyRequestFlag();
11034}
11035
11036void ImGui::SetNavID(ImGuiID id, ImGuiNavLayer nav_layer, ImGuiID focus_scope_id, const ImRect& rect_rel)
11037{
11038 ImGuiContext& g = *GImGui;
11039 IM_ASSERT(g.NavWindow != NULL);
11040 IM_ASSERT(nav_layer == ImGuiNavLayer_Main || nav_layer == ImGuiNavLayer_Menu);
11041 g.NavId = id;
11042 g.NavLayer = nav_layer;
11043 g.NavFocusScopeId = focus_scope_id;
11044 g.NavWindow->NavLastIds[nav_layer] = id;
11045 g.NavWindow->NavRectRel[nav_layer] = rect_rel;
11046}
11047
11048void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window)
11049{
11050 ImGuiContext& g = *GImGui;
11051 IM_ASSERT(id != 0);
11052
11053 if (g.NavWindow != window)
11054 SetNavWindow(window);
11055
11056 // Assume that SetFocusID() is called in the context where its window->DC.NavLayerCurrent and g.CurrentFocusScopeId are valid.
11057 // Note that window may be != g.CurrentWindow (e.g. SetFocusID call in InputTextEx for multi-line text)
11058 const ImGuiNavLayer nav_layer = window->DC.NavLayerCurrent;
11059 g.NavId = id;
11060 g.NavLayer = nav_layer;
11061 g.NavFocusScopeId = g.CurrentFocusScopeId;
11062 window->NavLastIds[nav_layer] = id;
11063 if (g.LastItemData.ID == id)
11064 window->NavRectRel[nav_layer] = WindowRectAbsToRel(window, r: g.LastItemData.NavRect);
11065
11066 if (g.ActiveIdSource == ImGuiInputSource_Nav)
11067 g.NavDisableMouseHover = true;
11068 else
11069 g.NavDisableHighlight = true;
11070}
11071
11072ImGuiDir ImGetDirQuadrantFromDelta(float dx, float dy)
11073{
11074 if (ImFabs(dx) > ImFabs(dy))
11075 return (dx > 0.0f) ? ImGuiDir_Right : ImGuiDir_Left;
11076 return (dy > 0.0f) ? ImGuiDir_Down : ImGuiDir_Up;
11077}
11078
11079static float inline NavScoreItemDistInterval(float a0, float a1, float b0, float b1)
11080{
11081 if (a1 < b0)
11082 return a1 - b0;
11083 if (b1 < a0)
11084 return a0 - b1;
11085 return 0.0f;
11086}
11087
11088static void inline NavClampRectToVisibleAreaForMoveDir(ImGuiDir move_dir, ImRect& r, const ImRect& clip_rect)
11089{
11090 if (move_dir == ImGuiDir_Left || move_dir == ImGuiDir_Right)
11091 {
11092 r.Min.y = ImClamp(v: r.Min.y, mn: clip_rect.Min.y, mx: clip_rect.Max.y);
11093 r.Max.y = ImClamp(v: r.Max.y, mn: clip_rect.Min.y, mx: clip_rect.Max.y);
11094 }
11095 else // FIXME: PageUp/PageDown are leaving move_dir == None
11096 {
11097 r.Min.x = ImClamp(v: r.Min.x, mn: clip_rect.Min.x, mx: clip_rect.Max.x);
11098 r.Max.x = ImClamp(v: r.Max.x, mn: clip_rect.Min.x, mx: clip_rect.Max.x);
11099 }
11100}
11101
11102// Scoring function for gamepad/keyboard directional navigation. Based on https://gist.github.com/rygorous/6981057
11103static bool ImGui::NavScoreItem(ImGuiNavItemData* result)
11104{
11105 ImGuiContext& g = *GImGui;
11106 ImGuiWindow* window = g.CurrentWindow;
11107 if (g.NavLayer != window->DC.NavLayerCurrent)
11108 return false;
11109
11110 // FIXME: Those are not good variables names
11111 ImRect cand = g.LastItemData.NavRect; // Current item nav rectangle
11112 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)
11113 g.NavScoringDebugCount++;
11114
11115 // When entering through a NavFlattened border, we consider child window items as fully clipped for scoring
11116 if (window->ParentWindow == g.NavWindow)
11117 {
11118 IM_ASSERT((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened);
11119 if (!window->ClipRect.Overlaps(r: cand))
11120 return false;
11121 cand.ClipWithFull(r: window->ClipRect); // This allows the scored item to not overlap other candidates in the parent window
11122 }
11123
11124 // 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)
11125 // For example, this ensures that items in one column are not reached when moving vertically from items in another column.
11126 NavClampRectToVisibleAreaForMoveDir(move_dir: g.NavMoveClipDir, r&: cand, clip_rect: window->ClipRect);
11127
11128 // Compute distance between boxes
11129 // FIXME-NAV: Introducing biases for vertical navigation, needs to be removed.
11130 float dbx = NavScoreItemDistInterval(a0: cand.Min.x, a1: cand.Max.x, b0: curr.Min.x, b1: curr.Max.x);
11131 float dby = NavScoreItemDistInterval(a0: ImLerp(a: cand.Min.y, b: cand.Max.y, t: 0.2f), a1: ImLerp(a: cand.Min.y, b: cand.Max.y, t: 0.8f), b0: ImLerp(a: curr.Min.y, b: curr.Max.y, t: 0.2f), b1: ImLerp(a: curr.Min.y, b: curr.Max.y, t: 0.8f)); // Scale down on Y to keep using box-distance for vertically touching items
11132 if (dby != 0.0f && dbx != 0.0f)
11133 dbx = (dbx / 1000.0f) + ((dbx > 0.0f) ? +1.0f : -1.0f);
11134 float dist_box = ImFabs(dbx) + ImFabs(dby);
11135
11136 // 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)
11137 float dcx = (cand.Min.x + cand.Max.x) - (curr.Min.x + curr.Max.x);
11138 float dcy = (cand.Min.y + cand.Max.y) - (curr.Min.y + curr.Max.y);
11139 float dist_center = ImFabs(dcx) + ImFabs(dcy); // L1 metric (need this for our connectedness guarantee)
11140
11141 // Determine which quadrant of 'curr' our candidate item 'cand' lies in based on distance
11142 ImGuiDir quadrant;
11143 float dax = 0.0f, day = 0.0f, dist_axial = 0.0f;
11144 if (dbx != 0.0f || dby != 0.0f)
11145 {
11146 // For non-overlapping boxes, use distance between boxes
11147 dax = dbx;
11148 day = dby;
11149 dist_axial = dist_box;
11150 quadrant = ImGetDirQuadrantFromDelta(dx: dbx, dy: dby);
11151 }
11152 else if (dcx != 0.0f || dcy != 0.0f)
11153 {
11154 // For overlapping boxes with different centers, use distance between centers
11155 dax = dcx;
11156 day = dcy;
11157 dist_axial = dist_center;
11158 quadrant = ImGetDirQuadrantFromDelta(dx: dcx, dy: dcy);
11159 }
11160 else
11161 {
11162 // 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)
11163 quadrant = (g.LastItemData.ID < g.NavId) ? ImGuiDir_Left : ImGuiDir_Right;
11164 }
11165
11166#if IMGUI_DEBUG_NAV_SCORING
11167 char buf[128];
11168 if (IsMouseHoveringRect(cand.Min, cand.Max))
11169 {
11170 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]);
11171 ImDrawList* draw_list = GetForegroundDrawList(window);
11172 draw_list->AddRect(curr.Min, curr.Max, IM_COL32(255,200,0,100));
11173 draw_list->AddRect(cand.Min, cand.Max, IM_COL32(255,255,0,200));
11174 draw_list->AddRectFilled(cand.Max - ImVec2(4, 4), cand.Max + CalcTextSize(buf) + ImVec2(4, 4), IM_COL32(40,0,0,150));
11175 draw_list->AddText(cand.Max, ~0U, buf);
11176 }
11177 else if (g.IO.KeyCtrl) // Hold to preview score in matching quadrant. Press C to rotate.
11178 {
11179 if (quadrant == g.NavMoveDir)
11180 {
11181 ImFormatString(buf, IM_ARRAYSIZE(buf), "%.0f/%.0f", dist_box, dist_center);
11182 ImDrawList* draw_list = GetForegroundDrawList(window);
11183 draw_list->AddRectFilled(cand.Min, cand.Max, IM_COL32(255, 0, 0, 200));
11184 draw_list->AddText(cand.Min, IM_COL32(255, 255, 255, 255), buf);
11185 }
11186 }
11187#endif
11188
11189 // Is it in the quadrant we're interested in moving to?
11190 bool new_best = false;
11191 const ImGuiDir move_dir = g.NavMoveDir;
11192 if (quadrant == move_dir)
11193 {
11194 // Does it beat the current best candidate?
11195 if (dist_box < result->DistBox)
11196 {
11197 result->DistBox = dist_box;
11198 result->DistCenter = dist_center;
11199 return true;
11200 }
11201 if (dist_box == result->DistBox)
11202 {
11203 // Try using distance between center points to break ties
11204 if (dist_center < result->DistCenter)
11205 {
11206 result->DistCenter = dist_center;
11207 new_best = true;
11208 }
11209 else if (dist_center == result->DistCenter)
11210 {
11211 // Still tied! we need to be extra-careful to make sure everything gets linked properly. We consistently break ties by symbolically moving "later" items
11212 // (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),
11213 // 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.
11214 if (((move_dir == ImGuiDir_Up || move_dir == ImGuiDir_Down) ? dby : dbx) < 0.0f) // moving bj to the right/down decreases distance
11215 new_best = true;
11216 }
11217 }
11218 }
11219
11220 // 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
11221 // are found, so it only augments the graph produced by the above method using extra links. (important, since it doesn't guarantee strong connectedness)
11222 // 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.
11223 // 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.
11224 // Disabling it may lead to disconnected graphs when nodes are very spaced out on different axis. Perhaps consider offering this as an option?
11225 if (result->DistBox == FLT_MAX && dist_axial < result->DistAxial) // Check axial match
11226 if (g.NavLayer == ImGuiNavLayer_Menu && !(g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))
11227 if ((move_dir == ImGuiDir_Left && dax < 0.0f) || (move_dir == ImGuiDir_Right && dax > 0.0f) || (move_dir == ImGuiDir_Up && day < 0.0f) || (move_dir == ImGuiDir_Down && day > 0.0f))
11228 {
11229 result->DistAxial = dist_axial;
11230 new_best = true;
11231 }
11232
11233 return new_best;
11234}
11235
11236static void ImGui::NavApplyItemToResult(ImGuiNavItemData* result)
11237{
11238 ImGuiContext& g = *GImGui;
11239 ImGuiWindow* window = g.CurrentWindow;
11240 result->Window = window;
11241 result->ID = g.LastItemData.ID;
11242 result->FocusScopeId = g.CurrentFocusScopeId;
11243 result->InFlags = g.LastItemData.InFlags;
11244 result->RectRel = WindowRectAbsToRel(window, r: g.LastItemData.NavRect);
11245}
11246
11247// We get there when either NavId == id, or when g.NavAnyRequest is set (which is updated by NavUpdateAnyRequestFlag above)
11248// This is called after LastItemData is set.
11249static void ImGui::NavProcessItem()
11250{
11251 ImGuiContext& g = *GImGui;
11252 ImGuiWindow* window = g.CurrentWindow;
11253 const ImGuiID id = g.LastItemData.ID;
11254 const ImRect nav_bb = g.LastItemData.NavRect;
11255 const ImGuiItemFlags item_flags = g.LastItemData.InFlags;
11256
11257 // Process Init Request
11258 if (g.NavInitRequest && g.NavLayer == window->DC.NavLayerCurrent && (item_flags & ImGuiItemFlags_Disabled) == 0)
11259 {
11260 // Even if 'ImGuiItemFlags_NoNavDefaultFocus' is on (typically collapse/close button) we record the first ResultId so they can be used as a fallback
11261 const bool candidate_for_nav_default_focus = (item_flags & ImGuiItemFlags_NoNavDefaultFocus) == 0;
11262 if (candidate_for_nav_default_focus || g.NavInitResultId == 0)
11263 {
11264 g.NavInitResultId = id;
11265 g.NavInitResultRectRel = WindowRectAbsToRel(window, r: nav_bb);
11266 }
11267 if (candidate_for_nav_default_focus)
11268 {
11269 g.NavInitRequest = false; // Found a match, clear request
11270 NavUpdateAnyRequestFlag();
11271 }
11272 }
11273
11274 // Process Move Request (scoring for navigation)
11275 // FIXME-NAV: Consider policy for double scoring (scoring from NavScoringRect + scoring from a rect wrapped according to current wrapping policy)
11276 if (g.NavMoveScoringItems)
11277 {
11278 const bool is_tab_stop = (item_flags & ImGuiItemFlags_Inputable) && (item_flags & (ImGuiItemFlags_NoTabStop | ImGuiItemFlags_Disabled)) == 0;
11279 const bool is_tabbing = (g.NavMoveFlags & ImGuiNavMoveFlags_Tabbing) != 0;
11280 if (is_tabbing)
11281 {
11282 if (is_tab_stop || (g.NavMoveFlags & ImGuiNavMoveFlags_FocusApi))
11283 NavProcessItemForTabbingRequest(id);
11284 }
11285 else if ((g.NavId != id || (g.NavMoveFlags & ImGuiNavMoveFlags_AllowCurrentNavId)) && !(item_flags & ImGuiItemFlags_Disabled))
11286 {
11287 ImGuiNavItemData* result = (window == g.NavWindow) ? &g.NavMoveResultLocal : &g.NavMoveResultOther;
11288 if (!is_tabbing)
11289 {
11290 if (NavScoreItem(result))
11291 NavApplyItemToResult(result);
11292
11293 // Features like PageUp/PageDown need to maintain a separate score for the visible set of items.
11294 const float VISIBLE_RATIO = 0.70f;
11295 if ((g.NavMoveFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet) && window->ClipRect.Overlaps(r: nav_bb))
11296 if (ImClamp(v: nav_bb.Max.y, mn: window->ClipRect.Min.y, mx: window->ClipRect.Max.y) - ImClamp(v: nav_bb.Min.y, mn: window->ClipRect.Min.y, mx: window->ClipRect.Max.y) >= (nav_bb.Max.y - nav_bb.Min.y) * VISIBLE_RATIO)
11297 if (NavScoreItem(result: &g.NavMoveResultLocalVisible))
11298 NavApplyItemToResult(result: &g.NavMoveResultLocalVisible);
11299 }
11300 }
11301 }
11302
11303 // Update window-relative bounding box of navigated item
11304 if (g.NavId == id)
11305 {
11306 if (g.NavWindow != window)
11307 SetNavWindow(window); // Always refresh g.NavWindow, because some operations such as FocusItem() may not have a window.
11308 g.NavLayer = window->DC.NavLayerCurrent;
11309 g.NavFocusScopeId = g.CurrentFocusScopeId;
11310 g.NavIdIsAlive = true;
11311 window->NavRectRel[window->DC.NavLayerCurrent] = WindowRectAbsToRel(window, r: nav_bb); // Store item bounding box (relative to window position)
11312 }
11313}
11314
11315// Handle "scoring" of an item for a tabbing/focusing request initiated by NavUpdateCreateTabbingRequest().
11316// Note that SetKeyboardFocusHere() API calls are considered tabbing requests!
11317// - Case 1: no nav/active id: set result to first eligible item, stop storing.
11318// - Case 2: tab forward: on ref id set counter, on counter elapse store result
11319// - Case 3: tab forward wrap: set result to first eligible item (preemptively), on ref id set counter, on next frame if counter hasn't elapsed store result. // FIXME-TABBING: Could be done as a next-frame forwarded request
11320// - Case 4: tab backward: store all results, on ref id pick prev, stop storing
11321// - Case 5: tab backward wrap: store all results, on ref id if no result keep storing until last // FIXME-TABBING: Could be done as next-frame forwarded requested
11322void ImGui::NavProcessItemForTabbingRequest(ImGuiID id)
11323{
11324 ImGuiContext& g = *GImGui;
11325
11326 // Always store in NavMoveResultLocal (unlike directional request which uses NavMoveResultOther on sibling/flattened windows)
11327 ImGuiNavItemData* result = &g.NavMoveResultLocal;
11328 if (g.NavTabbingDir == +1)
11329 {
11330 // Tab Forward or SetKeyboardFocusHere() with >= 0
11331 if (g.NavTabbingResultFirst.ID == 0)
11332 NavApplyItemToResult(result: &g.NavTabbingResultFirst);
11333 if (--g.NavTabbingCounter == 0)
11334 NavMoveRequestResolveWithLastItem(result);
11335 else if (g.NavId == id)
11336 g.NavTabbingCounter = 1;
11337 }
11338 else if (g.NavTabbingDir == -1)
11339 {
11340 // Tab Backward
11341 if (g.NavId == id)
11342 {
11343 if (result->ID)
11344 {
11345 g.NavMoveScoringItems = false;
11346 NavUpdateAnyRequestFlag();
11347 }
11348 }
11349 else
11350 {
11351 NavApplyItemToResult(result);
11352 }
11353 }
11354 else if (g.NavTabbingDir == 0)
11355 {
11356 // Tab Init
11357 if (g.NavTabbingResultFirst.ID == 0)
11358 NavMoveRequestResolveWithLastItem(result: &g.NavTabbingResultFirst);
11359 }
11360}
11361
11362bool ImGui::NavMoveRequestButNoResultYet()
11363{
11364 ImGuiContext& g = *GImGui;
11365 return g.NavMoveScoringItems && g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0;
11366}
11367
11368// FIXME: ScoringRect is not set
11369void ImGui::NavMoveRequestSubmit(ImGuiDir move_dir, ImGuiDir clip_dir, ImGuiNavMoveFlags move_flags, ImGuiScrollFlags scroll_flags)
11370{
11371 ImGuiContext& g = *GImGui;
11372 IM_ASSERT(g.NavWindow != NULL);
11373
11374 if (move_flags & ImGuiNavMoveFlags_Tabbing)
11375 move_flags |= ImGuiNavMoveFlags_AllowCurrentNavId;
11376
11377 g.NavMoveSubmitted = g.NavMoveScoringItems = true;
11378 g.NavMoveDir = move_dir;
11379 g.NavMoveDirForDebug = move_dir;
11380 g.NavMoveClipDir = clip_dir;
11381 g.NavMoveFlags = move_flags;
11382 g.NavMoveScrollFlags = scroll_flags;
11383 g.NavMoveForwardToNextFrame = false;
11384 g.NavMoveKeyMods = g.IO.KeyMods;
11385 g.NavMoveResultLocal.Clear();
11386 g.NavMoveResultLocalVisible.Clear();
11387 g.NavMoveResultOther.Clear();
11388 g.NavTabbingCounter = 0;
11389 g.NavTabbingResultFirst.Clear();
11390 NavUpdateAnyRequestFlag();
11391}
11392
11393void ImGui::NavMoveRequestResolveWithLastItem(ImGuiNavItemData* result)
11394{
11395 ImGuiContext& g = *GImGui;
11396 g.NavMoveScoringItems = false; // Ensure request doesn't need more processing
11397 NavApplyItemToResult(result);
11398 NavUpdateAnyRequestFlag();
11399}
11400
11401void ImGui::NavMoveRequestCancel()
11402{
11403 ImGuiContext& g = *GImGui;
11404 g.NavMoveSubmitted = g.NavMoveScoringItems = false;
11405 NavUpdateAnyRequestFlag();
11406}
11407
11408// Forward will reuse the move request again on the next frame (generally with modifications done to it)
11409void ImGui::NavMoveRequestForward(ImGuiDir move_dir, ImGuiDir clip_dir, ImGuiNavMoveFlags move_flags, ImGuiScrollFlags scroll_flags)
11410{
11411 ImGuiContext& g = *GImGui;
11412 IM_ASSERT(g.NavMoveForwardToNextFrame == false);
11413 NavMoveRequestCancel();
11414 g.NavMoveForwardToNextFrame = true;
11415 g.NavMoveDir = move_dir;
11416 g.NavMoveClipDir = clip_dir;
11417 g.NavMoveFlags = move_flags | ImGuiNavMoveFlags_Forwarded;
11418 g.NavMoveScrollFlags = scroll_flags;
11419}
11420
11421// Navigation wrap-around logic is delayed to the end of the frame because this operation is only valid after entire
11422// popup is assembled and in case of appended popups it is not clear which EndPopup() call is final.
11423void ImGui::NavMoveRequestTryWrapping(ImGuiWindow* window, ImGuiNavMoveFlags wrap_flags)
11424{
11425 ImGuiContext& g = *GImGui;
11426 IM_ASSERT(wrap_flags != 0); // Call with _WrapX, _WrapY, _LoopX, _LoopY
11427 // In theory we should test for NavMoveRequestButNoResultYet() but there's no point doing it, NavEndFrame() will do the same test
11428 if (g.NavWindow == window && g.NavMoveScoringItems && g.NavLayer == ImGuiNavLayer_Main)
11429 g.NavMoveFlags |= wrap_flags;
11430}
11431
11432// FIXME: This could be replaced by updating a frame number in each window when (window == NavWindow) and (NavLayer == 0).
11433// This way we could find the last focused window among our children. It would be much less confusing this way?
11434static void ImGui::NavSaveLastChildNavWindowIntoParent(ImGuiWindow* nav_window)
11435{
11436 ImGuiWindow* parent = nav_window;
11437 while (parent && parent->RootWindow != parent && (parent->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0)
11438 parent = parent->ParentWindow;
11439 if (parent && parent != nav_window)
11440 parent->NavLastChildNavWindow = nav_window;
11441}
11442
11443// Restore the last focused child.
11444// Call when we are expected to land on the Main Layer (0) after FocusWindow()
11445static ImGuiWindow* ImGui::NavRestoreLastChildNavWindow(ImGuiWindow* window)
11446{
11447 if (window->NavLastChildNavWindow && window->NavLastChildNavWindow->WasActive)
11448 return window->NavLastChildNavWindow;
11449 if (window->DockNodeAsHost && window->DockNodeAsHost->TabBar)
11450 if (ImGuiTabItem* tab = TabBarFindMostRecentlySelectedTabForActiveWindow(tab_bar: window->DockNodeAsHost->TabBar))
11451 return tab->Window;
11452 return window;
11453}
11454
11455void ImGui::NavRestoreLayer(ImGuiNavLayer layer)
11456{
11457 ImGuiContext& g = *GImGui;
11458 if (layer == ImGuiNavLayer_Main)
11459 {
11460 ImGuiWindow* prev_nav_window = g.NavWindow;
11461 g.NavWindow = NavRestoreLastChildNavWindow(window: g.NavWindow); // FIXME-NAV: Should clear ongoing nav requests?
11462 if (prev_nav_window)
11463 IMGUI_DEBUG_LOG_FOCUS("[focus] NavRestoreLayer: from \"%s\" to SetNavWindow(\"%s\")\n", prev_nav_window->Name, g.NavWindow->Name);
11464 }
11465 ImGuiWindow* window = g.NavWindow;
11466 if (window->NavLastIds[layer] != 0)
11467 {
11468 SetNavID(id: window->NavLastIds[layer], nav_layer: layer, focus_scope_id: 0, rect_rel: window->NavRectRel[layer]);
11469 }
11470 else
11471 {
11472 g.NavLayer = layer;
11473 NavInitWindow(window, force_reinit: true);
11474 }
11475}
11476
11477void ImGui::NavRestoreHighlightAfterMove()
11478{
11479 ImGuiContext& g = *GImGui;
11480 g.NavDisableHighlight = false;
11481 g.NavDisableMouseHover = g.NavMousePosDirty = true;
11482}
11483
11484static inline void ImGui::NavUpdateAnyRequestFlag()
11485{
11486 ImGuiContext& g = *GImGui;
11487 g.NavAnyRequest = g.NavMoveScoringItems || g.NavInitRequest || (IMGUI_DEBUG_NAV_SCORING && g.NavWindow != NULL);
11488 if (g.NavAnyRequest)
11489 IM_ASSERT(g.NavWindow != NULL);
11490}
11491
11492// This needs to be called before we submit any widget (aka in or before Begin)
11493void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit)
11494{
11495 // FIXME: ChildWindow test here is wrong for docking
11496 ImGuiContext& g = *GImGui;
11497 IM_ASSERT(window == g.NavWindow);
11498
11499 if (window->Flags & ImGuiWindowFlags_NoNavInputs)
11500 {
11501 g.NavId = 0;
11502 g.NavFocusScopeId = window->NavRootFocusScopeId;
11503 return;
11504 }
11505
11506 bool init_for_nav = false;
11507 if (window == window->RootWindow || (window->Flags & ImGuiWindowFlags_Popup) || (window->NavLastIds[0] == 0) || force_reinit)
11508 init_for_nav = true;
11509 IMGUI_DEBUG_LOG_NAV("[nav] NavInitRequest: from NavInitWindow(), init_for_nav=%d, window=\"%s\", layer=%d\n", init_for_nav, window->Name, g.NavLayer);
11510 if (init_for_nav)
11511 {
11512 SetNavID(id: 0, nav_layer: g.NavLayer, focus_scope_id: window->NavRootFocusScopeId, rect_rel: ImRect());
11513 g.NavInitRequest = true;
11514 g.NavInitRequestFromMove = false;
11515 g.NavInitResultId = 0;
11516 g.NavInitResultRectRel = ImRect();
11517 NavUpdateAnyRequestFlag();
11518 }
11519 else
11520 {
11521 g.NavId = window->NavLastIds[0];
11522 g.NavFocusScopeId = window->NavRootFocusScopeId;
11523 }
11524}
11525
11526static ImVec2 ImGui::NavCalcPreferredRefPos()
11527{
11528 ImGuiContext& g = *GImGui;
11529 ImGuiWindow* window = g.NavWindow;
11530 if (g.NavDisableHighlight || !g.NavDisableMouseHover || !window)
11531 {
11532 // Mouse (we need a fallback in case the mouse becomes invalid after being used)
11533 // The +1.0f offset when stored by OpenPopupEx() allows reopening this or another popup (same or another mouse button) while not moving the mouse, it is pretty standard.
11534 // In theory we could move that +1.0f offset in OpenPopupEx()
11535 ImVec2 p = IsMousePosValid(mouse_pos: &g.IO.MousePos) ? g.IO.MousePos : g.MouseLastValidPos;
11536 return ImVec2(p.x + 1.0f, p.y);
11537 }
11538 else
11539 {
11540 // When navigation is active and mouse is disabled, pick a position around the bottom left of the currently navigated item
11541 // Take account of upcoming scrolling (maybe set mouse pos should be done in EndFrame?)
11542 ImRect rect_rel = WindowRectRelToAbs(window, r: window->NavRectRel[g.NavLayer]);
11543 if (window->LastFrameActive != g.FrameCount && (window->ScrollTarget.x != FLT_MAX || window->ScrollTarget.y != FLT_MAX))
11544 {
11545 ImVec2 next_scroll = CalcNextScrollFromScrollTargetAndClamp(window);
11546 rect_rel.Translate(d: window->Scroll - next_scroll);
11547 }
11548 ImVec2 pos = ImVec2(rect_rel.Min.x + ImMin(lhs: g.Style.FramePadding.x * 4, rhs: rect_rel.GetWidth()), rect_rel.Max.y - ImMin(lhs: g.Style.FramePadding.y, rhs: rect_rel.GetHeight()));
11549 ImGuiViewport* viewport = window->Viewport;
11550 return ImFloor(v: ImClamp(v: pos, mn: viewport->Pos, mx: 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.
11551 }
11552}
11553
11554float ImGui::GetNavTweakPressedAmount(ImGuiAxis axis)
11555{
11556 ImGuiContext& g = *GImGui;
11557 float repeat_delay, repeat_rate;
11558 GetTypematicRepeatRate(flags: ImGuiInputFlags_RepeatRateNavTweak, repeat_delay: &repeat_delay, repeat_rate: &repeat_rate);
11559
11560 ImGuiKey key_less, key_more;
11561 if (g.NavInputSource == ImGuiInputSource_Gamepad)
11562 {
11563 key_less = (axis == ImGuiAxis_X) ? ImGuiKey_GamepadDpadLeft : ImGuiKey_GamepadDpadUp;
11564 key_more = (axis == ImGuiAxis_X) ? ImGuiKey_GamepadDpadRight : ImGuiKey_GamepadDpadDown;
11565 }
11566 else
11567 {
11568 key_less = (axis == ImGuiAxis_X) ? ImGuiKey_LeftArrow : ImGuiKey_UpArrow;
11569 key_more = (axis == ImGuiAxis_X) ? ImGuiKey_RightArrow : ImGuiKey_DownArrow;
11570 }
11571 float amount = (float)GetKeyPressedAmount(key: key_more, repeat_delay, repeat_rate) - (float)GetKeyPressedAmount(key: key_less, repeat_delay, repeat_rate);
11572 if (amount != 0.0f && IsKeyDown(key: key_less) && IsKeyDown(key: key_more)) // Cancel when opposite directions are held, regardless of repeat phase
11573 amount = 0.0f;
11574 return amount;
11575}
11576
11577static void ImGui::NavUpdate()
11578{
11579 ImGuiContext& g = *GImGui;
11580 ImGuiIO& io = g.IO;
11581
11582 io.WantSetMousePos = false;
11583 //if (g.NavScoringDebugCount > 0) IMGUI_DEBUG_LOG_NAV("[nav] NavScoringDebugCount %d for '%s' layer %d (Init:%d, Move:%d)\n", g.NavScoringDebugCount, g.NavWindow ? g.NavWindow->Name : "NULL", g.NavLayer, g.NavInitRequest || g.NavInitResultId != 0, g.NavMoveRequest);
11584
11585 // Set input source based on which keys are last pressed (as some features differs when used with Gamepad vs Keyboard)
11586 // FIXME-NAV: Now that keys are separated maybe we can get rid of NavInputSource?
11587 const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
11588 const ImGuiKey nav_gamepad_keys_to_change_source[] = { ImGuiKey_GamepadFaceRight, ImGuiKey_GamepadFaceLeft, ImGuiKey_GamepadFaceUp, ImGuiKey_GamepadFaceDown, ImGuiKey_GamepadDpadRight, ImGuiKey_GamepadDpadLeft, ImGuiKey_GamepadDpadUp, ImGuiKey_GamepadDpadDown };
11589 if (nav_gamepad_active)
11590 for (ImGuiKey key : nav_gamepad_keys_to_change_source)
11591 if (IsKeyDown(key))
11592 g.NavInputSource = ImGuiInputSource_Gamepad;
11593 const bool nav_keyboard_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0;
11594 const ImGuiKey nav_keyboard_keys_to_change_source[] = { ImGuiKey_Space, ImGuiKey_Enter, ImGuiKey_Escape, ImGuiKey_RightArrow, ImGuiKey_LeftArrow, ImGuiKey_UpArrow, ImGuiKey_DownArrow };
11595 if (nav_keyboard_active)
11596 for (ImGuiKey key : nav_keyboard_keys_to_change_source)
11597 if (IsKeyDown(key))
11598 g.NavInputSource = ImGuiInputSource_Keyboard;
11599
11600 // Process navigation init request (select first/default focus)
11601 if (g.NavInitResultId != 0)
11602 NavInitRequestApplyResult();
11603 g.NavInitRequest = false;
11604 g.NavInitRequestFromMove = false;
11605 g.NavInitResultId = 0;
11606 g.NavJustMovedToId = 0;
11607
11608 // Process navigation move request
11609 if (g.NavMoveSubmitted)
11610 NavMoveRequestApplyResult();
11611 g.NavTabbingCounter = 0;
11612 g.NavMoveSubmitted = g.NavMoveScoringItems = false;
11613
11614 // Schedule mouse position update (will be done at the bottom of this function, after 1) processing all move requests and 2) updating scrolling)
11615 bool set_mouse_pos = false;
11616 if (g.NavMousePosDirty && g.NavIdIsAlive)
11617 if (!g.NavDisableHighlight && g.NavDisableMouseHover && g.NavWindow)
11618 set_mouse_pos = true;
11619 g.NavMousePosDirty = false;
11620 IM_ASSERT(g.NavLayer == ImGuiNavLayer_Main || g.NavLayer == ImGuiNavLayer_Menu);
11621
11622 // Store our return window (for returning from Menu Layer to Main Layer) and clear it as soon as we step back in our own Layer 0
11623 if (g.NavWindow)
11624 NavSaveLastChildNavWindowIntoParent(nav_window: g.NavWindow);
11625 if (g.NavWindow && g.NavWindow->NavLastChildNavWindow != NULL && g.NavLayer == ImGuiNavLayer_Main)
11626 g.NavWindow->NavLastChildNavWindow = NULL;
11627
11628 // Update CTRL+TAB and Windowing features (hold Square to move/resize/etc.)
11629 NavUpdateWindowing();
11630
11631 // Set output flags for user application
11632 io.NavActive = (nav_keyboard_active || nav_gamepad_active) && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs);
11633 io.NavVisible = (io.NavActive && g.NavId != 0 && !g.NavDisableHighlight) || (g.NavWindowingTarget != NULL);
11634
11635 // Process NavCancel input (to close a popup, get back to parent, clear focus)
11636 NavUpdateCancelRequest();
11637
11638 // Process manual activation request
11639 g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavActivateInputId = 0;
11640 g.NavActivateFlags = ImGuiActivateFlags_None;
11641 if (g.NavId != 0 && !g.NavDisableHighlight && !g.NavWindowingTarget && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))
11642 {
11643 const bool activate_down = (nav_keyboard_active && IsKeyDown(key: ImGuiKey_Space)) || (nav_gamepad_active && IsKeyDown(ImGuiKey_NavGamepadActivate));
11644 const bool activate_pressed = activate_down && ((nav_keyboard_active && IsKeyPressed(key: ImGuiKey_Space, repeat: false)) || (nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadActivate, repeat: false)));
11645 const bool input_down = (nav_keyboard_active && IsKeyDown(key: ImGuiKey_Enter)) || (nav_gamepad_active && IsKeyDown(ImGuiKey_NavGamepadInput));
11646 const bool input_pressed = input_down && ((nav_keyboard_active && IsKeyPressed(key: ImGuiKey_Enter, repeat: false)) || (nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadInput, repeat: false)));
11647 if (g.ActiveId == 0 && activate_pressed)
11648 {
11649 g.NavActivateId = g.NavId;
11650 g.NavActivateFlags = ImGuiActivateFlags_PreferTweak;
11651 }
11652 if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && input_pressed)
11653 {
11654 g.NavActivateInputId = g.NavId;
11655 g.NavActivateFlags = ImGuiActivateFlags_PreferInput;
11656 }
11657 if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_down)
11658 g.NavActivateDownId = g.NavId;
11659 if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_pressed)
11660 g.NavActivatePressedId = g.NavId;
11661 }
11662 if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))
11663 g.NavDisableHighlight = true;
11664 if (g.NavActivateId != 0)
11665 IM_ASSERT(g.NavActivateDownId == g.NavActivateId);
11666
11667 // Process programmatic activation request
11668 // FIXME-NAV: Those should eventually be queued (unlike focus they don't cancel each others)
11669 if (g.NavNextActivateId != 0)
11670 {
11671 if (g.NavNextActivateFlags & ImGuiActivateFlags_PreferInput)
11672 g.NavActivateInputId = g.NavNextActivateId;
11673 else
11674 g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavNextActivateId;
11675 g.NavActivateFlags = g.NavNextActivateFlags;
11676 }
11677 g.NavNextActivateId = 0;
11678
11679 // Process move requests
11680 NavUpdateCreateMoveRequest();
11681 if (g.NavMoveDir == ImGuiDir_None)
11682 NavUpdateCreateTabbingRequest();
11683 NavUpdateAnyRequestFlag();
11684 g.NavIdIsAlive = false;
11685
11686 // Scrolling
11687 if (g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.NavWindowingTarget)
11688 {
11689 // *Fallback* manual-scroll with Nav directional keys when window has no navigable item
11690 ImGuiWindow* window = g.NavWindow;
11691 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.
11692 const ImGuiDir move_dir = g.NavMoveDir;
11693 if (window->DC.NavLayersActiveMask == 0x00 && window->DC.NavHasScroll && move_dir != ImGuiDir_None)
11694 {
11695 if (move_dir == ImGuiDir_Left || move_dir == ImGuiDir_Right)
11696 SetScrollX(window, scroll_x: ImFloor(f: window->Scroll.x + ((move_dir == ImGuiDir_Left) ? -1.0f : +1.0f) * scroll_speed));
11697 if (move_dir == ImGuiDir_Up || move_dir == ImGuiDir_Down)
11698 SetScrollY(window, scroll_y: ImFloor(f: window->Scroll.y + ((move_dir == ImGuiDir_Up) ? -1.0f : +1.0f) * scroll_speed));
11699 }
11700
11701 // *Normal* Manual scroll with LStick
11702 // Next movement request will clamp the NavId reference rectangle to the visible area, so navigation will resume within those bounds.
11703 if (nav_gamepad_active)
11704 {
11705 const ImVec2 scroll_dir = GetKeyVector2d(key_left: ImGuiKey_GamepadLStickLeft, key_right: ImGuiKey_GamepadLStickRight, key_up: ImGuiKey_GamepadLStickUp, key_down: ImGuiKey_GamepadLStickDown);
11706 const float tweak_factor = IsKeyDown(ImGuiKey_NavGamepadTweakSlow) ? 1.0f / 10.0f : IsKeyDown(ImGuiKey_NavGamepadTweakFast) ? 10.0f : 1.0f;
11707 if (scroll_dir.x != 0.0f && window->ScrollbarX)
11708 SetScrollX(window, scroll_x: ImFloor(f: window->Scroll.x + scroll_dir.x * scroll_speed * tweak_factor));
11709 if (scroll_dir.y != 0.0f)
11710 SetScrollY(window, scroll_y: ImFloor(f: window->Scroll.y + scroll_dir.y * scroll_speed * tweak_factor));
11711 }
11712 }
11713
11714 // Always prioritize mouse highlight if navigation is disabled
11715 if (!nav_keyboard_active && !nav_gamepad_active)
11716 {
11717 g.NavDisableHighlight = true;
11718 g.NavDisableMouseHover = set_mouse_pos = false;
11719 }
11720
11721 // Update mouse position if requested
11722 // (This will take into account the possibility that a Scroll was queued in the window to offset our absolute mouse position before scroll has been applied)
11723 if (set_mouse_pos && (io.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos) && (io.BackendFlags & ImGuiBackendFlags_HasSetMousePos))
11724 {
11725 io.MousePos = io.MousePosPrev = NavCalcPreferredRefPos();
11726 io.WantSetMousePos = true;
11727 //IMGUI_DEBUG_LOG_IO("SetMousePos: (%.1f,%.1f)\n", io.MousePos.x, io.MousePos.y);
11728 }
11729
11730 // [DEBUG]
11731 g.NavScoringDebugCount = 0;
11732#if IMGUI_DEBUG_NAV_RECTS
11733 if (g.NavWindow)
11734 {
11735 ImDrawList* draw_list = GetForegroundDrawList(g.NavWindow);
11736 if (1) { for (int layer = 0; layer < 2; layer++) { ImRect r = WindowRectRelToAbs(g.NavWindow, g.NavWindow->NavRectRel[layer]); draw_list->AddRect(r.Min, r.Max, IM_COL32(255,200,0,255)); } } // [DEBUG]
11737 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); }
11738 }
11739#endif
11740}
11741
11742void ImGui::NavInitRequestApplyResult()
11743{
11744 // 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)
11745 ImGuiContext& g = *GImGui;
11746 if (!g.NavWindow)
11747 return;
11748
11749 // Apply result from previous navigation init request (will typically select the first item, unless SetItemDefaultFocus() has been called)
11750 // FIXME-NAV: On _NavFlattened windows, g.NavWindow will only be updated during subsequent frame. Not a problem currently.
11751 IMGUI_DEBUG_LOG_NAV("[nav] NavInitRequest: ApplyResult: NavID 0x%08X in Layer %d Window \"%s\"\n", g.NavInitResultId, g.NavLayer, g.NavWindow->Name);
11752 SetNavID(id: g.NavInitResultId, nav_layer: g.NavLayer, focus_scope_id: 0, rect_rel: g.NavInitResultRectRel);
11753 g.NavIdIsAlive = true; // Mark as alive from previous frame as we got a result
11754 if (g.NavInitRequestFromMove)
11755 NavRestoreHighlightAfterMove();
11756}
11757
11758void ImGui::NavUpdateCreateMoveRequest()
11759{
11760 ImGuiContext& g = *GImGui;
11761 ImGuiIO& io = g.IO;
11762 ImGuiWindow* window = g.NavWindow;
11763 const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
11764 const bool nav_keyboard_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0;
11765
11766 if (g.NavMoveForwardToNextFrame && window != NULL)
11767 {
11768 // 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)
11769 // (preserve most state, which were already set by the NavMoveRequestForward() function)
11770 IM_ASSERT(g.NavMoveDir != ImGuiDir_None && g.NavMoveClipDir != ImGuiDir_None);
11771 IM_ASSERT(g.NavMoveFlags & ImGuiNavMoveFlags_Forwarded);
11772 IMGUI_DEBUG_LOG_NAV("[nav] NavMoveRequestForward %d\n", g.NavMoveDir);
11773 }
11774 else
11775 {
11776 // Initiate directional inputs request
11777 g.NavMoveDir = ImGuiDir_None;
11778 g.NavMoveFlags = ImGuiNavMoveFlags_None;
11779 g.NavMoveScrollFlags = ImGuiScrollFlags_None;
11780 if (window && !g.NavWindowingTarget && !(window->Flags & ImGuiWindowFlags_NoNavInputs))
11781 {
11782 const ImGuiInputFlags repeat_mode = ImGuiInputFlags_Repeat | ImGuiInputFlags_RepeatRateNavMove;
11783 if (!IsActiveIdUsingNavDir(dir: ImGuiDir_Left) && ((nav_gamepad_active && IsKeyPressed(key: ImGuiKey_GamepadDpadLeft, ImGuiKeyOwner_None, flags: repeat_mode)) || (nav_keyboard_active && IsKeyPressed(key: ImGuiKey_LeftArrow, ImGuiKeyOwner_None, flags: repeat_mode)))) { g.NavMoveDir = ImGuiDir_Left; }
11784 if (!IsActiveIdUsingNavDir(dir: ImGuiDir_Right) && ((nav_gamepad_active && IsKeyPressed(key: ImGuiKey_GamepadDpadRight, ImGuiKeyOwner_None, flags: repeat_mode)) || (nav_keyboard_active && IsKeyPressed(key: ImGuiKey_RightArrow, ImGuiKeyOwner_None, flags: repeat_mode)))) { g.NavMoveDir = ImGuiDir_Right; }
11785 if (!IsActiveIdUsingNavDir(dir: ImGuiDir_Up) && ((nav_gamepad_active && IsKeyPressed(key: ImGuiKey_GamepadDpadUp, ImGuiKeyOwner_None, flags: repeat_mode)) || (nav_keyboard_active && IsKeyPressed(key: ImGuiKey_UpArrow, ImGuiKeyOwner_None, flags: repeat_mode)))) { g.NavMoveDir = ImGuiDir_Up; }
11786 if (!IsActiveIdUsingNavDir(dir: ImGuiDir_Down) && ((nav_gamepad_active && IsKeyPressed(key: ImGuiKey_GamepadDpadDown, ImGuiKeyOwner_None, flags: repeat_mode)) || (nav_keyboard_active && IsKeyPressed(key: ImGuiKey_DownArrow, ImGuiKeyOwner_None, flags: repeat_mode)))) { g.NavMoveDir = ImGuiDir_Down; }
11787 }
11788 g.NavMoveClipDir = g.NavMoveDir;
11789 g.NavScoringNoClipRect = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX);
11790 }
11791
11792 // Update PageUp/PageDown/Home/End scroll
11793 // FIXME-NAV: Consider enabling those keys even without the master ImGuiConfigFlags_NavEnableKeyboard flag?
11794 float scoring_rect_offset_y = 0.0f;
11795 if (window && g.NavMoveDir == ImGuiDir_None && nav_keyboard_active)
11796 scoring_rect_offset_y = NavUpdatePageUpPageDown();
11797 if (scoring_rect_offset_y != 0.0f)
11798 {
11799 g.NavScoringNoClipRect = window->InnerRect;
11800 g.NavScoringNoClipRect.TranslateY(dy: scoring_rect_offset_y);
11801 }
11802
11803 // [DEBUG] Always send a request
11804#if IMGUI_DEBUG_NAV_SCORING
11805 if (io.KeyCtrl && IsKeyPressed(ImGuiKey_C))
11806 g.NavMoveDirForDebug = (ImGuiDir)((g.NavMoveDirForDebug + 1) & 3);
11807 if (io.KeyCtrl && g.NavMoveDir == ImGuiDir_None)
11808 {
11809 g.NavMoveDir = g.NavMoveDirForDebug;
11810 g.NavMoveFlags |= ImGuiNavMoveFlags_DebugNoResult;
11811 }
11812#endif
11813
11814 // Submit
11815 g.NavMoveForwardToNextFrame = false;
11816 if (g.NavMoveDir != ImGuiDir_None)
11817 NavMoveRequestSubmit(move_dir: g.NavMoveDir, clip_dir: g.NavMoveClipDir, move_flags: g.NavMoveFlags, scroll_flags: g.NavMoveScrollFlags);
11818
11819 // Moving with no reference triggers an init request (will be used as a fallback if the direction fails to find a match)
11820 if (g.NavMoveSubmitted && g.NavId == 0)
11821 {
11822 IMGUI_DEBUG_LOG_NAV("[nav] NavInitRequest: from move, window \"%s\", layer=%d\n", window ? window->Name : "<NULL>", g.NavLayer);
11823 g.NavInitRequest = g.NavInitRequestFromMove = true;
11824 g.NavInitResultId = 0;
11825 g.NavDisableHighlight = false;
11826 }
11827
11828 // When using gamepad, we project the reference nav bounding box into window visible area.
11829 // This is to allow resuming navigation inside the visible area after doing a large amount of scrolling, since with gamepad all movements are relative
11830 // (can't focus a visible object like we can with the mouse).
11831 if (g.NavMoveSubmitted && g.NavInputSource == ImGuiInputSource_Gamepad && g.NavLayer == ImGuiNavLayer_Main && window != NULL)// && (g.NavMoveFlags & ImGuiNavMoveFlags_Forwarded))
11832 {
11833 bool clamp_x = (g.NavMoveFlags & (ImGuiNavMoveFlags_LoopX | ImGuiNavMoveFlags_WrapX)) == 0;
11834 bool clamp_y = (g.NavMoveFlags & (ImGuiNavMoveFlags_LoopY | ImGuiNavMoveFlags_WrapY)) == 0;
11835 ImRect inner_rect_rel = WindowRectAbsToRel(window, r: ImRect(window->InnerRect.Min - ImVec2(1, 1), window->InnerRect.Max + ImVec2(1, 1)));
11836 if ((clamp_x || clamp_y) && !inner_rect_rel.Contains(r: window->NavRectRel[g.NavLayer]))
11837 {
11838 //IMGUI_DEBUG_LOG_NAV("[nav] NavMoveRequest: clamp NavRectRel for gamepad move\n");
11839 float pad_x = ImMin(lhs: inner_rect_rel.GetWidth(), rhs: window->CalcFontSize() * 0.5f);
11840 float pad_y = ImMin(lhs: inner_rect_rel.GetHeight(), rhs: window->CalcFontSize() * 0.5f); // Terrible approximation for the intent of starting navigation from first fully visible item
11841 inner_rect_rel.Min.x = clamp_x ? (inner_rect_rel.Min.x + pad_x) : -FLT_MAX;
11842 inner_rect_rel.Max.x = clamp_x ? (inner_rect_rel.Max.x - pad_x) : +FLT_MAX;
11843 inner_rect_rel.Min.y = clamp_y ? (inner_rect_rel.Min.y + pad_y) : -FLT_MAX;
11844 inner_rect_rel.Max.y = clamp_y ? (inner_rect_rel.Max.y - pad_y) : +FLT_MAX;
11845 window->NavRectRel[g.NavLayer].ClipWithFull(r: inner_rect_rel);
11846 g.NavId = 0;
11847 }
11848 }
11849
11850 // 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)
11851 ImRect scoring_rect;
11852 if (window != NULL)
11853 {
11854 ImRect nav_rect_rel = !window->NavRectRel[g.NavLayer].IsInverted() ? window->NavRectRel[g.NavLayer] : ImRect(0, 0, 0, 0);
11855 scoring_rect = WindowRectRelToAbs(window, r: nav_rect_rel);
11856 scoring_rect.TranslateY(dy: scoring_rect_offset_y);
11857 scoring_rect.Min.x = ImMin(lhs: scoring_rect.Min.x + 1.0f, rhs: scoring_rect.Max.x);
11858 scoring_rect.Max.x = scoring_rect.Min.x;
11859 IM_ASSERT(!scoring_rect.IsInverted()); // Ensure if we have a finite, non-inverted bounding box here will allow us to remove extraneous ImFabs() calls in NavScoreItem().
11860 //GetForegroundDrawList()->AddRect(scoring_rect.Min, scoring_rect.Max, IM_COL32(255,200,0,255)); // [DEBUG]
11861 //if (!g.NavScoringNoClipRect.IsInverted()) { GetForegroundDrawList()->AddRect(g.NavScoringNoClipRect.Min, g.NavScoringNoClipRect.Max, IM_COL32(255, 200, 0, 255)); } // [DEBUG]
11862 }
11863 g.NavScoringRect = scoring_rect;
11864 g.NavScoringNoClipRect.Add(r: scoring_rect);
11865}
11866
11867void ImGui::NavUpdateCreateTabbingRequest()
11868{
11869 ImGuiContext& g = *GImGui;
11870 ImGuiWindow* window = g.NavWindow;
11871 IM_ASSERT(g.NavMoveDir == ImGuiDir_None);
11872 if (window == NULL || g.NavWindowingTarget != NULL || (window->Flags & ImGuiWindowFlags_NoNavInputs))
11873 return;
11874
11875 const bool tab_pressed = IsKeyPressed(key: ImGuiKey_Tab, ImGuiKeyOwner_None, flags: ImGuiInputFlags_Repeat) && !g.IO.KeyCtrl && !g.IO.KeyAlt;
11876 if (!tab_pressed)
11877 return;
11878
11879 // Initiate tabbing request
11880 // (this is ALWAYS ENABLED, regardless of ImGuiConfigFlags_NavEnableKeyboard flag!)
11881 // Initially this was designed to use counters and modulo arithmetic, but that could not work with unsubmitted items (list clipper). Instead we use a strategy close to other move requests.
11882 // See NavProcessItemForTabbingRequest() for a description of the various forward/backward tabbing cases with and without wrapping.
11883 //// FIXME: We use (g.ActiveId == 0) but (g.NavDisableHighlight == false) might be righter once we can tab through anything
11884 g.NavTabbingDir = g.IO.KeyShift ? -1 : (g.ActiveId == 0) ? 0 : +1;
11885 ImGuiScrollFlags scroll_flags = window->Appearing ? ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_AlwaysCenterY : ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_KeepVisibleEdgeY;
11886 ImGuiDir clip_dir = (g.NavTabbingDir < 0) ? ImGuiDir_Up : ImGuiDir_Down;
11887 NavMoveRequestSubmit(move_dir: ImGuiDir_None, clip_dir, move_flags: ImGuiNavMoveFlags_Tabbing, scroll_flags); // FIXME-NAV: Once we refactor tabbing, add LegacyApi flag to not activate non-inputable.
11888 g.NavTabbingCounter = -1;
11889}
11890
11891// Apply result from previous frame navigation directional move request. Always called from NavUpdate()
11892void ImGui::NavMoveRequestApplyResult()
11893{
11894 ImGuiContext& g = *GImGui;
11895#if IMGUI_DEBUG_NAV_SCORING
11896 if (g.NavMoveFlags & ImGuiNavMoveFlags_DebugNoResult) // [DEBUG] Scoring all items in NavWindow at all times
11897 return;
11898#endif
11899
11900 // Select which result to use
11901 ImGuiNavItemData* result = (g.NavMoveResultLocal.ID != 0) ? &g.NavMoveResultLocal : (g.NavMoveResultOther.ID != 0) ? &g.NavMoveResultOther : NULL;
11902
11903 // Tabbing forward wrap
11904 if (g.NavMoveFlags & ImGuiNavMoveFlags_Tabbing)
11905 if ((g.NavTabbingCounter == 1 || g.NavTabbingDir == 0) && g.NavTabbingResultFirst.ID)
11906 result = &g.NavTabbingResultFirst;
11907
11908 // In a situation when there are no results but NavId != 0, re-enable the Navigation highlight (because g.NavId is not considered as a possible result)
11909 if (result == NULL)
11910 {
11911 if (g.NavMoveFlags & ImGuiNavMoveFlags_Tabbing)
11912 g.NavMoveFlags |= ImGuiNavMoveFlags_DontSetNavHighlight;
11913 if (g.NavId != 0 && (g.NavMoveFlags & ImGuiNavMoveFlags_DontSetNavHighlight) == 0)
11914 NavRestoreHighlightAfterMove();
11915 return;
11916 }
11917
11918 // PageUp/PageDown behavior first jumps to the bottom/top mostly visible item, _otherwise_ use the result from the previous/next page.
11919 if (g.NavMoveFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet)
11920 if (g.NavMoveResultLocalVisible.ID != 0 && g.NavMoveResultLocalVisible.ID != g.NavId)
11921 result = &g.NavMoveResultLocalVisible;
11922
11923 // Maybe entering a flattened child from the outside? In this case solve the tie using the regular scoring rules.
11924 if (result != &g.NavMoveResultOther && g.NavMoveResultOther.ID != 0 && g.NavMoveResultOther.Window->ParentWindow == g.NavWindow)
11925 if ((g.NavMoveResultOther.DistBox < result->DistBox) || (g.NavMoveResultOther.DistBox == result->DistBox && g.NavMoveResultOther.DistCenter < result->DistCenter))
11926 result = &g.NavMoveResultOther;
11927 IM_ASSERT(g.NavWindow && result->Window);
11928
11929 // Scroll to keep newly navigated item fully into view.
11930 if (g.NavLayer == ImGuiNavLayer_Main)
11931 {
11932 if (g.NavMoveFlags & ImGuiNavMoveFlags_ScrollToEdgeY)
11933 {
11934 // FIXME: Should remove this
11935 float scroll_target = (g.NavMoveDir == ImGuiDir_Up) ? result->Window->ScrollMax.y : 0.0f;
11936 SetScrollY(window: result->Window, scroll_y: scroll_target);
11937 }
11938 else
11939 {
11940 ImRect rect_abs = WindowRectRelToAbs(window: result->Window, r: result->RectRel);
11941 ScrollToRectEx(window: result->Window, item_rect: rect_abs, flags: g.NavMoveScrollFlags);
11942 }
11943 }
11944
11945 if (g.NavWindow != result->Window)
11946 {
11947 IMGUI_DEBUG_LOG_FOCUS("[focus] NavMoveRequest: SetNavWindow(\"%s\")\n", result->Window->Name);
11948 g.NavWindow = result->Window;
11949 }
11950 if (g.ActiveId != result->ID)
11951 ClearActiveID();
11952 if (g.NavId != result->ID)
11953 {
11954 // Don't set NavJustMovedToId if just landed on the same spot (which may happen with ImGuiNavMoveFlags_AllowCurrentNavId)
11955 g.NavJustMovedToId = result->ID;
11956 g.NavJustMovedToFocusScopeId = result->FocusScopeId;
11957 g.NavJustMovedToKeyMods = g.NavMoveKeyMods;
11958 }
11959
11960 // Focus
11961 IMGUI_DEBUG_LOG_NAV("[nav] NavMoveRequest: result NavID 0x%08X in Layer %d Window \"%s\"\n", result->ID, g.NavLayer, g.NavWindow->Name);
11962 SetNavID(id: result->ID, nav_layer: g.NavLayer, focus_scope_id: result->FocusScopeId, rect_rel: result->RectRel);
11963
11964 // Tabbing: Activates Inputable or Focus non-Inputable
11965 if ((g.NavMoveFlags & ImGuiNavMoveFlags_Tabbing) && (result->InFlags & ImGuiItemFlags_Inputable))
11966 {
11967 g.NavNextActivateId = result->ID;
11968 g.NavNextActivateFlags = ImGuiActivateFlags_PreferInput | ImGuiActivateFlags_TryToPreserveState;
11969 g.NavMoveFlags |= ImGuiNavMoveFlags_DontSetNavHighlight;
11970 }
11971
11972 // Activate
11973 if (g.NavMoveFlags & ImGuiNavMoveFlags_Activate)
11974 {
11975 g.NavNextActivateId = result->ID;
11976 g.NavNextActivateFlags = ImGuiActivateFlags_None;
11977 }
11978
11979 // Enable nav highlight
11980 if ((g.NavMoveFlags & ImGuiNavMoveFlags_DontSetNavHighlight) == 0)
11981 NavRestoreHighlightAfterMove();
11982}
11983
11984// Process NavCancel input (to close a popup, get back to parent, clear focus)
11985// FIXME: In order to support e.g. Escape to clear a selection we'll need:
11986// - either to store the equivalent of ActiveIdUsingKeyInputMask for a FocusScope and test for it.
11987// - either to move most/all of those tests to the epilogue/end functions of the scope they are dealing with (e.g. exit child window in EndChild()) or in EndFrame(), to allow an earlier intercept
11988static void ImGui::NavUpdateCancelRequest()
11989{
11990 ImGuiContext& g = *GImGui;
11991 const bool nav_gamepad_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (g.IO.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
11992 const bool nav_keyboard_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0;
11993 if (!(nav_keyboard_active && IsKeyPressed(key: ImGuiKey_Escape, ImGuiKeyOwner_None)) && !(nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadCancel, ImGuiKeyOwner_None)))
11994 return;
11995
11996 IMGUI_DEBUG_LOG_NAV("[nav] NavUpdateCancelRequest()\n");
11997 if (g.ActiveId != 0)
11998 {
11999 ClearActiveID();
12000 }
12001 else if (g.NavLayer != ImGuiNavLayer_Main)
12002 {
12003 // Leave the "menu" layer
12004 NavRestoreLayer(layer: ImGuiNavLayer_Main);
12005 NavRestoreHighlightAfterMove();
12006 }
12007 else if (g.NavWindow && g.NavWindow != g.NavWindow->RootWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_Popup) && g.NavWindow->ParentWindow)
12008 {
12009 // Exit child window
12010 ImGuiWindow* child_window = g.NavWindow;
12011 ImGuiWindow* parent_window = g.NavWindow->ParentWindow;
12012 IM_ASSERT(child_window->ChildId != 0);
12013 ImRect child_rect = child_window->Rect();
12014 FocusWindow(window: parent_window);
12015 SetNavID(id: child_window->ChildId, nav_layer: ImGuiNavLayer_Main, focus_scope_id: 0, rect_rel: WindowRectAbsToRel(window: parent_window, r: child_rect));
12016 NavRestoreHighlightAfterMove();
12017 }
12018 else if (g.OpenPopupStack.Size > 0 && g.OpenPopupStack.back().Window != NULL && !(g.OpenPopupStack.back().Window->Flags & ImGuiWindowFlags_Modal))
12019 {
12020 // Close open popup/menu
12021 ClosePopupToLevel(remaining: g.OpenPopupStack.Size - 1, restore_focus_to_window_under_popup: true);
12022 }
12023 else
12024 {
12025 // Clear NavLastId for popups but keep it for regular child window so we can leave one and come back where we were
12026 if (g.NavWindow && ((g.NavWindow->Flags & ImGuiWindowFlags_Popup) || !(g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow)))
12027 g.NavWindow->NavLastIds[0] = 0;
12028 g.NavId = 0;
12029 }
12030}
12031
12032// Handle PageUp/PageDown/Home/End keys
12033// Called from NavUpdateCreateMoveRequest() which will use our output to create a move request
12034// FIXME-NAV: This doesn't work properly with NavFlattened siblings as we use NavWindow rectangle for reference
12035// FIXME-NAV: how to get Home/End to aim at the beginning/end of a 2D grid?
12036static float ImGui::NavUpdatePageUpPageDown()
12037{
12038 ImGuiContext& g = *GImGui;
12039 ImGuiWindow* window = g.NavWindow;
12040 if ((window->Flags & ImGuiWindowFlags_NoNavInputs) || g.NavWindowingTarget != NULL)
12041 return 0.0f;
12042
12043 const bool page_up_held = IsKeyDown(key: ImGuiKey_PageUp, ImGuiKeyOwner_None);
12044 const bool page_down_held = IsKeyDown(key: ImGuiKey_PageDown, ImGuiKeyOwner_None);
12045 const bool home_pressed = IsKeyPressed(key: ImGuiKey_Home, ImGuiKeyOwner_None, flags: ImGuiInputFlags_Repeat);
12046 const bool end_pressed = IsKeyPressed(key: ImGuiKey_End, ImGuiKeyOwner_None, flags: ImGuiInputFlags_Repeat);
12047 if (page_up_held == page_down_held && home_pressed == end_pressed) // Proceed if either (not both) are pressed, otherwise early out
12048 return 0.0f;
12049
12050 if (g.NavLayer != ImGuiNavLayer_Main)
12051 NavRestoreLayer(layer: ImGuiNavLayer_Main);
12052
12053 if (window->DC.NavLayersActiveMask == 0x00 && window->DC.NavHasScroll)
12054 {
12055 // Fallback manual-scroll when window has no navigable item
12056 if (IsKeyPressed(key: ImGuiKey_PageUp, ImGuiKeyOwner_None, flags: ImGuiInputFlags_Repeat))
12057 SetScrollY(window, scroll_y: window->Scroll.y - window->InnerRect.GetHeight());
12058 else if (IsKeyPressed(key: ImGuiKey_PageDown, ImGuiKeyOwner_None, flags: ImGuiInputFlags_Repeat))
12059 SetScrollY(window, scroll_y: window->Scroll.y + window->InnerRect.GetHeight());
12060 else if (home_pressed)
12061 SetScrollY(window, scroll_y: 0.0f);
12062 else if (end_pressed)
12063 SetScrollY(window, scroll_y: window->ScrollMax.y);
12064 }
12065 else
12066 {
12067 ImRect& nav_rect_rel = window->NavRectRel[g.NavLayer];
12068 const float page_offset_y = ImMax(lhs: 0.0f, rhs: window->InnerRect.GetHeight() - window->CalcFontSize() * 1.0f + nav_rect_rel.GetHeight());
12069 float nav_scoring_rect_offset_y = 0.0f;
12070 if (IsKeyPressed(key: ImGuiKey_PageUp, repeat: true))
12071 {
12072 nav_scoring_rect_offset_y = -page_offset_y;
12073 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)
12074 g.NavMoveClipDir = ImGuiDir_Up;
12075 g.NavMoveFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet;
12076 }
12077 else if (IsKeyPressed(key: ImGuiKey_PageDown, repeat: true))
12078 {
12079 nav_scoring_rect_offset_y = +page_offset_y;
12080 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)
12081 g.NavMoveClipDir = ImGuiDir_Down;
12082 g.NavMoveFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet;
12083 }
12084 else if (home_pressed)
12085 {
12086 // FIXME-NAV: handling of Home/End is assuming that the top/bottom most item will be visible with Scroll.y == 0/ScrollMax.y
12087 // Scrolling will be handled via the ImGuiNavMoveFlags_ScrollToEdgeY flag, we don't scroll immediately to avoid scrolling happening before nav result.
12088 // Preserve current horizontal position if we have any.
12089 nav_rect_rel.Min.y = nav_rect_rel.Max.y = 0.0f;
12090 if (nav_rect_rel.IsInverted())
12091 nav_rect_rel.Min.x = nav_rect_rel.Max.x = 0.0f;
12092 g.NavMoveDir = ImGuiDir_Down;
12093 g.NavMoveFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_ScrollToEdgeY;
12094 // FIXME-NAV: MoveClipDir left to _None, intentional?
12095 }
12096 else if (end_pressed)
12097 {
12098 nav_rect_rel.Min.y = nav_rect_rel.Max.y = window->ContentSize.y;
12099 if (nav_rect_rel.IsInverted())
12100 nav_rect_rel.Min.x = nav_rect_rel.Max.x = 0.0f;
12101 g.NavMoveDir = ImGuiDir_Up;
12102 g.NavMoveFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_ScrollToEdgeY;
12103 // FIXME-NAV: MoveClipDir left to _None, intentional?
12104 }
12105 return nav_scoring_rect_offset_y;
12106 }
12107 return 0.0f;
12108}
12109
12110static void ImGui::NavEndFrame()
12111{
12112 ImGuiContext& g = *GImGui;
12113
12114 // Show CTRL+TAB list window
12115 if (g.NavWindowingTarget != NULL)
12116 NavUpdateWindowingOverlay();
12117
12118 // Perform wrap-around in menus
12119 // FIXME-NAV: Wrap may need to apply a weight bias on the other axis. e.g. 4x4 grid with 2 last items missing on last item won't handle LoopY/WrapY correctly.
12120 // FIXME-NAV: Wrap (not Loop) support could be handled by the scoring function and then WrapX would function without an extra frame.
12121 const ImGuiNavMoveFlags wanted_flags = ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX | ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY;
12122 if (g.NavWindow && NavMoveRequestButNoResultYet() && (g.NavMoveFlags & wanted_flags) && (g.NavMoveFlags & ImGuiNavMoveFlags_Forwarded) == 0)
12123 NavUpdateCreateWrappingRequest();
12124}
12125
12126static void ImGui::NavUpdateCreateWrappingRequest()
12127{
12128 ImGuiContext& g = *GImGui;
12129 ImGuiWindow* window = g.NavWindow;
12130
12131 bool do_forward = false;
12132 ImRect bb_rel = window->NavRectRel[g.NavLayer];
12133 ImGuiDir clip_dir = g.NavMoveDir;
12134 const ImGuiNavMoveFlags move_flags = g.NavMoveFlags;
12135 if (g.NavMoveDir == ImGuiDir_Left && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX)))
12136 {
12137 bb_rel.Min.x = bb_rel.Max.x = window->ContentSize.x + window->WindowPadding.x;
12138 if (move_flags & ImGuiNavMoveFlags_WrapX)
12139 {
12140 bb_rel.TranslateY(dy: -bb_rel.GetHeight()); // Previous row
12141 clip_dir = ImGuiDir_Up;
12142 }
12143 do_forward = true;
12144 }
12145 if (g.NavMoveDir == ImGuiDir_Right && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX)))
12146 {
12147 bb_rel.Min.x = bb_rel.Max.x = -window->WindowPadding.x;
12148 if (move_flags & ImGuiNavMoveFlags_WrapX)
12149 {
12150 bb_rel.TranslateY(dy: +bb_rel.GetHeight()); // Next row
12151 clip_dir = ImGuiDir_Down;
12152 }
12153 do_forward = true;
12154 }
12155 if (g.NavMoveDir == ImGuiDir_Up && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY)))
12156 {
12157 bb_rel.Min.y = bb_rel.Max.y = window->ContentSize.y + window->WindowPadding.y;
12158 if (move_flags & ImGuiNavMoveFlags_WrapY)
12159 {
12160 bb_rel.TranslateX(dx: -bb_rel.GetWidth()); // Previous column
12161 clip_dir = ImGuiDir_Left;
12162 }
12163 do_forward = true;
12164 }
12165 if (g.NavMoveDir == ImGuiDir_Down && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY)))
12166 {
12167 bb_rel.Min.y = bb_rel.Max.y = -window->WindowPadding.y;
12168 if (move_flags & ImGuiNavMoveFlags_WrapY)
12169 {
12170 bb_rel.TranslateX(dx: +bb_rel.GetWidth()); // Next column
12171 clip_dir = ImGuiDir_Right;
12172 }
12173 do_forward = true;
12174 }
12175 if (!do_forward)
12176 return;
12177 window->NavRectRel[g.NavLayer] = bb_rel;
12178 NavMoveRequestForward(move_dir: g.NavMoveDir, clip_dir, move_flags, scroll_flags: g.NavMoveScrollFlags);
12179}
12180
12181static int ImGui::FindWindowFocusIndex(ImGuiWindow* window)
12182{
12183 ImGuiContext& g = *GImGui;
12184 IM_UNUSED(g);
12185 int order = window->FocusOrder;
12186 IM_ASSERT(window->RootWindow == window); // No child window (not testing _ChildWindow because of docking)
12187 IM_ASSERT(g.WindowsFocusOrder[order] == window);
12188 return order;
12189}
12190
12191static ImGuiWindow* FindWindowNavFocusable(int i_start, int i_stop, int dir) // FIXME-OPT O(N)
12192{
12193 ImGuiContext& g = *GImGui;
12194 for (int i = i_start; i >= 0 && i < g.WindowsFocusOrder.Size && i != i_stop; i += dir)
12195 if (ImGui::IsWindowNavFocusable(window: g.WindowsFocusOrder[i]))
12196 return g.WindowsFocusOrder[i];
12197 return NULL;
12198}
12199
12200static void NavUpdateWindowingHighlightWindow(int focus_change_dir)
12201{
12202 ImGuiContext& g = *GImGui;
12203 IM_ASSERT(g.NavWindowingTarget);
12204 if (g.NavWindowingTarget->Flags & ImGuiWindowFlags_Modal)
12205 return;
12206
12207 const int i_current = ImGui::FindWindowFocusIndex(window: g.NavWindowingTarget);
12208 ImGuiWindow* window_target = FindWindowNavFocusable(i_start: i_current + focus_change_dir, i_stop: -INT_MAX, dir: focus_change_dir);
12209 if (!window_target)
12210 window_target = FindWindowNavFocusable(i_start: (focus_change_dir < 0) ? (g.WindowsFocusOrder.Size - 1) : 0, i_stop: i_current, dir: focus_change_dir);
12211 if (window_target) // Don't reset windowing target if there's a single window in the list
12212 {
12213 g.NavWindowingTarget = g.NavWindowingTargetAnim = window_target;
12214 g.NavWindowingAccumDeltaPos = g.NavWindowingAccumDeltaSize = ImVec2(0.0f, 0.0f);
12215 }
12216 g.NavWindowingToggleLayer = false;
12217}
12218
12219// Windowing management mode
12220// Keyboard: CTRL+Tab (change focus/move/resize), Alt (toggle menu layer)
12221// Gamepad: Hold Menu/Square (change focus/move/resize), Tap Menu/Square (toggle menu layer)
12222static void ImGui::NavUpdateWindowing()
12223{
12224 ImGuiContext& g = *GImGui;
12225 ImGuiIO& io = g.IO;
12226
12227 ImGuiWindow* apply_focus_window = NULL;
12228 bool apply_toggle_layer = false;
12229
12230 ImGuiWindow* modal_window = GetTopMostPopupModal();
12231 bool allow_windowing = (modal_window == NULL);
12232 if (!allow_windowing)
12233 g.NavWindowingTarget = NULL;
12234
12235 // Fade out
12236 if (g.NavWindowingTargetAnim && g.NavWindowingTarget == NULL)
12237 {
12238 g.NavWindowingHighlightAlpha = ImMax(lhs: g.NavWindowingHighlightAlpha - io.DeltaTime * 10.0f, rhs: 0.0f);
12239 if (g.DimBgRatio <= 0.0f && g.NavWindowingHighlightAlpha <= 0.0f)
12240 g.NavWindowingTargetAnim = NULL;
12241 }
12242
12243 // Start CTRL+Tab or Square+L/R window selection
12244 const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
12245 const bool nav_keyboard_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0;
12246 const bool keyboard_next_window = allow_windowing && g.ConfigNavWindowingKeyNext && Shortcut(key_chord: g.ConfigNavWindowingKeyNext, ImGuiKeyOwner_None, flags: ImGuiInputFlags_Repeat | ImGuiInputFlags_RouteAlways);
12247 const bool keyboard_prev_window = allow_windowing && g.ConfigNavWindowingKeyPrev && Shortcut(key_chord: g.ConfigNavWindowingKeyPrev, ImGuiKeyOwner_None, flags: ImGuiInputFlags_Repeat | ImGuiInputFlags_RouteAlways);
12248 const bool start_windowing_with_gamepad = allow_windowing && nav_gamepad_active && !g.NavWindowingTarget && IsKeyPressed(ImGuiKey_NavGamepadMenu, owner_id: 0, flags: ImGuiInputFlags_None);
12249 const bool start_windowing_with_keyboard = allow_windowing && !g.NavWindowingTarget && (keyboard_next_window || keyboard_prev_window); // Note: enabled even without NavEnableKeyboard!
12250 if (start_windowing_with_gamepad || start_windowing_with_keyboard)
12251 if (ImGuiWindow* window = g.NavWindow ? g.NavWindow : FindWindowNavFocusable(i_start: g.WindowsFocusOrder.Size - 1, i_stop: -INT_MAX, dir: -1))
12252 {
12253 g.NavWindowingTarget = g.NavWindowingTargetAnim = window->RootWindow;
12254 g.NavWindowingTimer = g.NavWindowingHighlightAlpha = 0.0f;
12255 g.NavWindowingAccumDeltaPos = g.NavWindowingAccumDeltaSize = ImVec2(0.0f, 0.0f);
12256 g.NavWindowingToggleLayer = start_windowing_with_gamepad ? true : false; // Gamepad starts toggling layer
12257 g.NavInputSource = start_windowing_with_keyboard ? ImGuiInputSource_Keyboard : ImGuiInputSource_Gamepad;
12258 }
12259
12260 // Gamepad update
12261 g.NavWindowingTimer += io.DeltaTime;
12262 if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_Gamepad)
12263 {
12264 // 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
12265 g.NavWindowingHighlightAlpha = ImMax(lhs: g.NavWindowingHighlightAlpha, rhs: ImSaturate(f: (g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f));
12266
12267 // Select window to focus
12268 const int focus_change_dir = (int)IsKeyPressed(key: ImGuiKey_GamepadL1) - (int)IsKeyPressed(key: ImGuiKey_GamepadR1);
12269 if (focus_change_dir != 0)
12270 {
12271 NavUpdateWindowingHighlightWindow(focus_change_dir);
12272 g.NavWindowingHighlightAlpha = 1.0f;
12273 }
12274
12275 // Single press toggles NavLayer, long press with L/R apply actual focus on release (until then the window was merely rendered top-most)
12276 if (!IsKeyDown(ImGuiKey_NavGamepadMenu))
12277 {
12278 g.NavWindowingToggleLayer &= (g.NavWindowingHighlightAlpha < 1.0f); // Once button was held long enough we don't consider it a tap-to-toggle-layer press anymore.
12279 if (g.NavWindowingToggleLayer && g.NavWindow)
12280 apply_toggle_layer = true;
12281 else if (!g.NavWindowingToggleLayer)
12282 apply_focus_window = g.NavWindowingTarget;
12283 g.NavWindowingTarget = NULL;
12284 }
12285 }
12286
12287 // Keyboard: Focus
12288 if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_Keyboard)
12289 {
12290 // Visuals only appears after a brief time after pressing TAB the first time, so that a fast CTRL+TAB doesn't add visual noise
12291 ImGuiKeyChord shared_mods = ((g.ConfigNavWindowingKeyNext ? g.ConfigNavWindowingKeyNext : ImGuiMod_Mask_) & (g.ConfigNavWindowingKeyPrev ? g.ConfigNavWindowingKeyPrev : ImGuiMod_Mask_)) & ImGuiMod_Mask_;
12292 IM_ASSERT(shared_mods != 0); // Next/Prev shortcut currently needs a shared modifier to "hold", otherwise Prev actions would keep cycling between two windows.
12293 g.NavWindowingHighlightAlpha = ImMax(lhs: g.NavWindowingHighlightAlpha, rhs: ImSaturate(f: (g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f)); // 1.0f
12294 if (keyboard_next_window || keyboard_prev_window)
12295 NavUpdateWindowingHighlightWindow(focus_change_dir: keyboard_next_window ? -1 : +1);
12296 else if ((io.KeyMods & shared_mods) != shared_mods)
12297 apply_focus_window = g.NavWindowingTarget;
12298 }
12299
12300 // Keyboard: Press and Release ALT to toggle menu layer
12301 // - Testing that only Alt is tested prevents Alt+Shift or AltGR from toggling menu layer.
12302 // - 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.
12303 if (nav_keyboard_active && IsKeyPressed(key: ImGuiMod_Alt, ImGuiKeyOwner_None))
12304 {
12305 g.NavWindowingToggleLayer = true;
12306 g.NavInputSource = ImGuiInputSource_Keyboard;
12307 }
12308 if (g.NavWindowingToggleLayer && g.NavInputSource == ImGuiInputSource_Keyboard)
12309 {
12310 // We cancel toggling nav layer when any text has been typed (generally while holding Alt). (See #370)
12311 // We cancel toggling nav layer when other modifiers are pressed. (See #4439)
12312 // We cancel toggling nav layer if an owner has claimed the key.
12313 if (io.InputQueueCharacters.Size > 0 || io.KeyCtrl || io.KeyShift || io.KeySuper || TestKeyOwner(key: ImGuiMod_Alt, ImGuiKeyOwner_None) == false)
12314 g.NavWindowingToggleLayer = false;
12315
12316 // Apply layer toggle on release
12317 // 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.
12318 if (IsKeyReleased(key: ImGuiMod_Alt) && g.NavWindowingToggleLayer)
12319 if (g.ActiveId == 0 || g.ActiveIdAllowOverlap)
12320 if (IsMousePosValid(mouse_pos: &io.MousePos) == IsMousePosValid(mouse_pos: &io.MousePosPrev))
12321 apply_toggle_layer = true;
12322 if (!IsKeyDown(key: ImGuiMod_Alt))
12323 g.NavWindowingToggleLayer = false;
12324 }
12325
12326 // Move window
12327 if (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoMove))
12328 {
12329 ImVec2 nav_move_dir;
12330 if (g.NavInputSource == ImGuiInputSource_Keyboard && !io.KeyShift)
12331 nav_move_dir = GetKeyVector2d(key_left: ImGuiKey_LeftArrow, key_right: ImGuiKey_RightArrow, key_up: ImGuiKey_UpArrow, key_down: ImGuiKey_DownArrow);
12332 if (g.NavInputSource == ImGuiInputSource_Gamepad)
12333 nav_move_dir = GetKeyVector2d(key_left: ImGuiKey_GamepadLStickLeft, key_right: ImGuiKey_GamepadLStickRight, key_up: ImGuiKey_GamepadLStickUp, key_down: ImGuiKey_GamepadLStickDown);
12334 if (nav_move_dir.x != 0.0f || nav_move_dir.y != 0.0f)
12335 {
12336 const float NAV_MOVE_SPEED = 800.0f;
12337 const float move_step = NAV_MOVE_SPEED * io.DeltaTime * ImMin(lhs: io.DisplayFramebufferScale.x, rhs: io.DisplayFramebufferScale.y);
12338 g.NavWindowingAccumDeltaPos += nav_move_dir * move_step;
12339 g.NavDisableMouseHover = true;
12340 ImVec2 accum_floored = ImFloor(v: g.NavWindowingAccumDeltaPos);
12341 if (accum_floored.x != 0.0f || accum_floored.y != 0.0f)
12342 {
12343 ImGuiWindow* moving_window = g.NavWindowingTarget->RootWindowDockTree;
12344 SetWindowPos(window: moving_window, pos: moving_window->Pos + accum_floored, cond: ImGuiCond_Always);
12345 g.NavWindowingAccumDeltaPos -= accum_floored;
12346 }
12347 }
12348 }
12349
12350 // Apply final focus
12351 if (apply_focus_window && (g.NavWindow == NULL || apply_focus_window != g.NavWindow->RootWindow))
12352 {
12353 ImGuiViewport* previous_viewport = g.NavWindow ? g.NavWindow->Viewport : NULL;
12354 ClearActiveID();
12355 NavRestoreHighlightAfterMove();
12356 apply_focus_window = NavRestoreLastChildNavWindow(window: apply_focus_window);
12357 ClosePopupsOverWindow(ref_window: apply_focus_window, restore_focus_to_window_under_popup: false);
12358 FocusWindow(window: apply_focus_window);
12359 if (apply_focus_window->NavLastIds[0] == 0)
12360 NavInitWindow(window: apply_focus_window, force_reinit: false);
12361
12362 // If the window has ONLY a menu layer (no main layer), select it directly
12363 // Use NavLayersActiveMaskNext since windows didn't have a chance to be Begin()-ed on this frame,
12364 // so CTRL+Tab where the keys are only held for 1 frame will be able to use correct layers mask since
12365 // the target window as already been previewed once.
12366 // FIXME-NAV: This should be done in NavInit.. or in FocusWindow... However in both of those cases,
12367 // we won't have a guarantee that windows has been visible before and therefore NavLayersActiveMask*
12368 // won't be valid.
12369 if (apply_focus_window->DC.NavLayersActiveMaskNext == (1 << ImGuiNavLayer_Menu))
12370 g.NavLayer = ImGuiNavLayer_Menu;
12371
12372 // Request OS level focus
12373 if (apply_focus_window->Viewport != previous_viewport && g.PlatformIO.Platform_SetWindowFocus)
12374 g.PlatformIO.Platform_SetWindowFocus(apply_focus_window->Viewport);
12375 }
12376 if (apply_focus_window)
12377 g.NavWindowingTarget = NULL;
12378
12379 // Apply menu/layer toggle
12380 if (apply_toggle_layer && g.NavWindow)
12381 {
12382 ClearActiveID();
12383
12384 // Move to parent menu if necessary
12385 ImGuiWindow* new_nav_window = g.NavWindow;
12386 while (new_nav_window->ParentWindow
12387 && (new_nav_window->DC.NavLayersActiveMask & (1 << ImGuiNavLayer_Menu)) == 0
12388 && (new_nav_window->Flags & ImGuiWindowFlags_ChildWindow) != 0
12389 && (new_nav_window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0)
12390 new_nav_window = new_nav_window->ParentWindow;
12391 if (new_nav_window != g.NavWindow)
12392 {
12393 ImGuiWindow* old_nav_window = g.NavWindow;
12394 FocusWindow(window: new_nav_window);
12395 new_nav_window->NavLastChildNavWindow = old_nav_window;
12396 }
12397
12398 // Toggle layer
12399 const ImGuiNavLayer new_nav_layer = (g.NavWindow->DC.NavLayersActiveMask & (1 << ImGuiNavLayer_Menu)) ? (ImGuiNavLayer)((int)g.NavLayer ^ 1) : ImGuiNavLayer_Main;
12400 if (new_nav_layer != g.NavLayer)
12401 {
12402 // Reinitialize navigation when entering menu bar with the Alt key (FIXME: could be a properly of the layer?)
12403 const bool preserve_layer_1_nav_id = (new_nav_window->DockNodeAsHost != NULL);
12404 if (new_nav_layer == ImGuiNavLayer_Menu && !preserve_layer_1_nav_id)
12405 g.NavWindow->NavLastIds[new_nav_layer] = 0;
12406 NavRestoreLayer(layer: new_nav_layer);
12407 NavRestoreHighlightAfterMove();
12408 }
12409 }
12410}
12411
12412// Window has already passed the IsWindowNavFocusable()
12413static const char* GetFallbackWindowNameForWindowingList(ImGuiWindow* window)
12414{
12415 if (window->Flags & ImGuiWindowFlags_Popup)
12416 return ImGui::LocalizeGetMsg(key: ImGuiLocKey_WindowingPopup);
12417 if ((window->Flags & ImGuiWindowFlags_MenuBar) && strcmp(s1: window->Name, s2: "##MainMenuBar") == 0)
12418 return ImGui::LocalizeGetMsg(key: ImGuiLocKey_WindowingMainMenuBar);
12419 if (window->DockNodeAsHost)
12420 return "(Dock node)"; // Not normally shown to user.
12421 return ImGui::LocalizeGetMsg(key: ImGuiLocKey_WindowingUntitled);
12422}
12423
12424// Overlay displayed when using CTRL+TAB. Called by EndFrame().
12425void ImGui::NavUpdateWindowingOverlay()
12426{
12427 ImGuiContext& g = *GImGui;
12428 IM_ASSERT(g.NavWindowingTarget != NULL);
12429
12430 if (g.NavWindowingTimer < NAV_WINDOWING_LIST_APPEAR_DELAY)
12431 return;
12432
12433 if (g.NavWindowingListWindow == NULL)
12434 g.NavWindowingListWindow = FindWindowByName(name: "###NavWindowingList");
12435 const ImGuiViewport* viewport = /*g.NavWindow ? g.NavWindow->Viewport :*/ GetMainViewport();
12436 SetNextWindowSizeConstraints(size_min: ImVec2(viewport->Size.x * 0.20f, viewport->Size.y * 0.20f), size_max: ImVec2(FLT_MAX, FLT_MAX));
12437 SetNextWindowPos(pos: viewport->GetCenter(), cond: ImGuiCond_Always, pivot: ImVec2(0.5f, 0.5f));
12438 PushStyleVar(idx: ImGuiStyleVar_WindowPadding, val: g.Style.WindowPadding * 2.0f);
12439 Begin(name: "###NavWindowingList", NULL, flags: ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings);
12440 for (int n = g.WindowsFocusOrder.Size - 1; n >= 0; n--)
12441 {
12442 ImGuiWindow* window = g.WindowsFocusOrder[n];
12443 IM_ASSERT(window != NULL); // Fix static analyzers
12444 if (!IsWindowNavFocusable(window))
12445 continue;
12446 const char* label = window->Name;
12447 if (label == FindRenderedTextEnd(text: label))
12448 label = GetFallbackWindowNameForWindowingList(window);
12449 Selectable(label, selected: g.NavWindowingTarget == window);
12450 }
12451 End();
12452 PopStyleVar();
12453}
12454
12455
12456//-----------------------------------------------------------------------------
12457// [SECTION] DRAG AND DROP
12458//-----------------------------------------------------------------------------
12459
12460bool ImGui::IsDragDropActive()
12461{
12462 ImGuiContext& g = *GImGui;
12463 return g.DragDropActive;
12464}
12465
12466void ImGui::ClearDragDrop()
12467{
12468 ImGuiContext& g = *GImGui;
12469 g.DragDropActive = false;
12470 g.DragDropPayload.Clear();
12471 g.DragDropAcceptFlags = ImGuiDragDropFlags_None;
12472 g.DragDropAcceptIdCurr = g.DragDropAcceptIdPrev = 0;
12473 g.DragDropAcceptIdCurrRectSurface = FLT_MAX;
12474 g.DragDropAcceptFrameCount = -1;
12475
12476 g.DragDropPayloadBufHeap.clear();
12477 memset(s: &g.DragDropPayloadBufLocal, c: 0, n: sizeof(g.DragDropPayloadBufLocal));
12478}
12479
12480// When this returns true you need to: a) call SetDragDropPayload() exactly once, b) you may render the payload visual/description, c) call EndDragDropSource()
12481// If the item has an identifier:
12482// - This assume/require the item to be activated (typically via ButtonBehavior).
12483// - 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.
12484// - We then pull and use the mouse button that was used to activate the item and use it to carry on the drag.
12485// If the item has no identifier:
12486// - Currently always assume left mouse button.
12487bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags)
12488{
12489 ImGuiContext& g = *GImGui;
12490 ImGuiWindow* window = g.CurrentWindow;
12491
12492 // FIXME-DRAGDROP: While in the common-most "drag from non-zero active id" case we can tell the mouse button,
12493 // in both SourceExtern and id==0 cases we may requires something else (explicit flags or some heuristic).
12494 ImGuiMouseButton mouse_button = ImGuiMouseButton_Left;
12495
12496 bool source_drag_active = false;
12497 ImGuiID source_id = 0;
12498 ImGuiID source_parent_id = 0;
12499 if (!(flags & ImGuiDragDropFlags_SourceExtern))
12500 {
12501 source_id = g.LastItemData.ID;
12502 if (source_id != 0)
12503 {
12504 // Common path: items with ID
12505 if (g.ActiveId != source_id)
12506 return false;
12507 if (g.ActiveIdMouseButton != -1)
12508 mouse_button = g.ActiveIdMouseButton;
12509 if (g.IO.MouseDown[mouse_button] == false || window->SkipItems)
12510 return false;
12511 g.ActiveIdAllowOverlap = false;
12512 }
12513 else
12514 {
12515 // Uncommon path: items without ID
12516 if (g.IO.MouseDown[mouse_button] == false || window->SkipItems)
12517 return false;
12518 if ((g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect) == 0 && (g.ActiveId == 0 || g.ActiveIdWindow != window))
12519 return false;
12520
12521 // If you want to use BeginDragDropSource() on an item with no unique identifier for interaction, such as Text() or Image(), you need to:
12522 // A) Read the explanation below, B) Use the ImGuiDragDropFlags_SourceAllowNullID flag.
12523 if (!(flags & ImGuiDragDropFlags_SourceAllowNullID))
12524 {
12525 IM_ASSERT(0);
12526 return false;
12527 }
12528
12529 // Magic fallback to handle items with no assigned ID, e.g. Text(), Image()
12530 // We build a throwaway ID based on current ID stack + relative AABB of items in window.
12531 // THE IDENTIFIER WON'T SURVIVE ANY REPOSITIONING/RESIZINGG OF THE WIDGET, so if your widget moves your dragging operation will be canceled.
12532 // We don't need to maintain/call ClearActiveID() as releasing the button will early out this function and trigger !ActiveIdIsAlive.
12533 // Rely on keeping other window->LastItemXXX fields intact.
12534 source_id = g.LastItemData.ID = window->GetIDFromRectangle(r_abs: g.LastItemData.Rect);
12535 KeepAliveID(id: source_id);
12536 bool is_hovered = ItemHoverable(bb: g.LastItemData.Rect, id: source_id);
12537 if (is_hovered && g.IO.MouseClicked[mouse_button])
12538 {
12539 SetActiveID(id: source_id, window);
12540 FocusWindow(window);
12541 }
12542 if (g.ActiveId == source_id) // Allow the underlying widget to display/return hovered during the mouse release frame, else we would get a flicker.
12543 g.ActiveIdAllowOverlap = is_hovered;
12544 }
12545 if (g.ActiveId != source_id)
12546 return false;
12547 source_parent_id = window->IDStack.back();
12548 source_drag_active = IsMouseDragging(button: mouse_button);
12549
12550 // Disable navigation and key inputs while dragging + cancel existing request if any
12551 SetActiveIdUsingAllKeyboardKeys();
12552 }
12553 else
12554 {
12555 window = NULL;
12556 source_id = ImHashStr(data_p: "#SourceExtern");
12557 source_drag_active = true;
12558 }
12559
12560 if (source_drag_active)
12561 {
12562 if (!g.DragDropActive)
12563 {
12564 IM_ASSERT(source_id != 0);
12565 ClearDragDrop();
12566 ImGuiPayload& payload = g.DragDropPayload;
12567 payload.SourceId = source_id;
12568 payload.SourceParentId = source_parent_id;
12569 g.DragDropActive = true;
12570 g.DragDropSourceFlags = flags;
12571 g.DragDropMouseButton = mouse_button;
12572 if (payload.SourceId == g.ActiveId)
12573 g.ActiveIdNoClearOnFocusLoss = true;
12574 }
12575 g.DragDropSourceFrameCount = g.FrameCount;
12576 g.DragDropWithinSource = true;
12577
12578 if (!(flags & ImGuiDragDropFlags_SourceNoPreviewTooltip))
12579 {
12580 // Target can request the Source to not display its tooltip (we use a dedicated flag to make this request explicit)
12581 // We unfortunately can't just modify the source flags and skip the call to BeginTooltip, as caller may be emitting contents.
12582 BeginTooltip();
12583 if (g.DragDropAcceptIdPrev && (g.DragDropAcceptFlags & ImGuiDragDropFlags_AcceptNoPreviewTooltip))
12584 {
12585 ImGuiWindow* tooltip_window = g.CurrentWindow;
12586 tooltip_window->Hidden = tooltip_window->SkipItems = true;
12587 tooltip_window->HiddenFramesCanSkipItems = 1;
12588 }
12589 }
12590
12591 if (!(flags & ImGuiDragDropFlags_SourceNoDisableHover) && !(flags & ImGuiDragDropFlags_SourceExtern))
12592 g.LastItemData.StatusFlags &= ~ImGuiItemStatusFlags_HoveredRect;
12593
12594 return true;
12595 }
12596 return false;
12597}
12598
12599void ImGui::EndDragDropSource()
12600{
12601 ImGuiContext& g = *GImGui;
12602 IM_ASSERT(g.DragDropActive);
12603 IM_ASSERT(g.DragDropWithinSource && "Not after a BeginDragDropSource()?");
12604
12605 if (!(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoPreviewTooltip))
12606 EndTooltip();
12607
12608 // Discard the drag if have not called SetDragDropPayload()
12609 if (g.DragDropPayload.DataFrameCount == -1)
12610 ClearDragDrop();
12611 g.DragDropWithinSource = false;
12612}
12613
12614// Use 'cond' to choose to submit payload on drag start or every frame
12615bool ImGui::SetDragDropPayload(const char* type, const void* data, size_t data_size, ImGuiCond cond)
12616{
12617 ImGuiContext& g = *GImGui;
12618 ImGuiPayload& payload = g.DragDropPayload;
12619 if (cond == 0)
12620 cond = ImGuiCond_Always;
12621
12622 IM_ASSERT(type != NULL);
12623 IM_ASSERT(strlen(type) < IM_ARRAYSIZE(payload.DataType) && "Payload type can be at most 32 characters long");
12624 IM_ASSERT((data != NULL && data_size > 0) || (data == NULL && data_size == 0));
12625 IM_ASSERT(cond == ImGuiCond_Always || cond == ImGuiCond_Once);
12626 IM_ASSERT(payload.SourceId != 0); // Not called between BeginDragDropSource() and EndDragDropSource()
12627
12628 if (cond == ImGuiCond_Always || payload.DataFrameCount == -1)
12629 {
12630 // Copy payload
12631 ImStrncpy(dst: payload.DataType, src: type, IM_ARRAYSIZE(payload.DataType));
12632 g.DragDropPayloadBufHeap.resize(new_size: 0);
12633 if (data_size > sizeof(g.DragDropPayloadBufLocal))
12634 {
12635 // Store in heap
12636 g.DragDropPayloadBufHeap.resize(new_size: (int)data_size);
12637 payload.Data = g.DragDropPayloadBufHeap.Data;
12638 memcpy(dest: payload.Data, src: data, n: data_size);
12639 }
12640 else if (data_size > 0)
12641 {
12642 // Store locally
12643 memset(s: &g.DragDropPayloadBufLocal, c: 0, n: sizeof(g.DragDropPayloadBufLocal));
12644 payload.Data = g.DragDropPayloadBufLocal;
12645 memcpy(dest: payload.Data, src: data, n: data_size);
12646 }
12647 else
12648 {
12649 payload.Data = NULL;
12650 }
12651 payload.DataSize = (int)data_size;
12652 }
12653 payload.DataFrameCount = g.FrameCount;
12654
12655 // Return whether the payload has been accepted
12656 return (g.DragDropAcceptFrameCount == g.FrameCount) || (g.DragDropAcceptFrameCount == g.FrameCount - 1);
12657}
12658
12659bool ImGui::BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id)
12660{
12661 ImGuiContext& g = *GImGui;
12662 if (!g.DragDropActive)
12663 return false;
12664
12665 ImGuiWindow* window = g.CurrentWindow;
12666 ImGuiWindow* hovered_window = g.HoveredWindowUnderMovingWindow;
12667 if (hovered_window == NULL || window->RootWindowDockTree != hovered_window->RootWindowDockTree)
12668 return false;
12669 IM_ASSERT(id != 0);
12670 if (!IsMouseHoveringRect(r_min: bb.Min, r_max: bb.Max) || (id == g.DragDropPayload.SourceId))
12671 return false;
12672 if (window->SkipItems)
12673 return false;
12674
12675 IM_ASSERT(g.DragDropWithinTarget == false);
12676 g.DragDropTargetRect = bb;
12677 g.DragDropTargetId = id;
12678 g.DragDropWithinTarget = true;
12679 return true;
12680}
12681
12682// We don't use BeginDragDropTargetCustom() and duplicate its code because:
12683// 1) we use LastItemRectHoveredRect which handles items that push a temporarily clip rectangle in their code. Calling BeginDragDropTargetCustom(LastItemRect) would not handle them.
12684// 2) and it's faster. as this code may be very frequently called, we want to early out as fast as we can.
12685// Also note how the HoveredWindow test is positioned differently in both functions (in both functions we optimize for the cheapest early out case)
12686bool ImGui::BeginDragDropTarget()
12687{
12688 ImGuiContext& g = *GImGui;
12689 if (!g.DragDropActive)
12690 return false;
12691
12692 ImGuiWindow* window = g.CurrentWindow;
12693 if (!(g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredRect))
12694 return false;
12695 ImGuiWindow* hovered_window = g.HoveredWindowUnderMovingWindow;
12696 if (hovered_window == NULL || window->RootWindowDockTree != hovered_window->RootWindowDockTree || window->SkipItems)
12697 return false;
12698
12699 const ImRect& display_rect = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HasDisplayRect) ? g.LastItemData.DisplayRect : g.LastItemData.Rect;
12700 ImGuiID id = g.LastItemData.ID;
12701 if (id == 0)
12702 {
12703 id = window->GetIDFromRectangle(r_abs: display_rect);
12704 KeepAliveID(id);
12705 }
12706 if (g.DragDropPayload.SourceId == id)
12707 return false;
12708
12709 IM_ASSERT(g.DragDropWithinTarget == false);
12710 g.DragDropTargetRect = display_rect;
12711 g.DragDropTargetId = id;
12712 g.DragDropWithinTarget = true;
12713 return true;
12714}
12715
12716bool ImGui::IsDragDropPayloadBeingAccepted()
12717{
12718 ImGuiContext& g = *GImGui;
12719 return g.DragDropActive && g.DragDropAcceptIdPrev != 0;
12720}
12721
12722const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDropFlags flags)
12723{
12724 ImGuiContext& g = *GImGui;
12725 ImGuiWindow* window = g.CurrentWindow;
12726 ImGuiPayload& payload = g.DragDropPayload;
12727 IM_ASSERT(g.DragDropActive); // Not called between BeginDragDropTarget() and EndDragDropTarget() ?
12728 IM_ASSERT(payload.DataFrameCount != -1); // Forgot to call EndDragDropTarget() ?
12729 if (type != NULL && !payload.IsDataType(type))
12730 return NULL;
12731
12732 // Accept smallest drag target bounding box, this allows us to nest drag targets conveniently without ordering constraints.
12733 // NB: We currently accept NULL id as target. However, overlapping targets requires a unique ID to function!
12734 const bool was_accepted_previously = (g.DragDropAcceptIdPrev == g.DragDropTargetId);
12735 ImRect r = g.DragDropTargetRect;
12736 float r_surface = r.GetWidth() * r.GetHeight();
12737 if (r_surface <= g.DragDropAcceptIdCurrRectSurface)
12738 {
12739 g.DragDropAcceptFlags = flags;
12740 g.DragDropAcceptIdCurr = g.DragDropTargetId;
12741 g.DragDropAcceptIdCurrRectSurface = r_surface;
12742 }
12743
12744 // Render default drop visuals
12745 payload.Preview = was_accepted_previously;
12746 flags |= (g.DragDropSourceFlags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect); // Source can also inhibit the preview (useful for external sources that live for 1 frame)
12747 if (!(flags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect) && payload.Preview)
12748 window->DrawList->AddRect(p_min: r.Min - ImVec2(3.5f,3.5f), p_max: r.Max + ImVec2(3.5f, 3.5f), col: GetColorU32(idx: ImGuiCol_DragDropTarget), rounding: 0.0f, flags: 0, thickness: 2.0f);
12749
12750 g.DragDropAcceptFrameCount = g.FrameCount;
12751 payload.Delivery = was_accepted_previously && !IsMouseDown(button: g.DragDropMouseButton); // For extern drag sources affecting os window focus, it's easier to just test !IsMouseDown() instead of IsMouseReleased()
12752 if (!payload.Delivery && !(flags & ImGuiDragDropFlags_AcceptBeforeDelivery))
12753 return NULL;
12754
12755 return &payload;
12756}
12757
12758// FIXME-DRAGDROP: Settle on a proper default visuals for drop target.
12759void ImGui::RenderDragDropTargetRect(const ImRect& bb)
12760{
12761 GetWindowDrawList()->AddRect(p_min: bb.Min - ImVec2(3.5f, 3.5f), p_max: bb.Max + ImVec2(3.5f, 3.5f), col: GetColorU32(idx: ImGuiCol_DragDropTarget), rounding: 0.0f, flags: 0, thickness: 2.0f);
12762}
12763
12764const ImGuiPayload* ImGui::GetDragDropPayload()
12765{
12766 ImGuiContext& g = *GImGui;
12767 return (g.DragDropActive && g.DragDropPayload.DataFrameCount != -1) ? &g.DragDropPayload : NULL;
12768}
12769
12770// We don't really use/need this now, but added it for the sake of consistency and because we might need it later.
12771void ImGui::EndDragDropTarget()
12772{
12773 ImGuiContext& g = *GImGui;
12774 IM_ASSERT(g.DragDropActive);
12775 IM_ASSERT(g.DragDropWithinTarget);
12776 g.DragDropWithinTarget = false;
12777}
12778
12779//-----------------------------------------------------------------------------
12780// [SECTION] LOGGING/CAPTURING
12781//-----------------------------------------------------------------------------
12782// All text output from the interface can be captured into tty/file/clipboard.
12783// By default, tree nodes are automatically opened during logging.
12784//-----------------------------------------------------------------------------
12785
12786// Pass text data straight to log (without being displayed)
12787static inline void LogTextV(ImGuiContext& g, const char* fmt, va_list args)
12788{
12789 if (g.LogFile)
12790 {
12791 g.LogBuffer.Buf.resize(new_size: 0);
12792 g.LogBuffer.appendfv(fmt, args);
12793 ImFileWrite(data: g.LogBuffer.c_str(), sz: sizeof(char), count: (ImU64)g.LogBuffer.size(), f: g.LogFile);
12794 }
12795 else
12796 {
12797 g.LogBuffer.appendfv(fmt, args);
12798 }
12799}
12800
12801void ImGui::LogText(const char* fmt, ...)
12802{
12803 ImGuiContext& g = *GImGui;
12804 if (!g.LogEnabled)
12805 return;
12806
12807 va_list args;
12808 va_start(args, fmt);
12809 LogTextV(g, fmt, args);
12810 va_end(args);
12811}
12812
12813void ImGui::LogTextV(const char* fmt, va_list args)
12814{
12815 ImGuiContext& g = *GImGui;
12816 if (!g.LogEnabled)
12817 return;
12818
12819 LogTextV(g, fmt, args);
12820}
12821
12822// Internal version that takes a position to decide on newline placement and pad items according to their depth.
12823// We split text into individual lines to add current tree level padding
12824// FIXME: This code is a little complicated perhaps, considering simplifying the whole system.
12825void ImGui::LogRenderedText(const ImVec2* ref_pos, const char* text, const char* text_end)
12826{
12827 ImGuiContext& g = *GImGui;
12828 ImGuiWindow* window = g.CurrentWindow;
12829
12830 const char* prefix = g.LogNextPrefix;
12831 const char* suffix = g.LogNextSuffix;
12832 g.LogNextPrefix = g.LogNextSuffix = NULL;
12833
12834 if (!text_end)
12835 text_end = FindRenderedTextEnd(text, text_end);
12836
12837 const bool log_new_line = ref_pos && (ref_pos->y > g.LogLinePosY + g.Style.FramePadding.y + 1);
12838 if (ref_pos)
12839 g.LogLinePosY = ref_pos->y;
12840 if (log_new_line)
12841 {
12842 LogText(IM_NEWLINE);
12843 g.LogLineFirstItem = true;
12844 }
12845
12846 if (prefix)
12847 LogRenderedText(ref_pos, text: prefix, text_end: prefix + strlen(s: prefix)); // Calculate end ourself to ensure "##" are included here.
12848
12849 // Re-adjust padding if we have popped out of our starting depth
12850 if (g.LogDepthRef > window->DC.TreeDepth)
12851 g.LogDepthRef = window->DC.TreeDepth;
12852 const int tree_depth = (window->DC.TreeDepth - g.LogDepthRef);
12853
12854 const char* text_remaining = text;
12855 for (;;)
12856 {
12857 // Split the string. Each new line (after a '\n') is followed by indentation corresponding to the current depth of our log entry.
12858 // We don't add a trailing \n yet to allow a subsequent item on the same line to be captured.
12859 const char* line_start = text_remaining;
12860 const char* line_end = ImStreolRange(str: line_start, str_end: text_end);
12861 const bool is_last_line = (line_end == text_end);
12862 if (line_start != line_end || !is_last_line)
12863 {
12864 const int line_length = (int)(line_end - line_start);
12865 const int indentation = g.LogLineFirstItem ? tree_depth * 4 : 1;
12866 LogText(fmt: "%*s%.*s", indentation, "", line_length, line_start);
12867 g.LogLineFirstItem = false;
12868 if (*line_end == '\n')
12869 {
12870 LogText(IM_NEWLINE);
12871 g.LogLineFirstItem = true;
12872 }
12873 }
12874 if (is_last_line)
12875 break;
12876 text_remaining = line_end + 1;
12877 }
12878
12879 if (suffix)
12880 LogRenderedText(ref_pos, text: suffix, text_end: suffix + strlen(s: suffix));
12881}
12882
12883// Start logging/capturing text output
12884void ImGui::LogBegin(ImGuiLogType type, int auto_open_depth)
12885{
12886 ImGuiContext& g = *GImGui;
12887 ImGuiWindow* window = g.CurrentWindow;
12888 IM_ASSERT(g.LogEnabled == false);
12889 IM_ASSERT(g.LogFile == NULL);
12890 IM_ASSERT(g.LogBuffer.empty());
12891 g.LogEnabled = true;
12892 g.LogType = type;
12893 g.LogNextPrefix = g.LogNextSuffix = NULL;
12894 g.LogDepthRef = window->DC.TreeDepth;
12895 g.LogDepthToExpand = ((auto_open_depth >= 0) ? auto_open_depth : g.LogDepthToExpandDefault);
12896 g.LogLinePosY = FLT_MAX;
12897 g.LogLineFirstItem = true;
12898}
12899
12900// Important: doesn't copy underlying data, use carefully (prefix/suffix must be in scope at the time of the next LogRenderedText)
12901void ImGui::LogSetNextTextDecoration(const char* prefix, const char* suffix)
12902{
12903 ImGuiContext& g = *GImGui;
12904 g.LogNextPrefix = prefix;
12905 g.LogNextSuffix = suffix;
12906}
12907
12908void ImGui::LogToTTY(int auto_open_depth)
12909{
12910 ImGuiContext& g = *GImGui;
12911 if (g.LogEnabled)
12912 return;
12913 IM_UNUSED(auto_open_depth);
12914#ifndef IMGUI_DISABLE_TTY_FUNCTIONS
12915 LogBegin(type: ImGuiLogType_TTY, auto_open_depth);
12916 g.LogFile = stdout;
12917#endif
12918}
12919
12920// Start logging/capturing text output to given file
12921void ImGui::LogToFile(int auto_open_depth, const char* filename)
12922{
12923 ImGuiContext& g = *GImGui;
12924 if (g.LogEnabled)
12925 return;
12926
12927 // FIXME: We could probably open the file in text mode "at", however note that clipboard/buffer logging will still
12928 // be subject to outputting OS-incompatible carriage return if within strings the user doesn't use IM_NEWLINE.
12929 // By opening the file in binary mode "ab" we have consistent output everywhere.
12930 if (!filename)
12931 filename = g.IO.LogFilename;
12932 if (!filename || !filename[0])
12933 return;
12934 ImFileHandle f = ImFileOpen(filename, mode: "ab");
12935 if (!f)
12936 {
12937 IM_ASSERT(0);
12938 return;
12939 }
12940
12941 LogBegin(type: ImGuiLogType_File, auto_open_depth);
12942 g.LogFile = f;
12943}
12944
12945// Start logging/capturing text output to clipboard
12946void ImGui::LogToClipboard(int auto_open_depth)
12947{
12948 ImGuiContext& g = *GImGui;
12949 if (g.LogEnabled)
12950 return;
12951 LogBegin(type: ImGuiLogType_Clipboard, auto_open_depth);
12952}
12953
12954void ImGui::LogToBuffer(int auto_open_depth)
12955{
12956 ImGuiContext& g = *GImGui;
12957 if (g.LogEnabled)
12958 return;
12959 LogBegin(type: ImGuiLogType_Buffer, auto_open_depth);
12960}
12961
12962void ImGui::LogFinish()
12963{
12964 ImGuiContext& g = *GImGui;
12965 if (!g.LogEnabled)
12966 return;
12967
12968 LogText(IM_NEWLINE);
12969 switch (g.LogType)
12970 {
12971 case ImGuiLogType_TTY:
12972#ifndef IMGUI_DISABLE_TTY_FUNCTIONS
12973 fflush(stream: g.LogFile);
12974#endif
12975 break;
12976 case ImGuiLogType_File:
12977 ImFileClose(f: g.LogFile);
12978 break;
12979 case ImGuiLogType_Buffer:
12980 break;
12981 case ImGuiLogType_Clipboard:
12982 if (!g.LogBuffer.empty())
12983 SetClipboardText(g.LogBuffer.begin());
12984 break;
12985 case ImGuiLogType_None:
12986 IM_ASSERT(0);
12987 break;
12988 }
12989
12990 g.LogEnabled = false;
12991 g.LogType = ImGuiLogType_None;
12992 g.LogFile = NULL;
12993 g.LogBuffer.clear();
12994}
12995
12996// Helper to display logging buttons
12997// FIXME-OBSOLETE: We should probably obsolete this and let the user have their own helper (this is one of the oldest function alive!)
12998void ImGui::LogButtons()
12999{
13000 ImGuiContext& g = *GImGui;
13001
13002 PushID(str_id: "LogButtons");
13003#ifndef IMGUI_DISABLE_TTY_FUNCTIONS
13004 const bool log_to_tty = Button(label: "Log To TTY"); SameLine();
13005#else
13006 const bool log_to_tty = false;
13007#endif
13008 const bool log_to_file = Button(label: "Log To File"); SameLine();
13009 const bool log_to_clipboard = Button(label: "Log To Clipboard"); SameLine();
13010 PushAllowKeyboardFocus(allow_keyboard_focus: false);
13011 SetNextItemWidth(80.0f);
13012 SliderInt(label: "Default Depth", v: &g.LogDepthToExpandDefault, v_min: 0, v_max: 9, NULL);
13013 PopAllowKeyboardFocus();
13014 PopID();
13015
13016 // Start logging at the end of the function so that the buttons don't appear in the log
13017 if (log_to_tty)
13018 LogToTTY();
13019 if (log_to_file)
13020 LogToFile();
13021 if (log_to_clipboard)
13022 LogToClipboard();
13023}
13024
13025
13026//-----------------------------------------------------------------------------
13027// [SECTION] SETTINGS
13028//-----------------------------------------------------------------------------
13029// - UpdateSettings() [Internal]
13030// - MarkIniSettingsDirty() [Internal]
13031// - CreateNewWindowSettings() [Internal]
13032// - FindWindowSettings() [Internal]
13033// - FindOrCreateWindowSettings() [Internal]
13034// - FindSettingsHandler() [Internal]
13035// - ClearIniSettings() [Internal]
13036// - LoadIniSettingsFromDisk()
13037// - LoadIniSettingsFromMemory()
13038// - SaveIniSettingsToDisk()
13039// - SaveIniSettingsToMemory()
13040// - WindowSettingsHandler_***() [Internal]
13041//-----------------------------------------------------------------------------
13042
13043// Called by NewFrame()
13044void ImGui::UpdateSettings()
13045{
13046 // Load settings on first frame (if not explicitly loaded manually before)
13047 ImGuiContext& g = *GImGui;
13048 if (!g.SettingsLoaded)
13049 {
13050 IM_ASSERT(g.SettingsWindows.empty());
13051 if (g.IO.IniFilename)
13052 LoadIniSettingsFromDisk(ini_filename: g.IO.IniFilename);
13053 g.SettingsLoaded = true;
13054 }
13055
13056 // Save settings (with a delay after the last modification, so we don't spam disk too much)
13057 if (g.SettingsDirtyTimer > 0.0f)
13058 {
13059 g.SettingsDirtyTimer -= g.IO.DeltaTime;
13060 if (g.SettingsDirtyTimer <= 0.0f)
13061 {
13062 if (g.IO.IniFilename != NULL)
13063 SaveIniSettingsToDisk(ini_filename: g.IO.IniFilename);
13064 else
13065 g.IO.WantSaveIniSettings = true; // Let user know they can call SaveIniSettingsToMemory(). user will need to clear io.WantSaveIniSettings themselves.
13066 g.SettingsDirtyTimer = 0.0f;
13067 }
13068 }
13069}
13070
13071void ImGui::MarkIniSettingsDirty()
13072{
13073 ImGuiContext& g = *GImGui;
13074 if (g.SettingsDirtyTimer <= 0.0f)
13075 g.SettingsDirtyTimer = g.IO.IniSavingRate;
13076}
13077
13078void ImGui::MarkIniSettingsDirty(ImGuiWindow* window)
13079{
13080 ImGuiContext& g = *GImGui;
13081 if (!(window->Flags & ImGuiWindowFlags_NoSavedSettings))
13082 if (g.SettingsDirtyTimer <= 0.0f)
13083 g.SettingsDirtyTimer = g.IO.IniSavingRate;
13084}
13085
13086ImGuiWindowSettings* ImGui::CreateNewWindowSettings(const char* name)
13087{
13088 ImGuiContext& g = *GImGui;
13089
13090#if !IMGUI_DEBUG_INI_SETTINGS
13091 // Skip to the "###" marker if any. We don't skip past to match the behavior of GetID()
13092 // Preserve the full string when IMGUI_DEBUG_INI_SETTINGS is set to make .ini inspection easier.
13093 if (const char* p = strstr(s1: name, s2: "###"))
13094 name = p;
13095#endif
13096 const size_t name_len = strlen(s: name);
13097
13098 // Allocate chunk
13099 const size_t chunk_size = sizeof(ImGuiWindowSettings) + name_len + 1;
13100 ImGuiWindowSettings* settings = g.SettingsWindows.alloc_chunk(sz: chunk_size);
13101 IM_PLACEMENT_NEW(settings) ImGuiWindowSettings();
13102 settings->ID = ImHashStr(data_p: name, data_size: name_len);
13103 memcpy(dest: settings->GetName(), src: name, n: name_len + 1); // Store with zero terminator
13104
13105 return settings;
13106}
13107
13108ImGuiWindowSettings* ImGui::FindWindowSettings(ImGuiID id)
13109{
13110 ImGuiContext& g = *GImGui;
13111 for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(p: settings))
13112 if (settings->ID == id)
13113 return settings;
13114 return NULL;
13115}
13116
13117ImGuiWindowSettings* ImGui::FindOrCreateWindowSettings(const char* name)
13118{
13119 if (ImGuiWindowSettings* settings = FindWindowSettings(id: ImHashStr(data_p: name)))
13120 return settings;
13121 return CreateNewWindowSettings(name);
13122}
13123
13124void ImGui::AddSettingsHandler(const ImGuiSettingsHandler* handler)
13125{
13126 ImGuiContext& g = *GImGui;
13127 IM_ASSERT(FindSettingsHandler(handler->TypeName) == NULL);
13128 g.SettingsHandlers.push_back(v: *handler);
13129}
13130
13131void ImGui::RemoveSettingsHandler(const char* type_name)
13132{
13133 ImGuiContext& g = *GImGui;
13134 if (ImGuiSettingsHandler* handler = FindSettingsHandler(type_name))
13135 g.SettingsHandlers.erase(it: handler);
13136}
13137
13138ImGuiSettingsHandler* ImGui::FindSettingsHandler(const char* type_name)
13139{
13140 ImGuiContext& g = *GImGui;
13141 const ImGuiID type_hash = ImHashStr(data_p: type_name);
13142 for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++)
13143 if (g.SettingsHandlers[handler_n].TypeHash == type_hash)
13144 return &g.SettingsHandlers[handler_n];
13145 return NULL;
13146}
13147
13148void ImGui::ClearIniSettings()
13149{
13150 ImGuiContext& g = *GImGui;
13151 g.SettingsIniData.clear();
13152 for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++)
13153 if (g.SettingsHandlers[handler_n].ClearAllFn)
13154 g.SettingsHandlers[handler_n].ClearAllFn(&g, &g.SettingsHandlers[handler_n]);
13155}
13156
13157void ImGui::LoadIniSettingsFromDisk(const char* ini_filename)
13158{
13159 size_t file_data_size = 0;
13160 char* file_data = (char*)ImFileLoadToMemory(filename: ini_filename, mode: "rb", out_file_size: &file_data_size);
13161 if (!file_data)
13162 return;
13163 if (file_data_size > 0)
13164 LoadIniSettingsFromMemory(ini_data: file_data, ini_size: (size_t)file_data_size);
13165 IM_FREE(file_data);
13166}
13167
13168// Zero-tolerance, no error reporting, cheap .ini parsing
13169void ImGui::LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size)
13170{
13171 ImGuiContext& g = *GImGui;
13172 IM_ASSERT(g.Initialized);
13173 //IM_ASSERT(!g.WithinFrameScope && "Cannot be called between NewFrame() and EndFrame()");
13174 //IM_ASSERT(g.SettingsLoaded == false && g.FrameCount == 0);
13175
13176 // For user convenience, we allow passing a non zero-terminated string (hence the ini_size parameter).
13177 // 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..
13178 if (ini_size == 0)
13179 ini_size = strlen(s: ini_data);
13180 g.SettingsIniData.Buf.resize(new_size: (int)ini_size + 1);
13181 char* const buf = g.SettingsIniData.Buf.Data;
13182 char* const buf_end = buf + ini_size;
13183 memcpy(dest: buf, src: ini_data, n: ini_size);
13184 buf_end[0] = 0;
13185
13186 // Call pre-read handlers
13187 // Some types will clear their data (e.g. dock information) some types will allow merge/override (window)
13188 for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++)
13189 if (g.SettingsHandlers[handler_n].ReadInitFn)
13190 g.SettingsHandlers[handler_n].ReadInitFn(&g, &g.SettingsHandlers[handler_n]);
13191
13192 void* entry_data = NULL;
13193 ImGuiSettingsHandler* entry_handler = NULL;
13194
13195 char* line_end = NULL;
13196 for (char* line = buf; line < buf_end; line = line_end + 1)
13197 {
13198 // Skip new lines markers, then find end of the line
13199 while (*line == '\n' || *line == '\r')
13200 line++;
13201 line_end = line;
13202 while (line_end < buf_end && *line_end != '\n' && *line_end != '\r')
13203 line_end++;
13204 line_end[0] = 0;
13205 if (line[0] == ';')
13206 continue;
13207 if (line[0] == '[' && line_end > line && line_end[-1] == ']')
13208 {
13209 // Parse "[Type][Name]". Note that 'Name' can itself contains [] characters, which is acceptable with the current format and parsing code.
13210 line_end[-1] = 0;
13211 const char* name_end = line_end - 1;
13212 const char* type_start = line + 1;
13213 char* type_end = (char*)(void*)ImStrchrRange(str: type_start, str_end: name_end, c: ']');
13214 const char* name_start = type_end ? ImStrchrRange(str: type_end + 1, str_end: name_end, c: '[') : NULL;
13215 if (!type_end || !name_start)
13216 continue;
13217 *type_end = 0; // Overwrite first ']'
13218 name_start++; // Skip second '['
13219 entry_handler = FindSettingsHandler(type_name: type_start);
13220 entry_data = entry_handler ? entry_handler->ReadOpenFn(&g, entry_handler, name_start) : NULL;
13221 }
13222 else if (entry_handler != NULL && entry_data != NULL)
13223 {
13224 // Let type handler parse the line
13225 entry_handler->ReadLineFn(&g, entry_handler, entry_data, line);
13226 }
13227 }
13228 g.SettingsLoaded = true;
13229
13230 // [DEBUG] Restore untouched copy so it can be browsed in Metrics (not strictly necessary)
13231 memcpy(dest: buf, src: ini_data, n: ini_size);
13232
13233 // Call post-read handlers
13234 for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++)
13235 if (g.SettingsHandlers[handler_n].ApplyAllFn)
13236 g.SettingsHandlers[handler_n].ApplyAllFn(&g, &g.SettingsHandlers[handler_n]);
13237}
13238
13239void ImGui::SaveIniSettingsToDisk(const char* ini_filename)
13240{
13241 ImGuiContext& g = *GImGui;
13242 g.SettingsDirtyTimer = 0.0f;
13243 if (!ini_filename)
13244 return;
13245
13246 size_t ini_data_size = 0;
13247 const char* ini_data = SaveIniSettingsToMemory(out_ini_size: &ini_data_size);
13248 ImFileHandle f = ImFileOpen(filename: ini_filename, mode: "wt");
13249 if (!f)
13250 return;
13251 ImFileWrite(data: ini_data, sz: sizeof(char), count: ini_data_size, f);
13252 ImFileClose(f);
13253}
13254
13255// Call registered handlers (e.g. SettingsHandlerWindow_WriteAll() + custom handlers) to write their stuff into a text buffer
13256const char* ImGui::SaveIniSettingsToMemory(size_t* out_size)
13257{
13258 ImGuiContext& g = *GImGui;
13259 g.SettingsDirtyTimer = 0.0f;
13260 g.SettingsIniData.Buf.resize(new_size: 0);
13261 g.SettingsIniData.Buf.push_back(v: 0);
13262 for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++)
13263 {
13264 ImGuiSettingsHandler* handler = &g.SettingsHandlers[handler_n];
13265 handler->WriteAllFn(&g, handler, &g.SettingsIniData);
13266 }
13267 if (out_size)
13268 *out_size = (size_t)g.SettingsIniData.size();
13269 return g.SettingsIniData.c_str();
13270}
13271
13272static void WindowSettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettingsHandler*)
13273{
13274 ImGuiContext& g = *ctx;
13275 for (int i = 0; i != g.Windows.Size; i++)
13276 g.Windows[i]->SettingsOffset = -1;
13277 g.SettingsWindows.clear();
13278}
13279
13280static void* WindowSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name)
13281{
13282 ImGuiWindowSettings* settings = ImGui::FindOrCreateWindowSettings(name);
13283 ImGuiID id = settings->ID;
13284 *settings = ImGuiWindowSettings(); // Clear existing if recycling previous entry
13285 settings->ID = id;
13286 settings->WantApply = true;
13287 return (void*)settings;
13288}
13289
13290static void WindowSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line)
13291{
13292 ImGuiWindowSettings* settings = (ImGuiWindowSettings*)entry;
13293 int x, y;
13294 int i;
13295 ImU32 u1;
13296 if (sscanf(s: line, format: "Pos=%i,%i", &x, &y) == 2) { settings->Pos = ImVec2ih((short)x, (short)y); }
13297 else if (sscanf(s: line, format: "Size=%i,%i", &x, &y) == 2) { settings->Size = ImVec2ih((short)x, (short)y); }
13298 else if (sscanf(s: line, format: "ViewportId=0x%08X", &u1) == 1) { settings->ViewportId = u1; }
13299 else if (sscanf(s: line, format: "ViewportPos=%i,%i", &x, &y) == 2){ settings->ViewportPos = ImVec2ih((short)x, (short)y); }
13300 else if (sscanf(s: line, format: "Collapsed=%d", &i) == 1) { settings->Collapsed = (i != 0); }
13301 else if (sscanf(s: line, format: "DockId=0x%X,%d", &u1, &i) == 2) { settings->DockId = u1; settings->DockOrder = (short)i; }
13302 else if (sscanf(s: line, format: "DockId=0x%X", &u1) == 1) { settings->DockId = u1; settings->DockOrder = -1; }
13303 else if (sscanf(s: line, format: "ClassId=0x%X", &u1) == 1) { settings->ClassId = u1; }
13304}
13305
13306// Apply to existing windows (if any)
13307static void WindowSettingsHandler_ApplyAll(ImGuiContext* ctx, ImGuiSettingsHandler*)
13308{
13309 ImGuiContext& g = *ctx;
13310 for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(p: settings))
13311 if (settings->WantApply)
13312 {
13313 if (ImGuiWindow* window = ImGui::FindWindowByID(id: settings->ID))
13314 ApplyWindowSettings(window, settings);
13315 settings->WantApply = false;
13316 }
13317}
13318
13319static void WindowSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf)
13320{
13321 // Gather data from windows that were active during this session
13322 // (if a window wasn't opened in this session we preserve its settings)
13323 ImGuiContext& g = *ctx;
13324 for (int i = 0; i != g.Windows.Size; i++)
13325 {
13326 ImGuiWindow* window = g.Windows[i];
13327 if (window->Flags & ImGuiWindowFlags_NoSavedSettings)
13328 continue;
13329
13330 ImGuiWindowSettings* settings = (window->SettingsOffset != -1) ? g.SettingsWindows.ptr_from_offset(off: window->SettingsOffset) : ImGui::FindWindowSettings(id: window->ID);
13331 if (!settings)
13332 {
13333 settings = ImGui::CreateNewWindowSettings(name: window->Name);
13334 window->SettingsOffset = g.SettingsWindows.offset_from_ptr(p: settings);
13335 }
13336 IM_ASSERT(settings->ID == window->ID);
13337 settings->Pos = ImVec2ih(window->Pos - window->ViewportPos);
13338 settings->Size = ImVec2ih(window->SizeFull);
13339 settings->ViewportId = window->ViewportId;
13340 settings->ViewportPos = ImVec2ih(window->ViewportPos);
13341 IM_ASSERT(window->DockNode == NULL || window->DockNode->ID == window->DockId);
13342 settings->DockId = window->DockId;
13343 settings->ClassId = window->WindowClass.ClassId;
13344 settings->DockOrder = window->DockOrder;
13345 settings->Collapsed = window->Collapsed;
13346 }
13347
13348 // Write to text buffer
13349 buf->reserve(capacity: buf->size() + g.SettingsWindows.size() * 6); // ballpark reserve
13350 for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(p: settings))
13351 {
13352 const char* settings_name = settings->GetName();
13353 buf->appendf(fmt: "[%s][%s]\n", handler->TypeName, settings_name);
13354 if (settings->ViewportId != 0 && settings->ViewportId != ImGui::IMGUI_VIEWPORT_DEFAULT_ID)
13355 {
13356 buf->appendf(fmt: "ViewportPos=%d,%d\n", settings->ViewportPos.x, settings->ViewportPos.y);
13357 buf->appendf(fmt: "ViewportId=0x%08X\n", settings->ViewportId);
13358 }
13359 if (settings->Pos.x != 0 || settings->Pos.y != 0 || settings->ViewportId == ImGui::IMGUI_VIEWPORT_DEFAULT_ID)
13360 buf->appendf(fmt: "Pos=%d,%d\n", settings->Pos.x, settings->Pos.y);
13361 if (settings->Size.x != 0 || settings->Size.y != 0)
13362 buf->appendf(fmt: "Size=%d,%d\n", settings->Size.x, settings->Size.y);
13363 buf->appendf(fmt: "Collapsed=%d\n", settings->Collapsed);
13364 if (settings->DockId != 0)
13365 {
13366 //buf->appendf("TabId=0x%08X\n", ImHashStr("#TAB", 4, settings->ID)); // window->TabId: this is not read back but writing it makes "debugging" the .ini data easier.
13367 if (settings->DockOrder == -1)
13368 buf->appendf(fmt: "DockId=0x%08X\n", settings->DockId);
13369 else
13370 buf->appendf(fmt: "DockId=0x%08X,%d\n", settings->DockId, settings->DockOrder);
13371 if (settings->ClassId != 0)
13372 buf->appendf(fmt: "ClassId=0x%08X\n", settings->ClassId);
13373 }
13374 buf->append(str: "\n");
13375 }
13376}
13377
13378
13379//-----------------------------------------------------------------------------
13380// [SECTION] LOCALIZATION
13381//-----------------------------------------------------------------------------
13382
13383void ImGui::LocalizeRegisterEntries(const ImGuiLocEntry* entries, int count)
13384{
13385 ImGuiContext& g = *GImGui;
13386 for (int n = 0; n < count; n++)
13387 g.LocalizationTable[entries[n].Key] = entries[n].Text;
13388}
13389
13390
13391//-----------------------------------------------------------------------------
13392// [SECTION] VIEWPORTS, PLATFORM WINDOWS
13393//-----------------------------------------------------------------------------
13394// - GetMainViewport()
13395// - FindViewportByID()
13396// - FindViewportByPlatformHandle()
13397// - SetCurrentViewport() [Internal]
13398// - SetWindowViewport() [Internal]
13399// - GetWindowAlwaysWantOwnViewport() [Internal]
13400// - UpdateTryMergeWindowIntoHostViewport() [Internal]
13401// - UpdateTryMergeWindowIntoHostViewports() [Internal]
13402// - TranslateWindowsInViewport() [Internal]
13403// - ScaleWindowsInViewport() [Internal]
13404// - FindHoveredViewportFromPlatformWindowStack() [Internal]
13405// - UpdateViewportsNewFrame() [Internal]
13406// - UpdateViewportsEndFrame() [Internal]
13407// - AddUpdateViewport() [Internal]
13408// - WindowSelectViewport() [Internal]
13409// - WindowSyncOwnedViewport() [Internal]
13410// - UpdatePlatformWindows()
13411// - RenderPlatformWindowsDefault()
13412// - FindPlatformMonitorForPos() [Internal]
13413// - FindPlatformMonitorForRect() [Internal]
13414// - UpdateViewportPlatformMonitor() [Internal]
13415// - DestroyPlatformWindow() [Internal]
13416// - DestroyPlatformWindows()
13417//-----------------------------------------------------------------------------
13418
13419ImGuiViewport* ImGui::GetMainViewport()
13420{
13421 ImGuiContext& g = *GImGui;
13422 return g.Viewports[0];
13423}
13424
13425// FIXME: This leaks access to viewports not listed in PlatformIO.Viewports[]. Problematic? (#4236)
13426ImGuiViewport* ImGui::FindViewportByID(ImGuiID id)
13427{
13428 ImGuiContext& g = *GImGui;
13429 for (int n = 0; n < g.Viewports.Size; n++)
13430 if (g.Viewports[n]->ID == id)
13431 return g.Viewports[n];
13432 return NULL;
13433}
13434
13435ImGuiViewport* ImGui::FindViewportByPlatformHandle(void* platform_handle)
13436{
13437 ImGuiContext& g = *GImGui;
13438 for (int i = 0; i != g.Viewports.Size; i++)
13439 if (g.Viewports[i]->PlatformHandle == platform_handle)
13440 return g.Viewports[i];
13441 return NULL;
13442}
13443
13444void ImGui::SetCurrentViewport(ImGuiWindow* current_window, ImGuiViewportP* viewport)
13445{
13446 ImGuiContext& g = *GImGui;
13447 (void)current_window;
13448
13449 if (viewport)
13450 viewport->LastFrameActive = g.FrameCount;
13451 if (g.CurrentViewport == viewport)
13452 return;
13453 g.CurrentDpiScale = viewport ? viewport->DpiScale : 1.0f;
13454 g.CurrentViewport = viewport;
13455 //IMGUI_DEBUG_LOG_VIEWPORT("[viewport] SetCurrentViewport changed '%s' 0x%08X\n", current_window ? current_window->Name : NULL, viewport ? viewport->ID : 0);
13456
13457 // Notify platform layer of viewport changes
13458 // FIXME-DPI: This is only currently used for experimenting with handling of multiple DPI
13459 if (g.CurrentViewport && g.PlatformIO.Platform_OnChangedViewport)
13460 g.PlatformIO.Platform_OnChangedViewport(g.CurrentViewport);
13461}
13462
13463void ImGui::SetWindowViewport(ImGuiWindow* window, ImGuiViewportP* viewport)
13464{
13465 // Abandon viewport
13466 if (window->ViewportOwned && window->Viewport->Window == window)
13467 window->Viewport->Size = ImVec2(0.0f, 0.0f);
13468
13469 window->Viewport = viewport;
13470 window->ViewportId = viewport->ID;
13471 window->ViewportOwned = (viewport->Window == window);
13472}
13473
13474static bool ImGui::GetWindowAlwaysWantOwnViewport(ImGuiWindow* window)
13475{
13476 // 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.
13477 ImGuiContext& g = *GImGui;
13478 if (g.IO.ConfigViewportsNoAutoMerge || (window->WindowClass.ViewportFlagsOverrideSet & ImGuiViewportFlags_NoAutoMerge))
13479 if (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable)
13480 if (!window->DockIsActive)
13481 if ((window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_Tooltip)) == 0)
13482 if ((window->Flags & ImGuiWindowFlags_Popup) == 0 || (window->Flags & ImGuiWindowFlags_Modal) != 0)
13483 return true;
13484 return false;
13485}
13486
13487static bool ImGui::UpdateTryMergeWindowIntoHostViewport(ImGuiWindow* window, ImGuiViewportP* viewport)
13488{
13489 ImGuiContext& g = *GImGui;
13490 if (window->Viewport == viewport)
13491 return false;
13492 if ((viewport->Flags & ImGuiViewportFlags_CanHostOtherWindows) == 0)
13493 return false;
13494 if ((viewport->Flags & ImGuiViewportFlags_Minimized) != 0)
13495 return false;
13496 if (!viewport->GetMainRect().Contains(r: window->Rect()))
13497 return false;
13498 if (GetWindowAlwaysWantOwnViewport(window))
13499 return false;
13500
13501 // FIXME: Can't use g.WindowsFocusOrder[] for root windows only as we care about Z order. If we maintained a DisplayOrder along with FocusOrder we could..
13502 for (int n = 0; n < g.Windows.Size; n++)
13503 {
13504 ImGuiWindow* window_behind = g.Windows[n];
13505 if (window_behind == window)
13506 break;
13507 if (window_behind->WasActive && window_behind->ViewportOwned && !(window_behind->Flags & ImGuiWindowFlags_ChildWindow))
13508 if (window_behind->Viewport->GetMainRect().Overlaps(r: window->Rect()))
13509 return false;
13510 }
13511
13512 // Move to the existing viewport, Move child/hosted windows as well (FIXME-OPT: iterate child)
13513 ImGuiViewportP* old_viewport = window->Viewport;
13514 if (window->ViewportOwned)
13515 for (int n = 0; n < g.Windows.Size; n++)
13516 if (g.Windows[n]->Viewport == old_viewport)
13517 SetWindowViewport(window: g.Windows[n], viewport);
13518 SetWindowViewport(window, viewport);
13519 BringWindowToDisplayFront(window);
13520
13521 return true;
13522}
13523
13524// FIXME: handle 0 to N host viewports
13525static bool ImGui::UpdateTryMergeWindowIntoHostViewports(ImGuiWindow* window)
13526{
13527 ImGuiContext& g = *GImGui;
13528 return UpdateTryMergeWindowIntoHostViewport(window, viewport: g.Viewports[0]);
13529}
13530
13531// Translate Dear ImGui windows when a Host Viewport has been moved
13532// (This additionally keeps windows at the same place when ImGuiConfigFlags_ViewportsEnable is toggled!)
13533void ImGui::TranslateWindowsInViewport(ImGuiViewportP* viewport, const ImVec2& old_pos, const ImVec2& new_pos)
13534{
13535 ImGuiContext& g = *GImGui;
13536 IM_ASSERT(viewport->Window == NULL && (viewport->Flags & ImGuiViewportFlags_CanHostOtherWindows));
13537
13538 // 1) We test if ImGuiConfigFlags_ViewportsEnable was just toggled, which allows us to conveniently
13539 // translate imgui windows from OS-window-local to absolute coordinates or vice-versa.
13540 // 2) If it's not going to fit into the new size, keep it at same absolute position.
13541 // One problem with this is that most Win32 applications doesn't update their render while dragging,
13542 // and so the window will appear to teleport when releasing the mouse.
13543 const bool translate_all_windows = (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable) != (g.ConfigFlagsLastFrame & ImGuiConfigFlags_ViewportsEnable);
13544 ImRect test_still_fit_rect(old_pos, old_pos + viewport->Size);
13545 ImVec2 delta_pos = new_pos - old_pos;
13546 for (int window_n = 0; window_n < g.Windows.Size; window_n++) // FIXME-OPT
13547 if (translate_all_windows || (g.Windows[window_n]->Viewport == viewport && test_still_fit_rect.Contains(r: g.Windows[window_n]->Rect())))
13548 TranslateWindow(window: g.Windows[window_n], delta: delta_pos);
13549}
13550
13551// Scale all windows (position, size). Use when e.g. changing DPI. (This is a lossy operation!)
13552void ImGui::ScaleWindowsInViewport(ImGuiViewportP* viewport, float scale)
13553{
13554 ImGuiContext& g = *GImGui;
13555 if (viewport->Window)
13556 {
13557 ScaleWindow(window: viewport->Window, scale);
13558 }
13559 else
13560 {
13561 for (int i = 0; i != g.Windows.Size; i++)
13562 if (g.Windows[i]->Viewport == viewport)
13563 ScaleWindow(window: g.Windows[i], scale);
13564 }
13565}
13566
13567// If the backend doesn't set MouseLastHoveredViewport or doesn't honor ImGuiViewportFlags_NoInputs, we do a search ourselves.
13568// A) It won't take account of the possibility that non-imgui windows may be in-between our dragged window and our target window.
13569// B) It requires Platform_GetWindowFocus to be implemented by backend.
13570ImGuiViewportP* ImGui::FindHoveredViewportFromPlatformWindowStack(const ImVec2& mouse_platform_pos)
13571{
13572 ImGuiContext& g = *GImGui;
13573 ImGuiViewportP* best_candidate = NULL;
13574 for (int n = 0; n < g.Viewports.Size; n++)
13575 {
13576 ImGuiViewportP* viewport = g.Viewports[n];
13577 if (!(viewport->Flags & (ImGuiViewportFlags_NoInputs | ImGuiViewportFlags_Minimized)) && viewport->GetMainRect().Contains(p: mouse_platform_pos))
13578 if (best_candidate == NULL || best_candidate->LastFrontMostStampCount < viewport->LastFrontMostStampCount)
13579 best_candidate = viewport;
13580 }
13581 return best_candidate;
13582}
13583
13584// Update viewports and monitor infos
13585// Note that this is running even if 'ImGuiConfigFlags_ViewportsEnable' is not set, in order to clear unused viewports (if any) and update monitor info.
13586static void ImGui::UpdateViewportsNewFrame()
13587{
13588 ImGuiContext& g = *GImGui;
13589 IM_ASSERT(g.PlatformIO.Viewports.Size <= g.Viewports.Size);
13590
13591 // Update Minimized status (we need it first in order to decide if we'll apply Pos/Size of the main viewport)
13592 const bool viewports_enabled = (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable) != 0;
13593 if (viewports_enabled)
13594 {
13595 for (int n = 0; n < g.Viewports.Size; n++)
13596 {
13597 ImGuiViewportP* viewport = g.Viewports[n];
13598 const bool platform_funcs_available = viewport->PlatformWindowCreated;
13599 if (g.PlatformIO.Platform_GetWindowMinimized && platform_funcs_available)
13600 {
13601 bool minimized = g.PlatformIO.Platform_GetWindowMinimized(viewport);
13602 if (minimized)
13603 viewport->Flags |= ImGuiViewportFlags_Minimized;
13604 else
13605 viewport->Flags &= ~ImGuiViewportFlags_Minimized;
13606 }
13607 }
13608 }
13609
13610 // Create/update main viewport with current platform position.
13611 // FIXME-VIEWPORT: Size is driven by backend/user code for backward-compatibility but we should aim to make this more consistent.
13612 ImGuiViewportP* main_viewport = g.Viewports[0];
13613 IM_ASSERT(main_viewport->ID == IMGUI_VIEWPORT_DEFAULT_ID);
13614 IM_ASSERT(main_viewport->Window == NULL);
13615 ImVec2 main_viewport_pos = viewports_enabled ? g.PlatformIO.Platform_GetWindowPos(main_viewport) : ImVec2(0.0f, 0.0f);
13616 ImVec2 main_viewport_size = g.IO.DisplaySize;
13617 if (viewports_enabled && (main_viewport->Flags & ImGuiViewportFlags_Minimized))
13618 {
13619 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)
13620 main_viewport_size = main_viewport->Size;
13621 }
13622 AddUpdateViewport(NULL, id: IMGUI_VIEWPORT_DEFAULT_ID, platform_pos: main_viewport_pos, size: main_viewport_size, flags: ImGuiViewportFlags_OwnedByApp | ImGuiViewportFlags_CanHostOtherWindows);
13623
13624 g.CurrentDpiScale = 0.0f;
13625 g.CurrentViewport = NULL;
13626 g.MouseViewport = NULL;
13627 for (int n = 0; n < g.Viewports.Size; n++)
13628 {
13629 ImGuiViewportP* viewport = g.Viewports[n];
13630 viewport->Idx = n;
13631
13632 // Erase unused viewports
13633 if (n > 0 && viewport->LastFrameActive < g.FrameCount - 2)
13634 {
13635 DestroyViewport(viewport);
13636 n--;
13637 continue;
13638 }
13639
13640 const bool platform_funcs_available = viewport->PlatformWindowCreated;
13641 if (viewports_enabled)
13642 {
13643 // Update Position and Size (from Platform Window to ImGui) if requested.
13644 // We do it early in the frame instead of waiting for UpdatePlatformWindows() to avoid a frame of lag when moving/resizing using OS facilities.
13645 if (!(viewport->Flags & ImGuiViewportFlags_Minimized) && platform_funcs_available)
13646 {
13647 // Viewport->WorkPos and WorkSize will be updated below
13648 if (viewport->PlatformRequestMove)
13649 viewport->Pos = viewport->LastPlatformPos = g.PlatformIO.Platform_GetWindowPos(viewport);
13650 if (viewport->PlatformRequestResize)
13651 viewport->Size = viewport->LastPlatformSize = g.PlatformIO.Platform_GetWindowSize(viewport);
13652 }
13653 }
13654
13655 // Update/copy monitor info
13656 UpdateViewportPlatformMonitor(viewport);
13657
13658 // Lock down space taken by menu bars and status bars, reset the offset for functions like BeginMainMenuBar() to alter them again.
13659 viewport->WorkOffsetMin = viewport->BuildWorkOffsetMin;
13660 viewport->WorkOffsetMax = viewport->BuildWorkOffsetMax;
13661 viewport->BuildWorkOffsetMin = viewport->BuildWorkOffsetMax = ImVec2(0.0f, 0.0f);
13662 viewport->UpdateWorkRect();
13663
13664 // Reset alpha every frame. Users of transparency (docking) needs to request a lower alpha back.
13665 viewport->Alpha = 1.0f;
13666
13667 // Translate Dear ImGui windows when a Host Viewport has been moved
13668 // (This additionally keeps windows at the same place when ImGuiConfigFlags_ViewportsEnable is toggled!)
13669 const ImVec2 viewport_delta_pos = viewport->Pos - viewport->LastPos;
13670 if ((viewport->Flags & ImGuiViewportFlags_CanHostOtherWindows) && (viewport_delta_pos.x != 0.0f || viewport_delta_pos.y != 0.0f))
13671 TranslateWindowsInViewport(viewport, old_pos: viewport->LastPos, new_pos: viewport->Pos);
13672
13673 // Update DPI scale
13674 float new_dpi_scale;
13675 if (g.PlatformIO.Platform_GetWindowDpiScale && platform_funcs_available)
13676 new_dpi_scale = g.PlatformIO.Platform_GetWindowDpiScale(viewport);
13677 else if (viewport->PlatformMonitor != -1)
13678 new_dpi_scale = g.PlatformIO.Monitors[viewport->PlatformMonitor].DpiScale;
13679 else
13680 new_dpi_scale = (viewport->DpiScale != 0.0f) ? viewport->DpiScale : 1.0f;
13681 if (viewport->DpiScale != 0.0f && new_dpi_scale != viewport->DpiScale)
13682 {
13683 float scale_factor = new_dpi_scale / viewport->DpiScale;
13684 if (g.IO.ConfigFlags & ImGuiConfigFlags_DpiEnableScaleViewports)
13685 ScaleWindowsInViewport(viewport, scale: scale_factor);
13686 //if (viewport == GetMainViewport())
13687 // g.PlatformInterface.SetWindowSize(viewport, viewport->Size * scale_factor);
13688
13689 // Scale our window moving pivot so that the window will rescale roughly around the mouse position.
13690 // FIXME-VIEWPORT: This currently creates a resizing feedback loop when a window is straddling a DPI transition border.
13691 // (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.)
13692 //if (g.MovingWindow != NULL && g.MovingWindow->Viewport == viewport)
13693 // g.ActiveIdClickOffset = ImFloor(g.ActiveIdClickOffset * scale_factor);
13694 }
13695 viewport->DpiScale = new_dpi_scale;
13696 }
13697
13698 // Update fallback monitor
13699 if (g.PlatformIO.Monitors.Size == 0)
13700 {
13701 ImGuiPlatformMonitor* monitor = &g.FallbackMonitor;
13702 monitor->MainPos = main_viewport->Pos;
13703 monitor->MainSize = main_viewport->Size;
13704 monitor->WorkPos = main_viewport->WorkPos;
13705 monitor->WorkSize = main_viewport->WorkSize;
13706 monitor->DpiScale = main_viewport->DpiScale;
13707 }
13708
13709 if (!viewports_enabled)
13710 {
13711 g.MouseViewport = main_viewport;
13712 return;
13713 }
13714
13715 // Mouse handling: decide on the actual mouse viewport for this frame between the active/focused viewport and the hovered viewport.
13716 // Note that 'viewport_hovered' should skip over any viewport that has the ImGuiViewportFlags_NoInputs flags set.
13717 ImGuiViewportP* viewport_hovered = NULL;
13718 if (g.IO.BackendFlags & ImGuiBackendFlags_HasMouseHoveredViewport)
13719 {
13720 viewport_hovered = g.IO.MouseHoveredViewport ? (ImGuiViewportP*)FindViewportByID(id: g.IO.MouseHoveredViewport) : NULL;
13721 if (viewport_hovered && (viewport_hovered->Flags & ImGuiViewportFlags_NoInputs))
13722 viewport_hovered = FindHoveredViewportFromPlatformWindowStack(mouse_platform_pos: g.IO.MousePos); // Backend failed to handle _NoInputs viewport: revert to our fallback.
13723 }
13724 else
13725 {
13726 // If the backend doesn't know how to honor ImGuiViewportFlags_NoInputs, we do a search ourselves. Note that this search:
13727 // A) won't take account of the possibility that non-imgui windows may be in-between our dragged window and our target window.
13728 // B) won't take account of how the backend apply parent<>child relationship to secondary viewports, which affects their Z order.
13729 // C) uses LastFrameAsRefViewport as a flawed replacement for the last time a window was focused (we could/should fix that by introducing Focus functions in PlatformIO)
13730 viewport_hovered = FindHoveredViewportFromPlatformWindowStack(mouse_platform_pos: g.IO.MousePos);
13731 }
13732 if (viewport_hovered != NULL)
13733 g.MouseLastHoveredViewport = viewport_hovered;
13734 else if (g.MouseLastHoveredViewport == NULL)
13735 g.MouseLastHoveredViewport = g.Viewports[0];
13736
13737 // Update mouse reference viewport
13738 // (when moving a window we aim at its viewport, but this will be overwritten below if we go in drag and drop mode)
13739 // (MovingViewport->Viewport will be NULL in the rare situation where the window disappared while moving, set UpdateMouseMovingWindowNewFrame() for details)
13740 if (g.MovingWindow && g.MovingWindow->Viewport)
13741 g.MouseViewport = g.MovingWindow->Viewport;
13742 else
13743 g.MouseViewport = g.MouseLastHoveredViewport;
13744
13745 // When dragging something, always refer to the last hovered viewport.
13746 // - when releasing a moving window we will revert to aiming behind (at viewport_hovered)
13747 // - 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)
13748 // - 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.
13749 // FIXME-VIEWPORT: This is essentially broken, when ImGuiBackendFlags_HasMouseHoveredViewport is set we want to trust when viewport_hovered==NULL and use that.
13750 const bool is_mouse_dragging_with_an_expected_destination = g.DragDropActive;
13751 if (is_mouse_dragging_with_an_expected_destination && viewport_hovered == NULL)
13752 viewport_hovered = g.MouseLastHoveredViewport;
13753 if (is_mouse_dragging_with_an_expected_destination || g.ActiveId == 0 || !IsAnyMouseDown())
13754 if (viewport_hovered != NULL && viewport_hovered != g.MouseViewport && !(viewport_hovered->Flags & ImGuiViewportFlags_NoInputs))
13755 g.MouseViewport = viewport_hovered;
13756
13757 IM_ASSERT(g.MouseViewport != NULL);
13758}
13759
13760// Update user-facing viewport list (g.Viewports -> g.PlatformIO.Viewports after filtering out some)
13761static void ImGui::UpdateViewportsEndFrame()
13762{
13763 ImGuiContext& g = *GImGui;
13764 g.PlatformIO.Viewports.resize(new_size: 0);
13765 for (int i = 0; i < g.Viewports.Size; i++)
13766 {
13767 ImGuiViewportP* viewport = g.Viewports[i];
13768 viewport->LastPos = viewport->Pos;
13769 if (viewport->LastFrameActive < g.FrameCount || viewport->Size.x <= 0.0f || viewport->Size.y <= 0.0f)
13770 if (i > 0) // Always include main viewport in the list
13771 continue;
13772 if (viewport->Window && !IsWindowActiveAndVisible(window: viewport->Window))
13773 continue;
13774 if (i > 0)
13775 IM_ASSERT(viewport->Window != NULL);
13776 g.PlatformIO.Viewports.push_back(v: viewport);
13777 }
13778 g.Viewports[0]->ClearRequestFlags(); // Clear main viewport flags because UpdatePlatformWindows() won't do it and may not even be called
13779}
13780
13781// FIXME: We should ideally refactor the system to call this every frame (we currently don't)
13782ImGuiViewportP* ImGui::AddUpdateViewport(ImGuiWindow* window, ImGuiID id, const ImVec2& pos, const ImVec2& size, ImGuiViewportFlags flags)
13783{
13784 ImGuiContext& g = *GImGui;
13785 IM_ASSERT(id != 0);
13786
13787 flags |= ImGuiViewportFlags_IsPlatformWindow;
13788 if (window != NULL)
13789 {
13790 if (g.MovingWindow && g.MovingWindow->RootWindowDockTree == window)
13791 flags |= ImGuiViewportFlags_NoInputs | ImGuiViewportFlags_NoFocusOnAppearing;
13792 if ((window->Flags & ImGuiWindowFlags_NoMouseInputs) && (window->Flags & ImGuiWindowFlags_NoNavInputs))
13793 flags |= ImGuiViewportFlags_NoInputs;
13794 if (window->Flags & ImGuiWindowFlags_NoFocusOnAppearing)
13795 flags |= ImGuiViewportFlags_NoFocusOnAppearing;
13796 }
13797
13798 ImGuiViewportP* viewport = (ImGuiViewportP*)FindViewportByID(id);
13799 if (viewport)
13800 {
13801 // Always update for main viewport as we are already pulling correct platform pos/size (see #4900)
13802 if (!viewport->PlatformRequestMove || viewport->ID == IMGUI_VIEWPORT_DEFAULT_ID)
13803 viewport->Pos = pos;
13804 if (!viewport->PlatformRequestResize || viewport->ID == IMGUI_VIEWPORT_DEFAULT_ID)
13805 viewport->Size = size;
13806 viewport->Flags = flags | (viewport->Flags & ImGuiViewportFlags_Minimized); // Preserve existing flags
13807 }
13808 else
13809 {
13810 // New viewport
13811 viewport = IM_NEW(ImGuiViewportP)();
13812 viewport->ID = id;
13813 viewport->Idx = g.Viewports.Size;
13814 viewport->Pos = viewport->LastPos = pos;
13815 viewport->Size = size;
13816 viewport->Flags = flags;
13817 UpdateViewportPlatformMonitor(viewport);
13818 g.Viewports.push_back(v: viewport);
13819 IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Add Viewport %08X '%s'\n", id, window ? window->Name : "<NULL>");
13820
13821 // We normally setup for all viewports in NewFrame() but here need to handle the mid-frame creation of a new viewport.
13822 // We need to extend the fullscreen clip rect so the OverlayDrawList clip is correct for that the first frame
13823 g.DrawListSharedData.ClipRectFullscreen.x = ImMin(lhs: g.DrawListSharedData.ClipRectFullscreen.x, rhs: viewport->Pos.x);
13824 g.DrawListSharedData.ClipRectFullscreen.y = ImMin(lhs: g.DrawListSharedData.ClipRectFullscreen.y, rhs: viewport->Pos.y);
13825 g.DrawListSharedData.ClipRectFullscreen.z = ImMax(lhs: g.DrawListSharedData.ClipRectFullscreen.z, rhs: viewport->Pos.x + viewport->Size.x);
13826 g.DrawListSharedData.ClipRectFullscreen.w = ImMax(lhs: g.DrawListSharedData.ClipRectFullscreen.w, rhs: viewport->Pos.y + viewport->Size.y);
13827
13828 // Store initial DpiScale before the OS platform window creation, based on expected monitor data.
13829 // This is so we can select an appropriate font size on the first frame of our window lifetime
13830 if (viewport->PlatformMonitor != -1)
13831 viewport->DpiScale = g.PlatformIO.Monitors[viewport->PlatformMonitor].DpiScale;
13832 }
13833
13834 viewport->Window = window;
13835 viewport->LastFrameActive = g.FrameCount;
13836 viewport->UpdateWorkRect();
13837 IM_ASSERT(window == NULL || viewport->ID == window->ID);
13838
13839 if (window != NULL)
13840 window->ViewportOwned = true;
13841
13842 return viewport;
13843}
13844
13845static void ImGui::DestroyViewport(ImGuiViewportP* viewport)
13846{
13847 // Clear references to this viewport in windows (window->ViewportId becomes the master data)
13848 ImGuiContext& g = *GImGui;
13849 for (int window_n = 0; window_n < g.Windows.Size; window_n++)
13850 {
13851 ImGuiWindow* window = g.Windows[window_n];
13852 if (window->Viewport != viewport)
13853 continue;
13854 window->Viewport = NULL;
13855 window->ViewportOwned = false;
13856 }
13857 if (viewport == g.MouseLastHoveredViewport)
13858 g.MouseLastHoveredViewport = NULL;
13859
13860 // Destroy
13861 IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Delete Viewport %08X '%s'\n", viewport->ID, viewport->Window ? viewport->Window->Name : "n/a");
13862 DestroyPlatformWindow(viewport); // In most circumstances the platform window will already be destroyed here.
13863 IM_ASSERT(g.PlatformIO.Viewports.contains(viewport) == false);
13864 IM_ASSERT(g.Viewports[viewport->Idx] == viewport);
13865 g.Viewports.erase(it: g.Viewports.Data + viewport->Idx);
13866 IM_DELETE(p: viewport);
13867}
13868
13869// FIXME-VIEWPORT: This is all super messy and ought to be clarified or rewritten.
13870static void ImGui::WindowSelectViewport(ImGuiWindow* window)
13871{
13872 ImGuiContext& g = *GImGui;
13873 ImGuiWindowFlags flags = window->Flags;
13874 window->ViewportAllowPlatformMonitorExtend = -1;
13875
13876 // Restore main viewport if multi-viewport is not supported by the backend
13877 ImGuiViewportP* main_viewport = (ImGuiViewportP*)(void*)GetMainViewport();
13878 if (!(g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable))
13879 {
13880 SetWindowViewport(window, viewport: main_viewport);
13881 return;
13882 }
13883 window->ViewportOwned = false;
13884
13885 // Appearing popups reset their viewport so they can inherit again
13886 if ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) && window->Appearing)
13887 {
13888 window->Viewport = NULL;
13889 window->ViewportId = 0;
13890 }
13891
13892 if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasViewport) == 0)
13893 {
13894 // By default inherit from parent window
13895 if (window->Viewport == NULL && window->ParentWindow && (!window->ParentWindow->IsFallbackWindow || window->ParentWindow->WasActive))
13896 window->Viewport = window->ParentWindow->Viewport;
13897
13898 // 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
13899 if (window->Viewport == NULL && window->ViewportId != 0)
13900 {
13901 window->Viewport = (ImGuiViewportP*)FindViewportByID(id: window->ViewportId);
13902 if (window->Viewport == NULL && window->ViewportPos.x != FLT_MAX && window->ViewportPos.y != FLT_MAX)
13903 window->Viewport = AddUpdateViewport(window, id: window->ID, pos: window->ViewportPos, size: window->Size, flags: ImGuiViewportFlags_None);
13904 }
13905 }
13906
13907 bool lock_viewport = false;
13908 if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasViewport)
13909 {
13910 // Code explicitly request a viewport
13911 window->Viewport = (ImGuiViewportP*)FindViewportByID(id: g.NextWindowData.ViewportId);
13912 window->ViewportId = g.NextWindowData.ViewportId; // Store ID even if Viewport isn't resolved yet.
13913 lock_viewport = true;
13914 }
13915 else if ((flags & ImGuiWindowFlags_ChildWindow) || (flags & ImGuiWindowFlags_ChildMenu))
13916 {
13917 // Always inherit viewport from parent window
13918 if (window->DockNode && window->DockNode->HostWindow)
13919 IM_ASSERT(window->DockNode->HostWindow->Viewport == window->ParentWindow->Viewport);
13920 window->Viewport = window->ParentWindow->Viewport;
13921 }
13922 else if (window->DockNode && window->DockNode->HostWindow)
13923 {
13924 // This covers the "always inherit viewport from parent window" case for when a window reattach to a node that was just created mid-frame
13925 window->Viewport = window->DockNode->HostWindow->Viewport;
13926 }
13927 else if (flags & ImGuiWindowFlags_Tooltip)
13928 {
13929 window->Viewport = g.MouseViewport;
13930 }
13931 else if (GetWindowAlwaysWantOwnViewport(window))
13932 {
13933 window->Viewport = AddUpdateViewport(window, id: window->ID, pos: window->Pos, size: window->Size, flags: ImGuiViewportFlags_None);
13934 }
13935 else if (g.MovingWindow && g.MovingWindow->RootWindowDockTree == window && IsMousePosValid())
13936 {
13937 if (window->Viewport != NULL && window->Viewport->Window == window)
13938 window->Viewport = AddUpdateViewport(window, id: window->ID, pos: window->Pos, size: window->Size, flags: ImGuiViewportFlags_None);
13939 }
13940 else
13941 {
13942 // Merge into host viewport?
13943 // We cannot test window->ViewportOwned as it set lower in the function.
13944 // Testing (g.ActiveId == 0 || g.ActiveIdAllowOverlap) to avoid merging during a short-term widget interaction. Main intent was to avoid during resize (see #4212)
13945 bool try_to_merge_into_host_viewport = (window->Viewport && window == window->Viewport->Window && (g.ActiveId == 0 || g.ActiveIdAllowOverlap));
13946 if (try_to_merge_into_host_viewport)
13947 UpdateTryMergeWindowIntoHostViewports(window);
13948 }
13949
13950 // Fallback: merge in default viewport if z-order matches, otherwise create a new viewport
13951 if (window->Viewport == NULL)
13952 if (!UpdateTryMergeWindowIntoHostViewport(window, viewport: main_viewport))
13953 window->Viewport = AddUpdateViewport(window, id: window->ID, pos: window->Pos, size: window->Size, flags: ImGuiViewportFlags_None);
13954
13955 // Mark window as allowed to protrude outside of its viewport and into the current monitor
13956 if (!lock_viewport)
13957 {
13958 if (flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup))
13959 {
13960 // We need to take account of the possibility that mouse may become invalid.
13961 // Popups/Tooltip always set ViewportAllowPlatformMonitorExtend so GetWindowAllowedExtentRect() will return full monitor bounds.
13962 ImVec2 mouse_ref = (flags & ImGuiWindowFlags_Tooltip) ? g.IO.MousePos : g.BeginPopupStack.back().OpenMousePos;
13963 bool use_mouse_ref = (g.NavDisableHighlight || !g.NavDisableMouseHover || !g.NavWindow);
13964 bool mouse_valid = IsMousePosValid(mouse_pos: &mouse_ref);
13965 if ((window->Appearing || (flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_ChildMenu))) && (!use_mouse_ref || mouse_valid))
13966 window->ViewportAllowPlatformMonitorExtend = FindPlatformMonitorForPos(pos: (use_mouse_ref && mouse_valid) ? mouse_ref : NavCalcPreferredRefPos());
13967 else
13968 window->ViewportAllowPlatformMonitorExtend = window->Viewport->PlatformMonitor;
13969 }
13970 else if (window->Viewport && window != window->Viewport->Window && window->Viewport->Window && !(flags & ImGuiWindowFlags_ChildWindow) && window->DockNode == NULL)
13971 {
13972 // When called from Begin() we don't have access to a proper version of the Hidden flag yet, so we replicate this code.
13973 const bool will_be_visible = (window->DockIsActive && !window->DockTabIsVisible) ? false : true;
13974 if ((window->Flags & ImGuiWindowFlags_DockNodeHost) && window->Viewport->LastFrameActive < g.FrameCount && will_be_visible)
13975 {
13976 // Steal/transfer ownership
13977 IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Window '%s' steal Viewport %08X from Window '%s'\n", window->Name, window->Viewport->ID, window->Viewport->Window->Name);
13978 window->Viewport->Window = window;
13979 window->Viewport->ID = window->ID;
13980 window->Viewport->LastNameHash = 0;
13981 }
13982 else if (!UpdateTryMergeWindowIntoHostViewports(window)) // Merge?
13983 {
13984 // New viewport
13985 window->Viewport = AddUpdateViewport(window, id: window->ID, pos: window->Pos, size: window->Size, flags: ImGuiViewportFlags_NoFocusOnAppearing);
13986 }
13987 }
13988 else if (window->ViewportAllowPlatformMonitorExtend < 0 && (flags & ImGuiWindowFlags_ChildWindow) == 0)
13989 {
13990 // Regular (non-child, non-popup) windows by default are also allowed to protrude
13991 // Child windows are kept contained within their parent.
13992 window->ViewportAllowPlatformMonitorExtend = window->Viewport->PlatformMonitor;
13993 }
13994 }
13995
13996 // Update flags
13997 window->ViewportOwned = (window == window->Viewport->Window);
13998 window->ViewportId = window->Viewport->ID;
13999
14000 // If the OS window has a title bar, hide our imgui title bar
14001 //if (window->ViewportOwned && !(window->Viewport->Flags & ImGuiViewportFlags_NoDecoration))
14002 // window->Flags |= ImGuiWindowFlags_NoTitleBar;
14003}
14004
14005void ImGui::WindowSyncOwnedViewport(ImGuiWindow* window, ImGuiWindow* parent_window_in_stack)
14006{
14007 ImGuiContext& g = *GImGui;
14008
14009 bool viewport_rect_changed = false;
14010
14011 // Synchronize window --> viewport in most situations
14012 // Synchronize viewport -> window in case the platform window has been moved or resized from the OS/WM
14013 if (window->Viewport->PlatformRequestMove)
14014 {
14015 window->Pos = window->Viewport->Pos;
14016 MarkIniSettingsDirty(window);
14017 }
14018 else if (memcmp(s1: &window->Viewport->Pos, s2: &window->Pos, n: sizeof(window->Pos)) != 0)
14019 {
14020 viewport_rect_changed = true;
14021 window->Viewport->Pos = window->Pos;
14022 }
14023
14024 if (window->Viewport->PlatformRequestResize)
14025 {
14026 window->Size = window->SizeFull = window->Viewport->Size;
14027 MarkIniSettingsDirty(window);
14028 }
14029 else if (memcmp(s1: &window->Viewport->Size, s2: &window->Size, n: sizeof(window->Size)) != 0)
14030 {
14031 viewport_rect_changed = true;
14032 window->Viewport->Size = window->Size;
14033 }
14034 window->Viewport->UpdateWorkRect();
14035
14036 // The viewport may have changed monitor since the global update in UpdateViewportsNewFrame()
14037 // Either a SetNextWindowPos() call in the current frame or a SetWindowPos() call in the previous frame may have this effect.
14038 if (viewport_rect_changed)
14039 UpdateViewportPlatformMonitor(viewport: window->Viewport);
14040
14041 // Update common viewport flags
14042 const ImGuiViewportFlags viewport_flags_to_clear = ImGuiViewportFlags_TopMost | ImGuiViewportFlags_NoTaskBarIcon | ImGuiViewportFlags_NoDecoration | ImGuiViewportFlags_NoRendererClear;
14043 ImGuiViewportFlags viewport_flags = window->Viewport->Flags & ~viewport_flags_to_clear;
14044 ImGuiWindowFlags window_flags = window->Flags;
14045 const bool is_modal = (window_flags & ImGuiWindowFlags_Modal) != 0;
14046 const bool is_short_lived_floating_window = (window_flags & (ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup)) != 0;
14047 if (window_flags & ImGuiWindowFlags_Tooltip)
14048 viewport_flags |= ImGuiViewportFlags_TopMost;
14049 if ((g.IO.ConfigViewportsNoTaskBarIcon || is_short_lived_floating_window) && !is_modal)
14050 viewport_flags |= ImGuiViewportFlags_NoTaskBarIcon;
14051 if (g.IO.ConfigViewportsNoDecoration || is_short_lived_floating_window)
14052 viewport_flags |= ImGuiViewportFlags_NoDecoration;
14053
14054 // Not correct to set modal as topmost because:
14055 // - Because other popups can be stacked above a modal (e.g. combo box in a modal)
14056 // - ImGuiViewportFlags_TopMost is currently handled different in backends: in Win32 it is "appear top most" whereas in GLFW and SDL it is "stay topmost"
14057 //if (flags & ImGuiWindowFlags_Modal)
14058 // viewport_flags |= ImGuiViewportFlags_TopMost;
14059
14060 // For popups and menus that may be protruding out of their parent viewport, we enable _NoFocusOnClick so that clicking on them
14061 // won't steal the OS focus away from their parent window (which may be reflected in OS the title bar decoration).
14062 // 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,
14063 // but it shouldn't be much of a problem considering those are already popups that are closed when clicking elsewhere.
14064 if (is_short_lived_floating_window && !is_modal)
14065 viewport_flags |= ImGuiViewportFlags_NoFocusOnAppearing | ImGuiViewportFlags_NoFocusOnClick;
14066
14067 // We can overwrite viewport flags using ImGuiWindowClass (advanced users)
14068 if (window->WindowClass.ViewportFlagsOverrideSet)
14069 viewport_flags |= window->WindowClass.ViewportFlagsOverrideSet;
14070 if (window->WindowClass.ViewportFlagsOverrideClear)
14071 viewport_flags &= ~window->WindowClass.ViewportFlagsOverrideClear;
14072
14073 // We can also tell the backend that clearing the platform window won't be necessary,
14074 // as our window background is filling the viewport and we have disabled BgAlpha.
14075 // FIXME: Work on support for per-viewport transparency (#2766)
14076 if (!(window_flags & ImGuiWindowFlags_NoBackground))
14077 viewport_flags |= ImGuiViewportFlags_NoRendererClear;
14078
14079 window->Viewport->Flags = viewport_flags;
14080
14081 // Update parent viewport ID
14082 // (the !IsFallbackWindow test mimic the one done in WindowSelectViewport())
14083 if (window->WindowClass.ParentViewportId != (ImGuiID)-1)
14084 window->Viewport->ParentViewportId = window->WindowClass.ParentViewportId;
14085 else if ((window_flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) && parent_window_in_stack && (!parent_window_in_stack->IsFallbackWindow || parent_window_in_stack->WasActive))
14086 window->Viewport->ParentViewportId = parent_window_in_stack->Viewport->ID;
14087 else
14088 window->Viewport->ParentViewportId = g.IO.ConfigViewportsNoDefaultParent ? 0 : IMGUI_VIEWPORT_DEFAULT_ID;
14089}
14090
14091// Called by user at the end of the main loop, after EndFrame()
14092// This will handle the creation/update of all OS windows via function defined in the ImGuiPlatformIO api.
14093void ImGui::UpdatePlatformWindows()
14094{
14095 ImGuiContext& g = *GImGui;
14096 IM_ASSERT(g.FrameCountEnded == g.FrameCount && "Forgot to call Render() or EndFrame() before UpdatePlatformWindows()?");
14097 IM_ASSERT(g.FrameCountPlatformEnded < g.FrameCount);
14098 g.FrameCountPlatformEnded = g.FrameCount;
14099 if (!(g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable))
14100 return;
14101
14102 // Create/resize/destroy platform windows to match each active viewport.
14103 // Skip the main viewport (index 0), which is always fully handled by the application!
14104 for (int i = 1; i < g.Viewports.Size; i++)
14105 {
14106 ImGuiViewportP* viewport = g.Viewports[i];
14107
14108 // Destroy platform window if the viewport hasn't been submitted or if it is hosting a hidden window
14109 // (the implicit/fallback Debug##Default window will be registering its viewport then be disabled, causing a dummy DestroyPlatformWindow to be made each frame)
14110 bool destroy_platform_window = false;
14111 destroy_platform_window |= (viewport->LastFrameActive < g.FrameCount - 1);
14112 destroy_platform_window |= (viewport->Window && !IsWindowActiveAndVisible(window: viewport->Window));
14113 if (destroy_platform_window)
14114 {
14115 DestroyPlatformWindow(viewport);
14116 continue;
14117 }
14118
14119 // New windows that appears directly in a new viewport won't always have a size on their first frame
14120 if (viewport->LastFrameActive < g.FrameCount || viewport->Size.x <= 0 || viewport->Size.y <= 0)
14121 continue;
14122
14123 // Create window
14124 const bool is_new_platform_window = (viewport->PlatformWindowCreated == false);
14125 if (is_new_platform_window)
14126 {
14127 IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Create Platform Window %08X '%s'\n", viewport->ID, viewport->Window ? viewport->Window->Name : "n/a");
14128 g.PlatformIO.Platform_CreateWindow(viewport);
14129 if (g.PlatformIO.Renderer_CreateWindow != NULL)
14130 g.PlatformIO.Renderer_CreateWindow(viewport);
14131 viewport->LastNameHash = 0;
14132 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?)
14133 viewport->LastRendererSize = viewport->Size; // We don't need to call Renderer_SetWindowSize() as it is expected Renderer_CreateWindow() already did it.
14134 viewport->PlatformWindowCreated = true;
14135 }
14136
14137 // Apply Position and Size (from ImGui to Platform/Renderer backends)
14138 if ((viewport->LastPlatformPos.x != viewport->Pos.x || viewport->LastPlatformPos.y != viewport->Pos.y) && !viewport->PlatformRequestMove)
14139 g.PlatformIO.Platform_SetWindowPos(viewport, viewport->Pos);
14140 if ((viewport->LastPlatformSize.x != viewport->Size.x || viewport->LastPlatformSize.y != viewport->Size.y) && !viewport->PlatformRequestResize)
14141 g.PlatformIO.Platform_SetWindowSize(viewport, viewport->Size);
14142 if ((viewport->LastRendererSize.x != viewport->Size.x || viewport->LastRendererSize.y != viewport->Size.y) && g.PlatformIO.Renderer_SetWindowSize)
14143 g.PlatformIO.Renderer_SetWindowSize(viewport, viewport->Size);
14144 viewport->LastPlatformPos = viewport->Pos;
14145 viewport->LastPlatformSize = viewport->LastRendererSize = viewport->Size;
14146
14147 // Update title bar (if it changed)
14148 if (ImGuiWindow* window_for_title = GetWindowForTitleDisplay(window: viewport->Window))
14149 {
14150 const char* title_begin = window_for_title->Name;
14151 char* title_end = (char*)(intptr_t)FindRenderedTextEnd(text: title_begin);
14152 const ImGuiID title_hash = ImHashStr(data_p: title_begin, data_size: title_end - title_begin);
14153 if (viewport->LastNameHash != title_hash)
14154 {
14155 char title_end_backup_c = *title_end;
14156 *title_end = 0; // Cut existing buffer short instead of doing an alloc/free, no small gain.
14157 g.PlatformIO.Platform_SetWindowTitle(viewport, title_begin);
14158 *title_end = title_end_backup_c;
14159 viewport->LastNameHash = title_hash;
14160 }
14161 }
14162
14163 // Update alpha (if it changed)
14164 if (viewport->LastAlpha != viewport->Alpha && g.PlatformIO.Platform_SetWindowAlpha)
14165 g.PlatformIO.Platform_SetWindowAlpha(viewport, viewport->Alpha);
14166 viewport->LastAlpha = viewport->Alpha;
14167
14168 // Optional, general purpose call to allow the backend to perform general book-keeping even if things haven't changed.
14169 if (g.PlatformIO.Platform_UpdateWindow)
14170 g.PlatformIO.Platform_UpdateWindow(viewport);
14171
14172 if (is_new_platform_window)
14173 {
14174 // 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)
14175 if (g.FrameCount < 3)
14176 viewport->Flags |= ImGuiViewportFlags_NoFocusOnAppearing;
14177
14178 // Show window
14179 g.PlatformIO.Platform_ShowWindow(viewport);
14180
14181 // Even without focus, we assume the window becomes front-most.
14182 // This is useful for our platform z-order heuristic when io.MouseHoveredViewport is not available.
14183 if (viewport->LastFrontMostStampCount != g.ViewportFrontMostStampCount)
14184 viewport->LastFrontMostStampCount = ++g.ViewportFrontMostStampCount;
14185 }
14186
14187 // Clear request flags
14188 viewport->ClearRequestFlags();
14189 }
14190
14191 // Update our implicit z-order knowledge of platform windows, which is used when the backend cannot provide io.MouseHoveredViewport.
14192 // When setting Platform_GetWindowFocus, it is expected that the platform backend can handle calls without crashing if it doesn't have data stored.
14193 // FIXME-VIEWPORT: We should use this information to also set dear imgui-side focus, allowing us to handle os-level alt+tab.
14194 if (g.PlatformIO.Platform_GetWindowFocus != NULL)
14195 {
14196 ImGuiViewportP* focused_viewport = NULL;
14197 for (int n = 0; n < g.Viewports.Size && focused_viewport == NULL; n++)
14198 {
14199 ImGuiViewportP* viewport = g.Viewports[n];
14200 if (viewport->PlatformWindowCreated)
14201 if (g.PlatformIO.Platform_GetWindowFocus(viewport))
14202 focused_viewport = viewport;
14203 }
14204
14205 // Store a tag so we can infer z-order easily from all our windows
14206 // We compare PlatformLastFocusedViewportId so newly created viewports with _NoFocusOnAppearing flag
14207 // will keep the front most stamp instead of losing it back to their parent viewport.
14208 if (focused_viewport && g.PlatformLastFocusedViewportId != focused_viewport->ID)
14209 {
14210 if (focused_viewport->LastFrontMostStampCount != g.ViewportFrontMostStampCount)
14211 focused_viewport->LastFrontMostStampCount = ++g.ViewportFrontMostStampCount;
14212 g.PlatformLastFocusedViewportId = focused_viewport->ID;
14213 }
14214 }
14215}
14216
14217// This is a default/basic function for performing the rendering/swap of multiple Platform Windows.
14218// Custom renderers may prefer to not call this function at all, and instead iterate the publicly exposed platform data and handle rendering/sync themselves.
14219// The Render/Swap functions stored in ImGuiPlatformIO are merely here to allow for this helper to exist, but you can do it yourself:
14220//
14221// ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
14222// for (int i = 1; i < platform_io.Viewports.Size; i++)
14223// if ((platform_io.Viewports[i]->Flags & ImGuiViewportFlags_Minimized) == 0)
14224// MyRenderFunction(platform_io.Viewports[i], my_args);
14225// for (int i = 1; i < platform_io.Viewports.Size; i++)
14226// if ((platform_io.Viewports[i]->Flags & ImGuiViewportFlags_Minimized) == 0)
14227// MySwapBufferFunction(platform_io.Viewports[i], my_args);
14228//
14229void ImGui::RenderPlatformWindowsDefault(void* platform_render_arg, void* renderer_render_arg)
14230{
14231 // Skip the main viewport (index 0), which is always fully handled by the application!
14232 ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
14233 for (int i = 1; i < platform_io.Viewports.Size; i++)
14234 {
14235 ImGuiViewport* viewport = platform_io.Viewports[i];
14236 if (viewport->Flags & ImGuiViewportFlags_Minimized)
14237 continue;
14238 if (platform_io.Platform_RenderWindow) platform_io.Platform_RenderWindow(viewport, platform_render_arg);
14239 if (platform_io.Renderer_RenderWindow) platform_io.Renderer_RenderWindow(viewport, renderer_render_arg);
14240 }
14241 for (int i = 1; i < platform_io.Viewports.Size; i++)
14242 {
14243 ImGuiViewport* viewport = platform_io.Viewports[i];
14244 if (viewport->Flags & ImGuiViewportFlags_Minimized)
14245 continue;
14246 if (platform_io.Platform_SwapBuffers) platform_io.Platform_SwapBuffers(viewport, platform_render_arg);
14247 if (platform_io.Renderer_SwapBuffers) platform_io.Renderer_SwapBuffers(viewport, renderer_render_arg);
14248 }
14249}
14250
14251static int ImGui::FindPlatformMonitorForPos(const ImVec2& pos)
14252{
14253 ImGuiContext& g = *GImGui;
14254 for (int monitor_n = 0; monitor_n < g.PlatformIO.Monitors.Size; monitor_n++)
14255 {
14256 const ImGuiPlatformMonitor& monitor = g.PlatformIO.Monitors[monitor_n];
14257 if (ImRect(monitor.MainPos, monitor.MainPos + monitor.MainSize).Contains(p: pos))
14258 return monitor_n;
14259 }
14260 return -1;
14261}
14262
14263// Search for the monitor with the largest intersection area with the given rectangle
14264// We generally try to avoid searching loops but the monitor count should be very small here
14265// FIXME-OPT: We could test the last monitor used for that viewport first, and early
14266static int ImGui::FindPlatformMonitorForRect(const ImRect& rect)
14267{
14268 ImGuiContext& g = *GImGui;
14269
14270 const int monitor_count = g.PlatformIO.Monitors.Size;
14271 if (monitor_count <= 1)
14272 return monitor_count - 1;
14273
14274 // 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.
14275 // This is necessary for tooltips which always resize down to zero at first.
14276 const float surface_threshold = ImMax(lhs: rect.GetWidth() * rect.GetHeight() * 0.5f, rhs: 1.0f);
14277 int best_monitor_n = -1;
14278 float best_monitor_surface = 0.001f;
14279
14280 for (int monitor_n = 0; monitor_n < g.PlatformIO.Monitors.Size && best_monitor_surface < surface_threshold; monitor_n++)
14281 {
14282 const ImGuiPlatformMonitor& monitor = g.PlatformIO.Monitors[monitor_n];
14283 const ImRect monitor_rect = ImRect(monitor.MainPos, monitor.MainPos + monitor.MainSize);
14284 if (monitor_rect.Contains(r: rect))
14285 return monitor_n;
14286 ImRect overlapping_rect = rect;
14287 overlapping_rect.ClipWithFull(r: monitor_rect);
14288 float overlapping_surface = overlapping_rect.GetWidth() * overlapping_rect.GetHeight();
14289 if (overlapping_surface < best_monitor_surface)
14290 continue;
14291 best_monitor_surface = overlapping_surface;
14292 best_monitor_n = monitor_n;
14293 }
14294 return best_monitor_n;
14295}
14296
14297// Update monitor from viewport rectangle (we'll use this info to clamp windows and save windows lost in a removed monitor)
14298static void ImGui::UpdateViewportPlatformMonitor(ImGuiViewportP* viewport)
14299{
14300 viewport->PlatformMonitor = (short)FindPlatformMonitorForRect(rect: viewport->GetMainRect());
14301}
14302
14303// Return value is always != NULL, but don't hold on it across frames.
14304const ImGuiPlatformMonitor* ImGui::GetViewportPlatformMonitor(ImGuiViewport* viewport_p)
14305{
14306 ImGuiContext& g = *GImGui;
14307 ImGuiViewportP* viewport = (ImGuiViewportP*)(void*)viewport_p;
14308 int monitor_idx = viewport->PlatformMonitor;
14309 if (monitor_idx >= 0 && monitor_idx < g.PlatformIO.Monitors.Size)
14310 return &g.PlatformIO.Monitors[monitor_idx];
14311 return &g.FallbackMonitor;
14312}
14313
14314void ImGui::DestroyPlatformWindow(ImGuiViewportP* viewport)
14315{
14316 ImGuiContext& g = *GImGui;
14317 if (viewport->PlatformWindowCreated)
14318 {
14319 if (g.PlatformIO.Renderer_DestroyWindow)
14320 g.PlatformIO.Renderer_DestroyWindow(viewport);
14321 if (g.PlatformIO.Platform_DestroyWindow)
14322 g.PlatformIO.Platform_DestroyWindow(viewport);
14323 IM_ASSERT(viewport->RendererUserData == NULL && viewport->PlatformUserData == NULL);
14324
14325 // Don't clear PlatformWindowCreated for the main viewport, as we initially set that up to true in Initialize()
14326 // The righter way may be to leave it to the backend to set this flag all-together, and made the flag public.
14327 if (viewport->ID != IMGUI_VIEWPORT_DEFAULT_ID)
14328 viewport->PlatformWindowCreated = false;
14329 }
14330 else
14331 {
14332 IM_ASSERT(viewport->RendererUserData == NULL && viewport->PlatformUserData == NULL && viewport->PlatformHandle == NULL);
14333 }
14334 viewport->RendererUserData = viewport->PlatformUserData = viewport->PlatformHandle = NULL;
14335 viewport->ClearRequestFlags();
14336}
14337
14338void ImGui::DestroyPlatformWindows()
14339{
14340 // We call the destroy window on every viewport (including the main viewport, index 0) to give a chance to the backend
14341 // to clear any data they may have stored in e.g. PlatformUserData, RendererUserData.
14342 // It is convenient for the platform backend code to store something in the main viewport, in order for e.g. the mouse handling
14343 // code to operator a consistent manner.
14344 // It is expected that the backend can handle calls to Renderer_DestroyWindow/Platform_DestroyWindow without
14345 // crashing if it doesn't have data stored.
14346 ImGuiContext& g = *GImGui;
14347 for (int i = 0; i < g.Viewports.Size; i++)
14348 DestroyPlatformWindow(viewport: g.Viewports[i]);
14349}
14350
14351
14352//-----------------------------------------------------------------------------
14353// [SECTION] DOCKING
14354//-----------------------------------------------------------------------------
14355// Docking: Internal Types
14356// Docking: Forward Declarations
14357// Docking: ImGuiDockContext
14358// Docking: ImGuiDockContext Docking/Undocking functions
14359// Docking: ImGuiDockNode
14360// Docking: ImGuiDockNode Tree manipulation functions
14361// Docking: Public Functions (SetWindowDock, DockSpace, DockSpaceOverViewport)
14362// Docking: Builder Functions
14363// Docking: Begin/End Support Functions (called from Begin/End)
14364// Docking: Settings
14365//-----------------------------------------------------------------------------
14366
14367//-----------------------------------------------------------------------------
14368// Typical Docking call flow: (root level is generally public API):
14369//-----------------------------------------------------------------------------
14370// - NewFrame() new dear imgui frame
14371// | DockContextNewFrameUpdateUndocking() - process queued undocking requests
14372// | - DockContextProcessUndockWindow() - process one window undocking request
14373// | - DockContextProcessUndockNode() - process one whole node undocking request
14374// | DockContextNewFrameUpdateUndocking() - process queue docking requests, create floating dock nodes
14375// | - update g.HoveredDockNode - [debug] update node hovered by mouse
14376// | - DockContextProcessDock() - process one docking request
14377// | - DockNodeUpdate()
14378// | - DockNodeUpdateForRootNode()
14379// | - DockNodeUpdateFlagsAndCollapse()
14380// | - DockNodeFindInfo()
14381// | - destroy unused node or tab bar
14382// | - create dock node host window
14383// | - Begin() etc.
14384// | - DockNodeStartMouseMovingWindow()
14385// | - DockNodeTreeUpdatePosSize()
14386// | - DockNodeTreeUpdateSplitter()
14387// | - draw node background
14388// | - DockNodeUpdateTabBar() - create/update tab bar for a docking node
14389// | - DockNodeAddTabBar()
14390// | - DockNodeUpdateWindowMenu()
14391// | - DockNodeCalcTabBarLayout()
14392// | - BeginTabBarEx()
14393// | - TabItemEx() calls
14394// | - EndTabBar()
14395// | - BeginDockableDragDropTarget()
14396// | - DockNodeUpdate() - recurse into child nodes...
14397//-----------------------------------------------------------------------------
14398// - DockSpace() user submit a dockspace into a window
14399// | Begin(Child) - create a child window
14400// | DockNodeUpdate() - call main dock node update function
14401// | End(Child)
14402// | ItemSize()
14403//-----------------------------------------------------------------------------
14404// - Begin()
14405// | BeginDocked()
14406// | BeginDockableDragDropSource()
14407// | BeginDockableDragDropTarget()
14408// | - DockNodePreviewDockRender()
14409//-----------------------------------------------------------------------------
14410// - EndFrame()
14411// | DockContextEndFrame()
14412//-----------------------------------------------------------------------------
14413
14414//-----------------------------------------------------------------------------
14415// Docking: Internal Types
14416//-----------------------------------------------------------------------------
14417// - ImGuiDockRequestType
14418// - ImGuiDockRequest
14419// - ImGuiDockPreviewData
14420// - ImGuiDockNodeSettings
14421// - ImGuiDockContext
14422//-----------------------------------------------------------------------------
14423
14424enum ImGuiDockRequestType
14425{
14426 ImGuiDockRequestType_None = 0,
14427 ImGuiDockRequestType_Dock,
14428 ImGuiDockRequestType_Undock,
14429 ImGuiDockRequestType_Split // Split is the same as Dock but without a DockPayload
14430};
14431
14432struct ImGuiDockRequest
14433{
14434 ImGuiDockRequestType Type;
14435 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)
14436 ImGuiDockNode* DockTargetNode; // Destination/Target Node to dock into
14437 ImGuiWindow* DockPayload; // Source/Payload window to dock (may be a loose window or a DockNode), [Optional]
14438 ImGuiDir DockSplitDir;
14439 float DockSplitRatio;
14440 bool DockSplitOuter;
14441 ImGuiWindow* UndockTargetWindow;
14442 ImGuiDockNode* UndockTargetNode;
14443
14444 ImGuiDockRequest()
14445 {
14446 Type = ImGuiDockRequestType_None;
14447 DockTargetWindow = DockPayload = UndockTargetWindow = NULL;
14448 DockTargetNode = UndockTargetNode = NULL;
14449 DockSplitDir = ImGuiDir_None;
14450 DockSplitRatio = 0.5f;
14451 DockSplitOuter = false;
14452 }
14453};
14454
14455struct ImGuiDockPreviewData
14456{
14457 ImGuiDockNode FutureNode;
14458 bool IsDropAllowed;
14459 bool IsCenterAvailable;
14460 bool IsSidesAvailable; // Hold your breath, grammar freaks..
14461 bool IsSplitDirExplicit; // Set when hovered the drop rect (vs. implicit SplitDir==None when hovered the window)
14462 ImGuiDockNode* SplitNode;
14463 ImGuiDir SplitDir;
14464 float SplitRatio;
14465 ImRect DropRectsDraw[ImGuiDir_COUNT + 1]; // May be slightly different from hit-testing drop rects used in DockNodeCalcDropRects()
14466
14467 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); }
14468};
14469
14470// Persistent Settings data, stored contiguously in SettingsNodes (sizeof() ~32 bytes)
14471struct ImGuiDockNodeSettings
14472{
14473 ImGuiID ID;
14474 ImGuiID ParentNodeId;
14475 ImGuiID ParentWindowId;
14476 ImGuiID SelectedTabId;
14477 signed char SplitAxis;
14478 char Depth;
14479 ImGuiDockNodeFlags Flags; // NB: We save individual flags one by one in ascii format (ImGuiDockNodeFlags_SavedFlagsMask_)
14480 ImVec2ih Pos;
14481 ImVec2ih Size;
14482 ImVec2ih SizeRef;
14483 ImGuiDockNodeSettings() { memset(s: this, c: 0, n: sizeof(*this)); SplitAxis = ImGuiAxis_None; }
14484};
14485
14486//-----------------------------------------------------------------------------
14487// Docking: Forward Declarations
14488//-----------------------------------------------------------------------------
14489
14490namespace ImGui
14491{
14492 // ImGuiDockContext
14493 static ImGuiDockNode* DockContextAddNode(ImGuiContext* ctx, ImGuiID id);
14494 static void DockContextRemoveNode(ImGuiContext* ctx, ImGuiDockNode* node, bool merge_sibling_into_parent_node);
14495 static void DockContextQueueNotifyRemovedNode(ImGuiContext* ctx, ImGuiDockNode* node);
14496 static void DockContextProcessDock(ImGuiContext* ctx, ImGuiDockRequest* req);
14497 static void DockContextProcessUndockWindow(ImGuiContext* ctx, ImGuiWindow* window, bool clear_persistent_docking_ref = true);
14498 static void DockContextProcessUndockNode(ImGuiContext* ctx, ImGuiDockNode* node);
14499 static void DockContextPruneUnusedSettingsNodes(ImGuiContext* ctx);
14500 static ImGuiDockNode* DockContextBindNodeToWindow(ImGuiContext* ctx, ImGuiWindow* window);
14501 static void DockContextBuildNodesFromSettings(ImGuiContext* ctx, ImGuiDockNodeSettings* node_settings_array, int node_settings_count);
14502 static void DockContextBuildAddWindowsToNodes(ImGuiContext* ctx, ImGuiID root_id); // Use root_id==0 to add all
14503
14504 // ImGuiDockNode
14505 static void DockNodeAddWindow(ImGuiDockNode* node, ImGuiWindow* window, bool add_to_tab_bar);
14506 static void DockNodeMoveWindows(ImGuiDockNode* dst_node, ImGuiDockNode* src_node);
14507 static void DockNodeMoveChildNodes(ImGuiDockNode* dst_node, ImGuiDockNode* src_node);
14508 static ImGuiWindow* DockNodeFindWindowByID(ImGuiDockNode* node, ImGuiID id);
14509 static void DockNodeApplyPosSizeToWindows(ImGuiDockNode* node);
14510 static void DockNodeRemoveWindow(ImGuiDockNode* node, ImGuiWindow* window, ImGuiID save_dock_id);
14511 static void DockNodeHideHostWindow(ImGuiDockNode* node);
14512 static void DockNodeUpdate(ImGuiDockNode* node);
14513 static void DockNodeUpdateForRootNode(ImGuiDockNode* node);
14514 static void DockNodeUpdateFlagsAndCollapse(ImGuiDockNode* node);
14515 static void DockNodeUpdateHasCentralNodeChild(ImGuiDockNode* node);
14516 static void DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_window);
14517 static void DockNodeAddTabBar(ImGuiDockNode* node);
14518 static void DockNodeRemoveTabBar(ImGuiDockNode* node);
14519 static ImGuiID DockNodeUpdateWindowMenu(ImGuiDockNode* node, ImGuiTabBar* tab_bar);
14520 static void DockNodeUpdateVisibleFlag(ImGuiDockNode* node);
14521 static void DockNodeStartMouseMovingWindow(ImGuiDockNode* node, ImGuiWindow* window);
14522 static bool DockNodeIsDropAllowed(ImGuiWindow* host_window, ImGuiWindow* payload_window);
14523 static void DockNodePreviewDockSetup(ImGuiWindow* host_window, ImGuiDockNode* host_node, ImGuiWindow* payload_window, ImGuiDockNode* payload_node, ImGuiDockPreviewData* preview_data, bool is_explicit_target, bool is_outer_docking);
14524 static void DockNodePreviewDockRender(ImGuiWindow* host_window, ImGuiDockNode* host_node, ImGuiWindow* payload_window, const ImGuiDockPreviewData* preview_data);
14525 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);
14526 static void DockNodeCalcSplitRects(ImVec2& pos_old, ImVec2& size_old, ImVec2& pos_new, ImVec2& size_new, ImGuiDir dir, ImVec2 size_new_desired);
14527 static bool DockNodeCalcDropRectsAndTestMousePos(const ImRect& parent, ImGuiDir dir, ImRect& out_draw, bool outer_docking, ImVec2* test_mouse_pos);
14528 static const char* DockNodeGetHostWindowTitle(ImGuiDockNode* node, char* buf, int buf_size) { ImFormatString(buf, buf_size, fmt: "##DockNode_%02X", node->ID); return buf; }
14529 static int DockNodeGetTabOrder(ImGuiWindow* window);
14530
14531 // ImGuiDockNode tree manipulations
14532 static void DockNodeTreeSplit(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImGuiAxis split_axis, int split_first_child, float split_ratio, ImGuiDockNode* new_node);
14533 static void DockNodeTreeMerge(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImGuiDockNode* merge_lead_child);
14534 static void DockNodeTreeUpdatePosSize(ImGuiDockNode* node, ImVec2 pos, ImVec2 size, ImGuiDockNode* only_write_to_single_node = NULL);
14535 static void DockNodeTreeUpdateSplitter(ImGuiDockNode* node);
14536 static ImGuiDockNode* DockNodeTreeFindVisibleNodeByPos(ImGuiDockNode* node, ImVec2 pos);
14537 static ImGuiDockNode* DockNodeTreeFindFallbackLeafNode(ImGuiDockNode* node);
14538
14539 // Settings
14540 static void DockSettingsRenameNodeReferences(ImGuiID old_node_id, ImGuiID new_node_id);
14541 static void DockSettingsRemoveNodeReferences(ImGuiID* node_ids, int node_ids_count);
14542 static ImGuiDockNodeSettings* DockSettingsFindNodeSettings(ImGuiContext* ctx, ImGuiID node_id);
14543 static void DockSettingsHandler_ClearAll(ImGuiContext*, ImGuiSettingsHandler*);
14544 static void DockSettingsHandler_ApplyAll(ImGuiContext*, ImGuiSettingsHandler*);
14545 static void* DockSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name);
14546 static void DockSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line);
14547 static void DockSettingsHandler_WriteAll(ImGuiContext* imgui_ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf);
14548}
14549
14550//-----------------------------------------------------------------------------
14551// Docking: ImGuiDockContext
14552//-----------------------------------------------------------------------------
14553// The lifetime model is different from the one of regular windows: we always create a ImGuiDockNode for each ImGuiDockNodeSettings,
14554// 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.
14555// At boot time only, we run a simple GC to remove nodes that have no references.
14556// Because dock node settings (which are small, contiguous structures) are always mirrored by their corresponding dock nodes (more complete structures),
14557// we can also very easily recreate the nodes from scratch given the settings data (this is what DockContextRebuild() does).
14558// This is convenient as docking reconfiguration can be implemented by mostly poking at the simpler settings data.
14559//-----------------------------------------------------------------------------
14560// - DockContextInitialize()
14561// - DockContextShutdown()
14562// - DockContextClearNodes()
14563// - DockContextRebuildNodes()
14564// - DockContextNewFrameUpdateUndocking()
14565// - DockContextNewFrameUpdateDocking()
14566// - DockContextEndFrame()
14567// - DockContextFindNodeByID()
14568// - DockContextBindNodeToWindow()
14569// - DockContextGenNodeID()
14570// - DockContextAddNode()
14571// - DockContextRemoveNode()
14572// - ImGuiDockContextPruneNodeData
14573// - DockContextPruneUnusedSettingsNodes()
14574// - DockContextBuildNodesFromSettings()
14575// - DockContextBuildAddWindowsToNodes()
14576//-----------------------------------------------------------------------------
14577
14578void ImGui::DockContextInitialize(ImGuiContext* ctx)
14579{
14580 ImGuiContext& g = *ctx;
14581
14582 // Add .ini handle for persistent docking data
14583 ImGuiSettingsHandler ini_handler;
14584 ini_handler.TypeName = "Docking";
14585 ini_handler.TypeHash = ImHashStr(data_p: "Docking");
14586 ini_handler.ClearAllFn = DockSettingsHandler_ClearAll;
14587 ini_handler.ReadInitFn = DockSettingsHandler_ClearAll; // Also clear on read
14588 ini_handler.ReadOpenFn = DockSettingsHandler_ReadOpen;
14589 ini_handler.ReadLineFn = DockSettingsHandler_ReadLine;
14590 ini_handler.ApplyAllFn = DockSettingsHandler_ApplyAll;
14591 ini_handler.WriteAllFn = DockSettingsHandler_WriteAll;
14592 g.SettingsHandlers.push_back(v: ini_handler);
14593}
14594
14595void ImGui::DockContextShutdown(ImGuiContext* ctx)
14596{
14597 ImGuiDockContext* dc = &ctx->DockContext;
14598 for (int n = 0; n < dc->Nodes.Data.Size; n++)
14599 if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p)
14600 IM_DELETE(p: node);
14601}
14602
14603void ImGui::DockContextClearNodes(ImGuiContext* ctx, ImGuiID root_id, bool clear_settings_refs)
14604{
14605 IM_UNUSED(ctx);
14606 IM_ASSERT(ctx == GImGui);
14607 DockBuilderRemoveNodeDockedWindows(node_id: root_id, clear_settings_refs);
14608 DockBuilderRemoveNodeChildNodes(node_id: root_id);
14609}
14610
14611// [DEBUG] This function also acts as a defacto test to make sure we can rebuild from scratch without a glitch
14612// (Different from DockSettingsHandler_ClearAll() + DockSettingsHandler_ApplyAll() because this reuses current settings!)
14613void ImGui::DockContextRebuildNodes(ImGuiContext* ctx)
14614{
14615 ImGuiContext& g = *ctx;
14616 ImGuiDockContext* dc = &ctx->DockContext;
14617 IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextRebuildNodes\n");
14618 SaveIniSettingsToMemory();
14619 ImGuiID root_id = 0; // Rebuild all
14620 DockContextClearNodes(ctx, root_id, clear_settings_refs: false);
14621 DockContextBuildNodesFromSettings(ctx, node_settings_array: dc->NodesSettings.Data, node_settings_count: dc->NodesSettings.Size);
14622 DockContextBuildAddWindowsToNodes(ctx, root_id);
14623}
14624
14625// Docking context update function, called by NewFrame()
14626void ImGui::DockContextNewFrameUpdateUndocking(ImGuiContext* ctx)
14627{
14628 ImGuiContext& g = *ctx;
14629 ImGuiDockContext* dc = &ctx->DockContext;
14630 if (!(g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable))
14631 {
14632 if (dc->Nodes.Data.Size > 0 || dc->Requests.Size > 0)
14633 DockContextClearNodes(ctx, root_id: 0, clear_settings_refs: true);
14634 return;
14635 }
14636
14637 // Setting NoSplit at runtime merges all nodes
14638 if (g.IO.ConfigDockingNoSplit)
14639 for (int n = 0; n < dc->Nodes.Data.Size; n++)
14640 if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p)
14641 if (node->IsRootNode() && node->IsSplitNode())
14642 {
14643 DockBuilderRemoveNodeChildNodes(node_id: node->ID);
14644 //dc->WantFullRebuild = true;
14645 }
14646
14647 // Process full rebuild
14648#if 0
14649 if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_C)))
14650 dc->WantFullRebuild = true;
14651#endif
14652 if (dc->WantFullRebuild)
14653 {
14654 DockContextRebuildNodes(ctx);
14655 dc->WantFullRebuild = false;
14656 }
14657
14658 // Process Undocking requests (we need to process them _before_ the UpdateMouseMovingWindowNewFrame call in NewFrame)
14659 for (int n = 0; n < dc->Requests.Size; n++)
14660 {
14661 ImGuiDockRequest* req = &dc->Requests[n];
14662 if (req->Type == ImGuiDockRequestType_Undock && req->UndockTargetWindow)
14663 DockContextProcessUndockWindow(ctx, window: req->UndockTargetWindow);
14664 else if (req->Type == ImGuiDockRequestType_Undock && req->UndockTargetNode)
14665 DockContextProcessUndockNode(ctx, node: req->UndockTargetNode);
14666 }
14667}
14668
14669// Docking context update function, called by NewFrame()
14670void ImGui::DockContextNewFrameUpdateDocking(ImGuiContext* ctx)
14671{
14672 ImGuiContext& g = *ctx;
14673 ImGuiDockContext* dc = &ctx->DockContext;
14674 if (!(g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable))
14675 return;
14676
14677 // [DEBUG] Store hovered dock node.
14678 // We could in theory use DockNodeTreeFindVisibleNodeByPos() on the root host dock node, but using ->DockNode is a good shortcut.
14679 // Note this is mostly a debug thing and isn't actually used for docking target, because docking involve more detailed filtering.
14680 g.DebugHoveredDockNode = NULL;
14681 if (ImGuiWindow* hovered_window = g.HoveredWindowUnderMovingWindow)
14682 {
14683 if (hovered_window->DockNodeAsHost)
14684 g.DebugHoveredDockNode = DockNodeTreeFindVisibleNodeByPos(node: hovered_window->DockNodeAsHost, pos: g.IO.MousePos);
14685 else if (hovered_window->RootWindow->DockNode)
14686 g.DebugHoveredDockNode = hovered_window->RootWindow->DockNode;
14687 }
14688
14689 // Process Docking requests
14690 for (int n = 0; n < dc->Requests.Size; n++)
14691 if (dc->Requests[n].Type == ImGuiDockRequestType_Dock)
14692 DockContextProcessDock(ctx, req: &dc->Requests[n]);
14693 dc->Requests.resize(new_size: 0);
14694
14695 // Create windows for each automatic docking nodes
14696 // 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)
14697 for (int n = 0; n < dc->Nodes.Data.Size; n++)
14698 if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p)
14699 if (node->IsFloatingNode())
14700 DockNodeUpdate(node);
14701}
14702
14703void ImGui::DockContextEndFrame(ImGuiContext* ctx)
14704{
14705 // Draw backgrounds of node missing their window
14706 ImGuiContext& g = *ctx;
14707 ImGuiDockContext* dc = &g.DockContext;
14708 for (int n = 0; n < dc->Nodes.Data.Size; n++)
14709 if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p)
14710 if (node->LastFrameActive == g.FrameCount && node->IsVisible && node->HostWindow && node->IsLeafNode() && !node->IsBgDrawnThisFrame)
14711 {
14712 ImRect bg_rect(node->Pos + ImVec2(0.0f, GetFrameHeight()), node->Pos + node->Size);
14713 ImDrawFlags bg_rounding_flags = CalcRoundingFlagsForRectInRect(r_in: bg_rect, r_outer: node->HostWindow->Rect(), threshold: DOCKING_SPLITTER_SIZE);
14714 node->HostWindow->DrawList->ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_BG);
14715 node->HostWindow->DrawList->AddRectFilled(p_min: bg_rect.Min, p_max: bg_rect.Max, col: node->LastBgColor, rounding: node->HostWindow->WindowRounding, flags: bg_rounding_flags);
14716 }
14717}
14718
14719ImGuiDockNode* ImGui::DockContextFindNodeByID(ImGuiContext* ctx, ImGuiID id)
14720{
14721 return (ImGuiDockNode*)ctx->DockContext.Nodes.GetVoidPtr(key: id);
14722}
14723
14724ImGuiID ImGui::DockContextGenNodeID(ImGuiContext* ctx)
14725{
14726 // Generate an ID for new node (the exact ID value doesn't matter as long as it is not already used)
14727 // FIXME-OPT FIXME-DOCK: This is suboptimal, even if the node count is small enough not to be a worry.0
14728 // We should poke in ctx->Nodes to find a suitable ID faster. Even more so trivial that ctx->Nodes lookup is already sorted.
14729 ImGuiID id = 0x0001;
14730 while (DockContextFindNodeByID(ctx, id) != NULL)
14731 id++;
14732 return id;
14733}
14734
14735static ImGuiDockNode* ImGui::DockContextAddNode(ImGuiContext* ctx, ImGuiID id)
14736{
14737 // 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.
14738 ImGuiContext& g = *ctx;
14739 if (id == 0)
14740 id = DockContextGenNodeID(ctx);
14741 else
14742 IM_ASSERT(DockContextFindNodeByID(ctx, id) == NULL);
14743
14744 // We don't set node->LastFrameAlive on construction. Nodes are always created at all time to reflect .ini settings!
14745 IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextAddNode 0x%08X\n", id);
14746 ImGuiDockNode* node = IM_NEW(ImGuiDockNode)(id);
14747 ctx->DockContext.Nodes.SetVoidPtr(key: node->ID, val: node);
14748 return node;
14749}
14750
14751static void ImGui::DockContextRemoveNode(ImGuiContext* ctx, ImGuiDockNode* node, bool merge_sibling_into_parent_node)
14752{
14753 ImGuiContext& g = *ctx;
14754 ImGuiDockContext* dc = &ctx->DockContext;
14755
14756 IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextRemoveNode 0x%08X\n", node->ID);
14757 IM_ASSERT(DockContextFindNodeByID(ctx, node->ID) == node);
14758 IM_ASSERT(node->ChildNodes[0] == NULL && node->ChildNodes[1] == NULL);
14759 IM_ASSERT(node->Windows.Size == 0);
14760
14761 if (node->HostWindow)
14762 node->HostWindow->DockNodeAsHost = NULL;
14763
14764 ImGuiDockNode* parent_node = node->ParentNode;
14765 const bool merge = (merge_sibling_into_parent_node && parent_node != NULL);
14766 if (merge)
14767 {
14768 IM_ASSERT(parent_node->ChildNodes[0] == node || parent_node->ChildNodes[1] == node);
14769 ImGuiDockNode* sibling_node = (parent_node->ChildNodes[0] == node ? parent_node->ChildNodes[1] : parent_node->ChildNodes[0]);
14770 DockNodeTreeMerge(ctx: &g, parent_node, merge_lead_child: sibling_node);
14771 }
14772 else
14773 {
14774 for (int n = 0; parent_node && n < IM_ARRAYSIZE(parent_node->ChildNodes); n++)
14775 if (parent_node->ChildNodes[n] == node)
14776 node->ParentNode->ChildNodes[n] = NULL;
14777 dc->Nodes.SetVoidPtr(key: node->ID, NULL);
14778 IM_DELETE(p: node);
14779 }
14780}
14781
14782static int IMGUI_CDECL DockNodeComparerDepthMostFirst(const void* lhs, const void* rhs)
14783{
14784 const ImGuiDockNode* a = *(const ImGuiDockNode* const*)lhs;
14785 const ImGuiDockNode* b = *(const ImGuiDockNode* const*)rhs;
14786 return ImGui::DockNodeGetDepth(node: b) - ImGui::DockNodeGetDepth(node: a);
14787}
14788
14789// Pre C++0x doesn't allow us to use a function-local type (without linkage) as template parameter, so we moved this here.
14790struct ImGuiDockContextPruneNodeData
14791{
14792 int CountWindows, CountChildWindows, CountChildNodes;
14793 ImGuiID RootId;
14794 ImGuiDockContextPruneNodeData() { CountWindows = CountChildWindows = CountChildNodes = 0; RootId = 0; }
14795};
14796
14797// Garbage collect unused nodes (run once at init time)
14798static void ImGui::DockContextPruneUnusedSettingsNodes(ImGuiContext* ctx)
14799{
14800 ImGuiContext& g = *ctx;
14801 ImGuiDockContext* dc = &ctx->DockContext;
14802 IM_ASSERT(g.Windows.Size == 0);
14803
14804 ImPool<ImGuiDockContextPruneNodeData> pool;
14805 pool.Reserve(capacity: dc->NodesSettings.Size);
14806
14807 // Count child nodes and compute RootID
14808 for (int settings_n = 0; settings_n < dc->NodesSettings.Size; settings_n++)
14809 {
14810 ImGuiDockNodeSettings* settings = &dc->NodesSettings[settings_n];
14811 ImGuiDockContextPruneNodeData* parent_data = settings->ParentNodeId ? pool.GetByKey(key: settings->ParentNodeId) : 0;
14812 pool.GetOrAddByKey(key: settings->ID)->RootId = parent_data ? parent_data->RootId : settings->ID;
14813 if (settings->ParentNodeId)
14814 pool.GetOrAddByKey(key: settings->ParentNodeId)->CountChildNodes++;
14815 }
14816
14817 // Count reference to dock ids from dockspaces
14818 // We track the 'auto-DockNode <- manual-Window <- manual-DockSpace' in order to avoid 'auto-DockNode' being ditched by DockContextPruneUnusedSettingsNodes()
14819 for (int settings_n = 0; settings_n < dc->NodesSettings.Size; settings_n++)
14820 {
14821 ImGuiDockNodeSettings* settings = &dc->NodesSettings[settings_n];
14822 if (settings->ParentWindowId != 0)
14823 if (ImGuiWindowSettings* window_settings = FindWindowSettings(id: settings->ParentWindowId))
14824 if (window_settings->DockId)
14825 if (ImGuiDockContextPruneNodeData* data = pool.GetByKey(key: window_settings->DockId))
14826 data->CountChildNodes++;
14827 }
14828
14829 // Count reference to dock ids from window settings
14830 // We guard against the possibility of an invalid .ini file (RootID may point to a missing node)
14831 for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(p: settings))
14832 if (ImGuiID dock_id = settings->DockId)
14833 if (ImGuiDockContextPruneNodeData* data = pool.GetByKey(key: dock_id))
14834 {
14835 data->CountWindows++;
14836 if (ImGuiDockContextPruneNodeData* data_root = (data->RootId == dock_id) ? data : pool.GetByKey(key: data->RootId))
14837 data_root->CountChildWindows++;
14838 }
14839
14840 // Prune
14841 for (int settings_n = 0; settings_n < dc->NodesSettings.Size; settings_n++)
14842 {
14843 ImGuiDockNodeSettings* settings = &dc->NodesSettings[settings_n];
14844 ImGuiDockContextPruneNodeData* data = pool.GetByKey(key: settings->ID);
14845 if (data->CountWindows > 1)
14846 continue;
14847 ImGuiDockContextPruneNodeData* data_root = (data->RootId == settings->ID) ? data : pool.GetByKey(key: data->RootId);
14848
14849 bool remove = false;
14850 remove |= (data->CountWindows == 1 && settings->ParentNodeId == 0 && data->CountChildNodes == 0 && !(settings->Flags & ImGuiDockNodeFlags_CentralNode)); // Floating root node with only 1 window
14851 remove |= (data->CountWindows == 0 && settings->ParentNodeId == 0 && data->CountChildNodes == 0); // Leaf nodes with 0 window
14852 remove |= (data_root->CountChildWindows == 0);
14853 if (remove)
14854 {
14855 IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextPruneUnusedSettingsNodes: Prune 0x%08X\n", settings->ID);
14856 DockSettingsRemoveNodeReferences(node_ids: &settings->ID, node_ids_count: 1);
14857 settings->ID = 0;
14858 }
14859 }
14860}
14861
14862static void ImGui::DockContextBuildNodesFromSettings(ImGuiContext* ctx, ImGuiDockNodeSettings* node_settings_array, int node_settings_count)
14863{
14864 // Build nodes
14865 for (int node_n = 0; node_n < node_settings_count; node_n++)
14866 {
14867 ImGuiDockNodeSettings* settings = &node_settings_array[node_n];
14868 if (settings->ID == 0)
14869 continue;
14870 ImGuiDockNode* node = DockContextAddNode(ctx, id: settings->ID);
14871 node->ParentNode = settings->ParentNodeId ? DockContextFindNodeByID(ctx, id: settings->ParentNodeId) : NULL;
14872 node->Pos = ImVec2(settings->Pos.x, settings->Pos.y);
14873 node->Size = ImVec2(settings->Size.x, settings->Size.y);
14874 node->SizeRef = ImVec2(settings->SizeRef.x, settings->SizeRef.y);
14875 node->AuthorityForPos = node->AuthorityForSize = node->AuthorityForViewport = ImGuiDataAuthority_DockNode;
14876 if (node->ParentNode && node->ParentNode->ChildNodes[0] == NULL)
14877 node->ParentNode->ChildNodes[0] = node;
14878 else if (node->ParentNode && node->ParentNode->ChildNodes[1] == NULL)
14879 node->ParentNode->ChildNodes[1] = node;
14880 node->SelectedTabId = settings->SelectedTabId;
14881 node->SplitAxis = (ImGuiAxis)settings->SplitAxis;
14882 node->SetLocalFlags(settings->Flags & ImGuiDockNodeFlags_SavedFlagsMask_);
14883
14884 // Bind host window immediately if it already exist (in case of a rebuild)
14885 // This is useful as the RootWindowForTitleBarHighlight links necessary to highlight the currently focused node requires node->HostWindow to be set.
14886 char host_window_title[20];
14887 ImGuiDockNode* root_node = DockNodeGetRootNode(node);
14888 node->HostWindow = FindWindowByName(name: DockNodeGetHostWindowTitle(node: root_node, buf: host_window_title, IM_ARRAYSIZE(host_window_title)));
14889 }
14890}
14891
14892void ImGui::DockContextBuildAddWindowsToNodes(ImGuiContext* ctx, ImGuiID root_id)
14893{
14894 // Rebind all windows to nodes (they can also lazily rebind but we'll have a visible glitch during the first frame)
14895 ImGuiContext& g = *ctx;
14896 for (int n = 0; n < g.Windows.Size; n++)
14897 {
14898 ImGuiWindow* window = g.Windows[n];
14899 if (window->DockId == 0 || window->LastFrameActive < g.FrameCount - 1)
14900 continue;
14901 if (window->DockNode != NULL)
14902 continue;
14903
14904 ImGuiDockNode* node = DockContextFindNodeByID(ctx, id: window->DockId);
14905 IM_ASSERT(node != NULL); // This should have been called after DockContextBuildNodesFromSettings()
14906 if (root_id == 0 || DockNodeGetRootNode(node)->ID == root_id)
14907 DockNodeAddWindow(node, window, add_to_tab_bar: true);
14908 }
14909}
14910
14911//-----------------------------------------------------------------------------
14912// Docking: ImGuiDockContext Docking/Undocking functions
14913//-----------------------------------------------------------------------------
14914// - DockContextQueueDock()
14915// - DockContextQueueUndockWindow()
14916// - DockContextQueueUndockNode()
14917// - DockContextQueueNotifyRemovedNode()
14918// - DockContextProcessDock()
14919// - DockContextProcessUndockWindow()
14920// - DockContextProcessUndockNode()
14921// - DockContextCalcDropPosForDocking()
14922//-----------------------------------------------------------------------------
14923
14924void ImGui::DockContextQueueDock(ImGuiContext* ctx, ImGuiWindow* target, ImGuiDockNode* target_node, ImGuiWindow* payload, ImGuiDir split_dir, float split_ratio, bool split_outer)
14925{
14926 IM_ASSERT(target != payload);
14927 ImGuiDockRequest req;
14928 req.Type = ImGuiDockRequestType_Dock;
14929 req.DockTargetWindow = target;
14930 req.DockTargetNode = target_node;
14931 req.DockPayload = payload;
14932 req.DockSplitDir = split_dir;
14933 req.DockSplitRatio = split_ratio;
14934 req.DockSplitOuter = split_outer;
14935 ctx->DockContext.Requests.push_back(v: req);
14936}
14937
14938void ImGui::DockContextQueueUndockWindow(ImGuiContext* ctx, ImGuiWindow* window)
14939{
14940 ImGuiDockRequest req;
14941 req.Type = ImGuiDockRequestType_Undock;
14942 req.UndockTargetWindow = window;
14943 ctx->DockContext.Requests.push_back(v: req);
14944}
14945
14946void ImGui::DockContextQueueUndockNode(ImGuiContext* ctx, ImGuiDockNode* node)
14947{
14948 ImGuiDockRequest req;
14949 req.Type = ImGuiDockRequestType_Undock;
14950 req.UndockTargetNode = node;
14951 ctx->DockContext.Requests.push_back(v: req);
14952}
14953
14954void ImGui::DockContextQueueNotifyRemovedNode(ImGuiContext* ctx, ImGuiDockNode* node)
14955{
14956 ImGuiDockContext* dc = &ctx->DockContext;
14957 for (int n = 0; n < dc->Requests.Size; n++)
14958 if (dc->Requests[n].DockTargetNode == node)
14959 dc->Requests[n].Type = ImGuiDockRequestType_None;
14960}
14961
14962void ImGui::DockContextProcessDock(ImGuiContext* ctx, ImGuiDockRequest* req)
14963{
14964 IM_ASSERT((req->Type == ImGuiDockRequestType_Dock && req->DockPayload != NULL) || (req->Type == ImGuiDockRequestType_Split && req->DockPayload == NULL));
14965 IM_ASSERT(req->DockTargetWindow != NULL || req->DockTargetNode != NULL);
14966
14967 ImGuiContext& g = *ctx;
14968 IM_UNUSED(g);
14969
14970 ImGuiWindow* payload_window = req->DockPayload; // Optional
14971 ImGuiWindow* target_window = req->DockTargetWindow;
14972 ImGuiDockNode* node = req->DockTargetNode;
14973 if (payload_window)
14974 IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextProcessDock node 0x%08X target '%s' dock window '%s', split_dir %d\n", node ? node->ID : 0, target_window ? target_window->Name : "NULL", payload_window->Name, req->DockSplitDir);
14975 else
14976 IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextProcessDock node 0x%08X, split_dir %d\n", node ? node->ID : 0, req->DockSplitDir);
14977
14978 // Decide which Tab will be selected at the end of the operation
14979 ImGuiID next_selected_id = 0;
14980 ImGuiDockNode* payload_node = NULL;
14981 if (payload_window)
14982 {
14983 payload_node = payload_window->DockNodeAsHost;
14984 payload_window->DockNodeAsHost = NULL; // Important to clear this as the node will have its life as a child which might be merged/deleted later.
14985 if (payload_node && payload_node->IsLeafNode())
14986 next_selected_id = payload_node->TabBar->NextSelectedTabId ? payload_node->TabBar->NextSelectedTabId : payload_node->TabBar->SelectedTabId;
14987 if (payload_node == NULL)
14988 next_selected_id = payload_window->TabId;
14989 }
14990
14991 // FIXME-DOCK: When we are trying to dock an existing single-window node into a loose window, transfer Node ID as well
14992 // When processing an interactive split, usually LastFrameAlive will be < g.FrameCount. But DockBuilder operations can make it ==.
14993 if (node)
14994 IM_ASSERT(node->LastFrameAlive <= g.FrameCount);
14995 if (node && target_window && node == target_window->DockNodeAsHost)
14996 IM_ASSERT(node->Windows.Size > 0 || node->IsSplitNode() || node->IsCentralNode());
14997
14998 // Create new node and add existing window to it
14999 if (node == NULL)
15000 {
15001 node = DockContextAddNode(ctx, id: 0);
15002 node->Pos = target_window->Pos;
15003 node->Size = target_window->Size;
15004 if (target_window->DockNodeAsHost == NULL)
15005 {
15006 DockNodeAddWindow(node, window: target_window, add_to_tab_bar: true);
15007 node->TabBar->Tabs[0].Flags &= ~ImGuiTabItemFlags_Unsorted;
15008 target_window->DockIsActive = true;
15009 }
15010 }
15011
15012 ImGuiDir split_dir = req->DockSplitDir;
15013 if (split_dir != ImGuiDir_None)
15014 {
15015 // Split into two, one side will be our payload node unless we are dropping a loose window
15016 const ImGuiAxis split_axis = (split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Right) ? ImGuiAxis_X : ImGuiAxis_Y;
15017 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
15018 const float split_ratio = req->DockSplitRatio;
15019 DockNodeTreeSplit(ctx, parent_node: node, split_axis, split_first_child: split_inheritor_child_idx, split_ratio, new_node: payload_node); // payload_node may be NULL here!
15020 ImGuiDockNode* new_node = node->ChildNodes[split_inheritor_child_idx ^ 1];
15021 new_node->HostWindow = node->HostWindow;
15022 node = new_node;
15023 }
15024 node->SetLocalFlags(node->LocalFlags & ~ImGuiDockNodeFlags_HiddenTabBar);
15025
15026 if (node != payload_node)
15027 {
15028 // 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!)
15029 if (node->Windows.Size > 0 && node->TabBar == NULL)
15030 {
15031 DockNodeAddTabBar(node);
15032 for (int n = 0; n < node->Windows.Size; n++)
15033 TabBarAddTab(tab_bar: node->TabBar, tab_flags: ImGuiTabItemFlags_None, window: node->Windows[n]);
15034 }
15035
15036 if (payload_node != NULL)
15037 {
15038 // Transfer full payload node (with 1+ child windows or child nodes)
15039 if (payload_node->IsSplitNode())
15040 {
15041 if (node->Windows.Size > 0)
15042 {
15043 // 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.
15044 // In this situation, we move the windows of the target node into the currently visible node of the payload.
15045 // This allows us to preserve some of the underlying dock tree settings nicely.
15046 IM_ASSERT(payload_node->OnlyNodeWithWindows != NULL); // The docking should have been blocked by DockNodePreviewDockSetup() early on and never submitted.
15047 ImGuiDockNode* visible_node = payload_node->OnlyNodeWithWindows;
15048 if (visible_node->TabBar)
15049 IM_ASSERT(visible_node->TabBar->Tabs.Size > 0);
15050 DockNodeMoveWindows(dst_node: node, src_node: visible_node);
15051 DockNodeMoveWindows(dst_node: visible_node, src_node: node);
15052 DockSettingsRenameNodeReferences(old_node_id: node->ID, new_node_id: visible_node->ID);
15053 }
15054 if (node->IsCentralNode())
15055 {
15056 // Central node property needs to be moved to a leaf node, pick the last focused one.
15057 // FIXME-DOCK: If we had to transfer other flags here, what would the policy be?
15058 ImGuiDockNode* last_focused_node = DockContextFindNodeByID(ctx, id: payload_node->LastFocusedNodeId);
15059 IM_ASSERT(last_focused_node != NULL);
15060 ImGuiDockNode* last_focused_root_node = DockNodeGetRootNode(node: last_focused_node);
15061 IM_ASSERT(last_focused_root_node == DockNodeGetRootNode(payload_node));
15062 last_focused_node->SetLocalFlags(last_focused_node->LocalFlags | ImGuiDockNodeFlags_CentralNode);
15063 node->SetLocalFlags(node->LocalFlags & ~ImGuiDockNodeFlags_CentralNode);
15064 last_focused_root_node->CentralNode = last_focused_node;
15065 }
15066
15067 IM_ASSERT(node->Windows.Size == 0);
15068 DockNodeMoveChildNodes(dst_node: node, src_node: payload_node);
15069 }
15070 else
15071 {
15072 const ImGuiID payload_dock_id = payload_node->ID;
15073 DockNodeMoveWindows(dst_node: node, src_node: payload_node);
15074 DockSettingsRenameNodeReferences(old_node_id: payload_dock_id, new_node_id: node->ID);
15075 }
15076 DockContextRemoveNode(ctx, node: payload_node, merge_sibling_into_parent_node: true);
15077 }
15078 else if (payload_window)
15079 {
15080 // Transfer single window
15081 const ImGuiID payload_dock_id = payload_window->DockId;
15082 node->VisibleWindow = payload_window;
15083 DockNodeAddWindow(node, window: payload_window, add_to_tab_bar: true);
15084 if (payload_dock_id != 0)
15085 DockSettingsRenameNodeReferences(old_node_id: payload_dock_id, new_node_id: node->ID);
15086 }
15087 }
15088 else
15089 {
15090 // When docking a floating single window node we want to reevaluate auto-hiding of the tab bar
15091 node->WantHiddenTabBarUpdate = true;
15092 }
15093
15094 // Update selection immediately
15095 if (ImGuiTabBar* tab_bar = node->TabBar)
15096 tab_bar->NextSelectedTabId = next_selected_id;
15097 MarkIniSettingsDirty();
15098}
15099
15100// Problem:
15101// Undocking a large (~full screen) window would leave it so large that the bottom right sizing corner would more
15102// than likely be off the screen and the window would be hard to resize to fit on screen. This can be particularly problematic
15103// with 'ConfigWindowsMoveFromTitleBarOnly=true' and/or with 'ConfigWindowsResizeFromEdges=false' as well (the later can be
15104// due to missing ImGuiBackendFlags_HasMouseCursors backend flag).
15105// Solution:
15106// When undocking a window we currently force its maximum size to 90% of the host viewport or monitor.
15107// Reevaluate this when we implement preserving docked/undocked size ("docking_wip/undocked_size" branch).
15108static ImVec2 FixLargeWindowsWhenUndocking(const ImVec2& size, ImGuiViewport* ref_viewport)
15109{
15110 if (ref_viewport == NULL)
15111 return size;
15112
15113 ImGuiContext& g = *GImGui;
15114 ImVec2 max_size = ImFloor(v: ref_viewport->WorkSize * 0.90f);
15115 if (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable)
15116 {
15117 const ImGuiPlatformMonitor* monitor = ImGui::GetViewportPlatformMonitor(viewport_p: ref_viewport);
15118 max_size = ImFloor(v: monitor->WorkSize * 0.90f);
15119 }
15120 return ImMin(lhs: size, rhs: max_size);
15121}
15122
15123void ImGui::DockContextProcessUndockWindow(ImGuiContext* ctx, ImGuiWindow* window, bool clear_persistent_docking_ref)
15124{
15125 ImGuiContext& g = *ctx;
15126 IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextProcessUndockWindow window '%s', clear_persistent_docking_ref = %d\n", window->Name, clear_persistent_docking_ref);
15127 if (window->DockNode)
15128 DockNodeRemoveWindow(node: window->DockNode, window, save_dock_id: clear_persistent_docking_ref ? 0 : window->DockId);
15129 else
15130 window->DockId = 0;
15131 window->Collapsed = false;
15132 window->DockIsActive = false;
15133 window->DockNodeIsVisible = window->DockTabIsVisible = false;
15134 window->Size = window->SizeFull = FixLargeWindowsWhenUndocking(size: window->SizeFull, ref_viewport: window->Viewport);
15135
15136 MarkIniSettingsDirty();
15137}
15138
15139void ImGui::DockContextProcessUndockNode(ImGuiContext* ctx, ImGuiDockNode* node)
15140{
15141 ImGuiContext& g = *ctx;
15142 IMGUI_DEBUG_LOG_DOCKING("[docking] DockContextProcessUndockNode node %08X\n", node->ID);
15143 IM_ASSERT(node->IsLeafNode());
15144 IM_ASSERT(node->Windows.Size >= 1);
15145
15146 if (node->IsRootNode() || node->IsCentralNode())
15147 {
15148 // 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.
15149 ImGuiDockNode* new_node = DockContextAddNode(ctx, id: 0);
15150 new_node->Pos = node->Pos;
15151 new_node->Size = node->Size;
15152 new_node->SizeRef = node->SizeRef;
15153 DockNodeMoveWindows(dst_node: new_node, src_node: node);
15154 DockSettingsRenameNodeReferences(old_node_id: node->ID, new_node_id: new_node->ID);
15155 node = new_node;
15156 }
15157 else
15158 {
15159 // Otherwise extract our node and merge our sibling back into the parent node.
15160 IM_ASSERT(node->ParentNode->ChildNodes[0] == node || node->ParentNode->ChildNodes[1] == node);
15161 int index_in_parent = (node->ParentNode->ChildNodes[0] == node) ? 0 : 1;
15162 node->ParentNode->ChildNodes[index_in_parent] = NULL;
15163 DockNodeTreeMerge(ctx, parent_node: node->ParentNode, merge_lead_child: node->ParentNode->ChildNodes[index_in_parent ^ 1]);
15164 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
15165 node->ParentNode = NULL;
15166 }
15167 for (int n = 0; n < node->Windows.Size; n++)
15168 {
15169 ImGuiWindow* window = node->Windows[n];
15170 window->Flags &= ~ImGuiWindowFlags_ChildWindow;
15171 if (window->ParentWindow)
15172 window->ParentWindow->DC.ChildWindows.find_erase(v: window);
15173 UpdateWindowParentAndRootLinks(window, flags: window->Flags, NULL);
15174 }
15175 node->AuthorityForPos = node->AuthorityForSize = ImGuiDataAuthority_DockNode;
15176 node->Size = FixLargeWindowsWhenUndocking(size: node->Size, ref_viewport: node->Windows[0]->Viewport);
15177 node->WantMouseMove = true;
15178 MarkIniSettingsDirty();
15179}
15180
15181// This is mostly used for automation.
15182bool ImGui::DockContextCalcDropPosForDocking(ImGuiWindow* target, ImGuiDockNode* target_node, ImGuiWindow* payload_window, ImGuiDockNode* payload_node, ImGuiDir split_dir, bool split_outer, ImVec2* out_pos)
15183{
15184 // In DockNodePreviewDockSetup() for a root central node instead of showing both "inner" and "outer" drop rects
15185 // (which would be functionally identical) we only show the outer one. Reflect this here.
15186 if (target_node && target_node->ParentNode == NULL && target_node->IsCentralNode() && split_dir != ImGuiDir_None)
15187 split_outer = true;
15188 ImGuiDockPreviewData split_data;
15189 DockNodePreviewDockSetup(host_window: target, host_node: target_node, payload_window, payload_node, preview_data: &split_data, is_explicit_target: false, is_outer_docking: split_outer);
15190 if (split_data.DropRectsDraw[split_dir+1].IsInverted())
15191 return false;
15192 *out_pos = split_data.DropRectsDraw[split_dir+1].GetCenter();
15193 return true;
15194}
15195
15196//-----------------------------------------------------------------------------
15197// Docking: ImGuiDockNode
15198//-----------------------------------------------------------------------------
15199// - DockNodeGetTabOrder()
15200// - DockNodeAddWindow()
15201// - DockNodeRemoveWindow()
15202// - DockNodeMoveChildNodes()
15203// - DockNodeMoveWindows()
15204// - DockNodeApplyPosSizeToWindows()
15205// - DockNodeHideHostWindow()
15206// - ImGuiDockNodeFindInfoResults
15207// - DockNodeFindInfo()
15208// - DockNodeFindWindowByID()
15209// - DockNodeUpdateFlagsAndCollapse()
15210// - DockNodeUpdateHasCentralNodeFlag()
15211// - DockNodeUpdateVisibleFlag()
15212// - DockNodeStartMouseMovingWindow()
15213// - DockNodeUpdate()
15214// - DockNodeUpdateWindowMenu()
15215// - DockNodeBeginAmendTabBar()
15216// - DockNodeEndAmendTabBar()
15217// - DockNodeUpdateTabBar()
15218// - DockNodeAddTabBar()
15219// - DockNodeRemoveTabBar()
15220// - DockNodeIsDropAllowedOne()
15221// - DockNodeIsDropAllowed()
15222// - DockNodeCalcTabBarLayout()
15223// - DockNodeCalcSplitRects()
15224// - DockNodeCalcDropRectsAndTestMousePos()
15225// - DockNodePreviewDockSetup()
15226// - DockNodePreviewDockRender()
15227//-----------------------------------------------------------------------------
15228
15229ImGuiDockNode::ImGuiDockNode(ImGuiID id)
15230{
15231 ID = id;
15232 SharedFlags = LocalFlags = LocalFlagsInWindows = MergedFlags = ImGuiDockNodeFlags_None;
15233 ParentNode = ChildNodes[0] = ChildNodes[1] = NULL;
15234 TabBar = NULL;
15235 SplitAxis = ImGuiAxis_None;
15236
15237 State = ImGuiDockNodeState_Unknown;
15238 LastBgColor = IM_COL32_WHITE;
15239 HostWindow = VisibleWindow = NULL;
15240 CentralNode = OnlyNodeWithWindows = NULL;
15241 CountNodeWithWindows = 0;
15242 LastFrameAlive = LastFrameActive = LastFrameFocused = -1;
15243 LastFocusedNodeId = 0;
15244 SelectedTabId = 0;
15245 WantCloseTabId = 0;
15246 AuthorityForPos = AuthorityForSize = ImGuiDataAuthority_DockNode;
15247 AuthorityForViewport = ImGuiDataAuthority_Auto;
15248 IsVisible = true;
15249 IsFocused = HasCloseButton = HasWindowMenuButton = HasCentralNodeChild = false;
15250 IsBgDrawnThisFrame = false;
15251 WantCloseAll = WantLockSizeOnce = WantMouseMove = WantHiddenTabBarUpdate = WantHiddenTabBarToggle = false;
15252}
15253
15254ImGuiDockNode::~ImGuiDockNode()
15255{
15256 IM_DELETE(p: TabBar);
15257 TabBar = NULL;
15258 ChildNodes[0] = ChildNodes[1] = NULL;
15259}
15260
15261int ImGui::DockNodeGetTabOrder(ImGuiWindow* window)
15262{
15263 ImGuiTabBar* tab_bar = window->DockNode->TabBar;
15264 if (tab_bar == NULL)
15265 return -1;
15266 ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id: window->TabId);
15267 return tab ? tab_bar->GetTabOrder(tab) : -1;
15268}
15269
15270static void DockNodeHideWindowDuringHostWindowCreation(ImGuiWindow* window)
15271{
15272 window->Hidden = true;
15273 window->HiddenFramesCanSkipItems = window->Active ? 1 : 2;
15274}
15275
15276static void ImGui::DockNodeAddWindow(ImGuiDockNode* node, ImGuiWindow* window, bool add_to_tab_bar)
15277{
15278 ImGuiContext& g = *GImGui; (void)g;
15279 if (window->DockNode)
15280 {
15281 // Can overwrite an existing window->DockNode (e.g. pointing to a disabled DockSpace node)
15282 IM_ASSERT(window->DockNode->ID != node->ID);
15283 DockNodeRemoveWindow(node: window->DockNode, window, save_dock_id: 0);
15284 }
15285 IM_ASSERT(window->DockNode == NULL || window->DockNodeAsHost == NULL);
15286 IMGUI_DEBUG_LOG_DOCKING("[docking] DockNodeAddWindow node 0x%08X window '%s'\n", node->ID, window->Name);
15287
15288 // If more than 2 windows appeared on the same frame leading to the creation of a new hosting window,
15289 // 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).
15290 // We will call DockNodeHideWindowDuringHostWindowCreation() on ourselves in Begin()
15291 if (node->HostWindow == NULL && node->Windows.Size == 1 && node->Windows[0]->WasActive == false)
15292 DockNodeHideWindowDuringHostWindowCreation(window: node->Windows[0]);
15293
15294 node->Windows.push_back(v: window);
15295 node->WantHiddenTabBarUpdate = true;
15296 window->DockNode = node;
15297 window->DockId = node->ID;
15298 window->DockIsActive = (node->Windows.Size > 1);
15299 window->DockTabWantClose = false;
15300
15301 // When reactivating a node with one or two loose window, the window pos/size/viewport are authoritative over the node storage.
15302 // In particular it is important we init the viewport from the first window so we don't create two viewports and drop one.
15303 if (node->HostWindow == NULL && node->IsFloatingNode())
15304 {
15305 if (node->AuthorityForPos == ImGuiDataAuthority_Auto)
15306 node->AuthorityForPos = ImGuiDataAuthority_Window;
15307 if (node->AuthorityForSize == ImGuiDataAuthority_Auto)
15308 node->AuthorityForSize = ImGuiDataAuthority_Window;
15309 if (node->AuthorityForViewport == ImGuiDataAuthority_Auto)
15310 node->AuthorityForViewport = ImGuiDataAuthority_Window;
15311 }
15312
15313 // Add to tab bar if requested
15314 if (add_to_tab_bar)
15315 {
15316 if (node->TabBar == NULL)
15317 {
15318 DockNodeAddTabBar(node);
15319 node->TabBar->SelectedTabId = node->TabBar->NextSelectedTabId = node->SelectedTabId;
15320
15321 // Add existing windows
15322 for (int n = 0; n < node->Windows.Size - 1; n++)
15323 TabBarAddTab(tab_bar: node->TabBar, tab_flags: ImGuiTabItemFlags_None, window: node->Windows[n]);
15324 }
15325 TabBarAddTab(tab_bar: node->TabBar, tab_flags: ImGuiTabItemFlags_Unsorted, window);
15326 }
15327
15328 DockNodeUpdateVisibleFlag(node);
15329
15330 // 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.
15331 if (node->HostWindow)
15332 UpdateWindowParentAndRootLinks(window, flags: window->Flags | ImGuiWindowFlags_ChildWindow, parent_window: node->HostWindow);
15333}
15334
15335static void ImGui::DockNodeRemoveWindow(ImGuiDockNode* node, ImGuiWindow* window, ImGuiID save_dock_id)
15336{
15337 ImGuiContext& g = *GImGui;
15338 IM_ASSERT(window->DockNode == node);
15339 //IM_ASSERT(window->RootWindowDockTree == node->HostWindow);
15340 //IM_ASSERT(window->LastFrameActive < g.FrameCount); // We may call this from Begin()
15341 IM_ASSERT(save_dock_id == 0 || save_dock_id == node->ID);
15342 IMGUI_DEBUG_LOG_DOCKING("[docking] DockNodeRemoveWindow node 0x%08X window '%s'\n", node->ID, window->Name);
15343
15344 window->DockNode = NULL;
15345 window->DockIsActive = window->DockTabWantClose = false;
15346 window->DockId = save_dock_id;
15347 window->Flags &= ~ImGuiWindowFlags_ChildWindow;
15348 if (window->ParentWindow)
15349 window->ParentWindow->DC.ChildWindows.find_erase(v: window);
15350 UpdateWindowParentAndRootLinks(window, flags: window->Flags, NULL); // Update immediately
15351
15352 // Remove window
15353 bool erased = false;
15354 for (int n = 0; n < node->Windows.Size; n++)
15355 if (node->Windows[n] == window)
15356 {
15357 node->Windows.erase(it: node->Windows.Data + n);
15358 erased = true;
15359 break;
15360 }
15361 if (!erased)
15362 IM_ASSERT(erased);
15363 if (node->VisibleWindow == window)
15364 node->VisibleWindow = NULL;
15365
15366 // Remove tab and possibly tab bar
15367 node->WantHiddenTabBarUpdate = true;
15368 if (node->TabBar)
15369 {
15370 TabBarRemoveTab(tab_bar: node->TabBar, tab_id: window->TabId);
15371 const int tab_count_threshold_for_tab_bar = node->IsCentralNode() ? 1 : 2;
15372 if (node->Windows.Size < tab_count_threshold_for_tab_bar)
15373 DockNodeRemoveTabBar(node);
15374 }
15375
15376 if (node->Windows.Size == 0 && !node->IsCentralNode() && !node->IsDockSpace() && window->DockId != node->ID)
15377 {
15378 // Automatic dock node delete themselves if they are not holding at least one tab
15379 DockContextRemoveNode(ctx: &g, node, merge_sibling_into_parent_node: true);
15380 return;
15381 }
15382
15383 if (node->Windows.Size == 1 && !node->IsCentralNode() && node->HostWindow)
15384 {
15385 ImGuiWindow* remaining_window = node->Windows[0];
15386 if (node->HostWindow->ViewportOwned && node->IsRootNode())
15387 {
15388 // Transfer viewport back to the remaining loose window
15389 IMGUI_DEBUG_LOG_VIEWPORT("[viewport] Node %08X transfer Viewport %08X=>%08X for Window '%s'\n", node->ID, node->HostWindow->Viewport->ID, remaining_window->ID, remaining_window->Name);
15390 IM_ASSERT(node->HostWindow->Viewport->Window == node->HostWindow);
15391 node->HostWindow->Viewport->Window = remaining_window;
15392 node->HostWindow->Viewport->ID = remaining_window->ID;
15393 }
15394 remaining_window->Collapsed = node->HostWindow->Collapsed;
15395 }
15396
15397 // Update visibility immediately is required so the DockNodeUpdateRemoveInactiveChilds() processing can reflect changes up the tree
15398 DockNodeUpdateVisibleFlag(node);
15399}
15400
15401static void ImGui::DockNodeMoveChildNodes(ImGuiDockNode* dst_node, ImGuiDockNode* src_node)
15402{
15403 IM_ASSERT(dst_node->Windows.Size == 0);
15404 dst_node->ChildNodes[0] = src_node->ChildNodes[0];
15405 dst_node->ChildNodes[1] = src_node->ChildNodes[1];
15406 if (dst_node->ChildNodes[0])
15407 dst_node->ChildNodes[0]->ParentNode = dst_node;
15408 if (dst_node->ChildNodes[1])
15409 dst_node->ChildNodes[1]->ParentNode = dst_node;
15410 dst_node->SplitAxis = src_node->SplitAxis;
15411 dst_node->SizeRef = src_node->SizeRef;
15412 src_node->ChildNodes[0] = src_node->ChildNodes[1] = NULL;
15413}
15414
15415static void ImGui::DockNodeMoveWindows(ImGuiDockNode* dst_node, ImGuiDockNode* src_node)
15416{
15417 // Insert tabs in the same orders as currently ordered (node->Windows isn't ordered)
15418 IM_ASSERT(src_node && dst_node && dst_node != src_node);
15419 ImGuiTabBar* src_tab_bar = src_node->TabBar;
15420 if (src_tab_bar != NULL)
15421 IM_ASSERT(src_node->Windows.Size <= src_node->TabBar->Tabs.Size);
15422
15423 // If the dst_node is empty we can just move the entire tab bar (to preserve selection, scrolling, etc.)
15424 bool move_tab_bar = (src_tab_bar != NULL) && (dst_node->TabBar == NULL);
15425 if (move_tab_bar)
15426 {
15427 dst_node->TabBar = src_node->TabBar;
15428 src_node->TabBar = NULL;
15429 }
15430
15431 // Tab order is not important here, it is preserved by sorting in DockNodeUpdateTabBar().
15432 for (ImGuiWindow* window : src_node->Windows)
15433 {
15434 window->DockNode = NULL;
15435 window->DockIsActive = false;
15436 DockNodeAddWindow(node: dst_node, window, add_to_tab_bar: !move_tab_bar);
15437 }
15438 src_node->Windows.clear();
15439
15440 if (!move_tab_bar && src_node->TabBar)
15441 {
15442 if (dst_node->TabBar)
15443 dst_node->TabBar->SelectedTabId = src_node->TabBar->SelectedTabId;
15444 DockNodeRemoveTabBar(node: src_node);
15445 }
15446}
15447
15448static void ImGui::DockNodeApplyPosSizeToWindows(ImGuiDockNode* node)
15449{
15450 for (int n = 0; n < node->Windows.Size; n++)
15451 {
15452 SetWindowPos(window: node->Windows[n], pos: node->Pos, cond: ImGuiCond_Always); // We don't assign directly to Pos because it can break the calculation of SizeContents on next frame
15453 SetWindowSize(window: node->Windows[n], size: node->Size, cond: ImGuiCond_Always);
15454 }
15455}
15456
15457static void ImGui::DockNodeHideHostWindow(ImGuiDockNode* node)
15458{
15459 if (node->HostWindow)
15460 {
15461 if (node->HostWindow->DockNodeAsHost == node)
15462 node->HostWindow->DockNodeAsHost = NULL;
15463 node->HostWindow = NULL;
15464 }
15465
15466 if (node->Windows.Size == 1)
15467 {
15468 node->VisibleWindow = node->Windows[0];
15469 node->Windows[0]->DockIsActive = false;
15470 }
15471
15472 if (node->TabBar)
15473 DockNodeRemoveTabBar(node);
15474}
15475
15476// Search function called once by root node in DockNodeUpdate()
15477struct ImGuiDockNodeTreeInfo
15478{
15479 ImGuiDockNode* CentralNode;
15480 ImGuiDockNode* FirstNodeWithWindows;
15481 int CountNodesWithWindows;
15482 //ImGuiWindowClass WindowClassForMerges;
15483
15484 ImGuiDockNodeTreeInfo() { memset(s: this, c: 0, n: sizeof(*this)); }
15485};
15486
15487static void DockNodeFindInfo(ImGuiDockNode* node, ImGuiDockNodeTreeInfo* info)
15488{
15489 if (node->Windows.Size > 0)
15490 {
15491 if (info->FirstNodeWithWindows == NULL)
15492 info->FirstNodeWithWindows = node;
15493 info->CountNodesWithWindows++;
15494 }
15495 if (node->IsCentralNode())
15496 {
15497 IM_ASSERT(info->CentralNode == NULL); // Should be only one
15498 IM_ASSERT(node->IsLeafNode() && "If you get this assert: please submit .ini file + repro of actions leading to this.");
15499 info->CentralNode = node;
15500 }
15501 if (info->CountNodesWithWindows > 1 && info->CentralNode != NULL)
15502 return;
15503 if (node->ChildNodes[0])
15504 DockNodeFindInfo(node: node->ChildNodes[0], info);
15505 if (node->ChildNodes[1])
15506 DockNodeFindInfo(node: node->ChildNodes[1], info);
15507}
15508
15509static ImGuiWindow* ImGui::DockNodeFindWindowByID(ImGuiDockNode* node, ImGuiID id)
15510{
15511 IM_ASSERT(id != 0);
15512 for (int n = 0; n < node->Windows.Size; n++)
15513 if (node->Windows[n]->ID == id)
15514 return node->Windows[n];
15515 return NULL;
15516}
15517
15518// - Remove inactive windows/nodes.
15519// - Update visibility flag.
15520static void ImGui::DockNodeUpdateFlagsAndCollapse(ImGuiDockNode* node)
15521{
15522 ImGuiContext& g = *GImGui;
15523 IM_ASSERT(node->ParentNode == NULL || node->ParentNode->ChildNodes[0] == node || node->ParentNode->ChildNodes[1] == node);
15524
15525 // Inherit most flags
15526 if (node->ParentNode)
15527 node->SharedFlags = node->ParentNode->SharedFlags & ImGuiDockNodeFlags_SharedFlagsInheritMask_;
15528
15529 // Recurse into children
15530 // There is the possibility that one of our child becoming empty will delete itself and moving its sibling contents into 'node'.
15531 // If 'node->ChildNode[0]' delete itself, then 'node->ChildNode[1]->Windows' will be moved into 'node'
15532 // 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)
15533 node->HasCentralNodeChild = false;
15534 if (node->ChildNodes[0])
15535 DockNodeUpdateFlagsAndCollapse(node: node->ChildNodes[0]);
15536 if (node->ChildNodes[1])
15537 DockNodeUpdateFlagsAndCollapse(node: node->ChildNodes[1]);
15538
15539 // Remove inactive windows, collapse nodes
15540 // Merge node flags overrides stored in windows
15541 node->LocalFlagsInWindows = ImGuiDockNodeFlags_None;
15542 for (int window_n = 0; window_n < node->Windows.Size; window_n++)
15543 {
15544 ImGuiWindow* window = node->Windows[window_n];
15545 IM_ASSERT(window->DockNode == node);
15546
15547 bool node_was_active = (node->LastFrameActive + 1 == g.FrameCount);
15548 bool remove = false;
15549 remove |= node_was_active && (window->LastFrameActive + 1 < g.FrameCount);
15550 remove |= node_was_active && (node->WantCloseAll || node->WantCloseTabId == window->TabId) && window->HasCloseButton && !(window->Flags & ImGuiWindowFlags_UnsavedDocument); // Submit all _expected_ closure from last frame
15551 remove |= (window->DockTabWantClose);
15552 if (remove)
15553 {
15554 window->DockTabWantClose = false;
15555 if (node->Windows.Size == 1 && !node->IsCentralNode())
15556 {
15557 DockNodeHideHostWindow(node);
15558 node->State = ImGuiDockNodeState_HostWindowHiddenBecauseSingleWindow;
15559 DockNodeRemoveWindow(node, window, save_dock_id: node->ID); // Will delete the node so it'll be invalid on return
15560 return;
15561 }
15562 DockNodeRemoveWindow(node, window, save_dock_id: node->ID);
15563 window_n--;
15564 continue;
15565 }
15566
15567 // FIXME-DOCKING: Missing policies for conflict resolution, hence the "Experimental" tag on this.
15568 //node->LocalFlagsInWindow &= ~window->WindowClass.DockNodeFlagsOverrideClear;
15569 node->LocalFlagsInWindows |= window->WindowClass.DockNodeFlagsOverrideSet;
15570 }
15571 node->UpdateMergedFlags();
15572
15573 // Auto-hide tab bar option
15574 ImGuiDockNodeFlags node_flags = node->MergedFlags;
15575 if (node->WantHiddenTabBarUpdate && node->Windows.Size == 1 && (node_flags & ImGuiDockNodeFlags_AutoHideTabBar) && !node->IsHiddenTabBar())
15576 node->WantHiddenTabBarToggle = true;
15577 node->WantHiddenTabBarUpdate = false;
15578
15579 // Cancel toggling if we know our tab bar is enforced to be hidden at all times
15580 if (node->WantHiddenTabBarToggle && node->VisibleWindow && (node->VisibleWindow->WindowClass.DockNodeFlagsOverrideSet & ImGuiDockNodeFlags_HiddenTabBar))
15581 node->WantHiddenTabBarToggle = false;
15582
15583 // Apply toggles at a single point of the frame (here!)
15584 if (node->Windows.Size > 1)
15585 node->SetLocalFlags(node->LocalFlags & ~ImGuiDockNodeFlags_HiddenTabBar);
15586 else if (node->WantHiddenTabBarToggle)
15587 node->SetLocalFlags(node->LocalFlags ^ ImGuiDockNodeFlags_HiddenTabBar);
15588 node->WantHiddenTabBarToggle = false;
15589
15590 DockNodeUpdateVisibleFlag(node);
15591}
15592
15593// This is rarely called as DockNodeUpdateForRootNode() generally does it most frames.
15594static void ImGui::DockNodeUpdateHasCentralNodeChild(ImGuiDockNode* node)
15595{
15596 node->HasCentralNodeChild = false;
15597 if (node->ChildNodes[0])
15598 DockNodeUpdateHasCentralNodeChild(node: node->ChildNodes[0]);
15599 if (node->ChildNodes[1])
15600 DockNodeUpdateHasCentralNodeChild(node: node->ChildNodes[1]);
15601 if (node->IsRootNode())
15602 {
15603 ImGuiDockNode* mark_node = node->CentralNode;
15604 while (mark_node)
15605 {
15606 mark_node->HasCentralNodeChild = true;
15607 mark_node = mark_node->ParentNode;
15608 }
15609 }
15610}
15611
15612static void ImGui::DockNodeUpdateVisibleFlag(ImGuiDockNode* node)
15613{
15614 // Update visibility flag
15615 bool is_visible = (node->ParentNode == NULL) ? node->IsDockSpace() : node->IsCentralNode();
15616 is_visible |= (node->Windows.Size > 0);
15617 is_visible |= (node->ChildNodes[0] && node->ChildNodes[0]->IsVisible);
15618 is_visible |= (node->ChildNodes[1] && node->ChildNodes[1]->IsVisible);
15619 node->IsVisible = is_visible;
15620}
15621
15622static void ImGui::DockNodeStartMouseMovingWindow(ImGuiDockNode* node, ImGuiWindow* window)
15623{
15624 ImGuiContext& g = *GImGui;
15625 IM_ASSERT(node->WantMouseMove == true);
15626 StartMouseMovingWindow(window);
15627 g.ActiveIdClickOffset = g.IO.MouseClickedPos[0] - node->Pos;
15628 g.MovingWindow = window; // If we are docked into a non moveable root window, StartMouseMovingWindow() won't set g.MovingWindow. Override that decision.
15629 node->WantMouseMove = false;
15630}
15631
15632// Update CentralNode, OnlyNodeWithWindows, LastFocusedNodeID. Copy window class.
15633static void ImGui::DockNodeUpdateForRootNode(ImGuiDockNode* node)
15634{
15635 DockNodeUpdateFlagsAndCollapse(node);
15636
15637 // - Setup central node pointers
15638 // - 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!)
15639 // Cannot merge this with DockNodeUpdateFlagsAndCollapse() because FirstNodeWithWindows is found after window removal and child collapsing
15640 ImGuiDockNodeTreeInfo info;
15641 DockNodeFindInfo(node, info: &info);
15642 node->CentralNode = info.CentralNode;
15643 node->OnlyNodeWithWindows = (info.CountNodesWithWindows == 1) ? info.FirstNodeWithWindows : NULL;
15644 node->CountNodeWithWindows = info.CountNodesWithWindows;
15645 if (node->LastFocusedNodeId == 0 && info.FirstNodeWithWindows != NULL)
15646 node->LastFocusedNodeId = info.FirstNodeWithWindows->ID;
15647
15648 // Copy the window class from of our first window so it can be used for proper dock filtering.
15649 // When node has mixed windows, prioritize the class with the most constraint (DockingAllowUnclassed = false) as the reference to copy.
15650 // FIXME-DOCK: We don't recurse properly, this code could be reworked to work from DockNodeUpdateScanRec.
15651 if (ImGuiDockNode* first_node_with_windows = info.FirstNodeWithWindows)
15652 {
15653 node->WindowClass = first_node_with_windows->Windows[0]->WindowClass;
15654 for (int n = 1; n < first_node_with_windows->Windows.Size; n++)
15655 if (first_node_with_windows->Windows[n]->WindowClass.DockingAllowUnclassed == false)
15656 {
15657 node->WindowClass = first_node_with_windows->Windows[n]->WindowClass;
15658 break;
15659 }
15660 }
15661
15662 ImGuiDockNode* mark_node = node->CentralNode;
15663 while (mark_node)
15664 {
15665 mark_node->HasCentralNodeChild = true;
15666 mark_node = mark_node->ParentNode;
15667 }
15668}
15669
15670static void DockNodeSetupHostWindow(ImGuiDockNode* node, ImGuiWindow* host_window)
15671{
15672 // Remove ourselves from any previous different host window
15673 // This can happen if a user mistakenly does (see #4295 for details):
15674 // - N+0: DockBuilderAddNode(id, 0) // missing ImGuiDockNodeFlags_DockSpace
15675 // - N+1: NewFrame() // will create floating host window for that node
15676 // - N+1: DockSpace(id) // requalify node as dockspace, moving host window
15677 if (node->HostWindow && node->HostWindow != host_window && node->HostWindow->DockNodeAsHost == node)
15678 node->HostWindow->DockNodeAsHost = NULL;
15679
15680 host_window->DockNodeAsHost = node;
15681 node->HostWindow = host_window;
15682}
15683
15684static void ImGui::DockNodeUpdate(ImGuiDockNode* node)
15685{
15686 ImGuiContext& g = *GImGui;
15687 IM_ASSERT(node->LastFrameActive != g.FrameCount);
15688 node->LastFrameAlive = g.FrameCount;
15689 node->IsBgDrawnThisFrame = false;
15690
15691 node->CentralNode = node->OnlyNodeWithWindows = NULL;
15692 if (node->IsRootNode())
15693 DockNodeUpdateForRootNode(node);
15694
15695 // Remove tab bar if not needed
15696 if (node->TabBar && node->IsNoTabBar())
15697 DockNodeRemoveTabBar(node);
15698
15699 // 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)
15700 bool want_to_hide_host_window = false;
15701 if (node->IsFloatingNode())
15702 {
15703 if (node->Windows.Size <= 1 && node->IsLeafNode())
15704 if (!g.IO.ConfigDockingAlwaysTabBar && (node->Windows.Size == 0 || !node->Windows[0]->WindowClass.DockingAlwaysTabBar))
15705 want_to_hide_host_window = true;
15706 if (node->CountNodeWithWindows == 0)
15707 want_to_hide_host_window = true;
15708 }
15709 if (want_to_hide_host_window)
15710 {
15711 if (node->Windows.Size == 1)
15712 {
15713 // Floating window pos/size is authoritative
15714 ImGuiWindow* single_window = node->Windows[0];
15715 node->Pos = single_window->Pos;
15716 node->Size = single_window->SizeFull;
15717 node->AuthorityForPos = node->AuthorityForSize = node->AuthorityForViewport = ImGuiDataAuthority_Window;
15718
15719 // Transfer focus immediately so when we revert to a regular window it is immediately selected
15720 if (node->HostWindow && g.NavWindow == node->HostWindow)
15721 FocusWindow(window: single_window);
15722 if (node->HostWindow)
15723 {
15724 single_window->Viewport = node->HostWindow->Viewport;
15725 single_window->ViewportId = node->HostWindow->ViewportId;
15726 if (node->HostWindow->ViewportOwned)
15727 {
15728 single_window->Viewport->Window = single_window;
15729 single_window->ViewportOwned = true;
15730 }
15731 }
15732 }
15733
15734 DockNodeHideHostWindow(node);
15735 node->State = ImGuiDockNodeState_HostWindowHiddenBecauseSingleWindow;
15736 node->WantCloseAll = false;
15737 node->WantCloseTabId = 0;
15738 node->HasCloseButton = node->HasWindowMenuButton = false;
15739 node->LastFrameActive = g.FrameCount;
15740
15741 if (node->WantMouseMove && node->Windows.Size == 1)
15742 DockNodeStartMouseMovingWindow(node, window: node->Windows[0]);
15743 return;
15744 }
15745
15746 // In some circumstance we will defer creating the host window (so everything will be kept hidden),
15747 // while the expected visible window is resizing itself.
15748 // This is important for first-time (no ini settings restored) single window when io.ConfigDockingAlwaysTabBar is enabled,
15749 // otherwise the node ends up using the minimum window size. Effectively those windows will take an extra frame to show up:
15750 // N+0: Begin(): window created (with no known size), node is created
15751 // N+1: DockNodeUpdate(): node skip creating host window / Begin(): window size applied, not visible
15752 // N+2: DockNodeUpdate(): node can create host window / Begin(): window becomes visible
15753 // We could remove this frame if we could reliably calculate the expected window size during node update, before the Begin() code.
15754 // It would require a generalization of CalcWindowExpectedSize(), probably extracting code away from Begin().
15755 // In reality it isn't very important as user quickly ends up with size data in .ini file.
15756 if (node->IsVisible && node->HostWindow == NULL && node->IsFloatingNode() && node->IsLeafNode())
15757 {
15758 IM_ASSERT(node->Windows.Size > 0);
15759 ImGuiWindow* ref_window = NULL;
15760 if (node->SelectedTabId != 0) // Note that we prune single-window-node settings on .ini loading, so this is generally 0 for them!
15761 ref_window = DockNodeFindWindowByID(node, id: node->SelectedTabId);
15762 if (ref_window == NULL)
15763 ref_window = node->Windows[0];
15764 if (ref_window->AutoFitFramesX > 0 || ref_window->AutoFitFramesY > 0)
15765 {
15766 node->State = ImGuiDockNodeState_HostWindowHiddenBecauseWindowsAreResizing;
15767 return;
15768 }
15769 }
15770
15771 const ImGuiDockNodeFlags node_flags = node->MergedFlags;
15772
15773 // Decide if the node will have a close button and a window menu button
15774 node->HasWindowMenuButton = (node->Windows.Size > 0) && (node_flags & ImGuiDockNodeFlags_NoWindowMenuButton) == 0;
15775 node->HasCloseButton = false;
15776 for (int window_n = 0; window_n < node->Windows.Size; window_n++)
15777 {
15778 // FIXME-DOCK: Setting DockIsActive here means that for single active window in a leaf node, DockIsActive will be cleared until the next Begin() call.
15779 ImGuiWindow* window = node->Windows[window_n];
15780 node->HasCloseButton |= window->HasCloseButton;
15781 window->DockIsActive = (node->Windows.Size > 1);
15782 }
15783 if (node_flags & ImGuiDockNodeFlags_NoCloseButton)
15784 node->HasCloseButton = false;
15785
15786 // Bind or create host window
15787 ImGuiWindow* host_window = NULL;
15788 bool beginned_into_host_window = false;
15789 if (node->IsDockSpace())
15790 {
15791 // [Explicit root dockspace node]
15792 IM_ASSERT(node->HostWindow);
15793 host_window = node->HostWindow;
15794 }
15795 else
15796 {
15797 // [Automatic root or child nodes]
15798 if (node->IsRootNode() && node->IsVisible)
15799 {
15800 ImGuiWindow* ref_window = (node->Windows.Size > 0) ? node->Windows[0] : NULL;
15801
15802 // Sync Pos
15803 if (node->AuthorityForPos == ImGuiDataAuthority_Window && ref_window)
15804 SetNextWindowPos(pos: ref_window->Pos);
15805 else if (node->AuthorityForPos == ImGuiDataAuthority_DockNode)
15806 SetNextWindowPos(pos: node->Pos);
15807
15808 // Sync Size
15809 if (node->AuthorityForSize == ImGuiDataAuthority_Window && ref_window)
15810 SetNextWindowSize(size: ref_window->SizeFull);
15811 else if (node->AuthorityForSize == ImGuiDataAuthority_DockNode)
15812 SetNextWindowSize(size: node->Size);
15813
15814 // Sync Collapsed
15815 if (node->AuthorityForSize == ImGuiDataAuthority_Window && ref_window)
15816 SetNextWindowCollapsed(collapsed: ref_window->Collapsed);
15817
15818 // Sync Viewport
15819 if (node->AuthorityForViewport == ImGuiDataAuthority_Window && ref_window)
15820 SetNextWindowViewport(ref_window->ViewportId);
15821
15822 SetNextWindowClass(&node->WindowClass);
15823
15824 // Begin into the host window
15825 char window_label[20];
15826 DockNodeGetHostWindowTitle(node, buf: window_label, IM_ARRAYSIZE(window_label));
15827 ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_DockNodeHost;
15828 window_flags |= ImGuiWindowFlags_NoFocusOnAppearing;
15829 window_flags |= ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoCollapse;
15830 window_flags |= ImGuiWindowFlags_NoTitleBar;
15831
15832 SetNextWindowBgAlpha(0.0f); // Don't set ImGuiWindowFlags_NoBackground because it disables borders
15833 PushStyleVar(idx: ImGuiStyleVar_WindowPadding, val: ImVec2(0, 0));
15834 Begin(name: window_label, NULL, flags: window_flags);
15835 PopStyleVar();
15836 beginned_into_host_window = true;
15837
15838 host_window = g.CurrentWindow;
15839 DockNodeSetupHostWindow(node, host_window);
15840 host_window->DC.CursorPos = host_window->Pos;
15841 node->Pos = host_window->Pos;
15842 node->Size = host_window->Size;
15843
15844 // We set ImGuiWindowFlags_NoFocusOnAppearing because we don't want the host window to take full focus (e.g. steal NavWindow)
15845 // But we still it bring it to the front of display. There's no way to choose this precise behavior via window flags.
15846 // 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.
15847 // 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
15848 // 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
15849 // after the dock host window, losing their top-most status.
15850 if (node->HostWindow->Appearing)
15851 BringWindowToDisplayFront(window: node->HostWindow);
15852
15853 node->AuthorityForPos = node->AuthorityForSize = node->AuthorityForViewport = ImGuiDataAuthority_Auto;
15854 }
15855 else if (node->ParentNode)
15856 {
15857 node->HostWindow = host_window = node->ParentNode->HostWindow;
15858 node->AuthorityForPos = node->AuthorityForSize = node->AuthorityForViewport = ImGuiDataAuthority_Auto;
15859 }
15860 if (node->WantMouseMove && node->HostWindow)
15861 DockNodeStartMouseMovingWindow(node, window: node->HostWindow);
15862 }
15863
15864 // Update focused node (the one whose title bar is highlight) within a node tree
15865 if (node->IsSplitNode())
15866 IM_ASSERT(node->TabBar == NULL);
15867 if (node->IsRootNode())
15868 if (ImGuiWindow* p_window = g.NavWindow ? g.NavWindow->RootWindow : NULL)
15869 while (p_window != NULL && p_window->DockNode != NULL)
15870 {
15871 ImGuiDockNode* p_node = DockNodeGetRootNode(node: p_window->DockNode);
15872 if (p_node == node)
15873 {
15874 node->LastFocusedNodeId = p_window->DockNode->ID; // Note: not using root node ID!
15875 break;
15876 }
15877 p_window = p_node->HostWindow ? p_node->HostWindow->RootWindow : NULL;
15878 }
15879
15880 // Register a hit-test hole in the window unless we are currently dragging a window that is compatible with our dockspace
15881 ImGuiDockNode* central_node = node->CentralNode;
15882 const bool central_node_hole = node->IsRootNode() && host_window && (node_flags & ImGuiDockNodeFlags_PassthruCentralNode) != 0 && central_node != NULL && central_node->IsEmpty();
15883 bool central_node_hole_register_hit_test_hole = central_node_hole;
15884 if (central_node_hole)
15885 if (const ImGuiPayload* payload = ImGui::GetDragDropPayload())
15886 if (payload->IsDataType(IMGUI_PAYLOAD_TYPE_WINDOW) && DockNodeIsDropAllowed(host_window, payload_window: *(ImGuiWindow**)payload->Data))
15887 central_node_hole_register_hit_test_hole = false;
15888 if (central_node_hole_register_hit_test_hole)
15889 {
15890 // We add a little padding to match the "resize from edges" behavior and allow grabbing the splitter easily.
15891 // (But we only add it if there's something else on the other side of the hole, otherwise for e.g. fullscreen
15892 // covering passthru node we'd have a gap on the edge not covered by the hole)
15893 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
15894 ImGuiDockNode* root_node = DockNodeGetRootNode(node: central_node);
15895 ImRect root_rect(root_node->Pos, root_node->Pos + root_node->Size);
15896 ImRect hole_rect(central_node->Pos, central_node->Pos + central_node->Size);
15897 if (hole_rect.Min.x > root_rect.Min.x) { hole_rect.Min.x += WINDOWS_HOVER_PADDING; }
15898 if (hole_rect.Max.x < root_rect.Max.x) { hole_rect.Max.x -= WINDOWS_HOVER_PADDING; }
15899 if (hole_rect.Min.y > root_rect.Min.y) { hole_rect.Min.y += WINDOWS_HOVER_PADDING; }
15900 if (hole_rect.Max.y < root_rect.Max.y) { hole_rect.Max.y -= WINDOWS_HOVER_PADDING; }
15901 //GetForegroundDrawList()->AddRect(hole_rect.Min, hole_rect.Max, IM_COL32(255, 0, 0, 255));
15902 if (central_node_hole && !hole_rect.IsInverted())
15903 {
15904 SetWindowHitTestHole(window: host_window, pos: hole_rect.Min, size: hole_rect.Max - hole_rect.Min);
15905 if (host_window->ParentWindow)
15906 SetWindowHitTestHole(window: host_window->ParentWindow, pos: hole_rect.Min, size: hole_rect.Max - hole_rect.Min);
15907 }
15908 }
15909
15910 // Update position/size, process and draw resizing splitters
15911 if (node->IsRootNode() && host_window)
15912 {
15913 DockNodeTreeUpdatePosSize(node, pos: host_window->Pos, size: host_window->Size);
15914 DockNodeTreeUpdateSplitter(node);
15915 }
15916
15917 // Draw empty node background (currently can only be the Central Node)
15918 if (host_window && node->IsEmpty() && node->IsVisible)
15919 {
15920 host_window->DrawList->ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_BG);
15921 node->LastBgColor = (node_flags & ImGuiDockNodeFlags_PassthruCentralNode) ? 0 : GetColorU32(idx: ImGuiCol_DockingEmptyBg);
15922 if (node->LastBgColor != 0)
15923 host_window->DrawList->AddRectFilled(p_min: node->Pos, p_max: node->Pos + node->Size, col: node->LastBgColor);
15924 node->IsBgDrawnThisFrame = true;
15925 }
15926
15927 // Draw whole dockspace background if ImGuiDockNodeFlags_PassthruCentralNode if set.
15928 // We need to draw a background at the root level if requested by ImGuiDockNodeFlags_PassthruCentralNode, but we will only know the correct pos/size
15929 // _after_ processing the resizing splitters. So we are using the DrawList channel splitting facility to submit drawing primitives out of order!
15930 const bool render_dockspace_bg = node->IsRootNode() && host_window && (node_flags & ImGuiDockNodeFlags_PassthruCentralNode) != 0;
15931 if (render_dockspace_bg && node->IsVisible)
15932 {
15933 host_window->DrawList->ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_BG);
15934 if (central_node_hole)
15935 RenderRectFilledWithHole(draw_list: host_window->DrawList, outer: node->Rect(), inner: central_node->Rect(), col: GetColorU32(idx: ImGuiCol_WindowBg), rounding: 0.0f);
15936 else
15937 host_window->DrawList->AddRectFilled(p_min: node->Pos, p_max: node->Pos + node->Size, col: GetColorU32(idx: ImGuiCol_WindowBg), rounding: 0.0f);
15938 }
15939
15940 // Draw and populate Tab Bar
15941 if (host_window)
15942 host_window->DrawList->ChannelsSetCurrent(DOCKING_HOST_DRAW_CHANNEL_FG);
15943 if (host_window && node->Windows.Size > 0)
15944 {
15945 DockNodeUpdateTabBar(node, host_window);
15946 }
15947 else
15948 {
15949 node->WantCloseAll = false;
15950 node->WantCloseTabId = 0;
15951 node->IsFocused = false;
15952 }
15953 if (node->TabBar && node->TabBar->SelectedTabId)
15954 node->SelectedTabId = node->TabBar->SelectedTabId;
15955 else if (node->Windows.Size > 0)
15956 node->SelectedTabId = node->Windows[0]->TabId;
15957
15958 // Draw payload drop target
15959 if (host_window && node->IsVisible)
15960 if (node->IsRootNode() && (g.MovingWindow == NULL || g.MovingWindow->RootWindowDockTree != host_window))
15961 BeginDockableDragDropTarget(window: host_window);
15962
15963 // We update this after DockNodeUpdateTabBar()
15964 node->LastFrameActive = g.FrameCount;
15965
15966 // Recurse into children
15967 // FIXME-DOCK FIXME-OPT: Should not need to recurse into children
15968 if (host_window)
15969 {
15970 if (node->ChildNodes[0])
15971 DockNodeUpdate(node: node->ChildNodes[0]);
15972 if (node->ChildNodes[1])
15973 DockNodeUpdate(node: node->ChildNodes[1]);
15974
15975 // Render outer borders last (after the tab bar)
15976 if (node->IsRootNode())
15977 RenderWindowOuterBorders(window: host_window);
15978 }
15979
15980 // End host window
15981 if (beginned_into_host_window) //-V1020
15982 End();
15983}
15984
15985// 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.
15986static int IMGUI_CDECL TabItemComparerByDockOrder(const void* lhs, const void* rhs)
15987{
15988 ImGuiWindow* a = ((const ImGuiTabItem*)lhs)->Window;
15989 ImGuiWindow* b = ((const ImGuiTabItem*)rhs)->Window;
15990 if (int d = ((a->DockOrder == -1) ? INT_MAX : a->DockOrder) - ((b->DockOrder == -1) ? INT_MAX : b->DockOrder))
15991 return d;
15992 return (a->BeginOrderWithinContext - b->BeginOrderWithinContext);
15993}
15994
15995static ImGuiID ImGui::DockNodeUpdateWindowMenu(ImGuiDockNode* node, ImGuiTabBar* tab_bar)
15996{
15997 // Try to position the menu so it is more likely to stays within the same viewport
15998 ImGuiContext& g = *GImGui;
15999 ImGuiID ret_tab_id = 0;
16000 if (g.Style.WindowMenuButtonPosition == ImGuiDir_Left)
16001 SetNextWindowPos(pos: ImVec2(node->Pos.x, node->Pos.y + GetFrameHeight()), cond: ImGuiCond_Always, pivot: ImVec2(0.0f, 0.0f));
16002 else
16003 SetNextWindowPos(pos: ImVec2(node->Pos.x + node->Size.x, node->Pos.y + GetFrameHeight()), cond: ImGuiCond_Always, pivot: ImVec2(1.0f, 0.0f));
16004 if (BeginPopup(str_id: "#WindowMenu"))
16005 {
16006 node->IsFocused = true;
16007 if (tab_bar->Tabs.Size == 1)
16008 {
16009 if (MenuItem(label: LocalizeGetMsg(key: ImGuiLocKey_DockingHideTabBar), NULL, selected: node->IsHiddenTabBar()))
16010 node->WantHiddenTabBarToggle = true;
16011 }
16012 else
16013 {
16014 for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
16015 {
16016 ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
16017 if (tab->Flags & ImGuiTabItemFlags_Button)
16018 continue;
16019 if (Selectable(label: tab_bar->GetTabName(tab), selected: tab->ID == tab_bar->SelectedTabId))
16020 ret_tab_id = tab->ID;
16021 SameLine();
16022 Text(fmt: " ");
16023 }
16024 }
16025 EndPopup();
16026 }
16027 return ret_tab_id;
16028}
16029
16030// User helper to append/amend into a dock node tab bar. Most commonly used to add e.g. a "+" button.
16031bool ImGui::DockNodeBeginAmendTabBar(ImGuiDockNode* node)
16032{
16033 if (node->TabBar == NULL || node->HostWindow == NULL)
16034 return false;
16035 if (node->MergedFlags & ImGuiDockNodeFlags_KeepAliveOnly)
16036 return false;
16037 Begin(name: node->HostWindow->Name);
16038 PushOverrideID(id: node->ID);
16039 bool ret = BeginTabBarEx(tab_bar: node->TabBar, bb: node->TabBar->BarRect, flags: node->TabBar->Flags, dock_node: node);
16040 IM_UNUSED(ret);
16041 IM_ASSERT(ret);
16042 return true;
16043}
16044
16045void ImGui::DockNodeEndAmendTabBar()
16046{
16047 EndTabBar();
16048 PopID();
16049 End();
16050}
16051
16052static bool IsDockNodeTitleBarHighlighted(ImGuiDockNode* node, ImGuiDockNode* root_node)
16053{
16054 // CTRL+Tab highlight (only highlighting leaf node, not whole hierarchy)
16055 ImGuiContext& g = *GImGui;
16056 if (g.NavWindowingTarget)
16057 return (g.NavWindowingTarget->DockNode == node);
16058
16059 // FIXME-DOCKING: May want alternative to treat central node void differently? e.g. if (g.NavWindow == host_window)
16060 if (g.NavWindow && root_node->LastFocusedNodeId == node->ID)
16061 {
16062 // FIXME: This could all be backed in RootWindowForTitleBarHighlight? Probably need to reorganize for both dock nodes + other RootWindowForTitleBarHighlight users (not-node)
16063 ImGuiWindow* parent_window = g.NavWindow->RootWindow;
16064 while (parent_window->Flags & ImGuiWindowFlags_ChildMenu)
16065 parent_window = parent_window->ParentWindow->RootWindow;
16066 ImGuiDockNode* start_parent_node = parent_window->DockNodeAsHost ? parent_window->DockNodeAsHost : parent_window->DockNode;
16067 for (ImGuiDockNode* parent_node = start_parent_node; parent_node != NULL; parent_node = parent_node->HostWindow ? parent_node->HostWindow->RootWindow->DockNode : NULL)
16068 if ((parent_node = ImGui::DockNodeGetRootNode(node: parent_node)) == root_node)
16069 return true;
16070 }
16071 return false;
16072}
16073
16074// Submit the tab bar corresponding to a dock node and various housekeeping details.
16075static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_window)
16076{
16077 ImGuiContext& g = *GImGui;
16078 ImGuiStyle& style = g.Style;
16079
16080 const bool node_was_active = (node->LastFrameActive + 1 == g.FrameCount);
16081 const bool closed_all = node->WantCloseAll && node_was_active;
16082 const ImGuiID closed_one = node->WantCloseTabId && node_was_active;
16083 node->WantCloseAll = false;
16084 node->WantCloseTabId = 0;
16085
16086 // Decide if we should use a focused title bar color
16087 bool is_focused = false;
16088 ImGuiDockNode* root_node = DockNodeGetRootNode(node);
16089 if (IsDockNodeTitleBarHighlighted(node, root_node))
16090 is_focused = true;
16091
16092 // Hidden tab bar will show a triangle on the upper-left (in Begin)
16093 if (node->IsHiddenTabBar() || node->IsNoTabBar())
16094 {
16095 node->VisibleWindow = (node->Windows.Size > 0) ? node->Windows[0] : NULL;
16096 node->IsFocused = is_focused;
16097 if (is_focused)
16098 node->LastFrameFocused = g.FrameCount;
16099 if (node->VisibleWindow)
16100 {
16101 // Notify root of visible window (used to display title in OS task bar)
16102 if (is_focused || root_node->VisibleWindow == NULL)
16103 root_node->VisibleWindow = node->VisibleWindow;
16104 if (node->TabBar)
16105 node->TabBar->VisibleTabId = node->VisibleWindow->TabId;
16106 }
16107 return;
16108 }
16109
16110 // 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
16111 bool backup_skip_item = host_window->SkipItems;
16112 if (!node->IsDockSpace())
16113 {
16114 host_window->SkipItems = false;
16115 host_window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;
16116 }
16117
16118 // Use PushOverrideID() instead of PushID() to use the node id _without_ the host window ID.
16119 // 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,
16120 // as docked windows themselves will override the stack with their own root ID.
16121 PushOverrideID(id: node->ID);
16122 ImGuiTabBar* tab_bar = node->TabBar;
16123 bool tab_bar_is_recreated = (tab_bar == NULL); // Tab bar are automatically destroyed when a node gets hidden
16124 if (tab_bar == NULL)
16125 {
16126 DockNodeAddTabBar(node);
16127 tab_bar = node->TabBar;
16128 }
16129
16130 ImGuiID focus_tab_id = 0;
16131 node->IsFocused = is_focused;
16132
16133 const ImGuiDockNodeFlags node_flags = node->MergedFlags;
16134 const bool has_window_menu_button = (node_flags & ImGuiDockNodeFlags_NoWindowMenuButton) == 0 && (style.WindowMenuButtonPosition != ImGuiDir_None);
16135
16136 // In a dock node, the Collapse Button turns into the Window Menu button.
16137 // FIXME-DOCK FIXME-OPT: Could we recycle popups id across multiple dock nodes?
16138 if (has_window_menu_button && IsPopupOpen(str_id: "#WindowMenu"))
16139 {
16140 if (ImGuiID tab_id = DockNodeUpdateWindowMenu(node, tab_bar))
16141 focus_tab_id = tab_bar->NextSelectedTabId = tab_id;
16142 is_focused |= node->IsFocused;
16143 }
16144
16145 // Layout
16146 ImRect title_bar_rect, tab_bar_rect;
16147 ImVec2 window_menu_button_pos;
16148 ImVec2 close_button_pos;
16149 DockNodeCalcTabBarLayout(node, out_title_rect: &title_bar_rect, out_tab_bar_rect: &tab_bar_rect, out_window_menu_button_pos: &window_menu_button_pos, out_close_button_pos: &close_button_pos);
16150
16151 // Submit new tabs, they will be added as Unsorted and sorted below based on relative DockOrder value.
16152 const int tabs_count_old = tab_bar->Tabs.Size;
16153 for (int window_n = 0; window_n < node->Windows.Size; window_n++)
16154 {
16155 ImGuiWindow* window = node->Windows[window_n];
16156 if (TabBarFindTabByID(tab_bar, tab_id: window->TabId) == NULL)
16157 TabBarAddTab(tab_bar, tab_flags: ImGuiTabItemFlags_Unsorted, window);
16158 }
16159
16160 // Title bar
16161 if (is_focused)
16162 node->LastFrameFocused = g.FrameCount;
16163 ImU32 title_bar_col = GetColorU32(idx: host_window->Collapsed ? ImGuiCol_TitleBgCollapsed : is_focused ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg);
16164 ImDrawFlags rounding_flags = CalcRoundingFlagsForRectInRect(r_in: title_bar_rect, r_outer: host_window->Rect(), threshold: DOCKING_SPLITTER_SIZE);
16165 host_window->DrawList->AddRectFilled(p_min: title_bar_rect.Min, p_max: title_bar_rect.Max, col: title_bar_col, rounding: host_window->WindowRounding, flags: rounding_flags);
16166
16167 // Docking/Collapse button
16168 if (has_window_menu_button)
16169 {
16170 if (CollapseButton(id: host_window->GetID(str: "#COLLAPSE"), pos: window_menu_button_pos, dock_node: node)) // == DockNodeGetWindowMenuButtonId(node)
16171 OpenPopup(str_id: "#WindowMenu");
16172 if (IsItemActive())
16173 focus_tab_id = tab_bar->SelectedTabId;
16174 }
16175
16176 // If multiple tabs are appearing on the same frame, sort them based on their persistent DockOrder value
16177 int tabs_unsorted_start = tab_bar->Tabs.Size;
16178 for (int tab_n = tab_bar->Tabs.Size - 1; tab_n >= 0 && (tab_bar->Tabs[tab_n].Flags & ImGuiTabItemFlags_Unsorted); tab_n--)
16179 {
16180 // 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?
16181 tab_bar->Tabs[tab_n].Flags &= ~ImGuiTabItemFlags_Unsorted;
16182 tabs_unsorted_start = tab_n;
16183 }
16184 if (tab_bar->Tabs.Size > tabs_unsorted_start)
16185 {
16186 IMGUI_DEBUG_LOG_DOCKING("[docking] In node 0x%08X: %d new appearing tabs:%s\n", node->ID, tab_bar->Tabs.Size - tabs_unsorted_start, (tab_bar->Tabs.Size > tabs_unsorted_start + 1) ? " (will sort)" : "");
16187 for (int tab_n = tabs_unsorted_start; tab_n < tab_bar->Tabs.Size; tab_n++)
16188 IMGUI_DEBUG_LOG_DOCKING("[docking] - Tab '%s' Order %d\n", tab_bar->Tabs[tab_n].Window->Name, tab_bar->Tabs[tab_n].Window->DockOrder);
16189 if (tab_bar->Tabs.Size > tabs_unsorted_start + 1)
16190 ImQsort(base: tab_bar->Tabs.Data + tabs_unsorted_start, count: tab_bar->Tabs.Size - tabs_unsorted_start, size_of_element: sizeof(ImGuiTabItem), compare_func: TabItemComparerByDockOrder);
16191 }
16192
16193 // Apply NavWindow focus back to the tab bar
16194 if (g.NavWindow && g.NavWindow->RootWindow->DockNode == node)
16195 tab_bar->SelectedTabId = g.NavWindow->RootWindow->TabId;
16196
16197 // Selected newly added tabs, or persistent tab ID if the tab bar was just recreated
16198 if (tab_bar_is_recreated && TabBarFindTabByID(tab_bar, tab_id: node->SelectedTabId) != NULL)
16199 tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = node->SelectedTabId;
16200 else if (tab_bar->Tabs.Size > tabs_count_old)
16201 tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = tab_bar->Tabs.back().Window->TabId;
16202
16203 // Begin tab bar
16204 ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_AutoSelectNewTabs; // | ImGuiTabBarFlags_NoTabListScrollingButtons);
16205 tab_bar_flags |= ImGuiTabBarFlags_SaveSettings | ImGuiTabBarFlags_DockNode;
16206 if (!host_window->Collapsed && is_focused)
16207 tab_bar_flags |= ImGuiTabBarFlags_IsFocused;
16208 BeginTabBarEx(tab_bar, bb: tab_bar_rect, flags: tab_bar_flags, dock_node: node);
16209 //host_window->DrawList->AddRect(tab_bar_rect.Min, tab_bar_rect.Max, IM_COL32(255,0,255,255));
16210
16211 // Backup style colors
16212 ImVec4 backup_style_cols[ImGuiWindowDockStyleCol_COUNT];
16213 for (int color_n = 0; color_n < ImGuiWindowDockStyleCol_COUNT; color_n++)
16214 backup_style_cols[color_n] = g.Style.Colors[GWindowDockStyleColors[color_n]];
16215
16216 // Submit actual tabs
16217 node->VisibleWindow = NULL;
16218 for (int window_n = 0; window_n < node->Windows.Size; window_n++)
16219 {
16220 ImGuiWindow* window = node->Windows[window_n];
16221 if ((closed_all || closed_one == window->TabId) && window->HasCloseButton && !(window->Flags & ImGuiWindowFlags_UnsavedDocument))
16222 continue;
16223 if (window->LastFrameActive + 1 >= g.FrameCount || !node_was_active)
16224 {
16225 ImGuiTabItemFlags tab_item_flags = 0;
16226 tab_item_flags |= window->WindowClass.TabItemFlagsOverrideSet;
16227 if (window->Flags & ImGuiWindowFlags_UnsavedDocument)
16228 tab_item_flags |= ImGuiTabItemFlags_UnsavedDocument;
16229 if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)
16230 tab_item_flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton;
16231
16232 // Apply stored style overrides for the window
16233 for (int color_n = 0; color_n < ImGuiWindowDockStyleCol_COUNT; color_n++)
16234 g.Style.Colors[GWindowDockStyleColors[color_n]] = ColorConvertU32ToFloat4(in: window->DockStyle.Colors[color_n]);
16235
16236 // Note that TabItemEx() calls TabBarCalcTabID() so our tab item ID will ignore the current ID stack (rightly so)
16237 bool tab_open = true;
16238 TabItemEx(tab_bar, label: window->Name, p_open: window->HasCloseButton ? &tab_open : NULL, flags: tab_item_flags, docked_window: window);
16239 if (!tab_open)
16240 node->WantCloseTabId = window->TabId;
16241 if (tab_bar->VisibleTabId == window->TabId)
16242 node->VisibleWindow = window;
16243
16244 // Store last item data so it can be queried with IsItemXXX functions after the user Begin() call
16245 window->DockTabItemStatusFlags = g.LastItemData.StatusFlags;
16246 window->DockTabItemRect = g.LastItemData.Rect;
16247
16248 // Update navigation ID on menu layer
16249 if (g.NavWindow && g.NavWindow->RootWindow == window && (window->DC.NavLayersActiveMask & (1 << ImGuiNavLayer_Menu)) == 0)
16250 host_window->NavLastIds[1] = window->TabId;
16251 }
16252 }
16253
16254 // Restore style colors
16255 for (int color_n = 0; color_n < ImGuiWindowDockStyleCol_COUNT; color_n++)
16256 g.Style.Colors[GWindowDockStyleColors[color_n]] = backup_style_cols[color_n];
16257
16258 // Notify root of visible window (used to display title in OS task bar)
16259 if (node->VisibleWindow)
16260 if (is_focused || root_node->VisibleWindow == NULL)
16261 root_node->VisibleWindow = node->VisibleWindow;
16262
16263 // Close button (after VisibleWindow was updated)
16264 // Note that VisibleWindow may have been overrided by CTRL+Tabbing, so VisibleWindow->TabId may be != from tab_bar->SelectedTabId
16265 const bool close_button_is_enabled = node->HasCloseButton && node->VisibleWindow && node->VisibleWindow->HasCloseButton;
16266 const bool close_button_is_visible = node->HasCloseButton;
16267 //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)
16268 if (close_button_is_visible)
16269 {
16270 if (!close_button_is_enabled)
16271 {
16272 PushItemFlag(option: ImGuiItemFlags_Disabled, enabled: true);
16273 PushStyleColor(idx: ImGuiCol_Text, col: style.Colors[ImGuiCol_Text] * ImVec4(1.0f,1.0f,1.0f,0.4f));
16274 }
16275 if (CloseButton(id: host_window->GetID(str: "#CLOSE"), pos: close_button_pos))
16276 {
16277 node->WantCloseAll = true;
16278 for (int n = 0; n < tab_bar->Tabs.Size; n++)
16279 TabBarCloseTab(tab_bar, tab: &tab_bar->Tabs[n]);
16280 }
16281 //if (IsItemActive())
16282 // focus_tab_id = tab_bar->SelectedTabId;
16283 if (!close_button_is_enabled)
16284 {
16285 PopStyleColor();
16286 PopItemFlag();
16287 }
16288 }
16289
16290 // When clicking on the title bar outside of tabs, we still focus the selected tab for that node
16291 // FIXME: TabItem use AllowItemOverlap so we manually perform a more specific test for now (hovered || held)
16292 ImGuiID title_bar_id = host_window->GetID(str: "#TITLEBAR");
16293 if (g.HoveredId == 0 || g.HoveredId == title_bar_id || g.ActiveId == title_bar_id)
16294 {
16295 bool held;
16296 ButtonBehavior(bb: title_bar_rect, id: title_bar_id, NULL, out_held: &held, flags: ImGuiButtonFlags_AllowItemOverlap);
16297 if (g.HoveredId == title_bar_id)
16298 {
16299 // ImGuiButtonFlags_AllowItemOverlap + SetItemAllowOverlap() required for appending into dock node tab bar,
16300 // otherwise dragging window will steal HoveredId and amended tabs cannot get them.
16301 g.LastItemData.ID = title_bar_id;
16302 SetItemAllowOverlap();
16303 }
16304 if (held)
16305 {
16306 if (IsMouseClicked(button: 0))
16307 focus_tab_id = tab_bar->SelectedTabId;
16308
16309 // Forward moving request to selected window
16310 if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id: tab_bar->SelectedTabId))
16311 StartMouseMovingWindowOrNode(window: tab->Window ? tab->Window : node->HostWindow, node, undock_floating_node: false);
16312 }
16313 }
16314
16315 // Forward focus from host node to selected window
16316 //if (is_focused && g.NavWindow == host_window && !g.NavWindowingTarget)
16317 // focus_tab_id = tab_bar->SelectedTabId;
16318
16319 // When clicked on a tab we requested focus to the docked child
16320 // This overrides the value set by "forward focus from host node to selected window".
16321 if (tab_bar->NextSelectedTabId)
16322 focus_tab_id = tab_bar->NextSelectedTabId;
16323
16324 // Apply navigation focus
16325 if (focus_tab_id != 0)
16326 if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_id: focus_tab_id))
16327 if (tab->Window)
16328 {
16329 FocusWindow(window: tab->Window);
16330 NavInitWindow(window: tab->Window, force_reinit: false);
16331 }
16332
16333 EndTabBar();
16334 PopID();
16335
16336 // Restore SkipItems flag
16337 if (!node->IsDockSpace())
16338 {
16339 host_window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
16340 host_window->SkipItems = backup_skip_item;
16341 }
16342}
16343
16344static void ImGui::DockNodeAddTabBar(ImGuiDockNode* node)
16345{
16346 IM_ASSERT(node->TabBar == NULL);
16347 node->TabBar = IM_NEW(ImGuiTabBar);
16348}
16349
16350static void ImGui::DockNodeRemoveTabBar(ImGuiDockNode* node)
16351{
16352 if (node->TabBar == NULL)
16353 return;
16354 IM_DELETE(p: node->TabBar);
16355 node->TabBar = NULL;
16356}
16357
16358static bool DockNodeIsDropAllowedOne(ImGuiWindow* payload, ImGuiWindow* host_window)
16359{
16360 if (host_window->DockNodeAsHost && host_window->DockNodeAsHost->IsDockSpace() && payload->BeginOrderWithinContext < host_window->BeginOrderWithinContext)
16361 return false;
16362
16363 ImGuiWindowClass* host_class = host_window->DockNodeAsHost ? &host_window->DockNodeAsHost->WindowClass : &host_window->WindowClass;
16364 ImGuiWindowClass* payload_class = &payload->WindowClass;
16365 if (host_class->ClassId != payload_class->ClassId)
16366 {
16367 if (host_class->ClassId != 0 && host_class->DockingAllowUnclassed && payload_class->ClassId == 0)
16368 return true;
16369 if (payload_class->ClassId != 0 && payload_class->DockingAllowUnclassed && host_class->ClassId == 0)
16370 return true;
16371 return false;
16372 }
16373
16374 // Prevent docking any window created above a popup
16375 // Technically we should support it (e.g. in the case of a long-lived modal window that had fancy docking features),
16376 // by e.g. adding a 'if (!ImGui::IsWindowWithinBeginStackOf(host_window, popup_window))' test.
16377 // But it would requires more work on our end because the dock host windows is technically created in NewFrame()
16378 // and our ->ParentXXX and ->RootXXX pointers inside windows are currently mislading or lacking.
16379 ImGuiContext& g = *GImGui;
16380 for (int i = g.OpenPopupStack.Size - 1; i >= 0; i--)
16381 if (ImGuiWindow* popup_window = g.OpenPopupStack[i].Window)
16382 if (ImGui::IsWindowWithinBeginStackOf(window: payload, potential_parent: popup_window)) // Payload is created from within a popup begin stack.
16383 return false;
16384
16385 return true;
16386}
16387
16388static bool ImGui::DockNodeIsDropAllowed(ImGuiWindow* host_window, ImGuiWindow* root_payload)
16389{
16390 if (root_payload->DockNodeAsHost && root_payload->DockNodeAsHost->IsSplitNode()) // FIXME-DOCK: Missing filtering
16391 return true;
16392
16393 const int payload_count = root_payload->DockNodeAsHost ? root_payload->DockNodeAsHost->Windows.Size : 1;
16394 for (int payload_n = 0; payload_n < payload_count; payload_n++)
16395 {
16396 ImGuiWindow* payload = root_payload->DockNodeAsHost ? root_payload->DockNodeAsHost->Windows[payload_n] : root_payload;
16397 if (DockNodeIsDropAllowedOne(payload, host_window))
16398 return true;
16399 }
16400 return false;
16401}
16402
16403// window menu button == collapse button when not in a dock node.
16404// FIXME: This is similar to RenderWindowTitleBarContents(), may want to share code.
16405static 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)
16406{
16407 ImGuiContext& g = *GImGui;
16408 ImGuiStyle& style = g.Style;
16409
16410 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);
16411 if (out_title_rect) { *out_title_rect = r; }
16412
16413 r.Min.x += style.WindowBorderSize;
16414 r.Max.x -= style.WindowBorderSize;
16415
16416 float button_sz = g.FontSize;
16417
16418 ImVec2 window_menu_button_pos = r.Min;
16419 r.Min.x += style.FramePadding.x;
16420 r.Max.x -= style.FramePadding.x;
16421 if (node->HasCloseButton)
16422 {
16423 r.Max.x -= button_sz;
16424 if (out_close_button_pos) *out_close_button_pos = ImVec2(r.Max.x - style.FramePadding.x, r.Min.y);
16425 }
16426 if (node->HasWindowMenuButton && style.WindowMenuButtonPosition == ImGuiDir_Left)
16427 {
16428 r.Min.x += button_sz + style.ItemInnerSpacing.x;
16429 }
16430 else if (node->HasWindowMenuButton && style.WindowMenuButtonPosition == ImGuiDir_Right)
16431 {
16432 r.Max.x -= button_sz + style.FramePadding.x;
16433 window_menu_button_pos = ImVec2(r.Max.x, r.Min.y);
16434 }
16435 if (out_tab_bar_rect) { *out_tab_bar_rect = r; }
16436 if (out_window_menu_button_pos) { *out_window_menu_button_pos = window_menu_button_pos; }
16437}
16438
16439void ImGui::DockNodeCalcSplitRects(ImVec2& pos_old, ImVec2& size_old, ImVec2& pos_new, ImVec2& size_new, ImGuiDir dir, ImVec2 size_new_desired)
16440{
16441 ImGuiContext& g = *GImGui;
16442 const float dock_spacing = g.Style.ItemInnerSpacing.x;
16443 const ImGuiAxis axis = (dir == ImGuiDir_Left || dir == ImGuiDir_Right) ? ImGuiAxis_X : ImGuiAxis_Y;
16444 pos_new[axis ^ 1] = pos_old[axis ^ 1];
16445 size_new[axis ^ 1] = size_old[axis ^ 1];
16446
16447 // Distribute size on given axis (with a desired size or equally)
16448 const float w_avail = size_old[axis] - dock_spacing;
16449 if (size_new_desired[axis] > 0.0f && size_new_desired[axis] <= w_avail * 0.5f)
16450 {
16451 size_new[axis] = size_new_desired[axis];
16452 size_old[axis] = IM_FLOOR(w_avail - size_new[axis]);
16453 }
16454 else
16455 {
16456 size_new[axis] = IM_FLOOR(w_avail * 0.5f);
16457 size_old[axis] = IM_FLOOR(w_avail - size_new[axis]);
16458 }
16459
16460 // Position each node
16461 if (dir == ImGuiDir_Right || dir == ImGuiDir_Down)
16462 {
16463 pos_new[axis] = pos_old[axis] + size_old[axis] + dock_spacing;
16464 }
16465 else if (dir == ImGuiDir_Left || dir == ImGuiDir_Up)
16466 {
16467 pos_new[axis] = pos_old[axis];
16468 pos_old[axis] = pos_new[axis] + size_new[axis] + dock_spacing;
16469 }
16470}
16471
16472// Retrieve the drop rectangles for a given direction or for the center + perform hit testing.
16473bool ImGui::DockNodeCalcDropRectsAndTestMousePos(const ImRect& parent, ImGuiDir dir, ImRect& out_r, bool outer_docking, ImVec2* test_mouse_pos)
16474{
16475 ImGuiContext& g = *GImGui;
16476
16477 const float parent_smaller_axis = ImMin(lhs: parent.GetWidth(), rhs: parent.GetHeight());
16478 const float hs_for_central_nodes = ImMin(lhs: g.FontSize * 1.5f, rhs: ImMax(lhs: g.FontSize * 0.5f, rhs: parent_smaller_axis / 8.0f));
16479 float hs_w; // Half-size, longer axis
16480 float hs_h; // Half-size, smaller axis
16481 ImVec2 off; // Distance from edge or center
16482 if (outer_docking)
16483 {
16484 //hs_w = ImFloor(ImClamp(parent_smaller_axis - hs_for_central_nodes * 4.0f, g.FontSize * 0.5f, g.FontSize * 8.0f));
16485 //hs_h = ImFloor(hs_w * 0.15f);
16486 //off = ImVec2(ImFloor(parent.GetWidth() * 0.5f - GetFrameHeightWithSpacing() * 1.4f - hs_h), ImFloor(parent.GetHeight() * 0.5f - GetFrameHeightWithSpacing() * 1.4f - hs_h));
16487 hs_w = ImFloor(f: hs_for_central_nodes * 1.50f);
16488 hs_h = ImFloor(f: hs_for_central_nodes * 0.80f);
16489 off = ImVec2(ImFloor(f: parent.GetWidth() * 0.5f - hs_h), ImFloor(f: parent.GetHeight() * 0.5f - hs_h));
16490 }
16491 else
16492 {
16493 hs_w = ImFloor(f: hs_for_central_nodes);
16494 hs_h = ImFloor(f: hs_for_central_nodes * 0.90f);
16495 off = ImVec2(ImFloor(f: hs_w * 2.40f), ImFloor(f: hs_w * 2.40f));
16496 }
16497
16498 ImVec2 c = ImFloor(v: parent.GetCenter());
16499 if (dir == ImGuiDir_None) { out_r = ImRect(c.x - hs_w, c.y - hs_w, c.x + hs_w, c.y + hs_w); }
16500 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); }
16501 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); }
16502 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); }
16503 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); }
16504
16505 if (test_mouse_pos == NULL)
16506 return false;
16507
16508 ImRect hit_r = out_r;
16509 if (!outer_docking)
16510 {
16511 // Custom hit testing for the 5-way selection, designed to reduce flickering when moving diagonally between sides
16512 hit_r.Expand(amount: ImFloor(f: hs_w * 0.30f));
16513 ImVec2 mouse_delta = (*test_mouse_pos - c);
16514 float mouse_delta_len2 = ImLengthSqr(lhs: mouse_delta);
16515 float r_threshold_center = hs_w * 1.4f;
16516 float r_threshold_sides = hs_w * (1.4f + 1.2f);
16517 if (mouse_delta_len2 < r_threshold_center * r_threshold_center)
16518 return (dir == ImGuiDir_None);
16519 if (mouse_delta_len2 < r_threshold_sides * r_threshold_sides)
16520 return (dir == ImGetDirQuadrantFromDelta(dx: mouse_delta.x, dy: mouse_delta.y));
16521 }
16522 return hit_r.Contains(p: *test_mouse_pos);
16523}
16524
16525// host_node may be NULL if the window doesn't have a DockNode already.
16526// FIXME-DOCK: This is misnamed since it's also doing the filtering.
16527static void ImGui::DockNodePreviewDockSetup(ImGuiWindow* host_window, ImGuiDockNode* host_node, ImGuiWindow* payload_window, ImGuiDockNode* payload_node, ImGuiDockPreviewData* data, bool is_explicit_target, bool is_outer_docking)
16528{
16529 ImGuiContext& g = *GImGui;
16530
16531 // There is an edge case when docking into a dockspace which only has inactive nodes.
16532 // In this case DockNodeTreeFindNodeByPos() will have selected a leaf node which is inactive.
16533 // Because the inactive leaf node doesn't have proper pos/size yet, we'll use the root node as reference.
16534 if (payload_node == NULL)
16535 payload_node = payload_window->DockNodeAsHost;
16536 ImGuiDockNode* ref_node_for_rect = (host_node && !host_node->IsVisible) ? DockNodeGetRootNode(node: host_node) : host_node;
16537 if (ref_node_for_rect)
16538 IM_ASSERT(ref_node_for_rect->IsVisible == true);
16539
16540 // Filter, figure out where we are allowed to dock
16541 ImGuiDockNodeFlags src_node_flags = payload_node ? payload_node->MergedFlags : payload_window->WindowClass.DockNodeFlagsOverrideSet;
16542 ImGuiDockNodeFlags dst_node_flags = host_node ? host_node->MergedFlags : host_window->WindowClass.DockNodeFlagsOverrideSet;
16543 data->IsCenterAvailable = true;
16544 if (is_outer_docking)
16545 data->IsCenterAvailable = false;
16546 else if (dst_node_flags & ImGuiDockNodeFlags_NoDocking)
16547 data->IsCenterAvailable = false;
16548 else if (host_node && (dst_node_flags & ImGuiDockNodeFlags_NoDockingInCentralNode) && host_node->IsCentralNode())
16549 data->IsCenterAvailable = false;
16550 else if ((!host_node || !host_node->IsEmpty()) && payload_node && payload_node->IsSplitNode() && (payload_node->OnlyNodeWithWindows == NULL)) // Is _visibly_ split?
16551 data->IsCenterAvailable = false;
16552 else if (dst_node_flags & ImGuiDockNodeFlags_NoDockingOverMe)
16553 data->IsCenterAvailable = false;
16554 else if ((src_node_flags & ImGuiDockNodeFlags_NoDockingOverOther) && (!host_node || !host_node->IsEmpty()))
16555 data->IsCenterAvailable = false;
16556 else if ((src_node_flags & ImGuiDockNodeFlags_NoDockingOverEmpty) && host_node && host_node->IsEmpty())
16557 data->IsCenterAvailable = false;
16558
16559 data->IsSidesAvailable = true;
16560 if ((dst_node_flags & ImGuiDockNodeFlags_NoSplit) || g.IO.ConfigDockingNoSplit)
16561 data->IsSidesAvailable = false;
16562 else if (!is_outer_docking && host_node && host_node->ParentNode == NULL && host_node->IsCentralNode())
16563 data->IsSidesAvailable = false;
16564 else if ((dst_node_flags & ImGuiDockNodeFlags_NoDockingSplitMe) || (src_node_flags & ImGuiDockNodeFlags_NoDockingSplitOther))
16565 data->IsSidesAvailable = false;
16566
16567 // Build a tentative future node (reuse same structure because it is practical. Shape will be readjusted when previewing a split)
16568 data->FutureNode.HasCloseButton = (host_node ? host_node->HasCloseButton : host_window->HasCloseButton) || (payload_window->HasCloseButton);
16569 data->FutureNode.HasWindowMenuButton = host_node ? true : ((host_window->Flags & ImGuiWindowFlags_NoCollapse) == 0);
16570 data->FutureNode.Pos = ref_node_for_rect ? ref_node_for_rect->Pos : host_window->Pos;
16571 data->FutureNode.Size = ref_node_for_rect ? ref_node_for_rect->Size : host_window->Size;
16572
16573 // Calculate drop shapes geometry for allowed splitting directions
16574 IM_ASSERT(ImGuiDir_None == -1);
16575 data->SplitNode = host_node;
16576 data->SplitDir = ImGuiDir_None;
16577 data->IsSplitDirExplicit = false;
16578 if (!host_window->Collapsed)
16579 for (int dir = ImGuiDir_None; dir < ImGuiDir_COUNT; dir++)
16580 {
16581 if (dir == ImGuiDir_None && !data->IsCenterAvailable)
16582 continue;
16583 if (dir != ImGuiDir_None && !data->IsSidesAvailable)
16584 continue;
16585 if (DockNodeCalcDropRectsAndTestMousePos(parent: data->FutureNode.Rect(), dir: (ImGuiDir)dir, out_r&: data->DropRectsDraw[dir+1], outer_docking: is_outer_docking, test_mouse_pos: &g.IO.MousePos))
16586 {
16587 data->SplitDir = (ImGuiDir)dir;
16588 data->IsSplitDirExplicit = true;
16589 }
16590 }
16591
16592 // When docking without holding Shift, we only allow and preview docking when hovering over a drop rect or over the title bar
16593 data->IsDropAllowed = (data->SplitDir != ImGuiDir_None) || (data->IsCenterAvailable);
16594 if (!is_explicit_target && !data->IsSplitDirExplicit && !g.IO.ConfigDockingWithShift)
16595 data->IsDropAllowed = false;
16596
16597 // Calculate split area
16598 data->SplitRatio = 0.0f;
16599 if (data->SplitDir != ImGuiDir_None)
16600 {
16601 ImGuiDir split_dir = data->SplitDir;
16602 ImGuiAxis split_axis = (split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Right) ? ImGuiAxis_X : ImGuiAxis_Y;
16603 ImVec2 pos_new, pos_old = data->FutureNode.Pos;
16604 ImVec2 size_new, size_old = data->FutureNode.Size;
16605 DockNodeCalcSplitRects(pos_old, size_old, pos_new, size_new, dir: split_dir, size_new_desired: payload_window->Size);
16606
16607 // Calculate split ratio so we can pass it down the docking request
16608 float split_ratio = ImSaturate(f: size_new[split_axis] / data->FutureNode.Size[split_axis]);
16609 data->FutureNode.Pos = pos_new;
16610 data->FutureNode.Size = size_new;
16611 data->SplitRatio = (split_dir == ImGuiDir_Right || split_dir == ImGuiDir_Down) ? (1.0f - split_ratio) : (split_ratio);
16612 }
16613}
16614
16615static void ImGui::DockNodePreviewDockRender(ImGuiWindow* host_window, ImGuiDockNode* host_node, ImGuiWindow* root_payload, const ImGuiDockPreviewData* data)
16616{
16617 ImGuiContext& g = *GImGui;
16618 IM_ASSERT(g.CurrentWindow == host_window); // Because we rely on font size to calculate tab sizes
16619
16620 // With this option, we only display the preview on the target viewport, and the payload viewport is made transparent.
16621 // To compensate for the single layer obstructed by the payload, we'll increase the alpha of the preview nodes.
16622 const bool is_transparent_payload = g.IO.ConfigDockingTransparentPayload;
16623
16624 // In case the two windows involved are on different viewports, we will draw the overlay on each of them.
16625 int overlay_draw_lists_count = 0;
16626 ImDrawList* overlay_draw_lists[2];
16627 overlay_draw_lists[overlay_draw_lists_count++] = GetForegroundDrawList(viewport: host_window->Viewport);
16628 if (host_window->Viewport != root_payload->Viewport && !is_transparent_payload)
16629 overlay_draw_lists[overlay_draw_lists_count++] = GetForegroundDrawList(viewport: root_payload->Viewport);
16630
16631 // Draw main preview rectangle
16632 const ImU32 overlay_col_main = GetColorU32(idx: ImGuiCol_DockingPreview, alpha_mul: is_transparent_payload ? 0.60f : 0.40f);
16633 const ImU32 overlay_col_drop = GetColorU32(idx: ImGuiCol_DockingPreview, alpha_mul: is_transparent_payload ? 0.90f : 0.70f);
16634 const ImU32 overlay_col_drop_hovered = GetColorU32(idx: ImGuiCol_DockingPreview, alpha_mul: is_transparent_payload ? 1.20f : 1.00f);
16635 const ImU32 overlay_col_lines = GetColorU32(idx: ImGuiCol_NavWindowingHighlight, alpha_mul: is_transparent_payload ? 0.80f : 0.60f);
16636
16637 // Display area preview
16638 const bool can_preview_tabs = (root_payload->DockNodeAsHost == NULL || root_payload->DockNodeAsHost->Windows.Size > 0);
16639 if (data->IsDropAllowed)
16640 {
16641 ImRect overlay_rect = data->FutureNode.Rect();
16642 if (data->SplitDir == ImGuiDir_None && can_preview_tabs)
16643 overlay_rect.Min.y += GetFrameHeight();
16644 if (data->SplitDir != ImGuiDir_None || data->IsCenterAvailable)
16645 for (int overlay_n = 0; overlay_n < overlay_draw_lists_count; overlay_n++)
16646 overlay_draw_lists[overlay_n]->AddRectFilled(p_min: overlay_rect.Min, p_max: overlay_rect.Max, col: overlay_col_main, rounding: host_window->WindowRounding, flags: CalcRoundingFlagsForRectInRect(r_in: overlay_rect, r_outer: host_window->Rect(), threshold: DOCKING_SPLITTER_SIZE));
16647 }
16648
16649 // Display tab shape/label preview unless we are splitting node (it generally makes the situation harder to read)
16650 if (data->IsDropAllowed && can_preview_tabs && data->SplitDir == ImGuiDir_None && data->IsCenterAvailable)
16651 {
16652 // Compute target tab bar geometry so we can locate our preview tabs
16653 ImRect tab_bar_rect;
16654 DockNodeCalcTabBarLayout(node: &data->FutureNode, NULL, out_tab_bar_rect: &tab_bar_rect, NULL, NULL);
16655 ImVec2 tab_pos = tab_bar_rect.Min;
16656 if (host_node && host_node->TabBar)
16657 {
16658 if (!host_node->IsHiddenTabBar() && !host_node->IsNoTabBar())
16659 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.
16660 else
16661 tab_pos.x += g.Style.ItemInnerSpacing.x + TabItemCalcSize(window: host_node->Windows[0]).x;
16662 }
16663 else if (!(host_window->Flags & ImGuiWindowFlags_DockNodeHost))
16664 {
16665 tab_pos.x += g.Style.ItemInnerSpacing.x + TabItemCalcSize(window: host_window).x; // Account for slight offset which will be added when changing from title bar to tab bar
16666 }
16667
16668 // Draw tab shape/label preview (payload may be a loose window or a host window carrying multiple tabbed windows)
16669 if (root_payload->DockNodeAsHost)
16670 IM_ASSERT(root_payload->DockNodeAsHost->Windows.Size <= root_payload->DockNodeAsHost->TabBar->Tabs.Size);
16671 ImGuiTabBar* tab_bar_with_payload = root_payload->DockNodeAsHost ? root_payload->DockNodeAsHost->TabBar : NULL;
16672 const int payload_count = tab_bar_with_payload ? tab_bar_with_payload->Tabs.Size : 1;
16673 for (int payload_n = 0; payload_n < payload_count; payload_n++)
16674 {
16675 // DockNode's TabBar may have non-window Tabs manually appended by user
16676 ImGuiWindow* payload_window = tab_bar_with_payload ? tab_bar_with_payload->Tabs[payload_n].Window : root_payload;
16677 if (tab_bar_with_payload && payload_window == NULL)
16678 continue;
16679 if (!DockNodeIsDropAllowedOne(payload: payload_window, host_window))
16680 continue;
16681
16682 // Calculate the tab bounding box for each payload window
16683 ImVec2 tab_size = TabItemCalcSize(window: payload_window);
16684 ImRect tab_bb(tab_pos.x, tab_pos.y, tab_pos.x + tab_size.x, tab_pos.y + tab_size.y);
16685 tab_pos.x += tab_size.x + g.Style.ItemInnerSpacing.x;
16686 const ImU32 overlay_col_text = GetColorU32(col: payload_window->DockStyle.Colors[ImGuiWindowDockStyleCol_Text]);
16687 const ImU32 overlay_col_tabs = GetColorU32(col: payload_window->DockStyle.Colors[ImGuiWindowDockStyleCol_TabActive]);
16688 PushStyleColor(idx: ImGuiCol_Text, col: overlay_col_text);
16689 for (int overlay_n = 0; overlay_n < overlay_draw_lists_count; overlay_n++)
16690 {
16691 ImGuiTabItemFlags tab_flags = ImGuiTabItemFlags_Preview | ((payload_window->Flags & ImGuiWindowFlags_UnsavedDocument) ? ImGuiTabItemFlags_UnsavedDocument : 0);
16692 if (!tab_bar_rect.Contains(r: tab_bb))
16693 overlay_draw_lists[overlay_n]->PushClipRect(clip_rect_min: tab_bar_rect.Min, clip_rect_max: tab_bar_rect.Max);
16694 TabItemBackground(draw_list: overlay_draw_lists[overlay_n], bb: tab_bb, flags: tab_flags, col: overlay_col_tabs);
16695 TabItemLabelAndCloseButton(draw_list: overlay_draw_lists[overlay_n], bb: tab_bb, flags: tab_flags, frame_padding: g.Style.FramePadding, label: payload_window->Name, tab_id: 0, close_button_id: 0, is_contents_visible: false, NULL, NULL);
16696 if (!tab_bar_rect.Contains(r: tab_bb))
16697 overlay_draw_lists[overlay_n]->PopClipRect();
16698 }
16699 PopStyleColor();
16700 }
16701 }
16702
16703 // Display drop boxes
16704 const float overlay_rounding = ImMax(lhs: 3.0f, rhs: g.Style.FrameRounding);
16705 for (int dir = ImGuiDir_None; dir < ImGuiDir_COUNT; dir++)
16706 {
16707 if (!data->DropRectsDraw[dir + 1].IsInverted())
16708 {
16709 ImRect draw_r = data->DropRectsDraw[dir + 1];
16710 ImRect draw_r_in = draw_r;
16711 draw_r_in.Expand(amount: -2.0f);
16712 ImU32 overlay_col = (data->SplitDir == (ImGuiDir)dir && data->IsSplitDirExplicit) ? overlay_col_drop_hovered : overlay_col_drop;
16713 for (int overlay_n = 0; overlay_n < overlay_draw_lists_count; overlay_n++)
16714 {
16715 ImVec2 center = ImFloor(v: draw_r_in.GetCenter());
16716 overlay_draw_lists[overlay_n]->AddRectFilled(p_min: draw_r.Min, p_max: draw_r.Max, col: overlay_col, rounding: overlay_rounding);
16717 overlay_draw_lists[overlay_n]->AddRect(p_min: draw_r_in.Min, p_max: draw_r_in.Max, col: overlay_col_lines, rounding: overlay_rounding);
16718 if (dir == ImGuiDir_Left || dir == ImGuiDir_Right)
16719 overlay_draw_lists[overlay_n]->AddLine(p1: ImVec2(center.x, draw_r_in.Min.y), p2: ImVec2(center.x, draw_r_in.Max.y), col: overlay_col_lines);
16720 if (dir == ImGuiDir_Up || dir == ImGuiDir_Down)
16721 overlay_draw_lists[overlay_n]->AddLine(p1: ImVec2(draw_r_in.Min.x, center.y), p2: ImVec2(draw_r_in.Max.x, center.y), col: overlay_col_lines);
16722 }
16723 }
16724
16725 // Stop after ImGuiDir_None
16726 if ((host_node && (host_node->MergedFlags & ImGuiDockNodeFlags_NoSplit)) || g.IO.ConfigDockingNoSplit)
16727 return;
16728 }
16729}
16730
16731//-----------------------------------------------------------------------------
16732// Docking: ImGuiDockNode Tree manipulation functions
16733//-----------------------------------------------------------------------------
16734// - DockNodeTreeSplit()
16735// - DockNodeTreeMerge()
16736// - DockNodeTreeUpdatePosSize()
16737// - DockNodeTreeUpdateSplitterFindTouchingNode()
16738// - DockNodeTreeUpdateSplitter()
16739// - DockNodeTreeFindFallbackLeafNode()
16740// - DockNodeTreeFindNodeByPos()
16741//-----------------------------------------------------------------------------
16742
16743void ImGui::DockNodeTreeSplit(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImGuiAxis split_axis, int split_inheritor_child_idx, float split_ratio, ImGuiDockNode* new_node)
16744{
16745 ImGuiContext& g = *GImGui;
16746 IM_ASSERT(split_axis != ImGuiAxis_None);
16747
16748 ImGuiDockNode* child_0 = (new_node && split_inheritor_child_idx != 0) ? new_node : DockContextAddNode(ctx, id: 0);
16749 child_0->ParentNode = parent_node;
16750
16751 ImGuiDockNode* child_1 = (new_node && split_inheritor_child_idx != 1) ? new_node : DockContextAddNode(ctx, id: 0);
16752 child_1->ParentNode = parent_node;
16753
16754 ImGuiDockNode* child_inheritor = (split_inheritor_child_idx == 0) ? child_0 : child_1;
16755 DockNodeMoveChildNodes(dst_node: child_inheritor, src_node: parent_node);
16756 parent_node->ChildNodes[0] = child_0;
16757 parent_node->ChildNodes[1] = child_1;
16758 parent_node->ChildNodes[split_inheritor_child_idx]->VisibleWindow = parent_node->VisibleWindow;
16759 parent_node->SplitAxis = split_axis;
16760 parent_node->VisibleWindow = NULL;
16761 parent_node->AuthorityForPos = parent_node->AuthorityForSize = ImGuiDataAuthority_DockNode;
16762
16763 float size_avail = (parent_node->Size[split_axis] - DOCKING_SPLITTER_SIZE);
16764 size_avail = ImMax(lhs: size_avail, rhs: g.Style.WindowMinSize[split_axis] * 2.0f);
16765 IM_ASSERT(size_avail > 0.0f); // If you created a node manually with DockBuilderAddNode(), you need to also call DockBuilderSetNodeSize() before splitting.
16766 child_0->SizeRef = child_1->SizeRef = parent_node->Size;
16767 child_0->SizeRef[split_axis] = ImFloor(f: size_avail * split_ratio);
16768 child_1->SizeRef[split_axis] = ImFloor(f: size_avail - child_0->SizeRef[split_axis]);
16769
16770 DockNodeMoveWindows(dst_node: parent_node->ChildNodes[split_inheritor_child_idx], src_node: parent_node);
16771 DockSettingsRenameNodeReferences(old_node_id: parent_node->ID, new_node_id: parent_node->ChildNodes[split_inheritor_child_idx]->ID);
16772 DockNodeUpdateHasCentralNodeChild(node: DockNodeGetRootNode(node: parent_node));
16773 DockNodeTreeUpdatePosSize(node: parent_node, pos: parent_node->Pos, size: parent_node->Size);
16774
16775 // Flags transfer (e.g. this is where we transfer the ImGuiDockNodeFlags_CentralNode property)
16776 child_0->SharedFlags = parent_node->SharedFlags & ImGuiDockNodeFlags_SharedFlagsInheritMask_;
16777 child_1->SharedFlags = parent_node->SharedFlags & ImGuiDockNodeFlags_SharedFlagsInheritMask_;
16778 child_inheritor->LocalFlags = parent_node->LocalFlags & ImGuiDockNodeFlags_LocalFlagsTransferMask_;
16779 parent_node->LocalFlags &= ~ImGuiDockNodeFlags_LocalFlagsTransferMask_;
16780 child_0->UpdateMergedFlags();
16781 child_1->UpdateMergedFlags();
16782 parent_node->UpdateMergedFlags();
16783 if (child_inheritor->IsCentralNode())
16784 DockNodeGetRootNode(node: parent_node)->CentralNode = child_inheritor;
16785}
16786
16787void ImGui::DockNodeTreeMerge(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImGuiDockNode* merge_lead_child)
16788{
16789 // When called from DockContextProcessUndockNode() it is possible that one of the child is NULL.
16790 ImGuiContext& g = *GImGui;
16791 ImGuiDockNode* child_0 = parent_node->ChildNodes[0];
16792 ImGuiDockNode* child_1 = parent_node->ChildNodes[1];
16793 IM_ASSERT(child_0 || child_1);
16794 IM_ASSERT(merge_lead_child == child_0 || merge_lead_child == child_1);
16795 if ((child_0 && child_0->Windows.Size > 0) || (child_1 && child_1->Windows.Size > 0))
16796 {
16797 IM_ASSERT(parent_node->TabBar == NULL);
16798 IM_ASSERT(parent_node->Windows.Size == 0);
16799 }
16800 IMGUI_DEBUG_LOG_DOCKING("[docking] DockNodeTreeMerge: 0x%08X + 0x%08X back into parent 0x%08X\n", child_0 ? child_0->ID : 0, child_1 ? child_1->ID : 0, parent_node->ID);
16801
16802 ImVec2 backup_last_explicit_size = parent_node->SizeRef;
16803 DockNodeMoveChildNodes(dst_node: parent_node, src_node: merge_lead_child);
16804 if (child_0)
16805 {
16806 DockNodeMoveWindows(dst_node: parent_node, src_node: child_0); // Generally only 1 of the 2 child node will have windows
16807 DockSettingsRenameNodeReferences(old_node_id: child_0->ID, new_node_id: parent_node->ID);
16808 }
16809 if (child_1)
16810 {
16811 DockNodeMoveWindows(dst_node: parent_node, src_node: child_1);
16812 DockSettingsRenameNodeReferences(old_node_id: child_1->ID, new_node_id: parent_node->ID);
16813 }
16814 DockNodeApplyPosSizeToWindows(node: parent_node);
16815 parent_node->AuthorityForPos = parent_node->AuthorityForSize = parent_node->AuthorityForViewport = ImGuiDataAuthority_Auto;
16816 parent_node->VisibleWindow = merge_lead_child->VisibleWindow;
16817 parent_node->SizeRef = backup_last_explicit_size;
16818
16819 // Flags transfer
16820 parent_node->LocalFlags &= ~ImGuiDockNodeFlags_LocalFlagsTransferMask_; // Preserve Dockspace flag
16821 parent_node->LocalFlags |= (child_0 ? child_0->LocalFlags : 0) & ImGuiDockNodeFlags_LocalFlagsTransferMask_;
16822 parent_node->LocalFlags |= (child_1 ? child_1->LocalFlags : 0) & ImGuiDockNodeFlags_LocalFlagsTransferMask_;
16823 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
16824 parent_node->UpdateMergedFlags();
16825
16826 if (child_0)
16827 {
16828 ctx->DockContext.Nodes.SetVoidPtr(key: child_0->ID, NULL);
16829 IM_DELETE(p: child_0);
16830 }
16831 if (child_1)
16832 {
16833 ctx->DockContext.Nodes.SetVoidPtr(key: child_1->ID, NULL);
16834 IM_DELETE(p: child_1);
16835 }
16836}
16837
16838// Update Pos/Size for a node hierarchy (don't affect child Windows yet)
16839// (Depth-first, Pre-Order)
16840void ImGui::DockNodeTreeUpdatePosSize(ImGuiDockNode* node, ImVec2 pos, ImVec2 size, ImGuiDockNode* only_write_to_single_node)
16841{
16842 // During the regular dock node update we write to all nodes.
16843 // 'only_write_to_single_node' is only set when turning a node visible mid-frame and we need its size right-away.
16844 const bool write_to_node = only_write_to_single_node == NULL || only_write_to_single_node == node;
16845 if (write_to_node)
16846 {
16847 node->Pos = pos;
16848 node->Size = size;
16849 }
16850
16851 if (node->IsLeafNode())
16852 return;
16853
16854 ImGuiDockNode* child_0 = node->ChildNodes[0];
16855 ImGuiDockNode* child_1 = node->ChildNodes[1];
16856 ImVec2 child_0_pos = pos, child_1_pos = pos;
16857 ImVec2 child_0_size = size, child_1_size = size;
16858
16859 const bool child_0_is_toward_single_node = (only_write_to_single_node != NULL && DockNodeIsInHierarchyOf(node: only_write_to_single_node, parent: child_0));
16860 const bool child_1_is_toward_single_node = (only_write_to_single_node != NULL && DockNodeIsInHierarchyOf(node: only_write_to_single_node, parent: child_1));
16861 const bool child_0_is_or_will_be_visible = child_0->IsVisible || child_0_is_toward_single_node;
16862 const bool child_1_is_or_will_be_visible = child_1->IsVisible || child_1_is_toward_single_node;
16863
16864 if (child_0_is_or_will_be_visible && child_1_is_or_will_be_visible)
16865 {
16866 ImGuiContext& g = *GImGui;
16867 const float spacing = DOCKING_SPLITTER_SIZE;
16868 const ImGuiAxis axis = (ImGuiAxis)node->SplitAxis;
16869 const float size_avail = ImMax(lhs: size[axis] - spacing, rhs: 0.0f);
16870
16871 // Size allocation policy
16872 // 1) The first 0..WindowMinSize[axis]*2 are allocated evenly to both windows.
16873 const float size_min_each = ImFloor(f: ImMin(lhs: size_avail, rhs: g.Style.WindowMinSize[axis] * 2.0f) * 0.5f);
16874
16875 // FIXME: Blocks 2) and 3) are essentially doing nearly the same thing.
16876 // Difference are: write-back to SizeRef; application of a minimum size; rounding before ImFloor()
16877 // Clarify and rework differences between Size & SizeRef and purpose of WantLockSizeOnce
16878
16879 // 2) Process locked absolute size (during a splitter resize we preserve the child of nodes not touching the splitter edge)
16880 if (child_0->WantLockSizeOnce && !child_1->WantLockSizeOnce)
16881 {
16882 child_0_size[axis] = child_0->SizeRef[axis] = ImMin(lhs: size_avail - 1.0f, rhs: child_0->Size[axis]);
16883 child_1_size[axis] = child_1->SizeRef[axis] = (size_avail - child_0_size[axis]);
16884 IM_ASSERT(child_0->SizeRef[axis] > 0.0f && child_1->SizeRef[axis] > 0.0f);
16885 }
16886 else if (child_1->WantLockSizeOnce && !child_0->WantLockSizeOnce)
16887 {
16888 child_1_size[axis] = child_1->SizeRef[axis] = ImMin(lhs: size_avail - 1.0f, rhs: child_1->Size[axis]);
16889 child_0_size[axis] = child_0->SizeRef[axis] = (size_avail - child_1_size[axis]);
16890 IM_ASSERT(child_0->SizeRef[axis] > 0.0f && child_1->SizeRef[axis] > 0.0f);
16891 }
16892 else if (child_0->WantLockSizeOnce && child_1->WantLockSizeOnce)
16893 {
16894 // FIXME-DOCK: We cannot honor the requested size, so apply ratio.
16895 // Currently this path will only be taken if code programmatically sets WantLockSizeOnce
16896 float split_ratio = child_0_size[axis] / (child_0_size[axis] + child_1_size[axis]);
16897 child_0_size[axis] = child_0->SizeRef[axis] = ImFloor(f: size_avail * split_ratio);
16898 child_1_size[axis] = child_1->SizeRef[axis] = (size_avail - child_0_size[axis]);
16899 IM_ASSERT(child_0->SizeRef[axis] > 0.0f && child_1->SizeRef[axis] > 0.0f);
16900 }
16901
16902 // 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
16903 else if (child_0->SizeRef[axis] != 0.0f && child_1->HasCentralNodeChild)
16904 {
16905 child_0_size[axis] = ImMin(lhs: size_avail - size_min_each, rhs: child_0->SizeRef[axis]);
16906 child_1_size[axis] = (size_avail - child_0_size[axis]);
16907 }
16908 else if (child_1->SizeRef[axis] != 0.0f && child_0->HasCentralNodeChild)
16909 {
16910 child_1_size[axis] = ImMin(lhs: size_avail - size_min_each, rhs: child_1->SizeRef[axis]);
16911 child_0_size[axis] = (size_avail - child_1_size[axis]);
16912 }
16913 else
16914 {
16915 // 4) Otherwise distribute according to the relative ratio of each SizeRef value
16916 float split_ratio = child_0->SizeRef[axis] / (child_0->SizeRef[axis] + child_1->SizeRef[axis]);
16917 child_0_size[axis] = ImMax(lhs: size_min_each, rhs: ImFloor(f: size_avail * split_ratio + 0.5f));
16918 child_1_size[axis] = (size_avail - child_0_size[axis]);
16919 }
16920
16921 child_1_pos[axis] += spacing + child_0_size[axis];
16922 }
16923
16924 if (only_write_to_single_node == NULL)
16925 child_0->WantLockSizeOnce = child_1->WantLockSizeOnce = false;
16926
16927 const bool child_0_recurse = only_write_to_single_node ? child_0_is_toward_single_node : child_0->IsVisible;
16928 const bool child_1_recurse = only_write_to_single_node ? child_1_is_toward_single_node : child_1->IsVisible;
16929 if (child_0_recurse)
16930 DockNodeTreeUpdatePosSize(node: child_0, pos: child_0_pos, size: child_0_size);
16931 if (child_1_recurse)
16932 DockNodeTreeUpdatePosSize(node: child_1, pos: child_1_pos, size: child_1_size);
16933}
16934
16935static void DockNodeTreeUpdateSplitterFindTouchingNode(ImGuiDockNode* node, ImGuiAxis axis, int side, ImVector<ImGuiDockNode*>* touching_nodes)
16936{
16937 if (node->IsLeafNode())
16938 {
16939 touching_nodes->push_back(v: node);
16940 return;
16941 }
16942 if (node->ChildNodes[0]->IsVisible)
16943 if (node->SplitAxis != axis || side == 0 || !node->ChildNodes[1]->IsVisible)
16944 DockNodeTreeUpdateSplitterFindTouchingNode(node: node->ChildNodes[0], axis, side, touching_nodes);
16945 if (node->ChildNodes[1]->IsVisible)
16946 if (node->SplitAxis != axis || side == 1 || !node->ChildNodes[0]->IsVisible)
16947 DockNodeTreeUpdateSplitterFindTouchingNode(node: node->ChildNodes[1], axis, side, touching_nodes);
16948}
16949
16950// (Depth-First, Pre-Order)
16951void ImGui::DockNodeTreeUpdateSplitter(ImGuiDockNode* node)
16952{
16953 if (node->IsLeafNode())
16954 return;
16955
16956 ImGuiContext& g = *GImGui;
16957
16958 ImGuiDockNode* child_0 = node->ChildNodes[0];
16959 ImGuiDockNode* child_1 = node->ChildNodes[1];
16960 if (child_0->IsVisible && child_1->IsVisible)
16961 {
16962 // Bounding box of the splitter cover the space between both nodes (w = Spacing, h = Size[xy^1] for when splitting horizontally)
16963 const ImGuiAxis axis = (ImGuiAxis)node->SplitAxis;
16964 IM_ASSERT(axis != ImGuiAxis_None);
16965 ImRect bb;
16966 bb.Min = child_0->Pos;
16967 bb.Max = child_1->Pos;
16968 bb.Min[axis] += child_0->Size[axis];
16969 bb.Max[axis ^ 1] += child_1->Size[axis ^ 1];
16970 //if (g.IO.KeyCtrl) GetForegroundDrawList(g.CurrentWindow->Viewport)->AddRect(bb.Min, bb.Max, IM_COL32(255,0,255,255));
16971
16972 const ImGuiDockNodeFlags merged_flags = child_0->MergedFlags | child_1->MergedFlags; // Merged flags for BOTH childs
16973 const ImGuiDockNodeFlags no_resize_axis_flag = (axis == ImGuiAxis_X) ? ImGuiDockNodeFlags_NoResizeX : ImGuiDockNodeFlags_NoResizeY;
16974 if ((merged_flags & ImGuiDockNodeFlags_NoResize) || (merged_flags & no_resize_axis_flag))
16975 {
16976 ImGuiWindow* window = g.CurrentWindow;
16977 window->DrawList->AddRectFilled(p_min: bb.Min, p_max: bb.Max, col: GetColorU32(idx: ImGuiCol_Separator), rounding: g.Style.FrameRounding);
16978 }
16979 else
16980 {
16981 //bb.Min[axis] += 1; // Display a little inward so highlight doesn't connect with nearby tabs on the neighbor node.
16982 //bb.Max[axis] -= 1;
16983 PushID(int_id: node->ID);
16984
16985 // Find resizing limits by gathering list of nodes that are touching the splitter line.
16986 ImVector<ImGuiDockNode*> touching_nodes[2];
16987 float min_size = g.Style.WindowMinSize[axis];
16988 float resize_limits[2];
16989 resize_limits[0] = node->ChildNodes[0]->Pos[axis] + min_size;
16990 resize_limits[1] = node->ChildNodes[1]->Pos[axis] + node->ChildNodes[1]->Size[axis] - min_size;
16991
16992 ImGuiID splitter_id = GetID(str_id: "##Splitter");
16993 if (g.ActiveId == splitter_id) // Only process when splitter is active
16994 {
16995 DockNodeTreeUpdateSplitterFindTouchingNode(node: child_0, axis, side: 1, touching_nodes: &touching_nodes[0]);
16996 DockNodeTreeUpdateSplitterFindTouchingNode(node: child_1, axis, side: 0, touching_nodes: &touching_nodes[1]);
16997 for (int touching_node_n = 0; touching_node_n < touching_nodes[0].Size; touching_node_n++)
16998 resize_limits[0] = ImMax(lhs: resize_limits[0], rhs: touching_nodes[0][touching_node_n]->Rect().Min[axis] + min_size);
16999 for (int touching_node_n = 0; touching_node_n < touching_nodes[1].Size; touching_node_n++)
17000 resize_limits[1] = ImMin(lhs: resize_limits[1], rhs: touching_nodes[1][touching_node_n]->Rect().Max[axis] - min_size);
17001
17002 // [DEBUG] Render touching nodes & limits
17003 /*
17004 ImDrawList* draw_list = node->HostWindow ? GetForegroundDrawList(node->HostWindow) : GetForegroundDrawList(GetMainViewport());
17005 for (int n = 0; n < 2; n++)
17006 {
17007 for (int touching_node_n = 0; touching_node_n < touching_nodes[n].Size; touching_node_n++)
17008 draw_list->AddRect(touching_nodes[n][touching_node_n]->Pos, touching_nodes[n][touching_node_n]->Pos + touching_nodes[n][touching_node_n]->Size, IM_COL32(0, 255, 0, 255));
17009 if (axis == ImGuiAxis_X)
17010 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);
17011 else
17012 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);
17013 }
17014 */
17015 }
17016
17017 // Use a short delay before highlighting the splitter (and changing the mouse cursor) in order for regular mouse movement to not highlight many splitters
17018 float cur_size_0 = child_0->Size[axis];
17019 float cur_size_1 = child_1->Size[axis];
17020 float min_size_0 = resize_limits[0] - child_0->Pos[axis];
17021 float min_size_1 = child_1->Pos[axis] + child_1->Size[axis] - resize_limits[1];
17022 ImU32 bg_col = GetColorU32(idx: ImGuiCol_WindowBg);
17023 if (SplitterBehavior(bb, id: GetID(str_id: "##Splitter"), axis, size1: &cur_size_0, size2: &cur_size_1, min_size1: min_size_0, min_size2: min_size_1, hover_extend: WINDOWS_HOVER_PADDING, hover_visibility_delay: WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER, bg_col))
17024 {
17025 if (touching_nodes[0].Size > 0 && touching_nodes[1].Size > 0)
17026 {
17027 child_0->Size[axis] = child_0->SizeRef[axis] = cur_size_0;
17028 child_1->Pos[axis] -= cur_size_1 - child_1->Size[axis];
17029 child_1->Size[axis] = child_1->SizeRef[axis] = cur_size_1;
17030
17031 // Lock the size of every node that is a sibling of the node we are touching
17032 // This might be less desirable if we can merge sibling of a same axis into the same parental level.
17033 for (int side_n = 0; side_n < 2; side_n++)
17034 for (int touching_node_n = 0; touching_node_n < touching_nodes[side_n].Size; touching_node_n++)
17035 {
17036 ImGuiDockNode* touching_node = touching_nodes[side_n][touching_node_n];
17037 //ImDrawList* draw_list = node->HostWindow ? GetForegroundDrawList(node->HostWindow) : GetForegroundDrawList(GetMainViewport());
17038 //draw_list->AddRect(touching_node->Pos, touching_node->Pos + touching_node->Size, IM_COL32(255, 128, 0, 255));
17039 while (touching_node->ParentNode != node)
17040 {
17041 if (touching_node->ParentNode->SplitAxis == axis)
17042 {
17043 // Mark other node so its size will be preserved during the upcoming call to DockNodeTreeUpdatePosSize().
17044 ImGuiDockNode* node_to_preserve = touching_node->ParentNode->ChildNodes[side_n];
17045 node_to_preserve->WantLockSizeOnce = true;
17046 //draw_list->AddRect(touching_node->Pos, touching_node->Rect().Max, IM_COL32(255, 0, 0, 255));
17047 //draw_list->AddRectFilled(node_to_preserve->Pos, node_to_preserve->Rect().Max, IM_COL32(0, 255, 0, 100));
17048 }
17049 touching_node = touching_node->ParentNode;
17050 }
17051 }
17052
17053 DockNodeTreeUpdatePosSize(node: child_0, pos: child_0->Pos, size: child_0->Size);
17054 DockNodeTreeUpdatePosSize(node: child_1, pos: child_1->Pos, size: child_1->Size);
17055 MarkIniSettingsDirty();
17056 }
17057 }
17058 PopID();
17059 }
17060 }
17061
17062 if (child_0->IsVisible)
17063 DockNodeTreeUpdateSplitter(node: child_0);
17064 if (child_1->IsVisible)
17065 DockNodeTreeUpdateSplitter(node: child_1);
17066}
17067
17068ImGuiDockNode* ImGui::DockNodeTreeFindFallbackLeafNode(ImGuiDockNode* node)
17069{
17070 if (node->IsLeafNode())
17071 return node;
17072 if (ImGuiDockNode* leaf_node = DockNodeTreeFindFallbackLeafNode(node: node->ChildNodes[0]))
17073 return leaf_node;
17074 if (ImGuiDockNode* leaf_node = DockNodeTreeFindFallbackLeafNode(node: node->ChildNodes[1]))
17075 return leaf_node;
17076 return NULL;
17077}
17078
17079ImGuiDockNode* ImGui::DockNodeTreeFindVisibleNodeByPos(ImGuiDockNode* node, ImVec2 pos)
17080{
17081 if (!node->IsVisible)
17082 return NULL;
17083
17084 const float dock_spacing = 0.0f;// g.Style.ItemInnerSpacing.x; // FIXME: Relation to DOCKING_SPLITTER_SIZE?
17085 ImRect r(node->Pos, node->Pos + node->Size);
17086 r.Expand(amount: dock_spacing * 0.5f);
17087 bool inside = r.Contains(p: pos);
17088 if (!inside)
17089 return NULL;
17090
17091 if (node->IsLeafNode())
17092 return node;
17093 if (ImGuiDockNode* hovered_node = DockNodeTreeFindVisibleNodeByPos(node: node->ChildNodes[0], pos))
17094 return hovered_node;
17095 if (ImGuiDockNode* hovered_node = DockNodeTreeFindVisibleNodeByPos(node: node->ChildNodes[1], pos))
17096 return hovered_node;
17097
17098 // This means we are hovering over the splitter/spacing of a parent node
17099 return node;
17100}
17101
17102//-----------------------------------------------------------------------------
17103// Docking: Public Functions (SetWindowDock, DockSpace, DockSpaceOverViewport)
17104//-----------------------------------------------------------------------------
17105// - SetWindowDock() [Internal]
17106// - DockSpace()
17107// - DockSpaceOverViewport()
17108//-----------------------------------------------------------------------------
17109
17110// [Internal] Called via SetNextWindowDockID()
17111void ImGui::SetWindowDock(ImGuiWindow* window, ImGuiID dock_id, ImGuiCond cond)
17112{
17113 // Test condition (NB: bit 0 is always true) and clear flags for next time
17114 if (cond && (window->SetWindowDockAllowFlags & cond) == 0)
17115 return;
17116 window->SetWindowDockAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
17117
17118 if (window->DockId == dock_id)
17119 return;
17120
17121 // If the user attempt to set a dock id that is a split node, we'll dig within to find a suitable docking spot
17122 ImGuiContext* ctx = GImGui;
17123 if (ImGuiDockNode* new_node = DockContextFindNodeByID(ctx, id: dock_id))
17124 if (new_node->IsSplitNode())
17125 {
17126 // Policy: Find central node or latest focused node. We first move back to our root node.
17127 new_node = DockNodeGetRootNode(node: new_node);
17128 if (new_node->CentralNode)
17129 {
17130 IM_ASSERT(new_node->CentralNode->IsCentralNode());
17131 dock_id = new_node->CentralNode->ID;
17132 }
17133 else
17134 {
17135 dock_id = new_node->LastFocusedNodeId;
17136 }
17137 }
17138
17139 if (window->DockId == dock_id)
17140 return;
17141
17142 if (window->DockNode)
17143 DockNodeRemoveWindow(node: window->DockNode, window, save_dock_id: 0);
17144 window->DockId = dock_id;
17145}
17146
17147// Create an explicit dockspace node within an existing window. Also expose dock node flags and creates a CentralNode by default.
17148// The Central Node is always displayed even when empty and shrink/extend according to the requested size of its neighbors.
17149// DockSpace() needs to be submitted _before_ any window they can host. If you use a dockspace, submit it early in your app.
17150ImGuiID ImGui::DockSpace(ImGuiID id, const ImVec2& size_arg, ImGuiDockNodeFlags flags, const ImGuiWindowClass* window_class)
17151{
17152 ImGuiContext* ctx = GImGui;
17153 ImGuiContext& g = *ctx;
17154 ImGuiWindow* window = GetCurrentWindow();
17155 if (!(g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable))
17156 return 0;
17157
17158 // Early out if parent window is hidden/collapsed
17159 // This is faster but also DockNodeUpdateTabBar() relies on TabBarLayout() running (which won't if SkipItems=true) to set NextSelectedTabId = 0). See #2960.
17160 // If for whichever reason this is causing problem we would need to ensure that DockNodeUpdateTabBar() ends up clearing NextSelectedTabId even if SkipItems=true.
17161 if (window->SkipItems)
17162 flags |= ImGuiDockNodeFlags_KeepAliveOnly;
17163
17164 IM_ASSERT((flags & ImGuiDockNodeFlags_DockSpace) == 0);
17165 IM_ASSERT(id != 0);
17166 ImGuiDockNode* node = DockContextFindNodeByID(ctx, id);
17167 if (!node)
17168 {
17169 IMGUI_DEBUG_LOG_DOCKING("[docking] DockSpace: dockspace node 0x%08X created\n", id);
17170 node = DockContextAddNode(ctx, id);
17171 node->SetLocalFlags(ImGuiDockNodeFlags_CentralNode);
17172 }
17173 if (window_class && window_class->ClassId != node->WindowClass.ClassId)
17174 IMGUI_DEBUG_LOG_DOCKING("[docking] DockSpace: dockspace node 0x%08X: setup WindowClass 0x%08X -> 0x%08X\n", id, node->WindowClass.ClassId, window_class->ClassId);
17175 node->SharedFlags = flags;
17176 node->WindowClass = window_class ? *window_class : ImGuiWindowClass();
17177
17178 // When a DockSpace transitioned form implicit to explicit this may be called a second time
17179 // 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.
17180 if (node->LastFrameActive == g.FrameCount && !(flags & ImGuiDockNodeFlags_KeepAliveOnly))
17181 {
17182 IM_ASSERT(node->IsDockSpace() == false && "Cannot call DockSpace() twice a frame with the same ID");
17183 node->SetLocalFlags(node->LocalFlags | ImGuiDockNodeFlags_DockSpace);
17184 return id;
17185 }
17186 node->SetLocalFlags(node->LocalFlags | ImGuiDockNodeFlags_DockSpace);
17187
17188 // Keep alive mode, this is allow windows docked into this node so stay docked even if they are not visible
17189 if (flags & ImGuiDockNodeFlags_KeepAliveOnly)
17190 {
17191 node->LastFrameAlive = g.FrameCount;
17192 return id;
17193 }
17194
17195 const ImVec2 content_avail = GetContentRegionAvail();
17196 ImVec2 size = ImFloor(v: size_arg);
17197 if (size.x <= 0.0f)
17198 size.x = ImMax(lhs: content_avail.x + size.x, rhs: 4.0f); // Arbitrary minimum child size (0.0f causing too much issues)
17199 if (size.y <= 0.0f)
17200 size.y = ImMax(lhs: content_avail.y + size.y, rhs: 4.0f);
17201 IM_ASSERT(size.x > 0.0f && size.y > 0.0f);
17202
17203 node->Pos = window->DC.CursorPos;
17204 node->Size = node->SizeRef = size;
17205 SetNextWindowPos(pos: node->Pos);
17206 SetNextWindowSize(size: node->Size);
17207 g.NextWindowData.PosUndock = false;
17208
17209 // FIXME-DOCK: Why do we need a child window to host a dockspace, could we host it in the existing window?
17210 // FIXME-DOCK: What is the reason for not simply calling BeginChild()? (OK to have a reason but should be commented)
17211 ImGuiWindowFlags window_flags = ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_DockNodeHost;
17212 window_flags |= ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar;
17213 window_flags |= ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse;
17214 window_flags |= ImGuiWindowFlags_NoBackground;
17215
17216 char title[256];
17217 ImFormatString(buf: title, IM_ARRAYSIZE(title), fmt: "%s/DockSpace_%08X", window->Name, id);
17218
17219 PushStyleVar(idx: ImGuiStyleVar_ChildBorderSize, val: 0.0f);
17220 Begin(name: title, NULL, flags: window_flags);
17221 PopStyleVar();
17222
17223 ImGuiWindow* host_window = g.CurrentWindow;
17224 DockNodeSetupHostWindow(node, host_window);
17225 host_window->ChildId = window->GetID(str: title);
17226 node->OnlyNodeWithWindows = NULL;
17227
17228 IM_ASSERT(node->IsRootNode());
17229
17230 // We need to handle the rare case were a central node is missing.
17231 // This can happen if the node was first created manually with DockBuilderAddNode() but _without_ the ImGuiDockNodeFlags_Dockspace.
17232 // Doing it correctly would set the _CentralNode flags, which would then propagate according to subsequent split.
17233 // 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.
17234 // The specific sub-property of _CentralNode we are interested in recovering here is the "Don't delete when empty" property,
17235 // as it doesn't make sense for an empty dockspace to not have this property.
17236 if (node->IsLeafNode() && !node->IsCentralNode())
17237 node->SetLocalFlags(node->LocalFlags | ImGuiDockNodeFlags_CentralNode);
17238
17239 // Update the node
17240 DockNodeUpdate(node);
17241
17242 End();
17243 ItemSize(size);
17244 return id;
17245}
17246
17247// Tips: Use with ImGuiDockNodeFlags_PassthruCentralNode!
17248// The limitation with this call is that your window won't have a menu bar.
17249// 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.
17250// 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.
17251ImGuiID ImGui::DockSpaceOverViewport(const ImGuiViewport* viewport, ImGuiDockNodeFlags dockspace_flags, const ImGuiWindowClass* window_class)
17252{
17253 if (viewport == NULL)
17254 viewport = GetMainViewport();
17255
17256 SetNextWindowPos(pos: viewport->WorkPos);
17257 SetNextWindowSize(size: viewport->WorkSize);
17258 SetNextWindowViewport(viewport->ID);
17259
17260 ImGuiWindowFlags host_window_flags = 0;
17261 host_window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDocking;
17262 host_window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;
17263 if (dockspace_flags & ImGuiDockNodeFlags_PassthruCentralNode)
17264 host_window_flags |= ImGuiWindowFlags_NoBackground;
17265
17266 char label[32];
17267 ImFormatString(buf: label, IM_ARRAYSIZE(label), fmt: "DockSpaceViewport_%08X", viewport->ID);
17268
17269 PushStyleVar(idx: ImGuiStyleVar_WindowRounding, val: 0.0f);
17270 PushStyleVar(idx: ImGuiStyleVar_WindowBorderSize, val: 0.0f);
17271 PushStyleVar(idx: ImGuiStyleVar_WindowPadding, val: ImVec2(0.0f, 0.0f));
17272 Begin(name: label, NULL, flags: host_window_flags);
17273 PopStyleVar(count: 3);
17274
17275 ImGuiID dockspace_id = GetID(str_id: "DockSpace");
17276 DockSpace(id: dockspace_id, size_arg: ImVec2(0.0f, 0.0f), flags: dockspace_flags, window_class);
17277 End();
17278
17279 return dockspace_id;
17280}
17281
17282//-----------------------------------------------------------------------------
17283// Docking: Builder Functions
17284//-----------------------------------------------------------------------------
17285// Very early end-user API to manipulate dock nodes.
17286// Only available in imgui_internal.h. Expect this API to change/break!
17287// It is expected that those functions are all called _before_ the dockspace node submission.
17288//-----------------------------------------------------------------------------
17289// - DockBuilderDockWindow()
17290// - DockBuilderGetNode()
17291// - DockBuilderSetNodePos()
17292// - DockBuilderSetNodeSize()
17293// - DockBuilderAddNode()
17294// - DockBuilderRemoveNode()
17295// - DockBuilderRemoveNodeChildNodes()
17296// - DockBuilderRemoveNodeDockedWindows()
17297// - DockBuilderSplitNode()
17298// - DockBuilderCopyNodeRec()
17299// - DockBuilderCopyNode()
17300// - DockBuilderCopyWindowSettings()
17301// - DockBuilderCopyDockSpace()
17302// - DockBuilderFinish()
17303//-----------------------------------------------------------------------------
17304
17305void ImGui::DockBuilderDockWindow(const char* window_name, ImGuiID node_id)
17306{
17307 // We don't preserve relative order of multiple docked windows (by clearing DockOrder back to -1)
17308 ImGuiID window_id = ImHashStr(data_p: window_name);
17309 if (ImGuiWindow* window = FindWindowByID(id: window_id))
17310 {
17311 // Apply to created window
17312 SetWindowDock(window, dock_id: node_id, cond: ImGuiCond_Always);
17313 window->DockOrder = -1;
17314 }
17315 else
17316 {
17317 // Apply to settings
17318 ImGuiWindowSettings* settings = FindWindowSettings(id: window_id);
17319 if (settings == NULL)
17320 settings = CreateNewWindowSettings(name: window_name);
17321 settings->DockId = node_id;
17322 settings->DockOrder = -1;
17323 }
17324}
17325
17326ImGuiDockNode* ImGui::DockBuilderGetNode(ImGuiID node_id)
17327{
17328 ImGuiContext* ctx = GImGui;
17329 return DockContextFindNodeByID(ctx, id: node_id);
17330}
17331
17332void ImGui::DockBuilderSetNodePos(ImGuiID node_id, ImVec2 pos)
17333{
17334 ImGuiContext* ctx = GImGui;
17335 ImGuiDockNode* node = DockContextFindNodeByID(ctx, id: node_id);
17336 if (node == NULL)
17337 return;
17338 node->Pos = pos;
17339 node->AuthorityForPos = ImGuiDataAuthority_DockNode;
17340}
17341
17342void ImGui::DockBuilderSetNodeSize(ImGuiID node_id, ImVec2 size)
17343{
17344 ImGuiContext* ctx = GImGui;
17345 ImGuiDockNode* node = DockContextFindNodeByID(ctx, id: node_id);
17346 if (node == NULL)
17347 return;
17348 IM_ASSERT(size.x > 0.0f && size.y > 0.0f);
17349 node->Size = node->SizeRef = size;
17350 node->AuthorityForSize = ImGuiDataAuthority_DockNode;
17351}
17352
17353// Make sure to use the ImGuiDockNodeFlags_DockSpace flag to create a dockspace node! Otherwise this will create a floating node!
17354// - Floating node: you can then call DockBuilderSetNodePos()/DockBuilderSetNodeSize() to position and size the floating node.
17355// - Dockspace node: calling DockBuilderSetNodePos() is unnecessary.
17356// - If you intend to split a node immediately after creation using DockBuilderSplitNode(), make sure to call DockBuilderSetNodeSize() beforehand!
17357// For various reason, the splitting code currently needs a base size otherwise space may not be allocated as precisely as you would expect.
17358// - Use (id == 0) to let the system allocate a node identifier.
17359// - Existing node with a same id will be removed.
17360ImGuiID ImGui::DockBuilderAddNode(ImGuiID id, ImGuiDockNodeFlags flags)
17361{
17362 ImGuiContext* ctx = GImGui;
17363
17364 if (id != 0)
17365 DockBuilderRemoveNode(node_id: id);
17366
17367 ImGuiDockNode* node = NULL;
17368 if (flags & ImGuiDockNodeFlags_DockSpace)
17369 {
17370 DockSpace(id, size_arg: ImVec2(0, 0), flags: (flags & ~ImGuiDockNodeFlags_DockSpace) | ImGuiDockNodeFlags_KeepAliveOnly);
17371 node = DockContextFindNodeByID(ctx, id);
17372 }
17373 else
17374 {
17375 node = DockContextAddNode(ctx, id);
17376 node->SetLocalFlags(flags);
17377 }
17378 node->LastFrameAlive = ctx->FrameCount; // Set this otherwise BeginDocked will undock during the same frame.
17379 return node->ID;
17380}
17381
17382void ImGui::DockBuilderRemoveNode(ImGuiID node_id)
17383{
17384 ImGuiContext* ctx = GImGui;
17385 ImGuiDockNode* node = DockContextFindNodeByID(ctx, id: node_id);
17386 if (node == NULL)
17387 return;
17388 DockBuilderRemoveNodeDockedWindows(node_id, clear_settings_refs: true);
17389 DockBuilderRemoveNodeChildNodes(node_id);
17390 // Node may have moved or deleted if e.g. any merge happened
17391 node = DockContextFindNodeByID(ctx, id: node_id);
17392 if (node == NULL)
17393 return;
17394 if (node->IsCentralNode() && node->ParentNode)
17395 node->ParentNode->SetLocalFlags(node->ParentNode->LocalFlags | ImGuiDockNodeFlags_CentralNode);
17396 DockContextRemoveNode(ctx, node, merge_sibling_into_parent_node: true);
17397}
17398
17399// root_id = 0 to remove all, root_id != 0 to remove child of given node.
17400void ImGui::DockBuilderRemoveNodeChildNodes(ImGuiID root_id)
17401{
17402 ImGuiContext* ctx = GImGui;
17403 ImGuiDockContext* dc = &ctx->DockContext;
17404
17405 ImGuiDockNode* root_node = root_id ? DockContextFindNodeByID(ctx, id: root_id) : NULL;
17406 if (root_id && root_node == NULL)
17407 return;
17408 bool has_central_node = false;
17409
17410 ImGuiDataAuthority backup_root_node_authority_for_pos = root_node ? root_node->AuthorityForPos : ImGuiDataAuthority_Auto;
17411 ImGuiDataAuthority backup_root_node_authority_for_size = root_node ? root_node->AuthorityForSize : ImGuiDataAuthority_Auto;
17412
17413 // Process active windows
17414 ImVector<ImGuiDockNode*> nodes_to_remove;
17415 for (int n = 0; n < dc->Nodes.Data.Size; n++)
17416 if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p)
17417 {
17418 bool want_removal = (root_id == 0) || (node->ID != root_id && DockNodeGetRootNode(node)->ID == root_id);
17419 if (want_removal)
17420 {
17421 if (node->IsCentralNode())
17422 has_central_node = true;
17423 if (root_id != 0)
17424 DockContextQueueNotifyRemovedNode(ctx, node);
17425 if (root_node)
17426 {
17427 DockNodeMoveWindows(dst_node: root_node, src_node: node);
17428 DockSettingsRenameNodeReferences(old_node_id: node->ID, new_node_id: root_node->ID);
17429 }
17430 nodes_to_remove.push_back(v: node);
17431 }
17432 }
17433
17434 // DockNodeMoveWindows->DockNodeAddWindow will normally set those when reaching two windows (which is only adequate during interactive merge)
17435 // Make sure we don't lose our current pos/size. (FIXME-DOCK: Consider tidying up that code in DockNodeAddWindow instead)
17436 if (root_node)
17437 {
17438 root_node->AuthorityForPos = backup_root_node_authority_for_pos;
17439 root_node->AuthorityForSize = backup_root_node_authority_for_size;
17440 }
17441
17442 // Apply to settings
17443 for (ImGuiWindowSettings* settings = ctx->SettingsWindows.begin(); settings != NULL; settings = ctx->SettingsWindows.next_chunk(p: settings))
17444 if (ImGuiID window_settings_dock_id = settings->DockId)
17445 for (int n = 0; n < nodes_to_remove.Size; n++)
17446 if (nodes_to_remove[n]->ID == window_settings_dock_id)
17447 {
17448 settings->DockId = root_id;
17449 break;
17450 }
17451
17452 // Not really efficient, but easier to destroy a whole hierarchy considering DockContextRemoveNode is attempting to merge nodes
17453 if (nodes_to_remove.Size > 1)
17454 ImQsort(base: nodes_to_remove.Data, count: nodes_to_remove.Size, size_of_element: sizeof(ImGuiDockNode*), compare_func: DockNodeComparerDepthMostFirst);
17455 for (int n = 0; n < nodes_to_remove.Size; n++)
17456 DockContextRemoveNode(ctx, node: nodes_to_remove[n], merge_sibling_into_parent_node: false);
17457
17458 if (root_id == 0)
17459 {
17460 dc->Nodes.Clear();
17461 dc->Requests.clear();
17462 }
17463 else if (has_central_node)
17464 {
17465 root_node->CentralNode = root_node;
17466 root_node->SetLocalFlags(root_node->LocalFlags | ImGuiDockNodeFlags_CentralNode);
17467 }
17468}
17469
17470void ImGui::DockBuilderRemoveNodeDockedWindows(ImGuiID root_id, bool clear_settings_refs)
17471{
17472 // Clear references in settings
17473 ImGuiContext* ctx = GImGui;
17474 ImGuiContext& g = *ctx;
17475 if (clear_settings_refs)
17476 {
17477 for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(p: settings))
17478 {
17479 bool want_removal = (root_id == 0) || (settings->DockId == root_id);
17480 if (!want_removal && settings->DockId != 0)
17481 if (ImGuiDockNode* node = DockContextFindNodeByID(ctx, id: settings->DockId))
17482 if (DockNodeGetRootNode(node)->ID == root_id)
17483 want_removal = true;
17484 if (want_removal)
17485 settings->DockId = 0;
17486 }
17487 }
17488
17489 // Clear references in windows
17490 for (int n = 0; n < g.Windows.Size; n++)
17491 {
17492 ImGuiWindow* window = g.Windows[n];
17493 bool want_removal = (root_id == 0) || (window->DockNode && DockNodeGetRootNode(node: window->DockNode)->ID == root_id) || (window->DockNodeAsHost && window->DockNodeAsHost->ID == root_id);
17494 if (want_removal)
17495 {
17496 const ImGuiID backup_dock_id = window->DockId;
17497 IM_UNUSED(backup_dock_id);
17498 DockContextProcessUndockWindow(ctx, window, clear_persistent_docking_ref: clear_settings_refs);
17499 if (!clear_settings_refs)
17500 IM_ASSERT(window->DockId == backup_dock_id);
17501 }
17502 }
17503}
17504
17505// 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.
17506// Return value is ID of the node at the specified direction, so same as (*out_id_at_dir) if that pointer is set.
17507// FIXME-DOCK: We are not exposing nor using split_outer.
17508ImGuiID 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)
17509{
17510 ImGuiContext& g = *GImGui;
17511 IM_ASSERT(split_dir != ImGuiDir_None);
17512 IMGUI_DEBUG_LOG_DOCKING("[docking] DockBuilderSplitNode: node 0x%08X, split_dir %d\n", id, split_dir);
17513
17514 ImGuiDockNode* node = DockContextFindNodeByID(ctx: &g, id);
17515 if (node == NULL)
17516 {
17517 IM_ASSERT(node != NULL);
17518 return 0;
17519 }
17520
17521 IM_ASSERT(!node->IsSplitNode()); // Assert if already Split
17522
17523 ImGuiDockRequest req;
17524 req.Type = ImGuiDockRequestType_Split;
17525 req.DockTargetWindow = NULL;
17526 req.DockTargetNode = node;
17527 req.DockPayload = NULL;
17528 req.DockSplitDir = split_dir;
17529 req.DockSplitRatio = ImSaturate(f: (split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Up) ? size_ratio_for_node_at_dir : 1.0f - size_ratio_for_node_at_dir);
17530 req.DockSplitOuter = false;
17531 DockContextProcessDock(ctx: &g, req: &req);
17532
17533 ImGuiID id_at_dir = node->ChildNodes[(split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Up) ? 0 : 1]->ID;
17534 ImGuiID id_at_opposite_dir = node->ChildNodes[(split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Up) ? 1 : 0]->ID;
17535 if (out_id_at_dir)
17536 *out_id_at_dir = id_at_dir;
17537 if (out_id_at_opposite_dir)
17538 *out_id_at_opposite_dir = id_at_opposite_dir;
17539 return id_at_dir;
17540}
17541
17542static ImGuiDockNode* DockBuilderCopyNodeRec(ImGuiDockNode* src_node, ImGuiID dst_node_id_if_known, ImVector<ImGuiID>* out_node_remap_pairs)
17543{
17544 ImGuiContext& g = *GImGui;
17545 ImGuiDockNode* dst_node = ImGui::DockContextAddNode(ctx: &g, id: dst_node_id_if_known);
17546 dst_node->SharedFlags = src_node->SharedFlags;
17547 dst_node->LocalFlags = src_node->LocalFlags;
17548 dst_node->LocalFlagsInWindows = ImGuiDockNodeFlags_None;
17549 dst_node->Pos = src_node->Pos;
17550 dst_node->Size = src_node->Size;
17551 dst_node->SizeRef = src_node->SizeRef;
17552 dst_node->SplitAxis = src_node->SplitAxis;
17553 dst_node->UpdateMergedFlags();
17554
17555 out_node_remap_pairs->push_back(v: src_node->ID);
17556 out_node_remap_pairs->push_back(v: dst_node->ID);
17557
17558 for (int child_n = 0; child_n < IM_ARRAYSIZE(src_node->ChildNodes); child_n++)
17559 if (src_node->ChildNodes[child_n])
17560 {
17561 dst_node->ChildNodes[child_n] = DockBuilderCopyNodeRec(src_node: src_node->ChildNodes[child_n], dst_node_id_if_known: 0, out_node_remap_pairs);
17562 dst_node->ChildNodes[child_n]->ParentNode = dst_node;
17563 }
17564
17565 IMGUI_DEBUG_LOG_DOCKING("[docking] Fork node %08X -> %08X (%d childs)\n", src_node->ID, dst_node->ID, dst_node->IsSplitNode() ? 2 : 0);
17566 return dst_node;
17567}
17568
17569void ImGui::DockBuilderCopyNode(ImGuiID src_node_id, ImGuiID dst_node_id, ImVector<ImGuiID>* out_node_remap_pairs)
17570{
17571 ImGuiContext* ctx = GImGui;
17572 IM_ASSERT(src_node_id != 0);
17573 IM_ASSERT(dst_node_id != 0);
17574 IM_ASSERT(out_node_remap_pairs != NULL);
17575
17576 DockBuilderRemoveNode(node_id: dst_node_id);
17577
17578 ImGuiDockNode* src_node = DockContextFindNodeByID(ctx, id: src_node_id);
17579 IM_ASSERT(src_node != NULL);
17580
17581 out_node_remap_pairs->clear();
17582 DockBuilderCopyNodeRec(src_node, dst_node_id_if_known: dst_node_id, out_node_remap_pairs);
17583
17584 IM_ASSERT((out_node_remap_pairs->Size % 2) == 0);
17585}
17586
17587void ImGui::DockBuilderCopyWindowSettings(const char* src_name, const char* dst_name)
17588{
17589 ImGuiWindow* src_window = FindWindowByName(name: src_name);
17590 if (src_window == NULL)
17591 return;
17592 if (ImGuiWindow* dst_window = FindWindowByName(name: dst_name))
17593 {
17594 dst_window->Pos = src_window->Pos;
17595 dst_window->Size = src_window->Size;
17596 dst_window->SizeFull = src_window->SizeFull;
17597 dst_window->Collapsed = src_window->Collapsed;
17598 }
17599 else if (ImGuiWindowSettings* dst_settings = FindOrCreateWindowSettings(name: dst_name))
17600 {
17601 ImVec2ih window_pos_2ih = ImVec2ih(src_window->Pos);
17602 if (src_window->ViewportId != 0 && src_window->ViewportId != IMGUI_VIEWPORT_DEFAULT_ID)
17603 {
17604 dst_settings->ViewportPos = window_pos_2ih;
17605 dst_settings->ViewportId = src_window->ViewportId;
17606 dst_settings->Pos = ImVec2ih(0, 0);
17607 }
17608 else
17609 {
17610 dst_settings->Pos = window_pos_2ih;
17611 }
17612 dst_settings->Size = ImVec2ih(src_window->SizeFull);
17613 dst_settings->Collapsed = src_window->Collapsed;
17614 }
17615}
17616
17617// FIXME: Will probably want to change this signature, in particular how the window remapping pairs are passed.
17618void ImGui::DockBuilderCopyDockSpace(ImGuiID src_dockspace_id, ImGuiID dst_dockspace_id, ImVector<const char*>* in_window_remap_pairs)
17619{
17620 ImGuiContext& g = *GImGui;
17621 IM_ASSERT(src_dockspace_id != 0);
17622 IM_ASSERT(dst_dockspace_id != 0);
17623 IM_ASSERT(in_window_remap_pairs != NULL);
17624 IM_ASSERT((in_window_remap_pairs->Size % 2) == 0);
17625
17626 // Duplicate entire dock
17627 // 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,
17628 // whereas we could attempt to at least keep them together in a new, same floating node.
17629 ImVector<ImGuiID> node_remap_pairs;
17630 DockBuilderCopyNode(src_node_id: src_dockspace_id, dst_node_id: dst_dockspace_id, out_node_remap_pairs: &node_remap_pairs);
17631
17632 // Attempt to transition all the upcoming windows associated to dst_dockspace_id into the newly created hierarchy of dock nodes
17633 // (The windows associated to src_dockspace_id are staying in place)
17634 ImVector<ImGuiID> src_windows;
17635 for (int remap_window_n = 0; remap_window_n < in_window_remap_pairs->Size; remap_window_n += 2)
17636 {
17637 const char* src_window_name = (*in_window_remap_pairs)[remap_window_n];
17638 const char* dst_window_name = (*in_window_remap_pairs)[remap_window_n + 1];
17639 ImGuiID src_window_id = ImHashStr(data_p: src_window_name);
17640 src_windows.push_back(v: src_window_id);
17641
17642 // Search in the remapping tables
17643 ImGuiID src_dock_id = 0;
17644 if (ImGuiWindow* src_window = FindWindowByID(id: src_window_id))
17645 src_dock_id = src_window->DockId;
17646 else if (ImGuiWindowSettings* src_window_settings = FindWindowSettings(id: src_window_id))
17647 src_dock_id = src_window_settings->DockId;
17648 ImGuiID dst_dock_id = 0;
17649 for (int dock_remap_n = 0; dock_remap_n < node_remap_pairs.Size; dock_remap_n += 2)
17650 if (node_remap_pairs[dock_remap_n] == src_dock_id)
17651 {
17652 dst_dock_id = node_remap_pairs[dock_remap_n + 1];
17653 //node_remap_pairs[dock_remap_n] = node_remap_pairs[dock_remap_n + 1] = 0; // Clear
17654 break;
17655 }
17656
17657 if (dst_dock_id != 0)
17658 {
17659 // Docked windows gets redocked into the new node hierarchy.
17660 IMGUI_DEBUG_LOG_DOCKING("[docking] Remap live window '%s' 0x%08X -> '%s' 0x%08X\n", src_window_name, src_dock_id, dst_window_name, dst_dock_id);
17661 DockBuilderDockWindow(window_name: dst_window_name, node_id: dst_dock_id);
17662 }
17663 else
17664 {
17665 // Floating windows gets their settings transferred (regardless of whether the new window already exist or not)
17666 // When this is leading to a Copy and not a Move, we would get two overlapping floating windows. Could we possibly dock them together?
17667 IMGUI_DEBUG_LOG_DOCKING("[docking] Remap window settings '%s' -> '%s'\n", src_window_name, dst_window_name);
17668 DockBuilderCopyWindowSettings(src_name: src_window_name, dst_name: dst_window_name);
17669 }
17670 }
17671
17672 // 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")
17673 // Find those windows and move to them to the cloned dock node. This may be optional?
17674 for (int dock_remap_n = 0; dock_remap_n < node_remap_pairs.Size; dock_remap_n += 2)
17675 if (ImGuiID src_dock_id = node_remap_pairs[dock_remap_n])
17676 {
17677 ImGuiID dst_dock_id = node_remap_pairs[dock_remap_n + 1];
17678 ImGuiDockNode* node = DockBuilderGetNode(node_id: src_dock_id);
17679 for (int window_n = 0; window_n < node->Windows.Size; window_n++)
17680 {
17681 ImGuiWindow* window = node->Windows[window_n];
17682 if (src_windows.contains(v: window->ID))
17683 continue;
17684
17685 // Docked windows gets redocked into the new node hierarchy.
17686 IMGUI_DEBUG_LOG_DOCKING("[docking] Remap window '%s' %08X -> %08X\n", window->Name, src_dock_id, dst_dock_id);
17687 DockBuilderDockWindow(window_name: window->Name, node_id: dst_dock_id);
17688 }
17689 }
17690}
17691
17692// FIXME-DOCK: This is awkward because in series of split user is likely to loose access to its root node.
17693void ImGui::DockBuilderFinish(ImGuiID root_id)
17694{
17695 ImGuiContext* ctx = GImGui;
17696 //DockContextRebuild(ctx);
17697 DockContextBuildAddWindowsToNodes(ctx, root_id);
17698}
17699
17700//-----------------------------------------------------------------------------
17701// Docking: Begin/End Support Functions (called from Begin/End)
17702//-----------------------------------------------------------------------------
17703// - GetWindowAlwaysWantOwnTabBar()
17704// - DockContextBindNodeToWindow()
17705// - BeginDocked()
17706// - BeginDockableDragDropSource()
17707// - BeginDockableDragDropTarget()
17708//-----------------------------------------------------------------------------
17709
17710bool ImGui::GetWindowAlwaysWantOwnTabBar(ImGuiWindow* window)
17711{
17712 ImGuiContext& g = *GImGui;
17713 if (g.IO.ConfigDockingAlwaysTabBar || window->WindowClass.DockingAlwaysTabBar)
17714 if ((window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoDocking)) == 0)
17715 if (!window->IsFallbackWindow) // We don't support AlwaysTabBar on the fallback/implicit window to avoid unused dock-node overhead/noise
17716 return true;
17717 return false;
17718}
17719
17720static ImGuiDockNode* ImGui::DockContextBindNodeToWindow(ImGuiContext* ctx, ImGuiWindow* window)
17721{
17722 ImGuiContext& g = *ctx;
17723 ImGuiDockNode* node = DockContextFindNodeByID(ctx, id: window->DockId);
17724 IM_ASSERT(window->DockNode == NULL);
17725
17726 // We should not be docking into a split node (SetWindowDock should avoid this)
17727 if (node && node->IsSplitNode())
17728 {
17729 DockContextProcessUndockWindow(ctx, window);
17730 return NULL;
17731 }
17732
17733 // Create node
17734 if (node == NULL)
17735 {
17736 node = DockContextAddNode(ctx, id: window->DockId);
17737 node->AuthorityForPos = node->AuthorityForSize = node->AuthorityForViewport = ImGuiDataAuthority_Window;
17738 node->LastFrameAlive = g.FrameCount;
17739 }
17740
17741 // If the node just turned visible and is part of a hierarchy, it doesn't have a Size assigned by DockNodeTreeUpdatePosSize() yet,
17742 // so we're forcing a Pos/Size update from the first ancestor that is already visible (often it will be the root node).
17743 // 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.
17744 // This is a little wonky because we don't normally update the Pos/Size of visible node mid-frame.
17745 if (!node->IsVisible)
17746 {
17747 ImGuiDockNode* ancestor_node = node;
17748 while (!ancestor_node->IsVisible && ancestor_node->ParentNode)
17749 ancestor_node = ancestor_node->ParentNode;
17750 IM_ASSERT(ancestor_node->Size.x > 0.0f && ancestor_node->Size.y > 0.0f);
17751 DockNodeUpdateHasCentralNodeChild(node: DockNodeGetRootNode(node: ancestor_node));
17752 DockNodeTreeUpdatePosSize(node: ancestor_node, pos: ancestor_node->Pos, size: ancestor_node->Size, only_write_to_single_node: node);
17753 }
17754
17755 // Add window to node
17756 bool node_was_visible = node->IsVisible;
17757 DockNodeAddWindow(node, window, add_to_tab_bar: true);
17758 node->IsVisible = node_was_visible; // Don't mark visible right away (so DockContextEndFrame() doesn't render it, maybe other side effects? will see)
17759 IM_ASSERT(node == window->DockNode);
17760 return node;
17761}
17762
17763void ImGui::BeginDocked(ImGuiWindow* window, bool* p_open)
17764{
17765 ImGuiContext* ctx = GImGui;
17766 ImGuiContext& g = *ctx;
17767
17768 // Clear fields ahead so most early-out paths don't have to do it
17769 window->DockIsActive = window->DockNodeIsVisible = window->DockTabIsVisible = false;
17770
17771 const bool auto_dock_node = GetWindowAlwaysWantOwnTabBar(window);
17772 if (auto_dock_node)
17773 {
17774 if (window->DockId == 0)
17775 {
17776 IM_ASSERT(window->DockNode == NULL);
17777 window->DockId = DockContextGenNodeID(ctx);
17778 }
17779 }
17780 else
17781 {
17782 // Calling SetNextWindowPos() undock windows by default (by setting PosUndock)
17783 bool want_undock = false;
17784 want_undock |= (window->Flags & ImGuiWindowFlags_NoDocking) != 0;
17785 want_undock |= (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasPos) && (window->SetWindowPosAllowFlags & g.NextWindowData.PosCond) && g.NextWindowData.PosUndock;
17786 if (want_undock)
17787 {
17788 DockContextProcessUndockWindow(ctx, window);
17789 return;
17790 }
17791 }
17792
17793 // Bind to our dock node
17794 ImGuiDockNode* node = window->DockNode;
17795 if (node != NULL)
17796 IM_ASSERT(window->DockId == node->ID);
17797 if (window->DockId != 0 && node == NULL)
17798 {
17799 node = DockContextBindNodeToWindow(ctx, window);
17800 if (node == NULL)
17801 return;
17802 }
17803
17804#if 0
17805 // Undock if the ImGuiDockNodeFlags_NoDockingInCentralNode got set
17806 if (node->IsCentralNode && (node->Flags & ImGuiDockNodeFlags_NoDockingInCentralNode))
17807 {
17808 DockContextProcessUndockWindow(ctx, window);
17809 return;
17810 }
17811#endif
17812
17813 // Undock if our dockspace node disappeared
17814 // Note how we are testing for LastFrameAlive and NOT LastFrameActive. A DockSpace node can be maintained alive while being inactive with ImGuiDockNodeFlags_KeepAliveOnly.
17815 if (node->LastFrameAlive < g.FrameCount)
17816 {
17817 // If the window has been orphaned, transition the docknode to an implicit node processed in DockContextNewFrameUpdateDocking()
17818 ImGuiDockNode* root_node = DockNodeGetRootNode(node);
17819 if (root_node->LastFrameAlive < g.FrameCount)
17820 DockContextProcessUndockWindow(ctx, window);
17821 else
17822 window->DockIsActive = true;
17823 return;
17824 }
17825
17826 // Store style overrides
17827 for (int color_n = 0; color_n < ImGuiWindowDockStyleCol_COUNT; color_n++)
17828 window->DockStyle.Colors[color_n] = ColorConvertFloat4ToU32(in: g.Style.Colors[GWindowDockStyleColors[color_n]]);
17829
17830 // Fast path return. It is common for windows to hold on a persistent DockId but be the only visible window,
17831 // and never create neither a host window neither a tab bar.
17832 // FIXME-DOCK: replace ->HostWindow NULL compare with something more explicit (~was initially intended as a first frame test)
17833 if (node->HostWindow == NULL)
17834 {
17835 if (node->State == ImGuiDockNodeState_HostWindowHiddenBecauseWindowsAreResizing)
17836 window->DockIsActive = true;
17837 if (node->Windows.Size > 1)
17838 DockNodeHideWindowDuringHostWindowCreation(window);
17839 return;
17840 }
17841
17842 // We can have zero-sized nodes (e.g. children of a small-size dockspace)
17843 IM_ASSERT(node->HostWindow);
17844 IM_ASSERT(node->IsLeafNode());
17845 IM_ASSERT(node->Size.x >= 0.0f && node->Size.y >= 0.0f);
17846 node->State = ImGuiDockNodeState_HostWindowVisible;
17847
17848 // Undock if we are submitted earlier than the host window
17849 if (!(node->MergedFlags & ImGuiDockNodeFlags_KeepAliveOnly) && window->BeginOrderWithinContext < node->HostWindow->BeginOrderWithinContext)
17850 {
17851 DockContextProcessUndockWindow(ctx, window);
17852 return;
17853 }
17854
17855 // Position/Size window
17856 SetNextWindowPos(pos: node->Pos);
17857 SetNextWindowSize(size: node->Size);
17858 g.NextWindowData.PosUndock = false; // Cancel implicit undocking of SetNextWindowPos()
17859 window->DockIsActive = true;
17860 window->DockNodeIsVisible = true;
17861 window->DockTabIsVisible = false;
17862 if (node->MergedFlags & ImGuiDockNodeFlags_KeepAliveOnly)
17863 return;
17864
17865 // When the window is selected we mark it as visible.
17866 if (node->VisibleWindow == window)
17867 window->DockTabIsVisible = true;
17868
17869 // Update window flag
17870 IM_ASSERT((window->Flags & ImGuiWindowFlags_ChildWindow) == 0);
17871 window->Flags |= ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_AlwaysUseWindowPadding | ImGuiWindowFlags_NoResize;
17872 if (node->IsHiddenTabBar() || node->IsNoTabBar())
17873 window->Flags |= ImGuiWindowFlags_NoTitleBar;
17874 else
17875 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!
17876
17877 // Save new dock order only if the window has been visible once already
17878 // This allows multiple windows to be created in the same frame and have their respective dock orders preserved.
17879 if (node->TabBar && window->WasActive)
17880 window->DockOrder = (short)DockNodeGetTabOrder(window);
17881
17882 if ((node->WantCloseAll || node->WantCloseTabId == window->TabId) && p_open != NULL)
17883 *p_open = false;
17884
17885 // Update ChildId to allow returning from Child to Parent with Escape
17886 ImGuiWindow* parent_window = window->DockNode->HostWindow;
17887 window->ChildId = parent_window->GetID(str: window->Name);
17888}
17889
17890void ImGui::BeginDockableDragDropSource(ImGuiWindow* window)
17891{
17892 ImGuiContext& g = *GImGui;
17893 IM_ASSERT(g.ActiveId == window->MoveId);
17894 IM_ASSERT(g.MovingWindow == window);
17895 IM_ASSERT(g.CurrentWindow == window);
17896
17897 g.LastItemData.ID = window->MoveId;
17898 window = window->RootWindowDockTree;
17899 IM_ASSERT((window->Flags & ImGuiWindowFlags_NoDocking) == 0);
17900 bool is_drag_docking = (g.IO.ConfigDockingWithShift) || ImRect(0, 0, window->SizeFull.x, GetFrameHeight()).Contains(p: g.ActiveIdClickOffset); // FIXME-DOCKING: Need to make this stateful and explicit
17901 if (is_drag_docking && BeginDragDropSource(flags: ImGuiDragDropFlags_SourceNoPreviewTooltip | ImGuiDragDropFlags_SourceNoHoldToOpenOthers | ImGuiDragDropFlags_SourceAutoExpirePayload))
17902 {
17903 SetDragDropPayload(IMGUI_PAYLOAD_TYPE_WINDOW, data: &window, data_size: sizeof(window));
17904 EndDragDropSource();
17905
17906 // Store style overrides
17907 for (int color_n = 0; color_n < ImGuiWindowDockStyleCol_COUNT; color_n++)
17908 window->DockStyle.Colors[color_n] = ColorConvertFloat4ToU32(in: g.Style.Colors[GWindowDockStyleColors[color_n]]);
17909 }
17910}
17911
17912void ImGui::BeginDockableDragDropTarget(ImGuiWindow* window)
17913{
17914 ImGuiContext* ctx = GImGui;
17915 ImGuiContext& g = *ctx;
17916
17917 //IM_ASSERT(window->RootWindowDockTree == window); // May also be a DockSpace
17918 IM_ASSERT((window->Flags & ImGuiWindowFlags_NoDocking) == 0);
17919 if (!g.DragDropActive)
17920 return;
17921 //GetForegroundDrawList(window)->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 255, 0, 255));
17922 if (!BeginDragDropTargetCustom(bb: window->Rect(), id: window->ID))
17923 return;
17924
17925 // Peek into the payload before calling AcceptDragDropPayload() so we can handle overlapping dock nodes with filtering
17926 // (this is a little unusual pattern, normally most code would call AcceptDragDropPayload directly)
17927 const ImGuiPayload* payload = &g.DragDropPayload;
17928 if (!payload->IsDataType(IMGUI_PAYLOAD_TYPE_WINDOW) || !DockNodeIsDropAllowed(host_window: window, root_payload: *(ImGuiWindow**)payload->Data))
17929 {
17930 EndDragDropTarget();
17931 return;
17932 }
17933
17934 ImGuiWindow* payload_window = *(ImGuiWindow**)payload->Data;
17935 if (AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_WINDOW, flags: ImGuiDragDropFlags_AcceptBeforeDelivery | ImGuiDragDropFlags_AcceptNoDrawDefaultRect))
17936 {
17937 // Select target node
17938 // (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)
17939 bool dock_into_floating_window = false;
17940 ImGuiDockNode* node = NULL;
17941 if (window->DockNodeAsHost)
17942 {
17943 // Cannot assume that node will != NULL even though we passed the rectangle test: it depends on padding/spacing handled by DockNodeTreeFindVisibleNodeByPos().
17944 node = DockNodeTreeFindVisibleNodeByPos(node: window->DockNodeAsHost, pos: g.IO.MousePos);
17945
17946 // There is an edge case when docking into a dockspace which only has _inactive_ nodes (because none of the windows are active)
17947 // In this case we need to fallback into any leaf mode, possibly the central node.
17948 // FIXME-20181220: We should not have to test for IsLeafNode() here but we have another bug to fix first.
17949 if (node && node->IsDockSpace() && node->IsRootNode())
17950 node = (node->CentralNode && node->IsLeafNode()) ? node->CentralNode : DockNodeTreeFindFallbackLeafNode(node);
17951 }
17952 else
17953 {
17954 if (window->DockNode)
17955 node = window->DockNode;
17956 else
17957 dock_into_floating_window = true; // Dock into a regular window
17958 }
17959
17960 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()));
17961 const bool is_explicit_target = g.IO.ConfigDockingWithShift || IsMouseHoveringRect(r_min: explicit_target_rect.Min, r_max: explicit_target_rect.Max);
17962
17963 // Preview docking request and find out split direction/ratio
17964 //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.
17965 const bool do_preview = payload->IsPreview() || payload->IsDelivery();
17966 if (do_preview && (node != NULL || dock_into_floating_window))
17967 {
17968 // If we have a non-leaf node it means we are hovering the border of a parent node, in which case only outer markers will appear.
17969 ImGuiDockPreviewData split_inner;
17970 ImGuiDockPreviewData split_outer;
17971 ImGuiDockPreviewData* split_data = &split_inner;
17972 if (node && (node->ParentNode || node->IsCentralNode() || !node->IsLeafNode()))
17973 if (ImGuiDockNode* root_node = DockNodeGetRootNode(node))
17974 {
17975 DockNodePreviewDockSetup(host_window: window, host_node: root_node, payload_window, NULL, data: &split_outer, is_explicit_target, is_outer_docking: true);
17976 if (split_outer.IsSplitDirExplicit)
17977 split_data = &split_outer;
17978 }
17979 if (!node || node->IsLeafNode())
17980 DockNodePreviewDockSetup(host_window: window, host_node: node, payload_window, NULL, data: &split_inner, is_explicit_target, is_outer_docking: false);
17981 if (split_data == &split_outer)
17982 split_inner.IsDropAllowed = false;
17983
17984 // Draw inner then outer, so that previewed tab (in inner data) will be behind the outer drop boxes
17985 DockNodePreviewDockRender(host_window: window, host_node: node, root_payload: payload_window, data: &split_inner);
17986 DockNodePreviewDockRender(host_window: window, host_node: node, root_payload: payload_window, data: &split_outer);
17987
17988 // Queue docking request
17989 if (split_data->IsDropAllowed && payload->IsDelivery())
17990 DockContextQueueDock(ctx, target: window, target_node: split_data->SplitNode, payload: payload_window, split_dir: split_data->SplitDir, split_ratio: split_data->SplitRatio, split_outer: split_data == &split_outer);
17991 }
17992 }
17993 EndDragDropTarget();
17994}
17995
17996//-----------------------------------------------------------------------------
17997// Docking: Settings
17998//-----------------------------------------------------------------------------
17999// - DockSettingsRenameNodeReferences()
18000// - DockSettingsRemoveNodeReferences()
18001// - DockSettingsFindNodeSettings()
18002// - DockSettingsHandler_ApplyAll()
18003// - DockSettingsHandler_ReadOpen()
18004// - DockSettingsHandler_ReadLine()
18005// - DockSettingsHandler_DockNodeToSettings()
18006// - DockSettingsHandler_WriteAll()
18007//-----------------------------------------------------------------------------
18008
18009static void ImGui::DockSettingsRenameNodeReferences(ImGuiID old_node_id, ImGuiID new_node_id)
18010{
18011 ImGuiContext& g = *GImGui;
18012 IMGUI_DEBUG_LOG_DOCKING("[docking] DockSettingsRenameNodeReferences: from 0x%08X -> to 0x%08X\n", old_node_id, new_node_id);
18013 for (int window_n = 0; window_n < g.Windows.Size; window_n++)
18014 {
18015 ImGuiWindow* window = g.Windows[window_n];
18016 if (window->DockId == old_node_id && window->DockNode == NULL)
18017 window->DockId = new_node_id;
18018 }
18019 //// FIXME-OPT: We could remove this loop by storing the index in the map
18020 for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(p: settings))
18021 if (settings->DockId == old_node_id)
18022 settings->DockId = new_node_id;
18023}
18024
18025// Remove references stored in ImGuiWindowSettings to the given ImGuiDockNodeSettings
18026static void ImGui::DockSettingsRemoveNodeReferences(ImGuiID* node_ids, int node_ids_count)
18027{
18028 ImGuiContext& g = *GImGui;
18029 int found = 0;
18030 //// FIXME-OPT: We could remove this loop by storing the index in the map
18031 for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(p: settings))
18032 for (int node_n = 0; node_n < node_ids_count; node_n++)
18033 if (settings->DockId == node_ids[node_n])
18034 {
18035 settings->DockId = 0;
18036 settings->DockOrder = -1;
18037 if (++found < node_ids_count)
18038 break;
18039 return;
18040 }
18041}
18042
18043static ImGuiDockNodeSettings* ImGui::DockSettingsFindNodeSettings(ImGuiContext* ctx, ImGuiID id)
18044{
18045 // FIXME-OPT
18046 ImGuiDockContext* dc = &ctx->DockContext;
18047 for (int n = 0; n < dc->NodesSettings.Size; n++)
18048 if (dc->NodesSettings[n].ID == id)
18049 return &dc->NodesSettings[n];
18050 return NULL;
18051}
18052
18053// Clear settings data
18054static void ImGui::DockSettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettingsHandler*)
18055{
18056 ImGuiDockContext* dc = &ctx->DockContext;
18057 dc->NodesSettings.clear();
18058 DockContextClearNodes(ctx, root_id: 0, clear_settings_refs: true);
18059}
18060
18061// Recreate nodes based on settings data
18062static void ImGui::DockSettingsHandler_ApplyAll(ImGuiContext* ctx, ImGuiSettingsHandler*)
18063{
18064 // Prune settings at boot time only
18065 ImGuiDockContext* dc = &ctx->DockContext;
18066 if (ctx->Windows.Size == 0)
18067 DockContextPruneUnusedSettingsNodes(ctx);
18068 DockContextBuildNodesFromSettings(ctx, node_settings_array: dc->NodesSettings.Data, node_settings_count: dc->NodesSettings.Size);
18069 DockContextBuildAddWindowsToNodes(ctx, root_id: 0);
18070}
18071
18072static void* ImGui::DockSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name)
18073{
18074 if (strcmp(s1: name, s2: "Data") != 0)
18075 return NULL;
18076 return (void*)1;
18077}
18078
18079static void ImGui::DockSettingsHandler_ReadLine(ImGuiContext* ctx, ImGuiSettingsHandler*, void*, const char* line)
18080{
18081 char c = 0;
18082 int x = 0, y = 0;
18083 int r = 0;
18084
18085 // Parsing, e.g.
18086 // " DockNode ID=0x00000001 Pos=383,193 Size=201,322 Split=Y,0.506 "
18087 // " DockNode ID=0x00000002 Parent=0x00000001 "
18088 // Important: this code expect currently fields in a fixed order.
18089 ImGuiDockNodeSettings node;
18090 line = ImStrSkipBlank(str: line);
18091 if (strncmp(s1: line, s2: "DockNode", n: 8) == 0) { line = ImStrSkipBlank(str: line + strlen(s: "DockNode")); }
18092 else if (strncmp(s1: line, s2: "DockSpace", n: 9) == 0) { line = ImStrSkipBlank(str: line + strlen(s: "DockSpace")); node.Flags |= ImGuiDockNodeFlags_DockSpace; }
18093 else return;
18094 if (sscanf(s: line, format: "ID=0x%08X%n", &node.ID, &r) == 1) { line += r; } else return;
18095 if (sscanf(s: line, format: " Parent=0x%08X%n", &node.ParentNodeId, &r) == 1) { line += r; if (node.ParentNodeId == 0) return; }
18096 if (sscanf(s: line, format: " Window=0x%08X%n", &node.ParentWindowId, &r) ==1) { line += r; if (node.ParentWindowId == 0) return; }
18097 if (node.ParentNodeId == 0)
18098 {
18099 if (sscanf(s: line, format: " Pos=%i,%i%n", &x, &y, &r) == 2) { line += r; node.Pos = ImVec2ih((short)x, (short)y); } else return;
18100 if (sscanf(s: line, format: " Size=%i,%i%n", &x, &y, &r) == 2) { line += r; node.Size = ImVec2ih((short)x, (short)y); } else return;
18101 }
18102 else
18103 {
18104 if (sscanf(s: line, format: " SizeRef=%i,%i%n", &x, &y, &r) == 2) { line += r; node.SizeRef = ImVec2ih((short)x, (short)y); }
18105 }
18106 if (sscanf(s: line, format: " Split=%c%n", &c, &r) == 1) { line += r; if (c == 'X') node.SplitAxis = ImGuiAxis_X; else if (c == 'Y') node.SplitAxis = ImGuiAxis_Y; }
18107 if (sscanf(s: line, format: " NoResize=%d%n", &x, &r) == 1) { line += r; if (x != 0) node.Flags |= ImGuiDockNodeFlags_NoResize; }
18108 if (sscanf(s: line, format: " CentralNode=%d%n", &x, &r) == 1) { line += r; if (x != 0) node.Flags |= ImGuiDockNodeFlags_CentralNode; }
18109 if (sscanf(s: line, format: " NoTabBar=%d%n", &x, &r) == 1) { line += r; if (x != 0) node.Flags |= ImGuiDockNodeFlags_NoTabBar; }
18110 if (sscanf(s: line, format: " HiddenTabBar=%d%n", &x, &r) == 1) { line += r; if (x != 0) node.Flags |= ImGuiDockNodeFlags_HiddenTabBar; }
18111 if (sscanf(s: line, format: " NoWindowMenuButton=%d%n", &x, &r) == 1) { line += r; if (x != 0) node.Flags |= ImGuiDockNodeFlags_NoWindowMenuButton; }
18112 if (sscanf(s: line, format: " NoCloseButton=%d%n", &x, &r) == 1) { line += r; if (x != 0) node.Flags |= ImGuiDockNodeFlags_NoCloseButton; }
18113 if (sscanf(s: line, format: " Selected=0x%08X%n", &node.SelectedTabId,&r) == 1) { line += r; }
18114 if (node.ParentNodeId != 0)
18115 if (ImGuiDockNodeSettings* parent_settings = DockSettingsFindNodeSettings(ctx, id: node.ParentNodeId))
18116 node.Depth = parent_settings->Depth + 1;
18117 ctx->DockContext.NodesSettings.push_back(v: node);
18118}
18119
18120static void DockSettingsHandler_DockNodeToSettings(ImGuiDockContext* dc, ImGuiDockNode* node, int depth)
18121{
18122 ImGuiDockNodeSettings node_settings;
18123 IM_ASSERT(depth < (1 << (sizeof(node_settings.Depth) << 3)));
18124 node_settings.ID = node->ID;
18125 node_settings.ParentNodeId = node->ParentNode ? node->ParentNode->ID : 0;
18126 node_settings.ParentWindowId = (node->IsDockSpace() && node->HostWindow && node->HostWindow->ParentWindow) ? node->HostWindow->ParentWindow->ID : 0;
18127 node_settings.SelectedTabId = node->SelectedTabId;
18128 node_settings.SplitAxis = (signed char)(node->IsSplitNode() ? node->SplitAxis : ImGuiAxis_None);
18129 node_settings.Depth = (char)depth;
18130 node_settings.Flags = (node->LocalFlags & ImGuiDockNodeFlags_SavedFlagsMask_);
18131 node_settings.Pos = ImVec2ih(node->Pos);
18132 node_settings.Size = ImVec2ih(node->Size);
18133 node_settings.SizeRef = ImVec2ih(node->SizeRef);
18134 dc->NodesSettings.push_back(v: node_settings);
18135 if (node->ChildNodes[0])
18136 DockSettingsHandler_DockNodeToSettings(dc, node: node->ChildNodes[0], depth: depth + 1);
18137 if (node->ChildNodes[1])
18138 DockSettingsHandler_DockNodeToSettings(dc, node: node->ChildNodes[1], depth: depth + 1);
18139}
18140
18141static void ImGui::DockSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf)
18142{
18143 ImGuiContext& g = *ctx;
18144 ImGuiDockContext* dc = &ctx->DockContext;
18145 if (!(g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable))
18146 return;
18147
18148 // Gather settings data
18149 // (unlike our windows settings, because nodes are always built we can do a full rewrite of the SettingsNode buffer)
18150 dc->NodesSettings.resize(new_size: 0);
18151 dc->NodesSettings.reserve(new_capacity: dc->Nodes.Data.Size);
18152 for (int n = 0; n < dc->Nodes.Data.Size; n++)
18153 if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p)
18154 if (node->IsRootNode())
18155 DockSettingsHandler_DockNodeToSettings(dc, node, depth: 0);
18156
18157 int max_depth = 0;
18158 for (int node_n = 0; node_n < dc->NodesSettings.Size; node_n++)
18159 max_depth = ImMax(lhs: (int)dc->NodesSettings[node_n].Depth, rhs: max_depth);
18160
18161 // Write to text buffer
18162 buf->appendf(fmt: "[%s][Data]\n", handler->TypeName);
18163 for (int node_n = 0; node_n < dc->NodesSettings.Size; node_n++)
18164 {
18165 const int line_start_pos = buf->size(); (void)line_start_pos;
18166 const ImGuiDockNodeSettings* node_settings = &dc->NodesSettings[node_n];
18167 buf->appendf(fmt: "%*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
18168 buf->appendf(fmt: " ID=0x%08X", node_settings->ID);
18169 if (node_settings->ParentNodeId)
18170 {
18171 buf->appendf(fmt: " Parent=0x%08X SizeRef=%d,%d", node_settings->ParentNodeId, node_settings->SizeRef.x, node_settings->SizeRef.y);
18172 }
18173 else
18174 {
18175 if (node_settings->ParentWindowId)
18176 buf->appendf(fmt: " Window=0x%08X", node_settings->ParentWindowId);
18177 buf->appendf(fmt: " Pos=%d,%d Size=%d,%d", node_settings->Pos.x, node_settings->Pos.y, node_settings->Size.x, node_settings->Size.y);
18178 }
18179 if (node_settings->SplitAxis != ImGuiAxis_None)
18180 buf->appendf(fmt: " Split=%c", (node_settings->SplitAxis == ImGuiAxis_X) ? 'X' : 'Y');
18181 if (node_settings->Flags & ImGuiDockNodeFlags_NoResize)
18182 buf->appendf(fmt: " NoResize=1");
18183 if (node_settings->Flags & ImGuiDockNodeFlags_CentralNode)
18184 buf->appendf(fmt: " CentralNode=1");
18185 if (node_settings->Flags & ImGuiDockNodeFlags_NoTabBar)
18186 buf->appendf(fmt: " NoTabBar=1");
18187 if (node_settings->Flags & ImGuiDockNodeFlags_HiddenTabBar)
18188 buf->appendf(fmt: " HiddenTabBar=1");
18189 if (node_settings->Flags & ImGuiDockNodeFlags_NoWindowMenuButton)
18190 buf->appendf(fmt: " NoWindowMenuButton=1");
18191 if (node_settings->Flags & ImGuiDockNodeFlags_NoCloseButton)
18192 buf->appendf(fmt: " NoCloseButton=1");
18193 if (node_settings->SelectedTabId)
18194 buf->appendf(fmt: " Selected=0x%08X", node_settings->SelectedTabId);
18195
18196#if IMGUI_DEBUG_INI_SETTINGS
18197 // [DEBUG] Include comments in the .ini file to ease debugging
18198 if (ImGuiDockNode* node = DockContextFindNodeByID(ctx, node_settings->ID))
18199 {
18200 buf->appendf("%*s", ImMax(2, (line_start_pos + 92) - buf->size()), ""); // Align everything
18201 if (node->IsDockSpace() && node->HostWindow && node->HostWindow->ParentWindow)
18202 buf->appendf(" ; in '%s'", node->HostWindow->ParentWindow->Name);
18203 // Iterate settings so we can give info about windows that didn't exist during the session.
18204 int contains_window = 0;
18205 for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings))
18206 if (settings->DockId == node_settings->ID)
18207 {
18208 if (contains_window++ == 0)
18209 buf->appendf(" ; contains ");
18210 buf->appendf("'%s' ", settings->GetName());
18211 }
18212 }
18213#endif
18214 buf->appendf(fmt: "\n");
18215 }
18216 buf->appendf(fmt: "\n");
18217}
18218
18219
18220//-----------------------------------------------------------------------------
18221// [SECTION] PLATFORM DEPENDENT HELPERS
18222//-----------------------------------------------------------------------------
18223
18224#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS)
18225
18226#ifdef _MSC_VER
18227#pragma comment(lib, "user32")
18228#pragma comment(lib, "kernel32")
18229#endif
18230
18231// Win32 clipboard implementation
18232// We use g.ClipboardHandlerData for temporary storage to ensure it is freed on Shutdown()
18233static const char* GetClipboardTextFn_DefaultImpl(void*)
18234{
18235 ImGuiContext& g = *GImGui;
18236 g.ClipboardHandlerData.clear();
18237 if (!::OpenClipboard(NULL))
18238 return NULL;
18239 HANDLE wbuf_handle = ::GetClipboardData(CF_UNICODETEXT);
18240 if (wbuf_handle == NULL)
18241 {
18242 ::CloseClipboard();
18243 return NULL;
18244 }
18245 if (const WCHAR* wbuf_global = (const WCHAR*)::GlobalLock(wbuf_handle))
18246 {
18247 int buf_len = ::WideCharToMultiByte(CP_UTF8, 0, wbuf_global, -1, NULL, 0, NULL, NULL);
18248 g.ClipboardHandlerData.resize(buf_len);
18249 ::WideCharToMultiByte(CP_UTF8, 0, wbuf_global, -1, g.ClipboardHandlerData.Data, buf_len, NULL, NULL);
18250 }
18251 ::GlobalUnlock(wbuf_handle);
18252 ::CloseClipboard();
18253 return g.ClipboardHandlerData.Data;
18254}
18255
18256static void SetClipboardTextFn_DefaultImpl(void*, const char* text)
18257{
18258 if (!::OpenClipboard(NULL))
18259 return;
18260 const int wbuf_length = ::MultiByteToWideChar(CP_UTF8, 0, text, -1, NULL, 0);
18261 HGLOBAL wbuf_handle = ::GlobalAlloc(GMEM_MOVEABLE, (SIZE_T)wbuf_length * sizeof(WCHAR));
18262 if (wbuf_handle == NULL)
18263 {
18264 ::CloseClipboard();
18265 return;
18266 }
18267 WCHAR* wbuf_global = (WCHAR*)::GlobalLock(wbuf_handle);
18268 ::MultiByteToWideChar(CP_UTF8, 0, text, -1, wbuf_global, wbuf_length);
18269 ::GlobalUnlock(wbuf_handle);
18270 ::EmptyClipboard();
18271 if (::SetClipboardData(CF_UNICODETEXT, wbuf_handle) == NULL)
18272 ::GlobalFree(wbuf_handle);
18273 ::CloseClipboard();
18274}
18275
18276#elif defined(__APPLE__) && TARGET_OS_OSX && defined(IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS)
18277
18278#include <Carbon/Carbon.h> // Use old API to avoid need for separate .mm file
18279static PasteboardRef main_clipboard = 0;
18280
18281// OSX clipboard implementation
18282// If you enable this you will need to add '-framework ApplicationServices' to your linker command-line!
18283static void SetClipboardTextFn_DefaultImpl(void*, const char* text)
18284{
18285 if (!main_clipboard)
18286 PasteboardCreate(kPasteboardClipboard, &main_clipboard);
18287 PasteboardClear(main_clipboard);
18288 CFDataRef cf_data = CFDataCreate(kCFAllocatorDefault, (const UInt8*)text, strlen(text));
18289 if (cf_data)
18290 {
18291 PasteboardPutItemFlavor(main_clipboard, (PasteboardItemID)1, CFSTR("public.utf8-plain-text"), cf_data, 0);
18292 CFRelease(cf_data);
18293 }
18294}
18295
18296static const char* GetClipboardTextFn_DefaultImpl(void*)
18297{
18298 if (!main_clipboard)
18299 PasteboardCreate(kPasteboardClipboard, &main_clipboard);
18300 PasteboardSynchronize(main_clipboard);
18301
18302 ItemCount item_count = 0;
18303 PasteboardGetItemCount(main_clipboard, &item_count);
18304 for (ItemCount i = 0; i < item_count; i++)
18305 {
18306 PasteboardItemID item_id = 0;
18307 PasteboardGetItemIdentifier(main_clipboard, i + 1, &item_id);
18308 CFArrayRef flavor_type_array = 0;
18309 PasteboardCopyItemFlavors(main_clipboard, item_id, &flavor_type_array);
18310 for (CFIndex j = 0, nj = CFArrayGetCount(flavor_type_array); j < nj; j++)
18311 {
18312 CFDataRef cf_data;
18313 if (PasteboardCopyItemFlavorData(main_clipboard, item_id, CFSTR("public.utf8-plain-text"), &cf_data) == noErr)
18314 {
18315 ImGuiContext& g = *GImGui;
18316 g.ClipboardHandlerData.clear();
18317 int length = (int)CFDataGetLength(cf_data);
18318 g.ClipboardHandlerData.resize(length + 1);
18319 CFDataGetBytes(cf_data, CFRangeMake(0, length), (UInt8*)g.ClipboardHandlerData.Data);
18320 g.ClipboardHandlerData[length] = 0;
18321 CFRelease(cf_data);
18322 return g.ClipboardHandlerData.Data;
18323 }
18324 }
18325 }
18326 return NULL;
18327}
18328
18329#else
18330
18331// Local Dear ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers.
18332static const char* GetClipboardTextFn_DefaultImpl(void*)
18333{
18334 ImGuiContext& g = *GImGui;
18335 return g.ClipboardHandlerData.empty() ? NULL : g.ClipboardHandlerData.begin();
18336}
18337
18338static void SetClipboardTextFn_DefaultImpl(void*, const char* text)
18339{
18340 ImGuiContext& g = *GImGui;
18341 g.ClipboardHandlerData.clear();
18342 const char* text_end = text + strlen(s: text);
18343 g.ClipboardHandlerData.resize(new_size: (int)(text_end - text) + 1);
18344 memcpy(dest: &g.ClipboardHandlerData[0], src: text, n: (size_t)(text_end - text));
18345 g.ClipboardHandlerData[(int)(text_end - text)] = 0;
18346}
18347
18348#endif
18349
18350// Win32 API IME support (for Asian languages, etc.)
18351#if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS)
18352
18353#include <imm.h>
18354#ifdef _MSC_VER
18355#pragma comment(lib, "imm32")
18356#endif
18357
18358static void SetPlatformImeDataFn_DefaultImpl(ImGuiViewport* viewport, ImGuiPlatformImeData* data)
18359{
18360 // Notify OS Input Method Editor of text input position
18361 HWND hwnd = (HWND)viewport->PlatformHandleRaw;
18362#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
18363 if (hwnd == 0)
18364 hwnd = (HWND)ImGui::GetIO().ImeWindowHandle;
18365#endif
18366 if (hwnd == 0)
18367 return;
18368
18369 //::ImmAssociateContextEx(hwnd, NULL, data->WantVisible ? IACE_DEFAULT : 0);
18370 if (HIMC himc = ::ImmGetContext(hwnd))
18371 {
18372 COMPOSITIONFORM composition_form = {};
18373 composition_form.ptCurrentPos.x = (LONG)(data->InputPos.x - viewport->Pos.x);
18374 composition_form.ptCurrentPos.y = (LONG)(data->InputPos.y - viewport->Pos.y);
18375 composition_form.dwStyle = CFS_FORCE_POSITION;
18376 ::ImmSetCompositionWindow(himc, &composition_form);
18377 CANDIDATEFORM candidate_form = {};
18378 candidate_form.dwStyle = CFS_CANDIDATEPOS;
18379 candidate_form.ptCurrentPos.x = (LONG)(data->InputPos.x - viewport->Pos.x);
18380 candidate_form.ptCurrentPos.y = (LONG)(data->InputPos.y - viewport->Pos.y);
18381 ::ImmSetCandidateWindow(himc, &candidate_form);
18382 ::ImmReleaseContext(hwnd, himc);
18383 }
18384}
18385
18386#else
18387
18388static void SetPlatformImeDataFn_DefaultImpl(ImGuiViewport*, ImGuiPlatformImeData*) {}
18389
18390#endif
18391
18392//-----------------------------------------------------------------------------
18393// [SECTION] METRICS/DEBUGGER WINDOW
18394//-----------------------------------------------------------------------------
18395// - RenderViewportThumbnail() [Internal]
18396// - RenderViewportsThumbnails() [Internal]
18397// - DebugTextEncoding()
18398// - MetricsHelpMarker() [Internal]
18399// - ShowFontAtlas() [Internal]
18400// - ShowMetricsWindow()
18401// - DebugNodeColumns() [Internal]
18402// - DebugNodeDockNode() [Internal]
18403// - DebugNodeDrawList() [Internal]
18404// - DebugNodeDrawCmdShowMeshAndBoundingBox() [Internal]
18405// - DebugNodeFont() [Internal]
18406// - DebugNodeFontGlyph() [Internal]
18407// - DebugNodeStorage() [Internal]
18408// - DebugNodeTabBar() [Internal]
18409// - DebugNodeViewport() [Internal]
18410// - DebugNodeWindow() [Internal]
18411// - DebugNodeWindowSettings() [Internal]
18412// - DebugNodeWindowsList() [Internal]
18413// - DebugNodeWindowsListByBeginStackParent() [Internal]
18414//-----------------------------------------------------------------------------
18415
18416#ifndef IMGUI_DISABLE_DEBUG_TOOLS
18417
18418void ImGui::DebugRenderViewportThumbnail(ImDrawList* draw_list, ImGuiViewportP* viewport, const ImRect& bb)
18419{
18420 ImGuiContext& g = *GImGui;
18421 ImGuiWindow* window = g.CurrentWindow;
18422
18423 ImVec2 scale = bb.GetSize() / viewport->Size;
18424 ImVec2 off = bb.Min - viewport->Pos * scale;
18425 float alpha_mul = (viewport->Flags & ImGuiViewportFlags_Minimized) ? 0.30f : 1.00f;
18426 window->DrawList->AddRectFilled(p_min: bb.Min, p_max: bb.Max, col: ImGui::GetColorU32(idx: ImGuiCol_Border, alpha_mul: alpha_mul * 0.40f));
18427 for (int i = 0; i != g.Windows.Size; i++)
18428 {
18429 ImGuiWindow* thumb_window = g.Windows[i];
18430 if (!thumb_window->WasActive || (thumb_window->Flags & ImGuiWindowFlags_ChildWindow))
18431 continue;
18432 if (thumb_window->Viewport != viewport)
18433 continue;
18434
18435 ImRect thumb_r = thumb_window->Rect();
18436 ImRect title_r = thumb_window->TitleBarRect();
18437 thumb_r = ImRect(ImFloor(v: off + thumb_r.Min * scale), ImFloor(v: off + thumb_r.Max * scale));
18438 title_r = ImRect(ImFloor(v: off + title_r.Min * scale), ImFloor(v: off + ImVec2(title_r.Max.x, title_r.Min.y) * scale) + ImVec2(0,5)); // Exaggerate title bar height
18439 thumb_r.ClipWithFull(r: bb);
18440 title_r.ClipWithFull(r: bb);
18441 const bool window_is_focused = (g.NavWindow && thumb_window->RootWindowForTitleBarHighlight == g.NavWindow->RootWindowForTitleBarHighlight);
18442 window->DrawList->AddRectFilled(p_min: thumb_r.Min, p_max: thumb_r.Max, col: GetColorU32(idx: ImGuiCol_WindowBg, alpha_mul));
18443 window->DrawList->AddRectFilled(p_min: title_r.Min, p_max: title_r.Max, col: GetColorU32(idx: window_is_focused ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg, alpha_mul));
18444 window->DrawList->AddRect(p_min: thumb_r.Min, p_max: thumb_r.Max, col: GetColorU32(idx: ImGuiCol_Border, alpha_mul));
18445 window->DrawList->AddText(font: g.Font, font_size: g.FontSize * 1.0f, pos: title_r.Min, col: GetColorU32(idx: ImGuiCol_Text, alpha_mul), text_begin: thumb_window->Name, text_end: FindRenderedTextEnd(text: thumb_window->Name));
18446 }
18447 draw_list->AddRect(p_min: bb.Min, p_max: bb.Max, col: GetColorU32(idx: ImGuiCol_Border, alpha_mul));
18448}
18449
18450static void RenderViewportsThumbnails()
18451{
18452 ImGuiContext& g = *GImGui;
18453 ImGuiWindow* window = g.CurrentWindow;
18454
18455 // 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.
18456 float SCALE = 1.0f / 8.0f;
18457 ImRect bb_full(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX);
18458 for (int n = 0; n < g.Viewports.Size; n++)
18459 bb_full.Add(r: g.Viewports[n]->GetMainRect());
18460 ImVec2 p = window->DC.CursorPos;
18461 ImVec2 off = p - bb_full.Min * SCALE;
18462 for (int n = 0; n < g.Viewports.Size; n++)
18463 {
18464 ImGuiViewportP* viewport = g.Viewports[n];
18465 ImRect viewport_draw_bb(off + (viewport->Pos) * SCALE, off + (viewport->Pos + viewport->Size) * SCALE);
18466 ImGui::DebugRenderViewportThumbnail(draw_list: window->DrawList, viewport, bb: viewport_draw_bb);
18467 }
18468 ImGui::Dummy(size: bb_full.GetSize() * SCALE);
18469}
18470
18471static int IMGUI_CDECL ViewportComparerByFrontMostStampCount(const void* lhs, const void* rhs)
18472{
18473 const ImGuiViewportP* a = *(const ImGuiViewportP* const*)lhs;
18474 const ImGuiViewportP* b = *(const ImGuiViewportP* const*)rhs;
18475 return b->LastFrontMostStampCount - a->LastFrontMostStampCount;
18476}
18477
18478// Draw an arbitrary US keyboard layout to visualize translated keys
18479void ImGui::DebugRenderKeyboardPreview(ImDrawList* draw_list)
18480{
18481 const ImVec2 key_size = ImVec2(35.0f, 35.0f);
18482 const float key_rounding = 3.0f;
18483 const ImVec2 key_face_size = ImVec2(25.0f, 25.0f);
18484 const ImVec2 key_face_pos = ImVec2(5.0f, 3.0f);
18485 const float key_face_rounding = 2.0f;
18486 const ImVec2 key_label_pos = ImVec2(7.0f, 4.0f);
18487 const ImVec2 key_step = ImVec2(key_size.x - 1.0f, key_size.y - 1.0f);
18488 const float key_row_offset = 9.0f;
18489
18490 ImVec2 board_min = GetCursorScreenPos();
18491 ImVec2 board_max = ImVec2(board_min.x + 3 * key_step.x + 2 * key_row_offset + 10.0f, board_min.y + 3 * key_step.y + 10.0f);
18492 ImVec2 start_pos = ImVec2(board_min.x + 5.0f - key_step.x, board_min.y);
18493
18494 struct KeyLayoutData { int Row, Col; const char* Label; ImGuiKey Key; };
18495 const KeyLayoutData keys_to_display[] =
18496 {
18497 { .Row: 0, .Col: 0, .Label: "", .Key: ImGuiKey_Tab }, { .Row: 0, .Col: 1, .Label: "Q", .Key: ImGuiKey_Q }, { .Row: 0, .Col: 2, .Label: "W", .Key: ImGuiKey_W }, { .Row: 0, .Col: 3, .Label: "E", .Key: ImGuiKey_E }, { .Row: 0, .Col: 4, .Label: "R", .Key: ImGuiKey_R },
18498 { .Row: 1, .Col: 0, .Label: "", .Key: ImGuiKey_CapsLock }, { .Row: 1, .Col: 1, .Label: "A", .Key: ImGuiKey_A }, { .Row: 1, .Col: 2, .Label: "S", .Key: ImGuiKey_S }, { .Row: 1, .Col: 3, .Label: "D", .Key: ImGuiKey_D }, { .Row: 1, .Col: 4, .Label: "F", .Key: ImGuiKey_F },
18499 { .Row: 2, .Col: 0, .Label: "", .Key: ImGuiKey_LeftShift },{ .Row: 2, .Col: 1, .Label: "Z", .Key: ImGuiKey_Z }, { .Row: 2, .Col: 2, .Label: "X", .Key: ImGuiKey_X }, { .Row: 2, .Col: 3, .Label: "C", .Key: ImGuiKey_C }, { .Row: 2, .Col: 4, .Label: "V", .Key: ImGuiKey_V }
18500 };
18501
18502 // Elements rendered manually via ImDrawList API are not clipped automatically.
18503 // While not strictly necessary, here IsItemVisible() is used to avoid rendering these shapes when they are out of view.
18504 Dummy(size: board_max - board_min);
18505 if (!IsItemVisible())
18506 return;
18507 draw_list->PushClipRect(clip_rect_min: board_min, clip_rect_max: board_max, intersect_with_current_clip_rect: true);
18508 for (int n = 0; n < IM_ARRAYSIZE(keys_to_display); n++)
18509 {
18510 const KeyLayoutData* key_data = &keys_to_display[n];
18511 ImVec2 key_min = ImVec2(start_pos.x + key_data->Col * key_step.x + key_data->Row * key_row_offset, start_pos.y + key_data->Row * key_step.y);
18512 ImVec2 key_max = key_min + key_size;
18513 draw_list->AddRectFilled(p_min: key_min, p_max: key_max, IM_COL32(204, 204, 204, 255), rounding: key_rounding);
18514 draw_list->AddRect(p_min: key_min, p_max: key_max, IM_COL32(24, 24, 24, 255), rounding: key_rounding);
18515 ImVec2 face_min = ImVec2(key_min.x + key_face_pos.x, key_min.y + key_face_pos.y);
18516 ImVec2 face_max = ImVec2(face_min.x + key_face_size.x, face_min.y + key_face_size.y);
18517 draw_list->AddRect(p_min: face_min, p_max: face_max, IM_COL32(193, 193, 193, 255), rounding: key_face_rounding, flags: ImDrawFlags_None, thickness: 2.0f);
18518 draw_list->AddRectFilled(p_min: face_min, p_max: face_max, IM_COL32(252, 252, 252, 255), rounding: key_face_rounding);
18519 ImVec2 label_min = ImVec2(key_min.x + key_label_pos.x, key_min.y + key_label_pos.y);
18520 draw_list->AddText(pos: label_min, IM_COL32(64, 64, 64, 255), text_begin: key_data->Label);
18521 if (ImGui::IsKeyDown(key: key_data->Key))
18522 draw_list->AddRectFilled(p_min: key_min, p_max: key_max, IM_COL32(255, 0, 0, 128), rounding: key_rounding);
18523 }
18524 draw_list->PopClipRect();
18525}
18526
18527// Helper tool to diagnose between text encoding issues and font loading issues. Pass your UTF-8 string and verify that there are correct.
18528void ImGui::DebugTextEncoding(const char* str)
18529{
18530 Text(fmt: "Text: \"%s\"", str);
18531 if (!BeginTable(str_id: "list", column: 4, flags: ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit))
18532 return;
18533 TableSetupColumn(label: "Offset");
18534 TableSetupColumn(label: "UTF-8");
18535 TableSetupColumn(label: "Glyph");
18536 TableSetupColumn(label: "Codepoint");
18537 TableHeadersRow();
18538 for (const char* p = str; *p != 0; )
18539 {
18540 unsigned int c;
18541 const int c_utf8_len = ImTextCharFromUtf8(out_char: &c, in_text: p, NULL);
18542 TableNextColumn();
18543 Text(fmt: "%d", (int)(p - str));
18544 TableNextColumn();
18545 for (int byte_index = 0; byte_index < c_utf8_len; byte_index++)
18546 {
18547 if (byte_index > 0)
18548 SameLine();
18549 Text(fmt: "0x%02X", (int)(unsigned char)p[byte_index]);
18550 }
18551 TableNextColumn();
18552 if (GetFont()->FindGlyphNoFallback(c: (ImWchar)c))
18553 TextUnformatted(text: p, text_end: p + c_utf8_len);
18554 else
18555 TextUnformatted(text: (c == IM_UNICODE_CODEPOINT_INVALID) ? "[invalid]" : "[missing]");
18556 TableNextColumn();
18557 Text(fmt: "U+%04X", (int)c);
18558 p += c_utf8_len;
18559 }
18560 EndTable();
18561}
18562
18563// Avoid naming collision with imgui_demo.cpp's HelpMarker() for unity builds.
18564static void MetricsHelpMarker(const char* desc)
18565{
18566 ImGui::TextDisabled(fmt: "(?)");
18567 if (ImGui::IsItemHovered(flags: ImGuiHoveredFlags_DelayShort))
18568 {
18569 ImGui::BeginTooltip();
18570 ImGui::PushTextWrapPos(wrap_pos_x: ImGui::GetFontSize() * 35.0f);
18571 ImGui::TextUnformatted(text: desc);
18572 ImGui::PopTextWrapPos();
18573 ImGui::EndTooltip();
18574 }
18575}
18576
18577// [DEBUG] List fonts in a font atlas and display its texture
18578void ImGui::ShowFontAtlas(ImFontAtlas* atlas)
18579{
18580 for (int i = 0; i < atlas->Fonts.Size; i++)
18581 {
18582 ImFont* font = atlas->Fonts[i];
18583 PushID(ptr_id: font);
18584 DebugNodeFont(font);
18585 PopID();
18586 }
18587 if (TreeNode(str_id: "Atlas texture", fmt: "Atlas texture (%dx%d pixels)", atlas->TexWidth, atlas->TexHeight))
18588 {
18589 ImVec4 tint_col = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
18590 ImVec4 border_col = ImVec4(1.0f, 1.0f, 1.0f, 0.5f);
18591 Image(user_texture_id: atlas->TexID, size: ImVec2((float)atlas->TexWidth, (float)atlas->TexHeight), uv0: ImVec2(0.0f, 0.0f), uv1: ImVec2(1.0f, 1.0f), tint_col, border_col);
18592 TreePop();
18593 }
18594}
18595
18596void ImGui::ShowMetricsWindow(bool* p_open)
18597{
18598 ImGuiContext& g = *GImGui;
18599 ImGuiIO& io = g.IO;
18600 ImGuiMetricsConfig* cfg = &g.DebugMetricsConfig;
18601 if (cfg->ShowDebugLog)
18602 ShowDebugLogWindow(p_open: &cfg->ShowDebugLog);
18603 if (cfg->ShowStackTool)
18604 ShowStackToolWindow(p_open: &cfg->ShowStackTool);
18605
18606 if (!Begin(name: "Dear ImGui Metrics/Debugger", p_open) || GetCurrentWindow()->BeginCount > 1)
18607 {
18608 End();
18609 return;
18610 }
18611
18612 // Basic info
18613 Text(fmt: "Dear ImGui %s", GetVersion());
18614 Text(fmt: "Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
18615 Text(fmt: "%d vertices, %d indices (%d triangles)", io.MetricsRenderVertices, io.MetricsRenderIndices, io.MetricsRenderIndices / 3);
18616 Text(fmt: "%d visible windows, %d active allocations", io.MetricsRenderWindows, io.MetricsActiveAllocations);
18617 //SameLine(); if (SmallButton("GC")) { g.GcCompactAll = true; }
18618
18619 Separator();
18620
18621 // Debugging enums
18622 enum { WRT_OuterRect, WRT_OuterRectClipped, WRT_InnerRect, WRT_InnerClipRect, WRT_WorkRect, WRT_Content, WRT_ContentIdeal, WRT_ContentRegionRect, WRT_Count }; // Windows Rect Type
18623 const char* wrt_rects_names[WRT_Count] = { "OuterRect", "OuterRectClipped", "InnerRect", "InnerClipRect", "WorkRect", "Content", "ContentIdeal", "ContentRegionRect" };
18624 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
18625 const char* trt_rects_names[TRT_Count] = { "OuterRect", "InnerRect", "WorkRect", "HostClipRect", "InnerClipRect", "BackgroundClipRect", "ColumnsRect", "ColumnsWorkRect", "ColumnsClipRect", "ColumnsContentHeadersUsed", "ColumnsContentHeadersIdeal", "ColumnsContentFrozen", "ColumnsContentUnfrozen" };
18626 if (cfg->ShowWindowsRectsType < 0)
18627 cfg->ShowWindowsRectsType = WRT_WorkRect;
18628 if (cfg->ShowTablesRectsType < 0)
18629 cfg->ShowTablesRectsType = TRT_WorkRect;
18630
18631 struct Funcs
18632 {
18633 static ImRect GetTableRect(ImGuiTable* table, int rect_type, int n)
18634 {
18635 ImGuiTableInstanceData* table_instance = TableGetInstanceData(table, instance_no: table->InstanceCurrent); // Always using last submitted instance
18636 if (rect_type == TRT_OuterRect) { return table->OuterRect; }
18637 else if (rect_type == TRT_InnerRect) { return table->InnerRect; }
18638 else if (rect_type == TRT_WorkRect) { return table->WorkRect; }
18639 else if (rect_type == TRT_HostClipRect) { return table->HostClipRect; }
18640 else if (rect_type == TRT_InnerClipRect) { return table->InnerClipRect; }
18641 else if (rect_type == TRT_BackgroundClipRect) { return table->BgClipRect; }
18642 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_instance->LastOuterHeight); }
18643 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); }
18644 else if (rect_type == TRT_ColumnsClipRect) { ImGuiTableColumn* c = &table->Columns[n]; return c->ClipRect; }
18645 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_instance->LastFirstRowHeight); } // Note: y1/y2 not always accurate
18646 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_instance->LastFirstRowHeight); }
18647 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_instance->LastFirstRowHeight); }
18648 else if (rect_type == TRT_ColumnsContentUnfrozen) { ImGuiTableColumn* c = &table->Columns[n]; return ImRect(c->WorkMinX, table->InnerClipRect.Min.y + table_instance->LastFirstRowHeight, c->ContentMaxXUnfrozen, table->InnerClipRect.Max.y); }
18649 IM_ASSERT(0);
18650 return ImRect();
18651 }
18652
18653 static ImRect GetWindowRect(ImGuiWindow* window, int rect_type)
18654 {
18655 if (rect_type == WRT_OuterRect) { return window->Rect(); }
18656 else if (rect_type == WRT_OuterRectClipped) { return window->OuterRectClipped; }
18657 else if (rect_type == WRT_InnerRect) { return window->InnerRect; }
18658 else if (rect_type == WRT_InnerClipRect) { return window->InnerClipRect; }
18659 else if (rect_type == WRT_WorkRect) { return window->WorkRect; }
18660 else if (rect_type == WRT_Content) { ImVec2 min = window->InnerRect.Min - window->Scroll + window->WindowPadding; return ImRect(min, min + window->ContentSize); }
18661 else if (rect_type == WRT_ContentIdeal) { ImVec2 min = window->InnerRect.Min - window->Scroll + window->WindowPadding; return ImRect(min, min + window->ContentSizeIdeal); }
18662 else if (rect_type == WRT_ContentRegionRect) { return window->ContentRegionRect; }
18663 IM_ASSERT(0);
18664 return ImRect();
18665 }
18666 };
18667
18668 // Tools
18669 if (TreeNode(label: "Tools"))
18670 {
18671 bool show_encoding_viewer = TreeNode(label: "UTF-8 Encoding viewer");
18672 SameLine();
18673 MetricsHelpMarker(desc: "You can also call ImGui::DebugTextEncoding() from your code with a given string to test that your UTF-8 encoding settings are correct.");
18674 if (show_encoding_viewer)
18675 {
18676 static char buf[100] = "";
18677 SetNextItemWidth(-FLT_MIN);
18678 InputText(label: "##Text", buf, IM_ARRAYSIZE(buf));
18679 if (buf[0] != 0)
18680 DebugTextEncoding(str: buf);
18681 TreePop();
18682 }
18683
18684 // The Item Picker tool is super useful to visually select an item and break into the call-stack of where it was submitted.
18685 if (Checkbox(label: "Show Item Picker", v: &g.DebugItemPickerActive) && g.DebugItemPickerActive)
18686 DebugStartItemPicker();
18687 SameLine();
18688 MetricsHelpMarker(desc: "Will call the IM_DEBUG_BREAK() macro to break in debugger.\nWarning: If you don't have a debugger attached, this will probably crash.");
18689
18690 // Stack Tool is your best friend!
18691 Checkbox(label: "Show Debug Log", v: &cfg->ShowDebugLog);
18692 SameLine();
18693 MetricsHelpMarker(desc: "You can also call ImGui::ShowDebugLogWindow() from your code.");
18694
18695 // Stack Tool is your best friend!
18696 Checkbox(label: "Show Stack Tool", v: &cfg->ShowStackTool);
18697 SameLine();
18698 MetricsHelpMarker(desc: "You can also call ImGui::ShowStackToolWindow() from your code.");
18699
18700 Checkbox(label: "Show windows begin order", v: &cfg->ShowWindowsBeginOrder);
18701 Checkbox(label: "Show windows rectangles", v: &cfg->ShowWindowsRects);
18702 SameLine();
18703 SetNextItemWidth(GetFontSize() * 12);
18704 cfg->ShowWindowsRects |= Combo(label: "##show_windows_rect_type", current_item: &cfg->ShowWindowsRectsType, items: wrt_rects_names, items_count: WRT_Count, popup_max_height_in_items: WRT_Count);
18705 if (cfg->ShowWindowsRects && g.NavWindow != NULL)
18706 {
18707 BulletText(fmt: "'%s':", g.NavWindow->Name);
18708 Indent();
18709 for (int rect_n = 0; rect_n < WRT_Count; rect_n++)
18710 {
18711 ImRect r = Funcs::GetWindowRect(window: g.NavWindow, rect_type: rect_n);
18712 Text(fmt: "(%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]);
18713 }
18714 Unindent();
18715 }
18716
18717 Checkbox(label: "Show tables rectangles", v: &cfg->ShowTablesRects);
18718 SameLine();
18719 SetNextItemWidth(GetFontSize() * 12);
18720 cfg->ShowTablesRects |= Combo(label: "##show_table_rects_type", current_item: &cfg->ShowTablesRectsType, items: trt_rects_names, items_count: TRT_Count, popup_max_height_in_items: TRT_Count);
18721 if (cfg->ShowTablesRects && g.NavWindow != NULL)
18722 {
18723 for (int table_n = 0; table_n < g.Tables.GetMapSize(); table_n++)
18724 {
18725 ImGuiTable* table = g.Tables.TryGetMapData(n: table_n);
18726 if (table == NULL || table->LastFrameActive < g.FrameCount - 1 || (table->OuterWindow != g.NavWindow && table->InnerWindow != g.NavWindow))
18727 continue;
18728
18729 BulletText(fmt: "Table 0x%08X (%d columns, in '%s')", table->ID, table->ColumnsCount, table->OuterWindow->Name);
18730 if (IsItemHovered())
18731 GetForegroundDrawList()->AddRect(p_min: table->OuterRect.Min - ImVec2(1, 1), p_max: table->OuterRect.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), rounding: 0.0f, flags: 0, thickness: 2.0f);
18732 Indent();
18733 char buf[128];
18734 for (int rect_n = 0; rect_n < TRT_Count; rect_n++)
18735 {
18736 if (rect_n >= TRT_ColumnsRect)
18737 {
18738 if (rect_n != TRT_ColumnsRect && rect_n != TRT_ColumnsClipRect)
18739 continue;
18740 for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
18741 {
18742 ImRect r = Funcs::GetTableRect(table, rect_type: rect_n, n: column_n);
18743 ImFormatString(buf, IM_ARRAYSIZE(buf), fmt: "(%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]);
18744 Selectable(label: buf);
18745 if (IsItemHovered())
18746 GetForegroundDrawList()->AddRect(p_min: r.Min - ImVec2(1, 1), p_max: r.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), rounding: 0.0f, flags: 0, thickness: 2.0f);
18747 }
18748 }
18749 else
18750 {
18751 ImRect r = Funcs::GetTableRect(table, rect_type: rect_n, n: -1);
18752 ImFormatString(buf, IM_ARRAYSIZE(buf), fmt: "(%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]);
18753 Selectable(label: buf);
18754 if (IsItemHovered())
18755 GetForegroundDrawList()->AddRect(p_min: r.Min - ImVec2(1, 1), p_max: r.Max + ImVec2(1, 1), IM_COL32(255, 255, 0, 255), rounding: 0.0f, flags: 0, thickness: 2.0f);
18756 }
18757 }
18758 Unindent();
18759 }
18760 }
18761
18762 TreePop();
18763 }
18764
18765 // Windows
18766 if (TreeNode(str_id: "Windows", fmt: "Windows (%d)", g.Windows.Size))
18767 {
18768 //SetNextItemOpen(true, ImGuiCond_Once);
18769 DebugNodeWindowsList(windows: &g.Windows, label: "By display order");
18770 DebugNodeWindowsList(windows: &g.WindowsFocusOrder, label: "By focus order (root windows)");
18771 if (TreeNode(label: "By submission order (begin stack)"))
18772 {
18773 // Here we display windows in their submitted order/hierarchy, however note that the Begin stack doesn't constitute a Parent<>Child relationship!
18774 ImVector<ImGuiWindow*>& temp_buffer = g.WindowsTempSortBuffer;
18775 temp_buffer.resize(new_size: 0);
18776 for (int i = 0; i < g.Windows.Size; i++)
18777 if (g.Windows[i]->LastFrameActive + 1 >= g.FrameCount)
18778 temp_buffer.push_back(v: g.Windows[i]);
18779 struct Func { static int IMGUI_CDECL WindowComparerByBeginOrder(const void* lhs, const void* rhs) { return ((int)(*(const ImGuiWindow* const *)lhs)->BeginOrderWithinContext - (*(const ImGuiWindow* const*)rhs)->BeginOrderWithinContext); } };
18780 ImQsort(base: temp_buffer.Data, count: (size_t)temp_buffer.Size, size_of_element: sizeof(ImGuiWindow*), compare_func: Func::WindowComparerByBeginOrder);
18781 DebugNodeWindowsListByBeginStackParent(windows: temp_buffer.Data, windows_size: temp_buffer.Size, NULL);
18782 TreePop();
18783 }
18784
18785 TreePop();
18786 }
18787
18788 // DrawLists
18789 int drawlist_count = 0;
18790 for (int viewport_i = 0; viewport_i < g.Viewports.Size; viewport_i++)
18791 drawlist_count += g.Viewports[viewport_i]->DrawDataBuilder.GetDrawListCount();
18792 if (TreeNode(str_id: "DrawLists", fmt: "DrawLists (%d)", drawlist_count))
18793 {
18794 Checkbox(label: "Show ImDrawCmd mesh when hovering", v: &cfg->ShowDrawCmdMesh);
18795 Checkbox(label: "Show ImDrawCmd bounding boxes when hovering", v: &cfg->ShowDrawCmdBoundingBoxes);
18796 for (int viewport_i = 0; viewport_i < g.Viewports.Size; viewport_i++)
18797 {
18798 ImGuiViewportP* viewport = g.Viewports[viewport_i];
18799 bool viewport_has_drawlist = false;
18800 for (int layer_i = 0; layer_i < IM_ARRAYSIZE(viewport->DrawDataBuilder.Layers); layer_i++)
18801 for (int draw_list_i = 0; draw_list_i < viewport->DrawDataBuilder.Layers[layer_i].Size; draw_list_i++)
18802 {
18803 if (!viewport_has_drawlist)
18804 Text(fmt: "Active DrawLists in Viewport #%d, ID: 0x%08X", viewport->Idx, viewport->ID);
18805 viewport_has_drawlist = true;
18806 DebugNodeDrawList(NULL, viewport, draw_list: viewport->DrawDataBuilder.Layers[layer_i][draw_list_i], label: "DrawList");
18807 }
18808 }
18809 TreePop();
18810 }
18811
18812 // Viewports
18813 if (TreeNode(str_id: "Viewports", fmt: "Viewports (%d)", g.Viewports.Size))
18814 {
18815 Indent(indent_w: GetTreeNodeToLabelSpacing());
18816 RenderViewportsThumbnails();
18817 Unindent(indent_w: GetTreeNodeToLabelSpacing());
18818
18819 bool open = TreeNode(str_id: "Monitors", fmt: "Monitors (%d)", g.PlatformIO.Monitors.Size);
18820 SameLine();
18821 MetricsHelpMarker(desc: "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.");
18822 if (open)
18823 {
18824 for (int i = 0; i < g.PlatformIO.Monitors.Size; i++)
18825 {
18826 const ImGuiPlatformMonitor& mon = g.PlatformIO.Monitors[i];
18827 BulletText(fmt: "Monitor #%d: DPI %.0f%%\n MainMin (%.0f,%.0f), MainMax (%.0f,%.0f), MainSize (%.0f,%.0f)\n WorkMin (%.0f,%.0f), WorkMax (%.0f,%.0f), WorkSize (%.0f,%.0f)",
18828 i, mon.DpiScale * 100.0f,
18829 mon.MainPos.x, mon.MainPos.y, mon.MainPos.x + mon.MainSize.x, mon.MainPos.y + mon.MainSize.y, mon.MainSize.x, mon.MainSize.y,
18830 mon.WorkPos.x, mon.WorkPos.y, mon.WorkPos.x + mon.WorkSize.x, mon.WorkPos.y + mon.WorkSize.y, mon.WorkSize.x, mon.WorkSize.y);
18831 }
18832 TreePop();
18833 }
18834
18835 BulletText(fmt: "MouseViewport: 0x%08X (UserHovered 0x%08X, LastHovered 0x%08X)", g.MouseViewport ? g.MouseViewport->ID : 0, g.IO.MouseHoveredViewport, g.MouseLastHoveredViewport ? g.MouseLastHoveredViewport->ID : 0);
18836 if (TreeNode(label: "Inferred Z order (front-to-back)"))
18837 {
18838 static ImVector<ImGuiViewportP*> viewports;
18839 viewports.resize(new_size: g.Viewports.Size);
18840 memcpy(dest: viewports.Data, src: g.Viewports.Data, n: g.Viewports.size_in_bytes());
18841 if (viewports.Size > 1)
18842 ImQsort(base: viewports.Data, count: viewports.Size, size_of_element: sizeof(ImGuiViewport*), compare_func: ViewportComparerByFrontMostStampCount);
18843 for (int i = 0; i < viewports.Size; i++)
18844 BulletText(fmt: "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");
18845 TreePop();
18846 }
18847
18848 for (int i = 0; i < g.Viewports.Size; i++)
18849 DebugNodeViewport(viewport: g.Viewports[i]);
18850 TreePop();
18851 }
18852
18853 // Details for Popups
18854 if (TreeNode(str_id: "Popups", fmt: "Popups (%d)", g.OpenPopupStack.Size))
18855 {
18856 for (int i = 0; i < g.OpenPopupStack.Size; i++)
18857 {
18858 // As it's difficult to interact with tree nodes while popups are open, we display everything inline.
18859 const ImGuiPopupData* popup_data = &g.OpenPopupStack[i];
18860 ImGuiWindow* window = popup_data->Window;
18861 BulletText(fmt: "PopupID: %08x, Window: '%s' (%s%s), BackupNavWindow '%s', ParentWindow '%s'",
18862 popup_data->PopupId, window ? window->Name : "NULL", window && (window->Flags & ImGuiWindowFlags_ChildWindow) ? "Child;" : "", window && (window->Flags & ImGuiWindowFlags_ChildMenu) ? "Menu;" : "",
18863 popup_data->BackupNavWindow ? popup_data->BackupNavWindow->Name : "NULL", window && window->ParentWindow ? window->ParentWindow->Name : "NULL");
18864 }
18865 TreePop();
18866 }
18867
18868 // Details for TabBars
18869 if (TreeNode(str_id: "TabBars", fmt: "Tab Bars (%d)", g.TabBars.GetAliveCount()))
18870 {
18871 for (int n = 0; n < g.TabBars.GetMapSize(); n++)
18872 if (ImGuiTabBar* tab_bar = g.TabBars.TryGetMapData(n))
18873 {
18874 PushID(ptr_id: tab_bar);
18875 DebugNodeTabBar(tab_bar, label: "TabBar");
18876 PopID();
18877 }
18878 TreePop();
18879 }
18880
18881 // Details for Tables
18882 if (TreeNode(str_id: "Tables", fmt: "Tables (%d)", g.Tables.GetAliveCount()))
18883 {
18884 for (int n = 0; n < g.Tables.GetMapSize(); n++)
18885 if (ImGuiTable* table = g.Tables.TryGetMapData(n))
18886 DebugNodeTable(table);
18887 TreePop();
18888 }
18889
18890 // Details for Fonts
18891 ImFontAtlas* atlas = g.IO.Fonts;
18892 if (TreeNode(str_id: "Fonts", fmt: "Fonts (%d)", atlas->Fonts.Size))
18893 {
18894 ShowFontAtlas(atlas);
18895 TreePop();
18896 }
18897
18898 // Details for InputText
18899 if (TreeNode(label: "InputText"))
18900 {
18901 DebugNodeInputTextState(state: &g.InputTextState);
18902 TreePop();
18903 }
18904
18905 // Details for Docking
18906#ifdef IMGUI_HAS_DOCK
18907 if (TreeNode(label: "Docking"))
18908 {
18909 static bool root_nodes_only = true;
18910 ImGuiDockContext* dc = &g.DockContext;
18911 Checkbox(label: "List root nodes", v: &root_nodes_only);
18912 Checkbox(label: "Ctrl shows window dock info", v: &cfg->ShowDockingNodes);
18913 if (SmallButton(label: "Clear nodes")) { DockContextClearNodes(ctx: &g, root_id: 0, clear_settings_refs: true); }
18914 SameLine();
18915 if (SmallButton(label: "Rebuild all")) { dc->WantFullRebuild = true; }
18916 for (int n = 0; n < dc->Nodes.Data.Size; n++)
18917 if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p)
18918 if (!root_nodes_only || node->IsRootNode())
18919 DebugNodeDockNode(node, label: "Node");
18920 TreePop();
18921 }
18922#endif // #ifdef IMGUI_HAS_DOCK
18923
18924 // Settings
18925 if (TreeNode(label: "Settings"))
18926 {
18927 if (SmallButton(label: "Clear"))
18928 ClearIniSettings();
18929 SameLine();
18930 if (SmallButton(label: "Save to memory"))
18931 SaveIniSettingsToMemory();
18932 SameLine();
18933 if (SmallButton(label: "Save to disk"))
18934 SaveIniSettingsToDisk(ini_filename: g.IO.IniFilename);
18935 SameLine();
18936 if (g.IO.IniFilename)
18937 Text(fmt: "\"%s\"", g.IO.IniFilename);
18938 else
18939 TextUnformatted(text: "<NULL>");
18940 Text(fmt: "SettingsDirtyTimer %.2f", g.SettingsDirtyTimer);
18941 if (TreeNode(str_id: "SettingsHandlers", fmt: "Settings handlers: (%d)", g.SettingsHandlers.Size))
18942 {
18943 for (int n = 0; n < g.SettingsHandlers.Size; n++)
18944 BulletText(fmt: "%s", g.SettingsHandlers[n].TypeName);
18945 TreePop();
18946 }
18947 if (TreeNode(str_id: "SettingsWindows", fmt: "Settings packed data: Windows: %d bytes", g.SettingsWindows.size()))
18948 {
18949 for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(p: settings))
18950 DebugNodeWindowSettings(settings);
18951 TreePop();
18952 }
18953
18954 if (TreeNode(str_id: "SettingsTables", fmt: "Settings packed data: Tables: %d bytes", g.SettingsTables.size()))
18955 {
18956 for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(p: settings))
18957 DebugNodeTableSettings(settings);
18958 TreePop();
18959 }
18960
18961#ifdef IMGUI_HAS_DOCK
18962 if (TreeNode(str_id: "SettingsDocking", fmt: "Settings packed data: Docking"))
18963 {
18964 ImGuiDockContext* dc = &g.DockContext;
18965 Text(fmt: "In SettingsWindows:");
18966 for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(p: settings))
18967 if (settings->DockId != 0)
18968 BulletText(fmt: "Window '%s' -> DockId %08X", settings->GetName(), settings->DockId);
18969 Text(fmt: "In SettingsNodes:");
18970 for (int n = 0; n < dc->NodesSettings.Size; n++)
18971 {
18972 ImGuiDockNodeSettings* settings = &dc->NodesSettings[n];
18973 const char* selected_tab_name = NULL;
18974 if (settings->SelectedTabId)
18975 {
18976 if (ImGuiWindow* window = FindWindowByID(id: settings->SelectedTabId))
18977 selected_tab_name = window->Name;
18978 else if (ImGuiWindowSettings* window_settings = FindWindowSettings(id: settings->SelectedTabId))
18979 selected_tab_name = window_settings->GetName();
18980 }
18981 BulletText(fmt: "Node %08X, Parent %08X, SelectedTab %08X ('%s')", settings->ID, settings->ParentNodeId, settings->SelectedTabId, selected_tab_name ? selected_tab_name : settings->SelectedTabId ? "N/A" : "");
18982 }
18983 TreePop();
18984 }
18985#endif // #ifdef IMGUI_HAS_DOCK
18986
18987 if (TreeNode(str_id: "SettingsIniData", fmt: "Settings unpacked data (.ini): %d bytes", g.SettingsIniData.size()))
18988 {
18989 InputTextMultiline(label: "##Ini", buf: (char*)(void*)g.SettingsIniData.c_str(), buf_size: g.SettingsIniData.Buf.Size, size: ImVec2(-FLT_MIN, GetTextLineHeight() * 20), flags: ImGuiInputTextFlags_ReadOnly);
18990 TreePop();
18991 }
18992 TreePop();
18993 }
18994
18995 if (TreeNode(label: "Inputs"))
18996 {
18997 Text(fmt: "KEYBOARD/GAMEPAD/MOUSE KEYS");
18998 {
18999 // We iterate both legacy native range and named ImGuiKey ranges, which is a little odd but this allows displaying the data for old/new backends.
19000 // User code should never have to go through such hoops: old code may use native keycodes, new code may use ImGuiKey codes.
19001 Indent();
19002#ifdef IMGUI_DISABLE_OBSOLETE_KEYIO
19003 struct funcs { static bool IsLegacyNativeDupe(ImGuiKey) { return false; } };
19004#else
19005 struct funcs { static bool IsLegacyNativeDupe(ImGuiKey key) { return key < 512 && GetIO().KeyMap[key] != -1; } }; // Hide Native<>ImGuiKey duplicates when both exists in the array
19006 //Text("Legacy raw:"); for (ImGuiKey key = ImGuiKey_KeysData_OFFSET; key < ImGuiKey_COUNT; key++) { if (io.KeysDown[key]) { SameLine(); Text("\"%s\" %d", GetKeyName(key), key); } }
19007#endif
19008 Text(fmt: "Keys down:"); for (ImGuiKey key = ImGuiKey_KeysData_OFFSET; key < ImGuiKey_COUNT; key = (ImGuiKey)(key + 1)) { if (funcs::IsLegacyNativeDupe(key) || !IsKeyDown(key)) continue; SameLine(); Text(fmt: IsNamedKey(key) ? "\"%s\"" : "\"%s\" %d", GetKeyName(key), key); SameLine(); Text(fmt: "(%.02f)", GetKeyData(key)->DownDuration); }
19009 Text(fmt: "Keys pressed:"); for (ImGuiKey key = ImGuiKey_KeysData_OFFSET; key < ImGuiKey_COUNT; key = (ImGuiKey)(key + 1)) { if (funcs::IsLegacyNativeDupe(key) || !IsKeyPressed(key)) continue; SameLine(); Text(fmt: IsNamedKey(key) ? "\"%s\"" : "\"%s\" %d", GetKeyName(key), key); }
19010 Text(fmt: "Keys released:"); for (ImGuiKey key = ImGuiKey_KeysData_OFFSET; key < ImGuiKey_COUNT; key = (ImGuiKey)(key + 1)) { if (funcs::IsLegacyNativeDupe(key) || !IsKeyReleased(key)) continue; SameLine(); Text(fmt: IsNamedKey(key) ? "\"%s\"" : "\"%s\" %d", GetKeyName(key), key); }
19011 Text(fmt: "Keys mods: %s%s%s%s", io.KeyCtrl ? "CTRL " : "", io.KeyShift ? "SHIFT " : "", io.KeyAlt ? "ALT " : "", io.KeySuper ? "SUPER " : "");
19012 Text(fmt: "Chars queue:"); for (int i = 0; i < io.InputQueueCharacters.Size; i++) { ImWchar c = io.InputQueueCharacters[i]; SameLine(); Text(fmt: "\'%c\' (0x%04X)", (c > ' ' && c <= 255) ? (char)c : '?', c); } // FIXME: We should convert 'c' to UTF-8 here but the functions are not public.
19013 DebugRenderKeyboardPreview(draw_list: GetWindowDrawList());
19014 Unindent();
19015 }
19016
19017 Text(fmt: "MOUSE STATE");
19018 {
19019 Indent();
19020 if (IsMousePosValid())
19021 Text(fmt: "Mouse pos: (%g, %g)", io.MousePos.x, io.MousePos.y);
19022 else
19023 Text(fmt: "Mouse pos: <INVALID>");
19024 Text(fmt: "Mouse delta: (%g, %g)", io.MouseDelta.x, io.MouseDelta.y);
19025 int count = IM_ARRAYSIZE(io.MouseDown);
19026 Text(fmt: "Mouse down:"); for (int i = 0; i < count; i++) if (IsMouseDown(button: i)) { SameLine(); Text(fmt: "b%d (%.02f secs)", i, io.MouseDownDuration[i]); }
19027 Text(fmt: "Mouse clicked:"); for (int i = 0; i < count; i++) if (IsMouseClicked(button: i)) { SameLine(); Text(fmt: "b%d (%d)", i, io.MouseClickedCount[i]); }
19028 Text(fmt: "Mouse released:"); for (int i = 0; i < count; i++) if (IsMouseReleased(button: i)) { SameLine(); Text(fmt: "b%d", i); }
19029 Text(fmt: "Mouse wheel: %.1f", io.MouseWheel);
19030 Text(fmt: "Pen Pressure: %.1f", io.PenPressure); // Note: currently unused
19031 Unindent();
19032 }
19033
19034 Text(fmt: "MOUSE WHEELING");
19035 {
19036 Indent();
19037 Text(fmt: "WheelingWindow: '%s'", g.WheelingWindow ? g.WheelingWindow->Name : "NULL");
19038 Text(fmt: "WheelingWindowReleaseTimer: %.2f", g.WheelingWindowReleaseTimer);
19039 Text(fmt: "WheelingAxisAvg[] = { %.3f, %.3f }, Main Axis: %s", g.WheelingAxisAvg.x, g.WheelingAxisAvg.y, (g.WheelingAxisAvg.x > g.WheelingAxisAvg.y) ? "X" : (g.WheelingAxisAvg.x < g.WheelingAxisAvg.y) ? "Y" : "<none>");
19040 Unindent();
19041 }
19042
19043 Text(fmt: "KEY OWNERS");
19044 {
19045 Indent();
19046 if (BeginListBox(label: "##owners", size: ImVec2(-FLT_MIN, GetTextLineHeightWithSpacing() * 6)))
19047 {
19048 for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1))
19049 {
19050 ImGuiKeyOwnerData* owner_data = GetKeyOwnerData(key);
19051 if (owner_data->OwnerCurr == ImGuiKeyOwner_None)
19052 continue;
19053 Text(fmt: "%s: 0x%08X%s", GetKeyName(key), owner_data->OwnerCurr,
19054 owner_data->LockUntilRelease ? " LockUntilRelease" : owner_data->LockThisFrame ? " LockThisFrame" : "");
19055 DebugLocateItemOnHover(target_id: owner_data->OwnerCurr);
19056 }
19057 EndListBox();
19058 }
19059 Unindent();
19060 }
19061 Text(fmt: "SHORTCUT ROUTING");
19062 {
19063 Indent();
19064 if (BeginListBox(label: "##routes", size: ImVec2(-FLT_MIN, GetTextLineHeightWithSpacing() * 6)))
19065 {
19066 for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1))
19067 {
19068 ImGuiKeyRoutingTable* rt = &g.KeysRoutingTable;
19069 for (ImGuiKeyRoutingIndex idx = rt->Index[key - ImGuiKey_NamedKey_BEGIN]; idx != -1; )
19070 {
19071 char key_chord_name[64];
19072 ImGuiKeyRoutingData* routing_data = &rt->Entries[idx];
19073 GetKeyChordName(key_chord: key | routing_data->Mods, out_buf: key_chord_name, IM_ARRAYSIZE(key_chord_name));
19074 Text(fmt: "%s: 0x%08X", key_chord_name, routing_data->RoutingCurr);
19075 DebugLocateItemOnHover(target_id: routing_data->RoutingCurr);
19076 idx = routing_data->NextEntryIndex;
19077 }
19078 }
19079 EndListBox();
19080 }
19081 Text(fmt: "(ActiveIdUsing: AllKeyboardKeys: %d, NavDirMask: 0x%X)", g.ActiveIdUsingAllKeyboardKeys, g.ActiveIdUsingNavDirMask);
19082 Unindent();
19083 }
19084 TreePop();
19085 }
19086
19087 if (TreeNode(label: "Internal state"))
19088 {
19089 Text(fmt: "WINDOWING");
19090 Indent();
19091 Text(fmt: "HoveredWindow: '%s'", g.HoveredWindow ? g.HoveredWindow->Name : "NULL");
19092 Text(fmt: "HoveredWindow->Root: '%s'", g.HoveredWindow ? g.HoveredWindow->RootWindowDockTree->Name : "NULL");
19093 Text(fmt: "HoveredWindowUnderMovingWindow: '%s'", g.HoveredWindowUnderMovingWindow ? g.HoveredWindowUnderMovingWindow->Name : "NULL");
19094 Text(fmt: "HoveredDockNode: 0x%08X", g.DebugHoveredDockNode ? g.DebugHoveredDockNode->ID : 0);
19095 Text(fmt: "MovingWindow: '%s'", g.MovingWindow ? g.MovingWindow->Name : "NULL");
19096 Text(fmt: "MouseViewport: 0x%08X (UserHovered 0x%08X, LastHovered 0x%08X)", g.MouseViewport->ID, g.IO.MouseHoveredViewport, g.MouseLastHoveredViewport ? g.MouseLastHoveredViewport->ID : 0);
19097 Unindent();
19098
19099 Text(fmt: "ITEMS");
19100 Indent();
19101 Text(fmt: "ActiveId: 0x%08X/0x%08X (%.2f sec), AllowOverlap: %d, Source: %s", g.ActiveId, g.ActiveIdPreviousFrame, g.ActiveIdTimer, g.ActiveIdAllowOverlap, GetInputSourceName(source: g.ActiveIdSource));
19102 DebugLocateItemOnHover(target_id: g.ActiveId);
19103 Text(fmt: "ActiveIdWindow: '%s'", g.ActiveIdWindow ? g.ActiveIdWindow->Name : "NULL");
19104 Text(fmt: "ActiveIdUsing: AllKeyboardKeys: %d, NavDirMask: %X", g.ActiveIdUsingAllKeyboardKeys, g.ActiveIdUsingNavDirMask);
19105 Text(fmt: "HoveredId: 0x%08X (%.2f sec), AllowOverlap: %d", g.HoveredIdPreviousFrame, g.HoveredIdTimer, g.HoveredIdAllowOverlap); // Not displaying g.HoveredId as it is update mid-frame
19106 Text(fmt: "HoverDelayId: 0x%08X, Timer: %.2f, ClearTimer: %.2f", g.HoverDelayId, g.HoverDelayTimer, g.HoverDelayClearTimer);
19107 Text(fmt: "DragDrop: %d, SourceId = 0x%08X, Payload \"%s\" (%d bytes)", g.DragDropActive, g.DragDropPayload.SourceId, g.DragDropPayload.DataType, g.DragDropPayload.DataSize);
19108 DebugLocateItemOnHover(target_id: g.DragDropPayload.SourceId);
19109 Unindent();
19110
19111 Text(fmt: "NAV,FOCUS");
19112 Indent();
19113 Text(fmt: "NavWindow: '%s'", g.NavWindow ? g.NavWindow->Name : "NULL");
19114 Text(fmt: "NavId: 0x%08X, NavLayer: %d", g.NavId, g.NavLayer);
19115 DebugLocateItemOnHover(target_id: g.NavId);
19116 Text(fmt: "NavInputSource: %s", GetInputSourceName(source: g.NavInputSource));
19117 Text(fmt: "NavActive: %d, NavVisible: %d", g.IO.NavActive, g.IO.NavVisible);
19118 Text(fmt: "NavActivateId/DownId/PressedId/InputId: %08X/%08X/%08X/%08X", g.NavActivateId, g.NavActivateDownId, g.NavActivatePressedId, g.NavActivateInputId);
19119 Text(fmt: "NavActivateFlags: %04X", g.NavActivateFlags);
19120 Text(fmt: "NavDisableHighlight: %d, NavDisableMouseHover: %d", g.NavDisableHighlight, g.NavDisableMouseHover);
19121 Text(fmt: "NavFocusScopeId = 0x%08X", g.NavFocusScopeId);
19122 Text(fmt: "NavWindowingTarget: '%s'", g.NavWindowingTarget ? g.NavWindowingTarget->Name : "NULL");
19123 Unindent();
19124
19125 TreePop();
19126 }
19127
19128 // Overlay: Display windows Rectangles and Begin Order
19129 if (cfg->ShowWindowsRects || cfg->ShowWindowsBeginOrder)
19130 {
19131 for (int n = 0; n < g.Windows.Size; n++)
19132 {
19133 ImGuiWindow* window = g.Windows[n];
19134 if (!window->WasActive)
19135 continue;
19136 ImDrawList* draw_list = GetForegroundDrawList(window);
19137 if (cfg->ShowWindowsRects)
19138 {
19139 ImRect r = Funcs::GetWindowRect(window, rect_type: cfg->ShowWindowsRectsType);
19140 draw_list->AddRect(p_min: r.Min, p_max: r.Max, IM_COL32(255, 0, 128, 255));
19141 }
19142 if (cfg->ShowWindowsBeginOrder && !(window->Flags & ImGuiWindowFlags_ChildWindow))
19143 {
19144 char buf[32];
19145 ImFormatString(buf, IM_ARRAYSIZE(buf), fmt: "%d", window->BeginOrderWithinContext);
19146 float font_size = GetFontSize();
19147 draw_list->AddRectFilled(p_min: window->Pos, p_max: window->Pos + ImVec2(font_size, font_size), IM_COL32(200, 100, 100, 255));
19148 draw_list->AddText(pos: window->Pos, IM_COL32(255, 255, 255, 255), text_begin: buf);
19149 }
19150 }
19151 }
19152
19153 // Overlay: Display Tables Rectangles
19154 if (cfg->ShowTablesRects)
19155 {
19156 for (int table_n = 0; table_n < g.Tables.GetMapSize(); table_n++)
19157 {
19158 ImGuiTable* table = g.Tables.TryGetMapData(n: table_n);
19159 if (table == NULL || table->LastFrameActive < g.FrameCount - 1)
19160 continue;
19161 ImDrawList* draw_list = GetForegroundDrawList(window: table->OuterWindow);
19162 if (cfg->ShowTablesRectsType >= TRT_ColumnsRect)
19163 {
19164 for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
19165 {
19166 ImRect r = Funcs::GetTableRect(table, rect_type: cfg->ShowTablesRectsType, n: column_n);
19167 ImU32 col = (table->HoveredColumnBody == column_n) ? IM_COL32(255, 255, 128, 255) : IM_COL32(255, 0, 128, 255);
19168 float thickness = (table->HoveredColumnBody == column_n) ? 3.0f : 1.0f;
19169 draw_list->AddRect(p_min: r.Min, p_max: r.Max, col, rounding: 0.0f, flags: 0, thickness);
19170 }
19171 }
19172 else
19173 {
19174 ImRect r = Funcs::GetTableRect(table, rect_type: cfg->ShowTablesRectsType, n: -1);
19175 draw_list->AddRect(p_min: r.Min, p_max: r.Max, IM_COL32(255, 0, 128, 255));
19176 }
19177 }
19178 }
19179
19180#ifdef IMGUI_HAS_DOCK
19181 // Overlay: Display Docking info
19182 if (cfg->ShowDockingNodes && g.IO.KeyCtrl && g.DebugHoveredDockNode)
19183 {
19184 char buf[64] = "";
19185 char* p = buf;
19186 ImGuiDockNode* node = g.DebugHoveredDockNode;
19187 ImDrawList* overlay_draw_list = node->HostWindow ? GetForegroundDrawList(window: node->HostWindow) : GetForegroundDrawList(viewport: GetMainViewport());
19188 p += ImFormatString(buf: p, buf_size: buf + IM_ARRAYSIZE(buf) - p, fmt: "DockId: %X%s\n", node->ID, node->IsCentralNode() ? " *CentralNode*" : "");
19189 p += ImFormatString(buf: p, buf_size: buf + IM_ARRAYSIZE(buf) - p, fmt: "WindowClass: %08X\n", node->WindowClass.ClassId);
19190 p += ImFormatString(buf: p, buf_size: buf + IM_ARRAYSIZE(buf) - p, fmt: "Size: (%.0f, %.0f)\n", node->Size.x, node->Size.y);
19191 p += ImFormatString(buf: p, buf_size: buf + IM_ARRAYSIZE(buf) - p, fmt: "SizeRef: (%.0f, %.0f)\n", node->SizeRef.x, node->SizeRef.y);
19192 int depth = DockNodeGetDepth(node);
19193 overlay_draw_list->AddRect(p_min: node->Pos + ImVec2(3, 3) * (float)depth, p_max: node->Pos + node->Size - ImVec2(3, 3) * (float)depth, IM_COL32(200, 100, 100, 255));
19194 ImVec2 pos = node->Pos + ImVec2(3, 3) * (float)depth;
19195 overlay_draw_list->AddRectFilled(p_min: pos - ImVec2(1, 1), p_max: pos + CalcTextSize(text: buf) + ImVec2(1, 1), IM_COL32(200, 100, 100, 255));
19196 overlay_draw_list->AddText(NULL, font_size: 0.0f, pos, IM_COL32(255, 255, 255, 255), text_begin: buf);
19197 }
19198#endif // #ifdef IMGUI_HAS_DOCK
19199
19200 End();
19201}
19202
19203// [DEBUG] Display contents of Columns
19204void ImGui::DebugNodeColumns(ImGuiOldColumns* columns)
19205{
19206 if (!TreeNode(ptr_id: (void*)(uintptr_t)columns->ID, fmt: "Columns Id: 0x%08X, Count: %d, Flags: 0x%04X", columns->ID, columns->Count, columns->Flags))
19207 return;
19208 BulletText(fmt: "Width: %.1f (MinX: %.1f, MaxX: %.1f)", columns->OffMaxX - columns->OffMinX, columns->OffMinX, columns->OffMaxX);
19209 for (int column_n = 0; column_n < columns->Columns.Size; column_n++)
19210 BulletText(fmt: "Column %02d: OffsetNorm %.3f (= %.1f px)", column_n, columns->Columns[column_n].OffsetNorm, GetColumnOffsetFromNorm(columns, offset_norm: columns->Columns[column_n].OffsetNorm));
19211 TreePop();
19212}
19213
19214static void DebugNodeDockNodeFlags(ImGuiDockNodeFlags* p_flags, const char* label, bool enabled)
19215{
19216 using namespace ImGui;
19217 PushID(str_id: label);
19218 PushStyleVar(idx: ImGuiStyleVar_FramePadding, val: ImVec2(0.0f, 0.0f));
19219 Text(fmt: "%s:", label);
19220 if (!enabled)
19221 BeginDisabled();
19222 CheckboxFlags(label: "NoSplit", flags: p_flags, flags_value: ImGuiDockNodeFlags_NoSplit);
19223 CheckboxFlags(label: "NoResize", flags: p_flags, flags_value: ImGuiDockNodeFlags_NoResize);
19224 CheckboxFlags(label: "NoResizeX", flags: p_flags, flags_value: ImGuiDockNodeFlags_NoResizeX);
19225 CheckboxFlags(label: "NoResizeY",flags: p_flags, flags_value: ImGuiDockNodeFlags_NoResizeY);
19226 CheckboxFlags(label: "NoTabBar", flags: p_flags, flags_value: ImGuiDockNodeFlags_NoTabBar);
19227 CheckboxFlags(label: "HiddenTabBar", flags: p_flags, flags_value: ImGuiDockNodeFlags_HiddenTabBar);
19228 CheckboxFlags(label: "NoWindowMenuButton", flags: p_flags, flags_value: ImGuiDockNodeFlags_NoWindowMenuButton);
19229 CheckboxFlags(label: "NoCloseButton", flags: p_flags, flags_value: ImGuiDockNodeFlags_NoCloseButton);
19230 CheckboxFlags(label: "NoDocking", flags: p_flags, flags_value: ImGuiDockNodeFlags_NoDocking);
19231 CheckboxFlags(label: "NoDockingSplitMe", flags: p_flags, flags_value: ImGuiDockNodeFlags_NoDockingSplitMe);
19232 CheckboxFlags(label: "NoDockingSplitOther", flags: p_flags, flags_value: ImGuiDockNodeFlags_NoDockingSplitOther);
19233 CheckboxFlags(label: "NoDockingOverMe", flags: p_flags, flags_value: ImGuiDockNodeFlags_NoDockingOverMe);
19234 CheckboxFlags(label: "NoDockingOverOther", flags: p_flags, flags_value: ImGuiDockNodeFlags_NoDockingOverOther);
19235 CheckboxFlags(label: "NoDockingOverEmpty", flags: p_flags, flags_value: ImGuiDockNodeFlags_NoDockingOverEmpty);
19236 if (!enabled)
19237 EndDisabled();
19238 PopStyleVar();
19239 PopID();
19240}
19241
19242// [DEBUG] Display contents of ImDockNode
19243void ImGui::DebugNodeDockNode(ImGuiDockNode* node, const char* label)
19244{
19245 ImGuiContext& g = *GImGui;
19246 const bool is_alive = (g.FrameCount - node->LastFrameAlive < 2); // Submitted with ImGuiDockNodeFlags_KeepAliveOnly
19247 const bool is_active = (g.FrameCount - node->LastFrameActive < 2); // Submitted
19248 if (!is_alive) { PushStyleColor(idx: ImGuiCol_Text, col: GetStyleColorVec4(idx: ImGuiCol_TextDisabled)); }
19249 bool open;
19250 ImGuiTreeNodeFlags tree_node_flags = node->IsFocused ? ImGuiTreeNodeFlags_Selected : ImGuiTreeNodeFlags_None;
19251 if (node->Windows.Size > 0)
19252 open = TreeNodeEx(ptr_id: (void*)(intptr_t)node->ID, flags: tree_node_flags, fmt: "%s 0x%04X%s: %d windows (vis: '%s')", label, node->ID, node->IsVisible ? "" : " (hidden)", node->Windows.Size, node->VisibleWindow ? node->VisibleWindow->Name : "NULL");
19253 else
19254 open = TreeNodeEx(ptr_id: (void*)(intptr_t)node->ID, flags: tree_node_flags, fmt: "%s 0x%04X%s: %s (vis: '%s')", label, node->ID, node->IsVisible ? "" : " (hidden)", (node->SplitAxis == ImGuiAxis_X) ? "horizontal split" : (node->SplitAxis == ImGuiAxis_Y) ? "vertical split" : "empty", node->VisibleWindow ? node->VisibleWindow->Name : "NULL");
19255 if (!is_alive) { PopStyleColor(); }
19256 if (is_active && IsItemHovered())
19257 if (ImGuiWindow* window = node->HostWindow ? node->HostWindow : node->VisibleWindow)
19258 GetForegroundDrawList(window)->AddRect(p_min: node->Pos, p_max: node->Pos + node->Size, IM_COL32(255, 255, 0, 255));
19259 if (open)
19260 {
19261 IM_ASSERT(node->ChildNodes[0] == NULL || node->ChildNodes[0]->ParentNode == node);
19262 IM_ASSERT(node->ChildNodes[1] == NULL || node->ChildNodes[1]->ParentNode == node);
19263 BulletText(fmt: "Pos (%.0f,%.0f), Size (%.0f, %.0f) Ref (%.0f, %.0f)",
19264 node->Pos.x, node->Pos.y, node->Size.x, node->Size.y, node->SizeRef.x, node->SizeRef.y);
19265 DebugNodeWindow(window: node->HostWindow, label: "HostWindow");
19266 DebugNodeWindow(window: node->VisibleWindow, label: "VisibleWindow");
19267 BulletText(fmt: "SelectedTabID: 0x%08X, LastFocusedNodeID: 0x%08X", node->SelectedTabId, node->LastFocusedNodeId);
19268 BulletText(fmt: "Misc:%s%s%s%s%s%s%s",
19269 node->IsDockSpace() ? " IsDockSpace" : "",
19270 node->IsCentralNode() ? " IsCentralNode" : "",
19271 is_alive ? " IsAlive" : "", is_active ? " IsActive" : "", node->IsFocused ? " IsFocused" : "",
19272 node->WantLockSizeOnce ? " WantLockSizeOnce" : "",
19273 node->HasCentralNodeChild ? " HasCentralNodeChild" : "");
19274 if (TreeNode(str_id: "flags", fmt: "Flags Merged: 0x%04X, Local: 0x%04X, InWindows: 0x%04X, Shared: 0x%04X", node->MergedFlags, node->LocalFlags, node->LocalFlagsInWindows, node->SharedFlags))
19275 {
19276 if (BeginTable(str_id: "flags", column: 4))
19277 {
19278 TableNextColumn(); DebugNodeDockNodeFlags(p_flags: &node->MergedFlags, label: "MergedFlags", enabled: false);
19279 TableNextColumn(); DebugNodeDockNodeFlags(p_flags: &node->LocalFlags, label: "LocalFlags", enabled: true);
19280 TableNextColumn(); DebugNodeDockNodeFlags(p_flags: &node->LocalFlagsInWindows, label: "LocalFlagsInWindows", enabled: false);
19281 TableNextColumn(); DebugNodeDockNodeFlags(p_flags: &node->SharedFlags, label: "SharedFlags", enabled: true);
19282 EndTable();
19283 }
19284 TreePop();
19285 }
19286 if (node->ParentNode)
19287 DebugNodeDockNode(node: node->ParentNode, label: "ParentNode");
19288 if (node->ChildNodes[0])
19289 DebugNodeDockNode(node: node->ChildNodes[0], label: "Child[0]");
19290 if (node->ChildNodes[1])
19291 DebugNodeDockNode(node: node->ChildNodes[1], label: "Child[1]");
19292 if (node->TabBar)
19293 DebugNodeTabBar(tab_bar: node->TabBar, label: "TabBar");
19294 DebugNodeWindowsList(windows: &node->Windows, label: "Windows");
19295
19296 TreePop();
19297 }
19298}
19299
19300// [DEBUG] Display contents of ImDrawList
19301// Note that both 'window' and 'viewport' may be NULL here. Viewport is generally null of destroyed popups which previously owned a viewport.
19302void ImGui::DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, const ImDrawList* draw_list, const char* label)
19303{
19304 ImGuiContext& g = *GImGui;
19305 ImGuiMetricsConfig* cfg = &g.DebugMetricsConfig;
19306 int cmd_count = draw_list->CmdBuffer.Size;
19307 if (cmd_count > 0 && draw_list->CmdBuffer.back().ElemCount == 0 && draw_list->CmdBuffer.back().UserCallback == NULL)
19308 cmd_count--;
19309 bool node_open = TreeNode(ptr_id: draw_list, fmt: "%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);
19310 if (draw_list == GetWindowDrawList())
19311 {
19312 SameLine();
19313 TextColored(col: ImVec4(1.0f, 0.4f, 0.4f, 1.0f), fmt: "CURRENTLY APPENDING"); // Can't display stats for active draw list! (we don't have the data double-buffered)
19314 if (node_open)
19315 TreePop();
19316 return;
19317 }
19318
19319 ImDrawList* fg_draw_list = viewport ? GetForegroundDrawList(viewport) : NULL; // Render additional visuals into the top-most draw list
19320 if (window && IsItemHovered() && fg_draw_list)
19321 fg_draw_list->AddRect(p_min: window->Pos, p_max: window->Pos + window->Size, IM_COL32(255, 255, 0, 255));
19322 if (!node_open)
19323 return;
19324
19325 if (window && !window->WasActive)
19326 TextDisabled(fmt: "Warning: owning Window is inactive. This DrawList is not being rendered!");
19327
19328 for (const ImDrawCmd* pcmd = draw_list->CmdBuffer.Data; pcmd < draw_list->CmdBuffer.Data + cmd_count; pcmd++)
19329 {
19330 if (pcmd->UserCallback)
19331 {
19332 BulletText(fmt: "Callback %p, user_data %p", pcmd->UserCallback, pcmd->UserCallbackData);
19333 continue;
19334 }
19335
19336 char buf[300];
19337 ImFormatString(buf, IM_ARRAYSIZE(buf), fmt: "DrawCmd:%5d tris, Tex 0x%p, ClipRect (%4.0f,%4.0f)-(%4.0f,%4.0f)",
19338 pcmd->ElemCount / 3, (void*)(intptr_t)pcmd->TextureId,
19339 pcmd->ClipRect.x, pcmd->ClipRect.y, pcmd->ClipRect.z, pcmd->ClipRect.w);
19340 bool pcmd_node_open = TreeNode(ptr_id: (void*)(pcmd - draw_list->CmdBuffer.begin()), fmt: "%s", buf);
19341 if (IsItemHovered() && (cfg->ShowDrawCmdMesh || cfg->ShowDrawCmdBoundingBoxes) && fg_draw_list)
19342 DebugNodeDrawCmdShowMeshAndBoundingBox(out_draw_list: fg_draw_list, draw_list, draw_cmd: pcmd, show_mesh: cfg->ShowDrawCmdMesh, show_aabb: cfg->ShowDrawCmdBoundingBoxes);
19343 if (!pcmd_node_open)
19344 continue;
19345
19346 // Calculate approximate coverage area (touched pixel count)
19347 // This will be in pixels squared as long there's no post-scaling happening to the renderer output.
19348 const ImDrawIdx* idx_buffer = (draw_list->IdxBuffer.Size > 0) ? draw_list->IdxBuffer.Data : NULL;
19349 const ImDrawVert* vtx_buffer = draw_list->VtxBuffer.Data + pcmd->VtxOffset;
19350 float total_area = 0.0f;
19351 for (unsigned int idx_n = pcmd->IdxOffset; idx_n < pcmd->IdxOffset + pcmd->ElemCount; )
19352 {
19353 ImVec2 triangle[3];
19354 for (int n = 0; n < 3; n++, idx_n++)
19355 triangle[n] = vtx_buffer[idx_buffer ? idx_buffer[idx_n] : idx_n].pos;
19356 total_area += ImTriangleArea(a: triangle[0], b: triangle[1], c: triangle[2]);
19357 }
19358
19359 // Display vertex information summary. Hover to get all triangles drawn in wire-frame
19360 ImFormatString(buf, IM_ARRAYSIZE(buf), fmt: "Mesh: ElemCount: %d, VtxOffset: +%d, IdxOffset: +%d, Area: ~%0.f px", pcmd->ElemCount, pcmd->VtxOffset, pcmd->IdxOffset, total_area);
19361 Selectable(label: buf);
19362 if (IsItemHovered() && fg_draw_list)
19363 DebugNodeDrawCmdShowMeshAndBoundingBox(out_draw_list: fg_draw_list, draw_list, draw_cmd: pcmd, show_mesh: true, show_aabb: false);
19364
19365 // Display individual triangles/vertices. Hover on to get the corresponding triangle highlighted.
19366 ImGuiListClipper clipper;
19367 clipper.Begin(items_count: pcmd->ElemCount / 3); // Manually coarse clip our print out of individual vertices to save CPU, only items that may be visible.
19368 while (clipper.Step())
19369 for (int prim = clipper.DisplayStart, idx_i = pcmd->IdxOffset + clipper.DisplayStart * 3; prim < clipper.DisplayEnd; prim++)
19370 {
19371 char* buf_p = buf, * buf_end = buf + IM_ARRAYSIZE(buf);
19372 ImVec2 triangle[3];
19373 for (int n = 0; n < 3; n++, idx_i++)
19374 {
19375 const ImDrawVert& v = vtx_buffer[idx_buffer ? idx_buffer[idx_i] : idx_i];
19376 triangle[n] = v.pos;
19377 buf_p += ImFormatString(buf: buf_p, buf_size: buf_end - buf_p, fmt: "%s %04d: pos (%8.2f,%8.2f), uv (%.6f,%.6f), col %08X\n",
19378 (n == 0) ? "Vert:" : " ", idx_i, v.pos.x, v.pos.y, v.uv.x, v.uv.y, v.col);
19379 }
19380
19381 Selectable(label: buf, selected: false);
19382 if (fg_draw_list && IsItemHovered())
19383 {
19384 ImDrawListFlags backup_flags = fg_draw_list->Flags;
19385 fg_draw_list->Flags &= ~ImDrawListFlags_AntiAliasedLines; // Disable AA on triangle outlines is more readable for very large and thin triangles.
19386 fg_draw_list->AddPolyline(points: triangle, num_points: 3, IM_COL32(255, 255, 0, 255), flags: ImDrawFlags_Closed, thickness: 1.0f);
19387 fg_draw_list->Flags = backup_flags;
19388 }
19389 }
19390 TreePop();
19391 }
19392 TreePop();
19393}
19394
19395// [DEBUG] Display mesh/aabb of a ImDrawCmd
19396void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, const ImDrawList* draw_list, const ImDrawCmd* draw_cmd, bool show_mesh, bool show_aabb)
19397{
19398 IM_ASSERT(show_mesh || show_aabb);
19399
19400 // Draw wire-frame version of all triangles
19401 ImRect clip_rect = draw_cmd->ClipRect;
19402 ImRect vtxs_rect(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX);
19403 ImDrawListFlags backup_flags = out_draw_list->Flags;
19404 out_draw_list->Flags &= ~ImDrawListFlags_AntiAliasedLines; // Disable AA on triangle outlines is more readable for very large and thin triangles.
19405 for (unsigned int idx_n = draw_cmd->IdxOffset, idx_end = draw_cmd->IdxOffset + draw_cmd->ElemCount; idx_n < idx_end; )
19406 {
19407 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
19408 ImDrawVert* vtx_buffer = draw_list->VtxBuffer.Data + draw_cmd->VtxOffset;
19409
19410 ImVec2 triangle[3];
19411 for (int n = 0; n < 3; n++, idx_n++)
19412 vtxs_rect.Add(p: (triangle[n] = vtx_buffer[idx_buffer ? idx_buffer[idx_n] : idx_n].pos));
19413 if (show_mesh)
19414 out_draw_list->AddPolyline(points: triangle, num_points: 3, IM_COL32(255, 255, 0, 255), flags: ImDrawFlags_Closed, thickness: 1.0f); // In yellow: mesh triangles
19415 }
19416 // Draw bounding boxes
19417 if (show_aabb)
19418 {
19419 out_draw_list->AddRect(p_min: ImFloor(v: clip_rect.Min), p_max: ImFloor(v: clip_rect.Max), IM_COL32(255, 0, 255, 255)); // In pink: clipping rectangle submitted to GPU
19420 out_draw_list->AddRect(p_min: ImFloor(v: vtxs_rect.Min), p_max: ImFloor(v: vtxs_rect.Max), IM_COL32(0, 255, 255, 255)); // In cyan: bounding box of triangles
19421 }
19422 out_draw_list->Flags = backup_flags;
19423}
19424
19425// [DEBUG] Display details for a single font, called by ShowStyleEditor().
19426void ImGui::DebugNodeFont(ImFont* font)
19427{
19428 bool opened = TreeNode(ptr_id: font, fmt: "Font: \"%s\"\n%.2f px, %d glyphs, %d file(s)",
19429 font->ConfigData ? font->ConfigData[0].Name : "", font->FontSize, font->Glyphs.Size, font->ConfigDataCount);
19430 SameLine();
19431 if (SmallButton(label: "Set as default"))
19432 GetIO().FontDefault = font;
19433 if (!opened)
19434 return;
19435
19436 // Display preview text
19437 PushFont(font);
19438 Text(fmt: "The quick brown fox jumps over the lazy dog");
19439 PopFont();
19440
19441 // Display details
19442 SetNextItemWidth(GetFontSize() * 8);
19443 DragFloat(label: "Font scale", v: &font->Scale, v_speed: 0.005f, v_min: 0.3f, v_max: 2.0f, format: "%.1f");
19444 SameLine(); MetricsHelpMarker(
19445 desc: "Note than the default embedded font is NOT meant to be scaled.\n\n"
19446 "Font are currently rendered into bitmaps at a given size at the time of building the atlas. "
19447 "You may oversample them to get some flexibility with scaling. "
19448 "You can also render at multiple sizes and select which one to use at runtime.\n\n"
19449 "(Glimmer of hope: the atlas system will be rewritten in the future to make scaling more flexible.)");
19450 Text(fmt: "Ascent: %f, Descent: %f, Height: %f", font->Ascent, font->Descent, font->Ascent - font->Descent);
19451 char c_str[5];
19452 Text(fmt: "Fallback character: '%s' (U+%04X)", ImTextCharToUtf8(out_buf: c_str, c: font->FallbackChar), font->FallbackChar);
19453 Text(fmt: "Ellipsis character: '%s' (U+%04X)", ImTextCharToUtf8(out_buf: c_str, c: font->EllipsisChar), font->EllipsisChar);
19454 const int surface_sqrt = (int)ImSqrt((float)font->MetricsTotalSurface);
19455 Text(fmt: "Texture Area: about %d px ~%dx%d px", font->MetricsTotalSurface, surface_sqrt, surface_sqrt);
19456 for (int config_i = 0; config_i < font->ConfigDataCount; config_i++)
19457 if (font->ConfigData)
19458 if (const ImFontConfig* cfg = &font->ConfigData[config_i])
19459 BulletText(fmt: "Input %d: \'%s\', Oversample: (%d,%d), PixelSnapH: %d, Offset: (%.1f,%.1f)",
19460 config_i, cfg->Name, cfg->OversampleH, cfg->OversampleV, cfg->PixelSnapH, cfg->GlyphOffset.x, cfg->GlyphOffset.y);
19461
19462 // Display all glyphs of the fonts in separate pages of 256 characters
19463 if (TreeNode(str_id: "Glyphs", fmt: "Glyphs (%d)", font->Glyphs.Size))
19464 {
19465 ImDrawList* draw_list = GetWindowDrawList();
19466 const ImU32 glyph_col = GetColorU32(idx: ImGuiCol_Text);
19467 const float cell_size = font->FontSize * 1;
19468 const float cell_spacing = GetStyle().ItemSpacing.y;
19469 for (unsigned int base = 0; base <= IM_UNICODE_CODEPOINT_MAX; base += 256)
19470 {
19471 // Skip ahead if a large bunch of glyphs are not present in the font (test in chunks of 4k)
19472 // This is only a small optimization to reduce the number of iterations when IM_UNICODE_MAX_CODEPOINT
19473 // is large // (if ImWchar==ImWchar32 we will do at least about 272 queries here)
19474 if (!(base & 4095) && font->IsGlyphRangeUnused(c_begin: base, c_last: base + 4095))
19475 {
19476 base += 4096 - 256;
19477 continue;
19478 }
19479
19480 int count = 0;
19481 for (unsigned int n = 0; n < 256; n++)
19482 if (font->FindGlyphNoFallback(c: (ImWchar)(base + n)))
19483 count++;
19484 if (count <= 0)
19485 continue;
19486 if (!TreeNode(ptr_id: (void*)(intptr_t)base, fmt: "U+%04X..U+%04X (%d %s)", base, base + 255, count, count > 1 ? "glyphs" : "glyph"))
19487 continue;
19488
19489 // Draw a 16x16 grid of glyphs
19490 ImVec2 base_pos = GetCursorScreenPos();
19491 for (unsigned int n = 0; n < 256; n++)
19492 {
19493 // We use ImFont::RenderChar as a shortcut because we don't have UTF-8 conversion functions
19494 // available here and thus cannot easily generate a zero-terminated UTF-8 encoded string.
19495 ImVec2 cell_p1(base_pos.x + (n % 16) * (cell_size + cell_spacing), base_pos.y + (n / 16) * (cell_size + cell_spacing));
19496 ImVec2 cell_p2(cell_p1.x + cell_size, cell_p1.y + cell_size);
19497 const ImFontGlyph* glyph = font->FindGlyphNoFallback(c: (ImWchar)(base + n));
19498 draw_list->AddRect(p_min: cell_p1, p_max: cell_p2, col: glyph ? IM_COL32(255, 255, 255, 100) : IM_COL32(255, 255, 255, 50));
19499 if (!glyph)
19500 continue;
19501 font->RenderChar(draw_list, size: cell_size, pos: cell_p1, col: glyph_col, c: (ImWchar)(base + n));
19502 if (IsMouseHoveringRect(r_min: cell_p1, r_max: cell_p2))
19503 {
19504 BeginTooltip();
19505 DebugNodeFontGlyph(font, glyph);
19506 EndTooltip();
19507 }
19508 }
19509 Dummy(size: ImVec2((cell_size + cell_spacing) * 16, (cell_size + cell_spacing) * 16));
19510 TreePop();
19511 }
19512 TreePop();
19513 }
19514 TreePop();
19515}
19516
19517void ImGui::DebugNodeFontGlyph(ImFont*, const ImFontGlyph* glyph)
19518{
19519 Text(fmt: "Codepoint: U+%04X", glyph->Codepoint);
19520 Separator();
19521 Text(fmt: "Visible: %d", glyph->Visible);
19522 Text(fmt: "AdvanceX: %.1f", glyph->AdvanceX);
19523 Text(fmt: "Pos: (%.2f,%.2f)->(%.2f,%.2f)", glyph->X0, glyph->Y0, glyph->X1, glyph->Y1);
19524 Text(fmt: "UV: (%.3f,%.3f)->(%.3f,%.3f)", glyph->U0, glyph->V0, glyph->U1, glyph->V1);
19525}
19526
19527// [DEBUG] Display contents of ImGuiStorage
19528void ImGui::DebugNodeStorage(ImGuiStorage* storage, const char* label)
19529{
19530 if (!TreeNode(str_id: label, fmt: "%s: %d entries, %d bytes", label, storage->Data.Size, storage->Data.size_in_bytes()))
19531 return;
19532 for (int n = 0; n < storage->Data.Size; n++)
19533 {
19534 const ImGuiStorage::ImGuiStoragePair& p = storage->Data[n];
19535 BulletText(fmt: "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.
19536 }
19537 TreePop();
19538}
19539
19540// [DEBUG] Display contents of ImGuiTabBar
19541void ImGui::DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label)
19542{
19543 // Standalone tab bars (not associated to docking/windows functionality) currently hold no discernible strings.
19544 char buf[256];
19545 char* p = buf;
19546 const char* buf_end = buf + IM_ARRAYSIZE(buf);
19547 const bool is_active = (tab_bar->PrevFrameVisible >= GetFrameCount() - 2);
19548 p += ImFormatString(buf: p, buf_size: buf_end - p, fmt: "%s 0x%08X (%d tabs)%s", label, tab_bar->ID, tab_bar->Tabs.Size, is_active ? "" : " *Inactive*");
19549 p += ImFormatString(buf: p, buf_size: buf_end - p, fmt: " { ");
19550 for (int tab_n = 0; tab_n < ImMin(lhs: tab_bar->Tabs.Size, rhs: 3); tab_n++)
19551 {
19552 ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
19553 p += ImFormatString(buf: p, buf_size: buf_end - p, fmt: "%s'%s'",
19554 tab_n > 0 ? ", " : "", (tab->Window || tab->NameOffset != -1) ? tab_bar->GetTabName(tab) : "???");
19555 }
19556 p += ImFormatString(buf: p, buf_size: buf_end - p, fmt: (tab_bar->Tabs.Size > 3) ? " ... }" : " } ");
19557 if (!is_active) { PushStyleColor(idx: ImGuiCol_Text, col: GetStyleColorVec4(idx: ImGuiCol_TextDisabled)); }
19558 bool open = TreeNode(str_id: label, fmt: "%s", buf);
19559 if (!is_active) { PopStyleColor(); }
19560 if (is_active && IsItemHovered())
19561 {
19562 ImDrawList* draw_list = GetForegroundDrawList();
19563 draw_list->AddRect(p_min: tab_bar->BarRect.Min, p_max: tab_bar->BarRect.Max, IM_COL32(255, 255, 0, 255));
19564 draw_list->AddLine(p1: ImVec2(tab_bar->ScrollingRectMinX, tab_bar->BarRect.Min.y), p2: ImVec2(tab_bar->ScrollingRectMinX, tab_bar->BarRect.Max.y), IM_COL32(0, 255, 0, 255));
19565 draw_list->AddLine(p1: ImVec2(tab_bar->ScrollingRectMaxX, tab_bar->BarRect.Min.y), p2: ImVec2(tab_bar->ScrollingRectMaxX, tab_bar->BarRect.Max.y), IM_COL32(0, 255, 0, 255));
19566 }
19567 if (open)
19568 {
19569 for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
19570 {
19571 const ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
19572 PushID(ptr_id: tab);
19573 if (SmallButton(label: "<")) { TabBarQueueReorder(tab_bar, tab, offset: -1); } SameLine(offset_from_start_x: 0, spacing_w: 2);
19574 if (SmallButton(label: ">")) { TabBarQueueReorder(tab_bar, tab, offset: +1); } SameLine();
19575 Text(fmt: "%02d%c Tab 0x%08X '%s' Offset: %.2f, Width: %.2f/%.2f",
19576 tab_n, (tab->ID == tab_bar->SelectedTabId) ? '*' : ' ', tab->ID, (tab->Window || tab->NameOffset != -1) ? tab_bar->GetTabName(tab) : "???", tab->Offset, tab->Width, tab->ContentWidth);
19577 PopID();
19578 }
19579 TreePop();
19580 }
19581}
19582
19583void ImGui::DebugNodeViewport(ImGuiViewportP* viewport)
19584{
19585 SetNextItemOpen(is_open: true, cond: ImGuiCond_Once);
19586 if (TreeNode(ptr_id: (void*)(intptr_t)viewport->ID, fmt: "Viewport #%d, ID: 0x%08X, Parent: 0x%08X, Window: \"%s\"", viewport->Idx, viewport->ID, viewport->ParentViewportId, viewport->Window ? viewport->Window->Name : "N/A"))
19587 {
19588 ImGuiWindowFlags flags = viewport->Flags;
19589 BulletText(fmt: "Main Pos: (%.0f,%.0f), Size: (%.0f,%.0f)\nWorkArea Offset Left: %.0f Top: %.0f, Right: %.0f, Bottom: %.0f\nMonitor: %d, DpiScale: %.0f%%",
19590 viewport->Pos.x, viewport->Pos.y, viewport->Size.x, viewport->Size.y,
19591 viewport->WorkOffsetMin.x, viewport->WorkOffsetMin.y, viewport->WorkOffsetMax.x, viewport->WorkOffsetMax.y,
19592 viewport->PlatformMonitor, viewport->DpiScale * 100.0f);
19593 if (viewport->Idx > 0) { SameLine(); if (SmallButton(label: "Reset Pos")) { viewport->Pos = ImVec2(200, 200); viewport->UpdateWorkRect(); if (viewport->Window) viewport->Window->Pos = viewport->Pos; } }
19594 BulletText(fmt: "Flags: 0x%04X =%s%s%s%s%s%s%s%s%s%s%s%s", viewport->Flags,
19595 //(flags & ImGuiViewportFlags_IsPlatformWindow) ? " IsPlatformWindow" : "", // Omitting because it is the standard
19596 (flags & ImGuiViewportFlags_IsPlatformMonitor) ? " IsPlatformMonitor" : "",
19597 (flags & ImGuiViewportFlags_OwnedByApp) ? " OwnedByApp" : "",
19598 (flags & ImGuiViewportFlags_NoDecoration) ? " NoDecoration" : "",
19599 (flags & ImGuiViewportFlags_NoTaskBarIcon) ? " NoTaskBarIcon" : "",
19600 (flags & ImGuiViewportFlags_NoFocusOnAppearing) ? " NoFocusOnAppearing" : "",
19601 (flags & ImGuiViewportFlags_NoFocusOnClick) ? " NoFocusOnClick" : "",
19602 (flags & ImGuiViewportFlags_NoInputs) ? " NoInputs" : "",
19603 (flags & ImGuiViewportFlags_NoRendererClear) ? " NoRendererClear" : "",
19604 (flags & ImGuiViewportFlags_TopMost) ? " TopMost" : "",
19605 (flags & ImGuiViewportFlags_Minimized) ? " Minimized" : "",
19606 (flags & ImGuiViewportFlags_NoAutoMerge) ? " NoAutoMerge" : "",
19607 (flags & ImGuiViewportFlags_CanHostOtherWindows) ? " CanHostOtherWindows" : "");
19608 for (int layer_i = 0; layer_i < IM_ARRAYSIZE(viewport->DrawDataBuilder.Layers); layer_i++)
19609 for (int draw_list_i = 0; draw_list_i < viewport->DrawDataBuilder.Layers[layer_i].Size; draw_list_i++)
19610 DebugNodeDrawList(NULL, viewport, draw_list: viewport->DrawDataBuilder.Layers[layer_i][draw_list_i], label: "DrawList");
19611 TreePop();
19612 }
19613}
19614
19615void ImGui::DebugNodeWindow(ImGuiWindow* window, const char* label)
19616{
19617 if (window == NULL)
19618 {
19619 BulletText(fmt: "%s: NULL", label);
19620 return;
19621 }
19622
19623 ImGuiContext& g = *GImGui;
19624 const bool is_active = window->WasActive;
19625 ImGuiTreeNodeFlags tree_node_flags = (window == g.NavWindow) ? ImGuiTreeNodeFlags_Selected : ImGuiTreeNodeFlags_None;
19626 if (!is_active) { PushStyleColor(idx: ImGuiCol_Text, col: GetStyleColorVec4(idx: ImGuiCol_TextDisabled)); }
19627 const bool open = TreeNodeEx(str_id: label, flags: tree_node_flags, fmt: "%s '%s'%s", label, window->Name, is_active ? "" : " *Inactive*");
19628 if (!is_active) { PopStyleColor(); }
19629 if (IsItemHovered() && is_active)
19630 GetForegroundDrawList(window)->AddRect(p_min: window->Pos, p_max: window->Pos + window->Size, IM_COL32(255, 255, 0, 255));
19631 if (!open)
19632 return;
19633
19634 if (window->MemoryCompacted)
19635 TextDisabled(fmt: "Note: some memory buffers have been compacted/freed.");
19636
19637 ImGuiWindowFlags flags = window->Flags;
19638 DebugNodeDrawList(window, viewport: window->Viewport, draw_list: window->DrawList, label: "DrawList");
19639 BulletText(fmt: "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);
19640 BulletText(fmt: "Flags: 0x%08X (%s%s%s%s%s%s%s%s%s..)", flags,
19641 (flags & ImGuiWindowFlags_ChildWindow) ? "Child " : "", (flags & ImGuiWindowFlags_Tooltip) ? "Tooltip " : "", (flags & ImGuiWindowFlags_Popup) ? "Popup " : "",
19642 (flags & ImGuiWindowFlags_Modal) ? "Modal " : "", (flags & ImGuiWindowFlags_ChildMenu) ? "ChildMenu " : "", (flags & ImGuiWindowFlags_NoSavedSettings) ? "NoSavedSettings " : "",
19643 (flags & ImGuiWindowFlags_NoMouseInputs)? "NoMouseInputs":"", (flags & ImGuiWindowFlags_NoNavInputs) ? "NoNavInputs" : "", (flags & ImGuiWindowFlags_AlwaysAutoResize) ? "AlwaysAutoResize" : "");
19644 BulletText(fmt: "WindowClassId: 0x%08X", window->WindowClass.ClassId);
19645 BulletText(fmt: "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" : "");
19646 BulletText(fmt: "Active: %d/%d, WriteAccessed: %d, BeginOrderWithinContext: %d", window->Active, window->WasActive, window->WriteAccessed, (window->Active || window->WasActive) ? window->BeginOrderWithinContext : -1);
19647 BulletText(fmt: "Appearing: %d, Hidden: %d (CanSkip %d Cannot %d), SkipItems: %d", window->Appearing, window->Hidden, window->HiddenFramesCanSkipItems, window->HiddenFramesCannotSkipItems, window->SkipItems);
19648 for (int layer = 0; layer < ImGuiNavLayer_COUNT; layer++)
19649 {
19650 ImRect r = window->NavRectRel[layer];
19651 if (r.Min.x >= r.Max.y && r.Min.y >= r.Max.y)
19652 BulletText(fmt: "NavLastIds[%d]: 0x%08X", layer, window->NavLastIds[layer]);
19653 else
19654 BulletText(fmt: "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);
19655 DebugLocateItemOnHover(target_id: window->NavLastIds[layer]);
19656 }
19657 BulletText(fmt: "NavLayersActiveMask: %X, NavLastChildNavWindow: %s", window->DC.NavLayersActiveMask, window->NavLastChildNavWindow ? window->NavLastChildNavWindow->Name : "NULL");
19658
19659 BulletText(fmt: "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);
19660 BulletText(fmt: "ViewportMonitor: %d", window->Viewport ? window->Viewport->PlatformMonitor : -1);
19661 BulletText(fmt: "DockId: 0x%04X, DockOrder: %d, Act: %d, Vis: %d", window->DockId, window->DockOrder, window->DockIsActive, window->DockTabIsVisible);
19662 if (window->DockNode || window->DockNodeAsHost)
19663 DebugNodeDockNode(node: window->DockNodeAsHost ? window->DockNodeAsHost : window->DockNode, label: window->DockNodeAsHost ? "DockNodeAsHost" : "DockNode");
19664
19665 if (window->RootWindow != window) { DebugNodeWindow(window: window->RootWindow, label: "RootWindow"); }
19666 if (window->RootWindowDockTree != window->RootWindow) { DebugNodeWindow(window: window->RootWindowDockTree, label: "RootWindowDockTree"); }
19667 if (window->ParentWindow != NULL) { DebugNodeWindow(window: window->ParentWindow, label: "ParentWindow"); }
19668 if (window->DC.ChildWindows.Size > 0) { DebugNodeWindowsList(windows: &window->DC.ChildWindows, label: "ChildWindows"); }
19669 if (window->ColumnsStorage.Size > 0 && TreeNode(str_id: "Columns", fmt: "Columns sets (%d)", window->ColumnsStorage.Size))
19670 {
19671 for (int n = 0; n < window->ColumnsStorage.Size; n++)
19672 DebugNodeColumns(columns: &window->ColumnsStorage[n]);
19673 TreePop();
19674 }
19675 DebugNodeStorage(storage: &window->StateStorage, label: "Storage");
19676 TreePop();
19677}
19678
19679void ImGui::DebugNodeWindowSettings(ImGuiWindowSettings* settings)
19680{
19681 Text(fmt: "0x%08X \"%s\" Pos (%d,%d) Size (%d,%d) Collapsed=%d",
19682 settings->ID, settings->GetName(), settings->Pos.x, settings->Pos.y, settings->Size.x, settings->Size.y, settings->Collapsed);
19683}
19684
19685void ImGui::DebugNodeWindowsList(ImVector<ImGuiWindow*>* windows, const char* label)
19686{
19687 if (!TreeNode(str_id: label, fmt: "%s (%d)", label, windows->Size))
19688 return;
19689 for (int i = windows->Size - 1; i >= 0; i--) // Iterate front to back
19690 {
19691 PushID(ptr_id: (*windows)[i]);
19692 DebugNodeWindow(window: (*windows)[i], label: "Window");
19693 PopID();
19694 }
19695 TreePop();
19696}
19697
19698// FIXME-OPT: This is technically suboptimal, but it is simpler this way.
19699void ImGui::DebugNodeWindowsListByBeginStackParent(ImGuiWindow** windows, int windows_size, ImGuiWindow* parent_in_begin_stack)
19700{
19701 for (int i = 0; i < windows_size; i++)
19702 {
19703 ImGuiWindow* window = windows[i];
19704 if (window->ParentWindowInBeginStack != parent_in_begin_stack)
19705 continue;
19706 char buf[20];
19707 ImFormatString(buf, IM_ARRAYSIZE(buf), fmt: "[%04d] Window", window->BeginOrderWithinContext);
19708 //BulletText("[%04d] Window '%s'", window->BeginOrderWithinContext, window->Name);
19709 DebugNodeWindow(window, label: buf);
19710 Indent();
19711 DebugNodeWindowsListByBeginStackParent(windows: windows + i + 1, windows_size: windows_size - i - 1, parent_in_begin_stack: window);
19712 Unindent();
19713 }
19714}
19715
19716//-----------------------------------------------------------------------------
19717// [SECTION] DEBUG LOG WINDOW
19718//-----------------------------------------------------------------------------
19719
19720void ImGui::DebugLog(const char* fmt, ...)
19721{
19722 va_list args;
19723 va_start(args, fmt);
19724 DebugLogV(fmt, args);
19725 va_end(args);
19726}
19727
19728void ImGui::DebugLogV(const char* fmt, va_list args)
19729{
19730 ImGuiContext& g = *GImGui;
19731 const int old_size = g.DebugLogBuf.size();
19732 g.DebugLogBuf.appendf(fmt: "[%05d] ", g.FrameCount);
19733 g.DebugLogBuf.appendfv(fmt, args);
19734 if (g.DebugLogFlags & ImGuiDebugLogFlags_OutputToTTY)
19735 IMGUI_DEBUG_PRINTF("%s", g.DebugLogBuf.begin() + old_size);
19736 g.DebugLogIndex.append(base: g.DebugLogBuf.c_str(), old_size, new_size: g.DebugLogBuf.size());
19737}
19738
19739void ImGui::ShowDebugLogWindow(bool* p_open)
19740{
19741 ImGuiContext& g = *GImGui;
19742 if (!(g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize))
19743 SetNextWindowSize(size: ImVec2(0.0f, GetFontSize() * 12.0f), cond: ImGuiCond_FirstUseEver);
19744 if (!Begin(name: "Dear ImGui Debug Log", p_open) || GetCurrentWindow()->BeginCount > 1)
19745 {
19746 End();
19747 return;
19748 }
19749
19750 AlignTextToFramePadding();
19751 Text(fmt: "Log events:");
19752 SameLine(); CheckboxFlags(label: "All", flags: &g.DebugLogFlags, flags_value: ImGuiDebugLogFlags_EventMask_);
19753 SameLine(); CheckboxFlags(label: "ActiveId", flags: &g.DebugLogFlags, flags_value: ImGuiDebugLogFlags_EventActiveId);
19754 SameLine(); CheckboxFlags(label: "Focus", flags: &g.DebugLogFlags, flags_value: ImGuiDebugLogFlags_EventFocus);
19755 SameLine(); CheckboxFlags(label: "Popup", flags: &g.DebugLogFlags, flags_value: ImGuiDebugLogFlags_EventPopup);
19756 SameLine(); CheckboxFlags(label: "Nav", flags: &g.DebugLogFlags, flags_value: ImGuiDebugLogFlags_EventNav);
19757 SameLine(); CheckboxFlags(label: "Clipper", flags: &g.DebugLogFlags, flags_value: ImGuiDebugLogFlags_EventClipper);
19758 SameLine(); CheckboxFlags(label: "IO", flags: &g.DebugLogFlags, flags_value: ImGuiDebugLogFlags_EventIO);
19759 SameLine(); CheckboxFlags(label: "Docking", flags: &g.DebugLogFlags, flags_value: ImGuiDebugLogFlags_EventDocking);
19760 SameLine(); CheckboxFlags(label: "Viewport", flags: &g.DebugLogFlags, flags_value: ImGuiDebugLogFlags_EventViewport);
19761
19762 if (SmallButton(label: "Clear"))
19763 {
19764 g.DebugLogBuf.clear();
19765 g.DebugLogIndex.clear();
19766 }
19767 SameLine();
19768 if (SmallButton(label: "Copy"))
19769 SetClipboardText(g.DebugLogBuf.c_str());
19770 BeginChild(str_id: "##log", size_arg: ImVec2(0.0f, 0.0f), border: true, extra_flags: ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar);
19771
19772 ImGuiListClipper clipper;
19773 clipper.Begin(items_count: g.DebugLogIndex.size());
19774 while (clipper.Step())
19775 for (int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++)
19776 {
19777 const char* line_begin = g.DebugLogIndex.get_line_begin(base: g.DebugLogBuf.c_str(), n: line_no);
19778 const char* line_end = g.DebugLogIndex.get_line_end(base: g.DebugLogBuf.c_str(), n: line_no);
19779 TextUnformatted(text: line_begin, text_end: line_end);
19780 ImRect text_rect = g.LastItemData.Rect;
19781 if (IsItemHovered())
19782 for (const char* p = line_begin; p < line_end - 10; p++)
19783 {
19784 ImGuiID id = 0;
19785 if (p[0] != '0' || (p[1] != 'x' && p[1] != 'X') || sscanf(s: p + 2, format: "%X", &id) != 1)
19786 continue;
19787 ImVec2 p0 = CalcTextSize(text: line_begin, text_end: p);
19788 ImVec2 p1 = CalcTextSize(text: p, text_end: p + 10);
19789 g.LastItemData.Rect = ImRect(text_rect.Min + ImVec2(p0.x, 0.0f), text_rect.Min + ImVec2(p0.x + p1.x, p1.y));
19790 if (IsMouseHoveringRect(r_min: g.LastItemData.Rect.Min, r_max: g.LastItemData.Rect.Max, clip: true))
19791 DebugLocateItemOnHover(target_id: id);
19792 p += 10;
19793 }
19794 }
19795 if (GetScrollY() >= GetScrollMaxY())
19796 SetScrollHereY(1.0f);
19797 EndChild();
19798
19799 End();
19800}
19801
19802//-----------------------------------------------------------------------------
19803// [SECTION] OTHER DEBUG TOOLS (ITEM PICKER, STACK TOOL)
19804//-----------------------------------------------------------------------------
19805
19806static const ImU32 DEBUG_LOCATE_ITEM_COLOR = IM_COL32(0, 255, 0, 255); // Green
19807
19808void ImGui::DebugLocateItem(ImGuiID target_id)
19809{
19810 ImGuiContext& g = *GImGui;
19811 g.DebugLocateId = target_id;
19812 g.DebugLocateFrames = 2;
19813}
19814
19815void ImGui::DebugLocateItemOnHover(ImGuiID target_id)
19816{
19817 if (target_id == 0 || !IsItemHovered(flags: ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_AllowWhenBlockedByPopup))
19818 return;
19819 ImGuiContext& g = *GImGui;
19820 DebugLocateItem(target_id);
19821 GetForegroundDrawList(window: g.CurrentWindow)->AddRect(p_min: g.LastItemData.Rect.Min - ImVec2(3.0f, 3.0f), p_max: g.LastItemData.Rect.Max + ImVec2(3.0f, 3.0f), col: DEBUG_LOCATE_ITEM_COLOR);
19822}
19823
19824void ImGui::DebugLocateItemResolveWithLastItem()
19825{
19826 ImGuiContext& g = *GImGui;
19827 ImGuiLastItemData item_data = g.LastItemData;
19828 g.DebugLocateId = 0;
19829 ImDrawList* draw_list = GetForegroundDrawList(window: g.CurrentWindow);
19830 ImRect r = item_data.Rect;
19831 r.Expand(amount: 3.0f);
19832 ImVec2 p1 = g.IO.MousePos;
19833 ImVec2 p2 = ImVec2((p1.x < r.Min.x) ? r.Min.x : (p1.x > r.Max.x) ? r.Max.x : p1.x, (p1.y < r.Min.y) ? r.Min.y : (p1.y > r.Max.y) ? r.Max.y : p1.y);
19834 draw_list->AddRect(p_min: r.Min, p_max: r.Max, col: DEBUG_LOCATE_ITEM_COLOR);
19835 draw_list->AddLine(p1, p2, col: DEBUG_LOCATE_ITEM_COLOR);
19836}
19837
19838// [DEBUG] Item picker tool - start with DebugStartItemPicker() - useful to visually select an item and break into its call-stack.
19839void ImGui::UpdateDebugToolItemPicker()
19840{
19841 ImGuiContext& g = *GImGui;
19842 g.DebugItemPickerBreakId = 0;
19843 if (!g.DebugItemPickerActive)
19844 return;
19845
19846 const ImGuiID hovered_id = g.HoveredIdPreviousFrame;
19847 SetMouseCursor(ImGuiMouseCursor_Hand);
19848 if (IsKeyPressed(key: ImGuiKey_Escape))
19849 g.DebugItemPickerActive = false;
19850 const bool change_mapping = g.IO.KeyMods == (ImGuiMod_Ctrl | ImGuiMod_Shift);
19851 if (!change_mapping && IsMouseClicked(button: g.DebugItemPickerMouseButton) && hovered_id)
19852 {
19853 g.DebugItemPickerBreakId = hovered_id;
19854 g.DebugItemPickerActive = false;
19855 }
19856 for (int mouse_button = 0; mouse_button < 3; mouse_button++)
19857 if (change_mapping && IsMouseClicked(button: mouse_button))
19858 g.DebugItemPickerMouseButton = (ImU8)mouse_button;
19859 SetNextWindowBgAlpha(0.70f);
19860 BeginTooltip();
19861 Text(fmt: "HoveredId: 0x%08X", hovered_id);
19862 Text(fmt: "Press ESC to abort picking.");
19863 const char* mouse_button_names[] = { "Left", "Right", "Middle" };
19864 if (change_mapping)
19865 Text(fmt: "Remap w/ Ctrl+Shift: click anywhere to select new mouse button.");
19866 else
19867 TextColored(col: GetStyleColorVec4(idx: hovered_id ? ImGuiCol_Text : ImGuiCol_TextDisabled), fmt: "Click %s Button to break in debugger! (remap w/ Ctrl+Shift)", mouse_button_names[g.DebugItemPickerMouseButton]);
19868 EndTooltip();
19869}
19870
19871// [DEBUG] Stack Tool: update queries. Called by NewFrame()
19872void ImGui::UpdateDebugToolStackQueries()
19873{
19874 ImGuiContext& g = *GImGui;
19875 ImGuiStackTool* tool = &g.DebugStackTool;
19876
19877 // Clear hook when stack tool is not visible
19878 g.DebugHookIdInfo = 0;
19879 if (g.FrameCount != tool->LastActiveFrame + 1)
19880 return;
19881
19882 // Update queries. The steps are: -1: query Stack, >= 0: query each stack item
19883 // We can only perform 1 ID Info query every frame. This is designed so the GetID() tests are cheap and constant-time
19884 const ImGuiID query_id = g.HoveredIdPreviousFrame ? g.HoveredIdPreviousFrame : g.ActiveId;
19885 if (tool->QueryId != query_id)
19886 {
19887 tool->QueryId = query_id;
19888 tool->StackLevel = -1;
19889 tool->Results.resize(new_size: 0);
19890 }
19891 if (query_id == 0)
19892 return;
19893
19894 // Advance to next stack level when we got our result, or after 2 frames (in case we never get a result)
19895 int stack_level = tool->StackLevel;
19896 if (stack_level >= 0 && stack_level < tool->Results.Size)
19897 if (tool->Results[stack_level].QuerySuccess || tool->Results[stack_level].QueryFrameCount > 2)
19898 tool->StackLevel++;
19899
19900 // Update hook
19901 stack_level = tool->StackLevel;
19902 if (stack_level == -1)
19903 g.DebugHookIdInfo = query_id;
19904 if (stack_level >= 0 && stack_level < tool->Results.Size)
19905 {
19906 g.DebugHookIdInfo = tool->Results[stack_level].ID;
19907 tool->Results[stack_level].QueryFrameCount++;
19908 }
19909}
19910
19911// [DEBUG] Stack tool: hooks called by GetID() family functions
19912void ImGui::DebugHookIdInfo(ImGuiID id, ImGuiDataType data_type, const void* data_id, const void* data_id_end)
19913{
19914 ImGuiContext& g = *GImGui;
19915 ImGuiWindow* window = g.CurrentWindow;
19916 ImGuiStackTool* tool = &g.DebugStackTool;
19917
19918 // Step 0: stack query
19919 // This assumes that the ID was computed with the current ID stack, which tends to be the case for our widget.
19920 if (tool->StackLevel == -1)
19921 {
19922 tool->StackLevel++;
19923 tool->Results.resize(new_size: window->IDStack.Size + 1, v: ImGuiStackLevelInfo());
19924 for (int n = 0; n < window->IDStack.Size + 1; n++)
19925 tool->Results[n].ID = (n < window->IDStack.Size) ? window->IDStack[n] : id;
19926 return;
19927 }
19928
19929 // Step 1+: query for individual level
19930 IM_ASSERT(tool->StackLevel >= 0);
19931 if (tool->StackLevel != window->IDStack.Size)
19932 return;
19933 ImGuiStackLevelInfo* info = &tool->Results[tool->StackLevel];
19934 IM_ASSERT(info->ID == id && info->QueryFrameCount > 0);
19935
19936 switch (data_type)
19937 {
19938 case ImGuiDataType_S32:
19939 ImFormatString(buf: info->Desc, IM_ARRAYSIZE(info->Desc), fmt: "%d", (int)(intptr_t)data_id);
19940 break;
19941 case ImGuiDataType_String:
19942 ImFormatString(buf: info->Desc, IM_ARRAYSIZE(info->Desc), fmt: "%.*s", data_id_end ? (int)((const char*)data_id_end - (const char*)data_id) : (int)strlen(s: (const char*)data_id), (const char*)data_id);
19943 break;
19944 case ImGuiDataType_Pointer:
19945 ImFormatString(buf: info->Desc, IM_ARRAYSIZE(info->Desc), fmt: "(void*)0x%p", data_id);
19946 break;
19947 case ImGuiDataType_ID:
19948 if (info->Desc[0] != 0) // PushOverrideID() is often used to avoid hashing twice, which would lead to 2 calls to DebugHookIdInfo(). We prioritize the first one.
19949 return;
19950 ImFormatString(buf: info->Desc, IM_ARRAYSIZE(info->Desc), fmt: "0x%08X [override]", id);
19951 break;
19952 default:
19953 IM_ASSERT(0);
19954 }
19955 info->QuerySuccess = true;
19956 info->DataType = data_type;
19957}
19958
19959static int StackToolFormatLevelInfo(ImGuiStackTool* tool, int n, bool format_for_ui, char* buf, size_t buf_size)
19960{
19961 ImGuiStackLevelInfo* info = &tool->Results[n];
19962 ImGuiWindow* window = (info->Desc[0] == 0 && n == 0) ? ImGui::FindWindowByID(id: info->ID) : NULL;
19963 if (window) // Source: window name (because the root ID don't call GetID() and so doesn't get hooked)
19964 return ImFormatString(buf, buf_size, fmt: format_for_ui ? "\"%s\" [window]" : "%s", window->Name);
19965 if (info->QuerySuccess) // Source: GetID() hooks (prioritize over ItemInfo() because we frequently use patterns like: PushID(str), Button("") where they both have same id)
19966 return ImFormatString(buf, buf_size, fmt: (format_for_ui && info->DataType == ImGuiDataType_String) ? "\"%s\"" : "%s", info->Desc);
19967 if (tool->StackLevel < tool->Results.Size) // Only start using fallback below when all queries are done, so during queries we don't flickering ??? markers.
19968 return (*buf = 0);
19969#ifdef IMGUI_ENABLE_TEST_ENGINE
19970 if (const char* label = ImGuiTestEngine_FindItemDebugLabel(GImGui, info->ID)) // Source: ImGuiTestEngine's ItemInfo()
19971 return ImFormatString(buf, buf_size, format_for_ui ? "??? \"%s\"" : "%s", label);
19972#endif
19973 return ImFormatString(buf, buf_size, fmt: "???");
19974}
19975
19976// Stack Tool: Display UI
19977void ImGui::ShowStackToolWindow(bool* p_open)
19978{
19979 ImGuiContext& g = *GImGui;
19980 if (!(g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize))
19981 SetNextWindowSize(size: ImVec2(0.0f, GetFontSize() * 8.0f), cond: ImGuiCond_FirstUseEver);
19982 if (!Begin(name: "Dear ImGui Stack Tool", p_open) || GetCurrentWindow()->BeginCount > 1)
19983 {
19984 End();
19985 return;
19986 }
19987
19988 // Display hovered/active status
19989 ImGuiStackTool* tool = &g.DebugStackTool;
19990 const ImGuiID hovered_id = g.HoveredIdPreviousFrame;
19991 const ImGuiID active_id = g.ActiveId;
19992#ifdef IMGUI_ENABLE_TEST_ENGINE
19993 Text("HoveredId: 0x%08X (\"%s\"), ActiveId: 0x%08X (\"%s\")", hovered_id, hovered_id ? ImGuiTestEngine_FindItemDebugLabel(&g, hovered_id) : "", active_id, active_id ? ImGuiTestEngine_FindItemDebugLabel(&g, active_id) : "");
19994#else
19995 Text(fmt: "HoveredId: 0x%08X, ActiveId: 0x%08X", hovered_id, active_id);
19996#endif
19997 SameLine();
19998 MetricsHelpMarker(desc: "Hover an item with the mouse to display elements of the ID Stack leading to the item's final ID.\nEach level of the stack correspond to a PushID() call.\nAll levels of the stack are hashed together to make the final ID of a widget (ID displayed at the bottom level of the stack).\nRead FAQ entry about the ID stack for details.");
19999
20000 // CTRL+C to copy path
20001 const float time_since_copy = (float)g.Time - tool->CopyToClipboardLastTime;
20002 Checkbox(label: "Ctrl+C: copy path to clipboard", v: &tool->CopyToClipboardOnCtrlC);
20003 SameLine();
20004 TextColored(col: (time_since_copy >= 0.0f && time_since_copy < 0.75f && ImFmod(time_since_copy, 0.25f) < 0.25f * 0.5f) ? ImVec4(1.f, 1.f, 0.3f, 1.f) : ImVec4(), fmt: "*COPIED*");
20005 if (tool->CopyToClipboardOnCtrlC && IsKeyDown(key: ImGuiMod_Ctrl) && IsKeyPressed(key: ImGuiKey_C))
20006 {
20007 tool->CopyToClipboardLastTime = (float)g.Time;
20008 char* p = g.TempBuffer.Data;
20009 char* p_end = p + g.TempBuffer.Size;
20010 for (int stack_n = 0; stack_n < tool->Results.Size && p + 3 < p_end; stack_n++)
20011 {
20012 *p++ = '/';
20013 char level_desc[256];
20014 StackToolFormatLevelInfo(tool, n: stack_n, format_for_ui: false, buf: level_desc, IM_ARRAYSIZE(level_desc));
20015 for (int n = 0; level_desc[n] && p + 2 < p_end; n++)
20016 {
20017 if (level_desc[n] == '/')
20018 *p++ = '\\';
20019 *p++ = level_desc[n];
20020 }
20021 }
20022 *p = '\0';
20023 SetClipboardText(g.TempBuffer.Data);
20024 }
20025
20026 // Display decorated stack
20027 tool->LastActiveFrame = g.FrameCount;
20028 if (tool->Results.Size > 0 && BeginTable(str_id: "##table", column: 3, flags: ImGuiTableFlags_Borders))
20029 {
20030 const float id_width = CalcTextSize(text: "0xDDDDDDDD").x;
20031 TableSetupColumn(label: "Seed", flags: ImGuiTableColumnFlags_WidthFixed, init_width_or_weight: id_width);
20032 TableSetupColumn(label: "PushID", flags: ImGuiTableColumnFlags_WidthStretch);
20033 TableSetupColumn(label: "Result", flags: ImGuiTableColumnFlags_WidthFixed, init_width_or_weight: id_width);
20034 TableHeadersRow();
20035 for (int n = 0; n < tool->Results.Size; n++)
20036 {
20037 ImGuiStackLevelInfo* info = &tool->Results[n];
20038 TableNextColumn();
20039 Text(fmt: "0x%08X", (n > 0) ? tool->Results[n - 1].ID : 0);
20040 TableNextColumn();
20041 StackToolFormatLevelInfo(tool, n, format_for_ui: true, buf: g.TempBuffer.Data, buf_size: g.TempBuffer.Size);
20042 TextUnformatted(text: g.TempBuffer.Data);
20043 TableNextColumn();
20044 Text(fmt: "0x%08X", info->ID);
20045 if (n == tool->Results.Size - 1)
20046 TableSetBgColor(target: ImGuiTableBgTarget_CellBg, color: GetColorU32(idx: ImGuiCol_Header));
20047 }
20048 EndTable();
20049 }
20050 End();
20051}
20052
20053#else
20054
20055void ImGui::ShowMetricsWindow(bool*) {}
20056void ImGui::ShowFontAtlas(ImFontAtlas*) {}
20057void ImGui::DebugNodeColumns(ImGuiOldColumns*) {}
20058void ImGui::DebugNodeDrawList(ImGuiWindow*, ImGuiViewportP*, const ImDrawList*, const char*) {}
20059void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList*, const ImDrawList*, const ImDrawCmd*, bool, bool) {}
20060void ImGui::DebugNodeFont(ImFont*) {}
20061void ImGui::DebugNodeStorage(ImGuiStorage*, const char*) {}
20062void ImGui::DebugNodeTabBar(ImGuiTabBar*, const char*) {}
20063void ImGui::DebugNodeWindow(ImGuiWindow*, const char*) {}
20064void ImGui::DebugNodeWindowSettings(ImGuiWindowSettings*) {}
20065void ImGui::DebugNodeWindowsList(ImVector<ImGuiWindow*>*, const char*) {}
20066void ImGui::DebugNodeViewport(ImGuiViewportP*) {}
20067
20068void ImGui::DebugLog(const char*, ...) {}
20069void ImGui::DebugLogV(const char*, va_list) {}
20070void ImGui::ShowDebugLogWindow(bool*) {}
20071void ImGui::ShowStackToolWindow(bool*) {}
20072void ImGui::DebugHookIdInfo(ImGuiID, ImGuiDataType, const void*, const void*) {}
20073void ImGui::UpdateDebugToolItemPicker() {}
20074void ImGui::UpdateDebugToolStackQueries() {}
20075
20076#endif // #ifndef IMGUI_DISABLE_DEBUG_TOOLS
20077
20078//-----------------------------------------------------------------------------
20079
20080// Include imgui_user.inl at the end of imgui.cpp to access private data/functions that aren't exposed.
20081// 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.
20082#ifdef IMGUI_INCLUDE_IMGUI_USER_INL
20083#include "imgui_user.inl"
20084#endif
20085
20086//-----------------------------------------------------------------------------
20087
20088#endif // #ifndef IMGUI_DISABLE
20089

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com

source code of flutter_engine/third_party/imgui/imgui.cpp