1 // dear imgui, v1.79 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/3075 (please post your screenshots/video there!)
15 // - Glossary https://github.com/ocornut/imgui/wiki/Glossary
16 // - Wiki https://github.com/ocornut/imgui/wiki
17 // - Issues & support https://github.com/ocornut/imgui/issues
18
19 // Developed by Omar Cornut and every direct or indirect contributors to the GitHub.
20 // See LICENSE.txt for copyright and licensing details (standard MIT License).
21 // This library is free but needs your support to sustain development and maintenance.
22 // Businesses: you can support continued development via invoiced technical support, maintenance and sponsoring contracts. Please reach out to "contact AT dearimgui.org".
23 // Individuals: you can support continued development via donations. See docs/README or web page.
24
25 // It is recommended that you don't modify imgui.cpp! It will become difficult for you to update the library.
26 // Note that 'ImGui::' being a namespace, you can add functions into the namespace from your own source files, without
27 // modifying imgui.h or imgui.cpp. You may include imgui_internal.h to access internal data structures, but it doesn't
28 // come with any guarantee of forward compatibility. Discussing your changes on the GitHub Issue Tracker may lead you
29 // to a better solution or official support for them.
30
31 /*
32
33 Index of this file:
34
35 DOCUMENTATION
36
37 - MISSION STATEMENT
38 - END-USER GUIDE
39 - PROGRAMMER GUIDE
40 - READ FIRST
41 - HOW TO UPDATE TO A NEWER VERSION OF DEAR IMGUI
42 - GETTING STARTED WITH INTEGRATING DEAR IMGUI IN YOUR CODE/ENGINE
43 - HOW A SIMPLE APPLICATION MAY LOOK LIKE
44 - HOW A SIMPLE RENDERING FUNCTION MAY LOOK LIKE
45 - USING GAMEPAD/KEYBOARD NAVIGATION CONTROLS
46 - API BREAKING CHANGES (read me when you update!)
47 - FREQUENTLY ASKED QUESTIONS (FAQ)
48 - Read all answers online: https://www.dearimgui.org/faq, or in docs/FAQ.md (with a Markdown viewer)
49
50 CODE
51 (search for "[SECTION]" in the code to find them)
52
53 // [SECTION] INCLUDES
54 // [SECTION] FORWARD DECLARATIONS
55 // [SECTION] CONTEXT AND MEMORY ALLOCATORS
56 // [SECTION] USER FACING STRUCTURES (ImGuiStyle, ImGuiIO)
57 // [SECTION] MISC HELPERS/UTILITIES (Geometry functions)
58 // [SECTION] MISC HELPERS/UTILITIES (String, Format, Hash functions)
59 // [SECTION] MISC HELPERS/UTILITIES (File functions)
60 // [SECTION] MISC HELPERS/UTILITIES (ImText* functions)
61 // [SECTION] MISC HELPERS/UTILITIES (Color functions)
62 // [SECTION] ImGuiStorage
63 // [SECTION] ImGuiTextFilter
64 // [SECTION] ImGuiTextBuffer
65 // [SECTION] ImGuiListClipper
66 // [SECTION] STYLING
67 // [SECTION] RENDER HELPERS
68 // [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!)
69 // [SECTION] ERROR CHECKING
70 // [SECTION] LAYOUT
71 // [SECTION] SCROLLING
72 // [SECTION] TOOLTIPS
73 // [SECTION] POPUPS
74 // [SECTION] KEYBOARD/GAMEPAD NAVIGATION
75 // [SECTION] DRAG AND DROP
76 // [SECTION] LOGGING/CAPTURING
77 // [SECTION] SETTINGS
78 // [SECTION] VIEWPORTS, PLATFORM WINDOWS
79 // [SECTION] DOCKING
80 // [SECTION] PLATFORM DEPENDENT HELPERS
81 // [SECTION] METRICS/DEBUG WINDOW
82
83 */
84
85 //-----------------------------------------------------------------------------
86 // DOCUMENTATION
87 //-----------------------------------------------------------------------------
88
89 /*
90
91 MISSION STATEMENT
92 =================
93
94 - Easy to use to create code-driven and data-driven tools.
95 - Easy to use to create ad hoc short-lived tools and long-lived, more elaborate tools.
96 - Easy to hack and improve.
97 - Minimize screen real-estate usage.
98 - Minimize setup and maintenance.
99 - Minimize state storage on user side.
100 - Portable, minimize dependencies, run on target (consoles, phones, etc.).
101 - Efficient runtime and memory consumption (NB- we do allocate when "growing" content e.g. creating a window,.
102 opening a tree node for the first time, etc. but a typical frame should not allocate anything).
103
104 Designed for developers and content-creators, not the typical end-user! Some of the weaknesses includes:
105 - Doesn't look fancy, doesn't animate.
106 - Limited layout features, intricate layouts are typically crafted in code.
107
108
109 END-USER GUIDE
110 ==============
111
112 - Double-click on title bar to collapse window.
113 - Click upper right corner to close a window, available when 'bool* p_open' is passed to ImGui::Begin().
114 - Click and drag on lower right corner to resize window (double-click to auto fit window to its contents).
115 - Click and drag on any empty space to move window.
116 - TAB/SHIFT+TAB to cycle through keyboard editable fields.
117 - CTRL+Click on a slider or drag box to input value as text.
118 - Use mouse wheel to scroll.
119 - Text editor:
120 - Hold SHIFT or use mouse to select text.
121 - CTRL+Left/Right to word jump.
122 - CTRL+Shift+Left/Right to select words.
123 - CTRL+A our Double-Click to select all.
124 - CTRL+X,CTRL+C,CTRL+V to use OS clipboard/
125 - CTRL+Z,CTRL+Y to undo/redo.
126 - ESCAPE to revert text to its original value.
127 - You can apply arithmetic operators +,*,/ on numerical values. Use +- to subtract (because - would set a negative value!)
128 - Controls are automatically adjusted for OSX to match standard OSX text editing operations.
129 - General Keyboard controls: enable with ImGuiConfigFlags_NavEnableKeyboard.
130 - General Gamepad controls: enable with ImGuiConfigFlags_NavEnableGamepad. See suggested mappings in imgui.h ImGuiNavInput_ + download PNG/PSD at http://goo.gl/9LgVZW
131
132
133 PROGRAMMER GUIDE
134 ================
135
136 READ FIRST
137 ----------
138 - Remember to read the FAQ (https://www.dearimgui.org/faq)
139 - 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
140 or destruction steps, less superfluous data retention on your side, less state duplication, less state synchronization, less bugs.
141 - Call and read ImGui::ShowDemoWindow() for demo code demonstrating most features.
142 - The library is designed to be built from sources. Avoid pre-compiled binaries and packaged versions. See imconfig.h to configure your build.
143 - Dear ImGui is an implementation of the IMGUI paradigm (immediate-mode graphical user interface, a term coined by Casey Muratori).
144 You can learn about IMGUI principles at http://www.johno.se/book/imgui.html, http://mollyrocket.com/861 & more links in the FAQ.
145 - Dear ImGui is a "single pass" rasterizing implementation of the IMGUI paradigm, aimed at ease of use and high-performances.
146 For every application frame your UI code will be called only once. This is in contrast to e.g. Unity's own implementation of an IMGUI,
147 where the UI code is called multiple times ("multiple passes") from a single entry point. There are pros and cons to both approaches.
148 - Our origin are on the top-left. In axis aligned bounding boxes, Min = top-left, Max = bottom-right.
149 - This codebase is also optimized to yield decent performances with typical "Debug" builds settings.
150 - Please make sure you have asserts enabled (IM_ASSERT redirects to assert() by default, but can be redirected).
151 If you get an assert, read the messages and comments around the assert.
152 - 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.
153 - C++: ImVec2/ImVec4 do not expose math operators by default, because it is expected that you use your own math types.
154 See FAQ "How can I use my own math types instead of ImVec2/ImVec4?" for details about setting up imconfig.h for that.
155 However, imgui_internal.h can optionally export math operators for ImVec2/ImVec4, which we use in this codebase.
156 - C++: pay attention that ImVector<> manipulates plain-old-data and does not honor construction/destruction (avoid using it in your code!).
157
158
159 HOW TO UPDATE TO A NEWER VERSION OF DEAR IMGUI
160 ----------------------------------------------
161 - Overwrite all the sources files except for imconfig.h (if you have made modification to your copy of imconfig.h)
162 - Or maintain your own branch where you have imconfig.h modified.
163 - Read the "API BREAKING CHANGES" section (below). This is where we list occasional API breaking changes.
164 If a function/type has been renamed / or marked obsolete, try to fix the name in your code before it is permanently removed
165 from the public API. If you have a problem with a missing function/symbols, search for its name in the code, there will
166 likely be a comment about it. Please report any issue to the GitHub page!
167 - Try to keep your copy of dear imgui reasonably up to date.
168
169
170 GETTING STARTED WITH INTEGRATING DEAR IMGUI IN YOUR CODE/ENGINE
171 ---------------------------------------------------------------
172 - Run and study the examples and demo in imgui_demo.cpp to get acquainted with the library.
173 - In the majority of cases you should be able to use unmodified back-ends files available in the examples/ folder.
174 - Add the Dear ImGui source files + selected back-end source files to your projects or using your preferred build system.
175 It is recommended you build and statically link the .cpp files as part of your project and NOT as shared library (DLL).
176 - 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.
177 - When using Dear ImGui, your programming IDE is your friend: follow the declaration of variables, functions and types to find comments about them.
178 - Dear ImGui never touches or knows about your GPU state. The only function that knows about GPU is the draw function that you provide.
179 Effectively it means you can create widgets at any time in your code, regardless of considerations of being in "update" vs "render"
180 phases of your own application. All rendering information are stored into command-lists that you will retrieve after calling ImGui::Render().
181 - Refer to the bindings and demo applications in the examples/ folder for instruction on how to setup your code.
182 - 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.
183
184
185 HOW A SIMPLE APPLICATION MAY LOOK LIKE
186 --------------------------------------
187 EXHIBIT 1: USING THE EXAMPLE BINDINGS (= imgui_impl_XXX.cpp files from the examples/ folder).
188 The sub-folders in examples/ contains examples applications following this structure.
189
190 // Application init: create a dear imgui context, setup some options, load fonts
191 ImGui::CreateContext();
192 ImGuiIO& io = ImGui::GetIO();
193 // TODO: Set optional io.ConfigFlags values, e.g. 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard' to enable keyboard controls.
194 // TODO: Fill optional fields of the io structure later.
195 // TODO: Load TTF/OTF fonts if you don't want to use the default font.
196
197 // Initialize helper Platform and Renderer bindings (here we are using imgui_impl_win32.cpp and imgui_impl_dx11.cpp)
198 ImGui_ImplWin32_Init(hwnd);
199 ImGui_ImplDX11_Init(g_pd3dDevice, g_pd3dDeviceContext);
200
201 // Application main loop
202 while (true)
203 {
204 // Feed inputs to dear imgui, start new frame
205 ImGui_ImplDX11_NewFrame();
206 ImGui_ImplWin32_NewFrame();
207 ImGui::NewFrame();
208
209 // Any application code here
210 ImGui::Text("Hello, world!");
211
212 // Render dear imgui into screen
213 ImGui::Render();
214 ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
215 g_pSwapChain->Present(1, 0);
216 }
217
218 // Shutdown
219 ImGui_ImplDX11_Shutdown();
220 ImGui_ImplWin32_Shutdown();
221 ImGui::DestroyContext();
222
223 EXHIBIT 2: IMPLEMENTING CUSTOM BINDING / CUSTOM ENGINE
224
225 // Application init: create a dear imgui context, setup some options, load fonts
226 ImGui::CreateContext();
227 ImGuiIO& io = ImGui::GetIO();
228 // TODO: Set optional io.ConfigFlags values, e.g. 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard' to enable keyboard controls.
229 // TODO: Fill optional fields of the io structure later.
230 // TODO: Load TTF/OTF fonts if you don't want to use the default font.
231
232 // Build and load the texture atlas into a texture
233 // (In the examples/ app this is usually done within the ImGui_ImplXXX_Init() function from one of the demo Renderer)
234 int width, height;
235 unsigned char* pixels = NULL;
236 io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
237
238 // At this point you've got the texture data and you need to upload that your your graphic system:
239 // After we have created the texture, store its pointer/identifier (_in whichever format your engine uses_) in 'io.Fonts->TexID'.
240 // This will be passed back to your via the renderer. Basically ImTextureID == void*. Read FAQ for details about ImTextureID.
241 MyTexture* texture = MyEngine::CreateTextureFromMemoryPixels(pixels, width, height, TEXTURE_TYPE_RGBA32)
242 io.Fonts->TexID = (void*)texture;
243
244 // Application main loop
245 while (true)
246 {
247 // Setup low-level inputs, e.g. on Win32: calling GetKeyboardState(), or write to those fields from your Windows message handlers, etc.
248 // (In the examples/ app this is usually done within the ImGui_ImplXXX_NewFrame() function from one of the demo Platform bindings)
249 io.DeltaTime = 1.0f/60.0f; // set the time elapsed since the previous frame (in seconds)
250 io.DisplaySize.x = 1920.0f; // set the current display width
251 io.DisplaySize.y = 1280.0f; // set the current display height here
252 io.MousePos = my_mouse_pos; // set the mouse position
253 io.MouseDown[0] = my_mouse_buttons[0]; // set the mouse button states
254 io.MouseDown[1] = my_mouse_buttons[1];
255
256 // Call NewFrame(), after this point you can use ImGui::* functions anytime
257 // (So you want to try calling NewFrame() as early as you can in your mainloop to be able to use Dear ImGui everywhere)
258 ImGui::NewFrame();
259
260 // Most of your application code here
261 ImGui::Text("Hello, world!");
262 MyGameUpdate(); // may use any Dear ImGui functions, e.g. ImGui::Begin("My window"); ImGui::Text("Hello, world!"); ImGui::End();
263 MyGameRender(); // may use any Dear ImGui functions as well!
264
265 // Render dear imgui, swap buffers
266 // (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)
267 ImGui::EndFrame();
268 ImGui::Render();
269 ImDrawData* draw_data = ImGui::GetDrawData();
270 MyImGuiRenderFunction(draw_data);
271 SwapBuffers();
272 }
273
274 // Shutdown
275 ImGui::DestroyContext();
276
277 To decide whether to dispatch mouse/keyboard inputs to Dear ImGui to the rest your application,
278 you should read the 'io.WantCaptureMouse', 'io.WantCaptureKeyboard' and 'io.WantTextInput' flags!
279 Please read the FAQ and example applications for details about this!
280
281
282 HOW A SIMPLE RENDERING FUNCTION MAY LOOK LIKE
283 ---------------------------------------------
284 The bindings in impl_impl_XXX.cpp files contains many working implementations of a rendering function.
285
286 void void MyImGuiRenderFunction(ImDrawData* draw_data)
287 {
288 // TODO: Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled
289 // TODO: Setup viewport covering draw_data->DisplayPos to draw_data->DisplayPos + draw_data->DisplaySize
290 // TODO: Setup orthographic projection matrix cover draw_data->DisplayPos to draw_data->DisplayPos + draw_data->DisplaySize
291 // TODO: Setup shader: vertex { float2 pos, float2 uv, u32 color }, fragment shader sample color from 1 texture, multiply by vertex color.
292 for (int n = 0; n < draw_data->CmdListsCount; n++)
293 {
294 const ImDrawList* cmd_list = draw_data->CmdLists[n];
295 const ImDrawVert* vtx_buffer = cmd_list->VtxBuffer.Data; // vertex buffer generated by Dear ImGui
296 const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data; // index buffer generated by Dear ImGui
297 for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
298 {
299 const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
300 if (pcmd->UserCallback)
301 {
302 pcmd->UserCallback(cmd_list, pcmd);
303 }
304 else
305 {
306 // The texture for the draw call is specified by pcmd->TextureId.
307 // The vast majority of draw calls will use the Dear ImGui texture atlas, which value you have set yourself during initialization.
308 MyEngineBindTexture((MyTexture*)pcmd->TextureId);
309
310 // We are using scissoring to clip some objects. All low-level graphics API should supports it.
311 // - If your engine doesn't support scissoring yet, you may ignore this at first. You will get some small glitches
312 // (some elements visible outside their bounds) but you can fix that once everything else works!
313 // - Clipping coordinates are provided in imgui coordinates space (from draw_data->DisplayPos to draw_data->DisplayPos + draw_data->DisplaySize)
314 // In a single viewport application, draw_data->DisplayPos will always be (0,0) and draw_data->DisplaySize will always be == io.DisplaySize.
315 // However, in the interest of supporting multi-viewport applications in the future (see 'viewport' branch on github),
316 // always subtract draw_data->DisplayPos from clipping bounds to convert them to your viewport space.
317 // - Note that pcmd->ClipRect contains Min+Max bounds. Some graphics API may use Min+Max, other may use Min+Size (size being Max-Min)
318 ImVec2 pos = draw_data->DisplayPos;
319 MyEngineScissor((int)(pcmd->ClipRect.x - pos.x), (int)(pcmd->ClipRect.y - pos.y), (int)(pcmd->ClipRect.z - pos.x), (int)(pcmd->ClipRect.w - pos.y));
320
321 // Render 'pcmd->ElemCount/3' indexed triangles.
322 // 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.
323 MyEngineDrawIndexedTriangles(pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer, vtx_buffer);
324 }
325 idx_buffer += pcmd->ElemCount;
326 }
327 }
328 }
329
330
331 USING GAMEPAD/KEYBOARD NAVIGATION CONTROLS
332 ------------------------------------------
333 - The gamepad/keyboard navigation is fairly functional and keeps being improved.
334 - Gamepad support is particularly useful to use Dear ImGui on a console system (e.g. PS4, Switch, XB1) without a mouse!
335 - You can ask questions and report issues at https://github.com/ocornut/imgui/issues/787
336 - The initial focus was to support game controllers, but keyboard is becoming increasingly and decently usable.
337 - Keyboard:
338 - Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard to enable.
339 NewFrame() will automatically fill io.NavInputs[] based on your io.KeysDown[] + io.KeyMap[] arrays.
340 - When keyboard navigation is active (io.NavActive + ImGuiConfigFlags_NavEnableKeyboard), the io.WantCaptureKeyboard flag
341 will be set. For more advanced uses, you may want to read from:
342 - io.NavActive: true when a window is focused and it doesn't have the ImGuiWindowFlags_NoNavInputs flag set.
343 - io.NavVisible: true when the navigation cursor is visible (and usually goes false when mouse is used).
344 - or query focus information with e.g. IsWindowFocused(ImGuiFocusedFlags_AnyWindow), IsItemFocused() etc. functions.
345 Please reach out if you think the game vs navigation input sharing could be improved.
346 - Gamepad:
347 - Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad to enable.
348 - Backend: Set io.BackendFlags |= ImGuiBackendFlags_HasGamepad + fill the io.NavInputs[] fields before calling NewFrame().
349 Note that io.NavInputs[] is cleared by EndFrame().
350 - See 'enum ImGuiNavInput_' in imgui.h for a description of inputs. For each entry of io.NavInputs[], set the following values:
351 0.0f= not held. 1.0f= fully held. Pass intermediate 0.0f..1.0f values for analog triggers/sticks.
352 - We uses a simple >0.0f test for activation testing, and won't attempt to test for a dead-zone.
353 Your code will probably need to transform your raw inputs (such as e.g. remapping your 0.2..0.9 raw input range to 0.0..1.0 imgui range, etc.).
354 - You can download PNG/PSD files depicting the gamepad controls for common controllers at: http://goo.gl/9LgVZW.
355 - If you need to share inputs between your game and the imgui parts, the easiest approach is to go all-or-nothing, with a buttons combo
356 to toggle the target. Please reach out if you think the game vs navigation input sharing could be improved.
357 - Mouse:
358 - PS4 users: Consider emulating a mouse cursor with DualShock4 touch pad or a spare analog stick as a mouse-emulation fallback.
359 - 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.
360 - 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.
361 Enabling ImGuiConfigFlags_NavEnableSetMousePos + ImGuiBackendFlags_HasSetMousePos instructs dear imgui to move your mouse cursor along with navigation movements.
362 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.
363 When that happens your back-end NEEDS to move the OS or underlying mouse cursor on the next frame. Some of the binding in examples/ do that.
364 (If you set the NavEnableSetMousePos flag but don't honor 'io.WantSetMousePos' properly, imgui will misbehave as it will see your mouse as moving back and forth!)
365 (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
366 to set a boolean to ignore your other external mouse positions until the external source is moved again.)
367
368
369 API BREAKING CHANGES
370 ====================
371
372 Occasionally introducing changes that are breaking the API. We try to make the breakage minor and easy to fix.
373 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.
374 When you are not sure about a old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all imgui files.
375 You can read releases logs https://github.com/ocornut/imgui/releases for more details.
376
377 (Docking/Viewport Branch)
378 - 2020/XX/XX (1.XX) - when multi-viewports are enabled, all positions will be in your natural OS coordinates space. It means that:
379 - reference to hard-coded positions such as in SetNextWindowPos(ImVec2(0,0)) are probably not what you want anymore.
380 you may use GetMainViewport()->Pos to offset hard-coded positions, e.g. SetNextWindowPos(GetMainViewport()->Pos)
381 - likewise io.MousePos and GetMousePos() will use OS coordinates.
382 If you query mouse positions to interact with non-imgui coordinates you will need to offset them, e.g. subtract GetWindowViewport()->Pos.
383 - 2020/XX/XX (1.XX) - Moved IME support functions from io.ImeSetInputScreenPosFn, io.ImeWindowHandle to the PlatformIO api.
384
385
386 - 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().
387 replaced the 'float power=1.0f' argument with integer-based flags defaulting to 0 (as with all our flags).
388 worked out a backward-compatibility scheme so hopefully most C++ codebase should not be affected. in short, when calling those functions:
389 - if you omitted the 'power' parameter (likely!), you are not affected.
390 - 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.
391 - 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.
392 see https://github.com/ocornut/imgui/issues/3361 for all details.
393 kept inline redirection functions (will obsolete) apart for: DragFloatRange2(), VSliderFloat(), VSliderScalar(). For those three the 'float power=1.0f' version were removed directly as they were most unlikely ever used.
394 - 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.
395 - 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.
396 - 2020/06/15 (1.77) - renamed OpenPopupOnItemClick() to OpenPopupContextItem(). Kept inline redirection function (will obsolete).
397 - 2020/06/15 (1.77) - removed CalcItemRectClosestPoint() entry point which was made obsolete and asserting in December 2017.
398 - 2020/04/23 (1.77) - removed unnecessary ID (first arg) of ImFontAtlas::AddCustomRectRegular().
399 - 2020/01/22 (1.75) - ImDrawList::AddCircle()/AddCircleFilled() functions don't accept negative radius any more.
400 - 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.
401 - 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.
402 - 2019/12/08 (1.75) - removed redirecting functions/enums that were marked obsolete in 1.53 (December 2017):
403 - ShowTestWindow() -> use ShowDemoWindow()
404 - IsRootWindowFocused() -> use IsWindowFocused(ImGuiFocusedFlags_RootWindow)
405 - IsRootWindowOrAnyChildFocused() -> use IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)
406 - SetNextWindowContentWidth(w) -> use SetNextWindowContentSize(ImVec2(w, 0.0f)
407 - GetItemsLineHeightWithSpacing() -> use GetFrameHeightWithSpacing()
408 - ImGuiCol_ChildWindowBg -> use ImGuiCol_ChildBg
409 - ImGuiStyleVar_ChildWindowRounding -> use ImGuiStyleVar_ChildRounding
410 - ImGuiTreeNodeFlags_AllowOverlapMode -> use ImGuiTreeNodeFlags_AllowItemOverlap
411 - IMGUI_DISABLE_TEST_WINDOWS -> use IMGUI_DISABLE_DEMO_WINDOWS
412 - 2019/12/08 (1.75) - obsoleted calling ImDrawList::PrimReserve() with a negative count (which was the vaguely documented and rarely if ever used). Instead we added an explicit PrimUnreserve() API.
413 - 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).
414 - 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.
415 - 2019/11/19 (1.74) - renamed IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS to IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS for consistency.
416 - 2019/11/19 (1.74) - renamed IMGUI_DISABLE_MATH_FUNCTIONS to IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS for consistency.
417 - 2019/10/22 (1.74) - removed redirecting functions/enums that were marked obsolete in 1.52 (October 2017):
418 - Begin() [old 5 args version] -> use Begin() [3 args], use SetNextWindowSize() SetNextWindowBgAlpha() if needed
419 - IsRootWindowOrAnyChildHovered() -> use IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows)
420 - AlignFirstTextHeightToWidgets() -> use AlignTextToFramePadding()
421 - SetNextWindowPosCenter() -> use SetNextWindowPos() with a pivot of (0.5f, 0.5f)
422 - ImFont::Glyph -> use ImFontGlyph
423 - 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.
424 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.
425 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).
426 If you never altered io.KeyRepeatRate nor used GetKeyPressedAmount() this won't affect you.
427 - 2019/07/15 (1.72) - removed TreeAdvanceToLabelPos() which is rarely used and only does SetCursorPosX(GetCursorPosX() + GetTreeNodeToLabelSpacing()). Kept redirection function (will obsolete).
428 - 2019/07/12 (1.72) - renamed ImFontAtlas::CustomRect to ImFontAtlasCustomRect. Kept redirection typedef (will obsolete).
429 - 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.
430 - 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
431 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.
432 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.
433 Please reach out if you are affected.
434 - 2019/05/13 (1.71) - renamed SetNextTreeNodeOpen() to SetNextItemOpen(). Kept inline redirection function (will obsolete).
435 - 2019/05/11 (1.71) - changed io.AddInputCharacter(unsigned short c) signature to io.AddInputCharacter(unsigned int c).
436 - 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.
437 - 2019/04/29 (1.70) - removed GetContentRegionAvailWidth(), use GetContentRegionAvail().x instead. Kept inline redirection function (will obsolete).
438 - 2019/03/04 (1.69) - renamed GetOverlayDrawList() to GetForegroundDrawList(). Kept redirection function (will obsolete).
439 - 2019/02/26 (1.69) - renamed ImGuiColorEditFlags_RGB/ImGuiColorEditFlags_HSV/ImGuiColorEditFlags_HEX to ImGuiColorEditFlags_DisplayRGB/ImGuiColorEditFlags_DisplayHSV/ImGuiColorEditFlags_DisplayHex. Kept redirection enums (will obsolete).
440 - 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 arbitrary small value!
441 - 2019/02/01 (1.68) - removed io.DisplayVisibleMin/DisplayVisibleMax (which were marked obsolete and removed from viewport/docking branch already).
442 - 2019/01/06 (1.67) - renamed io.InputCharacters[], marked internal as was always intended. Please don't access directly, and use AddInputCharacter() instead!
443 - 2019/01/06 (1.67) - renamed ImFontAtlas::GlyphRangesBuilder to ImFontGlyphRangesBuilder. Kept redirection typedef (will obsolete).
444 - 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.
445 - 2018/12/10 (1.67) - renamed io.ConfigResizeWindowsFromEdges to io.ConfigWindowsResizeFromEdges as we are doing a large pass on configuration flags.
446 - 2018/10/12 (1.66) - renamed misc/stl/imgui_stl.* to misc/cpp/imgui_stdlib.* in prevision for other C++ helper files.
447 - 2018/09/28 (1.66) - renamed SetScrollHere() to SetScrollHereY(). Kept redirection function (will obsolete).
448 - 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.
449 If you were conveniently using the imgui copy of those STB headers in your project you will have to update your include paths.
450 - 2018/09/05 (1.65) - renamed io.OptCursorBlink/io.ConfigCursorBlink to io.ConfigInputTextCursorBlink. (#1427)
451 - 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.
452 NONE OF THE FUNCTIONS HAVE CHANGED. THE CODE IS SEMANTICALLY 100% IDENTICAL, BUT _EVERY_ FUNCTION HAS BEEN MOVED.
453 Because of this, any local modifications to imgui.cpp will likely conflict when you update. Read docs/CHANGELOG.txt for suggestions.
454 - 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).
455 - 2018/08/21 (1.63) - renamed ImGuiTextEditCallback to ImGuiInputTextCallback, ImGuiTextEditCallbackData to ImGuiInputTextCallbackData for consistency. Kept redirection types (will obsolete).
456 - 2018/08/21 (1.63) - removed ImGuiInputTextCallbackData::ReadOnly since it is a duplication of (ImGuiInputTextCallbackData::Flags & ImGuiInputTextFlags_ReadOnly).
457 - 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.
458 - 2018/08/01 (1.63) - renamed io.OptCursorBlink to io.ConfigCursorBlink [-> io.ConfigInputTextCursorBlink in 1.65], io.OptMacOSXBehaviors to ConfigMacOSXBehaviors for consistency.
459 - 2018/07/22 (1.63) - changed ImGui::GetTime() return value from float to double to avoid accumulating floating point imprecisions over time.
460 - 2018/07/08 (1.63) - style: renamed ImGuiCol_ModalWindowDarkening to ImGuiCol_ModalWindowDimBg for consistency with other features. Kept redirection enum (will obsolete).
461 - 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.).
462 old bindings will still work as is, however prefer using the separated bindings as they will be updated to support multi-viewports.
463 when adopting new bindings follow the main.cpp code of your preferred examples/ folder to know which functions to call.
464 in particular, note that old bindings called ImGui::NewFrame() at the end of their ImGui_ImplXXXX_NewFrame() function.
465 - 2018/06/06 (1.62) - renamed GetGlyphRangesChinese() to GetGlyphRangesChineseFull() to distinguish other variants and discourage using the full set.
466 - 2018/06/06 (1.62) - TreeNodeEx()/TreeNodeBehavior(): the ImGuiTreeNodeFlags_CollapsingHeader helper now include the ImGuiTreeNodeFlags_NoTreePushOnOpen flag. See Changelog for details.
467 - 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.
468 If you used DragInt() with custom format strings, make sure you change them to use %d or an integer-compatible format.
469 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.
470 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.
471 - 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",
472 consistent with other functions. Kept redirection functions (will obsolete).
473 - 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.
474 - 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 binding ahead of merging the Nav branch).
475 - 2018/03/12 (1.60) - removed ImGuiCol_CloseButton, ImGuiCol_CloseButtonActive, ImGuiCol_CloseButtonHovered as the closing cross uses regular button colors now.
476 - 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.
477 - 2018/03/03 (1.60) - renamed ImGuiStyleVar_Count_ to ImGuiStyleVar_COUNT and ImGuiMouseCursor_Count_ to ImGuiMouseCursor_COUNT for consistency with other public enums.
478 - 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.
479 - 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.
480 - 2018/02/07 (1.60) - reorganized context handling to be more explicit,
481 - YOU NOW NEED TO CALL ImGui::CreateContext() AT THE BEGINNING OF YOUR APP, AND CALL ImGui::DestroyContext() AT THE END.
482 - removed Shutdown() function, as DestroyContext() serve this purpose.
483 - you may pass a ImFontAtlas* pointer to CreateContext() to share a font atlas between contexts. Otherwise CreateContext() will create its own font atlas instance.
484 - removed allocator parameters from CreateContext(), they are now setup with SetAllocatorFunctions(), and shared by all contexts.
485 - removed the default global context and font atlas instance, which were confusing for users of DLL reloading and users of multiple contexts.
486 - 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.
487 - 2018/01/11 (1.60) - obsoleted IsAnyWindowHovered() in favor of IsWindowHovered(ImGuiHoveredFlags_AnyWindow). Kept redirection function (will obsolete).
488 - 2018/01/11 (1.60) - obsoleted IsAnyWindowFocused() in favor of IsWindowFocused(ImGuiFocusedFlags_AnyWindow). Kept redirection function (will obsolete).
489 - 2018/01/03 (1.60) - renamed ImGuiSizeConstraintCallback to ImGuiSizeCallback, ImGuiSizeConstraintCallbackData to ImGuiSizeCallbackData.
490 - 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.
491 - 2017/12/24 (1.53) - renamed the emblematic ShowTestWindow() function to ShowDemoWindow(). Kept redirection function (will obsolete).
492 - 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
493 - 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.
494 - 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.
495 - 2017/12/13 (1.53) - renamed GetItemsLineHeightWithSpacing() to GetFrameHeightWithSpacing(). Kept redirection function (will obsolete).
496 - 2017/12/13 (1.53) - obsoleted IsRootWindowFocused() in favor of using IsWindowFocused(ImGuiFocusedFlags_RootWindow). Kept redirection function (will obsolete).
497 - obsoleted IsRootWindowOrAnyChildFocused() in favor of using IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows). Kept redirection function (will obsolete).
498 - 2017/12/12 (1.53) - renamed ImGuiTreeNodeFlags_AllowOverlapMode to ImGuiTreeNodeFlags_AllowItemOverlap. Kept redirection enum (will obsolete).
499 - 2017/12/10 (1.53) - removed SetNextWindowContentWidth(), prefer using SetNextWindowContentSize(). Kept redirection function (will obsolete).
500 - 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.
501 - 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.
502 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.
503 - 2017/11/18 (1.53) - Style: removed ImGuiCol_ComboBg in favor of combo boxes using ImGuiCol_PopupBg for consistency.
504 - 2017/11/18 (1.53) - Style: renamed ImGuiCol_ChildWindowBg to ImGuiCol_ChildBg.
505 - 2017/11/18 (1.53) - Style: renamed style.ChildWindowRounding to style.ChildRounding, ImGuiStyleVar_ChildWindowRounding to ImGuiStyleVar_ChildRounding.
506 - 2017/11/02 (1.53) - obsoleted IsRootWindowOrAnyChildHovered() in favor of using IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows);
507 - 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.
508 - 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.
509 - 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.
510 removed the IsItemRectHovered()/IsWindowRectHovered() names introduced in 1.51 since they were merely more consistent names for the two functions we are now obsoleting.
511 IsItemHoveredRect() --> IsItemHovered(ImGuiHoveredFlags_RectOnly)
512 IsMouseHoveringAnyWindow() --> IsWindowHovered(ImGuiHoveredFlags_AnyWindow)
513 IsMouseHoveringWindow() --> IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem) [weird, old behavior]
514 - 2017/10/17 (1.52) - marked the old 5-parameters version of Begin() as obsolete (still available). Use SetNextWindowSize()+Begin() instead!
515 - 2017/10/11 (1.52) - renamed AlignFirstTextHeightToWidgets() to AlignTextToFramePadding(). Kept inline redirection function (will obsolete).
516 - 2017/09/26 (1.52) - renamed ImFont::Glyph to ImFontGlyph. Kept redirection typedef (will obsolete).
517 - 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).
518 - 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 binding if you need to support unavailable mouse, make sure to replace "io.MousePos = ImVec2(-1,-1)" with "io.MousePos = ImVec2(-FLT_MAX,-FLT_MAX)".
519 - 2017/08/22 (1.51) - renamed IsItemHoveredRect() to IsItemRectHovered(). Kept inline redirection function (will obsolete). -> (1.52) use IsItemHovered(ImGuiHoveredFlags_RectOnly)!
520 - renamed IsMouseHoveringAnyWindow() to IsAnyWindowHovered() for consistency. Kept inline redirection function (will obsolete).
521 - renamed IsMouseHoveringWindow() to IsWindowRectHovered() for consistency. Kept inline redirection function (will obsolete).
522 - 2017/08/20 (1.51) - renamed GetStyleColName() to GetStyleColorName() for consistency.
523 - 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.
524 - 2017/08/15 (1.51) - marked the weird IMGUI_ONCE_UPON_A_FRAME helper macro as obsolete. prefer using the more explicit ImGuiOnceUponAFrame type.
525 - 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.
526 - 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).
527 - 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).
528 - 2017/08/09 (1.51) - removed ValueColor() helpers, they are equivalent to calling Text(label) + SameLine() + ColorButton().
529 - 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.
530 - 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.
531 - 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))'
532 - 2017/07/20 (1.51) - removed IsPosHoveringAnyWindow(ImVec2), which was partly broken and misleading. ASSERT + redirect user to io.WantCaptureMouse
533 - 2017/05/26 (1.50) - removed ImFontConfig::MergeGlyphCenterV in favor of a more multipurpose ImFontConfig::GlyphOffset.
534 - 2017/05/01 (1.50) - renamed ImDrawList::PathFill() (rarely used directly) to ImDrawList::PathFillConvex() for clarity.
535 - 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().
536 - 2016/10/15 (1.50) - avoid 'void* user_data' parameter to io.SetClipboardTextFn/io.GetClipboardTextFn pointers. We pass io.ClipboardUserData to it.
537 - 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.
538 - 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.
539 - 2016/05/12 (1.49) - title bar (using ImGuiCol_TitleBg/ImGuiCol_TitleBgActive colors) isn't rendered over a window background (ImGuiCol_WindowBg color) anymore.
540 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 tweak your custom theme to readjust for the fact that we don't draw a WindowBg background behind the title bar.
541 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:
542 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); }
543 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.
544 - 2016/05/07 (1.49) - removed confusing set of GetInternalState(), GetInternalStateSize(), SetInternalState() functions. Now using CreateContext(), DestroyContext(), GetCurrentContext(), SetCurrentContext().
545 - 2016/05/02 (1.49) - renamed SetNextTreeNodeOpened() to SetNextTreeNodeOpen(), no redirection.
546 - 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).
547 - 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.
548 - 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).
549 - 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)
550 - 2016/03/21 (1.48) - renamed GetWindowFont() to GetFont(), GetWindowFontSize() to GetFontSize(). Kept inline redirection function (will obsolete).
551 - 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.
552 - 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.
553 - 2015/12/27 (1.48) - fixed ImDrawList::AddRect() which used to render a rectangle 1 px too large on each axis.
554 - 2015/12/04 (1.47) - renamed Color() helpers to ValueColor() - dangerously named, rarely used and probably to be made obsolete.
555 - 2015/08/29 (1.45) - with the addition of horizontal scrollbar we made various fixes to inconsistencies with dealing with cursor position.
556 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.
557 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!
558 - 2015/08/29 (1.45) - renamed style.ScrollbarWidth to style.ScrollbarSize
559 - 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.
560 - 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
561 - 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.
562 you need to render your textured triangles with bilinear filtering to benefit from sub-pixel positioning of text.
563 - 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.
564 this necessary change will break your rendering function! the fix should be very easy. sorry for that :(
565 - if you are using a vanilla copy of one of the imgui_impl_XXXX.cpp provided in the example, you just need to update your copy and you can ignore the rest.
566 - the signature of the io.RenderDrawListsFn handler has changed!
567 old: ImGui_XXXX_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count)
568 new: ImGui_XXXX_RenderDrawLists(ImDrawData* draw_data).
569 parameters: 'cmd_lists' becomes 'draw_data->CmdLists', 'cmd_lists_count' becomes 'draw_data->CmdListsCount'
570 ImDrawList: 'commands' becomes 'CmdBuffer', 'vtx_buffer' becomes 'VtxBuffer', 'IdxBuffer' is new.
571 ImDrawCmd: 'vtx_count' becomes 'ElemCount', 'clip_rect' becomes 'ClipRect', 'user_callback' becomes 'UserCallback', 'texture_id' becomes 'TextureId'.
572 - 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.
573 - 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!
574 - refer to code in the examples/ folder or ask on the GitHub if you are unsure of how to upgrade. please upgrade!
575 - 2015/07/10 (1.43) - changed SameLine() parameters from int to float.
576 - 2015/07/02 (1.42) - renamed SetScrollPosHere() to SetScrollFromCursorPos(). Kept inline redirection function (will obsolete).
577 - 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.
578 - 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
579 - 2015/06/14 (1.41) - changed Selectable() API from (label, selected, size) to (label, selected, flags, size). Size override should have been rarely be used. Sorry!
580 - 2015/05/31 (1.40) - renamed GetWindowCollapsed() to IsWindowCollapsed() for consistency. Kept inline redirection function (will obsolete).
581 - 2015/05/31 (1.40) - renamed IsRectClipped() to IsRectVisible() for consistency. Note that return value is opposite! Kept inline redirection function (will obsolete).
582 - 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.
583 - 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.
584 - 2015/05/03 (1.40) - removed style.AutoFitPadding, using style.WindowPadding makes more sense (the default values were already the same).
585 - 2015/04/13 (1.38) - renamed IsClipped() to IsRectClipped(). Kept inline redirection function until 1.50.
586 - 2015/04/09 (1.38) - renamed ImDrawList::AddArc() to ImDrawList::AddArcFast() for compatibility with future API
587 - 2015/04/03 (1.38) - removed ImGuiCol_CheckHovered, ImGuiCol_CheckActive, replaced with the more general ImGuiCol_FrameBgHovered, ImGuiCol_FrameBgActive.
588 - 2014/04/03 (1.38) - removed support for passing -FLT_MAX..+FLT_MAX as the range for a SliderFloat(). Use DragFloat() or Inputfloat() instead.
589 - 2015/03/17 (1.36) - renamed GetItemBoxMin()/GetItemBoxMax()/IsMouseHoveringBox() to GetItemRectMin()/GetItemRectMax()/IsMouseHoveringRect(). Kept inline redirection function until 1.50.
590 - 2015/03/15 (1.36) - renamed style.TreeNodeSpacing to style.IndentSpacing, ImGuiStyleVar_TreeNodeSpacing to ImGuiStyleVar_IndentSpacing
591 - 2015/03/13 (1.36) - renamed GetWindowIsFocused() to IsWindowFocused(). Kept inline redirection function until 1.50.
592 - 2015/03/08 (1.35) - renamed style.ScrollBarWidth to style.ScrollbarWidth (casing)
593 - 2015/02/27 (1.34) - renamed OpenNextNode(bool) to SetNextTreeNodeOpened(bool, ImGuiSetCond). Kept inline redirection function until 1.50.
594 - 2015/02/27 (1.34) - renamed ImGuiSetCondition_*** to ImGuiSetCond_***, and _FirstUseThisSession becomes _Once.
595 - 2015/02/11 (1.32) - changed text input callback ImGuiTextEditCallback return type from void-->int. reserved for future use, return 0 for now.
596 - 2015/02/10 (1.32) - renamed GetItemWidth() to CalcItemWidth() to clarify its evolving behavior
597 - 2015/02/08 (1.31) - renamed GetTextLineSpacing() to GetTextLineHeightWithSpacing()
598 - 2015/02/01 (1.31) - removed IO.MemReallocFn (unused)
599 - 2015/01/19 (1.30) - renamed ImGuiStorage::GetIntPtr()/GetFloatPtr() to GetIntRef()/GetIntRef() because Ptr was conflicting with actual pointer storage functions.
600 - 2015/01/11 (1.30) - big font/image API change! now loads TTF file. allow for multiple fonts. no need for a PNG loader.
601 - 2015/01/11 (1.30) - removed GetDefaultFontData(). uses io.Fonts->GetTextureData*() API to retrieve uncompressed pixels.
602 - old: const void* png_data; unsigned int png_size; ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); [..Upload texture to GPU..];
603 - new: unsigned char* pixels; int width, height; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); [..Upload texture to GPU..]; io.Fonts->TexId = YourTexIdentifier;
604 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.
605 - 2015/01/11 (1.30) - added texture identifier in ImDrawCmd passed to your render function (we can now render images). make sure to set io.Fonts->TexID.
606 - 2015/01/11 (1.30) - removed IO.PixelCenterOffset (unnecessary, can be handled in user projection matrix)
607 - 2015/01/11 (1.30) - removed ImGui::IsItemFocused() in favor of ImGui::IsItemActive() which handles all widgets
608 - 2014/12/10 (1.18) - removed SetNewWindowDefaultPos() in favor of new generic API SetNextWindowPos(pos, ImGuiSetCondition_FirstUseEver)
609 - 2014/11/28 (1.17) - moved IO.Font*** options to inside the IO.Font-> structure (FontYOffset, FontTexUvForWhite, FontBaseScale, FontFallbackGlyph)
610 - 2014/11/26 (1.17) - reworked syntax of IMGUI_ONCE_UPON_A_FRAME helper macro to increase compiler compatibility
611 - 2014/11/07 (1.15) - renamed IsHovered() to IsItemHovered()
612 - 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)
613 - 2014/09/25 (1.13) - removed 'text_end' parameter from IO.SetClipboardTextFn (the string is now always zero-terminated for simplicity)
614 - 2014/09/24 (1.12) - renamed SetFontScale() to SetWindowFontScale()
615 - 2014/09/24 (1.12) - moved IM_MALLOC/IM_REALLOC/IM_FREE preprocessor defines to IO.MemAllocFn/IO.MemReallocFn/IO.MemFreeFn
616 - 2014/08/30 (1.09) - removed IO.FontHeight (now computed automatically)
617 - 2014/08/30 (1.09) - moved IMGUI_FONT_TEX_UV_FOR_WHITE preprocessor define to IO.FontTexUvForWhite
618 - 2014/08/28 (1.09) - changed the behavior of IO.PixelCenterOffset following various rendering fixes
619
620
621 FREQUENTLY ASKED QUESTIONS (FAQ)
622 ================================
623
624 Read all answers online:
625 https://www.dearimgui.org/faq or https://github.com/ocornut/imgui/blob/master/docs/FAQ.md (same url)
626 Read all answers locally (with a text editor or ideally a Markdown viewer):
627 docs/FAQ.md
628 Some answers are copied down here to facilitate searching in code.
629
630 Q&A: Basics
631 ===========
632
633 Q: Where is the documentation?
634 A: This library is poorly documented at the moment and expects of the user to be acquainted with C/C++.
635 - Run the examples/ and explore them.
636 - See demo code in imgui_demo.cpp and particularly the ImGui::ShowDemoWindow() function.
637 - The demo covers most features of Dear ImGui, so you can read the code and see its output.
638 - See documentation and comments at the top of imgui.cpp + effectively imgui.h.
639 - Dozens of standalone example applications using e.g. OpenGL/DirectX are provided in the
640 examples/ folder to explain how to integrate Dear ImGui with your own engine/application.
641 - The Wiki (https://github.com/ocornut/imgui/wiki) has many resources and links.
642 - The Glossary (https://github.com/ocornut/imgui/wiki/Glossary) page also may be useful.
643 - Your programming IDE is your friend, find the type or function declaration to find comments
644 associated to it.
645
646 Q: What is this library called?
647 Q: Which version should I get?
648 >> This library is called "Dear ImGui", please don't call it "ImGui" :)
649 >> See https://www.dearimgui.org/faq for details.
650
651 Q&A: Integration
652 ================
653
654 Q: How to get started?
655 A: Read 'PROGRAMMER GUIDE' above. Read examples/README.txt.
656
657 Q: How can I tell whether to dispatch mouse/keyboard to Dear ImGui or to my application?
658 A: You should read the 'io.WantCaptureMouse', 'io.WantCaptureKeyboard' and 'io.WantTextInput' flags!
659 >> See https://www.dearimgui.org/faq for fully detailed answer. You really want to read this.
660
661 Q. How can I enable keyboard controls?
662 Q: How can I use this without a mouse, without a keyboard or without a screen? (gamepad, input share, remote display)
663 Q: I integrated Dear ImGui in my engine and little squares are showing instead of text..
664 Q: I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around..
665 Q: I integrated Dear ImGui in my engine and some elements are displaying outside their expected windows boundaries..
666 >> See https://www.dearimgui.org/faq
667
668 Q&A: Usage
669 ----------
670
671 Q: Why is my widget not reacting when I click on it?
672 Q: How can I have widgets with an empty label?
673 Q: How can I have multiple widgets with the same label?
674 Q: How can I display an image? What is ImTextureID, how does it works?
675 Q: How can I use my own math types instead of ImVec2/ImVec4?
676 Q: How can I interact with standard C++ types (such as std::string and std::vector)?
677 Q: How can I display custom shapes? (using low-level ImDrawList API)
678 >> See https://www.dearimgui.org/faq
679
680 Q&A: Fonts, Text
681 ================
682
683 Q: How should I handle DPI in my application?
684 Q: How can I load a different font than the default?
685 Q: How can I easily use icons in my application?
686 Q: How can I load multiple fonts?
687 Q: How can I display and input non-Latin characters such as Chinese, Japanese, Korean, Cyrillic?
688 >> See https://www.dearimgui.org/faq and https://github.com/ocornut/imgui/edit/master/docs/FONTS.md
689
690 Q&A: Concerns
691 =============
692
693 Q: Who uses Dear ImGui?
694 Q: Can you create elaborate/serious tools with Dear ImGui?
695 Q: Can you reskin the look of Dear ImGui?
696 Q: Why using C++ (as opposed to C)?
697 >> See https://www.dearimgui.org/faq
698
699 Q&A: Community
700 ==============
701
702 Q: How can I help?
703 A: - Businesses: please reach out to "contact AT dearimgui.org" if you work in a place using Dear ImGui!
704 We can discuss ways for your company to fund development via invoiced technical support, maintenance or sponsoring contacts.
705 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.
706 - Individuals: you can support continued development via PayPal donations. See README.
707 - If you are experienced with Dear ImGui and C++, look at the github issues, look at the Wiki, read docs/TODO.txt
708 and see how you want to help and can help!
709 - Disclose your usage of Dear ImGui via a dev blog post, a tweet, a screenshot, a mention somewhere etc.
710 You may post screenshot or links in the gallery threads (github.com/ocornut/imgui/issues/3075). Visuals are ideal as they inspire other programmers.
711 But even without visuals, disclosing your use of dear imgui help the library grow credibility, and help other teams and programmers with taking decisions.
712 - 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).
713
714 */
715
716 //-------------------------------------------------------------------------
717 // [SECTION] INCLUDES
718 //-------------------------------------------------------------------------
719
720 #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
721 #define _CRT_SECURE_NO_WARNINGS
722 #endif
723
724 #include "imgui.h"
725 #ifndef IMGUI_DISABLE
726
727 #ifndef IMGUI_DEFINE_MATH_OPERATORS
728 #define IMGUI_DEFINE_MATH_OPERATORS
729 #endif
730 #include "imgui_internal.h"
731
732 // System includes
733 #include <ctype.h> // toupper
734 #include <stdio.h> // vsnprintf, sscanf, printf
735 #if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier
736 #include <stddef.h> // intptr_t
737 #else
738 #include <stdint.h> // intptr_t
739 #endif
740
741 // [Windows] OS specific includes (optional)
742 #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)
743 #define IMGUI_DISABLE_WIN32_FUNCTIONS
744 #endif
745 #if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS)
746 #ifndef WIN32_LEAN_AND_MEAN
747 #define WIN32_LEAN_AND_MEAN
748 #endif
749 #ifndef NOMINMAX
750 #define NOMINMAX
751 #endif
752 #ifndef __MINGW32__
753 #include <Windows.h> // _wfopen, OpenClipboard
754 #else
755 #include <windows.h>
756 #endif
757 #if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) // UWP doesn't have all Win32 functions
758 #define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS
759 #define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS
760 #endif
761 #endif
762
763 // [Apple] OS specific includes
764 #if defined(__APPLE__)
765 #include <TargetConditionals.h>
766 #endif
767
768 // Visual Studio warnings
769 #ifdef _MSC_VER
770 #pragma warning (disable: 4127) // condition expression is constant
771 #pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen
772 #if defined(_MSC_VER) && _MSC_VER >= 1922 // MSVC 2019 16.2 or later
773 #pragma warning (disable: 5054) // operator '|': deprecated between enumerations of different types
774 #endif
775 #endif
776
777 // Clang/GCC warnings with -Weverything
778 #if defined(__clang__)
779 #if __has_warning("-Wunknown-warning-option")
780 #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!
781 #endif
782 #pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx'
783 #pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse.
784 #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.
785 #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.
786 #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.
787 #pragma clang diagnostic ignored "-Wglobal-constructors" // warning: declaration requires a global destructor // similar to above, not sure what the exact difference is.
788 #pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness
789 #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.
790 #pragma clang diagnostic ignored "-Wint-to-void-pointer-cast" // warning: cast to 'void *' from smaller integer type 'int'
791 #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant // some standard header variations use #define NULL 0
792 #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.
793 #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision
794 #elif defined(__GNUC__)
795 // We disable -Wpragmas because GCC doesn't provide an has_warning equivalent and some forks/patches may not following the warning/version association.
796 #pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind
797 #pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used
798 #pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size
799 #pragma GCC diagnostic ignored "-Wformat" // warning: format '%p' expects argument of type 'void*', but argument 6 has type 'ImGuiWindow*'
800 #pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function
801 #pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value
802 #pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked
803 #pragma GCC diagnostic ignored "-Wstrict-overflow" // warning: assuming signed overflow does not occur when assuming that (X - c) > X is always false
804 #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
805 #endif
806
807 // Debug options
808 #define IMGUI_DEBUG_NAV_SCORING 0 // Display navigation scoring preview when hovering items. Display last moving direction matches when holding CTRL
809 #define IMGUI_DEBUG_NAV_RECTS 0 // Display the reference navigation rectangle for each window
810 #define IMGUI_DEBUG_INI_SETTINGS 0 // Save additional comments in .ini file (particularly helps for Docking, but makes saving slower)
811
812 // 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.
813 static const float NAV_WINDOWING_HIGHLIGHT_DELAY = 0.20f; // Time before the highlight and screen dimming starts fading in
814 static const float NAV_WINDOWING_LIST_APPEAR_DELAY = 0.15f; // Time before the window list starts to appear
815
816 // Window resizing from edges (when io.ConfigWindowsResizeFromEdges = true and ImGuiBackendFlags_HasMouseCursors is set in io.BackendFlags by back-end)
817 static const float WINDOWS_RESIZE_FROM_EDGES_HALF_THICKNESS = 4.0f; // Extend outside and inside windows. Affect FindHoveredWindow().
818 static const float WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER = 0.04f; // Reduce visual noise by only highlighting the border after a certain time.
819 static const float WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER = 2.00f; // Lock scrolled window (so it doesn't pick child windows that are scrolling through) for a certain time, unless mouse moved.
820
821 // Docking
822 static const float DOCKING_TRANSPARENT_PAYLOAD_ALPHA = 0.50f; // For use with io.ConfigDockingTransparentPayload. Apply to Viewport _or_ WindowBg in host viewport.
823 static const float DOCKING_SPLITTER_SIZE = 2.0f;
824
825 //-------------------------------------------------------------------------
826 // [SECTION] FORWARD DECLARATIONS
827 //-------------------------------------------------------------------------
828
829 static void SetCurrentWindow(ImGuiWindow* window);
830 static void FindHoveredWindow();
831 static ImGuiWindow* CreateNewWindow(const char* name, ImGuiWindowFlags flags);
832 static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window);
833
834 static void AddDrawListToDrawData(ImVector<ImDrawList*>* out_list, ImDrawList* draw_list);
835 static void AddWindowToSortBuffer(ImVector<ImGuiWindow*>* out_sorted_windows, ImGuiWindow* window);
836
837 // Settings
838 static void WindowSettingsHandler_ClearAll(ImGuiContext*, ImGuiSettingsHandler*);
839 static void* WindowSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name);
840 static void WindowSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line);
841 static void WindowSettingsHandler_ApplyAll(ImGuiContext*, ImGuiSettingsHandler*);
842 static void WindowSettingsHandler_WriteAll(ImGuiContext*, ImGuiSettingsHandler*, ImGuiTextBuffer* buf);
843
844 // Platform Dependents default implementation for IO functions
845 static const char* GetClipboardTextFn_DefaultImpl(void* user_data);
846 static void SetClipboardTextFn_DefaultImpl(void* user_data, const char* text);
847
848 namespace ImGui
849 {
850 // Navigation
851 static void NavUpdate();
852 static void NavUpdateWindowing();
853 static void NavUpdateWindowingOverlay();
854 static void NavUpdateMoveResult();
855 static float NavUpdatePageUpPageDown();
856 static inline void NavUpdateAnyRequestFlag();
857 static void NavEndFrame();
858 static bool NavScoreItem(ImGuiNavMoveResult* result, ImRect cand);
859 static void NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, ImGuiID id);
860 static ImVec2 NavCalcPreferredRefPos();
861 static void NavSaveLastChildNavWindowIntoParent(ImGuiWindow* nav_window);
862 static ImGuiWindow* NavRestoreLastChildNavWindow(ImGuiWindow* window);
863 static int FindWindowFocusIndex(ImGuiWindow* window);
864
865 // Error Checking
866 static void ErrorCheckNewFrameSanityChecks();
867 static void ErrorCheckEndFrameSanityChecks();
868 static void ErrorCheckBeginEndCompareStacksSize(ImGuiWindow* window, bool write);
869
870 // Misc
871 static void UpdateSettings();
872 static void UpdateMouseInputs();
873 static void UpdateMouseWheel();
874 static void UpdateTabFocus();
875 static void UpdateDebugToolItemPicker();
876 static bool UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4], const ImRect& visibility_rect);
877 static void RenderWindowOuterBorders(ImGuiWindow* window);
878 static void RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar_rect, bool title_bar_is_highlight, bool handle_borders_and_resize_grips, int resize_grip_count, const ImU32 resize_grip_col[4], float resize_grip_draw_size);
879 static void RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& title_bar_rect, const char* name, bool* p_open);
880 static void EndFrameDrawDimmedBackgrounds();
881
882 // Viewports
883 const ImGuiID IMGUI_VIEWPORT_DEFAULT_ID = 0x11111111; // Using an arbitrary constant instead of e.g. ImHashStr("ViewportDefault", 0); so it's easier to spot in the debugger. The exact value doesn't matter.
884 static ImGuiViewportP* AddUpdateViewport(ImGuiWindow* window, ImGuiID id, const ImVec2& platform_pos, const ImVec2& size, ImGuiViewportFlags flags);
885 static void UpdateViewportsNewFrame();
886 static void UpdateViewportsEndFrame();
887 static void UpdateSelectWindowViewport(ImGuiWindow* window);
888 static bool UpdateTryMergeWindowIntoHostViewport(ImGuiWindow* window, ImGuiViewportP* host_viewport);
889 static bool UpdateTryMergeWindowIntoHostViewports(ImGuiWindow* window);
890 static void SetCurrentViewport(ImGuiWindow* window, ImGuiViewportP* viewport);
891 static bool GetWindowAlwaysWantOwnViewport(ImGuiWindow* window);
892 static int FindPlatformMonitorForPos(const ImVec2& pos);
893 static int FindPlatformMonitorForRect(const ImRect& r);
894 static void UpdateViewportPlatformMonitor(ImGuiViewportP* viewport);
895
896 }
897
898 //-----------------------------------------------------------------------------
899 // [SECTION] CONTEXT AND MEMORY ALLOCATORS
900 //-----------------------------------------------------------------------------
901
902 // Current context pointer. Implicitly used by all Dear ImGui functions. Always assumed to be != NULL.
903 // ImGui::CreateContext() will automatically set this pointer if it is NULL. Change to a different context by calling ImGui::SetCurrentContext().
904 // 1) Important: globals are not shared across DLL boundaries! If you use DLLs or any form of hot-reloading: you will need to call
905 // SetCurrentContext() (with the pointer you got from CreateContext) from each unique static/DLL boundary, and after each hot-reloading.
906 // In your debugger, add GImGui to your watch window and notice how its value changes depending on which location you are currently stepping into.
907 // 2) Important: Dear ImGui functions are not thread-safe because of this pointer.
908 // If you want thread-safety to allow N threads to access N different contexts, you can:
909 // - Change this variable to use thread local storage so each thread can refer to a different context, in imconfig.h:
910 // struct ImGuiContext;
911 // extern thread_local ImGuiContext* MyImGuiTLS;
912 // #define GImGui MyImGuiTLS
913 // And then define MyImGuiTLS in one of your cpp file. Note that thread_local is a C++11 keyword, earlier C++ uses compiler-specific keyword.
914 // - Future development aim to make this context pointer explicit to all calls. Also read https://github.com/ocornut/imgui/issues/586
915 // - If you need a finite number of contexts, you may compile and use multiple instances of the ImGui code from different namespace.
916 #ifndef GImGui
917 ImGuiContext* GImGui = NULL;
918 #endif
919
920 // Memory Allocator functions. Use SetAllocatorFunctions() to change them.
921 // If you use DLL hotreloading you might need to call SetAllocatorFunctions() after reloading code from this file.
922 // Otherwise, you probably don't want to modify them mid-program, and if you use global/static e.g. ImVector<> instances you may need to keep them accessible during program destruction.
923 #ifndef IMGUI_DISABLE_DEFAULT_ALLOCATORS
MallocWrapper(size_t size,void * user_data)924 static void* MallocWrapper(size_t size, void* user_data) { IM_UNUSED(user_data); return malloc(size); }
FreeWrapper(void * ptr,void * user_data)925 static void FreeWrapper(void* ptr, void* user_data) { IM_UNUSED(user_data); free(ptr); }
926 #else
MallocWrapper(size_t size,void * user_data)927 static void* MallocWrapper(size_t size, void* user_data) { IM_UNUSED(user_data); IM_UNUSED(size); IM_ASSERT(0); return NULL; }
FreeWrapper(void * ptr,void * user_data)928 static void FreeWrapper(void* ptr, void* user_data) { IM_UNUSED(user_data); IM_UNUSED(ptr); IM_ASSERT(0); }
929 #endif
930
931 static void* (*GImAllocatorAllocFunc)(size_t size, void* user_data) = MallocWrapper;
932 static void (*GImAllocatorFreeFunc)(void* ptr, void* user_data) = FreeWrapper;
933 static void* GImAllocatorUserData = NULL;
934
935 //-----------------------------------------------------------------------------
936 // [SECTION] USER FACING STRUCTURES (ImGuiStyle, ImGuiIO)
937 //-----------------------------------------------------------------------------
938
ImGuiStyle()939 ImGuiStyle::ImGuiStyle()
940 {
941 Alpha = 1.0f; // Global alpha applies to everything in ImGui
942 WindowPadding = ImVec2(8,8); // Padding within a window
943 WindowRounding = 7.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.
944 WindowBorderSize = 1.0f; // Thickness of border around windows. Generally set to 0.0f or 1.0f. Other values not well tested.
945 WindowMinSize = ImVec2(32,32); // Minimum window size
946 WindowTitleAlign = ImVec2(0.0f,0.5f);// Alignment for title bar text
947 WindowMenuButtonPosition= ImGuiDir_Left; // Position of the collapsing/docking button in the title bar (left/right). Defaults to ImGuiDir_Left.
948 ChildRounding = 0.0f; // Radius of child window corners rounding. Set to 0.0f to have rectangular child windows
949 ChildBorderSize = 1.0f; // Thickness of border around child windows. Generally set to 0.0f or 1.0f. Other values not well tested.
950 PopupRounding = 0.0f; // Radius of popup window corners rounding. Set to 0.0f to have rectangular child windows
951 PopupBorderSize = 1.0f; // Thickness of border around popup or tooltip windows. Generally set to 0.0f or 1.0f. Other values not well tested.
952 FramePadding = ImVec2(4,3); // Padding within a framed rectangle (used by most widgets)
953 FrameRounding = 0.0f; // Radius of frame corners rounding. Set to 0.0f to have rectangular frames (used by most widgets).
954 FrameBorderSize = 0.0f; // Thickness of border around frames. Generally set to 0.0f or 1.0f. Other values not well tested.
955 ItemSpacing = ImVec2(8,4); // Horizontal and vertical spacing between widgets/lines
956 ItemInnerSpacing = ImVec2(4,4); // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label)
957 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!
958 IndentSpacing = 21.0f; // Horizontal spacing when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2).
959 ColumnsMinSpacing = 6.0f; // Minimum horizontal spacing between two columns. Preferably > (FramePadding.x + 1).
960 ScrollbarSize = 14.0f; // Width of the vertical scrollbar, Height of the horizontal scrollbar
961 ScrollbarRounding = 9.0f; // Radius of grab corners rounding for scrollbar
962 GrabMinSize = 10.0f; // Minimum width/height of a grab box for slider/scrollbar
963 GrabRounding = 0.0f; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs.
964 LogSliderDeadzone = 4.0f; // The size in pixels of the dead-zone around zero on logarithmic sliders that cross zero.
965 TabRounding = 4.0f; // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs.
966 TabBorderSize = 0.0f; // Thickness of border around tabs.
967 TabMinWidthForUnselectedCloseButton = 0.0f; // Minimum width for close button to appears on an unselected tab when hovered. Set to 0.0f to always show when hovering, set to FLT_MAX to never show close button unless selected.
968 ColorButtonPosition = ImGuiDir_Right; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right.
969 ButtonTextAlign = ImVec2(0.5f,0.5f);// Alignment of button text when button is larger than text.
970 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.
971 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.
972 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.
973 MouseCursorScale = 1.0f; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later.
974 AntiAliasedLines = true; // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU.
975 AntiAliasedLinesUseTex = true; // Enable anti-aliased lines/borders using textures where possible. Require back-end to render with bilinear filtering.
976 AntiAliasedFill = true; // Enable anti-aliased filled shapes (rounded rectangles, circles, etc.).
977 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.
978 CircleSegmentMaxError = 1.60f; // 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.
979
980 // Default theme
981 ImGui::StyleColorsDark(this);
982 }
983
984 // 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.
985 // 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.
ScaleAllSizes(float scale_factor)986 void ImGuiStyle::ScaleAllSizes(float scale_factor)
987 {
988 WindowPadding = ImFloor(WindowPadding * scale_factor);
989 WindowRounding = ImFloor(WindowRounding * scale_factor);
990 WindowMinSize = ImFloor(WindowMinSize * scale_factor);
991 ChildRounding = ImFloor(ChildRounding * scale_factor);
992 PopupRounding = ImFloor(PopupRounding * scale_factor);
993 FramePadding = ImFloor(FramePadding * scale_factor);
994 FrameRounding = ImFloor(FrameRounding * scale_factor);
995 ItemSpacing = ImFloor(ItemSpacing * scale_factor);
996 ItemInnerSpacing = ImFloor(ItemInnerSpacing * scale_factor);
997 TouchExtraPadding = ImFloor(TouchExtraPadding * scale_factor);
998 IndentSpacing = ImFloor(IndentSpacing * scale_factor);
999 ColumnsMinSpacing = ImFloor(ColumnsMinSpacing * scale_factor);
1000 ScrollbarSize = ImFloor(ScrollbarSize * scale_factor);
1001 ScrollbarRounding = ImFloor(ScrollbarRounding * scale_factor);
1002 GrabMinSize = ImFloor(GrabMinSize * scale_factor);
1003 GrabRounding = ImFloor(GrabRounding * scale_factor);
1004 LogSliderDeadzone = ImFloor(LogSliderDeadzone * scale_factor);
1005 TabRounding = ImFloor(TabRounding * scale_factor);
1006 if (TabMinWidthForUnselectedCloseButton != FLT_MAX)
1007 TabMinWidthForUnselectedCloseButton = ImFloor(TabMinWidthForUnselectedCloseButton * scale_factor);
1008 DisplayWindowPadding = ImFloor(DisplayWindowPadding * scale_factor);
1009 DisplaySafeAreaPadding = ImFloor(DisplaySafeAreaPadding * scale_factor);
1010 MouseCursorScale = ImFloor(MouseCursorScale * scale_factor);
1011 }
1012
ImGuiIO()1013 ImGuiIO::ImGuiIO()
1014 {
1015 // Most fields are initialized with zero
1016 memset(this, 0, sizeof(*this));
1017 IM_ASSERT(IM_ARRAYSIZE(ImGuiIO::MouseDown) == ImGuiMouseButton_COUNT && IM_ARRAYSIZE(ImGuiIO::MouseClicked) == ImGuiMouseButton_COUNT); // Our pre-C++11 IM_STATIC_ASSERT() macros triggers warning on modern compilers so we don't use it here.
1018
1019 // Settings
1020 ConfigFlags = ImGuiConfigFlags_None;
1021 BackendFlags = ImGuiBackendFlags_None;
1022 DisplaySize = ImVec2(-1.0f, -1.0f);
1023 DeltaTime = 1.0f / 60.0f;
1024 IniSavingRate = 5.0f;
1025 IniFilename = "imgui.ini";
1026 LogFilename = "imgui_log.txt";
1027 MouseDoubleClickTime = 0.30f;
1028 MouseDoubleClickMaxDist = 6.0f;
1029 for (int i = 0; i < ImGuiKey_COUNT; i++)
1030 KeyMap[i] = -1;
1031 KeyRepeatDelay = 0.275f;
1032 KeyRepeatRate = 0.050f;
1033 UserData = NULL;
1034
1035 Fonts = NULL;
1036 FontGlobalScale = 1.0f;
1037 FontDefault = NULL;
1038 FontAllowUserScaling = false;
1039 DisplayFramebufferScale = ImVec2(1.0f, 1.0f);
1040
1041 // Docking options (when ImGuiConfigFlags_DockingEnable is set)
1042 ConfigDockingNoSplit = false;
1043 ConfigDockingWithShift = false;
1044 ConfigDockingAlwaysTabBar = false;
1045 ConfigDockingTransparentPayload = false;
1046
1047 // Viewport options (when ImGuiConfigFlags_ViewportsEnable is set)
1048 ConfigViewportsNoAutoMerge = false;
1049 ConfigViewportsNoTaskBarIcon = false;
1050 ConfigViewportsNoDecoration = true;
1051 ConfigViewportsNoDefaultParent = false;
1052
1053 // Miscellaneous options
1054 MouseDrawCursor = false;
1055 #ifdef __APPLE__
1056 ConfigMacOSXBehaviors = true; // Set Mac OS X style defaults based on __APPLE__ compile time flag
1057 #else
1058 ConfigMacOSXBehaviors = false;
1059 #endif
1060 ConfigInputTextCursorBlink = true;
1061 ConfigWindowsResizeFromEdges = true;
1062 ConfigWindowsMoveFromTitleBarOnly = false;
1063 ConfigWindowsMemoryCompactTimer = 60.0f;
1064
1065 // Platform Functions
1066 BackendPlatformName = BackendRendererName = NULL;
1067 BackendPlatformUserData = BackendRendererUserData = BackendLanguageUserData = NULL;
1068 GetClipboardTextFn = GetClipboardTextFn_DefaultImpl; // Platform dependent default implementations
1069 SetClipboardTextFn = SetClipboardTextFn_DefaultImpl;
1070 ClipboardUserData = NULL;
1071
1072 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
1073 RenderDrawListsFn = NULL;
1074 #endif
1075
1076 // Input (NB: we already have memset zero the entire structure!)
1077 MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
1078 MousePosPrev = ImVec2(-FLT_MAX, -FLT_MAX);
1079 MouseDragThreshold = 6.0f;
1080 for (int i = 0; i < IM_ARRAYSIZE(MouseDownDuration); i++) MouseDownDuration[i] = MouseDownDurationPrev[i] = -1.0f;
1081 for (int i = 0; i < IM_ARRAYSIZE(KeysDownDuration); i++) KeysDownDuration[i] = KeysDownDurationPrev[i] = -1.0f;
1082 for (int i = 0; i < IM_ARRAYSIZE(NavInputsDownDuration); i++) NavInputsDownDuration[i] = -1.0f;
1083 }
1084
1085 // Pass in translated ASCII characters for text input.
1086 // - with glfw you can get those from the callback set in glfwSetCharCallback()
1087 // - on Windows you can get those using ToAscii+keyboard state, or via the WM_CHAR message
AddInputCharacter(unsigned int c)1088 void ImGuiIO::AddInputCharacter(unsigned int c)
1089 {
1090 if (c != 0)
1091 InputQueueCharacters.push_back(c <= IM_UNICODE_CODEPOINT_MAX ? (ImWchar)c : IM_UNICODE_CODEPOINT_INVALID);
1092 }
1093
1094 // UTF16 strings use surrogate pairs to encode codepoints >= 0x10000, so
1095 // we should save the high surrogate.
AddInputCharacterUTF16(ImWchar16 c)1096 void ImGuiIO::AddInputCharacterUTF16(ImWchar16 c)
1097 {
1098 if (c == 0 && InputQueueSurrogate == 0)
1099 return;
1100
1101 if ((c & 0xFC00) == 0xD800) // High surrogate, must save
1102 {
1103 if (InputQueueSurrogate != 0)
1104 InputQueueCharacters.push_back(IM_UNICODE_CODEPOINT_INVALID);
1105 InputQueueSurrogate = c;
1106 return;
1107 }
1108
1109 ImWchar cp = c;
1110 if (InputQueueSurrogate != 0)
1111 {
1112 if ((c & 0xFC00) != 0xDC00) // Invalid low surrogate
1113 InputQueueCharacters.push_back(IM_UNICODE_CODEPOINT_INVALID);
1114 else if (IM_UNICODE_CODEPOINT_MAX == (0xFFFF)) // Codepoint will not fit in ImWchar (extra parenthesis around 0xFFFF somehow fixes -Wunreachable-code with Clang)
1115 cp = IM_UNICODE_CODEPOINT_INVALID;
1116 else
1117 cp = (ImWchar)(((InputQueueSurrogate - 0xD800) << 10) + (c - 0xDC00) + 0x10000);
1118 InputQueueSurrogate = 0;
1119 }
1120 InputQueueCharacters.push_back(cp);
1121 }
1122
AddInputCharactersUTF8(const char * utf8_chars)1123 void ImGuiIO::AddInputCharactersUTF8(const char* utf8_chars)
1124 {
1125 while (*utf8_chars != 0)
1126 {
1127 unsigned int c = 0;
1128 utf8_chars += ImTextCharFromUtf8(&c, utf8_chars, NULL);
1129 if (c != 0)
1130 InputQueueCharacters.push_back((ImWchar)c);
1131 }
1132 }
1133
ClearInputCharacters()1134 void ImGuiIO::ClearInputCharacters()
1135 {
1136 InputQueueCharacters.resize(0);
1137 }
1138
1139 //-----------------------------------------------------------------------------
1140 // [SECTION] MISC HELPERS/UTILITIES (Geometry functions)
1141 //-----------------------------------------------------------------------------
1142
ImBezierClosestPoint(const ImVec2 & p1,const ImVec2 & p2,const ImVec2 & p3,const ImVec2 & p4,const ImVec2 & p,int num_segments)1143 ImVec2 ImBezierClosestPoint(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& p, int num_segments)
1144 {
1145 IM_ASSERT(num_segments > 0); // Use ImBezierClosestPointCasteljau()
1146 ImVec2 p_last = p1;
1147 ImVec2 p_closest;
1148 float p_closest_dist2 = FLT_MAX;
1149 float t_step = 1.0f / (float)num_segments;
1150 for (int i_step = 1; i_step <= num_segments; i_step++)
1151 {
1152 ImVec2 p_current = ImBezierCalc(p1, p2, p3, p4, t_step * i_step);
1153 ImVec2 p_line = ImLineClosestPoint(p_last, p_current, p);
1154 float dist2 = ImLengthSqr(p - p_line);
1155 if (dist2 < p_closest_dist2)
1156 {
1157 p_closest = p_line;
1158 p_closest_dist2 = dist2;
1159 }
1160 p_last = p_current;
1161 }
1162 return p_closest;
1163 }
1164
1165 // Closely mimics PathBezierToCasteljau() in imgui_draw.cpp
BezierClosestPointCasteljauStep(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)1166 static void BezierClosestPointCasteljauStep(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)
1167 {
1168 float dx = x4 - x1;
1169 float dy = y4 - y1;
1170 float d2 = ((x2 - x4) * dy - (y2 - y4) * dx);
1171 float d3 = ((x3 - x4) * dy - (y3 - y4) * dx);
1172 d2 = (d2 >= 0) ? d2 : -d2;
1173 d3 = (d3 >= 0) ? d3 : -d3;
1174 if ((d2 + d3) * (d2 + d3) < tess_tol * (dx * dx + dy * dy))
1175 {
1176 ImVec2 p_current(x4, y4);
1177 ImVec2 p_line = ImLineClosestPoint(p_last, p_current, p);
1178 float dist2 = ImLengthSqr(p - p_line);
1179 if (dist2 < p_closest_dist2)
1180 {
1181 p_closest = p_line;
1182 p_closest_dist2 = dist2;
1183 }
1184 p_last = p_current;
1185 }
1186 else if (level < 10)
1187 {
1188 float x12 = (x1 + x2)*0.5f, y12 = (y1 + y2)*0.5f;
1189 float x23 = (x2 + x3)*0.5f, y23 = (y2 + y3)*0.5f;
1190 float x34 = (x3 + x4)*0.5f, y34 = (y3 + y4)*0.5f;
1191 float x123 = (x12 + x23)*0.5f, y123 = (y12 + y23)*0.5f;
1192 float x234 = (x23 + x34)*0.5f, y234 = (y23 + y34)*0.5f;
1193 float x1234 = (x123 + x234)*0.5f, y1234 = (y123 + y234)*0.5f;
1194 BezierClosestPointCasteljauStep(p, p_closest, p_last, p_closest_dist2, x1, y1, x12, y12, x123, y123, x1234, y1234, tess_tol, level + 1);
1195 BezierClosestPointCasteljauStep(p, p_closest, p_last, p_closest_dist2, x1234, y1234, x234, y234, x34, y34, x4, y4, tess_tol, level + 1);
1196 }
1197 }
1198
1199 // tess_tol is generally the same value you would find in ImGui::GetStyle().CurveTessellationTol
1200 // Because those ImXXX functions are lower-level than ImGui:: we cannot access this value automatically.
ImBezierClosestPointCasteljau(const ImVec2 & p1,const ImVec2 & p2,const ImVec2 & p3,const ImVec2 & p4,const ImVec2 & p,float tess_tol)1201 ImVec2 ImBezierClosestPointCasteljau(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, const ImVec2& p, float tess_tol)
1202 {
1203 IM_ASSERT(tess_tol > 0.0f);
1204 ImVec2 p_last = p1;
1205 ImVec2 p_closest;
1206 float p_closest_dist2 = FLT_MAX;
1207 BezierClosestPointCasteljauStep(p, p_closest, p_last, p_closest_dist2, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, tess_tol, 0);
1208 return p_closest;
1209 }
1210
ImLineClosestPoint(const ImVec2 & a,const ImVec2 & b,const ImVec2 & p)1211 ImVec2 ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& p)
1212 {
1213 ImVec2 ap = p - a;
1214 ImVec2 ab_dir = b - a;
1215 float dot = ap.x * ab_dir.x + ap.y * ab_dir.y;
1216 if (dot < 0.0f)
1217 return a;
1218 float ab_len_sqr = ab_dir.x * ab_dir.x + ab_dir.y * ab_dir.y;
1219 if (dot > ab_len_sqr)
1220 return b;
1221 return a + ab_dir * dot / ab_len_sqr;
1222 }
1223
ImTriangleContainsPoint(const ImVec2 & a,const ImVec2 & b,const ImVec2 & c,const ImVec2 & p)1224 bool ImTriangleContainsPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p)
1225 {
1226 bool b1 = ((p.x - b.x) * (a.y - b.y) - (p.y - b.y) * (a.x - b.x)) < 0.0f;
1227 bool b2 = ((p.x - c.x) * (b.y - c.y) - (p.y - c.y) * (b.x - c.x)) < 0.0f;
1228 bool b3 = ((p.x - a.x) * (c.y - a.y) - (p.y - a.y) * (c.x - a.x)) < 0.0f;
1229 return ((b1 == b2) && (b2 == b3));
1230 }
1231
ImTriangleBarycentricCoords(const ImVec2 & a,const ImVec2 & b,const ImVec2 & c,const ImVec2 & p,float & out_u,float & out_v,float & out_w)1232 void ImTriangleBarycentricCoords(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p, float& out_u, float& out_v, float& out_w)
1233 {
1234 ImVec2 v0 = b - a;
1235 ImVec2 v1 = c - a;
1236 ImVec2 v2 = p - a;
1237 const float denom = v0.x * v1.y - v1.x * v0.y;
1238 out_v = (v2.x * v1.y - v1.x * v2.y) / denom;
1239 out_w = (v0.x * v2.y - v2.x * v0.y) / denom;
1240 out_u = 1.0f - out_v - out_w;
1241 }
1242
ImTriangleClosestPoint(const ImVec2 & a,const ImVec2 & b,const ImVec2 & c,const ImVec2 & p)1243 ImVec2 ImTriangleClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p)
1244 {
1245 ImVec2 proj_ab = ImLineClosestPoint(a, b, p);
1246 ImVec2 proj_bc = ImLineClosestPoint(b, c, p);
1247 ImVec2 proj_ca = ImLineClosestPoint(c, a, p);
1248 float dist2_ab = ImLengthSqr(p - proj_ab);
1249 float dist2_bc = ImLengthSqr(p - proj_bc);
1250 float dist2_ca = ImLengthSqr(p - proj_ca);
1251 float m = ImMin(dist2_ab, ImMin(dist2_bc, dist2_ca));
1252 if (m == dist2_ab)
1253 return proj_ab;
1254 if (m == dist2_bc)
1255 return proj_bc;
1256 return proj_ca;
1257 }
1258
1259 //-----------------------------------------------------------------------------
1260 // [SECTION] MISC HELPERS/UTILITIES (String, Format, Hash functions)
1261 //-----------------------------------------------------------------------------
1262
1263 // Consider using _stricmp/_strnicmp under Windows or strcasecmp/strncasecmp. We don't actually use either ImStricmp/ImStrnicmp in the codebase any more.
ImStricmp(const char * str1,const char * str2)1264 int ImStricmp(const char* str1, const char* str2)
1265 {
1266 int d;
1267 while ((d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; }
1268 return d;
1269 }
1270
ImStrnicmp(const char * str1,const char * str2,size_t count)1271 int ImStrnicmp(const char* str1, const char* str2, size_t count)
1272 {
1273 int d = 0;
1274 while (count > 0 && (d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; count--; }
1275 return d;
1276 }
1277
ImStrncpy(char * dst,const char * src,size_t count)1278 void ImStrncpy(char* dst, const char* src, size_t count)
1279 {
1280 if (count < 1)
1281 return;
1282 if (count > 1)
1283 strncpy(dst, src, count - 1);
1284 dst[count - 1] = 0;
1285 }
1286
ImStrdup(const char * str)1287 char* ImStrdup(const char* str)
1288 {
1289 size_t len = strlen(str);
1290 void* buf = IM_ALLOC(len + 1);
1291 return (char*)memcpy(buf, (const void*)str, len + 1);
1292 }
1293
ImStrdupcpy(char * dst,size_t * p_dst_size,const char * src)1294 char* ImStrdupcpy(char* dst, size_t* p_dst_size, const char* src)
1295 {
1296 size_t dst_buf_size = p_dst_size ? *p_dst_size : strlen(dst) + 1;
1297 size_t src_size = strlen(src) + 1;
1298 if (dst_buf_size < src_size)
1299 {
1300 IM_FREE(dst);
1301 dst = (char*)IM_ALLOC(src_size);
1302 if (p_dst_size)
1303 *p_dst_size = src_size;
1304 }
1305 return (char*)memcpy(dst, (const void*)src, src_size);
1306 }
1307
ImStrchrRange(const char * str,const char * str_end,char c)1308 const char* ImStrchrRange(const char* str, const char* str_end, char c)
1309 {
1310 const char* p = (const char*)memchr(str, (int)c, str_end - str);
1311 return p;
1312 }
1313
ImStrlenW(const ImWchar * str)1314 int ImStrlenW(const ImWchar* str)
1315 {
1316 //return (int)wcslen((const wchar_t*)str); // FIXME-OPT: Could use this when wchar_t are 16-bit
1317 int n = 0;
1318 while (*str++) n++;
1319 return n;
1320 }
1321
1322 // Find end-of-line. Return pointer will point to either first \n, either str_end.
ImStreolRange(const char * str,const char * str_end)1323 const char* ImStreolRange(const char* str, const char* str_end)
1324 {
1325 const char* p = (const char*)memchr(str, '\n', str_end - str);
1326 return p ? p : str_end;
1327 }
1328
ImStrbolW(const ImWchar * buf_mid_line,const ImWchar * buf_begin)1329 const ImWchar* ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* buf_begin) // find beginning-of-line
1330 {
1331 while (buf_mid_line > buf_begin && buf_mid_line[-1] != '\n')
1332 buf_mid_line--;
1333 return buf_mid_line;
1334 }
1335
ImStristr(const char * haystack,const char * haystack_end,const char * needle,const char * needle_end)1336 const char* ImStristr(const char* haystack, const char* haystack_end, const char* needle, const char* needle_end)
1337 {
1338 if (!needle_end)
1339 needle_end = needle + strlen(needle);
1340
1341 const char un0 = (char)toupper(*needle);
1342 while ((!haystack_end && *haystack) || (haystack_end && haystack < haystack_end))
1343 {
1344 if (toupper(*haystack) == un0)
1345 {
1346 const char* b = needle + 1;
1347 for (const char* a = haystack + 1; b < needle_end; a++, b++)
1348 if (toupper(*a) != toupper(*b))
1349 break;
1350 if (b == needle_end)
1351 return haystack;
1352 }
1353 haystack++;
1354 }
1355 return NULL;
1356 }
1357
1358 // 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.
ImStrTrimBlanks(char * buf)1359 void ImStrTrimBlanks(char* buf)
1360 {
1361 char* p = buf;
1362 while (p[0] == ' ' || p[0] == '\t') // Leading blanks
1363 p++;
1364 char* p_start = p;
1365 while (*p != 0) // Find end of string
1366 p++;
1367 while (p > p_start && (p[-1] == ' ' || p[-1] == '\t')) // Trailing blanks
1368 p--;
1369 if (p_start != buf) // Copy memory if we had leading blanks
1370 memmove(buf, p_start, p - p_start);
1371 buf[p - p_start] = 0; // Zero terminate
1372 }
1373
ImStrSkipBlank(const char * str)1374 const char* ImStrSkipBlank(const char* str)
1375 {
1376 while (str[0] == ' ' || str[0] == '\t')
1377 str++;
1378 return str;
1379 }
1380
1381 // A) MSVC version appears to return -1 on overflow, whereas glibc appears to return total count (which may be >= buf_size).
1382 // 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.
1383 // B) When buf==NULL vsnprintf() will return the output size.
1384 #ifndef IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS
1385
1386 // We support stb_sprintf which is much faster (see: https://github.com/nothings/stb/blob/master/stb_sprintf.h)
1387 // You may set IMGUI_USE_STB_SPRINTF to use our default wrapper, or set IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS
1388 // and setup the wrapper yourself. (FIXME-OPT: Some of our high-level operations such as ImGuiTextBuffer::appendfv() are
1389 // designed using two-passes worst case, which probably could be improved using the stbsp_vsprintfcb() function.)
1390 #ifdef IMGUI_USE_STB_SPRINTF
1391 #define STB_SPRINTF_IMPLEMENTATION
1392 #include "stb_sprintf.h"
1393 #endif
1394
1395 #if defined(_MSC_VER) && !defined(vsnprintf)
1396 #define vsnprintf _vsnprintf
1397 #endif
1398
ImFormatString(char * buf,size_t buf_size,const char * fmt,...)1399 int ImFormatString(char* buf, size_t buf_size, const char* fmt, ...)
1400 {
1401 va_list args;
1402 va_start(args, fmt);
1403 #ifdef IMGUI_USE_STB_SPRINTF
1404 int w = stbsp_vsnprintf(buf, (int)buf_size, fmt, args);
1405 #else
1406 int w = vsnprintf(buf, buf_size, fmt, args);
1407 #endif
1408 va_end(args);
1409 if (buf == NULL)
1410 return w;
1411 if (w == -1 || w >= (int)buf_size)
1412 w = (int)buf_size - 1;
1413 buf[w] = 0;
1414 return w;
1415 }
1416
ImFormatStringV(char * buf,size_t buf_size,const char * fmt,va_list args)1417 int ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args)
1418 {
1419 #ifdef IMGUI_USE_STB_SPRINTF
1420 int w = stbsp_vsnprintf(buf, (int)buf_size, fmt, args);
1421 #else
1422 int w = vsnprintf(buf, buf_size, fmt, args);
1423 #endif
1424 if (buf == NULL)
1425 return w;
1426 if (w == -1 || w >= (int)buf_size)
1427 w = (int)buf_size - 1;
1428 buf[w] = 0;
1429 return w;
1430 }
1431 #endif // #ifdef IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS
1432
1433 // CRC32 needs a 1KB lookup table (not cache friendly)
1434 // Although the code to generate the table is simple and shorter than the table itself, using a const table allows us to easily:
1435 // - avoid an unnecessary branch/memory tap, - keep the ImHashXXX functions usable by static constructors, - make it thread-safe.
1436 static const ImU32 GCrc32LookupTable[256] =
1437 {
1438 0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F,0xE963A535,0x9E6495A3,0x0EDB8832,0x79DCB8A4,0xE0D5E91E,0x97D2D988,0x09B64C2B,0x7EB17CBD,0xE7B82D07,0x90BF1D91,
1439 0x1DB71064,0x6AB020F2,0xF3B97148,0x84BE41DE,0x1ADAD47D,0x6DDDE4EB,0xF4D4B551,0x83D385C7,0x136C9856,0x646BA8C0,0xFD62F97A,0x8A65C9EC,0x14015C4F,0x63066CD9,0xFA0F3D63,0x8D080DF5,
1440 0x3B6E20C8,0x4C69105E,0xD56041E4,0xA2677172,0x3C03E4D1,0x4B04D447,0xD20D85FD,0xA50AB56B,0x35B5A8FA,0x42B2986C,0xDBBBC9D6,0xACBCF940,0x32D86CE3,0x45DF5C75,0xDCD60DCF,0xABD13D59,
1441 0x26D930AC,0x51DE003A,0xC8D75180,0xBFD06116,0x21B4F4B5,0x56B3C423,0xCFBA9599,0xB8BDA50F,0x2802B89E,0x5F058808,0xC60CD9B2,0xB10BE924,0x2F6F7C87,0x58684C11,0xC1611DAB,0xB6662D3D,
1442 0x76DC4190,0x01DB7106,0x98D220BC,0xEFD5102A,0x71B18589,0x06B6B51F,0x9FBFE4A5,0xE8B8D433,0x7807C9A2,0x0F00F934,0x9609A88E,0xE10E9818,0x7F6A0DBB,0x086D3D2D,0x91646C97,0xE6635C01,
1443 0x6B6B51F4,0x1C6C6162,0x856530D8,0xF262004E,0x6C0695ED,0x1B01A57B,0x8208F4C1,0xF50FC457,0x65B0D9C6,0x12B7E950,0x8BBEB8EA,0xFCB9887C,0x62DD1DDF,0x15DA2D49,0x8CD37CF3,0xFBD44C65,
1444 0x4DB26158,0x3AB551CE,0xA3BC0074,0xD4BB30E2,0x4ADFA541,0x3DD895D7,0xA4D1C46D,0xD3D6F4FB,0x4369E96A,0x346ED9FC,0xAD678846,0xDA60B8D0,0x44042D73,0x33031DE5,0xAA0A4C5F,0xDD0D7CC9,
1445 0x5005713C,0x270241AA,0xBE0B1010,0xC90C2086,0x5768B525,0x206F85B3,0xB966D409,0xCE61E49F,0x5EDEF90E,0x29D9C998,0xB0D09822,0xC7D7A8B4,0x59B33D17,0x2EB40D81,0xB7BD5C3B,0xC0BA6CAD,
1446 0xEDB88320,0x9ABFB3B6,0x03B6E20C,0x74B1D29A,0xEAD54739,0x9DD277AF,0x04DB2615,0x73DC1683,0xE3630B12,0x94643B84,0x0D6D6A3E,0x7A6A5AA8,0xE40ECF0B,0x9309FF9D,0x0A00AE27,0x7D079EB1,
1447 0xF00F9344,0x8708A3D2,0x1E01F268,0x6906C2FE,0xF762575D,0x806567CB,0x196C3671,0x6E6B06E7,0xFED41B76,0x89D32BE0,0x10DA7A5A,0x67DD4ACC,0xF9B9DF6F,0x8EBEEFF9,0x17B7BE43,0x60B08ED5,
1448 0xD6D6A3E8,0xA1D1937E,0x38D8C2C4,0x4FDFF252,0xD1BB67F1,0xA6BC5767,0x3FB506DD,0x48B2364B,0xD80D2BDA,0xAF0A1B4C,0x36034AF6,0x41047A60,0xDF60EFC3,0xA867DF55,0x316E8EEF,0x4669BE79,
1449 0xCB61B38C,0xBC66831A,0x256FD2A0,0x5268E236,0xCC0C7795,0xBB0B4703,0x220216B9,0x5505262F,0xC5BA3BBE,0xB2BD0B28,0x2BB45A92,0x5CB36A04,0xC2D7FFA7,0xB5D0CF31,0x2CD99E8B,0x5BDEAE1D,
1450 0x9B64C2B0,0xEC63F226,0x756AA39C,0x026D930A,0x9C0906A9,0xEB0E363F,0x72076785,0x05005713,0x95BF4A82,0xE2B87A14,0x7BB12BAE,0x0CB61B38,0x92D28E9B,0xE5D5BE0D,0x7CDCEFB7,0x0BDBDF21,
1451 0x86D3D2D4,0xF1D4E242,0x68DDB3F8,0x1FDA836E,0x81BE16CD,0xF6B9265B,0x6FB077E1,0x18B74777,0x88085AE6,0xFF0F6A70,0x66063BCA,0x11010B5C,0x8F659EFF,0xF862AE69,0x616BFFD3,0x166CCF45,
1452 0xA00AE278,0xD70DD2EE,0x4E048354,0x3903B3C2,0xA7672661,0xD06016F7,0x4969474D,0x3E6E77DB,0xAED16A4A,0xD9D65ADC,0x40DF0B66,0x37D83BF0,0xA9BCAE53,0xDEBB9EC5,0x47B2CF7F,0x30B5FFE9,
1453 0xBDBDF21C,0xCABAC28A,0x53B39330,0x24B4A3A6,0xBAD03605,0xCDD70693,0x54DE5729,0x23D967BF,0xB3667A2E,0xC4614AB8,0x5D681B02,0x2A6F2B94,0xB40BBE37,0xC30C8EA1,0x5A05DF1B,0x2D02EF8D,
1454 };
1455
1456 // Known size hash
1457 // It is ok to call ImHashData on a string with known length but the ### operator won't be supported.
1458 // FIXME-OPT: Replace with e.g. FNV1a hash? CRC32 pretty much randomly access 1KB. Need to do proper measurements.
ImHashData(const void * data_p,size_t data_size,ImU32 seed)1459 ImU32 ImHashData(const void* data_p, size_t data_size, ImU32 seed)
1460 {
1461 ImU32 crc = ~seed;
1462 const unsigned char* data = (const unsigned char*)data_p;
1463 const ImU32* crc32_lut = GCrc32LookupTable;
1464 while (data_size-- != 0)
1465 crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ *data++];
1466 return ~crc;
1467 }
1468
1469 // Zero-terminated string hash, with support for ### to reset back to seed value
1470 // We support a syntax of "label###id" where only "###id" is included in the hash, and only "label" gets displayed.
1471 // Because this syntax is rarely used we are optimizing for the common case.
1472 // - If we reach ### in the string we discard the hash so far and reset to the seed.
1473 // - We don't do 'current += 2; continue;' after handling ### to keep the code smaller/faster (measured ~10% diff in Debug build)
1474 // FIXME-OPT: Replace with e.g. FNV1a hash? CRC32 pretty much randomly access 1KB. Need to do proper measurements.
ImHashStr(const char * data_p,size_t data_size,ImU32 seed)1475 ImU32 ImHashStr(const char* data_p, size_t data_size, ImU32 seed)
1476 {
1477 seed = ~seed;
1478 ImU32 crc = seed;
1479 const unsigned char* data = (const unsigned char*)data_p;
1480 const ImU32* crc32_lut = GCrc32LookupTable;
1481 if (data_size != 0)
1482 {
1483 while (data_size-- != 0)
1484 {
1485 unsigned char c = *data++;
1486 if (c == '#' && data_size >= 2 && data[0] == '#' && data[1] == '#')
1487 crc = seed;
1488 crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c];
1489 }
1490 }
1491 else
1492 {
1493 while (unsigned char c = *data++)
1494 {
1495 if (c == '#' && data[0] == '#' && data[1] == '#')
1496 crc = seed;
1497 crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c];
1498 }
1499 }
1500 return ~crc;
1501 }
1502
1503 //-----------------------------------------------------------------------------
1504 // [SECTION] MISC HELPERS/UTILITIES (File functions)
1505 //-----------------------------------------------------------------------------
1506
1507 // Default file functions
1508 #ifndef IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS
1509
ImFileOpen(const char * filename,const char * mode)1510 ImFileHandle ImFileOpen(const char* filename, const char* mode)
1511 {
1512 #if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && !defined(__CYGWIN__) && !defined(__GNUC__)
1513 // We need a fopen() wrapper because MSVC/Windows fopen doesn't handle UTF-8 filenames.
1514 // Previously we used ImTextCountCharsFromUtf8/ImTextStrFromUtf8 here but we now need to support ImWchar16 and ImWchar32!
1515 const int filename_wsize = ::MultiByteToWideChar(CP_UTF8, 0, filename, -1, NULL, 0);
1516 const int mode_wsize = ::MultiByteToWideChar(CP_UTF8, 0, mode, -1, NULL, 0);
1517 ImVector<ImWchar> buf;
1518 buf.resize(filename_wsize + mode_wsize);
1519 ::MultiByteToWideChar(CP_UTF8, 0, filename, -1, (wchar_t*)&buf[0], filename_wsize);
1520 ::MultiByteToWideChar(CP_UTF8, 0, mode, -1, (wchar_t*)&buf[filename_wsize], mode_wsize);
1521 return ::_wfopen((const wchar_t*)&buf[0], (const wchar_t*)&buf[filename_wsize]);
1522 #else
1523 return fopen(filename, mode);
1524 #endif
1525 }
1526
1527 // 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.
ImFileClose(ImFileHandle f)1528 bool ImFileClose(ImFileHandle f) { return fclose(f) == 0; }
ImFileGetSize(ImFileHandle f)1529 ImU64 ImFileGetSize(ImFileHandle f) { long off = 0, sz = 0; return ((off = ftell(f)) != -1 && !fseek(f, 0, SEEK_END) && (sz = ftell(f)) != -1 && !fseek(f, off, SEEK_SET)) ? (ImU64)sz : (ImU64)-1; }
ImFileRead(void * data,ImU64 sz,ImU64 count,ImFileHandle f)1530 ImU64 ImFileRead(void* data, ImU64 sz, ImU64 count, ImFileHandle f) { return fread(data, (size_t)sz, (size_t)count, f); }
ImFileWrite(const void * data,ImU64 sz,ImU64 count,ImFileHandle f)1531 ImU64 ImFileWrite(const void* data, ImU64 sz, ImU64 count, ImFileHandle f) { return fwrite(data, (size_t)sz, (size_t)count, f); }
1532 #endif // #ifndef IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS
1533
1534 // Helper: Load file content into memory
1535 // Memory allocated with IM_ALLOC(), must be freed by user using IM_FREE() == ImGui::MemFree()
1536 // This can't really be used with "rt" because fseek size won't match read size.
ImFileLoadToMemory(const char * filename,const char * mode,size_t * out_file_size,int padding_bytes)1537 void* ImFileLoadToMemory(const char* filename, const char* mode, size_t* out_file_size, int padding_bytes)
1538 {
1539 IM_ASSERT(filename && mode);
1540 if (out_file_size)
1541 *out_file_size = 0;
1542
1543 ImFileHandle f;
1544 if ((f = ImFileOpen(filename, mode)) == NULL)
1545 return NULL;
1546
1547 size_t file_size = (size_t)ImFileGetSize(f);
1548 if (file_size == (size_t)-1)
1549 {
1550 ImFileClose(f);
1551 return NULL;
1552 }
1553
1554 void* file_data = IM_ALLOC(file_size + padding_bytes);
1555 if (file_data == NULL)
1556 {
1557 ImFileClose(f);
1558 return NULL;
1559 }
1560 if (ImFileRead(file_data, 1, file_size, f) != file_size)
1561 {
1562 ImFileClose(f);
1563 IM_FREE(file_data);
1564 return NULL;
1565 }
1566 if (padding_bytes > 0)
1567 memset((void*)(((char*)file_data) + file_size), 0, (size_t)padding_bytes);
1568
1569 ImFileClose(f);
1570 if (out_file_size)
1571 *out_file_size = file_size;
1572
1573 return file_data;
1574 }
1575
1576 //-----------------------------------------------------------------------------
1577 // [SECTION] MISC HELPERS/UTILITIES (ImText* functions)
1578 //-----------------------------------------------------------------------------
1579
1580 // Convert UTF-8 to 32-bit character, process single character input.
1581 // Based on stb_from_utf8() from github.com/nothings/stb/
1582 // We handle UTF-8 decoding error by skipping forward.
ImTextCharFromUtf8(unsigned int * out_char,const char * in_text,const char * in_text_end)1583 int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* in_text_end)
1584 {
1585 unsigned int c = (unsigned int)-1;
1586 const unsigned char* str = (const unsigned char*)in_text;
1587 if (!(*str & 0x80))
1588 {
1589 c = (unsigned int)(*str++);
1590 *out_char = c;
1591 return 1;
1592 }
1593 if ((*str & 0xe0) == 0xc0)
1594 {
1595 *out_char = IM_UNICODE_CODEPOINT_INVALID; // will be invalid but not end of string
1596 if (in_text_end && in_text_end - (const char*)str < 2) return 1;
1597 if (*str < 0xc2) return 2;
1598 c = (unsigned int)((*str++ & 0x1f) << 6);
1599 if ((*str & 0xc0) != 0x80) return 2;
1600 c += (*str++ & 0x3f);
1601 *out_char = c;
1602 return 2;
1603 }
1604 if ((*str & 0xf0) == 0xe0)
1605 {
1606 *out_char = IM_UNICODE_CODEPOINT_INVALID; // will be invalid but not end of string
1607 if (in_text_end && in_text_end - (const char*)str < 3) return 1;
1608 if (*str == 0xe0 && (str[1] < 0xa0 || str[1] > 0xbf)) return 3;
1609 if (*str == 0xed && str[1] > 0x9f) return 3; // str[1] < 0x80 is checked below
1610 c = (unsigned int)((*str++ & 0x0f) << 12);
1611 if ((*str & 0xc0) != 0x80) return 3;
1612 c += (unsigned int)((*str++ & 0x3f) << 6);
1613 if ((*str & 0xc0) != 0x80) return 3;
1614 c += (*str++ & 0x3f);
1615 *out_char = c;
1616 return 3;
1617 }
1618 if ((*str & 0xf8) == 0xf0)
1619 {
1620 *out_char = IM_UNICODE_CODEPOINT_INVALID; // will be invalid but not end of string
1621 if (in_text_end && in_text_end - (const char*)str < 4) return 1;
1622 if (*str > 0xf4) return 4;
1623 if (*str == 0xf0 && (str[1] < 0x90 || str[1] > 0xbf)) return 4;
1624 if (*str == 0xf4 && str[1] > 0x8f) return 4; // str[1] < 0x80 is checked below
1625 c = (unsigned int)((*str++ & 0x07) << 18);
1626 if ((*str & 0xc0) != 0x80) return 4;
1627 c += (unsigned int)((*str++ & 0x3f) << 12);
1628 if ((*str & 0xc0) != 0x80) return 4;
1629 c += (unsigned int)((*str++ & 0x3f) << 6);
1630 if ((*str & 0xc0) != 0x80) return 4;
1631 c += (*str++ & 0x3f);
1632 // utf-8 encodings of values used in surrogate pairs are invalid
1633 if ((c & 0xFFFFF800) == 0xD800) return 4;
1634 // If codepoint does not fit in ImWchar, use replacement character U+FFFD instead
1635 if (c > IM_UNICODE_CODEPOINT_MAX) c = IM_UNICODE_CODEPOINT_INVALID;
1636 *out_char = c;
1637 return 4;
1638 }
1639 *out_char = 0;
1640 return 0;
1641 }
1642
ImTextStrFromUtf8(ImWchar * buf,int buf_size,const char * in_text,const char * in_text_end,const char ** in_text_remaining)1643 int ImTextStrFromUtf8(ImWchar* buf, int buf_size, const char* in_text, const char* in_text_end, const char** in_text_remaining)
1644 {
1645 ImWchar* buf_out = buf;
1646 ImWchar* buf_end = buf + buf_size;
1647 while (buf_out < buf_end - 1 && (!in_text_end || in_text < in_text_end) && *in_text)
1648 {
1649 unsigned int c;
1650 in_text += ImTextCharFromUtf8(&c, in_text, in_text_end);
1651 if (c == 0)
1652 break;
1653 *buf_out++ = (ImWchar)c;
1654 }
1655 *buf_out = 0;
1656 if (in_text_remaining)
1657 *in_text_remaining = in_text;
1658 return (int)(buf_out - buf);
1659 }
1660
ImTextCountCharsFromUtf8(const char * in_text,const char * in_text_end)1661 int ImTextCountCharsFromUtf8(const char* in_text, const char* in_text_end)
1662 {
1663 int char_count = 0;
1664 while ((!in_text_end || in_text < in_text_end) && *in_text)
1665 {
1666 unsigned int c;
1667 in_text += ImTextCharFromUtf8(&c, in_text, in_text_end);
1668 if (c == 0)
1669 break;
1670 char_count++;
1671 }
1672 return char_count;
1673 }
1674
1675 // Based on stb_to_utf8() from github.com/nothings/stb/
ImTextCharToUtf8(char * buf,int buf_size,unsigned int c)1676 static inline int ImTextCharToUtf8(char* buf, int buf_size, unsigned int c)
1677 {
1678 if (c < 0x80)
1679 {
1680 buf[0] = (char)c;
1681 return 1;
1682 }
1683 if (c < 0x800)
1684 {
1685 if (buf_size < 2) return 0;
1686 buf[0] = (char)(0xc0 + (c >> 6));
1687 buf[1] = (char)(0x80 + (c & 0x3f));
1688 return 2;
1689 }
1690 if (c < 0x10000)
1691 {
1692 if (buf_size < 3) return 0;
1693 buf[0] = (char)(0xe0 + (c >> 12));
1694 buf[1] = (char)(0x80 + ((c >> 6) & 0x3f));
1695 buf[2] = (char)(0x80 + ((c ) & 0x3f));
1696 return 3;
1697 }
1698 if (c <= 0x10FFFF)
1699 {
1700 if (buf_size < 4) return 0;
1701 buf[0] = (char)(0xf0 + (c >> 18));
1702 buf[1] = (char)(0x80 + ((c >> 12) & 0x3f));
1703 buf[2] = (char)(0x80 + ((c >> 6) & 0x3f));
1704 buf[3] = (char)(0x80 + ((c ) & 0x3f));
1705 return 4;
1706 }
1707 // Invalid code point, the max unicode is 0x10FFFF
1708 return 0;
1709 }
1710
1711 // Not optimal but we very rarely use this function.
ImTextCountUtf8BytesFromChar(const char * in_text,const char * in_text_end)1712 int ImTextCountUtf8BytesFromChar(const char* in_text, const char* in_text_end)
1713 {
1714 unsigned int unused = 0;
1715 return ImTextCharFromUtf8(&unused, in_text, in_text_end);
1716 }
1717
ImTextCountUtf8BytesFromChar(unsigned int c)1718 static inline int ImTextCountUtf8BytesFromChar(unsigned int c)
1719 {
1720 if (c < 0x80) return 1;
1721 if (c < 0x800) return 2;
1722 if (c < 0x10000) return 3;
1723 if (c <= 0x10FFFF) return 4;
1724 return 3;
1725 }
1726
ImTextStrToUtf8(char * buf,int buf_size,const ImWchar * in_text,const ImWchar * in_text_end)1727 int ImTextStrToUtf8(char* buf, int buf_size, const ImWchar* in_text, const ImWchar* in_text_end)
1728 {
1729 char* buf_out = buf;
1730 const char* buf_end = buf + buf_size;
1731 while (buf_out < buf_end - 1 && (!in_text_end || in_text < in_text_end) && *in_text)
1732 {
1733 unsigned int c = (unsigned int)(*in_text++);
1734 if (c < 0x80)
1735 *buf_out++ = (char)c;
1736 else
1737 buf_out += ImTextCharToUtf8(buf_out, (int)(buf_end - buf_out - 1), c);
1738 }
1739 *buf_out = 0;
1740 return (int)(buf_out - buf);
1741 }
1742
ImTextCountUtf8BytesFromStr(const ImWchar * in_text,const ImWchar * in_text_end)1743 int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_end)
1744 {
1745 int bytes_count = 0;
1746 while ((!in_text_end || in_text < in_text_end) && *in_text)
1747 {
1748 unsigned int c = (unsigned int)(*in_text++);
1749 if (c < 0x80)
1750 bytes_count++;
1751 else
1752 bytes_count += ImTextCountUtf8BytesFromChar(c);
1753 }
1754 return bytes_count;
1755 }
1756
1757 //-----------------------------------------------------------------------------
1758 // [SECTION] MISC HELPERS/UTILITIES (Color functions)
1759 // Note: The Convert functions are early design which are not consistent with other API.
1760 //-----------------------------------------------------------------------------
1761
ImAlphaBlendColors(ImU32 col_a,ImU32 col_b)1762 IMGUI_API ImU32 ImAlphaBlendColors(ImU32 col_a, ImU32 col_b)
1763 {
1764 float t = ((col_b >> IM_COL32_A_SHIFT) & 0xFF) / 255.f;
1765 int r = ImLerp((int)(col_a >> IM_COL32_R_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_R_SHIFT) & 0xFF, t);
1766 int g = ImLerp((int)(col_a >> IM_COL32_G_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_G_SHIFT) & 0xFF, t);
1767 int b = ImLerp((int)(col_a >> IM_COL32_B_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_B_SHIFT) & 0xFF, t);
1768 return IM_COL32(r, g, b, 0xFF);
1769 }
1770
ColorConvertU32ToFloat4(ImU32 in)1771 ImVec4 ImGui::ColorConvertU32ToFloat4(ImU32 in)
1772 {
1773 float s = 1.0f / 255.0f;
1774 return ImVec4(
1775 ((in >> IM_COL32_R_SHIFT) & 0xFF) * s,
1776 ((in >> IM_COL32_G_SHIFT) & 0xFF) * s,
1777 ((in >> IM_COL32_B_SHIFT) & 0xFF) * s,
1778 ((in >> IM_COL32_A_SHIFT) & 0xFF) * s);
1779 }
1780
ColorConvertFloat4ToU32(const ImVec4 & in)1781 ImU32 ImGui::ColorConvertFloat4ToU32(const ImVec4& in)
1782 {
1783 ImU32 out;
1784 out = ((ImU32)IM_F32_TO_INT8_SAT(in.x)) << IM_COL32_R_SHIFT;
1785 out |= ((ImU32)IM_F32_TO_INT8_SAT(in.y)) << IM_COL32_G_SHIFT;
1786 out |= ((ImU32)IM_F32_TO_INT8_SAT(in.z)) << IM_COL32_B_SHIFT;
1787 out |= ((ImU32)IM_F32_TO_INT8_SAT(in.w)) << IM_COL32_A_SHIFT;
1788 return out;
1789 }
1790
1791 // Convert rgb floats ([0-1],[0-1],[0-1]) to hsv floats ([0-1],[0-1],[0-1]), from Foley & van Dam p592
1792 // Optimized http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv
ColorConvertRGBtoHSV(float r,float g,float b,float & out_h,float & out_s,float & out_v)1793 void ImGui::ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& out_s, float& out_v)
1794 {
1795 float K = 0.f;
1796 if (g < b)
1797 {
1798 ImSwap(g, b);
1799 K = -1.f;
1800 }
1801 if (r < g)
1802 {
1803 ImSwap(r, g);
1804 K = -2.f / 6.f - K;
1805 }
1806
1807 const float chroma = r - (g < b ? g : b);
1808 out_h = ImFabs(K + (g - b) / (6.f * chroma + 1e-20f));
1809 out_s = chroma / (r + 1e-20f);
1810 out_v = r;
1811 }
1812
1813 // Convert hsv floats ([0-1],[0-1],[0-1]) to rgb floats ([0-1],[0-1],[0-1]), from Foley & van Dam p593
1814 // also http://en.wikipedia.org/wiki/HSL_and_HSV
ColorConvertHSVtoRGB(float h,float s,float v,float & out_r,float & out_g,float & out_b)1815 void ImGui::ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& out_g, float& out_b)
1816 {
1817 if (s == 0.0f)
1818 {
1819 // gray
1820 out_r = out_g = out_b = v;
1821 return;
1822 }
1823
1824 h = ImFmod(h, 1.0f) / (60.0f / 360.0f);
1825 int i = (int)h;
1826 float f = h - (float)i;
1827 float p = v * (1.0f - s);
1828 float q = v * (1.0f - s * f);
1829 float t = v * (1.0f - s * (1.0f - f));
1830
1831 switch (i)
1832 {
1833 case 0: out_r = v; out_g = t; out_b = p; break;
1834 case 1: out_r = q; out_g = v; out_b = p; break;
1835 case 2: out_r = p; out_g = v; out_b = t; break;
1836 case 3: out_r = p; out_g = q; out_b = v; break;
1837 case 4: out_r = t; out_g = p; out_b = v; break;
1838 case 5: default: out_r = v; out_g = p; out_b = q; break;
1839 }
1840 }
1841
1842 //-----------------------------------------------------------------------------
1843 // [SECTION] ImGuiStorage
1844 // Helper: Key->value storage
1845 //-----------------------------------------------------------------------------
1846
1847 // std::lower_bound but without the bullshit
LowerBound(ImVector<ImGuiStorage::ImGuiStoragePair> & data,ImGuiID key)1848 static ImGuiStorage::ImGuiStoragePair* LowerBound(ImVector<ImGuiStorage::ImGuiStoragePair>& data, ImGuiID key)
1849 {
1850 ImGuiStorage::ImGuiStoragePair* first = data.Data;
1851 ImGuiStorage::ImGuiStoragePair* last = data.Data + data.Size;
1852 size_t count = (size_t)(last - first);
1853 while (count > 0)
1854 {
1855 size_t count2 = count >> 1;
1856 ImGuiStorage::ImGuiStoragePair* mid = first + count2;
1857 if (mid->key < key)
1858 {
1859 first = ++mid;
1860 count -= count2 + 1;
1861 }
1862 else
1863 {
1864 count = count2;
1865 }
1866 }
1867 return first;
1868 }
1869
1870 // For quicker full rebuild of a storage (instead of an incremental one), you may add all your contents and then sort once.
BuildSortByKey()1871 void ImGuiStorage::BuildSortByKey()
1872 {
1873 struct StaticFunc
1874 {
1875 static int IMGUI_CDECL PairCompareByID(const void* lhs, const void* rhs)
1876 {
1877 // We can't just do a subtraction because qsort uses signed integers and subtracting our ID doesn't play well with that.
1878 if (((const ImGuiStoragePair*)lhs)->key > ((const ImGuiStoragePair*)rhs)->key) return +1;
1879 if (((const ImGuiStoragePair*)lhs)->key < ((const ImGuiStoragePair*)rhs)->key) return -1;
1880 return 0;
1881 }
1882 };
1883 if (Data.Size > 1)
1884 ImQsort(Data.Data, (size_t)Data.Size, sizeof(ImGuiStoragePair), StaticFunc::PairCompareByID);
1885 }
1886
GetInt(ImGuiID key,int default_val) const1887 int ImGuiStorage::GetInt(ImGuiID key, int default_val) const
1888 {
1889 ImGuiStoragePair* it = LowerBound(const_cast<ImVector<ImGuiStoragePair>&>(Data), key);
1890 if (it == Data.end() || it->key != key)
1891 return default_val;
1892 return it->val_i;
1893 }
1894
GetBool(ImGuiID key,bool default_val) const1895 bool ImGuiStorage::GetBool(ImGuiID key, bool default_val) const
1896 {
1897 return GetInt(key, default_val ? 1 : 0) != 0;
1898 }
1899
GetFloat(ImGuiID key,float default_val) const1900 float ImGuiStorage::GetFloat(ImGuiID key, float default_val) const
1901 {
1902 ImGuiStoragePair* it = LowerBound(const_cast<ImVector<ImGuiStoragePair>&>(Data), key);
1903 if (it == Data.end() || it->key != key)
1904 return default_val;
1905 return it->val_f;
1906 }
1907
GetVoidPtr(ImGuiID key) const1908 void* ImGuiStorage::GetVoidPtr(ImGuiID key) const
1909 {
1910 ImGuiStoragePair* it = LowerBound(const_cast<ImVector<ImGuiStoragePair>&>(Data), key);
1911 if (it == Data.end() || it->key != key)
1912 return NULL;
1913 return it->val_p;
1914 }
1915
1916 // 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.
GetIntRef(ImGuiID key,int default_val)1917 int* ImGuiStorage::GetIntRef(ImGuiID key, int default_val)
1918 {
1919 ImGuiStoragePair* it = LowerBound(Data, key);
1920 if (it == Data.end() || it->key != key)
1921 it = Data.insert(it, ImGuiStoragePair(key, default_val));
1922 return &it->val_i;
1923 }
1924
GetBoolRef(ImGuiID key,bool default_val)1925 bool* ImGuiStorage::GetBoolRef(ImGuiID key, bool default_val)
1926 {
1927 return (bool*)GetIntRef(key, default_val ? 1 : 0);
1928 }
1929
GetFloatRef(ImGuiID key,float default_val)1930 float* ImGuiStorage::GetFloatRef(ImGuiID key, float default_val)
1931 {
1932 ImGuiStoragePair* it = LowerBound(Data, key);
1933 if (it == Data.end() || it->key != key)
1934 it = Data.insert(it, ImGuiStoragePair(key, default_val));
1935 return &it->val_f;
1936 }
1937
GetVoidPtrRef(ImGuiID key,void * default_val)1938 void** ImGuiStorage::GetVoidPtrRef(ImGuiID key, void* default_val)
1939 {
1940 ImGuiStoragePair* it = LowerBound(Data, key);
1941 if (it == Data.end() || it->key != key)
1942 it = Data.insert(it, ImGuiStoragePair(key, default_val));
1943 return &it->val_p;
1944 }
1945
1946 // 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)
SetInt(ImGuiID key,int val)1947 void ImGuiStorage::SetInt(ImGuiID key, int val)
1948 {
1949 ImGuiStoragePair* it = LowerBound(Data, key);
1950 if (it == Data.end() || it->key != key)
1951 {
1952 Data.insert(it, ImGuiStoragePair(key, val));
1953 return;
1954 }
1955 it->val_i = val;
1956 }
1957
SetBool(ImGuiID key,bool val)1958 void ImGuiStorage::SetBool(ImGuiID key, bool val)
1959 {
1960 SetInt(key, val ? 1 : 0);
1961 }
1962
SetFloat(ImGuiID key,float val)1963 void ImGuiStorage::SetFloat(ImGuiID key, float val)
1964 {
1965 ImGuiStoragePair* it = LowerBound(Data, key);
1966 if (it == Data.end() || it->key != key)
1967 {
1968 Data.insert(it, ImGuiStoragePair(key, val));
1969 return;
1970 }
1971 it->val_f = val;
1972 }
1973
SetVoidPtr(ImGuiID key,void * val)1974 void ImGuiStorage::SetVoidPtr(ImGuiID key, void* val)
1975 {
1976 ImGuiStoragePair* it = LowerBound(Data, key);
1977 if (it == Data.end() || it->key != key)
1978 {
1979 Data.insert(it, ImGuiStoragePair(key, val));
1980 return;
1981 }
1982 it->val_p = val;
1983 }
1984
SetAllInt(int v)1985 void ImGuiStorage::SetAllInt(int v)
1986 {
1987 for (int i = 0; i < Data.Size; i++)
1988 Data[i].val_i = v;
1989 }
1990
1991 //-----------------------------------------------------------------------------
1992 // [SECTION] ImGuiTextFilter
1993 //-----------------------------------------------------------------------------
1994
1995 // Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]"
ImGuiTextFilter(const char * default_filter)1996 ImGuiTextFilter::ImGuiTextFilter(const char* default_filter)
1997 {
1998 if (default_filter)
1999 {
2000 ImStrncpy(InputBuf, default_filter, IM_ARRAYSIZE(InputBuf));
2001 Build();
2002 }
2003 else
2004 {
2005 InputBuf[0] = 0;
2006 CountGrep = 0;
2007 }
2008 }
2009
Draw(const char * label,float width)2010 bool ImGuiTextFilter::Draw(const char* label, float width)
2011 {
2012 if (width != 0.0f)
2013 ImGui::SetNextItemWidth(width);
2014 bool value_changed = ImGui::InputText(label, InputBuf, IM_ARRAYSIZE(InputBuf));
2015 if (value_changed)
2016 Build();
2017 return value_changed;
2018 }
2019
split(char separator,ImVector<ImGuiTextRange> * out) const2020 void ImGuiTextFilter::ImGuiTextRange::split(char separator, ImVector<ImGuiTextRange>* out) const
2021 {
2022 out->resize(0);
2023 const char* wb = b;
2024 const char* we = wb;
2025 while (we < e)
2026 {
2027 if (*we == separator)
2028 {
2029 out->push_back(ImGuiTextRange(wb, we));
2030 wb = we + 1;
2031 }
2032 we++;
2033 }
2034 if (wb != we)
2035 out->push_back(ImGuiTextRange(wb, we));
2036 }
2037
Build()2038 void ImGuiTextFilter::Build()
2039 {
2040 Filters.resize(0);
2041 ImGuiTextRange input_range(InputBuf, InputBuf + strlen(InputBuf));
2042 input_range.split(',', &Filters);
2043
2044 CountGrep = 0;
2045 for (int i = 0; i != Filters.Size; i++)
2046 {
2047 ImGuiTextRange& f = Filters[i];
2048 while (f.b < f.e && ImCharIsBlankA(f.b[0]))
2049 f.b++;
2050 while (f.e > f.b && ImCharIsBlankA(f.e[-1]))
2051 f.e--;
2052 if (f.empty())
2053 continue;
2054 if (Filters[i].b[0] != '-')
2055 CountGrep += 1;
2056 }
2057 }
2058
PassFilter(const char * text,const char * text_end) const2059 bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const
2060 {
2061 if (Filters.empty())
2062 return true;
2063
2064 if (text == NULL)
2065 text = "";
2066
2067 for (int i = 0; i != Filters.Size; i++)
2068 {
2069 const ImGuiTextRange& f = Filters[i];
2070 if (f.empty())
2071 continue;
2072 if (f.b[0] == '-')
2073 {
2074 // Subtract
2075 if (ImStristr(text, text_end, f.b + 1, f.e) != NULL)
2076 return false;
2077 }
2078 else
2079 {
2080 // Grep
2081 if (ImStristr(text, text_end, f.b, f.e) != NULL)
2082 return true;
2083 }
2084 }
2085
2086 // Implicit * grep
2087 if (CountGrep == 0)
2088 return true;
2089
2090 return false;
2091 }
2092
2093 //-----------------------------------------------------------------------------
2094 // [SECTION] ImGuiTextBuffer
2095 //-----------------------------------------------------------------------------
2096
2097 // On some platform vsnprintf() takes va_list by reference and modifies it.
2098 // va_copy is the 'correct' way to copy a va_list but Visual Studio prior to 2013 doesn't have it.
2099 #ifndef va_copy
2100 #if defined(__GNUC__) || defined(__clang__)
2101 #define va_copy(dest, src) __builtin_va_copy(dest, src)
2102 #else
2103 #define va_copy(dest, src) (dest = src)
2104 #endif
2105 #endif
2106
2107 char ImGuiTextBuffer::EmptyString[1] = { 0 };
2108
append(const char * str,const char * str_end)2109 void ImGuiTextBuffer::append(const char* str, const char* str_end)
2110 {
2111 int len = str_end ? (int)(str_end - str) : (int)strlen(str);
2112
2113 // Add zero-terminator the first time
2114 const int write_off = (Buf.Size != 0) ? Buf.Size : 1;
2115 const int needed_sz = write_off + len;
2116 if (write_off + len >= Buf.Capacity)
2117 {
2118 int new_capacity = Buf.Capacity * 2;
2119 Buf.reserve(needed_sz > new_capacity ? needed_sz : new_capacity);
2120 }
2121
2122 Buf.resize(needed_sz);
2123 memcpy(&Buf[write_off - 1], str, (size_t)len);
2124 Buf[write_off - 1 + len] = 0;
2125 }
2126
appendf(const char * fmt,...)2127 void ImGuiTextBuffer::appendf(const char* fmt, ...)
2128 {
2129 va_list args;
2130 va_start(args, fmt);
2131 appendfv(fmt, args);
2132 va_end(args);
2133 }
2134
2135 // Helper: Text buffer for logging/accumulating text
appendfv(const char * fmt,va_list args)2136 void ImGuiTextBuffer::appendfv(const char* fmt, va_list args)
2137 {
2138 va_list args_copy;
2139 va_copy(args_copy, args);
2140
2141 int len = ImFormatStringV(NULL, 0, fmt, args); // FIXME-OPT: could do a first pass write attempt, likely successful on first pass.
2142 if (len <= 0)
2143 {
2144 va_end(args_copy);
2145 return;
2146 }
2147
2148 // Add zero-terminator the first time
2149 const int write_off = (Buf.Size != 0) ? Buf.Size : 1;
2150 const int needed_sz = write_off + len;
2151 if (write_off + len >= Buf.Capacity)
2152 {
2153 int new_capacity = Buf.Capacity * 2;
2154 Buf.reserve(needed_sz > new_capacity ? needed_sz : new_capacity);
2155 }
2156
2157 Buf.resize(needed_sz);
2158 ImFormatStringV(&Buf[write_off - 1], (size_t)len + 1, fmt, args_copy);
2159 va_end(args_copy);
2160 }
2161
2162 //-----------------------------------------------------------------------------
2163 // [SECTION] ImGuiListClipper
2164 // This is currently not as flexible/powerful as it should be and really confusing/spaghetti, mostly because we changed
2165 // the API mid-way through development and support two ways to using the clipper, needs some rework (see TODO)
2166 //-----------------------------------------------------------------------------
2167
2168 // Helper to calculate coarse clipping of large list of evenly sized items.
2169 // NB: Prefer using the ImGuiListClipper higher-level helper if you can! Read comments and instructions there on how those use this sort of pattern.
2170 // NB: 'items_count' is only used to clamp the result, if you don't know your count you can use INT_MAX
CalcListClipping(int items_count,float items_height,int * out_items_display_start,int * out_items_display_end)2171 void ImGui::CalcListClipping(int items_count, float items_height, int* out_items_display_start, int* out_items_display_end)
2172 {
2173 ImGuiContext& g = *GImGui;
2174 ImGuiWindow* window = g.CurrentWindow;
2175 if (g.LogEnabled)
2176 {
2177 // If logging is active, do not perform any clipping
2178 *out_items_display_start = 0;
2179 *out_items_display_end = items_count;
2180 return;
2181 }
2182 if (window->SkipItems)
2183 {
2184 *out_items_display_start = *out_items_display_end = 0;
2185 return;
2186 }
2187
2188 // We create the union of the ClipRect and the NavScoringRect which at worst should be 1 page away from ClipRect
2189 ImRect unclipped_rect = window->ClipRect;
2190 if (g.NavMoveRequest)
2191 unclipped_rect.Add(g.NavScoringRect);
2192 if (g.NavJustMovedToId && window->NavLastIds[0] == g.NavJustMovedToId)
2193 unclipped_rect.Add(ImRect(window->Pos + window->NavRectRel[0].Min, window->Pos + window->NavRectRel[0].Max));
2194
2195 const ImVec2 pos = window->DC.CursorPos;
2196 int start = (int)((unclipped_rect.Min.y - pos.y) / items_height);
2197 int end = (int)((unclipped_rect.Max.y - pos.y) / items_height);
2198
2199 // When performing a navigation request, ensure we have one item extra in the direction we are moving to
2200 if (g.NavMoveRequest && g.NavMoveClipDir == ImGuiDir_Up)
2201 start--;
2202 if (g.NavMoveRequest && g.NavMoveClipDir == ImGuiDir_Down)
2203 end++;
2204
2205 start = ImClamp(start, 0, items_count);
2206 end = ImClamp(end + 1, start, items_count);
2207 *out_items_display_start = start;
2208 *out_items_display_end = end;
2209 }
2210
SetCursorPosYAndSetupForPrevLine(float pos_y,float line_height)2211 static void SetCursorPosYAndSetupForPrevLine(float pos_y, float line_height)
2212 {
2213 // Set cursor position and a few other things so that SetScrollHereY() and Columns() can work when seeking cursor.
2214 // FIXME: It is problematic that we have to do that here, because custom/equivalent end-user code would stumble on the same issue.
2215 // The clipper should probably have a 4th step to display the last item in a regular manner.
2216 ImGuiContext& g = *GImGui;
2217 ImGuiWindow* window = g.CurrentWindow;
2218 window->DC.CursorPos.y = pos_y;
2219 window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, pos_y);
2220 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.
2221 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.
2222 if (ImGuiColumns* columns = window->DC.CurrentColumns)
2223 columns->LineMinY = window->DC.CursorPos.y; // Setting this so that cell Y position are set properly
2224 }
2225
2226 // Use case A: Begin() called from constructor with items_height<0, then called again from Sync() in StepNo 1
2227 // Use case B: Begin() called from constructor with items_height>0
2228 // FIXME-LEGACY: Ideally we should remove the Begin/End functions but they are part of the legacy API we still support. This is why some of the code in Step() calling Begin() and reassign some fields, spaghetti style.
Begin(int count,float items_height)2229 void ImGuiListClipper::Begin(int count, float items_height)
2230 {
2231 ImGuiContext& g = *GImGui;
2232 ImGuiWindow* window = g.CurrentWindow;
2233
2234 StartPosY = window->DC.CursorPos.y;
2235 ItemsHeight = items_height;
2236 ItemsCount = count;
2237 StepNo = 0;
2238 DisplayEnd = DisplayStart = -1;
2239 if (ItemsHeight > 0.0f)
2240 {
2241 ImGui::CalcListClipping(ItemsCount, ItemsHeight, &DisplayStart, &DisplayEnd); // calculate how many to clip/display
2242 if (DisplayStart > 0)
2243 SetCursorPosYAndSetupForPrevLine(StartPosY + DisplayStart * ItemsHeight, ItemsHeight); // advance cursor
2244 StepNo = 2;
2245 }
2246 }
2247
End()2248 void ImGuiListClipper::End()
2249 {
2250 if (ItemsCount < 0)
2251 return;
2252 // In theory here we should assert that ImGui::GetCursorPosY() == StartPosY + DisplayEnd * ItemsHeight, but it feels saner to just seek at the end and not assert/crash the user.
2253 if (ItemsCount < INT_MAX)
2254 SetCursorPosYAndSetupForPrevLine(StartPosY + ItemsCount * ItemsHeight, ItemsHeight); // advance cursor
2255 ItemsCount = -1;
2256 StepNo = 3;
2257 }
2258
Step()2259 bool ImGuiListClipper::Step()
2260 {
2261 ImGuiContext& g = *GImGui;
2262 ImGuiWindow* window = g.CurrentWindow;
2263
2264 if (ItemsCount == 0 || window->SkipItems)
2265 {
2266 ItemsCount = -1;
2267 return false;
2268 }
2269 if (StepNo == 0) // Step 0: the clipper let you process the first element, regardless of it being visible or not, so we can measure the element height.
2270 {
2271 DisplayStart = 0;
2272 DisplayEnd = 1;
2273 StartPosY = window->DC.CursorPos.y;
2274 StepNo = 1;
2275 return true;
2276 }
2277 if (StepNo == 1) // Step 1: the clipper infer height from first element, calculate the actual range of elements to display, and position the cursor before the first element.
2278 {
2279 if (ItemsCount == 1) { ItemsCount = -1; return false; }
2280 float items_height = window->DC.CursorPos.y - StartPosY;
2281 IM_ASSERT(items_height > 0.0f); // If this triggers, it means Item 0 hasn't moved the cursor vertically
2282 Begin(ItemsCount - 1, items_height);
2283 DisplayStart++;
2284 DisplayEnd++;
2285 StepNo = 3;
2286 return true;
2287 }
2288 if (StepNo == 2) // Step 2: empty step only required if an explicit items_height was passed to constructor or Begin() and user still call Step(). Does nothing and switch to Step 3.
2289 {
2290 IM_ASSERT(DisplayStart >= 0 && DisplayEnd >= 0);
2291 StepNo = 3;
2292 return true;
2293 }
2294 if (StepNo == 3) // Step 3: the clipper validate that we have reached the expected Y position (corresponding to element DisplayEnd), advance the cursor to the end of the list and then returns 'false' to end the loop.
2295 End();
2296 return false;
2297 }
2298
2299 //-----------------------------------------------------------------------------
2300 // [SECTION] STYLING
2301 //-----------------------------------------------------------------------------
2302
GetStyle()2303 ImGuiStyle& ImGui::GetStyle()
2304 {
2305 IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext() ?");
2306 return GImGui->Style;
2307 }
2308
GetColorU32(ImGuiCol idx,float alpha_mul)2309 ImU32 ImGui::GetColorU32(ImGuiCol idx, float alpha_mul)
2310 {
2311 ImGuiStyle& style = GImGui->Style;
2312 ImVec4 c = style.Colors[idx];
2313 c.w *= style.Alpha * alpha_mul;
2314 return ColorConvertFloat4ToU32(c);
2315 }
2316
GetColorU32(const ImVec4 & col)2317 ImU32 ImGui::GetColorU32(const ImVec4& col)
2318 {
2319 ImGuiStyle& style = GImGui->Style;
2320 ImVec4 c = col;
2321 c.w *= style.Alpha;
2322 return ColorConvertFloat4ToU32(c);
2323 }
2324
GetStyleColorVec4(ImGuiCol idx)2325 const ImVec4& ImGui::GetStyleColorVec4(ImGuiCol idx)
2326 {
2327 ImGuiStyle& style = GImGui->Style;
2328 return style.Colors[idx];
2329 }
2330
GetColorU32(ImU32 col)2331 ImU32 ImGui::GetColorU32(ImU32 col)
2332 {
2333 ImGuiStyle& style = GImGui->Style;
2334 if (style.Alpha >= 1.0f)
2335 return col;
2336 ImU32 a = (col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT;
2337 a = (ImU32)(a * style.Alpha); // We don't need to clamp 0..255 because Style.Alpha is in 0..1 range.
2338 return (col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT);
2339 }
2340
2341 // 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
PushStyleColor(ImGuiCol idx,ImU32 col)2342 void ImGui::PushStyleColor(ImGuiCol idx, ImU32 col)
2343 {
2344 ImGuiContext& g = *GImGui;
2345 ImGuiColorMod backup;
2346 backup.Col = idx;
2347 backup.BackupValue = g.Style.Colors[idx];
2348 g.ColorModifiers.push_back(backup);
2349 g.Style.Colors[idx] = ColorConvertU32ToFloat4(col);
2350 }
2351
PushStyleColor(ImGuiCol idx,const ImVec4 & col)2352 void ImGui::PushStyleColor(ImGuiCol idx, const ImVec4& col)
2353 {
2354 ImGuiContext& g = *GImGui;
2355 ImGuiColorMod backup;
2356 backup.Col = idx;
2357 backup.BackupValue = g.Style.Colors[idx];
2358 g.ColorModifiers.push_back(backup);
2359 g.Style.Colors[idx] = col;
2360 }
2361
PopStyleColor(int count)2362 void ImGui::PopStyleColor(int count)
2363 {
2364 ImGuiContext& g = *GImGui;
2365 while (count > 0)
2366 {
2367 ImGuiColorMod& backup = g.ColorModifiers.back();
2368 g.Style.Colors[backup.Col] = backup.BackupValue;
2369 g.ColorModifiers.pop_back();
2370 count--;
2371 }
2372 }
2373
2374 struct ImGuiStyleVarInfo
2375 {
2376 ImGuiDataType Type;
2377 ImU32 Count;
2378 ImU32 Offset;
GetVarPtrImGuiStyleVarInfo2379 void* GetVarPtr(ImGuiStyle* style) const { return (void*)((unsigned char*)style + Offset); }
2380 };
2381
2382 static const ImGuiStyleVarInfo GStyleVarInfo[] =
2383 {
2384 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, Alpha) }, // ImGuiStyleVar_Alpha
2385 { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowPadding) }, // ImGuiStyleVar_WindowPadding
2386 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowRounding) }, // ImGuiStyleVar_WindowRounding
2387 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowBorderSize) }, // ImGuiStyleVar_WindowBorderSize
2388 { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowMinSize) }, // ImGuiStyleVar_WindowMinSize
2389 { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowTitleAlign) }, // ImGuiStyleVar_WindowTitleAlign
2390 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ChildRounding) }, // ImGuiStyleVar_ChildRounding
2391 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ChildBorderSize) }, // ImGuiStyleVar_ChildBorderSize
2392 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, PopupRounding) }, // ImGuiStyleVar_PopupRounding
2393 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, PopupBorderSize) }, // ImGuiStyleVar_PopupBorderSize
2394 { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, FramePadding) }, // ImGuiStyleVar_FramePadding
2395 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, FrameRounding) }, // ImGuiStyleVar_FrameRounding
2396 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, FrameBorderSize) }, // ImGuiStyleVar_FrameBorderSize
2397 { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemSpacing) }, // ImGuiStyleVar_ItemSpacing
2398 { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemInnerSpacing) }, // ImGuiStyleVar_ItemInnerSpacing
2399 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, IndentSpacing) }, // ImGuiStyleVar_IndentSpacing
2400 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ScrollbarSize) }, // ImGuiStyleVar_ScrollbarSize
2401 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ScrollbarRounding) }, // ImGuiStyleVar_ScrollbarRounding
2402 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, GrabMinSize) }, // ImGuiStyleVar_GrabMinSize
2403 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, GrabRounding) }, // ImGuiStyleVar_GrabRounding
2404 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, TabRounding) }, // ImGuiStyleVar_TabRounding
2405 { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, ButtonTextAlign) }, // ImGuiStyleVar_ButtonTextAlign
2406 { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, SelectableTextAlign) }, // ImGuiStyleVar_SelectableTextAlign
2407 };
2408
GetStyleVarInfo(ImGuiStyleVar idx)2409 static const ImGuiStyleVarInfo* GetStyleVarInfo(ImGuiStyleVar idx)
2410 {
2411 IM_ASSERT(idx >= 0 && idx < ImGuiStyleVar_COUNT);
2412 IM_ASSERT(IM_ARRAYSIZE(GStyleVarInfo) == ImGuiStyleVar_COUNT);
2413 return &GStyleVarInfo[idx];
2414 }
2415
PushStyleVar(ImGuiStyleVar idx,float val)2416 void ImGui::PushStyleVar(ImGuiStyleVar idx, float val)
2417 {
2418 const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx);
2419 if (var_info->Type == ImGuiDataType_Float && var_info->Count == 1)
2420 {
2421 ImGuiContext& g = *GImGui;
2422 float* pvar = (float*)var_info->GetVarPtr(&g.Style);
2423 g.StyleModifiers.push_back(ImGuiStyleMod(idx, *pvar));
2424 *pvar = val;
2425 return;
2426 }
2427 IM_ASSERT(0 && "Called PushStyleVar() float variant but variable is not a float!");
2428 }
2429
PushStyleVar(ImGuiStyleVar idx,const ImVec2 & val)2430 void ImGui::PushStyleVar(ImGuiStyleVar idx, const ImVec2& val)
2431 {
2432 const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx);
2433 if (var_info->Type == ImGuiDataType_Float && var_info->Count == 2)
2434 {
2435 ImGuiContext& g = *GImGui;
2436 ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(&g.Style);
2437 g.StyleModifiers.push_back(ImGuiStyleMod(idx, *pvar));
2438 *pvar = val;
2439 return;
2440 }
2441 IM_ASSERT(0 && "Called PushStyleVar() ImVec2 variant but variable is not a ImVec2!");
2442 }
2443
PopStyleVar(int count)2444 void ImGui::PopStyleVar(int count)
2445 {
2446 ImGuiContext& g = *GImGui;
2447 while (count > 0)
2448 {
2449 // We avoid a generic memcpy(data, &backup.Backup.., GDataTypeSize[info->Type] * info->Count), the overhead in Debug is not worth it.
2450 ImGuiStyleMod& backup = g.StyleModifiers.back();
2451 const ImGuiStyleVarInfo* info = GetStyleVarInfo(backup.VarIdx);
2452 void* data = info->GetVarPtr(&g.Style);
2453 if (info->Type == ImGuiDataType_Float && info->Count == 1) { ((float*)data)[0] = backup.BackupFloat[0]; }
2454 else if (info->Type == ImGuiDataType_Float && info->Count == 2) { ((float*)data)[0] = backup.BackupFloat[0]; ((float*)data)[1] = backup.BackupFloat[1]; }
2455 g.StyleModifiers.pop_back();
2456 count--;
2457 }
2458 }
2459
GetStyleColorName(ImGuiCol idx)2460 const char* ImGui::GetStyleColorName(ImGuiCol idx)
2461 {
2462 // Create switch-case from enum with regexp: ImGuiCol_{.*}, --> case ImGuiCol_\1: return "\1";
2463 switch (idx)
2464 {
2465 case ImGuiCol_Text: return "Text";
2466 case ImGuiCol_TextDisabled: return "TextDisabled";
2467 case ImGuiCol_WindowBg: return "WindowBg";
2468 case ImGuiCol_ChildBg: return "ChildBg";
2469 case ImGuiCol_PopupBg: return "PopupBg";
2470 case ImGuiCol_Border: return "Border";
2471 case ImGuiCol_BorderShadow: return "BorderShadow";
2472 case ImGuiCol_FrameBg: return "FrameBg";
2473 case ImGuiCol_FrameBgHovered: return "FrameBgHovered";
2474 case ImGuiCol_FrameBgActive: return "FrameBgActive";
2475 case ImGuiCol_TitleBg: return "TitleBg";
2476 case ImGuiCol_TitleBgActive: return "TitleBgActive";
2477 case ImGuiCol_TitleBgCollapsed: return "TitleBgCollapsed";
2478 case ImGuiCol_MenuBarBg: return "MenuBarBg";
2479 case ImGuiCol_ScrollbarBg: return "ScrollbarBg";
2480 case ImGuiCol_ScrollbarGrab: return "ScrollbarGrab";
2481 case ImGuiCol_ScrollbarGrabHovered: return "ScrollbarGrabHovered";
2482 case ImGuiCol_ScrollbarGrabActive: return "ScrollbarGrabActive";
2483 case ImGuiCol_CheckMark: return "CheckMark";
2484 case ImGuiCol_SliderGrab: return "SliderGrab";
2485 case ImGuiCol_SliderGrabActive: return "SliderGrabActive";
2486 case ImGuiCol_Button: return "Button";
2487 case ImGuiCol_ButtonHovered: return "ButtonHovered";
2488 case ImGuiCol_ButtonActive: return "ButtonActive";
2489 case ImGuiCol_Header: return "Header";
2490 case ImGuiCol_HeaderHovered: return "HeaderHovered";
2491 case ImGuiCol_HeaderActive: return "HeaderActive";
2492 case ImGuiCol_Separator: return "Separator";
2493 case ImGuiCol_SeparatorHovered: return "SeparatorHovered";
2494 case ImGuiCol_SeparatorActive: return "SeparatorActive";
2495 case ImGuiCol_ResizeGrip: return "ResizeGrip";
2496 case ImGuiCol_ResizeGripHovered: return "ResizeGripHovered";
2497 case ImGuiCol_ResizeGripActive: return "ResizeGripActive";
2498 case ImGuiCol_Tab: return "Tab";
2499 case ImGuiCol_TabHovered: return "TabHovered";
2500 case ImGuiCol_TabActive: return "TabActive";
2501 case ImGuiCol_TabUnfocused: return "TabUnfocused";
2502 case ImGuiCol_TabUnfocusedActive: return "TabUnfocusedActive";
2503 case ImGuiCol_DockingPreview: return "DockingPreview";
2504 case ImGuiCol_DockingEmptyBg: return "DockingEmptyBg";
2505 case ImGuiCol_PlotLines: return "PlotLines";
2506 case ImGuiCol_PlotLinesHovered: return "PlotLinesHovered";
2507 case ImGuiCol_PlotHistogram: return "PlotHistogram";
2508 case ImGuiCol_PlotHistogramHovered: return "PlotHistogramHovered";
2509 case ImGuiCol_TextSelectedBg: return "TextSelectedBg";
2510 case ImGuiCol_DragDropTarget: return "DragDropTarget";
2511 case ImGuiCol_NavHighlight: return "NavHighlight";
2512 case ImGuiCol_NavWindowingHighlight: return "NavWindowingHighlight";
2513 case ImGuiCol_NavWindowingDimBg: return "NavWindowingDimBg";
2514 case ImGuiCol_ModalWindowDimBg: return "ModalWindowDimBg";
2515 }
2516 IM_ASSERT(0);
2517 return "Unknown";
2518 }
2519
2520
2521 //-----------------------------------------------------------------------------
2522 // [SECTION] RENDER HELPERS
2523 // Some of those (internal) functions are currently quite a legacy mess - their signature and behavior will change,
2524 // we need a nicer separation between low-level functions and high-level functions relying on the ImGui context.
2525 // Also see imgui_draw.cpp for some more which have been reworked to not rely on ImGui:: context.
2526 //-----------------------------------------------------------------------------
2527
FindRenderedTextEnd(const char * text,const char * text_end)2528 const char* ImGui::FindRenderedTextEnd(const char* text, const char* text_end)
2529 {
2530 const char* text_display_end = text;
2531 if (!text_end)
2532 text_end = (const char*)-1;
2533
2534 while (text_display_end < text_end && *text_display_end != '\0' && (text_display_end[0] != '#' || text_display_end[1] != '#'))
2535 text_display_end++;
2536 return text_display_end;
2537 }
2538
2539 // Internal ImGui functions to render text
2540 // RenderText***() functions calls ImDrawList::AddText() calls ImBitmapFont::RenderText()
RenderText(ImVec2 pos,const char * text,const char * text_end,bool hide_text_after_hash)2541 void ImGui::RenderText(ImVec2 pos, const char* text, const char* text_end, bool hide_text_after_hash)
2542 {
2543 ImGuiContext& g = *GImGui;
2544 ImGuiWindow* window = g.CurrentWindow;
2545
2546 // Hide anything after a '##' string
2547 const char* text_display_end;
2548 if (hide_text_after_hash)
2549 {
2550 text_display_end = FindRenderedTextEnd(text, text_end);
2551 }
2552 else
2553 {
2554 if (!text_end)
2555 text_end = text + strlen(text); // FIXME-OPT
2556 text_display_end = text_end;
2557 }
2558
2559 if (text != text_display_end)
2560 {
2561 window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end);
2562 if (g.LogEnabled)
2563 LogRenderedText(&pos, text, text_display_end);
2564 }
2565 }
2566
RenderTextWrapped(ImVec2 pos,const char * text,const char * text_end,float wrap_width)2567 void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width)
2568 {
2569 ImGuiContext& g = *GImGui;
2570 ImGuiWindow* window = g.CurrentWindow;
2571
2572 if (!text_end)
2573 text_end = text + strlen(text); // FIXME-OPT
2574
2575 if (text != text_end)
2576 {
2577 window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_end, wrap_width);
2578 if (g.LogEnabled)
2579 LogRenderedText(&pos, text, text_end);
2580 }
2581 }
2582
2583 // Default clip_rect uses (pos_min,pos_max)
2584 // Handle clipping on CPU immediately (vs typically let the GPU clip the triangles that are overlapping the clipping rectangle edges)
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)2585 void ImGui::RenderTextClippedEx(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_display_end, const ImVec2* text_size_if_known, const ImVec2& align, const ImRect* clip_rect)
2586 {
2587 // Perform CPU side clipping for single clipped element to avoid using scissor state
2588 ImVec2 pos = pos_min;
2589 const ImVec2 text_size = text_size_if_known ? *text_size_if_known : CalcTextSize(text, text_display_end, false, 0.0f);
2590
2591 const ImVec2* clip_min = clip_rect ? &clip_rect->Min : &pos_min;
2592 const ImVec2* clip_max = clip_rect ? &clip_rect->Max : &pos_max;
2593 bool need_clipping = (pos.x + text_size.x >= clip_max->x) || (pos.y + text_size.y >= clip_max->y);
2594 if (clip_rect) // If we had no explicit clipping rectangle then pos==clip_min
2595 need_clipping |= (pos.x < clip_min->x) || (pos.y < clip_min->y);
2596
2597 // Align whole block. We should defer that to the better rendering function when we'll have support for individual line alignment.
2598 if (align.x > 0.0f) pos.x = ImMax(pos.x, pos.x + (pos_max.x - pos.x - text_size.x) * align.x);
2599 if (align.y > 0.0f) pos.y = ImMax(pos.y, pos.y + (pos_max.y - pos.y - text_size.y) * align.y);
2600
2601 // Render
2602 if (need_clipping)
2603 {
2604 ImVec4 fine_clip_rect(clip_min->x, clip_min->y, clip_max->x, clip_max->y);
2605 draw_list->AddText(NULL, 0.0f, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, &fine_clip_rect);
2606 }
2607 else
2608 {
2609 draw_list->AddText(NULL, 0.0f, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, NULL);
2610 }
2611 }
2612
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)2613 void ImGui::RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align, const ImRect* clip_rect)
2614 {
2615 // Hide anything after a '##' string
2616 const char* text_display_end = FindRenderedTextEnd(text, text_end);
2617 const int text_len = (int)(text_display_end - text);
2618 if (text_len == 0)
2619 return;
2620
2621 ImGuiContext& g = *GImGui;
2622 ImGuiWindow* window = g.CurrentWindow;
2623 RenderTextClippedEx(window->DrawList, pos_min, pos_max, text, text_display_end, text_size_if_known, align, clip_rect);
2624 if (g.LogEnabled)
2625 LogRenderedText(&pos_min, text, text_display_end);
2626 }
2627
2628
2629 // Another overly complex function until we reorganize everything into a nice all-in-one helper.
2630 // 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.
2631 // 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.
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)2632 void ImGui::RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, float clip_max_x, float ellipsis_max_x, const char* text, const char* text_end_full, const ImVec2* text_size_if_known)
2633 {
2634 ImGuiContext& g = *GImGui;
2635 if (text_end_full == NULL)
2636 text_end_full = FindRenderedTextEnd(text);
2637 const ImVec2 text_size = text_size_if_known ? *text_size_if_known : CalcTextSize(text, text_end_full, false, 0.0f);
2638
2639 //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));
2640 //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));
2641 //draw_list->AddLine(ImVec2(clip_max_x, pos_min.y), ImVec2(clip_max_x, pos_max.y), IM_COL32(255, 0, 0, 255));
2642 // FIXME: We could technically remove (last_glyph->AdvanceX - last_glyph->X1) from text_size.x here and save a few pixels.
2643 if (text_size.x > pos_max.x - pos_min.x)
2644 {
2645 // Hello wo...
2646 // | | |
2647 // min max ellipsis_max
2648 // <-> this is generally some padding value
2649
2650 const ImFont* font = draw_list->_Data->Font;
2651 const float font_size = draw_list->_Data->FontSize;
2652 const char* text_end_ellipsis = NULL;
2653
2654 ImWchar ellipsis_char = font->EllipsisChar;
2655 int ellipsis_char_count = 1;
2656 if (ellipsis_char == (ImWchar)-1)
2657 {
2658 ellipsis_char = (ImWchar)'.';
2659 ellipsis_char_count = 3;
2660 }
2661 const ImFontGlyph* glyph = font->FindGlyph(ellipsis_char);
2662
2663 float ellipsis_glyph_width = glyph->X1; // Width of the glyph with no padding on either side
2664 float ellipsis_total_width = ellipsis_glyph_width; // Full width of entire ellipsis
2665
2666 if (ellipsis_char_count > 1)
2667 {
2668 // Full ellipsis size without free spacing after it.
2669 const float spacing_between_dots = 1.0f * (draw_list->_Data->FontSize / font->FontSize);
2670 ellipsis_glyph_width = glyph->X1 - glyph->X0 + spacing_between_dots;
2671 ellipsis_total_width = ellipsis_glyph_width * (float)ellipsis_char_count - spacing_between_dots;
2672 }
2673
2674 // We can now claim the space between pos_max.x and ellipsis_max.x
2675 const float text_avail_width = ImMax((ImMax(pos_max.x, ellipsis_max_x) - ellipsis_total_width) - pos_min.x, 1.0f);
2676 float text_size_clipped_x = font->CalcTextSizeA(font_size, text_avail_width, 0.0f, text, text_end_full, &text_end_ellipsis).x;
2677 if (text == text_end_ellipsis && text_end_ellipsis < text_end_full)
2678 {
2679 // Always display at least 1 character if there's no room for character + ellipsis
2680 text_end_ellipsis = text + ImTextCountUtf8BytesFromChar(text, text_end_full);
2681 text_size_clipped_x = font->CalcTextSizeA(font_size, FLT_MAX, 0.0f, text, text_end_ellipsis).x;
2682 }
2683 while (text_end_ellipsis > text && ImCharIsBlankA(text_end_ellipsis[-1]))
2684 {
2685 // 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)
2686 text_end_ellipsis--;
2687 text_size_clipped_x -= font->CalcTextSizeA(font_size, FLT_MAX, 0.0f, text_end_ellipsis, text_end_ellipsis + 1).x; // Ascii blanks are always 1 byte
2688 }
2689
2690 // Render text, render ellipsis
2691 RenderTextClippedEx(draw_list, pos_min, ImVec2(clip_max_x, pos_max.y), text, text_end_ellipsis, &text_size, ImVec2(0.0f, 0.0f));
2692 float ellipsis_x = pos_min.x + text_size_clipped_x;
2693 if (ellipsis_x + ellipsis_total_width <= ellipsis_max_x)
2694 for (int i = 0; i < ellipsis_char_count; i++)
2695 {
2696 font->RenderChar(draw_list, font_size, ImVec2(ellipsis_x, pos_min.y), GetColorU32(ImGuiCol_Text), ellipsis_char);
2697 ellipsis_x += ellipsis_glyph_width;
2698 }
2699 }
2700 else
2701 {
2702 RenderTextClippedEx(draw_list, pos_min, ImVec2(clip_max_x, pos_max.y), text, text_end_full, &text_size, ImVec2(0.0f, 0.0f));
2703 }
2704
2705 if (g.LogEnabled)
2706 LogRenderedText(&pos_min, text, text_end_full);
2707 }
2708
2709 // Render a rectangle shaped with optional rounding and borders
RenderFrame(ImVec2 p_min,ImVec2 p_max,ImU32 fill_col,bool border,float rounding)2710 void ImGui::RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border, float rounding)
2711 {
2712 ImGuiContext& g = *GImGui;
2713 ImGuiWindow* window = g.CurrentWindow;
2714 window->DrawList->AddRectFilled(p_min, p_max, fill_col, rounding);
2715 const float border_size = g.Style.FrameBorderSize;
2716 if (border && border_size > 0.0f)
2717 {
2718 window->DrawList->AddRect(p_min + ImVec2(1, 1), p_max + ImVec2(1, 1), GetColorU32(ImGuiCol_BorderShadow), rounding, ImDrawCornerFlags_All, border_size);
2719 window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, ImDrawCornerFlags_All, border_size);
2720 }
2721 }
2722
RenderFrameBorder(ImVec2 p_min,ImVec2 p_max,float rounding)2723 void ImGui::RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding)
2724 {
2725 ImGuiContext& g = *GImGui;
2726 ImGuiWindow* window = g.CurrentWindow;
2727 const float border_size = g.Style.FrameBorderSize;
2728 if (border_size > 0.0f)
2729 {
2730 window->DrawList->AddRect(p_min + ImVec2(1, 1), p_max + ImVec2(1, 1), GetColorU32(ImGuiCol_BorderShadow), rounding, ImDrawCornerFlags_All, border_size);
2731 window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, ImDrawCornerFlags_All, border_size);
2732 }
2733 }
2734
RenderNavHighlight(const ImRect & bb,ImGuiID id,ImGuiNavHighlightFlags flags)2735 void ImGui::RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFlags flags)
2736 {
2737 ImGuiContext& g = *GImGui;
2738 if (id != g.NavId)
2739 return;
2740 if (g.NavDisableHighlight && !(flags & ImGuiNavHighlightFlags_AlwaysDraw))
2741 return;
2742 ImGuiWindow* window = g.CurrentWindow;
2743 if (window->DC.NavHideHighlightOneFrame)
2744 return;
2745
2746 float rounding = (flags & ImGuiNavHighlightFlags_NoRounding) ? 0.0f : g.Style.FrameRounding;
2747 ImRect display_rect = bb;
2748 display_rect.ClipWith(window->ClipRect);
2749 if (flags & ImGuiNavHighlightFlags_TypeDefault)
2750 {
2751 const float THICKNESS = 2.0f;
2752 const float DISTANCE = 3.0f + THICKNESS * 0.5f;
2753 display_rect.Expand(ImVec2(DISTANCE, DISTANCE));
2754 bool fully_visible = window->ClipRect.Contains(display_rect);
2755 if (!fully_visible)
2756 window->DrawList->PushClipRect(display_rect.Min, display_rect.Max);
2757 window->DrawList->AddRect(display_rect.Min + ImVec2(THICKNESS * 0.5f, THICKNESS * 0.5f), display_rect.Max - ImVec2(THICKNESS * 0.5f, THICKNESS * 0.5f), GetColorU32(ImGuiCol_NavHighlight), rounding, ImDrawCornerFlags_All, THICKNESS);
2758 if (!fully_visible)
2759 window->DrawList->PopClipRect();
2760 }
2761 if (flags & ImGuiNavHighlightFlags_TypeThin)
2762 {
2763 window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavHighlight), rounding, ~0, 1.0f);
2764 }
2765 }
2766
2767 //-----------------------------------------------------------------------------
2768 // [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!)
2769 //-----------------------------------------------------------------------------
2770
2771 // ImGuiWindow is mostly a dumb struct. It merely has a constructor and a few helper methods
ImGuiWindow(ImGuiContext * context,const char * name)2772 ImGuiWindow::ImGuiWindow(ImGuiContext* context, const char* name)
2773 : DrawListInst(&context->DrawListSharedData)
2774 {
2775 Name = ImStrdup(name);
2776 ID = ImHashStr(name);
2777 IDStack.push_back(ID);
2778 Flags = FlagsPreviousFrame = ImGuiWindowFlags_None;
2779 Viewport = NULL;
2780 ViewportId = 0;
2781 ViewportAllowPlatformMonitorExtend = -1;
2782 ViewportPos = ImVec2(FLT_MAX, FLT_MAX);
2783 Pos = ImVec2(0.0f, 0.0f);
2784 Size = SizeFull = ImVec2(0.0f, 0.0f);
2785 ContentSize = ContentSizeExplicit = ImVec2(0.0f, 0.0f);
2786 WindowPadding = ImVec2(0.0f, 0.0f);
2787 WindowRounding = 0.0f;
2788 WindowBorderSize = 0.0f;
2789 NameBufLen = (int)strlen(name) + 1;
2790 MoveId = GetID("#MOVE");
2791 ChildId = 0;
2792 Scroll = ImVec2(0.0f, 0.0f);
2793 ScrollTarget = ImVec2(FLT_MAX, FLT_MAX);
2794 ScrollTargetCenterRatio = ImVec2(0.5f, 0.5f);
2795 ScrollbarSizes = ImVec2(0.0f, 0.0f);
2796 ScrollbarX = ScrollbarY = false;
2797 ViewportOwned = false;
2798 Active = WasActive = false;
2799 WriteAccessed = false;
2800 Collapsed = false;
2801 WantCollapseToggle = false;
2802 SkipItems = false;
2803 Appearing = false;
2804 Hidden = false;
2805 IsFallbackWindow = false;
2806 HasCloseButton = false;
2807 ResizeBorderHeld = -1;
2808 BeginCount = 0;
2809 BeginOrderWithinParent = -1;
2810 BeginOrderWithinContext = -1;
2811 PopupId = 0;
2812 AutoFitFramesX = AutoFitFramesY = -1;
2813 AutoFitChildAxises = 0x00;
2814 AutoFitOnlyGrows = false;
2815 AutoPosLastDirection = ImGuiDir_None;
2816 HiddenFramesCanSkipItems = HiddenFramesCannotSkipItems = 0;
2817 SetWindowPosAllowFlags = SetWindowSizeAllowFlags = SetWindowCollapsedAllowFlags = SetWindowDockAllowFlags = ImGuiCond_Always | ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing;
2818 SetWindowPosVal = SetWindowPosPivot = ImVec2(FLT_MAX, FLT_MAX);
2819
2820 InnerRect = ImRect(0.0f, 0.0f, 0.0f, 0.0f); // Clear so the InnerRect.GetSize() code in Begin() doesn't lead to overflow even if the result isn't used.
2821
2822 LastFrameActive = -1;
2823 LastFrameJustFocused = -1;
2824 LastTimeActive = -1.0f;
2825 ItemWidthDefault = 0.0f;
2826 FontWindowScale = FontDpiScale = 1.0f;
2827 SettingsOffset = -1;
2828
2829 DrawList = &DrawListInst;
2830 DrawList->_OwnerName = Name;
2831 ParentWindow = NULL;
2832 RootWindow = NULL;
2833 RootWindowDockStop = NULL;
2834 RootWindowForTitleBarHighlight = NULL;
2835 RootWindowForNav = NULL;
2836
2837 NavLastIds[0] = NavLastIds[1] = 0;
2838 NavRectRel[0] = NavRectRel[1] = ImRect();
2839 NavLastChildNavWindow = NULL;
2840
2841 MemoryCompacted = false;
2842 MemoryDrawListIdxCapacity = MemoryDrawListVtxCapacity = 0;
2843
2844 DockNode = DockNodeAsHost = NULL;
2845 DockId = 0;
2846 DockTabItemStatusFlags = ImGuiItemStatusFlags_None;
2847 DockOrder = -1;
2848 DockIsActive = DockTabIsVisible = DockTabWantClose = false;
2849 }
2850
~ImGuiWindow()2851 ImGuiWindow::~ImGuiWindow()
2852 {
2853 IM_ASSERT(DrawList == &DrawListInst);
2854 IM_DELETE(Name);
2855 for (int i = 0; i != ColumnsStorage.Size; i++)
2856 ColumnsStorage[i].~ImGuiColumns();
2857 }
2858
GetID(const char * str,const char * str_end)2859 ImGuiID ImGuiWindow::GetID(const char* str, const char* str_end)
2860 {
2861 ImGuiID seed = IDStack.back();
2862 ImGuiID id = ImHashStr(str, str_end ? (str_end - str) : 0, seed);
2863 ImGui::KeepAliveID(id);
2864 #ifdef IMGUI_ENABLE_TEST_ENGINE
2865 ImGuiContext& g = *GImGui;
2866 IMGUI_TEST_ENGINE_ID_INFO2(id, ImGuiDataType_String, str, str_end);
2867 #endif
2868 return id;
2869 }
2870
GetID(const void * ptr)2871 ImGuiID ImGuiWindow::GetID(const void* ptr)
2872 {
2873 ImGuiID seed = IDStack.back();
2874 ImGuiID id = ImHashData(&ptr, sizeof(void*), seed);
2875 ImGui::KeepAliveID(id);
2876 #ifdef IMGUI_ENABLE_TEST_ENGINE
2877 ImGuiContext& g = *GImGui;
2878 IMGUI_TEST_ENGINE_ID_INFO(id, ImGuiDataType_Pointer, ptr);
2879 #endif
2880 return id;
2881 }
2882
GetID(int n)2883 ImGuiID ImGuiWindow::GetID(int n)
2884 {
2885 ImGuiID seed = IDStack.back();
2886 ImGuiID id = ImHashData(&n, sizeof(n), seed);
2887 ImGui::KeepAliveID(id);
2888 #ifdef IMGUI_ENABLE_TEST_ENGINE
2889 ImGuiContext& g = *GImGui;
2890 IMGUI_TEST_ENGINE_ID_INFO(id, ImGuiDataType_S32, (intptr_t)n);
2891 #endif
2892 return id;
2893 }
2894
GetIDNoKeepAlive(const char * str,const char * str_end)2895 ImGuiID ImGuiWindow::GetIDNoKeepAlive(const char* str, const char* str_end)
2896 {
2897 ImGuiID seed = IDStack.back();
2898 ImGuiID id = ImHashStr(str, str_end ? (str_end - str) : 0, seed);
2899 #ifdef IMGUI_ENABLE_TEST_ENGINE
2900 ImGuiContext& g = *GImGui;
2901 IMGUI_TEST_ENGINE_ID_INFO2(id, ImGuiDataType_String, str, str_end);
2902 #endif
2903 return id;
2904 }
2905
GetIDNoKeepAlive(const void * ptr)2906 ImGuiID ImGuiWindow::GetIDNoKeepAlive(const void* ptr)
2907 {
2908 ImGuiID seed = IDStack.back();
2909 ImGuiID id = ImHashData(&ptr, sizeof(void*), seed);
2910 #ifdef IMGUI_ENABLE_TEST_ENGINE
2911 ImGuiContext& g = *GImGui;
2912 IMGUI_TEST_ENGINE_ID_INFO(id, ImGuiDataType_Pointer, ptr);
2913 #endif
2914 return id;
2915 }
2916
GetIDNoKeepAlive(int n)2917 ImGuiID ImGuiWindow::GetIDNoKeepAlive(int n)
2918 {
2919 ImGuiID seed = IDStack.back();
2920 ImGuiID id = ImHashData(&n, sizeof(n), seed);
2921 #ifdef IMGUI_ENABLE_TEST_ENGINE
2922 ImGuiContext& g = *GImGui;
2923 IMGUI_TEST_ENGINE_ID_INFO(id, ImGuiDataType_S32, (intptr_t)n);
2924 #endif
2925 return id;
2926 }
2927
2928 // This is only used in rare/specific situations to manufacture an ID out of nowhere.
GetIDFromRectangle(const ImRect & r_abs)2929 ImGuiID ImGuiWindow::GetIDFromRectangle(const ImRect& r_abs)
2930 {
2931 ImGuiID seed = IDStack.back();
2932 const int r_rel[4] = { (int)(r_abs.Min.x - Pos.x), (int)(r_abs.Min.y - Pos.y), (int)(r_abs.Max.x - Pos.x), (int)(r_abs.Max.y - Pos.y) };
2933 ImGuiID id = ImHashData(&r_rel, sizeof(r_rel), seed);
2934 ImGui::KeepAliveID(id);
2935 return id;
2936 }
2937
SetCurrentWindow(ImGuiWindow * window)2938 static void SetCurrentWindow(ImGuiWindow* window)
2939 {
2940 ImGuiContext& g = *GImGui;
2941 g.CurrentWindow = window;
2942 if (window)
2943 g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize();
2944 }
2945
2946 // Free up/compact internal window buffers, we can use this when a window becomes unused.
2947 // This is currently unused by the library, but you may call this yourself for easy GC.
2948 // Not freed:
2949 // - ImGuiWindow, ImGuiWindowSettings, Name
2950 // - StateStorage, ColumnsStorage (may hold useful data)
2951 // This should have no noticeable visual effect. When the window reappear however, expect new allocation/buffer growth/copy cost.
GcCompactTransientWindowBuffers(ImGuiWindow * window)2952 void ImGui::GcCompactTransientWindowBuffers(ImGuiWindow* window)
2953 {
2954 window->MemoryCompacted = true;
2955 window->MemoryDrawListIdxCapacity = window->DrawList->IdxBuffer.Capacity;
2956 window->MemoryDrawListVtxCapacity = window->DrawList->VtxBuffer.Capacity;
2957 window->IDStack.clear();
2958 window->DrawList->_ClearFreeMemory();
2959 window->DC.ChildWindows.clear();
2960 window->DC.ItemFlagsStack.clear();
2961 window->DC.ItemWidthStack.clear();
2962 window->DC.TextWrapPosStack.clear();
2963 window->DC.GroupStack.clear();
2964 }
2965
GcAwakeTransientWindowBuffers(ImGuiWindow * window)2966 void ImGui::GcAwakeTransientWindowBuffers(ImGuiWindow* window)
2967 {
2968 // We stored capacity of the ImDrawList buffer to reduce growth-caused allocation/copy when awakening.
2969 // The other buffers tends to amortize much faster.
2970 window->MemoryCompacted = false;
2971 window->DrawList->IdxBuffer.reserve(window->MemoryDrawListIdxCapacity);
2972 window->DrawList->VtxBuffer.reserve(window->MemoryDrawListVtxCapacity);
2973 window->MemoryDrawListIdxCapacity = window->MemoryDrawListVtxCapacity = 0;
2974 }
2975
SetActiveID(ImGuiID id,ImGuiWindow * window)2976 void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window)
2977 {
2978 ImGuiContext& g = *GImGui;
2979 g.ActiveIdIsJustActivated = (g.ActiveId != id);
2980 if (g.ActiveIdIsJustActivated)
2981 {
2982 g.ActiveIdTimer = 0.0f;
2983 g.ActiveIdHasBeenPressedBefore = false;
2984 g.ActiveIdHasBeenEditedBefore = false;
2985 if (id != 0)
2986 {
2987 g.LastActiveId = id;
2988 g.LastActiveIdTimer = 0.0f;
2989 }
2990 }
2991 g.ActiveId = id;
2992 g.ActiveIdAllowOverlap = false;
2993 g.ActiveIdNoClearOnFocusLoss = false;
2994 g.ActiveIdWindow = window;
2995 g.ActiveIdHasBeenEditedThisFrame = false;
2996 if (id)
2997 {
2998 g.ActiveIdIsAlive = id;
2999 g.ActiveIdSource = (g.NavActivateId == id || g.NavInputId == id || g.NavJustTabbedId == id || g.NavJustMovedToId == id) ? ImGuiInputSource_Nav : ImGuiInputSource_Mouse;
3000 }
3001
3002 // Clear declaration of inputs claimed by the widget
3003 // (Please note that this is WIP and not all keys/inputs are thoroughly declared by all widgets yet)
3004 g.ActiveIdUsingNavDirMask = 0x00;
3005 g.ActiveIdUsingNavInputMask = 0x00;
3006 g.ActiveIdUsingKeyInputMask = 0x00;
3007 }
3008
ClearActiveID()3009 void ImGui::ClearActiveID()
3010 {
3011 SetActiveID(0, NULL); // g.ActiveId = 0;
3012 }
3013
SetHoveredID(ImGuiID id)3014 void ImGui::SetHoveredID(ImGuiID id)
3015 {
3016 ImGuiContext& g = *GImGui;
3017 g.HoveredId = id;
3018 g.HoveredIdAllowOverlap = false;
3019 if (id != 0 && g.HoveredIdPreviousFrame != id)
3020 g.HoveredIdTimer = g.HoveredIdNotActiveTimer = 0.0f;
3021 }
3022
GetHoveredID()3023 ImGuiID ImGui::GetHoveredID()
3024 {
3025 ImGuiContext& g = *GImGui;
3026 return g.HoveredId ? g.HoveredId : g.HoveredIdPreviousFrame;
3027 }
3028
KeepAliveID(ImGuiID id)3029 void ImGui::KeepAliveID(ImGuiID id)
3030 {
3031 ImGuiContext& g = *GImGui;
3032 if (g.ActiveId == id)
3033 g.ActiveIdIsAlive = id;
3034 if (g.ActiveIdPreviousFrame == id)
3035 g.ActiveIdPreviousFrameIsAlive = true;
3036 }
3037
MarkItemEdited(ImGuiID id)3038 void ImGui::MarkItemEdited(ImGuiID id)
3039 {
3040 // This marking is solely to be able to provide info for IsItemDeactivatedAfterEdit().
3041 // ActiveId might have been released by the time we call this (as in the typical press/release button behavior) but still need need to fill the data.
3042 ImGuiContext& g = *GImGui;
3043 IM_ASSERT(g.ActiveId == id || g.ActiveId == 0 || g.DragDropActive);
3044 IM_UNUSED(id); // Avoid unused variable warnings when asserts are compiled out.
3045 //IM_ASSERT(g.CurrentWindow->DC.LastItemId == id);
3046 g.ActiveIdHasBeenEditedThisFrame = true;
3047 g.ActiveIdHasBeenEditedBefore = true;
3048 g.CurrentWindow->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_Edited;
3049 }
3050
IsWindowContentHoverable(ImGuiWindow * window,ImGuiHoveredFlags flags)3051 static inline bool IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flags)
3052 {
3053 // An active popup disable hovering on other windows (apart from its own children)
3054 // FIXME-OPT: This could be cached/stored within the window.
3055 ImGuiContext& g = *GImGui;
3056 if (g.NavWindow)
3057 if (ImGuiWindow* focused_root_window = g.NavWindow->RootWindow)
3058 if (focused_root_window->WasActive && focused_root_window != window->RootWindow)
3059 {
3060 // For the purpose of those flags we differentiate "standard popup" from "modal popup"
3061 // NB: The order of those two tests is important because Modal windows are also Popups.
3062 if (focused_root_window->Flags & ImGuiWindowFlags_Modal)
3063 return false;
3064 if ((focused_root_window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiHoveredFlags_AllowWhenBlockedByPopup))
3065 return false;
3066 }
3067
3068 // Filter by viewport
3069 if (window->Viewport != g.MouseViewport)
3070 if (g.MovingWindow == NULL || window->RootWindow != g.MovingWindow->RootWindow)
3071 return false;
3072
3073 return true;
3074 }
3075
3076 // This is roughly matching the behavior of internal-facing ItemHoverable()
3077 // - 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()
3078 // - this should work even for non-interactive items that have no ID, so we cannot use LastItemId
IsItemHovered(ImGuiHoveredFlags flags)3079 bool ImGui::IsItemHovered(ImGuiHoveredFlags flags)
3080 {
3081 ImGuiContext& g = *GImGui;
3082 ImGuiWindow* window = g.CurrentWindow;
3083 if (g.NavDisableMouseHover && !g.NavDisableHighlight)
3084 return IsItemFocused();
3085
3086 // Test for bounding box overlap, as updated as ItemAdd()
3087 if (!(window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect))
3088 return false;
3089 IM_ASSERT((flags & (ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows)) == 0); // Flags not supported by this function
3090
3091 // Test if we are hovering the right window (our window could be behind another window)
3092 // [2017/10/16] Reverted commit 344d48be3 and testing RootWindow instead. I believe it is correct to NOT test for RootWindow but this leaves us unable to use IsItemHovered() after EndChild() itself.
3093 // Until a solution is found I believe reverting to the test from 2017/09/27 is safe since this was the test that has been running for a long while.
3094 //if (g.HoveredWindow != window)
3095 // return false;
3096 if (g.HoveredRootWindow != window->RootWindow && !(flags & ImGuiHoveredFlags_AllowWhenOverlapped))
3097 return false;
3098
3099 // Test if another item is active (e.g. being dragged)
3100 if (!(flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
3101 if (g.ActiveId != 0 && g.ActiveId != window->DC.LastItemId && !g.ActiveIdAllowOverlap && g.ActiveId != window->MoveId)
3102 return false;
3103
3104 // Test if interactions on this window are blocked by an active popup or modal.
3105 // The ImGuiHoveredFlags_AllowWhenBlockedByPopup flag will be tested here.
3106 if (!IsWindowContentHoverable(window, flags))
3107 return false;
3108
3109 // Test if the item is disabled
3110 if ((window->DC.ItemFlags & ImGuiItemFlags_Disabled) && !(flags & ImGuiHoveredFlags_AllowWhenDisabled))
3111 return false;
3112
3113 // Special handling for calling after Begin() which represent the title bar or tab.
3114 // When the window is collapsed (SkipItems==true) that last item will never be overwritten so we need to detect the case.
3115 if ((window->DC.LastItemId == window->ID || window->DC.LastItemId == window->MoveId) && window->WriteAccessed)
3116 return false;
3117 return true;
3118 }
3119
3120 // Internal facing ItemHoverable() used when submitting widgets. Differs slightly from IsItemHovered().
ItemHoverable(const ImRect & bb,ImGuiID id)3121 bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id)
3122 {
3123 ImGuiContext& g = *GImGui;
3124 if (g.HoveredId != 0 && g.HoveredId != id && !g.HoveredIdAllowOverlap)
3125 return false;
3126
3127 ImGuiWindow* window = g.CurrentWindow;
3128 if (g.HoveredWindow != window)
3129 return false;
3130 if (g.ActiveId != 0 && g.ActiveId != id && !g.ActiveIdAllowOverlap)
3131 return false;
3132 if (!IsMouseHoveringRect(bb.Min, bb.Max))
3133 return false;
3134 if (g.NavDisableMouseHover)
3135 return false;
3136 if (!IsWindowContentHoverable(window, ImGuiHoveredFlags_None) || (window->DC.ItemFlags & ImGuiItemFlags_Disabled))
3137 {
3138 g.HoveredIdDisabled = true;
3139 return false;
3140 }
3141
3142 // We exceptionally allow this function to be called with id==0 to allow using it for easy high-level
3143 // hover test in widgets code. We could also decide to split this function is two.
3144 if (id != 0)
3145 {
3146 SetHoveredID(id);
3147
3148 // [DEBUG] Item Picker tool!
3149 // We perform the check here because SetHoveredID() is not frequently called (1~ time a frame), making
3150 // the cost of this tool near-zero. We can get slightly better call-stack and support picking non-hovered
3151 // items if we perform the test in ItemAdd(), but that would incur a small runtime cost.
3152 // #define IMGUI_DEBUG_TOOL_ITEM_PICKER_EX in imconfig.h if you want this check to also be performed in ItemAdd().
3153 if (g.DebugItemPickerActive && g.HoveredIdPreviousFrame == id)
3154 GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(255, 255, 0, 255));
3155 if (g.DebugItemPickerBreakId == id)
3156 IM_DEBUG_BREAK();
3157 }
3158
3159 return true;
3160 }
3161
IsClippedEx(const ImRect & bb,ImGuiID id,bool clip_even_when_logged)3162 bool ImGui::IsClippedEx(const ImRect& bb, ImGuiID id, bool clip_even_when_logged)
3163 {
3164 ImGuiContext& g = *GImGui;
3165 ImGuiWindow* window = g.CurrentWindow;
3166 if (!bb.Overlaps(window->ClipRect))
3167 if (id == 0 || (id != g.ActiveId && id != g.NavId))
3168 if (clip_even_when_logged || !g.LogEnabled)
3169 return true;
3170 return false;
3171 }
3172
3173 // This is also inlined in ItemAdd()
3174 // Note: if ImGuiItemStatusFlags_HasDisplayRect is set, user needs to set window->DC.LastItemDisplayRect!
SetLastItemData(ImGuiWindow * window,ImGuiID item_id,ImGuiItemStatusFlags item_flags,const ImRect & item_rect)3175 void ImGui::SetLastItemData(ImGuiWindow* window, ImGuiID item_id, ImGuiItemStatusFlags item_flags, const ImRect& item_rect)
3176 {
3177 window->DC.LastItemId = item_id;
3178 window->DC.LastItemStatusFlags = item_flags;
3179 window->DC.LastItemRect = item_rect;
3180 }
3181
3182 // Process TAB/Shift+TAB. Be mindful that this function may _clear_ the ActiveID when tabbing out.
FocusableItemRegister(ImGuiWindow * window,ImGuiID id)3183 bool ImGui::FocusableItemRegister(ImGuiWindow* window, ImGuiID id)
3184 {
3185 ImGuiContext& g = *GImGui;
3186
3187 // Increment counters
3188 const bool is_tab_stop = (window->DC.ItemFlags & (ImGuiItemFlags_NoTabStop | ImGuiItemFlags_Disabled)) == 0;
3189 window->DC.FocusCounterRegular++;
3190 if (is_tab_stop)
3191 window->DC.FocusCounterTabStop++;
3192
3193 // Process TAB/Shift-TAB to tab *OUT* of the currently focused item.
3194 // (Note that we can always TAB out of a widget that doesn't allow tabbing in)
3195 if (g.ActiveId == id && g.FocusTabPressed && !IsActiveIdUsingKey(ImGuiKey_Tab) && g.FocusRequestNextWindow == NULL)
3196 {
3197 g.FocusRequestNextWindow = window;
3198 g.FocusRequestNextCounterTabStop = window->DC.FocusCounterTabStop + (g.IO.KeyShift ? (is_tab_stop ? -1 : 0) : +1); // Modulo on index will be applied at the end of frame once we've got the total counter of items.
3199 }
3200
3201 // Handle focus requests
3202 if (g.FocusRequestCurrWindow == window)
3203 {
3204 if (window->DC.FocusCounterRegular == g.FocusRequestCurrCounterRegular)
3205 return true;
3206 if (is_tab_stop && window->DC.FocusCounterTabStop == g.FocusRequestCurrCounterTabStop)
3207 {
3208 g.NavJustTabbedId = id;
3209 return true;
3210 }
3211
3212 // If another item is about to be focused, we clear our own active id
3213 if (g.ActiveId == id)
3214 ClearActiveID();
3215 }
3216
3217 return false;
3218 }
3219
FocusableItemUnregister(ImGuiWindow * window)3220 void ImGui::FocusableItemUnregister(ImGuiWindow* window)
3221 {
3222 window->DC.FocusCounterRegular--;
3223 window->DC.FocusCounterTabStop--;
3224 }
3225
CalcWrapWidthForPos(const ImVec2 & pos,float wrap_pos_x)3226 float ImGui::CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x)
3227 {
3228 if (wrap_pos_x < 0.0f)
3229 return 0.0f;
3230
3231 ImGuiContext& g = *GImGui;
3232 ImGuiWindow* window = g.CurrentWindow;
3233 if (wrap_pos_x == 0.0f)
3234 {
3235 // We could decide to setup a default wrapping max point for auto-resizing windows,
3236 // or have auto-wrap (with unspecified wrapping pos) behave as a ContentSize extending function?
3237 //if (window->Hidden && (window->Flags & ImGuiWindowFlags_AlwaysAutoResize))
3238 // wrap_pos_x = ImMax(window->WorkRect.Min.x + g.FontSize * 10.0f, window->WorkRect.Max.x);
3239 //else
3240 wrap_pos_x = window->WorkRect.Max.x;
3241 }
3242 else if (wrap_pos_x > 0.0f)
3243 {
3244 wrap_pos_x += window->Pos.x - window->Scroll.x; // wrap_pos_x is provided is window local space
3245 }
3246
3247 return ImMax(wrap_pos_x - pos.x, 1.0f);
3248 }
3249
3250 // IM_ALLOC() == ImGui::MemAlloc()
MemAlloc(size_t size)3251 void* ImGui::MemAlloc(size_t size)
3252 {
3253 if (ImGuiContext* ctx = GImGui)
3254 ctx->IO.MetricsActiveAllocations++;
3255 return GImAllocatorAllocFunc(size, GImAllocatorUserData);
3256 }
3257
3258 // IM_FREE() == ImGui::MemFree()
MemFree(void * ptr)3259 void ImGui::MemFree(void* ptr)
3260 {
3261 if (ptr)
3262 if (ImGuiContext* ctx = GImGui)
3263 ctx->IO.MetricsActiveAllocations--;
3264 return GImAllocatorFreeFunc(ptr, GImAllocatorUserData);
3265 }
3266
GetClipboardText()3267 const char* ImGui::GetClipboardText()
3268 {
3269 ImGuiContext& g = *GImGui;
3270 return g.IO.GetClipboardTextFn ? g.IO.GetClipboardTextFn(g.IO.ClipboardUserData) : "";
3271 }
3272
SetClipboardText(const char * text)3273 void ImGui::SetClipboardText(const char* text)
3274 {
3275 ImGuiContext& g = *GImGui;
3276 if (g.IO.SetClipboardTextFn)
3277 g.IO.SetClipboardTextFn(g.IO.ClipboardUserData, text);
3278 }
3279
GetVersion()3280 const char* ImGui::GetVersion()
3281 {
3282 return IMGUI_VERSION;
3283 }
3284
3285 // Internal state access - if you want to share Dear ImGui state between modules (e.g. DLL) or allocate it yourself
3286 // 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
GetCurrentContext()3287 ImGuiContext* ImGui::GetCurrentContext()
3288 {
3289 return GImGui;
3290 }
3291
SetCurrentContext(ImGuiContext * ctx)3292 void ImGui::SetCurrentContext(ImGuiContext* ctx)
3293 {
3294 #ifdef IMGUI_SET_CURRENT_CONTEXT_FUNC
3295 IMGUI_SET_CURRENT_CONTEXT_FUNC(ctx); // For custom thread-based hackery you may want to have control over this.
3296 #else
3297 GImGui = ctx;
3298 #endif
3299 }
3300
SetAllocatorFunctions(void * (* alloc_func)(size_t sz,void * user_data),void (* free_func)(void * ptr,void * user_data),void * user_data)3301 void ImGui::SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* user_data), void (*free_func)(void* ptr, void* user_data), void* user_data)
3302 {
3303 GImAllocatorAllocFunc = alloc_func;
3304 GImAllocatorFreeFunc = free_func;
3305 GImAllocatorUserData = user_data;
3306 }
3307
CreateContext(ImFontAtlas * shared_font_atlas)3308 ImGuiContext* ImGui::CreateContext(ImFontAtlas* shared_font_atlas)
3309 {
3310 ImGuiContext* ctx = IM_NEW(ImGuiContext)(shared_font_atlas);
3311 if (GImGui == NULL)
3312 SetCurrentContext(ctx);
3313 Initialize(ctx);
3314 return ctx;
3315 }
3316
DestroyContext(ImGuiContext * ctx)3317 void ImGui::DestroyContext(ImGuiContext* ctx)
3318 {
3319 if (ctx == NULL)
3320 ctx = GImGui;
3321 Shutdown(ctx);
3322 if (GImGui == ctx)
3323 SetCurrentContext(NULL);
3324 IM_DELETE(ctx);
3325 }
3326
GetIO()3327 ImGuiIO& ImGui::GetIO()
3328 {
3329 IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext() ?");
3330 return GImGui->IO;
3331 }
3332
GetPlatformIO()3333 ImGuiPlatformIO& ImGui::GetPlatformIO()
3334 {
3335 IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() or ImGui::SetCurrentContext()?");
3336 return GImGui->PlatformIO;
3337 }
3338
3339 // Same value as passed to the old io.RenderDrawListsFn function. Valid after Render() and until the next call to NewFrame()
GetDrawData()3340 ImDrawData* ImGui::GetDrawData()
3341 {
3342 ImGuiContext& g = *GImGui;
3343 return g.Viewports[0]->DrawDataP.Valid ? &g.Viewports[0]->DrawDataP : NULL;
3344 }
3345
GetTime()3346 double ImGui::GetTime()
3347 {
3348 return GImGui->Time;
3349 }
3350
GetFrameCount()3351 int ImGui::GetFrameCount()
3352 {
3353 return GImGui->FrameCount;
3354 }
3355
GetViewportDrawList(ImGuiViewportP * viewport,size_t drawlist_no,const char * drawlist_name)3356 static ImDrawList* GetViewportDrawList(ImGuiViewportP* viewport, size_t drawlist_no, const char* drawlist_name)
3357 {
3358 // Create the draw list on demand, because they are not frequently used for all viewports
3359 ImGuiContext& g = *GImGui;
3360 IM_ASSERT(drawlist_no < IM_ARRAYSIZE(viewport->DrawLists));
3361 ImDrawList* draw_list = viewport->DrawLists[drawlist_no];
3362 if (draw_list == NULL)
3363 {
3364 draw_list = IM_NEW(ImDrawList)(&g.DrawListSharedData);
3365 draw_list->_OwnerName = drawlist_name;
3366 viewport->DrawLists[drawlist_no] = draw_list;
3367 }
3368
3369 // Our ImDrawList system requires that there is always a command
3370 if (viewport->LastFrameDrawLists[drawlist_no] != g.FrameCount)
3371 {
3372 draw_list->_ResetForNewFrame();
3373 draw_list->PushTextureID(g.IO.Fonts->TexID);
3374 draw_list->PushClipRect(viewport->Pos, viewport->Pos + viewport->Size, false);
3375 viewport->LastFrameDrawLists[drawlist_no] = g.FrameCount;
3376 }
3377 return draw_list;
3378 }
3379
GetBackgroundDrawList(ImGuiViewport * viewport)3380 ImDrawList* ImGui::GetBackgroundDrawList(ImGuiViewport* viewport)
3381 {
3382 return GetViewportDrawList((ImGuiViewportP*)viewport, 0, "##Background");
3383 }
3384
GetBackgroundDrawList()3385 ImDrawList* ImGui::GetBackgroundDrawList()
3386 {
3387 ImGuiWindow* window = GImGui->CurrentWindow;
3388 return GetBackgroundDrawList(window->Viewport);
3389 }
3390
GetForegroundDrawList(ImGuiViewport * viewport)3391 ImDrawList* ImGui::GetForegroundDrawList(ImGuiViewport* viewport)
3392 {
3393 return GetViewportDrawList((ImGuiViewportP*)viewport, 1, "##Foreground");
3394 }
3395
GetForegroundDrawList()3396 ImDrawList* ImGui::GetForegroundDrawList()
3397 {
3398 ImGuiWindow* window = GImGui->CurrentWindow;
3399 return GetForegroundDrawList(window->Viewport);
3400 }
3401
GetDrawListSharedData()3402 ImDrawListSharedData* ImGui::GetDrawListSharedData()
3403 {
3404 return &GImGui->DrawListSharedData;
3405 }
3406
StartMouseMovingWindow(ImGuiWindow * window)3407 void ImGui::StartMouseMovingWindow(ImGuiWindow* window)
3408 {
3409 // Set ActiveId even if the _NoMove flag is set. Without it, dragging away from a window with _NoMove would activate hover on other windows.
3410 // We _also_ call this when clicking in a window empty space when io.ConfigWindowsMoveFromTitleBarOnly is set, but clear g.MovingWindow afterward.
3411 // This is because we want ActiveId to be set even when the window is not permitted to move.
3412 ImGuiContext& g = *GImGui;
3413 FocusWindow(window);
3414 SetActiveID(window->MoveId, window);
3415 g.NavDisableHighlight = true;
3416 g.ActiveIdNoClearOnFocusLoss = true;
3417 g.ActiveIdClickOffset = g.IO.MouseClickedPos[0] - window->RootWindow->Pos;
3418
3419 bool can_move_window = true;
3420 if ((window->Flags & ImGuiWindowFlags_NoMove) || (window->RootWindow->Flags & ImGuiWindowFlags_NoMove))
3421 can_move_window = false;
3422 if (ImGuiDockNode* node = window->DockNodeAsHost)
3423 if (node->VisibleWindow && (node->VisibleWindow->Flags & ImGuiWindowFlags_NoMove))
3424 can_move_window = false;
3425 if (can_move_window)
3426 g.MovingWindow = window;
3427 }
3428
3429 // We use 'undock_floating_node == false' when dragging from title bar to allow moving groups of floating nodes without undocking them.
3430 // - undock_floating_node == true: when dragging from a floating node within a hierarchy, always undock the node.
3431 // - undock_floating_node == false: when dragging from a floating node within a hierarchy, move root window.
StartMouseMovingWindowOrNode(ImGuiWindow * window,ImGuiDockNode * node,bool undock_floating_node)3432 void ImGui::StartMouseMovingWindowOrNode(ImGuiWindow* window, ImGuiDockNode* node, bool undock_floating_node)
3433 {
3434 ImGuiContext& g = *GImGui;
3435 bool can_undock_node = false;
3436 if (node != NULL && node->VisibleWindow && (node->VisibleWindow->Flags & ImGuiWindowFlags_NoMove) == 0)
3437 {
3438 // Can undock if:
3439 // - part of a floating node hierarchy with more than one visible node (if only one is visible, we'll just move the whole hierarchy)
3440 // - part of a dockspace node hierarchy (trivia: undocking from a fixed/central node will create a new node and copy windows)
3441 ImGuiDockNode* root_node = DockNodeGetRootNode(node);
3442 if (root_node->OnlyNodeWithWindows != node || root_node->CentralNode != NULL) // -V1051 PVS-Studio thinks node should be root_node and is wrong about that.
3443 if (undock_floating_node || root_node->IsDockSpace())
3444 can_undock_node = true;
3445 }
3446
3447 const bool clicked = IsMouseClicked(0);
3448 const bool dragging = IsMouseDragging(0, g.IO.MouseDragThreshold * 1.70f);
3449 if (can_undock_node && dragging)
3450 DockContextQueueUndockNode(&g, node); // Will lead to DockNodeStartMouseMovingWindow() -> StartMouseMovingWindow() being called next frame
3451 else if (!can_undock_node && (clicked || dragging) && g.MovingWindow != window)
3452 StartMouseMovingWindow(window);
3453 }
3454
3455 // Handle mouse moving window
3456 // Note: moving window with the navigation keys (Square + d-pad / CTRL+TAB + Arrows) are processed in NavUpdateWindowing()
3457 // FIXME: We don't have strong guarantee that g.MovingWindow stay synched with g.ActiveId == g.MovingWindow->MoveId.
3458 // This is currently enforced by the fact that BeginDragDropSource() is setting all g.ActiveIdUsingXXXX flags to inhibit navigation inputs,
3459 // but if we should more thoroughly test cases where g.ActiveId or g.MovingWindow gets changed and not the other.
UpdateMouseMovingWindowNewFrame()3460 void ImGui::UpdateMouseMovingWindowNewFrame()
3461 {
3462 ImGuiContext& g = *GImGui;
3463 if (g.MovingWindow != NULL)
3464 {
3465 // We actually want to move the root window. g.MovingWindow == window we clicked on (could be a child window).
3466 // We track it to preserve Focus and so that generally ActiveIdWindow == MovingWindow and ActiveId == MovingWindow->MoveId for consistency.
3467 KeepAliveID(g.ActiveId);
3468 IM_ASSERT(g.MovingWindow && g.MovingWindow->RootWindow);
3469 ImGuiWindow* moving_window = g.MovingWindow->RootWindow;
3470 if (g.IO.MouseDown[0] && IsMousePosValid(&g.IO.MousePos))
3471 {
3472 ImVec2 pos = g.IO.MousePos - g.ActiveIdClickOffset;
3473 if (moving_window->Pos.x != pos.x || moving_window->Pos.y != pos.y)
3474 {
3475 MarkIniSettingsDirty(moving_window);
3476 SetWindowPos(moving_window, pos, ImGuiCond_Always);
3477 if (moving_window->ViewportOwned) // Synchronize viewport immediately because some overlays may relies on clipping rectangle before we Begin() into the window.
3478 moving_window->Viewport->Pos = pos;
3479 }
3480 FocusWindow(g.MovingWindow);
3481 }
3482 else
3483 {
3484 // Try to merge the window back into the main viewport.
3485 // This works because MouseViewport should be != MovingWindow->Viewport on release (as per code in UpdateViewports)
3486 if (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable)
3487 UpdateTryMergeWindowIntoHostViewport(moving_window, g.MouseViewport);
3488
3489 // Restore the mouse viewport so that we don't hover the viewport _under_ the moved window during the frame we released the mouse button.
3490 if (!IsDragDropPayloadBeingAccepted())
3491 g.MouseViewport = moving_window->Viewport;
3492
3493 // Clear the NoInput window flag set by the Viewport system
3494 moving_window->Viewport->Flags &= ~ImGuiViewportFlags_NoInputs;
3495
3496 ClearActiveID();
3497 g.MovingWindow = NULL;
3498 }
3499 }
3500 else
3501 {
3502 // When clicking/dragging from a window that has the _NoMove flag, we still set the ActiveId in order to prevent hovering others.
3503 if (g.ActiveIdWindow && g.ActiveIdWindow->MoveId == g.ActiveId)
3504 {
3505 KeepAliveID(g.ActiveId);
3506 if (!g.IO.MouseDown[0])
3507 ClearActiveID();
3508 }
3509 }
3510 }
3511
3512 // Initiate moving window when clicking on empty space or title bar.
3513 // Handle left-click and right-click focus.
UpdateMouseMovingWindowEndFrame()3514 void ImGui::UpdateMouseMovingWindowEndFrame()
3515 {
3516 ImGuiContext& g = *GImGui;
3517 if (g.ActiveId != 0 || g.HoveredId != 0)
3518 return;
3519
3520 // Unless we just made a window/popup appear
3521 if (g.NavWindow && g.NavWindow->Appearing)
3522 return;
3523
3524 // Click on void to focus window and start moving
3525 // (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!)
3526 if (g.IO.MouseClicked[0])
3527 {
3528 // Handle the edge case of a popup being closed while clicking in its empty space.
3529 // If we try to focus it, FocusWindow() > ClosePopupsOverWindow() will accidentally close any parent popups because they are not linked together any more.
3530 ImGuiWindow* root_window = g.HoveredWindow ? g.HoveredWindow->RootWindowDockStop : NULL;
3531 const bool is_closed_popup = root_window && (root_window->Flags & ImGuiWindowFlags_Popup) && !IsPopupOpen(root_window->PopupId, ImGuiPopupFlags_AnyPopupLevel);
3532
3533 if (root_window != NULL && !is_closed_popup)
3534 {
3535 StartMouseMovingWindow(g.HoveredWindow);
3536
3537 // Cancel moving if clicked outside of title bar
3538 if (g.IO.ConfigWindowsMoveFromTitleBarOnly)
3539 if (!(root_window->Flags & ImGuiWindowFlags_NoTitleBar) || root_window->DockIsActive)
3540 if (!root_window->TitleBarRect().Contains(g.IO.MouseClickedPos[0]))
3541 g.MovingWindow = NULL;
3542
3543 // Cancel moving if clicked over an item which was disabled or inhibited by popups (note that we know HoveredId == 0 already)
3544 if (g.HoveredIdDisabled)
3545 g.MovingWindow = NULL;
3546 }
3547 else if (root_window == NULL && g.NavWindow != NULL && GetTopMostPopupModal() == NULL)
3548 {
3549 // Clicking on void disable focus
3550 FocusWindow(NULL);
3551 }
3552 }
3553
3554 // With right mouse button we close popups without changing focus based on where the mouse is aimed
3555 // Instead, focus will be restored to the window under the bottom-most closed popup.
3556 // (The left mouse button path calls FocusWindow on the hovered window, which will lead NewFrame->ClosePopupsOverWindow to trigger)
3557 if (g.IO.MouseClicked[1])
3558 {
3559 // Find the top-most window between HoveredWindow and the top-most Modal Window.
3560 // This is where we can trim the popup stack.
3561 ImGuiWindow* modal = GetTopMostPopupModal();
3562 bool hovered_window_above_modal = false;
3563 if (modal == NULL)
3564 hovered_window_above_modal = true;
3565 for (int i = g.Windows.Size - 1; i >= 0 && hovered_window_above_modal == false; i--)
3566 {
3567 ImGuiWindow* window = g.Windows[i];
3568 if (window == modal)
3569 break;
3570 if (window == g.HoveredWindow)
3571 hovered_window_above_modal = true;
3572 }
3573 ClosePopupsOverWindow(hovered_window_above_modal ? g.HoveredWindow : modal, true);
3574 }
3575 }
3576
TranslateWindow(ImGuiWindow * window,const ImVec2 & delta)3577 static void TranslateWindow(ImGuiWindow* window, const ImVec2& delta)
3578 {
3579 window->Pos += delta;
3580 window->ClipRect.Translate(delta);
3581 window->OuterRectClipped.Translate(delta);
3582 window->InnerRect.Translate(delta);
3583 window->DC.CursorPos += delta;
3584 window->DC.CursorStartPos += delta;
3585 window->DC.CursorMaxPos += delta;
3586 window->DC.LastItemRect.Translate(delta);
3587 window->DC.LastItemDisplayRect.Translate(delta);
3588 }
3589
ScaleWindow(ImGuiWindow * window,float scale)3590 static void ScaleWindow(ImGuiWindow* window, float scale)
3591 {
3592 ImVec2 origin = window->Viewport->Pos;
3593 window->Pos = ImFloor((window->Pos - origin) * scale + origin);
3594 window->Size = ImFloor(window->Size * scale);
3595 window->SizeFull = ImFloor(window->SizeFull * scale);
3596 window->ContentSize = ImFloor(window->ContentSize * scale);
3597 }
3598
IsWindowActiveAndVisible(ImGuiWindow * window)3599 static bool IsWindowActiveAndVisible(ImGuiWindow* window)
3600 {
3601 return (window->Active) && (!window->Hidden);
3602 }
3603
UpdateMouseInputs()3604 static void ImGui::UpdateMouseInputs()
3605 {
3606 ImGuiContext& g = *GImGui;
3607
3608 // Round mouse position to avoid spreading non-rounded position (e.g. UpdateManualResize doesn't support them well)
3609 if (IsMousePosValid(&g.IO.MousePos))
3610 g.IO.MousePos = g.LastValidMousePos = ImFloor(g.IO.MousePos);
3611
3612 // If mouse just appeared or disappeared (usually denoted by -FLT_MAX components) we cancel out movement in MouseDelta
3613 if (IsMousePosValid(&g.IO.MousePos) && IsMousePosValid(&g.IO.MousePosPrev))
3614 g.IO.MouseDelta = g.IO.MousePos - g.IO.MousePosPrev;
3615 else
3616 g.IO.MouseDelta = ImVec2(0.0f, 0.0f);
3617 if (g.IO.MouseDelta.x != 0.0f || g.IO.MouseDelta.y != 0.0f)
3618 g.NavDisableMouseHover = false;
3619
3620 g.IO.MousePosPrev = g.IO.MousePos;
3621 for (int i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++)
3622 {
3623 g.IO.MouseClicked[i] = g.IO.MouseDown[i] && g.IO.MouseDownDuration[i] < 0.0f;
3624 g.IO.MouseReleased[i] = !g.IO.MouseDown[i] && g.IO.MouseDownDuration[i] >= 0.0f;
3625 g.IO.MouseDownDurationPrev[i] = g.IO.MouseDownDuration[i];
3626 g.IO.MouseDownDuration[i] = g.IO.MouseDown[i] ? (g.IO.MouseDownDuration[i] < 0.0f ? 0.0f : g.IO.MouseDownDuration[i] + g.IO.DeltaTime) : -1.0f;
3627 g.IO.MouseDoubleClicked[i] = false;
3628 if (g.IO.MouseClicked[i])
3629 {
3630 if ((float)(g.Time - g.IO.MouseClickedTime[i]) < g.IO.MouseDoubleClickTime)
3631 {
3632 ImVec2 delta_from_click_pos = IsMousePosValid(&g.IO.MousePos) ? (g.IO.MousePos - g.IO.MouseClickedPos[i]) : ImVec2(0.0f, 0.0f);
3633 if (ImLengthSqr(delta_from_click_pos) < g.IO.MouseDoubleClickMaxDist * g.IO.MouseDoubleClickMaxDist)
3634 g.IO.MouseDoubleClicked[i] = true;
3635 g.IO.MouseClickedTime[i] = -g.IO.MouseDoubleClickTime * 2.0f; // Mark as "old enough" so the third click isn't turned into a double-click
3636 }
3637 else
3638 {
3639 g.IO.MouseClickedTime[i] = g.Time;
3640 }
3641 g.IO.MouseClickedPos[i] = g.IO.MousePos;
3642 g.IO.MouseDownWasDoubleClick[i] = g.IO.MouseDoubleClicked[i];
3643 g.IO.MouseDragMaxDistanceAbs[i] = ImVec2(0.0f, 0.0f);
3644 g.IO.MouseDragMaxDistanceSqr[i] = 0.0f;
3645 }
3646 else if (g.IO.MouseDown[i])
3647 {
3648 // Maintain the maximum distance we reaching from the initial click position, which is used with dragging threshold
3649 ImVec2 delta_from_click_pos = IsMousePosValid(&g.IO.MousePos) ? (g.IO.MousePos - g.IO.MouseClickedPos[i]) : ImVec2(0.0f, 0.0f);
3650 g.IO.MouseDragMaxDistanceSqr[i] = ImMax(g.IO.MouseDragMaxDistanceSqr[i], ImLengthSqr(delta_from_click_pos));
3651 g.IO.MouseDragMaxDistanceAbs[i].x = ImMax(g.IO.MouseDragMaxDistanceAbs[i].x, delta_from_click_pos.x < 0.0f ? -delta_from_click_pos.x : delta_from_click_pos.x);
3652 g.IO.MouseDragMaxDistanceAbs[i].y = ImMax(g.IO.MouseDragMaxDistanceAbs[i].y, delta_from_click_pos.y < 0.0f ? -delta_from_click_pos.y : delta_from_click_pos.y);
3653 }
3654 if (!g.IO.MouseDown[i] && !g.IO.MouseReleased[i])
3655 g.IO.MouseDownWasDoubleClick[i] = false;
3656 if (g.IO.MouseClicked[i]) // Clicking any mouse button reactivate mouse hovering which may have been deactivated by gamepad/keyboard navigation
3657 g.NavDisableMouseHover = false;
3658 }
3659 }
3660
StartLockWheelingWindow(ImGuiWindow * window)3661 static void StartLockWheelingWindow(ImGuiWindow* window)
3662 {
3663 ImGuiContext& g = *GImGui;
3664 if (g.WheelingWindow == window)
3665 return;
3666 g.WheelingWindow = window;
3667 g.WheelingWindowRefMousePos = g.IO.MousePos;
3668 g.WheelingWindowTimer = WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER;
3669 }
3670
UpdateMouseWheel()3671 void ImGui::UpdateMouseWheel()
3672 {
3673 ImGuiContext& g = *GImGui;
3674
3675 // Reset the locked window if we move the mouse or after the timer elapses
3676 if (g.WheelingWindow != NULL)
3677 {
3678 g.WheelingWindowTimer -= g.IO.DeltaTime;
3679 if (IsMousePosValid() && ImLengthSqr(g.IO.MousePos - g.WheelingWindowRefMousePos) > g.IO.MouseDragThreshold * g.IO.MouseDragThreshold)
3680 g.WheelingWindowTimer = 0.0f;
3681 if (g.WheelingWindowTimer <= 0.0f)
3682 {
3683 g.WheelingWindow = NULL;
3684 g.WheelingWindowTimer = 0.0f;
3685 }
3686 }
3687
3688 if (g.IO.MouseWheel == 0.0f && g.IO.MouseWheelH == 0.0f)
3689 return;
3690
3691 ImGuiWindow* window = g.WheelingWindow ? g.WheelingWindow : g.HoveredWindow;
3692 if (!window || window->Collapsed)
3693 return;
3694
3695 // Zoom / Scale window
3696 // FIXME-OBSOLETE: This is an old feature, it still works but pretty much nobody is using it and may be best redesigned.
3697 if (g.IO.MouseWheel != 0.0f && g.IO.KeyCtrl && g.IO.FontAllowUserScaling)
3698 {
3699 StartLockWheelingWindow(window);
3700 const float new_font_scale = ImClamp(window->FontWindowScale + g.IO.MouseWheel * 0.10f, 0.50f, 2.50f);
3701 const float scale = new_font_scale / window->FontWindowScale;
3702 window->FontWindowScale = new_font_scale;
3703 if (!(window->Flags & ImGuiWindowFlags_ChildWindow))
3704 {
3705 const ImVec2 offset = window->Size * (1.0f - scale) * (g.IO.MousePos - window->Pos) / window->Size;
3706 SetWindowPos(window, window->Pos + offset, 0);
3707 window->Size = ImFloor(window->Size * scale);
3708 window->SizeFull = ImFloor(window->SizeFull * scale);
3709 }
3710 return;
3711 }
3712
3713 // Mouse wheel scrolling
3714 // If a child window has the ImGuiWindowFlags_NoScrollWithMouse flag, we give a chance to scroll its parent
3715
3716 // Vertical Mouse Wheel scrolling
3717 const float wheel_y = (g.IO.MouseWheel != 0.0f && !g.IO.KeyShift) ? g.IO.MouseWheel : 0.0f;
3718 if (wheel_y != 0.0f && !g.IO.KeyCtrl)
3719 {
3720 StartLockWheelingWindow(window);
3721 while ((window->Flags & ImGuiWindowFlags_ChildWindow) && ((window->ScrollMax.y == 0.0f) || ((window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs))))
3722 window = window->ParentWindow;
3723 if (!(window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs))
3724 {
3725 float max_step = window->InnerRect.GetHeight() * 0.67f;
3726 float scroll_step = ImFloor(ImMin(5 * window->CalcFontSize(), max_step));
3727 SetScrollY(window, window->Scroll.y - wheel_y * scroll_step);
3728 }
3729 }
3730
3731 // Horizontal Mouse Wheel scrolling, or Vertical Mouse Wheel w/ Shift held
3732 const float wheel_x = (g.IO.MouseWheelH != 0.0f && !g.IO.KeyShift) ? g.IO.MouseWheelH : (g.IO.MouseWheel != 0.0f && g.IO.KeyShift) ? g.IO.MouseWheel : 0.0f;
3733 if (wheel_x != 0.0f && !g.IO.KeyCtrl)
3734 {
3735 StartLockWheelingWindow(window);
3736 while ((window->Flags & ImGuiWindowFlags_ChildWindow) && ((window->ScrollMax.x == 0.0f) || ((window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs))))
3737 window = window->ParentWindow;
3738 if (!(window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(window->Flags & ImGuiWindowFlags_NoMouseInputs))
3739 {
3740 float max_step = window->InnerRect.GetWidth() * 0.67f;
3741 float scroll_step = ImFloor(ImMin(2 * window->CalcFontSize(), max_step));
3742 SetScrollX(window, window->Scroll.x - wheel_x * scroll_step);
3743 }
3744 }
3745 }
3746
UpdateTabFocus()3747 void ImGui::UpdateTabFocus()
3748 {
3749 ImGuiContext& g = *GImGui;
3750
3751 // Pressing TAB activate widget focus
3752 g.FocusTabPressed = (g.NavWindow && g.NavWindow->Active && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab));
3753 if (g.ActiveId == 0 && g.FocusTabPressed)
3754 {
3755 // Note that SetKeyboardFocusHere() sets the Next fields mid-frame. To be consistent we also
3756 // manipulate the Next fields even, even though they will be turned into Curr fields by the code below.
3757 g.FocusRequestNextWindow = g.NavWindow;
3758 g.FocusRequestNextCounterRegular = INT_MAX;
3759 if (g.NavId != 0 && g.NavIdTabCounter != INT_MAX)
3760 g.FocusRequestNextCounterTabStop = g.NavIdTabCounter + 1 + (g.IO.KeyShift ? -1 : 1);
3761 else
3762 g.FocusRequestNextCounterTabStop = g.IO.KeyShift ? -1 : 0;
3763 }
3764
3765 // Turn queued focus request into current one
3766 g.FocusRequestCurrWindow = NULL;
3767 g.FocusRequestCurrCounterRegular = g.FocusRequestCurrCounterTabStop = INT_MAX;
3768 if (g.FocusRequestNextWindow != NULL)
3769 {
3770 ImGuiWindow* window = g.FocusRequestNextWindow;
3771 g.FocusRequestCurrWindow = window;
3772 if (g.FocusRequestNextCounterRegular != INT_MAX && window->DC.FocusCounterRegular != -1)
3773 g.FocusRequestCurrCounterRegular = ImModPositive(g.FocusRequestNextCounterRegular, window->DC.FocusCounterRegular + 1);
3774 if (g.FocusRequestNextCounterTabStop != INT_MAX && window->DC.FocusCounterTabStop != -1)
3775 g.FocusRequestCurrCounterTabStop = ImModPositive(g.FocusRequestNextCounterTabStop, window->DC.FocusCounterTabStop + 1);
3776 g.FocusRequestNextWindow = NULL;
3777 g.FocusRequestNextCounterRegular = g.FocusRequestNextCounterTabStop = INT_MAX;
3778 }
3779
3780 g.NavIdTabCounter = INT_MAX;
3781 }
3782
3783 // 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)
UpdateHoveredWindowAndCaptureFlags()3784 void ImGui::UpdateHoveredWindowAndCaptureFlags()
3785 {
3786 ImGuiContext& g = *GImGui;
3787
3788 // Find the window hovered by mouse:
3789 // - Child windows can extend beyond the limit of their parent so we need to derive HoveredRootWindow from HoveredWindow.
3790 // - 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.
3791 // - 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.
3792 bool clear_hovered_windows = false;
3793 FindHoveredWindow();
3794 IM_ASSERT(g.HoveredWindow == NULL || g.HoveredWindow == g.MovingWindow || g.HoveredWindow->Viewport == g.MouseViewport);
3795
3796 // Modal windows prevents mouse from hovering behind them.
3797 ImGuiWindow* modal_window = GetTopMostPopupModal();
3798 if (modal_window && g.HoveredRootWindow && !IsWindowChildOf(g.HoveredRootWindow, modal_window))
3799 clear_hovered_windows = true;
3800
3801 // Disabled mouse?
3802 if (g.IO.ConfigFlags & ImGuiConfigFlags_NoMouse)
3803 clear_hovered_windows = true;
3804
3805 // We track click ownership. When clicked outside of a window the click is owned by the application and won't report hovering nor request capture even while dragging over our windows afterward.
3806 int mouse_earliest_button_down = -1;
3807 bool mouse_any_down = false;
3808 for (int i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++)
3809 {
3810 if (g.IO.MouseClicked[i])
3811 g.IO.MouseDownOwned[i] = (g.HoveredWindow != NULL) || (g.OpenPopupStack.Size > 0);
3812 mouse_any_down |= g.IO.MouseDown[i];
3813 if (g.IO.MouseDown[i])
3814 if (mouse_earliest_button_down == -1 || g.IO.MouseClickedTime[i] < g.IO.MouseClickedTime[mouse_earliest_button_down])
3815 mouse_earliest_button_down = i;
3816 }
3817 const bool mouse_avail_to_imgui = (mouse_earliest_button_down == -1) || g.IO.MouseDownOwned[mouse_earliest_button_down];
3818
3819 // If mouse was first clicked outside of ImGui bounds we also cancel out hovering.
3820 // FIXME: For patterns of drag and drop across OS windows, we may need to rework/remove this test (first committed 311c0ca9 on 2015/02)
3821 const bool mouse_dragging_extern_payload = g.DragDropActive && (g.DragDropSourceFlags & ImGuiDragDropFlags_SourceExtern) != 0;
3822 if (!mouse_avail_to_imgui && !mouse_dragging_extern_payload)
3823 clear_hovered_windows = true;
3824
3825 if (clear_hovered_windows)
3826 g.HoveredWindow = g.HoveredRootWindow = g.HoveredWindowUnderMovingWindow = NULL;
3827
3828 // Update io.WantCaptureMouse for the user application (true = dispatch mouse info to imgui, false = dispatch mouse info to Dear ImGui + app)
3829 if (g.WantCaptureMouseNextFrame != -1)
3830 g.IO.WantCaptureMouse = (g.WantCaptureMouseNextFrame != 0);
3831 else
3832 g.IO.WantCaptureMouse = (mouse_avail_to_imgui && (g.HoveredWindow != NULL || mouse_any_down)) || (g.OpenPopupStack.Size > 0);
3833
3834 // Update io.WantCaptureKeyboard for the user application (true = dispatch keyboard info to imgui, false = dispatch keyboard info to Dear ImGui + app)
3835 if (g.WantCaptureKeyboardNextFrame != -1)
3836 g.IO.WantCaptureKeyboard = (g.WantCaptureKeyboardNextFrame != 0);
3837 else
3838 g.IO.WantCaptureKeyboard = (g.ActiveId != 0) || (modal_window != NULL);
3839 if (g.IO.NavActive && (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) && !(g.IO.ConfigFlags & ImGuiConfigFlags_NavNoCaptureKeyboard))
3840 g.IO.WantCaptureKeyboard = true;
3841
3842 // Update io.WantTextInput flag, this is to allow systems without a keyboard (e.g. mobile, hand-held) to show a software keyboard if possible
3843 g.IO.WantTextInput = (g.WantTextInputNextFrame != -1) ? (g.WantTextInputNextFrame != 0) : false;
3844 }
3845
GetMergedKeyModFlags()3846 ImGuiKeyModFlags ImGui::GetMergedKeyModFlags()
3847 {
3848 ImGuiContext& g = *GImGui;
3849 ImGuiKeyModFlags key_mod_flags = ImGuiKeyModFlags_None;
3850 if (g.IO.KeyCtrl) { key_mod_flags |= ImGuiKeyModFlags_Ctrl; }
3851 if (g.IO.KeyShift) { key_mod_flags |= ImGuiKeyModFlags_Shift; }
3852 if (g.IO.KeyAlt) { key_mod_flags |= ImGuiKeyModFlags_Alt; }
3853 if (g.IO.KeySuper) { key_mod_flags |= ImGuiKeyModFlags_Super; }
3854 return key_mod_flags;
3855 }
3856
NewFrame()3857 void ImGui::NewFrame()
3858 {
3859 IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext() ?");
3860 ImGuiContext& g = *GImGui;
3861
3862 #ifdef IMGUI_ENABLE_TEST_ENGINE
3863 ImGuiTestEngineHook_PreNewFrame(&g);
3864 #endif
3865
3866 // Check and assert for various common IO and Configuration mistakes
3867 g.ConfigFlagsLastFrame = g.ConfigFlagsCurrFrame;
3868 ErrorCheckNewFrameSanityChecks();
3869 g.ConfigFlagsCurrFrame = g.IO.ConfigFlags;
3870
3871 // Load settings on first frame, save settings when modified (after a delay)
3872 UpdateSettings();
3873
3874 g.Time += g.IO.DeltaTime;
3875 g.WithinFrameScope = true;
3876 g.FrameCount += 1;
3877 g.TooltipOverrideCount = 0;
3878 g.WindowsActiveCount = 0;
3879 g.MenusIdSubmittedThisFrame.resize(0);
3880
3881 // Calculate frame-rate for the user, as a purely luxurious feature
3882 g.FramerateSecPerFrameAccum += g.IO.DeltaTime - g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx];
3883 g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx] = g.IO.DeltaTime;
3884 g.FramerateSecPerFrameIdx = (g.FramerateSecPerFrameIdx + 1) % IM_ARRAYSIZE(g.FramerateSecPerFrame);
3885 g.IO.Framerate = (g.FramerateSecPerFrameAccum > 0.0f) ? (1.0f / (g.FramerateSecPerFrameAccum / (float)IM_ARRAYSIZE(g.FramerateSecPerFrame))) : FLT_MAX;
3886
3887 UpdateViewportsNewFrame();
3888
3889 // Setup current font and draw list shared data
3890 // FIXME-VIEWPORT: the concept of a single ClipRectFullscreen is not ideal!
3891 g.IO.Fonts->Locked = true;
3892 SetCurrentFont(GetDefaultFont());
3893 IM_ASSERT(g.Font->IsLoaded());
3894 ImRect virtual_space(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX);
3895 for (int n = 0; n < g.Viewports.Size; n++)
3896 virtual_space.Add(g.Viewports[n]->GetMainRect());
3897 g.DrawListSharedData.ClipRectFullscreen = ImVec4(virtual_space.Min.x, virtual_space.Min.y, virtual_space.Max.x, virtual_space.Max.y);
3898 g.DrawListSharedData.CurveTessellationTol = g.Style.CurveTessellationTol;
3899 g.DrawListSharedData.SetCircleSegmentMaxError(g.Style.CircleSegmentMaxError);
3900 g.DrawListSharedData.InitialFlags = ImDrawListFlags_None;
3901 if (g.Style.AntiAliasedLines)
3902 g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLines;
3903 if (g.Style.AntiAliasedLinesUseTex && !(g.Font->ContainerAtlas->Flags & ImFontAtlasFlags_NoBakedLines))
3904 g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLinesUseTex;
3905 if (g.Style.AntiAliasedFill)
3906 g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedFill;
3907 if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset)
3908 g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AllowVtxOffset;
3909
3910 // Mark rendering data as invalid to prevent user who may have a handle on it to use it.
3911 for (int n = 0; n < g.Viewports.Size; n++)
3912 {
3913 ImGuiViewportP* viewport = g.Viewports[n];
3914 viewport->DrawData = NULL;
3915 viewport->DrawDataP.Clear();
3916 }
3917
3918 // Drag and drop keep the source ID alive so even if the source disappear our state is consistent
3919 if (g.DragDropActive && g.DragDropPayload.SourceId == g.ActiveId)
3920 KeepAliveID(g.DragDropPayload.SourceId);
3921
3922 // Update HoveredId data
3923 if (!g.HoveredIdPreviousFrame)
3924 g.HoveredIdTimer = 0.0f;
3925 if (!g.HoveredIdPreviousFrame || (g.HoveredId && g.ActiveId == g.HoveredId))
3926 g.HoveredIdNotActiveTimer = 0.0f;
3927 if (g.HoveredId)
3928 g.HoveredIdTimer += g.IO.DeltaTime;
3929 if (g.HoveredId && g.ActiveId != g.HoveredId)
3930 g.HoveredIdNotActiveTimer += g.IO.DeltaTime;
3931 g.HoveredIdPreviousFrame = g.HoveredId;
3932 g.HoveredId = 0;
3933 g.HoveredIdAllowOverlap = false;
3934 g.HoveredIdDisabled = false;
3935
3936 // Update ActiveId data (clear reference to active widget if the widget isn't alive anymore)
3937 if (g.ActiveIdIsAlive != g.ActiveId && g.ActiveIdPreviousFrame == g.ActiveId && g.ActiveId != 0)
3938 ClearActiveID();
3939 if (g.ActiveId)
3940 g.ActiveIdTimer += g.IO.DeltaTime;
3941 g.LastActiveIdTimer += g.IO.DeltaTime;
3942 g.ActiveIdPreviousFrame = g.ActiveId;
3943 g.ActiveIdPreviousFrameWindow = g.ActiveIdWindow;
3944 g.ActiveIdPreviousFrameHasBeenEditedBefore = g.ActiveIdHasBeenEditedBefore;
3945 g.ActiveIdIsAlive = 0;
3946 g.ActiveIdHasBeenEditedThisFrame = false;
3947 g.ActiveIdPreviousFrameIsAlive = false;
3948 g.ActiveIdIsJustActivated = false;
3949 if (g.TempInputId != 0 && g.ActiveId != g.TempInputId)
3950 g.TempInputId = 0;
3951 if (g.ActiveId == 0)
3952 {
3953 g.ActiveIdUsingNavDirMask = 0x00;
3954 g.ActiveIdUsingNavInputMask = 0x00;
3955 g.ActiveIdUsingKeyInputMask = 0x00;
3956 }
3957
3958 // Drag and drop
3959 g.DragDropAcceptIdPrev = g.DragDropAcceptIdCurr;
3960 g.DragDropAcceptIdCurr = 0;
3961 g.DragDropAcceptIdCurrRectSurface = FLT_MAX;
3962 g.DragDropWithinSource = false;
3963 g.DragDropWithinTarget = false;
3964 g.DragDropHoldJustPressedId = 0;
3965
3966 // Update keyboard input state
3967 // Synchronize io.KeyMods with individual modifiers io.KeyXXX bools
3968 g.IO.KeyMods = GetMergedKeyModFlags();
3969 memcpy(g.IO.KeysDownDurationPrev, g.IO.KeysDownDuration, sizeof(g.IO.KeysDownDuration));
3970 for (int i = 0; i < IM_ARRAYSIZE(g.IO.KeysDown); i++)
3971 g.IO.KeysDownDuration[i] = g.IO.KeysDown[i] ? (g.IO.KeysDownDuration[i] < 0.0f ? 0.0f : g.IO.KeysDownDuration[i] + g.IO.DeltaTime) : -1.0f;
3972
3973 // Update gamepad/keyboard navigation
3974 NavUpdate();
3975
3976 // Update mouse input state
3977 UpdateMouseInputs();
3978
3979 // Undocking
3980 // (needs to be before UpdateMouseMovingWindowNewFrame so the window is already offset and following the mouse on the detaching frame)
3981 DockContextUpdateUndocking(&g);
3982
3983 // Find hovered window
3984 // (needs to be before UpdateMouseMovingWindowNewFrame so we fill g.HoveredWindowUnderMovingWindow on the mouse release frame)
3985 UpdateHoveredWindowAndCaptureFlags();
3986
3987 // Handle user moving window with mouse (at the beginning of the frame to avoid input lag or sheering)
3988 UpdateMouseMovingWindowNewFrame();
3989
3990 // Background darkening/whitening
3991 if (GetTopMostPopupModal() != NULL || (g.NavWindowingTarget != NULL && g.NavWindowingHighlightAlpha > 0.0f))
3992 g.DimBgRatio = ImMin(g.DimBgRatio + g.IO.DeltaTime * 6.0f, 1.0f);
3993 else
3994 g.DimBgRatio = ImMax(g.DimBgRatio - g.IO.DeltaTime * 10.0f, 0.0f);
3995
3996 g.MouseCursor = ImGuiMouseCursor_Arrow;
3997 g.WantCaptureMouseNextFrame = g.WantCaptureKeyboardNextFrame = g.WantTextInputNextFrame = -1;
3998 g.PlatformImePos = ImVec2(1.0f, 1.0f); // OS Input Method Editor showing on top-left of our window by default
3999 g.PlatformImePosViewport = NULL;
4000
4001 // Mouse wheel scrolling, scale
4002 UpdateMouseWheel();
4003
4004 // Update legacy TAB focus
4005 UpdateTabFocus();
4006
4007 // Mark all windows as not visible and compact unused memory.
4008 IM_ASSERT(g.WindowsFocusOrder.Size == g.Windows.Size);
4009 const float memory_compact_start_time = (g.IO.ConfigWindowsMemoryCompactTimer >= 0.0f) ? (float)g.Time - g.IO.ConfigWindowsMemoryCompactTimer : FLT_MAX;
4010 for (int i = 0; i != g.Windows.Size; i++)
4011 {
4012 ImGuiWindow* window = g.Windows[i];
4013 window->WasActive = window->Active;
4014 window->BeginCount = 0;
4015 window->Active = false;
4016 window->WriteAccessed = false;
4017
4018 // Garbage collect transient buffers of recently unused windows
4019 if (!window->WasActive && !window->MemoryCompacted && window->LastTimeActive < memory_compact_start_time)
4020 GcCompactTransientWindowBuffers(window);
4021 }
4022
4023 // Closing the focused window restore focus to the first active root window in descending z-order
4024 if (g.NavWindow && !g.NavWindow->WasActive)
4025 FocusTopMostWindowUnderOne(NULL, NULL);
4026
4027 // No window should be open at the beginning of the frame.
4028 // But in order to allow the user to call NewFrame() multiple times without calling Render(), we are doing an explicit clear.
4029 g.CurrentWindowStack.resize(0);
4030 g.BeginPopupStack.resize(0);
4031 ClosePopupsOverWindow(g.NavWindow, false);
4032
4033 // Docking
4034 DockContextUpdateDocking(&g);
4035
4036 // [DEBUG] Item picker tool - start with DebugStartItemPicker() - useful to visually select an item and break into its call-stack.
4037 UpdateDebugToolItemPicker();
4038
4039 // Create implicit/fallback window - which we will only render it if the user has added something to it.
4040 // We don't use "Debug" to avoid colliding with user trying to create a "Debug" window with custom flags.
4041 // This fallback is particularly important as it avoid ImGui:: calls from crashing.
4042 g.WithinFrameScopeWithImplicitWindow = true;
4043 SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver);
4044 Begin("Debug##Default");
4045 IM_ASSERT(g.CurrentWindow->IsFallbackWindow == true);
4046
4047 #ifdef IMGUI_ENABLE_TEST_ENGINE
4048 ImGuiTestEngineHook_PostNewFrame(&g);
4049 #endif
4050 }
4051
4052 // [DEBUG] Item picker tool - start with DebugStartItemPicker() - useful to visually select an item and break into its call-stack.
UpdateDebugToolItemPicker()4053 void ImGui::UpdateDebugToolItemPicker()
4054 {
4055 ImGuiContext& g = *GImGui;
4056 g.DebugItemPickerBreakId = 0;
4057 if (g.DebugItemPickerActive)
4058 {
4059 const ImGuiID hovered_id = g.HoveredIdPreviousFrame;
4060 ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
4061 if (ImGui::IsKeyPressedMap(ImGuiKey_Escape))
4062 g.DebugItemPickerActive = false;
4063 if (ImGui::IsMouseClicked(0) && hovered_id)
4064 {
4065 g.DebugItemPickerBreakId = hovered_id;
4066 g.DebugItemPickerActive = false;
4067 }
4068 ImGui::SetNextWindowBgAlpha(0.60f);
4069 ImGui::BeginTooltip();
4070 ImGui::Text("HoveredId: 0x%08X", hovered_id);
4071 ImGui::Text("Press ESC to abort picking.");
4072 ImGui::TextColored(GetStyleColorVec4(hovered_id ? ImGuiCol_Text : ImGuiCol_TextDisabled), "Click to break in debugger!");
4073 ImGui::EndTooltip();
4074 }
4075 }
4076
Initialize(ImGuiContext * context)4077 void ImGui::Initialize(ImGuiContext* context)
4078 {
4079 ImGuiContext& g = *context;
4080 IM_ASSERT(!g.Initialized && !g.SettingsLoaded);
4081
4082 // Add .ini handle for ImGuiWindow type
4083 {
4084 ImGuiSettingsHandler ini_handler;
4085 ini_handler.TypeName = "Window";
4086 ini_handler.TypeHash = ImHashStr("Window");
4087 ini_handler.ClearAllFn = WindowSettingsHandler_ClearAll;
4088 ini_handler.ReadOpenFn = WindowSettingsHandler_ReadOpen;
4089 ini_handler.ReadLineFn = WindowSettingsHandler_ReadLine;
4090 ini_handler.ApplyAllFn = WindowSettingsHandler_ApplyAll;
4091 ini_handler.WriteAllFn = WindowSettingsHandler_WriteAll;
4092 g.SettingsHandlers.push_back(ini_handler);
4093 }
4094
4095 #ifdef IMGUI_HAS_TABLE
4096 // Add .ini handle for ImGuiTable type
4097 {
4098 ImGuiSettingsHandler ini_handler;
4099 ini_handler.TypeName = "Table";
4100 ini_handler.TypeHash = ImHashStr("Table");
4101 ini_handler.ReadOpenFn = TableSettingsHandler_ReadOpen;
4102 ini_handler.ReadLineFn = TableSettingsHandler_ReadLine;
4103 ini_handler.WriteAllFn = TableSettingsHandler_WriteAll;
4104 g.SettingsHandlers.push_back(ini_handler);
4105 }
4106 #endif // #ifdef IMGUI_HAS_TABLE
4107
4108 #ifdef IMGUI_HAS_DOCK
4109 // Create default viewport
4110 ImGuiViewportP* viewport = IM_NEW(ImGuiViewportP)();
4111 viewport->ID = IMGUI_VIEWPORT_DEFAULT_ID;
4112 viewport->Idx = 0;
4113 viewport->PlatformWindowCreated = true;
4114 g.Viewports.push_back(viewport);
4115 g.PlatformIO.MainViewport = g.Viewports[0]; // Make it accessible in public-facing GetPlatformIO() immediately (before the first call to EndFrame)
4116 g.PlatformIO.Viewports.push_back(g.Viewports[0]);
4117
4118 // Extensions
4119 DockContextInitialize(&g);
4120 #endif // #ifdef IMGUI_HAS_DOCK
4121
4122 g.Initialized = true;
4123 }
4124
4125 // This function is merely here to free heap allocations.
Shutdown(ImGuiContext * context)4126 void ImGui::Shutdown(ImGuiContext* context)
4127 {
4128 // 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)
4129 ImGuiContext& g = *context;
4130 if (g.IO.Fonts && g.FontAtlasOwnedByContext)
4131 {
4132 g.IO.Fonts->Locked = false;
4133 IM_DELETE(g.IO.Fonts);
4134 }
4135 g.IO.Fonts = NULL;
4136
4137 // Cleanup of other data are conditional on actually having initialized Dear ImGui.
4138 if (!g.Initialized)
4139 return;
4140
4141 // Save settings (unless we haven't attempted to load them: CreateContext/DestroyContext without a call to NewFrame shouldn't save an empty file)
4142 if (g.SettingsLoaded && g.IO.IniFilename != NULL)
4143 {
4144 ImGuiContext* backup_context = GImGui;
4145 SetCurrentContext(context);
4146 SaveIniSettingsToDisk(g.IO.IniFilename);
4147 SetCurrentContext(backup_context);
4148 }
4149
4150 // Destroy platform windows
4151 ImGuiContext* backup_context = ImGui::GetCurrentContext();
4152 SetCurrentContext(context);
4153 DestroyPlatformWindows();
4154 SetCurrentContext(backup_context);
4155
4156 // Shutdown extensions
4157 DockContextShutdown(&g);
4158
4159 // Notify hooked test engine, if any
4160 #ifdef IMGUI_ENABLE_TEST_ENGINE
4161 ImGuiTestEngineHook_Shutdown(context);
4162 #endif
4163
4164 // Clear everything else
4165 for (int i = 0; i < g.Windows.Size; i++)
4166 IM_DELETE(g.Windows[i]);
4167 g.Windows.clear();
4168 g.WindowsFocusOrder.clear();
4169 g.WindowsTempSortBuffer.clear();
4170 g.CurrentWindow = NULL;
4171 g.CurrentWindowStack.clear();
4172 g.WindowsById.Clear();
4173 g.NavWindow = NULL;
4174 g.HoveredWindow = g.HoveredRootWindow = g.HoveredWindowUnderMovingWindow = NULL;
4175 g.ActiveIdWindow = g.ActiveIdPreviousFrameWindow = NULL;
4176 g.MovingWindow = NULL;
4177 g.ColorModifiers.clear();
4178 g.StyleModifiers.clear();
4179 g.FontStack.clear();
4180 g.OpenPopupStack.clear();
4181 g.BeginPopupStack.clear();
4182
4183 g.CurrentViewport = g.MouseViewport = g.MouseLastHoveredViewport = NULL;
4184 for (int i = 0; i < g.Viewports.Size; i++)
4185 IM_DELETE(g.Viewports[i]);
4186 g.Viewports.clear();
4187
4188 g.TabBars.Clear();
4189 g.CurrentTabBarStack.clear();
4190 g.ShrinkWidthBuffer.clear();
4191
4192 g.ClipboardHandlerData.clear();
4193 g.MenusIdSubmittedThisFrame.clear();
4194 g.InputTextState.ClearFreeMemory();
4195
4196 g.SettingsWindows.clear();
4197 g.SettingsHandlers.clear();
4198
4199 if (g.LogFile)
4200 {
4201 #ifndef IMGUI_DISABLE_TTY_FUNCTIONS
4202 if (g.LogFile != stdout)
4203 #endif
4204 ImFileClose(g.LogFile);
4205 g.LogFile = NULL;
4206 }
4207 g.LogBuffer.clear();
4208
4209 g.Initialized = false;
4210 }
4211
4212 // FIXME: Add a more explicit sort order in the window structure.
ChildWindowComparer(const void * lhs,const void * rhs)4213 static int IMGUI_CDECL ChildWindowComparer(const void* lhs, const void* rhs)
4214 {
4215 const ImGuiWindow* const a = *(const ImGuiWindow* const *)lhs;
4216 const ImGuiWindow* const b = *(const ImGuiWindow* const *)rhs;
4217 if (int d = (a->Flags & ImGuiWindowFlags_Popup) - (b->Flags & ImGuiWindowFlags_Popup))
4218 return d;
4219 if (int d = (a->Flags & ImGuiWindowFlags_Tooltip) - (b->Flags & ImGuiWindowFlags_Tooltip))
4220 return d;
4221 return (a->BeginOrderWithinParent - b->BeginOrderWithinParent);
4222 }
4223
AddWindowToSortBuffer(ImVector<ImGuiWindow * > * out_sorted_windows,ImGuiWindow * window)4224 static void AddWindowToSortBuffer(ImVector<ImGuiWindow*>* out_sorted_windows, ImGuiWindow* window)
4225 {
4226 out_sorted_windows->push_back(window);
4227 if (window->Active)
4228 {
4229 int count = window->DC.ChildWindows.Size;
4230 if (count > 1)
4231 ImQsort(window->DC.ChildWindows.Data, (size_t)count, sizeof(ImGuiWindow*), ChildWindowComparer);
4232 for (int i = 0; i < count; i++)
4233 {
4234 ImGuiWindow* child = window->DC.ChildWindows[i];
4235 if (child->Active)
4236 AddWindowToSortBuffer(out_sorted_windows, child);
4237 }
4238 }
4239 }
4240
AddDrawListToDrawData(ImVector<ImDrawList * > * out_list,ImDrawList * draw_list)4241 static void AddDrawListToDrawData(ImVector<ImDrawList*>* out_list, ImDrawList* draw_list)
4242 {
4243 // Remove trailing command if unused.
4244 // Technically we could return directly instead of popping, but this make things looks neat in Metrics window as well.
4245 draw_list->_PopUnusedDrawCmd();
4246 if (draw_list->CmdBuffer.Size == 0)
4247 return;
4248
4249 // Draw list sanity check. Detect mismatch between PrimReserve() calls and incrementing _VtxCurrentIdx, _VtxWritePtr etc.
4250 // May trigger for you if you are using PrimXXX functions incorrectly.
4251 IM_ASSERT(draw_list->VtxBuffer.Size == 0 || draw_list->_VtxWritePtr == draw_list->VtxBuffer.Data + draw_list->VtxBuffer.Size);
4252 IM_ASSERT(draw_list->IdxBuffer.Size == 0 || draw_list->_IdxWritePtr == draw_list->IdxBuffer.Data + draw_list->IdxBuffer.Size);
4253 if (!(draw_list->Flags & ImDrawListFlags_AllowVtxOffset))
4254 IM_ASSERT((int)draw_list->_VtxCurrentIdx == draw_list->VtxBuffer.Size);
4255
4256 // Check that draw_list doesn't use more vertices than indexable (default ImDrawIdx = unsigned short = 2 bytes = 64K vertices per ImDrawList = per window)
4257 // If this assert triggers because you are drawing lots of stuff manually:
4258 // - First, make sure you are coarse clipping yourself and not trying to draw many things outside visible bounds.
4259 // Be mindful that the ImDrawList API doesn't filter vertices. Use the Metrics window to inspect draw list contents.
4260 // - If you want large meshes with more than 64K vertices, you can either:
4261 // (A) Handle the ImDrawCmd::VtxOffset value in your renderer back-end, and set 'io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset'.
4262 // Most example back-ends already support this from 1.71. Pre-1.71 back-ends won't.
4263 // 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.
4264 // (B) Or handle 32-bit indices in your renderer back-end, and uncomment '#define ImDrawIdx unsigned int' line in imconfig.h.
4265 // Most example back-ends already support this. For example, the OpenGL example code detect index size at compile-time:
4266 // glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer_offset);
4267 // Your own engine or render API may use different parameters or function calls to specify index sizes.
4268 // 2 and 4 bytes indices are generally supported by most graphics API.
4269 // - If for some reason neither of those solutions works for you, a workaround is to call BeginChild()/EndChild() before reaching
4270 // the 64K limit to split your draw commands in multiple draw lists.
4271 if (sizeof(ImDrawIdx) == 2)
4272 IM_ASSERT(draw_list->_VtxCurrentIdx < (1 << 16) && "Too many vertices in ImDrawList using 16-bit indices. Read comment above");
4273
4274 out_list->push_back(draw_list);
4275 }
4276
AddWindowToDrawData(ImGuiWindow * window,int layer)4277 static void AddWindowToDrawData(ImGuiWindow* window, int layer)
4278 {
4279 ImGuiContext& g = *GImGui;
4280 g.IO.MetricsRenderWindows++;
4281 AddDrawListToDrawData(&window->Viewport->DrawDataBuilder.Layers[layer], window->DrawList);
4282 for (int i = 0; i < window->DC.ChildWindows.Size; i++)
4283 {
4284 ImGuiWindow* child = window->DC.ChildWindows[i];
4285 if (IsWindowActiveAndVisible(child)) // Clipped children may have been marked not active
4286 AddWindowToDrawData(child, layer);
4287 }
4288 }
4289
4290 // Layer is locked for the root window, however child windows may use a different viewport (e.g. extruding menu)
AddRootWindowToDrawData(ImGuiWindow * window)4291 static void AddRootWindowToDrawData(ImGuiWindow* window)
4292 {
4293 int layer = (window->Flags & ImGuiWindowFlags_Tooltip) ? 1 : 0;
4294 AddWindowToDrawData(window, layer);
4295 }
4296
FlattenIntoSingleLayer()4297 void ImDrawDataBuilder::FlattenIntoSingleLayer()
4298 {
4299 int n = Layers[0].Size;
4300 int size = n;
4301 for (int i = 1; i < IM_ARRAYSIZE(Layers); i++)
4302 size += Layers[i].Size;
4303 Layers[0].resize(size);
4304 for (int layer_n = 1; layer_n < IM_ARRAYSIZE(Layers); layer_n++)
4305 {
4306 ImVector<ImDrawList*>& layer = Layers[layer_n];
4307 if (layer.empty())
4308 continue;
4309 memcpy(&Layers[0][n], &layer[0], layer.Size * sizeof(ImDrawList*));
4310 n += layer.Size;
4311 layer.resize(0);
4312 }
4313 }
4314
SetupViewportDrawData(ImGuiViewportP * viewport,ImVector<ImDrawList * > * draw_lists)4315 static void SetupViewportDrawData(ImGuiViewportP* viewport, ImVector<ImDrawList*>* draw_lists)
4316 {
4317 // When minimized, we report draw_data->DisplaySize as zero to be consistent with non-viewport mode,
4318 // and to allow applications/back-ends to easily skip rendering.
4319 // FIXME: Note that we however do NOT attempt to report "zero drawlist / vertices" into the ImDrawData structure.
4320 // This is because the work has been done already, and its wasted! We should fix that and add optimizations for
4321 // it earlier in the pipeline, rather than pretend to hide the data at the end of the pipeline.
4322 const bool is_minimized = (viewport->Flags & ImGuiViewportFlags_Minimized) != 0;
4323
4324 ImDrawData* draw_data = &viewport->DrawDataP;
4325 viewport->DrawData = draw_data; // Make publicly accessible
4326 draw_data->Valid = true;
4327 draw_data->CmdLists = (draw_lists->Size > 0) ? draw_lists->Data : NULL;
4328 draw_data->CmdListsCount = draw_lists->Size;
4329 draw_data->TotalVtxCount = draw_data->TotalIdxCount = 0;
4330 draw_data->DisplayPos = viewport->Pos;
4331 draw_data->DisplaySize = is_minimized ? ImVec2(0.0f, 0.0f) : viewport->Size;
4332 draw_data->FramebufferScale = ImGui::GetIO().DisplayFramebufferScale; // FIXME-VIEWPORT: This may vary on a per-monitor/viewport basis?
4333 draw_data->OwnerViewport = viewport;
4334 for (int n = 0; n < draw_lists->Size; n++)
4335 {
4336 draw_data->TotalVtxCount += draw_lists->Data[n]->VtxBuffer.Size;
4337 draw_data->TotalIdxCount += draw_lists->Data[n]->IdxBuffer.Size;
4338 }
4339 }
4340
4341 // Push a clipping rectangle for both ImGui logic (hit-testing etc.) and low-level ImDrawList rendering.
4342 // - When using this function it is sane to ensure that float are perfectly rounded to integer values,
4343 // so that e.g. (int)(max.x-min.x) in user's render produce correct result.
4344 // - If the code here changes, may need to update code of functions like NextColumn() and PushColumnClipRect():
4345 // some frequently called functions which to modify both channels and clipping simultaneously tend to use the
4346 // more specialized SetWindowClipRectBeforeSetChannel() to avoid extraneous updates of underlying ImDrawCmds.
PushClipRect(const ImVec2 & clip_rect_min,const ImVec2 & clip_rect_max,bool intersect_with_current_clip_rect)4347 void ImGui::PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect)
4348 {
4349 ImGuiWindow* window = GetCurrentWindow();
4350 window->DrawList->PushClipRect(clip_rect_min, clip_rect_max, intersect_with_current_clip_rect);
4351 window->ClipRect = window->DrawList->_ClipRectStack.back();
4352 }
4353
PopClipRect()4354 void ImGui::PopClipRect()
4355 {
4356 ImGuiWindow* window = GetCurrentWindow();
4357 window->DrawList->PopClipRect();
4358 window->ClipRect = window->DrawList->_ClipRectStack.back();
4359 }
4360
FindFrontMostVisibleChildWindow(ImGuiWindow * window)4361 static ImGuiWindow* FindFrontMostVisibleChildWindow(ImGuiWindow* window)
4362 {
4363 for (int n = window->DC.ChildWindows.Size - 1; n >= 0; n--)
4364 if (IsWindowActiveAndVisible(window->DC.ChildWindows[n]))
4365 return FindFrontMostVisibleChildWindow(window->DC.ChildWindows[n]);
4366 return window;
4367 }
4368
EndFrameDrawDimmedBackgrounds()4369 static void ImGui::EndFrameDrawDimmedBackgrounds()
4370 {
4371 ImGuiContext& g = *GImGui;
4372
4373 // Draw modal whitening background on _other_ viewports than the one the modal is one
4374 ImGuiWindow* modal_window = GetTopMostPopupModal();
4375 const bool dim_bg_for_modal = (modal_window != NULL);
4376 const bool dim_bg_for_window_list = (g.NavWindowingTargetAnim != NULL);
4377 if (dim_bg_for_modal || dim_bg_for_window_list)
4378 for (int viewport_n = 0; viewport_n < g.Viewports.Size; viewport_n++)
4379 {
4380 ImGuiViewportP* viewport = g.Viewports[viewport_n];
4381 if (modal_window && viewport == modal_window->Viewport)
4382 continue;
4383 if (g.NavWindowingListWindow && viewport == g.NavWindowingListWindow->Viewport)
4384 continue;
4385 if (g.NavWindowingTargetAnim && viewport == g.NavWindowingTargetAnim->Viewport)
4386 continue;
4387 ImDrawList* draw_list = GetForegroundDrawList(viewport);
4388 const ImU32 dim_bg_col = GetColorU32(dim_bg_for_modal ? ImGuiCol_ModalWindowDimBg : ImGuiCol_NavWindowingDimBg, g.DimBgRatio);
4389 draw_list->AddRectFilled(viewport->Pos, viewport->Pos + viewport->Size, dim_bg_col);
4390 }
4391
4392 // Draw modal whitening background between CTRL-TAB list
4393 if (dim_bg_for_window_list && g.NavWindowingTargetAnim->Active)
4394 {
4395 // Choose a draw list that will be front-most across all our children
4396 // In the unlikely case that the window wasn't made active we can't rely on its drawlist and skip rendering all-together.
4397 ImGuiWindow* window = g.NavWindowingTargetAnim;
4398 ImDrawList* draw_list = FindFrontMostVisibleChildWindow(window->RootWindow)->DrawList;
4399 draw_list->PushClipRectFullScreen();
4400
4401 // Docking: draw modal whitening background on other nodes of a same dock tree
4402 // For CTRL+TAB within a docking node we need to render the dimming background in 8 steps
4403 // (Because the root node renders the background in one shot, in order to avoid flickering when a child dock node is not submitted)
4404 if (window->RootWindowDockStop->DockIsActive)
4405 if (window->RootWindow != window->RootWindowDockStop)
4406 RenderRectFilledWithHole(draw_list, window->RootWindow->Rect(), window->RootWindowDockStop->Rect(), GetColorU32(ImGuiCol_NavWindowingDimBg, g.DimBgRatio), g.Style.WindowRounding);
4407
4408 // Draw navigation selection/windowing rectangle border
4409 float rounding = ImMax(window->WindowRounding, g.Style.WindowRounding);
4410 ImRect bb = window->Rect();
4411 bb.Expand(g.FontSize);
4412 if (bb.Contains(window->Viewport->GetMainRect())) // If a window fits the entire viewport, adjust its highlight inward
4413 {
4414 bb.Expand(-g.FontSize - 1.0f);
4415 rounding = window->WindowRounding;
4416 }
4417 draw_list->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha), rounding, ~0, 3.0f);
4418 draw_list->PopClipRect();
4419 }
4420 }
4421
4422 // 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.
EndFrame()4423 void ImGui::EndFrame()
4424 {
4425 ImGuiContext& g = *GImGui;
4426 IM_ASSERT(g.Initialized);
4427
4428 // Don't process EndFrame() multiple times.
4429 if (g.FrameCountEnded == g.FrameCount)
4430 return;
4431 IM_ASSERT(g.WithinFrameScope && "Forgot to call ImGui::NewFrame()?");
4432
4433 ErrorCheckEndFrameSanityChecks();
4434
4435 // Notify OS when our Input Method Editor cursor has moved (e.g. CJK inputs using Microsoft IME)
4436 if (g.PlatformIO.Platform_SetImeInputPos && (g.PlatformImeLastPos.x == FLT_MAX || ImLengthSqr(g.PlatformImePos - g.PlatformImeLastPos) > 0.0001f))
4437 if (g.PlatformImePosViewport && g.PlatformImePosViewport->PlatformWindowCreated)
4438 {
4439 g.PlatformIO.Platform_SetImeInputPos(g.PlatformImePosViewport, g.PlatformImePos);
4440 g.PlatformImeLastPos = g.PlatformImePos;
4441 g.PlatformImePosViewport = NULL;
4442 }
4443
4444 // Hide implicit/fallback "Debug" window if it hasn't been used
4445 g.WithinFrameScopeWithImplicitWindow = false;
4446 if (g.CurrentWindow && !g.CurrentWindow->WriteAccessed)
4447 g.CurrentWindow->Active = false;
4448 End();
4449
4450 // Draw modal whitening background on _other_ viewports than the one the modal is one
4451 EndFrameDrawDimmedBackgrounds();
4452
4453 // Update navigation: CTRL+Tab, wrap-around requests
4454 NavEndFrame();
4455
4456 SetCurrentViewport(NULL, NULL);
4457
4458 // Drag and Drop: Elapse payload (if delivered, or if source stops being submitted)
4459 if (g.DragDropActive)
4460 {
4461 bool is_delivered = g.DragDropPayload.Delivery;
4462 bool is_elapsed = (g.DragDropPayload.DataFrameCount + 1 < g.FrameCount) && ((g.DragDropSourceFlags & ImGuiDragDropFlags_SourceAutoExpirePayload) || !IsMouseDown(g.DragDropMouseButton));
4463 if (is_delivered || is_elapsed)
4464 ClearDragDrop();
4465 }
4466
4467 // Drag and Drop: Fallback for source tooltip. This is not ideal but better than nothing.
4468 if (g.DragDropActive && g.DragDropSourceFrameCount < g.FrameCount && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoPreviewTooltip))
4469 {
4470 g.DragDropWithinSource = true;
4471 SetTooltip("...");
4472 g.DragDropWithinSource = false;
4473 }
4474
4475 // End frame
4476 g.WithinFrameScope = false;
4477 g.FrameCountEnded = g.FrameCount;
4478
4479 // Initiate moving window + handle left-click and right-click focus
4480 UpdateMouseMovingWindowEndFrame();
4481
4482 // Update user-facing viewport list (g.Viewports -> g.PlatformIO.Viewports after filtering out some)
4483 UpdateViewportsEndFrame();
4484
4485 // Sort the window list so that all child windows are after their parent
4486 // We cannot do that on FocusWindow() because children may not exist yet
4487 g.WindowsTempSortBuffer.resize(0);
4488 g.WindowsTempSortBuffer.reserve(g.Windows.Size);
4489 for (int i = 0; i != g.Windows.Size; i++)
4490 {
4491 ImGuiWindow* window = g.Windows[i];
4492 if (window->Active && (window->Flags & ImGuiWindowFlags_ChildWindow)) // if a child is active its parent will add it
4493 continue;
4494 AddWindowToSortBuffer(&g.WindowsTempSortBuffer, window);
4495 }
4496
4497 // 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.
4498 IM_ASSERT(g.Windows.Size == g.WindowsTempSortBuffer.Size);
4499 g.Windows.swap(g.WindowsTempSortBuffer);
4500 g.IO.MetricsActiveWindows = g.WindowsActiveCount;
4501
4502 // Unlock font atlas
4503 g.IO.Fonts->Locked = false;
4504
4505 // Clear Input data for next frame
4506 g.IO.MouseWheel = g.IO.MouseWheelH = 0.0f;
4507 g.IO.InputQueueCharacters.resize(0);
4508 memset(g.IO.NavInputs, 0, sizeof(g.IO.NavInputs));
4509 }
4510
Render()4511 void ImGui::Render()
4512 {
4513 ImGuiContext& g = *GImGui;
4514 IM_ASSERT(g.Initialized);
4515
4516 if (g.FrameCountEnded != g.FrameCount)
4517 EndFrame();
4518 g.FrameCountRendered = g.FrameCount;
4519 g.IO.MetricsRenderWindows = 0;
4520
4521 // Add background ImDrawList (for each active viewport)
4522 for (int n = 0; n != g.Viewports.Size; n++)
4523 {
4524 ImGuiViewportP* viewport = g.Viewports[n];
4525 viewport->DrawDataBuilder.Clear();
4526 if (viewport->DrawLists[0] != NULL)
4527 AddDrawListToDrawData(&viewport->DrawDataBuilder.Layers[0], GetBackgroundDrawList(viewport));
4528 }
4529
4530 // Add ImDrawList to render
4531 ImGuiWindow* windows_to_render_top_most[2];
4532 windows_to_render_top_most[0] = (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus)) ? g.NavWindowingTarget->RootWindow : NULL;
4533 windows_to_render_top_most[1] = (g.NavWindowingTarget ? g.NavWindowingListWindow : NULL);
4534 for (int n = 0; n != g.Windows.Size; n++)
4535 {
4536 ImGuiWindow* window = g.Windows[n];
4537 if (IsWindowActiveAndVisible(window) && (window->Flags & ImGuiWindowFlags_ChildWindow) == 0 && window != windows_to_render_top_most[0] && window != windows_to_render_top_most[1])
4538 AddRootWindowToDrawData(window);
4539 }
4540 for (int n = 0; n < IM_ARRAYSIZE(windows_to_render_top_most); n++)
4541 if (windows_to_render_top_most[n] && IsWindowActiveAndVisible(windows_to_render_top_most[n])) // NavWindowingTarget is always temporarily displayed as the top-most window
4542 AddRootWindowToDrawData(windows_to_render_top_most[n]);
4543
4544 ImVec2 mouse_cursor_offset, mouse_cursor_size, mouse_cursor_uv[4];
4545 if (g.IO.MouseDrawCursor && g.MouseCursor != ImGuiMouseCursor_None)
4546 g.IO.Fonts->GetMouseCursorTexData(g.MouseCursor, &mouse_cursor_offset, &mouse_cursor_size, &mouse_cursor_uv[0], &mouse_cursor_uv[2]);
4547
4548 // Setup ImDrawData structures for end-user
4549 g.IO.MetricsRenderVertices = g.IO.MetricsRenderIndices = 0;
4550 for (int n = 0; n < g.Viewports.Size; n++)
4551 {
4552 ImGuiViewportP* viewport = g.Viewports[n];
4553 viewport->DrawDataBuilder.FlattenIntoSingleLayer();
4554
4555 // Draw software mouse cursor if requested by io.MouseDrawCursor flag
4556 // (note we scale cursor by current viewport/monitor, however Windows 10 for its own hardware cursor seems to be using a different scale factor)
4557 if (mouse_cursor_size.x > 0.0f && mouse_cursor_size.y > 0.0f)
4558 {
4559 float scale = g.Style.MouseCursorScale * viewport->DpiScale;
4560 if (viewport->GetMainRect().Overlaps(ImRect(g.IO.MousePos, g.IO.MousePos + ImVec2(mouse_cursor_size.x + 2, mouse_cursor_size.y + 2) * scale)))
4561 RenderMouseCursor(GetForegroundDrawList(viewport), g.IO.MousePos, scale, g.MouseCursor, IM_COL32_WHITE, IM_COL32_BLACK, IM_COL32(0, 0, 0, 48));
4562 }
4563
4564 // Add foreground ImDrawList (for each active viewport)
4565 if (viewport->DrawLists[1] != NULL)
4566 AddDrawListToDrawData(&viewport->DrawDataBuilder.Layers[0], GetForegroundDrawList(viewport));
4567
4568 SetupViewportDrawData(viewport, &viewport->DrawDataBuilder.Layers[0]);
4569 g.IO.MetricsRenderVertices += viewport->DrawData->TotalVtxCount;
4570 g.IO.MetricsRenderIndices += viewport->DrawData->TotalIdxCount;
4571 }
4572
4573 // (Legacy) Call the Render callback function. The current prefer way is to let the user retrieve GetDrawData() and call the render function themselves.
4574 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
4575 if (g.Viewports[0]->DrawData->CmdListsCount > 0 && g.IO.RenderDrawListsFn != NULL)
4576 g.IO.RenderDrawListsFn(g.Viewports[0]->DrawData);
4577 #endif
4578 }
4579
4580 // Calculate text size. Text can be multi-line. Optionally ignore text after a ## marker.
4581 // CalcTextSize("") should return ImVec2(0.0f, g.FontSize)
CalcTextSize(const char * text,const char * text_end,bool hide_text_after_double_hash,float wrap_width)4582 ImVec2 ImGui::CalcTextSize(const char* text, const char* text_end, bool hide_text_after_double_hash, float wrap_width)
4583 {
4584 ImGuiContext& g = *GImGui;
4585
4586 const char* text_display_end;
4587 if (hide_text_after_double_hash)
4588 text_display_end = FindRenderedTextEnd(text, text_end); // Hide anything after a '##' string
4589 else
4590 text_display_end = text_end;
4591
4592 ImFont* font = g.Font;
4593 const float font_size = g.FontSize;
4594 if (text == text_display_end)
4595 return ImVec2(0.0f, font_size);
4596 ImVec2 text_size = font->CalcTextSizeA(font_size, FLT_MAX, wrap_width, text, text_display_end, NULL);
4597
4598 // Round
4599 text_size.x = IM_FLOOR(text_size.x + 0.95f);
4600
4601 return text_size;
4602 }
4603
4604 // Find window given position, search front-to-back
4605 // FIXME: Note that we have an inconsequential lag here: OuterRectClipped is updated in Begin(), so windows moved programmatically
4606 // with SetWindowPos() and not SetNextWindowPos() will have that rectangle lagging by a frame at the time FindHoveredWindow() is
4607 // called, aka before the next Begin(). Moving window isn't affected.
FindHoveredWindow()4608 static void FindHoveredWindow()
4609 {
4610 ImGuiContext& g = *GImGui;
4611
4612 // Special handling for the window being moved: Ignore the mouse viewport check (because it may reset/lose its viewport during the undocking frame)
4613 ImGuiViewportP* moving_window_viewport = g.MovingWindow ? g.MovingWindow->Viewport : NULL;
4614 if (g.MovingWindow)
4615 g.MovingWindow->Viewport = g.MouseViewport;
4616
4617 ImGuiWindow* hovered_window = NULL;
4618 ImGuiWindow* hovered_window_ignoring_moving_window = NULL;
4619 if (g.MovingWindow && !(g.MovingWindow->Flags & ImGuiWindowFlags_NoMouseInputs))
4620 hovered_window = g.MovingWindow;
4621
4622 ImVec2 padding_regular = g.Style.TouchExtraPadding;
4623 ImVec2 padding_for_resize_from_edges = g.IO.ConfigWindowsResizeFromEdges ? ImMax(g.Style.TouchExtraPadding, ImVec2(WINDOWS_RESIZE_FROM_EDGES_HALF_THICKNESS, WINDOWS_RESIZE_FROM_EDGES_HALF_THICKNESS)) : padding_regular;
4624 for (int i = g.Windows.Size - 1; i >= 0; i--)
4625 {
4626 ImGuiWindow* window = g.Windows[i];
4627 if (!window->Active || window->Hidden)
4628 continue;
4629 if (window->Flags & ImGuiWindowFlags_NoMouseInputs)
4630 continue;
4631 IM_ASSERT(window->Viewport);
4632 if (window->Viewport != g.MouseViewport)
4633 continue;
4634
4635 // Using the clipped AABB, a child window will typically be clipped by its parent (not always)
4636 ImRect bb(window->OuterRectClipped);
4637 if (window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize))
4638 bb.Expand(padding_regular);
4639 else
4640 bb.Expand(padding_for_resize_from_edges);
4641 if (!bb.Contains(g.IO.MousePos))
4642 continue;
4643
4644 // Support for one rectangular hole in any given window
4645 // FIXME: Consider generalizing hit-testing override (with more generic data, callback, etc.) (#1512)
4646 if (window->HitTestHoleSize.x != 0)
4647 {
4648 ImVec2 hole_pos(window->Pos.x + (float)window->HitTestHoleOffset.x, window->Pos.y + (float)window->HitTestHoleOffset.y);
4649 ImVec2 hole_size((float)window->HitTestHoleSize.x, (float)window->HitTestHoleSize.y);
4650 if (ImRect(hole_pos, hole_pos + hole_size).Contains(g.IO.MousePos))
4651 continue;
4652 }
4653
4654 if (hovered_window == NULL)
4655 hovered_window = window;
4656 if (hovered_window_ignoring_moving_window == NULL && (!g.MovingWindow || window->RootWindow != g.MovingWindow->RootWindow))
4657 hovered_window_ignoring_moving_window = window;
4658 if (hovered_window && hovered_window_ignoring_moving_window)
4659 break;
4660 }
4661
4662 g.HoveredWindow = hovered_window;
4663 g.HoveredRootWindow = g.HoveredWindow ? g.HoveredWindow->RootWindow : NULL;
4664 g.HoveredWindowUnderMovingWindow = hovered_window_ignoring_moving_window;
4665
4666 if (g.MovingWindow)
4667 g.MovingWindow->Viewport = moving_window_viewport;
4668 }
4669
4670 // Test if mouse cursor is hovering given rectangle
4671 // NB- Rectangle is clipped by our current clip setting
4672 // NB- Expand the rectangle to be generous on imprecise inputs systems (g.Style.TouchExtraPadding)
IsMouseHoveringRect(const ImVec2 & r_min,const ImVec2 & r_max,bool clip)4673 bool ImGui::IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool clip)
4674 {
4675 ImGuiContext& g = *GImGui;
4676
4677 // Clip
4678 ImRect rect_clipped(r_min, r_max);
4679 if (clip)
4680 rect_clipped.ClipWith(g.CurrentWindow->ClipRect);
4681
4682 // Expand for touch input
4683 const ImRect rect_for_touch(rect_clipped.Min - g.Style.TouchExtraPadding, rect_clipped.Max + g.Style.TouchExtraPadding);
4684 if (!rect_for_touch.Contains(g.IO.MousePos))
4685 return false;
4686 if (!g.MouseViewport->GetMainRect().Overlaps(rect_clipped))
4687 return false;
4688 return true;
4689 }
4690
GetKeyIndex(ImGuiKey imgui_key)4691 int ImGui::GetKeyIndex(ImGuiKey imgui_key)
4692 {
4693 IM_ASSERT(imgui_key >= 0 && imgui_key < ImGuiKey_COUNT);
4694 ImGuiContext& g = *GImGui;
4695 return g.IO.KeyMap[imgui_key];
4696 }
4697
4698 // Note that dear imgui doesn't know the semantic of each entry of io.KeysDown[]!
4699 // Use your own indices/enums according to how your back-end/engine stored them into io.KeysDown[]!
IsKeyDown(int user_key_index)4700 bool ImGui::IsKeyDown(int user_key_index)
4701 {
4702 if (user_key_index < 0)
4703 return false;
4704 ImGuiContext& g = *GImGui;
4705 IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(g.IO.KeysDown));
4706 return g.IO.KeysDown[user_key_index];
4707 }
4708
4709 // t0 = previous time (e.g.: g.Time - g.IO.DeltaTime)
4710 // t1 = current time (e.g.: g.Time)
4711 // An event is triggered at:
4712 // t = 0.0f t = repeat_delay, t = repeat_delay + repeat_rate*N
CalcTypematicRepeatAmount(float t0,float t1,float repeat_delay,float repeat_rate)4713 int ImGui::CalcTypematicRepeatAmount(float t0, float t1, float repeat_delay, float repeat_rate)
4714 {
4715 if (t1 == 0.0f)
4716 return 1;
4717 if (t0 >= t1)
4718 return 0;
4719 if (repeat_rate <= 0.0f)
4720 return (t0 < repeat_delay) && (t1 >= repeat_delay);
4721 const int count_t0 = (t0 < repeat_delay) ? -1 : (int)((t0 - repeat_delay) / repeat_rate);
4722 const int count_t1 = (t1 < repeat_delay) ? -1 : (int)((t1 - repeat_delay) / repeat_rate);
4723 const int count = count_t1 - count_t0;
4724 return count;
4725 }
4726
GetKeyPressedAmount(int key_index,float repeat_delay,float repeat_rate)4727 int ImGui::GetKeyPressedAmount(int key_index, float repeat_delay, float repeat_rate)
4728 {
4729 ImGuiContext& g = *GImGui;
4730 if (key_index < 0)
4731 return 0;
4732 IM_ASSERT(key_index >= 0 && key_index < IM_ARRAYSIZE(g.IO.KeysDown));
4733 const float t = g.IO.KeysDownDuration[key_index];
4734 return CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, repeat_delay, repeat_rate);
4735 }
4736
IsKeyPressed(int user_key_index,bool repeat)4737 bool ImGui::IsKeyPressed(int user_key_index, bool repeat)
4738 {
4739 ImGuiContext& g = *GImGui;
4740 if (user_key_index < 0)
4741 return false;
4742 IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(g.IO.KeysDown));
4743 const float t = g.IO.KeysDownDuration[user_key_index];
4744 if (t == 0.0f)
4745 return true;
4746 if (repeat && t > g.IO.KeyRepeatDelay)
4747 return GetKeyPressedAmount(user_key_index, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0;
4748 return false;
4749 }
4750
IsKeyReleased(int user_key_index)4751 bool ImGui::IsKeyReleased(int user_key_index)
4752 {
4753 ImGuiContext& g = *GImGui;
4754 if (user_key_index < 0) return false;
4755 IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(g.IO.KeysDown));
4756 return g.IO.KeysDownDurationPrev[user_key_index] >= 0.0f && !g.IO.KeysDown[user_key_index];
4757 }
4758
IsMouseDown(ImGuiMouseButton button)4759 bool ImGui::IsMouseDown(ImGuiMouseButton button)
4760 {
4761 ImGuiContext& g = *GImGui;
4762 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
4763 return g.IO.MouseDown[button];
4764 }
4765
IsMouseClicked(ImGuiMouseButton button,bool repeat)4766 bool ImGui::IsMouseClicked(ImGuiMouseButton button, bool repeat)
4767 {
4768 ImGuiContext& g = *GImGui;
4769 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
4770 const float t = g.IO.MouseDownDuration[button];
4771 if (t == 0.0f)
4772 return true;
4773
4774 if (repeat && t > g.IO.KeyRepeatDelay)
4775 {
4776 // FIXME: 2019/05/03: Our old repeat code was wrong here and led to doubling the repeat rate, which made it an ok rate for repeat on mouse hold.
4777 int amount = CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate * 0.50f);
4778 if (amount > 0)
4779 return true;
4780 }
4781 return false;
4782 }
4783
IsMouseReleased(ImGuiMouseButton button)4784 bool ImGui::IsMouseReleased(ImGuiMouseButton button)
4785 {
4786 ImGuiContext& g = *GImGui;
4787 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
4788 return g.IO.MouseReleased[button];
4789 }
4790
IsMouseDoubleClicked(ImGuiMouseButton button)4791 bool ImGui::IsMouseDoubleClicked(ImGuiMouseButton button)
4792 {
4793 ImGuiContext& g = *GImGui;
4794 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
4795 return g.IO.MouseDoubleClicked[button];
4796 }
4797
4798 // Return if a mouse click/drag went past the given threshold. Valid to call during the MouseReleased frame.
4799 // [Internal] This doesn't test if the button is pressed
IsMouseDragPastThreshold(ImGuiMouseButton button,float lock_threshold)4800 bool ImGui::IsMouseDragPastThreshold(ImGuiMouseButton button, float lock_threshold)
4801 {
4802 ImGuiContext& g = *GImGui;
4803 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
4804 if (lock_threshold < 0.0f)
4805 lock_threshold = g.IO.MouseDragThreshold;
4806 return g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold;
4807 }
4808
IsMouseDragging(ImGuiMouseButton button,float lock_threshold)4809 bool ImGui::IsMouseDragging(ImGuiMouseButton button, float lock_threshold)
4810 {
4811 ImGuiContext& g = *GImGui;
4812 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
4813 if (!g.IO.MouseDown[button])
4814 return false;
4815 return IsMouseDragPastThreshold(button, lock_threshold);
4816 }
4817
GetMousePos()4818 ImVec2 ImGui::GetMousePos()
4819 {
4820 ImGuiContext& g = *GImGui;
4821 return g.IO.MousePos;
4822 }
4823
4824 // NB: prefer to call right after BeginPopup(). At the time Selectable/MenuItem is activated, the popup is already closed!
GetMousePosOnOpeningCurrentPopup()4825 ImVec2 ImGui::GetMousePosOnOpeningCurrentPopup()
4826 {
4827 ImGuiContext& g = *GImGui;
4828 if (g.BeginPopupStack.Size > 0)
4829 return g.OpenPopupStack[g.BeginPopupStack.Size - 1].OpenMousePos;
4830 return g.IO.MousePos;
4831 }
4832
4833 // We typically use ImVec2(-FLT_MAX,-FLT_MAX) to denote an invalid mouse position.
IsMousePosValid(const ImVec2 * mouse_pos)4834 bool ImGui::IsMousePosValid(const ImVec2* mouse_pos)
4835 {
4836 // The assert is only to silence a false-positive in XCode Static Analysis.
4837 // 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).
4838 IM_ASSERT(GImGui != NULL);
4839 const float MOUSE_INVALID = -256000.0f;
4840 ImVec2 p = mouse_pos ? *mouse_pos : GImGui->IO.MousePos;
4841 return p.x >= MOUSE_INVALID && p.y >= MOUSE_INVALID;
4842 }
4843
IsAnyMouseDown()4844 bool ImGui::IsAnyMouseDown()
4845 {
4846 ImGuiContext& g = *GImGui;
4847 for (int n = 0; n < IM_ARRAYSIZE(g.IO.MouseDown); n++)
4848 if (g.IO.MouseDown[n])
4849 return true;
4850 return false;
4851 }
4852
4853 // Return the delta from the initial clicking position while the mouse button is clicked or was just released.
4854 // This is locked and return 0.0f until the mouse moves past a distance threshold at least once.
4855 // NB: This is only valid if IsMousePosValid(). Back-ends in theory should always keep mouse position valid when dragging even outside the client window.
GetMouseDragDelta(ImGuiMouseButton button,float lock_threshold)4856 ImVec2 ImGui::GetMouseDragDelta(ImGuiMouseButton button, float lock_threshold)
4857 {
4858 ImGuiContext& g = *GImGui;
4859 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
4860 if (lock_threshold < 0.0f)
4861 lock_threshold = g.IO.MouseDragThreshold;
4862 if (g.IO.MouseDown[button] || g.IO.MouseReleased[button])
4863 if (g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold)
4864 if (IsMousePosValid(&g.IO.MousePos) && IsMousePosValid(&g.IO.MouseClickedPos[button]))
4865 return g.IO.MousePos - g.IO.MouseClickedPos[button];
4866 return ImVec2(0.0f, 0.0f);
4867 }
4868
ResetMouseDragDelta(ImGuiMouseButton button)4869 void ImGui::ResetMouseDragDelta(ImGuiMouseButton button)
4870 {
4871 ImGuiContext& g = *GImGui;
4872 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
4873 // NB: We don't need to reset g.IO.MouseDragMaxDistanceSqr
4874 g.IO.MouseClickedPos[button] = g.IO.MousePos;
4875 }
4876
GetMouseCursor()4877 ImGuiMouseCursor ImGui::GetMouseCursor()
4878 {
4879 return GImGui->MouseCursor;
4880 }
4881
SetMouseCursor(ImGuiMouseCursor cursor_type)4882 void ImGui::SetMouseCursor(ImGuiMouseCursor cursor_type)
4883 {
4884 GImGui->MouseCursor = cursor_type;
4885 }
4886
CaptureKeyboardFromApp(bool capture)4887 void ImGui::CaptureKeyboardFromApp(bool capture)
4888 {
4889 GImGui->WantCaptureKeyboardNextFrame = capture ? 1 : 0;
4890 }
4891
CaptureMouseFromApp(bool capture)4892 void ImGui::CaptureMouseFromApp(bool capture)
4893 {
4894 GImGui->WantCaptureMouseNextFrame = capture ? 1 : 0;
4895 }
4896
IsItemActive()4897 bool ImGui::IsItemActive()
4898 {
4899 ImGuiContext& g = *GImGui;
4900 if (g.ActiveId)
4901 {
4902 ImGuiWindow* window = g.CurrentWindow;
4903 return g.ActiveId == window->DC.LastItemId;
4904 }
4905 return false;
4906 }
4907
IsItemActivated()4908 bool ImGui::IsItemActivated()
4909 {
4910 ImGuiContext& g = *GImGui;
4911 if (g.ActiveId)
4912 {
4913 ImGuiWindow* window = g.CurrentWindow;
4914 if (g.ActiveId == window->DC.LastItemId && g.ActiveIdPreviousFrame != window->DC.LastItemId)
4915 return true;
4916 }
4917 return false;
4918 }
4919
IsItemDeactivated()4920 bool ImGui::IsItemDeactivated()
4921 {
4922 ImGuiContext& g = *GImGui;
4923 ImGuiWindow* window = g.CurrentWindow;
4924 if (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HasDeactivated)
4925 return (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_Deactivated) != 0;
4926 return (g.ActiveIdPreviousFrame == window->DC.LastItemId && g.ActiveIdPreviousFrame != 0 && g.ActiveId != window->DC.LastItemId);
4927 }
4928
IsItemDeactivatedAfterEdit()4929 bool ImGui::IsItemDeactivatedAfterEdit()
4930 {
4931 ImGuiContext& g = *GImGui;
4932 return IsItemDeactivated() && (g.ActiveIdPreviousFrameHasBeenEditedBefore || (g.ActiveId == 0 && g.ActiveIdHasBeenEditedBefore));
4933 }
4934
IsItemFocused()4935 bool ImGui::IsItemFocused()
4936 {
4937 ImGuiContext& g = *GImGui;
4938 ImGuiWindow* window = g.CurrentWindow;
4939
4940 if (g.NavId == 0 || g.NavDisableHighlight || g.NavId != window->DC.LastItemId)
4941 return false;
4942
4943 // Special handling for the dummy item after Begin() which represent the title bar or tab.
4944 // When the window is collapsed (SkipItems==true) that last item will never be overwritten so we need to detect the case.
4945 if (window->DC.LastItemId == window->ID && window->WriteAccessed)
4946 return false;
4947
4948 return true;
4949 }
4950
IsItemClicked(ImGuiMouseButton mouse_button)4951 bool ImGui::IsItemClicked(ImGuiMouseButton mouse_button)
4952 {
4953 return IsMouseClicked(mouse_button) && IsItemHovered(ImGuiHoveredFlags_None);
4954 }
4955
IsItemToggledOpen()4956 bool ImGui::IsItemToggledOpen()
4957 {
4958 ImGuiContext& g = *GImGui;
4959 return (g.CurrentWindow->DC.LastItemStatusFlags & ImGuiItemStatusFlags_ToggledOpen) ? true : false;
4960 }
4961
IsItemToggledSelection()4962 bool ImGui::IsItemToggledSelection()
4963 {
4964 ImGuiContext& g = *GImGui;
4965 return (g.CurrentWindow->DC.LastItemStatusFlags & ImGuiItemStatusFlags_ToggledSelection) ? true : false;
4966 }
4967
IsAnyItemHovered()4968 bool ImGui::IsAnyItemHovered()
4969 {
4970 ImGuiContext& g = *GImGui;
4971 return g.HoveredId != 0 || g.HoveredIdPreviousFrame != 0;
4972 }
4973
IsAnyItemActive()4974 bool ImGui::IsAnyItemActive()
4975 {
4976 ImGuiContext& g = *GImGui;
4977 return g.ActiveId != 0;
4978 }
4979
IsAnyItemFocused()4980 bool ImGui::IsAnyItemFocused()
4981 {
4982 ImGuiContext& g = *GImGui;
4983 return g.NavId != 0 && !g.NavDisableHighlight;
4984 }
4985
IsItemVisible()4986 bool ImGui::IsItemVisible()
4987 {
4988 ImGuiWindow* window = GetCurrentWindowRead();
4989 return window->ClipRect.Overlaps(window->DC.LastItemRect);
4990 }
4991
IsItemEdited()4992 bool ImGui::IsItemEdited()
4993 {
4994 ImGuiWindow* window = GetCurrentWindowRead();
4995 return (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_Edited) != 0;
4996 }
4997
4998 // Allow last item to be overlapped by a subsequent item. Both may be activated during the same frame before the later one takes priority.
SetItemAllowOverlap()4999 void ImGui::SetItemAllowOverlap()
5000 {
5001 ImGuiContext& g = *GImGui;
5002 if (g.HoveredId == g.CurrentWindow->DC.LastItemId)
5003 g.HoveredIdAllowOverlap = true;
5004 if (g.ActiveId == g.CurrentWindow->DC.LastItemId)
5005 g.ActiveIdAllowOverlap = true;
5006 }
5007
GetItemRectMin()5008 ImVec2 ImGui::GetItemRectMin()
5009 {
5010 ImGuiWindow* window = GetCurrentWindowRead();
5011 return window->DC.LastItemRect.Min;
5012 }
5013
GetItemRectMax()5014 ImVec2 ImGui::GetItemRectMax()
5015 {
5016 ImGuiWindow* window = GetCurrentWindowRead();
5017 return window->DC.LastItemRect.Max;
5018 }
5019
GetItemRectSize()5020 ImVec2 ImGui::GetItemRectSize()
5021 {
5022 ImGuiWindow* window = GetCurrentWindowRead();
5023 return window->DC.LastItemRect.GetSize();
5024 }
5025
BeginChildEx(const char * name,ImGuiID id,const ImVec2 & size_arg,bool border,ImGuiWindowFlags flags)5026 bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags flags)
5027 {
5028 ImGuiContext& g = *GImGui;
5029 ImGuiWindow* parent_window = g.CurrentWindow;
5030
5031 flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NoDocking;
5032 flags |= (parent_window->Flags & ImGuiWindowFlags_NoMove); // Inherit the NoMove flag
5033
5034 // Size
5035 const ImVec2 content_avail = GetContentRegionAvail();
5036 ImVec2 size = ImFloor(size_arg);
5037 const int auto_fit_axises = ((size.x == 0.0f) ? (1 << ImGuiAxis_X) : 0x00) | ((size.y == 0.0f) ? (1 << ImGuiAxis_Y) : 0x00);
5038 if (size.x <= 0.0f)
5039 size.x = ImMax(content_avail.x + size.x, 4.0f); // Arbitrary minimum child size (0.0f causing too much issues)
5040 if (size.y <= 0.0f)
5041 size.y = ImMax(content_avail.y + size.y, 4.0f);
5042 SetNextWindowSize(size);
5043
5044 // 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.
5045 char title[256];
5046 if (name)
5047 ImFormatString(title, IM_ARRAYSIZE(title), "%s/%s_%08X", parent_window->Name, name, id);
5048 else
5049 ImFormatString(title, IM_ARRAYSIZE(title), "%s/%08X", parent_window->Name, id);
5050
5051 const float backup_border_size = g.Style.ChildBorderSize;
5052 if (!border)
5053 g.Style.ChildBorderSize = 0.0f;
5054 bool ret = Begin(title, NULL, flags);
5055 g.Style.ChildBorderSize = backup_border_size;
5056
5057 ImGuiWindow* child_window = g.CurrentWindow;
5058 child_window->ChildId = id;
5059 child_window->AutoFitChildAxises = (ImS8)auto_fit_axises;
5060
5061 // Set the cursor to handle case where the user called SetNextWindowPos()+BeginChild() manually.
5062 // While this is not really documented/defined, it seems that the expected thing to do.
5063 if (child_window->BeginCount == 1)
5064 parent_window->DC.CursorPos = child_window->Pos;
5065
5066 // Process navigation-in immediately so NavInit can run on first frame
5067 if (g.NavActivateId == id && !(flags & ImGuiWindowFlags_NavFlattened) && (child_window->DC.NavLayerActiveMask != 0 || child_window->DC.NavHasScroll))
5068 {
5069 FocusWindow(child_window);
5070 NavInitWindow(child_window, false);
5071 SetActiveID(id + 1, child_window); // Steal ActiveId with another arbitrary id so that key-press won't activate child item
5072 g.ActiveIdSource = ImGuiInputSource_Nav;
5073 }
5074 return ret;
5075 }
5076
BeginChild(const char * str_id,const ImVec2 & size_arg,bool border,ImGuiWindowFlags extra_flags)5077 bool ImGui::BeginChild(const char* str_id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags)
5078 {
5079 ImGuiWindow* window = GetCurrentWindow();
5080 return BeginChildEx(str_id, window->GetID(str_id), size_arg, border, extra_flags);
5081 }
5082
BeginChild(ImGuiID id,const ImVec2 & size_arg,bool border,ImGuiWindowFlags extra_flags)5083 bool ImGui::BeginChild(ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags)
5084 {
5085 IM_ASSERT(id != 0);
5086 return BeginChildEx(NULL, id, size_arg, border, extra_flags);
5087 }
5088
EndChild()5089 void ImGui::EndChild()
5090 {
5091 ImGuiContext& g = *GImGui;
5092 ImGuiWindow* window = g.CurrentWindow;
5093
5094 IM_ASSERT(g.WithinEndChild == false);
5095 IM_ASSERT(window->Flags & ImGuiWindowFlags_ChildWindow); // Mismatched BeginChild()/EndChild() calls
5096
5097 g.WithinEndChild = true;
5098 if (window->BeginCount > 1)
5099 {
5100 End();
5101 }
5102 else
5103 {
5104 ImVec2 sz = window->Size;
5105 if (window->AutoFitChildAxises & (1 << ImGuiAxis_X)) // Arbitrary minimum zero-ish child size of 4.0f causes less trouble than a 0.0f
5106 sz.x = ImMax(4.0f, sz.x);
5107 if (window->AutoFitChildAxises & (1 << ImGuiAxis_Y))
5108 sz.y = ImMax(4.0f, sz.y);
5109 End();
5110
5111 ImGuiWindow* parent_window = g.CurrentWindow;
5112 ImRect bb(parent_window->DC.CursorPos, parent_window->DC.CursorPos + sz);
5113 ItemSize(sz);
5114 if ((window->DC.NavLayerActiveMask != 0 || window->DC.NavHasScroll) && !(window->Flags & ImGuiWindowFlags_NavFlattened))
5115 {
5116 ItemAdd(bb, window->ChildId);
5117 RenderNavHighlight(bb, window->ChildId);
5118
5119 // When browsing a window that has no activable items (scroll only) we keep a highlight on the child
5120 if (window->DC.NavLayerActiveMask == 0 && window == g.NavWindow)
5121 RenderNavHighlight(ImRect(bb.Min - ImVec2(2, 2), bb.Max + ImVec2(2, 2)), g.NavId, ImGuiNavHighlightFlags_TypeThin);
5122 }
5123 else
5124 {
5125 // Not navigable into
5126 ItemAdd(bb, 0);
5127 }
5128 }
5129 g.WithinEndChild = false;
5130 }
5131
5132 // Helper to create a child window / scrolling region that looks like a normal widget frame.
BeginChildFrame(ImGuiID id,const ImVec2 & size,ImGuiWindowFlags extra_flags)5133 bool ImGui::BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags extra_flags)
5134 {
5135 ImGuiContext& g = *GImGui;
5136 const ImGuiStyle& style = g.Style;
5137 PushStyleColor(ImGuiCol_ChildBg, style.Colors[ImGuiCol_FrameBg]);
5138 PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding);
5139 PushStyleVar(ImGuiStyleVar_ChildBorderSize, style.FrameBorderSize);
5140 PushStyleVar(ImGuiStyleVar_WindowPadding, style.FramePadding);
5141 bool ret = BeginChild(id, size, true, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysUseWindowPadding | extra_flags);
5142 PopStyleVar(3);
5143 PopStyleColor();
5144 return ret;
5145 }
5146
EndChildFrame()5147 void ImGui::EndChildFrame()
5148 {
5149 EndChild();
5150 }
5151
SetWindowConditionAllowFlags(ImGuiWindow * window,ImGuiCond flags,bool enabled)5152 static void SetWindowConditionAllowFlags(ImGuiWindow* window, ImGuiCond flags, bool enabled)
5153 {
5154 window->SetWindowPosAllowFlags = enabled ? (window->SetWindowPosAllowFlags | flags) : (window->SetWindowPosAllowFlags & ~flags);
5155 window->SetWindowSizeAllowFlags = enabled ? (window->SetWindowSizeAllowFlags | flags) : (window->SetWindowSizeAllowFlags & ~flags);
5156 window->SetWindowCollapsedAllowFlags = enabled ? (window->SetWindowCollapsedAllowFlags | flags) : (window->SetWindowCollapsedAllowFlags & ~flags);
5157 window->SetWindowDockAllowFlags = enabled ? (window->SetWindowDockAllowFlags | flags) : (window->SetWindowDockAllowFlags & ~flags);
5158 }
5159
FindWindowByID(ImGuiID id)5160 ImGuiWindow* ImGui::FindWindowByID(ImGuiID id)
5161 {
5162 ImGuiContext& g = *GImGui;
5163 return (ImGuiWindow*)g.WindowsById.GetVoidPtr(id);
5164 }
5165
FindWindowByName(const char * name)5166 ImGuiWindow* ImGui::FindWindowByName(const char* name)
5167 {
5168 ImGuiID id = ImHashStr(name);
5169 return FindWindowByID(id);
5170 }
5171
ApplyWindowSettings(ImGuiWindow * window,ImGuiWindowSettings * settings)5172 static void ApplyWindowSettings(ImGuiWindow* window, ImGuiWindowSettings* settings)
5173 {
5174 ImGuiViewport* main_viewport = ImGui::GetMainViewport();
5175 window->ViewportPos = main_viewport->Pos;
5176 if (settings->ViewportId)
5177 {
5178 window->ViewportId = settings->ViewportId;
5179 window->ViewportPos = ImVec2(settings->ViewportPos.x, settings->ViewportPos.y);
5180 }
5181 window->Pos = ImFloor(ImVec2(settings->Pos.x + window->ViewportPos.x, settings->Pos.y + window->ViewportPos.y));
5182 if (settings->Size.x > 0 && settings->Size.y > 0)
5183 window->Size = window->SizeFull = ImFloor(ImVec2(settings->Size.x, settings->Size.y));
5184 window->Collapsed = settings->Collapsed;
5185 window->DockId = settings->DockId;
5186 window->DockOrder = settings->DockOrder;
5187 }
5188
CreateNewWindow(const char * name,ImGuiWindowFlags flags)5189 static ImGuiWindow* CreateNewWindow(const char* name, ImGuiWindowFlags flags)
5190 {
5191 ImGuiContext& g = *GImGui;
5192 //IMGUI_DEBUG_LOG("CreateNewWindow '%s', flags = 0x%08X\n", name, flags);
5193
5194 // Create window the first time
5195 ImGuiWindow* window = IM_NEW(ImGuiWindow)(&g, name);
5196 window->Flags = flags;
5197 g.WindowsById.SetVoidPtr(window->ID, window);
5198
5199 // Default/arbitrary window position. Use SetNextWindowPos() with the appropriate condition flag to change the initial position of a window.
5200 ImGuiViewport* main_viewport = ImGui::GetMainViewport();
5201 window->Pos = main_viewport->Pos + ImVec2(60, 60);
5202 window->ViewportPos = main_viewport->Pos;
5203
5204 // User can disable loading and saving of settings. Tooltip and child windows also don't store settings.
5205 if (!(flags & ImGuiWindowFlags_NoSavedSettings))
5206 if (ImGuiWindowSettings* settings = ImGui::FindWindowSettings(window->ID))
5207 {
5208 // Retrieve settings from .ini file
5209 window->SettingsOffset = g.SettingsWindows.offset_from_ptr(settings);
5210 SetWindowConditionAllowFlags(window, ImGuiCond_FirstUseEver, false);
5211 ApplyWindowSettings(window, settings);
5212 }
5213 window->DC.CursorStartPos = window->DC.CursorMaxPos = window->Pos; // So first call to CalcContentSize() doesn't return crazy values
5214
5215 if ((flags & ImGuiWindowFlags_AlwaysAutoResize) != 0)
5216 {
5217 window->AutoFitFramesX = window->AutoFitFramesY = 2;
5218 window->AutoFitOnlyGrows = false;
5219 }
5220 else
5221 {
5222 if (window->Size.x <= 0.0f)
5223 window->AutoFitFramesX = 2;
5224 if (window->Size.y <= 0.0f)
5225 window->AutoFitFramesY = 2;
5226 window->AutoFitOnlyGrows = (window->AutoFitFramesX > 0) || (window->AutoFitFramesY > 0);
5227 }
5228
5229 g.WindowsFocusOrder.push_back(window);
5230 if (flags & ImGuiWindowFlags_NoBringToFrontOnFocus)
5231 g.Windows.push_front(window); // Quite slow but rare and only once
5232 else
5233 g.Windows.push_back(window);
5234 return window;
5235 }
5236
GetWindowForTitleDisplay(ImGuiWindow * window)5237 static ImGuiWindow* GetWindowForTitleDisplay(ImGuiWindow* window)
5238 {
5239 return window->DockNodeAsHost ? window->DockNodeAsHost->VisibleWindow : window;
5240 }
5241
GetWindowForTitleAndMenuHeight(ImGuiWindow * window)5242 static ImGuiWindow* GetWindowForTitleAndMenuHeight(ImGuiWindow* window)
5243 {
5244 return (window->DockNodeAsHost && window->DockNodeAsHost->VisibleWindow) ? window->DockNodeAsHost->VisibleWindow : window;
5245 }
5246
CalcWindowSizeAfterConstraint(ImGuiWindow * window,ImVec2 new_size)5247 static ImVec2 CalcWindowSizeAfterConstraint(ImGuiWindow* window, ImVec2 new_size)
5248 {
5249 ImGuiContext& g = *GImGui;
5250 if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSizeConstraint)
5251 {
5252 // Using -1,-1 on either X/Y axis to preserve the current size.
5253 ImRect cr = g.NextWindowData.SizeConstraintRect;
5254 new_size.x = (cr.Min.x >= 0 && cr.Max.x >= 0) ? ImClamp(new_size.x, cr.Min.x, cr.Max.x) : window->SizeFull.x;
5255 new_size.y = (cr.Min.y >= 0 && cr.Max.y >= 0) ? ImClamp(new_size.y, cr.Min.y, cr.Max.y) : window->SizeFull.y;
5256 if (g.NextWindowData.SizeCallback)
5257 {
5258 ImGuiSizeCallbackData data;
5259 data.UserData = g.NextWindowData.SizeCallbackUserData;
5260 data.Pos = window->Pos;
5261 data.CurrentSize = window->SizeFull;
5262 data.DesiredSize = new_size;
5263 g.NextWindowData.SizeCallback(&data);
5264 new_size = data.DesiredSize;
5265 }
5266 new_size.x = IM_FLOOR(new_size.x);
5267 new_size.y = IM_FLOOR(new_size.y);
5268 }
5269
5270 // Minimum size
5271 if (!(window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_AlwaysAutoResize)))
5272 {
5273 ImGuiWindow* window_for_height = GetWindowForTitleAndMenuHeight(window);
5274 new_size = ImMax(new_size, g.Style.WindowMinSize);
5275 new_size.y = ImMax(new_size.y, window_for_height->TitleBarHeight() + window_for_height->MenuBarHeight() + ImMax(0.0f, g.Style.WindowRounding - 1.0f)); // Reduce artifacts with very small windows
5276 }
5277 return new_size;
5278 }
5279
CalcWindowContentSize(ImGuiWindow * window)5280 static ImVec2 CalcWindowContentSize(ImGuiWindow* window)
5281 {
5282 if (window->Collapsed)
5283 if (window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0)
5284 return window->ContentSize;
5285 if (window->Hidden && window->HiddenFramesCannotSkipItems == 0 && window->HiddenFramesCanSkipItems > 0)
5286 return window->ContentSize;
5287
5288 ImVec2 sz;
5289 sz.x = IM_FLOOR((window->ContentSizeExplicit.x != 0.0f) ? window->ContentSizeExplicit.x : window->DC.CursorMaxPos.x - window->DC.CursorStartPos.x);
5290 sz.y = IM_FLOOR((window->ContentSizeExplicit.y != 0.0f) ? window->ContentSizeExplicit.y : window->DC.CursorMaxPos.y - window->DC.CursorStartPos.y);
5291 return sz;
5292 }
5293
CalcWindowAutoFitSize(ImGuiWindow * window,const ImVec2 & size_contents)5294 static ImVec2 CalcWindowAutoFitSize(ImGuiWindow* window, const ImVec2& size_contents)
5295 {
5296 ImGuiContext& g = *GImGui;
5297 ImGuiStyle& style = g.Style;
5298 ImVec2 size_decorations = ImVec2(0.0f, window->TitleBarHeight() + window->MenuBarHeight());
5299 ImVec2 size_pad = window->WindowPadding * 2.0f;
5300 ImVec2 size_desired = size_contents + size_pad + size_decorations;
5301 if (window->Flags & ImGuiWindowFlags_Tooltip)
5302 {
5303 // Tooltip always resize
5304 return size_desired;
5305 }
5306 else
5307 {
5308 // Maximum window size is determined by the viewport size or monitor size
5309 const bool is_popup = (window->Flags & ImGuiWindowFlags_Popup) != 0;
5310 const bool is_menu = (window->Flags & ImGuiWindowFlags_ChildMenu) != 0;
5311 ImVec2 size_min = style.WindowMinSize;
5312 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)
5313 size_min = ImMin(size_min, ImVec2(4.0f, 4.0f));
5314
5315 // FIXME-VIEWPORT-WORKAREA: May want to use GetWorkSize() instead of Size depending on the type of windows?
5316 ImVec2 avail_size = window->Viewport->Size;
5317 if (window->ViewportOwned)
5318 avail_size = ImVec2(FLT_MAX, FLT_MAX);
5319 const int monitor_idx = window->ViewportAllowPlatformMonitorExtend;
5320 if (monitor_idx >= 0 && monitor_idx < g.PlatformIO.Monitors.Size)
5321 avail_size = g.PlatformIO.Monitors[monitor_idx].WorkSize;
5322 ImVec2 size_auto_fit = ImClamp(size_desired, size_min, ImMax(size_min, avail_size - g.Style.DisplaySafeAreaPadding * 2.0f));
5323
5324 // When the window cannot fit all contents (either because of constraints, either because screen is too small),
5325 // we are growing the size on the other axis to compensate for expected scrollbar. FIXME: Might turn bigger than ViewportSize-WindowPadding.
5326 ImVec2 size_auto_fit_after_constraint = CalcWindowSizeAfterConstraint(window, size_auto_fit);
5327 bool will_have_scrollbar_x = (size_auto_fit_after_constraint.x - size_pad.x - size_decorations.x < size_contents.x && !(window->Flags & ImGuiWindowFlags_NoScrollbar) && (window->Flags & ImGuiWindowFlags_HorizontalScrollbar)) || (window->Flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar);
5328 bool will_have_scrollbar_y = (size_auto_fit_after_constraint.y - size_pad.y - size_decorations.y < size_contents.y && !(window->Flags & ImGuiWindowFlags_NoScrollbar)) || (window->Flags & ImGuiWindowFlags_AlwaysVerticalScrollbar);
5329 if (will_have_scrollbar_x)
5330 size_auto_fit.y += style.ScrollbarSize;
5331 if (will_have_scrollbar_y)
5332 size_auto_fit.x += style.ScrollbarSize;
5333 return size_auto_fit;
5334 }
5335 }
5336
CalcWindowExpectedSize(ImGuiWindow * window)5337 ImVec2 ImGui::CalcWindowExpectedSize(ImGuiWindow* window)
5338 {
5339 ImVec2 size_contents = CalcWindowContentSize(window);
5340 ImVec2 size_auto_fit = CalcWindowAutoFitSize(window, size_contents);
5341 ImVec2 size_final = CalcWindowSizeAfterConstraint(window, size_auto_fit);
5342 return size_final;
5343 }
5344
GetWindowBgColorIdxFromFlags(ImGuiWindowFlags flags)5345 static ImGuiCol GetWindowBgColorIdxFromFlags(ImGuiWindowFlags flags)
5346 {
5347 if (flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup))
5348 return ImGuiCol_PopupBg;
5349 if (flags & ImGuiWindowFlags_ChildWindow)
5350 return ImGuiCol_ChildBg;
5351 return ImGuiCol_WindowBg;
5352 }
5353
CalcResizePosSizeFromAnyCorner(ImGuiWindow * window,const ImVec2 & corner_target,const ImVec2 & corner_norm,ImVec2 * out_pos,ImVec2 * out_size)5354 static void CalcResizePosSizeFromAnyCorner(ImGuiWindow* window, const ImVec2& corner_target, const ImVec2& corner_norm, ImVec2* out_pos, ImVec2* out_size)
5355 {
5356 ImVec2 pos_min = ImLerp(corner_target, window->Pos, corner_norm); // Expected window upper-left
5357 ImVec2 pos_max = ImLerp(window->Pos + window->Size, corner_target, corner_norm); // Expected window lower-right
5358 ImVec2 size_expected = pos_max - pos_min;
5359 ImVec2 size_constrained = CalcWindowSizeAfterConstraint(window, size_expected);
5360 *out_pos = pos_min;
5361 if (corner_norm.x == 0.0f)
5362 out_pos->x -= (size_constrained.x - size_expected.x);
5363 if (corner_norm.y == 0.0f)
5364 out_pos->y -= (size_constrained.y - size_expected.y);
5365 *out_size = size_constrained;
5366 }
5367
5368 struct ImGuiResizeGripDef
5369 {
5370 ImVec2 CornerPosN;
5371 ImVec2 InnerDir;
5372 int AngleMin12, AngleMax12;
5373 };
5374
5375 static const ImGuiResizeGripDef resize_grip_def[4] =
5376 {
5377 { ImVec2(1, 1), ImVec2(-1, -1), 0, 3 }, // Lower-right
5378 { ImVec2(0, 1), ImVec2(+1, -1), 3, 6 }, // Lower-left
5379 { ImVec2(0, 0), ImVec2(+1, +1), 6, 9 }, // Upper-left (Unused)
5380 { ImVec2(1, 0), ImVec2(-1, +1), 9, 12 }, // Upper-right (Unused)
5381 };
5382
5383 struct ImGuiResizeBorderDef
5384 {
5385 ImVec2 InnerDir;
5386 ImVec2 CornerPosN1, CornerPosN2;
5387 float OuterAngle;
5388 };
5389
5390 static const ImGuiResizeBorderDef resize_border_def[4] =
5391 {
5392 { ImVec2(0, +1), ImVec2(0, 0), ImVec2(1, 0), IM_PI * 1.50f }, // Top
5393 { ImVec2(-1, 0), ImVec2(1, 0), ImVec2(1, 1), IM_PI * 0.00f }, // Right
5394 { ImVec2(0, -1), ImVec2(1, 1), ImVec2(0, 1), IM_PI * 0.50f }, // Bottom
5395 { ImVec2(+1, 0), ImVec2(0, 1), ImVec2(0, 0), IM_PI * 1.00f } // Left
5396 };
5397
GetResizeBorderRect(ImGuiWindow * window,int border_n,float perp_padding,float thickness)5398 static ImRect GetResizeBorderRect(ImGuiWindow* window, int border_n, float perp_padding, float thickness)
5399 {
5400 ImRect rect = window->Rect();
5401 if (thickness == 0.0f) rect.Max -= ImVec2(1, 1);
5402 if (border_n == 0) { return ImRect(rect.Min.x + perp_padding, rect.Min.y - thickness, rect.Max.x - perp_padding, rect.Min.y + thickness); } // Top
5403 if (border_n == 1) { return ImRect(rect.Max.x - thickness, rect.Min.y + perp_padding, rect.Max.x + thickness, rect.Max.y - perp_padding); } // Right
5404 if (border_n == 2) { return ImRect(rect.Min.x + perp_padding, rect.Max.y - thickness, rect.Max.x - perp_padding, rect.Max.y + thickness); } // Bottom
5405 if (border_n == 3) { return ImRect(rect.Min.x - thickness, rect.Min.y + perp_padding, rect.Min.x + thickness, rect.Max.y - perp_padding); } // Left
5406 IM_ASSERT(0);
5407 return ImRect();
5408 }
5409
5410 // 0..3: corners (Lower-right, Lower-left, Unused, Unused)
5411 // 4..7: borders (Top, Right, Bottom, Left)
GetWindowResizeID(ImGuiWindow * window,int n)5412 ImGuiID ImGui::GetWindowResizeID(ImGuiWindow* window, int n)
5413 {
5414 IM_ASSERT(n >= 0 && n <= 7);
5415 ImGuiID id = window->DockIsActive ? window->DockNode->HostWindow->ID : window->ID;
5416 id = ImHashStr("#RESIZE", 0, id);
5417 id = ImHashData(&n, sizeof(int), id);
5418 return id;
5419 }
5420
5421 // Handle resize for: Resize Grips, Borders, Gamepad
5422 // Return true when using auto-fit (double click on resize grip)
UpdateWindowManualResize(ImGuiWindow * window,const ImVec2 & size_auto_fit,int * border_held,int resize_grip_count,ImU32 resize_grip_col[4],const ImRect & visibility_rect)5423 static bool ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4], const ImRect& visibility_rect)
5424 {
5425 ImGuiContext& g = *GImGui;
5426 ImGuiWindowFlags flags = window->Flags;
5427
5428 if ((flags & ImGuiWindowFlags_NoResize) || (flags & ImGuiWindowFlags_AlwaysAutoResize) || window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0)
5429 return false;
5430 if (window->WasActive == false) // Early out to avoid running this code for e.g. an hidden implicit/fallback Debug window.
5431 return false;
5432
5433 bool ret_auto_fit = false;
5434 const int resize_border_count = g.IO.ConfigWindowsResizeFromEdges ? 4 : 0;
5435 const float grip_draw_size = IM_FLOOR(ImMax(g.FontSize * 1.35f, window->WindowRounding + 1.0f + g.FontSize * 0.2f));
5436 const float grip_hover_inner_size = IM_FLOOR(grip_draw_size * 0.75f);
5437 const float grip_hover_outer_size = g.IO.ConfigWindowsResizeFromEdges ? WINDOWS_RESIZE_FROM_EDGES_HALF_THICKNESS : 0.0f;
5438
5439 ImVec2 pos_target(FLT_MAX, FLT_MAX);
5440 ImVec2 size_target(FLT_MAX, FLT_MAX);
5441
5442 // Clip mouse interaction rectangles within the viewport rectangle (in practice the narrowing is going to happen most of the time).
5443 // - Not narrowing would mostly benefit the situation where OS windows _without_ decoration have a threshold for hovering when outside their limits.
5444 // This is however not the case with current back-ends under Win32, but a custom borderless window implementation would benefit from it.
5445 // - 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.
5446 // - Note that we are unable to tell if the platform setup allows hovering with a distance threshold (on Win32, decorated window have such threshold).
5447 // We only clip interaction so we overwrite window->ClipRect, cannot call PushClipRect() yet as DrawList is not yet setup.
5448 const bool clip_with_viewport_rect = !(g.IO.BackendFlags & ImGuiBackendFlags_HasMouseHoveredViewport) || (g.IO.MouseHoveredViewport != window->ViewportId) || !(window->Viewport->Flags & ImGuiViewportFlags_NoDecoration);
5449 if (clip_with_viewport_rect)
5450 window->ClipRect = window->Viewport->GetMainRect();
5451
5452 // Resize grips and borders are on layer 1
5453 window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;
5454 window->DC.NavLayerCurrentMask = (1 << ImGuiNavLayer_Menu);
5455
5456 // Manual resize grips
5457 PushID("#RESIZE");
5458 for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++)
5459 {
5460 const ImGuiResizeGripDef& grip = resize_grip_def[resize_grip_n];
5461 const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, grip.CornerPosN);
5462
5463 // Using the FlattenChilds button flag we make the resize button accessible even if we are hovering over a child window
5464 ImRect resize_rect(corner - grip.InnerDir * grip_hover_outer_size, corner + grip.InnerDir * grip_hover_inner_size);
5465 if (resize_rect.Min.x > resize_rect.Max.x) ImSwap(resize_rect.Min.x, resize_rect.Max.x);
5466 if (resize_rect.Min.y > resize_rect.Max.y) ImSwap(resize_rect.Min.y, resize_rect.Max.y);
5467 bool hovered, held;
5468 ButtonBehavior(resize_rect, window->GetID(resize_grip_n), &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_NoNavFocus);
5469 //GetForegroundDrawList(window)->AddRect(resize_rect.Min, resize_rect.Max, IM_COL32(255, 255, 0, 255));
5470 if (hovered || held)
5471 g.MouseCursor = (resize_grip_n & 1) ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE;
5472
5473 if (held && g.IO.MouseDoubleClicked[0] && resize_grip_n == 0)
5474 {
5475 // Manual auto-fit when double-clicking
5476 size_target = CalcWindowSizeAfterConstraint(window, size_auto_fit);
5477 ret_auto_fit = true;
5478 ClearActiveID();
5479 }
5480 else if (held)
5481 {
5482 // Resize from any of the four corners
5483 // We don't use an incremental MouseDelta but rather compute an absolute target size based on mouse position
5484 ImVec2 corner_target = g.IO.MousePos - g.ActiveIdClickOffset + ImLerp(grip.InnerDir * grip_hover_outer_size, grip.InnerDir * -grip_hover_inner_size, grip.CornerPosN); // Corner of the window corresponding to our corner grip
5485 ImVec2 clamp_min = ImVec2(grip.CornerPosN.x == 1.0f ? visibility_rect.Min.x : -FLT_MAX, grip.CornerPosN.y == 1.0f ? visibility_rect.Min.y : -FLT_MAX);
5486 ImVec2 clamp_max = ImVec2(grip.CornerPosN.x == 0.0f ? visibility_rect.Max.x : +FLT_MAX, grip.CornerPosN.y == 0.0f ? visibility_rect.Max.y : +FLT_MAX);
5487 corner_target = ImClamp(corner_target, clamp_min, clamp_max);
5488 CalcResizePosSizeFromAnyCorner(window, corner_target, grip.CornerPosN, &pos_target, &size_target);
5489 }
5490 if (resize_grip_n == 0 || held || hovered)
5491 resize_grip_col[resize_grip_n] = GetColorU32(held ? ImGuiCol_ResizeGripActive : hovered ? ImGuiCol_ResizeGripHovered : ImGuiCol_ResizeGrip);
5492 }
5493 for (int border_n = 0; border_n < resize_border_count; border_n++)
5494 {
5495 bool hovered, held;
5496 ImRect border_rect = GetResizeBorderRect(window, border_n, grip_hover_inner_size, WINDOWS_RESIZE_FROM_EDGES_HALF_THICKNESS);
5497 ButtonBehavior(border_rect, window->GetID(border_n + 4), &hovered, &held, ImGuiButtonFlags_FlattenChildren);
5498 //GetForegroundDrawLists(window)->AddRect(border_rect.Min, border_rect.Max, IM_COL32(255, 255, 0, 255));
5499 if ((hovered && g.HoveredIdTimer > WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER) || held)
5500 {
5501 g.MouseCursor = (border_n & 1) ? ImGuiMouseCursor_ResizeEW : ImGuiMouseCursor_ResizeNS;
5502 if (held)
5503 *border_held = border_n;
5504 }
5505 if (held)
5506 {
5507 ImVec2 border_target = window->Pos;
5508 ImVec2 border_posn;
5509 if (border_n == 0) { border_posn = ImVec2(0, 0); border_target.y = (g.IO.MousePos.y - g.ActiveIdClickOffset.y + WINDOWS_RESIZE_FROM_EDGES_HALF_THICKNESS); } // Top
5510 if (border_n == 1) { border_posn = ImVec2(1, 0); border_target.x = (g.IO.MousePos.x - g.ActiveIdClickOffset.x + WINDOWS_RESIZE_FROM_EDGES_HALF_THICKNESS); } // Right
5511 if (border_n == 2) { border_posn = ImVec2(0, 1); border_target.y = (g.IO.MousePos.y - g.ActiveIdClickOffset.y + WINDOWS_RESIZE_FROM_EDGES_HALF_THICKNESS); } // Bottom
5512 if (border_n == 3) { border_posn = ImVec2(0, 0); border_target.x = (g.IO.MousePos.x - g.ActiveIdClickOffset.x + WINDOWS_RESIZE_FROM_EDGES_HALF_THICKNESS); } // Left
5513 ImVec2 clamp_min = ImVec2(border_n == 1 ? visibility_rect.Min.x : -FLT_MAX, border_n == 2 ? visibility_rect.Min.y : -FLT_MAX);
5514 ImVec2 clamp_max = ImVec2(border_n == 3 ? visibility_rect.Max.x : +FLT_MAX, border_n == 0 ? visibility_rect.Max.y : +FLT_MAX);
5515 border_target = ImClamp(border_target, clamp_min, clamp_max);
5516 CalcResizePosSizeFromAnyCorner(window, border_target, border_posn, &pos_target, &size_target);
5517 }
5518 }
5519 PopID();
5520
5521 // Restore nav layer
5522 window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
5523 window->DC.NavLayerCurrentMask = (1 << ImGuiNavLayer_Main);
5524
5525 // Navigation resize (keyboard/gamepad)
5526 if (g.NavWindowingTarget && g.NavWindowingTarget->RootWindow == window)
5527 {
5528 ImVec2 nav_resize_delta;
5529 if (g.NavInputSource == ImGuiInputSource_NavKeyboard && g.IO.KeyShift)
5530 nav_resize_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard, ImGuiInputReadMode_Down);
5531 if (g.NavInputSource == ImGuiInputSource_NavGamepad)
5532 nav_resize_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_Down);
5533 if (nav_resize_delta.x != 0.0f || nav_resize_delta.y != 0.0f)
5534 {
5535 const float NAV_RESIZE_SPEED = 600.0f;
5536 nav_resize_delta *= ImFloor(NAV_RESIZE_SPEED * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y));
5537 nav_resize_delta = ImMax(nav_resize_delta, visibility_rect.Min - window->Pos - window->Size);
5538 g.NavWindowingToggleLayer = false;
5539 g.NavDisableMouseHover = true;
5540 resize_grip_col[0] = GetColorU32(ImGuiCol_ResizeGripActive);
5541 // FIXME-NAV: Should store and accumulate into a separate size buffer to handle sizing constraints properly, right now a constraint will make us stuck.
5542 size_target = CalcWindowSizeAfterConstraint(window, window->SizeFull + nav_resize_delta);
5543 }
5544 }
5545
5546 // Apply back modified position/size to window
5547 if (size_target.x != FLT_MAX)
5548 {
5549 window->SizeFull = size_target;
5550 MarkIniSettingsDirty(window);
5551 }
5552 if (pos_target.x != FLT_MAX)
5553 {
5554 window->Pos = ImFloor(pos_target);
5555 MarkIniSettingsDirty(window);
5556 }
5557
5558 window->Size = window->SizeFull;
5559 return ret_auto_fit;
5560 }
5561
ClampWindowRect(ImGuiWindow * window,const ImRect & visibility_rect)5562 static inline void ClampWindowRect(ImGuiWindow* window, const ImRect& visibility_rect)
5563 {
5564 ImGuiContext& g = *GImGui;
5565 ImVec2 size_for_clamping = window->Size;
5566 if (g.IO.ConfigWindowsMoveFromTitleBarOnly && !(window->Flags & ImGuiWindowFlags_NoTitleBar))
5567 size_for_clamping.y = window->TitleBarHeight();
5568 window->Pos = ImClamp(window->Pos, visibility_rect.Min - size_for_clamping, visibility_rect.Max);
5569 }
5570
RenderWindowOuterBorders(ImGuiWindow * window)5571 static void ImGui::RenderWindowOuterBorders(ImGuiWindow* window)
5572 {
5573 ImGuiContext& g = *GImGui;
5574 float rounding = window->WindowRounding;
5575 float border_size = window->WindowBorderSize;
5576 if (border_size > 0.0f && !(window->Flags & ImGuiWindowFlags_NoBackground))
5577 window->DrawList->AddRect(window->Pos, window->Pos + window->Size, GetColorU32(ImGuiCol_Border), rounding, ImDrawCornerFlags_All, border_size);
5578
5579 int border_held = window->ResizeBorderHeld;
5580 if (border_held != -1)
5581 {
5582 const ImGuiResizeBorderDef& def = resize_border_def[border_held];
5583 ImRect border_r = GetResizeBorderRect(window, border_held, rounding, 0.0f);
5584 window->DrawList->PathArcTo(ImLerp(border_r.Min, border_r.Max, def.CornerPosN1) + ImVec2(0.5f, 0.5f) + def.InnerDir * rounding, rounding, def.OuterAngle - IM_PI * 0.25f, def.OuterAngle);
5585 window->DrawList->PathArcTo(ImLerp(border_r.Min, border_r.Max, def.CornerPosN2) + ImVec2(0.5f, 0.5f) + def.InnerDir * rounding, rounding, def.OuterAngle, def.OuterAngle + IM_PI * 0.25f);
5586 window->DrawList->PathStroke(GetColorU32(ImGuiCol_SeparatorActive), false, ImMax(2.0f, border_size)); // Thicker than usual
5587 }
5588 if (g.Style.FrameBorderSize > 0 && !(window->Flags & ImGuiWindowFlags_NoTitleBar) && !window->DockIsActive)
5589 {
5590 float y = window->Pos.y + window->TitleBarHeight() - 1;
5591 window->DrawList->AddLine(ImVec2(window->Pos.x + border_size, y), ImVec2(window->Pos.x + window->Size.x - border_size, y), GetColorU32(ImGuiCol_Border), g.Style.FrameBorderSize);
5592 }
5593 }
5594
5595 // Draw background and borders
5596 // Draw and handle scrollbars
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)5597 void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar_rect, bool title_bar_is_highlight, bool handle_borders_and_resize_grips, int resize_grip_count, const ImU32 resize_grip_col[4], float resize_grip_draw_size)
5598 {
5599 ImGuiContext& g = *GImGui;
5600 ImGuiStyle& style = g.Style;
5601 ImGuiWindowFlags flags = window->Flags;
5602
5603 // Ensure that ScrollBar doesn't read last frame's SkipItems
5604 window->SkipItems = false;
5605
5606 // Draw window + handle manual resize
5607 // As we highlight the title bar when want_focus is set, multiple reappearing windows will have have their title bar highlighted on their reappearing frame.
5608 const float window_rounding = window->WindowRounding;
5609 const float window_border_size = window->WindowBorderSize;
5610 if (window->Collapsed)
5611 {
5612 // Title bar only
5613 float backup_border_size = style.FrameBorderSize;
5614 g.Style.FrameBorderSize = window->WindowBorderSize;
5615 ImU32 title_bar_col = GetColorU32((title_bar_is_highlight && !g.NavDisableHighlight) ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBgCollapsed);
5616 RenderFrame(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, true, window_rounding);
5617 g.Style.FrameBorderSize = backup_border_size;
5618 }
5619 else
5620 {
5621 // Window background
5622 if (!(flags & ImGuiWindowFlags_NoBackground))
5623 {
5624 bool is_docking_transparent_payload = false;
5625 if (g.DragDropActive && (g.FrameCount - g.DragDropAcceptFrameCount) <= 1 && g.IO.ConfigDockingTransparentPayload)
5626 if (g.DragDropPayload.IsDataType(IMGUI_PAYLOAD_TYPE_WINDOW) && *(ImGuiWindow**)g.DragDropPayload.Data == window)
5627 is_docking_transparent_payload = true;
5628
5629 ImU32 bg_col = GetColorU32(GetWindowBgColorIdxFromFlags(flags));
5630 if (window->ViewportOwned)
5631 {
5632 // No alpha
5633 bg_col = (bg_col | IM_COL32_A_MASK);
5634 if (is_docking_transparent_payload)
5635 window->Viewport->Alpha *= DOCKING_TRANSPARENT_PAYLOAD_ALPHA;
5636 }
5637 else
5638 {
5639 // Adjust alpha. For docking
5640 bool override_alpha = false;
5641 float alpha = 1.0f;
5642 if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasBgAlpha)
5643 {
5644 alpha = g.NextWindowData.BgAlphaVal;
5645 override_alpha = true;
5646 }
5647 if (is_docking_transparent_payload)
5648 {
5649 alpha *= DOCKING_TRANSPARENT_PAYLOAD_ALPHA; // FIXME-DOCK: Should that be an override?
5650 override_alpha = true;
5651 }
5652 if (override_alpha)
5653 bg_col = (bg_col & ~IM_COL32_A_MASK) | (IM_F32_TO_INT8_SAT(alpha) << IM_COL32_A_SHIFT);
5654 }
5655 window->DrawList->AddRectFilled(window->Pos + ImVec2(0, window->TitleBarHeight()), window->Pos + window->Size, bg_col, window_rounding, (flags & ImGuiWindowFlags_NoTitleBar) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Bot);
5656 }
5657
5658 // Title bar
5659 // (when docked, DockNode are drawing their own title bar. Individual windows however do NOT set the _NoTitleBar flag,
5660 // in order for their pos/size to be matching their undocking state.)
5661 if (!(flags & ImGuiWindowFlags_NoTitleBar) && !window->DockIsActive)
5662 {
5663 ImU32 title_bar_col = GetColorU32(title_bar_is_highlight ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg);
5664 window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, window_rounding, ImDrawCornerFlags_Top);
5665 }
5666
5667 // Menu bar
5668 if (flags & ImGuiWindowFlags_MenuBar)
5669 {
5670 ImRect menu_bar_rect = window->MenuBarRect();
5671 menu_bar_rect.ClipWith(window->Rect()); // Soft clipping, in particular child window don't have minimum size covering the menu bar so this is useful for them.
5672 window->DrawList->AddRectFilled(menu_bar_rect.Min + ImVec2(window_border_size, 0), menu_bar_rect.Max - ImVec2(window_border_size, 0), GetColorU32(ImGuiCol_MenuBarBg), (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f, ImDrawCornerFlags_Top);
5673 if (style.FrameBorderSize > 0.0f && menu_bar_rect.Max.y < window->Pos.y + window->Size.y)
5674 window->DrawList->AddLine(menu_bar_rect.GetBL(), menu_bar_rect.GetBR(), GetColorU32(ImGuiCol_Border), style.FrameBorderSize);
5675 }
5676
5677 // Docking: Unhide tab bar (small triangle in the corner), drag from small triangle to quickly undock
5678 ImGuiDockNode* node = window->DockNode;
5679 if (window->DockIsActive && node->IsHiddenTabBar() && !node->IsNoTabBar())
5680 {
5681 float unhide_sz_draw = ImFloor(g.FontSize * 0.70f);
5682 float unhide_sz_hit = ImFloor(g.FontSize * 0.55f);
5683 ImVec2 p = node->Pos;
5684 ImRect r(p, p + ImVec2(unhide_sz_hit, unhide_sz_hit));
5685 bool hovered, held;
5686 if (ButtonBehavior(r, window->GetID("#UNHIDE"), &hovered, &held, ImGuiButtonFlags_FlattenChildren))
5687 node->WantHiddenTabBarToggle = true;
5688 else if (held && IsMouseDragging(0))
5689 StartMouseMovingWindowOrNode(window, node, true);
5690
5691 // FIXME-DOCK: Ideally we'd use ImGuiCol_TitleBgActive/ImGuiCol_TitleBg here, but neither is guaranteed to be visible enough at this sort of size..
5692 ImU32 col = GetColorU32(((held && hovered) || (node->IsFocused && !hovered)) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
5693 window->DrawList->AddTriangleFilled(p, p + ImVec2(unhide_sz_draw, 0.0f), p + ImVec2(0.0f, unhide_sz_draw), col);
5694 }
5695
5696 // Scrollbars
5697 if (window->ScrollbarX)
5698 Scrollbar(ImGuiAxis_X);
5699 if (window->ScrollbarY)
5700 Scrollbar(ImGuiAxis_Y);
5701
5702 // Render resize grips (after their input handling so we don't have a frame of latency)
5703 if (handle_borders_and_resize_grips && !(flags & ImGuiWindowFlags_NoResize))
5704 {
5705 for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++)
5706 {
5707 const ImGuiResizeGripDef& grip = resize_grip_def[resize_grip_n];
5708 const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, grip.CornerPosN);
5709 window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(window_border_size, resize_grip_draw_size) : ImVec2(resize_grip_draw_size, window_border_size)));
5710 window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(resize_grip_draw_size, window_border_size) : ImVec2(window_border_size, resize_grip_draw_size)));
5711 window->DrawList->PathArcToFast(ImVec2(corner.x + grip.InnerDir.x * (window_rounding + window_border_size), corner.y + grip.InnerDir.y * (window_rounding + window_border_size)), window_rounding, grip.AngleMin12, grip.AngleMax12);
5712 window->DrawList->PathFillConvex(resize_grip_col[resize_grip_n]);
5713 }
5714 }
5715
5716 // Borders (for dock node host they will be rendered over after the tab bar)
5717 if (handle_borders_and_resize_grips && !window->DockNodeAsHost)
5718 RenderWindowOuterBorders(window);
5719 }
5720 }
5721
5722 // Render title text, collapse button, close button
5723 // When inside a dock node, this is handled in DockNodeUpdateTabBar() instead.
RenderWindowTitleBarContents(ImGuiWindow * window,const ImRect & title_bar_rect,const char * name,bool * p_open)5724 void ImGui::RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& title_bar_rect, const char* name, bool* p_open)
5725 {
5726 ImGuiContext& g = *GImGui;
5727 ImGuiStyle& style = g.Style;
5728 ImGuiWindowFlags flags = window->Flags;
5729
5730 const bool has_close_button = (p_open != NULL);
5731 const bool has_collapse_button = !(flags & ImGuiWindowFlags_NoCollapse) && (style.WindowMenuButtonPosition != ImGuiDir_None);
5732
5733 // Close & Collapse button are on the Menu NavLayer and don't default focus (unless there's nothing else on that layer)
5734 const ImGuiItemFlags item_flags_backup = window->DC.ItemFlags;
5735 window->DC.ItemFlags |= ImGuiItemFlags_NoNavDefaultFocus;
5736 window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;
5737 window->DC.NavLayerCurrentMask = (1 << ImGuiNavLayer_Menu);
5738
5739 // Layout buttons
5740 // FIXME: Would be nice to generalize the subtleties expressed here into reusable code.
5741 float pad_l = style.FramePadding.x;
5742 float pad_r = style.FramePadding.x;
5743 float button_sz = g.FontSize;
5744 ImVec2 close_button_pos;
5745 ImVec2 collapse_button_pos;
5746 if (has_close_button)
5747 {
5748 pad_r += button_sz;
5749 close_button_pos = ImVec2(title_bar_rect.Max.x - pad_r - style.FramePadding.x, title_bar_rect.Min.y);
5750 }
5751 if (has_collapse_button && style.WindowMenuButtonPosition == ImGuiDir_Right)
5752 {
5753 pad_r += button_sz;
5754 collapse_button_pos = ImVec2(title_bar_rect.Max.x - pad_r - style.FramePadding.x, title_bar_rect.Min.y);
5755 }
5756 if (has_collapse_button && style.WindowMenuButtonPosition == ImGuiDir_Left)
5757 {
5758 collapse_button_pos = ImVec2(title_bar_rect.Min.x + pad_l - style.FramePadding.x, title_bar_rect.Min.y);
5759 pad_l += button_sz;
5760 }
5761
5762 // Collapse button (submitting first so it gets priority when choosing a navigation init fallback)
5763 if (has_collapse_button)
5764 if (CollapseButton(window->GetID("#COLLAPSE"), collapse_button_pos, NULL))
5765 window->WantCollapseToggle = true; // Defer actual collapsing to next frame as we are too far in the Begin() function
5766
5767 // Close button
5768 if (has_close_button)
5769 if (CloseButton(window->GetID("#CLOSE"), close_button_pos))
5770 *p_open = false;
5771
5772 window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
5773 window->DC.NavLayerCurrentMask = (1 << ImGuiNavLayer_Main);
5774 window->DC.ItemFlags = item_flags_backup;
5775
5776 // Title bar text (with: horizontal alignment, avoiding collapse/close button, optional "unsaved document" marker)
5777 // FIXME: Refactor text alignment facilities along with RenderText helpers, this is WAY too much messy code..
5778 const char* UNSAVED_DOCUMENT_MARKER = "*";
5779 const float marker_size_x = (flags & ImGuiWindowFlags_UnsavedDocument) ? CalcTextSize(UNSAVED_DOCUMENT_MARKER, NULL, false).x : 0.0f;
5780 const ImVec2 text_size = CalcTextSize(name, NULL, true) + ImVec2(marker_size_x, 0.0f);
5781
5782 // As a nice touch we try to ensure that centered title text doesn't get affected by visibility of Close/Collapse button,
5783 // while uncentered title text will still reach edges correct.
5784 if (pad_l > style.FramePadding.x)
5785 pad_l += g.Style.ItemInnerSpacing.x;
5786 if (pad_r > style.FramePadding.x)
5787 pad_r += g.Style.ItemInnerSpacing.x;
5788 if (style.WindowTitleAlign.x > 0.0f && style.WindowTitleAlign.x < 1.0f)
5789 {
5790 float centerness = ImSaturate(1.0f - ImFabs(style.WindowTitleAlign.x - 0.5f) * 2.0f); // 0.0f on either edges, 1.0f on center
5791 float pad_extend = ImMin(ImMax(pad_l, pad_r), title_bar_rect.GetWidth() - pad_l - pad_r - text_size.x);
5792 pad_l = ImMax(pad_l, pad_extend * centerness);
5793 pad_r = ImMax(pad_r, pad_extend * centerness);
5794 }
5795
5796 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);
5797 ImRect clip_r(layout_r.Min.x, layout_r.Min.y, layout_r.Max.x + g.Style.ItemInnerSpacing.x, layout_r.Max.y);
5798 //if (g.IO.KeyCtrl) window->DrawList->AddRect(layout_r.Min, layout_r.Max, IM_COL32(255, 128, 0, 255)); // [DEBUG]
5799 RenderTextClipped(layout_r.Min, layout_r.Max, name, NULL, &text_size, style.WindowTitleAlign, &clip_r);
5800 if (flags & ImGuiWindowFlags_UnsavedDocument)
5801 {
5802 ImVec2 marker_pos = ImVec2(ImMax(layout_r.Min.x, layout_r.Min.x + (layout_r.GetWidth() - text_size.x) * style.WindowTitleAlign.x) + text_size.x, layout_r.Min.y) + ImVec2(2 - marker_size_x, 0.0f);
5803 ImVec2 off = ImVec2(0.0f, IM_FLOOR(-g.FontSize * 0.25f));
5804 RenderTextClipped(marker_pos + off, layout_r.Max + off, UNSAVED_DOCUMENT_MARKER, NULL, NULL, ImVec2(0, style.WindowTitleAlign.y), &clip_r);
5805 }
5806 }
5807
UpdateWindowParentAndRootLinks(ImGuiWindow * window,ImGuiWindowFlags flags,ImGuiWindow * parent_window)5808 void ImGui::UpdateWindowParentAndRootLinks(ImGuiWindow* window, ImGuiWindowFlags flags, ImGuiWindow* parent_window)
5809 {
5810 window->ParentWindow = parent_window;
5811 window->RootWindow = window->RootWindowDockStop = window->RootWindowForTitleBarHighlight = window->RootWindowForNav = window;
5812 if (parent_window && (flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Tooltip))
5813 {
5814 window->RootWindow = parent_window->RootWindow;
5815 if (!window->DockIsActive && !(parent_window->Flags & ImGuiWindowFlags_DockNodeHost))
5816 window->RootWindowDockStop = parent_window->RootWindowDockStop;
5817 }
5818 if (parent_window && !(flags & ImGuiWindowFlags_Modal) && (flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup)))
5819 window->RootWindowForTitleBarHighlight = parent_window->RootWindowForTitleBarHighlight;
5820 while (window->RootWindowForNav->Flags & ImGuiWindowFlags_NavFlattened)
5821 {
5822 IM_ASSERT(window->RootWindowForNav->ParentWindow != NULL);
5823 window->RootWindowForNav = window->RootWindowForNav->ParentWindow;
5824 }
5825 }
5826
5827 // Push a new Dear ImGui window to add widgets to.
5828 // - 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.
5829 // - Begin/End can be called multiple times during the frame with the same window name to append content.
5830 // - The window name is used as a unique identifier to preserve window information across frames (and save rudimentary information to the .ini file).
5831 // 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.
5832 // - 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.
5833 // - 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.
Begin(const char * name,bool * p_open,ImGuiWindowFlags flags)5834 bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
5835 {
5836 ImGuiContext& g = *GImGui;
5837 const ImGuiStyle& style = g.Style;
5838 IM_ASSERT(name != NULL && name[0] != '\0'); // Window name required
5839 IM_ASSERT(g.WithinFrameScope); // Forgot to call ImGui::NewFrame()
5840 IM_ASSERT(g.FrameCountEnded != g.FrameCount); // Called ImGui::Render() or ImGui::EndFrame() and haven't called ImGui::NewFrame() again yet
5841
5842 // Find or create
5843 ImGuiWindow* window = FindWindowByName(name);
5844 const bool window_just_created = (window == NULL);
5845 if (window_just_created)
5846 window = CreateNewWindow(name, flags);
5847
5848 // Automatically disable manual moving/resizing when NoInputs is set
5849 if ((flags & ImGuiWindowFlags_NoInputs) == ImGuiWindowFlags_NoInputs)
5850 flags |= ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
5851
5852 if (flags & ImGuiWindowFlags_NavFlattened)
5853 IM_ASSERT(flags & ImGuiWindowFlags_ChildWindow);
5854
5855 const int current_frame = g.FrameCount;
5856 const bool first_begin_of_the_frame = (window->LastFrameActive != current_frame);
5857 window->IsFallbackWindow = (g.CurrentWindowStack.Size == 0 && g.WithinFrameScopeWithImplicitWindow);
5858
5859 // Update the Appearing flag
5860 bool window_just_activated_by_user = (window->LastFrameActive < current_frame - 1); // Not using !WasActive because the implicit "Debug" window would always toggle off->on
5861 const bool window_just_appearing_after_hidden_for_resize = (window->HiddenFramesCannotSkipItems > 0);
5862 if (flags & ImGuiWindowFlags_Popup)
5863 {
5864 ImGuiPopupData& popup_ref = g.OpenPopupStack[g.BeginPopupStack.Size];
5865 window_just_activated_by_user |= (window->PopupId != popup_ref.PopupId); // We recycle popups so treat window as activated if popup id changed
5866 window_just_activated_by_user |= (window != popup_ref.Window);
5867 }
5868 window->Appearing = (window_just_activated_by_user || window_just_appearing_after_hidden_for_resize);
5869 if (window->Appearing)
5870 SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, true);
5871
5872 // Update Flags, LastFrameActive, BeginOrderXXX fields
5873 if (first_begin_of_the_frame)
5874 {
5875 window->FlagsPreviousFrame = window->Flags;
5876 window->Flags = (ImGuiWindowFlags)flags;
5877 window->LastFrameActive = current_frame;
5878 window->LastTimeActive = (float)g.Time;
5879 window->BeginOrderWithinParent = 0;
5880 window->BeginOrderWithinContext = (short)(g.WindowsActiveCount++);
5881 }
5882 else
5883 {
5884 flags = window->Flags;
5885 }
5886
5887 // Docking
5888 // (NB: during the frame dock nodes are created, it is possible that (window->DockIsActive == false) even though (window->DockNode->Windows.Size > 1)
5889 IM_ASSERT(window->DockNode == NULL || window->DockNodeAsHost == NULL); // Cannot be both
5890 if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasDock)
5891 SetWindowDock(window, g.NextWindowData.DockId, g.NextWindowData.DockCond);
5892 if (first_begin_of_the_frame)
5893 {
5894 bool has_dock_node = (window->DockId != 0 || window->DockNode != NULL);
5895 bool new_auto_dock_node = !has_dock_node && GetWindowAlwaysWantOwnTabBar(window);
5896 if (has_dock_node || new_auto_dock_node)
5897 {
5898 BeginDocked(window, p_open);
5899 flags = window->Flags;
5900 if (window->DockIsActive)
5901 IM_ASSERT(window->DockNode != NULL);
5902
5903 // Docking currently override constraints
5904 g.NextWindowData.Flags &= ~ImGuiNextWindowDataFlags_HasSizeConstraint;
5905 }
5906 }
5907
5908 // 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
5909 ImGuiWindow* parent_window_in_stack = window->DockIsActive ? window->DockNode->HostWindow : g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back();
5910 ImGuiWindow* parent_window = first_begin_of_the_frame ? ((flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup)) ? parent_window_in_stack : NULL) : window->ParentWindow;
5911 IM_ASSERT(parent_window != NULL || !(flags & ImGuiWindowFlags_ChildWindow));
5912
5913 // We allow window memory to be compacted so recreate the base stack when needed.
5914 if (window->IDStack.Size == 0)
5915 window->IDStack.push_back(window->ID);
5916
5917 // Add to stack
5918 // We intentionally set g.CurrentWindow to NULL to prevent usage until when the viewport is set, then will call SetCurrentWindow()
5919 g.CurrentWindowStack.push_back(window);
5920 g.CurrentWindow = NULL;
5921 ErrorCheckBeginEndCompareStacksSize(window, true);
5922 if (flags & ImGuiWindowFlags_Popup)
5923 {
5924 ImGuiPopupData& popup_ref = g.OpenPopupStack[g.BeginPopupStack.Size];
5925 popup_ref.Window = window;
5926 g.BeginPopupStack.push_back(popup_ref);
5927 window->PopupId = popup_ref.PopupId;
5928 }
5929
5930 if (window_just_appearing_after_hidden_for_resize && !(flags & ImGuiWindowFlags_ChildWindow))
5931 window->NavLastIds[0] = 0;
5932
5933 // Update ->RootWindow and others pointers (before any possible call to FocusWindow)
5934 if (first_begin_of_the_frame)
5935 UpdateWindowParentAndRootLinks(window, flags, parent_window);
5936
5937 // Process SetNextWindow***() calls
5938 // (FIXME: Consider splitting the HasXXX flags into X/Y components
5939 bool window_pos_set_by_api = false;
5940 bool window_size_x_set_by_api = false, window_size_y_set_by_api = false;
5941 if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasPos)
5942 {
5943 window_pos_set_by_api = (window->SetWindowPosAllowFlags & g.NextWindowData.PosCond) != 0;
5944 if (window_pos_set_by_api && ImLengthSqr(g.NextWindowData.PosPivotVal) > 0.00001f)
5945 {
5946 // May be processed on the next frame if this is our first frame and we are measuring size
5947 // FIXME: Look into removing the branch so everything can go through this same code path for consistency.
5948 window->SetWindowPosVal = g.NextWindowData.PosVal;
5949 window->SetWindowPosPivot = g.NextWindowData.PosPivotVal;
5950 window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
5951 }
5952 else
5953 {
5954 SetWindowPos(window, g.NextWindowData.PosVal, g.NextWindowData.PosCond);
5955 }
5956 }
5957 if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasSize)
5958 {
5959 window_size_x_set_by_api = (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.x > 0.0f);
5960 window_size_y_set_by_api = (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.y > 0.0f);
5961 SetWindowSize(window, g.NextWindowData.SizeVal, g.NextWindowData.SizeCond);
5962 }
5963 if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasScroll)
5964 {
5965 if (g.NextWindowData.ScrollVal.x >= 0.0f)
5966 {
5967 window->ScrollTarget.x = g.NextWindowData.ScrollVal.x;
5968 window->ScrollTargetCenterRatio.x = 0.0f;
5969 }
5970 if (g.NextWindowData.ScrollVal.y >= 0.0f)
5971 {
5972 window->ScrollTarget.y = g.NextWindowData.ScrollVal.y;
5973 window->ScrollTargetCenterRatio.y = 0.0f;
5974 }
5975 }
5976 if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasContentSize)
5977 window->ContentSizeExplicit = g.NextWindowData.ContentSizeVal;
5978 else if (first_begin_of_the_frame)
5979 window->ContentSizeExplicit = ImVec2(0.0f, 0.0f);
5980 if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasWindowClass)
5981 window->WindowClass = g.NextWindowData.WindowClass;
5982 if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasCollapsed)
5983 SetWindowCollapsed(window, g.NextWindowData.CollapsedVal, g.NextWindowData.CollapsedCond);
5984 if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasFocus)
5985 FocusWindow(window);
5986 if (window->Appearing)
5987 SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, false);
5988
5989 // When reusing window again multiple times a frame, just append content (don't need to setup again)
5990 if (first_begin_of_the_frame)
5991 {
5992 // Initialize
5993 const bool window_is_child_tooltip = (flags & ImGuiWindowFlags_ChildWindow) && (flags & ImGuiWindowFlags_Tooltip); // FIXME-WIP: Undocumented behavior of Child+Tooltip for pinned tooltip (#1345)
5994 window->Active = true;
5995 window->HasCloseButton = (p_open != NULL);
5996 window->ClipRect = ImVec4(-FLT_MAX, -FLT_MAX, +FLT_MAX, +FLT_MAX);
5997 window->IDStack.resize(1);
5998 window->DrawList->_ResetForNewFrame();
5999
6000 // Restore buffer capacity when woken from a compacted state, to avoid
6001 if (window->MemoryCompacted)
6002 GcAwakeTransientWindowBuffers(window);
6003
6004 // Update stored window name when it changes (which can _only_ happen with the "###" operator, so the ID would stay unchanged).
6005 // 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.
6006 bool window_title_visible_elsewhere = false;
6007 if ((window->Viewport && window->Viewport->Window == window) || (window->DockIsActive))
6008 window_title_visible_elsewhere = true;
6009 else if (g.NavWindowingListWindow != NULL && (window->Flags & ImGuiWindowFlags_NoNavFocus) == 0) // Window titles visible when using CTRL+TAB
6010 window_title_visible_elsewhere = true;
6011 if (window_title_visible_elsewhere && !window_just_created && strcmp(name, window->Name) != 0)
6012 {
6013 size_t buf_len = (size_t)window->NameBufLen;
6014 window->Name = ImStrdupcpy(window->Name, &buf_len, name);
6015 window->NameBufLen = (int)buf_len;
6016 }
6017
6018 // UPDATE CONTENTS SIZE, UPDATE HIDDEN STATUS
6019
6020 // Update contents size from last frame for auto-fitting (or use explicit size)
6021 window->ContentSize = CalcWindowContentSize(window);
6022 if (window->HiddenFramesCanSkipItems > 0)
6023 window->HiddenFramesCanSkipItems--;
6024 if (window->HiddenFramesCannotSkipItems > 0)
6025 window->HiddenFramesCannotSkipItems--;
6026
6027 // Hide new windows for one frame until they calculate their size
6028 if (window_just_created && (!window_size_x_set_by_api || !window_size_y_set_by_api))
6029 window->HiddenFramesCannotSkipItems = 1;
6030
6031 // Hide popup/tooltip window when re-opening while we measure size (because we recycle the windows)
6032 // We reset Size/ContentSize for reappearing popups/tooltips early in this function, so further code won't be tempted to use the old size.
6033 if (window_just_activated_by_user && (flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) != 0)
6034 {
6035 window->HiddenFramesCannotSkipItems = 1;
6036 if (flags & ImGuiWindowFlags_AlwaysAutoResize)
6037 {
6038 if (!window_size_x_set_by_api)
6039 window->Size.x = window->SizeFull.x = 0.f;
6040 if (!window_size_y_set_by_api)
6041 window->Size.y = window->SizeFull.y = 0.f;
6042 window->ContentSize = ImVec2(0.f, 0.f);
6043 }
6044 }
6045
6046 // SELECT VIEWPORT
6047 // We need to do this before using any style/font sizes, as viewport with a different DPI may affect font sizes.
6048
6049 UpdateSelectWindowViewport(window);
6050 SetCurrentViewport(window, window->Viewport);
6051 window->FontDpiScale = (g.IO.ConfigFlags & ImGuiConfigFlags_DpiEnableScaleFonts) ? window->Viewport->DpiScale : 1.0f;
6052 SetCurrentWindow(window);
6053 flags = window->Flags;
6054
6055 // LOCK BORDER SIZE AND PADDING FOR THE FRAME (so that altering them doesn't cause inconsistencies)
6056 // We read Style data after the call to UpdateSelectWindowViewport() which might be swapping the style.
6057
6058 if (flags & ImGuiWindowFlags_ChildWindow)
6059 window->WindowBorderSize = style.ChildBorderSize;
6060 else
6061 window->WindowBorderSize = ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupBorderSize : style.WindowBorderSize;
6062 if (!window->DockIsActive && (flags & ImGuiWindowFlags_ChildWindow) && !(flags & (ImGuiWindowFlags_AlwaysUseWindowPadding | ImGuiWindowFlags_Popup)) && window->WindowBorderSize == 0.0f)
6063 window->WindowPadding = ImVec2(0.0f, (flags & ImGuiWindowFlags_MenuBar) ? style.WindowPadding.y : 0.0f);
6064 else
6065 window->WindowPadding = style.WindowPadding;
6066
6067 // Lock menu offset so size calculation can use it as menu-bar windows need a minimum size.
6068 window->DC.MenuBarOffset.x = ImMax(ImMax(window->WindowPadding.x, style.ItemSpacing.x), g.NextWindowData.MenuBarOffsetMinVal.x);
6069 window->DC.MenuBarOffset.y = g.NextWindowData.MenuBarOffsetMinVal.y;
6070
6071 // Collapse window by double-clicking on title bar
6072 // 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
6073 if (!(flags & ImGuiWindowFlags_NoTitleBar) && !(flags & ImGuiWindowFlags_NoCollapse) && !window->DockIsActive)
6074 {
6075 // 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.
6076 ImRect title_bar_rect = window->TitleBarRect();
6077 if (g.HoveredWindow == window && g.HoveredId == 0 && g.HoveredIdPreviousFrame == 0 && IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max) && g.IO.MouseDoubleClicked[0])
6078 window->WantCollapseToggle = true;
6079 if (window->WantCollapseToggle)
6080 {
6081 window->Collapsed = !window->Collapsed;
6082 MarkIniSettingsDirty(window);
6083 FocusWindow(window);
6084 }
6085 }
6086 else
6087 {
6088 window->Collapsed = false;
6089 }
6090 window->WantCollapseToggle = false;
6091
6092 // SIZE
6093
6094 // Calculate auto-fit size, handle automatic resize
6095 const ImVec2 size_auto_fit = CalcWindowAutoFitSize(window, window->ContentSize);
6096 bool use_current_size_for_scrollbar_x = window_just_created;
6097 bool use_current_size_for_scrollbar_y = window_just_created;
6098 if ((flags & ImGuiWindowFlags_AlwaysAutoResize) && !window->Collapsed)
6099 {
6100 // Using SetNextWindowSize() overrides ImGuiWindowFlags_AlwaysAutoResize, so it can be used on tooltips/popups, etc.
6101 if (!window_size_x_set_by_api)
6102 {
6103 window->SizeFull.x = size_auto_fit.x;
6104 use_current_size_for_scrollbar_x = true;
6105 }
6106 if (!window_size_y_set_by_api)
6107 {
6108 window->SizeFull.y = size_auto_fit.y;
6109 use_current_size_for_scrollbar_y = true;
6110 }
6111 }
6112 else if (window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0)
6113 {
6114 // Auto-fit may only grow window during the first few frames
6115 // We still process initial auto-fit on collapsed windows to get a window width, but otherwise don't honor ImGuiWindowFlags_AlwaysAutoResize when collapsed.
6116 if (!window_size_x_set_by_api && window->AutoFitFramesX > 0)
6117 {
6118 window->SizeFull.x = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.x, size_auto_fit.x) : size_auto_fit.x;
6119 use_current_size_for_scrollbar_x = true;
6120 }
6121 if (!window_size_y_set_by_api && window->AutoFitFramesY > 0)
6122 {
6123 window->SizeFull.y = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.y, size_auto_fit.y) : size_auto_fit.y;
6124 use_current_size_for_scrollbar_y = true;
6125 }
6126 if (!window->Collapsed)
6127 MarkIniSettingsDirty(window);
6128 }
6129
6130 // Apply minimum/maximum window size constraints and final size
6131 window->SizeFull = CalcWindowSizeAfterConstraint(window, window->SizeFull);
6132 window->Size = window->Collapsed && !(flags & ImGuiWindowFlags_ChildWindow) ? window->TitleBarRect().GetSize() : window->SizeFull;
6133
6134 // Decoration size
6135 const float decoration_up_height = window->TitleBarHeight() + window->MenuBarHeight();
6136
6137 // POSITION
6138
6139 // Popup latch its initial position, will position itself when it appears next frame
6140 if (window_just_activated_by_user)
6141 {
6142 window->AutoPosLastDirection = ImGuiDir_None;
6143 if ((flags & ImGuiWindowFlags_Popup) != 0 && !(flags & ImGuiWindowFlags_Modal) && !window_pos_set_by_api) // FIXME: BeginPopup() could use SetNextWindowPos()
6144 window->Pos = g.BeginPopupStack.back().OpenPopupPos;
6145 }
6146
6147 // Position child window
6148 if (flags & ImGuiWindowFlags_ChildWindow)
6149 {
6150 IM_ASSERT(parent_window && parent_window->Active);
6151 window->BeginOrderWithinParent = (short)parent_window->DC.ChildWindows.Size;
6152 parent_window->DC.ChildWindows.push_back(window);
6153 if (!(flags & ImGuiWindowFlags_Popup) && !window_pos_set_by_api && !window_is_child_tooltip)
6154 window->Pos = parent_window->DC.CursorPos;
6155 }
6156
6157 const bool window_pos_with_pivot = (window->SetWindowPosVal.x != FLT_MAX && window->HiddenFramesCannotSkipItems == 0);
6158 if (window_pos_with_pivot)
6159 SetWindowPos(window, window->SetWindowPosVal - window->SizeFull * window->SetWindowPosPivot, 0); // Position given a pivot (e.g. for centering)
6160 else if ((flags & ImGuiWindowFlags_ChildMenu) != 0)
6161 window->Pos = FindBestWindowPosForPopup(window);
6162 else if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api && window_just_appearing_after_hidden_for_resize)
6163 window->Pos = FindBestWindowPosForPopup(window);
6164 else if ((flags & ImGuiWindowFlags_Tooltip) != 0 && !window_pos_set_by_api && !window_is_child_tooltip)
6165 window->Pos = FindBestWindowPosForPopup(window);
6166
6167 // Late create viewport if we don't fit within our current host viewport.
6168 if (window->ViewportAllowPlatformMonitorExtend >= 0 && !window->ViewportOwned && !(window->Viewport->Flags & ImGuiViewportFlags_Minimized))
6169 if (!window->Viewport->GetMainRect().Contains(window->Rect()))
6170 {
6171 // This is based on the assumption that the DPI will be known ahead (same as the DPI of the selection done in UpdateSelectWindowViewport)
6172 //ImGuiViewport* old_viewport = window->Viewport;
6173 window->Viewport = AddUpdateViewport(window, window->ID, window->Pos, window->Size, ImGuiViewportFlags_NoFocusOnAppearing);
6174
6175 // FIXME-DPI
6176 //IM_ASSERT(old_viewport->DpiScale == window->Viewport->DpiScale); // FIXME-DPI: Something went wrong
6177 SetCurrentViewport(window, window->Viewport);
6178 window->FontDpiScale = (g.IO.ConfigFlags & ImGuiConfigFlags_DpiEnableScaleFonts) ? window->Viewport->DpiScale : 1.0f;
6179 SetCurrentWindow(window);
6180 }
6181
6182 bool viewport_rect_changed = false;
6183 if (window->ViewportOwned)
6184 {
6185 // Synchronize window --> viewport in most situations
6186 // Synchronize viewport -> window in case the platform window has been moved or resized from the OS/WM
6187 if (window->Viewport->PlatformRequestMove)
6188 {
6189 window->Pos = window->Viewport->Pos;
6190 MarkIniSettingsDirty(window);
6191 }
6192 else if (memcmp(&window->Viewport->Pos, &window->Pos, sizeof(window->Pos)) != 0)
6193 {
6194 viewport_rect_changed = true;
6195 window->Viewport->Pos = window->Pos;
6196 }
6197
6198 if (window->Viewport->PlatformRequestResize)
6199 {
6200 window->Size = window->SizeFull = window->Viewport->Size;
6201 MarkIniSettingsDirty(window);
6202 }
6203 else if (memcmp(&window->Viewport->Size, &window->Size, sizeof(window->Size)) != 0)
6204 {
6205 viewport_rect_changed = true;
6206 window->Viewport->Size = window->Size;
6207 }
6208
6209 // The viewport may have changed monitor since the global update in UpdateViewportsNewFrame()
6210 // Either a SetNextWindowPos() call in the current frame or a SetWindowPos() call in the previous frame may have this effect.
6211 if (viewport_rect_changed)
6212 UpdateViewportPlatformMonitor(window->Viewport);
6213
6214 // Update common viewport flags
6215 const ImGuiViewportFlags viewport_flags_to_clear = ImGuiViewportFlags_TopMost | ImGuiViewportFlags_NoTaskBarIcon | ImGuiViewportFlags_NoDecoration | ImGuiViewportFlags_NoRendererClear;
6216 ImGuiViewportFlags viewport_flags = window->Viewport->Flags & ~viewport_flags_to_clear;
6217 const bool is_short_lived_floating_window = (flags & (ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup)) != 0;
6218 if (flags & ImGuiWindowFlags_Tooltip)
6219 viewport_flags |= ImGuiViewportFlags_TopMost;
6220 if (g.IO.ConfigViewportsNoTaskBarIcon || is_short_lived_floating_window)
6221 viewport_flags |= ImGuiViewportFlags_NoTaskBarIcon;
6222 if (g.IO.ConfigViewportsNoDecoration || is_short_lived_floating_window)
6223 viewport_flags |= ImGuiViewportFlags_NoDecoration;
6224
6225 // For popups and menus that may be protruding out of their parent viewport, we enable _NoFocusOnClick so that clicking on them
6226 // won't steal the OS focus away from their parent window (which may be reflected in OS the title bar decoration).
6227 // 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,
6228 // but it shouldn't be much of a problem considering those are already popups that are closed when clicking elsewhere.
6229 if (is_short_lived_floating_window && (flags & ImGuiWindowFlags_Modal) == 0)
6230 viewport_flags |= ImGuiViewportFlags_NoFocusOnAppearing | ImGuiViewportFlags_NoFocusOnClick;
6231
6232 // We can overwrite viewport flags using ImGuiWindowClass (advanced users)
6233 // We don't default to the main viewport because.
6234 if (window->WindowClass.ParentViewportId)
6235 window->Viewport->ParentViewportId = window->WindowClass.ParentViewportId;
6236 else if ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) && parent_window_in_stack)
6237 window->Viewport->ParentViewportId = parent_window_in_stack->Viewport->ID;
6238 else
6239 window->Viewport->ParentViewportId = g.IO.ConfigViewportsNoDefaultParent ? 0 : IMGUI_VIEWPORT_DEFAULT_ID;
6240 if (window->WindowClass.ViewportFlagsOverrideSet)
6241 viewport_flags |= window->WindowClass.ViewportFlagsOverrideSet;
6242 if (window->WindowClass.ViewportFlagsOverrideClear)
6243 viewport_flags &= ~window->WindowClass.ViewportFlagsOverrideClear;
6244
6245 // We also tell the back-end that clearing the platform window won't be necessary, as our window is filling the viewport and we have disabled BgAlpha
6246 if (!(flags & ImGuiWindowFlags_NoBackground))
6247 viewport_flags &= ~ImGuiViewportFlags_NoRendererClear;
6248
6249 window->Viewport->Flags = viewport_flags;
6250 }
6251
6252 // Calculate the range of allowed position for that window (to be movable and visible past safe area padding)
6253 // When clamping to stay visible, we will enforce that window->Pos stays inside of visibility_rect.
6254 ImRect viewport_rect(window->Viewport->GetMainRect());
6255 ImRect viewport_work_rect(window->Viewport->GetWorkRect());
6256 ImVec2 visibility_padding = ImMax(style.DisplayWindowPadding, style.DisplaySafeAreaPadding);
6257 ImRect visibility_rect(viewport_work_rect.Min + visibility_padding, viewport_work_rect.Max - visibility_padding);
6258
6259 // Clamp position/size so window stays visible within its viewport or monitor
6260 // Ignore zero-sized display explicitly to avoid losing positions if a window manager reports zero-sized window when initializing or minimizing.
6261 // FIXME: Similar to code in GetWindowAllowedExtentRect()
6262 if (!window_pos_set_by_api && !(flags & ImGuiWindowFlags_ChildWindow) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0)
6263 {
6264 if (!window->ViewportOwned && viewport_rect.GetWidth() > 0 && viewport_rect.GetHeight() > 0.0f)
6265 {
6266 ClampWindowRect(window, visibility_rect);
6267 }
6268 else if (window->ViewportOwned && g.PlatformIO.Monitors.Size > 0)
6269 {
6270 if (window->Viewport->PlatformMonitor == -1)
6271 {
6272 // Fallback for "lost" window (e.g. a monitor disconnected): we move the window back over the main viewport
6273 SetWindowPos(window, g.Viewports[0]->Pos + style.DisplayWindowPadding, ImGuiCond_Always);
6274 }
6275 else
6276 {
6277 ImGuiPlatformMonitor& monitor = g.PlatformIO.Monitors[window->Viewport->PlatformMonitor];
6278 visibility_rect.Min = monitor.WorkPos + visibility_padding;
6279 visibility_rect.Max = monitor.WorkPos + monitor.WorkSize - visibility_padding;
6280 ClampWindowRect(window, visibility_rect);
6281 }
6282 }
6283 }
6284 window->Pos = ImFloor(window->Pos);
6285
6286 // Lock window rounding for the frame (so that altering them doesn't cause inconsistencies)
6287 // Large values tend to lead to variety of artifacts and are not recommended.
6288 if (window->ViewportOwned || window->DockIsActive)
6289 window->WindowRounding = 0.0f;
6290 else
6291 window->WindowRounding = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildRounding : ((flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupRounding : style.WindowRounding;
6292
6293 // For windows with title bar or menu bar, we clamp to FrameHeight(FontSize + FramePadding.y * 2.0f) to completely hide artifacts.
6294 //if ((window->Flags & ImGuiWindowFlags_MenuBar) || !(window->Flags & ImGuiWindowFlags_NoTitleBar))
6295 // window->WindowRounding = ImMin(window->WindowRounding, g.FontSize + style.FramePadding.y * 2.0f);
6296
6297 // Apply window focus (new and reactivated windows are moved to front)
6298 bool want_focus = false;
6299 if (window_just_activated_by_user && !(flags & ImGuiWindowFlags_NoFocusOnAppearing))
6300 {
6301 if (flags & ImGuiWindowFlags_Popup)
6302 want_focus = true;
6303 else if ((window->DockIsActive || (flags & ImGuiWindowFlags_ChildWindow) == 0) && !(flags & ImGuiWindowFlags_Tooltip))
6304 want_focus = true;
6305 }
6306
6307 // Decide if we are going to handle borders and resize grips
6308 const bool handle_borders_and_resize_grips = (window->DockNodeAsHost || !window->DockIsActive);
6309
6310 // Handle manual resize: Resize Grips, Borders, Gamepad
6311 int border_held = -1;
6312 ImU32 resize_grip_col[4] = {};
6313 const int resize_grip_count = g.IO.ConfigWindowsResizeFromEdges ? 2 : 1; // Allow resize from lower-left if we have the mouse cursor feedback for it.
6314 const float resize_grip_draw_size = IM_FLOOR(ImMax(g.FontSize * 1.35f, window->WindowRounding + 1.0f + g.FontSize * 0.2f));
6315 if (handle_borders_and_resize_grips && !window->Collapsed)
6316 if (UpdateWindowManualResize(window, size_auto_fit, &border_held, resize_grip_count, &resize_grip_col[0], visibility_rect))
6317 use_current_size_for_scrollbar_x = use_current_size_for_scrollbar_y = true;
6318 window->ResizeBorderHeld = (signed char)border_held;
6319
6320 // Synchronize window --> viewport again and one last time (clamping and manual resize may have affected either)
6321 if (window->ViewportOwned)
6322 {
6323 if (!window->Viewport->PlatformRequestMove)
6324 window->Viewport->Pos = window->Pos;
6325 if (!window->Viewport->PlatformRequestResize)
6326 window->Viewport->Size = window->Size;
6327 viewport_rect = window->Viewport->GetMainRect();
6328 }
6329
6330 // Save last known viewport position within the window itself (so it can be saved in .ini file and restored)
6331 window->ViewportPos = window->Viewport->Pos;
6332
6333 // SCROLLBAR VISIBILITY
6334
6335 // Update scrollbar visibility (based on the Size that was effective during last frame or the auto-resized Size).
6336 if (!window->Collapsed)
6337 {
6338 // When reading the current size we need to read it after size constraints have been applied.
6339 // When we use InnerRect here we are intentionally reading last frame size, same for ScrollbarSizes values before we set them again.
6340 ImVec2 avail_size_from_current_frame = ImVec2(window->SizeFull.x, window->SizeFull.y - decoration_up_height);
6341 ImVec2 avail_size_from_last_frame = window->InnerRect.GetSize() + window->ScrollbarSizes;
6342 ImVec2 needed_size_from_last_frame = window_just_created ? ImVec2(0, 0) : window->ContentSize + window->WindowPadding * 2.0f;
6343 float size_x_for_scrollbars = use_current_size_for_scrollbar_x ? avail_size_from_current_frame.x : avail_size_from_last_frame.x;
6344 float size_y_for_scrollbars = use_current_size_for_scrollbar_y ? avail_size_from_current_frame.y : avail_size_from_last_frame.y;
6345 //bool scrollbar_y_from_last_frame = window->ScrollbarY; // FIXME: May want to use that in the ScrollbarX expression? How many pros vs cons?
6346 window->ScrollbarY = (flags & ImGuiWindowFlags_AlwaysVerticalScrollbar) || ((needed_size_from_last_frame.y > size_y_for_scrollbars) && !(flags & ImGuiWindowFlags_NoScrollbar));
6347 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));
6348 if (window->ScrollbarX && !window->ScrollbarY)
6349 window->ScrollbarY = (needed_size_from_last_frame.y > size_y_for_scrollbars) && !(flags & ImGuiWindowFlags_NoScrollbar);
6350 window->ScrollbarSizes = ImVec2(window->ScrollbarY ? style.ScrollbarSize : 0.0f, window->ScrollbarX ? style.ScrollbarSize : 0.0f);
6351 }
6352
6353 // UPDATE RECTANGLES (1- THOSE NOT AFFECTED BY SCROLLING)
6354 // Update various regions. Variables they depends on should be set above in this function.
6355 // We set this up after processing the resize grip so that our rectangles doesn't lag by a frame.
6356
6357 // Outer rectangle
6358 // Not affected by window border size. Used by:
6359 // - FindHoveredWindow() (w/ extra padding when border resize is enabled)
6360 // - Begin() initial clipping rect for drawing window background and borders.
6361 // - Begin() clipping whole child
6362 const ImRect host_rect = ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) && !window_is_child_tooltip) ? parent_window->ClipRect : viewport_rect;
6363 const ImRect outer_rect = window->Rect();
6364 const ImRect title_bar_rect = window->TitleBarRect();
6365 window->OuterRectClipped = outer_rect;
6366 if (window->DockIsActive)
6367 window->OuterRectClipped.Min.y += window->TitleBarHeight();
6368 window->OuterRectClipped.ClipWith(host_rect);
6369
6370 // Inner rectangle
6371 // Not affected by window border size. Used by:
6372 // - InnerClipRect
6373 // - ScrollToBringRectIntoView()
6374 // - NavUpdatePageUpPageDown()
6375 // - Scrollbar()
6376 window->InnerRect.Min.x = window->Pos.x;
6377 window->InnerRect.Min.y = window->Pos.y + decoration_up_height;
6378 window->InnerRect.Max.x = window->Pos.x + window->Size.x - window->ScrollbarSizes.x;
6379 window->InnerRect.Max.y = window->Pos.y + window->Size.y - window->ScrollbarSizes.y;
6380
6381 // Inner clipping rectangle.
6382 // Will extend a little bit outside the normal work region.
6383 // This is to allow e.g. Selectable or CollapsingHeader or some separators to cover that space.
6384 // Force round operator last to ensure that e.g. (int)(max.x-min.x) in user's render code produce correct result.
6385 // Note that if our window is collapsed we will end up with an inverted (~null) clipping rectangle which is the correct behavior.
6386 // Affected by window/frame border size. Used by:
6387 // - Begin() initial clip rect
6388 float top_border_size = (((flags & ImGuiWindowFlags_MenuBar) || !(flags & ImGuiWindowFlags_NoTitleBar)) ? style.FrameBorderSize : window->WindowBorderSize);
6389 window->InnerClipRect.Min.x = ImFloor(0.5f + window->InnerRect.Min.x + ImMax(ImFloor(window->WindowPadding.x * 0.5f), window->WindowBorderSize));
6390 window->InnerClipRect.Min.y = ImFloor(0.5f + window->InnerRect.Min.y + top_border_size);
6391 window->InnerClipRect.Max.x = ImFloor(0.5f + window->InnerRect.Max.x - ImMax(ImFloor(window->WindowPadding.x * 0.5f), window->WindowBorderSize));
6392 window->InnerClipRect.Max.y = ImFloor(0.5f + window->InnerRect.Max.y - window->WindowBorderSize);
6393 window->InnerClipRect.ClipWithFull(host_rect);
6394
6395 // Default item width. Make it proportional to window size if window manually resizes
6396 if (window->Size.x > 0.0f && !(flags & ImGuiWindowFlags_Tooltip) && !(flags & ImGuiWindowFlags_AlwaysAutoResize))
6397 window->ItemWidthDefault = ImFloor(window->Size.x * 0.65f);
6398 else
6399 window->ItemWidthDefault = ImFloor(g.FontSize * 16.0f);
6400
6401 // SCROLLING
6402
6403 // Lock down maximum scrolling
6404 // The value of ScrollMax are ahead from ScrollbarX/ScrollbarY which is intentionally using InnerRect from previous rect in order to accommodate
6405 // for right/bottom aligned items without creating a scrollbar.
6406 window->ScrollMax.x = ImMax(0.0f, window->ContentSize.x + window->WindowPadding.x * 2.0f - window->InnerRect.GetWidth());
6407 window->ScrollMax.y = ImMax(0.0f, window->ContentSize.y + window->WindowPadding.y * 2.0f - window->InnerRect.GetHeight());
6408
6409 // Apply scrolling
6410 window->Scroll = CalcNextScrollFromScrollTargetAndClamp(window);
6411 window->ScrollTarget = ImVec2(FLT_MAX, FLT_MAX);
6412
6413 // DRAWING
6414
6415 // Setup draw list and outer clipping rectangle
6416 IM_ASSERT(window->DrawList->CmdBuffer.Size == 1 && window->DrawList->CmdBuffer[0].ElemCount == 0);
6417 window->DrawList->PushTextureID(g.Font->ContainerAtlas->TexID);
6418 PushClipRect(host_rect.Min, host_rect.Max, false);
6419
6420 // Draw modal or window list full viewport dimming background (for other viewports we'll render them in EndFrame)
6421 ImGuiWindow* window_window_list = g.NavWindowingListWindow;
6422 const bool dim_bg_for_modal = (flags & ImGuiWindowFlags_Modal) && window == GetTopMostPopupModal() && window->HiddenFramesCannotSkipItems <= 0;
6423 const bool dim_bg_for_window_list = g.NavWindowingTargetAnim && ((window == g.NavWindowingTargetAnim->RootWindow) || (window == window_window_list && window_window_list->Viewport != g.NavWindowingTargetAnim->Viewport));
6424 if (dim_bg_for_modal || dim_bg_for_window_list)
6425 {
6426 const ImU32 dim_bg_col = GetColorU32(dim_bg_for_modal ? ImGuiCol_ModalWindowDimBg : ImGuiCol_NavWindowingDimBg, g.DimBgRatio);
6427 window->DrawList->AddRectFilled(viewport_rect.Min, viewport_rect.Max, dim_bg_col);
6428 }
6429
6430 // Draw navigation selection/windowing rectangle background
6431 if (dim_bg_for_window_list && window == g.NavWindowingTargetAnim)
6432 {
6433 ImRect bb = window->Rect();
6434 bb.Expand(g.FontSize);
6435 if (!bb.Contains(viewport_rect)) // Avoid drawing if the window covers all the viewport anyway
6436 window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha * 0.25f), g.Style.WindowRounding);
6437 }
6438
6439 // Since 1.71, child window can render their decoration (bg color, border, scrollbars, etc.) within their parent to save a draw call.
6440 // When using overlapping child windows, this will break the assumption that child z-order is mapped to submission order.
6441 // We disable this when the parent window has zero vertices, which is a common pattern leading to laying out multiple overlapping child.
6442 // We also disabled this when we have dimming overlay behind this specific one child.
6443 // FIXME: More code may rely on explicit sorting of overlapping child window and would need to disable this somehow. Please get in contact if you are affected.
6444 const bool is_undocked_or_docked_visible = !window->DockIsActive || window->DockTabIsVisible;
6445 if (is_undocked_or_docked_visible)
6446 {
6447 bool render_decorations_in_parent = false;
6448 if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) && !window_is_child_tooltip)
6449 if (window->DrawList->CmdBuffer.back().ElemCount == 0 && parent_window->DrawList->VtxBuffer.Size > 0)
6450 render_decorations_in_parent = true;
6451 if (render_decorations_in_parent)
6452 window->DrawList = parent_window->DrawList;
6453
6454 // Handle title bar, scrollbar, resize grips and resize borders
6455 const ImGuiWindow* window_to_highlight = g.NavWindowingTarget ? g.NavWindowingTarget : g.NavWindow;
6456 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)));
6457 RenderWindowDecorations(window, title_bar_rect, title_bar_is_highlight, handle_borders_and_resize_grips, resize_grip_count, resize_grip_col, resize_grip_draw_size);
6458
6459 if (render_decorations_in_parent)
6460 window->DrawList = &window->DrawListInst;
6461 }
6462
6463 // Draw navigation selection/windowing rectangle border
6464 if (g.NavWindowingTargetAnim == window)
6465 {
6466 float rounding = ImMax(window->WindowRounding, g.Style.WindowRounding);
6467 ImRect bb = window->Rect();
6468 bb.Expand(g.FontSize);
6469 if (bb.Contains(viewport_rect)) // If a window fits the entire viewport, adjust its highlight inward
6470 {
6471 bb.Expand(-g.FontSize - 1.0f);
6472 rounding = window->WindowRounding;
6473 }
6474 window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha), rounding, ~0, 3.0f);
6475 }
6476
6477 // UPDATE RECTANGLES (2- THOSE AFFECTED BY SCROLLING)
6478
6479 // Work rectangle.
6480 // Affected by window padding and border size. Used by:
6481 // - Columns() for right-most edge
6482 // - TreeNode(), CollapsingHeader() for right-most edge
6483 // - BeginTabBar() for right-most edge
6484 const bool allow_scrollbar_x = !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar);
6485 const bool allow_scrollbar_y = !(flags & ImGuiWindowFlags_NoScrollbar);
6486 const float work_rect_size_x = (window->ContentSizeExplicit.x != 0.0f ? window->ContentSizeExplicit.x : ImMax(allow_scrollbar_x ? window->ContentSize.x : 0.0f, window->Size.x - window->WindowPadding.x * 2.0f - window->ScrollbarSizes.x));
6487 const float work_rect_size_y = (window->ContentSizeExplicit.y != 0.0f ? window->ContentSizeExplicit.y : ImMax(allow_scrollbar_y ? window->ContentSize.y : 0.0f, window->Size.y - window->WindowPadding.y * 2.0f - decoration_up_height - window->ScrollbarSizes.y));
6488 window->WorkRect.Min.x = ImFloor(window->InnerRect.Min.x - window->Scroll.x + ImMax(window->WindowPadding.x, window->WindowBorderSize));
6489 window->WorkRect.Min.y = ImFloor(window->InnerRect.Min.y - window->Scroll.y + ImMax(window->WindowPadding.y, window->WindowBorderSize));
6490 window->WorkRect.Max.x = window->WorkRect.Min.x + work_rect_size_x;
6491 window->WorkRect.Max.y = window->WorkRect.Min.y + work_rect_size_y;
6492 window->ParentWorkRect = window->WorkRect;
6493
6494 // [LEGACY] Content Region
6495 // FIXME-OBSOLETE: window->ContentRegionRect.Max is currently very misleading / partly faulty, but some BeginChild() patterns relies on it.
6496 // Used by:
6497 // - Mouse wheel scrolling + many other things
6498 window->ContentRegionRect.Min.x = window->Pos.x - window->Scroll.x + window->WindowPadding.x;
6499 window->ContentRegionRect.Min.y = window->Pos.y - window->Scroll.y + window->WindowPadding.y + decoration_up_height;
6500 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));
6501 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));
6502
6503 // Setup drawing context
6504 // (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.)
6505 window->DC.Indent.x = 0.0f + window->WindowPadding.x - window->Scroll.x;
6506 window->DC.GroupOffset.x = 0.0f;
6507 window->DC.ColumnsOffset.x = 0.0f;
6508 window->DC.CursorStartPos = window->Pos + ImVec2(window->DC.Indent.x + window->DC.ColumnsOffset.x, decoration_up_height + window->WindowPadding.y - window->Scroll.y);
6509 window->DC.CursorPos = window->DC.CursorStartPos;
6510 window->DC.CursorPosPrevLine = window->DC.CursorPos;
6511 window->DC.CursorMaxPos = window->DC.CursorStartPos;
6512 window->DC.CurrLineSize = window->DC.PrevLineSize = ImVec2(0.0f, 0.0f);
6513 window->DC.CurrLineTextBaseOffset = window->DC.PrevLineTextBaseOffset = 0.0f;
6514
6515 window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
6516 window->DC.NavLayerCurrentMask = (1 << ImGuiNavLayer_Main);
6517 window->DC.NavLayerActiveMask = window->DC.NavLayerActiveMaskNext;
6518 window->DC.NavLayerActiveMaskNext = 0x00;
6519 window->DC.NavFocusScopeIdCurrent = (flags & ImGuiWindowFlags_ChildWindow) ? parent_window->DC.NavFocusScopeIdCurrent : 0; // -V595
6520 window->DC.NavHideHighlightOneFrame = false;
6521 window->DC.NavHasScroll = (window->ScrollMax.y > 0.0f);
6522
6523 window->DC.MenuBarAppending = false;
6524 window->DC.MenuColumns.Update(3, style.ItemSpacing.x, window_just_activated_by_user);
6525 window->DC.TreeDepth = 0;
6526 window->DC.TreeJumpToParentOnPopMask = 0x00;
6527 window->DC.ChildWindows.resize(0);
6528 window->DC.StateStorage = &window->StateStorage;
6529 window->DC.CurrentColumns = NULL;
6530 window->DC.LayoutType = ImGuiLayoutType_Vertical;
6531 window->DC.ParentLayoutType = parent_window ? parent_window->DC.LayoutType : ImGuiLayoutType_Vertical;
6532 window->DC.FocusCounterRegular = window->DC.FocusCounterTabStop = -1;
6533
6534 window->DC.ItemWidth = window->ItemWidthDefault;
6535 window->DC.TextWrapPos = -1.0f; // disabled
6536 window->DC.ItemFlagsStack.resize(0);
6537 window->DC.ItemWidthStack.resize(0);
6538 window->DC.TextWrapPosStack.resize(0);
6539 window->DC.GroupStack.resize(0);
6540 window->DC.ItemFlags = parent_window ? parent_window->DC.ItemFlags : ImGuiItemFlags_Default_;
6541 if (parent_window)
6542 window->DC.ItemFlagsStack.push_back(window->DC.ItemFlags);
6543
6544 if (window->AutoFitFramesX > 0)
6545 window->AutoFitFramesX--;
6546 if (window->AutoFitFramesY > 0)
6547 window->AutoFitFramesY--;
6548
6549 // Apply focus (we need to call FocusWindow() AFTER setting DC.CursorStartPos so our initial navigation reference rectangle can start around there)
6550 if (want_focus)
6551 {
6552 FocusWindow(window);
6553 NavInitWindow(window, false);
6554 }
6555
6556 // Close requested by platform window
6557 if (p_open != NULL && window->Viewport->PlatformRequestClose && window->Viewport != GetMainViewport())
6558 {
6559 if (!window->DockIsActive || window->DockTabIsVisible)
6560 {
6561 window->Viewport->PlatformRequestClose = false;
6562 g.NavWindowingToggleLayer = false; // Assume user mapped PlatformRequestClose on ALT-F4 so we disable ALT for menu toggle. False positive not an issue.
6563 IMGUI_DEBUG_LOG_VIEWPORT("Window '%s' PlatformRequestClose\n", window->Name);
6564 *p_open = false;
6565 }
6566 }
6567
6568 // Title bar
6569 if (!(flags & ImGuiWindowFlags_NoTitleBar) && !window->DockIsActive)
6570 RenderWindowTitleBarContents(window, title_bar_rect, name, p_open);
6571
6572 // Clear hit test shape every frame
6573 window->HitTestHoleSize.x = window->HitTestHoleSize.y = 0;
6574
6575 // Pressing CTRL+C while holding on a window copy its content to the clipboard
6576 // 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.
6577 // Maybe we can support CTRL+C on every element?
6578 /*
6579 if (g.ActiveId == move_id)
6580 if (g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_C))
6581 LogToClipboard();
6582 */
6583
6584 if (g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable)
6585 {
6586 // Docking: Dragging a dockable window (or any of its child) turns it into a drag and drop source.
6587 // We need to do this _before_ we overwrite window->DC.LastItemId below because BeginDockableDragDropSource() also overwrites it.
6588 if ((g.MovingWindow == window) && (g.IO.ConfigDockingWithShift == g.IO.KeyShift))
6589 if ((window->RootWindow->Flags & ImGuiWindowFlags_NoDocking) == 0)
6590 BeginDockableDragDropSource(window);
6591
6592 // Docking: Any dockable window can act as a target. For dock node hosts we call BeginDockableDragDropTarget() in DockNodeUpdate() instead.
6593 if (g.DragDropActive && !(flags & ImGuiWindowFlags_NoDocking))
6594 if (g.MovingWindow == NULL || g.MovingWindow->RootWindow != window)
6595 if ((window == window->RootWindow) && !(window->Flags & ImGuiWindowFlags_DockNodeHost))
6596 BeginDockableDragDropTarget(window);
6597 }
6598
6599 // We fill last item data based on Title Bar/Tab, in order for IsItemHovered() and IsItemActive() to be usable after Begin().
6600 // This is useful to allow creating context menus on title bar only, etc.
6601 if (window->DockIsActive)
6602 SetLastItemData(window, window->ID, window->DockTabItemStatusFlags, window->DockTabItemRect);
6603 else
6604 SetLastItemData(window, window->MoveId, IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0, title_bar_rect);
6605
6606 #ifdef IMGUI_ENABLE_TEST_ENGINE
6607 if (!(window->Flags & ImGuiWindowFlags_NoTitleBar))
6608 IMGUI_TEST_ENGINE_ITEM_ADD(window->DC.LastItemRect, window->DC.LastItemId);
6609 #endif
6610 }
6611 else
6612 {
6613 // Append
6614 SetCurrentViewport(window, window->Viewport);
6615 SetCurrentWindow(window);
6616 }
6617
6618 if (!(flags & ImGuiWindowFlags_DockNodeHost))
6619 PushClipRect(window->InnerClipRect.Min, window->InnerClipRect.Max, true);
6620
6621 // 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)
6622 if (first_begin_of_the_frame)
6623 window->WriteAccessed = false;
6624
6625 window->BeginCount++;
6626 g.NextWindowData.ClearFlags();
6627
6628 // When we are about to select this tab (which will only be visible on the _next frame_), flag it with a non-zero HiddenFramesCannotSkipItems.
6629 // This will have the important effect of actually returning true in Begin() and not setting SkipItems, allowing an earlier submission of the window contents.
6630 // This is analogous to regular windows being hidden from one frame.
6631 // 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.
6632 if (window->DockIsActive && !window->DockTabIsVisible)
6633 {
6634 if (window->LastFrameJustFocused == g.FrameCount)
6635 window->HiddenFramesCannotSkipItems = 1;
6636 else
6637 window->HiddenFramesCanSkipItems = 1;
6638 }
6639
6640 if (flags & ImGuiWindowFlags_ChildWindow)
6641 {
6642 // Child window can be out of sight and have "negative" clip windows.
6643 // Mark them as collapsed so commands are skipped earlier (we can't manually collapse them because they have no title bar).
6644 IM_ASSERT((flags & ImGuiWindowFlags_NoTitleBar) != 0 || (window->DockIsActive));
6645 if (!(flags & ImGuiWindowFlags_AlwaysAutoResize) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0)
6646 if (window->OuterRectClipped.Min.x >= window->OuterRectClipped.Max.x || window->OuterRectClipped.Min.y >= window->OuterRectClipped.Max.y)
6647 window->HiddenFramesCanSkipItems = 1;
6648
6649 // Hide along with parent or if parent is collapsed
6650 if (parent_window && (parent_window->Collapsed || parent_window->HiddenFramesCanSkipItems > 0))
6651 window->HiddenFramesCanSkipItems = 1;
6652 if (parent_window && (parent_window->Collapsed || parent_window->HiddenFramesCannotSkipItems > 0))
6653 window->HiddenFramesCannotSkipItems = 1;
6654 }
6655
6656 // 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)
6657 if (style.Alpha <= 0.0f)
6658 window->HiddenFramesCanSkipItems = 1;
6659
6660 // Update the Hidden flag
6661 window->Hidden = (window->HiddenFramesCanSkipItems > 0) || (window->HiddenFramesCannotSkipItems > 0);
6662
6663 // Update the SkipItems flag, used to early out of all items functions (no layout required)
6664 bool skip_items = false;
6665 if (window->Collapsed || !window->Active || window->Hidden)
6666 if (window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0 && window->HiddenFramesCannotSkipItems <= 0)
6667 skip_items = true;
6668 window->SkipItems = skip_items;
6669
6670 return !skip_items;
6671 }
6672
End()6673 void ImGui::End()
6674 {
6675 ImGuiContext& g = *GImGui;
6676 ImGuiWindow* window = g.CurrentWindow;
6677
6678 // Error checking: verify that user hasn't called End() too many times!
6679 if (g.CurrentWindowStack.Size <= 1 && g.WithinFrameScopeWithImplicitWindow)
6680 {
6681 IM_ASSERT_USER_ERROR(g.CurrentWindowStack.Size > 1, "Calling End() too many times!");
6682 return;
6683 }
6684 IM_ASSERT(g.CurrentWindowStack.Size > 0);
6685
6686 // Error checking: verify that user doesn't directly call End() on a child window.
6687 if ((window->Flags & ImGuiWindowFlags_ChildWindow) && !window->DockIsActive)
6688 IM_ASSERT_USER_ERROR(g.WithinEndChild, "Must call EndChild() and not End()!");
6689
6690 // Close anything that is open
6691 if (window->DC.CurrentColumns)
6692 EndColumns();
6693 if (!(window->Flags & ImGuiWindowFlags_DockNodeHost)) // Pop inner window clip rectangle
6694 PopClipRect();
6695
6696 // Stop logging
6697 if (!(window->Flags & ImGuiWindowFlags_ChildWindow)) // FIXME: add more options for scope of logging
6698 LogFinish();
6699
6700 // Docking: report contents sizes to parent to allow for auto-resize
6701 if (window->DockNode && window->DockTabIsVisible)
6702 if (ImGuiWindow* host_window = window->DockNode->HostWindow) // FIXME-DOCK
6703 host_window->DC.CursorMaxPos = window->DC.CursorMaxPos + window->WindowPadding - host_window->WindowPadding;
6704
6705 // Pop from window stack
6706 g.CurrentWindowStack.pop_back();
6707 if (window->Flags & ImGuiWindowFlags_Popup)
6708 g.BeginPopupStack.pop_back();
6709 ErrorCheckBeginEndCompareStacksSize(window, false);
6710 SetCurrentWindow(g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back());
6711 if (g.CurrentWindow)
6712 SetCurrentViewport(g.CurrentWindow, g.CurrentWindow->Viewport);
6713 }
6714
BringWindowToFocusFront(ImGuiWindow * window)6715 void ImGui::BringWindowToFocusFront(ImGuiWindow* window)
6716 {
6717 ImGuiContext& g = *GImGui;
6718 if (g.WindowsFocusOrder.back() == window)
6719 return;
6720 for (int i = g.WindowsFocusOrder.Size - 2; i >= 0; i--) // We can ignore the top-most window
6721 if (g.WindowsFocusOrder[i] == window)
6722 {
6723 memmove(&g.WindowsFocusOrder[i], &g.WindowsFocusOrder[i + 1], (size_t)(g.WindowsFocusOrder.Size - i - 1) * sizeof(ImGuiWindow*));
6724 g.WindowsFocusOrder[g.WindowsFocusOrder.Size - 1] = window;
6725 break;
6726 }
6727 }
6728
BringWindowToDisplayFront(ImGuiWindow * window)6729 void ImGui::BringWindowToDisplayFront(ImGuiWindow* window)
6730 {
6731 ImGuiContext& g = *GImGui;
6732 ImGuiWindow* current_front_window = g.Windows.back();
6733 if (current_front_window == window || current_front_window->RootWindow == window) // Cheap early out (could be better)
6734 return;
6735 for (int i = g.Windows.Size - 2; i >= 0; i--) // We can ignore the top-most window
6736 if (g.Windows[i] == window)
6737 {
6738 memmove(&g.Windows[i], &g.Windows[i + 1], (size_t)(g.Windows.Size - i - 1) * sizeof(ImGuiWindow*));
6739 g.Windows[g.Windows.Size - 1] = window;
6740 break;
6741 }
6742 }
6743
BringWindowToDisplayBack(ImGuiWindow * window)6744 void ImGui::BringWindowToDisplayBack(ImGuiWindow* window)
6745 {
6746 ImGuiContext& g = *GImGui;
6747 if (g.Windows[0] == window)
6748 return;
6749 for (int i = 0; i < g.Windows.Size; i++)
6750 if (g.Windows[i] == window)
6751 {
6752 memmove(&g.Windows[1], &g.Windows[0], (size_t)i * sizeof(ImGuiWindow*));
6753 g.Windows[0] = window;
6754 break;
6755 }
6756 }
6757
6758 // Moving window to front of display and set focus (which happens to be back of our sorted list)
FocusWindow(ImGuiWindow * window)6759 void ImGui::FocusWindow(ImGuiWindow* window)
6760 {
6761 ImGuiContext& g = *GImGui;
6762
6763 if (g.NavWindow != window)
6764 {
6765 g.NavWindow = window;
6766 if (window && g.NavDisableMouseHover)
6767 g.NavMousePosDirty = true;
6768 g.NavInitRequest = false;
6769 g.NavId = window ? window->NavLastIds[0] : 0; // Restore NavId
6770 g.NavFocusScopeId = 0;
6771 g.NavIdIsAlive = false;
6772 g.NavLayer = ImGuiNavLayer_Main;
6773 //IMGUI_DEBUG_LOG("FocusWindow(\"%s\")\n", window ? window->Name : NULL);
6774 }
6775
6776 // Close popups if any
6777 ClosePopupsOverWindow(window, false);
6778
6779 // Move the root window to the top of the pile
6780 IM_ASSERT(window == NULL || window->RootWindow != NULL);
6781 ImGuiWindow* focus_front_window = window ? window->RootWindowDockStop : NULL;
6782 ImGuiWindow* display_front_window = window ? window->RootWindow : NULL;
6783 ImGuiDockNode* dock_node = window ? window->DockNode : NULL;
6784 bool active_id_window_is_dock_node_host = (g.ActiveIdWindow && dock_node && dock_node->HostWindow == g.ActiveIdWindow);
6785
6786 // Steal active widgets. Some of the cases it triggers includes:
6787 // - Focus a window while an InputText in another window is active, if focus happens before the old InputText can run.
6788 // - When using Nav to activate menu items (due to timing of activating on press->new window appears->losing ActiveId)
6789 // - Using dock host items (tab, collapse button) can trigger this before we redirect the ActiveIdWindow toward the child window.
6790 if (g.ActiveId != 0 && g.ActiveIdWindow && g.ActiveIdWindow->RootWindowDockStop != focus_front_window)
6791 if (!g.ActiveIdNoClearOnFocusLoss && !active_id_window_is_dock_node_host)
6792 ClearActiveID();
6793
6794 // Passing NULL allow to disable keyboard focus
6795 if (!window)
6796 return;
6797 window->LastFrameJustFocused = g.FrameCount;
6798
6799 // Select in dock node
6800 if (dock_node && dock_node->TabBar)
6801 dock_node->TabBar->SelectedTabId = dock_node->TabBar->NextSelectedTabId = window->ID;
6802
6803 // Bring to front
6804 BringWindowToFocusFront(focus_front_window);
6805 if (((window->Flags | focus_front_window->Flags | display_front_window->Flags) & ImGuiWindowFlags_NoBringToFrontOnFocus) == 0)
6806 BringWindowToDisplayFront(display_front_window);
6807 }
6808
FocusTopMostWindowUnderOne(ImGuiWindow * under_this_window,ImGuiWindow * ignore_window)6809 void ImGui::FocusTopMostWindowUnderOne(ImGuiWindow* under_this_window, ImGuiWindow* ignore_window)
6810 {
6811 ImGuiContext& g = *GImGui;
6812
6813 int start_idx = g.WindowsFocusOrder.Size - 1;
6814 if (under_this_window != NULL)
6815 {
6816 int under_this_window_idx = FindWindowFocusIndex(under_this_window);
6817 if (under_this_window_idx != -1)
6818 start_idx = under_this_window_idx - 1;
6819 }
6820 for (int i = start_idx; i >= 0; i--)
6821 {
6822 // 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.
6823 ImGuiWindow* window = g.WindowsFocusOrder[i];
6824 if (window != ignore_window && window->WasActive && window->RootWindowDockStop == window)
6825 if ((window->Flags & (ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs)) != (ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoNavInputs))
6826 {
6827 // FIXME-DOCK: This is failing (lagging by one frame) for docked windows.
6828 // If A and B are docked into window and B disappear, at the NewFrame() call site window->NavLastChildNavWindow will still point to B.
6829 // 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)
6830 // to tell which is the "previous" window. Or we may leverage 'LastFrameFocused/LastFrameJustFocused' and have this function handle child window itself?
6831 ImGuiWindow* focus_window = NavRestoreLastChildNavWindow(window);
6832 FocusWindow(focus_window);
6833 return;
6834 }
6835 }
6836 FocusWindow(NULL);
6837 }
6838
SetCurrentFont(ImFont * font)6839 void ImGui::SetCurrentFont(ImFont* font)
6840 {
6841 ImGuiContext& g = *GImGui;
6842 IM_ASSERT(font && font->IsLoaded()); // Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ?
6843 IM_ASSERT(font->Scale > 0.0f);
6844 g.Font = font;
6845 g.FontBaseSize = ImMax(1.0f, g.IO.FontGlobalScale * g.Font->FontSize * g.Font->Scale);
6846 g.FontSize = g.CurrentWindow ? g.CurrentWindow->CalcFontSize() : 0.0f;
6847
6848 ImFontAtlas* atlas = g.Font->ContainerAtlas;
6849 g.DrawListSharedData.TexUvWhitePixel = atlas->TexUvWhitePixel;
6850 g.DrawListSharedData.TexUvLines = atlas->TexUvLines;
6851 g.DrawListSharedData.Font = g.Font;
6852 g.DrawListSharedData.FontSize = g.FontSize;
6853 }
6854
PushFont(ImFont * font)6855 void ImGui::PushFont(ImFont* font)
6856 {
6857 ImGuiContext& g = *GImGui;
6858 if (!font)
6859 font = GetDefaultFont();
6860 SetCurrentFont(font);
6861 g.FontStack.push_back(font);
6862 g.CurrentWindow->DrawList->PushTextureID(font->ContainerAtlas->TexID);
6863 }
6864
PopFont()6865 void ImGui::PopFont()
6866 {
6867 ImGuiContext& g = *GImGui;
6868 g.CurrentWindow->DrawList->PopTextureID();
6869 g.FontStack.pop_back();
6870 SetCurrentFont(g.FontStack.empty() ? GetDefaultFont() : g.FontStack.back());
6871 }
6872
PushItemFlag(ImGuiItemFlags option,bool enabled)6873 void ImGui::PushItemFlag(ImGuiItemFlags option, bool enabled)
6874 {
6875 ImGuiWindow* window = GetCurrentWindow();
6876 if (enabled)
6877 window->DC.ItemFlags |= option;
6878 else
6879 window->DC.ItemFlags &= ~option;
6880 window->DC.ItemFlagsStack.push_back(window->DC.ItemFlags);
6881 }
6882
PopItemFlag()6883 void ImGui::PopItemFlag()
6884 {
6885 ImGuiWindow* window = GetCurrentWindow();
6886 window->DC.ItemFlagsStack.pop_back();
6887 window->DC.ItemFlags = window->DC.ItemFlagsStack.empty() ? ImGuiItemFlags_Default_ : window->DC.ItemFlagsStack.back();
6888 }
6889
6890 // FIXME: Look into renaming this once we have settled the new Focus/Activation/TabStop system.
PushAllowKeyboardFocus(bool allow_keyboard_focus)6891 void ImGui::PushAllowKeyboardFocus(bool allow_keyboard_focus)
6892 {
6893 PushItemFlag(ImGuiItemFlags_NoTabStop, !allow_keyboard_focus);
6894 }
6895
PopAllowKeyboardFocus()6896 void ImGui::PopAllowKeyboardFocus()
6897 {
6898 PopItemFlag();
6899 }
6900
PushButtonRepeat(bool repeat)6901 void ImGui::PushButtonRepeat(bool repeat)
6902 {
6903 PushItemFlag(ImGuiItemFlags_ButtonRepeat, repeat);
6904 }
6905
PopButtonRepeat()6906 void ImGui::PopButtonRepeat()
6907 {
6908 PopItemFlag();
6909 }
6910
PushTextWrapPos(float wrap_pos_x)6911 void ImGui::PushTextWrapPos(float wrap_pos_x)
6912 {
6913 ImGuiWindow* window = GetCurrentWindow();
6914 window->DC.TextWrapPos = wrap_pos_x;
6915 window->DC.TextWrapPosStack.push_back(wrap_pos_x);
6916 }
6917
PopTextWrapPos()6918 void ImGui::PopTextWrapPos()
6919 {
6920 ImGuiWindow* window = GetCurrentWindow();
6921 window->DC.TextWrapPosStack.pop_back();
6922 window->DC.TextWrapPos = window->DC.TextWrapPosStack.empty() ? -1.0f : window->DC.TextWrapPosStack.back();
6923 }
6924
IsWindowChildOf(ImGuiWindow * window,ImGuiWindow * potential_parent)6925 bool ImGui::IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent)
6926 {
6927 if (window->RootWindow == potential_parent)
6928 return true;
6929 while (window != NULL)
6930 {
6931 if (window == potential_parent)
6932 return true;
6933 window = window->ParentWindow;
6934 }
6935 return false;
6936 }
6937
IsWindowHovered(ImGuiHoveredFlags flags)6938 bool ImGui::IsWindowHovered(ImGuiHoveredFlags flags)
6939 {
6940 IM_ASSERT((flags & ImGuiHoveredFlags_AllowWhenOverlapped) == 0); // Flags not supported by this function
6941 ImGuiContext& g = *GImGui;
6942
6943 if (flags & ImGuiHoveredFlags_AnyWindow)
6944 {
6945 if (g.HoveredWindow == NULL)
6946 return false;
6947 }
6948 else
6949 {
6950 switch (flags & (ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows))
6951 {
6952 case ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows:
6953 if (g.HoveredWindow == NULL || g.HoveredWindow->RootWindowDockStop != g.CurrentWindow->RootWindowDockStop)
6954 return false;
6955 break;
6956 case ImGuiHoveredFlags_RootWindow:
6957 if (g.HoveredWindow != g.CurrentWindow->RootWindowDockStop)
6958 return false;
6959 break;
6960 case ImGuiHoveredFlags_ChildWindows:
6961 if (g.HoveredWindow == NULL || !IsWindowChildOf(g.HoveredWindow, g.CurrentWindow))
6962 return false;
6963 break;
6964 default:
6965 if (g.HoveredWindow != g.CurrentWindow)
6966 return false;
6967 break;
6968 }
6969 }
6970
6971 if (!IsWindowContentHoverable(g.HoveredWindow, flags))
6972 return false;
6973 if (!(flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
6974 if (g.ActiveId != 0 && !g.ActiveIdAllowOverlap && g.ActiveId != g.HoveredWindow->MoveId)
6975 return false;
6976 return true;
6977 }
6978
IsWindowFocused(ImGuiFocusedFlags flags)6979 bool ImGui::IsWindowFocused(ImGuiFocusedFlags flags)
6980 {
6981 ImGuiContext& g = *GImGui;
6982
6983 if (flags & ImGuiFocusedFlags_AnyWindow)
6984 return g.NavWindow != NULL;
6985
6986 IM_ASSERT(g.CurrentWindow); // Not inside a Begin()/End()
6987 switch (flags & (ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows))
6988 {
6989 case ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows:
6990 return g.NavWindow && g.NavWindow->RootWindowDockStop == g.CurrentWindow->RootWindowDockStop;
6991 case ImGuiFocusedFlags_RootWindow:
6992 return g.NavWindow == g.CurrentWindow->RootWindowDockStop;
6993 case ImGuiFocusedFlags_ChildWindows:
6994 return g.NavWindow && IsWindowChildOf(g.NavWindow, g.CurrentWindow);
6995 default:
6996 return g.NavWindow == g.CurrentWindow;
6997 }
6998 }
6999
GetWindowDockID()7000 ImGuiID ImGui::GetWindowDockID()
7001 {
7002 ImGuiContext& g = *GImGui;
7003 return g.CurrentWindow->DockId;
7004 }
7005
IsWindowDocked()7006 bool ImGui::IsWindowDocked()
7007 {
7008 ImGuiContext& g = *GImGui;
7009 return g.CurrentWindow->DockIsActive;
7010 }
7011
7012 // Can we focus this window with CTRL+TAB (or PadMenu + PadFocusPrev/PadFocusNext)
7013 // Note that NoNavFocus makes the window not reachable with CTRL+TAB but it can still be focused with mouse or programmatically.
7014 // If you want a window to never be focused, you may use the e.g. NoInputs flag.
IsWindowNavFocusable(ImGuiWindow * window)7015 bool ImGui::IsWindowNavFocusable(ImGuiWindow* window)
7016 {
7017 return window->Active && window == window->RootWindowDockStop && !(window->Flags & ImGuiWindowFlags_NoNavFocus);
7018 }
7019
GetWindowWidth()7020 float ImGui::GetWindowWidth()
7021 {
7022 ImGuiWindow* window = GImGui->CurrentWindow;
7023 return window->Size.x;
7024 }
7025
GetWindowHeight()7026 float ImGui::GetWindowHeight()
7027 {
7028 ImGuiWindow* window = GImGui->CurrentWindow;
7029 return window->Size.y;
7030 }
7031
GetWindowPos()7032 ImVec2 ImGui::GetWindowPos()
7033 {
7034 ImGuiContext& g = *GImGui;
7035 ImGuiWindow* window = g.CurrentWindow;
7036 return window->Pos;
7037 }
7038
SetWindowPos(ImGuiWindow * window,const ImVec2 & pos,ImGuiCond cond)7039 void ImGui::SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond)
7040 {
7041 // Test condition (NB: bit 0 is always true) and clear flags for next time
7042 if (cond && (window->SetWindowPosAllowFlags & cond) == 0)
7043 return;
7044
7045 IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
7046 window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
7047 window->SetWindowPosVal = ImVec2(FLT_MAX, FLT_MAX);
7048
7049 // Set
7050 const ImVec2 old_pos = window->Pos;
7051 window->Pos = ImFloor(pos);
7052 ImVec2 offset = window->Pos - old_pos;
7053 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
7054 window->DC.CursorMaxPos += offset; // And more importantly we need to offset CursorMaxPos/CursorStartPos this so ContentSize calculation doesn't get affected.
7055 window->DC.CursorStartPos += offset;
7056 }
7057
SetWindowPos(const ImVec2 & pos,ImGuiCond cond)7058 void ImGui::SetWindowPos(const ImVec2& pos, ImGuiCond cond)
7059 {
7060 ImGuiWindow* window = GetCurrentWindowRead();
7061 SetWindowPos(window, pos, cond);
7062 }
7063
SetWindowPos(const char * name,const ImVec2 & pos,ImGuiCond cond)7064 void ImGui::SetWindowPos(const char* name, const ImVec2& pos, ImGuiCond cond)
7065 {
7066 if (ImGuiWindow* window = FindWindowByName(name))
7067 SetWindowPos(window, pos, cond);
7068 }
7069
GetWindowSize()7070 ImVec2 ImGui::GetWindowSize()
7071 {
7072 ImGuiWindow* window = GetCurrentWindowRead();
7073 return window->Size;
7074 }
7075
SetWindowSize(ImGuiWindow * window,const ImVec2 & size,ImGuiCond cond)7076 void ImGui::SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond)
7077 {
7078 // Test condition (NB: bit 0 is always true) and clear flags for next time
7079 if (cond && (window->SetWindowSizeAllowFlags & cond) == 0)
7080 return;
7081
7082 IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
7083 window->SetWindowSizeAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
7084
7085 // Set
7086 if (size.x > 0.0f)
7087 {
7088 window->AutoFitFramesX = 0;
7089 window->SizeFull.x = IM_FLOOR(size.x);
7090 }
7091 else
7092 {
7093 window->AutoFitFramesX = 2;
7094 window->AutoFitOnlyGrows = false;
7095 }
7096 if (size.y > 0.0f)
7097 {
7098 window->AutoFitFramesY = 0;
7099 window->SizeFull.y = IM_FLOOR(size.y);
7100 }
7101 else
7102 {
7103 window->AutoFitFramesY = 2;
7104 window->AutoFitOnlyGrows = false;
7105 }
7106 }
7107
SetWindowSize(const ImVec2 & size,ImGuiCond cond)7108 void ImGui::SetWindowSize(const ImVec2& size, ImGuiCond cond)
7109 {
7110 SetWindowSize(GImGui->CurrentWindow, size, cond);
7111 }
7112
SetWindowSize(const char * name,const ImVec2 & size,ImGuiCond cond)7113 void ImGui::SetWindowSize(const char* name, const ImVec2& size, ImGuiCond cond)
7114 {
7115 if (ImGuiWindow* window = FindWindowByName(name))
7116 SetWindowSize(window, size, cond);
7117 }
7118
SetWindowCollapsed(ImGuiWindow * window,bool collapsed,ImGuiCond cond)7119 void ImGui::SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond)
7120 {
7121 // Test condition (NB: bit 0 is always true) and clear flags for next time
7122 if (cond && (window->SetWindowCollapsedAllowFlags & cond) == 0)
7123 return;
7124 window->SetWindowCollapsedAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
7125
7126 // Set
7127 window->Collapsed = collapsed;
7128 }
7129
SetWindowHitTestHole(ImGuiWindow * window,const ImVec2 & pos,const ImVec2 & size)7130 void ImGui::SetWindowHitTestHole(ImGuiWindow* window, const ImVec2& pos, const ImVec2& size)
7131 {
7132 IM_ASSERT(window->HitTestHoleSize.x == 0); // We don't support multiple holes/hit test filters
7133 window->HitTestHoleSize = ImVec2ih(size);
7134 window->HitTestHoleOffset = ImVec2ih(pos - window->Pos);
7135 }
7136
SetWindowCollapsed(bool collapsed,ImGuiCond cond)7137 void ImGui::SetWindowCollapsed(bool collapsed, ImGuiCond cond)
7138 {
7139 SetWindowCollapsed(GImGui->CurrentWindow, collapsed, cond);
7140 }
7141
IsWindowCollapsed()7142 bool ImGui::IsWindowCollapsed()
7143 {
7144 ImGuiWindow* window = GetCurrentWindowRead();
7145 return window->Collapsed;
7146 }
7147
IsWindowAppearing()7148 bool ImGui::IsWindowAppearing()
7149 {
7150 ImGuiWindow* window = GetCurrentWindowRead();
7151 return window->Appearing;
7152 }
7153
SetWindowCollapsed(const char * name,bool collapsed,ImGuiCond cond)7154 void ImGui::SetWindowCollapsed(const char* name, bool collapsed, ImGuiCond cond)
7155 {
7156 if (ImGuiWindow* window = FindWindowByName(name))
7157 SetWindowCollapsed(window, collapsed, cond);
7158 }
7159
SetWindowFocus()7160 void ImGui::SetWindowFocus()
7161 {
7162 FocusWindow(GImGui->CurrentWindow);
7163 }
7164
SetWindowFocus(const char * name)7165 void ImGui::SetWindowFocus(const char* name)
7166 {
7167 if (name)
7168 {
7169 if (ImGuiWindow* window = FindWindowByName(name))
7170 FocusWindow(window);
7171 }
7172 else
7173 {
7174 FocusWindow(NULL);
7175 }
7176 }
7177
SetNextWindowPos(const ImVec2 & pos,ImGuiCond cond,const ImVec2 & pivot)7178 void ImGui::SetNextWindowPos(const ImVec2& pos, ImGuiCond cond, const ImVec2& pivot)
7179 {
7180 ImGuiContext& g = *GImGui;
7181 IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
7182 g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasPos;
7183 g.NextWindowData.PosVal = pos;
7184 g.NextWindowData.PosPivotVal = pivot;
7185 g.NextWindowData.PosCond = cond ? cond : ImGuiCond_Always;
7186 g.NextWindowData.PosUndock = true;
7187 }
7188
SetNextWindowSize(const ImVec2 & size,ImGuiCond cond)7189 void ImGui::SetNextWindowSize(const ImVec2& size, ImGuiCond cond)
7190 {
7191 ImGuiContext& g = *GImGui;
7192 IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
7193 g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasSize;
7194 g.NextWindowData.SizeVal = size;
7195 g.NextWindowData.SizeCond = cond ? cond : ImGuiCond_Always;
7196 }
7197
SetNextWindowSizeConstraints(const ImVec2 & size_min,const ImVec2 & size_max,ImGuiSizeCallback custom_callback,void * custom_callback_user_data)7198 void ImGui::SetNextWindowSizeConstraints(const ImVec2& size_min, const ImVec2& size_max, ImGuiSizeCallback custom_callback, void* custom_callback_user_data)
7199 {
7200 ImGuiContext& g = *GImGui;
7201 g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasSizeConstraint;
7202 g.NextWindowData.SizeConstraintRect = ImRect(size_min, size_max);
7203 g.NextWindowData.SizeCallback = custom_callback;
7204 g.NextWindowData.SizeCallbackUserData = custom_callback_user_data;
7205 }
7206
7207 // Content size = inner scrollable rectangle, padded with WindowPadding.
7208 // SetNextWindowContentSize(ImVec2(100,100) + ImGuiWindowFlags_AlwaysAutoResize will always allow submitting a 100x100 item.
SetNextWindowContentSize(const ImVec2 & size)7209 void ImGui::SetNextWindowContentSize(const ImVec2& size)
7210 {
7211 ImGuiContext& g = *GImGui;
7212 g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasContentSize;
7213 g.NextWindowData.ContentSizeVal = size;
7214 }
7215
SetNextWindowScroll(const ImVec2 & scroll)7216 void ImGui::SetNextWindowScroll(const ImVec2& scroll)
7217 {
7218 ImGuiContext& g = *GImGui;
7219 g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasScroll;
7220 g.NextWindowData.ScrollVal = scroll;
7221 }
7222
SetNextWindowCollapsed(bool collapsed,ImGuiCond cond)7223 void ImGui::SetNextWindowCollapsed(bool collapsed, ImGuiCond cond)
7224 {
7225 ImGuiContext& g = *GImGui;
7226 IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
7227 g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasCollapsed;
7228 g.NextWindowData.CollapsedVal = collapsed;
7229 g.NextWindowData.CollapsedCond = cond ? cond : ImGuiCond_Always;
7230 }
7231
SetNextWindowFocus()7232 void ImGui::SetNextWindowFocus()
7233 {
7234 ImGuiContext& g = *GImGui;
7235 g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasFocus;
7236 }
7237
SetNextWindowBgAlpha(float alpha)7238 void ImGui::SetNextWindowBgAlpha(float alpha)
7239 {
7240 ImGuiContext& g = *GImGui;
7241 g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasBgAlpha;
7242 g.NextWindowData.BgAlphaVal = alpha;
7243 }
7244
SetNextWindowViewport(ImGuiID id)7245 void ImGui::SetNextWindowViewport(ImGuiID id)
7246 {
7247 ImGuiContext& g = *GImGui;
7248 g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasViewport;
7249 g.NextWindowData.ViewportId = id;
7250 }
7251
SetNextWindowDockID(ImGuiID id,ImGuiCond cond)7252 void ImGui::SetNextWindowDockID(ImGuiID id, ImGuiCond cond)
7253 {
7254 ImGuiContext& g = *GImGui;
7255 g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasDock;
7256 g.NextWindowData.DockCond = cond ? cond : ImGuiCond_Always;
7257 g.NextWindowData.DockId = id;
7258 }
7259
SetNextWindowClass(const ImGuiWindowClass * window_class)7260 void ImGui::SetNextWindowClass(const ImGuiWindowClass* window_class)
7261 {
7262 ImGuiContext& g = *GImGui;
7263 IM_ASSERT((window_class->ViewportFlagsOverrideSet & window_class->ViewportFlagsOverrideClear) == 0); // Cannot set both set and clear for the same bit
7264 g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasWindowClass;
7265 g.NextWindowData.WindowClass = *window_class;
7266 }
7267
GetWindowDrawList()7268 ImDrawList* ImGui::GetWindowDrawList()
7269 {
7270 ImGuiWindow* window = GetCurrentWindow();
7271 return window->DrawList;
7272 }
7273
GetWindowDpiScale()7274 float ImGui::GetWindowDpiScale()
7275 {
7276 ImGuiContext& g = *GImGui;
7277 return g.CurrentDpiScale;
7278 }
7279
GetWindowViewport()7280 ImGuiViewport* ImGui::GetWindowViewport()
7281 {
7282 ImGuiContext& g = *GImGui;
7283 IM_ASSERT(g.CurrentViewport != NULL && g.CurrentViewport == g.CurrentWindow->Viewport);
7284 return g.CurrentViewport;
7285 }
7286
GetFont()7287 ImFont* ImGui::GetFont()
7288 {
7289 return GImGui->Font;
7290 }
7291
GetFontSize()7292 float ImGui::GetFontSize()
7293 {
7294 return GImGui->FontSize;
7295 }
7296
GetFontTexUvWhitePixel()7297 ImVec2 ImGui::GetFontTexUvWhitePixel()
7298 {
7299 return GImGui->DrawListSharedData.TexUvWhitePixel;
7300 }
7301
SetWindowFontScale(float scale)7302 void ImGui::SetWindowFontScale(float scale)
7303 {
7304 IM_ASSERT(scale > 0.0f);
7305 ImGuiContext& g = *GImGui;
7306 ImGuiWindow* window = GetCurrentWindow();
7307 window->FontWindowScale = scale;
7308 g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize();
7309 }
7310
ActivateItem(ImGuiID id)7311 void ImGui::ActivateItem(ImGuiID id)
7312 {
7313 ImGuiContext& g = *GImGui;
7314 g.NavNextActivateId = id;
7315 }
7316
7317 // Note: this is storing in same stack as IDStack, so Push/Pop mismatch will be reported there.
PushFocusScope(ImGuiID id)7318 void ImGui::PushFocusScope(ImGuiID id)
7319 {
7320 ImGuiContext& g = *GImGui;
7321 ImGuiWindow* window = g.CurrentWindow;
7322 window->IDStack.push_back(window->DC.NavFocusScopeIdCurrent);
7323 window->DC.NavFocusScopeIdCurrent = id;
7324 }
7325
PopFocusScope()7326 void ImGui::PopFocusScope()
7327 {
7328 ImGuiContext& g = *GImGui;
7329 ImGuiWindow* window = g.CurrentWindow;
7330 window->DC.NavFocusScopeIdCurrent = window->IDStack.back();
7331 window->IDStack.pop_back();
7332 }
7333
SetKeyboardFocusHere(int offset)7334 void ImGui::SetKeyboardFocusHere(int offset)
7335 {
7336 IM_ASSERT(offset >= -1); // -1 is allowed but not below
7337 ImGuiContext& g = *GImGui;
7338 ImGuiWindow* window = g.CurrentWindow;
7339 g.FocusRequestNextWindow = window;
7340 g.FocusRequestNextCounterRegular = window->DC.FocusCounterRegular + 1 + offset;
7341 g.FocusRequestNextCounterTabStop = INT_MAX;
7342 }
7343
SetItemDefaultFocus()7344 void ImGui::SetItemDefaultFocus()
7345 {
7346 ImGuiContext& g = *GImGui;
7347 ImGuiWindow* window = g.CurrentWindow;
7348 if (!window->Appearing)
7349 return;
7350 if (g.NavWindow == window->RootWindowForNav && (g.NavInitRequest || g.NavInitResultId != 0) && g.NavLayer == g.NavWindow->DC.NavLayerCurrent)
7351 {
7352 g.NavInitRequest = false;
7353 g.NavInitResultId = g.NavWindow->DC.LastItemId;
7354 g.NavInitResultRectRel = ImRect(g.NavWindow->DC.LastItemRect.Min - g.NavWindow->Pos, g.NavWindow->DC.LastItemRect.Max - g.NavWindow->Pos);
7355 NavUpdateAnyRequestFlag();
7356 if (!IsItemVisible())
7357 SetScrollHereY();
7358 }
7359 }
7360
SetStateStorage(ImGuiStorage * tree)7361 void ImGui::SetStateStorage(ImGuiStorage* tree)
7362 {
7363 ImGuiWindow* window = GImGui->CurrentWindow;
7364 window->DC.StateStorage = tree ? tree : &window->StateStorage;
7365 }
7366
GetStateStorage()7367 ImGuiStorage* ImGui::GetStateStorage()
7368 {
7369 ImGuiWindow* window = GImGui->CurrentWindow;
7370 return window->DC.StateStorage;
7371 }
7372
PushID(const char * str_id)7373 void ImGui::PushID(const char* str_id)
7374 {
7375 ImGuiContext& g = *GImGui;
7376 ImGuiWindow* window = g.CurrentWindow;
7377 ImGuiID id = window->GetIDNoKeepAlive(str_id);
7378 window->IDStack.push_back(id);
7379 }
7380
PushID(const char * str_id_begin,const char * str_id_end)7381 void ImGui::PushID(const char* str_id_begin, const char* str_id_end)
7382 {
7383 ImGuiContext& g = *GImGui;
7384 ImGuiWindow* window = g.CurrentWindow;
7385 ImGuiID id = window->GetIDNoKeepAlive(str_id_begin, str_id_end);
7386 window->IDStack.push_back(id);
7387 }
7388
PushID(const void * ptr_id)7389 void ImGui::PushID(const void* ptr_id)
7390 {
7391 ImGuiContext& g = *GImGui;
7392 ImGuiWindow* window = g.CurrentWindow;
7393 ImGuiID id = window->GetIDNoKeepAlive(ptr_id);
7394 window->IDStack.push_back(id);
7395 }
7396
PushID(int int_id)7397 void ImGui::PushID(int int_id)
7398 {
7399 ImGuiContext& g = *GImGui;
7400 ImGuiWindow* window = g.CurrentWindow;
7401 ImGuiID id = window->GetIDNoKeepAlive(int_id);
7402 window->IDStack.push_back(id);
7403 }
7404
7405 // Push a given id value ignoring the ID stack as a seed.
PushOverrideID(ImGuiID id)7406 void ImGui::PushOverrideID(ImGuiID id)
7407 {
7408 ImGuiContext& g = *GImGui;
7409 ImGuiWindow* window = g.CurrentWindow;
7410 window->IDStack.push_back(id);
7411 }
7412
PopID()7413 void ImGui::PopID()
7414 {
7415 ImGuiWindow* window = GImGui->CurrentWindow;
7416 window->IDStack.pop_back();
7417 }
7418
GetID(const char * str_id)7419 ImGuiID ImGui::GetID(const char* str_id)
7420 {
7421 ImGuiWindow* window = GImGui->CurrentWindow;
7422 return window->GetID(str_id);
7423 }
7424
GetID(const char * str_id_begin,const char * str_id_end)7425 ImGuiID ImGui::GetID(const char* str_id_begin, const char* str_id_end)
7426 {
7427 ImGuiWindow* window = GImGui->CurrentWindow;
7428 return window->GetID(str_id_begin, str_id_end);
7429 }
7430
GetID(const void * ptr_id)7431 ImGuiID ImGui::GetID(const void* ptr_id)
7432 {
7433 ImGuiWindow* window = GImGui->CurrentWindow;
7434 return window->GetID(ptr_id);
7435 }
7436
IsRectVisible(const ImVec2 & size)7437 bool ImGui::IsRectVisible(const ImVec2& size)
7438 {
7439 ImGuiWindow* window = GImGui->CurrentWindow;
7440 return window->ClipRect.Overlaps(ImRect(window->DC.CursorPos, window->DC.CursorPos + size));
7441 }
7442
IsRectVisible(const ImVec2 & rect_min,const ImVec2 & rect_max)7443 bool ImGui::IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max)
7444 {
7445 ImGuiWindow* window = GImGui->CurrentWindow;
7446 return window->ClipRect.Overlaps(ImRect(rect_min, rect_max));
7447 }
7448
7449
7450 //-----------------------------------------------------------------------------
7451 // [SECTION] ERROR CHECKING
7452 //-----------------------------------------------------------------------------
7453
7454 // Helper function to verify ABI compatibility between caller code and compiled version of Dear ImGui.
7455 // Verify that the type sizes are matching between the calling file's compilation unit and imgui.cpp's compilation unit
7456 // If the user has inconsistent compilation settings, imgui configuration #define, packing pragma, etc. your user code
7457 // may see different structures than what imgui.cpp sees, which is problematic.
7458 // We usually require settings to be in imconfig.h to make sure that they are accessible to all compilation units involved with Dear 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)7459 bool ImGui::DebugCheckVersionAndDataLayout(const char* version, size_t sz_io, size_t sz_style, size_t sz_vec2, size_t sz_vec4, size_t sz_vert, size_t sz_idx)
7460 {
7461 bool error = false;
7462 if (strcmp(version, IMGUI_VERSION) != 0) { error = true; IM_ASSERT(strcmp(version, IMGUI_VERSION) == 0 && "Mismatched version string!"); }
7463 if (sz_io != sizeof(ImGuiIO)) { error = true; IM_ASSERT(sz_io == sizeof(ImGuiIO) && "Mismatched struct layout!"); }
7464 if (sz_style != sizeof(ImGuiStyle)) { error = true; IM_ASSERT(sz_style == sizeof(ImGuiStyle) && "Mismatched struct layout!"); }
7465 if (sz_vec2 != sizeof(ImVec2)) { error = true; IM_ASSERT(sz_vec2 == sizeof(ImVec2) && "Mismatched struct layout!"); }
7466 if (sz_vec4 != sizeof(ImVec4)) { error = true; IM_ASSERT(sz_vec4 == sizeof(ImVec4) && "Mismatched struct layout!"); }
7467 if (sz_vert != sizeof(ImDrawVert)) { error = true; IM_ASSERT(sz_vert == sizeof(ImDrawVert) && "Mismatched struct layout!"); }
7468 if (sz_idx != sizeof(ImDrawIdx)) { error = true; IM_ASSERT(sz_idx == sizeof(ImDrawIdx) && "Mismatched struct layout!"); }
7469 return !error;
7470 }
7471
ErrorCheckNewFrameSanityChecks()7472 static void ImGui::ErrorCheckNewFrameSanityChecks()
7473 {
7474 ImGuiContext& g = *GImGui;
7475
7476 // Check user IM_ASSERT macro
7477 // (IF YOU GET A WARNING OR COMPILE ERROR HERE: it means you assert macro is incorrectly defined!
7478 // If your macro uses multiple statements, it NEEDS to be surrounded by a 'do { ... } while (0)' block.
7479 // This is a common C/C++ idiom to allow multiple statements macros to be used in control flow blocks.)
7480 // #define IM_ASSERT(EXPR) SomeCode(EXPR); SomeMoreCode(); // Wrong!
7481 // #define IM_ASSERT(EXPR) do { SomeCode(EXPR); SomeMoreCode(); } while (0) // Correct!
7482 if (true) IM_ASSERT(1); else IM_ASSERT(0);
7483
7484 // Check user data
7485 // (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)
7486 IM_ASSERT(g.Initialized);
7487 IM_ASSERT((g.IO.DeltaTime > 0.0f || g.FrameCount == 0) && "Need a positive DeltaTime!");
7488 IM_ASSERT((g.FrameCount == 0 || g.FrameCountEnded == g.FrameCount) && "Forgot to call Render() or EndFrame() at the end of the previous frame?");
7489 IM_ASSERT(g.IO.DisplaySize.x >= 0.0f && g.IO.DisplaySize.y >= 0.0f && "Invalid DisplaySize value!");
7490 IM_ASSERT(g.IO.Fonts->Fonts.Size > 0 && "Font Atlas not built. Did you call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8() ?");
7491 IM_ASSERT(g.IO.Fonts->Fonts[0]->IsLoaded() && "Font Atlas not built. Did you call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8() ?");
7492 IM_ASSERT(g.Style.CurveTessellationTol > 0.0f && "Invalid style setting!");
7493 IM_ASSERT(g.Style.CircleSegmentMaxError > 0.0f && "Invalid style setting!");
7494 IM_ASSERT(g.Style.Alpha >= 0.0f && g.Style.Alpha <= 1.0f && "Invalid style setting. Alpha cannot be negative (allows us to avoid a few clamps in color computations)!");
7495 IM_ASSERT(g.Style.WindowMinSize.x >= 1.0f && g.Style.WindowMinSize.y >= 1.0f && "Invalid style setting.");
7496 IM_ASSERT(g.Style.WindowMenuButtonPosition == ImGuiDir_None || g.Style.WindowMenuButtonPosition == ImGuiDir_Left || g.Style.WindowMenuButtonPosition == ImGuiDir_Right);
7497 for (int n = 0; n < ImGuiKey_COUNT; n++)
7498 IM_ASSERT(g.IO.KeyMap[n] >= -1 && g.IO.KeyMap[n] < IM_ARRAYSIZE(g.IO.KeysDown) && "io.KeyMap[] contains an out of bound value (need to be 0..512, or -1 for unmapped key)");
7499
7500 // Perform simple 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 recently added in 1.60 WIP)
7501 if (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard)
7502 IM_ASSERT(g.IO.KeyMap[ImGuiKey_Space] != -1 && "ImGuiKey_Space is not mapped, required for keyboard navigation.");
7503
7504 // Perform simple check: the beta io.ConfigWindowsResizeFromEdges option requires back-end to honor mouse cursor changes and set the ImGuiBackendFlags_HasMouseCursors flag accordingly.
7505 if (g.IO.ConfigWindowsResizeFromEdges && !(g.IO.BackendFlags & ImGuiBackendFlags_HasMouseCursors))
7506 g.IO.ConfigWindowsResizeFromEdges = false;
7507
7508 // 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.
7509 if (g.FrameCount == 1 && (g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable) && (g.ConfigFlagsLastFrame & ImGuiConfigFlags_DockingEnable) == 0)
7510 IM_ASSERT(0 && "Please set DockingEnable before the first call to NewFrame()! Otherwise you will lose your .ini settings!");
7511 if (g.FrameCount == 1 && (g.IO.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) && (g.ConfigFlagsLastFrame & ImGuiConfigFlags_ViewportsEnable) == 0)
7512 IM_ASSERT(0 && "Please set ViewportsEnable before the first call to NewFrame()! Otherwise you will lose your .ini settings!");
7513
7514 // Perform simple checks: multi-viewport and platform windows support
7515 if (g.IO.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
7516 {
7517 if ((g.IO.BackendFlags & ImGuiBackendFlags_PlatformHasViewports) && (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasViewports))
7518 {
7519 IM_ASSERT((g.FrameCount == 0 || g.FrameCount == g.FrameCountPlatformEnded) && "Forgot to call UpdatePlatformWindows() in main loop after EndFrame()? Check examples/ applications for reference.");
7520 IM_ASSERT(g.PlatformIO.Platform_CreateWindow != NULL && "Platform init didn't install handlers?");
7521 IM_ASSERT(g.PlatformIO.Platform_DestroyWindow != NULL && "Platform init didn't install handlers?");
7522 IM_ASSERT(g.PlatformIO.Platform_GetWindowPos != NULL && "Platform init didn't install handlers?");
7523 IM_ASSERT(g.PlatformIO.Platform_SetWindowPos != NULL && "Platform init didn't install handlers?");
7524 IM_ASSERT(g.PlatformIO.Platform_GetWindowSize != NULL && "Platform init didn't install handlers?");
7525 IM_ASSERT(g.PlatformIO.Platform_SetWindowSize != NULL && "Platform init didn't install handlers?");
7526 IM_ASSERT(g.PlatformIO.Monitors.Size > 0 && "Platform init didn't setup Monitors list?");
7527 IM_ASSERT((g.Viewports[0]->PlatformUserData != NULL || g.Viewports[0]->PlatformHandle != NULL) && "Platform init didn't setup main viewport.");
7528 if (g.IO.ConfigDockingTransparentPayload && (g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable))
7529 IM_ASSERT(g.PlatformIO.Platform_SetWindowAlpha != NULL && "Platform_SetWindowAlpha handler is required to use io.ConfigDockingTransparent!");
7530 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
7531 IM_ASSERT(g.IO.RenderDrawListsFn == NULL); // Call ImGui::Render() then pass ImGui::GetDrawData() yourself to your render function!
7532 #endif
7533 }
7534 else
7535 {
7536 // Disable feature, our back-ends do not support it
7537 g.IO.ConfigFlags &= ~ImGuiConfigFlags_ViewportsEnable;
7538 }
7539
7540 // Perform simple checks on platform monitor data + compute a total bounding box for quick early outs
7541 for (int monitor_n = 0; monitor_n < g.PlatformIO.Monitors.Size; monitor_n++)
7542 {
7543 ImGuiPlatformMonitor& mon = g.PlatformIO.Monitors[monitor_n];
7544 IM_UNUSED(mon); // [Bruno Levy] silence a warning
7545 IM_ASSERT(mon.MainSize.x > 0.0f && mon.MainSize.y > 0.0f && "Monitor main bounds not setup properly.");
7546 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.");
7547 IM_ASSERT(mon.DpiScale != 0.0f);
7548 }
7549 }
7550 }
7551
ErrorCheckEndFrameSanityChecks()7552 static void ImGui::ErrorCheckEndFrameSanityChecks()
7553 {
7554 ImGuiContext& g = *GImGui;
7555
7556 // Verify that io.KeyXXX fields haven't been tampered with. Key mods should not be modified between NewFrame() and EndFrame()
7557 // One possible reason leading to this assert is that your back-ends update inputs _AFTER_ NewFrame().
7558 const ImGuiKeyModFlags expected_key_mod_flags = GetMergedKeyModFlags();
7559 IM_ASSERT(g.IO.KeyMods == expected_key_mod_flags && "Mismatching io.KeyCtrl/io.KeyShift/io.KeyAlt/io.KeySuper vs io.KeyMods");
7560 IM_UNUSED(expected_key_mod_flags);
7561
7562 // Report when there is a mismatch of Begin/BeginChild vs End/EndChild calls. Important: Remember that the Begin/BeginChild API requires you
7563 // to always call End/EndChild even if Begin/BeginChild returns false! (this is unfortunately inconsistent with most other Begin* API).
7564 if (g.CurrentWindowStack.Size != 1)
7565 {
7566 if (g.CurrentWindowStack.Size > 1)
7567 {
7568 IM_ASSERT_USER_ERROR(g.CurrentWindowStack.Size == 1, "Mismatched Begin/BeginChild vs End/EndChild calls: did you forget to call End/EndChild?");
7569 while (g.CurrentWindowStack.Size > 1)
7570 End();
7571 }
7572 else
7573 {
7574 IM_ASSERT_USER_ERROR(g.CurrentWindowStack.Size == 1, "Mismatched Begin/BeginChild vs End/EndChild calls: did you call End/EndChild too much?");
7575 }
7576 }
7577 }
7578
7579 // Save and compare stack sizes on Begin()/End() to detect usage errors
7580 // Begin() calls this with write=true
7581 // End() calls this with write=false
ErrorCheckBeginEndCompareStacksSize(ImGuiWindow * window,bool write)7582 static void ImGui::ErrorCheckBeginEndCompareStacksSize(ImGuiWindow* window, bool write)
7583 {
7584 ImGuiContext& g = *GImGui;
7585 short* p = &window->DC.StackSizesBackup[0];
7586
7587 // Window stacks
7588 // NOT checking: DC.ItemWidth, DC.AllowKeyboardFocus, DC.ButtonRepeat, DC.TextWrapPos (per window) to allow user to conveniently push once and not pop (they are cleared on Begin)
7589 { int n = window->IDStack.Size; if (write) *p = (short)n; else IM_ASSERT(*p == n && "PushID/PopID or TreeNode/TreePop Mismatch!"); p++; } // Too few or too many PopID()/TreePop()
7590 { int n = window->DC.GroupStack.Size; if (write) *p = (short)n; else IM_ASSERT(*p == n && "BeginGroup/EndGroup Mismatch!"); p++; } // Too few or too many EndGroup()
7591
7592 // Global stacks
7593 // 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.
7594 { int n = g.BeginPopupStack.Size; if (write) *p = (short)n; else IM_ASSERT(*p == n && "BeginMenu/EndMenu or BeginPopup/EndPopup Mismatch!"); p++; }// Too few or too many EndMenu()/EndPopup()
7595 { int n = g.ColorModifiers.Size; if (write) *p = (short)n; else IM_ASSERT(*p >= n && "PushStyleColor/PopStyleColor Mismatch!"); p++; } // Too few or too many PopStyleColor()
7596 { int n = g.StyleModifiers.Size; if (write) *p = (short)n; else IM_ASSERT(*p >= n && "PushStyleVar/PopStyleVar Mismatch!"); p++; } // Too few or too many PopStyleVar()
7597 { int n = g.FontStack.Size; if (write) *p = (short)n; else IM_ASSERT(*p >= n && "PushFont/PopFont Mismatch!"); p++; } // Too few or too many PopFont()
7598 IM_ASSERT(p == window->DC.StackSizesBackup + IM_ARRAYSIZE(window->DC.StackSizesBackup));
7599 }
7600
7601
7602 //-----------------------------------------------------------------------------
7603 // [SECTION] LAYOUT
7604 //-----------------------------------------------------------------------------
7605 // - ItemSize()
7606 // - ItemAdd()
7607 // - SameLine()
7608 // - GetCursorScreenPos()
7609 // - SetCursorScreenPos()
7610 // - GetCursorPos(), GetCursorPosX(), GetCursorPosY()
7611 // - SetCursorPos(), SetCursorPosX(), SetCursorPosY()
7612 // - GetCursorStartPos()
7613 // - Indent()
7614 // - Unindent()
7615 // - SetNextItemWidth()
7616 // - PushItemWidth()
7617 // - PushMultiItemsWidths()
7618 // - PopItemWidth()
7619 // - CalcItemWidth()
7620 // - CalcItemSize()
7621 // - GetTextLineHeight()
7622 // - GetTextLineHeightWithSpacing()
7623 // - GetFrameHeight()
7624 // - GetFrameHeightWithSpacing()
7625 // - GetContentRegionMax()
7626 // - GetContentRegionMaxAbs() [Internal]
7627 // - GetContentRegionAvail(),
7628 // - GetWindowContentRegionMin(), GetWindowContentRegionMax()
7629 // - GetWindowContentRegionWidth()
7630 // - BeginGroup()
7631 // - EndGroup()
7632 // Also see in imgui_widgets: tab bars, columns.
7633 //-----------------------------------------------------------------------------
7634
7635 // Advance cursor given item size for layout.
7636 // Register minimum needed size so it can extend the bounding box used for auto-fit calculation.
7637 // See comments in ItemAdd() about how/why the size provided to ItemSize() vs ItemAdd() may often different.
ItemSize(const ImVec2 & size,float text_baseline_y)7638 void ImGui::ItemSize(const ImVec2& size, float text_baseline_y)
7639 {
7640 ImGuiContext& g = *GImGui;
7641 ImGuiWindow* window = g.CurrentWindow;
7642 if (window->SkipItems)
7643 return;
7644
7645 // We increase the height in this function to accommodate for baseline offset.
7646 // In theory we should be offsetting the starting position (window->DC.CursorPos), that will be the topic of a larger refactor,
7647 // but since ItemSize() is not yet an API that moves the cursor (to handle e.g. wrapping) enlarging the height has the same effect.
7648 const float offset_to_match_baseline_y = (text_baseline_y >= 0) ? ImMax(0.0f, window->DC.CurrLineTextBaseOffset - text_baseline_y) : 0.0f;
7649 const float line_height = ImMax(window->DC.CurrLineSize.y, size.y + offset_to_match_baseline_y);
7650
7651 // Always align ourselves on pixel boundaries
7652 //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]
7653 window->DC.CursorPosPrevLine.x = window->DC.CursorPos.x + size.x;
7654 window->DC.CursorPosPrevLine.y = window->DC.CursorPos.y;
7655 window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x); // Next line
7656 window->DC.CursorPos.y = IM_FLOOR(window->DC.CursorPos.y + line_height + g.Style.ItemSpacing.y); // Next line
7657 window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPosPrevLine.x);
7658 window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y - g.Style.ItemSpacing.y);
7659 //if (g.IO.KeyAlt) window->DrawList->AddCircle(window->DC.CursorMaxPos, 3.0f, IM_COL32(255,0,0,255), 4); // [DEBUG]
7660
7661 window->DC.PrevLineSize.y = line_height;
7662 window->DC.CurrLineSize.y = 0.0f;
7663 window->DC.PrevLineTextBaseOffset = ImMax(window->DC.CurrLineTextBaseOffset, text_baseline_y);
7664 window->DC.CurrLineTextBaseOffset = 0.0f;
7665
7666 // Horizontal layout mode
7667 if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
7668 SameLine();
7669 }
7670
ItemSize(const ImRect & bb,float text_baseline_y)7671 void ImGui::ItemSize(const ImRect& bb, float text_baseline_y)
7672 {
7673 ItemSize(bb.GetSize(), text_baseline_y);
7674 }
7675
7676 // Declare item bounding box for clipping and interaction.
7677 // Note that the size can be different than the one provided to ItemSize(). Typically, widgets that spread over available surface
7678 // declare their minimum size requirement to ItemSize() and provide a larger region to ItemAdd() which is used drawing/interaction.
ItemAdd(const ImRect & bb,ImGuiID id,const ImRect * nav_bb_arg)7679 bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg)
7680 {
7681 ImGuiContext& g = *GImGui;
7682 ImGuiWindow* window = g.CurrentWindow;
7683
7684 if (id != 0)
7685 {
7686 // Navigation processing runs prior to clipping early-out
7687 // (a) So that NavInitRequest can be honored, for newly opened windows to select a default widget
7688 // (b) So that we can scroll up/down past clipped items. This adds a small O(N) cost to regular navigation requests
7689 // unfortunately, but it is still limited to one window. It may not scale very well for windows with ten of
7690 // thousands of item, but at least NavMoveRequest is only set on user interaction, aka maximum once a frame.
7691 // We could early out with "if (is_clipped && !g.NavInitRequest) return false;" but when we wouldn't be able
7692 // to reach unclipped widgets. This would work if user had explicit scrolling control (e.g. mapped on a stick).
7693 // We intentionally don't check if g.NavWindow != NULL because g.NavAnyRequest should only be set when it is non null.
7694 // If we crash on a NULL g.NavWindow we need to fix the bug elsewhere.
7695 window->DC.NavLayerActiveMaskNext |= window->DC.NavLayerCurrentMask;
7696 if (g.NavId == id || g.NavAnyRequest)
7697 if (g.NavWindow->RootWindowForNav == window->RootWindowForNav)
7698 if (window == g.NavWindow || ((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened))
7699 NavProcessItem(window, nav_bb_arg ? *nav_bb_arg : bb, id);
7700
7701 // [DEBUG] Item Picker tool, when enabling the "extended" version we perform the check in ItemAdd()
7702 #ifdef IMGUI_DEBUG_TOOL_ITEM_PICKER_EX
7703 if (id == g.DebugItemPickerBreakId)
7704 {
7705 IM_DEBUG_BREAK();
7706 g.DebugItemPickerBreakId = 0;
7707 }
7708 #endif
7709 }
7710
7711 // Equivalent to calling SetLastItemData()
7712 window->DC.LastItemId = id;
7713 window->DC.LastItemRect = bb;
7714 window->DC.LastItemStatusFlags = ImGuiItemStatusFlags_None;
7715 g.NextItemData.Flags = ImGuiNextItemDataFlags_None;
7716
7717 #ifdef IMGUI_ENABLE_TEST_ENGINE
7718 if (id != 0)
7719 IMGUI_TEST_ENGINE_ITEM_ADD(nav_bb_arg ? *nav_bb_arg : bb, id);
7720 #endif
7721
7722 // Clipping test
7723 const bool is_clipped = IsClippedEx(bb, id, false);
7724 if (is_clipped)
7725 return false;
7726 //if (g.IO.KeyAlt) window->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(255,255,0,120)); // [DEBUG]
7727
7728 // We need to calculate this now to take account of the current clipping rectangle (as items like Selectable may change them)
7729 if (IsMouseHoveringRect(bb.Min, bb.Max))
7730 window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HoveredRect;
7731 return true;
7732 }
7733
7734 // Gets back to previous line and continue with horizontal layout
7735 // offset_from_start_x == 0 : follow right after previous item
7736 // offset_from_start_x != 0 : align to specified x position (relative to window/group left)
7737 // spacing_w < 0 : use default spacing if pos_x == 0, no spacing if pos_x != 0
7738 // spacing_w >= 0 : enforce spacing amount
SameLine(float offset_from_start_x,float spacing_w)7739 void ImGui::SameLine(float offset_from_start_x, float spacing_w)
7740 {
7741 ImGuiWindow* window = GetCurrentWindow();
7742 if (window->SkipItems)
7743 return;
7744
7745 ImGuiContext& g = *GImGui;
7746 if (offset_from_start_x != 0.0f)
7747 {
7748 if (spacing_w < 0.0f) spacing_w = 0.0f;
7749 window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + offset_from_start_x + spacing_w + window->DC.GroupOffset.x + window->DC.ColumnsOffset.x;
7750 window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y;
7751 }
7752 else
7753 {
7754 if (spacing_w < 0.0f) spacing_w = g.Style.ItemSpacing.x;
7755 window->DC.CursorPos.x = window->DC.CursorPosPrevLine.x + spacing_w;
7756 window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y;
7757 }
7758 window->DC.CurrLineSize = window->DC.PrevLineSize;
7759 window->DC.CurrLineTextBaseOffset = window->DC.PrevLineTextBaseOffset;
7760 }
7761
GetCursorScreenPos()7762 ImVec2 ImGui::GetCursorScreenPos()
7763 {
7764 ImGuiWindow* window = GetCurrentWindowRead();
7765 return window->DC.CursorPos;
7766 }
7767
SetCursorScreenPos(const ImVec2 & pos)7768 void ImGui::SetCursorScreenPos(const ImVec2& pos)
7769 {
7770 ImGuiWindow* window = GetCurrentWindow();
7771 window->DC.CursorPos = pos;
7772 window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos);
7773 }
7774
7775 // User generally sees positions in window coordinates. Internally we store CursorPos in absolute screen coordinates because it is more convenient.
7776 // 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'.
GetCursorPos()7777 ImVec2 ImGui::GetCursorPos()
7778 {
7779 ImGuiWindow* window = GetCurrentWindowRead();
7780 return window->DC.CursorPos - window->Pos + window->Scroll;
7781 }
7782
GetCursorPosX()7783 float ImGui::GetCursorPosX()
7784 {
7785 ImGuiWindow* window = GetCurrentWindowRead();
7786 return window->DC.CursorPos.x - window->Pos.x + window->Scroll.x;
7787 }
7788
GetCursorPosY()7789 float ImGui::GetCursorPosY()
7790 {
7791 ImGuiWindow* window = GetCurrentWindowRead();
7792 return window->DC.CursorPos.y - window->Pos.y + window->Scroll.y;
7793 }
7794
SetCursorPos(const ImVec2 & local_pos)7795 void ImGui::SetCursorPos(const ImVec2& local_pos)
7796 {
7797 ImGuiWindow* window = GetCurrentWindow();
7798 window->DC.CursorPos = window->Pos - window->Scroll + local_pos;
7799 window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos);
7800 }
7801
SetCursorPosX(float x)7802 void ImGui::SetCursorPosX(float x)
7803 {
7804 ImGuiWindow* window = GetCurrentWindow();
7805 window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + x;
7806 window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPos.x);
7807 }
7808
SetCursorPosY(float y)7809 void ImGui::SetCursorPosY(float y)
7810 {
7811 ImGuiWindow* window = GetCurrentWindow();
7812 window->DC.CursorPos.y = window->Pos.y - window->Scroll.y + y;
7813 window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y);
7814 }
7815
GetCursorStartPos()7816 ImVec2 ImGui::GetCursorStartPos()
7817 {
7818 ImGuiWindow* window = GetCurrentWindowRead();
7819 return window->DC.CursorStartPos - window->Pos;
7820 }
7821
Indent(float indent_w)7822 void ImGui::Indent(float indent_w)
7823 {
7824 ImGuiContext& g = *GImGui;
7825 ImGuiWindow* window = GetCurrentWindow();
7826 window->DC.Indent.x += (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing;
7827 window->DC.CursorPos.x = window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x;
7828 }
7829
Unindent(float indent_w)7830 void ImGui::Unindent(float indent_w)
7831 {
7832 ImGuiContext& g = *GImGui;
7833 ImGuiWindow* window = GetCurrentWindow();
7834 window->DC.Indent.x -= (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing;
7835 window->DC.CursorPos.x = window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x;
7836 }
7837
7838 // Affect large frame+labels widgets only.
SetNextItemWidth(float item_width)7839 void ImGui::SetNextItemWidth(float item_width)
7840 {
7841 ImGuiContext& g = *GImGui;
7842 g.NextItemData.Flags |= ImGuiNextItemDataFlags_HasWidth;
7843 g.NextItemData.Width = item_width;
7844 }
7845
PushItemWidth(float item_width)7846 void ImGui::PushItemWidth(float item_width)
7847 {
7848 ImGuiContext& g = *GImGui;
7849 ImGuiWindow* window = g.CurrentWindow;
7850 window->DC.ItemWidth = (item_width == 0.0f ? window->ItemWidthDefault : item_width);
7851 window->DC.ItemWidthStack.push_back(window->DC.ItemWidth);
7852 g.NextItemData.Flags &= ~ImGuiNextItemDataFlags_HasWidth;
7853 }
7854
PushMultiItemsWidths(int components,float w_full)7855 void ImGui::PushMultiItemsWidths(int components, float w_full)
7856 {
7857 ImGuiContext& g = *GImGui;
7858 ImGuiWindow* window = g.CurrentWindow;
7859 const ImGuiStyle& style = g.Style;
7860 const float w_item_one = ImMax(1.0f, IM_FLOOR((w_full - (style.ItemInnerSpacing.x) * (components - 1)) / (float)components));
7861 const float w_item_last = ImMax(1.0f, IM_FLOOR(w_full - (w_item_one + style.ItemInnerSpacing.x) * (components - 1)));
7862 window->DC.ItemWidthStack.push_back(w_item_last);
7863 for (int i = 0; i < components - 1; i++)
7864 window->DC.ItemWidthStack.push_back(w_item_one);
7865 window->DC.ItemWidth = window->DC.ItemWidthStack.back();
7866 g.NextItemData.Flags &= ~ImGuiNextItemDataFlags_HasWidth;
7867 }
7868
PopItemWidth()7869 void ImGui::PopItemWidth()
7870 {
7871 ImGuiWindow* window = GetCurrentWindow();
7872 window->DC.ItemWidthStack.pop_back();
7873 window->DC.ItemWidth = window->DC.ItemWidthStack.empty() ? window->ItemWidthDefault : window->DC.ItemWidthStack.back();
7874 }
7875
7876 // Calculate default item width given value passed to PushItemWidth() or SetNextItemWidth().
7877 // The SetNextItemWidth() data is generally cleared/consumed by ItemAdd() or NextItemData.ClearFlags()
CalcItemWidth()7878 float ImGui::CalcItemWidth()
7879 {
7880 ImGuiContext& g = *GImGui;
7881 ImGuiWindow* window = g.CurrentWindow;
7882 float w;
7883 if (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasWidth)
7884 w = g.NextItemData.Width;
7885 else
7886 w = window->DC.ItemWidth;
7887 if (w < 0.0f)
7888 {
7889 float region_max_x = GetContentRegionMaxAbs().x;
7890 w = ImMax(1.0f, region_max_x - window->DC.CursorPos.x + w);
7891 }
7892 w = IM_FLOOR(w);
7893 return w;
7894 }
7895
7896 // [Internal] Calculate full item size given user provided 'size' parameter and default width/height. Default width is often == CalcItemWidth().
7897 // Those two functions CalcItemWidth vs CalcItemSize are awkwardly named because they are not fully symmetrical.
7898 // Note that only CalcItemWidth() is publicly exposed.
7899 // The 4.0f here may be changed to match CalcItemWidth() and/or BeginChild() (right now we have a mismatch which is harmless but undesirable)
CalcItemSize(ImVec2 size,float default_w,float default_h)7900 ImVec2 ImGui::CalcItemSize(ImVec2 size, float default_w, float default_h)
7901 {
7902 ImGuiWindow* window = GImGui->CurrentWindow;
7903
7904 ImVec2 region_max;
7905 if (size.x < 0.0f || size.y < 0.0f)
7906 region_max = GetContentRegionMaxAbs();
7907
7908 if (size.x == 0.0f)
7909 size.x = default_w;
7910 else if (size.x < 0.0f)
7911 size.x = ImMax(4.0f, region_max.x - window->DC.CursorPos.x + size.x);
7912
7913 if (size.y == 0.0f)
7914 size.y = default_h;
7915 else if (size.y < 0.0f)
7916 size.y = ImMax(4.0f, region_max.y - window->DC.CursorPos.y + size.y);
7917
7918 return size;
7919 }
7920
GetTextLineHeight()7921 float ImGui::GetTextLineHeight()
7922 {
7923 ImGuiContext& g = *GImGui;
7924 return g.FontSize;
7925 }
7926
GetTextLineHeightWithSpacing()7927 float ImGui::GetTextLineHeightWithSpacing()
7928 {
7929 ImGuiContext& g = *GImGui;
7930 return g.FontSize + g.Style.ItemSpacing.y;
7931 }
7932
GetFrameHeight()7933 float ImGui::GetFrameHeight()
7934 {
7935 ImGuiContext& g = *GImGui;
7936 return g.FontSize + g.Style.FramePadding.y * 2.0f;
7937 }
7938
GetFrameHeightWithSpacing()7939 float ImGui::GetFrameHeightWithSpacing()
7940 {
7941 ImGuiContext& g = *GImGui;
7942 return g.FontSize + g.Style.FramePadding.y * 2.0f + g.Style.ItemSpacing.y;
7943 }
7944
7945 // 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!
7946
7947 // FIXME: This is in window space (not screen space!).
GetContentRegionMax()7948 ImVec2 ImGui::GetContentRegionMax()
7949 {
7950 ImGuiContext& g = *GImGui;
7951 ImGuiWindow* window = g.CurrentWindow;
7952 ImVec2 mx = window->ContentRegionRect.Max - window->Pos;
7953 if (window->DC.CurrentColumns)
7954 mx.x = window->WorkRect.Max.x - window->Pos.x;
7955 return mx;
7956 }
7957
7958 // [Internal] Absolute coordinate. Saner. This is not exposed until we finishing refactoring work rect features.
GetContentRegionMaxAbs()7959 ImVec2 ImGui::GetContentRegionMaxAbs()
7960 {
7961 ImGuiContext& g = *GImGui;
7962 ImGuiWindow* window = g.CurrentWindow;
7963 ImVec2 mx = window->ContentRegionRect.Max;
7964 if (window->DC.CurrentColumns)
7965 mx.x = window->WorkRect.Max.x;
7966 return mx;
7967 }
7968
GetContentRegionAvail()7969 ImVec2 ImGui::GetContentRegionAvail()
7970 {
7971 ImGuiWindow* window = GImGui->CurrentWindow;
7972 return GetContentRegionMaxAbs() - window->DC.CursorPos;
7973 }
7974
7975 // In window space (not screen space!)
GetWindowContentRegionMin()7976 ImVec2 ImGui::GetWindowContentRegionMin()
7977 {
7978 ImGuiWindow* window = GImGui->CurrentWindow;
7979 return window->ContentRegionRect.Min - window->Pos;
7980 }
7981
GetWindowContentRegionMax()7982 ImVec2 ImGui::GetWindowContentRegionMax()
7983 {
7984 ImGuiWindow* window = GImGui->CurrentWindow;
7985 return window->ContentRegionRect.Max - window->Pos;
7986 }
7987
GetWindowContentRegionWidth()7988 float ImGui::GetWindowContentRegionWidth()
7989 {
7990 ImGuiWindow* window = GImGui->CurrentWindow;
7991 return window->ContentRegionRect.GetWidth();
7992 }
7993
7994 // 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.)
BeginGroup()7995 void ImGui::BeginGroup()
7996 {
7997 ImGuiContext& g = *GImGui;
7998 ImGuiWindow* window = GetCurrentWindow();
7999
8000 window->DC.GroupStack.resize(window->DC.GroupStack.Size + 1);
8001 ImGuiGroupData& group_data = window->DC.GroupStack.back();
8002 group_data.BackupCursorPos = window->DC.CursorPos;
8003 group_data.BackupCursorMaxPos = window->DC.CursorMaxPos;
8004 group_data.BackupIndent = window->DC.Indent;
8005 group_data.BackupGroupOffset = window->DC.GroupOffset;
8006 group_data.BackupCurrLineSize = window->DC.CurrLineSize;
8007 group_data.BackupCurrLineTextBaseOffset = window->DC.CurrLineTextBaseOffset;
8008 group_data.BackupActiveIdIsAlive = g.ActiveIdIsAlive;
8009 group_data.BackupActiveIdPreviousFrameIsAlive = g.ActiveIdPreviousFrameIsAlive;
8010 group_data.EmitItem = true;
8011
8012 window->DC.GroupOffset.x = window->DC.CursorPos.x - window->Pos.x - window->DC.ColumnsOffset.x;
8013 window->DC.Indent = window->DC.GroupOffset;
8014 window->DC.CursorMaxPos = window->DC.CursorPos;
8015 window->DC.CurrLineSize = ImVec2(0.0f, 0.0f);
8016 if (g.LogEnabled)
8017 g.LogLinePosY = -FLT_MAX; // To enforce Log carriage return
8018 }
8019
EndGroup()8020 void ImGui::EndGroup()
8021 {
8022 ImGuiContext& g = *GImGui;
8023 ImGuiWindow* window = GetCurrentWindow();
8024 IM_ASSERT(!window->DC.GroupStack.empty()); // Mismatched BeginGroup()/EndGroup() calls
8025
8026 ImGuiGroupData& group_data = window->DC.GroupStack.back();
8027
8028 ImRect group_bb(group_data.BackupCursorPos, ImMax(window->DC.CursorMaxPos, group_data.BackupCursorPos));
8029
8030 window->DC.CursorPos = group_data.BackupCursorPos;
8031 window->DC.CursorMaxPos = ImMax(group_data.BackupCursorMaxPos, window->DC.CursorMaxPos);
8032 window->DC.Indent = group_data.BackupIndent;
8033 window->DC.GroupOffset = group_data.BackupGroupOffset;
8034 window->DC.CurrLineSize = group_data.BackupCurrLineSize;
8035 window->DC.CurrLineTextBaseOffset = group_data.BackupCurrLineTextBaseOffset;
8036 if (g.LogEnabled)
8037 g.LogLinePosY = -FLT_MAX; // To enforce Log carriage return
8038
8039 if (!group_data.EmitItem)
8040 {
8041 window->DC.GroupStack.pop_back();
8042 return;
8043 }
8044
8045 window->DC.CurrLineTextBaseOffset = ImMax(window->DC.PrevLineTextBaseOffset, group_data.BackupCurrLineTextBaseOffset); // FIXME: Incorrect, we should grab the base offset from the *first line* of the group but it is hard to obtain now.
8046 ItemSize(group_bb.GetSize());
8047 ItemAdd(group_bb, 0);
8048
8049 // 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.
8050 // It would be be neater if we replaced window.DC.LastItemId by e.g. 'bool LastItemIsActive', but would put a little more burden on individual widgets.
8051 // Also if you grep for LastItemId you'll notice it is only used in that context.
8052 // (The tests not symmetrical because ActiveIdIsAlive is an ID itself, in order to be able to handle ActiveId being overwritten during the frame.)
8053 const bool group_contains_curr_active_id = (group_data.BackupActiveIdIsAlive != g.ActiveId) && (g.ActiveIdIsAlive == g.ActiveId) && g.ActiveId;
8054 const bool group_contains_prev_active_id = !group_data.BackupActiveIdPreviousFrameIsAlive && g.ActiveIdPreviousFrameIsAlive;
8055 if (group_contains_curr_active_id)
8056 window->DC.LastItemId = g.ActiveId;
8057 else if (group_contains_prev_active_id)
8058 window->DC.LastItemId = g.ActiveIdPreviousFrame;
8059 window->DC.LastItemRect = group_bb;
8060
8061 // Forward Edited flag
8062 if (group_contains_curr_active_id && g.ActiveIdHasBeenEditedThisFrame)
8063 window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_Edited;
8064
8065 // Forward Deactivated flag
8066 window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HasDeactivated;
8067 if (group_contains_prev_active_id && g.ActiveId != g.ActiveIdPreviousFrame)
8068 window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_Deactivated;
8069
8070 window->DC.GroupStack.pop_back();
8071 //window->DrawList->AddRect(group_bb.Min, group_bb.Max, IM_COL32(255,0,255,255)); // [Debug]
8072 }
8073
8074
8075 //-----------------------------------------------------------------------------
8076 // [SECTION] SCROLLING
8077 //-----------------------------------------------------------------------------
8078
CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow * window)8079 static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window)
8080 {
8081 ImVec2 scroll = window->Scroll;
8082 if (window->ScrollTarget.x < FLT_MAX)
8083 {
8084 float cr_x = window->ScrollTargetCenterRatio.x;
8085 float target_x = window->ScrollTarget.x;
8086 scroll.x = target_x - cr_x * (window->SizeFull.x - window->ScrollbarSizes.x);
8087 }
8088 if (window->ScrollTarget.y < FLT_MAX)
8089 {
8090 float decoration_up_height = window->TitleBarHeight() + window->MenuBarHeight();
8091 float cr_y = window->ScrollTargetCenterRatio.y;
8092 float target_y = window->ScrollTarget.y;
8093 scroll.y = target_y - cr_y * (window->SizeFull.y - window->ScrollbarSizes.y - decoration_up_height);
8094 }
8095 scroll.x = IM_FLOOR(ImMax(scroll.x, 0.0f));
8096 scroll.y = IM_FLOOR(ImMax(scroll.y, 0.0f));
8097 if (!window->Collapsed && !window->SkipItems)
8098 {
8099 scroll.x = ImMin(scroll.x, window->ScrollMax.x);
8100 scroll.y = ImMin(scroll.y, window->ScrollMax.y);
8101 }
8102 return scroll;
8103 }
8104
8105 // Scroll to keep newly navigated item fully into view
ScrollToBringRectIntoView(ImGuiWindow * window,const ImRect & item_rect)8106 ImVec2 ImGui::ScrollToBringRectIntoView(ImGuiWindow* window, const ImRect& item_rect)
8107 {
8108 ImGuiContext& g = *GImGui;
8109 ImRect window_rect(window->InnerRect.Min - ImVec2(1, 1), window->InnerRect.Max + ImVec2(1, 1));
8110 //GetForegroundDrawList(window)->AddRect(window_rect.Min, window_rect.Max, IM_COL32_WHITE); // [DEBUG]
8111
8112 ImVec2 delta_scroll;
8113 if (!window_rect.Contains(item_rect))
8114 {
8115 if (window->ScrollbarX && item_rect.Min.x < window_rect.Min.x)
8116 SetScrollFromPosX(window, item_rect.Min.x - window->Pos.x - g.Style.ItemSpacing.x, 0.0f);
8117 else if (window->ScrollbarX && item_rect.Max.x >= window_rect.Max.x)
8118 SetScrollFromPosX(window, item_rect.Max.x - window->Pos.x + g.Style.ItemSpacing.x, 1.0f);
8119 if (item_rect.Min.y < window_rect.Min.y)
8120 SetScrollFromPosY(window, item_rect.Min.y - window->Pos.y - g.Style.ItemSpacing.y, 0.0f);
8121 else if (item_rect.Max.y >= window_rect.Max.y)
8122 SetScrollFromPosY(window, item_rect.Max.y - window->Pos.y + g.Style.ItemSpacing.y, 1.0f);
8123
8124 ImVec2 next_scroll = CalcNextScrollFromScrollTargetAndClamp(window);
8125 delta_scroll = next_scroll - window->Scroll;
8126 }
8127
8128 // Also scroll parent window to keep us into view if necessary
8129 if (window->Flags & ImGuiWindowFlags_ChildWindow)
8130 delta_scroll += ScrollToBringRectIntoView(window->ParentWindow, ImRect(item_rect.Min - delta_scroll, item_rect.Max - delta_scroll));
8131
8132 return delta_scroll;
8133 }
8134
GetScrollX()8135 float ImGui::GetScrollX()
8136 {
8137 ImGuiWindow* window = GImGui->CurrentWindow;
8138 return window->Scroll.x;
8139 }
8140
GetScrollY()8141 float ImGui::GetScrollY()
8142 {
8143 ImGuiWindow* window = GImGui->CurrentWindow;
8144 return window->Scroll.y;
8145 }
8146
GetScrollMaxX()8147 float ImGui::GetScrollMaxX()
8148 {
8149 ImGuiWindow* window = GImGui->CurrentWindow;
8150 return window->ScrollMax.x;
8151 }
8152
GetScrollMaxY()8153 float ImGui::GetScrollMaxY()
8154 {
8155 ImGuiWindow* window = GImGui->CurrentWindow;
8156 return window->ScrollMax.y;
8157 }
8158
SetScrollX(float scroll_x)8159 void ImGui::SetScrollX(float scroll_x)
8160 {
8161 ImGuiWindow* window = GetCurrentWindow();
8162 window->ScrollTarget.x = scroll_x;
8163 window->ScrollTargetCenterRatio.x = 0.0f;
8164 }
8165
SetScrollY(float scroll_y)8166 void ImGui::SetScrollY(float scroll_y)
8167 {
8168 ImGuiWindow* window = GetCurrentWindow();
8169 window->ScrollTarget.y = scroll_y;
8170 window->ScrollTargetCenterRatio.y = 0.0f;
8171 }
8172
SetScrollX(ImGuiWindow * window,float new_scroll_x)8173 void ImGui::SetScrollX(ImGuiWindow* window, float new_scroll_x)
8174 {
8175 window->ScrollTarget.x = new_scroll_x;
8176 window->ScrollTargetCenterRatio.x = 0.0f;
8177 }
8178
SetScrollY(ImGuiWindow * window,float new_scroll_y)8179 void ImGui::SetScrollY(ImGuiWindow* window, float new_scroll_y)
8180 {
8181 window->ScrollTarget.y = new_scroll_y;
8182 window->ScrollTargetCenterRatio.y = 0.0f;
8183 }
8184
8185 // Note that a local position will vary depending on initial scroll value
8186 // We store a target position so centering can occur on the next frame when we are guaranteed to have a known window size
SetScrollFromPosX(ImGuiWindow * window,float local_x,float center_x_ratio)8187 void ImGui::SetScrollFromPosX(ImGuiWindow* window, float local_x, float center_x_ratio)
8188 {
8189 IM_ASSERT(center_x_ratio >= 0.0f && center_x_ratio <= 1.0f);
8190 window->ScrollTarget.x = IM_FLOOR(local_x + window->Scroll.x);
8191 window->ScrollTargetCenterRatio.x = center_x_ratio;
8192 }
8193
SetScrollFromPosY(ImGuiWindow * window,float local_y,float center_y_ratio)8194 void ImGui::SetScrollFromPosY(ImGuiWindow* window, float local_y, float center_y_ratio)
8195 {
8196 IM_ASSERT(center_y_ratio >= 0.0f && center_y_ratio <= 1.0f);
8197 local_y -= window->TitleBarHeight() + window->MenuBarHeight(); // FIXME: Would be nice to have a more standardized access to our scrollable/client rect
8198 window->ScrollTarget.y = IM_FLOOR(local_y + window->Scroll.y);
8199 window->ScrollTargetCenterRatio.y = center_y_ratio;
8200 }
8201
SetScrollFromPosX(float local_x,float center_x_ratio)8202 void ImGui::SetScrollFromPosX(float local_x, float center_x_ratio)
8203 {
8204 ImGuiContext& g = *GImGui;
8205 SetScrollFromPosX(g.CurrentWindow, local_x, center_x_ratio);
8206 }
8207
SetScrollFromPosY(float local_y,float center_y_ratio)8208 void ImGui::SetScrollFromPosY(float local_y, float center_y_ratio)
8209 {
8210 ImGuiContext& g = *GImGui;
8211 SetScrollFromPosY(g.CurrentWindow, local_y, center_y_ratio);
8212 }
8213
8214 // Tweak: snap on edges when aiming at an item very close to the edge,
8215 // So the difference between WindowPadding and ItemSpacing will be in the visible area after scrolling.
8216 // When we refactor the scrolling API this may be configurable with a flag?
8217 // Note that the effect for this won't be visible on X axis with default Style settings as WindowPadding.x == ItemSpacing.x by default.
CalcScrollSnap(float target,float snap_min,float snap_max,float snap_threshold,float center_ratio)8218 static float CalcScrollSnap(float target, float snap_min, float snap_max, float snap_threshold, float center_ratio)
8219 {
8220 if (target <= snap_min + snap_threshold)
8221 return ImLerp(snap_min, target, center_ratio);
8222 if (target >= snap_max - snap_threshold)
8223 return ImLerp(target, snap_max, center_ratio);
8224 return target;
8225 }
8226
8227 // center_x_ratio: 0.0f left of last item, 0.5f horizontal center of last item, 1.0f right of last item.
SetScrollHereX(float center_x_ratio)8228 void ImGui::SetScrollHereX(float center_x_ratio)
8229 {
8230 ImGuiContext& g = *GImGui;
8231 ImGuiWindow* window = g.CurrentWindow;
8232 float spacing_x = g.Style.ItemSpacing.x;
8233 float target_x = ImLerp(window->DC.LastItemRect.Min.x - spacing_x, window->DC.LastItemRect.Max.x + spacing_x, center_x_ratio);
8234
8235 // Tweak: snap on edges when aiming at an item very close to the edge
8236 const float snap_x_threshold = ImMax(0.0f, window->WindowPadding.x - spacing_x);
8237 const float snap_x_min = window->DC.CursorStartPos.x - window->WindowPadding.x;
8238 const float snap_x_max = window->DC.CursorStartPos.x + window->ContentSize.x + window->WindowPadding.x;
8239 target_x = CalcScrollSnap(target_x, snap_x_min, snap_x_max, snap_x_threshold, center_x_ratio);
8240
8241 SetScrollFromPosX(window, target_x - window->Pos.x, center_x_ratio);
8242 }
8243
8244 // center_y_ratio: 0.0f top of last item, 0.5f vertical center of last item, 1.0f bottom of last item.
SetScrollHereY(float center_y_ratio)8245 void ImGui::SetScrollHereY(float center_y_ratio)
8246 {
8247 ImGuiContext& g = *GImGui;
8248 ImGuiWindow* window = g.CurrentWindow;
8249 float spacing_y = g.Style.ItemSpacing.y;
8250 float target_y = ImLerp(window->DC.CursorPosPrevLine.y - spacing_y, window->DC.CursorPosPrevLine.y + window->DC.PrevLineSize.y + spacing_y, center_y_ratio);
8251
8252 // Tweak: snap on edges when aiming at an item very close to the edge
8253 const float snap_y_threshold = ImMax(0.0f, window->WindowPadding.y - spacing_y);
8254 const float snap_y_min = window->DC.CursorStartPos.y - window->WindowPadding.y;
8255 const float snap_y_max = window->DC.CursorStartPos.y + window->ContentSize.y + window->WindowPadding.y;
8256 target_y = CalcScrollSnap(target_y, snap_y_min, snap_y_max, snap_y_threshold, center_y_ratio);
8257
8258 SetScrollFromPosY(window, target_y - window->Pos.y, center_y_ratio);
8259 }
8260
8261 //-----------------------------------------------------------------------------
8262 // [SECTION] TOOLTIPS
8263 //-----------------------------------------------------------------------------
8264
BeginTooltip()8265 void ImGui::BeginTooltip()
8266 {
8267 BeginTooltipEx(ImGuiWindowFlags_None, ImGuiTooltipFlags_None);
8268 }
8269
BeginTooltipEx(ImGuiWindowFlags extra_flags,ImGuiTooltipFlags tooltip_flags)8270 void ImGui::BeginTooltipEx(ImGuiWindowFlags extra_flags, ImGuiTooltipFlags tooltip_flags)
8271 {
8272 ImGuiContext& g = *GImGui;
8273
8274 if (g.DragDropWithinSource || g.DragDropWithinTarget)
8275 {
8276 // 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)
8277 // In the context of a dragging tooltip we try to reduce that offset and we enforce following the cursor.
8278 // 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.
8279 //ImVec2 tooltip_pos = g.IO.MousePos - g.ActiveIdClickOffset - g.Style.WindowPadding;
8280 ImVec2 tooltip_pos = g.IO.MousePos + ImVec2(16 * g.Style.MouseCursorScale, 8 * g.Style.MouseCursorScale);
8281 SetNextWindowPos(tooltip_pos);
8282 SetNextWindowBgAlpha(g.Style.Colors[ImGuiCol_PopupBg].w * 0.60f);
8283 //PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * 0.60f); // This would be nice but e.g ColorButton with checkboard has issue with transparent colors :(
8284 tooltip_flags |= ImGuiTooltipFlags_OverridePreviousTooltip;
8285 }
8286
8287 char window_name[16];
8288 ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", g.TooltipOverrideCount);
8289 if (tooltip_flags & ImGuiTooltipFlags_OverridePreviousTooltip)
8290 if (ImGuiWindow* window = FindWindowByName(window_name))
8291 if (window->Active)
8292 {
8293 // Hide previous tooltip from being displayed. We can't easily "reset" the content of a window so we create a new one.
8294 window->Hidden = true;
8295 window->HiddenFramesCanSkipItems = 1;
8296 ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", ++g.TooltipOverrideCount);
8297 }
8298 ImGuiWindowFlags flags = ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDocking;
8299 Begin(window_name, NULL, flags | extra_flags);
8300 }
8301
EndTooltip()8302 void ImGui::EndTooltip()
8303 {
8304 IM_ASSERT(GetCurrentWindowRead()->Flags & ImGuiWindowFlags_Tooltip); // Mismatched BeginTooltip()/EndTooltip() calls
8305 End();
8306 }
8307
SetTooltipV(const char * fmt,va_list args)8308 void ImGui::SetTooltipV(const char* fmt, va_list args)
8309 {
8310 BeginTooltipEx(0, ImGuiTooltipFlags_OverridePreviousTooltip);
8311 TextV(fmt, args);
8312 EndTooltip();
8313 }
8314
SetTooltip(const char * fmt,...)8315 void ImGui::SetTooltip(const char* fmt, ...)
8316 {
8317 va_list args;
8318 va_start(args, fmt);
8319 SetTooltipV(fmt, args);
8320 va_end(args);
8321 }
8322
8323 //-----------------------------------------------------------------------------
8324 // [SECTION] POPUPS
8325 //-----------------------------------------------------------------------------
8326
8327 // Supported flags: ImGuiPopupFlags_AnyPopupId, ImGuiPopupFlags_AnyPopupLevel
IsPopupOpen(ImGuiID id,ImGuiPopupFlags popup_flags)8328 bool ImGui::IsPopupOpen(ImGuiID id, ImGuiPopupFlags popup_flags)
8329 {
8330 ImGuiContext& g = *GImGui;
8331 if (popup_flags & ImGuiPopupFlags_AnyPopupId)
8332 {
8333 // Return true if any popup is open at the current BeginPopup() level of the popup stack
8334 // This may be used to e.g. test for another popups already opened to handle popups priorities at the same level.
8335 IM_ASSERT(id == 0);
8336 if (popup_flags & ImGuiPopupFlags_AnyPopupLevel)
8337 return g.OpenPopupStack.Size > 0;
8338 else
8339 return g.OpenPopupStack.Size > g.BeginPopupStack.Size;
8340 }
8341 else
8342 {
8343 if (popup_flags & ImGuiPopupFlags_AnyPopupLevel)
8344 {
8345 // Return true if the popup is open anywhere in the popup stack
8346 for (int n = 0; n < g.OpenPopupStack.Size; n++)
8347 if (g.OpenPopupStack[n].PopupId == id)
8348 return true;
8349 return false;
8350 }
8351 else
8352 {
8353 // Return true if the popup is open at the current BeginPopup() level of the popup stack (this is the most-common query)
8354 return g.OpenPopupStack.Size > g.BeginPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].PopupId == id;
8355 }
8356 }
8357 }
8358
IsPopupOpen(const char * str_id,ImGuiPopupFlags popup_flags)8359 bool ImGui::IsPopupOpen(const char* str_id, ImGuiPopupFlags popup_flags)
8360 {
8361 ImGuiContext& g = *GImGui;
8362 ImGuiID id = (popup_flags & ImGuiPopupFlags_AnyPopupId) ? 0 : g.CurrentWindow->GetID(str_id);
8363 if ((popup_flags & ImGuiPopupFlags_AnyPopupLevel) && id != 0)
8364 IM_ASSERT(0 && "Cannot use IsPopupOpen() with a string id and ImGuiPopupFlags_AnyPopupLevel."); // But non-string version is legal and used internally
8365 return IsPopupOpen(id, popup_flags);
8366 }
8367
GetTopMostPopupModal()8368 ImGuiWindow* ImGui::GetTopMostPopupModal()
8369 {
8370 ImGuiContext& g = *GImGui;
8371 for (int n = g.OpenPopupStack.Size - 1; n >= 0; n--)
8372 if (ImGuiWindow* popup = g.OpenPopupStack.Data[n].Window)
8373 if (popup->Flags & ImGuiWindowFlags_Modal)
8374 return popup;
8375 return NULL;
8376 }
8377
OpenPopup(const char * str_id,ImGuiPopupFlags popup_flags)8378 void ImGui::OpenPopup(const char* str_id, ImGuiPopupFlags popup_flags)
8379 {
8380 ImGuiContext& g = *GImGui;
8381 OpenPopupEx(g.CurrentWindow->GetID(str_id), popup_flags);
8382 }
8383
8384 // Mark popup as open (toggle toward open state).
8385 // Popups are closed when user click outside, or activate a pressable item, or CloseCurrentPopup() is called within a BeginPopup()/EndPopup() block.
8386 // Popup identifiers are relative to the current ID-stack (so OpenPopup and BeginPopup needs to be at the same level).
8387 // One open popup per level of the popup hierarchy (NB: when assigning we reset the Window member of ImGuiPopupRef to NULL)
OpenPopupEx(ImGuiID id,ImGuiPopupFlags popup_flags)8388 void ImGui::OpenPopupEx(ImGuiID id, ImGuiPopupFlags popup_flags)
8389 {
8390 ImGuiContext& g = *GImGui;
8391 ImGuiWindow* parent_window = g.CurrentWindow;
8392 const int current_stack_size = g.BeginPopupStack.Size;
8393
8394 if (popup_flags & ImGuiPopupFlags_NoOpenOverExistingPopup)
8395 if (IsPopupOpen(0u, ImGuiPopupFlags_AnyPopupId))
8396 return;
8397
8398 ImGuiPopupData popup_ref; // Tagged as new ref as Window will be set back to NULL if we write this into OpenPopupStack.
8399 popup_ref.PopupId = id;
8400 popup_ref.Window = NULL;
8401 popup_ref.SourceWindow = g.NavWindow;
8402 popup_ref.OpenFrameCount = g.FrameCount;
8403 popup_ref.OpenParentId = parent_window->IDStack.back();
8404 popup_ref.OpenPopupPos = NavCalcPreferredRefPos();
8405 popup_ref.OpenMousePos = IsMousePosValid(&g.IO.MousePos) ? g.IO.MousePos : popup_ref.OpenPopupPos;
8406
8407 IMGUI_DEBUG_LOG_POPUP("OpenPopupEx(0x%08X)\n", id);
8408 if (g.OpenPopupStack.Size < current_stack_size + 1)
8409 {
8410 g.OpenPopupStack.push_back(popup_ref);
8411 }
8412 else
8413 {
8414 // 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
8415 // 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
8416 // 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.
8417 if (g.OpenPopupStack[current_stack_size].PopupId == id && g.OpenPopupStack[current_stack_size].OpenFrameCount == g.FrameCount - 1)
8418 {
8419 g.OpenPopupStack[current_stack_size].OpenFrameCount = popup_ref.OpenFrameCount;
8420 }
8421 else
8422 {
8423 // Close child popups if any, then flag popup for open/reopen
8424 ClosePopupToLevel(current_stack_size, false);
8425 g.OpenPopupStack.push_back(popup_ref);
8426 }
8427
8428 // When reopening a popup we first refocus its parent, otherwise if its parent is itself a popup it would get closed by ClosePopupsOverWindow().
8429 // This is equivalent to what ClosePopupToLevel() does.
8430 //if (g.OpenPopupStack[current_stack_size].PopupId == id)
8431 // FocusWindow(parent_window);
8432 }
8433 }
8434
8435 // When popups are stacked, clicking on a lower level popups puts focus back to it and close popups above it.
8436 // This function closes any popups that are over 'ref_window'.
ClosePopupsOverWindow(ImGuiWindow * ref_window,bool restore_focus_to_window_under_popup)8437 void ImGui::ClosePopupsOverWindow(ImGuiWindow* ref_window, bool restore_focus_to_window_under_popup)
8438 {
8439 ImGuiContext& g = *GImGui;
8440 if (g.OpenPopupStack.Size == 0)
8441 return;
8442
8443 // Don't close our own child popup windows.
8444 int popup_count_to_keep = 0;
8445 if (ref_window)
8446 {
8447 // Find the highest popup which is a descendant of the reference window (generally reference window = NavWindow)
8448 for (; popup_count_to_keep < g.OpenPopupStack.Size; popup_count_to_keep++)
8449 {
8450 ImGuiPopupData& popup = g.OpenPopupStack[popup_count_to_keep];
8451 if (!popup.Window)
8452 continue;
8453 IM_ASSERT((popup.Window->Flags & ImGuiWindowFlags_Popup) != 0);
8454 if (popup.Window->Flags & ImGuiWindowFlags_ChildWindow)
8455 continue;
8456
8457 // Trim the stack unless the popup is a direct parent of the reference window (the reference window is often the NavWindow)
8458 // - With this stack of window, clicking/focusing Popup1 will close Popup2 and Popup3:
8459 // Window -> Popup1 -> Popup2 -> Popup3
8460 // - Each popups may contain child windows, which is why we compare ->RootWindow!
8461 // Window -> Popup1 -> Popup1_Child -> Popup2 -> Popup2_Child
8462 bool ref_window_is_descendent_of_popup = false;
8463 for (int n = popup_count_to_keep; n < g.OpenPopupStack.Size; n++)
8464 if (ImGuiWindow* popup_window = g.OpenPopupStack[n].Window)
8465 if (popup_window->RootWindow == ref_window->RootWindow)
8466 {
8467 ref_window_is_descendent_of_popup = true;
8468 break;
8469 }
8470 if (!ref_window_is_descendent_of_popup)
8471 break;
8472 }
8473 }
8474 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
8475 {
8476 IMGUI_DEBUG_LOG_POPUP("ClosePopupsOverWindow(\"%s\") -> ClosePopupToLevel(%d)\n", ref_window->Name, popup_count_to_keep);
8477 ClosePopupToLevel(popup_count_to_keep, restore_focus_to_window_under_popup);
8478 }
8479 }
8480
ClosePopupToLevel(int remaining,bool restore_focus_to_window_under_popup)8481 void ImGui::ClosePopupToLevel(int remaining, bool restore_focus_to_window_under_popup)
8482 {
8483 ImGuiContext& g = *GImGui;
8484 IMGUI_DEBUG_LOG_POPUP("ClosePopupToLevel(%d), restore_focus_to_window_under_popup=%d\n", remaining, restore_focus_to_window_under_popup);
8485 IM_ASSERT(remaining >= 0 && remaining < g.OpenPopupStack.Size);
8486
8487 // Trim open popup stack
8488 ImGuiWindow* focus_window = g.OpenPopupStack[remaining].SourceWindow;
8489 ImGuiWindow* popup_window = g.OpenPopupStack[remaining].Window;
8490 g.OpenPopupStack.resize(remaining);
8491
8492 if (restore_focus_to_window_under_popup)
8493 {
8494 if (focus_window && !focus_window->WasActive && popup_window)
8495 {
8496 // Fallback
8497 FocusTopMostWindowUnderOne(popup_window, NULL);
8498 }
8499 else
8500 {
8501 if (g.NavLayer == ImGuiNavLayer_Main && focus_window)
8502 focus_window = NavRestoreLastChildNavWindow(focus_window);
8503 FocusWindow(focus_window);
8504 }
8505 }
8506 }
8507
8508 // Close the popup we have begin-ed into.
CloseCurrentPopup()8509 void ImGui::CloseCurrentPopup()
8510 {
8511 ImGuiContext& g = *GImGui;
8512 int popup_idx = g.BeginPopupStack.Size - 1;
8513 if (popup_idx < 0 || popup_idx >= g.OpenPopupStack.Size || g.BeginPopupStack[popup_idx].PopupId != g.OpenPopupStack[popup_idx].PopupId)
8514 return;
8515
8516 // Closing a menu closes its top-most parent popup (unless a modal)
8517 while (popup_idx > 0)
8518 {
8519 ImGuiWindow* popup_window = g.OpenPopupStack[popup_idx].Window;
8520 ImGuiWindow* parent_popup_window = g.OpenPopupStack[popup_idx - 1].Window;
8521 bool close_parent = false;
8522 if (popup_window && (popup_window->Flags & ImGuiWindowFlags_ChildMenu))
8523 if (parent_popup_window == NULL || !(parent_popup_window->Flags & ImGuiWindowFlags_Modal))
8524 close_parent = true;
8525 if (!close_parent)
8526 break;
8527 popup_idx--;
8528 }
8529 IMGUI_DEBUG_LOG_POPUP("CloseCurrentPopup %d -> %d\n", g.BeginPopupStack.Size - 1, popup_idx);
8530 ClosePopupToLevel(popup_idx, true);
8531
8532 // A common pattern is to close a popup when selecting a menu item/selectable that will open another window.
8533 // To improve this usage pattern, we avoid nav highlight for a single frame in the parent window.
8534 // Similarly, we could avoid mouse hover highlight in this window but it is less visually problematic.
8535 if (ImGuiWindow* window = g.NavWindow)
8536 window->DC.NavHideHighlightOneFrame = true;
8537 }
8538
8539 // Attention! BeginPopup() adds default flags which BeginPopupEx()!
BeginPopupEx(ImGuiID id,ImGuiWindowFlags flags)8540 bool ImGui::BeginPopupEx(ImGuiID id, ImGuiWindowFlags flags)
8541 {
8542 ImGuiContext& g = *GImGui;
8543 if (!IsPopupOpen(id, ImGuiPopupFlags_None))
8544 {
8545 g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
8546 return false;
8547 }
8548
8549 char name[20];
8550 if (flags & ImGuiWindowFlags_ChildMenu)
8551 ImFormatString(name, IM_ARRAYSIZE(name), "##Menu_%02d", g.BeginPopupStack.Size); // Recycle windows based on depth
8552 else
8553 ImFormatString(name, IM_ARRAYSIZE(name), "##Popup_%08x", id); // Not recycling, so we can close/open during the same frame
8554
8555 flags |= ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoDocking;
8556 bool is_open = Begin(name, NULL, flags);
8557 if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display)
8558 EndPopup();
8559
8560 return is_open;
8561 }
8562
BeginPopup(const char * str_id,ImGuiWindowFlags flags)8563 bool ImGui::BeginPopup(const char* str_id, ImGuiWindowFlags flags)
8564 {
8565 ImGuiContext& g = *GImGui;
8566 if (g.OpenPopupStack.Size <= g.BeginPopupStack.Size) // Early out for performance
8567 {
8568 g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
8569 return false;
8570 }
8571 flags |= ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings;
8572 return BeginPopupEx(g.CurrentWindow->GetID(str_id), flags);
8573 }
8574
8575 // If 'p_open' is specified for a modal popup window, the popup will have a regular close button which will close the popup.
8576 // 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.
BeginPopupModal(const char * name,bool * p_open,ImGuiWindowFlags flags)8577 bool ImGui::BeginPopupModal(const char* name, bool* p_open, ImGuiWindowFlags flags)
8578 {
8579 ImGuiContext& g = *GImGui;
8580 ImGuiWindow* window = g.CurrentWindow;
8581 const ImGuiID id = window->GetID(name);
8582 if (!IsPopupOpen(id, ImGuiPopupFlags_None))
8583 {
8584 g.NextWindowData.ClearFlags(); // We behave like Begin() and need to consume those values
8585 return false;
8586 }
8587
8588 // Center modal windows by default for increased visibility
8589 // (this won't really last as settings will kick in, and is mostly for backward compatibility. user may do the same themselves)
8590 // FIXME: Should test for (PosCond & window->SetWindowPosAllowFlags) with the upcoming window.
8591 if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasPos) == 0)
8592 {
8593 ImGuiViewportP* viewport = window->WasActive ? window->Viewport : (ImGuiViewportP*)GetMainViewport(); // FIXME-VIEWPORT: What may be our reference viewport?
8594 SetNextWindowPos(viewport->GetMainRect().GetCenter(), ImGuiCond_FirstUseEver, ImVec2(0.5f, 0.5f));
8595 }
8596
8597 flags |= ImGuiWindowFlags_Popup | ImGuiWindowFlags_Modal | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDocking;
8598 const bool is_open = Begin(name, p_open, flags);
8599 if (!is_open || (p_open && !*p_open)) // NB: is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
8600 {
8601 EndPopup();
8602 if (is_open)
8603 ClosePopupToLevel(g.BeginPopupStack.Size, true);
8604 return false;
8605 }
8606 return is_open;
8607 }
8608
EndPopup()8609 void ImGui::EndPopup()
8610 {
8611 ImGuiContext& g = *GImGui;
8612 ImGuiWindow* window = g.CurrentWindow;
8613 IM_ASSERT(window->Flags & ImGuiWindowFlags_Popup); // Mismatched BeginPopup()/EndPopup() calls
8614 IM_ASSERT(g.BeginPopupStack.Size > 0);
8615
8616 // Make all menus and popups wrap around for now, may need to expose that policy.
8617 if (g.NavWindow == window)
8618 NavMoveRequestTryWrapping(window, ImGuiNavMoveFlags_LoopY);
8619
8620 // Child-popups don't need to be laid out
8621 IM_ASSERT(g.WithinEndChild == false);
8622 if (window->Flags & ImGuiWindowFlags_ChildWindow)
8623 g.WithinEndChild = true;
8624 End();
8625 g.WithinEndChild = false;
8626 }
8627
8628 // Open a popup if mouse button is released over the item
OpenPopupContextItem(const char * str_id,ImGuiPopupFlags popup_flags)8629 bool ImGui::OpenPopupContextItem(const char* str_id, ImGuiPopupFlags popup_flags)
8630 {
8631 ImGuiWindow* window = GImGui->CurrentWindow;
8632 int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_);
8633 if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup))
8634 {
8635 ImGuiID id = str_id ? window->GetID(str_id) : window->DC.LastItemId; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict!
8636 IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item)
8637 OpenPopupEx(id, popup_flags);
8638 return true;
8639 }
8640 return false;
8641 }
8642
8643 // This is a helper to handle the simplest case of associating one named popup to one given widget.
8644 // - You can pass a NULL str_id to use the identifier of the last item.
8645 // - You may want to handle this on user side if you have specific needs (e.g. tweaking IsItemHovered() parameters).
8646 // - This is essentially the same as calling OpenPopupContextItem() + BeginPopup() but written to avoid
8647 // computing the ID twice because BeginPopupContextXXX functions are called very frequently.
BeginPopupContextItem(const char * str_id,ImGuiPopupFlags popup_flags)8648 bool ImGui::BeginPopupContextItem(const char* str_id, ImGuiPopupFlags popup_flags)
8649 {
8650 ImGuiWindow* window = GImGui->CurrentWindow;
8651 if (window->SkipItems)
8652 return false;
8653 ImGuiID id = str_id ? window->GetID(str_id) : window->DC.LastItemId; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict!
8654 IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item)
8655 int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_);
8656 if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup))
8657 OpenPopupEx(id, popup_flags);
8658 return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings);
8659 }
8660
BeginPopupContextWindow(const char * str_id,ImGuiPopupFlags popup_flags)8661 bool ImGui::BeginPopupContextWindow(const char* str_id, ImGuiPopupFlags popup_flags)
8662 {
8663 ImGuiWindow* window = GImGui->CurrentWindow;
8664 if (!str_id)
8665 str_id = "window_context";
8666 ImGuiID id = window->GetID(str_id);
8667 int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_);
8668 if (IsMouseReleased(mouse_button) && IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup))
8669 if (!(popup_flags & ImGuiPopupFlags_NoOpenOverItems) || !IsAnyItemHovered())
8670 OpenPopupEx(id, popup_flags);
8671 return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings);
8672 }
8673
BeginPopupContextVoid(const char * str_id,ImGuiPopupFlags popup_flags)8674 bool ImGui::BeginPopupContextVoid(const char* str_id, ImGuiPopupFlags popup_flags)
8675 {
8676 ImGuiWindow* window = GImGui->CurrentWindow;
8677 if (!str_id)
8678 str_id = "void_context";
8679 ImGuiID id = window->GetID(str_id);
8680 int mouse_button = (popup_flags & ImGuiPopupFlags_MouseButtonMask_);
8681 if (IsMouseReleased(mouse_button) && !IsWindowHovered(ImGuiHoveredFlags_AnyWindow))
8682 if (GetTopMostPopupModal() == NULL)
8683 OpenPopupEx(id, popup_flags);
8684 return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings);
8685 }
8686
8687 // 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.)
8688 // 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.
8689 // (r_outer is usually equivalent to the viewport rectangle minus padding, but when multi-viewports are enabled and monitor
8690 // information are available, it may represent the entire platform monitor from the frame of reference of the current viewport.
8691 // this allows us to have tooltips/popups displayed out of the parent viewport.)
FindBestWindowPosForPopupEx(const ImVec2 & ref_pos,const ImVec2 & size,ImGuiDir * last_dir,const ImRect & r_outer,const ImRect & r_avoid,ImGuiPopupPositionPolicy policy)8692 ImVec2 ImGui::FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& size, ImGuiDir* last_dir, const ImRect& r_outer, const ImRect& r_avoid, ImGuiPopupPositionPolicy policy)
8693 {
8694 ImVec2 base_pos_clamped = ImClamp(ref_pos, r_outer.Min, r_outer.Max - size);
8695 //GetForegroundDrawList()->AddRect(r_avoid.Min, r_avoid.Max, IM_COL32(255,0,0,255));
8696 //GetForegroundDrawList()->AddRect(r_outer.Min, r_outer.Max, IM_COL32(0,255,0,255));
8697
8698 // Combo Box policy (we want a connecting edge)
8699 if (policy == ImGuiPopupPositionPolicy_ComboBox)
8700 {
8701 const ImGuiDir dir_prefered_order[ImGuiDir_COUNT] = { ImGuiDir_Down, ImGuiDir_Right, ImGuiDir_Left, ImGuiDir_Up };
8702 for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_COUNT; n++)
8703 {
8704 const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n];
8705 if (n != -1 && dir == *last_dir) // Already tried this direction?
8706 continue;
8707 ImVec2 pos;
8708 if (dir == ImGuiDir_Down) pos = ImVec2(r_avoid.Min.x, r_avoid.Max.y); // Below, Toward Right (default)
8709 if (dir == ImGuiDir_Right) pos = ImVec2(r_avoid.Min.x, r_avoid.Min.y - size.y); // Above, Toward Right
8710 if (dir == ImGuiDir_Left) pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Max.y); // Below, Toward Left
8711 if (dir == ImGuiDir_Up) pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Min.y - size.y); // Above, Toward Left
8712 if (!r_outer.Contains(ImRect(pos, pos + size)))
8713 continue;
8714 *last_dir = dir;
8715 return pos;
8716 }
8717 }
8718
8719 // Default popup policy
8720 const ImGuiDir dir_prefered_order[ImGuiDir_COUNT] = { ImGuiDir_Right, ImGuiDir_Down, ImGuiDir_Up, ImGuiDir_Left };
8721 for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_COUNT; n++)
8722 {
8723 const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n];
8724 if (n != -1 && dir == *last_dir) // Already tried this direction?
8725 continue;
8726 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);
8727 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);
8728 if (avail_w < size.x || avail_h < size.y)
8729 continue;
8730 ImVec2 pos;
8731 pos.x = (dir == ImGuiDir_Left) ? r_avoid.Min.x - size.x : (dir == ImGuiDir_Right) ? r_avoid.Max.x : base_pos_clamped.x;
8732 pos.y = (dir == ImGuiDir_Up) ? r_avoid.Min.y - size.y : (dir == ImGuiDir_Down) ? r_avoid.Max.y : base_pos_clamped.y;
8733 *last_dir = dir;
8734 return pos;
8735 }
8736
8737 // Fallback, try to keep within display
8738 *last_dir = ImGuiDir_None;
8739 ImVec2 pos = ref_pos;
8740 pos.x = ImMax(ImMin(pos.x + size.x, r_outer.Max.x) - size.x, r_outer.Min.x);
8741 pos.y = ImMax(ImMin(pos.y + size.y, r_outer.Max.y) - size.y, r_outer.Min.y);
8742 return pos;
8743 }
8744
8745 // Note that this is used for popups, which can overlap the non work-area of individual viewports.
GetWindowAllowedExtentRect(ImGuiWindow * window)8746 ImRect ImGui::GetWindowAllowedExtentRect(ImGuiWindow* window)
8747 {
8748 ImGuiContext& g = *GImGui;
8749 ImRect r_screen;
8750 if (window->ViewportAllowPlatformMonitorExtend >= 0)
8751 {
8752 // Extent with be in the frame of reference of the given viewport (so Min is likely to be negative here)
8753 const ImGuiPlatformMonitor& monitor = g.PlatformIO.Monitors[window->ViewportAllowPlatformMonitorExtend];
8754 r_screen.Min = monitor.WorkPos;
8755 r_screen.Max = monitor.WorkPos + monitor.WorkSize;
8756 }
8757 else
8758 {
8759 // Use the full viewport area (not work area) for popups
8760 r_screen.Min = window->Viewport->Pos;
8761 r_screen.Max = window->Viewport->Pos + window->Viewport->Size;
8762 }
8763 ImVec2 padding = g.Style.DisplaySafeAreaPadding;
8764 r_screen.Expand(ImVec2((r_screen.GetWidth() > padding.x * 2) ? -padding.x : 0.0f, (r_screen.GetHeight() > padding.y * 2) ? -padding.y : 0.0f));
8765 return r_screen;
8766 }
8767
FindBestWindowPosForPopup(ImGuiWindow * window)8768 ImVec2 ImGui::FindBestWindowPosForPopup(ImGuiWindow* window)
8769 {
8770 ImGuiContext& g = *GImGui;
8771 if (window->Flags & ImGuiWindowFlags_ChildMenu)
8772 {
8773 // Child menus typically request _any_ position within the parent menu item, and then we move the new menu outside the parent bounds.
8774 // This is how we end up with child menus appearing (most-commonly) on the right of the parent menu.
8775 ImGuiWindow* parent_window = window->ParentWindow;
8776 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).
8777 ImRect r_outer = GetWindowAllowedExtentRect(window);
8778 ImRect r_avoid;
8779 if (parent_window->DC.MenuBarAppending)
8780 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
8781 else
8782 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);
8783 return FindBestWindowPosForPopupEx(window->Pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid);
8784 }
8785 if (window->Flags & ImGuiWindowFlags_Popup)
8786 {
8787 ImRect r_outer = GetWindowAllowedExtentRect(window);
8788 ImRect r_avoid = ImRect(window->Pos.x - 1, window->Pos.y - 1, window->Pos.x + 1, window->Pos.y + 1);
8789 return FindBestWindowPosForPopupEx(window->Pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid);
8790 }
8791 if (window->Flags & ImGuiWindowFlags_Tooltip)
8792 {
8793 // Position tooltip (always follows mouse)
8794 float sc = g.Style.MouseCursorScale;
8795 ImVec2 ref_pos = NavCalcPreferredRefPos();
8796 ImRect r_outer = GetWindowAllowedExtentRect(window);
8797 ImRect r_avoid;
8798 if (!g.NavDisableHighlight && g.NavDisableMouseHover && !(g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos))
8799 r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 16, ref_pos.y + 8);
8800 else
8801 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.
8802 ImVec2 pos = FindBestWindowPosForPopupEx(ref_pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid);
8803 if (window->AutoPosLastDirection == ImGuiDir_None)
8804 pos = ref_pos + ImVec2(2, 2); // If there's not enough room, for tooltip we prefer avoiding the cursor at all cost even if it means that part of the tooltip won't be visible.
8805 return pos;
8806 }
8807 IM_ASSERT(0);
8808 return window->Pos;
8809 }
8810
8811 //-----------------------------------------------------------------------------
8812 // [SECTION] KEYBOARD/GAMEPAD NAVIGATION
8813 //-----------------------------------------------------------------------------
8814
8815 // FIXME-NAV: The existence of SetNavID vs SetNavIDWithRectRel vs SetFocusID is incredibly messy and confusing,
8816 // and needs some explanation or serious refactoring.
SetNavID(ImGuiID id,int nav_layer,ImGuiID focus_scope_id)8817 void ImGui::SetNavID(ImGuiID id, int nav_layer, ImGuiID focus_scope_id)
8818 {
8819 ImGuiContext& g = *GImGui;
8820 IM_ASSERT(g.NavWindow);
8821 IM_ASSERT(nav_layer == 0 || nav_layer == 1);
8822 g.NavId = id;
8823 g.NavFocusScopeId = focus_scope_id;
8824 g.NavWindow->NavLastIds[nav_layer] = id;
8825 }
8826
SetNavIDWithRectRel(ImGuiID id,int nav_layer,ImGuiID focus_scope_id,const ImRect & rect_rel)8827 void ImGui::SetNavIDWithRectRel(ImGuiID id, int nav_layer, ImGuiID focus_scope_id, const ImRect& rect_rel)
8828 {
8829 ImGuiContext& g = *GImGui;
8830 SetNavID(id, nav_layer, focus_scope_id);
8831 g.NavWindow->NavRectRel[nav_layer] = rect_rel;
8832 g.NavMousePosDirty = true;
8833 g.NavDisableHighlight = false;
8834 g.NavDisableMouseHover = true;
8835 }
8836
SetFocusID(ImGuiID id,ImGuiWindow * window)8837 void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window)
8838 {
8839 ImGuiContext& g = *GImGui;
8840 IM_ASSERT(id != 0);
8841
8842 // Assume that SetFocusID() is called in the context where its window->DC.NavLayerCurrent and window->DC.NavFocusScopeIdCurrent are valid.
8843 // Note that window may be != g.CurrentWindow (e.g. SetFocusID call in InputTextEx for multi-line text)
8844 const ImGuiNavLayer nav_layer = window->DC.NavLayerCurrent;
8845 if (g.NavWindow != window)
8846 g.NavInitRequest = false;
8847 g.NavWindow = window;
8848 g.NavId = id;
8849 g.NavLayer = nav_layer;
8850 g.NavFocusScopeId = window->DC.NavFocusScopeIdCurrent;
8851 window->NavLastIds[nav_layer] = id;
8852 if (window->DC.LastItemId == id)
8853 window->NavRectRel[nav_layer] = ImRect(window->DC.LastItemRect.Min - window->Pos, window->DC.LastItemRect.Max - window->Pos);
8854
8855 if (g.ActiveIdSource == ImGuiInputSource_Nav)
8856 g.NavDisableMouseHover = true;
8857 else
8858 g.NavDisableHighlight = true;
8859 }
8860
ImGetDirQuadrantFromDelta(float dx,float dy)8861 ImGuiDir ImGetDirQuadrantFromDelta(float dx, float dy)
8862 {
8863 if (ImFabs(dx) > ImFabs(dy))
8864 return (dx > 0.0f) ? ImGuiDir_Right : ImGuiDir_Left;
8865 return (dy > 0.0f) ? ImGuiDir_Down : ImGuiDir_Up;
8866 }
8867
NavScoreItemDistInterval(float a0,float a1,float b0,float b1)8868 static float inline NavScoreItemDistInterval(float a0, float a1, float b0, float b1)
8869 {
8870 if (a1 < b0)
8871 return a1 - b0;
8872 if (b1 < a0)
8873 return a0 - b1;
8874 return 0.0f;
8875 }
8876
NavClampRectToVisibleAreaForMoveDir(ImGuiDir move_dir,ImRect & r,const ImRect & clip_rect)8877 static void inline NavClampRectToVisibleAreaForMoveDir(ImGuiDir move_dir, ImRect& r, const ImRect& clip_rect)
8878 {
8879 if (move_dir == ImGuiDir_Left || move_dir == ImGuiDir_Right)
8880 {
8881 r.Min.y = ImClamp(r.Min.y, clip_rect.Min.y, clip_rect.Max.y);
8882 r.Max.y = ImClamp(r.Max.y, clip_rect.Min.y, clip_rect.Max.y);
8883 }
8884 else
8885 {
8886 r.Min.x = ImClamp(r.Min.x, clip_rect.Min.x, clip_rect.Max.x);
8887 r.Max.x = ImClamp(r.Max.x, clip_rect.Min.x, clip_rect.Max.x);
8888 }
8889 }
8890
8891 // Scoring function for gamepad/keyboard directional navigation. Based on https://gist.github.com/rygorous/6981057
NavScoreItem(ImGuiNavMoveResult * result,ImRect cand)8892 static bool ImGui::NavScoreItem(ImGuiNavMoveResult* result, ImRect cand)
8893 {
8894 ImGuiContext& g = *GImGui;
8895 ImGuiWindow* window = g.CurrentWindow;
8896 if (g.NavLayer != window->DC.NavLayerCurrent)
8897 return false;
8898
8899 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)
8900 g.NavScoringCount++;
8901
8902 // When entering through a NavFlattened border, we consider child window items as fully clipped for scoring
8903 if (window->ParentWindow == g.NavWindow)
8904 {
8905 IM_ASSERT((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened);
8906 if (!window->ClipRect.Overlaps(cand))
8907 return false;
8908 cand.ClipWithFull(window->ClipRect); // This allows the scored item to not overlap other candidates in the parent window
8909 }
8910
8911 // 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)
8912 // For example, this ensure that items in one column are not reached when moving vertically from items in another column.
8913 NavClampRectToVisibleAreaForMoveDir(g.NavMoveClipDir, cand, window->ClipRect);
8914
8915 // Compute distance between boxes
8916 // FIXME-NAV: Introducing biases for vertical navigation, needs to be removed.
8917 float dbx = NavScoreItemDistInterval(cand.Min.x, cand.Max.x, curr.Min.x, curr.Max.x);
8918 float dby = NavScoreItemDistInterval(ImLerp(cand.Min.y, cand.Max.y, 0.2f), ImLerp(cand.Min.y, cand.Max.y, 0.8f), ImLerp(curr.Min.y, curr.Max.y, 0.2f), ImLerp(curr.Min.y, curr.Max.y, 0.8f)); // Scale down on Y to keep using box-distance for vertically touching items
8919 if (dby != 0.0f && dbx != 0.0f)
8920 dbx = (dbx / 1000.0f) + ((dbx > 0.0f) ? +1.0f : -1.0f);
8921 float dist_box = ImFabs(dbx) + ImFabs(dby);
8922
8923 // 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)
8924 float dcx = (cand.Min.x + cand.Max.x) - (curr.Min.x + curr.Max.x);
8925 float dcy = (cand.Min.y + cand.Max.y) - (curr.Min.y + curr.Max.y);
8926 float dist_center = ImFabs(dcx) + ImFabs(dcy); // L1 metric (need this for our connectedness guarantee)
8927
8928 // Determine which quadrant of 'curr' our candidate item 'cand' lies in based on distance
8929 ImGuiDir quadrant;
8930 float dax = 0.0f, day = 0.0f, dist_axial = 0.0f;
8931 if (dbx != 0.0f || dby != 0.0f)
8932 {
8933 // For non-overlapping boxes, use distance between boxes
8934 dax = dbx;
8935 day = dby;
8936 dist_axial = dist_box;
8937 quadrant = ImGetDirQuadrantFromDelta(dbx, dby);
8938 }
8939 else if (dcx != 0.0f || dcy != 0.0f)
8940 {
8941 // For overlapping boxes with different centers, use distance between centers
8942 dax = dcx;
8943 day = dcy;
8944 dist_axial = dist_center;
8945 quadrant = ImGetDirQuadrantFromDelta(dcx, dcy);
8946 }
8947 else
8948 {
8949 // 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)
8950 quadrant = (window->DC.LastItemId < g.NavId) ? ImGuiDir_Left : ImGuiDir_Right;
8951 }
8952
8953 #if IMGUI_DEBUG_NAV_SCORING
8954 char buf[128];
8955 if (IsMouseHoveringRect(cand.Min, cand.Max))
8956 {
8957 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]);
8958 ImDrawList* draw_list = GetForegroundDrawList(window);
8959 draw_list->AddRect(curr.Min, curr.Max, IM_COL32(255,200,0,100));
8960 draw_list->AddRect(cand.Min, cand.Max, IM_COL32(255,255,0,200));
8961 draw_list->AddRectFilled(cand.Max - ImVec2(4, 4), cand.Max + CalcTextSize(buf) + ImVec2(4, 4), IM_COL32(40,0,0,150));
8962 draw_list->AddText(g.IO.FontDefault, 13.0f, cand.Max, ~0U, buf);
8963 }
8964 else if (g.IO.KeyCtrl) // Hold to preview score in matching quadrant. Press C to rotate.
8965 {
8966 if (IsKeyPressedMap(ImGuiKey_C)) { g.NavMoveDirLast = (ImGuiDir)((g.NavMoveDirLast + 1) & 3); g.IO.KeysDownDuration[g.IO.KeyMap[ImGuiKey_C]] = 0.01f; }
8967 if (quadrant == g.NavMoveDir)
8968 {
8969 ImFormatString(buf, IM_ARRAYSIZE(buf), "%.0f/%.0f", dist_box, dist_center);
8970 ImDrawList* draw_list = GetForegroundDrawList(window);
8971 draw_list->AddRectFilled(cand.Min, cand.Max, IM_COL32(255, 0, 0, 200));
8972 draw_list->AddText(g.IO.FontDefault, 13.0f, cand.Min, IM_COL32(255, 255, 255, 255), buf);
8973 }
8974 }
8975 #endif
8976
8977 // Is it in the quadrant we're interesting in moving to?
8978 bool new_best = false;
8979 if (quadrant == g.NavMoveDir)
8980 {
8981 // Does it beat the current best candidate?
8982 if (dist_box < result->DistBox)
8983 {
8984 result->DistBox = dist_box;
8985 result->DistCenter = dist_center;
8986 return true;
8987 }
8988 if (dist_box == result->DistBox)
8989 {
8990 // Try using distance between center points to break ties
8991 if (dist_center < result->DistCenter)
8992 {
8993 result->DistCenter = dist_center;
8994 new_best = true;
8995 }
8996 else if (dist_center == result->DistCenter)
8997 {
8998 // Still tied! we need to be extra-careful to make sure everything gets linked properly. We consistently break ties by symbolically moving "later" items
8999 // (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),
9000 // 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.
9001 if (((g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) ? dby : dbx) < 0.0f) // moving bj to the right/down decreases distance
9002 new_best = true;
9003 }
9004 }
9005 }
9006
9007 // 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
9008 // are found, so it only augments the graph produced by the above method using extra links. (important, since it doesn't guarantee strong connectedness)
9009 // 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.
9010 // 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.
9011 // Disabling it may lead to disconnected graphs when nodes are very spaced out on different axis. Perhaps consider offering this as an option?
9012 if (result->DistBox == FLT_MAX && dist_axial < result->DistAxial) // Check axial match
9013 if (g.NavLayer == ImGuiNavLayer_Menu && !(g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))
9014 if ((g.NavMoveDir == ImGuiDir_Left && dax < 0.0f) || (g.NavMoveDir == ImGuiDir_Right && dax > 0.0f) || (g.NavMoveDir == ImGuiDir_Up && day < 0.0f) || (g.NavMoveDir == ImGuiDir_Down && day > 0.0f))
9015 {
9016 result->DistAxial = dist_axial;
9017 new_best = true;
9018 }
9019
9020 return new_best;
9021 }
9022
9023 // We get there when either NavId == id, or when g.NavAnyRequest is set (which is updated by NavUpdateAnyRequestFlag above)
NavProcessItem(ImGuiWindow * window,const ImRect & nav_bb,const ImGuiID id)9024 static void ImGui::NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, const ImGuiID id)
9025 {
9026 ImGuiContext& g = *GImGui;
9027 //if (!g.IO.NavActive) // [2017/10/06] Removed this possibly redundant test but I am not sure of all the side-effects yet. Some of the feature here will need to work regardless of using a _NoNavInputs flag.
9028 // return;
9029
9030 const ImGuiItemFlags item_flags = window->DC.ItemFlags;
9031 const ImRect nav_bb_rel(nav_bb.Min - window->Pos, nav_bb.Max - window->Pos);
9032
9033 // Process Init Request
9034 if (g.NavInitRequest && g.NavLayer == window->DC.NavLayerCurrent)
9035 {
9036 // Even if 'ImGuiItemFlags_NoNavDefaultFocus' is on (typically collapse/close button) we record the first ResultId so they can be used as a fallback
9037 if (!(item_flags & ImGuiItemFlags_NoNavDefaultFocus) || g.NavInitResultId == 0)
9038 {
9039 g.NavInitResultId = id;
9040 g.NavInitResultRectRel = nav_bb_rel;
9041 }
9042 if (!(item_flags & ImGuiItemFlags_NoNavDefaultFocus))
9043 {
9044 g.NavInitRequest = false; // Found a match, clear request
9045 NavUpdateAnyRequestFlag();
9046 }
9047 }
9048
9049 // Process Move Request (scoring for navigation)
9050 // FIXME-NAV: Consider policy for double scoring (scoring from NavScoringRectScreen + scoring from a rect wrapped according to current wrapping policy)
9051 if ((g.NavId != id || (g.NavMoveRequestFlags & ImGuiNavMoveFlags_AllowCurrentNavId)) && !(item_flags & (ImGuiItemFlags_Disabled | ImGuiItemFlags_NoNav)))
9052 {
9053 ImGuiNavMoveResult* result = (window == g.NavWindow) ? &g.NavMoveResultLocal : &g.NavMoveResultOther;
9054 #if IMGUI_DEBUG_NAV_SCORING
9055 // [DEBUG] Score all items in NavWindow at all times
9056 if (!g.NavMoveRequest)
9057 g.NavMoveDir = g.NavMoveDirLast;
9058 bool new_best = NavScoreItem(result, nav_bb) && g.NavMoveRequest;
9059 #else
9060 bool new_best = g.NavMoveRequest && NavScoreItem(result, nav_bb);
9061 #endif
9062 if (new_best)
9063 {
9064 result->Window = window;
9065 result->ID = id;
9066 result->FocusScopeId = window->DC.NavFocusScopeIdCurrent;
9067 result->RectRel = nav_bb_rel;
9068 }
9069
9070 // Features like PageUp/PageDown need to maintain a separate score for the visible set of items.
9071 const float VISIBLE_RATIO = 0.70f;
9072 if ((g.NavMoveRequestFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet) && window->ClipRect.Overlaps(nav_bb))
9073 if (ImClamp(nav_bb.Max.y, window->ClipRect.Min.y, window->ClipRect.Max.y) - ImClamp(nav_bb.Min.y, window->ClipRect.Min.y, window->ClipRect.Max.y) >= (nav_bb.Max.y - nav_bb.Min.y) * VISIBLE_RATIO)
9074 if (NavScoreItem(&g.NavMoveResultLocalVisibleSet, nav_bb))
9075 {
9076 result = &g.NavMoveResultLocalVisibleSet;
9077 result->Window = window;
9078 result->ID = id;
9079 result->FocusScopeId = window->DC.NavFocusScopeIdCurrent;
9080 result->RectRel = nav_bb_rel;
9081 }
9082 }
9083
9084 // Update window-relative bounding box of navigated item
9085 if (g.NavId == id)
9086 {
9087 g.NavWindow = window; // Always refresh g.NavWindow, because some operations such as FocusItem() don't have a window.
9088 g.NavLayer = window->DC.NavLayerCurrent;
9089 g.NavFocusScopeId = window->DC.NavFocusScopeIdCurrent;
9090 g.NavIdIsAlive = true;
9091 g.NavIdTabCounter = window->DC.FocusCounterTabStop;
9092 window->NavRectRel[window->DC.NavLayerCurrent] = nav_bb_rel; // Store item bounding box (relative to window position)
9093 }
9094 }
9095
NavMoveRequestButNoResultYet()9096 bool ImGui::NavMoveRequestButNoResultYet()
9097 {
9098 ImGuiContext& g = *GImGui;
9099 return g.NavMoveRequest && g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0;
9100 }
9101
NavMoveRequestCancel()9102 void ImGui::NavMoveRequestCancel()
9103 {
9104 ImGuiContext& g = *GImGui;
9105 g.NavMoveRequest = false;
9106 NavUpdateAnyRequestFlag();
9107 }
9108
NavMoveRequestForward(ImGuiDir move_dir,ImGuiDir clip_dir,const ImRect & bb_rel,ImGuiNavMoveFlags move_flags)9109 void ImGui::NavMoveRequestForward(ImGuiDir move_dir, ImGuiDir clip_dir, const ImRect& bb_rel, ImGuiNavMoveFlags move_flags)
9110 {
9111 ImGuiContext& g = *GImGui;
9112 IM_ASSERT(g.NavMoveRequestForward == ImGuiNavForward_None);
9113 NavMoveRequestCancel();
9114 g.NavMoveDir = move_dir;
9115 g.NavMoveClipDir = clip_dir;
9116 g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued;
9117 g.NavMoveRequestFlags = move_flags;
9118 g.NavWindow->NavRectRel[g.NavLayer] = bb_rel;
9119 }
9120
NavMoveRequestTryWrapping(ImGuiWindow * window,ImGuiNavMoveFlags move_flags)9121 void ImGui::NavMoveRequestTryWrapping(ImGuiWindow* window, ImGuiNavMoveFlags move_flags)
9122 {
9123 ImGuiContext& g = *GImGui;
9124
9125 // Navigation wrap-around logic is delayed to the end of the frame because this operation is only valid after entire
9126 // popup is assembled and in case of appended popups it is not clear which EndPopup() call is final.
9127 g.NavWrapRequestWindow = window;
9128 g.NavWrapRequestFlags = move_flags;
9129 }
9130
9131 // FIXME: This could be replaced by updating a frame number in each window when (window == NavWindow) and (NavLayer == 0).
9132 // This way we could find the last focused window among our children. It would be much less confusing this way?
NavSaveLastChildNavWindowIntoParent(ImGuiWindow * nav_window)9133 static void ImGui::NavSaveLastChildNavWindowIntoParent(ImGuiWindow* nav_window)
9134 {
9135 ImGuiWindow* parent = nav_window;
9136 while (parent && parent->RootWindowDockStop != parent && (parent->Flags & ImGuiWindowFlags_ChildWindow) != 0 && (parent->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0)
9137 parent = parent->ParentWindow;
9138 if (parent && parent != nav_window)
9139 parent->NavLastChildNavWindow = nav_window;
9140 }
9141
9142 // Restore the last focused child.
9143 // Call when we are expected to land on the Main Layer (0) after FocusWindow()
NavRestoreLastChildNavWindow(ImGuiWindow * window)9144 static ImGuiWindow* ImGui::NavRestoreLastChildNavWindow(ImGuiWindow* window)
9145 {
9146 if (window->NavLastChildNavWindow && window->NavLastChildNavWindow->WasActive)
9147 return window->NavLastChildNavWindow;
9148 if (window->DockNodeAsHost && window->DockNodeAsHost->TabBar)
9149 if (ImGuiTabItem* tab = TabBarFindMostRecentlySelectedTabForActiveWindow(window->DockNodeAsHost->TabBar))
9150 return tab->Window;
9151 return window;
9152 }
9153
NavRestoreLayer(ImGuiNavLayer layer)9154 static void NavRestoreLayer(ImGuiNavLayer layer)
9155 {
9156 ImGuiContext& g = *GImGui;
9157 g.NavLayer = layer;
9158 if (layer == 0)
9159 g.NavWindow = ImGui::NavRestoreLastChildNavWindow(g.NavWindow);
9160 ImGuiWindow* window = g.NavWindow;
9161 if (window->NavLastIds[layer] != 0)
9162 ImGui::SetNavIDWithRectRel(window->NavLastIds[layer], layer, 0, g.NavWindow->NavRectRel[layer]);
9163 else
9164 ImGui::NavInitWindow(window, true);
9165 }
9166
NavUpdateAnyRequestFlag()9167 static inline void ImGui::NavUpdateAnyRequestFlag()
9168 {
9169 ImGuiContext& g = *GImGui;
9170 g.NavAnyRequest = g.NavMoveRequest || g.NavInitRequest || (IMGUI_DEBUG_NAV_SCORING && g.NavWindow != NULL);
9171 if (g.NavAnyRequest)
9172 IM_ASSERT(g.NavWindow != NULL);
9173 }
9174
9175 // This needs to be called before we submit any widget (aka in or before Begin)
NavInitWindow(ImGuiWindow * window,bool force_reinit)9176 void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit)
9177 {
9178 ImGuiContext& g = *GImGui;
9179 IM_ASSERT(window == g.NavWindow);
9180 bool init_for_nav = false;
9181 if (!(window->Flags & ImGuiWindowFlags_NoNavInputs))
9182 if (!(window->Flags & ImGuiWindowFlags_ChildWindow) || (window->Flags & ImGuiWindowFlags_Popup) || (window->NavLastIds[0] == 0) || force_reinit)
9183 init_for_nav = true;
9184 //IMGUI_DEBUG_LOG("[Nav] NavInitWindow() init_for_nav=%d, window=\"%s\", layer=%d\n", init_for_nav, window->Name, g.NavLayer);
9185 if (init_for_nav)
9186 {
9187 SetNavID(0, g.NavLayer, 0);
9188 g.NavInitRequest = true;
9189 g.NavInitRequestFromMove = false;
9190 g.NavInitResultId = 0;
9191 g.NavInitResultRectRel = ImRect();
9192 NavUpdateAnyRequestFlag();
9193 }
9194 else
9195 {
9196 g.NavId = window->NavLastIds[0];
9197 g.NavFocusScopeId = 0;
9198 }
9199 }
9200
NavCalcPreferredRefPos()9201 static ImVec2 ImGui::NavCalcPreferredRefPos()
9202 {
9203 ImGuiContext& g = *GImGui;
9204 if (g.NavDisableHighlight || !g.NavDisableMouseHover || !g.NavWindow)
9205 {
9206 // Mouse (we need a fallback in case the mouse becomes invalid after being used)
9207 if (IsMousePosValid(&g.IO.MousePos))
9208 return g.IO.MousePos;
9209 return g.LastValidMousePos;
9210 }
9211 else
9212 {
9213 // When navigation is active and mouse is disabled, decide on an arbitrary position around the bottom left of the currently navigated item.
9214 const ImRect& rect_rel = g.NavWindow->NavRectRel[g.NavLayer];
9215 ImVec2 pos = g.NavWindow->Pos + ImVec2(rect_rel.Min.x + ImMin(g.Style.FramePadding.x * 4, rect_rel.GetWidth()), rect_rel.Max.y - ImMin(g.Style.FramePadding.y, rect_rel.GetHeight()));
9216 ImRect visible_rect = g.NavWindow->Viewport->GetMainRect();
9217 return ImFloor(ImClamp(pos, visible_rect.Min, visible_rect.Max)); // ImFloor() is important because non-integer mouse position application in back-end might be lossy and result in undesirable non-zero delta.
9218 }
9219 }
9220
GetNavInputAmount(ImGuiNavInput n,ImGuiInputReadMode mode)9221 float ImGui::GetNavInputAmount(ImGuiNavInput n, ImGuiInputReadMode mode)
9222 {
9223 ImGuiContext& g = *GImGui;
9224 if (mode == ImGuiInputReadMode_Down)
9225 return g.IO.NavInputs[n]; // Instant, read analog input (0.0f..1.0f, as provided by user)
9226
9227 const float t = g.IO.NavInputsDownDuration[n];
9228 if (t < 0.0f && mode == ImGuiInputReadMode_Released) // Return 1.0f when just released, no repeat, ignore analog input.
9229 return (g.IO.NavInputsDownDurationPrev[n] >= 0.0f ? 1.0f : 0.0f);
9230 if (t < 0.0f)
9231 return 0.0f;
9232 if (mode == ImGuiInputReadMode_Pressed) // Return 1.0f when just pressed, no repeat, ignore analog input.
9233 return (t == 0.0f) ? 1.0f : 0.0f;
9234 if (mode == ImGuiInputReadMode_Repeat)
9235 return (float)CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, g.IO.KeyRepeatDelay * 0.72f, g.IO.KeyRepeatRate * 0.80f);
9236 if (mode == ImGuiInputReadMode_RepeatSlow)
9237 return (float)CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, g.IO.KeyRepeatDelay * 1.25f, g.IO.KeyRepeatRate * 2.00f);
9238 if (mode == ImGuiInputReadMode_RepeatFast)
9239 return (float)CalcTypematicRepeatAmount(t - g.IO.DeltaTime, t, g.IO.KeyRepeatDelay * 0.72f, g.IO.KeyRepeatRate * 0.30f);
9240 return 0.0f;
9241 }
9242
GetNavInputAmount2d(ImGuiNavDirSourceFlags dir_sources,ImGuiInputReadMode mode,float slow_factor,float fast_factor)9243 ImVec2 ImGui::GetNavInputAmount2d(ImGuiNavDirSourceFlags dir_sources, ImGuiInputReadMode mode, float slow_factor, float fast_factor)
9244 {
9245 ImVec2 delta(0.0f, 0.0f);
9246 if (dir_sources & ImGuiNavDirSourceFlags_Keyboard)
9247 delta += ImVec2(GetNavInputAmount(ImGuiNavInput_KeyRight_, mode) - GetNavInputAmount(ImGuiNavInput_KeyLeft_, mode), GetNavInputAmount(ImGuiNavInput_KeyDown_, mode) - GetNavInputAmount(ImGuiNavInput_KeyUp_, mode));
9248 if (dir_sources & ImGuiNavDirSourceFlags_PadDPad)
9249 delta += ImVec2(GetNavInputAmount(ImGuiNavInput_DpadRight, mode) - GetNavInputAmount(ImGuiNavInput_DpadLeft, mode), GetNavInputAmount(ImGuiNavInput_DpadDown, mode) - GetNavInputAmount(ImGuiNavInput_DpadUp, mode));
9250 if (dir_sources & ImGuiNavDirSourceFlags_PadLStick)
9251 delta += ImVec2(GetNavInputAmount(ImGuiNavInput_LStickRight, mode) - GetNavInputAmount(ImGuiNavInput_LStickLeft, mode), GetNavInputAmount(ImGuiNavInput_LStickDown, mode) - GetNavInputAmount(ImGuiNavInput_LStickUp, mode));
9252 if (slow_factor != 0.0f && IsNavInputDown(ImGuiNavInput_TweakSlow))
9253 delta *= slow_factor;
9254 if (fast_factor != 0.0f && IsNavInputDown(ImGuiNavInput_TweakFast))
9255 delta *= fast_factor;
9256 return delta;
9257 }
9258
NavUpdate()9259 static void ImGui::NavUpdate()
9260 {
9261 ImGuiContext& g = *GImGui;
9262 g.IO.WantSetMousePos = false;
9263 g.NavWrapRequestWindow = NULL;
9264 g.NavWrapRequestFlags = ImGuiNavMoveFlags_None;
9265 #if 0
9266 if (g.NavScoringCount > 0) IMGUI_DEBUG_LOG("NavScoringCount %d for '%s' layer %d (Init:%d, Move:%d)\n", g.FrameCount, g.NavScoringCount, g.NavWindow ? g.NavWindow->Name : "NULL", g.NavLayer, g.NavInitRequest || g.NavInitResultId != 0, g.NavMoveRequest);
9267 #endif
9268
9269 // Set input source as Gamepad when buttons are pressed (as some features differs when used with Gamepad vs Keyboard)
9270 // (do it before we map Keyboard input!)
9271 bool nav_keyboard_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0;
9272 bool nav_gamepad_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (g.IO.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
9273 if (nav_gamepad_active)
9274 if (g.IO.NavInputs[ImGuiNavInput_Activate] > 0.0f || g.IO.NavInputs[ImGuiNavInput_Input] > 0.0f || g.IO.NavInputs[ImGuiNavInput_Cancel] > 0.0f || g.IO.NavInputs[ImGuiNavInput_Menu] > 0.0f)
9275 g.NavInputSource = ImGuiInputSource_NavGamepad;
9276
9277 // Update Keyboard->Nav inputs mapping
9278 if (nav_keyboard_active)
9279 {
9280 #define NAV_MAP_KEY(_KEY, _NAV_INPUT) do { if (IsKeyDown(g.IO.KeyMap[_KEY])) { g.IO.NavInputs[_NAV_INPUT] = 1.0f; g.NavInputSource = ImGuiInputSource_NavKeyboard; } } while (0)
9281 NAV_MAP_KEY(ImGuiKey_Space, ImGuiNavInput_Activate );
9282 NAV_MAP_KEY(ImGuiKey_Enter, ImGuiNavInput_Input );
9283 NAV_MAP_KEY(ImGuiKey_Escape, ImGuiNavInput_Cancel );
9284 NAV_MAP_KEY(ImGuiKey_LeftArrow, ImGuiNavInput_KeyLeft_ );
9285 NAV_MAP_KEY(ImGuiKey_RightArrow,ImGuiNavInput_KeyRight_);
9286 NAV_MAP_KEY(ImGuiKey_UpArrow, ImGuiNavInput_KeyUp_ );
9287 NAV_MAP_KEY(ImGuiKey_DownArrow, ImGuiNavInput_KeyDown_ );
9288 if (g.IO.KeyCtrl)
9289 g.IO.NavInputs[ImGuiNavInput_TweakSlow] = 1.0f;
9290 if (g.IO.KeyShift)
9291 g.IO.NavInputs[ImGuiNavInput_TweakFast] = 1.0f;
9292 if (g.IO.KeyAlt && !g.IO.KeyCtrl) // AltGR is Alt+Ctrl, also even on keyboards without AltGR we don't want Alt+Ctrl to open menu.
9293 g.IO.NavInputs[ImGuiNavInput_KeyMenu_] = 1.0f;
9294 #undef NAV_MAP_KEY
9295 }
9296 memcpy(g.IO.NavInputsDownDurationPrev, g.IO.NavInputsDownDuration, sizeof(g.IO.NavInputsDownDuration));
9297 for (int i = 0; i < IM_ARRAYSIZE(g.IO.NavInputs); i++)
9298 g.IO.NavInputsDownDuration[i] = (g.IO.NavInputs[i] > 0.0f) ? (g.IO.NavInputsDownDuration[i] < 0.0f ? 0.0f : g.IO.NavInputsDownDuration[i] + g.IO.DeltaTime) : -1.0f;
9299
9300 // Process navigation init request (select first/default focus)
9301 // 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)
9302 if (g.NavInitResultId != 0 && (!g.NavDisableHighlight || g.NavInitRequestFromMove) && g.NavWindow)
9303 {
9304 // Apply result from previous navigation init request (will typically select the first item, unless SetItemDefaultFocus() has been called)
9305 //IMGUI_DEBUG_LOG("[Nav] Apply NavInitRequest result: 0x%08X Layer %d in \"%s\"\n", g.NavInitResultId, g.NavLayer, g.NavWindow->Name);
9306 if (g.NavInitRequestFromMove)
9307 SetNavIDWithRectRel(g.NavInitResultId, g.NavLayer, 0, g.NavInitResultRectRel);
9308 else
9309 SetNavID(g.NavInitResultId, g.NavLayer, 0);
9310 g.NavWindow->NavRectRel[g.NavLayer] = g.NavInitResultRectRel;
9311 }
9312 g.NavInitRequest = false;
9313 g.NavInitRequestFromMove = false;
9314 g.NavInitResultId = 0;
9315 g.NavJustMovedToId = 0;
9316
9317 // Process navigation move request
9318 if (g.NavMoveRequest)
9319 NavUpdateMoveResult();
9320
9321 // When a forwarded move request failed, we restore the highlight that we disabled during the forward frame
9322 if (g.NavMoveRequestForward == ImGuiNavForward_ForwardActive)
9323 {
9324 IM_ASSERT(g.NavMoveRequest);
9325 if (g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0)
9326 g.NavDisableHighlight = false;
9327 g.NavMoveRequestForward = ImGuiNavForward_None;
9328 }
9329
9330 // Apply application mouse position movement, after we had a chance to process move request result.
9331 if (g.NavMousePosDirty && g.NavIdIsAlive)
9332 {
9333 // Set mouse position given our knowledge of the navigated item position from last frame
9334 if ((g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos) && (g.IO.BackendFlags & ImGuiBackendFlags_HasSetMousePos))
9335 {
9336 if (!g.NavDisableHighlight && g.NavDisableMouseHover && g.NavWindow)
9337 {
9338 g.IO.MousePos = g.IO.MousePosPrev = NavCalcPreferredRefPos();
9339 g.IO.WantSetMousePos = true;
9340 }
9341 }
9342 g.NavMousePosDirty = false;
9343 }
9344 g.NavIdIsAlive = false;
9345 g.NavJustTabbedId = 0;
9346 IM_ASSERT(g.NavLayer == 0 || g.NavLayer == 1);
9347
9348 // Store our return window (for returning from Layer 1 to Layer 0) and clear it as soon as we step back in our own Layer 0
9349 if (g.NavWindow)
9350 NavSaveLastChildNavWindowIntoParent(g.NavWindow);
9351 if (g.NavWindow && g.NavWindow->NavLastChildNavWindow != NULL && g.NavLayer == ImGuiNavLayer_Main)
9352 g.NavWindow->NavLastChildNavWindow = NULL;
9353
9354 // Update CTRL+TAB and Windowing features (hold Square to move/resize/etc.)
9355 NavUpdateWindowing();
9356
9357 // Set output flags for user application
9358 g.IO.NavActive = (nav_keyboard_active || nav_gamepad_active) && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs);
9359 g.IO.NavVisible = (g.IO.NavActive && g.NavId != 0 && !g.NavDisableHighlight) || (g.NavWindowingTarget != NULL);
9360
9361 // Process NavCancel input (to close a popup, get back to parent, clear focus)
9362 if (IsNavInputTest(ImGuiNavInput_Cancel, ImGuiInputReadMode_Pressed))
9363 {
9364 if (g.ActiveId != 0)
9365 {
9366 if (!IsActiveIdUsingNavInput(ImGuiNavInput_Cancel))
9367 ClearActiveID();
9368 }
9369 else if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow) && !(g.NavWindow->Flags & ImGuiWindowFlags_Popup) && g.NavWindow->ParentWindow && g.NavWindow != g.NavWindow->RootWindowDockStop)
9370 {
9371 // Exit child window
9372 ImGuiWindow* child_window = g.NavWindow;
9373 ImGuiWindow* parent_window = g.NavWindow->ParentWindow;
9374 IM_ASSERT(child_window->ChildId != 0);
9375 FocusWindow(parent_window);
9376 SetNavID(child_window->ChildId, 0, 0);
9377 // Reassigning with same value, we're being explicit here.
9378 g.NavIdIsAlive = false; // -V1048
9379 if (g.NavDisableMouseHover)
9380 g.NavMousePosDirty = true;
9381 }
9382 else if (g.OpenPopupStack.Size > 0)
9383 {
9384 // Close open popup/menu
9385 if (!(g.OpenPopupStack.back().Window->Flags & ImGuiWindowFlags_Modal))
9386 ClosePopupToLevel(g.OpenPopupStack.Size - 1, true);
9387 }
9388 else if (g.NavLayer != ImGuiNavLayer_Main)
9389 {
9390 // Leave the "menu" layer
9391 NavRestoreLayer(ImGuiNavLayer_Main);
9392 }
9393 else
9394 {
9395 // Clear NavLastId for popups but keep it for regular child window so we can leave one and come back where we were
9396 if (g.NavWindow && ((g.NavWindow->Flags & ImGuiWindowFlags_Popup) || !(g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow)))
9397 g.NavWindow->NavLastIds[0] = 0;
9398 g.NavId = g.NavFocusScopeId = 0;
9399 }
9400 }
9401
9402 // Process manual activation request
9403 g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavInputId = 0;
9404 if (g.NavId != 0 && !g.NavDisableHighlight && !g.NavWindowingTarget && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))
9405 {
9406 bool activate_down = IsNavInputDown(ImGuiNavInput_Activate);
9407 bool activate_pressed = activate_down && IsNavInputTest(ImGuiNavInput_Activate, ImGuiInputReadMode_Pressed);
9408 if (g.ActiveId == 0 && activate_pressed)
9409 g.NavActivateId = g.NavId;
9410 if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_down)
9411 g.NavActivateDownId = g.NavId;
9412 if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_pressed)
9413 g.NavActivatePressedId = g.NavId;
9414 if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && IsNavInputTest(ImGuiNavInput_Input, ImGuiInputReadMode_Pressed))
9415 g.NavInputId = g.NavId;
9416 }
9417 if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))
9418 g.NavDisableHighlight = true;
9419 if (g.NavActivateId != 0)
9420 IM_ASSERT(g.NavActivateDownId == g.NavActivateId);
9421 g.NavMoveRequest = false;
9422
9423 // Process programmatic activation request
9424 if (g.NavNextActivateId != 0)
9425 g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavInputId = g.NavNextActivateId;
9426 g.NavNextActivateId = 0;
9427
9428 // Initiate directional inputs request
9429 if (g.NavMoveRequestForward == ImGuiNavForward_None)
9430 {
9431 g.NavMoveDir = ImGuiDir_None;
9432 g.NavMoveRequestFlags = ImGuiNavMoveFlags_None;
9433 if (g.NavWindow && !g.NavWindowingTarget && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))
9434 {
9435 const ImGuiInputReadMode read_mode = ImGuiInputReadMode_Repeat;
9436 if (!IsActiveIdUsingNavDir(ImGuiDir_Left) && (IsNavInputTest(ImGuiNavInput_DpadLeft, read_mode) || IsNavInputTest(ImGuiNavInput_KeyLeft_, read_mode))) { g.NavMoveDir = ImGuiDir_Left; }
9437 if (!IsActiveIdUsingNavDir(ImGuiDir_Right) && (IsNavInputTest(ImGuiNavInput_DpadRight, read_mode) || IsNavInputTest(ImGuiNavInput_KeyRight_, read_mode))) { g.NavMoveDir = ImGuiDir_Right; }
9438 if (!IsActiveIdUsingNavDir(ImGuiDir_Up) && (IsNavInputTest(ImGuiNavInput_DpadUp, read_mode) || IsNavInputTest(ImGuiNavInput_KeyUp_, read_mode))) { g.NavMoveDir = ImGuiDir_Up; }
9439 if (!IsActiveIdUsingNavDir(ImGuiDir_Down) && (IsNavInputTest(ImGuiNavInput_DpadDown, read_mode) || IsNavInputTest(ImGuiNavInput_KeyDown_, read_mode))) { g.NavMoveDir = ImGuiDir_Down; }
9440 }
9441 g.NavMoveClipDir = g.NavMoveDir;
9442 }
9443 else
9444 {
9445 // 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)
9446 // (Preserve g.NavMoveRequestFlags, g.NavMoveClipDir which were set by the NavMoveRequestForward() function)
9447 IM_ASSERT(g.NavMoveDir != ImGuiDir_None && g.NavMoveClipDir != ImGuiDir_None);
9448 IM_ASSERT(g.NavMoveRequestForward == ImGuiNavForward_ForwardQueued);
9449 g.NavMoveRequestForward = ImGuiNavForward_ForwardActive;
9450 }
9451
9452 // Update PageUp/PageDown/Home/End scroll
9453 // FIXME-NAV: Consider enabling those keys even without the master ImGuiConfigFlags_NavEnableKeyboard flag?
9454 float nav_scoring_rect_offset_y = 0.0f;
9455 if (nav_keyboard_active)
9456 nav_scoring_rect_offset_y = NavUpdatePageUpPageDown();
9457
9458 // If we initiate a movement request and have no current NavId, we initiate a InitDefautRequest that will be used as a fallback if the direction fails to find a match
9459 if (g.NavMoveDir != ImGuiDir_None)
9460 {
9461 g.NavMoveRequest = true;
9462 g.NavMoveRequestKeyMods = g.IO.KeyMods;
9463 g.NavMoveDirLast = g.NavMoveDir;
9464 }
9465 if (g.NavMoveRequest && g.NavId == 0)
9466 {
9467 //IMGUI_DEBUG_LOG("[Nav] NavInitRequest from move, window \"%s\", layer=%d\n", g.NavWindow->Name, g.NavLayer);
9468 g.NavInitRequest = g.NavInitRequestFromMove = true;
9469 // Reassigning with same value, we're being explicit here.
9470 g.NavInitResultId = 0; // -V1048
9471 g.NavDisableHighlight = false;
9472 }
9473 NavUpdateAnyRequestFlag();
9474
9475 // Scrolling
9476 if (g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.NavWindowingTarget)
9477 {
9478 // *Fallback* manual-scroll with Nav directional keys when window has no navigable item
9479 ImGuiWindow* window = g.NavWindow;
9480 const float scroll_speed = IM_ROUND(window->CalcFontSize() * 100 * g.IO.DeltaTime); // We need round the scrolling speed because sub-pixel scroll isn't reliably supported.
9481 if (window->DC.NavLayerActiveMask == 0x00 && window->DC.NavHasScroll && g.NavMoveRequest)
9482 {
9483 if (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right)
9484 SetScrollX(window, ImFloor(window->Scroll.x + ((g.NavMoveDir == ImGuiDir_Left) ? -1.0f : +1.0f) * scroll_speed));
9485 if (g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down)
9486 SetScrollY(window, ImFloor(window->Scroll.y + ((g.NavMoveDir == ImGuiDir_Up) ? -1.0f : +1.0f) * scroll_speed));
9487 }
9488
9489 // *Normal* Manual scroll with NavScrollXXX keys
9490 // Next movement request will clamp the NavId reference rectangle to the visible area, so navigation will resume within those bounds.
9491 ImVec2 scroll_dir = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadLStick, ImGuiInputReadMode_Down, 1.0f / 10.0f, 10.0f);
9492 if (scroll_dir.x != 0.0f && window->ScrollbarX)
9493 {
9494 SetScrollX(window, ImFloor(window->Scroll.x + scroll_dir.x * scroll_speed));
9495 g.NavMoveFromClampedRefRect = true;
9496 }
9497 if (scroll_dir.y != 0.0f)
9498 {
9499 SetScrollY(window, ImFloor(window->Scroll.y + scroll_dir.y * scroll_speed));
9500 g.NavMoveFromClampedRefRect = true;
9501 }
9502 }
9503
9504 // Reset search results
9505 g.NavMoveResultLocal.Clear();
9506 g.NavMoveResultLocalVisibleSet.Clear();
9507 g.NavMoveResultOther.Clear();
9508
9509 // When we have manually scrolled (without using navigation) and NavId becomes out of bounds, we project its bounding box to the visible area to restart navigation within visible items
9510 if (g.NavMoveRequest && g.NavMoveFromClampedRefRect && g.NavLayer == ImGuiNavLayer_Main)
9511 {
9512 ImGuiWindow* window = g.NavWindow;
9513 ImRect window_rect_rel(window->InnerRect.Min - window->Pos - ImVec2(1, 1), window->InnerRect.Max - window->Pos + ImVec2(1, 1));
9514 if (!window_rect_rel.Contains(window->NavRectRel[g.NavLayer]))
9515 {
9516 float pad = window->CalcFontSize() * 0.5f;
9517 window_rect_rel.Expand(ImVec2(-ImMin(window_rect_rel.GetWidth(), pad), -ImMin(window_rect_rel.GetHeight(), pad))); // Terrible approximation for the intent of starting navigation from first fully visible item
9518 window->NavRectRel[g.NavLayer].ClipWith(window_rect_rel);
9519 g.NavId = g.NavFocusScopeId = 0;
9520 }
9521 g.NavMoveFromClampedRefRect = false;
9522 }
9523
9524 // 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)
9525 ImRect nav_rect_rel = (g.NavWindow && !g.NavWindow->NavRectRel[g.NavLayer].IsInverted()) ? g.NavWindow->NavRectRel[g.NavLayer] : ImRect(0, 0, 0, 0);
9526 g.NavScoringRect = g.NavWindow ? ImRect(g.NavWindow->Pos + nav_rect_rel.Min, g.NavWindow->Pos + nav_rect_rel.Max) : ImRect(0, 0, 0, 0);
9527 g.NavScoringRect.TranslateY(nav_scoring_rect_offset_y);
9528 g.NavScoringRect.Min.x = ImMin(g.NavScoringRect.Min.x + 1.0f, g.NavScoringRect.Max.x);
9529 g.NavScoringRect.Max.x = g.NavScoringRect.Min.x;
9530 IM_ASSERT(!g.NavScoringRect.IsInverted()); // Ensure if we have a finite, non-inverted bounding box here will allows us to remove extraneous ImFabs() calls in NavScoreItem().
9531 //GetForegroundDrawList()->AddRect(g.NavScoringRectScreen.Min, g.NavScoringRectScreen.Max, IM_COL32(255,200,0,255)); // [DEBUG]
9532 g.NavScoringCount = 0;
9533 #if IMGUI_DEBUG_NAV_RECTS
9534 if (g.NavWindow)
9535 {
9536 ImDrawList* draw_list = GetForegroundDrawList(g.NavWindow);
9537 if (1) { for (int layer = 0; layer < 2; layer++) draw_list->AddRect(g.NavWindow->Pos + g.NavWindow->NavRectRel[layer].Min, g.NavWindow->Pos + g.NavWindow->NavRectRel[layer].Max, IM_COL32(255,200,0,255)); } // [DEBUG]
9538 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); }
9539 }
9540 #endif
9541 }
9542
9543 // Apply result from previous frame navigation directional move request
NavUpdateMoveResult()9544 static void ImGui::NavUpdateMoveResult()
9545 {
9546 ImGuiContext& g = *GImGui;
9547 if (g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0)
9548 {
9549 // In a situation when there is no results but NavId != 0, re-enable the Navigation highlight (because g.NavId is not considered as a possible result)
9550 if (g.NavId != 0)
9551 {
9552 g.NavDisableHighlight = false;
9553 g.NavDisableMouseHover = true;
9554 }
9555 return;
9556 }
9557
9558 // Select which result to use
9559 ImGuiNavMoveResult* result = (g.NavMoveResultLocal.ID != 0) ? &g.NavMoveResultLocal : &g.NavMoveResultOther;
9560
9561 // PageUp/PageDown behavior first jumps to the bottom/top mostly visible item, _otherwise_ use the result from the previous/next page.
9562 if (g.NavMoveRequestFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet)
9563 if (g.NavMoveResultLocalVisibleSet.ID != 0 && g.NavMoveResultLocalVisibleSet.ID != g.NavId)
9564 result = &g.NavMoveResultLocalVisibleSet;
9565
9566 // Maybe entering a flattened child from the outside? In this case solve the tie using the regular scoring rules.
9567 if (result != &g.NavMoveResultOther && g.NavMoveResultOther.ID != 0 && g.NavMoveResultOther.Window->ParentWindow == g.NavWindow)
9568 if ((g.NavMoveResultOther.DistBox < result->DistBox) || (g.NavMoveResultOther.DistBox == result->DistBox && g.NavMoveResultOther.DistCenter < result->DistCenter))
9569 result = &g.NavMoveResultOther;
9570 IM_ASSERT(g.NavWindow && result->Window);
9571
9572 // Scroll to keep newly navigated item fully into view.
9573 if (g.NavLayer == ImGuiNavLayer_Main)
9574 {
9575 ImVec2 delta_scroll;
9576 if (g.NavMoveRequestFlags & ImGuiNavMoveFlags_ScrollToEdge)
9577 {
9578 float scroll_target = (g.NavMoveDir == ImGuiDir_Up) ? result->Window->ScrollMax.y : 0.0f;
9579 delta_scroll.y = result->Window->Scroll.y - scroll_target;
9580 SetScrollY(result->Window, scroll_target);
9581 }
9582 else
9583 {
9584 ImRect rect_abs = ImRect(result->RectRel.Min + result->Window->Pos, result->RectRel.Max + result->Window->Pos);
9585 delta_scroll = ScrollToBringRectIntoView(result->Window, rect_abs);
9586 }
9587
9588 // Offset our result position so mouse position can be applied immediately after in NavUpdate()
9589 result->RectRel.TranslateX(-delta_scroll.x);
9590 result->RectRel.TranslateY(-delta_scroll.y);
9591 }
9592
9593 ClearActiveID();
9594 g.NavWindow = result->Window;
9595 if (g.NavId != result->ID)
9596 {
9597 // Don't set NavJustMovedToId if just landed on the same spot (which may happen with ImGuiNavMoveFlags_AllowCurrentNavId)
9598 g.NavJustMovedToId = result->ID;
9599 g.NavJustMovedToFocusScopeId = result->FocusScopeId;
9600 g.NavJustMovedToKeyMods = g.NavMoveRequestKeyMods;
9601 }
9602 SetNavIDWithRectRel(result->ID, g.NavLayer, result->FocusScopeId, result->RectRel);
9603 g.NavMoveFromClampedRefRect = false;
9604 }
9605
9606 // Handle PageUp/PageDown/Home/End keys
NavUpdatePageUpPageDown()9607 static float ImGui::NavUpdatePageUpPageDown()
9608 {
9609 ImGuiContext& g = *GImGui;
9610 if (g.NavMoveDir != ImGuiDir_None || g.NavWindow == NULL)
9611 return 0.0f;
9612 if ((g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) || g.NavWindowingTarget != NULL || g.NavLayer != ImGuiNavLayer_Main)
9613 return 0.0f;
9614
9615 ImGuiWindow* window = g.NavWindow;
9616 const bool page_up_held = IsKeyDown(g.IO.KeyMap[ImGuiKey_PageUp]) && !IsActiveIdUsingKey(ImGuiKey_PageUp);
9617 const bool page_down_held = IsKeyDown(g.IO.KeyMap[ImGuiKey_PageDown]) && !IsActiveIdUsingKey(ImGuiKey_PageDown);
9618 const bool home_pressed = IsKeyPressed(g.IO.KeyMap[ImGuiKey_Home]) && !IsActiveIdUsingKey(ImGuiKey_Home);
9619 const bool end_pressed = IsKeyPressed(g.IO.KeyMap[ImGuiKey_End]) && !IsActiveIdUsingKey(ImGuiKey_End);
9620 if (page_up_held != page_down_held || home_pressed != end_pressed) // If either (not both) are pressed
9621 {
9622 if (window->DC.NavLayerActiveMask == 0x00 && window->DC.NavHasScroll)
9623 {
9624 // Fallback manual-scroll when window has no navigable item
9625 if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageUp], true))
9626 SetScrollY(window, window->Scroll.y - window->InnerRect.GetHeight());
9627 else if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageDown], true))
9628 SetScrollY(window, window->Scroll.y + window->InnerRect.GetHeight());
9629 else if (home_pressed)
9630 SetScrollY(window, 0.0f);
9631 else if (end_pressed)
9632 SetScrollY(window, window->ScrollMax.y);
9633 }
9634 else
9635 {
9636 ImRect& nav_rect_rel = window->NavRectRel[g.NavLayer];
9637 const float page_offset_y = ImMax(0.0f, window->InnerRect.GetHeight() - window->CalcFontSize() * 1.0f + nav_rect_rel.GetHeight());
9638 float nav_scoring_rect_offset_y = 0.0f;
9639 if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageUp], true))
9640 {
9641 nav_scoring_rect_offset_y = -page_offset_y;
9642 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)
9643 g.NavMoveClipDir = ImGuiDir_Up;
9644 g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet;
9645 }
9646 else if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageDown], true))
9647 {
9648 nav_scoring_rect_offset_y = +page_offset_y;
9649 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)
9650 g.NavMoveClipDir = ImGuiDir_Down;
9651 g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet;
9652 }
9653 else if (home_pressed)
9654 {
9655 // FIXME-NAV: handling of Home/End is assuming that the top/bottom most item will be visible with Scroll.y == 0/ScrollMax.y
9656 // Scrolling will be handled via the ImGuiNavMoveFlags_ScrollToEdge flag, we don't scroll immediately to avoid scrolling happening before nav result.
9657 // Preserve current horizontal position if we have any.
9658 nav_rect_rel.Min.y = nav_rect_rel.Max.y = -window->Scroll.y;
9659 if (nav_rect_rel.IsInverted())
9660 nav_rect_rel.Min.x = nav_rect_rel.Max.x = 0.0f;
9661 g.NavMoveDir = ImGuiDir_Down;
9662 g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_ScrollToEdge;
9663 }
9664 else if (end_pressed)
9665 {
9666 nav_rect_rel.Min.y = nav_rect_rel.Max.y = window->ScrollMax.y + window->SizeFull.y - window->Scroll.y;
9667 if (nav_rect_rel.IsInverted())
9668 nav_rect_rel.Min.x = nav_rect_rel.Max.x = 0.0f;
9669 g.NavMoveDir = ImGuiDir_Up;
9670 g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_ScrollToEdge;
9671 }
9672 return nav_scoring_rect_offset_y;
9673 }
9674 }
9675 return 0.0f;
9676 }
9677
NavEndFrame()9678 static void ImGui::NavEndFrame()
9679 {
9680 ImGuiContext& g = *GImGui;
9681
9682 // Show CTRL+TAB list window
9683 if (g.NavWindowingTarget != NULL)
9684 NavUpdateWindowingOverlay();
9685
9686 // Perform wrap-around in menus
9687 ImGuiWindow* window = g.NavWrapRequestWindow;
9688 ImGuiNavMoveFlags move_flags = g.NavWrapRequestFlags;
9689 if (window != NULL && g.NavWindow == window && NavMoveRequestButNoResultYet() && g.NavMoveRequestForward == ImGuiNavForward_None && g.NavLayer == ImGuiNavLayer_Main)
9690 {
9691 IM_ASSERT(move_flags != 0); // No points calling this with no wrapping
9692 ImRect bb_rel = window->NavRectRel[0];
9693
9694 ImGuiDir clip_dir = g.NavMoveDir;
9695 if (g.NavMoveDir == ImGuiDir_Left && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX)))
9696 {
9697 bb_rel.Min.x = bb_rel.Max.x =
9698 ImMax(window->SizeFull.x, window->ContentSize.x + window->WindowPadding.x * 2.0f) - window->Scroll.x;
9699 if (move_flags & ImGuiNavMoveFlags_WrapX)
9700 {
9701 bb_rel.TranslateY(-bb_rel.GetHeight());
9702 clip_dir = ImGuiDir_Up;
9703 }
9704 NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags);
9705 }
9706 if (g.NavMoveDir == ImGuiDir_Right && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX)))
9707 {
9708 bb_rel.Min.x = bb_rel.Max.x = -window->Scroll.x;
9709 if (move_flags & ImGuiNavMoveFlags_WrapX)
9710 {
9711 bb_rel.TranslateY(+bb_rel.GetHeight());
9712 clip_dir = ImGuiDir_Down;
9713 }
9714 NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags);
9715 }
9716 if (g.NavMoveDir == ImGuiDir_Up && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY)))
9717 {
9718 bb_rel.Min.y = bb_rel.Max.y =
9719 ImMax(window->SizeFull.y, window->ContentSize.y + window->WindowPadding.y * 2.0f) - window->Scroll.y;
9720 if (move_flags & ImGuiNavMoveFlags_WrapY)
9721 {
9722 bb_rel.TranslateX(-bb_rel.GetWidth());
9723 clip_dir = ImGuiDir_Left;
9724 }
9725 NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags);
9726 }
9727 if (g.NavMoveDir == ImGuiDir_Down && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY)))
9728 {
9729 bb_rel.Min.y = bb_rel.Max.y = -window->Scroll.y;
9730 if (move_flags & ImGuiNavMoveFlags_WrapY)
9731 {
9732 bb_rel.TranslateX(+bb_rel.GetWidth());
9733 clip_dir = ImGuiDir_Right;
9734 }
9735 NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags);
9736 }
9737 }
9738 }
9739
FindWindowFocusIndex(ImGuiWindow * window)9740 static int ImGui::FindWindowFocusIndex(ImGuiWindow* window) // FIXME-OPT O(N)
9741 {
9742 ImGuiContext& g = *GImGui;
9743 for (int i = g.WindowsFocusOrder.Size - 1; i >= 0; i--)
9744 if (g.WindowsFocusOrder[i] == window)
9745 return i;
9746 return -1;
9747 }
9748
FindWindowNavFocusable(int i_start,int i_stop,int dir)9749 static ImGuiWindow* FindWindowNavFocusable(int i_start, int i_stop, int dir) // FIXME-OPT O(N)
9750 {
9751 ImGuiContext& g = *GImGui;
9752 for (int i = i_start; i >= 0 && i < g.WindowsFocusOrder.Size && i != i_stop; i += dir)
9753 if (ImGui::IsWindowNavFocusable(g.WindowsFocusOrder[i]))
9754 return g.WindowsFocusOrder[i];
9755 return NULL;
9756 }
9757
NavUpdateWindowingHighlightWindow(int focus_change_dir)9758 static void NavUpdateWindowingHighlightWindow(int focus_change_dir)
9759 {
9760 ImGuiContext& g = *GImGui;
9761 IM_ASSERT(g.NavWindowingTarget);
9762 if (g.NavWindowingTarget->Flags & ImGuiWindowFlags_Modal)
9763 return;
9764
9765 const int i_current = ImGui::FindWindowFocusIndex(g.NavWindowingTarget);
9766 ImGuiWindow* window_target = FindWindowNavFocusable(i_current + focus_change_dir, -INT_MAX, focus_change_dir);
9767 if (!window_target)
9768 window_target = FindWindowNavFocusable((focus_change_dir < 0) ? (g.WindowsFocusOrder.Size - 1) : 0, i_current, focus_change_dir);
9769 if (window_target) // Don't reset windowing target if there's a single window in the list
9770 g.NavWindowingTarget = g.NavWindowingTargetAnim = window_target;
9771 g.NavWindowingToggleLayer = false;
9772 }
9773
9774 // Windowing management mode
9775 // Keyboard: CTRL+Tab (change focus/move/resize), Alt (toggle menu layer)
9776 // Gamepad: Hold Menu/Square (change focus/move/resize), Tap Menu/Square (toggle menu layer)
NavUpdateWindowing()9777 static void ImGui::NavUpdateWindowing()
9778 {
9779 ImGuiContext& g = *GImGui;
9780 ImGuiWindow* apply_focus_window = NULL;
9781 bool apply_toggle_layer = false;
9782
9783 ImGuiWindow* modal_window = GetTopMostPopupModal();
9784 if (modal_window != NULL)
9785 {
9786 g.NavWindowingTarget = NULL;
9787 return;
9788 }
9789
9790 // Fade out
9791 if (g.NavWindowingTargetAnim && g.NavWindowingTarget == NULL)
9792 {
9793 g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha - g.IO.DeltaTime * 10.0f, 0.0f);
9794 if (g.DimBgRatio <= 0.0f && g.NavWindowingHighlightAlpha <= 0.0f)
9795 g.NavWindowingTargetAnim = NULL;
9796 }
9797
9798 // Start CTRL-TAB or Square+L/R window selection
9799 bool start_windowing_with_gamepad = !g.NavWindowingTarget && IsNavInputTest(ImGuiNavInput_Menu, ImGuiInputReadMode_Pressed);
9800 bool start_windowing_with_keyboard = !g.NavWindowingTarget && g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab) && (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard);
9801 if (start_windowing_with_gamepad || start_windowing_with_keyboard)
9802 if (ImGuiWindow* window = g.NavWindow ? g.NavWindow : FindWindowNavFocusable(g.WindowsFocusOrder.Size - 1, -INT_MAX, -1))
9803 {
9804 g.NavWindowingTarget = g.NavWindowingTargetAnim = window->RootWindowDockStop;
9805 g.NavWindowingTimer = g.NavWindowingHighlightAlpha = 0.0f;
9806 g.NavWindowingToggleLayer = start_windowing_with_keyboard ? false : true;
9807 g.NavInputSource = start_windowing_with_keyboard ? ImGuiInputSource_NavKeyboard : ImGuiInputSource_NavGamepad;
9808 }
9809
9810 // Gamepad update
9811 g.NavWindowingTimer += g.IO.DeltaTime;
9812 if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_NavGamepad)
9813 {
9814 // 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
9815 g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f));
9816
9817 // Select window to focus
9818 const int focus_change_dir = (int)IsNavInputTest(ImGuiNavInput_FocusPrev, ImGuiInputReadMode_RepeatSlow) - (int)IsNavInputTest(ImGuiNavInput_FocusNext, ImGuiInputReadMode_RepeatSlow);
9819 if (focus_change_dir != 0)
9820 {
9821 NavUpdateWindowingHighlightWindow(focus_change_dir);
9822 g.NavWindowingHighlightAlpha = 1.0f;
9823 }
9824
9825 // Single press toggles NavLayer, long press with L/R apply actual focus on release (until then the window was merely rendered top-most)
9826 if (!IsNavInputDown(ImGuiNavInput_Menu))
9827 {
9828 g.NavWindowingToggleLayer &= (g.NavWindowingHighlightAlpha < 1.0f); // Once button was held long enough we don't consider it a tap-to-toggle-layer press anymore.
9829 if (g.NavWindowingToggleLayer && g.NavWindow)
9830 apply_toggle_layer = true;
9831 else if (!g.NavWindowingToggleLayer)
9832 apply_focus_window = g.NavWindowingTarget;
9833 g.NavWindowingTarget = NULL;
9834 }
9835 }
9836
9837 // Keyboard: Focus
9838 if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_NavKeyboard)
9839 {
9840 // Visuals only appears after a brief time after pressing TAB the first time, so that a fast CTRL+TAB doesn't add visual noise
9841 g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f)); // 1.0f
9842 if (IsKeyPressedMap(ImGuiKey_Tab, true))
9843 NavUpdateWindowingHighlightWindow(g.IO.KeyShift ? +1 : -1);
9844 if (!g.IO.KeyCtrl)
9845 apply_focus_window = g.NavWindowingTarget;
9846 }
9847
9848 // Keyboard: Press and Release ALT to toggle menu layer
9849 // FIXME: We lack an explicit IO variable for "is the imgui window focused", so compare mouse validity to detect the common case of back-end clearing releases all keys on ALT-TAB
9850 if (IsNavInputTest(ImGuiNavInput_KeyMenu_, ImGuiInputReadMode_Pressed))
9851 g.NavWindowingToggleLayer = true;
9852 if ((g.ActiveId == 0 || g.ActiveIdAllowOverlap) && g.NavWindowingToggleLayer && IsNavInputTest(ImGuiNavInput_KeyMenu_, ImGuiInputReadMode_Released))
9853 if (IsMousePosValid(&g.IO.MousePos) == IsMousePosValid(&g.IO.MousePosPrev))
9854 apply_toggle_layer = true;
9855
9856 // Move window
9857 if (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoMove))
9858 {
9859 ImVec2 move_delta;
9860 if (g.NavInputSource == ImGuiInputSource_NavKeyboard && !g.IO.KeyShift)
9861 move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard, ImGuiInputReadMode_Down);
9862 if (g.NavInputSource == ImGuiInputSource_NavGamepad)
9863 move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadLStick, ImGuiInputReadMode_Down);
9864 if (move_delta.x != 0.0f || move_delta.y != 0.0f)
9865 {
9866 const float NAV_MOVE_SPEED = 800.0f;
9867 const float move_speed = ImFloor(NAV_MOVE_SPEED * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y)); // FIXME: Doesn't handle variable framerate very well
9868 ImGuiWindow* moving_window = g.NavWindowingTarget->RootWindow;
9869 SetWindowPos(moving_window, moving_window->Pos + move_delta * move_speed, ImGuiCond_Always);
9870 MarkIniSettingsDirty(moving_window);
9871 g.NavDisableMouseHover = true;
9872 }
9873 }
9874
9875 // Apply final focus
9876 if (apply_focus_window && (g.NavWindow == NULL || apply_focus_window != g.NavWindow->RootWindowDockStop))
9877 {
9878 ImGuiViewport* previous_viewport = g.NavWindow ? g.NavWindow->Viewport : NULL;
9879 ClearActiveID();
9880 g.NavDisableHighlight = false;
9881 g.NavDisableMouseHover = true;
9882 apply_focus_window = NavRestoreLastChildNavWindow(apply_focus_window);
9883 ClosePopupsOverWindow(apply_focus_window, false);
9884 FocusWindow(apply_focus_window);
9885 if (apply_focus_window->NavLastIds[0] == 0)
9886 NavInitWindow(apply_focus_window, false);
9887
9888 // If the window only has a menu layer, select it directly
9889 if (apply_focus_window->DC.NavLayerActiveMask == (1 << ImGuiNavLayer_Menu))
9890 g.NavLayer = ImGuiNavLayer_Menu;
9891
9892 // Request OS level focus
9893 if (apply_focus_window->Viewport != previous_viewport && g.PlatformIO.Platform_SetWindowFocus)
9894 g.PlatformIO.Platform_SetWindowFocus(apply_focus_window->Viewport);
9895 }
9896 if (apply_focus_window)
9897 g.NavWindowingTarget = NULL;
9898
9899 // Apply menu/layer toggle
9900 if (apply_toggle_layer && g.NavWindow)
9901 {
9902 // Move to parent menu if necessary
9903 ImGuiWindow* new_nav_window = g.NavWindow;
9904 while (new_nav_window->ParentWindow
9905 && (new_nav_window->DC.NavLayerActiveMask & (1 << ImGuiNavLayer_Menu)) == 0
9906 && (new_nav_window->Flags & ImGuiWindowFlags_ChildWindow) != 0
9907 && (new_nav_window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0)
9908 new_nav_window = new_nav_window->ParentWindow;
9909 if (new_nav_window != g.NavWindow)
9910 {
9911 ImGuiWindow* old_nav_window = g.NavWindow;
9912 FocusWindow(new_nav_window);
9913 new_nav_window->NavLastChildNavWindow = old_nav_window;
9914 }
9915 g.NavDisableHighlight = false;
9916 g.NavDisableMouseHover = true;
9917
9918 // When entering a regular menu bar with the Alt key, we always reinitialize the navigation ID. It however persist on docking tab tabs.
9919 const ImGuiNavLayer new_nav_layer = (g.NavWindow->DC.NavLayerActiveMask & (1 << ImGuiNavLayer_Menu)) ? (ImGuiNavLayer)((int)g.NavLayer ^ 1) : ImGuiNavLayer_Main;
9920 const bool preserve_layer_1_nav_id = (new_nav_window->DockNodeAsHost != NULL);
9921 if (new_nav_layer == ImGuiNavLayer_Menu && !preserve_layer_1_nav_id)
9922 g.NavWindow->NavLastIds[ImGuiNavLayer_Menu] = 0;
9923 NavRestoreLayer(new_nav_layer);
9924 }
9925 }
9926
9927 // Window has already passed the IsWindowNavFocusable()
GetFallbackWindowNameForWindowingList(ImGuiWindow * window)9928 static const char* GetFallbackWindowNameForWindowingList(ImGuiWindow* window)
9929 {
9930 if (window->Flags & ImGuiWindowFlags_Popup)
9931 return "(Popup)";
9932 if ((window->Flags & ImGuiWindowFlags_MenuBar) && strcmp(window->Name, "##MainMenuBar") == 0)
9933 return "(Main menu bar)";
9934 if (window->DockNodeAsHost)
9935 return "(Dock node)";
9936 return "(Untitled)";
9937 }
9938
9939 // Overlay displayed when using CTRL+TAB. Called by EndFrame().
NavUpdateWindowingOverlay()9940 void ImGui::NavUpdateWindowingOverlay()
9941 {
9942 ImGuiContext& g = *GImGui;
9943 IM_ASSERT(g.NavWindowingTarget != NULL);
9944
9945 if (g.NavWindowingTimer < NAV_WINDOWING_LIST_APPEAR_DELAY)
9946 return;
9947
9948 if (g.NavWindowingListWindow == NULL)
9949 g.NavWindowingListWindow = FindWindowByName("###NavWindowingList");
9950 ImGuiViewportP* viewport = /*g.NavWindow ? g.NavWindow->Viewport :*/ (ImGuiViewportP*)GetMainViewport();
9951 SetNextWindowSizeConstraints(ImVec2(viewport->Size.x * 0.20f, viewport->Size.y * 0.20f), ImVec2(FLT_MAX, FLT_MAX));
9952 SetNextWindowPos(viewport->Pos + viewport->Size * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
9953 PushStyleVar(ImGuiStyleVar_WindowPadding, g.Style.WindowPadding * 2.0f);
9954 Begin("###NavWindowingList", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings);
9955 for (int n = g.WindowsFocusOrder.Size - 1; n >= 0; n--)
9956 {
9957 ImGuiWindow* window = g.WindowsFocusOrder[n];
9958 if (!IsWindowNavFocusable(window))
9959 continue;
9960 const char* label = window->Name;
9961 if (label == FindRenderedTextEnd(label))
9962 label = GetFallbackWindowNameForWindowingList(window);
9963 Selectable(label, g.NavWindowingTarget == window);
9964 }
9965 End();
9966 PopStyleVar();
9967 }
9968
9969
9970 //-----------------------------------------------------------------------------
9971 // [SECTION] DRAG AND DROP
9972 //-----------------------------------------------------------------------------
9973
ClearDragDrop()9974 void ImGui::ClearDragDrop()
9975 {
9976 ImGuiContext& g = *GImGui;
9977 g.DragDropActive = false;
9978 g.DragDropPayload.Clear();
9979 g.DragDropAcceptFlags = ImGuiDragDropFlags_None;
9980 g.DragDropAcceptIdCurr = g.DragDropAcceptIdPrev = 0;
9981 g.DragDropAcceptIdCurrRectSurface = FLT_MAX;
9982 g.DragDropAcceptFrameCount = -1;
9983
9984 g.DragDropPayloadBufHeap.clear();
9985 memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal));
9986 }
9987
9988 // Call when current ID is active.
9989 // When this returns true you need to: a) call SetDragDropPayload() exactly once, b) you may render the payload visual/description, c) call EndDragDropSource()
BeginDragDropSource(ImGuiDragDropFlags flags)9990 bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags)
9991 {
9992 ImGuiContext& g = *GImGui;
9993 ImGuiWindow* window = g.CurrentWindow;
9994
9995 bool source_drag_active = false;
9996 ImGuiID source_id = 0;
9997 ImGuiID source_parent_id = 0;
9998 ImGuiMouseButton mouse_button = ImGuiMouseButton_Left;
9999 if (!(flags & ImGuiDragDropFlags_SourceExtern))
10000 {
10001 source_id = window->DC.LastItemId;
10002 if (source_id != 0 && g.ActiveId != source_id) // Early out for most common case
10003 return false;
10004 if (g.IO.MouseDown[mouse_button] == false)
10005 return false;
10006
10007 if (source_id == 0)
10008 {
10009 // If you want to use BeginDragDropSource() on an item with no unique identifier for interaction, such as Text() or Image(), you need to:
10010 // A) Read the explanation below, B) Use the ImGuiDragDropFlags_SourceAllowNullID flag, C) Swallow your programmer pride.
10011 if (!(flags & ImGuiDragDropFlags_SourceAllowNullID))
10012 {
10013 IM_ASSERT(0);
10014 return false;
10015 }
10016
10017 // Early out
10018 if ((window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) == 0 && (g.ActiveId == 0 || g.ActiveIdWindow != window))
10019 return false;
10020
10021 // Magic fallback (=somehow reprehensible) to handle items with no assigned ID, e.g. Text(), Image()
10022 // We build a throwaway ID based on current ID stack + relative AABB of items in window.
10023 // THE IDENTIFIER WON'T SURVIVE ANY REPOSITIONING OF THE WIDGET, so if your widget moves your dragging operation will be canceled.
10024 // We don't need to maintain/call ClearActiveID() as releasing the button will early out this function and trigger !ActiveIdIsAlive.
10025 source_id = window->DC.LastItemId = window->GetIDFromRectangle(window->DC.LastItemRect);
10026 bool is_hovered = ItemHoverable(window->DC.LastItemRect, source_id);
10027 if (is_hovered && g.IO.MouseClicked[mouse_button])
10028 {
10029 SetActiveID(source_id, window);
10030 FocusWindow(window);
10031 }
10032 if (g.ActiveId == source_id) // Allow the underlying widget to display/return hovered during the mouse release frame, else we would get a flicker.
10033 g.ActiveIdAllowOverlap = is_hovered;
10034 }
10035 else
10036 {
10037 g.ActiveIdAllowOverlap = false;
10038 }
10039 if (g.ActiveId != source_id)
10040 return false;
10041 source_parent_id = window->IDStack.back();
10042 source_drag_active = IsMouseDragging(mouse_button);
10043
10044 // Disable navigation and key inputs while dragging
10045 g.ActiveIdUsingNavDirMask = ~(ImU32)0;
10046 g.ActiveIdUsingNavInputMask = ~(ImU32)0;
10047 g.ActiveIdUsingKeyInputMask = ~(ImU64)0;
10048 }
10049 else
10050 {
10051 window = NULL;
10052 source_id = ImHashStr("#SourceExtern");
10053 source_drag_active = true;
10054 }
10055
10056 if (source_drag_active)
10057 {
10058 if (!g.DragDropActive)
10059 {
10060 IM_ASSERT(source_id != 0);
10061 ClearDragDrop();
10062 ImGuiPayload& payload = g.DragDropPayload;
10063 payload.SourceId = source_id;
10064 payload.SourceParentId = source_parent_id;
10065 g.DragDropActive = true;
10066 g.DragDropSourceFlags = flags;
10067 g.DragDropMouseButton = mouse_button;
10068 }
10069 g.DragDropSourceFrameCount = g.FrameCount;
10070 g.DragDropWithinSource = true;
10071
10072 if (!(flags & ImGuiDragDropFlags_SourceNoPreviewTooltip))
10073 {
10074 // Target can request the Source to not display its tooltip (we use a dedicated flag to make this request explicit)
10075 // We unfortunately can't just modify the source flags and skip the call to BeginTooltip, as caller may be emitting contents.
10076 BeginTooltip();
10077 if (g.DragDropAcceptIdPrev && (g.DragDropAcceptFlags & ImGuiDragDropFlags_AcceptNoPreviewTooltip))
10078 {
10079 ImGuiWindow* tooltip_window = g.CurrentWindow;
10080 tooltip_window->SkipItems = true;
10081 tooltip_window->HiddenFramesCanSkipItems = 1;
10082 }
10083 }
10084
10085 if (!(flags & ImGuiDragDropFlags_SourceNoDisableHover) && !(flags & ImGuiDragDropFlags_SourceExtern))
10086 window->DC.LastItemStatusFlags &= ~ImGuiItemStatusFlags_HoveredRect;
10087
10088 return true;
10089 }
10090 return false;
10091 }
10092
EndDragDropSource()10093 void ImGui::EndDragDropSource()
10094 {
10095 ImGuiContext& g = *GImGui;
10096 IM_ASSERT(g.DragDropActive);
10097 IM_ASSERT(g.DragDropWithinSource && "Not after a BeginDragDropSource()?");
10098
10099 if (!(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoPreviewTooltip))
10100 EndTooltip();
10101
10102 // Discard the drag if have not called SetDragDropPayload()
10103 if (g.DragDropPayload.DataFrameCount == -1)
10104 ClearDragDrop();
10105 g.DragDropWithinSource = false;
10106 }
10107
10108 // Use 'cond' to choose to submit payload on drag start or every frame
SetDragDropPayload(const char * type,const void * data,size_t data_size,ImGuiCond cond)10109 bool ImGui::SetDragDropPayload(const char* type, const void* data, size_t data_size, ImGuiCond cond)
10110 {
10111 ImGuiContext& g = *GImGui;
10112 ImGuiPayload& payload = g.DragDropPayload;
10113 if (cond == 0)
10114 cond = ImGuiCond_Always;
10115
10116 IM_ASSERT(type != NULL);
10117 IM_ASSERT(strlen(type) < IM_ARRAYSIZE(payload.DataType) && "Payload type can be at most 32 characters long");
10118 IM_ASSERT((data != NULL && data_size > 0) || (data == NULL && data_size == 0));
10119 IM_ASSERT(cond == ImGuiCond_Always || cond == ImGuiCond_Once);
10120 IM_ASSERT(payload.SourceId != 0); // Not called between BeginDragDropSource() and EndDragDropSource()
10121
10122 if (cond == ImGuiCond_Always || payload.DataFrameCount == -1)
10123 {
10124 // Copy payload
10125 ImStrncpy(payload.DataType, type, IM_ARRAYSIZE(payload.DataType));
10126 g.DragDropPayloadBufHeap.resize(0);
10127 if (data_size > sizeof(g.DragDropPayloadBufLocal))
10128 {
10129 // Store in heap
10130 g.DragDropPayloadBufHeap.resize((int)data_size);
10131 payload.Data = g.DragDropPayloadBufHeap.Data;
10132 memcpy(payload.Data, data, data_size);
10133 }
10134 else if (data_size > 0)
10135 {
10136 // Store locally
10137 memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal));
10138 payload.Data = g.DragDropPayloadBufLocal;
10139 memcpy(payload.Data, data, data_size);
10140 }
10141 else
10142 {
10143 payload.Data = NULL;
10144 }
10145 payload.DataSize = (int)data_size;
10146 }
10147 payload.DataFrameCount = g.FrameCount;
10148
10149 return (g.DragDropAcceptFrameCount == g.FrameCount) || (g.DragDropAcceptFrameCount == g.FrameCount - 1);
10150 }
10151
BeginDragDropTargetCustom(const ImRect & bb,ImGuiID id)10152 bool ImGui::BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id)
10153 {
10154 ImGuiContext& g = *GImGui;
10155 if (!g.DragDropActive)
10156 return false;
10157
10158 ImGuiWindow* window = g.CurrentWindow;
10159 ImGuiWindow* hovered_window = g.HoveredWindowUnderMovingWindow;
10160 if (hovered_window == NULL || window->RootWindow != hovered_window->RootWindow)
10161 return false;
10162 IM_ASSERT(id != 0);
10163 if (!IsMouseHoveringRect(bb.Min, bb.Max) || (id == g.DragDropPayload.SourceId))
10164 return false;
10165 if (window->SkipItems)
10166 return false;
10167
10168 IM_ASSERT(g.DragDropWithinTarget == false);
10169 g.DragDropTargetRect = bb;
10170 g.DragDropTargetId = id;
10171 g.DragDropWithinTarget = true;
10172 return true;
10173 }
10174
10175 // We don't use BeginDragDropTargetCustom() and duplicate its code because:
10176 // 1) we use LastItemRectHoveredRect which handles items that pushes a temporarily clip rectangle in their code. Calling BeginDragDropTargetCustom(LastItemRect) would not handle them.
10177 // 2) and it's faster. as this code may be very frequently called, we want to early out as fast as we can.
10178 // Also note how the HoveredWindow test is positioned differently in both functions (in both functions we optimize for the cheapest early out case)
BeginDragDropTarget()10179 bool ImGui::BeginDragDropTarget()
10180 {
10181 ImGuiContext& g = *GImGui;
10182 if (!g.DragDropActive)
10183 return false;
10184
10185 ImGuiWindow* window = g.CurrentWindow;
10186 if (!(window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect))
10187 return false;
10188 ImGuiWindow* hovered_window = g.HoveredWindowUnderMovingWindow;
10189 if (hovered_window == NULL || window->RootWindow != hovered_window->RootWindow)
10190 return false;
10191
10192 const ImRect& display_rect = (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HasDisplayRect) ? window->DC.LastItemDisplayRect : window->DC.LastItemRect;
10193 ImGuiID id = window->DC.LastItemId;
10194 if (id == 0)
10195 id = window->GetIDFromRectangle(display_rect);
10196 if (g.DragDropPayload.SourceId == id)
10197 return false;
10198
10199 IM_ASSERT(g.DragDropWithinTarget == false);
10200 g.DragDropTargetRect = display_rect;
10201 g.DragDropTargetId = id;
10202 g.DragDropWithinTarget = true;
10203 return true;
10204 }
10205
IsDragDropPayloadBeingAccepted()10206 bool ImGui::IsDragDropPayloadBeingAccepted()
10207 {
10208 ImGuiContext& g = *GImGui;
10209 return g.DragDropActive && g.DragDropAcceptIdPrev != 0;
10210 }
10211
AcceptDragDropPayload(const char * type,ImGuiDragDropFlags flags)10212 const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDropFlags flags)
10213 {
10214 ImGuiContext& g = *GImGui;
10215 ImGuiWindow* window = g.CurrentWindow;
10216 ImGuiPayload& payload = g.DragDropPayload;
10217 IM_ASSERT(g.DragDropActive); // Not called between BeginDragDropTarget() and EndDragDropTarget() ?
10218 IM_ASSERT(payload.DataFrameCount != -1); // Forgot to call EndDragDropTarget() ?
10219 if (type != NULL && !payload.IsDataType(type))
10220 return NULL;
10221
10222 // Accept smallest drag target bounding box, this allows us to nest drag targets conveniently without ordering constraints.
10223 // NB: We currently accept NULL id as target. However, overlapping targets requires a unique ID to function!
10224 const bool was_accepted_previously = (g.DragDropAcceptIdPrev == g.DragDropTargetId);
10225 ImRect r = g.DragDropTargetRect;
10226 float r_surface = r.GetWidth() * r.GetHeight();
10227 if (r_surface < g.DragDropAcceptIdCurrRectSurface)
10228 {
10229 g.DragDropAcceptFlags = flags;
10230 g.DragDropAcceptIdCurr = g.DragDropTargetId;
10231 g.DragDropAcceptIdCurrRectSurface = r_surface;
10232 }
10233
10234 // Render default drop visuals
10235 payload.Preview = was_accepted_previously;
10236 flags |= (g.DragDropSourceFlags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect); // Source can also inhibit the preview (useful for external sources that lives for 1 frame)
10237 if (!(flags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect) && payload.Preview)
10238 {
10239 // FIXME-DRAG: Settle on a proper default visuals for drop target.
10240 r.Expand(3.5f);
10241 bool push_clip_rect = !window->ClipRect.Contains(r);
10242 if (push_clip_rect) window->DrawList->PushClipRect(r.Min - ImVec2(1, 1), r.Max + ImVec2(1, 1));
10243 window->DrawList->AddRect(r.Min, r.Max, GetColorU32(ImGuiCol_DragDropTarget), 0.0f, ~0, 2.0f);
10244 if (push_clip_rect) window->DrawList->PopClipRect();
10245 }
10246
10247 g.DragDropAcceptFrameCount = g.FrameCount;
10248 payload.Delivery = was_accepted_previously && !IsMouseDown(g.DragDropMouseButton); // For extern drag sources affecting os window focus, it's easier to just test !IsMouseDown() instead of IsMouseReleased()
10249 if (!payload.Delivery && !(flags & ImGuiDragDropFlags_AcceptBeforeDelivery))
10250 return NULL;
10251
10252 return &payload;
10253 }
10254
GetDragDropPayload()10255 const ImGuiPayload* ImGui::GetDragDropPayload()
10256 {
10257 ImGuiContext& g = *GImGui;
10258 return g.DragDropActive ? &g.DragDropPayload : NULL;
10259 }
10260
10261 // We don't really use/need this now, but added it for the sake of consistency and because we might need it later.
EndDragDropTarget()10262 void ImGui::EndDragDropTarget()
10263 {
10264 ImGuiContext& g = *GImGui;
10265 IM_ASSERT(g.DragDropActive);
10266 IM_ASSERT(g.DragDropWithinTarget);
10267 g.DragDropWithinTarget = false;
10268 }
10269
10270 //-----------------------------------------------------------------------------
10271 // [SECTION] LOGGING/CAPTURING
10272 //-----------------------------------------------------------------------------
10273 // All text output from the interface can be captured into tty/file/clipboard.
10274 // By default, tree nodes are automatically opened during logging.
10275 //-----------------------------------------------------------------------------
10276
10277 // Pass text data straight to log (without being displayed)
LogText(const char * fmt,...)10278 void ImGui::LogText(const char* fmt, ...)
10279 {
10280 ImGuiContext& g = *GImGui;
10281 if (!g.LogEnabled)
10282 return;
10283
10284 va_list args;
10285 va_start(args, fmt);
10286 if (g.LogFile)
10287 {
10288 g.LogBuffer.Buf.resize(0);
10289 g.LogBuffer.appendfv(fmt, args);
10290 ImFileWrite(g.LogBuffer.c_str(), sizeof(char), (ImU64)g.LogBuffer.size(), g.LogFile);
10291 }
10292 else
10293 {
10294 g.LogBuffer.appendfv(fmt, args);
10295 }
10296 va_end(args);
10297 }
10298
10299 // Internal version that takes a position to decide on newline placement and pad items according to their depth.
10300 // We split text into individual lines to add current tree level padding
LogRenderedText(const ImVec2 * ref_pos,const char * text,const char * text_end)10301 void ImGui::LogRenderedText(const ImVec2* ref_pos, const char* text, const char* text_end)
10302 {
10303 ImGuiContext& g = *GImGui;
10304 ImGuiWindow* window = g.CurrentWindow;
10305
10306 if (!text_end)
10307 text_end = FindRenderedTextEnd(text, text_end);
10308
10309 const bool log_new_line = ref_pos && (ref_pos->y > g.LogLinePosY + 1);
10310 if (ref_pos)
10311 g.LogLinePosY = ref_pos->y;
10312 if (log_new_line)
10313 g.LogLineFirstItem = true;
10314
10315 const char* text_remaining = text;
10316 if (g.LogDepthRef > window->DC.TreeDepth) // Re-adjust padding if we have popped out of our starting depth
10317 g.LogDepthRef = window->DC.TreeDepth;
10318 const int tree_depth = (window->DC.TreeDepth - g.LogDepthRef);
10319 for (;;)
10320 {
10321 // Split the string. Each new line (after a '\n') is followed by spacing corresponding to the current depth of our log entry.
10322 // We don't add a trailing \n to allow a subsequent item on the same line to be captured.
10323 const char* line_start = text_remaining;
10324 const char* line_end = ImStreolRange(line_start, text_end);
10325 const bool is_first_line = (line_start == text);
10326 const bool is_last_line = (line_end == text_end);
10327 if (!is_last_line || (line_start != line_end))
10328 {
10329 const int char_count = (int)(line_end - line_start);
10330 if (log_new_line || !is_first_line)
10331 LogText(IM_NEWLINE "%*s%.*s", tree_depth * 4, "", char_count, line_start);
10332 else if (g.LogLineFirstItem)
10333 LogText("%*s%.*s", tree_depth * 4, "", char_count, line_start);
10334 else
10335 LogText(" %.*s", char_count, line_start);
10336 g.LogLineFirstItem = false;
10337 }
10338 else if (log_new_line)
10339 {
10340 // An empty "" string at a different Y position should output a carriage return.
10341 LogText(IM_NEWLINE);
10342 break;
10343 }
10344
10345 if (is_last_line)
10346 break;
10347 text_remaining = line_end + 1;
10348 }
10349 }
10350
10351 // Start logging/capturing text output
LogBegin(ImGuiLogType type,int auto_open_depth)10352 void ImGui::LogBegin(ImGuiLogType type, int auto_open_depth)
10353 {
10354 ImGuiContext& g = *GImGui;
10355 ImGuiWindow* window = g.CurrentWindow;
10356 IM_ASSERT(g.LogEnabled == false);
10357 IM_ASSERT(g.LogFile == NULL);
10358 IM_ASSERT(g.LogBuffer.empty());
10359 g.LogEnabled = true;
10360 g.LogType = type;
10361 g.LogDepthRef = window->DC.TreeDepth;
10362 g.LogDepthToExpand = ((auto_open_depth >= 0) ? auto_open_depth : g.LogDepthToExpandDefault);
10363 g.LogLinePosY = FLT_MAX;
10364 g.LogLineFirstItem = true;
10365 }
10366
LogToTTY(int auto_open_depth)10367 void ImGui::LogToTTY(int auto_open_depth)
10368 {
10369 ImGuiContext& g = *GImGui;
10370 if (g.LogEnabled)
10371 return;
10372 IM_UNUSED(auto_open_depth);
10373 #ifndef IMGUI_DISABLE_TTY_FUNCTIONS
10374 LogBegin(ImGuiLogType_TTY, auto_open_depth);
10375 g.LogFile = stdout;
10376 #endif
10377 }
10378
10379 // Start logging/capturing text output to given file
LogToFile(int auto_open_depth,const char * filename)10380 void ImGui::LogToFile(int auto_open_depth, const char* filename)
10381 {
10382 ImGuiContext& g = *GImGui;
10383 if (g.LogEnabled)
10384 return;
10385
10386 // FIXME: We could probably open the file in text mode "at", however note that clipboard/buffer logging will still
10387 // be subject to outputting OS-incompatible carriage return if within strings the user doesn't use IM_NEWLINE.
10388 // By opening the file in binary mode "ab" we have consistent output everywhere.
10389 if (!filename)
10390 filename = g.IO.LogFilename;
10391 if (!filename || !filename[0])
10392 return;
10393 ImFileHandle f = ImFileOpen(filename, "ab");
10394 if (!f)
10395 {
10396 IM_ASSERT(0);
10397 return;
10398 }
10399
10400 LogBegin(ImGuiLogType_File, auto_open_depth);
10401 g.LogFile = f;
10402 }
10403
10404 // Start logging/capturing text output to clipboard
LogToClipboard(int auto_open_depth)10405 void ImGui::LogToClipboard(int auto_open_depth)
10406 {
10407 ImGuiContext& g = *GImGui;
10408 if (g.LogEnabled)
10409 return;
10410 LogBegin(ImGuiLogType_Clipboard, auto_open_depth);
10411 }
10412
LogToBuffer(int auto_open_depth)10413 void ImGui::LogToBuffer(int auto_open_depth)
10414 {
10415 ImGuiContext& g = *GImGui;
10416 if (g.LogEnabled)
10417 return;
10418 LogBegin(ImGuiLogType_Buffer, auto_open_depth);
10419 }
10420
LogFinish()10421 void ImGui::LogFinish()
10422 {
10423 ImGuiContext& g = *GImGui;
10424 if (!g.LogEnabled)
10425 return;
10426
10427 LogText(IM_NEWLINE);
10428 switch (g.LogType)
10429 {
10430 case ImGuiLogType_TTY:
10431 #ifndef IMGUI_DISABLE_TTY_FUNCTIONS
10432 fflush(g.LogFile);
10433 #endif
10434 break;
10435 case ImGuiLogType_File:
10436 ImFileClose(g.LogFile);
10437 break;
10438 case ImGuiLogType_Buffer:
10439 break;
10440 case ImGuiLogType_Clipboard:
10441 if (!g.LogBuffer.empty())
10442 SetClipboardText(g.LogBuffer.begin());
10443 break;
10444 case ImGuiLogType_None:
10445 IM_ASSERT(0);
10446 break;
10447 }
10448
10449 g.LogEnabled = false;
10450 g.LogType = ImGuiLogType_None;
10451 g.LogFile = NULL;
10452 g.LogBuffer.clear();
10453 }
10454
10455 // Helper to display logging buttons
10456 // FIXME-OBSOLETE: We should probably obsolete this and let the user have their own helper (this is one of the oldest function alive!)
LogButtons()10457 void ImGui::LogButtons()
10458 {
10459 ImGuiContext& g = *GImGui;
10460
10461 PushID("LogButtons");
10462 #ifndef IMGUI_DISABLE_TTY_FUNCTIONS
10463 const bool log_to_tty = Button("Log To TTY"); SameLine();
10464 #else
10465 const bool log_to_tty = false;
10466 #endif
10467 const bool log_to_file = Button("Log To File"); SameLine();
10468 const bool log_to_clipboard = Button("Log To Clipboard"); SameLine();
10469 PushAllowKeyboardFocus(false);
10470 SetNextItemWidth(80.0f);
10471 SliderInt("Default Depth", &g.LogDepthToExpandDefault, 0, 9, NULL);
10472 PopAllowKeyboardFocus();
10473 PopID();
10474
10475 // Start logging at the end of the function so that the buttons don't appear in the log
10476 if (log_to_tty)
10477 LogToTTY();
10478 if (log_to_file)
10479 LogToFile();
10480 if (log_to_clipboard)
10481 LogToClipboard();
10482 }
10483
10484
10485 //-----------------------------------------------------------------------------
10486 // [SECTION] SETTINGS
10487 //-----------------------------------------------------------------------------
10488 // - UpdateSettings() [Internal]
10489 // - MarkIniSettingsDirty() [Internal]
10490 // - CreateNewWindowSettings() [Internal]
10491 // - FindWindowSettings() [Internal]
10492 // - FindOrCreateWindowSettings() [Internal]
10493 // - FindSettingsHandler() [Internal]
10494 // - ClearIniSettings() [Internal]
10495 // - LoadIniSettingsFromDisk()
10496 // - LoadIniSettingsFromMemory()
10497 // - SaveIniSettingsToDisk()
10498 // - SaveIniSettingsToMemory()
10499 // - WindowSettingsHandler_***() [Internal]
10500 //-----------------------------------------------------------------------------
10501
10502 // Called by NewFrame()
UpdateSettings()10503 void ImGui::UpdateSettings()
10504 {
10505 // Load settings on first frame (if not explicitly loaded manually before)
10506 ImGuiContext& g = *GImGui;
10507 if (!g.SettingsLoaded)
10508 {
10509 IM_ASSERT(g.SettingsWindows.empty());
10510 if (g.IO.IniFilename)
10511 LoadIniSettingsFromDisk(g.IO.IniFilename);
10512 g.SettingsLoaded = true;
10513 }
10514
10515 // Save settings (with a delay after the last modification, so we don't spam disk too much)
10516 if (g.SettingsDirtyTimer > 0.0f)
10517 {
10518 g.SettingsDirtyTimer -= g.IO.DeltaTime;
10519 if (g.SettingsDirtyTimer <= 0.0f)
10520 {
10521 if (g.IO.IniFilename != NULL)
10522 SaveIniSettingsToDisk(g.IO.IniFilename);
10523 else
10524 g.IO.WantSaveIniSettings = true; // Let user know they can call SaveIniSettingsToMemory(). user will need to clear io.WantSaveIniSettings themselves.
10525 g.SettingsDirtyTimer = 0.0f;
10526 }
10527 }
10528 }
10529
MarkIniSettingsDirty()10530 void ImGui::MarkIniSettingsDirty()
10531 {
10532 ImGuiContext& g = *GImGui;
10533 if (g.SettingsDirtyTimer <= 0.0f)
10534 g.SettingsDirtyTimer = g.IO.IniSavingRate;
10535 }
10536
MarkIniSettingsDirty(ImGuiWindow * window)10537 void ImGui::MarkIniSettingsDirty(ImGuiWindow* window)
10538 {
10539 ImGuiContext& g = *GImGui;
10540 if (!(window->Flags & ImGuiWindowFlags_NoSavedSettings))
10541 if (g.SettingsDirtyTimer <= 0.0f)
10542 g.SettingsDirtyTimer = g.IO.IniSavingRate;
10543 }
10544
CreateNewWindowSettings(const char * name)10545 ImGuiWindowSettings* ImGui::CreateNewWindowSettings(const char* name)
10546 {
10547 ImGuiContext& g = *GImGui;
10548
10549 #if !IMGUI_DEBUG_INI_SETTINGS
10550 // Skip to the "###" marker if any. We don't skip past to match the behavior of GetID()
10551 // Preserve the full string when IMGUI_DEBUG_INI_SETTINGS is set to make .ini inspection easier.
10552 if (const char* p = strstr(name, "###"))
10553 name = p;
10554 #endif
10555 const size_t name_len = strlen(name);
10556
10557 // Allocate chunk
10558 const size_t chunk_size = sizeof(ImGuiWindowSettings) + name_len + 1;
10559 ImGuiWindowSettings* settings = g.SettingsWindows.alloc_chunk(chunk_size);
10560 IM_PLACEMENT_NEW(settings) ImGuiWindowSettings();
10561 settings->ID = ImHashStr(name, name_len);
10562 memcpy(settings->GetName(), name, name_len + 1); // Store with zero terminator
10563
10564 return settings;
10565 }
10566
FindWindowSettings(ImGuiID id)10567 ImGuiWindowSettings* ImGui::FindWindowSettings(ImGuiID id)
10568 {
10569 ImGuiContext& g = *GImGui;
10570 for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings))
10571 if (settings->ID == id)
10572 return settings;
10573 return NULL;
10574 }
10575
FindOrCreateWindowSettings(const char * name)10576 ImGuiWindowSettings* ImGui::FindOrCreateWindowSettings(const char* name)
10577 {
10578 if (ImGuiWindowSettings* settings = FindWindowSettings(ImHashStr(name)))
10579 return settings;
10580 return CreateNewWindowSettings(name);
10581 }
10582
FindSettingsHandler(const char * type_name)10583 ImGuiSettingsHandler* ImGui::FindSettingsHandler(const char* type_name)
10584 {
10585 ImGuiContext& g = *GImGui;
10586 const ImGuiID type_hash = ImHashStr(type_name);
10587 for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++)
10588 if (g.SettingsHandlers[handler_n].TypeHash == type_hash)
10589 return &g.SettingsHandlers[handler_n];
10590 return NULL;
10591 }
10592
ClearIniSettings()10593 void ImGui::ClearIniSettings()
10594 {
10595 ImGuiContext& g = *GImGui;
10596 g.SettingsIniData.clear();
10597 for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++)
10598 if (g.SettingsHandlers[handler_n].ClearAllFn)
10599 g.SettingsHandlers[handler_n].ClearAllFn(&g, &g.SettingsHandlers[handler_n]);
10600 }
10601
LoadIniSettingsFromDisk(const char * ini_filename)10602 void ImGui::LoadIniSettingsFromDisk(const char* ini_filename)
10603 {
10604 size_t file_data_size = 0;
10605 char* file_data = (char*)ImFileLoadToMemory(ini_filename, "rb", &file_data_size);
10606 if (!file_data)
10607 return;
10608 LoadIniSettingsFromMemory(file_data, (size_t)file_data_size);
10609 IM_FREE(file_data);
10610 }
10611
10612 // Zero-tolerance, no error reporting, cheap .ini parsing
LoadIniSettingsFromMemory(const char * ini_data,size_t ini_size)10613 void ImGui::LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size)
10614 {
10615 ImGuiContext& g = *GImGui;
10616 IM_ASSERT(g.Initialized);
10617 //IM_ASSERT(!g.WithinFrameScope && "Cannot be called between NewFrame() and EndFrame()");
10618 //IM_ASSERT(g.SettingsLoaded == false && g.FrameCount == 0);
10619
10620 // For user convenience, we allow passing a non zero-terminated string (hence the ini_size parameter).
10621 // 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..
10622 if (ini_size == 0)
10623 ini_size = strlen(ini_data);
10624 g.SettingsIniData.Buf.resize((int)ini_size + 1);
10625 char* const buf = g.SettingsIniData.Buf.Data;
10626 char* const buf_end = buf + ini_size;
10627 memcpy(buf, ini_data, ini_size);
10628 buf_end[0] = 0;
10629
10630 // Call pre-read handlers
10631 // Some types will clear their data (e.g. dock information) some types will allow merge/override (window)
10632 for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++)
10633 if (g.SettingsHandlers[handler_n].ReadInitFn)
10634 g.SettingsHandlers[handler_n].ReadInitFn(&g, &g.SettingsHandlers[handler_n]);
10635
10636 void* entry_data = NULL;
10637 ImGuiSettingsHandler* entry_handler = NULL;
10638
10639 char* line_end = NULL;
10640 for (char* line = buf; line < buf_end; line = line_end + 1)
10641 {
10642 // Skip new lines markers, then find end of the line
10643 while (*line == '\n' || *line == '\r')
10644 line++;
10645 line_end = line;
10646 while (line_end < buf_end && *line_end != '\n' && *line_end != '\r')
10647 line_end++;
10648 line_end[0] = 0;
10649 if (line[0] == ';')
10650 continue;
10651 if (line[0] == '[' && line_end > line && line_end[-1] == ']')
10652 {
10653 // Parse "[Type][Name]". Note that 'Name' can itself contains [] characters, which is acceptable with the current format and parsing code.
10654 line_end[-1] = 0;
10655 const char* name_end = line_end - 1;
10656 const char* type_start = line + 1;
10657 char* type_end = (char*)(void*)ImStrchrRange(type_start, name_end, ']');
10658 const char* name_start = type_end ? ImStrchrRange(type_end + 1, name_end, '[') : NULL;
10659 if (!type_end || !name_start)
10660 continue;
10661 *type_end = 0; // Overwrite first ']'
10662 name_start++; // Skip second '['
10663 entry_handler = FindSettingsHandler(type_start);
10664 entry_data = entry_handler ? entry_handler->ReadOpenFn(&g, entry_handler, name_start) : NULL;
10665 }
10666 else if (entry_handler != NULL && entry_data != NULL)
10667 {
10668 // Let type handler parse the line
10669 entry_handler->ReadLineFn(&g, entry_handler, entry_data, line);
10670 }
10671 }
10672 g.SettingsLoaded = true;
10673
10674 // [DEBUG] Restore untouched copy so it can be browsed in Metrics (not strictly necessary)
10675 memcpy(buf, ini_data, ini_size);
10676
10677 // Call post-read handlers
10678 for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++)
10679 if (g.SettingsHandlers[handler_n].ApplyAllFn)
10680 g.SettingsHandlers[handler_n].ApplyAllFn(&g, &g.SettingsHandlers[handler_n]);
10681 }
10682
SaveIniSettingsToDisk(const char * ini_filename)10683 void ImGui::SaveIniSettingsToDisk(const char* ini_filename)
10684 {
10685 ImGuiContext& g = *GImGui;
10686 g.SettingsDirtyTimer = 0.0f;
10687 if (!ini_filename)
10688 return;
10689
10690 size_t ini_data_size = 0;
10691 const char* ini_data = SaveIniSettingsToMemory(&ini_data_size);
10692 ImFileHandle f = ImFileOpen(ini_filename, "wt");
10693 if (!f)
10694 return;
10695 ImFileWrite(ini_data, sizeof(char), ini_data_size, f);
10696 ImFileClose(f);
10697 }
10698
10699 // Call registered handlers (e.g. SettingsHandlerWindow_WriteAll() + custom handlers) to write their stuff into a text buffer
SaveIniSettingsToMemory(size_t * out_size)10700 const char* ImGui::SaveIniSettingsToMemory(size_t* out_size)
10701 {
10702 ImGuiContext& g = *GImGui;
10703 g.SettingsDirtyTimer = 0.0f;
10704 g.SettingsIniData.Buf.resize(0);
10705 g.SettingsIniData.Buf.push_back(0);
10706 for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++)
10707 {
10708 ImGuiSettingsHandler* handler = &g.SettingsHandlers[handler_n];
10709 handler->WriteAllFn(&g, handler, &g.SettingsIniData);
10710 }
10711 if (out_size)
10712 *out_size = (size_t)g.SettingsIniData.size();
10713 return g.SettingsIniData.c_str();
10714 }
10715
WindowSettingsHandler_ClearAll(ImGuiContext * ctx,ImGuiSettingsHandler *)10716 static void WindowSettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettingsHandler*)
10717 {
10718 ImGuiContext& g = *ctx;
10719 for (int i = 0; i != g.Windows.Size; i++)
10720 g.Windows[i]->SettingsOffset = -1;
10721 g.SettingsWindows.clear();
10722 }
10723
WindowSettingsHandler_ReadOpen(ImGuiContext *,ImGuiSettingsHandler *,const char * name)10724 static void* WindowSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name)
10725 {
10726 ImGuiWindowSettings* settings = ImGui::FindOrCreateWindowSettings(name);
10727 ImGuiID id = settings->ID;
10728 *settings = ImGuiWindowSettings(); // Clear existing if recycling previous entry
10729 settings->ID = id;
10730 settings->WantApply = true;
10731 return (void*)settings;
10732 }
10733
WindowSettingsHandler_ReadLine(ImGuiContext *,ImGuiSettingsHandler *,void * entry,const char * line)10734 static void WindowSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line)
10735 {
10736 ImGuiWindowSettings* settings = (ImGuiWindowSettings*)entry;
10737 int x, y;
10738 int i;
10739 ImU32 u1;
10740 if (sscanf(line, "Pos=%i,%i", &x, &y) == 2) { settings->Pos = ImVec2ih((short)x, (short)y); }
10741 else if (sscanf(line, "Size=%i,%i", &x, &y) == 2) { settings->Size = ImVec2ih((short)x, (short)y); }
10742 else if (sscanf(line, "ViewportId=0x%08X", &u1) == 1) { settings->ViewportId = u1; }
10743 else if (sscanf(line, "ViewportPos=%i,%i", &x, &y) == 2){ settings->ViewportPos = ImVec2ih((short)x, (short)y); }
10744 else if (sscanf(line, "Collapsed=%d", &i) == 1) { settings->Collapsed = (i != 0); }
10745 else if (sscanf(line, "DockId=0x%X,%d", &u1, &i) == 2) { settings->DockId = u1; settings->DockOrder = (short)i; }
10746 else if (sscanf(line, "DockId=0x%X", &u1) == 1) { settings->DockId = u1; settings->DockOrder = -1; }
10747 else if (sscanf(line, "ClassId=0x%X", &u1) == 1) { settings->ClassId = u1; }
10748 }
10749
10750 // Apply to existing windows (if any)
WindowSettingsHandler_ApplyAll(ImGuiContext * ctx,ImGuiSettingsHandler *)10751 static void WindowSettingsHandler_ApplyAll(ImGuiContext* ctx, ImGuiSettingsHandler*)
10752 {
10753 ImGuiContext& g = *ctx;
10754 for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings))
10755 if (settings->WantApply)
10756 {
10757 if (ImGuiWindow* window = ImGui::FindWindowByID(settings->ID))
10758 ApplyWindowSettings(window, settings);
10759 settings->WantApply = false;
10760 }
10761 }
10762
WindowSettingsHandler_WriteAll(ImGuiContext * ctx,ImGuiSettingsHandler * handler,ImGuiTextBuffer * buf)10763 static void WindowSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf)
10764 {
10765 // Gather data from windows that were active during this session
10766 // (if a window wasn't opened in this session we preserve its settings)
10767 ImGuiContext& g = *ctx;
10768 for (int i = 0; i != g.Windows.Size; i++)
10769 {
10770 ImGuiWindow* window = g.Windows[i];
10771 if (window->Flags & ImGuiWindowFlags_NoSavedSettings)
10772 continue;
10773
10774 ImGuiWindowSettings* settings = (window->SettingsOffset != -1) ? g.SettingsWindows.ptr_from_offset(window->SettingsOffset) : ImGui::FindWindowSettings(window->ID);
10775 if (!settings)
10776 {
10777 settings = ImGui::CreateNewWindowSettings(window->Name);
10778 window->SettingsOffset = g.SettingsWindows.offset_from_ptr(settings);
10779 }
10780 IM_ASSERT(settings->ID == window->ID);
10781 settings->Pos = ImVec2ih(window->Pos - window->ViewportPos);
10782 settings->Size = ImVec2ih(window->SizeFull);
10783 settings->ViewportId = window->ViewportId;
10784 settings->ViewportPos = ImVec2ih(window->ViewportPos);
10785 IM_ASSERT(window->DockNode == NULL || window->DockNode->ID == window->DockId);
10786 settings->DockId = window->DockId;
10787 settings->ClassId = window->WindowClass.ClassId;
10788 settings->DockOrder = window->DockOrder;
10789 settings->Collapsed = window->Collapsed;
10790 }
10791
10792 // Write to text buffer
10793 buf->reserve(buf->size() + g.SettingsWindows.size() * 6); // ballpark reserve
10794 for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings))
10795 {
10796 const char* settings_name = settings->GetName();
10797 buf->appendf("[%s][%s]\n", handler->TypeName, settings_name);
10798 if (settings->ViewportId != 0 && settings->ViewportId != ImGui::IMGUI_VIEWPORT_DEFAULT_ID)
10799 {
10800 buf->appendf("ViewportPos=%d,%d\n", settings->ViewportPos.x, settings->ViewportPos.y);
10801 buf->appendf("ViewportId=0x%08X\n", settings->ViewportId);
10802 }
10803 if (settings->Pos.x != 0 || settings->Pos.y != 0 || settings->ViewportId == ImGui::IMGUI_VIEWPORT_DEFAULT_ID)
10804 buf->appendf("Pos=%d,%d\n", settings->Pos.x, settings->Pos.y);
10805 if (settings->Size.x != 0 || settings->Size.y != 0)
10806 buf->appendf("Size=%d,%d\n", settings->Size.x, settings->Size.y);
10807 buf->appendf("Collapsed=%d\n", settings->Collapsed);
10808 if (settings->DockId != 0)
10809 {
10810 // Write DockId as 4 digits if possible. Automatic DockId are small numbers, but full explicit DockSpace() are full ImGuiID range.
10811 if (settings->DockOrder == -1)
10812 buf->appendf("DockId=0x%08X\n", settings->DockId);
10813 else
10814 buf->appendf("DockId=0x%08X,%d\n", settings->DockId, settings->DockOrder);
10815 if (settings->ClassId != 0)
10816 buf->appendf("ClassId=0x%08X\n", settings->ClassId);
10817 }
10818 buf->append("\n");
10819 }
10820 }
10821
10822
10823 //-----------------------------------------------------------------------------
10824 // [SECTION] VIEWPORTS, PLATFORM WINDOWS
10825 //-----------------------------------------------------------------------------
10826 // - GetMainViewport()
10827 // - FindViewportByID()
10828 // - FindViewportByPlatformHandle()
10829 // - SetCurrentViewport() [Internal]
10830 // - SetWindowViewport() [Internal]
10831 // - GetWindowAlwaysWantOwnViewport() [Internal]
10832 // - UpdateTryMergeWindowIntoHostViewport() [Internal]
10833 // - UpdateTryMergeWindowIntoHostViewports() [Internal]
10834 // - TranslateWindowsInViewport() [Internal]
10835 // - ScaleWindowsInViewport() [Internal]
10836 // - FindHoveredViewportFromPlatformWindowStack() [Internal]
10837 // - UpdateViewportsNewFrame() [Internal]
10838 // - UpdateViewportsEndFrame() [Internal]
10839 // - AddUpdateViewport() [Internal]
10840 // - UpdateSelectWindowViewport() [Internal]
10841 // - UpdatePlatformWindows()
10842 // - RenderPlatformWindowsDefault()
10843 // - FindPlatformMonitorForPos() [Internal]
10844 // - FindPlatformMonitorForRect() [Internal]
10845 // - UpdateViewportPlatformMonitor() [Internal]
10846 // - DestroyPlatformWindow() [Internal]
10847 // - DestroyPlatformWindows()
10848 //-----------------------------------------------------------------------------
10849
GetMainViewport()10850 ImGuiViewport* ImGui::GetMainViewport()
10851 {
10852 ImGuiContext& g = *GImGui;
10853 return g.Viewports[0];
10854 }
10855
FindViewportByID(ImGuiID id)10856 ImGuiViewport* ImGui::FindViewportByID(ImGuiID id)
10857 {
10858 ImGuiContext& g = *GImGui;
10859 for (int n = 0; n < g.Viewports.Size; n++)
10860 if (g.Viewports[n]->ID == id)
10861 return g.Viewports[n];
10862 return NULL;
10863 }
10864
FindViewportByPlatformHandle(void * platform_handle)10865 ImGuiViewport* ImGui::FindViewportByPlatformHandle(void* platform_handle)
10866 {
10867 ImGuiContext& g = *GImGui;
10868 for (int i = 0; i != g.Viewports.Size; i++)
10869 if (g.Viewports[i]->PlatformHandle == platform_handle)
10870 return g.Viewports[i];
10871 return NULL;
10872 }
10873
SetCurrentViewport(ImGuiWindow * current_window,ImGuiViewportP * viewport)10874 void ImGui::SetCurrentViewport(ImGuiWindow* current_window, ImGuiViewportP* viewport)
10875 {
10876 ImGuiContext& g = *GImGui;
10877 (void)current_window;
10878
10879 if (viewport)
10880 viewport->LastFrameActive = g.FrameCount;
10881 if (g.CurrentViewport == viewport)
10882 return;
10883 g.CurrentDpiScale = viewport ? viewport->DpiScale : 1.0f;
10884 g.CurrentViewport = viewport;
10885 //IMGUI_DEBUG_LOG_VIEWPORT("SetCurrentViewport changed '%s' 0x%08X\n", current_window ? current_window->Name : NULL, viewport ? viewport->ID : 0);
10886
10887 // Notify platform layer of viewport changes
10888 // FIXME-DPI: This is only currently used for experimenting with handling of multiple DPI
10889 if (g.CurrentViewport && g.PlatformIO.Platform_OnChangedViewport)
10890 g.PlatformIO.Platform_OnChangedViewport(g.CurrentViewport);
10891 }
10892
SetWindowViewport(ImGuiWindow * window,ImGuiViewportP * viewport)10893 static void SetWindowViewport(ImGuiWindow* window, ImGuiViewportP* viewport)
10894 {
10895 window->Viewport = viewport;
10896 window->ViewportId = viewport->ID;
10897 window->ViewportOwned = (viewport->Window == window);
10898 }
10899
GetWindowAlwaysWantOwnViewport(ImGuiWindow * window)10900 static bool ImGui::GetWindowAlwaysWantOwnViewport(ImGuiWindow* window)
10901 {
10902 // 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.
10903 ImGuiContext& g = *GImGui;
10904 if (g.IO.ConfigViewportsNoAutoMerge || (window->WindowClass.ViewportFlagsOverrideSet & ImGuiViewportFlags_NoAutoMerge))
10905 if (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable)
10906 if (!window->DockIsActive)
10907 if ((window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_Tooltip)) == 0)
10908 if ((window->Flags & ImGuiWindowFlags_Popup) == 0 || (window->Flags & ImGuiWindowFlags_Modal) != 0)
10909 return true;
10910 return false;
10911 }
10912
UpdateTryMergeWindowIntoHostViewport(ImGuiWindow * window,ImGuiViewportP * viewport)10913 static bool ImGui::UpdateTryMergeWindowIntoHostViewport(ImGuiWindow* window, ImGuiViewportP* viewport)
10914 {
10915 ImGuiContext& g = *GImGui;
10916 if (window->Viewport == viewport)
10917 return false;
10918 if ((viewport->Flags & ImGuiViewportFlags_CanHostOtherWindows) == 0)
10919 return false;
10920 if ((viewport->Flags & ImGuiViewportFlags_Minimized) != 0)
10921 return false;
10922 if (!viewport->GetMainRect().Contains(window->Rect()))
10923 return false;
10924 if (GetWindowAlwaysWantOwnViewport(window))
10925 return false;
10926
10927 for (int n = 0; n < g.Windows.Size; n++)
10928 {
10929 ImGuiWindow* window_behind = g.Windows[n];
10930 if (window_behind == window)
10931 break;
10932 if (window_behind->WasActive && window_behind->ViewportOwned && !(window_behind->Flags & ImGuiWindowFlags_ChildWindow))
10933 if (window_behind->Viewport->GetMainRect().Overlaps(window->Rect()))
10934 return false;
10935 }
10936
10937 // Move to the existing viewport, Move child/hosted windows as well (FIXME-OPT: iterate child)
10938 ImGuiViewportP* old_viewport = window->Viewport;
10939 if (window->ViewportOwned)
10940 for (int n = 0; n < g.Windows.Size; n++)
10941 if (g.Windows[n]->Viewport == old_viewport)
10942 SetWindowViewport(g.Windows[n], viewport);
10943 SetWindowViewport(window, viewport);
10944 BringWindowToDisplayFront(window);
10945
10946 return true;
10947 }
10948
UpdateTryMergeWindowIntoHostViewports(ImGuiWindow * window)10949 static bool ImGui::UpdateTryMergeWindowIntoHostViewports(ImGuiWindow* window)
10950 {
10951 ImGuiContext& g = *GImGui;
10952 return UpdateTryMergeWindowIntoHostViewport(window, g.Viewports[0]);
10953 }
10954
10955 // Translate imgui windows when a Host Viewport has been moved
10956 // (This additionally keeps windows at the same place when ImGuiConfigFlags_ViewportsEnable is toggled!)
TranslateWindowsInViewport(ImGuiViewportP * viewport,const ImVec2 & old_pos,const ImVec2 & new_pos)10957 void ImGui::TranslateWindowsInViewport(ImGuiViewportP* viewport, const ImVec2& old_pos, const ImVec2& new_pos)
10958 {
10959 ImGuiContext& g = *GImGui;
10960 IM_ASSERT(viewport->Window == NULL && (viewport->Flags & ImGuiViewportFlags_CanHostOtherWindows));
10961
10962 // 1) We test if ImGuiConfigFlags_ViewportsEnable was just toggled, which allows us to conveniently
10963 // translate imgui windows from OS-window-local to absolute coordinates or vice-versa.
10964 // 2) If it's not going to fit into the new size, keep it at same absolute position.
10965 // One problem with this is that most Win32 applications doesn't update their render while dragging,
10966 // and so the window will appear to teleport when releasing the mouse.
10967 const bool translate_all_windows = (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable) != (g.ConfigFlagsLastFrame & ImGuiConfigFlags_ViewportsEnable);
10968 ImRect test_still_fit_rect(old_pos, old_pos + viewport->Size);
10969 ImVec2 delta_pos = new_pos - old_pos;
10970 for (int window_n = 0; window_n < g.Windows.Size; window_n++) // FIXME-OPT
10971 if (translate_all_windows || (g.Windows[window_n]->Viewport == viewport && test_still_fit_rect.Contains(g.Windows[window_n]->Rect())))
10972 TranslateWindow(g.Windows[window_n], delta_pos);
10973 }
10974
10975 // Scale all windows (position, size). Use when e.g. changing DPI. (This is a lossy operation!)
ScaleWindowsInViewport(ImGuiViewportP * viewport,float scale)10976 void ImGui::ScaleWindowsInViewport(ImGuiViewportP* viewport, float scale)
10977 {
10978 ImGuiContext& g = *GImGui;
10979 if (viewport->Window)
10980 {
10981 ScaleWindow(viewport->Window, scale);
10982 }
10983 else
10984 {
10985 for (int i = 0; i != g.Windows.Size; i++)
10986 if (g.Windows[i]->Viewport == viewport)
10987 ScaleWindow(g.Windows[i], scale);
10988 }
10989 }
10990
10991 // If the back-end doesn't set MouseLastHoveredViewport or doesn't honor ImGuiViewportFlags_NoInputs, we do a search ourselves.
10992 // A) It won't take account of the possibility that non-imgui windows may be in-between our dragged window and our target window.
10993 // B) It requires Platform_GetWindowFocus to be implemented by back-end.
FindHoveredViewportFromPlatformWindowStack(const ImVec2 mouse_platform_pos)10994 static ImGuiViewportP* FindHoveredViewportFromPlatformWindowStack(const ImVec2 mouse_platform_pos)
10995 {
10996 ImGuiContext& g = *GImGui;
10997 ImGuiViewportP* best_candidate = NULL;
10998 for (int n = 0; n < g.Viewports.Size; n++)
10999 {
11000 ImGuiViewportP* viewport = g.Viewports[n];
11001 if (!(viewport->Flags & (ImGuiViewportFlags_NoInputs | ImGuiViewportFlags_Minimized)) && viewport->GetMainRect().Contains(mouse_platform_pos))
11002 if (best_candidate == NULL || best_candidate->LastFrontMostStampCount < viewport->LastFrontMostStampCount)
11003 best_candidate = viewport;
11004 }
11005 return best_candidate;
11006 }
11007
11008 // Update viewports and monitor infos
11009 // Note that this is running even if 'ImGuiConfigFlags_ViewportsEnable' is not set, in order to clear unused viewports (if any) and update monitor info.
UpdateViewportsNewFrame()11010 static void ImGui::UpdateViewportsNewFrame()
11011 {
11012 ImGuiContext& g = *GImGui;
11013 IM_ASSERT(g.PlatformIO.Viewports.Size <= g.Viewports.Size);
11014
11015 // Update Minimized status (we need it first in order to decide if we'll apply Pos/Size of the main viewport)
11016 const bool viewports_enabled = (g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable) != 0;
11017 if (viewports_enabled)
11018 {
11019 for (int n = 0; n < g.Viewports.Size; n++)
11020 {
11021 ImGuiViewportP* viewport = g.Viewports[n];
11022 const bool platform_funcs_available = viewport->PlatformWindowCreated;
11023 if (g.PlatformIO.Platform_GetWindowMinimized && platform_funcs_available)
11024 {
11025 bool minimized = g.PlatformIO.Platform_GetWindowMinimized(viewport);
11026 if (minimized)
11027 viewport->Flags |= ImGuiViewportFlags_Minimized;
11028 else
11029 viewport->Flags &= ~ImGuiViewportFlags_Minimized;
11030 }
11031 }
11032 }
11033
11034 // Create/update main viewport with current platform position.
11035 // FIXME-VIEWPORT: Size is driven by back-end/user code for backward-compatibility but we should aim to make this more consistent.
11036 ImGuiViewportP* main_viewport = g.Viewports[0];
11037 IM_ASSERT(main_viewport->ID == IMGUI_VIEWPORT_DEFAULT_ID);
11038 IM_ASSERT(main_viewport->Window == NULL);
11039 ImVec2 main_viewport_pos = viewports_enabled ? g.PlatformIO.Platform_GetWindowPos(main_viewport) : ImVec2(0.0f, 0.0f);
11040 ImVec2 main_viewport_size = g.IO.DisplaySize;
11041 if (viewports_enabled && (main_viewport->Flags & ImGuiViewportFlags_Minimized))
11042 {
11043 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)
11044 main_viewport_size = main_viewport->Size;
11045 }
11046 AddUpdateViewport(NULL, IMGUI_VIEWPORT_DEFAULT_ID, main_viewport_pos, main_viewport_size, ImGuiViewportFlags_CanHostOtherWindows);
11047
11048 g.CurrentDpiScale = 0.0f;
11049 g.CurrentViewport = NULL;
11050 g.MouseViewport = NULL;
11051 for (int n = 0; n < g.Viewports.Size; n++)
11052 {
11053 ImGuiViewportP* viewport = g.Viewports[n];
11054 viewport->Idx = n;
11055
11056 // Erase unused viewports
11057 if (n > 0 && viewport->LastFrameActive < g.FrameCount - 2)
11058 {
11059 // Clear references to this viewport in windows (window->ViewportId becomes the master data)
11060 for (int window_n = 0; window_n < g.Windows.Size; window_n++)
11061 if (g.Windows[window_n]->Viewport == viewport)
11062 {
11063 g.Windows[window_n]->Viewport = NULL;
11064 g.Windows[window_n]->ViewportOwned = false;
11065 }
11066 if (viewport == g.MouseLastHoveredViewport)
11067 g.MouseLastHoveredViewport = NULL;
11068 g.Viewports.erase(g.Viewports.Data + n);
11069
11070 // Destroy
11071 IMGUI_DEBUG_LOG_VIEWPORT("Delete Viewport %08X (%s)\n", viewport->ID, viewport->Window ? viewport->Window->Name : "n/a");
11072 DestroyPlatformWindow(viewport); // In most circumstances the platform window will already be destroyed here.
11073 IM_ASSERT(g.PlatformIO.Viewports.contains(viewport) == false);
11074 IM_DELETE(viewport);
11075 n--;
11076 continue;
11077 }
11078
11079 const bool platform_funcs_available = viewport->PlatformWindowCreated;
11080 if (viewports_enabled)
11081 {
11082 // Update Position and Size (from Platform Window to ImGui) if requested.
11083 // We do it early in the frame instead of waiting for UpdatePlatformWindows() to avoid a frame of lag when moving/resizing using OS facilities.
11084 if (!(viewport->Flags & ImGuiViewportFlags_Minimized) && platform_funcs_available)
11085 {
11086 if (viewport->PlatformRequestMove)
11087 viewport->Pos = viewport->LastPlatformPos = g.PlatformIO.Platform_GetWindowPos(viewport);
11088 if (viewport->PlatformRequestResize)
11089 viewport->Size = viewport->LastPlatformSize = g.PlatformIO.Platform_GetWindowSize(viewport);
11090 }
11091 }
11092
11093 // Update/copy monitor info
11094 UpdateViewportPlatformMonitor(viewport);
11095
11096 // Lock down space taken by menu bars and status bars, reset the offset for fucntions like BeginMainMenuBar() to alter them again.
11097 viewport->WorkOffsetMin = viewport->CurrWorkOffsetMin;
11098 viewport->WorkOffsetMax = viewport->CurrWorkOffsetMax;
11099 viewport->CurrWorkOffsetMin = viewport->CurrWorkOffsetMax = ImVec2(0.0f, 0.0f);
11100
11101 // Reset alpha every frame. Users of transparency (docking) needs to request a lower alpha back.
11102 viewport->Alpha = 1.0f;
11103
11104 // Translate imgui windows when a Host Viewport has been moved
11105 // (This additionally keeps windows at the same place when ImGuiConfigFlags_ViewportsEnable is toggled!)
11106 const ImVec2 viewport_delta_pos = viewport->Pos - viewport->LastPos;
11107 if ((viewport->Flags & ImGuiViewportFlags_CanHostOtherWindows) && (viewport_delta_pos.x != 0.0f || viewport_delta_pos.y != 0.0f))
11108 TranslateWindowsInViewport(viewport, viewport->LastPos, viewport->Pos);
11109
11110 // Update DPI scale
11111 float new_dpi_scale;
11112 if (g.PlatformIO.Platform_GetWindowDpiScale && platform_funcs_available)
11113 new_dpi_scale = g.PlatformIO.Platform_GetWindowDpiScale(viewport);
11114 else if (viewport->PlatformMonitor != -1)
11115 new_dpi_scale = g.PlatformIO.Monitors[viewport->PlatformMonitor].DpiScale;
11116 else
11117 new_dpi_scale = (viewport->DpiScale != 0.0f) ? viewport->DpiScale : 1.0f;
11118 if (viewport->DpiScale != 0.0f && new_dpi_scale != viewport->DpiScale)
11119 {
11120 float scale_factor = new_dpi_scale / viewport->DpiScale;
11121 if (g.IO.ConfigFlags & ImGuiConfigFlags_DpiEnableScaleViewports)
11122 ScaleWindowsInViewport(viewport, scale_factor);
11123 //if (viewport == GetMainViewport())
11124 // g.PlatformInterface.SetWindowSize(viewport, viewport->Size * scale_factor);
11125
11126 // Scale our window moving pivot so that the window will rescale roughly around the mouse position.
11127 // FIXME-VIEWPORT: This currently creates a resizing feedback loop when a window is straddling a DPI transition border.
11128 // (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.)
11129 //if (g.MovingWindow != NULL && g.MovingWindow->Viewport == viewport)
11130 // g.ActiveIdClickOffset = ImFloor(g.ActiveIdClickOffset * scale_factor);
11131 }
11132 viewport->DpiScale = new_dpi_scale;
11133 }
11134
11135 if (!viewports_enabled)
11136 {
11137 g.MouseViewport = main_viewport;
11138 return;
11139 }
11140
11141 // Mouse handling: decide on the actual mouse viewport for this frame between the active/focused viewport and the hovered viewport.
11142 // Note that 'viewport_hovered' should skip over any viewport that has the ImGuiViewportFlags_NoInputs flags set.
11143 ImGuiViewportP* viewport_hovered = NULL;
11144 if (g.IO.BackendFlags & ImGuiBackendFlags_HasMouseHoveredViewport)
11145 {
11146 viewport_hovered = g.IO.MouseHoveredViewport ? (ImGuiViewportP*)FindViewportByID(g.IO.MouseHoveredViewport) : NULL;
11147 if (viewport_hovered && (viewport_hovered->Flags & ImGuiViewportFlags_NoInputs))
11148 {
11149 // Back-end failed at honoring its contract if it returned a viewport with the _NoInputs flag.
11150 IM_ASSERT(0);
11151 viewport_hovered = FindHoveredViewportFromPlatformWindowStack(g.IO.MousePos);
11152 }
11153 }
11154 else
11155 {
11156 // If the back-end doesn't know how to honor ImGuiViewportFlags_NoInputs, we do a search ourselves. Note that this search:
11157 // A) won't take account of the possibility that non-imgui windows may be in-between our dragged window and our target window.
11158 // B) uses LastFrameAsRefViewport as a flawed replacement for the last time a window was focused (we could/should fix that by introducing Focus functions in PlatformIO)
11159 viewport_hovered = FindHoveredViewportFromPlatformWindowStack(g.IO.MousePos);
11160 }
11161 if (viewport_hovered != NULL)
11162 g.MouseLastHoveredViewport = viewport_hovered;
11163 else if (g.MouseLastHoveredViewport == NULL)
11164 g.MouseLastHoveredViewport = g.Viewports[0];
11165
11166 // Update mouse reference viewport
11167 // (when moving a window we aim at its viewport, but this will be overwritten below if we go in drag and drop mode)
11168 if (g.MovingWindow)
11169 g.MouseViewport = g.MovingWindow->Viewport;
11170 else
11171 g.MouseViewport = g.MouseLastHoveredViewport;
11172
11173 // When dragging something, always refer to the last hovered viewport.
11174 // - when releasing a moving window we will revert to aiming behind (at viewport_hovered)
11175 // - 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)
11176 // - 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.
11177 const bool is_mouse_dragging_with_an_expected_destination = g.DragDropActive;
11178 if (is_mouse_dragging_with_an_expected_destination && viewport_hovered == NULL)
11179 viewport_hovered = g.MouseLastHoveredViewport;
11180 if (is_mouse_dragging_with_an_expected_destination || g.ActiveId == 0 || !IsAnyMouseDown())
11181 if (viewport_hovered != NULL && viewport_hovered != g.MouseViewport && !(viewport_hovered->Flags & ImGuiViewportFlags_NoInputs))
11182 g.MouseViewport = viewport_hovered;
11183
11184 IM_ASSERT(g.MouseViewport != NULL);
11185 }
11186
11187 // Update user-facing viewport list (g.Viewports -> g.PlatformIO.Viewports after filtering out some)
UpdateViewportsEndFrame()11188 static void ImGui::UpdateViewportsEndFrame()
11189 {
11190 ImGuiContext& g = *GImGui;
11191 g.PlatformIO.MainViewport = g.Viewports[0];
11192 g.PlatformIO.Viewports.resize(0);
11193 for (int i = 0; i < g.Viewports.Size; i++)
11194 {
11195 ImGuiViewportP* viewport = g.Viewports[i];
11196 viewport->LastPos = viewport->Pos;
11197 if (viewport->LastFrameActive < g.FrameCount || viewport->Size.x <= 0.0f || viewport->Size.y <= 0.0f)
11198 if (i > 0) // Always include main viewport in the list
11199 continue;
11200 if (viewport->Window && !IsWindowActiveAndVisible(viewport->Window))
11201 continue;
11202 if (i > 0)
11203 IM_ASSERT(viewport->Window != NULL);
11204 g.PlatformIO.Viewports.push_back(viewport);
11205 }
11206 g.Viewports[0]->ClearRequestFlags(); // Clear main viewport flags because UpdatePlatformWindows() won't do it and may not even be called
11207 }
11208
11209 // FIXME: We should ideally refactor the system to call this every frame (we currently don't)
AddUpdateViewport(ImGuiWindow * window,ImGuiID id,const ImVec2 & pos,const ImVec2 & size,ImGuiViewportFlags flags)11210 ImGuiViewportP* ImGui::AddUpdateViewport(ImGuiWindow* window, ImGuiID id, const ImVec2& pos, const ImVec2& size, ImGuiViewportFlags flags)
11211 {
11212 ImGuiContext& g = *GImGui;
11213 IM_ASSERT(id != 0);
11214
11215 if (window != NULL)
11216 {
11217 if (g.MovingWindow && g.MovingWindow->RootWindow == window)
11218 flags |= ImGuiViewportFlags_NoInputs | ImGuiViewportFlags_NoFocusOnAppearing;
11219 if ((window->Flags & ImGuiWindowFlags_NoMouseInputs) && (window->Flags & ImGuiWindowFlags_NoNavInputs))
11220 flags |= ImGuiViewportFlags_NoInputs;
11221 if (window->Flags & ImGuiWindowFlags_NoFocusOnAppearing)
11222 flags |= ImGuiViewportFlags_NoFocusOnAppearing;
11223 }
11224
11225 ImGuiViewportP* viewport = (ImGuiViewportP*)FindViewportByID(id);
11226 if (viewport)
11227 {
11228 if (!viewport->PlatformRequestMove)
11229 viewport->Pos = pos;
11230 if (!viewport->PlatformRequestResize)
11231 viewport->Size = size;
11232 viewport->Flags = flags | (viewport->Flags & ImGuiViewportFlags_Minimized); // Preserve existing flags
11233 }
11234 else
11235 {
11236 // New viewport
11237 viewport = IM_NEW(ImGuiViewportP)();
11238 viewport->ID = id;
11239 viewport->Idx = g.Viewports.Size;
11240 viewport->Pos = viewport->LastPos = pos;
11241 viewport->Size = size;
11242 viewport->Flags = flags;
11243 UpdateViewportPlatformMonitor(viewport);
11244 g.Viewports.push_back(viewport);
11245 IMGUI_DEBUG_LOG_VIEWPORT("Add Viewport %08X (%s)\n", id, window->Name);
11246
11247 // We normally setup for all viewports in NewFrame() but here need to handle the mid-frame creation of a new viewport.
11248 // We need to extend the fullscreen clip rect so the OverlayDrawList clip is correct for that the first frame
11249 g.DrawListSharedData.ClipRectFullscreen.x = ImMin(g.DrawListSharedData.ClipRectFullscreen.x, viewport->Pos.x);
11250 g.DrawListSharedData.ClipRectFullscreen.y = ImMin(g.DrawListSharedData.ClipRectFullscreen.y, viewport->Pos.y);
11251 g.DrawListSharedData.ClipRectFullscreen.z = ImMax(g.DrawListSharedData.ClipRectFullscreen.z, viewport->Pos.x + viewport->Size.x);
11252 g.DrawListSharedData.ClipRectFullscreen.w = ImMax(g.DrawListSharedData.ClipRectFullscreen.w, viewport->Pos.y + viewport->Size.y);
11253
11254 // Store initial DpiScale before the OS platform window creation, based on expected monitor data.
11255 // This is so we can select an appropriate font size on the first frame of our window lifetime
11256 if (viewport->PlatformMonitor != -1)
11257 viewport->DpiScale = g.PlatformIO.Monitors[viewport->PlatformMonitor].DpiScale;
11258 }
11259
11260 viewport->Window = window;
11261 viewport->LastFrameActive = g.FrameCount;
11262 IM_ASSERT(window == NULL || viewport->ID == window->ID);
11263
11264 if (window != NULL)
11265 window->ViewportOwned = true;
11266
11267 return viewport;
11268 }
11269
11270 // FIXME-VIEWPORT: This is all super messy and ought to be clarified or rewritten.
UpdateSelectWindowViewport(ImGuiWindow * window)11271 static void ImGui::UpdateSelectWindowViewport(ImGuiWindow* window)
11272 {
11273 ImGuiContext& g = *GImGui;
11274 ImGuiWindowFlags flags = window->Flags;
11275 window->ViewportAllowPlatformMonitorExtend = -1;
11276
11277 // Restore main viewport if multi-viewport is not supported by the back-end
11278 ImGuiViewportP* main_viewport = g.Viewports[0];
11279 if (!(g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable))
11280 {
11281 SetWindowViewport(window, main_viewport);
11282 return;
11283 }
11284 window->ViewportOwned = false;
11285
11286 // Appearing popups reset their viewport so they can inherit again
11287 if ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) && window->Appearing)
11288 {
11289 window->Viewport = NULL;
11290 window->ViewportId = 0;
11291 }
11292
11293 if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasViewport) == 0)
11294 {
11295 // By default inherit from parent window
11296 if (window->Viewport == NULL && window->ParentWindow && !window->ParentWindow->IsFallbackWindow)
11297 window->Viewport = window->ParentWindow->Viewport;
11298
11299 // 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
11300 if (window->Viewport == NULL && window->ViewportId != 0)
11301 {
11302 window->Viewport = (ImGuiViewportP*)FindViewportByID(window->ViewportId);
11303 if (window->Viewport == NULL && window->ViewportPos.x != FLT_MAX && window->ViewportPos.y != FLT_MAX)
11304 window->Viewport = AddUpdateViewport(window, window->ID, window->ViewportPos, window->Size, ImGuiViewportFlags_None);
11305 }
11306 }
11307
11308 bool lock_viewport = false;
11309 if (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasViewport)
11310 {
11311 // Code explicitly request a viewport
11312 window->Viewport = (ImGuiViewportP*)FindViewportByID(g.NextWindowData.ViewportId);
11313 window->ViewportId = g.NextWindowData.ViewportId; // Store ID even if Viewport isn't resolved yet.
11314 lock_viewport = true;
11315 }
11316 else if ((flags & ImGuiWindowFlags_ChildWindow) || (flags & ImGuiWindowFlags_ChildMenu))
11317 {
11318 // Always inherit viewport from parent window
11319 window->Viewport = window->ParentWindow->Viewport;
11320 }
11321 else if (flags & ImGuiWindowFlags_Tooltip)
11322 {
11323 window->Viewport = g.MouseViewport;
11324 }
11325 else if (GetWindowAlwaysWantOwnViewport(window))
11326 {
11327 window->Viewport = AddUpdateViewport(window, window->ID, window->Pos, window->Size, ImGuiViewportFlags_None);
11328 }
11329 else if (g.MovingWindow && g.MovingWindow->RootWindow == window && IsMousePosValid())
11330 {
11331 if (window->Viewport != NULL && window->Viewport->Window == window)
11332 window->Viewport = AddUpdateViewport(window, window->ID, window->Pos, window->Size, ImGuiViewportFlags_None);
11333 }
11334 else
11335 {
11336 // Merge into host viewport?
11337 // We cannot test window->ViewportOwned as it set lower in the function.
11338 bool try_to_merge_into_host_viewport = (window->Viewport && window == window->Viewport->Window && g.ActiveId == 0);
11339 if (try_to_merge_into_host_viewport)
11340 UpdateTryMergeWindowIntoHostViewports(window);
11341 }
11342
11343 // Fallback to default viewport
11344 if (window->Viewport == NULL)
11345 window->Viewport = main_viewport;
11346
11347 // Mark window as allowed to protrude outside of its viewport and into the current monitor
11348 if (!lock_viewport)
11349 {
11350 if (flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup))
11351 {
11352 // We need to take account of the possibility that mouse may become invalid.
11353 // Popups/Tooltip always set ViewportAllowPlatformMonitorExtend so GetWindowAllowedExtentRect() will return full monitor bounds.
11354 ImVec2 mouse_ref = (flags & ImGuiWindowFlags_Tooltip) ? g.IO.MousePos : g.BeginPopupStack.back().OpenMousePos;
11355 bool use_mouse_ref = (g.NavDisableHighlight || !g.NavDisableMouseHover || !g.NavWindow);
11356 bool mouse_valid = IsMousePosValid(&mouse_ref);
11357 if ((window->Appearing || (flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_ChildMenu))) && (!use_mouse_ref || mouse_valid))
11358 window->ViewportAllowPlatformMonitorExtend = FindPlatformMonitorForPos((use_mouse_ref && mouse_valid) ? mouse_ref : NavCalcPreferredRefPos());
11359 else
11360 window->ViewportAllowPlatformMonitorExtend = window->Viewport->PlatformMonitor;
11361 }
11362 else if (window->Viewport && window != window->Viewport->Window && window->Viewport->Window && !(flags & ImGuiWindowFlags_ChildWindow))
11363 {
11364 // When called from Begin() we don't have access to a proper version of the Hidden flag yet, so we replicate this code.
11365 const bool will_be_visible = (window->DockIsActive && !window->DockTabIsVisible) ? false : true;
11366 if ((window->Flags & ImGuiWindowFlags_DockNodeHost) && window->Viewport->LastFrameActive < g.FrameCount && will_be_visible)
11367 {
11368 // Steal/transfer ownership
11369 IMGUI_DEBUG_LOG_VIEWPORT("Window '%s' steal Viewport %08X from Window '%s'\n", window->Name, window->Viewport->ID, window->Viewport->Window->Name);
11370 window->Viewport->Window = window;
11371 window->Viewport->ID = window->ID;
11372 window->Viewport->LastNameHash = 0;
11373 }
11374 else if (!UpdateTryMergeWindowIntoHostViewports(window)) // Merge?
11375 {
11376 // New viewport
11377 window->Viewport = AddUpdateViewport(window, window->ID, window->Pos, window->Size, ImGuiViewportFlags_NoFocusOnAppearing);
11378 }
11379 }
11380 else if (window->ViewportAllowPlatformMonitorExtend < 0 && (flags & ImGuiWindowFlags_ChildWindow) == 0)
11381 {
11382 // Regular (non-child, non-popup) windows by default are also allowed to protrude
11383 // Child windows are kept contained within their parent.
11384 window->ViewportAllowPlatformMonitorExtend = window->Viewport->PlatformMonitor;
11385 }
11386 }
11387
11388 // Update flags
11389 window->ViewportOwned = (window == window->Viewport->Window);
11390 window->ViewportId = window->Viewport->ID;
11391
11392 // If the OS window has a title bar, hide our imgui title bar
11393 //if (window->ViewportOwned && !(window->Viewport->Flags & ImGuiViewportFlags_NoDecoration))
11394 // window->Flags |= ImGuiWindowFlags_NoTitleBar;
11395 }
11396
11397 // Called by user at the end of the main loop, after EndFrame()
11398 // This will handle the creation/update of all OS windows via function defined in the ImGuiPlatformIO api.
UpdatePlatformWindows()11399 void ImGui::UpdatePlatformWindows()
11400 {
11401 ImGuiContext& g = *GImGui;
11402 IM_ASSERT(g.FrameCountEnded == g.FrameCount && "Forgot to call Render() or EndFrame() before UpdatePlatformWindows()?");
11403 IM_ASSERT(g.FrameCountPlatformEnded < g.FrameCount);
11404 g.FrameCountPlatformEnded = g.FrameCount;
11405 if (!(g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable))
11406 return;
11407
11408 // Create/resize/destroy platform windows to match each active viewport.
11409 // Skip the main viewport (index 0), which is always fully handled by the application!
11410 for (int i = 1; i < g.Viewports.Size; i++)
11411 {
11412 ImGuiViewportP* viewport = g.Viewports[i];
11413
11414 // Destroy platform window if the viewport hasn't been submitted or if it is hosting a hidden window
11415 // (the implicit/fallback Debug##Default window will be registering its viewport then be disabled, causing a dummy DestroyPlatformWindow to be made each frame)
11416 bool destroy_platform_window = false;
11417 destroy_platform_window |= (viewport->LastFrameActive < g.FrameCount - 1);
11418 destroy_platform_window |= (viewport->Window && !IsWindowActiveAndVisible(viewport->Window));
11419 if (destroy_platform_window)
11420 {
11421 DestroyPlatformWindow(viewport);
11422 continue;
11423 }
11424
11425 // New windows that appears directly in a new viewport won't always have a size on their first frame
11426 if (viewport->LastFrameActive < g.FrameCount || viewport->Size.x <= 0 || viewport->Size.y <= 0)
11427 continue;
11428
11429 // Create window
11430 bool is_new_platform_window = (viewport->PlatformWindowCreated == false);
11431 if (is_new_platform_window)
11432 {
11433 IMGUI_DEBUG_LOG_VIEWPORT("Create Platform Window %08X (%s)\n", viewport->ID, viewport->Window ? viewport->Window->Name : "n/a");
11434 g.PlatformIO.Platform_CreateWindow(viewport);
11435 if (g.PlatformIO.Renderer_CreateWindow != NULL)
11436 g.PlatformIO.Renderer_CreateWindow(viewport);
11437 viewport->LastNameHash = 0;
11438 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?)
11439 viewport->LastRendererSize = viewport->Size; // We don't need to call Renderer_SetWindowSize() as it is expected Renderer_CreateWindow() already did it.
11440 viewport->PlatformWindowCreated = true;
11441 }
11442
11443 // Apply Position and Size (from ImGui to Platform/Renderer back-ends)
11444 if ((viewport->LastPlatformPos.x != viewport->Pos.x || viewport->LastPlatformPos.y != viewport->Pos.y) && !viewport->PlatformRequestMove)
11445 g.PlatformIO.Platform_SetWindowPos(viewport, viewport->Pos);
11446 if ((viewport->LastPlatformSize.x != viewport->Size.x || viewport->LastPlatformSize.y != viewport->Size.y) && !viewport->PlatformRequestResize)
11447 g.PlatformIO.Platform_SetWindowSize(viewport, viewport->Size);
11448 if ((viewport->LastRendererSize.x != viewport->Size.x || viewport->LastRendererSize.y != viewport->Size.y) && g.PlatformIO.Renderer_SetWindowSize)
11449 g.PlatformIO.Renderer_SetWindowSize(viewport, viewport->Size);
11450 viewport->LastPlatformPos = viewport->Pos;
11451 viewport->LastPlatformSize = viewport->LastRendererSize = viewport->Size;
11452
11453 // Update title bar (if it changed)
11454 if (ImGuiWindow* window_for_title = GetWindowForTitleDisplay(viewport->Window))
11455 {
11456 const char* title_begin = window_for_title->Name;
11457 char* title_end = (char*)(intptr_t)FindRenderedTextEnd(title_begin);
11458 const ImGuiID title_hash = ImHashStr(title_begin, title_end - title_begin);
11459 if (viewport->LastNameHash != title_hash)
11460 {
11461 char title_end_backup_c = *title_end;
11462 *title_end = 0; // Cut existing buffer short instead of doing an alloc/free, no small gain.
11463 g.PlatformIO.Platform_SetWindowTitle(viewport, title_begin);
11464 *title_end = title_end_backup_c;
11465 viewport->LastNameHash = title_hash;
11466 }
11467 }
11468
11469 // Update alpha (if it changed)
11470 if (viewport->LastAlpha != viewport->Alpha && g.PlatformIO.Platform_SetWindowAlpha)
11471 g.PlatformIO.Platform_SetWindowAlpha(viewport, viewport->Alpha);
11472 viewport->LastAlpha = viewport->Alpha;
11473
11474 // Optional, general purpose call to allow the back-end to perform general book-keeping even if things haven't changed.
11475 if (g.PlatformIO.Platform_UpdateWindow)
11476 g.PlatformIO.Platform_UpdateWindow(viewport);
11477
11478 if (is_new_platform_window)
11479 {
11480 // 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)
11481 if (g.FrameCount < 3)
11482 viewport->Flags |= ImGuiViewportFlags_NoFocusOnAppearing;
11483
11484 // Show window
11485 g.PlatformIO.Platform_ShowWindow(viewport);
11486
11487 // Even without focus, we assume the window becomes front-most.
11488 // This is useful for our platform z-order heuristic when io.MouseHoveredViewport is not available.
11489 if (viewport->LastFrontMostStampCount != g.ViewportFrontMostStampCount)
11490 viewport->LastFrontMostStampCount = ++g.ViewportFrontMostStampCount;
11491 }
11492
11493 // Clear request flags
11494 viewport->ClearRequestFlags();
11495 }
11496
11497 // Update our implicit z-order knowledge of platform windows, which is used when the back-end cannot provide io.MouseHoveredViewport.
11498 // When setting Platform_GetWindowFocus, it is expected that the platform back-end can handle calls without crashing if it doesn't have data stored.
11499 if (g.PlatformIO.Platform_GetWindowFocus != NULL)
11500 {
11501 ImGuiViewportP* focused_viewport = NULL;
11502 for (int n = 0; n < g.Viewports.Size && focused_viewport == NULL; n++)
11503 {
11504 ImGuiViewportP* viewport = g.Viewports[n];
11505 if (viewport->PlatformWindowCreated)
11506 if (g.PlatformIO.Platform_GetWindowFocus(viewport))
11507 focused_viewport = viewport;
11508 }
11509 if (focused_viewport && g.PlatformLastFocusedViewport != focused_viewport->ID)
11510 {
11511 if (focused_viewport->LastFrontMostStampCount != g.ViewportFrontMostStampCount)
11512 focused_viewport->LastFrontMostStampCount = ++g.ViewportFrontMostStampCount;
11513 g.PlatformLastFocusedViewport = focused_viewport->ID;
11514 }
11515 }
11516 }
11517
11518 // This is a default/basic function for performing the rendering/swap of multiple Platform Windows.
11519 // Custom renderers may prefer to not call this function at all, and instead iterate the publicly exposed platform data and handle rendering/sync themselves.
11520 // The Render/Swap functions stored in ImGuiPlatformIO are merely here to allow for this helper to exist, but you can do it yourself:
11521 //
11522 // ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
11523 // for (int i = 1; i < platform_io.Viewports.Size; i++)
11524 // if ((platform_io.Viewports[i]->Flags & ImGuiViewportFlags_Minimized) == 0)
11525 // MyRenderFunction(platform_io.Viewports[i], my_args);
11526 // for (int i = 1; i < platform_io.Viewports.Size; i++)
11527 // if ((platform_io.Viewports[i]->Flags & ImGuiViewportFlags_Minimized) == 0)
11528 // MySwapBufferFunction(platform_io.Viewports[i], my_args);
11529 //
RenderPlatformWindowsDefault(void * platform_render_arg,void * renderer_render_arg)11530 void ImGui::RenderPlatformWindowsDefault(void* platform_render_arg, void* renderer_render_arg)
11531 {
11532 // Skip the main viewport (index 0), which is always fully handled by the application!
11533 ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
11534 for (int i = 1; i < platform_io.Viewports.Size; i++)
11535 {
11536 ImGuiViewport* viewport = platform_io.Viewports[i];
11537 if (viewport->Flags & ImGuiViewportFlags_Minimized)
11538 continue;
11539 if (platform_io.Platform_RenderWindow) platform_io.Platform_RenderWindow(viewport, platform_render_arg);
11540 if (platform_io.Renderer_RenderWindow) platform_io.Renderer_RenderWindow(viewport, renderer_render_arg);
11541 }
11542 for (int i = 1; i < platform_io.Viewports.Size; i++)
11543 {
11544 ImGuiViewport* viewport = platform_io.Viewports[i];
11545 if (viewport->Flags & ImGuiViewportFlags_Minimized)
11546 continue;
11547 if (platform_io.Platform_SwapBuffers) platform_io.Platform_SwapBuffers(viewport, platform_render_arg);
11548 if (platform_io.Renderer_SwapBuffers) platform_io.Renderer_SwapBuffers(viewport, renderer_render_arg);
11549 }
11550 }
11551
FindPlatformMonitorForPos(const ImVec2 & pos)11552 static int ImGui::FindPlatformMonitorForPos(const ImVec2& pos)
11553 {
11554 ImGuiContext& g = *GImGui;
11555 for (int monitor_n = 0; monitor_n < g.PlatformIO.Monitors.Size; monitor_n++)
11556 {
11557 const ImGuiPlatformMonitor& monitor = g.PlatformIO.Monitors[monitor_n];
11558 if (ImRect(monitor.MainPos, monitor.MainPos + monitor.MainSize).Contains(pos))
11559 return monitor_n;
11560 }
11561 return -1;
11562 }
11563
11564 // Search for the monitor with the largest intersection area with the given rectangle
11565 // We generally try to avoid searching loops but the monitor count should be very small here
11566 // FIXME-OPT: We could test the last monitor used for that viewport first, and early
FindPlatformMonitorForRect(const ImRect & rect)11567 static int ImGui::FindPlatformMonitorForRect(const ImRect& rect)
11568 {
11569 ImGuiContext& g = *GImGui;
11570
11571 const int monitor_count = g.PlatformIO.Monitors.Size;
11572 if (monitor_count <= 1)
11573 return monitor_count - 1;
11574
11575 // 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.
11576 // This is necessary for tooltips which always resize down to zero at first.
11577 const float surface_threshold = ImMax(rect.GetWidth() * rect.GetHeight() * 0.5f, 1.0f);
11578 int best_monitor_n = -1;
11579 float best_monitor_surface = 0.001f;
11580
11581 for (int monitor_n = 0; monitor_n < g.PlatformIO.Monitors.Size && best_monitor_surface < surface_threshold; monitor_n++)
11582 {
11583 const ImGuiPlatformMonitor& monitor = g.PlatformIO.Monitors[monitor_n];
11584 const ImRect monitor_rect = ImRect(monitor.MainPos, monitor.MainPos + monitor.MainSize);
11585 if (monitor_rect.Contains(rect))
11586 return monitor_n;
11587 ImRect overlapping_rect = rect;
11588 overlapping_rect.ClipWithFull(monitor_rect);
11589 float overlapping_surface = overlapping_rect.GetWidth() * overlapping_rect.GetHeight();
11590 if (overlapping_surface < best_monitor_surface)
11591 continue;
11592 best_monitor_surface = overlapping_surface;
11593 best_monitor_n = monitor_n;
11594 }
11595 return best_monitor_n;
11596 }
11597
11598 // Update monitor from viewport rectangle (we'll use this info to clamp windows and save windows lost in a removed monitor)
UpdateViewportPlatformMonitor(ImGuiViewportP * viewport)11599 static void ImGui::UpdateViewportPlatformMonitor(ImGuiViewportP* viewport)
11600 {
11601 viewport->PlatformMonitor = (short)FindPlatformMonitorForRect(viewport->GetMainRect());
11602 }
11603
DestroyPlatformWindow(ImGuiViewportP * viewport)11604 void ImGui::DestroyPlatformWindow(ImGuiViewportP* viewport)
11605 {
11606 ImGuiContext& g = *GImGui;
11607 if (viewport->PlatformWindowCreated)
11608 {
11609 if (g.PlatformIO.Renderer_DestroyWindow)
11610 g.PlatformIO.Renderer_DestroyWindow(viewport);
11611 if (g.PlatformIO.Platform_DestroyWindow)
11612 g.PlatformIO.Platform_DestroyWindow(viewport);
11613 IM_ASSERT(viewport->RendererUserData == NULL && viewport->PlatformUserData == NULL);
11614
11615 // Don't clear PlatformWindowCreated for the main viewport, as we initially set that up to true in Initialize()
11616 // The right-er way may be to leave it to the back-end to set this flag all-together, and made the flag public.
11617 if (viewport->ID != IMGUI_VIEWPORT_DEFAULT_ID)
11618 viewport->PlatformWindowCreated = false;
11619 }
11620 else
11621 {
11622 IM_ASSERT(viewport->RendererUserData == NULL && viewport->PlatformUserData == NULL && viewport->PlatformHandle == NULL);
11623 }
11624 viewport->RendererUserData = viewport->PlatformUserData = viewport->PlatformHandle = NULL;
11625 viewport->ClearRequestFlags();
11626 }
11627
DestroyPlatformWindows()11628 void ImGui::DestroyPlatformWindows()
11629 {
11630 // We call the destroy window on every viewport (including the main viewport, index 0) to give a chance to the back-end
11631 // to clear any data they may have stored in e.g. PlatformUserData, RendererUserData.
11632 // It is convenient for the platform back-end code to store something in the main viewport, in order for e.g. the mouse handling
11633 // code to operator a consistent manner.
11634 // It is expected that the back-end can handle calls to Renderer_DestroyWindow/Platform_DestroyWindow without
11635 // crashing if it doesn't have data stored.
11636 ImGuiContext& g = *GImGui;
11637 for (int i = 0; i < g.Viewports.Size; i++)
11638 DestroyPlatformWindow(g.Viewports[i]);
11639 }
11640
11641
11642 //-----------------------------------------------------------------------------
11643 // [SECTION] DOCKING
11644 //-----------------------------------------------------------------------------
11645 // Docking: Internal Types
11646 // Docking: Forward Declarations
11647 // Docking: ImGuiDockContext
11648 // Docking: ImGuiDockContext Docking/Undocking functions
11649 // Docking: ImGuiDockNode
11650 // Docking: ImGuiDockNode Tree manipulation functions
11651 // Docking: Public Functions (SetWindowDock, DockSpace, DockSpaceOverViewport)
11652 // Docking: Builder Functions
11653 // Docking: Begin/End Support Functions (called from Begin/End)
11654 // Docking: Settings
11655 //-----------------------------------------------------------------------------
11656
11657 //-----------------------------------------------------------------------------
11658 // Docking: Internal Types
11659 //-----------------------------------------------------------------------------
11660 // - ImGuiDockRequestType
11661 // - ImGuiDockRequest
11662 // - ImGuiDockPreviewData
11663 // - ImGuiDockNodeSettings
11664 // - ImGuiDockContext
11665 //-----------------------------------------------------------------------------
11666
11667 enum ImGuiDockRequestType
11668 {
11669 ImGuiDockRequestType_None = 0,
11670 ImGuiDockRequestType_Dock,
11671 ImGuiDockRequestType_Undock,
11672 ImGuiDockRequestType_Split // Split is the same as Dock but without a DockPayload
11673 };
11674
11675 struct ImGuiDockRequest
11676 {
11677 ImGuiDockRequestType Type;
11678 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)
11679 ImGuiDockNode* DockTargetNode; // Destination/Target Node to dock into
11680 ImGuiWindow* DockPayload; // Source/Payload window to dock (may be a loose window or a DockNode), [Optional]
11681 ImGuiDir DockSplitDir;
11682 float DockSplitRatio;
11683 bool DockSplitOuter;
11684 ImGuiWindow* UndockTargetWindow;
11685 ImGuiDockNode* UndockTargetNode;
11686
ImGuiDockRequestImGuiDockRequest11687 ImGuiDockRequest()
11688 {
11689 Type = ImGuiDockRequestType_None;
11690 DockTargetWindow = DockPayload = UndockTargetWindow = NULL;
11691 DockTargetNode = UndockTargetNode = NULL;
11692 DockSplitDir = ImGuiDir_None;
11693 DockSplitRatio = 0.5f;
11694 DockSplitOuter = false;
11695 }
11696 };
11697
11698 struct ImGuiDockPreviewData
11699 {
11700 ImGuiDockNode FutureNode;
11701 bool IsDropAllowed;
11702 bool IsCenterAvailable;
11703 bool IsSidesAvailable; // Hold your breath, grammar freaks..
11704 bool IsSplitDirExplicit; // Set when hovered the drop rect (vs. implicit SplitDir==None when hovered the window)
11705 ImGuiDockNode* SplitNode;
11706 ImGuiDir SplitDir;
11707 float SplitRatio;
11708 ImRect DropRectsDraw[ImGuiDir_COUNT + 1]; // May be slightly different from hit-testing drop rects used in DockNodeCalcDropRects()
11709
ImGuiDockPreviewDataImGuiDockPreviewData11710 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); }
11711 };
11712
11713 // Persistent Settings data, stored contiguously in SettingsNodes (sizeof() ~32 bytes)
11714 struct ImGuiDockNodeSettings
11715 {
11716 ImGuiID ID;
11717 ImGuiID ParentNodeId;
11718 ImGuiID ParentWindowId;
11719 ImGuiID SelectedWindowId;
11720 signed char SplitAxis;
11721 char Depth;
11722 ImGuiDockNodeFlags Flags; // NB: We save individual flags one by one in ascii format (ImGuiDockNodeFlags_SavedFlagsMask_)
11723 ImVec2ih Pos;
11724 ImVec2ih Size;
11725 ImVec2ih SizeRef;
ImGuiDockNodeSettingsImGuiDockNodeSettings11726 ImGuiDockNodeSettings() { ID = ParentNodeId = ParentWindowId = SelectedWindowId = 0; SplitAxis = ImGuiAxis_None; Depth = 0; Flags = ImGuiDockNodeFlags_None; }
11727 };
11728
11729 //-----------------------------------------------------------------------------
11730 // Docking: Forward Declarations
11731 //-----------------------------------------------------------------------------
11732
11733 namespace ImGui
11734 {
11735 // ImGuiDockContext
11736 static ImGuiDockNode* DockContextAddNode(ImGuiContext* ctx, ImGuiID id);
11737 static void DockContextRemoveNode(ImGuiContext* ctx, ImGuiDockNode* node, bool merge_sibling_into_parent_node);
11738 static void DockContextQueueNotifyRemovedNode(ImGuiContext* ctx, ImGuiDockNode* node);
11739 static void DockContextProcessDock(ImGuiContext* ctx, ImGuiDockRequest* req);
11740 static void DockContextProcessUndockWindow(ImGuiContext* ctx, ImGuiWindow* window, bool clear_persistent_docking_ref = true);
11741 static void DockContextProcessUndockNode(ImGuiContext* ctx, ImGuiDockNode* node);
11742 static void DockContextPruneUnusedSettingsNodes(ImGuiContext* ctx);
11743 static ImGuiDockNode* DockContextFindNodeByID(ImGuiContext* ctx, ImGuiID id);
11744 static ImGuiDockNode* DockContextBindNodeToWindow(ImGuiContext* ctx, ImGuiWindow* window);
11745 static void DockContextBuildNodesFromSettings(ImGuiContext* ctx, ImGuiDockNodeSettings* node_settings_array, int node_settings_count);
11746 static void DockContextBuildAddWindowsToNodes(ImGuiContext* ctx, ImGuiID root_id); // Use root_id==0 to add all
11747
11748 // ImGuiDockNode
11749 static void DockNodeAddWindow(ImGuiDockNode* node, ImGuiWindow* window, bool add_to_tab_bar);
11750 static void DockNodeMoveWindows(ImGuiDockNode* dst_node, ImGuiDockNode* src_node);
11751 static void DockNodeMoveChildNodes(ImGuiDockNode* dst_node, ImGuiDockNode* src_node);
11752 static ImGuiWindow* DockNodeFindWindowByID(ImGuiDockNode* node, ImGuiID id);
11753 static void DockNodeApplyPosSizeToWindows(ImGuiDockNode* node);
11754 static void DockNodeRemoveWindow(ImGuiDockNode* node, ImGuiWindow* window, ImGuiID save_dock_id);
11755 static void DockNodeHideHostWindow(ImGuiDockNode* node);
11756 static void DockNodeUpdate(ImGuiDockNode* node);
11757 static void DockNodeUpdateForRootNode(ImGuiDockNode* node);
11758 static void DockNodeUpdateVisibleFlagAndInactiveChilds(ImGuiDockNode* node);
11759 static void DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_window);
11760 static void DockNodeAddTabBar(ImGuiDockNode* node);
11761 static void DockNodeRemoveTabBar(ImGuiDockNode* node);
11762 static ImGuiID DockNodeUpdateWindowMenu(ImGuiDockNode* node, ImGuiTabBar* tab_bar);
11763 static void DockNodeUpdateVisibleFlag(ImGuiDockNode* node);
11764 static void DockNodeStartMouseMovingWindow(ImGuiDockNode* node, ImGuiWindow* window);
11765 static bool DockNodeIsDropAllowed(ImGuiWindow* host_window, ImGuiWindow* payload_window);
11766 static void DockNodePreviewDockSetup(ImGuiWindow* host_window, ImGuiDockNode* host_node, ImGuiWindow* payload_window, ImGuiDockPreviewData* preview_data, bool is_explicit_target, bool is_outer_docking);
11767 static void DockNodePreviewDockRender(ImGuiWindow* host_window, ImGuiDockNode* host_node, ImGuiWindow* payload_window, const ImGuiDockPreviewData* preview_data);
11768 static void DockNodeCalcTabBarLayout(const ImGuiDockNode* node, ImRect* out_title_rect, ImRect* out_tab_bar_rect, ImVec2* out_window_menu_button_pos);
11769 static void DockNodeCalcSplitRects(ImVec2& pos_old, ImVec2& size_old, ImVec2& pos_new, ImVec2& size_new, ImGuiDir dir, ImVec2 size_new_desired);
11770 static bool DockNodeCalcDropRectsAndTestMousePos(const ImRect& parent, ImGuiDir dir, ImRect& out_draw, bool outer_docking, ImVec2* test_mouse_pos);
DockNodeGetHostWindowTitle(ImGuiDockNode * node,char * buf,int buf_size)11771 static const char* DockNodeGetHostWindowTitle(ImGuiDockNode* node, char* buf, int buf_size) { ImFormatString(buf, buf_size, "##DockNode_%02X", node->ID); return buf; }
11772 static int DockNodeGetTabOrder(ImGuiWindow* window);
11773
11774 // ImGuiDockNode tree manipulations
11775 static void DockNodeTreeSplit(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImGuiAxis split_axis, int split_first_child, float split_ratio, ImGuiDockNode* new_node);
11776 static void DockNodeTreeMerge(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImGuiDockNode* merge_lead_child);
11777 static void DockNodeTreeUpdatePosSize(ImGuiDockNode* node, ImVec2 pos, ImVec2 size, bool only_write_to_marked_nodes = false);
11778 static void DockNodeTreeUpdateSplitter(ImGuiDockNode* node);
11779 static ImGuiDockNode* DockNodeTreeFindVisibleNodeByPos(ImGuiDockNode* node, ImVec2 pos);
11780 static ImGuiDockNode* DockNodeTreeFindFallbackLeafNode(ImGuiDockNode* node);
11781
11782 // Settings
11783 static void DockSettingsRenameNodeReferences(ImGuiID old_node_id, ImGuiID new_node_id);
11784 static void DockSettingsRemoveNodeReferences(ImGuiID* node_ids, int node_ids_count);
11785 static ImGuiDockNodeSettings* DockSettingsFindNodeSettings(ImGuiContext* ctx, ImGuiID node_id);
11786 static void DockSettingsHandler_ClearAll(ImGuiContext*, ImGuiSettingsHandler*);
11787 static void DockSettingsHandler_ApplyAll(ImGuiContext*, ImGuiSettingsHandler*);
11788 static void* DockSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name);
11789 static void DockSettingsHandler_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line);
11790 static void DockSettingsHandler_WriteAll(ImGuiContext* imgui_ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf);
11791 }
11792
11793 //-----------------------------------------------------------------------------
11794 // Docking: ImGuiDockContext
11795 //-----------------------------------------------------------------------------
11796 // The lifetime model is different from the one of regular windows: we always create a ImGuiDockNode for each ImGuiDockNodeSettings,
11797 // 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.
11798 // At boot time only, we run a simple GC to remove nodes that have no references.
11799 // Because dock node settings (which are small, contiguous structures) are always mirrored by their corresponding dock nodes (more complete structures),
11800 // we can also very easily recreate the nodes from scratch given the settings data (this is what DockContextRebuild() does).
11801 // This is convenient as docking reconfiguration can be implemented by mostly poking at the simpler settings data.
11802 //-----------------------------------------------------------------------------
11803 // - DockContextInitialize()
11804 // - DockContextShutdown()
11805 // - DockContextClearNodes()
11806 // - DockContextRebuildNodes()
11807 // - DockContextUpdateUndocking()
11808 // - DockContextUpdateDocking()
11809 // - DockContextFindNodeByID()
11810 // - DockContextBindNodeToWindow()
11811 // - DockContextGenNodeID()
11812 // - DockContextAddNode()
11813 // - DockContextRemoveNode()
11814 // - ImGuiDockContextPruneNodeData
11815 // - DockContextPruneUnusedSettingsNodes()
11816 // - DockContextBuildNodesFromSettings()
11817 // - DockContextBuildAddWindowsToNodes()
11818 //-----------------------------------------------------------------------------
11819
DockContextInitialize(ImGuiContext * ctx)11820 void ImGui::DockContextInitialize(ImGuiContext* ctx)
11821 {
11822 ImGuiContext& g = *ctx;
11823
11824 // Add .ini handle for persistent docking data
11825 ImGuiSettingsHandler ini_handler;
11826 ini_handler.TypeName = "Docking";
11827 ini_handler.TypeHash = ImHashStr("Docking");
11828 ini_handler.ClearAllFn = DockSettingsHandler_ClearAll;
11829 ini_handler.ReadInitFn = DockSettingsHandler_ClearAll; // Also clear on read
11830 ini_handler.ReadOpenFn = DockSettingsHandler_ReadOpen;
11831 ini_handler.ReadLineFn = DockSettingsHandler_ReadLine;
11832 ini_handler.ApplyAllFn = DockSettingsHandler_ApplyAll;
11833 ini_handler.WriteAllFn = DockSettingsHandler_WriteAll;
11834 g.SettingsHandlers.push_back(ini_handler);
11835 }
11836
DockContextShutdown(ImGuiContext * ctx)11837 void ImGui::DockContextShutdown(ImGuiContext* ctx)
11838 {
11839 ImGuiDockContext* dc = &ctx->DockContext;
11840 for (int n = 0; n < dc->Nodes.Data.Size; n++)
11841 if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p)
11842 IM_DELETE(node);
11843 }
11844
DockContextClearNodes(ImGuiContext * ctx,ImGuiID root_id,bool clear_settings_refs)11845 void ImGui::DockContextClearNodes(ImGuiContext* ctx, ImGuiID root_id, bool clear_settings_refs)
11846 {
11847 IM_UNUSED(ctx);
11848 IM_ASSERT(ctx == GImGui);
11849 DockBuilderRemoveNodeDockedWindows(root_id, clear_settings_refs);
11850 DockBuilderRemoveNodeChildNodes(root_id);
11851 }
11852
11853 // [DEBUG] This function also acts as a defacto test to make sure we can rebuild from scratch without a glitch
11854 // (Different from DockSettingsHandler_ClearAll() + DockSettingsHandler_ApplyAll() because this reuses current settings!)
DockContextRebuildNodes(ImGuiContext * ctx)11855 void ImGui::DockContextRebuildNodes(ImGuiContext* ctx)
11856 {
11857 IMGUI_DEBUG_LOG_DOCKING("DockContextRebuild()\n");
11858 ImGuiDockContext* dc = &ctx->DockContext;
11859 SaveIniSettingsToMemory();
11860 ImGuiID root_id = 0; // Rebuild all
11861 DockContextClearNodes(ctx, root_id, false);
11862 DockContextBuildNodesFromSettings(ctx, dc->NodesSettings.Data, dc->NodesSettings.Size);
11863 DockContextBuildAddWindowsToNodes(ctx, root_id);
11864 }
11865
11866 // Docking context update function, called by NewFrame()
DockContextUpdateUndocking(ImGuiContext * ctx)11867 void ImGui::DockContextUpdateUndocking(ImGuiContext* ctx)
11868 {
11869 ImGuiContext& g = *ctx;
11870 ImGuiDockContext* dc = &ctx->DockContext;
11871 if (!(g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable))
11872 {
11873 if (dc->Nodes.Data.Size > 0 || dc->Requests.Size > 0)
11874 DockContextClearNodes(ctx, 0, true);
11875 return;
11876 }
11877
11878 // Setting NoSplit at runtime merges all nodes
11879 if (g.IO.ConfigDockingNoSplit)
11880 for (int n = 0; n < dc->Nodes.Data.Size; n++)
11881 if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p)
11882 if (node->IsRootNode() && node->IsSplitNode())
11883 {
11884 DockBuilderRemoveNodeChildNodes(node->ID);
11885 //dc->WantFullRebuild = true;
11886 }
11887
11888 // Process full rebuild
11889 #if 0
11890 if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_C)))
11891 dc->WantFullRebuild = true;
11892 #endif
11893 if (dc->WantFullRebuild)
11894 {
11895 DockContextRebuildNodes(ctx);
11896 dc->WantFullRebuild = false;
11897 }
11898
11899 // Process Undocking requests (we need to process them _before_ the UpdateMouseMovingWindowNewFrame call in NewFrame)
11900 for (int n = 0; n < dc->Requests.Size; n++)
11901 {
11902 ImGuiDockRequest* req = &dc->Requests[n];
11903 if (req->Type == ImGuiDockRequestType_Undock && req->UndockTargetWindow)
11904 DockContextProcessUndockWindow(ctx, req->UndockTargetWindow);
11905 else if (req->Type == ImGuiDockRequestType_Undock && req->UndockTargetNode)
11906 DockContextProcessUndockNode(ctx, req->UndockTargetNode);
11907 }
11908 }
11909
11910 // Docking context update function, called by NewFrame()
DockContextUpdateDocking(ImGuiContext * ctx)11911 void ImGui::DockContextUpdateDocking(ImGuiContext* ctx)
11912 {
11913 ImGuiContext& g = *ctx;
11914 ImGuiDockContext* dc = &ctx->DockContext;
11915 if (!(g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable))
11916 return;
11917
11918 // Store hovered dock node. We could in theory use DockNodeTreeFindVisibleNodeByPos() on the root host dock node, but using ->DockNode is a good shortcut.
11919 g.HoveredDockNode = NULL;
11920 if (ImGuiWindow* hovered_window = g.HoveredWindowUnderMovingWindow)
11921 {
11922 if (hovered_window->DockNodeAsHost)
11923 g.HoveredDockNode = DockNodeTreeFindVisibleNodeByPos(hovered_window->DockNodeAsHost, g.IO.MousePos);
11924 else if (hovered_window->RootWindowDockStop->DockNode)
11925 g.HoveredDockNode = hovered_window->RootWindowDockStop->DockNode;
11926 }
11927
11928 // Process Docking requests
11929 for (int n = 0; n < dc->Requests.Size; n++)
11930 if (dc->Requests[n].Type == ImGuiDockRequestType_Dock)
11931 DockContextProcessDock(ctx, &dc->Requests[n]);
11932 dc->Requests.resize(0);
11933
11934 // Create windows for each automatic docking nodes
11935 // 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)
11936 for (int n = 0; n < dc->Nodes.Data.Size; n++)
11937 if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p)
11938 if (node->IsFloatingNode())
11939 DockNodeUpdate(node);
11940 }
11941
DockContextFindNodeByID(ImGuiContext * ctx,ImGuiID id)11942 static ImGuiDockNode* ImGui::DockContextFindNodeByID(ImGuiContext* ctx, ImGuiID id)
11943 {
11944 return (ImGuiDockNode*)ctx->DockContext.Nodes.GetVoidPtr(id);
11945 }
11946
DockContextGenNodeID(ImGuiContext * ctx)11947 ImGuiID ImGui::DockContextGenNodeID(ImGuiContext* ctx)
11948 {
11949 // Generate an ID for new node (the exact ID value doesn't matter as long as it is not already used)
11950 // FIXME-OPT FIXME-DOCK: This is suboptimal, even if the node count is small enough not to be a worry. We should poke in ctx->Nodes to find a suitable ID faster.
11951 ImGuiID id = 0x0001;
11952 while (DockContextFindNodeByID(ctx, id) != NULL)
11953 id++;
11954 return id;
11955 }
11956
DockContextAddNode(ImGuiContext * ctx,ImGuiID id)11957 static ImGuiDockNode* ImGui::DockContextAddNode(ImGuiContext* ctx, ImGuiID id)
11958 {
11959 // 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.
11960 if (id == 0)
11961 id = DockContextGenNodeID(ctx);
11962 else
11963 IM_ASSERT(DockContextFindNodeByID(ctx, id) == NULL);
11964
11965 // We don't set node->LastFrameAlive on construction. Nodes are always created at all time to reflect .ini settings!
11966 IMGUI_DEBUG_LOG_DOCKING("DockContextAddNode 0x%08X\n", id);
11967 ImGuiDockNode* node = IM_NEW(ImGuiDockNode)(id);
11968 ctx->DockContext.Nodes.SetVoidPtr(node->ID, node);
11969 return node;
11970 }
11971
DockContextRemoveNode(ImGuiContext * ctx,ImGuiDockNode * node,bool merge_sibling_into_parent_node)11972 static void ImGui::DockContextRemoveNode(ImGuiContext* ctx, ImGuiDockNode* node, bool merge_sibling_into_parent_node)
11973 {
11974 ImGuiContext& g = *ctx;
11975 ImGuiDockContext* dc = &ctx->DockContext;
11976
11977 IMGUI_DEBUG_LOG_DOCKING("DockContextRemoveNode 0x%08X\n", node->ID);
11978 IM_ASSERT(DockContextFindNodeByID(ctx, node->ID) == node);
11979 IM_ASSERT(node->ChildNodes[0] == NULL && node->ChildNodes[1] == NULL);
11980 IM_ASSERT(node->Windows.Size == 0);
11981
11982 if (node->HostWindow)
11983 node->HostWindow->DockNodeAsHost = NULL;
11984
11985 ImGuiDockNode* parent_node = node->ParentNode;
11986 const bool merge = (merge_sibling_into_parent_node && parent_node != NULL);
11987 if (merge)
11988 {
11989 IM_ASSERT(parent_node->ChildNodes[0] == node || parent_node->ChildNodes[1] == node);
11990 ImGuiDockNode* sibling_node = (parent_node->ChildNodes[0] == node ? parent_node->ChildNodes[1] : parent_node->ChildNodes[0]);
11991 DockNodeTreeMerge(&g, parent_node, sibling_node);
11992 }
11993 else
11994 {
11995 for (int n = 0; parent_node && n < IM_ARRAYSIZE(parent_node->ChildNodes); n++)
11996 if (parent_node->ChildNodes[n] == node)
11997 node->ParentNode->ChildNodes[n] = NULL;
11998 dc->Nodes.SetVoidPtr(node->ID, NULL);
11999 IM_DELETE(node);
12000 }
12001 }
12002
DockNodeComparerDepthMostFirst(const void * lhs,const void * rhs)12003 static int IMGUI_CDECL DockNodeComparerDepthMostFirst(const void* lhs, const void* rhs)
12004 {
12005 const ImGuiDockNode* a = *(const ImGuiDockNode* const*)lhs;
12006 const ImGuiDockNode* b = *(const ImGuiDockNode* const*)rhs;
12007 return ImGui::DockNodeGetDepth(b) - ImGui::DockNodeGetDepth(a);
12008 }
12009
12010 // Pre C++0x doesn't allow us to use a function-local type (without linkage) as template parameter, so we moved this here.
12011 struct ImGuiDockContextPruneNodeData
12012 {
12013 int CountWindows, CountChildWindows, CountChildNodes;
12014 ImGuiID RootId;
ImGuiDockContextPruneNodeDataImGuiDockContextPruneNodeData12015 ImGuiDockContextPruneNodeData() { CountWindows = CountChildWindows = CountChildNodes = 0; RootId = 0; }
12016 };
12017
12018 // Garbage collect unused nodes (run once at init time)
DockContextPruneUnusedSettingsNodes(ImGuiContext * ctx)12019 static void ImGui::DockContextPruneUnusedSettingsNodes(ImGuiContext* ctx)
12020 {
12021 ImGuiContext& g = *ctx;
12022 ImGuiDockContext* dc = &ctx->DockContext;
12023 IM_ASSERT(g.Windows.Size == 0);
12024
12025 ImPool<ImGuiDockContextPruneNodeData> pool;
12026 pool.Reserve(dc->NodesSettings.Size);
12027
12028 // Count child nodes and compute RootID
12029 for (int settings_n = 0; settings_n < dc->NodesSettings.Size; settings_n++)
12030 {
12031 ImGuiDockNodeSettings* settings = &dc->NodesSettings[settings_n];
12032 ImGuiDockContextPruneNodeData* parent_data = settings->ParentNodeId ? pool.GetByKey(settings->ParentNodeId) : 0;
12033 pool.GetOrAddByKey(settings->ID)->RootId = parent_data ? parent_data->RootId : settings->ID;
12034 if (settings->ParentNodeId)
12035 pool.GetOrAddByKey(settings->ParentNodeId)->CountChildNodes++;
12036 }
12037
12038 // Count reference to dock ids from dockspaces
12039 // We track the 'auto-DockNode <- manual-Window <- manual-DockSpace' in order to avoid 'auto-DockNode' being ditched by DockContextPruneUnusedSettingsNodes()
12040 for (int settings_n = 0; settings_n < dc->NodesSettings.Size; settings_n++)
12041 {
12042 ImGuiDockNodeSettings* settings = &dc->NodesSettings[settings_n];
12043 if (settings->ParentWindowId != 0)
12044 if (ImGuiWindowSettings* window_settings = FindWindowSettings(settings->ParentWindowId))
12045 if (window_settings->DockId)
12046 if (ImGuiDockContextPruneNodeData* data = pool.GetByKey(window_settings->DockId))
12047 data->CountChildNodes++;
12048 }
12049
12050 // Count reference to dock ids from window settings
12051 // We guard against the possibility of an invalid .ini file (RootID may point to a missing node)
12052 for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings))
12053 if (ImGuiID dock_id = settings->DockId)
12054 if (ImGuiDockContextPruneNodeData* data = pool.GetByKey(dock_id))
12055 {
12056 data->CountWindows++;
12057 if (ImGuiDockContextPruneNodeData* data_root = (data->RootId == dock_id) ? data : pool.GetByKey(data->RootId))
12058 data_root->CountChildWindows++;
12059 }
12060
12061 // Prune
12062 for (int settings_n = 0; settings_n < dc->NodesSettings.Size; settings_n++)
12063 {
12064 ImGuiDockNodeSettings* settings = &dc->NodesSettings[settings_n];
12065 ImGuiDockContextPruneNodeData* data = pool.GetByKey(settings->ID);
12066 if (data->CountWindows > 1)
12067 continue;
12068 ImGuiDockContextPruneNodeData* data_root = (data->RootId == settings->ID) ? data : pool.GetByKey(data->RootId);
12069
12070 bool remove = false;
12071 remove |= (data->CountWindows == 1 && settings->ParentNodeId == 0 && data->CountChildNodes == 0 && !(settings->Flags & ImGuiDockNodeFlags_CentralNode)); // Floating root node with only 1 window
12072 remove |= (data->CountWindows == 0 && settings->ParentNodeId == 0 && data->CountChildNodes == 0); // Leaf nodes with 0 window
12073 remove |= (data_root->CountChildWindows == 0);
12074 if (remove)
12075 {
12076 IMGUI_DEBUG_LOG_DOCKING("DockContextPruneUnusedSettingsNodes: Prune 0x%08X\n", settings->ID);
12077 DockSettingsRemoveNodeReferences(&settings->ID, 1);
12078 settings->ID = 0;
12079 }
12080 }
12081 }
12082
DockContextBuildNodesFromSettings(ImGuiContext * ctx,ImGuiDockNodeSettings * node_settings_array,int node_settings_count)12083 static void ImGui::DockContextBuildNodesFromSettings(ImGuiContext* ctx, ImGuiDockNodeSettings* node_settings_array, int node_settings_count)
12084 {
12085 // Build nodes
12086 for (int node_n = 0; node_n < node_settings_count; node_n++)
12087 {
12088 ImGuiDockNodeSettings* settings = &node_settings_array[node_n];
12089 if (settings->ID == 0)
12090 continue;
12091 ImGuiDockNode* node = DockContextAddNode(ctx, settings->ID);
12092 node->ParentNode = settings->ParentNodeId ? DockContextFindNodeByID(ctx, settings->ParentNodeId) : NULL;
12093 node->Pos = ImVec2(settings->Pos.x, settings->Pos.y);
12094 node->Size = ImVec2(settings->Size.x, settings->Size.y);
12095 node->SizeRef = ImVec2(settings->SizeRef.x, settings->SizeRef.y);
12096 node->AuthorityForPos = node->AuthorityForSize = node->AuthorityForViewport = ImGuiDataAuthority_DockNode;
12097 if (node->ParentNode && node->ParentNode->ChildNodes[0] == NULL)
12098 node->ParentNode->ChildNodes[0] = node;
12099 else if (node->ParentNode && node->ParentNode->ChildNodes[1] == NULL)
12100 node->ParentNode->ChildNodes[1] = node;
12101 node->SelectedTabId = settings->SelectedWindowId;
12102 node->SplitAxis = (ImGuiAxis)settings->SplitAxis;
12103 node->LocalFlags |= (settings->Flags & ImGuiDockNodeFlags_SavedFlagsMask_);
12104
12105 // Bind host window immediately if it already exist (in case of a rebuild)
12106 // This is useful as the RootWindowForTitleBarHighlight links necessary to highlight the currently focused node requires node->HostWindow to be set.
12107 char host_window_title[20];
12108 ImGuiDockNode* root_node = DockNodeGetRootNode(node);
12109 node->HostWindow = FindWindowByName(DockNodeGetHostWindowTitle(root_node, host_window_title, IM_ARRAYSIZE(host_window_title)));
12110 }
12111 }
12112
DockContextBuildAddWindowsToNodes(ImGuiContext * ctx,ImGuiID root_id)12113 void ImGui::DockContextBuildAddWindowsToNodes(ImGuiContext* ctx, ImGuiID root_id)
12114 {
12115 // Rebind all windows to nodes (they can also lazily rebind but we'll have a visible glitch during the first frame)
12116 ImGuiContext& g = *ctx;
12117 for (int n = 0; n < g.Windows.Size; n++)
12118 {
12119 ImGuiWindow* window = g.Windows[n];
12120 if (window->DockId == 0 || window->LastFrameActive < g.FrameCount - 1)
12121 continue;
12122 if (window->DockNode != NULL)
12123 continue;
12124
12125 ImGuiDockNode* node = DockContextFindNodeByID(ctx, window->DockId);
12126 IM_ASSERT(node != NULL); // This should have been called after DockContextBuildNodesFromSettings()
12127 if (root_id == 0 || DockNodeGetRootNode(node)->ID == root_id)
12128 DockNodeAddWindow(node, window, true);
12129 }
12130 }
12131
12132 //-----------------------------------------------------------------------------
12133 // Docking: ImGuiDockContext Docking/Undocking functions
12134 //-----------------------------------------------------------------------------
12135 // - DockContextQueueDock()
12136 // - DockContextQueueUndockWindow()
12137 // - DockContextQueueUndockNode()
12138 // - DockContextQueueNotifyRemovedNode()
12139 // - DockContextProcessDock()
12140 // - DockContextProcessUndockWindow()
12141 // - DockContextProcessUndockNode()
12142 // - DockContextCalcDropPosForDocking()
12143 //-----------------------------------------------------------------------------
12144
DockContextQueueDock(ImGuiContext * ctx,ImGuiWindow * target,ImGuiDockNode * target_node,ImGuiWindow * payload,ImGuiDir split_dir,float split_ratio,bool split_outer)12145 void ImGui::DockContextQueueDock(ImGuiContext* ctx, ImGuiWindow* target, ImGuiDockNode* target_node, ImGuiWindow* payload, ImGuiDir split_dir, float split_ratio, bool split_outer)
12146 {
12147 IM_ASSERT(target != payload);
12148 ImGuiDockRequest req;
12149 req.Type = ImGuiDockRequestType_Dock;
12150 req.DockTargetWindow = target;
12151 req.DockTargetNode = target_node;
12152 req.DockPayload = payload;
12153 req.DockSplitDir = split_dir;
12154 req.DockSplitRatio = split_ratio;
12155 req.DockSplitOuter = split_outer;
12156 ctx->DockContext.Requests.push_back(req);
12157 }
12158
DockContextQueueUndockWindow(ImGuiContext * ctx,ImGuiWindow * window)12159 void ImGui::DockContextQueueUndockWindow(ImGuiContext* ctx, ImGuiWindow* window)
12160 {
12161 ImGuiDockRequest req;
12162 req.Type = ImGuiDockRequestType_Undock;
12163 req.UndockTargetWindow = window;
12164 ctx->DockContext.Requests.push_back(req);
12165 }
12166
DockContextQueueUndockNode(ImGuiContext * ctx,ImGuiDockNode * node)12167 void ImGui::DockContextQueueUndockNode(ImGuiContext* ctx, ImGuiDockNode* node)
12168 {
12169 ImGuiDockRequest req;
12170 req.Type = ImGuiDockRequestType_Undock;
12171 req.UndockTargetNode = node;
12172 ctx->DockContext.Requests.push_back(req);
12173 }
12174
DockContextQueueNotifyRemovedNode(ImGuiContext * ctx,ImGuiDockNode * node)12175 void ImGui::DockContextQueueNotifyRemovedNode(ImGuiContext* ctx, ImGuiDockNode* node)
12176 {
12177 ImGuiDockContext* dc = &ctx->DockContext;
12178 for (int n = 0; n < dc->Requests.Size; n++)
12179 if (dc->Requests[n].DockTargetNode == node)
12180 dc->Requests[n].Type = ImGuiDockRequestType_None;
12181 }
12182
DockContextProcessDock(ImGuiContext * ctx,ImGuiDockRequest * req)12183 void ImGui::DockContextProcessDock(ImGuiContext* ctx, ImGuiDockRequest* req)
12184 {
12185 IM_ASSERT((req->Type == ImGuiDockRequestType_Dock && req->DockPayload != NULL) || (req->Type == ImGuiDockRequestType_Split && req->DockPayload == NULL));
12186 IM_ASSERT(req->DockTargetWindow != NULL || req->DockTargetNode != NULL);
12187
12188 ImGuiContext& g = *ctx;
12189 IM_UNUSED(g);
12190
12191 ImGuiWindow* payload_window = req->DockPayload; // Optional
12192 ImGuiWindow* target_window = req->DockTargetWindow;
12193 ImGuiDockNode* node = req->DockTargetNode;
12194 if (payload_window)
12195 IMGUI_DEBUG_LOG_DOCKING("DockContextProcessDock node 0x%08X target '%s' dock window '%s', split_dir %d\n", node ? node->ID : 0, target_window ? target_window->Name : "NULL", payload_window ? payload_window->Name : "NULL", req->DockSplitDir);
12196 else
12197 IMGUI_DEBUG_LOG_DOCKING("DockContextProcessDock node 0x%08X, split_dir %d\n", node ? node->ID : 0, req->DockSplitDir);
12198
12199 // Decide which Tab will be selected at the end of the operation
12200 ImGuiID next_selected_id = 0;
12201 ImGuiDockNode* payload_node = NULL;
12202 if (payload_window)
12203 {
12204 payload_node = payload_window->DockNodeAsHost;
12205 payload_window->DockNodeAsHost = NULL; // Important to clear this as the node will have its life as a child which might be merged/deleted later.
12206 if (payload_node && payload_node->IsLeafNode())
12207 next_selected_id = payload_node->TabBar->NextSelectedTabId ? payload_node->TabBar->NextSelectedTabId : payload_node->TabBar->SelectedTabId;
12208 if (payload_node == NULL)
12209 next_selected_id = payload_window->ID;
12210 }
12211
12212 // FIXME-DOCK: When we are trying to dock an existing single-window node into a loose window, transfer Node ID as well
12213 // When processing an interactive split, usually LastFrameAlive will be < g.FrameCount. But DockBuilder operations can make it ==.
12214 if (node)
12215 IM_ASSERT(node->LastFrameAlive <= g.FrameCount);
12216 if (node && target_window && node == target_window->DockNodeAsHost)
12217 IM_ASSERT(node->Windows.Size > 0 || node->IsSplitNode() || node->IsCentralNode());
12218
12219 // Create new node and add existing window to it
12220 if (node == NULL)
12221 {
12222 node = DockContextAddNode(ctx, 0);
12223 node->Pos = target_window->Pos;
12224 node->Size = target_window->Size;
12225 if (target_window->DockNodeAsHost == NULL)
12226 {
12227 DockNodeAddWindow(node, target_window, true);
12228 node->TabBar->Tabs[0].Flags &= ~ImGuiTabItemFlags_Unsorted;
12229 target_window->DockIsActive = true;
12230 }
12231 }
12232
12233 ImGuiDir split_dir = req->DockSplitDir;
12234 if (split_dir != ImGuiDir_None)
12235 {
12236 // Split into one, one side will be our payload node unless we are dropping a loose window
12237 const ImGuiAxis split_axis = (split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Right) ? ImGuiAxis_X : ImGuiAxis_Y;
12238 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
12239 const float split_ratio = req->DockSplitRatio;
12240 DockNodeTreeSplit(ctx, node, split_axis, split_inheritor_child_idx, split_ratio, payload_node); // payload_node may be NULL here!
12241 ImGuiDockNode* new_node = node->ChildNodes[split_inheritor_child_idx ^ 1];
12242 new_node->HostWindow = node->HostWindow;
12243 node = new_node;
12244 }
12245 node->LocalFlags &= ~ImGuiDockNodeFlags_HiddenTabBar;
12246
12247 if (node != payload_node)
12248 {
12249 // 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!)
12250 if (node->Windows.Size > 0 && node->TabBar == NULL)
12251 {
12252 DockNodeAddTabBar(node);
12253 for (int n = 0; n < node->Windows.Size; n++)
12254 TabBarAddTab(node->TabBar, ImGuiTabItemFlags_None, node->Windows[n]);
12255 }
12256
12257 if (payload_node != NULL)
12258 {
12259 // Transfer full payload node (with 1+ child windows or child nodes)
12260 if (payload_node->IsSplitNode())
12261 {
12262 if (node->Windows.Size > 0)
12263 {
12264 // 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.
12265 // In this situation, we move the windows of the target node into the currently visible node of the payload.
12266 // This allows us to preserve some of the underlying dock tree settings nicely.
12267 IM_ASSERT(payload_node->OnlyNodeWithWindows != NULL); // The docking should have been blocked by DockNodePreviewDockSetup() early on and never submitted.
12268 ImGuiDockNode* visible_node = payload_node->OnlyNodeWithWindows;
12269 if (visible_node->TabBar)
12270 IM_ASSERT(visible_node->TabBar->Tabs.Size > 0);
12271 DockNodeMoveWindows(node, visible_node);
12272 DockNodeMoveWindows(visible_node, node);
12273 DockSettingsRenameNodeReferences(node->ID, visible_node->ID);
12274 }
12275 if (node->IsCentralNode())
12276 {
12277 // Central node property needs to be moved to a leaf node, pick the last focused one.
12278 // FIXME-DOCK: If we had to transfer other flags here, what would the policy be?
12279 ImGuiDockNode* last_focused_node = DockContextFindNodeByID(ctx, payload_node->LastFocusedNodeId);
12280 IM_ASSERT(last_focused_node != NULL);
12281 ImGuiDockNode* last_focused_root_node = DockNodeGetRootNode(last_focused_node);
12282 IM_ASSERT(last_focused_root_node == DockNodeGetRootNode(payload_node));
12283 last_focused_node->LocalFlags |= ImGuiDockNodeFlags_CentralNode;
12284 node->LocalFlags &= ~ImGuiDockNodeFlags_CentralNode;
12285 last_focused_root_node->CentralNode = last_focused_node;
12286 }
12287
12288 IM_ASSERT(node->Windows.Size == 0);
12289 DockNodeMoveChildNodes(node, payload_node);
12290 }
12291 else
12292 {
12293 const ImGuiID payload_dock_id = payload_node->ID;
12294 DockNodeMoveWindows(node, payload_node);
12295 DockSettingsRenameNodeReferences(payload_dock_id, node->ID);
12296 }
12297 DockContextRemoveNode(ctx, payload_node, true);
12298 }
12299 else if (payload_window)
12300 {
12301 // Transfer single window
12302 const ImGuiID payload_dock_id = payload_window->DockId;
12303 node->VisibleWindow = payload_window;
12304 DockNodeAddWindow(node, payload_window, true);
12305 if (payload_dock_id != 0)
12306 DockSettingsRenameNodeReferences(payload_dock_id, node->ID);
12307 }
12308 }
12309 else
12310 {
12311 // When docking a floating single window node we want to reevaluate auto-hiding of the tab bar
12312 node->WantHiddenTabBarUpdate = true;
12313 }
12314
12315 // Update selection immediately
12316 if (ImGuiTabBar* tab_bar = node->TabBar)
12317 tab_bar->NextSelectedTabId = next_selected_id;
12318 MarkIniSettingsDirty();
12319 }
12320
DockContextProcessUndockWindow(ImGuiContext * ctx,ImGuiWindow * window,bool clear_persistent_docking_ref)12321 void ImGui::DockContextProcessUndockWindow(ImGuiContext* ctx, ImGuiWindow* window, bool clear_persistent_docking_ref)
12322 {
12323 IMGUI_DEBUG_LOG_DOCKING("DockContextProcessUndockWindow window '%s', clear_persistent_docking_ref = %d\n", window->Name, clear_persistent_docking_ref);
12324 IM_UNUSED(ctx);
12325 if (window->DockNode)
12326 DockNodeRemoveWindow(window->DockNode, window, clear_persistent_docking_ref ? 0 : window->DockId);
12327 else
12328 window->DockId = 0;
12329 window->Collapsed = false;
12330 window->DockIsActive = false;
12331 window->DockTabIsVisible = false;
12332 MarkIniSettingsDirty();
12333 }
12334
DockContextProcessUndockNode(ImGuiContext * ctx,ImGuiDockNode * node)12335 void ImGui::DockContextProcessUndockNode(ImGuiContext* ctx, ImGuiDockNode* node)
12336 {
12337 IMGUI_DEBUG_LOG_DOCKING("DockContextProcessUndockNode node %08X\n", node->ID);
12338 IM_ASSERT(node->IsLeafNode());
12339 IM_ASSERT(node->Windows.Size >= 1);
12340
12341 if (node->IsRootNode() || node->IsCentralNode())
12342 {
12343 // 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.
12344 ImGuiDockNode* new_node = DockContextAddNode(ctx, 0);
12345 DockNodeMoveWindows(new_node, node);
12346 DockSettingsRenameNodeReferences(node->ID, new_node->ID);
12347 for (int n = 0; n < new_node->Windows.Size; n++)
12348 UpdateWindowParentAndRootLinks(new_node->Windows[n], new_node->Windows[n]->Flags, NULL);
12349 node = new_node;
12350 }
12351 else
12352 {
12353 // Otherwise extract our node and merging our sibling back into the parent node.
12354 IM_ASSERT(node->ParentNode->ChildNodes[0] == node || node->ParentNode->ChildNodes[1] == node);
12355 int index_in_parent = (node->ParentNode->ChildNodes[0] == node) ? 0 : 1;
12356 node->ParentNode->ChildNodes[index_in_parent] = NULL;
12357 DockNodeTreeMerge(ctx, node->ParentNode, node->ParentNode->ChildNodes[index_in_parent ^ 1]);
12358 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
12359 node->ParentNode = NULL;
12360 }
12361 node->AuthorityForPos = node->AuthorityForSize = ImGuiDataAuthority_Window;
12362 node->WantMouseMove = true;
12363 MarkIniSettingsDirty();
12364 }
12365
12366 // This is mostly used for automation.
DockContextCalcDropPosForDocking(ImGuiWindow * target,ImGuiDockNode * target_node,ImGuiWindow * payload,ImGuiDir split_dir,bool split_outer,ImVec2 * out_pos)12367 bool ImGui::DockContextCalcDropPosForDocking(ImGuiWindow* target, ImGuiDockNode* target_node, ImGuiWindow* payload, ImGuiDir split_dir, bool split_outer, ImVec2* out_pos)
12368 {
12369 if (split_outer)
12370 {
12371 IM_ASSERT(0);
12372 }
12373 else
12374 {
12375 ImGuiDockPreviewData split_data;
12376 DockNodePreviewDockSetup(target, target_node, payload, &split_data, false, split_outer);
12377 if (split_data.DropRectsDraw[split_dir+1].IsInverted())
12378 return false;
12379 *out_pos = split_data.DropRectsDraw[split_dir+1].GetCenter();
12380 return true;
12381 }
12382 return false;
12383 }
12384
12385 //-----------------------------------------------------------------------------
12386 // Docking: ImGuiDockNode
12387 //-----------------------------------------------------------------------------
12388 // - DockNodeGetTabOrder()
12389 // - DockNodeAddWindow()
12390 // - DockNodeRemoveWindow()
12391 // - DockNodeMoveChildNodes()
12392 // - DockNodeMoveWindows()
12393 // - DockNodeApplyPosSizeToWindows()
12394 // - DockNodeHideHostWindow()
12395 // - ImGuiDockNodeFindInfoResults
12396 // - DockNodeFindInfo()
12397 // - DockNodeFindWindowByID()
12398 // - DockNodeUpdateVisibleFlagAndInactiveChilds()
12399 // - DockNodeUpdateVisibleFlag()
12400 // - DockNodeStartMouseMovingWindow()
12401 // - DockNodeUpdate()
12402 // - DockNodeUpdateWindowMenu()
12403 // - DockNodeUpdateTabBar()
12404 // - DockNodeAddTabBar()
12405 // - DockNodeRemoveTabBar()
12406 // - DockNodeIsDropAllowedOne()
12407 // - DockNodeIsDropAllowed()
12408 // - DockNodeCalcTabBarLayout()
12409 // - DockNodeCalcSplitRects()
12410 // - DockNodeCalcDropRectsAndTestMousePos()
12411 // - DockNodePreviewDockSetup()
12412 // - DockNodePreviewDockRender()
12413 //-----------------------------------------------------------------------------
12414
ImGuiDockNode(ImGuiID id)12415 ImGuiDockNode::ImGuiDockNode(ImGuiID id)
12416 {
12417 ID = id;
12418 SharedFlags = LocalFlags = ImGuiDockNodeFlags_None;
12419 ParentNode = ChildNodes[0] = ChildNodes[1] = NULL;
12420 TabBar = NULL;
12421 SplitAxis = ImGuiAxis_None;
12422
12423 State = ImGuiDockNodeState_Unknown;
12424 HostWindow = VisibleWindow = NULL;
12425 CentralNode = OnlyNodeWithWindows = NULL;
12426 LastFrameAlive = LastFrameActive = LastFrameFocused = -1;
12427 LastFocusedNodeId = 0;
12428 SelectedTabId = 0;
12429 WantCloseTabId = 0;
12430 AuthorityForPos = AuthorityForSize = ImGuiDataAuthority_DockNode;
12431 AuthorityForViewport = ImGuiDataAuthority_Auto;
12432 IsVisible = true;
12433 IsFocused = HasCloseButton = HasWindowMenuButton = EnableCloseButton = false;
12434 WantCloseAll = WantLockSizeOnce = WantMouseMove = WantHiddenTabBarUpdate = WantHiddenTabBarToggle = false;
12435 MarkedForPosSizeWrite = false;
12436 }
12437
~ImGuiDockNode()12438 ImGuiDockNode::~ImGuiDockNode()
12439 {
12440 IM_DELETE(TabBar);
12441 TabBar = NULL;
12442 ChildNodes[0] = ChildNodes[1] = NULL;
12443 }
12444
DockNodeGetTabOrder(ImGuiWindow * window)12445 int ImGui::DockNodeGetTabOrder(ImGuiWindow* window)
12446 {
12447 ImGuiTabBar* tab_bar = window->DockNode->TabBar;
12448 if (tab_bar == NULL)
12449 return -1;
12450 ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, window->ID);
12451 return tab ? tab_bar->GetTabOrder(tab) : -1;
12452 }
12453
DockNodeAddWindow(ImGuiDockNode * node,ImGuiWindow * window,bool add_to_tab_bar)12454 static void ImGui::DockNodeAddWindow(ImGuiDockNode* node, ImGuiWindow* window, bool add_to_tab_bar)
12455 {
12456 ImGuiContext& g = *GImGui; (void)g;
12457 if (window->DockNode)
12458 {
12459 // Can overwrite an existing window->DockNode (e.g. pointing to a disabled DockSpace node)
12460 IM_ASSERT(window->DockNode->ID != node->ID);
12461 DockNodeRemoveWindow(window->DockNode, window, 0);
12462 }
12463 IM_ASSERT(window->DockNode == NULL || window->DockNodeAsHost == NULL);
12464 IMGUI_DEBUG_LOG_DOCKING("DockNodeAddWindow node 0x%08X window '%s'\n", node->ID, window->Name);
12465
12466 node->Windows.push_back(window);
12467 node->WantHiddenTabBarUpdate = true;
12468 window->DockNode = node;
12469 window->DockId = node->ID;
12470 window->DockIsActive = (node->Windows.Size > 1);
12471 window->DockTabWantClose = false;
12472
12473 // If more than 2 windows appeared on the same frame, we'll create a new hosting DockNode from the point of the second window submission.
12474 // Then we need to hide the first window (after its been output) otherwise it would be visible as a standalone window for one frame.
12475 if (node->HostWindow == NULL && node->Windows.Size == 2 && node->Windows[0]->WasActive == false)
12476 {
12477 node->Windows[0]->Hidden = true;
12478 node->Windows[0]->HiddenFramesCanSkipItems = 1;
12479 }
12480
12481 // When reactivating a node with one or two loose window, the window pos/size/viewport are authoritative over the node storage.
12482 // In particular it is important we init the viewport from the first window so we don't create two viewports and drop one.
12483 if (node->HostWindow == NULL && node->IsFloatingNode())
12484 {
12485 if (node->AuthorityForPos == ImGuiDataAuthority_Auto)
12486 node->AuthorityForPos = ImGuiDataAuthority_Window;
12487 if (node->AuthorityForSize == ImGuiDataAuthority_Auto)
12488 node->AuthorityForSize = ImGuiDataAuthority_Window;
12489 if (node->AuthorityForViewport == ImGuiDataAuthority_Auto)
12490 node->AuthorityForViewport = ImGuiDataAuthority_Window;
12491 }
12492
12493 // Add to tab bar if requested
12494 if (add_to_tab_bar)
12495 {
12496 if (node->TabBar == NULL)
12497 {
12498 DockNodeAddTabBar(node);
12499 node->TabBar->SelectedTabId = node->TabBar->NextSelectedTabId = node->SelectedTabId;
12500
12501 // Add existing windows
12502 for (int n = 0; n < node->Windows.Size - 1; n++)
12503 TabBarAddTab(node->TabBar, ImGuiTabItemFlags_None, node->Windows[n]);
12504 }
12505 TabBarAddTab(node->TabBar, ImGuiTabItemFlags_Unsorted, window);
12506 }
12507
12508 DockNodeUpdateVisibleFlag(node);
12509
12510 // 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.
12511 if (node->HostWindow)
12512 UpdateWindowParentAndRootLinks(window, window->Flags | ImGuiWindowFlags_ChildWindow, node->HostWindow);
12513 }
12514
DockNodeRemoveWindow(ImGuiDockNode * node,ImGuiWindow * window,ImGuiID save_dock_id)12515 static void ImGui::DockNodeRemoveWindow(ImGuiDockNode* node, ImGuiWindow* window, ImGuiID save_dock_id)
12516 {
12517 ImGuiContext& g = *GImGui;
12518 IM_ASSERT(window->DockNode == node);
12519 //IM_ASSERT(window->RootWindow == node->HostWindow);
12520 //IM_ASSERT(window->LastFrameActive < g.FrameCount); // We may call this from Begin()
12521 IM_ASSERT(save_dock_id == 0 || save_dock_id == node->ID);
12522 IMGUI_DEBUG_LOG_DOCKING("DockNodeRemoveWindow node 0x%08X window '%s'\n", node->ID, window->Name);
12523
12524 window->DockNode = NULL;
12525 window->DockIsActive = window->DockTabWantClose = false;
12526 window->DockId = save_dock_id;
12527 UpdateWindowParentAndRootLinks(window, window->Flags & ~ImGuiWindowFlags_ChildWindow, NULL); // Update immediately
12528
12529 // Remove window
12530 bool erased = false;
12531 IM_UNUSED(erased); // [Bruno Levy] silence a warning
12532 for (int n = 0; n < node->Windows.Size; n++)
12533 if (node->Windows[n] == window)
12534 {
12535 node->Windows.erase(node->Windows.Data + n);
12536 erased = true;
12537 break;
12538 }
12539 IM_ASSERT(erased);
12540 if (node->VisibleWindow == window)
12541 node->VisibleWindow = NULL;
12542
12543 // Remove tab and possibly tab bar
12544 node->WantHiddenTabBarUpdate = true;
12545 if (node->TabBar)
12546 {
12547 TabBarRemoveTab(node->TabBar, window->ID);
12548 const int tab_count_threshold_for_tab_bar = node->IsCentralNode() ? 1 : 2;
12549 if (node->Windows.Size < tab_count_threshold_for_tab_bar)
12550 DockNodeRemoveTabBar(node);
12551 }
12552
12553 if (node->Windows.Size == 0 && !node->IsCentralNode() && !node->IsDockSpace() && window->DockId != node->ID)
12554 {
12555 // Automatic dock node delete themselves if they are not holding at least one tab
12556 DockContextRemoveNode(&g, node, true);
12557 return;
12558 }
12559
12560 if (node->Windows.Size == 1 && !node->IsCentralNode() && node->HostWindow)
12561 {
12562 ImGuiWindow* remaining_window = node->Windows[0];
12563 if (node->HostWindow->ViewportOwned && node->IsRootNode())
12564 {
12565 // Transfer viewport back to the remaining loose window
12566 IM_ASSERT(node->HostWindow->Viewport->Window == node->HostWindow);
12567 node->HostWindow->Viewport->Window = remaining_window;
12568 node->HostWindow->Viewport->ID = remaining_window->ID;
12569 }
12570 remaining_window->Collapsed = node->HostWindow->Collapsed;
12571 }
12572
12573 // Update visibility immediately is required so the DockNodeUpdateRemoveInactiveChilds() processing can reflect changes up the tree
12574 DockNodeUpdateVisibleFlag(node);
12575 }
12576
DockNodeMoveChildNodes(ImGuiDockNode * dst_node,ImGuiDockNode * src_node)12577 static void ImGui::DockNodeMoveChildNodes(ImGuiDockNode* dst_node, ImGuiDockNode* src_node)
12578 {
12579 IM_ASSERT(dst_node->Windows.Size == 0);
12580 dst_node->ChildNodes[0] = src_node->ChildNodes[0];
12581 dst_node->ChildNodes[1] = src_node->ChildNodes[1];
12582 if (dst_node->ChildNodes[0])
12583 dst_node->ChildNodes[0]->ParentNode = dst_node;
12584 if (dst_node->ChildNodes[1])
12585 dst_node->ChildNodes[1]->ParentNode = dst_node;
12586 dst_node->SplitAxis = src_node->SplitAxis;
12587 dst_node->SizeRef = src_node->SizeRef;
12588 src_node->ChildNodes[0] = src_node->ChildNodes[1] = NULL;
12589 }
12590
DockNodeMoveWindows(ImGuiDockNode * dst_node,ImGuiDockNode * src_node)12591 static void ImGui::DockNodeMoveWindows(ImGuiDockNode* dst_node, ImGuiDockNode* src_node)
12592 {
12593 // Insert tabs in the same orders as currently ordered (node->Windows isn't ordered)
12594 IM_ASSERT(src_node && dst_node && dst_node != src_node);
12595 ImGuiTabBar* src_tab_bar = src_node->TabBar;
12596 if (src_tab_bar != NULL)
12597 IM_ASSERT(src_node->Windows.Size == src_node->TabBar->Tabs.Size);
12598
12599 // If the dst_node is empty we can just move the entire tab bar (to preserve selection, scrolling, etc.)
12600 bool move_tab_bar = (src_tab_bar != NULL) && (dst_node->TabBar == NULL);
12601 if (move_tab_bar)
12602 {
12603 dst_node->TabBar = src_node->TabBar;
12604 src_node->TabBar = NULL;
12605 }
12606
12607 for (int n = 0; n < src_node->Windows.Size; n++)
12608 {
12609 ImGuiWindow* window = src_tab_bar ? src_tab_bar->Tabs[n].Window : src_node->Windows[n];
12610 window->DockNode = NULL;
12611 window->DockIsActive = false;
12612 DockNodeAddWindow(dst_node, window, move_tab_bar ? false : true);
12613 }
12614 src_node->Windows.clear();
12615
12616 if (!move_tab_bar && src_node->TabBar)
12617 {
12618 if (dst_node->TabBar)
12619 dst_node->TabBar->SelectedTabId = src_node->TabBar->SelectedTabId;
12620 DockNodeRemoveTabBar(src_node);
12621 }
12622 }
12623
DockNodeApplyPosSizeToWindows(ImGuiDockNode * node)12624 static void ImGui::DockNodeApplyPosSizeToWindows(ImGuiDockNode* node)
12625 {
12626 for (int n = 0; n < node->Windows.Size; n++)
12627 {
12628 SetWindowPos(node->Windows[n], node->Pos, ImGuiCond_Always); // We don't assign directly to Pos because it can break the calculation of SizeContents on next frame
12629 SetWindowSize(node->Windows[n], node->Size, ImGuiCond_Always);
12630 }
12631 }
12632
DockNodeHideHostWindow(ImGuiDockNode * node)12633 static void ImGui::DockNodeHideHostWindow(ImGuiDockNode* node)
12634 {
12635 if (node->HostWindow)
12636 {
12637 if (node->HostWindow->DockNodeAsHost == node)
12638 node->HostWindow->DockNodeAsHost = NULL;
12639 node->HostWindow = NULL;
12640 }
12641
12642 if (node->Windows.Size == 1)
12643 {
12644 node->VisibleWindow = node->Windows[0];
12645 node->Windows[0]->DockIsActive = false;
12646 }
12647
12648 if (node->TabBar)
12649 DockNodeRemoveTabBar(node);
12650 }
12651
12652 // Search function called once by root node in DockNodeUpdate()
12653 struct ImGuiDockNodeFindInfoResults
12654 {
12655 ImGuiDockNode* CentralNode;
12656 ImGuiDockNode* FirstNodeWithWindows;
12657 int CountNodesWithWindows;
12658 //ImGuiWindowClass WindowClassForMerges;
12659
ImGuiDockNodeFindInfoResultsImGuiDockNodeFindInfoResults12660 ImGuiDockNodeFindInfoResults() { CentralNode = FirstNodeWithWindows = NULL; CountNodesWithWindows = 0; }
12661 };
12662
DockNodeFindInfo(ImGuiDockNode * node,ImGuiDockNodeFindInfoResults * results)12663 static void DockNodeFindInfo(ImGuiDockNode* node, ImGuiDockNodeFindInfoResults* results)
12664 {
12665 if (node->Windows.Size > 0)
12666 {
12667 if (results->FirstNodeWithWindows == NULL)
12668 results->FirstNodeWithWindows = node;
12669 results->CountNodesWithWindows++;
12670 }
12671 if (node->IsCentralNode())
12672 {
12673 IM_ASSERT(results->CentralNode == NULL); // Should be only one
12674 IM_ASSERT(node->IsLeafNode() && "If you get this assert: please submit .ini file + repro of actions leading to this.");
12675 results->CentralNode = node;
12676 }
12677 if (results->CountNodesWithWindows > 1 && results->CentralNode != NULL)
12678 return;
12679 if (node->ChildNodes[0])
12680 DockNodeFindInfo(node->ChildNodes[0], results);
12681 if (node->ChildNodes[1])
12682 DockNodeFindInfo(node->ChildNodes[1], results);
12683 }
12684
DockNodeFindWindowByID(ImGuiDockNode * node,ImGuiID id)12685 static ImGuiWindow* ImGui::DockNodeFindWindowByID(ImGuiDockNode* node, ImGuiID id)
12686 {
12687 IM_ASSERT(id != 0);
12688 for (int n = 0; n < node->Windows.Size; n++)
12689 if (node->Windows[n]->ID == id)
12690 return node->Windows[n];
12691 return NULL;
12692 }
12693
12694 // - Remove inactive windows/nodes.
12695 // - Update visibility flag.
DockNodeUpdateVisibleFlagAndInactiveChilds(ImGuiDockNode * node)12696 static void ImGui::DockNodeUpdateVisibleFlagAndInactiveChilds(ImGuiDockNode* node)
12697 {
12698 ImGuiContext& g = *GImGui;
12699 IM_ASSERT(node->ParentNode == NULL || node->ParentNode->ChildNodes[0] == node || node->ParentNode->ChildNodes[1] == node);
12700
12701 // Inherit most flags
12702 if (node->ParentNode)
12703 node->SharedFlags = node->ParentNode->SharedFlags & ImGuiDockNodeFlags_SharedFlagsInheritMask_;
12704
12705 // Recurse into children
12706 // There is the possibility that one of our child becoming empty will delete itself and moving its sibling contents into 'node'.
12707 // If 'node->ChildNode[0]' delete itself, then 'node->ChildNode[1]->Windows' will be moved into 'node'
12708 // 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)
12709 if (node->ChildNodes[0])
12710 DockNodeUpdateVisibleFlagAndInactiveChilds(node->ChildNodes[0]);
12711 if (node->ChildNodes[1])
12712 DockNodeUpdateVisibleFlagAndInactiveChilds(node->ChildNodes[1]);
12713
12714 // Remove inactive windows
12715 // Merge node flags overrides stored in windows
12716 for (int window_n = 0; window_n < node->Windows.Size; window_n++)
12717 {
12718 ImGuiWindow* window = node->Windows[window_n];
12719 IM_ASSERT(window->DockNode == node);
12720
12721 bool node_was_active = (node->LastFrameActive + 1 == g.FrameCount);
12722 bool remove = false;
12723 remove |= node_was_active && (window->LastFrameActive + 1 < g.FrameCount);
12724 remove |= node_was_active && (node->WantCloseAll || node->WantCloseTabId == window->ID) && window->HasCloseButton && !(window->Flags & ImGuiWindowFlags_UnsavedDocument); // Submit all _expected_ closure from last frame
12725 remove |= (window->DockTabWantClose);
12726 if (remove)
12727 {
12728 window->DockTabWantClose = false;
12729 if (node->Windows.Size == 1 && !node->IsCentralNode())
12730 {
12731 DockNodeHideHostWindow(node);
12732 node->State = ImGuiDockNodeState_HostWindowHiddenBecauseSingleWindow;
12733 DockNodeRemoveWindow(node, window, node->ID); // Will delete the node so it'll be invalid on return
12734 return;
12735 }
12736 DockNodeRemoveWindow(node, window, node->ID);
12737 window_n--;
12738 }
12739 else
12740 {
12741 node->LocalFlags &= ~window->WindowClass.DockNodeFlagsOverrideClear;
12742 node->LocalFlags |= window->WindowClass.DockNodeFlagsOverrideSet;
12743 }
12744 }
12745
12746 // Auto-hide tab bar option
12747 ImGuiDockNodeFlags node_flags = node->GetMergedFlags();
12748 if (node->WantHiddenTabBarUpdate && node->Windows.Size == 1 && (node_flags & ImGuiDockNodeFlags_AutoHideTabBar) && !node->IsHiddenTabBar())
12749 node->WantHiddenTabBarToggle = true;
12750 node->WantHiddenTabBarUpdate = false;
12751
12752 // Cancel toggling if we know our tab bar is enforced to be hidden at all times
12753 if (node->WantHiddenTabBarToggle && node->VisibleWindow && (node->VisibleWindow->WindowClass.DockNodeFlagsOverrideSet & ImGuiDockNodeFlags_HiddenTabBar))
12754 node->WantHiddenTabBarToggle = false;
12755
12756 // Apply toggles at a single point of the frame (here!)
12757 if (node->Windows.Size > 1)
12758 node->LocalFlags &= ~ImGuiDockNodeFlags_HiddenTabBar;
12759 else if (node->WantHiddenTabBarToggle)
12760 node->LocalFlags ^= ImGuiDockNodeFlags_HiddenTabBar;
12761 node->WantHiddenTabBarToggle = false;
12762
12763 DockNodeUpdateVisibleFlag(node);
12764 }
12765
DockNodeUpdateVisibleFlag(ImGuiDockNode * node)12766 static void ImGui::DockNodeUpdateVisibleFlag(ImGuiDockNode* node)
12767 {
12768 // Update visibility flag
12769 bool is_visible = (node->ParentNode == NULL) ? node->IsDockSpace() : node->IsCentralNode();
12770 is_visible |= (node->Windows.Size > 0);
12771 is_visible |= (node->ChildNodes[0] && node->ChildNodes[0]->IsVisible);
12772 is_visible |= (node->ChildNodes[1] && node->ChildNodes[1]->IsVisible);
12773 node->IsVisible = is_visible;
12774 }
12775
DockNodeStartMouseMovingWindow(ImGuiDockNode * node,ImGuiWindow * window)12776 static void ImGui::DockNodeStartMouseMovingWindow(ImGuiDockNode* node, ImGuiWindow* window)
12777 {
12778 ImGuiContext& g = *GImGui;
12779 IM_ASSERT(node->WantMouseMove == true);
12780 StartMouseMovingWindow(window);
12781 g.ActiveIdClickOffset = g.IO.MouseClickedPos[0] - node->Pos;
12782 g.MovingWindow = window; // If we are docked into a non moveable root window, StartMouseMovingWindow() won't set g.MovingWindow. Override that decision.
12783 node->WantMouseMove = false;
12784 }
12785
12786 // Update CentralNode, OnlyNodeWithWindows, LastFocusedNodeID. Copy window class.
DockNodeUpdateForRootNode(ImGuiDockNode * node)12787 static void ImGui::DockNodeUpdateForRootNode(ImGuiDockNode* node)
12788 {
12789 DockNodeUpdateVisibleFlagAndInactiveChilds(node);
12790
12791 // FIXME-DOCK: Merge this scan into the one above.
12792 // - Setup central node pointers
12793 // - 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!)
12794 ImGuiDockNodeFindInfoResults results;
12795 DockNodeFindInfo(node, &results);
12796 node->CentralNode = results.CentralNode;
12797 node->OnlyNodeWithWindows = (results.CountNodesWithWindows == 1) ? results.FirstNodeWithWindows : NULL;
12798 if (node->LastFocusedNodeId == 0 && results.FirstNodeWithWindows != NULL)
12799 node->LastFocusedNodeId = results.FirstNodeWithWindows->ID;
12800
12801 // Copy the window class from of our first window so it can be used for proper dock filtering.
12802 // When node has mixed windows, prioritize the class with the most constraint (DockingAllowUnclassed = false) as the reference to copy.
12803 // FIXME-DOCK: We don't recurse properly, this code could be reworked to work from DockNodeUpdateScanRec.
12804 if (ImGuiDockNode* first_node_with_windows = results.FirstNodeWithWindows)
12805 {
12806 node->WindowClass = first_node_with_windows->Windows[0]->WindowClass;
12807 for (int n = 1; n < first_node_with_windows->Windows.Size; n++)
12808 if (first_node_with_windows->Windows[n]->WindowClass.DockingAllowUnclassed == false)
12809 {
12810 node->WindowClass = first_node_with_windows->Windows[n]->WindowClass;
12811 break;
12812 }
12813 }
12814 }
12815
DockNodeUpdate(ImGuiDockNode * node)12816 static void ImGui::DockNodeUpdate(ImGuiDockNode* node)
12817 {
12818 ImGuiContext& g = *GImGui;
12819 IM_ASSERT(node->LastFrameActive != g.FrameCount);
12820 node->LastFrameAlive = g.FrameCount;
12821 node->MarkedForPosSizeWrite = false;
12822
12823 node->CentralNode = node->OnlyNodeWithWindows = NULL;
12824 if (node->IsRootNode())
12825 DockNodeUpdateForRootNode(node);
12826
12827 // Remove tab bar if not needed
12828 if (node->TabBar && node->IsNoTabBar())
12829 DockNodeRemoveTabBar(node);
12830
12831 // 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)
12832 bool want_to_hide_host_window = false;
12833 if (node->Windows.Size <= 1 && node->IsFloatingNode() && node->IsLeafNode())
12834 if (!g.IO.ConfigDockingAlwaysTabBar && (node->Windows.Size == 0 || !node->Windows[0]->WindowClass.DockingAlwaysTabBar))
12835 want_to_hide_host_window = true;
12836 if (want_to_hide_host_window)
12837 {
12838 if (node->Windows.Size == 1)
12839 {
12840 // Floating window pos/size is authoritative
12841 ImGuiWindow* single_window = node->Windows[0];
12842 node->Pos = single_window->Pos;
12843 node->Size = single_window->SizeFull;
12844 node->AuthorityForPos = node->AuthorityForSize = node->AuthorityForViewport = ImGuiDataAuthority_Window;
12845
12846 // Transfer focus immediately so when we revert to a regular window it is immediately selected
12847 if (node->HostWindow && g.NavWindow == node->HostWindow)
12848 FocusWindow(single_window);
12849 if (node->HostWindow)
12850 {
12851 single_window->Viewport = node->HostWindow->Viewport;
12852 single_window->ViewportId = node->HostWindow->ViewportId;
12853 if (node->HostWindow->ViewportOwned)
12854 {
12855 single_window->Viewport->Window = single_window;
12856 single_window->ViewportOwned = true;
12857 }
12858 }
12859 }
12860
12861 DockNodeHideHostWindow(node);
12862 node->State = ImGuiDockNodeState_HostWindowHiddenBecauseSingleWindow;
12863 node->WantCloseAll = false;
12864 node->WantCloseTabId = 0;
12865 node->HasCloseButton = node->HasWindowMenuButton = node->EnableCloseButton = false;
12866 node->LastFrameActive = g.FrameCount;
12867
12868 if (node->WantMouseMove && node->Windows.Size == 1)
12869 DockNodeStartMouseMovingWindow(node, node->Windows[0]);
12870 return;
12871 }
12872
12873 // In some circumstance we will defer creating the host window (so everything will be kept hidden),
12874 // while the expected visible window is resizing itself.
12875 // This is important for first-time (no ini settings restored) single window when io.ConfigDockingAlwaysTabBar is enabled,
12876 // otherwise the node ends up using the minimum window size. Effectively those windows will take an extra frame to show up:
12877 // N+0: Begin(): window created (with no known size), node is created
12878 // N+1: DockNodeUpdate(): node skip creating host window / Begin(): window size applied, not visible
12879 // N+2: DockNodeUpdate(): node can create host window / Begin(): window becomes visible
12880 // We could remove this frame if we could reliably calculate the expected window size during node update, before the Begin() code.
12881 // It would require a generalization of CalcWindowExpectedSize(), probably extracting code away from Begin().
12882 // In reality it isn't very important as user quickly ends up with size data in .ini file.
12883 if (node->IsVisible && node->HostWindow == NULL && node->IsFloatingNode() && node->IsLeafNode())
12884 {
12885 IM_ASSERT(node->Windows.Size > 0);
12886 ImGuiWindow* ref_window = NULL;
12887 if (node->SelectedTabId != 0) // Note that we prune single-window-node settings on .ini loading, so this is generally 0 for them!
12888 ref_window = DockNodeFindWindowByID(node, node->SelectedTabId);
12889 if (ref_window == NULL)
12890 ref_window = node->Windows[0];
12891 if (ref_window->AutoFitFramesX > 0 || ref_window->AutoFitFramesY > 0)
12892 {
12893 node->State = ImGuiDockNodeState_HostWindowHiddenBecauseWindowsAreResizing;
12894 return;
12895 }
12896 }
12897
12898 const ImGuiDockNodeFlags node_flags = node->GetMergedFlags();
12899
12900 // Bind or create host window
12901 ImGuiWindow* host_window = NULL;
12902 bool beginned_into_host_window = false;
12903 if (node->IsDockSpace())
12904 {
12905 // [Explicit root dockspace node]
12906 IM_ASSERT(node->HostWindow);
12907 node->EnableCloseButton = false;
12908 node->HasCloseButton = (node_flags & ImGuiDockNodeFlags_NoCloseButton) == 0;
12909 node->HasWindowMenuButton = (node_flags & ImGuiDockNodeFlags_NoWindowMenuButton) == 0;
12910 host_window = node->HostWindow;
12911 }
12912 else
12913 {
12914 // [Automatic root or child nodes]
12915 node->EnableCloseButton = false;
12916 node->HasCloseButton = (node->Windows.Size > 0) && (node_flags & ImGuiDockNodeFlags_NoCloseButton) == 0;
12917 node->HasWindowMenuButton = (node->Windows.Size > 0) && (node_flags & ImGuiDockNodeFlags_NoWindowMenuButton) == 0;
12918 for (int window_n = 0; window_n < node->Windows.Size; window_n++)
12919 {
12920 // FIXME-DOCK: Setting DockIsActive here means that for single active window in a leaf node, DockIsActive will be cleared until the next Begin() call.
12921 ImGuiWindow* window = node->Windows[window_n];
12922 window->DockIsActive = (node->Windows.Size > 1);
12923 node->EnableCloseButton |= window->HasCloseButton;
12924 }
12925
12926 if (node->IsRootNode() && node->IsVisible)
12927 {
12928 ImGuiWindow* ref_window = (node->Windows.Size > 0) ? node->Windows[0] : NULL;
12929
12930 // Sync Pos
12931 if (node->AuthorityForPos == ImGuiDataAuthority_Window && ref_window)
12932 SetNextWindowPos(ref_window->Pos);
12933 else if (node->AuthorityForPos == ImGuiDataAuthority_DockNode)
12934 SetNextWindowPos(node->Pos);
12935
12936 // Sync Size
12937 if (node->AuthorityForSize == ImGuiDataAuthority_Window && ref_window)
12938 SetNextWindowSize(ref_window->SizeFull);
12939 else if (node->AuthorityForSize == ImGuiDataAuthority_DockNode)
12940 SetNextWindowSize(node->Size);
12941
12942 // Sync Collapsed
12943 if (node->AuthorityForSize == ImGuiDataAuthority_Window && ref_window)
12944 SetNextWindowCollapsed(ref_window->Collapsed);
12945
12946 // Sync Viewport
12947 if (node->AuthorityForViewport == ImGuiDataAuthority_Window && ref_window)
12948 SetNextWindowViewport(ref_window->ViewportId);
12949
12950 SetNextWindowClass(&node->WindowClass);
12951
12952 // Begin into the host window
12953 char window_label[20];
12954 DockNodeGetHostWindowTitle(node, window_label, IM_ARRAYSIZE(window_label));
12955 ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_DockNodeHost;
12956 window_flags |= ImGuiWindowFlags_NoFocusOnAppearing;
12957 window_flags |= ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoCollapse;
12958 window_flags |= ImGuiWindowFlags_NoTitleBar;
12959
12960 PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
12961 Begin(window_label, NULL, window_flags);
12962 PopStyleVar();
12963 beginned_into_host_window = true;
12964
12965 node->HostWindow = host_window = g.CurrentWindow;
12966 host_window->DockNodeAsHost = node;
12967 host_window->DC.CursorPos = host_window->Pos;
12968 node->Pos = host_window->Pos;
12969 node->Size = host_window->Size;
12970
12971 // We set ImGuiWindowFlags_NoFocusOnAppearing because we don't want the host window to take full focus (e.g. steal NavWindow)
12972 // But we still it bring it to the front of display. There's no way to choose this precise behavior via window flags.
12973 // 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.
12974 // 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
12975 // 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
12976 // after the dock host window, losing their top-most status.
12977 if (node->HostWindow->Appearing)
12978 BringWindowToDisplayFront(node->HostWindow);
12979
12980 node->AuthorityForPos = node->AuthorityForSize = node->AuthorityForViewport = ImGuiDataAuthority_Auto;
12981 }
12982 else if (node->ParentNode)
12983 {
12984 node->HostWindow = host_window = node->ParentNode->HostWindow;
12985 node->AuthorityForPos = node->AuthorityForSize = node->AuthorityForViewport = ImGuiDataAuthority_Auto;
12986 }
12987 if (node->WantMouseMove && node->HostWindow)
12988 DockNodeStartMouseMovingWindow(node, node->HostWindow);
12989 }
12990
12991 // Update focused node (the one whose title bar is highlight) within a node tree
12992 if (node->IsSplitNode())
12993 IM_ASSERT(node->TabBar == NULL);
12994 if (node->IsRootNode())
12995 if (g.NavWindow && g.NavWindow->RootWindowDockStop->DockNode && g.NavWindow->RootWindowDockStop->ParentWindow == host_window)
12996 node->LastFocusedNodeId = g.NavWindow->RootWindowDockStop->DockNode->ID;
12997
12998 // We need to draw a background at the root level if requested by ImGuiDockNodeFlags_PassthruCentralNode, but we will only know the correct pos/size after
12999 // processing the resizing splitters. So we are using the DrawList channel splitting facility to submit drawing primitives out of order!
13000 const bool render_dockspace_bg = node->IsRootNode() && host_window && (node_flags & ImGuiDockNodeFlags_PassthruCentralNode) != 0;
13001 if (render_dockspace_bg)
13002 {
13003 host_window->DrawList->ChannelsSplit(2);
13004 host_window->DrawList->ChannelsSetCurrent(1);
13005 }
13006
13007 // Register a hit-test hole in the window unless we are currently dragging a window that is compatible with our dockspace
13008 const ImGuiDockNode* central_node = node->CentralNode;
13009 const bool central_node_hole = node->IsRootNode() && host_window && (node_flags & ImGuiDockNodeFlags_PassthruCentralNode) != 0 && central_node != NULL && central_node->IsEmpty();
13010 bool central_node_hole_register_hit_test_hole = central_node_hole;
13011 if (central_node_hole)
13012 if (const ImGuiPayload* payload = ImGui::GetDragDropPayload())
13013 if (payload->IsDataType(IMGUI_PAYLOAD_TYPE_WINDOW) && DockNodeIsDropAllowed(host_window, *(ImGuiWindow**)payload->Data))
13014 central_node_hole_register_hit_test_hole = false;
13015 if (central_node_hole_register_hit_test_hole)
13016 {
13017 // Add a little padding to match the "resize from edges" behavior and allow grabbing the splitter easily.
13018 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
13019 ImRect central_hole(central_node->Pos, central_node->Pos + central_node->Size);
13020 central_hole.Expand(ImVec2(-WINDOWS_RESIZE_FROM_EDGES_HALF_THICKNESS, -WINDOWS_RESIZE_FROM_EDGES_HALF_THICKNESS));
13021 if (central_node_hole && !central_hole.IsInverted())
13022 {
13023 SetWindowHitTestHole(host_window, central_hole.Min, central_hole.Max - central_hole.Min);
13024 SetWindowHitTestHole(host_window->ParentWindow, central_hole.Min, central_hole.Max - central_hole.Min);
13025 }
13026 }
13027
13028 // Update position/size, process and draw resizing splitters
13029 if (node->IsRootNode() && host_window)
13030 {
13031 DockNodeTreeUpdatePosSize(node, host_window->Pos, host_window->Size);
13032 DockNodeTreeUpdateSplitter(node);
13033 }
13034
13035 // Draw empty node background (currently can only be the Central Node)
13036 if (host_window && node->IsEmpty() && node->IsVisible && !(node_flags & ImGuiDockNodeFlags_PassthruCentralNode))
13037 host_window->DrawList->AddRectFilled(node->Pos, node->Pos + node->Size, GetColorU32(ImGuiCol_DockingEmptyBg));
13038
13039 // Draw whole dockspace background if ImGuiDockNodeFlags_PassthruCentralNode if set.
13040 if (render_dockspace_bg && node->IsVisible)
13041 {
13042 host_window->DrawList->ChannelsSetCurrent(0);
13043 if (central_node_hole)
13044 RenderRectFilledWithHole(host_window->DrawList, node->Rect(), central_node->Rect(), GetColorU32(ImGuiCol_WindowBg), 0.0f);
13045 else
13046 host_window->DrawList->AddRectFilled(node->Pos, node->Pos + node->Size, GetColorU32(ImGuiCol_WindowBg), 0.0f);
13047 host_window->DrawList->ChannelsMerge();
13048 }
13049
13050 // Draw and populate Tab Bar
13051 if (host_window && node->Windows.Size > 0)
13052 {
13053 DockNodeUpdateTabBar(node, host_window);
13054 }
13055 else
13056 {
13057 node->WantCloseAll = false;
13058 node->WantCloseTabId = 0;
13059 node->IsFocused = false;
13060 }
13061 if (node->TabBar && node->TabBar->SelectedTabId)
13062 node->SelectedTabId = node->TabBar->SelectedTabId;
13063 else if (node->Windows.Size > 0)
13064 node->SelectedTabId = node->Windows[0]->ID;
13065
13066 // Draw payload drop target
13067 if (host_window && node->IsVisible)
13068 if (node->IsRootNode() && (g.MovingWindow == NULL || g.MovingWindow->RootWindow != host_window))
13069 BeginDockableDragDropTarget(host_window);
13070
13071 // We update this after DockNodeUpdateTabBar()
13072 node->LastFrameActive = g.FrameCount;
13073
13074 // Recurse into children
13075 // FIXME-DOCK FIXME-OPT: Should not need to recurse into children
13076 if (host_window)
13077 {
13078 if (node->ChildNodes[0])
13079 DockNodeUpdate(node->ChildNodes[0]);
13080 if (node->ChildNodes[1])
13081 DockNodeUpdate(node->ChildNodes[1]);
13082
13083 // Render outer borders last (after the tab bar)
13084 if (node->IsRootNode())
13085 RenderWindowOuterBorders(host_window);
13086 }
13087
13088 // End host window
13089 if (beginned_into_host_window) //-V1020
13090 End();
13091 }
13092
13093 // 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.
TabItemComparerByDockOrder(const void * lhs,const void * rhs)13094 static int IMGUI_CDECL TabItemComparerByDockOrder(const void* lhs, const void* rhs)
13095 {
13096 ImGuiWindow* a = ((const ImGuiTabItem*)lhs)->Window;
13097 ImGuiWindow* b = ((const ImGuiTabItem*)rhs)->Window;
13098 if (int d = ((a->DockOrder == -1) ? INT_MAX : a->DockOrder) - ((b->DockOrder == -1) ? INT_MAX : b->DockOrder))
13099 return d;
13100 return (a->BeginOrderWithinContext - b->BeginOrderWithinContext);
13101 }
13102
DockNodeUpdateWindowMenu(ImGuiDockNode * node,ImGuiTabBar * tab_bar)13103 static ImGuiID ImGui::DockNodeUpdateWindowMenu(ImGuiDockNode* node, ImGuiTabBar* tab_bar)
13104 {
13105 // Try to position the menu so it is more likely to stays within the same viewport
13106 ImGuiContext& g = *GImGui;
13107 ImGuiID ret_tab_id = 0;
13108 if (g.Style.WindowMenuButtonPosition == ImGuiDir_Left)
13109 SetNextWindowPos(ImVec2(node->Pos.x, node->Pos.y + GetFrameHeight()), ImGuiCond_Always, ImVec2(0.0f, 0.0f));
13110 else
13111 SetNextWindowPos(ImVec2(node->Pos.x + node->Size.x, node->Pos.y + GetFrameHeight()), ImGuiCond_Always, ImVec2(1.0f, 0.0f));
13112 if (BeginPopup("#WindowMenu"))
13113 {
13114 node->IsFocused = true;
13115 if (tab_bar->Tabs.Size == 1)
13116 {
13117 if (MenuItem("Hide tab bar", NULL, node->IsHiddenTabBar()))
13118 node->WantHiddenTabBarToggle = true;
13119 }
13120 else
13121 {
13122 for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
13123 {
13124 ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
13125 IM_ASSERT(tab->Window != NULL);
13126 if (Selectable(tab->Window->Name, tab->ID == tab_bar->SelectedTabId))
13127 ret_tab_id = tab->ID;
13128 SameLine();
13129 Text(" ");
13130 }
13131 }
13132 EndPopup();
13133 }
13134 return ret_tab_id;
13135 }
13136
DockNodeUpdateTabBar(ImGuiDockNode * node,ImGuiWindow * host_window)13137 static void ImGui::DockNodeUpdateTabBar(ImGuiDockNode* node, ImGuiWindow* host_window)
13138 {
13139 ImGuiContext& g = *GImGui;
13140 ImGuiStyle& style = g.Style;
13141
13142 const bool node_was_active = (node->LastFrameActive + 1 == g.FrameCount);
13143 const bool closed_all = node->WantCloseAll && node_was_active;
13144 const ImGuiID closed_one = node->WantCloseTabId && node_was_active;
13145 node->WantCloseAll = false;
13146 node->WantCloseTabId = 0;
13147
13148 // Decide if we should use a focused title bar color
13149 bool is_focused = false;
13150 ImGuiDockNode* root_node = DockNodeGetRootNode(node);
13151 if (g.NavWindowingTarget)
13152 is_focused = (g.NavWindowingTarget->DockNode == node);
13153 else if (g.NavWindow && g.NavWindow->RootWindowForTitleBarHighlight == host_window->RootWindow && root_node->LastFocusedNodeId == node->ID)
13154 is_focused = true;
13155
13156 // Hidden tab bar will show a triangle on the upper-left (in Begin)
13157 if (node->IsHiddenTabBar() || node->IsNoTabBar())
13158 {
13159 node->VisibleWindow = (node->Windows.Size > 0) ? node->Windows[0] : NULL;
13160 node->IsFocused = is_focused;
13161 if (is_focused)
13162 node->LastFrameFocused = g.FrameCount;
13163 if (node->VisibleWindow)
13164 {
13165 // Notify root of visible window (used to display title in OS task bar)
13166 if (is_focused || root_node->VisibleWindow == NULL)
13167 root_node->VisibleWindow = node->VisibleWindow;
13168 if (node->TabBar)
13169 node->TabBar->VisibleTabId = node->VisibleWindow->ID;
13170 }
13171 return;
13172 }
13173
13174 // 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
13175 bool backup_skip_item = host_window->SkipItems;
13176 if (!node->IsDockSpace())
13177 {
13178 host_window->SkipItems = false;
13179 host_window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;
13180 host_window->DC.NavLayerCurrentMask = (1 << ImGuiNavLayer_Menu);
13181 }
13182
13183 // Use PushOverrideID() instead of PushID() to use the node id _without_ the host window ID.
13184 // 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,
13185 // as docked windows themselves will override the stack with their own root ID.
13186 PushOverrideID(node->ID);
13187 ImGuiTabBar* tab_bar = node->TabBar;
13188 bool tab_bar_is_recreated = (tab_bar == NULL); // Tab bar are automatically destroyed when a node gets hidden
13189 if (tab_bar == NULL)
13190 {
13191 DockNodeAddTabBar(node);
13192 tab_bar = node->TabBar;
13193 }
13194
13195 ImGuiID focus_tab_id = 0;
13196 node->IsFocused = is_focused;
13197
13198 const ImGuiDockNodeFlags node_flags = node->GetMergedFlags();
13199 const bool has_window_menu_button = (node_flags & ImGuiDockNodeFlags_NoWindowMenuButton) == 0;
13200 const bool has_close_button = (node_flags & ImGuiDockNodeFlags_NoCloseButton) == 0;
13201
13202 // In a dock node, the Collapse Button turns into the Window Menu button.
13203 // FIXME-DOCK FIXME-OPT: Could we recycle popups id across multiple dock nodes?
13204 if (has_window_menu_button && IsPopupOpen("#WindowMenu"))
13205 {
13206 if (ImGuiID tab_id = DockNodeUpdateWindowMenu(node, tab_bar))
13207 focus_tab_id = tab_bar->NextSelectedTabId = tab_id;
13208 is_focused |= node->IsFocused;
13209 }
13210
13211 // Layout
13212 ImRect title_bar_rect, tab_bar_rect;
13213 ImVec2 window_menu_button_pos;
13214 DockNodeCalcTabBarLayout(node, &title_bar_rect, &tab_bar_rect, &window_menu_button_pos);
13215
13216 // Title bar
13217 if (is_focused)
13218 node->LastFrameFocused = g.FrameCount;
13219 ImU32 title_bar_col = GetColorU32(host_window->Collapsed ? ImGuiCol_TitleBgCollapsed : is_focused ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg);
13220 host_window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, host_window->WindowRounding, ImDrawCornerFlags_Top);
13221
13222 // Docking/Collapse button
13223 if (has_window_menu_button)
13224 {
13225 if (CollapseButton(host_window->GetID("#COLLAPSE"), window_menu_button_pos, node))
13226 OpenPopup("#WindowMenu");
13227 if (IsItemActive())
13228 focus_tab_id = tab_bar->SelectedTabId;
13229 }
13230
13231 // Submit new tabs and apply NavWindow focus back to the tab bar. They will be added as Unsorted and sorted below based on relative DockOrder value.
13232 const int tabs_count_old = tab_bar->Tabs.Size;
13233 for (int window_n = 0; window_n < node->Windows.Size; window_n++)
13234 {
13235 ImGuiWindow* window = node->Windows[window_n];
13236 if (g.NavWindow && g.NavWindow->RootWindowDockStop == window)
13237 tab_bar->SelectedTabId = window->ID;
13238 if (TabBarFindTabByID(tab_bar, window->ID) == NULL)
13239 TabBarAddTab(tab_bar, ImGuiTabItemFlags_Unsorted, window);
13240 }
13241
13242 // If multiple tabs are appearing on the same frame, sort them based on their persistent DockOrder value
13243 int tabs_unsorted_start = tab_bar->Tabs.Size;
13244 for (int tab_n = tab_bar->Tabs.Size - 1; tab_n >= 0 && (tab_bar->Tabs[tab_n].Flags & ImGuiTabItemFlags_Unsorted); tab_n--)
13245 {
13246 // 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?
13247 tab_bar->Tabs[tab_n].Flags &= ~ImGuiTabItemFlags_Unsorted;
13248 tabs_unsorted_start = tab_n;
13249 }
13250 if (tab_bar->Tabs.Size > tabs_unsorted_start)
13251 {
13252 IMGUI_DEBUG_LOG_DOCKING("In node 0x%08X: %d new appearing tabs:%s\n", node->ID, tab_bar->Tabs.Size - tabs_unsorted_start, (tab_bar->Tabs.Size > tabs_unsorted_start + 1) ? " (will sort)" : "");
13253 for (int tab_n = tabs_unsorted_start; tab_n < tab_bar->Tabs.Size; tab_n++)
13254 IMGUI_DEBUG_LOG_DOCKING(" - Tab '%s' Order %d\n", tab_bar->Tabs[tab_n].Window->Name, tab_bar->Tabs[tab_n].Window->DockOrder);
13255 if (tab_bar->Tabs.Size > tabs_unsorted_start + 1)
13256 ImQsort(tab_bar->Tabs.Data + tabs_unsorted_start, tab_bar->Tabs.Size - tabs_unsorted_start, sizeof(ImGuiTabItem), TabItemComparerByDockOrder);
13257 }
13258
13259 // Selected newly added tabs, or persistent tab ID if the tab bar was just recreated
13260 if (tab_bar_is_recreated && TabBarFindTabByID(tab_bar, node->SelectedTabId) != NULL)
13261 tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = node->SelectedTabId;
13262 else if (tab_bar->Tabs.Size > tabs_count_old)
13263 tab_bar->SelectedTabId = tab_bar->NextSelectedTabId = tab_bar->Tabs.back().Window->ID;
13264
13265 // Begin tab bar
13266 ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_AutoSelectNewTabs; // | ImGuiTabBarFlags_NoTabListScrollingButtons);
13267 tab_bar_flags |= ImGuiTabBarFlags_SaveSettings | ImGuiTabBarFlags_DockNode;
13268 if (!host_window->Collapsed && is_focused)
13269 tab_bar_flags |= ImGuiTabBarFlags_IsFocused;
13270 BeginTabBarEx(tab_bar, tab_bar_rect, tab_bar_flags, node);
13271 //host_window->DrawList->AddRect(tab_bar_rect.Min, tab_bar_rect.Max, IM_COL32(255,0,255,255));
13272
13273 // Submit actual tabs
13274 node->VisibleWindow = NULL;
13275 for (int window_n = 0; window_n < node->Windows.Size; window_n++)
13276 {
13277 ImGuiWindow* window = node->Windows[window_n];
13278 if ((closed_all || closed_one == window->ID) && window->HasCloseButton && !(window->Flags & ImGuiWindowFlags_UnsavedDocument))
13279 continue;
13280 if (window->LastFrameActive + 1 >= g.FrameCount || !node_was_active)
13281 {
13282 ImGuiTabItemFlags tab_item_flags = 0;
13283 if (window->Flags & ImGuiWindowFlags_UnsavedDocument)
13284 tab_item_flags |= ImGuiTabItemFlags_UnsavedDocument;
13285 if (tab_bar->Flags & ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)
13286 tab_item_flags |= ImGuiTabItemFlags_NoCloseWithMiddleMouseButton;
13287
13288 bool tab_open = true;
13289 TabItemEx(tab_bar, window->Name, window->HasCloseButton ? &tab_open : NULL, tab_item_flags, window);
13290 if (!tab_open)
13291 node->WantCloseTabId = window->ID;
13292 if (tab_bar->VisibleTabId == window->ID)
13293 node->VisibleWindow = window;
13294
13295 // Store last item data so it can be queried with IsItemXXX functions after the user Begin() call
13296 window->DockTabItemStatusFlags = host_window->DC.LastItemStatusFlags;
13297 window->DockTabItemRect = host_window->DC.LastItemRect;
13298
13299 // Update navigation ID on menu layer
13300 if (g.NavWindow && g.NavWindow->RootWindowDockStop == window && (window->DC.NavLayerActiveMask & (1 << ImGuiNavLayer_Menu)) == 0)
13301 host_window->NavLastIds[1] = window->ID;
13302 }
13303 }
13304
13305 // Notify root of visible window (used to display title in OS task bar)
13306 if (node->VisibleWindow)
13307 if (is_focused || root_node->VisibleWindow == NULL)
13308 root_node->VisibleWindow = node->VisibleWindow;
13309
13310 // Close button (after VisibleWindow was updated)
13311 // Note that VisibleWindow may have been overrided by CTRL+Tabbing, so VisibleWindow->ID may be != from tab_bar->SelectedTabId
13312 if (has_close_button && node->VisibleWindow)
13313 {
13314 if (!node->VisibleWindow->HasCloseButton)
13315 {
13316 PushItemFlag(ImGuiItemFlags_Disabled, true);
13317 PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_Text] * ImVec4(1.0f,1.0f,1.0f,0.5f));
13318 }
13319 const float button_sz = g.FontSize;
13320 if (CloseButton(host_window->GetID("#CLOSE"), title_bar_rect.GetTR() + ImVec2(-style.FramePadding.x * 2.0f - button_sz, 0.0f)))
13321 if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_bar->VisibleTabId))
13322 {
13323 node->WantCloseTabId = tab->ID;
13324 TabBarCloseTab(tab_bar, tab);
13325 }
13326 //if (IsItemActive())
13327 // focus_tab_id = tab_bar->SelectedTabId;
13328 if (!node->VisibleWindow->HasCloseButton)
13329 {
13330 PopStyleColor();
13331 PopItemFlag();
13332 }
13333 }
13334
13335 // When clicking on the title bar outside of tabs, we still focus the selected tab for that node
13336 // FIXME: TabItem use AllowItemOverlap so we manually perform a more specific test for now (hovered || held)
13337 ImGuiID title_bar_id = host_window->GetID("#TITLEBAR");
13338 if (g.HoveredId == 0 || g.HoveredId == title_bar_id || g.ActiveId == title_bar_id)
13339 {
13340 bool held;
13341 ButtonBehavior(title_bar_rect, title_bar_id, NULL, &held);
13342 if (held)
13343 {
13344 if (IsMouseClicked(0))
13345 focus_tab_id = tab_bar->SelectedTabId;
13346
13347 // Forward moving request to selected window
13348 if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, tab_bar->SelectedTabId))
13349 StartMouseMovingWindowOrNode(tab->Window, node, false);
13350 }
13351 }
13352
13353 // Forward focus from host node to selected window
13354 //if (is_focused && g.NavWindow == host_window && !g.NavWindowingTarget)
13355 // focus_tab_id = tab_bar->SelectedTabId;
13356
13357 // When clicked on a tab we requested focus to the docked child
13358 // This overrides the value set by "forward focus from host node to selected window".
13359 if (tab_bar->NextSelectedTabId)
13360 focus_tab_id = tab_bar->NextSelectedTabId;
13361
13362 // Apply navigation focus
13363 if (focus_tab_id != 0)
13364 if (ImGuiTabItem* tab = TabBarFindTabByID(tab_bar, focus_tab_id))
13365 {
13366 FocusWindow(tab->Window);
13367 NavInitWindow(tab->Window, false);
13368 }
13369
13370 EndTabBar();
13371 PopID();
13372
13373 // Restore SkipItems flag
13374 if (!node->IsDockSpace())
13375 {
13376 host_window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
13377 host_window->DC.NavLayerCurrentMask = (1 << ImGuiNavLayer_Main);
13378 host_window->SkipItems = backup_skip_item;
13379 }
13380 }
13381
DockNodeAddTabBar(ImGuiDockNode * node)13382 static void ImGui::DockNodeAddTabBar(ImGuiDockNode* node)
13383 {
13384 IM_ASSERT(node->TabBar == NULL);
13385 node->TabBar = IM_NEW(ImGuiTabBar);
13386 }
13387
DockNodeRemoveTabBar(ImGuiDockNode * node)13388 static void ImGui::DockNodeRemoveTabBar(ImGuiDockNode* node)
13389 {
13390 if (node->TabBar == NULL)
13391 return;
13392 IM_DELETE(node->TabBar);
13393 node->TabBar = NULL;
13394 }
13395
DockNodeIsDropAllowedOne(ImGuiWindow * payload,ImGuiWindow * host_window)13396 static bool DockNodeIsDropAllowedOne(ImGuiWindow* payload, ImGuiWindow* host_window)
13397 {
13398 if (host_window->DockNodeAsHost && host_window->DockNodeAsHost->IsDockSpace() && payload->BeginOrderWithinContext < host_window->BeginOrderWithinContext)
13399 return false;
13400
13401 ImGuiWindowClass* host_class = host_window->DockNodeAsHost ? &host_window->DockNodeAsHost->WindowClass : &host_window->WindowClass;
13402 ImGuiWindowClass* payload_class = &payload->WindowClass;
13403 if (host_class->ClassId != payload_class->ClassId)
13404 {
13405 if (host_class->ClassId != 0 && host_class->DockingAllowUnclassed && payload_class->ClassId == 0)
13406 return true;
13407 if (payload_class->ClassId != 0 && payload_class->DockingAllowUnclassed && host_class->ClassId == 0)
13408 return true;
13409 return false;
13410 }
13411
13412 return true;
13413 }
13414
DockNodeIsDropAllowed(ImGuiWindow * host_window,ImGuiWindow * root_payload)13415 static bool ImGui::DockNodeIsDropAllowed(ImGuiWindow* host_window, ImGuiWindow* root_payload)
13416 {
13417 if (root_payload->DockNodeAsHost && root_payload->DockNodeAsHost->IsSplitNode())
13418 return true;
13419
13420 const int payload_count = root_payload->DockNodeAsHost ? root_payload->DockNodeAsHost->Windows.Size : 1;
13421 for (int payload_n = 0; payload_n < payload_count; payload_n++)
13422 {
13423 ImGuiWindow* payload = root_payload->DockNodeAsHost ? root_payload->DockNodeAsHost->Windows[payload_n] : root_payload;
13424 if (DockNodeIsDropAllowedOne(payload, host_window))
13425 return true;
13426 }
13427 return false;
13428 }
13429
13430 // window menu button == collapse button when not in a dock node.
13431 // FIXME: This is similar to RenderWindowTitleBarContents, may want to share code.
DockNodeCalcTabBarLayout(const ImGuiDockNode * node,ImRect * out_title_rect,ImRect * out_tab_bar_rect,ImVec2 * out_window_menu_button_pos)13432 static void ImGui::DockNodeCalcTabBarLayout(const ImGuiDockNode* node, ImRect* out_title_rect, ImRect* out_tab_bar_rect, ImVec2* out_window_menu_button_pos)
13433 {
13434 ImGuiContext& g = *GImGui;
13435 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);
13436 if (out_title_rect) { *out_title_rect = r; }
13437
13438 ImVec2 window_menu_button_pos = r.Min;
13439 r.Min.x += g.Style.FramePadding.x;
13440 r.Max.x -= g.Style.FramePadding.x;
13441 if (node->HasCloseButton)
13442 {
13443 r.Max.x -= g.FontSize;// +1.0f; // In DockNodeUpdateTabBar() we currently display a disabled close button even if there is none.
13444 }
13445 if (node->HasWindowMenuButton && g.Style.WindowMenuButtonPosition == ImGuiDir_Left)
13446 {
13447 r.Min.x += g.FontSize; // + g.Style.ItemInnerSpacing.x; // <-- Adding ItemInnerSpacing makes the title text moves slightly when in a docking tab bar. Instead we adjusted RenderArrowDockMenu()
13448 }
13449 else if (node->HasWindowMenuButton && g.Style.WindowMenuButtonPosition == ImGuiDir_Right)
13450 {
13451 r.Max.x -= g.FontSize + g.Style.FramePadding.x;
13452 window_menu_button_pos = ImVec2(r.Max.x, r.Min.y);
13453 }
13454 if (out_tab_bar_rect) { *out_tab_bar_rect = r; }
13455 if (out_window_menu_button_pos) { *out_window_menu_button_pos = window_menu_button_pos; }
13456 }
13457
DockNodeCalcSplitRects(ImVec2 & pos_old,ImVec2 & size_old,ImVec2 & pos_new,ImVec2 & size_new,ImGuiDir dir,ImVec2 size_new_desired)13458 void ImGui::DockNodeCalcSplitRects(ImVec2& pos_old, ImVec2& size_old, ImVec2& pos_new, ImVec2& size_new, ImGuiDir dir, ImVec2 size_new_desired)
13459 {
13460 ImGuiContext& g = *GImGui;
13461 const float dock_spacing = g.Style.ItemInnerSpacing.x;
13462 const ImGuiAxis axis = (dir == ImGuiDir_Left || dir == ImGuiDir_Right) ? ImGuiAxis_X : ImGuiAxis_Y;
13463 pos_new[axis ^ 1] = pos_old[axis ^ 1];
13464 size_new[axis ^ 1] = size_old[axis ^ 1];
13465
13466 // Distribute size on given axis (with a desired size or equally)
13467 const float w_avail = size_old[axis] - dock_spacing;
13468 if (size_new_desired[axis] > 0.0f && size_new_desired[axis] <= w_avail * 0.5f)
13469 {
13470 size_new[axis] = size_new_desired[axis];
13471 size_old[axis] = IM_FLOOR(w_avail - size_new[axis]);
13472 }
13473 else
13474 {
13475 size_new[axis] = IM_FLOOR(w_avail * 0.5f);
13476 size_old[axis] = IM_FLOOR(w_avail - size_new[axis]);
13477 }
13478
13479 // Position each node
13480 if (dir == ImGuiDir_Right || dir == ImGuiDir_Down)
13481 {
13482 pos_new[axis] = pos_old[axis] + size_old[axis] + dock_spacing;
13483 }
13484 else if (dir == ImGuiDir_Left || dir == ImGuiDir_Up)
13485 {
13486 pos_new[axis] = pos_old[axis];
13487 pos_old[axis] = pos_new[axis] + size_new[axis] + dock_spacing;
13488 }
13489 }
13490
13491 // Retrieve the drop rectangles for a given direction or for the center + perform hit testing.
DockNodeCalcDropRectsAndTestMousePos(const ImRect & parent,ImGuiDir dir,ImRect & out_r,bool outer_docking,ImVec2 * test_mouse_pos)13492 bool ImGui::DockNodeCalcDropRectsAndTestMousePos(const ImRect& parent, ImGuiDir dir, ImRect& out_r, bool outer_docking, ImVec2* test_mouse_pos)
13493 {
13494 ImGuiContext& g = *GImGui;
13495
13496 const float parent_smaller_axis = ImMin(parent.GetWidth(), parent.GetHeight());
13497 const float hs_for_central_nodes = ImMin(g.FontSize * 1.5f, ImMax(g.FontSize * 0.5f, parent_smaller_axis / 8.0f));
13498 float hs_w; // Half-size, longer axis
13499 float hs_h; // Half-size, smaller axis
13500 ImVec2 off; // Distance from edge or center
13501 if (outer_docking)
13502 {
13503 //hs_w = ImFloor(ImClamp(parent_smaller_axis - hs_for_central_nodes * 4.0f, g.FontSize * 0.5f, g.FontSize * 8.0f));
13504 //hs_h = ImFloor(hs_w * 0.15f);
13505 //off = ImVec2(ImFloor(parent.GetWidth() * 0.5f - GetFrameHeightWithSpacing() * 1.4f - hs_h), ImFloor(parent.GetHeight() * 0.5f - GetFrameHeightWithSpacing() * 1.4f - hs_h));
13506 hs_w = ImFloor(hs_for_central_nodes * 1.50f);
13507 hs_h = ImFloor(hs_for_central_nodes * 0.80f);
13508 off = ImVec2(ImFloor(parent.GetWidth() * 0.5f - hs_h), ImFloor(parent.GetHeight() * 0.5f - hs_h));
13509 }
13510 else
13511 {
13512 hs_w = ImFloor(hs_for_central_nodes);
13513 hs_h = ImFloor(hs_for_central_nodes * 0.90f);
13514 off = ImVec2(ImFloor(hs_w * 2.40f), ImFloor(hs_w * 2.40f));
13515 }
13516
13517 ImVec2 c = ImFloor(parent.GetCenter());
13518 if (dir == ImGuiDir_None) { out_r = ImRect(c.x - hs_w, c.y - hs_w, c.x + hs_w, c.y + hs_w); }
13519 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); }
13520 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); }
13521 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); }
13522 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); }
13523
13524 if (test_mouse_pos == NULL)
13525 return false;
13526
13527 ImRect hit_r = out_r;
13528 if (!outer_docking)
13529 {
13530 // Custom hit testing for the 5-way selection, designed to reduce flickering when moving diagonally between sides
13531 hit_r.Expand(ImFloor(hs_w * 0.30f));
13532 ImVec2 mouse_delta = (*test_mouse_pos - c);
13533 float mouse_delta_len2 = ImLengthSqr(mouse_delta);
13534 float r_threshold_center = hs_w * 1.4f;
13535 float r_threshold_sides = hs_w * (1.4f + 1.2f);
13536 if (mouse_delta_len2 < r_threshold_center * r_threshold_center)
13537 return (dir == ImGuiDir_None);
13538 if (mouse_delta_len2 < r_threshold_sides * r_threshold_sides)
13539 return (dir == ImGetDirQuadrantFromDelta(mouse_delta.x, mouse_delta.y));
13540 }
13541 return hit_r.Contains(*test_mouse_pos);
13542 }
13543
13544 // host_node may be NULL if the window doesn't have a DockNode already.
13545 // FIXME-DOCK: This is misnamed since it's also doing the filtering.
DockNodePreviewDockSetup(ImGuiWindow * host_window,ImGuiDockNode * host_node,ImGuiWindow * root_payload,ImGuiDockPreviewData * data,bool is_explicit_target,bool is_outer_docking)13546 static void ImGui::DockNodePreviewDockSetup(ImGuiWindow* host_window, ImGuiDockNode* host_node, ImGuiWindow* root_payload, ImGuiDockPreviewData* data, bool is_explicit_target, bool is_outer_docking)
13547 {
13548 ImGuiContext& g = *GImGui;
13549
13550 // There is an edge case when docking into a dockspace which only has inactive nodes.
13551 // In this case DockNodeTreeFindNodeByPos() will have selected a leaf node which is inactive.
13552 // Because the inactive leaf node doesn't have proper pos/size yet, we'll use the root node as reference.
13553 ImGuiDockNode* root_payload_as_host = root_payload->DockNodeAsHost;
13554 ImGuiDockNode* ref_node_for_rect = (host_node && !host_node->IsVisible) ? DockNodeGetRootNode(host_node) : host_node;
13555 if (ref_node_for_rect)
13556 IM_ASSERT(ref_node_for_rect->IsVisible);
13557
13558 // Filter, figure out where we are allowed to dock
13559 ImGuiDockNodeFlags src_node_flags = root_payload_as_host ? root_payload_as_host->GetMergedFlags() : root_payload->WindowClass.DockNodeFlagsOverrideSet;
13560 ImGuiDockNodeFlags dst_node_flags = host_node ? host_node->GetMergedFlags() : host_window->WindowClass.DockNodeFlagsOverrideSet;
13561 data->IsCenterAvailable = true;
13562 if (is_outer_docking)
13563 data->IsCenterAvailable = false;
13564 else if (dst_node_flags & ImGuiDockNodeFlags_NoDocking)
13565 data->IsCenterAvailable = false;
13566 else if (host_node && (dst_node_flags & ImGuiDockNodeFlags_NoDockingInCentralNode) && host_node->IsCentralNode())
13567 data->IsCenterAvailable = false;
13568 else if ((!host_node || !host_node->IsEmpty()) && root_payload_as_host && root_payload_as_host->IsSplitNode() && (root_payload_as_host->OnlyNodeWithWindows == NULL)) // Is _visibly_ split?
13569 data->IsCenterAvailable = false;
13570 else if ((dst_node_flags & ImGuiDockNodeFlags_NoDockingOverMe) || (src_node_flags & ImGuiDockNodeFlags_NoDockingOverOther))
13571 data->IsCenterAvailable = false;
13572
13573 data->IsSidesAvailable = true;
13574 if ((dst_node_flags & ImGuiDockNodeFlags_NoSplit) || g.IO.ConfigDockingNoSplit)
13575 data->IsSidesAvailable = false;
13576 else if (!is_outer_docking && host_node && host_node->ParentNode == NULL && host_node->IsCentralNode())
13577 data->IsSidesAvailable = false;
13578 else if ((dst_node_flags & ImGuiDockNodeFlags_NoDockingSplitMe) || (src_node_flags & ImGuiDockNodeFlags_NoDockingSplitOther))
13579 data->IsSidesAvailable = false;
13580
13581 // Build a tentative future node (reuse same structure because it is practical. Shape will be readjusted when previewing a split)
13582 data->FutureNode.HasCloseButton = (host_node ? host_node->HasCloseButton : host_window->HasCloseButton) || (root_payload->HasCloseButton);
13583 data->FutureNode.HasWindowMenuButton = host_node ? true : ((host_window->Flags & ImGuiWindowFlags_NoCollapse) == 0);
13584 data->FutureNode.Pos = ref_node_for_rect ? ref_node_for_rect->Pos : host_window->Pos;
13585 data->FutureNode.Size = ref_node_for_rect ? ref_node_for_rect->Size : host_window->Size;
13586
13587 // Calculate drop shapes geometry for allowed splitting directions
13588 IM_ASSERT(ImGuiDir_None == -1);
13589 data->SplitNode = host_node;
13590 data->SplitDir = ImGuiDir_None;
13591 data->IsSplitDirExplicit = false;
13592 if (!host_window->Collapsed)
13593 for (int dir = ImGuiDir_None; dir < ImGuiDir_COUNT; dir++)
13594 {
13595 if (dir == ImGuiDir_None && !data->IsCenterAvailable)
13596 continue;
13597 if (dir != ImGuiDir_None && !data->IsSidesAvailable)
13598 continue;
13599 if (DockNodeCalcDropRectsAndTestMousePos(data->FutureNode.Rect(), (ImGuiDir)dir, data->DropRectsDraw[dir+1], is_outer_docking, &g.IO.MousePos))
13600 {
13601 data->SplitDir = (ImGuiDir)dir;
13602 data->IsSplitDirExplicit = true;
13603 }
13604 }
13605
13606 // When docking without holding Shift, we only allow and preview docking when hovering over a drop rect or over the title bar
13607 data->IsDropAllowed = (data->SplitDir != ImGuiDir_None) || (data->IsCenterAvailable);
13608 if (!is_explicit_target && !data->IsSplitDirExplicit && !g.IO.ConfigDockingWithShift)
13609 data->IsDropAllowed = false;
13610
13611 // Calculate split area
13612 data->SplitRatio = 0.0f;
13613 if (data->SplitDir != ImGuiDir_None)
13614 {
13615 ImGuiDir split_dir = data->SplitDir;
13616 ImGuiAxis split_axis = (split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Right) ? ImGuiAxis_X : ImGuiAxis_Y;
13617 ImVec2 pos_new, pos_old = data->FutureNode.Pos;
13618 ImVec2 size_new, size_old = data->FutureNode.Size;
13619 DockNodeCalcSplitRects(pos_old, size_old, pos_new, size_new, split_dir, root_payload->Size);
13620
13621 // Calculate split ratio so we can pass it down the docking request
13622 float split_ratio = ImSaturate(size_new[split_axis] / data->FutureNode.Size[split_axis]);
13623 data->FutureNode.Pos = pos_new;
13624 data->FutureNode.Size = size_new;
13625 data->SplitRatio = (split_dir == ImGuiDir_Right || split_dir == ImGuiDir_Down) ? (1.0f - split_ratio) : (split_ratio);
13626 }
13627 }
13628
DockNodePreviewDockRender(ImGuiWindow * host_window,ImGuiDockNode * host_node,ImGuiWindow * root_payload,const ImGuiDockPreviewData * data)13629 static void ImGui::DockNodePreviewDockRender(ImGuiWindow* host_window, ImGuiDockNode* host_node, ImGuiWindow* root_payload, const ImGuiDockPreviewData* data)
13630 {
13631 ImGuiContext& g = *GImGui;
13632 IM_ASSERT(g.CurrentWindow == host_window); // Because we rely on font size to calculate tab sizes
13633
13634 // With this option, we only display the preview on the target viewport, and the payload viewport is made transparent.
13635 // To compensate for the single layer obstructed by the payload, we'll increase the alpha of the preview nodes.
13636 const bool is_transparent_payload = g.IO.ConfigDockingTransparentPayload;
13637
13638 // In case the two windows involved are on different viewports, we will draw the overlay on each of them.
13639 int overlay_draw_lists_count = 0;
13640 ImDrawList* overlay_draw_lists[2];
13641 overlay_draw_lists[overlay_draw_lists_count++] = GetForegroundDrawList(host_window->Viewport);
13642 if (host_window->Viewport != root_payload->Viewport && !is_transparent_payload)
13643 overlay_draw_lists[overlay_draw_lists_count++] = GetForegroundDrawList(root_payload->Viewport);
13644
13645 // Draw main preview rectangle
13646 const ImU32 overlay_col_tabs = GetColorU32(ImGuiCol_TabActive);
13647 const ImU32 overlay_col_main = GetColorU32(ImGuiCol_DockingPreview, is_transparent_payload ? 0.60f : 0.40f);
13648 const ImU32 overlay_col_drop = GetColorU32(ImGuiCol_DockingPreview, is_transparent_payload ? 0.90f : 0.70f);
13649 const ImU32 overlay_col_drop_hovered = GetColorU32(ImGuiCol_DockingPreview, is_transparent_payload ? 1.20f : 1.00f);
13650 const ImU32 overlay_col_lines = GetColorU32(ImGuiCol_NavWindowingHighlight, is_transparent_payload ? 0.80f : 0.60f);
13651
13652 // Display area preview
13653 const bool can_preview_tabs = (root_payload->DockNodeAsHost == NULL || root_payload->DockNodeAsHost->Windows.Size > 0);
13654 if (data->IsDropAllowed)
13655 {
13656 ImRect overlay_rect = data->FutureNode.Rect();
13657 if (data->SplitDir == ImGuiDir_None && can_preview_tabs)
13658 overlay_rect.Min.y += GetFrameHeight();
13659 if (data->SplitDir != ImGuiDir_None || data->IsCenterAvailable)
13660 for (int overlay_n = 0; overlay_n < overlay_draw_lists_count; overlay_n++)
13661 overlay_draw_lists[overlay_n]->AddRectFilled(overlay_rect.Min, overlay_rect.Max, overlay_col_main, host_window->WindowRounding);
13662 }
13663
13664 // Display tab shape/label preview unless we are splitting node (it generally makes the situation harder to read)
13665 if (data->IsDropAllowed && can_preview_tabs && data->SplitDir == ImGuiDir_None && data->IsCenterAvailable)
13666 {
13667 // Compute target tab bar geometry so we can locate our preview tabs
13668 ImRect tab_bar_rect;
13669 DockNodeCalcTabBarLayout(&data->FutureNode, NULL, &tab_bar_rect, NULL);
13670 ImVec2 tab_pos = tab_bar_rect.Min;
13671 if (host_node && host_node->TabBar)
13672 {
13673 if (!host_node->IsHiddenTabBar() && !host_node->IsNoTabBar())
13674 tab_pos.x += host_node->TabBar->OffsetMax + g.Style.ItemInnerSpacing.x; // We don't use OffsetNewTab because when using non-persistent-order tab bar it is incremented with each Tab submission.
13675 else
13676 tab_pos.x += g.Style.ItemInnerSpacing.x + TabItemCalcSize(host_node->Windows[0]->Name, host_node->Windows[0]->HasCloseButton).x;
13677 }
13678 else if (!(host_window->Flags & ImGuiWindowFlags_DockNodeHost))
13679 {
13680 tab_pos.x += g.Style.ItemInnerSpacing.x + TabItemCalcSize(host_window->Name, host_window->HasCloseButton).x; // Account for slight offset which will be added when changing from title bar to tab bar
13681 }
13682
13683 // Draw tab shape/label preview (payload may be a loose window or a host window carrying multiple tabbed windows)
13684 if (root_payload->DockNodeAsHost)
13685 IM_ASSERT(root_payload->DockNodeAsHost->Windows.Size == root_payload->DockNodeAsHost->TabBar->Tabs.Size);
13686 const int payload_count = root_payload->DockNodeAsHost ? root_payload->DockNodeAsHost->TabBar->Tabs.Size : 1;
13687 for (int payload_n = 0; payload_n < payload_count; payload_n++)
13688 {
13689 // Calculate the tab bounding box for each payload window
13690 ImGuiWindow* payload = root_payload->DockNodeAsHost ? root_payload->DockNodeAsHost->TabBar->Tabs[payload_n].Window : root_payload;
13691 if (!DockNodeIsDropAllowedOne(payload, host_window))
13692 continue;
13693
13694 ImVec2 tab_size = TabItemCalcSize(payload->Name, payload->HasCloseButton);
13695 ImRect tab_bb(tab_pos.x, tab_pos.y, tab_pos.x + tab_size.x, tab_pos.y + tab_size.y);
13696 tab_pos.x += tab_size.x + g.Style.ItemInnerSpacing.x;
13697 for (int overlay_n = 0; overlay_n < overlay_draw_lists_count; overlay_n++)
13698 {
13699 ImGuiTabItemFlags tab_flags = ImGuiTabItemFlags_Preview | ((payload->Flags & ImGuiWindowFlags_UnsavedDocument) ? ImGuiTabItemFlags_UnsavedDocument : 0);
13700 if (!tab_bar_rect.Contains(tab_bb))
13701 overlay_draw_lists[overlay_n]->PushClipRect(tab_bar_rect.Min, tab_bar_rect.Max);
13702 TabItemBackground(overlay_draw_lists[overlay_n], tab_bb, tab_flags, overlay_col_tabs);
13703 TabItemLabelAndCloseButton(overlay_draw_lists[overlay_n], tab_bb, tab_flags, g.Style.FramePadding, payload->Name, 0, 0, false);
13704 if (!tab_bar_rect.Contains(tab_bb))
13705 overlay_draw_lists[overlay_n]->PopClipRect();
13706 }
13707 }
13708 }
13709
13710 // Display drop boxes
13711 const float overlay_rounding = ImMax(3.0f, g.Style.FrameRounding);
13712 for (int dir = ImGuiDir_None; dir < ImGuiDir_COUNT; dir++)
13713 {
13714 if (!data->DropRectsDraw[dir + 1].IsInverted())
13715 {
13716 ImRect draw_r = data->DropRectsDraw[dir + 1];
13717 ImRect draw_r_in = draw_r;
13718 draw_r_in.Expand(-2.0f);
13719 ImU32 overlay_col = (data->SplitDir == (ImGuiDir)dir && data->IsSplitDirExplicit) ? overlay_col_drop_hovered : overlay_col_drop;
13720 for (int overlay_n = 0; overlay_n < overlay_draw_lists_count; overlay_n++)
13721 {
13722 ImVec2 center = ImFloor(draw_r_in.GetCenter());
13723 overlay_draw_lists[overlay_n]->AddRectFilled(draw_r.Min, draw_r.Max, overlay_col, overlay_rounding);
13724 overlay_draw_lists[overlay_n]->AddRect(draw_r_in.Min, draw_r_in.Max, overlay_col_lines, overlay_rounding);
13725 if (dir == ImGuiDir_Left || dir == ImGuiDir_Right)
13726 overlay_draw_lists[overlay_n]->AddLine(ImVec2(center.x, draw_r_in.Min.y), ImVec2(center.x, draw_r_in.Max.y), overlay_col_lines);
13727 if (dir == ImGuiDir_Up || dir == ImGuiDir_Down)
13728 overlay_draw_lists[overlay_n]->AddLine(ImVec2(draw_r_in.Min.x, center.y), ImVec2(draw_r_in.Max.x, center.y), overlay_col_lines);
13729 }
13730 }
13731
13732 // Stop after ImGuiDir_None
13733 if ((host_node && (host_node->GetMergedFlags() & ImGuiDockNodeFlags_NoSplit)) || g.IO.ConfigDockingNoSplit)
13734 return;
13735 }
13736 }
13737
13738 //-----------------------------------------------------------------------------
13739 // Docking: ImGuiDockNode Tree manipulation functions
13740 //-----------------------------------------------------------------------------
13741 // - DockNodeTreeSplit()
13742 // - DockNodeTreeMerge()
13743 // - DockNodeTreeUpdatePosSize()
13744 // - DockNodeTreeUpdateSplitterFindTouchingNode()
13745 // - DockNodeTreeUpdateSplitter()
13746 // - DockNodeTreeFindFallbackLeafNode()
13747 // - DockNodeTreeFindNodeByPos()
13748 //-----------------------------------------------------------------------------
13749
DockNodeTreeSplit(ImGuiContext * ctx,ImGuiDockNode * parent_node,ImGuiAxis split_axis,int split_inheritor_child_idx,float split_ratio,ImGuiDockNode * new_node)13750 void ImGui::DockNodeTreeSplit(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImGuiAxis split_axis, int split_inheritor_child_idx, float split_ratio, ImGuiDockNode* new_node)
13751 {
13752 ImGuiContext& g = *GImGui;
13753 IM_ASSERT(split_axis != ImGuiAxis_None);
13754
13755 ImGuiDockNode* child_0 = (new_node && split_inheritor_child_idx != 0) ? new_node : DockContextAddNode(ctx, 0);
13756 child_0->ParentNode = parent_node;
13757
13758 ImGuiDockNode* child_1 = (new_node && split_inheritor_child_idx != 1) ? new_node : DockContextAddNode(ctx, 0);
13759 child_1->ParentNode = parent_node;
13760
13761 ImGuiDockNode* child_inheritor = (split_inheritor_child_idx == 0) ? child_0 : child_1;
13762 DockNodeMoveChildNodes(child_inheritor, parent_node);
13763 parent_node->ChildNodes[0] = child_0;
13764 parent_node->ChildNodes[1] = child_1;
13765 parent_node->ChildNodes[split_inheritor_child_idx]->VisibleWindow = parent_node->VisibleWindow;
13766 parent_node->SplitAxis = split_axis;
13767 parent_node->VisibleWindow = NULL;
13768 parent_node->AuthorityForPos = parent_node->AuthorityForSize = ImGuiDataAuthority_DockNode;
13769
13770 float size_avail = (parent_node->Size[split_axis] - DOCKING_SPLITTER_SIZE);
13771 size_avail = ImMax(size_avail, g.Style.WindowMinSize[split_axis] * 2.0f);
13772 IM_ASSERT(size_avail > 0.0f); // If you created a node manually with DockBuilderAddNode(), you need to also call DockBuilderSetNodeSize() before splitting.
13773 child_0->SizeRef = child_1->SizeRef = parent_node->Size;
13774 child_0->SizeRef[split_axis] = ImFloor(size_avail * split_ratio);
13775 child_1->SizeRef[split_axis] = ImFloor(size_avail - child_0->SizeRef[split_axis]);
13776
13777 DockNodeMoveWindows(parent_node->ChildNodes[split_inheritor_child_idx], parent_node);
13778 DockNodeTreeUpdatePosSize(parent_node, parent_node->Pos, parent_node->Size);
13779
13780 // Flags transfer (e.g. this is where we transfer the ImGuiDockNodeFlags_CentralNode property)
13781 child_0->SharedFlags = parent_node->SharedFlags & ImGuiDockNodeFlags_SharedFlagsInheritMask_;
13782 child_1->SharedFlags = parent_node->SharedFlags & ImGuiDockNodeFlags_SharedFlagsInheritMask_;
13783 child_inheritor->LocalFlags = parent_node->LocalFlags & ImGuiDockNodeFlags_LocalFlagsTransferMask_;
13784 parent_node->LocalFlags &= ~ImGuiDockNodeFlags_LocalFlagsTransferMask_;
13785 if (child_inheritor->IsCentralNode())
13786 DockNodeGetRootNode(parent_node)->CentralNode = child_inheritor;
13787 }
13788
DockNodeTreeMerge(ImGuiContext * ctx,ImGuiDockNode * parent_node,ImGuiDockNode * merge_lead_child)13789 void ImGui::DockNodeTreeMerge(ImGuiContext* ctx, ImGuiDockNode* parent_node, ImGuiDockNode* merge_lead_child)
13790 {
13791 // When called from DockContextProcessUndockNode() it is possible that one of the child is NULL.
13792 ImGuiDockNode* child_0 = parent_node->ChildNodes[0];
13793 ImGuiDockNode* child_1 = parent_node->ChildNodes[1];
13794 IM_ASSERT(child_0 || child_1);
13795 IM_ASSERT(merge_lead_child == child_0 || merge_lead_child == child_1);
13796 if ((child_0 && child_0->Windows.Size > 0) || (child_1 && child_1->Windows.Size > 0))
13797 {
13798 IM_ASSERT(parent_node->TabBar == NULL);
13799 IM_ASSERT(parent_node->Windows.Size == 0);
13800 }
13801 IMGUI_DEBUG_LOG_DOCKING("DockNodeTreeMerge 0x%08X & 0x%08X back into parent 0x%08X\n", child_0 ? child_0->ID : 0, child_1 ? child_1->ID : 0, parent_node->ID);
13802
13803 ImVec2 backup_last_explicit_size = parent_node->SizeRef;
13804 DockNodeMoveChildNodes(parent_node, merge_lead_child);
13805 if (child_0)
13806 {
13807 DockNodeMoveWindows(parent_node, child_0); // Generally only 1 of the 2 child node will have windows
13808 DockSettingsRenameNodeReferences(child_0->ID, parent_node->ID);
13809 }
13810 if (child_1)
13811 {
13812 DockNodeMoveWindows(parent_node, child_1);
13813 DockSettingsRenameNodeReferences(child_1->ID, parent_node->ID);
13814 }
13815 DockNodeApplyPosSizeToWindows(parent_node);
13816 parent_node->AuthorityForPos = parent_node->AuthorityForSize = parent_node->AuthorityForViewport = ImGuiDataAuthority_Auto;
13817 parent_node->VisibleWindow = merge_lead_child->VisibleWindow;
13818 parent_node->SizeRef = backup_last_explicit_size;
13819
13820 // Flags transfer
13821 parent_node->LocalFlags &= ~ImGuiDockNodeFlags_LocalFlagsTransferMask_; // Preserve Dockspace flag
13822 parent_node->LocalFlags |= (child_0 ? child_0->LocalFlags : 0) & ImGuiDockNodeFlags_LocalFlagsTransferMask_;
13823 parent_node->LocalFlags |= (child_1 ? child_1->LocalFlags : 0) & ImGuiDockNodeFlags_LocalFlagsTransferMask_;
13824
13825 if (child_0)
13826 {
13827 ctx->DockContext.Nodes.SetVoidPtr(child_0->ID, NULL);
13828 IM_DELETE(child_0);
13829 }
13830 if (child_1)
13831 {
13832 ctx->DockContext.Nodes.SetVoidPtr(child_1->ID, NULL);
13833 IM_DELETE(child_1);
13834 }
13835 }
13836
13837 // Update Pos/Size for a node hierarchy (don't affect child Windows yet)
DockNodeTreeUpdatePosSize(ImGuiDockNode * node,ImVec2 pos,ImVec2 size,bool only_write_to_marked_nodes)13838 void ImGui::DockNodeTreeUpdatePosSize(ImGuiDockNode* node, ImVec2 pos, ImVec2 size, bool only_write_to_marked_nodes)
13839 {
13840 // During the regular dock node update we write to all nodes.
13841 // 'only_write_to_marked_nodes' is only set when turning a node visible mid-frame and we need its size right-away.
13842 const bool write_to_node = (only_write_to_marked_nodes == false) || (node->MarkedForPosSizeWrite);
13843 if (write_to_node)
13844 {
13845 node->Pos = pos;
13846 node->Size = size;
13847 }
13848
13849 if (node->IsLeafNode())
13850 return;
13851
13852 ImGuiDockNode* child_0 = node->ChildNodes[0];
13853 ImGuiDockNode* child_1 = node->ChildNodes[1];
13854 ImVec2 child_0_pos = pos, child_1_pos = pos;
13855 ImVec2 child_0_size = size, child_1_size = size;
13856 if (child_0->IsVisible && child_1->IsVisible)
13857 {
13858 const float spacing = DOCKING_SPLITTER_SIZE;
13859 const ImGuiAxis axis = (ImGuiAxis)node->SplitAxis;
13860 const float size_avail = ImMax(size[axis] - spacing, 0.0f);
13861
13862 // Size allocation policy
13863 // 1) The first 0..WindowMinSize[axis]*2 are allocated evenly to both windows.
13864 ImGuiContext& g = *GImGui;
13865 const float size_min_each = ImFloor(ImMin(size_avail, g.Style.WindowMinSize[axis] * 2.0f) * 0.5f);
13866
13867 // 2) Process locked absolute size (during a splitter resize we preserve the child of nodes not touching the splitter edge)
13868 if (child_0->WantLockSizeOnce && !child_1->WantLockSizeOnce)
13869 {
13870 child_0_size[axis] = child_0->SizeRef[axis] = ImMin(size_avail - 1.0f, child_0->Size[axis]);
13871 child_1_size[axis] = child_1->SizeRef[axis] = (size_avail - child_0_size[axis]);
13872 IM_ASSERT(child_0->SizeRef[axis] > 0.0f && child_1->SizeRef[axis] > 0.0f);
13873 }
13874 else if (child_1->WantLockSizeOnce && !child_0->WantLockSizeOnce)
13875 {
13876 child_1_size[axis] = child_1->SizeRef[axis] = ImMin(size_avail - 1.0f, child_1->Size[axis]);
13877 child_0_size[axis] = child_0->SizeRef[axis] = (size_avail - child_1_size[axis]);
13878 IM_ASSERT(child_0->SizeRef[axis] > 0.0f && child_1->SizeRef[axis] > 0.0f);
13879 }
13880 else if (child_0->WantLockSizeOnce && child_1->WantLockSizeOnce)
13881 {
13882 // FIXME-DOCK: We cannot honor the requested size, so apply ratio.
13883 // Currently this path will only be taken if code programmatically sets WantLockSizeOnce
13884 float ratio_0 = child_0_size[axis] / (child_0_size[axis] + child_1_size[axis]);
13885 child_0_size[axis] = child_0->SizeRef[axis] = ImFloor(size_avail * ratio_0);
13886 child_1_size[axis] = child_1->SizeRef[axis] = (size_avail - child_0_size[axis]);
13887 IM_ASSERT(child_0->SizeRef[axis] > 0.0f && child_1->SizeRef[axis] > 0.0f);
13888 }
13889
13890 // 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
13891 else if (child_1->IsCentralNode() && child_0->SizeRef[axis] != 0.0f)
13892 {
13893 child_0_size[axis] = ImMin(size_avail - size_min_each, child_0->SizeRef[axis]);
13894 child_1_size[axis] = (size_avail - child_0_size[axis]);
13895 }
13896 else if (child_0->IsCentralNode() && child_1->SizeRef[axis] != 0.0f)
13897 {
13898 child_1_size[axis] = ImMin(size_avail - size_min_each, child_1->SizeRef[axis]);
13899 child_0_size[axis] = (size_avail - child_1_size[axis]);
13900 }
13901 else
13902 {
13903 // 4) Otherwise distribute according to the relative ratio of each SizeRef value
13904 float split_ratio = child_0->SizeRef[axis] / (child_0->SizeRef[axis] + child_1->SizeRef[axis]);
13905 child_0_size[axis] = ImMax(size_min_each, ImFloor(size_avail * split_ratio + 0.5F));
13906 child_1_size[axis] = (size_avail - child_0_size[axis]);
13907 }
13908
13909 child_1_pos[axis] += spacing + child_0_size[axis];
13910 }
13911 child_0->WantLockSizeOnce = child_1->WantLockSizeOnce = false;
13912
13913 if (child_0->IsVisible)
13914 DockNodeTreeUpdatePosSize(child_0, child_0_pos, child_0_size);
13915 if (child_1->IsVisible)
13916 DockNodeTreeUpdatePosSize(child_1, child_1_pos, child_1_size);
13917 }
13918
DockNodeTreeUpdateSplitterFindTouchingNode(ImGuiDockNode * node,ImGuiAxis axis,int side,ImVector<ImGuiDockNode * > * touching_nodes)13919 static void DockNodeTreeUpdateSplitterFindTouchingNode(ImGuiDockNode* node, ImGuiAxis axis, int side, ImVector<ImGuiDockNode*>* touching_nodes)
13920 {
13921 if (node->IsLeafNode())
13922 {
13923 touching_nodes->push_back(node);
13924 return;
13925 }
13926 if (node->ChildNodes[0]->IsVisible)
13927 if (node->SplitAxis != axis || side == 0 || !node->ChildNodes[1]->IsVisible)
13928 DockNodeTreeUpdateSplitterFindTouchingNode(node->ChildNodes[0], axis, side, touching_nodes);
13929 if (node->ChildNodes[1]->IsVisible)
13930 if (node->SplitAxis != axis || side == 1 || !node->ChildNodes[0]->IsVisible)
13931 DockNodeTreeUpdateSplitterFindTouchingNode(node->ChildNodes[1], axis, side, touching_nodes);
13932 }
13933
DockNodeTreeUpdateSplitter(ImGuiDockNode * node)13934 void ImGui::DockNodeTreeUpdateSplitter(ImGuiDockNode* node)
13935 {
13936 if (node->IsLeafNode())
13937 return;
13938
13939 ImGuiContext& g = *GImGui;
13940
13941 ImGuiDockNode* child_0 = node->ChildNodes[0];
13942 ImGuiDockNode* child_1 = node->ChildNodes[1];
13943 if (child_0->IsVisible && child_1->IsVisible)
13944 {
13945 // Bounding box of the splitter cover the space between both nodes (w = Spacing, h = Size[xy^1] for when splitting horizontally)
13946 const ImGuiAxis axis = (ImGuiAxis)node->SplitAxis;
13947 IM_ASSERT(axis != ImGuiAxis_None);
13948 ImRect bb;
13949 bb.Min = child_0->Pos;
13950 bb.Max = child_1->Pos;
13951 bb.Min[axis] += child_0->Size[axis];
13952 bb.Max[axis ^ 1] += child_1->Size[axis ^ 1];
13953 //if (g.IO.KeyCtrl) GetForegroundDrawList(g.CurrentWindow->Viewport)->AddRect(bb.Min, bb.Max, IM_COL32(255,0,255,255));
13954
13955 const ImGuiDockNodeFlags merged_flags = child_0->GetMergedFlags() | child_1->GetMergedFlags();
13956 const ImGuiDockNodeFlags no_resize_axis_flag = (axis == ImGuiAxis_X) ? ImGuiDockNodeFlags_NoResizeX : ImGuiDockNodeFlags_NoResizeY;
13957 if ((merged_flags & ImGuiDockNodeFlags_NoResize) || (merged_flags & no_resize_axis_flag))
13958 {
13959 ImGuiWindow* window = g.CurrentWindow;
13960 window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_Separator), g.Style.FrameRounding);
13961 }
13962 else
13963 {
13964 //bb.Min[axis] += 1; // Display a little inward so highlight doesn't connect with nearby tabs on the neighbor node.
13965 //bb.Max[axis] -= 1;
13966 PushID(node->ID);
13967
13968 // Gather list of nodes that are touching the splitter line. Find resizing limits based on those nodes.
13969 ImVector<ImGuiDockNode*> touching_nodes[2];
13970 float min_size = g.Style.WindowMinSize[axis];
13971 float resize_limits[2];
13972 resize_limits[0] = node->ChildNodes[0]->Pos[axis] + min_size;
13973 resize_limits[1] = node->ChildNodes[1]->Pos[axis] + node->ChildNodes[1]->Size[axis] - min_size;
13974
13975 ImGuiID splitter_id = GetID("##Splitter");
13976 if (g.ActiveId == splitter_id)
13977 {
13978 // Only process when splitter is active
13979 DockNodeTreeUpdateSplitterFindTouchingNode(child_0, axis, 1, &touching_nodes[0]);
13980 DockNodeTreeUpdateSplitterFindTouchingNode(child_1, axis, 0, &touching_nodes[1]);
13981 for (int touching_node_n = 0; touching_node_n < touching_nodes[0].Size; touching_node_n++)
13982 resize_limits[0] = ImMax(resize_limits[0], touching_nodes[0][touching_node_n]->Rect().Min[axis] + min_size);
13983 for (int touching_node_n = 0; touching_node_n < touching_nodes[1].Size; touching_node_n++)
13984 resize_limits[1] = ImMin(resize_limits[1], touching_nodes[1][touching_node_n]->Rect().Max[axis] - min_size);
13985
13986 /*
13987 // [DEBUG] Render limits
13988 ImDrawList* draw_list = node->HostWindow ? GetForegroundDrawList(node->HostWindow) : GetForegroundDrawList((ImGuiViewportP*)GetMainViewport());
13989 for (int n = 0; n < 2; n++)
13990 if (axis == ImGuiAxis_X)
13991 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);
13992 else
13993 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);
13994 */
13995 }
13996
13997 // Use a short delay before highlighting the splitter (and changing the mouse cursor) in order for regular mouse movement to not highlight many splitters
13998 float cur_size_0 = child_0->Size[axis];
13999 float cur_size_1 = child_1->Size[axis];
14000 float min_size_0 = resize_limits[0] - child_0->Pos[axis];
14001 float min_size_1 = child_1->Pos[axis] + child_1->Size[axis] - resize_limits[1];
14002 if (SplitterBehavior(bb, GetID("##Splitter"), axis, &cur_size_0, &cur_size_1, min_size_0, min_size_1, WINDOWS_RESIZE_FROM_EDGES_HALF_THICKNESS, WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER))
14003 {
14004 if (touching_nodes[0].Size > 0 && touching_nodes[1].Size > 0)
14005 {
14006 child_0->Size[axis] = child_0->SizeRef[axis] = cur_size_0;
14007 child_1->Pos[axis] -= cur_size_1 - child_1->Size[axis];
14008 child_1->Size[axis] = child_1->SizeRef[axis] = cur_size_1;
14009
14010 // Lock the size of every node that is a sibling of the node we are touching
14011 // This might be less desirable if we can merge sibling of a same axis into the same parental level.
14012 for (int side_n = 0; side_n < 2; side_n++)
14013 for (int touching_node_n = 0; touching_node_n < touching_nodes[side_n].Size; touching_node_n++)
14014 {
14015 ImGuiDockNode* touching_node = touching_nodes[side_n][touching_node_n];
14016 //ImDrawList* draw_list = node->HostWindow ? GetForegroundDrawList(node->HostWindow) : GetForegroundDrawList((ImGuiViewportP*)GetMainViewport());
14017 //draw_list->AddRect(touching_node->Pos, touching_node->Pos + touching_node->Size, IM_COL32(255, 128, 0, 255));
14018 while (touching_node->ParentNode != node)
14019 {
14020 if (touching_node->ParentNode->SplitAxis == axis)
14021 {
14022 // Mark other node so its size will be preserved during the upcoming call to DockNodeTreeUpdatePosSize().
14023 ImGuiDockNode* node_to_preserve = touching_node->ParentNode->ChildNodes[side_n];
14024 node_to_preserve->WantLockSizeOnce = true;
14025 //draw_list->AddRect(touching_node->Pos, touching_node->Rect().Max, IM_COL32(255, 0, 0, 255));
14026 //draw_list->AddRectFilled(node_to_preserve->Pos, node_to_preserve->Rect().Max, IM_COL32(0, 255, 0, 100));
14027 }
14028 touching_node = touching_node->ParentNode;
14029 }
14030 }
14031
14032 DockNodeTreeUpdatePosSize(child_0, child_0->Pos, child_0->Size);
14033 DockNodeTreeUpdatePosSize(child_1, child_1->Pos, child_1->Size);
14034 MarkIniSettingsDirty();
14035 }
14036 }
14037 PopID();
14038 }
14039 }
14040
14041 if (child_0->IsVisible)
14042 DockNodeTreeUpdateSplitter(child_0);
14043 if (child_1->IsVisible)
14044 DockNodeTreeUpdateSplitter(child_1);
14045 }
14046
DockNodeTreeFindFallbackLeafNode(ImGuiDockNode * node)14047 ImGuiDockNode* ImGui::DockNodeTreeFindFallbackLeafNode(ImGuiDockNode* node)
14048 {
14049 if (node->IsLeafNode())
14050 return node;
14051 if (ImGuiDockNode* leaf_node = DockNodeTreeFindFallbackLeafNode(node->ChildNodes[0]))
14052 return leaf_node;
14053 if (ImGuiDockNode* leaf_node = DockNodeTreeFindFallbackLeafNode(node->ChildNodes[1]))
14054 return leaf_node;
14055 return NULL;
14056 }
14057
DockNodeTreeFindVisibleNodeByPos(ImGuiDockNode * node,ImVec2 pos)14058 ImGuiDockNode* ImGui::DockNodeTreeFindVisibleNodeByPos(ImGuiDockNode* node, ImVec2 pos)
14059 {
14060 if (!node->IsVisible)
14061 return NULL;
14062
14063 const float dock_spacing = 0.0f;// g.Style.ItemInnerSpacing.x; // FIXME: Relation to DOCKING_SPLITTER_SIZE?
14064 ImRect r(node->Pos, node->Pos + node->Size);
14065 r.Expand(dock_spacing * 0.5f);
14066 bool inside = r.Contains(pos);
14067 if (!inside)
14068 return NULL;
14069
14070 if (node->IsLeafNode())
14071 return node;
14072 if (ImGuiDockNode* hovered_node = DockNodeTreeFindVisibleNodeByPos(node->ChildNodes[0], pos))
14073 return hovered_node;
14074 if (ImGuiDockNode* hovered_node = DockNodeTreeFindVisibleNodeByPos(node->ChildNodes[1], pos))
14075 return hovered_node;
14076
14077 return NULL;
14078 }
14079
14080 //-----------------------------------------------------------------------------
14081 // Docking: Public Functions (SetWindowDock, DockSpace, DockSpaceOverViewport)
14082 //-----------------------------------------------------------------------------
14083 // - SetWindowDock() [Internal]
14084 // - DockSpace()
14085 // - DockSpaceOverViewport()
14086 //-----------------------------------------------------------------------------
14087
14088 // [Internal] Called via SetNextWindowDockID()
SetWindowDock(ImGuiWindow * window,ImGuiID dock_id,ImGuiCond cond)14089 void ImGui::SetWindowDock(ImGuiWindow* window, ImGuiID dock_id, ImGuiCond cond)
14090 {
14091 // Test condition (NB: bit 0 is always true) and clear flags for next time
14092 if (cond && (window->SetWindowDockAllowFlags & cond) == 0)
14093 return;
14094 window->SetWindowDockAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
14095
14096 if (window->DockId == dock_id)
14097 return;
14098
14099 // If the user attempt to set a dock id that is a split node, we'll dig within to find a suitable docking spot
14100 ImGuiContext* ctx = GImGui;
14101 if (ImGuiDockNode* new_node = DockContextFindNodeByID(ctx, dock_id))
14102 if (new_node->IsSplitNode())
14103 {
14104 // Policy: Find central node or latest focused node. We first move back to our root node.
14105 new_node = DockNodeGetRootNode(new_node);
14106 if (new_node->CentralNode)
14107 {
14108 IM_ASSERT(new_node->CentralNode->IsCentralNode());
14109 dock_id = new_node->CentralNode->ID;
14110 }
14111 else
14112 {
14113 dock_id = new_node->LastFocusedNodeId;
14114 }
14115 }
14116
14117 if (window->DockId == dock_id)
14118 return;
14119
14120 if (window->DockNode)
14121 DockNodeRemoveWindow(window->DockNode, window, 0);
14122 window->DockId = dock_id;
14123 }
14124
14125 // Create an explicit dockspace node within an existing window. Also expose dock node flags and creates a CentralNode by default.
14126 // The Central Node is always displayed even when empty and shrink/extend according to the requested size of its neighbors.
14127 // DockSpace() needs to be submitted _before_ any window they can host. If you use a dockspace, submit it early in your app.
DockSpace(ImGuiID id,const ImVec2 & size_arg,ImGuiDockNodeFlags flags,const ImGuiWindowClass * window_class)14128 void ImGui::DockSpace(ImGuiID id, const ImVec2& size_arg, ImGuiDockNodeFlags flags, const ImGuiWindowClass* window_class)
14129 {
14130 ImGuiContext* ctx = GImGui;
14131 ImGuiContext& g = *ctx;
14132 ImGuiWindow* window = GetCurrentWindow();
14133 if (!(g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable))
14134 return;
14135
14136 // Early out if parent window is hidden/collapsed
14137 // This is faster but also DockNodeUpdateTabBar() relies on TabBarLayout() running (which won't if SkipItems=true) to set NextSelectedTabId = 0). See #2960.
14138 // If for whichever reason this is causing problem we would need to ensure that DockNodeUpdateTabBar() ends up clearing NextSelectedTabId even if SkipItems=true.
14139 if (window->SkipItems)
14140 flags |= ImGuiDockNodeFlags_KeepAliveOnly;
14141
14142 IM_ASSERT((flags & ImGuiDockNodeFlags_DockSpace) == 0);
14143 ImGuiDockNode* node = DockContextFindNodeByID(ctx, id);
14144 if (!node)
14145 {
14146 IMGUI_DEBUG_LOG_DOCKING("DockSpace: dockspace node 0x%08X created\n", id);
14147 node = DockContextAddNode(ctx, id);
14148 node->LocalFlags |= ImGuiDockNodeFlags_CentralNode;
14149 }
14150 if (window_class && window_class->ClassId != node->WindowClass.ClassId)
14151 IMGUI_DEBUG_LOG_DOCKING("DockSpace: dockspace node 0x%08X: setup WindowClass 0x%08X -> 0x%08X\n", id, node->WindowClass.ClassId, window_class->ClassId);
14152 node->SharedFlags = flags;
14153 node->WindowClass = window_class ? *window_class : ImGuiWindowClass();
14154
14155 // When a DockSpace transitioned form implicit to explicit this may be called a second time
14156 // 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.
14157 if (node->LastFrameActive == g.FrameCount && !(flags & ImGuiDockNodeFlags_KeepAliveOnly))
14158 {
14159 IM_ASSERT(node->IsDockSpace() == false && "Cannot call DockSpace() twice a frame with the same ID");
14160 node->LocalFlags |= ImGuiDockNodeFlags_DockSpace;
14161 return;
14162 }
14163 node->LocalFlags |= ImGuiDockNodeFlags_DockSpace;
14164
14165 // Keep alive mode, this is allow windows docked into this node so stay docked even if they are not visible
14166 if (flags & ImGuiDockNodeFlags_KeepAliveOnly)
14167 {
14168 node->LastFrameAlive = g.FrameCount;
14169 return;
14170 }
14171
14172 const ImVec2 content_avail = GetContentRegionAvail();
14173 ImVec2 size = ImFloor(size_arg);
14174 if (size.x <= 0.0f)
14175 size.x = ImMax(content_avail.x + size.x, 4.0f); // Arbitrary minimum child size (0.0f causing too much issues)
14176 if (size.y <= 0.0f)
14177 size.y = ImMax(content_avail.y + size.y, 4.0f);
14178 IM_ASSERT(size.x > 0.0f && size.y > 0.0f);
14179
14180 node->Pos = window->DC.CursorPos;
14181 node->Size = node->SizeRef = size;
14182 SetNextWindowPos(node->Pos);
14183 SetNextWindowSize(node->Size);
14184 g.NextWindowData.PosUndock = false;
14185
14186 // FIXME-DOCK Why do we need a child window to host a dockspace, could we host it in the existing window?
14187 ImGuiWindowFlags window_flags = ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_DockNodeHost;
14188 window_flags |= ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar;
14189 window_flags |= ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse;
14190
14191 char title[256];
14192 ImFormatString(title, IM_ARRAYSIZE(title), "%s/DockSpace_%08X", window->Name, id);
14193
14194 // FIXME-DOCK: What is the reason for not simply calling BeginChild()?
14195 if (node->Windows.Size > 0 || node->IsSplitNode())
14196 PushStyleColor(ImGuiCol_ChildBg, IM_COL32(0, 0, 0, 0));
14197 PushStyleVar(ImGuiStyleVar_ChildBorderSize, 0.0f);
14198 Begin(title, NULL, window_flags);
14199 PopStyleVar();
14200 if (node->Windows.Size > 0 || node->IsSplitNode())
14201 PopStyleColor();
14202
14203 ImGuiWindow* host_window = g.CurrentWindow;
14204 host_window->DockNodeAsHost = node;
14205 host_window->ChildId = window->GetID(title);
14206 node->HostWindow = host_window;
14207 node->OnlyNodeWithWindows = NULL;
14208
14209 IM_ASSERT(node->IsRootNode());
14210
14211 // We need to handle the rare case were a central node is missing.
14212 // This can happen if the node was first created manually with DockBuilderAddNode() but _without_ the ImGuiDockNodeFlags_Dockspace.
14213 // Doing it correctly would set the _CentralNode flags, which would then propagate according to subsequent split.
14214 // 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.
14215 // The specific sub-property of _CentralNode we are interested in recovering here is the "Don't delete when empty" property,
14216 // as it doesn't make sense for an empty dockspace to not have this property.
14217 if (node->IsLeafNode() && !node->IsCentralNode())
14218 node->LocalFlags |= ImGuiDockNodeFlags_CentralNode;
14219
14220 // Update the node
14221 DockNodeUpdate(node);
14222
14223 g.WithinEndChild = true;
14224 End();
14225 ItemSize(size);
14226 g.WithinEndChild = false;
14227 }
14228
14229 // Tips: Use with ImGuiDockNodeFlags_PassthruCentralNode!
14230 // The limitation with this call is that your window won't have a menu bar.
14231 // 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.
14232 // 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.
DockSpaceOverViewport(ImGuiViewport * viewport,ImGuiDockNodeFlags dockspace_flags,const ImGuiWindowClass * window_class)14233 ImGuiID ImGui::DockSpaceOverViewport(ImGuiViewport* viewport, ImGuiDockNodeFlags dockspace_flags, const ImGuiWindowClass* window_class)
14234 {
14235 if (viewport == NULL)
14236 viewport = GetMainViewport();
14237
14238 SetNextWindowPos(viewport->GetWorkPos());
14239 SetNextWindowSize(viewport->GetWorkSize());
14240 SetNextWindowViewport(viewport->ID);
14241
14242 ImGuiWindowFlags host_window_flags = 0;
14243 host_window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDocking;
14244 host_window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;
14245 if (dockspace_flags & ImGuiDockNodeFlags_PassthruCentralNode)
14246 host_window_flags |= ImGuiWindowFlags_NoBackground;
14247
14248 char label[32];
14249 ImFormatString(label, IM_ARRAYSIZE(label), "DockSpaceViewport_%08X", viewport->ID);
14250
14251 PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
14252 PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
14253 PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
14254 Begin(label, NULL, host_window_flags);
14255 PopStyleVar(3);
14256
14257 ImGuiID dockspace_id = GetID("DockSpace");
14258 DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags, window_class);
14259 End();
14260
14261 return dockspace_id;
14262 }
14263
14264 //-----------------------------------------------------------------------------
14265 // Docking: Builder Functions
14266 //-----------------------------------------------------------------------------
14267 // Very early end-user API to manipulate dock nodes.
14268 // Only available in imgui_internal.h. Expect this API to change/break!
14269 // It is expected that those functions are all called _before_ the dockspace node submission.
14270 //-----------------------------------------------------------------------------
14271 // - DockBuilderDockWindow()
14272 // - DockBuilderGetNode()
14273 // - DockBuilderSetNodePos()
14274 // - DockBuilderSetNodeSize()
14275 // - DockBuilderAddNode()
14276 // - DockBuilderRemoveNode()
14277 // - DockBuilderRemoveNodeChildNodes()
14278 // - DockBuilderRemoveNodeDockedWindows()
14279 // - DockBuilderSplitNode()
14280 // - DockBuilderCopyNodeRec()
14281 // - DockBuilderCopyNode()
14282 // - DockBuilderCopyWindowSettings()
14283 // - DockBuilderCopyDockSpace()
14284 // - DockBuilderFinish()
14285 //-----------------------------------------------------------------------------
14286
DockBuilderDockWindow(const char * window_name,ImGuiID node_id)14287 void ImGui::DockBuilderDockWindow(const char* window_name, ImGuiID node_id)
14288 {
14289 // We don't preserve relative order of multiple docked windows (by clearing DockOrder back to -1)
14290 ImGuiID window_id = ImHashStr(window_name);
14291 if (ImGuiWindow* window = FindWindowByID(window_id))
14292 {
14293 // Apply to created window
14294 SetWindowDock(window, node_id, ImGuiCond_Always);
14295 window->DockOrder = -1;
14296 }
14297 else
14298 {
14299 // Apply to settings
14300 ImGuiWindowSettings* settings = FindWindowSettings(window_id);
14301 if (settings == NULL)
14302 settings = CreateNewWindowSettings(window_name);
14303 settings->DockId = node_id;
14304 settings->DockOrder = -1;
14305 }
14306 }
14307
DockBuilderGetNode(ImGuiID node_id)14308 ImGuiDockNode* ImGui::DockBuilderGetNode(ImGuiID node_id)
14309 {
14310 ImGuiContext* ctx = GImGui;
14311 return DockContextFindNodeByID(ctx, node_id);
14312 }
14313
DockBuilderSetNodePos(ImGuiID node_id,ImVec2 pos)14314 void ImGui::DockBuilderSetNodePos(ImGuiID node_id, ImVec2 pos)
14315 {
14316 ImGuiContext* ctx = GImGui;
14317 ImGuiDockNode* node = DockContextFindNodeByID(ctx, node_id);
14318 if (node == NULL)
14319 return;
14320 node->Pos = pos;
14321 node->AuthorityForPos = ImGuiDataAuthority_DockNode;
14322 }
14323
DockBuilderSetNodeSize(ImGuiID node_id,ImVec2 size)14324 void ImGui::DockBuilderSetNodeSize(ImGuiID node_id, ImVec2 size)
14325 {
14326 ImGuiContext* ctx = GImGui;
14327 ImGuiDockNode* node = DockContextFindNodeByID(ctx, node_id);
14328 if (node == NULL)
14329 return;
14330 IM_ASSERT(size.x > 0.0f && size.y > 0.0f);
14331 node->Size = node->SizeRef = size;
14332 node->AuthorityForSize = ImGuiDataAuthority_DockNode;
14333 }
14334
14335 // Make sure to use the ImGuiDockNodeFlags_DockSpace flag to create a dockspace node! Otherwise this will create a floating node!
14336 // - Floating node: you can then call DockBuilderSetNodePos()/DockBuilderSetNodeSize() to position and size the floating node.
14337 // - Dockspace node: calling DockBuilderSetNodePos() is unnecessary.
14338 // - If you intend to split a node immediately after creation using DockBuilderSplitNode(), make sure to call DockBuilderSetNodeSize() beforehand!
14339 // For various reason, the splitting code currently needs a base size otherwise space may not be allocated as precisely as you would expect.
14340 // - Use (id == 0) to let the system allocate a node identifier.
14341 // - Existing node with a same id will be removed.
DockBuilderAddNode(ImGuiID id,ImGuiDockNodeFlags flags)14342 ImGuiID ImGui::DockBuilderAddNode(ImGuiID id, ImGuiDockNodeFlags flags)
14343 {
14344 ImGuiContext* ctx = GImGui;
14345 ImGuiDockNode* node = NULL;
14346
14347 if (id != 0)
14348 DockBuilderRemoveNode(id);
14349
14350 if (flags & ImGuiDockNodeFlags_DockSpace)
14351 {
14352 DockSpace(id, ImVec2(0, 0), (flags & ~ImGuiDockNodeFlags_DockSpace) | ImGuiDockNodeFlags_KeepAliveOnly);
14353 node = DockContextFindNodeByID(ctx, id);
14354 }
14355 else
14356 {
14357 node = DockContextAddNode(ctx, id);
14358 node->LocalFlags = flags;
14359 }
14360 node->LastFrameAlive = ctx->FrameCount; // Set this otherwise BeginDocked will undock during the same frame.
14361 return node->ID;
14362 }
14363
DockBuilderRemoveNode(ImGuiID node_id)14364 void ImGui::DockBuilderRemoveNode(ImGuiID node_id)
14365 {
14366 ImGuiContext* ctx = GImGui;
14367 ImGuiDockNode* node = DockContextFindNodeByID(ctx, node_id);
14368 if (node == NULL)
14369 return;
14370 DockBuilderRemoveNodeDockedWindows(node_id, true);
14371 DockBuilderRemoveNodeChildNodes(node_id);
14372 if (node->IsCentralNode() && node->ParentNode)
14373 node->ParentNode->LocalFlags |= ImGuiDockNodeFlags_CentralNode;
14374 DockContextRemoveNode(ctx, node, true);
14375 }
14376
14377 // root_id = 0 to remove all, root_id != 0 to remove child of given node.
DockBuilderRemoveNodeChildNodes(ImGuiID root_id)14378 void ImGui::DockBuilderRemoveNodeChildNodes(ImGuiID root_id)
14379 {
14380 ImGuiContext* ctx = GImGui;
14381 ImGuiDockContext* dc = &ctx->DockContext;
14382
14383 ImGuiDockNode* root_node = root_id ? DockContextFindNodeByID(ctx, root_id) : NULL;
14384 if (root_id && root_node == NULL)
14385 return;
14386 bool has_central_node = false;
14387
14388 ImGuiDataAuthority backup_root_node_authority_for_pos = root_node ? root_node->AuthorityForPos : ImGuiDataAuthority_Auto;
14389 ImGuiDataAuthority backup_root_node_authority_for_size = root_node ? root_node->AuthorityForSize : ImGuiDataAuthority_Auto;
14390
14391 // Process active windows
14392 ImVector<ImGuiDockNode*> nodes_to_remove;
14393 for (int n = 0; n < dc->Nodes.Data.Size; n++)
14394 if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p)
14395 {
14396 bool want_removal = (root_id == 0) || (node->ID != root_id && DockNodeGetRootNode(node)->ID == root_id);
14397 if (want_removal)
14398 {
14399 if (node->IsCentralNode())
14400 has_central_node = true;
14401 if (root_id != 0)
14402 DockContextQueueNotifyRemovedNode(ctx, node);
14403 if (root_node)
14404 DockNodeMoveWindows(root_node, node);
14405 nodes_to_remove.push_back(node);
14406 }
14407 }
14408
14409 // DockNodeMoveWindows->DockNodeAddWindow will normally set those when reaching two windows (which is only adequate during interactive merge)
14410 // Make sure we don't lose our current pos/size. (FIXME-DOCK: Consider tidying up that code in DockNodeAddWindow instead)
14411 if (root_node)
14412 {
14413 root_node->AuthorityForPos = backup_root_node_authority_for_pos;
14414 root_node->AuthorityForSize = backup_root_node_authority_for_size;
14415 }
14416
14417 // Apply to settings
14418 for (ImGuiWindowSettings* settings = ctx->SettingsWindows.begin(); settings != NULL; settings = ctx->SettingsWindows.next_chunk(settings))
14419 if (ImGuiID window_settings_dock_id = settings->DockId)
14420 for (int n = 0; n < nodes_to_remove.Size; n++)
14421 if (nodes_to_remove[n]->ID == window_settings_dock_id)
14422 {
14423 settings->DockId = root_id;
14424 break;
14425 }
14426
14427 // Not really efficient, but easier to destroy a whole hierarchy considering DockContextRemoveNode is attempting to merge nodes
14428 if (nodes_to_remove.Size > 1)
14429 ImQsort(nodes_to_remove.Data, nodes_to_remove.Size, sizeof(ImGuiDockNode*), DockNodeComparerDepthMostFirst);
14430 for (int n = 0; n < nodes_to_remove.Size; n++)
14431 DockContextRemoveNode(ctx, nodes_to_remove[n], false);
14432
14433 if (root_id == 0)
14434 {
14435 dc->Nodes.Clear();
14436 dc->Requests.clear();
14437 }
14438 else if (has_central_node)
14439 {
14440 root_node->LocalFlags |= ImGuiDockNodeFlags_CentralNode;
14441 root_node->CentralNode = root_node;
14442 }
14443 }
14444
DockBuilderRemoveNodeDockedWindows(ImGuiID root_id,bool clear_settings_refs)14445 void ImGui::DockBuilderRemoveNodeDockedWindows(ImGuiID root_id, bool clear_settings_refs)
14446 {
14447 // Clear references in settings
14448 ImGuiContext* ctx = GImGui;
14449 ImGuiContext& g = *ctx;
14450 if (clear_settings_refs)
14451 {
14452 for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings))
14453 {
14454 bool want_removal = (root_id == 0) || (settings->DockId == root_id);
14455 if (!want_removal && settings->DockId != 0)
14456 if (ImGuiDockNode* node = DockContextFindNodeByID(ctx, settings->DockId))
14457 if (DockNodeGetRootNode(node)->ID == root_id)
14458 want_removal = true;
14459 if (want_removal)
14460 settings->DockId = 0;
14461 }
14462 }
14463
14464 // Clear references in windows
14465 for (int n = 0; n < g.Windows.Size; n++)
14466 {
14467 ImGuiWindow* window = g.Windows[n];
14468 bool want_removal = (root_id == 0) || (window->DockNode && DockNodeGetRootNode(window->DockNode)->ID == root_id) || (window->DockNodeAsHost && window->DockNodeAsHost->ID == root_id);
14469 if (want_removal)
14470 {
14471 const ImGuiID backup_dock_id = window->DockId;
14472 IM_UNUSED(backup_dock_id);
14473 DockContextProcessUndockWindow(ctx, window, clear_settings_refs);
14474 if (!clear_settings_refs)
14475 IM_ASSERT(window->DockId == backup_dock_id);
14476 }
14477 }
14478 }
14479
14480 // 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.
14481 // Return value is ID of the node at the specified direction, so same as (*out_id_at_dir) if that pointer is set.
14482 // FIXME-DOCK: We are not exposing nor using split_outer.
DockBuilderSplitNode(ImGuiID id,ImGuiDir split_dir,float size_ratio_for_node_at_dir,ImGuiID * out_id_at_dir,ImGuiID * out_id_at_opposite_dir)14483 ImGuiID ImGui::DockBuilderSplitNode(ImGuiID id, ImGuiDir split_dir, float size_ratio_for_node_at_dir, ImGuiID* out_id_at_dir, ImGuiID* out_id_at_opposite_dir)
14484 {
14485 ImGuiContext* ctx = GImGui;
14486 IM_ASSERT(split_dir != ImGuiDir_None);
14487 IMGUI_DEBUG_LOG_DOCKING("DockBuilderSplitNode node 0x%08X, split_dir %d\n", id, split_dir);
14488
14489 ImGuiDockNode* node = DockContextFindNodeByID(ctx, id);
14490 if (node == NULL)
14491 {
14492 IM_ASSERT(node != NULL);
14493 return 0;
14494 }
14495
14496 IM_ASSERT(!node->IsSplitNode()); // Assert if already Split
14497
14498 ImGuiDockRequest req;
14499 req.Type = ImGuiDockRequestType_Split;
14500 req.DockTargetWindow = NULL;
14501 req.DockTargetNode = node;
14502 req.DockPayload = NULL;
14503 req.DockSplitDir = split_dir;
14504 req.DockSplitRatio = ImSaturate((split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Up) ? size_ratio_for_node_at_dir : 1.0f - size_ratio_for_node_at_dir);
14505 req.DockSplitOuter = false;
14506 DockContextProcessDock(ctx, &req);
14507
14508 ImGuiID id_at_dir = node->ChildNodes[(split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Up) ? 0 : 1]->ID;
14509 ImGuiID id_at_opposite_dir = node->ChildNodes[(split_dir == ImGuiDir_Left || split_dir == ImGuiDir_Up) ? 1 : 0]->ID;
14510 if (out_id_at_dir)
14511 *out_id_at_dir = id_at_dir;
14512 if (out_id_at_opposite_dir)
14513 *out_id_at_opposite_dir = id_at_opposite_dir;
14514 return id_at_dir;
14515 }
14516
DockBuilderCopyNodeRec(ImGuiDockNode * src_node,ImGuiID dst_node_id_if_known,ImVector<ImGuiID> * out_node_remap_pairs)14517 static ImGuiDockNode* DockBuilderCopyNodeRec(ImGuiDockNode* src_node, ImGuiID dst_node_id_if_known, ImVector<ImGuiID>* out_node_remap_pairs)
14518 {
14519 ImGuiContext* ctx = GImGui;
14520 ImGuiDockNode* dst_node = ImGui::DockContextAddNode(ctx, dst_node_id_if_known);
14521 dst_node->SharedFlags = src_node->SharedFlags;
14522 dst_node->LocalFlags = src_node->LocalFlags;
14523 dst_node->Pos = src_node->Pos;
14524 dst_node->Size = src_node->Size;
14525 dst_node->SizeRef = src_node->SizeRef;
14526 dst_node->SplitAxis = src_node->SplitAxis;
14527
14528 out_node_remap_pairs->push_back(src_node->ID);
14529 out_node_remap_pairs->push_back(dst_node->ID);
14530
14531 for (int child_n = 0; child_n < IM_ARRAYSIZE(src_node->ChildNodes); child_n++)
14532 if (src_node->ChildNodes[child_n])
14533 {
14534 dst_node->ChildNodes[child_n] = DockBuilderCopyNodeRec(src_node->ChildNodes[child_n], 0, out_node_remap_pairs);
14535 dst_node->ChildNodes[child_n]->ParentNode = dst_node;
14536 }
14537
14538 IMGUI_DEBUG_LOG_DOCKING("Fork node %08X -> %08X (%d childs)\n", src_node->ID, dst_node->ID, dst_node->IsSplitNode() ? 2 : 0);
14539 return dst_node;
14540 }
14541
DockBuilderCopyNode(ImGuiID src_node_id,ImGuiID dst_node_id,ImVector<ImGuiID> * out_node_remap_pairs)14542 void ImGui::DockBuilderCopyNode(ImGuiID src_node_id, ImGuiID dst_node_id, ImVector<ImGuiID>* out_node_remap_pairs)
14543 {
14544 ImGuiContext* ctx = GImGui;
14545 IM_ASSERT(src_node_id != 0);
14546 IM_ASSERT(dst_node_id != 0);
14547 IM_ASSERT(out_node_remap_pairs != NULL);
14548
14549 ImGuiDockNode* src_node = DockContextFindNodeByID(ctx, src_node_id);
14550 IM_ASSERT(src_node != NULL);
14551
14552 out_node_remap_pairs->clear();
14553 DockBuilderRemoveNode(dst_node_id);
14554 DockBuilderCopyNodeRec(src_node, dst_node_id, out_node_remap_pairs);
14555
14556 IM_ASSERT((out_node_remap_pairs->Size % 2) == 0);
14557 }
14558
DockBuilderCopyWindowSettings(const char * src_name,const char * dst_name)14559 void ImGui::DockBuilderCopyWindowSettings(const char* src_name, const char* dst_name)
14560 {
14561 ImGuiWindow* src_window = FindWindowByName(src_name);
14562 if (src_window == NULL)
14563 return;
14564 if (ImGuiWindow* dst_window = FindWindowByName(dst_name))
14565 {
14566 dst_window->Pos = src_window->Pos;
14567 dst_window->Size = src_window->Size;
14568 dst_window->SizeFull = src_window->SizeFull;
14569 dst_window->Collapsed = src_window->Collapsed;
14570 }
14571 else if (ImGuiWindowSettings* dst_settings = FindOrCreateWindowSettings(dst_name))
14572 {
14573 ImVec2ih window_pos_2ih = ImVec2ih(src_window->Pos);
14574 if (src_window->ViewportId != 0 && src_window->ViewportId != IMGUI_VIEWPORT_DEFAULT_ID)
14575 {
14576 dst_settings->ViewportPos = window_pos_2ih;
14577 dst_settings->ViewportId = src_window->ViewportId;
14578 dst_settings->Pos = ImVec2ih(0, 0);
14579 }
14580 else
14581 {
14582 dst_settings->Pos = window_pos_2ih;
14583 }
14584 dst_settings->Size = ImVec2ih(src_window->SizeFull);
14585 dst_settings->Collapsed = src_window->Collapsed;
14586 }
14587 }
14588
14589 // FIXME: Will probably want to change this signature, in particular how the window remapping pairs are passed.
DockBuilderCopyDockSpace(ImGuiID src_dockspace_id,ImGuiID dst_dockspace_id,ImVector<const char * > * in_window_remap_pairs)14590 void ImGui::DockBuilderCopyDockSpace(ImGuiID src_dockspace_id, ImGuiID dst_dockspace_id, ImVector<const char*>* in_window_remap_pairs)
14591 {
14592 IM_ASSERT(src_dockspace_id != 0);
14593 IM_ASSERT(dst_dockspace_id != 0);
14594 IM_ASSERT(in_window_remap_pairs != NULL);
14595 IM_ASSERT((in_window_remap_pairs->Size % 2) == 0);
14596
14597 // Duplicate entire dock
14598 // 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,
14599 // whereas we could attempt to at least keep them together in a new, same floating node.
14600 ImVector<ImGuiID> node_remap_pairs;
14601 DockBuilderCopyNode(src_dockspace_id, dst_dockspace_id, &node_remap_pairs);
14602
14603 // Attempt to transition all the upcoming windows associated to dst_dockspace_id into the newly created hierarchy of dock nodes
14604 // (The windows associated to src_dockspace_id are staying in place)
14605 ImVector<ImGuiID> src_windows;
14606 for (int remap_window_n = 0; remap_window_n < in_window_remap_pairs->Size; remap_window_n += 2)
14607 {
14608 const char* src_window_name = (*in_window_remap_pairs)[remap_window_n];
14609 const char* dst_window_name = (*in_window_remap_pairs)[remap_window_n + 1];
14610 ImGuiID src_window_id = ImHashStr(src_window_name);
14611 src_windows.push_back(src_window_id);
14612
14613 // Search in the remapping tables
14614 ImGuiID src_dock_id = 0;
14615 if (ImGuiWindow* src_window = FindWindowByID(src_window_id))
14616 src_dock_id = src_window->DockId;
14617 else if (ImGuiWindowSettings* src_window_settings = FindWindowSettings(src_window_id))
14618 src_dock_id = src_window_settings->DockId;
14619 ImGuiID dst_dock_id = 0;
14620 for (int dock_remap_n = 0; dock_remap_n < node_remap_pairs.Size; dock_remap_n += 2)
14621 if (node_remap_pairs[dock_remap_n] == src_dock_id)
14622 {
14623 dst_dock_id = node_remap_pairs[dock_remap_n + 1];
14624 //node_remap_pairs[dock_remap_n] = node_remap_pairs[dock_remap_n + 1] = 0; // Clear
14625 break;
14626 }
14627
14628 if (dst_dock_id != 0)
14629 {
14630 // Docked windows gets redocked into the new node hierarchy.
14631 IMGUI_DEBUG_LOG_DOCKING("Remap live window '%s' 0x%08X -> '%s' 0x%08X\n", src_window_name, src_dock_id, dst_window_name, dst_dock_id);
14632 DockBuilderDockWindow(dst_window_name, dst_dock_id);
14633 }
14634 else
14635 {
14636 // Floating windows gets their settings transferred (regardless of whether the new window already exist or not)
14637 // When this is leading to a Copy and not a Move, we would get two overlapping floating windows. Could we possibly dock them together?
14638 IMGUI_DEBUG_LOG_DOCKING("Remap window settings '%s' -> '%s'\n", src_window_name, dst_window_name);
14639 DockBuilderCopyWindowSettings(src_window_name, dst_window_name);
14640 }
14641 }
14642
14643 // 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")
14644 // Find those windows and move to them to the cloned dock node. This may be optional?
14645 for (int dock_remap_n = 0; dock_remap_n < node_remap_pairs.Size; dock_remap_n += 2)
14646 if (ImGuiID src_dock_id = node_remap_pairs[dock_remap_n])
14647 {
14648 ImGuiID dst_dock_id = node_remap_pairs[dock_remap_n + 1];
14649 ImGuiDockNode* node = DockBuilderGetNode(src_dock_id);
14650 for (int window_n = 0; window_n < node->Windows.Size; window_n++)
14651 {
14652 ImGuiWindow* window = node->Windows[window_n];
14653 if (src_windows.contains(window->ID))
14654 continue;
14655
14656 // Docked windows gets redocked into the new node hierarchy.
14657 IMGUI_DEBUG_LOG_DOCKING("Remap window '%s' %08X -> %08X\n", window->Name, src_dock_id, dst_dock_id);
14658 DockBuilderDockWindow(window->Name, dst_dock_id);
14659 }
14660 }
14661 }
14662
DockBuilderFinish(ImGuiID root_id)14663 void ImGui::DockBuilderFinish(ImGuiID root_id)
14664 {
14665 ImGuiContext* ctx = GImGui;
14666 //DockContextRebuild(ctx);
14667 DockContextBuildAddWindowsToNodes(ctx, root_id);
14668 }
14669
14670 //-----------------------------------------------------------------------------
14671 // Docking: Begin/End Support Functions (called from Begin/End)
14672 //-----------------------------------------------------------------------------
14673 // - GetWindowAlwaysWantOwnTabBar()
14674 // - DockContextBindNodeToWindow()
14675 // - BeginDocked()
14676 // - BeginDockableDragDropSource()
14677 // - BeginDockableDragDropTarget()
14678 //-----------------------------------------------------------------------------
14679
GetWindowAlwaysWantOwnTabBar(ImGuiWindow * window)14680 bool ImGui::GetWindowAlwaysWantOwnTabBar(ImGuiWindow* window)
14681 {
14682 ImGuiContext& g = *GImGui;
14683 if (g.IO.ConfigDockingAlwaysTabBar || window->WindowClass.DockingAlwaysTabBar)
14684 if ((window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoDocking)) == 0)
14685 if (!window->IsFallbackWindow) // We don't support AlwaysTabBar on the fallback/implicit window to avoid unused dock-node overhead/noise
14686 return true;
14687 return false;
14688 }
14689
DockContextBindNodeToWindow(ImGuiContext * ctx,ImGuiWindow * window)14690 static ImGuiDockNode* ImGui::DockContextBindNodeToWindow(ImGuiContext* ctx, ImGuiWindow* window)
14691 {
14692 ImGuiContext& g = *ctx;
14693 ImGuiDockNode* node = DockContextFindNodeByID(ctx, window->DockId);
14694 IM_ASSERT(window->DockNode == NULL);
14695
14696 // We should not be docking into a split node (SetWindowDock should avoid this)
14697 if (node && node->IsSplitNode())
14698 {
14699 DockContextProcessUndockWindow(ctx, window);
14700 return NULL;
14701 }
14702
14703 // Create node
14704 if (node == NULL)
14705 {
14706 node = DockContextAddNode(ctx, window->DockId);
14707 node->AuthorityForPos = node->AuthorityForSize = node->AuthorityForViewport = ImGuiDataAuthority_Window;
14708 node->LastFrameAlive = g.FrameCount;
14709 }
14710
14711 // If the node just turned visible and is part of a hierarchy, it doesn't have a Size assigned by DockNodeTreeUpdatePosSize() yet,
14712 // so we're forcing a Pos/Size update from the first ancestor that is already visible (often it will be the root node).
14713 // 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.
14714 // This is a little wonky because we don't normally update the Pos/Size of visible node mid-frame.
14715 if (!node->IsVisible)
14716 {
14717 ImGuiDockNode* ancestor_node = node;
14718 while (!ancestor_node->IsVisible)
14719 {
14720 ancestor_node->IsVisible = true;
14721 ancestor_node->MarkedForPosSizeWrite = true;
14722 if (ancestor_node->ParentNode)
14723 ancestor_node = ancestor_node->ParentNode;
14724 }
14725 IM_ASSERT(ancestor_node->Size.x > 0.0f && ancestor_node->Size.y > 0.0f);
14726 DockNodeTreeUpdatePosSize(ancestor_node, ancestor_node->Pos, ancestor_node->Size, true);
14727 }
14728
14729 // Add window to node
14730 DockNodeAddWindow(node, window, true);
14731 IM_ASSERT(node == window->DockNode);
14732 return node;
14733 }
14734
BeginDocked(ImGuiWindow * window,bool * p_open)14735 void ImGui::BeginDocked(ImGuiWindow* window, bool* p_open)
14736 {
14737 ImGuiContext* ctx = GImGui;
14738 ImGuiContext& g = *ctx;
14739
14740 const bool auto_dock_node = GetWindowAlwaysWantOwnTabBar(window);
14741 if (auto_dock_node)
14742 {
14743 if (window->DockId == 0)
14744 {
14745 IM_ASSERT(window->DockNode == NULL);
14746 window->DockId = DockContextGenNodeID(ctx);
14747 }
14748 }
14749 else
14750 {
14751 // Calling SetNextWindowPos() undock windows by default (by setting PosUndock)
14752 bool want_undock = false;
14753 want_undock |= (window->Flags & ImGuiWindowFlags_NoDocking) != 0;
14754 want_undock |= (g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasPos) && (window->SetWindowPosAllowFlags & g.NextWindowData.PosCond) && g.NextWindowData.PosUndock;
14755 if (want_undock)
14756 {
14757 DockContextProcessUndockWindow(ctx, window);
14758 return;
14759 }
14760 }
14761
14762 // Bind to our dock node
14763 ImGuiDockNode* node = window->DockNode;
14764 if (node != NULL)
14765 IM_ASSERT(window->DockId == node->ID);
14766 if (window->DockId != 0 && node == NULL)
14767 {
14768 node = DockContextBindNodeToWindow(ctx, window);
14769 if (node == NULL)
14770 return;
14771 }
14772
14773 #if 0
14774 // Undock if the ImGuiDockNodeFlags_NoDockingInCentralNode got set
14775 if (node->IsCentralNode && (node->Flags & ImGuiDockNodeFlags_NoDockingInCentralNode))
14776 {
14777 DockContextProcessUndockWindow(ctx, window);
14778 return;
14779 }
14780 #endif
14781
14782 // Undock if our dockspace node disappeared
14783 // Note how we are testing for LastFrameAlive and NOT LastFrameActive. A DockSpace node can be maintained alive while being inactive with ImGuiDockNodeFlags_KeepAliveOnly.
14784 if (node->LastFrameAlive < g.FrameCount)
14785 {
14786 // If the window has been orphaned, transition the docknode to an implicit node processed in DockContextUpdateDocking()
14787 ImGuiDockNode* root_node = DockNodeGetRootNode(node);
14788 if (root_node->LastFrameAlive < g.FrameCount)
14789 {
14790 DockContextProcessUndockWindow(ctx, window);
14791 }
14792 else
14793 {
14794 window->DockIsActive = true;
14795 window->DockTabIsVisible = false;
14796 }
14797 return;
14798 }
14799
14800 // Fast path return. It is common for windows to hold on a persistent DockId but be the only visible window,
14801 // and never create neither a host window neither a tab bar.
14802 // FIXME-DOCK: replace ->HostWindow NULL compare with something more explicit (~was initially intended as a first frame test)
14803 if (node->HostWindow == NULL)
14804 {
14805 window->DockIsActive = (node->State == ImGuiDockNodeState_HostWindowHiddenBecauseWindowsAreResizing);
14806 window->DockTabIsVisible = false;
14807 return;
14808 }
14809
14810 // We can have zero-sized nodes (e.g. children of a small-size dockspace)
14811 IM_ASSERT(node->HostWindow);
14812 IM_ASSERT(node->IsLeafNode());
14813 IM_ASSERT(node->Size.x >= 0.0f && node->Size.y >= 0.0f);
14814 node->State = ImGuiDockNodeState_HostWindowVisible;
14815
14816 // Undock if we are submitted earlier than the host window
14817 if (window->BeginOrderWithinContext < node->HostWindow->BeginOrderWithinContext)
14818 {
14819 DockContextProcessUndockWindow(ctx, window);
14820 return;
14821 }
14822
14823 // Position/Size window
14824 SetNextWindowPos(node->Pos);
14825 SetNextWindowSize(node->Size);
14826 g.NextWindowData.PosUndock = false; // Cancel implicit undocking of SetNextWindowPos()
14827 window->DockIsActive = true;
14828 window->DockTabIsVisible = false;
14829 if (node->SharedFlags & ImGuiDockNodeFlags_KeepAliveOnly)
14830 return;
14831
14832 // When the window is selected we mark it as visible.
14833 if (node->VisibleWindow == window)
14834 window->DockTabIsVisible = true;
14835
14836 // Update window flag
14837 IM_ASSERT((window->Flags & ImGuiWindowFlags_ChildWindow) == 0);
14838 window->Flags |= ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_AlwaysUseWindowPadding | ImGuiWindowFlags_NoResize;
14839 if (node->IsHiddenTabBar() || node->IsNoTabBar())
14840 window->Flags |= ImGuiWindowFlags_NoTitleBar;
14841 else
14842 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!
14843
14844 // Save new dock order only if the tab bar has been visible once.
14845 // This allows multiple windows to be created in the same frame and have their respective dock orders preserved.
14846 if (node->TabBar && node->TabBar->CurrFrameVisible != -1)
14847 window->DockOrder = (short)DockNodeGetTabOrder(window);
14848
14849 if ((node->WantCloseAll || node->WantCloseTabId == window->ID) && p_open != NULL)
14850 *p_open = false;
14851
14852 // Update ChildId to allow returning from Child to Parent with Escape
14853 ImGuiWindow* parent_window = window->DockNode->HostWindow;
14854 window->ChildId = parent_window->GetID(window->Name);
14855 }
14856
BeginDockableDragDropSource(ImGuiWindow * window)14857 void ImGui::BeginDockableDragDropSource(ImGuiWindow* window)
14858 {
14859 ImGuiContext& g = *GImGui;
14860 IM_ASSERT(g.ActiveId == window->MoveId);
14861 IM_ASSERT(g.MovingWindow == window);
14862
14863 window->DC.LastItemId = window->MoveId;
14864 window = window->RootWindow;
14865 IM_ASSERT((window->Flags & ImGuiWindowFlags_NoDocking) == 0);
14866 bool is_drag_docking = (g.IO.ConfigDockingWithShift) || ImRect(0, 0, window->SizeFull.x, GetFrameHeight()).Contains(g.ActiveIdClickOffset);
14867 if (is_drag_docking && BeginDragDropSource(ImGuiDragDropFlags_SourceNoPreviewTooltip | ImGuiDragDropFlags_SourceNoHoldToOpenOthers | ImGuiDragDropFlags_SourceAutoExpirePayload))
14868 {
14869 SetDragDropPayload(IMGUI_PAYLOAD_TYPE_WINDOW, &window, sizeof(window));
14870 EndDragDropSource();
14871 }
14872 }
14873
BeginDockableDragDropTarget(ImGuiWindow * window)14874 void ImGui::BeginDockableDragDropTarget(ImGuiWindow* window)
14875 {
14876 ImGuiContext* ctx = GImGui;
14877 ImGuiContext& g = *ctx;
14878
14879 //IM_ASSERT(window->RootWindow == window); // May also be a DockSpace
14880 IM_ASSERT((window->Flags & ImGuiWindowFlags_NoDocking) == 0);
14881 if (!g.DragDropActive)
14882 return;
14883 //GetForegroundDrawList(window)->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 255, 0, 255));
14884 if (!BeginDragDropTargetCustom(window->Rect(), window->ID))
14885 return;
14886
14887 // Peek into the payload before calling AcceptDragDropPayload() so we can handle overlapping dock nodes with filtering
14888 // (this is a little unusual pattern, normally most code would call AcceptDragDropPayload directly)
14889 const ImGuiPayload* payload = &g.DragDropPayload;
14890 if (!payload->IsDataType(IMGUI_PAYLOAD_TYPE_WINDOW) || !DockNodeIsDropAllowed(window, *(ImGuiWindow**)payload->Data))
14891 {
14892 EndDragDropTarget();
14893 return;
14894 }
14895
14896 ImGuiWindow* payload_window = *(ImGuiWindow**)payload->Data;
14897 if (AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_WINDOW, ImGuiDragDropFlags_AcceptBeforeDelivery | ImGuiDragDropFlags_AcceptNoDrawDefaultRect))
14898 {
14899 // Select target node
14900 // (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)
14901 bool dock_into_floating_window = false;
14902 ImGuiDockNode* node = NULL;
14903 if (window->DockNodeAsHost)
14904 {
14905 // Cannot assume that node will != NULL even though we passed the rectangle test: it depends on padding/spacing handled by DockNodeTreeFindVisibleNodeByPos().
14906 node = DockNodeTreeFindVisibleNodeByPos(window->DockNodeAsHost, g.IO.MousePos);
14907
14908 // There is an edge case when docking into a dockspace which only has _inactive_ nodes (because none of the windows are active)
14909 // In this case we need to fallback into any leaf mode, possibly the central node.
14910 // FIXME-20181220: We should not have to test for IsLeafNode() here but we have another bug to fix first.
14911 if (node && node->IsDockSpace() && node->IsRootNode())
14912 node = (node->CentralNode && node->IsLeafNode()) ? node->CentralNode : DockNodeTreeFindFallbackLeafNode(node);
14913 }
14914 else
14915 {
14916 if (window->DockNode)
14917 node = window->DockNode;
14918 else
14919 dock_into_floating_window = true; // Dock into a regular window
14920 }
14921
14922 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()));
14923 const bool is_explicit_target = g.IO.ConfigDockingWithShift || IsMouseHoveringRect(explicit_target_rect.Min, explicit_target_rect.Max);
14924
14925 // Preview docking request and find out split direction/ratio
14926 //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.
14927 const bool do_preview = payload->IsPreview() || payload->IsDelivery();
14928 if (do_preview && (node != NULL || dock_into_floating_window))
14929 {
14930 ImGuiDockPreviewData split_inner;
14931 ImGuiDockPreviewData split_outer;
14932 ImGuiDockPreviewData* split_data = &split_inner;
14933 if (node && (node->ParentNode || node->IsCentralNode()))
14934 if (ImGuiDockNode* root_node = DockNodeGetRootNode(node))
14935 {
14936 DockNodePreviewDockSetup(window, root_node, payload_window, &split_outer, is_explicit_target, true);
14937 if (split_outer.IsSplitDirExplicit)
14938 split_data = &split_outer;
14939 }
14940 DockNodePreviewDockSetup(window, node, payload_window, &split_inner, is_explicit_target, false);
14941 if (split_data == &split_outer)
14942 split_inner.IsDropAllowed = false;
14943
14944 // Draw inner then outer, so that previewed tab (in inner data) will be behind the outer drop boxes
14945 DockNodePreviewDockRender(window, node, payload_window, &split_inner);
14946 DockNodePreviewDockRender(window, node, payload_window, &split_outer);
14947
14948 // Queue docking request
14949 if (split_data->IsDropAllowed && payload->IsDelivery())
14950 DockContextQueueDock(ctx, window, split_data->SplitNode, payload_window, split_data->SplitDir, split_data->SplitRatio, split_data == &split_outer);
14951 }
14952 }
14953 EndDragDropTarget();
14954 }
14955
14956 //-----------------------------------------------------------------------------
14957 // Docking: Settings
14958 //-----------------------------------------------------------------------------
14959 // - DockSettingsRenameNodeReferences()
14960 // - DockSettingsRemoveNodeReferences()
14961 // - DockSettingsFindNodeSettings()
14962 // - DockSettingsHandler_ApplyAll()
14963 // - DockSettingsHandler_ReadOpen()
14964 // - DockSettingsHandler_ReadLine()
14965 // - DockSettingsHandler_DockNodeToSettings()
14966 // - DockSettingsHandler_WriteAll()
14967 //-----------------------------------------------------------------------------
14968
DockSettingsRenameNodeReferences(ImGuiID old_node_id,ImGuiID new_node_id)14969 static void ImGui::DockSettingsRenameNodeReferences(ImGuiID old_node_id, ImGuiID new_node_id)
14970 {
14971 ImGuiContext& g = *GImGui;
14972 IMGUI_DEBUG_LOG_DOCKING("DockSettingsRenameNodeReferences: from 0x%08X -> to 0x%08X\n", old_node_id, new_node_id);
14973 for (int window_n = 0; window_n < g.Windows.Size; window_n++)
14974 {
14975 ImGuiWindow* window = g.Windows[window_n];
14976 if (window->DockId == old_node_id && window->DockNode == NULL)
14977 window->DockId = new_node_id;
14978 }
14979 //// FIXME-OPT: We could remove this loop by storing the index in the map
14980 for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings))
14981 if (settings->DockId == old_node_id)
14982 settings->DockId = new_node_id;
14983 }
14984
14985 // Remove references stored in ImGuiWindowSettings to the given ImGuiDockNodeSettings
DockSettingsRemoveNodeReferences(ImGuiID * node_ids,int node_ids_count)14986 static void ImGui::DockSettingsRemoveNodeReferences(ImGuiID* node_ids, int node_ids_count)
14987 {
14988 ImGuiContext& g = *GImGui;
14989 int found = 0;
14990 //// FIXME-OPT: We could remove this loop by storing the index in the map
14991 for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings))
14992 for (int node_n = 0; node_n < node_ids_count; node_n++)
14993 if (settings->DockId == node_ids[node_n])
14994 {
14995 settings->DockId = 0;
14996 settings->DockOrder = -1;
14997 if (++found < node_ids_count)
14998 break;
14999 return;
15000 }
15001 }
15002
DockSettingsFindNodeSettings(ImGuiContext * ctx,ImGuiID id)15003 static ImGuiDockNodeSettings* ImGui::DockSettingsFindNodeSettings(ImGuiContext* ctx, ImGuiID id)
15004 {
15005 // FIXME-OPT
15006 ImGuiDockContext* dc = &ctx->DockContext;
15007 for (int n = 0; n < dc->NodesSettings.Size; n++)
15008 if (dc->NodesSettings[n].ID == id)
15009 return &dc->NodesSettings[n];
15010 return NULL;
15011 }
15012
15013 // Clear settings data
DockSettingsHandler_ClearAll(ImGuiContext * ctx,ImGuiSettingsHandler *)15014 static void ImGui::DockSettingsHandler_ClearAll(ImGuiContext* ctx, ImGuiSettingsHandler*)
15015 {
15016 ImGuiDockContext* dc = &ctx->DockContext;
15017 dc->NodesSettings.clear();
15018 DockContextClearNodes(ctx, 0, true);
15019 }
15020
15021 // Recreate nodes based on settings data
DockSettingsHandler_ApplyAll(ImGuiContext * ctx,ImGuiSettingsHandler *)15022 static void ImGui::DockSettingsHandler_ApplyAll(ImGuiContext* ctx, ImGuiSettingsHandler*)
15023 {
15024 // Prune settings at boot time only
15025 ImGuiDockContext* dc = &ctx->DockContext;
15026 if (ctx->Windows.Size == 0)
15027 DockContextPruneUnusedSettingsNodes(ctx);
15028 DockContextBuildNodesFromSettings(ctx, dc->NodesSettings.Data, dc->NodesSettings.Size);
15029 DockContextBuildAddWindowsToNodes(ctx, 0);
15030 }
15031
DockSettingsHandler_ReadOpen(ImGuiContext *,ImGuiSettingsHandler *,const char * name)15032 static void* ImGui::DockSettingsHandler_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name)
15033 {
15034 if (strcmp(name, "Data") != 0)
15035 return NULL;
15036 return (void*)1;
15037 }
15038
DockSettingsHandler_ReadLine(ImGuiContext * ctx,ImGuiSettingsHandler *,void *,const char * line)15039 static void ImGui::DockSettingsHandler_ReadLine(ImGuiContext* ctx, ImGuiSettingsHandler*, void*, const char* line)
15040 {
15041 char c = 0;
15042 int x = 0, y = 0;
15043 int r = 0;
15044
15045 // Parsing, e.g.
15046 // " DockNode ID=0x00000001 Pos=383,193 Size=201,322 Split=Y,0.506 "
15047 // " DockNode ID=0x00000002 Parent=0x00000001 "
15048 // Important: this code expect currently fields in a fixed order.
15049 ImGuiDockNodeSettings node;
15050 line = ImStrSkipBlank(line);
15051 if (strncmp(line, "DockNode", 8) == 0) { line = ImStrSkipBlank(line + strlen("DockNode")); }
15052 else if (strncmp(line, "DockSpace", 9) == 0) { line = ImStrSkipBlank(line + strlen("DockSpace")); node.Flags |= ImGuiDockNodeFlags_DockSpace; }
15053 else return;
15054 if (sscanf(line, "ID=0x%08X%n", &node.ID, &r) == 1) { line += r; } else return;
15055 if (sscanf(line, " Parent=0x%08X%n", &node.ParentNodeId, &r) == 1) { line += r; if (node.ParentNodeId == 0) return; }
15056 if (sscanf(line, " Window=0x%08X%n", &node.ParentWindowId, &r) ==1) { line += r; if (node.ParentWindowId == 0) return; }
15057 if (node.ParentNodeId == 0)
15058 {
15059 if (sscanf(line, " Pos=%i,%i%n", &x, &y, &r) == 2) { line += r; node.Pos = ImVec2ih((short)x, (short)y); } else return;
15060 if (sscanf(line, " Size=%i,%i%n", &x, &y, &r) == 2) { line += r; node.Size = ImVec2ih((short)x, (short)y); } else return;
15061 }
15062 else
15063 {
15064 if (sscanf(line, " SizeRef=%i,%i%n", &x, &y, &r) == 2) { line += r; node.SizeRef = ImVec2ih((short)x, (short)y); }
15065 }
15066 if (sscanf(line, " Split=%c%n", &c, &r) == 1) { line += r; if (c == 'X') node.SplitAxis = ImGuiAxis_X; else if (c == 'Y') node.SplitAxis = ImGuiAxis_Y; }
15067 if (sscanf(line, " NoResize=%d%n", &x, &r) == 1) { line += r; if (x != 0) node.Flags |= ImGuiDockNodeFlags_NoResize; }
15068 if (sscanf(line, " CentralNode=%d%n", &x, &r) == 1) { line += r; if (x != 0) node.Flags |= ImGuiDockNodeFlags_CentralNode; }
15069 if (sscanf(line, " NoTabBar=%d%n", &x, &r) == 1) { line += r; if (x != 0) node.Flags |= ImGuiDockNodeFlags_NoTabBar; }
15070 if (sscanf(line, " HiddenTabBar=%d%n", &x, &r) == 1) { line += r; if (x != 0) node.Flags |= ImGuiDockNodeFlags_HiddenTabBar; }
15071 if (sscanf(line, " NoWindowMenuButton=%d%n", &x, &r) == 1) { line += r; if (x != 0) node.Flags |= ImGuiDockNodeFlags_NoWindowMenuButton; }
15072 if (sscanf(line, " NoCloseButton=%d%n", &x, &r) == 1) { line += r; if (x != 0) node.Flags |= ImGuiDockNodeFlags_NoCloseButton; }
15073 if (sscanf(line, " Selected=0x%08X%n", &node.SelectedWindowId,&r) == 1) { line += r; }
15074 if (node.ParentNodeId != 0)
15075 if (ImGuiDockNodeSettings* parent_settings = DockSettingsFindNodeSettings(ctx, node.ParentNodeId))
15076 node.Depth = parent_settings->Depth + 1;
15077 ctx->DockContext.NodesSettings.push_back(node);
15078 }
15079
DockSettingsHandler_DockNodeToSettings(ImGuiDockContext * dc,ImGuiDockNode * node,int depth)15080 static void DockSettingsHandler_DockNodeToSettings(ImGuiDockContext* dc, ImGuiDockNode* node, int depth)
15081 {
15082 ImGuiDockNodeSettings node_settings;
15083 IM_ASSERT(depth < (1 << (sizeof(node_settings.Depth) << 3)));
15084 node_settings.ID = node->ID;
15085 node_settings.ParentNodeId = node->ParentNode ? node->ParentNode->ID : 0;
15086 node_settings.ParentWindowId = (node->IsDockSpace() && node->HostWindow && node->HostWindow->ParentWindow) ? node->HostWindow->ParentWindow->ID : 0;
15087 node_settings.SelectedWindowId = node->SelectedTabId;
15088 node_settings.SplitAxis = (signed char)(node->IsSplitNode() ? node->SplitAxis : ImGuiAxis_None);
15089 node_settings.Depth = (char)depth;
15090 node_settings.Flags = (node->LocalFlags & ImGuiDockNodeFlags_SavedFlagsMask_);
15091 node_settings.Pos = ImVec2ih(node->Pos);
15092 node_settings.Size = ImVec2ih(node->Size);
15093 node_settings.SizeRef = ImVec2ih(node->SizeRef);
15094 dc->NodesSettings.push_back(node_settings);
15095 if (node->ChildNodes[0])
15096 DockSettingsHandler_DockNodeToSettings(dc, node->ChildNodes[0], depth + 1);
15097 if (node->ChildNodes[1])
15098 DockSettingsHandler_DockNodeToSettings(dc, node->ChildNodes[1], depth + 1);
15099 }
15100
DockSettingsHandler_WriteAll(ImGuiContext * ctx,ImGuiSettingsHandler * handler,ImGuiTextBuffer * buf)15101 static void ImGui::DockSettingsHandler_WriteAll(ImGuiContext* ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf)
15102 {
15103 ImGuiContext& g = *ctx;
15104 ImGuiDockContext* dc = &ctx->DockContext;
15105 if (!(g.IO.ConfigFlags & ImGuiConfigFlags_DockingEnable))
15106 return;
15107
15108 // Gather settings data
15109 // (unlike our windows settings, because nodes are always built we can do a full rewrite of the SettingsNode buffer)
15110 dc->NodesSettings.resize(0);
15111 dc->NodesSettings.reserve(dc->Nodes.Data.Size);
15112 for (int n = 0; n < dc->Nodes.Data.Size; n++)
15113 if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p)
15114 if (node->IsRootNode())
15115 DockSettingsHandler_DockNodeToSettings(dc, node, 0);
15116
15117 int max_depth = 0;
15118 for (int node_n = 0; node_n < dc->NodesSettings.Size; node_n++)
15119 max_depth = ImMax((int)dc->NodesSettings[node_n].Depth, max_depth);
15120
15121 // Write to text buffer
15122 buf->appendf("[%s][Data]\n", handler->TypeName);
15123 for (int node_n = 0; node_n < dc->NodesSettings.Size; node_n++)
15124 {
15125 const int line_start_pos = buf->size(); (void)line_start_pos;
15126 const ImGuiDockNodeSettings* node_settings = &dc->NodesSettings[node_n];
15127 buf->appendf("%*s%s%*s", node_settings->Depth * 2, "", (node_settings->Flags & ImGuiDockNodeFlags_DockSpace) ? "DockSpace" : "DockNode ", (max_depth - node_settings->Depth) * 2, ""); // Text align nodes to facilitate looking at .ini file
15128 buf->appendf(" ID=0x%08X", node_settings->ID);
15129 if (node_settings->ParentNodeId)
15130 {
15131 buf->appendf(" Parent=0x%08X SizeRef=%d,%d", node_settings->ParentNodeId, node_settings->SizeRef.x, node_settings->SizeRef.y);
15132 }
15133 else
15134 {
15135 if (node_settings->ParentWindowId)
15136 buf->appendf(" Window=0x%08X", node_settings->ParentWindowId);
15137 buf->appendf(" Pos=%d,%d Size=%d,%d", node_settings->Pos.x, node_settings->Pos.y, node_settings->Size.x, node_settings->Size.y);
15138 }
15139 if (node_settings->SplitAxis != ImGuiAxis_None)
15140 buf->appendf(" Split=%c", (node_settings->SplitAxis == ImGuiAxis_X) ? 'X' : 'Y');
15141 if (node_settings->Flags & ImGuiDockNodeFlags_NoResize)
15142 buf->appendf(" NoResize=1");
15143 if (node_settings->Flags & ImGuiDockNodeFlags_CentralNode)
15144 buf->appendf(" CentralNode=1");
15145 if (node_settings->Flags & ImGuiDockNodeFlags_NoTabBar)
15146 buf->appendf(" NoTabBar=1");
15147 if (node_settings->Flags & ImGuiDockNodeFlags_HiddenTabBar)
15148 buf->appendf(" HiddenTabBar=1");
15149 if (node_settings->Flags & ImGuiDockNodeFlags_NoWindowMenuButton)
15150 buf->appendf(" NoWindowMenuButton=1");
15151 if (node_settings->Flags & ImGuiDockNodeFlags_NoCloseButton)
15152 buf->appendf(" NoCloseButton=1");
15153 if (node_settings->SelectedWindowId)
15154 buf->appendf(" Selected=0x%08X", node_settings->SelectedWindowId);
15155
15156 #if IMGUI_DEBUG_INI_SETTINGS
15157 // [DEBUG] Include comments in the .ini file to ease debugging
15158 if (ImGuiDockNode* node = DockContextFindNodeByID(ctx, node_settings->ID))
15159 {
15160 buf->appendf("%*s", ImMax(2, (line_start_pos + 92) - buf->size()), ""); // Align everything
15161 if (node->IsDockSpace() && node->HostWindow && node->HostWindow->ParentWindow)
15162 buf->appendf(" ; in '%s'", node->HostWindow->ParentWindow->Name);
15163 // Iterate settings so we can give info about windows that didn't exist during the session.
15164 int contains_window = 0;
15165 for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings))
15166 if (settings->DockId == node_settings->ID)
15167 {
15168 if (contains_window++ == 0)
15169 buf->appendf(" ; contains ");
15170 buf->appendf("'%s' ", settings->GetName());
15171 }
15172 }
15173 #endif
15174 buf->appendf("\n");
15175 }
15176 buf->appendf("\n");
15177 }
15178
15179
15180 //-----------------------------------------------------------------------------
15181 // [SECTION] PLATFORM DEPENDENT HELPERS
15182 //-----------------------------------------------------------------------------
15183
15184 #if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS)
15185
15186 #ifdef _MSC_VER
15187 #pragma comment(lib, "user32")
15188 #pragma comment(lib, "kernel32")
15189 #endif
15190
15191 // Win32 clipboard implementation
15192 // We use g.ClipboardHandlerData for temporary storage to ensure it is freed on Shutdown()
GetClipboardTextFn_DefaultImpl(void *)15193 static const char* GetClipboardTextFn_DefaultImpl(void*)
15194 {
15195 ImGuiContext& g = *GImGui;
15196 g.ClipboardHandlerData.clear();
15197 if (!::OpenClipboard(NULL))
15198 return NULL;
15199 HANDLE wbuf_handle = ::GetClipboardData(CF_UNICODETEXT);
15200 if (wbuf_handle == NULL)
15201 {
15202 ::CloseClipboard();
15203 return NULL;
15204 }
15205 if (const WCHAR* wbuf_global = (const WCHAR*)::GlobalLock(wbuf_handle))
15206 {
15207 int buf_len = ::WideCharToMultiByte(CP_UTF8, 0, wbuf_global, -1, NULL, 0, NULL, NULL);
15208 g.ClipboardHandlerData.resize(buf_len);
15209 ::WideCharToMultiByte(CP_UTF8, 0, wbuf_global, -1, g.ClipboardHandlerData.Data, buf_len, NULL, NULL);
15210 }
15211 ::GlobalUnlock(wbuf_handle);
15212 ::CloseClipboard();
15213 return g.ClipboardHandlerData.Data;
15214 }
15215
SetClipboardTextFn_DefaultImpl(void *,const char * text)15216 static void SetClipboardTextFn_DefaultImpl(void*, const char* text)
15217 {
15218 if (!::OpenClipboard(NULL))
15219 return;
15220 const int wbuf_length = ::MultiByteToWideChar(CP_UTF8, 0, text, -1, NULL, 0);
15221 HGLOBAL wbuf_handle = ::GlobalAlloc(GMEM_MOVEABLE, (SIZE_T)wbuf_length * sizeof(WCHAR));
15222 if (wbuf_handle == NULL)
15223 {
15224 ::CloseClipboard();
15225 return;
15226 }
15227 WCHAR* wbuf_global = (WCHAR*)::GlobalLock(wbuf_handle);
15228 ::MultiByteToWideChar(CP_UTF8, 0, text, -1, wbuf_global, wbuf_length);
15229 ::GlobalUnlock(wbuf_handle);
15230 ::EmptyClipboard();
15231 if (::SetClipboardData(CF_UNICODETEXT, wbuf_handle) == NULL)
15232 ::GlobalFree(wbuf_handle);
15233 ::CloseClipboard();
15234 }
15235
15236 #elif defined(__APPLE__) && TARGET_OS_OSX && defined(IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS)
15237
15238 #include <Carbon/Carbon.h> // Use old API to avoid need for separate .mm file
15239 static PasteboardRef main_clipboard = 0;
15240
15241 // OSX clipboard implementation
15242 // If you enable this you will need to add '-framework ApplicationServices' to your linker command-line!
SetClipboardTextFn_DefaultImpl(void *,const char * text)15243 static void SetClipboardTextFn_DefaultImpl(void*, const char* text)
15244 {
15245 if (!main_clipboard)
15246 PasteboardCreate(kPasteboardClipboard, &main_clipboard);
15247 PasteboardClear(main_clipboard);
15248 CFDataRef cf_data = CFDataCreate(kCFAllocatorDefault, (const UInt8*)text, strlen(text));
15249 if (cf_data)
15250 {
15251 PasteboardPutItemFlavor(main_clipboard, (PasteboardItemID)1, CFSTR("public.utf8-plain-text"), cf_data, 0);
15252 CFRelease(cf_data);
15253 }
15254 }
15255
GetClipboardTextFn_DefaultImpl(void *)15256 static const char* GetClipboardTextFn_DefaultImpl(void*)
15257 {
15258 if (!main_clipboard)
15259 PasteboardCreate(kPasteboardClipboard, &main_clipboard);
15260 PasteboardSynchronize(main_clipboard);
15261
15262 ItemCount item_count = 0;
15263 PasteboardGetItemCount(main_clipboard, &item_count);
15264 for (ItemCount i = 0; i < item_count; i++)
15265 {
15266 PasteboardItemID item_id = 0;
15267 PasteboardGetItemIdentifier(main_clipboard, i + 1, &item_id);
15268 CFArrayRef flavor_type_array = 0;
15269 PasteboardCopyItemFlavors(main_clipboard, item_id, &flavor_type_array);
15270 for (CFIndex j = 0, nj = CFArrayGetCount(flavor_type_array); j < nj; j++)
15271 {
15272 CFDataRef cf_data;
15273 if (PasteboardCopyItemFlavorData(main_clipboard, item_id, CFSTR("public.utf8-plain-text"), &cf_data) == noErr)
15274 {
15275 ImGuiContext& g = *GImGui;
15276 g.ClipboardHandlerData.clear();
15277 int length = (int)CFDataGetLength(cf_data);
15278 g.ClipboardHandlerData.resize(length + 1);
15279 CFDataGetBytes(cf_data, CFRangeMake(0, length), (UInt8*)g.ClipboardHandlerData.Data);
15280 g.ClipboardHandlerData[length] = 0;
15281 CFRelease(cf_data);
15282 return g.ClipboardHandlerData.Data;
15283 }
15284 }
15285 }
15286 return NULL;
15287 }
15288
15289 #else
15290
15291 // Local Dear ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers.
GetClipboardTextFn_DefaultImpl(void *)15292 static const char* GetClipboardTextFn_DefaultImpl(void*)
15293 {
15294 ImGuiContext& g = *GImGui;
15295 return g.ClipboardHandlerData.empty() ? NULL : g.ClipboardHandlerData.begin();
15296 }
15297
SetClipboardTextFn_DefaultImpl(void *,const char * text)15298 static void SetClipboardTextFn_DefaultImpl(void*, const char* text)
15299 {
15300 ImGuiContext& g = *GImGui;
15301 g.ClipboardHandlerData.clear();
15302 const char* text_end = text + strlen(text);
15303 g.ClipboardHandlerData.resize((int)(text_end - text) + 1);
15304 memcpy(&g.ClipboardHandlerData[0], text, (size_t)(text_end - text));
15305 g.ClipboardHandlerData[(int)(text_end - text)] = 0;
15306 }
15307
15308 #endif
15309
15310 //-----------------------------------------------------------------------------
15311 // [SECTION] METRICS/DEBUG WINDOW
15312 //-----------------------------------------------------------------------------
15313
RenderViewportThumbnail(ImDrawList * draw_list,ImGuiViewportP * viewport,const ImRect & bb)15314 static void RenderViewportThumbnail(ImDrawList* draw_list, ImGuiViewportP* viewport, const ImRect& bb)
15315 {
15316 ImGuiContext& g = *GImGui;
15317 ImGuiWindow* window = g.CurrentWindow;
15318
15319 ImVec2 scale = bb.GetSize() / viewport->Size;
15320 ImVec2 off = bb.Min - viewport->Pos * scale;
15321 float alpha_mul = (viewport->Flags & ImGuiViewportFlags_Minimized) ? 0.30f : 1.00f;
15322 window->DrawList->AddRectFilled(bb.Min, bb.Max, ImGui::GetColorU32(ImGuiCol_Border, alpha_mul * 0.40f));
15323 for (int i = 0; i != g.Windows.Size; i++)
15324 {
15325 ImGuiWindow* thumb_window = g.Windows[i];
15326 if (!thumb_window->WasActive || ((thumb_window->Flags & ImGuiWindowFlags_ChildWindow)))
15327 continue;
15328 if (thumb_window->SkipItems && (thumb_window->Flags & ImGuiWindowFlags_ChildWindow)) // FIXME-DOCK: Skip hidden docked windows. Identify those betters.
15329 continue;
15330 if (thumb_window->Viewport != viewport)
15331 continue;
15332
15333 ImRect thumb_r = thumb_window->Rect();
15334 ImRect title_r = thumb_window->TitleBarRect();
15335 ImRect thumb_r_scaled = ImRect(ImFloor(off + thumb_r.Min * scale), ImFloor(off + thumb_r.Max * scale));
15336 ImRect title_r_scaled = ImRect(ImFloor(off + title_r.Min * scale), ImFloor(off + ImVec2(title_r.Max.x, title_r.Min.y) * scale) + ImVec2(0,5)); // Exaggerate title bar height
15337 thumb_r_scaled.ClipWithFull(bb);
15338 title_r_scaled.ClipWithFull(bb);
15339 const bool window_is_focused = (g.NavWindow && thumb_window->RootWindowForTitleBarHighlight == g.NavWindow->RootWindowForTitleBarHighlight);
15340 window->DrawList->AddRectFilled(thumb_r_scaled.Min, thumb_r_scaled.Max, ImGui::GetColorU32(ImGuiCol_WindowBg, alpha_mul));
15341 window->DrawList->AddRectFilled(title_r_scaled.Min, title_r_scaled.Max, ImGui::GetColorU32(window_is_focused ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg, alpha_mul));
15342 window->DrawList->AddRect(thumb_r_scaled.Min, thumb_r_scaled.Max, ImGui::GetColorU32(ImGuiCol_Border, alpha_mul));
15343 if (ImGuiWindow* window_for_title = GetWindowForTitleDisplay(thumb_window))
15344 window->DrawList->AddText(g.Font, g.FontSize * 1.0f, title_r_scaled.Min, ImGui::GetColorU32(ImGuiCol_Text, alpha_mul), window_for_title->Name, ImGui::FindRenderedTextEnd(window_for_title->Name));
15345 }
15346 draw_list->AddRect(bb.Min, bb.Max, ImGui::GetColorU32(ImGuiCol_Border, alpha_mul));
15347 }
15348
ShowViewportThumbnails()15349 void ImGui::ShowViewportThumbnails()
15350 {
15351 ImGuiContext& g = *GImGui;
15352 ImGuiWindow* window = g.CurrentWindow;
15353
15354 // 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.
15355 float SCALE = 1.0f / 8.0f;
15356 ImRect bb_full;
15357 //for (int n = 0; n < g.PlatformIO.Monitors.Size; n++)
15358 // bb_full.Add(GetPlatformMonitorMainRect(g.PlatformIO.Monitors[n]));
15359 for (int n = 0; n < g.Viewports.Size; n++)
15360 bb_full.Add(g.Viewports[n]->GetMainRect());
15361 ImVec2 p = window->DC.CursorPos;
15362 ImVec2 off = p - bb_full.Min * SCALE;
15363 //for (int n = 0; n < g.PlatformIO.Monitors.Size; n++)
15364 // window->DrawList->AddRect(off + g.PlatformIO.Monitors[n].MainPos * SCALE, off + (g.PlatformIO.Monitors[n].MainPos + g.PlatformIO.Monitors[n].MainSize) * SCALE, ImGui::GetColorU32(ImGuiCol_Border));
15365 for (int n = 0; n < g.Viewports.Size; n++)
15366 {
15367 ImGuiViewportP* viewport = g.Viewports[n];
15368 ImRect viewport_draw_bb(off + (viewport->Pos) * SCALE, off + (viewport->Pos + viewport->Size) * SCALE);
15369 RenderViewportThumbnail(window->DrawList, viewport, viewport_draw_bb);
15370 }
15371 ImGui::Dummy(bb_full.GetSize() * SCALE);
15372 }
15373
15374 #ifndef IMGUI_DISABLE_METRICS_WINDOW
15375
15376 // Avoid naming collision with imgui_demo.cpp's HelpMarker() for unity builds.
MetricsHelpMarker(const char * desc)15377 static void MetricsHelpMarker(const char* desc)
15378 {
15379 ImGui::TextDisabled("(?)");
15380 if (ImGui::IsItemHovered())
15381 {
15382 ImGui::BeginTooltip();
15383 ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
15384 ImGui::TextUnformatted(desc);
15385 ImGui::PopTextWrapPos();
15386 ImGui::EndTooltip();
15387 }
15388 }
15389
ShowMetricsWindow(bool * p_open)15390 void ImGui::ShowMetricsWindow(bool* p_open)
15391 {
15392 if (!ImGui::Begin("Dear ImGui Metrics", p_open))
15393 {
15394 ImGui::End();
15395 return;
15396 }
15397
15398 // Debugging enums
15399 enum { WRT_OuterRect, WRT_OuterRectClipped, WRT_InnerRect, WRT_InnerClipRect, WRT_WorkRect, WRT_Content, WRT_ContentRegionRect, WRT_Count }; // Windows Rect Type
15400 const char* wrt_rects_names[WRT_Count] = { "OuterRect", "OuterRectClipped", "InnerRect", "InnerClipRect", "WorkRect", "Content", "ContentRegionRect" };
15401 enum { TRT_OuterRect, TRT_WorkRect, TRT_HostClipRect, TRT_InnerClipRect, TRT_BackgroundClipRect, TRT_ColumnsRect, TRT_ColumnsClipRect, TRT_ColumnsContentHeadersUsed, TRT_ColumnsContentHeadersIdeal, TRT_ColumnsContentRowsFrozen, TRT_ColumnsContentRowsUnfrozen, TRT_Count }; // Tables Rect Type
15402 const char* trt_rects_names[TRT_Count] = { "OuterRect", "WorkRect", "HostClipRect", "InnerClipRect", "BackgroundClipRect", "ColumnsRect", "ColumnsClipRect", "ColumnsContentHeadersUsed", "ColumnsContentHeadersIdeal", "ColumnsContentRowsFrozen", "ColumnsContentRowsUnfrozen" };
15403
15404 // State
15405 static bool show_windows_rects = false;
15406 static int show_windows_rect_type = WRT_WorkRect;
15407 static bool show_windows_begin_order = false;
15408 static bool show_tables_rects = false;
15409 static int show_tables_rect_type = TRT_WorkRect;
15410 static bool show_drawcmd_mesh = true;
15411 static bool show_drawcmd_aabb = true;
15412 static bool show_docking_nodes = false;
15413
15414 // Basic info
15415 ImGuiContext& g = *GImGui;
15416 ImGuiIO& io = ImGui::GetIO();
15417 ImGui::Text("Dear ImGui %s", ImGui::GetVersion());
15418 ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
15419 ImGui::Text("%d vertices, %d indices (%d triangles)", io.MetricsRenderVertices, io.MetricsRenderIndices, io.MetricsRenderIndices / 3);
15420 ImGui::Text("%d active windows (%d visible)", io.MetricsActiveWindows, io.MetricsRenderWindows);
15421 ImGui::Text("%d active allocations", io.MetricsActiveAllocations);
15422 ImGui::Separator();
15423
15424 // Helper functions to display common structures:
15425 // - NodeDrawList()
15426 // - NodeColumns()
15427 // - NodeWindow()
15428 // - NodeWindows()
15429 // - NodeViewport()
15430 // - NodeDockNode()
15431 // - NodeTabBar()
15432 // - NodeStorage()
15433 struct Funcs
15434 {
15435 static ImRect GetWindowRect(ImGuiWindow* window, int rect_type)
15436 {
15437 if (rect_type == WRT_OuterRect) { return window->Rect(); }
15438 else if (rect_type == WRT_OuterRectClipped) { return window->OuterRectClipped; }
15439 else if (rect_type == WRT_InnerRect) { return window->InnerRect; }
15440 else if (rect_type == WRT_InnerClipRect) { return window->InnerClipRect; }
15441 else if (rect_type == WRT_WorkRect) { return window->WorkRect; }
15442 else if (rect_type == WRT_Content) { ImVec2 min = window->InnerRect.Min - window->Scroll + window->WindowPadding; return ImRect(min, min + window->ContentSize); }
15443 else if (rect_type == WRT_ContentRegionRect) { return window->ContentRegionRect; }
15444 IM_ASSERT(0);
15445 return ImRect();
15446 }
15447
15448 static void NodeDrawCmdShowMeshAndBoundingBox(ImDrawList* fg_draw_list, const ImDrawList* draw_list, const ImDrawCmd* draw_cmd, int elem_offset, bool show_mesh, bool show_aabb)
15449 {
15450 IM_ASSERT(show_mesh || show_aabb);
15451 ImDrawIdx* idx_buffer = (draw_list->IdxBuffer.Size > 0) ? draw_list->IdxBuffer.Data : NULL;
15452
15453 // Draw wire-frame version of all triangles
15454 ImRect clip_rect = draw_cmd->ClipRect;
15455 ImRect vtxs_rect(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX);
15456 ImDrawListFlags backup_flags = fg_draw_list->Flags;
15457 fg_draw_list->Flags &= ~ImDrawListFlags_AntiAliasedLines; // Disable AA on triangle outlines is more readable for very large and thin triangles.
15458 for (unsigned int base_idx = elem_offset; base_idx < (elem_offset + draw_cmd->ElemCount); base_idx += 3)
15459 {
15460 ImVec2 triangle[3];
15461 for (int n = 0; n < 3; n++)
15462 {
15463 ImVec2 p = draw_list->VtxBuffer[idx_buffer ? idx_buffer[base_idx + n] : (base_idx + n)].pos;
15464 triangle[n] = p;
15465 vtxs_rect.Add(p);
15466 }
15467 if (show_mesh)
15468 fg_draw_list->AddPolyline(triangle, 3, IM_COL32(255, 255, 0, 255), true, 1.0f); // In yellow: mesh triangles
15469 }
15470 // Draw bounding boxes
15471 if (show_aabb)
15472 {
15473 fg_draw_list->AddRect(ImFloor(clip_rect.Min), ImFloor(clip_rect.Max), IM_COL32(255, 0, 255, 255)); // In pink: clipping rectangle submitted to GPU
15474 fg_draw_list->AddRect(ImFloor(vtxs_rect.Min), ImFloor(vtxs_rect.Max), IM_COL32(0, 255, 255, 255)); // In cyan: bounding box of triangles
15475 }
15476 fg_draw_list->Flags = backup_flags;
15477 }
15478
15479 // Note that both 'window' and 'viewport' may be NULL here. Viewport is generally null of destroyed popups which previously owned a viewport.
15480 static void NodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, ImDrawList* draw_list, const char* label)
15481 {
15482 bool node_open = ImGui::TreeNode(draw_list, "%s: '%s' %d vtx, %d indices, %d cmds", label, draw_list->_OwnerName ? draw_list->_OwnerName : "", draw_list->VtxBuffer.Size, draw_list->IdxBuffer.Size, draw_list->CmdBuffer.Size);
15483 if (draw_list == ImGui::GetWindowDrawList())
15484 {
15485 ImGui::SameLine();
15486 ImGui::TextColored(ImVec4(1.0f, 0.4f, 0.4f, 1.0f), "CURRENTLY APPENDING"); // Can't display stats for active draw list! (we don't have the data double-buffered)
15487 if (node_open) ImGui::TreePop();
15488 return;
15489 }
15490
15491 ImDrawList* fg_draw_list = viewport ? GetForegroundDrawList(viewport) : NULL; // Render additional visuals into the top-most draw list
15492 if (window && fg_draw_list && ImGui::IsItemHovered())
15493 fg_draw_list->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 255, 0, 255));
15494 if (!node_open)
15495 return;
15496
15497 if (window && !window->WasActive)
15498 ImGui::TextDisabled("Warning: owning Window is inactive. This DrawList is not being rendered!");
15499
15500 unsigned int elem_offset = 0;
15501 for (const ImDrawCmd* pcmd = draw_list->CmdBuffer.begin(); pcmd < draw_list->CmdBuffer.end(); elem_offset += pcmd->ElemCount, pcmd++)
15502 {
15503 if (pcmd->UserCallback == NULL && pcmd->ElemCount == 0)
15504 continue;
15505 if (pcmd->UserCallback)
15506 {
15507 ImGui::BulletText("Callback %p, user_data %p", pcmd->UserCallback, pcmd->UserCallbackData);
15508 continue;
15509 }
15510
15511 ImDrawIdx* idx_buffer = (draw_list->IdxBuffer.Size > 0) ? draw_list->IdxBuffer.Data : NULL;
15512 char buf[300];
15513 ImFormatString(buf, IM_ARRAYSIZE(buf), "DrawCmd:%5d triangles, Tex 0x%p, ClipRect (%4.0f,%4.0f)-(%4.0f,%4.0f)",
15514 pcmd->ElemCount / 3, (void*)(intptr_t)pcmd->TextureId,
15515 pcmd->ClipRect.x, pcmd->ClipRect.y, pcmd->ClipRect.z, pcmd->ClipRect.w);
15516 bool pcmd_node_open = ImGui::TreeNode((void*)(pcmd - draw_list->CmdBuffer.begin()), "%s", buf);
15517 if (ImGui::IsItemHovered() && (show_drawcmd_mesh || show_drawcmd_aabb) && fg_draw_list)
15518 NodeDrawCmdShowMeshAndBoundingBox(fg_draw_list, draw_list, pcmd, elem_offset, show_drawcmd_mesh, show_drawcmd_aabb);
15519 if (!pcmd_node_open)
15520 continue;
15521
15522 // Calculate approximate coverage area (touched pixel count)
15523 // This will be in pixels squared as long there's no post-scaling happening to the renderer output.
15524 float total_area = 0.0f;
15525 for (unsigned int base_idx = elem_offset; base_idx < (elem_offset + pcmd->ElemCount); base_idx += 3)
15526 {
15527 ImVec2 triangle[3];
15528 for (int n = 0; n < 3; n++)
15529 triangle[n] = draw_list->VtxBuffer[idx_buffer ? idx_buffer[base_idx + n] : (base_idx + n)].pos;
15530 total_area += ImTriangleArea(triangle[0], triangle[1], triangle[2]);
15531 }
15532
15533 // Display vertex information summary. Hover to get all triangles drawn in wire-frame
15534 ImFormatString(buf, IM_ARRAYSIZE(buf), "Mesh: ElemCount: %d, VtxOffset: +%d, IdxOffset: +%d, Area: ~%0.f px", pcmd->ElemCount, pcmd->VtxOffset, pcmd->IdxOffset, total_area);
15535 ImGui::Selectable(buf);
15536 if (ImGui::IsItemHovered() && fg_draw_list)
15537 NodeDrawCmdShowMeshAndBoundingBox(fg_draw_list, draw_list, pcmd, elem_offset, true, false);
15538
15539 // Display individual triangles/vertices. Hover on to get the corresponding triangle highlighted.
15540 ImGuiListClipper clipper(pcmd->ElemCount / 3); // Manually coarse clip our print out of individual vertices to save CPU, only items that may be visible.
15541 while (clipper.Step())
15542 for (int prim = clipper.DisplayStart, idx_i = elem_offset + clipper.DisplayStart * 3; prim < clipper.DisplayEnd; prim++)
15543 {
15544 char* buf_p = buf, *buf_end = buf + IM_ARRAYSIZE(buf);
15545 ImVec2 triangle[3];
15546 for (int n = 0; n < 3; n++, idx_i++)
15547 {
15548 ImDrawVert& v = draw_list->VtxBuffer[idx_buffer ? idx_buffer[idx_i] : idx_i];
15549 triangle[n] = v.pos;
15550 buf_p += ImFormatString(buf_p, buf_end - buf_p, "%s %04d: pos (%8.2f,%8.2f), uv (%.6f,%.6f), col %08X\n",
15551 (n == 0) ? "Vert:" : " ", idx_i, v.pos.x, v.pos.y, v.uv.x, v.uv.y, v.col);
15552 }
15553
15554 ImGui::Selectable(buf, false);
15555 if (fg_draw_list && ImGui::IsItemHovered())
15556 {
15557 ImDrawListFlags backup_flags = fg_draw_list->Flags;
15558 fg_draw_list->Flags &= ~ImDrawListFlags_AntiAliasedLines; // Disable AA on triangle outlines is more readable for very large and thin triangles.
15559 fg_draw_list->AddPolyline(triangle, 3, IM_COL32(255,255,0,255), true, 1.0f);
15560 fg_draw_list->Flags = backup_flags;
15561 }
15562 }
15563 ImGui::TreePop();
15564 }
15565 ImGui::TreePop();
15566 }
15567
15568 static void NodeColumns(const ImGuiColumns* columns)
15569 {
15570 if (!ImGui::TreeNode((void*)(uintptr_t)columns->ID, "Columns Id: 0x%08X, Count: %d, Flags: 0x%04X", columns->ID, columns->Count, columns->Flags))
15571 return;
15572 ImGui::BulletText("Width: %.1f (MinX: %.1f, MaxX: %.1f)", columns->OffMaxX - columns->OffMinX, columns->OffMinX, columns->OffMaxX);
15573 for (int column_n = 0; column_n < columns->Columns.Size; column_n++)
15574 ImGui::BulletText("Column %02d: OffsetNorm %.3f (= %.1f px)", column_n, columns->Columns[column_n].OffsetNorm, GetColumnOffsetFromNorm(columns, columns->Columns[column_n].OffsetNorm));
15575 ImGui::TreePop();
15576 }
15577
15578 static void NodeWindows(ImVector<ImGuiWindow*>& windows, const char* label)
15579 {
15580 if (!ImGui::TreeNode(label, "%s (%d)", label, windows.Size))
15581 return;
15582 ImGui::Text("(In front-to-back order:)");
15583 for (int i = windows.Size - 1; i >= 0; i--) // Iterate front to back
15584 {
15585 ImGui::PushID(windows[i]);
15586 Funcs::NodeWindow(windows[i], "Window");
15587 ImGui::PopID();
15588 }
15589 ImGui::TreePop();
15590 }
15591
15592 static void NodeWindow(ImGuiWindow* window, const char* label)
15593 {
15594 if (window == NULL)
15595 {
15596 ImGui::BulletText("%s: NULL", label);
15597 return;
15598 }
15599
15600 ImGuiContext& g = *GImGui;
15601 const bool is_active = window->WasActive;
15602 ImGuiTreeNodeFlags tree_node_flags = (window == g.NavWindow) ? ImGuiTreeNodeFlags_Selected : ImGuiTreeNodeFlags_None;
15603 if (!is_active) { PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled)); }
15604 const bool open = ImGui::TreeNodeEx(label, tree_node_flags, "%s '%s'%s", label, window->Name, is_active ? "" : " *Inactive*");
15605 if (!is_active) { PopStyleColor(); }
15606 if (ImGui::IsItemHovered() && is_active)
15607 ImGui::GetForegroundDrawList(window)->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 255, 0, 255));
15608 if (!open)
15609 return;
15610
15611 if (window->MemoryCompacted)
15612 ImGui::TextDisabled("Note: some memory buffers have been compacted/freed.");
15613
15614 ImGuiWindowFlags flags = window->Flags;
15615 NodeDrawList(window, window->Viewport, window->DrawList, "DrawList");
15616 ImGui::BulletText("Pos: (%.1f,%.1f), Size: (%.1f,%.1f), ContentSize (%.1f,%.1f)", window->Pos.x, window->Pos.y, window->Size.x, window->Size.y, window->ContentSize.x, window->ContentSize.y);
15617 ImGui::BulletText("Flags: 0x%08X (%s%s%s%s%s%s%s%s%s..)", flags,
15618 (flags & ImGuiWindowFlags_ChildWindow) ? "Child " : "", (flags & ImGuiWindowFlags_Tooltip) ? "Tooltip " : "", (flags & ImGuiWindowFlags_Popup) ? "Popup " : "",
15619 (flags & ImGuiWindowFlags_Modal) ? "Modal " : "", (flags & ImGuiWindowFlags_ChildMenu) ? "ChildMenu " : "", (flags & ImGuiWindowFlags_NoSavedSettings) ? "NoSavedSettings " : "",
15620 (flags & ImGuiWindowFlags_NoMouseInputs)? "NoMouseInputs":"", (flags & ImGuiWindowFlags_NoNavInputs) ? "NoNavInputs" : "", (flags & ImGuiWindowFlags_AlwaysAutoResize) ? "AlwaysAutoResize" : "");
15621 ImGui::BulletText("WindowClassId: 0x%08X", window->WindowClass.ClassId);
15622 ImGui::BulletText("Scroll: (%.2f/%.2f,%.2f/%.2f) Scrollbar:%s%s", window->Scroll.x, window->ScrollMax.x, window->Scroll.y, window->ScrollMax.y, window->ScrollbarX ? "X" : "", window->ScrollbarY ? "Y" : "");
15623 ImGui::BulletText("Active: %d/%d, WriteAccessed: %d, BeginOrderWithinContext: %d", window->Active, window->WasActive, window->WriteAccessed, (window->Active || window->WasActive) ? window->BeginOrderWithinContext : -1);
15624 ImGui::BulletText("Appearing: %d, Hidden: %d (CanSkip %d Cannot %d), SkipItems: %d", window->Appearing, window->Hidden, window->HiddenFramesCanSkipItems, window->HiddenFramesCannotSkipItems, window->SkipItems);
15625 ImGui::BulletText("NavLastIds: 0x%08X,0x%08X, NavLayerActiveMask: %X", window->NavLastIds[0], window->NavLastIds[1], window->DC.NavLayerActiveMask);
15626 ImGui::BulletText("NavLastChildNavWindow: %s", window->NavLastChildNavWindow ? window->NavLastChildNavWindow->Name : "NULL");
15627 if (!window->NavRectRel[0].IsInverted())
15628 ImGui::BulletText("NavRectRel[0]: (%.1f,%.1f)(%.1f,%.1f)", window->NavRectRel[0].Min.x, window->NavRectRel[0].Min.y, window->NavRectRel[0].Max.x, window->NavRectRel[0].Max.y);
15629 else
15630 ImGui::BulletText("NavRectRel[0]: <None>");
15631 ImGui::BulletText("Viewport: %d%s, ViewportId: 0x%08X, ViewportPos: (%.1f,%.1f)", window->Viewport ? window->Viewport->Idx : -1, window->ViewportOwned ? " (Owned)" : "", window->ViewportId, window->ViewportPos.x, window->ViewportPos.y);
15632 ImGui::BulletText("ViewportMonitor: %d", window->Viewport ? window->Viewport->PlatformMonitor : -1);
15633 ImGui::BulletText("DockId: 0x%04X, DockOrder: %d, Act: %d, Vis: %d", window->DockId, window->DockOrder, window->DockIsActive, window->DockTabIsVisible);
15634 if (window->DockNode || window->DockNodeAsHost)
15635 NodeDockNode(window->DockNodeAsHost ? window->DockNodeAsHost : window->DockNode, window->DockNodeAsHost ? "DockNodeAsHost" : "DockNode");
15636 if (window->RootWindow != window) NodeWindow(window->RootWindow, "RootWindow");
15637 if (window->RootWindowDockStop != window->RootWindow) NodeWindow(window->RootWindowDockStop, "RootWindowDockStop");
15638 if (window->ParentWindow != NULL) NodeWindow(window->ParentWindow, "ParentWindow");
15639 if (window->DC.ChildWindows.Size > 0) NodeWindows(window->DC.ChildWindows, "ChildWindows");
15640 if (window->ColumnsStorage.Size > 0 && ImGui::TreeNode("Columns", "Columns sets (%d)", window->ColumnsStorage.Size))
15641 {
15642 for (int n = 0; n < window->ColumnsStorage.Size; n++)
15643 NodeColumns(&window->ColumnsStorage[n]);
15644 ImGui::TreePop();
15645 }
15646 NodeStorage(&window->StateStorage, "Storage");
15647 ImGui::TreePop();
15648 }
15649
15650 static void NodeWindowSettings(ImGuiWindowSettings* settings)
15651 {
15652 ImGui::Text("0x%08X \"%s\" Pos (%d,%d) Size (%d,%d) Collapsed=%d",
15653 settings->ID, settings->GetName(), settings->Pos.x, settings->Pos.y, settings->Size.x, settings->Size.y, settings->Collapsed);
15654 }
15655
15656 static void NodeViewport(ImGuiViewportP* viewport)
15657 {
15658 ImGui::SetNextItemOpen(true, ImGuiCond_Once);
15659 if (ImGui::TreeNode((void*)(intptr_t)viewport->ID, "Viewport #%d, ID: 0x%08X, Parent: 0x%08X, Window: \"%s\"", viewport->Idx, viewport->ID, viewport->ParentViewportId, viewport->Window ? viewport->Window->Name : "N/A"))
15660 {
15661 ImGuiWindowFlags flags = viewport->Flags;
15662 ImGui::BulletText("Main Pos: (%.0f,%.0f), Size: (%.0f,%.0f)\nWorkArea Offset Left: %.0f Top: %.0f, Right: %.0f, Bottom: %.0f\nMonitor: %d, DpiScale: %.0f%%",
15663 viewport->Pos.x, viewport->Pos.y, viewport->Size.x, viewport->Size.y,
15664 viewport->WorkOffsetMin.x, viewport->WorkOffsetMin.y, viewport->WorkOffsetMax.x, viewport->WorkOffsetMax.y,
15665 viewport->PlatformMonitor, viewport->DpiScale * 100.0f);
15666 if (viewport->Idx > 0) { ImGui::SameLine(); if (ImGui::SmallButton("Reset Pos")) { viewport->Pos = ImVec2(200,200); if (viewport->Window) viewport->Window->Pos = ImVec2(200,200); } }
15667 ImGui::BulletText("Flags: 0x%04X =%s%s%s%s%s%s%s", viewport->Flags,
15668 (flags & ImGuiViewportFlags_CanHostOtherWindows) ? " CanHostOtherWindows" : "", (flags & ImGuiViewportFlags_NoDecoration) ? " NoDecoration" : "",
15669 (flags & ImGuiViewportFlags_NoFocusOnAppearing) ? " NoFocusOnAppearing" : "", (flags & ImGuiViewportFlags_NoInputs) ? " NoInputs" : "",
15670 (flags & ImGuiViewportFlags_NoRendererClear) ? " NoRendererClear" : "", (flags & ImGuiViewportFlags_Minimized) ? " Minimized" : "",
15671 (flags & ImGuiViewportFlags_NoAutoMerge) ? " NoAutoMerge" : "");
15672 for (int layer_i = 0; layer_i < IM_ARRAYSIZE(viewport->DrawDataBuilder.Layers); layer_i++)
15673 for (int draw_list_i = 0; draw_list_i < viewport->DrawDataBuilder.Layers[layer_i].Size; draw_list_i++)
15674 Funcs::NodeDrawList(NULL, viewport, viewport->DrawDataBuilder.Layers[layer_i][draw_list_i], "DrawList");
15675 ImGui::TreePop();
15676 }
15677 }
15678
15679 static void NodeDockNode(ImGuiDockNode* node, const char* label)
15680 {
15681 ImGuiContext& g = *GImGui;
15682 const bool is_alive = (g.FrameCount - node->LastFrameAlive < 2); // Submitted with ImGuiDockNodeFlags_KeepAliveOnly
15683 const bool is_active = (g.FrameCount - node->LastFrameActive < 2); // Submitted
15684 if (!is_alive) { PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled)); }
15685 bool open;
15686 if (node->Windows.Size > 0)
15687 open = ImGui::TreeNode((void*)(intptr_t)node->ID, "%s 0x%04X%s: %d windows (vis: '%s')", label, node->ID, node->IsVisible ? "" : " (hidden)", node->Windows.Size, node->VisibleWindow ? node->VisibleWindow->Name : "NULL");
15688 else
15689 open = ImGui::TreeNode((void*)(intptr_t)node->ID, "%s 0x%04X%s: %s split (vis: '%s')", label, node->ID, node->IsVisible ? "" : " (hidden)", (node->SplitAxis == ImGuiAxis_X) ? "horizontal" : (node->SplitAxis == ImGuiAxis_Y) ? "vertical" : "n/a", node->VisibleWindow ? node->VisibleWindow->Name : "NULL");
15690 if (!is_alive) { PopStyleColor(); }
15691 if (is_active && ImGui::IsItemHovered())
15692 GetForegroundDrawList(node->HostWindow ? node->HostWindow : node->VisibleWindow)->AddRect(node->Pos, node->Pos + node->Size, IM_COL32(255, 255, 0, 255));
15693 if (open)
15694 {
15695 IM_ASSERT(node->ChildNodes[0] == NULL || node->ChildNodes[0]->ParentNode == node);
15696 IM_ASSERT(node->ChildNodes[1] == NULL || node->ChildNodes[1]->ParentNode == node);
15697 ImGui::BulletText("Pos (%.0f,%.0f), Size (%.0f, %.0f) Ref (%.0f, %.0f)",
15698 node->Pos.x, node->Pos.y, node->Size.x, node->Size.y, node->SizeRef.x, node->SizeRef.y);
15699 NodeWindow(node->HostWindow, "HostWindow");
15700 NodeWindow(node->VisibleWindow, "VisibleWindow");
15701 ImGui::BulletText("SelectedTabID: 0x%08X, LastFocusedNodeID: 0x%08X", node->SelectedTabId, node->LastFocusedNodeId);
15702 ImGui::BulletText("Misc:%s%s%s%s%s",
15703 node->IsDockSpace() ? " IsDockSpace" : "",
15704 node->IsCentralNode() ? " IsCentralNode" : "",
15705 is_alive ? " IsAlive" : "", is_active ? " IsActive" : "",
15706 node->WantLockSizeOnce ? " WantLockSizeOnce" : "");
15707 if (ImGui::TreeNode("flags", "LocalFlags: 0x%04X SharedFlags: 0x%04X", node->LocalFlags, node->SharedFlags))
15708 {
15709 ImGui::CheckboxFlags("LocalFlags: NoDocking", (ImU32*)&node->LocalFlags, ImGuiDockNodeFlags_NoDocking);
15710 ImGui::CheckboxFlags("LocalFlags: NoSplit", (ImU32*)&node->LocalFlags, ImGuiDockNodeFlags_NoSplit);
15711 ImGui::CheckboxFlags("LocalFlags: NoResize", (ImU32*)&node->LocalFlags, ImGuiDockNodeFlags_NoResize);
15712 ImGui::CheckboxFlags("LocalFlags: NoResizeX", (ImU32*)&node->LocalFlags, ImGuiDockNodeFlags_NoResizeX);
15713 ImGui::CheckboxFlags("LocalFlags: NoResizeY", (ImU32*)&node->LocalFlags, ImGuiDockNodeFlags_NoResizeY);
15714 ImGui::CheckboxFlags("LocalFlags: NoTabBar", (ImU32*)&node->LocalFlags, ImGuiDockNodeFlags_NoTabBar);
15715 ImGui::CheckboxFlags("LocalFlags: HiddenTabBar", (ImU32*)&node->LocalFlags, ImGuiDockNodeFlags_HiddenTabBar);
15716 ImGui::CheckboxFlags("LocalFlags: NoWindowMenuButton", (ImU32*)&node->LocalFlags, ImGuiDockNodeFlags_NoWindowMenuButton);
15717 ImGui::CheckboxFlags("LocalFlags: NoCloseButton", (ImU32*)&node->LocalFlags, ImGuiDockNodeFlags_NoCloseButton);
15718 ImGui::TreePop();
15719 }
15720 if (node->ParentNode)
15721 NodeDockNode(node->ParentNode, "ParentNode");
15722 if (node->ChildNodes[0])
15723 NodeDockNode(node->ChildNodes[0], "Child[0]");
15724 if (node->ChildNodes[1])
15725 NodeDockNode(node->ChildNodes[1], "Child[1]");
15726 if (node->TabBar)
15727 NodeTabBar(node->TabBar);
15728 ImGui::TreePop();
15729 }
15730 }
15731
15732 static void NodeTabBar(ImGuiTabBar* tab_bar)
15733 {
15734 // Standalone tab bars (not associated to docking/windows functionality) currently hold no discernible strings.
15735 char buf[256];
15736 char* p = buf;
15737 const char* buf_end = buf + IM_ARRAYSIZE(buf);
15738 const bool is_active = (tab_bar->PrevFrameVisible >= ImGui::GetFrameCount() - 2);
15739 p += ImFormatString(p, buf_end - p, "Tab Bar 0x%08X (%d tabs)%s", tab_bar->ID, tab_bar->Tabs.Size, is_active ? "" : " *Inactive*");
15740 if (tab_bar->Flags & ImGuiTabBarFlags_DockNode)
15741 {
15742 p += ImFormatString(p, buf_end - p, " { ");
15743 for (int tab_n = 0; tab_n < ImMin(tab_bar->Tabs.Size, 3); tab_n++)
15744 p += ImFormatString(p, buf_end - p, "%s'%s'", tab_n > 0 ? ", " : "", tab_bar->Tabs[tab_n].Window->Name);
15745 p += ImFormatString(p, buf_end - p, (tab_bar->Tabs.Size > 3) ? " ... }" : " } ");
15746 }
15747 if (!is_active) { PushStyleColor(ImGuiCol_Text, GetStyleColorVec4(ImGuiCol_TextDisabled)); }
15748 bool open = ImGui::TreeNode(tab_bar, "%s", buf);
15749 if (!is_active) { PopStyleColor(); }
15750 if (open)
15751 {
15752 for (int tab_n = 0; tab_n < tab_bar->Tabs.Size; tab_n++)
15753 {
15754 const ImGuiTabItem* tab = &tab_bar->Tabs[tab_n];
15755 ImGui::PushID(tab);
15756 if (ImGui::SmallButton("<")) { TabBarQueueChangeTabOrder(tab_bar, tab, -1); } ImGui::SameLine(0, 2);
15757 if (ImGui::SmallButton(">")) { TabBarQueueChangeTabOrder(tab_bar, tab, +1); } ImGui::SameLine();
15758 ImGui::Text("%02d%c Tab 0x%08X '%s'", tab_n, (tab->ID == tab_bar->SelectedTabId) ? '*' : ' ', tab->ID, (tab->Window || tab->NameOffset != -1) ? tab_bar->GetTabName(tab) : "");
15759 ImGui::PopID();
15760 }
15761 ImGui::TreePop();
15762 }
15763 }
15764
15765 static void NodeStorage(ImGuiStorage* storage, const char* label)
15766 {
15767 if (!ImGui::TreeNode(label, "%s: %d entries, %d bytes", label, storage->Data.Size, storage->Data.size_in_bytes()))
15768 return;
15769 for (int n = 0; n < storage->Data.Size; n++)
15770 {
15771 const ImGuiStorage::ImGuiStoragePair& p = storage->Data[n];
15772 ImGui::BulletText("Key 0x%08X Value { i: %d }", p.key, p.val_i); // Important: we currently don't store a type, real value may not be integer.
15773 }
15774 ImGui::TreePop();
15775 }
15776 };
15777
15778 // Tools
15779 if (ImGui::TreeNode("Tools"))
15780 {
15781 // The Item Picker tool is super useful to visually select an item and break into the call-stack of where it was submitted.
15782 if (ImGui::Button("Item Picker.."))
15783 ImGui::DebugStartItemPicker();
15784 ImGui::SameLine();
15785 MetricsHelpMarker("Will call the IM_DEBUG_BREAK() macro to break in debugger.\nWarning: If you don't have a debugger attached, this will probably crash.");
15786
15787 ImGui::Checkbox("Show windows begin order", &show_windows_begin_order);
15788 ImGui::Checkbox("Show windows rectangles", &show_windows_rects);
15789 ImGui::SameLine();
15790 ImGui::SetNextItemWidth(ImGui::GetFontSize() * 12);
15791 show_windows_rects |= ImGui::Combo("##show_windows_rect_type", &show_windows_rect_type, wrt_rects_names, WRT_Count, WRT_Count);
15792 if (show_windows_rects && g.NavWindow)
15793 {
15794 ImGui::BulletText("'%s':", g.NavWindow->Name);
15795 ImGui::Indent();
15796 for (int rect_n = 0; rect_n < WRT_Count; rect_n++)
15797 {
15798 ImRect r = Funcs::GetWindowRect(g.NavWindow, rect_n);
15799 ImGui::Text("(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) %s", r.Min.x, r.Min.y, r.Max.x, r.Max.y, r.GetWidth(), r.GetHeight(), wrt_rects_names[rect_n]);
15800 }
15801 ImGui::Unindent();
15802 }
15803 ImGui::Checkbox("Show mesh when hovering ImDrawCmd", &show_drawcmd_mesh);
15804 ImGui::Checkbox("Show bounding boxes when hovering ImDrawCmd", &show_drawcmd_aabb);
15805 ImGui::TreePop();
15806 }
15807
15808 // Contents
15809 Funcs::NodeWindows(g.Windows, "Windows");
15810 //Funcs::NodeWindows(g.WindowsFocusOrder, "WindowsFocusOrder");
15811 if (ImGui::TreeNode("Viewport", "Viewports (%d)", g.Viewports.Size))
15812 {
15813 ImGui::Indent(ImGui::GetTreeNodeToLabelSpacing());
15814 ImGui::ShowViewportThumbnails();
15815 ImGui::Unindent(ImGui::GetTreeNodeToLabelSpacing());
15816 bool open = ImGui::TreeNode("Monitors", "Monitors (%d)", g.PlatformIO.Monitors.Size);
15817 ImGui::SameLine();
15818 MetricsHelpMarker("Dear ImGui uses monitor data:\n- to query DPI settings on a per monitor basis\n- to position popup/tooltips so they don't straddle monitors.");
15819 if (open)
15820 {
15821 for (int i = 0; i < g.PlatformIO.Monitors.Size; i++)
15822 {
15823 const ImGuiPlatformMonitor& mon = g.PlatformIO.Monitors[i];
15824 ImGui::BulletText("Monitor #%d: DPI %.0f%%\n MainMin (%.0f,%.0f), MainMax (%.0f,%.0f), MainSize (%.0f,%.0f)\n WorkMin (%.0f,%.0f), WorkMax (%.0f,%.0f), WorkSize (%.0f,%.0f)",
15825 i, mon.DpiScale * 100.0f,
15826 mon.MainPos.x, mon.MainPos.y, mon.MainPos.x + mon.MainSize.x, mon.MainPos.y + mon.MainSize.y, mon.MainSize.x, mon.MainSize.y,
15827 mon.WorkPos.x, mon.WorkPos.y, mon.WorkPos.x + mon.WorkSize.x, mon.WorkPos.y + mon.WorkSize.y, mon.WorkSize.x, mon.WorkSize.y);
15828 }
15829 ImGui::TreePop();
15830 }
15831 for (int i = 0; i < g.Viewports.Size; i++)
15832 Funcs::NodeViewport(g.Viewports[i]);
15833 ImGui::TreePop();
15834 }
15835
15836 // Details for Popups
15837 if (ImGui::TreeNode("Popups", "Popups (%d)", g.OpenPopupStack.Size))
15838 {
15839 for (int i = 0; i < g.OpenPopupStack.Size; i++)
15840 {
15841 ImGuiWindow* window = g.OpenPopupStack[i].Window;
15842 ImGui::BulletText("PopupID: %08x, Window: '%s'%s%s", g.OpenPopupStack[i].PopupId, window ? window->Name : "NULL", window && (window->Flags & ImGuiWindowFlags_ChildWindow) ? " ChildWindow" : "", window && (window->Flags & ImGuiWindowFlags_ChildMenu) ? " ChildMenu" : "");
15843 }
15844 ImGui::TreePop();
15845 }
15846
15847 // Details for TabBars
15848 if (ImGui::TreeNode("TabBars", "Tab Bars (%d)", g.TabBars.GetSize()))
15849 {
15850 for (int n = 0; n < g.TabBars.GetSize(); n++)
15851 Funcs::NodeTabBar(g.TabBars.GetByIndex(n));
15852 ImGui::TreePop();
15853 }
15854
15855 // Details for Tables
15856 IM_UNUSED(trt_rects_names);
15857 IM_UNUSED(show_tables_rects);
15858 IM_UNUSED(show_tables_rect_type);
15859 #ifdef IMGUI_HAS_TABLE
15860 if (ImGui::TreeNode("Tables", "Tables (%d)", g.Tables.GetSize()))
15861 {
15862 for (int n = 0; n < g.Tables.GetSize(); n++)
15863 Funcs::NodeTable(g.Tables.GetByIndex(n));
15864 ImGui::TreePop();
15865 }
15866 #endif // #ifdef IMGUI_HAS_TABLE
15867
15868 // Details for Docking
15869 #ifdef IMGUI_HAS_DOCK
15870 if (ImGui::TreeNode("Dock nodes"))
15871 {
15872 static bool root_nodes_only = true;
15873 ImGuiDockContext* dc = &g.DockContext;
15874 ImGui::Checkbox("List root nodes", &root_nodes_only);
15875 ImGui::Checkbox("Ctrl shows window dock info", &show_docking_nodes);
15876 if (ImGui::SmallButton("Clear nodes")) { DockContextClearNodes(&g, 0, true); }
15877 ImGui::SameLine();
15878 if (ImGui::SmallButton("Rebuild all")) { dc->WantFullRebuild = true; }
15879 for (int n = 0; n < dc->Nodes.Data.Size; n++)
15880 if (ImGuiDockNode* node = (ImGuiDockNode*)dc->Nodes.Data[n].val_p)
15881 if (!root_nodes_only || node->IsRootNode())
15882 Funcs::NodeDockNode(node, "Node");
15883 ImGui::TreePop();
15884 }
15885 #endif // #ifdef IMGUI_HAS_DOCK
15886
15887 // Settings
15888 if (ImGui::TreeNode("Settings"))
15889 {
15890 if (ImGui::SmallButton("Clear"))
15891 ImGui::ClearIniSettings();
15892 ImGui::SameLine();
15893 if (ImGui::SmallButton("Save to memory"))
15894 ImGui::SaveIniSettingsToMemory();
15895 ImGui::SameLine();
15896 if (ImGui::SmallButton("Save to disk"))
15897 ImGui::SaveIniSettingsToDisk(g.IO.IniFilename);
15898 ImGui::SameLine();
15899 if (g.IO.IniFilename)
15900 ImGui::Text("\"%s\"", g.IO.IniFilename);
15901 else
15902 ImGui::TextUnformatted("<NULL>");
15903 ImGui::Text("SettingsDirtyTimer %.2f", g.SettingsDirtyTimer);
15904 if (ImGui::TreeNode("SettingsHandlers", "Settings handlers: (%d)", g.SettingsHandlers.Size))
15905 {
15906 for (int n = 0; n < g.SettingsHandlers.Size; n++)
15907 ImGui::BulletText("%s", g.SettingsHandlers[n].TypeName);
15908 ImGui::TreePop();
15909 }
15910 if (ImGui::TreeNode("SettingsWindows", "Settings packed data: Windows: %d bytes", g.SettingsWindows.size()))
15911 {
15912 for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings))
15913 Funcs::NodeWindowSettings(settings);
15914 ImGui::TreePop();
15915 }
15916
15917 #ifdef IMGUI_HAS_TABLE
15918 if (ImGui::TreeNode("SettingsTables", "Settings packed data: Tables: %d bytes", g.SettingsTables.size()))
15919 {
15920 for (ImGuiTableSettings* settings = g.SettingsTables.begin(); settings != NULL; settings = g.SettingsTables.next_chunk(settings))
15921 Funcs::NodeTableSettings(settings);
15922 ImGui::TreePop();
15923 }
15924 #endif // #ifdef IMGUI_HAS_TABLE
15925
15926 #ifdef IMGUI_HAS_DOCK
15927 if (ImGui::TreeNode("SettingsDocking", "Settings packed data: Docking"))
15928 {
15929 ImGuiDockContext* dc = &g.DockContext;
15930 ImGui::Text("In SettingsWindows:");
15931 for (ImGuiWindowSettings* settings = g.SettingsWindows.begin(); settings != NULL; settings = g.SettingsWindows.next_chunk(settings))
15932 if (settings->DockId != 0)
15933 ImGui::BulletText("Window '%s' -> DockId %08X", settings->GetName(), settings->DockId);
15934 ImGui::Text("In SettingsNodes:");
15935 for (int n = 0; n < dc->NodesSettings.Size; n++)
15936 {
15937 ImGuiDockNodeSettings* settings = &dc->NodesSettings[n];
15938 const char* selected_tab_name = NULL;
15939 if (settings->SelectedWindowId)
15940 {
15941 if (ImGuiWindow* window = FindWindowByID(settings->SelectedWindowId))
15942 selected_tab_name = window->Name;
15943 else if (ImGuiWindowSettings* window_settings = FindWindowSettings(settings->SelectedWindowId))
15944 selected_tab_name = window_settings->GetName();
15945 }
15946 ImGui::BulletText("Node %08X, Parent %08X, SelectedTab %08X ('%s')", settings->ID, settings->ParentNodeId, settings->SelectedWindowId, selected_tab_name ? selected_tab_name : settings->SelectedWindowId ? "N/A" : "");
15947 }
15948 ImGui::TreePop();
15949 }
15950 #endif // #ifdef IMGUI_HAS_DOCK
15951
15952 if (ImGui::TreeNode("SettingsIniData", "Settings unpacked data (.ini): %d bytes", g.SettingsIniData.size()))
15953 {
15954 ImGui::InputTextMultiline("##Ini", (char*)(void*)g.SettingsIniData.c_str(), g.SettingsIniData.Buf.Size, ImVec2(-FLT_MIN, 0.0f), ImGuiInputTextFlags_ReadOnly);
15955 ImGui::TreePop();
15956 }
15957 ImGui::TreePop();
15958 }
15959
15960 // Misc Details
15961 if (ImGui::TreeNode("Internal state"))
15962 {
15963 const char* input_source_names[] = { "None", "Mouse", "Nav", "NavKeyboard", "NavGamepad" }; IM_ASSERT(IM_ARRAYSIZE(input_source_names) == ImGuiInputSource_COUNT);
15964
15965 ImGui::Text("WINDOWING");
15966 ImGui::Indent();
15967 ImGui::Text("HoveredWindow: '%s'", g.HoveredWindow ? g.HoveredWindow->Name : "NULL");
15968 ImGui::Text("HoveredRootWindow: '%s'", g.HoveredRootWindow ? g.HoveredRootWindow->Name : "NULL");
15969 ImGui::Text("HoveredWindowUnderMovingWindow: '%s'", g.HoveredWindowUnderMovingWindow ? g.HoveredWindowUnderMovingWindow->Name : "NULL");
15970 ImGui::Text("HoveredDockNode: 0x%08X", g.HoveredDockNode ? g.HoveredDockNode->ID : 0);
15971 ImGui::Text("MovingWindow: '%s'", g.MovingWindow ? g.MovingWindow->Name : "NULL");
15972 ImGui::Text("MouseViewport: 0x%08X (UserHovered 0x%08X, LastHovered 0x%08X)", g.MouseViewport->ID, g.IO.MouseHoveredViewport, g.MouseLastHoveredViewport ? g.MouseLastHoveredViewport->ID : 0);
15973 ImGui::Unindent();
15974
15975 ImGui::Text("ITEMS");
15976 ImGui::Indent();
15977 ImGui::Text("ActiveId: 0x%08X/0x%08X (%.2f sec), AllowOverlap: %d, Source: %s", g.ActiveId, g.ActiveIdPreviousFrame, g.ActiveIdTimer, g.ActiveIdAllowOverlap, input_source_names[g.ActiveIdSource]);
15978 ImGui::Text("ActiveIdWindow: '%s'", g.ActiveIdWindow ? g.ActiveIdWindow->Name : "NULL");
15979 ImGui::Text("HoveredId: 0x%08X/0x%08X (%.2f sec), AllowOverlap: %d", g.HoveredId, g.HoveredIdPreviousFrame, g.HoveredIdTimer, g.HoveredIdAllowOverlap); // Data is "in-flight" so depending on when the Metrics window is called we may see current frame information or not
15980 ImGui::Text("DragDrop: %d, SourceId = 0x%08X, Payload \"%s\" (%d bytes)", g.DragDropActive, g.DragDropPayload.SourceId, g.DragDropPayload.DataType, g.DragDropPayload.DataSize);
15981 ImGui::Unindent();
15982
15983 ImGui::Text("NAV,FOCUS");
15984 ImGui::Indent();
15985 ImGui::Text("NavWindow: '%s'", g.NavWindow ? g.NavWindow->Name : "NULL");
15986 ImGui::Text("NavId: 0x%08X, NavLayer: %d", g.NavId, g.NavLayer);
15987 ImGui::Text("NavInputSource: %s", input_source_names[g.NavInputSource]);
15988 ImGui::Text("NavActive: %d, NavVisible: %d", g.IO.NavActive, g.IO.NavVisible);
15989 ImGui::Text("NavActivateId: 0x%08X, NavInputId: 0x%08X", g.NavActivateId, g.NavInputId);
15990 ImGui::Text("NavDisableHighlight: %d, NavDisableMouseHover: %d", g.NavDisableHighlight, g.NavDisableMouseHover);
15991 ImGui::Text("NavFocusScopeId = 0x%08X", g.NavFocusScopeId);
15992 ImGui::Text("NavWindowingTarget: '%s'", g.NavWindowingTarget ? g.NavWindowingTarget->Name : "NULL");
15993 ImGui::Unindent();
15994
15995 ImGui::TreePop();
15996 }
15997
15998 // Overlay: Display windows Rectangles and Begin Order
15999 if (show_windows_rects || show_windows_begin_order)
16000 {
16001 for (int n = 0; n < g.Windows.Size; n++)
16002 {
16003 ImGuiWindow* window = g.Windows[n];
16004 if (!window->WasActive)
16005 continue;
16006 ImDrawList* draw_list = GetForegroundDrawList(window);
16007 if (show_windows_rects)
16008 {
16009 ImRect r = Funcs::GetWindowRect(window, show_windows_rect_type);
16010 draw_list->AddRect(r.Min, r.Max, IM_COL32(255, 0, 128, 255));
16011 }
16012 if (show_windows_begin_order && !(window->Flags & ImGuiWindowFlags_ChildWindow))
16013 {
16014 char buf[32];
16015 ImFormatString(buf, IM_ARRAYSIZE(buf), "%d", window->BeginOrderWithinContext);
16016 float font_size = ImGui::GetFontSize();
16017 draw_list->AddRectFilled(window->Pos, window->Pos + ImVec2(font_size, font_size), IM_COL32(200, 100, 100, 255));
16018 draw_list->AddText(window->Pos, IM_COL32(255, 255, 255, 255), buf);
16019 }
16020 }
16021 }
16022
16023 #ifdef IMGUI_HAS_TABLE
16024 // Overlay: Display Tables Rectangles
16025 if (show_tables_rects)
16026 {
16027 for (int table_n = 0; table_n < g.Tables.GetSize(); table_n++)
16028 {
16029 ImGuiTable* table = g.Tables.GetByIndex(table_n);
16030 }
16031 }
16032 #endif // #ifdef IMGUI_HAS_TABLE
16033
16034 #ifdef IMGUI_HAS_DOCK
16035 // Overlay: Display Docking info
16036 if (show_docking_nodes && g.IO.KeyCtrl && g.HoveredDockNode)
16037 {
16038 char buf[64] = "";
16039 char* p = buf;
16040 ImGuiDockNode* node = g.HoveredDockNode;
16041 ImDrawList* overlay_draw_list = node->HostWindow ? GetForegroundDrawList(node->HostWindow) : GetForegroundDrawList((ImGuiViewportP*)GetMainViewport());
16042 p += ImFormatString(p, buf + IM_ARRAYSIZE(buf) - p, "DockId: %X%s\n", node->ID, node->IsCentralNode() ? " *CentralNode*" : "");
16043 p += ImFormatString(p, buf + IM_ARRAYSIZE(buf) - p, "WindowClass: %08X\n", node->WindowClass.ClassId);
16044 p += ImFormatString(p, buf + IM_ARRAYSIZE(buf) - p, "Size: (%.0f, %.0f)\n", node->Size.x, node->Size.y);
16045 p += ImFormatString(p, buf + IM_ARRAYSIZE(buf) - p, "SizeRef: (%.0f, %.0f)\n", node->SizeRef.x, node->SizeRef.y);
16046 int depth = DockNodeGetDepth(node);
16047 overlay_draw_list->AddRect(node->Pos + ImVec2(3, 3) * (float)depth, node->Pos + node->Size - ImVec2(3, 3) * (float)depth, IM_COL32(200, 100, 100, 255));
16048 ImVec2 pos = node->Pos + ImVec2(3, 3) * (float)depth;
16049 overlay_draw_list->AddRectFilled(pos - ImVec2(1, 1), pos + CalcTextSize(buf) + ImVec2(1, 1), IM_COL32(200, 100, 100, 255));
16050 overlay_draw_list->AddText(NULL, 0.0f, pos, IM_COL32(255, 255, 255, 255), buf);
16051 }
16052 #endif // #ifdef IMGUI_HAS_DOCK
16053
16054 ImGui::End();
16055 }
16056
16057 #else
16058
ShowMetricsWindow(bool *)16059 void ImGui::ShowMetricsWindow(bool*) { }
16060
16061 #endif
16062
16063 //-----------------------------------------------------------------------------
16064
16065 // Include imgui_user.inl at the end of imgui.cpp to access private data/functions that aren't exposed.
16066 // 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.
16067 #ifdef IMGUI_INCLUDE_IMGUI_USER_INL
16068 #include "imgui_user.inl"
16069 #endif
16070
16071 //-----------------------------------------------------------------------------
16072
16073 #endif // #ifndef IMGUI_DISABLE
16074