1 // dear imgui, v1.60 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 /*
14
15 Index
16 - MISSION STATEMENT
17 - END-USER GUIDE
18 - PROGRAMMER GUIDE (read me!)
19 - Read first
20 - How to update to a newer version of Dear ImGui
21 - Getting started with integrating Dear ImGui in your code/engine
22 - Using gamepad/keyboard navigation [BETA]
23 - API BREAKING CHANGES (read me when you update!)
24 - ISSUES & TODO LIST
25 - FREQUENTLY ASKED QUESTIONS (FAQ), TIPS
26 - How can I help?
27 - How can I display an image? What is ImTextureID, how does it works?
28 - How can I have multiple widgets with the same label? Can I have widget without a label? (Yes). A primer on labels and the ID stack.
29 - How can I tell when Dear ImGui wants my mouse/keyboard inputs VS when I can pass them to my application?
30 - How can I load a different font than the default?
31 - How can I easily use icons in my application?
32 - How can I load multiple fonts?
33 - How can I display and input non-latin characters such as Chinese, Japanese, Korean, Cyrillic?
34 - How can I preserve my Dear ImGui context across reloading a DLL? (loss of the global/static variables)
35 - How can I use the drawing facilities without an ImGui window? (using ImDrawList API)
36 - I integrated Dear ImGui in my engine and the text or lines are blurry..
37 - I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around..
38 - ISSUES & TODO-LIST
39 - CODE
40
41
42 MISSION STATEMENT
43 =================
44
45 - Easy to use to create code-driven and data-driven tools
46 - Easy to use to create ad hoc short-lived tools and long-lived, more elaborate tools
47 - Easy to hack and improve
48 - Minimize screen real-estate usage
49 - Minimize setup and maintenance
50 - Minimize state storage on user side
51 - Portable, minimize dependencies, run on target (consoles, phones, etc.)
52 - Efficient runtime and memory consumption (NB- we do allocate when "growing" content e.g. creating a window, opening a tree node
53 for the first time, etc. but a typical frame won't allocate anything)
54
55 Designed for developers and content-creators, not the typical end-user! Some of the weaknesses includes:
56 - Doesn't look fancy, doesn't animate
57 - Limited layout features, intricate layouts are typically crafted in code
58
59
60 END-USER GUIDE
61 ==============
62
63 - Double-click on title bar to collapse window.
64 - Click upper right corner to close a window, available when 'bool* p_open' is passed to ImGui::Begin().
65 - Click and drag on lower right corner to resize window (double-click to auto fit window to its contents).
66 - Click and drag on any empty space to move window.
67 - TAB/SHIFT+TAB to cycle through keyboard editable fields.
68 - CTRL+Click on a slider or drag box to input value as text.
69 - Use mouse wheel to scroll.
70 - Text editor:
71 - Hold SHIFT or use mouse to select text.
72 - CTRL+Left/Right to word jump.
73 - CTRL+Shift+Left/Right to select words.
74 - CTRL+A our Double-Click to select all.
75 - CTRL+X,CTRL+C,CTRL+V to use OS clipboard/
76 - CTRL+Z,CTRL+Y to undo/redo.
77 - ESCAPE to revert text to its original value.
78 - You can apply arithmetic operators +,*,/ on numerical values. Use +- to subtract (because - would set a negative value!)
79 - Controls are automatically adjusted for OSX to match standard OSX text editing operations.
80 - Gamepad navigation: see suggested mappings in imgui.h ImGuiNavInput_
81
82
83 PROGRAMMER GUIDE
84 ================
85
86 READ FIRST
87
88 - Read the FAQ below this section!
89 - Your code creates the UI, if your code doesn't run the UI is gone! == very dynamic UI, no construction/destructions steps, less data retention
90 on your side, no state duplication, less sync, less bugs.
91 - Call and read ImGui::ShowDemoWindow() for demo code demonstrating most features.
92 - You can learn about immediate-mode gui principles at http://www.johno.se/book/imgui.html or watch http://mollyrocket.com/861
93
94 HOW TO UPDATE TO A NEWER VERSION OF DEAR IMGUI
95
96 - Overwrite all the sources files except for imconfig.h (if you have made modification to your copy of imconfig.h)
97 - Read the "API BREAKING CHANGES" section (below). This is where we list occasional API breaking changes.
98 If a function/type has been renamed / or marked obsolete, try to fix the name in your code before it is permanently removed from the public API.
99 If you have a problem with a missing function/symbols, search for its name in the code, there will likely be a comment about it.
100 Please report any issue to the GitHub page!
101 - Try to keep your copy of dear imgui reasonably up to date.
102
103 GETTING STARTED WITH INTEGRATING DEAR IMGUI IN YOUR CODE/ENGINE
104
105 - Add the Dear ImGui source files to your projects, using your preferred build system.
106 It is recommended you build the .cpp files as part of your project and not as a library.
107 - You can later customize the imconfig.h file to tweak some compilation time behavior, such as integrating imgui types with your own maths types.
108 - See examples/ folder for standalone sample applications.
109 - You may be able to grab and copy a ready made imgui_impl_*** file from the examples/.
110 - When using Dear ImGui, your programming IDE is your friend: follow the declaration of variables, functions and types to find comments about them.
111
112 - Init: retrieve the ImGuiIO structure with ImGui::GetIO() and fill the fields marked 'Settings': at minimum you need to set io.DisplaySize
113 (application resolution). Later on you will fill your keyboard mapping, clipboard handlers, and other advanced features but for a basic
114 integration you don't need to worry about it all.
115 - Init: call io.Fonts->GetTexDataAsRGBA32(...), it will build the font atlas texture, then load the texture pixels into graphics memory.
116 - Every frame:
117 - In your main loop as early a possible, fill the IO fields marked 'Input' (e.g. mouse position, buttons, keyboard info, etc.)
118 - Call ImGui::NewFrame() to begin the frame
119 - You can use any ImGui function you want between NewFrame() and Render()
120 - Call ImGui::Render() as late as you can to end the frame and finalize render data. it will call your io.RenderDrawListFn handler.
121 (Even if you don't render, call Render() and ignore the callback, or call EndFrame() instead. Otherwhise some features will break)
122 - All rendering information are stored into command-lists until ImGui::Render() is called.
123 - Dear ImGui never touches or knows about your GPU state. the only function that knows about GPU is the RenderDrawListFn handler that you provide.
124 - Effectively it means you can create widgets at any time in your code, regardless of considerations of being in "update" vs "render" phases
125 of your own application.
126 - Refer to the examples applications in the examples/ folder for instruction on how to setup your code.
127 - A minimal application skeleton may be:
128
129 // Application init
130 ImGui::CreateContext();
131 ImGuiIO& io = ImGui::GetIO();
132 io.DisplaySize.x = 1920.0f;
133 io.DisplaySize.y = 1280.0f;
134 // TODO: Fill others settings of the io structure later.
135
136 // Load texture atlas (there is a default font so you don't need to care about choosing a font yet)
137 unsigned char* pixels;
138 int width, height;
139 io.Fonts->GetTexDataAsRGBA32(pixels, &width, &height);
140 // TODO: At this points you've got the texture data and you need to upload that your your graphic system:
141 MyTexture* texture = MyEngine::CreateTextureFromMemoryPixels(pixels, width, height, TEXTURE_TYPE_RGBA)
142 // TODO: Store your texture pointer/identifier (whatever your engine uses) in 'io.Fonts->TexID'. This will be passed back to your via the renderer.
143 io.Fonts->TexID = (void*)texture;
144
145 // Application main loop
146 while (true)
147 {
148 // Setup low-level inputs (e.g. on Win32, GetKeyboardState(), or write to those fields from your Windows message loop handlers, etc.)
149 ImGuiIO& io = ImGui::GetIO();
150 io.DeltaTime = 1.0f/60.0f;
151 io.MousePos = mouse_pos;
152 io.MouseDown[0] = mouse_button_0;
153 io.MouseDown[1] = mouse_button_1;
154
155 // Call NewFrame(), after this point you can use ImGui::* functions anytime
156 ImGui::NewFrame();
157
158 // Most of your application code here
159 MyGameUpdate(); // may use any ImGui functions, e.g. ImGui::Begin("My window"); ImGui::Text("Hello, world!"); ImGui::End();
160 MyGameRender(); // may use any ImGui functions as well!
161
162 // Render & swap video buffers
163 ImGui::Render();
164 MyImGuiRenderFunction(ImGui::GetDrawData());
165 SwapBuffers();
166 }
167
168 // Shutdown
169 ImGui::DestroyContext();
170
171
172 - A minimal render function skeleton may be:
173
174 void void MyRenderFunction(ImDrawData* draw_data)
175 {
176 // TODO: Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled
177 // TODO: Setup viewport, orthographic projection matrix
178 // TODO: Setup shader: vertex { float2 pos, float2 uv, u32 color }, fragment shader sample color from 1 texture, multiply by vertex color.
179 for (int n = 0; n < draw_data->CmdListsCount; n++)
180 {
181 const ImDrawVert* vtx_buffer = cmd_list->VtxBuffer.Data; // vertex buffer generated by ImGui
182 const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data; // index buffer generated by ImGui
183 for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
184 {
185 const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
186 if (pcmd->UserCallback)
187 {
188 pcmd->UserCallback(cmd_list, pcmd);
189 }
190 else
191 {
192 // The texture for the draw call is specified by pcmd->TextureId.
193 // The vast majority of draw calls with use the imgui texture atlas, which value you have set yourself during initialization.
194 MyEngineBindTexture(pcmd->TextureId);
195
196 // We are using scissoring to clip some objects. All low-level graphics API supports it.
197 // If your engine doesn't support scissoring yet, you will get some small glitches (some elements outside their bounds) which you can fix later.
198 MyEngineScissor((int)pcmd->ClipRect.x, (int)pcmd->ClipRect.y, (int)(pcmd->ClipRect.z - pcmd->ClipRect.x), (int)(pcmd->ClipRect.w - pcmd->ClipRect.y));
199
200 // Render 'pcmd->ElemCount/3' indexed triangles.
201 // By default the indices ImDrawIdx are 16-bits, you can change them to 32-bits if your engine doesn't support 16-bits indices.
202 MyEngineDrawIndexedTriangles(pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer, vtx_buffer);
203 }
204 idx_buffer += pcmd->ElemCount;
205 }
206 }
207 }
208
209 - The examples/ folders contains many functional implementation of the pseudo-code above.
210 - When calling NewFrame(), the 'io.WantCaptureMouse'/'io.WantCaptureKeyboard'/'io.WantTextInput' flags are updated.
211 They tell you if ImGui intends to use your inputs. So for example, if 'io.WantCaptureMouse' is set you would typically want to hide
212 mouse inputs from the rest of your application. Read the FAQ below for more information about those flags.
213
214 USING GAMEPAD/KEYBOARD NAVIGATION [BETA]
215
216 - Ask questions and report issues at https://github.com/ocornut/imgui/issues/787
217 - The initial focus was to support game controllers, but keyboard is becoming increasingly and decently usable.
218 - Keyboard:
219 - Set io.NavFlags |= ImGuiNavFlags_EnableKeyboard to enable. NewFrame() will automatically fill io.NavInputs[] based on your io.KeyDown[] + io.KeyMap[] arrays.
220 - When keyboard navigation is active (io.NavActive + NavFlags_EnableKeyboard), the io.WantCaptureKeyboard flag will be set.
221 For more advanced uses, you may want to read from:
222 - io.NavActive: true when a window is focused and it doesn't have the ImGuiWindowFlags_NoNavInputs flag set.
223 - io.NavVisible: true when the navigation cursor is visible (and usually goes false when mouse is used).
224 - or query focus information with e.g. IsWindowFocused(), IsItemFocused() etc. functions.
225 Please reach out if you think the game vs navigation input sharing could be improved.
226 - Gamepad:
227 - Set io.NavFlags |= ImGuiNavFlags_EnableGamepad to enable. Fill the io.NavInputs[] fields before calling NewFrame(). Note that io.NavInputs[] is cleared by EndFrame().
228 - See 'enum ImGuiNavInput_' in imgui.h for a description of inputs. For each entry of io.NavInputs[], set the following values:
229 0.0f= not held. 1.0f= fully held. Pass intermediate 0.0f..1.0f values for analog triggers/sticks.
230 - We uses a simple >0.0f test for activation testing, and won't attempt to test for a dead-zone.
231 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, maybe a power curve, etc.).
232 - If you need to share inputs between your game and the imgui parts, the easiest approach is to go all-or-nothing, with a buttons combo to toggle the target.
233 Please reach out if you think the game vs navigation input sharing could be improved.
234 - Mouse:
235 - PS4 users: Consider emulating a mouse cursor with DualShock4 touch pad or a spare analog stick as a mouse-emulation fallback.
236 - Consoles/Tablet/Phone users: Consider using Synergy host (on your computer) + uSynergy.c (in your console/tablet/phone app) to use your PC mouse/keyboard.
237 - On a TV/console system where readability may be lower or mouse inputs may be awkward, you may want to set the ImGuiNavFlags_MoveMouse flag in io.NavFlags.
238 Enabling ImGuiNavFlags_MoveMouse instructs dear imgui to move your mouse cursor along with navigation movements.
239 When enabled, the NewFrame() function may alter 'io.MousePos' and set 'io.WantMoveMouse' to notify you that it wants the mouse cursor to be moved.
240 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.
241 (If you set the ImGuiNavFlags_MoveMouse flag but don't honor 'io.WantMoveMouse' properly, imgui will misbehave as it will see your mouse as moving back and forth.)
242 (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
243 to set a boolean to ignore your other external mouse positions until the external source is moved again.)
244
245
246 API BREAKING CHANGES
247 ====================
248
249 Occasionally introducing changes that are breaking the API. The breakage are generally minor and easy to fix.
250 Here is a change-log of API breaking changes, if you are using one of the functions listed, expect to have to fix some code.
251 Also read releases logs https://github.com/ocornut/imgui/releases for more details.
252
253 - 2018/02/18 (1.60) - BeginDragDropSource(): temporarily removed the optional mouse_button=0 parameter because it is really usable in typical conditions at the moment.
254 - 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.
255 - 2018/02/07 (1.60) - reorganized context handling to be more explicit,
256 - YOU NOW NEED TO CALL ImGui::CreateContext() AT THE BEGINNING OF YOUR APP, AND CALL ImGui::DestroyContext() AT THE END.
257 - removed Shutdown() function, as DestroyContext() serve this purpose.
258 - you may pass a ImFontAtlas* pointer to CreateContext() to share a font atlas between contexts. Otherwhise CreateContext() will create its own font atlas instance.
259 - removed allocator parameters from CreateContext(), they are now setup with SetAllocatorFunctions(), and shared by all contexts.
260 - removed the default global context and font atlas instance, which were confusing for users of DLL reloading and users of multiple contexts.
261 - 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.
262 - 2018/01/11 (1.60) - obsoleted IsAnyWindowHovered() in favor of IsWindowHovered(ImGuiHoveredFlags_AnyWindow). Kept redirection function (will obsolete).
263 - 2018/01/11 (1.60) - obsoleted IsAnyWindowFocused() in favor of IsWindowFocused(ImGuiFocusedFlags_AnyWindow). Kept redirection function (will obsolete).
264 - 2018/01/03 (1.60) - renamed ImGuiSizeConstraintCallback to ImGuiSizeCallback, ImGuiSizeConstraintCallbackData to ImGuiSizeCallbackData.
265 - 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.
266 - 2017/12/24 (1.53) - renamed the emblematic ShowTestWindow() function to ShowDemoWindow(). Kept redirection function (will obsolete).
267 - 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
268 - 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.
269 - 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.
270 - 2017/12/13 (1.53) - renamed GetItemsLineHeightWithSpacing() to GetFrameHeightWithSpacing(). Kept redirection function (will obsolete).
271 - 2017/12/13 (1.53) - obsoleted IsRootWindowFocused() in favor of using IsWindowFocused(ImGuiFocusedFlags_RootWindow). Kept redirection function (will obsolete).
272 - obsoleted IsRootWindowOrAnyChildFocused() in favor of using IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows). Kept redirection function (will obsolete).
273 - 2017/12/12 (1.53) - renamed ImGuiTreeNodeFlags_AllowOverlapMode to ImGuiTreeNodeFlags_AllowItemOverlap. Kept redirection enum (will obsolete).
274 - 2017/12/10 (1.53) - removed SetNextWindowContentWidth(), prefer using SetNextWindowContentSize(). Kept redirection function (will obsolete).
275 - 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.
276 - 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.
277 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.
278 - 2017/11/18 (1.53) - Style: removed ImGuiCol_ComboBg in favor of combo boxes using ImGuiCol_PopupBg for consistency.
279 - 2017/11/18 (1.53) - Style: renamed ImGuiCol_ChildWindowBg to ImGuiCol_ChildBg.
280 - 2017/11/18 (1.53) - Style: renamed style.ChildWindowRounding to style.ChildRounding, ImGuiStyleVar_ChildWindowRounding to ImGuiStyleVar_ChildRounding.
281 - 2017/11/02 (1.53) - obsoleted IsRootWindowOrAnyChildHovered() in favor of using IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows);
282 - 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.
283 - 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.
284 - 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.
285 removed the IsItemRectHovered()/IsWindowRectHovered() names introduced in 1.51 since they were merely more consistent names for the two functions we are now obsoleting.
286 - 2017/10/17 (1.52) - marked the old 5-parameters version of Begin() as obsolete (still available). Use SetNextWindowSize()+Begin() instead!
287 - 2017/10/11 (1.52) - renamed AlignFirstTextHeightToWidgets() to AlignTextToFramePadding(). Kept inline redirection function (will obsolete).
288 - 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).
289 - 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)".
290 - 2017/08/22 (1.51) - renamed IsItemHoveredRect() to IsItemRectHovered(). Kept inline redirection function (will obsolete). -> (1.52) use IsItemHovered(ImGuiHoveredFlags_RectOnly)!
291 - renamed IsMouseHoveringAnyWindow() to IsAnyWindowHovered() for consistency. Kept inline redirection function (will obsolete).
292 - renamed IsMouseHoveringWindow() to IsWindowRectHovered() for consistency. Kept inline redirection function (will obsolete).
293 - 2017/08/20 (1.51) - renamed GetStyleColName() to GetStyleColorName() for consistency.
294 - 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.
295 - 2017/08/15 (1.51) - marked the weird IMGUI_ONCE_UPON_A_FRAME helper macro as obsolete. prefer using the more explicit ImGuiOnceUponAFrame.
296 - 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.
297 - 2017/08/13 (1.51) - renamed ImGuiCol_Columns*** to ImGuiCol_Separator***. Kept redirection enums (will obsolete).
298 - 2017/08/11 (1.51) - renamed ImGuiSetCond_*** types and flags to ImGuiCond_***. Kept redirection enums (will obsolete).
299 - 2017/08/09 (1.51) - removed ValueColor() helpers, they are equivalent to calling Text(label) + SameLine() + ColorButton().
300 - 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.
301 - 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.
302 - 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))'
303 - 2017/07/20 (1.51) - removed IsPosHoveringAnyWindow(ImVec2), which was partly broken and misleading. ASSERT + redirect user to io.WantCaptureMouse
304 - 2017/05/26 (1.50) - removed ImFontConfig::MergeGlyphCenterV in favor of a more multipurpose ImFontConfig::GlyphOffset.
305 - 2017/05/01 (1.50) - renamed ImDrawList::PathFill() (rarely used directly) to ImDrawList::PathFillConvex() for clarity.
306 - 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().
307 - 2016/10/15 (1.50) - avoid 'void* user_data' parameter to io.SetClipboardTextFn/io.GetClipboardTextFn pointers. We pass io.ClipboardUserData to it.
308 - 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.
309 - 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.
310 - 2016/05/12 (1.49) - title bar (using ImGuiCol_TitleBg/ImGuiCol_TitleBgActive colors) isn't rendered over a window background (ImGuiCol_WindowBg color) anymore.
311 If your TitleBg/TitleBgActive alpha was 1.0f or you are using the default theme it will not affect you.
312 However 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.
313 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.
314 ImVec4 ConvertTitleBgCol(const ImVec4& win_bg_col, const ImVec4& title_bg_col)
315 {
316 float new_a = 1.0f - ((1.0f - win_bg_col.w) * (1.0f - title_bg_col.w)), k = title_bg_col.w / new_a;
317 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);
318 }
319 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.
320 - 2016/05/07 (1.49) - removed confusing set of GetInternalState(), GetInternalStateSize(), SetInternalState() functions. Now using CreateContext(), DestroyContext(), GetCurrentContext(), SetCurrentContext().
321 - 2016/05/02 (1.49) - renamed SetNextTreeNodeOpened() to SetNextTreeNodeOpen(), no redirection.
322 - 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).
323 - 2016/04/26 (1.49) - changed ImDrawList::PushClipRect(ImVec4 rect) to ImDraw::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.
324 - 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).
325 - 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)
326 - 2016/03/21 (1.48) - renamed GetWindowFont() to GetFont(), GetWindowFontSize() to GetFontSize(). Kept inline redirection function (will obsolete).
327 - 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.
328 - 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.
329 - 2015/12/27 (1.48) - fixed ImDrawList::AddRect() which used to render a rectangle 1 px too large on each axis.
330 - 2015/12/04 (1.47) - renamed Color() helpers to ValueColor() - dangerously named, rarely used and probably to be made obsolete.
331 - 2015/08/29 (1.45) - with the addition of horizontal scrollbar we made various fixes to inconsistencies with dealing with cursor position.
332 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.
333 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!
334 - 2015/08/29 (1.45) - renamed style.ScrollbarWidth to style.ScrollbarSize
335 - 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.
336 - 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
337 - 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.
338 you need to render your textured triangles with bilinear filtering to benefit from sub-pixel positioning of text.
339 - 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.
340 this necessary change will break your rendering function! the fix should be very easy. sorry for that :(
341 - 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.
342 - the signature of the io.RenderDrawListsFn handler has changed!
343 ImGui_XXXX_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count)
344 became:
345 ImGui_XXXX_RenderDrawLists(ImDrawData* draw_data).
346 argument 'cmd_lists' -> 'draw_data->CmdLists'
347 argument 'cmd_lists_count' -> 'draw_data->CmdListsCount'
348 ImDrawList 'commands' -> 'CmdBuffer'
349 ImDrawList 'vtx_buffer' -> 'VtxBuffer'
350 ImDrawList n/a -> 'IdxBuffer' (new)
351 ImDrawCmd 'vtx_count' -> 'ElemCount'
352 ImDrawCmd 'clip_rect' -> 'ClipRect'
353 ImDrawCmd 'user_callback' -> 'UserCallback'
354 ImDrawCmd 'texture_id' -> 'TextureId'
355 - 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.
356 - 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!
357 - refer to code in the examples/ folder or ask on the GitHub if you are unsure of how to upgrade. please upgrade!
358 - 2015/07/10 (1.43) - changed SameLine() parameters from int to float.
359 - 2015/07/02 (1.42) - renamed SetScrollPosHere() to SetScrollFromCursorPos(). Kept inline redirection function (will obsolete).
360 - 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.
361 - 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
362 - 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!
363 - 2015/05/31 (1.40) - renamed GetWindowCollapsed() to IsWindowCollapsed() for consistency. Kept inline redirection function (will obsolete).
364 - 2015/05/31 (1.40) - renamed IsRectClipped() to IsRectVisible() for consistency. Note that return value is opposite! Kept inline redirection function (will obsolete).
365 - 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.
366 - 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.
367 - 2015/05/03 (1.40) - removed style.AutoFitPadding, using style.WindowPadding makes more sense (the default values were already the same).
368 - 2015/04/13 (1.38) - renamed IsClipped() to IsRectClipped(). Kept inline redirection function until 1.50.
369 - 2015/04/09 (1.38) - renamed ImDrawList::AddArc() to ImDrawList::AddArcFast() for compatibility with future API
370 - 2015/04/03 (1.38) - removed ImGuiCol_CheckHovered, ImGuiCol_CheckActive, replaced with the more general ImGuiCol_FrameBgHovered, ImGuiCol_FrameBgActive.
371 - 2014/04/03 (1.38) - removed support for passing -FLT_MAX..+FLT_MAX as the range for a SliderFloat(). Use DragFloat() or Inputfloat() instead.
372 - 2015/03/17 (1.36) - renamed GetItemBoxMin()/GetItemBoxMax()/IsMouseHoveringBox() to GetItemRectMin()/GetItemRectMax()/IsMouseHoveringRect(). Kept inline redirection function until 1.50.
373 - 2015/03/15 (1.36) - renamed style.TreeNodeSpacing to style.IndentSpacing, ImGuiStyleVar_TreeNodeSpacing to ImGuiStyleVar_IndentSpacing
374 - 2015/03/13 (1.36) - renamed GetWindowIsFocused() to IsWindowFocused(). Kept inline redirection function until 1.50.
375 - 2015/03/08 (1.35) - renamed style.ScrollBarWidth to style.ScrollbarWidth (casing)
376 - 2015/02/27 (1.34) - renamed OpenNextNode(bool) to SetNextTreeNodeOpened(bool, ImGuiSetCond). Kept inline redirection function until 1.50.
377 - 2015/02/27 (1.34) - renamed ImGuiSetCondition_*** to ImGuiSetCond_***, and _FirstUseThisSession becomes _Once.
378 - 2015/02/11 (1.32) - changed text input callback ImGuiTextEditCallback return type from void-->int. reserved for future use, return 0 for now.
379 - 2015/02/10 (1.32) - renamed GetItemWidth() to CalcItemWidth() to clarify its evolving behavior
380 - 2015/02/08 (1.31) - renamed GetTextLineSpacing() to GetTextLineHeightWithSpacing()
381 - 2015/02/01 (1.31) - removed IO.MemReallocFn (unused)
382 - 2015/01/19 (1.30) - renamed ImGuiStorage::GetIntPtr()/GetFloatPtr() to GetIntRef()/GetIntRef() because Ptr was conflicting with actual pointer storage functions.
383 - 2015/01/11 (1.30) - big font/image API change! now loads TTF file. allow for multiple fonts. no need for a PNG loader.
384 (1.30) - removed GetDefaultFontData(). uses io.Fonts->GetTextureData*() API to retrieve uncompressed pixels.
385 this sequence:
386 const void* png_data;
387 unsigned int png_size;
388 ImGui::GetDefaultFontData(NULL, NULL, &png_data, &png_size);
389 // <Copy to GPU>
390 became:
391 unsigned char* pixels;
392 int width, height;
393 io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
394 // <Copy to GPU>
395 io.Fonts->TexID = (your_texture_identifier);
396 you now have much more flexibility to load multiple TTF fonts and manage the texture buffer for internal needs.
397 it is now recommended that you sample the font texture with bilinear interpolation.
398 (1.30) - added texture identifier in ImDrawCmd passed to your render function (we can now render images). make sure to set io.Fonts->TexID.
399 (1.30) - removed IO.PixelCenterOffset (unnecessary, can be handled in user projection matrix)
400 (1.30) - removed ImGui::IsItemFocused() in favor of ImGui::IsItemActive() which handles all widgets
401 - 2014/12/10 (1.18) - removed SetNewWindowDefaultPos() in favor of new generic API SetNextWindowPos(pos, ImGuiSetCondition_FirstUseEver)
402 - 2014/11/28 (1.17) - moved IO.Font*** options to inside the IO.Font-> structure (FontYOffset, FontTexUvForWhite, FontBaseScale, FontFallbackGlyph)
403 - 2014/11/26 (1.17) - reworked syntax of IMGUI_ONCE_UPON_A_FRAME helper macro to increase compiler compatibility
404 - 2014/11/07 (1.15) - renamed IsHovered() to IsItemHovered()
405 - 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)
406 - 2014/09/25 (1.13) - removed 'text_end' parameter from IO.SetClipboardTextFn (the string is now always zero-terminated for simplicity)
407 - 2014/09/24 (1.12) - renamed SetFontScale() to SetWindowFontScale()
408 - 2014/09/24 (1.12) - moved IM_MALLOC/IM_REALLOC/IM_FREE preprocessor defines to IO.MemAllocFn/IO.MemReallocFn/IO.MemFreeFn
409 - 2014/08/30 (1.09) - removed IO.FontHeight (now computed automatically)
410 - 2014/08/30 (1.09) - moved IMGUI_FONT_TEX_UV_FOR_WHITE preprocessor define to IO.FontTexUvForWhite
411 - 2014/08/28 (1.09) - changed the behavior of IO.PixelCenterOffset following various rendering fixes
412
413
414 ISSUES & TODO-LIST
415 ==================
416 See TODO.txt
417
418
419 FREQUENTLY ASKED QUESTIONS (FAQ), TIPS
420 ======================================
421
422 Q: How can I help?
423 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!
424 - Convince your company to fund development time! Individual users: you can also become a Patron (patreon.com/imgui) or donate on PayPal! See README.
425 - Disclose your usage of dear imgui via a dev blog post, a tweet, a screenshot, a mention somewhere etc.
426 You may post screenshot or links in the gallery threads (github.com/ocornut/imgui/issues/1269). Visuals are ideal as they inspire other programmers.
427 But even without visuals, disclosing your use of dear imgui help the library grow credibility, and help other teams and programmers with taking decisions.
428 - 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).
429
430 Q: How can I display an image? What is ImTextureID, how does it works?
431 A: ImTextureID is a void* used to pass renderer-agnostic texture references around until it hits your render function.
432 Dear ImGui knows nothing about what those bits represent, it just passes them around. It is up to you to decide what you want the void* to carry!
433 It could be an identifier to your OpenGL texture (cast GLuint to void*), a pointer to your custom engine material (cast MyMaterial* to void*), etc.
434 At the end of the chain, your renderer takes this void* to cast it back into whatever it needs to select a current texture to render.
435 Refer to examples applications, where each renderer (in a imgui_impl_xxxx.cpp file) is treating ImTextureID as a different thing.
436 (c++ tip: OpenGL uses integers to identify textures. You can safely store an integer into a void*, just cast it to void*, don't take it's address!)
437 To display a custom image/texture within an ImGui window, you may use ImGui::Image(), ImGui::ImageButton(), ImDrawList::AddImage() functions.
438 Dear ImGui will generate the geometry and draw calls using the ImTextureID that you passed and which your renderer can use.
439 You may call ImGui::ShowMetricsWindow() to explore active draw lists and visualize/understand how the draw data is generated.
440 It is your responsibility to get textures uploaded to your GPU.
441
442 Q: Can I have multiple widgets with the same label? Can I have widget without a label?
443 A: Yes. A primer on labels and the ID stack...
444
445 - Elements that are typically not clickable, such as Text() items don't need an ID.
446
447 - Interactive widgets require state to be carried over multiple frames (most typically Dear ImGui often needs to remember what is
448 the "active" widget). to do so they need a unique ID. unique ID are typically derived from a string label, an integer index or a pointer.
449
450 Button("OK"); // Label = "OK", ID = hash of "OK"
451 Button("Cancel"); // Label = "Cancel", ID = hash of "Cancel"
452
453 - ID are uniquely scoped within windows, tree nodes, etc. so no conflict can happen if you have two buttons called "OK"
454 in two different windows or in two different locations of a tree.
455
456 - If you have a same ID twice in the same location, you'll have a conflict:
457
458 Button("OK");
459 Button("OK"); // ID collision! Both buttons will be treated as the same.
460
461 Fear not! this is easy to solve and there are many ways to solve it!
462
463 - When passing a label you can optionally specify extra unique ID information within string itself.
464 Use "##" to pass a complement to the ID that won't be visible to the end-user.
465 This helps solving the simple collision cases when you know which items are going to be created.
466
467 Button("Play"); // Label = "Play", ID = hash of "Play"
468 Button("Play##foo1"); // Label = "Play", ID = hash of "Play##foo1" (different from above)
469 Button("Play##foo2"); // Label = "Play", ID = hash of "Play##foo2" (different from above)
470
471 - If you want to completely hide the label, but still need an ID:
472
473 Checkbox("##On", &b); // Label = "", ID = hash of "##On" (no label!)
474
475 - Occasionally/rarely you might want change a label while preserving a constant ID. This allows you to animate labels.
476 For example you may want to include varying information in a window title bar, but windows are uniquely identified by their ID..
477 Use "###" to pass a label that isn't part of ID:
478
479 Button("Hello###ID"; // Label = "Hello", ID = hash of "ID"
480 Button("World###ID"; // Label = "World", ID = hash of "ID" (same as above)
481
482 sprintf(buf, "My game (%f FPS)###MyGame", fps);
483 Begin(buf); // Variable label, ID = hash of "MyGame"
484
485 - Use PushID() / PopID() to create scopes and avoid ID conflicts within the same Window.
486 This is the most convenient way of distinguishing ID if you are iterating and creating many UI elements.
487 You can push a pointer, a string or an integer value. Remember that ID are formed from the concatenation of _everything_ in the ID stack!
488
489 for (int i = 0; i < 100; i++)
490 {
491 PushID(i);
492 Button("Click"); // Label = "Click", ID = hash of integer + "label" (unique)
493 PopID();
494 }
495
496 for (int i = 0; i < 100; i++)
497 {
498 MyObject* obj = Objects[i];
499 PushID(obj);
500 Button("Click"); // Label = "Click", ID = hash of pointer + "label" (unique)
501 PopID();
502 }
503
504 for (int i = 0; i < 100; i++)
505 {
506 MyObject* obj = Objects[i];
507 PushID(obj->Name);
508 Button("Click"); // Label = "Click", ID = hash of string + "label" (unique)
509 PopID();
510 }
511
512 - More example showing that you can stack multiple prefixes into the ID stack:
513
514 Button("Click"); // Label = "Click", ID = hash of "Click"
515 PushID("node");
516 Button("Click"); // Label = "Click", ID = hash of "node" + "Click"
517 PushID(my_ptr);
518 Button("Click"); // Label = "Click", ID = hash of "node" + ptr + "Click"
519 PopID();
520 PopID();
521
522 - Tree nodes implicitly creates a scope for you by calling PushID().
523
524 Button("Click"); // Label = "Click", ID = hash of "Click"
525 if (TreeNode("node"))
526 {
527 Button("Click"); // Label = "Click", ID = hash of "node" + "Click"
528 TreePop();
529 }
530
531 - When working with trees, ID are used to preserve the open/close state of each tree node.
532 Depending on your use cases you may want to use strings, indices or pointers as ID.
533 e.g. when displaying a single object that may change over time (dynamic 1-1 relationship), using a static string as ID will preserve your
534 node open/closed state when the targeted object change.
535 e.g. when displaying a list of objects, using indices or pointers as ID will preserve the node open/closed state differently.
536 experiment and see what makes more sense!
537
538 Q: How can I tell when Dear ImGui wants my mouse/keyboard inputs VS when I can pass them to my application?
539 A: You can read the 'io.WantCaptureMouse'/'io.WantCaptureKeyboard'/'ioWantTextInput' flags from the ImGuiIO structure.
540 - When 'io.WantCaptureMouse' or 'io.WantCaptureKeyboard' flags are set you may want to discard/hide the inputs from the rest of your application.
541 - 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).
542 Preferably read the flags after calling ImGui::NewFrame() to avoid them lagging by one frame. But reading those flags before calling NewFrame() is
543 also generally ok, as the bool toggles fairly rarely and you don't generally expect to interact with either Dear ImGui or your application during
544 the same frame when that transition occurs. Dear ImGui is tracking dragging and widget activity that may occur outside the boundary of a window,
545 so 'io.WantCaptureMouse' is more accurate and correct than checking if a window is hovered.
546 (Advanced note: text input releases focus on Return 'KeyDown', so the following Return 'KeyUp' event that your application receive will typically
547 have 'io.WantCaptureKeyboard=false'. Depending on your application logic it may or not be inconvenient. You might want to track which key-downs
548 were for Dear ImGui, e.g. with an array of bool, and filter out the corresponding key-ups.)
549
550 Q: How can I load a different font than the default? (default is an embedded version of ProggyClean.ttf, rendered at size 13)
551 A: Use the font atlas to load the TTF/OTF file you want:
552 ImGuiIO& io = ImGui::GetIO();
553 io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels);
554 io.Fonts->GetTexDataAsRGBA32() or GetTexDataAsAlpha8()
555
556 New programmers: remember that in C/C++ and most programming languages if you want to use a backslash \ in a string literal you need to write a double backslash "\\":
557 io.Fonts->AddFontFromFileTTF("MyDataFolder\MyFontFile.ttf", size_in_pixels); // WRONG
558 io.Fonts->AddFontFromFileTTF("MyDataFolder\\MyFontFile.ttf", size_in_pixels); // CORRECT
559 io.Fonts->AddFontFromFileTTF("MyDataFolder/MyFontFile.ttf", size_in_pixels); // ALSO CORRECT
560
561 Q: How can I easily use icons in my application?
562 A: The most convenient and practical way is to merge an icon font such as FontAwesome inside you main font. Then you can refer to icons within your
563 strings. Read 'How can I load multiple fonts?' and the file 'misc/fonts/README.txt' for instructions and useful header files.
564
565 Q: How can I load multiple fonts?
566 A: Use the font atlas to pack them into a single texture:
567 (Read misc/fonts/README.txt and the code in ImFontAtlas for more details.)
568
569 ImGuiIO& io = ImGui::GetIO();
570 ImFont* font0 = io.Fonts->AddFontDefault();
571 ImFont* font1 = io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels);
572 ImFont* font2 = io.Fonts->AddFontFromFileTTF("myfontfile2.ttf", size_in_pixels);
573 io.Fonts->GetTexDataAsRGBA32() or GetTexDataAsAlpha8()
574 // the first loaded font gets used by default
575 // use ImGui::PushFont()/ImGui::PopFont() to change the font at runtime
576
577 // Options
578 ImFontConfig config;
579 config.OversampleH = 3;
580 config.OversampleV = 1;
581 config.GlyphOffset.y -= 2.0f; // Move everything by 2 pixels up
582 config.GlyphExtraSpacing.x = 1.0f; // Increase spacing between characters
583 io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, &config);
584
585 // Combine multiple fonts into one (e.g. for icon fonts)
586 ImWchar ranges[] = { 0xf000, 0xf3ff, 0 };
587 ImFontConfig config;
588 config.MergeMode = true;
589 io.Fonts->AddFontDefault();
590 io.Fonts->LoadFromFileTTF("fontawesome-webfont.ttf", 16.0f, &config, ranges); // Merge icon font
591 io.Fonts->LoadFromFileTTF("myfontfile.ttf", size_pixels, NULL, &config, io.Fonts->GetGlyphRangesJapanese()); // Merge japanese glyphs
592
593 Q: How can I display and input non-Latin characters such as Chinese, Japanese, Korean, Cyrillic?
594 A: When loading a font, pass custom Unicode ranges to specify the glyphs to load.
595
596 // Add default Japanese ranges
597 io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels, NULL, io.Fonts->GetGlyphRangesJapanese());
598
599 // 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)
600 ImVector<ImWchar> ranges;
601 ImFontAtlas::GlyphRangesBuilder builder;
602 builder.AddText("Hello world"); // Add a string (here "Hello world" contains 7 unique characters)
603 builder.AddChar(0x7262); // Add a specific character
604 builder.AddRanges(io.Fonts->GetGlyphRangesJapanese()); // Add one of the default ranges
605 builder.BuildRanges(&ranges); // Build the final result (ordered ranges with all the unique characters submitted)
606 io.Fonts->AddFontFromFileTTF("myfontfile.ttf", size_in_pixels, NULL, ranges.Data);
607
608 All your strings needs to use UTF-8 encoding. In C++11 you can encode a string literal in UTF-8 by using the u8"hello" syntax.
609 Specifying literal in your source code using a local code page (such as CP-923 for Japanese or CP-1251 for Cyrillic) will NOT work!
610 Otherwise you can convert yourself to UTF-8 or load text data from file already saved as UTF-8.
611
612 Text input: it is up to your application to pass the right character code to io.AddInputCharacter(). The applications in examples/ are doing that.
613 For languages using IME, on Windows you can copy the Hwnd of your application to io.ImeWindowHandle.
614 The default implementation of io.ImeSetInputScreenPosFn() on Windows will set your IME position correctly.
615
616 Q: How can I preserve my Dear ImGui context across reloading a DLL? (loss of the global/static variables)
617 A: Create your own context 'ctx = CreateContext()' + 'SetCurrentContext(ctx)' and your own font atlas 'ctx->GetIO().Fonts = new ImFontAtlas()'
618 so you don't rely on the default globals.
619
620 Q: How can I use the drawing facilities without an ImGui window? (using ImDrawList API)
621 A: - You can create a dummy window. Call Begin() with NoTitleBar|NoResize|NoMove|NoScrollbar|NoSavedSettings|NoInputs flag,
622 push a ImGuiCol_WindowBg with zero alpha, then retrieve the ImDrawList* via GetWindowDrawList() and draw to it in any way you like.
623 - You can call ImGui::GetOverlayDrawList() and use this draw list to display contents over every other imgui windows.
624 - You can create your own ImDrawList instance. You'll need to initialize them ImGui::GetDrawListSharedData(), or create your own ImDrawListSharedData.
625
626 Q: I integrated Dear ImGui in my engine and the text or lines are blurry..
627 A: In your Render function, try translating your projection matrix by (0.5f,0.5f) or (0.375f,0.375f).
628 Also make sure your orthographic projection matrix and io.DisplaySize matches your actual framebuffer dimension.
629
630 Q: I integrated Dear ImGui in my engine and some elements are clipping or disappearing when I move windows around..
631 A: You are probably mishandling the clipping rectangles in your render function.
632 Rectangles provided by ImGui are defined as (x1=left,y1=top,x2=right,y2=bottom) and NOT as (x1,y1,width,height).
633
634
635 - tip: you can call Begin() multiple times with the same name during the same frame, it will keep appending to the same window.
636 this is also useful to set yourself in the context of another window (to get/set other settings)
637 - tip: you can create widgets without a Begin()/End() block, they will go in an implicit window called "Debug".
638 - 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
639 of a deep nested inner loop in your code.
640 - tip: you can call Render() multiple times (e.g for VR renders).
641 - tip: call and read the ShowDemoWindow() code in imgui_demo.cpp for more example of how to use ImGui!
642
643 */
644
645 #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
646 #define _CRT_SECURE_NO_WARNINGS
647 #endif
648
649 #include "imgui.h"
650 #define IMGUI_DEFINE_MATH_OPERATORS
651 #include "imgui_internal.h"
652
653 #include <ctype.h> // toupper, isprint
654 #include <stdlib.h> // NULL, malloc, free, qsort, atoi
655 #include <stdio.h> // vsnprintf, sscanf, printf
656 #if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier
657 #include <stddef.h> // intptr_t
658 #else
659 #include <stdint.h> // intptr_t
660 #endif
661
662 #define IMGUI_DEBUG_NAV_SCORING 0
663 #define IMGUI_DEBUG_NAV_RECTS 0
664
665 // Visual Studio warnings
666 #ifdef _MSC_VER
667 #pragma warning(disable : 4127) // condition expression is constant
668 #pragma warning(disable : 4505) // unreferenced local function has been removed (stb stuff)
669 #pragma warning(disable : 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen
670 #endif
671
672 // Clang warnings with -Weverything
673 #ifdef __clang__
674 #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!
675 #pragma clang diagnostic ignored "-Wold-style-cast" // warning : use of old-style cast // yes, they are more terse.
676 #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.
677 #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.
678 #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.
679 #pragma clang diagnostic ignored "-Wglobal-constructors" // warning : declaration requires a global destructor // similar to above, not sure what the exact difference it.
680 #pragma clang diagnostic ignored "-Wsign-conversion" // warning : implicit conversion changes signedness //
681 #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.
682 #pragma clang diagnostic ignored "-Wint-to-void-pointer-cast" // warning : cast to 'void *' from smaller integer type 'int' //
683 #elif defined(__GNUC__)
684 #pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used
685 #pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size
686 #pragma GCC diagnostic ignored "-Wformat" // warning: format '%p' expects argument of type 'void*', but argument 6 has type 'ImGuiWindow*'
687 #pragma GCC diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function
688 #pragma GCC diagnostic ignored "-Wconversion" // warning: conversion to 'xxxx' from 'xxxx' may alter its value
689 #pragma GCC diagnostic ignored "-Wcast-qual" // warning: cast from type 'xxxx' to type 'xxxx' casts away qualifiers
690 #pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked
691 #pragma GCC diagnostic ignored "-Wstrict-overflow" // warning: assuming signed overflow does not occur when assuming that (X - c) > X is always false
692 #endif
693
694 // Enforce cdecl calling convention for functions called by the standard library, in case compilation settings changed the default to e.g. __vectorcall
695 #ifdef _MSC_VER
696 #define IMGUI_CDECL __cdecl
697 #else
698 #define IMGUI_CDECL
699 #endif
700
701 //-------------------------------------------------------------------------
702 // Forward Declarations
703 //-------------------------------------------------------------------------
704
705 static bool IsKeyPressedMap(ImGuiKey key, bool repeat = true);
706
707 static ImFont* GetDefaultFont();
708 static void SetCurrentWindow(ImGuiWindow* window);
709 static void SetWindowScrollX(ImGuiWindow* window, float new_scroll_x);
710 static void SetWindowScrollY(ImGuiWindow* window, float new_scroll_y);
711 static void SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond);
712 static void SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond);
713 static void SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond);
714 static ImGuiWindow* FindHoveredWindow();
715 static ImGuiWindow* CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFlags flags);
716 static void CheckStacksSize(ImGuiWindow* window, bool write);
717 static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window);
718
719 static void AddDrawListToDrawData(ImVector<ImDrawList*>* out_list, ImDrawList* draw_list);
720 static void AddWindowToDrawData(ImVector<ImDrawList*>* out_list, ImGuiWindow* window);
721 static void AddWindowToSortedBuffer(ImVector<ImGuiWindow*>* out_sorted_windows, ImGuiWindow* window);
722
723 static ImGuiWindowSettings* AddWindowSettings(const char* name);
724
725 static void LoadIniSettingsFromDisk(const char* ini_filename);
726 static void LoadIniSettingsFromMemory(const char* buf);
727 static void SaveIniSettingsToDisk(const char* ini_filename);
728 static void SaveIniSettingsToMemory(ImVector<char>& out_buf);
729 static void MarkIniSettingsDirty(ImGuiWindow* window);
730
731 static ImRect GetViewportRect();
732
733 static void ClosePopupToLevel(int remaining);
734 static ImGuiWindow* GetFrontMostModalRootWindow();
735
736 static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data);
737 static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end);
738 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);
739
740 static inline void DataTypeFormatString(ImGuiDataType data_type, void* data_ptr, const char* display_format, char* buf, int buf_size);
741 static inline void DataTypeFormatString(ImGuiDataType data_type, void* data_ptr, int decimal_precision, char* buf, int buf_size);
742 static void DataTypeApplyOp(ImGuiDataType data_type, int op, void* value1, const void* value2);
743 static bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* scalar_format);
744
745 namespace ImGui
746 {
747 static void NavUpdate();
748 static void NavUpdateWindowing();
749 static void NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, const ImGuiID id);
750
751 static void UpdateMovingWindow();
752 static void UpdateManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4]);
753 static void FocusFrontMostActiveWindow(ImGuiWindow* ignore_window);
754 } // namespace ImGui
755
756 //-----------------------------------------------------------------------------
757 // Platform dependent default implementations
758 //-----------------------------------------------------------------------------
759
760 static const char* GetClipboardTextFn_DefaultImpl(void* user_data);
761 static void SetClipboardTextFn_DefaultImpl(void* user_data, const char* text);
762 static void ImeSetInputScreenPosFn_DefaultImpl(int x, int y);
763
764 //-----------------------------------------------------------------------------
765 // Context
766 //-----------------------------------------------------------------------------
767
768 // Current context pointer. Implicitely used by all ImGui functions. Always assumed to be != NULL.
769 // CreateContext() will automatically set this pointer if it is NULL. Change to a different context by calling ImGui::SetCurrentContext().
770 // If you use DLL hotreloading you might need to call SetCurrentContext() after reloading code from this file.
771 // 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:
772 // - 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
773 // - Having multiple instances of the ImGui code compiled inside different namespace (easiest/safest, if you have a finite number of contexts)
774 #ifndef GImGui
775 ImGuiContext* GImGui = NULL;
776 #endif
777
778 // Memory Allocator functions. Use SetAllocatorFunctions() to change them.
779 // If you use DLL hotreloading you might need to call SetAllocatorFunctions() after reloading code from this file.
780 // 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.
781 #ifndef IMGUI_DISABLE_DEFAULT_ALLOCATORS
MallocWrapper(size_t size,void * user_data)782 static void* MallocWrapper(size_t size, void* user_data)
783 {
784 (void)user_data;
785 return malloc(size);
786 }
FreeWrapper(void * ptr,void * user_data)787 static void FreeWrapper(void* ptr, void* user_data)
788 {
789 (void)user_data;
790 free(ptr);
791 }
792 #else
MallocWrapper(size_t size,void * user_data)793 static void* MallocWrapper(size_t size, void* user_data)
794 {
795 (void)user_data;
796 (void)size;
797 IM_ASSERT(0);
798 return NULL;
799 }
FreeWrapper(void * ptr,void * user_data)800 static void FreeWrapper(void* ptr, void* user_data)
801 {
802 (void)user_data;
803 (void)ptr;
804 IM_ASSERT(0);
805 }
806 #endif
807
808 static void* (*GImAllocatorAllocFunc)(size_t size, void* user_data) = MallocWrapper;
809 static void (*GImAllocatorFreeFunc)(void* ptr, void* user_data) = FreeWrapper;
810 static void* GImAllocatorUserData = NULL;
811 static size_t GImAllocatorActiveAllocationsCount = 0;
812
813 //-----------------------------------------------------------------------------
814 // User facing structures
815 //-----------------------------------------------------------------------------
816
ImGuiStyle()817 ImGuiStyle::ImGuiStyle()
818 {
819 Alpha = 1.0f; // Global alpha applies to everything in ImGui
820 WindowPadding = ImVec2(8, 8); // Padding within a window
821 WindowRounding = 7.0f; // Radius of window corners rounding. Set to 0.0f to have rectangular windows
822 WindowBorderSize = 1.0f; // Thickness of border around windows. Generally set to 0.0f or 1.0f. Other values not well tested.
823 WindowMinSize = ImVec2(32, 32); // Minimum window size
824 WindowTitleAlign = ImVec2(0.0f, 0.5f); // Alignment for title bar text
825 ChildRounding = 0.0f; // Radius of child window corners rounding. Set to 0.0f to have rectangular child windows
826 ChildBorderSize = 1.0f; // Thickness of border around child windows. Generally set to 0.0f or 1.0f. Other values not well tested.
827 PopupRounding = 0.0f; // Radius of popup window corners rounding. Set to 0.0f to have rectangular child windows
828 PopupBorderSize = 1.0f; // Thickness of border around popup or tooltip windows. Generally set to 0.0f or 1.0f. Other values not well tested.
829 FramePadding = ImVec2(4, 3); // Padding within a framed rectangle (used by most widgets)
830 FrameRounding = 0.0f; // Radius of frame corners rounding. Set to 0.0f to have rectangular frames (used by most widgets).
831 FrameBorderSize = 0.0f; // Thickness of border around frames. Generally set to 0.0f or 1.0f. Other values not well tested.
832 ItemSpacing = ImVec2(8, 4); // Horizontal and vertical spacing between widgets/lines
833 ItemInnerSpacing = ImVec2(4, 4); // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label)
834 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!
835 IndentSpacing = 21.0f; // Horizontal spacing when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2).
836 ColumnsMinSpacing = 6.0f; // Minimum horizontal spacing between two columns
837 ScrollbarSize = 16.0f; // Width of the vertical scrollbar, Height of the horizontal scrollbar
838 ScrollbarRounding = 9.0f; // Radius of grab corners rounding for scrollbar
839 GrabMinSize = 10.0f; // Minimum width/height of a grab box for slider/scrollbar
840 GrabRounding = 0.0f; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs.
841 ButtonTextAlign = ImVec2(0.5f, 0.5f); // Alignment of button text when button is larger than text.
842 DisplayWindowPadding = ImVec2(22, 22); // Window positions are clamped to be visible within the display area by at least this amount. Only covers regular windows.
843 DisplaySafeAreaPadding = ImVec2(4, 4); // 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.
844 MouseCursorScale = 1.0f; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later.
845 AntiAliasedLines = true; // Enable anti-aliasing on lines/borders. Disable if you are really short on CPU/GPU.
846 AntiAliasedFill = true; // Enable anti-aliasing on filled shapes (rounded rectangles, circles, etc.)
847 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.
848
849 ImGui::StyleColorsClassic(this);
850 }
851
852 // 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.
853 // Important: This operation is lossy because we round all sizes to integer. If you need to change your scale multiples, call this over a freshly initialized ImGuiStyle structure rather than scaling multiple times.
ScaleAllSizes(float scale_factor)854 void ImGuiStyle::ScaleAllSizes(float scale_factor)
855 {
856 WindowPadding = ImFloor(WindowPadding * scale_factor);
857 WindowRounding = ImFloor(WindowRounding * scale_factor);
858 WindowMinSize = ImFloor(WindowMinSize * scale_factor);
859 ChildRounding = ImFloor(ChildRounding * scale_factor);
860 PopupRounding = ImFloor(PopupRounding * scale_factor);
861 FramePadding = ImFloor(FramePadding * scale_factor);
862 FrameRounding = ImFloor(FrameRounding * scale_factor);
863 ItemSpacing = ImFloor(ItemSpacing * scale_factor);
864 ItemInnerSpacing = ImFloor(ItemInnerSpacing * scale_factor);
865 TouchExtraPadding = ImFloor(TouchExtraPadding * scale_factor);
866 IndentSpacing = ImFloor(IndentSpacing * scale_factor);
867 ColumnsMinSpacing = ImFloor(ColumnsMinSpacing * scale_factor);
868 ScrollbarSize = ImFloor(ScrollbarSize * scale_factor);
869 ScrollbarRounding = ImFloor(ScrollbarRounding * scale_factor);
870 GrabMinSize = ImFloor(GrabMinSize * scale_factor);
871 GrabRounding = ImFloor(GrabRounding * scale_factor);
872 DisplayWindowPadding = ImFloor(DisplayWindowPadding * scale_factor);
873 DisplaySafeAreaPadding = ImFloor(DisplaySafeAreaPadding * scale_factor);
874 MouseCursorScale = ImFloor(MouseCursorScale * scale_factor);
875 }
876
ImGuiIO()877 ImGuiIO::ImGuiIO()
878 {
879 // Most fields are initialized with zero
880 memset(this, 0, sizeof(*this));
881
882 // Settings
883 DisplaySize = ImVec2(-1.0f, -1.0f);
884 DeltaTime = 1.0f / 60.0f;
885 NavFlags = 0x00;
886 IniSavingRate = 5.0f;
887 IniFilename = "imgui.ini";
888 LogFilename = "imgui_log.txt";
889 MouseDoubleClickTime = 0.30f;
890 MouseDoubleClickMaxDist = 6.0f;
891 for (int i = 0; i < ImGuiKey_COUNT; i++)
892 KeyMap[i] = -1;
893 KeyRepeatDelay = 0.250f;
894 KeyRepeatRate = 0.050f;
895 UserData = NULL;
896
897 Fonts = NULL;
898 FontGlobalScale = 1.0f;
899 FontDefault = NULL;
900 FontAllowUserScaling = false;
901 DisplayFramebufferScale = ImVec2(1.0f, 1.0f);
902 DisplayVisibleMin = DisplayVisibleMax = ImVec2(0.0f, 0.0f);
903
904 // Advanced/subtle behaviors
905 #ifdef __APPLE__
906 OptMacOSXBehaviors = true; // Set Mac OS X style defaults based on __APPLE__ compile time flag
907 #else
908 OptMacOSXBehaviors = false;
909 #endif
910 OptCursorBlink = true;
911
912 // Settings (User Functions)
913 GetClipboardTextFn = GetClipboardTextFn_DefaultImpl; // Platform dependent default implementations
914 SetClipboardTextFn = SetClipboardTextFn_DefaultImpl;
915 ClipboardUserData = NULL;
916 ImeSetInputScreenPosFn = ImeSetInputScreenPosFn_DefaultImpl;
917 ImeWindowHandle = NULL;
918
919 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
920 RenderDrawListsFn = NULL;
921 #endif
922
923 // Input (NB: we already have memset zero the entire structure)
924 MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
925 MousePosPrev = ImVec2(-FLT_MAX, -FLT_MAX);
926 MouseDragThreshold = 6.0f;
927 for (int i = 0; i < IM_ARRAYSIZE(MouseDownDuration); i++) MouseDownDuration[i] = MouseDownDurationPrev[i] = -1.0f;
928 for (int i = 0; i < IM_ARRAYSIZE(KeysDownDuration); i++) KeysDownDuration[i] = KeysDownDurationPrev[i] = -1.0f;
929 for (int i = 0; i < IM_ARRAYSIZE(NavInputsDownDuration); i++) NavInputsDownDuration[i] = -1.0f;
930 }
931
932 // Pass in translated ASCII characters for text input.
933 // - with glfw you can get those from the callback set in glfwSetCharCallback()
934 // - on Windows you can get those using ToAscii+keyboard state, or via the WM_CHAR message
AddInputCharacter(ImWchar c)935 void ImGuiIO::AddInputCharacter(ImWchar c)
936 {
937 const int n = ImStrlenW(InputCharacters);
938 if (n + 1 < IM_ARRAYSIZE(InputCharacters))
939 {
940 InputCharacters[n] = c;
941 InputCharacters[n + 1] = '\0';
942 }
943 }
944
AddInputCharactersUTF8(const char * utf8_chars)945 void ImGuiIO::AddInputCharactersUTF8(const char* utf8_chars)
946 {
947 // We can't pass more wchars than ImGuiIO::InputCharacters[] can hold so don't convert more
948 const int wchars_buf_len = sizeof(ImGuiIO::InputCharacters) / sizeof(ImWchar);
949 ImWchar wchars[wchars_buf_len];
950 ImTextStrFromUtf8(wchars, wchars_buf_len, utf8_chars, NULL);
951 for (int i = 0; i < wchars_buf_len && wchars[i] != 0; i++)
952 AddInputCharacter(wchars[i]);
953 }
954
955 //-----------------------------------------------------------------------------
956 // HELPERS
957 //-----------------------------------------------------------------------------
958
959 #define IM_F32_TO_INT8_UNBOUND(_VAL) ((int)((_VAL)*255.0f + ((_VAL) >= 0 ? 0.5f : -0.5f))) // Unsaturated, for display purpose
960 #define IM_F32_TO_INT8_SAT(_VAL) ((int)(ImSaturate(_VAL) * 255.0f + 0.5f)) // Saturated, always output 0..255
961
962 // Play it nice with Windows users. Notepad in 2015 still doesn't display text data with Unix-style \n.
963 #ifdef _WIN32
964 #define IM_NEWLINE "\r\n"
965 #else
966 #define IM_NEWLINE "\n"
967 #endif
968
ImLineClosestPoint(const ImVec2 & a,const ImVec2 & b,const ImVec2 & p)969 ImVec2 ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& p)
970 {
971 ImVec2 ap = p - a;
972 ImVec2 ab_dir = b - a;
973 float dot = ap.x * ab_dir.x + ap.y * ab_dir.y;
974 if (dot < 0.0f)
975 return a;
976 float ab_len_sqr = ab_dir.x * ab_dir.x + ab_dir.y * ab_dir.y;
977 if (dot > ab_len_sqr)
978 return b;
979 return a + ab_dir * dot / ab_len_sqr;
980 }
981
ImTriangleContainsPoint(const ImVec2 & a,const ImVec2 & b,const ImVec2 & c,const ImVec2 & p)982 bool ImTriangleContainsPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p)
983 {
984 bool b1 = ((p.x - b.x) * (a.y - b.y) - (p.y - b.y) * (a.x - b.x)) < 0.0f;
985 bool b2 = ((p.x - c.x) * (b.y - c.y) - (p.y - c.y) * (b.x - c.x)) < 0.0f;
986 bool b3 = ((p.x - a.x) * (c.y - a.y) - (p.y - a.y) * (c.x - a.x)) < 0.0f;
987 return ((b1 == b2) && (b2 == b3));
988 }
989
ImTriangleBarycentricCoords(const ImVec2 & a,const ImVec2 & b,const ImVec2 & c,const ImVec2 & p,float & out_u,float & out_v,float & out_w)990 void ImTriangleBarycentricCoords(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p, float& out_u, float& out_v, float& out_w)
991 {
992 ImVec2 v0 = b - a;
993 ImVec2 v1 = c - a;
994 ImVec2 v2 = p - a;
995 const float denom = v0.x * v1.y - v1.x * v0.y;
996 out_v = (v2.x * v1.y - v1.x * v2.y) / denom;
997 out_w = (v0.x * v2.y - v2.x * v0.y) / denom;
998 out_u = 1.0f - out_v - out_w;
999 }
1000
ImTriangleClosestPoint(const ImVec2 & a,const ImVec2 & b,const ImVec2 & c,const ImVec2 & p)1001 ImVec2 ImTriangleClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p)
1002 {
1003 ImVec2 proj_ab = ImLineClosestPoint(a, b, p);
1004 ImVec2 proj_bc = ImLineClosestPoint(b, c, p);
1005 ImVec2 proj_ca = ImLineClosestPoint(c, a, p);
1006 float dist2_ab = ImLengthSqr(p - proj_ab);
1007 float dist2_bc = ImLengthSqr(p - proj_bc);
1008 float dist2_ca = ImLengthSqr(p - proj_ca);
1009 float m = ImMin(dist2_ab, ImMin(dist2_bc, dist2_ca));
1010 if (m == dist2_ab)
1011 return proj_ab;
1012 if (m == dist2_bc)
1013 return proj_bc;
1014 return proj_ca;
1015 }
1016
ImStricmp(const char * str1,const char * str2)1017 int ImStricmp(const char* str1, const char* str2)
1018 {
1019 int d;
1020 while ((d = toupper(*str2) - toupper(*str1)) == 0 && *str1)
1021 {
1022 str1++;
1023 str2++;
1024 }
1025 return d;
1026 }
1027
ImStrnicmp(const char * str1,const char * str2,size_t count)1028 int ImStrnicmp(const char* str1, const char* str2, size_t count)
1029 {
1030 int d = 0;
1031 while (count > 0 && (d = toupper(*str2) - toupper(*str1)) == 0 && *str1)
1032 {
1033 str1++;
1034 str2++;
1035 count--;
1036 }
1037 return d;
1038 }
1039
ImStrncpy(char * dst,const char * src,size_t count)1040 void ImStrncpy(char* dst, const char* src, size_t count)
1041 {
1042 if (count < 1) return;
1043 strncpy(dst, src, count);
1044 dst[count - 1] = 0;
1045 }
1046
ImStrdup(const char * str)1047 char* ImStrdup(const char* str)
1048 {
1049 size_t len = strlen(str) + 1;
1050 void* buf = ImGui::MemAlloc(len);
1051 return (char*)memcpy(buf, (const void*)str, len);
1052 }
1053
ImStrchrRange(const char * str,const char * str_end,char c)1054 char* ImStrchrRange(const char* str, const char* str_end, char c)
1055 {
1056 for (; str < str_end; str++)
1057 if (*str == c)
1058 return (char*)str;
1059 return NULL;
1060 }
1061
ImStrlenW(const ImWchar * str)1062 int ImStrlenW(const ImWchar* str)
1063 {
1064 int n = 0;
1065 while (*str++) n++;
1066 return n;
1067 }
1068
ImStrbolW(const ImWchar * buf_mid_line,const ImWchar * buf_begin)1069 const ImWchar* ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* buf_begin) // find beginning-of-line
1070 {
1071 while (buf_mid_line > buf_begin && buf_mid_line[-1] != '\n')
1072 buf_mid_line--;
1073 return buf_mid_line;
1074 }
1075
ImStristr(const char * haystack,const char * haystack_end,const char * needle,const char * needle_end)1076 const char* ImStristr(const char* haystack, const char* haystack_end, const char* needle, const char* needle_end)
1077 {
1078 if (!needle_end)
1079 needle_end = needle + strlen(needle);
1080
1081 const char un0 = (char)toupper(*needle);
1082 while ((!haystack_end && *haystack) || (haystack_end && haystack < haystack_end))
1083 {
1084 if (toupper(*haystack) == un0)
1085 {
1086 const char* b = needle + 1;
1087 for (const char* a = haystack + 1; b < needle_end; a++, b++)
1088 if (toupper(*a) != toupper(*b))
1089 break;
1090 if (b == needle_end)
1091 return haystack;
1092 }
1093 haystack++;
1094 }
1095 return NULL;
1096 }
1097
ImAtoi(const char * src,int * output)1098 static const char* ImAtoi(const char* src, int* output)
1099 {
1100 int negative = 0;
1101 if (*src == '-')
1102 {
1103 negative = 1;
1104 src++;
1105 }
1106 if (*src == '+')
1107 {
1108 src++;
1109 }
1110 int v = 0;
1111 while (*src >= '0' && *src <= '9')
1112 v = (v * 10) + (*src++ - '0');
1113 *output = negative ? -v : v;
1114 return src;
1115 }
1116
1117 // A) MSVC version appears to return -1 on overflow, whereas glibc appears to return total count (which may be >= buf_size).
1118 // 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.
1119 // B) When buf==NULL vsnprintf() will return the output size.
1120 #ifndef IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS
ImFormatString(char * buf,size_t buf_size,const char * fmt,...)1121 int ImFormatString(char* buf, size_t buf_size, const char* fmt, ...)
1122 {
1123 va_list args;
1124 va_start(args, fmt);
1125 int w = vsnprintf(buf, buf_size, fmt, args);
1126 va_end(args);
1127 if (buf == NULL)
1128 return w;
1129 if (w == -1 || w >= (int)buf_size)
1130 w = (int)buf_size - 1;
1131 buf[w] = 0;
1132 return w;
1133 }
1134
ImFormatStringV(char * buf,size_t buf_size,const char * fmt,va_list args)1135 int ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args)
1136 {
1137 int w = vsnprintf(buf, buf_size, fmt, args);
1138 if (buf == NULL)
1139 return w;
1140 if (w == -1 || w >= (int)buf_size)
1141 w = (int)buf_size - 1;
1142 buf[w] = 0;
1143 return w;
1144 }
1145 #endif // #ifdef IMGUI_DISABLE_FORMAT_STRING_FUNCTIONS
1146
1147 // Pass data_size==0 for zero-terminated strings
1148 // FIXME-OPT: Replace with e.g. FNV1a hash? CRC32 pretty much randomly access 1KB. Need to do proper measurements.
ImHash(const void * data,int data_size,ImU32 seed)1149 ImU32 ImHash(const void* data, int data_size, ImU32 seed)
1150 {
1151 static ImU32 crc32_lut[256] = {0};
1152 if (!crc32_lut[1])
1153 {
1154 const ImU32 polynomial = 0xEDB88320;
1155 for (ImU32 i = 0; i < 256; i++)
1156 {
1157 ImU32 crc = i;
1158 for (ImU32 j = 0; j < 8; j++)
1159 crc = (crc >> 1) ^ (ImU32(-int(crc & 1)) & polynomial);
1160 crc32_lut[i] = crc;
1161 }
1162 }
1163
1164 seed = ~seed;
1165 ImU32 crc = seed;
1166 const unsigned char* current = (const unsigned char*)data;
1167
1168 if (data_size > 0)
1169 {
1170 // Known size
1171 while (data_size--)
1172 crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ *current++];
1173 }
1174 else
1175 {
1176 // Zero-terminated string
1177 while (unsigned char c = *current++)
1178 {
1179 // We support a syntax of "label###id" where only "###id" is included in the hash, and only "label" gets displayed.
1180 // Because this syntax is rarely used we are optimizing for the common case.
1181 // - If we reach ### in the string we discard the hash so far and reset to the seed.
1182 // - We don't do 'current += 2; continue;' after handling ### to keep the code smaller.
1183 if (c == '#' && current[0] == '#' && current[1] == '#')
1184 crc = seed;
1185 crc = (crc >> 8) ^ crc32_lut[(crc & 0xFF) ^ c];
1186 }
1187 }
1188 return ~crc;
1189 }
1190
1191 //-----------------------------------------------------------------------------
1192 // ImText* helpers
1193 //-----------------------------------------------------------------------------
1194
1195 // Convert UTF-8 to 32-bits character, process single character input.
1196 // Based on stb_from_utf8() from github.com/nothings/stb/
1197 // We handle UTF-8 decoding error by skipping forward.
ImTextCharFromUtf8(unsigned int * out_char,const char * in_text,const char * in_text_end)1198 int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* in_text_end)
1199 {
1200 unsigned int c = (unsigned int)-1;
1201 const unsigned char* str = (const unsigned char*)in_text;
1202 if (!(*str & 0x80))
1203 {
1204 c = (unsigned int)(*str++);
1205 *out_char = c;
1206 return 1;
1207 }
1208 if ((*str & 0xe0) == 0xc0)
1209 {
1210 *out_char = 0xFFFD; // will be invalid but not end of string
1211 if (in_text_end && in_text_end - (const char*)str < 2) return 1;
1212 if (*str < 0xc2) return 2;
1213 c = (unsigned int)((*str++ & 0x1f) << 6);
1214 if ((*str & 0xc0) != 0x80) return 2;
1215 c += (*str++ & 0x3f);
1216 *out_char = c;
1217 return 2;
1218 }
1219 if ((*str & 0xf0) == 0xe0)
1220 {
1221 *out_char = 0xFFFD; // will be invalid but not end of string
1222 if (in_text_end && in_text_end - (const char*)str < 3) return 1;
1223 if (*str == 0xe0 && (str[1] < 0xa0 || str[1] > 0xbf)) return 3;
1224 if (*str == 0xed && str[1] > 0x9f) return 3; // str[1] < 0x80 is checked below
1225 c = (unsigned int)((*str++ & 0x0f) << 12);
1226 if ((*str & 0xc0) != 0x80) return 3;
1227 c += (unsigned int)((*str++ & 0x3f) << 6);
1228 if ((*str & 0xc0) != 0x80) return 3;
1229 c += (*str++ & 0x3f);
1230 *out_char = c;
1231 return 3;
1232 }
1233 if ((*str & 0xf8) == 0xf0)
1234 {
1235 *out_char = 0xFFFD; // will be invalid but not end of string
1236 if (in_text_end && in_text_end - (const char*)str < 4) return 1;
1237 if (*str > 0xf4) return 4;
1238 if (*str == 0xf0 && (str[1] < 0x90 || str[1] > 0xbf)) return 4;
1239 if (*str == 0xf4 && str[1] > 0x8f) return 4; // str[1] < 0x80 is checked below
1240 c = (unsigned int)((*str++ & 0x07) << 18);
1241 if ((*str & 0xc0) != 0x80) return 4;
1242 c += (unsigned int)((*str++ & 0x3f) << 12);
1243 if ((*str & 0xc0) != 0x80) return 4;
1244 c += (unsigned int)((*str++ & 0x3f) << 6);
1245 if ((*str & 0xc0) != 0x80) return 4;
1246 c += (*str++ & 0x3f);
1247 // utf-8 encodings of values used in surrogate pairs are invalid
1248 if ((c & 0xFFFFF800) == 0xD800) return 4;
1249 *out_char = c;
1250 return 4;
1251 }
1252 *out_char = 0;
1253 return 0;
1254 }
1255
ImTextStrFromUtf8(ImWchar * buf,int buf_size,const char * in_text,const char * in_text_end,const char ** in_text_remaining)1256 int ImTextStrFromUtf8(ImWchar* buf, int buf_size, const char* in_text, const char* in_text_end, const char** in_text_remaining)
1257 {
1258 ImWchar* buf_out = buf;
1259 ImWchar* buf_end = buf + buf_size;
1260 while (buf_out < buf_end - 1 && (!in_text_end || in_text < in_text_end) && *in_text)
1261 {
1262 unsigned int c;
1263 in_text += ImTextCharFromUtf8(&c, in_text, in_text_end);
1264 if (c == 0)
1265 break;
1266 if (c < 0x10000) // FIXME: Losing characters that don't fit in 2 bytes
1267 *buf_out++ = (ImWchar)c;
1268 }
1269 *buf_out = 0;
1270 if (in_text_remaining)
1271 *in_text_remaining = in_text;
1272 return (int)(buf_out - buf);
1273 }
1274
ImTextCountCharsFromUtf8(const char * in_text,const char * in_text_end)1275 int ImTextCountCharsFromUtf8(const char* in_text, const char* in_text_end)
1276 {
1277 int char_count = 0;
1278 while ((!in_text_end || in_text < in_text_end) && *in_text)
1279 {
1280 unsigned int c;
1281 in_text += ImTextCharFromUtf8(&c, in_text, in_text_end);
1282 if (c == 0)
1283 break;
1284 if (c < 0x10000)
1285 char_count++;
1286 }
1287 return char_count;
1288 }
1289
1290 // Based on stb_to_utf8() from github.com/nothings/stb/
ImTextCharToUtf8(char * buf,int buf_size,unsigned int c)1291 static inline int ImTextCharToUtf8(char* buf, int buf_size, unsigned int c)
1292 {
1293 if (c < 0x80)
1294 {
1295 buf[0] = (char)c;
1296 return 1;
1297 }
1298 if (c < 0x800)
1299 {
1300 if (buf_size < 2) return 0;
1301 buf[0] = (char)(0xc0 + (c >> 6));
1302 buf[1] = (char)(0x80 + (c & 0x3f));
1303 return 2;
1304 }
1305 if (c >= 0xdc00 && c < 0xe000)
1306 {
1307 return 0;
1308 }
1309 if (c >= 0xd800 && c < 0xdc00)
1310 {
1311 if (buf_size < 4) return 0;
1312 buf[0] = (char)(0xf0 + (c >> 18));
1313 buf[1] = (char)(0x80 + ((c >> 12) & 0x3f));
1314 buf[2] = (char)(0x80 + ((c >> 6) & 0x3f));
1315 buf[3] = (char)(0x80 + ((c)&0x3f));
1316 return 4;
1317 }
1318 //else if (c < 0x10000)
1319 {
1320 if (buf_size < 3) return 0;
1321 buf[0] = (char)(0xe0 + (c >> 12));
1322 buf[1] = (char)(0x80 + ((c >> 6) & 0x3f));
1323 buf[2] = (char)(0x80 + ((c)&0x3f));
1324 return 3;
1325 }
1326 }
1327
ImTextCountUtf8BytesFromChar(unsigned int c)1328 static inline int ImTextCountUtf8BytesFromChar(unsigned int c)
1329 {
1330 if (c < 0x80) return 1;
1331 if (c < 0x800) return 2;
1332 if (c >= 0xdc00 && c < 0xe000) return 0;
1333 if (c >= 0xd800 && c < 0xdc00) return 4;
1334 return 3;
1335 }
1336
ImTextStrToUtf8(char * buf,int buf_size,const ImWchar * in_text,const ImWchar * in_text_end)1337 int ImTextStrToUtf8(char* buf, int buf_size, const ImWchar* in_text, const ImWchar* in_text_end)
1338 {
1339 char* buf_out = buf;
1340 const char* buf_end = buf + buf_size;
1341 while (buf_out < buf_end - 1 && (!in_text_end || in_text < in_text_end) && *in_text)
1342 {
1343 unsigned int c = (unsigned int)(*in_text++);
1344 if (c < 0x80)
1345 *buf_out++ = (char)c;
1346 else
1347 buf_out += ImTextCharToUtf8(buf_out, (int)(buf_end - buf_out - 1), c);
1348 }
1349 *buf_out = 0;
1350 return (int)(buf_out - buf);
1351 }
1352
ImTextCountUtf8BytesFromStr(const ImWchar * in_text,const ImWchar * in_text_end)1353 int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_end)
1354 {
1355 int bytes_count = 0;
1356 while ((!in_text_end || in_text < in_text_end) && *in_text)
1357 {
1358 unsigned int c = (unsigned int)(*in_text++);
1359 if (c < 0x80)
1360 bytes_count++;
1361 else
1362 bytes_count += ImTextCountUtf8BytesFromChar(c);
1363 }
1364 return bytes_count;
1365 }
1366
ColorConvertU32ToFloat4(ImU32 in)1367 ImVec4 ImGui::ColorConvertU32ToFloat4(ImU32 in)
1368 {
1369 float s = 1.0f / 255.0f;
1370 return ImVec4(
1371 ((in >> IM_COL32_R_SHIFT) & 0xFF) * s,
1372 ((in >> IM_COL32_G_SHIFT) & 0xFF) * s,
1373 ((in >> IM_COL32_B_SHIFT) & 0xFF) * s,
1374 ((in >> IM_COL32_A_SHIFT) & 0xFF) * s);
1375 }
1376
ColorConvertFloat4ToU32(const ImVec4 & in)1377 ImU32 ImGui::ColorConvertFloat4ToU32(const ImVec4& in)
1378 {
1379 ImU32 out;
1380 out = ((ImU32)IM_F32_TO_INT8_SAT(in.x)) << IM_COL32_R_SHIFT;
1381 out |= ((ImU32)IM_F32_TO_INT8_SAT(in.y)) << IM_COL32_G_SHIFT;
1382 out |= ((ImU32)IM_F32_TO_INT8_SAT(in.z)) << IM_COL32_B_SHIFT;
1383 out |= ((ImU32)IM_F32_TO_INT8_SAT(in.w)) << IM_COL32_A_SHIFT;
1384 return out;
1385 }
1386
GetColorU32(ImGuiCol idx,float alpha_mul)1387 ImU32 ImGui::GetColorU32(ImGuiCol idx, float alpha_mul)
1388 {
1389 ImGuiStyle& style = GImGui->Style;
1390 ImVec4 c = style.Colors[idx];
1391 c.w *= style.Alpha * alpha_mul;
1392 return ColorConvertFloat4ToU32(c);
1393 }
1394
GetColorU32(const ImVec4 & col)1395 ImU32 ImGui::GetColorU32(const ImVec4& col)
1396 {
1397 ImGuiStyle& style = GImGui->Style;
1398 ImVec4 c = col;
1399 c.w *= style.Alpha;
1400 return ColorConvertFloat4ToU32(c);
1401 }
1402
GetStyleColorVec4(ImGuiCol idx)1403 const ImVec4& ImGui::GetStyleColorVec4(ImGuiCol idx)
1404 {
1405 ImGuiStyle& style = GImGui->Style;
1406 return style.Colors[idx];
1407 }
1408
GetColorU32(ImU32 col)1409 ImU32 ImGui::GetColorU32(ImU32 col)
1410 {
1411 float style_alpha = GImGui->Style.Alpha;
1412 if (style_alpha >= 1.0f)
1413 return col;
1414 int a = (col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT;
1415 a = (int)(a * style_alpha); // We don't need to clamp 0..255 because Style.Alpha is in 0..1 range.
1416 return (col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT);
1417 }
1418
1419 // Convert rgb floats ([0-1],[0-1],[0-1]) to hsv floats ([0-1],[0-1],[0-1]), from Foley & van Dam p592
1420 // Optimized http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv
ColorConvertRGBtoHSV(float r,float g,float b,float & out_h,float & out_s,float & out_v)1421 void ImGui::ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& out_s, float& out_v)
1422 {
1423 float K = 0.f;
1424 if (g < b)
1425 {
1426 ImSwap(g, b);
1427 K = -1.f;
1428 }
1429 if (r < g)
1430 {
1431 ImSwap(r, g);
1432 K = -2.f / 6.f - K;
1433 }
1434
1435 const float chroma = r - (g < b ? g : b);
1436 out_h = fabsf(K + (g - b) / (6.f * chroma + 1e-20f));
1437 out_s = chroma / (r + 1e-20f);
1438 out_v = r;
1439 }
1440
1441 // Convert hsv floats ([0-1],[0-1],[0-1]) to rgb floats ([0-1],[0-1],[0-1]), from Foley & van Dam p593
1442 // also http://en.wikipedia.org/wiki/HSL_and_HSV
ColorConvertHSVtoRGB(float h,float s,float v,float & out_r,float & out_g,float & out_b)1443 void ImGui::ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& out_g, float& out_b)
1444 {
1445 if (s == 0.0f)
1446 {
1447 // gray
1448 out_r = out_g = out_b = v;
1449 return;
1450 }
1451
1452 h = fmodf(h, 1.0f) / (60.0f / 360.0f);
1453 int i = (int)h;
1454 float f = h - (float)i;
1455 float p = v * (1.0f - s);
1456 float q = v * (1.0f - s * f);
1457 float t = v * (1.0f - s * (1.0f - f));
1458
1459 switch (i)
1460 {
1461 case 0:
1462 out_r = v;
1463 out_g = t;
1464 out_b = p;
1465 break;
1466 case 1:
1467 out_r = q;
1468 out_g = v;
1469 out_b = p;
1470 break;
1471 case 2:
1472 out_r = p;
1473 out_g = v;
1474 out_b = t;
1475 break;
1476 case 3:
1477 out_r = p;
1478 out_g = q;
1479 out_b = v;
1480 break;
1481 case 4:
1482 out_r = t;
1483 out_g = p;
1484 out_b = v;
1485 break;
1486 case 5:
1487 default:
1488 out_r = v;
1489 out_g = p;
1490 out_b = q;
1491 break;
1492 }
1493 }
1494
ImFileOpen(const char * filename,const char * mode)1495 FILE* ImFileOpen(const char* filename, const char* mode)
1496 {
1497 #if defined(_WIN32) && !defined(__CYGWIN__)
1498 // 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)
1499 const int filename_wsize = ImTextCountCharsFromUtf8(filename, NULL) + 1;
1500 const int mode_wsize = ImTextCountCharsFromUtf8(mode, NULL) + 1;
1501 ImVector<ImWchar> buf;
1502 buf.resize(filename_wsize + mode_wsize);
1503 ImTextStrFromUtf8(&buf[0], filename_wsize, filename, NULL);
1504 ImTextStrFromUtf8(&buf[filename_wsize], mode_wsize, mode, NULL);
1505 return _wfopen((wchar_t*)&buf[0], (wchar_t*)&buf[filename_wsize]);
1506 #else
1507 return fopen(filename, mode);
1508 #endif
1509 }
1510
1511 // Load file content into memory
1512 // Memory allocated with ImGui::MemAlloc(), must be freed by user using ImGui::MemFree()
ImFileLoadToMemory(const char * filename,const char * file_open_mode,int * out_file_size,int padding_bytes)1513 void* ImFileLoadToMemory(const char* filename, const char* file_open_mode, int* out_file_size, int padding_bytes)
1514 {
1515 IM_ASSERT(filename && file_open_mode);
1516 if (out_file_size)
1517 *out_file_size = 0;
1518
1519 FILE* f;
1520 if ((f = ImFileOpen(filename, file_open_mode)) == NULL)
1521 return NULL;
1522
1523 long file_size_signed;
1524 if (fseek(f, 0, SEEK_END) || (file_size_signed = ftell(f)) == -1 || fseek(f, 0, SEEK_SET))
1525 {
1526 fclose(f);
1527 return NULL;
1528 }
1529
1530 int file_size = (int)file_size_signed;
1531 void* file_data = ImGui::MemAlloc(file_size + padding_bytes);
1532 if (file_data == NULL)
1533 {
1534 fclose(f);
1535 return NULL;
1536 }
1537 if (fread(file_data, 1, (size_t)file_size, f) != (size_t)file_size)
1538 {
1539 fclose(f);
1540 ImGui::MemFree(file_data);
1541 return NULL;
1542 }
1543 if (padding_bytes > 0)
1544 memset((void*)(((char*)file_data) + file_size), 0, padding_bytes);
1545
1546 fclose(f);
1547 if (out_file_size)
1548 *out_file_size = file_size;
1549
1550 return file_data;
1551 }
1552
1553 //-----------------------------------------------------------------------------
1554 // ImGuiStorage
1555 // Helper: Key->value storage
1556 //-----------------------------------------------------------------------------
1557
1558 // std::lower_bound but without the bullshit
LowerBound(ImVector<ImGuiStorage::Pair> & data,ImGuiID key)1559 static ImVector<ImGuiStorage::Pair>::iterator LowerBound(ImVector<ImGuiStorage::Pair>& data, ImGuiID key)
1560 {
1561 ImVector<ImGuiStorage::Pair>::iterator first = data.begin();
1562 ImVector<ImGuiStorage::Pair>::iterator last = data.end();
1563 size_t count = (size_t)(last - first);
1564 while (count > 0)
1565 {
1566 size_t count2 = count >> 1;
1567 ImVector<ImGuiStorage::Pair>::iterator mid = first + count2;
1568 if (mid->key < key)
1569 {
1570 first = ++mid;
1571 count -= count2 + 1;
1572 }
1573 else
1574 {
1575 count = count2;
1576 }
1577 }
1578 return first;
1579 }
1580
1581 // For quicker full rebuild of a storage (instead of an incremental one), you may add all your contents and then sort once.
BuildSortByKey()1582 void ImGuiStorage::BuildSortByKey()
1583 {
1584 struct StaticFunc
1585 {
1586 static int IMGUI_CDECL PairCompareByID(const void* lhs, const void* rhs)
1587 {
1588 // We can't just do a subtraction because qsort uses signed integers and subtracting our ID doesn't play well with that.
1589 if (((const Pair*)lhs)->key > ((const Pair*)rhs)->key) return +1;
1590 if (((const Pair*)lhs)->key < ((const Pair*)rhs)->key) return -1;
1591 return 0;
1592 }
1593 };
1594 if (Data.Size > 1)
1595 qsort(Data.Data, (size_t)Data.Size, sizeof(Pair), StaticFunc::PairCompareByID);
1596 }
1597
GetInt(ImGuiID key,int default_val) const1598 int ImGuiStorage::GetInt(ImGuiID key, int default_val) const
1599 {
1600 ImVector<Pair>::iterator it = LowerBound(const_cast<ImVector<ImGuiStorage::Pair>&>(Data), key);
1601 if (it == Data.end() || it->key != key)
1602 return default_val;
1603 return it->val_i;
1604 }
1605
GetBool(ImGuiID key,bool default_val) const1606 bool ImGuiStorage::GetBool(ImGuiID key, bool default_val) const
1607 {
1608 return GetInt(key, default_val ? 1 : 0) != 0;
1609 }
1610
GetFloat(ImGuiID key,float default_val) const1611 float ImGuiStorage::GetFloat(ImGuiID key, float default_val) const
1612 {
1613 ImVector<Pair>::iterator it = LowerBound(const_cast<ImVector<ImGuiStorage::Pair>&>(Data), key);
1614 if (it == Data.end() || it->key != key)
1615 return default_val;
1616 return it->val_f;
1617 }
1618
GetVoidPtr(ImGuiID key) const1619 void* ImGuiStorage::GetVoidPtr(ImGuiID key) const
1620 {
1621 ImVector<Pair>::iterator it = LowerBound(const_cast<ImVector<ImGuiStorage::Pair>&>(Data), key);
1622 if (it == Data.end() || it->key != key)
1623 return NULL;
1624 return it->val_p;
1625 }
1626
1627 // References are only valid until a new value is added to the storage. Calling a Set***() function or a Get***Ref() function invalidates the pointer.
GetIntRef(ImGuiID key,int default_val)1628 int* ImGuiStorage::GetIntRef(ImGuiID key, int default_val)
1629 {
1630 ImVector<Pair>::iterator it = LowerBound(Data, key);
1631 if (it == Data.end() || it->key != key)
1632 it = Data.insert(it, Pair(key, default_val));
1633 return &it->val_i;
1634 }
1635
GetBoolRef(ImGuiID key,bool default_val)1636 bool* ImGuiStorage::GetBoolRef(ImGuiID key, bool default_val)
1637 {
1638 return (bool*)GetIntRef(key, default_val ? 1 : 0);
1639 }
1640
GetFloatRef(ImGuiID key,float default_val)1641 float* ImGuiStorage::GetFloatRef(ImGuiID key, float default_val)
1642 {
1643 ImVector<Pair>::iterator it = LowerBound(Data, key);
1644 if (it == Data.end() || it->key != key)
1645 it = Data.insert(it, Pair(key, default_val));
1646 return &it->val_f;
1647 }
1648
GetVoidPtrRef(ImGuiID key,void * default_val)1649 void** ImGuiStorage::GetVoidPtrRef(ImGuiID key, void* default_val)
1650 {
1651 ImVector<Pair>::iterator it = LowerBound(Data, key);
1652 if (it == Data.end() || it->key != key)
1653 it = Data.insert(it, Pair(key, default_val));
1654 return &it->val_p;
1655 }
1656
1657 // FIXME-OPT: Need a way to reuse the result of lower_bound when doing GetInt()/SetInt() - not too bad because it only happens on explicit interaction (maximum one a frame)
SetInt(ImGuiID key,int val)1658 void ImGuiStorage::SetInt(ImGuiID key, int val)
1659 {
1660 ImVector<Pair>::iterator it = LowerBound(Data, key);
1661 if (it == Data.end() || it->key != key)
1662 {
1663 Data.insert(it, Pair(key, val));
1664 return;
1665 }
1666 it->val_i = val;
1667 }
1668
SetBool(ImGuiID key,bool val)1669 void ImGuiStorage::SetBool(ImGuiID key, bool val)
1670 {
1671 SetInt(key, val ? 1 : 0);
1672 }
1673
SetFloat(ImGuiID key,float val)1674 void ImGuiStorage::SetFloat(ImGuiID key, float val)
1675 {
1676 ImVector<Pair>::iterator it = LowerBound(Data, key);
1677 if (it == Data.end() || it->key != key)
1678 {
1679 Data.insert(it, Pair(key, val));
1680 return;
1681 }
1682 it->val_f = val;
1683 }
1684
SetVoidPtr(ImGuiID key,void * val)1685 void ImGuiStorage::SetVoidPtr(ImGuiID key, void* val)
1686 {
1687 ImVector<Pair>::iterator it = LowerBound(Data, key);
1688 if (it == Data.end() || it->key != key)
1689 {
1690 Data.insert(it, Pair(key, val));
1691 return;
1692 }
1693 it->val_p = val;
1694 }
1695
SetAllInt(int v)1696 void ImGuiStorage::SetAllInt(int v)
1697 {
1698 for (int i = 0; i < Data.Size; i++)
1699 Data[i].val_i = v;
1700 }
1701
1702 //-----------------------------------------------------------------------------
1703 // ImGuiTextFilter
1704 //-----------------------------------------------------------------------------
1705
1706 // Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]"
ImGuiTextFilter(const char * default_filter)1707 ImGuiTextFilter::ImGuiTextFilter(const char* default_filter)
1708 {
1709 if (default_filter)
1710 {
1711 ImStrncpy(InputBuf, default_filter, IM_ARRAYSIZE(InputBuf));
1712 Build();
1713 }
1714 else
1715 {
1716 InputBuf[0] = 0;
1717 CountGrep = 0;
1718 }
1719 }
1720
Draw(const char * label,float width)1721 bool ImGuiTextFilter::Draw(const char* label, float width)
1722 {
1723 if (width != 0.0f)
1724 ImGui::PushItemWidth(width);
1725 bool value_changed = ImGui::InputText(label, InputBuf, IM_ARRAYSIZE(InputBuf));
1726 if (width != 0.0f)
1727 ImGui::PopItemWidth();
1728 if (value_changed)
1729 Build();
1730 return value_changed;
1731 }
1732
split(char separator,ImVector<TextRange> & out)1733 void ImGuiTextFilter::TextRange::split(char separator, ImVector<TextRange>& out)
1734 {
1735 out.resize(0);
1736 const char* wb = b;
1737 const char* we = wb;
1738 while (we < e)
1739 {
1740 if (*we == separator)
1741 {
1742 out.push_back(TextRange(wb, we));
1743 wb = we + 1;
1744 }
1745 we++;
1746 }
1747 if (wb != we)
1748 out.push_back(TextRange(wb, we));
1749 }
1750
Build()1751 void ImGuiTextFilter::Build()
1752 {
1753 Filters.resize(0);
1754 TextRange input_range(InputBuf, InputBuf + strlen(InputBuf));
1755 input_range.split(',', Filters);
1756
1757 CountGrep = 0;
1758 for (int i = 0; i != Filters.Size; i++)
1759 {
1760 Filters[i].trim_blanks();
1761 if (Filters[i].empty())
1762 continue;
1763 if (Filters[i].front() != '-')
1764 CountGrep += 1;
1765 }
1766 }
1767
PassFilter(const char * text,const char * text_end) const1768 bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const
1769 {
1770 if (Filters.empty())
1771 return true;
1772
1773 if (text == NULL)
1774 text = "";
1775
1776 for (int i = 0; i != Filters.Size; i++)
1777 {
1778 const TextRange& f = Filters[i];
1779 if (f.empty())
1780 continue;
1781 if (f.front() == '-')
1782 {
1783 // Subtract
1784 if (ImStristr(text, text_end, f.begin() + 1, f.end()) != NULL)
1785 return false;
1786 }
1787 else
1788 {
1789 // Grep
1790 if (ImStristr(text, text_end, f.begin(), f.end()) != NULL)
1791 return true;
1792 }
1793 }
1794
1795 // Implicit * grep
1796 if (CountGrep == 0)
1797 return true;
1798
1799 return false;
1800 }
1801
1802 //-----------------------------------------------------------------------------
1803 // ImGuiTextBuffer
1804 //-----------------------------------------------------------------------------
1805
1806 // On some platform vsnprintf() takes va_list by reference and modifies it.
1807 // va_copy is the 'correct' way to copy a va_list but Visual Studio prior to 2013 doesn't have it.
1808 #ifndef va_copy
1809 #define va_copy(dest, src) (dest = src)
1810 #endif
1811
1812 // Helper: Text buffer for logging/accumulating text
appendfv(const char * fmt,va_list args)1813 void ImGuiTextBuffer::appendfv(const char* fmt, va_list args)
1814 {
1815 va_list args_copy;
1816 va_copy(args_copy, args);
1817
1818 int len = ImFormatStringV(NULL, 0, fmt, args); // FIXME-OPT: could do a first pass write attempt, likely successful on first pass.
1819 if (len <= 0)
1820 return;
1821
1822 const int write_off = Buf.Size;
1823 const int needed_sz = write_off + len;
1824 if (write_off + len >= Buf.Capacity)
1825 {
1826 int double_capacity = Buf.Capacity * 2;
1827 Buf.reserve(needed_sz > double_capacity ? needed_sz : double_capacity);
1828 }
1829
1830 Buf.resize(needed_sz);
1831 ImFormatStringV(&Buf[write_off - 1], len + 1, fmt, args_copy);
1832 }
1833
appendf(const char * fmt,...)1834 void ImGuiTextBuffer::appendf(const char* fmt, ...)
1835 {
1836 va_list args;
1837 va_start(args, fmt);
1838 appendfv(fmt, args);
1839 va_end(args);
1840 }
1841
1842 //-----------------------------------------------------------------------------
1843 // ImGuiSimpleColumns (internal use only)
1844 //-----------------------------------------------------------------------------
1845
ImGuiMenuColumns()1846 ImGuiMenuColumns::ImGuiMenuColumns()
1847 {
1848 Count = 0;
1849 Spacing = Width = NextWidth = 0.0f;
1850 memset(Pos, 0, sizeof(Pos));
1851 memset(NextWidths, 0, sizeof(NextWidths));
1852 }
1853
Update(int count,float spacing,bool clear)1854 void ImGuiMenuColumns::Update(int count, float spacing, bool clear)
1855 {
1856 IM_ASSERT(Count <= IM_ARRAYSIZE(Pos));
1857 Count = count;
1858 Width = NextWidth = 0.0f;
1859 Spacing = spacing;
1860 if (clear) memset(NextWidths, 0, sizeof(NextWidths));
1861 for (int i = 0; i < Count; i++)
1862 {
1863 if (i > 0 && NextWidths[i] > 0.0f)
1864 Width += Spacing;
1865 Pos[i] = (float)(int)Width;
1866 Width += NextWidths[i];
1867 NextWidths[i] = 0.0f;
1868 }
1869 }
1870
DeclColumns(float w0,float w1,float w2)1871 float ImGuiMenuColumns::DeclColumns(float w0, float w1, float w2) // not using va_arg because they promote float to double
1872 {
1873 NextWidth = 0.0f;
1874 NextWidths[0] = ImMax(NextWidths[0], w0);
1875 NextWidths[1] = ImMax(NextWidths[1], w1);
1876 NextWidths[2] = ImMax(NextWidths[2], w2);
1877 for (int i = 0; i < 3; i++)
1878 NextWidth += NextWidths[i] + ((i > 0 && NextWidths[i] > 0.0f) ? Spacing : 0.0f);
1879 return ImMax(Width, NextWidth);
1880 }
1881
CalcExtraSpace(float avail_w)1882 float ImGuiMenuColumns::CalcExtraSpace(float avail_w)
1883 {
1884 return ImMax(0.0f, avail_w - Width);
1885 }
1886
1887 //-----------------------------------------------------------------------------
1888 // ImGuiListClipper
1889 //-----------------------------------------------------------------------------
1890
SetCursorPosYAndSetupDummyPrevLine(float pos_y,float line_height)1891 static void SetCursorPosYAndSetupDummyPrevLine(float pos_y, float line_height)
1892 {
1893 // Set cursor position and a few other things so that SetScrollHere() and Columns() can work when seeking cursor.
1894 // FIXME: It is problematic that we have to do that here, because custom/equivalent end-user code would stumble on the same issue. Consider moving within SetCursorXXX functions?
1895 ImGui::SetCursorPosY(pos_y);
1896 ImGuiWindow* window = ImGui::GetCurrentWindow();
1897 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.
1898 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.
1899 if (window->DC.ColumnsSet)
1900 window->DC.ColumnsSet->CellMinY = window->DC.CursorPos.y; // Setting this so that cell Y position are set properly
1901 }
1902
1903 // Use case A: Begin() called from constructor with items_height<0, then called again from Sync() in StepNo 1
1904 // Use case B: Begin() called from constructor with items_height>0
1905 // FIXME-LEGACY: Ideally we should remove the Begin/End functions but they are part of the legacy API we still support. This is why some of the code in Step() calling Begin() and reassign some fields, spaghetti style.
Begin(int count,float items_height)1906 void ImGuiListClipper::Begin(int count, float items_height)
1907 {
1908 StartPosY = ImGui::GetCursorPosY();
1909 ItemsHeight = items_height;
1910 ItemsCount = count;
1911 StepNo = 0;
1912 DisplayEnd = DisplayStart = -1;
1913 if (ItemsHeight > 0.0f)
1914 {
1915 ImGui::CalcListClipping(ItemsCount, ItemsHeight, &DisplayStart, &DisplayEnd); // calculate how many to clip/display
1916 if (DisplayStart > 0)
1917 SetCursorPosYAndSetupDummyPrevLine(StartPosY + DisplayStart * ItemsHeight, ItemsHeight); // advance cursor
1918 StepNo = 2;
1919 }
1920 }
1921
End()1922 void ImGuiListClipper::End()
1923 {
1924 if (ItemsCount < 0)
1925 return;
1926 // 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.
1927 if (ItemsCount < INT_MAX)
1928 SetCursorPosYAndSetupDummyPrevLine(StartPosY + ItemsCount * ItemsHeight, ItemsHeight); // advance cursor
1929 ItemsCount = -1;
1930 StepNo = 3;
1931 }
1932
Step()1933 bool ImGuiListClipper::Step()
1934 {
1935 if (ItemsCount == 0 || ImGui::GetCurrentWindowRead()->SkipItems)
1936 {
1937 ItemsCount = -1;
1938 return false;
1939 }
1940 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.
1941 {
1942 DisplayStart = 0;
1943 DisplayEnd = 1;
1944 StartPosY = ImGui::GetCursorPosY();
1945 StepNo = 1;
1946 return true;
1947 }
1948 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.
1949 {
1950 if (ItemsCount == 1)
1951 {
1952 ItemsCount = -1;
1953 return false;
1954 }
1955 float items_height = ImGui::GetCursorPosY() - StartPosY;
1956 IM_ASSERT(items_height > 0.0f); // If this triggers, it means Item 0 hasn't moved the cursor vertically
1957 Begin(ItemsCount - 1, items_height);
1958 DisplayStart++;
1959 DisplayEnd++;
1960 StepNo = 3;
1961 return true;
1962 }
1963 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.
1964 {
1965 IM_ASSERT(DisplayStart >= 0 && DisplayEnd >= 0);
1966 StepNo = 3;
1967 return true;
1968 }
1969 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.
1970 End();
1971 return false;
1972 }
1973
1974 //-----------------------------------------------------------------------------
1975 // ImGuiWindow
1976 //-----------------------------------------------------------------------------
1977
ImGuiWindow(ImGuiContext * context,const char * name)1978 ImGuiWindow::ImGuiWindow(ImGuiContext* context, const char* name)
1979 {
1980 Name = ImStrdup(name);
1981 ID = ImHash(name, 0);
1982 IDStack.push_back(ID);
1983 Flags = 0;
1984 PosFloat = Pos = ImVec2(0.0f, 0.0f);
1985 Size = SizeFull = ImVec2(0.0f, 0.0f);
1986 SizeContents = SizeContentsExplicit = ImVec2(0.0f, 0.0f);
1987 WindowPadding = ImVec2(0.0f, 0.0f);
1988 WindowRounding = 0.0f;
1989 WindowBorderSize = 0.0f;
1990 MoveId = GetID("#MOVE");
1991 ChildId = 0;
1992 Scroll = ImVec2(0.0f, 0.0f);
1993 ScrollTarget = ImVec2(FLT_MAX, FLT_MAX);
1994 ScrollTargetCenterRatio = ImVec2(0.5f, 0.5f);
1995 ScrollbarX = ScrollbarY = false;
1996 ScrollbarSizes = ImVec2(0.0f, 0.0f);
1997 Active = WasActive = false;
1998 WriteAccessed = false;
1999 Collapsed = false;
2000 CollapseToggleWanted = false;
2001 SkipItems = false;
2002 Appearing = false;
2003 CloseButton = false;
2004 BeginOrderWithinParent = -1;
2005 BeginOrderWithinContext = -1;
2006 BeginCount = 0;
2007 PopupId = 0;
2008 AutoFitFramesX = AutoFitFramesY = -1;
2009 AutoFitOnlyGrows = false;
2010 AutoFitChildAxises = 0x00;
2011 AutoPosLastDirection = ImGuiDir_None;
2012 HiddenFrames = 0;
2013 SetWindowPosAllowFlags = SetWindowSizeAllowFlags = SetWindowCollapsedAllowFlags = ImGuiCond_Always | ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing;
2014 SetWindowPosVal = SetWindowPosPivot = ImVec2(FLT_MAX, FLT_MAX);
2015
2016 LastFrameActive = -1;
2017 ItemWidthDefault = 0.0f;
2018 FontWindowScale = 1.0f;
2019
2020 DrawList = IM_NEW(ImDrawList)(&context->DrawListSharedData);
2021 DrawList->_OwnerName = Name;
2022 ParentWindow = NULL;
2023 RootWindow = NULL;
2024 RootWindowForTitleBarHighlight = NULL;
2025 RootWindowForTabbing = NULL;
2026 RootWindowForNav = NULL;
2027
2028 NavLastIds[0] = NavLastIds[1] = 0;
2029 NavRectRel[0] = NavRectRel[1] = ImRect();
2030 NavLastChildNavWindow = NULL;
2031
2032 FocusIdxAllCounter = FocusIdxTabCounter = -1;
2033 FocusIdxAllRequestCurrent = FocusIdxTabRequestCurrent = INT_MAX;
2034 FocusIdxAllRequestNext = FocusIdxTabRequestNext = INT_MAX;
2035 }
2036
~ImGuiWindow()2037 ImGuiWindow::~ImGuiWindow()
2038 {
2039 IM_DELETE(DrawList);
2040 IM_DELETE(Name);
2041 for (int i = 0; i != ColumnsStorage.Size; i++)
2042 ColumnsStorage[i].~ImGuiColumnsSet();
2043 }
2044
GetID(const char * str,const char * str_end)2045 ImGuiID ImGuiWindow::GetID(const char* str, const char* str_end)
2046 {
2047 ImGuiID seed = IDStack.back();
2048 ImGuiID id = ImHash(str, str_end ? (int)(str_end - str) : 0, seed);
2049 ImGui::KeepAliveID(id);
2050 return id;
2051 }
2052
GetID(const void * ptr)2053 ImGuiID ImGuiWindow::GetID(const void* ptr)
2054 {
2055 ImGuiID seed = IDStack.back();
2056 ImGuiID id = ImHash(&ptr, sizeof(void*), seed);
2057 ImGui::KeepAliveID(id);
2058 return id;
2059 }
2060
GetIDNoKeepAlive(const char * str,const char * str_end)2061 ImGuiID ImGuiWindow::GetIDNoKeepAlive(const char* str, const char* str_end)
2062 {
2063 ImGuiID seed = IDStack.back();
2064 return ImHash(str, str_end ? (int)(str_end - str) : 0, seed);
2065 }
2066
2067 // This is only used in rare/specific situations to manufacture an ID out of nowhere.
GetIDFromRectangle(const ImRect & r_abs)2068 ImGuiID ImGuiWindow::GetIDFromRectangle(const ImRect& r_abs)
2069 {
2070 ImGuiID seed = IDStack.back();
2071 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)};
2072 ImGuiID id = ImHash(&r_rel, sizeof(r_rel), seed);
2073 ImGui::KeepAliveID(id);
2074 return id;
2075 }
2076
2077 //-----------------------------------------------------------------------------
2078 // Internal API exposed in imgui_internal.h
2079 //-----------------------------------------------------------------------------
2080
SetCurrentWindow(ImGuiWindow * window)2081 static void SetCurrentWindow(ImGuiWindow* window)
2082 {
2083 ImGuiContext& g = *GImGui;
2084 g.CurrentWindow = window;
2085 if (window)
2086 g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize();
2087 }
2088
SetNavID(ImGuiID id,int nav_layer)2089 static void SetNavID(ImGuiID id, int nav_layer)
2090 {
2091 ImGuiContext& g = *GImGui;
2092 IM_ASSERT(g.NavWindow);
2093 IM_ASSERT(nav_layer == 0 || nav_layer == 1);
2094 g.NavId = id;
2095 g.NavWindow->NavLastIds[nav_layer] = id;
2096 }
2097
SetNavIDAndMoveMouse(ImGuiID id,int nav_layer,const ImRect & rect_rel)2098 static void SetNavIDAndMoveMouse(ImGuiID id, int nav_layer, const ImRect& rect_rel)
2099 {
2100 ImGuiContext& g = *GImGui;
2101 SetNavID(id, nav_layer);
2102 g.NavWindow->NavRectRel[nav_layer] = rect_rel;
2103 g.NavMousePosDirty = true;
2104 g.NavDisableHighlight = false;
2105 g.NavDisableMouseHover = true;
2106 }
2107
SetActiveID(ImGuiID id,ImGuiWindow * window)2108 void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window)
2109 {
2110 ImGuiContext& g = *GImGui;
2111 g.ActiveIdIsJustActivated = (g.ActiveId != id);
2112 if (g.ActiveIdIsJustActivated)
2113 g.ActiveIdTimer = 0.0f;
2114 g.ActiveId = id;
2115 g.ActiveIdAllowNavDirFlags = 0;
2116 g.ActiveIdAllowOverlap = false;
2117 g.ActiveIdWindow = window;
2118 if (id)
2119 {
2120 g.ActiveIdIsAlive = true;
2121 g.ActiveIdSource = (g.NavActivateId == id || g.NavInputId == id || g.NavJustTabbedId == id || g.NavJustMovedToId == id) ? ImGuiInputSource_Nav : ImGuiInputSource_Mouse;
2122 }
2123 }
2124
GetActiveID()2125 ImGuiID ImGui::GetActiveID()
2126 {
2127 ImGuiContext& g = *GImGui;
2128 return g.ActiveId;
2129 }
2130
SetFocusID(ImGuiID id,ImGuiWindow * window)2131 void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window)
2132 {
2133 ImGuiContext& g = *GImGui;
2134 IM_ASSERT(id != 0);
2135
2136 // Assume that SetFocusID() is called in the context where its NavLayer is the current layer, which is the case everywhere we call it.
2137 const int nav_layer = window->DC.NavLayerCurrent;
2138 if (g.NavWindow != window)
2139 g.NavInitRequest = false;
2140 g.NavId = id;
2141 g.NavWindow = window;
2142 g.NavLayer = nav_layer;
2143 window->NavLastIds[nav_layer] = id;
2144 if (window->DC.LastItemId == id)
2145 window->NavRectRel[nav_layer] = ImRect(window->DC.LastItemRect.Min - window->Pos, window->DC.LastItemRect.Max - window->Pos);
2146
2147 if (g.ActiveIdSource == ImGuiInputSource_Nav)
2148 g.NavDisableMouseHover = true;
2149 else
2150 g.NavDisableHighlight = true;
2151 }
2152
ClearActiveID()2153 void ImGui::ClearActiveID()
2154 {
2155 SetActiveID(0, NULL);
2156 }
2157
SetHoveredID(ImGuiID id)2158 void ImGui::SetHoveredID(ImGuiID id)
2159 {
2160 ImGuiContext& g = *GImGui;
2161 g.HoveredId = id;
2162 g.HoveredIdAllowOverlap = false;
2163 g.HoveredIdTimer = (id != 0 && g.HoveredIdPreviousFrame == id) ? (g.HoveredIdTimer + g.IO.DeltaTime) : 0.0f;
2164 }
2165
GetHoveredID()2166 ImGuiID ImGui::GetHoveredID()
2167 {
2168 ImGuiContext& g = *GImGui;
2169 return g.HoveredId ? g.HoveredId : g.HoveredIdPreviousFrame;
2170 }
2171
KeepAliveID(ImGuiID id)2172 void ImGui::KeepAliveID(ImGuiID id)
2173 {
2174 ImGuiContext& g = *GImGui;
2175 if (g.ActiveId == id)
2176 g.ActiveIdIsAlive = true;
2177 }
2178
IsWindowContentHoverable(ImGuiWindow * window,ImGuiHoveredFlags flags)2179 static inline bool IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flags)
2180 {
2181 // An active popup disable hovering on other windows (apart from its own children)
2182 // FIXME-OPT: This could be cached/stored within the window.
2183 ImGuiContext& g = *GImGui;
2184 if (g.NavWindow)
2185 if (ImGuiWindow* focused_root_window = g.NavWindow->RootWindow)
2186 if (focused_root_window->WasActive && focused_root_window != window->RootWindow)
2187 {
2188 // For the purpose of those flags we differentiate "standard popup" from "modal popup"
2189 // NB: The order of those two tests is important because Modal windows are also Popups.
2190 if (focused_root_window->Flags & ImGuiWindowFlags_Modal)
2191 return false;
2192 if ((focused_root_window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiHoveredFlags_AllowWhenBlockedByPopup))
2193 return false;
2194 }
2195
2196 return true;
2197 }
2198
2199 // Advance cursor given item size for layout.
ItemSize(const ImVec2 & size,float text_offset_y)2200 void ImGui::ItemSize(const ImVec2& size, float text_offset_y)
2201 {
2202 ImGuiContext& g = *GImGui;
2203 ImGuiWindow* window = g.CurrentWindow;
2204 if (window->SkipItems)
2205 return;
2206
2207 // Always align ourselves on pixel boundaries
2208 const float line_height = ImMax(window->DC.CurrentLineHeight, size.y);
2209 const float text_base_offset = ImMax(window->DC.CurrentLineTextBaseOffset, text_offset_y);
2210 //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]
2211 window->DC.CursorPosPrevLine = ImVec2(window->DC.CursorPos.x + size.x, window->DC.CursorPos.y);
2212 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));
2213 window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPosPrevLine.x);
2214 window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y - g.Style.ItemSpacing.y);
2215 //if (g.IO.KeyAlt) window->DrawList->AddCircle(window->DC.CursorMaxPos, 3.0f, IM_COL32(255,0,0,255), 4); // [DEBUG]
2216
2217 window->DC.PrevLineHeight = line_height;
2218 window->DC.PrevLineTextBaseOffset = text_base_offset;
2219 window->DC.CurrentLineHeight = window->DC.CurrentLineTextBaseOffset = 0.0f;
2220
2221 // Horizontal layout mode
2222 if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
2223 SameLine();
2224 }
2225
ItemSize(const ImRect & bb,float text_offset_y)2226 void ImGui::ItemSize(const ImRect& bb, float text_offset_y)
2227 {
2228 ItemSize(bb.GetSize(), text_offset_y);
2229 }
2230
NavScoreItemGetQuadrant(float dx,float dy)2231 static ImGuiDir NavScoreItemGetQuadrant(float dx, float dy)
2232 {
2233 if (fabsf(dx) > fabsf(dy))
2234 return (dx > 0.0f) ? ImGuiDir_Right : ImGuiDir_Left;
2235 return (dy > 0.0f) ? ImGuiDir_Down : ImGuiDir_Up;
2236 }
2237
NavScoreItemDistInterval(float a0,float a1,float b0,float b1)2238 static float NavScoreItemDistInterval(float a0, float a1, float b0, float b1)
2239 {
2240 if (a1 < b0)
2241 return a1 - b0;
2242 if (b1 < a0)
2243 return a0 - b1;
2244 return 0.0f;
2245 }
2246
2247 // Scoring function for directional navigation. Based on https://gist.github.com/rygorous/6981057
NavScoreItem(ImGuiNavMoveResult * result,ImRect cand)2248 static bool NavScoreItem(ImGuiNavMoveResult* result, ImRect cand)
2249 {
2250 ImGuiContext& g = *GImGui;
2251 ImGuiWindow* window = g.CurrentWindow;
2252 if (g.NavLayer != window->DC.NavLayerCurrent)
2253 return false;
2254
2255 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)
2256 g.NavScoringCount++;
2257
2258 // We perform scoring on items bounding box clipped by their parent window on the other axis (clipping on our movement axis would give us equal scores for all clipped items)
2259 if (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right)
2260 {
2261 cand.Min.y = ImClamp(cand.Min.y, window->ClipRect.Min.y, window->ClipRect.Max.y);
2262 cand.Max.y = ImClamp(cand.Max.y, window->ClipRect.Min.y, window->ClipRect.Max.y);
2263 }
2264 else
2265 {
2266 cand.Min.x = ImClamp(cand.Min.x, window->ClipRect.Min.x, window->ClipRect.Max.x);
2267 cand.Max.x = ImClamp(cand.Max.x, window->ClipRect.Min.x, window->ClipRect.Max.x);
2268 }
2269
2270 // Compute distance between boxes
2271 // FIXME-NAV: Introducing biases for vertical navigation, needs to be removed.
2272 float dbx = NavScoreItemDistInterval(cand.Min.x, cand.Max.x, curr.Min.x, curr.Max.x);
2273 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
2274 if (dby != 0.0f && dbx != 0.0f)
2275 dbx = (dbx / 1000.0f) + ((dbx > 0.0f) ? +1.0f : -1.0f);
2276 float dist_box = fabsf(dbx) + fabsf(dby);
2277
2278 // 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)
2279 float dcx = (cand.Min.x + cand.Max.x) - (curr.Min.x + curr.Max.x);
2280 float dcy = (cand.Min.y + cand.Max.y) - (curr.Min.y + curr.Max.y);
2281 float dist_center = fabsf(dcx) + fabsf(dcy); // L1 metric (need this for our connectedness guarantee)
2282
2283 // Determine which quadrant of 'curr' our candidate item 'cand' lies in based on distance
2284 ImGuiDir quadrant;
2285 float dax = 0.0f, day = 0.0f, dist_axial = 0.0f;
2286 if (dbx != 0.0f || dby != 0.0f)
2287 {
2288 // For non-overlapping boxes, use distance between boxes
2289 dax = dbx;
2290 day = dby;
2291 dist_axial = dist_box;
2292 quadrant = NavScoreItemGetQuadrant(dbx, dby);
2293 }
2294 else if (dcx != 0.0f || dcy != 0.0f)
2295 {
2296 // For overlapping boxes with different centers, use distance between centers
2297 dax = dcx;
2298 day = dcy;
2299 dist_axial = dist_center;
2300 quadrant = NavScoreItemGetQuadrant(dcx, dcy);
2301 }
2302 else
2303 {
2304 // 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)
2305 quadrant = (window->DC.LastItemId < g.NavId) ? ImGuiDir_Left : ImGuiDir_Right;
2306 }
2307
2308 #if IMGUI_DEBUG_NAV_SCORING
2309 char buf[128];
2310 if (ImGui::IsMouseHoveringRect(cand.Min, cand.Max))
2311 {
2312 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]);
2313 g.OverlayDrawList.AddRect(curr.Min, curr.Max, IM_COL32(255, 200, 0, 100));
2314 g.OverlayDrawList.AddRect(cand.Min, cand.Max, IM_COL32(255, 255, 0, 200));
2315 g.OverlayDrawList.AddRectFilled(cand.Max - ImVec2(4, 4), cand.Max + ImGui::CalcTextSize(buf) + ImVec2(4, 4), IM_COL32(40, 0, 0, 150));
2316 g.OverlayDrawList.AddText(g.IO.FontDefault, 13.0f, cand.Max, ~0U, buf);
2317 }
2318 else if (g.IO.KeyCtrl) // Hold to preview score in matching quadrant. Press C to rotate.
2319 {
2320 if (IsKeyPressedMap(ImGuiKey_C))
2321 {
2322 g.NavMoveDirLast = (ImGuiDir)((g.NavMoveDirLast + 1) & 3);
2323 g.IO.KeysDownDuration[g.IO.KeyMap[ImGuiKey_C]] = 0.01f;
2324 }
2325 if (quadrant == g.NavMoveDir)
2326 {
2327 ImFormatString(buf, IM_ARRAYSIZE(buf), "%.0f/%.0f", dist_box, dist_center);
2328 g.OverlayDrawList.AddRectFilled(cand.Min, cand.Max, IM_COL32(255, 0, 0, 200));
2329 g.OverlayDrawList.AddText(g.IO.FontDefault, 13.0f, cand.Min, IM_COL32(255, 255, 255, 255), buf);
2330 }
2331 }
2332 #endif
2333
2334 // Is it in the quadrant we're interesting in moving to?
2335 bool new_best = false;
2336 if (quadrant == g.NavMoveDir)
2337 {
2338 // Does it beat the current best candidate?
2339 if (dist_box < result->DistBox)
2340 {
2341 result->DistBox = dist_box;
2342 result->DistCenter = dist_center;
2343 return true;
2344 }
2345 if (dist_box == result->DistBox)
2346 {
2347 // Try using distance between center points to break ties
2348 if (dist_center < result->DistCenter)
2349 {
2350 result->DistCenter = dist_center;
2351 new_best = true;
2352 }
2353 else if (dist_center == result->DistCenter)
2354 {
2355 // Still tied! we need to be extra-careful to make sure everything gets linked properly. We consistently break ties by symbolically moving "later" items
2356 // (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),
2357 // 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.
2358 if (((g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) ? dby : dbx) < 0.0f) // moving bj to the right/down decreases distance
2359 new_best = true;
2360 }
2361 }
2362 }
2363
2364 // 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
2365 // are found, so it only augments the graph produced by the above method using extra links. (important, since it doesn't guarantee strong connectedness)
2366 // 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.
2367 // 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.
2368 // Disabling it may however lead to disconnected graphs when nodes are very spaced out on different axis. Perhaps consider offering this as an option?
2369 if (result->DistBox == FLT_MAX && dist_axial < result->DistAxial) // Check axial match
2370 if (g.NavLayer == 1 && !(g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))
2371 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))
2372 {
2373 result->DistAxial = dist_axial;
2374 new_best = true;
2375 }
2376
2377 return new_best;
2378 }
2379
NavSaveLastChildNavWindow(ImGuiWindow * child_window)2380 static void NavSaveLastChildNavWindow(ImGuiWindow* child_window)
2381 {
2382 ImGuiWindow* parent_window = child_window;
2383 while (parent_window && (parent_window->Flags & ImGuiWindowFlags_ChildWindow) != 0 && (parent_window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) == 0)
2384 parent_window = parent_window->ParentWindow;
2385 if (parent_window && parent_window != child_window)
2386 parent_window->NavLastChildNavWindow = child_window;
2387 }
2388
2389 // Call when we are expected to land on Layer 0 after FocusWindow()
NavRestoreLastChildNavWindow(ImGuiWindow * window)2390 static ImGuiWindow* NavRestoreLastChildNavWindow(ImGuiWindow* window)
2391 {
2392 return window->NavLastChildNavWindow ? window->NavLastChildNavWindow : window;
2393 }
2394
NavRestoreLayer(int layer)2395 static void NavRestoreLayer(int layer)
2396 {
2397 ImGuiContext& g = *GImGui;
2398 g.NavLayer = layer;
2399 if (layer == 0)
2400 g.NavWindow = NavRestoreLastChildNavWindow(g.NavWindow);
2401 if (layer == 0 && g.NavWindow->NavLastIds[0] != 0)
2402 SetNavIDAndMoveMouse(g.NavWindow->NavLastIds[0], layer, g.NavWindow->NavRectRel[0]);
2403 else
2404 ImGui::NavInitWindow(g.NavWindow, true);
2405 }
2406
NavUpdateAnyRequestFlag()2407 static inline void NavUpdateAnyRequestFlag()
2408 {
2409 ImGuiContext& g = *GImGui;
2410 g.NavAnyRequest = g.NavMoveRequest || g.NavInitRequest || IMGUI_DEBUG_NAV_SCORING;
2411 }
2412
NavMoveRequestButNoResultYet()2413 static bool NavMoveRequestButNoResultYet()
2414 {
2415 ImGuiContext& g = *GImGui;
2416 return g.NavMoveRequest && g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0;
2417 }
2418
NavMoveRequestCancel()2419 void ImGui::NavMoveRequestCancel()
2420 {
2421 ImGuiContext& g = *GImGui;
2422 g.NavMoveRequest = false;
2423 NavUpdateAnyRequestFlag();
2424 }
2425
2426 // We get there when either NavId == id, or when g.NavAnyRequest is set (which is updated by NavUpdateAnyRequestFlag above)
NavProcessItem(ImGuiWindow * window,const ImRect & nav_bb,const ImGuiID id)2427 static void ImGui::NavProcessItem(ImGuiWindow* window, const ImRect& nav_bb, const ImGuiID id)
2428 {
2429 ImGuiContext& g = *GImGui;
2430 //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.
2431 // return;
2432
2433 const ImGuiItemFlags item_flags = window->DC.ItemFlags;
2434 const ImRect nav_bb_rel(nav_bb.Min - window->Pos, nav_bb.Max - window->Pos);
2435 if (g.NavInitRequest && g.NavLayer == window->DC.NavLayerCurrent)
2436 {
2437 // Even if 'ImGuiItemFlags_NoNavDefaultFocus' is on (typically collapse/close button) we record the first ResultId so they can be used as a fallback
2438 if (!(item_flags & ImGuiItemFlags_NoNavDefaultFocus) || g.NavInitResultId == 0)
2439 {
2440 g.NavInitResultId = id;
2441 g.NavInitResultRectRel = nav_bb_rel;
2442 }
2443 if (!(item_flags & ImGuiItemFlags_NoNavDefaultFocus))
2444 {
2445 g.NavInitRequest = false; // Found a match, clear request
2446 NavUpdateAnyRequestFlag();
2447 }
2448 }
2449
2450 // Scoring for navigation
2451 if (g.NavId != id && !(item_flags & ImGuiItemFlags_NoNav))
2452 {
2453 ImGuiNavMoveResult* result = (window == g.NavWindow) ? &g.NavMoveResultLocal : &g.NavMoveResultOther;
2454 #if IMGUI_DEBUG_NAV_SCORING
2455 // [DEBUG] Score all items in NavWindow at all times
2456 if (!g.NavMoveRequest)
2457 g.NavMoveDir = g.NavMoveDirLast;
2458 bool new_best = NavScoreItem(result, nav_bb) && g.NavMoveRequest;
2459 #else
2460 bool new_best = g.NavMoveRequest && NavScoreItem(result, nav_bb);
2461 #endif
2462 if (new_best)
2463 {
2464 result->ID = id;
2465 result->ParentID = window->IDStack.back();
2466 result->Window = window;
2467 result->RectRel = nav_bb_rel;
2468 }
2469 }
2470
2471 // Update window-relative bounding box of navigated item
2472 if (g.NavId == id)
2473 {
2474 g.NavWindow = window; // Always refresh g.NavWindow, because some operations such as FocusItem() don't have a window.
2475 g.NavLayer = window->DC.NavLayerCurrent;
2476 g.NavIdIsAlive = true;
2477 g.NavIdTabCounter = window->FocusIdxTabCounter;
2478 window->NavRectRel[window->DC.NavLayerCurrent] = nav_bb_rel; // Store item bounding box (relative to window position)
2479 }
2480 }
2481
2482 // Declare item bounding box for clipping and interaction.
2483 // Note that the size can be different than the one provided to ItemSize(). Typically, widgets that spread over available surface
2484 // declare their minimum size requirement to ItemSize() and then use a larger region for drawing/interaction, which is passed to ItemAdd().
ItemAdd(const ImRect & bb,ImGuiID id,const ImRect * nav_bb_arg)2485 bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg)
2486 {
2487 ImGuiContext& g = *GImGui;
2488 ImGuiWindow* window = g.CurrentWindow;
2489
2490 if (id != 0)
2491 {
2492 // Navigation processing runs prior to clipping early-out
2493 // (a) So that NavInitRequest can be honored, for newly opened windows to select a default widget
2494 // (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.
2495 // 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.
2496 // 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)
2497 window->DC.NavLayerActiveMaskNext |= window->DC.NavLayerCurrentMask;
2498 if (g.NavId == id || g.NavAnyRequest)
2499 if (g.NavWindow->RootWindowForNav == window->RootWindowForNav)
2500 if (window == g.NavWindow || ((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened))
2501 NavProcessItem(window, nav_bb_arg ? *nav_bb_arg : bb, id);
2502 }
2503
2504 window->DC.LastItemId = id;
2505 window->DC.LastItemRect = bb;
2506 window->DC.LastItemStatusFlags = 0;
2507
2508 // Clipping test
2509 const bool is_clipped = IsClippedEx(bb, id, false);
2510 if (is_clipped)
2511 return false;
2512 //if (g.IO.KeyAlt) window->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(255,255,0,120)); // [DEBUG]
2513
2514 // We need to calculate this now to take account of the current clipping rectangle (as items like Selectable may change them)
2515 if (IsMouseHoveringRect(bb.Min, bb.Max))
2516 window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HoveredRect;
2517 return true;
2518 }
2519
2520 // This is roughly matching the behavior of internal-facing ItemHoverable()
2521 // - 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()
2522 // - this should work even for non-interactive items that have no ID, so we cannot use LastItemId
IsItemHovered(ImGuiHoveredFlags flags)2523 bool ImGui::IsItemHovered(ImGuiHoveredFlags flags)
2524 {
2525 ImGuiContext& g = *GImGui;
2526 ImGuiWindow* window = g.CurrentWindow;
2527 if (g.NavDisableMouseHover && !g.NavDisableHighlight)
2528 return IsItemFocused();
2529
2530 // Test for bounding box overlap, as updated as ItemAdd()
2531 if (!(window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect))
2532 return false;
2533 IM_ASSERT((flags & (ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows)) == 0); // Flags not supported by this function
2534
2535 // Test if we are hovering the right window (our window could be behind another window)
2536 // [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.
2537 // 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.
2538 //if (g.HoveredWindow != window)
2539 // return false;
2540 if (g.HoveredRootWindow != window->RootWindow && !(flags & ImGuiHoveredFlags_AllowWhenOverlapped))
2541 return false;
2542
2543 // Test if another item is active (e.g. being dragged)
2544 if (!(flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
2545 if (g.ActiveId != 0 && g.ActiveId != window->DC.LastItemId && !g.ActiveIdAllowOverlap && g.ActiveId != window->MoveId)
2546 return false;
2547
2548 // Test if interactions on this window are blocked by an active popup or modal
2549 if (!IsWindowContentHoverable(window, flags))
2550 return false;
2551
2552 // Test if the item is disabled
2553 if (window->DC.ItemFlags & ImGuiItemFlags_Disabled)
2554 return false;
2555
2556 // 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.
2557 if (window->DC.LastItemId == window->MoveId && window->WriteAccessed)
2558 return false;
2559 return true;
2560 }
2561
2562 // Internal facing ItemHoverable() used when submitting widgets. Differs slightly from IsItemHovered().
ItemHoverable(const ImRect & bb,ImGuiID id)2563 bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id)
2564 {
2565 ImGuiContext& g = *GImGui;
2566 if (g.HoveredId != 0 && g.HoveredId != id && !g.HoveredIdAllowOverlap)
2567 return false;
2568
2569 ImGuiWindow* window = g.CurrentWindow;
2570 if (g.HoveredWindow != window)
2571 return false;
2572 if (g.ActiveId != 0 && g.ActiveId != id && !g.ActiveIdAllowOverlap)
2573 return false;
2574 if (!IsMouseHoveringRect(bb.Min, bb.Max))
2575 return false;
2576 if (g.NavDisableMouseHover || !IsWindowContentHoverable(window, ImGuiHoveredFlags_Default))
2577 return false;
2578 if (window->DC.ItemFlags & ImGuiItemFlags_Disabled)
2579 return false;
2580
2581 SetHoveredID(id);
2582 return true;
2583 }
2584
IsClippedEx(const ImRect & bb,ImGuiID id,bool clip_even_when_logged)2585 bool ImGui::IsClippedEx(const ImRect& bb, ImGuiID id, bool clip_even_when_logged)
2586 {
2587 ImGuiContext& g = *GImGui;
2588 ImGuiWindow* window = g.CurrentWindow;
2589 if (!bb.Overlaps(window->ClipRect))
2590 if (id == 0 || id != g.ActiveId)
2591 if (clip_even_when_logged || !g.LogEnabled)
2592 return true;
2593 return false;
2594 }
2595
FocusableItemRegister(ImGuiWindow * window,ImGuiID id,bool tab_stop)2596 bool ImGui::FocusableItemRegister(ImGuiWindow* window, ImGuiID id, bool tab_stop)
2597 {
2598 ImGuiContext& g = *GImGui;
2599
2600 const bool allow_keyboard_focus = (window->DC.ItemFlags & (ImGuiItemFlags_AllowKeyboardFocus | ImGuiItemFlags_Disabled)) == ImGuiItemFlags_AllowKeyboardFocus;
2601 window->FocusIdxAllCounter++;
2602 if (allow_keyboard_focus)
2603 window->FocusIdxTabCounter++;
2604
2605 // Process keyboard input at this point: TAB/Shift-TAB to tab out of the currently focused item.
2606 // Note that we can always TAB out of a widget that doesn't allow tabbing in.
2607 if (tab_stop && (g.ActiveId == id) && window->FocusIdxAllRequestNext == INT_MAX && window->FocusIdxTabRequestNext == INT_MAX && !g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab))
2608 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.
2609
2610 if (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent)
2611 return true;
2612 if (allow_keyboard_focus && window->FocusIdxTabCounter == window->FocusIdxTabRequestCurrent)
2613 {
2614 g.NavJustTabbedId = id;
2615 return true;
2616 }
2617
2618 return false;
2619 }
2620
FocusableItemUnregister(ImGuiWindow * window)2621 void ImGui::FocusableItemUnregister(ImGuiWindow* window)
2622 {
2623 window->FocusIdxAllCounter--;
2624 window->FocusIdxTabCounter--;
2625 }
2626
CalcItemSize(ImVec2 size,float default_x,float default_y)2627 ImVec2 ImGui::CalcItemSize(ImVec2 size, float default_x, float default_y)
2628 {
2629 ImGuiContext& g = *GImGui;
2630 ImVec2 content_max;
2631 if (size.x < 0.0f || size.y < 0.0f)
2632 content_max = g.CurrentWindow->Pos + GetContentRegionMax();
2633 if (size.x <= 0.0f)
2634 size.x = (size.x == 0.0f) ? default_x : ImMax(content_max.x - g.CurrentWindow->DC.CursorPos.x, 4.0f) + size.x;
2635 if (size.y <= 0.0f)
2636 size.y = (size.y == 0.0f) ? default_y : ImMax(content_max.y - g.CurrentWindow->DC.CursorPos.y, 4.0f) + size.y;
2637 return size;
2638 }
2639
CalcWrapWidthForPos(const ImVec2 & pos,float wrap_pos_x)2640 float ImGui::CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x)
2641 {
2642 if (wrap_pos_x < 0.0f)
2643 return 0.0f;
2644
2645 ImGuiWindow* window = GetCurrentWindowRead();
2646 if (wrap_pos_x == 0.0f)
2647 wrap_pos_x = GetContentRegionMax().x + window->Pos.x;
2648 else if (wrap_pos_x > 0.0f)
2649 wrap_pos_x += window->Pos.x - window->Scroll.x; // wrap_pos_x is provided is window local space
2650
2651 return ImMax(wrap_pos_x - pos.x, 1.0f);
2652 }
2653
2654 //-----------------------------------------------------------------------------
2655
MemAlloc(size_t sz)2656 void* ImGui::MemAlloc(size_t sz)
2657 {
2658 GImAllocatorActiveAllocationsCount++;
2659 return GImAllocatorAllocFunc(sz, GImAllocatorUserData);
2660 }
2661
MemFree(void * ptr)2662 void ImGui::MemFree(void* ptr)
2663 {
2664 if (ptr) GImAllocatorActiveAllocationsCount--;
2665 return GImAllocatorFreeFunc(ptr, GImAllocatorUserData);
2666 }
2667
GetClipboardText()2668 const char* ImGui::GetClipboardText()
2669 {
2670 return GImGui->IO.GetClipboardTextFn ? GImGui->IO.GetClipboardTextFn(GImGui->IO.ClipboardUserData) : "";
2671 }
2672
SetClipboardText(const char * text)2673 void ImGui::SetClipboardText(const char* text)
2674 {
2675 if (GImGui->IO.SetClipboardTextFn)
2676 GImGui->IO.SetClipboardTextFn(GImGui->IO.ClipboardUserData, text);
2677 }
2678
GetVersion()2679 const char* ImGui::GetVersion()
2680 {
2681 return IMGUI_VERSION;
2682 }
2683
2684 // Internal state access - if you want to share ImGui state between modules (e.g. DLL) or allocate it yourself
2685 // Note that we still point to some static data and members (such as GFontAtlas), so the state instance you end up using will point to the static data within its module
GetCurrentContext()2686 ImGuiContext* ImGui::GetCurrentContext()
2687 {
2688 return GImGui;
2689 }
2690
SetCurrentContext(ImGuiContext * ctx)2691 void ImGui::SetCurrentContext(ImGuiContext* ctx)
2692 {
2693 #ifdef IMGUI_SET_CURRENT_CONTEXT_FUNC
2694 IMGUI_SET_CURRENT_CONTEXT_FUNC(ctx); // For custom thread-based hackery you may want to have control over this.
2695 #else
2696 GImGui = ctx;
2697 #endif
2698 }
2699
SetAllocatorFunctions(void * (* alloc_func)(size_t sz,void * user_data),void (* free_func)(void * ptr,void * user_data),void * user_data)2700 void ImGui::SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* user_data), void (*free_func)(void* ptr, void* user_data), void* user_data)
2701 {
2702 GImAllocatorAllocFunc = alloc_func;
2703 GImAllocatorFreeFunc = free_func;
2704 GImAllocatorUserData = user_data;
2705 }
2706
CreateContext(ImFontAtlas * shared_font_atlas)2707 ImGuiContext* ImGui::CreateContext(ImFontAtlas* shared_font_atlas)
2708 {
2709 ImGuiContext* ctx = IM_NEW(ImGuiContext)(shared_font_atlas);
2710 if (GImGui == NULL)
2711 SetCurrentContext(ctx);
2712 Initialize(ctx);
2713 return ctx;
2714 }
2715
DestroyContext(ImGuiContext * ctx)2716 void ImGui::DestroyContext(ImGuiContext* ctx)
2717 {
2718 if (ctx == NULL)
2719 ctx = GImGui;
2720 Shutdown(ctx);
2721 if (GImGui == ctx)
2722 SetCurrentContext(NULL);
2723 IM_DELETE(ctx);
2724 }
2725
GetIO()2726 ImGuiIO& ImGui::GetIO()
2727 {
2728 IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() or ImGui::SetCurrentContext()?");
2729 return GImGui->IO;
2730 }
2731
GetStyle()2732 ImGuiStyle& ImGui::GetStyle()
2733 {
2734 IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() or ImGui::SetCurrentContext()?");
2735 return GImGui->Style;
2736 }
2737
2738 // Same value as passed to the old io.RenderDrawListsFn function. Valid after Render() and until the next call to NewFrame()
GetDrawData()2739 ImDrawData* ImGui::GetDrawData()
2740 {
2741 ImGuiContext& g = *GImGui;
2742 return g.DrawData.Valid ? &g.DrawData : NULL;
2743 }
2744
GetTime()2745 float ImGui::GetTime()
2746 {
2747 return GImGui->Time;
2748 }
2749
GetFrameCount()2750 int ImGui::GetFrameCount()
2751 {
2752 return GImGui->FrameCount;
2753 }
2754
GetOverlayDrawList()2755 ImDrawList* ImGui::GetOverlayDrawList()
2756 {
2757 return &GImGui->OverlayDrawList;
2758 }
2759
GetDrawListSharedData()2760 ImDrawListSharedData* ImGui::GetDrawListSharedData()
2761 {
2762 return &GImGui->DrawListSharedData;
2763 }
2764
2765 // This needs to be called before we submit any widget (aka in or before Begin)
NavInitWindow(ImGuiWindow * window,bool force_reinit)2766 void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit)
2767 {
2768 ImGuiContext& g = *GImGui;
2769 IM_ASSERT(window == g.NavWindow);
2770 bool init_for_nav = false;
2771 if (!(window->Flags & ImGuiWindowFlags_NoNavInputs))
2772 if (!(window->Flags & ImGuiWindowFlags_ChildWindow) || (window->Flags & ImGuiWindowFlags_Popup) || (window->NavLastIds[0] == 0) || force_reinit)
2773 init_for_nav = true;
2774 if (init_for_nav)
2775 {
2776 SetNavID(0, g.NavLayer);
2777 g.NavInitRequest = true;
2778 g.NavInitRequestFromMove = false;
2779 g.NavInitResultId = 0;
2780 g.NavInitResultRectRel = ImRect();
2781 NavUpdateAnyRequestFlag();
2782 }
2783 else
2784 {
2785 g.NavId = window->NavLastIds[0];
2786 }
2787 }
2788
NavCalcPreferredMousePos()2789 static ImVec2 NavCalcPreferredMousePos()
2790 {
2791 ImGuiContext& g = *GImGui;
2792 ImGuiWindow* window = g.NavWindow;
2793 if (!window)
2794 return g.IO.MousePos;
2795 const ImRect& rect_rel = window->NavRectRel[g.NavLayer];
2796 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()));
2797 ImRect visible_rect = GetViewportRect();
2798 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.
2799 }
2800
FindWindowIndex(ImGuiWindow * window)2801 static int FindWindowIndex(ImGuiWindow* window) // FIXME-OPT O(N)
2802 {
2803 ImGuiContext& g = *GImGui;
2804 for (int i = g.Windows.Size - 1; i >= 0; i--)
2805 if (g.Windows[i] == window)
2806 return i;
2807 return -1;
2808 }
2809
FindWindowNavigable(int i_start,int i_stop,int dir)2810 static ImGuiWindow* FindWindowNavigable(int i_start, int i_stop, int dir) // FIXME-OPT O(N)
2811 {
2812 ImGuiContext& g = *GImGui;
2813 for (int i = i_start; i >= 0 && i < g.Windows.Size && i != i_stop; i += dir)
2814 if (ImGui::IsWindowNavFocusable(g.Windows[i]))
2815 return g.Windows[i];
2816 return NULL;
2817 }
2818
GetNavInputAmount(ImGuiNavInput n,ImGuiInputReadMode mode)2819 float ImGui::GetNavInputAmount(ImGuiNavInput n, ImGuiInputReadMode mode)
2820 {
2821 ImGuiContext& g = *GImGui;
2822 if (mode == ImGuiInputReadMode_Down)
2823 return g.IO.NavInputs[n]; // Instant, read analog input (0.0f..1.0f, as provided by user)
2824
2825 const float t = g.IO.NavInputsDownDuration[n];
2826 if (t < 0.0f && mode == ImGuiInputReadMode_Released) // Return 1.0f when just released, no repeat, ignore analog input.
2827 return (g.IO.NavInputsDownDurationPrev[n] >= 0.0f ? 1.0f : 0.0f);
2828 if (t < 0.0f)
2829 return 0.0f;
2830 if (mode == ImGuiInputReadMode_Pressed) // Return 1.0f when just pressed, no repeat, ignore analog input.
2831 return (t == 0.0f) ? 1.0f : 0.0f;
2832 if (mode == ImGuiInputReadMode_Repeat)
2833 return (float)CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 0.80f, g.IO.KeyRepeatRate * 0.80f);
2834 if (mode == ImGuiInputReadMode_RepeatSlow)
2835 return (float)CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 1.00f, g.IO.KeyRepeatRate * 2.00f);
2836 if (mode == ImGuiInputReadMode_RepeatFast)
2837 return (float)CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, g.IO.KeyRepeatDelay * 0.80f, g.IO.KeyRepeatRate * 0.30f);
2838 return 0.0f;
2839 }
2840
2841 // Equivalent of IsKeyDown() for NavInputs[]
IsNavInputDown(ImGuiNavInput n)2842 static bool IsNavInputDown(ImGuiNavInput n)
2843 {
2844 return GImGui->IO.NavInputs[n] > 0.0f;
2845 }
2846
2847 // Equivalent of IsKeyPressed() for NavInputs[]
IsNavInputPressed(ImGuiNavInput n,ImGuiInputReadMode mode)2848 static bool IsNavInputPressed(ImGuiNavInput n, ImGuiInputReadMode mode)
2849 {
2850 return ImGui::GetNavInputAmount(n, mode) > 0.0f;
2851 }
2852
IsNavInputPressedAnyOfTwo(ImGuiNavInput n1,ImGuiNavInput n2,ImGuiInputReadMode mode)2853 static bool IsNavInputPressedAnyOfTwo(ImGuiNavInput n1, ImGuiNavInput n2, ImGuiInputReadMode mode)
2854 {
2855 return (ImGui::GetNavInputAmount(n1, mode) + ImGui::GetNavInputAmount(n2, mode)) > 0.0f;
2856 }
2857
GetNavInputAmount2d(ImGuiNavDirSourceFlags dir_sources,ImGuiInputReadMode mode,float slow_factor,float fast_factor)2858 ImVec2 ImGui::GetNavInputAmount2d(ImGuiNavDirSourceFlags dir_sources, ImGuiInputReadMode mode, float slow_factor, float fast_factor)
2859 {
2860 ImVec2 delta(0.0f, 0.0f);
2861 if (dir_sources & ImGuiNavDirSourceFlags_Keyboard)
2862 delta += ImVec2(GetNavInputAmount(ImGuiNavInput_KeyRight_, mode) - GetNavInputAmount(ImGuiNavInput_KeyLeft_, mode), GetNavInputAmount(ImGuiNavInput_KeyDown_, mode) - GetNavInputAmount(ImGuiNavInput_KeyUp_, mode));
2863 if (dir_sources & ImGuiNavDirSourceFlags_PadDPad)
2864 delta += ImVec2(GetNavInputAmount(ImGuiNavInput_DpadRight, mode) - GetNavInputAmount(ImGuiNavInput_DpadLeft, mode), GetNavInputAmount(ImGuiNavInput_DpadDown, mode) - GetNavInputAmount(ImGuiNavInput_DpadUp, mode));
2865 if (dir_sources & ImGuiNavDirSourceFlags_PadLStick)
2866 delta += ImVec2(GetNavInputAmount(ImGuiNavInput_LStickRight, mode) - GetNavInputAmount(ImGuiNavInput_LStickLeft, mode), GetNavInputAmount(ImGuiNavInput_LStickDown, mode) - GetNavInputAmount(ImGuiNavInput_LStickUp, mode));
2867 if (slow_factor != 0.0f && IsNavInputDown(ImGuiNavInput_TweakSlow))
2868 delta *= slow_factor;
2869 if (fast_factor != 0.0f && IsNavInputDown(ImGuiNavInput_TweakFast))
2870 delta *= fast_factor;
2871 return delta;
2872 }
2873
NavUpdateWindowingHighlightWindow(int focus_change_dir)2874 static void NavUpdateWindowingHighlightWindow(int focus_change_dir)
2875 {
2876 ImGuiContext& g = *GImGui;
2877 IM_ASSERT(g.NavWindowingTarget);
2878 if (g.NavWindowingTarget->Flags & ImGuiWindowFlags_Modal)
2879 return;
2880
2881 const int i_current = FindWindowIndex(g.NavWindowingTarget);
2882 ImGuiWindow* window_target = FindWindowNavigable(i_current + focus_change_dir, -INT_MAX, focus_change_dir);
2883 if (!window_target)
2884 window_target = FindWindowNavigable((focus_change_dir < 0) ? (g.Windows.Size - 1) : 0, i_current, focus_change_dir);
2885 g.NavWindowingTarget = window_target;
2886 g.NavWindowingToggleLayer = false;
2887 }
2888
2889 // Window management mode (hold to: change focus/move/resize, tap to: toggle menu layer)
NavUpdateWindowing()2890 static void ImGui::NavUpdateWindowing()
2891 {
2892 ImGuiContext& g = *GImGui;
2893 ImGuiWindow* apply_focus_window = NULL;
2894 bool apply_toggle_layer = false;
2895
2896 bool start_windowing_with_gamepad = !g.NavWindowingTarget && IsNavInputPressed(ImGuiNavInput_Menu, ImGuiInputReadMode_Pressed);
2897 bool start_windowing_with_keyboard = !g.NavWindowingTarget && g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab) && (g.IO.NavFlags & ImGuiNavFlags_EnableKeyboard);
2898 if (start_windowing_with_gamepad || start_windowing_with_keyboard)
2899 if (ImGuiWindow* window = g.NavWindow ? g.NavWindow : FindWindowNavigable(g.Windows.Size - 1, -INT_MAX, -1))
2900 {
2901 g.NavWindowingTarget = window->RootWindowForTabbing;
2902 g.NavWindowingHighlightTimer = g.NavWindowingHighlightAlpha = 0.0f;
2903 g.NavWindowingToggleLayer = start_windowing_with_keyboard ? false : true;
2904 g.NavWindowingInputSource = start_windowing_with_keyboard ? ImGuiInputSource_NavKeyboard : ImGuiInputSource_NavGamepad;
2905 }
2906
2907 // Gamepad update
2908 g.NavWindowingHighlightTimer += g.IO.DeltaTime;
2909 if (g.NavWindowingTarget && g.NavWindowingInputSource == ImGuiInputSource_NavGamepad)
2910 {
2911 // 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
2912 g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingHighlightTimer - 0.20f) / 0.05f));
2913
2914 // Select window to focus
2915 const int focus_change_dir = (int)IsNavInputPressed(ImGuiNavInput_FocusPrev, ImGuiInputReadMode_RepeatSlow) - (int)IsNavInputPressed(ImGuiNavInput_FocusNext, ImGuiInputReadMode_RepeatSlow);
2916 if (focus_change_dir != 0)
2917 {
2918 NavUpdateWindowingHighlightWindow(focus_change_dir);
2919 g.NavWindowingHighlightAlpha = 1.0f;
2920 }
2921
2922 // Single press toggles NavLayer, long press with L/R apply actual focus on release (until then the window was merely rendered front-most)
2923 if (!IsNavInputDown(ImGuiNavInput_Menu))
2924 {
2925 g.NavWindowingToggleLayer &= (g.NavWindowingHighlightAlpha < 1.0f); // Once button was held long enough we don't consider it a tap-to-toggle-layer press anymore.
2926 if (g.NavWindowingToggleLayer && g.NavWindow)
2927 apply_toggle_layer = true;
2928 else if (!g.NavWindowingToggleLayer)
2929 apply_focus_window = g.NavWindowingTarget;
2930 g.NavWindowingTarget = NULL;
2931 }
2932 }
2933
2934 // Keyboard: Focus
2935 if (g.NavWindowingTarget && g.NavWindowingInputSource == ImGuiInputSource_NavKeyboard)
2936 {
2937 // Visuals only appears after a brief time after pressing TAB the first time, so that a fast CTRL+TAB doesn't add visual noise
2938 g.NavWindowingHighlightAlpha = ImMax(g.NavWindowingHighlightAlpha, ImSaturate((g.NavWindowingHighlightTimer - 0.15f) / 0.04f)); // 1.0f
2939 if (IsKeyPressedMap(ImGuiKey_Tab, true))
2940 NavUpdateWindowingHighlightWindow(g.IO.KeyShift ? +1 : -1);
2941 if (!g.IO.KeyCtrl)
2942 apply_focus_window = g.NavWindowingTarget;
2943 }
2944
2945 // Keyboard: Press and Release ALT to toggle menu layer
2946 // 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
2947 if ((g.ActiveId == 0 || g.ActiveIdAllowOverlap) && IsNavInputPressed(ImGuiNavInput_KeyMenu_, ImGuiInputReadMode_Released))
2948 if (IsMousePosValid(&g.IO.MousePos) == IsMousePosValid(&g.IO.MousePosPrev))
2949 apply_toggle_layer = true;
2950
2951 // Move window
2952 if (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoMove))
2953 {
2954 ImVec2 move_delta;
2955 if (g.NavWindowingInputSource == ImGuiInputSource_NavKeyboard && !g.IO.KeyShift)
2956 move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard, ImGuiInputReadMode_Down);
2957 if (g.NavWindowingInputSource == ImGuiInputSource_NavGamepad)
2958 move_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadLStick, ImGuiInputReadMode_Down);
2959 if (move_delta.x != 0.0f || move_delta.y != 0.0f)
2960 {
2961 const float NAV_MOVE_SPEED = 800.0f;
2962 const float move_speed = ImFloor(NAV_MOVE_SPEED * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y));
2963 g.NavWindowingTarget->PosFloat += move_delta * move_speed;
2964 g.NavDisableMouseHover = true;
2965 MarkIniSettingsDirty(g.NavWindowingTarget);
2966 }
2967 }
2968
2969 // Apply final focus
2970 if (apply_focus_window && (g.NavWindow == NULL || apply_focus_window != g.NavWindow->RootWindowForTabbing))
2971 {
2972 g.NavDisableHighlight = false;
2973 g.NavDisableMouseHover = true;
2974 apply_focus_window = NavRestoreLastChildNavWindow(apply_focus_window);
2975 ClosePopupsOverWindow(apply_focus_window);
2976 FocusWindow(apply_focus_window);
2977 if (apply_focus_window->NavLastIds[0] == 0)
2978 NavInitWindow(apply_focus_window, false);
2979
2980 // If the window only has a menu layer, select it directly
2981 if (apply_focus_window->DC.NavLayerActiveMask == (1 << 1))
2982 g.NavLayer = 1;
2983 }
2984 if (apply_focus_window)
2985 g.NavWindowingTarget = NULL;
2986
2987 // Apply menu/layer toggle
2988 if (apply_toggle_layer && g.NavWindow)
2989 {
2990 ImGuiWindow* new_nav_window = g.NavWindow;
2991 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)
2992 new_nav_window = new_nav_window->ParentWindow;
2993 if (new_nav_window != g.NavWindow)
2994 {
2995 ImGuiWindow* old_nav_window = g.NavWindow;
2996 FocusWindow(new_nav_window);
2997 new_nav_window->NavLastChildNavWindow = old_nav_window;
2998 }
2999 g.NavDisableHighlight = false;
3000 g.NavDisableMouseHover = true;
3001 NavRestoreLayer((g.NavWindow->DC.NavLayerActiveMask & (1 << 1)) ? (g.NavLayer ^ 1) : 0);
3002 }
3003 }
3004
3005 // NB: We modify rect_rel by the amount we scrolled for, so it is immediately updated.
NavScrollToBringItemIntoView(ImGuiWindow * window,ImRect & item_rect_rel)3006 static void NavScrollToBringItemIntoView(ImGuiWindow* window, ImRect& item_rect_rel)
3007 {
3008 // Scroll to keep newly navigated item fully into view
3009 ImRect window_rect_rel(window->InnerRect.Min - window->Pos - ImVec2(1, 1), window->InnerRect.Max - window->Pos + ImVec2(1, 1));
3010 //g.OverlayDrawList.AddRect(window->Pos + window_rect_rel.Min, window->Pos + window_rect_rel.Max, IM_COL32_WHITE); // [DEBUG]
3011 if (window_rect_rel.Contains(item_rect_rel))
3012 return;
3013
3014 ImGuiContext& g = *GImGui;
3015 if (window->ScrollbarX && item_rect_rel.Min.x < window_rect_rel.Min.x)
3016 {
3017 window->ScrollTarget.x = item_rect_rel.Min.x + window->Scroll.x - g.Style.ItemSpacing.x;
3018 window->ScrollTargetCenterRatio.x = 0.0f;
3019 }
3020 else if (window->ScrollbarX && item_rect_rel.Max.x >= window_rect_rel.Max.x)
3021 {
3022 window->ScrollTarget.x = item_rect_rel.Max.x + window->Scroll.x + g.Style.ItemSpacing.x;
3023 window->ScrollTargetCenterRatio.x = 1.0f;
3024 }
3025 if (item_rect_rel.Min.y < window_rect_rel.Min.y)
3026 {
3027 window->ScrollTarget.y = item_rect_rel.Min.y + window->Scroll.y - g.Style.ItemSpacing.y;
3028 window->ScrollTargetCenterRatio.y = 0.0f;
3029 }
3030 else if (item_rect_rel.Max.y >= window_rect_rel.Max.y)
3031 {
3032 window->ScrollTarget.y = item_rect_rel.Max.y + window->Scroll.y + g.Style.ItemSpacing.y;
3033 window->ScrollTargetCenterRatio.y = 1.0f;
3034 }
3035
3036 // Estimate upcoming scroll so we can offset our relative mouse position so mouse position can be applied immediately (under this block)
3037 ImVec2 next_scroll = CalcNextScrollFromScrollTargetAndClamp(window);
3038 item_rect_rel.Translate(window->Scroll - next_scroll);
3039 }
3040
NavUpdate()3041 static void ImGui::NavUpdate()
3042 {
3043 ImGuiContext& g = *GImGui;
3044 g.IO.WantMoveMouse = false;
3045
3046 #if 0
3047 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);
3048 #endif
3049
3050 // Update Keyboard->Nav inputs mapping
3051 memset(g.IO.NavInputs + ImGuiNavInput_InternalStart_, 0, (ImGuiNavInput_COUNT - ImGuiNavInput_InternalStart_) * sizeof(g.IO.NavInputs[0]));
3052 if (g.IO.NavFlags & ImGuiNavFlags_EnableKeyboard)
3053 {
3054 #define NAV_MAP_KEY(_KEY, _NAV_INPUT) \
3055 if (g.IO.KeyMap[_KEY] != -1 && IsKeyDown(g.IO.KeyMap[_KEY])) g.IO.NavInputs[_NAV_INPUT] = 1.0f;
3056 NAV_MAP_KEY(ImGuiKey_Space, ImGuiNavInput_Activate);
3057 NAV_MAP_KEY(ImGuiKey_Enter, ImGuiNavInput_Input);
3058 NAV_MAP_KEY(ImGuiKey_Escape, ImGuiNavInput_Cancel);
3059 NAV_MAP_KEY(ImGuiKey_LeftArrow, ImGuiNavInput_KeyLeft_);
3060 NAV_MAP_KEY(ImGuiKey_RightArrow, ImGuiNavInput_KeyRight_);
3061 NAV_MAP_KEY(ImGuiKey_UpArrow, ImGuiNavInput_KeyUp_);
3062 NAV_MAP_KEY(ImGuiKey_DownArrow, ImGuiNavInput_KeyDown_);
3063 if (g.IO.KeyCtrl) g.IO.NavInputs[ImGuiNavInput_TweakSlow] = 1.0f;
3064 if (g.IO.KeyShift) g.IO.NavInputs[ImGuiNavInput_TweakFast] = 1.0f;
3065 if (g.IO.KeyAlt) g.IO.NavInputs[ImGuiNavInput_KeyMenu_] = 1.0f;
3066 #undef NAV_MAP_KEY
3067 }
3068
3069 memcpy(g.IO.NavInputsDownDurationPrev, g.IO.NavInputsDownDuration, sizeof(g.IO.NavInputsDownDuration));
3070 for (int i = 0; i < IM_ARRAYSIZE(g.IO.NavInputs); i++)
3071 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;
3072
3073 // Process navigation init request (select first/default focus)
3074 if (g.NavInitResultId != 0 && (!g.NavDisableHighlight || g.NavInitRequestFromMove))
3075 {
3076 // Apply result from previous navigation init request (will typically select the first item, unless SetItemDefaultFocus() has been called)
3077 IM_ASSERT(g.NavWindow);
3078 if (g.NavInitRequestFromMove)
3079 SetNavIDAndMoveMouse(g.NavInitResultId, g.NavLayer, g.NavInitResultRectRel);
3080 else
3081 SetNavID(g.NavInitResultId, g.NavLayer);
3082 g.NavWindow->NavRectRel[g.NavLayer] = g.NavInitResultRectRel;
3083 }
3084 g.NavInitRequest = false;
3085 g.NavInitRequestFromMove = false;
3086 g.NavInitResultId = 0;
3087 g.NavJustMovedToId = 0;
3088
3089 // Process navigation move request
3090 if (g.NavMoveRequest && (g.NavMoveResultLocal.ID != 0 || g.NavMoveResultOther.ID != 0))
3091 {
3092 // Select which result to use
3093 ImGuiNavMoveResult* result = (g.NavMoveResultLocal.ID != 0) ? &g.NavMoveResultLocal : &g.NavMoveResultOther;
3094 if (g.NavMoveResultOther.ID != 0 && g.NavMoveResultOther.Window->ParentWindow == g.NavWindow) // Maybe entering a flattened child? In this case solve the tie using the regular scoring rules
3095 if ((g.NavMoveResultOther.DistBox < g.NavMoveResultLocal.DistBox) || (g.NavMoveResultOther.DistBox == g.NavMoveResultLocal.DistBox && g.NavMoveResultOther.DistCenter < g.NavMoveResultLocal.DistCenter))
3096 result = &g.NavMoveResultOther;
3097
3098 IM_ASSERT(g.NavWindow && result->Window);
3099
3100 // Scroll to keep newly navigated item fully into view
3101 if (g.NavLayer == 0)
3102 NavScrollToBringItemIntoView(result->Window, result->RectRel);
3103
3104 // Apply result from previous frame navigation directional move request
3105 ClearActiveID();
3106 g.NavWindow = result->Window;
3107 SetNavIDAndMoveMouse(result->ID, g.NavLayer, result->RectRel);
3108 g.NavJustMovedToId = result->ID;
3109 g.NavMoveFromClampedRefRect = false;
3110 }
3111
3112 // When a forwarded move request failed, we restore the highlight that we disabled during the forward frame
3113 if (g.NavMoveRequestForward == ImGuiNavForward_ForwardActive)
3114 {
3115 IM_ASSERT(g.NavMoveRequest);
3116 if (g.NavMoveResultLocal.ID == 0 && g.NavMoveResultOther.ID == 0)
3117 g.NavDisableHighlight = false;
3118 g.NavMoveRequestForward = ImGuiNavForward_None;
3119 }
3120
3121 // Apply application mouse position movement, after we had a chance to process move request result.
3122 if (g.NavMousePosDirty && g.NavIdIsAlive)
3123 {
3124 // Set mouse position given our knowledge of the nav widget position from last frame
3125 if (g.IO.NavFlags & ImGuiNavFlags_MoveMouse)
3126 {
3127 g.IO.MousePos = g.IO.MousePosPrev = NavCalcPreferredMousePos();
3128 g.IO.WantMoveMouse = true;
3129 }
3130 g.NavMousePosDirty = false;
3131 }
3132 g.NavIdIsAlive = false;
3133 g.NavJustTabbedId = 0;
3134 IM_ASSERT(g.NavLayer == 0 || g.NavLayer == 1);
3135
3136 // 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
3137 if (g.NavWindow)
3138 NavSaveLastChildNavWindow(g.NavWindow);
3139 if (g.NavWindow && g.NavWindow->NavLastChildNavWindow != NULL && g.NavLayer == 0)
3140 g.NavWindow->NavLastChildNavWindow = NULL;
3141
3142 NavUpdateWindowing();
3143
3144 // Set output flags for user application
3145 g.IO.NavActive = (g.IO.NavFlags & (ImGuiNavFlags_EnableGamepad | ImGuiNavFlags_EnableKeyboard)) && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs);
3146 g.IO.NavVisible = (g.IO.NavActive && g.NavId != 0 && !g.NavDisableHighlight) || (g.NavWindowingTarget != NULL) || g.NavInitRequest;
3147
3148 // Process NavCancel input (to close a popup, get back to parent, clear focus)
3149 if (IsNavInputPressed(ImGuiNavInput_Cancel, ImGuiInputReadMode_Pressed))
3150 {
3151 if (g.ActiveId != 0)
3152 {
3153 ClearActiveID();
3154 }
3155 else if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow) && !(g.NavWindow->Flags & ImGuiWindowFlags_Popup) && g.NavWindow->ParentWindow)
3156 {
3157 // Exit child window
3158 ImGuiWindow* child_window = g.NavWindow;
3159 ImGuiWindow* parent_window = g.NavWindow->ParentWindow;
3160 IM_ASSERT(child_window->ChildId != 0);
3161 FocusWindow(parent_window);
3162 SetNavID(child_window->ChildId, 0);
3163 g.NavIdIsAlive = false;
3164 if (g.NavDisableMouseHover)
3165 g.NavMousePosDirty = true;
3166 }
3167 else if (g.OpenPopupStack.Size > 0)
3168 {
3169 // Close open popup/menu
3170 if (!(g.OpenPopupStack.back().Window->Flags & ImGuiWindowFlags_Modal))
3171 ClosePopupToLevel(g.OpenPopupStack.Size - 1);
3172 }
3173 else if (g.NavLayer != 0)
3174 {
3175 // Leave the "menu" layer
3176 NavRestoreLayer(0);
3177 }
3178 else
3179 {
3180 // Clear NavLastId for popups but keep it for regular child window so we can leave one and come back where we were
3181 if (g.NavWindow && ((g.NavWindow->Flags & ImGuiWindowFlags_Popup) || !(g.NavWindow->Flags & ImGuiWindowFlags_ChildWindow)))
3182 g.NavWindow->NavLastIds[0] = 0;
3183 g.NavId = 0;
3184 }
3185 }
3186
3187 // Process manual activation request
3188 g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavInputId = 0;
3189 if (g.NavId != 0 && !g.NavDisableHighlight && !g.NavWindowingTarget && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))
3190 {
3191 bool activate_down = IsNavInputDown(ImGuiNavInput_Activate);
3192 bool activate_pressed = activate_down && IsNavInputPressed(ImGuiNavInput_Activate, ImGuiInputReadMode_Pressed);
3193 if (g.ActiveId == 0 && activate_pressed)
3194 g.NavActivateId = g.NavId;
3195 if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_down)
3196 g.NavActivateDownId = g.NavId;
3197 if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && activate_pressed)
3198 g.NavActivatePressedId = g.NavId;
3199 if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && IsNavInputPressed(ImGuiNavInput_Input, ImGuiInputReadMode_Pressed))
3200 g.NavInputId = g.NavId;
3201 }
3202 if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))
3203 g.NavDisableHighlight = true;
3204 if (g.NavActivateId != 0)
3205 IM_ASSERT(g.NavActivateDownId == g.NavActivateId);
3206 g.NavMoveRequest = false;
3207
3208 // Process programmatic activation request
3209 if (g.NavNextActivateId != 0)
3210 g.NavActivateId = g.NavActivateDownId = g.NavActivatePressedId = g.NavInputId = g.NavNextActivateId;
3211 g.NavNextActivateId = 0;
3212
3213 // Initiate directional inputs request
3214 const int allowed_dir_flags = (g.ActiveId == 0) ? ~0 : g.ActiveIdAllowNavDirFlags;
3215 if (g.NavMoveRequestForward == ImGuiNavForward_None)
3216 {
3217 g.NavMoveDir = ImGuiDir_None;
3218 if (g.NavWindow && !g.NavWindowingTarget && allowed_dir_flags && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs))
3219 {
3220 if ((allowed_dir_flags & (1 << ImGuiDir_Left)) && IsNavInputPressedAnyOfTwo(ImGuiNavInput_DpadLeft, ImGuiNavInput_KeyLeft_, ImGuiInputReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Left;
3221 if ((allowed_dir_flags & (1 << ImGuiDir_Right)) && IsNavInputPressedAnyOfTwo(ImGuiNavInput_DpadRight, ImGuiNavInput_KeyRight_, ImGuiInputReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Right;
3222 if ((allowed_dir_flags & (1 << ImGuiDir_Up)) && IsNavInputPressedAnyOfTwo(ImGuiNavInput_DpadUp, ImGuiNavInput_KeyUp_, ImGuiInputReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Up;
3223 if ((allowed_dir_flags & (1 << ImGuiDir_Down)) && IsNavInputPressedAnyOfTwo(ImGuiNavInput_DpadDown, ImGuiNavInput_KeyDown_, ImGuiInputReadMode_Repeat)) g.NavMoveDir = ImGuiDir_Down;
3224 }
3225 }
3226 else
3227 {
3228 // 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)
3229 IM_ASSERT(g.NavMoveDir != ImGuiDir_None);
3230 IM_ASSERT(g.NavMoveRequestForward == ImGuiNavForward_ForwardQueued);
3231 g.NavMoveRequestForward = ImGuiNavForward_ForwardActive;
3232 }
3233
3234 if (g.NavMoveDir != ImGuiDir_None)
3235 {
3236 g.NavMoveRequest = true;
3237 g.NavMoveDirLast = g.NavMoveDir;
3238 }
3239
3240 // 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
3241 if (g.NavMoveRequest && g.NavId == 0)
3242 {
3243 g.NavInitRequest = g.NavInitRequestFromMove = true;
3244 g.NavInitResultId = 0;
3245 g.NavDisableHighlight = false;
3246 }
3247
3248 NavUpdateAnyRequestFlag();
3249
3250 // Scrolling
3251 if (g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.NavWindowingTarget)
3252 {
3253 // *Fallback* manual-scroll with NavUp/NavDown when window has no navigable item
3254 ImGuiWindow* window = g.NavWindow;
3255 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.
3256 if (window->DC.NavLayerActiveMask == 0x00 && window->DC.NavHasScroll && g.NavMoveRequest)
3257 {
3258 if (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right)
3259 SetWindowScrollX(window, ImFloor(window->Scroll.x + ((g.NavMoveDir == ImGuiDir_Left) ? -1.0f : +1.0f) * scroll_speed));
3260 if (g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down)
3261 SetWindowScrollY(window, ImFloor(window->Scroll.y + ((g.NavMoveDir == ImGuiDir_Up) ? -1.0f : +1.0f) * scroll_speed));
3262 }
3263
3264 // *Normal* Manual scroll with NavScrollXXX keys
3265 // Next movement request will clamp the NavId reference rectangle to the visible area, so navigation will resume within those bounds.
3266 ImVec2 scroll_dir = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadLStick, ImGuiInputReadMode_Down, 1.0f / 10.0f, 10.0f);
3267 if (scroll_dir.x != 0.0f && window->ScrollbarX)
3268 {
3269 SetWindowScrollX(window, ImFloor(window->Scroll.x + scroll_dir.x * scroll_speed));
3270 g.NavMoveFromClampedRefRect = true;
3271 }
3272 if (scroll_dir.y != 0.0f)
3273 {
3274 SetWindowScrollY(window, ImFloor(window->Scroll.y + scroll_dir.y * scroll_speed));
3275 g.NavMoveFromClampedRefRect = true;
3276 }
3277 }
3278
3279 // Reset search results
3280 g.NavMoveResultLocal.Clear();
3281 g.NavMoveResultOther.Clear();
3282
3283 // 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
3284 if (g.NavMoveRequest && g.NavMoveFromClampedRefRect && g.NavLayer == 0)
3285 {
3286 ImGuiWindow* window = g.NavWindow;
3287 ImRect window_rect_rel(window->InnerRect.Min - window->Pos - ImVec2(1, 1), window->InnerRect.Max - window->Pos + ImVec2(1, 1));
3288 if (!window_rect_rel.Contains(window->NavRectRel[g.NavLayer]))
3289 {
3290 float pad = window->CalcFontSize() * 0.5f;
3291 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
3292 window->NavRectRel[g.NavLayer].ClipWith(window_rect_rel);
3293 g.NavId = 0;
3294 }
3295 g.NavMoveFromClampedRefRect = false;
3296 }
3297
3298 // 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)
3299 ImRect nav_rect_rel = (g.NavWindow && g.NavWindow->NavRectRel[g.NavLayer].IsFinite()) ? g.NavWindow->NavRectRel[g.NavLayer] : ImRect(0, 0, 0, 0);
3300 g.NavScoringRectScreen = g.NavWindow ? ImRect(g.NavWindow->Pos + nav_rect_rel.Min, g.NavWindow->Pos + nav_rect_rel.Max) : GetViewportRect();
3301 g.NavScoringRectScreen.Min.x = ImMin(g.NavScoringRectScreen.Min.x + 1.0f, g.NavScoringRectScreen.Max.x);
3302 g.NavScoringRectScreen.Max.x = g.NavScoringRectScreen.Min.x;
3303 IM_ASSERT(!g.NavScoringRectScreen.IsInverted()); // Ensure if we have a finite, non-inverted bounding box here will allows us to remove extraneous fabsf() calls in NavScoreItem().
3304 //g.OverlayDrawList.AddRect(g.NavScoringRectScreen.Min, g.NavScoringRectScreen.Max, IM_COL32(255,200,0,255)); // [DEBUG]
3305 g.NavScoringCount = 0;
3306 #if IMGUI_DEBUG_NAV_RECTS
3307 if (g.NavWindow)
3308 {
3309 for (int layer = 0; layer < 2; layer++) g.OverlayDrawList.AddRect(g.NavWindow->Pos + g.NavWindow->NavRectRel[layer].Min, g.NavWindow->Pos + g.NavWindow->NavRectRel[layer].Max, IM_COL32(255, 200, 0, 255));
3310 } // [DEBUG]
3311 if (g.NavWindow)
3312 {
3313 ImU32 col = (g.NavWindow->HiddenFrames <= 0) ? IM_COL32(255, 0, 255, 255) : IM_COL32(255, 0, 0, 255);
3314 ImVec2 p = NavCalcPreferredMousePos();
3315 char buf[32];
3316 ImFormatString(buf, 32, "%d", g.NavLayer);
3317 g.OverlayDrawList.AddCircleFilled(p, 3.0f, col);
3318 g.OverlayDrawList.AddText(NULL, 13.0f, p + ImVec2(8, -4), col, buf);
3319 }
3320 #endif
3321 }
3322
UpdateMovingWindow()3323 static void ImGui::UpdateMovingWindow()
3324 {
3325 ImGuiContext& g = *GImGui;
3326 if (g.MovingWindow && g.MovingWindow->MoveId == g.ActiveId && g.ActiveIdSource == ImGuiInputSource_Mouse)
3327 {
3328 // We actually want to move the root window. g.MovingWindow == window we clicked on (could be a child window).
3329 // We track it to preserve Focus and so that ActiveIdWindow == MovingWindow and ActiveId == MovingWindow->MoveId for consistency.
3330 KeepAliveID(g.ActiveId);
3331 IM_ASSERT(g.MovingWindow && g.MovingWindow->RootWindow);
3332 ImGuiWindow* moving_window = g.MovingWindow->RootWindow;
3333 if (g.IO.MouseDown[0])
3334 {
3335 ImVec2 pos = g.IO.MousePos - g.ActiveIdClickOffset;
3336 if (moving_window->PosFloat.x != pos.x || moving_window->PosFloat.y != pos.y)
3337 {
3338 MarkIniSettingsDirty(moving_window);
3339 moving_window->PosFloat = pos;
3340 }
3341 FocusWindow(g.MovingWindow);
3342 }
3343 else
3344 {
3345 ClearActiveID();
3346 g.MovingWindow = NULL;
3347 }
3348 }
3349 else
3350 {
3351 // When clicking/dragging from a window that has the _NoMove flag, we still set the ActiveId in order to prevent hovering others.
3352 if (g.ActiveIdWindow && g.ActiveIdWindow->MoveId == g.ActiveId)
3353 {
3354 KeepAliveID(g.ActiveId);
3355 if (!g.IO.MouseDown[0])
3356 ClearActiveID();
3357 }
3358 g.MovingWindow = NULL;
3359 }
3360 }
3361
NewFrame()3362 void ImGui::NewFrame()
3363 {
3364 IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() or ImGui::SetCurrentContext()?");
3365 ImGuiContext& g = *GImGui;
3366
3367 // Check user data
3368 // (We pass an error message in the assert expression as a trick to get it visible to programmers who are not using a debugger, as most assert handlers display their argument)
3369 IM_ASSERT(g.Initialized);
3370 IM_ASSERT(g.IO.DeltaTime >= 0.0f && "Need a positive DeltaTime (zero is tolerated but will cause some timing issues)");
3371 IM_ASSERT(g.IO.DisplaySize.x >= 0.0f && g.IO.DisplaySize.y >= 0.0f && "Invalid DisplaySize value");
3372 IM_ASSERT(g.IO.Fonts->Fonts.Size > 0 && "Font Atlas not built. Did you call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8() ?");
3373 IM_ASSERT(g.IO.Fonts->Fonts[0]->IsLoaded() && "Font Atlas not built. Did you call io.Fonts->GetTexDataAsRGBA32() / GetTexDataAsAlpha8() ?");
3374 IM_ASSERT(g.Style.CurveTessellationTol > 0.0f && "Invalid style setting");
3375 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)");
3376 IM_ASSERT((g.FrameCount == 0 || g.FrameCountEnded == g.FrameCount) && "Forgot to call Render() or EndFrame() at the end of the previous frame?");
3377 for (int n = 0; n < ImGuiKey_COUNT; n++)
3378 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)");
3379
3380 // Do a 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 super recently added in 1.60 WIP)
3381 if (g.IO.NavFlags & ImGuiNavFlags_EnableKeyboard)
3382 IM_ASSERT(g.IO.KeyMap[ImGuiKey_Space] != -1 && "ImGuiKey_Space is not mapped, required for keyboard navigation.");
3383
3384 // Load settings on first frame
3385 if (!g.SettingsLoaded)
3386 {
3387 IM_ASSERT(g.SettingsWindows.empty());
3388 LoadIniSettingsFromDisk(g.IO.IniFilename);
3389 g.SettingsLoaded = true;
3390 }
3391
3392 g.Time += g.IO.DeltaTime;
3393 g.FrameCount += 1;
3394 g.TooltipOverrideCount = 0;
3395 g.WindowsActiveCount = 0;
3396
3397 SetCurrentFont(GetDefaultFont());
3398 IM_ASSERT(g.Font->IsLoaded());
3399 g.DrawListSharedData.ClipRectFullscreen = ImVec4(0.0f, 0.0f, g.IO.DisplaySize.x, g.IO.DisplaySize.y);
3400 g.DrawListSharedData.CurveTessellationTol = g.Style.CurveTessellationTol;
3401
3402 g.OverlayDrawList.Clear();
3403 g.OverlayDrawList.PushTextureID(g.IO.Fonts->TexID);
3404 g.OverlayDrawList.PushClipRectFullScreen();
3405 g.OverlayDrawList.Flags = (g.Style.AntiAliasedLines ? ImDrawListFlags_AntiAliasedLines : 0) | (g.Style.AntiAliasedFill ? ImDrawListFlags_AntiAliasedFill : 0);
3406
3407 // Mark rendering data as invalid to prevent user who may have a handle on it to use it
3408 g.DrawData.Clear();
3409
3410 // Clear reference to active widget if the widget isn't alive anymore
3411 if (!g.HoveredIdPreviousFrame)
3412 g.HoveredIdTimer = 0.0f;
3413 g.HoveredIdPreviousFrame = g.HoveredId;
3414 g.HoveredId = 0;
3415 g.HoveredIdAllowOverlap = false;
3416 if (!g.ActiveIdIsAlive && g.ActiveIdPreviousFrame == g.ActiveId && g.ActiveId != 0)
3417 ClearActiveID();
3418 if (g.ActiveId)
3419 g.ActiveIdTimer += g.IO.DeltaTime;
3420 g.ActiveIdPreviousFrame = g.ActiveId;
3421 g.ActiveIdIsAlive = false;
3422 g.ActiveIdIsJustActivated = false;
3423 if (g.ScalarAsInputTextId && g.ActiveId != g.ScalarAsInputTextId)
3424 g.ScalarAsInputTextId = 0;
3425
3426 // Elapse drag & drop payload
3427 if (g.DragDropActive && g.DragDropPayload.DataFrameCount + 1 < g.FrameCount)
3428 {
3429 ClearDragDrop();
3430 g.DragDropPayloadBufHeap.clear();
3431 memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal));
3432 }
3433 g.DragDropAcceptIdPrev = g.DragDropAcceptIdCurr;
3434 g.DragDropAcceptIdCurr = 0;
3435 g.DragDropAcceptIdCurrRectSurface = FLT_MAX;
3436
3437 // Update keyboard input state
3438 memcpy(g.IO.KeysDownDurationPrev, g.IO.KeysDownDuration, sizeof(g.IO.KeysDownDuration));
3439 for (int i = 0; i < IM_ARRAYSIZE(g.IO.KeysDown); i++)
3440 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;
3441
3442 // Update gamepad/keyboard directional navigation
3443 NavUpdate();
3444
3445 // Update mouse input state
3446 // 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
3447 if (IsMousePosValid(&g.IO.MousePos) && IsMousePosValid(&g.IO.MousePosPrev))
3448 g.IO.MouseDelta = g.IO.MousePos - g.IO.MousePosPrev;
3449 else
3450 g.IO.MouseDelta = ImVec2(0.0f, 0.0f);
3451 if (g.IO.MouseDelta.x != 0.0f || g.IO.MouseDelta.y != 0.0f)
3452 g.NavDisableMouseHover = false;
3453
3454 g.IO.MousePosPrev = g.IO.MousePos;
3455 for (int i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++)
3456 {
3457 g.IO.MouseClicked[i] = g.IO.MouseDown[i] && g.IO.MouseDownDuration[i] < 0.0f;
3458 g.IO.MouseReleased[i] = !g.IO.MouseDown[i] && g.IO.MouseDownDuration[i] >= 0.0f;
3459 g.IO.MouseDownDurationPrev[i] = g.IO.MouseDownDuration[i];
3460 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;
3461 g.IO.MouseDoubleClicked[i] = false;
3462 if (g.IO.MouseClicked[i])
3463 {
3464 if (g.Time - g.IO.MouseClickedTime[i] < g.IO.MouseDoubleClickTime)
3465 {
3466 if (ImLengthSqr(g.IO.MousePos - g.IO.MouseClickedPos[i]) < g.IO.MouseDoubleClickMaxDist * g.IO.MouseDoubleClickMaxDist)
3467 g.IO.MouseDoubleClicked[i] = true;
3468 g.IO.MouseClickedTime[i] = -FLT_MAX; // so the third click isn't turned into a double-click
3469 }
3470 else
3471 {
3472 g.IO.MouseClickedTime[i] = g.Time;
3473 }
3474 g.IO.MouseClickedPos[i] = g.IO.MousePos;
3475 g.IO.MouseDragMaxDistanceAbs[i] = ImVec2(0.0f, 0.0f);
3476 g.IO.MouseDragMaxDistanceSqr[i] = 0.0f;
3477 }
3478 else if (g.IO.MouseDown[i])
3479 {
3480 ImVec2 mouse_delta = g.IO.MousePos - g.IO.MouseClickedPos[i];
3481 g.IO.MouseDragMaxDistanceAbs[i].x = ImMax(g.IO.MouseDragMaxDistanceAbs[i].x, mouse_delta.x < 0.0f ? -mouse_delta.x : mouse_delta.x);
3482 g.IO.MouseDragMaxDistanceAbs[i].y = ImMax(g.IO.MouseDragMaxDistanceAbs[i].y, mouse_delta.y < 0.0f ? -mouse_delta.y : mouse_delta.y);
3483 g.IO.MouseDragMaxDistanceSqr[i] = ImMax(g.IO.MouseDragMaxDistanceSqr[i], ImLengthSqr(mouse_delta));
3484 }
3485 if (g.IO.MouseClicked[i]) // Clicking any mouse button reactivate mouse hovering which may have been deactivated by gamepad/keyboard navigation
3486 g.NavDisableMouseHover = false;
3487 }
3488
3489 // Calculate frame-rate for the user, as a purely luxurious feature
3490 g.FramerateSecPerFrameAccum += g.IO.DeltaTime - g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx];
3491 g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx] = g.IO.DeltaTime;
3492 g.FramerateSecPerFrameIdx = (g.FramerateSecPerFrameIdx + 1) % IM_ARRAYSIZE(g.FramerateSecPerFrame);
3493 g.IO.Framerate = 1.0f / (g.FramerateSecPerFrameAccum / (float)IM_ARRAYSIZE(g.FramerateSecPerFrame));
3494
3495 // Handle user moving window with mouse (at the beginning of the frame to avoid input lag or sheering)
3496 UpdateMovingWindow();
3497
3498 // Delay saving settings so we don't spam disk too much
3499 if (g.SettingsDirtyTimer > 0.0f)
3500 {
3501 g.SettingsDirtyTimer -= g.IO.DeltaTime;
3502 if (g.SettingsDirtyTimer <= 0.0f)
3503 SaveIniSettingsToDisk(g.IO.IniFilename);
3504 }
3505
3506 // Find the window we are hovering
3507 // - Child windows can extend beyond the limit of their parent so we need to derive HoveredRootWindow from HoveredWindow.
3508 // - When moving a window we can skip the search, which also conveniently bypasses the fact that window->WindowRectClipped is lagging as this point.
3509 // - 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.
3510 g.HoveredWindow = (g.MovingWindow && !(g.MovingWindow->Flags & ImGuiWindowFlags_NoInputs)) ? g.MovingWindow : FindHoveredWindow();
3511 g.HoveredRootWindow = g.HoveredWindow ? g.HoveredWindow->RootWindow : NULL;
3512
3513 ImGuiWindow* modal_window = GetFrontMostModalRootWindow();
3514 if (modal_window != NULL)
3515 {
3516 g.ModalWindowDarkeningRatio = ImMin(g.ModalWindowDarkeningRatio + g.IO.DeltaTime * 6.0f, 1.0f);
3517 if (g.HoveredRootWindow && !IsWindowChildOf(g.HoveredRootWindow, modal_window))
3518 g.HoveredRootWindow = g.HoveredWindow = NULL;
3519 }
3520 else
3521 {
3522 g.ModalWindowDarkeningRatio = 0.0f;
3523 }
3524
3525 // Update the WantCaptureMouse/WantCaptureKeyboard flags, so user can capture/discard the inputs away from the rest of their application.
3526 // When clicking outside of a window we assume the click is owned by the application and won't request capture. We need to track click ownership.
3527 int mouse_earliest_button_down = -1;
3528 bool mouse_any_down = false;
3529 for (int i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++)
3530 {
3531 if (g.IO.MouseClicked[i])
3532 g.IO.MouseDownOwned[i] = (g.HoveredWindow != NULL) || (!g.OpenPopupStack.empty());
3533 mouse_any_down |= g.IO.MouseDown[i];
3534 if (g.IO.MouseDown[i])
3535 if (mouse_earliest_button_down == -1 || g.IO.MouseClickedTime[i] < g.IO.MouseClickedTime[mouse_earliest_button_down])
3536 mouse_earliest_button_down = i;
3537 }
3538 bool mouse_avail_to_imgui = (mouse_earliest_button_down == -1) || g.IO.MouseDownOwned[mouse_earliest_button_down];
3539 if (g.WantCaptureMouseNextFrame != -1)
3540 g.IO.WantCaptureMouse = (g.WantCaptureMouseNextFrame != 0);
3541 else
3542 g.IO.WantCaptureMouse = (mouse_avail_to_imgui && (g.HoveredWindow != NULL || mouse_any_down)) || (!g.OpenPopupStack.empty());
3543
3544 if (g.WantCaptureKeyboardNextFrame != -1)
3545 g.IO.WantCaptureKeyboard = (g.WantCaptureKeyboardNextFrame != 0);
3546 else
3547 g.IO.WantCaptureKeyboard = (g.ActiveId != 0) || (modal_window != NULL);
3548 if (g.IO.NavActive && (g.IO.NavFlags & ImGuiNavFlags_EnableKeyboard) && !(g.IO.NavFlags & ImGuiNavFlags_NoCaptureKeyboard))
3549 g.IO.WantCaptureKeyboard = true;
3550
3551 g.IO.WantTextInput = (g.WantTextInputNextFrame != -1) ? (g.WantTextInputNextFrame != 0) : 0;
3552 g.MouseCursor = ImGuiMouseCursor_Arrow;
3553 g.WantCaptureMouseNextFrame = g.WantCaptureKeyboardNextFrame = g.WantTextInputNextFrame = -1;
3554 g.OsImePosRequest = ImVec2(1.0f, 1.0f); // OS Input Method Editor showing on top-left of our window by default
3555
3556 // If mouse was first clicked outside of ImGui bounds we also cancel out hovering.
3557 // FIXME: For patterns of drag and drop across OS windows, we may need to rework/remove this test (first committed 311c0ca9 on 2015/02)
3558 bool mouse_dragging_extern_payload = g.DragDropActive && (g.DragDropSourceFlags & ImGuiDragDropFlags_SourceExtern) != 0;
3559 if (!mouse_avail_to_imgui && !mouse_dragging_extern_payload)
3560 g.HoveredWindow = g.HoveredRootWindow = NULL;
3561
3562 // Mouse wheel scrolling, scale
3563 if (g.HoveredWindow && !g.HoveredWindow->Collapsed && (g.IO.MouseWheel != 0.0f || g.IO.MouseWheelH != 0.0f))
3564 {
3565 // 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).
3566 ImGuiWindow* window = g.HoveredWindow;
3567 ImGuiWindow* scroll_window = window;
3568 while ((scroll_window->Flags & ImGuiWindowFlags_ChildWindow) && (scroll_window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(scroll_window->Flags & ImGuiWindowFlags_NoScrollbar) && !(scroll_window->Flags & ImGuiWindowFlags_NoInputs) && scroll_window->ParentWindow)
3569 scroll_window = scroll_window->ParentWindow;
3570 const bool scroll_allowed = !(scroll_window->Flags & ImGuiWindowFlags_NoScrollWithMouse) && !(scroll_window->Flags & ImGuiWindowFlags_NoInputs);
3571
3572 if (g.IO.MouseWheel != 0.0f)
3573 {
3574 if (g.IO.KeyCtrl && g.IO.FontAllowUserScaling)
3575 {
3576 // Zoom / Scale window
3577 const float new_font_scale = ImClamp(window->FontWindowScale + g.IO.MouseWheel * 0.10f, 0.50f, 2.50f);
3578 const float scale = new_font_scale / window->FontWindowScale;
3579 window->FontWindowScale = new_font_scale;
3580
3581 const ImVec2 offset = window->Size * (1.0f - scale) * (g.IO.MousePos - window->Pos) / window->Size;
3582 window->Pos += offset;
3583 window->PosFloat += offset;
3584 window->Size *= scale;
3585 window->SizeFull *= scale;
3586 }
3587 else if (!g.IO.KeyCtrl && scroll_allowed)
3588 {
3589 // Mouse wheel vertical scrolling
3590 float scroll_amount = 5 * scroll_window->CalcFontSize();
3591 scroll_amount = (float)(int)ImMin(scroll_amount, (scroll_window->ContentsRegionRect.GetHeight() + scroll_window->WindowPadding.y * 2.0f) * 0.67f);
3592 SetWindowScrollY(scroll_window, scroll_window->Scroll.y - g.IO.MouseWheel * scroll_amount);
3593 }
3594 }
3595 if (g.IO.MouseWheelH != 0.0f && scroll_allowed)
3596 {
3597 // Mouse wheel horizontal scrolling (for hardware that supports it)
3598 float scroll_amount = scroll_window->CalcFontSize();
3599 if (!g.IO.KeyCtrl && !(window->Flags & ImGuiWindowFlags_NoScrollWithMouse))
3600 SetWindowScrollX(window, window->Scroll.x - g.IO.MouseWheelH * scroll_amount);
3601 }
3602 }
3603
3604 // Pressing TAB activate widget focus
3605 if (g.ActiveId == 0 && g.NavWindow != NULL && g.NavWindow->Active && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs) && !g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_Tab, false))
3606 {
3607 if (g.NavId != 0 && g.NavIdTabCounter != INT_MAX)
3608 g.NavWindow->FocusIdxTabRequestNext = g.NavIdTabCounter + 1 + (g.IO.KeyShift ? -1 : 1);
3609 else
3610 g.NavWindow->FocusIdxTabRequestNext = g.IO.KeyShift ? -1 : 0;
3611 }
3612 g.NavIdTabCounter = INT_MAX;
3613
3614 // Mark all windows as not visible
3615 for (int i = 0; i != g.Windows.Size; i++)
3616 {
3617 ImGuiWindow* window = g.Windows[i];
3618 window->WasActive = window->Active;
3619 window->Active = false;
3620 window->WriteAccessed = false;
3621 }
3622
3623 // Closing the focused window restore focus to the first active root window in descending z-order
3624 if (g.NavWindow && !g.NavWindow->WasActive)
3625 FocusFrontMostActiveWindow(NULL);
3626
3627 // No window should be open at the beginning of the frame.
3628 // But in order to allow the user to call NewFrame() multiple times without calling Render(), we are doing an explicit clear.
3629 g.CurrentWindowStack.resize(0);
3630 g.CurrentPopupStack.resize(0);
3631 ClosePopupsOverWindow(g.NavWindow);
3632
3633 // Create implicit window - we will only render it if the user has added something to it.
3634 // We don't use "Debug" to avoid colliding with user trying to create a "Debug" window with custom flags.
3635 SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver);
3636 Begin("Debug##Default");
3637 }
3638
SettingsHandlerWindow_ReadOpen(ImGuiContext *,ImGuiSettingsHandler *,const char * name)3639 static void* SettingsHandlerWindow_ReadOpen(ImGuiContext*, ImGuiSettingsHandler*, const char* name)
3640 {
3641 ImGuiWindowSettings* settings = ImGui::FindWindowSettings(ImHash(name, 0));
3642 if (!settings)
3643 settings = AddWindowSettings(name);
3644 return (void*)settings;
3645 }
3646
SettingsHandlerWindow_ReadLine(ImGuiContext *,ImGuiSettingsHandler *,void * entry,const char * line)3647 static void SettingsHandlerWindow_ReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line)
3648 {
3649 ImGuiWindowSettings* settings = (ImGuiWindowSettings*)entry;
3650 float x, y;
3651 int i;
3652 if (sscanf(line, "Pos=%f,%f", &x, &y) == 2)
3653 settings->Pos = ImVec2(x, y);
3654 else if (sscanf(line, "Size=%f,%f", &x, &y) == 2)
3655 settings->Size = ImMax(ImVec2(x, y), GImGui->Style.WindowMinSize);
3656 else if (sscanf(line, "Collapsed=%d", &i) == 1)
3657 settings->Collapsed = (i != 0);
3658 }
3659
SettingsHandlerWindow_WriteAll(ImGuiContext * imgui_ctx,ImGuiSettingsHandler * handler,ImGuiTextBuffer * buf)3660 static void SettingsHandlerWindow_WriteAll(ImGuiContext* imgui_ctx, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf)
3661 {
3662 // Gather data from windows that were active during this session
3663 ImGuiContext& g = *imgui_ctx;
3664 for (int i = 0; i != g.Windows.Size; i++)
3665 {
3666 ImGuiWindow* window = g.Windows[i];
3667 if (window->Flags & ImGuiWindowFlags_NoSavedSettings)
3668 continue;
3669 ImGuiWindowSettings* settings = ImGui::FindWindowSettings(window->ID);
3670 if (!settings)
3671 settings = AddWindowSettings(window->Name);
3672 settings->Pos = window->Pos;
3673 settings->Size = window->SizeFull;
3674 settings->Collapsed = window->Collapsed;
3675 }
3676
3677 // Write a buffer
3678 // If a window wasn't opened in this session we preserve its settings
3679 buf->reserve(buf->size() + g.SettingsWindows.Size * 96); // ballpark reserve
3680 for (int i = 0; i != g.SettingsWindows.Size; i++)
3681 {
3682 const ImGuiWindowSettings* settings = &g.SettingsWindows[i];
3683 if (settings->Pos.x == FLT_MAX)
3684 continue;
3685 const char* name = settings->Name;
3686 if (const char* p = strstr(name, "###")) // Skip to the "###" marker if any. We don't skip past to match the behavior of GetID()
3687 name = p;
3688 buf->appendf("[%s][%s]\n", handler->TypeName, name);
3689 buf->appendf("Pos=%d,%d\n", (int)settings->Pos.x, (int)settings->Pos.y);
3690 buf->appendf("Size=%d,%d\n", (int)settings->Size.x, (int)settings->Size.y);
3691 buf->appendf("Collapsed=%d\n", settings->Collapsed);
3692 buf->appendf("\n");
3693 }
3694 }
3695
Initialize(ImGuiContext * context)3696 void ImGui::Initialize(ImGuiContext* context)
3697 {
3698 ImGuiContext& g = *context;
3699 IM_ASSERT(!g.Initialized && !g.SettingsLoaded);
3700 g.LogClipboard = IM_NEW(ImGuiTextBuffer)();
3701
3702 // Add .ini handle for ImGuiWindow type
3703 ImGuiSettingsHandler ini_handler;
3704 ini_handler.TypeName = "Window";
3705 ini_handler.TypeHash = ImHash("Window", 0, 0);
3706 ini_handler.ReadOpenFn = SettingsHandlerWindow_ReadOpen;
3707 ini_handler.ReadLineFn = SettingsHandlerWindow_ReadLine;
3708 ini_handler.WriteAllFn = SettingsHandlerWindow_WriteAll;
3709 g.SettingsHandlers.push_front(ini_handler);
3710
3711 g.Initialized = true;
3712 }
3713
3714 // This function is merely here to free heap allocations.
Shutdown(ImGuiContext * context)3715 void ImGui::Shutdown(ImGuiContext* context)
3716 {
3717 ImGuiContext& g = *context;
3718
3719 // 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)
3720 if (g.IO.Fonts && g.FontAtlasOwnedByContext)
3721 IM_DELETE(g.IO.Fonts);
3722
3723 // Cleanup of other data are conditional on actually having initialize ImGui.
3724 if (!g.Initialized)
3725 return;
3726
3727 SaveIniSettingsToDisk(g.IO.IniFilename);
3728
3729 // Clear everything else
3730 for (int i = 0; i < g.Windows.Size; i++)
3731 IM_DELETE(g.Windows[i]);
3732 g.Windows.clear();
3733 g.WindowsSortBuffer.clear();
3734 g.CurrentWindow = NULL;
3735 g.CurrentWindowStack.clear();
3736 g.WindowsById.Clear();
3737 g.NavWindow = NULL;
3738 g.HoveredWindow = NULL;
3739 g.HoveredRootWindow = NULL;
3740 g.ActiveIdWindow = NULL;
3741 g.MovingWindow = NULL;
3742 for (int i = 0; i < g.SettingsWindows.Size; i++)
3743 IM_DELETE(g.SettingsWindows[i].Name);
3744 g.ColorModifiers.clear();
3745 g.StyleModifiers.clear();
3746 g.FontStack.clear();
3747 g.OpenPopupStack.clear();
3748 g.CurrentPopupStack.clear();
3749 g.DrawDataBuilder.ClearFreeMemory();
3750 g.OverlayDrawList.ClearFreeMemory();
3751 g.PrivateClipboard.clear();
3752 g.InputTextState.Text.clear();
3753 g.InputTextState.InitialText.clear();
3754 g.InputTextState.TempTextBuffer.clear();
3755
3756 g.SettingsWindows.clear();
3757 g.SettingsHandlers.clear();
3758
3759 if (g.LogFile && g.LogFile != stdout)
3760 {
3761 fclose(g.LogFile);
3762 g.LogFile = NULL;
3763 }
3764 if (g.LogClipboard)
3765 IM_DELETE(g.LogClipboard);
3766
3767 g.Initialized = false;
3768 }
3769
FindWindowSettings(ImGuiID id)3770 ImGuiWindowSettings* ImGui::FindWindowSettings(ImGuiID id)
3771 {
3772 ImGuiContext& g = *GImGui;
3773 for (int i = 0; i != g.SettingsWindows.Size; i++)
3774 if (g.SettingsWindows[i].Id == id)
3775 return &g.SettingsWindows[i];
3776 return NULL;
3777 }
3778
AddWindowSettings(const char * name)3779 static ImGuiWindowSettings* AddWindowSettings(const char* name)
3780 {
3781 ImGuiContext& g = *GImGui;
3782 g.SettingsWindows.push_back(ImGuiWindowSettings());
3783 ImGuiWindowSettings* settings = &g.SettingsWindows.back();
3784 settings->Name = ImStrdup(name);
3785 settings->Id = ImHash(name, 0);
3786 return settings;
3787 }
3788
LoadIniSettingsFromDisk(const char * ini_filename)3789 static void LoadIniSettingsFromDisk(const char* ini_filename)
3790 {
3791 if (!ini_filename)
3792 return;
3793 char* file_data = (char*)ImFileLoadToMemory(ini_filename, "rb", NULL, +1);
3794 if (!file_data)
3795 return;
3796 LoadIniSettingsFromMemory(file_data);
3797 ImGui::MemFree(file_data);
3798 }
3799
FindSettingsHandler(const char * type_name)3800 ImGuiSettingsHandler* ImGui::FindSettingsHandler(const char* type_name)
3801 {
3802 ImGuiContext& g = *GImGui;
3803 const ImGuiID type_hash = ImHash(type_name, 0, 0);
3804 for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++)
3805 if (g.SettingsHandlers[handler_n].TypeHash == type_hash)
3806 return &g.SettingsHandlers[handler_n];
3807 return NULL;
3808 }
3809
3810 // Zero-tolerance, no error reporting, cheap .ini parsing
LoadIniSettingsFromMemory(const char * buf_readonly)3811 static void LoadIniSettingsFromMemory(const char* buf_readonly)
3812 {
3813 // For convenience and to make the code simpler, we'll write zero terminators inside the buffer. So let's create a writable copy.
3814 char* buf = ImStrdup(buf_readonly);
3815 char* buf_end = buf + strlen(buf);
3816
3817 ImGuiContext& g = *GImGui;
3818 void* entry_data = NULL;
3819 ImGuiSettingsHandler* entry_handler = NULL;
3820
3821 char* line_end = NULL;
3822 for (char* line = buf; line < buf_end; line = line_end + 1)
3823 {
3824 // Skip new lines markers, then find end of the line
3825 while (*line == '\n' || *line == '\r')
3826 line++;
3827 line_end = line;
3828 while (line_end < buf_end && *line_end != '\n' && *line_end != '\r')
3829 line_end++;
3830 line_end[0] = 0;
3831
3832 if (line[0] == '[' && line_end > line && line_end[-1] == ']')
3833 {
3834 // Parse "[Type][Name]". Note that 'Name' can itself contains [] characters, which is acceptable with the current format and parsing code.
3835 line_end[-1] = 0;
3836 const char* name_end = line_end - 1;
3837 const char* type_start = line + 1;
3838 char* type_end = ImStrchrRange(type_start, name_end, ']');
3839 const char* name_start = type_end ? ImStrchrRange(type_end + 1, name_end, '[') : NULL;
3840 if (!type_end || !name_start)
3841 {
3842 name_start = type_start; // Import legacy entries that have no type
3843 type_start = "Window";
3844 }
3845 else
3846 {
3847 *type_end = 0; // Overwrite first ']'
3848 name_start++; // Skip second '['
3849 }
3850 entry_handler = ImGui::FindSettingsHandler(type_start);
3851 entry_data = entry_handler ? entry_handler->ReadOpenFn(&g, entry_handler, name_start) : NULL;
3852 }
3853 else if (entry_handler != NULL && entry_data != NULL)
3854 {
3855 // Let type handler parse the line
3856 entry_handler->ReadLineFn(&g, entry_handler, entry_data, line);
3857 }
3858 }
3859 ImGui::MemFree(buf);
3860 g.SettingsLoaded = true;
3861 }
3862
SaveIniSettingsToDisk(const char * ini_filename)3863 static void SaveIniSettingsToDisk(const char* ini_filename)
3864 {
3865 ImGuiContext& g = *GImGui;
3866 g.SettingsDirtyTimer = 0.0f;
3867 if (!ini_filename)
3868 return;
3869
3870 ImVector<char> buf;
3871 SaveIniSettingsToMemory(buf);
3872
3873 FILE* f = ImFileOpen(ini_filename, "wt");
3874 if (!f)
3875 return;
3876 fwrite(buf.Data, sizeof(char), (size_t)buf.Size, f);
3877 fclose(f);
3878 }
3879
SaveIniSettingsToMemory(ImVector<char> & out_buf)3880 static void SaveIniSettingsToMemory(ImVector<char>& out_buf)
3881 {
3882 ImGuiContext& g = *GImGui;
3883 g.SettingsDirtyTimer = 0.0f;
3884
3885 ImGuiTextBuffer buf;
3886 for (int handler_n = 0; handler_n < g.SettingsHandlers.Size; handler_n++)
3887 {
3888 ImGuiSettingsHandler* handler = &g.SettingsHandlers[handler_n];
3889 handler->WriteAllFn(&g, handler, &buf);
3890 }
3891
3892 buf.Buf.pop_back(); // Remove extra zero-terminator used by ImGuiTextBuffer
3893 out_buf.swap(buf.Buf);
3894 }
3895
MarkIniSettingsDirty()3896 void ImGui::MarkIniSettingsDirty()
3897 {
3898 ImGuiContext& g = *GImGui;
3899 if (g.SettingsDirtyTimer <= 0.0f)
3900 g.SettingsDirtyTimer = g.IO.IniSavingRate;
3901 }
3902
MarkIniSettingsDirty(ImGuiWindow * window)3903 static void MarkIniSettingsDirty(ImGuiWindow* window)
3904 {
3905 ImGuiContext& g = *GImGui;
3906 if (!(window->Flags & ImGuiWindowFlags_NoSavedSettings))
3907 if (g.SettingsDirtyTimer <= 0.0f)
3908 g.SettingsDirtyTimer = g.IO.IniSavingRate;
3909 }
3910
3911 // FIXME: Add a more explicit sort order in the window structure.
ChildWindowComparer(const void * lhs,const void * rhs)3912 static int IMGUI_CDECL ChildWindowComparer(const void* lhs, const void* rhs)
3913 {
3914 const ImGuiWindow* a = *(const ImGuiWindow**)lhs;
3915 const ImGuiWindow* b = *(const ImGuiWindow**)rhs;
3916 if (int d = (a->Flags & ImGuiWindowFlags_Popup) - (b->Flags & ImGuiWindowFlags_Popup))
3917 return d;
3918 if (int d = (a->Flags & ImGuiWindowFlags_Tooltip) - (b->Flags & ImGuiWindowFlags_Tooltip))
3919 return d;
3920 return (a->BeginOrderWithinParent - b->BeginOrderWithinParent);
3921 }
3922
AddWindowToSortedBuffer(ImVector<ImGuiWindow * > * out_sorted_windows,ImGuiWindow * window)3923 static void AddWindowToSortedBuffer(ImVector<ImGuiWindow*>* out_sorted_windows, ImGuiWindow* window)
3924 {
3925 out_sorted_windows->push_back(window);
3926 if (window->Active)
3927 {
3928 int count = window->DC.ChildWindows.Size;
3929 if (count > 1)
3930 qsort(window->DC.ChildWindows.begin(), (size_t)count, sizeof(ImGuiWindow*), ChildWindowComparer);
3931 for (int i = 0; i < count; i++)
3932 {
3933 ImGuiWindow* child = window->DC.ChildWindows[i];
3934 if (child->Active)
3935 AddWindowToSortedBuffer(out_sorted_windows, child);
3936 }
3937 }
3938 }
3939
AddDrawListToDrawData(ImVector<ImDrawList * > * out_render_list,ImDrawList * draw_list)3940 static void AddDrawListToDrawData(ImVector<ImDrawList*>* out_render_list, ImDrawList* draw_list)
3941 {
3942 if (draw_list->CmdBuffer.empty())
3943 return;
3944
3945 // Remove trailing command if unused
3946 ImDrawCmd& last_cmd = draw_list->CmdBuffer.back();
3947 if (last_cmd.ElemCount == 0 && last_cmd.UserCallback == NULL)
3948 {
3949 draw_list->CmdBuffer.pop_back();
3950 if (draw_list->CmdBuffer.empty())
3951 return;
3952 }
3953
3954 // 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.
3955 IM_ASSERT(draw_list->VtxBuffer.Size == 0 || draw_list->_VtxWritePtr == draw_list->VtxBuffer.Data + draw_list->VtxBuffer.Size);
3956 IM_ASSERT(draw_list->IdxBuffer.Size == 0 || draw_list->_IdxWritePtr == draw_list->IdxBuffer.Data + draw_list->IdxBuffer.Size);
3957 IM_ASSERT((int)draw_list->_VtxCurrentIdx == draw_list->VtxBuffer.Size);
3958
3959 // Check that draw_list doesn't use more vertices than indexable (default ImDrawIdx = unsigned short = 2 bytes = 64K vertices per ImDrawList = per window)
3960 // If this assert triggers because you are drawing lots of stuff manually:
3961 // 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.
3962 // 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.
3963 // 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:
3964 // glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer_offset);
3965 // 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.
3966 // 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.
3967 if (sizeof(ImDrawIdx) == 2)
3968 IM_ASSERT(draw_list->_VtxCurrentIdx < (1 << 16) && "Too many vertices in ImDrawList using 16-bit indices. Read comment above");
3969
3970 out_render_list->push_back(draw_list);
3971 }
3972
AddWindowToDrawData(ImVector<ImDrawList * > * out_render_list,ImGuiWindow * window)3973 static void AddWindowToDrawData(ImVector<ImDrawList*>* out_render_list, ImGuiWindow* window)
3974 {
3975 AddDrawListToDrawData(out_render_list, window->DrawList);
3976 for (int i = 0; i < window->DC.ChildWindows.Size; i++)
3977 {
3978 ImGuiWindow* child = window->DC.ChildWindows[i];
3979 if (child->Active && child->HiddenFrames <= 0) // clipped children may have been marked not active
3980 AddWindowToDrawData(out_render_list, child);
3981 }
3982 }
3983
AddWindowToDrawDataSelectLayer(ImGuiWindow * window)3984 static void AddWindowToDrawDataSelectLayer(ImGuiWindow* window)
3985 {
3986 ImGuiContext& g = *GImGui;
3987 g.IO.MetricsActiveWindows++;
3988 if (window->Flags & ImGuiWindowFlags_Tooltip)
3989 AddWindowToDrawData(&g.DrawDataBuilder.Layers[1], window);
3990 else
3991 AddWindowToDrawData(&g.DrawDataBuilder.Layers[0], window);
3992 }
3993
FlattenIntoSingleLayer()3994 void ImDrawDataBuilder::FlattenIntoSingleLayer()
3995 {
3996 int n = Layers[0].Size;
3997 int size = n;
3998 for (int i = 1; i < IM_ARRAYSIZE(Layers); i++)
3999 size += Layers[i].Size;
4000 Layers[0].resize(size);
4001 for (int layer_n = 1; layer_n < IM_ARRAYSIZE(Layers); layer_n++)
4002 {
4003 ImVector<ImDrawList*>& layer = Layers[layer_n];
4004 if (layer.empty())
4005 continue;
4006 memcpy(&Layers[0][n], &layer[0], layer.Size * sizeof(ImDrawList*));
4007 n += layer.Size;
4008 layer.resize(0);
4009 }
4010 }
4011
SetupDrawData(ImVector<ImDrawList * > * draw_lists,ImDrawData * out_draw_data)4012 static void SetupDrawData(ImVector<ImDrawList*>* draw_lists, ImDrawData* out_draw_data)
4013 {
4014 out_draw_data->Valid = true;
4015 out_draw_data->CmdLists = (draw_lists->Size > 0) ? draw_lists->Data : NULL;
4016 out_draw_data->CmdListsCount = draw_lists->Size;
4017 out_draw_data->TotalVtxCount = out_draw_data->TotalIdxCount = 0;
4018 for (int n = 0; n < draw_lists->Size; n++)
4019 {
4020 out_draw_data->TotalVtxCount += draw_lists->Data[n]->VtxBuffer.Size;
4021 out_draw_data->TotalIdxCount += draw_lists->Data[n]->IdxBuffer.Size;
4022 }
4023 }
4024
4025 // 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.
PushClipRect(const ImVec2 & clip_rect_min,const ImVec2 & clip_rect_max,bool intersect_with_current_clip_rect)4026 void ImGui::PushClipRect(const ImVec2& clip_rect_min, const ImVec2& clip_rect_max, bool intersect_with_current_clip_rect)
4027 {
4028 ImGuiWindow* window = GetCurrentWindow();
4029 window->DrawList->PushClipRect(clip_rect_min, clip_rect_max, intersect_with_current_clip_rect);
4030 window->ClipRect = window->DrawList->_ClipRectStack.back();
4031 }
4032
PopClipRect()4033 void ImGui::PopClipRect()
4034 {
4035 ImGuiWindow* window = GetCurrentWindow();
4036 window->DrawList->PopClipRect();
4037 window->ClipRect = window->DrawList->_ClipRectStack.back();
4038 }
4039
4040 // This is normally called by Render(). You may want to call it directly if you want to avoid calling Render() but the gain will be very minimal.
EndFrame()4041 void ImGui::EndFrame()
4042 {
4043 ImGuiContext& g = *GImGui;
4044 IM_ASSERT(g.Initialized); // Forgot to call ImGui::NewFrame()
4045 if (g.FrameCountEnded == g.FrameCount) // Don't process EndFrame() multiple times.
4046 return;
4047
4048 // Notify OS when our Input Method Editor cursor has moved (e.g. CJK inputs using Microsoft IME)
4049 if (g.IO.ImeSetInputScreenPosFn && ImLengthSqr(g.OsImePosRequest - g.OsImePosSet) > 0.0001f)
4050 {
4051 g.IO.ImeSetInputScreenPosFn((int)g.OsImePosRequest.x, (int)g.OsImePosRequest.y);
4052 g.OsImePosSet = g.OsImePosRequest;
4053 }
4054
4055 // Hide implicit "Debug" window if it hasn't been used
4056 IM_ASSERT(g.CurrentWindowStack.Size == 1); // Mismatched Begin()/End() calls
4057 if (g.CurrentWindow && !g.CurrentWindow->WriteAccessed)
4058 g.CurrentWindow->Active = false;
4059 End();
4060
4061 if (g.ActiveId == 0 && g.HoveredId == 0)
4062 {
4063 if (!g.NavWindow || !g.NavWindow->Appearing) // Unless we just made a window/popup appear
4064 {
4065 // Click to focus window and start moving (after we're done with all our widgets)
4066 if (g.IO.MouseClicked[0])
4067 {
4068 if (g.HoveredRootWindow != NULL)
4069 {
4070 // Set ActiveId even if the _NoMove flag is set, without it dragging away from a window with _NoMove would activate hover on other windows.
4071 FocusWindow(g.HoveredWindow);
4072 SetActiveID(g.HoveredWindow->MoveId, g.HoveredWindow);
4073 g.NavDisableHighlight = true;
4074 g.ActiveIdClickOffset = g.IO.MousePos - g.HoveredRootWindow->Pos;
4075 if (!(g.HoveredWindow->Flags & ImGuiWindowFlags_NoMove) && !(g.HoveredRootWindow->Flags & ImGuiWindowFlags_NoMove))
4076 g.MovingWindow = g.HoveredWindow;
4077 }
4078 else if (g.NavWindow != NULL && GetFrontMostModalRootWindow() == NULL)
4079 {
4080 // Clicking on void disable focus
4081 FocusWindow(NULL);
4082 }
4083 }
4084
4085 // With right mouse button we close popups without changing focus
4086 // (The left mouse button path calls FocusWindow which will lead NewFrame->ClosePopupsOverWindow to trigger)
4087 if (g.IO.MouseClicked[1])
4088 {
4089 // Find the top-most window between HoveredWindow and the front most Modal Window.
4090 // This is where we can trim the popup stack.
4091 ImGuiWindow* modal = GetFrontMostModalRootWindow();
4092 bool hovered_window_above_modal = false;
4093 if (modal == NULL)
4094 hovered_window_above_modal = true;
4095 for (int i = g.Windows.Size - 1; i >= 0 && hovered_window_above_modal == false; i--)
4096 {
4097 ImGuiWindow* window = g.Windows[i];
4098 if (window == modal)
4099 break;
4100 if (window == g.HoveredWindow)
4101 hovered_window_above_modal = true;
4102 }
4103 ClosePopupsOverWindow(hovered_window_above_modal ? g.HoveredWindow : modal);
4104 }
4105 }
4106 }
4107
4108 // Sort the window list so that all child windows are after their parent
4109 // We cannot do that on FocusWindow() because childs may not exist yet
4110 g.WindowsSortBuffer.resize(0);
4111 g.WindowsSortBuffer.reserve(g.Windows.Size);
4112 for (int i = 0; i != g.Windows.Size; i++)
4113 {
4114 ImGuiWindow* window = g.Windows[i];
4115 if (window->Active && (window->Flags & ImGuiWindowFlags_ChildWindow)) // if a child is active its parent will add it
4116 continue;
4117 AddWindowToSortedBuffer(&g.WindowsSortBuffer, window);
4118 }
4119
4120 IM_ASSERT(g.Windows.Size == g.WindowsSortBuffer.Size); // we done something wrong
4121 g.Windows.swap(g.WindowsSortBuffer);
4122
4123 // Clear Input data for next frame
4124 g.IO.MouseWheel = g.IO.MouseWheelH = 0.0f;
4125 memset(g.IO.InputCharacters, 0, sizeof(g.IO.InputCharacters));
4126 memset(g.IO.NavInputs, 0, sizeof(g.IO.NavInputs));
4127
4128 g.FrameCountEnded = g.FrameCount;
4129 }
4130
Render()4131 void ImGui::Render()
4132 {
4133 ImGuiContext& g = *GImGui;
4134 IM_ASSERT(g.Initialized); // Forgot to call ImGui::NewFrame()
4135
4136 if (g.FrameCountEnded != g.FrameCount)
4137 ImGui::EndFrame();
4138 g.FrameCountRendered = g.FrameCount;
4139
4140 // Skip render altogether if alpha is 0.0
4141 // Note that vertex buffers have been created and are wasted, so it is best practice that you don't create windows in the first place, or consistently respond to Begin() returning false.
4142 if (g.Style.Alpha > 0.0f)
4143 {
4144 // Gather windows to render
4145 g.IO.MetricsRenderVertices = g.IO.MetricsRenderIndices = g.IO.MetricsActiveWindows = 0;
4146 g.DrawDataBuilder.Clear();
4147 ImGuiWindow* window_to_render_front_most = (g.NavWindowingTarget && !(g.NavWindowingTarget->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus)) ? g.NavWindowingTarget : NULL;
4148 for (int n = 0; n != g.Windows.Size; n++)
4149 {
4150 ImGuiWindow* window = g.Windows[n];
4151 if (window->Active && window->HiddenFrames <= 0 && (window->Flags & (ImGuiWindowFlags_ChildWindow)) == 0 && window != window_to_render_front_most)
4152 AddWindowToDrawDataSelectLayer(window);
4153 }
4154 if (window_to_render_front_most && window_to_render_front_most->Active && window_to_render_front_most->HiddenFrames <= 0) // NavWindowingTarget is always temporarily displayed as the front-most window
4155 AddWindowToDrawDataSelectLayer(window_to_render_front_most);
4156 g.DrawDataBuilder.FlattenIntoSingleLayer();
4157
4158 // Draw software mouse cursor if requested
4159 ImVec2 offset, size, uv[4];
4160 if (g.IO.MouseDrawCursor && g.IO.Fonts->GetMouseCursorTexData(g.MouseCursor, &offset, &size, &uv[0], &uv[2]))
4161 {
4162 const ImVec2 pos = g.IO.MousePos - offset;
4163 const ImTextureID tex_id = g.IO.Fonts->TexID;
4164 const float sc = g.Style.MouseCursorScale;
4165 g.OverlayDrawList.PushTextureID(tex_id);
4166 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
4167 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
4168 g.OverlayDrawList.AddImage(tex_id, pos, pos + size * sc, uv[2], uv[3], IM_COL32(0, 0, 0, 255)); // Black border
4169 g.OverlayDrawList.AddImage(tex_id, pos, pos + size * sc, uv[0], uv[1], IM_COL32(255, 255, 255, 255)); // White fill
4170 g.OverlayDrawList.PopTextureID();
4171 }
4172 if (!g.OverlayDrawList.VtxBuffer.empty())
4173 AddDrawListToDrawData(&g.DrawDataBuilder.Layers[0], &g.OverlayDrawList);
4174
4175 // Setup ImDrawData structure for end-user
4176 SetupDrawData(&g.DrawDataBuilder.Layers[0], &g.DrawData);
4177 g.IO.MetricsRenderVertices = g.DrawData.TotalVtxCount;
4178 g.IO.MetricsRenderIndices = g.DrawData.TotalIdxCount;
4179
4180 // Render. If user hasn't set a callback then they may retrieve the draw data via GetDrawData()
4181 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
4182 if (g.DrawData.CmdListsCount > 0 && g.IO.RenderDrawListsFn != NULL)
4183 g.IO.RenderDrawListsFn(&g.DrawData);
4184 #endif
4185 }
4186 }
4187
FindRenderedTextEnd(const char * text,const char * text_end)4188 const char* ImGui::FindRenderedTextEnd(const char* text, const char* text_end)
4189 {
4190 const char* text_display_end = text;
4191 if (!text_end)
4192 text_end = (const char*)-1;
4193
4194 while (text_display_end < text_end && *text_display_end != '\0' && (text_display_end[0] != '#' || text_display_end[1] != '#'))
4195 text_display_end++;
4196 return text_display_end;
4197 }
4198
4199 // Pass text data straight to log (without being displayed)
LogText(const char * fmt,...)4200 void ImGui::LogText(const char* fmt, ...)
4201 {
4202 ImGuiContext& g = *GImGui;
4203 if (!g.LogEnabled)
4204 return;
4205
4206 va_list args;
4207 va_start(args, fmt);
4208 if (g.LogFile)
4209 {
4210 vfprintf(g.LogFile, fmt, args);
4211 }
4212 else
4213 {
4214 g.LogClipboard->appendfv(fmt, args);
4215 }
4216 va_end(args);
4217 }
4218
4219 // Internal version that takes a position to decide on newline placement and pad items according to their depth.
4220 // We split text into individual lines to add current tree level padding
LogRenderedText(const ImVec2 * ref_pos,const char * text,const char * text_end=NULL)4221 static void LogRenderedText(const ImVec2* ref_pos, const char* text, const char* text_end = NULL)
4222 {
4223 ImGuiContext& g = *GImGui;
4224 ImGuiWindow* window = g.CurrentWindow;
4225
4226 if (!text_end)
4227 text_end = ImGui::FindRenderedTextEnd(text, text_end);
4228
4229 const bool log_new_line = ref_pos && (ref_pos->y > window->DC.LogLinePosY + 1);
4230 if (ref_pos)
4231 window->DC.LogLinePosY = ref_pos->y;
4232
4233 const char* text_remaining = text;
4234 if (g.LogStartDepth > window->DC.TreeDepth) // Re-adjust padding if we have popped out of our starting depth
4235 g.LogStartDepth = window->DC.TreeDepth;
4236 const int tree_depth = (window->DC.TreeDepth - g.LogStartDepth);
4237 for (;;)
4238 {
4239 // Split the string. Each new line (after a '\n') is followed by spacing corresponding to the current depth of our log entry.
4240 const char* line_end = text_remaining;
4241 while (line_end < text_end)
4242 if (*line_end == '\n')
4243 break;
4244 else
4245 line_end++;
4246 if (line_end >= text_end)
4247 line_end = NULL;
4248
4249 const bool is_first_line = (text == text_remaining);
4250 bool is_last_line = false;
4251 if (line_end == NULL)
4252 {
4253 is_last_line = true;
4254 line_end = text_end;
4255 }
4256 if (line_end != NULL && !(is_last_line && (line_end - text_remaining) == 0))
4257 {
4258 const int char_count = (int)(line_end - text_remaining);
4259 if (log_new_line || !is_first_line)
4260 ImGui::LogText(IM_NEWLINE "%*s%.*s", tree_depth * 4, "", char_count, text_remaining);
4261 else
4262 ImGui::LogText(" %.*s", char_count, text_remaining);
4263 }
4264
4265 if (is_last_line)
4266 break;
4267 text_remaining = line_end + 1;
4268 }
4269 }
4270
4271 // Internal ImGui functions to render text
4272 // RenderText***() functions calls ImDrawList::AddText() calls ImBitmapFont::RenderText()
RenderText(ImVec2 pos,const char * text,const char * text_end,bool hide_text_after_hash)4273 void ImGui::RenderText(ImVec2 pos, const char* text, const char* text_end, bool hide_text_after_hash)
4274 {
4275 ImGuiContext& g = *GImGui;
4276 ImGuiWindow* window = g.CurrentWindow;
4277
4278 // Hide anything after a '##' string
4279 const char* text_display_end;
4280 if (hide_text_after_hash)
4281 {
4282 text_display_end = FindRenderedTextEnd(text, text_end);
4283 }
4284 else
4285 {
4286 if (!text_end)
4287 text_end = text + strlen(text); // FIXME-OPT
4288 text_display_end = text_end;
4289 }
4290
4291 const int text_len = (int)(text_display_end - text);
4292 if (text_len > 0)
4293 {
4294 window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end);
4295 if (g.LogEnabled)
4296 LogRenderedText(&pos, text, text_display_end);
4297 }
4298 }
4299
RenderTextWrapped(ImVec2 pos,const char * text,const char * text_end,float wrap_width)4300 void ImGui::RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width)
4301 {
4302 ImGuiContext& g = *GImGui;
4303 ImGuiWindow* window = g.CurrentWindow;
4304
4305 if (!text_end)
4306 text_end = text + strlen(text); // FIXME-OPT
4307
4308 const int text_len = (int)(text_end - text);
4309 if (text_len > 0)
4310 {
4311 window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_end, wrap_width);
4312 if (g.LogEnabled)
4313 LogRenderedText(&pos, text, text_end);
4314 }
4315 }
4316
4317 // Default clip_rect uses (pos_min,pos_max)
4318 // Handle clipping on CPU immediately (vs typically let the GPU clip the triangles that are overlapping the clipping rectangle edges)
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)4319 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)
4320 {
4321 // Hide anything after a '##' string
4322 const char* text_display_end = FindRenderedTextEnd(text, text_end);
4323 const int text_len = (int)(text_display_end - text);
4324 if (text_len == 0)
4325 return;
4326
4327 ImGuiContext& g = *GImGui;
4328 ImGuiWindow* window = g.CurrentWindow;
4329
4330 // Perform CPU side clipping for single clipped element to avoid using scissor state
4331 ImVec2 pos = pos_min;
4332 const ImVec2 text_size = text_size_if_known ? *text_size_if_known : CalcTextSize(text, text_display_end, false, 0.0f);
4333
4334 const ImVec2* clip_min = clip_rect ? &clip_rect->Min : &pos_min;
4335 const ImVec2* clip_max = clip_rect ? &clip_rect->Max : &pos_max;
4336 bool need_clipping = (pos.x + text_size.x >= clip_max->x) || (pos.y + text_size.y >= clip_max->y);
4337 if (clip_rect) // If we had no explicit clipping rectangle then pos==clip_min
4338 need_clipping |= (pos.x < clip_min->x) || (pos.y < clip_min->y);
4339
4340 // Align whole block. We should defer that to the better rendering function when we'll have support for individual line alignment.
4341 if (align.x > 0.0f) pos.x = ImMax(pos.x, pos.x + (pos_max.x - pos.x - text_size.x) * align.x);
4342 if (align.y > 0.0f) pos.y = ImMax(pos.y, pos.y + (pos_max.y - pos.y - text_size.y) * align.y);
4343
4344 // Render
4345 if (need_clipping)
4346 {
4347 ImVec4 fine_clip_rect(clip_min->x, clip_min->y, clip_max->x, clip_max->y);
4348 window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, &fine_clip_rect);
4349 }
4350 else
4351 {
4352 window->DrawList->AddText(g.Font, g.FontSize, pos, GetColorU32(ImGuiCol_Text), text, text_display_end, 0.0f, NULL);
4353 }
4354 if (g.LogEnabled)
4355 LogRenderedText(&pos, text, text_display_end);
4356 }
4357
4358 // Render a rectangle shaped with optional rounding and borders
RenderFrame(ImVec2 p_min,ImVec2 p_max,ImU32 fill_col,bool border,float rounding)4359 void ImGui::RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border, float rounding)
4360 {
4361 ImGuiContext& g = *GImGui;
4362 ImGuiWindow* window = g.CurrentWindow;
4363 window->DrawList->AddRectFilled(p_min, p_max, fill_col, rounding);
4364 const float border_size = g.Style.FrameBorderSize;
4365 if (border && border_size > 0.0f)
4366 {
4367 window->DrawList->AddRect(p_min + ImVec2(1, 1), p_max + ImVec2(1, 1), GetColorU32(ImGuiCol_BorderShadow), rounding, ImDrawCornerFlags_All, border_size);
4368 window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, ImDrawCornerFlags_All, border_size);
4369 }
4370 }
4371
RenderFrameBorder(ImVec2 p_min,ImVec2 p_max,float rounding)4372 void ImGui::RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding)
4373 {
4374 ImGuiContext& g = *GImGui;
4375 ImGuiWindow* window = g.CurrentWindow;
4376 const float border_size = g.Style.FrameBorderSize;
4377 if (border_size > 0.0f)
4378 {
4379 window->DrawList->AddRect(p_min + ImVec2(1, 1), p_max + ImVec2(1, 1), GetColorU32(ImGuiCol_BorderShadow), rounding, ImDrawCornerFlags_All, border_size);
4380 window->DrawList->AddRect(p_min, p_max, GetColorU32(ImGuiCol_Border), rounding, ImDrawCornerFlags_All, border_size);
4381 }
4382 }
4383
4384 // Render a triangle to denote expanded/collapsed state
RenderTriangle(ImVec2 p_min,ImGuiDir dir,float scale)4385 void ImGui::RenderTriangle(ImVec2 p_min, ImGuiDir dir, float scale)
4386 {
4387 ImGuiContext& g = *GImGui;
4388 ImGuiWindow* window = g.CurrentWindow;
4389
4390 const float h = g.FontSize * 1.00f;
4391 float r = h * 0.40f * scale;
4392 ImVec2 center = p_min + ImVec2(h * 0.50f, h * 0.50f * scale);
4393
4394 ImVec2 a, b, c;
4395 switch (dir)
4396 {
4397 case ImGuiDir_Up:
4398 case ImGuiDir_Down:
4399 if (dir == ImGuiDir_Up) r = -r;
4400 center.y -= r * 0.25f;
4401 a = ImVec2(0, 1) * r;
4402 b = ImVec2(-0.866f, -0.5f) * r;
4403 c = ImVec2(+0.866f, -0.5f) * r;
4404 break;
4405 case ImGuiDir_Left:
4406 case ImGuiDir_Right:
4407 if (dir == ImGuiDir_Left) r = -r;
4408 center.x -= r * 0.25f;
4409 a = ImVec2(1, 0) * r;
4410 b = ImVec2(-0.500f, +0.866f) * r;
4411 c = ImVec2(-0.500f, -0.866f) * r;
4412 break;
4413 case ImGuiDir_None:
4414 case ImGuiDir_Count_:
4415 IM_ASSERT(0);
4416 break;
4417 }
4418
4419 window->DrawList->AddTriangleFilled(center + a, center + b, center + c, GetColorU32(ImGuiCol_Text));
4420 }
4421
RenderBullet(ImVec2 pos)4422 void ImGui::RenderBullet(ImVec2 pos)
4423 {
4424 ImGuiContext& g = *GImGui;
4425 ImGuiWindow* window = g.CurrentWindow;
4426 window->DrawList->AddCircleFilled(pos, GImGui->FontSize * 0.20f, GetColorU32(ImGuiCol_Text), 8);
4427 }
4428
RenderCheckMark(ImVec2 pos,ImU32 col,float sz)4429 void ImGui::RenderCheckMark(ImVec2 pos, ImU32 col, float sz)
4430 {
4431 ImGuiContext& g = *GImGui;
4432 ImGuiWindow* window = g.CurrentWindow;
4433
4434 float thickness = ImMax(sz / 5.0f, 1.0f);
4435 sz -= thickness * 0.5f;
4436 pos += ImVec2(thickness * 0.25f, thickness * 0.25f);
4437
4438 float third = sz / 3.0f;
4439 float bx = pos.x + third;
4440 float by = pos.y + sz - third * 0.5f;
4441 window->DrawList->PathLineTo(ImVec2(bx - third, by - third));
4442 window->DrawList->PathLineTo(ImVec2(bx, by));
4443 window->DrawList->PathLineTo(ImVec2(bx + third * 2, by - third * 2));
4444 window->DrawList->PathStroke(col, false, thickness);
4445 }
4446
RenderNavHighlight(const ImRect & bb,ImGuiID id,ImGuiNavHighlightFlags flags)4447 void ImGui::RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFlags flags)
4448 {
4449 ImGuiContext& g = *GImGui;
4450 if (id != g.NavId)
4451 return;
4452 if (g.NavDisableHighlight && !(flags & ImGuiNavHighlightFlags_AlwaysDraw))
4453 return;
4454 ImGuiWindow* window = ImGui::GetCurrentWindow();
4455 if (window->DC.NavHideHighlightOneFrame)
4456 return;
4457
4458 float rounding = (flags & ImGuiNavHighlightFlags_NoRounding) ? 0.0f : g.Style.FrameRounding;
4459 ImRect display_rect = bb;
4460 display_rect.ClipWith(window->ClipRect);
4461 if (flags & ImGuiNavHighlightFlags_TypeDefault)
4462 {
4463 const float THICKNESS = 2.0f;
4464 const float DISTANCE = 3.0f + THICKNESS * 0.5f;
4465 display_rect.Expand(ImVec2(DISTANCE, DISTANCE));
4466 bool fully_visible = window->ClipRect.Contains(display_rect);
4467 if (!fully_visible)
4468 window->DrawList->PushClipRect(display_rect.Min, display_rect.Max);
4469 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);
4470 if (!fully_visible)
4471 window->DrawList->PopClipRect();
4472 }
4473 if (flags & ImGuiNavHighlightFlags_TypeThin)
4474 {
4475 window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavHighlight), rounding, ~0, 1.0f);
4476 }
4477 }
4478
4479 // Calculate text size. Text can be multi-line. Optionally ignore text after a ## marker.
4480 // CalcTextSize("") should return ImVec2(0.0f, GImGui->FontSize)
CalcTextSize(const char * text,const char * text_end,bool hide_text_after_double_hash,float wrap_width)4481 ImVec2 ImGui::CalcTextSize(const char* text, const char* text_end, bool hide_text_after_double_hash, float wrap_width)
4482 {
4483 ImGuiContext& g = *GImGui;
4484
4485 const char* text_display_end;
4486 if (hide_text_after_double_hash)
4487 text_display_end = FindRenderedTextEnd(text, text_end); // Hide anything after a '##' string
4488 else
4489 text_display_end = text_end;
4490
4491 ImFont* font = g.Font;
4492 const float font_size = g.FontSize;
4493 if (text == text_display_end)
4494 return ImVec2(0.0f, font_size);
4495 ImVec2 text_size = font->CalcTextSizeA(font_size, FLT_MAX, wrap_width, text, text_display_end, NULL);
4496
4497 // Cancel out character spacing for the last character of a line (it is baked into glyph->AdvanceX field)
4498 const float font_scale = font_size / font->FontSize;
4499 const float character_spacing_x = 1.0f * font_scale;
4500 if (text_size.x > 0.0f)
4501 text_size.x -= character_spacing_x;
4502 text_size.x = (float)(int)(text_size.x + 0.95f);
4503
4504 return text_size;
4505 }
4506
4507 // Helper to calculate coarse clipping of large list of evenly sized items.
4508 // NB: Prefer using the ImGuiListClipper higher-level helper if you can! Read comments and instructions there on how those use this sort of pattern.
4509 // NB: 'items_count' is only used to clamp the result, if you don't know your count you can use INT_MAX
CalcListClipping(int items_count,float items_height,int * out_items_display_start,int * out_items_display_end)4510 void ImGui::CalcListClipping(int items_count, float items_height, int* out_items_display_start, int* out_items_display_end)
4511 {
4512 ImGuiContext& g = *GImGui;
4513 ImGuiWindow* window = g.CurrentWindow;
4514 if (g.LogEnabled)
4515 {
4516 // If logging is active, do not perform any clipping
4517 *out_items_display_start = 0;
4518 *out_items_display_end = items_count;
4519 return;
4520 }
4521 if (window->SkipItems)
4522 {
4523 *out_items_display_start = *out_items_display_end = 0;
4524 return;
4525 }
4526
4527 const ImVec2 pos = window->DC.CursorPos;
4528 int start = (int)((window->ClipRect.Min.y - pos.y) / items_height);
4529 int end = (int)((window->ClipRect.Max.y - pos.y) / items_height);
4530 if (g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Up) // When performing a navigation request, ensure we have one item extra in the direction we are moving to
4531 start--;
4532 if (g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down)
4533 end++;
4534
4535 start = ImClamp(start, 0, items_count);
4536 end = ImClamp(end + 1, start, items_count);
4537 *out_items_display_start = start;
4538 *out_items_display_end = end;
4539 }
4540
4541 // Find window given position, search front-to-back
4542 // 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.
FindHoveredWindow()4543 static ImGuiWindow* FindHoveredWindow()
4544 {
4545 ImGuiContext& g = *GImGui;
4546 for (int i = g.Windows.Size - 1; i >= 0; i--)
4547 {
4548 ImGuiWindow* window = g.Windows[i];
4549 if (!window->Active)
4550 continue;
4551 if (window->Flags & ImGuiWindowFlags_NoInputs)
4552 continue;
4553
4554 // Using the clipped AABB, a child window will typically be clipped by its parent (not always)
4555 ImRect bb(window->WindowRectClipped.Min - g.Style.TouchExtraPadding, window->WindowRectClipped.Max + g.Style.TouchExtraPadding);
4556 if (bb.Contains(g.IO.MousePos))
4557 return window;
4558 }
4559 return NULL;
4560 }
4561
4562 // Test if mouse cursor is hovering given rectangle
4563 // NB- Rectangle is clipped by our current clip setting
4564 // NB- Expand the rectangle to be generous on imprecise inputs systems (g.Style.TouchExtraPadding)
IsMouseHoveringRect(const ImVec2 & r_min,const ImVec2 & r_max,bool clip)4565 bool ImGui::IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool clip)
4566 {
4567 ImGuiContext& g = *GImGui;
4568 ImGuiWindow* window = g.CurrentWindow;
4569
4570 // Clip
4571 ImRect rect_clipped(r_min, r_max);
4572 if (clip)
4573 rect_clipped.ClipWith(window->ClipRect);
4574
4575 // Expand for touch input
4576 const ImRect rect_for_touch(rect_clipped.Min - g.Style.TouchExtraPadding, rect_clipped.Max + g.Style.TouchExtraPadding);
4577 return rect_for_touch.Contains(g.IO.MousePos);
4578 }
4579
IsKeyPressedMap(ImGuiKey key,bool repeat)4580 static bool IsKeyPressedMap(ImGuiKey key, bool repeat)
4581 {
4582 const int key_index = GImGui->IO.KeyMap[key];
4583 return (key_index >= 0) ? ImGui::IsKeyPressed(key_index, repeat) : false;
4584 }
4585
GetKeyIndex(ImGuiKey imgui_key)4586 int ImGui::GetKeyIndex(ImGuiKey imgui_key)
4587 {
4588 IM_ASSERT(imgui_key >= 0 && imgui_key < ImGuiKey_COUNT);
4589 return GImGui->IO.KeyMap[imgui_key];
4590 }
4591
4592 // Note that imgui doesn't know the semantic of each entry of io.KeyDown[]. Use your own indices/enums according to how your back-end/engine stored them into KeyDown[]!
IsKeyDown(int user_key_index)4593 bool ImGui::IsKeyDown(int user_key_index)
4594 {
4595 if (user_key_index < 0) return false;
4596 IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(GImGui->IO.KeysDown));
4597 return GImGui->IO.KeysDown[user_key_index];
4598 }
4599
CalcTypematicPressedRepeatAmount(float t,float t_prev,float repeat_delay,float repeat_rate)4600 int ImGui::CalcTypematicPressedRepeatAmount(float t, float t_prev, float repeat_delay, float repeat_rate)
4601 {
4602 if (t == 0.0f)
4603 return 1;
4604 if (t <= repeat_delay || repeat_rate <= 0.0f)
4605 return 0;
4606 const int count = (int)((t - repeat_delay) / repeat_rate) - (int)((t_prev - repeat_delay) / repeat_rate);
4607 return (count > 0) ? count : 0;
4608 }
4609
GetKeyPressedAmount(int key_index,float repeat_delay,float repeat_rate)4610 int ImGui::GetKeyPressedAmount(int key_index, float repeat_delay, float repeat_rate)
4611 {
4612 ImGuiContext& g = *GImGui;
4613 if (key_index < 0) return false;
4614 IM_ASSERT(key_index >= 0 && key_index < IM_ARRAYSIZE(g.IO.KeysDown));
4615 const float t = g.IO.KeysDownDuration[key_index];
4616 return CalcTypematicPressedRepeatAmount(t, t - g.IO.DeltaTime, repeat_delay, repeat_rate);
4617 }
4618
IsKeyPressed(int user_key_index,bool repeat)4619 bool ImGui::IsKeyPressed(int user_key_index, bool repeat)
4620 {
4621 ImGuiContext& g = *GImGui;
4622 if (user_key_index < 0) return false;
4623 IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(g.IO.KeysDown));
4624 const float t = g.IO.KeysDownDuration[user_key_index];
4625 if (t == 0.0f)
4626 return true;
4627 if (repeat && t > g.IO.KeyRepeatDelay)
4628 return GetKeyPressedAmount(user_key_index, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0;
4629 return false;
4630 }
4631
IsKeyReleased(int user_key_index)4632 bool ImGui::IsKeyReleased(int user_key_index)
4633 {
4634 ImGuiContext& g = *GImGui;
4635 if (user_key_index < 0) return false;
4636 IM_ASSERT(user_key_index >= 0 && user_key_index < IM_ARRAYSIZE(g.IO.KeysDown));
4637 return g.IO.KeysDownDurationPrev[user_key_index] >= 0.0f && !g.IO.KeysDown[user_key_index];
4638 }
4639
IsMouseDown(int button)4640 bool ImGui::IsMouseDown(int button)
4641 {
4642 ImGuiContext& g = *GImGui;
4643 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
4644 return g.IO.MouseDown[button];
4645 }
4646
IsAnyMouseDown()4647 bool ImGui::IsAnyMouseDown()
4648 {
4649 ImGuiContext& g = *GImGui;
4650 for (int n = 0; n < IM_ARRAYSIZE(g.IO.MouseDown); n++)
4651 if (g.IO.MouseDown[n])
4652 return true;
4653 return false;
4654 }
4655
IsMouseClicked(int button,bool repeat)4656 bool ImGui::IsMouseClicked(int button, bool repeat)
4657 {
4658 ImGuiContext& g = *GImGui;
4659 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
4660 const float t = g.IO.MouseDownDuration[button];
4661 if (t == 0.0f)
4662 return true;
4663
4664 if (repeat && t > g.IO.KeyRepeatDelay)
4665 {
4666 float delay = g.IO.KeyRepeatDelay, rate = g.IO.KeyRepeatRate;
4667 if ((fmodf(t - delay, rate) > rate * 0.5f) != (fmodf(t - delay - g.IO.DeltaTime, rate) > rate * 0.5f))
4668 return true;
4669 }
4670
4671 return false;
4672 }
4673
IsMouseReleased(int button)4674 bool ImGui::IsMouseReleased(int button)
4675 {
4676 ImGuiContext& g = *GImGui;
4677 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
4678 return g.IO.MouseReleased[button];
4679 }
4680
IsMouseDoubleClicked(int button)4681 bool ImGui::IsMouseDoubleClicked(int button)
4682 {
4683 ImGuiContext& g = *GImGui;
4684 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
4685 return g.IO.MouseDoubleClicked[button];
4686 }
4687
IsMouseDragging(int button,float lock_threshold)4688 bool ImGui::IsMouseDragging(int button, float lock_threshold)
4689 {
4690 ImGuiContext& g = *GImGui;
4691 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
4692 if (!g.IO.MouseDown[button])
4693 return false;
4694 if (lock_threshold < 0.0f)
4695 lock_threshold = g.IO.MouseDragThreshold;
4696 return g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold;
4697 }
4698
GetMousePos()4699 ImVec2 ImGui::GetMousePos()
4700 {
4701 return GImGui->IO.MousePos;
4702 }
4703
4704 // NB: prefer to call right after BeginPopup(). At the time Selectable/MenuItem is activated, the popup is already closed!
GetMousePosOnOpeningCurrentPopup()4705 ImVec2 ImGui::GetMousePosOnOpeningCurrentPopup()
4706 {
4707 ImGuiContext& g = *GImGui;
4708 if (g.CurrentPopupStack.Size > 0)
4709 return g.OpenPopupStack[g.CurrentPopupStack.Size - 1].OpenMousePos;
4710 return g.IO.MousePos;
4711 }
4712
4713 // We typically use ImVec2(-FLT_MAX,-FLT_MAX) to denote an invalid mouse position
IsMousePosValid(const ImVec2 * mouse_pos)4714 bool ImGui::IsMousePosValid(const ImVec2* mouse_pos)
4715 {
4716 if (mouse_pos == NULL)
4717 mouse_pos = &GImGui->IO.MousePos;
4718 const float MOUSE_INVALID = -256000.0f;
4719 return mouse_pos->x >= MOUSE_INVALID && mouse_pos->y >= MOUSE_INVALID;
4720 }
4721
4722 // NB: This is only valid if IsMousePosValid(). Back-ends in theory should always keep mouse position valid when dragging even outside the client window.
GetMouseDragDelta(int button,float lock_threshold)4723 ImVec2 ImGui::GetMouseDragDelta(int button, float lock_threshold)
4724 {
4725 ImGuiContext& g = *GImGui;
4726 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
4727 if (lock_threshold < 0.0f)
4728 lock_threshold = g.IO.MouseDragThreshold;
4729 if (g.IO.MouseDown[button])
4730 if (g.IO.MouseDragMaxDistanceSqr[button] >= lock_threshold * lock_threshold)
4731 return g.IO.MousePos - g.IO.MouseClickedPos[button]; // Assume we can only get active with left-mouse button (at the moment).
4732 return ImVec2(0.0f, 0.0f);
4733 }
4734
ResetMouseDragDelta(int button)4735 void ImGui::ResetMouseDragDelta(int button)
4736 {
4737 ImGuiContext& g = *GImGui;
4738 IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown));
4739 // NB: We don't need to reset g.IO.MouseDragMaxDistanceSqr
4740 g.IO.MouseClickedPos[button] = g.IO.MousePos;
4741 }
4742
GetMouseCursor()4743 ImGuiMouseCursor ImGui::GetMouseCursor()
4744 {
4745 return GImGui->MouseCursor;
4746 }
4747
SetMouseCursor(ImGuiMouseCursor cursor_type)4748 void ImGui::SetMouseCursor(ImGuiMouseCursor cursor_type)
4749 {
4750 GImGui->MouseCursor = cursor_type;
4751 }
4752
CaptureKeyboardFromApp(bool capture)4753 void ImGui::CaptureKeyboardFromApp(bool capture)
4754 {
4755 GImGui->WantCaptureKeyboardNextFrame = capture ? 1 : 0;
4756 }
4757
CaptureMouseFromApp(bool capture)4758 void ImGui::CaptureMouseFromApp(bool capture)
4759 {
4760 GImGui->WantCaptureMouseNextFrame = capture ? 1 : 0;
4761 }
4762
IsItemActive()4763 bool ImGui::IsItemActive()
4764 {
4765 ImGuiContext& g = *GImGui;
4766 if (g.ActiveId)
4767 {
4768 ImGuiWindow* window = g.CurrentWindow;
4769 return g.ActiveId == window->DC.LastItemId;
4770 }
4771 return false;
4772 }
4773
IsItemFocused()4774 bool ImGui::IsItemFocused()
4775 {
4776 ImGuiContext& g = *GImGui;
4777 return g.NavId && !g.NavDisableHighlight && g.NavId == g.CurrentWindow->DC.LastItemId;
4778 }
4779
IsItemClicked(int mouse_button)4780 bool ImGui::IsItemClicked(int mouse_button)
4781 {
4782 return IsMouseClicked(mouse_button) && IsItemHovered(ImGuiHoveredFlags_Default);
4783 }
4784
IsAnyItemHovered()4785 bool ImGui::IsAnyItemHovered()
4786 {
4787 ImGuiContext& g = *GImGui;
4788 return g.HoveredId != 0 || g.HoveredIdPreviousFrame != 0;
4789 }
4790
IsAnyItemActive()4791 bool ImGui::IsAnyItemActive()
4792 {
4793 ImGuiContext& g = *GImGui;
4794 return g.ActiveId != 0;
4795 }
4796
IsAnyItemFocused()4797 bool ImGui::IsAnyItemFocused()
4798 {
4799 ImGuiContext& g = *GImGui;
4800 return g.NavId != 0 && !g.NavDisableHighlight;
4801 }
4802
IsItemVisible()4803 bool ImGui::IsItemVisible()
4804 {
4805 ImGuiWindow* window = GetCurrentWindowRead();
4806 return window->ClipRect.Overlaps(window->DC.LastItemRect);
4807 }
4808
4809 // Allow last item to be overlapped by a subsequent item. Both may be activated during the same frame before the later one takes priority.
SetItemAllowOverlap()4810 void ImGui::SetItemAllowOverlap()
4811 {
4812 ImGuiContext& g = *GImGui;
4813 if (g.HoveredId == g.CurrentWindow->DC.LastItemId)
4814 g.HoveredIdAllowOverlap = true;
4815 if (g.ActiveId == g.CurrentWindow->DC.LastItemId)
4816 g.ActiveIdAllowOverlap = true;
4817 }
4818
GetItemRectMin()4819 ImVec2 ImGui::GetItemRectMin()
4820 {
4821 ImGuiWindow* window = GetCurrentWindowRead();
4822 return window->DC.LastItemRect.Min;
4823 }
4824
GetItemRectMax()4825 ImVec2 ImGui::GetItemRectMax()
4826 {
4827 ImGuiWindow* window = GetCurrentWindowRead();
4828 return window->DC.LastItemRect.Max;
4829 }
4830
GetItemRectSize()4831 ImVec2 ImGui::GetItemRectSize()
4832 {
4833 ImGuiWindow* window = GetCurrentWindowRead();
4834 return window->DC.LastItemRect.GetSize();
4835 }
4836
GetViewportRect()4837 static ImRect GetViewportRect()
4838 {
4839 ImGuiContext& g = *GImGui;
4840 if (g.IO.DisplayVisibleMin.x != g.IO.DisplayVisibleMax.x && g.IO.DisplayVisibleMin.y != g.IO.DisplayVisibleMax.y)
4841 return ImRect(g.IO.DisplayVisibleMin, g.IO.DisplayVisibleMax);
4842 return ImRect(0.0f, 0.0f, g.IO.DisplaySize.x, g.IO.DisplaySize.y);
4843 }
4844
4845 // Not exposed publicly as BeginTooltip() because bool parameters are evil. Let's see if other needs arise first.
BeginTooltipEx(ImGuiWindowFlags extra_flags,bool override_previous_tooltip)4846 void ImGui::BeginTooltipEx(ImGuiWindowFlags extra_flags, bool override_previous_tooltip)
4847 {
4848 ImGuiContext& g = *GImGui;
4849 char window_name[16];
4850 ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", g.TooltipOverrideCount);
4851 if (override_previous_tooltip)
4852 if (ImGuiWindow* window = FindWindowByName(window_name))
4853 if (window->Active)
4854 {
4855 // Hide previous tooltips. We can't easily "reset" the content of a window so we create a new one.
4856 window->HiddenFrames = 1;
4857 ImFormatString(window_name, IM_ARRAYSIZE(window_name), "##Tooltip_%02d", ++g.TooltipOverrideCount);
4858 }
4859 ImGuiWindowFlags flags = ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoNav;
4860 Begin(window_name, NULL, flags | extra_flags);
4861 }
4862
SetTooltipV(const char * fmt,va_list args)4863 void ImGui::SetTooltipV(const char* fmt, va_list args)
4864 {
4865 BeginTooltipEx(0, true);
4866 TextV(fmt, args);
4867 EndTooltip();
4868 }
4869
SetTooltip(const char * fmt,...)4870 void ImGui::SetTooltip(const char* fmt, ...)
4871 {
4872 va_list args;
4873 va_start(args, fmt);
4874 SetTooltipV(fmt, args);
4875 va_end(args);
4876 }
4877
BeginTooltip()4878 void ImGui::BeginTooltip()
4879 {
4880 BeginTooltipEx(0, false);
4881 }
4882
EndTooltip()4883 void ImGui::EndTooltip()
4884 {
4885 IM_ASSERT(GetCurrentWindowRead()->Flags & ImGuiWindowFlags_Tooltip); // Mismatched BeginTooltip()/EndTooltip() calls
4886 End();
4887 }
4888
4889 // Mark popup as open (toggle toward open state).
4890 // Popups are closed when user click outside, or activate a pressable item, or CloseCurrentPopup() is called within a BeginPopup()/EndPopup() block.
4891 // Popup identifiers are relative to the current ID-stack (so OpenPopup and BeginPopup needs to be at the same level).
4892 // One open popup per level of the popup hierarchy (NB: when assigning we reset the Window member of ImGuiPopupRef to NULL)
OpenPopupEx(ImGuiID id)4893 void ImGui::OpenPopupEx(ImGuiID id)
4894 {
4895 ImGuiContext& g = *GImGui;
4896 ImGuiWindow* parent_window = g.CurrentWindow;
4897 int current_stack_size = g.CurrentPopupStack.Size;
4898 ImGuiPopupRef popup_ref; // Tagged as new ref as Window will be set back to NULL if we write this into OpenPopupStack.
4899 popup_ref.PopupId = id;
4900 popup_ref.Window = NULL;
4901 popup_ref.ParentWindow = parent_window;
4902 popup_ref.OpenFrameCount = g.FrameCount;
4903 popup_ref.OpenParentId = parent_window->IDStack.back();
4904 popup_ref.OpenMousePos = g.IO.MousePos;
4905 popup_ref.OpenPopupPos = (!g.NavDisableHighlight && g.NavDisableMouseHover) ? NavCalcPreferredMousePos() : g.IO.MousePos;
4906
4907 if (g.OpenPopupStack.Size < current_stack_size + 1)
4908 {
4909 g.OpenPopupStack.push_back(popup_ref);
4910 }
4911 else
4912 {
4913 // Close child popups if any
4914 g.OpenPopupStack.resize(current_stack_size + 1);
4915
4916 // 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
4917 // 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
4918 // 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.
4919 if (g.OpenPopupStack[current_stack_size].PopupId == id && g.OpenPopupStack[current_stack_size].OpenFrameCount == g.FrameCount - 1)
4920 g.OpenPopupStack[current_stack_size].OpenFrameCount = popup_ref.OpenFrameCount;
4921 else
4922 g.OpenPopupStack[current_stack_size] = popup_ref;
4923
4924 // When reopening a popup we first refocus its parent, otherwise if its parent is itself a popup it would get closed by ClosePopupsOverWindow().
4925 // This is equivalent to what ClosePopupToLevel() does.
4926 //if (g.OpenPopupStack[current_stack_size].PopupId == id)
4927 // FocusWindow(parent_window);
4928 }
4929 }
4930
OpenPopup(const char * str_id)4931 void ImGui::OpenPopup(const char* str_id)
4932 {
4933 ImGuiContext& g = *GImGui;
4934 OpenPopupEx(g.CurrentWindow->GetID(str_id));
4935 }
4936
ClosePopupsOverWindow(ImGuiWindow * ref_window)4937 void ImGui::ClosePopupsOverWindow(ImGuiWindow* ref_window)
4938 {
4939 ImGuiContext& g = *GImGui;
4940 if (g.OpenPopupStack.empty())
4941 return;
4942
4943 // When popups are stacked, clicking on a lower level popups puts focus back to it and close popups above it.
4944 // Don't close our own child popup windows.
4945 int n = 0;
4946 if (ref_window)
4947 {
4948 for (n = 0; n < g.OpenPopupStack.Size; n++)
4949 {
4950 ImGuiPopupRef& popup = g.OpenPopupStack[n];
4951 if (!popup.Window)
4952 continue;
4953 IM_ASSERT((popup.Window->Flags & ImGuiWindowFlags_Popup) != 0);
4954 if (popup.Window->Flags & ImGuiWindowFlags_ChildWindow)
4955 continue;
4956
4957 // Trim the stack if popups are not direct descendant of the reference window (which is often the NavWindow)
4958 bool has_focus = false;
4959 for (int m = n; m < g.OpenPopupStack.Size && !has_focus; m++)
4960 has_focus = (g.OpenPopupStack[m].Window && g.OpenPopupStack[m].Window->RootWindow == ref_window->RootWindow);
4961 if (!has_focus)
4962 break;
4963 }
4964 }
4965 if (n < g.OpenPopupStack.Size) // This test is not required but it allows to set a convenient breakpoint on the block below
4966 ClosePopupToLevel(n);
4967 }
4968
GetFrontMostModalRootWindow()4969 static ImGuiWindow* GetFrontMostModalRootWindow()
4970 {
4971 ImGuiContext& g = *GImGui;
4972 for (int n = g.OpenPopupStack.Size - 1; n >= 0; n--)
4973 if (ImGuiWindow* popup = g.OpenPopupStack.Data[n].Window)
4974 if (popup->Flags & ImGuiWindowFlags_Modal)
4975 return popup;
4976 return NULL;
4977 }
4978
ClosePopupToLevel(int remaining)4979 static void ClosePopupToLevel(int remaining)
4980 {
4981 IM_ASSERT(remaining >= 0);
4982 ImGuiContext& g = *GImGui;
4983 ImGuiWindow* focus_window = (remaining > 0) ? g.OpenPopupStack[remaining - 1].Window : g.OpenPopupStack[0].ParentWindow;
4984 if (g.NavLayer == 0)
4985 focus_window = NavRestoreLastChildNavWindow(focus_window);
4986 ImGui::FocusWindow(focus_window);
4987 focus_window->DC.NavHideHighlightOneFrame = true;
4988 g.OpenPopupStack.resize(remaining);
4989 }
4990
ClosePopup(ImGuiID id)4991 void ImGui::ClosePopup(ImGuiID id)
4992 {
4993 if (!IsPopupOpen(id))
4994 return;
4995 ImGuiContext& g = *GImGui;
4996 ClosePopupToLevel(g.OpenPopupStack.Size - 1);
4997 }
4998
4999 // Close the popup we have begin-ed into.
CloseCurrentPopup()5000 void ImGui::CloseCurrentPopup()
5001 {
5002 ImGuiContext& g = *GImGui;
5003 int popup_idx = g.CurrentPopupStack.Size - 1;
5004 if (popup_idx < 0 || popup_idx >= g.OpenPopupStack.Size || g.CurrentPopupStack[popup_idx].PopupId != g.OpenPopupStack[popup_idx].PopupId)
5005 return;
5006 while (popup_idx > 0 && g.OpenPopupStack[popup_idx].Window && (g.OpenPopupStack[popup_idx].Window->Flags & ImGuiWindowFlags_ChildMenu))
5007 popup_idx--;
5008 ClosePopupToLevel(popup_idx);
5009 }
5010
BeginPopupEx(ImGuiID id,ImGuiWindowFlags extra_flags)5011 bool ImGui::BeginPopupEx(ImGuiID id, ImGuiWindowFlags extra_flags)
5012 {
5013 ImGuiContext& g = *GImGui;
5014 if (!IsPopupOpen(id))
5015 {
5016 g.NextWindowData.Clear(); // We behave like Begin() and need to consume those values
5017 return false;
5018 }
5019
5020 char name[20];
5021 if (extra_flags & ImGuiWindowFlags_ChildMenu)
5022 ImFormatString(name, IM_ARRAYSIZE(name), "##Menu_%02d", g.CurrentPopupStack.Size); // Recycle windows based on depth
5023 else
5024 ImFormatString(name, IM_ARRAYSIZE(name), "##Popup_%08x", id); // Not recycling, so we can close/open during the same frame
5025
5026 bool is_open = Begin(name, NULL, extra_flags | ImGuiWindowFlags_Popup);
5027 if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display)
5028 EndPopup();
5029
5030 return is_open;
5031 }
5032
BeginPopup(const char * str_id,ImGuiWindowFlags flags)5033 bool ImGui::BeginPopup(const char* str_id, ImGuiWindowFlags flags)
5034 {
5035 ImGuiContext& g = *GImGui;
5036 if (g.OpenPopupStack.Size <= g.CurrentPopupStack.Size) // Early out for performance
5037 {
5038 g.NextWindowData.Clear(); // We behave like Begin() and need to consume those values
5039 return false;
5040 }
5041 return BeginPopupEx(g.CurrentWindow->GetID(str_id), flags | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings);
5042 }
5043
IsPopupOpen(ImGuiID id)5044 bool ImGui::IsPopupOpen(ImGuiID id)
5045 {
5046 ImGuiContext& g = *GImGui;
5047 return g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].PopupId == id;
5048 }
5049
IsPopupOpen(const char * str_id)5050 bool ImGui::IsPopupOpen(const char* str_id)
5051 {
5052 ImGuiContext& g = *GImGui;
5053 return g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].PopupId == g.CurrentWindow->GetID(str_id);
5054 }
5055
BeginPopupModal(const char * name,bool * p_open,ImGuiWindowFlags flags)5056 bool ImGui::BeginPopupModal(const char* name, bool* p_open, ImGuiWindowFlags flags)
5057 {
5058 ImGuiContext& g = *GImGui;
5059 ImGuiWindow* window = g.CurrentWindow;
5060 const ImGuiID id = window->GetID(name);
5061 if (!IsPopupOpen(id))
5062 {
5063 g.NextWindowData.Clear(); // We behave like Begin() and need to consume those values
5064 return false;
5065 }
5066
5067 // Center modal windows by default
5068 // FIXME: Should test for (PosCond & window->SetWindowPosAllowFlags) with the upcoming window.
5069 if (g.NextWindowData.PosCond == 0)
5070 SetNextWindowPos(g.IO.DisplaySize * 0.5f, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
5071
5072 bool is_open = Begin(name, p_open, flags | ImGuiWindowFlags_Popup | ImGuiWindowFlags_Modal | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoSavedSettings);
5073 if (!is_open || (p_open && !*p_open)) // NB: is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
5074 {
5075 EndPopup();
5076 if (is_open)
5077 ClosePopup(id);
5078 return false;
5079 }
5080
5081 return is_open;
5082 }
5083
NavProcessMoveRequestWrapAround(ImGuiWindow * window)5084 static void NavProcessMoveRequestWrapAround(ImGuiWindow* window)
5085 {
5086 ImGuiContext& g = *GImGui;
5087 if (g.NavWindow == window && NavMoveRequestButNoResultYet())
5088 if ((g.NavMoveDir == ImGuiDir_Up || g.NavMoveDir == ImGuiDir_Down) && g.NavMoveRequestForward == ImGuiNavForward_None && g.NavLayer == 0)
5089 {
5090 g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued;
5091 ImGui::NavMoveRequestCancel();
5092 g.NavWindow->NavRectRel[0].Min.y = g.NavWindow->NavRectRel[0].Max.y = ((g.NavMoveDir == ImGuiDir_Up) ? ImMax(window->SizeFull.y, window->SizeContents.y) : 0.0f) - window->Scroll.y;
5093 }
5094 }
5095
EndPopup()5096 void ImGui::EndPopup()
5097 {
5098 ImGuiContext& g = *GImGui;
5099 (void)g;
5100 IM_ASSERT(g.CurrentWindow->Flags & ImGuiWindowFlags_Popup); // Mismatched BeginPopup()/EndPopup() calls
5101 IM_ASSERT(g.CurrentPopupStack.Size > 0);
5102
5103 // Make all menus and popups wrap around for now, may need to expose that policy.
5104 NavProcessMoveRequestWrapAround(g.CurrentWindow);
5105
5106 End();
5107 }
5108
OpenPopupOnItemClick(const char * str_id,int mouse_button)5109 bool ImGui::OpenPopupOnItemClick(const char* str_id, int mouse_button)
5110 {
5111 ImGuiWindow* window = GImGui->CurrentWindow;
5112 if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup))
5113 {
5114 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!
5115 IM_ASSERT(id != 0); // However, you cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item)
5116 OpenPopupEx(id);
5117 return true;
5118 }
5119 return false;
5120 }
5121
5122 // This is a helper to handle the simplest case of associating one named popup to one given widget.
5123 // You may want to handle this on user side if you have specific needs (e.g. tweaking IsItemHovered() parameters).
5124 // You can pass a NULL str_id to use the identifier of the last item.
BeginPopupContextItem(const char * str_id,int mouse_button)5125 bool ImGui::BeginPopupContextItem(const char* str_id, int mouse_button)
5126 {
5127 ImGuiWindow* window = GImGui->CurrentWindow;
5128 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!
5129 IM_ASSERT(id != 0); // However, you cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item)
5130 if (IsMouseReleased(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup))
5131 OpenPopupEx(id);
5132 return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings);
5133 }
5134
BeginPopupContextWindow(const char * str_id,int mouse_button,bool also_over_items)5135 bool ImGui::BeginPopupContextWindow(const char* str_id, int mouse_button, bool also_over_items)
5136 {
5137 if (!str_id)
5138 str_id = "window_context";
5139 ImGuiID id = GImGui->CurrentWindow->GetID(str_id);
5140 if (IsMouseReleased(mouse_button) && IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup))
5141 if (also_over_items || !IsAnyItemHovered())
5142 OpenPopupEx(id);
5143 return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings);
5144 }
5145
BeginPopupContextVoid(const char * str_id,int mouse_button)5146 bool ImGui::BeginPopupContextVoid(const char* str_id, int mouse_button)
5147 {
5148 if (!str_id)
5149 str_id = "void_context";
5150 ImGuiID id = GImGui->CurrentWindow->GetID(str_id);
5151 if (IsMouseReleased(mouse_button) && !IsWindowHovered(ImGuiHoveredFlags_AnyWindow))
5152 OpenPopupEx(id);
5153 return BeginPopupEx(id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings);
5154 }
5155
BeginChildEx(const char * name,ImGuiID id,const ImVec2 & size_arg,bool border,ImGuiWindowFlags extra_flags)5156 static bool BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags)
5157 {
5158 ImGuiContext& g = *GImGui;
5159 ImGuiWindow* parent_window = ImGui::GetCurrentWindow();
5160 ImGuiWindowFlags flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_ChildWindow;
5161 flags |= (parent_window->Flags & ImGuiWindowFlags_NoMove); // Inherit the NoMove flag
5162
5163 const ImVec2 content_avail = ImGui::GetContentRegionAvail();
5164 ImVec2 size = ImFloor(size_arg);
5165 const int auto_fit_axises = ((size.x == 0.0f) ? (1 << ImGuiAxis_X) : 0x00) | ((size.y == 0.0f) ? (1 << ImGuiAxis_Y) : 0x00);
5166 if (size.x <= 0.0f)
5167 size.x = ImMax(content_avail.x + size.x, 4.0f); // Arbitrary minimum child size (0.0f causing too much issues)
5168 if (size.y <= 0.0f)
5169 size.y = ImMax(content_avail.y + size.y, 4.0f);
5170
5171 const float backup_border_size = g.Style.ChildBorderSize;
5172 if (!border)
5173 g.Style.ChildBorderSize = 0.0f;
5174 flags |= extra_flags;
5175
5176 char title[256];
5177 if (name)
5178 ImFormatString(title, IM_ARRAYSIZE(title), "%s/%s_%08X", parent_window->Name, name, id);
5179 else
5180 ImFormatString(title, IM_ARRAYSIZE(title), "%s/%08X", parent_window->Name, id);
5181
5182 ImGui::SetNextWindowSize(size);
5183 bool ret = ImGui::Begin(title, NULL, flags);
5184 ImGuiWindow* child_window = ImGui::GetCurrentWindow();
5185 child_window->ChildId = id;
5186 child_window->AutoFitChildAxises = auto_fit_axises;
5187 g.Style.ChildBorderSize = backup_border_size;
5188
5189 // Process navigation-in immediately so NavInit can run on first frame
5190 if (!(flags & ImGuiWindowFlags_NavFlattened) && (child_window->DC.NavLayerActiveMask != 0 || child_window->DC.NavHasScroll) && g.NavActivateId == id)
5191 {
5192 ImGui::FocusWindow(child_window);
5193 ImGui::NavInitWindow(child_window, false);
5194 ImGui::SetActiveID(id + 1, child_window); // Steal ActiveId with a dummy id so that key-press won't activate child item
5195 g.ActiveIdSource = ImGuiInputSource_Nav;
5196 }
5197
5198 return ret;
5199 }
5200
BeginChild(const char * str_id,const ImVec2 & size_arg,bool border,ImGuiWindowFlags extra_flags)5201 bool ImGui::BeginChild(const char* str_id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags)
5202 {
5203 ImGuiWindow* window = GetCurrentWindow();
5204 return BeginChildEx(str_id, window->GetID(str_id), size_arg, border, extra_flags);
5205 }
5206
BeginChild(ImGuiID id,const ImVec2 & size_arg,bool border,ImGuiWindowFlags extra_flags)5207 bool ImGui::BeginChild(ImGuiID id, const ImVec2& size_arg, bool border, ImGuiWindowFlags extra_flags)
5208 {
5209 IM_ASSERT(id != 0);
5210 return BeginChildEx(NULL, id, size_arg, border, extra_flags);
5211 }
5212
EndChild()5213 void ImGui::EndChild()
5214 {
5215 ImGuiContext& g = *GImGui;
5216 ImGuiWindow* window = g.CurrentWindow;
5217
5218 IM_ASSERT(window->Flags & ImGuiWindowFlags_ChildWindow); // Mismatched BeginChild()/EndChild() callss
5219 if (window->BeginCount > 1)
5220 {
5221 End();
5222 }
5223 else
5224 {
5225 // When using auto-filling child window, we don't provide full width/height to ItemSize so that it doesn't feed back into automatic size-fitting.
5226 ImVec2 sz = GetWindowSize();
5227 if (window->AutoFitChildAxises & (1 << ImGuiAxis_X)) // Arbitrary minimum zero-ish child size of 4.0f causes less trouble than a 0.0f
5228 sz.x = ImMax(4.0f, sz.x);
5229 if (window->AutoFitChildAxises & (1 << ImGuiAxis_Y))
5230 sz.y = ImMax(4.0f, sz.y);
5231 End();
5232
5233 ImGuiWindow* parent_window = g.CurrentWindow;
5234 ImRect bb(parent_window->DC.CursorPos, parent_window->DC.CursorPos + sz);
5235 ItemSize(sz);
5236 if ((window->DC.NavLayerActiveMask != 0 || window->DC.NavHasScroll) && !(window->Flags & ImGuiWindowFlags_NavFlattened))
5237 {
5238 ItemAdd(bb, window->ChildId);
5239 RenderNavHighlight(bb, window->ChildId);
5240
5241 // When browsing a window that has no activable items (scroll only) we keep a highlight on the child
5242 if (window->DC.NavLayerActiveMask == 0 && window == g.NavWindow)
5243 RenderNavHighlight(ImRect(bb.Min - ImVec2(2, 2), bb.Max + ImVec2(2, 2)), g.NavId, ImGuiNavHighlightFlags_TypeThin);
5244 }
5245 else
5246 {
5247 // Not navigable into
5248 ItemAdd(bb, 0);
5249 }
5250 }
5251 }
5252
5253 // Helper to create a child window / scrolling region that looks like a normal widget frame.
BeginChildFrame(ImGuiID id,const ImVec2 & size,ImGuiWindowFlags extra_flags)5254 bool ImGui::BeginChildFrame(ImGuiID id, const ImVec2& size, ImGuiWindowFlags extra_flags)
5255 {
5256 ImGuiContext& g = *GImGui;
5257 const ImGuiStyle& style = g.Style;
5258 PushStyleColor(ImGuiCol_ChildBg, style.Colors[ImGuiCol_FrameBg]);
5259 PushStyleVar(ImGuiStyleVar_ChildRounding, style.FrameRounding);
5260 PushStyleVar(ImGuiStyleVar_ChildBorderSize, style.FrameBorderSize);
5261 PushStyleVar(ImGuiStyleVar_WindowPadding, style.FramePadding);
5262 return BeginChild(id, size, true, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysUseWindowPadding | extra_flags);
5263 }
5264
EndChildFrame()5265 void ImGui::EndChildFrame()
5266 {
5267 EndChild();
5268 PopStyleVar(3);
5269 PopStyleColor();
5270 }
5271
5272 // Save and compare stack sizes on Begin()/End() to detect usage errors
CheckStacksSize(ImGuiWindow * window,bool write)5273 static void CheckStacksSize(ImGuiWindow* window, bool write)
5274 {
5275 // 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)
5276 ImGuiContext& g = *GImGui;
5277 int* p_backup = &window->DC.StackSizesBackup[0];
5278 {
5279 int current = window->IDStack.Size;
5280 if (write)
5281 *p_backup = current;
5282 else
5283 IM_ASSERT(*p_backup == current && "PushID/PopID or TreeNode/TreePop Mismatch!");
5284 p_backup++;
5285 } // Too few or too many PopID()/TreePop()
5286 {
5287 int current = window->DC.GroupStack.Size;
5288 if (write)
5289 *p_backup = current;
5290 else
5291 IM_ASSERT(*p_backup == current && "BeginGroup/EndGroup Mismatch!");
5292 p_backup++;
5293 } // Too few or too many EndGroup()
5294 {
5295 int current = g.CurrentPopupStack.Size;
5296 if (write)
5297 *p_backup = current;
5298 else
5299 IM_ASSERT(*p_backup == current && "BeginMenu/EndMenu or BeginPopup/EndPopup Mismatch");
5300 p_backup++;
5301 } // Too few or too many EndMenu()/EndPopup()
5302 {
5303 int current = g.ColorModifiers.Size;
5304 if (write)
5305 *p_backup = current;
5306 else
5307 IM_ASSERT(*p_backup == current && "PushStyleColor/PopStyleColor Mismatch!");
5308 p_backup++;
5309 } // Too few or too many PopStyleColor()
5310 {
5311 int current = g.StyleModifiers.Size;
5312 if (write)
5313 *p_backup = current;
5314 else
5315 IM_ASSERT(*p_backup == current && "PushStyleVar/PopStyleVar Mismatch!");
5316 p_backup++;
5317 } // Too few or too many PopStyleVar()
5318 {
5319 int current = g.FontStack.Size;
5320 if (write)
5321 *p_backup = current;
5322 else
5323 IM_ASSERT(*p_backup == current && "PushFont/PopFont Mismatch!");
5324 p_backup++;
5325 } // Too few or too many PopFont()
5326 IM_ASSERT(p_backup == window->DC.StackSizesBackup + IM_ARRAYSIZE(window->DC.StackSizesBackup));
5327 }
5328
5329 enum ImGuiPopupPositionPolicy
5330 {
5331 ImGuiPopupPositionPolicy_Default,
5332 ImGuiPopupPositionPolicy_ComboBox
5333 };
5334
FindBestWindowPosForPopup(const ImVec2 & ref_pos,const ImVec2 & size,ImGuiDir * last_dir,const ImRect & r_avoid,ImGuiPopupPositionPolicy policy=ImGuiPopupPositionPolicy_Default)5335 static ImVec2 FindBestWindowPosForPopup(const ImVec2& ref_pos, const ImVec2& size, ImGuiDir* last_dir, const ImRect& r_avoid, ImGuiPopupPositionPolicy policy = ImGuiPopupPositionPolicy_Default)
5336 {
5337 const ImGuiStyle& style = GImGui->Style;
5338
5339 // 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.)
5340 // 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.
5341 ImVec2 safe_padding = style.DisplaySafeAreaPadding;
5342 ImRect r_outer(GetViewportRect());
5343 r_outer.Expand(ImVec2((size.x - r_outer.GetWidth() > safe_padding.x * 2) ? -safe_padding.x : 0.0f, (size.y - r_outer.GetHeight() > safe_padding.y * 2) ? -safe_padding.y : 0.0f));
5344 ImVec2 base_pos_clamped = ImClamp(ref_pos, r_outer.Min, r_outer.Max - size);
5345 //GImGui->OverlayDrawList.AddRect(r_avoid.Min, r_avoid.Max, IM_COL32(255,0,0,255));
5346 //GImGui->OverlayDrawList.AddRect(r_outer.Min, r_outer.Max, IM_COL32(0,255,0,255));
5347
5348 // Combo Box policy (we want a connecting edge)
5349 if (policy == ImGuiPopupPositionPolicy_ComboBox)
5350 {
5351 const ImGuiDir dir_prefered_order[ImGuiDir_Count_] = {ImGuiDir_Down, ImGuiDir_Right, ImGuiDir_Left, ImGuiDir_Up};
5352 for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_Count_; n++)
5353 {
5354 const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n];
5355 if (n != -1 && dir == *last_dir) // Already tried this direction?
5356 continue;
5357 ImVec2 pos;
5358 if (dir == ImGuiDir_Down) pos = ImVec2(r_avoid.Min.x, r_avoid.Max.y); // Below, Toward Right (default)
5359 if (dir == ImGuiDir_Right) pos = ImVec2(r_avoid.Min.x, r_avoid.Min.y - size.y); // Above, Toward Right
5360 if (dir == ImGuiDir_Left) pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Max.y); // Below, Toward Left
5361 if (dir == ImGuiDir_Up) pos = ImVec2(r_avoid.Max.x - size.x, r_avoid.Min.y - size.y); // Above, Toward Left
5362 if (!r_outer.Contains(ImRect(pos, pos + size)))
5363 continue;
5364 *last_dir = dir;
5365 return pos;
5366 }
5367 }
5368
5369 // Default popup policy
5370 const ImGuiDir dir_prefered_order[ImGuiDir_Count_] = {ImGuiDir_Right, ImGuiDir_Down, ImGuiDir_Up, ImGuiDir_Left};
5371 for (int n = (*last_dir != ImGuiDir_None) ? -1 : 0; n < ImGuiDir_Count_; n++)
5372 {
5373 const ImGuiDir dir = (n == -1) ? *last_dir : dir_prefered_order[n];
5374 if (n != -1 && dir == *last_dir) // Already tried this direction?
5375 continue;
5376 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);
5377 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);
5378 if (avail_w < size.x || avail_h < size.y)
5379 continue;
5380 ImVec2 pos;
5381 pos.x = (dir == ImGuiDir_Left) ? r_avoid.Min.x - size.x : (dir == ImGuiDir_Right) ? r_avoid.Max.x : base_pos_clamped.x;
5382 pos.y = (dir == ImGuiDir_Up) ? r_avoid.Min.y - size.y : (dir == ImGuiDir_Down) ? r_avoid.Max.y : base_pos_clamped.y;
5383 *last_dir = dir;
5384 return pos;
5385 }
5386
5387 // Fallback, try to keep within display
5388 *last_dir = ImGuiDir_None;
5389 ImVec2 pos = ref_pos;
5390 pos.x = ImMax(ImMin(pos.x + size.x, r_outer.Max.x) - size.x, r_outer.Min.x);
5391 pos.y = ImMax(ImMin(pos.y + size.y, r_outer.Max.y) - size.y, r_outer.Min.y);
5392 return pos;
5393 }
5394
SetWindowConditionAllowFlags(ImGuiWindow * window,ImGuiCond flags,bool enabled)5395 static void SetWindowConditionAllowFlags(ImGuiWindow* window, ImGuiCond flags, bool enabled)
5396 {
5397 window->SetWindowPosAllowFlags = enabled ? (window->SetWindowPosAllowFlags | flags) : (window->SetWindowPosAllowFlags & ~flags);
5398 window->SetWindowSizeAllowFlags = enabled ? (window->SetWindowSizeAllowFlags | flags) : (window->SetWindowSizeAllowFlags & ~flags);
5399 window->SetWindowCollapsedAllowFlags = enabled ? (window->SetWindowCollapsedAllowFlags | flags) : (window->SetWindowCollapsedAllowFlags & ~flags);
5400 }
5401
FindWindowByName(const char * name)5402 ImGuiWindow* ImGui::FindWindowByName(const char* name)
5403 {
5404 ImGuiContext& g = *GImGui;
5405 ImGuiID id = ImHash(name, 0);
5406 return (ImGuiWindow*)g.WindowsById.GetVoidPtr(id);
5407 }
5408
CreateNewWindow(const char * name,ImVec2 size,ImGuiWindowFlags flags)5409 static ImGuiWindow* CreateNewWindow(const char* name, ImVec2 size, ImGuiWindowFlags flags)
5410 {
5411 ImGuiContext& g = *GImGui;
5412
5413 // Create window the first time
5414 ImGuiWindow* window = IM_NEW(ImGuiWindow)(&g, name);
5415 window->Flags = flags;
5416 g.WindowsById.SetVoidPtr(window->ID, window);
5417
5418 // User can disable loading and saving of settings. Tooltip and child windows also don't store settings.
5419 if (!(flags & ImGuiWindowFlags_NoSavedSettings))
5420 {
5421 // Retrieve settings from .ini file
5422 // Use SetWindowPos() or SetNextWindowPos() with the appropriate condition flag to change the initial position of a window.
5423 window->Pos = window->PosFloat = ImVec2(60, 60);
5424
5425 if (ImGuiWindowSettings* settings = ImGui::FindWindowSettings(window->ID))
5426 {
5427 SetWindowConditionAllowFlags(window, ImGuiCond_FirstUseEver, false);
5428 window->PosFloat = settings->Pos;
5429 window->Pos = ImFloor(window->PosFloat);
5430 window->Collapsed = settings->Collapsed;
5431 if (ImLengthSqr(settings->Size) > 0.00001f)
5432 size = settings->Size;
5433 }
5434 }
5435 window->Size = window->SizeFull = window->SizeFullAtLastBegin = size;
5436
5437 if ((flags & ImGuiWindowFlags_AlwaysAutoResize) != 0)
5438 {
5439 window->AutoFitFramesX = window->AutoFitFramesY = 2;
5440 window->AutoFitOnlyGrows = false;
5441 }
5442 else
5443 {
5444 if (window->Size.x <= 0.0f)
5445 window->AutoFitFramesX = 2;
5446 if (window->Size.y <= 0.0f)
5447 window->AutoFitFramesY = 2;
5448 window->AutoFitOnlyGrows = (window->AutoFitFramesX > 0) || (window->AutoFitFramesY > 0);
5449 }
5450
5451 if (flags & ImGuiWindowFlags_NoBringToFrontOnFocus)
5452 g.Windows.insert(g.Windows.begin(), window); // Quite slow but rare and only once
5453 else
5454 g.Windows.push_back(window);
5455 return window;
5456 }
5457
CalcSizeAfterConstraint(ImGuiWindow * window,ImVec2 new_size)5458 static ImVec2 CalcSizeAfterConstraint(ImGuiWindow* window, ImVec2 new_size)
5459 {
5460 ImGuiContext& g = *GImGui;
5461 if (g.NextWindowData.SizeConstraintCond != 0)
5462 {
5463 // Using -1,-1 on either X/Y axis to preserve the current size.
5464 ImRect cr = g.NextWindowData.SizeConstraintRect;
5465 new_size.x = (cr.Min.x >= 0 && cr.Max.x >= 0) ? ImClamp(new_size.x, cr.Min.x, cr.Max.x) : window->SizeFull.x;
5466 new_size.y = (cr.Min.y >= 0 && cr.Max.y >= 0) ? ImClamp(new_size.y, cr.Min.y, cr.Max.y) : window->SizeFull.y;
5467 if (g.NextWindowData.SizeCallback)
5468 {
5469 ImGuiSizeCallbackData data;
5470 data.UserData = g.NextWindowData.SizeCallbackUserData;
5471 data.Pos = window->Pos;
5472 data.CurrentSize = window->SizeFull;
5473 data.DesiredSize = new_size;
5474 g.NextWindowData.SizeCallback(&data);
5475 new_size = data.DesiredSize;
5476 }
5477 }
5478
5479 // Minimum size
5480 if (!(window->Flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_AlwaysAutoResize)))
5481 {
5482 new_size = ImMax(new_size, g.Style.WindowMinSize);
5483 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
5484 }
5485 return new_size;
5486 }
5487
CalcSizeContents(ImGuiWindow * window)5488 static ImVec2 CalcSizeContents(ImGuiWindow* window)
5489 {
5490 ImVec2 sz;
5491 sz.x = (float)(int)((window->SizeContentsExplicit.x != 0.0f) ? window->SizeContentsExplicit.x : (window->DC.CursorMaxPos.x - window->Pos.x + window->Scroll.x));
5492 sz.y = (float)(int)((window->SizeContentsExplicit.y != 0.0f) ? window->SizeContentsExplicit.y : (window->DC.CursorMaxPos.y - window->Pos.y + window->Scroll.y));
5493 return sz + window->WindowPadding;
5494 }
5495
CalcSizeAutoFit(ImGuiWindow * window,const ImVec2 & size_contents)5496 static ImVec2 CalcSizeAutoFit(ImGuiWindow* window, const ImVec2& size_contents)
5497 {
5498 ImGuiContext& g = *GImGui;
5499 ImGuiStyle& style = g.Style;
5500 ImGuiWindowFlags flags = window->Flags;
5501 ImVec2 size_auto_fit;
5502 if ((flags & ImGuiWindowFlags_Tooltip) != 0)
5503 {
5504 // Tooltip always resize. We keep the spacing symmetric on both axises for aesthetic purpose.
5505 size_auto_fit = size_contents;
5506 }
5507 else
5508 {
5509 // 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.
5510 size_auto_fit = ImClamp(size_contents, style.WindowMinSize, ImMax(style.WindowMinSize, g.IO.DisplaySize - g.Style.DisplaySafeAreaPadding));
5511 ImVec2 size_auto_fit_after_constraint = CalcSizeAfterConstraint(window, size_auto_fit);
5512 if (size_auto_fit_after_constraint.x < size_contents.x && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar))
5513 size_auto_fit.y += style.ScrollbarSize;
5514 if (size_auto_fit_after_constraint.y < size_contents.y && !(flags & ImGuiWindowFlags_NoScrollbar))
5515 size_auto_fit.x += style.ScrollbarSize;
5516 }
5517 return size_auto_fit;
5518 }
5519
GetScrollMaxX(ImGuiWindow * window)5520 static float GetScrollMaxX(ImGuiWindow* window)
5521 {
5522 return ImMax(0.0f, window->SizeContents.x - (window->SizeFull.x - window->ScrollbarSizes.x));
5523 }
5524
GetScrollMaxY(ImGuiWindow * window)5525 static float GetScrollMaxY(ImGuiWindow* window)
5526 {
5527 return ImMax(0.0f, window->SizeContents.y - (window->SizeFull.y - window->ScrollbarSizes.y));
5528 }
5529
CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow * window)5530 static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window)
5531 {
5532 ImVec2 scroll = window->Scroll;
5533 float cr_x = window->ScrollTargetCenterRatio.x;
5534 float cr_y = window->ScrollTargetCenterRatio.y;
5535 if (window->ScrollTarget.x < FLT_MAX)
5536 scroll.x = window->ScrollTarget.x - cr_x * (window->SizeFull.x - window->ScrollbarSizes.x);
5537 if (window->ScrollTarget.y < FLT_MAX)
5538 scroll.y = window->ScrollTarget.y - (1.0f - cr_y) * (window->TitleBarHeight() + window->MenuBarHeight()) - cr_y * (window->SizeFull.y - window->ScrollbarSizes.y);
5539 scroll = ImMax(scroll, ImVec2(0.0f, 0.0f));
5540 if (!window->Collapsed && !window->SkipItems)
5541 {
5542 scroll.x = ImMin(scroll.x, GetScrollMaxX(window));
5543 scroll.y = ImMin(scroll.y, GetScrollMaxY(window));
5544 }
5545 return scroll;
5546 }
5547
GetWindowBgColorIdxFromFlags(ImGuiWindowFlags flags)5548 static ImGuiCol GetWindowBgColorIdxFromFlags(ImGuiWindowFlags flags)
5549 {
5550 if (flags & (ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_Popup))
5551 return ImGuiCol_PopupBg;
5552 if (flags & ImGuiWindowFlags_ChildWindow)
5553 return ImGuiCol_ChildBg;
5554 return ImGuiCol_WindowBg;
5555 }
5556
CalcResizePosSizeFromAnyCorner(ImGuiWindow * window,const ImVec2 & corner_target,const ImVec2 & corner_norm,ImVec2 * out_pos,ImVec2 * out_size)5557 static void CalcResizePosSizeFromAnyCorner(ImGuiWindow* window, const ImVec2& corner_target, const ImVec2& corner_norm, ImVec2* out_pos, ImVec2* out_size)
5558 {
5559 ImVec2 pos_min = ImLerp(corner_target, window->Pos, corner_norm); // Expected window upper-left
5560 ImVec2 pos_max = ImLerp(window->Pos + window->Size, corner_target, corner_norm); // Expected window lower-right
5561 ImVec2 size_expected = pos_max - pos_min;
5562 ImVec2 size_constrained = CalcSizeAfterConstraint(window, size_expected);
5563 *out_pos = pos_min;
5564 if (corner_norm.x == 0.0f)
5565 out_pos->x -= (size_constrained.x - size_expected.x);
5566 if (corner_norm.y == 0.0f)
5567 out_pos->y -= (size_constrained.y - size_expected.y);
5568 *out_size = size_constrained;
5569 }
5570
5571 struct ImGuiResizeGripDef
5572 {
5573 ImVec2 CornerPos;
5574 ImVec2 InnerDir;
5575 int AngleMin12, AngleMax12;
5576 };
5577
5578 const ImGuiResizeGripDef resize_grip_def[4] =
5579 {
5580 {ImVec2(1, 1), ImVec2(-1, -1), 0, 3}, // Lower right
5581 {ImVec2(0, 1), ImVec2(+1, -1), 3, 6}, // Lower left
5582 {ImVec2(0, 0), ImVec2(+1, +1), 6, 9}, // Upper left
5583 {ImVec2(1, 0), ImVec2(-1, +1), 9, 12}, // Upper right
5584 };
5585
GetBorderRect(ImGuiWindow * window,int border_n,float perp_padding,float thickness)5586 static ImRect GetBorderRect(ImGuiWindow* window, int border_n, float perp_padding, float thickness)
5587 {
5588 ImRect rect = window->Rect();
5589 if (thickness == 0.0f) rect.Max -= ImVec2(1, 1);
5590 if (border_n == 0) return ImRect(rect.Min.x + perp_padding, rect.Min.y, rect.Max.x - perp_padding, rect.Min.y + thickness);
5591 if (border_n == 1) return ImRect(rect.Max.x - thickness, rect.Min.y + perp_padding, rect.Max.x, rect.Max.y - perp_padding);
5592 if (border_n == 2) return ImRect(rect.Min.x + perp_padding, rect.Max.y - thickness, rect.Max.x - perp_padding, rect.Max.y);
5593 if (border_n == 3) return ImRect(rect.Min.x, rect.Min.y + perp_padding, rect.Min.x + thickness, rect.Max.y - perp_padding);
5594 IM_ASSERT(0);
5595 return ImRect();
5596 }
5597
5598 // Handle resize for: Resize Grips, Borders, Gamepad
UpdateManualResize(ImGuiWindow * window,const ImVec2 & size_auto_fit,int * border_held,int resize_grip_count,ImU32 resize_grip_col[4])5599 static void ImGui::UpdateManualResize(ImGuiWindow* window, const ImVec2& size_auto_fit, int* border_held, int resize_grip_count, ImU32 resize_grip_col[4])
5600 {
5601 ImGuiContext& g = *GImGui;
5602 ImGuiWindowFlags flags = window->Flags;
5603 if ((flags & ImGuiWindowFlags_NoResize) || (flags & ImGuiWindowFlags_AlwaysAutoResize) || window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0)
5604 return;
5605
5606 const int resize_border_count = (flags & ImGuiWindowFlags_ResizeFromAnySide) ? 4 : 0;
5607 const float grip_draw_size = (float)(int)ImMax(g.FontSize * 1.35f, window->WindowRounding + 1.0f + g.FontSize * 0.2f);
5608 const float grip_hover_size = (float)(int)(grip_draw_size * 0.75f);
5609
5610 ImVec2 pos_target(FLT_MAX, FLT_MAX);
5611 ImVec2 size_target(FLT_MAX, FLT_MAX);
5612
5613 // Manual resize grips
5614 PushID("#RESIZE");
5615 for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++)
5616 {
5617 const ImGuiResizeGripDef& grip = resize_grip_def[resize_grip_n];
5618 const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, grip.CornerPos);
5619
5620 // Using the FlattenChilds button flag we make the resize button accessible even if we are hovering over a child window
5621 ImRect resize_rect(corner, corner + grip.InnerDir * grip_hover_size);
5622 resize_rect.FixInverted();
5623 bool hovered, held;
5624 ButtonBehavior(resize_rect, window->GetID((void*)(intptr_t)resize_grip_n), &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_NoNavFocus);
5625 if (hovered || held)
5626 g.MouseCursor = (resize_grip_n & 1) ? ImGuiMouseCursor_ResizeNESW : ImGuiMouseCursor_ResizeNWSE;
5627
5628 if (g.HoveredWindow == window && held && g.IO.MouseDoubleClicked[0] && resize_grip_n == 0)
5629 {
5630 // Manual auto-fit when double-clicking
5631 size_target = CalcSizeAfterConstraint(window, size_auto_fit);
5632 ClearActiveID();
5633 }
5634 else if (held)
5635 {
5636 // Resize from any of the four corners
5637 // We don't use an incremental MouseDelta but rather compute an absolute target size based on mouse position
5638 ImVec2 corner_target = g.IO.MousePos - g.ActiveIdClickOffset + resize_rect.GetSize() * grip.CornerPos; // Corner of the window corresponding to our corner grip
5639 CalcResizePosSizeFromAnyCorner(window, corner_target, grip.CornerPos, &pos_target, &size_target);
5640 }
5641 if (resize_grip_n == 0 || held || hovered)
5642 resize_grip_col[resize_grip_n] = GetColorU32(held ? ImGuiCol_ResizeGripActive : hovered ? ImGuiCol_ResizeGripHovered : ImGuiCol_ResizeGrip);
5643 }
5644 for (int border_n = 0; border_n < resize_border_count; border_n++)
5645 {
5646 const float BORDER_SIZE = 5.0f; // FIXME: Only works _inside_ window because of HoveredWindow check.
5647 const float BORDER_APPEAR_TIMER = 0.05f; // Reduce visual noise
5648 bool hovered, held;
5649 ImRect border_rect = GetBorderRect(window, border_n, grip_hover_size, BORDER_SIZE);
5650 ButtonBehavior(border_rect, window->GetID((void*)(intptr_t)(border_n + 4)), &hovered, &held, ImGuiButtonFlags_FlattenChildren);
5651 if ((hovered && g.HoveredIdTimer > BORDER_APPEAR_TIMER) || held)
5652 {
5653 g.MouseCursor = (border_n & 1) ? ImGuiMouseCursor_ResizeEW : ImGuiMouseCursor_ResizeNS;
5654 if (held) *border_held = border_n;
5655 }
5656 if (held)
5657 {
5658 ImVec2 border_target = window->Pos;
5659 ImVec2 border_posn;
5660 if (border_n == 0)
5661 {
5662 border_posn = ImVec2(0, 0);
5663 border_target.y = (g.IO.MousePos.y - g.ActiveIdClickOffset.y);
5664 }
5665 if (border_n == 1)
5666 {
5667 border_posn = ImVec2(1, 0);
5668 border_target.x = (g.IO.MousePos.x - g.ActiveIdClickOffset.x + BORDER_SIZE);
5669 }
5670 if (border_n == 2)
5671 {
5672 border_posn = ImVec2(0, 1);
5673 border_target.y = (g.IO.MousePos.y - g.ActiveIdClickOffset.y + BORDER_SIZE);
5674 }
5675 if (border_n == 3)
5676 {
5677 border_posn = ImVec2(0, 0);
5678 border_target.x = (g.IO.MousePos.x - g.ActiveIdClickOffset.x);
5679 }
5680 CalcResizePosSizeFromAnyCorner(window, border_target, border_posn, &pos_target, &size_target);
5681 }
5682 }
5683 PopID();
5684
5685 // Navigation/gamepad resize
5686 if (g.NavWindowingTarget == window)
5687 {
5688 ImVec2 nav_resize_delta;
5689 if (g.NavWindowingInputSource == ImGuiInputSource_NavKeyboard && g.IO.KeyShift)
5690 nav_resize_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard, ImGuiInputReadMode_Down);
5691 if (g.NavWindowingInputSource == ImGuiInputSource_NavGamepad)
5692 nav_resize_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_Down);
5693 if (nav_resize_delta.x != 0.0f || nav_resize_delta.y != 0.0f)
5694 {
5695 const float NAV_RESIZE_SPEED = 600.0f;
5696 nav_resize_delta *= ImFloor(NAV_RESIZE_SPEED * g.IO.DeltaTime * ImMin(g.IO.DisplayFramebufferScale.x, g.IO.DisplayFramebufferScale.y));
5697 g.NavWindowingToggleLayer = false;
5698 g.NavDisableMouseHover = true;
5699 resize_grip_col[0] = GetColorU32(ImGuiCol_ResizeGripActive);
5700 // FIXME-NAV: Should store and accumulate into a separate size buffer to handle sizing constraints properly, right now a constraint will make us stuck.
5701 size_target = CalcSizeAfterConstraint(window, window->SizeFull + nav_resize_delta);
5702 }
5703 }
5704
5705 // Apply back modified position/size to window
5706 if (size_target.x != FLT_MAX)
5707 {
5708 window->SizeFull = size_target;
5709 MarkIniSettingsDirty(window);
5710 }
5711 if (pos_target.x != FLT_MAX)
5712 {
5713 window->Pos = window->PosFloat = ImFloor(pos_target);
5714 MarkIniSettingsDirty(window);
5715 }
5716
5717 window->Size = window->SizeFull;
5718 }
5719
5720 // Push a new ImGui window to add widgets to.
5721 // - 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.
5722 // - Begin/End can be called multiple times during the frame with the same window name to append content.
5723 // - The window name is used as a unique identifier to preserve window information across frames (and save rudimentary information to the .ini file).
5724 // 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.
5725 // - 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.
5726 // - Passing 'bool* p_open' displays a Close button on the upper-right corner of the window, the pointed value will be set to false when the button is pressed.
Begin(const char * name,bool * p_open,ImGuiWindowFlags flags)5727 bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags)
5728 {
5729 ImGuiContext& g = *GImGui;
5730 const ImGuiStyle& style = g.Style;
5731 IM_ASSERT(name != NULL); // Window name required
5732 IM_ASSERT(g.Initialized); // Forgot to call ImGui::NewFrame()
5733 IM_ASSERT(g.FrameCountEnded != g.FrameCount); // Called ImGui::Render() or ImGui::EndFrame() and haven't called ImGui::NewFrame() again yet
5734
5735 // Find or create
5736 ImGuiWindow* window = FindWindowByName(name);
5737 if (!window)
5738 {
5739 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.
5740 window = CreateNewWindow(name, size_on_first_use, flags);
5741 }
5742
5743 // Automatically disable manual moving/resizing when NoInputs is set
5744 if (flags & ImGuiWindowFlags_NoInputs)
5745 flags |= ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
5746
5747 if (flags & ImGuiWindowFlags_NavFlattened)
5748 IM_ASSERT(flags & ImGuiWindowFlags_ChildWindow);
5749
5750 const int current_frame = g.FrameCount;
5751 const bool first_begin_of_the_frame = (window->LastFrameActive != current_frame);
5752 if (first_begin_of_the_frame)
5753 window->Flags = (ImGuiWindowFlags)flags;
5754 else
5755 flags = window->Flags;
5756
5757 // Update the Appearing flag
5758 bool window_just_activated_by_user = (window->LastFrameActive < current_frame - 1); // Not using !WasActive because the implicit "Debug" window would always toggle off->on
5759 const bool window_just_appearing_after_hidden_for_resize = (window->HiddenFrames == 1);
5760 if (flags & ImGuiWindowFlags_Popup)
5761 {
5762 ImGuiPopupRef& popup_ref = g.OpenPopupStack[g.CurrentPopupStack.Size];
5763 window_just_activated_by_user |= (window->PopupId != popup_ref.PopupId); // We recycle popups so treat window as activated if popup id changed
5764 window_just_activated_by_user |= (window != popup_ref.Window);
5765 }
5766 window->Appearing = (window_just_activated_by_user || window_just_appearing_after_hidden_for_resize);
5767 window->CloseButton = (p_open != NULL);
5768 if (window->Appearing)
5769 SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, true);
5770
5771 // 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
5772 ImGuiWindow* parent_window_in_stack = g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back();
5773 ImGuiWindow* parent_window = first_begin_of_the_frame ? ((flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup)) ? parent_window_in_stack : NULL) : window->ParentWindow;
5774 IM_ASSERT(parent_window != NULL || !(flags & ImGuiWindowFlags_ChildWindow));
5775
5776 // Add to stack
5777 g.CurrentWindowStack.push_back(window);
5778 SetCurrentWindow(window);
5779 CheckStacksSize(window, true);
5780 if (flags & ImGuiWindowFlags_Popup)
5781 {
5782 ImGuiPopupRef& popup_ref = g.OpenPopupStack[g.CurrentPopupStack.Size];
5783 popup_ref.Window = window;
5784 g.CurrentPopupStack.push_back(popup_ref);
5785 window->PopupId = popup_ref.PopupId;
5786 }
5787
5788 if (window_just_appearing_after_hidden_for_resize && !(flags & ImGuiWindowFlags_ChildWindow))
5789 window->NavLastIds[0] = 0;
5790
5791 // Process SetNextWindow***() calls
5792 bool window_pos_set_by_api = false;
5793 bool window_size_x_set_by_api = false, window_size_y_set_by_api = false;
5794 if (g.NextWindowData.PosCond)
5795 {
5796 window_pos_set_by_api = (window->SetWindowPosAllowFlags & g.NextWindowData.PosCond) != 0;
5797 if (window_pos_set_by_api && ImLengthSqr(g.NextWindowData.PosPivotVal) > 0.00001f)
5798 {
5799 // May be processed on the next frame if this is our first frame and we are measuring size
5800 // FIXME: Look into removing the branch so everything can go through this same code path for consistency.
5801 window->SetWindowPosVal = g.NextWindowData.PosVal;
5802 window->SetWindowPosPivot = g.NextWindowData.PosPivotVal;
5803 window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
5804 }
5805 else
5806 {
5807 SetWindowPos(window, g.NextWindowData.PosVal, g.NextWindowData.PosCond);
5808 }
5809 g.NextWindowData.PosCond = 0;
5810 }
5811 if (g.NextWindowData.SizeCond)
5812 {
5813 window_size_x_set_by_api = (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.x > 0.0f);
5814 window_size_y_set_by_api = (window->SetWindowSizeAllowFlags & g.NextWindowData.SizeCond) != 0 && (g.NextWindowData.SizeVal.y > 0.0f);
5815 SetWindowSize(window, g.NextWindowData.SizeVal, g.NextWindowData.SizeCond);
5816 g.NextWindowData.SizeCond = 0;
5817 }
5818 if (g.NextWindowData.ContentSizeCond)
5819 {
5820 // Adjust passed "client size" to become a "window size"
5821 window->SizeContentsExplicit = g.NextWindowData.ContentSizeVal;
5822 if (window->SizeContentsExplicit.y != 0.0f)
5823 window->SizeContentsExplicit.y += window->TitleBarHeight() + window->MenuBarHeight();
5824 g.NextWindowData.ContentSizeCond = 0;
5825 }
5826 else if (first_begin_of_the_frame)
5827 {
5828 window->SizeContentsExplicit = ImVec2(0.0f, 0.0f);
5829 }
5830 if (g.NextWindowData.CollapsedCond)
5831 {
5832 SetWindowCollapsed(window, g.NextWindowData.CollapsedVal, g.NextWindowData.CollapsedCond);
5833 g.NextWindowData.CollapsedCond = 0;
5834 }
5835 if (g.NextWindowData.FocusCond)
5836 {
5837 SetWindowFocus();
5838 g.NextWindowData.FocusCond = 0;
5839 }
5840 if (window->Appearing)
5841 SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, false);
5842
5843 // When reusing window again multiple times a frame, just append content (don't need to setup again)
5844 if (first_begin_of_the_frame)
5845 {
5846 const bool window_is_child_tooltip = (flags & ImGuiWindowFlags_ChildWindow) && (flags & ImGuiWindowFlags_Tooltip); // FIXME-WIP: Undocumented behavior of Child+Tooltip for pinned tooltip (#1345)
5847
5848 // Initialize
5849 window->ParentWindow = parent_window;
5850 window->RootWindow = window->RootWindowForTitleBarHighlight = window->RootWindowForTabbing = window->RootWindowForNav = window;
5851 if (parent_window && (flags & ImGuiWindowFlags_ChildWindow) && !window_is_child_tooltip)
5852 window->RootWindow = parent_window->RootWindow;
5853 if (parent_window && !(flags & ImGuiWindowFlags_Modal) && (flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Popup)))
5854 window->RootWindowForTitleBarHighlight = window->RootWindowForTabbing = parent_window->RootWindowForTitleBarHighlight; // Same value in master branch, will differ for docking
5855 while (window->RootWindowForNav->Flags & ImGuiWindowFlags_NavFlattened)
5856 window->RootWindowForNav = window->RootWindowForNav->ParentWindow;
5857
5858 window->Active = true;
5859 window->BeginOrderWithinParent = 0;
5860 window->BeginOrderWithinContext = g.WindowsActiveCount++;
5861 window->BeginCount = 0;
5862 window->ClipRect = ImVec4(-FLT_MAX, -FLT_MAX, +FLT_MAX, +FLT_MAX);
5863 window->LastFrameActive = current_frame;
5864 window->IDStack.resize(1);
5865
5866 // Lock window rounding, border size and rounding so that altering the border sizes for children doesn't have side-effects.
5867 window->WindowRounding = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildRounding : ((flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupRounding : style.WindowRounding;
5868 window->WindowBorderSize = (flags & ImGuiWindowFlags_ChildWindow) ? style.ChildBorderSize : ((flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiWindowFlags_Modal)) ? style.PopupBorderSize : style.WindowBorderSize;
5869 window->WindowPadding = style.WindowPadding;
5870 if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & (ImGuiWindowFlags_AlwaysUseWindowPadding | ImGuiWindowFlags_Popup)) && window->WindowBorderSize == 0.0f)
5871 window->WindowPadding = ImVec2(0.0f, (flags & ImGuiWindowFlags_MenuBar) ? style.WindowPadding.y : 0.0f);
5872
5873 // Collapse window by double-clicking on title bar
5874 // 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
5875 if (!(flags & ImGuiWindowFlags_NoTitleBar) && !(flags & ImGuiWindowFlags_NoCollapse))
5876 {
5877 ImRect title_bar_rect = window->TitleBarRect();
5878 if (window->CollapseToggleWanted || (g.HoveredWindow == window && IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max) && g.IO.MouseDoubleClicked[0]))
5879 {
5880 window->Collapsed = !window->Collapsed;
5881 MarkIniSettingsDirty(window);
5882 FocusWindow(window);
5883 }
5884 }
5885 else
5886 {
5887 window->Collapsed = false;
5888 }
5889 window->CollapseToggleWanted = false;
5890
5891 // SIZE
5892
5893 // Update contents size from last frame for auto-fitting (unless explicitly specified)
5894 window->SizeContents = CalcSizeContents(window);
5895
5896 // Hide popup/tooltip window when re-opening while we measure size (because we recycle the windows)
5897 if (window->HiddenFrames > 0)
5898 window->HiddenFrames--;
5899 if ((flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_Tooltip)) != 0 && window_just_activated_by_user)
5900 {
5901 window->HiddenFrames = 1;
5902 if (flags & ImGuiWindowFlags_AlwaysAutoResize)
5903 {
5904 if (!window_size_x_set_by_api)
5905 window->Size.x = window->SizeFull.x = 0.f;
5906 if (!window_size_y_set_by_api)
5907 window->Size.y = window->SizeFull.y = 0.f;
5908 window->SizeContents = ImVec2(0.f, 0.f);
5909 }
5910 }
5911
5912 // Calculate auto-fit size, handle automatic resize
5913 const ImVec2 size_auto_fit = CalcSizeAutoFit(window, window->SizeContents);
5914 ImVec2 size_full_modified(FLT_MAX, FLT_MAX);
5915 if (flags & ImGuiWindowFlags_AlwaysAutoResize && !window->Collapsed)
5916 {
5917 // Using SetNextWindowSize() overrides ImGuiWindowFlags_AlwaysAutoResize, so it can be used on tooltips/popups, etc.
5918 if (!window_size_x_set_by_api)
5919 window->SizeFull.x = size_full_modified.x = size_auto_fit.x;
5920 if (!window_size_y_set_by_api)
5921 window->SizeFull.y = size_full_modified.y = size_auto_fit.y;
5922 }
5923 else if (window->AutoFitFramesX > 0 || window->AutoFitFramesY > 0)
5924 {
5925 // Auto-fit only grows during the first few frames
5926 // We still process initial auto-fit on collapsed windows to get a window width, but otherwise don't honor ImGuiWindowFlags_AlwaysAutoResize when collapsed.
5927 if (!window_size_x_set_by_api && window->AutoFitFramesX > 0)
5928 window->SizeFull.x = size_full_modified.x = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.x, size_auto_fit.x) : size_auto_fit.x;
5929 if (!window_size_y_set_by_api && window->AutoFitFramesY > 0)
5930 window->SizeFull.y = size_full_modified.y = window->AutoFitOnlyGrows ? ImMax(window->SizeFull.y, size_auto_fit.y) : size_auto_fit.y;
5931 if (!window->Collapsed)
5932 MarkIniSettingsDirty(window);
5933 }
5934
5935 // Apply minimum/maximum window size constraints and final size
5936 window->SizeFull = CalcSizeAfterConstraint(window, window->SizeFull);
5937 window->Size = window->Collapsed && !(flags & ImGuiWindowFlags_ChildWindow) ? window->TitleBarRect().GetSize() : window->SizeFull;
5938
5939 // SCROLLBAR STATUS
5940
5941 // Update scrollbar status (based on the Size that was effective during last frame or the auto-resized Size).
5942 if (!window->Collapsed)
5943 {
5944 // When reading the current size we need to read it after size constraints have been applied
5945 float size_x_for_scrollbars = size_full_modified.x != FLT_MAX ? window->SizeFull.x : window->SizeFullAtLastBegin.x;
5946 float size_y_for_scrollbars = size_full_modified.y != FLT_MAX ? window->SizeFull.y : window->SizeFullAtLastBegin.y;
5947 window->ScrollbarY = (flags & ImGuiWindowFlags_AlwaysVerticalScrollbar) || ((window->SizeContents.y > size_y_for_scrollbars) && !(flags & ImGuiWindowFlags_NoScrollbar));
5948 window->ScrollbarX = (flags & ImGuiWindowFlags_AlwaysHorizontalScrollbar) || ((window->SizeContents.x > size_x_for_scrollbars - (window->ScrollbarY ? style.ScrollbarSize : 0.0f)) && !(flags & ImGuiWindowFlags_NoScrollbar) && (flags & ImGuiWindowFlags_HorizontalScrollbar));
5949 if (window->ScrollbarX && !window->ScrollbarY)
5950 window->ScrollbarY = (window->SizeContents.y > size_y_for_scrollbars - style.ScrollbarSize) && !(flags & ImGuiWindowFlags_NoScrollbar);
5951 window->ScrollbarSizes = ImVec2(window->ScrollbarY ? style.ScrollbarSize : 0.0f, window->ScrollbarX ? style.ScrollbarSize : 0.0f);
5952 }
5953
5954 // POSITION
5955
5956 // Popup latch its initial position, will position itself when it appears next frame
5957 if (window_just_activated_by_user)
5958 {
5959 window->AutoPosLastDirection = ImGuiDir_None;
5960 if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api)
5961 window->Pos = window->PosFloat = g.CurrentPopupStack.back().OpenPopupPos;
5962 }
5963
5964 // Position child window
5965 if (flags & ImGuiWindowFlags_ChildWindow)
5966 {
5967 window->BeginOrderWithinParent = parent_window->DC.ChildWindows.Size;
5968 parent_window->DC.ChildWindows.push_back(window);
5969 if (!(flags & ImGuiWindowFlags_Popup) && !window_pos_set_by_api && !window_is_child_tooltip)
5970 window->Pos = window->PosFloat = parent_window->DC.CursorPos;
5971 }
5972
5973 const bool window_pos_with_pivot = (window->SetWindowPosVal.x != FLT_MAX && window->HiddenFrames == 0);
5974 if (window_pos_with_pivot)
5975 {
5976 // Position given a pivot (e.g. for centering)
5977 SetWindowPos(window, ImMax(style.DisplaySafeAreaPadding, window->SetWindowPosVal - window->SizeFull * window->SetWindowPosPivot), 0);
5978 }
5979 else if (flags & ImGuiWindowFlags_ChildMenu)
5980 {
5981 // Child menus typically request _any_ position within the parent menu item, and then our FindBestPopupWindowPos() function will move the new menu outside the parent bounds.
5982 // This is how we end up with child menus appearing (most-commonly) on the right of the parent menu.
5983 IM_ASSERT(window_pos_set_by_api);
5984 float horizontal_overlap = style.ItemSpacing.x; // We want some overlap to convey the relative depth of each popup (currently the amount of overlap it is hard-coded to style.ItemSpacing.x, may need to introduce another style value).
5985 ImGuiWindow* parent_menu = parent_window_in_stack;
5986 ImRect rect_to_avoid;
5987 if (parent_menu->DC.MenuBarAppending)
5988 rect_to_avoid = ImRect(-FLT_MAX, parent_menu->Pos.y + parent_menu->TitleBarHeight(), FLT_MAX, parent_menu->Pos.y + parent_menu->TitleBarHeight() + parent_menu->MenuBarHeight());
5989 else
5990 rect_to_avoid = ImRect(parent_menu->Pos.x + horizontal_overlap, -FLT_MAX, parent_menu->Pos.x + parent_menu->Size.x - horizontal_overlap - parent_menu->ScrollbarSizes.x, FLT_MAX);
5991 window->PosFloat = FindBestWindowPosForPopup(window->PosFloat, window->Size, &window->AutoPosLastDirection, rect_to_avoid);
5992 }
5993 else if ((flags & ImGuiWindowFlags_Popup) != 0 && !window_pos_set_by_api && window_just_appearing_after_hidden_for_resize)
5994 {
5995 ImRect rect_to_avoid(window->PosFloat.x - 1, window->PosFloat.y - 1, window->PosFloat.x + 1, window->PosFloat.y + 1);
5996 window->PosFloat = FindBestWindowPosForPopup(window->PosFloat, window->Size, &window->AutoPosLastDirection, rect_to_avoid);
5997 }
5998
5999 // Position tooltip (always follows mouse)
6000 if ((flags & ImGuiWindowFlags_Tooltip) != 0 && !window_pos_set_by_api && !window_is_child_tooltip)
6001 {
6002 float sc = g.Style.MouseCursorScale;
6003 ImVec2 ref_pos = (!g.NavDisableHighlight && g.NavDisableMouseHover) ? NavCalcPreferredMousePos() : g.IO.MousePos;
6004 ImRect rect_to_avoid;
6005 if (!g.NavDisableHighlight && g.NavDisableMouseHover && !(g.IO.NavFlags & ImGuiNavFlags_MoveMouse))
6006 rect_to_avoid = ImRect(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 16, ref_pos.y + 8);
6007 else
6008 rect_to_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.
6009 window->PosFloat = FindBestWindowPosForPopup(ref_pos, window->Size, &window->AutoPosLastDirection, rect_to_avoid);
6010 if (window->AutoPosLastDirection == ImGuiDir_None)
6011 window->PosFloat = 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.
6012 }
6013
6014 // Clamp position so it stays visible
6015 if (!(flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Tooltip))
6016 {
6017 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.
6018 {
6019 ImVec2 padding = ImMax(style.DisplayWindowPadding, style.DisplaySafeAreaPadding);
6020 window->PosFloat = ImMax(window->PosFloat + window->Size, padding) - window->Size;
6021 window->PosFloat = ImMin(window->PosFloat, g.IO.DisplaySize - padding);
6022 }
6023 }
6024 window->Pos = ImFloor(window->PosFloat);
6025
6026 // Default item width. Make it proportional to window size if window manually resizes
6027 if (window->Size.x > 0.0f && !(flags & ImGuiWindowFlags_Tooltip) && !(flags & ImGuiWindowFlags_AlwaysAutoResize))
6028 window->ItemWidthDefault = (float)(int)(window->Size.x * 0.65f);
6029 else
6030 window->ItemWidthDefault = (float)(int)(g.FontSize * 16.0f);
6031
6032 // Prepare for focus requests
6033 window->FocusIdxAllRequestCurrent = (window->FocusIdxAllRequestNext == INT_MAX || window->FocusIdxAllCounter == -1) ? INT_MAX : (window->FocusIdxAllRequestNext + (window->FocusIdxAllCounter + 1)) % (window->FocusIdxAllCounter + 1);
6034 window->FocusIdxTabRequestCurrent = (window->FocusIdxTabRequestNext == INT_MAX || window->FocusIdxTabCounter == -1) ? INT_MAX : (window->FocusIdxTabRequestNext + (window->FocusIdxTabCounter + 1)) % (window->FocusIdxTabCounter + 1);
6035 window->FocusIdxAllCounter = window->FocusIdxTabCounter = -1;
6036 window->FocusIdxAllRequestNext = window->FocusIdxTabRequestNext = INT_MAX;
6037
6038 // Apply scrolling
6039 window->Scroll = CalcNextScrollFromScrollTargetAndClamp(window);
6040 window->ScrollTarget = ImVec2(FLT_MAX, FLT_MAX);
6041
6042 // Apply focus, new windows appears in front
6043 bool want_focus = false;
6044 if (window_just_activated_by_user && !(flags & ImGuiWindowFlags_NoFocusOnAppearing))
6045 if (!(flags & (ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_Tooltip)) || (flags & ImGuiWindowFlags_Popup))
6046 want_focus = true;
6047
6048 // Handle manual resize: Resize Grips, Borders, Gamepad
6049 int border_held = -1;
6050 ImU32 resize_grip_col[4] = {0};
6051 const int resize_grip_count = (flags & ImGuiWindowFlags_ResizeFromAnySide) ? 2 : 1; // 4
6052 const float grip_draw_size = (float)(int)ImMax(g.FontSize * 1.35f, window->WindowRounding + 1.0f + g.FontSize * 0.2f);
6053 if (!window->Collapsed)
6054 UpdateManualResize(window, size_auto_fit, &border_held, resize_grip_count, &resize_grip_col[0]);
6055
6056 // DRAWING
6057
6058 // Setup draw list and outer clipping rectangle
6059 window->DrawList->Clear();
6060 window->DrawList->Flags = (g.Style.AntiAliasedLines ? ImDrawListFlags_AntiAliasedLines : 0) | (g.Style.AntiAliasedFill ? ImDrawListFlags_AntiAliasedFill : 0);
6061 window->DrawList->PushTextureID(g.Font->ContainerAtlas->TexID);
6062 ImRect viewport_rect(GetViewportRect());
6063 if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup) && !window_is_child_tooltip)
6064 PushClipRect(parent_window->ClipRect.Min, parent_window->ClipRect.Max, true);
6065 else
6066 PushClipRect(viewport_rect.Min, viewport_rect.Max, true);
6067
6068 // Draw modal window background (darkens what is behind them)
6069 if ((flags & ImGuiWindowFlags_Modal) != 0 && window == GetFrontMostModalRootWindow())
6070 window->DrawList->AddRectFilled(viewport_rect.Min, viewport_rect.Max, GetColorU32(ImGuiCol_ModalWindowDarkening, g.ModalWindowDarkeningRatio));
6071
6072 // Draw navigation selection/windowing rectangle background
6073 if (g.NavWindowingTarget == window)
6074 {
6075 ImRect bb = window->Rect();
6076 bb.Expand(g.FontSize);
6077 if (!bb.Contains(viewport_rect)) // Avoid drawing if the window covers all the viewport anyway
6078 window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha * 0.25f), g.Style.WindowRounding);
6079 }
6080
6081 // Draw window + handle manual resize
6082 const float window_rounding = window->WindowRounding;
6083 const float window_border_size = window->WindowBorderSize;
6084 const bool title_bar_is_highlight = want_focus || (g.NavWindow && window->RootWindowForTitleBarHighlight == g.NavWindow->RootWindowForTitleBarHighlight);
6085 const ImRect title_bar_rect = window->TitleBarRect();
6086 if (window->Collapsed)
6087 {
6088 // Title bar only
6089 float backup_border_size = style.FrameBorderSize;
6090 g.Style.FrameBorderSize = window->WindowBorderSize;
6091 ImU32 title_bar_col = GetColorU32((title_bar_is_highlight && !g.NavDisableHighlight) ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBgCollapsed);
6092 RenderFrame(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, true, window_rounding);
6093 g.Style.FrameBorderSize = backup_border_size;
6094 }
6095 else
6096 {
6097 // Window background
6098 ImU32 bg_col = GetColorU32(GetWindowBgColorIdxFromFlags(flags));
6099 if (g.NextWindowData.BgAlphaCond != 0)
6100 {
6101 bg_col = (bg_col & ~IM_COL32_A_MASK) | (IM_F32_TO_INT8_SAT(g.NextWindowData.BgAlphaVal) << IM_COL32_A_SHIFT);
6102 g.NextWindowData.BgAlphaCond = 0;
6103 }
6104 window->DrawList->AddRectFilled(window->Pos + ImVec2(0, window->TitleBarHeight()), window->Pos + window->Size, bg_col, window_rounding, (flags & ImGuiWindowFlags_NoTitleBar) ? ImDrawCornerFlags_All : ImDrawCornerFlags_Bot);
6105
6106 // Title bar
6107 ImU32 title_bar_col = GetColorU32(window->Collapsed ? ImGuiCol_TitleBgCollapsed : title_bar_is_highlight ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg);
6108 if (!(flags & ImGuiWindowFlags_NoTitleBar))
6109 window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, window_rounding, ImDrawCornerFlags_Top);
6110
6111 // Menu bar
6112 if (flags & ImGuiWindowFlags_MenuBar)
6113 {
6114 ImRect menu_bar_rect = window->MenuBarRect();
6115 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.
6116 window->DrawList->AddRectFilled(menu_bar_rect.Min, menu_bar_rect.Max, GetColorU32(ImGuiCol_MenuBarBg), (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f, ImDrawCornerFlags_Top);
6117 if (style.FrameBorderSize > 0.0f && menu_bar_rect.Max.y < window->Pos.y + window->Size.y)
6118 window->DrawList->AddLine(menu_bar_rect.GetBL(), menu_bar_rect.GetBR(), GetColorU32(ImGuiCol_Border), style.FrameBorderSize);
6119 }
6120
6121 // Scrollbars
6122 if (window->ScrollbarX)
6123 Scrollbar(ImGuiLayoutType_Horizontal);
6124 if (window->ScrollbarY)
6125 Scrollbar(ImGuiLayoutType_Vertical);
6126
6127 // Render resize grips (after their input handling so we don't have a frame of latency)
6128 if (!(flags & ImGuiWindowFlags_NoResize))
6129 {
6130 for (int resize_grip_n = 0; resize_grip_n < resize_grip_count; resize_grip_n++)
6131 {
6132 const ImGuiResizeGripDef& grip = resize_grip_def[resize_grip_n];
6133 const ImVec2 corner = ImLerp(window->Pos, window->Pos + window->Size, grip.CornerPos);
6134 window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(window_border_size, grip_draw_size) : ImVec2(grip_draw_size, window_border_size)));
6135 window->DrawList->PathLineTo(corner + grip.InnerDir * ((resize_grip_n & 1) ? ImVec2(grip_draw_size, window_border_size) : ImVec2(window_border_size, grip_draw_size)));
6136 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);
6137 window->DrawList->PathFillConvex(resize_grip_col[resize_grip_n]);
6138 }
6139 }
6140
6141 // Borders
6142 if (window_border_size > 0.0f)
6143 window->DrawList->AddRect(window->Pos, window->Pos + window->Size, GetColorU32(ImGuiCol_Border), window_rounding, ImDrawCornerFlags_All, window_border_size);
6144 if (border_held != -1)
6145 {
6146 ImRect border = GetBorderRect(window, border_held, grip_draw_size, 0.0f);
6147 window->DrawList->AddLine(border.Min, border.Max, GetColorU32(ImGuiCol_SeparatorActive), ImMax(1.0f, window_border_size));
6148 }
6149 if (style.FrameBorderSize > 0 && !(flags & ImGuiWindowFlags_NoTitleBar))
6150 window->DrawList->AddLine(title_bar_rect.GetBL() + ImVec2(style.WindowBorderSize, -1), title_bar_rect.GetBR() + ImVec2(-style.WindowBorderSize, -1), GetColorU32(ImGuiCol_Border), style.FrameBorderSize);
6151 }
6152
6153 // Draw navigation selection/windowing rectangle border
6154 if (g.NavWindowingTarget == window)
6155 {
6156 float rounding = ImMax(window->WindowRounding, g.Style.WindowRounding);
6157 ImRect bb = window->Rect();
6158 bb.Expand(g.FontSize);
6159 if (bb.Contains(viewport_rect)) // If a window fits the entire viewport, adjust its highlight inward
6160 {
6161 bb.Expand(-g.FontSize - 1.0f);
6162 rounding = window->WindowRounding;
6163 }
6164 window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_NavWindowingHighlight, g.NavWindowingHighlightAlpha), rounding, ~0, 3.0f);
6165 }
6166
6167 // Store a backup of SizeFull which we will use next frame to decide if we need scrollbars.
6168 window->SizeFullAtLastBegin = window->SizeFull;
6169
6170 // Update ContentsRegionMax. All the variable it depends on are set above in this function.
6171 window->ContentsRegionRect.Min.x = -window->Scroll.x + window->WindowPadding.x;
6172 window->ContentsRegionRect.Min.y = -window->Scroll.y + window->WindowPadding.y + window->TitleBarHeight() + window->MenuBarHeight();
6173 window->ContentsRegionRect.Max.x = -window->Scroll.x - window->WindowPadding.x + (window->SizeContentsExplicit.x != 0.0f ? window->SizeContentsExplicit.x : (window->Size.x - window->ScrollbarSizes.x));
6174 window->ContentsRegionRect.Max.y = -window->Scroll.y - window->WindowPadding.y + (window->SizeContentsExplicit.y != 0.0f ? window->SizeContentsExplicit.y : (window->Size.y - window->ScrollbarSizes.y));
6175
6176 // Setup drawing context
6177 // (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.)
6178 window->DC.IndentX = 0.0f + window->WindowPadding.x - window->Scroll.x;
6179 window->DC.GroupOffsetX = 0.0f;
6180 window->DC.ColumnsOffsetX = 0.0f;
6181 window->DC.CursorStartPos = window->Pos + ImVec2(window->DC.IndentX + window->DC.ColumnsOffsetX, window->TitleBarHeight() + window->MenuBarHeight() + window->WindowPadding.y - window->Scroll.y);
6182 window->DC.CursorPos = window->DC.CursorStartPos;
6183 window->DC.CursorPosPrevLine = window->DC.CursorPos;
6184 window->DC.CursorMaxPos = window->DC.CursorStartPos;
6185 window->DC.CurrentLineHeight = window->DC.PrevLineHeight = 0.0f;
6186 window->DC.CurrentLineTextBaseOffset = window->DC.PrevLineTextBaseOffset = 0.0f;
6187 window->DC.NavHideHighlightOneFrame = false;
6188 window->DC.NavHasScroll = (GetScrollMaxY() > 0.0f);
6189 window->DC.NavLayerActiveMask = window->DC.NavLayerActiveMaskNext;
6190 window->DC.NavLayerActiveMaskNext = 0x00;
6191 window->DC.MenuBarAppending = false;
6192 window->DC.MenuBarOffsetX = ImMax(window->WindowPadding.x, style.ItemSpacing.x);
6193 window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f;
6194 window->DC.ChildWindows.resize(0);
6195 window->DC.LayoutType = ImGuiLayoutType_Vertical;
6196 window->DC.ParentLayoutType = parent_window ? parent_window->DC.LayoutType : ImGuiLayoutType_Vertical;
6197 window->DC.ItemFlags = ImGuiItemFlags_Default_;
6198 window->DC.ItemWidth = window->ItemWidthDefault;
6199 window->DC.TextWrapPos = -1.0f; // disabled
6200 window->DC.ItemFlagsStack.resize(0);
6201 window->DC.ItemWidthStack.resize(0);
6202 window->DC.TextWrapPosStack.resize(0);
6203 window->DC.ColumnsSet = NULL;
6204 window->DC.TreeDepth = 0;
6205 window->DC.TreeDepthMayCloseOnPop = 0x00;
6206 window->DC.StateStorage = &window->StateStorage;
6207 window->DC.GroupStack.resize(0);
6208 window->MenuColumns.Update(3, style.ItemSpacing.x, window_just_activated_by_user);
6209
6210 if ((flags & ImGuiWindowFlags_ChildWindow) && (window->DC.ItemFlags != parent_window->DC.ItemFlags))
6211 {
6212 window->DC.ItemFlags = parent_window->DC.ItemFlags;
6213 window->DC.ItemFlagsStack.push_back(window->DC.ItemFlags);
6214 }
6215
6216 if (window->AutoFitFramesX > 0)
6217 window->AutoFitFramesX--;
6218 if (window->AutoFitFramesY > 0)
6219 window->AutoFitFramesY--;
6220
6221 // Apply focus (we need to call FocusWindow() AFTER setting DC.CursorStartPos so our initial navigation reference rectangle can start around there)
6222 if (want_focus)
6223 {
6224 FocusWindow(window);
6225 NavInitWindow(window, false);
6226 }
6227
6228 // Title bar
6229 if (!(flags & ImGuiWindowFlags_NoTitleBar))
6230 {
6231 // Close & collapse button are on layer 1 (same as menus) and don't default focus
6232 const ImGuiItemFlags item_flags_backup = window->DC.ItemFlags;
6233 window->DC.ItemFlags |= ImGuiItemFlags_NoNavDefaultFocus;
6234 window->DC.NavLayerCurrent++;
6235 window->DC.NavLayerCurrentMask <<= 1;
6236
6237 // Collapse button
6238 if (!(flags & ImGuiWindowFlags_NoCollapse))
6239 {
6240 ImGuiID id = window->GetID("#COLLAPSE");
6241 ImRect bb(window->Pos + style.FramePadding + ImVec2(1, 1), window->Pos + style.FramePadding + ImVec2(g.FontSize, g.FontSize) - ImVec2(1, 1));
6242 ItemAdd(bb, id); // To allow navigation
6243 if (ButtonBehavior(bb, id, NULL, NULL))
6244 window->CollapseToggleWanted = true; // Defer collapsing to next frame as we are too far in the Begin() function
6245 RenderNavHighlight(bb, id);
6246 RenderTriangle(window->Pos + style.FramePadding, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f);
6247 }
6248
6249 // Close button
6250 if (p_open != NULL)
6251 {
6252 const float PAD = 2.0f;
6253 const float rad = (window->TitleBarHeight() - PAD * 2.0f) * 0.5f;
6254 if (CloseButton(window->GetID("#CLOSE"), window->Rect().GetTR() + ImVec2(-PAD - rad, PAD + rad), rad))
6255 *p_open = false;
6256 }
6257
6258 window->DC.NavLayerCurrent--;
6259 window->DC.NavLayerCurrentMask >>= 1;
6260 window->DC.ItemFlags = item_flags_backup;
6261
6262 // Title text (FIXME: refactor text alignment facilities along with RenderText helpers)
6263 ImVec2 text_size = CalcTextSize(name, NULL, true);
6264 ImRect text_r = title_bar_rect;
6265 float pad_left = (flags & ImGuiWindowFlags_NoCollapse) == 0 ? (style.FramePadding.x + g.FontSize + style.ItemInnerSpacing.x) : style.FramePadding.x;
6266 float pad_right = (p_open != NULL) ? (style.FramePadding.x + g.FontSize + style.ItemInnerSpacing.x) : style.FramePadding.x;
6267 if (style.WindowTitleAlign.x > 0.0f) pad_right = ImLerp(pad_right, pad_left, style.WindowTitleAlign.x);
6268 text_r.Min.x += pad_left;
6269 text_r.Max.x -= pad_right;
6270 ImRect clip_rect = text_r;
6271 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()
6272 RenderTextClipped(text_r.Min, text_r.Max, name, NULL, &text_size, style.WindowTitleAlign, &clip_rect);
6273 }
6274
6275 // Save clipped aabb so we can access it in constant-time in FindHoveredWindow()
6276 window->WindowRectClipped = window->Rect();
6277 window->WindowRectClipped.ClipWith(window->ClipRect);
6278
6279 // Pressing CTRL+C while holding on a window copy its content to the clipboard
6280 // 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.
6281 // Maybe we can support CTRL+C on every element?
6282 /*
6283 if (g.ActiveId == move_id)
6284 if (g.IO.KeyCtrl && IsKeyPressedMap(ImGuiKey_C))
6285 ImGui::LogToClipboard();
6286 */
6287
6288 // Inner rectangle
6289 // We set this up after processing the resize grip so that our clip rectangle doesn't lag by a frame
6290 // Note that if our window is collapsed we will end up with a null clipping rectangle which is the correct behavior.
6291 window->InnerRect.Min.x = title_bar_rect.Min.x + window->WindowBorderSize;
6292 window->InnerRect.Min.y = title_bar_rect.Max.y + window->MenuBarHeight() + (((flags & ImGuiWindowFlags_MenuBar) || !(flags & ImGuiWindowFlags_NoTitleBar)) ? style.FrameBorderSize : window->WindowBorderSize);
6293 window->InnerRect.Max.x = window->Pos.x + window->Size.x - window->ScrollbarSizes.x - window->WindowBorderSize;
6294 window->InnerRect.Max.y = window->Pos.y + window->Size.y - window->ScrollbarSizes.y - window->WindowBorderSize;
6295 //window->DrawList->AddRect(window->InnerRect.Min, window->InnerRect.Max, IM_COL32_WHITE);
6296
6297 // After Begin() we fill the last item / hovered data using the title bar data. Make that a standard behavior (to allow usage of context menus on title bar only, etc.).
6298 window->DC.LastItemId = window->MoveId;
6299 window->DC.LastItemStatusFlags = IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0;
6300 window->DC.LastItemRect = title_bar_rect;
6301 }
6302
6303 // Inner clipping rectangle
6304 // Force round operator last to ensure that e.g. (int)(max.x-min.x) in user's render code produce correct result.
6305 const float border_size = window->WindowBorderSize;
6306 ImRect clip_rect;
6307 clip_rect.Min.x = ImFloor(0.5f + window->InnerRect.Min.x + ImMax(0.0f, ImFloor(window->WindowPadding.x * 0.5f - border_size)));
6308 clip_rect.Min.y = ImFloor(0.5f + window->InnerRect.Min.y);
6309 clip_rect.Max.x = ImFloor(0.5f + window->InnerRect.Max.x - ImMax(0.0f, ImFloor(window->WindowPadding.x * 0.5f - border_size)));
6310 clip_rect.Max.y = ImFloor(0.5f + window->InnerRect.Max.y);
6311 PushClipRect(clip_rect.Min, clip_rect.Max, true);
6312
6313 // 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)
6314 if (first_begin_of_the_frame)
6315 window->WriteAccessed = false;
6316
6317 window->BeginCount++;
6318 g.NextWindowData.SizeConstraintCond = 0;
6319
6320 // Child window can be out of sight and have "negative" clip windows.
6321 // Mark them as collapsed so commands are skipped earlier (we can't manually collapse because they have no title bar).
6322 if (flags & ImGuiWindowFlags_ChildWindow)
6323 {
6324 IM_ASSERT((flags & ImGuiWindowFlags_NoTitleBar) != 0);
6325 window->Collapsed = parent_window && parent_window->Collapsed;
6326
6327 if (!(flags & ImGuiWindowFlags_AlwaysAutoResize) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0)
6328 window->Collapsed |= (window->WindowRectClipped.Min.x >= window->WindowRectClipped.Max.x || window->WindowRectClipped.Min.y >= window->WindowRectClipped.Max.y);
6329
6330 // We also hide the window from rendering because we've already added its border to the command list.
6331 // (we could perform the check earlier in the function but it is simpler at this point)
6332 if (window->Collapsed)
6333 window->Active = false;
6334 }
6335 if (style.Alpha <= 0.0f)
6336 window->Active = false;
6337
6338 // Return false if we don't intend to display anything to allow user to perform an early out optimization
6339 window->SkipItems = (window->Collapsed || !window->Active) && window->AutoFitFramesX <= 0 && window->AutoFitFramesY <= 0;
6340 return !window->SkipItems;
6341 }
6342
6343 // Old Begin() API with 5 parameters, avoid calling this version directly! Use SetNextWindowSize()/SetNextWindowBgAlpha() + Begin() instead.
6344 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
Begin(const char * name,bool * p_open,const ImVec2 & size_first_use,float bg_alpha_override,ImGuiWindowFlags flags)6345 bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_first_use, float bg_alpha_override, ImGuiWindowFlags flags)
6346 {
6347 // 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.
6348 if (size_first_use.x != 0.0f || size_first_use.y != 0.0f)
6349 ImGui::SetNextWindowSize(size_first_use, ImGuiCond_FirstUseEver);
6350
6351 // Old API feature: override the window background alpha with a parameter.
6352 if (bg_alpha_override >= 0.0f)
6353 ImGui::SetNextWindowBgAlpha(bg_alpha_override);
6354
6355 return ImGui::Begin(name, p_open, flags);
6356 }
6357 #endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS
6358
End()6359 void ImGui::End()
6360 {
6361 ImGuiContext& g = *GImGui;
6362 ImGuiWindow* window = g.CurrentWindow;
6363
6364 if (window->DC.ColumnsSet != NULL)
6365 EndColumns();
6366 PopClipRect(); // Inner window clip rectangle
6367
6368 // Stop logging
6369 if (!(window->Flags & ImGuiWindowFlags_ChildWindow)) // FIXME: add more options for scope of logging
6370 LogFinish();
6371
6372 // Pop from window stack
6373 g.CurrentWindowStack.pop_back();
6374 if (window->Flags & ImGuiWindowFlags_Popup)
6375 g.CurrentPopupStack.pop_back();
6376 CheckStacksSize(window, false);
6377 SetCurrentWindow(g.CurrentWindowStack.empty() ? NULL : g.CurrentWindowStack.back());
6378 }
6379
6380 // Vertical scrollbar
6381 // The entire piece of code below is rather confusing because:
6382 // - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab)
6383 // - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar
6384 // - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal.
Scrollbar(ImGuiLayoutType direction)6385 void ImGui::Scrollbar(ImGuiLayoutType direction)
6386 {
6387 ImGuiContext& g = *GImGui;
6388 ImGuiWindow* window = g.CurrentWindow;
6389
6390 const bool horizontal = (direction == ImGuiLayoutType_Horizontal);
6391 const ImGuiStyle& style = g.Style;
6392 const ImGuiID id = window->GetID(horizontal ? "#SCROLLX" : "#SCROLLY");
6393
6394 // Render background
6395 bool other_scrollbar = (horizontal ? window->ScrollbarY : window->ScrollbarX);
6396 float other_scrollbar_size_w = other_scrollbar ? style.ScrollbarSize : 0.0f;
6397 const ImRect window_rect = window->Rect();
6398 const float border_size = window->WindowBorderSize;
6399 ImRect bb = horizontal
6400 ? 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)
6401 : 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);
6402 if (!horizontal)
6403 bb.Min.y += window->TitleBarHeight() + ((window->Flags & ImGuiWindowFlags_MenuBar) ? window->MenuBarHeight() : 0.0f);
6404 if (bb.GetWidth() <= 0.0f || bb.GetHeight() <= 0.0f)
6405 return;
6406
6407 int window_rounding_corners;
6408 if (horizontal)
6409 window_rounding_corners = ImDrawCornerFlags_BotLeft | (other_scrollbar ? 0 : ImDrawCornerFlags_BotRight);
6410 else
6411 window_rounding_corners = (((window->Flags & ImGuiWindowFlags_NoTitleBar) && !(window->Flags & ImGuiWindowFlags_MenuBar)) ? ImDrawCornerFlags_TopRight : 0) | (other_scrollbar ? 0 : ImDrawCornerFlags_BotRight);
6412 window->DrawList->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_ScrollbarBg), window->WindowRounding, window_rounding_corners);
6413 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)));
6414
6415 // V denote the main, longer axis of the scrollbar (= height for a vertical scrollbar)
6416 float scrollbar_size_v = horizontal ? bb.GetWidth() : bb.GetHeight();
6417 float scroll_v = horizontal ? window->Scroll.x : window->Scroll.y;
6418 float win_size_avail_v = (horizontal ? window->SizeFull.x : window->SizeFull.y) - other_scrollbar_size_w;
6419 float win_size_contents_v = horizontal ? window->SizeContents.x : window->SizeContents.y;
6420
6421 // Calculate the height of our grabbable box. It generally represent the amount visible (vs the total scrollable amount)
6422 // But we maintain a minimum size in pixel to allow for the user to still aim inside.
6423 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.
6424 const float win_size_v = ImMax(ImMax(win_size_contents_v, win_size_avail_v), 1.0f);
6425 const float grab_h_pixels = ImClamp(scrollbar_size_v * (win_size_avail_v / win_size_v), style.GrabMinSize, scrollbar_size_v);
6426 const float grab_h_norm = grab_h_pixels / scrollbar_size_v;
6427
6428 // Handle input right away. None of the code of Begin() is relying on scrolling position before calling Scrollbar().
6429 bool held = false;
6430 bool hovered = false;
6431 const bool previously_held = (g.ActiveId == id);
6432 ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_NoNavFocus);
6433
6434 float scroll_max = ImMax(1.0f, win_size_contents_v - win_size_avail_v);
6435 float scroll_ratio = ImSaturate(scroll_v / scroll_max);
6436 float grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;
6437 if (held && grab_h_norm < 1.0f)
6438 {
6439 float scrollbar_pos_v = horizontal ? bb.Min.x : bb.Min.y;
6440 float mouse_pos_v = horizontal ? g.IO.MousePos.x : g.IO.MousePos.y;
6441 float* click_delta_to_grab_center_v = horizontal ? &g.ScrollbarClickDeltaToGrabCenter.x : &g.ScrollbarClickDeltaToGrabCenter.y;
6442
6443 // Click position in scrollbar normalized space (0.0f->1.0f)
6444 const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v);
6445 SetHoveredID(id);
6446
6447 bool seek_absolute = false;
6448 if (!previously_held)
6449 {
6450 // On initial click calculate the distance between mouse and the center of the grab
6451 if (clicked_v_norm >= grab_v_norm && clicked_v_norm <= grab_v_norm + grab_h_norm)
6452 {
6453 *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f;
6454 }
6455 else
6456 {
6457 seek_absolute = true;
6458 *click_delta_to_grab_center_v = 0.0f;
6459 }
6460 }
6461
6462 // Apply scroll
6463 // 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
6464 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));
6465 scroll_v = (float)(int)(0.5f + scroll_v_norm * scroll_max); //(win_size_contents_v - win_size_v));
6466 if (horizontal)
6467 window->Scroll.x = scroll_v;
6468 else
6469 window->Scroll.y = scroll_v;
6470
6471 // Update values for rendering
6472 scroll_ratio = ImSaturate(scroll_v / scroll_max);
6473 grab_v_norm = scroll_ratio * (scrollbar_size_v - grab_h_pixels) / scrollbar_size_v;
6474
6475 // Update distance to grab now that we have seeked and saturated
6476 if (seek_absolute)
6477 *click_delta_to_grab_center_v = clicked_v_norm - grab_v_norm - grab_h_norm * 0.5f;
6478 }
6479
6480 // Render
6481 const ImU32 grab_col = GetColorU32(held ? ImGuiCol_ScrollbarGrabActive : hovered ? ImGuiCol_ScrollbarGrabHovered : ImGuiCol_ScrollbarGrab);
6482 ImRect grab_rect;
6483 if (horizontal)
6484 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);
6485 else
6486 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));
6487 window->DrawList->AddRectFilled(grab_rect.Min, grab_rect.Max, grab_col, style.ScrollbarRounding);
6488 }
6489
BringWindowToFront(ImGuiWindow * window)6490 void ImGui::BringWindowToFront(ImGuiWindow* window)
6491 {
6492 ImGuiContext& g = *GImGui;
6493 ImGuiWindow* current_front_window = g.Windows.back();
6494 if (current_front_window == window || current_front_window->RootWindow == window)
6495 return;
6496 for (int i = g.Windows.Size - 2; i >= 0; i--) // We can ignore the front most window
6497 if (g.Windows[i] == window)
6498 {
6499 g.Windows.erase(g.Windows.Data + i);
6500 g.Windows.push_back(window);
6501 break;
6502 }
6503 }
6504
BringWindowToBack(ImGuiWindow * window)6505 void ImGui::BringWindowToBack(ImGuiWindow* window)
6506 {
6507 ImGuiContext& g = *GImGui;
6508 if (g.Windows[0] == window)
6509 return;
6510 for (int i = 0; i < g.Windows.Size; i++)
6511 if (g.Windows[i] == window)
6512 {
6513 memmove(&g.Windows[1], &g.Windows[0], (size_t)i * sizeof(ImGuiWindow*));
6514 g.Windows[0] = window;
6515 break;
6516 }
6517 }
6518
6519 // Moving window to front of display and set focus (which happens to be back of our sorted list)
FocusWindow(ImGuiWindow * window)6520 void ImGui::FocusWindow(ImGuiWindow* window)
6521 {
6522 ImGuiContext& g = *GImGui;
6523
6524 if (g.NavWindow != window)
6525 {
6526 g.NavWindow = window;
6527 if (window && g.NavDisableMouseHover)
6528 g.NavMousePosDirty = true;
6529 g.NavInitRequest = false;
6530 g.NavId = window ? window->NavLastIds[0] : 0; // Restore NavId
6531 g.NavIdIsAlive = false;
6532 g.NavLayer = 0;
6533 }
6534
6535 // Passing NULL allow to disable keyboard focus
6536 if (!window)
6537 return;
6538
6539 // Move the root window to the top of the pile
6540 if (window->RootWindow)
6541 window = window->RootWindow;
6542
6543 // Steal focus on active widgets
6544 if (window->Flags & ImGuiWindowFlags_Popup) // FIXME: This statement should be unnecessary. Need further testing before removing it..
6545 if (g.ActiveId != 0 && g.ActiveIdWindow && g.ActiveIdWindow->RootWindow != window)
6546 ClearActiveID();
6547
6548 // Bring to front
6549 if (!(window->Flags & ImGuiWindowFlags_NoBringToFrontOnFocus))
6550 BringWindowToFront(window);
6551 }
6552
FocusFrontMostActiveWindow(ImGuiWindow * ignore_window)6553 void ImGui::FocusFrontMostActiveWindow(ImGuiWindow* ignore_window)
6554 {
6555 ImGuiContext& g = *GImGui;
6556 for (int i = g.Windows.Size - 1; i >= 0; i--)
6557 if (g.Windows[i] != ignore_window && g.Windows[i]->WasActive && !(g.Windows[i]->Flags & ImGuiWindowFlags_ChildWindow))
6558 {
6559 ImGuiWindow* focus_window = NavRestoreLastChildNavWindow(g.Windows[i]);
6560 FocusWindow(focus_window);
6561 return;
6562 }
6563 }
6564
PushItemWidth(float item_width)6565 void ImGui::PushItemWidth(float item_width)
6566 {
6567 ImGuiWindow* window = GetCurrentWindow();
6568 window->DC.ItemWidth = (item_width == 0.0f ? window->ItemWidthDefault : item_width);
6569 window->DC.ItemWidthStack.push_back(window->DC.ItemWidth);
6570 }
6571
PushMultiItemsWidths(int components,float w_full)6572 void ImGui::PushMultiItemsWidths(int components, float w_full)
6573 {
6574 ImGuiWindow* window = GetCurrentWindow();
6575 const ImGuiStyle& style = GImGui->Style;
6576 if (w_full <= 0.0f)
6577 w_full = CalcItemWidth();
6578 const float w_item_one = ImMax(1.0f, (float)(int)((w_full - (style.ItemInnerSpacing.x) * (components - 1)) / (float)components));
6579 const float w_item_last = ImMax(1.0f, (float)(int)(w_full - (w_item_one + style.ItemInnerSpacing.x) * (components - 1)));
6580 window->DC.ItemWidthStack.push_back(w_item_last);
6581 for (int i = 0; i < components - 1; i++)
6582 window->DC.ItemWidthStack.push_back(w_item_one);
6583 window->DC.ItemWidth = window->DC.ItemWidthStack.back();
6584 }
6585
PopItemWidth()6586 void ImGui::PopItemWidth()
6587 {
6588 ImGuiWindow* window = GetCurrentWindow();
6589 window->DC.ItemWidthStack.pop_back();
6590 window->DC.ItemWidth = window->DC.ItemWidthStack.empty() ? window->ItemWidthDefault : window->DC.ItemWidthStack.back();
6591 }
6592
CalcItemWidth()6593 float ImGui::CalcItemWidth()
6594 {
6595 ImGuiWindow* window = GetCurrentWindowRead();
6596 float w = window->DC.ItemWidth;
6597 if (w < 0.0f)
6598 {
6599 // 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.
6600 float width_to_right_edge = GetContentRegionAvail().x;
6601 w = ImMax(1.0f, width_to_right_edge + w);
6602 }
6603 w = (float)(int)w;
6604 return w;
6605 }
6606
GetDefaultFont()6607 static ImFont* GetDefaultFont()
6608 {
6609 ImGuiContext& g = *GImGui;
6610 return g.IO.FontDefault ? g.IO.FontDefault : g.IO.Fonts->Fonts[0];
6611 }
6612
SetCurrentFont(ImFont * font)6613 void ImGui::SetCurrentFont(ImFont* font)
6614 {
6615 ImGuiContext& g = *GImGui;
6616 IM_ASSERT(font && font->IsLoaded()); // Font Atlas not created. Did you call io.Fonts->GetTexDataAsRGBA32 / GetTexDataAsAlpha8 ?
6617 IM_ASSERT(font->Scale > 0.0f);
6618 g.Font = font;
6619 g.FontBaseSize = g.IO.FontGlobalScale * g.Font->FontSize * g.Font->Scale;
6620 g.FontSize = g.CurrentWindow ? g.CurrentWindow->CalcFontSize() : 0.0f;
6621
6622 ImFontAtlas* atlas = g.Font->ContainerAtlas;
6623 g.DrawListSharedData.TexUvWhitePixel = atlas->TexUvWhitePixel;
6624 g.DrawListSharedData.Font = g.Font;
6625 g.DrawListSharedData.FontSize = g.FontSize;
6626 }
6627
PushFont(ImFont * font)6628 void ImGui::PushFont(ImFont* font)
6629 {
6630 ImGuiContext& g = *GImGui;
6631 if (!font)
6632 font = GetDefaultFont();
6633 SetCurrentFont(font);
6634 g.FontStack.push_back(font);
6635 g.CurrentWindow->DrawList->PushTextureID(font->ContainerAtlas->TexID);
6636 }
6637
PopFont()6638 void ImGui::PopFont()
6639 {
6640 ImGuiContext& g = *GImGui;
6641 g.CurrentWindow->DrawList->PopTextureID();
6642 g.FontStack.pop_back();
6643 SetCurrentFont(g.FontStack.empty() ? GetDefaultFont() : g.FontStack.back());
6644 }
6645
PushItemFlag(ImGuiItemFlags option,bool enabled)6646 void ImGui::PushItemFlag(ImGuiItemFlags option, bool enabled)
6647 {
6648 ImGuiWindow* window = GetCurrentWindow();
6649 if (enabled)
6650 window->DC.ItemFlags |= option;
6651 else
6652 window->DC.ItemFlags &= ~option;
6653 window->DC.ItemFlagsStack.push_back(window->DC.ItemFlags);
6654 }
6655
PopItemFlag()6656 void ImGui::PopItemFlag()
6657 {
6658 ImGuiWindow* window = GetCurrentWindow();
6659 window->DC.ItemFlagsStack.pop_back();
6660 window->DC.ItemFlags = window->DC.ItemFlagsStack.empty() ? ImGuiItemFlags_Default_ : window->DC.ItemFlagsStack.back();
6661 }
6662
PushAllowKeyboardFocus(bool allow_keyboard_focus)6663 void ImGui::PushAllowKeyboardFocus(bool allow_keyboard_focus)
6664 {
6665 PushItemFlag(ImGuiItemFlags_AllowKeyboardFocus, allow_keyboard_focus);
6666 }
6667
PopAllowKeyboardFocus()6668 void ImGui::PopAllowKeyboardFocus()
6669 {
6670 PopItemFlag();
6671 }
6672
PushButtonRepeat(bool repeat)6673 void ImGui::PushButtonRepeat(bool repeat)
6674 {
6675 PushItemFlag(ImGuiItemFlags_ButtonRepeat, repeat);
6676 }
6677
PopButtonRepeat()6678 void ImGui::PopButtonRepeat()
6679 {
6680 PopItemFlag();
6681 }
6682
PushTextWrapPos(float wrap_pos_x)6683 void ImGui::PushTextWrapPos(float wrap_pos_x)
6684 {
6685 ImGuiWindow* window = GetCurrentWindow();
6686 window->DC.TextWrapPos = wrap_pos_x;
6687 window->DC.TextWrapPosStack.push_back(wrap_pos_x);
6688 }
6689
PopTextWrapPos()6690 void ImGui::PopTextWrapPos()
6691 {
6692 ImGuiWindow* window = GetCurrentWindow();
6693 window->DC.TextWrapPosStack.pop_back();
6694 window->DC.TextWrapPos = window->DC.TextWrapPosStack.empty() ? -1.0f : window->DC.TextWrapPosStack.back();
6695 }
6696
6697 // FIXME: This may incur a round-trip (if the end user got their data from a float4) but eventually we aim to store the in-flight colors as ImU32
PushStyleColor(ImGuiCol idx,ImU32 col)6698 void ImGui::PushStyleColor(ImGuiCol idx, ImU32 col)
6699 {
6700 ImGuiContext& g = *GImGui;
6701 ImGuiColMod backup;
6702 backup.Col = idx;
6703 backup.BackupValue = g.Style.Colors[idx];
6704 g.ColorModifiers.push_back(backup);
6705 g.Style.Colors[idx] = ColorConvertU32ToFloat4(col);
6706 }
6707
PushStyleColor(ImGuiCol idx,const ImVec4 & col)6708 void ImGui::PushStyleColor(ImGuiCol idx, const ImVec4& col)
6709 {
6710 ImGuiContext& g = *GImGui;
6711 ImGuiColMod backup;
6712 backup.Col = idx;
6713 backup.BackupValue = g.Style.Colors[idx];
6714 g.ColorModifiers.push_back(backup);
6715 g.Style.Colors[idx] = col;
6716 }
6717
PopStyleColor(int count)6718 void ImGui::PopStyleColor(int count)
6719 {
6720 ImGuiContext& g = *GImGui;
6721 while (count > 0)
6722 {
6723 ImGuiColMod& backup = g.ColorModifiers.back();
6724 g.Style.Colors[backup.Col] = backup.BackupValue;
6725 g.ColorModifiers.pop_back();
6726 count--;
6727 }
6728 }
6729
6730 struct ImGuiStyleVarInfo
6731 {
6732 ImGuiDataType Type;
6733 ImU32 Offset;
GetVarPtrImGuiStyleVarInfo6734 void* GetVarPtr(ImGuiStyle* style) const { return (void*)((unsigned char*)style + Offset); }
6735 };
6736
6737 static const ImGuiStyleVarInfo GStyleVarInfo[] =
6738 {
6739 {ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, Alpha)}, // ImGuiStyleVar_Alpha
6740 {ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowPadding)}, // ImGuiStyleVar_WindowPadding
6741 {ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowRounding)}, // ImGuiStyleVar_WindowRounding
6742 {ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowBorderSize)}, // ImGuiStyleVar_WindowBorderSize
6743 {ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowMinSize)}, // ImGuiStyleVar_WindowMinSize
6744 {ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, WindowTitleAlign)}, // ImGuiStyleVar_WindowTitleAlign
6745 {ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, ChildRounding)}, // ImGuiStyleVar_ChildRounding
6746 {ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, ChildBorderSize)}, // ImGuiStyleVar_ChildBorderSize
6747 {ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, PopupRounding)}, // ImGuiStyleVar_PopupRounding
6748 {ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, PopupBorderSize)}, // ImGuiStyleVar_PopupBorderSize
6749 {ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, FramePadding)}, // ImGuiStyleVar_FramePadding
6750 {ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, FrameRounding)}, // ImGuiStyleVar_FrameRounding
6751 {ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, FrameBorderSize)}, // ImGuiStyleVar_FrameBorderSize
6752 {ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemSpacing)}, // ImGuiStyleVar_ItemSpacing
6753 {ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, ItemInnerSpacing)}, // ImGuiStyleVar_ItemInnerSpacing
6754 {ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, IndentSpacing)}, // ImGuiStyleVar_IndentSpacing
6755 {ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, ScrollbarSize)}, // ImGuiStyleVar_ScrollbarSize
6756 {ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, ScrollbarRounding)}, // ImGuiStyleVar_ScrollbarRounding
6757 {ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, GrabMinSize)}, // ImGuiStyleVar_GrabMinSize
6758 {ImGuiDataType_Float, (ImU32)IM_OFFSETOF(ImGuiStyle, GrabRounding)}, // ImGuiStyleVar_GrabRounding
6759 {ImGuiDataType_Float2, (ImU32)IM_OFFSETOF(ImGuiStyle, ButtonTextAlign)}, // ImGuiStyleVar_ButtonTextAlign
6760 };
6761
GetStyleVarInfo(ImGuiStyleVar idx)6762 static const ImGuiStyleVarInfo* GetStyleVarInfo(ImGuiStyleVar idx)
6763 {
6764 IM_ASSERT(idx >= 0 && idx < ImGuiStyleVar_Count_);
6765 IM_ASSERT(IM_ARRAYSIZE(GStyleVarInfo) == ImGuiStyleVar_Count_);
6766 return &GStyleVarInfo[idx];
6767 }
6768
PushStyleVar(ImGuiStyleVar idx,float val)6769 void ImGui::PushStyleVar(ImGuiStyleVar idx, float val)
6770 {
6771 const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx);
6772 if (var_info->Type == ImGuiDataType_Float)
6773 {
6774 ImGuiContext& g = *GImGui;
6775 float* pvar = (float*)var_info->GetVarPtr(&g.Style);
6776 g.StyleModifiers.push_back(ImGuiStyleMod(idx, *pvar));
6777 *pvar = val;
6778 return;
6779 }
6780 IM_ASSERT(0); // Called function with wrong-type? Variable is not a float.
6781 }
6782
PushStyleVar(ImGuiStyleVar idx,const ImVec2 & val)6783 void ImGui::PushStyleVar(ImGuiStyleVar idx, const ImVec2& val)
6784 {
6785 const ImGuiStyleVarInfo* var_info = GetStyleVarInfo(idx);
6786 if (var_info->Type == ImGuiDataType_Float2)
6787 {
6788 ImGuiContext& g = *GImGui;
6789 ImVec2* pvar = (ImVec2*)var_info->GetVarPtr(&g.Style);
6790 g.StyleModifiers.push_back(ImGuiStyleMod(idx, *pvar));
6791 *pvar = val;
6792 return;
6793 }
6794 IM_ASSERT(0); // Called function with wrong-type? Variable is not a ImVec2.
6795 }
6796
PopStyleVar(int count)6797 void ImGui::PopStyleVar(int count)
6798 {
6799 ImGuiContext& g = *GImGui;
6800 while (count > 0)
6801 {
6802 ImGuiStyleMod& backup = g.StyleModifiers.back();
6803 const ImGuiStyleVarInfo* info = GetStyleVarInfo(backup.VarIdx);
6804 if (info->Type == ImGuiDataType_Float)
6805 (*(float*)info->GetVarPtr(&g.Style)) = backup.BackupFloat[0];
6806 else if (info->Type == ImGuiDataType_Float2)
6807 (*(ImVec2*)info->GetVarPtr(&g.Style)) = ImVec2(backup.BackupFloat[0], backup.BackupFloat[1]);
6808 else if (info->Type == ImGuiDataType_Int)
6809 (*(int*)info->GetVarPtr(&g.Style)) = backup.BackupInt[0];
6810 g.StyleModifiers.pop_back();
6811 count--;
6812 }
6813 }
6814
GetStyleColorName(ImGuiCol idx)6815 const char* ImGui::GetStyleColorName(ImGuiCol idx)
6816 {
6817 // Create switch-case from enum with regexp: ImGuiCol_{.*}, --> case ImGuiCol_\1: return "\1";
6818 switch (idx)
6819 {
6820 case ImGuiCol_Text:
6821 return "Text";
6822 case ImGuiCol_TextDisabled:
6823 return "TextDisabled";
6824 case ImGuiCol_WindowBg:
6825 return "WindowBg";
6826 case ImGuiCol_ChildBg:
6827 return "ChildBg";
6828 case ImGuiCol_PopupBg:
6829 return "PopupBg";
6830 case ImGuiCol_Border:
6831 return "Border";
6832 case ImGuiCol_BorderShadow:
6833 return "BorderShadow";
6834 case ImGuiCol_FrameBg:
6835 return "FrameBg";
6836 case ImGuiCol_FrameBgHovered:
6837 return "FrameBgHovered";
6838 case ImGuiCol_FrameBgActive:
6839 return "FrameBgActive";
6840 case ImGuiCol_TitleBg:
6841 return "TitleBg";
6842 case ImGuiCol_TitleBgActive:
6843 return "TitleBgActive";
6844 case ImGuiCol_TitleBgCollapsed:
6845 return "TitleBgCollapsed";
6846 case ImGuiCol_MenuBarBg:
6847 return "MenuBarBg";
6848 case ImGuiCol_ScrollbarBg:
6849 return "ScrollbarBg";
6850 case ImGuiCol_ScrollbarGrab:
6851 return "ScrollbarGrab";
6852 case ImGuiCol_ScrollbarGrabHovered:
6853 return "ScrollbarGrabHovered";
6854 case ImGuiCol_ScrollbarGrabActive:
6855 return "ScrollbarGrabActive";
6856 case ImGuiCol_CheckMark:
6857 return "CheckMark";
6858 case ImGuiCol_SliderGrab:
6859 return "SliderGrab";
6860 case ImGuiCol_SliderGrabActive:
6861 return "SliderGrabActive";
6862 case ImGuiCol_Button:
6863 return "Button";
6864 case ImGuiCol_ButtonHovered:
6865 return "ButtonHovered";
6866 case ImGuiCol_ButtonActive:
6867 return "ButtonActive";
6868 case ImGuiCol_Header:
6869 return "Header";
6870 case ImGuiCol_HeaderHovered:
6871 return "HeaderHovered";
6872 case ImGuiCol_HeaderActive:
6873 return "HeaderActive";
6874 case ImGuiCol_Separator:
6875 return "Separator";
6876 case ImGuiCol_SeparatorHovered:
6877 return "SeparatorHovered";
6878 case ImGuiCol_SeparatorActive:
6879 return "SeparatorActive";
6880 case ImGuiCol_ResizeGrip:
6881 return "ResizeGrip";
6882 case ImGuiCol_ResizeGripHovered:
6883 return "ResizeGripHovered";
6884 case ImGuiCol_ResizeGripActive:
6885 return "ResizeGripActive";
6886 case ImGuiCol_CloseButton:
6887 return "CloseButton";
6888 case ImGuiCol_CloseButtonHovered:
6889 return "CloseButtonHovered";
6890 case ImGuiCol_CloseButtonActive:
6891 return "CloseButtonActive";
6892 case ImGuiCol_PlotLines:
6893 return "PlotLines";
6894 case ImGuiCol_PlotLinesHovered:
6895 return "PlotLinesHovered";
6896 case ImGuiCol_PlotHistogram:
6897 return "PlotHistogram";
6898 case ImGuiCol_PlotHistogramHovered:
6899 return "PlotHistogramHovered";
6900 case ImGuiCol_TextSelectedBg:
6901 return "TextSelectedBg";
6902 case ImGuiCol_ModalWindowDarkening:
6903 return "ModalWindowDarkening";
6904 case ImGuiCol_DragDropTarget:
6905 return "DragDropTarget";
6906 case ImGuiCol_NavHighlight:
6907 return "NavHighlight";
6908 case ImGuiCol_NavWindowingHighlight:
6909 return "NavWindowingHighlight";
6910 }
6911 IM_ASSERT(0);
6912 return "Unknown";
6913 }
6914
IsWindowChildOf(ImGuiWindow * window,ImGuiWindow * potential_parent)6915 bool ImGui::IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent)
6916 {
6917 if (window->RootWindow == potential_parent)
6918 return true;
6919 while (window != NULL)
6920 {
6921 if (window == potential_parent)
6922 return true;
6923 window = window->ParentWindow;
6924 }
6925 return false;
6926 }
6927
IsWindowHovered(ImGuiHoveredFlags flags)6928 bool ImGui::IsWindowHovered(ImGuiHoveredFlags flags)
6929 {
6930 IM_ASSERT((flags & ImGuiHoveredFlags_AllowWhenOverlapped) == 0); // Flags not supported by this function
6931 ImGuiContext& g = *GImGui;
6932
6933 if (flags & ImGuiHoveredFlags_AnyWindow)
6934 {
6935 if (g.HoveredWindow == NULL)
6936 return false;
6937 }
6938 else
6939 {
6940 switch (flags & (ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows))
6941 {
6942 case ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows:
6943 if (g.HoveredRootWindow != g.CurrentWindow->RootWindow)
6944 return false;
6945 break;
6946 case ImGuiHoveredFlags_RootWindow:
6947 if (g.HoveredWindow != g.CurrentWindow->RootWindow)
6948 return false;
6949 break;
6950 case ImGuiHoveredFlags_ChildWindows:
6951 if (g.HoveredWindow == NULL || !IsWindowChildOf(g.HoveredWindow, g.CurrentWindow))
6952 return false;
6953 break;
6954 default:
6955 if (g.HoveredWindow != g.CurrentWindow)
6956 return false;
6957 break;
6958 }
6959 }
6960
6961 if (!IsWindowContentHoverable(g.HoveredRootWindow, flags))
6962 return false;
6963 if (!(flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
6964 if (g.ActiveId != 0 && !g.ActiveIdAllowOverlap && g.ActiveId != g.HoveredWindow->MoveId)
6965 return false;
6966 return true;
6967 }
6968
IsWindowFocused(ImGuiFocusedFlags flags)6969 bool ImGui::IsWindowFocused(ImGuiFocusedFlags flags)
6970 {
6971 ImGuiContext& g = *GImGui;
6972 IM_ASSERT(g.CurrentWindow); // Not inside a Begin()/End()
6973
6974 if (flags & ImGuiFocusedFlags_AnyWindow)
6975 return g.NavWindow != NULL;
6976
6977 switch (flags & (ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows))
6978 {
6979 case ImGuiFocusedFlags_RootWindow | ImGuiFocusedFlags_ChildWindows:
6980 return g.NavWindow && g.NavWindow->RootWindow == g.CurrentWindow->RootWindow;
6981 case ImGuiFocusedFlags_RootWindow:
6982 return g.NavWindow == g.CurrentWindow->RootWindow;
6983 case ImGuiFocusedFlags_ChildWindows:
6984 return g.NavWindow && IsWindowChildOf(g.NavWindow, g.CurrentWindow);
6985 default:
6986 return g.NavWindow == g.CurrentWindow;
6987 }
6988 }
6989
6990 // Can we focus this window with CTRL+TAB (or PadMenu + PadFocusPrev/PadFocusNext)
IsWindowNavFocusable(ImGuiWindow * window)6991 bool ImGui::IsWindowNavFocusable(ImGuiWindow* window)
6992 {
6993 ImGuiContext& g = *GImGui;
6994 return window->Active && window == window->RootWindowForTabbing && (!(window->Flags & ImGuiWindowFlags_NoNavFocus) || window == g.NavWindow);
6995 }
6996
GetWindowWidth()6997 float ImGui::GetWindowWidth()
6998 {
6999 ImGuiWindow* window = GImGui->CurrentWindow;
7000 return window->Size.x;
7001 }
7002
GetWindowHeight()7003 float ImGui::GetWindowHeight()
7004 {
7005 ImGuiWindow* window = GImGui->CurrentWindow;
7006 return window->Size.y;
7007 }
7008
GetWindowPos()7009 ImVec2 ImGui::GetWindowPos()
7010 {
7011 ImGuiContext& g = *GImGui;
7012 ImGuiWindow* window = g.CurrentWindow;
7013 return window->Pos;
7014 }
7015
SetWindowScrollX(ImGuiWindow * window,float new_scroll_x)7016 static void SetWindowScrollX(ImGuiWindow* window, float new_scroll_x)
7017 {
7018 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.
7019 window->Scroll.x = new_scroll_x;
7020 window->DC.CursorMaxPos.x -= window->Scroll.x;
7021 }
7022
SetWindowScrollY(ImGuiWindow * window,float new_scroll_y)7023 static void SetWindowScrollY(ImGuiWindow* window, float new_scroll_y)
7024 {
7025 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.
7026 window->Scroll.y = new_scroll_y;
7027 window->DC.CursorMaxPos.y -= window->Scroll.y;
7028 }
7029
SetWindowPos(ImGuiWindow * window,const ImVec2 & pos,ImGuiCond cond)7030 static void SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiCond cond)
7031 {
7032 // Test condition (NB: bit 0 is always true) and clear flags for next time
7033 if (cond && (window->SetWindowPosAllowFlags & cond) == 0)
7034 return;
7035 window->SetWindowPosAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
7036 window->SetWindowPosVal = ImVec2(FLT_MAX, FLT_MAX);
7037
7038 // Set
7039 const ImVec2 old_pos = window->Pos;
7040 window->PosFloat = pos;
7041 window->Pos = ImFloor(pos);
7042 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
7043 window->DC.CursorMaxPos += (window->Pos - old_pos); // And more importantly we need to adjust this so size calculation doesn't get affected.
7044 }
7045
SetWindowPos(const ImVec2 & pos,ImGuiCond cond)7046 void ImGui::SetWindowPos(const ImVec2& pos, ImGuiCond cond)
7047 {
7048 ImGuiWindow* window = GetCurrentWindowRead();
7049 SetWindowPos(window, pos, cond);
7050 }
7051
SetWindowPos(const char * name,const ImVec2 & pos,ImGuiCond cond)7052 void ImGui::SetWindowPos(const char* name, const ImVec2& pos, ImGuiCond cond)
7053 {
7054 if (ImGuiWindow* window = FindWindowByName(name))
7055 SetWindowPos(window, pos, cond);
7056 }
7057
GetWindowSize()7058 ImVec2 ImGui::GetWindowSize()
7059 {
7060 ImGuiWindow* window = GetCurrentWindowRead();
7061 return window->Size;
7062 }
7063
SetWindowSize(ImGuiWindow * window,const ImVec2 & size,ImGuiCond cond)7064 static void SetWindowSize(ImGuiWindow* window, const ImVec2& size, ImGuiCond cond)
7065 {
7066 // Test condition (NB: bit 0 is always true) and clear flags for next time
7067 if (cond && (window->SetWindowSizeAllowFlags & cond) == 0)
7068 return;
7069 window->SetWindowSizeAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
7070
7071 // Set
7072 if (size.x > 0.0f)
7073 {
7074 window->AutoFitFramesX = 0;
7075 window->SizeFull.x = size.x;
7076 }
7077 else
7078 {
7079 window->AutoFitFramesX = 2;
7080 window->AutoFitOnlyGrows = false;
7081 }
7082 if (size.y > 0.0f)
7083 {
7084 window->AutoFitFramesY = 0;
7085 window->SizeFull.y = size.y;
7086 }
7087 else
7088 {
7089 window->AutoFitFramesY = 2;
7090 window->AutoFitOnlyGrows = false;
7091 }
7092 }
7093
SetWindowSize(const ImVec2 & size,ImGuiCond cond)7094 void ImGui::SetWindowSize(const ImVec2& size, ImGuiCond cond)
7095 {
7096 SetWindowSize(GImGui->CurrentWindow, size, cond);
7097 }
7098
SetWindowSize(const char * name,const ImVec2 & size,ImGuiCond cond)7099 void ImGui::SetWindowSize(const char* name, const ImVec2& size, ImGuiCond cond)
7100 {
7101 if (ImGuiWindow* window = FindWindowByName(name))
7102 SetWindowSize(window, size, cond);
7103 }
7104
SetWindowCollapsed(ImGuiWindow * window,bool collapsed,ImGuiCond cond)7105 static void SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond)
7106 {
7107 // Test condition (NB: bit 0 is always true) and clear flags for next time
7108 if (cond && (window->SetWindowCollapsedAllowFlags & cond) == 0)
7109 return;
7110 window->SetWindowCollapsedAllowFlags &= ~(ImGuiCond_Once | ImGuiCond_FirstUseEver | ImGuiCond_Appearing);
7111
7112 // Set
7113 window->Collapsed = collapsed;
7114 }
7115
SetWindowCollapsed(bool collapsed,ImGuiCond cond)7116 void ImGui::SetWindowCollapsed(bool collapsed, ImGuiCond cond)
7117 {
7118 SetWindowCollapsed(GImGui->CurrentWindow, collapsed, cond);
7119 }
7120
IsWindowCollapsed()7121 bool ImGui::IsWindowCollapsed()
7122 {
7123 ImGuiWindow* window = GetCurrentWindowRead();
7124 return window->Collapsed;
7125 }
7126
IsWindowAppearing()7127 bool ImGui::IsWindowAppearing()
7128 {
7129 ImGuiWindow* window = GetCurrentWindowRead();
7130 return window->Appearing;
7131 }
7132
SetWindowCollapsed(const char * name,bool collapsed,ImGuiCond cond)7133 void ImGui::SetWindowCollapsed(const char* name, bool collapsed, ImGuiCond cond)
7134 {
7135 if (ImGuiWindow* window = FindWindowByName(name))
7136 SetWindowCollapsed(window, collapsed, cond);
7137 }
7138
SetWindowFocus()7139 void ImGui::SetWindowFocus()
7140 {
7141 FocusWindow(GImGui->CurrentWindow);
7142 }
7143
SetWindowFocus(const char * name)7144 void ImGui::SetWindowFocus(const char* name)
7145 {
7146 if (name)
7147 {
7148 if (ImGuiWindow* window = FindWindowByName(name))
7149 FocusWindow(window);
7150 }
7151 else
7152 {
7153 FocusWindow(NULL);
7154 }
7155 }
7156
SetNextWindowPos(const ImVec2 & pos,ImGuiCond cond,const ImVec2 & pivot)7157 void ImGui::SetNextWindowPos(const ImVec2& pos, ImGuiCond cond, const ImVec2& pivot)
7158 {
7159 ImGuiContext& g = *GImGui;
7160 g.NextWindowData.PosVal = pos;
7161 g.NextWindowData.PosPivotVal = pivot;
7162 g.NextWindowData.PosCond = cond ? cond : ImGuiCond_Always;
7163 }
7164
SetNextWindowSize(const ImVec2 & size,ImGuiCond cond)7165 void ImGui::SetNextWindowSize(const ImVec2& size, ImGuiCond cond)
7166 {
7167 ImGuiContext& g = *GImGui;
7168 g.NextWindowData.SizeVal = size;
7169 g.NextWindowData.SizeCond = cond ? cond : ImGuiCond_Always;
7170 }
7171
SetNextWindowSizeConstraints(const ImVec2 & size_min,const ImVec2 & size_max,ImGuiSizeCallback custom_callback,void * custom_callback_user_data)7172 void ImGui::SetNextWindowSizeConstraints(const ImVec2& size_min, const ImVec2& size_max, ImGuiSizeCallback custom_callback, void* custom_callback_user_data)
7173 {
7174 ImGuiContext& g = *GImGui;
7175 g.NextWindowData.SizeConstraintCond = ImGuiCond_Always;
7176 g.NextWindowData.SizeConstraintRect = ImRect(size_min, size_max);
7177 g.NextWindowData.SizeCallback = custom_callback;
7178 g.NextWindowData.SizeCallbackUserData = custom_callback_user_data;
7179 }
7180
SetNextWindowContentSize(const ImVec2 & size)7181 void ImGui::SetNextWindowContentSize(const ImVec2& size)
7182 {
7183 ImGuiContext& g = *GImGui;
7184 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.
7185 g.NextWindowData.ContentSizeCond = ImGuiCond_Always;
7186 }
7187
SetNextWindowCollapsed(bool collapsed,ImGuiCond cond)7188 void ImGui::SetNextWindowCollapsed(bool collapsed, ImGuiCond cond)
7189 {
7190 ImGuiContext& g = *GImGui;
7191 g.NextWindowData.CollapsedVal = collapsed;
7192 g.NextWindowData.CollapsedCond = cond ? cond : ImGuiCond_Always;
7193 }
7194
SetNextWindowFocus()7195 void ImGui::SetNextWindowFocus()
7196 {
7197 ImGuiContext& g = *GImGui;
7198 g.NextWindowData.FocusCond = ImGuiCond_Always; // Using a Cond member for consistency (may transition all of them to single flag set for fast Clear() op)
7199 }
7200
SetNextWindowBgAlpha(float alpha)7201 void ImGui::SetNextWindowBgAlpha(float alpha)
7202 {
7203 ImGuiContext& g = *GImGui;
7204 g.NextWindowData.BgAlphaVal = alpha;
7205 g.NextWindowData.BgAlphaCond = ImGuiCond_Always; // Using a Cond member for consistency (may transition all of them to single flag set for fast Clear() op)
7206 }
7207
7208 // In window space (not screen space!)
GetContentRegionMax()7209 ImVec2 ImGui::GetContentRegionMax()
7210 {
7211 ImGuiWindow* window = GetCurrentWindowRead();
7212 ImVec2 mx = window->ContentsRegionRect.Max;
7213 if (window->DC.ColumnsSet)
7214 mx.x = GetColumnOffset(window->DC.ColumnsSet->Current + 1) - window->WindowPadding.x;
7215 return mx;
7216 }
7217
GetContentRegionAvail()7218 ImVec2 ImGui::GetContentRegionAvail()
7219 {
7220 ImGuiWindow* window = GetCurrentWindowRead();
7221 return GetContentRegionMax() - (window->DC.CursorPos - window->Pos);
7222 }
7223
GetContentRegionAvailWidth()7224 float ImGui::GetContentRegionAvailWidth()
7225 {
7226 return GetContentRegionAvail().x;
7227 }
7228
7229 // In window space (not screen space!)
GetWindowContentRegionMin()7230 ImVec2 ImGui::GetWindowContentRegionMin()
7231 {
7232 ImGuiWindow* window = GetCurrentWindowRead();
7233 return window->ContentsRegionRect.Min;
7234 }
7235
GetWindowContentRegionMax()7236 ImVec2 ImGui::GetWindowContentRegionMax()
7237 {
7238 ImGuiWindow* window = GetCurrentWindowRead();
7239 return window->ContentsRegionRect.Max;
7240 }
7241
GetWindowContentRegionWidth()7242 float ImGui::GetWindowContentRegionWidth()
7243 {
7244 ImGuiWindow* window = GetCurrentWindowRead();
7245 return window->ContentsRegionRect.Max.x - window->ContentsRegionRect.Min.x;
7246 }
7247
GetTextLineHeight()7248 float ImGui::GetTextLineHeight()
7249 {
7250 ImGuiContext& g = *GImGui;
7251 return g.FontSize;
7252 }
7253
GetTextLineHeightWithSpacing()7254 float ImGui::GetTextLineHeightWithSpacing()
7255 {
7256 ImGuiContext& g = *GImGui;
7257 return g.FontSize + g.Style.ItemSpacing.y;
7258 }
7259
GetFrameHeight()7260 float ImGui::GetFrameHeight()
7261 {
7262 ImGuiContext& g = *GImGui;
7263 return g.FontSize + g.Style.FramePadding.y * 2.0f;
7264 }
7265
GetFrameHeightWithSpacing()7266 float ImGui::GetFrameHeightWithSpacing()
7267 {
7268 ImGuiContext& g = *GImGui;
7269 return g.FontSize + g.Style.FramePadding.y * 2.0f + g.Style.ItemSpacing.y;
7270 }
7271
GetWindowDrawList()7272 ImDrawList* ImGui::GetWindowDrawList()
7273 {
7274 ImGuiWindow* window = GetCurrentWindow();
7275 return window->DrawList;
7276 }
7277
GetFont()7278 ImFont* ImGui::GetFont()
7279 {
7280 return GImGui->Font;
7281 }
7282
GetFontSize()7283 float ImGui::GetFontSize()
7284 {
7285 return GImGui->FontSize;
7286 }
7287
GetFontTexUvWhitePixel()7288 ImVec2 ImGui::GetFontTexUvWhitePixel()
7289 {
7290 return GImGui->DrawListSharedData.TexUvWhitePixel;
7291 }
7292
SetWindowFontScale(float scale)7293 void ImGui::SetWindowFontScale(float scale)
7294 {
7295 ImGuiContext& g = *GImGui;
7296 ImGuiWindow* window = GetCurrentWindow();
7297 window->FontWindowScale = scale;
7298 g.FontSize = g.DrawListSharedData.FontSize = window->CalcFontSize();
7299 }
7300
7301 // User generally sees positions in window coordinates. Internally we store CursorPos in absolute screen coordinates because it is more convenient.
7302 // Conversion happens as we pass the value to user, but it makes our naming convention confusing because GetCursorPos() == (DC.CursorPos - window.Pos). May want to rename 'DC.CursorPos'.
GetCursorPos()7303 ImVec2 ImGui::GetCursorPos()
7304 {
7305 ImGuiWindow* window = GetCurrentWindowRead();
7306 return window->DC.CursorPos - window->Pos + window->Scroll;
7307 }
7308
GetCursorPosX()7309 float ImGui::GetCursorPosX()
7310 {
7311 ImGuiWindow* window = GetCurrentWindowRead();
7312 return window->DC.CursorPos.x - window->Pos.x + window->Scroll.x;
7313 }
7314
GetCursorPosY()7315 float ImGui::GetCursorPosY()
7316 {
7317 ImGuiWindow* window = GetCurrentWindowRead();
7318 return window->DC.CursorPos.y - window->Pos.y + window->Scroll.y;
7319 }
7320
SetCursorPos(const ImVec2 & local_pos)7321 void ImGui::SetCursorPos(const ImVec2& local_pos)
7322 {
7323 ImGuiWindow* window = GetCurrentWindow();
7324 window->DC.CursorPos = window->Pos - window->Scroll + local_pos;
7325 window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos);
7326 }
7327
SetCursorPosX(float x)7328 void ImGui::SetCursorPosX(float x)
7329 {
7330 ImGuiWindow* window = GetCurrentWindow();
7331 window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + x;
7332 window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, window->DC.CursorPos.x);
7333 }
7334
SetCursorPosY(float y)7335 void ImGui::SetCursorPosY(float y)
7336 {
7337 ImGuiWindow* window = GetCurrentWindow();
7338 window->DC.CursorPos.y = window->Pos.y - window->Scroll.y + y;
7339 window->DC.CursorMaxPos.y = ImMax(window->DC.CursorMaxPos.y, window->DC.CursorPos.y);
7340 }
7341
GetCursorStartPos()7342 ImVec2 ImGui::GetCursorStartPos()
7343 {
7344 ImGuiWindow* window = GetCurrentWindowRead();
7345 return window->DC.CursorStartPos - window->Pos;
7346 }
7347
GetCursorScreenPos()7348 ImVec2 ImGui::GetCursorScreenPos()
7349 {
7350 ImGuiWindow* window = GetCurrentWindowRead();
7351 return window->DC.CursorPos;
7352 }
7353
SetCursorScreenPos(const ImVec2 & screen_pos)7354 void ImGui::SetCursorScreenPos(const ImVec2& screen_pos)
7355 {
7356 ImGuiWindow* window = GetCurrentWindow();
7357 window->DC.CursorPos = screen_pos;
7358 window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, window->DC.CursorPos);
7359 }
7360
GetScrollX()7361 float ImGui::GetScrollX()
7362 {
7363 return GImGui->CurrentWindow->Scroll.x;
7364 }
7365
GetScrollY()7366 float ImGui::GetScrollY()
7367 {
7368 return GImGui->CurrentWindow->Scroll.y;
7369 }
7370
GetScrollMaxX()7371 float ImGui::GetScrollMaxX()
7372 {
7373 return GetScrollMaxX(GImGui->CurrentWindow);
7374 }
7375
GetScrollMaxY()7376 float ImGui::GetScrollMaxY()
7377 {
7378 return GetScrollMaxY(GImGui->CurrentWindow);
7379 }
7380
SetScrollX(float scroll_x)7381 void ImGui::SetScrollX(float scroll_x)
7382 {
7383 ImGuiWindow* window = GetCurrentWindow();
7384 window->ScrollTarget.x = scroll_x;
7385 window->ScrollTargetCenterRatio.x = 0.0f;
7386 }
7387
SetScrollY(float scroll_y)7388 void ImGui::SetScrollY(float scroll_y)
7389 {
7390 ImGuiWindow* window = GetCurrentWindow();
7391 window->ScrollTarget.y = scroll_y + window->TitleBarHeight() + window->MenuBarHeight(); // title bar height canceled out when using ScrollTargetRelY
7392 window->ScrollTargetCenterRatio.y = 0.0f;
7393 }
7394
SetScrollFromPosY(float pos_y,float center_y_ratio)7395 void ImGui::SetScrollFromPosY(float pos_y, float center_y_ratio)
7396 {
7397 // We store a target position so centering can occur on the next frame when we are guaranteed to have a known window size
7398 ImGuiWindow* window = GetCurrentWindow();
7399 IM_ASSERT(center_y_ratio >= 0.0f && center_y_ratio <= 1.0f);
7400 window->ScrollTarget.y = (float)(int)(pos_y + window->Scroll.y);
7401 window->ScrollTargetCenterRatio.y = center_y_ratio;
7402
7403 // Minor hack to to make scrolling to top/bottom of window take account of WindowPadding, it looks more right to the user this way
7404 if (center_y_ratio <= 0.0f && window->ScrollTarget.y <= window->WindowPadding.y)
7405 window->ScrollTarget.y = 0.0f;
7406 else if (center_y_ratio >= 1.0f && window->ScrollTarget.y >= window->SizeContents.y - window->WindowPadding.y + GImGui->Style.ItemSpacing.y)
7407 window->ScrollTarget.y = window->SizeContents.y;
7408 }
7409
7410 // center_y_ratio: 0.0f top of last item, 0.5f vertical center of last item, 1.0f bottom of last item.
SetScrollHere(float center_y_ratio)7411 void ImGui::SetScrollHere(float center_y_ratio)
7412 {
7413 ImGuiWindow* window = GetCurrentWindow();
7414 float target_y = window->DC.CursorPosPrevLine.y - window->Pos.y; // Top of last item, in window space
7415 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.
7416 SetScrollFromPosY(target_y, center_y_ratio);
7417 }
7418
ActivateItem(ImGuiID id)7419 void ImGui::ActivateItem(ImGuiID id)
7420 {
7421 ImGuiContext& g = *GImGui;
7422 g.NavNextActivateId = id;
7423 }
7424
SetKeyboardFocusHere(int offset)7425 void ImGui::SetKeyboardFocusHere(int offset)
7426 {
7427 IM_ASSERT(offset >= -1); // -1 is allowed but not below
7428 ImGuiWindow* window = GetCurrentWindow();
7429 window->FocusIdxAllRequestNext = window->FocusIdxAllCounter + 1 + offset;
7430 window->FocusIdxTabRequestNext = INT_MAX;
7431 }
7432
SetItemDefaultFocus()7433 void ImGui::SetItemDefaultFocus()
7434 {
7435 ImGuiContext& g = *GImGui;
7436 ImGuiWindow* window = g.CurrentWindow;
7437 if (!window->Appearing)
7438 return;
7439 if (g.NavWindow == window->RootWindowForNav && (g.NavInitRequest || g.NavInitResultId != 0) && g.NavLayer == g.NavWindow->DC.NavLayerCurrent)
7440 {
7441 g.NavInitRequest = false;
7442 g.NavInitResultId = g.NavWindow->DC.LastItemId;
7443 g.NavInitResultRectRel = ImRect(g.NavWindow->DC.LastItemRect.Min - g.NavWindow->Pos, g.NavWindow->DC.LastItemRect.Max - g.NavWindow->Pos);
7444 NavUpdateAnyRequestFlag();
7445 if (!IsItemVisible())
7446 SetScrollHere();
7447 }
7448 }
7449
SetStateStorage(ImGuiStorage * tree)7450 void ImGui::SetStateStorage(ImGuiStorage* tree)
7451 {
7452 ImGuiWindow* window = GetCurrentWindow();
7453 window->DC.StateStorage = tree ? tree : &window->StateStorage;
7454 }
7455
GetStateStorage()7456 ImGuiStorage* ImGui::GetStateStorage()
7457 {
7458 ImGuiWindow* window = GetCurrentWindowRead();
7459 return window->DC.StateStorage;
7460 }
7461
TextV(const char * fmt,va_list args)7462 void ImGui::TextV(const char* fmt, va_list args)
7463 {
7464 ImGuiWindow* window = GetCurrentWindow();
7465 if (window->SkipItems)
7466 return;
7467
7468 ImGuiContext& g = *GImGui;
7469 const char* text_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
7470 TextUnformatted(g.TempBuffer, text_end);
7471 }
7472
Text(const char * fmt,...)7473 void ImGui::Text(const char* fmt, ...)
7474 {
7475 va_list args;
7476 va_start(args, fmt);
7477 TextV(fmt, args);
7478 va_end(args);
7479 }
7480
TextColoredV(const ImVec4 & col,const char * fmt,va_list args)7481 void ImGui::TextColoredV(const ImVec4& col, const char* fmt, va_list args)
7482 {
7483 PushStyleColor(ImGuiCol_Text, col);
7484 TextV(fmt, args);
7485 PopStyleColor();
7486 }
7487
TextColored(const ImVec4 & col,const char * fmt,...)7488 void ImGui::TextColored(const ImVec4& col, const char* fmt, ...)
7489 {
7490 va_list args;
7491 va_start(args, fmt);
7492 TextColoredV(col, fmt, args);
7493 va_end(args);
7494 }
7495
TextDisabledV(const char * fmt,va_list args)7496 void ImGui::TextDisabledV(const char* fmt, va_list args)
7497 {
7498 PushStyleColor(ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled]);
7499 TextV(fmt, args);
7500 PopStyleColor();
7501 }
7502
TextDisabled(const char * fmt,...)7503 void ImGui::TextDisabled(const char* fmt, ...)
7504 {
7505 va_list args;
7506 va_start(args, fmt);
7507 TextDisabledV(fmt, args);
7508 va_end(args);
7509 }
7510
TextWrappedV(const char * fmt,va_list args)7511 void ImGui::TextWrappedV(const char* fmt, va_list args)
7512 {
7513 bool need_wrap = (GImGui->CurrentWindow->DC.TextWrapPos < 0.0f); // Keep existing wrap position is one ia already set
7514 if (need_wrap) PushTextWrapPos(0.0f);
7515 TextV(fmt, args);
7516 if (need_wrap) PopTextWrapPos();
7517 }
7518
TextWrapped(const char * fmt,...)7519 void ImGui::TextWrapped(const char* fmt, ...)
7520 {
7521 va_list args;
7522 va_start(args, fmt);
7523 TextWrappedV(fmt, args);
7524 va_end(args);
7525 }
7526
TextUnformatted(const char * text,const char * text_end)7527 void ImGui::TextUnformatted(const char* text, const char* text_end)
7528 {
7529 ImGuiWindow* window = GetCurrentWindow();
7530 if (window->SkipItems)
7531 return;
7532
7533 ImGuiContext& g = *GImGui;
7534 IM_ASSERT(text != NULL);
7535 const char* text_begin = text;
7536 if (text_end == NULL)
7537 text_end = text + strlen(text); // FIXME-OPT
7538
7539 const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrentLineTextBaseOffset);
7540 const float wrap_pos_x = window->DC.TextWrapPos;
7541 const bool wrap_enabled = wrap_pos_x >= 0.0f;
7542 if (text_end - text > 2000 && !wrap_enabled)
7543 {
7544 // Long text!
7545 // Perform manual coarse clipping to optimize for long multi-line text
7546 // From this point we will only compute the width of lines that are visible. Optimization only available when word-wrapping is disabled.
7547 // 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.
7548 const char* line = text;
7549 const float line_height = GetTextLineHeight();
7550 const ImRect clip_rect = window->ClipRect;
7551 ImVec2 text_size(0, 0);
7552
7553 if (text_pos.y <= clip_rect.Max.y)
7554 {
7555 ImVec2 pos = text_pos;
7556
7557 // Lines to skip (can't skip when logging text)
7558 if (!g.LogEnabled)
7559 {
7560 int lines_skippable = (int)((clip_rect.Min.y - text_pos.y) / line_height);
7561 if (lines_skippable > 0)
7562 {
7563 int lines_skipped = 0;
7564 while (line < text_end && lines_skipped < lines_skippable)
7565 {
7566 const char* line_end = strchr(line, '\n');
7567 if (!line_end)
7568 line_end = text_end;
7569 line = line_end + 1;
7570 lines_skipped++;
7571 }
7572 pos.y += lines_skipped * line_height;
7573 }
7574 }
7575
7576 // Lines to render
7577 if (line < text_end)
7578 {
7579 ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height));
7580 while (line < text_end)
7581 {
7582 const char* line_end = strchr(line, '\n');
7583 if (IsClippedEx(line_rect, 0, false))
7584 break;
7585
7586 const ImVec2 line_size = CalcTextSize(line, line_end, false);
7587 text_size.x = ImMax(text_size.x, line_size.x);
7588 RenderText(pos, line, line_end, false);
7589 if (!line_end)
7590 line_end = text_end;
7591 line = line_end + 1;
7592 line_rect.Min.y += line_height;
7593 line_rect.Max.y += line_height;
7594 pos.y += line_height;
7595 }
7596
7597 // Count remaining lines
7598 int lines_skipped = 0;
7599 while (line < text_end)
7600 {
7601 const char* line_end = strchr(line, '\n');
7602 if (!line_end)
7603 line_end = text_end;
7604 line = line_end + 1;
7605 lines_skipped++;
7606 }
7607 pos.y += lines_skipped * line_height;
7608 }
7609
7610 text_size.y += (pos - text_pos).y;
7611 }
7612
7613 ImRect bb(text_pos, text_pos + text_size);
7614 ItemSize(bb);
7615 ItemAdd(bb, 0);
7616 }
7617 else
7618 {
7619 const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f;
7620 const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width);
7621
7622 // Account of baseline offset
7623 ImRect bb(text_pos, text_pos + text_size);
7624 ItemSize(text_size);
7625 if (!ItemAdd(bb, 0))
7626 return;
7627
7628 // Render (we don't hide text after ## in this end-user function)
7629 RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width);
7630 }
7631 }
7632
AlignTextToFramePadding()7633 void ImGui::AlignTextToFramePadding()
7634 {
7635 ImGuiWindow* window = GetCurrentWindow();
7636 if (window->SkipItems)
7637 return;
7638
7639 ImGuiContext& g = *GImGui;
7640 window->DC.CurrentLineHeight = ImMax(window->DC.CurrentLineHeight, g.FontSize + g.Style.FramePadding.y * 2);
7641 window->DC.CurrentLineTextBaseOffset = ImMax(window->DC.CurrentLineTextBaseOffset, g.Style.FramePadding.y);
7642 }
7643
7644 // Add a label+text combo aligned to other label+value widgets
LabelTextV(const char * label,const char * fmt,va_list args)7645 void ImGui::LabelTextV(const char* label, const char* fmt, va_list args)
7646 {
7647 ImGuiWindow* window = GetCurrentWindow();
7648 if (window->SkipItems)
7649 return;
7650
7651 ImGuiContext& g = *GImGui;
7652 const ImGuiStyle& style = g.Style;
7653 const float w = CalcItemWidth();
7654
7655 const ImVec2 label_size = CalcTextSize(label, NULL, true);
7656 const ImRect value_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2));
7657 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);
7658 ItemSize(total_bb, style.FramePadding.y);
7659 if (!ItemAdd(total_bb, 0))
7660 return;
7661
7662 // Render
7663 const char* value_text_begin = &g.TempBuffer[0];
7664 const char* value_text_end = value_text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
7665 RenderTextClipped(value_bb.Min, value_bb.Max, value_text_begin, value_text_end, NULL, ImVec2(0.0f, 0.5f));
7666 if (label_size.x > 0.0f)
7667 RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label);
7668 }
7669
LabelText(const char * label,const char * fmt,...)7670 void ImGui::LabelText(const char* label, const char* fmt, ...)
7671 {
7672 va_list args;
7673 va_start(args, fmt);
7674 LabelTextV(label, fmt, args);
7675 va_end(args);
7676 }
7677
ButtonBehavior(const ImRect & bb,ImGuiID id,bool * out_hovered,bool * out_held,ImGuiButtonFlags flags)7678 bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags)
7679 {
7680 ImGuiContext& g = *GImGui;
7681 ImGuiWindow* window = GetCurrentWindow();
7682
7683 if (flags & ImGuiButtonFlags_Disabled)
7684 {
7685 if (out_hovered) *out_hovered = false;
7686 if (out_held) *out_held = false;
7687 if (g.ActiveId == id) ClearActiveID();
7688 return false;
7689 }
7690
7691 // Default behavior requires click+release on same spot
7692 if ((flags & (ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnRelease | ImGuiButtonFlags_PressedOnDoubleClick)) == 0)
7693 flags |= ImGuiButtonFlags_PressedOnClickRelease;
7694
7695 ImGuiWindow* backup_hovered_window = g.HoveredWindow;
7696 if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window)
7697 g.HoveredWindow = window;
7698
7699 bool pressed = false;
7700 bool hovered = ItemHoverable(bb, id);
7701
7702 // Special mode for Drag and Drop where holding button pressed for a long time while dragging another item triggers the button
7703 if ((flags & ImGuiButtonFlags_PressedOnDragDropHold) && g.DragDropActive && !(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoHoldToOpenOthers))
7704 if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
7705 {
7706 hovered = true;
7707 SetHoveredID(id);
7708 if (CalcTypematicPressedRepeatAmount(g.HoveredIdTimer + 0.0001f, g.HoveredIdTimer + 0.0001f - g.IO.DeltaTime, 0.01f, 0.70f)) // FIXME: Our formula for CalcTypematicPressedRepeatAmount() is fishy
7709 {
7710 pressed = true;
7711 FocusWindow(window);
7712 }
7713 }
7714
7715 if ((flags & ImGuiButtonFlags_FlattenChildren) && g.HoveredRootWindow == window)
7716 g.HoveredWindow = backup_hovered_window;
7717
7718 // 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.
7719 if (hovered && (flags & ImGuiButtonFlags_AllowItemOverlap) && (g.HoveredIdPreviousFrame != id && g.HoveredIdPreviousFrame != 0))
7720 hovered = false;
7721
7722 // Mouse
7723 if (hovered)
7724 {
7725 if (!(flags & ImGuiButtonFlags_NoKeyModifiers) || (!g.IO.KeyCtrl && !g.IO.KeyShift && !g.IO.KeyAlt))
7726 {
7727 // | CLICKING | HOLDING with ImGuiButtonFlags_Repeat
7728 // PressedOnClickRelease | <on release>* | <on repeat> <on repeat> .. (NOT on release) <-- MOST COMMON! (*) only if both click/release were over bounds
7729 // PressedOnClick | <on click> | <on click> <on repeat> <on repeat> ..
7730 // PressedOnRelease | <on release> | <on repeat> <on repeat> .. (NOT on release)
7731 // PressedOnDoubleClick | <on dclick> | <on dclick> <on repeat> <on repeat> ..
7732 // FIXME-NAV: We don't honor those different behaviors.
7733 if ((flags & ImGuiButtonFlags_PressedOnClickRelease) && g.IO.MouseClicked[0])
7734 {
7735 SetActiveID(id, window);
7736 if (!(flags & ImGuiButtonFlags_NoNavFocus))
7737 SetFocusID(id, window);
7738 FocusWindow(window);
7739 }
7740 if (((flags & ImGuiButtonFlags_PressedOnClick) && g.IO.MouseClicked[0]) || ((flags & ImGuiButtonFlags_PressedOnDoubleClick) && g.IO.MouseDoubleClicked[0]))
7741 {
7742 pressed = true;
7743 if (flags & ImGuiButtonFlags_NoHoldingActiveID)
7744 ClearActiveID();
7745 else
7746 SetActiveID(id, window); // Hold on ID
7747 FocusWindow(window);
7748 }
7749 if ((flags & ImGuiButtonFlags_PressedOnRelease) && g.IO.MouseReleased[0])
7750 {
7751 if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps <on release>
7752 pressed = true;
7753 ClearActiveID();
7754 }
7755
7756 // 'Repeat' mode acts when held regardless of _PressedOn flags (see table above).
7757 // Relies on repeat logic of IsMouseClicked() but we may as well do it ourselves if we end up exposing finer RepeatDelay/RepeatRate settings.
7758 if ((flags & ImGuiButtonFlags_Repeat) && g.ActiveId == id && g.IO.MouseDownDuration[0] > 0.0f && IsMouseClicked(0, true))
7759 pressed = true;
7760 }
7761
7762 if (pressed)
7763 g.NavDisableHighlight = true;
7764 }
7765
7766 // Gamepad/Keyboard navigation
7767 // We report navigated item as hovered but we don't set g.HoveredId to not interfere with mouse.
7768 if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover && (g.ActiveId == 0 || g.ActiveId == id || g.ActiveId == window->MoveId))
7769 hovered = true;
7770
7771 if (g.NavActivateDownId == id)
7772 {
7773 bool nav_activated_by_code = (g.NavActivateId == id);
7774 bool nav_activated_by_inputs = IsNavInputPressed(ImGuiNavInput_Activate, (flags & ImGuiButtonFlags_Repeat) ? ImGuiInputReadMode_Repeat : ImGuiInputReadMode_Pressed);
7775 if (nav_activated_by_code || nav_activated_by_inputs)
7776 pressed = true;
7777 if (nav_activated_by_code || nav_activated_by_inputs || g.ActiveId == id)
7778 {
7779 // Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button.
7780 g.NavActivateId = id; // This is so SetActiveId assign a Nav source
7781 SetActiveID(id, window);
7782 if (!(flags & ImGuiButtonFlags_NoNavFocus))
7783 SetFocusID(id, window);
7784 g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right) | (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
7785 }
7786 }
7787
7788 bool held = false;
7789 if (g.ActiveId == id)
7790 {
7791 if (g.ActiveIdSource == ImGuiInputSource_Mouse)
7792 {
7793 if (g.ActiveIdIsJustActivated)
7794 g.ActiveIdClickOffset = g.IO.MousePos - bb.Min;
7795 if (g.IO.MouseDown[0])
7796 {
7797 held = true;
7798 }
7799 else
7800 {
7801 if (hovered && (flags & ImGuiButtonFlags_PressedOnClickRelease))
7802 if (!((flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[0] >= g.IO.KeyRepeatDelay)) // Repeat mode trumps <on release>
7803 if (!g.DragDropActive)
7804 pressed = true;
7805 ClearActiveID();
7806 }
7807 if (!(flags & ImGuiButtonFlags_NoNavFocus))
7808 g.NavDisableHighlight = true;
7809 }
7810 else if (g.ActiveIdSource == ImGuiInputSource_Nav)
7811 {
7812 if (g.NavActivateDownId != id)
7813 ClearActiveID();
7814 }
7815 }
7816
7817 if (out_hovered) *out_hovered = hovered;
7818 if (out_held) *out_held = held;
7819
7820 return pressed;
7821 }
7822
ButtonEx(const char * label,const ImVec2 & size_arg,ImGuiButtonFlags flags)7823 bool ImGui::ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags)
7824 {
7825 ImGuiWindow* window = GetCurrentWindow();
7826 if (window->SkipItems)
7827 return false;
7828
7829 ImGuiContext& g = *GImGui;
7830 const ImGuiStyle& style = g.Style;
7831 const ImGuiID id = window->GetID(label);
7832 const ImVec2 label_size = CalcTextSize(label, NULL, true);
7833
7834 ImVec2 pos = window->DC.CursorPos;
7835 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)
7836 pos.y += window->DC.CurrentLineTextBaseOffset - style.FramePadding.y;
7837 ImVec2 size = CalcItemSize(size_arg, label_size.x + style.FramePadding.x * 2.0f, label_size.y + style.FramePadding.y * 2.0f);
7838
7839 const ImRect bb(pos, pos + size);
7840 ItemSize(bb, style.FramePadding.y);
7841 if (!ItemAdd(bb, id))
7842 return false;
7843
7844 if (window->DC.ItemFlags & ImGuiItemFlags_ButtonRepeat)
7845 flags |= ImGuiButtonFlags_Repeat;
7846 bool hovered, held;
7847 bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
7848
7849 // Render
7850 const ImU32 col = GetColorU32((hovered && held) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
7851 RenderNavHighlight(bb, id);
7852 RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);
7853 RenderTextClipped(bb.Min + style.FramePadding, bb.Max - style.FramePadding, label, NULL, &label_size, style.ButtonTextAlign, &bb);
7854
7855 // Automatically close popups
7856 //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
7857 // CloseCurrentPopup();
7858
7859 return pressed;
7860 }
7861
Button(const char * label,const ImVec2 & size_arg)7862 bool ImGui::Button(const char* label, const ImVec2& size_arg)
7863 {
7864 return ButtonEx(label, size_arg, 0);
7865 }
7866
7867 // Small buttons fits within text without additional vertical spacing.
SmallButton(const char * label)7868 bool ImGui::SmallButton(const char* label)
7869 {
7870 ImGuiContext& g = *GImGui;
7871 float backup_padding_y = g.Style.FramePadding.y;
7872 g.Style.FramePadding.y = 0.0f;
7873 bool pressed = ButtonEx(label, ImVec2(0, 0), ImGuiButtonFlags_AlignTextBaseLine);
7874 g.Style.FramePadding.y = backup_padding_y;
7875 return pressed;
7876 }
7877
7878 // Tip: use ImGui::PushID()/PopID() to push indices or pointers in the ID stack.
7879 // 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)
InvisibleButton(const char * str_id,const ImVec2 & size_arg)7880 bool ImGui::InvisibleButton(const char* str_id, const ImVec2& size_arg)
7881 {
7882 ImGuiWindow* window = GetCurrentWindow();
7883 if (window->SkipItems)
7884 return false;
7885
7886 const ImGuiID id = window->GetID(str_id);
7887 ImVec2 size = CalcItemSize(size_arg, 0.0f, 0.0f);
7888 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
7889 ItemSize(bb);
7890 if (!ItemAdd(bb, id))
7891 return false;
7892
7893 bool hovered, held;
7894 bool pressed = ButtonBehavior(bb, id, &hovered, &held);
7895
7896 return pressed;
7897 }
7898
7899 // Button to close a window
CloseButton(ImGuiID id,const ImVec2 & pos,float radius)7900 bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos, float radius)
7901 {
7902 ImGuiContext& g = *GImGui;
7903 ImGuiWindow* window = g.CurrentWindow;
7904
7905 // We intentionally allow interaction when clipped so that a mechanical Alt,Right,Validate sequence close a window.
7906 // (this isn't the regular behavior of buttons, but it doesn't affect the user much because navigation tends to keep items visible).
7907 const ImRect bb(pos - ImVec2(radius, radius), pos + ImVec2(radius, radius));
7908 bool is_clipped = !ItemAdd(bb, id);
7909
7910 bool hovered, held;
7911 bool pressed = ButtonBehavior(bb, id, &hovered, &held);
7912 if (is_clipped)
7913 return pressed;
7914
7915 // Render
7916 const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_CloseButtonActive : hovered ? ImGuiCol_CloseButtonHovered : ImGuiCol_CloseButton);
7917 ImVec2 center = bb.GetCenter();
7918 window->DrawList->AddCircleFilled(center, ImMax(2.0f, radius), col, 12);
7919
7920 const float cross_extent = (radius * 0.7071f) - 1.0f;
7921 if (hovered)
7922 {
7923 center -= ImVec2(0.5f, 0.5f);
7924 window->DrawList->AddLine(center + ImVec2(+cross_extent, +cross_extent), center + ImVec2(-cross_extent, -cross_extent), GetColorU32(ImGuiCol_Text));
7925 window->DrawList->AddLine(center + ImVec2(+cross_extent, -cross_extent), center + ImVec2(-cross_extent, +cross_extent), GetColorU32(ImGuiCol_Text));
7926 }
7927 return pressed;
7928 }
7929
7930 // [Internal]
ArrowButton(ImGuiID id,ImGuiDir dir,ImVec2 padding,ImGuiButtonFlags flags)7931 bool ImGui::ArrowButton(ImGuiID id, ImGuiDir dir, ImVec2 padding, ImGuiButtonFlags flags)
7932 {
7933 ImGuiContext& g = *GImGui;
7934 ImGuiWindow* window = g.CurrentWindow;
7935 if (window->SkipItems)
7936 return false;
7937
7938 const ImGuiStyle& style = g.Style;
7939
7940 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize + padding.x * 2.0f, g.FontSize + padding.y * 2.0f));
7941 ItemSize(bb, style.FramePadding.y);
7942 if (!ItemAdd(bb, id))
7943 return false;
7944
7945 bool hovered, held;
7946 bool pressed = ButtonBehavior(bb, id, &hovered, &held, flags);
7947
7948 const ImU32 col = GetColorU32((hovered && held) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
7949 RenderNavHighlight(bb, id);
7950 RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);
7951 RenderTriangle(bb.Min + padding, dir, 1.0f);
7952
7953 return pressed;
7954 }
7955
Image(ImTextureID user_texture_id,const ImVec2 & size,const ImVec2 & uv0,const ImVec2 & uv1,const ImVec4 & tint_col,const ImVec4 & border_col)7956 void ImGui::Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col)
7957 {
7958 ImGuiWindow* window = GetCurrentWindow();
7959 if (window->SkipItems)
7960 return;
7961
7962 ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
7963 if (border_col.w > 0.0f)
7964 bb.Max += ImVec2(2, 2);
7965 ItemSize(bb);
7966 if (!ItemAdd(bb, 0))
7967 return;
7968
7969 if (border_col.w > 0.0f)
7970 {
7971 window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(border_col), 0.0f);
7972 window->DrawList->AddImage(user_texture_id, bb.Min + ImVec2(1, 1), bb.Max - ImVec2(1, 1), uv0, uv1, GetColorU32(tint_col));
7973 }
7974 else
7975 {
7976 window->DrawList->AddImage(user_texture_id, bb.Min, bb.Max, uv0, uv1, GetColorU32(tint_col));
7977 }
7978 }
7979
7980 // frame_padding < 0: uses FramePadding from style (default)
7981 // frame_padding = 0: no framing
7982 // frame_padding > 0: set framing size
7983 // The color used are the button colors.
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)7984 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)
7985 {
7986 ImGuiWindow* window = GetCurrentWindow();
7987 if (window->SkipItems)
7988 return false;
7989
7990 ImGuiContext& g = *GImGui;
7991 const ImGuiStyle& style = g.Style;
7992
7993 // Default to using texture ID as ID. User can still push string/integer prefixes.
7994 // We could hash the size/uv to create a unique ID but that would prevent the user from animating UV.
7995 PushID((void*)user_texture_id);
7996 const ImGuiID id = window->GetID("#image");
7997 PopID();
7998
7999 const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) : style.FramePadding;
8000 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding * 2);
8001 const ImRect image_bb(window->DC.CursorPos + padding, window->DC.CursorPos + padding + size);
8002 ItemSize(bb);
8003 if (!ItemAdd(bb, id))
8004 return false;
8005
8006 bool hovered, held;
8007 bool pressed = ButtonBehavior(bb, id, &hovered, &held);
8008
8009 // Render
8010 const ImU32 col = GetColorU32((hovered && held) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
8011 RenderNavHighlight(bb, id);
8012 RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, style.FrameRounding));
8013 if (bg_col.w > 0.0f)
8014 window->DrawList->AddRectFilled(image_bb.Min, image_bb.Max, GetColorU32(bg_col));
8015 window->DrawList->AddImage(user_texture_id, image_bb.Min, image_bb.Max, uv0, uv1, GetColorU32(tint_col));
8016
8017 return pressed;
8018 }
8019
8020 // Start logging ImGui output to TTY
LogToTTY(int max_depth)8021 void ImGui::LogToTTY(int max_depth)
8022 {
8023 ImGuiContext& g = *GImGui;
8024 if (g.LogEnabled)
8025 return;
8026 ImGuiWindow* window = g.CurrentWindow;
8027
8028 IM_ASSERT(g.LogFile == NULL);
8029 g.LogFile = stdout;
8030 g.LogEnabled = true;
8031 g.LogStartDepth = window->DC.TreeDepth;
8032 if (max_depth >= 0)
8033 g.LogAutoExpandMaxDepth = max_depth;
8034 }
8035
8036 // Start logging ImGui output to given file
LogToFile(int max_depth,const char * filename)8037 void ImGui::LogToFile(int max_depth, const char* filename)
8038 {
8039 ImGuiContext& g = *GImGui;
8040 if (g.LogEnabled)
8041 return;
8042 ImGuiWindow* window = g.CurrentWindow;
8043
8044 if (!filename)
8045 {
8046 filename = g.IO.LogFilename;
8047 if (!filename)
8048 return;
8049 }
8050
8051 IM_ASSERT(g.LogFile == NULL);
8052 g.LogFile = ImFileOpen(filename, "ab");
8053 if (!g.LogFile)
8054 {
8055 IM_ASSERT(g.LogFile != NULL); // Consider this an error
8056 return;
8057 }
8058 g.LogEnabled = true;
8059 g.LogStartDepth = window->DC.TreeDepth;
8060 if (max_depth >= 0)
8061 g.LogAutoExpandMaxDepth = max_depth;
8062 }
8063
8064 // Start logging ImGui output to clipboard
LogToClipboard(int max_depth)8065 void ImGui::LogToClipboard(int max_depth)
8066 {
8067 ImGuiContext& g = *GImGui;
8068 if (g.LogEnabled)
8069 return;
8070 ImGuiWindow* window = g.CurrentWindow;
8071
8072 IM_ASSERT(g.LogFile == NULL);
8073 g.LogFile = NULL;
8074 g.LogEnabled = true;
8075 g.LogStartDepth = window->DC.TreeDepth;
8076 if (max_depth >= 0)
8077 g.LogAutoExpandMaxDepth = max_depth;
8078 }
8079
LogFinish()8080 void ImGui::LogFinish()
8081 {
8082 ImGuiContext& g = *GImGui;
8083 if (!g.LogEnabled)
8084 return;
8085
8086 LogText(IM_NEWLINE);
8087 if (g.LogFile != NULL)
8088 {
8089 if (g.LogFile == stdout)
8090 fflush(g.LogFile);
8091 else
8092 fclose(g.LogFile);
8093 g.LogFile = NULL;
8094 }
8095 if (g.LogClipboard->size() > 1)
8096 {
8097 SetClipboardText(g.LogClipboard->begin());
8098 g.LogClipboard->clear();
8099 }
8100 g.LogEnabled = false;
8101 }
8102
8103 // Helper to display logging buttons
LogButtons()8104 void ImGui::LogButtons()
8105 {
8106 ImGuiContext& g = *GImGui;
8107
8108 PushID("LogButtons");
8109 const bool log_to_tty = Button("Log To TTY");
8110 SameLine();
8111 const bool log_to_file = Button("Log To File");
8112 SameLine();
8113 const bool log_to_clipboard = Button("Log To Clipboard");
8114 SameLine();
8115 PushItemWidth(80.0f);
8116 PushAllowKeyboardFocus(false);
8117 SliderInt("Depth", &g.LogAutoExpandMaxDepth, 0, 9, NULL);
8118 PopAllowKeyboardFocus();
8119 PopItemWidth();
8120 PopID();
8121
8122 // Start logging at the end of the function so that the buttons don't appear in the log
8123 if (log_to_tty)
8124 LogToTTY(g.LogAutoExpandMaxDepth);
8125 if (log_to_file)
8126 LogToFile(g.LogAutoExpandMaxDepth, g.IO.LogFilename);
8127 if (log_to_clipboard)
8128 LogToClipboard(g.LogAutoExpandMaxDepth);
8129 }
8130
TreeNodeBehaviorIsOpen(ImGuiID id,ImGuiTreeNodeFlags flags)8131 bool ImGui::TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags)
8132 {
8133 if (flags & ImGuiTreeNodeFlags_Leaf)
8134 return true;
8135
8136 // We only write to the tree storage if the user clicks (or explicitely use SetNextTreeNode*** functions)
8137 ImGuiContext& g = *GImGui;
8138 ImGuiWindow* window = g.CurrentWindow;
8139 ImGuiStorage* storage = window->DC.StateStorage;
8140
8141 bool is_open;
8142 if (g.NextTreeNodeOpenCond != 0)
8143 {
8144 if (g.NextTreeNodeOpenCond & ImGuiCond_Always)
8145 {
8146 is_open = g.NextTreeNodeOpenVal;
8147 storage->SetInt(id, is_open);
8148 }
8149 else
8150 {
8151 // We treat ImGuiCond_Once and ImGuiCond_FirstUseEver the same because tree node state are not saved persistently.
8152 const int stored_value = storage->GetInt(id, -1);
8153 if (stored_value == -1)
8154 {
8155 is_open = g.NextTreeNodeOpenVal;
8156 storage->SetInt(id, is_open);
8157 }
8158 else
8159 {
8160 is_open = stored_value != 0;
8161 }
8162 }
8163 g.NextTreeNodeOpenCond = 0;
8164 }
8165 else
8166 {
8167 is_open = storage->GetInt(id, (flags & ImGuiTreeNodeFlags_DefaultOpen) ? 1 : 0) != 0;
8168 }
8169
8170 // When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior).
8171 // NB- If we are above max depth we still allow manually opened nodes to be logged.
8172 if (g.LogEnabled && !(flags & ImGuiTreeNodeFlags_NoAutoOpenOnLog) && window->DC.TreeDepth < g.LogAutoExpandMaxDepth)
8173 is_open = true;
8174
8175 return is_open;
8176 }
8177
TreeNodeBehavior(ImGuiID id,ImGuiTreeNodeFlags flags,const char * label,const char * label_end)8178 bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end)
8179 {
8180 ImGuiWindow* window = GetCurrentWindow();
8181 if (window->SkipItems)
8182 return false;
8183
8184 ImGuiContext& g = *GImGui;
8185 const ImGuiStyle& style = g.Style;
8186 const bool display_frame = (flags & ImGuiTreeNodeFlags_Framed) != 0;
8187 const ImVec2 padding = (display_frame || (flags & ImGuiTreeNodeFlags_FramePadding)) ? style.FramePadding : ImVec2(style.FramePadding.x, 0.0f);
8188
8189 if (!label_end)
8190 label_end = FindRenderedTextEnd(label);
8191 const ImVec2 label_size = CalcTextSize(label, label_end, false);
8192
8193 // We vertically grow up to current line height up the typical widget height.
8194 const float text_base_offset_y = ImMax(padding.y, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it
8195 const float frame_height = ImMax(ImMin(window->DC.CurrentLineHeight, g.FontSize + style.FramePadding.y * 2), label_size.y + padding.y * 2);
8196 ImRect frame_bb = ImRect(window->DC.CursorPos, ImVec2(window->Pos.x + GetContentRegionMax().x, window->DC.CursorPos.y + frame_height));
8197 if (display_frame)
8198 {
8199 // Framed header expand a little outside the default padding
8200 frame_bb.Min.x -= (float)(int)(window->WindowPadding.x * 0.5f) - 1;
8201 frame_bb.Max.x += (float)(int)(window->WindowPadding.x * 0.5f) - 1;
8202 }
8203
8204 const float text_offset_x = (g.FontSize + (display_frame ? padding.x * 3 : padding.x * 2)); // Collapser arrow width + Spacing
8205 const float text_width = g.FontSize + (label_size.x > 0.0f ? label_size.x + padding.x * 2 : 0.0f); // Include collapser
8206 ItemSize(ImVec2(text_width, frame_height), text_base_offset_y);
8207
8208 // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing
8209 // (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)
8210 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);
8211 bool is_open = TreeNodeBehaviorIsOpen(id, flags);
8212
8213 // Store a flag for the current depth to tell if we will allow closing this node when navigating one of its child.
8214 // For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop().
8215 // This is currently only support 32 level deep and we are fine with (1 << Depth) overflowing into a zero.
8216 if (is_open && !g.NavIdIsAlive && (flags & ImGuiTreeNodeFlags_NavCloseFromChild) && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
8217 window->DC.TreeDepthMayCloseOnPop |= (1 << window->DC.TreeDepth);
8218
8219 bool item_add = ItemAdd(interact_bb, id);
8220 window->DC.LastItemStatusFlags |= ImGuiItemStatusFlags_HasDisplayRect;
8221 window->DC.LastItemDisplayRect = frame_bb;
8222
8223 if (!item_add)
8224 {
8225 if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
8226 TreePushRawID(id);
8227 return is_open;
8228 }
8229
8230 // Flags that affects opening behavior:
8231 // - 0(default) ..................... single-click anywhere to open
8232 // - OpenOnDoubleClick .............. double-click anywhere to open
8233 // - OpenOnArrow .................... single-click on arrow to open
8234 // - OpenOnDoubleClick|OpenOnArrow .. single-click on arrow or double-click anywhere to open
8235 ImGuiButtonFlags button_flags = ImGuiButtonFlags_NoKeyModifiers | ((flags & ImGuiTreeNodeFlags_AllowItemOverlap) ? ImGuiButtonFlags_AllowItemOverlap : 0);
8236 if (!(flags & ImGuiTreeNodeFlags_Leaf))
8237 button_flags |= ImGuiButtonFlags_PressedOnDragDropHold;
8238 if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)
8239 button_flags |= ImGuiButtonFlags_PressedOnDoubleClick | ((flags & ImGuiTreeNodeFlags_OpenOnArrow) ? ImGuiButtonFlags_PressedOnClickRelease : 0);
8240
8241 bool hovered, held, pressed = ButtonBehavior(interact_bb, id, &hovered, &held, button_flags);
8242 if (!(flags & ImGuiTreeNodeFlags_Leaf))
8243 {
8244 bool toggled = false;
8245 if (pressed)
8246 {
8247 toggled = !(flags & (ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick)) || (g.NavActivateId == id);
8248 if (flags & ImGuiTreeNodeFlags_OpenOnArrow)
8249 toggled |= IsMouseHoveringRect(interact_bb.Min, ImVec2(interact_bb.Min.x + text_offset_x, interact_bb.Max.y)) && (!g.NavDisableMouseHover);
8250 if (flags & ImGuiTreeNodeFlags_OpenOnDoubleClick)
8251 toggled |= g.IO.MouseDoubleClicked[0];
8252 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.
8253 toggled = false;
8254 }
8255
8256 if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Left && is_open)
8257 {
8258 toggled = true;
8259 NavMoveRequestCancel();
8260 }
8261 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?
8262 {
8263 toggled = true;
8264 NavMoveRequestCancel();
8265 }
8266
8267 if (toggled)
8268 {
8269 is_open = !is_open;
8270 window->DC.StateStorage->SetInt(id, is_open);
8271 }
8272 }
8273 if (flags & ImGuiTreeNodeFlags_AllowItemOverlap)
8274 SetItemAllowOverlap();
8275
8276 // Render
8277 const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
8278 const ImVec2 text_pos = frame_bb.Min + ImVec2(text_offset_x, text_base_offset_y);
8279 if (display_frame)
8280 {
8281 // Framed type
8282 RenderFrame(frame_bb.Min, frame_bb.Max, col, true, style.FrameRounding);
8283 RenderNavHighlight(frame_bb, id, ImGuiNavHighlightFlags_TypeThin);
8284 RenderTriangle(frame_bb.Min + ImVec2(padding.x, text_base_offset_y), is_open ? ImGuiDir_Down : ImGuiDir_Right, 1.0f);
8285 if (g.LogEnabled)
8286 {
8287 // 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.
8288 const char log_prefix[] = "\n##";
8289 const char log_suffix[] = "##";
8290 LogRenderedText(&text_pos, log_prefix, log_prefix + 3);
8291 RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size);
8292 LogRenderedText(&text_pos, log_suffix + 1, log_suffix + 3);
8293 }
8294 else
8295 {
8296 RenderTextClipped(text_pos, frame_bb.Max, label, label_end, &label_size);
8297 }
8298 }
8299 else
8300 {
8301 // Unframed typed for tree nodes
8302 if (hovered || (flags & ImGuiTreeNodeFlags_Selected))
8303 {
8304 RenderFrame(frame_bb.Min, frame_bb.Max, col, false);
8305 RenderNavHighlight(frame_bb, id, ImGuiNavHighlightFlags_TypeThin);
8306 }
8307
8308 if (flags & ImGuiTreeNodeFlags_Bullet)
8309 RenderBullet(frame_bb.Min + ImVec2(text_offset_x * 0.5f, g.FontSize * 0.50f + text_base_offset_y));
8310 else if (!(flags & ImGuiTreeNodeFlags_Leaf))
8311 RenderTriangle(frame_bb.Min + ImVec2(padding.x, g.FontSize * 0.15f + text_base_offset_y), is_open ? ImGuiDir_Down : ImGuiDir_Right, 0.70f);
8312 if (g.LogEnabled)
8313 LogRenderedText(&text_pos, ">");
8314 RenderText(text_pos, label, label_end, false);
8315 }
8316
8317 if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
8318 TreePushRawID(id);
8319 return is_open;
8320 }
8321
8322 // CollapsingHeader returns true when opened but do not indent nor push into the ID stack (because of the ImGuiTreeNodeFlags_NoTreePushOnOpen flag).
8323 // This is basically the same as calling TreeNodeEx(label, ImGuiTreeNodeFlags_CollapsingHeader | ImGuiTreeNodeFlags_NoTreePushOnOpen). You can remove the _NoTreePushOnOpen flag if you want behavior closer to normal TreeNode().
CollapsingHeader(const char * label,ImGuiTreeNodeFlags flags)8324 bool ImGui::CollapsingHeader(const char* label, ImGuiTreeNodeFlags flags)
8325 {
8326 ImGuiWindow* window = GetCurrentWindow();
8327 if (window->SkipItems)
8328 return false;
8329
8330 return TreeNodeBehavior(window->GetID(label), flags | ImGuiTreeNodeFlags_CollapsingHeader | ImGuiTreeNodeFlags_NoTreePushOnOpen, label);
8331 }
8332
CollapsingHeader(const char * label,bool * p_open,ImGuiTreeNodeFlags flags)8333 bool ImGui::CollapsingHeader(const char* label, bool* p_open, ImGuiTreeNodeFlags flags)
8334 {
8335 ImGuiWindow* window = GetCurrentWindow();
8336 if (window->SkipItems)
8337 return false;
8338
8339 if (p_open && !*p_open)
8340 return false;
8341
8342 ImGuiID id = window->GetID(label);
8343 bool is_open = TreeNodeBehavior(id, flags | ImGuiTreeNodeFlags_CollapsingHeader | ImGuiTreeNodeFlags_NoTreePushOnOpen | (p_open ? ImGuiTreeNodeFlags_AllowItemOverlap : 0), label);
8344 if (p_open)
8345 {
8346 // Create a small overlapping close button // FIXME: We can evolve this into user accessible helpers to add extra buttons on title bars, headers, etc.
8347 ImGuiContext& g = *GImGui;
8348 float button_sz = g.FontSize * 0.5f;
8349 ImGuiItemHoveredDataBackup last_item_backup;
8350 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))
8351 *p_open = false;
8352 last_item_backup.Restore();
8353 }
8354
8355 return is_open;
8356 }
8357
TreeNodeEx(const char * label,ImGuiTreeNodeFlags flags)8358 bool ImGui::TreeNodeEx(const char* label, ImGuiTreeNodeFlags flags)
8359 {
8360 ImGuiWindow* window = GetCurrentWindow();
8361 if (window->SkipItems)
8362 return false;
8363
8364 return TreeNodeBehavior(window->GetID(label), flags, label, NULL);
8365 }
8366
TreeNodeExV(const char * str_id,ImGuiTreeNodeFlags flags,const char * fmt,va_list args)8367 bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
8368 {
8369 ImGuiWindow* window = GetCurrentWindow();
8370 if (window->SkipItems)
8371 return false;
8372
8373 ImGuiContext& g = *GImGui;
8374 const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
8375 return TreeNodeBehavior(window->GetID(str_id), flags, g.TempBuffer, label_end);
8376 }
8377
TreeNodeExV(const void * ptr_id,ImGuiTreeNodeFlags flags,const char * fmt,va_list args)8378 bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args)
8379 {
8380 ImGuiWindow* window = GetCurrentWindow();
8381 if (window->SkipItems)
8382 return false;
8383
8384 ImGuiContext& g = *GImGui;
8385 const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
8386 return TreeNodeBehavior(window->GetID(ptr_id), flags, g.TempBuffer, label_end);
8387 }
8388
TreeNodeV(const char * str_id,const char * fmt,va_list args)8389 bool ImGui::TreeNodeV(const char* str_id, const char* fmt, va_list args)
8390 {
8391 return TreeNodeExV(str_id, 0, fmt, args);
8392 }
8393
TreeNodeV(const void * ptr_id,const char * fmt,va_list args)8394 bool ImGui::TreeNodeV(const void* ptr_id, const char* fmt, va_list args)
8395 {
8396 return TreeNodeExV(ptr_id, 0, fmt, args);
8397 }
8398
TreeNodeEx(const char * str_id,ImGuiTreeNodeFlags flags,const char * fmt,...)8399 bool ImGui::TreeNodeEx(const char* str_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
8400 {
8401 va_list args;
8402 va_start(args, fmt);
8403 bool is_open = TreeNodeExV(str_id, flags, fmt, args);
8404 va_end(args);
8405 return is_open;
8406 }
8407
TreeNodeEx(const void * ptr_id,ImGuiTreeNodeFlags flags,const char * fmt,...)8408 bool ImGui::TreeNodeEx(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, ...)
8409 {
8410 va_list args;
8411 va_start(args, fmt);
8412 bool is_open = TreeNodeExV(ptr_id, flags, fmt, args);
8413 va_end(args);
8414 return is_open;
8415 }
8416
TreeNode(const char * str_id,const char * fmt,...)8417 bool ImGui::TreeNode(const char* str_id, const char* fmt, ...)
8418 {
8419 va_list args;
8420 va_start(args, fmt);
8421 bool is_open = TreeNodeExV(str_id, 0, fmt, args);
8422 va_end(args);
8423 return is_open;
8424 }
8425
TreeNode(const void * ptr_id,const char * fmt,...)8426 bool ImGui::TreeNode(const void* ptr_id, const char* fmt, ...)
8427 {
8428 va_list args;
8429 va_start(args, fmt);
8430 bool is_open = TreeNodeExV(ptr_id, 0, fmt, args);
8431 va_end(args);
8432 return is_open;
8433 }
8434
TreeNode(const char * label)8435 bool ImGui::TreeNode(const char* label)
8436 {
8437 ImGuiWindow* window = GetCurrentWindow();
8438 if (window->SkipItems)
8439 return false;
8440 return TreeNodeBehavior(window->GetID(label), 0, label, NULL);
8441 }
8442
TreeAdvanceToLabelPos()8443 void ImGui::TreeAdvanceToLabelPos()
8444 {
8445 ImGuiContext& g = *GImGui;
8446 g.CurrentWindow->DC.CursorPos.x += GetTreeNodeToLabelSpacing();
8447 }
8448
8449 // Horizontal distance preceding label when using TreeNode() or Bullet()
GetTreeNodeToLabelSpacing()8450 float ImGui::GetTreeNodeToLabelSpacing()
8451 {
8452 ImGuiContext& g = *GImGui;
8453 return g.FontSize + (g.Style.FramePadding.x * 2.0f);
8454 }
8455
SetNextTreeNodeOpen(bool is_open,ImGuiCond cond)8456 void ImGui::SetNextTreeNodeOpen(bool is_open, ImGuiCond cond)
8457 {
8458 ImGuiContext& g = *GImGui;
8459 if (g.CurrentWindow->SkipItems)
8460 return;
8461 g.NextTreeNodeOpenVal = is_open;
8462 g.NextTreeNodeOpenCond = cond ? cond : ImGuiCond_Always;
8463 }
8464
PushID(const char * str_id)8465 void ImGui::PushID(const char* str_id)
8466 {
8467 ImGuiWindow* window = GetCurrentWindowRead();
8468 window->IDStack.push_back(window->GetID(str_id));
8469 }
8470
PushID(const char * str_id_begin,const char * str_id_end)8471 void ImGui::PushID(const char* str_id_begin, const char* str_id_end)
8472 {
8473 ImGuiWindow* window = GetCurrentWindowRead();
8474 window->IDStack.push_back(window->GetID(str_id_begin, str_id_end));
8475 }
8476
PushID(const void * ptr_id)8477 void ImGui::PushID(const void* ptr_id)
8478 {
8479 ImGuiWindow* window = GetCurrentWindowRead();
8480 window->IDStack.push_back(window->GetID(ptr_id));
8481 }
8482
PushID(int int_id)8483 void ImGui::PushID(int int_id)
8484 {
8485 const void* ptr_id = (void*)(intptr_t)int_id;
8486 ImGuiWindow* window = GetCurrentWindowRead();
8487 window->IDStack.push_back(window->GetID(ptr_id));
8488 }
8489
PopID()8490 void ImGui::PopID()
8491 {
8492 ImGuiWindow* window = GetCurrentWindowRead();
8493 window->IDStack.pop_back();
8494 }
8495
GetID(const char * str_id)8496 ImGuiID ImGui::GetID(const char* str_id)
8497 {
8498 return GImGui->CurrentWindow->GetID(str_id);
8499 }
8500
GetID(const char * str_id_begin,const char * str_id_end)8501 ImGuiID ImGui::GetID(const char* str_id_begin, const char* str_id_end)
8502 {
8503 return GImGui->CurrentWindow->GetID(str_id_begin, str_id_end);
8504 }
8505
GetID(const void * ptr_id)8506 ImGuiID ImGui::GetID(const void* ptr_id)
8507 {
8508 return GImGui->CurrentWindow->GetID(ptr_id);
8509 }
8510
Bullet()8511 void ImGui::Bullet()
8512 {
8513 ImGuiWindow* window = GetCurrentWindow();
8514 if (window->SkipItems)
8515 return;
8516
8517 ImGuiContext& g = *GImGui;
8518 const ImGuiStyle& style = g.Style;
8519 const float line_height = ImMax(ImMin(window->DC.CurrentLineHeight, g.FontSize + g.Style.FramePadding.y * 2), g.FontSize);
8520 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height));
8521 ItemSize(bb);
8522 if (!ItemAdd(bb, 0))
8523 {
8524 SameLine(0, style.FramePadding.x * 2);
8525 return;
8526 }
8527
8528 // Render and stay on same line
8529 RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, line_height * 0.5f));
8530 SameLine(0, style.FramePadding.x * 2);
8531 }
8532
8533 // Text with a little bullet aligned to the typical tree node.
BulletTextV(const char * fmt,va_list args)8534 void ImGui::BulletTextV(const char* fmt, va_list args)
8535 {
8536 ImGuiWindow* window = GetCurrentWindow();
8537 if (window->SkipItems)
8538 return;
8539
8540 ImGuiContext& g = *GImGui;
8541 const ImGuiStyle& style = g.Style;
8542
8543 const char* text_begin = g.TempBuffer;
8544 const char* text_end = text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args);
8545 const ImVec2 label_size = CalcTextSize(text_begin, text_end, false);
8546 const float text_base_offset_y = ImMax(0.0f, window->DC.CurrentLineTextBaseOffset); // Latch before ItemSize changes it
8547 const float line_height = ImMax(ImMin(window->DC.CurrentLineHeight, g.FontSize + g.Style.FramePadding.y * 2), g.FontSize);
8548 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
8549 ItemSize(bb);
8550 if (!ItemAdd(bb, 0))
8551 return;
8552
8553 // Render
8554 RenderBullet(bb.Min + ImVec2(style.FramePadding.x + g.FontSize * 0.5f, line_height * 0.5f));
8555 RenderText(bb.Min + ImVec2(g.FontSize + style.FramePadding.x * 2, text_base_offset_y), text_begin, text_end, false);
8556 }
8557
BulletText(const char * fmt,...)8558 void ImGui::BulletText(const char* fmt, ...)
8559 {
8560 va_list args;
8561 va_start(args, fmt);
8562 BulletTextV(fmt, args);
8563 va_end(args);
8564 }
8565
DataTypeFormatString(ImGuiDataType data_type,void * data_ptr,const char * display_format,char * buf,int buf_size)8566 static inline void DataTypeFormatString(ImGuiDataType data_type, void* data_ptr, const char* display_format, char* buf, int buf_size)
8567 {
8568 if (data_type == ImGuiDataType_Int)
8569 ImFormatString(buf, buf_size, display_format, *(int*)data_ptr);
8570 else if (data_type == ImGuiDataType_Float)
8571 ImFormatString(buf, buf_size, display_format, *(float*)data_ptr);
8572 }
8573
DataTypeFormatString(ImGuiDataType data_type,void * data_ptr,int decimal_precision,char * buf,int buf_size)8574 static inline void DataTypeFormatString(ImGuiDataType data_type, void* data_ptr, int decimal_precision, char* buf, int buf_size)
8575 {
8576 if (data_type == ImGuiDataType_Int)
8577 {
8578 if (decimal_precision < 0)
8579 ImFormatString(buf, buf_size, "%d", *(int*)data_ptr);
8580 else
8581 ImFormatString(buf, buf_size, "%.*d", decimal_precision, *(int*)data_ptr);
8582 }
8583 else if (data_type == ImGuiDataType_Float)
8584 {
8585 if (decimal_precision < 0)
8586 ImFormatString(buf, buf_size, "%f", *(float*)data_ptr); // Ideally we'd have a minimum decimal precision of 1 to visually denote that it is a float, while hiding non-significant digits?
8587 else
8588 ImFormatString(buf, buf_size, "%.*f", decimal_precision, *(float*)data_ptr);
8589 }
8590 }
8591
DataTypeApplyOp(ImGuiDataType data_type,int op,void * value1,const void * value2)8592 static void DataTypeApplyOp(ImGuiDataType data_type, int op, void* value1, const void* value2) // Store into value1
8593 {
8594 if (data_type == ImGuiDataType_Int)
8595 {
8596 if (op == '+')
8597 *(int*)value1 = *(int*)value1 + *(const int*)value2;
8598 else if (op == '-')
8599 *(int*)value1 = *(int*)value1 - *(const int*)value2;
8600 }
8601 else if (data_type == ImGuiDataType_Float)
8602 {
8603 if (op == '+')
8604 *(float*)value1 = *(float*)value1 + *(const float*)value2;
8605 else if (op == '-')
8606 *(float*)value1 = *(float*)value1 - *(const float*)value2;
8607 }
8608 }
8609
8610 // User can input math operators (e.g. +100) to edit a numerical values.
DataTypeApplyOpFromText(const char * buf,const char * initial_value_buf,ImGuiDataType data_type,void * data_ptr,const char * scalar_format)8611 static bool DataTypeApplyOpFromText(const char* buf, const char* initial_value_buf, ImGuiDataType data_type, void* data_ptr, const char* scalar_format)
8612 {
8613 while (ImCharIsSpace(*buf))
8614 buf++;
8615
8616 // We don't support '-' op because it would conflict with inputing negative value.
8617 // Instead you can use +-100 to subtract from an existing value
8618 char op = buf[0];
8619 if (op == '+' || op == '*' || op == '/')
8620 {
8621 buf++;
8622 while (ImCharIsSpace(*buf))
8623 buf++;
8624 }
8625 else
8626 {
8627 op = 0;
8628 }
8629 if (!buf[0])
8630 return false;
8631
8632 if (data_type == ImGuiDataType_Int)
8633 {
8634 if (!scalar_format)
8635 scalar_format = "%d";
8636 int* v = (int*)data_ptr;
8637 const int old_v = *v;
8638 int arg0i = *v;
8639 if (op && sscanf(initial_value_buf, scalar_format, &arg0i) < 1)
8640 return false;
8641
8642 // 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
8643 float arg1f = 0.0f;
8644 if (op == '+')
8645 {
8646 if (sscanf(buf, "%f", &arg1f) == 1) *v = (int)(arg0i + arg1f);
8647 } // Add (use "+-" to subtract)
8648 else if (op == '*')
8649 {
8650 if (sscanf(buf, "%f", &arg1f) == 1) *v = (int)(arg0i * arg1f);
8651 } // Multiply
8652 else if (op == '/')
8653 {
8654 if (sscanf(buf, "%f", &arg1f) == 1 && arg1f != 0.0f) *v = (int)(arg0i / arg1f);
8655 } // Divide
8656 else
8657 {
8658 if (sscanf(buf, scalar_format, &arg0i) == 1) *v = arg0i;
8659 } // Assign constant (read as integer so big values are not lossy)
8660 return (old_v != *v);
8661 }
8662 else if (data_type == ImGuiDataType_Float)
8663 {
8664 // For floats we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in
8665 scalar_format = "%f";
8666 float* v = (float*)data_ptr;
8667 const float old_v = *v;
8668 float arg0f = *v;
8669 if (op && sscanf(initial_value_buf, scalar_format, &arg0f) < 1)
8670 return false;
8671
8672 float arg1f = 0.0f;
8673 if (sscanf(buf, scalar_format, &arg1f) < 1)
8674 return false;
8675 if (op == '+')
8676 {
8677 *v = arg0f + arg1f;
8678 } // Add (use "+-" to subtract)
8679 else if (op == '*')
8680 {
8681 *v = arg0f * arg1f;
8682 } // Multiply
8683 else if (op == '/')
8684 {
8685 if (arg1f != 0.0f) *v = arg0f / arg1f;
8686 } // Divide
8687 else
8688 {
8689 *v = arg1f;
8690 } // Assign constant
8691 return (old_v != *v);
8692 }
8693
8694 return false;
8695 }
8696
8697 // Create text input in place of a slider (when CTRL+Clicking on slider)
8698 // FIXME: Logic is messy and confusing.
InputScalarAsWidgetReplacement(const ImRect & aabb,const char * label,ImGuiDataType data_type,void * data_ptr,ImGuiID id,int decimal_precision)8699 bool ImGui::InputScalarAsWidgetReplacement(const ImRect& aabb, const char* label, ImGuiDataType data_type, void* data_ptr, ImGuiID id, int decimal_precision)
8700 {
8701 ImGuiContext& g = *GImGui;
8702 ImGuiWindow* window = GetCurrentWindow();
8703
8704 // Our replacement widget will override the focus ID (registered previously to allow for a TAB focus to happen)
8705 // On the first frame, g.ScalarAsInputTextId == 0, then on subsequent frames it becomes == id
8706 SetActiveID(g.ScalarAsInputTextId, window);
8707 g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
8708 SetHoveredID(0);
8709 FocusableItemUnregister(window);
8710
8711 char buf[32];
8712 DataTypeFormatString(data_type, data_ptr, decimal_precision, buf, IM_ARRAYSIZE(buf));
8713 bool text_value_changed = InputTextEx(label, buf, IM_ARRAYSIZE(buf), aabb.GetSize(), ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_AutoSelectAll);
8714 if (g.ScalarAsInputTextId == 0) // First frame we started displaying the InputText widget
8715 {
8716 IM_ASSERT(g.ActiveId == id); // InputText ID expected to match the Slider ID (else we'd need to store them both, which is also possible)
8717 g.ScalarAsInputTextId = g.ActiveId;
8718 SetHoveredID(id);
8719 }
8720 if (text_value_changed)
8721 return DataTypeApplyOpFromText(buf, GImGui->InputTextState.InitialText.begin(), data_type, data_ptr, NULL);
8722 return false;
8723 }
8724
8725 // Parse display precision back from the display format string
ParseFormatPrecision(const char * fmt,int default_precision)8726 int ImGui::ParseFormatPrecision(const char* fmt, int default_precision)
8727 {
8728 int precision = default_precision;
8729 while ((fmt = strchr(fmt, '%')) != NULL)
8730 {
8731 fmt++;
8732 if (fmt[0] == '%')
8733 {
8734 fmt++;
8735 continue;
8736 } // Ignore "%%"
8737 while (*fmt >= '0' && *fmt <= '9')
8738 fmt++;
8739 if (*fmt == '.')
8740 {
8741 fmt = ImAtoi(fmt + 1, &precision);
8742 if (precision < 0 || precision > 10)
8743 precision = default_precision;
8744 }
8745 if (*fmt == 'e' || *fmt == 'E') // Maximum precision with scientific notation
8746 precision = -1;
8747 break;
8748 }
8749 return precision;
8750 }
8751
GetMinimumStepAtDecimalPrecision(int decimal_precision)8752 static float GetMinimumStepAtDecimalPrecision(int decimal_precision)
8753 {
8754 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};
8755 return (decimal_precision >= 0 && decimal_precision < 10) ? min_steps[decimal_precision] : powf(10.0f, (float)-decimal_precision);
8756 }
8757
RoundScalar(float value,int decimal_precision)8758 float ImGui::RoundScalar(float value, int decimal_precision)
8759 {
8760 // Round past decimal precision
8761 // So when our value is 1.99999 with a precision of 0.001 we'll end up rounding to 2.0
8762 // FIXME: Investigate better rounding methods
8763 if (decimal_precision < 0)
8764 return value;
8765 const float min_step = GetMinimumStepAtDecimalPrecision(decimal_precision);
8766 bool negative = value < 0.0f;
8767 value = fabsf(value);
8768 float remainder = fmodf(value, min_step);
8769 if (remainder <= min_step * 0.5f)
8770 value -= remainder;
8771 else
8772 value += (min_step - remainder);
8773 return negative ? -value : value;
8774 }
8775
SliderBehaviorCalcRatioFromValue(float v,float v_min,float v_max,float power,float linear_zero_pos)8776 static inline float SliderBehaviorCalcRatioFromValue(float v, float v_min, float v_max, float power, float linear_zero_pos)
8777 {
8778 if (v_min == v_max)
8779 return 0.0f;
8780
8781 const bool is_non_linear = (power < 1.0f - 0.00001f) || (power > 1.0f + 0.00001f);
8782 const float v_clamped = (v_min < v_max) ? ImClamp(v, v_min, v_max) : ImClamp(v, v_max, v_min);
8783 if (is_non_linear)
8784 {
8785 if (v_clamped < 0.0f)
8786 {
8787 const float f = 1.0f - (v_clamped - v_min) / (ImMin(0.0f, v_max) - v_min);
8788 return (1.0f - powf(f, 1.0f / power)) * linear_zero_pos;
8789 }
8790 else
8791 {
8792 const float f = (v_clamped - ImMax(0.0f, v_min)) / (v_max - ImMax(0.0f, v_min));
8793 return linear_zero_pos + powf(f, 1.0f / power) * (1.0f - linear_zero_pos);
8794 }
8795 }
8796
8797 // Linear slider
8798 return (v_clamped - v_min) / (v_max - v_min);
8799 }
8800
SliderBehavior(const ImRect & frame_bb,ImGuiID id,float * v,float v_min,float v_max,float power,int decimal_precision,ImGuiSliderFlags flags)8801 bool ImGui::SliderBehavior(const ImRect& frame_bb, ImGuiID id, float* v, float v_min, float v_max, float power, int decimal_precision, ImGuiSliderFlags flags)
8802 {
8803 ImGuiContext& g = *GImGui;
8804 ImGuiWindow* window = GetCurrentWindow();
8805 const ImGuiStyle& style = g.Style;
8806
8807 // Draw frame
8808 const ImU32 frame_col = GetColorU32((g.ActiveId == id && g.ActiveIdSource == ImGuiInputSource_Nav) ? ImGuiCol_FrameBgActive : ImGuiCol_FrameBg);
8809 RenderNavHighlight(frame_bb, id);
8810 RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding);
8811
8812 const bool is_non_linear = (power < 1.0f - 0.00001f) || (power > 1.0f + 0.00001f);
8813 const bool is_horizontal = (flags & ImGuiSliderFlags_Vertical) == 0;
8814
8815 const float grab_padding = 2.0f;
8816 const float slider_sz = is_horizontal ? (frame_bb.GetWidth() - grab_padding * 2.0f) : (frame_bb.GetHeight() - grab_padding * 2.0f);
8817 float grab_sz;
8818 if (decimal_precision != 0)
8819 grab_sz = ImMin(style.GrabMinSize, slider_sz);
8820 else
8821 grab_sz = ImMin(ImMax(1.0f * (slider_sz / ((v_min < v_max ? v_max - v_min : v_min - v_max) + 1.0f)), style.GrabMinSize), slider_sz); // Integer sliders, if possible have the grab size represent 1 unit
8822 const float slider_usable_sz = slider_sz - grab_sz;
8823 const float slider_usable_pos_min = (is_horizontal ? frame_bb.Min.x : frame_bb.Min.y) + grab_padding + grab_sz * 0.5f;
8824 const float slider_usable_pos_max = (is_horizontal ? frame_bb.Max.x : frame_bb.Max.y) - grab_padding - grab_sz * 0.5f;
8825
8826 // For logarithmic sliders that cross over sign boundary we want the exponential increase to be symmetric around 0.0f
8827 float linear_zero_pos = 0.0f; // 0.0->1.0f
8828 if (v_min * v_max < 0.0f)
8829 {
8830 // Different sign
8831 const float linear_dist_min_to_0 = powf(fabsf(0.0f - v_min), 1.0f / power);
8832 const float linear_dist_max_to_0 = powf(fabsf(v_max - 0.0f), 1.0f / power);
8833 linear_zero_pos = linear_dist_min_to_0 / (linear_dist_min_to_0 + linear_dist_max_to_0);
8834 }
8835 else
8836 {
8837 // Same sign
8838 linear_zero_pos = v_min < 0.0f ? 1.0f : 0.0f;
8839 }
8840
8841 // Process interacting with the slider
8842 bool value_changed = false;
8843 if (g.ActiveId == id)
8844 {
8845 bool set_new_value = false;
8846 float clicked_t = 0.0f;
8847 if (g.ActiveIdSource == ImGuiInputSource_Mouse)
8848 {
8849 if (!g.IO.MouseDown[0])
8850 {
8851 ClearActiveID();
8852 }
8853 else
8854 {
8855 const float mouse_abs_pos = is_horizontal ? g.IO.MousePos.x : g.IO.MousePos.y;
8856 clicked_t = (slider_usable_sz > 0.0f) ? ImClamp((mouse_abs_pos - slider_usable_pos_min) / slider_usable_sz, 0.0f, 1.0f) : 0.0f;
8857 if (!is_horizontal)
8858 clicked_t = 1.0f - clicked_t;
8859 set_new_value = true;
8860 }
8861 }
8862 else if (g.ActiveIdSource == ImGuiInputSource_Nav)
8863 {
8864 const ImVec2 delta2 = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 0.0f, 0.0f);
8865 float delta = is_horizontal ? delta2.x : -delta2.y;
8866 if (g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
8867 {
8868 ClearActiveID();
8869 }
8870 else if (delta != 0.0f)
8871 {
8872 clicked_t = SliderBehaviorCalcRatioFromValue(*v, v_min, v_max, power, linear_zero_pos);
8873 if (decimal_precision == 0 && !is_non_linear)
8874 {
8875 if (fabsf(v_max - v_min) <= 100.0f || IsNavInputDown(ImGuiNavInput_TweakSlow))
8876 delta = ((delta < 0.0f) ? -1.0f : +1.0f) / (v_max - v_min); // Gamepad/keyboard tweak speeds in integer steps
8877 else
8878 delta /= 100.0f;
8879 }
8880 else
8881 {
8882 delta /= 100.0f; // Gamepad/keyboard tweak speeds in % of slider bounds
8883 if (IsNavInputDown(ImGuiNavInput_TweakSlow))
8884 delta /= 10.0f;
8885 }
8886 if (IsNavInputDown(ImGuiNavInput_TweakFast))
8887 delta *= 10.0f;
8888 set_new_value = true;
8889 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
8890 set_new_value = false;
8891 else
8892 clicked_t = ImSaturate(clicked_t + delta);
8893 }
8894 }
8895
8896 if (set_new_value)
8897 {
8898 float new_value;
8899 if (is_non_linear)
8900 {
8901 // Account for logarithmic scale on both sides of the zero
8902 if (clicked_t < linear_zero_pos)
8903 {
8904 // Negative: rescale to the negative range before powering
8905 float a = 1.0f - (clicked_t / linear_zero_pos);
8906 a = powf(a, power);
8907 new_value = ImLerp(ImMin(v_max, 0.0f), v_min, a);
8908 }
8909 else
8910 {
8911 // Positive: rescale to the positive range before powering
8912 float a;
8913 if (fabsf(linear_zero_pos - 1.0f) > 1.e-6f)
8914 a = (clicked_t - linear_zero_pos) / (1.0f - linear_zero_pos);
8915 else
8916 a = clicked_t;
8917 a = powf(a, power);
8918 new_value = ImLerp(ImMax(v_min, 0.0f), v_max, a);
8919 }
8920 }
8921 else
8922 {
8923 // Linear slider
8924 new_value = ImLerp(v_min, v_max, clicked_t);
8925 }
8926
8927 // Round past decimal precision
8928 new_value = RoundScalar(new_value, decimal_precision);
8929 if (*v != new_value)
8930 {
8931 *v = new_value;
8932 value_changed = true;
8933 }
8934 }
8935 }
8936
8937 // Draw
8938 float grab_t = SliderBehaviorCalcRatioFromValue(*v, v_min, v_max, power, linear_zero_pos);
8939 if (!is_horizontal)
8940 grab_t = 1.0f - grab_t;
8941 const float grab_pos = ImLerp(slider_usable_pos_min, slider_usable_pos_max, grab_t);
8942 ImRect grab_bb;
8943 if (is_horizontal)
8944 grab_bb = ImRect(ImVec2(grab_pos - grab_sz * 0.5f, frame_bb.Min.y + grab_padding), ImVec2(grab_pos + grab_sz * 0.5f, frame_bb.Max.y - grab_padding));
8945 else
8946 grab_bb = ImRect(ImVec2(frame_bb.Min.x + grab_padding, grab_pos - grab_sz * 0.5f), ImVec2(frame_bb.Max.x - grab_padding, grab_pos + grab_sz * 0.5f));
8947 window->DrawList->AddRectFilled(grab_bb.Min, grab_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_SliderGrabActive : ImGuiCol_SliderGrab), style.GrabRounding);
8948
8949 return value_changed;
8950 }
8951
8952 // Use power!=1.0 for logarithmic sliders.
8953 // Adjust display_format to decorate the value with a prefix or a suffix.
8954 // "%.3f" 1.234
8955 // "%5.2f secs" 01.23 secs
8956 // "Gold: %.0f" Gold: 1
SliderFloat(const char * label,float * v,float v_min,float v_max,const char * display_format,float power)8957 bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, const char* display_format, float power)
8958 {
8959 ImGuiWindow* window = GetCurrentWindow();
8960 if (window->SkipItems)
8961 return false;
8962
8963 ImGuiContext& g = *GImGui;
8964 const ImGuiStyle& style = g.Style;
8965 const ImGuiID id = window->GetID(label);
8966 const float w = CalcItemWidth();
8967
8968 const ImVec2 label_size = CalcTextSize(label, NULL, true);
8969 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
8970 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));
8971
8972 // NB- we don't call ItemSize() yet because we may turn into a text edit box below
8973 if (!ItemAdd(total_bb, id, &frame_bb))
8974 {
8975 ItemSize(total_bb, style.FramePadding.y);
8976 return false;
8977 }
8978 const bool hovered = ItemHoverable(frame_bb, id);
8979
8980 if (!display_format)
8981 display_format = "%.3f";
8982 int decimal_precision = ParseFormatPrecision(display_format, 3);
8983
8984 // Tabbing or CTRL-clicking on Slider turns it into an input box
8985 bool start_text_input = false;
8986 const bool tab_focus_requested = FocusableItemRegister(window, id);
8987 if (tab_focus_requested || (hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || (g.NavInputId == id && g.ScalarAsInputTextId != id))
8988 {
8989 SetActiveID(id, window);
8990 SetFocusID(id, window);
8991 FocusWindow(window);
8992 g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
8993 if (tab_focus_requested || g.IO.KeyCtrl || g.NavInputId == id)
8994 {
8995 start_text_input = true;
8996 g.ScalarAsInputTextId = 0;
8997 }
8998 }
8999 if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id))
9000 return InputScalarAsWidgetReplacement(frame_bb, label, ImGuiDataType_Float, v, id, decimal_precision);
9001
9002 // Actual slider behavior + render grab
9003 ItemSize(total_bb, style.FramePadding.y);
9004 const bool value_changed = SliderBehavior(frame_bb, id, v, v_min, v_max, power, decimal_precision);
9005
9006 // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
9007 char value_buf[64];
9008 const char* value_buf_end = value_buf + ImFormatString(value_buf, IM_ARRAYSIZE(value_buf), display_format, *v);
9009 RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
9010
9011 if (label_size.x > 0.0f)
9012 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
9013
9014 return value_changed;
9015 }
9016
VSliderFloat(const char * label,const ImVec2 & size,float * v,float v_min,float v_max,const char * display_format,float power)9017 bool ImGui::VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* display_format, float power)
9018 {
9019 ImGuiWindow* window = GetCurrentWindow();
9020 if (window->SkipItems)
9021 return false;
9022
9023 ImGuiContext& g = *GImGui;
9024 const ImGuiStyle& style = g.Style;
9025 const ImGuiID id = window->GetID(label);
9026
9027 const ImVec2 label_size = CalcTextSize(label, NULL, true);
9028 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
9029 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));
9030
9031 ItemSize(bb, style.FramePadding.y);
9032 if (!ItemAdd(frame_bb, id))
9033 return false;
9034 const bool hovered = ItemHoverable(frame_bb, id);
9035
9036 if (!display_format)
9037 display_format = "%.3f";
9038 int decimal_precision = ParseFormatPrecision(display_format, 3);
9039
9040 if ((hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavInputId == id)
9041 {
9042 SetActiveID(id, window);
9043 SetFocusID(id, window);
9044 FocusWindow(window);
9045 g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
9046 }
9047
9048 // Actual slider behavior + render grab
9049 bool value_changed = SliderBehavior(frame_bb, id, v, v_min, v_max, power, decimal_precision, ImGuiSliderFlags_Vertical);
9050
9051 // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
9052 // For the vertical slider we allow centered text to overlap the frame padding
9053 char value_buf[64];
9054 char* value_buf_end = value_buf + ImFormatString(value_buf, IM_ARRAYSIZE(value_buf), display_format, *v);
9055 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));
9056 if (label_size.x > 0.0f)
9057 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
9058
9059 return value_changed;
9060 }
9061
SliderAngle(const char * label,float * v_rad,float v_degrees_min,float v_degrees_max)9062 bool ImGui::SliderAngle(const char* label, float* v_rad, float v_degrees_min, float v_degrees_max)
9063 {
9064 float v_deg = (*v_rad) * 360.0f / (2 * IM_PI);
9065 bool value_changed = SliderFloat(label, &v_deg, v_degrees_min, v_degrees_max, "%.0f deg", 1.0f);
9066 *v_rad = v_deg * (2 * IM_PI) / 360.0f;
9067 return value_changed;
9068 }
9069
SliderInt(const char * label,int * v,int v_min,int v_max,const char * display_format)9070 bool ImGui::SliderInt(const char* label, int* v, int v_min, int v_max, const char* display_format)
9071 {
9072 if (!display_format)
9073 display_format = "%.0f";
9074 float v_f = (float)*v;
9075 bool value_changed = SliderFloat(label, &v_f, (float)v_min, (float)v_max, display_format, 1.0f);
9076 *v = (int)v_f;
9077 return value_changed;
9078 }
9079
VSliderInt(const char * label,const ImVec2 & size,int * v,int v_min,int v_max,const char * display_format)9080 bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* display_format)
9081 {
9082 if (!display_format)
9083 display_format = "%.0f";
9084 float v_f = (float)*v;
9085 bool value_changed = VSliderFloat(label, size, &v_f, (float)v_min, (float)v_max, display_format, 1.0f);
9086 *v = (int)v_f;
9087 return value_changed;
9088 }
9089
9090 // Add multiple sliders on 1 line for compact edition of multiple components
SliderFloatN(const char * label,float * v,int components,float v_min,float v_max,const char * display_format,float power)9091 bool ImGui::SliderFloatN(const char* label, float* v, int components, float v_min, float v_max, const char* display_format, float power)
9092 {
9093 ImGuiWindow* window = GetCurrentWindow();
9094 if (window->SkipItems)
9095 return false;
9096
9097 ImGuiContext& g = *GImGui;
9098 bool value_changed = false;
9099 BeginGroup();
9100 PushID(label);
9101 PushMultiItemsWidths(components);
9102 for (int i = 0; i < components; i++)
9103 {
9104 PushID(i);
9105 value_changed |= SliderFloat("##v", &v[i], v_min, v_max, display_format, power);
9106 SameLine(0, g.Style.ItemInnerSpacing.x);
9107 PopID();
9108 PopItemWidth();
9109 }
9110 PopID();
9111
9112 TextUnformatted(label, FindRenderedTextEnd(label));
9113 EndGroup();
9114
9115 return value_changed;
9116 }
9117
SliderFloat2(const char * label,float v[2],float v_min,float v_max,const char * display_format,float power)9118 bool ImGui::SliderFloat2(const char* label, float v[2], float v_min, float v_max, const char* display_format, float power)
9119 {
9120 return SliderFloatN(label, v, 2, v_min, v_max, display_format, power);
9121 }
9122
SliderFloat3(const char * label,float v[3],float v_min,float v_max,const char * display_format,float power)9123 bool ImGui::SliderFloat3(const char* label, float v[3], float v_min, float v_max, const char* display_format, float power)
9124 {
9125 return SliderFloatN(label, v, 3, v_min, v_max, display_format, power);
9126 }
9127
SliderFloat4(const char * label,float v[4],float v_min,float v_max,const char * display_format,float power)9128 bool ImGui::SliderFloat4(const char* label, float v[4], float v_min, float v_max, const char* display_format, float power)
9129 {
9130 return SliderFloatN(label, v, 4, v_min, v_max, display_format, power);
9131 }
9132
SliderIntN(const char * label,int * v,int components,int v_min,int v_max,const char * display_format)9133 bool ImGui::SliderIntN(const char* label, int* v, int components, int v_min, int v_max, const char* display_format)
9134 {
9135 ImGuiWindow* window = GetCurrentWindow();
9136 if (window->SkipItems)
9137 return false;
9138
9139 ImGuiContext& g = *GImGui;
9140 bool value_changed = false;
9141 BeginGroup();
9142 PushID(label);
9143 PushMultiItemsWidths(components);
9144 for (int i = 0; i < components; i++)
9145 {
9146 PushID(i);
9147 value_changed |= SliderInt("##v", &v[i], v_min, v_max, display_format);
9148 SameLine(0, g.Style.ItemInnerSpacing.x);
9149 PopID();
9150 PopItemWidth();
9151 }
9152 PopID();
9153
9154 TextUnformatted(label, FindRenderedTextEnd(label));
9155 EndGroup();
9156
9157 return value_changed;
9158 }
9159
SliderInt2(const char * label,int v[2],int v_min,int v_max,const char * display_format)9160 bool ImGui::SliderInt2(const char* label, int v[2], int v_min, int v_max, const char* display_format)
9161 {
9162 return SliderIntN(label, v, 2, v_min, v_max, display_format);
9163 }
9164
SliderInt3(const char * label,int v[3],int v_min,int v_max,const char * display_format)9165 bool ImGui::SliderInt3(const char* label, int v[3], int v_min, int v_max, const char* display_format)
9166 {
9167 return SliderIntN(label, v, 3, v_min, v_max, display_format);
9168 }
9169
SliderInt4(const char * label,int v[4],int v_min,int v_max,const char * display_format)9170 bool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* display_format)
9171 {
9172 return SliderIntN(label, v, 4, v_min, v_max, display_format);
9173 }
9174
DragBehavior(const ImRect & frame_bb,ImGuiID id,float * v,float v_speed,float v_min,float v_max,int decimal_precision,float power)9175 bool ImGui::DragBehavior(const ImRect& frame_bb, ImGuiID id, float* v, float v_speed, float v_min, float v_max, int decimal_precision, float power)
9176 {
9177 ImGuiContext& g = *GImGui;
9178 const ImGuiStyle& style = g.Style;
9179
9180 // Draw frame
9181 const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
9182 RenderNavHighlight(frame_bb, id);
9183 RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding);
9184
9185 bool value_changed = false;
9186
9187 // Process interacting with the drag
9188 if (g.ActiveId == id)
9189 {
9190 if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0])
9191 ClearActiveID();
9192 else if (g.ActiveIdSource == ImGuiInputSource_Nav && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
9193 ClearActiveID();
9194 }
9195 if (g.ActiveId == id)
9196 {
9197 if (g.ActiveIdIsJustActivated)
9198 {
9199 // Lock current value on click
9200 g.DragCurrentValue = *v;
9201 g.DragLastMouseDelta = ImVec2(0.f, 0.f);
9202 }
9203
9204 if (v_speed == 0.0f && (v_max - v_min) != 0.0f && (v_max - v_min) < FLT_MAX)
9205 v_speed = (v_max - v_min) * g.DragSpeedDefaultRatio;
9206
9207 float v_cur = g.DragCurrentValue;
9208 const ImVec2 mouse_drag_delta = GetMouseDragDelta(0, 1.0f);
9209 float adjust_delta = 0.0f;
9210 if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid())
9211 {
9212 adjust_delta = mouse_drag_delta.x - g.DragLastMouseDelta.x;
9213 if (g.IO.KeyShift && g.DragSpeedScaleFast >= 0.0f)
9214 adjust_delta *= g.DragSpeedScaleFast;
9215 if (g.IO.KeyAlt && g.DragSpeedScaleSlow >= 0.0f)
9216 adjust_delta *= g.DragSpeedScaleSlow;
9217 g.DragLastMouseDelta.x = mouse_drag_delta.x;
9218 }
9219 if (g.ActiveIdSource == ImGuiInputSource_Nav)
9220 {
9221 adjust_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 1.0f / 10.0f, 10.0f).x;
9222 if (v_min < v_max && ((v_cur >= v_max && adjust_delta > 0.0f) || (v_cur <= v_min && adjust_delta < 0.0f))) // This is to avoid applying the saturation when already past the limits
9223 adjust_delta = 0.0f;
9224 v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision));
9225 }
9226 adjust_delta *= v_speed;
9227
9228 if (fabsf(adjust_delta) > 0.0f)
9229 {
9230 if (fabsf(power - 1.0f) > 0.001f)
9231 {
9232 // Logarithmic curve on both side of 0.0
9233 float v0_abs = v_cur >= 0.0f ? v_cur : -v_cur;
9234 float v0_sign = v_cur >= 0.0f ? 1.0f : -1.0f;
9235 float v1 = powf(v0_abs, 1.0f / power) + (adjust_delta * v0_sign);
9236 float v1_abs = v1 >= 0.0f ? v1 : -v1;
9237 float v1_sign = v1 >= 0.0f ? 1.0f : -1.0f; // Crossed sign line
9238 v_cur = powf(v1_abs, power) * v0_sign * v1_sign; // Reapply sign
9239 }
9240 else
9241 {
9242 v_cur += adjust_delta;
9243 }
9244
9245 // Clamp
9246 if (v_min < v_max)
9247 v_cur = ImClamp(v_cur, v_min, v_max);
9248 g.DragCurrentValue = v_cur;
9249 }
9250
9251 // Round to user desired precision, then apply
9252 v_cur = RoundScalar(v_cur, decimal_precision);
9253 if (*v != v_cur)
9254 {
9255 *v = v_cur;
9256 value_changed = true;
9257 }
9258 }
9259
9260 return value_changed;
9261 }
9262
DragFloat(const char * label,float * v,float v_speed,float v_min,float v_max,const char * display_format,float power)9263 bool ImGui::DragFloat(const char* label, float* v, float v_speed, float v_min, float v_max, const char* display_format, float power)
9264 {
9265 ImGuiWindow* window = GetCurrentWindow();
9266 if (window->SkipItems)
9267 return false;
9268
9269 ImGuiContext& g = *GImGui;
9270 const ImGuiStyle& style = g.Style;
9271 const ImGuiID id = window->GetID(label);
9272 const float w = CalcItemWidth();
9273
9274 const ImVec2 label_size = CalcTextSize(label, NULL, true);
9275 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
9276 const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
9277 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));
9278
9279 // NB- we don't call ItemSize() yet because we may turn into a text edit box below
9280 if (!ItemAdd(total_bb, id, &frame_bb))
9281 {
9282 ItemSize(total_bb, style.FramePadding.y);
9283 return false;
9284 }
9285 const bool hovered = ItemHoverable(frame_bb, id);
9286
9287 if (!display_format)
9288 display_format = "%.3f";
9289 int decimal_precision = ParseFormatPrecision(display_format, 3);
9290
9291 // Tabbing or CTRL-clicking on Drag turns it into an input box
9292 bool start_text_input = false;
9293 const bool tab_focus_requested = FocusableItemRegister(window, id);
9294 if (tab_focus_requested || (hovered && (g.IO.MouseClicked[0] || g.IO.MouseDoubleClicked[0])) || g.NavActivateId == id || (g.NavInputId == id && g.ScalarAsInputTextId != id))
9295 {
9296 SetActiveID(id, window);
9297 SetFocusID(id, window);
9298 FocusWindow(window);
9299 g.ActiveIdAllowNavDirFlags = (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
9300 if (tab_focus_requested || g.IO.KeyCtrl || g.IO.MouseDoubleClicked[0] || g.NavInputId == id)
9301 {
9302 start_text_input = true;
9303 g.ScalarAsInputTextId = 0;
9304 }
9305 }
9306 if (start_text_input || (g.ActiveId == id && g.ScalarAsInputTextId == id))
9307 return InputScalarAsWidgetReplacement(frame_bb, label, ImGuiDataType_Float, v, id, decimal_precision);
9308
9309 // Actual drag behavior
9310 ItemSize(total_bb, style.FramePadding.y);
9311 const bool value_changed = DragBehavior(frame_bb, id, v, v_speed, v_min, v_max, decimal_precision, power);
9312
9313 // Display value using user-provided display format so user can add prefix/suffix/decorations to the value.
9314 char value_buf[64];
9315 const char* value_buf_end = value_buf + ImFormatString(value_buf, IM_ARRAYSIZE(value_buf), display_format, *v);
9316 RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
9317
9318 if (label_size.x > 0.0f)
9319 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label);
9320
9321 return value_changed;
9322 }
9323
DragFloatN(const char * label,float * v,int components,float v_speed,float v_min,float v_max,const char * display_format,float power)9324 bool ImGui::DragFloatN(const char* label, float* v, int components, float v_speed, float v_min, float v_max, const char* display_format, float power)
9325 {
9326 ImGuiWindow* window = GetCurrentWindow();
9327 if (window->SkipItems)
9328 return false;
9329
9330 ImGuiContext& g = *GImGui;
9331 bool value_changed = false;
9332 BeginGroup();
9333 PushID(label);
9334 PushMultiItemsWidths(components);
9335 for (int i = 0; i < components; i++)
9336 {
9337 PushID(i);
9338 value_changed |= DragFloat("##v", &v[i], v_speed, v_min, v_max, display_format, power);
9339 SameLine(0, g.Style.ItemInnerSpacing.x);
9340 PopID();
9341 PopItemWidth();
9342 }
9343 PopID();
9344
9345 TextUnformatted(label, FindRenderedTextEnd(label));
9346 EndGroup();
9347
9348 return value_changed;
9349 }
9350
DragFloat2(const char * label,float v[2],float v_speed,float v_min,float v_max,const char * display_format,float power)9351 bool ImGui::DragFloat2(const char* label, float v[2], float v_speed, float v_min, float v_max, const char* display_format, float power)
9352 {
9353 return DragFloatN(label, v, 2, v_speed, v_min, v_max, display_format, power);
9354 }
9355
DragFloat3(const char * label,float v[3],float v_speed,float v_min,float v_max,const char * display_format,float power)9356 bool ImGui::DragFloat3(const char* label, float v[3], float v_speed, float v_min, float v_max, const char* display_format, float power)
9357 {
9358 return DragFloatN(label, v, 3, v_speed, v_min, v_max, display_format, power);
9359 }
9360
DragFloat4(const char * label,float v[4],float v_speed,float v_min,float v_max,const char * display_format,float power)9361 bool ImGui::DragFloat4(const char* label, float v[4], float v_speed, float v_min, float v_max, const char* display_format, float power)
9362 {
9363 return DragFloatN(label, v, 4, v_speed, v_min, v_max, display_format, power);
9364 }
9365
DragFloatRange2(const char * label,float * v_current_min,float * v_current_max,float v_speed,float v_min,float v_max,const char * display_format,const char * display_format_max,float power)9366 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* display_format, const char* display_format_max, float power)
9367 {
9368 ImGuiWindow* window = GetCurrentWindow();
9369 if (window->SkipItems)
9370 return false;
9371
9372 ImGuiContext& g = *GImGui;
9373 PushID(label);
9374 BeginGroup();
9375 PushMultiItemsWidths(2);
9376
9377 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), display_format, power);
9378 PopItemWidth();
9379 SameLine(0, g.Style.ItemInnerSpacing.x);
9380 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, display_format_max ? display_format_max : display_format, power);
9381 PopItemWidth();
9382 SameLine(0, g.Style.ItemInnerSpacing.x);
9383
9384 TextUnformatted(label, FindRenderedTextEnd(label));
9385 EndGroup();
9386 PopID();
9387
9388 return value_changed;
9389 }
9390
9391 // NB: v_speed is float to allow adjusting the drag speed with more precision
DragInt(const char * label,int * v,float v_speed,int v_min,int v_max,const char * display_format)9392 bool ImGui::DragInt(const char* label, int* v, float v_speed, int v_min, int v_max, const char* display_format)
9393 {
9394 if (!display_format)
9395 display_format = "%.0f";
9396 float v_f = (float)*v;
9397 bool value_changed = DragFloat(label, &v_f, v_speed, (float)v_min, (float)v_max, display_format);
9398 *v = (int)v_f;
9399 return value_changed;
9400 }
9401
DragIntN(const char * label,int * v,int components,float v_speed,int v_min,int v_max,const char * display_format)9402 bool ImGui::DragIntN(const char* label, int* v, int components, float v_speed, int v_min, int v_max, const char* display_format)
9403 {
9404 ImGuiWindow* window = GetCurrentWindow();
9405 if (window->SkipItems)
9406 return false;
9407
9408 ImGuiContext& g = *GImGui;
9409 bool value_changed = false;
9410 BeginGroup();
9411 PushID(label);
9412 PushMultiItemsWidths(components);
9413 for (int i = 0; i < components; i++)
9414 {
9415 PushID(i);
9416 value_changed |= DragInt("##v", &v[i], v_speed, v_min, v_max, display_format);
9417 SameLine(0, g.Style.ItemInnerSpacing.x);
9418 PopID();
9419 PopItemWidth();
9420 }
9421 PopID();
9422
9423 TextUnformatted(label, FindRenderedTextEnd(label));
9424 EndGroup();
9425
9426 return value_changed;
9427 }
9428
DragInt2(const char * label,int v[2],float v_speed,int v_min,int v_max,const char * display_format)9429 bool ImGui::DragInt2(const char* label, int v[2], float v_speed, int v_min, int v_max, const char* display_format)
9430 {
9431 return DragIntN(label, v, 2, v_speed, v_min, v_max, display_format);
9432 }
9433
DragInt3(const char * label,int v[3],float v_speed,int v_min,int v_max,const char * display_format)9434 bool ImGui::DragInt3(const char* label, int v[3], float v_speed, int v_min, int v_max, const char* display_format)
9435 {
9436 return DragIntN(label, v, 3, v_speed, v_min, v_max, display_format);
9437 }
9438
DragInt4(const char * label,int v[4],float v_speed,int v_min,int v_max,const char * display_format)9439 bool ImGui::DragInt4(const char* label, int v[4], float v_speed, int v_min, int v_max, const char* display_format)
9440 {
9441 return DragIntN(label, v, 4, v_speed, v_min, v_max, display_format);
9442 }
9443
DragIntRange2(const char * label,int * v_current_min,int * v_current_max,float v_speed,int v_min,int v_max,const char * display_format,const char * display_format_max)9444 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* display_format, const char* display_format_max)
9445 {
9446 ImGuiWindow* window = GetCurrentWindow();
9447 if (window->SkipItems)
9448 return false;
9449
9450 ImGuiContext& g = *GImGui;
9451 PushID(label);
9452 BeginGroup();
9453 PushMultiItemsWidths(2);
9454
9455 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), display_format);
9456 PopItemWidth();
9457 SameLine(0, g.Style.ItemInnerSpacing.x);
9458 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, display_format_max ? display_format_max : display_format);
9459 PopItemWidth();
9460 SameLine(0, g.Style.ItemInnerSpacing.x);
9461
9462 TextUnformatted(label, FindRenderedTextEnd(label));
9463 EndGroup();
9464 PopID();
9465
9466 return value_changed;
9467 }
9468
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)9469 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)
9470 {
9471 ImGuiWindow* window = GetCurrentWindow();
9472 if (window->SkipItems)
9473 return;
9474
9475 ImGuiContext& g = *GImGui;
9476 const ImGuiStyle& style = g.Style;
9477
9478 const ImVec2 label_size = CalcTextSize(label, NULL, true);
9479 if (graph_size.x == 0.0f)
9480 graph_size.x = CalcItemWidth();
9481 if (graph_size.y == 0.0f)
9482 graph_size.y = label_size.y + (style.FramePadding.y * 2);
9483
9484 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(graph_size.x, graph_size.y));
9485 const ImRect inner_bb(frame_bb.Min + style.FramePadding, frame_bb.Max - style.FramePadding);
9486 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));
9487 ItemSize(total_bb, style.FramePadding.y);
9488 if (!ItemAdd(total_bb, 0, &frame_bb))
9489 return;
9490 const bool hovered = ItemHoverable(inner_bb, 0);
9491
9492 // Determine scale from values if not specified
9493 if (scale_min == FLT_MAX || scale_max == FLT_MAX)
9494 {
9495 float v_min = FLT_MAX;
9496 float v_max = -FLT_MAX;
9497 for (int i = 0; i < values_count; i++)
9498 {
9499 const float v = values_getter(data, i);
9500 v_min = ImMin(v_min, v);
9501 v_max = ImMax(v_max, v);
9502 }
9503 if (scale_min == FLT_MAX)
9504 scale_min = v_min;
9505 if (scale_max == FLT_MAX)
9506 scale_max = v_max;
9507 }
9508
9509 RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
9510
9511 if (values_count > 0)
9512 {
9513 int res_w = ImMin((int)graph_size.x, values_count) + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
9514 int item_count = values_count + ((plot_type == ImGuiPlotType_Lines) ? -1 : 0);
9515
9516 // Tooltip on hover
9517 int v_hovered = -1;
9518 if (hovered)
9519 {
9520 const float t = ImClamp((g.IO.MousePos.x - inner_bb.Min.x) / (inner_bb.Max.x - inner_bb.Min.x), 0.0f, 0.9999f);
9521 const int v_idx = (int)(t * item_count);
9522 IM_ASSERT(v_idx >= 0 && v_idx < values_count);
9523
9524 const float v0 = values_getter(data, (v_idx + values_offset) % values_count);
9525 const float v1 = values_getter(data, (v_idx + 1 + values_offset) % values_count);
9526 if (plot_type == ImGuiPlotType_Lines)
9527 SetTooltip("%d: %8.4g\n%d: %8.4g", v_idx, v0, v_idx + 1, v1);
9528 else if (plot_type == ImGuiPlotType_Histogram)
9529 SetTooltip("%d: %8.4g", v_idx, v0);
9530 v_hovered = v_idx;
9531 }
9532
9533 const float t_step = 1.0f / (float)res_w;
9534 const float inv_scale = (scale_min == scale_max) ? 0.0f : (1.0f / (scale_max - scale_min));
9535
9536 float v0 = values_getter(data, (0 + values_offset) % values_count);
9537 float t0 = 0.0f;
9538 ImVec2 tp0 = ImVec2(t0, 1.0f - ImSaturate((v0 - scale_min) * inv_scale)); // Point in the normalized space of our target rectangle
9539 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
9540
9541 const ImU32 col_base = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLines : ImGuiCol_PlotHistogram);
9542 const ImU32 col_hovered = GetColorU32((plot_type == ImGuiPlotType_Lines) ? ImGuiCol_PlotLinesHovered : ImGuiCol_PlotHistogramHovered);
9543
9544 for (int n = 0; n < res_w; n++)
9545 {
9546 const float t1 = t0 + t_step;
9547 const int v1_idx = (int)(t0 * item_count + 0.5f);
9548 IM_ASSERT(v1_idx >= 0 && v1_idx < values_count);
9549 const float v1 = values_getter(data, (v1_idx + values_offset + 1) % values_count);
9550 const ImVec2 tp1 = ImVec2(t1, 1.0f - ImSaturate((v1 - scale_min) * inv_scale));
9551
9552 // 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.
9553 ImVec2 pos0 = ImLerp(inner_bb.Min, inner_bb.Max, tp0);
9554 ImVec2 pos1 = ImLerp(inner_bb.Min, inner_bb.Max, (plot_type == ImGuiPlotType_Lines) ? tp1 : ImVec2(tp1.x, histogram_zero_line_t));
9555 if (plot_type == ImGuiPlotType_Lines)
9556 {
9557 window->DrawList->AddLine(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base);
9558 }
9559 else if (plot_type == ImGuiPlotType_Histogram)
9560 {
9561 if (pos1.x >= pos0.x + 2.0f)
9562 pos1.x -= 1.0f;
9563 window->DrawList->AddRectFilled(pos0, pos1, v_hovered == v1_idx ? col_hovered : col_base);
9564 }
9565
9566 t0 = t1;
9567 tp0 = tp1;
9568 }
9569 }
9570
9571 // Text overlay
9572 if (overlay_text)
9573 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));
9574
9575 if (label_size.x > 0.0f)
9576 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label);
9577 }
9578
9579 struct ImGuiPlotArrayGetterData
9580 {
9581 const float* Values;
9582 int Stride;
9583
ImGuiPlotArrayGetterDataImGuiPlotArrayGetterData9584 ImGuiPlotArrayGetterData(const float* values, int stride)
9585 {
9586 Values = values;
9587 Stride = stride;
9588 }
9589 };
9590
Plot_ArrayGetter(void * data,int idx)9591 static float Plot_ArrayGetter(void* data, int idx)
9592 {
9593 ImGuiPlotArrayGetterData* plot_data = (ImGuiPlotArrayGetterData*)data;
9594 const float v = *(float*)(void*)((unsigned char*)plot_data->Values + (size_t)idx * plot_data->Stride);
9595 return v;
9596 }
9597
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)9598 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)
9599 {
9600 ImGuiPlotArrayGetterData data(values, stride);
9601 PlotEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
9602 }
9603
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)9604 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)
9605 {
9606 PlotEx(ImGuiPlotType_Lines, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
9607 }
9608
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)9609 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)
9610 {
9611 ImGuiPlotArrayGetterData data(values, stride);
9612 PlotEx(ImGuiPlotType_Histogram, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
9613 }
9614
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)9615 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)
9616 {
9617 PlotEx(ImGuiPlotType_Histogram, label, values_getter, data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
9618 }
9619
9620 // size_arg (for each axis) < 0.0f: align to end, 0.0f: auto, > 0.0f: specified size
ProgressBar(float fraction,const ImVec2 & size_arg,const char * overlay)9621 void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* overlay)
9622 {
9623 ImGuiWindow* window = GetCurrentWindow();
9624 if (window->SkipItems)
9625 return;
9626
9627 ImGuiContext& g = *GImGui;
9628 const ImGuiStyle& style = g.Style;
9629
9630 ImVec2 pos = window->DC.CursorPos;
9631 ImRect bb(pos, pos + CalcItemSize(size_arg, CalcItemWidth(), g.FontSize + style.FramePadding.y * 2.0f));
9632 ItemSize(bb, style.FramePadding.y);
9633 if (!ItemAdd(bb, 0))
9634 return;
9635
9636 // Render
9637 fraction = ImSaturate(fraction);
9638 RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
9639 bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize));
9640 const ImVec2 fill_br = ImVec2(ImLerp(bb.Min.x, bb.Max.x, fraction), bb.Max.y);
9641 RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), 0.0f, fraction, style.FrameRounding);
9642
9643 // Default displaying the fraction as percentage string, but user can override it
9644 char overlay_buf[32];
9645 if (!overlay)
9646 {
9647 ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction * 100 + 0.01f);
9648 overlay = overlay_buf;
9649 }
9650
9651 ImVec2 overlay_size = CalcTextSize(overlay, NULL);
9652 if (overlay_size.x > 0.0f)
9653 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);
9654 }
9655
Checkbox(const char * label,bool * v)9656 bool ImGui::Checkbox(const char* label, bool* v)
9657 {
9658 ImGuiWindow* window = GetCurrentWindow();
9659 if (window->SkipItems)
9660 return false;
9661
9662 ImGuiContext& g = *GImGui;
9663 const ImGuiStyle& style = g.Style;
9664 const ImGuiID id = window->GetID(label);
9665 const ImVec2 label_size = CalcTextSize(label, NULL, true);
9666
9667 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
9668 ItemSize(check_bb, style.FramePadding.y);
9669
9670 ImRect total_bb = check_bb;
9671 if (label_size.x > 0)
9672 SameLine(0, style.ItemInnerSpacing.x);
9673 const ImRect text_bb(window->DC.CursorPos + ImVec2(0, style.FramePadding.y), window->DC.CursorPos + ImVec2(0, style.FramePadding.y) + label_size);
9674 if (label_size.x > 0)
9675 {
9676 ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight()), style.FramePadding.y);
9677 total_bb = ImRect(ImMin(check_bb.Min, text_bb.Min), ImMax(check_bb.Max, text_bb.Max));
9678 }
9679
9680 if (!ItemAdd(total_bb, id))
9681 return false;
9682
9683 bool hovered, held;
9684 bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
9685 if (pressed)
9686 *v = !(*v);
9687
9688 RenderNavHighlight(total_bb, id);
9689 RenderFrame(check_bb.Min, check_bb.Max, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding);
9690 if (*v)
9691 {
9692 const float check_sz = ImMin(check_bb.GetWidth(), check_bb.GetHeight());
9693 const float pad = ImMax(1.0f, (float)(int)(check_sz / 6.0f));
9694 RenderCheckMark(check_bb.Min + ImVec2(pad, pad), GetColorU32(ImGuiCol_CheckMark), check_bb.GetWidth() - pad * 2.0f);
9695 }
9696
9697 if (g.LogEnabled)
9698 LogRenderedText(&text_bb.Min, *v ? "[x]" : "[ ]");
9699 if (label_size.x > 0.0f)
9700 RenderText(text_bb.Min, label);
9701
9702 return pressed;
9703 }
9704
CheckboxFlags(const char * label,unsigned int * flags,unsigned int flags_value)9705 bool ImGui::CheckboxFlags(const char* label, unsigned int* flags, unsigned int flags_value)
9706 {
9707 bool v = ((*flags & flags_value) == flags_value);
9708 bool pressed = Checkbox(label, &v);
9709 if (pressed)
9710 {
9711 if (v)
9712 *flags |= flags_value;
9713 else
9714 *flags &= ~flags_value;
9715 }
9716
9717 return pressed;
9718 }
9719
RadioButton(const char * label,bool active)9720 bool ImGui::RadioButton(const char* label, bool active)
9721 {
9722 ImGuiWindow* window = GetCurrentWindow();
9723 if (window->SkipItems)
9724 return false;
9725
9726 ImGuiContext& g = *GImGui;
9727 const ImGuiStyle& style = g.Style;
9728 const ImGuiID id = window->GetID(label);
9729 const ImVec2 label_size = CalcTextSize(label, NULL, true);
9730
9731 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));
9732 ItemSize(check_bb, style.FramePadding.y);
9733
9734 ImRect total_bb = check_bb;
9735 if (label_size.x > 0)
9736 SameLine(0, style.ItemInnerSpacing.x);
9737 const ImRect text_bb(window->DC.CursorPos + ImVec2(0, style.FramePadding.y), window->DC.CursorPos + ImVec2(0, style.FramePadding.y) + label_size);
9738 if (label_size.x > 0)
9739 {
9740 ItemSize(ImVec2(text_bb.GetWidth(), check_bb.GetHeight()), style.FramePadding.y);
9741 total_bb.Add(text_bb);
9742 }
9743
9744 if (!ItemAdd(total_bb, id))
9745 return false;
9746
9747 ImVec2 center = check_bb.GetCenter();
9748 center.x = (float)(int)center.x + 0.5f;
9749 center.y = (float)(int)center.y + 0.5f;
9750 const float radius = check_bb.GetHeight() * 0.5f;
9751
9752 bool hovered, held;
9753 bool pressed = ButtonBehavior(total_bb, id, &hovered, &held);
9754
9755 RenderNavHighlight(total_bb, id);
9756 window->DrawList->AddCircleFilled(center, radius, GetColorU32((held && hovered) ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), 16);
9757 if (active)
9758 {
9759 const float check_sz = ImMin(check_bb.GetWidth(), check_bb.GetHeight());
9760 const float pad = ImMax(1.0f, (float)(int)(check_sz / 6.0f));
9761 window->DrawList->AddCircleFilled(center, radius - pad, GetColorU32(ImGuiCol_CheckMark), 16);
9762 }
9763
9764 if (style.FrameBorderSize > 0.0f)
9765 {
9766 window->DrawList->AddCircle(center + ImVec2(1, 1), radius, GetColorU32(ImGuiCol_BorderShadow), 16, style.FrameBorderSize);
9767 window->DrawList->AddCircle(center, radius, GetColorU32(ImGuiCol_Border), 16, style.FrameBorderSize);
9768 }
9769
9770 if (g.LogEnabled)
9771 LogRenderedText(&text_bb.Min, active ? "(x)" : "( )");
9772 if (label_size.x > 0.0f)
9773 RenderText(text_bb.Min, label);
9774
9775 return pressed;
9776 }
9777
RadioButton(const char * label,int * v,int v_button)9778 bool ImGui::RadioButton(const char* label, int* v, int v_button)
9779 {
9780 const bool pressed = RadioButton(label, *v == v_button);
9781 if (pressed)
9782 {
9783 *v = v_button;
9784 }
9785 return pressed;
9786 }
9787
InputTextCalcTextLenAndLineCount(const char * text_begin,const char ** out_text_end)9788 static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end)
9789 {
9790 int line_count = 0;
9791 const char* s = text_begin;
9792 while (char c = *s++) // We are only matching for \n so we can ignore UTF-8 decoding
9793 if (c == '\n')
9794 line_count++;
9795 s--;
9796 if (s[0] != '\n' && s[0] != '\r')
9797 line_count++;
9798 *out_text_end = s;
9799 return line_count;
9800 }
9801
InputTextCalcTextSizeW(const ImWchar * text_begin,const ImWchar * text_end,const ImWchar ** remaining,ImVec2 * out_offset,bool stop_on_new_line)9802 static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining, ImVec2* out_offset, bool stop_on_new_line)
9803 {
9804 ImFont* font = GImGui->Font;
9805 const float line_height = GImGui->FontSize;
9806 const float scale = line_height / font->FontSize;
9807
9808 ImVec2 text_size = ImVec2(0, 0);
9809 float line_width = 0.0f;
9810
9811 const ImWchar* s = text_begin;
9812 while (s < text_end)
9813 {
9814 unsigned int c = (unsigned int)(*s++);
9815 if (c == '\n')
9816 {
9817 text_size.x = ImMax(text_size.x, line_width);
9818 text_size.y += line_height;
9819 line_width = 0.0f;
9820 if (stop_on_new_line)
9821 break;
9822 continue;
9823 }
9824 if (c == '\r')
9825 continue;
9826
9827 const float char_width = font->GetCharAdvance((unsigned short)c) * scale;
9828 line_width += char_width;
9829 }
9830
9831 if (text_size.x < line_width)
9832 text_size.x = line_width;
9833
9834 if (out_offset)
9835 *out_offset = ImVec2(line_width, text_size.y + line_height); // offset allow for the possibility of sitting after a trailing \n
9836
9837 if (line_width > 0 || text_size.y == 0.0f) // whereas size.y will ignore the trailing \n
9838 text_size.y += line_height;
9839
9840 if (remaining)
9841 *remaining = s;
9842
9843 return text_size;
9844 }
9845
9846 // 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)
9847 namespace ImGuiStb
9848 {
STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING * obj)9849 static int STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj) { return obj->CurLenW; }
STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING * obj,int idx)9850 static ImWchar STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING* obj, int idx) { return obj->Text[idx]; }
STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING * obj,int line_start_idx,int char_idx)9851 static float STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx)
9852 {
9853 ImWchar c = obj->Text[line_start_idx + char_idx];
9854 if (c == '\n') return STB_TEXTEDIT_GETWIDTH_NEWLINE;
9855 return GImGui->Font->GetCharAdvance(c) * (GImGui->FontSize / GImGui->Font->FontSize);
9856 }
STB_TEXTEDIT_KEYTOTEXT(int key)9857 static int STB_TEXTEDIT_KEYTOTEXT(int key) { return key >= 0x10000 ? 0 : key; }
9858 static ImWchar STB_TEXTEDIT_NEWLINE = '\n';
STB_TEXTEDIT_LAYOUTROW(StbTexteditRow * r,STB_TEXTEDIT_STRING * obj,int line_start_idx)9859 static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, STB_TEXTEDIT_STRING* obj, int line_start_idx)
9860 {
9861 const ImWchar* text = obj->Text.Data;
9862 const ImWchar* text_remaining = NULL;
9863 const ImVec2 size = InputTextCalcTextSizeW(text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true);
9864 r->x0 = 0.0f;
9865 r->x1 = size.x;
9866 r->baseline_y_delta = size.y;
9867 r->ymin = 0.0f;
9868 r->ymax = size.y;
9869 r->num_chars = (int)(text_remaining - (text + line_start_idx));
9870 }
9871
is_separator(unsigned int c)9872 static bool is_separator(unsigned int c) { return ImCharIsSpace(c) || c == ',' || c == ';' || c == '(' || c == ')' || c == '{' || c == '}' || c == '[' || c == ']' || c == '|'; }
is_word_boundary_from_right(STB_TEXTEDIT_STRING * obj,int idx)9873 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; }
STB_TEXTEDIT_MOVEWORDLEFT_IMPL(STB_TEXTEDIT_STRING * obj,int idx)9874 static int STB_TEXTEDIT_MOVEWORDLEFT_IMPL(STB_TEXTEDIT_STRING* obj, int idx)
9875 {
9876 idx--;
9877 while (idx >= 0 && !is_word_boundary_from_right(obj, idx)) idx--;
9878 return idx < 0 ? 0 : idx;
9879 }
9880 #ifdef __APPLE__ // FIXME: Move setting to IO structure
is_word_boundary_from_left(STB_TEXTEDIT_STRING * obj,int idx)9881 static int is_word_boundary_from_left(STB_TEXTEDIT_STRING* obj, int idx)
9882 {
9883 return idx > 0 ? (!is_separator(obj->Text[idx - 1]) && is_separator(obj->Text[idx])) : 1;
9884 }
STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING * obj,int idx)9885 static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx)
9886 {
9887 idx++;
9888 int len = obj->CurLenW;
9889 while (idx < len && !is_word_boundary_from_left(obj, idx)) idx++;
9890 return idx > len ? len : idx;
9891 }
9892 #else
STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING * obj,int idx)9893 static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(STB_TEXTEDIT_STRING* obj, int idx)
9894 {
9895 idx++;
9896 int len = obj->CurLenW;
9897 while (idx < len && !is_word_boundary_from_right(obj, idx)) idx++;
9898 return idx > len ? len : idx;
9899 }
9900 #endif
9901 #define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h
9902 #define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_IMPL
9903
STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING * obj,int pos,int n)9904 static void STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING* obj, int pos, int n)
9905 {
9906 ImWchar* dst = obj->Text.Data + pos;
9907
9908 // We maintain our buffer length in both UTF-8 and wchar formats
9909 obj->CurLenA -= ImTextCountUtf8BytesFromStr(dst, dst + n);
9910 obj->CurLenW -= n;
9911
9912 // Offset remaining text
9913 const ImWchar* src = obj->Text.Data + pos + n;
9914 while (ImWchar c = *src++)
9915 *dst++ = c;
9916 *dst = '\0';
9917 }
9918
STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING * obj,int pos,const ImWchar * new_text,int new_text_len)9919 static bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int pos, const ImWchar* new_text, int new_text_len)
9920 {
9921 const int text_len = obj->CurLenW;
9922 IM_ASSERT(pos <= text_len);
9923 if (new_text_len + text_len + 1 > obj->Text.Size)
9924 return false;
9925
9926 const int new_text_len_utf8 = ImTextCountUtf8BytesFromStr(new_text, new_text + new_text_len);
9927 if (new_text_len_utf8 + obj->CurLenA + 1 > obj->BufSizeA)
9928 return false;
9929
9930 ImWchar* text = obj->Text.Data;
9931 if (pos != text_len)
9932 memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos) * sizeof(ImWchar));
9933 memcpy(text + pos, new_text, (size_t)new_text_len * sizeof(ImWchar));
9934
9935 obj->CurLenW += new_text_len;
9936 obj->CurLenA += new_text_len_utf8;
9937 obj->Text[obj->CurLenW] = '\0';
9938
9939 return true;
9940 }
9941
9942 // 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)
9943 #define STB_TEXTEDIT_K_LEFT 0x10000 // keyboard input to move cursor left
9944 #define STB_TEXTEDIT_K_RIGHT 0x10001 // keyboard input to move cursor right
9945 #define STB_TEXTEDIT_K_UP 0x10002 // keyboard input to move cursor up
9946 #define STB_TEXTEDIT_K_DOWN 0x10003 // keyboard input to move cursor down
9947 #define STB_TEXTEDIT_K_LINESTART 0x10004 // keyboard input to move cursor to start of line
9948 #define STB_TEXTEDIT_K_LINEEND 0x10005 // keyboard input to move cursor to end of line
9949 #define STB_TEXTEDIT_K_TEXTSTART 0x10006 // keyboard input to move cursor to start of text
9950 #define STB_TEXTEDIT_K_TEXTEND 0x10007 // keyboard input to move cursor to end of text
9951 #define STB_TEXTEDIT_K_DELETE 0x10008 // keyboard input to delete selection or character under cursor
9952 #define STB_TEXTEDIT_K_BACKSPACE 0x10009 // keyboard input to delete selection or character left of cursor
9953 #define STB_TEXTEDIT_K_UNDO 0x1000A // keyboard input to perform undo
9954 #define STB_TEXTEDIT_K_REDO 0x1000B // keyboard input to perform redo
9955 #define STB_TEXTEDIT_K_WORDLEFT 0x1000C // keyboard input to move cursor left one word
9956 #define STB_TEXTEDIT_K_WORDRIGHT 0x1000D // keyboard input to move cursor right one word
9957 #define STB_TEXTEDIT_K_SHIFT 0x20000
9958
9959 #define STB_TEXTEDIT_IMPLEMENTATION
9960 #include "stb_textedit.h"
9961
9962 } // namespace ImGuiStb
9963
OnKeyPressed(int key)9964 void ImGuiTextEditState::OnKeyPressed(int key)
9965 {
9966 stb_textedit_key(this, &StbState, key);
9967 CursorFollow = true;
9968 CursorAnimReset();
9969 }
9970
9971 // Public API to manipulate UTF-8 text
9972 // We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar)
9973 // FIXME: The existence of this rarely exercised code path is a bit of a nuisance.
DeleteChars(int pos,int bytes_count)9974 void ImGuiTextEditCallbackData::DeleteChars(int pos, int bytes_count)
9975 {
9976 IM_ASSERT(pos + bytes_count <= BufTextLen);
9977 char* dst = Buf + pos;
9978 const char* src = Buf + pos + bytes_count;
9979 while (char c = *src++)
9980 *dst++ = c;
9981 *dst = '\0';
9982
9983 if (CursorPos + bytes_count >= pos)
9984 CursorPos -= bytes_count;
9985 else if (CursorPos >= pos)
9986 CursorPos = pos;
9987 SelectionStart = SelectionEnd = CursorPos;
9988 BufDirty = true;
9989 BufTextLen -= bytes_count;
9990 }
9991
InsertChars(int pos,const char * new_text,const char * new_text_end)9992 void ImGuiTextEditCallbackData::InsertChars(int pos, const char* new_text, const char* new_text_end)
9993 {
9994 const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)strlen(new_text);
9995 if (new_text_len + BufTextLen + 1 >= BufSize)
9996 return;
9997
9998 if (BufTextLen != pos)
9999 memmove(Buf + pos + new_text_len, Buf + pos, (size_t)(BufTextLen - pos));
10000 memcpy(Buf + pos, new_text, (size_t)new_text_len * sizeof(char));
10001 Buf[BufTextLen + new_text_len] = '\0';
10002
10003 if (CursorPos >= pos)
10004 CursorPos += new_text_len;
10005 SelectionStart = SelectionEnd = CursorPos;
10006 BufDirty = true;
10007 BufTextLen += new_text_len;
10008 }
10009
10010 // Return false to discard a character.
InputTextFilterCharacter(unsigned int * p_char,ImGuiInputTextFlags flags,ImGuiTextEditCallback callback,void * user_data)10011 static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data)
10012 {
10013 unsigned int c = *p_char;
10014
10015 if (c < 128 && c != ' ' && !isprint((int)(c & 0xFF)))
10016 {
10017 bool pass = false;
10018 pass |= (c == '\n' && (flags & ImGuiInputTextFlags_Multiline));
10019 pass |= (c == '\t' && (flags & ImGuiInputTextFlags_AllowTabInput));
10020 if (!pass)
10021 return false;
10022 }
10023
10024 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.
10025 return false;
10026
10027 if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank))
10028 {
10029 if (flags & ImGuiInputTextFlags_CharsDecimal)
10030 if (!(c >= '0' && c <= '9') && (c != '.') && (c != '-') && (c != '+') && (c != '*') && (c != '/'))
10031 return false;
10032
10033 if (flags & ImGuiInputTextFlags_CharsHexadecimal)
10034 if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F'))
10035 return false;
10036
10037 if (flags & ImGuiInputTextFlags_CharsUppercase)
10038 if (c >= 'a' && c <= 'z')
10039 *p_char = (c += (unsigned int)('A' - 'a'));
10040
10041 if (flags & ImGuiInputTextFlags_CharsNoBlank)
10042 if (ImCharIsSpace(c))
10043 return false;
10044 }
10045
10046 if (flags & ImGuiInputTextFlags_CallbackCharFilter)
10047 {
10048 ImGuiTextEditCallbackData callback_data;
10049 memset(&callback_data, 0, sizeof(ImGuiTextEditCallbackData));
10050 callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter;
10051 callback_data.EventChar = (ImWchar)c;
10052 callback_data.Flags = flags;
10053 callback_data.UserData = user_data;
10054 if (callback(&callback_data) != 0)
10055 return false;
10056 *p_char = callback_data.EventChar;
10057 if (!callback_data.EventChar)
10058 return false;
10059 }
10060
10061 return true;
10062 }
10063
10064 // Edit a string of text
10065 // 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.
10066 // 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
InputTextEx(const char * label,char * buf,int buf_size,const ImVec2 & size_arg,ImGuiInputTextFlags flags,ImGuiTextEditCallback callback,void * user_data)10067 bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data)
10068 {
10069 ImGuiWindow* window = GetCurrentWindow();
10070 if (window->SkipItems)
10071 return false;
10072
10073 IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline))); // Can't use both together (they both use up/down keys)
10074 IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key)
10075
10076 ImGuiContext& g = *GImGui;
10077 const ImGuiIO& io = g.IO;
10078 const ImGuiStyle& style = g.Style;
10079
10080 const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0;
10081 const bool is_editable = (flags & ImGuiInputTextFlags_ReadOnly) == 0;
10082 const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0;
10083 const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0;
10084
10085 if (is_multiline) // Open group before calling GetID() because groups tracks id created during their spawn
10086 BeginGroup();
10087 const ImGuiID id = window->GetID(label);
10088 const ImVec2 label_size = CalcTextSize(label, NULL, true);
10089 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
10090 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size);
10091 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));
10092
10093 ImGuiWindow* draw_window = window;
10094 if (is_multiline)
10095 {
10096 ItemAdd(total_bb, id, &frame_bb);
10097 if (!BeginChildFrame(id, frame_bb.GetSize()))
10098 {
10099 EndChildFrame();
10100 EndGroup();
10101 return false;
10102 }
10103 draw_window = GetCurrentWindow();
10104 size.x -= draw_window->ScrollbarSizes.x;
10105 }
10106 else
10107 {
10108 ItemSize(total_bb, style.FramePadding.y);
10109 if (!ItemAdd(total_bb, id, &frame_bb))
10110 return false;
10111 }
10112 const bool hovered = ItemHoverable(frame_bb, id);
10113 if (hovered)
10114 g.MouseCursor = ImGuiMouseCursor_TextInput;
10115
10116 // Password pushes a temporary font with only a fallback glyph
10117 if (is_password)
10118 {
10119 const ImFontGlyph* glyph = g.Font->FindGlyph('*');
10120 ImFont* password_font = &g.InputTextPasswordFont;
10121 password_font->FontSize = g.Font->FontSize;
10122 password_font->Scale = g.Font->Scale;
10123 password_font->DisplayOffset = g.Font->DisplayOffset;
10124 password_font->Ascent = g.Font->Ascent;
10125 password_font->Descent = g.Font->Descent;
10126 password_font->ContainerAtlas = g.Font->ContainerAtlas;
10127 password_font->FallbackGlyph = glyph;
10128 password_font->FallbackAdvanceX = glyph->AdvanceX;
10129 IM_ASSERT(password_font->Glyphs.empty() && password_font->IndexAdvanceX.empty() && password_font->IndexLookup.empty());
10130 PushFont(password_font);
10131 }
10132
10133 // NB: we are only allowed to access 'edit_state' if we are the active widget.
10134 ImGuiTextEditState& edit_state = g.InputTextState;
10135
10136 const bool focus_requested = FocusableItemRegister(window, id, (flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_AllowTabInput)) == 0); // Using completion callback disable keyboard tabbing
10137 const bool focus_requested_by_code = focus_requested && (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent);
10138 const bool focus_requested_by_tab = focus_requested && !focus_requested_by_code;
10139
10140 const bool user_clicked = hovered && io.MouseClicked[0];
10141 const bool user_scrolled = is_multiline && g.ActiveId == 0 && edit_state.Id == id && g.ActiveIdPreviousFrame == draw_window->GetIDNoKeepAlive("#SCROLLY");
10142
10143 bool clear_active_id = false;
10144
10145 bool select_all = (g.ActiveId != id) && (((flags & ImGuiInputTextFlags_AutoSelectAll) != 0) || (g.NavInputId == id)) && (!is_multiline);
10146 if (focus_requested || user_clicked || user_scrolled || g.NavInputId == id)
10147 {
10148 if (g.ActiveId != id)
10149 {
10150 // Start edition
10151 // Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar)
10152 // From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode)
10153 const int prev_len_w = edit_state.CurLenW;
10154 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.
10155 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.
10156 ImStrncpy(edit_state.InitialText.Data, buf, edit_state.InitialText.Size);
10157 const char* buf_end = NULL;
10158 edit_state.CurLenW = ImTextStrFromUtf8(edit_state.Text.Data, edit_state.Text.Size, buf, NULL, &buf_end);
10159 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.
10160 edit_state.CursorAnimReset();
10161
10162 // Preserve cursor position and undo/redo stack if we come back to same widget
10163 // FIXME: We should probably compare the whole buffer to be on the safety side. Comparing buf (utf8) and edit_state.Text (wchar).
10164 const bool recycle_state = (edit_state.Id == id) && (prev_len_w == edit_state.CurLenW);
10165 if (recycle_state)
10166 {
10167 // Recycle existing cursor/selection/undo stack but clamp position
10168 // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler.
10169 edit_state.CursorClamp();
10170 }
10171 else
10172 {
10173 edit_state.Id = id;
10174 edit_state.ScrollX = 0.0f;
10175 stb_textedit_initialize_state(&edit_state.StbState, !is_multiline);
10176 if (!is_multiline && focus_requested_by_code)
10177 select_all = true;
10178 }
10179 if (flags & ImGuiInputTextFlags_AlwaysInsertMode)
10180 edit_state.StbState.insert_mode = true;
10181 if (!is_multiline && (focus_requested_by_tab || (user_clicked && io.KeyCtrl)))
10182 select_all = true;
10183 }
10184 SetActiveID(id, window);
10185 SetFocusID(id, window);
10186 FocusWindow(window);
10187 if (!is_multiline && !(flags & ImGuiInputTextFlags_CallbackHistory))
10188 g.ActiveIdAllowNavDirFlags |= ((1 << ImGuiDir_Up) | (1 << ImGuiDir_Down));
10189 }
10190 else if (io.MouseClicked[0])
10191 {
10192 // Release focus when we click outside
10193 clear_active_id = true;
10194 }
10195
10196 bool value_changed = false;
10197 bool enter_pressed = false;
10198
10199 if (g.ActiveId == id)
10200 {
10201 if (!is_editable && !g.ActiveIdIsJustActivated)
10202 {
10203 // When read-only we always use the live data passed to the function
10204 edit_state.Text.resize(buf_size + 1);
10205 const char* buf_end = NULL;
10206 edit_state.CurLenW = ImTextStrFromUtf8(edit_state.Text.Data, edit_state.Text.Size, buf, NULL, &buf_end);
10207 edit_state.CurLenA = (int)(buf_end - buf);
10208 edit_state.CursorClamp();
10209 }
10210
10211 edit_state.BufSizeA = buf_size;
10212
10213 // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget.
10214 // Down the line we should have a cleaner library-wide concept of Selected vs Active.
10215 g.ActiveIdAllowOverlap = !io.MouseDown[0];
10216 g.WantTextInputNextFrame = 1;
10217
10218 // Edit in progress
10219 const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + edit_state.ScrollX;
10220 const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y - style.FramePadding.y) : (g.FontSize * 0.5f));
10221
10222 const bool osx_double_click_selects_words = io.OptMacOSXBehaviors; // OS X style: Double click selects by word instead of selecting whole text
10223 if (select_all || (hovered && !osx_double_click_selects_words && io.MouseDoubleClicked[0]))
10224 {
10225 edit_state.SelectAll();
10226 edit_state.SelectedAllMouseLock = true;
10227 }
10228 else if (hovered && osx_double_click_selects_words && io.MouseDoubleClicked[0])
10229 {
10230 // Select a word only, OS X style (by simulating keystrokes)
10231 edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT);
10232 edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT);
10233 }
10234 else if (io.MouseClicked[0] && !edit_state.SelectedAllMouseLock)
10235 {
10236 if (hovered)
10237 {
10238 stb_textedit_click(&edit_state, &edit_state.StbState, mouse_x, mouse_y);
10239 edit_state.CursorAnimReset();
10240 }
10241 }
10242 else if (io.MouseDown[0] && !edit_state.SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f))
10243 {
10244 stb_textedit_drag(&edit_state, &edit_state.StbState, mouse_x, mouse_y);
10245 edit_state.CursorAnimReset();
10246 edit_state.CursorFollow = true;
10247 }
10248 if (edit_state.SelectedAllMouseLock && !io.MouseDown[0])
10249 edit_state.SelectedAllMouseLock = false;
10250
10251 if (io.InputCharacters[0])
10252 {
10253 // Process text input (before we check for Return because using some IME will effectively send a Return?)
10254 // We ignore CTRL inputs, but need to allow CTRL+ALT as some keyboards (e.g. German) use AltGR - which is Alt+Ctrl - to input certain characters.
10255 if (!(io.KeyCtrl && !io.KeyAlt) && is_editable)
10256 {
10257 for (int n = 0; n < IM_ARRAYSIZE(io.InputCharacters) && io.InputCharacters[n]; n++)
10258 if (unsigned int c = (unsigned int)io.InputCharacters[n])
10259 {
10260 // Insert character if they pass filtering
10261 if (!InputTextFilterCharacter(&c, flags, callback, user_data))
10262 continue;
10263 edit_state.OnKeyPressed((int)c);
10264 }
10265 }
10266
10267 // Consume characters
10268 memset(g.IO.InputCharacters, 0, sizeof(g.IO.InputCharacters));
10269 }
10270 }
10271
10272 bool cancel_edit = false;
10273 if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id)
10274 {
10275 // Handle key-presses
10276 const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0);
10277 const bool is_shortcut_key_only = (io.OptMacOSXBehaviors ? (io.KeySuper && !io.KeyCtrl) : (io.KeyCtrl && !io.KeySuper)) && !io.KeyAlt && !io.KeyShift; // OS X style: Shortcuts using Cmd/Super instead of Ctrl
10278 const bool is_wordmove_key_down = io.OptMacOSXBehaviors ? io.KeyAlt : io.KeyCtrl; // OS X style: Text editing cursor movement using Alt instead of Ctrl
10279 const bool is_startend_key_down = io.OptMacOSXBehaviors && io.KeySuper && !io.KeyCtrl && !io.KeyAlt; // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End
10280 const bool is_ctrl_key_only = io.KeyCtrl && !io.KeyShift && !io.KeyAlt && !io.KeySuper;
10281 const bool is_shift_key_only = io.KeyShift && !io.KeyCtrl && !io.KeyAlt && !io.KeySuper;
10282
10283 const bool is_cut = ((is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_X)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Delete))) && is_editable && !is_password && (!is_multiline || edit_state.HasSelection());
10284 const bool is_copy = ((is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_C)) || (is_ctrl_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && !is_password && (!is_multiline || edit_state.HasSelection());
10285 const bool is_paste = ((is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_V)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && is_editable;
10286
10287 if (IsKeyPressedMap(ImGuiKey_LeftArrow))
10288 {
10289 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);
10290 }
10291 else if (IsKeyPressedMap(ImGuiKey_RightArrow))
10292 {
10293 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);
10294 }
10295 else if (IsKeyPressedMap(ImGuiKey_UpArrow) && is_multiline)
10296 {
10297 if (io.KeyCtrl)
10298 SetWindowScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f));
10299 else
10300 edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask);
10301 }
10302 else if (IsKeyPressedMap(ImGuiKey_DownArrow) && is_multiline)
10303 {
10304 if (io.KeyCtrl)
10305 SetWindowScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY()));
10306 else
10307 edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask);
10308 }
10309 else if (IsKeyPressedMap(ImGuiKey_Home))
10310 {
10311 edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask);
10312 }
10313 else if (IsKeyPressedMap(ImGuiKey_End))
10314 {
10315 edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask);
10316 }
10317 else if (IsKeyPressedMap(ImGuiKey_Delete) && is_editable)
10318 {
10319 edit_state.OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask);
10320 }
10321 else if (IsKeyPressedMap(ImGuiKey_Backspace) && is_editable)
10322 {
10323 if (!edit_state.HasSelection())
10324 {
10325 if (is_wordmove_key_down)
10326 edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT);
10327 else if (io.OptMacOSXBehaviors && io.KeySuper && !io.KeyAlt && !io.KeyCtrl)
10328 edit_state.OnKeyPressed(STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT);
10329 }
10330 edit_state.OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask);
10331 }
10332 else if (IsKeyPressedMap(ImGuiKey_Enter))
10333 {
10334 bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0;
10335 if (!is_multiline || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl))
10336 {
10337 enter_pressed = clear_active_id = true;
10338 }
10339 else if (is_editable)
10340 {
10341 unsigned int c = '\n'; // Insert new line
10342 if (InputTextFilterCharacter(&c, flags, callback, user_data))
10343 edit_state.OnKeyPressed((int)c);
10344 }
10345 }
10346 else if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(ImGuiKey_Tab) && !io.KeyCtrl && !io.KeyShift && !io.KeyAlt && is_editable)
10347 {
10348 unsigned int c = '\t'; // Insert TAB
10349 if (InputTextFilterCharacter(&c, flags, callback, user_data))
10350 edit_state.OnKeyPressed((int)c);
10351 }
10352 else if (IsKeyPressedMap(ImGuiKey_Escape))
10353 {
10354 clear_active_id = cancel_edit = true;
10355 }
10356 else if (is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_Z) && is_editable && is_undoable)
10357 {
10358 edit_state.OnKeyPressed(STB_TEXTEDIT_K_UNDO);
10359 edit_state.ClearSelection();
10360 }
10361 else if (is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_Y) && is_editable && is_undoable)
10362 {
10363 edit_state.OnKeyPressed(STB_TEXTEDIT_K_REDO);
10364 edit_state.ClearSelection();
10365 }
10366 else if (is_shortcut_key_only && IsKeyPressedMap(ImGuiKey_A))
10367 {
10368 edit_state.SelectAll();
10369 edit_state.CursorFollow = true;
10370 }
10371 else if (is_cut || is_copy)
10372 {
10373 // Cut, Copy
10374 if (io.SetClipboardTextFn)
10375 {
10376 const int ib = edit_state.HasSelection() ? ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end) : 0;
10377 const int ie = edit_state.HasSelection() ? ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end) : edit_state.CurLenW;
10378 edit_state.TempTextBuffer.resize((ie - ib) * 4 + 1);
10379 ImTextStrToUtf8(edit_state.TempTextBuffer.Data, edit_state.TempTextBuffer.Size, edit_state.Text.Data + ib, edit_state.Text.Data + ie);
10380 SetClipboardText(edit_state.TempTextBuffer.Data);
10381 }
10382
10383 if (is_cut)
10384 {
10385 if (!edit_state.HasSelection())
10386 edit_state.SelectAll();
10387 edit_state.CursorFollow = true;
10388 stb_textedit_cut(&edit_state, &edit_state.StbState);
10389 }
10390 }
10391 else if (is_paste)
10392 {
10393 // Paste
10394 if (const char* clipboard = GetClipboardText())
10395 {
10396 // Filter pasted buffer
10397 const int clipboard_len = (int)strlen(clipboard);
10398 ImWchar* clipboard_filtered = (ImWchar*)ImGui::MemAlloc((clipboard_len + 1) * sizeof(ImWchar));
10399 int clipboard_filtered_len = 0;
10400 for (const char* s = clipboard; *s;)
10401 {
10402 unsigned int c;
10403 s += ImTextCharFromUtf8(&c, s, NULL);
10404 if (c == 0)
10405 break;
10406 if (c >= 0x10000 || !InputTextFilterCharacter(&c, flags, callback, user_data))
10407 continue;
10408 clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c;
10409 }
10410 clipboard_filtered[clipboard_filtered_len] = 0;
10411 if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation
10412 {
10413 stb_textedit_paste(&edit_state, &edit_state.StbState, clipboard_filtered, clipboard_filtered_len);
10414 edit_state.CursorFollow = true;
10415 }
10416 ImGui::MemFree(clipboard_filtered);
10417 }
10418 }
10419 }
10420
10421 if (g.ActiveId == id)
10422 {
10423 if (cancel_edit)
10424 {
10425 // Restore initial value
10426 if (is_editable)
10427 {
10428 ImStrncpy(buf, edit_state.InitialText.Data, buf_size);
10429 value_changed = true;
10430 }
10431 }
10432
10433 // 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.
10434 // 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.
10435 bool apply_edit_back_to_user_buffer = !cancel_edit || (enter_pressed && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0);
10436 if (apply_edit_back_to_user_buffer)
10437 {
10438 // Apply new value immediately - copy modified buffer back
10439 // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer
10440 // FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect.
10441 // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks.
10442 if (is_editable)
10443 {
10444 edit_state.TempTextBuffer.resize(edit_state.Text.Size * 4);
10445 ImTextStrToUtf8(edit_state.TempTextBuffer.Data, edit_state.TempTextBuffer.Size, edit_state.Text.Data, NULL);
10446 }
10447
10448 // User callback
10449 if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackAlways)) != 0)
10450 {
10451 IM_ASSERT(callback != NULL);
10452
10453 // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment.
10454 ImGuiInputTextFlags event_flag = 0;
10455 ImGuiKey event_key = ImGuiKey_COUNT;
10456 if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && IsKeyPressedMap(ImGuiKey_Tab))
10457 {
10458 event_flag = ImGuiInputTextFlags_CallbackCompletion;
10459 event_key = ImGuiKey_Tab;
10460 }
10461 else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_UpArrow))
10462 {
10463 event_flag = ImGuiInputTextFlags_CallbackHistory;
10464 event_key = ImGuiKey_UpArrow;
10465 }
10466 else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressedMap(ImGuiKey_DownArrow))
10467 {
10468 event_flag = ImGuiInputTextFlags_CallbackHistory;
10469 event_key = ImGuiKey_DownArrow;
10470 }
10471 else if (flags & ImGuiInputTextFlags_CallbackAlways)
10472 event_flag = ImGuiInputTextFlags_CallbackAlways;
10473
10474 if (event_flag)
10475 {
10476 ImGuiTextEditCallbackData callback_data;
10477 memset(&callback_data, 0, sizeof(ImGuiTextEditCallbackData));
10478 callback_data.EventFlag = event_flag;
10479 callback_data.Flags = flags;
10480 callback_data.UserData = user_data;
10481 callback_data.ReadOnly = !is_editable;
10482
10483 callback_data.EventKey = event_key;
10484 callback_data.Buf = edit_state.TempTextBuffer.Data;
10485 callback_data.BufTextLen = edit_state.CurLenA;
10486 callback_data.BufSize = edit_state.BufSizeA;
10487 callback_data.BufDirty = false;
10488
10489 // 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)
10490 ImWchar* text = edit_state.Text.Data;
10491 const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.cursor);
10492 const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_start);
10493 const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_end);
10494
10495 // Call user code
10496 callback(&callback_data);
10497
10498 // Read back what user may have modified
10499 IM_ASSERT(callback_data.Buf == edit_state.TempTextBuffer.Data); // Invalid to modify those fields
10500 IM_ASSERT(callback_data.BufSize == edit_state.BufSizeA);
10501 IM_ASSERT(callback_data.Flags == flags);
10502 if (callback_data.CursorPos != utf8_cursor_pos) edit_state.StbState.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos);
10503 if (callback_data.SelectionStart != utf8_selection_start) edit_state.StbState.select_start = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart);
10504 if (callback_data.SelectionEnd != utf8_selection_end) edit_state.StbState.select_end = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd);
10505 if (callback_data.BufDirty)
10506 {
10507 IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text!
10508 edit_state.CurLenW = ImTextStrFromUtf8(edit_state.Text.Data, edit_state.Text.Size, callback_data.Buf, NULL);
10509 edit_state.CurLenA = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen()
10510 edit_state.CursorAnimReset();
10511 }
10512 }
10513 }
10514
10515 // Copy back to user buffer
10516 if (is_editable && strcmp(edit_state.TempTextBuffer.Data, buf) != 0)
10517 {
10518 ImStrncpy(buf, edit_state.TempTextBuffer.Data, buf_size);
10519 value_changed = true;
10520 }
10521 }
10522 }
10523
10524 // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value)
10525 if (clear_active_id && g.ActiveId == id)
10526 ClearActiveID();
10527
10528 // Render
10529 // 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.
10530 const char* buf_display = (g.ActiveId == id && is_editable) ? edit_state.TempTextBuffer.Data : buf;
10531 buf = NULL;
10532
10533 RenderNavHighlight(frame_bb, id);
10534 if (!is_multiline)
10535 RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
10536
10537 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
10538 ImVec2 render_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding;
10539 ImVec2 text_size(0.f, 0.f);
10540 const bool is_currently_scrolling = (edit_state.Id == id && is_multiline && g.ActiveId == draw_window->GetIDNoKeepAlive("#SCROLLY"));
10541 if (g.ActiveId == id || is_currently_scrolling)
10542 {
10543 edit_state.CursorAnim += io.DeltaTime;
10544
10545 // This is going to be messy. We need to:
10546 // - Display the text (this alone can be more easily clipped)
10547 // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation)
10548 // - Measure text height (for scrollbar)
10549 // 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)
10550 // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8.
10551 const ImWchar* text_begin = edit_state.Text.Data;
10552 ImVec2 cursor_offset, select_start_offset;
10553
10554 {
10555 // Count lines + find lines numbers straddling 'cursor' and 'select_start' position.
10556 const ImWchar* searches_input_ptr[2];
10557 searches_input_ptr[0] = text_begin + edit_state.StbState.cursor;
10558 searches_input_ptr[1] = NULL;
10559 int searches_remaining = 1;
10560 int searches_result_line_number[2] = {-1, -999};
10561 if (edit_state.StbState.select_start != edit_state.StbState.select_end)
10562 {
10563 searches_input_ptr[1] = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end);
10564 searches_result_line_number[1] = -1;
10565 searches_remaining++;
10566 }
10567
10568 // Iterate all lines to find our line numbers
10569 // In multi-line mode, we never exit the loop until all lines are counted, so add one extra to the searches_remaining counter.
10570 searches_remaining += is_multiline ? 1 : 0;
10571 int line_count = 0;
10572 for (const ImWchar* s = text_begin; *s != 0; s++)
10573 if (*s == '\n')
10574 {
10575 line_count++;
10576 if (searches_result_line_number[0] == -1 && s >= searches_input_ptr[0])
10577 {
10578 searches_result_line_number[0] = line_count;
10579 if (--searches_remaining <= 0) break;
10580 }
10581 if (searches_result_line_number[1] == -1 && s >= searches_input_ptr[1])
10582 {
10583 searches_result_line_number[1] = line_count;
10584 if (--searches_remaining <= 0) break;
10585 }
10586 }
10587 line_count++;
10588 if (searches_result_line_number[0] == -1) searches_result_line_number[0] = line_count;
10589 if (searches_result_line_number[1] == -1) searches_result_line_number[1] = line_count;
10590
10591 // Calculate 2d position by finding the beginning of the line and measuring distance
10592 cursor_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[0], text_begin), searches_input_ptr[0]).x;
10593 cursor_offset.y = searches_result_line_number[0] * g.FontSize;
10594 if (searches_result_line_number[1] >= 0)
10595 {
10596 select_start_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[1], text_begin), searches_input_ptr[1]).x;
10597 select_start_offset.y = searches_result_line_number[1] * g.FontSize;
10598 }
10599
10600 // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224)
10601 if (is_multiline)
10602 text_size = ImVec2(size.x, line_count * g.FontSize);
10603 }
10604
10605 // Scroll
10606 if (edit_state.CursorFollow)
10607 {
10608 // Horizontal scroll in chunks of quarter width
10609 if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll))
10610 {
10611 const float scroll_increment_x = size.x * 0.25f;
10612 if (cursor_offset.x < edit_state.ScrollX)
10613 edit_state.ScrollX = (float)(int)ImMax(0.0f, cursor_offset.x - scroll_increment_x);
10614 else if (cursor_offset.x - size.x >= edit_state.ScrollX)
10615 edit_state.ScrollX = (float)(int)(cursor_offset.x - size.x + scroll_increment_x);
10616 }
10617 else
10618 {
10619 edit_state.ScrollX = 0.0f;
10620 }
10621
10622 // Vertical scroll
10623 if (is_multiline)
10624 {
10625 float scroll_y = draw_window->Scroll.y;
10626 if (cursor_offset.y - g.FontSize < scroll_y)
10627 scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize);
10628 else if (cursor_offset.y - size.y >= scroll_y)
10629 scroll_y = cursor_offset.y - size.y;
10630 draw_window->DC.CursorPos.y += (draw_window->Scroll.y - scroll_y); // To avoid a frame of lag
10631 draw_window->Scroll.y = scroll_y;
10632 render_pos.y = draw_window->DC.CursorPos.y;
10633 }
10634 }
10635 edit_state.CursorFollow = false;
10636 const ImVec2 render_scroll = ImVec2(edit_state.ScrollX, 0.0f);
10637
10638 // Draw selection
10639 if (edit_state.StbState.select_start != edit_state.StbState.select_end)
10640 {
10641 const ImWchar* text_selected_begin = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end);
10642 const ImWchar* text_selected_end = text_begin + ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end);
10643
10644 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.
10645 float bg_offy_dn = is_multiline ? 0.0f : 2.0f;
10646 ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg);
10647 ImVec2 rect_pos = render_pos + select_start_offset - render_scroll;
10648 for (const ImWchar* p = text_selected_begin; p < text_selected_end;)
10649 {
10650 if (rect_pos.y > clip_rect.w + g.FontSize)
10651 break;
10652 if (rect_pos.y < clip_rect.y)
10653 {
10654 while (p < text_selected_end)
10655 if (*p++ == '\n')
10656 break;
10657 }
10658 else
10659 {
10660 ImVec2 rect_size = InputTextCalcTextSizeW(p, text_selected_end, &p, NULL, true);
10661 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
10662 ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos + ImVec2(rect_size.x, bg_offy_dn));
10663 rect.ClipWith(clip_rect);
10664 if (rect.Overlaps(clip_rect))
10665 draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color);
10666 }
10667 rect_pos.x = render_pos.x - render_scroll.x;
10668 rect_pos.y += g.FontSize;
10669 }
10670 }
10671
10672 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);
10673
10674 // Draw blinking cursor
10675 bool cursor_is_visible = (!g.IO.OptCursorBlink) || (g.InputTextState.CursorAnim <= 0.0f) || fmodf(g.InputTextState.CursorAnim, 1.20f) <= 0.80f;
10676 ImVec2 cursor_screen_pos = render_pos + cursor_offset - render_scroll;
10677 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);
10678 if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect))
10679 draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text));
10680
10681 // 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.)
10682 if (is_editable)
10683 g.OsImePosRequest = ImVec2(cursor_screen_pos.x - 1, cursor_screen_pos.y - g.FontSize);
10684 }
10685 else
10686 {
10687 // Render text only
10688 const char* buf_end = NULL;
10689 if (is_multiline)
10690 text_size = ImVec2(size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_end) * g.FontSize); // We don't need width
10691 draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos, GetColorU32(ImGuiCol_Text), buf_display, buf_end, 0.0f, is_multiline ? NULL : &clip_rect);
10692 }
10693
10694 if (is_multiline)
10695 {
10696 Dummy(text_size + ImVec2(0.0f, g.FontSize)); // Always add room to scroll an extra line
10697 EndChildFrame();
10698 EndGroup();
10699 }
10700
10701 if (is_password)
10702 PopFont();
10703
10704 // Log as text
10705 if (g.LogEnabled && !is_password)
10706 LogRenderedText(&render_pos, buf_display, NULL);
10707
10708 if (label_size.x > 0)
10709 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
10710
10711 if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0)
10712 return enter_pressed;
10713 else
10714 return value_changed;
10715 }
10716
InputText(const char * label,char * buf,size_t buf_size,ImGuiInputTextFlags flags,ImGuiTextEditCallback callback,void * user_data)10717 bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data)
10718 {
10719 IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline()
10720 return InputTextEx(label, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data);
10721 }
10722
InputTextMultiline(const char * label,char * buf,size_t buf_size,const ImVec2 & size,ImGuiInputTextFlags flags,ImGuiTextEditCallback callback,void * user_data)10723 bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data)
10724 {
10725 return InputTextEx(label, buf, (int)buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data);
10726 }
10727
10728 // NB: scalar_format here must be a simple "%xx" format string with no prefix/suffix (unlike the Drag/Slider functions "display_format" argument)
InputScalarEx(const char * label,ImGuiDataType data_type,void * data_ptr,void * step_ptr,void * step_fast_ptr,const char * scalar_format,ImGuiInputTextFlags extra_flags)10729 bool ImGui::InputScalarEx(const char* label, ImGuiDataType data_type, void* data_ptr, void* step_ptr, void* step_fast_ptr, const char* scalar_format, ImGuiInputTextFlags extra_flags)
10730 {
10731 ImGuiWindow* window = GetCurrentWindow();
10732 if (window->SkipItems)
10733 return false;
10734
10735 ImGuiContext& g = *GImGui;
10736 const ImGuiStyle& style = g.Style;
10737 const ImVec2 label_size = CalcTextSize(label, NULL, true);
10738
10739 BeginGroup();
10740 PushID(label);
10741 const ImVec2 button_sz = ImVec2(GetFrameHeight(), GetFrameHeight());
10742 if (step_ptr)
10743 PushItemWidth(ImMax(1.0f, CalcItemWidth() - (button_sz.x + style.ItemInnerSpacing.x) * 2));
10744
10745 char buf[64];
10746 DataTypeFormatString(data_type, data_ptr, scalar_format, buf, IM_ARRAYSIZE(buf));
10747
10748 bool value_changed = false;
10749 if (!(extra_flags & ImGuiInputTextFlags_CharsHexadecimal))
10750 extra_flags |= ImGuiInputTextFlags_CharsDecimal;
10751 extra_flags |= ImGuiInputTextFlags_AutoSelectAll;
10752 if (InputText("", buf, IM_ARRAYSIZE(buf), extra_flags)) // PushId(label) + "" gives us the expected ID from outside point of view
10753 value_changed = DataTypeApplyOpFromText(buf, GImGui->InputTextState.InitialText.begin(), data_type, data_ptr, scalar_format);
10754
10755 // Step buttons
10756 if (step_ptr)
10757 {
10758 PopItemWidth();
10759 SameLine(0, style.ItemInnerSpacing.x);
10760 if (ButtonEx("-", button_sz, ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups))
10761 {
10762 DataTypeApplyOp(data_type, '-', data_ptr, g.IO.KeyCtrl && step_fast_ptr ? step_fast_ptr : step_ptr);
10763 value_changed = true;
10764 }
10765 SameLine(0, style.ItemInnerSpacing.x);
10766 if (ButtonEx("+", button_sz, ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups))
10767 {
10768 DataTypeApplyOp(data_type, '+', data_ptr, g.IO.KeyCtrl && step_fast_ptr ? step_fast_ptr : step_ptr);
10769 value_changed = true;
10770 }
10771 }
10772 PopID();
10773
10774 if (label_size.x > 0)
10775 {
10776 SameLine(0, style.ItemInnerSpacing.x);
10777 RenderText(ImVec2(window->DC.CursorPos.x, window->DC.CursorPos.y + style.FramePadding.y), label);
10778 ItemSize(label_size, style.FramePadding.y);
10779 }
10780 EndGroup();
10781
10782 return value_changed;
10783 }
10784
InputFloat(const char * label,float * v,float step,float step_fast,int decimal_precision,ImGuiInputTextFlags extra_flags)10785 bool ImGui::InputFloat(const char* label, float* v, float step, float step_fast, int decimal_precision, ImGuiInputTextFlags extra_flags)
10786 {
10787 char display_format[16];
10788 if (decimal_precision < 0)
10789 strcpy(display_format, "%f"); // Ideally we'd have a minimum decimal precision of 1 to visually denote that this is a float, while hiding non-significant digits? %f doesn't have a minimum of 1
10790 else
10791 ImFormatString(display_format, IM_ARRAYSIZE(display_format), "%%.%df", decimal_precision);
10792 return InputScalarEx(label, ImGuiDataType_Float, (void*)v, (void*)(step > 0.0f ? &step : NULL), (void*)(step_fast > 0.0f ? &step_fast : NULL), display_format, extra_flags);
10793 }
10794
InputInt(const char * label,int * v,int step,int step_fast,ImGuiInputTextFlags extra_flags)10795 bool ImGui::InputInt(const char* label, int* v, int step, int step_fast, ImGuiInputTextFlags extra_flags)
10796 {
10797 // 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.
10798 const char* scalar_format = (extra_flags & ImGuiInputTextFlags_CharsHexadecimal) ? "%08X" : "%d";
10799 return InputScalarEx(label, ImGuiDataType_Int, (void*)v, (void*)(step > 0.0f ? &step : NULL), (void*)(step_fast > 0.0f ? &step_fast : NULL), scalar_format, extra_flags);
10800 }
10801
InputFloatN(const char * label,float * v,int components,int decimal_precision,ImGuiInputTextFlags extra_flags)10802 bool ImGui::InputFloatN(const char* label, float* v, int components, int decimal_precision, ImGuiInputTextFlags extra_flags)
10803 {
10804 ImGuiWindow* window = GetCurrentWindow();
10805 if (window->SkipItems)
10806 return false;
10807
10808 ImGuiContext& g = *GImGui;
10809 bool value_changed = false;
10810 BeginGroup();
10811 PushID(label);
10812 PushMultiItemsWidths(components);
10813 for (int i = 0; i < components; i++)
10814 {
10815 PushID(i);
10816 value_changed |= InputFloat("##v", &v[i], 0, 0, decimal_precision, extra_flags);
10817 SameLine(0, g.Style.ItemInnerSpacing.x);
10818 PopID();
10819 PopItemWidth();
10820 }
10821 PopID();
10822
10823 TextUnformatted(label, FindRenderedTextEnd(label));
10824 EndGroup();
10825
10826 return value_changed;
10827 }
10828
InputFloat2(const char * label,float v[2],int decimal_precision,ImGuiInputTextFlags extra_flags)10829 bool ImGui::InputFloat2(const char* label, float v[2], int decimal_precision, ImGuiInputTextFlags extra_flags)
10830 {
10831 return InputFloatN(label, v, 2, decimal_precision, extra_flags);
10832 }
10833
InputFloat3(const char * label,float v[3],int decimal_precision,ImGuiInputTextFlags extra_flags)10834 bool ImGui::InputFloat3(const char* label, float v[3], int decimal_precision, ImGuiInputTextFlags extra_flags)
10835 {
10836 return InputFloatN(label, v, 3, decimal_precision, extra_flags);
10837 }
10838
InputFloat4(const char * label,float v[4],int decimal_precision,ImGuiInputTextFlags extra_flags)10839 bool ImGui::InputFloat4(const char* label, float v[4], int decimal_precision, ImGuiInputTextFlags extra_flags)
10840 {
10841 return InputFloatN(label, v, 4, decimal_precision, extra_flags);
10842 }
10843
InputIntN(const char * label,int * v,int components,ImGuiInputTextFlags extra_flags)10844 bool ImGui::InputIntN(const char* label, int* v, int components, ImGuiInputTextFlags extra_flags)
10845 {
10846 ImGuiWindow* window = GetCurrentWindow();
10847 if (window->SkipItems)
10848 return false;
10849
10850 ImGuiContext& g = *GImGui;
10851 bool value_changed = false;
10852 BeginGroup();
10853 PushID(label);
10854 PushMultiItemsWidths(components);
10855 for (int i = 0; i < components; i++)
10856 {
10857 PushID(i);
10858 value_changed |= InputInt("##v", &v[i], 0, 0, extra_flags);
10859 SameLine(0, g.Style.ItemInnerSpacing.x);
10860 PopID();
10861 PopItemWidth();
10862 }
10863 PopID();
10864
10865 TextUnformatted(label, FindRenderedTextEnd(label));
10866 EndGroup();
10867
10868 return value_changed;
10869 }
10870
InputInt2(const char * label,int v[2],ImGuiInputTextFlags extra_flags)10871 bool ImGui::InputInt2(const char* label, int v[2], ImGuiInputTextFlags extra_flags)
10872 {
10873 return InputIntN(label, v, 2, extra_flags);
10874 }
10875
InputInt3(const char * label,int v[3],ImGuiInputTextFlags extra_flags)10876 bool ImGui::InputInt3(const char* label, int v[3], ImGuiInputTextFlags extra_flags)
10877 {
10878 return InputIntN(label, v, 3, extra_flags);
10879 }
10880
InputInt4(const char * label,int v[4],ImGuiInputTextFlags extra_flags)10881 bool ImGui::InputInt4(const char* label, int v[4], ImGuiInputTextFlags extra_flags)
10882 {
10883 return InputIntN(label, v, 4, extra_flags);
10884 }
10885
CalcMaxPopupHeightFromItemCount(int items_count)10886 static float CalcMaxPopupHeightFromItemCount(int items_count)
10887 {
10888 ImGuiContext& g = *GImGui;
10889 if (items_count <= 0)
10890 return FLT_MAX;
10891 return (g.FontSize + g.Style.ItemSpacing.y) * items_count - g.Style.ItemSpacing.y + (g.Style.WindowPadding.y * 2);
10892 }
10893
BeginCombo(const char * label,const char * preview_value,ImGuiComboFlags flags)10894 bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboFlags flags)
10895 {
10896 // Always consume the SetNextWindowSizeConstraint() call in our early return paths
10897 ImGuiContext& g = *GImGui;
10898 ImGuiCond backup_next_window_size_constraint = g.NextWindowData.SizeConstraintCond;
10899 g.NextWindowData.SizeConstraintCond = 0;
10900
10901 ImGuiWindow* window = GetCurrentWindow();
10902 if (window->SkipItems)
10903 return false;
10904
10905 const ImGuiStyle& style = g.Style;
10906 const ImGuiID id = window->GetID(label);
10907 const float w = CalcItemWidth();
10908
10909 const ImVec2 label_size = CalcTextSize(label, NULL, true);
10910 const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
10911 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));
10912 ItemSize(total_bb, style.FramePadding.y);
10913 if (!ItemAdd(total_bb, id, &frame_bb))
10914 return false;
10915
10916 bool hovered, held;
10917 bool pressed = ButtonBehavior(frame_bb, id, &hovered, &held);
10918 bool popup_open = IsPopupOpen(id);
10919
10920 const float arrow_size = GetFrameHeight();
10921 const ImRect value_bb(frame_bb.Min, frame_bb.Max - ImVec2(arrow_size, 0.0f));
10922 RenderNavHighlight(frame_bb, id);
10923 RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding);
10924 RenderFrame(ImVec2(frame_bb.Max.x - arrow_size, frame_bb.Min.y), frame_bb.Max, GetColorU32(popup_open || hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button), true, style.FrameRounding); // FIXME-ROUNDING
10925 RenderTriangle(ImVec2(frame_bb.Max.x - arrow_size + style.FramePadding.y, frame_bb.Min.y + style.FramePadding.y), ImGuiDir_Down);
10926 if (preview_value != NULL)
10927 RenderTextClipped(frame_bb.Min + style.FramePadding, value_bb.Max, preview_value, NULL, NULL, ImVec2(0.0f, 0.0f));
10928 if (label_size.x > 0)
10929 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
10930
10931 if ((pressed || g.NavActivateId == id) && !popup_open)
10932 {
10933 if (window->DC.NavLayerCurrent == 0)
10934 window->NavLastIds[0] = id;
10935 OpenPopupEx(id);
10936 popup_open = true;
10937 }
10938
10939 if (!popup_open)
10940 return false;
10941
10942 if (backup_next_window_size_constraint)
10943 {
10944 g.NextWindowData.SizeConstraintCond = backup_next_window_size_constraint;
10945 g.NextWindowData.SizeConstraintRect.Min.x = ImMax(g.NextWindowData.SizeConstraintRect.Min.x, w);
10946 }
10947 else
10948 {
10949 if ((flags & ImGuiComboFlags_HeightMask_) == 0)
10950 flags |= ImGuiComboFlags_HeightRegular;
10951 IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiComboFlags_HeightMask_)); // Only one
10952 int popup_max_height_in_items = -1;
10953 if (flags & ImGuiComboFlags_HeightRegular)
10954 popup_max_height_in_items = 8;
10955 else if (flags & ImGuiComboFlags_HeightSmall)
10956 popup_max_height_in_items = 4;
10957 else if (flags & ImGuiComboFlags_HeightLarge)
10958 popup_max_height_in_items = 20;
10959 SetNextWindowSizeConstraints(ImVec2(w, 0.0f), ImVec2(FLT_MAX, CalcMaxPopupHeightFromItemCount(popup_max_height_in_items)));
10960 }
10961
10962 char name[16];
10963 ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.CurrentPopupStack.Size); // Recycle windows based on depth
10964
10965 // Peak into expected window size so we can position it
10966 if (ImGuiWindow* popup_window = FindWindowByName(name))
10967 if (popup_window->WasActive)
10968 {
10969 ImVec2 size_contents = CalcSizeContents(popup_window);
10970 ImVec2 size_expected = CalcSizeAfterConstraint(popup_window, CalcSizeAutoFit(popup_window, size_contents));
10971 if (flags & ImGuiComboFlags_PopupAlignLeft)
10972 popup_window->AutoPosLastDirection = ImGuiDir_Left;
10973 ImVec2 pos = FindBestWindowPosForPopup(frame_bb.GetBL(), size_expected, &popup_window->AutoPosLastDirection, frame_bb, ImGuiPopupPositionPolicy_ComboBox);
10974 SetNextWindowPos(pos);
10975 }
10976
10977 ImGuiWindowFlags window_flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_Popup | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings;
10978 if (!Begin(name, NULL, window_flags))
10979 {
10980 EndPopup();
10981 IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above
10982 return false;
10983 }
10984
10985 // Horizontally align ourselves with the framed text
10986 if (style.FramePadding.x != style.WindowPadding.x)
10987 Indent(style.FramePadding.x - style.WindowPadding.x);
10988
10989 return true;
10990 }
10991
EndCombo()10992 void ImGui::EndCombo()
10993 {
10994 const ImGuiStyle& style = GImGui->Style;
10995 if (style.FramePadding.x != style.WindowPadding.x)
10996 Unindent(style.FramePadding.x - style.WindowPadding.x);
10997 EndPopup();
10998 }
10999
11000 // Old API, prefer using BeginCombo() nowadays if you can.
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)11001 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)
11002 {
11003 ImGuiContext& g = *GImGui;
11004
11005 const char* preview_text = NULL;
11006 if (*current_item >= 0 && *current_item < items_count)
11007 items_getter(data, *current_item, &preview_text);
11008
11009 // The old Combo() API exposed "popup_max_height_in_items", however the new more general BeginCombo() API doesn't, so we emulate it here.
11010 if (popup_max_height_in_items != -1 && !g.NextWindowData.SizeConstraintCond)
11011 {
11012 float popup_max_height = CalcMaxPopupHeightFromItemCount(popup_max_height_in_items);
11013 SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, popup_max_height));
11014 }
11015
11016 if (!BeginCombo(label, preview_text, 0))
11017 return false;
11018
11019 // Display items
11020 // FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed)
11021 bool value_changed = false;
11022 for (int i = 0; i < items_count; i++)
11023 {
11024 PushID((void*)(intptr_t)i);
11025 const bool item_selected = (i == *current_item);
11026 const char* item_text;
11027 if (!items_getter(data, i, &item_text))
11028 item_text = "*Unknown item*";
11029 if (Selectable(item_text, item_selected))
11030 {
11031 value_changed = true;
11032 *current_item = i;
11033 }
11034 if (item_selected)
11035 SetItemDefaultFocus();
11036 PopID();
11037 }
11038
11039 EndCombo();
11040 return value_changed;
11041 }
11042
Items_ArrayGetter(void * data,int idx,const char ** out_text)11043 static bool Items_ArrayGetter(void* data, int idx, const char** out_text)
11044 {
11045 const char* const* items = (const char* const*)data;
11046 if (out_text)
11047 *out_text = items[idx];
11048 return true;
11049 }
11050
Items_SingleStringGetter(void * data,int idx,const char ** out_text)11051 static bool Items_SingleStringGetter(void* data, int idx, const char** out_text)
11052 {
11053 // FIXME-OPT: we could pre-compute the indices to fasten this. But only 1 active combo means the waste is limited.
11054 const char* items_separated_by_zeros = (const char*)data;
11055 int items_count = 0;
11056 const char* p = items_separated_by_zeros;
11057 while (*p)
11058 {
11059 if (idx == items_count)
11060 break;
11061 p += strlen(p) + 1;
11062 items_count++;
11063 }
11064 if (!*p)
11065 return false;
11066 if (out_text)
11067 *out_text = p;
11068 return true;
11069 }
11070
11071 // Combo box helper allowing to pass an array of strings.
Combo(const char * label,int * current_item,const char * const items[],int items_count,int height_in_items)11072 bool ImGui::Combo(const char* label, int* current_item, const char* const items[], int items_count, int height_in_items)
11073 {
11074 const bool value_changed = Combo(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_in_items);
11075 return value_changed;
11076 }
11077
11078 // Combo box helper allowing to pass all items in a single string.
Combo(const char * label,int * current_item,const char * items_separated_by_zeros,int height_in_items)11079 bool ImGui::Combo(const char* label, int* current_item, const char* items_separated_by_zeros, int height_in_items)
11080 {
11081 int items_count = 0;
11082 const char* p = items_separated_by_zeros; // FIXME-OPT: Avoid computing this, or at least only when combo is open
11083 while (*p)
11084 {
11085 p += strlen(p) + 1;
11086 items_count++;
11087 }
11088 bool value_changed = Combo(label, current_item, Items_SingleStringGetter, (void*)items_separated_by_zeros, items_count, height_in_items);
11089 return value_changed;
11090 }
11091
11092 // Tip: pass an empty label (e.g. "##dummy") then you can use the space to draw other text or image.
11093 // But you need to make sure the ID is unique, e.g. enclose calls in PushID/PopID.
Selectable(const char * label,bool selected,ImGuiSelectableFlags flags,const ImVec2 & size_arg)11094 bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
11095 {
11096 ImGuiWindow* window = GetCurrentWindow();
11097 if (window->SkipItems)
11098 return false;
11099
11100 ImGuiContext& g = *GImGui;
11101 const ImGuiStyle& style = g.Style;
11102
11103 if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet) // FIXME-OPT: Avoid if vertically clipped.
11104 PopClipRect();
11105
11106 ImGuiID id = window->GetID(label);
11107 ImVec2 label_size = CalcTextSize(label, NULL, true);
11108 ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y);
11109 ImVec2 pos = window->DC.CursorPos;
11110 pos.y += window->DC.CurrentLineTextBaseOffset;
11111 ImRect bb(pos, pos + size);
11112 ItemSize(bb);
11113
11114 // Fill horizontal space.
11115 ImVec2 window_padding = window->WindowPadding;
11116 float max_x = (flags & ImGuiSelectableFlags_SpanAllColumns) ? GetWindowContentRegionMax().x : GetContentRegionMax().x;
11117 float w_draw = ImMax(label_size.x, window->Pos.x + max_x - window_padding.x - window->DC.CursorPos.x);
11118 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);
11119 ImRect bb_with_spacing(pos, pos + size_draw);
11120 if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_DrawFillAvailWidth))
11121 bb_with_spacing.Max.x += window_padding.x;
11122
11123 // Selectables are tightly packed together, we extend the box to cover spacing between selectable.
11124 float spacing_L = (float)(int)(style.ItemSpacing.x * 0.5f);
11125 float spacing_U = (float)(int)(style.ItemSpacing.y * 0.5f);
11126 float spacing_R = style.ItemSpacing.x - spacing_L;
11127 float spacing_D = style.ItemSpacing.y - spacing_U;
11128 bb_with_spacing.Min.x -= spacing_L;
11129 bb_with_spacing.Min.y -= spacing_U;
11130 bb_with_spacing.Max.x += spacing_R;
11131 bb_with_spacing.Max.y += spacing_D;
11132 if (!ItemAdd(bb_with_spacing, (flags & ImGuiSelectableFlags_Disabled) ? 0 : id))
11133 {
11134 if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet)
11135 PushColumnClipRect();
11136 return false;
11137 }
11138
11139 ImGuiButtonFlags button_flags = 0;
11140 if (flags & ImGuiSelectableFlags_Menu) button_flags |= ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_NoHoldingActiveID;
11141 if (flags & ImGuiSelectableFlags_MenuItem) button_flags |= ImGuiButtonFlags_PressedOnRelease;
11142 if (flags & ImGuiSelectableFlags_Disabled) button_flags |= ImGuiButtonFlags_Disabled;
11143 if (flags & ImGuiSelectableFlags_AllowDoubleClick) button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick;
11144 bool hovered, held;
11145 bool pressed = ButtonBehavior(bb_with_spacing, id, &hovered, &held, button_flags);
11146 if (flags & ImGuiSelectableFlags_Disabled)
11147 selected = false;
11148
11149 // Hovering selectable with mouse updates NavId accordingly so navigation can be resumed with gamepad/keyboard (this doesn't happen on most widgets)
11150 if (pressed || hovered) // && (g.IO.MouseDelta.x != 0.0f || g.IO.MouseDelta.y != 0.0f))
11151 if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerActiveMask)
11152 {
11153 g.NavDisableHighlight = true;
11154 SetNavID(id, window->DC.NavLayerCurrent);
11155 }
11156
11157 // Render
11158 if (hovered || selected)
11159 {
11160 const ImU32 col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
11161 RenderFrame(bb_with_spacing.Min, bb_with_spacing.Max, col, false, 0.0f);
11162 RenderNavHighlight(bb_with_spacing, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding);
11163 }
11164
11165 if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.ColumnsSet)
11166 {
11167 PushColumnClipRect();
11168 bb_with_spacing.Max.x -= (GetContentRegionMax().x - max_x);
11169 }
11170
11171 if (flags & ImGuiSelectableFlags_Disabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
11172 RenderTextClipped(bb.Min, bb_with_spacing.Max, label, NULL, &label_size, ImVec2(0.0f, 0.0f));
11173 if (flags & ImGuiSelectableFlags_Disabled) PopStyleColor();
11174
11175 // Automatically close popups
11176 if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(window->DC.ItemFlags & ImGuiItemFlags_SelectableDontClosePopup))
11177 CloseCurrentPopup();
11178 return pressed;
11179 }
11180
Selectable(const char * label,bool * p_selected,ImGuiSelectableFlags flags,const ImVec2 & size_arg)11181 bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags flags, const ImVec2& size_arg)
11182 {
11183 if (Selectable(label, *p_selected, flags, size_arg))
11184 {
11185 *p_selected = !*p_selected;
11186 return true;
11187 }
11188 return false;
11189 }
11190
11191 // Helper to calculate the size of a listbox and display a label on the right.
11192 // Tip: To have a list filling the entire window width, PushItemWidth(-1) and pass an empty label "##empty"
ListBoxHeader(const char * label,const ImVec2 & size_arg)11193 bool ImGui::ListBoxHeader(const char* label, const ImVec2& size_arg)
11194 {
11195 ImGuiWindow* window = GetCurrentWindow();
11196 if (window->SkipItems)
11197 return false;
11198
11199 const ImGuiStyle& style = GetStyle();
11200 const ImGuiID id = GetID(label);
11201 const ImVec2 label_size = CalcTextSize(label, NULL, true);
11202
11203 // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
11204 ImVec2 size = CalcItemSize(size_arg, CalcItemWidth(), GetTextLineHeightWithSpacing() * 7.4f + style.ItemSpacing.y);
11205 ImVec2 frame_size = ImVec2(size.x, ImMax(size.y, label_size.y));
11206 ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + frame_size);
11207 ImRect bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
11208 window->DC.LastItemRect = bb; // Forward storage for ListBoxFooter.. dodgy.
11209
11210 BeginGroup();
11211 if (label_size.x > 0)
11212 RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
11213
11214 BeginChildFrame(id, frame_bb.GetSize());
11215 return true;
11216 }
11217
ListBoxHeader(const char * label,int items_count,int height_in_items)11218 bool ImGui::ListBoxHeader(const char* label, int items_count, int height_in_items)
11219 {
11220 // Size default to hold ~7 items. Fractional number of items helps seeing that we can scroll down/up without looking at scrollbar.
11221 // However 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.
11222 // I am expecting that someone will come and complain about this behavior in a remote future, then we can advise on a better solution.
11223 if (height_in_items < 0)
11224 height_in_items = ImMin(items_count, 7);
11225 float height_in_items_f = height_in_items < items_count ? (height_in_items + 0.40f) : (height_in_items + 0.00f);
11226
11227 // 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().
11228 ImVec2 size;
11229 size.x = 0.0f;
11230 size.y = GetTextLineHeightWithSpacing() * height_in_items_f + GetStyle().ItemSpacing.y;
11231 return ListBoxHeader(label, size);
11232 }
11233
ListBoxFooter()11234 void ImGui::ListBoxFooter()
11235 {
11236 ImGuiWindow* parent_window = GetCurrentWindow()->ParentWindow;
11237 const ImRect bb = parent_window->DC.LastItemRect;
11238 const ImGuiStyle& style = GetStyle();
11239
11240 EndChildFrame();
11241
11242 // Redeclare item size so that it includes the label (we have stored the full size in LastItemRect)
11243 // We call SameLine() to restore DC.CurrentLine* data
11244 SameLine();
11245 parent_window->DC.CursorPos = bb.Min;
11246 ItemSize(bb, style.FramePadding.y);
11247 EndGroup();
11248 }
11249
ListBox(const char * label,int * current_item,const char * const items[],int items_count,int height_items)11250 bool ImGui::ListBox(const char* label, int* current_item, const char* const items[], int items_count, int height_items)
11251 {
11252 const bool value_changed = ListBox(label, current_item, Items_ArrayGetter, (void*)items, items_count, height_items);
11253 return value_changed;
11254 }
11255
ListBox(const char * label,int * current_item,bool (* items_getter)(void *,int,const char **),void * data,int items_count,int height_in_items)11256 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)
11257 {
11258 if (!ListBoxHeader(label, items_count, height_in_items))
11259 return false;
11260
11261 // 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.
11262 bool value_changed = false;
11263 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.
11264 while (clipper.Step())
11265 for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
11266 {
11267 const bool item_selected = (i == *current_item);
11268 const char* item_text;
11269 if (!items_getter(data, i, &item_text))
11270 item_text = "*Unknown item*";
11271
11272 PushID(i);
11273 if (Selectable(item_text, item_selected))
11274 {
11275 *current_item = i;
11276 value_changed = true;
11277 }
11278 if (item_selected)
11279 SetItemDefaultFocus();
11280 PopID();
11281 }
11282 ListBoxFooter();
11283 return value_changed;
11284 }
11285
MenuItem(const char * label,const char * shortcut,bool selected,bool enabled)11286 bool ImGui::MenuItem(const char* label, const char* shortcut, bool selected, bool enabled)
11287 {
11288 ImGuiWindow* window = GetCurrentWindow();
11289 if (window->SkipItems)
11290 return false;
11291
11292 ImGuiContext& g = *GImGui;
11293 ImGuiStyle& style = g.Style;
11294 ImVec2 pos = window->DC.CursorPos;
11295 ImVec2 label_size = CalcTextSize(label, NULL, true);
11296
11297 ImGuiSelectableFlags flags = ImGuiSelectableFlags_MenuItem | (enabled ? 0 : ImGuiSelectableFlags_Disabled);
11298 bool pressed;
11299 if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
11300 {
11301 // Mimic the exact layout spacing of BeginMenu() to allow MenuItem() inside a menu bar, which is a little misleading but may be useful
11302 // Note that in this situation we render neither the shortcut neither the selected tick mark
11303 float w = label_size.x;
11304 window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f);
11305 PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f);
11306 pressed = Selectable(label, false, flags, ImVec2(w, 0.0f));
11307 PopStyleVar();
11308 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().
11309 }
11310 else
11311 {
11312 ImVec2 shortcut_size = shortcut ? CalcTextSize(shortcut, NULL) : ImVec2(0.0f, 0.0f);
11313 float w = window->MenuColumns.DeclColumns(label_size.x, shortcut_size.x, (float)(int)(g.FontSize * 1.20f)); // Feedback for next frame
11314 float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w);
11315 pressed = Selectable(label, false, flags | ImGuiSelectableFlags_DrawFillAvailWidth, ImVec2(w, 0.0f));
11316 if (shortcut_size.x > 0.0f)
11317 {
11318 PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
11319 RenderText(pos + ImVec2(window->MenuColumns.Pos[1] + extra_w, 0.0f), shortcut, NULL, false);
11320 PopStyleColor();
11321 }
11322 if (selected)
11323 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);
11324 }
11325 return pressed;
11326 }
11327
MenuItem(const char * label,const char * shortcut,bool * p_selected,bool enabled)11328 bool ImGui::MenuItem(const char* label, const char* shortcut, bool* p_selected, bool enabled)
11329 {
11330 if (MenuItem(label, shortcut, p_selected ? *p_selected : false, enabled))
11331 {
11332 if (p_selected)
11333 *p_selected = !*p_selected;
11334 return true;
11335 }
11336 return false;
11337 }
11338
BeginMainMenuBar()11339 bool ImGui::BeginMainMenuBar()
11340 {
11341 ImGuiContext& g = *GImGui;
11342 SetNextWindowPos(ImVec2(0.0f, 0.0f));
11343 SetNextWindowSize(ImVec2(g.IO.DisplaySize.x, g.FontBaseSize + g.Style.FramePadding.y * 2.0f));
11344 PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
11345 PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0, 0));
11346 if (!Begin("##MainMenuBar", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_MenuBar) || !BeginMenuBar())
11347 {
11348 End();
11349 PopStyleVar(2);
11350 return false;
11351 }
11352 g.CurrentWindow->DC.MenuBarOffsetX += g.Style.DisplaySafeAreaPadding.x;
11353 return true;
11354 }
11355
EndMainMenuBar()11356 void ImGui::EndMainMenuBar()
11357 {
11358 EndMenuBar();
11359
11360 // When the user has left the menu layer (typically: closed menus through activation of an item), we restore focus to the previous window
11361 ImGuiContext& g = *GImGui;
11362 if (g.CurrentWindow == g.NavWindow && g.NavLayer == 0)
11363 FocusFrontMostActiveWindow(g.NavWindow);
11364
11365 End();
11366 PopStyleVar(2);
11367 }
11368
BeginMenuBar()11369 bool ImGui::BeginMenuBar()
11370 {
11371 ImGuiWindow* window = GetCurrentWindow();
11372 if (window->SkipItems)
11373 return false;
11374 if (!(window->Flags & ImGuiWindowFlags_MenuBar))
11375 return false;
11376
11377 IM_ASSERT(!window->DC.MenuBarAppending);
11378 BeginGroup(); // Save position
11379 PushID("##menubar");
11380
11381 // We don't clip with regular window clipping rectangle as it is already set to the area below. However we clip with window full rect.
11382 // We remove 1 worth of rounding to Max.x to that text in long menus don't tend to display over the lower-right rounded area, which looks particularly glitchy.
11383 ImRect bar_rect = window->MenuBarRect();
11384 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));
11385 clip_rect.ClipWith(window->WindowRectClipped);
11386 PushClipRect(clip_rect.Min, clip_rect.Max, false);
11387
11388 window->DC.CursorPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffsetX, bar_rect.Min.y); // + g.Style.FramePadding.y);
11389 window->DC.LayoutType = ImGuiLayoutType_Horizontal;
11390 window->DC.NavLayerCurrent++;
11391 window->DC.NavLayerCurrentMask <<= 1;
11392 window->DC.MenuBarAppending = true;
11393 AlignTextToFramePadding();
11394 return true;
11395 }
11396
EndMenuBar()11397 void ImGui::EndMenuBar()
11398 {
11399 ImGuiWindow* window = GetCurrentWindow();
11400 if (window->SkipItems)
11401 return;
11402 ImGuiContext& g = *GImGui;
11403
11404 // Nav: When a move request within one of our child menu failed, capture the request to navigate among our siblings.
11405 if (NavMoveRequestButNoResultYet() && (g.NavMoveDir == ImGuiDir_Left || g.NavMoveDir == ImGuiDir_Right) && (g.NavWindow->Flags & ImGuiWindowFlags_ChildMenu))
11406 {
11407 ImGuiWindow* nav_earliest_child = g.NavWindow;
11408 while (nav_earliest_child->ParentWindow && (nav_earliest_child->ParentWindow->Flags & ImGuiWindowFlags_ChildMenu))
11409 nav_earliest_child = nav_earliest_child->ParentWindow;
11410 if (nav_earliest_child->ParentWindow == window && nav_earliest_child->DC.ParentLayoutType == ImGuiLayoutType_Horizontal && g.NavMoveRequestForward == ImGuiNavForward_None)
11411 {
11412 // To do so we claim focus back, restore NavId and then process the movement request for yet another frame.
11413 // 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)
11414 IM_ASSERT(window->DC.NavLayerActiveMaskNext & 0x02); // Sanity check
11415 FocusWindow(window);
11416 SetNavIDAndMoveMouse(window->NavLastIds[1], 1, window->NavRectRel[1]);
11417 g.NavLayer = 1;
11418 g.NavDisableHighlight = true; // Hide highlight for the current frame so we don't see the intermediary selection.
11419 g.NavMoveRequestForward = ImGuiNavForward_ForwardQueued;
11420 NavMoveRequestCancel();
11421 }
11422 }
11423
11424 IM_ASSERT(window->Flags & ImGuiWindowFlags_MenuBar);
11425 IM_ASSERT(window->DC.MenuBarAppending);
11426 PopClipRect();
11427 PopID();
11428 window->DC.MenuBarOffsetX = window->DC.CursorPos.x - window->MenuBarRect().Min.x;
11429 window->DC.GroupStack.back().AdvanceCursor = false;
11430 EndGroup();
11431 window->DC.LayoutType = ImGuiLayoutType_Vertical;
11432 window->DC.NavLayerCurrent--;
11433 window->DC.NavLayerCurrentMask >>= 1;
11434 window->DC.MenuBarAppending = false;
11435 }
11436
BeginMenu(const char * label,bool enabled)11437 bool ImGui::BeginMenu(const char* label, bool enabled)
11438 {
11439 ImGuiWindow* window = GetCurrentWindow();
11440 if (window->SkipItems)
11441 return false;
11442
11443 ImGuiContext& g = *GImGui;
11444 const ImGuiStyle& style = g.Style;
11445 const ImGuiID id = window->GetID(label);
11446
11447 ImVec2 label_size = CalcTextSize(label, NULL, true);
11448
11449 bool pressed;
11450 bool menu_is_open = IsPopupOpen(id);
11451 bool menuset_is_open = !(window->Flags & ImGuiWindowFlags_Popup) && (g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].OpenParentId == window->IDStack.back());
11452 ImGuiWindow* backed_nav_window = g.NavWindow;
11453 if (menuset_is_open)
11454 g.NavWindow = window; // Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent)
11455
11456 // The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu (using FindBestPopupWindowPos).
11457 ImVec2 popup_pos, pos = window->DC.CursorPos;
11458 if (window->DC.LayoutType == ImGuiLayoutType_Horizontal)
11459 {
11460 // Menu inside an horizontal menu bar
11461 // Selectable extend their highlight by half ItemSpacing in each direction.
11462 // For ChildMenu, the popup position will be overwritten by the call to FindBestPopupWindowPos() in Begin()
11463 popup_pos = ImVec2(pos.x - window->WindowPadding.x, pos.y - style.FramePadding.y + window->MenuBarHeight());
11464 window->DC.CursorPos.x += (float)(int)(style.ItemSpacing.x * 0.5f);
11465 PushStyleVar(ImGuiStyleVar_ItemSpacing, style.ItemSpacing * 2.0f);
11466 float w = label_size.x;
11467 pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_Menu | ImGuiSelectableFlags_DontClosePopups | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f));
11468 PopStyleVar();
11469 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().
11470 }
11471 else
11472 {
11473 // Menu inside a menu
11474 popup_pos = ImVec2(pos.x, pos.y - style.WindowPadding.y);
11475 float w = window->MenuColumns.DeclColumns(label_size.x, 0.0f, (float)(int)(g.FontSize * 1.20f)); // Feedback to next frame
11476 float extra_w = ImMax(0.0f, GetContentRegionAvail().x - w);
11477 pressed = Selectable(label, menu_is_open, ImGuiSelectableFlags_Menu | ImGuiSelectableFlags_DontClosePopups | ImGuiSelectableFlags_DrawFillAvailWidth | (!enabled ? ImGuiSelectableFlags_Disabled : 0), ImVec2(w, 0.0f));
11478 if (!enabled) PushStyleColor(ImGuiCol_Text, g.Style.Colors[ImGuiCol_TextDisabled]);
11479 RenderTriangle(pos + ImVec2(window->MenuColumns.Pos[2] + extra_w + g.FontSize * 0.30f, 0.0f), ImGuiDir_Right);
11480 if (!enabled) PopStyleColor();
11481 }
11482
11483 const bool hovered = enabled && ItemHoverable(window->DC.LastItemRect, id);
11484 if (menuset_is_open)
11485 g.NavWindow = backed_nav_window;
11486
11487 bool want_open = false, want_close = false;
11488 if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu))
11489 {
11490 // Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive.
11491 bool moving_within_opened_triangle = false;
11492 if (g.HoveredWindow == window && g.OpenPopupStack.Size > g.CurrentPopupStack.Size && g.OpenPopupStack[g.CurrentPopupStack.Size].ParentWindow == window && !(window->Flags & ImGuiWindowFlags_MenuBar))
11493 {
11494 if (ImGuiWindow* next_window = g.OpenPopupStack[g.CurrentPopupStack.Size].Window)
11495 {
11496 ImRect next_window_rect = next_window->Rect();
11497 ImVec2 ta = g.IO.MousePos - g.IO.MouseDelta;
11498 ImVec2 tb = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR();
11499 ImVec2 tc = (window->Pos.x < next_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR();
11500 float extra = ImClamp(fabsf(ta.x - tb.x) * 0.30f, 5.0f, 30.0f); // add a bit of extra slack.
11501 ta.x += (window->Pos.x < next_window->Pos.x) ? -0.5f : +0.5f; // to avoid numerical issues
11502 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?
11503 tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +100.0f);
11504 moving_within_opened_triangle = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos);
11505 //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
11506 }
11507 }
11508
11509 want_close = (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_within_opened_triangle);
11510 want_open = (!menu_is_open && hovered && !moving_within_opened_triangle) || (!menu_is_open && hovered && pressed);
11511
11512 if (g.NavActivateId == id)
11513 {
11514 want_close = menu_is_open;
11515 want_open = !menu_is_open;
11516 }
11517 if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open
11518 {
11519 want_open = true;
11520 NavMoveRequestCancel();
11521 }
11522 }
11523 else
11524 {
11525 // Menu bar
11526 if (menu_is_open && pressed && menuset_is_open) // Click an open menu again to close it
11527 {
11528 want_close = true;
11529 want_open = menu_is_open = false;
11530 }
11531 else if (pressed || (hovered && menuset_is_open && !menu_is_open)) // First click to open, then hover to open others
11532 {
11533 want_open = true;
11534 }
11535 else if (g.NavId == id && g.NavMoveRequest && g.NavMoveDir == ImGuiDir_Down) // Nav-Down to open
11536 {
11537 want_open = true;
11538 NavMoveRequestCancel();
11539 }
11540 }
11541
11542 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.. }'
11543 want_close = true;
11544 if (want_close && IsPopupOpen(id))
11545 ClosePopupToLevel(g.CurrentPopupStack.Size);
11546
11547 if (!menu_is_open && want_open && g.OpenPopupStack.Size > g.CurrentPopupStack.Size)
11548 {
11549 // Don't recycle same menu level in the same frame, first close the other menu and yield for a frame.
11550 OpenPopup(label);
11551 return false;
11552 }
11553
11554 menu_is_open |= want_open;
11555 if (want_open)
11556 OpenPopup(label);
11557
11558 if (menu_is_open)
11559 {
11560 SetNextWindowPos(popup_pos, ImGuiCond_Always);
11561 ImGuiWindowFlags flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ((window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu)) ? ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_ChildWindow : ImGuiWindowFlags_ChildMenu);
11562 menu_is_open = BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
11563 }
11564
11565 return menu_is_open;
11566 }
11567
EndMenu()11568 void ImGui::EndMenu()
11569 {
11570 // Nav: When a left move request _within our child menu_ failed, close the menu.
11571 // A menu doesn't close itself because EndMenuBar() wants the catch the last Left<>Right inputs.
11572 // 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.
11573 ImGuiContext& g = *GImGui;
11574 ImGuiWindow* window = g.CurrentWindow;
11575 if (g.NavWindow && g.NavWindow->ParentWindow == window && g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet() && window->DC.LayoutType == ImGuiLayoutType_Vertical)
11576 {
11577 ClosePopupToLevel(g.OpenPopupStack.Size - 1);
11578 NavMoveRequestCancel();
11579 }
11580
11581 EndPopup();
11582 }
11583
11584 // Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
ColorTooltip(const char * text,const float * col,ImGuiColorEditFlags flags)11585 void ImGui::ColorTooltip(const char* text, const float* col, ImGuiColorEditFlags flags)
11586 {
11587 ImGuiContext& g = *GImGui;
11588
11589 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]);
11590 BeginTooltipEx(0, true);
11591
11592 const char* text_end = text ? FindRenderedTextEnd(text, NULL) : text;
11593 if (text_end > text)
11594 {
11595 TextUnformatted(text, text_end);
11596 Separator();
11597 }
11598
11599 ImVec2 sz(g.FontSize * 3 + g.Style.FramePadding.y * 2, g.FontSize * 3 + g.Style.FramePadding.y * 2);
11600 ColorButton("##preview", ImVec4(col[0], col[1], col[2], col[3]), (flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf)) | ImGuiColorEditFlags_NoTooltip, sz);
11601 SameLine();
11602 if (flags & ImGuiColorEditFlags_NoAlpha)
11603 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]);
11604 else
11605 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]);
11606 EndTooltip();
11607 }
11608
ImAlphaBlendColor(ImU32 col_a,ImU32 col_b)11609 static inline ImU32 ImAlphaBlendColor(ImU32 col_a, ImU32 col_b)
11610 {
11611 float t = ((col_b >> IM_COL32_A_SHIFT) & 0xFF) / 255.f;
11612 int r = ImLerp((int)(col_a >> IM_COL32_R_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_R_SHIFT) & 0xFF, t);
11613 int g = ImLerp((int)(col_a >> IM_COL32_G_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_G_SHIFT) & 0xFF, t);
11614 int b = ImLerp((int)(col_a >> IM_COL32_B_SHIFT) & 0xFF, (int)(col_b >> IM_COL32_B_SHIFT) & 0xFF, t);
11615 return IM_COL32(r, g, b, 0xFF);
11616 }
11617
11618 // 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.
11619 // 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.
RenderColorRectWithAlphaCheckerboard(ImVec2 p_min,ImVec2 p_max,ImU32 col,float grid_step,ImVec2 grid_off,float rounding,int rounding_corners_flags)11620 void ImGui::RenderColorRectWithAlphaCheckerboard(ImVec2 p_min, ImVec2 p_max, ImU32 col, float grid_step, ImVec2 grid_off, float rounding, int rounding_corners_flags)
11621 {
11622 ImGuiWindow* window = GetCurrentWindow();
11623 if (((col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT) < 0xFF)
11624 {
11625 ImU32 col_bg1 = GetColorU32(ImAlphaBlendColor(IM_COL32(204, 204, 204, 255), col));
11626 ImU32 col_bg2 = GetColorU32(ImAlphaBlendColor(IM_COL32(128, 128, 128, 255), col));
11627 window->DrawList->AddRectFilled(p_min, p_max, col_bg1, rounding, rounding_corners_flags);
11628
11629 int yi = 0;
11630 for (float y = p_min.y + grid_off.y; y < p_max.y; y += grid_step, yi++)
11631 {
11632 float y1 = ImClamp(y, p_min.y, p_max.y), y2 = ImMin(y + grid_step, p_max.y);
11633 if (y2 <= y1)
11634 continue;
11635 for (float x = p_min.x + grid_off.x + (yi & 1) * grid_step; x < p_max.x; x += grid_step * 2.0f)
11636 {
11637 float x1 = ImClamp(x, p_min.x, p_max.x), x2 = ImMin(x + grid_step, p_max.x);
11638 if (x2 <= x1)
11639 continue;
11640 int rounding_corners_flags_cell = 0;
11641 if (y1 <= p_min.y)
11642 {
11643 if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopLeft;
11644 if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_TopRight;
11645 }
11646 if (y2 >= p_max.y)
11647 {
11648 if (x1 <= p_min.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotLeft;
11649 if (x2 >= p_max.x) rounding_corners_flags_cell |= ImDrawCornerFlags_BotRight;
11650 }
11651 rounding_corners_flags_cell &= rounding_corners_flags;
11652 window->DrawList->AddRectFilled(ImVec2(x1, y1), ImVec2(x2, y2), col_bg2, rounding_corners_flags_cell ? rounding : 0.0f, rounding_corners_flags_cell);
11653 }
11654 }
11655 }
11656 else
11657 {
11658 window->DrawList->AddRectFilled(p_min, p_max, col, rounding, rounding_corners_flags);
11659 }
11660 }
11661
SetColorEditOptions(ImGuiColorEditFlags flags)11662 void ImGui::SetColorEditOptions(ImGuiColorEditFlags flags)
11663 {
11664 ImGuiContext& g = *GImGui;
11665 if ((flags & ImGuiColorEditFlags__InputsMask) == 0)
11666 flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__InputsMask;
11667 if ((flags & ImGuiColorEditFlags__DataTypeMask) == 0)
11668 flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__DataTypeMask;
11669 if ((flags & ImGuiColorEditFlags__PickerMask) == 0)
11670 flags |= ImGuiColorEditFlags__OptionsDefault & ImGuiColorEditFlags__PickerMask;
11671 IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__InputsMask))); // Check only 1 option is selected
11672 IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__DataTypeMask))); // Check only 1 option is selected
11673 IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask))); // Check only 1 option is selected
11674 g.ColorEditOptions = flags;
11675 }
11676
11677 // A little colored square. Return true when clicked.
11678 // FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip.
11679 // 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip.
ColorButton(const char * desc_id,const ImVec4 & col,ImGuiColorEditFlags flags,ImVec2 size)11680 bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags, ImVec2 size)
11681 {
11682 ImGuiWindow* window = GetCurrentWindow();
11683 if (window->SkipItems)
11684 return false;
11685
11686 ImGuiContext& g = *GImGui;
11687 const ImGuiID id = window->GetID(desc_id);
11688 float default_size = GetFrameHeight();
11689 if (size.x == 0.0f)
11690 size.x = default_size;
11691 if (size.y == 0.0f)
11692 size.y = default_size;
11693 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
11694 ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f);
11695 if (!ItemAdd(bb, id))
11696 return false;
11697
11698 bool hovered, held;
11699 bool pressed = ButtonBehavior(bb, id, &hovered, &held);
11700
11701 if (flags & ImGuiColorEditFlags_NoAlpha)
11702 flags &= ~(ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf);
11703
11704 ImVec4 col_without_alpha(col.x, col.y, col.z, 1.0f);
11705 float grid_step = ImMin(size.x, size.y) / 2.99f;
11706 float rounding = ImMin(g.Style.FrameRounding, grid_step * 0.5f);
11707 ImRect bb_inner = bb;
11708 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.
11709 bb_inner.Expand(off);
11710 if ((flags & ImGuiColorEditFlags_AlphaPreviewHalf) && col.w < 1.0f)
11711 {
11712 float mid_x = (float)(int)((bb_inner.Min.x + bb_inner.Max.x) * 0.5f + 0.5f);
11713 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);
11714 window->DrawList->AddRectFilled(bb_inner.Min, ImVec2(mid_x, bb_inner.Max.y), GetColorU32(col_without_alpha), rounding, ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_BotLeft);
11715 }
11716 else
11717 {
11718 // Because GetColorU32() multiplies by the global style Alpha and we don't want to display a checkerboard if the source code had no alpha
11719 ImVec4 col_source = (flags & ImGuiColorEditFlags_AlphaPreview) ? col : col_without_alpha;
11720 if (col_source.w < 1.0f)
11721 RenderColorRectWithAlphaCheckerboard(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), grid_step, ImVec2(off, off), rounding);
11722 else
11723 window->DrawList->AddRectFilled(bb_inner.Min, bb_inner.Max, GetColorU32(col_source), rounding, ImDrawCornerFlags_All);
11724 }
11725 RenderNavHighlight(bb, id);
11726 if (g.Style.FrameBorderSize > 0.0f)
11727 RenderFrameBorder(bb.Min, bb.Max, rounding);
11728 else
11729 window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color button are often in need of some sort of border
11730
11731 // Drag and Drop Source
11732 if (g.ActiveId == id && BeginDragDropSource()) // NB: The ActiveId test is merely an optional micro-optimization
11733 {
11734 if (flags & ImGuiColorEditFlags_NoAlpha)
11735 SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F, &col, sizeof(float) * 3, ImGuiCond_Once);
11736 else
11737 SetDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F, &col, sizeof(float) * 4, ImGuiCond_Once);
11738 ColorButton(desc_id, col, flags);
11739 SameLine();
11740 TextUnformatted("Color");
11741 EndDragDropSource();
11742 hovered = false;
11743 }
11744
11745 // Tooltip
11746 if (!(flags & ImGuiColorEditFlags_NoTooltip) && hovered)
11747 ColorTooltip(desc_id, &col.x, flags & (ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf));
11748
11749 return pressed;
11750 }
11751
ColorEdit3(const char * label,float col[3],ImGuiColorEditFlags flags)11752 bool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags)
11753 {
11754 return ColorEdit4(label, col, flags | ImGuiColorEditFlags_NoAlpha);
11755 }
11756
ColorEditOptionsPopup(const float * col,ImGuiColorEditFlags flags)11757 void ImGui::ColorEditOptionsPopup(const float* col, ImGuiColorEditFlags flags)
11758 {
11759 bool allow_opt_inputs = !(flags & ImGuiColorEditFlags__InputsMask);
11760 bool allow_opt_datatype = !(flags & ImGuiColorEditFlags__DataTypeMask);
11761 if ((!allow_opt_inputs && !allow_opt_datatype) || !BeginPopup("context"))
11762 return;
11763 ImGuiContext& g = *GImGui;
11764 ImGuiColorEditFlags opts = g.ColorEditOptions;
11765 if (allow_opt_inputs)
11766 {
11767 if (RadioButton("RGB", (opts & ImGuiColorEditFlags_RGB) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_RGB;
11768 if (RadioButton("HSV", (opts & ImGuiColorEditFlags_HSV) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HSV;
11769 if (RadioButton("HEX", (opts & ImGuiColorEditFlags_HEX) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__InputsMask) | ImGuiColorEditFlags_HEX;
11770 }
11771 if (allow_opt_datatype)
11772 {
11773 if (allow_opt_inputs) Separator();
11774 if (RadioButton("0..255", (opts & ImGuiColorEditFlags_Uint8) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Uint8;
11775 if (RadioButton("0.00..1.00", (opts & ImGuiColorEditFlags_Float) ? 1 : 0)) opts = (opts & ~ImGuiColorEditFlags__DataTypeMask) | ImGuiColorEditFlags_Float;
11776 }
11777
11778 if (allow_opt_inputs || allow_opt_datatype)
11779 Separator();
11780 if (Button("Copy as..", ImVec2(-1, 0)))
11781 OpenPopup("Copy");
11782 if (BeginPopup("Copy"))
11783 {
11784 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]);
11785 char buf[64];
11786 ImFormatString(buf, IM_ARRAYSIZE(buf), "(%.3ff, %.3ff, %.3ff, %.3ff)", col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
11787 if (Selectable(buf))
11788 SetClipboardText(buf);
11789 ImFormatString(buf, IM_ARRAYSIZE(buf), "(%d,%d,%d,%d)", cr, cg, cb, ca);
11790 if (Selectable(buf))
11791 SetClipboardText(buf);
11792 if (flags & ImGuiColorEditFlags_NoAlpha)
11793 ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X", cr, cg, cb);
11794 else
11795 ImFormatString(buf, IM_ARRAYSIZE(buf), "0x%02X%02X%02X%02X", cr, cg, cb, ca);
11796 if (Selectable(buf))
11797 SetClipboardText(buf);
11798 EndPopup();
11799 }
11800
11801 g.ColorEditOptions = opts;
11802 EndPopup();
11803 }
11804
ColorPickerOptionsPopup(ImGuiColorEditFlags flags,const float * ref_col)11805 static void ColorPickerOptionsPopup(ImGuiColorEditFlags flags, const float* ref_col)
11806 {
11807 bool allow_opt_picker = !(flags & ImGuiColorEditFlags__PickerMask);
11808 bool allow_opt_alpha_bar = !(flags & ImGuiColorEditFlags_NoAlpha) && !(flags & ImGuiColorEditFlags_AlphaBar);
11809 if ((!allow_opt_picker && !allow_opt_alpha_bar) || !ImGui::BeginPopup("context"))
11810 return;
11811 ImGuiContext& g = *GImGui;
11812 if (allow_opt_picker)
11813 {
11814 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
11815 ImGui::PushItemWidth(picker_size.x);
11816 for (int picker_type = 0; picker_type < 2; picker_type++)
11817 {
11818 // Draw small/thumbnail version of each picker type (over an invisible button for selection)
11819 if (picker_type > 0) ImGui::Separator();
11820 ImGui::PushID(picker_type);
11821 ImGuiColorEditFlags picker_flags = ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_NoSidePreview | (flags & ImGuiColorEditFlags_NoAlpha);
11822 if (picker_type == 0) picker_flags |= ImGuiColorEditFlags_PickerHueBar;
11823 if (picker_type == 1) picker_flags |= ImGuiColorEditFlags_PickerHueWheel;
11824 ImVec2 backup_pos = ImGui::GetCursorScreenPos();
11825 if (ImGui::Selectable("##selectable", false, 0, picker_size)) // By default, Selectable() is closing popup
11826 g.ColorEditOptions = (g.ColorEditOptions & ~ImGuiColorEditFlags__PickerMask) | (picker_flags & ImGuiColorEditFlags__PickerMask);
11827 ImGui::SetCursorScreenPos(backup_pos);
11828 ImVec4 dummy_ref_col;
11829 memcpy(&dummy_ref_col.x, ref_col, sizeof(float) * (picker_flags & ImGuiColorEditFlags_NoAlpha ? 3 : 4));
11830 ImGui::ColorPicker4("##dummypicker", &dummy_ref_col.x, picker_flags);
11831 ImGui::PopID();
11832 }
11833 ImGui::PopItemWidth();
11834 }
11835 if (allow_opt_alpha_bar)
11836 {
11837 if (allow_opt_picker) ImGui::Separator();
11838 ImGui::CheckboxFlags("Alpha Bar", (unsigned int*)&g.ColorEditOptions, ImGuiColorEditFlags_AlphaBar);
11839 }
11840 ImGui::EndPopup();
11841 }
11842
11843 // Edit colors components (each component in 0.0f..1.0f range).
11844 // See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
11845 // 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.
ColorEdit4(const char * label,float col[4],ImGuiColorEditFlags flags)11846 bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags)
11847 {
11848 ImGuiWindow* window = GetCurrentWindow();
11849 if (window->SkipItems)
11850 return false;
11851
11852 ImGuiContext& g = *GImGui;
11853 const ImGuiStyle& style = g.Style;
11854 const float square_sz = GetFrameHeight();
11855 const float w_extra = (flags & ImGuiColorEditFlags_NoSmallPreview) ? 0.0f : (square_sz + style.ItemInnerSpacing.x);
11856 const float w_items_all = CalcItemWidth() - w_extra;
11857 const char* label_display_end = FindRenderedTextEnd(label);
11858
11859 const bool alpha = (flags & ImGuiColorEditFlags_NoAlpha) == 0;
11860 const bool hdr = (flags & ImGuiColorEditFlags_HDR) != 0;
11861 const int components = alpha ? 4 : 3;
11862 const ImGuiColorEditFlags flags_untouched = flags;
11863
11864 BeginGroup();
11865 PushID(label);
11866
11867 // If we're not showing any slider there's no point in doing any HSV conversions
11868 if (flags & ImGuiColorEditFlags_NoInputs)
11869 flags = (flags & (~ImGuiColorEditFlags__InputsMask)) | ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_NoOptions;
11870
11871 // Context menu: display and modify options (before defaults are applied)
11872 if (!(flags & ImGuiColorEditFlags_NoOptions))
11873 ColorEditOptionsPopup(col, flags);
11874
11875 // Read stored options
11876 if (!(flags & ImGuiColorEditFlags__InputsMask))
11877 flags |= (g.ColorEditOptions & ImGuiColorEditFlags__InputsMask);
11878 if (!(flags & ImGuiColorEditFlags__DataTypeMask))
11879 flags |= (g.ColorEditOptions & ImGuiColorEditFlags__DataTypeMask);
11880 if (!(flags & ImGuiColorEditFlags__PickerMask))
11881 flags |= (g.ColorEditOptions & ImGuiColorEditFlags__PickerMask);
11882 flags |= (g.ColorEditOptions & ~(ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask));
11883
11884 // Convert to the formats we need
11885 float f[4] = {col[0], col[1], col[2], alpha ? col[3] : 1.0f};
11886 if (flags & ImGuiColorEditFlags_HSV)
11887 ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]);
11888 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])};
11889
11890 bool value_changed = false;
11891 bool value_changed_as_float = false;
11892
11893 if ((flags & (ImGuiColorEditFlags_RGB | ImGuiColorEditFlags_HSV)) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
11894 {
11895 // RGB/HSV 0..255 Sliders
11896 const float w_item_one = ImMax(1.0f, (float)(int)((w_items_all - (style.ItemInnerSpacing.x) * (components - 1)) / (float)components));
11897 const float w_item_last = ImMax(1.0f, (float)(int)(w_items_all - (w_item_one + style.ItemInnerSpacing.x) * (components - 1)));
11898
11899 const bool hide_prefix = (w_item_one <= CalcTextSize((flags & ImGuiColorEditFlags_Float) ? "M:0.000" : "M:000").x);
11900 const char* ids[4] = {"##X", "##Y", "##Z", "##W"};
11901 const char* fmt_table_int[3][4] =
11902 {
11903 {"%3.0f", "%3.0f", "%3.0f", "%3.0f"}, // Short display
11904 {"R:%3.0f", "G:%3.0f", "B:%3.0f", "A:%3.0f"}, // Long display for RGBA
11905 {"H:%3.0f", "S:%3.0f", "V:%3.0f", "A:%3.0f"} // Long display for HSVA
11906 };
11907 const char* fmt_table_float[3][4] =
11908 {
11909 {"%0.3f", "%0.3f", "%0.3f", "%0.3f"}, // Short display
11910 {"R:%0.3f", "G:%0.3f", "B:%0.3f", "A:%0.3f"}, // Long display for RGBA
11911 {"H:%0.3f", "S:%0.3f", "V:%0.3f", "A:%0.3f"} // Long display for HSVA
11912 };
11913 const int fmt_idx = hide_prefix ? 0 : (flags & ImGuiColorEditFlags_HSV) ? 2 : 1;
11914
11915 PushItemWidth(w_item_one);
11916 for (int n = 0; n < components; n++)
11917 {
11918 if (n > 0)
11919 SameLine(0, style.ItemInnerSpacing.x);
11920 if (n + 1 == components)
11921 PushItemWidth(w_item_last);
11922 if (flags & ImGuiColorEditFlags_Float)
11923 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]);
11924 else
11925 value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]);
11926 if (!(flags & ImGuiColorEditFlags_NoOptions))
11927 OpenPopupOnItemClick("context");
11928 }
11929 PopItemWidth();
11930 PopItemWidth();
11931 }
11932 else if ((flags & ImGuiColorEditFlags_HEX) != 0 && (flags & ImGuiColorEditFlags_NoInputs) == 0)
11933 {
11934 // RGB Hexadecimal Input
11935 char buf[64];
11936 if (alpha)
11937 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));
11938 else
11939 ImFormatString(buf, IM_ARRAYSIZE(buf), "#%02X%02X%02X", ImClamp(i[0], 0, 255), ImClamp(i[1], 0, 255), ImClamp(i[2], 0, 255));
11940 PushItemWidth(w_items_all);
11941 if (InputText("##Text", buf, IM_ARRAYSIZE(buf), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase))
11942 {
11943 value_changed = true;
11944 char* p = buf;
11945 while (*p == '#' || ImCharIsSpace(*p))
11946 p++;
11947 i[0] = i[1] = i[2] = i[3] = 0;
11948 if (alpha)
11949 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)
11950 else
11951 sscanf(p, "%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]);
11952 }
11953 if (!(flags & ImGuiColorEditFlags_NoOptions))
11954 OpenPopupOnItemClick("context");
11955 PopItemWidth();
11956 }
11957
11958 ImGuiWindow* picker_active_window = NULL;
11959 if (!(flags & ImGuiColorEditFlags_NoSmallPreview))
11960 {
11961 if (!(flags & ImGuiColorEditFlags_NoInputs))
11962 SameLine(0, style.ItemInnerSpacing.x);
11963
11964 const ImVec4 col_v4(col[0], col[1], col[2], alpha ? col[3] : 1.0f);
11965 if (ColorButton("##ColorButton", col_v4, flags))
11966 {
11967 if (!(flags & ImGuiColorEditFlags_NoPicker))
11968 {
11969 // Store current color and open a picker
11970 g.ColorPickerRef = col_v4;
11971 OpenPopup("picker");
11972 SetNextWindowPos(window->DC.LastItemRect.GetBL() + ImVec2(-1, style.ItemSpacing.y));
11973 }
11974 }
11975 if (!(flags & ImGuiColorEditFlags_NoOptions))
11976 OpenPopupOnItemClick("context");
11977
11978 if (BeginPopup("picker"))
11979 {
11980 picker_active_window = g.CurrentWindow;
11981 if (label != label_display_end)
11982 {
11983 TextUnformatted(label, label_display_end);
11984 Separator();
11985 }
11986 ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar;
11987 ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags__InputsMask | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf;
11988 PushItemWidth(square_sz * 12.0f); // Use 256 + bar sizes?
11989 value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x);
11990 PopItemWidth();
11991 EndPopup();
11992 }
11993 }
11994
11995 if (label != label_display_end && !(flags & ImGuiColorEditFlags_NoLabel))
11996 {
11997 SameLine(0, style.ItemInnerSpacing.x);
11998 TextUnformatted(label, label_display_end);
11999 }
12000
12001 // Convert back
12002 if (picker_active_window == NULL)
12003 {
12004 if (!value_changed_as_float)
12005 for (int n = 0; n < 4; n++)
12006 f[n] = i[n] / 255.0f;
12007 if (flags & ImGuiColorEditFlags_HSV)
12008 ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]);
12009 if (value_changed)
12010 {
12011 col[0] = f[0];
12012 col[1] = f[1];
12013 col[2] = f[2];
12014 if (alpha)
12015 col[3] = f[3];
12016 }
12017 }
12018
12019 PopID();
12020 EndGroup();
12021
12022 // Drag and Drop Target
12023 if ((window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) && BeginDragDropTarget()) // NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test.
12024 {
12025 if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
12026 {
12027 memcpy((float*)col, payload->Data, sizeof(float) * 3);
12028 value_changed = true;
12029 }
12030 if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
12031 {
12032 memcpy((float*)col, payload->Data, sizeof(float) * components);
12033 value_changed = true;
12034 }
12035 EndDragDropTarget();
12036 }
12037
12038 // When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4().
12039 if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window)
12040 window->DC.LastItemId = g.ActiveId;
12041
12042 return value_changed;
12043 }
12044
ColorPicker3(const char * label,float col[3],ImGuiColorEditFlags flags)12045 bool ImGui::ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags)
12046 {
12047 float col4[4] = {col[0], col[1], col[2], 1.0f};
12048 if (!ColorPicker4(label, col4, flags | ImGuiColorEditFlags_NoAlpha))
12049 return false;
12050 col[0] = col4[0];
12051 col[1] = col4[1];
12052 col[2] = col4[2];
12053 return true;
12054 }
12055
12056 // 'pos' is position of the arrow tip. half_sz.x is length from base to tip. half_sz.y is length on each side.
RenderArrow(ImDrawList * draw_list,ImVec2 pos,ImVec2 half_sz,ImGuiDir direction,ImU32 col)12057 static void RenderArrow(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, ImGuiDir direction, ImU32 col)
12058 {
12059 switch (direction)
12060 {
12061 case ImGuiDir_Left:
12062 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);
12063 return;
12064 case ImGuiDir_Right:
12065 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);
12066 return;
12067 case ImGuiDir_Up:
12068 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);
12069 return;
12070 case ImGuiDir_Down:
12071 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);
12072 return;
12073 case ImGuiDir_None:
12074 case ImGuiDir_Count_:
12075 break; // Fix warnings
12076 }
12077 }
12078
RenderArrowsForVerticalBar(ImDrawList * draw_list,ImVec2 pos,ImVec2 half_sz,float bar_w)12079 static void RenderArrowsForVerticalBar(ImDrawList* draw_list, ImVec2 pos, ImVec2 half_sz, float bar_w)
12080 {
12081 RenderArrow(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);
12082 RenderArrow(draw_list, ImVec2(pos.x + half_sz.x, pos.y), half_sz, ImGuiDir_Right, IM_COL32_WHITE);
12083 RenderArrow(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);
12084 RenderArrow(draw_list, ImVec2(pos.x + bar_w - half_sz.x, pos.y), half_sz, ImGuiDir_Left, IM_COL32_WHITE);
12085 }
12086
12087 // ColorPicker
12088 // Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
12089 // 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..)
ColorPicker4(const char * label,float col[4],ImGuiColorEditFlags flags,const float * ref_col)12090 bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags, const float* ref_col)
12091 {
12092 ImGuiContext& g = *GImGui;
12093 ImGuiWindow* window = GetCurrentWindow();
12094 ImDrawList* draw_list = window->DrawList;
12095
12096 ImGuiStyle& style = g.Style;
12097 ImGuiIO& io = g.IO;
12098
12099 PushID(label);
12100 BeginGroup();
12101
12102 if (!(flags & ImGuiColorEditFlags_NoSidePreview))
12103 flags |= ImGuiColorEditFlags_NoSmallPreview;
12104
12105 // Context menu: display and store options.
12106 if (!(flags & ImGuiColorEditFlags_NoOptions))
12107 ColorPickerOptionsPopup(flags, col);
12108
12109 // Read stored options
12110 if (!(flags & ImGuiColorEditFlags__PickerMask))
12111 flags |= ((g.ColorEditOptions & ImGuiColorEditFlags__PickerMask) ? g.ColorEditOptions : ImGuiColorEditFlags__OptionsDefault) & ImGuiColorEditFlags__PickerMask;
12112 IM_ASSERT(ImIsPowerOfTwo((int)(flags & ImGuiColorEditFlags__PickerMask))); // Check that only 1 is selected
12113 if (!(flags & ImGuiColorEditFlags_NoOptions))
12114 flags |= (g.ColorEditOptions & ImGuiColorEditFlags_AlphaBar);
12115
12116 // Setup
12117 int components = (flags & ImGuiColorEditFlags_NoAlpha) ? 3 : 4;
12118 bool alpha_bar = (flags & ImGuiColorEditFlags_AlphaBar) && !(flags & ImGuiColorEditFlags_NoAlpha);
12119 ImVec2 picker_pos = window->DC.CursorPos;
12120 float square_sz = GetFrameHeight();
12121 float bars_width = square_sz; // Arbitrary smallish width of Hue/Alpha picking bars
12122 float sv_picker_size = ImMax(bars_width * 1, CalcItemWidth() - (alpha_bar ? 2 : 1) * (bars_width + style.ItemInnerSpacing.x)); // Saturation/Value picking box
12123 float bar0_pos_x = picker_pos.x + sv_picker_size + style.ItemInnerSpacing.x;
12124 float bar1_pos_x = bar0_pos_x + bars_width + style.ItemInnerSpacing.x;
12125 float bars_triangles_half_sz = (float)(int)(bars_width * 0.20f);
12126
12127 float backup_initial_col[4];
12128 memcpy(backup_initial_col, col, components * sizeof(float));
12129
12130 float wheel_thickness = sv_picker_size * 0.08f;
12131 float wheel_r_outer = sv_picker_size * 0.50f;
12132 float wheel_r_inner = wheel_r_outer - wheel_thickness;
12133 ImVec2 wheel_center(picker_pos.x + (sv_picker_size + bars_width) * 0.5f, picker_pos.y + sv_picker_size * 0.5f);
12134
12135 // Note: the triangle is displayed rotated with triangle_pa pointing to Hue, but most coordinates stays unrotated for logic.
12136 float triangle_r = wheel_r_inner - (int)(sv_picker_size * 0.027f);
12137 ImVec2 triangle_pa = ImVec2(triangle_r, 0.0f); // Hue point.
12138 ImVec2 triangle_pb = ImVec2(triangle_r * -0.5f, triangle_r * -0.866025f); // Black point.
12139 ImVec2 triangle_pc = ImVec2(triangle_r * -0.5f, triangle_r * +0.866025f); // White point.
12140
12141 float H, S, V;
12142 ColorConvertRGBtoHSV(col[0], col[1], col[2], H, S, V);
12143
12144 bool value_changed = false, value_changed_h = false, value_changed_sv = false;
12145
12146 PushItemFlag(ImGuiItemFlags_NoNav, true);
12147 if (flags & ImGuiColorEditFlags_PickerHueWheel)
12148 {
12149 // Hue wheel + SV triangle logic
12150 InvisibleButton("hsv", ImVec2(sv_picker_size + style.ItemInnerSpacing.x + bars_width, sv_picker_size));
12151 if (IsItemActive())
12152 {
12153 ImVec2 initial_off = g.IO.MouseClickedPos[0] - wheel_center;
12154 ImVec2 current_off = g.IO.MousePos - wheel_center;
12155 float initial_dist2 = ImLengthSqr(initial_off);
12156 if (initial_dist2 >= (wheel_r_inner - 1) * (wheel_r_inner - 1) && initial_dist2 <= (wheel_r_outer + 1) * (wheel_r_outer + 1))
12157 {
12158 // Interactive with Hue wheel
12159 H = atan2f(current_off.y, current_off.x) / IM_PI * 0.5f;
12160 if (H < 0.0f)
12161 H += 1.0f;
12162 value_changed = value_changed_h = true;
12163 }
12164 float cos_hue_angle = cosf(-H * 2.0f * IM_PI);
12165 float sin_hue_angle = sinf(-H * 2.0f * IM_PI);
12166 if (ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, ImRotate(initial_off, cos_hue_angle, sin_hue_angle)))
12167 {
12168 // Interacting with SV triangle
12169 ImVec2 current_off_unrotated = ImRotate(current_off, cos_hue_angle, sin_hue_angle);
12170 if (!ImTriangleContainsPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated))
12171 current_off_unrotated = ImTriangleClosestPoint(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated);
12172 float uu, vv, ww;
12173 ImTriangleBarycentricCoords(triangle_pa, triangle_pb, triangle_pc, current_off_unrotated, uu, vv, ww);
12174 V = ImClamp(1.0f - vv, 0.0001f, 1.0f);
12175 S = ImClamp(uu / V, 0.0001f, 1.0f);
12176 value_changed = value_changed_sv = true;
12177 }
12178 }
12179 if (!(flags & ImGuiColorEditFlags_NoOptions))
12180 OpenPopupOnItemClick("context");
12181 }
12182 else if (flags & ImGuiColorEditFlags_PickerHueBar)
12183 {
12184 // SV rectangle logic
12185 InvisibleButton("sv", ImVec2(sv_picker_size, sv_picker_size));
12186 if (IsItemActive())
12187 {
12188 S = ImSaturate((io.MousePos.x - picker_pos.x) / (sv_picker_size - 1));
12189 V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));
12190 value_changed = value_changed_sv = true;
12191 }
12192 if (!(flags & ImGuiColorEditFlags_NoOptions))
12193 OpenPopupOnItemClick("context");
12194
12195 // Hue bar logic
12196 SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y));
12197 InvisibleButton("hue", ImVec2(bars_width, sv_picker_size));
12198 if (IsItemActive())
12199 {
12200 H = ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));
12201 value_changed = value_changed_h = true;
12202 }
12203 }
12204
12205 // Alpha bar logic
12206 if (alpha_bar)
12207 {
12208 SetCursorScreenPos(ImVec2(bar1_pos_x, picker_pos.y));
12209 InvisibleButton("alpha", ImVec2(bars_width, sv_picker_size));
12210 if (IsItemActive())
12211 {
12212 col[3] = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));
12213 value_changed = true;
12214 }
12215 }
12216 PopItemFlag(); // ImGuiItemFlags_NoNav
12217
12218 if (!(flags & ImGuiColorEditFlags_NoSidePreview))
12219 {
12220 SameLine(0, style.ItemInnerSpacing.x);
12221 BeginGroup();
12222 }
12223
12224 if (!(flags & ImGuiColorEditFlags_NoLabel))
12225 {
12226 const char* label_display_end = FindRenderedTextEnd(label);
12227 if (label != label_display_end)
12228 {
12229 if ((flags & ImGuiColorEditFlags_NoSidePreview))
12230 SameLine(0, style.ItemInnerSpacing.x);
12231 TextUnformatted(label, label_display_end);
12232 }
12233 }
12234
12235 if (!(flags & ImGuiColorEditFlags_NoSidePreview))
12236 {
12237 PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true);
12238 ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
12239 if ((flags & ImGuiColorEditFlags_NoLabel))
12240 Text("Current");
12241 ColorButton("##current", col_v4, (flags & (ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf | ImGuiColorEditFlags_NoTooltip)), ImVec2(square_sz * 3, square_sz * 2));
12242 if (ref_col != NULL)
12243 {
12244 Text("Original");
12245 ImVec4 ref_col_v4(ref_col[0], ref_col[1], ref_col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : ref_col[3]);
12246 if (ColorButton("##original", ref_col_v4, (flags & (ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf | ImGuiColorEditFlags_NoTooltip)), ImVec2(square_sz * 3, square_sz * 2)))
12247 {
12248 memcpy(col, ref_col, components * sizeof(float));
12249 value_changed = true;
12250 }
12251 }
12252 PopItemFlag();
12253 EndGroup();
12254 }
12255
12256 // Convert back color to RGB
12257 if (value_changed_h || value_changed_sv)
12258 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]);
12259
12260 // R,G,B and H,S,V slider color editor
12261 if ((flags & ImGuiColorEditFlags_NoInputs) == 0)
12262 {
12263 PushItemWidth((alpha_bar ? bar1_pos_x : bar0_pos_x) + bars_width - picker_pos.x);
12264 ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoOptions | ImGuiColorEditFlags_NoSmallPreview | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf;
12265 ImGuiColorEditFlags sub_flags = (flags & sub_flags_to_forward) | ImGuiColorEditFlags_NoPicker;
12266 if (flags & ImGuiColorEditFlags_RGB || (flags & ImGuiColorEditFlags__InputsMask) == 0)
12267 value_changed |= ColorEdit4("##rgb", col, sub_flags | ImGuiColorEditFlags_RGB);
12268 if (flags & ImGuiColorEditFlags_HSV || (flags & ImGuiColorEditFlags__InputsMask) == 0)
12269 value_changed |= ColorEdit4("##hsv", col, sub_flags | ImGuiColorEditFlags_HSV);
12270 if (flags & ImGuiColorEditFlags_HEX || (flags & ImGuiColorEditFlags__InputsMask) == 0)
12271 value_changed |= ColorEdit4("##hex", col, sub_flags | ImGuiColorEditFlags_HEX);
12272 PopItemWidth();
12273 }
12274
12275 // Try to cancel hue wrap (after ColorEdit), if any
12276 if (value_changed)
12277 {
12278 float new_H, new_S, new_V;
12279 ColorConvertRGBtoHSV(col[0], col[1], col[2], new_H, new_S, new_V);
12280 if (new_H <= 0 && H > 0)
12281 {
12282 if (new_V <= 0 && V != new_V)
12283 ColorConvertHSVtoRGB(H, S, new_V <= 0 ? V * 0.5f : new_V, col[0], col[1], col[2]);
12284 else if (new_S <= 0)
12285 ColorConvertHSVtoRGB(H, new_S <= 0 ? S * 0.5f : new_S, new_V, col[0], col[1], col[2]);
12286 }
12287 }
12288
12289 ImVec4 hue_color_f(1, 1, 1, 1);
12290 ColorConvertHSVtoRGB(H, 1, 1, hue_color_f.x, hue_color_f.y, hue_color_f.z);
12291 ImU32 hue_color32 = ColorConvertFloat4ToU32(hue_color_f);
12292 ImU32 col32_no_alpha = ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 1.0f));
12293
12294 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)};
12295 ImVec2 sv_cursor_pos;
12296
12297 if (flags & ImGuiColorEditFlags_PickerHueWheel)
12298 {
12299 // Render Hue Wheel
12300 const float aeps = 1.5f / wheel_r_outer; // Half a pixel arc length in radians (2pi cancels out).
12301 const int segment_per_arc = ImMax(4, (int)wheel_r_outer / 12);
12302 for (int n = 0; n < 6; n++)
12303 {
12304 const float a0 = (n) / 6.0f * 2.0f * IM_PI - aeps;
12305 const float a1 = (n + 1.0f) / 6.0f * 2.0f * IM_PI + aeps;
12306 const int vert_start_idx = draw_list->VtxBuffer.Size;
12307 draw_list->PathArcTo(wheel_center, (wheel_r_inner + wheel_r_outer) * 0.5f, a0, a1, segment_per_arc);
12308 draw_list->PathStroke(IM_COL32_WHITE, false, wheel_thickness);
12309 const int vert_end_idx = draw_list->VtxBuffer.Size;
12310
12311 // Paint colors over existing vertices
12312 ImVec2 gradient_p0(wheel_center.x + cosf(a0) * wheel_r_inner, wheel_center.y + sinf(a0) * wheel_r_inner);
12313 ImVec2 gradient_p1(wheel_center.x + cosf(a1) * wheel_r_inner, wheel_center.y + sinf(a1) * wheel_r_inner);
12314 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]);
12315 }
12316
12317 // Render Cursor + preview on Hue Wheel
12318 float cos_hue_angle = cosf(H * 2.0f * IM_PI);
12319 float sin_hue_angle = sinf(H * 2.0f * IM_PI);
12320 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);
12321 float hue_cursor_rad = value_changed_h ? wheel_thickness * 0.65f : wheel_thickness * 0.55f;
12322 int hue_cursor_segments = ImClamp((int)(hue_cursor_rad / 1.4f), 9, 32);
12323 draw_list->AddCircleFilled(hue_cursor_pos, hue_cursor_rad, hue_color32, hue_cursor_segments);
12324 draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad + 1, IM_COL32(128, 128, 128, 255), hue_cursor_segments);
12325 draw_list->AddCircle(hue_cursor_pos, hue_cursor_rad, IM_COL32_WHITE, hue_cursor_segments);
12326
12327 // Render SV triangle (rotated according to hue)
12328 ImVec2 tra = wheel_center + ImRotate(triangle_pa, cos_hue_angle, sin_hue_angle);
12329 ImVec2 trb = wheel_center + ImRotate(triangle_pb, cos_hue_angle, sin_hue_angle);
12330 ImVec2 trc = wheel_center + ImRotate(triangle_pc, cos_hue_angle, sin_hue_angle);
12331 ImVec2 uv_white = GetFontTexUvWhitePixel();
12332 draw_list->PrimReserve(6, 6);
12333 draw_list->PrimVtx(tra, uv_white, hue_color32);
12334 draw_list->PrimVtx(trb, uv_white, hue_color32);
12335 draw_list->PrimVtx(trc, uv_white, IM_COL32_WHITE);
12336 draw_list->PrimVtx(tra, uv_white, IM_COL32_BLACK_TRANS);
12337 draw_list->PrimVtx(trb, uv_white, IM_COL32_BLACK);
12338 draw_list->PrimVtx(trc, uv_white, IM_COL32_BLACK_TRANS);
12339 draw_list->AddTriangle(tra, trb, trc, IM_COL32(128, 128, 128, 255), 1.5f);
12340 sv_cursor_pos = ImLerp(ImLerp(trc, tra, ImSaturate(S)), trb, ImSaturate(1 - V));
12341 }
12342 else if (flags & ImGuiColorEditFlags_PickerHueBar)
12343 {
12344 // Render SV Square
12345 draw_list->AddRectFilledMultiColor(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), IM_COL32_WHITE, hue_color32, hue_color32, IM_COL32_WHITE);
12346 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);
12347 RenderFrameBorder(picker_pos, picker_pos + ImVec2(sv_picker_size, sv_picker_size), 0.0f);
12348 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
12349 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);
12350
12351 // Render Hue Bar
12352 for (int i = 0; i < 6; ++i)
12353 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]);
12354 float bar0_line_y = (float)(int)(picker_pos.y + H * sv_picker_size + 0.5f);
12355 RenderFrameBorder(ImVec2(bar0_pos_x, picker_pos.y), ImVec2(bar0_pos_x + bars_width, picker_pos.y + sv_picker_size), 0.0f);
12356 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);
12357 }
12358
12359 // Render cursor/preview circle (clamp S/V within 0..1 range because floating points colors may lead HSV values to be out of range)
12360 float sv_cursor_rad = value_changed_sv ? 10.0f : 6.0f;
12361 draw_list->AddCircleFilled(sv_cursor_pos, sv_cursor_rad, col32_no_alpha, 12);
12362 draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad + 1, IM_COL32(128, 128, 128, 255), 12);
12363 draw_list->AddCircle(sv_cursor_pos, sv_cursor_rad, IM_COL32_WHITE, 12);
12364
12365 // Render alpha bar
12366 if (alpha_bar)
12367 {
12368 float alpha = ImSaturate(col[3]);
12369 ImRect bar1_bb(bar1_pos_x, picker_pos.y, bar1_pos_x + bars_width, picker_pos.y + sv_picker_size);
12370 RenderColorRectWithAlphaCheckerboard(bar1_bb.Min, bar1_bb.Max, IM_COL32(0, 0, 0, 0), bar1_bb.GetWidth() / 2.0f, ImVec2(0.0f, 0.0f));
12371 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);
12372 float bar1_line_y = (float)(int)(picker_pos.y + (1.0f - alpha) * sv_picker_size + 0.5f);
12373 RenderFrameBorder(bar1_bb.Min, bar1_bb.Max, 0.0f);
12374 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);
12375 }
12376
12377 EndGroup();
12378 PopID();
12379
12380 return value_changed && memcmp(backup_initial_col, col, components * sizeof(float));
12381 }
12382
12383 // Horizontal separating line.
Separator()12384 void ImGui::Separator()
12385 {
12386 ImGuiWindow* window = GetCurrentWindow();
12387 if (window->SkipItems)
12388 return;
12389 ImGuiContext& g = *GImGui;
12390
12391 ImGuiWindowFlags flags = 0;
12392 if ((flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical)) == 0)
12393 flags |= (window->DC.LayoutType == ImGuiLayoutType_Horizontal) ? ImGuiSeparatorFlags_Vertical : ImGuiSeparatorFlags_Horizontal;
12394 IM_ASSERT(ImIsPowerOfTwo((int)(flags & (ImGuiSeparatorFlags_Horizontal | ImGuiSeparatorFlags_Vertical)))); // Check that only 1 option is selected
12395 if (flags & ImGuiSeparatorFlags_Vertical)
12396 {
12397 VerticalSeparator();
12398 return;
12399 }
12400
12401 // Horizontal Separator
12402 if (window->DC.ColumnsSet)
12403 PopClipRect();
12404
12405 float x1 = window->Pos.x;
12406 float x2 = window->Pos.x + window->Size.x;
12407 if (!window->DC.GroupStack.empty())
12408 x1 += window->DC.IndentX;
12409
12410 const ImRect bb(ImVec2(x1, window->DC.CursorPos.y), ImVec2(x2, window->DC.CursorPos.y + 1.0f));
12411 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.
12412 if (!ItemAdd(bb, 0))
12413 {
12414 if (window->DC.ColumnsSet)
12415 PushColumnClipRect();
12416 return;
12417 }
12418
12419 window->DrawList->AddLine(bb.Min, ImVec2(bb.Max.x, bb.Min.y), GetColorU32(ImGuiCol_Separator));
12420
12421 if (g.LogEnabled)
12422 LogRenderedText(NULL, IM_NEWLINE "--------------------------------");
12423
12424 if (window->DC.ColumnsSet)
12425 {
12426 PushColumnClipRect();
12427 window->DC.ColumnsSet->CellMinY = window->DC.CursorPos.y;
12428 }
12429 }
12430
VerticalSeparator()12431 void ImGui::VerticalSeparator()
12432 {
12433 ImGuiWindow* window = GetCurrentWindow();
12434 if (window->SkipItems)
12435 return;
12436 ImGuiContext& g = *GImGui;
12437
12438 float y1 = window->DC.CursorPos.y;
12439 float y2 = window->DC.CursorPos.y + window->DC.CurrentLineHeight;
12440 const ImRect bb(ImVec2(window->DC.CursorPos.x, y1), ImVec2(window->DC.CursorPos.x + 1.0f, y2));
12441 ItemSize(ImVec2(bb.GetWidth(), 0.0f));
12442 if (!ItemAdd(bb, 0))
12443 return;
12444
12445 window->DrawList->AddLine(ImVec2(bb.Min.x, bb.Min.y), ImVec2(bb.Min.x, bb.Max.y), GetColorU32(ImGuiCol_Separator));
12446 if (g.LogEnabled)
12447 LogText(" |");
12448 }
12449
SplitterBehavior(ImGuiID id,const ImRect & bb,ImGuiAxis axis,float * size1,float * size2,float min_size1,float min_size2,float hover_extend)12450 bool ImGui::SplitterBehavior(ImGuiID id, const ImRect& bb, ImGuiAxis axis, float* size1, float* size2, float min_size1, float min_size2, float hover_extend)
12451 {
12452 ImGuiContext& g = *GImGui;
12453 ImGuiWindow* window = g.CurrentWindow;
12454
12455 const ImGuiItemFlags item_flags_backup = window->DC.ItemFlags;
12456 window->DC.ItemFlags |= ImGuiItemFlags_NoNav | ImGuiItemFlags_NoNavDefaultFocus;
12457 bool item_add = ItemAdd(bb, id);
12458 window->DC.ItemFlags = item_flags_backup;
12459 if (!item_add)
12460 return false;
12461
12462 bool hovered, held;
12463 ImRect bb_interact = bb;
12464 bb_interact.Expand(axis == ImGuiAxis_Y ? ImVec2(0.0f, hover_extend) : ImVec2(hover_extend, 0.0f));
12465 ButtonBehavior(bb_interact, id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap);
12466 if (g.ActiveId != id)
12467 SetItemAllowOverlap();
12468
12469 if (held || (g.HoveredId == id && g.HoveredIdPreviousFrame == id))
12470 SetMouseCursor(axis == ImGuiAxis_Y ? ImGuiMouseCursor_ResizeNS : ImGuiMouseCursor_ResizeEW);
12471
12472 ImRect bb_render = bb;
12473 if (held)
12474 {
12475 ImVec2 mouse_delta_2d = g.IO.MousePos - g.ActiveIdClickOffset - bb_interact.Min;
12476 float mouse_delta = (axis == ImGuiAxis_Y) ? mouse_delta_2d.y : mouse_delta_2d.x;
12477
12478 // Minimum pane size
12479 if (mouse_delta < min_size1 - *size1)
12480 mouse_delta = min_size1 - *size1;
12481 if (mouse_delta > *size2 - min_size2)
12482 mouse_delta = *size2 - min_size2;
12483
12484 // Apply resize
12485 *size1 += mouse_delta;
12486 *size2 -= mouse_delta;
12487 bb_render.Translate((axis == ImGuiAxis_X) ? ImVec2(mouse_delta, 0.0f) : ImVec2(0.0f, mouse_delta));
12488 }
12489
12490 // Render
12491 const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : hovered ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator);
12492 window->DrawList->AddRectFilled(bb_render.Min, bb_render.Max, col, g.Style.FrameRounding);
12493
12494 return held;
12495 }
12496
Spacing()12497 void ImGui::Spacing()
12498 {
12499 ImGuiWindow* window = GetCurrentWindow();
12500 if (window->SkipItems)
12501 return;
12502 ItemSize(ImVec2(0, 0));
12503 }
12504
Dummy(const ImVec2 & size)12505 void ImGui::Dummy(const ImVec2& size)
12506 {
12507 ImGuiWindow* window = GetCurrentWindow();
12508 if (window->SkipItems)
12509 return;
12510
12511 const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
12512 ItemSize(bb);
12513 ItemAdd(bb, 0);
12514 }
12515
IsRectVisible(const ImVec2 & size)12516 bool ImGui::IsRectVisible(const ImVec2& size)
12517 {
12518 ImGuiWindow* window = GetCurrentWindowRead();
12519 return window->ClipRect.Overlaps(ImRect(window->DC.CursorPos, window->DC.CursorPos + size));
12520 }
12521
IsRectVisible(const ImVec2 & rect_min,const ImVec2 & rect_max)12522 bool ImGui::IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max)
12523 {
12524 ImGuiWindow* window = GetCurrentWindowRead();
12525 return window->ClipRect.Overlaps(ImRect(rect_min, rect_max));
12526 }
12527
12528 // Lock horizontal starting position + capture group bounding box into one "item" (so you can use IsItemHovered() or layout primitives such as SameLine() on whole group, etc.)
BeginGroup()12529 void ImGui::BeginGroup()
12530 {
12531 ImGuiWindow* window = GetCurrentWindow();
12532
12533 window->DC.GroupStack.resize(window->DC.GroupStack.Size + 1);
12534 ImGuiGroupData& group_data = window->DC.GroupStack.back();
12535 group_data.BackupCursorPos = window->DC.CursorPos;
12536 group_data.BackupCursorMaxPos = window->DC.CursorMaxPos;
12537 group_data.BackupIndentX = window->DC.IndentX;
12538 group_data.BackupGroupOffsetX = window->DC.GroupOffsetX;
12539 group_data.BackupCurrentLineHeight = window->DC.CurrentLineHeight;
12540 group_data.BackupCurrentLineTextBaseOffset = window->DC.CurrentLineTextBaseOffset;
12541 group_data.BackupLogLinePosY = window->DC.LogLinePosY;
12542 group_data.BackupActiveIdIsAlive = GImGui->ActiveIdIsAlive;
12543 group_data.AdvanceCursor = true;
12544
12545 window->DC.GroupOffsetX = window->DC.CursorPos.x - window->Pos.x - window->DC.ColumnsOffsetX;
12546 window->DC.IndentX = window->DC.GroupOffsetX;
12547 window->DC.CursorMaxPos = window->DC.CursorPos;
12548 window->DC.CurrentLineHeight = 0.0f;
12549 window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f;
12550 }
12551
EndGroup()12552 void ImGui::EndGroup()
12553 {
12554 ImGuiContext& g = *GImGui;
12555 ImGuiWindow* window = GetCurrentWindow();
12556
12557 IM_ASSERT(!window->DC.GroupStack.empty()); // Mismatched BeginGroup()/EndGroup() calls
12558
12559 ImGuiGroupData& group_data = window->DC.GroupStack.back();
12560
12561 ImRect group_bb(group_data.BackupCursorPos, window->DC.CursorMaxPos);
12562 group_bb.Max = ImMax(group_bb.Min, group_bb.Max);
12563
12564 window->DC.CursorPos = group_data.BackupCursorPos;
12565 window->DC.CursorMaxPos = ImMax(group_data.BackupCursorMaxPos, window->DC.CursorMaxPos);
12566 window->DC.CurrentLineHeight = group_data.BackupCurrentLineHeight;
12567 window->DC.CurrentLineTextBaseOffset = group_data.BackupCurrentLineTextBaseOffset;
12568 window->DC.IndentX = group_data.BackupIndentX;
12569 window->DC.GroupOffsetX = group_data.BackupGroupOffsetX;
12570 window->DC.LogLinePosY = window->DC.CursorPos.y - 9999.0f;
12571
12572 if (group_data.AdvanceCursor)
12573 {
12574 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.
12575 ItemSize(group_bb.GetSize(), group_data.BackupCurrentLineTextBaseOffset);
12576 ItemAdd(group_bb, 0);
12577 }
12578
12579 // If the current ActiveId was declared within the boundary of our group, we copy it to LastItemId so IsItemActive() will be functional on the entire group.
12580 // It would be be neater if we replaced window.DC.LastItemId by e.g. 'bool LastItemIsActive', but if you search for LastItemId you'll notice it is only used in that context.
12581 const bool active_id_within_group = (!group_data.BackupActiveIdIsAlive && g.ActiveIdIsAlive && g.ActiveId && g.ActiveIdWindow->RootWindow == window->RootWindow);
12582 if (active_id_within_group)
12583 window->DC.LastItemId = g.ActiveId;
12584 window->DC.LastItemRect = group_bb;
12585
12586 window->DC.GroupStack.pop_back();
12587
12588 //window->DrawList->AddRect(group_bb.Min, group_bb.Max, IM_COL32(255,0,255,255)); // [Debug]
12589 }
12590
12591 // Gets back to previous line and continue with horizontal layout
12592 // pos_x == 0 : follow right after previous item
12593 // pos_x != 0 : align to specified x position (relative to window/group left)
12594 // spacing_w < 0 : use default spacing if pos_x == 0, no spacing if pos_x != 0
12595 // spacing_w >= 0 : enforce spacing amount
SameLine(float pos_x,float spacing_w)12596 void ImGui::SameLine(float pos_x, float spacing_w)
12597 {
12598 ImGuiWindow* window = GetCurrentWindow();
12599 if (window->SkipItems)
12600 return;
12601
12602 ImGuiContext& g = *GImGui;
12603 if (pos_x != 0.0f)
12604 {
12605 if (spacing_w < 0.0f) spacing_w = 0.0f;
12606 window->DC.CursorPos.x = window->Pos.x - window->Scroll.x + pos_x + spacing_w + window->DC.GroupOffsetX + window->DC.ColumnsOffsetX;
12607 window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y;
12608 }
12609 else
12610 {
12611 if (spacing_w < 0.0f) spacing_w = g.Style.ItemSpacing.x;
12612 window->DC.CursorPos.x = window->DC.CursorPosPrevLine.x + spacing_w;
12613 window->DC.CursorPos.y = window->DC.CursorPosPrevLine.y;
12614 }
12615 window->DC.CurrentLineHeight = window->DC.PrevLineHeight;
12616 window->DC.CurrentLineTextBaseOffset = window->DC.PrevLineTextBaseOffset;
12617 }
12618
NewLine()12619 void ImGui::NewLine()
12620 {
12621 ImGuiWindow* window = GetCurrentWindow();
12622 if (window->SkipItems)
12623 return;
12624
12625 ImGuiContext& g = *GImGui;
12626 const ImGuiLayoutType backup_layout_type = window->DC.LayoutType;
12627 window->DC.LayoutType = ImGuiLayoutType_Vertical;
12628 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.
12629 ItemSize(ImVec2(0, 0));
12630 else
12631 ItemSize(ImVec2(0.0f, g.FontSize));
12632 window->DC.LayoutType = backup_layout_type;
12633 }
12634
NextColumn()12635 void ImGui::NextColumn()
12636 {
12637 ImGuiWindow* window = GetCurrentWindow();
12638 if (window->SkipItems || window->DC.ColumnsSet == NULL)
12639 return;
12640
12641 ImGuiContext& g = *GImGui;
12642 PopItemWidth();
12643 PopClipRect();
12644
12645 ImGuiColumnsSet* columns = window->DC.ColumnsSet;
12646 columns->CellMaxY = ImMax(columns->CellMaxY, window->DC.CursorPos.y);
12647 if (++columns->Current < columns->Count)
12648 {
12649 // Columns 1+ cancel out IndentX
12650 window->DC.ColumnsOffsetX = GetColumnOffset(columns->Current) - window->DC.IndentX + g.Style.ItemSpacing.x;
12651 window->DrawList->ChannelsSetCurrent(columns->Current);
12652 }
12653 else
12654 {
12655 window->DC.ColumnsOffsetX = 0.0f;
12656 window->DrawList->ChannelsSetCurrent(0);
12657 columns->Current = 0;
12658 columns->CellMinY = columns->CellMaxY;
12659 }
12660 window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX);
12661 window->DC.CursorPos.y = columns->CellMinY;
12662 window->DC.CurrentLineHeight = 0.0f;
12663 window->DC.CurrentLineTextBaseOffset = 0.0f;
12664
12665 PushColumnClipRect();
12666 PushItemWidth(GetColumnWidth() * 0.65f); // FIXME: Move on columns setup
12667 }
12668
GetColumnIndex()12669 int ImGui::GetColumnIndex()
12670 {
12671 ImGuiWindow* window = GetCurrentWindowRead();
12672 return window->DC.ColumnsSet ? window->DC.ColumnsSet->Current : 0;
12673 }
12674
GetColumnsCount()12675 int ImGui::GetColumnsCount()
12676 {
12677 ImGuiWindow* window = GetCurrentWindowRead();
12678 return window->DC.ColumnsSet ? window->DC.ColumnsSet->Count : 1;
12679 }
12680
OffsetNormToPixels(const ImGuiColumnsSet * columns,float offset_norm)12681 static float OffsetNormToPixels(const ImGuiColumnsSet* columns, float offset_norm)
12682 {
12683 return offset_norm * (columns->MaxX - columns->MinX);
12684 }
12685
PixelsToOffsetNorm(const ImGuiColumnsSet * columns,float offset)12686 static float PixelsToOffsetNorm(const ImGuiColumnsSet* columns, float offset)
12687 {
12688 return offset / (columns->MaxX - columns->MinX);
12689 }
12690
GetColumnsRectHalfWidth()12691 static inline float GetColumnsRectHalfWidth() { return 4.0f; }
12692
GetDraggedColumnOffset(ImGuiColumnsSet * columns,int column_index)12693 static float GetDraggedColumnOffset(ImGuiColumnsSet* columns, int column_index)
12694 {
12695 // Active (dragged) column always follow mouse. The reason we need this is that dragging a column to the right edge of an auto-resizing
12696 // window creates a feedback loop because we store normalized positions. So while dragging we enforce absolute positioning.
12697 ImGuiContext& g = *GImGui;
12698 ImGuiWindow* window = g.CurrentWindow;
12699 IM_ASSERT(column_index > 0); // We cannot drag column 0. If you get this assert you may have a conflict between the ID of your columns and another widgets.
12700 IM_ASSERT(g.ActiveId == columns->ID + ImGuiID(column_index));
12701
12702 float x = g.IO.MousePos.x - g.ActiveIdClickOffset.x + GetColumnsRectHalfWidth() - window->Pos.x;
12703 x = ImMax(x, ImGui::GetColumnOffset(column_index - 1) + g.Style.ColumnsMinSpacing);
12704 if ((columns->Flags & ImGuiColumnsFlags_NoPreserveWidths))
12705 x = ImMin(x, ImGui::GetColumnOffset(column_index + 1) - g.Style.ColumnsMinSpacing);
12706
12707 return x;
12708 }
12709
GetColumnOffset(int column_index)12710 float ImGui::GetColumnOffset(int column_index)
12711 {
12712 ImGuiWindow* window = GetCurrentWindowRead();
12713 ImGuiColumnsSet* columns = window->DC.ColumnsSet;
12714 IM_ASSERT(columns != NULL);
12715
12716 if (column_index < 0)
12717 column_index = columns->Current;
12718 IM_ASSERT(column_index < columns->Columns.Size);
12719
12720 /*
12721 if (g.ActiveId)
12722 {
12723 ImGuiContext& g = *GImGui;
12724 const ImGuiID column_id = columns->ColumnsSetId + ImGuiID(column_index);
12725 if (g.ActiveId == column_id)
12726 return GetDraggedColumnOffset(columns, column_index);
12727 }
12728 */
12729
12730 const float t = columns->Columns[column_index].OffsetNorm;
12731 const float x_offset = ImLerp(columns->MinX, columns->MaxX, t);
12732 return x_offset;
12733 }
12734
GetColumnWidthEx(ImGuiColumnsSet * columns,int column_index,bool before_resize=false)12735 static float GetColumnWidthEx(ImGuiColumnsSet* columns, int column_index, bool before_resize = false)
12736 {
12737 if (column_index < 0)
12738 column_index = columns->Current;
12739
12740 float offset_norm;
12741 if (before_resize)
12742 offset_norm = columns->Columns[column_index + 1].OffsetNormBeforeResize - columns->Columns[column_index].OffsetNormBeforeResize;
12743 else
12744 offset_norm = columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm;
12745 return OffsetNormToPixels(columns, offset_norm);
12746 }
12747
GetColumnWidth(int column_index)12748 float ImGui::GetColumnWidth(int column_index)
12749 {
12750 ImGuiWindow* window = GetCurrentWindowRead();
12751 ImGuiColumnsSet* columns = window->DC.ColumnsSet;
12752 IM_ASSERT(columns != NULL);
12753
12754 if (column_index < 0)
12755 column_index = columns->Current;
12756 return OffsetNormToPixels(columns, columns->Columns[column_index + 1].OffsetNorm - columns->Columns[column_index].OffsetNorm);
12757 }
12758
SetColumnOffset(int column_index,float offset)12759 void ImGui::SetColumnOffset(int column_index, float offset)
12760 {
12761 ImGuiContext& g = *GImGui;
12762 ImGuiWindow* window = g.CurrentWindow;
12763 ImGuiColumnsSet* columns = window->DC.ColumnsSet;
12764 IM_ASSERT(columns != NULL);
12765
12766 if (column_index < 0)
12767 column_index = columns->Current;
12768 IM_ASSERT(column_index < columns->Columns.Size);
12769
12770 const bool preserve_width = !(columns->Flags & ImGuiColumnsFlags_NoPreserveWidths) && (column_index < columns->Count - 1);
12771 const float width = preserve_width ? GetColumnWidthEx(columns, column_index, columns->IsBeingResized) : 0.0f;
12772
12773 if (!(columns->Flags & ImGuiColumnsFlags_NoForceWithinWindow))
12774 offset = ImMin(offset, columns->MaxX - g.Style.ColumnsMinSpacing * (columns->Count - column_index));
12775 columns->Columns[column_index].OffsetNorm = PixelsToOffsetNorm(columns, offset - columns->MinX);
12776
12777 if (preserve_width)
12778 SetColumnOffset(column_index + 1, offset + ImMax(g.Style.ColumnsMinSpacing, width));
12779 }
12780
SetColumnWidth(int column_index,float width)12781 void ImGui::SetColumnWidth(int column_index, float width)
12782 {
12783 ImGuiWindow* window = GetCurrentWindowRead();
12784 ImGuiColumnsSet* columns = window->DC.ColumnsSet;
12785 IM_ASSERT(columns != NULL);
12786
12787 if (column_index < 0)
12788 column_index = columns->Current;
12789 SetColumnOffset(column_index + 1, GetColumnOffset(column_index) + width);
12790 }
12791
PushColumnClipRect(int column_index)12792 void ImGui::PushColumnClipRect(int column_index)
12793 {
12794 ImGuiWindow* window = GetCurrentWindowRead();
12795 ImGuiColumnsSet* columns = window->DC.ColumnsSet;
12796 if (column_index < 0)
12797 column_index = columns->Current;
12798
12799 PushClipRect(columns->Columns[column_index].ClipRect.Min, columns->Columns[column_index].ClipRect.Max, false);
12800 }
12801
FindOrAddColumnsSet(ImGuiWindow * window,ImGuiID id)12802 static ImGuiColumnsSet* FindOrAddColumnsSet(ImGuiWindow* window, ImGuiID id)
12803 {
12804 for (int n = 0; n < window->ColumnsStorage.Size; n++)
12805 if (window->ColumnsStorage[n].ID == id)
12806 return &window->ColumnsStorage[n];
12807
12808 window->ColumnsStorage.push_back(ImGuiColumnsSet());
12809 ImGuiColumnsSet* columns = &window->ColumnsStorage.back();
12810 columns->ID = id;
12811 return columns;
12812 }
12813
BeginColumns(const char * str_id,int columns_count,ImGuiColumnsFlags flags)12814 void ImGui::BeginColumns(const char* str_id, int columns_count, ImGuiColumnsFlags flags)
12815 {
12816 ImGuiContext& g = *GImGui;
12817 ImGuiWindow* window = GetCurrentWindow();
12818
12819 IM_ASSERT(columns_count > 1);
12820 IM_ASSERT(window->DC.ColumnsSet == NULL); // Nested columns are currently not supported
12821
12822 // Differentiate column ID with an arbitrary prefix for cases where users name their columns set the same as another widget.
12823 // In addition, when an identifier isn't explicitly provided we include the number of columns in the hash to make it uniquer.
12824 PushID(0x11223347 + (str_id ? 0 : columns_count));
12825 ImGuiID id = window->GetID(str_id ? str_id : "columns");
12826 PopID();
12827
12828 // Acquire storage for the columns set
12829 ImGuiColumnsSet* columns = FindOrAddColumnsSet(window, id);
12830 IM_ASSERT(columns->ID == id);
12831 columns->Current = 0;
12832 columns->Count = columns_count;
12833 columns->Flags = flags;
12834 window->DC.ColumnsSet = columns;
12835
12836 // Set state for first column
12837 const float content_region_width = (window->SizeContentsExplicit.x != 0.0f) ? (window->SizeContentsExplicit.x) : (window->Size.x - window->ScrollbarSizes.x);
12838 columns->MinX = window->DC.IndentX - g.Style.ItemSpacing.x; // Lock our horizontal range
12839 //column->MaxX = content_region_width - window->Scroll.x - ((window->Flags & ImGuiWindowFlags_NoScrollbar) ? 0 : g.Style.ScrollbarSize);// - window->WindowPadding().x;
12840 columns->MaxX = content_region_width - window->Scroll.x;
12841 columns->StartPosY = window->DC.CursorPos.y;
12842 columns->StartMaxPosX = window->DC.CursorMaxPos.x;
12843 columns->CellMinY = columns->CellMaxY = window->DC.CursorPos.y;
12844 window->DC.ColumnsOffsetX = 0.0f;
12845 window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX);
12846
12847 // Clear data if columns count changed
12848 if (columns->Columns.Size != 0 && columns->Columns.Size != columns_count + 1)
12849 columns->Columns.resize(0);
12850
12851 // Initialize defaults
12852 columns->IsFirstFrame = (columns->Columns.Size == 0);
12853 if (columns->Columns.Size == 0)
12854 {
12855 columns->Columns.reserve(columns_count + 1);
12856 for (int n = 0; n < columns_count + 1; n++)
12857 {
12858 ImGuiColumnData column;
12859 column.OffsetNorm = n / (float)columns_count;
12860 columns->Columns.push_back(column);
12861 }
12862 }
12863
12864 for (int n = 0; n < columns_count + 1; n++)
12865 {
12866 // Clamp position
12867 ImGuiColumnData* column = &columns->Columns[n];
12868 float t = column->OffsetNorm;
12869 if (!(columns->Flags & ImGuiColumnsFlags_NoForceWithinWindow))
12870 t = ImMin(t, PixelsToOffsetNorm(columns, (columns->MaxX - columns->MinX) - g.Style.ColumnsMinSpacing * (columns->Count - n)));
12871 column->OffsetNorm = t;
12872
12873 if (n == columns_count)
12874 continue;
12875
12876 // Compute clipping rectangle
12877 float clip_x1 = ImFloor(0.5f + window->Pos.x + GetColumnOffset(n) - 1.0f);
12878 float clip_x2 = ImFloor(0.5f + window->Pos.x + GetColumnOffset(n + 1) - 1.0f);
12879 column->ClipRect = ImRect(clip_x1, -FLT_MAX, clip_x2, +FLT_MAX);
12880 column->ClipRect.ClipWith(window->ClipRect);
12881 }
12882
12883 window->DrawList->ChannelsSplit(columns->Count);
12884 PushColumnClipRect();
12885 PushItemWidth(GetColumnWidth() * 0.65f);
12886 }
12887
EndColumns()12888 void ImGui::EndColumns()
12889 {
12890 ImGuiContext& g = *GImGui;
12891 ImGuiWindow* window = GetCurrentWindow();
12892 ImGuiColumnsSet* columns = window->DC.ColumnsSet;
12893 IM_ASSERT(columns != NULL);
12894
12895 PopItemWidth();
12896 PopClipRect();
12897 window->DrawList->ChannelsMerge();
12898
12899 columns->CellMaxY = ImMax(columns->CellMaxY, window->DC.CursorPos.y);
12900 window->DC.CursorPos.y = columns->CellMaxY;
12901 if (!(columns->Flags & ImGuiColumnsFlags_GrowParentContentsSize))
12902 window->DC.CursorMaxPos.x = ImMax(columns->StartMaxPosX, columns->MaxX); // Restore cursor max pos, as columns don't grow parent
12903
12904 // Draw columns borders and handle resize
12905 bool is_being_resized = false;
12906 if (!(columns->Flags & ImGuiColumnsFlags_NoBorder) && !window->SkipItems)
12907 {
12908 const float y1 = columns->StartPosY;
12909 const float y2 = window->DC.CursorPos.y;
12910 int dragging_column = -1;
12911 for (int n = 1; n < columns->Count; n++)
12912 {
12913 float x = window->Pos.x + GetColumnOffset(n);
12914 const ImGuiID column_id = columns->ID + ImGuiID(n);
12915 const float column_hw = GetColumnsRectHalfWidth(); // Half-width for interaction
12916 const ImRect column_rect(ImVec2(x - column_hw, y1), ImVec2(x + column_hw, y2));
12917 KeepAliveID(column_id);
12918 if (IsClippedEx(column_rect, column_id, false))
12919 continue;
12920
12921 bool hovered = false, held = false;
12922 if (!(columns->Flags & ImGuiColumnsFlags_NoResize))
12923 {
12924 ButtonBehavior(column_rect, column_id, &hovered, &held);
12925 if (hovered || held)
12926 g.MouseCursor = ImGuiMouseCursor_ResizeEW;
12927 if (held && !(columns->Columns[n].Flags & ImGuiColumnsFlags_NoResize))
12928 dragging_column = n;
12929 }
12930
12931 // Draw column (we clip the Y boundaries CPU side because very long triangles are mishandled by some GPU drivers.)
12932 const ImU32 col = GetColorU32(held ? ImGuiCol_SeparatorActive : hovered ? ImGuiCol_SeparatorHovered : ImGuiCol_Separator);
12933 const float xi = (float)(int)x;
12934 window->DrawList->AddLine(ImVec2(xi, ImMax(y1 + 1.0f, window->ClipRect.Min.y)), ImVec2(xi, ImMin(y2, window->ClipRect.Max.y)), col);
12935 }
12936
12937 // Apply dragging after drawing the column lines, so our rendered lines are in sync with how items were displayed during the frame.
12938 if (dragging_column != -1)
12939 {
12940 if (!columns->IsBeingResized)
12941 for (int n = 0; n < columns->Count + 1; n++)
12942 columns->Columns[n].OffsetNormBeforeResize = columns->Columns[n].OffsetNorm;
12943 columns->IsBeingResized = is_being_resized = true;
12944 float x = GetDraggedColumnOffset(columns, dragging_column);
12945 SetColumnOffset(dragging_column, x);
12946 }
12947 }
12948 columns->IsBeingResized = is_being_resized;
12949
12950 window->DC.ColumnsSet = NULL;
12951 window->DC.ColumnsOffsetX = 0.0f;
12952 window->DC.CursorPos.x = (float)(int)(window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX);
12953 }
12954
12955 // [2017/12: This is currently the only public API, while we are working on making BeginColumns/EndColumns user-facing]
Columns(int columns_count,const char * id,bool border)12956 void ImGui::Columns(int columns_count, const char* id, bool border)
12957 {
12958 ImGuiWindow* window = GetCurrentWindow();
12959 IM_ASSERT(columns_count >= 1);
12960 if (window->DC.ColumnsSet != NULL && window->DC.ColumnsSet->Count != columns_count)
12961 EndColumns();
12962
12963 ImGuiColumnsFlags flags = (border ? 0 : ImGuiColumnsFlags_NoBorder);
12964 //flags |= ImGuiColumnsFlags_NoPreserveWidths; // NB: Legacy behavior
12965 if (columns_count != 1)
12966 BeginColumns(id, columns_count, flags);
12967 }
12968
Indent(float indent_w)12969 void ImGui::Indent(float indent_w)
12970 {
12971 ImGuiContext& g = *GImGui;
12972 ImGuiWindow* window = GetCurrentWindow();
12973 window->DC.IndentX += (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing;
12974 window->DC.CursorPos.x = window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX;
12975 }
12976
Unindent(float indent_w)12977 void ImGui::Unindent(float indent_w)
12978 {
12979 ImGuiContext& g = *GImGui;
12980 ImGuiWindow* window = GetCurrentWindow();
12981 window->DC.IndentX -= (indent_w != 0.0f) ? indent_w : g.Style.IndentSpacing;
12982 window->DC.CursorPos.x = window->Pos.x + window->DC.IndentX + window->DC.ColumnsOffsetX;
12983 }
12984
TreePush(const char * str_id)12985 void ImGui::TreePush(const char* str_id)
12986 {
12987 ImGuiWindow* window = GetCurrentWindow();
12988 Indent();
12989 window->DC.TreeDepth++;
12990 PushID(str_id ? str_id : "#TreePush");
12991 }
12992
TreePush(const void * ptr_id)12993 void ImGui::TreePush(const void* ptr_id)
12994 {
12995 ImGuiWindow* window = GetCurrentWindow();
12996 Indent();
12997 window->DC.TreeDepth++;
12998 PushID(ptr_id ? ptr_id : (const void*)"#TreePush");
12999 }
13000
TreePushRawID(ImGuiID id)13001 void ImGui::TreePushRawID(ImGuiID id)
13002 {
13003 ImGuiWindow* window = GetCurrentWindow();
13004 Indent();
13005 window->DC.TreeDepth++;
13006 window->IDStack.push_back(id);
13007 }
13008
TreePop()13009 void ImGui::TreePop()
13010 {
13011 ImGuiContext& g = *GImGui;
13012 ImGuiWindow* window = g.CurrentWindow;
13013 Unindent();
13014
13015 window->DC.TreeDepth--;
13016 if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())
13017 if (g.NavIdIsAlive && (window->DC.TreeDepthMayCloseOnPop & (1 << window->DC.TreeDepth)))
13018 {
13019 SetNavID(window->IDStack.back(), g.NavLayer);
13020 NavMoveRequestCancel();
13021 }
13022 window->DC.TreeDepthMayCloseOnPop &= (1 << window->DC.TreeDepth) - 1;
13023
13024 PopID();
13025 }
13026
Value(const char * prefix,bool b)13027 void ImGui::Value(const char* prefix, bool b)
13028 {
13029 Text("%s: %s", prefix, (b ? "true" : "false"));
13030 }
13031
Value(const char * prefix,int v)13032 void ImGui::Value(const char* prefix, int v)
13033 {
13034 Text("%s: %d", prefix, v);
13035 }
13036
Value(const char * prefix,unsigned int v)13037 void ImGui::Value(const char* prefix, unsigned int v)
13038 {
13039 Text("%s: %d", prefix, v);
13040 }
13041
Value(const char * prefix,float v,const char * float_format)13042 void ImGui::Value(const char* prefix, float v, const char* float_format)
13043 {
13044 if (float_format)
13045 {
13046 char fmt[64];
13047 ImFormatString(fmt, IM_ARRAYSIZE(fmt), "%%s: %s", float_format);
13048 Text(fmt, prefix, v);
13049 }
13050 else
13051 {
13052 Text("%s: %.3f", prefix, v);
13053 }
13054 }
13055
13056 //-----------------------------------------------------------------------------
13057 // DRAG AND DROP
13058 //-----------------------------------------------------------------------------
13059
ClearDragDrop()13060 void ImGui::ClearDragDrop()
13061 {
13062 ImGuiContext& g = *GImGui;
13063 g.DragDropActive = false;
13064 g.DragDropPayload.Clear();
13065 g.DragDropAcceptIdCurr = g.DragDropAcceptIdPrev = 0;
13066 g.DragDropAcceptIdCurrRectSurface = FLT_MAX;
13067 g.DragDropAcceptFrameCount = -1;
13068 }
13069
13070 // Call when current ID is active.
13071 // When this returns true you need to: a) call SetDragDropPayload() exactly once, b) you may render the payload visual/description, c) call EndDragDropSource()
BeginDragDropSource(ImGuiDragDropFlags flags)13072 bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags)
13073 {
13074 ImGuiContext& g = *GImGui;
13075 ImGuiWindow* window = g.CurrentWindow;
13076
13077 bool source_drag_active = false;
13078 ImGuiID source_id = 0;
13079 ImGuiID source_parent_id = 0;
13080 int mouse_button = 0;
13081 if (!(flags & ImGuiDragDropFlags_SourceExtern))
13082 {
13083 source_id = window->DC.LastItemId;
13084 if (source_id != 0 && g.ActiveId != source_id) // Early out for most common case
13085 return false;
13086 if (g.IO.MouseDown[mouse_button] == false)
13087 return false;
13088
13089 if (source_id == 0)
13090 {
13091 // If you want to use BeginDragDropSource() on an item with no unique identifier for interaction, such as Text() or Image(), you need to:
13092 // A) Read the explanation below, B) Use the ImGuiDragDropFlags_SourceAllowNullID flag, C) Swallow your programmer pride.
13093 if (!(flags & ImGuiDragDropFlags_SourceAllowNullID))
13094 {
13095 IM_ASSERT(0);
13096 return false;
13097 }
13098
13099 // Magic fallback (=somehow reprehensible) to handle items with no assigned ID, e.g. Text(), Image()
13100 // We build a throwaway ID based on current ID stack + relative AABB of items in window.
13101 // THE IDENTIFIER WON'T SURVIVE ANY REPOSITIONING OF THE WIDGET, so if your widget moves your dragging operation will be canceled.
13102 // We don't need to maintain/call ClearActiveID() as releasing the button will early out this function and trigger !ActiveIdIsAlive.
13103 bool is_hovered = (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect) != 0;
13104 if (!is_hovered && (g.ActiveId == 0 || g.ActiveIdWindow != window))
13105 return false;
13106 source_id = window->DC.LastItemId = window->GetIDFromRectangle(window->DC.LastItemRect);
13107 if (is_hovered)
13108 SetHoveredID(source_id);
13109 if (is_hovered && g.IO.MouseClicked[mouse_button])
13110 {
13111 SetActiveID(source_id, window);
13112 FocusWindow(window);
13113 }
13114 if (g.ActiveId == source_id) // Allow the underlying widget to display/return hovered during the mouse release frame, else we would get a flicker.
13115 g.ActiveIdAllowOverlap = is_hovered;
13116 }
13117 if (g.ActiveId != source_id)
13118 return false;
13119 source_parent_id = window->IDStack.back();
13120 source_drag_active = IsMouseDragging(mouse_button);
13121 }
13122 else
13123 {
13124 window = NULL;
13125 source_id = ImHash("#SourceExtern", 0);
13126 source_drag_active = true;
13127 }
13128
13129 if (source_drag_active)
13130 {
13131 if (!g.DragDropActive)
13132 {
13133 IM_ASSERT(source_id != 0);
13134 ClearDragDrop();
13135 ImGuiPayload& payload = g.DragDropPayload;
13136 payload.SourceId = source_id;
13137 payload.SourceParentId = source_parent_id;
13138 g.DragDropActive = true;
13139 g.DragDropSourceFlags = flags;
13140 g.DragDropMouseButton = mouse_button;
13141 }
13142
13143 if (!(flags & ImGuiDragDropFlags_SourceNoPreviewTooltip))
13144 {
13145 // FIXME-DRAG
13146 //SetNextWindowPos(g.IO.MousePos - g.ActiveIdClickOffset - g.Style.WindowPadding);
13147 //PushStyleVar(ImGuiStyleVar_Alpha, g.Style.Alpha * 0.60f); // This is better but e.g ColorButton with checkboard has issue with transparent colors :(
13148 SetNextWindowPos(g.IO.MousePos);
13149 PushStyleColor(ImGuiCol_PopupBg, GetStyleColorVec4(ImGuiCol_PopupBg) * ImVec4(1.0f, 1.0f, 1.0f, 0.6f));
13150 BeginTooltip();
13151 }
13152
13153 if (!(flags & ImGuiDragDropFlags_SourceNoDisableHover) && !(flags & ImGuiDragDropFlags_SourceExtern))
13154 window->DC.LastItemStatusFlags &= ~ImGuiItemStatusFlags_HoveredRect;
13155
13156 return true;
13157 }
13158 return false;
13159 }
13160
EndDragDropSource()13161 void ImGui::EndDragDropSource()
13162 {
13163 ImGuiContext& g = *GImGui;
13164 IM_ASSERT(g.DragDropActive);
13165
13166 if (!(g.DragDropSourceFlags & ImGuiDragDropFlags_SourceNoPreviewTooltip))
13167 {
13168 EndTooltip();
13169 PopStyleColor();
13170 //PopStyleVar();
13171 }
13172
13173 // Discard the drag if have not called SetDragDropPayload()
13174 if (g.DragDropPayload.DataFrameCount == -1)
13175 ClearDragDrop();
13176 }
13177
13178 // Use 'cond' to choose to submit payload on drag start or every frame
SetDragDropPayload(const char * type,const void * data,size_t data_size,ImGuiCond cond)13179 bool ImGui::SetDragDropPayload(const char* type, const void* data, size_t data_size, ImGuiCond cond)
13180 {
13181 ImGuiContext& g = *GImGui;
13182 ImGuiPayload& payload = g.DragDropPayload;
13183 if (cond == 0)
13184 cond = ImGuiCond_Always;
13185
13186 IM_ASSERT(type != NULL);
13187 IM_ASSERT(strlen(type) < IM_ARRAYSIZE(payload.DataType) && "Payload type can be at most 12 characters long");
13188 IM_ASSERT((data != NULL && data_size > 0) || (data == NULL && data_size == 0));
13189 IM_ASSERT(cond == ImGuiCond_Always || cond == ImGuiCond_Once);
13190 IM_ASSERT(payload.SourceId != 0); // Not called between BeginDragDropSource() and EndDragDropSource()
13191
13192 if (cond == ImGuiCond_Always || payload.DataFrameCount == -1)
13193 {
13194 // Copy payload
13195 ImStrncpy(payload.DataType, type, IM_ARRAYSIZE(payload.DataType));
13196 g.DragDropPayloadBufHeap.resize(0);
13197 if (data_size > sizeof(g.DragDropPayloadBufLocal))
13198 {
13199 // Store in heap
13200 g.DragDropPayloadBufHeap.resize((int)data_size);
13201 payload.Data = g.DragDropPayloadBufHeap.Data;
13202 memcpy((void*)payload.Data, data, data_size);
13203 }
13204 else if (data_size > 0)
13205 {
13206 // Store locally
13207 memset(&g.DragDropPayloadBufLocal, 0, sizeof(g.DragDropPayloadBufLocal));
13208 payload.Data = g.DragDropPayloadBufLocal;
13209 memcpy((void*)payload.Data, data, data_size);
13210 }
13211 else
13212 {
13213 payload.Data = NULL;
13214 }
13215 payload.DataSize = (int)data_size;
13216 }
13217 payload.DataFrameCount = g.FrameCount;
13218
13219 return (g.DragDropAcceptFrameCount == g.FrameCount) || (g.DragDropAcceptFrameCount == g.FrameCount - 1);
13220 }
13221
BeginDragDropTargetCustom(const ImRect & bb,ImGuiID id)13222 bool ImGui::BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id)
13223 {
13224 ImGuiContext& g = *GImGui;
13225 if (!g.DragDropActive)
13226 return false;
13227
13228 ImGuiWindow* window = g.CurrentWindow;
13229 if (g.HoveredWindow == NULL || window->RootWindow != g.HoveredWindow->RootWindow)
13230 return false;
13231 IM_ASSERT(id != 0);
13232 if (!IsMouseHoveringRect(bb.Min, bb.Max) || (id == g.DragDropPayload.SourceId))
13233 return false;
13234
13235 g.DragDropTargetRect = bb;
13236 g.DragDropTargetId = id;
13237 return true;
13238 }
13239
13240 // We don't use BeginDragDropTargetCustom() and duplicate its code because:
13241 // 1) we use LastItemRectHoveredRect which handles items that pushes a temporarily clip rectangle in their code. Calling BeginDragDropTargetCustom(LastItemRect) would not handle them.
13242 // 2) and it's faster. as this code may be very frequently called, we want to early out as fast as we can.
13243 // Also note how the HoveredWindow test is positioned differently in both functions (in both functions we optimize for the cheapest early out case)
BeginDragDropTarget()13244 bool ImGui::BeginDragDropTarget()
13245 {
13246 ImGuiContext& g = *GImGui;
13247 if (!g.DragDropActive)
13248 return false;
13249
13250 ImGuiWindow* window = g.CurrentWindow;
13251 if (!(window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HoveredRect))
13252 return false;
13253 if (g.HoveredWindow == NULL || window->RootWindow != g.HoveredWindow->RootWindow)
13254 return false;
13255
13256 const ImRect& display_rect = (window->DC.LastItemStatusFlags & ImGuiItemStatusFlags_HasDisplayRect) ? window->DC.LastItemDisplayRect : window->DC.LastItemRect;
13257 ImGuiID id = window->DC.LastItemId;
13258 if (id == 0)
13259 id = window->GetIDFromRectangle(display_rect);
13260 if (g.DragDropPayload.SourceId == id)
13261 return false;
13262
13263 g.DragDropTargetRect = display_rect;
13264 g.DragDropTargetId = id;
13265 return true;
13266 }
13267
IsDragDropPayloadBeingAccepted()13268 bool ImGui::IsDragDropPayloadBeingAccepted()
13269 {
13270 ImGuiContext& g = *GImGui;
13271 return g.DragDropActive && g.DragDropAcceptIdPrev != 0;
13272 }
13273
AcceptDragDropPayload(const char * type,ImGuiDragDropFlags flags)13274 const ImGuiPayload* ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDropFlags flags)
13275 {
13276 ImGuiContext& g = *GImGui;
13277 ImGuiWindow* window = g.CurrentWindow;
13278 ImGuiPayload& payload = g.DragDropPayload;
13279 IM_ASSERT(g.DragDropActive); // Not called between BeginDragDropTarget() and EndDragDropTarget() ?
13280 IM_ASSERT(payload.DataFrameCount != -1); // Forgot to call EndDragDropTarget() ?
13281 if (type != NULL && !payload.IsDataType(type))
13282 return NULL;
13283
13284 // Accept smallest drag target bounding box, this allows us to nest drag targets conveniently without ordering constraints.
13285 // NB: We currently accept NULL id as target. However, overlapping targets requires a unique ID to function!
13286 const bool was_accepted_previously = (g.DragDropAcceptIdPrev == g.DragDropTargetId);
13287 ImRect r = g.DragDropTargetRect;
13288 float r_surface = r.GetWidth() * r.GetHeight();
13289 if (r_surface < g.DragDropAcceptIdCurrRectSurface)
13290 {
13291 g.DragDropAcceptIdCurr = g.DragDropTargetId;
13292 g.DragDropAcceptIdCurrRectSurface = r_surface;
13293 }
13294
13295 // Render default drop visuals
13296 payload.Preview = was_accepted_previously;
13297 flags |= (g.DragDropSourceFlags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect); // Source can also inhibit the preview (useful for external sources that lives for 1 frame)
13298 if (!(flags & ImGuiDragDropFlags_AcceptNoDrawDefaultRect) && payload.Preview)
13299 {
13300 // FIXME-DRAG: Settle on a proper default visuals for drop target.
13301 r.Expand(3.5f);
13302 bool push_clip_rect = !window->ClipRect.Contains(r);
13303 if (push_clip_rect) window->DrawList->PushClipRectFullScreen();
13304 window->DrawList->AddRect(r.Min, r.Max, GetColorU32(ImGuiCol_DragDropTarget), 0.0f, ~0, 2.0f);
13305 if (push_clip_rect) window->DrawList->PopClipRect();
13306 }
13307
13308 g.DragDropAcceptFrameCount = g.FrameCount;
13309 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()
13310 if (!payload.Delivery && !(flags & ImGuiDragDropFlags_AcceptBeforeDelivery))
13311 return NULL;
13312
13313 return &payload;
13314 }
13315
13316 // We don't really use/need this now, but added it for the sake of consistency and because we might need it later.
EndDragDropTarget()13317 void ImGui::EndDragDropTarget()
13318 {
13319 ImGuiContext& g = *GImGui;
13320 (void)g;
13321 IM_ASSERT(g.DragDropActive);
13322 }
13323
13324 //-----------------------------------------------------------------------------
13325 // PLATFORM DEPENDENT HELPERS
13326 //-----------------------------------------------------------------------------
13327
13328 #if defined(_WIN32) && !defined(_WINDOWS_) && (!defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS) || !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS))
13329 #undef WIN32_LEAN_AND_MEAN
13330 #define WIN32_LEAN_AND_MEAN
13331 #ifndef __MINGW32__
13332 #include <Windows.h>
13333 #else
13334 #include <windows.h>
13335 #endif
13336 #endif
13337
13338 // Win32 API clipboard implementation
13339 #if defined(_WIN32) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS)
13340
13341 #ifdef _MSC_VER
13342 #pragma comment(lib, "user32")
13343 #endif
13344
GetClipboardTextFn_DefaultImpl(void *)13345 static const char* GetClipboardTextFn_DefaultImpl(void*)
13346 {
13347 static ImVector<char> buf_local;
13348 buf_local.clear();
13349 if (!OpenClipboard(NULL))
13350 return NULL;
13351 HANDLE wbuf_handle = GetClipboardData(CF_UNICODETEXT);
13352 if (wbuf_handle == NULL)
13353 {
13354 CloseClipboard();
13355 return NULL;
13356 }
13357 if (ImWchar* wbuf_global = (ImWchar*)GlobalLock(wbuf_handle))
13358 {
13359 int buf_len = ImTextCountUtf8BytesFromStr(wbuf_global, NULL) + 1;
13360 buf_local.resize(buf_len);
13361 ImTextStrToUtf8(buf_local.Data, buf_len, wbuf_global, NULL);
13362 }
13363 GlobalUnlock(wbuf_handle);
13364 CloseClipboard();
13365 return buf_local.Data;
13366 }
13367
SetClipboardTextFn_DefaultImpl(void *,const char * text)13368 static void SetClipboardTextFn_DefaultImpl(void*, const char* text)
13369 {
13370 if (!OpenClipboard(NULL))
13371 return;
13372 const int wbuf_length = ImTextCountCharsFromUtf8(text, NULL) + 1;
13373 HGLOBAL wbuf_handle = GlobalAlloc(GMEM_MOVEABLE, (SIZE_T)wbuf_length * sizeof(ImWchar));
13374 if (wbuf_handle == NULL)
13375 {
13376 CloseClipboard();
13377 return;
13378 }
13379 ImWchar* wbuf_global = (ImWchar*)GlobalLock(wbuf_handle);
13380 ImTextStrFromUtf8(wbuf_global, wbuf_length, text, NULL);
13381 GlobalUnlock(wbuf_handle);
13382 EmptyClipboard();
13383 SetClipboardData(CF_UNICODETEXT, wbuf_handle);
13384 CloseClipboard();
13385 }
13386
13387 #else
13388
13389 // Local ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers
GetClipboardTextFn_DefaultImpl(void *)13390 static const char* GetClipboardTextFn_DefaultImpl(void*)
13391 {
13392 ImGuiContext& g = *GImGui;
13393 return g.PrivateClipboard.empty() ? NULL : g.PrivateClipboard.begin();
13394 }
13395
13396 // Local ImGui-only clipboard implementation, if user hasn't defined better clipboard handlers
SetClipboardTextFn_DefaultImpl(void *,const char * text)13397 static void SetClipboardTextFn_DefaultImpl(void*, const char* text)
13398 {
13399 ImGuiContext& g = *GImGui;
13400 g.PrivateClipboard.clear();
13401 const char* text_end = text + strlen(text);
13402 g.PrivateClipboard.resize((int)(text_end - text) + 1);
13403 memcpy(&g.PrivateClipboard[0], text, (size_t)(text_end - text));
13404 g.PrivateClipboard[(int)(text_end - text)] = 0;
13405 }
13406
13407 #endif
13408
13409 // Win32 API IME support (for Asian languages, etc.)
13410 #if defined(_WIN32) && !defined(__GNUC__) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS)
13411
13412 #include <imm.h>
13413 #ifdef _MSC_VER
13414 #pragma comment(lib, "imm32")
13415 #endif
13416
ImeSetInputScreenPosFn_DefaultImpl(int x,int y)13417 static void ImeSetInputScreenPosFn_DefaultImpl(int x, int y)
13418 {
13419 // Notify OS Input Method Editor of text input position
13420 if (HWND hwnd = (HWND)GImGui->IO.ImeWindowHandle)
13421 if (HIMC himc = ImmGetContext(hwnd))
13422 {
13423 COMPOSITIONFORM cf;
13424 cf.ptCurrentPos.x = x;
13425 cf.ptCurrentPos.y = y;
13426 cf.dwStyle = CFS_FORCE_POSITION;
13427 ImmSetCompositionWindow(himc, &cf);
13428 }
13429 }
13430
13431 #else
13432
ImeSetInputScreenPosFn_DefaultImpl(int,int)13433 static void ImeSetInputScreenPosFn_DefaultImpl(int, int)
13434 {
13435 }
13436
13437 #endif
13438
13439 //-----------------------------------------------------------------------------
13440 // HELP
13441 //-----------------------------------------------------------------------------
13442
ShowMetricsWindow(bool * p_open)13443 void ImGui::ShowMetricsWindow(bool* p_open)
13444 {
13445 if (ImGui::Begin("ImGui Metrics", p_open))
13446 {
13447 ImGui::Text("Dear ImGui %s", ImGui::GetVersion());
13448 ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
13449 ImGui::Text("%d vertices, %d indices (%d triangles)", ImGui::GetIO().MetricsRenderVertices, ImGui::GetIO().MetricsRenderIndices, ImGui::GetIO().MetricsRenderIndices / 3);
13450 ImGui::Text("%d allocations", (int)GImAllocatorActiveAllocationsCount);
13451 static bool show_clip_rects = true;
13452 ImGui::Checkbox("Show clipping rectangles when hovering draw commands", &show_clip_rects);
13453 ImGui::Separator();
13454
13455 struct Funcs
13456 {
13457 static void NodeDrawList(ImGuiWindow* window, ImDrawList* draw_list, const char* label)
13458 {
13459 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);
13460 if (draw_list == ImGui::GetWindowDrawList())
13461 {
13462 ImGui::SameLine();
13463 ImGui::TextColored(ImColor(255, 100, 100), "CURRENTLY APPENDING"); // Can't display stats for active draw list! (we don't have the data double-buffered)
13464 if (node_open) ImGui::TreePop();
13465 return;
13466 }
13467
13468 ImDrawList* overlay_draw_list = ImGui::GetOverlayDrawList(); // Render additional visuals into the top-most draw list
13469 if (window && ImGui::IsItemHovered())
13470 overlay_draw_list->AddRect(window->Pos, window->Pos + window->Size, IM_COL32(255, 255, 0, 255));
13471 if (!node_open)
13472 return;
13473
13474 int elem_offset = 0;
13475 for (const ImDrawCmd* pcmd = draw_list->CmdBuffer.begin(); pcmd < draw_list->CmdBuffer.end(); elem_offset += pcmd->ElemCount, pcmd++)
13476 {
13477 if (pcmd->UserCallback == NULL && pcmd->ElemCount == 0)
13478 continue;
13479 if (pcmd->UserCallback)
13480 {
13481 ImGui::BulletText("Callback %p, user_data %p", pcmd->UserCallback, pcmd->UserCallbackData);
13482 continue;
13483 }
13484 ImDrawIdx* idx_buffer = (draw_list->IdxBuffer.Size > 0) ? draw_list->IdxBuffer.Data : NULL;
13485 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);
13486 if (show_clip_rects && ImGui::IsItemHovered())
13487 {
13488 ImRect clip_rect = pcmd->ClipRect;
13489 ImRect vtxs_rect;
13490 for (int i = elem_offset; i < elem_offset + (int)pcmd->ElemCount; i++)
13491 vtxs_rect.Add(draw_list->VtxBuffer[idx_buffer ? idx_buffer[i] : i].pos);
13492 clip_rect.Floor();
13493 overlay_draw_list->AddRect(clip_rect.Min, clip_rect.Max, IM_COL32(255, 255, 0, 255));
13494 vtxs_rect.Floor();
13495 overlay_draw_list->AddRect(vtxs_rect.Min, vtxs_rect.Max, IM_COL32(255, 0, 255, 255));
13496 }
13497 if (!pcmd_node_open)
13498 continue;
13499
13500 // Display individual triangles/vertices. Hover on to get the corresponding triangle highlighted.
13501 ImGuiListClipper clipper(pcmd->ElemCount / 3); // Manually coarse clip our print out of individual vertices to save CPU, only items that may be visible.
13502 while (clipper.Step())
13503 for (int prim = clipper.DisplayStart, vtx_i = elem_offset + clipper.DisplayStart * 3; prim < clipper.DisplayEnd; prim++)
13504 {
13505 char buf[300];
13506 char *buf_p = buf, *buf_end = buf + IM_ARRAYSIZE(buf);
13507 ImVec2 triangles_pos[3];
13508 for (int n = 0; n < 3; n++, vtx_i++)
13509 {
13510 ImDrawVert& v = draw_list->VtxBuffer[idx_buffer ? idx_buffer[vtx_i] : vtx_i];
13511 triangles_pos[n] = v.pos;
13512 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);
13513 }
13514 ImGui::Selectable(buf, false);
13515 if (ImGui::IsItemHovered())
13516 {
13517 ImDrawListFlags backup_flags = overlay_draw_list->Flags;
13518 overlay_draw_list->Flags &= ~ImDrawListFlags_AntiAliasedLines; // Disable AA on triangle outlines at is more readable for very large and thin triangles.
13519 overlay_draw_list->AddPolyline(triangles_pos, 3, IM_COL32(255, 255, 0, 255), true, 1.0f);
13520 overlay_draw_list->Flags = backup_flags;
13521 }
13522 }
13523 ImGui::TreePop();
13524 }
13525 ImGui::TreePop();
13526 }
13527
13528 static void NodeWindows(ImVector<ImGuiWindow*>& windows, const char* label)
13529 {
13530 if (!ImGui::TreeNode(label, "%s (%d)", label, windows.Size))
13531 return;
13532 for (int i = 0; i < windows.Size; i++)
13533 Funcs::NodeWindow(windows[i], "Window");
13534 ImGui::TreePop();
13535 }
13536
13537 static void NodeWindow(ImGuiWindow* window, const char* label)
13538 {
13539 if (!ImGui::TreeNode(window, "%s '%s', %d @ 0x%p", label, window->Name, window->Active || window->WasActive, window))
13540 return;
13541 ImGuiWindowFlags flags = window->Flags;
13542 NodeDrawList(window, window->DrawList, "DrawList");
13543 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);
13544 ImGui::BulletText("Flags: 0x%08X (%s%s%s%s%s%s..)", flags,
13545 (flags & ImGuiWindowFlags_ChildWindow) ? "Child " : "", (flags & ImGuiWindowFlags_Tooltip) ? "Tooltip " : "", (flags & ImGuiWindowFlags_Popup) ? "Popup " : "",
13546 (flags & ImGuiWindowFlags_Modal) ? "Modal " : "", (flags & ImGuiWindowFlags_ChildMenu) ? "ChildMenu " : "", (flags & ImGuiWindowFlags_NoSavedSettings) ? "NoSavedSettings " : "");
13547 ImGui::BulletText("Scroll: (%.2f/%.2f,%.2f/%.2f)", window->Scroll.x, GetScrollMaxX(window), window->Scroll.y, GetScrollMaxY(window));
13548 ImGui::BulletText("Active: %d, WriteAccessed: %d", window->Active, window->WriteAccessed);
13549 ImGui::BulletText("NavLastIds: 0x%08X,0x%08X, NavLayerActiveMask: %X", window->NavLastIds[0], window->NavLastIds[1], window->DC.NavLayerActiveMask);
13550 ImGui::BulletText("NavLastChildNavWindow: %s", window->NavLastChildNavWindow ? window->NavLastChildNavWindow->Name : "NULL");
13551 if (window->NavRectRel[0].IsFinite())
13552 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);
13553 else
13554 ImGui::BulletText("NavRectRel[0]: <None>");
13555 if (window->RootWindow != window) NodeWindow(window->RootWindow, "RootWindow");
13556 if (window->DC.ChildWindows.Size > 0) NodeWindows(window->DC.ChildWindows, "ChildWindows");
13557 ImGui::BulletText("Storage: %d bytes", window->StateStorage.Data.Size * (int)sizeof(ImGuiStorage::Pair));
13558 ImGui::TreePop();
13559 }
13560 };
13561
13562 // Access private state, we are going to display the draw lists from last frame
13563 ImGuiContext& g = *GImGui;
13564 Funcs::NodeWindows(g.Windows, "Windows");
13565 if (ImGui::TreeNode("DrawList", "Active DrawLists (%d)", g.DrawDataBuilder.Layers[0].Size))
13566 {
13567 for (int i = 0; i < g.DrawDataBuilder.Layers[0].Size; i++)
13568 Funcs::NodeDrawList(NULL, g.DrawDataBuilder.Layers[0][i], "DrawList");
13569 ImGui::TreePop();
13570 }
13571 if (ImGui::TreeNode("Popups", "Open Popups Stack (%d)", g.OpenPopupStack.Size))
13572 {
13573 for (int i = 0; i < g.OpenPopupStack.Size; i++)
13574 {
13575 ImGuiWindow* window = g.OpenPopupStack[i].Window;
13576 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" : "");
13577 }
13578 ImGui::TreePop();
13579 }
13580 if (ImGui::TreeNode("Internal state"))
13581 {
13582 const char* input_source_names[] = {"None", "Mouse", "Nav", "NavGamepad", "NavKeyboard"};
13583 IM_ASSERT(IM_ARRAYSIZE(input_source_names) == ImGuiInputSource_Count_);
13584 ImGui::Text("HoveredWindow: '%s'", g.HoveredWindow ? g.HoveredWindow->Name : "NULL");
13585 ImGui::Text("HoveredRootWindow: '%s'", g.HoveredRootWindow ? g.HoveredRootWindow->Name : "NULL");
13586 ImGui::Text("HoveredId: 0x%08X/0x%08X (%.2f sec)", g.HoveredId, g.HoveredIdPreviousFrame, g.HoveredIdTimer); // Data is "in-flight" so depending on when the Metrics window is called we may see current frame information or not
13587 ImGui::Text("ActiveId: 0x%08X/0x%08X (%.2f sec), ActiveIdSource: %s", g.ActiveId, g.ActiveIdPreviousFrame, g.ActiveIdTimer, input_source_names[g.ActiveIdSource]);
13588 ImGui::Text("ActiveIdWindow: '%s'", g.ActiveIdWindow ? g.ActiveIdWindow->Name : "NULL");
13589 ImGui::Text("NavWindow: '%s'", g.NavWindow ? g.NavWindow->Name : "NULL");
13590 ImGui::Text("NavId: 0x%08X, NavLayer: %d", g.NavId, g.NavLayer);
13591 ImGui::Text("NavActive: %d, NavVisible: %d", g.IO.NavActive, g.IO.NavVisible);
13592 ImGui::Text("NavActivateId: 0x%08X, NavInputId: 0x%08X", g.NavActivateId, g.NavInputId);
13593 ImGui::Text("NavDisableHighlight: %d, NavDisableMouseHover: %d", g.NavDisableHighlight, g.NavDisableMouseHover);
13594 ImGui::Text("DragDrop: %d, SourceId = 0x%08X, Payload \"%s\" (%d bytes)", g.DragDropActive, g.DragDropPayload.SourceId, g.DragDropPayload.DataType, g.DragDropPayload.DataSize);
13595 ImGui::TreePop();
13596 }
13597 }
13598 ImGui::End();
13599 }
13600
13601 //-----------------------------------------------------------------------------
13602
13603 // Include imgui_user.inl at the end of imgui.cpp to access private data/functions that aren't exposed.
13604 // 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.
13605 #ifdef IMGUI_INCLUDE_IMGUI_USER_INL
13606 #include "imgui_user.inl"
13607 #endif
13608
13609 //-----------------------------------------------------------------------------
13610