0498f1e98b81ffb90a4684854fa0877458cb6b03
[mesa.git] / src / imgui / imgui.cpp
1 // dear imgui, v1.63 WIP
2 // (main code and documentation)
3
4 // Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp for demo code.
5 // Newcomers, read 'Programmer guide' below for notes on how to setup Dear ImGui in your codebase.
6 // Get latest version at https://github.com/ocornut/imgui
7 // Releases change-log at https://github.com/ocornut/imgui/releases
8 // Gallery (please post your screenshots/video there!): https://github.com/ocornut/imgui/issues/1269
9 // Developed by Omar Cornut and every direct or indirect contributors to the GitHub.
10 // This library is free but I need your support to sustain development and maintenance.
11 // If you work for a company, please consider financial support, see README. For individuals: https://www.patreon.com/imgui
12
13 // It is recommended that you don't modify imgui.cpp! It will become difficult for you to update the library.
14 // Note that 'ImGui::' being a namespace, you can add functions into the namespace from your own source files, without
15 // modifying imgui.h or imgui.cpp. You may include imgui_internal.h to access internal data structures, but it doesn't
16 // come with any guarantee of forward compatibility. Discussing your changes on the GitHub Issue Tracker may lead you
17 // to a better solution or official support for them.
18
19 /*
20
21 Index
22 - MISSION STATEMENT
23 - END-USER GUIDE
24 - PROGRAMMER GUIDE (read me!)
25 - Read first
26 - How to update to a newer version of Dear ImGui
27 - Getting started with integrating Dear ImGui in your code/engine
28 - Using gamepad/keyboard navigation controls [BETA]
29 - API BREAKING CHANGES (read me when you update!)
30 - ISSUES & TODO LIST
31 - FREQUENTLY ASKED QUESTIONS (FAQ), TIPS
32 - How can I tell whether to dispatch mouse/keyboard to imgui or to my application?
33 - How can I display an image? What is ImTextureID, how does it works?
34 - How can I have multiple widgets with the same label or without a label? A primer on labels and the ID Stack.
35 - How can I use my own math types instead of ImVec2/ImVec4?
36 - How can I load a different font than the default?
37 - How can I easily use icons in my application?
38 - How can I load multiple fonts?
39 - How can I display and input non-latin characters such as Chinese, Japanese, Korean, Cyrillic?
40 - How can I use the drawing facilities without an ImGui window? (using ImDrawList API)
41 - I integrated Dear ImGui in my engine and the text or lines are blurry..
42 - I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around..
43 - How can I help?
44 - ISSUES & TODO-LIST
45 - CODE
46
47
48 MISSION STATEMENT
49 =================
50
51 - Easy to use to create code-driven and data-driven tools
52 - Easy to use to create ad hoc short-lived tools and long-lived, more elaborate tools
53 - Easy to hack and improve
54 - Minimize screen real-estate usage
55 - Minimize setup and maintenance
56 - Minimize state storage on user side
57 - Portable, minimize dependencies, run on target (consoles, phones, etc.)
58 - Efficient runtime and memory consumption (NB- we do allocate when "growing" content e.g. creating a window,
59 opening a tree node for the first time, etc. but a typical frame should not allocate anything)
60
61 Designed for developers and content-creators, not the typical end-user! Some of the weaknesses includes:
62 - Doesn't look fancy, doesn't animate
63 - Limited layout features, intricate layouts are typically crafted in code
64
65
66 END-USER GUIDE
67 ==============
68
69 - Double-click on title bar to collapse window.
70 - Click upper right corner to close a window, available when 'bool* p_open' is passed to ImGui::Begin().
71 - Click and drag on lower right corner to resize window (double-click to auto fit window to its contents).
72 - Click and drag on any empty space to move window.
73 - TAB/SHIFT+TAB to cycle through keyboard editable fields.
74 - CTRL+Click on a slider or drag box to input value as text.
75 - Use mouse wheel to scroll.
76 - Text editor:
77 - Hold SHIFT or use mouse to select text.
78 - CTRL+Left/Right to word jump.
79 - CTRL+Shift+Left/Right to select words.
80 - CTRL+A our Double-Click to select all.
81 - CTRL+X,CTRL+C,CTRL+V to use OS clipboard/
82 - CTRL+Z,CTRL+Y to undo/redo.
83 - ESCAPE to revert text to its original value.
84 - You can apply arithmetic operators +,*,/ on numerical values. Use +- to subtract (because - would set a negative value!)
85 - Controls are automatically adjusted for OSX to match standard OSX text editing operations.
86 - General Keyboard controls: enable with ImGuiConfigFlags_NavEnableKeyboard.
87 - General Gamepad controls: enable with ImGuiConfigFlags_NavEnableGamepad. See suggested mappings in imgui.h ImGuiNavInput_ + download PNG/PSD at http://goo.gl/9LgVZW
88
89
90 PROGRAMMER GUIDE
91 ================
92
93 READ FIRST
94
95 - Read the FAQ below this section!
96 - 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
97 or destruction steps, less data retention on your side, less state duplication, less state synchronization, less bugs.
98 - Call and read ImGui::ShowDemoWindow() for demo code demonstrating most features.
99 - You can learn about immediate-mode gui principles at http://www.johno.se/book/imgui.html or watch http://mollyrocket.com/861
100
101 HOW TO UPDATE TO A NEWER VERSION OF DEAR IMGUI
102
103 - Overwrite all the sources files except for imconfig.h (if you have made modification to your copy of imconfig.h)
104 - Read the "API BREAKING CHANGES" section (below). This is where we list occasional API breaking changes.
105 If a function/type has been renamed / or marked obsolete, try to fix the name in your code before it is permanently removed
106 from the public API. If you have a problem with a missing function/symbols, search for its name in the code, there will
107 likely be a comment about it. Please report any issue to the GitHub page!
108 - Try to keep your copy of dear imgui reasonably up to date.
109
110 GETTING STARTED WITH INTEGRATING DEAR IMGUI IN YOUR CODE/ENGINE
111
112 - Run and study the examples and demo to get acquainted with the library.
113 - Add the Dear ImGui source files to your projects or using your preferred build system.
114 It is recommended you build the .cpp files as part of your project and not as a library.
115 - You can later customize the imconfig.h file to tweak some compilation time behavior, such as integrating imgui types with your own maths types.
116 - You may be able to grab and copy a ready made imgui_impl_*** file from the examples/ folder.
117 - When using Dear ImGui, your programming IDE is your friend: follow the declaration of variables, functions and types to find comments about them.
118 - Dear ImGui never touches or knows about your GPU state. The only function that knows about GPU is the draw function that you provide.
119 Effectively it means you can create widgets at any time in your code, regardless of considerations of being in "update" vs "render"
120 phases of your own application. All rendering informatioe are stored into command-lists that you will retrieve after calling ImGui::Render().
121 - Refer to the bindings and demo applications in the examples/ folder for instruction on how to setup your code.
122
123 THIS IS HOW A SIMPLE APPLICATION MAY LOOK LIKE
124 EXHIBIT 1: USING THE EXAMPLE BINDINGS (imgui_impl_XXX.cpp files from the examples/ folder)
125
126 // Application init: create a dear imgui context, setup some options, load fonts
127 ImGui::CreateContext();
128 ImGuiIO& io = ImGui::GetIO();
129 // TODO: Set optional io.ConfigFlags values, e.g. 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard' to enable keyboard controls
130 // TODO: Fill optional settings of the io structure later.
131 // TODO: Load TTF/OTF fonts if you don't want to use the default font.
132
133 // Initialize helper Platform and Renderer bindings (here we are using imgui_impl_win32 and imgui_impl_dx11)
134 ImGui_ImplWin32_Init(hwnd);
135 ImGui_ImplDX11_Init(g_pd3dDevice, g_pd3dDeviceContext);
136
137 // Application main loop
138 while (true)
139 {
140 // Feed inputs to dear imgui, start new frame
141 ImGui_ImplDX11_NewFrame();
142 ImGui_ImplWin32_NewFrame();
143 ImGui::NewFrame();
144
145 // Any application code here
146 ImGui::Text("Hello, world!");
147
148 // Render dear imgui into screen
149 ImGui::Render();
150 ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
151 g_pSwapChain->Present(1, 0);
152 }
153
154 // Shutdown
155 ImGui_ImplDX11_Shutdown();
156 ImGui_ImplWin32_Shutdown();
157 ImGui::DestroyContext();
158
159 THIS IS HOW A SIMPLE APPLICATION MAY LOOK LIKE
160 EXHIBIT 2: IMPLEMENTING CUSTOM BINDING / CUSTOM ENGINE
161
162 // Application init: create a dear imgui context, setup some options, load fonts
163 ImGui::CreateContext();
164 ImGuiIO& io = ImGui::GetIO();
165 // TODO: Set optional io.ConfigFlags values, e.g. 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard' to enable keyboard controls
166 // TODO: Fill optional settings of the io structure later.
167 // TODO: Load TTF/OTF fonts if you don't want to use the default font.
168
169 // Build and load the texture atlas into a texture
170 // (In the examples/ app this is usually done within the ImGui_ImplXXX_Init() function from one of the demo Renderer)
171 int width, height;
172 unsigned char* pixels = NULL;
173 io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
174
175 // At this point you've got the texture data and you need to upload that your your graphic system:
176 // After we have created the texture, store its pointer/identifier (_in whichever format your engine uses_) in 'io.Fonts->TexID'.
177 // This will be passed back to your via the renderer. Basically ImTextureID == void*. Read FAQ below for details about ImTextureID.
178 MyTexture* texture = MyEngine::CreateTextureFromMemoryPixels(pixels, width, height, TEXTURE_TYPE_RGBA32)
179 io.Fonts->TexID = (void*)texture;
180
181 // Application main loop
182 while (true)
183 {
184 // Setup low-level inputs, e.g. on Win32: calling GetKeyboardState(), or write to those fields from your Windows message handlers, etc.
185 // (In the examples/ app this is usually done within the ImGui_ImplXXX_NewFrame() function from one of the demo Platform bindings)
186 io.DeltaTime = 1.0f/60.0f; // set the time elapsed since the previous frame (in seconds)
187 io.DisplaySize.x = 1920.0f; // set the current display width
188 io.DisplaySize.y = 1280.0f; // set the current display height here
189 io.MousePos = my_mouse_pos; // set the mouse position
190 io.MouseDown[0] = my_mouse_buttons[0]; // set the mouse button states
191 io.MouseDown[1] = my_mouse_buttons[1];
192
193 // Call NewFrame(), after this point you can use ImGui::* functions anytime
194 // (So you want to try calling NewFrame() as early as you can in your mainloop to be able to use imgui everywhere)
195 ImGui::NewFrame();
196
197 // Most of your application code here
198 ImGui::Text("Hello, world!");
199 MyGameUpdate(); // may use any ImGui functions, e.g. ImGui::Begin("My window"); ImGui::Text("Hello, world!"); ImGui::End();
200 MyGameRender(); // may use any ImGui functions as well!
201
202 // Render imgui, swap buffers
203 // (You want to try calling EndFrame/Render as late as you can, to be able to use imgui in your own game rendering code)
204 ImGui::EndFrame();
205 ImGui::Render();
206 ImDrawData* draw_data = ImGui::GetDrawData();
207 MyImGuiRenderFunction(draw_data);
208 SwapBuffers();
209 }
210
211 // Shutdown
212 ImGui::DestroyContext();
213
214 THIS HOW A SIMPLE RENDERING FUNCTION MAY LOOK LIKE
215
216 void void MyImGuiRenderFunction(ImDrawData* draw_data)
217 {
218 // TODO: Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled
219 // TODO: Setup viewport using draw_data->DisplaySize
220 // TODO: Setup orthographic projection matrix cover draw_data->DisplayPos to draw_data->DisplayPos + draw_data->DisplaySize
221 // TODO: Setup shader: vertex { float2 pos, float2 uv, u32 color }, fragment shader sample color from 1 texture, multiply by vertex color.
222 for (int n = 0; n < draw_data->CmdListsCount; n++)
223 {
224 const ImDrawVert* vtx_buffer = cmd_list->VtxBuffer.Data; // vertex buffer generated by ImGui
225 const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data; // index buffer generated by ImGui
226 for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
227 {
228 const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
229 if (pcmd->UserCallback)
230 {
231 pcmd->UserCallback(cmd_list, pcmd);
232 }
233 else
234 {
235 // The texture for the draw call is specified by pcmd->TextureId.
236 // The vast majority of draw calls with use the imgui texture atlas, which value you have set yourself during initialization.
237 MyEngineBindTexture(pcmd->TextureId);
238
239 // We are using scissoring to clip some objects. All low-level graphics API should supports it.
240 // - If your engine doesn't support scissoring yet, you may ignore this at first. You will get some small glitches
241 // (some elements visible outside their bounds) but you can fix that once everywhere else works!
242 // - Clipping coordinates are provided in imgui coordinates space (from draw_data->DisplayPos to draw_data->DisplayPos + draw_data->DisplaySize)
243 // In a single viewport application, draw_data->DisplayPos will always be (0,0) and draw_data->DisplaySize will always be == io.DisplaySize.
244 // However, in the interest of supporting multi-viewport applications in the future, always subtract draw_data->DisplayPos from
245 // clipping bounds to convert them to your viewport space.
246 // - Note that pcmd->ClipRect contains Min+Max bounds. Some graphics API may use Min+Max, other may use Min+Size (size being Max-Min)
247 ImVec2 pos = draw_data->DisplayPos;
248 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));
249
250 // Render 'pcmd->ElemCount/3' indexed triangles.
251 // By default the indices ImDrawIdx are 16-bits, you can change them to 32-bits if your engine doesn't support 16-bits indices.
252 MyEngineDrawIndexedTriangles(pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer, vtx_buffer);
253 }
254 idx_buffer += pcmd->ElemCount;
255 }
256 }
257 }
258
259 - The examples/ folders contains many functional implementation of the pseudo-code above.
260 - When calling NewFrame(), the 'io.WantCaptureMouse', 'io.WantCaptureKeyboard' and 'io.WantTextInput' flags are updated.
261 They tell you if ImGui intends to use your inputs. When a flag is set you want to hide the corresponding inputs from the rest of your application.
262 In both cases you need to pass on the inputs to imgui. Read the FAQ below for more information about those flags.
263 - Please read the FAQ above. Amusingly, it is called a FAQ because people frequently have the same issues!
264
265 USING GAMEPAD/KEYBOARD NAVIGATION CONTROLS [BETA]
266
267 - The gamepad/keyboard navigation is in Beta. Ask questions and report issues at https://github.com/ocornut/imgui/issues/787
268 - The initial focus was to support game controllers, but keyboard is becoming increasingly and decently usable.
269 - Gamepad:
270 - Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad to enable.
271 - Backend: Set io.BackendFlags |= ImGuiBackendFlags_HasGamepad + fill the io.NavInputs[] fields before calling NewFrame().
272 Note that io.NavInputs[] is cleared by EndFrame().
273 - See 'enum ImGuiNavInput_' in imgui.h for a description of inputs. For each entry of io.NavInputs[], set the following values:
274 0.0f= not held. 1.0f= fully held. Pass intermediate 0.0f..1.0f values for analog triggers/sticks.
275 - We uses a simple >0.0f test for activation testing, and won't attempt to test for a dead-zone.
276 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.).
277 - You can download PNG/PSD files depicting the gamepad controls for common controllers at: goo.gl/9LgVZW.
278 - 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
279 to toggle the target. Please reach out if you think the game vs navigation input sharing could be improved.
280 - Keyboard:
281 - Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard to enable.
282 NewFrame() will automatically fill io.NavInputs[] based on your io.KeysDown[] + io.KeyMap[] arrays.
283 - When keyboard navigation is active (io.NavActive + ImGuiConfigFlags_NavEnableKeyboard), the io.WantCaptureKeyboard flag
284 will be set. For more advanced uses, you may want to read from:
285 - io.NavActive: true when a window is focused and it doesn't have the ImGuiWindowFlags_NoNavInputs flag set.
286 - io.NavVisible: true when the navigation cursor is visible (and usually goes false when mouse is used).
287 - or query focus information with e.g. IsWindowFocused(ImGuiFocusedFlags_AnyWindow), IsItemFocused() etc. functions.
288 Please reach out if you think the game vs navigation input sharing could be improved.
289 - Mouse:
290 - PS4 users: Consider emulating a mouse cursor with DualShock4 touch pad or a spare analog stick as a mouse-emulation fallback.
291 - 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.
292 - 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.
293 Enabling ImGuiConfigFlags_NavEnableSetMousePos + ImGuiBackendFlags_HasSetMousePos instructs dear imgui to move your mouse cursor along with navigation movements.
294 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.
295 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.
296 (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!)
297 (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
298 to set a boolean to ignore your other external mouse positions until the external source is moved again.)
299
300
301 API BREAKING CHANGES
302 ====================
303
304 Occasionally introducing changes that are breaking the API. We try to make the breakage minor and easy to fix.
305 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.
306 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.
307 You can read releases logs https://github.com/ocornut/imgui/releases for more details.
308
309 - 2018/07/22 (1.63) - changed ImGui::GetTime() return value from float to double to avoid accumulating floating point imprecisions over time.
310 - 2018/07/08 (1.63) - style: renamed ImGuiCol_ModalWindowDarkening to ImGuiCol_ModalWindowDimBg for consistency with other features. Kept redirection enum (will obsolete).
311 - 2018/07/06 (1.63) - removed per-window ImGuiWindowFlags_ResizeFromAnySide beta flag in favor of a global io.OptResizeWindowsFromEdges to enable the feature.
312 - 2018/06/06 (1.62) - renamed GetGlyphRangesChinese() to GetGlyphRangesChineseFull() to distinguish other variants and discourage using the full set.
313 - 2018/06/06 (1.62) - TreeNodeEx()/TreeNodeBehavior(): the ImGuiTreeNodeFlags_CollapsingHeader helper now include the ImGuiTreeNodeFlags_NoTreePushOnOpen flag. See Changelog for details.
314 - 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.
315 If you used DragInt() with custom format strings, make sure you change them to use %d or an integer-compatible format.
316 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.
317 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.
318 - 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",
319 consistent with other functions. Kept redirection functions (will obsolete).
320 - 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.
321 - 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).
322 - 2018/03/12 (1.60) - removed ImGuiCol_CloseButton, ImGuiCol_CloseButtonActive, ImGuiCol_CloseButtonHovered as the closing cross uses regular button colors now.
323 - 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.
324 - 2018/03/03 (1.60) - renamed ImGuiStyleVar_Count_ to ImGuiStyleVar_COUNT and ImGuiMouseCursor_Count_ to ImGuiMouseCursor_COUNT for consistency with other public enums.
325 - 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.
326 - 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.
327 - 2018/02/07 (1.60) - reorganized context handling to be more explicit,
328 - YOU NOW NEED TO CALL ImGui::CreateContext() AT THE BEGINNING OF YOUR APP, AND CALL ImGui::DestroyContext() AT THE END.
329 - removed Shutdown() function, as DestroyContext() serve this purpose.
330 - you may pass a ImFontAtlas* pointer to CreateContext() to share a font atlas between contexts. Otherwise CreateContext() will create its own font atlas instance.
331 - removed allocator parameters from CreateContext(), they are now setup with SetAllocatorFunctions(), and shared by all contexts.
332 - removed the default global context and font atlas instance, which were confusing for users of DLL reloading and users of multiple contexts.
333 - 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.
334 - 2018/01/11 (1.60) - obsoleted IsAnyWindowHovered() in favor of IsWindowHovered(ImGuiHoveredFlags_AnyWindow). Kept redirection function (will obsolete).
335 - 2018/01/11 (1.60) - obsoleted IsAnyWindowFocused() in favor of IsWindowFocused(ImGuiFocusedFlags_AnyWindow). Kept redirection function (will obsolete).
336 - 2018/01/03 (1.60) - renamed ImGuiSizeConstraintCallback to ImGuiSizeCallback, ImGuiSizeConstraintCallbackData to ImGuiSizeCallbackData.
337 - 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.
338 - 2017/12/24 (1.53) - renamed the emblematic ShowTestWindow() function to ShowDemoWindow(). Kept redirection function (will obsolete).
339 - 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
340 - 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.
341 - 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.
342 - 2017/12/13 (1.53) - renamed GetItemsLineHeightWithSpacing() to GetFrameHeightWithSpacing(). Kept redirection function (will obsolete).
343 - 2017/12/13 (1.53) - obsoleted IsRootWindowFocused() in favor of using IsWindowFocused(ImGuiFocusedFlags_RootWindow). Kept redirection function (will obsolete).
344 - obsoleted IsRootWindowOrAnyChildFocused() in favor of using IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows). Kept redirection function (will obsolete).
345 - 2017/12/12 (1.53) - renamed ImGuiTreeNodeFlags_AllowOverlapMode to ImGuiTreeNodeFlags_AllowItemOverlap. Kept redirection enum (will obsolete).
346 - 2017/12/10 (1.53) - removed SetNextWindowContentWidth(), prefer using SetNextWindowContentSize(). Kept redirection function (will obsolete).
347 - 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.
348 - 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.
349 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.
350 - 2017/11/18 (1.53) - Style: removed ImGuiCol_ComboBg in favor of combo boxes using ImGuiCol_PopupBg for consistency.
351 - 2017/11/18 (1.53) - Style: renamed ImGuiCol_ChildWindowBg to ImGuiCol_ChildBg.
352 - 2017/11/18 (1.53) - Style: renamed style.ChildWindowRounding to style.ChildRounding, ImGuiStyleVar_ChildWindowRounding to ImGuiStyleVar_ChildRounding.
353 - 2017/11/02 (1.53) - obsoleted IsRootWindowOrAnyChildHovered() in favor of using IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows);
354 - 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.
355 - 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.
356 - 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.
357 removed the IsItemRectHovered()/IsWindowRectHovered() names introduced in 1.51 since they were merely more consistent names for the two functions we are now obsoleting.
358 - 2017/10/17 (1.52) - marked the old 5-parameters version of Begin() as obsolete (still available). Use SetNextWindowSize()+Begin() instead!
359 - 2017/10/11 (1.52) - renamed AlignFirstTextHeightToWidgets() to AlignTextToFramePadding(). Kept inline redirection function (will obsolete).
360 - 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).
361 - 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)".
362 - 2017/08/22 (1.51) - renamed IsItemHoveredRect() to IsItemRectHovered(). Kept inline redirection function (will obsolete). -> (1.52) use IsItemHovered(ImGuiHoveredFlags_RectOnly)!
363 - renamed IsMouseHoveringAnyWindow() to IsAnyWindowHovered() for consistency. Kept inline redirection function (will obsolete).
364 - renamed IsMouseHoveringWindow() to IsWindowRectHovered() for consistency. Kept inline redirection function (will obsolete).
365 - 2017/08/20 (1.51) - renamed GetStyleColName() to GetStyleColorName() for consistency.
366 - 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.
367 - 2017/08/15 (1.51) - marked the weird IMGUI_ONCE_UPON_A_FRAME helper macro as obsolete. prefer using the more explicit ImGuiOnceUponAFrame.
368 - 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.
369 - 2017/08/13 (1.51) - renamed ImGuiCol_Columns*** to ImGuiCol_Separator***. Kept redirection enums (will obsolete).
370 - 2017/08/11 (1.51) - renamed ImGuiSetCond_*** types and flags to ImGuiCond_***. Kept redirection enums (will obsolete).
371 - 2017/08/09 (1.51) - removed ValueColor() helpers, they are equivalent to calling Text(label) + SameLine() + ColorButton().
372 - 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.
373 - 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.
374 - 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))'
375 - 2017/07/20 (1.51) - removed IsPosHoveringAnyWindow(ImVec2), which was partly broken and misleading. ASSERT + redirect user to io.WantCaptureMouse
376 - 2017/05/26 (1.50) - removed ImFontConfig::MergeGlyphCenterV in favor of a more multipurpose ImFontConfig::GlyphOffset.
377 - 2017/05/01 (1.50) - renamed ImDrawList::PathFill() (rarely used directly) to ImDrawList::PathFillConvex() for clarity.
378 - 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().
379 - 2016/10/15 (1.50) - avoid 'void* user_data' parameter to io.SetClipboardTextFn/io.GetClipboardTextFn pointers. We pass io.ClipboardUserData to it.
380 - 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.
381 - 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.
382 - 2016/05/12 (1.49) - title bar (using ImGuiCol_TitleBg/ImGuiCol_TitleBgActive colors) isn't rendered over a window background (ImGuiCol_WindowBg color) anymore.
383 If your TitleBg/TitleBgActive alpha was 1.0f or you are using the default theme it will not affect you.
384 If your TitleBg/TitleBgActive alpha was <1.0f you need to tweak your custom theme to readjust for the fact that we don't draw a WindowBg background behind the title bar.
385 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.
386 ImVec4 ConvertTitleBgCol(const ImVec4& win_bg_col, const ImVec4& title_bg_col)
387 {
388 float new_a = 1.0f - ((1.0f - win_bg_col.w) * (1.0f - title_bg_col.w)), k = title_bg_col.w / new_a;
389 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);
390 }
391 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.
392 - 2016/05/07 (1.49) - removed confusing set of GetInternalState(), GetInternalStateSize(), SetInternalState() functions. Now using CreateContext(), DestroyContext(), GetCurrentContext(), SetCurrentContext().
393 - 2016/05/02 (1.49) - renamed SetNextTreeNodeOpened() to SetNextTreeNodeOpen(), no redirection.
394 - 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).
395 - 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.
396 - 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).
397 - 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)
398 - 2016/03/21 (1.48) - renamed GetWindowFont() to GetFont(), GetWindowFontSize() to GetFontSize(). Kept inline redirection function (will obsolete).
399 - 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.
400 - 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.
401 - 2015/12/27 (1.48) - fixed ImDrawList::AddRect() which used to render a rectangle 1 px too large on each axis.
402 - 2015/12/04 (1.47) - renamed Color() helpers to ValueColor() - dangerously named, rarely used and probably to be made obsolete.
403 - 2015/08/29 (1.45) - with the addition of horizontal scrollbar we made various fixes to inconsistencies with dealing with cursor position.
404 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.
405 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!
406 - 2015/08/29 (1.45) - renamed style.ScrollbarWidth to style.ScrollbarSize
407 - 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.
408 - 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
409 - 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.
410 you need to render your textured triangles with bilinear filtering to benefit from sub-pixel positioning of text.
411 - 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.
412 this necessary change will break your rendering function! the fix should be very easy. sorry for that :(
413 - 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.
414 - the signature of the io.RenderDrawListsFn handler has changed!
415 old: ImGui_XXXX_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count)
416 new: ImGui_XXXX_RenderDrawLists(ImDrawData* draw_data).
417 argument: 'cmd_lists' becomes 'draw_data->CmdLists', 'cmd_lists_count' becomes 'draw_data->CmdListsCount'
418 ImDrawList: 'commands' becomes 'CmdBuffer', 'vtx_buffer' becomes 'VtxBuffer', 'IdxBuffer' is new.
419 ImDrawCmd: 'vtx_count' becomes 'ElemCount', 'clip_rect' becomes 'ClipRect', 'user_callback' becomes 'UserCallback', 'texture_id' becomes 'TextureId'.
420 - 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.
421 - 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!
422 - refer to code in the examples/ folder or ask on the GitHub if you are unsure of how to upgrade. please upgrade!
423 - 2015/07/10 (1.43) - changed SameLine() parameters from int to float.
424 - 2015/07/02 (1.42) - renamed SetScrollPosHere() to SetScrollFromCursorPos(). Kept inline redirection function (will obsolete).
425 - 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.
426 - 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
427 - 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!
428 - 2015/05/31 (1.40) - renamed GetWindowCollapsed() to IsWindowCollapsed() for consistency. Kept inline redirection function (will obsolete).
429 - 2015/05/31 (1.40) - renamed IsRectClipped() to IsRectVisible() for consistency. Note that return value is opposite! Kept inline redirection function (will obsolete).
430 - 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.
431 - 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.
432 - 2015/05/03 (1.40) - removed style.AutoFitPadding, using style.WindowPadding makes more sense (the default values were already the same).
433 - 2015/04/13 (1.38) - renamed IsClipped() to IsRectClipped(). Kept inline redirection function until 1.50.
434 - 2015/04/09 (1.38) - renamed ImDrawList::AddArc() to ImDrawList::AddArcFast() for compatibility with future API
435 - 2015/04/03 (1.38) - removed ImGuiCol_CheckHovered, ImGuiCol_CheckActive, replaced with the more general ImGuiCol_FrameBgHovered, ImGuiCol_FrameBgActive.
436 - 2014/04/03 (1.38) - removed support for passing -FLT_MAX..+FLT_MAX as the range for a SliderFloat(). Use DragFloat() or Inputfloat() instead.
437 - 2015/03/17 (1.36) - renamed GetItemBoxMin()/GetItemBoxMax()/IsMouseHoveringBox() to GetItemRectMin()/GetItemRectMax()/IsMouseHoveringRect(). Kept inline redirection function until 1.50.
438 - 2015/03/15 (1.36) - renamed style.TreeNodeSpacing to style.IndentSpacing, ImGuiStyleVar_TreeNodeSpacing to ImGuiStyleVar_IndentSpacing
439 - 2015/03/13 (1.36) - renamed GetWindowIsFocused() to IsWindowFocused(). Kept inline redirection function until 1.50.
440 - 2015/03/08 (1.35) - renamed style.ScrollBarWidth to style.ScrollbarWidth (casing)
441 - 2015/02/27 (1.34) - renamed OpenNextNode(bool) to SetNextTreeNodeOpened(bool, ImGuiSetCond). Kept inline redirection function until 1.50.
442 - 2015/02/27 (1.34) - renamed ImGuiSetCondition_*** to ImGuiSetCond_***, and _FirstUseThisSession becomes _Once.
443 - 2015/02/11 (1.32) - changed text input callback ImGuiTextEditCallback return type from void-->int. reserved for future use, return 0 for now.
444 - 2015/02/10 (1.32) - renamed GetItemWidth() to CalcItemWidth() to clarify its evolving behavior
445 - 2015/02/08 (1.31) - renamed GetTextLineSpacing() to GetTextLineHeightWithSpacing()
446 - 2015/02/01 (1.31) - removed IO.MemReallocFn (unused)
447 - 2015/01/19 (1.30) - renamed ImGuiStorage::GetIntPtr()/GetFloatPtr() to GetIntRef()/GetIntRef() because Ptr was conflicting with actual pointer storage functions.
448 - 2015/01/11 (1.30) - big font/image API change! now loads TTF file. allow for multiple fonts. no need for a PNG loader.
449 (1.30) - removed GetDefaultFontData(). uses io.Fonts->GetTextureData*() API to retrieve uncompressed pixels.
450 font init: const void* png_data; unsigned int png_size; ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size); <..Upload texture to GPU..>
451 became: unsigned char* pixels; int width, height; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); <..Upload texture to GPU>; io.Fonts->TexId = YourTextureIdentifier;
452 you now more flexibility to load multiple TTF fonts and manage the texture buffer for internal needs.
453 it is now recommended that you sample the font texture with bilinear interpolation.
454 (1.30) - added texture identifier in ImDrawCmd passed to your render function (we can now render images). make sure to set io.Fonts->TexID.
455 (1.30) - removed IO.PixelCenterOffset (unnecessary, can be handled in user projection matrix)
456 (1.30) - removed ImGui::IsItemFocused() in favor of ImGui::IsItemActive() which handles all widgets
457 - 2014/12/10 (1.18) - removed SetNewWindowDefaultPos() in favor of new generic API SetNextWindowPos(pos, ImGuiSetCondition_FirstUseEver)
458 - 2014/11/28 (1.17) - moved IO.Font*** options to inside the IO.Font-> structure (FontYOffset, FontTexUvForWhite, FontBaseScale, FontFallbackGlyph)
459 - 2014/11/26 (1.17) - reworked syntax of IMGUI_ONCE_UPON_A_FRAME helper macro to increase compiler compatibility
460 - 2014/11/07 (1.15) - renamed IsHovered() to IsItemHovered()
461 - 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)
462 - 2014/09/25 (1.13) - removed 'text_end' parameter from IO.SetClipboardTextFn (the string is now always zero-terminated for simplicity)
463 - 2014/09/24 (1.12) - renamed SetFontScale() to SetWindowFontScale()
464 - 2014/09/24 (1.12) - moved IM_MALLOC/IM_REALLOC/IM_FREE preprocessor defines to IO.MemAllocFn/IO.MemReallocFn/IO.MemFreeFn
465 - 2014/08/30 (1.09) - removed IO.FontHeight (now computed automatically)
466 - 2014/08/30 (1.09) - moved IMGUI_FONT_TEX_UV_FOR_WHITE preprocessor define to IO.FontTexUvForWhite
467 - 2014/08/28 (1.09) - changed the behavior of IO.PixelCenterOffset following various rendering fixes
468
469
470 ISSUES & TODO-LIST
471 ==================
472 See TODO.txt
473
474
475 FREQUENTLY ASKED QUESTIONS (FAQ), TIPS
476 ======================================
477
478 Q: How can I tell whether to dispatch mouse/keyboard to imgui or to my application?
479 A: You can read the 'io.WantCaptureMouse', 'io.WantCaptureKeyboard' and 'io.WantTextInput' flags from the ImGuiIO structure (e.g. if (ImGui::GetIO().WantCaptureMouse) { ... } )
480 - When 'io.WantCaptureMouse' is set, imgui wants to use your mouse state, and you may want to discard/hide the inputs from the rest of your application.
481 - When 'io.WantCaptureKeyboard' is set, imgui wants to use your keyboard state, and you may want to discard/hide the inputs from the rest of your application.
482 - When 'io.WantTextInput' is set to may want to notify your OS to popup an on-screen keyboard, if available (e.g. on a mobile phone, or console OS).
483 Note: you should always pass your mouse/keyboard inputs to imgui, even when the io.WantCaptureXXX flag are set false.
484 This is because imgui needs to detect that you clicked in the void to unfocus its windows.
485 Note: The 'io.WantCaptureMouse' is more accurate that any attempt to "check if the mouse is hovering a window" (don't do that!).
486 It handle mouse dragging correctly (both dragging that started over your application or over an imgui window) and handle e.g. modal windows blocking inputs.
487 Those flags are updated by ImGui::NewFrame(). Preferably read the flags after calling NewFrame() if you can afford it, but reading them before is also
488 perfectly fine, as the bool toggle fairly rarely. If you have on a touch device, you might find use for an early call to UpdateHoveredWindowAndCaptureFlags().
489 Note: Text input widget releases focus on "Return KeyDown", so the subsequent "Return KeyUp" event that your application receive will typically
490 have 'io.WantCaptureKeyboard=false'. Depending on your application logic it may or not be inconvenient. You might want to track which key-downs
491 were targeted for Dear ImGui, e.g. with an array of bool, and filter out the corresponding key-ups.)
492
493 Q: How can I display an image? What is ImTextureID, how does it works?
494 A: Short explanation:
495 - You may use functions such as ImGui::Image(), ImGui::ImageButton() or lower-level ImDrawList::AddImage() to emit draw calls that will use your own textures.
496 - Actual textures are identified in a way that is up to the user/engine.
497 - Loading image files from the disk and turning them into a texture is not within the scope of Dear ImGui (for a good reason).
498 Please read documentations or tutorials on your graphics API to understand how to display textures on the screen before moving onward.
499
500 Long explanation:
501 - Dear ImGui's job is to create "meshes", defined in a renderer-agnostic format made of draw commands and vertices.
502 At the end of the frame those meshes (ImDrawList) will be displayed by your rendering function. They are made up of textured polygons and the code
503 to render them is generally fairly short (a few dozen lines). In the examples/ folder we provide functions for popular graphics API (OpenGL, DirectX, etc.).
504 - Each rendering function decides on a data type to represent "textures". The concept of what is a "texture" is entirely tied to your underlying engine/graphics API.
505 We carry the information to identify a "texture" in the ImTextureID type.
506 ImTextureID is nothing more that a void*, aka 4/8 bytes worth of data: just enough to store 1 pointer or 1 integer of your choice.
507 Dear ImGui doesn't know or understand what you are storing in ImTextureID, it merely pass ImTextureID values until they reach your rendering function.
508 - In the examples/ bindings, for each graphics API binding we decided on a type that is likely to be a good representation for specifying
509 an image from the end-user perspective. This is what the _examples_ rendering functions are using:
510
511 OpenGL: ImTextureID = GLuint (see ImGui_ImplGlfwGL3_RenderDrawData() function in imgui_impl_glfw_gl3.cpp)
512 DirectX9: ImTextureID = LPDIRECT3DTEXTURE9 (see ImGui_ImplDX9_RenderDrawData() function in imgui_impl_dx9.cpp)
513 DirectX11: ImTextureID = ID3D11ShaderResourceView* (see ImGui_ImplDX11_RenderDrawData() function in imgui_impl_dx11.cpp)
514 DirectX12: ImTextureID = D3D12_GPU_DESCRIPTOR_HANDLE (see ImGui_ImplDX12_RenderDrawData() function in imgui_impl_dx12.cpp)
515
516 For example, in the OpenGL example binding we store raw OpenGL texture identifier (GLuint) inside ImTextureID.
517 Whereas in the DirectX11 example binding we store a pointer to ID3D11ShaderResourceView inside ImTextureID, which is a higher-level structure
518 tying together both the texture and information about its format and how to read it.
519 - If you have a custom engine built over e.g. OpenGL, instead of passing GLuint around you may decide to use a high-level data type to carry information about
520 the texture as well as how to display it (shaders, etc.). The decision of what to use as ImTextureID can always be made better knowing how your codebase
521 is designed. If your engine has high-level data types for "textures" and "material" then you may want to use them.
522 If you are starting with OpenGL or DirectX or Vulkan and haven't built much of a rendering engine over them, keeping the default ImTextureID
523 representation suggested by the example bindings is probably the best choice.
524 (Advanced users may also decide to keep a low-level type in ImTextureID, and use ImDrawList callback and pass information to their renderer)
525
526 User code may do:
527
528 // Cast our texture type to ImTextureID / void*
529 MyTexture* texture = g_CoffeeTableTexture;
530 ImGui::Image((void*)texture, ImVec2(texture->Width, texture->Height));
531
532 The renderer function called after ImGui::Render() will receive that same value that the user code passed:
533
534 // Cast ImTextureID / void* stored in the draw command as our texture type
535 MyTexture* texture = (MyTexture*)pcmd->TextureId;
536 MyEngineBindTexture2D(texture);
537
538 Once you understand this design you will understand that loading image files and turning them into displayable textures is not within the scope of Dear ImGui.
539 This is by design and is actually a good thing, because it means your code has full control over your data types and how you display them.
540 If you want to display an image file (e.g. PNG file) into the screen, please refer to documentation and tutorials for the graphics API you are using.
541
542 Here's a simplified OpenGL example using stb_image.h:
543
544 // Use stb_image.h to load a PNG from disk and turn it into raw RGBA pixel data:
545 #define STB_IMAGE_IMPLEMENTATION
546 #include <stb_image.h>
547 [...]
548 int my_image_width, my_image_height;
549 unsigned char* my_image_data = stbi_load("my_image.png", &my_image_width, &my_image_height, NULL, 4);
550
551 // Turn the RGBA pixel data into an OpenGL texture:
552 GLuint my_opengl_texture;
553 glGenTextures(1, &my_opengl_texture);
554 glBindTexture(GL_TEXTURE_2D, my_opengl_texture);
555 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
556 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image_width, image_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image_data);
557
558 // Now that we have an OpenGL texture, assuming our imgui rendering function (imgui_impl_xxx.cpp file) takes GLuint as ImTextureID, we can display it:
559 ImGui::Image((void*)(intptr_t)my_opengl_texture, ImVec2(my_image_width, my_image_height));
560
561 C/C++ tip: a void* is pointer-sized storage. You may safely store any pointer or integer into it by casting your value to ImTexture / void*, and vice-versa.
562 Because both end-points (user code and rendering function) are under your control, you know exactly what is stored inside the ImTexture / void*.
563 Examples:
564
565 GLuint my_tex = XXX;
566 void* my_void_ptr;
567 my_void_ptr = (void*)(intptr_t)my_tex; // cast a GLuint into a void* (we don't take its address! we literally store the value inside the pointer)
568 my_tex = (GLuint)(intptr_t)my_void_ptr; // cast a void* into a GLuint
569
570 ID3D11ShaderResourceView* my_dx11_srv = XXX;
571 void* my_void_ptr;
572 my_void_ptr = (void*)my_dx11_srv; // cast a ID3D11ShaderResourceView* into an opaque void*
573 my_dx11_srv = (ID3D11ShaderResourceView*)my_void_ptr; // cast a void* into a ID3D11ShaderResourceView*
574
575 Finally, you may call ImGui::ShowMetricsWindow() to explore/visualize/understand how the ImDrawList are generated.
576
577 Q: How can I have multiple widgets with the same label or without a label?
578 Q: I have multiple widgets with the same label, and only the first one works. Why is that?
579 A: A primer on labels and the ID Stack...
580
581 Dear ImGui internally need to uniquely identify UI elements.
582 Elements that are typically not clickable (such as calls to the Text functions) don't need an ID.
583 Interactive widgets (such as calls to Button buttons) need a unique ID.
584 Unique ID are used internally to track active widgets and occasionally associate state to widgets.
585 Unique ID are implicitly built from the hash of multiple elements that identify the "path" to the UI element.
586
587 - Unique ID are often derived from a string label:
588
589 Button("OK"); // Label = "OK", ID = hash of (..., "OK")
590 Button("Cancel"); // Label = "Cancel", ID = hash of (..., "Cancel")
591
592 - ID are uniquely scoped within windows, tree nodes, etc. which all pushes to the ID stack. Having
593 two buttons labeled "OK" in different windows or different tree locations is fine.
594 We used "..." above to signify whatever was already pushed to the ID stack previously:
595
596 Begin("MyWindow");
597 Button("OK"); // Label = "OK", ID = hash of ("MyWindow", "OK")
598 End();
599
600 - If you have a same ID twice in the same location, you'll have a conflict:
601
602 Button("OK");
603 Button("OK"); // ID collision! Interacting with either button will trigger the first one.
604
605 Fear not! this is easy to solve and there are many ways to solve it!
606
607 - Solving ID conflict in a simple/local context:
608 When passing a label you can optionally specify extra ID information within string itself.
609 Use "##" to pass a complement to the ID that won't be visible to the end-user.
610 This helps solving the simple collision cases when you know e.g. at compilation time which items
611 are going to be created:
612
613 Begin("MyWindow");
614 Button("Play"); // Label = "Play", ID = hash of ("MyWindow", "Play")
615 Button("Play##foo1"); // Label = "Play", ID = hash of ("MyWindow", "Play##foo1") // Different from above
616 Button("Play##foo2"); // Label = "Play", ID = hash of ("MyWindow", "Play##foo2") // Different from above
617 End();
618
619 - If you want to completely hide the label, but still need an ID:
620
621 Checkbox("##On", &b); // Label = "", ID = hash of (..., "##On") // No visible label!
622
623 - Occasionally/rarely you might want change a label while preserving a constant ID. This allows
624 you to animate labels. For example you may want to include varying information in a window title bar,
625 but windows are uniquely identified by their ID. Use "###" to pass a label that isn't part of ID:
626
627 Button("Hello###ID"; // Label = "Hello", ID = hash of (..., "ID")
628 Button("World###ID"; // Label = "World", ID = hash of (..., "ID") // Same as above, even though the label looks different
629
630 sprintf(buf, "My game (%f FPS)###MyGame", fps);
631 Begin(buf); // Variable label, ID = hash of "MyGame"
632
633 - Solving ID conflict in a more general manner:
634 Use PushID() / PopID() to create scopes and manipulate the ID stack, as to avoid ID conflicts
635 within the same window. This is the most convenient way of distinguishing ID when iterating and
636 creating many UI elements programmatically.
637 You can push a pointer, a string or an integer value into the ID stack.
638 Remember that ID are formed from the concatenation of _everything_ in the ID stack!
639
640 Begin("Window");
641 for (int i = 0; i < 100; i++)
642 {
643 PushID(i); // Push i to the id tack
644 Button("Click"); // Label = "Click", ID = Hash of ("Window", i, "Click")
645 PopID();
646 }
647 for (int i = 0; i < 100; i++)
648 {
649 MyObject* obj = Objects[i];
650 PushID(obj);
651 Button("Click"); // Label = "Click", ID = Hash of ("Window", obj pointer, "Click")
652 PopID();
653 }
654 for (int i = 0; i < 100; i++)
655 {
656 MyObject* obj = Objects[i];
657 PushID(obj->Name);
658 Button("Click"); // Label = "Click", ID = Hash of ("Window", obj->Name, "Click")
659 PopID();
660 }
661 End();
662
663 - More example showing that you can stack multiple prefixes into the ID stack:
664
665 Button("Click"); // Label = "Click", ID = hash of (..., "Click")
666 PushID("node");
667 Button("Click"); // Label = "Click", ID = hash of (..., "node", "Click")
668 PushID(my_ptr);
669 Button("Click"); // Label = "Click", ID = hash of (..., "node", my_ptr, "Click")
670 PopID();
671 PopID();
672
673 - Tree nodes implicitly creates a scope for you by calling PushID().
674
675 Button("Click"); // Label = "Click", ID = hash of (..., "Click")
676 if (TreeNode("node"))
677 {
678 Button("Click"); // Label = "Click", ID = hash of (..., "node", "Click")
679 TreePop();
680 }
681
682 - When working with trees, ID are used to preserve the open/close state of each tree node.
683 Depending on your use cases you may want to use strings, indices or pointers as ID.
684 e.g. when following a single pointer that may change over time, using a static string as ID
685 will preserve your node open/closed state when the targeted object change.
686 e.g. when displaying a list of objects, using indices or pointers as ID will preserve the
687 node open/closed state differently. See what makes more sense in your situation!
688
689 Q: How can I use my own math types instead of ImVec2/ImVec4?
690 A: You can edit imconfig.h and setup the IM_VEC2_CLASS_EXTRA/IM_VEC4_CLASS_EXTRA macros to add implicit type conversions.
691 This way you'll be able to use your own types everywhere, e.g. passsing glm::vec2 to ImGui functions instead of ImVec2.
692
693 Q: How can I load a different font than the default?
694 A: Use the font atlas to load the TTF/OTF file you want:
695 ImGuiIO& io = ImGui::GetIO();
696 io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels);
697 io.Fonts->GetTexDataAsRGBA32() or GetTexDataAsAlpha8()
698 (default is ProggyClean.ttf, rendered at size 13, embedded in dear imgui's source code)
699
700 New programmers: remember that in C/C++ and most programming languages if you want to use a
701 backslash \ within a string literal, you need to write it double backslash "\\":
702 io.Fonts->AddFontFromFileTTF("MyDataFolder\MyFontFile.ttf", size_in_pixels); // WRONG (you are escape the M here!)
703 io.Fonts->AddFontFromFileTTF("MyDataFolder\\MyFontFile.ttf", size_in_pixels); // CORRECT
704 io.Fonts->AddFontFromFileTTF("MyDataFolder/MyFontFile.ttf", size_in_pixels); // ALSO CORRECT
705
706 Q: How can I easily use icons in my application?
707 A: The most convenient and practical way is to merge an icon font such as FontAwesome inside you
708 main font. Then you can refer to icons within your strings. Read 'How can I load multiple fonts?'
709 and the file 'misc/fonts/README.txt' for instructions and useful header files.
710
711 Q: How can I load multiple fonts?
712 A: Use the font atlas to pack them into a single texture:
713 (Read misc/fonts/README.txt and the code in ImFontAtlas for more details.)
714
715 ImGuiIO& io = ImGui::GetIO();
716 ImFont* font0 = io.Fonts->AddFontDefault();
717 ImFont* font1 = io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels);
718 ImFont* font2 = io.Fonts->AddFontFromFileTTF("myfontfile2.ttf", size_in_pixels);
719 io.Fonts->GetTexDataAsRGBA32() or GetTexDataAsAlpha8()
720 // the first loaded font gets used by default
721 // use ImGui::PushFont()/ImGui::PopFont() to change the font at runtime
722
723 // Options
724 ImFontConfig config;
725 config.OversampleH = 3;
726 config.OversampleV = 1;
727 config.GlyphOffset.y -= 2.0f; // Move everything by 2 pixels up
728 config.GlyphExtraSpacing.x = 1.0f; // Increase spacing between characters
729 io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, &config);
730
731 // Combine multiple fonts into one (e.g. for icon fonts)
732 ImWchar ranges[] = { 0xf000, 0xf3ff, 0 };
733 ImFontConfig config;
734 config.MergeMode = true;
735 io.Fonts->AddFontDefault();
736 io.Fonts->LoadFromFileTTF("fontawesome-webfont.ttf", 16.0f, &config, ranges); // Merge icon font
737 io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, NULL, &config, io.Fonts->GetGlyphRangesJapanese()); // Merge japanese glyphs
738
739 Q: How can I display and input non-Latin characters such as Chinese, Japanese, Korean, Cyrillic?
740 A: When loading a font, pass custom Unicode ranges to specify the glyphs to load.
741
742 // Add default Japanese ranges
743 io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels, NULL, io.Fonts->GetGlyphRangesJapanese());
744
745 // Or create your own custom ranges (e.g. for a game you can feed your entire game script and only build the characters the game need)
746 ImVector<ImWchar> ranges;
747 ImFontAtlas::GlyphRangesBuilder builder;
748 builder.AddText("Hello world"); // Add a string (here "Hello world" contains 7 unique characters)
749 builder.AddChar(0x7262); // Add a specific character
750 builder.AddRanges(io.Fonts->GetGlyphRangesJapanese()); // Add one of the default ranges
751 builder.BuildRanges(&ranges); // Build the final result (ordered ranges with all the unique characters submitted)
752 io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels, NULL, ranges.Data);
753
754 All your strings needs to use UTF-8 encoding. In C++11 you can encode a string literal in UTF-8
755 by using the u8"hello" syntax. Specifying literal in your source code using a local code page
756 (such as CP-923 for Japanese or CP-1251 for Cyrillic) will NOT work!
757 Otherwise you can convert yourself to UTF-8 or load text data from file already saved as UTF-8.
758
759 Text input: it is up to your application to pass the right character code by calling io.AddInputCharacter().
760 The applications in examples/ are doing that.
761 Windows: you can use the WM_CHAR or WM_UNICHAR or WM_IME_CHAR message (depending if your app is built using Unicode or MultiByte mode).
762 You may also use MultiByteToWideChar() or ToUnicode() to retrieve Unicode codepoints from MultiByte characters or keyboard state.
763 Windows: if your language is relying on an Input Method Editor (IME), you copy the HWND of your window to io.ImeWindowHandle in order for
764 the default implementation of io.ImeSetInputScreenPosFn() to set your Microsoft IME position correctly.
765
766 Q: How can I use the drawing facilities without an ImGui window? (using ImDrawList API)
767 A: - You can create a dummy window. Call SetNextWindowBgAlpha(0.0f), call Begin() with NoTitleBar|NoResize|NoMove|NoScrollbar|NoSavedSettings|NoInputs flags.
768 Then you can retrieve the ImDrawList* via GetWindowDrawList() and draw to it in any way you like.
769 - You can call ImGui::GetOverlayDrawList() and use this draw list to display contents over every other imgui windows.
770 - You can create your own ImDrawList instance. You'll need to initialize them ImGui::GetDrawListSharedData(), or create your own ImDrawListSharedData.
771
772 Q: I integrated Dear ImGui in my engine and the text or lines are blurry..
773 A: In your Render function, try translating your projection matrix by (0.5f,0.5f) or (0.375f,0.375f).
774 Also make sure your orthographic projection matrix and io.DisplaySize matches your actual framebuffer dimension.
775
776 Q: I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around..
777 A: You are probably mishandling the clipping rectangles in your render function.
778 Rectangles provided by ImGui are defined as (x1=left,y1=top,x2=right,y2=bottom) and NOT as (x1,y1,width,height).
779
780 Q: How can I help?
781 A: - If you are experienced with Dear ImGui and C++, look at the github issues, or TODO.txt and see how you want/can help!
782 - Convince your company to fund development time! Individual users: you can also become a Patron (patreon.com/imgui) or donate on PayPal! See README.
783 - Disclose your usage of dear imgui via a dev blog post, a tweet, a screenshot, a mention somewhere etc.
784 You may post screenshot or links in the gallery threads (github.com/ocornut/imgui/issues/1269). Visuals are ideal as they inspire other programmers.
785 But even without visuals, disclosing your use of dear imgui help the library grow credibility, and help other teams and programmers with taking decisions.
786 - 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).
787
788 - tip: you can call Begin() multiple times with the same name during the same frame, it will keep appending to the same window.
789 this is also useful to set yourself in the context of another window (to get/set other settings)
790 - tip: you can create widgets without a Begin()/End() block, they will go in an implicit window called "Debug".
791 - tip: the ImGuiOnceUponAFrame helper will allow run the block of code only once a frame. You can use it to quickly add custom UI in the middle
792 of a deep nested inner loop in your code.
793 - tip: you can call Render() multiple times (e.g for VR renders).
794 - tip: call and read the ShowDemoWindow() code in imgui_demo.cpp for more example of how to use ImGui!
795
796 */
797
798 #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
799 #define _CRT_SECURE_NO_WARNINGS
800 #endif
801
802 #include "imgui.h"
803 #ifndef IMGUI_DEFINE_MATH_OPERATORS
804 #define IMGUI_DEFINE_MATH_OPERATORS
805 #endif
806 #include "imgui_internal.h"
807
808 #include <ctype.h> // toupper, isprint
809 #include <stdio.h> // vsnprintf, sscanf, printf
810 #if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier
811 #include <stddef.h> // intptr_t
812 #else
813 #include <stdint.h> // intptr_t
814 #endif
815
816 #define IMGUI_DEBUG_NAV_SCORING 0
817 #define IMGUI_DEBUG_NAV_RECTS 0
818
819 // Visual Studio warnings
820 #ifdef _MSC_VER
821 #pragma warning (disable: 4127) // condition expression is constant
822 #pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff)
823 #pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen
824 #endif
825
826 // Clang warnings with -Weverything
827 #ifdef __clang__
828 #pragma clang diagnostic ignored "-Wunknown-pragmas" // warning : unknown warning group '-Wformat-pedantic *' // not all warnings are known by all clang versions.. so ignoring warnings triggers new warnings on some configuration. great!
829 #pragma clang diagnostic ignored "-Wold-style-cast" // warning : use of old-style cast // yes, they are more terse.
830 #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.
831 #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.
832 #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.
833 #pragma clang diagnostic ignored "-Wglobal-constructors" // warning : declaration requires a global destructor // similar to above, not sure what the exact difference it.
834 #pragma clang diagnostic ignored "-Wsign-conversion" // warning : implicit conversion changes signedness //
835 #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.
836 #pragma clang diagnostic ignored "-Wint-to-void-pointer-cast" // warning : cast to 'void *' from smaller integer type 'int'
837 #elif defined(__GNUC__)
838 #pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used
839 #pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size
840 #pragma GCC diagnostic ignored "-Wformat" // warning: format '%p' expects argument of type 'void*', but argument 6 has type 'ImGuiWindow*'
841 #pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function
842 #pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value
843 #pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked
844 #pragma GCC diagnostic ignored "-Wstrict-overflow" // warning: assuming signed overflow does not occur when assuming that (X - c) > X is always false
845 #if __GNUC__ >= 8
846 #pragma GCC diagnostic ignored "-Wclass-memaccess" // warning: 'memset/memcpy' clearing/writing an object of type 'xxxx' with no trivial copy-assignment; use assignment or value-initialization instead
847 #endif
848 #endif
849
850 static const ImS32 IM_S32_MIN = 0x80000000; // INT_MIN;
851 static const ImS32 IM_S32_MAX = 0x7FFFFFFF; // INT_MAX;
852 static const ImU32 IM_U32_MIN = 0;
853 static const ImU32 IM_U32_MAX = 0xFFFFFFFF;
854 static const ImS64 IM_S64_MIN = -9223372036854775807ll - 1ll;
855 static const ImS64 IM_S64_MAX = 9223372036854775807ll;
856 static const ImU64 IM_U64_MIN = 0;
857 static const ImU64 IM_U64_MAX = 0xFFFFFFFFFFFFFFFFull;
858
859 // 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.
860 static const float NAV_WINDOWING_HIGHLIGHT_DELAY = 0.20f; // Time before the highlight and screen dimming starts fading in
861 static const float NAV_WINDOWING_LIST_APPEAR_DELAY = 0.15f; // Time before the window list starts to appear
862
863 //-------------------------------------------------------------------------
864 // Forward Declarations
865 //-------------------------------------------------------------------------
866
867 static bool IsKeyPressedMap(ImGuiKey key, bool repeat = true);
868
869 static void SetCurrentWindow(ImGuiWindow* window);
870 static void SetWindowScrollX(ImGuiWindow* window, float new_scroll_x);
871 static void SetWindowScrollY(ImGuiWindow* window, float new_scroll_y);
872 static void SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond);
873 static void SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond);
874 static void SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond);
875 static void FindHoveredWindow();
876 static ImGuiWindow* CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFlags flags);
877 static ImGuiWindowSettings* CreateNewWindowSettings(const char* name);
878 static void CheckStacksSize(ImGuiWindow* window, bool write);
879 static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window, bool snap_on_edges);
880
881 static void AddDrawListToDrawData(ImVector<ImDrawList*>* out_list, ImDrawList* draw_list);
882 static void AddWindowToDrawData(ImVector<ImDrawList*>* out_list, ImGuiWindow* window);
883 static void AddWindowToSortedBuffer(ImVector<ImGuiWindow*>* out_sorted_windows, ImGuiWindow* window);
884
885 static ImRect GetViewportRect();
886
887 static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data);
888 static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end);
889 static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false);
890
891 static inline int DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* data_ptr, const char* format);
892 static void DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, void* arg_1, const void* arg_2);
893 static bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* format);
894
895 namespace ImGui
896 {
897 static bool BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags flags);
898
899 static void NavUpdate();
900 static void NavUpdateWindowing();
901 static void NavUpdateWindowingList();
902 static void NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, const ImGuiID id);
903
904 static void UpdateMouseInputs();
905 static void UpdateMouseWheel();
906 static void UpdateManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4]);
907 static void FocusFrontMostActiveWindow(ImGuiWindow* ignore_window);
908
909 // Template widget behaviors
910 template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
911 static bool DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const TYPE v_min, const TYPE v_max, const char* format, float power);
912
913 template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
914 static bool SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, TYPE* v, const TYPE v_min, const TYPE v_max, const char* format, float power, ImGuiSliderFlags flags);
915 }
916
917 //-----------------------------------------------------------------------------
918 // Platform dependent default implementations
919 //-----------------------------------------------------------------------------
920
921 static const char* GetClipboardTextFn_DefaultImpl(void* user_data);
922 static void SetClipboardTextFn_DefaultImpl(void* user_data, const char* text);
923 static void ImeSetInputScreenPosFn_DefaultImpl(int x, int y);
924
925 //-----------------------------------------------------------------------------
926 // Context
927 //-----------------------------------------------------------------------------
928
929 // Current context pointer. Implicitly used by all ImGui functions. Always assumed to be != NULL.
930 // CreateContext() will automatically set this pointer if it is NULL. Change to a different context by calling ImGui::SetCurrentContext().
931 // If you use DLL hotreloading you might need to call SetCurrentContext() after reloading code from this file.
932 // ImGui functions are not thread-safe because of this pointer. If you want thread-safety to allow N threads to access N different contexts, you can:
933 // - Change this variable to use thread local storage. You may #define GImGui in imconfig.h for that purpose. Future development aim to make this context pointer explicit to all calls. Also read https://github.com/ocornut/imgui/issues/586
934 // - Having multiple instances of the ImGui code compiled inside different namespace (easiest/safest, if you have a finite number of contexts)
935 #ifndef GImGui
936 ImGuiContext* GImGui = NULL;
937 #endif
938
939 // Memory Allocator functions. Use SetAllocatorFunctions() to change them.
940 // If you use DLL hotreloading you might need to call SetAllocatorFunctions() after reloading code from this file.
941 // 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.
942 #ifndef IMGUI_DISABLE_DEFAULT_ALLOCATORS
943 static void* MallocWrapper(size_t size, void* user_data) { (void)user_data; return malloc(size); }
944 static void FreeWrapper(void* ptr, void* user_data) { (void)user_data; free(ptr); }
945 #else
946 static void* MallocWrapper(size_t size, void* user_data) { (void)user_data; (void)size; IM_ASSERT(0); return NULL; }
947 static void FreeWrapper(void* ptr, void* user_data) { (void)user_data; (void)ptr; IM_ASSERT(0); }
948 #endif
949
950 static void* (*GImAllocatorAllocFunc)(size_t size, void* user_data) = MallocWrapper;
951 static void (*GImAllocatorFreeFunc)(void* ptr, void* user_data) = FreeWrapper;
952 static void* GImAllocatorUserData = NULL;
953 static size_t GImAllocatorActiveAllocationsCount = 0;
954
955 //-----------------------------------------------------------------------------
956 // User facing structures
957 //-----------------------------------------------------------------------------
958
959 ImGuiStyle::ImGuiStyle()
960 {
961 Alpha = 1.0f; // Global alpha applies to everything in ImGui
962 WindowPadding = ImVec2(8,8); // Padding within a window
963 WindowRounding = 7.0f; // Radius of window corners rounding. Set to 0.0f to have rectangular windows
964 WindowBorderSize = 1.0f; // Thickness of border around windows. Generally set to 0.0f or 1.0f. Other values not well tested.
965 WindowMinSize = ImVec2(32,32); // Minimum window size
966 WindowTitleAlign = ImVec2(0.0f,0.5f);// Alignment for title bar text
967 ChildRounding = 0.0f; // Radius of child window corners rounding. Set to 0.0f to have rectangular child windows
968 ChildBorderSize = 1.0f; // Thickness of border around child windows. Generally set to 0.0f or 1.0f. Other values not well tested.
969 PopupRounding = 0.0f; // Radius of popup window corners rounding. Set to 0.0f to have rectangular child windows
970 PopupBorderSize = 1.0f; // Thickness of border around popup or tooltip windows. Generally set to 0.0f or 1.0f. Other values not well tested.
971 FramePadding = ImVec2(4,3); // Padding within a framed rectangle (used by most widgets)
972 FrameRounding = 0.0f; // Radius of frame corners rounding. Set to 0.0f to have rectangular frames (used by most widgets).
973 FrameBorderSize = 0.0f; // Thickness of border around frames. Generally set to 0.0f or 1.0f. Other values not well tested.
974 ItemSpacing = ImVec2(8,4); // Horizontal and vertical spacing between widgets/lines
975 ItemInnerSpacing = ImVec2(4,4); // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label)
976 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!
977 IndentSpacing = 21.0f; // Horizontal spacing when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2).
978 ColumnsMinSpacing = 6.0f; // Minimum horizontal spacing between two columns
979 ScrollbarSize = 16.0f; // Width of the vertical scrollbar, Height of the horizontal scrollbar
980 ScrollbarRounding = 9.0f; // Radius of grab corners rounding for scrollbar
981 GrabMinSize = 10.0f; // Minimum width/height of a grab box for slider/scrollbar
982 GrabRounding = 0.0f; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs.
983 ButtonTextAlign = ImVec2(0.5f,0.5f);// Alignment of button text when button is larger than text.
984 DisplayWindowPadding = ImVec2(20,20); // Window positions are clamped to be visible within the display area by at least this amount. Only covers regular windows.
985 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.
986 MouseCursorScale = 1.0f; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later.
987 AntiAliasedLines = true; // Enable anti-aliasing on lines/borders. Disable if you are really short on CPU/GPU.
988 AntiAliasedFill = true; // Enable anti-aliasing on filled shapes (rounded rectangles, circles, etc.)
989 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.
990
991 // Default theme
992 ImGui::StyleColorsDark(this);
993 }
994
995 // 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.
996 // 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.
997 void ImGuiStyle::ScaleAllSizes(float scale_factor)
998 {
999 WindowPadding = ImFloor(WindowPadding * scale_factor);
1000 WindowRounding = ImFloor(WindowRounding * scale_factor);
1001 WindowMinSize = ImFloor(WindowMinSize * scale_factor);
1002 ChildRounding = ImFloor(ChildRounding * scale_factor);
1003 PopupRounding = ImFloor(PopupRounding * scale_factor);
1004 FramePadding = ImFloor(FramePadding * scale_factor);
1005 FrameRounding = ImFloor(FrameRounding * scale_factor);
1006 ItemSpacing = ImFloor(ItemSpacing * scale_factor);
1007 ItemInnerSpacing = ImFloor(ItemInnerSpacing * scale_factor);
1008 TouchExtraPadding = ImFloor(TouchExtraPadding * scale_factor);
1009 IndentSpacing = ImFloor(IndentSpacing * scale_factor);
1010 ColumnsMinSpacing = ImFloor(ColumnsMinSpacing * scale_factor);
1011 ScrollbarSize = ImFloor(ScrollbarSize * scale_factor);
1012 ScrollbarRounding = ImFloor(ScrollbarRounding * scale_factor);
1013 GrabMinSize = ImFloor(GrabMinSize * scale_factor);
1014 GrabRounding = ImFloor(GrabRounding * scale_factor);
1015 DisplayWindowPadding = ImFloor(DisplayWindowPadding * scale_factor);
1016 DisplaySafeAreaPadding = ImFloor(DisplaySafeAreaPadding * scale_factor);
1017 MouseCursorScale = ImFloor(MouseCursorScale * scale_factor);
1018 }
1019
1020 ImGuiIO::ImGuiIO()
1021 {
1022 // Most fields are initialized with zero
1023 memset(this, 0, sizeof(*this));
1024
1025 // Settings
1026 ConfigFlags = 0x00;
1027 BackendFlags = 0x00;
1028 DisplaySize = ImVec2(-1.0f, -1.0f);
1029 DeltaTime = 1.0f/60.0f;
1030 IniSavingRate = 5.0f;
1031 IniFilename = "imgui.ini";
1032 LogFilename = "imgui_log.txt";
1033 MouseDoubleClickTime = 0.30f;
1034 MouseDoubleClickMaxDist = 6.0f;
1035 for (int i = 0; i < ImGuiKey_COUNT; i++)
1036 KeyMap[i] = -1;
1037 KeyRepeatDelay = 0.250f;
1038 KeyRepeatRate = 0.050f;
1039 UserData = NULL;
1040
1041 Fonts = NULL;
1042 FontGlobalScale = 1.0f;
1043 FontDefault = NULL;
1044 FontAllowUserScaling = false;
1045 DisplayFramebufferScale = ImVec2(1.0f, 1.0f);
1046 DisplayVisibleMin = DisplayVisibleMax = ImVec2(0.0f, 0.0f);
1047
1048 // Advanced/subtle behaviors
1049 #ifdef __APPLE__
1050 OptMacOSXBehaviors = true; // Set Mac OS X style defaults based on __APPLE__ compile time flag
1051 #else
1052 OptMacOSXBehaviors = false;
1053 #endif
1054 OptCursorBlink = true;
1055
1056 // Settings (User Functions)
1057 GetClipboardTextFn = GetClipboardTextFn_DefaultImpl; // Platform dependent default implementations
1058 SetClipboardTextFn = SetClipboardTextFn_DefaultImpl;
1059 ClipboardUserData = NULL;
1060 ImeSetInputScreenPosFn = ImeSetInputScreenPosFn_DefaultImpl;
1061 ImeWindowHandle = NULL;
1062
1063 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
1064 RenderDrawListsFn = NULL;
1065 #endif
1066
1067 // Input (NB: we already have memset zero the entire structure)
1068 MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
1069 MousePosPrev = ImVec2(-FLT_MAX, -FLT_MAX);
1070 MouseDragThreshold = 6.0f;
1071 for (int i = 0; i < IM_ARRAYSIZE(MouseDownDuration); i++) MouseDownDuration[i] = MouseDownDurationPrev[i] = -1.0f;
1072 for (int i = 0; i < IM_ARRAYSIZE(KeysDownDuration); i++) KeysDownDuration[i] = KeysDownDurationPrev[i] = -1.0f;
1073 for (int i = 0; i < IM_ARRAYSIZE(NavInputsDownDuration); i++) NavInputsDownDuration[i] = -1.0f;
1074 }
1075
1076 // Pass in translated ASCII characters for text input.
1077 // - with glfw you can get those from the callback set in glfwSetCharCallback()
1078 // - on Windows you can get those using ToAscii+keyboard state, or via the WM_CHAR message
1079 void ImGuiIO::AddInputCharacter(ImWchar c)
1080 {
1081 const int n = ImStrlenW(InputCharacters);
1082 if (n + 1 < IM_ARRAYSIZE(InputCharacters))
1083 {
1084 InputCharacters[n] = c;
1085 InputCharacters[n+1] = '\0';
1086 }
1087 }
1088
1089 void ImGuiIO::AddInputCharactersUTF8(const char* utf8_chars)
1090 {
1091 // We can't pass more wchars than ImGuiIO::InputCharacters[] can hold so don't convert more
1092 const int wchars_buf_len = sizeof(ImGuiIO::InputCharacters) / sizeof(ImWchar);
1093 ImWchar wchars[wchars_buf_len];
1094 ImTextStrFromUtf8(wchars, wchars_buf_len, utf8_chars, NULL);
1095 for (int i = 0; i < wchars_buf_len && wchars[i] != 0; i++)
1096 AddInputCharacter(wchars[i]);
1097 }
1098
1099 //-----------------------------------------------------------------------------
1100 // HELPERS
1101 //-----------------------------------------------------------------------------
1102
1103 #define IM_STATIC_ASSERT(_COND) typedef char static_assertion_##__line__[(_COND)?1:-1]
1104 #define IM_F32_TO_INT8_UNBOUND(_VAL) ((int)((_VAL) * 255.0f + ((_VAL)>=0 ? 0.5f : -0.5f))) // Unsaturated, for display purpose
1105 #define IM_F32_TO_INT8_SAT(_VAL) ((int)(ImSaturate(_VAL) * 255.0f + 0.5f)) // Saturated, always output 0..255
1106
1107 ImVec2 ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& p)
1108 {
1109 ImVec2 ap = p - a;
1110 ImVec2 ab_dir = b - a;
1111 float dot = ap.x * ab_dir.x + ap.y * ab_dir.y;
1112 if (dot < 0.0f)
1113 return a;
1114 float ab_len_sqr = ab_dir.x * ab_dir.x + ab_dir.y * ab_dir.y;
1115 if (dot > ab_len_sqr)
1116 return b;
1117 return a + ab_dir * dot / ab_len_sqr;
1118 }
1119
1120 bool ImTriangleContainsPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p)
1121 {
1122 bool b1 = ((p.x - b.x) * (a.y - b.y) - (p.y - b.y) * (a.x - b.x)) < 0.0f;
1123 bool b2 = ((p.x - c.x) * (b.y - c.y) - (p.y - c.y) * (b.x - c.x)) < 0.0f;
1124 bool b3 = ((p.x - a.x) * (c.y - a.y) - (p.y - a.y) * (c.x - a.x)) < 0.0f;
1125 return ((b1 == b2) && (b2 == b3));
1126 }
1127
1128 void ImTriangleBarycentricCoords(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p, float& out_u, float& out_v, float& out_w)
1129 {
1130 ImVec2 v0 = b - a;
1131 ImVec2 v1 = c - a;
1132 ImVec2 v2 = p - a;
1133 const float denom = v0.x * v1.y - v1.x * v0.y;
1134 out_v = (v2.x * v1.y - v1.x * v2.y) / denom;
1135 out_w = (v0.x * v2.y - v2.x * v0.y) / denom;
1136 out_u = 1.0f - out_v - out_w;
1137 }
1138
1139 ImVec2 ImTriangleClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p)
1140 {
1141 ImVec2 proj_ab = ImLineClosestPoint(a, b, p);
1142 ImVec2 proj_bc = ImLineClosestPoint(b, c, p);
1143 ImVec2 proj_ca = ImLineClosestPoint(c, a, p);
1144 float dist2_ab = ImLengthSqr(p - proj_ab);
1145 float dist2_bc = ImLengthSqr(p - proj_bc);
1146 float dist2_ca = ImLengthSqr(p - proj_ca);
1147 float m = ImMin(dist2_ab, ImMin(dist2_bc, dist2_ca));
1148 if (m == dist2_ab)
1149 return proj_ab;
1150 if (m == dist2_bc)
1151 return proj_bc;
1152 return proj_ca;
1153 }
1154
1155 int ImStricmp(const char* str1, const char* str2)
1156 {
1157 int d;
1158 while ((d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; }
1159 return d;
1160 }
1161
1162 int ImStrnicmp(const char* str1, const char* str2, size_t count)
1163 {
1164 int d = 0;
1165 while (count > 0 && (d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; count--; }
1166 return d;
1167 }
1168
1169 void ImStrncpy(char* dst, const char* src, size_t count)
1170 {
1171 if (count < 1) return;
1172 strncpy(dst, src, count);
1173 dst[count-1] = 0;
1174 }
1175
1176 char* ImStrdup(const char *str)
1177 {
1178 size_t len = strlen(str) + 1;
1179 void* buf = ImGui::MemAlloc(len);
1180 return (char*)memcpy(buf, (const void*)str, len);
1181 }
1182
1183 const char* ImStrchrRange(const char* str, const char* str_end, char c)
1184 {
1185 for ( ; str < str_end; str++)
1186 if (*str == c)
1187 return str;
1188 return NULL;
1189 }
1190
1191 int ImStrlenW(const ImWchar* str)
1192 {
1193 int n = 0;
1194 while (*str++) n++;
1195 return n;
1196 }
1197
1198 const ImWchar* ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* buf_begin) // find beginning-of-line
1199 {
1200 while (buf_mid_line > buf_begin && buf_mid_line[-1] != '\n')
1201 buf_mid_line--;
1202 return buf_mid_line;
1203 }
1204
1205 const char* ImStristr(const char* haystack, const char* haystack_end, const char* needle, const char* needle_end)
1206 {
1207 if (!needle_end)
1208 needle_end = needle + strlen(needle);
1209
1210 const char un0 = (char)toupper(*needle);
1211 while ((!haystack_end && *haystack) || (haystack_end && haystack < haystack_end))
1212 {
1213 if (toupper(*haystack) == un0)
1214 {
1215 const char* b = needle + 1;
1216 for (const char* a = haystack + 1; b < needle_end; a++, b++)
1217 if (toupper(*a) != toupper(*b))
1218 break;
1219 if (b == needle_end)
1220 return haystack;
1221 }
1222 haystack++;
1223 }
1224 return NULL;
1225 }
1226
1227 // 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.
1228 void ImStrTrimBlanks(char* buf)
1229 {
1230 char* p = buf;
1231 while (p[0] == ' ' || p[0] == '\t') // Leading blanks
1232 p++;
1233 char* p_start = p;
1234 while (*p != 0) // Find end of string
1235 p++;
1236 while (p > p_start && (p[-1] == ' ' || p[-1] == '\t')) // Trailing blanks
1237 p--;
1238 if (p_start != buf) // Copy memory if we had leading blanks
1239 memmove(buf, p_start, p - p_start);
1240 buf[p - p_start] = 0; // Zero terminate
1241 }
1242
1243 template<typename TYPE>
1244 static const char* ImAtoi(const char* src, TYPE* output)
1245 {
1246 int negative = 0;
1247 if (*src == '-') { negative = 1; src++; }
1248 if (*src == '+') { src++; }
1249 TYPE v = 0;
1250 while (*src >= '0' && *src <= '9')
1251 v = (v * 10) + (*src++ - '0');
1252 *output = negative ? -v : v;
1253 return src;
1254 }
1255
1256 // A) MSVC version appears to return -1 on overflow, whereas glibc appears to return total count (which may be >= buf_size).
1257 // 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.
1258 // B) When buf==NULL vsnprintf() will return the output size.
1259 #ifndef IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS
1260 int ImFormatString(char* buf, size_t buf_size, const char* fmt, ...)
1261 {
1262 IM_ASSERT(fmt != NULL);
1263 va_list args;
1264 va_start(args, fmt);
1265 int w = vsnprintf(buf, buf_size, fmt, args);
1266 va_end(args);
1267 if (buf == NULL)
1268 return w;
1269 if (w == -1 || w >= (int)buf_size)
1270 w = (int)buf_size - 1;
1271 buf[w] = 0;
1272 return w;
1273 }
1274
1275 int ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args)
1276 {
1277 IM_ASSERT(fmt != NULL);
1278 int w = vsnprintf(buf, buf_size, fmt, args);
1279 if (buf == NULL)
1280 return w;
1281 if (w == -1 || w >= (int)buf_size)
1282 w = (int)buf_size - 1;
1283 buf[w] = 0;
1284 return w;
1285 }
1286 #endif // #ifdef IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS
1287
1288 // Pass data_size==0 for zero-terminated strings
1289 // FIXME-OPT: Replace with e.g. FNV1a hash? CRC32 pretty much randomly access 1KB. Need to do proper measurements.
1290 ImU32 ImHash(const void* data, int data_size, ImU32 seed)
1291 {
1292 static ImU32 crc32_lut[256] = { 0 };
1293 if (!crc32_lut[1])
1294 {
1295 const ImU32 polynomial = 0xEDB88320;
1296 for (ImU32 i = 0; i < 256; i++)
1297 {
1298 ImU32 crc = i;
1299 for (ImU32 j = 0; j < 8; j++)
1300 crc = (crc >> 1) ^ (ImU32(-int(crc & 1)) & polynomial);
1301 crc32_lut[i] = crc;
1302 }
1303 }
1304
1305 seed = ~seed;
1306 ImU32 crc = seed;
1307 const unsigned char* current = (const unsigned char*)data;
1308
1309 if (data_size > 0)
1310 {
1311 // Known size
1312 while (data_size--)
1313 crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ *current++];
1314 }
1315 else
1316 {
1317 // Zero-terminated string
1318 while (unsigned char c = *current++)
1319 {
1320 // We support a syntax of "label###id" where only "###id" is included in the hash, and only "label" gets displayed.
1321 // Because this syntax is rarely used we are optimizing for the common case.
1322 // - If we reach ### in the string we discard the hash so far and reset to the seed.
1323 // - We don't do 'current += 2; continue;' after handling ### to keep the code smaller.
1324 if (c == '#' && current[0] == '#' && current[1] == '#')
1325 crc = seed;
1326 crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c];
1327 }
1328 }
1329 return ~crc;
1330 }
1331
1332 //-----------------------------------------------------------------------------
1333 // ImText* helpers
1334 //-----------------------------------------------------------------------------
1335
1336 // Convert UTF-8 to 32-bits character, process single character input.
1337 // Based on stb_from_utf8() from github.com/nothings/stb/
1338 // We handle UTF-8 decoding error by skipping forward.
1339 int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* in_text_end)
1340 {
1341 unsigned int c = (unsigned int)-1;
1342 const unsigned char* str = (const unsigned char*)in_text;
1343 if (!(*str & 0x80))
1344 {
1345 c = (unsigned int)(*str++);
1346 *out_char = c;
1347 return 1;
1348 }
1349 if ((*str & 0xe0) == 0xc0)
1350 {
1351 *out_char = 0xFFFD; // will be invalid but not end of string
1352 if (in_text_end && in_text_end - (const char*)str < 2) return 1;
1353 if (*str < 0xc2) return 2;
1354 c = (unsigned int)((*str++ & 0x1f) << 6);
1355 if ((*str & 0xc0) != 0x80) return 2;
1356 c += (*str++ & 0x3f);
1357 *out_char = c;
1358 return 2;
1359 }
1360 if ((*str & 0xf0) == 0xe0)
1361 {
1362 *out_char = 0xFFFD; // will be invalid but not end of string
1363 if (in_text_end && in_text_end - (const char*)str < 3) return 1;
1364 if (*str == 0xe0 && (str[1] < 0xa0 || str[1] > 0xbf)) return 3;
1365 if (*str == 0xed && str[1] > 0x9f) return 3; // str[1] < 0x80 is checked below
1366 c = (unsigned int)((*str++ & 0x0f) << 12);
1367 if ((*str & 0xc0) != 0x80) return 3;
1368 c += (unsigned int)((*str++ & 0x3f) << 6);
1369 if ((*str & 0xc0) != 0x80) return 3;
1370 c += (*str++ & 0x3f);
1371 *out_char = c;
1372 return 3;
1373 }
1374 if ((*str & 0xf8) == 0xf0)
1375 {
1376 *out_char = 0xFFFD; // will be invalid but not end of string
1377 if (in_text_end && in_text_end - (const char*)str < 4) return 1;
1378 if (*str > 0xf4) return 4;
1379 if (*str == 0xf0 && (str[1] < 0x90 || str[1] > 0xbf)) return 4;
1380 if (*str == 0xf4 && str[1] > 0x8f) return 4; // str[1] < 0x80 is checked below
1381 c = (unsigned int)((*str++ & 0x07) << 18);
1382 if ((*str & 0xc0) != 0x80) return 4;
1383 c += (unsigned int)((*str++ & 0x3f) << 12);
1384 if ((*str & 0xc0) != 0x80) return 4;
1385 c += (unsigned int)((*str++ & 0x3f) << 6);
1386 if ((*str & 0xc0) != 0x80) return 4;
1387 c += (*str++ & 0x3f);
1388 // utf-8 encodings of values used in surrogate pairs are invalid
1389 if ((c & 0xFFFFF800) == 0xD800) return 4;
1390 *out_char = c;
1391 return 4;
1392 }
1393 *out_char = 0;
1394 return 0;
1395 }
1396
1397 int ImTextStrFromUtf8(ImWchar* buf, int buf_size, const char* in_text, const char* in_text_end, const char** in_text_remaining)
1398 {
1399 ImWchar* buf_out = buf;
1400 ImWchar* buf_end = buf + buf_size;
1401 while (buf_out < buf_end-1 && (!in_text_end || in_text < in_text_end) && *in_text)
1402 {
1403 unsigned int c;
1404 in_text += ImTextCharFromUtf8(&c, in_text, in_text_end);
1405 if (c == 0)
1406 break;
1407 if (c < 0x10000) // FIXME: Losing characters that don't fit in 2 bytes
1408 *buf_out++ = (ImWchar)c;
1409 }
1410 *buf_out = 0;
1411 if (in_text_remaining)
1412 *in_text_remaining = in_text;
1413 return (int)(buf_out - buf);
1414 }
1415
1416 int ImTextCountCharsFromUtf8(const char* in_text, const char* in_text_end)
1417 {
1418 int char_count = 0;
1419 while ((!in_text_end || in_text < in_text_end) && *in_text)
1420 {
1421 unsigned int c;
1422 in_text += ImTextCharFromUtf8(&c, in_text, in_text_end);
1423 if (c == 0)
1424 break;
1425 if (c < 0x10000)
1426 char_count++;
1427 }
1428 return char_count;
1429 }
1430
1431 // Based on stb_to_utf8() from github.com/nothings/stb/
1432 static inline int ImTextCharToUtf8(char* buf, int buf_size, unsigned int c)
1433 {
1434 if (c < 0x80)
1435 {
1436 buf[0] = (char)c;
1437 return 1;
1438 }
1439 if (c < 0x800)
1440 {
1441 if (buf_size < 2) return 0;
1442 buf[0] = (char)(0xc0 + (c >> 6));
1443 buf[1] = (char)(0x80 + (c & 0x3f));
1444 return 2;
1445 }
1446 if (c >= 0xdc00 && c < 0xe000)
1447 {
1448 return 0;
1449 }
1450 if (c >= 0xd800 && c < 0xdc00)
1451 {
1452 if (buf_size < 4) return 0;
1453 buf[0] = (char)(0xf0 + (c >> 18));
1454 buf[1] = (char)(0x80 + ((c >> 12) & 0x3f));
1455 buf[2] = (char)(0x80 + ((c >> 6) & 0x3f));
1456 buf[3] = (char)(0x80 + ((c ) & 0x3f));
1457 return 4;
1458 }
1459 //else if (c < 0x10000)
1460 {
1461 if (buf_size < 3) return 0;
1462 buf[0] = (char)(0xe0 + (c >> 12));
1463 buf[1] = (char)(0x80 + ((c>> 6) & 0x3f));
1464 buf[2] = (char)(0x80 + ((c ) & 0x3f));
1465 return 3;
1466 }
1467 }
1468
1469 static inline int ImTextCountUtf8BytesFromChar(unsigned int c)
1470 {
1471 if (c < 0x80) return 1;
1472 if (c < 0x800) return 2;
1473 if (c >= 0xdc00 && c < 0xe000) return 0;
1474 if (c >= 0xd800 && c < 0xdc00) return 4;
1475 return 3;
1476 }
1477
1478 int ImTextStrToUtf8(char* buf, int buf_size, const ImWchar* in_text, const ImWchar* in_text_end)
1479 {
1480 char* buf_out = buf;
1481 const char* buf_end = buf + buf_size;
1482 while (buf_out < buf_end-1 && (!in_text_end || in_text < in_text_end) && *in_text)
1483 {
1484 unsigned int c = (unsigned int)(*in_text++);
1485 if (c < 0x80)
1486 *buf_out++ = (char)c;
1487 else
1488 buf_out += ImTextCharToUtf8(buf_out, (int)(buf_end-buf_out-1), c);
1489 }
1490 *buf_out = 0;
1491 return (int)(buf_out - buf);
1492 }
1493
1494 int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_end)
1495 {
1496 int bytes_count = 0;
1497 while ((!in_text_end || in_text < in_text_end) && *in_text)
1498 {
1499 unsigned int c = (unsigned int)(*in_text++);
1500 if (c < 0x80)
1501 bytes_count++;
1502 else
1503 bytes_count += ImTextCountUtf8BytesFromChar(c);
1504 }
1505 return bytes_count;
1506 }
1507
1508 ImVec4 ImGui::ColorConvertU32ToFloat4(ImU32 in)
1509 {
1510 float s = 1.0f/255.0f;
1511 return ImVec4(
1512 ((in >> IM_COL32_R_SHIFT) & 0xFF) * s,
1513 ((in >> IM_COL32_G_SHIFT) & 0xFF) * s,
1514 ((in >> IM_COL32_B_SHIFT) & 0xFF) * s,
1515 ((in >> IM_COL32_A_SHIFT) & 0xFF) * s);
1516 }
1517
1518 ImU32 ImGui::ColorConvertFloat4ToU32(const ImVec4& in)
1519 {
1520 ImU32 out;
1521 out = ((ImU32)IM_F32_TO_INT8_SAT(in.x)) << IM_COL32_R_SHIFT;
1522 out |= ((ImU32)IM_F32_TO_INT8_SAT(in.y)) << IM_COL32_G_SHIFT;
1523 out |= ((ImU32)IM_F32_TO_INT8_SAT(in.z)) << IM_COL32_B_SHIFT;
1524 out |= ((ImU32)IM_F32_TO_INT8_SAT(in.w)) << IM_COL32_A_SHIFT;
1525 return out;
1526 }
1527
1528 ImU32 ImGui::GetColorU32(ImGuiCol idx, float alpha_mul)
1529 {
1530 ImGuiStyle& style = GImGui->Style;
1531 ImVec4 c = style.Colors[idx];
1532 c.w *= style.Alpha * alpha_mul;
1533 return ColorConvertFloat4ToU32(c);
1534 }
1535
1536 ImU32 ImGui::GetColorU32(const ImVec4& col)
1537 {
1538 ImGuiStyle& style = GImGui->Style;
1539 ImVec4 c = col;
1540 c.w *= style.Alpha;
1541 return ColorConvertFloat4ToU32(c);
1542 }
1543
1544 const ImVec4& ImGui::GetStyleColorVec4(ImGuiCol idx)
1545 {
1546 ImGuiStyle& style = GImGui->Style;
1547 return style.Colors[idx];
1548 }
1549
1550 ImU32 ImGui::GetColorU32(ImU32 col)
1551 {
1552 float style_alpha = GImGui->Style.Alpha;
1553 if (style_alpha >= 1.0f)
1554 return col;
1555 ImU32 a = (col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT;
1556 a = (ImU32)(a * style_alpha); // We don't need to clamp 0..255 because Style.Alpha is in 0..1 range.
1557 return (col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT);
1558 }
1559
1560 // Convert rgb floats ([0-1],[0-1],[0-1]) to hsv floats ([0-1],[0-1],[0-1]), from Foley & van Dam p592
1561 // Optimized http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv
1562 void ImGui::ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& out_s, float& out_v)
1563 {
1564 float K = 0.f;
1565 if (g < b)
1566 {
1567 ImSwap(g, b);
1568 K = -1.f;
1569 }
1570 if (r < g)
1571 {
1572 ImSwap(r, g);
1573 K = -2.f / 6.f - K;
1574 }
1575
1576 const float chroma = r - (g < b ? g : b);
1577 out_h = ImFabs(K + (g - b) / (6.f * chroma + 1e-20f));
1578 out_s = chroma / (r + 1e-20f);
1579 out_v = r;
1580 }
1581
1582 // Convert hsv floats ([0-1],[0-1],[0-1]) to rgb floats ([0-1],[0-1],[0-1]), from Foley & van Dam p593
1583 // also http://en.wikipedia.org/wiki/HSL_and_HSV
1584 void ImGui::ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& out_g, float& out_b)
1585 {
1586 if (s == 0.0f)
1587 {
1588 // gray
1589 out_r = out_g = out_b = v;
1590 return;
1591 }
1592
1593 h = ImFmod(h, 1.0f) / (60.0f/360.0f);
1594 int i = (int)h;
1595 float f = h - (float)i;
1596 float p = v * (1.0f - s);
1597 float q = v * (1.0f - s * f);
1598 float t = v * (1.0f - s * (1.0f - f));
1599
1600 switch (i)
1601 {
1602 case 0: out_r = v; out_g = t; out_b = p; break;
1603 case 1: out_r = q; out_g = v; out_b = p; break;
1604 case 2: out_r = p; out_g = v; out_b = t; break;
1605 case 3: out_r = p; out_g = q; out_b = v; break;
1606 case 4: out_r = t; out_g = p; out_b = v; break;
1607 case 5: default: out_r = v; out_g = p; out_b = q; break;
1608 }
1609 }
1610
1611 FILE* ImFileOpen(const char* filename, const char* mode)
1612 {
1613 #if defined(_WIN32) && !defined(__CYGWIN__)
1614 // We need a fopen() wrapper because MSVC/Windows fopen doesn't handle UTF-8 filenames. Converting both strings from UTF-8 to wchar format (using a single allocation, because we can)
1615 const int filename_wsize = ImTextCountCharsFromUtf8(filename, NULL) + 1;
1616 const int mode_wsize = ImTextCountCharsFromUtf8(mode, NULL) + 1;
1617 ImVector<ImWchar> buf;
1618 buf.resize(filename_wsize + mode_wsize);
1619 ImTextStrFromUtf8(&buf[0], filename_wsize, filename, NULL);
1620 ImTextStrFromUtf8(&buf[filename_wsize], mode_wsize, mode, NULL);
1621 return _wfopen((wchar_t*)&buf[0], (wchar_t*)&buf[filename_wsize]);
1622 #else
1623 return fopen(filename, mode);
1624 #endif
1625 }
1626
1627 // Load file content into memory
1628 // Memory allocated with ImGui::MemAlloc(), must be freed by user using ImGui::MemFree()
1629 void* ImFileLoadToMemory(const char* filename, const char* file_open_mode, size_t* out_file_size, int padding_bytes)
1630 {
1631 IM_ASSERT(filename && file_open_mode);
1632 if (out_file_size)
1633 *out_file_size = 0;
1634
1635 FILE* f;
1636 if ((f = ImFileOpen(filename, file_open_mode)) == NULL)
1637 return NULL;
1638
1639 long file_size_signed;
1640 if (fseek(f, 0, SEEK_END) || (file_size_signed = ftell(f)) == -1 || fseek(f, 0, SEEK_SET))
1641 {
1642 fclose(f);
1643 return NULL;
1644 }
1645
1646 size_t file_size = (size_t)file_size_signed;
1647 void* file_data = ImGui::MemAlloc(file_size + padding_bytes);
1648 if (file_data == NULL)
1649 {
1650 fclose(f);
1651 return NULL;
1652 }
1653 if (fread(file_data, 1, file_size, f) != file_size)
1654 {
1655 fclose(f);
1656 ImGui::MemFree(file_data);
1657 return NULL;
1658 }
1659 if (padding_bytes > 0)
1660 memset((void *)(((char*)file_data) + file_size), 0, (size_t)padding_bytes);
1661
1662 fclose(f);
1663 if (out_file_size)
1664 *out_file_size = file_size;
1665
1666 return file_data;
1667 }
1668
1669 //-----------------------------------------------------------------------------
1670 // ImGuiStorage
1671 // Helper: Key->value storage
1672 //-----------------------------------------------------------------------------
1673
1674 // std::lower_bound but without the bullshit
1675 static ImVector<ImGuiStorage::Pair>::iterator LowerBound(ImVector<ImGuiStorage::Pair>& data, ImGuiID key)
1676 {
1677 ImVector<ImGuiStorage::Pair>::iterator first = data.begin();
1678 ImVector<ImGuiStorage::Pair>::iterator last = data.end();
1679 size_t count = (size_t)(last - first);
1680 while (count > 0)
1681 {
1682 size_t count2 = count >> 1;
1683 ImVector<ImGuiStorage::Pair>::iterator mid = first + count2;
1684 if (mid->key < key)
1685 {
1686 first = ++mid;
1687 count -= count2 + 1;
1688 }
1689 else
1690 {
1691 count = count2;
1692 }
1693 }
1694 return first;
1695 }
1696
1697 // For quicker full rebuild of a storage (instead of an incremental one), you may add all your contents and then sort once.
1698 void ImGuiStorage::BuildSortByKey()
1699 {
1700 struct StaticFunc
1701 {
1702 static int IMGUI_CDECL PairCompareByID(const void* lhs, const void* rhs)
1703 {
1704 // We can't just do a subtraction because qsort uses signed integers and subtracting our ID doesn't play well with that.
1705 if (((const Pair*)lhs)->key > ((const Pair*)rhs)->key) return +1;
1706 if (((const Pair*)lhs)->key < ((const Pair*)rhs)->key) return -1;
1707 return 0;
1708 }
1709 };
1710 if (Data.Size > 1)
1711 ImQsort(Data.Data, (size_t)Data.Size, sizeof(Pair), StaticFunc::PairCompareByID);
1712 }
1713
1714 int ImGuiStorage::GetInt(ImGuiID key, int default_val) const
1715 {
1716 ImVector<Pair>::iterator it = LowerBound(const_cast<ImVector<ImGuiStorage::Pair>&>(Data), key);
1717 if (it == Data.end() || it->key != key)
1718 return default_val;
1719 return it->val_i;
1720 }
1721
1722 bool ImGuiStorage::GetBool(ImGuiID key, bool default_val) const
1723 {
1724 return GetInt(key, default_val ? 1 : 0) != 0;
1725 }
1726
1727 float ImGuiStorage::GetFloat(ImGuiID key, float default_val) const
1728 {
1729 ImVector<Pair>::iterator it = LowerBound(const_cast<ImVector<ImGuiStorage::Pair>&>(Data), key);
1730 if (it == Data.end() || it->key != key)
1731 return default_val;
1732 return it->val_f;
1733 }
1734
1735 void* ImGuiStorage::GetVoidPtr(ImGuiID key) const
1736 {
1737 ImVector<Pair>::iterator it = LowerBound(const_cast<ImVector<ImGuiStorage::Pair>&>(Data), key);
1738 if (it == Data.end() || it->key != key)
1739 return NULL;
1740 return it->val_p;
1741 }
1742
1743 // 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.
1744 int* ImGuiStorage::GetIntRef(ImGuiID key, int default_val)
1745 {
1746 ImVector<Pair>::iterator it = LowerBound(Data, key);
1747 if (it == Data.end() || it->key != key)
1748 it = Data.insert(it, Pair(key, default_val));
1749 return &it->val_i;
1750 }
1751
1752 bool* ImGuiStorage::GetBoolRef(ImGuiID key, bool default_val)
1753 {
1754 return (bool*)GetIntRef(key, default_val ? 1 : 0);
1755 }
1756
1757 float* ImGuiStorage::GetFloatRef(ImGuiID key, float default_val)
1758 {
1759 ImVector<Pair>::iterator it = LowerBound(Data, key);
1760 if (it == Data.end() || it->key != key)
1761 it = Data.insert(it, Pair(key, default_val));
1762 return &it->val_f;
1763 }
1764
1765 void** ImGuiStorage::GetVoidPtrRef(ImGuiID key, void* default_val)
1766 {
1767 ImVector<Pair>::iterator it = LowerBound(Data, key);
1768 if (it == Data.end() || it->key != key)
1769 it = Data.insert(it, Pair(key, default_val));
1770 return &it->val_p;
1771 }
1772
1773 // 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)
1774 void ImGuiStorage::SetInt(ImGuiID key, int val)
1775 {
1776 ImVector<Pair>::iterator it = LowerBound(Data, key);
1777 if (it == Data.end() || it->key != key)
1778 {
1779 Data.insert(it, Pair(key, val));
1780 return;
1781 }
1782 it->val_i = val;
1783 }
1784
1785 void ImGuiStorage::SetBool(ImGuiID key, bool val)
1786 {
1787 SetInt(key, val ? 1 : 0);
1788 }
1789
1790 void ImGuiStorage::SetFloat(ImGuiID key, float val)
1791 {
1792 ImVector<Pair>::iterator it = LowerBound(Data, key);
1793 if (it == Data.end() || it->key != key)
1794 {
1795 Data.insert(it, Pair(key, val));
1796 return;
1797 }
1798 it->val_f = val;
1799 }
1800
1801 void ImGuiStorage::SetVoidPtr(ImGuiID key, void* val)
1802 {
1803 ImVector<Pair>::iterator it = LowerBound(Data, key);
1804 if (it == Data.end() || it->key != key)
1805 {
1806 Data.insert(it, Pair(key, val));
1807 return;
1808 }
1809 it->val_p = val;
1810 }
1811
1812 void ImGuiStorage::SetAllInt(int v)
1813 {
1814 for (int i = 0; i < Data.Size; i++)
1815 Data[i].val_i = v;
1816 }
1817
1818 //-----------------------------------------------------------------------------
1819 // ImGuiTextFilter
1820 //-----------------------------------------------------------------------------
1821
1822 // Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]"
1823 ImGuiTextFilter::ImGuiTextFilter(const char* default_filter)
1824 {
1825 if (default_filter)
1826 {
1827 ImStrncpy(InputBuf, default_filter, IM_ARRAYSIZE(InputBuf));
1828 Build();
1829 }
1830 else
1831 {
1832 InputBuf[0] = 0;
1833 CountGrep = 0;
1834 }
1835 }
1836
1837 bool ImGuiTextFilter::Draw(const char* label, float width)
1838 {
1839 if (width != 0.0f)
1840 ImGui::PushItemWidth(width);
1841 bool value_changed = ImGui::InputText(label, InputBuf, IM_ARRAYSIZE(InputBuf));
1842 if (width != 0.0f)
1843 ImGui::PopItemWidth();
1844 if (value_changed)
1845 Build();
1846 return value_changed;
1847 }
1848
1849 void ImGuiTextFilter::TextRange::split(char separator, ImVector<TextRange>* out) const
1850 {
1851 out->resize(0);
1852 const char* wb = b;
1853 const char* we = wb;
1854 while (we < e)
1855 {
1856 if (*we == separator)
1857 {
1858 out->push_back(TextRange(wb, we));
1859 wb = we + 1;
1860 }
1861 we++;
1862 }
1863 if (wb != we)
1864 out->push_back(TextRange(wb, we));
1865 }
1866
1867 void ImGuiTextFilter::Build()
1868 {
1869 Filters.resize(0);
1870 TextRange input_range(InputBuf, InputBuf+strlen(InputBuf));
1871 input_range.split(',', &Filters);
1872
1873 CountGrep = 0;
1874 for (int i = 0; i != Filters.Size; i++)
1875 {
1876 TextRange& f = Filters[i];
1877 while (f.b < f.e && ImCharIsBlankA(f.b[0]))
1878 f.b++;
1879 while (f.e > f.b && ImCharIsBlankA(f.e[-1]))
1880 f.e--;
1881 if (f.empty())
1882 continue;
1883 if (Filters[i].b[0] != '-')
1884 CountGrep += 1;
1885 }
1886 }
1887
1888 bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const
1889 {
1890 if (Filters.empty())
1891 return true;
1892
1893 if (text == NULL)
1894 text = "";
1895
1896 for (int i = 0; i != Filters.Size; i++)
1897 {
1898 const TextRange& f = Filters[i];
1899 if (f.empty())
1900 continue;
1901 if (f.b[0] == '-')
1902 {
1903 // Subtract
1904 if (ImStristr(text, text_end, f.begin()+1, f.end()) != NULL)
1905 return false;
1906 }
1907 else
1908 {
1909 // Grep
1910 if (ImStristr(text, text_end, f.begin(), f.end()) != NULL)
1911 return true;
1912 }
1913 }
1914
1915 // Implicit * grep
1916 if (CountGrep == 0)
1917 return true;
1918
1919 return false;
1920 }
1921
1922 //-----------------------------------------------------------------------------
1923 // ImGuiTextBuffer
1924 //-----------------------------------------------------------------------------
1925
1926 // On some platform vsnprintf() takes va_list by reference and modifies it.
1927 // va_copy is the 'correct' way to copy a va_list but Visual Studio prior to 2013 doesn't have it.
1928 #ifndef va_copy
1929 #define va_copy(dest, src) (dest = src)
1930 #endif
1931
1932 // Helper: Text buffer for logging/accumulating text
1933 void ImGuiTextBuffer::appendfv(const char* fmt, va_list args)
1934 {
1935 va_list args_copy;
1936 va_copy(args_copy, args);
1937
1938 int len = ImFormatStringV(NULL, 0, fmt, args); // FIXME-OPT: could do a first pass write attempt, likely successful on first pass.
1939 if (len <= 0)
1940 {
1941 va_end(args_copy);
1942 return;
1943 }
1944
1945 const int write_off = Buf.Size;
1946 const int needed_sz = write_off + len;
1947 if (write_off + len >= Buf.Capacity)
1948 {
1949 int double_capacity = Buf.Capacity * 2;
1950 Buf.reserve(needed_sz > double_capacity ? needed_sz : double_capacity);
1951 }
1952
1953 Buf.resize(needed_sz);
1954 ImFormatStringV(&Buf[write_off - 1], (size_t)len + 1, fmt, args_copy);
1955 va_end(args_copy);
1956 }
1957
1958 void ImGuiTextBuffer::appendf(const char* fmt, ...)
1959 {
1960 va_list args;
1961 va_start(args, fmt);
1962 appendfv(fmt, args);
1963 va_end(args);
1964 }
1965
1966 //-----------------------------------------------------------------------------
1967 // ImGuiSimpleColumns (internal use only)
1968 //-----------------------------------------------------------------------------
1969
1970 ImGuiMenuColumns::ImGuiMenuColumns()
1971 {
1972 Count = 0;
1973 Spacing = Width = NextWidth = 0.0f;
1974 memset(Pos, 0, sizeof(Pos));
1975 memset(NextWidths, 0, sizeof(NextWidths));
1976 }
1977
1978 void ImGuiMenuColumns::Update(int count, float spacing, bool clear)
1979 {
1980 IM_ASSERT(Count <= IM_ARRAYSIZE(Pos));
1981 Count = count;
1982 Width = NextWidth = 0.0f;
1983 Spacing = spacing;
1984 if (clear) memset(NextWidths, 0, sizeof(NextWidths));
1985 for (int i = 0; i < Count; i++)
1986 {
1987 if (i > 0 && NextWidths[i] > 0.0f)
1988 Width += Spacing;
1989 Pos[i] = (float)(int)Width;
1990 Width += NextWidths[i];
1991 NextWidths[i] = 0.0f;
1992 }
1993 }
1994
1995 float ImGuiMenuColumns::DeclColumns(float w0, float w1, float w2) // not using va_arg because they promote float to double
1996 {
1997 NextWidth = 0.0f;
1998 NextWidths[0] = ImMax(NextWidths[0], w0);
1999 NextWidths[1] = ImMax(NextWidths[1], w1);
2000 NextWidths[2] = ImMax(NextWidths[2], w2);
2001 for (int i = 0; i < 3; i++)
2002 NextWidth += NextWidths[i] + ((i > 0 && NextWidths[i] > 0.0f) ? Spacing : 0.0f);
2003 return ImMax(Width, NextWidth);
2004 }
2005
2006 float ImGuiMenuColumns::CalcExtraSpace(float avail_w)
2007 {
2008 return ImMax(0.0f, avail_w - Width);
2009 }
2010
2011 //-----------------------------------------------------------------------------
2012 // ImGuiListClipper
2013 //-----------------------------------------------------------------------------
2014
2015 static void SetCursorPosYAndSetupDummyPrevLine(float pos_y, float line_height)
2016 {
2017 // Set cursor position and a few other things so that SetScrollHere() and Columns() can work when seeking cursor.
2018 // FIXME: It is problematic that we have to do that here, because custom/equivalent end-user code would stumble on the same issue.
2019 // The clipper should probably have a 4th step to display the last item in a regular manner.
2020 ImGui::SetCursorPosY(pos_y);
2021 ImGuiWindow* window = ImGui::GetCurrentWindow();
2022 window->DC.CursorPosPrevLine.y = window->DC.CursorPos.y - line_height; // Setting those fields so that SetScrollHere() can properly function after the end of our clipper usage.
2023 window->DC.PrevLineHeight = (line_height - GImGui->Style.ItemSpacing.y); // If we end up needing more accurate data (to e.g. use SameLine) we may as well make the clipper have a fourth step to let user process and display the last item in their list.
2024 if (window->DC.ColumnsSet)
2025 window->DC.ColumnsSet->LineMinY = window->DC.CursorPos.y; // Setting this so that cell Y position are set properly
2026 }
2027
2028 // Use case A: Begin() called from constructor with items_height<0, then called again from Sync() in StepNo 1
2029 // Use case B: Begin() called from constructor with items_height>0
2030 // 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.
2031 void ImGuiListClipper::Begin(int count, float items_height)
2032 {
2033 StartPosY = ImGui::GetCursorPosY();
2034 ItemsHeight = items_height;
2035 ItemsCount = count;
2036 StepNo = 0;
2037 DisplayEnd = DisplayStart = -1;
2038 if (ItemsHeight > 0.0f)
2039 {
2040 ImGui::CalcListClipping(ItemsCount, ItemsHeight, &DisplayStart, &DisplayEnd); // calculate how many to clip/display
2041 if (DisplayStart > 0)
2042 SetCursorPosYAndSetupDummyPrevLine(StartPosY + DisplayStart * ItemsHeight, ItemsHeight); // advance cursor
2043 StepNo = 2;
2044 }
2045 }
2046
2047 void ImGuiListClipper::End()
2048 {
2049 if (ItemsCount < 0)
2050 return;
2051 // 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.
2052 if (ItemsCount < INT_MAX)
2053 SetCursorPosYAndSetupDummyPrevLine(StartPosY + ItemsCount * ItemsHeight, ItemsHeight); // advance cursor
2054 ItemsCount = -1;
2055 StepNo = 3;
2056 }
2057
2058 bool ImGuiListClipper::Step()
2059 {
2060 if (ItemsCount == 0 || ImGui::GetCurrentWindowRead()->SkipItems)
2061 {
2062 ItemsCount = -1;
2063 return false;
2064 }
2065 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.
2066 {
2067 DisplayStart = 0;
2068 DisplayEnd = 1;
2069 StartPosY = ImGui::GetCursorPosY();
2070 StepNo = 1;
2071 return true;
2072 }
2073 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.
2074 {
2075 if (ItemsCount == 1) { ItemsCount = -1; return false; }
2076 float items_height = ImGui::GetCursorPosY() - StartPosY;
2077 IM_ASSERT(items_height > 0.0f); // If this triggers, it means Item 0 hasn't moved the cursor vertically
2078 Begin(ItemsCount-1, items_height);
2079 DisplayStart++;
2080 DisplayEnd++;
2081 StepNo = 3;
2082 return true;
2083 }
2084 if (StepNo == 2) // Step 2: dummy 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.
2085 {
2086 IM_ASSERT(DisplayStart >= 0 && DisplayEnd >= 0);
2087 StepNo = 3;
2088 return true;
2089 }
2090 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.
2091 End();
2092 return false;
2093 }
2094
2095 //-----------------------------------------------------------------------------
2096 // ImGuiWindow
2097 //-----------------------------------------------------------------------------
2098
2099 ImGuiWindow::ImGuiWindow(ImGuiContext* context, const char* name)
2100 : DrawListInst(&context->DrawListSharedData)
2101 {
2102 Name = ImStrdup(name);
2103 ID = ImHash(name, 0);
2104 IDStack.push_back(ID);
2105 Flags = 0;
2106 Pos = ImVec2(0.0f, 0.0f);
2107 Size = SizeFull = ImVec2(0.0f, 0.0f);
2108 SizeContents = SizeContentsExplicit = ImVec2(0.0f, 0.0f);
2109 WindowPadding = ImVec2(0.0f, 0.0f);
2110 WindowRounding = 0.0f;
2111 WindowBorderSize = 0.0f;
2112 MoveId = GetID("#MOVE");
2113 ChildId = 0;
2114 Scroll = ImVec2(0.0f, 0.0f);
2115 ScrollTarget = ImVec2(FLT_MAX, FLT_MAX);
2116 ScrollTargetCenterRatio = ImVec2(0.5f, 0.5f);
2117 ScrollbarSizes = ImVec2(0.0f, 0.0f);
2118 ScrollbarX = ScrollbarY = false;
2119 Active = WasActive = false;
2120 WriteAccessed = false;
2121 Collapsed = false;
2122 CollapseToggleWanted = false;
2123 SkipItems = false;
2124 Appearing = false;
2125 Hidden = false;
2126 HasCloseButton = false;
2127 BeginOrderWithinParent = -1;
2128 BeginOrderWithinContext = -1;
2129 BeginCount = 0;
2130 PopupId = 0;
2131 AutoFitFramesX = AutoFitFramesY = -1;
2132 AutoFitOnlyGrows = false;
2133 AutoFitChildAxises = 0x00;
2134 AutoPosLastDirection = ImGuiDir_None;
2135 HiddenFramesRegular = HiddenFramesForResize = 0;
2136 SetWindowPosAllowFlags = SetWindowSizeAllowFlags = SetWindowCollapsedAllowFlags = ImGuiCond_Always | ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing;
2137 SetWindowPosVal = SetWindowPosPivot = ImVec2(FLT_MAX, FLT_MAX);
2138
2139 LastFrameActive = -1;
2140 ItemWidthDefault = 0.0f;
2141 FontWindowScale = 1.0f;
2142 SettingsIdx = -1;
2143
2144 DrawList = &DrawListInst;
2145 DrawList->_OwnerName = Name;
2146 ParentWindow = NULL;
2147 RootWindow = NULL;
2148 RootWindowForTitleBarHighlight = NULL;
2149 RootWindowForNav = NULL;
2150
2151 NavLastIds[0] = NavLastIds[1] = 0;
2152 NavRectRel[0] = NavRectRel[1] = ImRect();
2153 NavLastChildNavWindow = NULL;
2154
2155 FocusIdxAllCounter = FocusIdxTabCounter = -1;
2156 FocusIdxAllRequestCurrent = FocusIdxTabRequestCurrent = INT_MAX;
2157 FocusIdxAllRequestNext = FocusIdxTabRequestNext = INT_MAX;
2158 }
2159
2160 ImGuiWindow::~ImGuiWindow()
2161 {
2162 IM_ASSERT(DrawList == &DrawListInst);
2163 IM_DELETE(Name);
2164 for (int i = 0; i != ColumnsStorage.Size; i++)
2165 ColumnsStorage[i].~ImGuiColumnsSet();
2166 }
2167
2168 ImGuiID ImGuiWindow::GetID(const char* str, const char* str_end)
2169 {
2170 ImGuiID seed = IDStack.back();
2171 ImGuiID id = ImHash(str, str_end ? (int)(str_end - str) : 0, seed);
2172 ImGui::KeepAliveID(id);
2173 return id;
2174 }
2175
2176 ImGuiID ImGuiWindow::GetID(const void* ptr)
2177 {
2178 ImGuiID seed = IDStack.back();
2179 ImGuiID id = ImHash(&ptr, sizeof(void*), seed);
2180 ImGui::KeepAliveID(id);
2181 return id;
2182 }
2183
2184 ImGuiID ImGuiWindow::GetIDNoKeepAlive(const char* str, const char* str_end)
2185 {
2186 ImGuiID seed = IDStack.back();
2187 return ImHash(str, str_end ? (int)(str_end - str) : 0, seed);
2188 }
2189
2190 // This is only used in rare/specific situations to manufacture an ID out of nowhere.
2191 ImGuiID ImGuiWindow::GetIDFromRectangle(const ImRect& r_abs)
2192 {
2193 ImGuiID seed = IDStack.back();
2194 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) };
2195 ImGuiID id = ImHash(&r_rel, sizeof(r_rel), seed);
2196 ImGui::KeepAliveID(id);
2197 return id;
2198 }
2199
2200 //-----------------------------------------------------------------------------
2201 // Internal API exposed in imgui_internal.h
2202 //-----------------------------------------------------------------------------
2203
2204 static void SetCurrentWindow(ImGuiWindow* window)
2205 {
2206 ImGuiContext& g = *GImGui;
2207 g.CurrentWindow = window;
2208 if (window)
2209 g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize();
2210 }
2211
2212 static void SetNavID(ImGuiID id, int nav_layer)
2213 {
2214 ImGuiContext& g = *GImGui;
2215 IM_ASSERT(g.NavWindow);
2216 IM_ASSERT(nav_layer == 0 || nav_layer == 1);
2217 g.NavId = id;
2218 g.NavWindow->NavLastIds[nav_layer] = id;
2219 }
2220
2221 static void SetNavIDWithRectRel(ImGuiID id, int nav_layer, const ImRect& rect_rel)
2222 {
2223 ImGuiContext& g = *GImGui;
2224 SetNavID(id, nav_layer);
2225 g.NavWindow->NavRectRel[nav_layer] = rect_rel;
2226 g.NavMousePosDirty = true;
2227 g.NavDisableHighlight = false;
2228 g.NavDisableMouseHover = true;
2229 }
2230
2231 void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window)
2232 {
2233 ImGuiContext& g = *GImGui;
2234 g.ActiveIdIsJustActivated = (g.ActiveId != id);
2235 if (g.ActiveIdIsJustActivated)
2236 {
2237 g.ActiveIdTimer = 0.0f;
2238 g.ActiveIdValueChanged = false;
2239 if (id != 0)
2240 {
2241 g.LastActiveId = id;
2242 g.LastActiveIdTimer = 0.0f;
2243 }
2244 }
2245 g.ActiveId = id;
2246 g.ActiveIdAllowNavDirFlags = 0;
2247 g.ActiveIdAllowOverlap = false;
2248 g.ActiveIdWindow = window;
2249 if (id)
2250 {
2251 g.ActiveIdIsAlive = true;
2252 g.ActiveIdSource = (g.NavActivateId == id || g.NavInputId == id || g.NavJustTabbedId == id || g.NavJustMovedToId == id) ? ImGuiInputSource_Nav : ImGuiInputSource_Mouse;
2253 }
2254 }
2255
2256 void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window)
2257 {
2258 ImGuiContext& g = *GImGui;
2259 IM_ASSERT(id != 0);
2260
2261 // Assume that SetFocusID() is called in the context where its NavLayer is the current layer, which is the case everywhere we call it.
2262 const int nav_layer = window->DC.NavLayerCurrent;
2263 if (g.NavWindow != window)
2264 g.NavInitRequest = false;
2265 g.NavId = id;
2266 g.NavWindow = window;
2267 g.NavLayer = nav_layer;
2268 window->NavLastIds[nav_layer] = id;
2269 if (window->DC.LastItemId == id)
2270 window->NavRectRel[nav_layer] = ImRect(window->DC.LastItemRect.Min - window->Pos, window->DC.LastItemRect.Max - window->Pos);
2271
2272 if (g.ActiveIdSource == ImGuiInputSource_Nav)
2273 g.NavDisableMouseHover = true;
2274 else
2275 g.NavDisableHighlight = true;
2276 }
2277
2278 void ImGui::ClearActiveID()
2279 {
2280 SetActiveID(0, NULL);
2281 }
2282
2283 void ImGui::SetHoveredID(ImGuiID id)
2284 {
2285 ImGuiContext& g = *GImGui;
2286 g.HoveredId = id;
2287 g.HoveredIdAllowOverlap = false;
2288 g.HoveredIdTimer = (id != 0 && g.HoveredIdPreviousFrame == id) ? (g.HoveredIdTimer + g.IO.DeltaTime) : 0.0f;
2289 }
2290
2291 ImGuiID ImGui::GetHoveredID()
2292 {
2293 ImGuiContext& g = *GImGui;
2294 return g.HoveredId ? g.HoveredId : g.HoveredIdPreviousFrame;
2295 }
2296
2297 void ImGui::KeepAliveID(ImGuiID id)
2298 {
2299 ImGuiContext& g = *GImGui;
2300 if (g.ActiveId == id)
2301 g.ActiveIdIsAlive = true;
2302 if (g.ActiveIdPreviousFrame == id)
2303 g.ActiveIdPreviousFrameIsAlive = true;
2304 }
2305
2306 void ImGui::MarkItemValueChanged(ImGuiID id)
2307 {
2308 // This marking is solely to be able to provide info for IsItemDeactivatedAfterChange().
2309 // 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.
2310 (void)id; // Avoid unused variable warnings when asserts are compiled out.
2311 ImGuiContext& g = *GImGui;
2312 IM_ASSERT(g.ActiveId == id || g.ActiveId == 0 || g.DragDropActive);
2313 g.ActiveIdValueChanged = true;
2314 }
2315
2316 static inline bool IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flags)
2317 {
2318 // An active popup disable hovering on other windows (apart from its own children)
2319 // FIXME-OPT: This could be cached/stored within the window.
2320 ImGuiContext& g = *GImGui;
2321 if (g.NavWindow)
2322 if (ImGuiWindow* focused_root_window = g.NavWindow->RootWindow)
2323 if (focused_root_window->WasActive && focused_root_window != window->RootWindow)
2324 {
2325 // For the purpose of those flags we differentiate "standard popup" from "modal popup"
2326 // NB: The order of those two tests is important because Modal windows are also Popups.
2327 if (focused_root_window->Flags & ImGuiWindowFlags_Modal)
2328 return false;
2329 if ((focused_root_window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiHoveredFlags_AllowWhenBlockedByPopup))
2330 return false;
2331 }
2332
2333 return true;
2334 }
2335
2336 // Advance cursor given item size for layout.
2337 void ImGui::ItemSize(const ImVec2& size, float text_offset_y)
2338 {
2339 ImGuiContext& g = *GImGui;
2340 ImGuiWindow* window = g.CurrentWindow;
2341 if (window->SkipItems)
2342 return;
2343
2344 // Always align ourselves on pixel boundaries
2345 const float line_height = ImMax(window->DC.CurrentLineHeight, size.y);
2346 const float text_base_offset = ImMax(window->DC.CurrentLineTextBaseOffset, text_offset_y);
2347 //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]
2348 window->DC.CursorPosPrevLine = ImVec2(window->DC.CursorPos.x + size.x, window->DC.CursorPos.y);
2349 window->DC.CursorPos = ImVec2((float)(int)(window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX), (float)(int)(window->DC.CursorPos.y + line_height + g.Style.ItemSpacing.y));
2350 window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPosPrevLine.x);
2351 window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y - g.Style.ItemSpacing.y);
2352 //if (g.IO.KeyAlt) window->DrawList->AddCircle(window->DC.CursorMaxPos, 3.0f, IM_COL32(255,0,0,255), 4); // [DEBUG]
2353
2354 window->DC.PrevLineHeight = line_height;
2355 window->DC.PrevLineTextBaseOffset = text_base_offset;
2356 window->DC.CurrentLineHeight = window->DC.CurrentLineTextBaseOffset = 0.0f;
2357
2358 // Horizontal layout mode
2359 if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
2360 SameLine();
2361 }
2362
2363 void ImGui::ItemSize(const ImRect& bb, float text_offset_y)
2364 {
2365 ItemSize(bb.GetSize(), text_offset_y);
2366 }
2367
2368 static ImGuiDir inline NavScoreItemGetQuadrant(float dx, float dy)
2369 {
2370 if (ImFabs(dx) > ImFabs(dy))
2371 return (dx > 0.0f) ? ImGuiDir_Right : ImGuiDir_Left;
2372 return (dy > 0.0f) ? ImGuiDir_Down : ImGuiDir_Up;
2373 }
2374
2375 static float inline NavScoreItemDistInterval(float a0, float a1, float b0, float b1)
2376 {
2377 if (a1 < b0)
2378 return a1 - b0;
2379 if (b1 < a0)
2380 return a0 - b1;
2381 return 0.0f;
2382 }
2383
2384 static void inline NavClampRectToVisibleAreaForMoveDir(ImGuiDir move_dir, ImRect& r, const ImRect& clip_rect)
2385 {
2386 if (move_dir == ImGuiDir_Left || move_dir == ImGuiDir_Right)
2387 {
2388 r.Min.y = ImClamp(r.Min.y, clip_rect.Min.y, clip_rect.Max.y);
2389 r.Max.y = ImClamp(r.Max.y, clip_rect.Min.y, clip_rect.Max.y);
2390 }
2391 else
2392 {
2393 r.Min.x = ImClamp(r.Min.x, clip_rect.Min.x, clip_rect.Max.x);
2394 r.Max.x = ImClamp(r.Max.x, clip_rect.Min.x, clip_rect.Max.x);
2395 }
2396 }
2397
2398 // Scoring function for directional navigation. Based on https://gist.github.com/rygorous/6981057
2399 static bool NavScoreItem(ImGuiNavMoveResult* result, ImRect cand)
2400 {
2401 ImGuiContext& g = *GImGui;
2402 ImGuiWindow* window = g.CurrentWindow;
2403 if (g.NavLayer != window->DC.NavLayerCurrent)
2404 return false;
2405
2406 const ImRect& curr = g.NavScoringRectScreen; // Current modified source rect (NB: we've applied Max.x = Min.x in NavUpdate() to inhibit the effect of having varied item width)
2407 g.NavScoringCount++;
2408
2409 // When entering through a NavFlattened border, we consider child window items as fully clipped for scoring
2410 if (window->ParentWindow == g.NavWindow)
2411 {
2412 IM_ASSERT((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened);
2413 if (!window->ClipRect.Contains(cand))
2414 return false;
2415 cand.ClipWithFull(window->ClipRect); // This allows the scored item to not overlap other candidates in the parent window
2416 }
2417
2418 // 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)
2419 // For example, this ensure that items in one column are not reached when moving vertically from items in another column.
2420 NavClampRectToVisibleAreaForMoveDir(g.NavMoveClipDir, cand, window->ClipRect);
2421
2422 // Compute distance between boxes
2423 // FIXME-NAV: Introducing biases for vertical navigation, needs to be removed.
2424 float dbx = NavScoreItemDistInterval(cand.Min.x, cand.Max.x, curr.Min.x, curr.Max.x);
2425 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
2426 if (dby != 0.0f && dbx != 0.0f)
2427 dbx = (dbx/1000.0f) + ((dbx > 0.0f) ? +1.0f : -1.0f);
2428 float dist_box = ImFabs(dbx) + ImFabs(dby);
2429
2430 // 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)
2431 float dcx = (cand.Min.x + cand.Max.x) - (curr.Min.x + curr.Max.x);
2432 float dcy = (cand.Min.y + cand.Max.y) - (curr.Min.y + curr.Max.y);
2433 float dist_center = ImFabs(dcx) + ImFabs(dcy); // L1 metric (need this for our connectedness guarantee)
2434
2435 // Determine which quadrant of 'curr' our candidate item 'cand' lies in based on distance
2436 ImGuiDir quadrant;
2437 float dax = 0.0f, day = 0.0f, dist_axial = 0.0f;
2438 if (dbx != 0.0f || dby != 0.0f)
2439 {
2440 // For non-overlapping boxes, use distance between boxes
2441 dax = dbx;
2442 day = dby;
2443 dist_axial = dist_box;
2444 quadrant = NavScoreItemGetQuadrant(dbx, dby);
2445 }
2446 else if (dcx != 0.0f || dcy != 0.0f)
2447 {
2448 // For overlapping boxes with different centers, use distance between centers
2449 dax = dcx;
2450 day = dcy;
2451 dist_axial = dist_center;
2452 quadrant = NavScoreItemGetQuadrant(dcx, dcy);
2453 }
2454 else
2455 {
2456 // 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)
2457 quadrant = (window->DC.LastItemId < g.NavId) ? ImGuiDir_Left : ImGuiDir_Right;
2458 }
2459
2460 #if IMGUI_DEBUG_NAV_SCORING
2461 char buf[128];
2462 if (ImGui::IsMouseHoveringRect(cand.Min, cand.Max))
2463 {
2464 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]);
2465 ImDrawList* draw_list = ImGui::GetOverlayDrawList();
2466 draw_list->AddRect(curr.Min, curr.Max, IM_COL32(255,200,0,100));
2467 draw_list->AddRect(cand.Min, cand.Max, IM_COL32(255,255,0,200));
2468 draw_list->AddRectFilled(cand.Max-ImVec2(4,4), cand.Max+ImGui::CalcTextSize(buf)+ImVec2(4,4), IM_COL32(40,0,0,150));
2469 draw_list->AddText(g.IO.FontDefault, 13.0f, cand.Max, ~0U, buf);
2470 }
2471 else if (g.IO.KeyCtrl) // Hold to preview score in matching quadrant. Press C to rotate.
2472 {
2473 if (IsKeyPressedMap(ImGuiKey_C)) { g.NavMoveDirLast = (ImGuiDir)((g.NavMoveDirLast + 1) & 3); g.IO.KeysDownDuration[g.IO.KeyMap[ImGuiKey_C]] = 0.01f; }
2474 if (quadrant == g.NavMoveDir)
2475 {
2476 ImFormatString(buf, IM_ARRAYSIZE(buf), "%.0f/%.0f", dist_box, dist_center);
2477 ImDrawList* draw_list = ImGui::GetOverlayDrawList();
2478 draw_list->AddRectFilled(cand.Min, cand.Max, IM_COL32(255, 0, 0, 200));
2479 draw_list->AddText(g.IO.FontDefault, 13.0f, cand.Min, IM_COL32(255, 255, 255, 255), buf);
2480 }
2481 }
2482 #endif
2483
2484 // Is it in the quadrant we're interesting in moving to?
2485 bool new_best = false;
2486 if (quadrant == g.NavMoveDir)
2487 {
2488 // Does it beat the current best candidate?
2489 if (dist_box < result->DistBox)
2490 {
2491 result->DistBox = dist_box;
2492 result->DistCenter = dist_center;
2493 return true;
2494 }
2495 if (dist_box == result->DistBox)
2496 {
2497 // Try using distance between center points to break ties
2498 if (dist_center < result->DistCenter)
2499 {
2500 result->DistCenter = dist_center;
2501 new_best = true;
2502 }
2503 else if (dist_center == result->DistCenter)
2504 {
2505 // Still tied! we need to be extra-careful to make sure everything gets linked properly. We consistently break ties by symbolically moving "later" items
2506 // (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),
2507 // 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.
2508 if (((g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) ? dby : dbx) < 0.0f) // moving bj to the right/down decreases distance
2509 new_best = true;
2510 }
2511 }
2512 }
2513
2514 // 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
2515 // are found, so it only augments the graph produced by the above method using extra links. (important, since it doesn't guarantee strong connectedness)
2516 // 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.
2517 // 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.
2518 // Disabling it may lead to disconnected graphs when nodes are very spaced out on different axis. Perhaps consider offering this as an option?
2519 if (result->DistBox == FLT_MAX && dist_axial < result->DistAxial) // Check axial match
2520 if (g.NavLayer == 1 && !(g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))
2521 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))
2522 {
2523 result->DistAxial = dist_axial;
2524 new_best = true;
2525 }
2526
2527 return new_best;
2528 }
2529
2530 static void NavSaveLastChildNavWindow(ImGuiWindow* child_window)
2531 {
2532 ImGuiWindow* parent_window = child_window;
2533 while (parent_window && (parent_window->Flags & ImGuiWindowFlags_ChildWindow) != 0 && (parent_window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0)
2534 parent_window = parent_window->ParentWindow;
2535 if (parent_window && parent_window != child_window)
2536 parent_window->NavLastChildNavWindow = child_window;
2537 }
2538
2539 // Call when we are expected to land on Layer 0 after FocusWindow()
2540 static ImGuiWindow* NavRestoreLastChildNavWindow(ImGuiWindow* window)
2541 {
2542 return window->NavLastChildNavWindow ? window->NavLastChildNavWindow : window;
2543 }
2544
2545 static void NavRestoreLayer(int layer)
2546 {
2547 ImGuiContext& g = *GImGui;
2548 g.NavLayer = layer;
2549 if (layer == 0)
2550 g.NavWindow = NavRestoreLastChildNavWindow(g.NavWindow);
2551 if (layer == 0 && g.NavWindow->NavLastIds[0] != 0)
2552 SetNavIDWithRectRel(g.NavWindow->NavLastIds[0], layer, g.NavWindow->NavRectRel[0]);
2553 else
2554 ImGui::NavInitWindow(g.NavWindow, true);
2555 }
2556
2557 static inline void NavUpdateAnyRequestFlag()
2558 {
2559 ImGuiContext& g = *GImGui;
2560 g.NavAnyRequest = g.NavMoveRequest || g.NavInitRequest || (IMGUI_DEBUG_NAV_SCORING && g.NavWindow != NULL);
2561 if (g.NavAnyRequest)
2562 IM_ASSERT(g.NavWindow != NULL);
2563 }
2564
2565 static bool NavMoveRequestButNoResultYet()
2566 {
2567 ImGuiContext& g = *GImGui;
2568 return g.NavMoveRequest && g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0;
2569 }
2570
2571 void ImGui::NavMoveRequestCancel()
2572 {
2573 ImGuiContext& g = *GImGui;
2574 g.NavMoveRequest = false;
2575 NavUpdateAnyRequestFlag();
2576 }
2577
2578 // We get there when either NavId == id, or when g.NavAnyRequest is set (which is updated by NavUpdateAnyRequestFlag above)
2579 static void ImGui::NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, const ImGuiID id)
2580 {
2581 ImGuiContext& g = *GImGui;
2582 //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.
2583 // return;
2584
2585 const ImGuiItemFlags item_flags = window->DC.ItemFlags;
2586 const ImRect nav_bb_rel(nav_bb.Min - window->Pos, nav_bb.Max - window->Pos);
2587
2588 // Process Init Request
2589 if (g.NavInitRequest && g.NavLayer == window->DC.NavLayerCurrent)
2590 {
2591 // Even if 'ImGuiItemFlags_NoNavDefaultFocus' is on (typically collapse/close button) we record the first ResultId so they can be used as a fallback
2592 if (!(item_flags & ImGuiItemFlags_NoNavDefaultFocus) || g.NavInitResultId == 0)
2593 {
2594 g.NavInitResultId = id;
2595 g.NavInitResultRectRel = nav_bb_rel;
2596 }
2597 if (!(item_flags & ImGuiItemFlags_NoNavDefaultFocus))
2598 {
2599 g.NavInitRequest = false; // Found a match, clear request
2600 NavUpdateAnyRequestFlag();
2601 }
2602 }
2603
2604 // Process Move Request (scoring for navigation)
2605 // FIXME-NAV: Consider policy for double scoring (scoring from NavScoringRectScreen + scoring from a rect wrapped according to current wrapping policy)
2606 if ((g.NavId != id || (g.NavMoveRequestFlags & ImGuiNavMoveFlags_AllowCurrentNavId)) && !(item_flags & ImGuiItemFlags_NoNav))
2607 {
2608 ImGuiNavMoveResult* result = (window == g.NavWindow) ? &g.NavMoveResultLocal : &g.NavMoveResultOther;
2609 #if IMGUI_DEBUG_NAV_SCORING
2610 // [DEBUG] Score all items in NavWindow at all times
2611 if (!g.NavMoveRequest)
2612 g.NavMoveDir = g.NavMoveDirLast;
2613 bool new_best = NavScoreItem(result, nav_bb) && g.NavMoveRequest;
2614 #else
2615 bool new_best = g.NavMoveRequest && NavScoreItem(result, nav_bb);
2616 #endif
2617 if (new_best)
2618 {
2619 result->ID = id;
2620 result->Window = window;
2621 result->RectRel = nav_bb_rel;
2622 }
2623
2624 const float VISIBLE_RATIO = 0.70f;
2625 if ((g.NavMoveRequestFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet) && window->ClipRect.Overlaps(nav_bb))
2626 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)
2627 if (NavScoreItem(&g.NavMoveResultLocalVisibleSet, nav_bb))
2628 {
2629 result = &g.NavMoveResultLocalVisibleSet;
2630 result->ID = id;
2631 result->Window = window;
2632 result->RectRel = nav_bb_rel;
2633 }
2634 }
2635
2636 // Update window-relative bounding box of navigated item
2637 if (g.NavId == id)
2638 {
2639 g.NavWindow = window; // Always refresh g.NavWindow, because some operations such as FocusItem() don't have a window.
2640 g.NavLayer = window->DC.NavLayerCurrent;
2641 g.NavIdIsAlive = true;
2642 g.NavIdTabCounter = window->FocusIdxTabCounter;
2643 window->NavRectRel[window->DC.NavLayerCurrent] = nav_bb_rel; // Store item bounding box (relative to window position)
2644 }
2645 }
2646
2647 // Declare item bounding box for clipping and interaction.
2648 // Note that the size can be different than the one provided to ItemSize(). Typically, widgets that spread over available surface
2649 // declare their minimum size requirement to ItemSize() and then use a larger region for drawing/interaction, which is passed to ItemAdd().
2650 bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg)
2651 {
2652 ImGuiContext& g = *GImGui;
2653 ImGuiWindow* window = g.CurrentWindow;
2654
2655 if (id != 0)
2656 {
2657 // Navigation processing runs prior to clipping early-out
2658 // (a) So that NavInitRequest can be honored, for newly opened windows to select a default widget
2659 // (b) So that we can scroll up/down past clipped items. This adds a small O(N) cost to regular navigation requests unfortunately, but it is still limited to one window.
2660 // it may not scale very well for windows with ten of thousands of item, but at least NavMoveRequest is only set on user interaction, aka maximum once a frame.
2661 // We could early out with "if (is_clipped && !g.NavInitRequest) return false;" but when we wouldn't be able to reach unclipped widgets. This would work if user had explicit scrolling control (e.g. mapped on a stick)
2662 window->DC.NavLayerActiveMaskNext |= window->DC.NavLayerCurrentMask;
2663 if (g.NavId == id || g.NavAnyRequest)
2664 if (g.NavWindow->RootWindowForNav == window->RootWindowForNav)
2665 if (window == g.NavWindow || ((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened))
2666 NavProcessItem(window, nav_bb_arg ? *nav_bb_arg : bb, id);
2667 }
2668
2669 window->DC.LastItemId = id;
2670 window->DC.LastItemRect = bb;
2671 window->DC.LastItemStatusFlags = 0;
2672
2673 // Clipping test
2674 const bool is_clipped = IsClippedEx(bb, id, false);
2675 if (is_clipped)
2676 return false;
2677 //if (g.IO.KeyAlt) window->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(255,255,0,120)); // [DEBUG]
2678
2679 // We need to calculate this now to take account of the current clipping rectangle (as items like Selectable may change them)
2680 if (IsMouseHoveringRect(bb.Min, bb.Max))
2681 window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HoveredRect;
2682 return true;
2683 }
2684
2685 // This is roughly matching the behavior of internal-facing ItemHoverable()
2686 // - 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()
2687 // - this should work even for non-interactive items that have no ID, so we cannot use LastItemId
2688 bool ImGui::IsItemHovered(ImGuiHoveredFlags flags)
2689 {
2690 ImGuiContext& g = *GImGui;
2691 ImGuiWindow* window = g.CurrentWindow;
2692 if (g.NavDisableMouseHover && !g.NavDisableHighlight)
2693 return IsItemFocused();
2694
2695 // Test for bounding box overlap, as updated as ItemAdd()
2696 if (!(window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect))
2697 return false;
2698 IM_ASSERT((flags & (ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows)) == 0); // Flags not supported by this function
2699
2700 // Test if we are hovering the right window (our window could be behind another window)
2701 // [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.
2702 // 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.
2703 //if (g.HoveredWindow != window)
2704 // return false;
2705 if (g.HoveredRootWindow != window->RootWindow && !(flags & ImGuiHoveredFlags_AllowWhenOverlapped))
2706 return false;
2707
2708 // Test if another item is active (e.g. being dragged)
2709 if (!(flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
2710 if (g.ActiveId != 0 && g.ActiveId != window->DC.LastItemId && !g.ActiveIdAllowOverlap && g.ActiveId != window->MoveId)
2711 return false;
2712
2713 // Test if interactions on this window are blocked by an active popup or modal
2714 if (!IsWindowContentHoverable(window, flags))
2715 return false;
2716
2717 // Test if the item is disabled
2718 if ((window->DC.ItemFlags & ImGuiItemFlags_Disabled) && !(flags & ImGuiHoveredFlags_AllowWhenDisabled))
2719 return false;
2720
2721 // Special handling for the 1st item after Begin() which represent the title bar. When the window is collapsed (SkipItems==true) that last item will never be overwritten so we need to detect tht case.
2722 if (window->DC.LastItemId == window->MoveId && window->WriteAccessed)
2723 return false;
2724 return true;
2725 }
2726
2727 // Internal facing ItemHoverable() used when submitting widgets. Differs slightly from IsItemHovered().
2728 bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id)
2729 {
2730 ImGuiContext& g = *GImGui;
2731 if (g.HoveredId != 0 && g.HoveredId != id && !g.HoveredIdAllowOverlap)
2732 return false;
2733
2734 ImGuiWindow* window = g.CurrentWindow;
2735 if (g.HoveredWindow != window)
2736 return false;
2737 if (g.ActiveId != 0 && g.ActiveId != id && !g.ActiveIdAllowOverlap)
2738 return false;
2739 if (!IsMouseHoveringRect(bb.Min, bb.Max))
2740 return false;
2741 if (g.NavDisableMouseHover || !IsWindowContentHoverable(window, ImGuiHoveredFlags_None))
2742 return false;
2743 if (window->DC.ItemFlags & ImGuiItemFlags_Disabled)
2744 return false;
2745
2746 SetHoveredID(id);
2747 return true;
2748 }
2749
2750 bool ImGui::IsClippedEx(const ImRect& bb, ImGuiID id, bool clip_even_when_logged)
2751 {
2752 ImGuiContext& g = *GImGui;
2753 ImGuiWindow* window = g.CurrentWindow;
2754 if (!bb.Overlaps(window->ClipRect))
2755 if (id == 0 || id != g.ActiveId)
2756 if (clip_even_when_logged || !g.LogEnabled)
2757 return true;
2758 return false;
2759 }
2760
2761 bool ImGui::FocusableItemRegister(ImGuiWindow* window, ImGuiID id, bool tab_stop)
2762 {
2763 ImGuiContext& g = *GImGui;
2764
2765 const bool allow_keyboard_focus = (window->DC.ItemFlags & (ImGuiItemFlags_AllowKeyboardFocus | ImGuiItemFlags_Disabled)) == ImGuiItemFlags_AllowKeyboardFocus;
2766 window->FocusIdxAllCounter++;
2767 if (allow_keyboard_focus)
2768 window->FocusIdxTabCounter++;
2769
2770 // Process keyboard input at this point: TAB/Shift-TAB to tab out of the currently focused item.
2771 // Note that we can always TAB out of a widget that doesn't allow tabbing in.
2772 if (tab_stop && (g.ActiveId == id) && window->FocusIdxAllRequestNext == INT_MAX && window->FocusIdxTabRequestNext == INT_MAX && !g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab))
2773 window->FocusIdxTabRequestNext = window->FocusIdxTabCounter + (g.IO.KeyShift ? (allow_keyboard_focus ? -1 : 0) : +1); // Modulo on index will be applied at the end of frame once we've got the total counter of items.
2774
2775 if (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent)
2776 return true;
2777 if (allow_keyboard_focus && window->FocusIdxTabCounter == window->FocusIdxTabRequestCurrent)
2778 {
2779 g.NavJustTabbedId = id;
2780 return true;
2781 }
2782
2783 return false;
2784 }
2785
2786 void ImGui::FocusableItemUnregister(ImGuiWindow* window)
2787 {
2788 window->FocusIdxAllCounter--;
2789 window->FocusIdxTabCounter--;
2790 }
2791
2792 ImVec2 ImGui::CalcItemSize(ImVec2 size, float default_x, float default_y)
2793 {
2794 ImGuiContext& g = *GImGui;
2795 ImVec2 content_max;
2796 if (size.x < 0.0f || size.y < 0.0f)
2797 content_max = g.CurrentWindow->Pos + GetContentRegionMax();
2798 if (size.x <= 0.0f)
2799 size.x = (size.x == 0.0f) ? default_x : ImMax(content_max.x - g.CurrentWindow->DC.CursorPos.x, 4.0f) + size.x;
2800 if (size.y <= 0.0f)
2801 size.y = (size.y == 0.0f) ? default_y : ImMax(content_max.y - g.CurrentWindow->DC.CursorPos.y, 4.0f) + size.y;
2802 return size;
2803 }
2804
2805 float ImGui::CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x)
2806 {
2807 if (wrap_pos_x < 0.0f)
2808 return 0.0f;
2809
2810 ImGuiWindow* window = GetCurrentWindowRead();
2811 if (wrap_pos_x == 0.0f)
2812 wrap_pos_x = GetContentRegionMax().x + window->Pos.x;
2813 else if (wrap_pos_x > 0.0f)
2814 wrap_pos_x += window->Pos.x - window->Scroll.x; // wrap_pos_x is provided is window local space
2815
2816 return ImMax(wrap_pos_x - pos.x, 1.0f);
2817 }
2818
2819 //-----------------------------------------------------------------------------
2820
2821 void* ImGui::MemAlloc(size_t size)
2822 {
2823 GImAllocatorActiveAllocationsCount++;
2824 return GImAllocatorAllocFunc(size, GImAllocatorUserData);
2825 }
2826
2827 void ImGui::MemFree(void* ptr)
2828 {
2829 if (ptr) GImAllocatorActiveAllocationsCount--;
2830 return GImAllocatorFreeFunc(ptr, GImAllocatorUserData);
2831 }
2832
2833 const char* ImGui::GetClipboardText()
2834 {
2835 return GImGui->IO.GetClipboardTextFn ? GImGui->IO.GetClipboardTextFn(GImGui->IO.ClipboardUserData) : "";
2836 }
2837
2838 void ImGui::SetClipboardText(const char* text)
2839 {
2840 if (GImGui->IO.SetClipboardTextFn)
2841 GImGui->IO.SetClipboardTextFn(GImGui->IO.ClipboardUserData, text);
2842 }
2843
2844 const char* ImGui::GetVersion()
2845 {
2846 return IMGUI_VERSION;
2847 }
2848
2849 // Internal state access - if you want to share ImGui state between modules (e.g. DLL) or allocate it yourself
2850 // 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
2851 ImGuiContext* ImGui::GetCurrentContext()
2852 {
2853 return GImGui;
2854 }
2855
2856 void ImGui::SetCurrentContext(ImGuiContext* ctx)
2857 {
2858 #ifdef IMGUI_SET_CURRENT_CONTEXT_FUNC
2859 IMGUI_SET_CURRENT_CONTEXT_FUNC(ctx); // For custom thread-based hackery you may want to have control over this.
2860 #else
2861 GImGui = ctx;
2862 #endif
2863 }
2864
2865 // Helper function to verify that the type sizes are matching between the calling file's compilation unit and imgui.cpp's compilation unit
2866 // If the user has inconsistent compilation settings, imgui configuration #define, packing pragma, etc. you may see different structures from what imgui.cpp sees which is highly problematic.
2867 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)
2868 {
2869 bool error = false;
2870 if (strcmp(version, IMGUI_VERSION)!=0) { error = true; IM_ASSERT(strcmp(version,IMGUI_VERSION)==0 && "Mismatch version string!"); }
2871 if (sz_io != sizeof(ImGuiIO)) { error = true; IM_ASSERT(sz_io == sizeof(ImGuiIO) && "Mismatched struct layout!"); }
2872 if (sz_style != sizeof(ImGuiStyle)) { error = true; IM_ASSERT(sz_style == sizeof(ImGuiStyle) && "Mismatched struct layout!"); }
2873 if (sz_vec2 != sizeof(ImVec2)) { error = true; IM_ASSERT(sz_vec2 == sizeof(ImVec2) && "Mismatched struct layout!"); }
2874 if (sz_vec4 != sizeof(ImVec4)) { error = true; IM_ASSERT(sz_vec4 == sizeof(ImVec4) && "Mismatched struct layout!"); }
2875 if (sz_vert != sizeof(ImDrawVert)) { error = true; IM_ASSERT(sz_vert == sizeof(ImDrawVert) && "Mismatched struct layout!"); }
2876 return !error;
2877 }
2878
2879 void ImGui::SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* user_data), void(*free_func)(void* ptr, void* user_data), void* user_data)
2880 {
2881 GImAllocatorAllocFunc = alloc_func;
2882 GImAllocatorFreeFunc = free_func;
2883 GImAllocatorUserData = user_data;
2884 }
2885
2886 ImGuiContext* ImGui::CreateContext(ImFontAtlas* shared_font_atlas)
2887 {
2888 ImGuiContext* ctx = IM_NEW(ImGuiContext)(shared_font_atlas);
2889 if (GImGui == NULL)
2890 SetCurrentContext(ctx);
2891 Initialize(ctx);
2892 return ctx;
2893 }
2894
2895 void ImGui::DestroyContext(ImGuiContext* ctx)
2896 {
2897 if (ctx == NULL)
2898 ctx = GImGui;
2899 Shutdown(ctx);
2900 if (GImGui == ctx)
2901 SetCurrentContext(NULL);
2902 IM_DELETE(ctx);
2903 }
2904
2905 ImGuiIO& ImGui::GetIO()
2906 {
2907 IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() or ImGui::SetCurrentContext()?");
2908 return GImGui->IO;
2909 }
2910
2911 ImGuiStyle& ImGui::GetStyle()
2912 {
2913 IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() or ImGui::SetCurrentContext()?");
2914 return GImGui->Style;
2915 }
2916
2917 // Same value as passed to the old io.RenderDrawListsFn function. Valid after Render() and until the next call to NewFrame()
2918 ImDrawData* ImGui::GetDrawData()
2919 {
2920 ImGuiContext& g = *GImGui;
2921 return g.DrawData.Valid ? &g.DrawData : NULL;
2922 }
2923
2924 double ImGui::GetTime()
2925 {
2926 return GImGui->Time;
2927 }
2928
2929 int ImGui::GetFrameCount()
2930 {
2931 return GImGui->FrameCount;
2932 }
2933
2934 ImDrawList* ImGui::GetOverlayDrawList()
2935 {
2936 return &GImGui->OverlayDrawList;
2937 }
2938
2939 ImDrawListSharedData* ImGui::GetDrawListSharedData()
2940 {
2941 return &GImGui->DrawListSharedData;
2942 }
2943
2944 // This needs to be called before we submit any widget (aka in or before Begin)
2945 void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit)
2946 {
2947 ImGuiContext& g = *GImGui;
2948 IM_ASSERT(window == g.NavWindow);
2949 bool init_for_nav = false;
2950 if (!(window->Flags & ImGuiWindowFlags_NoNavInputs))
2951 if (!(window->Flags & ImGuiWindowFlags_ChildWindow) || (window->Flags & ImGuiWindowFlags_Popup) || (window->NavLastIds[0] == 0) || force_reinit)
2952 init_for_nav = true;
2953 if (init_for_nav)
2954 {
2955 SetNavID(0, g.NavLayer);
2956 g.NavInitRequest = true;
2957 g.NavInitRequestFromMove = false;
2958 g.NavInitResultId = 0;
2959 g.NavInitResultRectRel = ImRect();
2960 NavUpdateAnyRequestFlag();
2961 }
2962 else
2963 {
2964 g.NavId = window->NavLastIds[0];
2965 }
2966 }
2967
2968 static ImVec2 NavCalcPreferredRefPos()
2969 {
2970 ImGuiContext& g = *GImGui;
2971 if (g.NavDisableHighlight || !g.NavDisableMouseHover || !g.NavWindow)
2972 return ImFloor(g.IO.MousePos);
2973
2974 // When navigation is active and mouse is disabled, decide on an arbitrary position around the bottom left of the currently navigated item
2975 const ImRect& rect_rel = g.NavWindow->NavRectRel[g.NavLayer];
2976 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()));
2977 ImRect visible_rect = GetViewportRect();
2978 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.
2979 }
2980
2981 static int FindWindowIndex(ImGuiWindow* window) // FIXME-OPT O(N)
2982 {
2983 ImGuiContext& g = *GImGui;
2984 for (int i = g.Windows.Size-1; i >= 0; i--)
2985 if (g.Windows[i] == window)
2986 return i;
2987 return -1;
2988 }
2989
2990 static ImGuiWindow* FindWindowNavigable(int i_start, int i_stop, int dir) // FIXME-OPT O(N)
2991 {
2992 ImGuiContext& g = *GImGui;
2993 for (int i = i_start; i >= 0 && i < g.Windows.Size && i != i_stop; i += dir)
2994 if (ImGui::IsWindowNavFocusable(g.Windows[i]))
2995 return g.Windows[i];
2996 return NULL;
2997 }
2998
2999 float ImGui::GetNavInputAmount(ImGuiNavInput n, ImGuiInputReadMode mode)
3000 {
3001 ImGuiContext& g = *GImGui;
3002 if (mode == ImGuiInputReadMode_Down)
3003 return g.IO.NavInputs[n]; // Instant, read analog input (0.0f..1.0f, as provided by user)
3004
3005 const float t = g.IO.NavInputsDownDuration[n];
3006 if (t < 0.0f && mode == ImGuiInputReadMode_Released) // Return 1.0f when just released, no repeat, ignore analog input.
3007 return (g.IO.NavInputsDownDurationPrev[n] >= 0.0f ? 1.0f : 0.0f);
3008 if (t < 0.0f)
3009 return 0.0f;
3010 if (mode == ImGuiInputReadMode_Pressed) // Return 1.0f when just pressed, no repeat, ignore analog input.
3011 return (t == 0.0f) ? 1.0f : 0.0f;
3012 if (mode == ImGuiInputReadMode_Repeat)
3013 return (float)CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 0.80f, g.IO.KeyRepeatRate * 0.80f);
3014 if (mode == ImGuiInputReadMode_RepeatSlow)
3015 return (float)CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 1.00f, g.IO.KeyRepeatRate * 2.00f);
3016 if (mode == ImGuiInputReadMode_RepeatFast)
3017 return (float)CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 0.80f, g.IO.KeyRepeatRate * 0.30f);
3018 return 0.0f;
3019 }
3020
3021 // Equivalent of IsKeyDown() for NavInputs[]
3022 static bool IsNavInputDown(ImGuiNavInput n)
3023 {
3024 return GImGui->IO.NavInputs[n] > 0.0f;
3025 }
3026
3027 // Equivalent of IsKeyPressed() for NavInputs[]
3028 static bool IsNavInputPressed(ImGuiNavInput n, ImGuiInputReadMode mode)
3029 {
3030 return ImGui::GetNavInputAmount(n, mode) > 0.0f;
3031 }
3032
3033 static bool IsNavInputPressedAnyOfTwo(ImGuiNavInput n1, ImGuiNavInput n2, ImGuiInputReadMode mode)
3034 {
3035 return (ImGui::GetNavInputAmount(n1, mode) + ImGui::GetNavInputAmount(n2, mode)) > 0.0f;
3036 }
3037
3038 ImVec2 ImGui::GetNavInputAmount2d(ImGuiNavDirSourceFlags dir_sources, ImGuiInputReadMode mode, float slow_factor, float fast_factor)
3039 {
3040 ImVec2 delta(0.0f, 0.0f);
3041 if (dir_sources & ImGuiNavDirSourceFlags_Keyboard)
3042 delta += ImVec2(GetNavInputAmount(ImGuiNavInput_KeyRight_, mode) - GetNavInputAmount(ImGuiNavInput_KeyLeft_, mode), GetNavInputAmount(ImGuiNavInput_KeyDown_, mode) - GetNavInputAmount(ImGuiNavInput_KeyUp_, mode));
3043 if (dir_sources & ImGuiNavDirSourceFlags_PadDPad)
3044 delta += ImVec2(GetNavInputAmount(ImGuiNavInput_DpadRight, mode) - GetNavInputAmount(ImGuiNavInput_DpadLeft, mode), GetNavInputAmount(ImGuiNavInput_DpadDown, mode) - GetNavInputAmount(ImGuiNavInput_DpadUp, mode));
3045 if (dir_sources & ImGuiNavDirSourceFlags_PadLStick)
3046 delta += ImVec2(GetNavInputAmount(ImGuiNavInput_LStickRight, mode) - GetNavInputAmount(ImGuiNavInput_LStickLeft, mode), GetNavInputAmount(ImGuiNavInput_LStickDown, mode) - GetNavInputAmount(ImGuiNavInput_LStickUp, mode));
3047 if (slow_factor != 0.0f && IsNavInputDown(ImGuiNavInput_TweakSlow))
3048 delta *= slow_factor;
3049 if (fast_factor != 0.0f && IsNavInputDown(ImGuiNavInput_TweakFast))
3050 delta *= fast_factor;
3051 return delta;
3052 }
3053
3054 static void NavUpdateWindowingHighlightWindow(int focus_change_dir)
3055 {
3056 ImGuiContext& g = *GImGui;
3057 IM_ASSERT(g.NavWindowingTarget);
3058 if (g.NavWindowingTarget->Flags & ImGuiWindowFlags_Modal)
3059 return;
3060
3061 const int i_current = FindWindowIndex(g.NavWindowingTarget);
3062 ImGuiWindow* window_target = FindWindowNavigable(i_current + focus_change_dir, -INT_MAX, focus_change_dir);
3063 if (!window_target)
3064 window_target = FindWindowNavigable((focus_change_dir < 0) ? (g.Windows.Size - 1) : 0, i_current, focus_change_dir);
3065 if (window_target) // Don't reset windowing target if there's a single window in the list
3066 g.NavWindowingTarget = g.NavWindowingTargetAnim = window_target;
3067 g.NavWindowingToggleLayer = false;
3068 }
3069
3070 // Window management mode (hold to: change focus/move/resize, tap to: toggle menu layer)
3071 static void ImGui::NavUpdateWindowing()
3072 {
3073 ImGuiContext& g = *GImGui;
3074 ImGuiWindow* apply_focus_window = NULL;
3075 bool apply_toggle_layer = false;
3076
3077 ImGuiWindow* modal_window = GetFrontMostPopupModal();
3078 if (modal_window != NULL)
3079 {
3080 g.NavWindowingTarget = NULL;
3081 return;
3082 }
3083
3084 // Fade out
3085 if (g.NavWindowingTargetAnim && g.NavWindowingTarget == NULL)
3086 {
3087 g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha - g.IO.DeltaTime * 10.0f, 0.0f);
3088 if (g.DimBgRatio <= 0.0f && g.NavWindowingHighlightAlpha <= 0.0f)
3089 g.NavWindowingTargetAnim = NULL;
3090 }
3091
3092 // Start CTRL-TAB or Square+L/R window selection
3093 bool start_windowing_with_gamepad = !g.NavWindowingTarget && IsNavInputPressed(ImGuiNavInput_Menu, ImGuiInputReadMode_Pressed);
3094 bool start_windowing_with_keyboard = !g.NavWindowingTarget && g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab) && (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard);
3095 if (start_windowing_with_gamepad || start_windowing_with_keyboard)
3096 if (ImGuiWindow* window = g.NavWindow ? g.NavWindow : FindWindowNavigable(g.Windows.Size - 1, -INT_MAX, -1))
3097 {
3098 g.NavWindowingTarget = g.NavWindowingTargetAnim = window;
3099 g.NavWindowingTimer = g.NavWindowingHighlightAlpha = 0.0f;
3100 g.NavWindowingToggleLayer = start_windowing_with_keyboard ? false : true;
3101 g.NavInputSource = start_windowing_with_keyboard ? ImGuiInputSource_NavKeyboard : ImGuiInputSource_NavGamepad;
3102 }
3103
3104 // Gamepad update
3105 g.NavWindowingTimer += g.IO.DeltaTime;
3106 if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_NavGamepad)
3107 {
3108 // 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
3109 g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f));
3110
3111 // Select window to focus
3112 const int focus_change_dir = (int)IsNavInputPressed(ImGuiNavInput_FocusPrev, ImGuiInputReadMode_RepeatSlow) - (int)IsNavInputPressed(ImGuiNavInput_FocusNext, ImGuiInputReadMode_RepeatSlow);
3113 if (focus_change_dir != 0)
3114 {
3115 NavUpdateWindowingHighlightWindow(focus_change_dir);
3116 g.NavWindowingHighlightAlpha = 1.0f;
3117 }
3118
3119 // Single press toggles NavLayer, long press with L/R apply actual focus on release (until then the window was merely rendered front-most)
3120 if (!IsNavInputDown(ImGuiNavInput_Menu))
3121 {
3122 g.NavWindowingToggleLayer &= (g.NavWindowingHighlightAlpha < 1.0f); // Once button was held long enough we don't consider it a tap-to-toggle-layer press anymore.
3123 if (g.NavWindowingToggleLayer && g.NavWindow)
3124 apply_toggle_layer = true;
3125 else if (!g.NavWindowingToggleLayer)
3126 apply_focus_window = g.NavWindowingTarget;
3127 g.NavWindowingTarget = NULL;
3128 }
3129 }
3130
3131 // Keyboard: Focus
3132 if (g.NavWindowingTarget && g.NavInputSource == ImGuiInputSource_NavKeyboard)
3133 {
3134 // Visuals only appears after a brief time after pressing TAB the first time, so that a fast CTRL+TAB doesn't add visual noise
3135 g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingTimer - NAV_WINDOWING_HIGHLIGHT_DELAY) / 0.05f)); // 1.0f
3136 if (IsKeyPressedMap(ImGuiKey_Tab, true))
3137 NavUpdateWindowingHighlightWindow(g.IO.KeyShift ? +1 : -1);
3138 if (!g.IO.KeyCtrl)
3139 apply_focus_window = g.NavWindowingTarget;
3140 }
3141
3142 // Keyboard: Press and Release ALT to toggle menu layer
3143 // 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
3144 if ((g.ActiveId == 0 || g.ActiveIdAllowOverlap) && IsNavInputPressed(ImGuiNavInput_KeyMenu_, ImGuiInputReadMode_Released))
3145 if (IsMousePosValid(&g.IO.MousePos) == IsMousePosValid(&g.IO.MousePosPrev))
3146 apply_toggle_layer = true;
3147
3148 // Move window
3149 if (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoMove))
3150 {
3151 ImVec2 move_delta;
3152 if (g.NavInputSource == ImGuiInputSource_NavKeyboard && !g.IO.KeyShift)
3153 move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard, ImGuiInputReadMode_Down);
3154 if (g.NavInputSource == ImGuiInputSource_NavGamepad)
3155 move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadLStick, ImGuiInputReadMode_Down);
3156 if (move_delta.x != 0.0f || move_delta.y != 0.0f)
3157 {
3158 const float NAV_MOVE_SPEED = 800.0f;
3159 const float move_speed = ImFloor(NAV_MOVE_SPEED * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y)); // FIXME: Doesn't code variable framerate very well
3160 g.NavWindowingTarget->RootWindow->Pos += move_delta * move_speed;
3161 g.NavDisableMouseHover = true;
3162 MarkIniSettingsDirty(g.NavWindowingTarget);
3163 }
3164 }
3165
3166 // Apply final focus
3167 if (apply_focus_window && (g.NavWindow == NULL || apply_focus_window != g.NavWindow->RootWindow))
3168 {
3169 g.NavDisableHighlight = false;
3170 g.NavDisableMouseHover = true;
3171 apply_focus_window = NavRestoreLastChildNavWindow(apply_focus_window);
3172 ClosePopupsOverWindow(apply_focus_window);
3173 FocusWindow(apply_focus_window);
3174 if (apply_focus_window->NavLastIds[0] == 0)
3175 NavInitWindow(apply_focus_window, false);
3176
3177 // If the window only has a menu layer, select it directly
3178 if (apply_focus_window->DC.NavLayerActiveMask == (1 << 1))
3179 g.NavLayer = 1;
3180 }
3181 if (apply_focus_window)
3182 g.NavWindowingTarget = NULL;
3183
3184 // Apply menu/layer toggle
3185 if (apply_toggle_layer && g.NavWindow)
3186 {
3187 // Move to parent menu if necessary
3188 ImGuiWindow* new_nav_window = g.NavWindow;
3189 while ((new_nav_window->DC.NavLayerActiveMask & (1 << 1)) == 0 && (new_nav_window->Flags & ImGuiWindowFlags_ChildWindow) != 0 && (new_nav_window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0)
3190 new_nav_window = new_nav_window->ParentWindow;
3191
3192 if (new_nav_window != g.NavWindow)
3193 {
3194 ImGuiWindow* old_nav_window = g.NavWindow;
3195 FocusWindow(new_nav_window);
3196 new_nav_window->NavLastChildNavWindow = old_nav_window;
3197 }
3198 g.NavDisableHighlight = false;
3199 g.NavDisableMouseHover = true;
3200 NavRestoreLayer((g.NavWindow->DC.NavLayerActiveMask & (1 << 1)) ? (g.NavLayer ^ 1) : 0);
3201 }
3202 }
3203
3204 // Window has already passed the IsWindowNavFocusable()
3205 static const char* GetFallbackWindowNameForWindowingList(ImGuiWindow* window)
3206 {
3207 if (window->Flags & ImGuiWindowFlags_Popup)
3208 return "(Popup)";
3209 if ((window->Flags & ImGuiWindowFlags_MenuBar) && strcmp(window->Name, "##MainMenuBar") == 0)
3210 return "(Main menu bar)";
3211 return "(Untitled)";
3212 }
3213
3214 // Overlay displayed when using CTRL+TAB. Called by EndFrame().
3215 void ImGui::NavUpdateWindowingList()
3216 {
3217 ImGuiContext& g = *GImGui;
3218 IM_ASSERT(g.NavWindowingTarget != NULL);
3219
3220 if (g.NavWindowingTimer < NAV_WINDOWING_LIST_APPEAR_DELAY)
3221 return;
3222
3223 if (g.NavWindowingList == NULL)
3224 g.NavWindowingList = FindWindowByName("###NavWindowingList");
3225 SetNextWindowSizeConstraints(ImVec2(g.IO.DisplaySize.x * 0.20f, g.IO.DisplaySize.y * 0.20f), ImVec2(FLT_MAX, FLT_MAX));
3226 SetNextWindowPos(g.IO.DisplaySize * 0.5f, ImGuiCond_Always, ImVec2(0.5f, 0.5f));
3227 PushStyleVar(ImGuiStyleVar_WindowPadding, g.Style.WindowPadding * 2.0f);
3228 Begin("###NavWindowingList", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_AlwaysAutoResize);
3229 for (int n = g.Windows.Size - 1; n >= 0; n--)
3230 {
3231 ImGuiWindow* window = g.Windows[n];
3232 if (!IsWindowNavFocusable(window))
3233 continue;
3234 const char* label = window->Name;
3235 if (label == FindRenderedTextEnd(label))
3236 label = GetFallbackWindowNameForWindowingList(window);
3237 Selectable(label, g.NavWindowingTarget == window);
3238 }
3239 End();
3240 PopStyleVar();
3241 }
3242
3243 // Scroll to keep newly navigated item fully into view
3244 // NB: We modify rect_rel by the amount we scrolled for, so it is immediately updated.
3245 static void NavScrollToBringItemIntoView(ImGuiWindow* window, const ImRect& item_rect)
3246 {
3247 ImRect window_rect(window->InnerMainRect.Min - ImVec2(1, 1), window->InnerMainRect.Max + ImVec2(1, 1));
3248 //g.OverlayDrawList.AddRect(window_rect.Min, window_rect.Max, IM_COL32_WHITE); // [DEBUG]
3249 if (window_rect.Contains(item_rect))
3250 return;
3251
3252 ImGuiContext& g = *GImGui;
3253 if (window->ScrollbarX && item_rect.Min.x < window_rect.Min.x)
3254 {
3255 window->ScrollTarget.x = item_rect.Min.x - window->Pos.x + window->Scroll.x - g.Style.ItemSpacing.x;
3256 window->ScrollTargetCenterRatio.x = 0.0f;
3257 }
3258 else if (window->ScrollbarX && item_rect.Max.x >= window_rect.Max.x)
3259 {
3260 window->ScrollTarget.x = item_rect.Max.x - window->Pos.x + window->Scroll.x + g.Style.ItemSpacing.x;
3261 window->ScrollTargetCenterRatio.x = 1.0f;
3262 }
3263 if (item_rect.Min.y < window_rect.Min.y)
3264 {
3265 window->ScrollTarget.y = item_rect.Min.y - window->Pos.y + window->Scroll.y - g.Style.ItemSpacing.y;
3266 window->ScrollTargetCenterRatio.y = 0.0f;
3267 }
3268 else if (item_rect.Max.y >= window_rect.Max.y)
3269 {
3270 window->ScrollTarget.y = item_rect.Max.y - window->Pos.y + window->Scroll.y + g.Style.ItemSpacing.y;
3271 window->ScrollTargetCenterRatio.y = 1.0f;
3272 }
3273 }
3274
3275 static void ImGui::NavUpdate()
3276 {
3277 ImGuiContext& g = *GImGui;
3278 g.IO.WantSetMousePos = false;
3279
3280 #if 0
3281 if (g.NavScoringCount > 0) printf("[%05d] NavScoringCount %d for '%s' layer %d (Init:%d, Move:%d)\n", g.FrameCount, g.NavScoringCount, g.NavWindow ? g.NavWindow->Name : "NULL", g.NavLayer, g.NavInitRequest || g.NavInitResultId != 0, g.NavMoveRequest);
3282 #endif
3283
3284 bool nav_keyboard_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) != 0;
3285 bool nav_gamepad_active = (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (g.IO.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
3286
3287 // Set input source as Gamepad when buttons are pressed before we map Keyboard (some features differs when used with Gamepad vs Keyboard)
3288 if (nav_gamepad_active)
3289 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)
3290 g.NavInputSource = ImGuiInputSource_NavGamepad;
3291
3292 // Update Keyboard->Nav inputs mapping
3293 if (nav_keyboard_active)
3294 {
3295 #define NAV_MAP_KEY(_KEY, _NAV_INPUT) if (IsKeyDown(g.IO.KeyMap[_KEY])) { g.IO.NavInputs[_NAV_INPUT] = 1.0f; g.NavInputSource = ImGuiInputSource_NavKeyboard; }
3296 NAV_MAP_KEY(ImGuiKey_Space, ImGuiNavInput_Activate );
3297 NAV_MAP_KEY(ImGuiKey_Enter, ImGuiNavInput_Input );
3298 NAV_MAP_KEY(ImGuiKey_Escape, ImGuiNavInput_Cancel );
3299 NAV_MAP_KEY(ImGuiKey_LeftArrow, ImGuiNavInput_KeyLeft_ );
3300 NAV_MAP_KEY(ImGuiKey_RightArrow,ImGuiNavInput_KeyRight_);
3301 NAV_MAP_KEY(ImGuiKey_UpArrow, ImGuiNavInput_KeyUp_ );
3302 NAV_MAP_KEY(ImGuiKey_DownArrow, ImGuiNavInput_KeyDown_ );
3303 if (g.IO.KeyCtrl) g.IO.NavInputs[ImGuiNavInput_TweakSlow] = 1.0f;
3304 if (g.IO.KeyShift) g.IO.NavInputs[ImGuiNavInput_TweakFast] = 1.0f;
3305 if (g.IO.KeyAlt) g.IO.NavInputs[ImGuiNavInput_KeyMenu_] = 1.0f;
3306 #undef NAV_MAP_KEY
3307 }
3308
3309 memcpy(g.IO.NavInputsDownDurationPrev, g.IO.NavInputsDownDuration, sizeof(g.IO.NavInputsDownDuration));
3310 for (int i = 0; i < IM_ARRAYSIZE(g.IO.NavInputs); i++)
3311 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;
3312
3313 // Process navigation init request (select first/default focus)
3314 if (g.NavInitResultId != 0 && (!g.NavDisableHighlight || g.NavInitRequestFromMove))
3315 {
3316 // Apply result from previous navigation init request (will typically select the first item, unless SetItemDefaultFocus() has been called)
3317 IM_ASSERT(g.NavWindow);
3318 if (g.NavInitRequestFromMove)
3319 SetNavIDWithRectRel(g.NavInitResultId, g.NavLayer, g.NavInitResultRectRel);
3320 else
3321 SetNavID(g.NavInitResultId, g.NavLayer);
3322 g.NavWindow->NavRectRel[g.NavLayer] = g.NavInitResultRectRel;
3323 }
3324 g.NavInitRequest = false;
3325 g.NavInitRequestFromMove = false;
3326 g.NavInitResultId = 0;
3327 g.NavJustMovedToId = 0;
3328
3329 // Process navigation move request
3330 if (g.NavMoveRequest && (g.NavMoveResultLocal.ID != 0 || g.NavMoveResultOther.ID != 0))
3331 {
3332 // Select which result to use
3333 ImGuiNavMoveResult* result = (g.NavMoveResultLocal.ID != 0) ? &g.NavMoveResultLocal : &g.NavMoveResultOther;
3334
3335 // PageUp/PageDown behavior first jumps to the bottom/top mostly visible item, _otherwise_ use the result from the previous/next page.
3336 if (g.NavMoveRequestFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet)
3337 if (g.NavMoveResultLocalVisibleSet.ID != 0 && g.NavMoveResultLocalVisibleSet.ID != g.NavId)
3338 result = &g.NavMoveResultLocalVisibleSet;
3339
3340 // Maybe entering a flattened child from the outside? In this case solve the tie using the regular scoring rules.
3341 if (result != &g.NavMoveResultOther && g.NavMoveResultOther.ID != 0 && g.NavMoveResultOther.Window->ParentWindow == g.NavWindow)
3342 if ((g.NavMoveResultOther.DistBox < result->DistBox) || (g.NavMoveResultOther.DistBox == result->DistBox && g.NavMoveResultOther.DistCenter < result->DistCenter))
3343 result = &g.NavMoveResultOther;
3344 IM_ASSERT(g.NavWindow && result->Window);
3345
3346 // Scroll to keep newly navigated item fully into view.
3347 if (g.NavLayer == 0)
3348 {
3349 ImRect rect_abs = ImRect(result->RectRel.Min + result->Window->Pos, result->RectRel.Max + result->Window->Pos);
3350 NavScrollToBringItemIntoView(result->Window, rect_abs);
3351
3352 // Estimate upcoming scroll so we can offset our result position so mouse position can be applied immediately after in NavUpdate()
3353 ImVec2 next_scroll = CalcNextScrollFromScrollTargetAndClamp(result->Window, false);
3354 ImVec2 delta_scroll = result->Window->Scroll - next_scroll;
3355 result->RectRel.Translate(delta_scroll);
3356
3357 // Also scroll parent window to keep us into view if necessary (we could/should technically recurse back the whole the parent hierarchy).
3358 if (result->Window->Flags & ImGuiWindowFlags_ChildWindow)
3359 NavScrollToBringItemIntoView(result->Window->ParentWindow, ImRect(rect_abs.Min + delta_scroll, rect_abs.Max + delta_scroll));
3360 }
3361
3362 // Apply result from previous frame navigation directional move request
3363 ClearActiveID();
3364 g.NavWindow = result->Window;
3365 SetNavIDWithRectRel(result->ID, g.NavLayer, result->RectRel);
3366 g.NavJustMovedToId = result->ID;
3367 g.NavMoveFromClampedRefRect = false;
3368 }
3369
3370 // When a forwarded move request failed, we restore the highlight that we disabled during the forward frame
3371 if (g.NavMoveRequestForward == ImGuiNavForward_ForwardActive)
3372 {
3373 IM_ASSERT(g.NavMoveRequest);
3374 if (g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0)
3375 g.NavDisableHighlight = false;
3376 g.NavMoveRequestForward = ImGuiNavForward_None;
3377 }
3378
3379 // Apply application mouse position movement, after we had a chance to process move request result.
3380 if (g.NavMousePosDirty && g.NavIdIsAlive)
3381 {
3382 // Set mouse position given our knowledge of the navigated item position from last frame
3383 if ((g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos) && (g.IO.BackendFlags & ImGuiBackendFlags_HasSetMousePos))
3384 {
3385 if (!g.NavDisableHighlight && g.NavDisableMouseHover && g.NavWindow)
3386 {
3387 g.IO.MousePos = g.IO.MousePosPrev = NavCalcPreferredRefPos();
3388 g.IO.WantSetMousePos = true;
3389 }
3390 }
3391 g.NavMousePosDirty = false;
3392 }
3393 g.NavIdIsAlive = false;
3394 g.NavJustTabbedId = 0;
3395 IM_ASSERT(g.NavLayer == 0 || g.NavLayer == 1);
3396
3397 // 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
3398 if (g.NavWindow)
3399 NavSaveLastChildNavWindow(g.NavWindow);
3400 if (g.NavWindow && g.NavWindow->NavLastChildNavWindow != NULL && g.NavLayer == 0)
3401 g.NavWindow->NavLastChildNavWindow = NULL;
3402
3403 NavUpdateWindowing();
3404
3405 // Set output flags for user application
3406 g.IO.NavActive = (nav_keyboard_active || nav_gamepad_active) && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs);
3407 g.IO.NavVisible = (g.IO.NavActive && g.NavId != 0 && !g.NavDisableHighlight) || (g.NavWindowingTarget != NULL) || g.NavInitRequest;
3408
3409 // Process NavCancel input (to close a popup, get back to parent, clear focus)
3410 if (IsNavInputPressed(ImGuiNavInput_Cancel, ImGuiInputReadMode_Pressed))
3411 {
3412 if (g.ActiveId != 0)
3413 {
3414 ClearActiveID();
3415 }
3416 else if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow) && !(g.NavWindow->Flags & ImGuiWindowFlags_Popup) && g.NavWindow->ParentWindow)
3417 {
3418 // Exit child window
3419 ImGuiWindow* child_window = g.NavWindow;
3420 ImGuiWindow* parent_window = g.NavWindow->ParentWindow;
3421 IM_ASSERT(child_window->ChildId != 0);
3422 FocusWindow(parent_window);
3423 SetNavID(child_window->ChildId, 0);
3424 g.NavIdIsAlive = false;
3425 if (g.NavDisableMouseHover)
3426 g.NavMousePosDirty = true;
3427 }
3428 else if (g.OpenPopupStack.Size > 0)
3429 {
3430 // Close open popup/menu
3431 if (!(g.OpenPopupStack.back().Window->Flags & ImGuiWindowFlags_Modal))
3432 ClosePopupToLevel(g.OpenPopupStack.Size - 1);
3433 }
3434 else if (g.NavLayer != 0)
3435 {
3436 // Leave the "menu" layer
3437 NavRestoreLayer(0);
3438 }
3439 else
3440 {
3441 // Clear NavLastId for popups but keep it for regular child window so we can leave one and come back where we were
3442 if (g.NavWindow && ((g.NavWindow->Flags & ImGuiWindowFlags_Popup) || !(g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow)))
3443 g.NavWindow->NavLastIds[0] = 0;
3444 g.NavId = 0;
3445 }
3446 }
3447
3448 // Process manual activation request
3449 g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavInputId = 0;
3450 if (g.NavId != 0 && !g.NavDisableHighlight && !g.NavWindowingTarget && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))
3451 {
3452 bool activate_down = IsNavInputDown(ImGuiNavInput_Activate);
3453 bool activate_pressed = activate_down && IsNavInputPressed(ImGuiNavInput_Activate, ImGuiInputReadMode_Pressed);
3454 if (g.ActiveId == 0 && activate_pressed)
3455 g.NavActivateId = g.NavId;
3456 if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_down)
3457 g.NavActivateDownId = g.NavId;
3458 if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_pressed)
3459 g.NavActivatePressedId = g.NavId;
3460 if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && IsNavInputPressed(ImGuiNavInput_Input, ImGuiInputReadMode_Pressed))
3461 g.NavInputId = g.NavId;
3462 }
3463 if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))
3464 g.NavDisableHighlight = true;
3465 if (g.NavActivateId != 0)
3466 IM_ASSERT(g.NavActivateDownId == g.NavActivateId);
3467 g.NavMoveRequest = false;
3468
3469 // Process programmatic activation request
3470 if (g.NavNextActivateId != 0)
3471 g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavInputId = g.NavNextActivateId;
3472 g.NavNextActivateId = 0;
3473
3474 // Initiate directional inputs request
3475 const int allowed_dir_flags = (g.ActiveId == 0) ? ~0 : g.ActiveIdAllowNavDirFlags;
3476 if (g.NavMoveRequestForward == ImGuiNavForward_None)
3477 {
3478 g.NavMoveDir = ImGuiDir_None;
3479 g.NavMoveRequestFlags = 0;
3480 if (g.NavWindow && !g.NavWindowingTarget && allowed_dir_flags && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))
3481 {
3482 if ((allowed_dir_flags & (1<<ImGuiDir_Left)) && IsNavInputPressedAnyOfTwo(ImGuiNavInput_DpadLeft, ImGuiNavInput_KeyLeft_, ImGuiInputReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Left;
3483 if ((allowed_dir_flags & (1<<ImGuiDir_Right)) && IsNavInputPressedAnyOfTwo(ImGuiNavInput_DpadRight,ImGuiNavInput_KeyRight_,ImGuiInputReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Right;
3484 if ((allowed_dir_flags & (1<<ImGuiDir_Up)) && IsNavInputPressedAnyOfTwo(ImGuiNavInput_DpadUp, ImGuiNavInput_KeyUp_, ImGuiInputReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Up;
3485 if ((allowed_dir_flags & (1<<ImGuiDir_Down)) && IsNavInputPressedAnyOfTwo(ImGuiNavInput_DpadDown, ImGuiNavInput_KeyDown_, ImGuiInputReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Down;
3486 }
3487 g.NavMoveClipDir = g.NavMoveDir;
3488 }
3489 else
3490 {
3491 // 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)
3492 // (Preserve g.NavMoveRequestFlags, g.NavMoveClipDir which were set by the NavMoveRequestForward() function)
3493 IM_ASSERT(g.NavMoveDir != ImGuiDir_None && g.NavMoveClipDir != ImGuiDir_None);
3494 IM_ASSERT(g.NavMoveRequestForward == ImGuiNavForward_ForwardQueued);
3495 g.NavMoveRequestForward = ImGuiNavForward_ForwardActive;
3496 }
3497
3498 // PageUp/PageDown scroll
3499 float nav_scoring_rect_offset_y = 0.0f;
3500 if (nav_keyboard_active && g.NavMoveDir == ImGuiDir_None && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.NavWindowingTarget && g.NavLayer == 0)
3501 {
3502 ImGuiWindow* window = g.NavWindow;
3503 bool page_up_held = IsKeyDown(g.IO.KeyMap[ImGuiKey_PageUp]) && (allowed_dir_flags & (1 << ImGuiDir_Up));
3504 bool page_down_held = IsKeyDown(g.IO.KeyMap[ImGuiKey_PageDown]) && (allowed_dir_flags & (1 << ImGuiDir_Down));
3505 if ((page_up_held && !page_down_held) || (page_down_held && !page_up_held))
3506 {
3507 if (window->DC.NavLayerActiveMask == 0x00 && window->DC.NavHasScroll)
3508 {
3509 // Fallback manual-scroll when window has no navigable item
3510 if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageUp], true))
3511 SetWindowScrollY(window, window->Scroll.y - window->InnerClipRect.GetHeight());
3512 else if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageDown], true))
3513 SetWindowScrollY(window, window->Scroll.y + window->InnerClipRect.GetHeight());
3514 }
3515 else
3516 {
3517 const ImRect& nav_rect_rel = window->NavRectRel[g.NavLayer];
3518 const float page_offset_y = ImMax(0.0f, window->InnerClipRect.GetHeight() - window->CalcFontSize() * 1.0f + nav_rect_rel.GetHeight());
3519 if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageUp], true))
3520 {
3521 nav_scoring_rect_offset_y = -page_offset_y;
3522 g.NavMoveDir = ImGuiDir_Down; // Because our scoring rect is offset, we intentionally request the opposite direction (so we can always land on the last item)
3523 g.NavMoveClipDir = ImGuiDir_Up;
3524 g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet;
3525 }
3526 else if (IsKeyPressed(g.IO.KeyMap[ImGuiKey_PageDown], true))
3527 {
3528 nav_scoring_rect_offset_y = +page_offset_y;
3529 g.NavMoveDir = ImGuiDir_Up; // Because our scoring rect is offset, we intentionally request the opposite direction (so we can always land on the last item)
3530 g.NavMoveClipDir = ImGuiDir_Down;
3531 g.NavMoveRequestFlags = ImGuiNavMoveFlags_AllowCurrentNavId | ImGuiNavMoveFlags_AlsoScoreVisibleSet;
3532 }
3533 }
3534 }
3535 }
3536
3537 if (g.NavMoveDir != ImGuiDir_None)
3538 {
3539 g.NavMoveRequest = true;
3540 g.NavMoveDirLast = g.NavMoveDir;
3541 }
3542
3543 // 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
3544 if (g.NavMoveRequest && g.NavId == 0)
3545 {
3546 g.NavInitRequest = g.NavInitRequestFromMove = true;
3547 g.NavInitResultId = 0;
3548 g.NavDisableHighlight = false;
3549 }
3550
3551 NavUpdateAnyRequestFlag();
3552
3553 // Scrolling
3554 if (g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.NavWindowingTarget)
3555 {
3556 // *Fallback* manual-scroll with Nav directional keys when window has no navigable item
3557 ImGuiWindow* window = g.NavWindow;
3558 const float scroll_speed = ImFloor(window->CalcFontSize() * 100 * g.IO.DeltaTime + 0.5f); // We need round the scrolling speed because sub-pixel scroll isn't reliably supported.
3559 if (window->DC.NavLayerActiveMask == 0x00 && window->DC.NavHasScroll && g.NavMoveRequest)
3560 {
3561 if (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right)
3562 SetWindowScrollX(window, ImFloor(window->Scroll.x + ((g.NavMoveDir == ImGuiDir_Left) ? -1.0f : +1.0f) * scroll_speed));
3563 if (g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down)
3564 SetWindowScrollY(window, ImFloor(window->Scroll.y + ((g.NavMoveDir == ImGuiDir_Up) ? -1.0f : +1.0f) * scroll_speed));
3565 }
3566
3567 // *Normal* Manual scroll with NavScrollXXX keys
3568 // Next movement request will clamp the NavId reference rectangle to the visible area, so navigation will resume within those bounds.
3569 ImVec2 scroll_dir = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadLStick, ImGuiInputReadMode_Down, 1.0f/10.0f, 10.0f);
3570 if (scroll_dir.x != 0.0f && window->ScrollbarX)
3571 {
3572 SetWindowScrollX(window, ImFloor(window->Scroll.x + scroll_dir.x * scroll_speed));
3573 g.NavMoveFromClampedRefRect = true;
3574 }
3575 if (scroll_dir.y != 0.0f)
3576 {
3577 SetWindowScrollY(window, ImFloor(window->Scroll.y + scroll_dir.y * scroll_speed));
3578 g.NavMoveFromClampedRefRect = true;
3579 }
3580 }
3581
3582 // Reset search results
3583 g.NavMoveResultLocal.Clear();
3584 g.NavMoveResultLocalVisibleSet.Clear();
3585 g.NavMoveResultOther.Clear();
3586
3587 // 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
3588 if (g.NavMoveRequest && g.NavMoveFromClampedRefRect && g.NavLayer == 0)
3589 {
3590 ImGuiWindow* window = g.NavWindow;
3591 ImRect window_rect_rel(window->InnerMainRect.Min - window->Pos - ImVec2(1,1), window->InnerMainRect.Max - window->Pos + ImVec2(1,1));
3592 if (!window_rect_rel.Contains(window->NavRectRel[g.NavLayer]))
3593 {
3594 float pad = window->CalcFontSize() * 0.5f;
3595 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
3596 window->NavRectRel[g.NavLayer].ClipWith(window_rect_rel);
3597 g.NavId = 0;
3598 }
3599 g.NavMoveFromClampedRefRect = false;
3600 }
3601
3602 // 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)
3603 ImRect nav_rect_rel = (g.NavWindow && !g.NavWindow->NavRectRel[g.NavLayer].IsInverted()) ? g.NavWindow->NavRectRel[g.NavLayer] : ImRect(0,0,0,0);
3604 g.NavScoringRectScreen = g.NavWindow ? ImRect(g.NavWindow->Pos + nav_rect_rel.Min, g.NavWindow->Pos + nav_rect_rel.Max) : GetViewportRect();
3605 g.NavScoringRectScreen.TranslateY(nav_scoring_rect_offset_y);
3606 g.NavScoringRectScreen.Min.x = ImMin(g.NavScoringRectScreen.Min.x + 1.0f, g.NavScoringRectScreen.Max.x);
3607 g.NavScoringRectScreen.Max.x = g.NavScoringRectScreen.Min.x;
3608 IM_ASSERT(!g.NavScoringRectScreen.IsInverted()); // Ensure if we have a finite, non-inverted bounding box here will allows us to remove extraneous ImFabs() calls in NavScoreItem().
3609 //g.OverlayDrawList.AddRect(g.NavScoringRectScreen.Min, g.NavScoringRectScreen.Max, IM_COL32(255,200,0,255)); // [DEBUG]
3610 g.NavScoringCount = 0;
3611 #if IMGUI_DEBUG_NAV_RECTS
3612 if (g.NavWindow) { for (int layer = 0; layer < 2; layer++) GetOverlayDrawList()->AddRect(g.NavWindow->Pos + g.NavWindow->NavRectRel[layer].Min, g.NavWindow->Pos + g.NavWindow->NavRectRel[layer].Max, IM_COL32(255,200,0,255)); } // [DEBUG]
3613 if (g.NavWindow) { ImU32 col = (g.NavWindow->HiddenFrames == 0) ? IM_COL32(255,0,255,255) : IM_COL32(255,0,0,255); ImVec2 p = NavCalcPreferredRefPos(); char buf[32]; ImFormatString(buf, 32, "%d", g.NavLayer); g.OverlayDrawList.AddCircleFilled(p, 3.0f, col); g.OverlayDrawList.AddText(NULL, 13.0f, p + ImVec2(8,-4), col, buf); }
3614 #endif
3615 }
3616
3617 void ImGui::StartMouseMovingWindow(ImGuiWindow* window)
3618 {
3619 // Set ActiveId even if the _NoMove flag is set. Without it, dragging away from a window with _NoMove would activate hover on other windows.
3620 ImGuiContext& g = *GImGui;
3621 FocusWindow(window);
3622 SetActiveID(window->MoveId, window);
3623 g.NavDisableHighlight = true;
3624 g.ActiveIdClickOffset = g.IO.MousePos - window->RootWindow->Pos;
3625 if (!(window->Flags & ImGuiWindowFlags_NoMove) && !(window->RootWindow->Flags & ImGuiWindowFlags_NoMove))
3626 g.MovingWindow = window;
3627 }
3628
3629 // Handle mouse moving window
3630 // Note: moving window with the navigation keys (Square + d-pad / CTRL+TAB + Arrows) are processed in NavUpdateWindowing()
3631 void ImGui::UpdateMouseMovingWindow()
3632 {
3633 ImGuiContext& g = *GImGui;
3634 if (g.MovingWindow != NULL)
3635 {
3636 // We actually want to move the root window. g.MovingWindow == window we clicked on (could be a child window).
3637 // We track it to preserve Focus and so that generally ActiveIdWindow == MovingWindow and ActiveId == MovingWindow->MoveId for consistency.
3638 KeepAliveID(g.ActiveId);
3639 IM_ASSERT(g.MovingWindow && g.MovingWindow->RootWindow);
3640 ImGuiWindow* moving_window = g.MovingWindow->RootWindow;
3641 if (g.IO.MouseDown[0] && IsMousePosValid(&g.IO.MousePos))
3642 {
3643 ImVec2 pos = g.IO.MousePos - g.ActiveIdClickOffset;
3644 if (moving_window->Pos.x != pos.x || moving_window->Pos.y != pos.y)
3645 {
3646 MarkIniSettingsDirty(moving_window);
3647 SetWindowPos(moving_window, pos, ImGuiCond_Always);
3648 }
3649 FocusWindow(g.MovingWindow);
3650 }
3651 else
3652 {
3653 ClearActiveID();
3654 g.MovingWindow = NULL;
3655 }
3656 }
3657 else
3658 {
3659 // When clicking/dragging from a window that has the _NoMove flag, we still set the ActiveId in order to prevent hovering others.
3660 if (g.ActiveIdWindow && g.ActiveIdWindow->MoveId == g.ActiveId)
3661 {
3662 KeepAliveID(g.ActiveId);
3663 if (!g.IO.MouseDown[0])
3664 ClearActiveID();
3665 }
3666 }
3667 }
3668
3669 static bool IsWindowActiveAndVisible(ImGuiWindow* window)
3670 {
3671 return (window->Active) && (!window->Hidden);
3672 }
3673
3674 static void ImGui::UpdateMouseInputs()
3675 {
3676 ImGuiContext& g = *GImGui;
3677
3678 // If mouse just appeared or disappeared (usually denoted by -FLT_MAX component, but in reality we test for -256000.0f) we cancel out movement in MouseDelta
3679 if (IsMousePosValid(&g.IO.MousePos) && IsMousePosValid(&g.IO.MousePosPrev))
3680 g.IO.MouseDelta = g.IO.MousePos - g.IO.MousePosPrev;
3681 else
3682 g.IO.MouseDelta = ImVec2(0.0f, 0.0f);
3683 if (g.IO.MouseDelta.x != 0.0f || g.IO.MouseDelta.y != 0.0f)
3684 g.NavDisableMouseHover = false;
3685
3686 g.IO.MousePosPrev = g.IO.MousePos;
3687 for (int i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++)
3688 {
3689 g.IO.MouseClicked[i] = g.IO.MouseDown[i] && g.IO.MouseDownDuration[i] < 0.0f;
3690 g.IO.MouseReleased[i] = !g.IO.MouseDown[i] && g.IO.MouseDownDuration[i] >= 0.0f;
3691 g.IO.MouseDownDurationPrev[i] = g.IO.MouseDownDuration[i];
3692 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;
3693 g.IO.MouseDoubleClicked[i] = false;
3694 if (g.IO.MouseClicked[i])
3695 {
3696 if ((float)(g.Time - g.IO.MouseClickedTime[i]) < g.IO.MouseDoubleClickTime)
3697 {
3698 ImVec2 delta_from_click_pos = IsMousePosValid(&g.IO.MousePos) ? (g.IO.MousePos - g.IO.MouseClickedPos[i]) : ImVec2(0.0f, 0.0f);
3699 if (ImLengthSqr(delta_from_click_pos) < g.IO.MouseDoubleClickMaxDist * g.IO.MouseDoubleClickMaxDist)
3700 g.IO.MouseDoubleClicked[i] = true;
3701 g.IO.MouseClickedTime[i] = -FLT_MAX; // so the third click isn't turned into a double-click
3702 }
3703 else
3704 {
3705 g.IO.MouseClickedTime[i] = g.Time;
3706 }
3707 g.IO.MouseClickedPos[i] = g.IO.MousePos;
3708 g.IO.MouseDragMaxDistanceAbs[i] = ImVec2(0.0f, 0.0f);
3709 g.IO.MouseDragMaxDistanceSqr[i] = 0.0f;
3710 }
3711 else if (g.IO.MouseDown[i])
3712 {
3713 // Maintain the maximum distance we reaching from the initial click position, which is used with dragging threshold
3714 ImVec2 delta_from_click_pos = IsMousePosValid(&g.IO.MousePos) ? (g.IO.MousePos - g.IO.MouseClickedPos[i]) : ImVec2(0.0f, 0.0f);
3715 g.IO.MouseDragMaxDistanceSqr[i] = ImMax(g.IO.MouseDragMaxDistanceSqr[i], ImLengthSqr(delta_from_click_pos));
3716 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);
3717 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);
3718 }
3719 if (g.IO.MouseClicked[i]) // Clicking any mouse button reactivate mouse hovering which may have been deactivated by gamepad/keyboard navigation
3720 g.NavDisableMouseHover = false;
3721 }
3722 }
3723
3724 void ImGui::UpdateMouseWheel()
3725 {
3726 ImGuiContext& g = *GImGui;
3727 if (!g.HoveredWindow || g.HoveredWindow->Collapsed)
3728 return;
3729 if (g.IO.MouseWheel == 0.0f && g.IO.MouseWheelH == 0.0f)
3730 return;
3731
3732 // If a child window has the ImGuiWindowFlags_NoScrollWithMouse flag, we give a chance to scroll its parent (unless either ImGuiWindowFlags_NoInputs or ImGuiWindowFlags_NoScrollbar are also set).
3733 ImGuiWindow* window = g.HoveredWindow;
3734 ImGuiWindow* scroll_window = window;
3735 while ((scroll_window->Flags & ImGuiWindowFlags_ChildWindow) && (scroll_window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(scroll_window->Flags & ImGuiWindowFlags_NoScrollbar) && !(scroll_window->Flags & ImGuiWindowFlags_NoInputs) && scroll_window->ParentWindow)
3736 scroll_window = scroll_window->ParentWindow;
3737 const bool scroll_allowed = !(scroll_window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(scroll_window->Flags & ImGuiWindowFlags_NoInputs);
3738
3739 if (g.IO.MouseWheel != 0.0f)
3740 {
3741 if (g.IO.KeyCtrl && g.IO.FontAllowUserScaling)
3742 {
3743 // Zoom / Scale window
3744 const float new_font_scale = ImClamp(window->FontWindowScale + g.IO.MouseWheel * 0.10f, 0.50f, 2.50f);
3745 const float scale = new_font_scale / window->FontWindowScale;
3746 window->FontWindowScale = new_font_scale;
3747
3748 const ImVec2 offset = window->Size * (1.0f - scale) * (g.IO.MousePos - window->Pos) / window->Size;
3749 window->Pos += offset;
3750 window->Size *= scale;
3751 window->SizeFull *= scale;
3752 }
3753 else if (!g.IO.KeyCtrl && scroll_allowed)
3754 {
3755 // Mouse wheel vertical scrolling
3756 float scroll_amount = 5 * scroll_window->CalcFontSize();
3757 scroll_amount = (float)(int)ImMin(scroll_amount, (scroll_window->ContentsRegionRect.GetHeight() + scroll_window->WindowPadding.y * 2.0f) * 0.67f);
3758 SetWindowScrollY(scroll_window, scroll_window->Scroll.y - g.IO.MouseWheel * scroll_amount);
3759 }
3760 }
3761 if (g.IO.MouseWheelH != 0.0f && scroll_allowed && !g.IO.KeyCtrl)
3762 {
3763 // Mouse wheel horizontal scrolling (for hardware that supports it)
3764 float scroll_amount = scroll_window->CalcFontSize();
3765 SetWindowScrollX(scroll_window, scroll_window->Scroll.x - g.IO.MouseWheelH * scroll_amount);
3766 }
3767 }
3768
3769 // 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)
3770 void ImGui::UpdateHoveredWindowAndCaptureFlags()
3771 {
3772 ImGuiContext& g = *GImGui;
3773
3774 // Find the window hovered by mouse:
3775 // - Child windows can extend beyond the limit of their parent so we need to derive HoveredRootWindow from HoveredWindow.
3776 // - 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.
3777 // - 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.
3778 FindHoveredWindow();
3779
3780 // Modal windows prevents cursor from hovering behind them.
3781 ImGuiWindow* modal_window = GetFrontMostPopupModal();
3782 if (modal_window)
3783 if (g.HoveredRootWindow && !IsWindowChildOf(g.HoveredRootWindow, modal_window))
3784 g.HoveredRootWindow = g.HoveredWindow = NULL;
3785
3786 // Disabled mouse?
3787 if (g.IO.ConfigFlags & ImGuiConfigFlags_NoMouse)
3788 g.HoveredWindow = g.HoveredRootWindow = NULL;
3789
3790 // 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.
3791 int mouse_earliest_button_down = -1;
3792 bool mouse_any_down = false;
3793 for (int i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++)
3794 {
3795 if (g.IO.MouseClicked[i])
3796 g.IO.MouseDownOwned[i] = (g.HoveredWindow != NULL) || (!g.OpenPopupStack.empty());
3797 mouse_any_down |= g.IO.MouseDown[i];
3798 if (g.IO.MouseDown[i])
3799 if (mouse_earliest_button_down == -1 || g.IO.MouseClickedTime[i] < g.IO.MouseClickedTime[mouse_earliest_button_down])
3800 mouse_earliest_button_down = i;
3801 }
3802 const bool mouse_avail_to_imgui = (mouse_earliest_button_down == -1) || g.IO.MouseDownOwned[mouse_earliest_button_down];
3803
3804 // If mouse was first clicked outside of ImGui bounds we also cancel out hovering.
3805 // FIXME: For patterns of drag and drop across OS windows, we may need to rework/remove this test (first committed 311c0ca9 on 2015/02)
3806 const bool mouse_dragging_extern_payload = g.DragDropActive && (g.DragDropSourceFlags & ImGuiDragDropFlags_SourceExtern) != 0;
3807 if (!mouse_avail_to_imgui && !mouse_dragging_extern_payload)
3808 g.HoveredWindow = g.HoveredRootWindow = NULL;
3809
3810 // Update io.WantCaptureMouse for the user application (true = dispatch mouse info to imgui, false = dispatch mouse info to imgui + app)
3811 if (g.WantCaptureMouseNextFrame != -1)
3812 g.IO.WantCaptureMouse = (g.WantCaptureMouseNextFrame != 0);
3813 else
3814 g.IO.WantCaptureMouse = (mouse_avail_to_imgui && (g.HoveredWindow != NULL || mouse_any_down)) || (!g.OpenPopupStack.empty());
3815
3816 // Update io.WantCaptureKeyboard for the user application (true = dispatch keyboard info to imgui, false = dispatch keyboard info to imgui + app)
3817 if (g.WantCaptureKeyboardNextFrame != -1)
3818 g.IO.WantCaptureKeyboard = (g.WantCaptureKeyboardNextFrame != 0);
3819 else
3820 g.IO.WantCaptureKeyboard = (g.ActiveId != 0) || (modal_window != NULL);
3821 if (g.IO.NavActive && (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard) && !(g.IO.ConfigFlags & ImGuiConfigFlags_NavNoCaptureKeyboard))
3822 g.IO.WantCaptureKeyboard = true;
3823
3824 // Update io.WantTextInput flag, this is to allow systems without a keyboard (e.g. mobile, hand-held) to show a software keyboard if possible
3825 g.IO.WantTextInput = (g.WantTextInputNextFrame != -1) ? (g.WantTextInputNextFrame != 0) : false;
3826 }
3827
3828 void ImGui::NewFrame()
3829 {
3830 IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() or ImGui::SetCurrentContext()?");
3831 ImGuiContext& g = *GImGui;
3832
3833 // Check user data
3834 // (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)
3835 IM_ASSERT(g.Initialized);
3836 IM_ASSERT(g.IO.DeltaTime >= 0.0f && "Need a positive DeltaTime (zero is tolerated but will cause some timing issues)");
3837 IM_ASSERT(g.IO.DisplaySize.x >= 0.0f && g.IO.DisplaySize.y >= 0.0f && "Invalid DisplaySize value");
3838 IM_ASSERT(g.IO.Fonts->Fonts.Size > 0 && "Font Atlas not built. Did you call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8() ?");
3839 IM_ASSERT(g.IO.Fonts->Fonts[0]->IsLoaded() && "Font Atlas not built. Did you call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8() ?");
3840 IM_ASSERT(g.Style.CurveTessellationTol > 0.0f && "Invalid style setting");
3841 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)");
3842 IM_ASSERT((g.FrameCount == 0 || g.FrameCountEnded == g.FrameCount) && "Forgot to call Render() or EndFrame() at the end of the previous frame?");
3843 for (int n = 0; n < ImGuiKey_COUNT; n++)
3844 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)");
3845
3846 // Perform simple check for 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)
3847 if (g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableKeyboard)
3848 IM_ASSERT(g.IO.KeyMap[ImGuiKey_Space] != -1 && "ImGuiKey_Space is not mapped, required for keyboard navigation.");
3849
3850 // The beta io.OptResizeWindowsFromEdges option requires back-end to honor mouse cursor changes and set the ImGuiBackendFlags_HasMouseCursors flag accordingly.
3851 if (g.IO.OptResizeWindowsFromEdges && !(g.IO.BackendFlags & ImGuiBackendFlags_HasMouseCursors))
3852 g.IO.OptResizeWindowsFromEdges = false;
3853
3854 // Load settings on first frame (if not explicitly loaded manually before)
3855 if (!g.SettingsLoaded)
3856 {
3857 IM_ASSERT(g.SettingsWindows.empty());
3858 if (g.IO.IniFilename)
3859 LoadIniSettingsFromDisk(g.IO.IniFilename);
3860 g.SettingsLoaded = true;
3861 }
3862
3863 // Save settings (with a delay after the last modification, so we don't spam disk too much)
3864 if (g.SettingsDirtyTimer > 0.0f)
3865 {
3866 g.SettingsDirtyTimer -= g.IO.DeltaTime;
3867 if (g.SettingsDirtyTimer <= 0.0f)
3868 {
3869 if (g.IO.IniFilename != NULL)
3870 SaveIniSettingsToDisk(g.IO.IniFilename);
3871 else
3872 g.IO.WantSaveIniSettings = true; // Let user know they can call SaveIniSettingsToMemory(). user will need to clear io.WantSaveIniSettings themselves.
3873 g.SettingsDirtyTimer = 0.0f;
3874 }
3875 }
3876
3877 g.Time += g.IO.DeltaTime;
3878 g.FrameCount += 1;
3879 g.TooltipOverrideCount = 0;
3880 g.WindowsActiveCount = 0;
3881
3882 // Setup current font and draw list
3883 g.IO.Fonts->Locked = true;
3884 SetCurrentFont(GetDefaultFont());
3885 IM_ASSERT(g.Font->IsLoaded());
3886 g.DrawListSharedData.ClipRectFullscreen = ImVec4(0.0f, 0.0f, g.IO.DisplaySize.x, g.IO.DisplaySize.y);
3887 g.DrawListSharedData.CurveTessellationTol = g.Style.CurveTessellationTol;
3888
3889 g.OverlayDrawList.Clear();
3890 g.OverlayDrawList.PushTextureID(g.IO.Fonts->TexID);
3891 g.OverlayDrawList.PushClipRectFullScreen();
3892 g.OverlayDrawList.Flags = (g.Style.AntiAliasedLines ? ImDrawListFlags_AntiAliasedLines : 0) | (g.Style.AntiAliasedFill ? ImDrawListFlags_AntiAliasedFill : 0);
3893
3894 // Mark rendering data as invalid to prevent user who may have a handle on it to use it
3895 g.DrawData.Clear();
3896
3897 // Clear reference to active widget if the widget isn't alive anymore
3898 if (!g.HoveredIdPreviousFrame)
3899 g.HoveredIdTimer = 0.0f;
3900 g.HoveredIdPreviousFrame = g.HoveredId;
3901 g.HoveredId = 0;
3902 g.HoveredIdAllowOverlap = false;
3903 if (!g.ActiveIdIsAlive && g.ActiveIdPreviousFrame == g.ActiveId && g.ActiveId != 0)
3904 ClearActiveID();
3905 if (g.ActiveId)
3906 g.ActiveIdTimer += g.IO.DeltaTime;
3907 g.LastActiveIdTimer += g.IO.DeltaTime;
3908 g.ActiveIdPreviousFrame = g.ActiveId;
3909 g.ActiveIdPreviousFrameWindow = g.ActiveIdWindow;
3910 g.ActiveIdPreviousFrameValueChanged = g.ActiveIdValueChanged;
3911 g.ActiveIdIsAlive = g.ActiveIdPreviousFrameIsAlive = false;
3912 g.ActiveIdIsJustActivated = false;
3913 if (g.ScalarAsInputTextId && g.ActiveId != g.ScalarAsInputTextId)
3914 g.ScalarAsInputTextId = 0;
3915
3916 // Elapse drag & drop payload
3917 if (g.DragDropActive && g.DragDropPayload.DataFrameCount + 1 < g.FrameCount)
3918 {
3919 ClearDragDrop();
3920 g.DragDropPayloadBufHeap.clear();
3921 memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal));
3922 }
3923 g.DragDropAcceptIdPrev = g.DragDropAcceptIdCurr;
3924 g.DragDropAcceptIdCurr = 0;
3925 g.DragDropAcceptIdCurrRectSurface = FLT_MAX;
3926 g.DragDropWithinSourceOrTarget = false;
3927
3928 // Update keyboard input state
3929 memcpy(g.IO.KeysDownDurationPrev, g.IO.KeysDownDuration, sizeof(g.IO.KeysDownDuration));
3930 for (int i = 0; i < IM_ARRAYSIZE(g.IO.KeysDown); i++)
3931 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;
3932
3933 // Update gamepad/keyboard directional navigation
3934 NavUpdate();
3935
3936 // Update mouse input state
3937 UpdateMouseInputs();
3938
3939 // Calculate frame-rate for the user, as a purely luxurious feature
3940 g.FramerateSecPerFrameAccum += g.IO.DeltaTime - g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx];
3941 g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx] = g.IO.DeltaTime;
3942 g.FramerateSecPerFrameIdx = (g.FramerateSecPerFrameIdx + 1) % IM_ARRAYSIZE(g.FramerateSecPerFrame);
3943 g.IO.Framerate = (g.FramerateSecPerFrameAccum > 0.0f) ? (1.0f / (g.FramerateSecPerFrameAccum / (float)IM_ARRAYSIZE(g.FramerateSecPerFrame))) : FLT_MAX;
3944
3945 // Handle user moving window with mouse (at the beginning of the frame to avoid input lag or sheering)
3946 UpdateMouseMovingWindow();
3947 UpdateHoveredWindowAndCaptureFlags();
3948
3949 // Background darkening/whitening
3950 if (GetFrontMostPopupModal() != NULL || (g.NavWindowingTarget != NULL && g.NavWindowingHighlightAlpha > 0.0f))
3951 g.DimBgRatio = ImMin(g.DimBgRatio + g.IO.DeltaTime * 6.0f, 1.0f);
3952 else
3953 g.DimBgRatio = ImMax(g.DimBgRatio - g.IO.DeltaTime * 10.0f, 0.0f);
3954
3955 g.MouseCursor = ImGuiMouseCursor_Arrow;
3956 g.WantCaptureMouseNextFrame = g.WantCaptureKeyboardNextFrame = g.WantTextInputNextFrame = -1;
3957 g.PlatformImePos = ImVec2(1.0f, 1.0f); // OS Input Method Editor showing on top-left of our window by default
3958
3959 // Mouse wheel scrolling, scale
3960 UpdateMouseWheel();
3961
3962 // Pressing TAB activate widget focus
3963 if (g.ActiveId == 0 && g.NavWindow != NULL && g.NavWindow->Active && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab, false))
3964 {
3965 if (g.NavId != 0 && g.NavIdTabCounter != INT_MAX)
3966 g.NavWindow->FocusIdxTabRequestNext = g.NavIdTabCounter + 1 + (g.IO.KeyShift ? -1 : 1);
3967 else
3968 g.NavWindow->FocusIdxTabRequestNext = g.IO.KeyShift ? -1 : 0;
3969 }
3970 g.NavIdTabCounter = INT_MAX;
3971
3972 // Mark all windows as not visible
3973 for (int i = 0; i != g.Windows.Size; i++)
3974 {
3975 ImGuiWindow* window = g.Windows[i];
3976 window->WasActive = window->Active;
3977 window->Active = false;
3978 window->WriteAccessed = false;
3979 }
3980
3981 // Closing the focused window restore focus to the first active root window in descending z-order
3982 if (g.NavWindow && !g.NavWindow->WasActive)
3983 FocusFrontMostActiveWindow(NULL);
3984
3985 // No window should be open at the beginning of the frame.
3986 // But in order to allow the user to call NewFrame() multiple times without calling Render(), we are doing an explicit clear.
3987 g.CurrentWindowStack.resize(0);
3988 g.CurrentPopupStack.resize(0);
3989 ClosePopupsOverWindow(g.NavWindow);
3990
3991 // Create implicit window - we will only render it if the user has added something to it.
3992 // We don't use "Debug" to avoid colliding with user trying to create a "Debug" window with custom flags.
3993 SetNextWindowSize(ImVec2(400,400), ImGuiCond_FirstUseEver);
3994 Begin("Debug##Default");
3995 }
3996
3997 static void* SettingsHandlerWindow_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name)
3998 {
3999 ImGuiWindowSettings* settings = ImGui::FindWindowSettings(ImHash(name, 0));
4000 if (!settings)
4001 settings = CreateNewWindowSettings(name);
4002 return (void*)settings;
4003 }
4004
4005 static void SettingsHandlerWindow_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line)
4006 {
4007 ImGuiWindowSettings* settings = (ImGuiWindowSettings*)entry;
4008 float x, y;
4009 int i;
4010 if (sscanf(line, "Pos=%f,%f", &x, &y) == 2) settings->Pos = ImVec2(x, y);
4011 else if (sscanf(line, "Size=%f,%f", &x, &y) == 2) settings->Size = ImMax(ImVec2(x, y), GImGui->Style.WindowMinSize);
4012 else if (sscanf(line, "Collapsed=%d", &i) == 1) settings->Collapsed = (i != 0);
4013 }
4014
4015 static void SettingsHandlerWindow_WriteAll(ImGuiContext* imgui_ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf)
4016 {
4017 // Gather data from windows that were active during this session
4018 ImGuiContext& g = *imgui_ctx;
4019 for (int i = 0; i != g.Windows.Size; i++)
4020 {
4021 ImGuiWindow* window = g.Windows[i];
4022 if (window->Flags & ImGuiWindowFlags_NoSavedSettings)
4023 continue;
4024
4025 ImGuiWindowSettings* settings = (window->SettingsIdx != -1) ? &g.SettingsWindows[window->SettingsIdx] : ImGui::FindWindowSettings(window->ID);
4026 if (!settings)
4027 {
4028 settings = CreateNewWindowSettings(window->Name);
4029 window->SettingsIdx = g.SettingsWindows.index_from_pointer(settings);
4030 }
4031 IM_ASSERT(settings->ID == window->ID);
4032 settings->Pos = window->Pos;
4033 settings->Size = window->SizeFull;
4034 settings->Collapsed = window->Collapsed;
4035 }
4036
4037 // Write a buffer
4038 // If a window wasn't opened in this session we preserve its settings
4039 buf->reserve(buf->size() + g.SettingsWindows.Size * 96); // ballpark reserve
4040 for (int i = 0; i != g.SettingsWindows.Size; i++)
4041 {
4042 const ImGuiWindowSettings* settings = &g.SettingsWindows[i];
4043 if (settings->Pos.x == FLT_MAX)
4044 continue;
4045 const char* name = settings->Name;
4046 if (const char* p = strstr(name, "###")) // Skip to the "###" marker if any. We don't skip past to match the behavior of GetID()
4047 name = p;
4048 buf->appendf("[%s][%s]\n", handler->TypeName, name);
4049 buf->appendf("Pos=%d,%d\n", (int)settings->Pos.x, (int)settings->Pos.y);
4050 buf->appendf("Size=%d,%d\n", (int)settings->Size.x, (int)settings->Size.y);
4051 buf->appendf("Collapsed=%d\n", settings->Collapsed);
4052 buf->appendf("\n");
4053 }
4054 }
4055
4056 void ImGui::Initialize(ImGuiContext* context)
4057 {
4058 ImGuiContext& g = *context;
4059 IM_ASSERT(!g.Initialized && !g.SettingsLoaded);
4060
4061 // Add .ini handle for ImGuiWindow type
4062 ImGuiSettingsHandler ini_handler;
4063 ini_handler.TypeName = "Window";
4064 ini_handler.TypeHash = ImHash("Window", 0, 0);
4065 ini_handler.ReadOpenFn = SettingsHandlerWindow_ReadOpen;
4066 ini_handler.ReadLineFn = SettingsHandlerWindow_ReadLine;
4067 ini_handler.WriteAllFn = SettingsHandlerWindow_WriteAll;
4068 g.SettingsHandlers.push_front(ini_handler);
4069
4070 g.Initialized = true;
4071 }
4072
4073 // This function is merely here to free heap allocations.
4074 void ImGui::Shutdown(ImGuiContext* context)
4075 {
4076 // 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)
4077 ImGuiContext& g = *context;
4078 if (g.IO.Fonts && g.FontAtlasOwnedByContext)
4079 IM_DELETE(g.IO.Fonts);
4080 g.IO.Fonts = NULL;
4081
4082 // Cleanup of other data are conditional on actually having initialized ImGui.
4083 if (!g.Initialized)
4084 return;
4085
4086 // Save settings (unless we haven't attempted to load them: CreateContext/DestroyContext without a call to NewFrame shouldn't save an empty file)
4087 if (g.SettingsLoaded && g.IO.IniFilename != NULL)
4088 SaveIniSettingsToDisk(g.IO.IniFilename);
4089
4090 // Clear everything else
4091 for (int i = 0; i < g.Windows.Size; i++)
4092 IM_DELETE(g.Windows[i]);
4093 g.Windows.clear();
4094 g.WindowsSortBuffer.clear();
4095 g.CurrentWindow = NULL;
4096 g.CurrentWindowStack.clear();
4097 g.WindowsById.Clear();
4098 g.NavWindow = NULL;
4099 g.HoveredWindow = NULL;
4100 g.HoveredRootWindow = NULL;
4101 g.ActiveIdWindow = g.ActiveIdPreviousFrameWindow = NULL;
4102 g.MovingWindow = NULL;
4103 g.ColorModifiers.clear();
4104 g.StyleModifiers.clear();
4105 g.FontStack.clear();
4106 g.OpenPopupStack.clear();
4107 g.CurrentPopupStack.clear();
4108 g.DrawDataBuilder.ClearFreeMemory();
4109 g.OverlayDrawList.ClearFreeMemory();
4110 g.PrivateClipboard.clear();
4111 g.InputTextState.Text.clear();
4112 g.InputTextState.InitialText.clear();
4113 g.InputTextState.TempTextBuffer.clear();
4114
4115 for (int i = 0; i < g.SettingsWindows.Size; i++)
4116 IM_DELETE(g.SettingsWindows[i].Name);
4117 g.SettingsWindows.clear();
4118 g.SettingsHandlers.clear();
4119
4120 if (g.LogFile && g.LogFile != stdout)
4121 {
4122 fclose(g.LogFile);
4123 g.LogFile = NULL;
4124 }
4125 g.LogClipboard.clear();
4126
4127 g.Initialized = false;
4128 }
4129
4130 ImGuiWindowSettings* ImGui::FindWindowSettings(ImGuiID id)
4131 {
4132 ImGuiContext& g = *GImGui;
4133 for (int i = 0; i != g.SettingsWindows.Size; i++)
4134 if (g.SettingsWindows[i].ID == id)
4135 return &g.SettingsWindows[i];
4136 return NULL;
4137 }
4138
4139 static ImGuiWindowSettings* CreateNewWindowSettings(const char* name)
4140 {
4141 ImGuiContext& g = *GImGui;
4142 g.SettingsWindows.push_back(ImGuiWindowSettings());
4143 ImGuiWindowSettings* settings = &g.SettingsWindows.back();
4144 settings->Name = ImStrdup(name);
4145 settings->ID = ImHash(name, 0);
4146 return settings;
4147 }
4148
4149 void ImGui::LoadIniSettingsFromDisk(const char* ini_filename)
4150 {
4151 size_t file_data_size = 0;
4152 char* file_data = (char*)ImFileLoadToMemory(ini_filename, "rb", &file_data_size);
4153 if (!file_data)
4154 return;
4155 LoadIniSettingsFromMemory(file_data, (size_t)file_data_size);
4156 ImGui::MemFree(file_data);
4157 }
4158
4159 ImGuiSettingsHandler* ImGui::FindSettingsHandler(const char* type_name)
4160 {
4161 ImGuiContext& g = *GImGui;
4162 const ImGuiID type_hash = ImHash(type_name, 0, 0);
4163 for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++)
4164 if (g.SettingsHandlers[handler_n].TypeHash == type_hash)
4165 return &g.SettingsHandlers[handler_n];
4166 return NULL;
4167 }
4168
4169 // Zero-tolerance, no error reporting, cheap .ini parsing
4170 void ImGui::LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size)
4171 {
4172 ImGuiContext& g = *GImGui;
4173 IM_ASSERT(g.Initialized);
4174 IM_ASSERT(g.SettingsLoaded == false && g.FrameCount == 0);
4175
4176 // For user convenience, we allow passing a non zero-terminated string (hence the ini_size parameter).
4177 // 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..
4178 if (ini_size == 0)
4179 ini_size = strlen(ini_data);
4180 char* buf = (char*)ImGui::MemAlloc(ini_size + 1);
4181 char* buf_end = buf + ini_size;
4182 memcpy(buf, ini_data, ini_size);
4183 buf[ini_size] = 0;
4184
4185 void* entry_data = NULL;
4186 ImGuiSettingsHandler* entry_handler = NULL;
4187
4188 char* line_end = NULL;
4189 for (char* line = buf; line < buf_end; line = line_end + 1)
4190 {
4191 // Skip new lines markers, then find end of the line
4192 while (*line == '\n' || *line == '\r')
4193 line++;
4194 line_end = line;
4195 while (line_end < buf_end && *line_end != '\n' && *line_end != '\r')
4196 line_end++;
4197 line_end[0] = 0;
4198
4199 if (line[0] == '[' && line_end > line && line_end[-1] == ']')
4200 {
4201 // Parse "[Type][Name]". Note that 'Name' can itself contains [] characters, which is acceptable with the current format and parsing code.
4202 line_end[-1] = 0;
4203 const char* name_end = line_end - 1;
4204 const char* type_start = line + 1;
4205 char* type_end = (char*)(intptr_t)ImStrchrRange(type_start, name_end, ']');
4206 const char* name_start = type_end ? ImStrchrRange(type_end + 1, name_end, '[') : NULL;
4207 if (!type_end || !name_start)
4208 {
4209 name_start = type_start; // Import legacy entries that have no type
4210 type_start = "Window";
4211 }
4212 else
4213 {
4214 *type_end = 0; // Overwrite first ']'
4215 name_start++; // Skip second '['
4216 }
4217 entry_handler = FindSettingsHandler(type_start);
4218 entry_data = entry_handler ? entry_handler->ReadOpenFn(&g, entry_handler, name_start) : NULL;
4219 }
4220 else if (entry_handler != NULL && entry_data != NULL)
4221 {
4222 // Let type handler parse the line
4223 entry_handler->ReadLineFn(&g, entry_handler, entry_data, line);
4224 }
4225 }
4226 ImGui::MemFree(buf);
4227 g.SettingsLoaded = true;
4228 }
4229
4230 void ImGui::SaveIniSettingsToDisk(const char* ini_filename)
4231 {
4232 ImGuiContext& g = *GImGui;
4233 g.SettingsDirtyTimer = 0.0f;
4234 if (!ini_filename)
4235 return;
4236
4237 size_t ini_data_size = 0;
4238 const char* ini_data = SaveIniSettingsToMemory(&ini_data_size);
4239 FILE* f = ImFileOpen(ini_filename, "wt");
4240 if (!f)
4241 return;
4242 fwrite(ini_data, sizeof(char), ini_data_size, f);
4243 fclose(f);
4244 }
4245
4246 // Call registered handlers (e.g. SettingsHandlerWindow_WriteAll() + custom handlers) to write their stuff into a text buffer
4247 const char* ImGui::SaveIniSettingsToMemory(size_t* out_size)
4248 {
4249 ImGuiContext& g = *GImGui;
4250 g.SettingsDirtyTimer = 0.0f;
4251 g.SettingsIniData.Buf.resize(0);
4252 g.SettingsIniData.Buf.push_back(0);
4253 for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++)
4254 {
4255 ImGuiSettingsHandler* handler = &g.SettingsHandlers[handler_n];
4256 handler->WriteAllFn(&g, handler, &g.SettingsIniData);
4257 }
4258 if (out_size)
4259 *out_size = (size_t)g.SettingsIniData.size();
4260 return g.SettingsIniData.c_str();
4261 }
4262
4263 void ImGui::MarkIniSettingsDirty()
4264 {
4265 ImGuiContext& g = *GImGui;
4266 if (g.SettingsDirtyTimer <= 0.0f)
4267 g.SettingsDirtyTimer = g.IO.IniSavingRate;
4268 }
4269
4270 void ImGui::MarkIniSettingsDirty(ImGuiWindow* window)
4271 {
4272 ImGuiContext& g = *GImGui;
4273 if (!(window->Flags & ImGuiWindowFlags_NoSavedSettings))
4274 if (g.SettingsDirtyTimer <= 0.0f)
4275 g.SettingsDirtyTimer = g.IO.IniSavingRate;
4276 }
4277
4278 // FIXME: Add a more explicit sort order in the window structure.
4279 static int IMGUI_CDECL ChildWindowComparer(const void* lhs, const void* rhs)
4280 {
4281 const ImGuiWindow* const a = *(const ImGuiWindow* const *)lhs;
4282 const ImGuiWindow* const b = *(const ImGuiWindow* const *)rhs;
4283 if (int d = (a->Flags & ImGuiWindowFlags_Popup) - (b->Flags & ImGuiWindowFlags_Popup))
4284 return d;
4285 if (int d = (a->Flags & ImGuiWindowFlags_Tooltip) - (b->Flags & ImGuiWindowFlags_Tooltip))
4286 return d;
4287 return (a->BeginOrderWithinParent - b->BeginOrderWithinParent);
4288 }
4289
4290 static void AddWindowToSortedBuffer(ImVector<ImGuiWindow*>* out_sorted_windows, ImGuiWindow* window)
4291 {
4292 out_sorted_windows->push_back(window);
4293 if (window->Active)
4294 {
4295 int count = window->DC.ChildWindows.Size;
4296 if (count > 1)
4297 ImQsort(window->DC.ChildWindows.begin(), (size_t)count, sizeof(ImGuiWindow*), ChildWindowComparer);
4298 for (int i = 0; i < count; i++)
4299 {
4300 ImGuiWindow* child = window->DC.ChildWindows[i];
4301 if (child->Active)
4302 AddWindowToSortedBuffer(out_sorted_windows, child);
4303 }
4304 }
4305 }
4306
4307 static void AddDrawListToDrawData(ImVector<ImDrawList*>* out_list, ImDrawList* draw_list)
4308 {
4309 if (draw_list->CmdBuffer.empty())
4310 return;
4311
4312 // Remove trailing command if unused
4313 ImDrawCmd& last_cmd = draw_list->CmdBuffer.back();
4314 if (last_cmd.ElemCount == 0 && last_cmd.UserCallback == NULL)
4315 {
4316 draw_list->CmdBuffer.pop_back();
4317 if (draw_list->CmdBuffer.empty())
4318 return;
4319 }
4320
4321 // Draw list sanity check. Detect mismatch between PrimReserve() calls and incrementing _VtxCurrentIdx, _VtxWritePtr etc. May trigger for you if you are using PrimXXX functions incorrectly.
4322 IM_ASSERT(draw_list->VtxBuffer.Size == 0 || draw_list->_VtxWritePtr == draw_list->VtxBuffer.Data + draw_list->VtxBuffer.Size);
4323 IM_ASSERT(draw_list->IdxBuffer.Size == 0 || draw_list->_IdxWritePtr == draw_list->IdxBuffer.Data + draw_list->IdxBuffer.Size);
4324 IM_ASSERT((int)draw_list->_VtxCurrentIdx == draw_list->VtxBuffer.Size);
4325
4326 // Check that draw_list doesn't use more vertices than indexable (default ImDrawIdx = unsigned short = 2 bytes = 64K vertices per ImDrawList = per window)
4327 // If this assert triggers because you are drawing lots of stuff manually:
4328 // A) Make sure you are coarse clipping, because ImDrawList let all your vertices pass. You can use the Metrics window to inspect draw list contents.
4329 // B) If you need/want meshes with more than 64K vertices, uncomment the '#define ImDrawIdx unsigned int' line in imconfig.h to set the index size to 4 bytes.
4330 // You'll need to handle the 4-bytes indices to your renderer. For example, the OpenGL example code detect index size at compile-time by doing:
4331 // glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer_offset);
4332 // Your own engine or render API may use different parameters or function calls to specify index sizes. 2 and 4 bytes indices are generally supported by most API.
4333 // C) If for some reason you cannot use 4 bytes indices or don't want to, a workaround is to call BeginChild()/EndChild() before reaching the 64K limit to split your draw commands in multiple draw lists.
4334 if (sizeof(ImDrawIdx) == 2)
4335 IM_ASSERT(draw_list->_VtxCurrentIdx < (1 << 16) && "Too many vertices in ImDrawList using 16-bit indices. Read comment above");
4336
4337 out_list->push_back(draw_list);
4338 }
4339
4340 static void AddWindowToDrawData(ImVector<ImDrawList*>* out_render_list, ImGuiWindow* window)
4341 {
4342 AddDrawListToDrawData(out_render_list, window->DrawList);
4343 for (int i = 0; i < window->DC.ChildWindows.Size; i++)
4344 {
4345 ImGuiWindow* child = window->DC.ChildWindows[i];
4346 if (IsWindowActiveAndVisible(child)) // clipped children may have been marked not active
4347 AddWindowToDrawData(out_render_list, child);
4348 }
4349 }
4350
4351 static void AddWindowToDrawDataSelectLayer(ImGuiWindow* window)
4352 {
4353 ImGuiContext& g = *GImGui;
4354 g.IO.MetricsActiveWindows++;
4355 if (window->Flags & ImGuiWindowFlags_Tooltip)
4356 AddWindowToDrawData(&g.DrawDataBuilder.Layers[1], window);
4357 else
4358 AddWindowToDrawData(&g.DrawDataBuilder.Layers[0], window);
4359 }
4360
4361 void ImDrawDataBuilder::FlattenIntoSingleLayer()
4362 {
4363 int n = Layers[0].Size;
4364 int size = n;
4365 for (int i = 1; i < IM_ARRAYSIZE(Layers); i++)
4366 size += Layers[i].Size;
4367 Layers[0].resize(size);
4368 for (int layer_n = 1; layer_n < IM_ARRAYSIZE(Layers); layer_n++)
4369 {
4370 ImVector<ImDrawList*>& layer = Layers[layer_n];
4371 if (layer.empty())
4372 continue;
4373 memcpy(&Layers[0][n], &layer[0], layer.Size * sizeof(ImDrawList*));
4374 n += layer.Size;
4375 layer.resize(0);
4376 }
4377 }
4378
4379 static void SetupDrawData(ImVector<ImDrawList*>* draw_lists, ImDrawData* out_draw_data)
4380 {
4381 ImGuiIO& io = ImGui::GetIO();
4382 out_draw_data->Valid = true;
4383 out_draw_data->CmdLists = (draw_lists->Size > 0) ? draw_lists->Data : NULL;
4384 out_draw_data->CmdListsCount = draw_lists->Size;
4385 out_draw_data->TotalVtxCount = out_draw_data->TotalIdxCount = 0;
4386 out_draw_data->DisplayPos = ImVec2(0.0f, 0.0f);
4387 out_draw_data->DisplaySize = io.DisplaySize;
4388 for (int n = 0; n < draw_lists->Size; n++)
4389 {
4390 out_draw_data->TotalVtxCount += draw_lists->Data[n]->VtxBuffer.Size;
4391 out_draw_data->TotalIdxCount += draw_lists->Data[n]->IdxBuffer.Size;
4392 }
4393 }
4394
4395 // When using this function it is sane to ensure that float are perfectly rounded to integer values, to that e.g. (int)(max.x-min.x) in user's render produce correct result.
4396 void ImGui::PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect)
4397 {
4398 ImGuiWindow* window = GetCurrentWindow();
4399 window->DrawList->PushClipRect(clip_rect_min, clip_rect_max, intersect_with_current_clip_rect);
4400 window->ClipRect = window->DrawList->_ClipRectStack.back();
4401 }
4402
4403 void ImGui::PopClipRect()
4404 {
4405 ImGuiWindow* window = GetCurrentWindow();
4406 window->DrawList->PopClipRect();
4407 window->ClipRect = window->DrawList->_ClipRectStack.back();
4408 }
4409
4410 // 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.
4411 void ImGui::EndFrame()
4412 {
4413 ImGuiContext& g = *GImGui;
4414 IM_ASSERT(g.Initialized); // Forgot to call ImGui::NewFrame()
4415 if (g.FrameCountEnded == g.FrameCount) // Don't process EndFrame() multiple times.
4416 return;
4417
4418 // Notify OS when our Input Method Editor cursor has moved (e.g. CJK inputs using Microsoft IME)
4419 if (g.IO.ImeSetInputScreenPosFn && ImLengthSqr(g.PlatformImeLastPos - g.PlatformImePos) > 0.0001f)
4420 {
4421 g.IO.ImeSetInputScreenPosFn((int)g.PlatformImePos.x, (int)g.PlatformImePos.y);
4422 g.PlatformImeLastPos = g.PlatformImePos;
4423 }
4424
4425 // Hide implicit "Debug" window if it hasn't been used
4426 IM_ASSERT(g.CurrentWindowStack.Size == 1); // Mismatched Begin()/End() calls, did you forget to call end on g.CurrentWindow->Name?
4427 if (g.CurrentWindow && !g.CurrentWindow->WriteAccessed)
4428 g.CurrentWindow->Active = false;
4429 End();
4430
4431 // Show CTRL+TAB list
4432 if (g.NavWindowingTarget)
4433 NavUpdateWindowingList();
4434
4435 // Initiate moving window
4436 if (g.ActiveId == 0 && g.HoveredId == 0)
4437 {
4438 if (!g.NavWindow || !g.NavWindow->Appearing) // Unless we just made a window/popup appear
4439 {
4440 // Click to focus window and start moving (after we're done with all our widgets)
4441 if (g.IO.MouseClicked[0])
4442 {
4443 if (g.HoveredRootWindow != NULL)
4444 StartMouseMovingWindow(g.HoveredWindow);
4445 else if (g.NavWindow != NULL && GetFrontMostPopupModal() == NULL)
4446 FocusWindow(NULL); // Clicking on void disable focus
4447 }
4448
4449 // With right mouse button we close popups without changing focus
4450 // (The left mouse button path calls FocusWindow which will lead NewFrame->ClosePopupsOverWindow to trigger)
4451 if (g.IO.MouseClicked[1])
4452 {
4453 // Find the top-most window between HoveredWindow and the front most Modal Window.
4454 // This is where we can trim the popup stack.
4455 ImGuiWindow* modal = GetFrontMostPopupModal();
4456 bool hovered_window_above_modal = false;
4457 if (modal == NULL)
4458 hovered_window_above_modal = true;
4459 for (int i = g.Windows.Size - 1; i >= 0 && hovered_window_above_modal == false; i--)
4460 {
4461 ImGuiWindow* window = g.Windows[i];
4462 if (window == modal)
4463 break;
4464 if (window == g.HoveredWindow)
4465 hovered_window_above_modal = true;
4466 }
4467 ClosePopupsOverWindow(hovered_window_above_modal ? g.HoveredWindow : modal);
4468 }
4469 }
4470 }
4471
4472 // Sort the window list so that all child windows are after their parent
4473 // We cannot do that on FocusWindow() because childs may not exist yet
4474 g.WindowsSortBuffer.resize(0);
4475 g.WindowsSortBuffer.reserve(g.Windows.Size);
4476 for (int i = 0; i != g.Windows.Size; i++)
4477 {
4478 ImGuiWindow* window = g.Windows[i];
4479 if (window->Active && (window->Flags & ImGuiWindowFlags_ChildWindow)) // if a child is active its parent will add it
4480 continue;
4481 AddWindowToSortedBuffer(&g.WindowsSortBuffer, window);
4482 }
4483
4484 IM_ASSERT(g.Windows.Size == g.WindowsSortBuffer.Size); // we done something wrong
4485 g.Windows.swap(g.WindowsSortBuffer);
4486
4487 // Unlock font atlas
4488 g.IO.Fonts->Locked = false;
4489
4490 // Clear Input data for next frame
4491 g.IO.MouseWheel = g.IO.MouseWheelH = 0.0f;
4492 memset(g.IO.InputCharacters, 0, sizeof(g.IO.InputCharacters));
4493 memset(g.IO.NavInputs, 0, sizeof(g.IO.NavInputs));
4494
4495 g.FrameCountEnded = g.FrameCount;
4496 }
4497
4498 void ImGui::Render()
4499 {
4500 ImGuiContext& g = *GImGui;
4501 IM_ASSERT(g.Initialized); // Forgot to call ImGui::NewFrame()
4502
4503 if (g.FrameCountEnded != g.FrameCount)
4504 ImGui::EndFrame();
4505 g.FrameCountRendered = g.FrameCount;
4506
4507 // Gather windows to render
4508 g.IO.MetricsRenderVertices = g.IO.MetricsRenderIndices = g.IO.MetricsActiveWindows = 0;
4509 g.DrawDataBuilder.Clear();
4510 ImGuiWindow* windows_to_render_front_most[2];
4511 windows_to_render_front_most[0] = (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus)) ? g.NavWindowingTarget->RootWindow : NULL;
4512 windows_to_render_front_most[1] = g.NavWindowingTarget ? g.NavWindowingList : NULL;
4513 for (int n = 0; n != g.Windows.Size; n++)
4514 {
4515 ImGuiWindow* window = g.Windows[n];
4516 if (IsWindowActiveAndVisible(window) && (window->Flags & ImGuiWindowFlags_ChildWindow) == 0 && window != windows_to_render_front_most[0] && window != windows_to_render_front_most[1])
4517 AddWindowToDrawDataSelectLayer(window);
4518 }
4519 for (int n = 0; n < IM_ARRAYSIZE(windows_to_render_front_most); n++)
4520 if (windows_to_render_front_most[n] && IsWindowActiveAndVisible(windows_to_render_front_most[n])) // NavWindowingTarget is always temporarily displayed as the front-most window
4521 AddWindowToDrawDataSelectLayer(windows_to_render_front_most[n]);
4522 g.DrawDataBuilder.FlattenIntoSingleLayer();
4523
4524 // Draw software mouse cursor if requested
4525 ImVec2 offset, size, uv[4];
4526 if (g.IO.MouseDrawCursor && g.IO.Fonts->GetMouseCursorTexData(g.MouseCursor, &offset, &size, &uv[0], &uv[2]))
4527 {
4528 const ImVec2 pos = g.IO.MousePos - offset;
4529 const ImTextureID tex_id = g.IO.Fonts->TexID;
4530 const float sc = g.Style.MouseCursorScale;
4531 g.OverlayDrawList.PushTextureID(tex_id);
4532 g.OverlayDrawList.AddImage(tex_id, pos + ImVec2(1,0)*sc, pos+ImVec2(1,0)*sc + size*sc, uv[2], uv[3], IM_COL32(0,0,0,48)); // Shadow
4533 g.OverlayDrawList.AddImage(tex_id, pos + ImVec2(2,0)*sc, pos+ImVec2(2,0)*sc + size*sc, uv[2], uv[3], IM_COL32(0,0,0,48)); // Shadow
4534 g.OverlayDrawList.AddImage(tex_id, pos, pos + size*sc, uv[2], uv[3], IM_COL32(0,0,0,255)); // Black border
4535 g.OverlayDrawList.AddImage(tex_id, pos, pos + size*sc, uv[0], uv[1], IM_COL32(255,255,255,255)); // White fill
4536 g.OverlayDrawList.PopTextureID();
4537 }
4538 if (!g.OverlayDrawList.VtxBuffer.empty())
4539 AddDrawListToDrawData(&g.DrawDataBuilder.Layers[0], &g.OverlayDrawList);
4540
4541 // Setup ImDrawData structure for end-user
4542 SetupDrawData(&g.DrawDataBuilder.Layers[0], &g.DrawData);
4543 g.IO.MetricsRenderVertices = g.DrawData.TotalVtxCount;
4544 g.IO.MetricsRenderIndices = g.DrawData.TotalIdxCount;
4545
4546 // Render. If user hasn't set a callback then they may retrieve the draw data via GetDrawData()
4547 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
4548 if (g.DrawData.CmdListsCount > 0 && g.IO.RenderDrawListsFn != NULL)
4549 g.IO.RenderDrawListsFn(&g.DrawData);
4550 #endif
4551 }
4552
4553 const char* ImGui::FindRenderedTextEnd(const char* text, const char* text_end)
4554 {
4555 const char* text_display_end = text;
4556 if (!text_end)
4557 text_end = (const char*)-1;
4558
4559 while (text_display_end < text_end && *text_display_end != '\0' && (text_display_end[0] != '#' || text_display_end[1] != '#'))
4560 text_display_end++;
4561 return text_display_end;
4562 }
4563
4564 // Pass text data straight to log (without being displayed)
4565 void ImGui::LogText(const char* fmt, ...)
4566 {
4567 ImGuiContext& g = *GImGui;
4568 if (!g.LogEnabled)
4569 return;
4570
4571 va_list args;
4572 va_start(args, fmt);
4573 if (g.LogFile)
4574 vfprintf(g.LogFile, fmt, args);
4575 else
4576 g.LogClipboard.appendfv(fmt, args);
4577 va_end(args);
4578 }
4579
4580 // Internal version that takes a position to decide on newline placement and pad items according to their depth.
4581 // We split text into individual lines to add current tree level padding
4582 static void LogRenderedText(const ImVec2* ref_pos, const char* text, const char* text_end = NULL)
4583 {
4584 ImGuiContext& g = *GImGui;
4585 ImGuiWindow* window = g.CurrentWindow;
4586
4587 if (!text_end)
4588 text_end = ImGui::FindRenderedTextEnd(text, text_end);
4589
4590 const bool log_new_line = ref_pos && (ref_pos->y > window->DC.LogLinePosY + 1);
4591 if (ref_pos)
4592 window->DC.LogLinePosY = ref_pos->y;
4593
4594 const char* text_remaining = text;
4595 if (g.LogStartDepth > window->DC.TreeDepth) // Re-adjust padding if we have popped out of our starting depth
4596 g.LogStartDepth = window->DC.TreeDepth;
4597 const int tree_depth = (window->DC.TreeDepth - g.LogStartDepth);
4598 for (;;)
4599 {
4600 // Split the string. Each new line (after a '\n') is followed by spacing corresponding to the current depth of our log entry.
4601 const char* line_end = text_remaining;
4602 while (line_end < text_end)
4603 if (*line_end == '\n')
4604 break;
4605 else
4606 line_end++;
4607 if (line_end >= text_end)
4608 line_end = NULL;
4609
4610 const bool is_first_line = (text == text_remaining);
4611 bool is_last_line = false;
4612 if (line_end == NULL)
4613 {
4614 is_last_line = true;
4615 line_end = text_end;
4616 }
4617 if (line_end != NULL && !(is_last_line && (line_end - text_remaining)==0))
4618 {
4619 const int char_count = (int)(line_end - text_remaining);
4620 if (log_new_line || !is_first_line)
4621 ImGui::LogText(IM_NEWLINE "%*s%.*s", tree_depth*4, "", char_count, text_remaining);
4622 else
4623 ImGui::LogText(" %.*s", char_count, text_remaining);
4624 }
4625
4626 if (is_last_line)
4627 break;
4628 text_remaining = line_end + 1;
4629 }
4630 }
4631
4632 // Internal ImGui functions to render text
4633 // RenderText***() functions calls ImDrawList::AddText() calls ImBitmapFont::RenderText()
4634 void ImGui::RenderText(ImVec2 pos, const char* text, const char* text_end, bool hide_text_after_hash)
4635 {
4636 ImGuiContext& g = *GImGui;
4637 ImGuiWindow* window = g.CurrentWindow;
4638
4639 // Hide anything after a '##' string
4640 const char* text_display_end;
4641 if (hide_text_after_hash)
4642 {
4643 text_display_end = FindRenderedTextEnd(text, text_end);
4644 }
4645 else
4646 {
4647 if (!text_end)
4648 text_end = text + strlen(text); // FIXME-OPT
4649 text_display_end = text_end;
4650 }
4651
4652 if (text != text_display_end)
4653 {
4654 window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end);
4655 if (g.LogEnabled)
4656 LogRenderedText(&pos, text, text_display_end);
4657 }
4658 }
4659
4660 void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width)
4661 {
4662 ImGuiContext& g = *GImGui;
4663 ImGuiWindow* window = g.CurrentWindow;
4664
4665 if (!text_end)
4666 text_end = text + strlen(text); // FIXME-OPT
4667
4668 if (text != text_end)
4669 {
4670 window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_end, wrap_width);
4671 if (g.LogEnabled)
4672 LogRenderedText(&pos, text, text_end);
4673 }
4674 }
4675
4676 // Default clip_rect uses (pos_min,pos_max)
4677 // Handle clipping on CPU immediately (vs typically let the GPU clip the triangles that are overlapping the clipping rectangle edges)
4678 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)
4679 {
4680 // Hide anything after a '##' string
4681 const char* text_display_end = FindRenderedTextEnd(text, text_end);
4682 const int text_len = (int)(text_display_end - text);
4683 if (text_len == 0)
4684 return;
4685
4686 ImGuiContext& g = *GImGui;
4687 ImGuiWindow* window = g.CurrentWindow;
4688
4689 // Perform CPU side clipping for single clipped element to avoid using scissor state
4690 ImVec2 pos = pos_min;
4691 const ImVec2 text_size = text_size_if_known ? *text_size_if_known : CalcTextSize(text, text_display_end, false, 0.0f);
4692
4693 const ImVec2* clip_min = clip_rect ? &clip_rect->Min : &pos_min;
4694 const ImVec2* clip_max = clip_rect ? &clip_rect->Max : &pos_max;
4695 bool need_clipping = (pos.x + text_size.x >= clip_max->x) || (pos.y + text_size.y >= clip_max->y);
4696 if (clip_rect) // If we had no explicit clipping rectangle then pos==clip_min
4697 need_clipping |= (pos.x < clip_min->x) || (pos.y < clip_min->y);
4698
4699 // Align whole block. We should defer that to the better rendering function when we'll have support for individual line alignment.
4700 if (align.x > 0.0f) pos.x = ImMax(pos.x, pos.x + (pos_max.x - pos.x - text_size.x) * align.x);
4701 if (align.y > 0.0f) pos.y = ImMax(pos.y, pos.y + (pos_max.y - pos.y - text_size.y) * align.y);
4702
4703 // Render
4704 if (need_clipping)
4705 {
4706 ImVec4 fine_clip_rect(clip_min->x, clip_min->y, clip_max->x, clip_max->y);
4707 window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, &fine_clip_rect);
4708 }
4709 else
4710 {
4711 window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, NULL);
4712 }
4713 if (g.LogEnabled)
4714 LogRenderedText(&pos, text, text_display_end);
4715 }
4716
4717 // Render a rectangle shaped with optional rounding and borders
4718 void ImGui::RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border, float rounding)
4719 {
4720 ImGuiContext& g = *GImGui;
4721 ImGuiWindow* window = g.CurrentWindow;
4722 window->DrawList->AddRectFilled(p_min, p_max, fill_col, rounding);
4723 const float border_size = g.Style.FrameBorderSize;
4724 if (border && border_size > 0.0f)
4725 {
4726 window->DrawList->AddRect(p_min+ImVec2(1,1), p_max+ImVec2(1,1), GetColorU32(ImGuiCol_BorderShadow), rounding, ImDrawCornerFlags_All, border_size);
4727 window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, ImDrawCornerFlags_All, border_size);
4728 }
4729 }
4730
4731 void ImGui::RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding)
4732 {
4733 ImGuiContext& g = *GImGui;
4734 ImGuiWindow* window = g.CurrentWindow;
4735 const float border_size = g.Style.FrameBorderSize;
4736 if (border_size > 0.0f)
4737 {
4738 window->DrawList->AddRect(p_min+ImVec2(1,1), p_max+ImVec2(1,1), GetColorU32(ImGuiCol_BorderShadow), rounding, ImDrawCornerFlags_All, border_size);
4739 window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, ImDrawCornerFlags_All, border_size);
4740 }
4741 }
4742
4743 // Render an arrow. 'pos' is position of the arrow tip. half_sz.x is length from base to tip. half_sz.y is length on each side.
4744 void ImGui::RenderArrowPointingAt(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, ImGuiDir direction, ImU32 col)
4745 {
4746 switch (direction)
4747 {
4748 case ImGuiDir_Left: draw_list->AddTriangleFilled(ImVec2(pos.x + half_sz.x, pos.y - half_sz.y), ImVec2(pos.x + half_sz.x, pos.y + half_sz.y), pos, col); return;
4749 case ImGuiDir_Right: draw_list->AddTriangleFilled(ImVec2(pos.x - half_sz.x, pos.y + half_sz.y), ImVec2(pos.x - half_sz.x, pos.y - half_sz.y), pos, col); return;
4750 case ImGuiDir_Up: draw_list->AddTriangleFilled(ImVec2(pos.x + half_sz.x, pos.y + half_sz.y), ImVec2(pos.x - half_sz.x, pos.y + half_sz.y), pos, col); return;
4751 case ImGuiDir_Down: draw_list->AddTriangleFilled(ImVec2(pos.x - half_sz.x, pos.y - half_sz.y), ImVec2(pos.x + half_sz.x, pos.y - half_sz.y), pos, col); return;
4752 case ImGuiDir_None: case ImGuiDir_COUNT: break; // Fix warnings
4753 }
4754 }
4755
4756 // Render an arrow aimed to be aligned with text (p_min is a position in the same space text would be positioned). To e.g. denote expanded/collapsed state
4757 void ImGui::RenderArrow(ImVec2 p_min, ImGuiDir dir, float scale)
4758 {
4759 ImGuiContext& g = *GImGui;
4760
4761 const float h = g.FontSize * 1.00f;
4762 float r = h * 0.40f * scale;
4763 ImVec2 center = p_min + ImVec2(h * 0.50f, h * 0.50f * scale);
4764
4765 ImVec2 a, b, c;
4766 switch (dir)
4767 {
4768 case ImGuiDir_Up:
4769 case ImGuiDir_Down:
4770 if (dir == ImGuiDir_Up) r = -r;
4771 a = ImVec2(+0.000f,+0.750f) * r;
4772 b = ImVec2(-0.866f,-0.750f) * r;
4773 c = ImVec2(+0.866f,-0.750f) * r;
4774 break;
4775 case ImGuiDir_Left:
4776 case ImGuiDir_Right:
4777 if (dir == ImGuiDir_Left) r = -r;
4778 a = ImVec2(+0.750f,+0.000f) * r;
4779 b = ImVec2(-0.750f,+0.866f) * r;
4780 c = ImVec2(-0.750f,-0.866f) * r;
4781 break;
4782 case ImGuiDir_None:
4783 case ImGuiDir_COUNT:
4784 IM_ASSERT(0);
4785 break;
4786 }
4787
4788 g.CurrentWindow->DrawList->AddTriangleFilled(center + a, center + b, center + c, GetColorU32(ImGuiCol_Text));
4789 }
4790
4791 void ImGui::RenderBullet(ImVec2 pos)
4792 {
4793 ImGuiContext& g = *GImGui;
4794 ImGuiWindow* window = g.CurrentWindow;
4795 window->DrawList->AddCircleFilled(pos, GImGui->FontSize*0.20f, GetColorU32(ImGuiCol_Text), 8);
4796 }
4797
4798 void ImGui::RenderCheckMark(ImVec2 pos, ImU32 col, float sz)
4799 {
4800 ImGuiContext& g = *GImGui;
4801 ImGuiWindow* window = g.CurrentWindow;
4802
4803 float thickness = ImMax(sz / 5.0f, 1.0f);
4804 sz -= thickness*0.5f;
4805 pos += ImVec2(thickness*0.25f, thickness*0.25f);
4806
4807 float third = sz / 3.0f;
4808 float bx = pos.x + third;
4809 float by = pos.y + sz - third*0.5f;
4810 window->DrawList->PathLineTo(ImVec2(bx - third, by - third));
4811 window->DrawList->PathLineTo(ImVec2(bx, by));
4812 window->DrawList->PathLineTo(ImVec2(bx + third*2, by - third*2));
4813 window->DrawList->PathStroke(col, false, thickness);
4814 }
4815
4816 void ImGui::RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFlags flags)
4817 {
4818 ImGuiContext& g = *GImGui;
4819 if (id != g.NavId)
4820 return;
4821 if (g.NavDisableHighlight && !(flags & ImGuiNavHighlightFlags_AlwaysDraw))
4822 return;
4823 ImGuiWindow* window = ImGui::GetCurrentWindow();
4824 if (window->DC.NavHideHighlightOneFrame)
4825 return;
4826
4827 float rounding = (flags & ImGuiNavHighlightFlags_NoRounding) ? 0.0f : g.Style.FrameRounding;
4828 ImRect display_rect = bb;
4829 display_rect.ClipWith(window->ClipRect);
4830 if (flags & ImGuiNavHighlightFlags_TypeDefault)
4831 {
4832 const float THICKNESS = 2.0f;
4833 const float DISTANCE = 3.0f + THICKNESS * 0.5f;
4834 display_rect.Expand(ImVec2(DISTANCE,DISTANCE));
4835 bool fully_visible = window->ClipRect.Contains(display_rect);
4836 if (!fully_visible)
4837 window->DrawList->PushClipRect(display_rect.Min, display_rect.Max);
4838 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);
4839 if (!fully_visible)
4840 window->DrawList->PopClipRect();
4841 }
4842 if (flags & ImGuiNavHighlightFlags_TypeThin)
4843 {
4844 window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavHighlight), rounding, ~0, 1.0f);
4845 }
4846 }
4847
4848 // Calculate text size. Text can be multi-line. Optionally ignore text after a ## marker.
4849 // CalcTextSize("") should return ImVec2(0.0f, GImGui->FontSize)
4850 ImVec2 ImGui::CalcTextSize(const char* text, const char* text_end, bool hide_text_after_double_hash, float wrap_width)
4851 {
4852 ImGuiContext& g = *GImGui;
4853
4854 const char* text_display_end;
4855 if (hide_text_after_double_hash)
4856 text_display_end = FindRenderedTextEnd(text, text_end); // Hide anything after a '##' string
4857 else
4858 text_display_end = text_end;
4859
4860 ImFont* font = g.Font;
4861 const float font_size = g.FontSize;
4862 if (text == text_display_end)
4863 return ImVec2(0.0f, font_size);
4864 ImVec2 text_size = font->CalcTextSizeA(font_size, FLT_MAX, wrap_width, text, text_display_end, NULL);
4865
4866 // Cancel out character spacing for the last character of a line (it is baked into glyph->AdvanceX field)
4867 const float font_scale = font_size / font->FontSize;
4868 const float character_spacing_x = 1.0f * font_scale;
4869 if (text_size.x > 0.0f)
4870 text_size.x -= character_spacing_x;
4871 text_size.x = (float)(int)(text_size.x + 0.95f);
4872
4873 return text_size;
4874 }
4875
4876 // Helper to calculate coarse clipping of large list of evenly sized items.
4877 // NB: Prefer using the ImGuiListClipper higher-level helper if you can! Read comments and instructions there on how those use this sort of pattern.
4878 // NB: 'items_count' is only used to clamp the result, if you don't know your count you can use INT_MAX
4879 void ImGui::CalcListClipping(int items_count, float items_height, int* out_items_display_start, int* out_items_display_end)
4880 {
4881 ImGuiContext& g = *GImGui;
4882 ImGuiWindow* window = g.CurrentWindow;
4883 if (g.LogEnabled)
4884 {
4885 // If logging is active, do not perform any clipping
4886 *out_items_display_start = 0;
4887 *out_items_display_end = items_count;
4888 return;
4889 }
4890 if (window->SkipItems)
4891 {
4892 *out_items_display_start = *out_items_display_end = 0;
4893 return;
4894 }
4895
4896 // We create the union of the ClipRect and the NavScoringRect which at worst should be 1 page away from ClipRect
4897 ImRect unclipped_rect = window->ClipRect;
4898 if (g.NavMoveRequest)
4899 unclipped_rect.Add(g.NavScoringRectScreen);
4900
4901 const ImVec2 pos = window->DC.CursorPos;
4902 int start = (int)((unclipped_rect.Min.y - pos.y) / items_height);
4903 int end = (int)((unclipped_rect.Max.y - pos.y) / items_height);
4904
4905 // When performing a navigation request, ensure we have one item extra in the direction we are moving to
4906 if (g.NavMoveRequest && g.NavMoveClipDir == ImGuiDir_Up)
4907 start--;
4908 if (g.NavMoveRequest && g.NavMoveClipDir == ImGuiDir_Down)
4909 end++;
4910
4911 start = ImClamp(start, 0, items_count);
4912 end = ImClamp(end + 1, start, items_count);
4913 *out_items_display_start = start;
4914 *out_items_display_end = end;
4915 }
4916
4917 // Find window given position, search front-to-back
4918 // FIXME: Note that we have a lag here because WindowRectClipped is updated in Begin() so windows moved by user via SetWindowPos() and not SetNextWindowPos() will have that rectangle lagging by a frame at the time FindHoveredWindow() is called, aka before the next Begin(). Moving window thankfully isn't affected.
4919 static void FindHoveredWindow()
4920 {
4921 ImGuiContext& g = *GImGui;
4922
4923 ImGuiWindow* hovered_window = NULL;
4924 if (g.MovingWindow && !(g.MovingWindow->Flags & ImGuiWindowFlags_NoInputs))
4925 hovered_window = g.MovingWindow;
4926
4927 for (int i = g.Windows.Size - 1; i >= 0 && hovered_window == NULL; i--)
4928 {
4929 ImGuiWindow* window = g.Windows[i];
4930 if (!window->Active || window->Hidden)
4931 continue;
4932 if (window->Flags & ImGuiWindowFlags_NoInputs)
4933 continue;
4934
4935 // Using the clipped AABB, a child window will typically be clipped by its parent (not always)
4936 ImRect bb(window->OuterRectClipped.Min - g.Style.TouchExtraPadding, window->OuterRectClipped.Max + g.Style.TouchExtraPadding);
4937 if (bb.Contains(g.IO.MousePos))
4938 {
4939 if (hovered_window == NULL)
4940 hovered_window = window;
4941 if (hovered_window)
4942 break;
4943 }
4944 }
4945
4946 g.HoveredWindow = hovered_window;
4947 g.HoveredRootWindow = g.HoveredWindow ? g.HoveredWindow->RootWindow : NULL;
4948
4949 }
4950
4951 // Test if mouse cursor is hovering given rectangle
4952 // NB- Rectangle is clipped by our current clip setting
4953 // NB- Expand the rectangle to be generous on imprecise inputs systems (g.Style.TouchExtraPadding)
4954 bool ImGui::IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool clip)
4955 {
4956 ImGuiContext& g = *GImGui;
4957
4958 // Clip
4959 ImRect rect_clipped(r_min, r_max);
4960 if (clip)
4961 rect_clipped.ClipWith(g.CurrentWindow->ClipRect);
4962
4963 // Expand for touch input
4964 const ImRect rect_for_touch(rect_clipped.Min - g.Style.TouchExtraPadding, rect_clipped.Max + g.Style.TouchExtraPadding);
4965 return rect_for_touch.Contains(g.IO.MousePos);
4966 }
4967
4968 static bool IsKeyPressedMap(ImGuiKey key, bool repeat)
4969 {
4970 const int key_index = GImGui->IO.KeyMap[key];
4971 return (key_index >= 0) ? ImGui::IsKeyPressed(key_index, repeat) : false;
4972 }
4973
4974 int ImGui::GetKeyIndex(ImGuiKey imgui_key)
4975 {
4976 IM_ASSERT(imgui_key >= 0 && imgui_key < ImGuiKey_COUNT);
4977 return GImGui->IO.KeyMap[imgui_key];
4978 }
4979
4980 // Note that imgui doesn't know the semantic of each entry of io.KeysDown[]. Use your own indices/enums according to how your back-end/engine stored them into io.KeysDown[]!
4981 bool ImGui::IsKeyDown(int user_key_index)
4982 {
4983 if (user_key_index < 0) return false;
4984 IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(GImGui->IO.KeysDown));
4985 return GImGui->IO.KeysDown[user_key_index];
4986 }
4987
4988 int ImGui::CalcTypematicPressedRepeatAmount(float t, float t_prev, float repeat_delay, float repeat_rate)
4989 {
4990 if (t == 0.0f)
4991 return 1;
4992 if (t <= repeat_delay || repeat_rate <= 0.0f)
4993 return 0;
4994 const int count = (int)((t - repeat_delay) / repeat_rate) - (int)((t_prev - repeat_delay) / repeat_rate);
4995 return (count > 0) ? count : 0;
4996 }
4997
4998 int ImGui::GetKeyPressedAmount(int key_index, float repeat_delay, float repeat_rate)
4999 {
5000 ImGuiContext& g = *GImGui;
5001 if (key_index < 0) return false;
5002 IM_ASSERT(key_index >= 0 && key_index < IM_ARRAYSIZE(g.IO.KeysDown));
5003 const float t = g.IO.KeysDownDuration[key_index];
5004 return CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, repeat_delay, repeat_rate);
5005 }
5006
5007 bool ImGui::IsKeyPressed(int user_key_index, bool repeat)
5008 {
5009 ImGuiContext& g = *GImGui;
5010 if (user_key_index < 0) return false;
5011 IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(g.IO.KeysDown));
5012 const float t = g.IO.KeysDownDuration[user_key_index];
5013 if (t == 0.0f)
5014 return true;
5015 if (repeat && t > g.IO.KeyRepeatDelay)
5016 return GetKeyPressedAmount(user_key_index, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0;
5017 return false;
5018 }
5019
5020 bool ImGui::IsKeyReleased(int user_key_index)
5021 {
5022 ImGuiContext& g = *GImGui;
5023 if (user_key_index < 0) return false;
5024 IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(g.IO.KeysDown));
5025 return g.IO.KeysDownDurationPrev[user_key_index] >= 0.0f && !g.IO.KeysDown[user_key_index];
5026 }
5027
5028 bool ImGui::IsMouseDown(int button)
5029 {
5030 ImGuiContext& g = *GImGui;
5031 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
5032 return g.IO.MouseDown[button];
5033 }
5034
5035 bool ImGui::IsAnyMouseDown()
5036 {
5037 ImGuiContext& g = *GImGui;
5038 for (int n = 0; n < IM_ARRAYSIZE(g.IO.MouseDown); n++)
5039 if (g.IO.MouseDown[n])
5040 return true;
5041 return false;
5042 }
5043
5044 bool ImGui::IsMouseClicked(int button, bool repeat)
5045 {
5046 ImGuiContext& g = *GImGui;
5047 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
5048 const float t = g.IO.MouseDownDuration[button];
5049 if (t == 0.0f)
5050 return true;
5051
5052 if (repeat && t > g.IO.KeyRepeatDelay)
5053 {
5054 float delay = g.IO.KeyRepeatDelay, rate = g.IO.KeyRepeatRate;
5055 if ((ImFmod(t - delay, rate) > rate*0.5f) != (ImFmod(t - delay - g.IO.DeltaTime, rate) > rate*0.5f))
5056 return true;
5057 }
5058
5059 return false;
5060 }
5061
5062 bool ImGui::IsMouseReleased(int button)
5063 {
5064 ImGuiContext& g = *GImGui;
5065 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
5066 return g.IO.MouseReleased[button];
5067 }
5068
5069 bool ImGui::IsMouseDoubleClicked(int button)
5070 {
5071 ImGuiContext& g = *GImGui;
5072 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
5073 return g.IO.MouseDoubleClicked[button];
5074 }
5075
5076 bool ImGui::IsMouseDragging(int button, float lock_threshold)
5077 {
5078 ImGuiContext& g = *GImGui;
5079 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
5080 if (!g.IO.MouseDown[button])
5081 return false;
5082 if (lock_threshold < 0.0f)
5083 lock_threshold = g.IO.MouseDragThreshold;
5084 return g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold;
5085 }
5086
5087 ImVec2 ImGui::GetMousePos()
5088 {
5089 return GImGui->IO.MousePos;
5090 }
5091
5092 // NB: prefer to call right after BeginPopup(). At the time Selectable/MenuItem is activated, the popup is already closed!
5093 ImVec2 ImGui::GetMousePosOnOpeningCurrentPopup()
5094 {
5095 ImGuiContext& g = *GImGui;
5096 if (g.CurrentPopupStack.Size > 0)
5097 return g.OpenPopupStack[g.CurrentPopupStack.Size-1].OpenMousePos;
5098 return g.IO.MousePos;
5099 }
5100
5101 // We typically use ImVec2(-FLT_MAX,-FLT_MAX) to denote an invalid mouse position
5102 bool ImGui::IsMousePosValid(const ImVec2* mouse_pos)
5103 {
5104 if (mouse_pos == NULL)
5105 mouse_pos = &GImGui->IO.MousePos;
5106 const float MOUSE_INVALID = -256000.0f;
5107 return mouse_pos->x >= MOUSE_INVALID && mouse_pos->y >= MOUSE_INVALID;
5108 }
5109
5110 // NB: This is only valid if IsMousePosValid(). Back-ends in theory should always keep mouse position valid when dragging even outside the client window.
5111 ImVec2 ImGui::GetMouseDragDelta(int button, float lock_threshold)
5112 {
5113 ImGuiContext& g = *GImGui;
5114 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
5115 if (lock_threshold < 0.0f)
5116 lock_threshold = g.IO.MouseDragThreshold;
5117 if (g.IO.MouseDown[button])
5118 if (g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold)
5119 return g.IO.MousePos - g.IO.MouseClickedPos[button]; // Assume we can only get active with left-mouse button (at the moment).
5120 return ImVec2(0.0f, 0.0f);
5121 }
5122
5123 void ImGui::ResetMouseDragDelta(int button)
5124 {
5125 ImGuiContext& g = *GImGui;
5126 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
5127 // NB: We don't need to reset g.IO.MouseDragMaxDistanceSqr
5128 g.IO.MouseClickedPos[button] = g.IO.MousePos;
5129 }
5130
5131 ImGuiMouseCursor ImGui::GetMouseCursor()
5132 {
5133 return GImGui->MouseCursor;
5134 }
5135
5136 void ImGui::SetMouseCursor(ImGuiMouseCursor cursor_type)
5137 {
5138 GImGui->MouseCursor = cursor_type;
5139 }
5140
5141 void ImGui::CaptureKeyboardFromApp(bool capture)
5142 {
5143 GImGui->WantCaptureKeyboardNextFrame = capture ? 1 : 0;
5144 }
5145
5146 void ImGui::CaptureMouseFromApp(bool capture)
5147 {
5148 GImGui->WantCaptureMouseNextFrame = capture ? 1 : 0;
5149 }
5150
5151 bool ImGui::IsItemActive()
5152 {
5153 ImGuiContext& g = *GImGui;
5154 if (g.ActiveId)
5155 {
5156 ImGuiWindow* window = g.CurrentWindow;
5157 return g.ActiveId == window->DC.LastItemId;
5158 }
5159 return false;
5160 }
5161
5162 bool ImGui::IsItemDeactivated()
5163 {
5164 ImGuiContext& g = *GImGui;
5165 ImGuiWindow* window = g.CurrentWindow;
5166 return (g.ActiveIdPreviousFrame == window->DC.LastItemId && g.ActiveIdPreviousFrame != 0 && g.ActiveId != window->DC.LastItemId);
5167 }
5168
5169 bool ImGui::IsItemDeactivatedAfterChange()
5170 {
5171 ImGuiContext& g = *GImGui;
5172 return IsItemDeactivated() && (g.ActiveIdPreviousFrameValueChanged || (g.ActiveId == 0 && g.ActiveIdValueChanged));
5173 }
5174
5175 bool ImGui::IsItemFocused()
5176 {
5177 ImGuiContext& g = *GImGui;
5178 return g.NavId && !g.NavDisableHighlight && g.NavId == g.CurrentWindow->DC.LastItemId;
5179 }
5180
5181 bool ImGui::IsItemClicked(int mouse_button)
5182 {
5183 return IsMouseClicked(mouse_button) && IsItemHovered(ImGuiHoveredFlags_None);
5184 }
5185
5186 bool ImGui::IsAnyItemHovered()
5187 {
5188 ImGuiContext& g = *GImGui;
5189 return g.HoveredId != 0 || g.HoveredIdPreviousFrame != 0;
5190 }
5191
5192 bool ImGui::IsAnyItemActive()
5193 {
5194 ImGuiContext& g = *GImGui;
5195 return g.ActiveId != 0;
5196 }
5197
5198 bool ImGui::IsAnyItemFocused()
5199 {
5200 ImGuiContext& g = *GImGui;
5201 return g.NavId != 0 && !g.NavDisableHighlight;
5202 }
5203
5204 bool ImGui::IsItemVisible()
5205 {
5206 ImGuiWindow* window = GetCurrentWindowRead();
5207 return window->ClipRect.Overlaps(window->DC.LastItemRect);
5208 }
5209
5210 // Allow last item to be overlapped by a subsequent item. Both may be activated during the same frame before the later one takes priority.
5211 void ImGui::SetItemAllowOverlap()
5212 {
5213 ImGuiContext& g = *GImGui;
5214 if (g.HoveredId == g.CurrentWindow->DC.LastItemId)
5215 g.HoveredIdAllowOverlap = true;
5216 if (g.ActiveId == g.CurrentWindow->DC.LastItemId)
5217 g.ActiveIdAllowOverlap = true;
5218 }
5219
5220 ImVec2 ImGui::GetItemRectMin()
5221 {
5222 ImGuiWindow* window = GetCurrentWindowRead();
5223 return window->DC.LastItemRect.Min;
5224 }
5225
5226 ImVec2 ImGui::GetItemRectMax()
5227 {
5228 ImGuiWindow* window = GetCurrentWindowRead();
5229 return window->DC.LastItemRect.Max;
5230 }
5231
5232 ImVec2 ImGui::GetItemRectSize()
5233 {
5234 ImGuiWindow* window = GetCurrentWindowRead();
5235 return window->DC.LastItemRect.GetSize();
5236 }
5237
5238 static ImRect GetViewportRect()
5239 {
5240 ImGuiContext& g = *GImGui;
5241 if (g.IO.DisplayVisibleMin.x != g.IO.DisplayVisibleMax.x && g.IO.DisplayVisibleMin.y != g.IO.DisplayVisibleMax.y)
5242 return ImRect(g.IO.DisplayVisibleMin, g.IO.DisplayVisibleMax);
5243 return ImRect(0.0f, 0.0f, g.IO.DisplaySize.x, g.IO.DisplaySize.y);
5244 }
5245
5246 // Not exposed publicly as BeginTooltip() because bool parameters are evil. Let's see if other needs arise first.
5247 void ImGui::BeginTooltipEx(ImGuiWindowFlags extra_flags, bool override_previous_tooltip)
5248 {
5249 ImGuiContext& g = *GImGui;
5250 char window_name[16];
5251 ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", g.TooltipOverrideCount);
5252 if (override_previous_tooltip)
5253 if (ImGuiWindow* window = FindWindowByName(window_name))
5254 if (window->Active)
5255 {
5256 // Hide previous tooltip from being displayed. We can't easily "reset" the content of a window so we create a new one.
5257 window->Hidden = true;
5258 window->HiddenFramesRegular = 1;
5259 ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", ++g.TooltipOverrideCount);
5260 }
5261 ImGuiWindowFlags flags = ImGuiWindowFlags_Tooltip|ImGuiWindowFlags_NoInputs|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoNav;
5262 Begin(window_name, NULL, flags | extra_flags);
5263 }
5264
5265 void ImGui::SetTooltipV(const char* fmt, va_list args)
5266 {
5267 BeginTooltipEx(0, true);
5268 TextV(fmt, args);
5269 EndTooltip();
5270 }
5271
5272 void ImGui::SetTooltip(const char* fmt, ...)
5273 {
5274 va_list args;
5275 va_start(args, fmt);
5276 SetTooltipV(fmt, args);
5277 va_end(args);
5278 }
5279
5280 void ImGui::BeginTooltip()
5281 {
5282 ImGuiContext& g = *GImGui;
5283 if (g.DragDropWithinSourceOrTarget)
5284 {
5285 // 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)
5286 // In the context of a dragging tooltip we try to reduce that offset and we enforce following the cursor.
5287 // 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.
5288 //ImVec2 tooltip_pos = g.IO.MousePos - g.ActiveIdClickOffset - g.Style.WindowPadding;
5289 ImVec2 tooltip_pos = g.IO.MousePos + ImVec2(16 * g.Style.MouseCursorScale, 8 * g.Style.MouseCursorScale);
5290 SetNextWindowPos(tooltip_pos);
5291 SetNextWindowBgAlpha(g.Style.Colors[ImGuiCol_PopupBg].w * 0.60f);
5292 //PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * 0.60f); // This would be nice but e.g ColorButton with checkboard has issue with transparent colors :(
5293 BeginTooltipEx(0, true);
5294 }
5295 else
5296 {
5297 BeginTooltipEx(0, false);
5298 }
5299 }
5300
5301 void ImGui::EndTooltip()
5302 {
5303 IM_ASSERT(GetCurrentWindowRead()->Flags & ImGuiWindowFlags_Tooltip); // Mismatched BeginTooltip()/EndTooltip() calls
5304 End();
5305 }
5306
5307 // Mark popup as open (toggle toward open state).
5308 // Popups are closed when user click outside, or activate a pressable item, or CloseCurrentPopup() is called within a BeginPopup()/EndPopup() block.
5309 // Popup identifiers are relative to the current ID-stack (so OpenPopup and BeginPopup needs to be at the same level).
5310 // One open popup per level of the popup hierarchy (NB: when assigning we reset the Window member of ImGuiPopupRef to NULL)
5311 void ImGui::OpenPopupEx(ImGuiID id)
5312 {
5313 ImGuiContext& g = *GImGui;
5314 ImGuiWindow* parent_window = g.CurrentWindow;
5315 int current_stack_size = g.CurrentPopupStack.Size;
5316 ImGuiPopupRef popup_ref; // Tagged as new ref as Window will be set back to NULL if we write this into OpenPopupStack.
5317 popup_ref.PopupId = id;
5318 popup_ref.Window = NULL;
5319 popup_ref.ParentWindow = parent_window;
5320 popup_ref.OpenFrameCount = g.FrameCount;
5321 popup_ref.OpenParentId = parent_window->IDStack.back();
5322 popup_ref.OpenMousePos = g.IO.MousePos;
5323 popup_ref.OpenPopupPos = NavCalcPreferredRefPos();
5324
5325 //printf("[%05d] OpenPopupEx(0x%08X)\n", g.FrameCount, id);
5326 if (g.OpenPopupStack.Size < current_stack_size + 1)
5327 {
5328 g.OpenPopupStack.push_back(popup_ref);
5329 }
5330 else
5331 {
5332 // 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
5333 // 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
5334 // 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.
5335 if (g.OpenPopupStack[current_stack_size].PopupId == id && g.OpenPopupStack[current_stack_size].OpenFrameCount == g.FrameCount - 1)
5336 {
5337 g.OpenPopupStack[current_stack_size].OpenFrameCount = popup_ref.OpenFrameCount;
5338 }
5339 else
5340 {
5341 // Close child popups if any, then flag popup for open/reopen
5342 g.OpenPopupStack.resize(current_stack_size + 1);
5343 g.OpenPopupStack[current_stack_size] = popup_ref;
5344 }
5345
5346 // When reopening a popup we first refocus its parent, otherwise if its parent is itself a popup it would get closed by ClosePopupsOverWindow().
5347 // This is equivalent to what ClosePopupToLevel() does.
5348 //if (g.OpenPopupStack[current_stack_size].PopupId == id)
5349 // FocusWindow(parent_window);
5350 }
5351 }
5352
5353 void ImGui::OpenPopup(const char* str_id)
5354 {
5355 ImGuiContext& g = *GImGui;
5356 OpenPopupEx(g.CurrentWindow->GetID(str_id));
5357 }
5358
5359 void ImGui::ClosePopupsOverWindow(ImGuiWindow* ref_window)
5360 {
5361 ImGuiContext& g = *GImGui;
5362 if (g.OpenPopupStack.empty())
5363 return;
5364
5365 // When popups are stacked, clicking on a lower level popups puts focus back to it and close popups above it.
5366 // Don't close our own child popup windows.
5367 int n = 0;
5368 if (ref_window)
5369 {
5370 for (n = 0; n < g.OpenPopupStack.Size; n++)
5371 {
5372 ImGuiPopupRef& popup = g.OpenPopupStack[n];
5373 if (!popup.Window)
5374 continue;
5375 IM_ASSERT((popup.Window->Flags & ImGuiWindowFlags_Popup) != 0);
5376 if (popup.Window->Flags & ImGuiWindowFlags_ChildWindow)
5377 continue;
5378
5379 // Trim the stack if popups are not direct descendant of the reference window (which is often the NavWindow)
5380 bool has_focus = false;
5381 for (int m = n; m < g.OpenPopupStack.Size && !has_focus; m++)
5382 has_focus = (g.OpenPopupStack[m].Window && g.OpenPopupStack[m].Window->RootWindow == ref_window->RootWindow);
5383 if (!has_focus)
5384 break;
5385 }
5386 }
5387 if (n < g.OpenPopupStack.Size) // This test is not required but it allows to set a convenient breakpoint on the block below
5388 ClosePopupToLevel(n);
5389 }
5390
5391 ImGuiWindow* ImGui::GetFrontMostPopupModal()
5392 {
5393 ImGuiContext& g = *GImGui;
5394 for (int n = g.OpenPopupStack.Size-1; n >= 0; n--)
5395 if (ImGuiWindow* popup = g.OpenPopupStack.Data[n].Window)
5396 if (popup->Flags & ImGuiWindowFlags_Modal)
5397 return popup;
5398 return NULL;
5399 }
5400
5401 void ImGui::ClosePopupToLevel(int remaining)
5402 {
5403 IM_ASSERT(remaining >= 0);
5404 ImGuiContext& g = *GImGui;
5405 ImGuiWindow* focus_window = (remaining > 0) ? g.OpenPopupStack[remaining-1].Window : g.OpenPopupStack[0].ParentWindow;
5406 if (g.NavLayer == 0)
5407 focus_window = NavRestoreLastChildNavWindow(focus_window);
5408 FocusWindow(focus_window);
5409 focus_window->DC.NavHideHighlightOneFrame = true;
5410 g.OpenPopupStack.resize(remaining);
5411 }
5412
5413 void ImGui::ClosePopup(ImGuiID id)
5414 {
5415 if (!IsPopupOpen(id))
5416 return;
5417 ImGuiContext& g = *GImGui;
5418 ClosePopupToLevel(g.OpenPopupStack.Size - 1);
5419 }
5420
5421 // Close the popup we have begin-ed into.
5422 void ImGui::CloseCurrentPopup()
5423 {
5424 ImGuiContext& g = *GImGui;
5425 int popup_idx = g.CurrentPopupStack.Size - 1;
5426 if (popup_idx < 0 || popup_idx >= g.OpenPopupStack.Size || g.CurrentPopupStack[popup_idx].PopupId != g.OpenPopupStack[popup_idx].PopupId)
5427 return;
5428 while (popup_idx > 0 && g.OpenPopupStack[popup_idx].Window && (g.OpenPopupStack[popup_idx].Window->Flags & ImGuiWindowFlags_ChildMenu))
5429 popup_idx--;
5430 ClosePopupToLevel(popup_idx);
5431 }
5432
5433 bool ImGui::BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_flags)
5434 {
5435 ImGuiContext& g = *GImGui;
5436 if (!IsPopupOpen(id))
5437 {
5438 g.NextWindowData.Clear(); // We behave like Begin() and need to consume those values
5439 return false;
5440 }
5441
5442 char name[20];
5443 if (extra_flags & ImGuiWindowFlags_ChildMenu)
5444 ImFormatString(name, IM_ARRAYSIZE(name), "##Menu_%02d", g.CurrentPopupStack.Size); // Recycle windows based on depth
5445 else
5446 ImFormatString(name, IM_ARRAYSIZE(name), "##Popup_%08x", id); // Not recycling, so we can close/open during the same frame
5447
5448 bool is_open = Begin(name, NULL, extra_flags | ImGuiWindowFlags_Popup);
5449 if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display)
5450 EndPopup();
5451
5452 return is_open;
5453 }
5454
5455 bool ImGui::BeginPopup(const char* str_id, ImGuiWindowFlags flags)
5456 {
5457 ImGuiContext& g = *GImGui;
5458 if (g.OpenPopupStack.Size <= g.CurrentPopupStack.Size) // Early out for performance
5459 {
5460 g.NextWindowData.Clear(); // We behave like Begin() and need to consume those values
5461 return false;
5462 }
5463 return BeginPopupEx(g.CurrentWindow->GetID(str_id), flags|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings);
5464 }
5465
5466 bool ImGui::IsPopupOpen(ImGuiID id)
5467 {
5468 ImGuiContext& g = *GImGui;
5469 return g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].PopupId == id;
5470 }
5471
5472 bool ImGui::IsPopupOpen(const char* str_id)
5473 {
5474 ImGuiContext& g = *GImGui;
5475 return g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].PopupId == g.CurrentWindow->GetID(str_id);
5476 }
5477
5478 bool ImGui::BeginPopupModal(const char* name, bool* p_open, ImGuiWindowFlags flags)
5479 {
5480 ImGuiContext& g = *GImGui;
5481 ImGuiWindow* window = g.CurrentWindow;
5482 const ImGuiID id = window->GetID(name);
5483 if (!IsPopupOpen(id))
5484 {
5485 g.NextWindowData.Clear(); // We behave like Begin() and need to consume those values
5486 return false;
5487 }
5488
5489 // Center modal windows by default
5490 // FIXME: Should test for (PosCond & window->SetWindowPosAllowFlags) with the upcoming window.
5491 if (g.NextWindowData.PosCond == 0)
5492 SetNextWindowPos(g.IO.DisplaySize * 0.5f, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
5493
5494 bool is_open = Begin(name, p_open, flags | ImGuiWindowFlags_Popup | ImGuiWindowFlags_Modal | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings);
5495 if (!is_open || (p_open && !*p_open)) // NB: is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
5496 {
5497 EndPopup();
5498 if (is_open)
5499 ClosePopup(id);
5500 return false;
5501 }
5502
5503 return is_open;
5504 }
5505
5506 void ImGui::NavMoveRequestForward(ImGuiDir move_dir, ImGuiDir clip_dir, const ImRect& bb_rel, ImGuiNavMoveFlags move_flags)
5507 {
5508 ImGuiContext& g = *GImGui;
5509 IM_ASSERT(g.NavMoveRequestForward == ImGuiNavForward_None);
5510 ImGui::NavMoveRequestCancel();
5511 g.NavMoveDir = move_dir;
5512 g.NavMoveClipDir = clip_dir;
5513 g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued;
5514 g.NavMoveRequestFlags = move_flags;
5515 g.NavWindow->NavRectRel[g.NavLayer] = bb_rel;
5516 }
5517
5518 void ImGui::NavMoveRequestTryWrapping(ImGuiWindow* window, ImGuiNavMoveFlags move_flags)
5519 {
5520 ImGuiContext& g = *GImGui;
5521 if (g.NavWindow != window || !NavMoveRequestButNoResultYet() || g.NavMoveRequestForward != ImGuiNavForward_None || g.NavLayer != 0)
5522 return;
5523 IM_ASSERT(move_flags != 0); // No points calling this with no wrapping
5524 ImRect bb_rel = window->NavRectRel[0];
5525
5526 ImGuiDir clip_dir = g.NavMoveDir;
5527 if (g.NavMoveDir == ImGuiDir_Left && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX)))
5528 {
5529 bb_rel.Min.x = bb_rel.Max.x = ImMax(window->SizeFull.x, window->SizeContents.x) - window->Scroll.x;
5530 if (move_flags & ImGuiNavMoveFlags_WrapX) { bb_rel.TranslateY(-bb_rel.GetHeight()); clip_dir = ImGuiDir_Up; }
5531 NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags);
5532 }
5533 if (g.NavMoveDir == ImGuiDir_Right && (move_flags & (ImGuiNavMoveFlags_WrapX | ImGuiNavMoveFlags_LoopX)))
5534 {
5535 bb_rel.Min.x = bb_rel.Max.x = -window->Scroll.x;
5536 if (move_flags & ImGuiNavMoveFlags_WrapX) { bb_rel.TranslateY(+bb_rel.GetHeight()); clip_dir = ImGuiDir_Down; }
5537 NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags);
5538 }
5539 if (g.NavMoveDir == ImGuiDir_Up && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY)))
5540 {
5541 bb_rel.Min.y = bb_rel.Max.y = ImMax(window->SizeFull.y, window->SizeContents.y) - window->Scroll.y;
5542 if (move_flags & ImGuiNavMoveFlags_WrapY) { bb_rel.TranslateX(-bb_rel.GetWidth()); clip_dir = ImGuiDir_Left; }
5543 NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags);
5544 }
5545 if (g.NavMoveDir == ImGuiDir_Down && (move_flags & (ImGuiNavMoveFlags_WrapY | ImGuiNavMoveFlags_LoopY)))
5546 {
5547 bb_rel.Min.y = bb_rel.Max.y = -window->Scroll.y;
5548 if (move_flags & ImGuiNavMoveFlags_WrapY) { bb_rel.TranslateX(+bb_rel.GetWidth()); clip_dir = ImGuiDir_Right; }
5549 NavMoveRequestForward(g.NavMoveDir, clip_dir, bb_rel, move_flags);
5550 }
5551 }
5552
5553 void ImGui::EndPopup()
5554 {
5555 ImGuiContext& g = *GImGui; (void)g;
5556 IM_ASSERT(g.CurrentWindow->Flags & ImGuiWindowFlags_Popup); // Mismatched BeginPopup()/EndPopup() calls
5557 IM_ASSERT(g.CurrentPopupStack.Size > 0);
5558
5559 // Make all menus and popups wrap around for now, may need to expose that policy.
5560 NavMoveRequestTryWrapping(g.CurrentWindow, ImGuiNavMoveFlags_LoopY);
5561
5562 End();
5563 }
5564
5565 bool ImGui::OpenPopupOnItemClick(const char* str_id, int mouse_button)
5566 {
5567 ImGuiWindow* window = GImGui->CurrentWindow;
5568 if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup))
5569 {
5570 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!
5571 IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item)
5572 OpenPopupEx(id);
5573 return true;
5574 }
5575 return false;
5576 }
5577
5578 // This is a helper to handle the simplest case of associating one named popup to one given widget.
5579 // You may want to handle this on user side if you have specific needs (e.g. tweaking IsItemHovered() parameters).
5580 // You can pass a NULL str_id to use the identifier of the last item.
5581 bool ImGui::BeginPopupContextItem(const char* str_id, int mouse_button)
5582 {
5583 ImGuiWindow* window = GImGui->CurrentWindow;
5584 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!
5585 IM_ASSERT(id != 0); // You cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item)
5586 if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup))
5587 OpenPopupEx(id);
5588 return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings);
5589 }
5590
5591 bool ImGui::BeginPopupContextWindow(const char* str_id, int mouse_button, bool also_over_items)
5592 {
5593 if (!str_id)
5594 str_id = "window_context";
5595 ImGuiID id = GImGui->CurrentWindow->GetID(str_id);
5596 if (IsMouseReleased(mouse_button) && IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup))
5597 if (also_over_items || !IsAnyItemHovered())
5598 OpenPopupEx(id);
5599 return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings);
5600 }
5601
5602 bool ImGui::BeginPopupContextVoid(const char* str_id, int mouse_button)
5603 {
5604 if (!str_id)
5605 str_id = "void_context";
5606 ImGuiID id = GImGui->CurrentWindow->GetID(str_id);
5607 if (IsMouseReleased(mouse_button) && !IsWindowHovered(ImGuiHoveredFlags_AnyWindow))
5608 OpenPopupEx(id);
5609 return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings);
5610 }
5611
5612 static bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags flags)
5613 {
5614 ImGuiContext& g = *GImGui;
5615 ImGuiWindow* parent_window = g.CurrentWindow;
5616
5617 flags |= ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoSavedSettings|ImGuiWindowFlags_ChildWindow;
5618 flags |= (parent_window->Flags & ImGuiWindowFlags_NoMove); // Inherit the NoMove flag
5619
5620 // Size
5621 const ImVec2 content_avail = GetContentRegionAvail();
5622 ImVec2 size = ImFloor(size_arg);
5623 const int auto_fit_axises = ((size.x == 0.0f) ? (1 << ImGuiAxis_X) : 0x00) | ((size.y == 0.0f) ? (1 << ImGuiAxis_Y) : 0x00);
5624 if (size.x <= 0.0f)
5625 size.x = ImMax(content_avail.x + size.x, 4.0f); // Arbitrary minimum child size (0.0f causing too much issues)
5626 if (size.y <= 0.0f)
5627 size.y = ImMax(content_avail.y + size.y, 4.0f);
5628 SetNextWindowSize(size);
5629
5630 // Name
5631 char title[256];
5632 if (name)
5633 ImFormatString(title, IM_ARRAYSIZE(title), "%s/%s", parent_window->Name, name);
5634 else
5635 ImFormatString(title, IM_ARRAYSIZE(title), "%s/%08X", parent_window->Name, id);
5636
5637 const float backup_border_size = g.Style.ChildBorderSize;
5638 if (!border)
5639 g.Style.ChildBorderSize = 0.0f;
5640 bool ret = Begin(title, NULL, flags);
5641 g.Style.ChildBorderSize = backup_border_size;
5642
5643 ImGuiWindow* child_window = g.CurrentWindow;
5644 child_window->ChildId = id;
5645 child_window->AutoFitChildAxises = auto_fit_axises;
5646
5647 // Process navigation-in immediately so NavInit can run on first frame
5648 if (g.NavActivateId == id && !(flags & ImGuiWindowFlags_NavFlattened) && (child_window->DC.NavLayerActiveMask != 0 || child_window->DC.NavHasScroll))
5649 {
5650 FocusWindow(child_window);
5651 NavInitWindow(child_window, false);
5652 SetActiveID(id+1, child_window); // Steal ActiveId with a dummy id so that key-press won't activate child item
5653 g.ActiveIdSource = ImGuiInputSource_Nav;
5654 }
5655
5656 return ret;
5657 }
5658
5659 bool ImGui::BeginChild(const char* str_id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags)
5660 {
5661 ImGuiWindow* window = GetCurrentWindow();
5662 return BeginChildEx(str_id, window->GetID(str_id), size_arg, border, extra_flags);
5663 }
5664
5665 bool ImGui::BeginChild(ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags)
5666 {
5667 IM_ASSERT(id != 0);
5668 return BeginChildEx(NULL, id, size_arg, border, extra_flags);
5669 }
5670
5671 void ImGui::EndChild()
5672 {
5673 ImGuiContext& g = *GImGui;
5674 ImGuiWindow* window = g.CurrentWindow;
5675
5676 IM_ASSERT(window->Flags & ImGuiWindowFlags_ChildWindow); // Mismatched BeginChild()/EndChild() callss
5677 if (window->BeginCount > 1)
5678 {
5679 End();
5680 }
5681 else
5682 {
5683 ImVec2 sz = window->Size;
5684 if (window->AutoFitChildAxises & (1 << ImGuiAxis_X)) // Arbitrary minimum zero-ish child size of 4.0f causes less trouble than a 0.0f
5685 sz.x = ImMax(4.0f, sz.x);
5686 if (window->AutoFitChildAxises & (1 << ImGuiAxis_Y))
5687 sz.y = ImMax(4.0f, sz.y);
5688 End();
5689
5690 ImGuiWindow* parent_window = g.CurrentWindow;
5691 ImRect bb(parent_window->DC.CursorPos, parent_window->DC.CursorPos + sz);
5692 ItemSize(sz);
5693 if ((window->DC.NavLayerActiveMask != 0 || window->DC.NavHasScroll) && !(window->Flags & ImGuiWindowFlags_NavFlattened))
5694 {
5695 ItemAdd(bb, window->ChildId);
5696 RenderNavHighlight(bb, window->ChildId);
5697
5698 // When browsing a window that has no activable items (scroll only) we keep a highlight on the child
5699 if (window->DC.NavLayerActiveMask == 0 && window == g.NavWindow)
5700 RenderNavHighlight(ImRect(bb.Min - ImVec2(2,2), bb.Max + ImVec2(2,2)), g.NavId, ImGuiNavHighlightFlags_TypeThin);
5701 }
5702 else
5703 {
5704 // Not navigable into
5705 ItemAdd(bb, 0);
5706 }
5707 }
5708 }
5709
5710 // Helper to create a child window / scrolling region that looks like a normal widget frame.
5711 bool ImGui::BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags extra_flags)
5712 {
5713 ImGuiContext& g = *GImGui;
5714 const ImGuiStyle& style = g.Style;
5715 PushStyleColor(ImGuiCol_ChildBg, style.Colors[ImGuiCol_FrameBg]);
5716 PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding);
5717 PushStyleVar(ImGuiStyleVar_ChildBorderSize, style.FrameBorderSize);
5718 PushStyleVar(ImGuiStyleVar_WindowPadding, style.FramePadding);
5719 bool ret = BeginChild(id, size, true, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysUseWindowPadding | extra_flags);
5720 PopStyleVar(3);
5721 PopStyleColor();
5722 return ret;
5723 }
5724
5725 void ImGui::EndChildFrame()
5726 {
5727 EndChild();
5728 }
5729
5730 // Save and compare stack sizes on Begin()/End() to detect usage errors
5731 static void CheckStacksSize(ImGuiWindow* window, bool write)
5732 {
5733 // 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)
5734 ImGuiContext& g = *GImGui;
5735 int* p_backup = &window->DC.StackSizesBackup[0];
5736 { int current = window->IDStack.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "PushID/PopID or TreeNode/TreePop Mismatch!"); p_backup++; } // Too few or too many PopID()/TreePop()
5737 { int current = window->DC.GroupStack.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "BeginGroup/EndGroup Mismatch!"); p_backup++; } // Too few or too many EndGroup()
5738 { int current = g.CurrentPopupStack.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup == current && "BeginMenu/EndMenu or BeginPopup/EndPopup Mismatch"); p_backup++;}// Too few or too many EndMenu()/EndPopup()
5739 // 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.
5740 { int current = g.ColorModifiers.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup >= current && "PushStyleColor/PopStyleColor Mismatch!"); p_backup++; } // Too few or too many PopStyleColor()
5741 { int current = g.StyleModifiers.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup >= current && "PushStyleVar/PopStyleVar Mismatch!"); p_backup++; } // Too few or too many PopStyleVar()
5742 { int current = g.FontStack.Size; if (write) *p_backup = current; else IM_ASSERT(*p_backup >= current && "PushFont/PopFont Mismatch!"); p_backup++; } // Too few or too many PopFont()
5743 IM_ASSERT(p_backup == window->DC.StackSizesBackup + IM_ARRAYSIZE(window->DC.StackSizesBackup));
5744 }
5745
5746 enum ImGuiPopupPositionPolicy
5747 {
5748 ImGuiPopupPositionPolicy_Default,
5749 ImGuiPopupPositionPolicy_ComboBox
5750 };
5751
5752 static ImRect FindAllowedExtentRectForWindow(ImGuiWindow*)
5753 {
5754 ImVec2 padding = GImGui->Style.DisplaySafeAreaPadding;
5755 ImRect r_screen = GetViewportRect();
5756 r_screen.Expand(ImVec2((r_screen.GetWidth() > padding.x * 2) ? -padding.x : 0.0f, (r_screen.GetHeight() > padding.y * 2) ? -padding.y : 0.0f));
5757 return r_screen;
5758 }
5759
5760 // 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.)
5761 // 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.
5762 static ImVec2 FindBestWindowPosForPopupEx(const ImVec2& ref_pos, const ImVec2& size, ImGuiDir* last_dir, const ImRect& r_outer, const ImRect& r_avoid, ImGuiPopupPositionPolicy policy = ImGuiPopupPositionPolicy_Default)
5763 {
5764 ImVec2 base_pos_clamped = ImClamp(ref_pos, r_outer.Min, r_outer.Max - size);
5765 //GImGui->OverlayDrawList.AddRect(r_avoid.Min, r_avoid.Max, IM_COL32(255,0,0,255));
5766 //GImGui->OverlayDrawList.AddRect(r_outer.Min, r_outer.Max, IM_COL32(0,255,0,255));
5767
5768 // Combo Box policy (we want a connecting edge)
5769 if (policy == ImGuiPopupPositionPolicy_ComboBox)
5770 {
5771 const ImGuiDir dir_prefered_order[ImGuiDir_COUNT] = { ImGuiDir_Down, ImGuiDir_Right, ImGuiDir_Left, ImGuiDir_Up };
5772 for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_COUNT; n++)
5773 {
5774 const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n];
5775 if (n != -1 && dir == *last_dir) // Already tried this direction?
5776 continue;
5777 ImVec2 pos;
5778 if (dir == ImGuiDir_Down) pos = ImVec2(r_avoid.Min.x, r_avoid.Max.y); // Below, Toward Right (default)
5779 if (dir == ImGuiDir_Right) pos = ImVec2(r_avoid.Min.x, r_avoid.Min.y - size.y); // Above, Toward Right
5780 if (dir == ImGuiDir_Left) pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Max.y); // Below, Toward Left
5781 if (dir == ImGuiDir_Up) pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Min.y - size.y); // Above, Toward Left
5782 if (!r_outer.Contains(ImRect(pos, pos + size)))
5783 continue;
5784 *last_dir = dir;
5785 return pos;
5786 }
5787 }
5788
5789 // Default popup policy
5790 const ImGuiDir dir_prefered_order[ImGuiDir_COUNT] = { ImGuiDir_Right, ImGuiDir_Down, ImGuiDir_Up, ImGuiDir_Left };
5791 for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_COUNT; n++)
5792 {
5793 const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n];
5794 if (n != -1 && dir == *last_dir) // Already tried this direction?
5795 continue;
5796 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);
5797 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);
5798 if (avail_w < size.x || avail_h < size.y)
5799 continue;
5800 ImVec2 pos;
5801 pos.x = (dir == ImGuiDir_Left) ? r_avoid.Min.x - size.x : (dir == ImGuiDir_Right) ? r_avoid.Max.x : base_pos_clamped.x;
5802 pos.y = (dir == ImGuiDir_Up) ? r_avoid.Min.y - size.y : (dir == ImGuiDir_Down) ? r_avoid.Max.y : base_pos_clamped.y;
5803 *last_dir = dir;
5804 return pos;
5805 }
5806
5807 // Fallback, try to keep within display
5808 *last_dir = ImGuiDir_None;
5809 ImVec2 pos = ref_pos;
5810 pos.x = ImMax(ImMin(pos.x + size.x, r_outer.Max.x) - size.x, r_outer.Min.x);
5811 pos.y = ImMax(ImMin(pos.y + size.y, r_outer.Max.y) - size.y, r_outer.Min.y);
5812 return pos;
5813 }
5814
5815 static ImVec2 FindBestWindowPosForPopup(ImGuiWindow* window)
5816 {
5817 ImGuiContext& g = *GImGui;
5818
5819 ImRect r_outer = FindAllowedExtentRectForWindow(window);
5820 if (window->Flags & ImGuiWindowFlags_ChildMenu)
5821 {
5822 // Child menus typically request _any_ position within the parent menu item, and then our FindBestWindowPosForPopup() function will move the new menu outside the parent bounds.
5823 // This is how we end up with child menus appearing (most-commonly) on the right of the parent menu.
5824 IM_ASSERT(g.CurrentWindow == window);
5825 ImGuiWindow* parent_window = g.CurrentWindowStack[g.CurrentWindowStack.Size - 2];
5826 float horizontal_overlap = g.Style.ItemSpacing.x; // We want some overlap to convey the relative depth of each menu (currently the amount of overlap is hard-coded to style.ItemSpacing.x).
5827 ImRect r_avoid;
5828 if (parent_window->DC.MenuBarAppending)
5829 r_avoid = ImRect(-FLT_MAX, parent_window->Pos.y + parent_window->TitleBarHeight(), FLT_MAX, parent_window->Pos.y + parent_window->TitleBarHeight() + parent_window->MenuBarHeight());
5830 else
5831 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);
5832 return FindBestWindowPosForPopupEx(window->Pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid);
5833 }
5834 if (window->Flags & ImGuiWindowFlags_Popup)
5835 {
5836 ImRect r_avoid = ImRect(window->Pos.x - 1, window->Pos.y - 1, window->Pos.x + 1, window->Pos.y + 1);
5837 return FindBestWindowPosForPopupEx(window->Pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid);
5838 }
5839 if (window->Flags & ImGuiWindowFlags_Tooltip)
5840 {
5841 // Position tooltip (always follows mouse)
5842 float sc = g.Style.MouseCursorScale;
5843 ImVec2 ref_pos = NavCalcPreferredRefPos();
5844 ImRect r_avoid;
5845 if (!g.NavDisableHighlight && g.NavDisableMouseHover && !(g.IO.ConfigFlags & ImGuiConfigFlags_NavEnableSetMousePos))
5846 r_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 16, ref_pos.y + 8);
5847 else
5848 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.
5849 ImVec2 pos = FindBestWindowPosForPopupEx(ref_pos, window->Size, &window->AutoPosLastDirection, r_outer, r_avoid);
5850 if (window->AutoPosLastDirection == ImGuiDir_None)
5851 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.
5852 return pos;
5853 }
5854 IM_ASSERT(0);
5855 return window->Pos;
5856 }
5857
5858 static void SetWindowConditionAllowFlags(ImGuiWindow* window, ImGuiCond flags, bool enabled)
5859 {
5860 window->SetWindowPosAllowFlags = enabled ? (window->SetWindowPosAllowFlags | flags) : (window->SetWindowPosAllowFlags & ~flags);
5861 window->SetWindowSizeAllowFlags = enabled ? (window->SetWindowSizeAllowFlags | flags) : (window->SetWindowSizeAllowFlags & ~flags);
5862 window->SetWindowCollapsedAllowFlags = enabled ? (window->SetWindowCollapsedAllowFlags | flags) : (window->SetWindowCollapsedAllowFlags & ~flags);
5863 }
5864
5865 ImGuiWindow* ImGui::FindWindowByName(const char* name)
5866 {
5867 ImGuiContext& g = *GImGui;
5868 ImGuiID id = ImHash(name, 0);
5869 return (ImGuiWindow*)g.WindowsById.GetVoidPtr(id);
5870 }
5871
5872 static ImGuiWindow* CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFlags flags)
5873 {
5874 ImGuiContext& g = *GImGui;
5875
5876 // Create window the first time
5877 ImGuiWindow* window = IM_NEW(ImGuiWindow)(&g, name);
5878 window->Flags = flags;
5879 g.WindowsById.SetVoidPtr(window->ID, window);
5880
5881 // Default/arbitrary window position. Use SetNextWindowPos() with the appropriate condition flag to change the initial position of a window.
5882 window->Pos = ImVec2(60, 60);
5883
5884 // User can disable loading and saving of settings. Tooltip and child windows also don't store settings.
5885 if (!(flags & ImGuiWindowFlags_NoSavedSettings))
5886 if (ImGuiWindowSettings* settings = ImGui::FindWindowSettings(window->ID))
5887 {
5888 // Retrieve settings from .ini file
5889 window->SettingsIdx = g.SettingsWindows.index_from_pointer(settings);
5890 SetWindowConditionAllowFlags(window, ImGuiCond_FirstUseEver, false);
5891 window->Pos = ImFloor(settings->Pos);
5892 window->Collapsed = settings->Collapsed;
5893 if (ImLengthSqr(settings->Size) > 0.00001f)
5894 size = ImFloor(settings->Size);
5895 }
5896 window->Size = window->SizeFull = window->SizeFullAtLastBegin = size;
5897 window->DC.CursorMaxPos = window->Pos; // So first call to CalcSizeContents() doesn't return crazy values
5898
5899 if ((flags & ImGuiWindowFlags_AlwaysAutoResize) != 0)
5900 {
5901 window->AutoFitFramesX = window->AutoFitFramesY = 2;
5902 window->AutoFitOnlyGrows = false;
5903 }
5904 else
5905 {
5906 if (window->Size.x <= 0.0f)
5907 window->AutoFitFramesX = 2;
5908 if (window->Size.y <= 0.0f)
5909 window->AutoFitFramesY = 2;
5910 window->AutoFitOnlyGrows = (window->AutoFitFramesX > 0) || (window->AutoFitFramesY > 0);
5911 }
5912
5913 if (flags & ImGuiWindowFlags_NoBringToFrontOnFocus)
5914 g.Windows.insert(g.Windows.begin(), window); // Quite slow but rare and only once
5915 else
5916 g.Windows.push_back(window);
5917 return window;
5918 }
5919
5920 static ImVec2 CalcSizeAfterConstraint(ImGuiWindow* window, ImVec2 new_size)
5921 {
5922 ImGuiContext& g = *GImGui;
5923 if (g.NextWindowData.SizeConstraintCond != 0)
5924 {
5925 // Using -1,-1 on either X/Y axis to preserve the current size.
5926 ImRect cr = g.NextWindowData.SizeConstraintRect;
5927 new_size.x = (cr.Min.x >= 0 && cr.Max.x >= 0) ? ImClamp(new_size.x, cr.Min.x, cr.Max.x) : window->SizeFull.x;
5928 new_size.y = (cr.Min.y >= 0 && cr.Max.y >= 0) ? ImClamp(new_size.y, cr.Min.y, cr.Max.y) : window->SizeFull.y;
5929 if (g.NextWindowData.SizeCallback)
5930 {
5931 ImGuiSizeCallbackData data;
5932 data.UserData = g.NextWindowData.SizeCallbackUserData;
5933 data.Pos = window->Pos;
5934 data.CurrentSize = window->SizeFull;
5935 data.DesiredSize = new_size;
5936 g.NextWindowData.SizeCallback(&data);
5937 new_size = data.DesiredSize;
5938 }
5939 }
5940
5941 // Minimum size
5942 if (!(window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_AlwaysAutoResize)))
5943 {
5944 new_size = ImMax(new_size, g.Style.WindowMinSize);
5945 new_size.y = ImMax(new_size.y, window->TitleBarHeight() + window->MenuBarHeight() + ImMax(0.0f, g.Style.WindowRounding - 1.0f)); // Reduce artifacts with very small windows
5946 }
5947 return new_size;
5948 }
5949
5950 static ImVec2 CalcSizeContents(ImGuiWindow* window)
5951 {
5952 ImVec2 sz;
5953 sz.x = (float)(int)((window->SizeContentsExplicit.x != 0.0f) ? window->SizeContentsExplicit.x : (window->DC.CursorMaxPos.x - window->Pos.x + window->Scroll.x));
5954 sz.y = (float)(int)((window->SizeContentsExplicit.y != 0.0f) ? window->SizeContentsExplicit.y : (window->DC.CursorMaxPos.y - window->Pos.y + window->Scroll.y));
5955 return sz + window->WindowPadding;
5956 }
5957
5958 static ImVec2 CalcSizeAutoFit(ImGuiWindow* window, const ImVec2& size_contents)
5959 {
5960 ImGuiContext& g = *GImGui;
5961 ImGuiStyle& style = g.Style;
5962 if (window->Flags & ImGuiWindowFlags_Tooltip)
5963 {
5964 // Tooltip always resize
5965 return size_contents;
5966 }
5967 else
5968 {
5969 // When the window cannot fit all contents (either because of constraints, either because screen is too small): we are growing the size on the other axis to compensate for expected scrollbar. FIXME: Might turn bigger than DisplaySize-WindowPadding.
5970 const bool is_popup = (window->Flags & ImGuiWindowFlags_Popup) != 0;
5971 const bool is_menu = (window->Flags & ImGuiWindowFlags_ChildMenu) != 0;
5972 ImVec2 size_min = style.WindowMinSize;
5973 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)
5974 size_min = ImMin(size_min, ImVec2(4.0f, 4.0f));
5975 ImVec2 size_auto_fit = ImClamp(size_contents, size_min, ImMax(size_min, g.IO.DisplaySize - style.DisplaySafeAreaPadding * 2.0f));
5976 ImVec2 size_auto_fit_after_constraint = CalcSizeAfterConstraint(window, size_auto_fit);
5977 if (size_auto_fit_after_constraint.x < size_contents.x && !(window->Flags & ImGuiWindowFlags_NoScrollbar) && (window->Flags & ImGuiWindowFlags_HorizontalScrollbar))
5978 size_auto_fit.y += style.ScrollbarSize;
5979 if (size_auto_fit_after_constraint.y < size_contents.y && !(window->Flags & ImGuiWindowFlags_NoScrollbar))
5980 size_auto_fit.x += style.ScrollbarSize;
5981 return size_auto_fit;
5982 }
5983 }
5984
5985 static float GetScrollMaxX(ImGuiWindow* window)
5986 {
5987 return ImMax(0.0f, window->SizeContents.x - (window->SizeFull.x - window->ScrollbarSizes.x));
5988 }
5989
5990 static float GetScrollMaxY(ImGuiWindow* window)
5991 {
5992 return ImMax(0.0f, window->SizeContents.y - (window->SizeFull.y - window->ScrollbarSizes.y));
5993 }
5994
5995 static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window, bool snap_on_edges)
5996 {
5997 ImGuiContext& g = *GImGui;
5998 ImVec2 scroll = window->Scroll;
5999 if (window->ScrollTarget.x < FLT_MAX)
6000 {
6001 float cr_x = window->ScrollTargetCenterRatio.x;
6002 scroll.x = window->ScrollTarget.x - cr_x * (window->SizeFull.x - window->ScrollbarSizes.x);
6003 }
6004 if (window->ScrollTarget.y < FLT_MAX)
6005 {
6006 // 'snap_on_edges' allows for a discontinuity at the edge of scrolling limits to take account of WindowPadding so that scrolling to make the last item visible scroll far enough to see the padding.
6007 float cr_y = window->ScrollTargetCenterRatio.y;
6008 float target_y = window->ScrollTarget.y;
6009 if (snap_on_edges && cr_y <= 0.0f && target_y <= window->WindowPadding.y)
6010 target_y = 0.0f;
6011 if (snap_on_edges && cr_y >= 1.0f && target_y >= window->SizeContents.y - window->WindowPadding.y + g.Style.ItemSpacing.y)
6012 target_y = window->SizeContents.y;
6013 scroll.y = target_y - (1.0f - cr_y) * (window->TitleBarHeight() + window->MenuBarHeight()) - cr_y * (window->SizeFull.y - window->ScrollbarSizes.y);
6014 }
6015 scroll = ImMax(scroll, ImVec2(0.0f, 0.0f));
6016 if (!window->Collapsed && !window->SkipItems)
6017 {
6018 scroll.x = ImMin(scroll.x, GetScrollMaxX(window));
6019 scroll.y = ImMin(scroll.y, GetScrollMaxY(window));
6020 }
6021 return scroll;
6022 }
6023
6024 static ImGuiCol GetWindowBgColorIdxFromFlags(ImGuiWindowFlags flags)
6025 {
6026 if (flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup))
6027 return ImGuiCol_PopupBg;
6028 if (flags & ImGuiWindowFlags_ChildWindow)
6029 return ImGuiCol_ChildBg;
6030 return ImGuiCol_WindowBg;
6031 }
6032
6033 static void CalcResizePosSizeFromAnyCorner(ImGuiWindow* window, const ImVec2& corner_target, const ImVec2& corner_norm, ImVec2* out_pos, ImVec2* out_size)
6034 {
6035 ImVec2 pos_min = ImLerp(corner_target, window->Pos, corner_norm); // Expected window upper-left
6036 ImVec2 pos_max = ImLerp(window->Pos + window->Size, corner_target, corner_norm); // Expected window lower-right
6037 ImVec2 size_expected = pos_max - pos_min;
6038 ImVec2 size_constrained = CalcSizeAfterConstraint(window, size_expected);
6039 *out_pos = pos_min;
6040 if (corner_norm.x == 0.0f)
6041 out_pos->x -= (size_constrained.x - size_expected.x);
6042 if (corner_norm.y == 0.0f)
6043 out_pos->y -= (size_constrained.y - size_expected.y);
6044 *out_size = size_constrained;
6045 }
6046
6047 struct ImGuiResizeGripDef
6048 {
6049 ImVec2 CornerPos;
6050 ImVec2 InnerDir;
6051 int AngleMin12, AngleMax12;
6052 };
6053
6054 const ImGuiResizeGripDef resize_grip_def[4] =
6055 {
6056 { ImVec2(1,1), ImVec2(-1,-1), 0, 3 }, // Lower right
6057 { ImVec2(0,1), ImVec2(+1,-1), 3, 6 }, // Lower left
6058 { ImVec2(0,0), ImVec2(+1,+1), 6, 9 }, // Upper left
6059 { ImVec2(1,0), ImVec2(-1,+1), 9,12 }, // Upper right
6060 };
6061
6062 static ImRect GetBorderRect(ImGuiWindow* window, int border_n, float perp_padding, float thickness)
6063 {
6064 ImRect rect = window->Rect();
6065 if (thickness == 0.0f) rect.Max -= ImVec2(1,1);
6066 if (border_n == 0) return ImRect(rect.Min.x + perp_padding, rect.Min.y, rect.Max.x - perp_padding, rect.Min.y + thickness);
6067 if (border_n == 1) return ImRect(rect.Max.x - thickness, rect.Min.y + perp_padding, rect.Max.x, rect.Max.y - perp_padding);
6068 if (border_n == 2) return ImRect(rect.Min.x + perp_padding, rect.Max.y - thickness, rect.Max.x - perp_padding, rect.Max.y);
6069 if (border_n == 3) return ImRect(rect.Min.x, rect.Min.y + perp_padding, rect.Min.x + thickness, rect.Max.y - perp_padding);
6070 IM_ASSERT(0);
6071 return ImRect();
6072 }
6073
6074 // Handle resize for: Resize Grips, Borders, Gamepad
6075 static void ImGui::UpdateManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4])
6076 {
6077 ImGuiContext& g = *GImGui;
6078 ImGuiWindowFlags flags = window->Flags;
6079 if ((flags & ImGuiWindowFlags_NoResize) || (flags & ImGuiWindowFlags_AlwaysAutoResize) || window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0)
6080 return;
6081
6082 const int resize_border_count = g.IO.OptResizeWindowsFromEdges ? 4 : 0;
6083 const float grip_draw_size = (float)(int)ImMax(g.FontSize * 1.35f, window->WindowRounding + 1.0f + g.FontSize * 0.2f);
6084 const float grip_hover_size = (float)(int)(grip_draw_size * 0.75f);
6085
6086 ImVec2 pos_target(FLT_MAX, FLT_MAX);
6087 ImVec2 size_target(FLT_MAX, FLT_MAX);
6088
6089 // Manual resize grips
6090 PushID("#RESIZE");
6091 for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++)
6092 {
6093 const ImGuiResizeGripDef& grip = resize_grip_def[resize_grip_n];
6094 const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, grip.CornerPos);
6095
6096 // Using the FlattenChilds button flag we make the resize button accessible even if we are hovering over a child window
6097 ImRect resize_rect(corner, corner + grip.InnerDir * grip_hover_size);
6098 if (resize_rect.Min.x > resize_rect.Max.x) ImSwap(resize_rect.Min.x, resize_rect.Max.x);
6099 if (resize_rect.Min.y > resize_rect.Max.y) ImSwap(resize_rect.Min.y, resize_rect.Max.y);
6100 bool hovered, held;
6101 ButtonBehavior(resize_rect, window->GetID((void*)(intptr_t)resize_grip_n), &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_NoNavFocus);
6102 if (hovered || held)
6103 g.MouseCursor = (resize_grip_n & 1) ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE;
6104
6105 if (held && g.IO.MouseDoubleClicked[0] && resize_grip_n == 0)
6106 {
6107 // Manual auto-fit when double-clicking
6108 size_target = CalcSizeAfterConstraint(window, size_auto_fit);
6109 ClearActiveID();
6110 }
6111 else if (held)
6112 {
6113 // Resize from any of the four corners
6114 // We don't use an incremental MouseDelta but rather compute an absolute target size based on mouse position
6115 ImVec2 corner_target = g.IO.MousePos - g.ActiveIdClickOffset + resize_rect.GetSize() * grip.CornerPos; // Corner of the window corresponding to our corner grip
6116 CalcResizePosSizeFromAnyCorner(window, corner_target, grip.CornerPos, &pos_target, &size_target);
6117 }
6118 if (resize_grip_n == 0 || held || hovered)
6119 resize_grip_col[resize_grip_n] = GetColorU32(held ? ImGuiCol_ResizeGripActive : hovered ? ImGuiCol_ResizeGripHovered : ImGuiCol_ResizeGrip);
6120 }
6121 for (int border_n = 0; border_n < resize_border_count; border_n++)
6122 {
6123 const float BORDER_SIZE = 5.0f; // FIXME: Only works _inside_ window because of HoveredWindow check.
6124 const float BORDER_APPEAR_TIMER = 0.05f; // Reduce visual noise
6125 bool hovered, held;
6126 ImRect border_rect = GetBorderRect(window, border_n, grip_hover_size, BORDER_SIZE);
6127 ButtonBehavior(border_rect, window->GetID((void*)(intptr_t)(border_n + 4)), &hovered, &held, ImGuiButtonFlags_FlattenChildren);
6128 if ((hovered && g.HoveredIdTimer > BORDER_APPEAR_TIMER) || held)
6129 {
6130 g.MouseCursor = (border_n & 1) ? ImGuiMouseCursor_ResizeEW : ImGuiMouseCursor_ResizeNS;
6131 if (held) *border_held = border_n;
6132 }
6133 if (held)
6134 {
6135 ImVec2 border_target = window->Pos;
6136 ImVec2 border_posn;
6137 if (border_n == 0) { border_posn = ImVec2(0, 0); border_target.y = (g.IO.MousePos.y - g.ActiveIdClickOffset.y); }
6138 if (border_n == 1) { border_posn = ImVec2(1, 0); border_target.x = (g.IO.MousePos.x - g.ActiveIdClickOffset.x + BORDER_SIZE); }
6139 if (border_n == 2) { border_posn = ImVec2(0, 1); border_target.y = (g.IO.MousePos.y - g.ActiveIdClickOffset.y + BORDER_SIZE); }
6140 if (border_n == 3) { border_posn = ImVec2(0, 0); border_target.x = (g.IO.MousePos.x - g.ActiveIdClickOffset.x); }
6141 CalcResizePosSizeFromAnyCorner(window, border_target, border_posn, &pos_target, &size_target);
6142 }
6143 }
6144 PopID();
6145
6146 // Navigation resize (keyboard/gamepad)
6147 if (g.NavWindowingTarget && g.NavWindowingTarget->RootWindow == window)
6148 {
6149 ImVec2 nav_resize_delta;
6150 if (g.NavInputSource == ImGuiInputSource_NavKeyboard && g.IO.KeyShift)
6151 nav_resize_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard, ImGuiInputReadMode_Down);
6152 if (g.NavInputSource == ImGuiInputSource_NavGamepad)
6153 nav_resize_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_Down);
6154 if (nav_resize_delta.x != 0.0f || nav_resize_delta.y != 0.0f)
6155 {
6156 const float NAV_RESIZE_SPEED = 600.0f;
6157 nav_resize_delta *= ImFloor(NAV_RESIZE_SPEED * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y));
6158 g.NavWindowingToggleLayer = false;
6159 g.NavDisableMouseHover = true;
6160 resize_grip_col[0] = GetColorU32(ImGuiCol_ResizeGripActive);
6161 // FIXME-NAV: Should store and accumulate into a separate size buffer to handle sizing constraints properly, right now a constraint will make us stuck.
6162 size_target = CalcSizeAfterConstraint(window, window->SizeFull + nav_resize_delta);
6163 }
6164 }
6165
6166 // Apply back modified position/size to window
6167 if (size_target.x != FLT_MAX)
6168 {
6169 window->SizeFull = size_target;
6170 MarkIniSettingsDirty(window);
6171 }
6172 if (pos_target.x != FLT_MAX)
6173 {
6174 window->Pos = ImFloor(pos_target);
6175 MarkIniSettingsDirty(window);
6176 }
6177
6178 window->Size = window->SizeFull;
6179 }
6180
6181 void ImGui::UpdateWindowParentAndRootLinks(ImGuiWindow* window, ImGuiWindowFlags flags, ImGuiWindow* parent_window)
6182 {
6183 window->ParentWindow = parent_window;
6184 window->RootWindow = window->RootWindowForTitleBarHighlight = window->RootWindowForNav = window;
6185 if (parent_window && (flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Tooltip))
6186 window->RootWindow = parent_window->RootWindow;
6187 if (parent_window && !(flags & ImGuiWindowFlags_Modal) && (flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup)))
6188 window->RootWindowForTitleBarHighlight = parent_window->RootWindowForTitleBarHighlight;
6189 while (window->RootWindowForNav->Flags & ImGuiWindowFlags_NavFlattened)
6190 window->RootWindowForNav = window->RootWindowForNav->ParentWindow;
6191 }
6192
6193 // Push a new ImGui window to add widgets to.
6194 // - 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.
6195 // - Begin/End can be called multiple times during the frame with the same window name to append content.
6196 // - The window name is used as a unique identifier to preserve window information across frames (and save rudimentary information to the .ini file).
6197 // 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.
6198 // - 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.
6199 // - 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.
6200 bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
6201 {
6202 ImGuiContext& g = *GImGui;
6203 const ImGuiStyle& style = g.Style;
6204 IM_ASSERT(name != NULL); // Window name required
6205 IM_ASSERT(g.Initialized); // Forgot to call ImGui::NewFrame()
6206 IM_ASSERT(g.FrameCountEnded != g.FrameCount); // Called ImGui::Render() or ImGui::EndFrame() and haven't called ImGui::NewFrame() again yet
6207
6208 // Find or create
6209 ImGuiWindow* window = FindWindowByName(name);
6210 const bool window_just_created = (window == NULL);
6211 if (window_just_created)
6212 {
6213 ImVec2 size_on_first_use = (g.NextWindowData.SizeCond != 0) ? g.NextWindowData.SizeVal : ImVec2(0.0f, 0.0f); // Any condition flag will do since we are creating a new window here.
6214 window = CreateNewWindow(name, size_on_first_use, flags);
6215 }
6216
6217 // Automatically disable manual moving/resizing when NoInputs is set
6218 if (flags & ImGuiWindowFlags_NoInputs)
6219 flags |= ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
6220
6221 if (flags & ImGuiWindowFlags_NavFlattened)
6222 IM_ASSERT(flags & ImGuiWindowFlags_ChildWindow);
6223
6224 const int current_frame = g.FrameCount;
6225 const bool first_begin_of_the_frame = (window->LastFrameActive != current_frame);
6226 if (first_begin_of_the_frame)
6227 window->Flags = (ImGuiWindowFlags)flags;
6228 else
6229 flags = window->Flags;
6230
6231 // 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
6232 ImGuiWindow* parent_window_in_stack = g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back();
6233 ImGuiWindow* parent_window = first_begin_of_the_frame ? ((flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup)) ? parent_window_in_stack : NULL) : window->ParentWindow;
6234 IM_ASSERT(parent_window != NULL || !(flags & ImGuiWindowFlags_ChildWindow));
6235 window->HasCloseButton = (p_open != NULL);
6236
6237 // Update the Appearing flag
6238 bool window_just_activated_by_user = (window->LastFrameActive < current_frame - 1); // Not using !WasActive because the implicit "Debug" window would always toggle off->on
6239 const bool window_just_appearing_after_hidden_for_resize = (window->HiddenFramesForResize > 0);
6240 if (flags & ImGuiWindowFlags_Popup)
6241 {
6242 ImGuiPopupRef& popup_ref = g.OpenPopupStack[g.CurrentPopupStack.Size];
6243 window_just_activated_by_user |= (window->PopupId != popup_ref.PopupId); // We recycle popups so treat window as activated if popup id changed
6244 window_just_activated_by_user |= (window != popup_ref.Window);
6245 }
6246 window->Appearing = (window_just_activated_by_user || window_just_appearing_after_hidden_for_resize);
6247 if (window->Appearing)
6248 SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, true);
6249
6250 // Add to stack
6251 g.CurrentWindowStack.push_back(window);
6252 SetCurrentWindow(window);
6253 CheckStacksSize(window, true);
6254 if (flags & ImGuiWindowFlags_Popup)
6255 {
6256 ImGuiPopupRef& popup_ref = g.OpenPopupStack[g.CurrentPopupStack.Size];
6257 popup_ref.Window = window;
6258 g.CurrentPopupStack.push_back(popup_ref);
6259 window->PopupId = popup_ref.PopupId;
6260 }
6261
6262 if (window_just_appearing_after_hidden_for_resize && !(flags & ImGuiWindowFlags_ChildWindow))
6263 window->NavLastIds[0] = 0;
6264
6265 // Process SetNextWindow***() calls
6266 bool window_pos_set_by_api = false;
6267 bool window_size_x_set_by_api = false, window_size_y_set_by_api = false;
6268 if (g.NextWindowData.PosCond)
6269 {
6270 window_pos_set_by_api = (window->SetWindowPosAllowFlags & g.NextWindowData.PosCond) != 0;
6271 if (window_pos_set_by_api && ImLengthSqr(g.NextWindowData.PosPivotVal) > 0.00001f)
6272 {
6273 // May be processed on the next frame if this is our first frame and we are measuring size
6274 // FIXME: Look into removing the branch so everything can go through this same code path for consistency.
6275 window->SetWindowPosVal = g.NextWindowData.PosVal;
6276 window->SetWindowPosPivot = g.NextWindowData.PosPivotVal;
6277 window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
6278 }
6279 else
6280 {
6281 SetWindowPos(window, g.NextWindowData.PosVal, g.NextWindowData.PosCond);
6282 }
6283 }
6284 if (g.NextWindowData.SizeCond)
6285 {
6286 window_size_x_set_by_api = (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.x > 0.0f);
6287 window_size_y_set_by_api = (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.y > 0.0f);
6288 SetWindowSize(window, g.NextWindowData.SizeVal, g.NextWindowData.SizeCond);
6289 }
6290 if (g.NextWindowData.ContentSizeCond)
6291 {
6292 // Adjust passed "client size" to become a "window size"
6293 window->SizeContentsExplicit = g.NextWindowData.ContentSizeVal;
6294 if (window->SizeContentsExplicit.y != 0.0f)
6295 window->SizeContentsExplicit.y += window->TitleBarHeight() + window->MenuBarHeight();
6296 }
6297 else if (first_begin_of_the_frame)
6298 {
6299 window->SizeContentsExplicit = ImVec2(0.0f, 0.0f);
6300 }
6301 if (g.NextWindowData.CollapsedCond)
6302 SetWindowCollapsed(window, g.NextWindowData.CollapsedVal, g.NextWindowData.CollapsedCond);
6303 if (g.NextWindowData.FocusCond)
6304 FocusWindow(window);
6305 if (window->Appearing)
6306 SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, false);
6307
6308 // When reusing window again multiple times a frame, just append content (don't need to setup again)
6309 if (first_begin_of_the_frame)
6310 {
6311 // Initialize
6312 const bool window_is_child_tooltip = (flags & ImGuiWindowFlags_ChildWindow) && (flags & ImGuiWindowFlags_Tooltip); // FIXME-WIP: Undocumented behavior of Child+Tooltip for pinned tooltip (#1345)
6313 UpdateWindowParentAndRootLinks(window, flags, parent_window);
6314
6315 window->Active = true;
6316 window->BeginOrderWithinParent = 0;
6317 window->BeginOrderWithinContext = g.WindowsActiveCount++;
6318 window->BeginCount = 0;
6319 window->ClipRect = ImVec4(-FLT_MAX,-FLT_MAX,+FLT_MAX,+FLT_MAX);
6320 window->LastFrameActive = current_frame;
6321 window->IDStack.resize(1);
6322
6323 // UPDATE CONTENTS SIZE, UPDATE HIDDEN STATUS
6324
6325 // Update contents size from last frame for auto-fitting (or use explicit size)
6326 window->SizeContents = CalcSizeContents(window);
6327 if (window->HiddenFramesRegular > 0)
6328 window->HiddenFramesRegular--;
6329 if (window->HiddenFramesForResize > 0)
6330 window->HiddenFramesForResize--;
6331
6332 // Hide new windows for one frame until they calculate their size
6333 if (window_just_created && (!window_size_x_set_by_api || !window_size_y_set_by_api))
6334 window->HiddenFramesForResize = 1;
6335
6336 // Hide popup/tooltip window when re-opening while we measure size (because we recycle the windows)
6337 // We reset Size/SizeContents for reappearing popups/tooltips early in this function, so further code won't be tempted to use the old size.
6338 if (window_just_activated_by_user && (flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) != 0)
6339 {
6340 window->HiddenFramesForResize = 1;
6341 if (flags & ImGuiWindowFlags_AlwaysAutoResize)
6342 {
6343 if (!window_size_x_set_by_api)
6344 window->Size.x = window->SizeFull.x = 0.f;
6345 if (!window_size_y_set_by_api)
6346 window->Size.y = window->SizeFull.y = 0.f;
6347 window->SizeContents = ImVec2(0.f, 0.f);
6348 }
6349 }
6350
6351 SetCurrentWindow(window);
6352
6353 // Lock border size and padding for the frame (so that altering them doesn't cause inconsistencies)
6354 window->WindowBorderSize = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildBorderSize : ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupBorderSize : style.WindowBorderSize;
6355 window->WindowPadding = style.WindowPadding;
6356 if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & (ImGuiWindowFlags_AlwaysUseWindowPadding | ImGuiWindowFlags_Popup)) && window->WindowBorderSize == 0.0f)
6357 window->WindowPadding = ImVec2(0.0f, (flags & ImGuiWindowFlags_MenuBar) ? style.WindowPadding.y : 0.0f);
6358 window->DC.MenuBarOffset.x = ImMax(ImMax(window->WindowPadding.x, style.ItemSpacing.x), g.NextWindowData.MenuBarOffsetMinVal.x);
6359 window->DC.MenuBarOffset.y = g.NextWindowData.MenuBarOffsetMinVal.y;
6360
6361 // Collapse window by double-clicking on title bar
6362 // 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
6363 if (!(flags & ImGuiWindowFlags_NoTitleBar) && !(flags & ImGuiWindowFlags_NoCollapse))
6364 {
6365 // 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.
6366 ImRect title_bar_rect = window->TitleBarRect();
6367 if (g.HoveredWindow == window && g.HoveredId == 0 && g.HoveredIdPreviousFrame == 0 && IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max) && g.IO.MouseDoubleClicked[0])
6368 window->CollapseToggleWanted = true;
6369 if (window->CollapseToggleWanted)
6370 {
6371 window->Collapsed = !window->Collapsed;
6372 MarkIniSettingsDirty(window);
6373 FocusWindow(window);
6374 }
6375 }
6376 else
6377 {
6378 window->Collapsed = false;
6379 }
6380 window->CollapseToggleWanted = false;
6381
6382 // SIZE
6383
6384 // Calculate auto-fit size, handle automatic resize
6385 const ImVec2 size_auto_fit = CalcSizeAutoFit(window, window->SizeContents);
6386 ImVec2 size_full_modified(FLT_MAX, FLT_MAX);
6387 if ((flags & ImGuiWindowFlags_AlwaysAutoResize) && !window->Collapsed)
6388 {
6389 // Using SetNextWindowSize() overrides ImGuiWindowFlags_AlwaysAutoResize, so it can be used on tooltips/popups, etc.
6390 if (!window_size_x_set_by_api)
6391 window->SizeFull.x = size_full_modified.x = size_auto_fit.x;
6392 if (!window_size_y_set_by_api)
6393 window->SizeFull.y = size_full_modified.y = size_auto_fit.y;
6394 }
6395 else if (window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0)
6396 {
6397 // Auto-fit may only grow window during the first few frames
6398 // We still process initial auto-fit on collapsed windows to get a window width, but otherwise don't honor ImGuiWindowFlags_AlwaysAutoResize when collapsed.
6399 if (!window_size_x_set_by_api && window->AutoFitFramesX > 0)
6400 window->SizeFull.x = size_full_modified.x = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.x, size_auto_fit.x) : size_auto_fit.x;
6401 if (!window_size_y_set_by_api && window->AutoFitFramesY > 0)
6402 window->SizeFull.y = size_full_modified.y = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.y, size_auto_fit.y) : size_auto_fit.y;
6403 if (!window->Collapsed)
6404 MarkIniSettingsDirty(window);
6405 }
6406
6407 // Apply minimum/maximum window size constraints and final size
6408 window->SizeFull = CalcSizeAfterConstraint(window, window->SizeFull);
6409 window->Size = window->Collapsed && !(flags & ImGuiWindowFlags_ChildWindow) ? window->TitleBarRect().GetSize() : window->SizeFull;
6410
6411 // SCROLLBAR STATUS
6412
6413 // Update scrollbar status (based on the Size that was effective during last frame or the auto-resized Size).
6414 if (!window->Collapsed)
6415 {
6416 // When reading the current size we need to read it after size constraints have been applied
6417 float size_x_for_scrollbars = size_full_modified.x != FLT_MAX ? window->SizeFull.x : window->SizeFullAtLastBegin.x;
6418 float size_y_for_scrollbars = size_full_modified.y != FLT_MAX ? window->SizeFull.y : window->SizeFullAtLastBegin.y;
6419 window->ScrollbarY = (flags & ImGuiWindowFlags_AlwaysVerticalScrollbar) || ((window->SizeContents.y > size_y_for_scrollbars) && !(flags & ImGuiWindowFlags_NoScrollbar));
6420 window->ScrollbarX = (flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar) || ((window->SizeContents.x > size_x_for_scrollbars - (window->ScrollbarY ? style.ScrollbarSize : 0.0f)) && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar));
6421 if (window->ScrollbarX && !window->ScrollbarY)
6422 window->ScrollbarY = (window->SizeContents.y > size_y_for_scrollbars - style.ScrollbarSize) && !(flags & ImGuiWindowFlags_NoScrollbar);
6423 window->ScrollbarSizes = ImVec2(window->ScrollbarY ? style.ScrollbarSize : 0.0f, window->ScrollbarX ? style.ScrollbarSize : 0.0f);
6424 }
6425
6426 // POSITION
6427
6428 // Popup latch its initial position, will position itself when it appears next frame
6429 if (window_just_activated_by_user)
6430 {
6431 window->AutoPosLastDirection = ImGuiDir_None;
6432 if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api)
6433 window->Pos = g.CurrentPopupStack.back().OpenPopupPos;
6434 }
6435
6436 // Position child window
6437 if (flags & ImGuiWindowFlags_ChildWindow)
6438 {
6439 window->BeginOrderWithinParent = parent_window->DC.ChildWindows.Size;
6440 parent_window->DC.ChildWindows.push_back(window);
6441 if (!(flags & ImGuiWindowFlags_Popup) && !window_pos_set_by_api && !window_is_child_tooltip)
6442 window->Pos = parent_window->DC.CursorPos;
6443 }
6444
6445 const bool window_pos_with_pivot = (window->SetWindowPosVal.x != FLT_MAX && window->HiddenFramesForResize == 0);
6446 if (window_pos_with_pivot)
6447 SetWindowPos(window, ImMax(style.DisplaySafeAreaPadding, window->SetWindowPosVal - window->SizeFull * window->SetWindowPosPivot), 0); // Position given a pivot (e.g. for centering)
6448 else if ((flags & ImGuiWindowFlags_ChildMenu) != 0)
6449 window->Pos = FindBestWindowPosForPopup(window);
6450 else if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api && window_just_appearing_after_hidden_for_resize)
6451 window->Pos = FindBestWindowPosForPopup(window);
6452 else if ((flags & ImGuiWindowFlags_Tooltip) != 0 && !window_pos_set_by_api && !window_is_child_tooltip)
6453 window->Pos = FindBestWindowPosForPopup(window);
6454
6455 // Clamp position so it stays visible
6456 if (!(flags & ImGuiWindowFlags_ChildWindow))
6457 {
6458 if (!window_pos_set_by_api && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0 && g.IO.DisplaySize.x > 0.0f && g.IO.DisplaySize.y > 0.0f) // Ignore zero-sized display explicitly to avoid losing positions if a window manager reports zero-sized window when initializing or minimizing.
6459 {
6460 ImVec2 padding = ImMax(style.DisplayWindowPadding, style.DisplaySafeAreaPadding);
6461 window->Pos = ImMax(window->Pos + window->Size, padding) - window->Size;
6462 window->Pos = ImMin(window->Pos, g.IO.DisplaySize - padding);
6463 }
6464 }
6465 window->Pos = ImFloor(window->Pos);
6466
6467 // Lock window rounding for the frame (so that altering them doesn't cause inconsistencies)
6468 window->WindowRounding = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildRounding : ((flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupRounding : style.WindowRounding;
6469
6470 // Prepare for item focus requests
6471 window->FocusIdxAllRequestCurrent = (window->FocusIdxAllRequestNext == INT_MAX || window->FocusIdxAllCounter == -1) ? INT_MAX : (window->FocusIdxAllRequestNext + (window->FocusIdxAllCounter+1)) % (window->FocusIdxAllCounter+1);
6472 window->FocusIdxTabRequestCurrent = (window->FocusIdxTabRequestNext == INT_MAX || window->FocusIdxTabCounter == -1) ? INT_MAX : (window->FocusIdxTabRequestNext + (window->FocusIdxTabCounter+1)) % (window->FocusIdxTabCounter+1);
6473 window->FocusIdxAllCounter = window->FocusIdxTabCounter = -1;
6474 window->FocusIdxAllRequestNext = window->FocusIdxTabRequestNext = INT_MAX;
6475
6476 // Apply scrolling
6477 window->Scroll = CalcNextScrollFromScrollTargetAndClamp(window, true);
6478 window->ScrollTarget = ImVec2(FLT_MAX, FLT_MAX);
6479
6480 // Apply window focus (new and reactivated windows are moved to front)
6481 bool want_focus = false;
6482 if (window_just_activated_by_user && !(flags & ImGuiWindowFlags_NoFocusOnAppearing))
6483 if (!(flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Tooltip)) || (flags & ImGuiWindowFlags_Popup))
6484 want_focus = true;
6485
6486 // Handle manual resize: Resize Grips, Borders, Gamepad
6487 int border_held = -1;
6488 ImU32 resize_grip_col[4] = { 0 };
6489 const int resize_grip_count = g.IO.OptResizeWindowsFromEdges ? 2 : 1; // 4
6490 const float grip_draw_size = (float)(int)ImMax(g.FontSize * 1.35f, window->WindowRounding + 1.0f + g.FontSize * 0.2f);
6491 if (!window->Collapsed)
6492 UpdateManualResize(window, size_auto_fit, &border_held, resize_grip_count, &resize_grip_col[0]);
6493
6494 // Default item width. Make it proportional to window size if window manually resizes
6495 if (window->Size.x > 0.0f && !(flags & ImGuiWindowFlags_Tooltip) && !(flags & ImGuiWindowFlags_AlwaysAutoResize))
6496 window->ItemWidthDefault = (float)(int)(window->Size.x * 0.65f);
6497 else
6498 window->ItemWidthDefault = (float)(int)(g.FontSize * 16.0f);
6499
6500 // DRAWING
6501
6502 // Setup draw list and outer clipping rectangle
6503 window->DrawList->Clear();
6504 window->DrawList->Flags = (g.Style.AntiAliasedLines ? ImDrawListFlags_AntiAliasedLines : 0) | (g.Style.AntiAliasedFill ? ImDrawListFlags_AntiAliasedFill : 0);
6505 window->DrawList->PushTextureID(g.Font->ContainerAtlas->TexID);
6506 ImRect viewport_rect(GetViewportRect());
6507 if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) && !window_is_child_tooltip)
6508 PushClipRect(parent_window->ClipRect.Min, parent_window->ClipRect.Max, true);
6509 else
6510 PushClipRect(viewport_rect.Min, viewport_rect.Max, true);
6511
6512 // Draw modal window background (darkens what is behind them, all viewports)
6513 const bool dim_bg_for_modal = (flags & ImGuiWindowFlags_Modal) && window == GetFrontMostPopupModal() && window->HiddenFramesForResize <= 0;
6514 const bool dim_bg_for_window_list = g.NavWindowingTargetAnim && (window == g.NavWindowingTargetAnim->RootWindow);
6515 if (dim_bg_for_modal || dim_bg_for_window_list)
6516 {
6517 const ImU32 dim_bg_col = GetColorU32(dim_bg_for_modal ? ImGuiCol_ModalWindowDimBg : ImGuiCol_NavWindowingDimBg, g.DimBgRatio);
6518 window->DrawList->AddRectFilled(viewport_rect.Min, viewport_rect.Max, dim_bg_col);
6519 }
6520
6521 // Draw navigation selection/windowing rectangle background
6522 if (dim_bg_for_window_list && window == g.NavWindowingTargetAnim)
6523 {
6524 ImRect bb = window->Rect();
6525 bb.Expand(g.FontSize);
6526 if (!bb.Contains(viewport_rect)) // Avoid drawing if the window covers all the viewport anyway
6527 window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha * 0.25f), g.Style.WindowRounding);
6528 }
6529
6530 // Draw window + handle manual resize
6531 const float window_rounding = window->WindowRounding;
6532 const float window_border_size = window->WindowBorderSize;
6533 const ImGuiWindow* window_to_highlight = g.NavWindowingTarget ? g.NavWindowingTarget : g.NavWindow;
6534 const bool title_bar_is_highlight = want_focus || (window_to_highlight && window->RootWindowForTitleBarHighlight == window_to_highlight->RootWindowForTitleBarHighlight);
6535 const ImRect title_bar_rect = window->TitleBarRect();
6536 if (window->Collapsed)
6537 {
6538 // Title bar only
6539 float backup_border_size = style.FrameBorderSize;
6540 g.Style.FrameBorderSize = window->WindowBorderSize;
6541 ImU32 title_bar_col = GetColorU32((title_bar_is_highlight && !g.NavDisableHighlight) ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBgCollapsed);
6542 RenderFrame(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, true, window_rounding);
6543 g.Style.FrameBorderSize = backup_border_size;
6544 }
6545 else
6546 {
6547 // Window background
6548 ImU32 bg_col = GetColorU32(GetWindowBgColorIdxFromFlags(flags));
6549 if (g.NextWindowData.BgAlphaCond != 0)
6550 {
6551 bg_col = (bg_col & ~IM_COL32_A_MASK) | (IM_F32_TO_INT8_SAT(g.NextWindowData.BgAlphaVal) << IM_COL32_A_SHIFT);
6552 g.NextWindowData.BgAlphaCond = 0;
6553 }
6554 window->DrawList->AddRectFilled(window->Pos + ImVec2(0, window->TitleBarHeight()), window->Pos + window->Size, bg_col, window_rounding, (flags & ImGuiWindowFlags_NoTitleBar) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Bot);
6555
6556 // Title bar
6557 ImU32 title_bar_col = GetColorU32(window->Collapsed ? ImGuiCol_TitleBgCollapsed : title_bar_is_highlight ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg);
6558 if (!(flags & ImGuiWindowFlags_NoTitleBar))
6559 window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, window_rounding, ImDrawCornerFlags_Top);
6560
6561 // Menu bar
6562 if (flags & ImGuiWindowFlags_MenuBar)
6563 {
6564 ImRect menu_bar_rect = window->MenuBarRect();
6565 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.
6566 window->DrawList->AddRectFilled(menu_bar_rect.Min, menu_bar_rect.Max, GetColorU32(ImGuiCol_MenuBarBg), (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f, ImDrawCornerFlags_Top);
6567 if (style.FrameBorderSize > 0.0f && menu_bar_rect.Max.y < window->Pos.y + window->Size.y)
6568 window->DrawList->AddLine(menu_bar_rect.GetBL(), menu_bar_rect.GetBR(), GetColorU32(ImGuiCol_Border), style.FrameBorderSize);
6569 }
6570
6571 // Scrollbars
6572 if (window->ScrollbarX)
6573 Scrollbar(ImGuiLayoutType_Horizontal);
6574 if (window->ScrollbarY)
6575 Scrollbar(ImGuiLayoutType_Vertical);
6576
6577 // Render resize grips (after their input handling so we don't have a frame of latency)
6578 if (!(flags & ImGuiWindowFlags_NoResize))
6579 {
6580 for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++)
6581 {
6582 const ImGuiResizeGripDef& grip = resize_grip_def[resize_grip_n];
6583 const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, grip.CornerPos);
6584 window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(window_border_size, grip_draw_size) : ImVec2(grip_draw_size, window_border_size)));
6585 window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(grip_draw_size, window_border_size) : ImVec2(window_border_size, grip_draw_size)));
6586 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);
6587 window->DrawList->PathFillConvex(resize_grip_col[resize_grip_n]);
6588 }
6589 }
6590
6591 // Borders
6592 if (window_border_size > 0.0f)
6593 window->DrawList->AddRect(window->Pos, window->Pos + window->Size, GetColorU32(ImGuiCol_Border), window_rounding, ImDrawCornerFlags_All, window_border_size);
6594 if (border_held != -1)
6595 {
6596 ImRect border = GetBorderRect(window, border_held, grip_draw_size, 0.0f);
6597 window->DrawList->AddLine(border.Min, border.Max, GetColorU32(ImGuiCol_SeparatorActive), ImMax(1.0f, window_border_size));
6598 }
6599 if (style.FrameBorderSize > 0 && !(flags & ImGuiWindowFlags_NoTitleBar))
6600 window->DrawList->AddLine(title_bar_rect.GetBL() + ImVec2(style.WindowBorderSize, -1), title_bar_rect.GetBR() + ImVec2(-style.WindowBorderSize, -1), GetColorU32(ImGuiCol_Border), style.FrameBorderSize);
6601 }
6602
6603 // Draw navigation selection/windowing rectangle border
6604 if (g.NavWindowingTargetAnim == window)
6605 {
6606 float rounding = ImMax(window->WindowRounding, g.Style.WindowRounding);
6607 ImRect bb = window->Rect();
6608 bb.Expand(g.FontSize);
6609 if (bb.Contains(viewport_rect)) // If a window fits the entire viewport, adjust its highlight inward
6610 {
6611 bb.Expand(-g.FontSize - 1.0f);
6612 rounding = window->WindowRounding;
6613 }
6614 window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha), rounding, ~0, 3.0f);
6615 }
6616
6617 // Store a backup of SizeFull which we will use next frame to decide if we need scrollbars.
6618 window->SizeFullAtLastBegin = window->SizeFull;
6619
6620 // Update various regions. Variables they depends on are set above in this function.
6621 // FIXME: window->ContentsRegionRect.Max is currently very misleading / partly faulty, but some BeginChild() patterns relies on it.
6622 window->ContentsRegionRect.Min.x = window->Pos.x - window->Scroll.x + window->WindowPadding.x;
6623 window->ContentsRegionRect.Min.y = window->Pos.y - window->Scroll.y + window->WindowPadding.y + window->TitleBarHeight() + window->MenuBarHeight();
6624 window->ContentsRegionRect.Max.x = window->Pos.x - window->Scroll.x - window->WindowPadding.x + (window->SizeContentsExplicit.x != 0.0f ? window->SizeContentsExplicit.x : (window->Size.x - window->ScrollbarSizes.x));
6625 window->ContentsRegionRect.Max.y = window->Pos.y - window->Scroll.y - window->WindowPadding.y + (window->SizeContentsExplicit.y != 0.0f ? window->SizeContentsExplicit.y : (window->Size.y - window->ScrollbarSizes.y));
6626
6627 // Setup drawing context
6628 // (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.)
6629 window->DC.IndentX = 0.0f + window->WindowPadding.x - window->Scroll.x;
6630 window->DC.GroupOffsetX = 0.0f;
6631 window->DC.ColumnsOffsetX = 0.0f;
6632 window->DC.CursorStartPos = window->Pos + ImVec2(window->DC.IndentX + window->DC.ColumnsOffsetX, window->TitleBarHeight() + window->MenuBarHeight() + window->WindowPadding.y - window->Scroll.y);
6633 window->DC.CursorPos = window->DC.CursorStartPos;
6634 window->DC.CursorPosPrevLine = window->DC.CursorPos;
6635 window->DC.CursorMaxPos = window->DC.CursorStartPos;
6636 window->DC.CurrentLineHeight = window->DC.PrevLineHeight = 0.0f;
6637 window->DC.CurrentLineTextBaseOffset = window->DC.PrevLineTextBaseOffset = 0.0f;
6638 window->DC.NavHideHighlightOneFrame = false;
6639 window->DC.NavHasScroll = (GetScrollMaxY() > 0.0f);
6640 window->DC.NavLayerActiveMask = window->DC.NavLayerActiveMaskNext;
6641 window->DC.NavLayerActiveMaskNext = 0x00;
6642 window->DC.MenuBarAppending = false;
6643 window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f;
6644 window->DC.ChildWindows.resize(0);
6645 window->DC.LayoutType = ImGuiLayoutType_Vertical;
6646 window->DC.ParentLayoutType = parent_window ? parent_window->DC.LayoutType : ImGuiLayoutType_Vertical;
6647 window->DC.ItemFlags = parent_window ? parent_window->DC.ItemFlags : ImGuiItemFlags_Default_;
6648 window->DC.ItemWidth = window->ItemWidthDefault;
6649 window->DC.TextWrapPos = -1.0f; // disabled
6650 window->DC.ItemFlagsStack.resize(0);
6651 window->DC.ItemWidthStack.resize(0);
6652 window->DC.TextWrapPosStack.resize(0);
6653 window->DC.ColumnsSet = NULL;
6654 window->DC.TreeDepth = 0;
6655 window->DC.TreeDepthMayJumpToParentOnPop = 0x00;
6656 window->DC.StateStorage = &window->StateStorage;
6657 window->DC.GroupStack.resize(0);
6658 window->MenuColumns.Update(3, style.ItemSpacing.x, window_just_activated_by_user);
6659
6660 if ((flags & ImGuiWindowFlags_ChildWindow) && (window->DC.ItemFlags != parent_window->DC.ItemFlags))
6661 {
6662 window->DC.ItemFlags = parent_window->DC.ItemFlags;
6663 window->DC.ItemFlagsStack.push_back(window->DC.ItemFlags);
6664 }
6665
6666 if (window->AutoFitFramesX > 0)
6667 window->AutoFitFramesX--;
6668 if (window->AutoFitFramesY > 0)
6669 window->AutoFitFramesY--;
6670
6671 // Apply focus (we need to call FocusWindow() AFTER setting DC.CursorStartPos so our initial navigation reference rectangle can start around there)
6672 if (want_focus)
6673 {
6674 FocusWindow(window);
6675 NavInitWindow(window, false);
6676 }
6677
6678 // Title bar
6679 if (!(flags & ImGuiWindowFlags_NoTitleBar))
6680 {
6681 // Close & collapse button are on layer 1 (same as menus) and don't default focus
6682 const ImGuiItemFlags item_flags_backup = window->DC.ItemFlags;
6683 window->DC.ItemFlags |= ImGuiItemFlags_NoNavDefaultFocus;
6684 window->DC.NavLayerCurrent++;
6685 window->DC.NavLayerCurrentMask <<= 1;
6686
6687 // Collapse button
6688 if (!(flags & ImGuiWindowFlags_NoCollapse))
6689 if (CollapseButton(window->GetID("#COLLAPSE"), window->Pos + style.FramePadding))
6690 window->CollapseToggleWanted = true; // Defer collapsing to next frame as we are too far in the Begin() function
6691
6692 // Close button
6693 if (p_open != NULL)
6694 {
6695 const float pad = style.FramePadding.y;
6696 const float rad = g.FontSize * 0.5f;
6697 if (CloseButton(window->GetID("#CLOSE"), window->Rect().GetTR() + ImVec2(-pad - rad, pad + rad), rad + 1))
6698 *p_open = false;
6699 }
6700
6701 window->DC.NavLayerCurrent--;
6702 window->DC.NavLayerCurrentMask >>= 1;
6703 window->DC.ItemFlags = item_flags_backup;
6704
6705 // Title text (FIXME: refactor text alignment facilities along with RenderText helpers, this is too much code for what it does.)
6706 ImVec2 text_size = CalcTextSize(name, NULL, true);
6707 ImRect text_r = title_bar_rect;
6708 float pad_left = (flags & ImGuiWindowFlags_NoCollapse) ? style.FramePadding.x : (style.FramePadding.x + g.FontSize + style.ItemInnerSpacing.x);
6709 float pad_right = (p_open == NULL) ? style.FramePadding.x : (style.FramePadding.x + g.FontSize + style.ItemInnerSpacing.x);
6710 if (style.WindowTitleAlign.x > 0.0f)
6711 pad_right = ImLerp(pad_right, pad_left, style.WindowTitleAlign.x);
6712 text_r.Min.x += pad_left;
6713 text_r.Max.x -= pad_right;
6714 ImRect clip_rect = text_r;
6715 clip_rect.Max.x = window->Pos.x + window->Size.x - (p_open ? title_bar_rect.GetHeight() - 3 : style.FramePadding.x); // Match the size of CloseButton()
6716 RenderTextClipped(text_r.Min, text_r.Max, name, NULL, &text_size, style.WindowTitleAlign, &clip_rect);
6717 }
6718
6719 // Save clipped aabb so we can access it in constant-time in FindHoveredWindow()
6720 window->OuterRectClipped = window->Rect();
6721 window->OuterRectClipped.ClipWith(window->ClipRect);
6722
6723 // Pressing CTRL+C while holding on a window copy its content to the clipboard
6724 // 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.
6725 // Maybe we can support CTRL+C on every element?
6726 /*
6727 if (g.ActiveId == move_id)
6728 if (g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_C))
6729 ImGui::LogToClipboard();
6730 */
6731
6732 // Inner rectangle
6733 // We set this up after processing the resize grip so that our clip rectangle doesn't lag by a frame
6734 // Note that if our window is collapsed we will end up with an inverted (~null) clipping rectangle which is the correct behavior.
6735 window->InnerMainRect.Min.x = title_bar_rect.Min.x + window->WindowBorderSize;
6736 window->InnerMainRect.Min.y = title_bar_rect.Max.y + window->MenuBarHeight() + (((flags & ImGuiWindowFlags_MenuBar) || !(flags & ImGuiWindowFlags_NoTitleBar)) ? style.FrameBorderSize : window->WindowBorderSize);
6737 window->InnerMainRect.Max.x = window->Pos.x + window->Size.x - window->ScrollbarSizes.x - window->WindowBorderSize;
6738 window->InnerMainRect.Max.y = window->Pos.y + window->Size.y - window->ScrollbarSizes.y - window->WindowBorderSize;
6739 //window->DrawList->AddRect(window->InnerRect.Min, window->InnerRect.Max, IM_COL32_WHITE);
6740
6741 // Inner clipping rectangle
6742 // Force round operator last to ensure that e.g. (int)(max.x-min.x) in user's render code produce correct result.
6743 window->InnerClipRect.Min.x = ImFloor(0.5f + window->InnerMainRect.Min.x + ImMax(0.0f, ImFloor(window->WindowPadding.x*0.5f - window->WindowBorderSize)));
6744 window->InnerClipRect.Min.y = ImFloor(0.5f + window->InnerMainRect.Min.y);
6745 window->InnerClipRect.Max.x = ImFloor(0.5f + window->InnerMainRect.Max.x - ImMax(0.0f, ImFloor(window->WindowPadding.x*0.5f - window->WindowBorderSize)));
6746 window->InnerClipRect.Max.y = ImFloor(0.5f + window->InnerMainRect.Max.y);
6747
6748 // After Begin() we fill the last item / hovered data based on title bar data. It is a standard behavior (to allow creation of context menus on title bar only, etc.).
6749 window->DC.LastItemId = window->MoveId;
6750 window->DC.LastItemStatusFlags = IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0;
6751 window->DC.LastItemRect = title_bar_rect;
6752 }
6753
6754 PushClipRect(window->InnerClipRect.Min, window->InnerClipRect.Max, true);
6755
6756 // 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)
6757 if (first_begin_of_the_frame)
6758 window->WriteAccessed = false;
6759
6760 window->BeginCount++;
6761 g.NextWindowData.Clear();
6762
6763 if (flags & ImGuiWindowFlags_ChildWindow)
6764 {
6765 // Child window can be out of sight and have "negative" clip windows.
6766 // Mark them as collapsed so commands are skipped earlier (we can't manually collapse them because they have no title bar).
6767 IM_ASSERT((flags & ImGuiWindowFlags_NoTitleBar) != 0);
6768
6769 if (!(flags & ImGuiWindowFlags_AlwaysAutoResize) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0)
6770 if (window->OuterRectClipped.Min.x >= window->OuterRectClipped.Max.x || window->OuterRectClipped.Min.y >= window->OuterRectClipped.Max.y)
6771 window->HiddenFramesRegular = 1;
6772
6773 // Completely hide along with parent or if parent is collapsed
6774 if (parent_window && (parent_window->Collapsed || parent_window->Hidden))
6775 window->HiddenFramesRegular = 1;
6776 }
6777
6778 // 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)
6779 if (style.Alpha <= 0.0f)
6780 window->HiddenFramesRegular = 1;
6781
6782 // Update the Hidden flag
6783 window->Hidden = (window->HiddenFramesRegular > 0) || (window->HiddenFramesForResize);
6784
6785 // Return false if we don't intend to display anything to allow user to perform an early out optimization
6786 window->SkipItems = (window->Collapsed || !window->Active || window->Hidden) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0 && window->HiddenFramesForResize <= 0;
6787
6788 return !window->SkipItems;
6789 }
6790
6791 // Old Begin() API with 5 parameters, avoid calling this version directly! Use SetNextWindowSize()/SetNextWindowBgAlpha() + Begin() instead.
6792 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
6793 bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_first_use, float bg_alpha_override, ImGuiWindowFlags flags)
6794 {
6795 // Old API feature: we could pass the initial window size as a parameter. This was misleading because it only had an effect if the window didn't have data in the .ini file.
6796 if (size_first_use.x != 0.0f || size_first_use.y != 0.0f)
6797 ImGui::SetNextWindowSize(size_first_use, ImGuiCond_FirstUseEver);
6798
6799 // Old API feature: override the window background alpha with a parameter.
6800 if (bg_alpha_override >= 0.0f)
6801 ImGui::SetNextWindowBgAlpha(bg_alpha_override);
6802
6803 return ImGui::Begin(name, p_open, flags);
6804 }
6805 #endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS
6806
6807 void ImGui::End()
6808 {
6809 ImGuiContext& g = *GImGui;
6810 ImGuiWindow* window = g.CurrentWindow;
6811
6812 if (window->DC.ColumnsSet != NULL)
6813 EndColumns();
6814 PopClipRect(); // Inner window clip rectangle
6815
6816 // Stop logging
6817 if (!(window->Flags & ImGuiWindowFlags_ChildWindow)) // FIXME: add more options for scope of logging
6818 LogFinish();
6819
6820 // Pop from window stack
6821 g.CurrentWindowStack.pop_back();
6822 if (window->Flags & ImGuiWindowFlags_Popup)
6823 g.CurrentPopupStack.pop_back();
6824 CheckStacksSize(window, false);
6825 SetCurrentWindow(g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back());
6826 }
6827
6828 // Vertical scrollbar
6829 // The entire piece of code below is rather confusing because:
6830 // - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab)
6831 // - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar
6832 // - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal.
6833 void ImGui::Scrollbar(ImGuiLayoutType direction)
6834 {
6835 ImGuiContext& g = *GImGui;
6836 ImGuiWindow* window = g.CurrentWindow;
6837
6838 const bool horizontal = (direction == ImGuiLayoutType_Horizontal);
6839 const ImGuiStyle& style = g.Style;
6840 const ImGuiID id = window->GetID(horizontal ? "#SCROLLX" : "#SCROLLY");
6841
6842 // Render background
6843 bool other_scrollbar = (horizontal ? window->ScrollbarY : window->ScrollbarX);
6844 float other_scrollbar_size_w = other_scrollbar ? style.ScrollbarSize : 0.0f;
6845 const ImRect window_rect = window->Rect();
6846 const float border_size = window->WindowBorderSize;
6847 ImRect bb = horizontal
6848 ? ImRect(window->Pos.x + border_size, window_rect.Max.y - style.ScrollbarSize, window_rect.Max.x - other_scrollbar_size_w - border_size, window_rect.Max.y - border_size)
6849 : ImRect(window_rect.Max.x - style.ScrollbarSize, window->Pos.y + border_size, window_rect.Max.x - border_size, window_rect.Max.y - other_scrollbar_size_w - border_size);
6850 if (!horizontal)
6851 bb.Min.y += window->TitleBarHeight() + ((window->Flags & ImGuiWindowFlags_MenuBar) ? window->MenuBarHeight() : 0.0f);
6852 if (bb.GetWidth() <= 0.0f || bb.GetHeight() <= 0.0f)
6853 return;
6854
6855 int window_rounding_corners;
6856 if (horizontal)
6857 window_rounding_corners = ImDrawCornerFlags_BotLeft | (other_scrollbar ? 0 : ImDrawCornerFlags_BotRight);
6858 else
6859 window_rounding_corners = (((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar)) ? ImDrawCornerFlags_TopRight : 0) | (other_scrollbar ? 0 : ImDrawCornerFlags_BotRight);
6860 window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_ScrollbarBg), window->WindowRounding, window_rounding_corners);
6861 bb.Expand(ImVec2(-ImClamp((float)(int)((bb.Max.x - bb.Min.x - 2.0f) * 0.5f), 0.0f, 3.0f), -ImClamp((float)(int)((bb.Max.y - bb.Min.y - 2.0f) * 0.5f), 0.0f, 3.0f)));
6862
6863 // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar)
6864 float scrollbar_size_v = horizontal ? bb.GetWidth() : bb.GetHeight();
6865 float scroll_v = horizontal ? window->Scroll.x : window->Scroll.y;
6866 float win_size_avail_v = (horizontal ? window->SizeFull.x : window->SizeFull.y) - other_scrollbar_size_w;
6867 float win_size_contents_v = horizontal ? window->SizeContents.x : window->SizeContents.y;
6868
6869 // Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount)
6870 // But we maintain a minimum size in pixel to allow for the user to still aim inside.
6871 IM_ASSERT(ImMax(win_size_contents_v, win_size_avail_v) > 0.0f); // Adding this assert to check if the ImMax(XXX,1.0f) is still needed. PLEASE CONTACT ME if this triggers.
6872 const float win_size_v = ImMax(ImMax(win_size_contents_v, win_size_avail_v), 1.0f);
6873 const float grab_h_pixels = ImClamp(scrollbar_size_v * (win_size_avail_v / win_size_v), style.GrabMinSize, scrollbar_size_v);
6874 const float grab_h_norm = grab_h_pixels / scrollbar_size_v;
6875
6876 // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar().
6877 bool held = false;
6878 bool hovered = false;
6879 const bool previously_held = (g.ActiveId == id);
6880 ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus);
6881
6882 float scroll_max = ImMax(1.0f, win_size_contents_v - win_size_avail_v);
6883 float scroll_ratio = ImSaturate(scroll_v / scroll_max);
6884 float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;
6885 if (held && grab_h_norm < 1.0f)
6886 {
6887 float scrollbar_pos_v = horizontal ? bb.Min.x : bb.Min.y;
6888 float mouse_pos_v = horizontal ? g.IO.MousePos.x : g.IO.MousePos.y;
6889 float* click_delta_to_grab_center_v = horizontal ? &g.ScrollbarClickDeltaToGrabCenter.x : &g.ScrollbarClickDeltaToGrabCenter.y;
6890
6891 // Click position in scrollbar normalized space (0.0f->1.0f)
6892 const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v);
6893 SetHoveredID(id);
6894
6895 bool seek_absolute = false;
6896 if (!previously_held)
6897 {
6898 // On initial click calculate the distance between mouse and the center of the grab
6899 if (clicked_v_norm >= grab_v_norm && clicked_v_norm <= grab_v_norm + grab_h_norm)
6900 {
6901 *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f;
6902 }
6903 else
6904 {
6905 seek_absolute = true;
6906 *click_delta_to_grab_center_v = 0.0f;
6907 }
6908 }
6909
6910 // Apply scroll
6911 // It is ok to modify Scroll here because we are being called in Begin() after the calculation of SizeContents and before setting up our starting position
6912 const float scroll_v_norm = ImSaturate((clicked_v_norm - *click_delta_to_grab_center_v - grab_h_norm*0.5f) / (1.0f - grab_h_norm));
6913 scroll_v = (float)(int)(0.5f + scroll_v_norm * scroll_max);//(win_size_contents_v - win_size_v));
6914 if (horizontal)
6915 window->Scroll.x = scroll_v;
6916 else
6917 window->Scroll.y = scroll_v;
6918
6919 // Update values for rendering
6920 scroll_ratio = ImSaturate(scroll_v / scroll_max);
6921 grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;
6922
6923 // Update distance to grab now that we have seeked and saturated
6924 if (seek_absolute)
6925 *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm*0.5f;
6926 }
6927
6928 // Render
6929 const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab);
6930 ImRect grab_rect;
6931 if (horizontal)
6932 grab_rect = ImRect(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm), bb.Min.y, ImMin(ImLerp(bb.Min.x, bb.Max.x, grab_v_norm) + grab_h_pixels, window_rect.Max.x), bb.Max.y);
6933 else
6934 grab_rect = ImRect(bb.Min.x, ImLerp(bb.Min.y, bb.Max.y, grab_v_norm), bb.Max.x, ImMin(ImLerp(bb.Min.y, bb.Max.y, grab_v_norm) + grab_h_pixels, window_rect.Max.y));
6935 window->DrawList->AddRectFilled(grab_rect.Min, grab_rect.Max, grab_col, style.ScrollbarRounding);
6936 }
6937
6938 void ImGui::BringWindowToFront(ImGuiWindow* window)
6939 {
6940 ImGuiContext& g = *GImGui;
6941 ImGuiWindow* current_front_window = g.Windows.back();
6942 if (current_front_window == window || current_front_window->RootWindow == window)
6943 return;
6944 for (int i = g.Windows.Size - 2; i >= 0; i--) // We can ignore the front most window
6945 if (g.Windows[i] == window)
6946 {
6947 g.Windows.erase(g.Windows.Data + i);
6948 g.Windows.push_back(window);
6949 break;
6950 }
6951 }
6952
6953 void ImGui::BringWindowToBack(ImGuiWindow* window)
6954 {
6955 ImGuiContext& g = *GImGui;
6956 if (g.Windows[0] == window)
6957 return;
6958 for (int i = 0; i < g.Windows.Size; i++)
6959 if (g.Windows[i] == window)
6960 {
6961 memmove(&g.Windows[1], &g.Windows[0], (size_t)i * sizeof(ImGuiWindow*));
6962 g.Windows[0] = window;
6963 break;
6964 }
6965 }
6966
6967 // Moving window to front of display and set focus (which happens to be back of our sorted list)
6968 void ImGui::FocusWindow(ImGuiWindow* window)
6969 {
6970 ImGuiContext& g = *GImGui;
6971
6972 if (g.NavWindow != window)
6973 {
6974 g.NavWindow = window;
6975 if (window && g.NavDisableMouseHover)
6976 g.NavMousePosDirty = true;
6977 g.NavInitRequest = false;
6978 g.NavId = window ? window->NavLastIds[0] : 0; // Restore NavId
6979 g.NavIdIsAlive = false;
6980 g.NavLayer = 0;
6981 //printf("[%05d] FocusWindow(\"%s\")\n", g.FrameCount, window ? window->Name : NULL);
6982 }
6983
6984 // Passing NULL allow to disable keyboard focus
6985 if (!window)
6986 return;
6987
6988 // Move the root window to the top of the pile
6989 if (window->RootWindow)
6990 window = window->RootWindow;
6991
6992 // Steal focus on active widgets
6993 if (window->Flags & ImGuiWindowFlags_Popup) // FIXME: This statement should be unnecessary. Need further testing before removing it..
6994 if (g.ActiveId != 0 && g.ActiveIdWindow && g.ActiveIdWindow->RootWindow != window)
6995 ClearActiveID();
6996
6997 // Bring to front
6998 if (!(window->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus))
6999 BringWindowToFront(window);
7000 }
7001
7002 void ImGui::FocusFrontMostActiveWindow(ImGuiWindow* ignore_window)
7003 {
7004 ImGuiContext& g = *GImGui;
7005 for (int i = g.Windows.Size - 1; i >= 0; i--)
7006 if (g.Windows[i] != ignore_window && g.Windows[i]->WasActive && !(g.Windows[i]->Flags & ImGuiWindowFlags_ChildWindow))
7007 {
7008 ImGuiWindow* focus_window = NavRestoreLastChildNavWindow(g.Windows[i]);
7009 FocusWindow(focus_window);
7010 return;
7011 }
7012 }
7013
7014 void ImGui::PushItemWidth(float item_width)
7015 {
7016 ImGuiWindow* window = GetCurrentWindow();
7017 window->DC.ItemWidth = (item_width == 0.0f ? window->ItemWidthDefault : item_width);
7018 window->DC.ItemWidthStack.push_back(window->DC.ItemWidth);
7019 }
7020
7021 void ImGui::PushMultiItemsWidths(int components, float w_full)
7022 {
7023 ImGuiWindow* window = GetCurrentWindow();
7024 const ImGuiStyle& style = GImGui->Style;
7025 if (w_full <= 0.0f)
7026 w_full = CalcItemWidth();
7027 const float w_item_one = ImMax(1.0f, (float)(int)((w_full - (style.ItemInnerSpacing.x) * (components-1)) / (float)components));
7028 const float w_item_last = ImMax(1.0f, (float)(int)(w_full - (w_item_one + style.ItemInnerSpacing.x) * (components-1)));
7029 window->DC.ItemWidthStack.push_back(w_item_last);
7030 for (int i = 0; i < components-1; i++)
7031 window->DC.ItemWidthStack.push_back(w_item_one);
7032 window->DC.ItemWidth = window->DC.ItemWidthStack.back();
7033 }
7034
7035 void ImGui::PopItemWidth()
7036 {
7037 ImGuiWindow* window = GetCurrentWindow();
7038 window->DC.ItemWidthStack.pop_back();
7039 window->DC.ItemWidth = window->DC.ItemWidthStack.empty() ? window->ItemWidthDefault : window->DC.ItemWidthStack.back();
7040 }
7041
7042 float ImGui::CalcItemWidth()
7043 {
7044 ImGuiWindow* window = GetCurrentWindowRead();
7045 float w = window->DC.ItemWidth;
7046 if (w < 0.0f)
7047 {
7048 // Align to a right-side limit. We include 1 frame padding in the calculation because this is how the width is always used (we add 2 frame padding to it), but we could move that responsibility to the widget as well.
7049 float width_to_right_edge = GetContentRegionAvail().x;
7050 w = ImMax(1.0f, width_to_right_edge + w);
7051 }
7052 w = (float)(int)w;
7053 return w;
7054 }
7055
7056 void ImGui::SetCurrentFont(ImFont* font)
7057 {
7058 ImGuiContext& g = *GImGui;
7059 IM_ASSERT(font && font->IsLoaded()); // Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ?
7060 IM_ASSERT(font->Scale > 0.0f);
7061 g.Font = font;
7062 g.FontBaseSize = g.IO.FontGlobalScale * g.Font->FontSize * g.Font->Scale;
7063 g.FontSize = g.CurrentWindow ? g.CurrentWindow->CalcFontSize() : 0.0f;
7064
7065 ImFontAtlas* atlas = g.Font->ContainerAtlas;
7066 g.DrawListSharedData.TexUvWhitePixel = atlas->TexUvWhitePixel;
7067 g.DrawListSharedData.Font = g.Font;
7068 g.DrawListSharedData.FontSize = g.FontSize;
7069 }
7070
7071 void ImGui::PushFont(ImFont* font)
7072 {
7073 ImGuiContext& g = *GImGui;
7074 if (!font)
7075 font = GetDefaultFont();
7076 SetCurrentFont(font);
7077 g.FontStack.push_back(font);
7078 g.CurrentWindow->DrawList->PushTextureID(font->ContainerAtlas->TexID);
7079 }
7080
7081 void ImGui::PopFont()
7082 {
7083 ImGuiContext& g = *GImGui;
7084 g.CurrentWindow->DrawList->PopTextureID();
7085 g.FontStack.pop_back();
7086 SetCurrentFont(g.FontStack.empty() ? GetDefaultFont() : g.FontStack.back());
7087 }
7088
7089 void ImGui::PushItemFlag(ImGuiItemFlags option, bool enabled)
7090 {
7091 ImGuiWindow* window = GetCurrentWindow();
7092 if (enabled)
7093 window->DC.ItemFlags |= option;
7094 else
7095 window->DC.ItemFlags &= ~option;
7096 window->DC.ItemFlagsStack.push_back(window->DC.ItemFlags);
7097 }
7098
7099 void ImGui::PopItemFlag()
7100 {
7101 ImGuiWindow* window = GetCurrentWindow();
7102 window->DC.ItemFlagsStack.pop_back();
7103 window->DC.ItemFlags = window->DC.ItemFlagsStack.empty() ? ImGuiItemFlags_Default_ : window->DC.ItemFlagsStack.back();
7104 }
7105
7106 void ImGui::PushAllowKeyboardFocus(bool allow_keyboard_focus)
7107 {
7108 PushItemFlag(ImGuiItemFlags_AllowKeyboardFocus, allow_keyboard_focus);
7109 }
7110
7111 void ImGui::PopAllowKeyboardFocus()
7112 {
7113 PopItemFlag();
7114 }
7115
7116 void ImGui::PushButtonRepeat(bool repeat)
7117 {
7118 PushItemFlag(ImGuiItemFlags_ButtonRepeat, repeat);
7119 }
7120
7121 void ImGui::PopButtonRepeat()
7122 {
7123 PopItemFlag();
7124 }
7125
7126 void ImGui::PushTextWrapPos(float wrap_pos_x)
7127 {
7128 ImGuiWindow* window = GetCurrentWindow();
7129 window->DC.TextWrapPos = wrap_pos_x;
7130 window->DC.TextWrapPosStack.push_back(wrap_pos_x);
7131 }
7132
7133 void ImGui::PopTextWrapPos()
7134 {
7135 ImGuiWindow* window = GetCurrentWindow();
7136 window->DC.TextWrapPosStack.pop_back();
7137 window->DC.TextWrapPos = window->DC.TextWrapPosStack.empty() ? -1.0f : window->DC.TextWrapPosStack.back();
7138 }
7139
7140 // 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
7141 void ImGui::PushStyleColor(ImGuiCol idx, ImU32 col)
7142 {
7143 ImGuiContext& g = *GImGui;
7144 ImGuiColMod backup;
7145 backup.Col = idx;
7146 backup.BackupValue = g.Style.Colors[idx];
7147 g.ColorModifiers.push_back(backup);
7148 g.Style.Colors[idx] = ColorConvertU32ToFloat4(col);
7149 }
7150
7151 void ImGui::PushStyleColor(ImGuiCol idx, const ImVec4& col)
7152 {
7153 ImGuiContext& g = *GImGui;
7154 ImGuiColMod backup;
7155 backup.Col = idx;
7156 backup.BackupValue = g.Style.Colors[idx];
7157 g.ColorModifiers.push_back(backup);
7158 g.Style.Colors[idx] = col;
7159 }
7160
7161 void ImGui::PopStyleColor(int count)
7162 {
7163 ImGuiContext& g = *GImGui;
7164 while (count > 0)
7165 {
7166 ImGuiColMod& backup = g.ColorModifiers.back();
7167 g.Style.Colors[backup.Col] = backup.BackupValue;
7168 g.ColorModifiers.pop_back();
7169 count--;
7170 }
7171 }
7172
7173 struct ImGuiStyleVarInfo
7174 {
7175 ImGuiDataType Type;
7176 ImU32 Count;
7177 ImU32 Offset;
7178 void* GetVarPtr(ImGuiStyle* style) const { return (void*)((unsigned char*)style + Offset); }
7179 };
7180
7181 static const ImGuiStyleVarInfo GStyleVarInfo[] =
7182 {
7183 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, Alpha) }, // ImGuiStyleVar_Alpha
7184 { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowPadding) }, // ImGuiStyleVar_WindowPadding
7185 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowRounding) }, // ImGuiStyleVar_WindowRounding
7186 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowBorderSize) }, // ImGuiStyleVar_WindowBorderSize
7187 { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowMinSize) }, // ImGuiStyleVar_WindowMinSize
7188 { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowTitleAlign) }, // ImGuiStyleVar_WindowTitleAlign
7189 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ChildRounding) }, // ImGuiStyleVar_ChildRounding
7190 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ChildBorderSize) }, // ImGuiStyleVar_ChildBorderSize
7191 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, PopupRounding) }, // ImGuiStyleVar_PopupRounding
7192 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, PopupBorderSize) }, // ImGuiStyleVar_PopupBorderSize
7193 { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, FramePadding) }, // ImGuiStyleVar_FramePadding
7194 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, FrameRounding) }, // ImGuiStyleVar_FrameRounding
7195 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, FrameBorderSize) }, // ImGuiStyleVar_FrameBorderSize
7196 { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemSpacing) }, // ImGuiStyleVar_ItemSpacing
7197 { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemInnerSpacing) }, // ImGuiStyleVar_ItemInnerSpacing
7198 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, IndentSpacing) }, // ImGuiStyleVar_IndentSpacing
7199 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ScrollbarSize) }, // ImGuiStyleVar_ScrollbarSize
7200 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, ScrollbarRounding) }, // ImGuiStyleVar_ScrollbarRounding
7201 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, GrabMinSize) }, // ImGuiStyleVar_GrabMinSize
7202 { ImGuiDataType_Float, 1, (ImU32)IM_OFFSETOF(ImGuiStyle, GrabRounding) }, // ImGuiStyleVar_GrabRounding
7203 { ImGuiDataType_Float, 2, (ImU32)IM_OFFSETOF(ImGuiStyle, ButtonTextAlign) }, // ImGuiStyleVar_ButtonTextAlign
7204 };
7205
7206 static const ImGuiStyleVarInfo* GetStyleVarInfo(ImGuiStyleVar idx)
7207 {
7208 IM_ASSERT(idx >= 0 && idx < ImGuiStyleVar_COUNT);
7209 IM_ASSERT(IM_ARRAYSIZE(GStyleVarInfo) == ImGuiStyleVar_COUNT);
7210 return &GStyleVarInfo[idx];
7211 }
7212
7213 void ImGui::PushStyleVar(ImGuiStyleVar idx, float val)
7214 {
7215 const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx);
7216 if (var_info->Type == ImGuiDataType_Float && var_info->Count == 1)
7217 {
7218 ImGuiContext& g = *GImGui;
7219 float* pvar = (float*)var_info->GetVarPtr(&g.Style);
7220 g.StyleModifiers.push_back(ImGuiStyleMod(idx, *pvar));
7221 *pvar = val;
7222 return;
7223 }
7224 IM_ASSERT(0); // Called function with wrong-type? Variable is not a float.
7225 }
7226
7227 void ImGui::PushStyleVar(ImGuiStyleVar idx, const ImVec2& val)
7228 {
7229 const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx);
7230 if (var_info->Type == ImGuiDataType_Float && var_info->Count == 2)
7231 {
7232 ImGuiContext& g = *GImGui;
7233 ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(&g.Style);
7234 g.StyleModifiers.push_back(ImGuiStyleMod(idx, *pvar));
7235 *pvar = val;
7236 return;
7237 }
7238 IM_ASSERT(0); // Called function with wrong-type? Variable is not a ImVec2.
7239 }
7240
7241 void ImGui::PopStyleVar(int count)
7242 {
7243 ImGuiContext& g = *GImGui;
7244 while (count > 0)
7245 {
7246 // We avoid a generic memcpy(data, &backup.Backup.., GDataTypeSize[info->Type] * info->Count), the overhead in Debug is not worth it.
7247 ImGuiStyleMod& backup = g.StyleModifiers.back();
7248 const ImGuiStyleVarInfo* info = GetStyleVarInfo(backup.VarIdx);
7249 void* data = info->GetVarPtr(&g.Style);
7250 if (info->Type == ImGuiDataType_Float && info->Count == 1) { ((float*)data)[0] = backup.BackupFloat[0]; }
7251 else if (info->Type == ImGuiDataType_Float && info->Count == 2) { ((float*)data)[0] = backup.BackupFloat[0]; ((float*)data)[1] = backup.BackupFloat[1]; }
7252 g.StyleModifiers.pop_back();
7253 count--;
7254 }
7255 }
7256
7257 const char* ImGui::GetStyleColorName(ImGuiCol idx)
7258 {
7259 // Create switch-case from enum with regexp: ImGuiCol_{.*}, --> case ImGuiCol_\1: return "\1";
7260 switch (idx)
7261 {
7262 case ImGuiCol_Text: return "Text";
7263 case ImGuiCol_TextDisabled: return "TextDisabled";
7264 case ImGuiCol_WindowBg: return "WindowBg";
7265 case ImGuiCol_ChildBg: return "ChildBg";
7266 case ImGuiCol_PopupBg: return "PopupBg";
7267 case ImGuiCol_Border: return "Border";
7268 case ImGuiCol_BorderShadow: return "BorderShadow";
7269 case ImGuiCol_FrameBg: return "FrameBg";
7270 case ImGuiCol_FrameBgHovered: return "FrameBgHovered";
7271 case ImGuiCol_FrameBgActive: return "FrameBgActive";
7272 case ImGuiCol_TitleBg: return "TitleBg";
7273 case ImGuiCol_TitleBgActive: return "TitleBgActive";
7274 case ImGuiCol_TitleBgCollapsed: return "TitleBgCollapsed";
7275 case ImGuiCol_MenuBarBg: return "MenuBarBg";
7276 case ImGuiCol_ScrollbarBg: return "ScrollbarBg";
7277 case ImGuiCol_ScrollbarGrab: return "ScrollbarGrab";
7278 case ImGuiCol_ScrollbarGrabHovered: return "ScrollbarGrabHovered";
7279 case ImGuiCol_ScrollbarGrabActive: return "ScrollbarGrabActive";
7280 case ImGuiCol_CheckMark: return "CheckMark";
7281 case ImGuiCol_SliderGrab: return "SliderGrab";
7282 case ImGuiCol_SliderGrabActive: return "SliderGrabActive";
7283 case ImGuiCol_Button: return "Button";
7284 case ImGuiCol_ButtonHovered: return "ButtonHovered";
7285 case ImGuiCol_ButtonActive: return "ButtonActive";
7286 case ImGuiCol_Header: return "Header";
7287 case ImGuiCol_HeaderHovered: return "HeaderHovered";
7288 case ImGuiCol_HeaderActive: return "HeaderActive";
7289 case ImGuiCol_Separator: return "Separator";
7290 case ImGuiCol_SeparatorHovered: return "SeparatorHovered";
7291 case ImGuiCol_SeparatorActive: return "SeparatorActive";
7292 case ImGuiCol_ResizeGrip: return "ResizeGrip";
7293 case ImGuiCol_ResizeGripHovered: return "ResizeGripHovered";
7294 case ImGuiCol_ResizeGripActive: return "ResizeGripActive";
7295 case ImGuiCol_PlotLines: return "PlotLines";
7296 case ImGuiCol_PlotLinesHovered: return "PlotLinesHovered";
7297 case ImGuiCol_PlotHistogram: return "PlotHistogram";
7298 case ImGuiCol_PlotHistogramHovered: return "PlotHistogramHovered";
7299 case ImGuiCol_TextSelectedBg: return "TextSelectedBg";
7300 case ImGuiCol_DragDropTarget: return "DragDropTarget";
7301 case ImGuiCol_NavHighlight: return "NavHighlight";
7302 case ImGuiCol_NavWindowingHighlight: return "NavWindowingHighlight";
7303 case ImGuiCol_NavWindowingDimBg: return "NavWindowingDimBg";
7304 case ImGuiCol_ModalWindowDimBg: return "ModalWindowDimBg";
7305 }
7306 IM_ASSERT(0);
7307 return "Unknown";
7308 }
7309
7310 bool ImGui::IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent)
7311 {
7312 if (window->RootWindow == potential_parent)
7313 return true;
7314 while (window != NULL)
7315 {
7316 if (window == potential_parent)
7317 return true;
7318 window = window->ParentWindow;
7319 }
7320 return false;
7321 }
7322
7323 bool ImGui::IsWindowHovered(ImGuiHoveredFlags flags)
7324 {
7325 IM_ASSERT((flags & ImGuiHoveredFlags_AllowWhenOverlapped) == 0); // Flags not supported by this function
7326 ImGuiContext& g = *GImGui;
7327
7328 if (flags & ImGuiHoveredFlags_AnyWindow)
7329 {
7330 if (g.HoveredWindow == NULL)
7331 return false;
7332 }
7333 else
7334 {
7335 switch (flags & (ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows))
7336 {
7337 case ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows:
7338 if (g.HoveredRootWindow != g.CurrentWindow->RootWindow)
7339 return false;
7340 break;
7341 case ImGuiHoveredFlags_RootWindow:
7342 if (g.HoveredWindow != g.CurrentWindow->RootWindow)
7343 return false;
7344 break;
7345 case ImGuiHoveredFlags_ChildWindows:
7346 if (g.HoveredWindow == NULL || !IsWindowChildOf(g.HoveredWindow, g.CurrentWindow))
7347 return false;
7348 break;
7349 default:
7350 if (g.HoveredWindow != g.CurrentWindow)
7351 return false;
7352 break;
7353 }
7354 }
7355
7356 if (!IsWindowContentHoverable(g.HoveredRootWindow, flags))
7357 return false;
7358 if (!(flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
7359 if (g.ActiveId != 0 && !g.ActiveIdAllowOverlap && g.ActiveId != g.HoveredWindow->MoveId)
7360 return false;
7361 return true;
7362 }
7363
7364 bool ImGui::IsWindowFocused(ImGuiFocusedFlags flags)
7365 {
7366 ImGuiContext& g = *GImGui;
7367 IM_ASSERT(g.CurrentWindow); // Not inside a Begin()/End()
7368
7369 if (flags & ImGuiFocusedFlags_AnyWindow)
7370 return g.NavWindow != NULL;
7371
7372 switch (flags & (ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows))
7373 {
7374 case ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows:
7375 return g.NavWindow && g.NavWindow->RootWindow == g.CurrentWindow->RootWindow;
7376 case ImGuiFocusedFlags_RootWindow:
7377 return g.NavWindow == g.CurrentWindow->RootWindow;
7378 case ImGuiFocusedFlags_ChildWindows:
7379 return g.NavWindow && IsWindowChildOf(g.NavWindow, g.CurrentWindow);
7380 default:
7381 return g.NavWindow == g.CurrentWindow;
7382 }
7383 }
7384
7385 // Can we focus this window with CTRL+TAB (or PadMenu + PadFocusPrev/PadFocusNext)
7386 bool ImGui::IsWindowNavFocusable(ImGuiWindow* window)
7387 {
7388 ImGuiContext& g = *GImGui;
7389 return window->Active && window == window->RootWindow && (!(window->Flags & ImGuiWindowFlags_NoNavFocus) || window == g.NavWindow);
7390 }
7391
7392 float ImGui::GetWindowWidth()
7393 {
7394 ImGuiWindow* window = GImGui->CurrentWindow;
7395 return window->Size.x;
7396 }
7397
7398 float ImGui::GetWindowHeight()
7399 {
7400 ImGuiWindow* window = GImGui->CurrentWindow;
7401 return window->Size.y;
7402 }
7403
7404 ImVec2 ImGui::GetWindowPos()
7405 {
7406 ImGuiContext& g = *GImGui;
7407 ImGuiWindow* window = g.CurrentWindow;
7408 return window->Pos;
7409 }
7410
7411 static void SetWindowScrollX(ImGuiWindow* window, float new_scroll_x)
7412 {
7413 window->DC.CursorMaxPos.x += window->Scroll.x; // SizeContents is generally computed based on CursorMaxPos which is affected by scroll position, so we need to apply our change to it.
7414 window->Scroll.x = new_scroll_x;
7415 window->DC.CursorMaxPos.x -= window->Scroll.x;
7416 }
7417
7418 static void SetWindowScrollY(ImGuiWindow* window, float new_scroll_y)
7419 {
7420 window->DC.CursorMaxPos.y += window->Scroll.y; // SizeContents is generally computed based on CursorMaxPos which is affected by scroll position, so we need to apply our change to it.
7421 window->Scroll.y = new_scroll_y;
7422 window->DC.CursorMaxPos.y -= window->Scroll.y;
7423 }
7424
7425 static void SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond)
7426 {
7427 // Test condition (NB: bit 0 is always true) and clear flags for next time
7428 if (cond && (window->SetWindowPosAllowFlags & cond) == 0)
7429 return;
7430
7431 IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
7432 window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
7433 window->SetWindowPosVal = ImVec2(FLT_MAX, FLT_MAX);
7434
7435 // Set
7436 const ImVec2 old_pos = window->Pos;
7437 window->Pos = ImFloor(pos);
7438 window->DC.CursorPos += (window->Pos - old_pos); // 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
7439 window->DC.CursorMaxPos += (window->Pos - old_pos); // And more importantly we need to adjust this so size calculation doesn't get affected.
7440 }
7441
7442 void ImGui::SetWindowPos(const ImVec2& pos, ImGuiCond cond)
7443 {
7444 ImGuiWindow* window = GetCurrentWindowRead();
7445 SetWindowPos(window, pos, cond);
7446 }
7447
7448 void ImGui::SetWindowPos(const char* name, const ImVec2& pos, ImGuiCond cond)
7449 {
7450 if (ImGuiWindow* window = FindWindowByName(name))
7451 SetWindowPos(window, pos, cond);
7452 }
7453
7454 ImVec2 ImGui::GetWindowSize()
7455 {
7456 ImGuiWindow* window = GetCurrentWindowRead();
7457 return window->Size;
7458 }
7459
7460 static void SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond)
7461 {
7462 // Test condition (NB: bit 0 is always true) and clear flags for next time
7463 if (cond && (window->SetWindowSizeAllowFlags & cond) == 0)
7464 return;
7465
7466 IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
7467 window->SetWindowSizeAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
7468
7469 // Set
7470 if (size.x > 0.0f)
7471 {
7472 window->AutoFitFramesX = 0;
7473 window->SizeFull.x = size.x;
7474 }
7475 else
7476 {
7477 window->AutoFitFramesX = 2;
7478 window->AutoFitOnlyGrows = false;
7479 }
7480 if (size.y > 0.0f)
7481 {
7482 window->AutoFitFramesY = 0;
7483 window->SizeFull.y = size.y;
7484 }
7485 else
7486 {
7487 window->AutoFitFramesY = 2;
7488 window->AutoFitOnlyGrows = false;
7489 }
7490 }
7491
7492 void ImGui::SetWindowSize(const ImVec2& size, ImGuiCond cond)
7493 {
7494 SetWindowSize(GImGui->CurrentWindow, size, cond);
7495 }
7496
7497 void ImGui::SetWindowSize(const char* name, const ImVec2& size, ImGuiCond cond)
7498 {
7499 if (ImGuiWindow* window = FindWindowByName(name))
7500 SetWindowSize(window, size, cond);
7501 }
7502
7503 static void SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond)
7504 {
7505 // Test condition (NB: bit 0 is always true) and clear flags for next time
7506 if (cond && (window->SetWindowCollapsedAllowFlags & cond) == 0)
7507 return;
7508 window->SetWindowCollapsedAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
7509
7510 // Set
7511 window->Collapsed = collapsed;
7512 }
7513
7514 void ImGui::SetWindowCollapsed(bool collapsed, ImGuiCond cond)
7515 {
7516 SetWindowCollapsed(GImGui->CurrentWindow, collapsed, cond);
7517 }
7518
7519 bool ImGui::IsWindowCollapsed()
7520 {
7521 ImGuiWindow* window = GetCurrentWindowRead();
7522 return window->Collapsed;
7523 }
7524
7525 bool ImGui::IsWindowAppearing()
7526 {
7527 ImGuiWindow* window = GetCurrentWindowRead();
7528 return window->Appearing;
7529 }
7530
7531 void ImGui::SetWindowCollapsed(const char* name, bool collapsed, ImGuiCond cond)
7532 {
7533 if (ImGuiWindow* window = FindWindowByName(name))
7534 SetWindowCollapsed(window, collapsed, cond);
7535 }
7536
7537 void ImGui::SetWindowFocus()
7538 {
7539 FocusWindow(GImGui->CurrentWindow);
7540 }
7541
7542 void ImGui::SetWindowFocus(const char* name)
7543 {
7544 if (name)
7545 {
7546 if (ImGuiWindow* window = FindWindowByName(name))
7547 FocusWindow(window);
7548 }
7549 else
7550 {
7551 FocusWindow(NULL);
7552 }
7553 }
7554
7555 void ImGui::SetNextWindowPos(const ImVec2& pos, ImGuiCond cond, const ImVec2& pivot)
7556 {
7557 ImGuiContext& g = *GImGui;
7558 IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
7559 g.NextWindowData.PosVal = pos;
7560 g.NextWindowData.PosPivotVal = pivot;
7561 g.NextWindowData.PosCond = cond ? cond : ImGuiCond_Always;
7562 }
7563
7564 void ImGui::SetNextWindowSize(const ImVec2& size, ImGuiCond cond)
7565 {
7566 ImGuiContext& g = *GImGui;
7567 IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
7568 g.NextWindowData.SizeVal = size;
7569 g.NextWindowData.SizeCond = cond ? cond : ImGuiCond_Always;
7570 }
7571
7572 void ImGui::SetNextWindowSizeConstraints(const ImVec2& size_min, const ImVec2& size_max, ImGuiSizeCallback custom_callback, void* custom_callback_user_data)
7573 {
7574 ImGuiContext& g = *GImGui;
7575 g.NextWindowData.SizeConstraintCond = ImGuiCond_Always;
7576 g.NextWindowData.SizeConstraintRect = ImRect(size_min, size_max);
7577 g.NextWindowData.SizeCallback = custom_callback;
7578 g.NextWindowData.SizeCallbackUserData = custom_callback_user_data;
7579 }
7580
7581 void ImGui::SetNextWindowContentSize(const ImVec2& size)
7582 {
7583 ImGuiContext& g = *GImGui;
7584 g.NextWindowData.ContentSizeVal = size; // In Begin() we will add the size of window decorations (title bar, menu etc.) to that to form a SizeContents value.
7585 g.NextWindowData.ContentSizeCond = ImGuiCond_Always;
7586 }
7587
7588 void ImGui::SetNextWindowCollapsed(bool collapsed, ImGuiCond cond)
7589 {
7590 ImGuiContext& g = *GImGui;
7591 IM_ASSERT(cond == 0 || ImIsPowerOfTwo(cond)); // Make sure the user doesn't attempt to combine multiple condition flags.
7592 g.NextWindowData.CollapsedVal = collapsed;
7593 g.NextWindowData.CollapsedCond = cond ? cond : ImGuiCond_Always;
7594 }
7595
7596 void ImGui::SetNextWindowFocus()
7597 {
7598 ImGuiContext& g = *GImGui;
7599 g.NextWindowData.FocusCond = ImGuiCond_Always; // Using a Cond member for consistency (may transition all of them to single flag set for fast Clear() op)
7600 }
7601
7602 void ImGui::SetNextWindowBgAlpha(float alpha)
7603 {
7604 ImGuiContext& g = *GImGui;
7605 g.NextWindowData.BgAlphaVal = alpha;
7606 g.NextWindowData.BgAlphaCond = ImGuiCond_Always; // Using a Cond member for consistency (may transition all of them to single flag set for fast Clear() op)
7607 }
7608
7609 // In window space (not screen space!)
7610 ImVec2 ImGui::GetContentRegionMax()
7611 {
7612 ImGuiWindow* window = GetCurrentWindowRead();
7613 ImVec2 mx = window->ContentsRegionRect.Max - window->Pos;
7614 if (window->DC.ColumnsSet)
7615 mx.x = GetColumnOffset(window->DC.ColumnsSet->Current + 1) - window->WindowPadding.x;
7616 return mx;
7617 }
7618
7619 ImVec2 ImGui::GetContentRegionAvail()
7620 {
7621 ImGuiWindow* window = GetCurrentWindowRead();
7622 return GetContentRegionMax() - (window->DC.CursorPos - window->Pos);
7623 }
7624
7625 float ImGui::GetContentRegionAvailWidth()
7626 {
7627 return GetContentRegionAvail().x;
7628 }
7629
7630 // In window space (not screen space!)
7631 ImVec2 ImGui::GetWindowContentRegionMin()
7632 {
7633 ImGuiWindow* window = GetCurrentWindowRead();
7634 return window->ContentsRegionRect.Min - window->Pos;
7635 }
7636
7637 ImVec2 ImGui::GetWindowContentRegionMax()
7638 {
7639 ImGuiWindow* window = GetCurrentWindowRead();
7640 return window->ContentsRegionRect.Max - window->Pos;
7641 }
7642
7643 float ImGui::GetWindowContentRegionWidth()
7644 {
7645 ImGuiWindow* window = GetCurrentWindowRead();
7646 return window->ContentsRegionRect.GetWidth();
7647 }
7648
7649 float ImGui::GetTextLineHeight()
7650 {
7651 ImGuiContext& g = *GImGui;
7652 return g.FontSize;
7653 }
7654
7655 float ImGui::GetTextLineHeightWithSpacing()
7656 {
7657 ImGuiContext& g = *GImGui;
7658 return g.FontSize + g.Style.ItemSpacing.y;
7659 }
7660
7661 float ImGui::GetFrameHeight()
7662 {
7663 ImGuiContext& g = *GImGui;
7664 return g.FontSize + g.Style.FramePadding.y * 2.0f;
7665 }
7666
7667 float ImGui::GetFrameHeightWithSpacing()
7668 {
7669 ImGuiContext& g = *GImGui;
7670 return g.FontSize + g.Style.FramePadding.y * 2.0f + g.Style.ItemSpacing.y;
7671 }
7672
7673 ImDrawList* ImGui::GetWindowDrawList()
7674 {
7675 ImGuiWindow* window = GetCurrentWindow();
7676 return window->DrawList;
7677 }
7678
7679 ImFont* ImGui::GetFont()
7680 {
7681 return GImGui->Font;
7682 }
7683
7684 float ImGui::GetFontSize()
7685 {
7686 return GImGui->FontSize;
7687 }
7688
7689 ImVec2 ImGui::GetFontTexUvWhitePixel()
7690 {
7691 return GImGui->DrawListSharedData.TexUvWhitePixel;
7692 }
7693
7694 void ImGui::SetWindowFontScale(float scale)
7695 {
7696 ImGuiContext& g = *GImGui;
7697 ImGuiWindow* window = GetCurrentWindow();
7698 window->FontWindowScale = scale;
7699 g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize();
7700 }
7701
7702 // User generally sees positions in window coordinates. Internally we store CursorPos in absolute screen coordinates because it is more convenient.
7703 // 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'.
7704 ImVec2 ImGui::GetCursorPos()
7705 {
7706 ImGuiWindow* window = GetCurrentWindowRead();
7707 return window->DC.CursorPos - window->Pos + window->Scroll;
7708 }
7709
7710 float ImGui::GetCursorPosX()
7711 {
7712 ImGuiWindow* window = GetCurrentWindowRead();
7713 return window->DC.CursorPos.x - window->Pos.x + window->Scroll.x;
7714 }
7715
7716 float ImGui::GetCursorPosY()
7717 {
7718 ImGuiWindow* window = GetCurrentWindowRead();
7719 return window->DC.CursorPos.y - window->Pos.y + window->Scroll.y;
7720 }
7721
7722 void ImGui::SetCursorPos(const ImVec2& local_pos)
7723 {
7724 ImGuiWindow* window = GetCurrentWindow();
7725 window->DC.CursorPos = window->Pos - window->Scroll + local_pos;
7726 window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos);
7727 }
7728
7729 void ImGui::SetCursorPosX(float x)
7730 {
7731 ImGuiWindow* window = GetCurrentWindow();
7732 window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + x;
7733 window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPos.x);
7734 }
7735
7736 void ImGui::SetCursorPosY(float y)
7737 {
7738 ImGuiWindow* window = GetCurrentWindow();
7739 window->DC.CursorPos.y = window->Pos.y - window->Scroll.y + y;
7740 window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y);
7741 }
7742
7743 ImVec2 ImGui::GetCursorStartPos()
7744 {
7745 ImGuiWindow* window = GetCurrentWindowRead();
7746 return window->DC.CursorStartPos - window->Pos;
7747 }
7748
7749 ImVec2 ImGui::GetCursorScreenPos()
7750 {
7751 ImGuiWindow* window = GetCurrentWindowRead();
7752 return window->DC.CursorPos;
7753 }
7754
7755 void ImGui::SetCursorScreenPos(const ImVec2& screen_pos)
7756 {
7757 ImGuiWindow* window = GetCurrentWindow();
7758 window->DC.CursorPos = screen_pos;
7759 window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos);
7760 }
7761
7762 float ImGui::GetScrollX()
7763 {
7764 return GImGui->CurrentWindow->Scroll.x;
7765 }
7766
7767 float ImGui::GetScrollY()
7768 {
7769 return GImGui->CurrentWindow->Scroll.y;
7770 }
7771
7772 float ImGui::GetScrollMaxX()
7773 {
7774 return GetScrollMaxX(GImGui->CurrentWindow);
7775 }
7776
7777 float ImGui::GetScrollMaxY()
7778 {
7779 return GetScrollMaxY(GImGui->CurrentWindow);
7780 }
7781
7782 void ImGui::SetScrollX(float scroll_x)
7783 {
7784 ImGuiWindow* window = GetCurrentWindow();
7785 window->ScrollTarget.x = scroll_x;
7786 window->ScrollTargetCenterRatio.x = 0.0f;
7787 }
7788
7789 void ImGui::SetScrollY(float scroll_y)
7790 {
7791 ImGuiWindow* window = GetCurrentWindow();
7792 window->ScrollTarget.y = scroll_y + window->TitleBarHeight() + window->MenuBarHeight(); // title bar height canceled out when using ScrollTargetRelY
7793 window->ScrollTargetCenterRatio.y = 0.0f;
7794 }
7795
7796 void ImGui::SetScrollFromPosY(float pos_y, float center_y_ratio)
7797 {
7798 // We store a target position so centering can occur on the next frame when we are guaranteed to have a known window size
7799 ImGuiWindow* window = GetCurrentWindow();
7800 IM_ASSERT(center_y_ratio >= 0.0f && center_y_ratio <= 1.0f);
7801 window->ScrollTarget.y = (float)(int)(pos_y + window->Scroll.y);
7802 window->ScrollTargetCenterRatio.y = center_y_ratio;
7803 }
7804
7805 // center_y_ratio: 0.0f top of last item, 0.5f vertical center of last item, 1.0f bottom of last item.
7806 void ImGui::SetScrollHere(float center_y_ratio)
7807 {
7808 ImGuiWindow* window = GetCurrentWindow();
7809 float target_y = window->DC.CursorPosPrevLine.y - window->Pos.y; // Top of last item, in window space
7810 target_y += (window->DC.PrevLineHeight * center_y_ratio) + (GImGui->Style.ItemSpacing.y * (center_y_ratio - 0.5f) * 2.0f); // Precisely aim above, in the middle or below the last line.
7811 SetScrollFromPosY(target_y, center_y_ratio);
7812 }
7813
7814 void ImGui::ActivateItem(ImGuiID id)
7815 {
7816 ImGuiContext& g = *GImGui;
7817 g.NavNextActivateId = id;
7818 }
7819
7820 void ImGui::SetKeyboardFocusHere(int offset)
7821 {
7822 IM_ASSERT(offset >= -1); // -1 is allowed but not below
7823 ImGuiWindow* window = GetCurrentWindow();
7824 window->FocusIdxAllRequestNext = window->FocusIdxAllCounter + 1 + offset;
7825 window->FocusIdxTabRequestNext = INT_MAX;
7826 }
7827
7828 void ImGui::SetItemDefaultFocus()
7829 {
7830 ImGuiContext& g = *GImGui;
7831 ImGuiWindow* window = g.CurrentWindow;
7832 if (!window->Appearing)
7833 return;
7834 if (g.NavWindow == window->RootWindowForNav && (g.NavInitRequest || g.NavInitResultId != 0) && g.NavLayer == g.NavWindow->DC.NavLayerCurrent)
7835 {
7836 g.NavInitRequest = false;
7837 g.NavInitResultId = g.NavWindow->DC.LastItemId;
7838 g.NavInitResultRectRel = ImRect(g.NavWindow->DC.LastItemRect.Min - g.NavWindow->Pos, g.NavWindow->DC.LastItemRect.Max - g.NavWindow->Pos);
7839 NavUpdateAnyRequestFlag();
7840 if (!IsItemVisible())
7841 SetScrollHere();
7842 }
7843 }
7844
7845 void ImGui::SetStateStorage(ImGuiStorage* tree)
7846 {
7847 ImGuiWindow* window = GetCurrentWindow();
7848 window->DC.StateStorage = tree ? tree : &window->StateStorage;
7849 }
7850
7851 ImGuiStorage* ImGui::GetStateStorage()
7852 {
7853 ImGuiWindow* window = GetCurrentWindowRead();
7854 return window->DC.StateStorage;
7855 }
7856
7857 void ImGui::TextV(const char* fmt, va_list args)
7858 {
7859 ImGuiWindow* window = GetCurrentWindow();
7860 if (window->SkipItems)
7861 return;
7862
7863 ImGuiContext& g = *GImGui;
7864 const char* text_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
7865 TextUnformatted(g.TempBuffer, text_end);
7866 }
7867
7868 void ImGui::Text(const char* fmt, ...)
7869 {
7870 va_list args;
7871 va_start(args, fmt);
7872 TextV(fmt, args);
7873 va_end(args);
7874 }
7875
7876 void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args)
7877 {
7878 PushStyleColor(ImGuiCol_Text, col);
7879 TextV(fmt, args);
7880 PopStyleColor();
7881 }
7882
7883 void ImGui::TextColored(const ImVec4& col, const char* fmt, ...)
7884 {
7885 va_list args;
7886 va_start(args, fmt);
7887 TextColoredV(col, fmt, args);
7888 va_end(args);
7889 }
7890
7891 void ImGui::TextDisabledV(const char* fmt, va_list args)
7892 {
7893 PushStyleColor(ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled]);
7894 TextV(fmt, args);
7895 PopStyleColor();
7896 }
7897
7898 void ImGui::TextDisabled(const char* fmt, ...)
7899 {
7900 va_list args;
7901 va_start(args, fmt);
7902 TextDisabledV(fmt, args);
7903 va_end(args);
7904 }
7905
7906 void ImGui::TextWrappedV(const char* fmt, va_list args)
7907 {
7908 bool need_wrap = (GImGui->CurrentWindow->DC.TextWrapPos < 0.0f); // Keep existing wrap position is one ia already set
7909 if (need_wrap) PushTextWrapPos(0.0f);
7910 TextV(fmt, args);
7911 if (need_wrap) PopTextWrapPos();
7912 }
7913
7914 void ImGui::TextWrapped(const char* fmt, ...)
7915 {
7916 va_list args;
7917 va_start(args, fmt);
7918 TextWrappedV(fmt, args);
7919 va_end(args);
7920 }
7921
7922 void ImGui::TextUnformatted(const char* text, const char* text_end)
7923 {
7924 ImGuiWindow* window = GetCurrentWindow();
7925 if (window->SkipItems)
7926 return;
7927
7928 ImGuiContext& g = *GImGui;
7929 IM_ASSERT(text != NULL);
7930 const char* text_begin = text;
7931 if (text_end == NULL)
7932 text_end = text + strlen(text); // FIXME-OPT
7933
7934 const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrentLineTextBaseOffset);
7935 const float wrap_pos_x = window->DC.TextWrapPos;
7936 const bool wrap_enabled = wrap_pos_x >= 0.0f;
7937 if (text_end - text > 2000 && !wrap_enabled)
7938 {
7939 // Long text!
7940 // Perform manual coarse clipping to optimize for long multi-line text
7941 // From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled.
7942 // We also don't vertically center the text within the line full height, which is unlikely to matter because we are likely the biggest and only item on the line.
7943 const char* line = text;
7944 const float line_height = GetTextLineHeight();
7945 const ImRect clip_rect = window->ClipRect;
7946 ImVec2 text_size(0,0);
7947
7948 if (text_pos.y <= clip_rect.Max.y)
7949 {
7950 ImVec2 pos = text_pos;
7951
7952 // Lines to skip (can't skip when logging text)
7953 if (!g.LogEnabled)
7954 {
7955 int lines_skippable = (int)((clip_rect.Min.y - text_pos.y) / line_height);
7956 if (lines_skippable > 0)
7957 {
7958 int lines_skipped = 0;
7959 while (line < text_end && lines_skipped < lines_skippable)
7960 {
7961 const char* line_end = strchr(line, '\n');
7962 if (!line_end)
7963 line_end = text_end;
7964 line = line_end + 1;
7965 lines_skipped++;
7966 }
7967 pos.y += lines_skipped * line_height;
7968 }
7969 }
7970
7971 // Lines to render
7972 if (line < text_end)
7973 {
7974 ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height));
7975 while (line < text_end)
7976 {
7977 const char* line_end = strchr(line, '\n');
7978 if (IsClippedEx(line_rect, 0, false))
7979 break;
7980
7981 const ImVec2 line_size = CalcTextSize(line, line_end, false);
7982 text_size.x = ImMax(text_size.x, line_size.x);
7983 RenderText(pos, line, line_end, false);
7984 if (!line_end)
7985 line_end = text_end;
7986 line = line_end + 1;
7987 line_rect.Min.y += line_height;
7988 line_rect.Max.y += line_height;
7989 pos.y += line_height;
7990 }
7991
7992 // Count remaining lines
7993 int lines_skipped = 0;
7994 while (line < text_end)
7995 {
7996 const char* line_end = strchr(line, '\n');
7997 if (!line_end)
7998 line_end = text_end;
7999 line = line_end + 1;
8000 lines_skipped++;
8001 }
8002 pos.y += lines_skipped * line_height;
8003 }
8004
8005 text_size.y += (pos - text_pos).y;
8006 }
8007
8008 ImRect bb(text_pos, text_pos + text_size);
8009 ItemSize(bb);
8010 ItemAdd(bb, 0);
8011 }
8012 else
8013 {
8014 const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f;
8015 const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width);
8016
8017 // Account of baseline offset
8018 ImRect bb(text_pos, text_pos + text_size);
8019 ItemSize(text_size);
8020 if (!ItemAdd(bb, 0))
8021 return;
8022
8023 // Render (we don't hide text after ## in this end-user function)
8024 RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width);
8025 }
8026 }
8027
8028 void ImGui::AlignTextToFramePadding()
8029 {
8030 ImGuiWindow* window = GetCurrentWindow();
8031 if (window->SkipItems)
8032 return;
8033
8034 ImGuiContext& g = *GImGui;
8035 window->DC.CurrentLineHeight = ImMax(window->DC.CurrentLineHeight, g.FontSize + g.Style.FramePadding.y * 2);
8036 window->DC.CurrentLineTextBaseOffset = ImMax(window->DC.CurrentLineTextBaseOffset, g.Style.FramePadding.y);
8037 }
8038
8039 // Add a label+text combo aligned to other label+value widgets
8040 void ImGui::LabelTextV(const char* label, const char* fmt, va_list args)
8041 {
8042 ImGuiWindow* window = GetCurrentWindow();
8043 if (window->SkipItems)
8044 return;
8045
8046 ImGuiContext& g = *GImGui;
8047 const ImGuiStyle& style = g.Style;
8048 const float w = CalcItemWidth();
8049
8050 const ImVec2 label_size = CalcTextSize(label, NULL, true);
8051 const ImRect value_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2));
8052 const ImRect total_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w + (label_size.x > 0.0f ? style.ItemInnerSpacing.x : 0.0f), style.FramePadding.y*2) + label_size);
8053 ItemSize(total_bb, style.FramePadding.y);
8054 if (!ItemAdd(total_bb, 0))
8055 return;
8056
8057 // Render
8058 const char* value_text_begin = &g.TempBuffer[0];
8059 const char* value_text_end = value_text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
8060 RenderTextClipped(value_bb.Min, value_bb.Max, value_text_begin, value_text_end, NULL, ImVec2(0.0f,0.5f));
8061 if (label_size.x > 0.0f)
8062 RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label);
8063 }
8064
8065 void ImGui::LabelText(const char* label, const char* fmt, ...)
8066 {
8067 va_list args;
8068 va_start(args, fmt);
8069 LabelTextV(label, fmt, args);
8070 va_end(args);
8071 }
8072
8073 bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags)
8074 {
8075 ImGuiContext& g = *GImGui;
8076 ImGuiWindow* window = GetCurrentWindow();
8077
8078 if (flags & ImGuiButtonFlags_Disabled)
8079 {
8080 if (out_hovered) *out_hovered = false;
8081 if (out_held) *out_held = false;
8082 if (g.ActiveId == id) ClearActiveID();
8083 return false;
8084 }
8085
8086 // Default behavior requires click+release on same spot
8087 if ((flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnRelease | ImGuiButtonFlags_PressedOnDoubleClick)) == 0)
8088 flags |= ImGuiButtonFlags_PressedOnClickRelease;
8089
8090 ImGuiWindow* backup_hovered_window = g.HoveredWindow;
8091 if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window)
8092 g.HoveredWindow = window;
8093
8094 bool pressed = false;
8095 bool hovered = ItemHoverable(bb, id);
8096
8097 // Drag source doesn't report as hovered
8098 if (hovered && g.DragDropActive && g.DragDropPayload.SourceId == id && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoDisableHover))
8099 hovered = false;
8100
8101 // Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button
8102 if (g.DragDropActive && (flags & ImGuiButtonFlags_PressedOnDragDropHold) && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers))
8103 if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
8104 {
8105 hovered = true;
8106 SetHoveredID(id);
8107 if (CalcTypematicPressedRepeatAmount(g.HoveredIdTimer + 0.0001f, g.HoveredIdTimer + 0.0001f - g.IO.DeltaTime, 0.01f, 0.70f)) // FIXME: Our formula for CalcTypematicPressedRepeatAmount() is fishy
8108 {
8109 pressed = true;
8110 FocusWindow(window);
8111 }
8112 }
8113
8114 if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window)
8115 g.HoveredWindow = backup_hovered_window;
8116
8117 // AllowOverlap mode (rarely used) requires previous frame HoveredId to be null or to match. This allows using patterns where a later submitted widget overlaps a previous one.
8118 if (hovered && (flags & ImGuiButtonFlags_AllowItemOverlap) && (g.HoveredIdPreviousFrame != id && g.HoveredIdPreviousFrame != 0))
8119 hovered = false;
8120
8121 // Mouse
8122 if (hovered)
8123 {
8124 if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt))
8125 {
8126 // | CLICKING | HOLDING with ImGuiButtonFlags_Repeat
8127 // PressedOnClickRelease | <on release>* | <on repeat> <on repeat> .. (NOT on release) <-- MOST COMMON! (*) only if both click/release were over bounds
8128 // PressedOnClick | <on click> | <on click> <on repeat> <on repeat> ..
8129 // PressedOnRelease | <on release> | <on repeat> <on repeat> .. (NOT on release)
8130 // PressedOnDoubleClick | <on dclick> | <on dclick> <on repeat> <on repeat> ..
8131 // FIXME-NAV: We don't honor those different behaviors.
8132 if ((flags & ImGuiButtonFlags_PressedOnClickRelease) && g.IO.MouseClicked[0])
8133 {
8134 SetActiveID(id, window);
8135 if (!(flags & ImGuiButtonFlags_NoNavFocus))
8136 SetFocusID(id, window);
8137 FocusWindow(window);
8138 }
8139 if (((flags & ImGuiButtonFlags_PressedOnClick) && g.IO.MouseClicked[0]) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDoubleClicked[0]))
8140 {
8141 pressed = true;
8142 if (flags & ImGuiButtonFlags_NoHoldingActiveID)
8143 ClearActiveID();
8144 else
8145 SetActiveID(id, window); // Hold on ID
8146 FocusWindow(window);
8147 }
8148 if ((flags & ImGuiButtonFlags_PressedOnRelease) && g.IO.MouseReleased[0])
8149 {
8150 if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps <on release>
8151 pressed = true;
8152 ClearActiveID();
8153 }
8154
8155 // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above).
8156 // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings.
8157 if ((flags & ImGuiButtonFlags_Repeat) && g.ActiveId == id && g.IO.MouseDownDuration[0] > 0.0f && IsMouseClicked(0, true))
8158 pressed = true;
8159 }
8160
8161 if (pressed)
8162 g.NavDisableHighlight = true;
8163 }
8164
8165 // Gamepad/Keyboard navigation
8166 // We report navigated item as hovered but we don't set g.HoveredId to not interfere with mouse.
8167 if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover && (g.ActiveId == 0 || g.ActiveId == id || g.ActiveId == window->MoveId))
8168 hovered = true;
8169
8170 if (g.NavActivateDownId == id)
8171 {
8172 bool nav_activated_by_code = (g.NavActivateId == id);
8173 bool nav_activated_by_inputs = IsNavInputPressed(ImGuiNavInput_Activate, (flags & ImGuiButtonFlags_Repeat) ? ImGuiInputReadMode_Repeat : ImGuiInputReadMode_Pressed);
8174 if (nav_activated_by_code || nav_activated_by_inputs)
8175 pressed = true;
8176 if (nav_activated_by_code || nav_activated_by_inputs || g.ActiveId == id)
8177 {
8178 // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button.
8179 g.NavActivateId = id; // This is so SetActiveId assign a Nav source
8180 SetActiveID(id, window);
8181 if (!(flags & ImGuiButtonFlags_NoNavFocus))
8182 SetFocusID(id, window);
8183 g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right) | (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
8184 }
8185 }
8186
8187 bool held = false;
8188 if (g.ActiveId == id)
8189 {
8190 if (g.ActiveIdSource == ImGuiInputSource_Mouse)
8191 {
8192 if (g.ActiveIdIsJustActivated)
8193 g.ActiveIdClickOffset = g.IO.MousePos - bb.Min;
8194 if (g.IO.MouseDown[0])
8195 {
8196 held = true;
8197 }
8198 else
8199 {
8200 if (hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease))
8201 if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps <on release>
8202 if (!g.DragDropActive)
8203 pressed = true;
8204 ClearActiveID();
8205 }
8206 if (!(flags & ImGuiButtonFlags_NoNavFocus))
8207 g.NavDisableHighlight = true;
8208 }
8209 else if (g.ActiveIdSource == ImGuiInputSource_Nav)
8210 {
8211 if (g.NavActivateDownId != id)
8212 ClearActiveID();
8213 }
8214 }
8215
8216 if (out_hovered) *out_hovered = hovered;
8217 if (out_held) *out_held = held;
8218
8219 return pressed;
8220 }
8221
8222 bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags)
8223 {
8224 ImGuiWindow* window = GetCurrentWindow();
8225 if (window->SkipItems)
8226 return false;
8227
8228 ImGuiContext& g = *GImGui;
8229 const ImGuiStyle& style = g.Style;
8230 const ImGuiID id = window->GetID(label);
8231 const ImVec2 label_size = CalcTextSize(label, NULL, true);
8232
8233 ImVec2 pos = window->DC.CursorPos;
8234 if ((flags & ImGuiButtonFlags_AlignTextBaseLine) && style.FramePadding.y < window->DC.CurrentLineTextBaseOffset) // Try to vertically align buttons that are smaller/have no padding so that text baseline matches (bit hacky, since it shouldn't be a flag)
8235 pos.y += window->DC.CurrentLineTextBaseOffset - style.FramePadding.y;
8236 ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f);
8237
8238 const ImRect bb(pos, pos + size);
8239 ItemSize(bb, style.FramePadding.y);
8240 if (!ItemAdd(bb, id))
8241 return false;
8242
8243 if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat)
8244 flags |= ImGuiButtonFlags_Repeat;
8245 bool hovered, held;
8246 bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
8247 if (pressed)
8248 MarkItemValueChanged(id);
8249
8250 // Render
8251 const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
8252 RenderNavHighlight(bb, id);
8253 RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);
8254 RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb);
8255
8256 // Automatically close popups
8257 //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
8258 // CloseCurrentPopup();
8259
8260 return pressed;
8261 }
8262
8263 bool ImGui::Button(const char* label, const ImVec2& size_arg)
8264 {
8265 return ButtonEx(label, size_arg, 0);
8266 }
8267
8268 // Small buttons fits within text without additional vertical spacing.
8269 bool ImGui::SmallButton(const char* label)
8270 {
8271 ImGuiContext& g = *GImGui;
8272 float backup_padding_y = g.Style.FramePadding.y;
8273 g.Style.FramePadding.y = 0.0f;
8274 bool pressed = ButtonEx(label, ImVec2(0,0), ImGuiButtonFlags_AlignTextBaseLine);
8275 g.Style.FramePadding.y = backup_padding_y;
8276 return pressed;
8277 }
8278
8279 bool ImGui::ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size, ImGuiButtonFlags flags)
8280 {
8281 ImGuiWindow* window = GetCurrentWindow();
8282 if (window->SkipItems)
8283 return false;
8284
8285 ImGuiContext& g = *GImGui;
8286 const ImGuiID id = window->GetID(str_id);
8287 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
8288 const float default_size = GetFrameHeight();
8289 ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f);
8290 if (!ItemAdd(bb, id))
8291 return false;
8292
8293 if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat)
8294 flags |= ImGuiButtonFlags_Repeat;
8295
8296 bool hovered, held;
8297 bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
8298
8299 // Render
8300 const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
8301 RenderNavHighlight(bb, id);
8302 RenderFrame(bb.Min, bb.Max, col, true, g.Style.FrameRounding);
8303 RenderArrow(bb.Min + ImVec2(ImMax(0.0f, size.x - g.FontSize - g.Style.FramePadding.x), ImMax(0.0f, size.y - g.FontSize - g.Style.FramePadding.y)), dir);
8304
8305 return pressed;
8306 }
8307
8308 bool ImGui::ArrowButton(const char* str_id, ImGuiDir dir)
8309 {
8310 float sz = GetFrameHeight();
8311 return ArrowButtonEx(str_id, dir, ImVec2(sz, sz), 0);
8312 }
8313
8314 // Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack.
8315 // Then you can keep 'str_id' empty or the same for all your buttons (instead of creating a string based on a non-string id)
8316 bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg)
8317 {
8318 ImGuiWindow* window = GetCurrentWindow();
8319 if (window->SkipItems)
8320 return false;
8321
8322 const ImGuiID id = window->GetID(str_id);
8323 ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f);
8324 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
8325 ItemSize(bb);
8326 if (!ItemAdd(bb, id))
8327 return false;
8328
8329 bool hovered, held;
8330 bool pressed = ButtonBehavior(bb, id, &hovered, &held);
8331
8332 return pressed;
8333 }
8334
8335 // Button to close a window
8336 bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos, float radius)
8337 {
8338 ImGuiContext& g = *GImGui;
8339 ImGuiWindow* window = g.CurrentWindow;
8340
8341 // We intentionally allow interaction when clipped so that a mechanical Alt,Right,Validate sequence close a window.
8342 // (this isn't the regular behavior of buttons, but it doesn't affect the user much because navigation tends to keep items visible).
8343 const ImRect bb(pos - ImVec2(radius,radius), pos + ImVec2(radius,radius));
8344 bool is_clipped = !ItemAdd(bb, id);
8345
8346 bool hovered, held;
8347 bool pressed = ButtonBehavior(bb, id, &hovered, &held);
8348 if (is_clipped)
8349 return pressed;
8350
8351 // Render
8352 ImVec2 center = bb.GetCenter();
8353 if (hovered)
8354 window->DrawList->AddCircleFilled(center, ImMax(2.0f, radius), GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered), 9);
8355
8356 float cross_extent = (radius * 0.7071f) - 1.0f;
8357 ImU32 cross_col = GetColorU32(ImGuiCol_Text);
8358 center -= ImVec2(0.5f, 0.5f);
8359 window->DrawList->AddLine(center + ImVec2(+cross_extent,+cross_extent), center + ImVec2(-cross_extent,-cross_extent), cross_col, 1.0f);
8360 window->DrawList->AddLine(center + ImVec2(+cross_extent,-cross_extent), center + ImVec2(-cross_extent,+cross_extent), cross_col, 1.0f);
8361
8362 return pressed;
8363 }
8364
8365 bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos)
8366 {
8367 ImGuiContext& g = *GImGui;
8368 ImGuiWindow* window = g.CurrentWindow;
8369
8370 ImRect bb(pos, pos + ImVec2(g.FontSize, g.FontSize));
8371 ItemAdd(bb, id);
8372 bool ret = ButtonBehavior(bb, id, NULL, NULL, ImGuiButtonFlags_None);
8373 RenderNavHighlight(bb, id);
8374 RenderArrow(bb.Min, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f);
8375
8376 // Switch to moving the window after mouse is moved beyond the initial drag threshold
8377 if (IsItemActive() && IsMouseDragging())
8378 StartMouseMovingWindow(window);
8379
8380 return ret;
8381 }
8382
8383 void ImGui::Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col)
8384 {
8385 ImGuiWindow* window = GetCurrentWindow();
8386 if (window->SkipItems)
8387 return;
8388
8389 ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
8390 if (border_col.w > 0.0f)
8391 bb.Max += ImVec2(2,2);
8392 ItemSize(bb);
8393 if (!ItemAdd(bb, 0))
8394 return;
8395
8396 if (border_col.w > 0.0f)
8397 {
8398 window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f);
8399 window->DrawList->AddImage(user_texture_id, bb.Min+ImVec2(1,1), bb.Max-ImVec2(1,1), uv0, uv1, GetColorU32(tint_col));
8400 }
8401 else
8402 {
8403 window->DrawList->AddImage(user_texture_id, bb.Min, bb.Max, uv0, uv1, GetColorU32(tint_col));
8404 }
8405 }
8406
8407 // frame_padding < 0: uses FramePadding from style (default)
8408 // frame_padding = 0: no framing
8409 // frame_padding > 0: set framing size
8410 // The color used are the button colors.
8411 bool ImGui::ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, int frame_padding, const ImVec4& bg_col, const ImVec4& tint_col)
8412 {
8413 ImGuiWindow* window = GetCurrentWindow();
8414 if (window->SkipItems)
8415 return false;
8416
8417 ImGuiContext& g = *GImGui;
8418 const ImGuiStyle& style = g.Style;
8419
8420 // Default to using texture ID as ID. User can still push string/integer prefixes.
8421 // We could hash the size/uv to create a unique ID but that would prevent the user from animating UV.
8422 PushID((void *)user_texture_id);
8423 const ImGuiID id = window->GetID("#image");
8424 PopID();
8425
8426 const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) : style.FramePadding;
8427 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding*2);
8428 const ImRect image_bb(window->DC.CursorPos + padding, window->DC.CursorPos + padding + size);
8429 ItemSize(bb);
8430 if (!ItemAdd(bb, id))
8431 return false;
8432
8433 bool hovered, held;
8434 bool pressed = ButtonBehavior(bb, id, &hovered, &held);
8435
8436 // Render
8437 const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
8438 RenderNavHighlight(bb, id);
8439 RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, style.FrameRounding));
8440 if (bg_col.w > 0.0f)
8441 window->DrawList->AddRectFilled(image_bb.Min, image_bb.Max, GetColorU32(bg_col));
8442 window->DrawList->AddImage(user_texture_id, image_bb.Min, image_bb.Max, uv0, uv1, GetColorU32(tint_col));
8443
8444 return pressed;
8445 }
8446
8447 // Start logging ImGui output to TTY
8448 void ImGui::LogToTTY(int max_depth)
8449 {
8450 ImGuiContext& g = *GImGui;
8451 if (g.LogEnabled)
8452 return;
8453 ImGuiWindow* window = g.CurrentWindow;
8454
8455 IM_ASSERT(g.LogFile == NULL);
8456 g.LogFile = stdout;
8457 g.LogEnabled = true;
8458 g.LogStartDepth = window->DC.TreeDepth;
8459 if (max_depth >= 0)
8460 g.LogAutoExpandMaxDepth = max_depth;
8461 }
8462
8463 // Start logging ImGui output to given file
8464 void ImGui::LogToFile(int max_depth, const char* filename)
8465 {
8466 ImGuiContext& g = *GImGui;
8467 if (g.LogEnabled)
8468 return;
8469 ImGuiWindow* window = g.CurrentWindow;
8470
8471 if (!filename)
8472 {
8473 filename = g.IO.LogFilename;
8474 if (!filename)
8475 return;
8476 }
8477
8478 IM_ASSERT(g.LogFile == NULL);
8479 g.LogFile = ImFileOpen(filename, "ab");
8480 if (!g.LogFile)
8481 {
8482 IM_ASSERT(g.LogFile != NULL); // Consider this an error
8483 return;
8484 }
8485 g.LogEnabled = true;
8486 g.LogStartDepth = window->DC.TreeDepth;
8487 if (max_depth >= 0)
8488 g.LogAutoExpandMaxDepth = max_depth;
8489 }
8490
8491 // Start logging ImGui output to clipboard
8492 void ImGui::LogToClipboard(int max_depth)
8493 {
8494 ImGuiContext& g = *GImGui;
8495 if (g.LogEnabled)
8496 return;
8497 ImGuiWindow* window = g.CurrentWindow;
8498
8499 IM_ASSERT(g.LogFile == NULL);
8500 g.LogFile = NULL;
8501 g.LogEnabled = true;
8502 g.LogStartDepth = window->DC.TreeDepth;
8503 if (max_depth >= 0)
8504 g.LogAutoExpandMaxDepth = max_depth;
8505 }
8506
8507 void ImGui::LogFinish()
8508 {
8509 ImGuiContext& g = *GImGui;
8510 if (!g.LogEnabled)
8511 return;
8512
8513 LogText(IM_NEWLINE);
8514 if (g.LogFile != NULL)
8515 {
8516 if (g.LogFile == stdout)
8517 fflush(g.LogFile);
8518 else
8519 fclose(g.LogFile);
8520 g.LogFile = NULL;
8521 }
8522 if (g.LogClipboard.size() > 1)
8523 {
8524 SetClipboardText(g.LogClipboard.begin());
8525 g.LogClipboard.clear();
8526 }
8527 g.LogEnabled = false;
8528 }
8529
8530 // Helper to display logging buttons
8531 void ImGui::LogButtons()
8532 {
8533 ImGuiContext& g = *GImGui;
8534
8535 PushID("LogButtons");
8536 const bool log_to_tty = Button("Log To TTY"); SameLine();
8537 const bool log_to_file = Button("Log To File"); SameLine();
8538 const bool log_to_clipboard = Button("Log To Clipboard"); SameLine();
8539 PushItemWidth(80.0f);
8540 PushAllowKeyboardFocus(false);
8541 SliderInt("Depth", &g.LogAutoExpandMaxDepth, 0, 9, NULL);
8542 PopAllowKeyboardFocus();
8543 PopItemWidth();
8544 PopID();
8545
8546 // Start logging at the end of the function so that the buttons don't appear in the log
8547 if (log_to_tty)
8548 LogToTTY(g.LogAutoExpandMaxDepth);
8549 if (log_to_file)
8550 LogToFile(g.LogAutoExpandMaxDepth, g.IO.LogFilename);
8551 if (log_to_clipboard)
8552 LogToClipboard(g.LogAutoExpandMaxDepth);
8553 }
8554
8555 bool ImGui::TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags)
8556 {
8557 if (flags & ImGuiTreeNodeFlags_Leaf)
8558 return true;
8559
8560 // We only write to the tree storage if the user clicks (or explicitly use SetNextTreeNode*** functions)
8561 ImGuiContext& g = *GImGui;
8562 ImGuiWindow* window = g.CurrentWindow;
8563 ImGuiStorage* storage = window->DC.StateStorage;
8564
8565 bool is_open;
8566 if (g.NextTreeNodeOpenCond != 0)
8567 {
8568 if (g.NextTreeNodeOpenCond & ImGuiCond_Always)
8569 {
8570 is_open = g.NextTreeNodeOpenVal;
8571 storage->SetInt(id, is_open);
8572 }
8573 else
8574 {
8575 // We treat ImGuiCond_Once and ImGuiCond_FirstUseEver the same because tree node state are not saved persistently.
8576 const int stored_value = storage->GetInt(id, -1);
8577 if (stored_value == -1)
8578 {
8579 is_open = g.NextTreeNodeOpenVal;
8580 storage->SetInt(id, is_open);
8581 }
8582 else
8583 {
8584 is_open = stored_value != 0;
8585 }
8586 }
8587 g.NextTreeNodeOpenCond = 0;
8588 }
8589 else
8590 {
8591 is_open = storage->GetInt(id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0;
8592 }
8593
8594 // When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior).
8595 // NB- If we are above max depth we still allow manually opened nodes to be logged.
8596 if (g.LogEnabled && !(flags & ImGuiTreeNodeFlags_NoAutoOpenOnLog) && window->DC.TreeDepth < g.LogAutoExpandMaxDepth)
8597 is_open = true;
8598
8599 return is_open;
8600 }
8601
8602 bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end)
8603 {
8604 ImGuiWindow* window = GetCurrentWindow();
8605 if (window->SkipItems)
8606 return false;
8607
8608 ImGuiContext& g = *GImGui;
8609 const ImGuiStyle& style = g.Style;
8610 const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0;
8611 const ImVec2 padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)) ? style.FramePadding : ImVec2(style.FramePadding.x, 0.0f);
8612
8613 if (!label_end)
8614 label_end = FindRenderedTextEnd(label);
8615 const ImVec2 label_size = CalcTextSize(label, label_end, false);
8616
8617 // We vertically grow up to current line height up the typical widget height.
8618 const float text_base_offset_y = ImMax(padding.y, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it
8619 const float frame_height = ImMax(ImMin(window->DC.CurrentLineHeight, g.FontSize + style.FramePadding.y*2), label_size.y + padding.y*2);
8620 ImRect frame_bb = ImRect(window->DC.CursorPos, ImVec2(window->Pos.x + GetContentRegionMax().x, window->DC.CursorPos.y + frame_height));
8621 if (display_frame)
8622 {
8623 // Framed header expand a little outside the default padding
8624 frame_bb.Min.x -= (float)(int)(window->WindowPadding.x*0.5f) - 1;
8625 frame_bb.Max.x += (float)(int)(window->WindowPadding.x*0.5f) - 1;
8626 }
8627
8628 const float text_offset_x = (g.FontSize + (display_frame ? padding.x*3 : padding.x*2)); // Collapser arrow width + Spacing
8629 const float text_width = g.FontSize + (label_size.x > 0.0f ? label_size.x + padding.x*2 : 0.0f); // Include collapser
8630 ItemSize(ImVec2(text_width, frame_height), text_base_offset_y);
8631
8632 // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing
8633 // (Ideally we'd want to add a flag for the user to specify if we want the hit test to be done up to the right side of the content or not)
8634 const ImRect interact_bb = display_frame ? frame_bb : ImRect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + text_width + style.ItemSpacing.x*2, frame_bb.Max.y);
8635 bool is_open = TreeNodeBehaviorIsOpen(id, flags);
8636
8637 // Store a flag for the current depth to tell if we will allow closing this node when navigating one of its child.
8638 // For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop().
8639 // This is currently only support 32 level deep and we are fine with (1 << Depth) overflowing into a zero.
8640 if (is_open && !g.NavIdIsAlive && (flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
8641 window->DC.TreeDepthMayJumpToParentOnPop |= (1 << window->DC.TreeDepth);
8642
8643 bool item_add = ItemAdd(interact_bb, id);
8644 window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HasDisplayRect;
8645 window->DC.LastItemDisplayRect = frame_bb;
8646
8647 if (!item_add)
8648 {
8649 if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
8650 TreePushRawID(id);
8651 return is_open;
8652 }
8653
8654 // Flags that affects opening behavior:
8655 // - 0(default) ..................... single-click anywhere to open
8656 // - OpenOnDoubleClick .............. double-click anywhere to open
8657 // - OpenOnArrow .................... single-click on arrow to open
8658 // - OpenOnDoubleClick|OpenOnArrow .. single-click on arrow or double-click anywhere to open
8659 ImGuiButtonFlags button_flags = ImGuiButtonFlags_NoKeyModifiers | ((flags & ImGuiTreeNodeFlags_AllowItemOverlap) ? ImGuiButtonFlags_AllowItemOverlap : 0);
8660 if (!(flags & ImGuiTreeNodeFlags_Leaf))
8661 button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
8662 if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)
8663 button_flags |= ImGuiButtonFlags_PressedOnDoubleClick | ((flags & ImGuiTreeNodeFlags_OpenOnArrow) ? ImGuiButtonFlags_PressedOnClickRelease : 0);
8664
8665 bool hovered, held, pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags);
8666 if (!(flags & ImGuiTreeNodeFlags_Leaf))
8667 {
8668 bool toggled = false;
8669 if (pressed)
8670 {
8671 toggled = !(flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) || (g.NavActivateId == id);
8672 if (flags & ImGuiTreeNodeFlags_OpenOnArrow)
8673 toggled |= IsMouseHoveringRect(interact_bb.Min, ImVec2(interact_bb.Min.x + text_offset_x, interact_bb.Max.y)) && (!g.NavDisableMouseHover);
8674 if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)
8675 toggled |= g.IO.MouseDoubleClicked[0];
8676 if (g.DragDropActive && is_open) // When using Drag and Drop "hold to open" we keep the node highlighted after opening, but never close it again.
8677 toggled = false;
8678 }
8679
8680 if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Left && is_open)
8681 {
8682 toggled = true;
8683 NavMoveRequestCancel();
8684 }
8685 if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right && !is_open) // If there's something upcoming on the line we may want to give it the priority?
8686 {
8687 toggled = true;
8688 NavMoveRequestCancel();
8689 }
8690
8691 if (toggled)
8692 {
8693 is_open = !is_open;
8694 window->DC.StateStorage->SetInt(id, is_open);
8695 }
8696 }
8697 if (flags & ImGuiTreeNodeFlags_AllowItemOverlap)
8698 SetItemAllowOverlap();
8699
8700 // Render
8701 const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
8702 const ImVec2 text_pos = frame_bb.Min + ImVec2(text_offset_x, text_base_offset_y);
8703 if (display_frame)
8704 {
8705 // Framed type
8706 RenderFrame(frame_bb.Min, frame_bb.Max, col, true, style.FrameRounding);
8707 RenderNavHighlight(frame_bb, id, ImGuiNavHighlightFlags_TypeThin);
8708 RenderArrow(frame_bb.Min + ImVec2(padding.x, text_base_offset_y), is_open ? ImGuiDir_Down : ImGuiDir_Right, 1.0f);
8709 if (g.LogEnabled)
8710 {
8711 // NB: '##' is normally used to hide text (as a library-wide feature), so we need to specify the text range to make sure the ## aren't stripped out here.
8712 const char log_prefix[] = "\n##";
8713 const char log_suffix[] = "##";
8714 LogRenderedText(&text_pos, log_prefix, log_prefix+3);
8715 RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size);
8716 LogRenderedText(&text_pos, log_suffix+1, log_suffix+3);
8717 }
8718 else
8719 {
8720 RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size);
8721 }
8722 }
8723 else
8724 {
8725 // Unframed typed for tree nodes
8726 if (hovered || (flags & ImGuiTreeNodeFlags_Selected))
8727 {
8728 RenderFrame(frame_bb.Min, frame_bb.Max, col, false);
8729 RenderNavHighlight(frame_bb, id, ImGuiNavHighlightFlags_TypeThin);
8730 }
8731
8732 if (flags & ImGuiTreeNodeFlags_Bullet)
8733 RenderBullet(frame_bb.Min + ImVec2(text_offset_x * 0.5f, g.FontSize*0.50f + text_base_offset_y));
8734 else if (!(flags & ImGuiTreeNodeFlags_Leaf))
8735 RenderArrow(frame_bb.Min + ImVec2(padding.x, g.FontSize*0.15f + text_base_offset_y), is_open ? ImGuiDir_Down : ImGuiDir_Right, 0.70f);
8736 if (g.LogEnabled)
8737 LogRenderedText(&text_pos, ">");
8738 RenderText(text_pos, label, label_end, false);
8739 }
8740
8741 if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
8742 TreePushRawID(id);
8743 return is_open;
8744 }
8745
8746 // CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag).
8747 // This is basically the same as calling TreeNodeEx(label, ImGuiTreeNodeFlags_CollapsingHeader). You can remove the _NoTreePushOnOpen flag if you want behavior closer to normal TreeNode().
8748 bool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags)
8749 {
8750 ImGuiWindow* window = GetCurrentWindow();
8751 if (window->SkipItems)
8752 return false;
8753
8754 return TreeNodeBehavior(window->GetID(label), flags | ImGuiTreeNodeFlags_CollapsingHeader, label);
8755 }
8756
8757 bool ImGui::CollapsingHeader(const char* label, bool* p_open, ImGuiTreeNodeFlags flags)
8758 {
8759 ImGuiWindow* window = GetCurrentWindow();
8760 if (window->SkipItems)
8761 return false;
8762
8763 if (p_open && !*p_open)
8764 return false;
8765
8766 ImGuiID id = window->GetID(label);
8767 bool is_open = TreeNodeBehavior(id, flags | ImGuiTreeNodeFlags_CollapsingHeader | (p_open ? ImGuiTreeNodeFlags_AllowItemOverlap : 0), label);
8768 if (p_open)
8769 {
8770 // Create a small overlapping close button // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc.
8771 ImGuiContext& g = *GImGui;
8772 float button_sz = g.FontSize * 0.5f;
8773 ImGuiItemHoveredDataBackup last_item_backup;
8774 if (CloseButton(window->GetID((void*)(intptr_t)(id+1)), ImVec2(ImMin(window->DC.LastItemRect.Max.x, window->ClipRect.Max.x) - g.Style.FramePadding.x - button_sz, window->DC.LastItemRect.Min.y + g.Style.FramePadding.y + button_sz), button_sz))
8775 *p_open = false;
8776 last_item_backup.Restore();
8777 }
8778
8779 return is_open;
8780 }
8781
8782 bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags)
8783 {
8784 ImGuiWindow* window = GetCurrentWindow();
8785 if (window->SkipItems)
8786 return false;
8787
8788 return TreeNodeBehavior(window->GetID(label), flags, label, NULL);
8789 }
8790
8791 bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
8792 {
8793 ImGuiWindow* window = GetCurrentWindow();
8794 if (window->SkipItems)
8795 return false;
8796
8797 ImGuiContext& g = *GImGui;
8798 const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
8799 return TreeNodeBehavior(window->GetID(str_id), flags, g.TempBuffer, label_end);
8800 }
8801
8802 bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
8803 {
8804 ImGuiWindow* window = GetCurrentWindow();
8805 if (window->SkipItems)
8806 return false;
8807
8808 ImGuiContext& g = *GImGui;
8809 const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
8810 return TreeNodeBehavior(window->GetID(ptr_id), flags, g.TempBuffer, label_end);
8811 }
8812
8813 bool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args)
8814 {
8815 return TreeNodeExV(str_id, 0, fmt, args);
8816 }
8817
8818 bool ImGui::TreeNodeV(const void* ptr_id, const char* fmt, va_list args)
8819 {
8820 return TreeNodeExV(ptr_id, 0, fmt, args);
8821 }
8822
8823 bool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
8824 {
8825 va_list args;
8826 va_start(args, fmt);
8827 bool is_open = TreeNodeExV(str_id, flags, fmt, args);
8828 va_end(args);
8829 return is_open;
8830 }
8831
8832 bool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
8833 {
8834 va_list args;
8835 va_start(args, fmt);
8836 bool is_open = TreeNodeExV(ptr_id, flags, fmt, args);
8837 va_end(args);
8838 return is_open;
8839 }
8840
8841 bool ImGui::TreeNode(const char* str_id, const char* fmt, ...)
8842 {
8843 va_list args;
8844 va_start(args, fmt);
8845 bool is_open = TreeNodeExV(str_id, 0, fmt, args);
8846 va_end(args);
8847 return is_open;
8848 }
8849
8850 bool ImGui::TreeNode(const void* ptr_id, const char* fmt, ...)
8851 {
8852 va_list args;
8853 va_start(args, fmt);
8854 bool is_open = TreeNodeExV(ptr_id, 0, fmt, args);
8855 va_end(args);
8856 return is_open;
8857 }
8858
8859 bool ImGui::TreeNode(const char* label)
8860 {
8861 ImGuiWindow* window = GetCurrentWindow();
8862 if (window->SkipItems)
8863 return false;
8864 return TreeNodeBehavior(window->GetID(label), 0, label, NULL);
8865 }
8866
8867 void ImGui::TreeAdvanceToLabelPos()
8868 {
8869 ImGuiContext& g = *GImGui;
8870 g.CurrentWindow->DC.CursorPos.x += GetTreeNodeToLabelSpacing();
8871 }
8872
8873 // Horizontal distance preceding label when using TreeNode() or Bullet()
8874 float ImGui::GetTreeNodeToLabelSpacing()
8875 {
8876 ImGuiContext& g = *GImGui;
8877 return g.FontSize + (g.Style.FramePadding.x * 2.0f);
8878 }
8879
8880 void ImGui::SetNextTreeNodeOpen(bool is_open, ImGuiCond cond)
8881 {
8882 ImGuiContext& g = *GImGui;
8883 if (g.CurrentWindow->SkipItems)
8884 return;
8885 g.NextTreeNodeOpenVal = is_open;
8886 g.NextTreeNodeOpenCond = cond ? cond : ImGuiCond_Always;
8887 }
8888
8889 void ImGui::PushID(const char* str_id)
8890 {
8891 ImGuiWindow* window = GetCurrentWindowRead();
8892 window->IDStack.push_back(window->GetID(str_id));
8893 }
8894
8895 void ImGui::PushID(const char* str_id_begin, const char* str_id_end)
8896 {
8897 ImGuiWindow* window = GetCurrentWindowRead();
8898 window->IDStack.push_back(window->GetID(str_id_begin, str_id_end));
8899 }
8900
8901 void ImGui::PushID(const void* ptr_id)
8902 {
8903 ImGuiWindow* window = GetCurrentWindowRead();
8904 window->IDStack.push_back(window->GetID(ptr_id));
8905 }
8906
8907 void ImGui::PushID(int int_id)
8908 {
8909 const void* ptr_id = (void*)(intptr_t)int_id;
8910 ImGuiWindow* window = GetCurrentWindowRead();
8911 window->IDStack.push_back(window->GetID(ptr_id));
8912 }
8913
8914 void ImGui::PopID()
8915 {
8916 ImGuiWindow* window = GetCurrentWindowRead();
8917 window->IDStack.pop_back();
8918 }
8919
8920 ImGuiID ImGui::GetID(const char* str_id)
8921 {
8922 return GImGui->CurrentWindow->GetID(str_id);
8923 }
8924
8925 ImGuiID ImGui::GetID(const char* str_id_begin, const char* str_id_end)
8926 {
8927 return GImGui->CurrentWindow->GetID(str_id_begin, str_id_end);
8928 }
8929
8930 ImGuiID ImGui::GetID(const void* ptr_id)
8931 {
8932 return GImGui->CurrentWindow->GetID(ptr_id);
8933 }
8934
8935 void ImGui::Bullet()
8936 {
8937 ImGuiWindow* window = GetCurrentWindow();
8938 if (window->SkipItems)
8939 return;
8940
8941 ImGuiContext& g = *GImGui;
8942 const ImGuiStyle& style = g.Style;
8943 const float line_height = ImMax(ImMin(window->DC.CurrentLineHeight, g.FontSize + g.Style.FramePadding.y*2), g.FontSize);
8944 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height));
8945 ItemSize(bb);
8946 if (!ItemAdd(bb, 0))
8947 {
8948 SameLine(0, style.FramePadding.x*2);
8949 return;
8950 }
8951
8952 // Render and stay on same line
8953 RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f));
8954 SameLine(0, style.FramePadding.x*2);
8955 }
8956
8957 // Text with a little bullet aligned to the typical tree node.
8958 void ImGui::BulletTextV(const char* fmt, va_list args)
8959 {
8960 ImGuiWindow* window = GetCurrentWindow();
8961 if (window->SkipItems)
8962 return;
8963
8964 ImGuiContext& g = *GImGui;
8965 const ImGuiStyle& style = g.Style;
8966
8967 const char* text_begin = g.TempBuffer;
8968 const char* text_end = text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
8969 const ImVec2 label_size = CalcTextSize(text_begin, text_end, false);
8970 const float text_base_offset_y = ImMax(0.0f, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it
8971 const float line_height = ImMax(ImMin(window->DC.CurrentLineHeight, g.FontSize + g.Style.FramePadding.y*2), g.FontSize);
8972 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize + (label_size.x > 0.0f ? (label_size.x + style.FramePadding.x*2) : 0.0f), ImMax(line_height, label_size.y))); // Empty text doesn't add padding
8973 ItemSize(bb);
8974 if (!ItemAdd(bb, 0))
8975 return;
8976
8977 // Render
8978 RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize*0.5f, line_height*0.5f));
8979 RenderText(bb.Min+ImVec2(g.FontSize + style.FramePadding.x*2, text_base_offset_y), text_begin, text_end, false);
8980 }
8981
8982 void ImGui::BulletText(const char* fmt, ...)
8983 {
8984 va_list args;
8985 va_start(args, fmt);
8986 BulletTextV(fmt, args);
8987 va_end(args);
8988 }
8989
8990 static inline int DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* data_ptr, const char* format)
8991 {
8992 if (data_type == ImGuiDataType_S32 || data_type == ImGuiDataType_U32) // Signedness doesn't matter when pushing the argument
8993 return ImFormatString(buf, buf_size, format, *(const ImU32*)data_ptr);
8994 if (data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64) // Signedness doesn't matter when pushing the argument
8995 return ImFormatString(buf, buf_size, format, *(const ImU64*)data_ptr);
8996 if (data_type == ImGuiDataType_Float)
8997 return ImFormatString(buf, buf_size, format, *(const float*)data_ptr);
8998 if (data_type == ImGuiDataType_Double)
8999 return ImFormatString(buf, buf_size, format, *(const double*)data_ptr);
9000 IM_ASSERT(0);
9001 return 0;
9002 }
9003
9004 // FIXME: Adding support for clamping on boundaries of the data type would be nice.
9005 static void DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, void* arg1, const void* arg2)
9006 {
9007 IM_ASSERT(op == '+' || op == '-');
9008 switch (data_type)
9009 {
9010 case ImGuiDataType_S32:
9011 if (op == '+') *(int*)output = *(const int*)arg1 + *(const int*)arg2;
9012 else if (op == '-') *(int*)output = *(const int*)arg1 - *(const int*)arg2;
9013 return;
9014 case ImGuiDataType_U32:
9015 if (op == '+') *(unsigned int*)output = *(const unsigned int*)arg1 + *(const ImU32*)arg2;
9016 else if (op == '-') *(unsigned int*)output = *(const unsigned int*)arg1 - *(const ImU32*)arg2;
9017 return;
9018 case ImGuiDataType_S64:
9019 if (op == '+') *(ImS64*)output = *(const ImS64*)arg1 + *(const ImS64*)arg2;
9020 else if (op == '-') *(ImS64*)output = *(const ImS64*)arg1 - *(const ImS64*)arg2;
9021 return;
9022 case ImGuiDataType_U64:
9023 if (op == '+') *(ImU64*)output = *(const ImU64*)arg1 + *(const ImU64*)arg2;
9024 else if (op == '-') *(ImU64*)output = *(const ImU64*)arg1 - *(const ImU64*)arg2;
9025 return;
9026 case ImGuiDataType_Float:
9027 if (op == '+') *(float*)output = *(const float*)arg1 + *(const float*)arg2;
9028 else if (op == '-') *(float*)output = *(const float*)arg1 - *(const float*)arg2;
9029 return;
9030 case ImGuiDataType_Double:
9031 if (op == '+') *(double*)output = *(const double*)arg1 + *(const double*)arg2;
9032 else if (op == '-') *(double*)output = *(const double*)arg1 - *(const double*)arg2;
9033 return;
9034 case ImGuiDataType_COUNT: break;
9035 }
9036 IM_ASSERT(0);
9037 }
9038
9039 struct ImGuiDataTypeInfo
9040 {
9041 size_t Size;
9042 const char* PrintFmt; // Unused
9043 const char* ScanFmt;
9044 };
9045
9046 static const ImGuiDataTypeInfo GDataTypeInfo[] =
9047 {
9048 { sizeof(int), "%d", "%d" },
9049 { sizeof(unsigned int), "%u", "%u" },
9050 #ifdef _MSC_VER
9051 { sizeof(ImS64), "%I64d","%I64d" },
9052 { sizeof(ImU64), "%I64u","%I64u" },
9053 #else
9054 { sizeof(ImS64), "%lld", "%lld" },
9055 { sizeof(ImU64), "%llu", "%llu" },
9056 #endif
9057 { sizeof(float), "%f", "%f" }, // float are promoted to double in va_arg
9058 { sizeof(double), "%f", "%lf" },
9059 };
9060 IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT);
9061
9062 // User can input math operators (e.g. +100) to edit a numerical values.
9063 // NB: This is _not_ a full expression evaluator. We should probably add one and replace this dumb mess..
9064 static bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* format)
9065 {
9066 while (ImCharIsBlankA(*buf))
9067 buf++;
9068
9069 // We don't support '-' op because it would conflict with inputing negative value.
9070 // Instead you can use +-100 to subtract from an existing value
9071 char op = buf[0];
9072 if (op == '+' || op == '*' || op == '/')
9073 {
9074 buf++;
9075 while (ImCharIsBlankA(*buf))
9076 buf++;
9077 }
9078 else
9079 {
9080 op = 0;
9081 }
9082 if (!buf[0])
9083 return false;
9084
9085 // Copy the value in an opaque buffer so we can compare at the end of the function if it changed at all.
9086 IM_ASSERT(data_type < ImGuiDataType_COUNT);
9087 int data_backup[2];
9088 IM_ASSERT(GDataTypeInfo[data_type].Size <= sizeof(data_backup));
9089 memcpy(data_backup, data_ptr, GDataTypeInfo[data_type].Size);
9090
9091 if (format == NULL)
9092 format = GDataTypeInfo[data_type].ScanFmt;
9093
9094 int arg1i = 0;
9095 if (data_type == ImGuiDataType_S32)
9096 {
9097 int* v = (int*)data_ptr;
9098 int arg0i = *v;
9099 float arg1f = 0.0f;
9100 if (op && sscanf(initial_value_buf, format, &arg0i) < 1)
9101 return false;
9102 // Store operand in a float so we can use fractional value for multipliers (*1.1), but constant always parsed as integer so we can fit big integers (e.g. 2000000003) past float precision
9103 if (op == '+') { if (sscanf(buf, "%d", &arg1i)) *v = (int)(arg0i + arg1i); } // Add (use "+-" to subtract)
9104 else if (op == '*') { if (sscanf(buf, "%f", &arg1f)) *v = (int)(arg0i * arg1f); } // Multiply
9105 else if (op == '/') { if (sscanf(buf, "%f", &arg1f) && arg1f != 0.0f) *v = (int)(arg0i / arg1f); } // Divide
9106 else { if (sscanf(buf, format, &arg1i) == 1) *v = arg1i; } // Assign constant
9107 }
9108 else if (data_type == ImGuiDataType_U32 || data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64)
9109 {
9110 // Assign constant
9111 // FIXME: We don't bother handling support for legacy operators since they are a little too crappy. Instead we may implement a proper expression evaluator in the future.
9112 sscanf(buf, format, data_ptr);
9113 }
9114 else if (data_type == ImGuiDataType_Float)
9115 {
9116 // For floats we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in
9117 format = "%f";
9118 float* v = (float*)data_ptr;
9119 float arg0f = *v, arg1f = 0.0f;
9120 if (op && sscanf(initial_value_buf, format, &arg0f) < 1)
9121 return false;
9122 if (sscanf(buf, format, &arg1f) < 1)
9123 return false;
9124 if (op == '+') { *v = arg0f + arg1f; } // Add (use "+-" to subtract)
9125 else if (op == '*') { *v = arg0f * arg1f; } // Multiply
9126 else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide
9127 else { *v = arg1f; } // Assign constant
9128 }
9129 else if (data_type == ImGuiDataType_Double)
9130 {
9131 format = "%lf"; // scanf differentiate float/double unlike printf which forces everything to double because of ellipsis
9132 double* v = (double*)data_ptr;
9133 double arg0f = *v, arg1f = 0.0;
9134 if (op && sscanf(initial_value_buf, format, &arg0f) < 1)
9135 return false;
9136 if (sscanf(buf, format, &arg1f) < 1)
9137 return false;
9138 if (op == '+') { *v = arg0f + arg1f; } // Add (use "+-" to subtract)
9139 else if (op == '*') { *v = arg0f * arg1f; } // Multiply
9140 else if (op == '/') { if (arg1f != 0.0f) *v = arg0f / arg1f; } // Divide
9141 else { *v = arg1f; } // Assign constant
9142 }
9143 return memcmp(data_backup, data_ptr, GDataTypeInfo[data_type].Size) != 0;
9144 }
9145
9146 // Create text input in place of a slider (when CTRL+Clicking on slider)
9147 // FIXME: Logic is messy and confusing.
9148 bool ImGui::InputScalarAsWidgetReplacement(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* data_ptr, const char* format)
9149 {
9150 ImGuiContext& g = *GImGui;
9151 ImGuiWindow* window = GetCurrentWindow();
9152
9153 // Our replacement widget will override the focus ID (registered previously to allow for a TAB focus to happen)
9154 // On the first frame, g.ScalarAsInputTextId == 0, then on subsequent frames it becomes == id
9155 SetActiveID(g.ScalarAsInputTextId, window);
9156 g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
9157 SetHoveredID(0);
9158 FocusableItemUnregister(window);
9159
9160 char fmt_buf[32];
9161 char data_buf[32];
9162 format = ImParseFormatTrimDecorations(format, fmt_buf, IM_ARRAYSIZE(fmt_buf));
9163 DataTypeFormatString(data_buf, IM_ARRAYSIZE(data_buf), data_type, data_ptr, format);
9164 ImStrTrimBlanks(data_buf);
9165 ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | ((data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) ? ImGuiInputTextFlags_CharsScientific : ImGuiInputTextFlags_CharsDecimal);
9166 bool value_changed = InputTextEx(label, data_buf, IM_ARRAYSIZE(data_buf), bb.GetSize(), flags);
9167 if (g.ScalarAsInputTextId == 0) // First frame we started displaying the InputText widget
9168 {
9169 IM_ASSERT(g.ActiveId == id); // InputText ID expected to match the Slider ID
9170 g.ScalarAsInputTextId = g.ActiveId;
9171 SetHoveredID(id);
9172 }
9173 if (value_changed)
9174 return DataTypeApplyOpFromText(data_buf, g.InputTextState.InitialText.begin(), data_type, data_ptr, NULL);
9175 return false;
9176 }
9177
9178 // We don't use strchr() because our strings are usually very short and often start with '%'
9179 const char* ImParseFormatFindStart(const char* fmt)
9180 {
9181 while (char c = fmt[0])
9182 {
9183 if (c == '%' && fmt[1] != '%')
9184 return fmt;
9185 else if (c == '%')
9186 fmt++;
9187 fmt++;
9188 }
9189 return fmt;
9190 }
9191
9192 const char* ImParseFormatFindEnd(const char* fmt)
9193 {
9194 // Printf/scanf types modifiers: I/L/h/j/l/t/w/z. Other uppercase letters qualify as types aka end of the format.
9195 if (fmt[0] != '%')
9196 return fmt;
9197 const unsigned int ignored_uppercase_mask = (1 << ('I'-'A')) | (1 << ('L'-'A'));
9198 const unsigned int ignored_lowercase_mask = (1 << ('h'-'a')) | (1 << ('j'-'a')) | (1 << ('l'-'a')) | (1 << ('t'-'a')) | (1 << ('w'-'a')) | (1 << ('z'-'a'));
9199 for (char c; (c = *fmt) != 0; fmt++)
9200 {
9201 if (c >= 'A' && c <= 'Z' && ((1 << (c - 'A')) & ignored_uppercase_mask) == 0)
9202 return fmt + 1;
9203 if (c >= 'a' && c <= 'z' && ((1 << (c - 'a')) & ignored_lowercase_mask) == 0)
9204 return fmt + 1;
9205 }
9206 return fmt;
9207 }
9208
9209 // Extract the format out of a format string with leading or trailing decorations
9210 // fmt = "blah blah" -> return fmt
9211 // fmt = "%.3f" -> return fmt
9212 // fmt = "hello %.3f" -> return fmt + 6
9213 // fmt = "%.3f hello" -> return buf written with "%.3f"
9214 const char* ImParseFormatTrimDecorations(const char* fmt, char* buf, int buf_size)
9215 {
9216 const char* fmt_start = ImParseFormatFindStart(fmt);
9217 if (fmt_start[0] != '%')
9218 return fmt;
9219 const char* fmt_end = ImParseFormatFindEnd(fmt_start);
9220 if (fmt_end[0] == 0) // If we only have leading decoration, we don't need to copy the data.
9221 return fmt_start;
9222 ImStrncpy(buf, fmt_start, ImMin((int)(fmt_end + 1 - fmt_start), buf_size));
9223 return buf;
9224 }
9225
9226 // Parse display precision back from the display format string
9227 // FIXME: This is still used by some navigation code path to infer a minimum tweak step, but we should aim to rework widgets so it isn't needed.
9228 int ImParseFormatPrecision(const char* fmt, int default_precision)
9229 {
9230 fmt = ImParseFormatFindStart(fmt);
9231 if (fmt[0] != '%')
9232 return default_precision;
9233 fmt++;
9234 while (*fmt >= '0' && *fmt <= '9')
9235 fmt++;
9236 int precision = INT_MAX;
9237 if (*fmt == '.')
9238 {
9239 fmt = ImAtoi<int>(fmt + 1, &precision);
9240 if (precision < 0 || precision > 99)
9241 precision = default_precision;
9242 }
9243 if (*fmt == 'e' || *fmt == 'E') // Maximum precision with scientific notation
9244 precision = -1;
9245 if ((*fmt == 'g' || *fmt == 'G') && precision == INT_MAX)
9246 precision = -1;
9247 return (precision == INT_MAX) ? default_precision : precision;
9248 }
9249
9250 static float GetMinimumStepAtDecimalPrecision(int decimal_precision)
9251 {
9252 static const float min_steps[10] = { 1.0f, 0.1f, 0.01f, 0.001f, 0.0001f, 0.00001f, 0.000001f, 0.0000001f, 0.00000001f, 0.000000001f };
9253 if (decimal_precision < 0)
9254 return FLT_MIN;
9255 return (decimal_precision >= 0 && decimal_precision < 10) ? min_steps[decimal_precision] : ImPow(10.0f, (float)-decimal_precision);
9256 }
9257
9258 template<typename TYPE, typename SIGNEDTYPE>
9259 static inline TYPE RoundScalarWithFormat(const char* format, ImGuiDataType data_type, TYPE v)
9260 {
9261 const char* fmt_start = ImParseFormatFindStart(format);
9262 if (fmt_start[0] != '%' || fmt_start[1] == '%') // Don't apply if the value is not visible in the format string
9263 return v;
9264 char v_str[64];
9265 ImFormatString(v_str, IM_ARRAYSIZE(v_str), fmt_start, v);
9266 const char* p = v_str;
9267 while (*p == ' ')
9268 p++;
9269 if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
9270 v = (TYPE)ImAtof(p);
9271 else
9272 ImAtoi(p, (SIGNEDTYPE*)&v);
9273 return v;
9274 }
9275
9276 template<typename TYPE, typename FLOATTYPE>
9277 static inline float SliderBehaviorCalcRatioFromValue(ImGuiDataType data_type, TYPE v, TYPE v_min, TYPE v_max, float power, float linear_zero_pos)
9278 {
9279 if (v_min == v_max)
9280 return 0.0f;
9281
9282 const bool is_power = (power != 1.0f) && (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double);
9283 const TYPE v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min);
9284 if (is_power)
9285 {
9286 if (v_clamped < 0.0f)
9287 {
9288 const float f = 1.0f - (float)((v_clamped - v_min) / (ImMin((TYPE)0, v_max) - v_min));
9289 return (1.0f - ImPow(f, 1.0f/power)) * linear_zero_pos;
9290 }
9291 else
9292 {
9293 const float f = (float)((v_clamped - ImMax((TYPE)0, v_min)) / (v_max - ImMax((TYPE)0, v_min)));
9294 return linear_zero_pos + ImPow(f, 1.0f/power) * (1.0f - linear_zero_pos);
9295 }
9296 }
9297
9298 // Linear slider
9299 return (float)((FLOATTYPE)(v_clamped - v_min) / (FLOATTYPE)(v_max - v_min));
9300 }
9301
9302 // FIXME: Move some of the code into SliderBehavior(). Current responsability is larger than what the equivalent DragBehaviorT<> does, we also do some rendering, etc.
9303 template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
9304 static bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, TYPE* v, const TYPE v_min, const TYPE v_max, const char* format, float power, ImGuiSliderFlags flags)
9305 {
9306 ImGuiContext& g = *GImGui;
9307 ImGuiWindow* window = g.CurrentWindow;
9308 const ImGuiStyle& style = g.Style;
9309
9310 const bool is_horizontal = (flags & ImGuiSliderFlags_Vertical) == 0;
9311 const bool is_decimal = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
9312 const bool is_power = (power != 1.0f) && is_decimal;
9313
9314 const float grab_padding = 2.0f;
9315 const float slider_sz = is_horizontal ? (bb.GetWidth() - grab_padding * 2.0f) : (bb.GetHeight() - grab_padding * 2.0f);
9316 float grab_sz = style.GrabMinSize;
9317 SIGNEDTYPE v_range = (v_min < v_max ? v_max - v_min : v_min - v_max);
9318 if (!is_decimal && v_range >= 0) // v_range < 0 may happen on integer overflows
9319 grab_sz = ImMax((float)(slider_sz / (v_range + 1)), style.GrabMinSize); // For integer sliders: if possible have the grab size represent 1 unit
9320 grab_sz = ImMin(grab_sz, slider_sz);
9321 const float slider_usable_sz = slider_sz - grab_sz;
9322 const float slider_usable_pos_min = (is_horizontal ? bb.Min.x : bb.Min.y) + grab_padding + grab_sz*0.5f;
9323 const float slider_usable_pos_max = (is_horizontal ? bb.Max.x : bb.Max.y) - grab_padding - grab_sz*0.5f;
9324
9325 // For power curve sliders that cross over sign boundary we want the curve to be symmetric around 0.0f
9326 float linear_zero_pos; // 0.0->1.0f
9327 if (is_power && v_min * v_max < 0.0f)
9328 {
9329 // Different sign
9330 const FLOATTYPE linear_dist_min_to_0 = ImPow(v_min >= 0 ? (FLOATTYPE)v_min : -(FLOATTYPE)v_min, (FLOATTYPE)1.0f/power);
9331 const FLOATTYPE linear_dist_max_to_0 = ImPow(v_max >= 0 ? (FLOATTYPE)v_max : -(FLOATTYPE)v_max, (FLOATTYPE)1.0f/power);
9332 linear_zero_pos = (float)(linear_dist_min_to_0 / (linear_dist_min_to_0 + linear_dist_max_to_0));
9333 }
9334 else
9335 {
9336 // Same sign
9337 linear_zero_pos = v_min < 0.0f ? 1.0f : 0.0f;
9338 }
9339
9340 // Process interacting with the slider
9341 bool value_changed = false;
9342 if (g.ActiveId == id)
9343 {
9344 bool set_new_value = false;
9345 float clicked_t = 0.0f;
9346 if (g.ActiveIdSource == ImGuiInputSource_Mouse)
9347 {
9348 if (!g.IO.MouseDown[0])
9349 {
9350 ClearActiveID();
9351 }
9352 else
9353 {
9354 const float mouse_abs_pos = is_horizontal ? g.IO.MousePos.x : g.IO.MousePos.y;
9355 clicked_t = (slider_usable_sz > 0.0f) ? ImClamp((mouse_abs_pos - slider_usable_pos_min) / slider_usable_sz, 0.0f, 1.0f) : 0.0f;
9356 if (!is_horizontal)
9357 clicked_t = 1.0f - clicked_t;
9358 set_new_value = true;
9359 }
9360 }
9361 else if (g.ActiveIdSource == ImGuiInputSource_Nav)
9362 {
9363 const ImVec2 delta2 = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 0.0f, 0.0f);
9364 float delta = is_horizontal ? delta2.x : -delta2.y;
9365 if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
9366 {
9367 ClearActiveID();
9368 }
9369 else if (delta != 0.0f)
9370 {
9371 clicked_t = SliderBehaviorCalcRatioFromValue<TYPE,FLOATTYPE>(data_type, *v, v_min, v_max, power, linear_zero_pos);
9372 const int decimal_precision = is_decimal ? ImParseFormatPrecision(format, 3) : 0;
9373 if ((decimal_precision > 0) || is_power)
9374 {
9375 delta /= 100.0f; // Gamepad/keyboard tweak speeds in % of slider bounds
9376 if (IsNavInputDown(ImGuiNavInput_TweakSlow))
9377 delta /= 10.0f;
9378 }
9379 else
9380 {
9381 if ((v_range >= -100.0f && v_range <= 100.0f) || IsNavInputDown(ImGuiNavInput_TweakSlow))
9382 delta = ((delta < 0.0f) ? -1.0f : +1.0f) / (float)v_range; // Gamepad/keyboard tweak speeds in integer steps
9383 else
9384 delta /= 100.0f;
9385 }
9386 if (IsNavInputDown(ImGuiNavInput_TweakFast))
9387 delta *= 10.0f;
9388 set_new_value = true;
9389 if ((clicked_t >= 1.0f && delta > 0.0f) || (clicked_t <= 0.0f && delta < 0.0f)) // This is to avoid applying the saturation when already past the limits
9390 set_new_value = false;
9391 else
9392 clicked_t = ImSaturate(clicked_t + delta);
9393 }
9394 }
9395
9396 if (set_new_value)
9397 {
9398 TYPE v_new;
9399 if (is_power)
9400 {
9401 // Account for power curve scale on both sides of the zero
9402 if (clicked_t < linear_zero_pos)
9403 {
9404 // Negative: rescale to the negative range before powering
9405 float a = 1.0f - (clicked_t / linear_zero_pos);
9406 a = ImPow(a, power);
9407 v_new = ImLerp(ImMin(v_max, (TYPE)0), v_min, a);
9408 }
9409 else
9410 {
9411 // Positive: rescale to the positive range before powering
9412 float a;
9413 if (ImFabs(linear_zero_pos - 1.0f) > 1.e-6f)
9414 a = (clicked_t - linear_zero_pos) / (1.0f - linear_zero_pos);
9415 else
9416 a = clicked_t;
9417 a = ImPow(a, power);
9418 v_new = ImLerp(ImMax(v_min, (TYPE)0), v_max, a);
9419 }
9420 }
9421 else
9422 {
9423 // Linear slider
9424 if (is_decimal)
9425 {
9426 v_new = ImLerp(v_min, v_max, clicked_t);
9427 }
9428 else
9429 {
9430 // For integer values we want the clicking position to match the grab box so we round above
9431 // This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property..
9432 FLOATTYPE v_new_off_f = (v_max - v_min) * clicked_t;
9433 TYPE v_new_off_floor = (TYPE)(v_new_off_f);
9434 TYPE v_new_off_round = (TYPE)(v_new_off_f + (FLOATTYPE)0.5);
9435 if (!is_decimal && v_new_off_floor < v_new_off_round)
9436 v_new = v_min + v_new_off_round;
9437 else
9438 v_new = v_min + v_new_off_floor;
9439 }
9440 }
9441
9442 // Round to user desired precision based on format string
9443 v_new = RoundScalarWithFormat<TYPE,SIGNEDTYPE>(format, data_type, v_new);
9444
9445 // Apply result
9446 if (*v != v_new)
9447 {
9448 *v = v_new;
9449 value_changed = true;
9450 }
9451 }
9452 }
9453
9454 // Draw
9455 float grab_t = SliderBehaviorCalcRatioFromValue<TYPE,FLOATTYPE>(data_type, *v, v_min, v_max, power, linear_zero_pos);
9456 if (!is_horizontal)
9457 grab_t = 1.0f - grab_t;
9458 const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t);
9459 ImRect grab_bb;
9460 if (is_horizontal)
9461 grab_bb = ImRect(grab_pos - grab_sz*0.5f, bb.Min.y + grab_padding, grab_pos + grab_sz*0.5f, bb.Max.y - grab_padding);
9462 else
9463 grab_bb = ImRect(bb.Min.x + grab_padding, grab_pos - grab_sz*0.5f, bb.Max.x - grab_padding, grab_pos + grab_sz*0.5f);
9464 window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);
9465
9466 return value_changed;
9467 }
9468
9469 // For 32-bits and larger types, slider bounds are limited to half the natural type range.
9470 // So e.g. an integer Slider between INT_MAX-10 and INT_MAX will fail, but an integer Slider between INT_MAX/2-10 and INT_MAX/2 will be ok.
9471 // It would be possible to lift that limitation with some work but it doesn't seem to be worth it for sliders.
9472 bool ImGui::SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format, float power, ImGuiSliderFlags flags)
9473 {
9474 // Draw frame
9475 ImGuiContext& g = *GImGui;
9476 const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
9477 RenderNavHighlight(bb, id);
9478 RenderFrame(bb.Min, bb.Max, frame_col, true, g.Style.FrameRounding);
9479
9480 switch (data_type)
9481 {
9482 case ImGuiDataType_S32:
9483 IM_ASSERT(*(const ImS32*)v_min >= IM_S32_MIN/2 && *(const ImS32*)v_max <= IM_S32_MAX/2);
9484 return SliderBehaviorT<ImS32, ImS32, float >(bb, id, data_type, (ImS32*)v, *(const ImS32*)v_min, *(const ImS32*)v_max, format, power, flags);
9485 case ImGuiDataType_U32:
9486 IM_ASSERT(*(const ImU32*)v_min <= IM_U32_MAX/2);
9487 return SliderBehaviorT<ImU32, ImS32, float >(bb, id, data_type, (ImU32*)v, *(const ImU32*)v_min, *(const ImU32*)v_max, format, power, flags);
9488 case ImGuiDataType_S64:
9489 IM_ASSERT(*(const ImS64*)v_min >= IM_S64_MIN/2 && *(const ImS64*)v_max <= IM_S64_MAX/2);
9490 return SliderBehaviorT<ImS64, ImS64, double>(bb, id, data_type, (ImS64*)v, *(const ImS64*)v_min, *(const ImS64*)v_max, format, power, flags);
9491 case ImGuiDataType_U64:
9492 IM_ASSERT(*(const ImU64*)v_min <= IM_U64_MAX/2);
9493 return SliderBehaviorT<ImU64, ImS64, double>(bb, id, data_type, (ImU64*)v, *(const ImU64*)v_min, *(const ImU64*)v_max, format, power, flags);
9494 case ImGuiDataType_Float:
9495 IM_ASSERT(*(const float*)v_min >= -FLT_MAX/2.0f && *(const float*)v_max <= FLT_MAX/2.0f);
9496 return SliderBehaviorT<float, float, float >(bb, id, data_type, (float*)v, *(const float*)v_min, *(const float*)v_max, format, power, flags);
9497 case ImGuiDataType_Double:
9498 IM_ASSERT(*(const double*)v_min >= -DBL_MAX/2.0f && *(const double*)v_max <= DBL_MAX/2.0f);
9499 return SliderBehaviorT<double,double,double>(bb, id, data_type, (double*)v, *(const double*)v_min, *(const double*)v_max, format, power, flags);
9500 case ImGuiDataType_COUNT: break;
9501 }
9502 IM_ASSERT(0);
9503 return false;
9504 }
9505
9506 // FIXME-LEGACY: Prior to 1.61 our DragInt() function internally used floats and because of this the compile-time default value for format was "%.0f".
9507 // Even though we changed the compile-time default, we expect users to have carried %f around, which would break the display of DragInt() calls.
9508 // To honor backward compatibility we are rewriting the format string, unless IMGUI_DISABLE_OBSOLETE_FUNCTIONS is enabled. What could possibly go wrong?!
9509 static const char* PatchFormatStringFloatToInt(const char* fmt)
9510 {
9511 if (fmt[0] == '%' && fmt[1] == '.' && fmt[2] == '0' && fmt[3] == 'f' && fmt[4] == 0) // Fast legacy path for "%.0f" which is expected to be the most common case.
9512 return "%d";
9513 const char* fmt_start = ImParseFormatFindStart(fmt); // Find % (if any, and ignore %%)
9514 const char* fmt_end = ImParseFormatFindEnd(fmt_start); // Find end of format specifier, which itself is an exercise of confidence/recklessness (because snprintf is dependent on libc or user).
9515 if (fmt_end > fmt_start && fmt_end[-1] == 'f')
9516 {
9517 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
9518 if (fmt_start == fmt && fmt_end[0] == 0)
9519 return "%d";
9520 ImGuiContext& g = *GImGui;
9521 ImFormatString(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), "%.*s%%d%s", (int)(fmt_start - fmt), fmt, fmt_end); // Honor leading and trailing decorations, but lose alignment/precision.
9522 return g.TempBuffer;
9523 #else
9524 IM_ASSERT(0 && "DragInt(): Invalid format string!"); // Old versions used a default parameter of "%.0f", please replace with e.g. "%d"
9525 #endif
9526 }
9527 return fmt;
9528 }
9529
9530 bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format, float power)
9531 {
9532 ImGuiWindow* window = GetCurrentWindow();
9533 if (window->SkipItems)
9534 return false;
9535
9536 ImGuiContext& g = *GImGui;
9537 const ImGuiStyle& style = g.Style;
9538 const ImGuiID id = window->GetID(label);
9539 const float w = CalcItemWidth();
9540
9541 const ImVec2 label_size = CalcTextSize(label, NULL, true);
9542 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f));
9543 const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
9544
9545 // NB- we don't call ItemSize() yet because we may turn into a text edit box below
9546 if (!ItemAdd(total_bb, id, &frame_bb))
9547 {
9548 ItemSize(total_bb, style.FramePadding.y);
9549 return false;
9550 }
9551
9552 // Default format string when passing NULL
9553 // Patch old "%.0f" format string to use "%d", read function comments for more details.
9554 IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
9555 if (format == NULL)
9556 format = GDataTypeInfo[data_type].PrintFmt;
9557 else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0)
9558 format = PatchFormatStringFloatToInt(format);
9559
9560 // Tabbing or CTRL-clicking on Slider turns it into an input box
9561 bool start_text_input = false;
9562 const bool tab_focus_requested = FocusableItemRegister(window, id);
9563 const bool hovered = ItemHoverable(frame_bb, id);
9564 if (tab_focus_requested || (hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || (g.NavInputId == id && g.ScalarAsInputTextId != id))
9565 {
9566 SetActiveID(id, window);
9567 SetFocusID(id, window);
9568 FocusWindow(window);
9569 g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
9570 if (tab_focus_requested || g.IO.KeyCtrl || g.NavInputId == id)
9571 {
9572 start_text_input = true;
9573 g.ScalarAsInputTextId = 0;
9574 }
9575 }
9576 if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id))
9577 return InputScalarAsWidgetReplacement(frame_bb, id, label, data_type, v, format);
9578
9579 // Actual slider behavior + render grab
9580 ItemSize(total_bb, style.FramePadding.y);
9581 const bool value_changed = SliderBehavior(frame_bb, id, data_type, v, v_min, v_max, format, power);
9582 if (value_changed)
9583 MarkItemValueChanged(id);
9584
9585 // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
9586 char value_buf[64];
9587 const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, v, format);
9588 RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f,0.5f));
9589
9590 if (label_size.x > 0.0f)
9591 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
9592
9593 return value_changed;
9594 }
9595
9596 bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, float power)
9597 {
9598 return SliderScalar(label, ImGuiDataType_Float, v, &v_min, &v_max, format, power);
9599 }
9600
9601 bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* v, const void* v_min, const void* v_max, const char* format, float power)
9602 {
9603 ImGuiWindow* window = GetCurrentWindow();
9604 if (window->SkipItems)
9605 return false;
9606
9607 ImGuiContext& g = *GImGui;
9608 const ImGuiStyle& style = g.Style;
9609 const ImGuiID id = window->GetID(label);
9610
9611 const ImVec2 label_size = CalcTextSize(label, NULL, true);
9612 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
9613 const ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
9614
9615 ItemSize(bb, style.FramePadding.y);
9616 if (!ItemAdd(frame_bb, id))
9617 return false;
9618
9619 // Default format string when passing NULL
9620 // Patch old "%.0f" format string to use "%d", read function comments for more details.
9621 IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
9622 if (format == NULL)
9623 format = GDataTypeInfo[data_type].PrintFmt;
9624 else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0)
9625 format = PatchFormatStringFloatToInt(format);
9626
9627 const bool hovered = ItemHoverable(frame_bb, id);
9628 if ((hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavInputId == id)
9629 {
9630 SetActiveID(id, window);
9631 SetFocusID(id, window);
9632 FocusWindow(window);
9633 g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
9634 }
9635
9636 // Actual slider behavior + render grab
9637 const bool value_changed = SliderBehavior(frame_bb, id, data_type, v, v_min, v_max, format, power, ImGuiSliderFlags_Vertical);
9638 if (value_changed)
9639 MarkItemValueChanged(id);
9640
9641 // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
9642 // For the vertical slider we allow centered text to overlap the frame padding
9643 char value_buf[64];
9644 const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, v, format);
9645 RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f,0.0f));
9646 if (label_size.x > 0.0f)
9647 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
9648
9649 return value_changed;
9650 }
9651
9652 bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, float v_degrees_max)
9653 {
9654 float v_deg = (*v_rad) * 360.0f / (2*IM_PI);
9655 bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, "%.0f deg", 1.0f);
9656 *v_rad = v_deg * (2*IM_PI) / 360.0f;
9657 return value_changed;
9658 }
9659
9660 bool ImGui::SliderInt(const char* label, int* v, int v_min, int v_max, const char* format)
9661 {
9662 return SliderScalar(label, ImGuiDataType_S32, v, &v_min, &v_max, format);
9663 }
9664
9665 bool ImGui::VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* format, float power)
9666 {
9667 return VSliderScalar(label, size, ImGuiDataType_Float, v, &v_min, &v_max, format, power);
9668 }
9669
9670 bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format)
9671 {
9672 return VSliderScalar(label, size, ImGuiDataType_S32, v, &v_min, &v_max, format);
9673 }
9674
9675 // Add multiple sliders on 1 line for compact edition of multiple components
9676 bool ImGui::SliderScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* v_min, const void* v_max, const char* format, float power)
9677 {
9678 ImGuiWindow* window = GetCurrentWindow();
9679 if (window->SkipItems)
9680 return false;
9681
9682 ImGuiContext& g = *GImGui;
9683 bool value_changed = false;
9684 BeginGroup();
9685 PushID(label);
9686 PushMultiItemsWidths(components);
9687 size_t type_size = GDataTypeInfo[data_type].Size;
9688 for (int i = 0; i < components; i++)
9689 {
9690 PushID(i);
9691 value_changed |= SliderScalar("##v", data_type, v, v_min, v_max, format, power);
9692 SameLine(0, g.Style.ItemInnerSpacing.x);
9693 PopID();
9694 PopItemWidth();
9695 v = (void*)((char*)v + type_size);
9696 }
9697 PopID();
9698
9699 TextUnformatted(label, FindRenderedTextEnd(label));
9700 EndGroup();
9701 return value_changed;
9702 }
9703
9704 bool ImGui::SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* format, float power)
9705 {
9706 return SliderScalarN(label, ImGuiDataType_Float, v, 2, &v_min, &v_max, format, power);
9707 }
9708
9709 bool ImGui::SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* format, float power)
9710 {
9711 return SliderScalarN(label, ImGuiDataType_Float, v, 3, &v_min, &v_max, format, power);
9712 }
9713
9714 bool ImGui::SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* format, float power)
9715 {
9716 return SliderScalarN(label, ImGuiDataType_Float, v, 4, &v_min, &v_max, format, power);
9717 }
9718
9719 bool ImGui::SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* format)
9720 {
9721 return SliderScalarN(label, ImGuiDataType_S32, v, 2, &v_min, &v_max, format);
9722 }
9723
9724 bool ImGui::SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* format)
9725 {
9726 return SliderScalarN(label, ImGuiDataType_S32, v, 3, &v_min, &v_max, format);
9727 }
9728
9729 bool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* format)
9730 {
9731 return SliderScalarN(label, ImGuiDataType_S32, v, 4, &v_min, &v_max, format);
9732 }
9733
9734 // This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls)
9735 template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
9736 static bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const TYPE v_min, const TYPE v_max, const char* format, float power)
9737 {
9738 ImGuiContext& g = *GImGui;
9739
9740 // Default tweak speed
9741 bool has_min_max = (v_min != v_max) && (v_max - v_max < FLT_MAX);
9742 if (v_speed == 0.0f && has_min_max)
9743 v_speed = (float)((v_max - v_min) * g.DragSpeedDefaultRatio);
9744
9745 // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings
9746 float adjust_delta = 0.0f;
9747 if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() && g.IO.MouseDragMaxDistanceSqr[0] > 1.0f*1.0f)
9748 {
9749 adjust_delta = g.IO.MouseDelta.x;
9750 if (g.IO.KeyAlt)
9751 adjust_delta *= 1.0f/100.0f;
9752 if (g.IO.KeyShift)
9753 adjust_delta *= 10.0f;
9754 }
9755 else if (g.ActiveIdSource == ImGuiInputSource_Nav)
9756 {
9757 int decimal_precision = (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) ? ImParseFormatPrecision(format, 3) : 0;
9758 adjust_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard|ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 1.0f/10.0f, 10.0f).x;
9759 v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision));
9760 }
9761 adjust_delta *= v_speed;
9762
9763 // Clear current value on activation
9764 // Avoid altering values and clamping when we are _already_ past the limits and heading in the same direction, so e.g. if range is 0..255, current value is 300 and we are pushing to the right side, keep the 300.
9765 bool is_just_activated = g.ActiveIdIsJustActivated;
9766 bool is_already_past_limits_and_pushing_outward = has_min_max && ((*v >= v_max && adjust_delta > 0.0f) || (*v <= v_min && adjust_delta < 0.0f));
9767 if (is_just_activated || is_already_past_limits_and_pushing_outward)
9768 {
9769 g.DragCurrentAccum = 0.0f;
9770 g.DragCurrentAccumDirty = false;
9771 }
9772 else if (adjust_delta != 0.0f)
9773 {
9774 g.DragCurrentAccum += adjust_delta;
9775 g.DragCurrentAccumDirty = true;
9776 }
9777
9778 if (!g.DragCurrentAccumDirty)
9779 return false;
9780
9781 TYPE v_cur = *v;
9782 FLOATTYPE v_old_ref_for_accum_remainder = (FLOATTYPE)0.0f;
9783
9784 const bool is_power = (power != 1.0f && (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) && has_min_max);
9785 if (is_power)
9786 {
9787 // Offset + round to user desired precision, with a curve on the v_min..v_max range to get more precision on one side of the range
9788 FLOATTYPE v_old_norm_curved = ImPow((FLOATTYPE)(v_cur - v_min) / (FLOATTYPE)(v_max - v_min), (FLOATTYPE)1.0f / power);
9789 FLOATTYPE v_new_norm_curved = v_old_norm_curved + (g.DragCurrentAccum / (v_max - v_min));
9790 v_cur = v_min + (TYPE)ImPow(ImSaturate((float)v_new_norm_curved), power) * (v_max - v_min);
9791 v_old_ref_for_accum_remainder = v_old_norm_curved;
9792 }
9793 else
9794 {
9795 v_cur += (TYPE)g.DragCurrentAccum;
9796 }
9797
9798 // Round to user desired precision based on format string
9799 v_cur = RoundScalarWithFormat<TYPE, SIGNEDTYPE>(format, data_type, v_cur);
9800
9801 // Preserve remainder after rounding has been applied. This also allow slow tweaking of values.
9802 g.DragCurrentAccumDirty = false;
9803 if (is_power)
9804 {
9805 FLOATTYPE v_cur_norm_curved = ImPow((FLOATTYPE)(v_cur - v_min) / (FLOATTYPE)(v_max - v_min), (FLOATTYPE)1.0f / power);
9806 g.DragCurrentAccum -= (float)(v_cur_norm_curved - v_old_ref_for_accum_remainder);
9807 }
9808 else
9809 {
9810 g.DragCurrentAccum -= (float)((SIGNEDTYPE)v_cur - (SIGNEDTYPE)*v);
9811 }
9812
9813 // Lose zero sign for float/double
9814 if (v_cur == (TYPE)-0)
9815 v_cur = (TYPE)0;
9816
9817 // Clamp values (handle overflow/wrap-around)
9818 if (*v != v_cur && has_min_max)
9819 {
9820 if (v_cur < v_min || (v_cur > *v && adjust_delta < 0.0f))
9821 v_cur = v_min;
9822 if (v_cur > v_max || (v_cur < *v && adjust_delta > 0.0f))
9823 v_cur = v_max;
9824 }
9825
9826 // Apply result
9827 if (*v == v_cur)
9828 return false;
9829 *v = v_cur;
9830 return true;
9831 }
9832
9833 bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* v, float v_speed, const void* v_min, const void* v_max, const char* format, float power)
9834 {
9835 ImGuiContext& g = *GImGui;
9836 if (g.ActiveId == id)
9837 {
9838 if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0])
9839 ClearActiveID();
9840 else if (g.ActiveIdSource == ImGuiInputSource_Nav && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
9841 ClearActiveID();
9842 }
9843 if (g.ActiveId != id)
9844 return false;
9845
9846 switch (data_type)
9847 {
9848 case ImGuiDataType_S32: return DragBehaviorT<ImS32, ImS32, float >(data_type, (ImS32*)v, v_speed, v_min ? *(const ImS32* )v_min : IM_S32_MIN, v_max ? *(const ImS32* )v_max : IM_S32_MAX, format, power);
9849 case ImGuiDataType_U32: return DragBehaviorT<ImU32, ImS32, float >(data_type, (ImU32*)v, v_speed, v_min ? *(const ImU32* )v_min : IM_U32_MIN, v_max ? *(const ImU32* )v_max : IM_U32_MAX, format, power);
9850 case ImGuiDataType_S64: return DragBehaviorT<ImS64, ImS64, double>(data_type, (ImS64*)v, v_speed, v_min ? *(const ImS64* )v_min : IM_S64_MIN, v_max ? *(const ImS64* )v_max : IM_S64_MAX, format, power);
9851 case ImGuiDataType_U64: return DragBehaviorT<ImU64, ImS64, double>(data_type, (ImU64*)v, v_speed, v_min ? *(const ImU64* )v_min : IM_U64_MIN, v_max ? *(const ImU64* )v_max : IM_U64_MAX, format, power);
9852 case ImGuiDataType_Float: return DragBehaviorT<float, float, float >(data_type, (float*)v, v_speed, v_min ? *(const float* )v_min : -FLT_MAX, v_max ? *(const float* )v_max : FLT_MAX, format, power);
9853 case ImGuiDataType_Double: return DragBehaviorT<double,double,double>(data_type, (double*)v, v_speed, v_min ? *(const double*)v_min : -DBL_MAX, v_max ? *(const double*)v_max : DBL_MAX, format, power);
9854 case ImGuiDataType_COUNT: break;
9855 }
9856 IM_ASSERT(0);
9857 return false;
9858 }
9859
9860 bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* v, float v_speed, const void* v_min, const void* v_max, const char* format, float power)
9861 {
9862 ImGuiWindow* window = GetCurrentWindow();
9863 if (window->SkipItems)
9864 return false;
9865
9866 if (power != 1.0f)
9867 IM_ASSERT(v_min != NULL && v_max != NULL); // When using a power curve the drag needs to have known bounds
9868
9869 ImGuiContext& g = *GImGui;
9870 const ImGuiStyle& style = g.Style;
9871 const ImGuiID id = window->GetID(label);
9872 const float w = CalcItemWidth();
9873
9874 const ImVec2 label_size = CalcTextSize(label, NULL, true);
9875 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f));
9876 const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
9877 const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
9878
9879 // NB- we don't call ItemSize() yet because we may turn into a text edit box below
9880 if (!ItemAdd(total_bb, id, &frame_bb))
9881 {
9882 ItemSize(total_bb, style.FramePadding.y);
9883 return false;
9884 }
9885 const bool hovered = ItemHoverable(frame_bb, id);
9886
9887 // Default format string when passing NULL
9888 // Patch old "%.0f" format string to use "%d", read function comments for more details.
9889 IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
9890 if (format == NULL)
9891 format = GDataTypeInfo[data_type].PrintFmt;
9892 else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0)
9893 format = PatchFormatStringFloatToInt(format);
9894
9895 // Tabbing or CTRL-clicking on Drag turns it into an input box
9896 bool start_text_input = false;
9897 const bool tab_focus_requested = FocusableItemRegister(window, id);
9898 if (tab_focus_requested || (hovered && (g.IO.MouseClicked[0] || g.IO.MouseDoubleClicked[0])) || g.NavActivateId == id || (g.NavInputId == id && g.ScalarAsInputTextId != id))
9899 {
9900 SetActiveID(id, window);
9901 SetFocusID(id, window);
9902 FocusWindow(window);
9903 g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
9904 if (tab_focus_requested || g.IO.KeyCtrl || g.IO.MouseDoubleClicked[0] || g.NavInputId == id)
9905 {
9906 start_text_input = true;
9907 g.ScalarAsInputTextId = 0;
9908 }
9909 }
9910 if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id))
9911 return InputScalarAsWidgetReplacement(frame_bb, id, label, data_type, v, format);
9912
9913 // Actual drag behavior
9914 ItemSize(total_bb, style.FramePadding.y);
9915 const bool value_changed = DragBehavior(id, data_type, v, v_speed, v_min, v_max, format, power);
9916 if (value_changed)
9917 MarkItemValueChanged(id);
9918
9919 // Draw frame
9920 const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
9921 RenderNavHighlight(frame_bb, id);
9922 RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding);
9923
9924 // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
9925 char value_buf[64];
9926 const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, v, format);
9927 RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
9928
9929 if (label_size.x > 0.0f)
9930 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label);
9931
9932 return value_changed;
9933 }
9934
9935 bool ImGui::DragScalarN(const char* label, ImGuiDataType data_type, void* v, int components, float v_speed, const void* v_min, const void* v_max, const char* format, float power)
9936 {
9937 ImGuiWindow* window = GetCurrentWindow();
9938 if (window->SkipItems)
9939 return false;
9940
9941 ImGuiContext& g = *GImGui;
9942 bool value_changed = false;
9943 BeginGroup();
9944 PushID(label);
9945 PushMultiItemsWidths(components);
9946 size_t type_size = GDataTypeInfo[data_type].Size;
9947 for (int i = 0; i < components; i++)
9948 {
9949 PushID(i);
9950 value_changed |= DragScalar("##v", data_type, v, v_speed, v_min, v_max, format, power);
9951 SameLine(0, g.Style.ItemInnerSpacing.x);
9952 PopID();
9953 PopItemWidth();
9954 v = (void*)((char*)v + type_size);
9955 }
9956 PopID();
9957
9958 TextUnformatted(label, FindRenderedTextEnd(label));
9959 EndGroup();
9960 return value_changed;
9961 }
9962
9963 bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* format, float power)
9964 {
9965 return DragScalar(label, ImGuiDataType_Float, v, v_speed, &v_min, &v_max, format, power);
9966 }
9967
9968 bool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* format, float power)
9969 {
9970 return DragScalarN(label, ImGuiDataType_Float, v, 2, v_speed, &v_min, &v_max, format, power);
9971 }
9972
9973 bool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* format, float power)
9974 {
9975 return DragScalarN(label, ImGuiDataType_Float, v, 3, v_speed, &v_min, &v_max, format, power);
9976 }
9977
9978 bool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* format, float power)
9979 {
9980 return DragScalarN(label, ImGuiDataType_Float, v, 4, v_speed, &v_min, &v_max, format, power);
9981 }
9982
9983 bool ImGui::DragFloatRange2(const char* label, float* v_current_min, float* v_current_max, float v_speed, float v_min, float v_max, const char* format, const char* format_max, float power)
9984 {
9985 ImGuiWindow* window = GetCurrentWindow();
9986 if (window->SkipItems)
9987 return false;
9988
9989 ImGuiContext& g = *GImGui;
9990 PushID(label);
9991 BeginGroup();
9992 PushMultiItemsWidths(2);
9993
9994 bool value_changed = DragFloat("##min", v_current_min, v_speed, (v_min >= v_max) ? -FLT_MAX : v_min, (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max), format, power);
9995 PopItemWidth();
9996 SameLine(0, g.Style.ItemInnerSpacing.x);
9997 value_changed |= DragFloat("##max", v_current_max, v_speed, (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min), (v_min >= v_max) ? FLT_MAX : v_max, format_max ? format_max : format, power);
9998 PopItemWidth();
9999 SameLine(0, g.Style.ItemInnerSpacing.x);
10000
10001 TextUnformatted(label, FindRenderedTextEnd(label));
10002 EndGroup();
10003 PopID();
10004 return value_changed;
10005 }
10006
10007 // NB: v_speed is float to allow adjusting the drag speed with more precision
10008 bool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* format)
10009 {
10010 return DragScalar(label, ImGuiDataType_S32, v, v_speed, &v_min, &v_max, format);
10011 }
10012
10013 bool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* format)
10014 {
10015 return DragScalarN(label, ImGuiDataType_S32, v, 2, v_speed, &v_min, &v_max, format);
10016 }
10017
10018 bool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* format)
10019 {
10020 return DragScalarN(label, ImGuiDataType_S32, v, 3, v_speed, &v_min, &v_max, format);
10021 }
10022
10023 bool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* format)
10024 {
10025 return DragScalarN(label, ImGuiDataType_S32, v, 4, v_speed, &v_min, &v_max, format);
10026 }
10027
10028 bool ImGui::DragIntRange2(const char* label, int* v_current_min, int* v_current_max, float v_speed, int v_min, int v_max, const char* format, const char* format_max)
10029 {
10030 ImGuiWindow* window = GetCurrentWindow();
10031 if (window->SkipItems)
10032 return false;
10033
10034 ImGuiContext& g = *GImGui;
10035 PushID(label);
10036 BeginGroup();
10037 PushMultiItemsWidths(2);
10038
10039 bool value_changed = DragInt("##min", v_current_min, v_speed, (v_min >= v_max) ? INT_MIN : v_min, (v_min >= v_max) ? *v_current_max : ImMin(v_max, *v_current_max), format);
10040 PopItemWidth();
10041 SameLine(0, g.Style.ItemInnerSpacing.x);
10042 value_changed |= DragInt("##max", v_current_max, v_speed, (v_min >= v_max) ? *v_current_min : ImMax(v_min, *v_current_min), (v_min >= v_max) ? INT_MAX : v_max, format_max ? format_max : format);
10043 PopItemWidth();
10044 SameLine(0, g.Style.ItemInnerSpacing.x);
10045
10046 TextUnformatted(label, FindRenderedTextEnd(label));
10047 EndGroup();
10048 PopID();
10049
10050 return value_changed;
10051 }
10052
10053 void ImGui::PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size)
10054 {
10055 ImGuiWindow* window = GetCurrentWindow();
10056 if (window->SkipItems)
10057 return;
10058
10059 ImGuiContext& g = *GImGui;
10060 const ImGuiStyle& style = g.Style;
10061
10062 const ImVec2 label_size = CalcTextSize(label, NULL, true);
10063 if (graph_size.x == 0.0f)
10064 graph_size.x = CalcItemWidth();
10065 if (graph_size.y == 0.0f)
10066 graph_size.y = label_size.y + (style.FramePadding.y * 2);
10067
10068 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(graph_size.x, graph_size.y));
10069 const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
10070 const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0));
10071 ItemSize(total_bb, style.FramePadding.y);
10072 if (!ItemAdd(total_bb, 0, &frame_bb))
10073 return;
10074 const bool hovered = ItemHoverable(inner_bb, 0);
10075
10076 // Determine scale from values if not specified
10077 if (scale_min == FLT_MAX || scale_max == FLT_MAX)
10078 {
10079 float v_min = FLT_MAX;
10080 float v_max = -FLT_MAX;
10081 for (int i = 0; i < values_count; i++)
10082 {
10083 const float v = values_getter(data, i);
10084 v_min = ImMin(v_min, v);
10085 v_max = ImMax(v_max, v);
10086 }
10087 if (scale_min == FLT_MAX)
10088 scale_min = v_min;
10089 if (scale_max == FLT_MAX)
10090 scale_max = v_max;
10091 }
10092
10093 RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
10094
10095 if (values_count > 0)
10096 {
10097 int res_w = ImMin((int)graph_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
10098 int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
10099
10100 // Tooltip on hover
10101 int v_hovered = -1;
10102 if (hovered)
10103 {
10104 const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f);
10105 const int v_idx = (int)(t * item_count);
10106 IM_ASSERT(v_idx >= 0 && v_idx < values_count);
10107
10108 const float v0 = values_getter(data, (v_idx + values_offset) % values_count);
10109 const float v1 = values_getter(data, (v_idx + 1 + values_offset) % values_count);
10110 if (plot_type == ImGuiPlotType_Lines)
10111 SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx+1, v1);
10112 else if (plot_type == ImGuiPlotType_Histogram)
10113 SetTooltip("%d: %8.4g", v_idx, v0);
10114 v_hovered = v_idx;
10115 }
10116
10117 const float t_step = 1.0f / (float)res_w;
10118 const float inv_scale = (scale_min == scale_max) ? 0.0f : (1.0f / (scale_max - scale_min));
10119
10120 float v0 = values_getter(data, (0 + values_offset) % values_count);
10121 float t0 = 0.0f;
10122 ImVec2 tp0 = ImVec2( t0, 1.0f - ImSaturate((v0 - scale_min) * inv_scale) ); // Point in the normalized space of our target rectangle
10123 float histogram_zero_line_t = (scale_min * scale_max < 0.0f) ? (-scale_min * inv_scale) : (scale_min < 0.0f ? 0.0f : 1.0f); // Where does the zero line stands
10124
10125 const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram);
10126 const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered);
10127
10128 for (int n = 0; n < res_w; n++)
10129 {
10130 const float t1 = t0 + t_step;
10131 const int v1_idx = (int)(t0 * item_count + 0.5f);
10132 IM_ASSERT(v1_idx >= 0 && v1_idx < values_count);
10133 const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count);
10134 const ImVec2 tp1 = ImVec2( t1, 1.0f - ImSaturate((v1 - scale_min) * inv_scale) );
10135
10136 // NB: Draw calls are merged together by the DrawList system. Still, we should render our batch are lower level to save a bit of CPU.
10137 ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0);
10138 ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, histogram_zero_line_t));
10139 if (plot_type == ImGuiPlotType_Lines)
10140 {
10141 window->DrawList->AddLine(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base);
10142 }
10143 else if (plot_type == ImGuiPlotType_Histogram)
10144 {
10145 if (pos1.x >= pos0.x + 2.0f)
10146 pos1.x -= 1.0f;
10147 window->DrawList->AddRectFilled(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base);
10148 }
10149
10150 t0 = t1;
10151 tp0 = tp1;
10152 }
10153 }
10154
10155 // Text overlay
10156 if (overlay_text)
10157 RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, overlay_text, NULL, NULL, ImVec2(0.5f,0.0f));
10158
10159 if (label_size.x > 0.0f)
10160 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label);
10161 }
10162
10163 struct ImGuiPlotArrayGetterData
10164 {
10165 const float* Values;
10166 int Stride;
10167
10168 ImGuiPlotArrayGetterData(const float* values, int stride) { Values = values; Stride = stride; }
10169 };
10170
10171 static float Plot_ArrayGetter(void* data, int idx)
10172 {
10173 ImGuiPlotArrayGetterData* plot_data = (ImGuiPlotArrayGetterData*)data;
10174 const float v = *(const float*)(const void*)((const unsigned char*)plot_data->Values + (size_t)idx * plot_data->Stride);
10175 return v;
10176 }
10177
10178 void ImGui::PlotLines(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride)
10179 {
10180 ImGuiPlotArrayGetterData data(values, stride);
10181 PlotEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
10182 }
10183
10184 void ImGui::PlotLines(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size)
10185 {
10186 PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
10187 }
10188
10189 void ImGui::PlotHistogram(const char* label, const float* values, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size, int stride)
10190 {
10191 ImGuiPlotArrayGetterData data(values, stride);
10192 PlotEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
10193 }
10194
10195 void ImGui::PlotHistogram(const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size)
10196 {
10197 PlotEx(ImGuiPlotType_Histogram, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
10198 }
10199
10200 // size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size
10201 void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay)
10202 {
10203 ImGuiWindow* window = GetCurrentWindow();
10204 if (window->SkipItems)
10205 return;
10206
10207 ImGuiContext& g = *GImGui;
10208 const ImGuiStyle& style = g.Style;
10209
10210 ImVec2 pos = window->DC.CursorPos;
10211 ImRect bb(pos, pos + CalcItemSize(size_arg, CalcItemWidth(), g.FontSize + style.FramePadding.y*2.0f));
10212 ItemSize(bb, style.FramePadding.y);
10213 if (!ItemAdd(bb, 0))
10214 return;
10215
10216 // Render
10217 fraction = ImSaturate(fraction);
10218 RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
10219 bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize));
10220 const ImVec2 fill_br = ImVec2(ImLerp(bb.Min.x, bb.Max.x, fraction), bb.Max.y);
10221 RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), 0.0f, fraction, style.FrameRounding);
10222
10223 // Default displaying the fraction as percentage string, but user can override it
10224 char overlay_buf[32];
10225 if (!overlay)
10226 {
10227 ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction*100+0.01f);
10228 overlay = overlay_buf;
10229 }
10230
10231 ImVec2 overlay_size = CalcTextSize(overlay, NULL);
10232 if (overlay_size.x > 0.0f)
10233 RenderTextClipped(ImVec2(ImClamp(fill_br.x + style.ItemSpacing.x, bb.Min.x, bb.Max.x - overlay_size.x - style.ItemInnerSpacing.x), bb.Min.y), bb.Max, overlay, NULL, &overlay_size, ImVec2(0.0f,0.5f), &bb);
10234 }
10235
10236 bool ImGui::Checkbox(const char* label, bool* v)
10237 {
10238 ImGuiWindow* window = GetCurrentWindow();
10239 if (window->SkipItems)
10240 return false;
10241
10242 ImGuiContext& g = *GImGui;
10243 const ImGuiStyle& style = g.Style;
10244 const ImGuiID id = window->GetID(label);
10245 const ImVec2 label_size = CalcTextSize(label, NULL, true);
10246
10247 const ImRect check_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(label_size.y + style.FramePadding.y*2, label_size.y + style.FramePadding.y*2)); // We want a square shape to we use Y twice
10248 ItemSize(check_bb, style.FramePadding.y);
10249
10250 ImRect total_bb = check_bb;
10251 if (label_size.x > 0)
10252 SameLine(0, style.ItemInnerSpacing.x);
10253 const ImRect text_bb(window->DC.CursorPos + ImVec2(0,style.FramePadding.y), window->DC.CursorPos + ImVec2(0,style.FramePadding.y) + label_size);
10254 if (label_size.x > 0)
10255 {
10256 ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight()), style.FramePadding.y);
10257 total_bb = ImRect(ImMin(check_bb.Min, text_bb.Min), ImMax(check_bb.Max, text_bb.Max));
10258 }
10259
10260 if (!ItemAdd(total_bb, id))
10261 return false;
10262
10263 bool hovered, held;
10264 bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
10265 if (pressed)
10266 {
10267 *v = !(*v);
10268 MarkItemValueChanged(id);
10269 }
10270
10271 RenderNavHighlight(total_bb, id);
10272 RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding);
10273 if (*v)
10274 {
10275 const float check_sz = ImMin(check_bb.GetWidth(), check_bb.GetHeight());
10276 const float pad = ImMax(1.0f, (float)(int)(check_sz / 6.0f));
10277 RenderCheckMark(check_bb.Min + ImVec2(pad,pad), GetColorU32(ImGuiCol_CheckMark), check_bb.GetWidth() - pad*2.0f);
10278 }
10279
10280 if (g.LogEnabled)
10281 LogRenderedText(&text_bb.Min, *v ? "[x]" : "[ ]");
10282 if (label_size.x > 0.0f)
10283 RenderText(text_bb.Min, label);
10284
10285 return pressed;
10286 }
10287
10288 bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value)
10289 {
10290 bool v = ((*flags & flags_value) == flags_value);
10291 bool pressed = Checkbox(label, &v);
10292 if (pressed)
10293 {
10294 if (v)
10295 *flags |= flags_value;
10296 else
10297 *flags &= ~flags_value;
10298 }
10299
10300 return pressed;
10301 }
10302
10303 bool ImGui::RadioButton(const char* label, bool active)
10304 {
10305 ImGuiWindow* window = GetCurrentWindow();
10306 if (window->SkipItems)
10307 return false;
10308
10309 ImGuiContext& g = *GImGui;
10310 const ImGuiStyle& style = g.Style;
10311 const ImGuiID id = window->GetID(label);
10312 const ImVec2 label_size = CalcTextSize(label, NULL, true);
10313
10314 const ImRect check_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(label_size.y + style.FramePadding.y*2-1, label_size.y + style.FramePadding.y*2-1));
10315 ItemSize(check_bb, style.FramePadding.y);
10316
10317 ImRect total_bb = check_bb;
10318 if (label_size.x > 0)
10319 SameLine(0, style.ItemInnerSpacing.x);
10320 const ImRect text_bb(window->DC.CursorPos + ImVec2(0, style.FramePadding.y), window->DC.CursorPos + ImVec2(0, style.FramePadding.y) + label_size);
10321 if (label_size.x > 0)
10322 {
10323 ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight()), style.FramePadding.y);
10324 total_bb.Add(text_bb);
10325 }
10326
10327 if (!ItemAdd(total_bb, id))
10328 return false;
10329
10330 ImVec2 center = check_bb.GetCenter();
10331 center.x = (float)(int)center.x + 0.5f;
10332 center.y = (float)(int)center.y + 0.5f;
10333 const float radius = check_bb.GetHeight() * 0.5f;
10334
10335 bool hovered, held;
10336 bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
10337 if (pressed)
10338 MarkItemValueChanged(id);
10339
10340 RenderNavHighlight(total_bb, id);
10341 window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), 16);
10342 if (active)
10343 {
10344 const float check_sz = ImMin(check_bb.GetWidth(), check_bb.GetHeight());
10345 const float pad = ImMax(1.0f, (float)(int)(check_sz / 6.0f));
10346 window->DrawList->AddCircleFilled(center, radius-pad, GetColorU32(ImGuiCol_CheckMark), 16);
10347 }
10348
10349 if (style.FrameBorderSize > 0.0f)
10350 {
10351 window->DrawList->AddCircle(center+ImVec2(1,1), radius, GetColorU32(ImGuiCol_BorderShadow), 16, style.FrameBorderSize);
10352 window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), 16, style.FrameBorderSize);
10353 }
10354
10355 if (g.LogEnabled)
10356 LogRenderedText(&text_bb.Min, active ? "(x)" : "( )");
10357 if (label_size.x > 0.0f)
10358 RenderText(text_bb.Min, label);
10359
10360 return pressed;
10361 }
10362
10363 bool ImGui::RadioButton(const char* label, int* v, int v_button)
10364 {
10365 const bool pressed = RadioButton(label, *v == v_button);
10366 if (pressed)
10367 *v = v_button;
10368 return pressed;
10369 }
10370
10371 static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end)
10372 {
10373 int line_count = 0;
10374 const char* s = text_begin;
10375 while (char c = *s++) // We are only matching for \n so we can ignore UTF-8 decoding
10376 if (c == '\n')
10377 line_count++;
10378 s--;
10379 if (s[0] != '\n' && s[0] != '\r')
10380 line_count++;
10381 *out_text_end = s;
10382 return line_count;
10383 }
10384
10385 static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining, ImVec2* out_offset, bool stop_on_new_line)
10386 {
10387 ImFont* font = GImGui->Font;
10388 const float line_height = GImGui->FontSize;
10389 const float scale = line_height / font->FontSize;
10390
10391 ImVec2 text_size = ImVec2(0,0);
10392 float line_width = 0.0f;
10393
10394 const ImWchar* s = text_begin;
10395 while (s < text_end)
10396 {
10397 unsigned int c = (unsigned int)(*s++);
10398 if (c == '\n')
10399 {
10400 text_size.x = ImMax(text_size.x, line_width);
10401 text_size.y += line_height;
10402 line_width = 0.0f;
10403 if (stop_on_new_line)
10404 break;
10405 continue;
10406 }
10407 if (c == '\r')
10408 continue;
10409
10410 const float char_width = font->GetCharAdvance((unsigned short)c) * scale;
10411 line_width += char_width;
10412 }
10413
10414 if (text_size.x < line_width)
10415 text_size.x = line_width;
10416
10417 if (out_offset)
10418 *out_offset = ImVec2(line_width, text_size.y + line_height); // offset allow for the possibility of sitting after a trailing \n
10419
10420 if (line_width > 0 || text_size.y == 0.0f) // whereas size.y will ignore the trailing \n
10421 text_size.y += line_height;
10422
10423 if (remaining)
10424 *remaining = s;
10425
10426 return text_size;
10427 }
10428
10429 // Wrapper for stb_textedit.h to edit text (our wrapper is for: statically sized buffer, single-line, wchar characters. InputText converts between UTF-8 and wchar)
10430 namespace ImGuiStb
10431 {
10432
10433 static int STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj) { return obj->CurLenW; }
10434 static ImWchar STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING* obj, int idx) { return obj->Text[idx]; }
10435 static float STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx) { ImWchar c = obj->Text[line_start_idx+char_idx]; if (c == '\n') return STB_TEXTEDIT_GETWIDTH_NEWLINE; return GImGui->Font->GetCharAdvance(c) * (GImGui->FontSize / GImGui->Font->FontSize); }
10436 static int STB_TEXTEDIT_KEYTOTEXT(int key) { return key >= 0x10000 ? 0 : key; }
10437 static ImWchar STB_TEXTEDIT_NEWLINE = '\n';
10438 static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, STB_TEXTEDIT_STRING* obj, int line_start_idx)
10439 {
10440 const ImWchar* text = obj->Text.Data;
10441 const ImWchar* text_remaining = NULL;
10442 const ImVec2 size = InputTextCalcTextSizeW(text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true);
10443 r->x0 = 0.0f;
10444 r->x1 = size.x;
10445 r->baseline_y_delta = size.y;
10446 r->ymin = 0.0f;
10447 r->ymax = size.y;
10448 r->num_chars = (int)(text_remaining - (text + line_start_idx));
10449 }
10450
10451 static bool is_separator(unsigned int c) { return ImCharIsBlankW(c) || c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|'; }
10452 static int is_word_boundary_from_right(STB_TEXTEDIT_STRING* obj, int idx) { return idx > 0 ? (is_separator( obj->Text[idx-1] ) && !is_separator( obj->Text[idx] ) ) : 1; }
10453 static int STB_TEXTEDIT_MOVEWORDLEFT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx--; while (idx >= 0 && !is_word_boundary_from_right(obj, idx)) idx--; return idx < 0 ? 0 : idx; }
10454 #ifdef __APPLE__ // FIXME: Move setting to IO structure
10455 static int is_word_boundary_from_left(STB_TEXTEDIT_STRING* obj, int idx) { return idx > 0 ? (!is_separator( obj->Text[idx-1] ) && is_separator( obj->Text[idx] ) ) : 1; }
10456 static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_left(obj, idx)) idx++; return idx > len ? len : idx; }
10457 #else
10458 static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_right(obj, idx)) idx++; return idx > len ? len : idx; }
10459 #endif
10460 #define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h
10461 #define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_IMPL
10462
10463 static void STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING* obj, int pos, int n)
10464 {
10465 ImWchar* dst = obj->Text.Data + pos;
10466
10467 // We maintain our buffer length in both UTF-8 and wchar formats
10468 obj->CurLenA -= ImTextCountUtf8BytesFromStr(dst, dst + n);
10469 obj->CurLenW -= n;
10470
10471 // Offset remaining text
10472 const ImWchar* src = obj->Text.Data + pos + n;
10473 while (ImWchar c = *src++)
10474 *dst++ = c;
10475 *dst = '\0';
10476 }
10477
10478 static bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int pos, const ImWchar* new_text, int new_text_len)
10479 {
10480 const int text_len = obj->CurLenW;
10481 IM_ASSERT(pos <= text_len);
10482 if (new_text_len + text_len + 1 > obj->Text.Size)
10483 return false;
10484
10485 const int new_text_len_utf8 = ImTextCountUtf8BytesFromStr(new_text, new_text + new_text_len);
10486 if (new_text_len_utf8 + obj->CurLenA + 1 > obj->BufSizeA)
10487 return false;
10488
10489 ImWchar* text = obj->Text.Data;
10490 if (pos != text_len)
10491 memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos) * sizeof(ImWchar));
10492 memcpy(text + pos, new_text, (size_t)new_text_len * sizeof(ImWchar));
10493
10494 obj->CurLenW += new_text_len;
10495 obj->CurLenA += new_text_len_utf8;
10496 obj->Text[obj->CurLenW] = '\0';
10497
10498 return true;
10499 }
10500
10501 // We don't use an enum so we can build even with conflicting symbols (if another user of stb_textedit.h leak their STB_TEXTEDIT_K_* symbols)
10502 #define STB_TEXTEDIT_K_LEFT 0x10000 // keyboard input to move cursor left
10503 #define STB_TEXTEDIT_K_RIGHT 0x10001 // keyboard input to move cursor right
10504 #define STB_TEXTEDIT_K_UP 0x10002 // keyboard input to move cursor up
10505 #define STB_TEXTEDIT_K_DOWN 0x10003 // keyboard input to move cursor down
10506 #define STB_TEXTEDIT_K_LINESTART 0x10004 // keyboard input to move cursor to start of line
10507 #define STB_TEXTEDIT_K_LINEEND 0x10005 // keyboard input to move cursor to end of line
10508 #define STB_TEXTEDIT_K_TEXTSTART 0x10006 // keyboard input to move cursor to start of text
10509 #define STB_TEXTEDIT_K_TEXTEND 0x10007 // keyboard input to move cursor to end of text
10510 #define STB_TEXTEDIT_K_DELETE 0x10008 // keyboard input to delete selection or character under cursor
10511 #define STB_TEXTEDIT_K_BACKSPACE 0x10009 // keyboard input to delete selection or character left of cursor
10512 #define STB_TEXTEDIT_K_UNDO 0x1000A // keyboard input to perform undo
10513 #define STB_TEXTEDIT_K_REDO 0x1000B // keyboard input to perform redo
10514 #define STB_TEXTEDIT_K_WORDLEFT 0x1000C // keyboard input to move cursor left one word
10515 #define STB_TEXTEDIT_K_WORDRIGHT 0x1000D // keyboard input to move cursor right one word
10516 #define STB_TEXTEDIT_K_SHIFT 0x20000
10517
10518 #define STB_TEXTEDIT_IMPLEMENTATION
10519 #include "stb_textedit.h"
10520
10521 }
10522
10523 void ImGuiTextEditState::OnKeyPressed(int key)
10524 {
10525 stb_textedit_key(this, &StbState, key);
10526 CursorFollow = true;
10527 CursorAnimReset();
10528 }
10529
10530 // Public API to manipulate UTF-8 text
10531 // We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar)
10532 // FIXME: The existence of this rarely exercised code path is a bit of a nuisance.
10533 void ImGuiTextEditCallbackData::DeleteChars(int pos, int bytes_count)
10534 {
10535 IM_ASSERT(pos + bytes_count <= BufTextLen);
10536 char* dst = Buf + pos;
10537 const char* src = Buf + pos + bytes_count;
10538 while (char c = *src++)
10539 *dst++ = c;
10540 *dst = '\0';
10541
10542 if (CursorPos + bytes_count >= pos)
10543 CursorPos -= bytes_count;
10544 else if (CursorPos >= pos)
10545 CursorPos = pos;
10546 SelectionStart = SelectionEnd = CursorPos;
10547 BufDirty = true;
10548 BufTextLen -= bytes_count;
10549 }
10550
10551 void ImGuiTextEditCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end)
10552 {
10553 const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text);
10554 if (new_text_len + BufTextLen + 1 >= BufSize)
10555 return;
10556
10557 if (BufTextLen != pos)
10558 memmove(Buf + pos + new_text_len, Buf + pos, (size_t)(BufTextLen - pos));
10559 memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char));
10560 Buf[BufTextLen + new_text_len] = '\0';
10561
10562 if (CursorPos >= pos)
10563 CursorPos += new_text_len;
10564 SelectionStart = SelectionEnd = CursorPos;
10565 BufDirty = true;
10566 BufTextLen += new_text_len;
10567 }
10568
10569 // Return false to discard a character.
10570 static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data)
10571 {
10572 unsigned int c = *p_char;
10573
10574 if (c < 128 && c != ' ' && !isprint((int)(c & 0xFF)))
10575 {
10576 bool pass = false;
10577 pass |= (c == '\n' && (flags & ImGuiInputTextFlags_Multiline));
10578 pass |= (c == '\t' && (flags & ImGuiInputTextFlags_AllowTabInput));
10579 if (!pass)
10580 return false;
10581 }
10582
10583 if (c >= 0xE000 && c <= 0xF8FF) // Filter private Unicode range. I don't imagine anybody would want to input them. GLFW on OSX seems to send private characters for special keys like arrow keys.
10584 return false;
10585
10586 if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific))
10587 {
10588 if (flags & ImGuiInputTextFlags_CharsDecimal)
10589 if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/'))
10590 return false;
10591
10592 if (flags & ImGuiInputTextFlags_CharsScientific)
10593 if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/') && (c != 'e') && (c != 'E'))
10594 return false;
10595
10596 if (flags & ImGuiInputTextFlags_CharsHexadecimal)
10597 if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F'))
10598 return false;
10599
10600 if (flags & ImGuiInputTextFlags_CharsUppercase)
10601 if (c >= 'a' && c <= 'z')
10602 *p_char = (c += (unsigned int)('A'-'a'));
10603
10604 if (flags & ImGuiInputTextFlags_CharsNoBlank)
10605 if (ImCharIsBlankW(c))
10606 return false;
10607 }
10608
10609 if (flags & ImGuiInputTextFlags_CallbackCharFilter)
10610 {
10611 ImGuiTextEditCallbackData callback_data;
10612 memset(&callback_data, 0, sizeof(ImGuiTextEditCallbackData));
10613 callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter;
10614 callback_data.EventChar = (ImWchar)c;
10615 callback_data.Flags = flags;
10616 callback_data.UserData = user_data;
10617 if (callback(&callback_data) != 0)
10618 return false;
10619 *p_char = callback_data.EventChar;
10620 if (!callback_data.EventChar)
10621 return false;
10622 }
10623
10624 return true;
10625 }
10626
10627 // Edit a string of text
10628 // NB: when active, hold on a privately held copy of the text (and apply back to 'buf'). So changing 'buf' while active has no effect.
10629 // FIXME: Rather messy function partly because we are doing UTF8 > u16 > UTF8 conversions on the go to more easily handle stb_textedit calls. Ideally we should stay in UTF-8 all the time. See https://github.com/nothings/stb/issues/188
10630 bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data)
10631 {
10632 ImGuiWindow* window = GetCurrentWindow();
10633 if (window->SkipItems)
10634 return false;
10635
10636 IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline))); // Can't use both together (they both use up/down keys)
10637 IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key)
10638
10639 ImGuiContext& g = *GImGui;
10640 const ImGuiIO& io = g.IO;
10641 const ImGuiStyle& style = g.Style;
10642
10643 const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0;
10644 const bool is_editable = (flags & ImGuiInputTextFlags_ReadOnly) == 0;
10645 const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0;
10646 const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0;
10647
10648 if (is_multiline) // Open group before calling GetID() because groups tracks id created within their scope,
10649 BeginGroup();
10650 const ImGuiID id = window->GetID(label);
10651 const ImVec2 label_size = CalcTextSize(label, NULL, true);
10652 ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), (is_multiline ? GetTextLineHeight() * 8.0f : label_size.y) + style.FramePadding.y*2.0f); // Arbitrary default of 8 lines high for multi-line
10653 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
10654 const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? (style.ItemInnerSpacing.x + label_size.x) : 0.0f, 0.0f));
10655
10656 ImGuiWindow* draw_window = window;
10657 if (is_multiline)
10658 {
10659 ItemAdd(total_bb, id, &frame_bb);
10660 if (!BeginChildFrame(id, frame_bb.GetSize()))
10661 {
10662 EndChildFrame();
10663 EndGroup();
10664 return false;
10665 }
10666 draw_window = GetCurrentWindow();
10667 draw_window->DC.NavLayerActiveMaskNext |= draw_window->DC.NavLayerCurrentMask; // This is to ensure that EndChild() will display a navigation highlight
10668 size.x -= draw_window->ScrollbarSizes.x;
10669 }
10670 else
10671 {
10672 ItemSize(total_bb, style.FramePadding.y);
10673 if (!ItemAdd(total_bb, id, &frame_bb))
10674 return false;
10675 }
10676 const bool hovered = ItemHoverable(frame_bb, id);
10677 if (hovered)
10678 g.MouseCursor = ImGuiMouseCursor_TextInput;
10679
10680 // Password pushes a temporary font with only a fallback glyph
10681 if (is_password)
10682 {
10683 const ImFontGlyph* glyph = g.Font->FindGlyph('*');
10684 ImFont* password_font = &g.InputTextPasswordFont;
10685 password_font->FontSize = g.Font->FontSize;
10686 password_font->Scale = g.Font->Scale;
10687 password_font->DisplayOffset = g.Font->DisplayOffset;
10688 password_font->Ascent = g.Font->Ascent;
10689 password_font->Descent = g.Font->Descent;
10690 password_font->ContainerAtlas = g.Font->ContainerAtlas;
10691 password_font->FallbackGlyph = glyph;
10692 password_font->FallbackAdvanceX = glyph->AdvanceX;
10693 IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexAdvanceX.empty() && password_font->IndexLookup.empty());
10694 PushFont(password_font);
10695 }
10696
10697 // NB: we are only allowed to access 'edit_state' if we are the active widget.
10698 ImGuiTextEditState& edit_state = g.InputTextState;
10699
10700 const bool focus_requested = FocusableItemRegister(window, id, (flags & (ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_AllowTabInput)) == 0); // Using completion callback disable keyboard tabbing
10701 const bool focus_requested_by_code = focus_requested && (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent);
10702 const bool focus_requested_by_tab = focus_requested && !focus_requested_by_code;
10703
10704 const bool user_clicked = hovered && io.MouseClicked[0];
10705 const bool user_scrolled = is_multiline && g.ActiveId == 0 && edit_state.Id == id && g.ActiveIdPreviousFrame == draw_window->GetIDNoKeepAlive("#SCROLLY");
10706 const bool user_nav_input_start = (g.ActiveId != id) && ((g.NavInputId == id) || (g.NavActivateId == id && g.NavInputSource == ImGuiInputSource_NavKeyboard));
10707
10708 bool clear_active_id = false;
10709
10710 bool select_all = (g.ActiveId != id) && ((flags & ImGuiInputTextFlags_AutoSelectAll) != 0 || user_nav_input_start) && (!is_multiline);
10711 if (focus_requested || user_clicked || user_scrolled || user_nav_input_start)
10712 {
10713 if (g.ActiveId != id)
10714 {
10715 // Start edition
10716 // Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar)
10717 // From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode)
10718 const int prev_len_w = edit_state.CurLenW;
10719 edit_state.Text.resize(buf_size+1); // wchar count <= UTF-8 count. we use +1 to make sure that .Data isn't NULL so it doesn't crash.
10720 edit_state.InitialText.resize(buf_size+1); // UTF-8. we use +1 to make sure that .Data isn't NULL so it doesn't crash.
10721 ImStrncpy(edit_state.InitialText.Data, buf, edit_state.InitialText.Size);
10722 const char* buf_end = NULL;
10723 edit_state.CurLenW = ImTextStrFromUtf8(edit_state.Text.Data, edit_state.Text.Size, buf, NULL, &buf_end);
10724 edit_state.CurLenA = (int)(buf_end - buf); // We can't get the result from ImFormatString() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8.
10725 edit_state.CursorAnimReset();
10726
10727 // Preserve cursor position and undo/redo stack if we come back to same widget
10728 // FIXME: We should probably compare the whole buffer to be on the safety side. Comparing buf (utf8) and edit_state.Text (wchar).
10729 const bool recycle_state = (edit_state.Id == id) && (prev_len_w == edit_state.CurLenW);
10730 if (recycle_state)
10731 {
10732 // Recycle existing cursor/selection/undo stack but clamp position
10733 // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler.
10734 edit_state.CursorClamp();
10735 }
10736 else
10737 {
10738 edit_state.Id = id;
10739 edit_state.ScrollX = 0.0f;
10740 stb_textedit_initialize_state(&edit_state.StbState, !is_multiline);
10741 if (!is_multiline && focus_requested_by_code)
10742 select_all = true;
10743 }
10744 if (flags & ImGuiInputTextFlags_AlwaysInsertMode)
10745 edit_state.StbState.insert_mode = true;
10746 if (!is_multiline && (focus_requested_by_tab || (user_clicked && io.KeyCtrl)))
10747 select_all = true;
10748 }
10749 SetActiveID(id, window);
10750 SetFocusID(id, window);
10751 FocusWindow(window);
10752 if (!is_multiline && !(flags & ImGuiInputTextFlags_CallbackHistory))
10753 g.ActiveIdAllowNavDirFlags |= ((1 << ImGuiDir_Up) | (1 << ImGuiDir_Down));
10754 }
10755 else if (io.MouseClicked[0])
10756 {
10757 // Release focus when we click outside
10758 clear_active_id = true;
10759 }
10760
10761 bool value_changed = false;
10762 bool enter_pressed = false;
10763
10764 if (g.ActiveId == id)
10765 {
10766 if (!is_editable && !g.ActiveIdIsJustActivated)
10767 {
10768 // When read-only we always use the live data passed to the function
10769 edit_state.Text.resize(buf_size+1);
10770 const char* buf_end = NULL;
10771 edit_state.CurLenW = ImTextStrFromUtf8(edit_state.Text.Data, edit_state.Text.Size, buf, NULL, &buf_end);
10772 edit_state.CurLenA = (int)(buf_end - buf);
10773 edit_state.CursorClamp();
10774 }
10775
10776 edit_state.BufSizeA = buf_size;
10777
10778 // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget.
10779 // Down the line we should have a cleaner library-wide concept of Selected vs Active.
10780 g.ActiveIdAllowOverlap = !io.MouseDown[0];
10781 g.WantTextInputNextFrame = 1;
10782
10783 // Edit in progress
10784 const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + edit_state.ScrollX;
10785 const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y - style.FramePadding.y) : (g.FontSize*0.5f));
10786
10787 const bool is_osx = io.OptMacOSXBehaviors;
10788 if (select_all || (hovered && !is_osx && io.MouseDoubleClicked[0]))
10789 {
10790 edit_state.SelectAll();
10791 edit_state.SelectedAllMouseLock = true;
10792 }
10793 else if (hovered && is_osx && io.MouseDoubleClicked[0])
10794 {
10795 // Double-click select a word only, OS X style (by simulating keystrokes)
10796 edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT);
10797 edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT);
10798 }
10799 else if (io.MouseClicked[0] && !edit_state.SelectedAllMouseLock)
10800 {
10801 if (hovered)
10802 {
10803 stb_textedit_click(&edit_state, &edit_state.StbState, mouse_x, mouse_y);
10804 edit_state.CursorAnimReset();
10805 }
10806 }
10807 else if (io.MouseDown[0] && !edit_state.SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f))
10808 {
10809 stb_textedit_drag(&edit_state, &edit_state.StbState, mouse_x, mouse_y);
10810 edit_state.CursorAnimReset();
10811 edit_state.CursorFollow = true;
10812 }
10813 if (edit_state.SelectedAllMouseLock && !io.MouseDown[0])
10814 edit_state.SelectedAllMouseLock = false;
10815
10816 if (io.InputCharacters[0])
10817 {
10818 // Process text input (before we check for Return because using some IME will effectively send a Return?)
10819 // We ignore CTRL inputs, but need to allow ALT+CTRL as some keyboards (e.g. German) use AltGR (which _is_ Alt+Ctrl) to input certain characters.
10820 bool ignore_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeySuper);
10821 if (!ignore_inputs && is_editable && !user_nav_input_start)
10822 for (int n = 0; n < IM_ARRAYSIZE(io.InputCharacters) && io.InputCharacters[n]; n++)
10823 {
10824 // Insert character if they pass filtering
10825 unsigned int c = (unsigned int)io.InputCharacters[n];
10826 if (InputTextFilterCharacter(&c, flags, callback, user_data))
10827 edit_state.OnKeyPressed((int)c);
10828 }
10829
10830 // Consume characters
10831 memset(g.IO.InputCharacters, 0, sizeof(g.IO.InputCharacters));
10832 }
10833 }
10834
10835 bool cancel_edit = false;
10836 if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id)
10837 {
10838 // Handle key-presses
10839 const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0);
10840 const bool is_osx = io.OptMacOSXBehaviors;
10841 const bool is_shortcut_key = (is_osx ? (io.KeySuper && !io.KeyCtrl) : (io.KeyCtrl && !io.KeySuper)) && !io.KeyAlt && !io.KeyShift; // OS X style: Shortcuts using Cmd/Super instead of Ctrl
10842 const bool is_osx_shift_shortcut = is_osx && io.KeySuper && io.KeyShift && !io.KeyCtrl && !io.KeyAlt;
10843 const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl; // OS X style: Text editing cursor movement using Alt instead of Ctrl
10844 const bool is_startend_key_down = is_osx && io.KeySuper && !io.KeyCtrl && !io.KeyAlt; // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End
10845 const bool is_ctrl_key_only = io.KeyCtrl && !io.KeyShift && !io.KeyAlt && !io.KeySuper;
10846 const bool is_shift_key_only = io.KeyShift && !io.KeyCtrl && !io.KeyAlt && !io.KeySuper;
10847
10848 const bool is_cut = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_X)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Delete))) && is_editable && !is_password && (!is_multiline || edit_state.HasSelection());
10849 const bool is_copy = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_C)) || (is_ctrl_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && !is_password && (!is_multiline || edit_state.HasSelection());
10850 const bool is_paste = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_V)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && is_editable;
10851 const bool is_undo = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Z)) && is_editable && is_undoable);
10852 const bool is_redo = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Y)) || (is_osx_shift_shortcut && IsKeyPressedMap(ImGuiKey_Z))) && is_editable && is_undoable;
10853
10854 if (IsKeyPressedMap(ImGuiKey_LeftArrow)) { edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); }
10855 else if (IsKeyPressedMap(ImGuiKey_RightArrow)) { edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); }
10856 else if (IsKeyPressedMap(ImGuiKey_UpArrow) && is_multiline) { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); }
10857 else if (IsKeyPressedMap(ImGuiKey_DownArrow) && is_multiline) { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY())); else edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); }
10858 else if (IsKeyPressedMap(ImGuiKey_Home)) { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); }
10859 else if (IsKeyPressedMap(ImGuiKey_End)) { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); }
10860 else if (IsKeyPressedMap(ImGuiKey_Delete) && is_editable) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); }
10861 else if (IsKeyPressedMap(ImGuiKey_Backspace) && is_editable)
10862 {
10863 if (!edit_state.HasSelection())
10864 {
10865 if (is_wordmove_key_down) edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT|STB_TEXTEDIT_K_SHIFT);
10866 else if (is_osx && io.KeySuper && !io.KeyAlt && !io.KeyCtrl) edit_state.OnKeyPressed(STB_TEXTEDIT_K_LINESTART|STB_TEXTEDIT_K_SHIFT);
10867 }
10868 edit_state.OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask);
10869 }
10870 else if (IsKeyPressedMap(ImGuiKey_Enter))
10871 {
10872 bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0;
10873 if (!is_multiline || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl))
10874 {
10875 enter_pressed = clear_active_id = true;
10876 }
10877 else if (is_editable)
10878 {
10879 unsigned int c = '\n'; // Insert new line
10880 if (InputTextFilterCharacter(&c, flags, callback, user_data))
10881 edit_state.OnKeyPressed((int)c);
10882 }
10883 }
10884 else if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(ImGuiKey_Tab) && !io.KeyCtrl && !io.KeyShift && !io.KeyAlt && is_editable)
10885 {
10886 unsigned int c = '\t'; // Insert TAB
10887 if (InputTextFilterCharacter(&c, flags, callback, user_data))
10888 edit_state.OnKeyPressed((int)c);
10889 }
10890 else if (IsKeyPressedMap(ImGuiKey_Escape))
10891 {
10892 clear_active_id = cancel_edit = true;
10893 }
10894 else if (is_undo || is_redo)
10895 {
10896 edit_state.OnKeyPressed(is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO);
10897 edit_state.ClearSelection();
10898 }
10899 else if (is_shortcut_key && IsKeyPressedMap(ImGuiKey_A))
10900 {
10901 edit_state.SelectAll();
10902 edit_state.CursorFollow = true;
10903 }
10904 else if (is_cut || is_copy)
10905 {
10906 // Cut, Copy
10907 if (io.SetClipboardTextFn)
10908 {
10909 const int ib = edit_state.HasSelection() ? ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end) : 0;
10910 const int ie = edit_state.HasSelection() ? ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end) : edit_state.CurLenW;
10911 edit_state.TempTextBuffer.resize((ie-ib) * 4 + 1);
10912 ImTextStrToUtf8(edit_state.TempTextBuffer.Data, edit_state.TempTextBuffer.Size, edit_state.Text.Data+ib, edit_state.Text.Data+ie);
10913 SetClipboardText(edit_state.TempTextBuffer.Data);
10914 }
10915 if (is_cut)
10916 {
10917 if (!edit_state.HasSelection())
10918 edit_state.SelectAll();
10919 edit_state.CursorFollow = true;
10920 stb_textedit_cut(&edit_state, &edit_state.StbState);
10921 }
10922 }
10923 else if (is_paste)
10924 {
10925 if (const char* clipboard = GetClipboardText())
10926 {
10927 // Filter pasted buffer
10928 const int clipboard_len = (int)strlen(clipboard);
10929 ImWchar* clipboard_filtered = (ImWchar*)ImGui::MemAlloc((clipboard_len+1) * sizeof(ImWchar));
10930 int clipboard_filtered_len = 0;
10931 for (const char* s = clipboard; *s; )
10932 {
10933 unsigned int c;
10934 s += ImTextCharFromUtf8(&c, s, NULL);
10935 if (c == 0)
10936 break;
10937 if (c >= 0x10000 || !InputTextFilterCharacter(&c, flags, callback, user_data))
10938 continue;
10939 clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c;
10940 }
10941 clipboard_filtered[clipboard_filtered_len] = 0;
10942 if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation
10943 {
10944 stb_textedit_paste(&edit_state, &edit_state.StbState, clipboard_filtered, clipboard_filtered_len);
10945 edit_state.CursorFollow = true;
10946 }
10947 ImGui::MemFree(clipboard_filtered);
10948 }
10949 }
10950 }
10951
10952 if (g.ActiveId == id)
10953 {
10954 if (cancel_edit)
10955 {
10956 // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents.
10957 if (is_editable && strncmp(buf, edit_state.InitialText.Data, buf_size) != 0)
10958 {
10959 ImStrncpy(buf, edit_state.InitialText.Data, buf_size);
10960 value_changed = true;
10961 }
10962 }
10963
10964 // When using 'ImGuiInputTextFlags_EnterReturnsTrue' as a special case we reapply the live buffer back to the input buffer before clearing ActiveId, even though strictly speaking it wasn't modified on this frame.
10965 // If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail. Also this allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue without maintaining any user-side storage.
10966 bool apply_edit_back_to_user_buffer = !cancel_edit || (enter_pressed && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0);
10967 if (apply_edit_back_to_user_buffer)
10968 {
10969 // Apply new value immediately - copy modified buffer back
10970 // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer
10971 // FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect.
10972 // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks.
10973 if (is_editable)
10974 {
10975 edit_state.TempTextBuffer.resize(edit_state.Text.Size * 4);
10976 ImTextStrToUtf8(edit_state.TempTextBuffer.Data, edit_state.TempTextBuffer.Size, edit_state.Text.Data, NULL);
10977 }
10978
10979 // User callback
10980 if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackAlways)) != 0)
10981 {
10982 IM_ASSERT(callback != NULL);
10983
10984 // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment.
10985 ImGuiInputTextFlags event_flag = 0;
10986 ImGuiKey event_key = ImGuiKey_COUNT;
10987 if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && IsKeyPressedMap(ImGuiKey_Tab))
10988 {
10989 event_flag = ImGuiInputTextFlags_CallbackCompletion;
10990 event_key = ImGuiKey_Tab;
10991 }
10992 else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_UpArrow))
10993 {
10994 event_flag = ImGuiInputTextFlags_CallbackHistory;
10995 event_key = ImGuiKey_UpArrow;
10996 }
10997 else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_DownArrow))
10998 {
10999 event_flag = ImGuiInputTextFlags_CallbackHistory;
11000 event_key = ImGuiKey_DownArrow;
11001 }
11002 else if (flags & ImGuiInputTextFlags_CallbackAlways)
11003 event_flag = ImGuiInputTextFlags_CallbackAlways;
11004
11005 if (event_flag)
11006 {
11007 ImGuiTextEditCallbackData callback_data;
11008 memset(&callback_data, 0, sizeof(ImGuiTextEditCallbackData));
11009 callback_data.EventFlag = event_flag;
11010 callback_data.Flags = flags;
11011 callback_data.UserData = user_data;
11012 callback_data.ReadOnly = !is_editable;
11013
11014 callback_data.EventKey = event_key;
11015 callback_data.Buf = edit_state.TempTextBuffer.Data;
11016 callback_data.BufTextLen = edit_state.CurLenA;
11017 callback_data.BufSize = edit_state.BufSizeA;
11018 callback_data.BufDirty = false;
11019
11020 // We have to convert from wchar-positions to UTF-8-positions, which can be pretty slow (an incentive to ditch the ImWchar buffer, see https://github.com/nothings/stb/issues/188)
11021 ImWchar* text = edit_state.Text.Data;
11022 const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.cursor);
11023 const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_start);
11024 const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_end);
11025
11026 // Call user code
11027 callback(&callback_data);
11028
11029 // Read back what user may have modified
11030 IM_ASSERT(callback_data.Buf == edit_state.TempTextBuffer.Data); // Invalid to modify those fields
11031 IM_ASSERT(callback_data.BufSize == edit_state.BufSizeA);
11032 IM_ASSERT(callback_data.Flags == flags);
11033 if (callback_data.CursorPos != utf8_cursor_pos) edit_state.StbState.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos);
11034 if (callback_data.SelectionStart != utf8_selection_start) edit_state.StbState.select_start = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart);
11035 if (callback_data.SelectionEnd != utf8_selection_end) edit_state.StbState.select_end = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd);
11036 if (callback_data.BufDirty)
11037 {
11038 IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text!
11039 edit_state.CurLenW = ImTextStrFromUtf8(edit_state.Text.Data, edit_state.Text.Size, callback_data.Buf, NULL);
11040 edit_state.CurLenA = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen()
11041 edit_state.CursorAnimReset();
11042 }
11043 }
11044 }
11045
11046 // Copy back to user buffer
11047 if (is_editable && strcmp(edit_state.TempTextBuffer.Data, buf) != 0)
11048 {
11049 ImStrncpy(buf, edit_state.TempTextBuffer.Data, buf_size);
11050 value_changed = true;
11051 }
11052 }
11053 }
11054
11055 // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value)
11056 if (clear_active_id && g.ActiveId == id)
11057 ClearActiveID();
11058
11059 // Render
11060 // Select which buffer we are going to display. When ImGuiInputTextFlags_NoLiveEdit is set 'buf' might still be the old value. We set buf to NULL to prevent accidental usage from now on.
11061 const char* buf_display = (g.ActiveId == id && is_editable) ? edit_state.TempTextBuffer.Data : buf; buf = NULL;
11062
11063 if (!is_multiline)
11064 {
11065 RenderNavHighlight(frame_bb, id);
11066 RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
11067 }
11068
11069 const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + size.x, frame_bb.Min.y + size.y); // Not using frame_bb.Max because we have adjusted size
11070 ImVec2 render_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding;
11071 ImVec2 text_size(0.f, 0.f);
11072 const bool is_currently_scrolling = (edit_state.Id == id && is_multiline && g.ActiveId == draw_window->GetIDNoKeepAlive("#SCROLLY"));
11073 if (g.ActiveId == id || is_currently_scrolling)
11074 {
11075 edit_state.CursorAnim += io.DeltaTime;
11076
11077 // This is going to be messy. We need to:
11078 // - Display the text (this alone can be more easily clipped)
11079 // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation)
11080 // - Measure text height (for scrollbar)
11081 // We are attempting to do most of that in **one main pass** to minimize the computation cost (non-negligible for large amount of text) + 2nd pass for selection rendering (we could merge them by an extra refactoring effort)
11082 // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8.
11083 const ImWchar* text_begin = edit_state.Text.Data;
11084 ImVec2 cursor_offset, select_start_offset;
11085
11086 {
11087 // Count lines + find lines numbers straddling 'cursor' and 'select_start' position.
11088 const ImWchar* searches_input_ptr[2];
11089 searches_input_ptr[0] = text_begin + edit_state.StbState.cursor;
11090 searches_input_ptr[1] = NULL;
11091 int searches_remaining = 1;
11092 int searches_result_line_number[2] = { -1, -999 };
11093 if (edit_state.StbState.select_start != edit_state.StbState.select_end)
11094 {
11095 searches_input_ptr[1] = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end);
11096 searches_result_line_number[1] = -1;
11097 searches_remaining++;
11098 }
11099
11100 // Iterate all lines to find our line numbers
11101 // In multi-line mode, we never exit the loop until all lines are counted, so add one extra to the searches_remaining counter.
11102 searches_remaining += is_multiline ? 1 : 0;
11103 int line_count = 0;
11104 for (const ImWchar* s = text_begin; *s != 0; s++)
11105 if (*s == '\n')
11106 {
11107 line_count++;
11108 if (searches_result_line_number[0] == -1 && s >= searches_input_ptr[0]) { searches_result_line_number[0] = line_count; if (--searches_remaining <= 0) break; }
11109 if (searches_result_line_number[1] == -1 && s >= searches_input_ptr[1]) { searches_result_line_number[1] = line_count; if (--searches_remaining <= 0) break; }
11110 }
11111 line_count++;
11112 if (searches_result_line_number[0] == -1) searches_result_line_number[0] = line_count;
11113 if (searches_result_line_number[1] == -1) searches_result_line_number[1] = line_count;
11114
11115 // Calculate 2d position by finding the beginning of the line and measuring distance
11116 cursor_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[0], text_begin), searches_input_ptr[0]).x;
11117 cursor_offset.y = searches_result_line_number[0] * g.FontSize;
11118 if (searches_result_line_number[1] >= 0)
11119 {
11120 select_start_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[1], text_begin), searches_input_ptr[1]).x;
11121 select_start_offset.y = searches_result_line_number[1] * g.FontSize;
11122 }
11123
11124 // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224)
11125 if (is_multiline)
11126 text_size = ImVec2(size.x, line_count * g.FontSize);
11127 }
11128
11129 // Scroll
11130 if (edit_state.CursorFollow)
11131 {
11132 // Horizontal scroll in chunks of quarter width
11133 if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll))
11134 {
11135 const float scroll_increment_x = size.x * 0.25f;
11136 if (cursor_offset.x < edit_state.ScrollX)
11137 edit_state.ScrollX = (float)(int)ImMax(0.0f, cursor_offset.x - scroll_increment_x);
11138 else if (cursor_offset.x - size.x >= edit_state.ScrollX)
11139 edit_state.ScrollX = (float)(int)(cursor_offset.x - size.x + scroll_increment_x);
11140 }
11141 else
11142 {
11143 edit_state.ScrollX = 0.0f;
11144 }
11145
11146 // Vertical scroll
11147 if (is_multiline)
11148 {
11149 float scroll_y = draw_window->Scroll.y;
11150 if (cursor_offset.y - g.FontSize < scroll_y)
11151 scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize);
11152 else if (cursor_offset.y - size.y >= scroll_y)
11153 scroll_y = cursor_offset.y - size.y;
11154 draw_window->DC.CursorPos.y += (draw_window->Scroll.y - scroll_y); // To avoid a frame of lag
11155 draw_window->Scroll.y = scroll_y;
11156 render_pos.y = draw_window->DC.CursorPos.y;
11157 }
11158 }
11159 edit_state.CursorFollow = false;
11160 const ImVec2 render_scroll = ImVec2(edit_state.ScrollX, 0.0f);
11161
11162 // Draw selection
11163 if (edit_state.StbState.select_start != edit_state.StbState.select_end)
11164 {
11165 const ImWchar* text_selected_begin = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end);
11166 const ImWchar* text_selected_end = text_begin + ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end);
11167
11168 float bg_offy_up = is_multiline ? 0.0f : -1.0f; // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection.
11169 float bg_offy_dn = is_multiline ? 0.0f : 2.0f;
11170 ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg);
11171 ImVec2 rect_pos = render_pos + select_start_offset - render_scroll;
11172 for (const ImWchar* p = text_selected_begin; p < text_selected_end; )
11173 {
11174 if (rect_pos.y > clip_rect.w + g.FontSize)
11175 break;
11176 if (rect_pos.y < clip_rect.y)
11177 {
11178 while (p < text_selected_end)
11179 if (*p++ == '\n')
11180 break;
11181 }
11182 else
11183 {
11184 ImVec2 rect_size = InputTextCalcTextSizeW(p, text_selected_end, &p, NULL, true);
11185 if (rect_size.x <= 0.0f) rect_size.x = (float)(int)(g.Font->GetCharAdvance((unsigned short)' ') * 0.50f); // So we can see selected empty lines
11186 ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos +ImVec2(rect_size.x, bg_offy_dn));
11187 rect.ClipWith(clip_rect);
11188 if (rect.Overlaps(clip_rect))
11189 draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color);
11190 }
11191 rect_pos.x = render_pos.x - render_scroll.x;
11192 rect_pos.y += g.FontSize;
11193 }
11194 }
11195
11196 draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos - render_scroll, GetColorU32(ImGuiCol_Text), buf_display, buf_display + edit_state.CurLenA, 0.0f, is_multiline ? NULL : &clip_rect);
11197
11198 // Draw blinking cursor
11199 bool cursor_is_visible = (!g.IO.OptCursorBlink) || (g.InputTextState.CursorAnim <= 0.0f) || ImFmod(g.InputTextState.CursorAnim, 1.20f) <= 0.80f;
11200 ImVec2 cursor_screen_pos = render_pos + cursor_offset - render_scroll;
11201 ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y-g.FontSize+0.5f, cursor_screen_pos.x+1.0f, cursor_screen_pos.y-1.5f);
11202 if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect))
11203 draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text));
11204
11205 // Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.)
11206 if (is_editable)
11207 g.PlatformImePos = ImVec2(cursor_screen_pos.x - 1, cursor_screen_pos.y - g.FontSize);
11208 }
11209 else
11210 {
11211 // Render text only
11212 const char* buf_end = NULL;
11213 if (is_multiline)
11214 text_size = ImVec2(size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_end) * g.FontSize); // We don't need width
11215 draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos, GetColorU32(ImGuiCol_Text), buf_display, buf_end, 0.0f, is_multiline ? NULL : &clip_rect);
11216 }
11217
11218 if (is_multiline)
11219 {
11220 Dummy(text_size + ImVec2(0.0f, g.FontSize)); // Always add room to scroll an extra line
11221 EndChildFrame();
11222 EndGroup();
11223 }
11224
11225 if (is_password)
11226 PopFont();
11227
11228 // Log as text
11229 if (g.LogEnabled && !is_password)
11230 LogRenderedText(&render_pos, buf_display, NULL);
11231
11232 if (label_size.x > 0)
11233 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
11234
11235 if (value_changed)
11236 MarkItemValueChanged(id);
11237
11238 if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0)
11239 return enter_pressed;
11240 else
11241 return value_changed;
11242 }
11243
11244 bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data)
11245 {
11246 IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline()
11247 return InputTextEx(label, buf, (int)buf_size, ImVec2(0,0), flags, callback, user_data);
11248 }
11249
11250 bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data)
11251 {
11252 return InputTextEx(label, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data);
11253 }
11254
11255 // NB: format here must be a simple "%xx" format string with no prefix/suffix (unlike the Drag/Slider functions "format" argument)
11256 bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* data_ptr, const void* step, const void* step_fast, const char* format, ImGuiInputTextFlags extra_flags)
11257 {
11258 ImGuiWindow* window = GetCurrentWindow();
11259 if (window->SkipItems)
11260 return false;
11261
11262 ImGuiContext& g = *GImGui;
11263 const ImGuiStyle& style = g.Style;
11264
11265 IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
11266 if (format == NULL)
11267 format = GDataTypeInfo[data_type].PrintFmt;
11268
11269 char buf[64];
11270 DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, data_ptr, format);
11271
11272 bool value_changed = false;
11273 if ((extra_flags & (ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsScientific)) == 0)
11274 extra_flags |= ImGuiInputTextFlags_CharsDecimal;
11275 extra_flags |= ImGuiInputTextFlags_AutoSelectAll;
11276
11277 if (step != NULL)
11278 {
11279 const float button_size = GetFrameHeight();
11280
11281 BeginGroup(); // The only purpose of the group here is to allow the caller to query item data e.g. IsItemActive()
11282 PushID(label);
11283 PushItemWidth(ImMax(1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
11284 if (InputText("", buf, IM_ARRAYSIZE(buf), extra_flags)) // PushId(label) + "" gives us the expected ID from outside point of view
11285 value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialText.Data, data_type, data_ptr, format);
11286 PopItemWidth();
11287
11288 // Step buttons
11289 SameLine(0, style.ItemInnerSpacing.x);
11290 if (ButtonEx("-", ImVec2(button_size, button_size), ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups))
11291 {
11292 DataTypeApplyOp(data_type, '-', data_ptr, data_ptr, g.IO.KeyCtrl && step_fast ? step_fast : step);
11293 value_changed = true;
11294 }
11295 SameLine(0, style.ItemInnerSpacing.x);
11296 if (ButtonEx("+", ImVec2(button_size, button_size), ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups))
11297 {
11298 DataTypeApplyOp(data_type, '+', data_ptr, data_ptr, g.IO.KeyCtrl && step_fast ? step_fast : step);
11299 value_changed = true;
11300 }
11301 SameLine(0, style.ItemInnerSpacing.x);
11302 TextUnformatted(label, FindRenderedTextEnd(label));
11303
11304 PopID();
11305 EndGroup();
11306 }
11307 else
11308 {
11309 if (InputText(label, buf, IM_ARRAYSIZE(buf), extra_flags))
11310 value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialText.Data, data_type, data_ptr, format);
11311 }
11312
11313 return value_changed;
11314 }
11315
11316 bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, const char* format, ImGuiInputTextFlags extra_flags)
11317 {
11318 extra_flags |= ImGuiInputTextFlags_CharsScientific;
11319 return InputScalar(label, ImGuiDataType_Float, (void*)v, (void*)(step>0.0f ? &step : NULL), (void*)(step_fast>0.0f ? &step_fast : NULL), format, extra_flags);
11320 }
11321
11322 bool ImGui::InputDouble(const char* label, double* v, double step, double step_fast, const char* format, ImGuiInputTextFlags extra_flags)
11323 {
11324 extra_flags |= ImGuiInputTextFlags_CharsScientific;
11325 return InputScalar(label, ImGuiDataType_Double, (void*)v, (void*)(step>0.0 ? &step : NULL), (void*)(step_fast>0.0 ? &step_fast : NULL), format, extra_flags);
11326 }
11327
11328 bool ImGui::InputInt(const char* label, int* v, int step, int step_fast, ImGuiInputTextFlags extra_flags)
11329 {
11330 // Hexadecimal input provided as a convenience but the flag name is awkward. Typically you'd use InputText() to parse your own data, if you want to handle prefixes.
11331 const char* format = (extra_flags & ImGuiInputTextFlags_CharsHexadecimal) ? "%08X" : "%d";
11332 return InputScalar(label, ImGuiDataType_S32, (void*)v, (void*)(step>0 ? &step : NULL), (void*)(step_fast>0 ? &step_fast : NULL), format, extra_flags);
11333 }
11334
11335 bool ImGui::InputScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* step, const void* step_fast, const char* format, ImGuiInputTextFlags extra_flags)
11336 {
11337 ImGuiWindow* window = GetCurrentWindow();
11338 if (window->SkipItems)
11339 return false;
11340
11341 ImGuiContext& g = *GImGui;
11342 bool value_changed = false;
11343 BeginGroup();
11344 PushID(label);
11345 PushMultiItemsWidths(components);
11346 size_t type_size = GDataTypeInfo[data_type].Size;
11347 for (int i = 0; i < components; i++)
11348 {
11349 PushID(i);
11350 value_changed |= InputScalar("##v", data_type, v, step, step_fast, format, extra_flags);
11351 SameLine(0, g.Style.ItemInnerSpacing.x);
11352 PopID();
11353 PopItemWidth();
11354 v = (void*)((char*)v + type_size);
11355 }
11356 PopID();
11357
11358 TextUnformatted(label, FindRenderedTextEnd(label));
11359 EndGroup();
11360 return value_changed;
11361 }
11362
11363 bool ImGui::InputFloat2(const char* label, float v[2], const char* format, ImGuiInputTextFlags extra_flags)
11364 {
11365 return InputScalarN(label, ImGuiDataType_Float, v, 2, NULL, NULL, format, extra_flags);
11366 }
11367
11368 bool ImGui::InputFloat3(const char* label, float v[3], const char* format, ImGuiInputTextFlags extra_flags)
11369 {
11370 return InputScalarN(label, ImGuiDataType_Float, v, 3, NULL, NULL, format, extra_flags);
11371 }
11372
11373 bool ImGui::InputFloat4(const char* label, float v[4], const char* format, ImGuiInputTextFlags extra_flags)
11374 {
11375 return InputScalarN(label, ImGuiDataType_Float, v, 4, NULL, NULL, format, extra_flags);
11376 }
11377
11378 // Prefer using "const char* format" directly, which is more flexible and consistent with other API.
11379 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
11380 bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, int decimal_precision, ImGuiInputTextFlags extra_flags)
11381 {
11382 char format[16] = "%f";
11383 if (decimal_precision >= 0)
11384 ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision);
11385 return InputFloat(label, v, step, step_fast, format, extra_flags);
11386 }
11387
11388 bool ImGui::InputFloat2(const char* label, float v[2], int decimal_precision, ImGuiInputTextFlags extra_flags)
11389 {
11390 char format[16] = "%f";
11391 if (decimal_precision >= 0)
11392 ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision);
11393 return InputScalarN(label, ImGuiDataType_Float, v, 2, NULL, NULL, format, extra_flags);
11394 }
11395
11396 bool ImGui::InputFloat3(const char* label, float v[3], int decimal_precision, ImGuiInputTextFlags extra_flags)
11397 {
11398 char format[16] = "%f";
11399 if (decimal_precision >= 0)
11400 ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision);
11401 return InputScalarN(label, ImGuiDataType_Float, v, 3, NULL, NULL, format, extra_flags);
11402 }
11403
11404 bool ImGui::InputFloat4(const char* label, float v[4], int decimal_precision, ImGuiInputTextFlags extra_flags)
11405 {
11406 char format[16] = "%f";
11407 if (decimal_precision >= 0)
11408 ImFormatString(format, IM_ARRAYSIZE(format), "%%.%df", decimal_precision);
11409 return InputScalarN(label, ImGuiDataType_Float, v, 4, NULL, NULL, format, extra_flags);
11410 }
11411 #endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS
11412
11413 bool ImGui::InputInt2(const char* label, int v[2], ImGuiInputTextFlags extra_flags)
11414 {
11415 return InputScalarN(label, ImGuiDataType_S32, v, 2, NULL, NULL, "%d", extra_flags);
11416 }
11417
11418 bool ImGui::InputInt3(const char* label, int v[3], ImGuiInputTextFlags extra_flags)
11419 {
11420 return InputScalarN(label, ImGuiDataType_S32, v, 3, NULL, NULL, "%d", extra_flags);
11421 }
11422
11423 bool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags extra_flags)
11424 {
11425 return InputScalarN(label, ImGuiDataType_S32, v, 4, NULL, NULL, "%d", extra_flags);
11426 }
11427
11428 static float CalcMaxPopupHeightFromItemCount(int items_count)
11429 {
11430 ImGuiContext& g = *GImGui;
11431 if (items_count <= 0)
11432 return FLT_MAX;
11433 return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2);
11434 }
11435
11436 bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags)
11437 {
11438 // Always consume the SetNextWindowSizeConstraint() call in our early return paths
11439 ImGuiContext& g = *GImGui;
11440 ImGuiCond backup_next_window_size_constraint = g.NextWindowData.SizeConstraintCond;
11441 g.NextWindowData.SizeConstraintCond = 0;
11442
11443 ImGuiWindow* window = GetCurrentWindow();
11444 if (window->SkipItems)
11445 return false;
11446
11447 IM_ASSERT((flags & (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)) != (ImGuiComboFlags_NoArrowButton | ImGuiComboFlags_NoPreview)); // Can't use both flags together
11448
11449 const ImGuiStyle& style = g.Style;
11450 const ImGuiID id = window->GetID(label);
11451
11452 const float arrow_size = (flags & ImGuiComboFlags_NoArrowButton) ? 0.0f : GetFrameHeight();
11453 const ImVec2 label_size = CalcTextSize(label, NULL, true);
11454 const float w = (flags & ImGuiComboFlags_NoPreview) ? arrow_size : CalcItemWidth();
11455 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y*2.0f));
11456 const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
11457 ItemSize(total_bb, style.FramePadding.y);
11458 if (!ItemAdd(total_bb, id, &frame_bb))
11459 return false;
11460
11461 bool hovered, held;
11462 bool pressed = ButtonBehavior(frame_bb, id, &hovered, &held);
11463 bool popup_open = IsPopupOpen(id);
11464
11465 const ImRect value_bb(frame_bb.Min, frame_bb.Max - ImVec2(arrow_size, 0.0f));
11466 const ImU32 frame_col = GetColorU32(hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
11467 RenderNavHighlight(frame_bb, id);
11468 if (!(flags & ImGuiComboFlags_NoPreview))
11469 window->DrawList->AddRectFilled(frame_bb.Min, ImVec2(frame_bb.Max.x - arrow_size, frame_bb.Max.y), frame_col, style.FrameRounding, ImDrawCornerFlags_Left);
11470 if (!(flags & ImGuiComboFlags_NoArrowButton))
11471 {
11472 window->DrawList->AddRectFilled(ImVec2(frame_bb.Max.x - arrow_size, frame_bb.Min.y), frame_bb.Max, GetColorU32((popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button), style.FrameRounding, (w <= arrow_size) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Right);
11473 RenderArrow(ImVec2(frame_bb.Max.x - arrow_size + style.FramePadding.y, frame_bb.Min.y + style.FramePadding.y), ImGuiDir_Down);
11474 }
11475 RenderFrameBorder(frame_bb.Min, frame_bb.Max, style.FrameRounding);
11476 if (preview_value != NULL && !(flags & ImGuiComboFlags_NoPreview))
11477 RenderTextClipped(frame_bb.Min + style.FramePadding, value_bb.Max, preview_value, NULL, NULL, ImVec2(0.0f,0.0f));
11478 if (label_size.x > 0)
11479 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
11480
11481 if ((pressed || g.NavActivateId == id) && !popup_open)
11482 {
11483 if (window->DC.NavLayerCurrent == 0)
11484 window->NavLastIds[0] = id;
11485 OpenPopupEx(id);
11486 popup_open = true;
11487 }
11488
11489 if (!popup_open)
11490 return false;
11491
11492 if (backup_next_window_size_constraint)
11493 {
11494 g.NextWindowData.SizeConstraintCond = backup_next_window_size_constraint;
11495 g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w);
11496 }
11497 else
11498 {
11499 if ((flags & ImGuiComboFlags_HeightMask_) == 0)
11500 flags |= ImGuiComboFlags_HeightRegular;
11501 IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one
11502 int popup_max_height_in_items = -1;
11503 if (flags & ImGuiComboFlags_HeightRegular) popup_max_height_in_items = 8;
11504 else if (flags & ImGuiComboFlags_HeightSmall) popup_max_height_in_items = 4;
11505 else if (flags & ImGuiComboFlags_HeightLarge) popup_max_height_in_items = 20;
11506 SetNextWindowSizeConstraints(ImVec2(w, 0.0f), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
11507 }
11508
11509 char name[16];
11510 ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.CurrentPopupStack.Size); // Recycle windows based on depth
11511
11512 // Peak into expected window size so we can position it
11513 if (ImGuiWindow* popup_window = FindWindowByName(name))
11514 if (popup_window->WasActive)
11515 {
11516 ImVec2 size_contents = CalcSizeContents(popup_window);
11517 ImVec2 size_expected = CalcSizeAfterConstraint(popup_window, CalcSizeAutoFit(popup_window, size_contents));
11518 if (flags & ImGuiComboFlags_PopupAlignLeft)
11519 popup_window->AutoPosLastDirection = ImGuiDir_Left;
11520 ImRect r_outer = FindAllowedExtentRectForWindow(popup_window);
11521 ImVec2 pos = FindBestWindowPosForPopupEx(frame_bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, r_outer, frame_bb, ImGuiPopupPositionPolicy_ComboBox);
11522 SetNextWindowPos(pos);
11523 }
11524
11525 // Horizontally align ourselves with the framed text
11526 ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings;
11527 PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(style.FramePadding.x, style.WindowPadding.y));
11528 bool ret = Begin(name, NULL, window_flags);
11529 PopStyleVar();
11530 if (!ret)
11531 {
11532 EndPopup();
11533 IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above
11534 return false;
11535 }
11536 return true;
11537 }
11538
11539 void ImGui::EndCombo()
11540 {
11541 EndPopup();
11542 }
11543
11544 // Getter for the old Combo() API: const char*[]
11545 static bool Items_ArrayGetter(void* data, int idx, const char** out_text)
11546 {
11547 const char* const* items = (const char* const*)data;
11548 if (out_text)
11549 *out_text = items[idx];
11550 return true;
11551 }
11552
11553 // Getter for the old Combo() API: "item1\0item2\0item3\0"
11554 static bool Items_SingleStringGetter(void* data, int idx, const char** out_text)
11555 {
11556 // FIXME-OPT: we could pre-compute the indices to fasten this. But only 1 active combo means the waste is limited.
11557 const char* items_separated_by_zeros = (const char*)data;
11558 int items_count = 0;
11559 const char* p = items_separated_by_zeros;
11560 while (*p)
11561 {
11562 if (idx == items_count)
11563 break;
11564 p += strlen(p) + 1;
11565 items_count++;
11566 }
11567 if (!*p)
11568 return false;
11569 if (out_text)
11570 *out_text = p;
11571 return true;
11572 }
11573
11574 // Old API, prefer using BeginCombo() nowadays if you can.
11575 bool ImGui::Combo(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int popup_max_height_in_items)
11576 {
11577 ImGuiContext& g = *GImGui;
11578
11579 // Call the getter to obtain the preview string which is a parameter to BeginCombo()
11580 const char* preview_value = NULL;
11581 if (*current_item >= 0 && *current_item < items_count)
11582 items_getter(data, *current_item, &preview_value);
11583
11584 // The old Combo() API exposed "popup_max_height_in_items". The new more general BeginCombo() API doesn't have/need it, but we emulate it here.
11585 if (popup_max_height_in_items != -1 && !g.NextWindowData.SizeConstraintCond)
11586 SetNextWindowSizeConstraints(ImVec2(0,0), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
11587
11588 if (!BeginCombo(label, preview_value, ImGuiComboFlags_None))
11589 return false;
11590
11591 // Display items
11592 // FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed)
11593 bool value_changed = false;
11594 for (int i = 0; i < items_count; i++)
11595 {
11596 PushID((void*)(intptr_t)i);
11597 const bool item_selected = (i == *current_item);
11598 const char* item_text;
11599 if (!items_getter(data, i, &item_text))
11600 item_text = "*Unknown item*";
11601 if (Selectable(item_text, item_selected))
11602 {
11603 value_changed = true;
11604 *current_item = i;
11605 }
11606 if (item_selected)
11607 SetItemDefaultFocus();
11608 PopID();
11609 }
11610
11611 EndCombo();
11612 return value_changed;
11613 }
11614
11615 // Combo box helper allowing to pass an array of strings.
11616 bool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items)
11617 {
11618 const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items);
11619 return value_changed;
11620 }
11621
11622 // Combo box helper allowing to pass all items in a single string literal holding multiple zero-terminated items "item1\0item2\0"
11623 bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items)
11624 {
11625 int items_count = 0;
11626 const char* p = items_separated_by_zeros; // FIXME-OPT: Avoid computing this, or at least only when combo is open
11627 while (*p)
11628 {
11629 p += strlen(p) + 1;
11630 items_count++;
11631 }
11632 bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items);
11633 return value_changed;
11634 }
11635
11636 // Tip: pass an empty label (e.g. "##dummy") then you can use the space to draw other text or image.
11637 // But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID or use ##unique_id.
11638 bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
11639 {
11640 ImGuiWindow* window = GetCurrentWindow();
11641 if (window->SkipItems)
11642 return false;
11643
11644 ImGuiContext& g = *GImGui;
11645 const ImGuiStyle& style = g.Style;
11646
11647 if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet) // FIXME-OPT: Avoid if vertically clipped.
11648 PopClipRect();
11649
11650 ImGuiID id = window->GetID(label);
11651 ImVec2 label_size = CalcTextSize(label, NULL, true);
11652 ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y);
11653 ImVec2 pos = window->DC.CursorPos;
11654 pos.y += window->DC.CurrentLineTextBaseOffset;
11655 ImRect bb_inner(pos, pos + size);
11656 ItemSize(bb_inner);
11657
11658 // Fill horizontal space.
11659 ImVec2 window_padding = window->WindowPadding;
11660 float max_x = (flags & ImGuiSelectableFlags_SpanAllColumns) ? GetWindowContentRegionMax().x : GetContentRegionMax().x;
11661 float w_draw = ImMax(label_size.x, window->Pos.x + max_x - window_padding.x - window->DC.CursorPos.x);
11662 ImVec2 size_draw((size_arg.x != 0 && !(flags & ImGuiSelectableFlags_DrawFillAvailWidth)) ? size_arg.x : w_draw, size_arg.y != 0.0f ? size_arg.y : size.y);
11663 ImRect bb(pos, pos + size_draw);
11664 if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_DrawFillAvailWidth))
11665 bb.Max.x += window_padding.x;
11666
11667 // Selectables are tightly packed together, we extend the box to cover spacing between selectable.
11668 float spacing_L = (float)(int)(style.ItemSpacing.x * 0.5f);
11669 float spacing_U = (float)(int)(style.ItemSpacing.y * 0.5f);
11670 float spacing_R = style.ItemSpacing.x - spacing_L;
11671 float spacing_D = style.ItemSpacing.y - spacing_U;
11672 bb.Min.x -= spacing_L;
11673 bb.Min.y -= spacing_U;
11674 bb.Max.x += spacing_R;
11675 bb.Max.y += spacing_D;
11676 if (!ItemAdd(bb, (flags & ImGuiSelectableFlags_Disabled) ? 0 : id))
11677 {
11678 if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet)
11679 PushColumnClipRect();
11680 return false;
11681 }
11682
11683 // We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries
11684 ImGuiButtonFlags button_flags = 0;
11685 if (flags & ImGuiSelectableFlags_NoHoldingActiveID) button_flags |= ImGuiButtonFlags_NoHoldingActiveID;
11686 if (flags & ImGuiSelectableFlags_PressedOnClick) button_flags |= ImGuiButtonFlags_PressedOnClick;
11687 if (flags & ImGuiSelectableFlags_PressedOnRelease) button_flags |= ImGuiButtonFlags_PressedOnRelease;
11688 if (flags & ImGuiSelectableFlags_Disabled) button_flags |= ImGuiButtonFlags_Disabled;
11689 if (flags & ImGuiSelectableFlags_AllowDoubleClick) button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick;
11690 bool hovered, held;
11691 bool pressed = ButtonBehavior(bb, id, &hovered, &held, button_flags);
11692 if (flags & ImGuiSelectableFlags_Disabled)
11693 selected = false;
11694
11695 // Hovering selectable with mouse updates NavId accordingly so navigation can be resumed with gamepad/keyboard (this doesn't happen on most widgets)
11696 if (pressed || hovered)
11697 if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent)
11698 {
11699 g.NavDisableHighlight = true;
11700 SetNavID(id, window->DC.NavLayerCurrent);
11701 }
11702 if (pressed)
11703 MarkItemValueChanged(id);
11704
11705 // Render
11706 if (hovered || selected)
11707 {
11708 const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
11709 RenderFrame(bb.Min, bb.Max, col, false, 0.0f);
11710 RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding);
11711 }
11712
11713 if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet)
11714 {
11715 PushColumnClipRect();
11716 bb.Max.x -= (GetContentRegionMax().x - max_x);
11717 }
11718
11719 if (flags & ImGuiSelectableFlags_Disabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
11720 RenderTextClipped(bb_inner.Min, bb.Max, label, NULL, &label_size, ImVec2(0.0f,0.0f));
11721 if (flags & ImGuiSelectableFlags_Disabled) PopStyleColor();
11722
11723 // Automatically close popups
11724 if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(window->DC.ItemFlags & ImGuiItemFlags_SelectableDontClosePopup))
11725 CloseCurrentPopup();
11726 return pressed;
11727 }
11728
11729 bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
11730 {
11731 if (Selectable(label, *p_selected, flags, size_arg))
11732 {
11733 *p_selected = !*p_selected;
11734 return true;
11735 }
11736 return false;
11737 }
11738
11739 // FIXME: Rename to BeginListBox()
11740 // Helper to calculate the size of a listbox and display a label on the right.
11741 // Tip: To have a list filling the entire window width, PushItemWidth(-1) and pass an empty label "##empty"
11742 bool ImGui::ListBoxHeader(const char* label, const ImVec2& size_arg)
11743 {
11744 ImGuiWindow* window = GetCurrentWindow();
11745 if (window->SkipItems)
11746 return false;
11747
11748 const ImGuiStyle& style = GetStyle();
11749 const ImGuiID id = GetID(label);
11750 const ImVec2 label_size = CalcTextSize(label, NULL, true);
11751
11752 // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
11753 ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.4f + style.ItemSpacing.y);
11754 ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y));
11755 ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
11756 ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
11757 window->DC.LastItemRect = bb; // Forward storage for ListBoxFooter.. dodgy.
11758
11759 BeginGroup();
11760 if (label_size.x > 0)
11761 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
11762
11763 BeginChildFrame(id, frame_bb.GetSize());
11764 return true;
11765 }
11766
11767 // FIXME: Rename to BeginListBox()
11768 bool ImGui::ListBoxHeader(const char* label, int items_count, int height_in_items)
11769 {
11770 // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
11771 // We don't add +0.40f if items_count <= height_in_items. It is slightly dodgy, because it means a dynamic list of items will make the widget resize occasionally when it crosses that size.
11772 // I am expecting that someone will come and complain about this behavior in a remote future, then we can advise on a better solution.
11773 if (height_in_items < 0)
11774 height_in_items = ImMin(items_count, 7);
11775 float height_in_items_f = height_in_items < items_count ? (height_in_items + 0.40f) : (height_in_items + 0.00f);
11776
11777 // We include ItemSpacing.y so that a list sized for the exact number of items doesn't make a scrollbar appears. We could also enforce that by passing a flag to BeginChild().
11778 ImVec2 size;
11779 size.x = 0.0f;
11780 size.y = GetTextLineHeightWithSpacing() * height_in_items_f + GetStyle().ItemSpacing.y;
11781 return ListBoxHeader(label, size);
11782 }
11783
11784 // FIXME: Rename to EndListBox()
11785 void ImGui::ListBoxFooter()
11786 {
11787 ImGuiWindow* parent_window = GetCurrentWindow()->ParentWindow;
11788 const ImRect bb = parent_window->DC.LastItemRect;
11789 const ImGuiStyle& style = GetStyle();
11790
11791 EndChildFrame();
11792
11793 // Redeclare item size so that it includes the label (we have stored the full size in LastItemRect)
11794 // We call SameLine() to restore DC.CurrentLine* data
11795 SameLine();
11796 parent_window->DC.CursorPos = bb.Min;
11797 ItemSize(bb, style.FramePadding.y);
11798 EndGroup();
11799 }
11800
11801 bool ImGui::ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_items)
11802 {
11803 const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items);
11804 return value_changed;
11805 }
11806
11807 bool ImGui::ListBox(const char* label, int* current_item, bool (*items_getter)(void*, int, const char**), void* data, int items_count, int height_in_items)
11808 {
11809 if (!ListBoxHeader(label, items_count, height_in_items))
11810 return false;
11811
11812 // Assume all items have even height (= 1 line of text). If you need items of different or variable sizes you can create a custom version of ListBox() in your code without using the clipper.
11813 bool value_changed = false;
11814 ImGuiListClipper clipper(items_count, GetTextLineHeightWithSpacing()); // We know exactly our line height here so we pass it as a minor optimization, but generally you don't need to.
11815 while (clipper.Step())
11816 for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
11817 {
11818 const bool item_selected = (i == *current_item);
11819 const char* item_text;
11820 if (!items_getter(data, i, &item_text))
11821 item_text = "*Unknown item*";
11822
11823 PushID(i);
11824 if (Selectable(item_text, item_selected))
11825 {
11826 *current_item = i;
11827 value_changed = true;
11828 }
11829 if (item_selected)
11830 SetItemDefaultFocus();
11831 PopID();
11832 }
11833 ListBoxFooter();
11834 return value_changed;
11835 }
11836
11837 bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled)
11838 {
11839 ImGuiWindow* window = GetCurrentWindow();
11840 if (window->SkipItems)
11841 return false;
11842
11843 ImGuiContext& g = *GImGui;
11844 ImGuiStyle& style = g.Style;
11845 ImVec2 pos = window->DC.CursorPos;
11846 ImVec2 label_size = CalcTextSize(label, NULL, true);
11847
11848 ImGuiSelectableFlags flags = ImGuiSelectableFlags_PressedOnRelease | (enabled ? 0 : ImGuiSelectableFlags_Disabled);
11849 bool pressed;
11850 if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
11851 {
11852 // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful
11853 // Note that in this situation we render neither the shortcut neither the selected tick mark
11854 float w = label_size.x;
11855 window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f);
11856 PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f);
11857 pressed = Selectable(label, false, flags, ImVec2(w, 0.0f));
11858 PopStyleVar();
11859 window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().
11860 }
11861 else
11862 {
11863 ImVec2 shortcut_size = shortcut ? CalcTextSize(shortcut, NULL) : ImVec2(0.0f, 0.0f);
11864 float w = window->MenuColumns.DeclColumns(label_size.x, shortcut_size.x, (float)(int)(g.FontSize * 1.20f)); // Feedback for next frame
11865 float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w);
11866 pressed = Selectable(label, false, flags | ImGuiSelectableFlags_DrawFillAvailWidth, ImVec2(w, 0.0f));
11867 if (shortcut_size.x > 0.0f)
11868 {
11869 PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
11870 RenderText(pos + ImVec2(window->MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false);
11871 PopStyleColor();
11872 }
11873 if (selected)
11874 RenderCheckMark(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.40f, g.FontSize * 0.134f * 0.5f), GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled), g.FontSize * 0.866f);
11875 }
11876 return pressed;
11877 }
11878
11879 bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled)
11880 {
11881 if (MenuItem(label, shortcut, p_selected ? *p_selected : false, enabled))
11882 {
11883 if (p_selected)
11884 *p_selected = !*p_selected;
11885 return true;
11886 }
11887 return false;
11888 }
11889
11890 // For the main menu bar, which cannot be moved, we honor g.Style.DisplaySafeAreaPadding to ensure text can be visible on a TV set.
11891 bool ImGui::BeginMainMenuBar()
11892 {
11893 ImGuiContext& g = *GImGui;
11894 g.NextWindowData.MenuBarOffsetMinVal = ImVec2(g.Style.DisplaySafeAreaPadding.x, ImMax(g.Style.DisplaySafeAreaPadding.y - g.Style.FramePadding.y, 0.0f));
11895 SetNextWindowPos(ImVec2(0.0f, 0.0f));
11896 SetNextWindowSize(ImVec2(g.IO.DisplaySize.x, g.NextWindowData.MenuBarOffsetMinVal.y + g.FontBaseSize + g.Style.FramePadding.y));
11897 PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
11898 PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0,0));
11899 ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar;
11900 bool is_open = Begin("##MainMenuBar", NULL, window_flags) && BeginMenuBar();
11901 PopStyleVar(2);
11902 g.NextWindowData.MenuBarOffsetMinVal = ImVec2(0.0f, 0.0f);
11903 if (!is_open)
11904 {
11905 End();
11906 return false;
11907 }
11908 return true;
11909 }
11910
11911 void ImGui::EndMainMenuBar()
11912 {
11913 EndMenuBar();
11914
11915 // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window
11916 ImGuiContext& g = *GImGui;
11917 if (g.CurrentWindow == g.NavWindow && g.NavLayer == 0)
11918 FocusFrontMostActiveWindow(g.NavWindow);
11919
11920 End();
11921 }
11922
11923 bool ImGui::BeginMenuBar()
11924 {
11925 ImGuiWindow* window = GetCurrentWindow();
11926 if (window->SkipItems)
11927 return false;
11928 if (!(window->Flags & ImGuiWindowFlags_MenuBar))
11929 return false;
11930
11931 IM_ASSERT(!window->DC.MenuBarAppending);
11932 BeginGroup(); // Backup position on layer 0
11933 PushID("##menubar");
11934
11935 // We don't clip with current window clipping rectangle as it is already set to the area below. However we clip with window full rect.
11936 // We remove 1 worth of rounding to Max.x to that text in long menus and small windows don't tend to display over the lower-right rounded area, which looks particularly glitchy.
11937 ImRect bar_rect = window->MenuBarRect();
11938 ImRect clip_rect(ImFloor(bar_rect.Min.x + 0.5f), ImFloor(bar_rect.Min.y + window->WindowBorderSize + 0.5f), ImFloor(ImMax(bar_rect.Min.x, bar_rect.Max.x - window->WindowRounding) + 0.5f), ImFloor(bar_rect.Max.y + 0.5f));
11939 clip_rect.ClipWith(window->OuterRectClipped);
11940 PushClipRect(clip_rect.Min, clip_rect.Max, false);
11941
11942 window->DC.CursorPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffset.x, bar_rect.Min.y + window->DC.MenuBarOffset.y);
11943 window->DC.LayoutType = ImGuiLayoutType_Horizontal;
11944 window->DC.NavLayerCurrent++;
11945 window->DC.NavLayerCurrentMask <<= 1;
11946 window->DC.MenuBarAppending = true;
11947 AlignTextToFramePadding();
11948 return true;
11949 }
11950
11951 void ImGui::EndMenuBar()
11952 {
11953 ImGuiWindow* window = GetCurrentWindow();
11954 if (window->SkipItems)
11955 return;
11956 ImGuiContext& g = *GImGui;
11957
11958 // Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings.
11959 if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))
11960 {
11961 ImGuiWindow* nav_earliest_child = g.NavWindow;
11962 while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu))
11963 nav_earliest_child = nav_earliest_child->ParentWindow;
11964 if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && g.NavMoveRequestForward == ImGuiNavForward_None)
11965 {
11966 // To do so we claim focus back, restore NavId and then process the movement request for yet another frame.
11967 // This involve a one-frame delay which isn't very problematic in this situation. We could remove it by scoring in advance for multiple window (probably not worth the hassle/cost)
11968 IM_ASSERT(window->DC.NavLayerActiveMaskNext & 0x02); // Sanity check
11969 FocusWindow(window);
11970 SetNavIDWithRectRel(window->NavLastIds[1], 1, window->NavRectRel[1]);
11971 g.NavLayer = 1;
11972 g.NavDisableHighlight = true; // Hide highlight for the current frame so we don't see the intermediary selection.
11973 g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued;
11974 NavMoveRequestCancel();
11975 }
11976 }
11977
11978 IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar);
11979 IM_ASSERT(window->DC.MenuBarAppending);
11980 PopClipRect();
11981 PopID();
11982 window->DC.MenuBarOffset.x = window->DC.CursorPos.x - window->MenuBarRect().Min.x; // Save horizontal position so next append can reuse it. This is kinda equivalent to a per-layer CursorPos.
11983 window->DC.GroupStack.back().AdvanceCursor = false;
11984 EndGroup(); // Restore position on layer 0
11985 window->DC.LayoutType = ImGuiLayoutType_Vertical;
11986 window->DC.NavLayerCurrent--;
11987 window->DC.NavLayerCurrentMask >>= 1;
11988 window->DC.MenuBarAppending = false;
11989 }
11990
11991 bool ImGui::BeginMenu(const char* label, bool enabled)
11992 {
11993 ImGuiWindow* window = GetCurrentWindow();
11994 if (window->SkipItems)
11995 return false;
11996
11997 ImGuiContext& g = *GImGui;
11998 const ImGuiStyle& style = g.Style;
11999 const ImGuiID id = window->GetID(label);
12000
12001 ImVec2 label_size = CalcTextSize(label, NULL, true);
12002
12003 bool pressed;
12004 bool menu_is_open = IsPopupOpen(id);
12005 bool menuset_is_open = !(window->Flags & ImGuiWindowFlags_Popup) && (g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].OpenParentId == window->IDStack.back());
12006 ImGuiWindow* backed_nav_window = g.NavWindow;
12007 if (menuset_is_open)
12008 g.NavWindow = window; // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent)
12009
12010 // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu (using FindBestWindowPosForPopup).
12011 ImVec2 popup_pos, pos = window->DC.CursorPos;
12012 if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
12013 {
12014 // Menu inside an horizontal menu bar
12015 // Selectable extend their highlight by half ItemSpacing in each direction.
12016 // For ChildMenu, the popup position will be overwritten by the call to FindBestWindowPosForPopup() in Begin()
12017 popup_pos = ImVec2(pos.x - window->WindowPadding.x, pos.y - style.FramePadding.y + window->MenuBarHeight());
12018 window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f);
12019 PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f);
12020 float w = label_size.x;
12021 pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_PressedOnClick | ImGuiSelectableFlags_DontClosePopups | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f));
12022 PopStyleVar();
12023 window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * (-1.0f + 0.5f)); // -1 spacing to compensate the spacing added when Selectable() did a SameLine(). It would also work to call SameLine() ourselves after the PopStyleVar().
12024 }
12025 else
12026 {
12027 // Menu inside a menu
12028 popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y);
12029 float w = window->MenuColumns.DeclColumns(label_size.x, 0.0f, (float)(int)(g.FontSize * 1.20f)); // Feedback to next frame
12030 float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w);
12031 pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_NoHoldingActiveID | ImGuiSelectableFlags_PressedOnClick | ImGuiSelectableFlags_DontClosePopups | ImGuiSelectableFlags_DrawFillAvailWidth | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f));
12032 if (!enabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
12033 RenderArrow(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.30f, 0.0f), ImGuiDir_Right);
12034 if (!enabled) PopStyleColor();
12035 }
12036
12037 const bool hovered = enabled && ItemHoverable(window->DC.LastItemRect, id);
12038 if (menuset_is_open)
12039 g.NavWindow = backed_nav_window;
12040
12041 bool want_open = false, want_close = false;
12042 if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))
12043 {
12044 // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive.
12045 bool moving_within_opened_triangle = false;
12046 if (g.HoveredWindow == window && g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].ParentWindow == window && !(window->Flags & ImGuiWindowFlags_MenuBar))
12047 {
12048 if (ImGuiWindow* next_window = g.OpenPopupStack[g.CurrentPopupStack.Size].Window)
12049 {
12050 ImRect next_window_rect = next_window->Rect();
12051 ImVec2 ta = g.IO.MousePos - g.IO.MouseDelta;
12052 ImVec2 tb = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR();
12053 ImVec2 tc = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR();
12054 float extra = ImClamp(ImFabs(ta.x - tb.x) * 0.30f, 5.0f, 30.0f); // add a bit of extra slack.
12055 ta.x += (window->Pos.x < next_window->Pos.x) ? -0.5f : +0.5f; // to avoid numerical issues
12056 tb.y = ta.y + ImMax((tb.y - extra) - ta.y, -100.0f); // triangle is maximum 200 high to limit the slope and the bias toward large sub-menus // FIXME: Multiply by fb_scale?
12057 tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +100.0f);
12058 moving_within_opened_triangle = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos);
12059 //window->DrawList->PushClipRectFullScreen(); window->DrawList->AddTriangleFilled(ta, tb, tc, moving_within_opened_triangle ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); window->DrawList->PopClipRect(); // Debug
12060 }
12061 }
12062
12063 want_close = (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_within_opened_triangle);
12064 want_open = (!menu_is_open && hovered && !moving_within_opened_triangle) || (!menu_is_open && hovered && pressed);
12065
12066 if (g.NavActivateId == id)
12067 {
12068 want_close = menu_is_open;
12069 want_open = !menu_is_open;
12070 }
12071 if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open
12072 {
12073 want_open = true;
12074 NavMoveRequestCancel();
12075 }
12076 }
12077 else
12078 {
12079 // Menu bar
12080 if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it
12081 {
12082 want_close = true;
12083 want_open = menu_is_open = false;
12084 }
12085 else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others
12086 {
12087 want_open = true;
12088 }
12089 else if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open
12090 {
12091 want_open = true;
12092 NavMoveRequestCancel();
12093 }
12094 }
12095
12096 if (!enabled) // explicitly close if an open menu becomes disabled, facilitate users code a lot in pattern such as 'if (BeginMenu("options", has_object)) { ..use object.. }'
12097 want_close = true;
12098 if (want_close && IsPopupOpen(id))
12099 ClosePopupToLevel(g.CurrentPopupStack.Size);
12100
12101 if (!menu_is_open && want_open && g.OpenPopupStack.Size > g.CurrentPopupStack.Size)
12102 {
12103 // Don't recycle same menu level in the same frame, first close the other menu and yield for a frame.
12104 OpenPopup(label);
12105 return false;
12106 }
12107
12108 menu_is_open |= want_open;
12109 if (want_open)
12110 OpenPopup(label);
12111
12112 if (menu_is_open)
12113 {
12114 // Sub-menus are ChildWindow so that mouse can be hovering across them (otherwise top-most popup menu would steal focus and not allow hovering on parent menu)
12115 SetNextWindowPos(popup_pos, ImGuiCond_Always);
12116 ImGuiWindowFlags flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ((window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu)) ? ImGuiWindowFlags_ChildMenu|ImGuiWindowFlags_ChildWindow : ImGuiWindowFlags_ChildMenu);
12117 menu_is_open = BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
12118 }
12119
12120 return menu_is_open;
12121 }
12122
12123 void ImGui::EndMenu()
12124 {
12125 // Nav: When a left move request _within our child menu_ failed, close the menu.
12126 // A menu doesn't close itself because EndMenuBar() wants the catch the last Left<>Right inputs.
12127 // However, it means that with the current code, a BeginMenu() from outside another menu or a menu-bar won't be closable with the Left direction.
12128 ImGuiContext& g = *GImGui;
12129 ImGuiWindow* window = g.CurrentWindow;
12130 if (g.NavWindow && g.NavWindow->ParentWindow == window && g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet() && window->DC.LayoutType == ImGuiLayoutType_Vertical)
12131 {
12132 ClosePopupToLevel(g.OpenPopupStack.Size - 1);
12133 NavMoveRequestCancel();
12134 }
12135
12136 EndPopup();
12137 }
12138
12139 // Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
12140 void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags)
12141 {
12142 ImGuiContext& g = *GImGui;
12143
12144 int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]);
12145 BeginTooltipEx(0, true);
12146
12147 const char* text_end = text ? FindRenderedTextEnd(text, NULL) : text;
12148 if (text_end > text)
12149 {
12150 TextUnformatted(text, text_end);
12151 Separator();
12152 }
12153
12154 ImVec2 sz(g.FontSize * 3 + g.Style.FramePadding.y * 2, g.FontSize * 3 + g.Style.FramePadding.y * 2);
12155 ColorButton("##preview", ImVec4(col[0], col[1], col[2], col[3]), (flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)) | ImGuiColorEditFlags_NoTooltip, sz);
12156 SameLine();
12157 if (flags & ImGuiColorEditFlags_NoAlpha)
12158 Text("#%02X%02X%02X\nR: %d, G: %d, B: %d\n(%.3f, %.3f, %.3f)", cr, cg, cb, cr, cg, cb, col[0], col[1], col[2]);
12159 else
12160 Text("#%02X%02X%02X%02X\nR:%d, G:%d, B:%d, A:%d\n(%.3f, %.3f, %.3f, %.3f)", cr, cg, cb, ca, cr, cg, cb, ca, col[0], col[1], col[2], col[3]);
12161 EndTooltip();
12162 }
12163
12164 static inline ImU32 ImAlphaBlendColor(ImU32 col_a, ImU32 col_b)
12165 {
12166 float t = ((col_b >> IM_COL32_A_SHIFT) & 0xFF) / 255.f;
12167 int r = ImLerp((int)(col_a >> IM_COL32_R_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_R_SHIFT) & 0xFF, t);
12168 int g = ImLerp((int)(col_a >> IM_COL32_G_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_G_SHIFT) & 0xFF, t);
12169 int b = ImLerp((int)(col_a >> IM_COL32_B_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_B_SHIFT) & 0xFF, t);
12170 return IM_COL32(r, g, b, 0xFF);
12171 }
12172
12173 // NB: This is rather brittle and will show artifact when rounding this enabled if rounded corners overlap multiple cells. Caller currently responsible for avoiding that.
12174 // I spent a non reasonable amount of time trying to getting this right for ColorButton with rounding+anti-aliasing+ImGuiColorEditFlags_HalfAlphaPreview flag + various grid sizes and offsets, and eventually gave up... probably more reasonable to disable rounding alltogether.
12175 void ImGui::RenderColorRectWithAlphaCheckerboard(ImVec2 p_min, ImVec2 p_max, ImU32 col, float grid_step, ImVec2 grid_off, float rounding, int rounding_corners_flags)
12176 {
12177 ImGuiWindow* window = GetCurrentWindow();
12178 if (((col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT) < 0xFF)
12179 {
12180 ImU32 col_bg1 = GetColorU32(ImAlphaBlendColor(IM_COL32(204,204,204,255), col));
12181 ImU32 col_bg2 = GetColorU32(ImAlphaBlendColor(IM_COL32(128,128,128,255), col));
12182 window->DrawList->AddRectFilled(p_min, p_max, col_bg1, rounding, rounding_corners_flags);
12183
12184 int yi = 0;
12185 for (float y = p_min.y + grid_off.y; y < p_max.y; y += grid_step, yi++)
12186 {
12187 float y1 = ImClamp(y, p_min.y, p_max.y), y2 = ImMin(y + grid_step, p_max.y);
12188 if (y2 <= y1)
12189 continue;
12190 for (float x = p_min.x + grid_off.x + (yi & 1) * grid_step; x < p_max.x; x += grid_step * 2.0f)
12191 {
12192 float x1 = ImClamp(x, p_min.x, p_max.x), x2 = ImMin(x + grid_step, p_max.x);
12193 if (x2 <= x1)
12194 continue;
12195 int rounding_corners_flags_cell = 0;
12196 if (y1 <= p_min.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopRight; }
12197 if (y2 >= p_max.y) { if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotLeft; if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotRight; }
12198 rounding_corners_flags_cell &= rounding_corners_flags;
12199 window->DrawList->AddRectFilled(ImVec2(x1,y1), ImVec2(x2,y2), col_bg2, rounding_corners_flags_cell ? rounding : 0.0f, rounding_corners_flags_cell);
12200 }
12201 }
12202 }
12203 else
12204 {
12205 window->DrawList->AddRectFilled(p_min, p_max, col, rounding, rounding_corners_flags);
12206 }
12207 }
12208
12209 void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags)
12210 {
12211 ImGuiContext& g = *GImGui;
12212 if ((flags & ImGuiColorEditFlags__InputsMask) == 0)
12213 flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__InputsMask;
12214 if ((flags & ImGuiColorEditFlags__DataTypeMask) == 0)
12215 flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__DataTypeMask;
12216 if ((flags & ImGuiColorEditFlags__PickerMask) == 0)
12217 flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__PickerMask;
12218 IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__InputsMask))); // Check only 1 option is selected
12219 IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__DataTypeMask))); // Check only 1 option is selected
12220 IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask))); // Check only 1 option is selected
12221 g.ColorEditOptions = flags;
12222 }
12223
12224 // A little colored square. Return true when clicked.
12225 // FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip.
12226 // 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip.
12227 bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags, ImVec2 size)
12228 {
12229 ImGuiWindow* window = GetCurrentWindow();
12230 if (window->SkipItems)
12231 return false;
12232
12233 ImGuiContext& g = *GImGui;
12234 const ImGuiID id = window->GetID(desc_id);
12235 float default_size = GetFrameHeight();
12236 if (size.x == 0.0f)
12237 size.x = default_size;
12238 if (size.y == 0.0f)
12239 size.y = default_size;
12240 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
12241 ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f);
12242 if (!ItemAdd(bb, id))
12243 return false;
12244
12245 bool hovered, held;
12246 bool pressed = ButtonBehavior(bb, id, &hovered, &held);
12247
12248 if (flags & ImGuiColorEditFlags_NoAlpha)
12249 flags &= ~(ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf);
12250
12251 ImVec4 col_without_alpha(col.x, col.y, col.z, 1.0f);
12252 float grid_step = ImMin(size.x, size.y) / 2.99f;
12253 float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f);
12254 ImRect bb_inner = bb;
12255 float off = -0.75f; // The border (using Col_FrameBg) tends to look off when color is near-opaque and rounding is enabled. This offset seemed like a good middle ground to reduce those artifacts.
12256 bb_inner.Expand(off);
12257 if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col.w < 1.0f)
12258 {
12259 float mid_x = (float)(int)((bb_inner.Min.x + bb_inner.Max.x) * 0.5f + 0.5f);
12260 RenderColorRectWithAlphaCheckerboard(ImVec2(bb_inner.Min.x + grid_step, bb_inner.Min.y), bb_inner.Max, GetColorU32(col), grid_step, ImVec2(-grid_step + off, off), rounding, ImDrawCornerFlags_TopRight| ImDrawCornerFlags_BotRight);
12261 window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_without_alpha), rounding, ImDrawCornerFlags_TopLeft|ImDrawCornerFlags_BotLeft);
12262 }
12263 else
12264 {
12265 // Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha
12266 ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaPreview) ? col : col_without_alpha;
12267 if (col_source.w < 1.0f)
12268 RenderColorRectWithAlphaCheckerboard(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding);
12269 else
12270 window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding, ImDrawCornerFlags_All);
12271 }
12272 RenderNavHighlight(bb, id);
12273 if (g.Style.FrameBorderSize > 0.0f)
12274 RenderFrameBorder(bb.Min, bb.Max, rounding);
12275 else
12276 window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color button are often in need of some sort of border
12277
12278 // Drag and Drop Source
12279 // NB: The ActiveId test is merely an optional micro-optimization, BeginDragDropSource() does the same test.
12280 if (g.ActiveId == id && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropSource())
12281 {
12282 if (flags & ImGuiColorEditFlags_NoAlpha)
12283 SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F, &col, sizeof(float) * 3, ImGuiCond_Once);
12284 else
12285 SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, &col, sizeof(float) * 4, ImGuiCond_Once);
12286 ColorButton(desc_id, col, flags);
12287 SameLine();
12288 TextUnformatted("Color");
12289 EndDragDropSource();
12290 }
12291
12292 // Tooltip
12293 if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered)
12294 ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf));
12295
12296 if (pressed)
12297 MarkItemValueChanged(id);
12298
12299 return pressed;
12300 }
12301
12302 bool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags)
12303 {
12304 return ColorEdit4(label, col, flags | ImGuiColorEditFlags_NoAlpha);
12305 }
12306
12307 void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags)
12308 {
12309 bool allow_opt_inputs = !(flags & ImGuiColorEditFlags__InputsMask);
12310 bool allow_opt_datatype = !(flags & ImGuiColorEditFlags__DataTypeMask);
12311 if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup("context"))
12312 return;
12313 ImGuiContext& g = *GImGui;
12314 ImGuiColorEditFlags opts = g.ColorEditOptions;
12315 if (allow_opt_inputs)
12316 {
12317 if (RadioButton("RGB", (opts & ImGuiColorEditFlags_RGB) != 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_RGB;
12318 if (RadioButton("HSV", (opts & ImGuiColorEditFlags_HSV) != 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HSV;
12319 if (RadioButton("HEX", (opts & ImGuiColorEditFlags_HEX) != 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HEX;
12320 }
12321 if (allow_opt_datatype)
12322 {
12323 if (allow_opt_inputs) Separator();
12324 if (RadioButton("0..255", (opts & ImGuiColorEditFlags_Uint8) != 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Uint8;
12325 if (RadioButton("0.00..1.00", (opts & ImGuiColorEditFlags_Float) != 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Float;
12326 }
12327
12328 if (allow_opt_inputs || allow_opt_datatype)
12329 Separator();
12330 if (Button("Copy as..", ImVec2(-1,0)))
12331 OpenPopup("Copy");
12332 if (BeginPopup("Copy"))
12333 {
12334 int cr = IM_F32_TO_INT8_SAT(col[0]), cg = IM_F32_TO_INT8_SAT(col[1]), cb = IM_F32_TO_INT8_SAT(col[2]), ca = (flags & ImGuiColorEditFlags_NoAlpha) ? 255 : IM_F32_TO_INT8_SAT(col[3]);
12335 char buf[64];
12336 ImFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff, %.3ff)", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
12337 if (Selectable(buf))
12338 SetClipboardText(buf);
12339 ImFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d,%d)", cr, cg, cb, ca);
12340 if (Selectable(buf))
12341 SetClipboardText(buf);
12342 if (flags & ImGuiColorEditFlags_NoAlpha)
12343 ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X", cr, cg, cb);
12344 else
12345 ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X%02X", cr, cg, cb, ca);
12346 if (Selectable(buf))
12347 SetClipboardText(buf);
12348 EndPopup();
12349 }
12350
12351 g.ColorEditOptions = opts;
12352 EndPopup();
12353 }
12354
12355 static void ColorPickerOptionsPopup(ImGuiColorEditFlags flags, const float* ref_col)
12356 {
12357 bool allow_opt_picker = !(flags & ImGuiColorEditFlags__PickerMask);
12358 bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar);
12359 if ((!allow_opt_picker && !allow_opt_alpha_bar) || !ImGui::BeginPopup("context"))
12360 return;
12361 ImGuiContext& g = *GImGui;
12362 if (allow_opt_picker)
12363 {
12364 ImVec2 picker_size(g.FontSize * 8, ImMax(g.FontSize * 8 - (ImGui::GetFrameHeight() + g.Style.ItemInnerSpacing.x), 1.0f)); // FIXME: Picker size copied from main picker function
12365 ImGui::PushItemWidth(picker_size.x);
12366 for (int picker_type = 0; picker_type < 2; picker_type++)
12367 {
12368 // Draw small/thumbnail version of each picker type (over an invisible button for selection)
12369 if (picker_type > 0) ImGui::Separator();
12370 ImGui::PushID(picker_type);
12371 ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoInputs|ImGuiColorEditFlags_NoOptions|ImGuiColorEditFlags_NoLabel|ImGuiColorEditFlags_NoSidePreview|(flags & ImGuiColorEditFlags_NoAlpha);
12372 if (picker_type == 0) picker_flags |= ImGuiColorEditFlags_PickerHueBar;
12373 if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel;
12374 ImVec2 backup_pos = ImGui::GetCursorScreenPos();
12375 if (ImGui::Selectable("##selectable", false, 0, picker_size)) // By default, Selectable() is closing popup
12376 g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags__PickerMask) | (picker_flags & ImGuiColorEditFlags__PickerMask);
12377 ImGui::SetCursorScreenPos(backup_pos);
12378 ImVec4 dummy_ref_col;
12379 memcpy(&dummy_ref_col.x, ref_col, sizeof(float) * (picker_flags & ImGuiColorEditFlags_NoAlpha ? 3 : 4));
12380 ImGui::ColorPicker4("##dummypicker", &dummy_ref_col.x, picker_flags);
12381 ImGui::PopID();
12382 }
12383 ImGui::PopItemWidth();
12384 }
12385 if (allow_opt_alpha_bar)
12386 {
12387 if (allow_opt_picker) ImGui::Separator();
12388 ImGui::CheckboxFlags("Alpha Bar", (unsigned int*)&g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar);
12389 }
12390 ImGui::EndPopup();
12391 }
12392
12393 // Edit colors components (each component in 0.0f..1.0f range).
12394 // See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
12395 // With typical options: Left-click on colored square to open color picker. Right-click to open option menu. CTRL-Click over input fields to edit them and TAB to go to next item.
12396 bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags)
12397 {
12398 ImGuiWindow* window = GetCurrentWindow();
12399 if (window->SkipItems)
12400 return false;
12401
12402 ImGuiContext& g = *GImGui;
12403 const ImGuiStyle& style = g.Style;
12404 const float square_sz = GetFrameHeight();
12405 const float w_extra = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x);
12406 const float w_items_all = CalcItemWidth() - w_extra;
12407 const char* label_display_end = FindRenderedTextEnd(label);
12408
12409 BeginGroup();
12410 PushID(label);
12411
12412 // If we're not showing any slider there's no point in doing any HSV conversions
12413 const ImGuiColorEditFlags flags_untouched = flags;
12414 if (flags & ImGuiColorEditFlags_NoInputs)
12415 flags = (flags & (~ImGuiColorEditFlags__InputsMask)) | ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_NoOptions;
12416
12417 // Context menu: display and modify options (before defaults are applied)
12418 if (!(flags & ImGuiColorEditFlags_NoOptions))
12419 ColorEditOptionsPopup(col, flags);
12420
12421 // Read stored options
12422 if (!(flags & ImGuiColorEditFlags__InputsMask))
12423 flags |= (g.ColorEditOptions & ImGuiColorEditFlags__InputsMask);
12424 if (!(flags & ImGuiColorEditFlags__DataTypeMask))
12425 flags |= (g.ColorEditOptions & ImGuiColorEditFlags__DataTypeMask);
12426 if (!(flags & ImGuiColorEditFlags__PickerMask))
12427 flags |= (g.ColorEditOptions & ImGuiColorEditFlags__PickerMask);
12428 flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask));
12429
12430 const bool alpha = (flags & ImGuiColorEditFlags_NoAlpha) == 0;
12431 const bool hdr = (flags & ImGuiColorEditFlags_HDR) != 0;
12432 const int components = alpha ? 4 : 3;
12433
12434 // Convert to the formats we need
12435 float f[4] = { col[0], col[1], col[2], alpha ? col[3] : 1.0f };
12436 if (flags & ImGuiColorEditFlags_HSV)
12437 ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]);
12438 int i[4] = { IM_F32_TO_INT8_UNBOUND(f[0]), IM_F32_TO_INT8_UNBOUND(f[1]), IM_F32_TO_INT8_UNBOUND(f[2]), IM_F32_TO_INT8_UNBOUND(f[3]) };
12439
12440 bool value_changed = false;
12441 bool value_changed_as_float = false;
12442
12443 if ((flags & (ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_HSV)) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
12444 {
12445 // RGB/HSV 0..255 Sliders
12446 const float w_item_one = ImMax(1.0f, (float)(int)((w_items_all - (style.ItemInnerSpacing.x) * (components-1)) / (float)components));
12447 const float w_item_last = ImMax(1.0f, (float)(int)(w_items_all - (w_item_one + style.ItemInnerSpacing.x) * (components-1)));
12448
12449 const bool hide_prefix = (w_item_one <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x);
12450 const char* ids[4] = { "##X", "##Y", "##Z", "##W" };
12451 const char* fmt_table_int[3][4] =
12452 {
12453 { "%3d", "%3d", "%3d", "%3d" }, // Short display
12454 { "R:%3d", "G:%3d", "B:%3d", "A:%3d" }, // Long display for RGBA
12455 { "H:%3d", "S:%3d", "V:%3d", "A:%3d" } // Long display for HSVA
12456 };
12457 const char* fmt_table_float[3][4] =
12458 {
12459 { "%0.3f", "%0.3f", "%0.3f", "%0.3f" }, // Short display
12460 { "R:%0.3f", "G:%0.3f", "B:%0.3f", "A:%0.3f" }, // Long display for RGBA
12461 { "H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f" } // Long display for HSVA
12462 };
12463 const int fmt_idx = hide_prefix ? 0 : (flags & ImGuiColorEditFlags_HSV) ? 2 : 1;
12464
12465 PushItemWidth(w_item_one);
12466 for (int n = 0; n < components; n++)
12467 {
12468 if (n > 0)
12469 SameLine(0, style.ItemInnerSpacing.x);
12470 if (n + 1 == components)
12471 PushItemWidth(w_item_last);
12472 if (flags & ImGuiColorEditFlags_Float)
12473 value_changed = value_changed_as_float = value_changed | DragFloat(ids[n], &f[n], 1.0f/255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n]);
12474 else
12475 value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]);
12476 if (!(flags & ImGuiColorEditFlags_NoOptions))
12477 OpenPopupOnItemClick("context");
12478 }
12479 PopItemWidth();
12480 PopItemWidth();
12481 }
12482 else if ((flags & ImGuiColorEditFlags_HEX) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
12483 {
12484 // RGB Hexadecimal Input
12485 char buf[64];
12486 if (alpha)
12487 ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X%02X", ImClamp(i[0],0,255), ImClamp(i[1],0,255), ImClamp(i[2],0,255), ImClamp(i[3],0,255));
12488 else
12489 ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", ImClamp(i[0],0,255), ImClamp(i[1],0,255), ImClamp(i[2],0,255));
12490 PushItemWidth(w_items_all);
12491 if (InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase))
12492 {
12493 value_changed = true;
12494 char* p = buf;
12495 while (*p == '#' || ImCharIsBlankA(*p))
12496 p++;
12497 i[0] = i[1] = i[2] = i[3] = 0;
12498 if (alpha)
12499 sscanf(p, "%02X%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2], (unsigned int*)&i[3]); // Treat at unsigned (%X is unsigned)
12500 else
12501 sscanf(p, "%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]);
12502 }
12503 if (!(flags & ImGuiColorEditFlags_NoOptions))
12504 OpenPopupOnItemClick("context");
12505 PopItemWidth();
12506 }
12507
12508 ImGuiWindow* picker_active_window = NULL;
12509 if (!(flags & ImGuiColorEditFlags_NoSmallPreview))
12510 {
12511 if (!(flags & ImGuiColorEditFlags_NoInputs))
12512 SameLine(0, style.ItemInnerSpacing.x);
12513
12514 const ImVec4 col_v4(col[0], col[1], col[2], alpha ? col[3] : 1.0f);
12515 if (ColorButton("##ColorButton", col_v4, flags))
12516 {
12517 if (!(flags & ImGuiColorEditFlags_NoPicker))
12518 {
12519 // Store current color and open a picker
12520 g.ColorPickerRef = col_v4;
12521 OpenPopup("picker");
12522 SetNextWindowPos(window->DC.LastItemRect.GetBL() + ImVec2(-1,style.ItemSpacing.y));
12523 }
12524 }
12525 if (!(flags & ImGuiColorEditFlags_NoOptions))
12526 OpenPopupOnItemClick("context");
12527
12528 if (BeginPopup("picker"))
12529 {
12530 picker_active_window = g.CurrentWindow;
12531 if (label != label_display_end)
12532 {
12533 TextUnformatted(label, label_display_end);
12534 Separator();
12535 }
12536 ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar;
12537 ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf;
12538 PushItemWidth(square_sz * 12.0f); // Use 256 + bar sizes?
12539 value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x);
12540 PopItemWidth();
12541 EndPopup();
12542 }
12543 }
12544
12545 if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel))
12546 {
12547 SameLine(0, style.ItemInnerSpacing.x);
12548 TextUnformatted(label, label_display_end);
12549 }
12550
12551 // Convert back
12552 if (picker_active_window == NULL)
12553 {
12554 if (!value_changed_as_float)
12555 for (int n = 0; n < 4; n++)
12556 f[n] = i[n] / 255.0f;
12557 if (flags & ImGuiColorEditFlags_HSV)
12558 ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]);
12559 if (value_changed)
12560 {
12561 col[0] = f[0];
12562 col[1] = f[1];
12563 col[2] = f[2];
12564 if (alpha)
12565 col[3] = f[3];
12566 }
12567 }
12568
12569 PopID();
12570 EndGroup();
12571
12572 // Drag and Drop Target
12573 // NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test.
12574 if ((window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) && !(flags & ImGuiColorEditFlags_NoDragDrop) && BeginDragDropTarget())
12575 {
12576 if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
12577 {
12578 memcpy((float*)col, payload->Data, sizeof(float) * 3);
12579 value_changed = true;
12580 }
12581 if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
12582 {
12583 memcpy((float*)col, payload->Data, sizeof(float) * components);
12584 value_changed = true;
12585 }
12586 EndDragDropTarget();
12587 }
12588
12589 // When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4().
12590 if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window)
12591 window->DC.LastItemId = g.ActiveId;
12592
12593 if (value_changed)
12594 MarkItemValueChanged(window->DC.LastItemId);
12595
12596 return value_changed;
12597 }
12598
12599 bool ImGui::ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags)
12600 {
12601 float col4[4] = { col[0], col[1], col[2], 1.0f };
12602 if (!ColorPicker4(label, col4, flags | ImGuiColorEditFlags_NoAlpha))
12603 return false;
12604 col[0] = col4[0]; col[1] = col4[1]; col[2] = col4[2];
12605 return true;
12606 }
12607
12608 static void RenderArrowsForVerticalBar(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, float bar_w)
12609 {
12610 ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x + 1, pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Right, IM_COL32_BLACK);
12611 ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + half_sz.x, pos.y), half_sz, ImGuiDir_Right, IM_COL32_WHITE);
12612 ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x - 1, pos.y), ImVec2(half_sz.x + 2, half_sz.y + 1), ImGuiDir_Left, IM_COL32_BLACK);
12613 ImGui::RenderArrowPointingAt(draw_list, ImVec2(pos.x + bar_w - half_sz.x, pos.y), half_sz, ImGuiDir_Left, IM_COL32_WHITE);
12614 }
12615
12616 // ColorPicker
12617 // Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
12618 // FIXME: we adjust the big color square height based on item width, which may cause a flickering feedback loop (if automatic height makes a vertical scrollbar appears, affecting automatic width..)
12619 bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags, const float* ref_col)
12620 {
12621 ImGuiContext& g = *GImGui;
12622 ImGuiWindow* window = GetCurrentWindow();
12623 ImDrawList* draw_list = window->DrawList;
12624
12625 ImGuiStyle& style = g.Style;
12626 ImGuiIO& io = g.IO;
12627
12628 PushID(label);
12629 BeginGroup();
12630
12631 if (!(flags & ImGuiColorEditFlags_NoSidePreview))
12632 flags |= ImGuiColorEditFlags_NoSmallPreview;
12633
12634 // Context menu: display and store options.
12635 if (!(flags & ImGuiColorEditFlags_NoOptions))
12636 ColorPickerOptionsPopup(flags, col);
12637
12638 // Read stored options
12639 if (!(flags & ImGuiColorEditFlags__PickerMask))
12640 flags |= ((g.ColorEditOptions & ImGuiColorEditFlags__PickerMask) ? g.ColorEditOptions : ImGuiColorEditFlags__OptionsDefault) & ImGuiColorEditFlags__PickerMask;
12641 IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask))); // Check that only 1 is selected
12642 if (!(flags & ImGuiColorEditFlags_NoOptions))
12643 flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar);
12644
12645 // Setup
12646 int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4;
12647 bool alpha_bar = (flags & ImGuiColorEditFlags_AlphaBar) && !(flags & ImGuiColorEditFlags_NoAlpha);
12648 ImVec2 picker_pos = window->DC.CursorPos;
12649 float square_sz = GetFrameHeight();
12650 float bars_width = square_sz; // Arbitrary smallish width of Hue/Alpha picking bars
12651 float sv_picker_size = ImMax(bars_width * 1, CalcItemWidth() - (alpha_bar ? 2 : 1) * (bars_width + style.ItemInnerSpacing.x)); // Saturation/Value picking box
12652 float bar0_pos_x = picker_pos.x + sv_picker_size + style.ItemInnerSpacing.x;
12653 float bar1_pos_x = bar0_pos_x + bars_width + style.ItemInnerSpacing.x;
12654 float bars_triangles_half_sz = (float)(int)(bars_width * 0.20f);
12655
12656 float backup_initial_col[4];
12657 memcpy(backup_initial_col, col, components * sizeof(float));
12658
12659 float wheel_thickness = sv_picker_size * 0.08f;
12660 float wheel_r_outer = sv_picker_size * 0.50f;
12661 float wheel_r_inner = wheel_r_outer - wheel_thickness;
12662 ImVec2 wheel_center(picker_pos.x + (sv_picker_size + bars_width)*0.5f, picker_pos.y + sv_picker_size*0.5f);
12663
12664 // Note: the triangle is displayed rotated with triangle_pa pointing to Hue, but most coordinates stays unrotated for logic.
12665 float triangle_r = wheel_r_inner - (int)(sv_picker_size * 0.027f);
12666 ImVec2 triangle_pa = ImVec2(triangle_r, 0.0f); // Hue point.
12667 ImVec2 triangle_pb = ImVec2(triangle_r * -0.5f, triangle_r * -0.866025f); // Black point.
12668 ImVec2 triangle_pc = ImVec2(triangle_r * -0.5f, triangle_r * +0.866025f); // White point.
12669
12670 float H,S,V;
12671 ColorConvertRGBtoHSV(col[0], col[1], col[2], H, S, V);
12672
12673 bool value_changed = false, value_changed_h = false, value_changed_sv = false;
12674
12675 PushItemFlag(ImGuiItemFlags_NoNav, true);
12676 if (flags & ImGuiColorEditFlags_PickerHueWheel)
12677 {
12678 // Hue wheel + SV triangle logic
12679 InvisibleButton("hsv", ImVec2(sv_picker_size + style.ItemInnerSpacing.x + bars_width, sv_picker_size));
12680 if (IsItemActive())
12681 {
12682 ImVec2 initial_off = g.IO.MouseClickedPos[0] - wheel_center;
12683 ImVec2 current_off = g.IO.MousePos - wheel_center;
12684 float initial_dist2 = ImLengthSqr(initial_off);
12685 if (initial_dist2 >= (wheel_r_inner-1)*(wheel_r_inner-1) && initial_dist2 <= (wheel_r_outer+1)*(wheel_r_outer+1))
12686 {
12687 // Interactive with Hue wheel
12688 H = ImAtan2(current_off.y, current_off.x) / IM_PI*0.5f;
12689 if (H < 0.0f)
12690 H += 1.0f;
12691 value_changed = value_changed_h = true;
12692 }
12693 float cos_hue_angle = ImCos(-H * 2.0f * IM_PI);
12694 float sin_hue_angle = ImSin(-H * 2.0f * IM_PI);
12695 if (ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, ImRotate(initial_off, cos_hue_angle, sin_hue_angle)))
12696 {
12697 // Interacting with SV triangle
12698 ImVec2 current_off_unrotated = ImRotate(current_off, cos_hue_angle, sin_hue_angle);
12699 if (!ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated))
12700 current_off_unrotated = ImTriangleClosestPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated);
12701 float uu, vv, ww;
12702 ImTriangleBarycentricCoords(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated, uu, vv, ww);
12703 V = ImClamp(1.0f - vv, 0.0001f, 1.0f);
12704 S = ImClamp(uu / V, 0.0001f, 1.0f);
12705 value_changed = value_changed_sv = true;
12706 }
12707 }
12708 if (!(flags & ImGuiColorEditFlags_NoOptions))
12709 OpenPopupOnItemClick("context");
12710 }
12711 else if (flags & ImGuiColorEditFlags_PickerHueBar)
12712 {
12713 // SV rectangle logic
12714 InvisibleButton("sv", ImVec2(sv_picker_size, sv_picker_size));
12715 if (IsItemActive())
12716 {
12717 S = ImSaturate((io.MousePos.x - picker_pos.x) / (sv_picker_size-1));
12718 V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1));
12719 value_changed = value_changed_sv = true;
12720 }
12721 if (!(flags & ImGuiColorEditFlags_NoOptions))
12722 OpenPopupOnItemClick("context");
12723
12724 // Hue bar logic
12725 SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y));
12726 InvisibleButton("hue", ImVec2(bars_width, sv_picker_size));
12727 if (IsItemActive())
12728 {
12729 H = ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1));
12730 value_changed = value_changed_h = true;
12731 }
12732 }
12733
12734 // Alpha bar logic
12735 if (alpha_bar)
12736 {
12737 SetCursorScreenPos(ImVec2(bar1_pos_x, picker_pos.y));
12738 InvisibleButton("alpha", ImVec2(bars_width, sv_picker_size));
12739 if (IsItemActive())
12740 {
12741 col[3] = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1));
12742 value_changed = true;
12743 }
12744 }
12745 PopItemFlag(); // ImGuiItemFlags_NoNav
12746
12747 if (!(flags & ImGuiColorEditFlags_NoSidePreview))
12748 {
12749 SameLine(0, style.ItemInnerSpacing.x);
12750 BeginGroup();
12751 }
12752
12753 if (!(flags & ImGuiColorEditFlags_NoLabel))
12754 {
12755 const char* label_display_end = FindRenderedTextEnd(label);
12756 if (label != label_display_end)
12757 {
12758 if ((flags & ImGuiColorEditFlags_NoSidePreview))
12759 SameLine(0, style.ItemInnerSpacing.x);
12760 TextUnformatted(label, label_display_end);
12761 }
12762 }
12763
12764 if (!(flags & ImGuiColorEditFlags_NoSidePreview))
12765 {
12766 PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true);
12767 ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
12768 if ((flags & ImGuiColorEditFlags_NoLabel))
12769 Text("Current");
12770 ColorButton("##current", col_v4, (flags & (ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_AlphaPreview|ImGuiColorEditFlags_AlphaPreviewHalf|ImGuiColorEditFlags_NoTooltip)), ImVec2(square_sz * 3, square_sz * 2));
12771 if (ref_col != NULL)
12772 {
12773 Text("Original");
12774 ImVec4 ref_col_v4(ref_col[0], ref_col[1], ref_col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : ref_col[3]);
12775 if (ColorButton("##original", ref_col_v4, (flags & (ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_AlphaPreview|ImGuiColorEditFlags_AlphaPreviewHalf|ImGuiColorEditFlags_NoTooltip)), ImVec2(square_sz * 3, square_sz * 2)))
12776 {
12777 memcpy(col, ref_col, components * sizeof(float));
12778 value_changed = true;
12779 }
12780 }
12781 PopItemFlag();
12782 EndGroup();
12783 }
12784
12785 // Convert back color to RGB
12786 if (value_changed_h || value_changed_sv)
12787 ColorConvertHSVtoRGB(H >= 1.0f ? H - 10 * 1e-6f : H, S > 0.0f ? S : 10*1e-6f, V > 0.0f ? V : 1e-6f, col[0], col[1], col[2]);
12788
12789 // R,G,B and H,S,V slider color editor
12790 if ((flags & ImGuiColorEditFlags_NoInputs) == 0)
12791 {
12792 PushItemWidth((alpha_bar ? bar1_pos_x : bar0_pos_x) + bars_width - picker_pos.x);
12793 ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf;
12794 ImGuiColorEditFlags sub_flags = (flags & sub_flags_to_forward) | ImGuiColorEditFlags_NoPicker;
12795 if (flags & ImGuiColorEditFlags_RGB || (flags & ImGuiColorEditFlags__InputsMask) == 0)
12796 value_changed |= ColorEdit4("##rgb", col, sub_flags | ImGuiColorEditFlags_RGB);
12797 if (flags & ImGuiColorEditFlags_HSV || (flags & ImGuiColorEditFlags__InputsMask) == 0)
12798 value_changed |= ColorEdit4("##hsv", col, sub_flags | ImGuiColorEditFlags_HSV);
12799 if (flags & ImGuiColorEditFlags_HEX || (flags & ImGuiColorEditFlags__InputsMask) == 0)
12800 value_changed |= ColorEdit4("##hex", col, sub_flags | ImGuiColorEditFlags_HEX);
12801 PopItemWidth();
12802 }
12803
12804 // Try to cancel hue wrap (after ColorEdit), if any
12805 if (value_changed)
12806 {
12807 float new_H, new_S, new_V;
12808 ColorConvertRGBtoHSV(col[0], col[1], col[2], new_H, new_S, new_V);
12809 if (new_H <= 0 && H > 0)
12810 {
12811 if (new_V <= 0 && V != new_V)
12812 ColorConvertHSVtoRGB(H, S, new_V <= 0 ? V * 0.5f : new_V, col[0], col[1], col[2]);
12813 else if (new_S <= 0)
12814 ColorConvertHSVtoRGB(H, new_S <= 0 ? S * 0.5f : new_S, new_V, col[0], col[1], col[2]);
12815 }
12816 }
12817
12818 ImVec4 hue_color_f(1, 1, 1, 1); ColorConvertHSVtoRGB(H, 1, 1, hue_color_f.x, hue_color_f.y, hue_color_f.z);
12819 ImU32 hue_color32 = ColorConvertFloat4ToU32(hue_color_f);
12820 ImU32 col32_no_alpha = ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 1.0f));
12821
12822 const ImU32 hue_colors[6+1] = { IM_COL32(255,0,0,255), IM_COL32(255,255,0,255), IM_COL32(0,255,0,255), IM_COL32(0,255,255,255), IM_COL32(0,0,255,255), IM_COL32(255,0,255,255), IM_COL32(255,0,0,255) };
12823 ImVec2 sv_cursor_pos;
12824
12825 if (flags & ImGuiColorEditFlags_PickerHueWheel)
12826 {
12827 // Render Hue Wheel
12828 const float aeps = 1.5f / wheel_r_outer; // Half a pixel arc length in radians (2pi cancels out).
12829 const int segment_per_arc = ImMax(4, (int)wheel_r_outer / 12);
12830 for (int n = 0; n < 6; n++)
12831 {
12832 const float a0 = (n) /6.0f * 2.0f * IM_PI - aeps;
12833 const float a1 = (n+1.0f)/6.0f * 2.0f * IM_PI + aeps;
12834 const int vert_start_idx = draw_list->VtxBuffer.Size;
12835 draw_list->PathArcTo(wheel_center, (wheel_r_inner + wheel_r_outer)*0.5f, a0, a1, segment_per_arc);
12836 draw_list->PathStroke(IM_COL32_WHITE, false, wheel_thickness);
12837 const int vert_end_idx = draw_list->VtxBuffer.Size;
12838
12839 // Paint colors over existing vertices
12840 ImVec2 gradient_p0(wheel_center.x + ImCos(a0) * wheel_r_inner, wheel_center.y + ImSin(a0) * wheel_r_inner);
12841 ImVec2 gradient_p1(wheel_center.x + ImCos(a1) * wheel_r_inner, wheel_center.y + ImSin(a1) * wheel_r_inner);
12842 ShadeVertsLinearColorGradientKeepAlpha(draw_list->VtxBuffer.Data + vert_start_idx, draw_list->VtxBuffer.Data + vert_end_idx, gradient_p0, gradient_p1, hue_colors[n], hue_colors[n+1]);
12843 }
12844
12845 // Render Cursor + preview on Hue Wheel
12846 float cos_hue_angle = ImCos(H * 2.0f * IM_PI);
12847 float sin_hue_angle = ImSin(H * 2.0f * IM_PI);
12848 ImVec2 hue_cursor_pos(wheel_center.x + cos_hue_angle * (wheel_r_inner+wheel_r_outer)*0.5f, wheel_center.y + sin_hue_angle * (wheel_r_inner+wheel_r_outer)*0.5f);
12849 float hue_cursor_rad = value_changed_h ? wheel_thickness * 0.65f : wheel_thickness * 0.55f;
12850 int hue_cursor_segments = ImClamp((int)(hue_cursor_rad / 1.4f), 9, 32);
12851 draw_list->AddCircleFilled(hue_cursor_pos, hue_cursor_rad, hue_color32, hue_cursor_segments);
12852 draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad+1, IM_COL32(128,128,128,255), hue_cursor_segments);
12853 draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad, IM_COL32_WHITE, hue_cursor_segments);
12854
12855 // Render SV triangle (rotated according to hue)
12856 ImVec2 tra = wheel_center + ImRotate(triangle_pa, cos_hue_angle, sin_hue_angle);
12857 ImVec2 trb = wheel_center + ImRotate(triangle_pb, cos_hue_angle, sin_hue_angle);
12858 ImVec2 trc = wheel_center + ImRotate(triangle_pc, cos_hue_angle, sin_hue_angle);
12859 ImVec2 uv_white = GetFontTexUvWhitePixel();
12860 draw_list->PrimReserve(6, 6);
12861 draw_list->PrimVtx(tra, uv_white, hue_color32);
12862 draw_list->PrimVtx(trb, uv_white, hue_color32);
12863 draw_list->PrimVtx(trc, uv_white, IM_COL32_WHITE);
12864 draw_list->PrimVtx(tra, uv_white, IM_COL32_BLACK_TRANS);
12865 draw_list->PrimVtx(trb, uv_white, IM_COL32_BLACK);
12866 draw_list->PrimVtx(trc, uv_white, IM_COL32_BLACK_TRANS);
12867 draw_list->AddTriangle(tra, trb, trc, IM_COL32(128,128,128,255), 1.5f);
12868 sv_cursor_pos = ImLerp(ImLerp(trc, tra, ImSaturate(S)), trb, ImSaturate(1 - V));
12869 }
12870 else if (flags & ImGuiColorEditFlags_PickerHueBar)
12871 {
12872 // Render SV Square
12873 draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), IM_COL32_WHITE, hue_color32, hue_color32, IM_COL32_WHITE);
12874 draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), IM_COL32_BLACK_TRANS, IM_COL32_BLACK_TRANS, IM_COL32_BLACK, IM_COL32_BLACK);
12875 RenderFrameBorder(picker_pos, picker_pos + ImVec2(sv_picker_size,sv_picker_size), 0.0f);
12876 sv_cursor_pos.x = ImClamp((float)(int)(picker_pos.x + ImSaturate(S) * sv_picker_size + 0.5f), picker_pos.x + 2, picker_pos.x + sv_picker_size - 2); // Sneakily prevent the circle to stick out too much
12877 sv_cursor_pos.y = ImClamp((float)(int)(picker_pos.y + ImSaturate(1 - V) * sv_picker_size + 0.5f), picker_pos.y + 2, picker_pos.y + sv_picker_size - 2);
12878
12879 // Render Hue Bar
12880 for (int i = 0; i < 6; ++i)
12881 draw_list->AddRectFilledMultiColor(ImVec2(bar0_pos_x, picker_pos.y + i * (sv_picker_size / 6)), ImVec2(bar0_pos_x + bars_width, picker_pos.y + (i + 1) * (sv_picker_size / 6)), hue_colors[i], hue_colors[i], hue_colors[i + 1], hue_colors[i + 1]);
12882 float bar0_line_y = (float)(int)(picker_pos.y + H * sv_picker_size + 0.5f);
12883 RenderFrameBorder(ImVec2(bar0_pos_x, picker_pos.y), ImVec2(bar0_pos_x + bars_width, picker_pos.y + sv_picker_size), 0.0f);
12884 RenderArrowsForVerticalBar(draw_list, ImVec2(bar0_pos_x - 1, bar0_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f);
12885 }
12886
12887 // Render cursor/preview circle (clamp S/V within 0..1 range because floating points colors may lead HSV values to be out of range)
12888 float sv_cursor_rad = value_changed_sv ? 10.0f : 6.0f;
12889 draw_list->AddCircleFilled(sv_cursor_pos, sv_cursor_rad, col32_no_alpha, 12);
12890 draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad+1, IM_COL32(128,128,128,255), 12);
12891 draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad, IM_COL32_WHITE, 12);
12892
12893 // Render alpha bar
12894 if (alpha_bar)
12895 {
12896 float alpha = ImSaturate(col[3]);
12897 ImRect bar1_bb(bar1_pos_x, picker_pos.y, bar1_pos_x + bars_width, picker_pos.y + sv_picker_size);
12898 RenderColorRectWithAlphaCheckerboard(bar1_bb.Min, bar1_bb.Max, IM_COL32(0,0,0,0), bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f));
12899 draw_list->AddRectFilledMultiColor(bar1_bb.Min, bar1_bb.Max, col32_no_alpha, col32_no_alpha, col32_no_alpha & ~IM_COL32_A_MASK, col32_no_alpha & ~IM_COL32_A_MASK);
12900 float bar1_line_y = (float)(int)(picker_pos.y + (1.0f - alpha) * sv_picker_size + 0.5f);
12901 RenderFrameBorder(bar1_bb.Min, bar1_bb.Max, 0.0f);
12902 RenderArrowsForVerticalBar(draw_list, ImVec2(bar1_pos_x - 1, bar1_line_y), ImVec2(bars_triangles_half_sz + 1, bars_triangles_half_sz), bars_width + 2.0f);
12903 }
12904
12905 EndGroup();
12906
12907 if (value_changed && memcmp(backup_initial_col, col, components * sizeof(float)) == 0)
12908 value_changed = false;
12909 if (value_changed)
12910 MarkItemValueChanged(window->DC.LastItemId);
12911
12912 PopID();
12913
12914 return value_changed;
12915 }
12916
12917 // Horizontal/vertical separating line
12918 void ImGui::Separator()
12919 {
12920 ImGuiWindow* window = GetCurrentWindow();
12921 if (window->SkipItems)
12922 return;
12923 ImGuiContext& g = *GImGui;
12924
12925 // Those flags should eventually be overridable by the user
12926 ImGuiSeparatorFlags flags = (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal;
12927 IM_ASSERT(ImIsPowerOfTwo((int)(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical)))); // Check that only 1 option is selected
12928 if (flags & ImGuiSeparatorFlags_Vertical)
12929 {
12930 VerticalSeparator();
12931 return;
12932 }
12933
12934 // Horizontal Separator
12935 if (window->DC.ColumnsSet)
12936 PopClipRect();
12937
12938 float x1 = window->Pos.x;
12939 float x2 = window->Pos.x + window->Size.x;
12940 if (!window->DC.GroupStack.empty())
12941 x1 += window->DC.IndentX;
12942
12943 const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y+1.0f));
12944 ItemSize(ImVec2(0.0f, 0.0f)); // NB: we don't provide our width so that it doesn't get feed back into AutoFit, we don't provide height to not alter layout.
12945 if (!ItemAdd(bb, 0))
12946 {
12947 if (window->DC.ColumnsSet)
12948 PushColumnClipRect();
12949 return;
12950 }
12951
12952 window->DrawList->AddLine(bb.Min, ImVec2(bb.Max.x,bb.Min.y), GetColorU32(ImGuiCol_Separator));
12953
12954 if (g.LogEnabled)
12955 LogRenderedText(NULL, IM_NEWLINE "--------------------------------");
12956
12957 if (window->DC.ColumnsSet)
12958 {
12959 PushColumnClipRect();
12960 window->DC.ColumnsSet->LineMinY = window->DC.CursorPos.y;
12961 }
12962 }
12963
12964 void ImGui::VerticalSeparator()
12965 {
12966 ImGuiWindow* window = GetCurrentWindow();
12967 if (window->SkipItems)
12968 return;
12969 ImGuiContext& g = *GImGui;
12970
12971 float y1 = window->DC.CursorPos.y;
12972 float y2 = window->DC.CursorPos.y + window->DC.CurrentLineHeight;
12973 const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + 1.0f, y2));
12974 ItemSize(ImVec2(bb.GetWidth(), 0.0f));
12975 if (!ItemAdd(bb, 0))
12976 return;
12977
12978 window->DrawList->AddLine(ImVec2(bb.Min.x, bb.Min.y), ImVec2(bb.Min.x, bb.Max.y), GetColorU32(ImGuiCol_Separator));
12979 if (g.LogEnabled)
12980 LogText(" |");
12981 }
12982
12983 // Using 'hover_visibility_delay' allows us to hide the highlight and mouse cursor for a short time, which can be convenient to reduce visual noise.
12984 bool ImGui::SplitterBehavior(const ImRect& bb, ImGuiID id, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend, float hover_visibility_delay)
12985 {
12986 ImGuiContext& g = *GImGui;
12987 ImGuiWindow* window = g.CurrentWindow;
12988
12989 const ImGuiItemFlags item_flags_backup = window->DC.ItemFlags;
12990 window->DC.ItemFlags |= ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus;
12991 bool item_add = ItemAdd(bb, id);
12992 window->DC.ItemFlags = item_flags_backup;
12993 if (!item_add)
12994 return false;
12995
12996 bool hovered, held;
12997 ImRect bb_interact = bb;
12998 bb_interact.Expand(axis == ImGuiAxis_Y ? ImVec2(0.0f, hover_extend) : ImVec2(hover_extend, 0.0f));
12999 ButtonBehavior(bb_interact, id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap);
13000 if (g.ActiveId != id)
13001 SetItemAllowOverlap();
13002
13003 if (held || (g.HoveredId == id && g.HoveredIdPreviousFrame == id && g.HoveredIdTimer >= hover_visibility_delay))
13004 SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW);
13005
13006 ImRect bb_render = bb;
13007 if (held)
13008 {
13009 ImVec2 mouse_delta_2d = g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min;
13010 float mouse_delta = (axis == ImGuiAxis_Y) ? mouse_delta_2d.y : mouse_delta_2d.x;
13011
13012 // Minimum pane size
13013 if (mouse_delta < min_size1 - *size1)
13014 mouse_delta = min_size1 - *size1;
13015 if (mouse_delta > *size2 - min_size2)
13016 mouse_delta = *size2 - min_size2;
13017
13018 // Apply resize
13019 *size1 += mouse_delta;
13020 *size2 -= mouse_delta;
13021 bb_render.Translate((axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta));
13022
13023 MarkItemValueChanged(id);
13024 }
13025
13026 // Render
13027 const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : (hovered && g.HoveredIdTimer >= hover_visibility_delay) ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator);
13028 window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, col, g.Style.FrameRounding);
13029
13030 return held;
13031 }
13032
13033 void ImGui::Spacing()
13034 {
13035 ImGuiWindow* window = GetCurrentWindow();
13036 if (window->SkipItems)
13037 return;
13038 ItemSize(ImVec2(0,0));
13039 }
13040
13041 void ImGui::Dummy(const ImVec2& size)
13042 {
13043 ImGuiWindow* window = GetCurrentWindow();
13044 if (window->SkipItems)
13045 return;
13046
13047 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
13048 ItemSize(bb);
13049 ItemAdd(bb, 0);
13050 }
13051
13052 bool ImGui::IsRectVisible(const ImVec2& size)
13053 {
13054 ImGuiWindow* window = GetCurrentWindowRead();
13055 return window->ClipRect.Overlaps(ImRect(window->DC.CursorPos, window->DC.CursorPos + size));
13056 }
13057
13058 bool ImGui::IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max)
13059 {
13060 ImGuiWindow* window = GetCurrentWindowRead();
13061 return window->ClipRect.Overlaps(ImRect(rect_min, rect_max));
13062 }
13063
13064 // 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.)
13065 void ImGui::BeginGroup()
13066 {
13067 ImGuiContext& g = *GImGui;
13068 ImGuiWindow* window = GetCurrentWindow();
13069
13070 window->DC.GroupStack.resize(window->DC.GroupStack.Size + 1);
13071 ImGuiGroupData& group_data = window->DC.GroupStack.back();
13072 group_data.BackupCursorPos = window->DC.CursorPos;
13073 group_data.BackupCursorMaxPos = window->DC.CursorMaxPos;
13074 group_data.BackupIndentX = window->DC.IndentX;
13075 group_data.BackupGroupOffsetX = window->DC.GroupOffsetX;
13076 group_data.BackupCurrentLineHeight = window->DC.CurrentLineHeight;
13077 group_data.BackupCurrentLineTextBaseOffset = window->DC.CurrentLineTextBaseOffset;
13078 group_data.BackupLogLinePosY = window->DC.LogLinePosY;
13079 group_data.BackupActiveIdIsAlive = g.ActiveIdIsAlive;
13080 group_data.BackupActiveIdPreviousFrameIsAlive = g.ActiveIdPreviousFrameIsAlive;
13081 group_data.AdvanceCursor = true;
13082
13083 window->DC.GroupOffsetX = window->DC.CursorPos.x - window->Pos.x - window->DC.ColumnsOffsetX;
13084 window->DC.IndentX = window->DC.GroupOffsetX;
13085 window->DC.CursorMaxPos = window->DC.CursorPos;
13086 window->DC.CurrentLineHeight = 0.0f;
13087 window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f; // To enforce Log carriage return
13088 }
13089
13090 void ImGui::EndGroup()
13091 {
13092 ImGuiContext& g = *GImGui;
13093 ImGuiWindow* window = GetCurrentWindow();
13094 IM_ASSERT(!window->DC.GroupStack.empty()); // Mismatched BeginGroup()/EndGroup() calls
13095
13096 ImGuiGroupData& group_data = window->DC.GroupStack.back();
13097
13098 ImRect group_bb(group_data.BackupCursorPos, window->DC.CursorMaxPos);
13099 group_bb.Max = ImMax(group_bb.Min, group_bb.Max);
13100
13101 window->DC.CursorPos = group_data.BackupCursorPos;
13102 window->DC.CursorMaxPos = ImMax(group_data.BackupCursorMaxPos, window->DC.CursorMaxPos);
13103 window->DC.IndentX = group_data.BackupIndentX;
13104 window->DC.GroupOffsetX = group_data.BackupGroupOffsetX;
13105 window->DC.CurrentLineHeight = group_data.BackupCurrentLineHeight;
13106 window->DC.CurrentLineTextBaseOffset = group_data.BackupCurrentLineTextBaseOffset;
13107 window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f; // To enforce Log carriage return
13108
13109 if (group_data.AdvanceCursor)
13110 {
13111 window->DC.CurrentLineTextBaseOffset = ImMax(window->DC.PrevLineTextBaseOffset, group_data.BackupCurrentLineTextBaseOffset); // FIXME: Incorrect, we should grab the base offset from the *first line* of the group but it is hard to obtain now.
13112 ItemSize(group_bb.GetSize(), group_data.BackupCurrentLineTextBaseOffset);
13113 ItemAdd(group_bb, 0);
13114 }
13115
13116 // 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.
13117 // It would be be neater if we replaced window.DC.LastItemId by e.g. 'bool LastItemIsActive', but put a little more burden on individual widgets.
13118 // (and if you grep for LastItemId you'll notice it is only used in that context.
13119 if (!group_data.BackupActiveIdIsAlive && g.ActiveIdIsAlive && g.ActiveId) // && g.ActiveIdWindow->RootWindow == window->RootWindow)
13120 window->DC.LastItemId = g.ActiveId;
13121 else if (!group_data.BackupActiveIdPreviousFrameIsAlive && g.ActiveIdPreviousFrameIsAlive) // && g.ActiveIdPreviousFrameWindow->RootWindow == window->RootWindow)
13122 window->DC.LastItemId = g.ActiveIdPreviousFrame;
13123 window->DC.LastItemRect = group_bb;
13124
13125 window->DC.GroupStack.pop_back();
13126
13127 //window->DrawList->AddRect(group_bb.Min, group_bb.Max, IM_COL32(255,0,255,255)); // [Debug]
13128 }
13129
13130 // Gets back to previous line and continue with horizontal layout
13131 // pos_x == 0 : follow right after previous item
13132 // pos_x != 0 : align to specified x position (relative to window/group left)
13133 // spacing_w < 0 : use default spacing if pos_x == 0, no spacing if pos_x != 0
13134 // spacing_w >= 0 : enforce spacing amount
13135 void ImGui::SameLine(float pos_x, float spacing_w)
13136 {
13137 ImGuiWindow* window = GetCurrentWindow();
13138 if (window->SkipItems)
13139 return;
13140
13141 ImGuiContext& g = *GImGui;
13142 if (pos_x != 0.0f)
13143 {
13144 if (spacing_w < 0.0f) spacing_w = 0.0f;
13145 window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + pos_x + spacing_w + window->DC.GroupOffsetX + window->DC.ColumnsOffsetX;
13146 window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y;
13147 }
13148 else
13149 {
13150 if (spacing_w < 0.0f) spacing_w = g.Style.ItemSpacing.x;
13151 window->DC.CursorPos.x = window->DC.CursorPosPrevLine.x + spacing_w;
13152 window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y;
13153 }
13154 window->DC.CurrentLineHeight = window->DC.PrevLineHeight;
13155 window->DC.CurrentLineTextBaseOffset = window->DC.PrevLineTextBaseOffset;
13156 }
13157
13158 void ImGui::NewLine()
13159 {
13160 ImGuiWindow* window = GetCurrentWindow();
13161 if (window->SkipItems)
13162 return;
13163
13164 ImGuiContext& g = *GImGui;
13165 const ImGuiLayoutType backup_layout_type = window->DC.LayoutType;
13166 window->DC.LayoutType = ImGuiLayoutType_Vertical;
13167 if (window->DC.CurrentLineHeight > 0.0f) // In the event that we are on a line with items that is smaller that FontSize high, we will preserve its height.
13168 ItemSize(ImVec2(0,0));
13169 else
13170 ItemSize(ImVec2(0.0f, g.FontSize));
13171 window->DC.LayoutType = backup_layout_type;
13172 }
13173
13174 void ImGui::NextColumn()
13175 {
13176 ImGuiWindow* window = GetCurrentWindow();
13177 if (window->SkipItems || window->DC.ColumnsSet == NULL)
13178 return;
13179
13180 ImGuiContext& g = *GImGui;
13181 PopItemWidth();
13182 PopClipRect();
13183
13184 ImGuiColumnsSet* columns = window->DC.ColumnsSet;
13185 columns->LineMaxY = ImMax(columns->LineMaxY, window->DC.CursorPos.y);
13186 if (++columns->Current < columns->Count)
13187 {
13188 // Columns 1+ cancel out IndentX
13189 window->DC.ColumnsOffsetX = GetColumnOffset(columns->Current) - window->DC.IndentX + g.Style.ItemSpacing.x;
13190 window->DrawList->ChannelsSetCurrent(columns->Current);
13191 }
13192 else
13193 {
13194 window->DC.ColumnsOffsetX = 0.0f;
13195 window->DrawList->ChannelsSetCurrent(0);
13196 columns->Current = 0;
13197 columns->LineMinY = columns->LineMaxY;
13198 }
13199 window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX);
13200 window->DC.CursorPos.y = columns->LineMinY;
13201 window->DC.CurrentLineHeight = 0.0f;
13202 window->DC.CurrentLineTextBaseOffset = 0.0f;
13203
13204 PushColumnClipRect();
13205 PushItemWidth(GetColumnWidth() * 0.65f); // FIXME: Move on columns setup
13206 }
13207
13208 int ImGui::GetColumnIndex()
13209 {
13210 ImGuiWindow* window = GetCurrentWindowRead();
13211 return window->DC.ColumnsSet ? window->DC.ColumnsSet->Current : 0;
13212 }
13213
13214 int ImGui::GetColumnsCount()
13215 {
13216 ImGuiWindow* window = GetCurrentWindowRead();
13217 return window->DC.ColumnsSet ? window->DC.ColumnsSet->Count : 1;
13218 }
13219
13220 static float OffsetNormToPixels(const ImGuiColumnsSet* columns, float offset_norm)
13221 {
13222 return offset_norm * (columns->MaxX - columns->MinX);
13223 }
13224
13225 static float PixelsToOffsetNorm(const ImGuiColumnsSet* columns, float offset)
13226 {
13227 return offset / (columns->MaxX - columns->MinX);
13228 }
13229
13230 static inline float GetColumnsRectHalfWidth() { return 4.0f; }
13231
13232 static float GetDraggedColumnOffset(ImGuiColumnsSet* columns, int column_index)
13233 {
13234 // Active (dragged) column always follow mouse. The reason we need this is that dragging a column to the right edge of an auto-resizing
13235 // window creates a feedback loop because we store normalized positions. So while dragging we enforce absolute positioning.
13236 ImGuiContext& g = *GImGui;
13237 ImGuiWindow* window = g.CurrentWindow;
13238 IM_ASSERT(column_index > 0); // We are not supposed to drag column 0.
13239 IM_ASSERT(g.ActiveId == columns->ID + ImGuiID(column_index));
13240
13241 float x = g.IO.MousePos.x - g.ActiveIdClickOffset.x + GetColumnsRectHalfWidth() - window->Pos.x;
13242 x = ImMax(x, ImGui::GetColumnOffset(column_index - 1) + g.Style.ColumnsMinSpacing);
13243 if ((columns->Flags & ImGuiColumnsFlags_NoPreserveWidths))
13244 x = ImMin(x, ImGui::GetColumnOffset(column_index + 1) - g.Style.ColumnsMinSpacing);
13245
13246 return x;
13247 }
13248
13249 float ImGui::GetColumnOffset(int column_index)
13250 {
13251 ImGuiWindow* window = GetCurrentWindowRead();
13252 ImGuiColumnsSet* columns = window->DC.ColumnsSet;
13253 IM_ASSERT(columns != NULL);
13254
13255 if (column_index < 0)
13256 column_index = columns->Current;
13257 IM_ASSERT(column_index < columns->Columns.Size);
13258
13259 const float t = columns->Columns[column_index].OffsetNorm;
13260 const float x_offset = ImLerp(columns->MinX, columns->MaxX, t);
13261 return x_offset;
13262 }
13263
13264 static float GetColumnWidthEx(ImGuiColumnsSet* columns, int column_index, bool before_resize = false)
13265 {
13266 if (column_index < 0)
13267 column_index = columns->Current;
13268
13269 float offset_norm;
13270 if (before_resize)
13271 offset_norm = columns->Columns[column_index + 1].OffsetNormBeforeResize - columns->Columns[column_index].OffsetNormBeforeResize;
13272 else
13273 offset_norm = columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm;
13274 return OffsetNormToPixels(columns, offset_norm);
13275 }
13276
13277 float ImGui::GetColumnWidth(int column_index)
13278 {
13279 ImGuiWindow* window = GetCurrentWindowRead();
13280 ImGuiColumnsSet* columns = window->DC.ColumnsSet;
13281 IM_ASSERT(columns != NULL);
13282
13283 if (column_index < 0)
13284 column_index = columns->Current;
13285 return OffsetNormToPixels(columns, columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm);
13286 }
13287
13288 void ImGui::SetColumnOffset(int column_index, float offset)
13289 {
13290 ImGuiContext& g = *GImGui;
13291 ImGuiWindow* window = g.CurrentWindow;
13292 ImGuiColumnsSet* columns = window->DC.ColumnsSet;
13293 IM_ASSERT(columns != NULL);
13294
13295 if (column_index < 0)
13296 column_index = columns->Current;
13297 IM_ASSERT(column_index < columns->Columns.Size);
13298
13299 const bool preserve_width = !(columns->Flags & ImGuiColumnsFlags_NoPreserveWidths) && (column_index < columns->Count-1);
13300 const float width = preserve_width ? GetColumnWidthEx(columns, column_index, columns->IsBeingResized) : 0.0f;
13301
13302 if (!(columns->Flags & ImGuiColumnsFlags_NoForceWithinWindow))
13303 offset = ImMin(offset, columns->MaxX - g.Style.ColumnsMinSpacing * (columns->Count - column_index));
13304 columns->Columns[column_index].OffsetNorm = PixelsToOffsetNorm(columns, offset - columns->MinX);
13305
13306 if (preserve_width)
13307 SetColumnOffset(column_index + 1, offset + ImMax(g.Style.ColumnsMinSpacing, width));
13308 }
13309
13310 void ImGui::SetColumnWidth(int column_index, float width)
13311 {
13312 ImGuiWindow* window = GetCurrentWindowRead();
13313 ImGuiColumnsSet* columns = window->DC.ColumnsSet;
13314 IM_ASSERT(columns != NULL);
13315
13316 if (column_index < 0)
13317 column_index = columns->Current;
13318 SetColumnOffset(column_index + 1, GetColumnOffset(column_index) + width);
13319 }
13320
13321 void ImGui::PushColumnClipRect(int column_index)
13322 {
13323 ImGuiWindow* window = GetCurrentWindowRead();
13324 ImGuiColumnsSet* columns = window->DC.ColumnsSet;
13325 if (column_index < 0)
13326 column_index = columns->Current;
13327
13328 PushClipRect(columns->Columns[column_index].ClipRect.Min, columns->Columns[column_index].ClipRect.Max, false);
13329 }
13330
13331 static ImGuiColumnsSet* FindOrAddColumnsSet(ImGuiWindow* window, ImGuiID id)
13332 {
13333 for (int n = 0; n < window->ColumnsStorage.Size; n++)
13334 if (window->ColumnsStorage[n].ID == id)
13335 return &window->ColumnsStorage[n];
13336
13337 window->ColumnsStorage.push_back(ImGuiColumnsSet());
13338 ImGuiColumnsSet* columns = &window->ColumnsStorage.back();
13339 columns->ID = id;
13340 return columns;
13341 }
13342
13343 void ImGui::BeginColumns(const char* str_id, int columns_count, ImGuiColumnsFlags flags)
13344 {
13345 ImGuiContext& g = *GImGui;
13346 ImGuiWindow* window = GetCurrentWindow();
13347
13348 IM_ASSERT(columns_count > 1);
13349 IM_ASSERT(window->DC.ColumnsSet == NULL); // Nested columns are currently not supported
13350
13351 // Differentiate column ID with an arbitrary prefix for cases where users name their columns set the same as another widget.
13352 // In addition, when an identifier isn't explicitly provided we include the number of columns in the hash to make it uniquer.
13353 PushID(0x11223347 + (str_id ? 0 : columns_count));
13354 ImGuiID id = window->GetID(str_id ? str_id : "columns");
13355 PopID();
13356
13357 // Acquire storage for the columns set
13358 ImGuiColumnsSet* columns = FindOrAddColumnsSet(window, id);
13359 IM_ASSERT(columns->ID == id);
13360 columns->Current = 0;
13361 columns->Count = columns_count;
13362 columns->Flags = flags;
13363 window->DC.ColumnsSet = columns;
13364
13365 // Set state for first column
13366 const float content_region_width = (window->SizeContentsExplicit.x != 0.0f) ? (window->SizeContentsExplicit.x) : (window->InnerClipRect.Max.x - window->Pos.x);
13367 columns->MinX = window->DC.IndentX - g.Style.ItemSpacing.x; // Lock our horizontal range
13368 columns->MaxX = ImMax(content_region_width - window->Scroll.x, columns->MinX + 1.0f);
13369 columns->StartPosY = window->DC.CursorPos.y;
13370 columns->StartMaxPosX = window->DC.CursorMaxPos.x;
13371 columns->LineMinY = columns->LineMaxY = window->DC.CursorPos.y;
13372 window->DC.ColumnsOffsetX = 0.0f;
13373 window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX);
13374
13375 // Clear data if columns count changed
13376 if (columns->Columns.Size != 0 && columns->Columns.Size != columns_count + 1)
13377 columns->Columns.resize(0);
13378
13379 // Initialize defaults
13380 columns->IsFirstFrame = (columns->Columns.Size == 0);
13381 if (columns->Columns.Size == 0)
13382 {
13383 columns->Columns.reserve(columns_count + 1);
13384 for (int n = 0; n < columns_count + 1; n++)
13385 {
13386 ImGuiColumnData column;
13387 column.OffsetNorm = n / (float)columns_count;
13388 columns->Columns.push_back(column);
13389 }
13390 }
13391
13392 for (int n = 0; n < columns_count; n++)
13393 {
13394 // Compute clipping rectangle
13395 ImGuiColumnData* column = &columns->Columns[n];
13396 float clip_x1 = ImFloor(0.5f + window->Pos.x + GetColumnOffset(n) - 1.0f);
13397 float clip_x2 = ImFloor(0.5f + window->Pos.x + GetColumnOffset(n + 1) - 1.0f);
13398 column->ClipRect = ImRect(clip_x1, -FLT_MAX, clip_x2, +FLT_MAX);
13399 column->ClipRect.ClipWith(window->ClipRect);
13400 }
13401
13402 window->DrawList->ChannelsSplit(columns->Count);
13403 PushColumnClipRect();
13404 PushItemWidth(GetColumnWidth() * 0.65f);
13405 }
13406
13407 void ImGui::EndColumns()
13408 {
13409 ImGuiContext& g = *GImGui;
13410 ImGuiWindow* window = GetCurrentWindow();
13411 ImGuiColumnsSet* columns = window->DC.ColumnsSet;
13412 IM_ASSERT(columns != NULL);
13413
13414 PopItemWidth();
13415 PopClipRect();
13416 window->DrawList->ChannelsMerge();
13417
13418 columns->LineMaxY = ImMax(columns->LineMaxY, window->DC.CursorPos.y);
13419 window->DC.CursorPos.y = columns->LineMaxY;
13420 if (!(columns->Flags & ImGuiColumnsFlags_GrowParentContentsSize))
13421 window->DC.CursorMaxPos.x = columns->StartMaxPosX; // Restore cursor max pos, as columns don't grow parent
13422
13423 // Draw columns borders and handle resize
13424 bool is_being_resized = false;
13425 if (!(columns->Flags & ImGuiColumnsFlags_NoBorder) && !window->SkipItems)
13426 {
13427 const float y1 = columns->StartPosY;
13428 const float y2 = window->DC.CursorPos.y;
13429 int dragging_column = -1;
13430 for (int n = 1; n < columns->Count; n++)
13431 {
13432 float x = window->Pos.x + GetColumnOffset(n);
13433 const ImGuiID column_id = columns->ID + ImGuiID(n);
13434 const float column_hw = GetColumnsRectHalfWidth(); // Half-width for interaction
13435 const ImRect column_rect(ImVec2(x - column_hw, y1), ImVec2(x + column_hw, y2));
13436 KeepAliveID(column_id);
13437 if (IsClippedEx(column_rect, column_id, false))
13438 continue;
13439
13440 bool hovered = false, held = false;
13441 if (!(columns->Flags & ImGuiColumnsFlags_NoResize))
13442 {
13443 ButtonBehavior(column_rect, column_id, &hovered, &held);
13444 if (hovered || held)
13445 g.MouseCursor = ImGuiMouseCursor_ResizeEW;
13446 if (held && !(columns->Columns[n].Flags & ImGuiColumnsFlags_NoResize))
13447 dragging_column = n;
13448 }
13449
13450 // Draw column (we clip the Y boundaries CPU side because very long triangles are mishandled by some GPU drivers.)
13451 const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : hovered ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator);
13452 const float xi = (float)(int)x;
13453 window->DrawList->AddLine(ImVec2(xi, ImMax(y1 + 1.0f, window->ClipRect.Min.y)), ImVec2(xi, ImMin(y2, window->ClipRect.Max.y)), col);
13454 }
13455
13456 // Apply dragging after drawing the column lines, so our rendered lines are in sync with how items were displayed during the frame.
13457 if (dragging_column != -1)
13458 {
13459 if (!columns->IsBeingResized)
13460 for (int n = 0; n < columns->Count + 1; n++)
13461 columns->Columns[n].OffsetNormBeforeResize = columns->Columns[n].OffsetNorm;
13462 columns->IsBeingResized = is_being_resized = true;
13463 float x = GetDraggedColumnOffset(columns, dragging_column);
13464 SetColumnOffset(dragging_column, x);
13465 }
13466 }
13467 columns->IsBeingResized = is_being_resized;
13468
13469 window->DC.ColumnsSet = NULL;
13470 window->DC.ColumnsOffsetX = 0.0f;
13471 window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX);
13472 }
13473
13474 // [2018-03: This is currently the only public API, while we are working on making BeginColumns/EndColumns user-facing]
13475 void ImGui::Columns(int columns_count, const char* id, bool border)
13476 {
13477 ImGuiWindow* window = GetCurrentWindow();
13478 IM_ASSERT(columns_count >= 1);
13479
13480 ImGuiColumnsFlags flags = (border ? 0 : ImGuiColumnsFlags_NoBorder);
13481 //flags |= ImGuiColumnsFlags_NoPreserveWidths; // NB: Legacy behavior
13482 if (window->DC.ColumnsSet != NULL && window->DC.ColumnsSet->Count == columns_count && window->DC.ColumnsSet->Flags == flags)
13483 return;
13484
13485 if (window->DC.ColumnsSet != NULL)
13486 EndColumns();
13487
13488 if (columns_count != 1)
13489 BeginColumns(id, columns_count, flags);
13490 }
13491
13492 void ImGui::Indent(float indent_w)
13493 {
13494 ImGuiContext& g = *GImGui;
13495 ImGuiWindow* window = GetCurrentWindow();
13496 window->DC.IndentX += (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing;
13497 window->DC.CursorPos.x = window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX;
13498 }
13499
13500 void ImGui::Unindent(float indent_w)
13501 {
13502 ImGuiContext& g = *GImGui;
13503 ImGuiWindow* window = GetCurrentWindow();
13504 window->DC.IndentX -= (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing;
13505 window->DC.CursorPos.x = window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX;
13506 }
13507
13508 void ImGui::TreePush(const char* str_id)
13509 {
13510 ImGuiWindow* window = GetCurrentWindow();
13511 Indent();
13512 window->DC.TreeDepth++;
13513 PushID(str_id ? str_id : "#TreePush");
13514 }
13515
13516 void ImGui::TreePush(const void* ptr_id)
13517 {
13518 ImGuiWindow* window = GetCurrentWindow();
13519 Indent();
13520 window->DC.TreeDepth++;
13521 PushID(ptr_id ? ptr_id : (const void*)"#TreePush");
13522 }
13523
13524 void ImGui::TreePushRawID(ImGuiID id)
13525 {
13526 ImGuiWindow* window = GetCurrentWindow();
13527 Indent();
13528 window->DC.TreeDepth++;
13529 window->IDStack.push_back(id);
13530 }
13531
13532 void ImGui::TreePop()
13533 {
13534 ImGuiContext& g = *GImGui;
13535 ImGuiWindow* window = g.CurrentWindow;
13536 Unindent();
13537
13538 window->DC.TreeDepth--;
13539 if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())
13540 if (g.NavIdIsAlive && (window->DC.TreeDepthMayJumpToParentOnPop & (1 << window->DC.TreeDepth)))
13541 {
13542 SetNavID(window->IDStack.back(), g.NavLayer);
13543 NavMoveRequestCancel();
13544 }
13545 window->DC.TreeDepthMayJumpToParentOnPop &= (1 << window->DC.TreeDepth) - 1;
13546
13547 IM_ASSERT(window->IDStack.Size > 1); // There should always be 1 element in the IDStack (pushed during window creation). If this triggers you called TreePop/PopID too much.
13548 PopID();
13549 }
13550
13551 void ImGui::Value(const char* prefix, bool b)
13552 {
13553 Text("%s: %s", prefix, (b ? "true" : "false"));
13554 }
13555
13556 void ImGui::Value(const char* prefix, int v)
13557 {
13558 Text("%s: %d", prefix, v);
13559 }
13560
13561 void ImGui::Value(const char* prefix, unsigned int v)
13562 {
13563 Text("%s: %d", prefix, v);
13564 }
13565
13566 void ImGui::Value(const char* prefix, float v, const char* float_format)
13567 {
13568 if (float_format)
13569 {
13570 char fmt[64];
13571 ImFormatString(fmt, IM_ARRAYSIZE(fmt), "%%s: %s", float_format);
13572 Text(fmt, prefix, v);
13573 }
13574 else
13575 {
13576 Text("%s: %.3f", prefix, v);
13577 }
13578 }
13579
13580 //-----------------------------------------------------------------------------
13581 // DRAG AND DROP
13582 //-----------------------------------------------------------------------------
13583
13584 void ImGui::ClearDragDrop()
13585 {
13586 ImGuiContext& g = *GImGui;
13587 g.DragDropActive = false;
13588 g.DragDropPayload.Clear();
13589 g.DragDropAcceptFlags = 0;
13590 g.DragDropAcceptIdCurr = g.DragDropAcceptIdPrev = 0;
13591 g.DragDropAcceptIdCurrRectSurface = FLT_MAX;
13592 g.DragDropAcceptFrameCount = -1;
13593 }
13594
13595 // Call when current ID is active.
13596 // When this returns true you need to: a) call SetDragDropPayload() exactly once, b) you may render the payload visual/description, c) call EndDragDropSource()
13597 bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags)
13598 {
13599 ImGuiContext& g = *GImGui;
13600 ImGuiWindow* window = g.CurrentWindow;
13601
13602 bool source_drag_active = false;
13603 ImGuiID source_id = 0;
13604 ImGuiID source_parent_id = 0;
13605 int mouse_button = 0;
13606 if (!(flags & ImGuiDragDropFlags_SourceExtern))
13607 {
13608 source_id = window->DC.LastItemId;
13609 if (source_id != 0 && g.ActiveId != source_id) // Early out for most common case
13610 return false;
13611 if (g.IO.MouseDown[mouse_button] == false)
13612 return false;
13613
13614 if (source_id == 0)
13615 {
13616 // If you want to use BeginDragDropSource() on an item with no unique identifier for interaction, such as Text() or Image(), you need to:
13617 // A) Read the explanation below, B) Use the ImGuiDragDropFlags_SourceAllowNullID flag, C) Swallow your programmer pride.
13618 if (!(flags & ImGuiDragDropFlags_SourceAllowNullID))
13619 {
13620 IM_ASSERT(0);
13621 return false;
13622 }
13623
13624 // Magic fallback (=somehow reprehensible) to handle items with no assigned ID, e.g. Text(), Image()
13625 // We build a throwaway ID based on current ID stack + relative AABB of items in window.
13626 // THE IDENTIFIER WON'T SURVIVE ANY REPOSITIONING OF THE WIDGET, so if your widget moves your dragging operation will be canceled.
13627 // We don't need to maintain/call ClearActiveID() as releasing the button will early out this function and trigger !ActiveIdIsAlive.
13628 bool is_hovered = (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) != 0;
13629 if (!is_hovered && (g.ActiveId == 0 || g.ActiveIdWindow != window))
13630 return false;
13631 source_id = window->DC.LastItemId = window->GetIDFromRectangle(window->DC.LastItemRect);
13632 if (is_hovered)
13633 SetHoveredID(source_id);
13634 if (is_hovered && g.IO.MouseClicked[mouse_button])
13635 {
13636 SetActiveID(source_id, window);
13637 FocusWindow(window);
13638 }
13639 if (g.ActiveId == source_id) // Allow the underlying widget to display/return hovered during the mouse release frame, else we would get a flicker.
13640 g.ActiveIdAllowOverlap = is_hovered;
13641 }
13642 else
13643 {
13644 g.ActiveIdAllowOverlap = false;
13645 }
13646 if (g.ActiveId != source_id)
13647 return false;
13648 source_parent_id = window->IDStack.back();
13649 source_drag_active = IsMouseDragging(mouse_button);
13650 }
13651 else
13652 {
13653 window = NULL;
13654 source_id = ImHash("#SourceExtern", 0);
13655 source_drag_active = true;
13656 }
13657
13658 if (source_drag_active)
13659 {
13660 if (!g.DragDropActive)
13661 {
13662 IM_ASSERT(source_id != 0);
13663 ClearDragDrop();
13664 ImGuiPayload& payload = g.DragDropPayload;
13665 payload.SourceId = source_id;
13666 payload.SourceParentId = source_parent_id;
13667 g.DragDropActive = true;
13668 g.DragDropSourceFlags = flags;
13669 g.DragDropMouseButton = mouse_button;
13670 }
13671 g.DragDropWithinSourceOrTarget = true;
13672
13673 if (!(flags & ImGuiDragDropFlags_SourceNoPreviewTooltip))
13674 {
13675 // Target can request the Source to not display its tooltip (we use a dedicated flag to make this request explicit)
13676 // We unfortunately can't just modify the source flags and skip the call to BeginTooltip, as caller may be emitting contents.
13677 BeginTooltip();
13678 if (g.DragDropActive && g.DragDropAcceptIdPrev && (g.DragDropAcceptFlags & ImGuiDragDropFlags_AcceptNoPreviewTooltip))
13679 {
13680 ImGuiWindow* tooltip_window = g.CurrentWindow;
13681 tooltip_window->SkipItems = true;
13682 tooltip_window->HiddenFramesRegular = 1;
13683 }
13684 }
13685
13686 if (!(flags & ImGuiDragDropFlags_SourceNoDisableHover) && !(flags & ImGuiDragDropFlags_SourceExtern))
13687 window->DC.LastItemStatusFlags &= ~ImGuiItemStatusFlags_HoveredRect;
13688
13689 return true;
13690 }
13691 return false;
13692 }
13693
13694 void ImGui::EndDragDropSource()
13695 {
13696 ImGuiContext& g = *GImGui;
13697 IM_ASSERT(g.DragDropActive);
13698 IM_ASSERT(g.DragDropWithinSourceOrTarget && "Not after a BeginDragDropSource()?");
13699
13700 if (!(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoPreviewTooltip))
13701 EndTooltip();
13702
13703 // Discard the drag if have not called SetDragDropPayload()
13704 if (g.DragDropPayload.DataFrameCount == -1)
13705 ClearDragDrop();
13706 g.DragDropWithinSourceOrTarget = false;
13707 }
13708
13709 // Use 'cond' to choose to submit payload on drag start or every frame
13710 bool ImGui::SetDragDropPayload(const char* type, const void* data, size_t data_size, ImGuiCond cond)
13711 {
13712 ImGuiContext& g = *GImGui;
13713 ImGuiPayload& payload = g.DragDropPayload;
13714 if (cond == 0)
13715 cond = ImGuiCond_Always;
13716
13717 IM_ASSERT(type != NULL);
13718 IM_ASSERT(strlen(type) < IM_ARRAYSIZE(payload.DataType) && "Payload type can be at most 32 characters long");
13719 IM_ASSERT((data != NULL && data_size > 0) || (data == NULL && data_size == 0));
13720 IM_ASSERT(cond == ImGuiCond_Always || cond == ImGuiCond_Once);
13721 IM_ASSERT(payload.SourceId != 0); // Not called between BeginDragDropSource() and EndDragDropSource()
13722
13723 if (cond == ImGuiCond_Always || payload.DataFrameCount == -1)
13724 {
13725 // Copy payload
13726 ImStrncpy(payload.DataType, type, IM_ARRAYSIZE(payload.DataType));
13727 g.DragDropPayloadBufHeap.resize(0);
13728 if (data_size > sizeof(g.DragDropPayloadBufLocal))
13729 {
13730 // Store in heap
13731 g.DragDropPayloadBufHeap.resize((int)data_size);
13732 payload.Data = g.DragDropPayloadBufHeap.Data;
13733 memcpy(payload.Data, data, data_size);
13734 }
13735 else if (data_size > 0)
13736 {
13737 // Store locally
13738 memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal));
13739 payload.Data = g.DragDropPayloadBufLocal;
13740 memcpy(payload.Data, data, data_size);
13741 }
13742 else
13743 {
13744 payload.Data = NULL;
13745 }
13746 payload.DataSize = (int)data_size;
13747 }
13748 payload.DataFrameCount = g.FrameCount;
13749
13750 return (g.DragDropAcceptFrameCount == g.FrameCount) || (g.DragDropAcceptFrameCount == g.FrameCount - 1);
13751 }
13752
13753 bool ImGui::BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id)
13754 {
13755 ImGuiContext& g = *GImGui;
13756 if (!g.DragDropActive)
13757 return false;
13758
13759 ImGuiWindow* window = g.CurrentWindow;
13760 if (g.HoveredWindow == NULL || window->RootWindow != g.HoveredWindow->RootWindow)
13761 return false;
13762 IM_ASSERT(id != 0);
13763 if (!IsMouseHoveringRect(bb.Min, bb.Max) || (id == g.DragDropPayload.SourceId))
13764 return false;
13765
13766 IM_ASSERT(g.DragDropWithinSourceOrTarget == false);
13767 g.DragDropTargetRect = bb;
13768 g.DragDropTargetId = id;
13769 g.DragDropWithinSourceOrTarget = true;
13770 return true;
13771 }
13772
13773 // We don't use BeginDragDropTargetCustom() and duplicate its code because:
13774 // 1) we use LastItemRectHoveredRect which handles items that pushes a temporarily clip rectangle in their code. Calling BeginDragDropTargetCustom(LastItemRect) would not handle them.
13775 // 2) and it's faster. as this code may be very frequently called, we want to early out as fast as we can.
13776 // Also note how the HoveredWindow test is positioned differently in both functions (in both functions we optimize for the cheapest early out case)
13777 bool ImGui::BeginDragDropTarget()
13778 {
13779 ImGuiContext& g = *GImGui;
13780 if (!g.DragDropActive)
13781 return false;
13782
13783 ImGuiWindow* window = g.CurrentWindow;
13784 if (!(window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect))
13785 return false;
13786 if (g.HoveredWindow == NULL || window->RootWindow != g.HoveredWindow->RootWindow)
13787 return false;
13788
13789 const ImRect& display_rect = (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HasDisplayRect) ? window->DC.LastItemDisplayRect : window->DC.LastItemRect;
13790 ImGuiID id = window->DC.LastItemId;
13791 if (id == 0)
13792 id = window->GetIDFromRectangle(display_rect);
13793 if (g.DragDropPayload.SourceId == id)
13794 return false;
13795
13796 IM_ASSERT(g.DragDropWithinSourceOrTarget == false);
13797 g.DragDropTargetRect = display_rect;
13798 g.DragDropTargetId = id;
13799 g.DragDropWithinSourceOrTarget = true;
13800 return true;
13801 }
13802
13803 bool ImGui::IsDragDropPayloadBeingAccepted()
13804 {
13805 ImGuiContext& g = *GImGui;
13806 return g.DragDropActive && g.DragDropAcceptIdPrev != 0;
13807 }
13808
13809 const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDropFlags flags)
13810 {
13811 ImGuiContext& g = *GImGui;
13812 ImGuiWindow* window = g.CurrentWindow;
13813 ImGuiPayload& payload = g.DragDropPayload;
13814 IM_ASSERT(g.DragDropActive); // Not called between BeginDragDropTarget() and EndDragDropTarget() ?
13815 IM_ASSERT(payload.DataFrameCount != -1); // Forgot to call EndDragDropTarget() ?
13816 if (type != NULL && !payload.IsDataType(type))
13817 return NULL;
13818
13819 // Accept smallest drag target bounding box, this allows us to nest drag targets conveniently without ordering constraints.
13820 // NB: We currently accept NULL id as target. However, overlapping targets requires a unique ID to function!
13821 const bool was_accepted_previously = (g.DragDropAcceptIdPrev == g.DragDropTargetId);
13822 ImRect r = g.DragDropTargetRect;
13823 float r_surface = r.GetWidth() * r.GetHeight();
13824 if (r_surface < g.DragDropAcceptIdCurrRectSurface)
13825 {
13826 g.DragDropAcceptFlags = flags;
13827 g.DragDropAcceptIdCurr = g.DragDropTargetId;
13828 g.DragDropAcceptIdCurrRectSurface = r_surface;
13829 }
13830
13831 // Render default drop visuals
13832 payload.Preview = was_accepted_previously;
13833 flags |= (g.DragDropSourceFlags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect); // Source can also inhibit the preview (useful for external sources that lives for 1 frame)
13834 if (!(flags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect) && payload.Preview)
13835 {
13836 // FIXME-DRAG: Settle on a proper default visuals for drop target.
13837 r.Expand(3.5f);
13838 bool push_clip_rect = !window->ClipRect.Contains(r);
13839 if (push_clip_rect) window->DrawList->PushClipRect(r.Min-ImVec2(1,1), r.Max+ImVec2(1,1));
13840 window->DrawList->AddRect(r.Min, r.Max, GetColorU32(ImGuiCol_DragDropTarget), 0.0f, ~0, 2.0f);
13841 if (push_clip_rect) window->DrawList->PopClipRect();
13842 }
13843
13844 g.DragDropAcceptFrameCount = g.FrameCount;
13845 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()
13846 if (!payload.Delivery && !(flags & ImGuiDragDropFlags_AcceptBeforeDelivery))
13847 return NULL;
13848
13849 return &payload;
13850 }
13851
13852 // We don't really use/need this now, but added it for the sake of consistency and because we might need it later.
13853 void ImGui::EndDragDropTarget()
13854 {
13855 ImGuiContext& g = *GImGui;
13856 IM_ASSERT(g.DragDropActive);
13857 IM_ASSERT(g.DragDropWithinSourceOrTarget);
13858 g.DragDropWithinSourceOrTarget = false;
13859 }
13860
13861 //-----------------------------------------------------------------------------
13862 // PLATFORM DEPENDENT HELPERS
13863 //-----------------------------------------------------------------------------
13864
13865 #if defined(_WIN32) && !defined(_WINDOWS_) && (!defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS) || !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS))
13866 #undef WIN32_LEAN_AND_MEAN
13867 #define WIN32_LEAN_AND_MEAN
13868 #ifndef __MINGW32__
13869 #include <Windows.h>
13870 #else
13871 #include <windows.h>
13872 #endif
13873 #endif
13874
13875 // Win32 API clipboard implementation
13876 #if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS)
13877
13878 #ifdef _MSC_VER
13879 #pragma comment(lib, "user32")
13880 #endif
13881
13882 static const char* GetClipboardTextFn_DefaultImpl(void*)
13883 {
13884 static ImVector<char> buf_local;
13885 buf_local.clear();
13886 if (!::OpenClipboard(NULL))
13887 return NULL;
13888 HANDLE wbuf_handle = ::GetClipboardData(CF_UNICODETEXT);
13889 if (wbuf_handle == NULL)
13890 {
13891 ::CloseClipboard();
13892 return NULL;
13893 }
13894 if (ImWchar* wbuf_global = (ImWchar*)::GlobalLock(wbuf_handle))
13895 {
13896 int buf_len = ImTextCountUtf8BytesFromStr(wbuf_global, NULL) + 1;
13897 buf_local.resize(buf_len);
13898 ImTextStrToUtf8(buf_local.Data, buf_len, wbuf_global, NULL);
13899 }
13900 ::GlobalUnlock(wbuf_handle);
13901 ::CloseClipboard();
13902 return buf_local.Data;
13903 }
13904
13905 static void SetClipboardTextFn_DefaultImpl(void*, const char* text)
13906 {
13907 if (!::OpenClipboard(NULL))
13908 return;
13909 const int wbuf_length = ImTextCountCharsFromUtf8(text, NULL) + 1;
13910 HGLOBAL wbuf_handle = ::GlobalAlloc(GMEM_MOVEABLE, (SIZE_T)wbuf_length * sizeof(ImWchar));
13911 if (wbuf_handle == NULL)
13912 {
13913 ::CloseClipboard();
13914 return;
13915 }
13916 ImWchar* wbuf_global = (ImWchar*)::GlobalLock(wbuf_handle);
13917 ImTextStrFromUtf8(wbuf_global, wbuf_length, text, NULL);
13918 ::GlobalUnlock(wbuf_handle);
13919 ::EmptyClipboard();
13920 if (::SetClipboardData(CF_UNICODETEXT, wbuf_handle) == NULL)
13921 ::GlobalFree(wbuf_handle);
13922 ::CloseClipboard();
13923 }
13924
13925 #else
13926
13927 // Local ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers
13928 static const char* GetClipboardTextFn_DefaultImpl(void*)
13929 {
13930 ImGuiContext& g = *GImGui;
13931 return g.PrivateClipboard.empty() ? NULL : g.PrivateClipboard.begin();
13932 }
13933
13934 // Local ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers
13935 static void SetClipboardTextFn_DefaultImpl(void*, const char* text)
13936 {
13937 ImGuiContext& g = *GImGui;
13938 g.PrivateClipboard.clear();
13939 const char* text_end = text + strlen(text);
13940 g.PrivateClipboard.resize((int)(text_end - text) + 1);
13941 memcpy(&g.PrivateClipboard[0], text, (size_t)(text_end - text));
13942 g.PrivateClipboard[(int)(text_end - text)] = 0;
13943 }
13944
13945 #endif
13946
13947 // Win32 API IME support (for Asian languages, etc.)
13948 #if defined(_WIN32) && !defined(__GNUC__) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS)
13949
13950 #include <imm.h>
13951 #ifdef _MSC_VER
13952 #pragma comment(lib, "imm32")
13953 #endif
13954
13955 static void ImeSetInputScreenPosFn_DefaultImpl(int x, int y)
13956 {
13957 // Notify OS Input Method Editor of text input position
13958 if (HWND hwnd = (HWND)GImGui->IO.ImeWindowHandle)
13959 if (HIMC himc = ::ImmGetContext(hwnd))
13960 {
13961 COMPOSITIONFORM cf;
13962 cf.ptCurrentPos.x = x;
13963 cf.ptCurrentPos.y = y;
13964 cf.dwStyle = CFS_FORCE_POSITION;
13965 ::ImmSetCompositionWindow(himc, &cf);
13966 ::ImmReleaseContext(hwnd, himc);
13967 }
13968 }
13969
13970 #else
13971
13972 static void ImeSetInputScreenPosFn_DefaultImpl(int, int) {}
13973
13974 #endif
13975
13976 //-----------------------------------------------------------------------------
13977 // HELP, METRICS
13978 //-----------------------------------------------------------------------------
13979
13980 void ImGui::ShowMetricsWindow(bool* p_open)
13981 {
13982 if (ImGui::Begin("ImGui Metrics", p_open))
13983 {
13984 static bool show_draw_cmd_clip_rects = true;
13985 static bool show_window_begin_order = false;
13986 ImGui::Text("Dear ImGui %s", ImGui::GetVersion());
13987 ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
13988 ImGui::Text("%d vertices, %d indices (%d triangles)", ImGui::GetIO().MetricsRenderVertices, ImGui::GetIO().MetricsRenderIndices, ImGui::GetIO().MetricsRenderIndices / 3);
13989 ImGui::Text("%d allocations", (int)GImAllocatorActiveAllocationsCount);
13990 ImGui::Checkbox("Show clipping rectangles when hovering draw commands", &show_draw_cmd_clip_rects);
13991 ImGui::Checkbox("Ctrl shows window begin order", &show_window_begin_order);
13992
13993 ImGui::Separator();
13994
13995 struct Funcs
13996 {
13997 static void NodeDrawList(ImGuiWindow* window, ImDrawList* draw_list, const char* label)
13998 {
13999 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);
14000 if (draw_list == ImGui::GetWindowDrawList())
14001 {
14002 ImGui::SameLine();
14003 ImGui::TextColored(ImColor(255,100,100), "CURRENTLY APPENDING"); // Can't display stats for active draw list! (we don't have the data double-buffered)
14004 if (node_open) ImGui::TreePop();
14005 return;
14006 }
14007
14008 ImDrawList* overlay_draw_list = GetOverlayDrawList(); // Render additional visuals into the top-most draw list
14009 if (window && IsItemHovered())
14010 overlay_draw_list->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 255, 0, 255));
14011 if (!node_open)
14012 return;
14013
14014 int elem_offset = 0;
14015 for (const ImDrawCmd* pcmd = draw_list->CmdBuffer.begin(); pcmd < draw_list->CmdBuffer.end(); elem_offset += pcmd->ElemCount, pcmd++)
14016 {
14017 if (pcmd->UserCallback == NULL && pcmd->ElemCount == 0)
14018 continue;
14019 if (pcmd->UserCallback)
14020 {
14021 ImGui::BulletText("Callback %p, user_data %p", pcmd->UserCallback, pcmd->UserCallbackData);
14022 continue;
14023 }
14024 ImDrawIdx* idx_buffer = (draw_list->IdxBuffer.Size > 0) ? draw_list->IdxBuffer.Data : NULL;
14025 bool pcmd_node_open = ImGui::TreeNode((void*)(pcmd - draw_list->CmdBuffer.begin()), "Draw %4d %s vtx, tex 0x%p, clip_rect (%4.0f,%4.0f)-(%4.0f,%4.0f)", pcmd->ElemCount, draw_list->IdxBuffer.Size > 0 ? "indexed" : "non-indexed", pcmd->TextureId, pcmd->ClipRect.x, pcmd->ClipRect.y, pcmd->ClipRect.z, pcmd->ClipRect.w);
14026 if (show_draw_cmd_clip_rects && ImGui::IsItemHovered())
14027 {
14028 ImRect clip_rect = pcmd->ClipRect;
14029 ImRect vtxs_rect;
14030 for (int i = elem_offset; i < elem_offset + (int)pcmd->ElemCount; i++)
14031 vtxs_rect.Add(draw_list->VtxBuffer[idx_buffer ? idx_buffer[i] : i].pos);
14032 clip_rect.Floor(); overlay_draw_list->AddRect(clip_rect.Min, clip_rect.Max, IM_COL32(255,255,0,255));
14033 vtxs_rect.Floor(); overlay_draw_list->AddRect(vtxs_rect.Min, vtxs_rect.Max, IM_COL32(255,0,255,255));
14034 }
14035 if (!pcmd_node_open)
14036 continue;
14037
14038 // Display individual triangles/vertices. Hover on to get the corresponding triangle highlighted.
14039 ImGuiListClipper clipper(pcmd->ElemCount/3); // Manually coarse clip our print out of individual vertices to save CPU, only items that may be visible.
14040 while (clipper.Step())
14041 for (int prim = clipper.DisplayStart, vtx_i = elem_offset + clipper.DisplayStart*3; prim < clipper.DisplayEnd; prim++)
14042 {
14043 char buf[300];
14044 char *buf_p = buf, *buf_end = buf + IM_ARRAYSIZE(buf);
14045 ImVec2 triangles_pos[3];
14046 for (int n = 0; n < 3; n++, vtx_i++)
14047 {
14048 ImDrawVert& v = draw_list->VtxBuffer[idx_buffer ? idx_buffer[vtx_i] : vtx_i];
14049 triangles_pos[n] = v.pos;
14050 buf_p += ImFormatString(buf_p, (int)(buf_end - buf_p), "%s %04d: pos (%8.2f,%8.2f), uv (%.6f,%.6f), col %08X\n", (n == 0) ? "vtx" : " ", vtx_i, v.pos.x, v.pos.y, v.uv.x, v.uv.y, v.col);
14051 }
14052 ImGui::Selectable(buf, false);
14053 if (ImGui::IsItemHovered())
14054 {
14055 ImDrawListFlags backup_flags = overlay_draw_list->Flags;
14056 overlay_draw_list->Flags &= ~ImDrawListFlags_AntiAliasedLines; // Disable AA on triangle outlines at is more readable for very large and thin triangles.
14057 overlay_draw_list->AddPolyline(triangles_pos, 3, IM_COL32(255,255,0,255), true, 1.0f);
14058 overlay_draw_list->Flags = backup_flags;
14059 }
14060 }
14061 ImGui::TreePop();
14062 }
14063 ImGui::TreePop();
14064 }
14065
14066 static void NodeWindows(ImVector<ImGuiWindow*>& windows, const char* label)
14067 {
14068 if (!ImGui::TreeNode(label, "%s (%d)", label, windows.Size))
14069 return;
14070 for (int i = 0; i < windows.Size; i++)
14071 Funcs::NodeWindow(windows[i], "Window");
14072 ImGui::TreePop();
14073 }
14074
14075 static void NodeWindow(ImGuiWindow* window, const char* label)
14076 {
14077 if (!ImGui::TreeNode(window, "%s '%s', %d @ 0x%p", label, window->Name, window->Active || window->WasActive, window))
14078 return;
14079 ImGuiWindowFlags flags = window->Flags;
14080 NodeDrawList(window, window->DrawList, "DrawList");
14081 ImGui::BulletText("Pos: (%.1f,%.1f), Size: (%.1f,%.1f), SizeContents (%.1f,%.1f)", window->Pos.x, window->Pos.y, window->Size.x, window->Size.y, window->SizeContents.x, window->SizeContents.y);
14082 ImGui::BulletText("Flags: 0x%08X (%s%s%s%s%s%s%s%s..)", flags,
14083 (flags & ImGuiWindowFlags_ChildWindow) ? "Child " : "", (flags & ImGuiWindowFlags_Tooltip) ? "Tooltip " : "", (flags & ImGuiWindowFlags_Popup) ? "Popup " : "",
14084 (flags & ImGuiWindowFlags_Modal) ? "Modal " : "", (flags & ImGuiWindowFlags_ChildMenu) ? "ChildMenu " : "", (flags & ImGuiWindowFlags_NoSavedSettings) ? "NoSavedSettings " : "",
14085 (flags & ImGuiWindowFlags_NoInputs) ? "NoInputs":"", (flags & ImGuiWindowFlags_AlwaysAutoResize) ? "AlwaysAutoResize" : "");
14086 ImGui::BulletText("Scroll: (%.2f/%.2f,%.2f/%.2f)", window->Scroll.x, GetScrollMaxX(window), window->Scroll.y, GetScrollMaxY(window));
14087 ImGui::BulletText("Active: %d/%d, WriteAccessed: %d, BeginOrderWithinContext: %d", window->Active, window->WasActive, window->WriteAccessed, (window->Active || window->WasActive) ? window->BeginOrderWithinContext : -1);
14088 ImGui::BulletText("Appearing: %d, Hidden: %d (Reg %d Resize %d), SkipItems: %d", window->Appearing, window->Hidden, window->HiddenFramesRegular, window->HiddenFramesForResize, window->SkipItems);
14089 ImGui::BulletText("NavLastIds: 0x%08X,0x%08X, NavLayerActiveMask: %X", window->NavLastIds[0], window->NavLastIds[1], window->DC.NavLayerActiveMask);
14090 ImGui::BulletText("NavLastChildNavWindow: %s", window->NavLastChildNavWindow ? window->NavLastChildNavWindow->Name : "NULL");
14091 if (!window->NavRectRel[0].IsInverted())
14092 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);
14093 else
14094 ImGui::BulletText("NavRectRel[0]: <None>");
14095 if (window->RootWindow != window) NodeWindow(window->RootWindow, "RootWindow");
14096 if (window->ParentWindow != NULL) NodeWindow(window->ParentWindow, "ParentWindow");
14097 if (window->DC.ChildWindows.Size > 0) NodeWindows(window->DC.ChildWindows, "ChildWindows");
14098 if (window->ColumnsStorage.Size > 0 && ImGui::TreeNode("Columns", "Columns sets (%d)", window->ColumnsStorage.Size))
14099 {
14100 for (int n = 0; n < window->ColumnsStorage.Size; n++)
14101 {
14102 const ImGuiColumnsSet* columns = &window->ColumnsStorage[n];
14103 if (ImGui::TreeNode((void*)(uintptr_t)columns->ID, "Columns Id: 0x%08X, Count: %d, Flags: 0x%04X", columns->ID, columns->Count, columns->Flags))
14104 {
14105 ImGui::BulletText("Width: %.1f (MinX: %.1f, MaxX: %.1f)", columns->MaxX - columns->MinX, columns->MinX, columns->MaxX);
14106 for (int column_n = 0; column_n < columns->Columns.Size; column_n++)
14107 ImGui::BulletText("Column %02d: OffsetNorm %.3f (= %.1f px)", column_n, columns->Columns[column_n].OffsetNorm, OffsetNormToPixels(columns, columns->Columns[column_n].OffsetNorm));
14108 ImGui::TreePop();
14109 }
14110 }
14111 ImGui::TreePop();
14112 }
14113 ImGui::BulletText("Storage: %d bytes", window->StateStorage.Data.Size * (int)sizeof(ImGuiStorage::Pair));
14114 ImGui::TreePop();
14115 }
14116 };
14117
14118 // Access private state, we are going to display the draw lists from last frame
14119 ImGuiContext& g = *GImGui;
14120 Funcs::NodeWindows(g.Windows, "Windows");
14121 if (ImGui::TreeNode("DrawList", "Active DrawLists (%d)", g.DrawDataBuilder.Layers[0].Size))
14122 {
14123 for (int i = 0; i < g.DrawDataBuilder.Layers[0].Size; i++)
14124 Funcs::NodeDrawList(NULL, g.DrawDataBuilder.Layers[0][i], "DrawList");
14125 ImGui::TreePop();
14126 }
14127 if (ImGui::TreeNode("Popups", "Popups (%d)", g.OpenPopupStack.Size))
14128 {
14129 for (int i = 0; i < g.OpenPopupStack.Size; i++)
14130 {
14131 ImGuiWindow* window = g.OpenPopupStack[i].Window;
14132 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" : "");
14133 }
14134 ImGui::TreePop();
14135 }
14136 if (ImGui::TreeNode("Internal state"))
14137 {
14138 const char* input_source_names[] = { "None", "Mouse", "Nav", "NavKeyboard", "NavGamepad" }; IM_ASSERT(IM_ARRAYSIZE(input_source_names) == ImGuiInputSource_COUNT);
14139 ImGui::Text("HoveredWindow: '%s'", g.HoveredWindow ? g.HoveredWindow->Name : "NULL");
14140 ImGui::Text("HoveredRootWindow: '%s'", g.HoveredRootWindow ? g.HoveredRootWindow->Name : "NULL");
14141 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
14142 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]);
14143 ImGui::Text("ActiveIdWindow: '%s'", g.ActiveIdWindow ? g.ActiveIdWindow->Name : "NULL");
14144 ImGui::Text("MovingWindow: '%s'", g.MovingWindow ? g.MovingWindow->Name : "NULL");
14145 ImGui::Text("NavWindow: '%s'", g.NavWindow ? g.NavWindow->Name : "NULL");
14146 ImGui::Text("NavId: 0x%08X, NavLayer: %d", g.NavId, g.NavLayer);
14147 ImGui::Text("NavInputSource: %s", input_source_names[g.NavInputSource]);
14148 ImGui::Text("NavActive: %d, NavVisible: %d", g.IO.NavActive, g.IO.NavVisible);
14149 ImGui::Text("NavActivateId: 0x%08X, NavInputId: 0x%08X", g.NavActivateId, g.NavInputId);
14150 ImGui::Text("NavDisableHighlight: %d, NavDisableMouseHover: %d", g.NavDisableHighlight, g.NavDisableMouseHover);
14151 ImGui::Text("NavWindowingTarget: '%s'", g.NavWindowingTarget ? g.NavWindowingTarget->Name : "NULL");
14152 ImGui::Text("DragDrop: %d, SourceId = 0x%08X, Payload \"%s\" (%d bytes)", g.DragDropActive, g.DragDropPayload.SourceId, g.DragDropPayload.DataType, g.DragDropPayload.DataSize);
14153 ImGui::TreePop();
14154 }
14155
14156
14157 if (g.IO.KeyCtrl && show_window_begin_order)
14158 {
14159 for (int n = 0; n < g.Windows.Size; n++)
14160 {
14161 ImGuiWindow* window = g.Windows[n];
14162 if ((window->Flags & ImGuiWindowFlags_ChildWindow) || !window->WasActive)
14163 continue;
14164 char buf[32];
14165 ImFormatString(buf, IM_ARRAYSIZE(buf), "%d", window->BeginOrderWithinContext);
14166 float font_size = ImGui::GetFontSize() * 2;
14167 ImDrawList* overlay_draw_list = GetOverlayDrawList();
14168 overlay_draw_list->AddRectFilled(window->Pos, window->Pos + ImVec2(font_size, font_size), IM_COL32(200, 100, 100, 255));
14169 overlay_draw_list->AddText(NULL, font_size, window->Pos, IM_COL32(255, 255, 255, 255), buf);
14170 }
14171 }
14172 }
14173 ImGui::End();
14174 }
14175
14176 //-----------------------------------------------------------------------------
14177
14178 // Include imgui_user.inl at the end of imgui.cpp to access private data/functions that aren't exposed.
14179 // 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.
14180 #ifdef IMGUI_INCLUDE_IMGUI_USER_INL
14181 #include "imgui_user.inl"
14182 #endif
14183
14184 //-----------------------------------------------------------------------------